code wiki / (root) / nx_emu_riscv32.nx

nx_emu_riscv32.nx source

↩ module page · 138 lines · 7012 B

1// nx_emu_riscv32.nx -- sovereign RV32IM interpreter (NX-EMU, 32-bit XLEN). 2// Sibling of nx_emu_rv64 for the 32-bit RISC-V target; nxc2's rv32 backend 3// represents i64 as register PAIRS, so the KAT exercises add/sub/mul/mulhu/sltu 4// (carry) over 32-bit lanes. Flat little-endian guest RAM, Linux rv32 syscalls 5// (exit=93, a7=num, a0..=args). NO qemu -- decode/execute per the RISC-V ISA. 6// license_tier: ORIGINAL 7import "nx_syscalls_x86_64.nx" 8 9const R32_GUEST: i64 = 16777216 10const R32_MASK: i64 = 0xFFFFFFFF 11const R32_SYS_EXIT: i64 = 93 12// B-type branch offset: 13 bits, bit 12 is the sign. Named rather than inline so the shape of the 13// immediate is readable and the magic-number ratchet has something to point at. 14const R32_BIMM_SIGN: i64 = 4096 // bit 12 of the 13-bit offset 15const R32_BIMM_SPAN: i64 = 8192 // 2 * R32_BIMM_SIGN -- subtract to sign-extend 16const R32_BIMM_HI_MASK: i64 = 0x3f // imm[10:5] field 17const R32_BIMM_LO_MASK: i64 = 0xf // imm[4:1] field 18const R32_XLEN_BITS: i64 = 32 19 20func r32_ld(mem: *u8, va: i64, width: i64) -> i64 { 21 var v: i64 = 0 22 var i: i64 = 0 23 while i < width { v = v | ((mem[va + i] & 0xff) << (i * 8)); i = i + 1 } 24 return v 25} 26func r32_st(mem: *u8, va: i64, width: i64, val: i64) -> i64 { 27 var i: i64 = 0 28 while i < width { mem[va + i] = (val >> (i * 8)) & 0xff; i = i + 1 } 29 return 0 30} 31func r32_sx(v: i64, bits: i64) -> i64 { let m: i64 = 1 << (bits - 1); if (v & m) != 0 { return v - (1 << bits) } return v } 32 33func emu_riscv32_run_mem(mem: *u8, mem_size: i64, entry: i64, sp0: i64) -> i64 { 34 let r: *i64 = sys_mmap(32 * 8) as *i64 35 var i: i64 = 0 36 while i < 32 { r[i] = 0; i = i + 1 } 37 r[2] = sp0 38 var pc: i64 = entry 39 var result: i64 = 0 40 var halted: i64 = 0 41 var steps: i64 = 0 42 while halted == 0 { 43 if steps > 5000000 { halted = 1 } else { 44 let w: i64 = r32_ld(mem, pc, 4) 45 let opc: i64 = w & 0x7F 46 let rd: i64 = (w >> 7) & 0x1F 47 let f3: i64 = (w >> 12) & 7 48 let rs1: i64 = (w >> 15) & 0x1F 49 let rs2: i64 = (w >> 20) & 0x1F 50 let f7: i64 = (w >> 25) & 0x7F 51 var next: i64 = pc + 4 52 var handled: i64 = 0 53 if opc == 0x13 { // addi (I) 54 handled = 1 55 let imm: i64 = r32_sx((w >> 20) & 0xFFF, 12) 56 r[rd] = (r[rs1] + imm) & R32_MASK 57 } 58 if opc == 0x37 { // lui (U) 59 handled = 1 60 r[rd] = w & 0xFFFFF000 61 } 62 if opc == 0x33 { // R-type 63 handled = 1 64 let a: i64 = r[rs1] & R32_MASK 65 let b: i64 = r[rs2] & R32_MASK 66 if f3 == 0 { if f7 == 0 { r[rd] = (a + b) & R32_MASK } if f7 == 0x20 { r[rd] = (a - b) & R32_MASK } if f7 == 1 { r[rd] = (a * b) & R32_MASK } } 67 if f3 == 3 { if f7 == 0 { if a < b { r[rd] = 1 } else { r[rd] = 0 } } if f7 == 1 { r[rd] = (a * b) >> 32 } } // sltu / mulhu (a,b<2^32) 68 if f3 == 7 { r[rd] = a & b } // and 69 if f3 == 6 { r[rd] = a | b } // or 70 if f3 == 4 { r[rd] = a ^ b } // xor 71 } 72 if opc == 0x03 { // loads 73 handled = 1 74 let imm: i64 = r32_sx((w >> 20) & 0xFFF, 12) 75 let ea: i64 = (r[rs1] + imm) & R32_MASK 76 if f3 == 2 { r[rd] = r32_ld(mem, ea, 4) & R32_MASK } // lw 77 if f3 == 0 { r[rd] = r32_sx(r32_ld(mem, ea, 1), 8) & R32_MASK } 78 } 79 if opc == 0x23 { // stores 80 handled = 1 81 let imm: i64 = r32_sx((((w >> 25) & 0x7F) << 5) | ((w >> 7) & 0x1F), 12) 82 let ea: i64 = (r[rs1] + imm) & R32_MASK 83 if f3 == 2 { r32_st(mem, ea, 4, r[rs2] & R32_MASK) } // sw 84 } 85 if opc == 0x6F { // jal (J) 86 handled = 1 87 let imm: i64 = r32_sx(((( w >> 21) & 0x3FF) << 1) | (((w >> 20) & 1) << 11) | (((w >> 12) & 0xFF) << 12) | (((w >> 31) & 1) << 20), 21) 88 r[rd] = (pc + 4) & R32_MASK 89 next = pc + imm 90 } 91 if opc == 0x67 { // jalr (I) 92 handled = 1 93 let imm: i64 = r32_sx((w >> 20) & 0xFFF, 12) 94 let t: i64 = (pc + 4) & R32_MASK 95 next = (r[rs1] + imm) & (R32_MASK - 1) 96 r[rd] = t 97 } 98 if opc == 0x73 { // ecall 99 handled = 1 100 let nr: i64 = r[17] 101 if nr == R32_SYS_EXIT { result = r[10] & 0xff; halted = 1 } 102 } 103 if opc == 0x63 { // conditional branches (B-type) 104 // ADDED 2026-09-03. Without these RV32 could not run a LOOP at all, exactly the gap 105 // nx_isa_conform_gate named in mips64 the same day: an emulator that cannot branch 106 // cannot execute any real program, so "we emulate RV32" was not yet a true claim. 107 // Mirrors the RV64 sibling; the compares are done on SIGN-EXTENDED 32-bit values for 108 // blt/bge and on the raw masked values for bltu/bgeu, which is the whole difference 109 // between the signed and unsigned forms. 110 handled = 1 111 var bimm: i64 = (((w >> 31) & 1) << 12) | (((w >> 7) & 1) << 11) | ((((w >> 25) & R32_BIMM_HI_MASK)) << 5) | ((((w >> 8) & R32_BIMM_LO_MASK)) << 1) 112 if (bimm & R32_BIMM_SIGN) != 0 { bimm = bimm - R32_BIMM_SPAN } 113 let sa: i64 = r32_sx(r[rs1], R32_XLEN_BITS) 114 let sb: i64 = r32_sx(r[rs2], R32_XLEN_BITS) 115 var take: i64 = 0 116 if f3 == 0 { if r[rs1] == r[rs2] { take = 1 } } // beq 117 if f3 == 1 { if r[rs1] != r[rs2] { take = 1 } } // bne 118 if f3 == 4 { if sa < sb { take = 1 } } // blt (signed) 119 if f3 == 5 { if sa >= sb { take = 1 } } // bge (signed) 120 if f3 == 6 { if r[rs1] < r[rs2] { take = 1 } } // bltu (raw 32-bit) 121 if f3 == 7 { if r[rs1] >= r[rs2] { take = 1 } } // bgeu (raw 32-bit) 122 if take == 1 { next = pc + bimm } 123 } 124 if handled == 0 { result = 0 - 1; halted = 1 } 125 r[0] = 0 126 pc = next 127 steps = steps + 1 128 } 129 } 130 return result 131} 132 133func emu_riscv32_run(code: *u8, code_len: i64) -> i64 { 134 let mem: *u8 = sys_mmap(R32_GUEST) 135 var i: i64 = 0 136 while i < code_len { mem[i] = code[i]; i = i + 1 } 137 return emu_riscv32_run_mem(mem, R32_GUEST, 0, 0x00800000) 138}