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}