HomeSystemVerilog VerificationDay 6 — Clocking Blocks
DAY 6 · SV FUNDAMENTALS

SystemVerilog Clocking Blocks — Input/Output Skew, Race-Free Testbenches

By EcrioniX · Updated Jun 12, 2026

The SystemVerilog clocking block solves one of the most dangerous problems in simulation: the race condition between the testbench and the DUT at the clock edge. Without it, your testbench may occasionally drive a signal at the same time the DUT samples it — and which one "wins" depends on non-deterministic simulation scheduling. Clocking blocks make this deterministic, correct, and professional-grade.

CLOCKING BLOCK TIMING posedge clk CLK Sample here input #1step INPUT SKEW Drive here output #1step OUTPUT SKEW DUT samples at edge TB samples 1step BEFORE edge TB drives 1step AFTER edge (seen next cycle)

What is the race condition problem in testbenches?

Consider this classic testbench code:

// DANGEROUS — race condition with the DUT
initial begin
  @(posedge clk);         // wait for the clock edge
  data_in <= 8'hA5;       // drive signal AT the same edge
  valid   <= 1'b1;        // ...just like the DUT samples it
end

The problem: in SystemVerilog simulation, all processes that trigger on posedge clk are scheduled at the same simulation time. The order they execute is non-deterministic — it depends on the simulator's internal scheduling. Sometimes the testbench drives data_in before the DUT's always_ff samples it (the testbench "wins" — this test appears to pass). Sometimes the DUT samples before the testbench drives (the DUT sees the old value — the test appears to fail). The same test passes on Monday and fails on Friday. This is a race condition.

How do clocking blocks solve the race condition problem?

A clocking block declares two things explicitly:

This is exactly how real hardware works: setup time (sample before edge) and clock-to-Q delay (output appears after edge). Clocking blocks make simulation match real synchronous design practice.

What is the syntax of a clocking block in SystemVerilog?

Clocking block syntax

clocking cb_name @(posedge clk);
  default input #1step output #1step;
  input  signal_a;          // sampled 1step before posedge
  output signal_b;          // driven 1step after posedge
  input  #2ns signal_c;     // custom: sampled 2ns before edge
  output #3ns signal_d;     // custom: driven 3ns after edge
endclocking

Driving signals through the clocking block

To drive a signal through a clocking block, use the clocking block name: cb.data <= value; — always with non-blocking assignment (<=). The signal will actually update at the output skew time after the next clock edge. To advance one clock cycle: ##1; (two hash symbols, then N cycles).

Sampling signals through the clocking block

To read a signal: value = cb.data; (simple reference). The value you get is the one captured at the input skew before the last clock edge. To wait for a clock edge: @(cb); — this waits for the clocking block's triggering event (the posedge of its clock).

How do ##N cycle delays work?

The ##N operator means "wait N clocking block cycles" — it is a synchronous delay counted in clock cycles, not time. ##1 advances one clock cycle. ##0 means "at the current clock edge" (used in concurrent assertions). You can chain them: ##3 cb.valid <= 1; ##1 cb.valid <= 0; drives valid for exactly one cycle, 3 cycles after the current time.

SyntaxMeaning
@(cb)Wait for one clock cycle (clocking block edge)
##1Advance one clock cycle of the default clocking block
##NAdvance N clock cycles
cb.signal <= valDrive signal at output skew after next edge
val = cb.signalRead signal sampled at input skew before last edge
@(posedge cb.signal)Wait for clocking-block-sampled posedge of signal

Where should clocking blocks live — interface or testbench?

Best practice is to put the clocking block inside the interface. This co-locates all timing information with the signal declarations and makes it available to any testbench that instantiates the interface via a virtual interface handle. The clocking block becomes part of the interface contract between the DUT and the testbench.

A modport that includes the clocking block looks like: modport TB_CB (clocking cb_name, output rst_n); — the testbench modport grants access to the clocking block and any non-clocked signals (like async reset).

Complete clocking block demonstration

clk_block_demo.sv — Interface with clocking block + race-free TB
// ============================================================
// clk_block_demo.sv — Clocking block: race-free TB demo
// EcrioniX · SV Verification Course · Day 6
// ============================================================

// ---- Interface with clocking block ----
interface sync_if (input logic clk);

  logic        rst_n;
  logic [7:0]  data;
  logic        valid;
  logic        ready;
  logic [7:0]  result;
  logic        result_valid;

  // ---- Clocking block: testbench perspective ----
  // input skew : sample 1step BEFORE posedge (see settled value)
  // output skew: drive  1step AFTER  posedge (DUT sees new value next cycle)
  clocking cb @(posedge clk);
    default input #1step output #1step;
    // From TB view: data/valid are outputs (TB drives them)
    output data;
    output valid;
    // From TB view: ready/result are inputs (TB reads them)
    input  ready;
    input  result;
    input  result_valid;
  endclocking

  // Modport for testbench: uses the clocking block + async rst_n
  modport TB_MP  (clocking cb, output rst_n);
  // Modport for DUT: direct signal access (synchronous flip-flops in DUT)
  modport DUT_MP (input clk, rst_n, data, valid, output ready, result, result_valid);

