Day 58: always blocks: combinational vs sequential, and inferred latches
Combinational vs sequential always blocks
The always block's sensitivity list determines what hardware it becomes. always @(posedge clk) → sequential (flip-flops), triggered on the clock edge. always @(*) → combinational, re-evaluated whenever any input changes. Getting this right is most of writing correct RTL; getting it wrong is how you accidentally infer a latch.
// sequential: the state register (flip-flops)
always @(posedge clk or negedge rst_n)
if (!rst_n) state <= IDLE;
else state <= next_state;
// combinational: compute next_state and outputs
always @(*) begin
next_state = state; // default: hold (prevents a latch!)
out = 1'b0; // default every output
case (state)
IDLE: if (start) next_state = RUN;
RUN: begin out = 1'b1; if (done) next_state = IDLE; end
endcase
endThe two-block FSM pattern is your friend
Splitting an FSM into a *sequential* block (just state <= next_state) and a *combinational* block (compute next_state/outputs) is the industry-standard, latch-safe pattern. The combinational block assigns defaults first, so every path assigns every signal — no latch. You'll use this shape for every controller in ChipX.
Key terms
- Sensitivity list
- What triggers an always block: a clock edge (sequential) or any input change, @(*) (combinational).
- Sequential always block
- Clock-edge-triggered; infers flip-flops. Use non-blocking assignments.
- Combinational always block
- Re-evaluated on any input change (@(*)); infers pure logic. Use blocking assignments.
- Two-block FSM
- One sequential block for the state register, one combinational block for next-state/output logic.
Before moving on, you should be able to
What kind of hardware does always @(posedge clk) infer?