module multiplier(mult_if.RTL inf);
always@(posedge inf.clk or posedge inf.reset) begin
if(inf.reset) begin
inf.out <= 0;
inf.ack <= 0;
end
else begin
if(inf.en) begin
inf.out <= inf.a * inf.b;
inf.ack <= 1;
end
else begin
inf.out <= inf.out;
inf.ack <= 0;
end
end
end
endmodule
interface mult_if (input logic clk, reset);
logic [7:0] a, b;
logic [15:0] out;
logic en;
logic ack;
clocking cb @(posedge clk);
default input #7 output #6;
input out, ack; // 这里数据宽度不是必要的
output a,b, en;
endclocking
// 这里的default input #7 output #6指的是对于@(posedge clk)的偏移,而非对于信号的延迟
modport TB (clocking cb, input clk, reset);
modport RTL (input clk, reset, a,b, en, output out, ack);
endinterface
module tb_top;
bit clk;
bit reset;
always #2 clk = ~clk;
initial begin
clk = 0;
reset = 1;
#2;
reset = 0;
end
mult_if inf(clk, reset);
multiplier DUT(inf);
`define TB_IF inf.cb
initial begin
$monitor("postponed time = %0t:\ta=%d\tb=%d\tout=%d\ten=%d\tack=%d\tTB_IF.out=%d\tTB_IF.ack=%d", \
$time, inf.a,inf.b,inf.out,inf.en,inf.ack,`TB_IF.out,`TB_IF.ack);
#1;
`TB_IF.a <= 'd20;
`TB_IF.b <= 'd7;
#5;
`TB_IF.en <= 1;
wait(`TB_IF.ack);
$display("time = %0t: finish", $time);
#10;
$finish;
end
initial begin
$dumpfile("dump.vcd"); $dumpvars;
end
endmodule
Output:
time = 0: a= x b= x out= 0 en=x ack=0 TB_IF.out= x TB_IF.ack=x
time = 8: a= 20 b= 7 out= 0 en=x ack=0 TB_IF.out= x TB_IF.ack=x
time = 10: a= 20 b= 7 out= 0 en=x ack=0 TB_IF.out= 0 TB_IF.ack=0
time = 12: a= 20 b= 7 out= 0 en=1 ack=0 TB_IF.out= 0 TB_IF.ack=0
time = 14: a= 20 b= 7 out= 140 en=1 ack=1 TB_IF.out= 0 TB_IF.ack=0
time = 22: finish
time = 22: a= 20 b= 7 out= 140 en=1 ack=1 TB_IF.out= 140 TB_IF.ack=1
Clocking Block 的作用及输出解释
Clocking Block 的作用
这段代码中的 clocking block (cb) 在接口 mult_if 中定义,主要作用有:
- 同步信号采样和驱动:为测试平台(TB)提供与时钟同步的信号访问方式
- 时序控制:
default input #7 output #6表示:- 输入信号(
input)在时钟沿前7个时间单位采样 - 输出信号(
output)在时钟沿后6个时间单位驱动
- 输入信号(
- 接口简化:通过 modport 为不同模块提供不同的信号访问权限
时间线分析:
time=0:
- 时钟(clk)初始化为0
- 复位(reset)初始化为1
- DUT处于复位状态,inf.out=0, inf.ack=0
time=1:
- 第一个$display显示当前时间
- 设置a=20, b=7 (由于clocking block的输出延迟#6,这些值不会立即生效)
time=2:
- 时钟上升沿(0→1)
- 复位信号从1变为0
- a和b的值在时钟沿后6个时间单位(即time=8)才会真正更新
time=6:
- 第二个$display显示当前时间
- en信号被设置为1 (同样有6个时间单位的输出延迟,将在time=12生效)
time=8:
- a和b的值(20和7)实际更新到接口
time=12:
- en信号实际变为1
- 在下一个时钟上升沿(time=14)时,DUT检测到en=1,开始计算乘法
time=14:
- 时钟上升沿
- DUT计算20*7=140,并设置ack=1
- 由于clocking block的输入采样延迟#7,测试平台将在时钟沿前7个时间单位(即time=7)采样信号,但此时计算尚未完成
time=22:
- 下一个时钟上升沿在time=18
- 测试平台在time=18-7=11采样信号,但此时计算仍未完成
- 再下一个时钟上升沿在time=22
- 测试平台在time=22-7=15采样信号,此时计算已完成(out=140, ack=1)
- 因此第三个$display显示正确的结果
关键点:
- clocking block的输入/输出延迟导致了信号更新的时间偏移
- 测试平台看到的信号状态与实际硬件中的状态有时间差
wait(TB_IF.ack)` 等待的是clocking block采样到的ack信号,而不是实时信号
这种机制确保了测试平台与设计之间的同步,避免了竞争条件,是SystemVerilog中验证环境常用的同步技术。
default input #7 output #8;
下面是结果:
time = 0: a= x b= x out= 0 en=x ack=0 TB_IF.out= x TB_IF.ack=x
time = 10: a= 20 b= 7 out= 0 en=x ack=0 TB_IF.out= 0 TB_IF.ack=0
time = 14: a= 20 b= 7 out= 0 en=1 ack=0 TB_IF.out= 0 TB_IF.ack=0
time = 18: a= 20 b= 7 out= 140 en=1 ack=1 TB_IF.out= 0 TB_IF.ack=0
time = 26: finish
time = 26: a= 20 b= 7 out= 140 en=1 ack=1 TB_IF.out= 140 TB_IF.ack=1