Case statements: priority, parallel, unique
Tell a priority chain from a parallel decode, say what unique promises and who checks it, and measure both forms before you choose.
- Lesson
- R1.5
- Time
- 25 min
- Runs on
- Verilator
- Check
- 5 questions
A selector passed one of four data words to its output, written as an if chain over four request lines. A reviewer called the chain too deep and asked for unique case "to make it parallel". The rewrite synthesized to 28 cells and 3 levels of logic, against the chain's 21 cells and 5 levels. In the one cycle where two requests arrived, the new gates output two data words ORed together, while the RTL simulation showed one.
- Cycle 0
One request: both circuits pass d0. Every one-request cycle looks like this, and so does the cycle with no request.
- Cycle 5
Two requests. The chain tests req[1] before req[2], so it passes d1, 0010, as the brief says.
- Cycle 5
The
uniquenetlist ANDs every word with its request and ORs the results: 0010 | 0100 = 0110. The RTL simulation of the same code shows 0010.
y (chain) is the lesson's bench on Verilator 5.052. y (unique, gates) is the netlist Yosys 0.70 built from the unique case, simulated with the same inputs.
Two shapes of decision
An if ... else if chain says: test these conditions in order, and the first true one wins. That is a priority structure. Each test matters only when every earlier test failed, so the logic is a chain of 2:1 multiplexers (muxes) in the order of the tests.
A case on a value says something else: here are the values, and the expression equals one of them. When the arms are different constants, they cannot overlap. The synthesizer can see that, so it may build a parallel decode: each arm's condition picks its data at once.
case (1'b1) with request bits as arms looks like the second kind. Its arms are variables, though, and two of them can be 1 together. The language runs the first matching arm, so this is the priority chain again. Measured on this flow, the chain and the plain case (1'b1) give the same counts: 21 cells, 5 levels of logic.
- The chain
req[0] is tested first, then req[1], and so on. A word passes only when every lower request is 0. Five levels of logic deep.
- The AND-OR
Every word is gated by its own request, all at once, and the four results are ORed. Three levels deep, but seven cells more.
- Two requests
With req = 0110 the chain passes d1, 0010. The AND-OR passes d1 and d2 together: 0110.
uniquetold the synthesizer this input never happens.Follow the calls, values and waits
- data req = 0110: req[3:0] → priority chain
- data req = 0110: req[3:0] → AND-OR
- data 0010: priority chain → y (if chain)
- data 0110: AND-OR → y (unique case)
Read the diagram as text
- req[3:0]: requests
- d0 .. d3: four 4-bit words
- priority chain: 21 cells, 5 levels
- AND-OR: 28 cells, 3 levels
- y (if chain): lowest request wins
- y (unique case): all requests mixed
- req[3:0] → priority chain: tested in order
- d0 .. d3 → priority chain: data
- priority chain → y (if chain)
- req[3:0] → AND-OR: one AND per word
- d0 .. d3 → AND-OR: data
- AND-OR → y (unique case)
Bigger and shallower: the parallel form pays cells to save levels. Neither shape is smaller by rule; measure.
What unique promises, and who checks it
unique case adds two promises to a case statement: at most one arm matches, and when there is no default, one arm always matches. The simulator checks the promises each time the statement runs. The synthesizer may assume them, which is how it may drop the order between the arms.
| Form | What it says | Verilator 5.052 with req = 0110 | Cells / levels |
|---|---|---|---|
if ... else if chain | first true condition wins | runs the req[1] branch; no message | 21 / 5 |
case (1'b1) | first matching arm wins | runs the req[1] arm; no message | 21 / 5 |
priority case (1'b1) | first matching arm wins, and some arm (or the default) matches | runs the req[1] arm; no message | 21 / 5 |
unique case (1'b1) | at most one arm matches; without a default, exactly one | stops: Assertion failed in tb.dut: unique case, but multiple matches found for '1'h1' | 28 / 3 |
unique0 case (1'b1) | at most one arm matches | stops: unique0 case, but multiple matches found | 28 / 3 |
Measured with the lab's ports, each form with a default arm of 0000. Cells and levels from Yosys 0.70 on this flow.
So a unique violation shows up only in simulation, and only if the stimulus produces it. It is a check on your claim. When the claim is false, the gates and the RTL simulation disagree: the trace above shows 0110 from the netlist where the simulation shows 0010.
Keep the default arm in every form. unique does not assign a value to a path where no arm matches, and a case (1'b1) with no default holds y when no request is set. That is the latch of R1.3.
Choose by meaning, then measure
Ask the brief
If two conditions can be true together, the brief must say who wins. Then the order is the meaning: write an
ifchain orpriority case.Claim only what is true
Write
unique caseonly when the inputs cannot overlap, for example a one-hot state register. Make the bench try the overlap anyway, so a false claim stops the run.Measure both
Synthesize each form and read cells and levels. On this flow the parallel form was 7 cells bigger and 2 levels shallower. Another design can come out the other way.
| Port | Direction | Width | Meaning |
|---|---|---|---|
| req | input | [3:0] | one request per word |
| d0, d1, d2, d3 | input | [3:0] | the four words |
| y | output | [3:0] | y is the word whose request is 1; with no request, y is 0000 |
- Latency
- combinational:
yfollowsreqin the same cycle - Parameters
- Memory: none: no latch, no flop
- Corner cases
- no request; two requests in one cycle. Priority: two or more requests can arrive together; the lowest index wins
Run the lab: seven cycles, 0 wrong. Synthesize: 21 cells, 5 levels of logic on this flow. Now rewrite the chain as unique case (1'b1) with one arm per request and a default of 0000. Run: Verilator stops at the two-request cycle with the assertion message from the table. Synthesize: 28 cells, 3 levels. Last, change unique to priority. The run is clean again, and the count is back to 21 cells and 5 levels.
Asked in interviews
What is the difference between
uniqueandpriority?Both say that some arm will match. A unique case also says that at most one arm matches, so the arms may be evaluated in parallel, and the simulator reports overlaps. A priority case keeps the order: the first matching arm wins, and overlaps are legal.
Does
unique casemake the logic smaller?Not by rule. It lets the synthesizer ignore the order of the arms. In this lesson that gave an AND-OR of 28 cells and 3 levels, against 21 cells and 5 levels for the chain: bigger and shallower. Measure every time.
When do you want a priority encoder?
When two requests can be active together and the spec says which one wins, as in an interrupt controller or a fixed-priority arbiter. The order is then part of the function, and a parallel form would mix the data.
Try it
Each one-request line passes the matching word, and the no-request line gives 0000. At edge 5 two requests are set, and the chain passes d1, 0010, because req[1] is tested before req[2]. That is what the brief asks for, so no cycle is wrong.
Rewrite as unique case (1'b1) and Verilator prints %Error: main.sv:9: Assertion failed in tb.dut: unique case, but multiple matches found for '1'h1' at the two-request cycle and stops (main.sv:9 is the line of the case keyword). Write priority case (1'b1) instead: the seven lines come back, and Synthesize reports 21 cells and 5 levels, as for the chain.
Synthesize the design to measure its cells and inspect the mapped gates.
e0 req=0001 y=0001 e1 req=0010 y=0010 e2 req=0100 y=0100 e3 req=1000 y=1000 e4 req=0000 y=0000 e5 req=0110 y=0010 e6 req=0001 y=0001 0 cycles wrongOriginal design · 21 cells · 0 flip-flops · 0 latches · 5 logic levels. Synthesis uses Yosys; counts depend on the toolchain.
Read included file: tb.sv
`timescale 1ns/1ps
module tb;
logic clk = 0;
bit done = 0;
initial while (!done) #5 clk = ~clk; // rising edges at 5, 15, 25, ...; stops when done
int edge_n = -1;
always @(posedge clk) edge_n = edge_n + 1;
logic [3:0] req = 0;
logic [3:0] d0 = 4'b0001, d1 = 4'b0010, d2 = 4'b0100, d3 = 4'b1000; // one bit each, so a mix shows
logic [3:0] y, want;
int wrong = 0;
sel4 dut (.req(req), .d0(d0), .d1(d1), .d2(d2), .d3(d3), .y(y));
// Advance to the next rising edge, wait for the design to settle, then print one line.
task automatic tick();
@(posedge clk); #1;
if (req[0]) want = d0; // the brief: the lowest request wins
else if (req[1]) want = d1;
else if (req[2]) want = d2;
else if (req[3]) want = d3;
else want = 4'b0000;
if (y !== want) wrong++;
$display("e%0d req=%b y=%b%s", edge_n, req, y, y === want ? "" : " wrong");
endtask
initial begin
req = 4'b0001; tick();
req = 4'b0010; tick();
req = 4'b0100; tick();
req = 4'b1000; tick();
req = 4'b0000; tick();
req = 4'b0110; tick(); // two requests in one cycle
req = 4'b0001; tick();
$display("%0d cycles wrong", wrong);
done = 1; // the clock stops; the run ends
end
endmodule
Check yourself
Remember
- An
if ... else ifchain is a priority structure: the first true condition wins, and synthesis builds a chain of muxes when the conditions can overlap. case (1'b1)over request bits is the same chain;uniqueclaims at most one arm matches, andpriorityclaims some arm matches while keeping the order.- Verilator checks
uniqueand stops at a violation; Icarus 12 does not check it. The synthesizer may use the claim, and a false claim makes the gates disagree with the simulation. - On this flow the parallel form was 28 cells and 3 levels against the chain's 21 and 5: write what the brief means, then measure.