Skip to content
code0.HDL

Selection, case, and the don't-care value

Build combinational selection with the ?: operator, case and functions, give every path a value, and use the don't-care value ? only where no one can observe it.

In this chapter

  • Read a conditional expression, an if and a case as multiplexers and decoders that compute in the same cycle
  • Give every path of an if or case a value and recognise the P0042 warning when one is missing
  • Decide where the don't-care value ? is safe, and replace it the moment it becomes observable
  • Use a valid flag to make an unused payload unobservable by protocol
Concepts in this chapter
  • Selection with conditional expressions and functions
Prerequisite concepts

1. A conditional expression is a multiplexer

condition ? a : b picks a when the Bool condition is True and b otherwise. In hardware it is the two-input multiplexer from the fundamentals: both a and b are computed, and the condition selects which one reaches the output. Both branches must have the same type, so a Bit#(8) on one side and a Bit#(4) on the other is the T0020 error of the last chapter; the operator's form and type rule are in Bool, Bit and pure functions.

Bit#(8) magnitude = negative ? 0 - value : value;

Chaining conditionals selects among more than two values, with the first true condition winning. That order is a priority, and later chapters use it on purpose.

2. Blocks and the if statement

2.1 begin and end

begin and end group several statements where the syntax expects one.

Form

begin
  statement1;
  statement2;
end

2.2 if and else

if chooses between statements according to a Bool condition.

Form

if (condition) statement
if (condition) statement else statement
UInt#(8) result = 0;
if (valid) begin
  result = payload;
end
PartMeaning
UInt#(8) result = 0;a default value for every path
if (valid)a runtime Bool signal
result = payload;on the true path, result names payload

When valid is a runtime signal, both paths become hardware: the if turns into a multiplexer that chooses the value of result. Elaboration does not pick one path in advance. In an action context, a branch may also contain register writes and method calls; those writes then happen only when the condition is true.

Caution. Give every path a value. If a path leaves a variable unassigned, bsc first warns with P0042, Assignment to `result' missing from some paths, and once a rule or method uses that value, elaboration stops with error G0028, `result' uses uninitialized value. No latch is created to remember an older value; the design does not compile at all. Initialize the variable before the if, or assign it in every branch.

2.3 A begin/end block as an expression

A begin/end block in parentheses computes one value with several statements. The last expression is the block's value.

Form

(begin
  statements;
  resultExpression;
end)
Bit#(8) normalized = (begin
  Bit#(8) masked = raw & 8'h7f;
  Bit#(8) result = masked | 8'h80;
  result;
end);

With raw = 8'h35, masked is 8'h35, and normalized is 8'hb5. The names inside are combinational intermediates; they create no registers.

3. case compares one value against several

case compares one expression against several values and runs the first matching item. default catches everything else. Inside a function, each item can return its result; in a plain statement list, each item assigns a variable.

Form

case (expression)
  value1: statement
  value2, value3: statement
  default: statement
endcase
Bit#(8) code;
case (op)
  2'b00: code = 8'h10;
  2'b01: code = 8'h20;
  default: code = 8'hff;
endcase
ItemMeaning
2'b00: ...runs when op equals 2'b00
value2, value3: statementone item may list several values
default: ...runs when no earlier item matched, so every path assigns code

A case whose items are constants is a decoder followed by a multiplexer: the comparisons produce one-hot selects, and the selects choose the result. It is still combinational; no clock is involved, and the result is available in the same cycle as op. The rule of section 2.2 applies here too: a case without default that assigns on only some items leaves the value undefined on the missing items, and using it stops elaboration with the same G0028 error. Pattern matching extends case so that items can test the shape of structured values.

4. The don't-care value says the value does not matter

4.1 What ? means

A lone question mark ? is a value of whatever type the context expects, with no content. It says "any bit pattern is acceptable here", and the compiler may pick whatever value makes the circuit simplest.

Form

?
Bit#(8) unused = ?;

case (code)
  2'd0: return 4'h3;
  // ... every other value handled as well
  default: return ?;   // a branch that is never reached
endcase

? is not Verilog's x, it is not a random number generator, and it does not promise x behaviour: a given build chooses one pattern and keeps it. Use it only where every possible choice gives the same observable behaviour, such as a branch no input can reach or a field of an inactive union member.

Note. The value of ? is unspecified. Never put it where it can reach an output, an interface, or another computation; mkReg(?) does not give a predictable reset value either.

