Skip to content

SystemVerilog · Module 8

Clocking Blocks

Eliminating TB-DUT races at the clock edge: input/output skews, the ##N operator, default clocking, and the canonical Preponed-sample/Re-NBA-drive pattern.

Module 8 · Page 8.3

How clocking blocks synchronise testbench stimulus and sampling to a clock edge, eliminate the setup/hold race condition between TB and DUT, and provide the ##N cycle-based delay operator.

Going deeper? See Clocking Blocks — Deep Dive, SVA, Coverage, Multi-Clock SoC, CDC for the Module 15.2 treatment covering the clocking block's role as the timing foundation of every modern verification stack — anchoring SVA properties, covergroup sampling events, multi-clock-domain VIPs, and CDC verification.

The Problem — TB and DUT Racing at the Clock Edge

Imagine your testbench drives data and your DUT samples it — both triggered by @(posedge clk). At the exact same simulation instant, the TB is writing the signal and the DUT is reading it. Which one happens first? The answer is undefined — it depends on the order the simulator processes the two always blocks in its internal event queue.

This is the setup/hold race condition in the testbench. Your simulation may pass on VCS but fail on Questa — not because the design is wrong, but because the two tools process active-region events in a different order.

SystemVerilog — the race: TB drives while DUT reads
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// TB: drives data at the SAME posedge clk that the DUT samples it
always @(posedge clk) begin
    data = 8'hAB;   // ← TB writes in Active region
end
 
// DUT: samples data at posedge clk — same event, same Active region
always_ff @(posedge clk) begin
    reg_data <= data;   // ← DUT reads in Active region
                         //   Does it see 0xAB or the OLD value?
                         //   SIMULATOR-DEPENDENT — this is a race.
end

What is a Clocking Block?

A clocking block is a construct that attaches to a clock edge and precisely controls when the testbench samples inputs (reads from the DUT) and drives outputs (writes to the DUT). It defines:

  • The reference clock edge — which clock and which edge (posedge / negedge) defines the synchronisation point for all signals in the block.
  • Input skew — how far before the clock edge the TB samples inputs. Default: #1step — samples in the Preponed region, before any Active-region writes fire.
  • Output skew — how far after the clock edge the TB drives outputs. Default: #1 — drives 1 time unit after the edge, safely after the DUT has sampled.
  • Signal list — which signals are driven or sampled through this clocking block. Each signal gets the clocking block's direction-controlled, skew-adjusted access.
