Menu Close

Verilog 文件操作-$fgetc,$fgets,$fscanf,$fread

在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])

verilog simulation $fgetc

图1-1

verilog simulation $fgetc

图1-2

图2

举例,从一个二进制文件中读取数据(test.bin)

verilog simulation read binary file

图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。

仿真结果如下:

verilog simulation $fgetc

图4

从图4的显示结果可以看出,在读取二进制文件时,读到349 个是就结束了。原因就是读取到8’hff 时,认为文件已经结束了, 所以整个程序就退出了。

如图5所示,控制台输出也反应了图4所对应的结果:

verilog simulation read binary file

图5

修改仿真文件, 将reg [7:0] c;  修改为reg [15:0] c;

仿真结果如下:

verilog simulation read binary file

图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

 

仿真结果如下:

verilog simulation $fgets

图8

verilog simulation $gets result

图9

相同的仿真文件在quartus 下仿真:

verilog simulation $gets

图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 才能将这个文本文件读取结束。

仿真结果如下:

verilog simulation $gets wave

图11

verilog simulation $gets result

图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函数,再次进行仿真。

verilog simulation $fscanf result

图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,那么是从二进制文件中读取一个字节了。

 

 

Posted in FPGA, FPGA 教材教案, Verilog, Verilog, 教材与教案, 文章

发表回复

相关链接