interface
和module
有类似的地方,都可以定义端口port
、定义function
和task
、使用initial
和always
;interface
用于连接硬件(DUT)和软件(验证环境),还可以简化模块之间的连接,为模块端口提供了标准化的封装方式;interface
中声明clocking
时钟块,可以避免delta cycle
问题,通过波形上的可见延迟帮助理解仿真时序;modport
只是对interface
中已经声明的信号再次声明方向;module
中可以例化module
,也可以例化interface
;interface
中可以例化interface
,不可以例化module
;重新写一下之前的例子,以前不知道从哪抄来的,好多细节不太清楚,重新复习,还是一位加法器
代码如下:
interface adder_interface(input bit clk);
logic a; // declare port signal
logic b;
logic cin;
logic cout;
logic sum;
clocking cp @(posedge clk); // declare which signals are triggered at the rising edge of the clk
output a, b, cin;
endclocking
clocking cn @(negedge clk); // declare which signals are triggered at the falling edge og the clk
input a, b, cin, cout, sum;
endclocking
modport simulus (clocking cp);
modport adder (input a, b, cin, output cout, sum);
modport monitor (clocking cn);
endinterface
module simulus(adder_interface.simulus port);
always @(port.cp) begin
port.cp.a <= $random() % 2;
port.cp.b <= $random() % 2;
port.cp.cin <= $random() % 2;
end
endmodule
module adder(adder_interface.adder port);
assign {port.cout, port.sum} = port.a + port.b + port.cin;
endmodule
module monitor(adder_interface.monitor mon);
always @(mon.cn) begin
$display("%0t: %d + %d + %d = %d %d", $time, mon.cn.a, mon.cn.b, mon.cn.cin, mon.cn.cout, mon.cn.sum);
end
endmodule
`timescale 1ns/1ps;
module top();
bit clk = 0;
always #10 clk = ~clk;
adder_interface adder_vif(clk); // 在test中例化接口
simulus sim(adder_vif.simulus);
adder add(adder_vif.adder);
monitor mon(adder_vif.monitor);
endmodule
上述代码直接仿真,可以看到监测器实在时钟下降沿触发打印的,因为打印发生在always @(mon.cn)
;
在实际电路中,组合逻辑都会有延迟,但是我们的仿真器不能够直接在波形上看到这个延迟时间,所有的信号都会在@(posedge clk)的时间变化,真是电路信号的变化都会在@(posedge clk)的前一点或者后一点;
vld是我们在仿真波形上看到的,信号的变化时间都是一样的,都在时钟上升沿;
vld_acutal是实际电路的变化;
vld_delay是我们可以clocking中指定偏移时间的结果,这样就可以直接在波形上看到这个偏差;
clocking my_if();
default input #1 output #2;
endclocking
clocking cp
里面使用defalut仿真时报错:注意想要在clocking中添加延时,必须要指定时间单位和时间精度,否则仿真报错; clocking cp @(posedge clk); // declare which signals are triggered at the rising edge of the clk
default ouput #2;
output a, b, cin;
endclocking
`timescale 1ns/1ns;
`include "adder_interface.sv"
module top();
// timeunit 1ns;
// timeprecision 1ns;
bit clk = 0;
always #10 clk = ~clk;
adder_interface adder_vif(clk);
simulus sim(adder_vif.simulus);
adder add(adder_vif.adder);
monitor mon(adder_vif.monitor);
endmodule
上面的例子相当于有三个模块,激励发生部分、加法器、监测器三个模块,三个模块共用一个接口的实例adder_vif,我在工作中暂时没有遇到过这种共用的用法,基本都是一个模块用一个接口;
关于clocking我在工作中暂时也没有用过,跑后仿的时候不知道需不需要查看detal cycle;
上面加法器的举例,不是很好,所有的模块相当于都是硬件环境,没有做到验证环境(软件环境)和硬件环境的分离
假设我想在验证环境中让dut打印时间后延,我可以在interface中的clocking中指定,也可以在验证环境中的task中手动添加延迟,规定在时钟上升沿后1ns就打印结果;
while(1) begin
@(posedge adder_vif.clk);
#1ns;
$display("%0t: %d + %d + %d = %d %d", $time, adder_vif.a, adder_vif.b, adder_vif.cin, adder_vif.cout, adder_vif.sum);
end
假如我的加法器有一个工作使能信号en,只有在en拉高的情况下,加法器才会计算,并输出结果,下面有两种写法。由于en存在连续两拍都拉高的情况,所以不能用边沿检测的方法监测en,只能用电平检测;
写法1:实际波形会在en拉高的下一拍,因为对输入数据的采样会以时钟上升沿前的为准;对于en信号的检查发生在一个时钟周期的结束,这有可能不是我们想要的;
while(1) begin
@(posedge adder_vif.clk);
if(adder_vif.en == 1)
$display("%0t: %d + %d + %d = %d %d", $time, adder_vif.a, adder_vif.b, adder_vif.cin, adder_vif.cout, adder_vif.sum);
end
写法2:手动添加延迟,我在每一个时钟周期内去检查,这样就可以保证实时,当拍打印当拍的结果;
while(1) begin
@(posedge adder_vif.clk);
#1ps; // 手动添加延时
if(adder_vif.en == 1)
$display("%0t: %d + %d + %d = %d %d", $time, adder_vif.a, adder_vif.b, adder_vif.cin, adder_vif.cout, adder_vif.sum);
end