Verilog系列:【3】文件的IO操作

Abstract

Verilog中文件操作主要涉及对特定文件或者变量的读写访问操作,通过这些操作可以极大地方便仿真验证工作.下文将针对经常使用到的一些函数进行示例说明介绍.

Introduction

1 文件的打开与关闭

1.1 $fopen

格式:function integer fopen(string filename,string access_mode);

filename      - 将要被操作的文件名

access_mode  - 文件被访问的模式(如下表所示)

如果文件打开成功,则函数将返回一个32位整数的文件描述符,如果打开失败,那么将返回整数0.返回的32位描述符根据access_mode的有无又分为“File Descriptor”(有access_mode)和“Multi-Channel Descriptor (无access_mode,此时默认文档处于可写状态)两种,multichannel descriptor指示的32位数据可以进行逻辑操作后实现对多个文件的同时操作.

文件使用方式

说明

"r"或者"rb"

为从文件读取数据打开一个文件

"w"或者"wb"

创建一个输出文件,如果该文件存在的话则在创建新文件时删除原文件中的内容

"a"或者"ab"

新写入文件的内容将附加在原文件的内容后

"r+","r+b"或者"rb+"

对于已存在的文件既可以输入数据也可以输出数据,即更新

"w+","w+b"或者"wb+"

新建一个文件,先向该文件写入数据,然后可进行读操作

"a+","a+b"或者"ab+"

不删除原文件,新更新的内容将追加至原文件末尾

注意:

1>使用方式中"b"后缀表示进行操作的对象为二进制文件,但是在很多系统中对二进制并不去做区分;

2>如果$fopen()中未指定文件访问方式,那么该文件将默认为以可写方式被打开;

1.2 $fclose

格式:task $fclose(integer file_descriptor);

如果使用$fopen打开文件后程序中并没有执行$fclose的话(特别是对文件的写操作),那么只有在仿真器关闭后,相应的内容才会更新至对应的文件中.因此,建议在编写程序时,$fopen$fclose成对出现.

1.3【示例】

仿真结果如下:

从上述仿真结果可以看出,access_mode没有指定的时候,文件句柄的最高位为“1,即表示该文档被成功打开;当access_mode被指定的时候,文件句柄的最高位为“0”;在文件句柄为MCD时可以对多个文件句柄进行逻辑操作,从而影响实际多个文件的数据操作.例如,如果将多个句柄进行或操作,那么在进行$display等操作时,相应的内容会被显示到各个被操作的文件中.这里需要注意的是fd模式下的多个文件句柄是不能通过逻辑操作实现对多个文件的同时操作.

2.文件的读写

2.1常用输出函数

十进制格式

二进制格式

十六进制格式

八进制格式

$fdisplay

$fdisplayb

$fdisplayh

$fdisplayo

$fwrite

$fwriteb

$fwriteh

$fwriteo

$fstrobe

$fstrobeb

$fstrobeh

$fstrobeo

$fmonitor

$fmonitorb

$fmonitorh

$fmonitoro

这里$fstrobe$fmonitor的操作方式类似于$strobe$monitor,只是他们会将对应的数据写入到相应的文件中.

2.2常用的格式字符

格式字符

说明

%h or  %H

以十六进制形式输出

%d or  %D

以十进制形式输出

%o or  %O

以八进制形式输出

%b or  %B

以二进制形式输出

%c or  %C

ASCII字符的形式输出

%l or  %L

显示库的关联信息

%v or  %V

显示对应线网信号的强度

%m or  %M

显示层次路径

%s or  %S

以字符串形式输出

%t or  %T

以当前时间的格式显示

%u or  %U

以两值(10)形式输出数据

%z or  %Z

以四值(10zx)形式输出数据

%e or  %E

以指数形式显示实数

%f or  %F

以十进制形式显示实数

%g or  %G

选用%f%e格式中输出宽度较短的一种形式


2.3格式化数据为指定字符串

2.3.1 $swrite

格式:

$swrite(output_reg,list_of_arguments);

$swrite$fwrite用法类似,也有针对二进制、十六进制、八进制的格式,唯一不同的是$swrite的第一个参数是一个存储字符串的reg类型变量,$fwrite为指向对应文件的句柄.

2.3.2$sformat

格式:

$sformat(output_reg,format_string,list_of_arguments);

$swrite$sformat格式类似.

2.4 示例

仿真结果如下:

