SystemVerilog 的 clocking
块(Clocking Block)是一种专门用于定义信号时序行为的构造,主要用于验证环境(如 UVM)中,以精确控制信号的采样和驱动时序。clocking
块通过将信号与特定时钟关联,简化了测试环境中对时序敏感信号的处理,减少了手动时序管理的复杂性。本文将详细介绍 SystemVerilog 中 clocking
块的各种用法,包括基本定义、输入输出信号、时序控制、接口中的使用、以及在验证中的应用,并提供示例代码和最佳实践。
clocking
块是 SystemVerilog 中用于封装信号与时钟关系的构造,通常定义在 interface
或模块中。它的主要功能是:
interface
结合,提供模块化的时序接口。clocking clocking_name @(clock_event);
input input_signal;
output output_signal;
endclocking
clocking_name
:时钟块的名称。clock_event
:触发时钟块的时钟事件(如 @(posedge clk)
)。input_signal
:输入信号,定义采样时序。output_signal
:输出信号,定义驱动时序。clocking
块通过指定信号的采样和驱动时序,简化了验证代码的编写。
module top;
logic clk = 0;
logic [7:0] data;
logic valid;
// 定义 clocking 块
clocking cb @(posedge clk);
input data;
input valid;
endclocking
// 测试逻辑
initial begin
forever #5 clk = ~clk;
end
initial begin
@(cb); // 等待 clocking 块的时钟边沿
if (cb.valid)
$display("Sampled data: %h", cb.data);
end
endmodule
说明:
clocking cb
定义了一个时钟块,基于 clk
的上升沿。input data
和 input valid
指定信号在时钟上升沿前采样。cb.data
和 cb.valid
访问同步信号。优点:
clocking
块支持通过 input
和 output
关键字定义信号的采样和驱动时序,并可以通过延迟指定具体的时序偏移。
input
信号在时钟边沿前采样,默认采样时间点为时钟边沿前的非阻塞赋值(NBA)区域。可以通过 input #delay
指定采样延迟。
output
信号在时钟边沿后驱动,默认驱动时间点为时钟边沿后的非阻塞赋值区域。可以通过 output #delay
指定驱动延迟。
interface simple_bus (input logic clk);
logic [7:0] data;
logic valid;
logic ready;
clocking cb @(posedge clk);
input #1ns data, valid; // 采样延迟 1ns
output #2ns ready; // 驱动延迟 2ns
endclocking
endinterface
module receiver (simple_bus bus);
always @(bus.cb) begin
bus.cb.ready <= 1; // 在时钟边沿后 2ns 驱动 ready
if (bus.cb.valid)
$display("Received data: %h", bus.cb.data);
end
endmodule
module top;
logic clk = 0;
always #5 clk = ~clk;
simple_bus bus_inst(.clk(clk));
receiver u_receiver (.bus(bus_inst));
initial begin
bus_inst.data = 8'hA5;
bus_inst.valid = 1;
#20 $finish;
end
endmodule
说明:
input #1ns
表示 data
和 valid
在时钟上升沿前 1ns 采样。output #2ns
表示 ready
在时钟上升沿后 2ns 驱动。bus.cb
访问同步信号。注意:
clocking
块通常定义在 interface
中,与信号和 modport
结合,提供模块化的时序接口。
interface simple_bus (input logic clk);
logic [7:0] data;
logic valid;
logic ready;
clocking cb @(posedge clk);
input data, valid;
output ready;
endclocking
modport slave (
input data, valid,
output ready
);
endinterface
module receiver (simple_bus.slave bus);
always @(bus.cb) begin
bus.cb.ready <= 1;
if (bus.cb.valid)
$display("Received data: %h", bus.cb.data);
end
endmodule
module top;
logic clk = 0;
always #5 clk = ~clk;
simple_bus bus_inst(.clk(clk));
receiver u_receiver (.bus(bus_inst));
initial begin
bus_inst.data = 8'hA5;
bus_inst.valid = 1;
#20 $finish;
end
endmodule
说明:
clocking cb
定义在 interface
中,与接口信号关联。receiver
模块通过 bus.cb
访问同步信号。modport slave
定义了信号方向,增强接口的模块化。优点:
clocking
块支持定义多个时钟块,用于处理多时钟域的信号。
interface multi_clock_bus (input logic clk1, clk2);
logic [7:0] data;
logic valid;
clocking cb1 @(posedge clk1);
input data, valid;
endclocking
clocking cb2 @(posedge clk2);
input data, valid;
endclocking
endinterface
module monitor (multi_clock_bus bus);
always @(bus.cb1) begin
if (bus.cb1.valid)
$display("clk1 domain: data = %h", bus.cb1.data);
end
always @(bus.cb2) begin
if (bus.cb2.valid)
$display("clk2 domain: data = %h", bus.cb2.data);
end
endmodule
module top;
logic clk1 = 0, clk2 = 0;
always #5 clk1 = ~clk1;
always #7 clk2 = ~clk2;
multi_clock_bus bus_inst(.clk1(clk1), .clk2(clk2));
monitor u_monitor (.bus(bus_inst));
initial begin
bus_inst.data = 8'hA5;
bus_inst.valid = 1;
#50 $finish;
end
endmodule
说明:
cb1
和 cb2
分别基于 clk1
和 clk2
定义时钟块。monitor
模块在不同时钟域中采样信号。注意:
clocking
块在验证环境(如 UVM)中广泛使用,用于连接 DUT 和测试环境,提供标准化的时序接口。
interface simple_bus (input logic clk);
logic [7:0] data;
logic valid;
logic ready;
clocking cb @(posedge clk);
input data, valid;
output ready;
endclocking
endinterface
module dut (simple_bus bus);
always @(posedge bus.clk) begin
if (bus.valid && bus.ready)
$display("DUT received: %h", bus.data);
end
endmodule
program testbench;
import uvm_pkg::*;
`include "uvm_macros.svh"
logic clk = 0;
always #5 clk = ~clk;
simple_bus bus_if(.clk(clk));
initial begin
// 设置 UVM 接口
uvm_config_db#(virtual simple_bus)::set(null, "*", "bus_if", bus_if);
run_test();
end
endprogram
说明:
simple_bus
包含 clocking cb
,为测试环境提供同步信号访问。uvm_config_db
获取虚拟接口。dut
通过接口与测试环境交互。优点:
可以使用 default clocking
指定默认的时钟块,简化代码中的时序引用。
interface simple_bus (input logic clk);
logic [7:0] data;
logic valid;
clocking cb @(posedge clk);
input data, valid;
endclocking
default clocking cb; // 设置默认时钟块
endinterface
module monitor (simple_bus bus);
always @(*) begin
if (valid) // 直接引用信号,等效于 bus.cb.valid
$display("Data: %h", data);
end
endmodule
说明:
default clocking cb
将 cb
设置为默认时钟块。valid
),等效于 bus.cb.valid
。注意:
clocking
块支持在仿真中动态调整时序(通过属性),但主要用于验证。
interface simple_bus (input logic clk);
logic [7:0] data;
logic valid;
clocking cb @(posedge clk);
input #1step data, valid; // 使用 1step 采样
endclocking
endinterface
说明:
#1step
表示在时钟边沿前的最小时间步长采样。时序定义:
接口结合:
clocking
块定义在 interface
中,与信号和 modport
结合。modport
明确信号方向,增强模块化。验证环境:
clocking
块。clocking
块简化驱动器和监视器的开发。综合限制:
clocking
块主要用于验证,不支持综合。clocking
块。多时钟域:
clocking
块。代码可读性:
clocking
块和信号提供清晰的命名。调试与验证:
clocking
块的时序行为。SystemVerilog 的 clocking
块是一种强大的验证工具,用于定义信号的采样和驱动时序,简化测试环境的时序管理。通过基本定义、输入输出信号、时序控制、接口结合、多时钟支持等功能,clocking
块在 UVM 等验证环境中发挥了关键作用。特别是在复杂设计中,clocking
块与 interface
的结合提供了模块化的时序接口,显著提高了验证效率和代码质量。遵循最佳实践并根据具体场景选择合适的 clocking
用法,能够有效提升验证的可靠性和可维护性。