Caution. Initializing visible state with ? leaves part of the design unspecified; simulation and synthesis may then legitimately differ.

4.2 Example: an unreachable default branch

decode in DontCare.bsv maps a 2-bit code to a 4-bit value. The case covers all four values 0 to 3, and its default branch returns ?. No input can reach that item, so what ? becomes cannot change any output.

DontCare.bsv
package DontCare;

// The default branch is unreachable: code is two bits and every value is
// covered above it. ? tells the compiler no particular value is required.
function Bit#(4) decode(Bit#(2) code);
  case (code)
    2'd0: return 4'h3;
    2'd1: return 4'h7;
    2'd2: return 4'hB;
    2'd3: return 4'hF;
    default: return ?;
  endcase
endfunction

(* synthesize *)
module mkTop(Empty);

  rule show;
    $display("decode(0) = %h", decode(2'd0));
    $display("decode(3) = %h", decode(2'd3));
    $finish(0);
  endrule

endmodule

endpackage

Practice this concept

Use this concept in a short BSV problem as your next step.

Predict before you run: how many values can a 2-bit argument take, and can any of them reach default? decode(0) and decode(3) are printed in hexadecimal; read the two matching items.

Check

If a third line printed decode(2'd2), which hexadecimal digit would it show?

Press Run and compare the two lines with the items 2'd0 and 2'd3.

Output lineWhat it shows
decode(0)3, from an explicit branch
decode(3)f, from an explicit branch

All four 2-bit values have explicit branches, so default is unreachable, and whatever value ? becomes cannot affect any output. Adding $display("decode(2) = %h", decode(2'd2)); prints b, the answer you predicted.

5. Change one thing

Delete the 2'd3 item in the lab and run again. The value 3 now falls through to default.

Check

With the 2'd3 item removed, what does the decode(3) line show?

Your answer

The rule that follows from this: ? belongs in branches you can prove unreachable, or in data that a separate flag marks as meaningless. The graded problem is the second case.

6. A flag can make a value unobservable

A struct groups named fields into one value. The problem below uses

typedef struct {
  Bool    valid;
  Bit#(8) payload;
} Decoded deriving (Bits, Eq);

and builds values with Decoded { valid: True, payload: 8'h11 }, reading them back as d.valid and d.payload. The deriving clause gives the type a bit representation and ==; User-defined types has the rest.

The contract of such a pair is a protocol: payload carries information only while valid is True. When valid is False, no correct caller reads payload, so its value is unobservable by agreement, and ? is the idiomatic thing to put there. A constant such as 8'h00 is equally correct; what makes either safe is the flag and the callers that honour it, not the spelling.

Check

decodeCommand(3'd4) is an illegal code. Which result follows the protocol?

Your answer

7. Apply it: mark the illegal codes invalid

In the graded problem, decodeCommand maps codes 0 to 3 to four payloads and its default item returns valid: True for the four illegal codes 4 to 7. Change only that item so every illegal code reports valid: False, and leave the four legal items as they are. The testbench prints the legal code 3 with its payload and the illegal code 4 with its flag only; grading checks all eight codes.

8. Common mistakes

8.1 ? in a reachable branch

// Wrong: with a 3-bit code, values 4 to 7 reach default and return an unspecified value
default: return ?;

// Right: a reachable branch returns a concrete value
default: return 4'h0;

Use ? only when you can show its value is never observed. First check that the explicit branches cover every possible input; with a 3-bit code and four explicit items they do not, and the example of section 4 would print the compiler's choice for four of the eight codes.

9. Summary

ElementMeaning
c ? a : ba two-input multiplexer; branches share one type; a chain is a priority
begin … endgroups statements; in parentheses, computes one value whose names are combinational intermediates
if (c) … else …a runtime condition becomes a multiplexer; in an action, the writes in a branch happen only when it is true
case (x) … default: … endcasecompare against constants; first match wins; a decoder followed by a multiplexer
every path assignsotherwise P0042 (for if) and error G0028 once the value is used; no latch is created
?don't-care: the compiler's choice, safe only where unobservable; do not use it for a required reset value
Type { field: value }, v.fieldbuild and read a struct; a valid flag can make a payload unobservable by protocol

Checkpoint

Submit the Practice problem for grading

Grading runs on the server against hidden tests; the result is recorded in this browser only.

Sources

Licences and notices for the sources this site follows