casez, and Why Not casex
Decoding a 4-bit opcode where the top two bits pick the class and the bottom two do not matter yet is awkward with plain case, which wants exact values. casez and casex both add don’t-care bits. One of them is an everyday tool. The other has been quietly breaking chips for decades, and they differ by one letter.
casez
In a casez item, ? or z means “either 0 or 1”:
always @(*) begin
casez (req)
4'b1???: grant = 2'd3;
4'b01??: grant = 2'd2;
4'b001?: grant = 2'd1;
4'b0001: grant = 2'd0;
default: grant = 2'd0;
endcase
end
If req[3] is set the first arm matches whatever the other bits are; this is a priority encoder written as a table. It synthesises cleanly and it is everywhere in decoders. Use it freely.
casex
casex does the same and also treats x as a wildcard. That sounds like a small extension and it is not. Before reset has driven a signal, or after any uninitialised path, that signal is x in simulation. Under casex an x in the expression matches the first arm whose pattern has a wildcard there, and the block picks an arm as if the input were known. The simulation runs clean, the waveform looks plausible, and the x that case or casez would have shown you on the output has been swallowed.
In silicon there is no x; every net is 0 or 1. So the hardware takes whichever arm the real value selects, which is not the one the simulation showed, and the testbench that passed never exercised that path. That is the shape of the bugs that appear in the lab and not in regression.
? and z |
x |
||
|---|---|---|---|
case |
exact | exact | always fine |
casez |
wildcard | exact | use for wildcards |
casex |
wildcard | wildcard | do not write |
Never write casex in new code. Style guides forbid it and linters flag it.
A decoder
always @(*) begin
casez (instr[6:0])
7'b0110011: instr_type = R_TYPE;
7'b0010011: instr_type = I_TYPE;
7'b0000011: instr_type = LOAD;
7'b0100011: instr_type = STORE;
7'b1100011: instr_type = BRANCH;
default: instr_type = ILLEGAL;
endcase
end
This one happens not to need a ?, since the opcode field decides the type on its own. The moment sub-formats are distinguished by some bits and not others, ? in a casez is the tool, and it stays honest through reset because an x is never a match.
Practice Exercise
Convert this to casez, and describe a reset-time scenario where the casex version reports a wrong grant that casez would not.
always @(*) begin
casex (req)
4'b1xxx: grant = 2'd3;
4'b01xx: grant = 2'd2;
4'b001x: grant = 2'd1;
4'b0001: grant = 2'd0;
default: grant = 2'd0;
endcase
end
Solution
always @(*) begin
casez (req)
4'b1???: grant = 2'd3;
4'b01??: grant = 2'd2;
4'b001?: grant = 2'd1;
4'b0001: grant = 2'd0;
default: grant = 2'd0;
endcase
end
Swap the keyword and the x fillers for ?. In the first nanoseconds of a simulation, before the testbench drives it, req is 4'bxxxx. Under casex the first arm matches, since every bit is x and x is a wildcard, and grant reads 3 with nothing requested. Under casez no arm matches an all-x input, the default fires, and grant is 0.
There is one more thing people write next to a case: the full_case and parallel_case pragmas, which tell the synthesiser things the simulator cannot see. The next article is about them and why current style guides say to leave them out.