verilog系统任务读写文件$fopen和$fdisplay的使用 $monitor 使用体会
几个简单的系统任务,$readmemb,$readmemh,$fopen,$fdisplay;基本上就可以完成对文件的读写操作。
一、读任务
在verilog语言中有两个系统任务$readmemb,$readmemh可以用来从文件中读取数据到存储器中。这两个任务可以在仿真的任何时刻被执行使用,其使用方法如下:$readmemb
1,$readmemb("<数据文件名(路径地址和文件名)>",<存储器名>);
2, $readmemb("<数据文件名>",<存储器名>,<起始地址(存储器的地址)>);
3, $readmemb("<数据文件名>",<存储器名>,<起始地址>,<结束地址>);
$readmemh
4,$readmemh("<数据文件名(路径地址和文件名)>",<存储器名>);
5, $readmemh("<数据文件名>",<存储器名>,<起始地址(存储器的地址)>);
5, $readmemh("<数据文件名>",<存储器名>,<起始地址>,<结束地址>);
这里需要注意的是:
1,存储器的空间要足够,如设定[7:0] remember[0:15];即是初始化了一个15个字长为8位的存储器;
2,文件中的数据是一次读到寄存器中的,仿真中通常使用数据文件来做信号源。并且读数据总是从文件起始地址读到结束地址结束,存储器容量要足够;
3,文件应为标准数据文件,如果文件只存放数据就默认第一个数据地址为初始地址;若第一个数据设定
@002
11111111 10101011
@004
10xz0011 1100101x
第0,和第1个数据没有写,系统读进存储器时默认为 xxxx_xxxx;
4,文件若只写文件名需要将文件放置在默认文件路径下,可以通过modelsim->file->changedirectory来更改。
二、写任务
这里写任务用$fopen和$fdisplay两个系统任务完成,其中前者用来得到文件句柄,所谓句柄就是用来打开这个文件的读写通道,这里笔者在modelsim环境下试验了一下一旦用$fopen打开文件就会将文件清空。因此要读文件最好用$readmemb任务,写文件再用$fopen,注意最好新建个新文件来写,以免重要数据被清空。
用$fopen取得想要写文件的句柄handle,然后我们desc=handle|1;(目的最低位置1)这样写文件时trancript框中也同样会输出;
用$fdisplay(desc,"display=%b",remember[k]);通过for语句就可以将原文件中的数据写到另一个文件中去了;
例子如下:
module rewr_test();
reg [7:0]mem[0:15];
integer k,handle2,desc;
initial
begin
handle2=$fopen("file2.dat");
$readmemb("file1.dat",mem);
desc=handle2 |1;
$display("handle2=%b desc=%b",handle2,desc);
for(k=0;k<=15;k=k+1)
begin
$fdisplay(desc,"%b",mem[k]);
end
end
endmodule
handle2是文件句柄,打开第一个文件句柄值为32‘h0000_0002;desc最后一位置1,transcript对话框中也有相应输出,打开文件发现file1.dat和file2.dat两文件一致。
1.$fopen打开文件
用法1.$fopen("<文件名>");
用法2.<文件句柄>=$fopen("<文件名>");
注意:用$fopen打开文件会将原来的文件清空,若要读数据就用$readmemb,$readmemh就可以了,这个语句不会清空原来文件中的数据。
用$fopen的情况是为了取得句柄,即文件地址,也就是写文件时用$fdisplay(desc,"display1");时才用。
用法1自然无须多解释,对于用法2,句柄就是任务$fopen返回的多通道描述符,默认为32位,最低位(第0位)默认被设置1,默认开放标准输出通道,即transcript窗口。
module disp;
integer handle1,handle2,handle3;
integer handle1,handle2,handle3;
initial
begin
handle1=$fopen("file1.dat");
handle2=$fopen("file2.dat");
handle3=$fopen("file3.dat");
begin
handle1=$fopen("file1.dat");
handle2=$fopen("file2.dat");
handle3=$fopen("file3.dat");
$display("%h %h %h",handle1,handle2,handle3);
end
endmodule
输出
handle1=32‘h0000_0002
handle2=32'h0000_0004
handle3=32'h0000_0008
即对每一次使用$fopen函数后都打开了一个新的通道,并且返回了一个设置为1的位相对应。默认应该是0001,以上每调用分别设置为0010 ,0100,1000(只考虑最低四位)。
这个句柄对我们非常有用,因为在写文件时会用到。
2.写文件我们用到系统任务$fdisplay,$fwrite.
两者用法相似,前者写完就会自动换行,后者不会换行。
用法:$fdisplay(<文件描述符(句柄,用于确定是写哪一个文件)>,p1,p2(写入内容));
描述符是很有意思的(默认为32位),如上我们知道,文件句柄最多只能有一个1,但是描述符可以是有多个1,哪一个位上有1,就同时在这些位对应的输出文件上写文件。因此可以同时写多个文件。
只考虑后四位情况:
1,0001时,只进行在transcript对话框中的输出,
2,0101时,即对file2文件写又在transcript框输出、
3,1111时,对全部文件写,同时在transcript框输出。
由于每个句柄只有一个位置上是1,因此我们想在哪些文件中同时输出我们就可以用以下语句来写
desc=handleI | handleK | handleM | handleN | 1(一般与1或,最低位置1再transcript框输出。)
一个例子如下:
module disp;
integer handle1,handle2,handle3;
integer desc1,desc2,desc3;
initial
begin
handle1=$fopen("file1.dat");
handle2=$fopen("file2.dat");
handle3=$fopen("file3.dat");
$display("%h %h %h",handle1,handle2,handle3);
desc1=handle1|1;
$fwrite(desc1,"display 1");
$fmoniter(desc1,"display 1");
$fstrobe(desc1,"display 1");
desc2=handle2|handle1|1;
$fdisplay(desc2,"display 2");
desc3=handle3|1;
$fdisplay(32'd15,"display 3");
end
endmodule
integer handle1,handle2,handle3;
integer desc1,desc2,desc3;
initial
begin
handle1=$fopen("file1.dat");
handle2=$fopen("file2.dat");
handle3=$fopen("file3.dat");
$display("%h %h %h",handle1,handle2,handle3);
desc1=handle1|1;
$fwrite(desc1,"display 1");
$fmoniter(desc1,"display 1");
$fstrobe(desc1,"display 1");
desc2=handle2|handle1|1;
$fdisplay(desc2,"display 2");
desc3=handle3|1;
$fdisplay(32'd15,"display 3");
end
endmodule
$monitor系统任务提供了监控和输出参数列表中的表达式和变量值的功能,其参数列表中输出控制格式字符串和输出表列的规则和$display一样,当启动一个带有一个或多个参数的$monitor任务时,每当参数列表中的变量或表达式的值发生变化时整个参数列表中变量或表达式的值都将输出显示。如果同一时刻有两个或多个表达式的值发生变化,只输出一次。 参数可以是$time,用于标明变化时刻。
如$monitor ($time,,"rxd=%b txd=%b",rxd,txd);这里的“,,”用来表示空格参数在输出时显示为空格
$monitor的打开和关闭分别用$monitoron和$monitoroff表示,用来控制任务的启动和停止,使得很容易控制任务何时发生,何时结束。
$monitor用于initial块中,只要不调用$monitoroff,该任务就可以不间断的对所设定信号进行监视。
例子如下(对一个4位加法计数器counter信号进行监控):
`timescale 1ns/1ns
module moni_test();
reg[3:0]counter;
reg clk;
reg rst;
initial
begin
rst=0;
#10 rst=1;
$monitor ($time,,"counter=%d",counter);
$monitoron;
end
initial
begin
clk=0;
forever #5 clk=~clk;
end
always@(negedge rst or posedge clk)
if(!rst)
counter<=4'd0;
else
counter<=counter+1;
endmodule
主要观察transcript对话框,可见每经过10ns,counter就会变化一次,此时参量时间也会输出。
注意1. 仅监测$time是不会每次都输出的,必须是除了时间之外的参量变化才可以做到参量变化就输出
2 .,,符号输出的是一个空格符,输出结果中得到了验证。
几个简单的系统任务,$readmemb,$readmemh,$fopen,$fdisplay;基本上就可以完成对文件的读写操作。
一、读任务
在verilog语言中有两个系统任务$readmemb,$readmemh可以用来从文件中读取数据到存储器中。这两个任务可以在仿真的任何时刻被执行使用,其使用方法如下:$readmemb
1,$readmemb("<数据文件名(路径地址和文件名)>",<存储器名>);
2, $readmemb("<数据文件名>",<存储器名>,<起始地址(存储器的地址)>);
3, $readmemb("<数据文件名>",<存储器名>,<起始地址>,<结束地址>);
$readmemh
4,$readmemh("<数据文件名(路径地址和文件名)>",<存储器名>);
5, $readmemh("<数据文件名>",<存储器名>,<起始地址(存储器的地址)>);
5, $readmemh("<数据文件名>",<存储器名>,<起始地址>,<结束地址>);
这里需要注意的是:
1,存储器的空间要足够,如设定[7:0] remember[0:15];即是初始化了一个15个字长为8位的存储器;
2,文件中的数据是一次读到寄存器中的,仿真中通常使用数据文件来做信号源。并且读数据总是从文件起始地址读到结束地址结束,存储器容量要足够;
3,文件应为标准数据文件,如果文件只存放数据就默认第一个数据地址为初始地址;若第一个数据设定
@002
11111111 10101011
@004
10xz0011 1100101x
第0,和第1个数据没有写,系统读进存储器时默认为 xxxx_xxxx;
4,文件若只写文件名需要将文件放置在默认文件路径下,可以通过modelsim->file->changedirectory来更改。
二、写任务
这里写任务用$fopen和$fdisplay两个系统任务完成,其中前者用来得到文件句柄,所谓句柄就是用来打开这个文件的读写通道,这里笔者在modelsim环境下试验了一下一旦用$fopen打开文件就会将文件清空。因此要读文件最好用$readmemb任务,写文件再用$fopen,注意最好新建个新文件来写,以免重要数据被清空。
用$fopen取得想要写文件的句柄handle,然后我们desc=handle|1;(目的最低位置1)这样写文件时trancript框中也同样会输出;
用$fdisplay(desc,"display=%b",remember[k]);通过for语句就可以将原文件中的数据写到另一个文件中去了;
例子如下:
module rewr_test();
reg [7:0]mem[0:15];
integer k,handle2,desc;
initial
begin
handle2=$fopen("file2.dat");
$readmemb("file1.dat",mem);
desc=handle2 |1;
$display("handle2=%b desc=%b",handle2,desc);
for(k=0;k<=15;k=k+1)
begin
$fdisplay(desc,"%b",mem[k]);
end
end
endmodule
handle2是文件句柄,打开第一个文件句柄值为32‘h0000_0002;desc最后一位置1,transcript对话框中也有相应输出,打开文件发现file1.dat和file2.dat两文件一致。