• 【Verilog 教程】7.2 Verilog 文件操作


    Verilog 提供了很多可以对文件进行操作的系统任务。经常使用的系统任务主要包括:

    文件开、闭:$fopen, $fclose, f e r r o r 文件写入: ferror 文件写入: ferror文件写入:fdisplay, $fwrite, $fstrobe, f m o n i t o r 字符串写入: fmonitor 字符串写入: fmonitor字符串写入:sformat, s w r i t e 文件读取: swrite 文件读取: swrite文件读取:fgetc, $fgets, $fscanf, f r e a d 文件定位: fread 文件定位: fread文件定位:fseek, $ftell, $feof, f r e w i n d 存储器加载: frewind 存储器加载: frewind存储器加载:readmemh, $readmemb
    使用文件操作任务(尤其注意 $sforamt, $gets, $sscanf 等)对文件进行操作时,需要根据文件性质和变量内容确定使用哪一种系统任务,并保证参数及读写变量类型与文件内容的一致性,不要将字符串类型和多进制类型相混淆。
    在这里插入图片描述
    举例代码如下:

    //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");    //打开存在的文件
          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("../../FILE_NOEXIST.HEX", "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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在这里插入图片描述
    文件打开方式 mode 类型及其描述如下:
    在这里插入图片描述
    文件写入
    写文件的系统任务主要包括:$fdisplay, $fwrite, $fstrobe, $fmonitor,以及它们对应的自带格式的系统任务 $fdisplayb, $fdisplayh, $fdisplayo 等。
    在这里插入图片描述
    相对于标准显示任务 $display, $write, $strobe, $monitor,写文件系统任务除了用法格式上需要多指定文件描述符 fd,其余打印条件、时刻特性等均与其对应的显示任务保持一致。

    利用追加写的方式,对文件进行写操作的举例如下:

    //(2) write file
       integer fd ;
       integer err, str ;
       initial begin
          fd = $fopen("./DATA_RD.HEX", "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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    打开文件 DATA_RD.HEX,则可以看到文件末端新增了 3 行数据。
    在这里插入图片描述
    字符串写入
    Verilog 还提供了往字符串里写数据的系统任务 $swrite 和 $sformat。

    在这里插入图片描述
    $sformat 第二个参数 format 为字符串类型,一般建议不要省略。该参数指定了输入变量的类型,指定类型时也可以包含其他字符串信息,类型种类及用法可参考显示函数 $display。该参数也可以为寄存器类型,但要求存储的数据为正常的字符串数据。

    写字符串代码举例如下:

    //(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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    忽略打印信息的空格,调试信息输出如下:
    在这里插入图片描述
    由此可知,$sformat 与 $swrite 用法可以一致,例如 $sformat 可采用指定格式的写字符串,或只写一次不含变量的字符串。此时 $sformat 相当于在第二个参数中未指定变量类型,所以第三个参数应该忽略不写。

    $swrite 还可以一次写多个不包含变量的字符串,而 $sformat 不允许如此调用。

    也建议,使用 $swrite 写包含变量的字符串时要指定变量类型,否则结果可能不可预测。
    在这里插入图片描述
    以"文件写入"仿真中的文件 DATA_RD.HEX 为读取的参考文件,进行举例,该文件内容如下。

    c0dec0de
    5555aaaa
    12345678
    aaaa5555
    New data1: 80000003
    New data2: 00000000
    New data3: 00000000
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    //(4.1) read char
       integer      i ;
       reg [31:0]   char_buf ;
       initial begin
          #30 ;
          fd = $fopen("DATA_RD.HEX", "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
             $write(".\n") ;
          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) ;  //read a
          $display("Read char after $ungetc: %s", char_buf);
          $fclose(fd);
       end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    仿真结果如下。

    由图可知,$fgetc 读取的 13 个字符正确,读取字符包括了换行符。

    $ungetc 向文件缓冲区写字符数据后,再用 $fgetc 可读取文件缓冲区的字符数据。读写遵循先写后出(FILO, First in Last out)原则,相当于压栈。字符数据先写"123"时,读出数据为"321"。

    文件缓冲区读取完毕后,再进行字符数据读取时,读出的数据依然紧随上一次文件读取的位置,即 log 中"a123"中的字符"a"。

    此过程中,文件 DATA_RD.HEX 内容一直没有改变。

    在这里插入图片描述

    //(4.2) read line
       integer      code ;
       reg [99:0]   line_buf [9:0] ;
       initial begin
          #31 ;
          fd = $fopen("DATA_RD.HEX", "r");
          err = $ferror(fd, str);
          if (!err) begin
             for (i=0; i<6; i++) begin  //按字符串格式逐行读取
                code = $fgets(line_buf[i], fd) ;  //末尾含"\n",将打印2行
                $display("Get line data%d: %s", i, line_buf[i]) ;
             end
          end
          //十六进制显示,将显示对应的 ASCIII 码字
          $display("Show hex line data%d: %h", 2, line_buf[2]) ;
          $display("Show hex line data%d: %h", 4, line_buf[4]) ;
          $fclose(fd) ;
       end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    仿真结果如下。

    前 4 行数据按照字符串类型读取和显示,结果正常。

    读取文件第 5 行数据时,由于变量 line_buf 位宽 100 的限制,文件内容"New data1: 80000003 "需要分 2 次才能完成读取。

    因为每一行末尾包含换行符"\n",所以使用 $display 函数打印时,会多出一行空行。

    按照字符串型读取、并对数据进行十六进制显示时,并不能直观的显示出文件对应的数据内容。例如第二行内容并没有显示"12345678,"而是显示其对应的 ASCII 码。所以 $fgets 任务读取时是按照字符串类型读取的,这里需要注意。

    在这里插入图片描述

    //(4.3) $fscanf/$sscanf
       reg [31: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("DATA_RD.HEX", "r");
          err = $ferror(fd, str);
          if (!err) begin
             for (i=0; i<4; i++) begin
                //前4行数据按照十六进制读取和显示
                code = $fscanf(fd, "%h", data_buf[i]);
                $display("$fscanf read data%d: %h", i, data_buf[i]) ;
             end
             for (i=4; i<6; i++) begin
                //后2行数据按照字符串类型读取和显示
                code = $fscanf(fd, "%s", string_buf[i]);
                $display("$fscanf read data%d: %s", i, string_buf[i]) ;
             end
          end
    
          //(1) $sscanf 源变量 data_test 为字符串类型
          data_test = "fedcba98" ;
          code = $sscanf(data_test, "%h", data_get);
          $display("$sscanf read data0: %h", data_get) ;
          //(2) $sscanf: 将源变量 data_test 先转为字符串变量
          code = $sformat(data_test, "%h", data_buf[2]);
          code = $sscanf(data_test, "%h", data_get);
          //直接输入十六进制变量是不建议的
          //code = $sscanf(data_buf[2], "%h", data_get); 
          $display("$sscanf read data0: %h", data_get) ;
          $fclose(fd) ;
       end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    仿真结果如下。

    利用 $fscanf 对文件前 4 行内容按照十六进制读取和显示,后 2 行内容按照字符串型读取和显示,均正常。

    利用 $sscanf 读取源寄存器内容然后搬移到目的寄存器时,源寄存器中的内容应该为字符串型数据。

    例如,利用 $sscanf 将十六进制的数据 data_buf[2] 搬移到寄存器变量 data_get 时,可以先利用写字符串任务 $sformat 将源变量 data_buf[2] 的内容转为字符串型,存放在变量 data_test 中。然后再利用 $sscanf 按照十六进制将 data_test 中的内容搬移到变量 data_get 中。此时按照十六进制格式打印变量 data_get 会显示正常。

    如果直接利用 $sscanf 将十六进制格式的数据 data_buf[2] 直接搬移到变量 data_get 中,则 data_get 中的内容将会是异常的。

    偷偷告诉你,寄存器之间是可以直接赋值的!!!
    在这里插入图片描述
    $fread 调用举例

    //(4.4) $fread
       reg [71:0]   bin_buf [3:0] ; //每行有8个字型数据和1个换行符
       reg [143:0]  bin_reg ;
       initial begin
          #40 ;
          fd = $fopen("DATA_RD.HEX", "r");
          err = $ferror(fd, str);
          if (!err) begin
             code = $fread(bin_buf, fd, 0, 4); //数组型读取,读取4次
             $display("$fread read data %h", bin_buf[0]) ;//十六进制显示
             $display("$fread read data %h", bin_buf[1]) ;
             $display("$fread read data %s", bin_buf[2]) ;//字符串显示
             $display("$fread read data %s", bin_buf[3]) ;
          end
    
          fd = $fopen("DATA_RD.HEX", "r");
          code = $fread(bin_reg, fd); //单个寄存器读取
          $display("$fread read data %h", bin_reg) ;
          $fclose(fd) ;
       end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    仿真结果如下。

    $fread 按二进制读取文件时 ,起始地址和读取长度都是设置数组型变量的参数。

    如果存储数据的变量类型是非数组的 reg 型,则只会进行一次读取,直至 reg 型变量被填充完毕。

    //(4.4) $fread
       reg [71:0]   bin_buf [3:0] ; //每行有8个字型数据和1个换行符
       reg [143:0]  bin_reg ;
       initial begin
          #40 ;
          fd = $fopen("DATA_RD.HEX", "r");
          err = $ferror(fd, str);
          if (!err) begin
             code = $fread(bin_buf, fd, 0, 4); //数组型读取,读取4次
             $display("$fread read data %h", bin_buf[0]) ;//十六进制显示
             $display("$fread read data %h", bin_buf[1]) ;
             $display("$fread read data %s", bin_buf[2]) ;//字符串显示
             $display("$fread read data %s", bin_buf[3]) ;
          end
    
          fd = $fopen("DATA_RD.HEX", "r");
          code = $fread(bin_reg, fd); //单个寄存器读取
          $display("$fread read data %h", bin_reg) ;
          $fclose(fd) ;
       end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    仿真结果如下。

    $fread 按二进制读取文件时 ,起始地址和读取长度都是设置数组型变量的参数。

    如果存储数据的变量类型是非数组的 reg 型,则只会进行一次读取,直至 reg 型变量被填充完毕。
    在这里插入图片描述
    文件定位
    在这里插入图片描述
    在这里插入图片描述
    文件定位测试代码如下:

    // file position
       reg [31:0]   data4 ;      //寄存器变量长度为 4bytes
       reg [199:0]  str_long ;
       integer      pos ;
       initial begin
          #40 ;
          fd = $fopen("DATA_RD.HEX", "r");
          err = $ferror(fd, str);
          if (!err) begin
             //first read
             code = $fscanf(fd, "%h", data4);//从0位置开始读
             pos  = $ftell(fd);      //读8byte后位置为8,坐标为(0,8)
             $display("Position after read: %d", pos) ;
             $display("1st read data: %h", data4) ;
    
             //type = 0
             code = $fseek(fd, 4, 0) ; //从位置4、坐标(0,4)开始读
             code = $fscanf(fd, "%h", data4); //读到换行符停止
             pos  = $ftell(fd);      //读4byte后位置为8,坐标为(0,8)
             $display("type 0: current position: %d", pos) ;
             $display("type 0: read data: %h", data4) ;
             //type = 1
             code = $fseek(fd, 4, 1) ; //从位置4+9=12、坐标(1,3)据开始读
             code = $fscanf(fd, "%h", data4); //读到换行符停止
             pos  = $ftell(fd);      //读5byte后位置为17,坐标为(1,8)
             $display("type 1: current position: %d", pos) ;
             $display("type 1: read data: %h", data4) ;
             //type = 2
             code = $fseek(fd, -(96-31), 2) ; //从位置31、坐标(3,4)开始读
             code = $fscanf(fd, "%h", data4); 
             pos  = $ftell(fd);      //读4byte后位置为35,坐标为(3,8)
             $display("type 2: current position: %d", pos) ;
             $display("type 2: read data: %h", data4) ;
    
             //rewind read
             code   = $rewind(fd) ;//重新将文件指针的位置指向文件首部
             pos    = $ftell(fd);  //此时位置为 0
             $display("Position after $rewind: %d", pos) ;
             //read all content of file
             while (!$feof(fd)) begin
                code   = $fgets(str_long, fd);
                $write("Read : %s", str_long) ;
             end
             $fclose(fd) ;
          end
       end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    仿真结果如下。

    由图可知 log 末尾多打了一行数据,这是因为文件 DATA_RD.TXT 末尾还有一行空白行(换行操作之后的结果),系统任务 $feof 并不认为该空白行为文件尾部,所以返回值仍然为 0。但实际该行并没有数据,所以读取的数据具有不可控制性。

    为消除文件最后一行数据中换行符的影响,可将"文件写入"例子中最后一个写文件系统任务 $fdisplay 替换为 $write 。

    其余 log 结合代码注释可知仿真正确,这里不再做统一解释。
    在这里插入图片描述
    加载存储器
    在这里插入图片描述
    文件 DATA_WITHNOTE.HEX 内容如下,将此文件的内容加载到存储器变量中。
    在这里插入图片描述

    //6 load mem
       reg [31:0]   mem_load [3:0] ;
       initial begin
          #50 ;
          $readmemh("./DATA_WITHNOTE.HEX", 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述

  • 相关阅读:
    ssm+vue的课程网络学习平台管理系统(有报告)。Javaee项目,ssm vue前后端分离项目。
    Centos7离线安装Mysql8(rpm安装)
    usb gadget configfs分析
    Servlet
    ActiveMQ用法
    Mybatis-Plus CRUD
    利用 Kubernetes 降本增效?EasyMR 基于 Kubernetes 部署的探索实践
    致敬最美抗击疫情的逆行者 DIV布局大学生抗疫感动专题网页设计作业模板 疫情感动人物静态HTML网页模板下载
    基于Qt HTTP应用程序项目案例
    Android-Framework 时间格式默认使用24小时制、时区为上海
  • 原文地址:https://blog.csdn.net/qq_43158059/article/details/133690202