Skip to content

Verilog · Chapter 18.5 · Timing Checks

Timing Violation Behaviour in Verilog — Notifiers & X-Propagation

Earlier sub-topics taught what each timing check detects. This one teaches what a fired check actually does, which is the part you live with when you debug a gate-level simulation. When a check fires, it toggles a notifier register, and the cell's functional model uses that toggle to force the captured value to X, modelling the metastability a real flop would suffer. That X then spreads through downstream logic, and Verilog handles X pessimistically, pushing it further than real silicon would. The result is the scenario every gate-level engineer eventually faces: one early violation balloons into a netlist-wide flood of unknown signals. This page drills the notifier, X-propagation, X-pessimism, and the debugging discipline that tames the flood by finding the earliest violation first.

Intermediate13 min readVerilogTiming ChecksNotifierX-PropagationDebugging

Chapter 18 · Section 18.5 · Timing Checks

1. The Engineering Problem

A gate-level simulation is running fine, and then — at one point in time — much of the design turns X. Hundreds of signals unknown, the violation log scrolling with timing-check messages scattered across many cycles. The instinct is to start fixing the violations you can see. After a day of it, nothing improves. Where do you actually start?

the-x-flood.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // A gate-level run, partway through:
   //   [VIOLATION] $setup  ... at t=1042
   //   [VIOLATION] $hold   ... at t=1207
   //   [VIOLATION] $setup  ... at t=1208
   //   ... dozens more ...
   //   and q[*] = X across half the netlist.
   //
   // Question: are these dozens of INDEPENDENT failures — or is most of the
   // X just the DOWNSTREAM SHADOW of one early violation?

To debug this, you need to know exactly what a fired timing check does to the simulation: how a violation becomes an X, how that X spreads, and why a single early violation can look like a hundred. That is this page.

A fired timing check toggles a notifier register; the cell model uses that toggle to force its output to X; the X propagates downstream — pessimistically — so one early violation can flood a netlist. The debugging discipline is to find the earliest violation, because the rest is usually its shadow.

2. Mental Model — Fire → Notifier → X → Propagation → Flood

3. The Notifier Register

Every timing check accepts an optional final argument: a notifier, a reg declared in the cell module.

notifier-declared.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module dff (output q, input d, clk);
       reg notifier;                                  // the notifier register
       specify
           $setuphold(posedge clk, d, 1.0, 0.5, notifier);
       endspecify
       // ... functional model uses 'notifier' (see §4) ...
   endmodule
  • The notifier is a plain register. Its value is meaningless — the check toggles it (flips it to a new value) whenever it fires.
  • The toggle is an event: the cell model edge-detects it. One notifier is typically shared by all of a cell's checks, so any violation produces the same effect.
  • Without a notifier argument, a fired check still prints a violation message — but does nothing else. The output is not corrupted; the simulation keeps producing a clean (possibly wrong) value. Production cell models always wire a notifier in, precisely so a violation has a visible consequence.

4. From Notifier to X

The notifier is the trigger; the cell's functional model is what turns it into an X on the output.

notifier-to-x.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // The flop's function is usually a SEQUENTIAL UDP that takes the notifier
   // as an input. Its table includes a row like:
   //
   //   // clk    d   notifier : state : next
   //      (01)   0   ?        :  ?    :  0      // normal posedge capture
   //      (01)   1   ?        :  ?    :  1
   //       ?     ?   *        :  ?    :  x      // notifier TOGGLES → output x
   //
   // The '*' on notifier means "any change", and it drives the next state to x.

When a check fires and toggles the notifier, the UDP's "notifier changed" row matches and drives the flop's output to X. This is the complete path: violation → notifier toggle → UDP forces output X. The X is the simulator's honest statement that, for this capture, the real flop's value is unknowable (the metastability of 18.1). The cell library author wires this up; you, reading or debugging, just need to know that a fired check becomes an X here.

Visual A — violation to X

How a fired check becomes an X

data flow
How a fired check becomes an Xcheck firessignal in a forbidden windownotifier togglesthe check flips the regUDP sees thetoggle'notifier changed' row matchesoutput forced toXmetastable value made visible
A fired timing check toggles the notifier register; the cell's sequential UDP detects the toggle and forces the output to X. Without a notifier, the check would only print a message — the notifier is what turns a detected violation into a visible X on the output.

5. X-Propagation

Once a flop's output is X, the unknown spreads through whatever it drives. Verilog's logic operators propagate X partially — a controlling value blocks it, an unknown one passes it:

x-propagation.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   0 & X  = 0      // AND: a controlling 0 blocks X
   1 & X  = X      // AND: no controlling value → X passes
   1 | X  = 1      // OR : a controlling 1 blocks X
   0 | X  = X      // OR : no controlling value → X passes
   ~X     = X      // NOT of unknown is unknown

So an X does not necessarily stop — through most arithmetic and any path without a controlling input, it keeps going, turning downstream signals X. A single unknown at the wrong place can spread across a datapath in one evaluation.

6. X-Pessimism — Why the Flood Is Bigger Than the Bug

Verilog's X handling is deliberately pessimistic: it treats X as "could be 0 or 1, and I will assume the worst," even in cases where real hardware would produce a defined value. The canonical example is a multiplexer:

