在组合逻辑中,同一个信号由于不同路径的延迟不同,到达电路某一汇合点的时间会有先后,就会产生竞争,从而产生一个尖峰脉冲。systemverilog为了避免竞争的问题,引入program,所有与设计相关的线程在module内执行,所有与验证有关的线程在program内执行。program内所有的元素都是在reactive region内执行,而module内的非堵塞赋值在active region,二者隔离开来,从而避免了竞争。
program的声明形式如下:
program test(input clk, input [7:0] addr, output [7:0] wdata);
endprogram
or
program test (interfece mem_intf);
endprogram
//design code
module design_ex(output bit [7:0] addr);
initial begin
addr <= 10;
end
endmodule
//testbench
module testbench(input bit [7:0] addr);
initial begin
$display("\t Addr = %0d", addr);
end
endmodule
//testbench top
module tbench_top;
//design instance
wire [7:0] addr;
design_ex dut(addr);
//testbench instance
testbench test(addr);
endmodule
module版本,下例中dut模块design_ex,initial块用非阻塞赋值驱动addr为10,而在testbench模块中,initial采样addr的值打印输出。这个时候,就存在先非阻塞赋值还是先采样打印的问题了,即竞争。通过verilog仿真事件队列,我们知道display处于active事件,会先执行,而非阻塞赋值的更新会后执行,因此addr打印为0.执行结果:
//design code
module design_ex(output bit [7:0] addr);
initial begin
addr <= 10;
end
endmodule
//testbench
program testbench(input bit [7:0] addr);
initial begin
$display("\t Addr = %0d", addr);
end
endprogram
//testbench top
module tbench_top;
wire [7:0] addr;
//design instance
design_ex dut(addr);
//testbench instance
testbench test(addr);
endmodule
而program版本,则是先执行module内的语句,然后执行program的语句打印结果,另外program把initial执行完后,会结束仿真。
在verilog中,不同的块之间的交流是通过模块的端。systemverilog添加了interface,封装了模块之间的通信。它把testbench和dut的信号和连线捆绑在一起。下图,是我们verilog testbench和dut的连接关系
下面这个图展示了有interface的design和testbench的连线。
一个简单的接口声明如下:
interface interface_name;
...
interface items
...
endinterface
interface intf;
//declaring the signals
logic [3:0] a;
logic [3:0] b;
logic [6:0] c;
endinterface //intf
module tb;
//creating the instance of interface
intf i_intf();
//DUT instance
adder DUT(
.a(i_intf.a),
.b(i_intf.b),
.c(i_intf.c)
);
initial begin
i_intf.a = 6;
i_intf.b = 8;
$display("Value of a = %0d, b= %0d", i_intf.a, i_intf.b);
#5;
$display("Sum of a and b, c = %0d", i_intf.c);
i_intf.a = 3;
i_intf.b = 8;
$display("Value of a = %0d, b= %0d", i_intf.a, i_intf.b);
#5;
$display("Sum of a and b, c = %0d", i_intf.c);
end
endmodule
module adder(
input [3:0] a ,
input [3:0] b ,
output [6:0] c
);
assign c = a + b;
endmodule
sv还可以用virtual 修饰interface,虚接口virtual interface表示一个接口实例。虚接口必须先初始化才能使用,即必须连接或者指向实际的接口,访问没有初始化的虚接口会导致运行时错误。虚接口也可以作为类成员,可以通过new方法初始化。虚接口变量可以作为参数传递给任务、函数和方法。通过虚接口句柄virtual_interface.variable可以访问所有的接口变量和方法。一个虚接口变量,可以在仿真时的不同时刻代表不同的接口实例。
虚接口仅支持操作符=,==,!=三种,另一个操作数可以是相同类型的虚接口,相同类型的接口实例或者空null。
我们的接口作为连接testbench和dut的连线,本质上是静态的,而类是动态的。因此我们不能在类内声明接口,但可以用一个变量指向接口。虚接口就是类访问接口信号的帮手。
interface intf;
//declaring the signals
logic [3:0] a ;
logic [3:0] b ;
logic [6:0] c ;
endinterface
class environment;
//virtual interface
virtual intf vif;
//constructor
function new(virtual intf vif);
//get the interface from test
this.vif = vif;
endfunction
//run tasks
task run;
vif.a = 6;
vif.b = 4;
$display("Value of a = %0d, b = %0d", vif.a, vif.b);
#5;
$display("Sum of a and b, c = %0d", vif.c);
$finish;
endtask
endclass
program test(intf i_intf);
//declaring environment instance
environment env;
initial begin
//creating environment
env = new(i_intf);
//calling run of env
env.run();
end
endprogram
module tbench_top;
//creating instance of interface
intf i_intf();
//testcase instance
test t1(i_intf);
//DUT instance,interface signals are connected to the DUT ports
adder DUT(
.a(i_intf.a),
.b(i_intf.b),
.c(i_intf.c)
);
endmodule
module adder(
input wire [3:0] a ,
input wire [3:0] b ,
output wire [6:0] c
);
assign c = a + b;
endmodule
来看看我们改版的testbench,首先定义接口,把dut的端口加入,接口把变量设置为logic类型,并给出位宽。然后定义类environment,该类包含虚接口vif,构造函数将传入的虚接口赋值给类内虚接口成员,还定义了任务run,run通过虚接口访问接口的信号,对其赋值给出激励。在program test中,传入虚接口,声明一个environment句柄,在initial块将其实例化,然后运行其方法run。
在tbench_top中,代码就很简洁了,创建接口实例,然后将其作为参数传入test中,就是我们的testcase。然后利用接口实例连接dut
执行结果如下:
波形如下: