A combinational block must drive each output on all paths. A skipped path holds the old value, which synthesizes a latch.
Legacy pragmas full_case and parallel_case can hide such bugs when their assumptions are wrong. The rules below express the same intent in code that simulation and synthesis both honor.
// yosys: latch
module latch_bad (
input logic en,
input logic [7:0] d,
output logic [7:0] q
);
always_comb begin
if (en) q = d; // missing else: q holds when en is low
end
endmoduleThe fix assigns a default before the branch. It carries // yosys: ok, so the build fails if a latch ever appears there.
// yosys: ok
module latch_fixed (
input logic en,
input logic [7:0] d,
output logic [7:0] q
);
always_comb begin
q = '0; // default first: every path assigns q
if (en) q = d;
end
endmoduleA case follows the same rule: a default item covers every selector value not listed, so no path is left unassigned.
// yosys: ok
module mux_plain (
input logic [1:0] sel,
input logic [7:0] a, b, c, d,
output logic [7:0] y
);
always_comb begin
case (sel)
2'd0: y = a;
2'd1: y = b;
2'd2: y = c;
default: y = d;
endcase
end
endmodulePlain case with a default is the safe choice. Every selector value maps somewhere, so no latch is inferred, and an X or Z select also lands in the default branch, because case compares with === and no branch label matches.
Form | Meaning | Rule |
|---|---|---|
Plain | Every value maps somewhere | Default choice for decoders and muxes |
| Requires exactly one matching item: no-match and multiple-match checks | Prove legal-input completeness and exclusivity; an unmatched RTL path with no prior assignment holds y |
| Legal: default handles no match; multiple matching explicit items still violate uniqueness | Use when a fallback is part of the contract and overlap checking remains useful |
| Requires at least one match unless default exists; first matching item wins | Plain case also chooses the first match. Priority adds completeness intent, not a new execution order |
module mux_unique (
input logic [1:0] sel,
input logic [7:0] a, b, c, d,
output logic [7:0] y
);
always_comb begin
unique case (sel) // no default: the branches cover all four values
2'd0: y = a;
2'd1: y = b;
2'd2: y = c;
2'd3: y = d;
endcase
end
endmoduleThese pragmas tell the tool that all cases are covered and none overlap. Simulation still evaluates every branch, so a wrong pragma makes synthesis disagree with simulation.
Simulation executes the first matching item for plain, unique and priority case. Without a match or default, no assignment occurs: absent a preceding assignment, y retains its previous four-state RTL value. A preceding default assignment instead supplies that value. Unique/priority add match-count diagnostics (tool support varies) and synthesis assumptions about legal inputs; behavior outside those assumptions can differ in a synthesized netlist. A default removes the no-match check, not unique's overlap check.
// snippet: legacy pragmas that hide missing cases from the tool; do not use
// case (sel) // synthesis full_case parallel_case
// 2'd0: y = a;
// 2'd1: y = b;
// endcasecase covers three of four selector values and has no default. What does synthesis build for the missing value?A latch. The output must hold its old value, so the tool infers storage. Assign a default first or add the missing branch.
priority case add to plain case?Both execute the first matching item. Priority additionally declares that some item must match unless default is provided; it does not prohibit overlaps. Use that completeness contract only when justified, and treat synthesis assumptions separately from RTL fallback behavior.
The finite bench checks all four binary selectors and 16 selected data patterns, then X/Z selectors in a four-state simulator. It checks plain/default and legal unique/default fallback, and bare unique's retained previous value separately; warning support is tool-dependent, but the missing assignment's RTL hold is not. The labels are disjoint: this bench does not exercise an overlap warning. A two-state simulation cannot check these unknown-input obligations.
module mux_plain (
input logic [1:0] sel,
input logic [7:0] a, b, c, d,
output logic [7:0] y
);
always_comb begin
case (sel)
2'd0: y = a;
2'd1: y = b;
2'd2: y = c;
default: y = d;
endcase
end
endmodule
module mux_unique (
input logic [1:0] sel,
input logic [7:0] a, b, c, d,
output logic [7:0] y
);
always_comb begin
unique case (sel)
2'd0: y = a;
2'd1: y = b;
2'd2: y = c;
2'd3: y = d;
endcase
end
endmodule
module mux_unique_default (
input logic [1:0] sel,
input logic [7:0] a, b, c, d,
output logic [7:0] y
);
always_comb begin
unique case (sel)
2'd0: y = a;
2'd1: y = b;
2'd2: y = c;
default: y = d;
endcase
end
endmodule
The bench checks all four binary selectors and 16 selected data patterns, then X and Z selectors in a four-state simulator, so it is simulation evidence. It is not synthesised-netlist equivalence. Warning support for unique and priority is tool-dependent. It does not exercise an overlap warning.