Barcelona Abroad · Week 1 · Day 3

Combinational Logic & always @(*)

CRAFT cycle · 2.5 hours · Wed 5/27 · PM visit: Sagrada Família

HDL for Digital System Design · UCF ECE · Barcelona Summer 2026

CRAFT

Today at a Glance

PhaseTimeActivity
🌍 Contextualize10 minReal-time decisions in Barcelona infrastructure
⚠️ Reframe15 minif/else vs case = different hardware
🛠 Assemble70 minLatch hunt · priority encoder · 4-bit ALU
🛡 Fortify45 minSim · yosys show diff · first PPA compare
🔗 Transfer10 minBrief for the Sagrada visit · Day 4 preview
First PPA exposure today. You'll synthesize the same priority encoder three ways and compare LUT counts on screen.

▸ Phase 1 of 5  ·  ~10 min

🌍 Contextualize

Decisions that have to happen the instant the inputs change

Combinational = Instant Response

  • Traffic light at Plaça Catalunya — a phase controller turns sensor inputs into a green/amber/red output the moment a car arrives
  • Sagrada Família structural model — load in → shape out, no memory of when the load arrived
  • Camp Nou turnstile — valid ticket bits → gate open signal, instant

This afternoon

Sagrada Família visit. Gaudí's designs are essentially massive combinational systems: every load distributes simultaneously through a branching structure. Take photos — we'll use them tomorrow.

All of today's hardware describes "input changes ⇒ output changes now." No clocks. No memory. Pure logic.

▸ Phase 2 of 5  ·  ~15 min

⚠️ Reframe

Same intent, different gates

⚠️ if/elsecase in Hardware

❌ Wrong Model

"They're equivalent — pick the prettier one."

✓ Right Model

if/elsepriority mux chain. Later branches sit on a longer combinational path.
caseparallel mux. All branches evaluated at once, tool picks by index.

Use case when branches are mutually exclusive (ALU opcodes, FSM states).
Use if/else when priority is part of the spec (arbiters, interrupt handlers).

Latch vs. Combinational — the Picture

An incompletely-assigned always@(*) block holds its old value and synthesizes to a latch; a fully-assigned block (default or else on every path) is pure combinational logic
Miss a branch in always @(*) and the signal must remember its old value → Yosys infers a latch. Cover every path → pure combinational logic. This is the bug you'll hunt in Ex 1.

The Latch Trap

