code wiki / _hdl_build / nx_rv64_fast.nx
nx_rv64_fast.nx source
↩ module page · 350 lines · 24598 B
1// nx_rv64_fast.nx -- a PREDECODED RV64 interpreter (the first perf-engineering step toward closing the QEMU-TCG gap
2// for the sovereign emulator). Where the behavioral sim re-decodes EVERY instruction EVERY step, this DECODES ONCE
3// (fills a decoded-op table indexed by word) then runs a tight dispatch loop -- so a hot loop pays decode cost only
4// per STATIC instruction, not per DYNAMIC step. Deterministic + bit-exact (nx_rv64_fast_gate proves equivalence with
5// the golden behavioral sim). RV64I base integer subset; flat memory @0x80000000; SiFive-finisher halt (store to
6// 0x100000). NOT a JIT (emits no native code) -- the honest decode-cache rung. Device-aware: routes the virtio-MMIO
7// window to the sovereign device model (rv64im_min_virtio), so the speedup applies to REAL driver workloads too, not
8// just pure compute. license_tier: ORIGINAL
9import "nx_syscalls.nx"
10import "nishi_hdl_primitives.nx"
11import "rv64im_min_virtio.nx"
12import "nx_rvc_expand.nx" // C (compressed) extension expander for the C-capable fast path
13
14const FK_ILL: i64 = 0
15const FK_LUI: i64 = 1
16const FK_ADDI: i64 = 2
17const FK_ANDI: i64 = 3
18const FK_ORI: i64 = 4
19const FK_XORI: i64 = 5
20const FK_SLLI: i64 = 6
21const FK_SRLI: i64 = 7
22const FK_SRAI: i64 = 8
23const FK_ADD: i64 = 9
24const FK_SUB: i64 = 10
25const FK_AND: i64 = 11
26const FK_OR: i64 = 12
27const FK_XOR: i64 = 13
28const FK_SLL: i64 = 14
29const FK_SRL: i64 = 15
30const FK_SRA: i64 = 16
31const FK_LW: i64 = 17
32const FK_LBU: i64 = 18
33const FK_LHU: i64 = 19
34const FK_LD: i64 = 20
35const FK_SW: i64 = 21
36const FK_SB: i64 = 22
37const FK_SH: i64 = 23
38const FK_SD: i64 = 24
39const FK_BEQ: i64 = 25
40const FK_BNE: i64 = 26
41const FK_BLT: i64 = 27
42const FK_BGE: i64 = 28
43const FK_BLTU: i64 = 29
44const FK_BGEU: i64 = 30
45const FK_JAL: i64 = 31
46const FK_JALR: i64 = 32
47const FK_MUL: i64 = 33
48const FK_DIV: i64 = 34
49const FK_DIVU: i64 = 35
50const FK_REM: i64 = 36
51const FK_REMU: i64 = 37
52const FK_MULH: i64 = 38
53const FK_MULHSU: i64 = 39
54const FK_MULHU: i64 = 40
55const FK_AUIPC: i64 = 41
56// RV64 *W (32-bit word) ops: operate on the low 32 bits, result SIGN-EXTENDED to 64. C `int` arithmetic uses these
57// constantly -- real compiled code is full of them (the fast family only tested 64-bit base ops before; this closes it).
58const FK_ADDIW: i64 = 42
59const FK_SLLIW: i64 = 43
60const FK_SRLIW: i64 = 44
61const FK_SRAIW: i64 = 45
62const FK_ADDW: i64 = 46
63const FK_SUBW: i64 = 47
64const FK_SLLW: i64 = 48
65const FK_SRLW: i64 = 49
66const FK_SRAW: i64 = 50
67const FK_MULW: i64 = 51
68const FK_DIVW: i64 = 52
69const FK_DIVUW: i64 = 53
70const FK_REMW: i64 = 54
71const FK_REMUW: i64 = 55
72const FK_AMOW: i64 = 56 // A extension, .w (32-bit); imm holds funct5
73const FK_AMOD: i64 = 57 // A extension, .d (64-bit)
74const FK_MEMBASE: i64 = 0x80000000
75const FK_FINISHER: i64 = 0x100000
76
77func fk_w32(c: *u8, o: i64) -> i64 { return (c[o] as i64)|((c[o+1] as i64)<<8)|((c[o+2] as i64)<<16)|((c[o+3] as i64)<<24) }
78func fk_sext(v: i64, bits: i64) -> i64 { let m: i64 = 1 << (bits-1); if (v & m) != 0 { return v - (1 << bits) } return v }
79func fk_imm_i(w: i64) -> i64 { return fk_sext((w>>20)&0xFFF, 12) }
80func fk_imm_s(w: i64) -> i64 { return fk_sext((((w>>25)&0x7F)<<5)|((w>>7)&0x1F), 12) }
81func fk_imm_b(w: i64) -> i64 { return fk_sext((((w>>31)&1)<<12)|(((w>>7)&1)<<11)|(((w>>25)&0x3F)<<5)|(((w>>8)&0xF)<<1), 13) }
82func fk_imm_u(w: i64) -> i64 { return fk_sext(w & 0xFFFFF000, 32) }
83func fk_imm_j(w: i64) -> i64 { return fk_sext((((w>>31)&1)<<20)|(((w>>12)&0xFF)<<12)|(((w>>20)&1)<<11)|(((w>>21)&0x3FF)<<1), 21) }
84func fk_ltu(x: i64, y: i64) -> i64 { if (x<0) != (y<0) { if x<0 { return 0 } return 1 } if x < y { return 1 } return 0 }
85func fk_srlmask(sh: i64) -> i64 { if sh==0 { return 0-1 } return (1 << (64-sh)) - 1 }
86func fk_sext32(v: i64) -> i64 { return fk_sext(v & 0xffffffff, 32) } // RV64 *W result: low 32 bits sign-extended to 64
87// OPTIONAL UART capture: a store to the 16550 THR (0x10000000) appends the byte to a caller-provided buffer, so fk can
88// run real UART-emitting programs and have its output checked (default off; purely additive -- registers/memory unchanged).
89const FK_UART_THR: i64 = 0x10000000
90static fk_txcap_ptr: i64 = 0
91static fk_txcnt: i64 = 0
92func fk_tx_reset(buf: *u8) -> i64 { fk_txcap_ptr = buf as i64; fk_txcnt = 0; return 0 }
93func fk_tx_count() -> i64 { return fk_txcnt }
94// A extension: atomic memory op. addr=reg[rs1], v=reg[rs2], f5=funct5, width=4/.w or 8/.d. Returns the rd value (old
95// mem for AMO, loaded value for LR, 0/1 success for SC). Single-hart reservation via a static addr. (6 args: nx_cc
96// >6-arg clobber-safe.)
97// nxasm_x86 cannot encode a NON-ZERO static initializer (G3_NONZERO_INIT_STATIC_UNSUPPORTED_BOTH_LANES).
98// This one line stopped 17 nx_rv64_*_gate builds; nx_rv64_fast has 50 importers. Initialising the ADDRESS
99// to 0 would be worse than the build break -- it would silently reserve address 0 at startup. Split the
100// sentinel into an address plus an explicit validity flag, both zero-init. Also fixes a latent bug: with
101// -1 as the sentinel, an SC to address -1 with NO prior LR compared equal and SUCCEEDED (RISC-V forbids).
102static fk_lr_addr: i64 = 0
103static fk_lr_held: i64 = 0
104func fk_amo(addr: i64, v: i64, f5: i64, mem: *u8, membytes: i64, width: i64) -> i64 {
105 let o: i64 = addr - FK_MEMBASE
106 if fk_memok(o, width, membytes)==0 { return 0 }
107 if f5==2 { var t: i64=fk_ld(mem,o,width); if width==4 { t=fk_sext32(t) } fk_lr_addr=addr; fk_lr_held=1; return t } // LR: load + reserve
108 if f5==3 { if fk_lr_held==1 { if fk_lr_addr==addr { fk_st(mem,o,width,v); fk_lr_held=0; return 0 } } return 1 } // SC: store-if-reserved (single-hart)
109 var orig: i64=fk_ld(mem,o,width); if width==4 { orig=fk_sext32(orig) } // AMO: rd=old, mem=op(old,v)
110 var res: i64=0
111 if f5==0 { res=orig+v } if f5==1 { res=v } if f5==4 { res=orig^v } if f5==8 { res=orig|v } if f5==12 { res=orig&v }
112 var ro: i64=v; if width==4 { ro=fk_sext32(v) }
113 if f5==16 { if orig<ro { res=orig } else { res=v } } // amomin (signed)
114 if f5==20 { if orig>ro { res=orig } else { res=v } } // amomax (signed)
115 var lu: i64=orig; var ru: i64=v; if width==4 { lu=orig&0xffffffff; ru=v&0xffffffff }
116 if f5==24 { if fk_ltu(lu,ru)==1 { res=orig } else { res=v } } // amominu
117 if f5==28 { if fk_ltu(lu,ru)==1 { res=v } else { res=orig } } // amomaxu
118 fk_st(mem,o,width,res)
119 return orig
120}
121// unsigned 64-bit divide/remainder (b!=0). NishiLang '/' is signed, so use long division with UNSIGNED compares (fk_ltu).
122func udiv_mod(a: i64, b: i64, wantrem: i64) -> i64 {
123 if b < 0 { if fk_ltu(a,b)==1 { if wantrem==1 { return a } return 0 } if wantrem==1 { return a-b } return 1 } // b>=2^63: quotient 0/1
124 var q: i64=0; var rem: i64=0; var i: i64=63
125 while i>=0 { rem=(rem<<1)|((a>>i)&1); if fk_ltu(rem,b)==0 { rem=rem-b; q=q|(1<<i) } i=i-1 }
126 if wantrem==1 { return rem }
127 return q
128}
129// signed div/rem with RV64 semantics: div-by-zero -> -1 (div) / dividend (rem); INT_MIN/-1 overflow -> INT_MIN (div) / 0 (rem).
130func sdiv64(a: i64, b: i64) -> i64 { if b==0 { return 0-1 } if a==(1<<63) { if b==(0-1) { return a } } return a/b }
131func srem64(a: i64, b: i64) -> i64 { if b==0 { return a } if a==(1<<63) { if b==(0-1) { return 0 } } return a%b }
132// UNSIGNED high 64 bits of a 64x64->128 product (Hacker's Delight mulhu on 32-bit halves). the M-ext MULHU. NOTE:
133// NishiLang '>>' is ARITHMETIC, and a 32x32 product can set bit 63 -> every carry-out shift is MASKED (&0xFFFFFFFF)
134// to recover the true unsigned top-32; only the FINAL sum is left full-width (it IS the 64-bit high result).
135func mulhu64(a: i64, b: i64) -> i64 {
136 let s32: i64 = 32 // nx_cc: an IMMEDIATE shift >31 blanks the .s -- use a VARIABLE shift amount
137 let u0: i64=a&0xFFFFFFFF; let u1: i64=(a>>s32)&0xFFFFFFFF; let v0: i64=b&0xFFFFFFFF; let v1: i64=(b>>s32)&0xFFFFFFFF
138 let w0: i64=u0*v0
139 let t: i64=u1*v0 + ((w0>>s32)&0xFFFFFFFF)
140 let w1lo: i64=t&0xFFFFFFFF; let w2: i64=(t>>s32)&0xFFFFFFFF
141 let w1: i64=u0*v1 + w1lo
142 return u1*v1 + w2 + ((w1>>s32)&0xFFFFFFFF)
143}
144// SIGNED high product (MULH) + signed*unsigned (MULHSU): correct the unsigned high by subtracting sign*other when negative.
145func mulh64(a: i64, b: i64) -> i64 { var h: i64=mulhu64(a,b); if a<0 { h=h-b } if b<0 { h=h-a } return h }
146func mulhsu64(a: i64, b: i64) -> i64 { var h: i64=mulhu64(a,b); if a<0 { h=h-b } return h } // a signed, b unsigned
147
148// PREDECODE: decode nbytes/4 words into the caller's decoded arrays ONCE. returns instruction count.
149func fk_predecode(code: *u8, nbytes: i64, opk: *i64, rd: *i64, rs1: *i64, rs2: *i64, imm: *i64) -> i64 {
150 let n: i64 = nbytes / 4
151 var i: i64 = 0
152 while i < n {
153 let w: i64 = fk_w32(code, i*4)
154 let op: i64 = w & 0x7F; let f3: i64 = (w>>12)&7; let f7: i64 = (w>>25)&0x7F
155 rd[i]=(w>>7)&0x1F; rs1[i]=(w>>15)&0x1F; rs2[i]=(w>>20)&0x1F; opk[i]=FK_ILL; imm[i]=0
156 if op==0x37 { opk[i]=FK_LUI; imm[i]=fk_imm_u(w) }
157 if op==0x17 { opk[i]=FK_AUIPC; imm[i]=fk_imm_u(w) } // AUIPC (base RV64I): rd = pc + (imm<<12); PC-RELATIVE, so correct in fk's 0-based frame (the offset is base-independent)
158 if op==0x13 { imm[i]=fk_imm_i(w)
159 if f3==0 { opk[i]=FK_ADDI } if f3==7 { opk[i]=FK_ANDI } if f3==6 { opk[i]=FK_ORI } if f3==4 { opk[i]=FK_XORI }
160 if f3==1 { opk[i]=FK_SLLI; imm[i]=(w>>20)&0x3F } if f3==5 { if f7==0x20 { opk[i]=FK_SRAI } else { opk[i]=FK_SRLI } imm[i]=(w>>20)&0x3F } }
161 if op==0x33 {
162 if f7==0x01 { if f3==0 { opk[i]=FK_MUL } if f3==1 { opk[i]=FK_MULH } if f3==2 { opk[i]=FK_MULHSU } if f3==3 { opk[i]=FK_MULHU } if f3==4 { opk[i]=FK_DIV } if f3==5 { opk[i]=FK_DIVU } if f3==6 { opk[i]=FK_REM } if f3==7 { opk[i]=FK_REMU } } // FULL M extension
163 else {
164 if f3==0 { if f7==0x20 { opk[i]=FK_SUB } else { opk[i]=FK_ADD } } if f3==7 { opk[i]=FK_AND } if f3==6 { opk[i]=FK_OR } if f3==4 { opk[i]=FK_XOR }
165 if f3==1 { opk[i]=FK_SLL } if f3==5 { if f7==0x20 { opk[i]=FK_SRA } else { opk[i]=FK_SRL } } } }
166 if op==0x1B { imm[i]=fk_imm_i(w) // OP-IMM-32 (*W immediate)
167 if f3==0 { opk[i]=FK_ADDIW }
168 if f3==1 { opk[i]=FK_SLLIW; imm[i]=(w>>20)&0x1F } if f3==5 { if f7==0x20 { opk[i]=FK_SRAIW } else { opk[i]=FK_SRLIW } imm[i]=(w>>20)&0x1F } }
169 if op==0x3B { // OP-32 (*W register)
170 if f7==0x01 { if f3==0 { opk[i]=FK_MULW } if f3==4 { opk[i]=FK_DIVW } if f3==5 { opk[i]=FK_DIVUW } if f3==6 { opk[i]=FK_REMW } if f3==7 { opk[i]=FK_REMUW } }
171 else { if f3==0 { if f7==0x20 { opk[i]=FK_SUBW } else { opk[i]=FK_ADDW } } if f3==1 { opk[i]=FK_SLLW } if f3==5 { if f7==0x20 { opk[i]=FK_SRAW } else { opk[i]=FK_SRLW } } } }
172 if op==0x2F { let f5: i64=(w>>27)&0x1F; if f3==2 { opk[i]=FK_AMOW; imm[i]=f5 } if f3==3 { opk[i]=FK_AMOD; imm[i]=f5 } } // A extension (AMO/LR/SC)
173 if op==0x03 { imm[i]=fk_imm_i(w); if f3==2 { opk[i]=FK_LW } if f3==4 { opk[i]=FK_LBU } if f3==5 { opk[i]=FK_LHU } if f3==3 { opk[i]=FK_LD } }
174 if op==0x23 { imm[i]=fk_imm_s(w); if f3==2 { opk[i]=FK_SW } if f3==0 { opk[i]=FK_SB } if f3==1 { opk[i]=FK_SH } if f3==3 { opk[i]=FK_SD } }
175 if op==0x63 { imm[i]=fk_imm_b(w); if f3==0 { opk[i]=FK_BEQ } if f3==1 { opk[i]=FK_BNE } if f3==4 { opk[i]=FK_BLT } if f3==5 { opk[i]=FK_BGE } if f3==6 { opk[i]=FK_BLTU } if f3==7 { opk[i]=FK_BGEU } }
176 if op==0x6F { opk[i]=FK_JAL; imm[i]=fk_imm_j(w) }
177 if op==0x67 { opk[i]=FK_JALR; imm[i]=fk_imm_i(w) }
178 i=i+1
179 }
180 return n
181}
182func fk_ld(mem: *u8, o: i64, w: i64) -> i64 { var v: i64=0; var i: i64=0; while i<w { v=v|((mem[o+i] as i64)<<(i*8)); i=i+1 } return v }
183func fk_st(mem: *u8, o: i64, w: i64, val: i64) -> i64 { var i: i64=0; while i<w { mem[o+i]=((val>>(i*8))&0xff) as u8; i=i+1 } return 0 }
184func fk_memok(o: i64, w: i64, membytes: i64) -> i64 { if o<0 { return 0 } if o+w>membytes { return 0 } return 1 }
185// is addr inside this (non-null) virtio device's 256-byte MMIO window? 1/0.
186func fk_mmio_hit(dev: *NxVirtioMmio, addr: i64) -> i64 { if (dev as i64)==0 { return 0 } if addr < dev.base { return 0 } if addr >= dev.base + 0x100 { return 0 } return 1 }
187
188// SINGLE-STEP the interpreter: execute the op at idx (pc = idx<<2), update reg[]/mem, return the next pc. haltbox[0]
189// is set to 1 iff this op halts (finisher store or illegal). SHARED by fk_run AND the tiered engine (nx_rv64_tier) so
190// the interpreter semantics cannot diverge between them.
191func fk_step(opk: *i64, rd: *i64, rs1: *i64, rs2: *i64, imm: *i64, idx: i64, pc: i64, ilen: i64, reg: *i64, mem: *u8, membytes: i64, dev: *NxVirtioMmio, valbox: *i64, haltbox: *i64) -> i64 {
192 let k: i64=opk[idx]; let d: i64=rd[idx]; let a: i64=rs1[idx]; let b: i64=rs2[idx]; let im: i64=imm[idx]
193 var next: i64 = pc + ilen // ilen = 4 (32-bit) or 2 (compressed); default sequential advance
194 if k==FK_ADDI { if d!=0 { reg[d]=reg[a]+im } }
195 if k==FK_ADD { if d!=0 { reg[d]=reg[a]+reg[b] } }
196 if k==FK_SUB { if d!=0 { reg[d]=reg[a]-reg[b] } }
197 if k==FK_LUI { if d!=0 { reg[d]=im } }
198 if k==FK_AUIPC { if d!=0 { reg[d]=pc+im } } // pc is REAL (0x80000000-based) -> reg[d] is a real address (correct for both control flow AND data/pointer use)
199 if k==FK_AMOW { let r: i64=fk_amo(reg[a], reg[b], im, mem, membytes, 4); if d!=0 { reg[d]=r } } // A ext .w
200 if k==FK_AMOD { let r: i64=fk_amo(reg[a], reg[b], im, mem, membytes, 8); if d!=0 { reg[d]=r } } // A ext .d
201 if k==FK_AND { if d!=0 { reg[d]=reg[a]®[b] } }
202 if k==FK_OR { if d!=0 { reg[d]=reg[a]|reg[b] } }
203 if k==FK_XOR { if d!=0 { reg[d]=reg[a]^reg[b] } }
204 if k==FK_ANDI { if d!=0 { reg[d]=reg[a]&im } }
205 if k==FK_ORI { if d!=0 { reg[d]=reg[a]|im } }
206 if k==FK_XORI { if d!=0 { reg[d]=reg[a]^im } }
207 if k==FK_SLLI { if d!=0 { reg[d]=reg[a]<<im } }
208 if k==FK_SRLI { if d!=0 { reg[d]=(reg[a]>>im)&fk_srlmask(im) } }
209 if k==FK_SRAI { if d!=0 { reg[d]=reg[a]>>im } }
210 if k==FK_SLL { if d!=0 { reg[d]=reg[a]<<(reg[b]&0x3F) } }
211 if k==FK_SRL { let sh: i64=reg[b]&0x3F; if d!=0 { reg[d]=(reg[a]>>sh)&fk_srlmask(sh) } }
212 if k==FK_SRA { if d!=0 { reg[d]=reg[a]>>(reg[b]&0x3F) } }
213 if k==FK_MUL { if d!=0 { reg[d]=reg[a]*reg[b] } }
214 if k==FK_MULH { if d!=0 { reg[d]=mulh64(reg[a],reg[b]) } }
215 if k==FK_MULHU { if d!=0 { reg[d]=mulhu64(reg[a],reg[b]) } }
216 if k==FK_MULHSU { if d!=0 { reg[d]=mulhsu64(reg[a],reg[b]) } }
217 if k==FK_DIV { if d!=0 { reg[d]=sdiv64(reg[a],reg[b]) } }
218 if k==FK_REM { if d!=0 { reg[d]=srem64(reg[a],reg[b]) } }
219 if k==FK_DIVU { if d!=0 { if reg[b]==0 { reg[d]=0-1 } else { reg[d]=udiv_mod(reg[a],reg[b],0) } } }
220 if k==FK_REMU { if d!=0 { if reg[b]==0 { reg[d]=reg[a] } else { reg[d]=udiv_mod(reg[a],reg[b],1) } } }
221 // *W (32-bit word) ops -- compute on low 32 bits, sign-extend to 64. shifts use 5-bit shamt.
222 if k==FK_ADDIW { if d!=0 { reg[d]=fk_sext32(reg[a]+im) } }
223 if k==FK_SLLIW { if d!=0 { reg[d]=fk_sext32(reg[a]<<im) } }
224 if k==FK_SRLIW { if d!=0 { reg[d]=fk_sext32((reg[a]&0xffffffff)>>im) } }
225 if k==FK_SRAIW { if d!=0 { reg[d]=fk_sext32(fk_sext32(reg[a])>>im) } }
226 if k==FK_ADDW { if d!=0 { reg[d]=fk_sext32(reg[a]+reg[b]) } }
227 if k==FK_SUBW { if d!=0 { reg[d]=fk_sext32(reg[a]-reg[b]) } }
228 if k==FK_SLLW { if d!=0 { reg[d]=fk_sext32(reg[a]<<(reg[b]&0x1F)) } }
229 if k==FK_SRLW { if d!=0 { reg[d]=fk_sext32((reg[a]&0xffffffff)>>(reg[b]&0x1F)) } }
230 if k==FK_SRAW { if d!=0 { reg[d]=fk_sext32(fk_sext32(reg[a])>>(reg[b]&0x1F)) } }
231 if k==FK_MULW { if d!=0 { reg[d]=fk_sext32(reg[a]*reg[b]) } }
232 if k==FK_DIVW { if d!=0 { reg[d]=fk_sext32(sdiv64(fk_sext32(reg[a]),fk_sext32(reg[b]))) } }
233 if k==FK_REMW { if d!=0 { reg[d]=fk_sext32(srem64(fk_sext32(reg[a]),fk_sext32(reg[b]))) } }
234 if k==FK_DIVUW { if d!=0 { let bb: i64=reg[b]&0xffffffff; if bb==0 { reg[d]=0-1 } else { reg[d]=fk_sext32(udiv_mod(reg[a]&0xffffffff,bb,0)) } } }
235 if k==FK_REMUW { if d!=0 { let bb: i64=reg[b]&0xffffffff; if bb==0 { reg[d]=fk_sext32(reg[a]&0xffffffff) } else { reg[d]=fk_sext32(udiv_mod(reg[a]&0xffffffff,bb,1)) } } }
236 if k==FK_BEQ { if reg[a]==reg[b] { next=pc+im } }
237 if k==FK_BNE { if reg[a]!=reg[b] { next=pc+im } }
238 if k==FK_BLT { if reg[a]<reg[b] { next=pc+im } }
239 if k==FK_BGE { if reg[a]>=reg[b] { next=pc+im } }
240 if k==FK_BLTU { if fk_ltu(reg[a],reg[b])==1 { next=pc+im } }
241 if k==FK_BGEU { if fk_ltu(reg[a],reg[b])==0 { next=pc+im } }
242 if k==FK_JAL { if d!=0 { reg[d]=pc+ilen } next=pc+im }
243 if k==FK_JALR { let t: i64=(reg[a]+im)&(0-2); if d!=0 { reg[d]=pc+ilen } next=t }
244 if k==FK_LW { let addr: i64=reg[a]+im
245 if fk_mmio_hit(dev, addr)==1 { nx_virtio_read32(dev, addr, valbox); if d!=0 { reg[d]=fk_sext(valbox[0]&0xffffffff,32) } }
246 else { let o: i64=addr-FK_MEMBASE; if fk_memok(o,4,membytes)==1 { if d!=0 { reg[d]=fk_sext(fk_ld(mem,o,4),32) } } } }
247 if k==FK_LBU { let o: i64=(reg[a]+im)-FK_MEMBASE; if fk_memok(o,1,membytes)==1 { if d!=0 { reg[d]=fk_ld(mem,o,1) } } }
248 if k==FK_LHU { let o: i64=(reg[a]+im)-FK_MEMBASE; if fk_memok(o,2,membytes)==1 { if d!=0 { reg[d]=fk_ld(mem,o,2) } } }
249 if k==FK_LD { let o: i64=(reg[a]+im)-FK_MEMBASE; if fk_memok(o,8,membytes)==1 { if d!=0 { reg[d]=fk_ld(mem,o,8) } } }
250 if k==FK_SW { let addr: i64=reg[a]+im
251 if addr==FK_FINISHER { haltbox[0]=1 }
252 else { if fk_mmio_hit(dev, addr)==1 { nx_virtio_write32(dev, addr, reg[b]&0xffffffff); if (addr - dev.base)==0x50 { nx_virtio_notify_dma(dev, mem, FK_MEMBASE, membytes) } }
253 else { let o: i64=addr-FK_MEMBASE; if fk_memok(o,4,membytes)==1 { fk_st(mem,o,4,reg[b]) } } } }
254 if k==FK_SB { let addr: i64=reg[a]+im
255 if addr==FK_UART_THR { if fk_txcap_ptr!=0 { let cb: *u8=fk_txcap_ptr as *u8; cb[fk_txcnt]=(reg[b]&0xff) as u8; fk_txcnt=fk_txcnt+1 } }
256 let o: i64=addr-FK_MEMBASE; if fk_memok(o,1,membytes)==1 { fk_st(mem,o,1,reg[b]) } }
257 if k==FK_SH { let o: i64=(reg[a]+im)-FK_MEMBASE; if fk_memok(o,2,membytes)==1 { fk_st(mem,o,2,reg[b]) } }
258 if k==FK_SD { let o: i64=(reg[a]+im)-FK_MEMBASE; if fk_memok(o,8,membytes)==1 { fk_st(mem,o,8,reg[b]) } }
259 if k==FK_ILL { haltbox[0]=1 }
260 return next
261}
262
263// RUN the predecoded program (flat dispatch on the pre-decoded kind -- NO re-decode). reg[32], flat mem
264// (base 0x80000000, size membytes). halts on the SiFive-finisher store or on running off the decoded range.
265// returns steps executed.
266func fk_run(opk: *i64, rd: *i64, rs1: *i64, rs2: *i64, imm: *i64, ncount: i64, reg: *i64, mem: *u8, membytes: i64, maxsteps: i64, dev: *NxVirtioMmio) -> i64 {
267 var pc: i64 = FK_MEMBASE; var steps: i64 = 0; var halt: i64 = 0 // pc is REAL (0x80000000-based) -> auipc/jalr/globals/fn-ptrs work like real hardware
268 let valbox: *i64 = sys_mmap(16) as *i64
269 let haltbox: *i64 = sys_mmap(16) as *i64
270 reg[0]=0
271 while halt==0 {
272 if steps >= maxsteps { halt=1 } else {
273 let idx: i64 = (pc - FK_MEMBASE) >> 2
274 if idx < 0 { halt=1 } else { if idx >= ncount { halt=1 } else {
275 haltbox[0]=0
276 let next: i64 = fk_step(opk, rd, rs1, rs2, imm, idx, pc, 4, reg, mem, membytes, dev, valbox, haltbox)
277 if haltbox[0]==1 { halt=1 }
278 steps=steps+1 // count the executed instruction (incl. the halting finisher store), matching the golden sim
279 if halt==0 { pc=next }
280 } }
281 }
282 }
283 return steps
284}
285// ===== C-CAPABLE FAST PATH (variable-length, halfword-indexed) -- runs REAL compressed rv64imac binaries on fk =====
286// decode ONE 32-bit word into slot i (shared by the C predecode). (mirrors fk_predecode's per-instr decode)
287func fk_decode_one(w: i64, i: i64, opk: *i64, rd: *i64, rs1: *i64, rs2: *i64, imm: *i64) -> i64 {
288 let op: i64 = w & 0x7F; let f3: i64 = (w>>12)&7; let f7: i64 = (w>>25)&0x7F
289 rd[i]=(w>>7)&0x1F; rs1[i]=(w>>15)&0x1F; rs2[i]=(w>>20)&0x1F; opk[i]=FK_ILL; imm[i]=0
290 if op==0x37 { opk[i]=FK_LUI; imm[i]=fk_imm_u(w) }
291 if op==0x17 { opk[i]=FK_AUIPC; imm[i]=fk_imm_u(w) }
292 if op==0x13 { imm[i]=fk_imm_i(w)
293 if f3==0 { opk[i]=FK_ADDI } if f3==7 { opk[i]=FK_ANDI } if f3==6 { opk[i]=FK_ORI } if f3==4 { opk[i]=FK_XORI }
294 if f3==1 { opk[i]=FK_SLLI; imm[i]=(w>>20)&0x3F } if f3==5 { if f7==0x20 { opk[i]=FK_SRAI } else { opk[i]=FK_SRLI } imm[i]=(w>>20)&0x3F } }
295 if op==0x33 {
296 if f7==0x01 { if f3==0 { opk[i]=FK_MUL } if f3==1 { opk[i]=FK_MULH } if f3==2 { opk[i]=FK_MULHSU } if f3==3 { opk[i]=FK_MULHU } if f3==4 { opk[i]=FK_DIV } if f3==5 { opk[i]=FK_DIVU } if f3==6 { opk[i]=FK_REM } if f3==7 { opk[i]=FK_REMU } }
297 else { if f3==0 { if f7==0x20 { opk[i]=FK_SUB } else { opk[i]=FK_ADD } } if f3==7 { opk[i]=FK_AND } if f3==6 { opk[i]=FK_OR } if f3==4 { opk[i]=FK_XOR }
298 if f3==1 { opk[i]=FK_SLL } if f3==5 { if f7==0x20 { opk[i]=FK_SRA } else { opk[i]=FK_SRL } } } }
299 if op==0x1B { imm[i]=fk_imm_i(w)
300 if f3==0 { opk[i]=FK_ADDIW }
301 if f3==1 { opk[i]=FK_SLLIW; imm[i]=(w>>20)&0x1F } if f3==5 { if f7==0x20 { opk[i]=FK_SRAIW } else { opk[i]=FK_SRLIW } imm[i]=(w>>20)&0x1F } }
302 if op==0x3B {
303 if f7==0x01 { if f3==0 { opk[i]=FK_MULW } if f3==4 { opk[i]=FK_DIVW } if f3==5 { opk[i]=FK_DIVUW } if f3==6 { opk[i]=FK_REMW } if f3==7 { opk[i]=FK_REMUW } }
304 else { if f3==0 { if f7==0x20 { opk[i]=FK_SUBW } else { opk[i]=FK_ADDW } } if f3==1 { opk[i]=FK_SLLW } if f3==5 { if f7==0x20 { opk[i]=FK_SRAW } else { opk[i]=FK_SRLW } } } }
305 if op==0x2F { let f5: i64=(w>>27)&0x1F; if f3==2 { opk[i]=FK_AMOW; imm[i]=f5 } if f3==3 { opk[i]=FK_AMOD; imm[i]=f5 } }
306 if op==0x03 { imm[i]=fk_imm_i(w); if f3==2 { opk[i]=FK_LW } if f3==4 { opk[i]=FK_LBU } if f3==5 { opk[i]=FK_LHU } if f3==3 { opk[i]=FK_LD } }
307 if op==0x23 { imm[i]=fk_imm_s(w); if f3==2 { opk[i]=FK_SW } if f3==0 { opk[i]=FK_SB } if f3==1 { opk[i]=FK_SH } if f3==3 { opk[i]=FK_SD } }
308 if op==0x63 { imm[i]=fk_imm_b(w); if f3==0 { opk[i]=FK_BEQ } if f3==1 { opk[i]=FK_BNE } if f3==4 { opk[i]=FK_BLT } if f3==5 { opk[i]=FK_BGE } if f3==6 { opk[i]=FK_BLTU } if f3==7 { opk[i]=FK_BGEU } }
309 if op==0x6F { opk[i]=FK_JAL; imm[i]=fk_imm_j(w) }
310 if op==0x67 { opk[i]=FK_JALR; imm[i]=fk_imm_i(w) }
311 return 0
312}
313// PREDECODE with C ext: variable-length walk, HALFWORD-indexed (slot = byteoffset>>1). Compressed (h&3 != 3) is
314// expanded to 32-bit; the 2nd halfword of a 32-bit instr is a HOLE (FK_ILL). returns the halfword-slot count.
315func fk_predecode_c(code: *u8, nbytes: i64, opk: *i64, rd: *i64, rs1: *i64, rs2: *i64, imm: *i64) -> i64 {
316 let nhalf: i64 = nbytes / 2
317 var h: i64 = 0; while h < nhalf { opk[h]=FK_ILL; rd[h]=0; rs1[h]=0; rs2[h]=0; imm[h]=0; h=h+1 }
318 var bo: i64 = 0
319 while bo + 2 <= nbytes {
320 let half: i64 = (code[bo] as i64) | ((code[bo+1] as i64) << 8)
321 var w: i64 = 0
322 if nx_rvc_is_compressed(half) == 1 { w = nx_rvc_expand(half); fk_decode_one(w, bo>>1, opk, rd, rs1, rs2, imm); bo = bo + 2 }
323 else { if bo + 4 <= nbytes { w = fk_w32(code, bo) } fk_decode_one(w, bo>>1, opk, rd, rs1, rs2, imm); bo = bo + 4 }
324 }
325 return nhalf
326}
327// RUN with C ext: HALFWORD-indexed (idx = (pc-base)>>1); the instruction length is derived from the code halfword in
328// mem (compressed=2/32-bit=4) and passed to the shared fk_step. Runs real rv64imac programs. (UART capture via fk_step.)
329func fk_run_c(opk: *i64, rd: *i64, rs1: *i64, rs2: *i64, imm: *i64, nhalf: i64, reg: *i64, mem: *u8, membytes: i64, maxsteps: i64, dev: *NxVirtioMmio) -> i64 {
330 var pc: i64 = FK_MEMBASE; var steps: i64 = 0; var halt: i64 = 0
331 let valbox: *i64 = sys_mmap(16) as *i64; let haltbox: *i64 = sys_mmap(16) as *i64
332 reg[0]=0
333 while halt==0 {
334 if steps >= maxsteps { halt=1 } else {
335 let o: i64 = pc - FK_MEMBASE
336 let idx: i64 = o >> 1
337 if idx < 0 { halt=1 } else { if idx >= nhalf { halt=1 } else {
338 var ilen: i64 = 4
339 if o+1 < membytes { let half: i64 = (mem[o] as i64) | ((mem[o+1] as i64)<<8); if (half&3)!=3 { ilen=2 } }
340 haltbox[0]=0
341 let next: i64 = fk_step(opk, rd, rs1, rs2, imm, idx, pc, ilen, reg, mem, membytes, dev, valbox, haltbox)
342 if haltbox[0]==1 { halt=1 }
343 steps=steps+1
344 if halt==0 { pc=next }
345 } }
346 }
347 }
348 return steps
349}
350func main() -> i64 { return 0 }