从上述仿真结果可以看到"Hi,Broadcast..."不仅出现在了仿真器的调试窗口,同时还出现在了生成的各个文件中,"Hi,Message..."仅出现在了各个生成的文件中,两者的主要差别是在使用$fdisplay,传入函数的第一个参数的最低位不同,程序中的broadcast的最低位为"1",而句柄中的最低位表示消息将会输出至标准输出(这里就是调试窗口).另外,$fstrobe$fmonitor功能类似与$strobe$monitor,一般这几个函数均发生在当前时间槽(time-slot)的最后执行,所以如果$fstrobe$fmonitor与相应的$fclose连续执行,那么在执行$fstrobe$fmonitor,相应的文件已经被$fclose关闭了,当执行$fstrobe$fmonitor时仿真器会报出找不到对应的文件句柄,即对应的文件操作将不能成功,为此可以增加时延.最后,需要注意在使用$fstrobe$fmonitor,给这两个函数传递的不是文件句柄而是寄存器变量,且该寄存器变量的位宽需要设置合适,否则字符串有可能不能完全存储到其中(在Verilog,一个字符一般占用8-bit的宽度).

3.读文件操作

文件的读操作分为两种方式实现:文件句柄和直接读取.

3.1 $fgetc

格式:

char = $fgetc(fd);

该函数实现从fd指定的文件读取一个字节.如果读操作失败,那么char将被赋值为EOF-1,因此建议指定的char的宽度大于8,这样在char被赋予EOF时可与'hFF区别.注意,此处的文件句柄在获取时必须指明其访问方式,否则将不能获得有效数据.

3.2$fgets

格式:

integer code = $fgets(str,fd);

将从fd指定的文件读取一行字符并存储到str,直到文件结束.如果读取失败,那么将会给code返回0,否则返回读取到的字符个数.

【示例】

仿真结果:

上述仿真结果为使用$fgetc单次读取一个字符的仿真结果:

上述仿真结果为使用$fgets单次读取一个字符串的仿真结果:

上述仿真结果为使用$unfgetc将字符chr插入到分配给句柄file_cbuffer,如果后续再次对file_c进行$fgetc读操作,将会首先读到该字符chr,这里需要注意,此时file_c指向的文件本身并未发生改变.

3.3 $fscanf$sscanf

格式:

integer code = $fscanf(fd,format,args);

integer code =$sscanf(str,format,args);

按照format指定的格式从fd指定的文件中读取数据,并将读回的数据存储在args变量中,如果fd指向的文件的数据格式与format不相符,那么仿真将会异常.$sscanf功能与$fscanf功能类似,只是其将指定字符串内容读到了对应的args.code指示了匹配format中指定格式的数据个数.如果参数个数不匹配,那么不匹配的参数将会被忽略,即返回值的个数以args为准.

【示例】

其中file1.datfile2.dat的格式如下:

程序中fp3fp1指向同一个数据文件,file1.dat.仿真结果如下:

通过仿真可以看到fp3指向的文件格式与$fscanf中指定的格式不匹配,那么该函数执行将得不到期望的数据.

3.4 读取二进制数据

格式:

integer code = $fread(myreg,fd);

integer code = $fread(mem,fd);

integer code = $fread(mem,fd,start);

integer code =$fread(mem,fd,start,count);

integer code = $fread(mem,fd,,count);

该函数的主要功能是将fd指定的数据读取到指定的myreg或者mem(存储数据的数组)中,其中参数startcount即对mem对象使用.code返回当前已完成传输的字节数,如果读取出现异常,code将返回0.从文件读出的数据按照大端方式进行存取操作,即读取的高位数据将存储在高位数据段.读出的数据将按字节以ASCII的形式被读出,因此对于读回的数据需要对照ASCII码表进行分析.

【示例】

仿真结果:

19-23行执行后,file.dat中的所有数据将会按照顺序读取到对应的寄存器(my_reg)中并显示出来;

5-30行将file.dat中的所有数据一次性读取到定义的mem,并将读取的数据存放到函数中指定起始位置开始存放,mem存取数据的个数由函数中指定的个数决定,仿真结果如下:

2-37行中的函数仅指定了数据存放在mem中的起始位置,那么函数执行时,将从该起始位置开始存放,指导指定的mem存放满或者file.dat中的数据完全读出.仿真结果如下:

9-44,函数中仅指定了要被存放的数据的个数,那么函数执行时,将从数据文件中取出指定个数的数据,依次按照mem的起始地址开始存放.

4从文件给存储体加载数据

格式: $readmemb("file_name",memory_name[,start_addr[,finish_addr]]);

$readmemb("file_name",memory_name[,start_addr[,finish_addr]]);

这两个函数可以将指定文件的数据加载入指定的仿真模型存储空间中,并且可在仿真的任何阶段执行.其中可以被操作文件中仅能包含以下格式的信息:

  • 空格(包括空格、换行等);

  • 注释;

  • 二进制或者十六进制格式的数据(数据可以不指定长度或者数据格式);

  • 数据文件中表示地址的@和地址之间不能存在空格等;

这里需要注意数据文件中标识的地址必须在函数指定的地址参数范围之内;

$readmemb("file_name",memory_name[,start_addr[,finish_addr]]);

【示例】

读取的数据文件中存放数据为:

仿真结果如下:

20行执行之后,将会从存储体的起始地址(此处为0地址)开始存放从数据文件中读取到的数据,结果如下所示:

21行执行之后,将按照函数中指定的起始地址开始存放数据.仿真结果如下图:

