Skip to content

Verilog · Chapter 18.2 · Timing Checks

$setup, $hold & $setuphold in Verilog — The Synchronous Timing Checks

The previous section established what setup and hold are, the keepout window around a clock edge. This page is how Verilog checks them, through three synchronous data-versus-clock timing tasks. The setup check verifies the data was stable for long enough before the clock edge, the hold check verifies it stayed stable for long enough after, and a combined check does both in one call. They share a simple core, since each measures the time between a data change and a clock edge and fires if that gap is smaller than a limit, but they hide a famous trap. The setup and hold checks take their data and clock arguments in opposite orders, and swapping them silently makes a hold check measure the wrong interval. This page covers the three tasks, the reference edge, the argument-order rule with a mnemonic, and the notifier that forces an unknown on the output.

Intermediate13 min readVerilogTiming Checks$setup$hold$setuphold

Chapter 18 · Section 18.2 · Timing Checks

1. The Engineering Problem

You understand the capture window (18.1): data must be stable for a setup time before the clock edge and a hold time after it. Now you are reading — or writing — a flip-flop's timing model, and you need Verilog to enforce that window. Three tasks do it:

the-three-synchronous-checks.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   specify
       // data stable BEFORE the edge:
       $setup(d, posedge clk, 1.0);
 
       // data stable AFTER the edge — note the arguments are SWAPPED:
       $hold(posedge clk, d, 0.5);
 
       // both at once (the form real cell models use):
       $setuphold(posedge clk, d, 1.0, 0.5, notifier);
   endspecify

Look closely at the first two lines. $setup lists the data first, the clock second. $hold lists the clock first, the data second. That reversal is not a typo — it is the language's rule, and getting it wrong produces a check that runs without error but measures the wrong thing.

$setup, $hold, and $setuphold each measure the time between a data change and a clock edge and fire when it is smaller than a limit. $setup takes (data, clock); $hold takes (clock, data) — the order is reversed — and $setuphold combines both with the clock first.

2. Mental Model — Each Check Lists Its Two Events in Time Order

3. $setup — Data Before the Clock

$setup checks that the data settled at least tsu before the reference clock edge.

setup-task.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // $setup(data_event, reference_event, limit [, notifier]);
   $setup(d, posedge clk, 1.0, notifier);
   //     ^data          ^reference   ^tsu  ^optional notifier
  • data_event (first) — the data signal whose stability is being judged (d).
  • reference_event (second) — the clock edge it is measured against (posedge clk).
  • limit — the required setup time (tsu).
  • Fires when (time_of_clock_edge − time_of_data_change) < tsu — i.e., the data changed too close before the edge (or after it). The data arrived too late.

The order reads in time: data, then clock — the data is supposed to be there first.

4. $hold — Data After the Clock (Arguments Reversed)

$hold checks that the data stayed stable at least th after the reference clock edge. Its arguments are in the opposite order to $setup.

hold-task.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // $hold(reference_event, data_event, limit [, notifier]);
   $hold(posedge clk, d, 0.5, notifier);
   //    ^reference   ^data ^th  ^optional notifier
  • reference_event (first) — the clock edge (posedge clk).
  • data_event (second) — the data signal (d).
  • limit — the required hold time (th).
  • Fires when (time_of_data_change − time_of_clock_edge) < th — i.e., the data changed too soon after the edge.

Again the order reads in time: clock, then data — the edge happens first, then the data must hold. The reversal versus $setup is the most error-prone detail in all of timing-check coding, and the temporal-order mnemonic (§2) is the cure.

5. $setuphold — Both in One Call

$setuphold performs the setup and hold checks together. It follows $hold's order — clock first, data second — then both limits.

setuphold-task.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // $setuphold(reference_event, data_event, setup_limit, hold_limit [, notifier]);
   $setuphold(posedge clk, d, 1.0, 0.5, notifier);
   //         ^reference   ^data ^tsu ^th  ^notifier
  • One task declares the whole window: tsu before the edge and th after it.
  • It is the form real cell models prefer — more compact, and it lets the tool treat the setup and hold limits as one coherent window (including support for negative limits, an advanced case where the window is offset from the edge).
  • Semantically it is exactly $setup(d, posedge clk, tsu) plus $hold(posedge clk, d, th) — one call, both checks, sharing a notifier.

