Default every output and `next` at the top of the combinational block.
Give the `case` an explicit recovery default for its RTL illegal-state behavior; verify mapped recovery separately when required.
Reset the state register to a known, legal state; reset anything the outputs depend on.
One driver per signal. Never assign the same variable from two processes.
Partition by responsibility. Controller bookkeeping may include counters; GCD operand arithmetic belongs to its datapath.
Without defaults, some paths leave outputs unassigned. Synthesis then infers a latch, as the faulty module below shows.
// yosys: latch
module fsm_bad (
input logic [1:0] state,
input logic go,
output logic [1:0] next,
output logic busy
);
always_comb begin
case (state)
2'd0: begin busy = 1'b0; if (go) next = 2'd1; end // next not assigned when go == 0
2'd1: begin busy = 1'b1; next = 2'd2; end
2'd2: begin busy = 1'b1; next = 2'd0; end
endcase // state 3, and busy/next unassigned there
end
endmoduleERROR: Latch inferred for signal `\fsm_bad.\next' from always_comb processYosys stops at the first error, so only next is reported; busy has the same problem in state 3.
// yosys: ok
module fsm_good (
input logic [1:0] state,
input logic go,
output logic [1:0] next,
output logic busy
);
always_comb begin
next = state; busy = 1'b0; // defaults: every path now assigns both
case (state)
2'd0: if (go) next = 2'd1;
2'd1: begin busy = 1'b1; next = 2'd2; end
2'd2: begin busy = 1'b1; next = 2'd0; end
default: next = 2'd0; // illegal state: recover
endcase
end
endmoduleWith a 2-bit state and 3 legal values, one encoding is unused. A bit flip or a bad reset can land there.
The assigned default defines RTL next-state recovery to IDLE. FSM extraction, recoding and unreachable-state optimization can change behavior outside reset-reachable operation; it is not physical fault recovery. See illegal states and recovery for fault models, containment and mapped-state checks.
Case form | No explicit item matches | Overlapping explicit items |
|---|---|---|
unique case with assigned default | Executes default; no no-match violation | Still violates uniqueness |
unique case without default | No-match violation; synthesis may assume coverage | Violates uniqueness |
Pre-case assignment, unique case without default | Assignment defines RTL value but does not suppress no-match violation | Violates uniqueness |
Adding unique does not turn an explicitly assigned case-default into a don't-care. Omitting the case-default makes a coverage promise to synthesis even if a pre-case assignment keeps simulation deterministic. Diagnose illegal states explicitly when a default is present; do not depend on a no-match warning. See the language foundations and the DVCon paper on SystemVerilog-2009 priority, unique and unique0.
fsm_bad, name two different paths on which a signal is left unassigned.(1) State 0 with go == 0: next is not assigned. (2) State 3 (not covered by the case): both next and busy are not assigned. In state 0, busy is always assigned; only next depends on go.