原文网站: Verilog 文件操作-$fgetc,$fgets,$fscanf,$fread – 芯片天地
在Verilog 仿真中, 我们有时需要将文件中的数据,读入到仿真工程中使用。在Verilog 语法中提供$fgetc,$fgets,$fscanf,$fread 等系统函数,帮助开发者将文件中的数据读出,供仿真程序使用。
$fgetc 使用
reg [7:0] <8-bit_reg>; <8-bit_reg> = $fgetc(<file_desc>); file_desc:为打开的文件句柄
从文件中读取一个字符, 每执行一次$fgetc,就从文件中读取一个字符, 文件的指针自动加一。 当读取到文件结束时, $fgetc 返回 -1, 可以通过-1(EOF) 来定位文件读取结束。
举例,从一个文本文件中读取数据(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
使用$fopen 打开文本文件,使用”r” 参数。 注意这里定义的是reg [7:0] c;因此使用 while ($signed(c) != -1) 来进行比较 ,而不能使用while (c!= -1) 。 因为 8’hff != -1
即255 != -1 这个条件是永远成立的,所以不能判断出文件读取结束。$write 的使用和 $display 系统函数类似, 只是$write 没有额外增加换行操作。
仿真结果如下:
举例,从一个二进制文件中读取数据(test.bin)
在读取二进制文件中, 使用$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’h00xx ,永远不会出现 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。
仿真结果如下:
在读取二进制文件,读到349 个是就结束了。原因就是读取到8’hff 时,认为文件已经结束了, 所以整个程序就退出了。
控制台输出结果如下:
修改仿真文件, 将reg [7:0] c; 修改为reg [15:0] c;
仿真结果如下:
我们会发现,平时读取的数据都是16’h00xx, 只有结束时, $fgetc 的结果为16’hffff。 相同的test.bin 文件,在读到690 个数值时, 才真正结束了。
控制台输出结果如下:
$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
仿真结果如下:
相同的仿真文件在quartus 下仿真:
从仿真结果来看,在仿真文件中使用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 才能将这个文本文件读取结束。
仿真结果如下:
这里,我们发现需要$fgets 读取25次,才能将文本文件读取结束。
$fscanf 使用
integer <integer>; <integer> = $fscanf(<file_desc>, "<format>", <destination_regs>); integer: 定义一个整型数值,正常读取为1,出错时为0,文件读取结束为 -1。 file_desc:为打开的文件句柄 format: 格式化输出,具体可以参照$display 中的格式化参数。表示以什么样的格式读取文件 destination_regs: 读取文件数据后, 保存在这个目标寄存器中。
按照格式将文件中的数据读到变量中, 格式可以参考$display 中的格式化内容。如果遇到空格或者换行,表示一次读取结束。 读取时,如果发生错误 则返回值为0,正常读取数据时为1, 读取文件结束时为-1。
text.txt 文件内容:
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*10-1:0] fbuf = 0;
//reg [31: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);
// char_num = $fscanf(fd,"%c",fbuf);
i = i + 1;
while ($signed(char_num) != -1)
begin
$display("%s", fbuf) ;
#10;
char_num = $fscanf(fd,"%s",fbuf);
// char_num = $fscanf(fd,"%c",fbuf);
i = i + 1;
end
#10;
$fclose(fd) ;
$display("\n ============= file closed... ============= ") ;
stop_flag = 1;
#100;
$stop;
end
initial
begin
stop_flag = 0;
#10;
while(char_num != 0) #10;
stop_flag = 1;
#100;
$stop;
end
endmodule
仿真文件中打开文本文件,$fscanf 以字符串方式读取文件的内容,遇到换车,换行,表示一次$fscanf 操作结束。
仿真结果如下:
遇到文本文件中的回车,换行 都表示一次$fscanf 操作结束。 因此, 打印结果会分为很多行打印输出。
$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 [4:0]; integer fd; initial begin fd = $fopen("test.bin", "rb"); code = $fread(mem, fd); code = $fread(mem, fd,1); code = $fread(mem, fd); code = $fread(mem, fd); end
使用code = $fread(mem, fd); 表示从二进制文件中读取数据,一次存放到mem[0],mem[1],mem[2],mem[3]。
使用code = $fread(mem, fd, 1); 表示从二进制文件中读取数据,一次存放到mem[1],mem[2],mem[3]。
使用code = $fread(mem, fd, 1, 2); 表示从二进制文件中读取数据,一次存放到mem[1],mem[2]。
使用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 [8*10-1:0] fbuf = 0;
reg [7:0] fbuf [3:0];
//reg [31:0] fbuf = 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
仿真文件结果:
每次从二进制文件中读取4个字节,但最后一次只能读出两个字节了,然后文件就结束了。
这里,我们可以看出,使用$fread 一次能从二进制文件中读出多少数据, 完全取决于fbuf 定义的大小,如果fbuf定义为reg [7:0]fbuf,那么是从二进制文件中读取一个字节了。