Clocking Blocks in SystemVerilog
A clocking block in System Verilog is a construct that simplifies and synchronizes signal interactions with a clock edge. It provides a clean way to specify the timing relationship between input and output signals in a testbench. Additionally, the clocking skew feature within clocking blocks provides fine-grained control over signal sampling and driving relative to a clock edge.
Clocking skew defines when signals are sampled or driven relative to the clock edge. This feature is critical for ensuring timing correctness in designs where the sampling and driving windows need precise alignment.
Why Use Clocking Blocks?
- Synchronization: Ensures inputs are sampled and outputs are driven at the correct clock edges.
- Timing Isolation: Abstracts timing details, reducing the risk of race conditions in testbenches.
- Readability: Improves clarity by defining input and output signals explicitly.
Syntax of Clocking Blocks:
clocking cb @(posedge clk);
default input #2 output #1;
input data_in;
output data_out;
inout bidir_signal;
endclocking
Here:
@(posedge clk)specifies the clock edge at which signals are sampled or driven.inputsignals are sampled at the clock edge.outputsignals are driven after the clock edge.inoutsignals can be both sampled and driven.- Input Skew: The input signals will be sampled 2 times unit before the clock edge.
- Output Skew: Delays the driving of output signals by a specified time relative to the clock edge. Here it is 1 time unit.
By default, the value of input and output skew is 0 time unit if it is not specified. The input and output skew must be parameter or constant.
Modports in SystemVerilog
A modport in System Verilog defines the access rules for an interface. It specifies which signals are inputs, outputs, or inouts for a particular module, providing fine-grained control over how different modules interact with the interface.
Why Use Modports?
- Direction Control: Clearly defines the direction (input, output, inout) of signals for each module accessing the interface.
- Role Separation: Enables different modules to use the same interface with different roles.
- Code Clarity: Improves readability by explicitly stating signal directions.
Syntax of Modports:
interface simple_if;
logic a, b, c;
// Define modports
modport master (input a, output b, inout c);
modport slave (output a, input b, inout c);
endinterface
Here:
- The
mastermodport allows a module to reada, writeb, and read/writec. - The
slavemodport reverses the roles, allowing a module to writea, readb, and read/writec.
Combining Clocking Blocks and Modports
Clocking blocks and modports can be used together to create highly modular and synchronized verification environments.
Example:
- Clocking block and modport in Interface and connecting interface to module
interface bus_if(input logic clk);
// Signals in the interface
logic [7:0] data;
logic valid;
logic ready;
// Clocking block for the testbench
clocking tb_cb @(posedge clk);
output data, valid; // Testbench drives these signals
input ready; // DUT drives this signal
endclocking
// Clocking block for the DUT
clocking dut_cb @(posedge clk);
input data, valid; // DUT samples these signals
output ready; // DUT drives this signal
endclocking
// Modports to define roles
modport DUT (clocking dut_cb); // DUT uses dut_cb clocking block
modport TB (clocking tb_cb); // Testbench uses tb_cb clocking block
endinterface
module dut(bus_if.DUT vif);
always @(vif.dut_cb) begin
if (vif.dut_cb.valid) begin
vif.dut_cb.ready <= 1; // Ready when valid is asserted
end else begin
vif.dut_cb.ready <= 0; // Not ready otherwise
end
end
endmodule
2. Testbench
module testbench;
logic clk;
bus_if vif(clk); // Instantiate the interface and connect to clk
// DUT instantiation using the modport
dut u_dut(.vif(vif.DUT));
// Clock generation
initial begin
clk = 0;
forever #5 clk = ~clk; // 10ns clock period
end
// Testbench Class
class tb_class;
virtual bus_if.TB vif; // Use the TB modport of the interface
function new(virtual bus_if.TB vif);
this.vif = vif;
endfunction
// Task to drive transactions
task run_test();
vif.tb_cb.valid <= 0;
vif.tb_cb.data <= 0;
#10;
vif.tb_cb.valid <= 1; // Assert valid
vif.tb_cb.data <= 8'hA5; // Send data
#10;
vif.tb_cb.valid <= 0; // Deassert valid
vif.tb_cb.data <= 0;
#10;
endtask
endclass
// Testbench Initialization
initial begin
tb_class tb = new(vif.TB); // Pass the TB modport to the testbench class
tb.run_test();
// End simulation
$finish;
end
// Monitor the interface signals
initial begin
$monitor("Time: %0t | valid: %b | ready: %b | data: %h",
$time, vif.tb_cb.valid, vif.tb_cb.ready, vif.tb_cb.data);
end
endmodule
Output:
Time: 0 | valid: 0 | ready: 0 | data: 00
Time: 10 | valid: 1 | ready: 1 | data: A5
Time: 20 | valid: 0 | ready: 0 | data: 00