endinterface : sync_if


// ---- Testbench using the clocking block ----
module clk_block_demo;

  logic clk = 0;
  always #5 clk = ~clk;    // 100 MHz

  sync_if bus (clk);

  // ---- Simulate a DUT: simple pass-through with 2-cycle latency ----
  logic [7:0] pipe1, pipe2;
  always_ff @(posedge clk or negedge bus.rst_n) begin
    if (!bus.rst_n) begin
      pipe1 <= 0; pipe2 <= 0;
      bus.ready        <= 0;
      bus.result       <= 0;
      bus.result_valid <= 0;
    end else begin
      bus.ready        <= 1;
      pipe1            <= bus.valid ? bus.data : pipe1;
      pipe2            <= pipe1;
      bus.result       <= pipe2;
      bus.result_valid <= bus.valid;
    end
  end

  // ---- Testbench: drive through clocking block ----
  initial begin
    // Async reset (not through clocking block — it's async)
    bus.rst_n = 0;
    bus.cb.data  <= 0;
    bus.cb.valid <= 0;
    repeat(3) @(bus.cb);   // wait 3 clocking block cycles
    bus.rst_n = 1;
    @(bus.cb);

    // ---- CORRECT: drive through clocking block ----
    // The <= assignment updates AFTER the edge (output skew)
    // The DUT will see these values in the NEXT cycle
    $display("[T=%0t] Driving 8'hA5 through clocking block", $time);
    bus.cb.data  <= 8'hA5;
    bus.cb.valid <= 1'b1;
    @(bus.cb);              // advance 1 cycle — data is now stable to DUT
    bus.cb.data  <= 8'hB6;
    @(bus.cb);
    bus.cb.valid <= 1'b0;

    // Wait and sample result
    ##3;                    // advance 3 more cycles (##N = N clocking events)
    $display("[T=%0t] result = 8'h%0h, result_valid = %b",
             $time, bus.cb.result, bus.cb.result_valid);

    // ---- Demonstrate reading sampled value (input skew) ----
    @(bus.cb);
    // bus.cb.ready is sampled 1step BEFORE the posedge — stable settled value
    if (bus.cb.ready)
      $display("[T=%0t] DUT is ready (sampled with input skew)", $time);

    // ---- ##N delays ----
    $display("[T=%0t] Waiting 5 cycles with ##5...", $time);
    ##5;
    $display("[T=%0t] Done. No race conditions — simulation is deterministic.", $time);

    $finish;
  end

  // ---- Demonstrate what WRONG code looks like (without clocking block) ----
  // DO NOT DO THIS:
  //   @(posedge clk);       // same time as DUT flip-flops
  //   bus.data <= 8'hA5;    // RACE: DUT may or may not see this yet

  // WITH clocking block:
  //   @(bus.cb);            // wait for edge
  //   bus.cb.data <= 8'hA5; // drives 1step AFTER edge — safe, deterministic

endmodule : clk_block_demo

Common mistake: blocking assignment through clocking block

Always use non-blocking assignment (<=) when driving through a clocking block: cb.data <= value. Using blocking assignment (=) bypasses the output skew mechanism and can reintroduce race conditions. The non-blocking form schedules the update for the NBA (non-blocking assignment) region, which respects the output skew timing.

Why are clocking blocks mandatory in professional testbenches?

Industry-grade verification environments — particularly those built on UVM — always use clocking blocks because:

The IEEE 1800 standard specifically added clocking blocks to address these simulation correctness problems. After Day 5 (interfaces) and Day 6 (clocking blocks), your testbench infrastructure is at an industry-professional level — you're ready for Day 7 (program blocks) and then OOP classes in Phase 2.

Day 6 takeaways

Frequently Asked Questions

What is a clocking block in SystemVerilog?

A clocking block is a named group of signals with explicit sampling (input skew) and drive (output skew) timing relative to a clock edge. It eliminates the testbench-vs-DUT race condition by ensuring the TB samples signals before the edge and drives signals after the edge — so they never compete at the same simulation time.

What is input and output skew in a clocking block?

Input skew: how far before the clock edge the testbench samples a signal (default #1step — one delta cycle before posedge). Output skew: how far after the clock edge the testbench drives a signal (default #1step — one delta cycle after posedge). Custom skew: input #2ns data; samples 2 ns before the edge. The DUT sees the TB's new value only in the next clock cycle.

How do clocking blocks prevent race conditions?

By moving the TB's drive to after the clock edge (output skew), the DUT has already latched the current cycle's values before the TB changes the signal. By sampling before the edge (input skew), the TB reads the fully-settled previous-cycle value, not an in-flight update. Together these ensure TB and DUT never race for the same signal at the same time.

What is ##1 in SystemVerilog?

##1 is a cycle delay counted in clocking block edges — "advance one clock cycle." It is used inside processes that operate in clocking block context (after @(cb)). ##3 waits 3 cycles. In concurrent assertions (SVA), ##1 means "exactly one cycle later." It is completely different from #1 (a 1ns/1-unit time delay).

Previous
← Day 5: Interfaces & Modports

← Full course roadmap