Skip to content

Dynamic Writes

A dynamic write stores into an element chosen by a runtime signal: the selected element takes the new value, every other element holds. Kathryn builds the per-element write-enable decode for you — a guarded clocked assign on each element’s register.

Dynamic writes require a reg backing and the clocked operator |=. There is no combinational variant: a wire cannot “hold” the non-selected elements.

The write-side mirror of a dynamic read — index with a signal (binary address) or oh(signal) (one-hot), then assign the field:

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) # binary address
self.ohs = reg(4) # one-hot select
self.src = reg(8)
@flow
def f(self):
with seq():
self.rf[self.sel].data |= self.src # binary: enable is (sel == k)
self.rf[oh(self.ohs)].data |= self.src # one-hot: enable is ohs[k]

For each element k, the emitted register write is guarded by that element’s enable:

always @(posedge clk) begin
if (seq_state) begin
if (EXPR_sel_EQ1) begin // sel == 1
REG_rf_E1_data[7:0] <= VAL_src[7:0]; // only element 1 written
end
end
end

The decode enables exactly the selected element; every other element holds:

flowchart TB
    SEL["sel (binary address)"] --> E0["element 0: en=(sel==0)"]
    SEL --> E1["element 1: en=(sel==1)"]
    SEL --> E2["element 2: en=(sel==2)"]
    SEL --> E3["element 3: en=(sel==3)"]
    E0 --> H0["holds"]
    E1 --> H1["written from src"]
    E2 --> H2["holds"]
    E3 --> H3["holds"]

A dynamic index also accepts the {field_name: source} map form — each named source lands on the field of that name, on the runtime-selected element:

self.rf[self.sel] |= {"valid": self.v, "data": self.d}

The built-in decodes cover “exactly one element, selected by binary or one-hot address”. When you need anything else — a range of elements, a comparison, a mask you compute yourself — use cus_dynamic_assign and write the enable logic in a callback:

Karray.cus_dynamic_assign(dims, src, write_fn, clocked=True)
  • dims — one entry per dimension: an int pins the dimension (only that index’s slice is touched), Spread fans it out over its extent. At least one dimension must be Spread.
  • src — a {field_name: source} map; sources are matched to fields by name, full-width.
  • write_fn — called once per spread element with a WriteView. The view exposes view.coord, the element’s static coordinate (a list of ints). The function must return a 1-bit write-enable signal; the element is written from src only when the enable is high, and holds otherwise.

Close over whatever runtime signals you like to build the enable — the coordinate is compile-time, the comparison is hardware:

self.rf = RegFile(HwComponentType.REG, (4,), "rf")
self.sel = reg(2)
self.src = reg(8)
with seq():
# equivalent to the built-in binary decode: enable = (sel == coord)
self.rf.cus_dynamic_assign(
[Spread], {"data": self.src},
lambda v: self.sel == v.coord[0],
)

The power is in non-trivial enables. This writes a sentinel into every element whose index is below a threshold — a multi-element write, which the built-in decodes cannot express:

# write SENTINEL wherever thr > coord (elements 0 and 1 when thr == 2)
self.rg.cus_dynamic_assign(
[Spread], {"data": self.c_sn},
lambda v: self.thr > v.coord[0],
)

On a 2-D array you can pin one dimension and spread the other: dims=[1, Spread] fans out over row 1 only.

Spread fans the callback out over the dimension’s extent — one enable per element, each built from that element’s static coord:

flowchart TB
    CUS["cus_dynamic_assign<br/>dims=[Spread]"] --> C0["coord=0: write_fn -> 1b enable"]
    CUS --> C1["coord=1: write_fn -> 1b enable"]
    CUS --> C2["coord=2: write_fn -> 1b enable"]
    CUS --> C3["coord=3: write_fn -> 1b enable"]
    C0 --> R["element written from src<br/>when enable high, else holds"]
    C1 --> R
    C2 --> R
    C3 --> R
  • tc32 exercises all three built-in write decodes on one register file — binary, one-hot, and a whole-element map — each landing on exactly one element while the others hold.
  • tc33 exercises cus_dynamic_assign: per-element enables built from view.coord against a closed-over selector, plus the threshold range write above.

Both are listed in the Examples Gallery.