SV语法中的打印信息
持续更新中
工作或学习中遇到的类似知识点会及时补充至此。
一、基础转义知识
字符 | 说明 |
---|---|
\n | 换行符 |
\t | 空一个TAB的距离 |
%h or %H or %x or %X | 以十六进制形式打印 |
%d or %D | 以十进制形式打印 |
%o or %O | 以八进制形式打印 |
%b or %B | 以二进制形式打印 |
%c or %C | 以ASCII码形式打印 |
%p or %P | 用于打印聚合表达式,如结构体、数组等 |
%s or %S | 以字符串形式打印 |
%t or %T | 打印当前时间 |
%e or %E | 以指数格式显示实数 |
%f or %F | 以小数形式打印,默认保留小数点后6位 |
二、$sformatf / $sformat (File I/O tasks and functions)
SV用户手册解释如下:
直接看代码:
module tb;
initial begin
string LP_str;
string LP_str1;
//--------$sformat方法--------
$sformat(LP_str,"value is = %0d",5);
$display("[$sformat method] xxx %s",LP_str);
//--------$sformatf方法--------
LP_str1 = $sformatf("value is = %0d",5);
$display("[$sformatf method] xxx %s",LP_str1);
//--------简写形式--------
$display($sformatf("[$sformatf method] xxx value is = %0d",5));
end
endmodule
打印数据如下:
- $sformat (最终输出变量,“字符串”,参数列表);
- $sformatf (“字符串”,参数列表);
可以看出 $ sformat( ) 相对于$ sformatf( )多出一个“最终输出变量”,若想用其字符串,直接把它的“最终输出变量”拿去使用即可;而对于 $ sformatf( ) 若想使用它的字符串,则需要将它整体拿走。
思考:如果我们将代码中的 %s 全部换成 %p 的形式,打印内容是否有变化呢?
打印数据如下:
从上图可看出,%s是会将双引号内的字符串打印出,而%p则是将双引号与其内部字符串当作整体合并打印出!
注:这两个函数并不具备打印的功能,只是负责整理字符串,真正打印的应是$display( )函数。
三、$display / $write (Display tasks)
SV用户手册解释如下:
$display
/$write
(参数列表);
但我们经常为了显示打印信息的具体解释,通常会这么做:
$display
/$write
(“需要打印的数据信息”,参数列表);
1、$display
直接看代码:
module tb;
initial begin
parameter YEAR = 2022;
$display("[testcase] This year is %0d ",YEAR);
$display("[testcase] This year is %d ",YEAR);
$display("[testcase] This year is %h ",YEAR);
$display("[testcase] This year is %b ",YEAR);
$display("[testcase] This year is %o ",YEAR);
end
endmodule
打印数据如下:
注:%d是指输出与系统默认位宽一致的整数,位宽不足时,前面用空格补齐;而%0d是根据实际位宽的整数来显示,不用空格补齐。
2、$write
直接看代码:
module tb;
initial begin
parameter YEAR = 2022;
$write("[testcase] This year is %0d ",YEAR);
$write("[testcase] This year is %d ",YEAR);
$write("[testcase] This year is %h ",YEAR);
$write("[testcase] This year is %b ",YEAR);
$write("[testcase] This year is %o ",YEAR);
end
endmodule
打印数据如下:
对比一下以上两个函数会发现:
$write
系统任务和 $display
系统任务的打印信息是相同的。但其区别是 $write
在打印输出时,没有换行的操作!
因此添加 “\n” 会使它们变得相同。
module tb;
initial begin
parameter YEAR = 2022;
$write("[testcase] This year is %0d\n ",YEAR);
$write("[testcase] This year is %d\n ",YEAR);
$write("[testcase] This year is %h\n ",YEAR);
$write("[testcase] This year is %b\n ",YEAR);
$write("[testcase] This year is %o\n ",YEAR);
end
endmodule
打印数据如下:
这印证了SV用户手册的描述
3、$displayb / $displayo / $displayh / $writeb / $writeh / $writeh
以 $displayb
/ $displayo
/ $displayh
为例,$writeb
/ $writeh
/ $writeh
类同。
形如 $display
中代码描述的那样,若我们想将10进制的 2022 打印为用16进制形式表示的 000007e6 ,我们需在 “需要打印的数据信息” 中指定其为 %h 才可以。
但如果使用 $displayb
/ $displayo
/ $displayh
,就会省去这一步骤:
module tb;
initial begin
parameter YEAR = 2022;
$displayh("[testcase] This year is ",YEAR);
$displayo("[testcase] This year is ",YEAR);
$displayb("[testcase] This year is ",YEAR);
end
endmodule
打印信息如下:
$writeb
/ $writeh
/ $writeh
则不再赘述。
四、$fopen / $fclose/ $fwrite / $fscanf / $feof (File I/O tasks and functions)
SV用户手册对于 $ fopen 与 $ fclose 解释如下:
从上图可看出 $ fopen 为文件 打开 函数;
从上图可看出 $ fclose 为文件 关闭 函数。
DV过程中,当我们在编写测试用例 (TestCase) 时,有时会需要从DUT的输出端口获得大量数据,如果将数据全部打印至 .log 文件中或显示在命令行里,均可能会与要打印的其它类型的数据掺杂在一起,显得冗余度较高、杂乱无章,因此将这些同一种类的数据打印至属于各自单独的文件中,则显得美观且易于查看。
当接收来自DUT的数据时,此时意味着要对该文件进行 写 操作。在此之前,首先应对文件进行 打开(open) 操作,根据上图可知,需要使用到的表达式如下:
fd = $fopen (filename , type);
【fd 指 file_desc,为该文件的句柄。若该句柄为0,则打开文件失败,反之成功)。filename
为该文件的名称。需注意:1.该文件必须放在仿真工程的目录中;2.若在TestCase中调用该函数,须用“/”来定位该文件具体存在的路径。type 指以什么样的形式打开此文件。例如想在TestCase同级的out目录下生成名称为pengpengpeng的 .txt 形式的文件,即写为 fd1 = $fopen ("./out/pengpengpeng.txt",“w”) ; 即可)】
其中 type 类型部分说明如下表所示:
type类型 | 说明 |
---|---|
w | 打开新的ASCII文本文件,只写文件 |
r | 打开已存在的ASCII文本文件,只读文件 |
r+ | 打开已存在的ASCII文本文件,读写文件 |
a | 打开ASCII文本文件,在打开的文件结尾处 继续添加内容 |
ab | 打开Binary二进制文本文件,在打开的文件结尾处 继续添加内容 |
打开文件后,需对文件进行 写 操作,因此选用 $ fwrite 函数将从DUT输出的数据写入至该文件中,SV用户手册对于 $ fwrite 解释如下:
从以上两图结合前文对 $fopen 的解释,遂转义如下所示:
$ fwrite (文件句柄,“以什么进制与格式写入”,参数列表);
因此 整个数据 写入 文件 的流程应为:
`timescale 1ns/1ps
module tb;
integer fd1 ;//声明句柄
reg [7:0] data1 ;//DUT输出数据
//---打开文件---
initial begin
fd1 = $fopen("./out/pengpengpeng.txt","w");
end
//---写入数据---
always @(posedge sysclk)begin
$write(fd1,"%h\n",data1);
end
endmodule
当需要从文件中读取DUT的数据时,此时意味着要对该文件进行 读 操作。在此之前,首先应对写入完毕的文件进行 关闭(close) 操作,只有进行了关闭操作后,文件才会被写入并保存,根据上图可知,需要使用到的表达式如下:
$fclose (fd);
【fd 指 file_desc,为该文件的句柄。若该句柄为0,则打开文件失败,反之成功)例如想关闭句柄为 fd1 的文件,即写为 $fclose (fd1) ; 即可)】
关闭文件后,需对该文件进行 读 操作,首先应对文件进行 打开操作 ,然后调用 $ feof 函数判断文件是否被系统读取完,最后调用 $ fscanf 函数将数据从文件中读取出,SV用户手册对于 $ feof 与 $ fscanf 解释如下:
举个例子:
举个例子:
从以上四张图片遂转义如下所示:
$ feof (文件句柄);
$ fscanf (文件句柄,“以什么进制与格式读出”,参数);
因此 整个从文件 读取 数据 的流程应为:
`timescale 1ns/1ps
module tb;
integer fd2 ;//声明句柄
integer C1 ;//声明句柄(可省略)
reg [7:0] data2 ;
initial begin
//---先关闭写入好的文件---
$fclose(fd1);
//---打开要读的文件,且变为 r---
fd2 = $fopen("./out/pengpengpeng.txt","r");
//---判断系统是否已读取完毕
while(!$feof(fd2))begin
//---开始读取数据
C1 = $fscanf(fd2,"%h\n",data2)
end
end
endmodule
注意:如需要将所读取到的数据打印出,依旧需选择$display ( )函数!
五、$time / $realtime / $stime (Simulation time functions)
SV用户手册解释如下:
1、$time
简言之:
$time
返回的是 64bits 的 整数 (四舍五入取整) 。
举个例子:
`timescale 1ns/100ps
module tb;
logic [4:0] L;
parameter p1 = 1.43;
parameter p2 = 1.44;
initial begin
#p1 L = 6;
$display($time,"\n L value is %d",L);
#p2 L = 8;
$display($time,"\n L value is %d",L);
end
endmodule
打印出的数据为:
再来看一组:
`timescale 1ns/100ps
module tb;
logic [4:0] L;
parameter p1 = 1.47;
parameter p2 = 1.49;
initial begin
#p1 L = 6;
$display($time,"\n L value is %d",L);
#p2 L = 8;
$display($time,"\n L value is %d",L);
end
endmodule
打印出的数据为:
综上两组可以看出:
第一次打印的时间取决于 `timescale 1ns/100ps 中的 时间单位 (1ns) !不管是 1.43四舍五入到1 还是 1.47四舍五入到2 都是围绕着时间单位1ns来转换的;
第二次打印的时间 = 第一次延时的时间 + 第二次延时的时间的值,即 1.43ns + 1.44ns = 2.87ns ≈ 3ns / 1.47ns + 1.49ns = 2.96ns ≈ 3ns。
2、$stime
简言之:
$stime
返回的是 32bits 的 整数 时间单位 (四舍五入取整) 。
举个例子:
`timescale 1ns/100ps
module tb;
logic L;
parameter p = 1.63;
initial begin
#p L = 6;
$display($stime,"L value is %d",L);
#p L = 8;
$display($stime,"L value is %d",L);
end
endmodule
打印出的数据为:
可以看出第一次打印的时间为2ns (因为时间单位为1ns,且必须为整数,所以四舍五入最后得出);
可以看出第二次打印的时间为3ns (1.63ns+1.63ns=3.26ns,四舍五入且为整数最后得出);
$stime
相对于 $time
从打印的数据中可以看出,表示时间的数值前的空格数减少了一半,这也是它们之间 唯一的区别 :返回的整数位宽,一个为64bits,另一个则为32bits 。
3、$realtime
简言之:
$realtime
返回的是 实数 。
举个例子:
`timescale 1ns/100ps
module tb;
logic L;
parameter p = 1.67;
initial begin
#p L = 6;
$display($realtime,"L value is %d",L);
#p L = 8;
$display($realtime,"L value is %d",L);
end
endmodule
打印出的数据为:
再来看一组:
`timescale 1ns/100ps
module tb;
logic [4:0] L;
parameter p1 = 1.87;
parameter p2 = 1.47;
initial begin
#p1 L = 6;
$display($realtime,"\n L value is %d",L);
#p2 L = 8;
$display($realtime,"\n L value is %d",L);
end
endmodule
打印出的数据为:
综上两组可以看出:
第一次打印的时间取决于 `timescale 1ns/100ps 中的 时间精度 (100ps) ! 都是在0.1ns,也就是100ps的精度上实现了四舍五入的进位操作;
第二次打印的时间相对于 $ time 与 $ stime的计算有着 显著差异!, 即第二次打印出的时间 = 第一次延时的进位操作后的值 + 第二次延时的进位操作后的值,即 1.7ns + 1.7ns = 3.4ns / 1.9ns + 1.4ns = 3.4ns。
六、$finish / $stop / $exit (Simulation control tasks)
SV用户手册解释如下:
1、$finish
SV用户手册关于 $finish
解释如下:
简言之:该函数会直接导致模拟器的
退出
。
当仿真遇到 $finish
时,会提示如下对话框:
如果选“是”,会直接退出Questasim;如果选“否”,指令行会出现代码是在哪一行执行了 $finish
2、$stop
SV用户手册关于 $stop
解释如下:
简言之:该函数会直接导致模拟器的
暂停
。
暂停后若想继续跑仿真,点击“continuerun”就完事了:
3、$exit
SV用户手册关于 $exit
解释如下:
简言之:在一个或多个
program
块中,当所有program中最后一个initial
块结束的时候,仿真实际也默认结束了,就像执行了$finish
一样。若其中一个initial
中包含forever
语句,则意味着仿真永远不会停下,但我们又想让其强行停下之后继续其他未完成的initial
块或program
块操作时,就必须调用$exit
!
未完待续~