SystemVerilog · Module 15
Clocking Blocks — Deep Dive, SVA, Coverage, Multi-Clock SoC, CDC
SystemVerilog clocking blocks at production depth — input/output skews, the ##N operator, multi-clocking-block patterns, default clocking, and the deeper architectural role as the foundation of SVA, functional coverage, multi-clock SoC verification, and clock-domain crossing (CDC) discipline.
Module 15 · Page 15.2 — Deep Dive
A clocking block packages a clock edge, its signal directions, and the sampling skews into a single named object inside an interface. The basics (covered at /learnings/systemverilog/clocking-blocks) explain how it eliminates the Active-region TB-DUT race. This deep-dive lesson goes further — into the architectural roles the clocking block plays as the foundation of SVA, functional coverage, multi-clock-domain verification, and clock-domain crossing (CDC) discipline. Every modern UVM testbench that touches a synchronous interface relies on it; every multi-clock SoC has one per clock domain; every SVA property is anchored to one; every cycle-sampled covergroup samples on one. Understanding the clocking block at this depth is the prerequisite to debugging the verification stack of any non-trivial chip.
1. Engineering Problem — The Three Things a Clocking Block Solves
A modern SoC verification environment faces three layered problems that pre-clocking-block testbenches couldn't solve cleanly:
- The TB-DUT race at the clock edge — module-based testbench code wakes in the Active region, before the DUT's NBA-region flip-flop updates commit. The TB reads pre-edge values; the DUT thinks it has updated them. Covered at the introductory level in /learnings/systemverilog/clocking-blocks and in 15.1 program-block.
- Multi-clock-domain coordination. A real SoC has 5–50 clock domains: a fast core clock, an interconnect clock at half speed, multiple DDR clocks, separate USB/PCIe/Ethernet clocks, an always-on (AON) clock. Every domain has its own sampling discipline, its own setup/hold requirements, and its own verification VIP. Without a per-domain clocking primitive, every monitor and driver re-invents the timing wheel — inconsistently.
- The SVA / coverage / CDC dependency chain. SystemVerilog assertions sample on a clock; functional coverage often samples on a clock; CDC verification depends on knowing exactly when signals are sampled on each side of a domain boundary. The clocking block is the single construct that anchors all three. Mis-specify the clocking block and your assertions evaluate on wrong edges, your coverage misses real bin hits, your CDC analysis flags false positives.
The clocking block exists to be the canonical timing contract for everything in the verification environment that has to coordinate with a clock. This lesson treats it at that depth.
2. Mental Model — The Contract Attached to a Clock Edge
The picture every engineer carries:
A clocking block is a typed contract attached to a clock edge. It says: "for this clock, here are the signals I observe, here are the signals I drive, here is exactly how long before the edge I sample inputs, and here is exactly how long after the edge I drive outputs." Every consumer of that contract — testbench code, SVA property, covergroup — automatically gets race-free, skew-correct access to those signals without having to think about scheduler regions again.
Four invariants this picture preserves:
- Direction is from the testbench's perspective.
inputin a clocking block means "the TB reads this signal" (the DUT drives it).outputmeans "the TB drives this signal" (the DUT reads it). This is the opposite convention from a module's port list and is the most common source of clocking-block confusion. - Inputs are sampled at
input_skewbefore the edge. The default is#1step— the last time step before the edge — guaranteeing the sample point is after any pending DUT updates from the previous edge have settled. Inputs read from the clocking block always hold post-settled DUT values. - Outputs are driven at
output_skewafter the edge. The default is#1— one time unit after the edge — guaranteeing the DUT sees the new stimulus with full setup time before the next edge. Outputs written through the clocking block always reach the DUT with proper setup. - Multiple clocking blocks can coexist in one interface. Master and slave perspectives, fast and slow domains, transmit and receive clocks — each gets its own clocking block. The interface bundles the signals; the clocking blocks bundle the timing.
3. Visual Explanation — The Input/Output Skew Window
The clocking block's timing model in one figure: input signals are sampled at the input-skew window (just before each clock edge); output signals are driven at the output-skew window (just after each edge). The pre-edge sample and post-edge drive together eliminate both races — DUT-reads-stimulus-too-early and TB-reads-response-too-late.
Figure — clocking block timing: input sampled before edge, output driven after edge
12 cyclesThe figure captures the asymmetry: inputs land before the edge (so the TB reads stable values), outputs land after the edge (so the DUT sees stable values for the next edge). This is the entire teaching of the clocking block timing model, compressed into one diagram.
4. Syntax & Semantics — Complete Reference
4.1 The general form
// ── General form ──────────────────────────────────────────────
clocking cb_name @(clocking_event);
default input #input_skew
output #output_skew; // default skews for all signals
input signal_a; // TB reads — DUT output
input signal_b;
output signal_c; // TB drives — DUT input
output signal_d;
inout signal_e; // bidirectional
input #2 signal_f; // per-signal skew override
output #3 signal_g;
endclocking
// ── Real APB example inside an interface ──────────────────────
interface apb_if (input logic pclk);
logic presetn;
logic psel, penable, pwrite, pready, pslverr;
logic [31:0] paddr, pwdata, prdata;
// Testbench clocking block — captures the timing contract once
clocking cb @(posedge pclk);
default input #1step // sample just before posedge
output #1; // drive 1 time unit after posedge
// Direction is from the TB's perspective:
input pready, pslverr, prdata; // TB reads (DUT drives these)
output psel, penable, pwrite, paddr, pwdata; // TB drives (DUT reads)
endclocking
// presetn is not in the clocking block — async signal, no clock skew needed
modport TB (clocking cb, output presetn);
modport DUT (input psel, penable, pwrite, paddr, pwdata, presetn,
output pready, pslverr, prdata);
endinterface4.2 Direction conventions — TB perspective vs DUT perspective
Direction in clocking cb | TB's role | DUT's role | Skew applied |
|---|---|---|---|
input signal_a | reads the signal | drives the signal | sampled at #input_skew before the edge |
output signal_b | drives the signal | reads the signal | scheduled at #output_skew after the edge |
inout signal_c | both reads + drives | both drives + reads | both skews apply |
The opposite-convention trap: a DUT module declares output [7:0] data_out (the DUT drives data_out). The same signal appears in the testbench's clocking block as input data_out (the TB reads data_out). Same wire, opposite direction keyword, depending on whose port you're looking at.
4.3 The full method set — @(cb), ##n, iff
// ── @(cb): wait for exactly one clocking event ────────────────
@(bus.cb); // wait for the next posedge pclk (as defined in cb)
// ── ##n: wait for exactly N clock cycles ──────────────────────
##1; // 1 clock cycle (syntactic sugar for @(default_cb))
##4; // 4 clock cycles — much cleaner than repeat(4) @(cb)
// ── ##[m:n]: range delay — wait between m and n cycles ────────
##[1:8]; // wait between 1 and 8 cycles (non-deterministic)
##[0:4] signal; // wait 0 to 4 cycles until signal is asserted
// ── @(cb iff condition): wait for clock edge WITH a condition ─
@(bus.cb iff bus.cb.pready); // posedge where pready=1
@(bus.cb iff !bus.cb.pslverr); // posedge where no error
@(bus.cb iff bus.cb.prdata == 32'hCAFE_0001); // value-specific wait4.4 Driving outputs — non-blocking is mandatory
// ── CORRECT: use <= (non-blocking) for clocking block outputs ─
bus.cb.psel <= 1; // scheduled to drive #1 after posedge pclk
bus.cb.paddr <= 32'h1000; // (default output skew applies)
bus.cb.pwrite <= 1;
// ── WRONG: blocking = inside a clocking block output ──────────
bus.cb.psel = 1; // COMPILE ERROR or undefined — outputs require <=The non-blocking requirement falls out of the skew implementation: the clocking block schedules the assignment in the NBA queue with the output skew added. A blocking = would bypass that scheduling, defeating the race-elimination purpose of the clocking block. Most simulators reject blocking assignment to clocking block outputs at compile time; some produce undefined behaviour.
4.5 default clocking — module-wide default
program clean_test (apb_if.TB bus);
default clocking bus.cb; // declare the program's default clocking
initial begin
##4; // 4 cycles — refers to bus.cb automatically
bus.cb.psel <= 1;
bus.cb.paddr <= 32'h8000;
bus.cb.pwrite <= 1;
##1;
bus.cb.penable <= 1;
@(bus.cb iff bus.cb.pready); // wait for DUT ready
bus.cb.psel <= 0;
bus.cb.penable <= 0;
$exit;
end
endprogramWithout default clocking, every ##n must be qualified: repeat (n) @(bus.cb);. The default-clocking declaration is one line; the readability win is significant in any program block longer than a few statements.
4.6 Multiple clocking blocks — master + slave perspectives
interface axi_if (input logic aclk);
logic aresetn, awvalid, awready, wvalid, wready, wlast, bvalid, bready;
logic [31:0] awaddr, wdata;
logic [1:0] bresp;
// ── Master clocking block: TB acts as AXI master ──────────
clocking master_cb @(posedge aclk);
default input #1step output #1;
output awvalid, awaddr; // master drives AW channel
input awready; // master reads slave's ready
output wvalid, wdata, wlast; // master drives W channel
input wready;
input bvalid, bresp; // master reads B channel
output bready;
endclocking
// ── Slave clocking block: TB acts as AXI slave ────────────
clocking slave_cb @(posedge aclk);
default input #1step output #1;
input awvalid, awaddr; // slave reads AW channel
output awready; // slave drives ready
input wvalid, wdata, wlast;
output wready;
output bvalid, bresp; // slave drives B channel
input bready;
endclocking
modport MASTER (clocking master_cb, output aresetn);
modport SLAVE (clocking slave_cb);
endinterfaceThe same set of signals appears in both clocking blocks with opposite directions. The master-VIP program connects to axi_if.MASTER; the slave-VIP program connects to axi_if.SLAVE. Both get race-free access to their side of the bus.
5. Simulation View — Where Sampling and Driving Happen
The clocking block extends the IEEE 1800-2017 scheduling model with two skew operations:
- Input skew (
#1stepdefault) schedules the sampling of the listed input signals to the Postponed region of the previous time step (one simulator-resolution-unit before the clock edge). The value the TB reads is the fully-settled post-NBA value from the previous edge. - Output skew (
#1default) schedules the driving of the listed output signals to one time unit after the clock edge. The DUT sees the new value with full setup time before its next sampling edge.
The asymmetry is deliberate. Inputs need to look backward (read what just settled); outputs need to look forward (drive what the DUT will sample next). The clocking block compresses both disciplines into one declaration so neither the TB nor the DUT can accidentally violate them.
#1step is a special time unit: not a fixed nanosecond, but one simulator time step before the specified clock edge — effectively, "sample in the Postponed region of the previous time slot." It is the recommended default input skew because it guarantees the sample point is before any active clock-edge evaluation.
6. Waveform — Skew in Action
The waveform below extends §3 with a realistic 3-cycle APB transaction. Pre-edge sampling captures DUT outputs; post-edge driving delivers TB stimulus. Notice how the DUT's pready is captured at the input-skew window of cycle 4 (the iff waits for the post-NBA-settled pready=1), and the TB's psel/penable deassertion lands at the output-skew window of cycle 6 (the DUT sees the clean deassertion on its cycle-7 sample).
Figure — clocking block over a 3-cycle APB transaction with iff-wait on pready
10 cycles7. Synthesis — Not Applicable
clocking blocks are a procedural simulation construct. They have no hardware footprint and no place in synthesisable RTL — synthesis tools reject the clocking keyword outright. This section is intentionally omitted; the topic does not warrant it.
The corresponding hardware-side discipline is just the synchronous interface itself — always_ff @(posedge clk) blocks with registered outputs and adequate setup/hold margin from the STA tool. The clocking block is the verification-side counterpart that gives the TB the same timing discipline the hardware naturally has.
8. Verification View — The Architectural Role
This is the section that distinguishes the deep-dive from the basic lesson. Clocking blocks are not just a TB-DUT race fix — they are the timing foundation that SVA, functional coverage, multi-clock-domain VIPs, and CDC verification all build on. Each subsection walks one of the dependent layers.
8.1 APB transaction — the canonical pattern
program apb_test (apb_if.TB bus);
default clocking bus.cb;
// ── APB Write ─────────────────────────────────────────────
task automatic apb_write(input [31:0] addr, data);
bus.cb.psel <= 1;
bus.cb.paddr <= addr;
bus.cb.pwrite <= 1;
bus.cb.pwdata <= data;
##1; // setup phase
bus.cb.penable <= 1; // access phase
@(bus.cb iff bus.cb.pready); // wait for slave ready
bus.cb.psel <= 0;
bus.cb.penable <= 0;
##1;
endtask
// ── APB Read ──────────────────────────────────────────────
task automatic apb_read(input [31:0] addr, output [31:0] data);
bus.cb.psel <= 1;
bus.cb.paddr <= addr;
bus.cb.pwrite <= 0;
##1;
bus.cb.penable <= 1;
@(bus.cb iff bus.cb.pready);
data = bus.cb.prdata; // capture read data after pready
bus.cb.psel <= 0;
bus.cb.penable <= 0;
##1;
endtask
initial begin
logic [31:0] rd_data;
bus.presetn = 0; ##4; bus.presetn = 1; ##2;
apb_write(32'h0000_0010, 32'hABCD_1234);
apb_read (32'h0000_0010, rd_data);
assert (rd_data == 32'hABCD_1234)
else $error("Read-back mismatch: got 0x%h", rd_data);
$exit;
end
endprogramEvery assignment uses <= through the clocking block; every wait uses @(bus.cb iff ...); every cycle delay uses ##n. No raw #delay values; no manual setup-time calculations. The clocking block makes the TB look like the spec: setup → access → ready → deassert.
8.2 SVA's dependence on clocking blocks
SystemVerilog Assertions sample on a clock. The clock can be specified per-property, per-property-set via default clocking, or inherited from the surrounding clocking block. Every concurrent assertion has a clocking-block dependency, explicit or implicit — and getting it wrong is the most common SVA bug class after the antecedent / consequent confusion itself.
interface apb_if (input logic pclk);
// ... signals ...
clocking cb @(posedge pclk);
default input #1step output #1;
input pready, pslverr, prdata;
output psel, penable, pwrite, paddr, pwdata;
endclocking
// ── Default clocking for ALL assertions in this scope ─────
default clocking cb;
// ── Properties — no explicit @(posedge pclk) needed ───────
// The default clocking provides the implicit sampling event
property psel_implies_penable_next_cycle;
psel && !penable |=> penable;
endproperty
assert property (psel_implies_penable_next_cycle);
// ── Property with explicit clocking — overrides the default
property addr_stable_during_transfer;
@(posedge pclk) disable iff (!presetn)
(psel && penable && !pready) |=> $stable(paddr);
endproperty
assert property (addr_stable_during_transfer);
// ── Property reading clocking-block-sampled signals ───────
// Inside an assertion, signals reach the property via the SAME
// input-skew sample that the TB uses. No race; values are settled.
property no_error_on_completion;
@(cb) (psel && penable && pready) |-> !pslverr;
endproperty
assert property (no_error_on_completion);
endinterfaceThree ways assertions get their clock, in order of preference:
default clocking— every property in the scope inherits this clock. The cleanest pattern; recommended for any interface with one dominant clock.@(cb)on the property itself — uses the clocking block by name, sharing its sample-skew semantics with the TB code.@(posedge clk)directly on the property — falls back to the raw clock edge, bypassing the clocking block's input skew. The property sees signals in the Active region, which can disagree with what the TB sees through the clocking block.
The third form is occasionally needed (e.g., for properties on signals that aren't in the clocking block), but every use deserves a comment explaining why. Mixed sampling — TB through clocking block, assertion through raw clock — produces the assertion thinks X happened but the TB log says Y divergence bugs.
8.3 Functional coverage's dependence on clocking blocks
covergroups sample on an event. The event is typically a clock edge, and binding the sampling event to a clocking block ensures the covergroup samples the same settled values the TB reads — same race-elimination, same skew discipline.
class apb_coverage;
virtual apb_if vif;
// ── Covergroup samples on the clocking block edge ─────────
covergroup apb_cg @(vif.cb);
cp_addr: coverpoint vif.cb.paddr {
bins low_addrs = {[0 : 32'h0FFF]};
bins mid_addrs = {[32'h1000 : 32'h7FFF]};
bins high_addrs = {[32'h8000 : 32'hFFFF_FFFF]};
}
cp_write: coverpoint vif.cb.pwrite {
bins write = {1};
bins read = {0};
}
cp_resp: coverpoint vif.cb.pslverr iff (vif.cb.psel && vif.cb.penable) {
bins ok = {0};
bins error = {1};
}
// ── Cross coverage: addr × write × resp ───────────────
addr_x_dir_x_resp: cross cp_addr, cp_write, cp_resp;
endgroup
function new(virtual apb_if vif);
this.vif = vif;
apb_cg = new();
endfunction
endclass
// ── Why the clocking-block sampling event matters ─────────────
// Covergroup samples are evaluated when the @(vif.cb) event fires.
// At that instant, vif.cb.paddr, vif.cb.pwrite, vif.cb.pslverr all
// reflect their input-skew-sampled values — the same values the TB sees.
// Without the clocking block (e.g., @(posedge pclk)), the covergroup
// would sample in the Active region before NBA — potentially capturing
// pre-edge values, leading to coverage bins that don't match what
// actually happened on the bus.The teaching: every cycle-sampled covergroup in a production VIP samples on a clocking block edge — not on the raw clock. The clocking block's input skew gives the coverage logic the same settled values the rest of the verification environment sees. Cycle-accurate coverage that disagrees with the rest of the TB log is almost always a covergroup sampled on the raw clock instead of the clocking block.
8.4 Multi-clock SoC — one clocking block per domain
Real SoCs have many clock domains. The standard architecture is one clocking block per domain, each in the interface for that domain:
// ── Fast core / AXI domain ────────────────────────────────────
interface axi_if (input logic aclk); // aclk: 400 MHz
// ... AXI signals ...
clocking cb @(posedge aclk);
default input #1step output #1;
// ...
endclocking
endinterface
// ── Slow peripheral / APB domain ──────────────────────────────
interface apb_if (input logic pclk); // pclk: 100 MHz
// ... APB signals ...
clocking cb @(posedge pclk);
default input #1step output #1;
// ...
endclocking
endinterface
// ── DDR controller domain ─────────────────────────────────────
interface ddr_if (input logic ddr_clk); // ddr_clk: 800 MHz
// ... DDR signals ...
clocking cb @(posedge ddr_clk);
default input #1step output #1;
// ...
endclocking
endinterface
// ── Always-on (AON) domain ────────────────────────────────────
interface aon_if (input logic aon_clk); // aon_clk: 32 kHz
// ... wakeup, reset control signals ...
clocking cb @(posedge aon_clk);
default input #1step output #1;
// ...
endclocking
endinterface
// ── Top-level wires everything together ───────────────────────
module tb_top;
logic aclk = 0, pclk = 0, ddr_clk = 0, aon_clk = 0;
always #1.25 aclk = ~aclk; // 400 MHz
always #5 pclk = ~pclk; // 100 MHz
always #0.625 ddr_clk = ~ddr_clk; // 800 MHz
always #15625 aon_clk = ~aon_clk; // 32 kHz
axi_if axi (.aclk(aclk));
apb_if apb (.pclk(pclk));
ddr_if ddr (.ddr_clk(ddr_clk));
aon_if aon (.aon_clk(aon_clk));
soc_dut dut (.axi(axi.DUT), .apb(apb.DUT), .ddr(ddr.DUT), .aon(aon.DUT));
// Each agent has its own program / module, each with its own clocking block
axi_master_test axi_tb (.axi(axi.MASTER));
apb_master_test apb_tb (.apb(apb.TB));
ddr_master_test ddr_tb (.ddr(ddr.TB));
aon_pmu_test aon_tb (.aon(aon.TB));
endmoduleArchitectural rules for multi-clock testbenches:
- Every clock domain that the TB touches gets its own interface and its own clocking block.
- Per-domain agents drive their own clocking blocks — never reach across into another domain's clocking block.
- Signals that cross between domains use CDC primitives (next section) and are typically not in either domain's clocking block — they're handled by dedicated synchroniser monitors.
- A "global" testbench top-level module orchestrates per-domain agents but does not host any clocking block of its own — it's the wiring layer.
8.5 Clock-Domain Crossing (CDC) — introduction
Two signals that originate in different clock domains and meet at a single flop are a clock-domain crossing. CDC is where most real silicon bugs hide; the verification discipline starts with the clocking block (which proves what each domain's TB sees) and then layers CDC-specific structures on top.
Why CDC is dangerous. A flip-flop has a setup-and-hold window around its clock edge. If a signal coming from a different clock domain transitions inside that window, the flop's output goes metastable — settling to either 0 or 1 eventually, but in an unbounded amount of time. Real silicon resolves metastability within a few hundred picoseconds; simulation models it as either an X-propagation or a non-deterministic 0/1.
The canonical CDC fix: the two-flip-flop synchroniser. Two flops in series in the destination domain absorb the metastable window — if the first flop goes metastable, the second flop has a full destination clock cycle to wait for it to settle before sampling.
Figure — two-flip-flop synchroniser absorbs metastability at a CDC boundary
12 cyclesCDC verification layers built on clocking-block infrastructure:
| Layer | What it does | Clocking-block role |
|---|---|---|
| Per-domain monitors | Sample each side of the boundary | One clocking block per domain — gives the monitor race-free post-NBA values |
| CDC assertions | Verify two-flop synchroniser is present; verify gray coding on FIFO pointers | default clocking of the destination domain anchors the assertion's sampling event |
| Metastability injection | Inject X for one cycle after asynchronous transitions | Disabled / overridden through the clocking block's input skew |
| Async FIFO verification | Verify full/empty flags computed correctly across domains | Two clocking blocks (write-side cb, read-side cb) sample the gray-coded pointers on each side |
| CDC coverage | Coverage on which CDC paths exercised, under what handshake | Covergroups sample on per-domain clocking blocks |
The introductory takeaway: the clocking block isn't just the TB-DUT race fix. It's the per-domain timing primitive that makes every higher-level CDC verification structure expressible. A multi-clock SoC without one clocking block per domain is a multi-clock SoC where every monitor, every assertion, every covergroup is making implicit and inconsistent timing assumptions. A multi-clock SoC with per-domain clocking blocks has a single source of truth for "when does each side sample what."
A full CDC lesson — Gray-code pointers, async FIFOs, handshake protocols, the IEEE 1800 race-detect mechanism, JasperGold CDC formal flows — is a Module 17 / advanced-topics treatment. This deep-dive establishes the foundation.
9. Industry Usage — Where Clocking Blocks Land in Real Verification
- UVM
uvm_driveranduvm_monitor— every UVM agent's driver and monitor accesses DUT signals through a virtual interface's clocking block. The TLM transactions are decoded into clocking-block reads/writes; the timing discipline is uniform across all UVM agents in the testbench. - Every shipping AMBA / DDR / PCIe / Ethernet / USB / I²C VIP uses one clocking block per channel / per direction. The VIP's bus functional model is built on
@(cb)waits andcb.signal <= valuedrives; tests using the VIP get race-free access without thinking about it. - Default clocking for assertion sets — SVA-heavy verification environments declare
default clocking cb;at the interface level so every property in the interface inherits the same sampling discipline. Property libraries are written assuming this convention. - Covergroup sampling events — UVM
uvm_subscriber-based coverage classes typically attach theircovergroup @(vif.cb)to a clocking block edge. Per-cycle coverage that doesn't depend on a clocking block is non-portable across simulators. - Multi-clock SoC verification — every modern SoC TB has 5–50 clock-domain interfaces, each with its own clocking block. The architecture rule "one clocking block per domain, no cross-domain access" is taught in every major VIP coding standard (Synopsys, Cadence, Siemens).
- CDC sign-off flows — formal CDC tools (JasperGold, Conformal LP, Onespin) consume the clocking-block declarations as their "this is the verification's notion of when each domain samples" reference. CDC bug reports cite the clocking block as the timing-discipline boundary.
- Power-aware verification (UPF / CPF) — domains can be powered down independently; the clocking block for a powered-down domain stops generating its event. Power-aware UVM environments use the clocking block presence as a signal for "this domain is active."
10. Design Review Notes — What a Senior Will Flag
| Pattern in the diff | What review will say |
|---|---|
interface bus_if (input logic clk); ... logic data; ... endinterface (no clocking block) | "Every synchronous interface needs a clocking block. Without one, every agent that uses the interface reinvents the timing discipline — inconsistently. Add clocking cb @(posedge clk); default input #1step output #1; ... endclocking." |
Module port output [7:0] data in DUT shown as output [7:0] data in the TB's clocking block | "Wrong direction. DUT output = TB-side input (the TB reads what the DUT produces). The clocking block's direction is from the TB's perspective." |
bus.cb.psel = 1; (blocking assignment) | "Clocking block outputs require <= (non-blocking). The skew is implemented through NBA scheduling; blocking bypasses it. Some simulators reject this at compile time; others produce undefined behaviour." |
Assertion property p; @(posedge pclk) ...; endproperty in an interface that has clocking cb @(posedge pclk) | "Use @(cb) or default clocking cb; so the assertion samples the same values the TB sees. Raw @(posedge pclk) bypasses the clocking block's input skew and reads Active-region pre-NBA values — assertion can disagree with the TB." |
covergroup cg @(posedge clk); coverpoint paddr; endgroup in a clocking-block-equipped interface | "Sample on @(vif.cb) instead. Sampling on the raw clock means the covergroup captures pre-NBA values; the bin hits won't match what the TB log shows." |
Multi-domain SoC TB with clocking cb @(posedge clk) reused across multiple domains | "One clocking block per clock domain. Reusing a clocking block across aclk/pclk/ddr_clk is structurally impossible (a clocking block is bound to one clock); the bug is probably that multiple interfaces share the wrong clock port. Audit the interface-to-DUT wiring." |
default input #1 output #1; (default skews without #1step) | "#1step is the recommended default for inputs — it samples in the Postponed region of the previous time step, before any active-edge evaluation. #1 is acceptable for outputs (drives 1 unit after the edge for setup margin), but inputs should use #1step for full pre-edge safety." |
Clocking block missing default input/output #... declaration with per-signal skews everywhere | "Declare the default once at the top; override per-signal only when truly needed. Each per-signal skew is one more thing for the next engineer to verify; defaults are the contract." |
| Async signals (resets, interrupts) listed in the clocking block | "Async signals belong outside the clocking block. Including them attaches a clock skew to a signal that's intentionally asynchronous — defeats the purpose. Drive resets via the modport's direct output, not via bus.cb.presetn <=." |
The single highest-value rule: one clocking block per clock domain, used by every cycle-sampling consumer in that domain — TB code, SVA properties, covergroups. The discipline question at design review is "does every cycle-accurate operation in this domain go through the same clocking block?" — if not, the verification stack is inconsistent.
11. Debugging Guide — Real Failures, Real Fixes
SVA fails for valid handshakes; assertion sees pready=0 when TB sees pready=1
SVA-BYPASSES-CLOCKING-BLOCKinterface apb_if (input logic pclk);
logic pready, /* ... */;
clocking cb @(posedge pclk);
default input #1step output #1;
input pready;
endclocking
// ── BUG: property samples raw pclk, not the clocking block
property handshake;
@(posedge pclk) psel && penable |-> pready;
endproperty
assert property (handshake);
endinterfacepready=1 and the test passes the functional check. Coverage shows the bin hit. Same simulator, same seed, contradictory results across the verification stack.@(posedge pclk) — Active region, before NBA. At that instant pready still holds its pre-edge value (0). The TB reads through bus.cb.pready which is sampled at #1step in the previous time step's Postponed region — post-NBA-settled. The assertion and the TB disagree on what pready was.@(cb) psel && penable |-> pready; or add default clocking cb; at the interface level so every property inherits the clocking block. Now the assertion samples the same value the TB reads. Cross-simulator regression noise disappears.Coverage shows zero bin hits for transactions that clearly happened
COVERGROUP-WRONG-SAMPLE-EVENTcovergroup apb_cg @(posedge pclk); // BUG: sampled on raw clock
cp_resp: coverpoint pslverr iff (psel && penable) {
bins ok = {0};
bins error = {1};
}
endgroupcp_resp.error = 0 (no error bin hits) even though the assertion log shows pslverr asserted 50 times. Coverage closure stalled at 95%.@(posedge pclk) samples in Active region. At that instant, psel && penable may still hold pre-edge values; the iff guard fails; the bin sample is skipped. The covergroup's notion of "when did the transaction complete" is one delta off from the TB's notion.covergroup apb_cg @(vif.cb); and reference signals as vif.cb.psel, vif.cb.penable, vif.cb.pslverr. The covergroup now samples post-NBA-settled values; bin hits match the assertion log; coverage closes.Clocking block output reaches DUT before its next sampling edge — DUT samples mid-flight value
OUTPUT-SKEW-TOO-SMALLclocking cb @(posedge pclk);
default input #1step output #0; // BUG: output skew = 0
output psel, paddr;
endclockingpsel=1 but paddr still showing the previous value. Same TB code, same DUT, simulator-dependent reproducibility.output #0 schedules the drive at the same time as the clock edge. The DUT samples its inputs at that same edge. Race: did the DUT sample paddr before or after the TB drove it? Tool-dependent.output #1 (one time unit after the edge). The DUT sees the new value with full setup time before its next sampling edge — no race. #0 output skew is only ever appropriate for purely asynchronous signals, which shouldn't be in a clocking block in the first place.Two AXI agents on the same clock domain — one master, one slave — both write to the same wire
WRONG-MODPORT-PER-AGENT// Both agents use the .MASTER modport — both drive awvalid
axi_master_test master_tb (.axi(axi.MASTER));
axi_slave_test slave_tb (.axi(axi.MASTER)); // BUG: wrong modportawvalid shows X (multi-driver conflict) every cycle. Simulator warns about multiple drivers. Test fails to complete a single transaction. Looks like a DUT-output bug; it's actually a TB-wiring bug.axi.MASTER modport, which exposes the master-side clocking block where awvalid is an output. Both agents try to drive it; the wire shows X. The slave agent should connect to axi.SLAVE where awvalid is an input.axi.SLAVE: axi_slave_test slave_tb (.axi(axi.SLAVE));. The slave's clocking block sees awvalid as input (the master drives, the slave reads). The X-conflict disappears; the bus runs cleanly.CDC test passes in simulation but silicon fails — metastability not modelled
NO-METASTABILITY-INJECTION// Two-flop synchroniser in the DUT
always_ff @(posedge dst_clk) begin
dst_meta <= src_sig; // first flop
dst_sync <= dst_meta; // second flop
end
// TB simply observes dst_sync and assumes it's correctdst_sync exactly one destination cycle after dst_meta. Coverage looks complete. Silicon brings up first time but exhibits intermittent failures under specific PVT corners that the regression never caught.src_sig transitions during dst_clk's setup/hold window, the simulator deterministically captures the new value — but real silicon may take an unbounded time to resolve and may produce the old value on the second flop the next cycle. The simulation gives a falsely optimistic picture of CDC behaviour.dst_meta when src_sig transitions inside the destination setup window; (b) use formal CDC tools (JasperGold CDC, Conformal LP) that prove the synchroniser is correctly placed and that all CDC paths use proper synchronisation; (c) run gate-level simulation with SDF back-annotation — the SDF-derived setup/hold checks expose the race. The clocking block isn't the whole CDC story but it's the per-domain timing primitive every CDC structure builds on.12. Interview Insights — What Interviewers Actually Probe
A clocking block is a SystemVerilog construct, declared inside an interface, that packages a clock edge with the signal directions and skews used to sample inputs and drive outputs. It solves three problems: (1) the TB-DUT race in the Active region (by sampling inputs at #1step in the previous Postponed region and driving outputs at #1 after the edge); (2) multi-clock-domain coordination (one clocking block per domain gives every consumer in that domain a consistent timing contract); (3) the SVA / coverage / CDC dependency chain (assertions, covergroups, and CDC monitors all anchor to the clocking block's sampling event so they see the same values the TB sees).
13. Exercises
1. Design — clocking block for AHB-Lite (Foundation)
Write an interface ahb_lite_if (input logic hclk); with a clocking block cb covering hsel, haddr, hwrite, hwdata, hready, hrdata, hresp. Use default input #1step output #1. Add a modport TB exposing cb and a modport DUT with the correct directions from the DUT's perspective.
2. Debug — the silent SVA failure (Intermediate)
A teammate's APB property property handshake; @(posedge pclk) psel && penable |-> pready; endproperty fires on cycles where the TB clearly logged pready=1. The interface has a clocking cb @(posedge pclk) with pready as an input. Identify the one-line fix and explain why the fix works.
3. Code review — multi-domain wiring (Intermediate)
A teammate's testbench wires an APB slave VIP using apb_slave_test slave (.apb(apb_bus.TB)); — the same modport as the master VIP. Coverage shows zero bin hits. Identify the architectural bug and propose the correct fix (hint: it involves a second clocking block + modport in the interface).
4. Trade-off — default clocking everywhere vs explicit @(cb) per property (Advanced)
A junior teammate argues: "always use default clocking at the interface — it's cleaner; never name the clocking block on individual properties." Argue the case against the blanket rule. When does an explicit @(cb) or @(posedge clk) on a specific property legitimately beat the default? Consider multi-clock interfaces, async assertions, and properties that need a different sampling discipline from the rest of the interface.
14. Summary
A clocking block is the per-clock timing contract at the centre of every modern SystemVerilog verification environment. It eliminates the TB-DUT race by sampling inputs at #1step before the edge and driving outputs at #1 after. It anchors the sampling event for SVA properties and functional coverage so the entire verification stack agrees on what each signal looked like at each edge. It scales to multi-clock SoCs via one clocking block per domain. It provides the per-domain timing primitive every CDC verification structure builds on.
Defaults to memorise. clocking cb @(posedge clk); default input #1step output #1; ...; endclocking; is the canonical declaration. Direction is from the TB's perspective. Outputs must use <= (non-blocking). Use default clocking cb; at the scope where properties / covergroups live so they inherit the same sampling event. One clocking block per clock domain; never reuse across domains. Async signals (resets, interrupts) live outside the clocking block.
This deep-dive completes Module 15.2. The companion introductory page at /learnings/systemverilog/clocking-blocks covers the basics. Next up: 15.3 — Input & Output Skews — the granular control over per-signal sampling and driving timing, including the #1step, fractional-cycle, and zero-skew use cases the deep-dive only sketched.