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
endmoduleafter 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
endmodulet=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.
#1stepor#0): how far before the edge the testbench samples.#1stepis 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
endmodulet=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
endclassEvery 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
| Guideline | Why it matters |
|---|---|
Sample DUT outputs with input #1step | Reads the stable pre-edge value, never a mid-edge update |
Drive DUT inputs with a non-zero output skew | Stimulus lands after the edge, off the DUT’s sampling point |
Always use <= on clocking outputs | Non-blocking drives align with the skewed, race-free schedule |
Prefer ##N over raw #delay | Stimulus stays correct if the clock period changes |
| Access signals only through the clocking block | Removes 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.
Share your question in comments or talk to our mentor team for batch guidance.
Compile and simulate this in the browser — no install. Edit the code and re-run instantly.
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