Verilog文件读写系统任务
Verilog 提供了可以对文件进行操作的系统任务
- 文件打开、关闭
$fopen $fclose $ferror
- 文件写入
$fdisplay $fwrite $fstrobe $fmonitor
- 字符串写入
$sformat $swrite
- 文件读取
$fgetc $fgets $fscanf $fread
- 文件定位
$fseek $ftell $feof $frewind
- 存储器加载
$readmemh $readmemb
文件操作过程中,要保证参数以及读写变量类型与文件内容的一致。
不要将字符串类型和多进制类型相混淆。
文件打开关闭
$fopen
integer fd; fd = $fopen("fname",mode);
- fname为打开文件的名字,fd返回32bit的文件描述符
- 正确打开, fd为非0值
- 打开错误时, fd为0
- mode 可选,用于指定打开文件的方式
mode类型 | 说明 |
r | 只读模式 |
w | 只写模式,如果文件存在,则原文件内容会被删除。如果文件不存在,则创建新文件。 |
a | 追加打开一个文本文件,并在文件末尾写数据。如果文件如果文件不存在,则创建新文件。 |
rb | 只读打开一个二进制文件,只允许读数据。 |
wb | 只写打开或建立一个二进制文件,只允许写数据。 |
ab | 追加打开一个二进制文件,并在文件末尾写数据。 |
r+ | 读写打开一个文本文件,允许读和写 |
w+ | 读写打开或建立一个文本文件,允许读写。 |
a+ | 读写打开一个文本文件,允许读和写。读写打开一个文本文件,允许读和写。 |
rb+ | 读写打开一个二进制文本文件,功能与 “r+” 类似。 |
wb+ | 读写打开一个二进制文本文件,功能与 “r+” 类似。 |
ab+ | 读写打开一个二进制文本文件,功能与 “a+” 类似。 |
$fclose
$fclose(fd);
- 关闭fd描述的对应文件
$ferror
err = $ferror(fd,str);
正常打开文件时
- err 与 str均为零值
打开文件出错时
- err返回非零值表示错误
- str返回非零值存储错误类型
建议str长度为640 bit位宽
demo
integer fd; integer err; reg [320:0] str; initial begin fd = $fopen("xxx.txt","r"); err = $ferror(fd,str); $display("file descriptor is %h",fd); $display("error number is %h",err); $display("error info is %s",str); $fclose(fd); end
文件写入
写入文件的系统任务主要包含
- $fdisplay
- $fwrite
- $fstrobe
- $fmonitor
对应的自带格式的系统任务
- $fdisplayb
- $fdisplayh
- $fdisplayo 等
$fdisplay
$fdisplay(fd, arguments);
按顺序或条件写文件,自动换行
$fwrite
$fwrite(fd, arguments);
按顺序或条件写文件,不自动换行
$fstrobe
$fstrobe(fd,arguments);
语句执行完毕后选通写文件
$fmonitor
$fmonitor(fd,arguments);
只要数据有变化就写文件
demo
相对于标准显示任务,$display, w r i t e , write,write,strobe,$monitor, 写文件系统任务除了用法格式上需要多指定文件描述符fd, 其余与对应的显示任务保持一致。
integer fd; integer err; reg [320:0] str; initial begin fd = $fopen("xxx.txt","a+"); err = $ferror(fd,str); if(!err)begin $fdisplay(fd,"new data: %h",fd); $write(fd,"new data: %h",err); // 最后一行不换行打印 end $fclose(fd); end
字符串写入
提供了向字符串中写数据的系统任务$write 和 $sformat
$swrite
$swrite(reg,list_of_arguments);
按顺序或者条件写字符串到变量reg中
$sformat
len = $sformat(reg,format_str,arguments);
按格式froamt_str 写字符串到变量reg中,格式与$display指定格式一致,可返回字符串长度len
demo
reg [299:0] str_swrite,str_sformat; reg [63:0] str_buf; integer len, age; initial begin #20; str_buf = "wkk"; age = 9; $swrite(str_swrite,"%s age is %d",str_buf,age); $swrite(str_swrite,"years","old"); // 直接写不含有格式字符串的字符串 $sformat(str_sformat,"%s age is %d",str_buf,age); len = $sformat(str_sformat,"years old"); end
文件读取
$fgetc
c = $fgetc(fd);
按照字符格式将fd数据输出到变量c, c位宽最少为8位。读取错误时,c的值为EOF(-1),可以用$ferrror检查错误类型
$ungetc
code = $ungetc(c,fd);
向文件fd缓存区写字符c
c值在下次调用$getc时返回,文件fd自身内容不会发生变化,正常写缓冲区返回值code=0, 错误返回值为EOF
$fgets
code = $fgets(str,fd);
按字符连续读,直至str被填满,或者一行内容读取完毕,或者文件结束。
正常读取返回值code为读取行数,发生错误时code为0
$fscanf
code = $fscanf(fd,format,args);
按格式format将文件fd中的数据读取到变量args中
读取一次的停止条件为空格或者换行。
读取发生错误时返回值code = 0
$sscanf
code = $sscanf(str,format,args);
按格式format将字符串型变量str读取到变量args中
$fread
code = $fread(store,fd,start,count);
按二进制数据流格式将数据从文件fd读取到数组或者寄存器变量store中
start为文件起始地址,count为读取长度
若start/count未指定,数据会全部填充至变量store中
若store为寄存器类型,则start/count参数无效,store变量填充满一次数据后便会停止读取
demo
integer i; reg [31:0] char_buf; initial begin #30; fd = $fopen("xxx.txt","r"); $write("Read char:"); err = $ferror(fd,str); if( !err ) begin for(i=0;i<13;i++)begin char_buf[7:0] = $fgetc(fd); // 按照单个字符读取, 包含换行符 $write("%c",char_buf[7:0]); end end $ungetc("1", fd) ; //连续写3次文件缓冲区 $ungetc("2", fd) ; $ungetc("3", fd) ; // 先写后出, 堆栈 char_buf[7:0] = $fgetc(fd) ; //read 3 char_buf[15:8] = $fgetc(fd) ; //read 2 char_buf[23:16] = $fgetc(fd) ; //read 1,read buffer end\ char_buf[31:24] = $fgetc(fd) ; //fd中原来的内容,紧随上一次文件读取的位置 end
integer code; reg [99:0] line_buf[9:0]; initial begin #32; fd = $fopen("...txt","r"); err = $ferror(fd,str); if(!err) begin for(i=0;i<6;i++) begin code = $fgets(line_buf[i],fd); // 读取内容包含"\n" $write("Get line content: %d->%s",i,line_buf[i]); end end //十六进制显示,将显示对应的 ASCIII 码字 $display("Show hex line data%d: %h", 2, line_buf[2]) ; end
$fgets 任务读取时是按照字符串类型读取的, 文件中的1 --> ASCII码: 49
reg [32:0] data_buf[9:0]; reg [63:0] string_buf [9:0]; reg [31:0] data_get ; reg [63:0] data_test ; initial begin #32; fd = $fopen("xxx.txt","r"); err = $ferror(fd,str); if(!err) begin for(i = 0;i<4;i++) begin code = $fscanf(fd,"%h",data_buf[i]); end for(i = 4;i<16;i++) begin code = $fscanf(fd,"%s",string_buf[i]); end end data_test = "fedcba98"; code = $sscanf(data_test,"%h",data_get); code = $sformat(data_test, "%h", data_buf[2]); code = $sscanf(data_test, "%h", data_get); end
reg [71:0] bin_buf [3:0] ; //每行有8个字型数据和1个换行符 reg [143:0] bin_reg ; initial begin #40; fd = $fopen("xxx.txt","r"); err = $ferror(fd,str); if ( !err ) begin code = $fread(bin_buf,fd,0,4); // 数组型读取,读取4次 end $fclose(fd); fd = $fopen("DATA_RD.HEX", "r"); code = $fread(bin_reg, fd); //单个寄存器读取 $fclose(fd) ; end
起始地址和读取长度都是设置数组型变量的参数, 如果存储数据的变量类型是非数组的 reg 型,则只会进行一次读取,直至 reg 型变量被填充完毕。
文件定位
$ftell
获取文件位置
pos = $ftell(fd);
返回文件当前位置距离文件首部的偏移量,初始地址为0
偏移量按照字节为1单位( 8bits)
配置$fseek使用
$fseek
重定位
code = $fseek(fd,offset,type);
设置文件下一个输入或者输出的位置
offset为设置的偏移量
type 为偏移量的参考位置
- 0 设置位置到偏移地址( 参考位置为文件头 )
- 1 设置位置到当前位置加偏移量( 参考位置为当前位置 )
- 2 设置位置到文件尾加偏移量,经常使用负数来表示文件尾向前的偏移量( 参考位置为文件尾 )
$rewind(fd)
无偏移重定位
code = $rewind(fd);
等价于$fseek(fd,0,0)
$feof
判断文件尾部
code = $feof(fd);
判断是否到文件尾部
检测文件尾部时返回值为1,否则为0
demo
reg [31:0] data; reg [199:0] str_long; integer pos ; initial begin #40; fd = $fopen("xxx.txt","r"); err = $ferror(fd,str); if(!err) begin code = $fscanf(fd,"%h",data); pos = $ftell(fd); // 结果为8 code = $rewind(fd); // 重新将文件指针的位置指向文件首部 while( !$feof(fd)) begin code = $fgets(str_long,fd); end end $fclose(fd); end
加载存储器
$readmemh
加载十六进制文件
$readmemh("fname",mem,start_addr,finish_addr);
- fname : 为数据文件名字
- mem: 为数组型/存储器型变量
- start_addr, finish_addr 分别为起始地址和终止地址, start_addr和finish_addr可以省略,此时加载数据的停止条件为存储器mem被填充完毕,或者文件读取完毕。
- 文件内容只应该有空白符(换行、空格),十六进制数据, 注释使用"//"进行标注,数据间建议使用换行符区分
$readmemb
$readmemb("fname",mem,start_addr,finish_addr);
用法格式同$readmemb , 不过文件内容为二进制数据
demo
reg [31:0] mem_load [3:0]; initial begin #50 $readmemh("xxx.hex",mem_load); end
常用例子
1. 文件写
integer fid; initial begin fid=$fopen("xxx.txt"); #`STOPTIME $fclose(fid); end always @ (posedge clk) begin if(data_valid) $fwrite(fid,"%d %d\n",$signed(RE) ,$signed(IM)); end
2. 文件读
localparam nums = 65536; reg [23:0] file_source [nums-1:0]; reg [15:0] read_cnt; reg [23:0] data_I; reg data_in_valid; initial $readmemh("xxxx.txt",file_source); always @(posedge i_clk) begin if(!i_rstn) begin read_cnt <= 16'b0; data_I <= 24'b0; data_in_valid <= 1'b0; end else if(data_in_ready) begin if(read_cnt <= nums) begin data_I <= file_source[read_cnt]; read_cnt <= read_cnt + 1'b1; data_in_valid <= 1'b1; end else begin data_I <= data_I; read_cnt <= read_cnt; data_in_valid <= 1'b0; end end else begin data_I <= data_I; read_cnt <= read_cnt; data_in_valid <= 1'b0; end end