Indexing
A Karray index can be more than a plain integer. Each [] hop accepts:
| Index | Meaning |
|---|---|
int | static — selects one element of the dimension at build time |
slice (d[0:2]) | static region — for karray-to-karray assignment only |
| signal | dynamic binary — a runtime binary-encoded address |
oh(signal) | dynamic one-hot — one runtime select bit per element |
Static indexing was covered in Karray Basics. This page is about the dynamic forms, where which element is decided by hardware at runtime.
Dynamic binary read
Section titled “Dynamic binary read”Index with a bare signal and Kathryn treats it as a binary-encoded address. Reading a field of the dynamically-selected element builds a balanced 2:1 mux tree over all candidate elements and returns a fresh wire of the field width:
class RfEntry(Karray): valid = kaf(1) data = kaf(8)
class worker(Module): @init def decl(self): self.rf = RfEntry(HwComponentType.REG, (4,), "rf") self.sel = reg(2) # 2-bit binary address for 4 elements self.out = reg(8)
@flow def f(self): with seq(): self.out |= self.rf[self.sel].data # mux over rf[0..3].dataThe read result is an 8-bit combinational wire (the mux output), regardless of
the array’s backing. Every element’s data is wired into the tree; each mux
layer switches on one bit of the address:
always @(*) begin WIRE_REG_rf_E0_data_DMUX[7:0] <= 8'h0; // default if (EXPR_VAL_bsel0_B0_N) begin // address bit 0 low WIRE_REG_rf_E0_data_DMUX[7:0] <= REG_rf_E0_data[7:0]; end else begin WIRE_REG_rf_E0_data_DMUX[7:0] <= REG_rf_E1_data[7:0]; endendEvery element’s field feeds a balanced 2:1 mux tree, one address bit per layer:
flowchart TB
E0["rf[0].data"] --> M0["2:1 mux<br/>addr bit 0"]
E1["rf[1].data"] --> M0
E2["rf[2].data"] --> M1["2:1 mux<br/>addr bit 0"]
E3["rf[3].data"] --> M1
M0 --> T["2:1 mux<br/>addr bit 1"]
M1 --> T
T --> OUT["out (8b wire)"]
One-hot read with oh()
Section titled “One-hot read with oh()”Wrap the selector in oh(...) to mark it as one-hot: one select bit per
element of the dimension, instead of a binary-encoded address.
self.rf = RfEntry(HwComponentType.REG, (4,), "rf")self.osel = reg(4) # 4-bit one-hot for 4 elements
got = self.rf[oh(self.osel)].data # element i selected when osel[i] is highoh(sig) is just a marker (OneHot) — it does not build hardware by itself.
Use it when your selector is already one-hot (grant vectors, match lines) and
you want to skip the binary decode.
The two selector forms address the same 4 elements differently:
flowchart LR
subgraph BIN["binary: sel = reg(2)"]
BA["2-bit address"] --> BD["decode into<br/>2:1 mux tree"]
end
subgraph OH["one-hot: osel = reg(4)"]
OA["4-bit select<br/>one bit per element"] --> OD["direct select,<br/>no binary decode"]
end
Mixing static and dynamic indices
Section titled “Mixing static and dynamic indices”Static and dynamic indices combine freely across dimensions. Pinning a dimension with an int shrinks the mux to just that row/column:
class Cell(Karray): v = kaf(1) d = kaf(6)
self.grid = Cell(HwComponentType.REG, (3, 4), "grid")self.sel = reg(2)
got = self.grid[2][self.sel].d # row 2 static, column dynamic -> 4:1 muxThe result is a single 6-bit wire selecting among grid[2][0..3].d only.
Two restrictions:
- A range slice cannot mix with dynamic indexing —
d[0:2][sel]is aTypeError. Slices exist only for static karray-to-karray regions. - A dynamic read must land on a field (
...[sel].data), never a whole element.
Dynamic reads build hardware — do them in scope
Section titled “Dynamic reads build hardware — do them in scope”A dynamic read materializes mux hardware the moment the reference is
resolved, so it must happen inside a module scope — an @init or @flow
method — like any other hardware construction. Using the result as an
assignment source inside a flow block (as in the examples above) is the
normal pattern.
Worked example: tc29
Section titled “Worked example: tc29”The example tc29_karray_dynamic_index fills a 4-entry register file with
known data, then reads it back through both decodes:
with seq(): # fill every element with its known (valid, data) for i in range(4): self.rf[i].valid |= self.c_v self.rf[i].data |= self.c_d[i]
# binary dynamic read of each address (rf[bsel_i].data == DATA[i]) for i in range(4): self.o_d[i] |= self.rf[self.bsel[i]].data
# one-hot dynamic read (osel = 1 << 2 -> element 2) self.o_od |= self.rf[oh(self.osel)].dataEvery address, plus a one-hot select with a non-trivial bit set, is verified end-to-end in simulation. See the Examples Gallery for the full file.
Writing through a dynamic index is covered next, in Dynamic Writes.