Video 1 of 4 · ~12 minutes
Dr. Mike Borowczak · Electrical & Computer Engineering · CECS · UCF
Procedural combinational blocks are where the decision logic of every digital system lives. ALU opcode decoders, address decoders, instruction decoders, bus arbiters — all always @(*) blocks with case statements or nested ifs. Read any CPU's RTL and half of it looks like today's material.
Day 3 ALU. Day 7 FSM output logic (Block 3). Day 11 UART state-to-output. Day 14 assertion fire-logic. Every remaining day builds on always @(*). Get the syntax clean now and you're set for the rest of the course.
always Is Not a Loop“always sounds like while(true). The block runs forever, executing its statements top-to-bottom, over and over.”
An always @(*) block describes a piece of combinational logic that re-evaluates whenever any input changes. The statements inside aren't “executed” — they're analyzed once by the synthesizer, producing a fixed chunk of gates that exist simultaneously on the chip.
for or while loops inside always @(*) doesn't mean runtime iteration — it means the synthesizer unrolls the loop and builds parallel copies of the loop body as hardware.
assign works great for simple expressions:
assign y = (sel == 2'b00) ? a :
(sel == 2'b01) ? b :
(sel == 2'b10) ? c : d; // 4:1 mux — nested ternary gets unreadable fast
But multi-way decisions become unreadable. The same logic with always @(*):
always @(*) begin
case (sel)
2'b00: y = a;
2'b01: y = b;
2'b10: y = c;
2'b11: y = d;
endcase
end
always @(*) gives us if/else, case, and procedural constructs for complex combinational logic.@(*)// Manual list — DON'T do this
always @(a or b or sel)
if (sel) y = a; else y = b;
// Wildcard — ALWAYS do this for combinational
always @(*)
if (sel) y = a; else y = b;
@(*) — no exceptions.
reg [3:0] r_out; // declared reg (assigned in always)
always @(*) begin // wildcard sensitivity
r_out = 4'b0000; // ← DEFAULT at top — prevents latches
if (sel)
r_out = i_data;
end
reg declaration, (2) always @(*), (3) default assignment first, (4) begin/end for multi-line. The default at top is what keeps you out of latch territory — we prove why in Video 3.
// What's wrong here?
always @(posedge sel) begin
y = a & b;
end
always @(a, b)
y = a | b;
always @(*) begin
if (sel) y = a;
// where's the else?
end
posedge sel makes this sequential — infers a flip-flop on a level signal, disaster for combinational intent.
(2) Manual list is fine here (both sigs listed), but use @(*) for consistency.
(3) Missing else → “hold value when sel=0” → inferred latch. We dive into this in Video 3.
reg [3:0] r_result;
always @(*) begin
r_result = a;
if (sel_b) r_result = b;
if (sel_c) r_result = c;
if (sel_d) r_result = d;
end
Predict:
sel_b = sel_c = 1 and others 0, what's the output?c — the later if overrides the earlier one. (3) Priority — later statements win when multiple are true, in reverse of if/else if. This is exactly why we prefer case or if/else if for clarity.
~4 minutes
▸ COMMANDS
cd labs/week1_day03/ex_mux_compare/
make sim # test both versions
make wave # GTKWave
# Both modules: identical
diff <(yosys -qp "read_verilog mux_assign.v; \
synth_ice40; stat" 2>&1) \
<(yosys -qp "read_verilog mux_always.v; \
synth_ice40; stat" 2>&1)
▸ EXPECTED OUTPUT
PASS: all 8 mux tests
=== 8 passed, 0 failed ===
# diff output:
(empty — identical LUT counts)
Both: 4 SB_LUT4, 0 SB_DFF
▸ KEY OBSERVATION
Two completely different code styles synthesize to exactly the same hardware. Use whichever is clearer for the problem. assign for one-liners, always @(*) for multi-way decisions.
A 4-bit 4:1 mux written with always @(*) and case:
$ yosys -p "read_verilog mux4_always.v; synth_ice40 -top mux4; stat" -q
=== mux4 ===
Number of wires: 5
Number of cells: 4
SB_DFF 0 ← still no flops (reg doesn't mean flop!)
SB_LUT4 4 ← one LUT per output bit
reg [3:0] r_y + always @(*) + case → zero flops. The reg keyword is syntactic: whether it becomes a flip-flop depends entirely on the sensitivity list. No posedge clk, no flops.
Ask AI: “Rewrite this assign-based mux as an always @(*) block. Will it synthesize to the same hardware?”
TASK
Ask AI to translate assign→always. Ask about hardware.
BEFORE
Predict: same hardware, different syntax. Good AI says “identical LUT count.”
AFTER
Some models incorrectly say always blocks “add latency” or “need a clock” — false.
TAKEAWAY
If AI says always adds latency, it's confused between @(*) and @(posedge).
① always @(*) for combinational — never manual sensitivity lists.
② reg in always @(*) is still combinational.
③ Default assignment at the top prevents latches.
④ Always use begin/end for multi-line blocks.
🔗 Transfer
Video 2 of 4 · ~14 minutes
▸ WHY THIS MATTERS NEXT
Now that you have the container (always @(*)), we need the decision constructs that go inside: if/else, case, and casez. Here's the subtlety: if/else synthesizes to a priority chain, case to a parallel mux. Same abstract logic, different gates, different timing. Video 2 shows you when to use which.