SystemVerilog Clocking Block: Synchronous Sampling & Skew

A SystemVerilog clocking block is a container that groups signals together with a clock and a set of timing rules, so that a testbench samples and drives those signals at precise, race-free moments relative to a clock edge. It exists to solve one stubborn problem in verification: when the testbench and the design-under-test (DUT) react to the same clock edge, who wins the race for a shared signal? The clocking block answers that question deterministically.

Why a testbench needs a clocking block

Both your stimulus code and the DUT’s RTL evaluate in response to posedge clk. If the testbench drives a signal with a blocking assignment at the edge while the DUT reads that same signal at the edge, the outcome depends on event-scheduler ordering — a classic read/write race. The simulator may give a stable answer, but it is accidental, and it can differ between tools or between simulation and a later run.

The clocking block removes the gamble. It introduces two deliberate offsets around each clock edge: an input skew that decides how early the testbench samples, and an output skew that decides how late the testbench drives. Samples are taken from a stable, pre-edge value; drives land after the edge. The DUT and the testbench therefore never contend for the same signal at the same instant.

Anatomy of a clocking block

A clocking block names a clocking event, sets default skews, and lists signal directions from the testbench’s point of view. output means the testbench drives it; input means the testbench samples it. Marking one block default lets you use cycle-delay operators like ##1 without naming the block each time.

module tb;
  logic clk = 0;
  logic [7:0] data;
  logic       valid;

  always #5 clk = ~clk;

  default clocking cb @(posedge clk);
    default input #1step output #2;
    output data, valid;
  endclocking

  initial begin
    data  = '0;
    valid = 1'b0;
    ##1;
    cb.data  <= 8'hA5;
    cb.valid <= 1'b1;
    ##1;
    $display("after 1st drive: t=%0t data=%02h valid=%b", $time, data, valid);
    ##2;
    $display("done at t=%0t", $time);
    $finish;
  end
endmodule
after 1st drive: t=15 data=a5 valid=1
done at t=35

Notice that drives use the clocking-block handle (cb.data) and the non-blocking operator. Because the output skew is #2, the assignment scheduled at the first edge (t=5) actually updates the signal two time units later. By the next edge at t=15 the value a5 is stable and visible.

Synchronous sampling and input skew

Input skew controls when the testbench reads a signal. The special value #1step samples in the preponed region — an infinitesimal moment before the active edge — so the testbench always sees the value that was stable going into the edge, never a value that the DUT updates during that same edge’s non-blocking phase.

The example below samples a free-running counter. The DUT increments count on every posedge, yet the clocking block consistently reports the pre-edge value, proving the sample is race-free rather than catching the mid-edge update.

module tb;
  logic clk = 0;
  logic [3:0] count;

  always #5 clk = ~clk;

  // Sampling clocking block: input skew #1step reads the value
  // stabilised just before each posedge, never the mid-cycle churn.
  clocking sample_cb @(posedge clk);
    default input #1step;
    input count;
  endclocking

  // DUT-like free-running counter.
  always @(posedge clk) count <= count + 1'b1;

  initial begin
    count = 4'd0;
    repeat (4) begin
      @(sample_cb);
      $display("t=%0t sampled count=%0d", $time, sample_cb.count);
    end
    $finish;
  end
endmodule
t=5 sampled count=0
t=15 sampled count=1
t=25 sampled count=2
t=35 sampled count=3

At t=5 the counter holds 0 going into the edge, so the sample is 0 even though the DUT schedules 1 at that very edge. Each subsequent cycle reports the clean previous value: 0, 1, 2, 3. That predictable one-cycle view is exactly what monitors and scoreboards rely on.

