文章参考
一、https://blog.youkuaiyun.com/weixin_42929607/article/details/105810390
二、7.2 Verilog 文件操作 | 菜鸟教程 (runoob.com)
三、关于verilog中系统任务$readmemb和$readmemh的用法(要点补充)_一CC一_新浪博客 (sina.com.cn)
前言
大部分编译器和仿真器在读文件的时候需要预先打开文件。在Vivado自带仿真器中,读文件不需要预先打开要读取的文件,也不需要将文件加入Vivado工程内部。介于此,本文总结了在Vivado中利用Verilog语言进行文件系统操作的各种详细使用方法。
一、Verilog文件操作简介
系统任务 | 命令1 | 命令2 | 命令3 | 命令4 |
---|---|---|---|---|
文件开、闭 | $fopen | $fclose | $ferror | |
文件写入 | $fdisplay | $fwrite | $fstrobe | $fmonitor |
字符串写入 | $sformat | $swrite | ||
文件读取 | $fgetc | $fgets | $fscanf | $fread |
文件定位 | $fseek | $ftell | $feof | $frewind |
存储器加载 | $readmemh | $readmemb |
使用文件操作任务(尤其注意 $sforamt, $gets, $sscanf 等)对文件进行操作时,需要根据文件性质和变量内容确定使用哪一种系统任务,并保证参数及读写变量类型与文件内容的一致性,不要将字符串类型和多进制类型相混淆。
注意:
文件访问有两种方式:相对路径访问、绝对路径访问
fd1 = $fopen("rom.txt", "r"); //打开存在的文件
fd2 = $fopen("E:/FPGA/FPGA_Projects/cs_907/cs_907.sim/sim_1/behav/xsim/rom2.dat", "r");//打开的文件不存在
- 相对路径访问
fd1 = $fopen("rom.txt", "r"); //打开存在的文件
对于上述表示方式rom.txt
,这个文件的位置,必须放在指定文件夹内。
- 打开vivado工程文件的路径
进入sim
文件夹,然后依次进入sim_1
,behav
,xsim
- 在此文件夹内,建立
rom.txt、rom2.dat
文件
在这里建立rom.txt、rom2.dat
文件即可,之后仿真的时候,就可以正常调用数据了。
2.绝对路径访问
fd2 = $fopen("E:/FPGA/FPGA_Projects/cs_907/cs_907.sim/sim_1/behav/xsim/rom2.dat", "r");//打开的文件不存在
如上所示,直接给出文件的绝对路径,这样就可以任意设定rom2.dat
文件的位置。
注意:斜杠一定要是/
,直接复制过来的win路径不是这个,需要修改。
1.除了书本上常用的六种(各三种)用法外,还可以如下方式:
$readmemb(“D:/file1/file2/ramh.dat”,a);
即可以调用到放置在任意处的存储文件。
2.当采用$readmemb(“ramh.dat”,a);这种方式时,ramh.dat文件必须放置在工程下的simulation📁下,亲测其他放置都无效。
3.关于存储文件后缀,.dat .txt 甚至不加后缀都可以,只要保证程序里调用的与文件夹中实际的一致,可以索引到即可!
4.存储顺序。文档中由上至下,对应数组由低到高。
5.不可综合。$readmemb、$readmemh、initial 都是不可综合语句(怎么可以这样呢!那大数组怎么赋值?)也就是说只能在仿真时调试用。
6.对于$readmemh对应的16进制文件,不用写成4'hA,最简单的A即可。
二、Verilog文件操作详细介绍
1.文件打开、关闭
系统任务 | 调用格式 | 任务描述 |
---|---|---|
文件打开 | fd = $fopen("fname", mode) ; | fname 为打开文件的名字 fd 为返回的 32bit 文件描述符 --- 正确打开时,fd 为非零值 --- 打开出错时,fd为零值 mode 用于指定文件打开的方式 |
文件关闭 | $fclose(fd) ; | 关闭 fd 描述的对应文件 |
文件错误 | err = $ferror(fd, str) ; | 正常打开文件时: --- err 与 str 均为零值, 打开文件出错时: --- err 返回非零值表示错误 --- str 返回非零值存储错误类型 --- 官方建议 str 长度为 640bit 位宽 |
对文件的打开、关闭操作的举例如下:
module cs01_tb(
);
//open/close file
integer fd1, fd2 ;
integer err1, err2 ;
reg [320:0] str1, str2 ; //错误类型的变量也可以为可支持的 string 类型
initial begin
//existing file
// fd1 = $fopen("./DATA_RD.HEX", "r"); //打开存在的文件
fd1 = $fopen("rom.txt", "r"); //打开存在的文件
err1 = $ferror(fd1, str1);
$display("File1 descriptor is: %h.", fd1 );//非零值
$display("Error1 number is: %h.", err1 ); //0
$display("Error2 info is: %s.", str1 ); //0
$fclose(fd1);
//not existing file
fd2 = $fopen("E:/FPGA/FPGA_Projects/cs_907/cs_907.sim/sim_1/behav/xsim/rom3.dat", "r");//打开的文件不存在
err2 = $ferror(fd2, str2);
$display("File2 descriptor is: %h.", fd2 ); //0
$display("Error2 number is: %h.", err2 ); //非零值
$display("Error2 info is: %s.", str2 ); //非零值
$fclose(fd2);
end
文件打开方式 mode 类型及其描述如下:
r | 只读打开一个文本文件,只允许读数据。 |
---|---|
w | 只写打开一个文本文件,只允许写数据。如果文件存在,则原文件内容会被删除。如果文件不存在,则创建新文件。 |
a | 追加打开一个文本文件,并在文件末尾写数据。如果文件如果文件不存在,则创建新文件。 |
rb | 只读打开一个二进制文件,只允许读数据。 |
wb | 只写打开或建立一个二进制文件,只允许写数据。 |
ab | 追加打开一个二进制文件,并在文件末尾写数据。 |
r+ | 读写打开一个文本文件,允许读和写 |
w+ | 读写打开或建立一个文本文件,允许读写。如果文件存在,则原文件内容会被删除。如果文件不存在,则创建新文件。 |
a+ | 读写打开一个文本文件,允许读和写。如果文件不存在,则创建新文件。读取文件会从文件起始地址的开始,写入只能是追加模式。 |
rb+ | 读写打开一个二进制文本文件,功能与 "r+" 类似。 |
wb+ | 读写打开或建立一个二进制文本文件,功能与 "w+" 类似。 |
ab+ | 读写打开一个二进制文本文件,功能与 "a+" 类似。 |
2.文件写入
写文件的系统任务主要包括:$fdisplay, $fwrite, $fstrobe, $fmonitor,以及它们对应的自带格式的系统任务 $fdisplayb, $fdisplayh, $fdisplayo 等。
调用格式 | 任务描述 |
---|---|
$fdisplay(fd, arguments) ; | 按顺序或条件写文件,自动换行 |
$fwrite(fd, arguments) ; | 按顺序或条件写文件,不自动换行 |
$fstrobe(fd, arguments) ; | 语句执行完毕后选通写文件 |
$fmonitor(fd, arguments) ; | 只要数据有变化就写文件 |
相对于标准显示任务 $display, $write, $strobe, $monitor,写文件系统任务除了用法格式上需要多指定文件描述符 fd,其余打印条件、时刻特性等均与其对应的显示任务保持一致。利用追加写的方式,对文件进行写操作的举例如下:
module cs02_tb(
);
//(2) write file
integer fd ;
integer err, str ;
initial begin
fd = $fopen("rom3.txt", "a+"); //末尾追加的方式打开
err = $ferror(fd, str);
if (!err) begin
$fdisplay(fd, "New data1: %h", fd) ;
$fdisplay(fd, "New data2: %h", str) ;
$fdisplay(fd, "New data3: %h", err) ;
//$write(fd, "New data3: %h", err) ; //最后一行不换行打印
end
$fclose(fd);
end
endmodule
3.字符串的写入
Verilog 还提供了往字符串里写数据的系统任务 $swrite 和 $sformat。
调用格式 | 任务描述 |
---|---|
$swrite(reg,"list_of_arguments",格式) ; | 按顺序或条件写字符串到变量 reg 中, list_of_arguments为所输入的字符串, |
len = $sformat(reg, format_str, arguments) ; | 按格式 format_str 写字符串到变量 reg 中 格式与 $display 指定格式时一致, 不建议省略第二个参数 format_str, 可返回字符串长度 len |
$sformat 第二个参数 format 为字符串类型,一般建议不要省略。该参数指定了输入变量的类型,指定类型时也可以包含其他字符串信息,类型种类及用法可参考显示函数 $display。该参数也可以为寄存器类型,但要求存储的数据为正常的字符串数据。写字符串代码举例如下:
module cs03(
);
//(3) write string
reg [299:0] str_swrite, str_sformat;
reg [63:0] str_buf ;
integer len, age ;
initial begin
#20 ;
str_buf = "runoob!" ;
age = 9 ;
//$swrite 指定格式写包含变量的字符串
$swrite(str_swrite, "%s age is %d", str_buf, age) ;
$display("%s", str_swrite);
//$swrite 直接写不含有变量的字符串
$swrite(str_swrite, "years ", "old.") ;
$display("%s", str_swrite);
//$swrite 不指定格式写包含变量的字符串,不建议
$swrite(str_swrite, age) ;
$display("$swrite err test: %d", str_swrite);
$display();
//$sformat 指定格式写包含变量的字符串
$sformat(str_sformat, "I have learnt in %s", str_buf) ;
$display("%s", str_sformat);
//$sformat 直接写不含有变量的字符串,并获取字符串长度
len = $sformat(str_sformat, "for 4 years!") ;
$display("%s", str_sformat);
$display("$sformat len: %d", len);
//$sformat 直接一次写多个不含有变量的字符串,不建议
$sformat(str_sformat, "for", "4", "years!") ;
$display("$sformat err test: %s", str_sformat);
end
endmodule
忽略打印信息的空格,调试信息输出如下:
由此可知,$sformat 与 $swrite 用法可以一致,例如 $sformat 可采用指定格式的写字符串,或只写一次不含变量的字符串。此时 $sformat 相当于在第二个参数中未指定变量类型,所以第三个参数应该忽略不写。
$swrite 还可以一次写多个不包含变量的字符串,而 $sformat 不允许如此调用。也建议,使用 $swrite 写包含变量的字符串时要指定变量类型,否则结果可能不可预测。
4.文件读取
系统任务 | 调用格式及说明 | |
---|---|---|
按字符读文件 | c = $fgetc( fd ) ; | |
按字符格式将 fd 数据输出给变量 c,c 位宽最少为 8 读取错误时 c 值为 EOF(-1),可以用 $ferror 检查错误类型 | ||
按字符写缓冲区 | code = $ungetc(c, fd ) ; | |
向文件 fd 缓冲区写字符 c c 值在下次调用 $fgetc 时返回,文件 fd 自身内容不会发生变化 正常写缓冲时返回值 code 为 0,发生错误时返回值 code 为 EOF | ||
按行读文件 | code = $fgets(str, fd) | |
按字符连续读,直至变量 str 被填满,或一行内容读取完毕,或文件结束 正常读取时返回值 code 为读取行数(次数),发生错误时 code 为 0 | ||
按格式读文件 | code = $fscanf(fd, format, args) ; | |
按格式 format 将文件 fd 中的数据读取到变量 args 中 format 可参考 $display 指定格式说明 读取一次的停止条件为空格或换行 读取发生错误时返回值 code 为 0 | ||
按格式读字符串 | code = $sscanf(str, format, args) ; | |
按格式 format 将字符串型变量 str 读取到变量 args 中 调用格式方法和 $fscanf 一致 | ||
按二进制读文件 | code = $fread(store, fd, start, count) ; | |
按二进制数据流格式将数据从文件 fd 读取到数组或寄存器变量 store 中 start 为文件起始地址,count 为读取长度 若 start/count 未指定,数据会全部填充至变量 store 中 若 store 为寄存器类型,则 start/count 参数无效,store 变量填充满一次数据后便会停止读取 |
5.文件定位
系统任务 | 调用格式 | 任务描述 |
---|---|---|
获取文件位置 | pos = $ftell( fd ) ; | 返回文件当前位置距离文件首部的偏移量,初始地址为 0 偏移量按照字节为一单位(8bits) 配合 $fseek 使用 |
重定位 | code = $fseek(fd, offset, type) ; | 设置文件下一个输入或输出的位置 offset 为设置的偏移量 type 为偏移量的操作类型 --- 0: 设置位置到偏移地址 --- 1: 设置位置到当前位置加偏移量 --- 2: 设置位置到文件尾加偏移量,经常使用负数来表示文件尾向前的偏移量 |
无偏移重定位 | code = $rewind( fd ) ; | 等价于 $fseek( fd, 0, 0) ; |
判断文件尾部 | code = $feof(fd) ; | 判读是否到文件尾部 检测到文件尾部时返回值为 1,否则为 0 |
6.加载存储器
系统任务 | 调用格式及说明 | |
---|---|---|
加载十六进制文件 | $readmemh("fname", mem, start_addr, finish_addr) | |
fname 为数据文件名字 mem 为数组型/存储器型变量 start_addr、finish_addr 分别为起始地址和终止地址 start_addr、finish_addr 可以省略,此时加载数据的停止条件为存储器变量 mem 被填充完毕,或文件读取完毕 文件内容只应该有空白符(或换行、空格符)、二进制或十六进制数据 注释用"//"进行标注,数据间建议用换行符区分 | ||
加载二进制文件 | $readmemb("fname", mem, start_addr, finish_addr) | |
用法格式同 $readmemb |
文件 rom.txt 内容如下,将此文件的内容加载到存储器变量中。
加载存储器举例代码如下:
module();
//6 load mem
reg [31:0] mem_load [3:0] ;
initial begin
#50 ;
$readmemh("rom.txt", mem_load);
$display("Read memory1: %h", mem_load[0]) ;
$display("Read memory2: %h", mem_load[1]) ;
$display("Read memory3: %h", mem_load[2]) ;
$display("Read memory4: %h", mem_load[3]) ;
end
endmodule
仿真结果如下:
总结
本文对Verilog文件操作进行了详细的阐述,仅作为学习记录所用。