3.5.3 · D4HDL & Digital Design Flow

Exercises — Sequential logic and always blocks

2,761 words13 min readBack to topic

Before we start, one picture fixes the vocabulary we will use in every single problem: the clock edge, the old value, and the new value.

Figure — Sequential logic and always blocks

Level 1 — Recognition

Goal: can you read a block and name what hardware it is?

Exercise 1.1

State whether each block models combinational or sequential logic, and name the trigger:

// (a)
always @(*)          y = a & b;
// (b)
always @(posedge clk) q <= d;
// (c)
always @(posedge clk or negedge rstn) begin
  if (!rstn) q <= 0; else q <= d;
end
Recall Solution 1.1
  • (a) Combinational. Trigger: any change of a read signal (a or b), because @(*) auto-builds the sensitivity list. No clock → no memory.
  • (b) Sequential. Trigger: the rising clock edge (posedge clk). One flip-flop.
  • (c) Sequential with an active-low asynchronous reset. Trigger: posedge clk for normal capture, negedge rstn for reset. rstn is in the list, so reset acts immediately, not on the clock. This is the reset style built step-by-step in D flip-flops and latches — reset that does not wait for the clock.

Exercise 1.2

Which assignment operator (= or <=) belongs in each of the two blocks above ((a) and (b)), and why in one sentence each?

Recall Solution 1.2
  • (a) combinational → blocking =. Order equals data-flow, so evaluating in program order is correct.
  • (b) sequential → non-blocking <=. All right-hand sides must be read from old values so parallel flip-flops update together.

Level 2 — Application

Goal: write a small block that meets a spec.

Exercise 2.1

Write a D flip-flop q with a synchronous, active-high reset that forces q to 0.

Recall Solution 2.1
reg q;                        // 1-bit storage box for the flip-flop
always @(posedge clk) begin   // rst NOT in the list → synchronous
  if (rst) q <= 1'b0;         // reset checked first = priority
  else     q <= d;
end

Why rst is not in the sensitivity list: synchronous reset only takes effect at a clock edge. Why if (rst) first: reset must dominate normal capture.

Exercise 2.2

Write a 4-bit up-counter count that resets asynchronously to 0 on active-high rst, else increments each clock.

Recall Solution 2.2
reg [3:0] count;                // 4-bit box, holds 0..15
always @(posedge clk or posedge rst) begin
  if (rst) count <= 4'b0000;    // async → rst in the list
  else     count <= count + 1;  // non-blocking: reads OLD count
end

Why <=: the right side reads the old count, adds 1, then commits — modelling one register that clocks once per edge. Start at 0000; after 15 edges it reads 1111 and the next edge wraps to 0000 (a 4-bit value rolls over modulo ).

Exercise 2.3

Predict the value of count from Exercise 2.2 after each of the first 6 clock edges following reset. (Report exactly 6 values — the state after edge 1 through after edge 6, not the pre-edge start.)

Recall Solution 2.3

Just before edge 1, count = 0 (reset value). Each edge adds 1, so the six requested values — the state after edges 1…6 — are: In 4-bit binary: 0001, 0010, 0011, 0100, 0101, 0110. The pre-edge 0000 is the starting state, not one of the six reported outputs. The waveform below traces exactly these steps — read the red count line changing one tick after each rising edge.

Figure — Sequential logic and always blocks

Level 3 — Analysis

Goal: trace values through tricky blocking/non-blocking code.

Exercise 3.1

Before the edge, a = 1, b = 2, c = 3. Trace a, b, c after one clock edge for BOTH versions.

// Version N (non-blocking)          // Version B (blocking)
always @(posedge clk) begin          always @(posedge clk) begin
  a <= b;                              a = b;
  b <= c;                              b = c;
  c <= a;                              c = a;
end                                    end
Recall Solution 3.1

Version N — non-blocking. All RHS read the old values a=1, b=2, c=3 first, then commit: Result: a=2, b=3, c=1 — a clean 3-way rotation.

Version B — blocking. Executes top-to-bottom, each line sees the just-updated value:

  • a = ba = 2
  • b = cb = 3
  • c = ac = 2 (uses the new a, not old!)

Result: a=2, b=3, c=2 — the rotation is broken; 1 is lost.

The figure below puts both versions side by side: the black arrows in Version N all reach back to the old values, while the red arrow in Version B feeds the just-written a forward — that single red arrow is why the 1 disappears.

Figure — Sequential logic and always blocks

Exercise 3.2

This mux compiles and simulates, yet the synthesizer warns about an inferred latch. Find the bug and its consequence.

