SystemVerilog · Module 15
Input & Output Skews
SystemVerilog clocking block skews — the granular timing margins around each clock edge. Input skew controls exactly when the testbench samples DUT outputs; output skew controls when the testbench drives DUT inputs. #1step, #N, #0, and #-N — what each resolves to, when each is right, and the protocol-specific choices for APB / AXI / SPI / DDR.
Module 15 · Page 15.3
Skews are the timing margins built into a clocking block. The input skew controls exactly when the testbench samples a DUT output relative to the clock edge. The output skew controls when the testbench drives a DUT input — giving the DUT guaranteed setup time before its next sampling edge. Choosing the right skews eliminates every timing-related false failure in a synchronous testbench: TB reads pre-edge stale values, DUT reads mid-flight stimulus, cross-protocol setup-time violations. This page walks the four skew forms (#1step, #N, #0, #-N), the per-signal override mechanism, and the protocol-specific defaults that production VIPs use for APB, AXI, SPI, and DDR.
1. Engineering Problem — Why Skews Exist
In real silicon, every flip-flop has two timing requirements specified in its datasheet:
- Setup time — the signal must be stable for at least
T_setupnanoseconds before the clock edge. - Hold time — the signal must remain stable for at least
T_holdnanoseconds after the clock edge.
A testbench that drives a signal at exactly the clock edge gives the DUT zero setup time. Real silicon would fail this — STA would flag a setup violation; a fabricated chip would produce metastable outputs. Simulation may or may not catch the problem depending on which Active-region operation the scheduler evaluates first — the canonical TB-DUT race covered in program-block.
Clocking-block skews are the simulation equivalent of these silicon timing margins:
- The output skew says "drive the new value
Ntime units after the clock edge" — giving the DUT a full clock period (minusN) of setup time before its next sampling edge. - The input skew says "sample the DUT output
Mtime units before the clock edge" — capturing a stable post-NBA-settled value, not a mid-evaluation Active-region snapshot.
Together, the two skews shape every TB↔DUT signal exchange into the natural setup-before-edge, sample-before-edge pattern of real synchronous interfaces. Choosing the wrong skew (#0 for inputs, #0 for outputs, a skew larger than the clock period) reintroduces the very race the clocking block was meant to eliminate.
2. Mental Model — Skew = Margin From the Edge
The picture every engineer carries:
A skew is a labelled offset from the active clock edge. Positive skew (
#N,N > 0) means after the edge. Negative skew (#-N) means before the edge.#0means at the edge. The special form#1stepmeans one simulator time step before the edge — guaranteed to fall in the Postponed region of the previous time step, after every NBA has committed and before any current-edge Active activity starts. The four forms cover every realistic timing relationship between the testbench and the DUT.
Three invariants this picture preserves:
#1stepis the only choice that is structurally race-free for inputs. The other forms can race with DUT-side evaluations in the same Active region;#1stepcannot — it samples before any current-edge activity begins.- Output skew gives the DUT setup time. The DUT's next sampling edge is one full clock period after the current edge. If the TB drives at
#Nafter the current edge, the DUT hasperiod − Ntime units of setup before its next sample. Standard#1givesperiod − 1— adequate for any synchronous DUT. - Skew magnitude must be less than the clock period. Skew
#15on a 10 ns clock samples or drives in the next cycle — reading the wrong value or driving past a cycle boundary. The simulator typically accepts the declaration but produces semantic chaos.
3. Visual Explanation — Three Input Skews Compared
The waveform below puts the three most common input skew values side-by-side on the same DUT output transition. #1step reads the previous cycle's settled value (the safe default). #0 reads at the edge — in the Active region where the DUT's NBA may not have committed yet. #2 reads after the DUT's propagation delay has settled.
Figure — three input skews on the same DUT output transition
12 cyclesThe teaching: same DUT signal, same clock edge — three different captured values depending on the skew. Choosing the wrong skew means the testbench's notion of what the DUT said at the edge silently disagrees with reality.
4. Syntax & Semantics — #1step, #N, #0, #-N
4.1 Input skew — declaration forms
clocking cb @(posedge clk);
default input #1step; // all inputs use #1step unless overridden
input sig_a; // uses default: #1step (safe pre-edge)
input #2 sig_b; // per-signal: 2 time units AFTER edge
input #-1 sig_c; // per-signal: 1 time unit BEFORE edge
input #0 sig_d; // per-signal: AT the edge (use with care)
endclocking4.2 Output skew — declaration forms
clocking cb @(posedge clk);
default output #1; // all outputs driven 1 time unit AFTER edge
output sig_a; // uses default: #1
output #3 sig_b; // per-signal: driven 3 time units AFTER edge
output #0 sig_c; // driven AT the edge (Active region — risky)
output #-2 sig_d; // driven 2 time units BEFORE the edge — pre-edge setup
endclocking4.3 Combined defaults — the canonical declaration
clocking cb @(posedge pclk);
default input #1step // sample just before posedge — race-free
output #1; // drive 1 time unit after posedge — gives DUT (period − 1) setup
input pready, pslverr, prdata; // DUT outputs the TB samples
output psel, penable, pwrite, paddr, pwdata; // DUT inputs the TB drives
endclocking#1step for inputs, #1 for outputs — the recommended default for any synchronous interface. Per-signal overrides only when a specific signal genuinely needs different timing (DUT outputs with known propagation delay, fast paths needing maximum setup).
4.4 The four skew forms in one reference table
| Skew value | When event occurs | Use for input | Use for output |
|---|---|---|---|
#1step | Postponed region of the time step just before the clock edge | Default — safest input sampling, race-free | Not applicable for outputs |
#0 | Exactly at the clock edge (Active region) | Combinational DUT outputs valid at the edge | Immediate drive — avoids skew but may race |
#N (N > 0) | N time units after the clock edge | DUT outputs with known propagation delay N | Standard — gives DUT (period − N) setup |
#-N (N > 0) | N time units before the clock edge | Sample the previous cycle's stable value | Drive N units before edge — maximum DUT setup |
Hard constraint: the skew magnitude must be less than the clock period. A #15 skew on a 10 ns clock crosses into the previous or next cycle, producing semantically incorrect sampling/driving. Most simulators accept the declaration but the behaviour is undefined — and is a frequent source of regression noise.
5. Simulation View — What Each Skew Resolves To
The simulator divides time into discrete time steps. Within each step there are ordered scheduling regions: Preponed → Active → Inactive → NBA → Observed → Reactive → Postponed. Skews map onto specific positions in this region sequence:
#1stepschedules the sample in the Postponed region of the time step before the clock edge. At this point, every non-blocking assignment from the previous edge has committed; no current-edge Active or NBA activity has started. The most stable, deterministic, race-free sampling point available.#0schedules the event in the Active region of the current time step — the same region the DUT's<=schedules its NBA updates to commit. Whether the TB sees the pre-NBA value or the post-NBA value depends on simulator evaluation order. Race.#N(positive) schedules the eventNtime units in absolute time after the clock edge. For an input, this means sampling after the DUT's combinational propagation has settled (use for DUT outputs with known delay). For an output, this means driving withperiod − Nof setup before the DUT's next sampling edge.#-N(negative) schedules the eventNtime units in absolute time before the clock edge. For an input, this samples in the previous cycle's stable window (capture a value that was already settled). For an output, this drives the signal before the upcoming edge — giving the DUT maximum setup time, useful when the protocol requires stable inputs well before the edge.
Why #1step beats #0: at #0, the simulator hasn't finished evaluating the Active region. The DUT's q <= d assignment may still be in the NBA queue, not committed to q yet. #1step is guaranteed to fall after every previous-cycle NBA commit and before any current-cycle Active activity. There is no simulator implementation where #1step races and #0 doesn't; in many it's the reverse.
6. Waveform — Both Skews Working Together in One Cycle
In a complete read-modify-write cycle, both skews are active simultaneously. The TB reads a DUT output via input skew, then drives a response via output skew — within the same clock cycle, both operations race-free.
Figure — both skews active in one cycle: read at #1step (before edge), drive at #1 (after edge)
12 cyclesThe asymmetry is the entire teaching: inputs look backward (read settled values from the previous cycle's stable window), outputs look forward (drive values that the DUT will sample at its next edge). The clocking block encodes both disciplines in one declaration.
7. Synthesis — Not Applicable
Clocking block skews are a procedural simulation construct. They have no hardware footprint and no place in synthesisable RTL — synthesis tools reject the clocking keyword entirely. This section is intentionally omitted; the topic does not warrant it.
The corresponding hardware-side discipline is the STA-verified setup/hold margin on each signal at each flop. The skew declarations are the verification-side counterpart: they model what the hardware naturally has via timing-arc analysis, ensuring the testbench gives the DUT the same temporal isolation that real silicon does.
8. Verification View — Protocol-Specific Skew Choices
Different protocols have different timing requirements. The clocking block's skew defaults are the right answer for the standard synchronous case; per-signal overrides handle the protocol-specific exceptions. The most common production patterns:
8.1 APB (AMBA Peripheral Bus) — defaults are correct
// 10 ns clock period. Signals registered on posedge.
// Standard synchronous interface — defaults work cleanly.
clocking apb_cb @(posedge pclk);
default input #1step
output #1; // 9 ns DUT setup — adequate
input pready, pslverr, prdata;
output psel, penable, pwrite, paddr, pwdata;
endclockingAPB is the simplest case: one clock, all signals registered, no fast combinational paths. #1step input + #1 output is correct out of the box.
8.2 AXI4 — per-signal override for the read-data path
// 5 ns clock period. DUT logic has propagation delays on some paths.
// Sample rdata 2 ns after edge — captures the post-propagation value.
clocking axi_cb @(posedge aclk);
default input #1step
output #1;
input awready, wready, bvalid, bresp;
input #2 rdata; // rdata path: 2 ns DUT propagation
output awvalid, awaddr, wvalid, wdata, wlast, bready;
output #0 arvalid; // read address: drive immediately
endclockingTwo per-signal overrides: input #2 rdata (sample after the DUT's combinational propagation has settled), output #0 arvalid (drive immediately at the edge to maximise setup time for the slave's read-address sampling). Every other signal uses the defaults.
8.3 SPI — opposite edges for drive vs sample
interface spi_if (input sclk);
logic mosi, miso, cs_n;
// Master drives MOSI on negedge so the slave's posedge sample is stable
clocking master_cb @(negedge sclk);
default input #1step output #1;
output mosi; // driven after negedge
input miso; // sampled just before negedge
endclocking
// Monitor captures what the slave sees — on posedge
clocking monitor_cb @(posedge sclk);
default input #1step;
input mosi, miso; // capture both lines at the slave's sample point
endclocking
endinterfaceSPI Mode 0: master shifts data out on negedge sclk; slave samples on posedge sclk. Two clocking blocks — one per edge — each with its own skew discipline. The interface bundles the signals; the clocking blocks bundle the per-edge timing contracts.
8.4 Fast designs — tight setup-time budget
// 4 ns clock period. DUT needs 1 ns setup minimum.
// TB must drive no later than 3 ns after posedge.
clocking fast_cb @(posedge clk); // 4 ns period
default input #1step
output #1; // 3 ns DUT setup — within spec
endclocking
// If you used output #2: only 2 ns DUT setup — marginal
// If you used output #3: only 1 ns DUT setup — at the limit; STA would flag thisFor fast clocks, the skew choice directly bounds the verification's representation of DUT setup time. A protocol that specs T_setup ≥ 1 ns on a 4 ns clock leaves the TB room to use output #3, but any reasonable VIP picks #1 to stay well clear of the boundary.
8.5 Per-signal overrides in mixed-timing interfaces
interface mixed_if (input clk);
logic reg_out; // registered DUT output
logic comb_out; // combinational DUT output — valid at the edge
logic slow_out; // slow DUT path — settles 3 ns after edge
logic fast_in; // input the DUT needs early — drive before edge
clocking cb @(posedge clk);
default input #1step output #1;
input reg_out; // default: #1step (registered path)
input #0 comb_out; // combinational: stable at edge
input #3 slow_out; // slow path: wait 3 ns for propagation
output #-3 fast_in; // drive 3 ns BEFORE edge for max setup
endclocking
endinterfaceThe pattern: declare the safe defaults at the top, override per-signal only when a specific path genuinely needs different timing. Each override is a documented timing-relationship statement; defaults are the contract for the rest of the interface.
9. Industry Usage — Where Skew Choices Land in Real Verification
- Every shipping APB / AHB / AXI / DDR VIP ships with a clocking block per channel using
#1stepinput +#1output as defaults. Per-signal overrides appear on signals with known propagation delays (memory-read paths, ECC-decode paths). The defaults are the industry standard; anything else is documented protocol-specific deviation. - SPI / I²C / UART VIPs use multiple clocking blocks per interface — one per active edge (e.g.,
negedgefor the master driver,posedgefor the slave sampler). The skew per clocking block is#1step/#1— the multi-clocking-block construct is what handles the multi-edge timing, not exotic skew values. - DDR controllers use clocking blocks anchored to both the rising and falling edges (DDR2/3/4 are dual-edge). Each clocking block has its own skew; the verification environment treats the two edges as independent timing domains.
- High-speed serial interfaces (PCIe Gen3+, USB SuperSpeed, Ethernet 10G) that approach the simulator's time-resolution limits use
#1stepexclusively for inputs and#1for outputs — even small positive skews can exceed the per-cycle budget at multi-gigahertz rates. - CDC verification environments use one clocking block per domain (per /learnings/systemverilog/clocking-blocks-deep-dive §8.4). Each domain's skews are independent; the destination domain's input skew is the sampling reference for the two-flop synchroniser.
- Sign-off CDC formal flows (JasperGold CDC, Conformal LP) consume the clocking-block skew declarations as the verification's timing reference. Skews that exceed the clock period or use non-standard values are flagged as setup/hold-relationship violations.
- Vendor lint rules (Spyglass, Synopsys VC SpyGlass CDC, Cadence Conformal CDC) check that every clocking block has explicit default skews declared. Implicit defaults (the LRM-permitted "if no default is given, signals are sampled at
#0") are flagged as a discipline violation.
10. Design Review Notes — What a Senior Will Flag
| Pattern in the diff | What review will say |
|---|---|
clocking cb @(posedge clk); input data; endclocking (no default skew declaration) | "Implicit skew defaults to #0 per LRM — racy. Always declare default input #1step output #1; explicitly. Every shop's lint rule flags missing defaults." |
input #0 q; on a registered DUT output | "Samples at the edge in Active region — NBA may not have committed yet. Use #1step for registered outputs; #0 is only valid for combinational outputs valid at the edge." |
input #15 data; on a 10 ns clock | "Skew magnitude (15) exceeds clock period (10) — samples into the next cycle, reading the wrong value. Skew must be less than period." |
output #0 req; on a synchronous interface | "Drives at the edge in Active region — races with DUT-side evaluations. Use #1 for period − 1 setup; use #-N if the protocol genuinely needs pre-edge driving." |
Per-signal skews on every signal with no default declaration | "Declare the default once at the top; per-signal overrides only when truly needed. Each per-signal skew is one more thing for the next engineer to verify." |
| Async signals (resets, interrupts) listed in the clocking block with a skew | "Async signals belong outside the clocking block. Attaching a clock skew to an asynchronous signal models a timing relationship that doesn't exist in hardware." |
input #-5 prev_data; on a 10 ns clock with no comment | "Negative skew reads from the previous cycle's stable window — uncommon enough to need a comment explaining why. Most readers will assume it's a bug." |
| Two clocking blocks in the same interface with different skews for the same signal | "Two clocking blocks for the same signal is fine (master vs slave perspective), but different skews on the same signal across clocking blocks is suspicious — they should agree on when the signal is sampled / driven. Verify the protocol requires the difference; if not, harmonise." |
default output #-1; (negative default for outputs) | "Drives before the edge by default — non-standard. Most readers expect output #N (positive) for the standard post-edge drive. Negative as default is a discipline outlier; should be output #1 with per-signal #-N overrides only where needed." |
| Skew with no time unit on a multi-time-precision design | "#2 is ambiguous if the surrounding scope has multiple timeunit/timeprecision declarations. Use explicit units (#2ns, #100ps) when the time-precision scope is non-trivial." |
The single highest-value rule: default input #1step output #1; is the canonical declaration. Any deviation should be documented inline with a one-line comment explaining the timing reason. The defaults are the contract; per-signal overrides are the documented exceptions.
11. Debugging Guide — Real Failures, Real Fixes
TB sees pre-NBA q value despite using a clocking block
INPUT-#0-RACEclocking cb @(posedge clk);
default input #0; // BUG: samples at edge
input q;
endclocking
// TB reads bus.cb.q at the posedge → in Active region
// → may capture pre-NBA value depending on simulator evaluation orderq is registered in the DUT via q <= d; and the TB reads q immediately after the edge but sees the pre-edge value about 30 % of the time. Looks like a DUT bug; isn't.input #0 samples in the Active region — the same region where the DUT's q <= d schedules its NBA update. Whether the NBA commits before or after the TB's read depends on simulator evaluation order. This is exactly the Active-region race the clocking block was meant to fix.default input #1step;. #1step samples in the Postponed region of the previous time step — guaranteed before any current-edge Active or NBA activity. The race is structurally eliminated. Reserve input #0 for combinational DUT outputs that are valid at the edge (rare and always commented).DUT samples wrong stimulus value mid-cycle
OUTPUT-#0-DRIVE-RACEclocking cb @(posedge clk);
default output #0; // BUG: drives at edge
output psel, paddr;
endclocking
bus.cb.psel <= 1;
bus.cb.paddr <= 32'h1000;
// DUT may sample psel=0 (old) or psel=1 (new) at this same edgepsel=1 but paddr is still the previous value. Same TB code, same DUT, simulator-dependent reproducibility. Coverage shows transactions completing at unexpected addresses.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? Simulator-dependent.default output #1;. The DUT sees the new values one time unit after the edge — with period − 1 of setup time before its next sampling edge. Race eliminated. output #0 is only appropriate for combinational signals that have no setup-time requirement, which is rare.TB reads next-cycle data instead of current-cycle data
POSITIVE-INPUT-SKEW-TOO-LARGE// 10 ns clock period
clocking cb @(posedge clk);
default input #1step;
input #15 data; // BUG: 15 > period (10)
endclockingdata value lags one cycle behind what the DUT produced. The TB log shows transactions completing at the wrong timing relationships; assertions fire unexpected data sequence. Looks like a DUT pipeline bug.#1step to #9 (post-edge sampling) or #-9 to #-1 (pre-edge sampling from the previous cycle). If the DUT's propagation is slower than the clock period, the design is broken at the timing level — not solvable in the clocking block.TB drives the wrong direction; bus contention on every cycle
WRONG-DIRECTIONclocking cb @(posedge clk);
default input #1step output #1;
output ack; // BUG: ack is driven by the DUT
output ready; // BUG: ready is driven by the DUT
endclockingack". Waveform shows ack and ready as X across every cycle. Test fails to complete a single transaction. Looks like a DUT-output bug; it's actually a TB-direction bug.output means "TB drives this signal." ack and ready are driven by the DUT — they should be input (TB reads what the DUT produces). The TB now tries to drive signals the DUT is also driving — multi-driver conflict shows as X.input ack; and input ready;. The clocking block direction is opposite to the DUT's port direction: a signal declared output in the DUT's port list is input in the testbench's clocking block. Always ask "does the TB read this (input) or drive this (output)?"`input #1step` reads stale value; engineer expected post-edge value
SKEW-DIRECTION-MISUNDERSTOODclocking cb @(posedge clk);
default input #1step;
input data_out;
endclocking
// At posedge N:
@(bus.cb);
x = bus.cb.data_out;
// Engineer expected x = value computed by DUT AT posedge N
// Actually got x = value computed by DUT AT posedge N-1 (one cycle stale)x against the expected DUT output for cycle N and consistently fails. The DUT computed the right value; the TB just captured it one cycle late. Looks like a DUT-functional bug; it's a TB-sampling-time misunderstanding.input #1step samples before the current edge — capturing the value set up during cycle N−1. If the engineer wanted the value the DUT computed at edge N (after its NBA commits), they need a positive input skew that lets the DUT's propagation settle: e.g., input #2 if the DUT takes 2 ns to propagate.#1step. For "the value the DUT computed at this edge (post-NBA-commit)": input #N where N exceeds the DUT's propagation delay. Most testbenches actually want #1step — the value the DUT held going into the edge — which matches how SVA and coverage also sample.12. Interview Insights — What Interviewers Actually Probe
Default input skew: #1step — samples one simulation time step before the clock edge, in the Postponed region of the previous time step. At that point, all NBA assignments from the previous edge have committed; no current-edge Active or NBA activity has started. Reading there is guaranteed race-free against any current-edge DUT evaluation.
Default output skew: #1 — drives the signal one time unit after the clock edge. The DUT sees the new value with (period − 1) setup time before its next sampling edge — for a 10 ns clock, 9 ns of setup, plenty for any synchronous DUT. #0 would drive at the edge and race; negative skews are only needed for protocols requiring stable pre-edge stimulus.
13. Exercises
1. Design — skew choices for a DDR controller (Foundation)
Write a clocking block for a DDR2 controller interface where data is sampled on both posedge and negedge of dqs_clk. Use two clocking blocks (one per edge). Pick appropriate default input and default output skews for each. Explain why DDR specifically needs dual-edge clocking blocks.
2. Debug — the silent skew race (Intermediate)
A teammate's APB testbench uses clocking cb @(posedge pclk); default input #0; ... endclocking. The test passes on VCS, fails on Questa with assertion pready_check firing inconsistently. Identify the bug, predict the race mechanism in scheduler terms, and write the one-line fix.
3. Code review — the cross-period skew (Intermediate)
A teammate submits clocking cb @(posedge clk); where clk has a 10 ns period and the block declares input #12 slow_data;. Identify the bug, predict the symptom (what value will the TB capture and why), and write the fix considering the design's true timing requirements.
4. Trade-off — output #1 vs output #-1 (Advanced)
A junior teammate proposes a coding rule: "always use output #-1 instead of output #1 — drive before the edge to give the DUT maximum setup time." Argue the case against the blanket rule. Consider what assumptions break (the prior cycle's stable window), what protocols this would violate (anything that doesn't have stable inputs the cycle before the edge), and when negative output skew is actually the right choice.
14. Summary
Skews are the per-signal timing margins in a clocking block. Input skew controls when the TB samples DUT outputs (#1step for safe pre-edge sampling, #N for post-propagation sampling, #-N for previous-cycle stable-window sampling). Output skew controls when the TB drives DUT inputs (#1 standard post-edge drive, #0 immediate drive — risky, #-N for protocols needing pre-edge stimulus).
Defaults to memorise. default input #1step output #1; is the canonical declaration for any synchronous interface. #1step is the only structurally race-free input skew. Skew magnitude must be less than the clock period. Direction in a clocking block is from the testbench's perspective — opposite the DUT's port direction. Per-signal overrides only where the timing genuinely differs from the default.
Next up: 15.4 — Program Block Limitations & Best Practices — the constraints, restrictions, the modern module + clocking block alternative, and why UVM moved past program blocks entirely. Closes Module 15 with the operational discipline the deep-dive lessons established the theoretical foundation for.