我们常常会在testcase,scoreboard ,driver,monitor甚至 ref中用到一些简单的延迟,比如说在testcase的main_phase结束时,我们发现scoreboard并没有将所有的数据都比对完,我们有时候为了方便,偷懒在main_phase的结尾处使用#1000,等待“1000ns” 等待scoreboard将所有的数据都比对完成(这个问题其实有更好的解决办法,我们不在这里讨论)。但是这里的#1000,真的是等待1000ns吗? 真的是按照我们的真实意图延迟1000ns吗?
接下来我们来探讨一下仿真中的时间概念,既,timescale,timeunit ,timeprecesion。
1 `timescale timeunit/timeprecision
我们知道在定义timescale时,timeunit代表的是仿真的时间单位,而timeprecesion代表的是时间精度,对于时间单位,通俗点讲就代表了我们#123.4567 中的123,代表我们延迟中的整数部分,如果我们`timescale设置了1ns/1ps ,整数部分123代表了延迟123ns,而小数部分:0.4567 表示延迟了 456ps + 0.7ps,由于精度是1ps,所以0.7ps按照四舍五入为 1ps,最后整个延迟时间为123457ps,这便是我们“通常”使用timescale时感受到的结果。
但在实际中真的是如此吗?
通过翻阅sv的IEEE标准和一些网上的文章以及自身的测试与试验发现事情并不是我们想的那样,通常来说,timescale具体设置的时间单位和时间精度使用离自己最近的文件的timescale,也就是说,在一个工程中,不同的文件可能由不同的人维护,每个人对自身维护的部分 timescale设置可能都不尽相同(RTL代码中的timescale也会影响到仿真中的timescale),而整个工程最后都编译完成后,我们是无法预测当前文件与之关联的“最近”的一次 timescale编译时到底设置的是多少,那么我们在testcase中写 #1000,就没有办法保证是1000ns的绝对延迟了。关于编译顺序对timescale的影响这个问题,有很多这方面的文档,自己也可以在测试工程上简单的测试出这个问题。
那么我们写#1000ns这次总该对了吧?
不好意思,我在vcs2018,2019上都经过测试发现,#1000ns这样的绝对延迟在VCS上仿真时貌似存在bug:
1,当我在testcase最上面加上timescale 1ns/1ps 时,$printtimescale 也是 1ns/1ps,#1000ns是真实的1000ns延迟。
2,当我在testcase最上面加上timescale 1ps/1ps 时,$printtimescale 却是 1ns/1ps,这个应该是在仿真时没有提供有效的timescale VCS自动安排的timescale,但是接下来神奇的事情发生了,#1000ns,变成了 #1000x1000ns , #1000ns 竟然扩大了1000倍!。
3,当我在testcase最上面加上timescale 10ns/1ps 时,#1000ns,变成了 #1000/10ns , #100ns 竟然缩小了10倍!。
4,这一点是我没法接受的,当我在testcase上面去掉 timescale 后,$printtimescale 还是 1ns/1ps,这个没有问题,可是#1000ns 仍然变成了 #1000x1000ns,
我暂时还不知道造成这一结果的原因是什么。。。。。
其实通过上面这块我们也能比较清楚的认识到,只要我们在每个class ,module,package等等最开始添加timescale ,那么至少在本模块内的最小时间单位,就是timescale的 timeunit,这样我们在使用#100,#1000,这样的延迟时,就能明确到底延迟的准确时间是多少?
2 两个重要函数
1,$printtimescale(path) path为制定路径的文件,与uvm中config_db set的路径类似,当然这个路径也可以为空,就代表你当前的文件路径了。调用这个函数之后,在log上会看到编译这个文件时使用的timescale是什么。
2,$timeformat 先来看一张参数图:
这个函数的参数说明如下:$timeformat(单位,精度,后缀,最小位宽);我们来看一个具体的例子:$timeformat(-9,3,"ns",5) 四个参数分别代表:
时间单位(精度)为-9 即 “ns”,
精确到小数点后3位,
后缀显示时间单位位纳秒“ns”,
显示最小位宽为5(和打印的%5d类似),
这个函数通常会出现在tb_top中,规定这个仿真环境的时间打印格式。
3,时间配置作用域
IEEE上对验证环境时间的配置的作用域上看,整体可以分为5个维度:module、program、interface、package,其他的归为一个域(比如各种class等)称之为compilation-unit scope。什么含义呢?也就说每一个module/program/interface/package都可以指定自己的timeunit/timeprecision,而所有的class/全局的function/task等归在一起的scope整体只能有一个timeunit/timeprecision。这个有什么用呢,我们后面有用到。
4,关键字timeunit/timeprecision
这个通常就是我们在 module program 或者 package中遇到的 timeunit 1ns;timeprecision 1ps; 这种东西,这两个参数与我们timescale中 所说到的仿真时间单位和时间精度是相同的。但是为什么会单独出现关键字的设置,我现在还没搞明白。不过刚刚我们说了,对于module等有一个单独的时间作用域,我们是否能够通过单独的时间作用域来产生精确地延迟呢,那当然必须是可以的!
5,构建精确的时间延迟
这里我们使用module来构建我们的精确延迟模块,代码如下:
module delay_gen();
timeunit 1ps;
timeprecision 1ps;
task delay_ps (int delay_time);
#delay_time;
endtask
task delay_ns (int delay_time);
#(delay_time * 1000);
endtask
task delay_us (int delay_time);
#(delay_time * 1000 * 1000);
endtask
endmodule
将该module include到工程中来,并在tb_top中将该模块例化进来:
module tb_top();
······
delay_gen delay_gen_inst();
······
endmodule
如果要在某个模块内使用,可以直接通过路径进行引用: sim_top.tb_top.delay_gen_inst.delay_ns(1000); 这样我们便得到了一个精确地1000ns的延迟。不需要关心其他模块将timescale改成了多少,你都可以自己定义自己最精确的 time_delay。
override_timescale
-timescale 1ps/1ps +ncoverride_timescale
使用这个命令在irun工具仿真时,irun将强制将所有的timescale都设置为 1ps/1ps,这样看起来很爽,但是对于不同的模块需要不同timescale时,就显得太暴力了,也很容易制造混乱,我们就让他保持不同吧,毕竟每个人都有自己的想法,在允许的范围内,保证各自代码的正确性即可。