3.5.2 · D3HDL & Digital Design Flow

Worked examples — Combinational logic in HDL

2,770 words13 min readBack to topic

Before we begin, three plain-word anchors so nothing below uses an unearned symbol:


The scenario matrix

Every combinational-HDL bug or pattern falls into one of these cells. The examples below are labelled with the cell(s) they cover.

# Case class Trigger condition Safe / Buggy? Example
C1 Fully-specified if/else every path assigns output ✅ combinational Ex 1
C2 if with no else one path leaves output unassigned ⚠️ latch Ex 2
C3 case without default, incomplete missing selector values ⚠️ latch Ex 3
C4 case fixed by default all values covered ✅ combinational Ex 3
C5 Incomplete sensitivity list manual @(a) misses b ⚠️ sim ≠ silicon Ex 4
C6 Blocking vs non-blocking ordering wrong operator changes logic ⚠️ wrong function Ex 5
C7 Degenerate input — constant/tied signal one input never changes ✅ folds to simpler gate Ex 6
C8 Multi-output block, partial assign some outputs missing on a path ⚠️ latch on that output only Ex 7
C9 Real-world word problem priority encoder / thermostat ✅ if fully covered Ex 8
C10 Exam twist — overlapping/priority if chain later branch shadows earlier ✅ but subtle Ex 9

The "sign/quadrant" analogue for this topic is the truth-table cell: every input combination is a cell, and our job is to prove every cell is covered. Missing cells = latches, exactly like a missing quadrant broke the naive arctan.


Example 1 — Fully-specified if/else (Cell C1)

Forecast: Guess — will this need a default? How many input cells must be covered, and are they all here?

  1. Enumerate the cells. sel is one bit ⇒ exactly two cells: sel=0 and sel=1. Why this step? Latch-freedom is a coverage claim; you cannot check coverage without listing what must be covered.

  2. Write the block with both branches.

    always @(*) begin
        if (sel) y = b;
        else     y = a;
    end

    Why this step? The else fills the sel=0 cell; the if fills sel=1. Both cells assign y.

  3. Coverage check. Look at the two rows of the truth table in Figure s01 — both are green (assigned).

    Why this step? Green everywhere = no path leaves y floating = no latch.

Verify: Boolean form is . Plug sel=0: ✓. Plug sel=1: ✓. Both cells match the spec.


Example 2 — if with no else (Cell C2, the classic latch)

Forecast: What happens to y when enable = 0? Is there a rule for that cell?

  1. List the cells of the controlling signal. enable = 1 and enable = 0. Why this step? Same coverage discipline as Ex 1.

  2. Trace enable = 0. The if body is skipped. No statement assigns y. Why this step? An unassigned output on a reachable path is exactly the latch trigger from the definition box.

  3. Name the hardware. The tool must "hold the previous y," so it builds a transparent latch enabled by enable. See Figure s02: the red cell (enable=0) is uncovered.

    Why this step? Making the missing cell visible is how you spot the bug in review.

  4. Fix — default before the branch.

    always @(*) begin
        y = 1'b0;          // covers enable=0
        if (enable) y = data;
    end

    Why this step? The unconditional y = 1'b0 runs first; the later blocking if overrides it when enable=1. Now both cells assign y.

Verify: enable=1 ⇒ default overwritten ⇒ y=data ✓. enable=0 ⇒ default stands ⇒ y=0 (defined, no memory) ✓. Latch eliminated.


Example 3 — case without default, then fixed (Cells C3 → C4)

Forecast: How many cells does a 2-bit sel have? How many are listed?

  1. Count the cells. 2 bits ⇒ values: 00, 01, 10, 11. Why this step? An -bit selector always has cells; here .

  2. Spot the gap. Only 00, 01, 10 are listed. 11 is uncoveredy unassigned when sel=11 ⇒ latch (Cell C3). See the red cell in Figure s03.

    Why this step? missing cell is enough for a latch.

  3. Repair with default (Cell C4).

    case (sel)
        2'b00: y = a;
        2'b01: y = b;
        2'b10: y = c;
        default: y = c;   // covers 2'b11 (and any X)
    endcase

    Why this step? default sweeps up all remaining cells in one line — you never have to enumerate them.

Verify: Cells covered after fix: {00,01,10} explicit + {11} via default cells . Full coverage ✓, no latch.


Example 4 — Incomplete sensitivity list (Cell C5, sim ≠ silicon)

Forecast: This one has no latch. Where can it possibly go wrong?

  1. Separate two questions. "Is y assigned on every path?" — yes (single unconditional statement). "Does the block re-run when it should?" — that is the sensitivity question, a different axis. Why this step? Coverage (latch axis) and sensitivity (simulation axis) are independent; a block can pass one and fail the other.

  2. Trace a b-only change. Hold a fixed, flip b. The block is sensitive only to a, so simulation does not re-evaluate — the simulated y stays stale. Figure s04 shows the timing mismatch.

    Why this step? The synthesized AND gate does react to b; so simulation and silicon disagree — the worst kind of bug because tests pass on a wrong model.

  3. Fix. always @(*) y = a & b; — the * auto-includes every read signal (a and b). Why this step? @(*) makes the simulation model track the same signals the gate does.

Verify (numeric): True hardware, a=1,b=0→1 should give y:0→1. Set a=1. With @(a): block never fires on b's change ⇒ simulated y stuck at old value 0 while hardware says 1. Mismatch confirmed; @(*) yields y = a & b = 1 ✓.


