Skip to content

Verilog · Chapter 14.4 · Behavioural Modeling

Timing Controls in Verilog — Delay (#), Event (@), and Level (wait)

Timing controls decide when a procedural statement runs. Verilog has three of them. A delay-based control waits a fixed number of time units, an event-based control waits for a signal event such as a clock edge or a value change, and a level-sensitive control waits until a condition becomes true. One of these is the backbone of synchronous design, the event control that triggers every clocked always block on a clock edge. The other two are mainly simulation and testbench tools, since delays model time and pace stimulus while a level wait blocks on a condition. This lesson surveys all three controls, makes clear which are synthesizable and which are simulation only, and sets up the deeper sub-topics that drill each one, including advanced techniques such as intra-assignment delays and named events.

Foundation13 min readVerilogTiming ControlsDelayEventwaitBehavioural

Chapter 14 · Section 14.4 · Behavioural Modeling

1. The Engineering Problem

A procedural statement runs when its block is triggered — but within a block, you often need to control when a statement executes: wait for a clock edge, pace a testbench by time, or block until a condition holds. The question:

How do you control when a procedural statement runs — after a delay, on an event, or when a condition becomes true — and which of these mechanisms is synthesizable?

The answer is the three timing controls: delay (#), event (@), and level (wait). The event control @(posedge clk) is the synthesizable one — it is how clocked logic is triggered — while # delays and wait are simulation/testbench tools. This page surveys all three; the sub-topics drill each.

2. Mental Model — Three Ways to Control "When"

3. The Three Timing Controls

timing-controls.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // DELAY (#) — wait a fixed time (simulation/testbench):
   #10 a = 1;                    // a = 1 after 10 time units
 
   // EVENT (@) — wait for a signal event (synthesizable for clock edges):
   @(posedge clk) q <= d;        // on the rising clock edge
   @(a or b) y = a & b;          // on a change in a or b
   @(done)    ...                // on a named event
 
   // LEVEL (wait) — wait until a condition is true (simulation/testbench):
   wait(ready) data = bus;       // block until ready=1, then assign
  • # introduces a delay; the statement runs after the specified time. Used to pace testbench stimulus and to model timing in simulation. Not synthesizable.
  • @ introduces an event control; the statement runs when the event occurs (an edge, a value change, or a named event trigger). Edge-sensitive @ is the trigger of clocked always blocks — synthesizable.
  • wait introduces a level-sensitive control; the statement runs once the condition holds. A testbench/simulation tool. Not synthesizable.

Visual A — the three timing controls

When does the statement run?

data flow
When does the statement run?# delayafter N time units ·sim@ eventon a signal event ·synthesizablewait conditionuntil condition true ·sim
Three timing controls decide when a procedural statement runs: a delay (#) waits a fixed time, an event (@) waits for a signal event, and wait blocks until a condition is true. Event control (@(posedge clk)) is synthesizable — it triggers clocked logic; delays and wait are simulation/testbench tools.

4. Synthesizable vs Simulation-Only

The practical division, central to using timing controls correctly:

ControlFormSynthesizable?Use
Event (edge)@(posedge clk)yesclocked always blocks
Event (level)@(*) / @(a, b)yescombinational sensitivity
Delay#10notestbench pacing, sim timing
Levelwait(c)notestbench synchronization
  • Event control is the synthesizable timing mechanism@(posedge clk) triggers flip-flops, @(*) triggers combinational logic (Chapter 14.1).
  • Delays and wait are simulation-only — synthesis ignores # delays (real timing comes from the library, Chapter 11.2) and has no hardware for wait. They belong in testbenches and timing models.

So in synthesizable design you use @ event control (especially @(posedge clk)); # and wait live in testbench code.

5. The Sub-Topics

§Sub-topicCovers
14.4.1Delay Based Timing# delays — simple, intra-assignment; pacing and modeling (sim)
14.4.2Event Based Timing@ event control — edges, level, named events; the synthesizable trigger
14.4.3Level Sensitive Timingwait — blocking on a condition (sim)
14.4.4Advanced Timing Techniquesintra-assignment delays, named events, combinations

6. Common Misconceptions

"# delays create hardware timing." False. Delays model time in simulation only; synthesis ignores them, and real timing comes from the cell library. # belongs in testbenches.

"wait is synthesizable." False. wait blocks on a condition in simulation; there is no hardware equivalent. It is a testbench synchronization tool.

"Event control is only for clocks." Misleading. @(posedge clk) triggers sequential logic, but @(*)/@(a, b) is the level-sensitive event control that triggers combinational logic (14.1). Both are synthesizable event controls.

7. Debugging Lab

One timing-control debug post-mortem

Pitfall — using @ where wait was needed (the block hangs forever)
Buggy Code
initial begin
  // Intent: don't proceed until 'ready' is high, then send.
  @(ready);              // waits for a CHANGE on 'ready', not for ready==1
  send_packet();
end

// '@(ready)' is EVENT control: it blocks until 'ready' next CHANGES value. If
// 'ready' is ALREADY high when this line is reached, there is no upcoming
// change to wait for, so the block waits forever and send_packet() never runs.
// Even '@(posedge ready)' fails the same way if ready went high before this
// point — the edge already happened. The testbench just hangs, with no error.
Symptom

A testbench (or behavioural model) silently hangs at a synchronization point — no error, no output, the simulation just stops making progress. It often "works" in one ordering (when the signal changes after the wait) and hangs in another (when the signal was already at the target value), making it look intermittent.

Root Cause

'@' and 'wait' are different kinds of timing control. '@(ready)' is EVENT (edge/change) control: it suspends until 'ready' next TRANSITIONS, regardless of its current value. 'wait(ready)' is LEVEL-sensitive: it checks the condition now and proceeds IMMEDIATELY if it is already true, only blocking while it is false. When 'ready' is already high, the event control has no future change to trigger on and hangs, whereas the level control would have proceeded at once.

The rule: use '@' when you want to react to a CHANGE/edge (a clock edge, a signal toggling); use 'wait' when you want to proceed once a LEVEL condition holds (which may already be true).

Fix
initial begin
  wait (ready);         // LEVEL-sensitive: proceeds now if ready is already 1
  send_packet();
end

// 'wait(ready)' evaluates the condition immediately and continues the moment it
// is true — including if it was already true on arrival — so the block never
// hangs on a pre-asserted signal. (Use '@(posedge clk)' for true edge
// synchronization, and 'wait' for level conditions. Both are simulation-only:
// wait has no hardware equivalent — 14.4.3.)

8. Interview Q&A

9. Summary

Timing controls decide when a procedural statement runs:

  • Delay (#) — after a fixed time; simulation/testbench only.
  • Event (@) — on a signal event (edge, change, named event); @(posedge clk) and @(*) are synthesizable (the triggers of clocked and combinational logic).
  • Level (wait) — until a condition is true; simulation/testbench only.

The split: event control is synthesizable (it triggers hardware); delays and wait are simulation-only.

The sub-topics drill each: Chapter 14.4.1 Delay Based Timing (#), 14.4.2 Event Based Timing (@ — the synthesizable trigger), 14.4.3 Level Sensitive Timing (wait), and 14.4.4 Advanced Timing Techniques (intra-assignment delays, named events). After timing controls, the chapter covers branching: 14.5 Multiway Branching (if/case).