Skip to content

Conversion & Resize

This page covers moving data between structures: copying regions of one Karray into another, what happens when an assignment source’s width does not match its destination, and — as the running worked example — the register-file pattern from tc25.

Assigning one Karray element or region to another copies field by field, paired by exact name and width:

class SrcArray(Karray):
valid = kaf(1)
data = kaf(8)
note = kaf(4)
class DstArray(Karray):
valid = kaf(1)
data = kaf(8)
tag = kaf(4)
self.src = SrcArray(HwComponentType.REG, (4,), "src")
self.dst = DstArray(HwComponentType.REG, (4,), "dst")
with seq():
self.dst[0:2] |= self.src[1:3] # region copy: dst0<-src1, dst1<-src2
self.dst[3] |= self.src[0] # single-element copy

The rules:

  • Shapes must match. A range slice keeps its dimension; an int collapses it. dst[0:2] and src[1:3] are both shape [2], so they pair up element-wise. Assigning a whole Karray (dst |= src) copies every element.
  • Fields pair by name + width. Here valid and data copy across. note (source-only) and tag (destination-only) have no partner, so they are skipped — and a Python warning fires naming the skipped field. A skipped destination field keeps whatever value it already had.
  • The operator follows the destination’s backing as usual: |= for reg/mem-backed, *= for wire-backed.
  • Slice step must be 1, and slices cannot mix with dynamic (signal) indices. A dynamic-index destination cannot take a Karray region source at all.

Fields pair by name and width; unpaired fields on either side are skipped:

flowchart LR
    SV["src.valid"] --> DV["dst.valid"]
    SD["src.data"] --> DD["dst.data"]
    SN["src.note (source-only)"] --> SK["skipped, warns"]
    DT["dst.tag (destination-only)"] --> DK["unpaired, keeps old value"]

The element/region forms all route through the same machinery: dst[i] |= src[j], dst[a:b] |= src[c:d], and dst |= src differ only in how much of each array the selectors keep.

Whenever any assignment’s source width differs from its destination — plain signals and Karray fields alike — the connector sanitizes the source rather than erroring:

MismatchBehaviour
source narrower than destinationzero-extended (unsigned); high bits become 0
source wider than destinationMSBs dropped; only the low bits land
self.wide = reg(8) # 0xAB
self.narrow = reg(4) # 0xD
self.trunc = reg(4)
self.extend = reg(8)
with seq():
self.trunc |= self.wide # 8 -> 4 : low nibble lands (0xB), warns
self.extend |= self.narrow # 4 -> 8 : zero-extended (0x0D), warns
flowchart TB
    N["source narrower than dest"] --> NZ["zero-extended (unsigned)<br/>high bits = 0, warns"]
    W["source wider than dest"] --> WD["MSBs dropped<br/>only low bits land, warns"]

Worked example: a tiny register file (tc25)

Section titled “Worked example: a tiny register file (tc25)”

The register-file pattern brings the pieces together: a 1-D reg-backed Karray as storage, static writes in both styles, and read-back through ordinary output registers.

class RfEntry(Karray):
valid = kaf(1)
data = kaf(7)
class tc25_karray_regfile(Module):
@init
def com_declare(self):
# 4-entry register file; each field its own reg
self.rf = RfEntry(HwComponentType.REG, (4,), "rf")
self.c_valid = val(1, 1, "c_valid")
self.c_data = val(7, 42, "c_data")
# outputs mirroring the read-back fields
self.o_valid = reg(1, "o_valid"); self.o_valid.mark_output("my_v")
self.o_data = reg(7, "o_data"); self.o_data.mark_output("my_d")
@flow
def my_flow(self):
self.o_valid.reset(0)
self.o_data.reset(0)
with seq():
# entry 0 — field-wise writes into the per-field regs
self.rf[0].valid |= self.c_valid
self.rf[0].data |= self.c_data
# entry 1 — whole-element write; each named source lands on its field
self.rf[1] |= {"valid": self.c_valid, "data": self.c_data}
# read entry 0 back out through the output regs
self.o_valid |= self.rf[0].valid
self.o_data |= self.rf[0].data

Emitted per (element, field) — one register and one guarded clocked write each:

reg [0:0] REG_rf_E0_valid_5159;
reg [6:0] REG_rf_E0_data_5160;
always @(posedge WIRE_clk) begin
if (SR_ST_seq_state_0) begin
REG_rf_E0_valid_5159[0:0] <= VAL_c_valid[0:0];
end
end

From here the pattern grows naturally:

  • swap the constant indices for signals to get a real read/write port (Indexing, Dynamic Writes);
  • add a Reduce to pick an entry by priority;
  • switch the backing to MEM_BLOCK when the table outgrows discrete registers (Backings).

The full, simulated versions are tc25 (register file), tc27 (resize), and tc28 (karray-to-karray) in the Examples Gallery.