SystemVerilog · Module 17
Specify Blocks & Path Delays
SystemVerilog specify blocks — model real pin-to-pin propagation delays and timing checks for gate-level simulation. Simple/full/edge-sensitive path forms, specparam vs parameter, the system timing checks ($setup, $hold, $setuphold, $width, $period, $recovery, $removal), SDF back-annotation, and the full GLS flow with min/typ/max corners.
Module 17 · Page 17.4
RTL simulation is zero-delay: assign y = a & b; makes y change the instant a or b does. Real silicon does not work that way — every gate has a pin-to-pin propagation delay, and every flip-flop has setup/hold requirements. The specify block is how a Verilog/SystemVerilog model carries that timing: it declares path delays (how long a transition takes to travel from an input pin to an output pin) and timing checks ($setup, $hold, …) that flag violations during simulation. Its values are named with specparam, which a Standard Delay Format (SDF) file from place-and-route can override at runtime — and that mechanism is the backbone of gate-level simulation (GLS), where the post-layout netlist runs with the chip's real delays. This lesson covers the three path-delay forms, specparam, the full timing-check set, and the SDF/GLS flow.
1. Engineering Problem — Zero-Delay RTL Hides Real Timing
Functionally-correct RTL can still fail on silicon because of timing. A combinational cone that is too deep does not meet the clock period; a data path that arrives too close to the clock edge violates setup; a fast path that arrives too early violates hold. None of this is visible in zero-delay RTL simulation, where every signal settles instantly.
You need a model that carries real delays so the simulator can show the truth: y changing 45 ps after its input, a flip-flop's Q arriving 340 ps after the clock edge, a register sampling stale data because setup was missed. That model is the specify block. Cell-library vendors ship it inside every standard-cell model (AND2X1, DFFX1, …); the place-and-route tool measures the actual post-layout delays and emits them as SDF; the simulator back-annotates the SDF onto the cells and runs gate-level simulation with the chip's true timing. Setup/hold violations then surface as X on the output — the same X that becomes a real functional failure on silicon.
2. Mental Model — Timing Layered Over Zero-Delay Function
The picture every engineer carries:
A
specifyblock adds a timing skin over a module's zero-delay function. The logic (and (Y,A,B)orassign) still computes what the output is; thespecifyblock declares when it gets there — a pin-to-pin path delay for each arc — plus timing checks that watch the relationship between signals and fire on a violation. Delay values are named withspecparam, and unlike aparameter, aspecparamcan be overridden at simulation runtime by an SDF file without recompiling. That single property is the whole reasonspecifyexists: it lets one netlist run with min, typical, or max post-layout delays selected at the command line.
Four invariants this picture preserves:
specifyis module-scope and simulation-only. It sits besidealways/assign, never inside them, and synthesis ignores it entirely — it is a timing model, not logic.- Paths are pin-to-pin (port-to-port). A path delay connects module ports, never internal wires.
specparamis the SDF hook. Timing constants must bespecparam(notparameter) for SDF back-annotation to reach them.- Timing checks need real delays to mean anything.
$setup/$holdon a zero-delay netlist never fire; they earn their keep in GLS with SDF-annotated delays, where the static counterpart is STA (static timing analysis).
3. Visual Explanation — Pin-to-Pin Path Delays
The functional and still decides Y's value; the specify block puts a measured delay on each input-to-output arc.
The payoff is concrete: in RTL, assign Y = A & B makes Y change the moment B does; with the specify arc (B => Y) = (45, 40), Y's rise lands 45 time units after B rises. GLS adds exactly this realism so paths that miss the target frequency become visible.
4. The Three Path-Delay Forms
4.1 Simple (parallel) path — in => out
Connects sources to destinations 1-to-1 by position. The delay value can be 1, 2, 3, or 6 numbers depending on how much transition detail you model.
specify
(in_a => out_y) = 5; // one value: rise = fall = z
(in_a => out_y) = (10, 8); // two: (rise, fall) — most common
(in_a => out_y) = (10, 8, 9); // three: (rise, fall, z-transition)
(in_a => out_y) = (10, 8, 9, 7, 9, 7); // six: (01,10,0z,z1,1z,z0)
(in_a, in_b => out_y, out_z) = 10; // paired 1:1 — a→y, b→z
endspecify4.2 Full path — in *> out
The *> operator connects every source to every destination (a cross-product) — one line instead of one per input when several inputs share a delay to the same output.
module mux4 (input logic a, b, c, d, sel0, sel1, output logic y);
assign y = sel1 ? (sel0 ? d : c) : (sel0 ? b : a);
specify
(a, b, c, d *> y) = 12; // all data inputs → y, same 12-unit delay
(sel0, sel1 *> y) = 15; // select inputs → y, slightly longer
endspecify
endmodule4.3 Edge-sensitive path — for flip-flops
A clock edge triggers the output transition. (posedge clk => (q : d)) = delay reads "on posedge clk, output q takes the value of d after delay" — exactly the clk-to-Q propagation of a flop.
module dff (input logic clk, rst_n, d, output logic q, qn);
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) q <= 1'b0;
else q <= d;
assign qn = ~q;
specify
specparam tCQR = 7, // clk-to-q rising
tCQF = 6, // clk-to-q falling
tPCQ = 8; // async clear-to-q
(posedge clk => (q : d)) = (tCQR, tCQF); // clk-to-Q
(posedge clk => (qn : d)) = (tCQF, tCQR); // qn is the complement
(negedge rst_n => (q : 1'b0)) = tPCQ; // async reset clears q
$setup(d, posedge clk, 5); // timing checks — §6
$hold (posedge clk, d, 3);
endspecify
endmodule5. specparam — Timing Parameters the SDF Can Override
specparam is the timing-specific cousin of parameter: it names delay values so you write tRISE instead of a bare 10. The decisive difference — specparam values can be overridden at runtime by an SDF file without recompiling, which is how post-layout timing is injected into GLS.
specify
specparam tRISE = 10, tFALL = 8, tSETUP = 5, tHOLD = 3;
(in_a => out_y) = (tRISE, tFALL); // named delays in a path
$setup(d, posedge clk, tSETUP); // named delays in a check
$hold (posedge clk, d, tHOLD);
endspecifyparameter | specparam | |
|---|---|---|
| Purpose | Design structure — widths, depths | Timing — delays, setup/hold limits |
| Overridden by | #( ) at instantiation | SDF file at runtime (no recompile) |
| Scope | Module | Inside (or at module scope for) specify |
6. System Timing Checks
System timing checks are special system tasks inside a specify block that enforce temporal relationships between signals and report a violation during simulation. The canonical pair is setup (data stable before the clock edge) and hold (data stable after it); a violation models metastability and drives the output to X.
| Task | Checks | Syntax |
|---|---|---|
$setup | data stable before the reference edge by ≥ limit | $setup(data_event, ref_event, limit) |
$hold | data stable after the reference edge for ≥ limit | $hold(ref_event, data_event, limit) |
$setuphold | both, in one statement | $setuphold(ref_event, data_event, t_su, t_hold) |
$width | pulse stays high/low ≥ limit | $width(event, limit) |
$period | clock period ≥ limit | $period(ref_event, limit) |
$recovery | async-reset release ≥ limit before next clock edge | $recovery(ref_event, data_event, limit) |
$removal | async reset held ≥ limit after clock edge | $removal(ref_event, data_event, limit) |
$skew | edge-to-edge skew ≤ limit | $skew(ref_event, data_event, limit) |
specify
specparam tSETUP = 5, tHOLD = 3, tWIDTH = 10, tPERIOD = 20, tRECOV = 4, tREMOV = 2;
$setup(d, posedge clk, tSETUP); // d stable tSETUP before posedge clk
$hold (posedge clk, d, tHOLD); // d stable tHOLD after posedge clk
$setuphold(posedge clk, d, tSETUP, tHOLD);
$width (posedge clk, tWIDTH); // clk-high pulse width
$period(posedge clk, tPERIOD); // clk period
$recovery(posedge rst_n, posedge clk, tRECOV); // async-reset recovery
$removal (posedge rst_n, posedge clk, tREMOV); // async-reset removal
endspecify$ vcs -v2k -sdf max:top:top.sdf top_netlist.v tb_top.v -o sim_gls && ./sim_gls
SDF annotation complete (2847 instances, 12341 arcs)
SETUP VIOLATION on d in dff (top.u_core.u_reg_A)
At time 4250ns Setup time: 5ns Actual: 3ns → output q set to X (metastability)
Total timing violations: 3 → PASS (functional) / FAIL (timing)7. SDF Back-Annotation — The Gate-Level Simulation Flow
SDF (Standard Delay Format) is a text file emitted by place-and-route containing the actual post-layout delays for every cell and interconnect. Back-annotation loads it into the simulator, overriding the specparam values in the cell models with real numbers.
The flow is three steps: (1) synthesis + place-and-route map RTL to cells, route wires, and compute real delays → gate-level netlist + SDF; (2) the simulator loads the SDF and netlist, overriding every cell's specparam at the named hierarchy; (3) GLS runs with real timing, and setup/hold violations appear as X on outputs.
(DELAYFILE
(SDFVERSION "3.0") (DESIGN "top") (TIMESCALE 1ps)
(CELL
(CELLTYPE "DFFX1")
(INSTANCE top.u_core.u_reg_A)
(DELAY (ABSOLUTE
(IOPATH CLK Q (312:340:370) (298:325:355))) // (min:typ:max) rise / fall
)
(TIMINGCHECK
(SETUP D (posedge CLK) (85:95:110)) // min:typ:max
(HOLD (posedge CLK) D (40:45:52)))
)
)# VCS — back-annotate with the max corner
vcs -v2k -negdelay -timescale=1ns/1ps -sdf max:top:top.sdf \
top_netlist.v prim_lib.v tb_top.v -o sim_gls
./sim_gls +notimingchecks # functional GLS — path delays only, no setup/hold
./sim_gls # full GLS — includes timing-check violations
# Questa
vsim -sdfmax /top=top.sdf top
# Xcelium
xrun -v2k -negdelay -sdf_file top.sdf top_netlist.v prim_lib.v tb_top.v8. Full Working Example — Standard-Cell AND Gate with Timing
The shape of a real standard-cell model: zero-delay function plus a specify block of specparams and path delays.
// AND2X1 — the kind of model a cell library (TSMC, GF, …) ships
module AND2X1 (input logic A, B, output logic Y);
and (Y, A, B); // gate primitive — the zero-delay function
specify
// Named delays — overridden by SDF at runtime (ps in a real library)
specparam A_Y_RISE = 42, A_Y_FALL = 38,
B_Y_RISE = 45, B_Y_FALL = 40;
(A => Y) = (A_Y_RISE, A_Y_FALL); // pin-to-pin arcs
(B => Y) = (B_Y_RISE, B_Y_FALL);
// No timing checks — $setup/$hold apply only to sequential cells
endspecify
endmodule
module tb_and;
logic a, b, y;
AND2X1 u_and (.A(a), .B(b), .Y(y));
initial begin
a = 0; b = 0;
#10 a = 1; // Y stays 0 (b still 0)
#10 b = 1; // at t=20 both are 1, but Y not yet risen
#50 $display("t=%0t A=%b B=%b Y=%b", $time, a, b, y);
end
endmodulet=0 A=0 B=0 Y=0
t=10 A=1 B=0 Y=0 ← Y stays 0 (b still 0)
t=20 A=1 B=1 Y=0 ← delay not elapsed yet
t=65 A=1 B=1 Y=1 ← Y rises at t=20+45 (B→Y rise delay = 45)9. Industry Usage — Where Specify Blocks Live
- Standard-cell libraries. Every cell model (
AND2X1,DFFX1,MUX2X1, …) from TSMC, GlobalFoundries, Samsung, etc. ships with aspecifyblock; thespecparams are placeholders the SDF overwrites. - Gate-level simulation (GLS) sign-off. Post-layout netlist + SDF is simulated to confirm the design still functions and meets timing after place-and-route — catching X-propagation and reset/clock issues that pure STA can miss.
- The STA relationship. Static timing analysis is the static counterpart — it checks every path's setup/hold against constraints without simulation.
specifytiming checks are the dynamic counterpart, firing on the specific vectors GLS exercises. Real flows use both: STA for exhaustive coverage, GLS for X/reset/async behaviour. - ASIC bring-up.
$recovery/$removalchecks on async reset release are a frequent source of real silicon bugs, and GLS is where they first show up.
Specify blocks are simulation-only — synthesis ignores them. They model timing; they never become gates.
10. Debugging Guide — The Mistakes Everyone Makes Once
specify blocks — bugs every engineer hits the first time
Symptom: Syntax error at the specify keyword.
Cause: specify is a module-scope item, parallel to always/assign — it cannot live inside an always/initial block.
always @(posedge clk) begin specify ... endspecify end // ❌ illegalFix: move specify … endspecify to module scope, between the ports and endmodule, alongside the always/assign statements.
Guardrail: a specify block is a sibling of always, never a child.
Symptom: SDF back-annotation runs but the delays never change — GLS uses the hard-coded values.
Cause: SDF can only override specparam. A parameter (or a literal) in a path delay is invisible to SDF annotation.
specify parameter tRISE = 10; (a => y) = tRISE; endspecify // ❌ SDF can't reach itFix: use specparam for every timing constant; reserve parameter for structural values (widths, depths).
Guardrail: if a number is a delay or a setup/hold limit, it is a specparam.
Symptom: GLS passes clean, yet silicon has setup/hold failures.
Cause: +notimingchecks skips all $setup/$hold evaluation — you get path delays but no violation detection, defeating the purpose of full GLS.
Fix: run at least the critical tests without +notimingchecks. If false X-propagation floods the log, fix it with SDF scoping or X-pessimism options — not by disabling all checks.
Guardrail: +notimingchecks is for early bring-up only; sign-off GLS runs checks on.
Symptom: Violation reports that point at the wrong signal or never fire.
Cause: $setup(data, ref, limit) lists data first; $hold(ref, data, limit) lists the reference first. Swapping them inverts the check.
Fix: remember setup = data arrives before clock (data first); hold = clock already fired (ref first). Or use $setuphold(ref, data, t_su, t_hold) to avoid the trap.
Guardrail: when in doubt, $setuphold — one consistent argument order for both.
Symptom: Compile error — the path's signal "is not a port."
Cause: Specify-block paths are pin-to-pin (port-to-port) only; an internal wire cannot be a path endpoint.
(internal_wire => out_y) = 5; // ❌ internal_wire is not a module portFix: model the composite delay as a single port-to-port arc, or (rarely) promote the internal signal to a port if the timing must be exposed.
Guardrail: path endpoints are always module ports — specify describes the cell's external timing, not its internals.
11. Q & A
12. Cross-References & What's Next
This lesson covered specify blocks — the three path-delay forms, specparam, the system timing checks, and the SDF/GLS flow.
- Previous: 17.3 — Checkers — assertion-based verification;
specifyis its timing-model counterpart for gate-level sign-off. - Module index: Module 17 — Advanced Topics — the advanced-feature arc.
Related material elsewhere in the curriculum:
- Input & Output Skews — clocking-block skews are the testbench-side timing-margin control;
specifytiming checks are the design-model side. - Clocking Blocks — Deep Dive — sampling/driving timing in the testbench, the dynamic-verification companion to gate-level timing.
13. Quick Reference
specify
// ── specparam (SDF-overridable timing constants) ──────────────────
specparam tRISE = 10, tFALL = 8, tSU = 5, tHD = 3, tW = 20;
// ── Simple (parallel) path — 1-to-1 ───────────────────────────────
(in_a => out_y) = 10; // single delay
(in_a => out_y) = (tRISE, tFALL); // (rise, fall)
(in_a => out_y) = (10, 8, 9); // (rise, fall, z)
// ── Full path — all-to-all ────────────────────────────────────────
(in_a, in_b, in_c *> out_y) = 12;
// ── Edge-sensitive path (flip-flop) ───────────────────────────────
(posedge clk => (q : d)) = (tRISE, tFALL); // clk-to-Q
(negedge rst_n => (q : 1'b0)) = tFALL; // async reset-to-Q
// ── Timing checks (mind $setup vs $hold arg order) ────────────────
$setup (d, posedge clk, tSU);
$hold (posedge clk, d, tHD);
$setuphold(posedge clk, d, tSU, tHD);
$width (posedge clk, tW);
$period (posedge clk, 20);
$recovery (posedge rst_n, posedge clk, 4);
$removal (posedge rst_n, posedge clk, 2);
endspecify
// GLS: vcs -sdf max:top:top.sdf ... (max for setup, min for hold)
// specify is module-scope + simulation-only; synthesis ignores it.14. Summary
A specify block layers real timing over a module's zero-delay function: path delays declare how long each pin-to-pin arc takes, and timing checks ($setup, $hold, $setuphold, $width, $period, $recovery, $removal, $skew) flag temporal violations. Paths come in three forms — simple => (1-to-1), full *> (all-to-all), and edge-sensitive (posedge clk => (q : d)) for flops — with 1/2/3/6-value delay specifications.
Timing constants must be specparam, not parameter, because only specparam can be overridden by an SDF file at runtime — the mechanism that injects post-layout delays into gate-level simulation without recompiling. Run GLS at both corners (max for setup, min for hold), keep timing checks on for sign-off, and mind the mirror-image argument order of $setup and $hold. specify is module-scope and simulation-only — synthesis ignores it — and it is the dynamic complement to STA: STA proves every path statically, while specify/SDF/GLS exposes the X-propagation, reset, and async behaviour that only a real-delay simulation reveals.