If you remember only one of the three, remember $setuphold — it is what you will most often read in a library cell.

6. The Reference Event and Conditioned Checks

The reference (and data) events can be edge-qualified and conditioned:

reference-and-conditions.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   $setup(d, posedge clk, 1.0);          // rising-edge reference
   $hold (negedge clk, d, 0.5);          // falling-edge reference
 
   // Conditioned check — only active when an enable is true (&&&):
   $setup(d, posedge clk &&& en, 1.0);   // check only while en = 1
  • posedge / negedge / edge select which clock transition is the reference. A flop that captures on the rising edge uses posedge clk.
  • &&& adds a condition — the check is active only while the condition holds. This models cells whose timing requirement applies only in certain modes (e.g. only when an enable is asserted), and avoids false violations on cycles where the path is not active.

7. The Notifier — Turning a Violation into an X

Each check takes an optional final argument: a notifier register.

notifier.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   reg notifier;                         // declared in the module
   specify
       $setuphold(posedge clk, d, 1.0, 0.5, notifier);
   endspecify
   // On any violation, the check TOGGLES 'notifier'. The cell's functional
   // model watches notifier and forces q to X when it changes.
  • When a check fires, it toggles the notifier register.
  • The cell's functional model (a UDP or behavioural block) includes the notifier so that a toggle drives the output to X — modelling the metastable, unknowable capture.
  • Without a notifier, a violation still prints a message but does not corrupt the output — so production cell models always wire one up.

This is the link from "a check fired" to "an X appeared downstream." The full mechanism — how the notifier reaches the output, and the X-propagation it triggers — is the subject of 18.5.

Visual A — what each task measures

The three synchronous checks and their intervals

data flow
The three synchronous checks and their intervals$setup(d, clk,tsu)gap data→edge must be≥ tsu$hold(clk, d, th)gap edge→data must be≥ th$setuphold(clk,d, tsu, th)both, one call
$setup measures the data-to-clock gap and fires if it is below tsu (data too late). $hold measures the clock-to-data gap and fires if it is below th (data too soon). $setuphold checks both around one edge. Note the argument order: setup is (data, clock); hold and setuphold are (clock, data).

8. Where They Live, and Where the Limits Come From

  • They live in the specify block (17.4), alongside path delays — one region of the cell model, two roles.
  • They run in gate-level simulation, on a delay-annotated netlist; in zero-delay RTL there is nothing to check (18.1, Chapter 18 overview).
  • The limits come from the cell library via SDF. The specify block declares which checks exist and against which edge; an SDF file back-annotates the real tsu/th numbers. So you rarely hand-author these in your own RTL — you encounter them in cell models and must read them correctly.

9. Common Mistakes

  1. Swapping the $setup/$hold argument order. $setup is (data, clock); $hold is (clock, data). Reverse them and the check measures the wrong interval — silently (§4, DebugLab). Use the temporal-order mnemonic.
  2. Wrong reference edge. A rising-edge flop must reference posedge clk; using negedge checks the wrong transition.
  3. Forgetting the notifier. Without it, a violation prints a message but the output is not forced to X, so downstream logic never sees the failure (§7).
  4. Omitting a needed condition. A path active only in one mode needs &&&; without it, the check can fire spuriously on cycles where the path is irrelevant (§6).
  5. Expecting them to synthesise. Timing checks are simulation-only — synthesis ignores the whole specify block (Chapter 17 overview).

10. Debugging Lab

The hold check that silently checked nothing

Pitfall — $hold written in $setup argument order
Buggy Code
module dff (output reg q, input d, clk);
  reg notifier;
  // ... functional model that forces q to X when notifier toggles ...

  specify
      (posedge clk => (q +: d)) = 2;

      // Setup check — correct: (data, clock)
      $setup(d, posedge clk, 1.0, notifier);

      // Hold check — WRONG ORDER. The author copied the $setup pattern
      // and wrote (data, clock) again, instead of $hold's (clock, data):
      $hold(d, posedge clk, 0.5, notifier);   // BUG: args reversed
  endspecify
endmodule

