systemverilog中的program与module
systemverilog中的调度
PLI(Programming Language Interface)
用来调用其它语言的函数的接口,比如C/C++)Active Region set
包含Active Region、Inactive Region、NBA Region,这是专门为RTL代码执行所设立的区域集合(set)Reactive Region set
包含了Reactive Region、Re-Inactive Region、Re-NBA Region,这是专门为验证平台所设计的区域集合Observed Region
专门为断言所设计的区域。
- Preponed Region:采样数据,为断言做准备。
- Observed Region:使用Preponed Region采样到的值来计算断言。
- Reactive Region:
- 所有定义在program内的阻塞赋值。
- 执行断言pass/fail代码?
- 所有定义在program内的非阻塞赋值RHS的计算,并将相应的更新事件调度至Re-NBA Region。
- 所有program内的连续赋值。
- 执行系统函数 e x i t 及隐式的 exit及隐式的 exit及隐式的exit命令。
其实仔细看下,和Active Region内执行事件是很类似的,只不过加了program的限定。
4. Re-Inactive Region:program内#0延迟的进程。
program的例子
为避免仿真和设计竞争问题(race condition),systemverilog中引入了program的概念
module counter(input clk);
bit [3:0] cnt;
always @(posedge clk) begin
cnt <= cnt + 3;
$display("%0t DUT cnt = %0d", $time, cnt);
end
endmodule
module tb1;
bit clk;
bit [3:0] cnt;
initial begin
forever #5ns clk <= ~clk;
end
counter counter_inst(clk);
always @(posedge clk) begin
$display("%0t TB cnt = %0d", $time, counter_inst.cnt);
end
endmodule
// 仿真结果如下
// 因为打印函数处于active调度区域,非阻塞赋值处于NBA调度区域,因此采样到的是变化前的值,即#5时采样得到的是0不是1,其他同理。
run 50ns
# 5 DUT cnt = 0
# 5 TB cnt = 0
# 15 DUT cnt = 3
# 15 TB cnt = 3
# 25 DUT cnt = 6
# 25 TB cnt = 6
# 35 DUT cnt = 9
# 35 TB cnt = 9
# 45 DUT cnt = 12
# 45 TB cnt = 12
如果我们把仿真激励改为如下:
module tb2;
bit clk1;
bit clk2;
bit [3:0] cnt;
initial begin
forever #5ns clk1 <= !clk1;
end
always @(clk1) begin
clk2 <= clk1;
end
counter dut(clk1);
always @(posedge clk2) begin
$display("%0t TB cnt = %0d", $time, dut.cnt);
end
endmodule
// 仿真结果如下
// 这是因为clk2和clk1之间存在非阻塞赋值,赋值区域在NBA区,按照先后顺序,得到clk2值后再打印tb cnt。
run 50ns
# 5 DUT cnt = 0
# 5 TB cnt = 3
# 15 DUT cnt = 3
# 15 TB cnt = 6
# 25 DUT cnt = 6
# 25 TB cnt = 9
# 35 DUT cnt = 9
# 35 TB cnt = 12
# 45 DUT cnt = 12
# 45 TB cnt = 15
两个例子:一个采样得到的是变化前的值,一个得到的是变化后的值。
因此我们如果Testbench中也一味地使用module,就有可能出现上述第二种问题。我们需要能控制住采样时刻。那么如果我们有时候需要采样第二种情况,难道每次都需要这样做吗?使用两个采样信号?
我们可以使用program:此时仿真结果和第二次一致,这是因为program的采样是在reactive中进行的,此时数据已经是变化后的稳定值,不会出现竞争的情况。
module counter(input clk);
bit [3:0] cnt;
always @(posedge clk) begin
cnt <= cnt + 1;
$display("@%0t DUT cnt = %0d", $time, cnt);
end
endmodule
program dsample(input clk);
initial begin
forever begin
@(posedge clk);
$display("@%0t TB cnt = %0d", $time, dut.cnt);
end
end
endprogram
module test_tb_top;
bit clk1;
bit [3:0] cnt;
initial begin
forever #5ns clk1 <= !clk1;
end
counter dut(clk1);
dsample spl(clk1);
endmodule
因此,我们一般推荐在Testbench中使用program
,在设计dut中使用module
,在顶层module中例化dut的module和 testbench的program。
program中的注意点:
- program中不能例化其他program和module
- program中不能出现interface和always,可以使用initial forever替代always
- program内部可以发起多个initial块
- program中内部定义的变量最好采用阻塞赋值,当然采用非阻塞仿真器也不会产生error,驱动外部信号则应该采用非阻塞赋值
- program中的initial块和module中的initial块执行位置不同,前者在reactive,后者在active块中执行。
- program中存在的多个initial块中,如果有一个initial采用了退出系统函数$exit(),则会结束该program,而不仅仅是该initial块。