在Verilog 仿真中, 我们有时需要将文件中的数据,读入到仿真工程中使用。在Verilog 语法中提供$fgetc,$fgets,$fscanf,$fread 等系统函数,帮助开发者将文件中的数据读出,供仿真程序使用。特别是在图像处理,dsp 处理等方面经常会被使用。
1. $fgetc 使用
reg [7:0] <8-bit_reg>; <8-bit_reg> = $fgetc(<file_desc>); file_desc:为打开的文件句柄
从文件中读取一个字符, 每执行一次$fgetc,就从文件中读取一个字符, 文件的指针自动加一。 当读取到文件结束时, $fgetc 返回 -1, 可以通过-1(EOF,end of file,文件末尾) 来定位文件读取结束。
举例,从一个文本文件中读取数据(test.txt)
test.txt文本文件的内容如下:
1234 world hello This is a test file.
仿真工程如下(vivado):
`timescale 1ns / 1ps module sim_top( ); reg stop_flag = 0; localparam FILE_TXT = "../../../test.txt"; //localparam FILE_TXT = "../../../test.bin"; integer fd; integer i; reg [7:0] c; initial begin i = 0; fd = $fopen(FILE_TXT, "r"); if(fd == 0) begin $display("$open file failed") ; $stop; end $display("\n ============= file opened... ============= ") ; c = $fgetc(fd); i = i + 1; while ($signed(c) != -1) begin $write("%c", c) ; #10; c = $fgetc(fd); i = i + 1; end #10; $fclose(fd) ; $display("\n ============= file closed... ============= ") ; #100; $stop; end endmodule
使用$fopen 打开文本文件,使用”r” 参数。 注意这里定义的是reg [7:0] c;因此使用 while ($signed(c) != -1) 来进行比较 ,而不能使用while (c!= -1) 。 因为 8’hff != -1
即255 != -1 这个条件是永远成立的,所以不能判断出文件读取结束。$write 的使用和 $display 系统函数类似, 只是$write 没有额外增加换行操作。
仿真结果如下:(其中紫色部分c[7:0] 以字符形式显示, 橙色部分是以16进制形式显示c[7:0])
图1-1
图1-2
图2
举例,从一个二进制文件中读取数据(test.bin)
图3
在读取二进制文件中, 使用$fgetc 系统函数 得到的结果 可以是 8’h00 — 8’hff 当中的任何一个数值。$fgetc 的返回值是8bit的。 因此在读取二进制正常的读取过程中, 就会出现8’hff 这个值,如果使用 while ($signed(c) != -1) , 就会在读出正常数据8’hff 时,认为文件读取已经结束,但实际上文件读取并没有结束。所以为了避免这种情况的发生,这里提供一种方法来解决,之后的文章还有其他方法可以使用。
reg[15:0] c ;
将c 定义为16 bit (只要大于8bit 既可),这样平时读取的数据只能是c = 16’h00yy ,永远不会出现 16’hffff这种情况, 只有读取文件结束时, 才能读到16’hffff这样的值。这样就可以判断出文件结束了。
当定义 reg [7:0] c; 的仿真程序:
`timescale 1ns / 1ps module sim_top( ); //localparam FILE_TXT = "../../../test.txt"; localparam FILE_TXT = "../../../test.bin"; integer fd; integer i; reg [7:0] c; initial begin i = 0; fd = $fopen(FILE_TXT, "rb"); if(fd == 0) begin $display("$open file failed") ; $stop; end $display("\n ============= file opened... ============= ") ; c = $fgetc(fd); i = i + 1; while ($signed(c) != -1) begin $write("%c", c) ; #10; c = $fgetc(fd); i = i + 1; end #10; $fclose(fd) ; $display("\n ============= file closed... ============= ") ; stop_flag = 1; #100; $stop; end endmodule
使用 fd = $fopen(FILE_TXT, “rb“); 打开二进制文件 test.bin。
仿真结果如下:
图4
从图4的显示结果可以看出,在读取二进制文件时,读到349 个是就结束了。原因就是读取到8’hff 时,认为文件已经结束了, 所以整个程序就退出了。
如图5所示,控制台输出也反应了图4所对应的结果:
图5
修改仿真文件, 将reg [7:0] c; 修改为reg [15:0] c;
仿真结果如下:
图6
从图6中可以看出,正常读取的数据都是16’h00yy, 只有结束时, $fgetc 的结果为16’hffff。 相同的test.bin 文件,在读到690 个数值时, 才真正结束了。
图7控制台输出结果也反应了图6的内容:
图7
2. $fgets 使用
integer <integer>;
reg [8*<#_of_chars>:0] <string_reg>;
<integer> = $fgets(<string_reg>, <file_desc>);
integer: 定义一个整型数值, 用来保存读取文件的当前行有多少个字节。 如果读出的字节为0,表示文件读取结束或者读取错误。(注:文本文件中空行也是能读到一个字节的)。
string_reg: 声明字符串变量,用来保存从文件中读取的数据。 file_desc:为打开的文件句柄
从文件中每次读取一行的数据, 并且将当前行有多少个数据当作$fgets 的返回值,如果返回值为0, 表示文件读取结束或者读取错误。$fgets 主要针对文本文件使用, 对于读取二进制文件,也是可以操作的,但是不能表示明确的行的含义。
定义一个文本文件:
1234 abc k world hello This is a test file.
仿真文件如下:
`timescale 1ns / 1ps module sim_top( ); reg stop_flag = 0; localparam FILE_TXT = "../../../test.txt"; //localparam FILE_TXT = "../../../test.bin"; integer fd; integer char_num; integer i; reg [8*32-1:0] fbuf = 0; //reg [7:0] fbuf[31:0]; initial begin i = 0; char_num = 0; fd = $fopen(FILE_TXT, "r"); if(fd == 0) begin $display("$open file failed") ; $stop; end $display("\n ============= file opened... ============= ") ; char_num = $fgets(fbuf,fd); i = i + 1; while (char_num != 0) begin $write("%s", fbuf) ; #10; char_num = $fgets(fbuf,fd); i = i + 1; end #10; $fclose(fd) ; $display("\n ============= file closed... ============= ") ; stop_flag = 1; #100; $stop; end endmodule
仿真结果如下:
图8
图9
相同的仿真文件在quartus 下仿真:
图10
从仿真结果来看,在仿真文件中使用reg [8*32-1:0] fbuf = 0; 我们这里定义了fbuf 是 32个字节,比文本文件中的每一行长度都大。所以在一次$fgets 可以完整读取一行文本数据,这里我们看到, 只需要7次就可以将文件完整读出。这里我们可以看出, fbuf 在vivado中没有显示,在quartus 下显示了正常的数据。 但在vivado中将fbuf的内容一个一个的打印输出, 发现结果依然是正确的。(这里使用的是vivado 2018.2)
当我们定义reg [15:0] fbuf = 0; 时,每次$fgets 最多只能读取两个字节,所以需要很多次$fgets 才能将这个文本文件读取结束。
仿真结果如下:
图11
图12
这里,我们发现需要$fgets 读取25次,才能将文本文件读取结束。
3. $fscanf 使用
integer <integer>; <integer> = $fscanf(<file_desc>, "<format>", <destination_regs>); integer: 定义一个整型数值,正常读取为1,出错时为0,文件读取结束为 -1。 file_desc:为打开的文件句柄 format: 格式化输出,具体可以参照$display 中的格式化参数。表示以什么样的格式读取文件 destination_regs: 读取文件数据后, 保存在这个目标寄存器中。
按照格式将文件中的数据读到变量中, 格式可以参考$display 中的格式化内容。如果遇到空格或者换行,表示一次读取结束。 读取时,如果发生错误 则返回值为0,正常读取数据时为1, 读取文件结束时为-1。
测试例程1:
被测试的text.txt 文件内容:
1234 abc k world hello This is a test file.
$fscanf 仿真代码:
`timescale 1ns / 1ps module sim_top( ); reg stop_flag = 0; localparam FILE_TXT = "../../../test.txt"; integer fd; integer char_num; integer i; reg [8*10-1:0] fbuf = 0; initial begin i = 0; char_num = 0; fd = $fopen(FILE_TXT, "r"); if(fd == 0) begin $display("$open file failed") ; $stop; end $display("\n ============= file opened... ============= ") ; char_num = $fscanf(fd,"%s",fbuf); i = i + 1; while ($signed(char_num) == 1) begin $write("%s", fbuf) ; #10; char_num = $fscanf(fd,"%s",fbuf); i = i + 1; end #10; $fclose(fd) ; $display("\n ============= file closed... ============= ") ; stop_flag = 1; #100; $stop; end endmodule
仿真文件中打开文本文件,$fscanf 以字符串方式读取文件的内容,遇到空格,换行,表示一次$fscanf 操作结束。
仿真结果如下:
图13
图14
从图13,图14 中可以看出,仿真工程中使用$write 函数时,所有输出的结果都在同一行内显示。从结果可以推断出利用$fscanf 函数不会将文本文件中换行读取到fbuf中。详细内容,请参考对应的视频教程。
例2:
如果为了观察方便也可以修改仿真工程文件,将$write 函数修改为$display函数,再次进行仿真。
图15
从图15中可以看出:遇到文本文件中的回车,换行 都表示一次$fscanf 操作结束。 因此, 打印结果会分为很多行打印输出。
例3
如果使用char_num = $fscanf(fd,”%c“,fbuf); 替代 char_num = $fscanf(fd,”%s“,fbuf); 使用的%c ,每次执行$fscanf 从文本文件中读出一个字符。而且换行符也可以直接读出。
修改仿真文件,重新进行测试。仿真结果入下图16 ,图17所示
图16
图17
从图16 中可以看出使用%c 时, 换行可以被存储到fbuf中。
例4:以2进制方式读取文本文件
修改文本文件test.txt
1010 1234 abcd 1010 w rl This
修改仿真测试工程:
`timescale 1ns / 1ps module tb( ); reg stop_flag = 0; localparam FILE_TXT = "../test.txt"; integer fd; integer char_num; integer i; reg [8*4-1:0] fbuf = 0; initial begin i = 0; char_num = 0; fd = $fopen(FILE_TXT, "r"); if(fd == 0) begin $display("$open file failed") ; $stop; end $display("\n ============= file opened... ============= ") ; char_num = $fscanf(fd,"%b",fbuf); i = i + 1; while (char_num == 1) begin $write("%b", fbuf) ; #10; char_num = $fscanf(fd,"%b",fbuf); i = i + 1; end #10; $fclose(fd) ; $display("\n ============= file closed... ============= ") ; stop_flag = 1; #100; $stop; end endmodule
仿真测试结果
图18
图18 中可以看出, 读取文本文件test.txt 中的内容,只要是0 或者1 都是可以被正确读取; 如果超过1 对于%b 来说, 不是合法数据。 之后的所有数据被认为不是合法数据了($fscanf的返回值将永远为0 ,表示读取文件错误, 即使后面的文本文件中包含 10101 ,也不会被正确读取了)。
例5:
修改仿真文件,以16进制方式(%h) 读取文本文件
仿真代码如下:
`timescale 1ns / 1ps module tb( ); reg stop_flag = 0; localparam FILE_TXT = "../../../test.txt"; integer fd; integer char_num; integer i; reg [8*4-1:0] fbuf = 0; initial begin i = 0; char_num = 0; fd = $fopen(FILE_TXT, "r"); if(fd == 0) begin $display("$open file failed") ; $stop; end $display("\n ============= file opened... ============= ") ; char_num = $fscanf(fd,"%h",fbuf); i = i + 1; while (char_num == 1) begin $write("%h", fbuf) ; #10; char_num = $fscanf(fd,"%h",fbuf); i = i + 1; end #10; $fclose(fd) ; $display("\n ============= file closed... ============= ") ; stop_flag = 1; #100; $stop; end endmodule
仿真结果如下:
图19
从图19 中可以看出,vivado 2018.2 软件下 $fscanf函数使用%h 参数 ,也是可以读取文件中的数据的。 直到读取到非法字符,$fscanf 的返回值才为0。
例6:读取10进制文本数据
修改输入的文本文件:dec_input.txt
0 1 2 3 +4 5 -6 7 8 9 -10 11 77 13 14 15 160 717 218 -319 320 521 22 23
仿真代码:
`timescale 1ns / 1ps module tb( ); reg stop_flag = 0; localparam FILE_TXT = "../dec_input.txt"; //localparam FILE_TXT = "../test.txt"; integer fd; integer char_num; integer i; reg [8*4-1:0] fbuf = 0; //reg [8*2-1:0] fbuf = 0; initial begin i = 0; char_num = 0; fd = $fopen(FILE_TXT, "r"); if(fd == 0) begin $display("$open file failed") ; $stop; end $display("\n ============= file opened... ============= ") ; char_num = $fscanf(fd,"%d",fbuf); i = i + 1; while (char_num == 1) begin $write("%d", $signed(fbuf)) ; #10; char_num = $fscanf(fd,"%d",fbuf); i = i + 1; end #10; $fclose(fd) ; $display("\n ============= file closed... ============= ") ; stop_flag = 1; #100; $stop; end endmodule
仿真结果:
图20
从图20 中可以看出, 文本文件中的10进制数, 不论正负,都可以正确的被$fscanf读取出来。 使用$fscanf 读取10进制的数组文本文件, 在实际工作中是经常遇到的。注:如图21 所示,vivado 2018.2 在读取 +4 时,有些问题,但返回值没有问题。modelsim读取+4 是正确的。
图21
从例4, 例5, 例6 可以看出,文本文件中的数据(ascii码) 可以通过$fscanf 函数 转换为所需要的10进制, 2进制, 16进制等数据保存到数组中。
练习题:
1)以文本方式打开文件, 使用%b 方式以2进制形式读出文件中的数据,转换成16进制数据,写入到一个新的文本文件中。
2)以文本方式打开文件, 使用%d 方式以10进制形式读出文件中的数据,转换成16进制数据,写入到一个新的文本文件中。
3)以文本方式打开文件, 使用%d 方式以10进制形式读出正弦函数文件中的数据。
例7:读取float进制文本数据
修改输入的文本文件:dec_input.txt
0 1 2 3 +4 5.4 -6 7.5 81 9 -10 11 77 13 14 15 160 717 218 -319 320 521 22 23
仿真代码:
`timescale 1ns / 1ps module tb( ); reg stop_flag = 0; localparam FILE_TXT = "../dec_input.txt"; //localparam FILE_TXT = "../test.txt"; integer fd; integer char_num; integer i; reg [8*4-1:0] fbuf = 0; //reg [8*2-1:0] fbuf = 0; initial begin i = 0; char_num = 0; fd = $fopen(FILE_TXT, "r"); if(fd == 0) begin $display("$open file failed") ; $stop; end $display("\n ============= file opened... ============= ") ; char_num = $fscanf(fd,"%f",fbuf); i = i + 1; while (char_num == 1) begin $write("%f", $signed(fbuf)) ; #10; char_num = $fscanf(fd,"%f",fbuf); i = i + 1; end #10; $fclose(fd) ; $display("\n ============= file closed... ============= ") ; stop_flag = 1; #100; $stop; end endmodule
仿真结果:
图22
从图21 可以看出,使用%f 格式读取文本文件时,支持’.’ 而且数值经过四舍五入的方式写入到fbuf 中。
经过以上各种格式的测试,对$fscanf 函数的读取格式总结如表1:
表1
格式 | 支持字符 | 不支持字符 | 描述 |
%s | 换行,空格不被写入, 支持字符串 | 读出内容转换为字符串 | |
%c | 回车,换行,空格 被写入,支持字符 | 读出内容转换为字符 | |
%b | 换行,空格不被写入,0, 1, X, x, Z, z, _ | 其他字符都不支持 ,包括 +,- | 读出内容转换为2进制数 |
%h or %x | 换行,空格不被写入,0-9, AF, af, _, X, x, Z, z | 其他字符都不支持 ,包括 +,- | 读出内容转换为16进制数 |
%d | 换行,空格不被写入,0-9, _, +, -, X, x, Z, z | 其他字符都不支持 , | 读出内容转换为10进制数 |
%f | 换行,空格不被写入,0-9, _, +, -,. | 其他字符都不支持 ,包括 X, x, Z, or z | 读出内容转换为10进制整数,四舍五入 |
%o | 换行,空格不被写入,0-7, _, X, x, Z, z | 其他字符都不支持 ,包括 +,- | 读出内容转换为8进制数 |
4. $fread 使用
-
$fread 使用格式如下:
integer <integer>; <integer> = $fread(<store>,<file_desc>); <integer> = $fread(<store>,<file_desc>,<start> ); <integer> = $fread(<store>,<file_desc>,<start>,<count> ); <integer> = $fread(<store>,<file_desc>, , <count> ); integer:整型数值,返回本次$fread 读取的真实字节数量,当返回值为0 ,表示错误读取或者文件结束。 store:将二进制文件中的数据读取到寄存器或者二维数组中。 file_desc:为打开的文件句柄 start: 为二维数组的起始地址 count: 从起始地址开始, 写入二维数组的数量。
- 举例:
integer code;
reg [7:0] mem [3:0];
integer fd;
initial
begin
fd = $fopen("test.bin", "rb");
code = $fread(mem, fd);
code = $fread(mem, fd,1);
code = $fread(mem, fd,1,2);
code = $fread(mem, fd, ,3);
end
根据$fread 使用格式,上面的程序解释如下:
(1)code = $fread(mem, fd); 表示从二进制文件中读取数据,一次存放到mem[0],mem[1],mem[2],mem[3]。
(2)code = $fread(mem, fd, 1); 表示从二进制文件中读取数据,一次存放到mem[1],mem[2],mem[3]。
(3)code = $fread(mem, fd, 1, 2); 表示从二进制文件中读取数据,一次存放到mem[1],mem[2]。
(4)code = $fread(mem, fd, , 3); 表示从二进制文件中读取数据,一次存放到mem[0],mem[1],mem[2]。
测试仿真文件:
`timescale 1ns / 1ps module sim_top( ); reg stop_flag = 0; //localparam FILE_TXT = "../../../test.txt"; localparam FILE_TXT = "../../../test.bin"; integer fd; integer char_num; integer i; reg [7:0] fbuf [3:0]; initial begin i = 0; char_num = 0; fd = $fopen(FILE_TXT, "rb"); if(fd == 0) begin $display("$open file failed") ; $stop; end $display("\n ============= file opened... ============= ") ; char_num = $fread(fbuf, fd, 0, 4); //读取二进制文件中的数据,存放到fbuf[0],fbuf[1],fbuf[2],fbuf[3] // char_num = $fread(fbuf, fd,1, 2); //读取二进制文件中的数据,存放到fbuf[1],fbuf[2] #10; $display("$fread[%03d]: %h%h%h%h", i,fbuf[3],fbuf[2],fbuf[1],fbuf[0]) ;//十六进制显示 i = i + 1; while ($signed(char_num) != 0) begin char_num = $fread(fbuf, fd, 0, 4); //读取二进制文件中的数据,存放到fbuf[0],fbuf[1],fbuf[2],fbuf[3] // char_num = $fread(fbuf, fd,1,2); //读取二进制文件中的数据,存放到fbuf[1],fbuf[2] #10; $display("$fread[%03d]: %h%h%h%h", i,fbuf[3],fbuf[2],fbuf[1],fbuf[0]) ;//十六进制显示 i = i + 1; end #10; $fclose(fd) ; $display("\n ============= file closed... ============= ") ; stop_flag = 1; #100; $stop; end endmodule
仿真文件结果:
图23
每次从二进制文件中读取4个字节,但最后一次只能读出两个字节了,然后文件就结束了。
图24
这里,我们可以看出,使用$fread 一次能从二进制文件中读出多少数据, 完全取决于fbuf 定义的大小,如果fbuf定义为reg [7:0]fbuf,那么是从二进制文件中读取一个字节了。