对上篇的博客做个补充!
1. 一些概念
interface
和module
有类似的地方,都可以定义端口port
、定义function
和task
、使用initial
和always
;interface
用于连接硬件(DUT)和软件(验证环境),还可以简化模块之间的连接,为模块端口提供了标准化的封装方式;- 在
interface
中声明clocking
时钟块,可以避免delta cycle
问题,通过波形上的可见延迟帮助理解仿真时序; modport
只是对interface
中已经声明的信号再次声明方向;module
中可以例化module
,也可以例化interface
;interface
中可以例化interface
,不可以例化module
;
2. 举个例子接口怎么用
重新写一下之前的例子,以前记录的,好多细节不太清楚,重新复习,还是一位加法器
- modport用于对接口中的信号分组,并指定方向,后续模块使用接口可以直接例化modprt;
- modprot的参数列表可以是port,也可以是clocking;
- 对于激励产生部分simulus,验证环境要输出a、b、cin作为dut的输入;
- 对于加法器adder,她要接收验证环境的输出作为自己的输入;
- 对于监测器monitor,她会接收所有的端口作为自己的输入;
- 对于一个接口中的同一个信号,比如a,她是dut的输入,又是环境的输出,这就需要用modport添加方向限制;也就是说,信号的方向对于不同的作用对象可以是不一样的;
代码如下:
2.1. 声明接口adder_interface
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
- 在clocking cn中,利用时钟clk的下降沿触发采集输出事件,其实也是一种延迟;
2.2. 激励的产生
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
2.3. 加法器
module adder(adder_interface.adder port);
assign {port.cout, port.sum} = port.a + port.b + port.cin;
endmodule
3.4. 监测器
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
3.5. 顶层文件
`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
3. 分析仿真结果
上述代码直接仿真,可以看到监测器实在时钟下降沿触发打印的,因为打印发生在always @(mon.cn)
;
在实际电路中,组合逻辑都会有延迟,但是我们的仿真器不能够直接在波形上看到这个延迟时间,所有的信号都会在@(posedge clk)的时间变化,真是电路信号的变化都会在@(posedge clk)的前一点或者后一点;
vld是我们在仿真波形上看到的,信号的变化时间都是一样的,都在时钟上升沿;
vld_acutal是实际电路的变化;
vld_delay是我们可以clocking中指定偏移时间的结果,这样就可以直接在波形上看到这个偏差;
4. 在clocking中设置偏移时间
- clocking内的输入信号是对接口信号的采样,也就是dut的输出信号;
- clocking内的输出信号是对接口信号进行驱动,也就是dut的输入信号;
- 在clocking中用default可以设置输入输出的偏移时间;
clocking my_if(); default input #1 output #2; endclocking
- 在
clocking cp
里面使用defalut仿真时报错:注意想要在clocking中添加延时,必须要指定时间单位和时间精度,否则仿真报错;
其实我在top最上面已经加了timescales 1ns/1ps,但还是报错说需要指定时间单位和精度,这是因为timescales的作用范围没有用到interface中(因为我的各个module都是在独立的.sv中),在top中将interface.sv导入进来就行;
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
- 对于dut来说,a、b、cin作为激励的输入来得晚,那自己的计算输出cin、cout就算的晚;也就是我延后了激励的输出,延后了simulus的输出
-
上面的例子相当于有三个模块,激励发生部分、加法器、监测器三个模块,三个模块共用一个接口的实例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