GLS · Chapter 8 · Timing-Violation Debug
Timing Checks, Notifiers & X-Injection
This lesson connects timing violations to unknown-propagation through the notifier mechanism. A cell's specify block holds timing checks for setup, hold, recovery, removal, and width, each with a notifier register that also feeds the cell's primitive. When a check fires, it toggles the notifier, and the primitive drives the cell output to an unknown. That injected unknown then propagates through downstream logic like any other, so a single timing violation can become a wide propagation problem, sometimes flooding the design from one small violation. This lesson traces the path from check to notifier to unknown to propagation, and shows how to manage the flood without masking real violations by disabling checks wholesale. Static timing analysis remains the arbiter of real timing throughout.
Foundation12 min readGLSNotifierX-InjectionTiming ChecksX-Propagation
Chapter 8 · Section 8.5 · Timing-Violation Debug
Project thread — a $setup/$hold firing on a counter/FSM flop injects X that flows to outputs (as in 6.6). This lesson is the bridge between a timing violation and the X-propagation it causes; 8.6 sees it on a clock-gated block.
1. Why Should I Learn This?
A timing violation rarely stays put — via the notifier it becomes an X that spreads.
- Check → notifier → UDP →
Xis the injection path (2.5); the windows themselves are setup & hold fundamentals. - The injected
Xpropagates (Chapter 6) — one violation can flood downstream. - Managing the flood without disabling real checks is the skill — see real vs false violations and controlling X propagation.
This unifies timing-violation debug (Ch8) with X-propagation (Ch6).
2. Real Silicon Story — one violation, a screen full of X
A single $hold violation on one flop produced a screen full of X downstream — dozens of signals unknown. The team assumed dozens of bugs.
There was one bug. The hold violation injected X on that flop (notifier), and the X propagated through the downstream logic (Chapter 6) — fanning out to everything the flop fed. Fixing the single hold race (add delay, 8.3) cleared the entire flood.
Lesson: a timing violation becomes an X-propagation problem — one injected X can flood the design. Trace the flood back to the single injecting violation; don't count the symptoms as separate bugs.
3. Concept — X-injection and its propagation
The injection path (recap 2.5):
- A cell's
specifyblock has timing checks ($setup/$hold/$recovery/$removal/$width), each with a notifierreg. - On a violation, the check fires → toggles the notifier.
- The notifier is a UDP input, so the toggle drives the cell output to
X— X-injection.
The propagation (Chapter 6):
- The injected
Xflows downstream by the X-flow rules (6.3): muxes propagate on unknown selects, flops captureX, controlling values absorb it. - One injected
Xcan fan out widely — a flood from a single violation.
Managing X-injection (without masking real violations):
- Trace the flood to its source — the single injecting violation (don't count symptoms).
- Fix the injecting violation (real → design — setup / hold; artifact → run — real vs false) — clears the whole flood.
- Selective control exists (per-instance notifier/check control,
+notimingchecks) but is a blunt tool: disabling checks hides real violations (8.4). Use it narrowly and justified, never wholesale.
Framing (accuracy):
- The notifier
Xis a modelling flag (Chapter 6) — investigate the source. - STA is the arbiter of real timing; managing
X-injection changes simulation noise, not the underlying timing (0.3). Check limits and delays arrive from SDF at the signoff corner.
4. Mental Model — one leak floods the whole floor
A timing violation is a single pipe leak (the injected X); propagation is the water spreading across the floor.
- One leak (notifier
X) can soak an entire room (downstream fan-out) — it looks like many problems. - You don't mop every puddle as a separate issue — you find the one leak and fix it (the injecting violation).
- Turning off the water main (disabling all checks) stops the flood and hides the next real leak (8.4) — too blunt.
Trace the water back to the one leak, fix that, and the floor dries.
5. Working Example — one violation, a propagating flood
The injection and its spread (representative):
// X-injection at the source flop — REPRESENTATIVE (2.5)
// NOTE the non-zero limit. A `$hold(..., 0, notify)` can never fire: no
// transition can fall inside a zero-width window, so a model written that way
// is silently check-free. Real limits arrive by SDF back-annotation.
$hold (posedge CK, D, 0.05, notify); // hold violation -> notifier toggles -> q = X (injected)
// q feeds a fan-out cone:
assign a = q & sel; // controlling? if sel=0, X absorbed; if sel=1, X propagates (6.3)
assign b = q ^ c; // XOR: no controlling value -> X propagates (6.3)
assign out = {a, b, ...}; // one q=X floods multiple outputs# The flood, traced back (tool-neutral):
# symptom: out[7:0] mostly X (looks like many bugs)
# trace back via X-flow (6.3): out <- a,b <- q (single X source)
# root: ONE $hold violation injecting X on q
# fix: fix that hold race (8.3) -> whole flood clears (do NOT disable checks wholesale, 8.4)Practical context (representative, tool-neutral):
# Manage X-injection (tool-neutral):
# 1) a flood of X? trace back via X-flow (6.3) to the ONE injecting violation
# 2) classify it (8.4): real (design fix, 8.2/8.3) or artifact (run fix)
# 3) fix the SOURCE -> the whole flood clears
# 4) selective check/notifier control is a BLUNT tool -- narrow + justified only; never wholesale
# 5) STA remains the arbiter of real timing (0.3)One injected X flooding downstream, as a real waveform:
One hold violation injects X on a flop; the X propagates and floods downstream outputs
8 cyclesThe wiring that actually injects the X
"The notifier is a UDP input, so the toggle drives the output to X" is the sentence everyone repeats. It is worth seeing the three places that sentence lives in real source, because every one of them is the library's choice, not the language's — and that is precisely what makes an X flood diagnosable rather than mysterious.
// ── 1. The notifier is an ordinary reg. Nothing special about it. ──────────
module DFFX1 (output Q, input D, CK);
reg notify;
// ── 2. It is passed to the UDP as an INPUT, alongside the real pins. ────
udp_dff_n u0 (Q, CK, D, notify);
specify
// ── 3. Each check names the notifier as its last argument. On a
// violation the simulator TOGGLES that reg. That is the entire
// language-defined behaviour — IEEE 1364/1800 say nothing about X.
$setup (D, posedge CK, 0.10, notify);
$hold (posedge CK, D, 0.05, notify);
$width (posedge CK, 0.20, 0, notify);
endspecify
endmodule
// ── And here is where the X actually comes from: one row of the UDP table.
primitive udp_dff_n (q, ck, d, notifier);
output q; reg q;
input ck, d, notifier;
table
// ck d notifier : q : q+
(01) 0 ? : ? : 0 ;
(01) 1 ? : ? : 1 ;
// ... normal capture rows ...
? ? * : ? : x ; // ← ANY change on notifier ⇒ output X
endtable
endprimitiveThat final table row is the whole of X-injection. Delete it and every check still fires, still prints, and Q stays clean.
Run it — one violation, a flood, and the flood clearing
"Try Yourself" below describes this experiment; here it is as a file. It builds the exact fan-out cone from §5 and reports how wide the flood is, with the violation present and with it removed.
// ─────────────────────────────────────────────────────────────────────────────
// One flop with timing checks feeds an XOR and a mux. A hold violation injects
// X on q; the X then propagates into both cones. The test counts how many
// downstream signals are X, first with the violation and then without it.
//
// iverilog -o xflood x_injection_flood.v && ./xflood
// (Run WITHOUT +notimingchecks — that switch removes the very check under test.)
// ─────────────────────────────────────────────────────────────────────────────
`timescale 1ns/1ps
primitive udp_dff_n (q, ck, d, notifier);
output q; reg q;
input ck, d, notifier;
table
(01) 0 ? : ? : 0 ;
(01) 1 ? : ? : 1 ;
(0?) 1 ? : 1 : 1 ;
(0?) 0 ? : 0 : 0 ;
(?0) ? ? : ? : - ;
(1x) ? ? : ? : - ;
? (??) ? : ? : - ;
? ? * : ? : x ; // the injection row
endtable
endprimitive
module DFFX1 (output Q, input D, CK);
reg notify;
udp_dff_n u0 (Q, CK, D, notify);
specify
(CK => Q) = 0.10;
$hold (posedge CK, D, 0.05, notify); // 50 ps hold window
endspecify
endmodule
module tb;
reg CK = 1'b0, D = 1'b0, sel = 1'b1, c = 1'b0;
wire q, a, b;
integer flood_with = 0, flood_without = 0;
reg violate = 1'b1;
DFFX1 dut (.Q(q), .D(D), .CK(CK));
assign a = q & sel; // sel=1 -> non-controlling -> X passes through
assign b = q ^ c; // XOR -> never controlling -> X always passes
always #1 CK = ~CK; // 2 ns period
// Count downstream signals that are X on each settled half-cycle.
always @(negedge CK) begin
integer n; n = 0;
if (a === 1'bx) n = n + 1;
if (b === 1'bx) n = n + 1;
if (violate) flood_with = flood_with + n;
else flood_without = flood_without + n;
end
initial begin
// ── Phase 1: move D 20 ps AFTER the capture edge — inside the hold window
repeat (4) begin
@(posedge CK);
#0.020 D = ~D;
end
#2;
// ── Phase 2: the SAME stimulus, moved clear of the window. One change.
violate = 1'b0;
repeat (4) begin
@(negedge CK) D = ~D;
@(posedge CK);
end
#2;
$display(" downstream X observed, violation present = %0d", flood_with);
$display(" downstream X observed, violation removed = %0d", flood_without);
if (flood_with > 0 && flood_without == 0)
$display("\n [PASS] the entire flood had ONE source - fixing it cleared every symptom");
else
$display("\n [FAIL] unexpected - check the UDP notifier wiring");
$finish;
end
endmodule ** Timing violation in tb.dut
$hold( posedge CK:3 ns, D:3.02 ns, 0.05 ns );
...
downstream X observed, violation present = 8
downstream X observed, violation removed = 0
[PASS] the entire flood had ONE source - fixing it cleared every symptomWhat the two counters prove. Nothing downstream was changed between the phases — a and b have identical logic, sel and c never move. The only edit was when D transitions, and it took the downstream X count from eight to zero. That is the chapter's thesis made measurable: the symptoms were never independent, so counting them was never going to converge on a bug count. Fixing one source cleared all of them.
Two variations are worth running. Set sel = 0 and watch a stop showing X while b still does — the controlling-value absorption from the X-flow rules, isolated. And re-run with +notimingchecks: the violation disappears, the flood disappears, and the design is exactly as broken as before, which is the strongest possible argument for §8's rule about never disabling checks wholesale.
6. Debugging Session — a screenful of X from one violation
A single timing violation injects X that propagates and floods dozens of downstream signals, looking like many bugs; tracing the flood back through the X-flow rules reaches the one injecting violation, and fixing it clears the whole flood without disabling checks
ONE INJECTED X FLOODS DOWNSTREAM — TRACE TO THE SOURCEOne timing check fires, and dozens of downstream signals go X. It looks like many separate failures.
One X-injection, propagated. The timing violation fired a check, which toggled the notifier, which drove the cell output to X (2.5) — X-injection. That single X then propagated through the downstream fan-out by the X-flow rules (6.3): non-controlling gates (XOR) and unknown-select muxes pass it, flops capture it — so one injected X floods everything the flop feeds. The dozens of Xs are symptoms of a single violation, not dozens of bugs. Counting them separately (or, worse, disabling checks to silence them, 8.4) misreads a propagation flood as many faults.
Trace the flood back through the X-flow (6.3) to the single injecting violation, then classify and fix that source (8.4): if real (long path / hold race / skew), fix the design (8.2/8.3); if artifact (edge-aligned stimulus, reset-active check, wrong corner), fix the run. Fixing the one source clears the whole flood. Only if the modelling X-pessimism is genuinely excessive should you use narrow, justified check/notifier control — never disable checks wholesale (it hides real violations, 8.4). The lesson: a timing violation injects X via the notifier, and that X propagates like any unknown (Chapter 6) — so one violation can flood the design; trace the flood back to the single injecting violation and fix it, rather than counting symptoms or disabling checks. (The notifier X is a modelling flag; STA is the arbiter of real timing, 2.5/0.3.)
7. Common Mistakes
- Counting propagated
Xs as separate bugs. One injection can flood many signals. - Disabling checks to silence the flood. Hides the real injecting violation (8.4).
- Not tracing the
Xback through the X-flow rules (6.3) to the source. - Fixing symptoms downstream instead of the injecting violation.
- Forgetting the
Xis a modelling flag. Investigate the source (Chapter 6).
8. Industry Best Practices
- Trace an
Xflood back to the single injecting violation (6.3). - Fix the source (real → design, artifact → run, 8.2–8.4) — the flood clears.
- Use check/notifier control narrowly and justified — never wholesale (8.4).
- Treat the notifier
Xas a flag — investigate, don't waive or mask. - Keep STA as the arbiter of real timing.
Senior Engineer Thinking
- Beginner: "Dozens of
Xs — dozens of timing bugs." - Senior: "Or one violation injecting
Xthat propagated. Let me trace the flood back through the X-flow to the source flop — fixing that one violation should clear all of it. I won't disable checks."
The senior reads an X flood as one injected X propagating and fixes the source.
Silicon Impact
Understanding X-injection prevents two failures. First, misattribution: a screenful of X from one violation looks like a catastrophe, and teams may over-react (or, worse, disable checks to quiet it, 8.4 — masking the real source that then ships, 0.3). Second, inefficiency: chasing dozens of propagated symptoms instead of the one source wastes days. Recognising that a timing violation becomes an X-propagation problem — trace the flood back to the single injecting violation, fix it — turns an overwhelming X flood into a single, targeted fix. And keeping checks on (managing injection narrowly, deferring to STA) ensures the next real violation still fires. This is the bridge that makes Ch6 and Ch8 one skill.
Engineering Checklist
- Traced the
Xflood back to the single injecting violation (6.3). - Classified the source (real vs artifact, 8.4).
- Fixed the source (design or run) — confirmed the flood cleared.
- Used check/notifier control narrowly (never wholesale).
- Kept the notifier
Xas a flag; deferred real-timing signoff to STA.
Try Yourself
- Create a
$holdviolation that injectsXon a flop feeding a fan-out cone (anXORand an unknown-select mux). - Observe: the single
Xfloods multiple downstream signals (X-injection + propagation, 6.3). - Change: fix the one hold race (add delay, 8.3).
- Expect: the entire flood clears from one fix — proving it was one injected
X, not many bugs. Confirm you never had to disable a check.
Any free Verilog simulator with SDF timing checks reproduces X-injection and its propagation. No paid tool required.
Interview Perspective
- Weak: "A timing violation just prints a message."
- Good: "A timing check fires, toggles the notifier, and injects
Xon the output, which then propagates downstream." - Senior: "A timing violation becomes an X-propagation problem: the notifier injects
X, and it floods the fan-out by the X-flow rules. So a screenful ofXis often one violation — I trace it back to the single injecting source and fix that (real → design, artifact → run), never disabling checks wholesale (which would mask real ones). TheXis a flag; STA is the arbiter."
9. Interview / Review Questions
Where This Is Specified
Timing checks and the notifier argument are defined in IEEE Std 1364 (Verilog) clause 15 — Timing checks and carried into IEEE Std 1800 (SystemVerilog) clause 31, together with the specify block that hosts them and the UDP definition (IEEE 1364 clause 8) that consumes the notifier. The IEEE Standards Association listing is the primary source.
The boundary those clauses draw is the one this page turns on. The standard defines the checks, their arguments, and that a violation toggles the notifier register. It stops there. Driving the cell output to X is the UDP's behaviour — one table row in the vendor's model — which is why X-injection can be present, absent, or selective depending on the library you were shipped, and why a clean run proves nothing about which switches were active.
The numbers are not in the language either: setup/hold limits and cell delays come from Liberty (.lib) characterisation and reach simulation via SDF back-annotation (IEEE Std 1497). See what is SDF and SDF annotation flow.
Related lessons. The windows being checked are setup & hold fundamentals; the models that own the notifier are sequential cell models and specify blocks and timing arcs. For the propagation half, where X comes from and X through muxes and flops; for the triage, real vs false violations, setup violation debug, and hold violation debug.
10. Key Takeaways
- A timing check that fires toggles a notifier, and because the notifier is a UDP input, the cell output is driven to
X— X-injection (2.5). - The injected
Xthen propagates downstream by the X-flow rules (Chapter 6) — so one timing violation can flood the design withX. - A screenful of
Xis often one injecting violation, not many bugs — trace the flood back through the X-flow (6.3) to the single source. - Fix the source (real → design, 8.2/8.3; artifact → run, 8.4) and the whole flood clears; use check/notifier control narrowly — never disable checks wholesale (masks real violations, 8.4).
- The notifier
Xis a modelling flag (Chapter 6); STA is the arbiter of real timing (2.5/0.3). Next: 8.6 — a clock-gated block violation.
Quick Revision
Timing violation → X-propagation: check fires → notifier toggles → UDP injects
X(X-injection, 2.5) →Xpropagates (Ch6 rules). One violation can flood the design — a screenful ofXis often ONE source. Trace the flood back (6.3) to the single injecting violation; fix IT (real → design, artifact → run, 8.4). Manage injection narrowly — never disable checks wholesale (masks real ones). STA is the arbiter. Next: 8.6 — a clock-gated block violation.