在上一节《程序和模块》中我们提到了各个设计自身可以作为一个大的线程,内部又包含多个并行的线程,而模块之间即线程的通信主要依靠信号的变化。可以想象,对于一个设计,如果在仿真开始没有任何的激励,譬如时钟和复位信号,那么仿真并未开始,也可以认为已经结束,因为对于设计内部并没有产生任何新的事件,也不会由这些事件进一步触发组合逻辑和时序逻辑。
那么,如果我们在仿真开始后提供时钟和复位信号,这对于验证而言是必要的步骤,但是它本身不会对设计的功能产生实质的功能影响。从设计的角度来看,复位信号只是为了让设计进入确定的状态,而时钟信号是如同血管的供血功能一般保证设计可以正常地“跳动”。
在Verilog的测试方式中,即便我们只给设计提供复位和时钟信号,整个仿真也会一直持续下去,并不会主动结束。即使DUT的输入激励已经执行完毕,仿真也会一直进行下去,这就需要通过Verilog系统函数主动结束仿真。
结束方式之一:系统函数
在Verilog测试中,需要通过Verilog提供的系统函数来结束仿真。下面的例子即在仿真500ns时通过系统函数$finish结束了仿真,而用户也看可以考虑使用$stop来暂停仿真。这两者的区别在于$finish会使得仿真退出,将控制权交回给操作系统,仿真无法再次继续;$stop会使得仿真暂停,用户还有机会让仿真继续运行。
module tb;
bit clk;
initial begin
forever #5ns clk <= !clk;
end
counter dut(clk);
initial begin
#500ns;
$finish();
end
endmodule
执行结果:
结束方式之二:program隐式结束
在SV推出program将验证部分与设计部分进行有效隔离以后,SV也会将每一个program块作为一个独立的测试,如果testbench中只有一个program,则会在执行完该program中最后一个initial后自动结束仿真。如果testbench中有多个program,那么需要等待所有program中最后一个initial才能结束仿真。
program pgm1;
initial begin: proc1
#100ns;
$display("@%0t p1.proc1 finished", $time);
end
initial begin: proc2
#400ns;
$display("@%0t p1.proc2 finished", $time);
end
endprogram
program pgm2;
initial begin: proc1
#200ns;
$display("@%0t p2.proc1 finished", $time);
end
initial begin: proc2
#300ns;
$display("@%0t p2.proc2 finished", $time);
end
endprogram
module tb;
bit clk;
initial begin
forever #5ns clk <= !clk;
end
counter dut(clk);
pgm1 p1();
pgm2 p2();
endmodule
执行结果:
# @100 p1.proc1 finished
# @200 p2.proc1 finished
# @300 p2.proc2 finished
# @400 p1.proc2 finished
从上面这个例子可以看到, 仿真会在p1.proc2最后执行完毕后自动结束。
结束方式之三:program显式结束
从上面的第二种结束方式来看,要求仿真自动结束的前提是所有program的initial块都应该在一定时间内完成,而实际上有的program内的initial语句块会一直运行下去,这就使得仿真无法等到所有的program均执行完毕,也就无法自动结束。这时候,我们可以在目标program内置入系统函数$exit来要求该program强行结束,待该program结束之后,仿真器仍然会等待其它program执行完毕后再结束仿真。
program pgm1;
initial begin: proc1
#100ns;
$display("@%0t p1.proc1 finished", $time);
end
initial begin: proc2
#200ns;
$display("@%0t p1.proc2 finished", $time);
end
endprogram
program pgm2;
initial begin: proc1
#700ns;
$display("@%0t p2.proc1 finished", $time);
$exit();
end
initial begin: proc2
forever begin
#300ns;
$display("@%0t p2.proc2 loop", $time);
end
end
endprogram
module tb;
bit clk;
initial begin
forever #5ns clk <= !clk;
end
counter dut(clk);
pgm1 p1();
pgm2 p2();
endmodule
执行结果:
# @100 p1.proc1 finished
# @200 p1.proc2 finished
# @300 p2.proc2 loop
# @600 p2.proc2 loop
# @700 p2.proc1 finished
从上面的例子可以看到p2由于有forever loop proc2,本身无法正常结束,所以仿真是无法自动结束的。这时,我们可以在p2内的proc1(任何一个initial块)置入一个系统函数$exit()。该系统函数的作用是可以强制结束它所在的program,这使得在proc1在700ns结束之后p2就结束了,而仿真器仍然会 统览其它的program2:p1,发现所有的program均执行完毕,于是就自动结束仿真了。
通过上面三种在SV中结束仿真的方式,我们可以更便利地控制仿真的结束,也能在更深的层次掌握仿真结束的机制。
至此,我们本篇《SV环境构建篇》就介绍完毕了,通过本篇的学习,读者可以在开始搭建“测试房子”之前懂得如何与设计做恰当的连接、模块的例化、验证与设计部分的隔离和结束测试的方式。
我们下一篇《SV组件实现篇》将对之前做模块验证的几位验证者做一线跟拍,看看他们是如何实现它们的验证组件和环境的。
谢谢你对路科验证的关注,也欢迎你分享和转发真正的技术价值,你的支持是我们保持前行的动力。