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}