Example 5 — Blocking vs non-blocking ordering (Cell C6)

Forecast: With <=, which value of t does y read — the new one or the old one?

  1. Blocking version (correct for combinational).

    always @(*) begin
        t = a & b;   // updates t NOW
        y = t | c;   // reads the NEW t
    end

    Why this step? = runs in order and updates immediately, so y sees the freshly computed t — exactly like two gates in series.

  2. Non-blocking version (wrong here).

    always @(*) begin
        t <= a & b;  // scheduled for end of step
        y <= t | c;  // reads OLD t (this step's start)
    end

    Why this step? <= defers updates, so y uses the previous t, computing a delayed/incorrect function. Figure s05 shows the one-step lag.

Verify (numeric): Take a=1,b=1,c=0, with old t=0. Blocking: t=1, then y = 1|0 = 1 ✓ (matches intended ). Non-blocking: y = t_{old}|c = 0|0 = 0 ✗ — a wrong 0. Ordering matters; use =.


Example 6 — Degenerate input, constant-folded (Cell C7)

Forecast: Guess each output before reading the algebra.

  1. Apply Boolean identities. (AND with 1 passes through). (OR with 1 forces high). Why this step? A tied-constant input is a degenerate cell; identities collapse the gate.

  2. Read off the synthesized hardware. y becomes a plain wire from a (no gate). z becomes a constant 1 (the synthesizer deletes a entirely). Why this step? Synthesis constant-folds; showing this reassures you no latch or wasted gate appears — a continuous assign is always combinational (Cell C7 is safe).

Verify: a=0: y=0&1=0=a ✓, z=0|1=1 ✓. a=1: y=1&1=1=a ✓, z=1|1=1 ✓. Both hold for all cells of a.


Example 7 — Multi-output block, one output missed (Cell C8)

Forecast: A latch appears per output. Which one?

  1. Check each output independently. hi is assigned in both branches ⇒ fully covered. lo is assigned only in the g=1 branch ⇒ the g=0 cell is missing. Why this step? Latch inference is decided output-by-output, not block-by-block.

  2. Diagnose. lo gets a transparent latch (enabled by g); hi stays pure combinational. See Figure s06 — hi row all green, lo row red on g=0.

    Why this step? Pinpointing the specific latched signal is what synthesis warnings do; you should predict them.

  3. Fix. Add lo = 1'b1; (or a chosen default) to the else, or set both defaults on top.

Verify: After fix, g=1hi=1,lo=0; g=0hi=0,lo=1. Each output covers both cells each ✓. No latch on either.


Example 8 — Real-world word problem: priority encoder (Cell C9)

Forecast: How many input cells, and how does the "none active" case avoid a latch?

  1. Cell count and priority rule. 3 inputs ⇒ cells. Priority: check s2 first, then s1, then s0, else default. Why this step? Word problems still reduce to covering every input cell.

  2. Code with a leading default (the "none" case).

    always @(*) begin
        code = 2'b00;            // default = none active
        if      (s2) code = 2'b10;
        else if (s1) code = 2'b01;
        else if (s0) code = 2'b00;
    end

    Why this step? The unconditional code = 2'b00 first covers the all-zero cell (and any un-hit cell), so the whole space is assigned ⇒ no latch. The if/else if chain enforces priority.

  3. Trace a mixed case. s2=1,s1=1,s0=0: first if fires ⇒ code=10; lower branches skipped by else. Highest priority wins, as required.

Verify (numeric):

  • s2s1s0=000 ⇒ default ⇒ code=00 (0) ✓
  • s2s1s0=001s0 branch ⇒ code=00 (0) ✓
  • s2s1s0=010s1 branch ⇒ code=01 (1) ✓
  • s2s1s0=110s2 branch ⇒ code=10 (2) ✓ (priority over s1)
  • s2s1s0=111s2 branch ⇒ code=10 (2) ✓

Example 9 — Exam twist: shadowed branches (Cell C10)

Forecast: Since the second if runs after the first, guess the final expression.

  1. Execute top-to-bottom (blocking). Start y=0. If a, set y=1. Then if b, set y=0 — this can overwrite the previous line. Why this step? Blocking statements are sequential; a later assignment shadows an earlier one when its condition holds.

  2. Derive the Boolean function. y=1 only if a is true and b is false (otherwise the second if clears it, or a never set it). So . Why this step? Reading the net effect of overriding assignments is the exam trap — you must simulate the order, not read lines in isolation.

  3. Latch check. y is assigned unconditionally on line 1 ⇒ every cell covered ⇒ combinational, despite the missing elses. Why this step? The default line saves it; shadowing changes the function, not the latch status.

Verify (numeric, all 4 cells of a,b):

  • a=0,b=0: y=0 then no changes ⇒ 0;
  • a=1,b=0: y→1, second if skipped ⇒ 1;
  • a=0,b=1: y=0, first skipped, second sets 00;
  • a=1,b=1: y→1 then →00;

Recall Scenario matrix — did we cover every cell?

C1 Ex1 · C2 Ex2 · C3+C4 Ex3 · C5 Ex4 · C6 Ex5 · C7 Ex6 · C8 Ex7 · C9 Ex8 · C10 Ex9 — all ten cells worked. ✓

Active recall

Connections