22行执行结果如下图:

如果在数据文件中确实行号,那么缺失的地址在存储时将会被跳过,即存储体中对应该地址的内容将不会被更新.

当函数中起始地址省略,仅保留结束,那么数据文件中的数据将会从存储体起始地址开始存放直至该结束地址,存放结果如下图:

注意事项:

1)如果任务声明语句中和数据文件里都没有进行地址说明,则默认的存放起始地址为存储体定义语句的起始地址.数据文件里的数据被连续存放到该存储体中,指导存储体单元存满为止或数据文件里的数据被读取完.

2)如果任务中说明了存放的起始地址,没有说明存放的结束地址(数据文件里没有进行地址说明),则数据从起始地址开始存放,存放到该存储体定义语句中的结束地址为止.

3)如果任务在声明语句中起始地址和结束地址都进行了说明,则数据文件里的数据按该指定的起始地址开始存放,直到存放的数据被存放完毕,而不考虑该存储器定义语句中起始地址和结束地址.

4)如果地址信息在任务和数据文件中都进行了说明,那么数据文件里的地址必须在任务中地址参数声明的位置之内,否则将会得不到期望的结果.

5)如果数据文件里的数据个数和任务中起始地址及结束地址按之间的数据个数不同,那么将会得到不期望的结果(数据丢失等).

5调试函数

格式:

integer flag = $feof(fd);

integer errno = $ferror(fd,str);

integer pos = $ftell(fd);

code = $fseek(fd,offset,operation);

code = $rewind(fd);    // which is equivalent to $fseek(fd,0,0);

$fflush(mcd/fd);

$fflush();

5.1 $feof

该函数用来判断当前的读操作是否进行到了文件尾,如果到达文件尾返回真,反之返回假.

5.2 $ferror

该函数用来报告文件的操作状态是否正确,如果在读文件的过程中出现异常,那么该函数返回0,并且只返回一次后即刻被清除.

5.3$ftell

该函数返回数据流的当前位置,也就是说,下一个读取或写入操作将要开始的位置距离文件起始位置的偏移量.

【示例】

仿真结果如下:


5.4 $fseek

$fseek允许对文件中的内容进行定位操作,这个操作将会改变下一个读取或写入操作的起始位置.

$fseek的参数说明如下:

Operation

说明

0

从数据流的起始位置起offset个字节,offset必须是一个非负数

1

从数据流的当前位置起offset个字节,offset是一个可正可负的数

2

从数据流的尾部位置起offset个字节,offset是一个可正可负的数,如果为正值,将定位到文件尾的后面,即类似于附加

【示例】

其中通过$fwrite生成的数据如下:

仿真结果:

23行通过$sfseek将句柄指向重新定位到了文件中的156(可通过$ftell获得)位置,26行针对重新定位后的句柄指向对文件进行读访问操作,从仿真结果可以看出26行将会从文件的156位置开始记性读访问操作.


5.5 $fflush

在对文件进行写操作时,整个操作过程类似下图:

当程序执行过程中发生以下三种情况时,数据缓冲区中的数据会被写入到对应的文件中:

  • 数据缓冲区满;

  • $fclose执行;

  • $fflush执行;

这里的$fflush函数可强迫将输出缓冲区中的内容写入到对应句柄指向的文件而无需等到$fclose的执行.$fflush在实际使用中存在三种格式:

$fflush(mcd);    // 立即将缓冲区内容写入到对应的句柄指示的文件

$fflush(fd);      // 立即将缓冲区内容写入到对应的句柄指示的文件

$fflush();       // 立即将缓冲区内容写入到所有打开的文件中

【示例】

上例中如果40行的$fflush不存在,那么file_d中的内容只有在44行执行后才会被更新,即缓冲区中的内容才会写入到对应的文件中.如果40行存在$fflush,那么在程序未执行到44行的$fclose之前,缓冲区中缓存的数据将会立即写入到对应的文件中.

Conclusion

在实际使用各种任务函数时,切记操作的数据格式以及生成的各种数据的格式,务必正确理解各种任务函数的使用方法,否则仿真结果可能与预期不相符.

全部评论
verilog难不难
点赞 回复 分享
发布于 2022-08-12 20:43

相关推荐

04-27 08:59
常州大学 Java
韵不凡:软件开发的工作需要博士吗?
点赞 评论 收藏
分享
05-07 17:58
门头沟学院 Java
wuwuwuoow:1.简历字体有些怪怪的,用啥写的? 2.Redis 一主二从为什么能解决双写一致性? 3.乐观锁指的是 SQL 层面的库存判断?比如 stock > 0。个人认为这种不算乐观锁,更像是乐观锁的思想,写 SQL 避免不了悲观锁的 4.奖项证书如果不是 ACM,说实话没什么必要写 5.逻辑过期时间为什么能解决缓存击穿问题?逻辑过期指的是什么 其实也没什么多大要改的。海投吧
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务