// ❌ Buggy — no else, no default
always @(*) begin
    if (sel == 2'b00) y = a;
    if (sel == 2'b01) y = b;
    // when sel == 2'b10 or 2'b11, y holds its previous value
    // → Yosys infers a LATCH
end
// ✓ Fixed — default at the top
always @(*) begin
    y = 1'b0;                   // default covers every path
    case (sel)
        2'b00: y = a;
        2'b01: y = b;
        2'b10: y = c;
        2'b11: y = d;
    endcase
end
Yosys warning found and reported 1 problem with "latch" in it = you have a bug. Always.

▸ Phase 3 of 5  ·  ~70 min  ·  You build

🛠 Assemble

Three builds, escalating complexity

Build Plan

  1. Ex 1 · 15 min  Latch detection — synth buggy code, find the latch warning, fix it, re-synth.
  2. Ex 2+3 · 25 min  Priority encoder — first with if/else, then with casez. Same I/O, different gates.
  3. Ex 5 · 25 min  4-bit ALUcase on opcode: ADD, SUB, AND, OR, XOR. Wire to switches + LEDs.
Stretch (Ex 6): Mixed ALU — arithmetic via case, but an interrupt-style "abort" using if override. See how both compose.

Priority Encoder — Two Styles

if/else style

always @(*) begin
    out = 3'd0;
    if      (in[3]) out = 3'd4;
    else if (in[2]) out = 3'd3;
    else if (in[1]) out = 3'd2;
    else if (in[0]) out = 3'd1;
end

casez style

always @(*) begin
    out = 3'd0;
    casez (in)
        4'b1???: out = 3'd4;
        4'b01??: out = 3'd3;
        4'b001?: out = 3'd2;
        4'b0001: out = 3'd1;
    endcase
end
Both intentionally encode priority. casez makes it explicit with ? don't-cares; if/else hides it in branch order.

4-Bit ALU — Canonical case

module alu4 (
    input  wire [3:0] a, b,
    input  wire [2:0] op,
    output reg  [3:0] y
);
    localparam OP_ADD = 3'd0, OP_SUB = 3'd1,
               OP_AND = 3'd2, OP_OR  = 3'd3, OP_XOR = 3'd4;

    always @(*) begin
        y = 4'd0;                       // default — kills latches
        case (op)
            OP_ADD: y = a + b;
            OP_SUB: y = a - b;
            OP_AND: y = a & b;
            OP_OR:  y = a | b;
            OP_XOR: y = a ^ b;
            default: y = 4'd0;          // belt and suspenders
        endcase
    end
endmodule

Use upper switches for op, lower switches for a/b, LEDs for y.

▸ Phase 4 of 5  ·  ~45 min  ·  Verify · test · harden · PPA

🛡 Fortify

Latch-free, sim-clean, gate-counted

Simulation as Latch Detective

cd labs/week1_day03/ex1_latch_bugs/starter
make sim    # TB outputs FAIL with an X if a latch is present

✓ Two independent checks

  • Sim TB catches X values → tells you which input drives a latch
  • Yosys log catches inference → tells you which signal latched
  • When both agree, the bug is real

❌ Easy to miss

A latch can produce correct outputs if the inputs happen to drive it the right way. Sim + synth catches the latent case before silicon does.

First PPA Compare — yosys stat

# Synthesize the SAME priority encoder three ways
yosys -p "synth_ice40 -top pe_if;   stat" pe_if.v
yosys -p "synth_ice40 -top pe_case; stat" pe_case.v
yosys -p "synth_ice40 -top pe_casez; stat" pe_casez.v

Compare the SB_LUT4 count for each variant. Predict which is cheapest before reading the numbers.

What you should see: the differences are smaller than you'd guess — the synthesizer is good. But the longest-path delay (we measure on Day 10) often differs more than the LUT count.

This is the start of the PPA thread — it runs all the way to Day 14.

Visualize the Difference — yosys show

yosys -p "read_verilog pe_if.v; \
          proc; opt; flatten; \
          show -prefix pe_if -format svg" 

Open pe_if.svg and pe_case.svg side by side on the projector. The if/else version is a visibly longer chain; the case version is a fan-in mux.

The key habit: when synthesis surprises you, look at the netlist. The Yosys schematic almost always explains why.

Hardware Verification — ALU

  • Each opcode visible on LEDs? Sweep op through 0..4 with fixed a, b.
  • SUB carry behavior makes sense? 3 - 5 wraps to 0xE in 4-bit unsigned.
  • Bitwise ops give expected pattern? Set a = 4'b1100, b = 4'b1010, check AND/OR/XOR.
  • Undefined opcode = default value? Set op = 3'd7 — should be all-zeros, never X.
The ALU is the smallest design that feels like a processor. Later you'll wrap one in an FSM (Day 7) to sequence operations.

▸ Phase 5 of 5  ·  ~10 min

🔗 Transfer

Carry it to Sagrada this afternoon

At Sagrada Família This Afternoon

  • Look up at the columns. Each one is a multi-way branch — Gaudí's physical case statement, splitting load into sub-loads.
  • Look for the catenary models. They're upside-down hanging chains — load goes in, shape comes out, no memory of when. That's combinational.
  • Look at the stained glass. Light in → colored light out. The window has no clock.
  • Bring a photo. One picture per student — we open Day 4 with them on the projector.
Gaudí thought in simultaneous structures. So does HDL. The trip is the textbook.

Thursday: Montserrat · Friday → Clocked Logic

Thu 5/28 is the Montserrat excursion — no class. Day 4 is Friday 5/29.

Day 4 introduces time: always @(posedge clk), flip-flops, counters, a real blinker from the 25 MHz crystal. This is where you start using <= instead of = — the rule is simple; the reasoning is deep.

Before Friday

  • 📺 Watch the Day 4 video (~50 min) — Thu evening, after Montserrat
  • 📝 Day 4 pre-class quiz
  • 🎨 Bring a Sagrada photo — we open Day 4 with them

🔗 End of Day 3 · Sagrada Família 17:00

No clocks. Just decisions.

You can now build any combinational circuit, detect a latch, and read a Yosys schematic.
After Montserrat, Friday we add a clock — and everything changes.

CRAFT