Menu Close

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 没有额外增加换行操作。

仿真结果如下:

%title插图%num

 

%title插图%num

 

%title插图%num

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

%title插图%num

在读取二进制文件中, 使用$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。

仿真结果如下:

%title插图%num

 

在读取二进制文件,读到349 个是就结束了。原因就是读取到8’hff 时,认为文件已经结束了, 所以整个程序就退出了。

控制台输出结果如下:

%title插图%num

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

仿真结果如下:

%title插图%num

我们会发现,平时读取的数据都是16’h00xx, 只有结束时, $fgetc 的结果为16’hffff。 相同的test.bin 文件,在读到690 个数值时, 才真正结束了。

控制台输出结果如下:

%title插图%num

$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

 

仿真结果如下:

%title插图%num

 

%title插图%num

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

%title插图%num

从仿真结果来看,在仿真文件中使用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 才能将这个文本文件读取结束。

仿真结果如下:

%title插图%num

%title插图%num

这里,我们发现需要$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 操作结束。

仿真结果如下:

%title插图%num

%title插图%num

遇到文本文件中的回车,换行 都表示一次$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

 

仿真文件结果:

%title插图%num

每次从二进制文件中读取4个字节,但最后一次只能读出两个字节了,然后文件就结束了。

%title插图%num

这里,我们可以看出,使用$fread 一次能从二进制文件中读出多少数据, 完全取决于fbuf 定义的大小,如果fbuf定义为reg [7:0]fbuf,那么是从二进制文件中读取一个字节了。

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

发表评论

相关链接