Bit Utilities
kathryn.lib is Kathryn’s small standard library: generic, reusable hardware
helpers built purely on the public DSL — nothing in it is magic, and
everything it builds you could write by hand with wires, zif/zelse, and
slices. This page covers the bit-manipulation helpers; interface helpers live
in Bundles & Handshake.
from kathryn.lib import width_of, zext, sext, cat, replicate, or_reduce, and_reduce, muxThe helpers split into two families, and the distinction matters for where you may call them:
- Expression-only —
width_of,zext,or_reduce,and_reducebuild (or inspect) expressions and are usable anywhere a signal is. - Wire-declaring —
sext,cat,replicate,muxdeclare a fresh wire and/or open zero-cycle blocks, so they must run inside an open module scope — in practice, call them from a@flowmethod.
flowchart TB
subgraph EXPR["expression-only: use anywhere"]
WO["width_of"] ~~~ ZX["zext"] ~~~ OR["or_reduce / and_reduce"]
end
subgraph WIRE["declare a fresh wire: call from @flow"]
SX["sext"] ~~~ CT["cat / replicate"] ~~~ MX["mux"]
end
Everything on this page is combinational: outputs reflect their inputs the
same cycle (worked example: tc34, which drives all inputs and checks every
derived output in the same cycle).
Width & extension
Section titled “Width & extension”width_of(sig)
Section titled “width_of(sig)”Returns the width of a signal as referenced — a slice view reports the
slice width, not the underlying signal’s width. Plain Python int, usable in
any host-side computation.
zext(sig, width)
Section titled “zext(sig, width)”Zero-extend to width bits. Wraps the native extend op, so it returns a plain
expression; if the signal is already at the target width it passes through
unchanged. A source wider than the target raises ValueError.
self.o_zext *= zext(self.b, 8) # 4-bit b, high 4 bits padded with 0assign EXPR_expr0 = {{4{1'b0}}, WIRE_b[3:0]};sext(sig, width, name=None)
Section titled “sext(sig, width, name=None)”Sign-extend to width bits: the low bits pass through and the high bits
mirror the source’s sign bit (its MSB). Unlike zext this declares a fresh
wire and drives it through a full-coverage zif/zelse pair, one branch per
sign value:
self.o_sext *= sext(self.b, 8) # b = 0x9 → 0xF9; b = 0x5 → 0x05Assembling words
Section titled “Assembling words”cat(*sigs, name=None)
Section titled “cat(*sigs, name=None)”Concatenate MSB-first — the same order as Verilog {a, b, c}, so the last
argument lands in the least-significant bits. The result is a fresh wire
whose width is the sum of the parts, each part driven onto its slice:
self.o_cat *= cat(self.b, self.a[3, 0]) # {b, a[3:0]} — b in the high halfreplicate(bit, n, name=None)
Section titled “replicate(bit, n, name=None)”n copies of a 1-bit signal (Verilog {n{bit}}); a wider source raises
ValueError. It is literally cat applied to the same bit n times.
self.o_repl *= replicate(self.c, 4) # 0b1111 when c is high, else 0b0000Reductions
Section titled “Reductions”or_reduce(sigs) / and_reduce(sigs)
Section titled “or_reduce(sigs) / and_reduce(sigs)”Fold a Python list of same-width signals (typically 1-bit flags) into one
result. Rather than a linear a | b | c | d … chain, the fold is a balanced
binary tree, so the hardware is log-depth:
flowchart TB
A0["a[0]"] --> O1["or"]
A1["a[1]"] --> O1
A2["a[2]"] --> O2["or"]
A3["a[3]"] --> O2
O1 --> O3["or"]
O2 --> O3
O3 --> R["result"]
self.o_orr *= or_reduce([self.a[i] for i in range(8)]) # any bit set?self.o_andr *= and_reduce([self.a[i] for i in range(8)]) # all bits set?An odd element is carried up and paired at the next layer — any list length
works. An empty list raises ValueError.
Selecting
Section titled “Selecting”mux(cond, a, b, name=None)
Section titled “mux(cond, a, b, name=None)”A 2:1 mux: cond ? a : b as a fresh wire, driven by a full-coverage
zif/zelse with no default. a and b may be signals or plain ints (ints
auto-wrap as usual — see Expressions), but at
least one must be a signal so the output width can be inferred
(max(width(a), width(b))); two ints raise ValueError.
self.o_mux *= mux(self.c, self.a, zext(self.b, 8)) # c ? a : zext(b)All eight helpers are exercised end-to-end in tc34_lib_bits — see the
Examples Gallery.