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}