SystemVerilog Clocking Blocks
默认情况下,模块端口和接口不指定任何时序要求或信号之间的同步方案。 在clocking和endclocking之间定义的时钟模块正是这样做的。 它是特定时钟同步的信号的集合,有助于满足时钟和信号之间的时序要求。
注意: 一个测试平台可以有许多时钟块,但每个时钟只能有一个块。
Syntax
[default] clocking [identifier_name] @ [event_or_identifier]
default input #[delay_or_edge] output #[delay_or_edge]
[list of signals]
endclocking
delay_value表示信号要从时钟事件偏离多少个时间单位的偏斜。 如果未指定默认偏斜,则所有输入信号将在#1step采样,并在指定事件后0ns输出输出信号。
clocking ckb @ (posedge clk);
default input #1step output negedge;
input ...;
output ...;
endclocking
clocking ck1 @ (posedge clk);
default input #5ns output #2ns;
input data, valid, ready = top.ele.ready;
output negedge grant;
input #1step addr;
endclocking
【1】创建了一个称为ck1的时钟块,它将在clk的上升沿**;
【2】默认情况下,时钟块内所有输入信号将在时钟前5ns采样,时钟块内上升沿后2ns驱动时钟块内的所有输出信号;
【3】data, valid and ready声明为块的输入,因此将在clk的构成之前2ns采样;
【4】grant是具有自身时间要求的输出信号。在这里,grant将以clk的下降沿来驱动,而不是默认的posedge。
在接口中使用
简而言之,时钟模块封装了共享公共时钟的一堆信号。 因此,在接口内部声明时钟块可以帮助节省连接到测试台所需的代码量,并可以节省开发时间。
时钟模块内部的信号方向是相对于测试平台而不是DUT。
时钟模块允许在指定的时钟事件时对输入进行采样并驱动输出。 如果提到了时钟块的输入偏斜(skew),则将在时钟事件之前以偏斜时间单位对该块内的所有输入信号进行采样。如果提到了时钟模块的输出偏斜,则在相应的时钟事件之后,将驱动该模块中的所有输出偏斜时间单位。
What are input and output skews ?
偏斜被指定为常量表达式或参数。如果仅使用数字,则将偏斜解释为遵循给定范围内的活动时间范围。
clocking cb @(clk);//声明了一个名为cb的时钟块
input #1ps req;//信号req被指定具有1ps的偏斜,并将在时钟沿clk之前的1 ps采样
output #2 gnt;//输出信号gnt具有2个时间单位的输出偏斜, 如果我们的时间标度为1ns / 1ps,则#2表示2 ns,因此将在时钟沿后2 ns被驱动。
input #1 output #3 sig;//sig为inout类型,将在时钟沿之前1 ns进行采样,并在时钟沿之后3 ns被驱动。
endclocking
1step的输入偏斜表示应该在上一个时间步的末尾,或者换句话说,紧接时钟正沿之前对信号进行采样。
clocking cb @(posedge clk);
input #1step req;
endclocking
例
考虑一个具有输入clk和req并驱动输出信号gnt的简单设计。为简单起见,让我们在收到请求后立即反馈。
module des (input req, clk, output reg gnt);
always @ (posedge clk)
if (req)
gnt <= 1;
else
gnt <= 0;
endmodule
为了处理设计端口信号,让我们创建一个名为if的简单接口。
interface _if (input bit clk);
logic gnt;
logic req;
clocking cb @(posedge clk);
input #1ns gnt;
output #5 req;
endclocking
endinterface
下一步是驱动设计的输入,以便它返回grant信号。
module tb;
bit clk;
//创建时钟并初始化输入信号
always #10 clk = ~clk;
initial begin
clk <= 0;
if0.cb.req <= 0;
end
// 实例化接口
_if if0 (.clk (clk));
// 实例化设计
des d0 ( .clk (clk),
.req (if0.req),
.gnt (if0.gnt));
// 驱动仿真
initial begin
for (int i = 0; i < 10; i++) begin
bit[3:0] delay = $random;
repeat (delay) @(posedge if0.clk);
if0.cb.req <= ~ if0.cb.req;
end
#20 $finish;
end
endmodule
输出偏斜(output skew)
为了清晰了解输出偏斜,让我们调整接口使其具有三个不同的时钟模块,每个时钟模块具有不同的输出偏斜。然后,让我们用每个时钟模块驱动req来查看差异。
interface _if (input bit clk);
logic gnt;
logic req;
clocking cb_0 @(posedge clk);
output #0 req;
endclocking
clocking cb_1 @(posedge clk);
output #2 req;
endclocking
clocking cb_2 @(posedge clk);
output #5 req;
endclocking
endinterface
在我们的测试平台中,我们将使用一个for循环来迭代每个激励,并为每个迭代使用不同的时钟块。
module tb;
// ... 代码部分与之前相同
// 驱动仿真
initial begin
for (int i = 0; i < 3; i++) begin
repeat (2) @(if0.cb_0);
case (i)
0 : if0.cb_0.req <= 1;
1 : if0.cb_1.req <= 1;
2 : if0.cb_2.req <= 1;
endcase
repeat (2) @ (if0.cb_0);
if0.req <= 0;
end
#20 $finish;
end
endmodule
输入偏斜 (Input skew)
为了了解输入偏斜,我们将DUT更改为仅出于我们的目的每#1ns简单地提供一个随机值。
module des (output reg[3:0] gnt);
always #1 gnt <= $random;
endmodule
接口块将具有不同的时钟块声明,就像之前的每个块具有不同的输入偏斜一样。
interface _if (input bit clk);
logic [3:0] gnt;
clocking cb_0 @(posedge clk);
input #0 gnt;
endclocking
clocking cb_1 @(posedge clk);
input #1step gnt;
endclocking
clocking cb_2 @(posedge clk);
input #1 gnt;
endclocking
clocking cb_3 @(posedge clk);
input #2 gnt;
endclocking
endinterface
在测试平台中,我们将在时间0ns分叉4个不同的线程,其中每个线程等待时钟的上升沿并对DUT的输出进行采样。
module tb;
bit clk;
always #5 clk = ~clk;
initial clk <= 0;
_if if0 (.clk (clk));
des d0 (.gnt (if0.gnt));
initial begin
fork
begin
@(if0.cb_0);
$display ("cb_0.gnt = 0x%0h", if0.cb_0.gnt);
end
begin
@(if0.cb_1);
$display ("cb_1.gnt = 0x%0h", if0.cb_1.gnt);
end
begin
@(if0.cb_2);
$display ("cb_2.gnt = 0x%0h", if0.cb_2.gnt);
end
begin
@(if0.cb_3);
$display ("cb_3.gnt = 0x%0h", if0.cb_3.gnt);
end
join
#10 $finish;
end
endmodule
输出波形如下图所示,可以看出该设计每#1ns驱动一次随机值。
参考文献:
【1】https://www.chipverify.com/systemverilog/systemverilog-clocking-blocks-part2