// $hold's signature is $hold(reference_event, data_event, limit). Here the
// FIRST argument 'd' is taken as the REFERENCE and 'posedge clk' as the
// DATA event. The check now measures the interval from a 'd' reference to
// the next clk edge — NOT the clock-edge-to-data hold interval it should.
//
// Result: genuine hold violations (data changing too soon AFTER the clock
// edge) are NEVER detected. The check runs, prints nothing, and provides a
// false sense of coverage. A hold-marginal path passes gate-level sim and
// fails in silicon.
Symptom

Gate-level simulation reports setup violations correctly but never reports a hold violation — even on a path known (from STA) to be hold-marginal. The cell appears to have a hold check (it is right there in the specify block), yet hold problems sail through. Silicon then shows hold-related failures the simulation never flagged.

Root Cause

$setup and $hold take their data and reference arguments in OPPOSITE orders: $setup(data_event, reference_event, limit) // data first $hold (reference_event, data_event, limit) // clock first ← reversed

The author wrote $hold(d, posedge clk, 0.5) — $setup's order — so the simulator interpreted 'd' as the reference event and 'posedge clk' as the data event. The check is now measuring a meaningless interval (a d-reference to a clk 'data' change), not the clock-edge-to-data-change hold window. It therefore never fires for a real hold violation. The check exists syntactically but verifies nothing useful — the most dangerous kind of bug, because it looks like coverage.

The temporal-order rule prevents this: list the two events in the order they legally occur. Hold requires the clock edge FIRST, then the data must stay stable — so the arguments are (clock, data).

Fix
module dff (output reg q, input d, clk);
  reg notifier;
  // ...
  specify
      (posedge clk => (q +: d)) = 2;

      $setup(d, posedge clk, 1.0, notifier);   // (data, clock)  ✓
      $hold (posedge clk, d, 0.5, notifier);   // (clock, data)  ✓ reference first
  endspecify
endmodule

// Or use the combined form, which only has ONE order to remember
// (clock, data) and does both checks at once:
//
//     $setuphold(posedge clk, d, 1.0, 0.5, notifier);
//
// Now real hold violations are detected: data changing within 0.5 ns AFTER
// the posedge fires the check and forces q to X.
//
// Lesson: $setup is (data, clock); $hold is (clock, data). List the events
// in the order they occur in time. When in doubt, use $setuphold.

11. Interview Q&A

12. Exercises

Exercise 1 — Write the checks

For a rising-edge flop with tsu = 0.8 ns and th = 0.3 ns on data d, write (a) separate $setup and $hold calls and (b) the equivalent $setuphold. Pay attention to argument order.

Exercise 2 — Spot the bug

What is wrong with $hold(d, posedge clk, 0.4);, and what does the simulator actually check as written?

Exercise 3 — Which fires?

Data changes 0.6 ns before a posedge and again 0.2 ns after it. With $setuphold(posedge clk, d, 0.8, 0.3, notifier), which check(s) fire and why?

Exercise 4 — Conditioned check

Explain what $setup(d, posedge clk &&& mode, 1.0) checks, and why the &&& mode is there.

13. Summary

$setup, $hold, and $setuphold are the synchronous data-vs-clock timing checks:

  • $setup(data, clock, tsu) — data first; fires when the clock arrives less than tsu after the data (data too late).
  • $hold(clock, data, th)clock first (reversed); fires when the data arrives less than th after the clock (data too soon).
  • $setuphold(clock, data, tsu, th) — both at once, clock first; the form real cell models use.
  • The mnemonic — list the events in temporal order (earlier first): setup = (data, clock), hold = (clock, data).
  • The notifier — toggles on a fire and lets the cell force the output to X; without it, a violation prints but does not corrupt the value.
  • Where — the specify block, in gate-level simulation; limits arrive via SDF.

The crux to keep: $setup is (data, clock); $hold is (clock, data) — swap them and the check silently verifies nothing.

The next sub-topic moves from synchronous data to asynchronous control: Chapter 18.3 $recovery, $removal & $recrem — the checks for reset/set release relative to the clock, and the reset-comes-out-in-X hazard.

Standards & specifications

Governing standard
IEEE Std 1364 (Verilog)(opens IEEE in a new tab)

Defines the Verilog language and its simulation semantics, including the event scheduling model. Synthesis support is defined by tools, not by this standard.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the Verilog HDL curriculum.