always @(*) begin
  if (sel == 2'b00) y = a;
  else if (sel == 2'b01) y = b;
  else if (sel == 2'b10) y = c;
  // sel == 2'b11 : nothing assigned
end
Recall Solution 3.2

When sel == 2'b11, no branch assigns y. Verilog interprets "keep the last value" as memory, so the synthesizer infers a latch on y — an unwanted 1-bit storage element in what should be pure combinational logic. In Combinational logic and assign statements the same job is done safely with a single assign and a full ternary, which is impossible to leave incompletely specified. Fix (either works):

always @(*) begin
  y = a;                    // default at top, then override
  if (sel == 2'b01) y = b;
  else if (sel == 2'b10) y = c;
  else if (sel == 2'b11) y = 1'b0;
end

Now y is assigned on every path → no latch.


Level 4 — Synthesis

Goal: build a multi-stage design from a word spec.

Exercise 4.1

Design a 3-stage shift register: input din enters, and on each clock the data shifts q1 → q2 → q3. Write it, and explain why <= is mandatory here.

Recall Solution 4.1
reg q1, q2, q3;               // three 1-bit boxes, one per stage
always @(posedge clk) begin
  q1 <= din;   // reads OLD din
  q2 <= q1;    // reads OLD q1
  q3 <= q2;    // reads OLD q2
end

Why <= is mandatory: each line must read the pre-edge value of the previous stage. With <=, all three RHS are sampled from old values, then committed together — so a bit truly moves one stage per edge. If you used blocking =, line 2 would see the new q1 and the whole register would collapse into a single stage.

Exercise 4.2

For the shift register above, din over 5 consecutive cycles is 1, 0, 1, 1, 0. All of q1,q2,q3 start at 0. Give (q1,q2,q3) after each of the 5 edges.

Recall Solution 4.2

Each edge: q1←din, q2←old q1, q3←old q2. Start (0,0,0).

edge din q1 q2 q3
1 1 1 0 0
2 0 0 1 0
3 1 1 0 1
4 1 1 1 0
5 0 0 1 1

Notice the first 1 (from edge 1) reaches q3 exactly on edge 3 — three stages, three cycles of delay. The waveform below shows this: follow the red highlight tracking that first 1 marching one stage to the right per clock.

Figure — Sequential logic and always blocks

Exercise 4.3

Build a 2-state FSM: a toggle flip-flop state that flips between 0 and 1 every clock when en=1, and holds when en=0. Async active-high reset to 0.

Recall Solution 4.3
reg state;                    // 1-bit box holding the FSM state
always @(posedge clk or posedge rst) begin
  if (rst)      state <= 1'b0;
  else if (en)  state <= ~state;  // flip
  else          state <= state;   // hold (explicit is clearest)
end

Why the explicit else state <= state: it documents the hold. In a clocked block the omission is safe (a flop naturally holds), unlike the combinational latch case in 3.2. This one-flip-flop toggle is the smallest example of the state-register pattern generalised in Finite State Machines in Verilog, where the same clocked block updates a multi-bit state.


Level 5 — Mastery

Goal: reason about timing, wrap-around, and mismatch at once.

Exercise 5.1

A counter count is 3 bits wide and increments every clock (no reset). It currently reads 6 (110). What does it read after 1 edge? After 3 edges? Explain the wrap.

Recall Solution 5.1

A 3-bit register counts modulo .

  • After 1 edge: 111.
  • After 3 edges: 001.

Why the wrap: 7 + 1 = 8 needs 4 bits, but only 3 exist, so the top bit is discarded, leaving 000. Counting continues 0,1,... — this is exactly arithmetic modulo 8.

Exercise 5.2

Two students write the same logic two ways. Student A uses always @(a or b) (forgets c); Student B uses always @(*). Both intend y = a & b & c. Explain what each simulates and what each synthesizes, and who is correct.

// Student A                 // Student B
always @(a or b)             always @(*)
  y = a & b & c;               y = a & b & c;
Recall Solution 5.2
  • Student A: the sensitivity list is missing c. Simulation: the block only re-runs when a or b changes; a change in c alone leaves y stale (wrong). Synthesis: the synthesizer reads the body, sees c, and builds the full AND gate anyway. → the block behaves one way in the simulator and another in silicon, the classic hazard catalogued in Synthesis vs simulation mismatch.
  • Student B: @(*) auto-includes a, b, c. Simulation and synthesis agree. ✅
  • Correct: Student B. Always use @(*) for combinational logic so no signal is ever forgotten.

Exercise 5.3

Final boss. In a posedge clk block you write q <= q + 1;. Explain, using the "old value" idea, why this does not create an infinite feedback loop even though q appears on both sides.

Recall Solution 5.3

The right side q + 1 is evaluated using the old value of q (the value before the edge). That result is then committed to the left q once, at the end of the edge. The block does not re-run just because q changed — as the parent note on always blocks stresses, an edge-triggered block is event-driven and wakes only on the next posedge clk. So each edge adds exactly one; no runaway loop. Contrast a combinational always @(*) q = q + 1;, where q changing would re-trigger the block → an illegal combinational loop. The clock edge is precisely what tames the feedback.


Recall Master checklist (reveal after all 5 levels)

Clocked → <=. Combinational → = with @(*). Async reset → in the sensitivity list, checked first. Every combinational branch assigns every output (else latch). One register → one always. N-bit register wraps modulo . always is event-driven, not a loop.

After a non-blocking 3-way rotate a<=b; b<=c; c<=a with a=1,b=2,c=3, what are a,b,c?
a=2, b=3, c=1.
After a blocking a=b; b=c; c=a with a=1,b=2,c=3, what are a,b,c?
a=2, b=3, c=2 (rotation broken).
A 3-bit counter at 6 reads what after 3 edges?
(6+3) mod 8 = 1, i.e. 001.
Why is q <= q+1 not an infinite loop?
RHS uses the old q, commits once, and the block only re-runs on the next clock edge.