code wiki / (root) / nishi_hdl_primitives.nx

nishi_hdl_primitives.nx source

↩ module page · 258 lines · 10710 B

1// nishi_hdl_primitives.nx -- Nishi HDL DSL primitive types. 2// 3// THE seed file for the NishiLang-hosted hardware description DSL. 4// Defines just the type primitives -- Module, Wire, Reg, Bus, Clock -- 5// so the first concrete user (rv64im_min_decoder.nx) has a vocabulary 6// to express logic in. 7// 8// Status: SEED. 2026-05-26. Types only; behavior (simulation, 9// netlist emit, synth) lands in follow-on commits per hdl/README.md 10// deliverables checklist. 11// 12// Cardinal "bits up, additive intelligence": these types are the 13// substrate the DSL builds on. Future commits add lex/parse/sim/ 14// netlist layers ABOVE them, NOT replacements OF them. 15// 16// Why NishiLang-hosted vs free-standing DSL syntax: 17// feedback-no-nxc2-c-extension-only-nishilang-forward -- the whole 18// stack is NishiLang. Verilog/SystemVerilog/Chisel/Migen are 19// external; substrate's HDL is just NishiLang types + functions. 20// The "DSL" is a coding convention + a constructor library. 21// 22// Surface comparison: 23// 24// Verilog 2005: wire [7:0] data; 25// Chisel (Scala): val data = Wire(UInt(8.W)) 26// NishiHDL: let data: NxHdlWire = nx_hdl_wire_make(width=8) 27// 28// The width-typed value flows through Module-graph constructors; 29// nishi-synth (future) reads the graph and emits a gate netlist. 30 31import "nx_syscalls.nx" 32 33// ===== Width ================================================= 34// 35// All signals carry an explicit bit width (1..512 for V1; wider 36// when V-extension lands in Tier B+). Width-mismatched connect 37// is a verdict, not a silent truncate. 38const NX_HDL_WIDTH_MIN: i64 = 1 39const NX_HDL_WIDTH_MAX: i64 = 512 40 41func nx_hdl_width_is_valid(w: i64) -> i64 { 42 if w < NX_HDL_WIDTH_MIN { return 0 } 43 if w > NX_HDL_WIDTH_MAX { return 0 } 44 return 1 45} 46 47// ===== Signal kinds (sealed enum) ================================================= 48// 49// Every signal in the HDL graph has a kind code. Sealed: no kind 50// > NX_HDL_KIND_N exists, so dispatch is a finite switch. 51const NX_HDL_KIND_WIRE: i64 = 0 // combinational; resolves each clock edge 52const NX_HDL_KIND_REG: i64 = 1 // sequential; holds value across edges 53const NX_HDL_KIND_INPUT: i64 = 2 // module port, driven by parent 54const NX_HDL_KIND_OUTPUT: i64 = 3 // module port, driven by self 55const NX_HDL_KIND_INOUT: i64 = 4 // bidirectional port (MMIO buses) 56const NX_HDL_KIND_CLOCK: i64 = 5 // clock domain root 57const NX_HDL_KIND_RESET: i64 = 6 // reset signal (sync or async) 58const NX_HDL_KIND_CONST: i64 = 7 // compile-time constant 59const NX_HDL_KIND_N_KINDS: i64 = 8 60 61func nx_hdl_kind_is_valid(k: i64) -> i64 { 62 if k < 0 { return 0 } 63 if k >= NX_HDL_KIND_N_KINDS { return 0 } 64 return 1 65} 66 67// ===== Verdicts ================================================= 68const NX_HDL_OK: i64 = 0 69const NX_HDL_BAD_WIDTH: i64 = 1 70const NX_HDL_BAD_KIND: i64 = 2 71const NX_HDL_WIDTH_MISMATCH: i64 = 3 // connect of incompatible widths 72const NX_HDL_KIND_MISMATCH: i64 = 4 // e.g., wire driven by another wire 73const NX_HDL_GRAPH_FULL: i64 = 5 // module-graph capacity exceeded 74const NX_HDL_DRIVER_CONFLICT: i64 = 6 // multiple drivers on a wire 75const NX_HDL_N_VERDICTS: i64 = 7 76 77func nx_hdl_verdict_is_valid(v: i64) -> i64 { 78 if v < 0 { return 0 } 79 if v >= NX_HDL_N_VERDICTS { return 0 } 80 return 1 81} 82 83// ===== Signal handle ================================================= 84// 85// A signal is identified by its index into the module's signal-table. 86// Handles are i64 (NOT pointers) so they're safe to store, compare, 87// and serialise without aliasing concerns. Handle == -1 means "no 88// signal" / "uninitialised". 89// 90// The handle's kind + width are looked up via nx_hdl_signal_kind(h) 91// and nx_hdl_signal_width(h) against the owning module. 92const NX_HDL_HANDLE_NONE: i64 = 0 - 1 93 94// ===== Reset polarity ================================================= 95const NX_HDL_RESET_ACTIVE_HIGH: i64 = 0 96const NX_HDL_RESET_ACTIVE_LOW: i64 = 1 97const NX_HDL_RESET_SYNC: i64 = 0 98const NX_HDL_RESET_ASYNC: i64 = 1 99 100// ===== Module-graph capacity (V1 fixed) ================================================= 101// 102// V1 modules have a static signal-table. When V2 needs dynamic 103// growth (deep cores), the table moves to nx_hal_alloc_pages. 104const NX_HDL_MODULE_MAX_SIGNALS: i64 = 8192 // RV64IM-min core + full ALU (divider+mulh+W-variant) 105const NX_HDL_MODULE_MAX_CHILDREN: i64 = 256 // sub-module instantiations 106 107// ===== Module shape (V1 -- flat) ================================================= 108// 109// V1 model: each module has a fixed-size table of signals, each with 110// (kind, width, driver_handle, name_offset). Sub-modules instantiate 111// by name; the parent module records the connections in a separate 112// table. 113// 114// Memory layout (4 i64 per signal): 115// kind :i64 one of NX_HDL_KIND_* 116// width :i64 1..NX_HDL_WIDTH_MAX 117// driver :i64 handle to driving signal (NX_HDL_HANDLE_NONE for inputs) 118// name :i64 offset into the module's name-pool (UTF-8 byte buf) 119// 120// Total per-signal storage: 4 * 8 = 32 bytes. 121// V1 module signal-table footprint: 4096 * 32 = 128 KiB. 122const NX_HDL_SIGNAL_STRIDE_BYTES: i64 = 32 123 124// Module record indices into the signal-stride array. 125const NX_HDL_SIGNAL_FIELD_KIND: i64 = 0 126const NX_HDL_SIGNAL_FIELD_WIDTH: i64 = 1 127const NX_HDL_SIGNAL_FIELD_DRIVER: i64 = 2 128const NX_HDL_SIGNAL_FIELD_NAME: i64 = 3 129 130// ===== Module struct ================================================= 131// 132// Caller allocates an NxHdlModule, then calls nx_hdl_module_init() to 133// stamp the signal-table layout. The substrate doesn't take ownership; 134// release follows the usual nx_hal_release_pages() contract. 135struct NxHdlModule { 136 name_buf: *u8 // module's identifier (UTF-8, NUL-terminated) 137 name_len: i64 138 n_signals: i64 // count of valid signals (<= MODULE_MAX_SIGNALS) 139 signals_buf: *i64 // 4096 * 4 i64s = 128 KiB 140 name_pool: *u8 // pool for signal names (UTF-8 byte buf) 141 name_pool_used: i64 142 name_pool_cap: i64 143} 144 145// ===== Signal accessors (read-only) ================================================= 146// 147// These look up signal-table fields by handle. Callers must validate 148// handle >= 0 && handle < module.n_signals before calling. 149 150func nx_hdl_signal_kind(m: *NxHdlModule, handle: i64) -> i64 { 151 if handle < 0 { return 0 - NX_HDL_BAD_KIND } 152 if handle >= m.n_signals { return 0 - NX_HDL_BAD_KIND } 153 return m.signals_buf[handle * 4 + NX_HDL_SIGNAL_FIELD_KIND] 154} 155 156func nx_hdl_signal_width(m: *NxHdlModule, handle: i64) -> i64 { 157 if handle < 0 { return 0 - NX_HDL_BAD_KIND } 158 if handle >= m.n_signals { return 0 - NX_HDL_BAD_KIND } 159 return m.signals_buf[handle * 4 + NX_HDL_SIGNAL_FIELD_WIDTH] 160} 161 162func nx_hdl_signal_driver(m: *NxHdlModule, handle: i64) -> i64 { 163 if handle < 0 { return NX_HDL_HANDLE_NONE } 164 if handle >= m.n_signals { return NX_HDL_HANDLE_NONE } 165 return m.signals_buf[handle * 4 + NX_HDL_SIGNAL_FIELD_DRIVER] 166} 167 168// ===== Signal constructors ================================================= 169// 170// Each constructor (a) validates inputs, (b) appends to the module's 171// signal-table, (c) returns the new handle (or negated verdict on 172// failure). Naming is convenience-only; pass null/empty for anon. 173 174func nx_hdl_signal_alloc(m: *NxHdlModule, kind: i64, width: i64) -> i64 { 175 if nx_hdl_kind_is_valid(kind) != 1 { return 0 - NX_HDL_BAD_KIND } 176 if nx_hdl_width_is_valid(width) != 1 { return 0 - NX_HDL_BAD_WIDTH } 177 if m.n_signals >= NX_HDL_MODULE_MAX_SIGNALS { return 0 - NX_HDL_GRAPH_FULL } 178 let h: i64 = m.n_signals 179 m.signals_buf[h * 4 + NX_HDL_SIGNAL_FIELD_KIND] = kind 180 m.signals_buf[h * 4 + NX_HDL_SIGNAL_FIELD_WIDTH] = width 181 m.signals_buf[h * 4 + NX_HDL_SIGNAL_FIELD_DRIVER] = NX_HDL_HANDLE_NONE 182 m.signals_buf[h * 4 + NX_HDL_SIGNAL_FIELD_NAME] = 0 183 m.n_signals = h + 1 184 return h 185} 186 187func nx_hdl_wire(m: *NxHdlModule, width: i64) -> i64 { 188 return nx_hdl_signal_alloc(m, NX_HDL_KIND_WIRE, width) 189} 190 191func nx_hdl_reg(m: *NxHdlModule, width: i64) -> i64 { 192 return nx_hdl_signal_alloc(m, NX_HDL_KIND_REG, width) 193} 194 195func nx_hdl_input(m: *NxHdlModule, width: i64) -> i64 { 196 return nx_hdl_signal_alloc(m, NX_HDL_KIND_INPUT, width) 197} 198 199func nx_hdl_output(m: *NxHdlModule, width: i64) -> i64 { 200 return nx_hdl_signal_alloc(m, NX_HDL_KIND_OUTPUT, width) 201} 202 203func nx_hdl_clock(m: *NxHdlModule) -> i64 { 204 return nx_hdl_signal_alloc(m, NX_HDL_KIND_CLOCK, 1) 205} 206 207func nx_hdl_reset(m: *NxHdlModule) -> i64 { 208 return nx_hdl_signal_alloc(m, NX_HDL_KIND_RESET, 1) 209} 210 211// ===== Connect (drive a signal) ================================================= 212// 213// Drives `sink` from `source`. Widths must match exactly (no 214// silent truncate; explicit nx_hdl_resize() for that). Sink must 215// be a writable kind (WIRE / REG / OUTPUT); driving an INPUT or 216// CONST is a kind-mismatch verdict. 217// 218// V1 enforces single-driver: re-connecting a sink yields a 219// DRIVER_CONFLICT verdict so unintended overwrites are loud. 220 221func nx_hdl_connect(m: *NxHdlModule, sink: i64, source: i64) -> i64 { 222 let sink_kind: i64 = nx_hdl_signal_kind(m, sink) 223 if sink_kind < 0 { return sink_kind } // already negated verdict 224 if sink_kind == NX_HDL_KIND_INPUT { return 0 - NX_HDL_KIND_MISMATCH } 225 if sink_kind == NX_HDL_KIND_CONST { return 0 - NX_HDL_KIND_MISMATCH } 226 if sink_kind == NX_HDL_KIND_CLOCK { return 0 - NX_HDL_KIND_MISMATCH } 227 if sink_kind == NX_HDL_KIND_RESET { return 0 - NX_HDL_KIND_MISMATCH } 228 229 let sink_w: i64 = nx_hdl_signal_width(m, sink) 230 let src_w: i64 = nx_hdl_signal_width(m, source) 231 if sink_w != src_w { return 0 - NX_HDL_WIDTH_MISMATCH } 232 233 let cur_driver: i64 = nx_hdl_signal_driver(m, sink) 234 if cur_driver != NX_HDL_HANDLE_NONE { return 0 - NX_HDL_DRIVER_CONFLICT } 235 236 m.signals_buf[sink * 4 + NX_HDL_SIGNAL_FIELD_DRIVER] = source 237 return NX_HDL_OK 238} 239 240// ===== Module init ================================================= 241// 242// Stamps a caller-allocated NxHdlModule with the signal-table layout. 243// The module's signals_buf must be at least 244// NX_HDL_MODULE_MAX_SIGNALS * 4 i64s (= 128 KiB). 245func nx_hdl_module_init(m: *NxHdlModule, name: *u8, name_len: i64, 246 signals_buf: *i64, name_pool: *u8, 247 name_pool_cap: i64) -> i64 { 248 if (m as i64) == 0 { return 0 - NX_HDL_BAD_KIND } 249 if (signals_buf as i64) == 0 { return 0 - NX_HDL_BAD_KIND } 250 m.name_buf = name 251 m.name_len = name_len 252 m.n_signals = 0 253 m.signals_buf = signals_buf 254 m.name_pool = name_pool 255 m.name_pool_used = 0 256 m.name_pool_cap = name_pool_cap 257 return NX_HDL_OK 258}