TrackWhat happensRace-safe?
input #1stepTB samples before the edge fires — Preponed regionYes
output #1TB drives 1 time unit after the edge — DUT already sampledYes
No skew (#0)TB drives/samples at the exact edge — same instant as DUTNo — race

Declaring a Clocking Block — Full Syntax

SystemVerilog — annotated clocking block inside an interface
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
interface apb_if (input logic pclk, presetn);
 
    logic [31:0] paddr, pwdata, prdata;
    logic        psel, penable, pwrite, pready, pslverr;
 
    // ①       ②   ③            ④
    clocking cb @(posedge pclk);
 
        // ⑤ Default skews: all inputs sampled #1step before edge,
        //                 all outputs driven #1 after edge
        default input #1step output #1;
 
        // ⑥ Input signals — TB reads these from the DUT
        input  prdata, pready, pslverr;
 
        // ⑦ Output signals — TB drives these to the DUT
        output paddr, psel, penable, pwrite, pwdata;
 
    endclocking
 
    // ⑧ Modport for testbench: exposes clocking block + essential inputs
    modport tb (clocking cb, input pclk, presetn);
 
endinterface
  • clocking keyword — opens the clocking block. It is a construct inside an interface or module, not a separate file.
  • cb — the clocking block name. Used to access it: bus.cb.paddr <= value, ##1 cycles advance relative to this block.
  • @(posedge pclk) — the synchronising clock event. All stimulus and sampling in this block aligns to this edge. Can be posedge, negedge, or edge.
  • ④ Clocking block body — contains default skews and the signal list. Every signal listed here is direction-controlled and skew-adjusted automatically.
  • default input #1step output #1 — the skew defaults. #1step for inputs means "sample in the Preponed region, one time-step before the clock edge" — guaranteed race-free. #1 for outputs means "drive 1 time unit after the clock edge".
  • input prdata, pready, pslverr — signals the TB reads from the DUT. From the TB's perspective these are inputs. They will be sampled at #1step before each posedge.
  • output paddr, psel... — signals the TB drives to the DUT. They will be updated #1 time unit after each posedge. The DUT samples them on the NEXT clock edge — well after they are stable.
  • modport tb (clocking cb, ...) — exposes the clocking block through the testbench modport. The clocking cb entry makes bus.cb accessible inside any module using this modport.

Input and Output Skew — Every Option

The skew values control the precise timing of sampling and driving relative to the clock edge. Understanding them prevents subtle race conditions.

Input skew options

SkewBehaviourVerdict
input #1stepBest practice (default). Samples in the Preponed region — the very first region of the time step, before any Active-region assignments fire.Race-free
input #1Samples 1 time unit before the clock edge. Works but depends on the timescale. Fragile when timescale changes.Fragile
input #0Samples at the exact clock edge — same moment as the Active region. Essentially the same as not using a clocking block.Race — avoid

Output skew options

SkewBehaviourVerdict
output #1Most common. Drives 1 time unit after the clock edge. The DUT has already sampled its inputs at the edge, so the new value is for the next cycle.Clean
output #2Drives 2 time units after the edge. Used when downstream logic needs the new value to be stable for longer before the next edge.Specialised
output #0Drives at the exact clock edge — in the Active region, same as the DUT. DUT may or may not see the new value depending on simulator ordering.Race — avoid

Rule of thumb: always use default input #1step output #1 unless you have a specific reason to deviate.

Using a Clocking Block — Complete Example

Once a clocking block is declared in the interface, the testbench accesses it via the interface handle. All signal reads and writes go through bus.cb.signal. Use ##N to advance N clock cycles.

SystemVerilog — program block driving via clocking block
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
program apb_test (apb_if.tb bus);
 
    initial begin
        // ── Reset ─────────────────────────────────────────────────────
        bus.cb.psel    <= 0;     // drive via clocking block — 1 unit after next edge
        bus.cb.penable <= 0;
        bus.cb.pwrite  <= 0;
        bus.cb.paddr   <= 0;
        bus.cb.pwdata  <= 0;
 
        ##5;    // wait 5 clock cycles (relative to posedge pclk in cb)
 
        // ── APB Write transaction ─────────────────────────────────────
        ##1;    // advance 1 cycle — setup phase
        bus.cb.paddr  <= 32'h4000_0000;
        bus.cb.pwdata <= 32'h1234_5678;
        bus.cb.psel   <= 1;
        bus.cb.pwrite <= 1;
 
        ##1;    // enable phase
        bus.cb.penable <= 1;
 
        // Wait until DUT signals ready — read via clocking block (race-free)
        while (!bus.cb.pready) ##1;
 
        // ── End of transfer ───────────────────────────────────────────
        ##1;
        bus.cb.psel    <= 0;
        bus.cb.penable <= 0;
        bus.cb.pwrite  <= 0;
 
        // ── APB Read transaction ──────────────────────────────────────
        ##2;
        bus.cb.paddr  <= 32'h4000_0000;
        bus.cb.psel   <= 1;
        bus.cb.pwrite <= 0;
        ##1;
        bus.cb.penable <= 1;
        while (!bus.cb.pready) ##1;
 
        // Read the returned data — sampled in Preponed region, guaranteed stable
        $display("Read data: 0x%08h", bus.cb.prdata);
 
        ##1;
        bus.cb.psel    <= 0;
        bus.cb.penable <= 0;
 
        ##5;
        $finish;
    end
 
endprogram

The ##N Cycle Delay Operator

##N is SystemVerilog's clock-cycle delay operator. It means "advance N edges of the clocking block's reference clock". Unlike #N (which delays by N time units regardless of the clock), ##N is always an exact number of clock cycles — independent of timescale.

SyntaxMeaningUse case
##1Advance exactly 1 clock cycleMove to next clock edge — most common in testbenches
##NAdvance N clock cyclesWait N cycles before next action (reset, setup time)
##0Advance to the current clock edge (if not already there)Rarely used; synchronise without advancing
##[1:5]Advance between 1 and 5 cycles (in SVA sequences)Non-deterministic delay range in assertions
##[*]Zero or more cycles (SVA)Eventually property — flexible timing in assertions
SystemVerilog — cycle delay vs time delay comparison
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Assume: `timescale 1ns/1ps,  clock period = 10ns
 
// ── #N: time-unit delay (fragile, timescale-dependent) ────────────────
#10;    // wait 10ns — equals 1 clock cycle ONLY if clock is exactly 10ns
        // If clock changes to 8ns, this waits 1.25 cycles — WRONG
 
// ── ##N: clock-cycle delay (robust, always correct) ───────────────────
##1;    // wait exactly 1 posedge of the clocking block's clock
        // If clock changes to 8ns, this still waits exactly 1 cycle — CORRECT
 
##10;   // wait 10 clock cycles — completely clock-frequency independent
 
// ── In a program block with default clocking ──────────────────────────
default clocking cb @(posedge clk);
    default input #1step output #1;
    input  dout;
    output din;
endclocking
 
initial begin
    ##5;                    // resolves against default clocking block
    cb.din <= 8'hAB;        // drive via default clocking block
    ##1;
    $display("%h", cb.dout); // sample via default clocking block
end

default clocking

When a program or module has a default clocking declaration, all bare ##N delays and clocking-block signal accesses without an explicit block name resolve against that default. This is the cleanest way to write testbenches — you declare the clock once and use ##N everywhere without qualifying it.

SystemVerilog — default clocking in a program block
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
program test (apb_if.tb bus);
 
    // Declare the default clocking — all ##N uses this clock
    default clocking bus.cb;   // refer to the clocking block in the interface
 
    initial begin
        // Reset sequence
        ##2;                     // 2 cycles — uses bus.cb automatically
        bus.cb.psel <= 0;
 
        ##3;
 
        // APB write — cycle by cycle
        bus.cb.paddr  <= 32'h1000;
        bus.cb.pwdata <= 32'hFF;
        bus.cb.psel   <= 1;
        bus.cb.pwrite <= 1;
        ##1;
        bus.cb.penable <= 1;
        ##1 (bus.cb.pready == 1);   // advance 1 cycle AND wait for pready
 
        bus.cb.psel    <= 0;
        bus.cb.penable <= 0;
        ##5;
        $finish;
    end
 
endprogram

Clocking Block Events — Waiting for the Clock

A clocking block itself is also a valid event. You can wait on it with @(clocking_block_name). This is equivalent to waiting for the next active edge of the clocking block's clock — and is the preferred way to synchronise testbench code to the clock inside a module (as opposed to a program block with ##N).

SystemVerilog — @(clocking_block) event
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module monitor (apb_if.tb bus);
 
    initial begin
        forever begin
            // Wait for next posedge of the clocking block's clock
            @(bus.cb);
 
            // At this point inputs are already sampled in the Preponed region
            // bus.cb.prdata holds the value from BEFORE this edge — race-free
            if (bus.cb.psel && bus.cb.penable && bus.cb.pready)
                $display("[MON] APB %s addr=%h data=%h",
                    bus.cb.pwrite ? "WR" : "RD",
                    bus.cb.paddr,
                    bus.cb.pwrite ? bus.cb.pwdata : bus.cb.prdata);
        end
    end
 
endmodule

Common Mistakes & How to Fix Them

  • Using = (blocking) to drive a clocking block output. Writing bus.cb.psel = 1; instead of bus.cb.psel <= 1;. The blocking assignment bypasses the output skew mechanism and drives the signal immediately in the Active region — creating a race with the DUT. Fix: always use <= (non-blocking) when writing to clocking block output signals. The clocking block's output skew is only applied when you use <=.

  • Forgetting to include the clocking block in the modport. Declaring a modport tb without the clocking cb entry. Inside any module using that modport, bus.cb is simply inaccessible — compile error. Fix: always add clocking cb to the modport used by the testbench: modport tb (clocking cb, input pclk, presetn);.

  • Using #N instead of ##N for clock-cycle delays. Writing #10 to wait "one clock cycle" when the clock period is 10ns. This breaks as soon as the clock frequency changes. It also does not synchronise to the clocking block — it is a raw time delay. Fix: use ##1 (or ##N) for all cycle-based delays in testbench code. This is always exactly N clock cycles, regardless of the clock period or timescale.

  • Reading a clocking block output instead of an input. The clocking block declares output paddr (TB drives it) and input prdata (TB reads it). Trying to read bus.cb.paddr inside the TB reads back the last value you drove — not the DUT's current state. Confusing and misleading. Fix: only read signals declared as input in the clocking block — those are DUT outputs. For signals the TB drives (output in the clocking block), you already know the value since you drove it.

Quick Reference — Clocking Block at a Glance

SystemVerilog — clocking block quick reference
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Declare inside an interface ───────────────────────────────────────
clocking cb @(posedge clk);
    default input #1step output #1;  // always use these defaults
    input  dut_out_a, dut_out_b;       // DUT → TB (TB reads these)
    output dut_in_x,  dut_in_y;       // TB → DUT (TB drives these)
endclocking
 
// ── Add to modport for testbench ──────────────────────────────────────
modport tb (clocking cb, input clk, rst_n);
 
// ── In the testbench program ──────────────────────────────────────────
default clocking bus.cb;
 
// Drive outputs — always use <=
bus.cb.dut_in_x <= 8'hAB;
 
// Read inputs — always stable (sampled in Preponed)
logic val = bus.cb.dut_out_a;
 
// Advance clock cycles — always ##N not #N
##1;           // 1 cycle
##5;           // 5 cycles
##1 (bus.cb.dut_out_b == 1);  // 1 cycle AND wait for condition
 
// Wait for clock event
@(bus.cb);    // synchronise to next posedge
 
// Per-signal skew override (overrides the default for just this signal)
clocking cb2 @(posedge clk);
    default input #1step output #1;
    output #2 slow_signal;   // this one signal uses #2 instead of #1
    input  fast_sig;
endclocking

Verification Usage — Clocking Blocks Are How UVM Drivers and Monitors Talk to the DUT

In any non-trivial UVM testbench, every driver and every monitor accesses bus signals through a clocking block — never bare. The driver drives via the testbench-side clocking block; the monitor samples via a separate monitor-only clocking block that has no drive permission. This isn't optional discipline; it is the canonical pattern. Skipping it means sporadic regression failures and the kind of debug sessions that end with "we added a #1 delay and now it works."

SystemVerilog — production UVM driver using a clocking block
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Interface with TB and monitor clocking blocks ───────────────
interface apb_if(input logic clk);
    logic [31:0] paddr, pwdata, prdata;
    logic        psel, penable, pwrite, pready;
 
    // Testbench-side: samples late, drives at NBA
    clocking tb_cb @(posedge clk);
        default input #1step output #0;
        output paddr, pwdata, pwrite, psel, penable;
        input  prdata, pready;
    endclocking
 
    // Monitor-only: samples late, never drives
    clocking mon_cb @(posedge clk);
        default input #1step;
        input paddr, pwdata, prdata, psel, penable, pwrite, pready;
    endclocking
 
    modport tb     (clocking tb_cb);
    modport monitor(clocking mon_cb);
endinterface
 
// ── UVM driver — every bus access through the clocking block ──
class apb_driver extends uvm_driver;
    virtual apb_if.tb vif;
 
    task run_phase(uvm_phase phase);
        forever begin
            apb_xact tr; seq_item_port.get_next_item(tr);
            @(vif.tb_cb);                       // wait for posedge
            vif.tb_cb.paddr  <= tr.paddr;       // drives land Re-NBA
            vif.tb_cb.pwdata <= tr.pwdata;
            vif.tb_cb.psel   <= 1'b1;
            @(vif.tb_cb);
            vif.tb_cb.penable <= 1'b1;
            while (!vif.tb_cb.pready) @(vif.tb_cb);   // sampled Preponed
            vif.tb_cb.psel    <= 1'b0;
            vif.tb_cb.penable <= 1'b0;
            seq_item_port.item_done();
        end
    endtask
endclass
 
// ── UVM monitor — input-only clocking block ────────────────────
class apb_monitor extends uvm_monitor;
    virtual apb_if.monitor vif;
 
    task run_phase(uvm_phase phase);
        forever begin
            @(vif.mon_cb);
            if (vif.mon_cb.psel && vif.mon_cb.penable && vif.mon_cb.pready) begin
                apb_xact obs = apb_xact::type_id::create("obs");
                obs.paddr  = vif.mon_cb.paddr;
                obs.pwdata = vif.mon_cb.pwdata;
                ap.write(obs);
            end
        end
    endtask
endclass

Simulation Behavior — Where Clocking Block Samples and Drives Land in Time

The clocking block's value is not the syntax — it's the timing semantics. Every read through a clocking block returns the signal's value as of the Preponed region (i.e., before any procedural code or NBA update fires at the current edge). Every drive through a clocking block lands in the Re-NBA region (i.e., after the DUT's NBA updates have settled). Understanding the per-region scheduling unlocks the right mental model for every test you write.

SystemVerilog — tracing clocking block timing across regions
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// DUT
module dut(input apb_if.slave apb);
    always_ff @(posedge apb.clk) begin
        if (apb.psel && apb.penable)
            internal_reg <= apb.pwdata;        // NBA update
    end
endmodule
 
// TB
module tb;
    apb_if u_apb(.clk);
    dut u_dut(.apb(u_apb.slave));
 
    initial begin
        @(u_apb.tb_cb);
        u_apb.tb_cb.pwdata <= 32'hAB;
        u_apb.tb_cb.psel   <= 1'b1;
        u_apb.tb_cb.penable<= 1'b1;
        @(u_apb.tb_cb);
        // What does the next read see?
        $display("after edge: pwdata=%h, internal=%h",
                 u_apb.tb_cb.pwdata,                    // sampled Preponed: pre-edge value
                 u_apb.u_dut.internal_reg);             // raw access: post-NBA value
    end
endmodule

Waveform Analysis — What Clocking Block Timing Looks Like in Verdi/DVE

Open the waveform viewer on a clocking-block-protected TB. The bus signals look exactly as you'd expect — paddr, psel, etc. drive cleanly relative to the clock. What's invisible in the waveform but visible in the cycle count is the structural race immunity: zero glitches, zero X's, zero "value depends on simulator" results.

Expected waveform — clocking-block-driven APB vs raw-signal-driven
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
With clocking block (input #1step, output #0):
 
  clk            _│‾│_│‾│_│‾│_│‾│_│‾│_
 
  paddr            --   100  100  100  --    ← drives land cleanly after NBA
  psel           0   1    1    1    1   0
  penable        0   0    1    1    1   0
  pready         0   0    0    0    1   --   ← stable Preponed sample
  internal_reg   --  --   --   --   100 100  ← DUT NBA captures stable value
 
  No glitch on paddr at the clock edge. No race between TB drive and DUT sample.
  Cycle count is deterministic across simulator versions.
 
 
Without clocking block (raw vif access in driver):
 
  clk            _│‾│_│‾│_│‾│_│‾│_│‾│_
 
  paddr            X100 ?    ?    ?    --   ← potential X glitch as TB drives
                                              same time DUT samples
  internal_reg   --  --   X?  100? --   --   ← either 100 or X depending on scheduler
 
  Test sometimes captures 100, sometimes X. Same source, different simulator
  versions or different machine loads produce different results.
  Classic sporadic regression failure mode.

Industry Insights — Clocking Block Practices in Production Teams

Debugging Academy — 5 Real Clocking Block Bugs

Each lab is a real failure mode pulled from production projects. Buggy code, symptom, root cause, fix.

1

Raw vif Access Mixed with Clocking Block — Sporadic Race

RACE CONDITION
Buggy Code
One raw access leaks into a clocking-block-driven task
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Driver mostly uses the clocking block, but one access leaked:
task drive_write(int addr, int data);
    @(vif.tb_cb);
    vif.tb_cb.paddr  <= addr;
    vif.tb_cb.pwdata <= data;
    vif.tb_cb.psel   <= 1;
 
    // ❌ Forgot to use tb_cb here:
    vif.penable <= 1;                    // raw access — Active region
 
    @(vif.tb_cb);
    while (!vif.tb_cb.pready) @(vif.tb_cb);
endtask
Symptom

The test passes on VCS in the morning, fails on Questa in the afternoon — same source, same seed. Sometimes the DUT sees penable=1 in the cycle the TB intended; sometimes it sees it a cycle late. $display traces show the bug "disappearing" when extra prints are added (which slow down execution and change scheduling).

Root Cause

One raw vif.penable assignment landed in the Active region; the other three landed in Re-NBA via the clocking block. The DUT's NBA update of internal state runs between them. Depending on the simulator's scheduler, the DUT may see penable one cycle early or one cycle late.

Fix

Every TB-side bus access must go through the clocking block: change vif.penable <= 1; to vif.tb_cb.penable <= 1;. Promote this to a lint rule: any vif.<signal> outside of vif.tb_cb. or vif.mon_cb. in a UVM file is a CI failure. Lint catches this in the next commit instead of in regression three weeks later.

2

Sampling Skew of 0 — Driver Reads Its Own Just-Driven Value

SAMPLE TIMING
Buggy Code
input #0 races with same-edge writes
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
clocking tb_cb @(posedge clk);
    default input #0 output #0;          // ← input #0 is the bug
    output paddr, pwdata, /* ... */;
    input  prdata, pready;
endclocking
 
// Driver
task wait_for_ready();
    while (!vif.tb_cb.pready) @(vif.tb_cb);
    // Sometimes sees pready=1 immediately even though DUT hasn't asserted it yet
endtask
Symptom

Driver exits its wait_for_ready wait state on the same edge it drove psel/penable — DUT hasn't even seen the transaction yet, but TB believes it's complete. Subsequent transactions queue up too fast; the bus shows back-to-back transfers when the DUT never asserted pready for the first one.

Root Cause

input #0 samples in the Active region — at the same instant as the edge fires. The driver's just-driven values are still "visible" because their NBA updates and the DUT's NBA updates haven't run yet. The standard, race-free default is input #1step, which samples in the Preponed region (immediately before the edge), guaranteeing the sample is the stable pre-edge value.

Fix
Canonical race-free defaults
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
default input #1step output #0;
// Reads see the pre-edge value (Preponed); drives land after NBA (Re-NBA).

Almost every production interface uses exactly this combination. Deviations need an explicit justification and a code-review note explaining why.

3

##N with Default Clocking Block from Another Bus

DEFAULT DRIFT
Buggy Code
AXI burst silently uses APB's clock
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Two buses with different clocks
apb_if u_apb(.clk(apb_clk));      // 50 MHz
axi_if u_axi(.clk(axi_clk));      // 200 MHz
 
module tb;
    default clocking u_apb.tb_cb;  // ← APB is the default
    initial run_test();
endmodule
 
// In an AXI driver task:
task axi_burst_4cycles();
    ##4;     // ← intended 4 AXI cycles (20 ns); actually 4 APB cycles (80 ns)!
    end_transaction();
endtask
Symptom

AXI bursts complete in 80 ns instead of 20 ns. AXI throughput tests report 4× lower bandwidth than the spec. The test code looks "obviously right" because ##4 reads as "4 cycles" — but it's 4 cycles of the wrong clock.

Root Cause

##N resolves to "N cycles of the default clocking block." With a top-level default clocking u_apb.tb_cb, every unqualified ##N in any file uses APB's clock — even inside AXI driver code, which expected AXI's clock.

Fix
Qualify the clock explicitly in reusable code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// In the AXI driver, use explicit clocking-block events:
repeat (4) @(vif.tb_cb);

Never use default clocking in a multi-bus testbench. Use it only at the test top level and never in reusable agent code.

4

Clocking Block Drive After Reset Released — Captures Stale Value

RESET RACE
Buggy Code
First post-reset drive misses the first edge
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Reset is asynchronous; TB drives signals just before/during reset
initial begin
    apb.tb_cb.psel    <= 0;
    apb.tb_cb.penable <= 0;
    rst_n = 0;
    #50ns;
    rst_n = 1;          // release reset
    @(apb.tb_cb);
    apb.tb_cb.paddr   <= 32'h100;   // first real transaction
    apb.tb_cb.pwdata  <= 32'hAB;
    apb.tb_cb.psel    <= 1;
    // ... DUT sometimes captures uninitialised internal_reg before this drive
end
Symptom

First post-reset transaction sometimes succeeds, sometimes shows the DUT capturing X or a previous value in internal_reg. The bug looks like a DUT reset issue but is actually a TB scheduling order during the reset release.

Root Cause

Reset is asynchronous (raw rst_n = 0;), but the TB's first drive is via the clocking block (Re-NBA region). The DUT's first post-reset clock edge may fire before the clocking-block drive lands — the DUT sees the bus in its reset-default state (X for output signals without explicit reset).

Fix
Drive idle values through the clocking block, then wait two edges
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
apb.tb_cb.psel    <= 0;
apb.tb_cb.penable <= 0;
// ... reset release ...
repeat (2) @(vif.tb_cb);   // let Re-NBA drives propagate
// now begin first real transaction

Drive initial bus values through the clocking block before releasing reset, and use repeat (2) @(vif.tb_cb); after the reset release before the first real transaction. Alternative: keep the bus idle (driven to known reset values) for the entire reset period — many TB libraries provide a drive_idle() task for this exact purpose.

5

Clocking Block in Synthesisable File — Synthesis Drops It Silently

SIM/SYNTH GAP
Buggy Code
RTL module accidentally references the clocking block
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Interface shipped to synthesis with the clocking block visible:
interface apb_if(input logic clk);
    logic [31:0] paddr, pwdata;
    logic        psel, penable;
 
    clocking tb_cb @(posedge clk);
        default input #1step output #0;
        output paddr, pwdata, psel, penable;
    endclocking
 
    modport tb(clocking tb_cb);
endinterface
 
// Some RTL module accidentally references the clocking block:
module bad_rtl(apb_if.tb apb);
    always_ff @(posedge apb.clk)
        apb.tb_cb.paddr <= some_addr;   // ← references clocking block!
endmodule
Symptom

Simulation works fine. Synthesis runs with a buried warning: "clocking block reference ignored." The synthesised netlist has no driver for paddr from this module. Silicon validation finds the RTL never asserts the address bus.

Root Cause

Clocking blocks are simulation-only constructs. Any RTL module that references one via vif.cb.signal has nothing to drive once synthesis strips the clocking block. The warning lives in a 50000-line log; nobody sees it.

Fix

Two layered defences. (1) RTL modules never connect via the tb modport — they use master/slave modports that don't have clocking blocks. (2) Wrap clocking blocks in `ifndef SYNTHESIS so synthesis never even sees them. (3) Lint rule: any RTL module accessing vif.<clocking_block> is a CI failure. Once enforced, the bug class is structurally eliminated.

Interview Q&A — 12 Questions on Clocking Blocks

Drawn from real interviews at chip-design and verification companies. Try to answer before reading each response.

The testbench-vs-DUT race at the active clock edge. Without clocking blocks, TB procedural assigns and DUT NBA updates run in implementation-defined order in the Active region; the test may see different values on different simulators or different runs. Clocking blocks specify when the TB samples (Preponed: before any edge-triggered work) and when the TB drives (Re-NBA: after DUT NBA updates). Race-free by construction.

Best Practices — Clocking Block Rules to Walk Away With

  1. Every protocol interface has at least one clocking block. No exceptions for "slow" or "simple" buses — retrofit cost is always higher than initial cost.
  2. Use default input #1step output #0. The canonical race-free skew. Deviations need explicit code-review justification.
  3. Two clocking blocks per interface: tb_cb (driver) and mon_cb (monitor-only). Drivers attach via tb modport; monitors via monitor modport. Roles enforced structurally.
  4. Lint rule: no bare vif.<signal> in UVM files. Every access through vif.tb_cb. or vif.mon_cb.. CI fails on violation.
  5. Never mix ##N and @(posedge clk) in the same task. Pick clocking-block-relative or raw-event timing; don't combine.
  6. Avoid default clocking in multi-bus testbenches. ##N silently uses the wrong clock. Reusable components qualify every reference.
  7. Drive idle values through the clocking block during reset. Prevents first-post-reset transactions from seeing X on the bus.
  8. Guard clocking blocks behind `ifndef SYNTHESIS or keep them in TB-only files. Synthesis silently strips them; RTL accidentally referencing them silently drops drives.
  9. Sample DUT internals via clocking blocks too. Hierarchical-reference reads in monitors and scoreboards have the same race exposure; clocking-block-protect them as well.
  10. Document the clocking-block contract in the interface header. Sampled signals, driven signals, skew values, intended consumers. The next reader needs the contract, not a forensic read of the source.