x-pessimism.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // 2:1 mux: y = sel ? a : b
   // Suppose sel = X (from a timing violation upstream), but a = b = 1.
   //
   //   Real hardware: BOTH inputs are 1, so y = 1 regardless of sel. DEFINED.
   //   Verilog:       sel is X, so it cannot "choose" → y = X. UNKNOWN.

Because the model does not reason "both inputs agree, so the select does not matter," one X on a select or control line can blacken an entire cone of logic that silicon would resolve cleanly. This is why an X flood is almost always larger than the actual failure: most of the screen of X is pessimistic propagation, not independent bugs. Recognising this is the key to not over-reacting to the flood.

7. Debugging the X Flood — Find the Earliest Violation

The discipline follows directly from §6: if most of the flood is the pessimistic shadow of one early X, then the way to debug it is to find that X, not to chase its consequences.

  1. Sort the violation log by simulation time. The earliest timing-check violation is the most likely root cause; everything after it is suspect as downstream propagation.
  2. Trace the earliest X to its source. The first violation names the cell, the signal, and the time — that is the injection point.
  3. Fix that one path (the real timing problem — a marginal setup path, an un-synchronized reset, a runt clock) and re-run.
  4. Watch the later violations vanish. When the root-cause X is gone, the cone of pessimistic X it fed collapses, and most of the later messages disappear because they were never independent failures.
  5. Iterate on whatever genuinely remains.

The wrong approach is to start at the latest, most visible X (the deepest part of the flood) — it is the furthest from the cause and almost always just a symptom.

Visual B — one root cause, a flood of shadow

The X flood — one early violation, a pessimistic cone

data flow
The X flood — one early violation, a pessimistic coneearliestviolationone marginal path → one XX propagatesdownstream logic goes XX-pessimismwidens itcontrol/select X blackens conesnetlist-widefloodhundreds of X — mostly shadow
A single early timing violation injects one X; propagation and X-pessimism widen it into a netlist-wide flood. The hundreds of X are mostly the shadow of the one root cause. Debug from the earliest violation — fix it and the shadow collapses.

8. The Anti-Pattern — Silencing the Check

When the flood is overwhelming, the tempting "fix" is to make it stop: disable the timing checks, or switch the simulator to an X-optimistic mode that does not propagate X. Resist it.

  • Disabling the check removes the only visible symptom of a real timing problem. The marginality is still in the silicon; you have just blinded the simulation to it — the same mistake as deleting a $setup to stop it firing (18.2).
  • X-optimistic modes can hide genuine X bugs along with the pessimistic ones. They are a tool for understanding pessimism, not a fix for a violation.
  • The legitimate moves are: fix the root-cause path, confirm with static timing analysis (which checks all paths, not just the exercised one), and use the violation log as a guide, not a nuisance to suppress.

A timing check earns its keep precisely in moments like this — it converted an invisible, silicon-only marginality into a loud, located X. Silencing it throws that away.

9. Common Mistakes

  1. Chasing the latest/deepest X instead of the earliest violation. The deepest X is furthest from the cause; the earliest violation is the root (§7, DebugLab).
  2. Treating every X in the flood as an independent bug. Most are pessimistic propagation from one source (§6).
  3. Forgetting the notifier. No notifier means a violation prints but never forces X — so a real failure produces a clean, defined, wrong value (§3).
  4. Silencing the check or going X-optimistic to clear the flood. That hides the symptom, not the bug (§8).
  5. Assuming the X flood proves the design is hopelessly broken. Thanks to pessimism, the flood usually overstates the damage; one fix often clears most of it (§6, §7).

10. Debugging Lab

The netlist that went all-X from one early violation

11. Interview Q&A

12. Exercises

Exercise 1 — Trace the chain

List, in order, the four steps from a $setup violation firing to an X appearing on a flop's output, naming the notifier's role.

Exercise 2 — X-pessimism

For y = sel ? a : b with a = 0, b = 0, sel = X: what does Verilog produce for y, what would real hardware produce, and what is this difference called?

Exercise 3 — Where to start

A GLS run has violations at t=900, t=1500, t=1500, and t=4000, and a huge X cone at t=4000. Which violation do you investigate first, and why?

Exercise 4 — The wrong fix

A teammate clears an X flood by disabling the cells' timing checks and the test goes green. Explain in two sentences why this is dangerous.

13. Summary

Violation behaviour is what every fired timing check actually does:

  • The notifier — a reg the check toggles on a violation; the cell's UDP sees the toggle and forces the output to X. No notifier → the check only prints.
  • X-propagation — the X spreads through downstream logic (a controlling value blocks it; otherwise it passes).
  • X-pessimism — Verilog over-propagates X versus silicon (the sel = X but a == b mux), so the flood is bigger than the bug.
  • The debugging discipline — find the earliest violation; fix that root path; the downstream shadow collapses. Never chase the latest X, and never silence the check.

Timing checks complete

This closes Chapter 18 Timing Checks — the concept (18.1 setup & hold), the synchronous checks (18.2 $setup/$hold/$setuphold), the asynchronous checks (18.3 $recovery/$removal/$recrem), the waveform-integrity checks (18.4 $width/$period/$nochange/$skew), and what a fired check does (18.5, this page). You can now read a cell's timing checks, understand what each detects, and debug the X a violation produces.

Chapters 17 and 18 have built the two halves of timing-aware simulation: delays make signals late, and checks judge whether late is too late. The final chapter explains the machinery beneath both — Chapter 19 Timing Regions — the event ordering that decides exactly which values a check observes and makes the whole simulation deterministic.