Reading input versus output skew

  • Input skew (e.g. #1step or #0): how far before the edge the testbench samples. #1step is the safe default for sampling DUT outputs.
  • Output skew (e.g. #1): how far after the edge the testbench’s drive takes effect, keeping stimulus off the active edge.

Driving the DUT without races

When you drive a DUT input, the output skew is what guarantees safety. The stimulus is applied a short delay after the edge, so by the next edge the DUT samples a settled value. The testbench and DUT are no longer fighting over the same nanosecond.

module tb;
  bit clk = 0;
  logic req;
  logic gnt;

  always #5 clk = ~clk;

  // Testbench drives req through the clocking block (output skew #1),
  // so the stimulus lands just after the edge and never races the DUT.
  default clocking drv @(posedge clk);
    default output #1;
    output req;
    input  gnt;
  endclocking

  // Simple DUT: grant follows request by one cycle.
  always @(posedge clk) gnt <= req;

  initial begin
    req = 1'b0;
    ##1 drv.req <= 1'b1;   // request on cycle 1
    ##1 drv.req <= 1'b0;   // release on cycle 2
    repeat (3) begin
      @(drv);
      $display("t=%0t req=%b gnt=%b", $time, req, drv.gnt);
    end
    $finish;
  end
endmodule
t=15 req=1 gnt=0
t=25 req=0 gnt=1
t=35 req=0 gnt=0

The log shows req=1 gnt=0, then req=0 gnt=1: the grant trails the request by exactly one cycle, and the sampled drv.gnt always reflects the pre-edge value. Because req is driven with output skew, the DUT never sees it change on the edge it is sampling.

default clocking and cycle delays

Declaring a block as default clocking unlocks the cycle-delay operator ##N, which advances the process by N occurrences of that block’s clocking event. Writing ##1; is far clearer than @(posedge clk); and, crucially, it synchronises to the same preponed/skewed timeline as your samples and drives. You can have only one default clocking per module or interface; additional blocks are referenced by name with @(cb_name).

A useful habit: keep stimulus loops expressed in cycles (##1, ##4) rather than raw delays (#10). Cycle delays stay correct if the clock period changes, whereas hard-coded time delays silently drift out of alignment.

Clocking blocks inside an interface

In real verification environments the clocking block lives inside an interface, paired with a modport, and is reached through a virtual interface from class-based stimulus. The testbench modport exposes only the clocking block, so a driver physically cannot make an unsynchronised assignment.

interface bus_if (input logic clk);
  logic        rst_n;
  logic [31:0] addr;
  logic [31:0] wdata;
  logic        write;

  // Testbench view: drive after the edge, sample just before it.
  clocking tb_cb @(posedge clk);
    default input #1step output #1;
    output addr, wdata, write;
    input  rst_n;
  endclocking

  modport TB (clocking tb_cb, input clk);
endinterface

class driver;
  virtual bus_if.TB vif;

  task automatic send(bit [31:0] a, bit [31:0] d);
    vif.tb_cb.addr  <= a;
    vif.tb_cb.wdata <= d;
    vif.tb_cb.write <= 1'b1;
    @(vif.tb_cb);          // advance one clock
    vif.tb_cb.write <= 1'b0;
  endtask
endclass

Every driver access goes through vif.tb_cb, inheriting the skews. The result is a driver that is correct by construction: it cannot accidentally create a race because it has no path to the raw signals.

Best practices to avoid races

GuidelineWhy it matters
Sample DUT outputs with input #1stepReads the stable pre-edge value, never a mid-edge update
Drive DUT inputs with a non-zero output skewStimulus lands after the edge, off the DUT’s sampling point
Always use <= on clocking outputsNon-blocking drives align with the skewed, race-free schedule
Prefer ##N over raw #delayStimulus stays correct if the clock period changes
Access signals only through the clocking blockRemoves any back door that could reintroduce a race

Treat the clocking block as the single synchronous boundary between your testbench and the DUT. Once every sample and drive flows through it, timing races stop being something you debug and become something the language prevents.

Quick check

What problem does a clocking block primarily solve?

It removes read/write races between the testbench and the DUT by giving every sample and drive a deterministic offset (skew) from the clock edge, instead of letting event-scheduler ordering decide the outcome.

What does an input skew of #1step mean?

The signal is sampled in the preponed region, an infinitesimal moment before the active clock edge, so the testbench always reads the value that was stable going into the edge rather than any value the DUT updates on that same edge.

Why drive clocking-block outputs with a non-zero output skew and <=?

A positive output skew applies the drive a short delay after the edge, keeping stimulus off the edge the DUT samples. The non-blocking operator schedules the update on the clocking block’s synchronous timeline, preserving the race-free behaviour.

What does declaring a block as default clocking enable?

It lets you use the cycle-delay operator ##N to advance by N clocking events without naming the block, and it ties those delays to the same skewed timeline as your samples and drives. Only one default clocking is allowed per module or interface.

Have questions about this topic?
Share your question in comments or talk to our mentor team for batch guidance.
▶ Try it in the ChipXpert Lab

Compile and simulate this in the browser — no install. Edit the code and re-run instantly.

Open in Verification Lab

Learn Verilog with mentors, labs and placement support

This free series is maintained by the ChipXpert training team in Hyderabad & Bengaluru. Go deeper with instructor-led courses on real EDA tools.

Explore VLSI Courses
Next batch starts 18th November 2026
Start your VLSI career with ChipXpert

Live online and classroom batches in Hyderabad & Bengaluru. Fill in your details and a counsellor will call you back.

  • Real EDA tools in your browser: industry-standard EDAReal EDA tools in your browser
  • Recorded sessions on the elearn portalRecorded sessions
  • Placement assistance: resume, mock interviewsPlacement assistance
  • Merit scholarship up to 60% · EMI optionsScholarship up to 60%
Hi! Ask me about courses, fees, batches or discounts. ×
BENGALURU
HYDERABAD