Intuition What this page is
The parent note gave you the rules. Here we stress-test them. We walk through every kind of situation a clocked always block can land in — every reset flavour, every trap where blocking vs non-blocking changes the answer, the degenerate "nothing happens" cases, and the exam twists that look like one thing but behave like another.
The plan: first a scenario matrix that lists every case-class this topic can throw at you. Then worked examples, each labelled with the matrix cell it covers, so by the end there is no scenario left unseen.
Think of every sequential-logic question as living in one of these boxes. If we work one example from each box, we've covered the whole space.
#
Case-class
What makes it distinct
Covered by
A
Pure capture (D→Q)
one flop, no reset, <=
Ex 1
B
Blocking vs non-blocking order
same code, two operators, different hardware
Ex 2
C
Async reset
reset acts between edges
Ex 3
D
Sync reset
reset waits for the edge
Ex 4
E
Enable / hold (degenerate: no change)
edge arrives but value must not update
Ex 5
F
Counter with wrap (limiting value)
max value overflows to zero
Ex 6
G
Combinational trap: inferred latch
missing branch creates memory by accident
Ex 7
H
Real-world word problem
traffic-light / debouncer style timing
Ex 8
I
Exam twist: read-then-write in same block
<= self-reference confuses beginners
Ex 9
The two axes underneath this table are: (1) does time matter? (async vs sync vs pure-comb) and (2) does update order matter? (= vs <=). Every row is one combination of those two questions.
Before any example, here is the one picture everything below refers to. We draw the clock as a square wave, mark its rising edges (the 0 → 1 jumps), and show that a flip-flop's output Q only steps at those rising edges.
Definition Rising edge, in plain words
A rising edge is the instant the clock goes from low (0 ) to high (1 ). always @(posedge clk) means "run the body once, exactly at that instant." Between edges the block sleeps and Q holds its value — that flat horizontal line in the figure is the memory.
Worked example One flip-flop, no reset
always @( posedge clk)
q <= d;
d is 0,1,1,0,1 on the cycles just before edges 1–5. What is q after each edge?
Forecast: guess the sequence of q values before reading on.
At edge 1 , sample the value d held before the edge: d=0, so q←0.
Why this step? The flop captures the input as it stood an instant before the clock ticked — that's the definition of "sample at the edge."
At edge 2 , d=1 → q←1.
At edge 3 , d=1 → q←1.
At edge 4 , d=0 → q←0.
At edge 5 , d=1 → q←1.
Why the same rule every time? One flop = one identical event handler; nothing about it changes between cycles.
Verify: q sequence is 0,1,1,0,1 — an exact copy of d delayed by one cycle . A single D flop always produces its input delayed by one clock. See the D flip-flops and latches note for the gate-level why.
Worked example Same three lines, two meanings
Initial state: a=1, b=0, c=0. On one clock edge, compare:
// Version NB (non-blocking) // Version B (blocking)
b <= a; b = a;
c <= b; c = b;
After one edge, what are b and c in each version?
Forecast: are the two versions equal? (Most people wrongly say yes.)
Version NB — evaluate all RHS first using old values: RHS of line 1 is old a=1; RHS of line 2 is old b=0.
Why this step? Non-blocking <= freezes every right-hand side at the pre-edge values, then assigns together — modelling a rank of flops clocking at the same instant.
Now assign: b←1, c←0. Result NB: b=1, c=0.
Version B — execute in order like software. Line 1: b = a → b becomes 1 immediately .
Why this step? Blocking = completes before the next line runs, so later lines see the new value.
Line 2: c = b, but b is already 1 → c becomes 1. Result B: b=1, c=1.
Verify: NB gives c=0 (the old b), B gives c=1 (the new b). They differ — proving the operator is not cosmetic. See Blocking vs non-blocking assignments for the general rule.
Common mistake The tempting wrong answer
Beginners assume both give c=0 "because it's parallel hardware." Only the non-blocking version is parallel. Blocking inside a clocked block silently chains the assignments — a classic Synthesis vs simulation mismatch source.
Worked example Reset that does not wait
always @( posedge clk or posedge rst) begin
if (rst) q <= 1'b0 ;
else q <= d;
end
Timeline: q=1. Then rst rises in the middle of a clock-high phase , far from any edge, and d=1 throughout. When does q become 0?
Forecast: at the next clock edge, or immediately?
rst is in the sensitivity list , so its rising edge itself wakes the block.
Why this step? posedge rst is a trigger event just like posedge clk. The block does not care that no clock edge happened.
Inside, if (rst) is true → q ← 0 immediately , mid-cycle.
Why check rst first? Priority: reset must dominate normal capture.
While rst stays high, any later clock edge also enters the block, still sees rst=1, and keeps q=0.
Verify: q drops to 0 at the moment rst rises — not at a clock edge. That is the whole point of asynchronous . Compare with Ex 4.
Worked example Same intent,
rst NOT in the list
always @( posedge clk) begin // rst not in sensitivity list
if (rst) q <= 1'b0 ;
else q <= d;
end
Same timeline as Ex 3: q=1, rst rises mid-cycle, d=1. When does q become 0?
Forecast: compare your answer to Ex 3.
The only trigger is posedge clk. rst rising mid-cycle does nothing — the block is asleep.
Why this step? A signal not in the sensitivity list cannot wake the block; it is only read when the block is already running.
At the next rising clock edge , the block runs, sees rst=1, and sets q ← 0.
Why the delay? The reset only "takes" when sampled by the clock — that's synchronous.
Verify: async (Ex 3) → q=0 mid-cycle; sync (Ex 4) → q=0 only at the next edge. Same source lines minus one word in the sensitivity list = different real hardware and different timing per Clocking and timing constraints .
Worked example Edge arrives but nothing must change
always @( posedge clk)
if (en) q <= d; // no else!
Here no else is intentional . q=1. Clock edges arrive with en=0 on edges 1–2, then en=1, d=0 on edge 3.
Forecast: does the missing else create a latch here like it would in @(*)?
Edges 1–2, en=0: the if is false, no assignment runs, q holds at 1.
Why this step? In a clocked block, "no assignment this edge" means the flip-flop simply keeps its stored value — that is normal, correct memory, not an inferred latch.
Edge 3, en=1, d=0: q ← 0.
Why no latch bug here (unlike Ex 7)? An inferred latch is a bug only in combinational always @(*). In an edge-triggered block the flop already is the memory, so holding is expected.
Verify: q sequence over edges 1,2,3 = 1,1,0. The degenerate "do nothing" case is legal and desirable for clock-enabled registers.
Common mistake Latch vs legitimate hold
"No else always makes a latch." False. No-else in @(posedge clk) = enable register (good). No-else in @(*) = inferred latch (bad, Ex 7). The trigger type decides everything.
Worked example 3-bit up-counter, overflow behaviour
always @( posedge clk)
count <= count + 1 ; // count is 3 bits: 0..7
Start count=6. Trace the next four edges. What happens at the top?
Forecast: what comes after 7 in a 3-bit register?
Edge 1: 6 + 1 = 7. Fits in 3 bits (7 = 11 1 2 ). count←7.
Why 3 bits matters? A 3-bit register can only hold 0 to 2 3 − 1 = 7 .
Edge 2: 7 + 1 = 8 = 1000_2, but only 3 bits are kept → the top bit is dropped → 000_2 = 0. count←0.
Why does it wrap? Fixed-width binary addition discards the carry-out; 8 mod 8 = 0 . This is the limiting/overflow case.
Edge 3: 0 + 1 = 1. Edge 4: 1 + 1 = 2.
Why does counting resume normally? Nothing special happened at wrap — it's just modular arithmetic.
Verify: sequence 6 → 7 → 0 → 1 → 2. In general an n -bit counter follows count next = ( count + 1 ) mod 2 n , here 2 3 = 8 .
Worked example Combinational block missing a branch
always @( * ) begin
if (sel) y = a; // no else — y unassigned when sel=0
end
sel goes 1 → 0, with a=1 then some new a. What does the synthesizer build, and why is it a bug?
Forecast: what is y when sel=0?
When sel=1: y = a, fine, purely combinational.
When sel=0: the code assigns nothing to y. But @(*) promises a pure function of inputs — so the tool must give y some value.
Why this step? To "remember" the last y when sel=0, the synthesizer inserts a latch — a level-sensitive memory element you never asked for.
This latch is a bug: it holds stale data, is hard to time, and often mismatches simulation.
Why not in Ex 5? Ex 5 was @(posedge clk) — an edge -triggered flop already provides intended memory. This is @(*), where memory is never wanted.
Verify (the fix): add a default y = 0; at the top, or an else y = b;. Then y is defined for every sel, no latch. See Combinational logic and assign statements and D flip-flops and latches .
Worked example Latching a button press across cycles
A crossing button btn is pressed for one clock cycle. The controller must remember the request (req) until the walk light services it (serviced). Design and trace it.
always @( posedge clk or posedge rst) begin
if (rst) req <= 1'b0 ;
else if (btn) req <= 1'b1 ; // capture press
else if (serviced) req <= 1'b0 ; // clear once handled
// else: hold (no assignment) — legitimate register hold
end
Trace: edge1 btn=1; edge2 btn=0; edge3 btn=0; edge4 serviced=1. Assume rst=0 after start.
Forecast: on which edge does req go back to 0?
Edge 1, btn=1: priority chain — rst false, btn true → req ← 1.
Why priority order? if/else if builds a fixed priority: reset beats capture beats clear.
Edge 2 & 3, btn=0, serviced=0: none of the branches fire → req holds at 1.
Why holding is correct here? This is the enable-hold pattern (Case E): the flop keeps the pending request until serviced.
Edge 4, serviced=1: third branch fires → req ← 0.
Verify: req over edges 1–4 = 1,1,1,0. The request survives the one-cycle press and clears exactly when serviced — the memory behaviour we needed. This is a tiny FSM-style latch of intent.
q <= q + something in one block
always @( posedge clk) begin
a <= b + 1 ;
b <= a + 1 ;
end
Start a=2, b=5. After one edge, find a and b. (The trap: people plug in updated values.)
Forecast: guess a and b.
Freeze all RHS at old values (non-blocking rule): old a=2, old b=5.
Why this step? <= reads every right side before any left side changes — same rule as Ex 2, now with mutual references.
Compute: RHS of line 1 = b+1 = 6; RHS of line 2 = a+1 = 3.
Why not use the new a? Because line 2's a is still the old 2 — the assignment to a hasn't "landed" yet.
Assign together: a←6, b←3.
Verify: a=6, b=3 after one edge. The naive blocking-style guess (a=6 then b = 6+1 = 7) is wrong; the two flops swap-ish because they read the pre-edge snapshot. This is exactly why clocked logic uses <=.
Every matrix cell A–I now has a worked example. Re-map yourself:
Recall Which example covered which cell?
A pure capture ::: Ex 1
B blocking vs non-blocking ::: Ex 2
C async reset ::: Ex 3
D sync reset ::: Ex 4
E enable/hold degenerate ::: Ex 5
F counter wrap (limiting) ::: Ex 6
G inferred latch trap ::: Ex 7
H real-world word problem ::: Ex 8
I exam twist self-reference ::: Ex 9
Recall Predict before revealing
In Ex 2, why is c different between the two versions? ::: Non-blocking freezes RHS at old values so c gets old b=0; blocking updates b first so c gets new b=1.
In Ex 3 vs Ex 4, what single change flips async to sync reset? ::: Removing or posedge rst from the sensitivity list.
In Ex 6, what is (7+1) mod 8? ::: 0 — the counter wraps.
Why is Ex 5's missing else fine but Ex 7's is a bug? ::: Ex 5 is edge-triggered (flop memory is intended); Ex 7 is @(*) where any implied memory is an unwanted inferred latch.
In Ex 9, why isn't b = 7? ::: a on the right is still the pre-edge value 2, so b = 2+1 = 3.