Clocking Blocks & Modports

How to solving race conditions in testbenches using Clocking Blocks (clocking) and controlling signal direction with Modports.

The Race Condition Problem

In RTL, if the Testbench drives a signal at the exact same time the active clock edge occurs, a race condition happens. The DUT might sample the old value or the new value randomly.

Clocking Blocks solve this by explicitly defining:

  • Input Skew: When to sample signals (e.g., #1step before clock).
  • Output Skew: When to drive signals (e.g., #1ns after clock).

Syntax & Example

Interface with Clocking Block

interface my_bus_if (input bit clk);
  logic [31:0] data;
  logic        valid;
  logic        ready;
  // Define Clocking Block for the DRIVER
  clocking cb @(posedge clk);
    default input #1step output #2ns; // Skews
    output data, valid; // Driver Drives these
    input  ready;       // Driver Samples this
  endclocking
  // Define Clocking Block for the MONITOR
  clocking mon_cb @(posedge clk);
    default input #1step output #2ns;
    input data, valid, ready; // Monitor passively samples EVERYTHING
  endclocking
  // Modports utilizing the clocking blocks
  modport DRIVER  (clocking cb, input clk);
  modport MONITOR (clocking mon_cb, input clk);
endinterface
                            

Using signals in Class

When using a clocking block, you must access signals via the block name (e.g., vif.cb.data) and drive them using the Non-blocking Drive operator (<=) (even in classes).


task drive();
  // Sync to clock block edge
  @(vif.cb); 
  // Drive signals (Effective after #2ns skew)
  vif.cb.valid <= 1;
  vif.cb.data  <= 32'hDEAD_BEEF;
  wait(vif.cb.ready == 1); // Sampled at #1step before posedge
endtask