code wiki / _hdl_build / rv64im_min_alu.nx
rv64im_min_alu.nx source
↩ module page · 355 lines · 13845 B
1// rv64im_min_alu.nx -- RV64IM-min ALU.
2//
3// Combinational 64-bit ALU covering every operation the decoder
4// (rv64im_min_decoder.nx) can dispatch to:
5// - RV64I base: add/sub/sll/slt/sltu/xor/srl/sra/or/and
6// - RV64I-64: addw/subw/sllw/srlw/sraw (32-bit ops with sign-ext)
7// - M extension: mul/mulh/mulhsu/mulhu/div/divu/rem/remu (64-bit)
8// - M ext-32: mulw/divw/divuw/remw/remuw (32-bit with sign-ext)
9//
10// Each operation gets a sealed-enum op code; the executor (future
11// commit) routes the decoded NX_RV64IM_OP_* + funct3 + funct7 through
12// nx_rv64im_alu_select() to derive the ALU op code, then drives
13// nx_rv64im_alu_compute() with the operand bits.
14//
15// Status: SEED. 2026-05-26. Pure-function compute layer; the
16// HDL-graph builder declares the module shape so nishi-sim /
17// nishi-synth can drive it later.
18
19import "nx_syscalls.nx"
20import "nishi_hdl_primitives.nx"
21
22// ===== ALU operation kinds (sealed enum) =================================================
23//
24// One slot per distinct combinational logic shape. The decoder
25// derives this from {opcode, funct3, funct7}; the ALU shouldn't
26// need to re-decode.
27
28const NX_RV64IM_ALU_INVALID: i64 = 0
29
30// RV64I base (10 ops)
31const NX_RV64IM_ALU_ADD: i64 = 1
32const NX_RV64IM_ALU_SUB: i64 = 2
33const NX_RV64IM_ALU_SLL: i64 = 3 // shift left logical (low 6 bits of rs2)
34const NX_RV64IM_ALU_SLT: i64 = 4 // set if less than (signed)
35const NX_RV64IM_ALU_SLTU: i64 = 5 // set if less than (unsigned)
36const NX_RV64IM_ALU_XOR: i64 = 6
37const NX_RV64IM_ALU_SRL: i64 = 7 // shift right logical
38const NX_RV64IM_ALU_SRA: i64 = 8 // shift right arithmetic
39const NX_RV64IM_ALU_OR: i64 = 9
40const NX_RV64IM_ALU_AND: i64 = 10
41
42// RV64I-64 W-suffix (5 ops; operate on low 32 bits, sign-extend result)
43const NX_RV64IM_ALU_ADDW: i64 = 11
44const NX_RV64IM_ALU_SUBW: i64 = 12
45const NX_RV64IM_ALU_SLLW: i64 = 13 // shift count from low 5 bits of rs2
46const NX_RV64IM_ALU_SRLW: i64 = 14
47const NX_RV64IM_ALU_SRAW: i64 = 15
48
49// M extension 64-bit (8 ops)
50const NX_RV64IM_ALU_MUL: i64 = 16 // low 64 bits of signed*signed
51const NX_RV64IM_ALU_MULH: i64 = 17 // high 64 bits of signed*signed
52const NX_RV64IM_ALU_MULHSU: i64 = 18 // high 64 bits of signed*unsigned
53const NX_RV64IM_ALU_MULHU: i64 = 19 // high 64 bits of unsigned*unsigned
54const NX_RV64IM_ALU_DIV: i64 = 20 // signed quotient (round toward 0)
55const NX_RV64IM_ALU_DIVU: i64 = 21
56const NX_RV64IM_ALU_REM: i64 = 22 // signed remainder
57const NX_RV64IM_ALU_REMU: i64 = 23
58
59// M extension 32-bit (5 ops)
60const NX_RV64IM_ALU_MULW: i64 = 24
61const NX_RV64IM_ALU_DIVW: i64 = 25
62const NX_RV64IM_ALU_DIVUW: i64 = 26
63const NX_RV64IM_ALU_REMW: i64 = 27
64const NX_RV64IM_ALU_REMUW: i64 = 28
65
66const NX_RV64IM_ALU_N: i64 = 29
67
68func nx_rv64im_alu_op_is_valid(op: i64) -> i64 {
69 if op < 0 { return 0 }
70 if op >= NX_RV64IM_ALU_N { return 0 }
71 return 1
72}
73
74// ===== 32-bit sign-extension helper =================================================
75//
76// W-suffix ops operate on the low 32 bits of operands, produce a
77// 32-bit result, then sign-extend bit 31 across bits 63..32.
78
79func nx_rv64im_sext32(v: i64) -> i64 {
80 let low32: i64 = v & 0xffffffff
81 if (low32 & 0x80000000) != 0 {
82 return low32 | (0 - 4294967296) // bits 32..63 = 1
83 }
84 return low32
85}
86
87// ===== Unsigned compare =================================================
88//
89// NishiLang i64 < is signed. For unsigned compare, flip the top bit
90// of both operands and compare signed. Equivalent to comparing as
91// unsigned because bit 63 dominance reverses.
92
93func nx_rv64im_ltu(a: i64, b: i64) -> i64 {
94 let bit63: i64 = 0 - 9223372036854775808 // 0x8000000000000000
95 let a_flip: i64 = a ^ bit63
96 let b_flip: i64 = b ^ bit63
97 if a_flip < b_flip { return 1 }
98 return 0
99}
100
101// ===== Logical (unsigned) right shift =================================================
102//
103// NishiLang `>>` on i64 is ARITHMETIC (it sign-extends bit 63), so it
104// cannot implement RV64 SRL or the limb extraction in the multiplier.
105// Logical shift = arithmetic shift, then mask off the n sign-extended
106// high bits. 0 < n < 64; callers handle n==0 (identity) and n>=64.
107func nx_rv64im_lshr(a: i64, n: i64) -> i64 {
108 if n == 0 { return a }
109 if n >= 64 { return 0 }
110 let mask: i64 = (1 << (64 - n)) - 1 // low (64-n) bits set
111 return (a >> n) & mask
112}
113
114// ===== Unsigned 64-bit division =================================================
115//
116// NishiLang `/` is SIGNED and traps (x86 idiv) on INT_MIN / -1, so it
117// cannot implement RV64 DIVU/REMU. This is a bits-up restoring binary
118// long division using only shifts / subtracts / unsigned-compare --
119// the same shape the silicon divider FSM emits. b != 0 is the caller's
120// guard. Returns the unsigned quotient.
121//
122// b with its high bit set (b >= 2^63) is special-cased: since any
123// 64-bit a < 2^64 <= 2b, the quotient is 0 or 1, which also avoids the
124// (r << 1) overflowing 64 bits in the main loop (there r < b < 2^63 so
125// r << 1 < 2^64 always fits).
126
127func nx_rv64im_udiv(a: i64, b: i64) -> i64 {
128 if b < 0 { // b high bit set => q in {0,1}
129 if nx_rv64im_ltu(a, b) == 1 { return 0 }
130 return 1
131 }
132 var q: i64 = 0
133 var r: i64 = 0
134 var i: i64 = 63
135 while i >= 0 {
136 r = (r << 1) | ((a >> i) & 1)
137 if nx_rv64im_ltu(r, b) == 0 { // r >= b (unsigned)
138 r = r - b
139 q = q | (1 << i)
140 }
141 i = i - 1
142 }
143 return q
144}
145
146// Unsigned remainder: a - udiv(a,b)*b. Since a = q*b + r exactly with
147// r < b < 2^64, the wrapped multiply's low 64 bits give back r.
148func nx_rv64im_urem(a: i64, b: i64) -> i64 {
149 let q: i64 = nx_rv64im_udiv(a, b)
150 return a - (q * b)
151}
152
153// ===== Multiply-high helpers =================================================
154//
155// V1: NishiLang has no native 128-bit type, and the substrate
156// does not yet ship a portable mulh primitive. The DIV/REM hardware
157// path will land via the silicon side when nishi-synth emits the
158// 64x64->128 multiplier; the simulator side composes 32-bit
159// half-products. Today's stub returns 0 with the contract published.
160
161// High 64 bits of the unsigned 64x64 product. Composed from four
162// 32-bit limb products (al,ah)*(bl,bh); each partial fits in 64 bits
163// because each limb is < 2^32. This is the schoolbook 2x2 multiply
164// the silicon Wallace/Booth tree emits in one combinational shot.
165func nx_rv64im_mulh_unsigned(a: i64, b: i64) -> i64 {
166 let al: i64 = a & 4294967295
167 let ah: i64 = (a >> 32) & 4294967295
168 let bl: i64 = b & 4294967295
169 let bh: i64 = (b >> 32) & 4294967295
170 let ll: i64 = al * bl
171 let lh: i64 = al * bh
172 let hl: i64 = ah * bl
173 let hh: i64 = ah * bh
174 let mid: i64 = nx_rv64im_lshr(ll, 32) + (lh & 4294967295) + (hl & 4294967295)
175 return hh + nx_rv64im_lshr(lh, 32) + nx_rv64im_lshr(hl, 32) + nx_rv64im_lshr(mid, 32)
176}
177
178// High 64 bits of the signed 64x64 product. Start from the unsigned
179// high word, then apply the two's-complement correction: subtract b
180// if a is negative, subtract a if b is negative.
181func nx_rv64im_mulh_signed(a: i64, b: i64) -> i64 {
182 var hi: i64 = nx_rv64im_mulh_unsigned(a, b)
183 if a < 0 { hi = hi - b }
184 if b < 0 { hi = hi - a }
185 return hi
186}
187
188// High 64 bits of signed-a x unsigned-b. Only operand a carries a
189// sign, so apply the correction for a only.
190func nx_rv64im_mulh_signed_unsigned(a: i64, b: i64) -> i64 {
191 var hi: i64 = nx_rv64im_mulh_unsigned(a, b)
192 if a < 0 { hi = hi - b }
193 return hi
194}
195
196// ===== Divide guards =================================================
197//
198// RV64IM spec ยง6.2 requires:
199// div by zero -> quotient = -1 (all-ones), remainder = dividend
200// signed overflow (INT_MIN / -1) -> quotient = INT_MIN, remainder = 0
201// Hardware avoids the trap; software must enforce these constants.
202
203const NX_RV64IM_INT_MIN: i64 = 0 - 9223372036854775808 // 0x8000000000000000
204const NX_RV64IM_ALL_ONES: i64 = 0 - 1 // 0xFFFFFFFFFFFFFFFF
205
206// ===== Top-level compute =================================================
207//
208// Combinational core: given an ALU op kind + two 64-bit operands,
209// returns the 64-bit result. No side effects, no branches into
210// memory, no exceptions raised. Invalid op returns 0 (the executor
211// is responsible for raising illegal-instruction; this layer just
212// computes).
213
214func nx_rv64im_alu_compute(op: i64, a: i64, b: i64) -> i64 {
215 if op == NX_RV64IM_ALU_ADD { return a + b }
216 if op == NX_RV64IM_ALU_SUB { return a - b }
217
218 if op == NX_RV64IM_ALU_SLL {
219 let shamt: i64 = b & 0x3f
220 return a << shamt
221 }
222 if op == NX_RV64IM_ALU_SRL {
223 let shamt: i64 = b & 0x3f
224 return nx_rv64im_lshr(a, shamt)
225 }
226 if op == NX_RV64IM_ALU_SRA {
227 // Arithmetic shift: replicate bit 63. NishiLang >> is
228 // logical; build SRA via sign-bit fill.
229 let shamt: i64 = b & 0x3f
230 let logical: i64 = a >> shamt
231 if (a & NX_RV64IM_INT_MIN) == 0 { return logical }
232 // sign bit was 1: OR in the top `shamt` ones.
233 if shamt == 0 { return a }
234 let fill: i64 = NX_RV64IM_ALL_ONES << (64 - shamt)
235 return logical | fill
236 }
237
238 if op == NX_RV64IM_ALU_SLT {
239 if a < b { return 1 }
240 return 0
241 }
242 if op == NX_RV64IM_ALU_SLTU { return nx_rv64im_ltu(a, b) }
243
244 if op == NX_RV64IM_ALU_XOR { return a ^ b }
245 if op == NX_RV64IM_ALU_OR { return a | b }
246 if op == NX_RV64IM_ALU_AND { return a & b }
247
248 if op == NX_RV64IM_ALU_ADDW { return nx_rv64im_sext32(a + b) }
249 if op == NX_RV64IM_ALU_SUBW { return nx_rv64im_sext32(a - b) }
250 if op == NX_RV64IM_ALU_SLLW {
251 let shamt: i64 = b & 0x1f
252 return nx_rv64im_sext32(a << shamt)
253 }
254 if op == NX_RV64IM_ALU_SRLW {
255 let shamt: i64 = b & 0x1f
256 let a32: i64 = a & 0xffffffff
257 return nx_rv64im_sext32(a32 >> shamt)
258 }
259 if op == NX_RV64IM_ALU_SRAW {
260 let shamt: i64 = b & 0x1f
261 let a32: i64 = nx_rv64im_sext32(a)
262 let logical: i64 = a32 >> shamt
263 if (a32 & NX_RV64IM_INT_MIN) == 0 { return nx_rv64im_sext32(logical) }
264 if shamt == 0 { return nx_rv64im_sext32(a32) }
265 let fill: i64 = NX_RV64IM_ALL_ONES << (64 - shamt)
266 return nx_rv64im_sext32(logical | fill)
267 }
268
269 if op == NX_RV64IM_ALU_MUL { return a * b } // low 64; NishiLang * wraps
270 if op == NX_RV64IM_ALU_MULH { return nx_rv64im_mulh_signed(a, b) }
271 if op == NX_RV64IM_ALU_MULHSU { return nx_rv64im_mulh_signed_unsigned(a, b) }
272 if op == NX_RV64IM_ALU_MULHU { return nx_rv64im_mulh_unsigned(a, b) }
273
274 if op == NX_RV64IM_ALU_DIV {
275 if b == 0 { return NX_RV64IM_ALL_ONES }
276 if a == NX_RV64IM_INT_MIN { if b == (0 - 1) { return NX_RV64IM_INT_MIN } }
277 return a / b
278 }
279 if op == NX_RV64IM_ALU_DIVU {
280 if b == 0 { return NX_RV64IM_ALL_ONES }
281 return nx_rv64im_udiv(a, b)
282 }
283 if op == NX_RV64IM_ALU_REM {
284 if b == 0 { return a }
285 if a == NX_RV64IM_INT_MIN { if b == (0 - 1) { return 0 } }
286 return a - ((a / b) * b)
287 }
288 if op == NX_RV64IM_ALU_REMU {
289 if b == 0 { return a }
290 return nx_rv64im_urem(a, b)
291 }
292
293 if op == NX_RV64IM_ALU_MULW { return nx_rv64im_sext32(a * b) }
294 if op == NX_RV64IM_ALU_DIVW {
295 if (b & 0xffffffff) == 0 { return NX_RV64IM_ALL_ONES }
296 let a32: i64 = nx_rv64im_sext32(a)
297 let b32: i64 = nx_rv64im_sext32(b)
298 if a32 == nx_rv64im_sext32(NX_RV64IM_INT_MIN) { if b32 == (0 - 1) { return nx_rv64im_sext32(NX_RV64IM_INT_MIN) } }
299 return nx_rv64im_sext32(a32 / b32)
300 }
301 if op == NX_RV64IM_ALU_DIVUW {
302 if (b & 0xffffffff) == 0 { return NX_RV64IM_ALL_ONES }
303 let a32: i64 = a & 0xffffffff
304 let b32: i64 = b & 0xffffffff
305 return nx_rv64im_sext32(a32 / b32)
306 }
307 if op == NX_RV64IM_ALU_REMW {
308 if (b & 0xffffffff) == 0 { return nx_rv64im_sext32(a) }
309 let a32: i64 = nx_rv64im_sext32(a)
310 let b32: i64 = nx_rv64im_sext32(b)
311 if a32 == nx_rv64im_sext32(NX_RV64IM_INT_MIN) { if b32 == (0 - 1) { return 0 } }
312 return nx_rv64im_sext32(a32 - ((a32 / b32) * b32))
313 }
314 if op == NX_RV64IM_ALU_REMUW {
315 if (b & 0xffffffff) == 0 { return nx_rv64im_sext32(a) }
316 let a32: i64 = a & 0xffffffff
317 let b32: i64 = b & 0xffffffff
318 return nx_rv64im_sext32(a32 - ((a32 / b32) * b32))
319 }
320
321 // NX_RV64IM_ALU_INVALID and any out-of-range op fall through.
322 return 0
323}
324
325// ===== HDL-graph builder =================================================
326
327const NX_RV64IM_ALU_WIDTH_OP: i64 = 8 // sealed enum fits 29 values
328const NX_RV64IM_ALU_WIDTH_OPERAND: i64 = 64
329
330struct NxRv64imAluPorts {
331 op_in: i64 // input wire, 8-bit (one of NX_RV64IM_ALU_*)
332 a_in: i64 // input wire, 64-bit
333 b_in: i64 // input wire, 64-bit
334 result: i64 // output wire, 64-bit
335}
336
337func nx_rv64im_alu_build(m: *NxHdlModule, ports: *NxRv64imAluPorts) -> i64 {
338 let p_op: i64 = nx_hdl_input(m, NX_RV64IM_ALU_WIDTH_OP)
339 if p_op < 0 { return p_op }
340 let p_a: i64 = nx_hdl_input(m, NX_RV64IM_ALU_WIDTH_OPERAND)
341 if p_a < 0 { return p_a }
342 let p_b: i64 = nx_hdl_input(m, NX_RV64IM_ALU_WIDTH_OPERAND)
343 if p_b < 0 { return p_b }
344 let p_result: i64 = nx_hdl_output(m, NX_RV64IM_ALU_WIDTH_OPERAND)
345 if p_result < 0 { return p_result }
346
347 ports.op_in = p_op
348 ports.a_in = p_a
349 ports.b_in = p_b
350 ports.result = p_result
351
352 // Driver edges declared by nishi-sim / nishi-synth (future
353 // commits) which call nx_rv64im_alu_compute() per cycle.
354 return NX_HDL_OK
355}