A reviewer should learn the signal from its name. Direction, polarity, and role belong in the suffix.
Suffix | Meaning | Example |
|---|---|---|
| Input or output port |
|
| Active-low input: |
|
| Registered state, next-state decode |
|
| Clock and reset take the same |
|
These are the port suffixes from the port-list lesson. In a block with two clocks, put the domain before the suffix (for example clk_fast_i), because a bare clk forces every reader to guess.
Each state element group gets one always_ff per clock and reset domain. Merging two domains into one process hides which edge updates what.
For this FSM, the house style keeps reset/register updates in the sequential process and decisions in combinational decode. A small counter can be clearer in one process, as in the inference lesson. Neither process count alone changes the intended hardware; reset priority, enable/hold behavior and complete combinational assignments determine the function.
The always_comb block computes next state and outputs from current state and inputs. The always_ff block moves next state to current state on the edge.
Every output gets a default value before the case. Then no input combination can leave an output unassigned.
The two versions below have the same function: a request in IDLE is granted in the same cycle and the machine spends one cycle in BUSY. The first is legal and short, but a reviewer has to decode it.
// Legal, same function, hard to review
module fsm_hard (
input logic clk, rst, r,
output logic g, b
);
logic s;
always_ff @(posedge clk or negedge rst)
if (!rst) s <= 1'b0;
else s <= s ? 1'b0 : r;
assign g = !s & r;
assign b = s;
endmodule// snippet: the house-style core of style_fsm (complete module under Testbench)
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) state_q <= IDLE;
else state_q <= state_d;
end
always_comb begin
state_d = state_q; // defaults first: no latch, no missing path
grant_o = 1'b0;
busy_o = 1'b0;
case (state_q)
IDLE: if (req_i) begin state_d = BUSY; grant_o = 1'b1; end
BUSY: begin state_d = IDLE; busy_o = 1'b1; end
default: state_d = IDLE;
endcase
endEach question a reviewer asks of the first version is answered by a name or a line in the second.
Reviewer question | fsm_hard | style_fsm |
|---|---|---|
Is reset active low? | Only the |
|
Which signal is state, which is input? |
|
|
What does state 1 mean? | A bare | Named |
Where is the next state decided? | Inside a ternary in the clocked block | In |
No. Compatible clock/reset event controls can share a process. Grouping by reset and enable intent often makes review easier, but a split FSM and a one-process counter are contextual style choices, not different hardware solely because of process count.
case. Which bug class disappears?Inferred latches. Every path assigns every output, so no output ever needs to hold state.
The full FSM in the house style, plus the self-checking testbench that steps a reference model alongside it.
module style_fsm (
input logic clk_i,
input logic rst_ni,
input logic req_i,
output logic grant_o,
output logic busy_o
);
localparam logic IDLE = 1'b0;
localparam logic BUSY = 1'b1;
logic state_q, state_d;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni)
state_q <= IDLE;
else
state_q <= state_d;
end
always_comb begin
state_d = state_q;
grant_o = 1'b0;
busy_o = 1'b0;
case (state_q)
IDLE: if (req_i) begin
state_d = BUSY;
grant_o = 1'b1;
end
BUSY: begin
state_d = IDLE;
busy_o = 1'b1;
end
default: state_d = IDLE;
endcase
end
endmodule
Testbench run with Icarus Verilog 13.0 (2026-10-16): prints PASS. It steps a reference model alongside the FSM. The benches in this topic are finite digital checks, not lint or RDC results.