Skip to content

Pick

pick is a multi-way gated selection that — unlike a cif/cselif chain — does not chain its branches. Every pif whose raw condition is high runs; keeping the conditions mutually exclusive is your responsibility. The optional pidef default runs only when no pif matched.

BlockRole
pick()container; holds only pif/pidef branches
pif(cond)branch, gated on its raw condition — no “and not the previous ones”
pidef()optional default; fires when every pif condition is low (at most one per pick)

Adapted from tc22_pick — a three-way select into a shared register:

class tc22_pick(Module):
@init
def com_declare(self):
self.r = reg (8, "r") # shared output
self.sel0 = wire(1, "sel0")
self.sel1 = wire(1, "sel1")
self.v_a = val (8, 11, "v_a")
self.v_b = val (8, 22, "v_b")
self.v_def = val (8, 33, "v_def")
self.sel0.mark_input("sel0")
self.sel1.mark_input("sel1")
self.r.mark_output("my_r")
@flow
def my_flow(self):
self.r.reset(0)
with pick():
with pif(self.sel0):
self.r |= self.v_a # runs whenever sel0 is high
with pif(self.sel1):
self.r |= self.v_b # runs whenever sel1 is high
with pidef():
self.r |= self.v_def # runs when neither sel is high

With sel0/sel1 kept mutually exclusive this is a clean 3-way mux into r, verified by the tc22 testbench:

  • sel0 = 1, sel1 = 0r == 11
  • sel0 = 0, sel1 = 1r == 22
  • sel0 = 0, sel1 = 0r == 33 (default)

The default’s gate is built for you: in the emitted Verilog the pidef branch launches on the AND of the inverted pif conditions:

assign EXPR_pick_not_0 = ~WIRE_sel0[0:0];
assign EXPR_pick_not_1 = ~WIRE_sel1[0:0];
assign EXPR_node_logic_expr = EXPR_pick_not_0[0:0] & EXPR_pick_not_1[0:0]; // pidef gate

Each pif is gated on its raw condition — no chaining — and pidef fires only when every pif is low:

flowchart TB
    P["pick entry"] --> S0{"sel0"}
    P --> S1{"sel1"}
    P --> D["pidef gate = not sel0 and not sel1"]
    S0 -->|"high"| RA["r = v_a (11)"]
    S1 -->|"high"| RB["r = v_b (22)"]
    D -->|"both low"| RD["r = v_def (33)"]

pif and pidef are complex blocks: they cannot hold assignments directly, so an inner skeleton block is opened for you automatically (a seq in a sequential context, a parallel skeleton in a parallel one). You can also open one explicitly, which reads well when the branch does several steps:

with pick():
with pif(a < b):
with seq():
r |= a
with pif(a > b):
with seq():
r |= b
with pidef():
with seq():
r |= a + b

Conditions are ordinary 1-bit SignalRefs — input wires, comparisons like a < b, or bit-slices.

pick gates each pif on its raw condition. If two conditions are high in the same cycle, both branches run. When they write the same register, the winner is decided by write priority, not by branch order — so overlapping conditions are almost always a bug. If you want priority-ordered arms, use a zif/zelif chain or a cif/cselif chain instead (Conditionals).

A pick may contain at most one pidef. A second one is rejected when the model is built (build_flow raises — covered by test_pick_rejects_two_defaults):

with pick():
with pif(a < b):
r |= a
with pidef():
r |= b
with pidef(): # error: second default — build_flow fails
r |= a

Unlike par_auto, the exit of a pick is not auto-synchronized: whichever branch fires drives the exit signal, and Kathryn prints a warning to stderr at build time to remind you.

  • Independent, one-hot style dispatch: each branch owns its own enable signal (command decoders, request handlers).
  • You want the default arm derived automatically from “none of the enables”.
  • You do not want the priority chaining that cif/zif chains impose.