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]&reg[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 }