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.

The chain, and the netlist of the unique rewriteOne request per cycle, except cycle 5, where req[1] and req[2] are both 1. Each data word has a single bit set: d0 = 0001, d1 = 0010, d2 = 0100, d3 = 1000.
0123456clkreq0001001001001000000001100001y (chain)0001001001001000000000100001y (unique, gates)0001001001001000000001100001
  1. Cycle 0

    One request: both circuits pass d0. Every one-request cycle looks like this, and so does the cycle with no request.

  2. Cycle 5

    Two requests. The chain tests req[1] before req[2], so it passes d1, 0010, as the brief says.

  3. Cycle 5

    The unique netlist 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.

What Yosys 0.70 built from each formThe same ports, two netlists. The chain tests the requests in order through eight 2:1 muxes and thirteen gates. The unique case ANDs each data word with its request and ORs the four results.
What Yosys 0.70 built from each formThe same ports, two netlists. The chain tests the requests in order through eight 2:1 muxes and thirteen gates. The unique case ANDs each data word with its request and ORs the four results. manager req[3:0] requests manager d0 .. d3 four 4-bit words bus priority chain 21 cells, 5 levels bus AND-OR 28 cells, 3 levels completer y (if chain) lowest request wins completer y (unique case) all requests mixed tested in orderdataone AND per worddata
  1. 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.

  2. 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.

  3. Two requests

    With req = 0110 the chain passes d1, 0010. The AND-OR passes d1 and d2 together: 0110. unique told the synthesizer this input never happens.

    Follow the calls, values and waits
    1. data req = 0110: req[3:0] → priority chain
    2. data req = 0110: req[3:0] → AND-OR
    3. data 0010: priority chain → y (if chain)
    4. 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.

FormWhat it saysVerilator 5.052 with req = 0110Cells / levels
if ... else if chainfirst true condition winsruns the req[1] branch; no message21 / 5
case (1'b1)first matching arm winsruns the req[1] arm; no message21 / 5
priority case (1'b1)first matching arm wins, and some arm (or the default) matchesruns the req[1] arm; no message21 / 5
unique case (1'b1)at most one arm matches; without a default, exactly onestops: Assertion failed in tb.dut: unique case, but multiple matches found for '1'h1'28 / 3
unique0 case (1'b1)at most one arm matchesstops: unique0 case, but multiple matches found28 / 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

  1. Ask the brief

    If two conditions can be true together, the brief must say who wins. Then the order is the meaning: write an if chain or priority case.

  2. Claim only what is true

    Write unique case only 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.

  3. 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.

Design brief
PortDirectionWidthMeaning
reqinput[3:0]one request per word
d0, d1, d2, d3input[3:0]the four words
youtput[3:0]y is the word whose request is 1; with no request, y is 0000
Latency
combinational: y follows req in 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
  1. What is the difference between unique and priority?

    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.

  2. Does unique case make 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.

  3. 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.

RTLVerilator · simulator service · Lint: Verilator service · Synthesize: Yosys service
Edit the design, then Run, Lint or Synthesize
Expected: 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 wrong

Original 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

Predict the outputThe arms of this case (1'b1) test req[3] first. Two requests are set: req = 0110. What does the program print?
module tb;
  logic [3:0] req;
  logic [1:0] who;
  always_comb begin
    who = 2'd0;
    case (1'b1)
      req[3]: who = 2'd3;
      req[2]: who = 2'd2;
      req[1]: who = 2'd1;
    endcase
  end
  initial begin
    req = 4'b0110; #1;
    $display("who=%0d", who);
    $finish(0);
  end
endmodule
Spot the bugEach option is the body of an always_comb block. The bench sets req = 01, then 00, and expects y = 0 with no request. Which body fails?
ConceptWhat does unique on a case statement guarantee about the netlist?
ConceptWhen is a priority if chain the right structure?
Read the waveformIn cycle 5 two requests are set. Which value does the if chain pass to y?
The selector as an if chain
0123456req0001001001001000000001100001y00010010010010000000?0001

d0 = 0001, d1 = 0010, d2 = 0100, d3 = 1000. The value of y in cycle 5 is hidden.

Remember

  • An if ... else if chain 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; unique claims at most one arm matches, and priority claims some arm matches while keeping the order.
  • Verilator checks unique and 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.