diff options
| author | Cliff Click <cliffc@acm.org> | 2026-10-01 14:58:31 -0700 |
|---|---|---|
| committer | Cliff Click <cliffc@acm.org> | 2026-10-01 14:58:31 -0700 |
| commit | 5dd5b7a2d03885cb767bbf2b609d56f8ef897301 (patch) | |
| tree | f524a9d000c588fa99fc3808e0d37012885cdf7e /isa/src/test-support/java | |
| parent | 154dd8b03010a96a48d7903e69394bcc4606c027 (diff) | |
| download | Simple-5dd5b7a2d03885cb767bbf2b609d56f8ef897301.tar.gz Simple-5dd5b7a2d03885cb767bbf2b609d56f8ef897301.zip | |
Move much of the ISA support to shared code
The ISA shared handlers here are required for a proper compiler, but contain only trivial bit-massaging operations.
Diffstat (limited to 'isa/src/test-support/java')
| -rw-r--r-- | isa/src/test-support/java/com/seaofnodes/isa/eval/EvalArm64.java | 775 | ||||
| -rw-r--r-- | isa/src/test-support/java/com/seaofnodes/isa/eval/EvalRisc5.java | 370 |
2 files changed, 1145 insertions, 0 deletions
diff --git a/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalArm64.java b/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalArm64.java new file mode 100644 index 00000000..2a4a2708 --- /dev/null +++ b/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalArm64.java @@ -0,0 +1,775 @@ +package com.seaofnodes.isa.eval; + +import com.seaofnodes.isa.Arm64; +import java.io.ByteArrayOutputStream; + +public class EvalArm64 { + // Memory image, always 0-based + public final byte[] _buf; + + // GPRs + public final long[] regs; + // FRs + public final double[] fregs; + + public int _pc; + + // Start of free memory for allocation + public int _heap; + + // Standard out and err streams + public ByteArrayOutputStream _stdout, _stderr; + + // Cycle counters + public int _cycle; + + // flags + public boolean N = false; + public boolean Z = false; + public boolean C = false; + public boolean V = false; + + // Default test-runtime entry points; callers can supply another link convention. + private final int _calloc, _write; + public EvalArm64(byte[] buf, int stackSize) { this(buf,stackSize,-4,-8); } + public EvalArm64(byte[] buf, int stackSize, int calloc, int write) { + _calloc = calloc; + _write = write; + _buf = buf; + regs = new long[32]; + fregs = new double[32]; + // Stack grows down, heap grows up + regs[31] = _heap = stackSize; + _pc = 0; + } + + // Little endian + public int ld1s(int x) { return _buf[x ] ; } + public int ld1z(int x) { return _buf[x ]&0xFF; } + public int ld2s(int x) { return ld1z(x) | ld1s(x+1)<< 8; } + public int ld2z(int x) { return ld1z(x) | ld1z(x+1)<< 8; } + public int ld4s(int x) { return ld2z(x) | ld2s(x+2)<<16; } + public long ld8 (int x) { return (long)ld4s(x)&0xFFFFFFFFL | ((long)ld4s(x+4))<<32; } + + + public float ld4f(int x) { return Float.intBitsToFloat(ld4s(x)); } + public double ld8f(int x) { return Double.longBitsToDouble(ld8(x)); } + + public void st1(int x, int val) { _buf[x] = (byte)val; } + public void st2(int x, int val) { st1(x,val); st1(x+1,val>> 8); } + public void st4(int x, int val) { st2(x,val); st2(x+2,val>>16); } + public void st8(int x, long val) { st4(x,(int)val); st4(x+4,(int)(val>>32)); } + + public int step(int maxops) { + int trap = 0; + long rval = 0; + double frval = 0; + int pc = _pc; + int cycle = _cycle; + outer: + for(int icount = 0; icount < maxops; icount++) { + boolean is_f = false; + int ir = 0; + rval = 0; + frval = 0; + cycle++; + if( pc >= _buf.length ) { + trap = 1 + 1; // Handle access violation on instruction read. + break; + } + if( (pc & 3)!=0 ) { + trap = 1; // Handle PC-misaligned access + break; + } + + // Load instruction from image buffer at PC + ir = ld4s( pc ); + + int rdid = ir & 0x1f; + // [31:24] + int opcode1 = (ir >> 24) & 0xFF; + + // for the opcode we just match for bits[31:24]; + switch( opcode1 ) { + case 0x14, 0x15, 0x16, 0x17: { //b + int imm26 = ir << 6 >> 6; + pc = pc + (imm26 * 4) - 4; + rdid = -1; + break; + } + case 0x1E: { // floats + is_f = true; + int rs1 = (ir >> 5) & 0x1F; + int rs2 = (ir >> 16) & 0x1F; + + int op = (ir >> 10) & 0x3F; + switch(op) { + case 16: frval = fregs[rs1]; break; // fmov + case 10: frval = fregs[rs1] + fregs[rs2]; break; // fadd + + case 6: if (fregs[rs2] == 0) frval = 0; else frval = fregs[rs1] / fregs[rs2]; break; // fdiv + case 8: { + // fcmp + double lhs = fregs[rs1]; + double rhs = fregs[rs2]; + + Z = (lhs == rhs); + N = (lhs < rhs); + C = (lhs > rhs); + V = Double.isNaN(lhs) || Double.isNaN(rhs); + rdid = -1; + break; + } + case 2: frval = fregs[rs1] * fregs[rs2]; break; // fmul + case 14: frval = fregs[rs1] - fregs[rs2]; break; // fsub + default: trap = (2+1); + } + break; + } + + case 0x38: { + // STRB/LDRB(register) + int rn = (ir >> 5) & 0x1F; + int rm = (ir >> 16) & 0x1F; + // full opcode + opcode1 = (ir >> 21) & 0x7FF; + switch( ir >> 21 & 0x7 ) { + case 0x1 -> { // store + if( opcode1 == Arm64.OP_STORE_R_8 ) st1((int)regs[rn] + (int)regs[rm], (int)regs[rdid]); + else trap = (2+1); + rdid = -1; + } + case 0x3 -> { // load + if( opcode1 == Arm64.OP_LOAD_R_8 ) rval = ld1s((int)regs[rn] + (int)regs[rm]); + else trap = (2+1); + } + default -> trap = (2+1); + } + break; + } + case 0x39: { + // STRB/LDRB(immediate) + // str/ldr(immediate) + // rn + int base = ir >> 5 & 0x1F; + int imm = (ir >> 10) & 0xFFF; + rval = regs[base] + imm; + int opc = (ir >> 22) & 0x3; + // full opcode + opcode1 = (ir >> 22) & 0x3FF; + + switch(opc) { + case 0x0: { + // store + if(opcode1 == Arm64.OP_STORE_IMM_8) { + st1((int)rval, (int)regs[rdid]); + } else trap = (2+1); + rdid = -1; + + break; + } + case 0x1: { + // LDR (immediate) + if(opcode1 == Arm64.OP_LOAD_IMM_8) { + rval = ld1s((int)rval); + } else trap = (2+1); + rdid = -1; + break; + } + default: trap = (2+1); + } + break; + } + case 0x54: { + // b.cond + int imm19 = ir << 8 >> 13; + imm19 = pc + (imm19 * 4) - 4; + rdid = -1; + + switch(ir & 0xF) { + case 0x0: if(Z) pc = imm19; break; // eq + case 0x1: if(!Z) pc = imm19; break; // ne + case 0x2: if(C) pc = imm19; break; // cs + case 0x3: if(!C) pc = imm19; break; + case 0x4: if(N) pc = imm19; break; // mi + case 0x5: if(!N) pc = imm19; break; // pl + case 0x6: if(V) pc = imm19; break; // vs + case 0x7: if(!V) pc = imm19; break; // vc + case 0x8: if(C && !Z) pc = imm19; break; // hi + case 0x9: if(!C || Z) pc = imm19; break; // ls + case 0xA: if(N == V) pc = imm19; break; // ge + case 0xB: if(N != V) pc = imm19; break; // lt + case 0xC: if(!Z && N == V) pc = imm19; break; // gt + case 0xD: if(Z || N != V) pc = imm19; break; // le + case 0xE: pc = imm19; break; // always executed(al) + default: trap = (2+1); + } + break; + } + + case 0x5c: { + // ldr + is_f = true; + // address = delta + int address = ir << 8 >> 13; + address *= 4; + if(address >= _buf.length-3) trap = (5 + 1); + frval = ld8f(pc + address); + break; + } + + case 0x78: { + // STRH/LDRH(register) + int opc = ir >> 21 & 0x7; + int rn = (ir >> 5) & 0x1F; + int rm = (ir >> 16) & 0x1F; + // full opcode + opcode1 = (ir >> 21) & 0x7FF; + switch(opc) { + case 0x1: { + // store + if(opcode1 == Arm64.OP_STORE_R_16) { + st1((int)regs[rn] + (int)regs[rm], (int)regs[rdid]); + }else { + trap = (2+1); + } + rdid = -1; + break; + } + case 0x3: { + // load + if(opcode1 == Arm64.OP_LOAD_R_16) { + rval = ld1s((int)regs[rn] + (int)regs[rm]); + } else { + trap = (2+1); + } + break; + } + default: trap = (2+1); + } + break; + } + + case 0x79: { + // STRH/LDRH(immediate) + int base = ir >> 5 & 0x1F; + int imm = (ir >> 10) & 0xFFF; + imm *= 2; + rval = regs[base] + imm; + int opc = (ir >> 22) & 0x3; + // full opcode + opcode1 = (ir >> 22) & 0x3FF; + + switch(opc) { + case 0x0: { + // store + if(opcode1 == Arm64.OP_STORE_IMM_16) { + st2((int)rval, (int)regs[rdid]); + } else trap = (2+1); + rdid = -1; + + break; + } + case 0x1: { + // LDR (immediate) + if(opcode1 == Arm64.OP_LOAD_IMM_16) { + rval = ld2s((int)rval); + } else trap = (2+1); + rdid = -1; + break; + } + default: trap = (2+1); + } + break; + } + + case 0x8A: { // AND + int rn = (ir >> 5) & 0x1F; + int rm = (ir >> 16) & 0x1F; + rval = regs[rn] & regs[rm]; + break; + } + + case 0x8B, 0xCB: { + // add/sub(register), unshifted form emitted by Simple + int rn = (ir >> 5) & 0x1F; + int rm = (ir >> 16) & 0x1F; + long lhs = regs[rn]; + long rhs = regs[rm]; + rval = opcode1==0x8B ? lhs + rhs : lhs - rhs; + break; + } + + case 0x90, 0xB0, 0xD0, 0xF0: { // adrp + int immhi = ( ir << 8 ) >>> (8+5); + int immlo = (ir>>29)& 3; + int imm = ((immhi<<2) | immlo); + imm = (imm << 11) >> 11; + imm <<= 12; + rval = (pc & ~0xFFF) + imm; + break; + } + + case 0x91, 0xD1: { // ADD/SUB (immediate), unsigned imm12, optional LSL #12 + int rn = (ir >> 5) & 0x1F; + long immediate = (ir >>> 10) & 0xFFF; + if( (ir & (1<<22))!=0 ) immediate <<= 12; + rval = opcode1==0x91 ? regs[rn] + immediate : regs[rn] - immediate; + break; + } + case 0x92: { // MOVN or AND (immediate) + if( (ir & 0x00800000)!=0 ) { + int shift = ((ir >> 21) & 3)*16; + rval = ~(((ir >> 5) & 0xFFFFL) << shift); + break; + } + int imm12 = (ir >> 10) & 0x1FFF; + long immediate = Arm64.decodeImm12(imm12); + int rn = (ir >> 5) & 0x1F; + rval = regs[rn] & immediate; + break; + } + case 0x93, 0xD3: { // SBFM/UBFM: shifts and signed/unsigned extracts + int immr = (ir >>> 16) & 63; + int imms = (ir >>> 10) & 63; + int rn = (ir >>> 5) & 31; + boolean signed = (ir & 0x40000000)==0; + long value = regs[rn] << (63-imms); + value = signed ? value >> (63-imms) : value >>> (63-imms); + rval = immr > imms ? value << (64-immr) + : signed ? value >> immr : value >>> immr; + break; + } + + case 0x94, 0x95, 0x96, 0x97: { // bl + rdid = -1; // BL writes only X30, not a displacement-selected register. + rval = pc + 4; + regs[30] = rval; + int imm26 = (ir & 0x03FFFFFF); + int imm = (imm26 << 6) >> 6; + pc = pc + (imm << 2) ; + if( pc == _calloc ) { + assert (_heap&7) == 0; // 8-byte aligned + long size = regs[0]*regs[1]; + size = (size+7) & -8; // 8-byte aligned + regs[0] = _heap; + _heap += (int)size; + pc = (int)rval; + } + if( pc == _write ) { + ByteArrayOutputStream baos = switch((int)regs[0]) { + case 1 -> _stdout==null ? (_stdout = new ByteArrayOutputStream()) : _stdout; + case 2 -> _stderr==null ? (_stderr = new ByteArrayOutputStream()) : _stderr; + default -> throw new IllegalArgumentException(); + }; + baos.write(_buf,(int)regs[1],(int)regs[2]); + regs[0] = regs[2]; + pc = (int)rval; + } + pc -= 4; + rdid = -1; + break; + } + + case 0x9A: { + int encodedCond = (ir >> 12) & 0xF; + int decodedCond = encodedCond ^ 1; + // conditional select(csel) + boolean cond = switch (decodedCond) { + case 0x0 -> Z ; // eq + case 0x1 -> !Z ; // ne + case 0x2 -> C ; // cs + case 0x3 -> !C ; // cc + case 0x4 -> N ; // mi + case 0x5 -> !N ; // pl + case 0x6 -> V ; // vs + case 0x7 -> !V ; // vc + case 0x8 -> ( C && !Z ) ; // hi + case 0x9 -> (!C || Z ) ; // ls + case 0xA -> ( N == V) ; // ge + case 0xB -> ( N != V) ; // lt + case 0xC -> (!Z && N == V) ; // gt + case 0xD -> ( Z || N != V ); // le + case 0xE -> true ; // always + case 0xF -> false ; // never + default -> throw new UnsupportedOperationException(); + }; + if( cond ) rval = 1; + break; + } + case 0x9B: { // mul + int rn = (ir >> 5) & 0x1F; + int rm = (ir >> 16) & 0x1F; + rval = regs[rn] * regs[rm]; + break; + } + case 0x9E: { + // scvtf + is_f = true; + int rs1 = (ir >> 5) & 0x1F; + frval = (double)regs[rs1]; + break; + } + + case 0xAA: { + // MOV(register) + int rm = (ir >> 16) & 0x1F; + rval = regs[rm]; + break; + } + + case 0xB8: { + // LDR/STR (register + index) 32 bits + int opc = ir >> 21 & 0x7; + int rn = (ir >> 5) & 0x1F; + int rm = (ir >> 16) & 0x1F; + // full opcode + opcode1 = (ir >> 21) & 0x7FF; + switch(opc) { + case 0x1: { + if(opcode1 == Arm64.OP_STORE_R_32) { + st4((int)regs[rn] + (int)regs[rm], (int)regs[rdid]); + }else { + trap = (2+1); + } + // store(expected to store the length here) + + rdid = -1; + break; + } + case 0x3: { + // load + if( opcode1 == Arm64.OP_LOAD_R_32) { + rval = ld4s((int) (regs[rn] + regs[rm])); + } else { + trap = (2+1); + } + break; + } + default: trap = (2+1); + } + + break; + } + + case 0xB9: { + // str/ldr(immediate) 32 bits + int base = ir >> 5 & 0x1F; + int imm = (ir >> 10) & 0xFFF; + imm *= 4; + + rval = regs[base] + imm; + int opc = (ir >> 22) & 0x3; + + opcode1 = (ir >> 22) & 0x3FF; + switch(opc) { + case 0x0: { + // store + if(opcode1 == Arm64.OP_STORE_IMM_32) { + st4((int)rval, (int)regs[rdid]); + } else trap = (2+1); + + rdid = -1; + break; + } + case 0x1: { + // LDR (immediate) + if(opcode1 == Arm64.OP_LOAD_IMM_32) { + rval = ld4s((int) rval); + } else trap = (2+1); + break; + } + default: trap = (2+1); + } + break; + } + + case 0xBC: { + // LDR/STR (register + index) float 32 bits + int opc = ir >> 21 & 0x7; + int rn = (ir >> 5) & 0x1F; + int rm = (ir >> 16) & 0x1F; + is_f = true; + // full opcode + opcode1 = (ir >> 21) & 0x7FF; + switch(opc) { + case 0x1: { + if(opcode1 == Arm64.OPF_STORE_R_32) { + st4((int)regs[rn] + (int)regs[rm], Float.floatToRawIntBits((float)fregs[rdid])); + }else { + trap = (2+1); + } + // store(expected to store the length here) + + rdid = -1; + break; + } + case 0x3: { + // load + if(opcode1 == Arm64.OPF_LOAD_R_32) { + frval = ld4f((int) (regs[rn] + regs[rm])); + } else { + trap = (2+1); + } + break; + } + default: trap = (2+1); + } + + break; + } + + case 0xBD: { + // LDR/STR(immediate) 32 bits floats + int base = ir >> 5 & 0x1F; + int imm = (ir >> 10) & 0xFFF; + + imm *= 4; + is_f = true; + rval = regs[base] + imm; + int opc = (ir >> 22) & 0x3; + // full opcode + opcode1 = (ir >> 22) & 0x3FF; + switch(opc) { + case 0x0: { + // store + if(opcode1 == Arm64.OPF_STORE_IMM_32) { + st4((int)rval, Float.floatToRawIntBits((float)fregs[rdid])); + } else trap = (2+1); + + rdid = -1; + break; + } + case 0x1: { + // LDR (immediate) + if(opcode1 == Arm64.OPF_LOAD_IMM_32) { + frval = ld4f((int)rval); + } else trap = (2+1); + break; + } + default: trap = (2+1); + } + break; + } + + case 0xD2: // MOVZ + case 0xF2: { // MOVK preserves the other halfwords + // movz + // get immediate + int shift = ((ir >> 21) & 3)*16; + rval = ((ir >> 5) & 0xFFFFL) << shift; + if( opcode1==0xF2 ) rval |= regs[rdid] & ~(0xFFFFL << shift); + break; + } + + case 0xEB: { + // subs(shifted register) + int rn = (ir >> 5) & 0x1F; + int rm = (ir >> 16) & 0x1F; + long lhs = regs[rn]; + long rhs = regs[rm]; + rval = lhs - rhs; + + // set flags as a side effect + Z = (rval == 0); + N = (rval < 0); + C = lhs >= rhs; + + boolean sign_lhs = (lhs >>> 63) != 0; + boolean sign_rhs = (rhs >>> 63) != 0; + boolean sign_res = (rval >>> 63) != 0; + V = (sign_lhs != sign_rhs) && (sign_res != sign_lhs); + rdid = -1; + break; + } + + case 0xD6: { + int op = ir & 0xFFFFFC1F; + int rn = (ir >>> 5) & 31; + int target = (int)regs[rn]; // Read before BLR overwrites X30. + rdid = -1; + if( op==0xD63F0000 ) regs[30] = pc+4; // BLR + else if( op!=0xD65F0000 ) { trap = 3; break; } // RET + pc = target; + if( op==0xD65F0000 && pc==0 ) break outer; + pc -= 4; + break; + } + + case 0xF1: { + // subs(immediate) + int rn = (ir >> 5) & 0x1F; + int immediate = ir << 10 >> 20; + long lhs = regs[rn]; + + rval = lhs - immediate; + + // set flags as a side effect + Z = (rval == 0); + N = (rval < 0); + C = lhs >= immediate; + + boolean sign_lhs = (lhs >>> 63) != 0; + boolean sign_rhs = (immediate >>> 63) != 0; + boolean sign_res = (rval >>> 63) != 0; + V = (sign_lhs != sign_rhs) && (sign_res != sign_lhs); + rdid = -1; + break; + } + case 0xF8: { + // LDR/STR (register + index) 64 bits + int opc = ir >> 21 & 0x7; + int rn = (ir >> 5) & 0x1F; + int rm = (ir >> 16) & 0x1F; + // full opcode + opcode1 = (ir >> 21) & 0x7FF; + switch(opc) { + case 0x1: { + if(opcode1 == Arm64.OP_STORE_R_64) { + st8((int)regs[rn] + (int)regs[rm], regs[rdid]); + }else { + trap = (2+1); + } + // store(expected to store the length here) + + rdid = -1; + break; + } + case 0x3: { + // load + if(opcode1 == Arm64.OP_LOAD_R_64) { + rval = ld8((int) (regs[rn] + regs[rm])); + } else { + trap = (2+1); + } + break; + } + default: trap = (2+1); + } + + break; + } + case 0xF9: { + // str/ldr(immediate) 64 bits + int base = ir >> 5 & 0x1F; + int imm = (ir >> 10) & 0xFFF; + + imm *= 8; + + rval = regs[base] + imm; + int opc = (ir >> 22) & 0x3; + // full opcode + opcode1 = (ir >> 22) & 0x3FF; + switch(opc) { + case 0x0: { + // store + if(opcode1 == Arm64.OP_STORE_IMM_64) { + st8((int)rval, regs[rdid]); + } else trap = (2+1); + + rdid = -1; + break; + } + case 0x1: { + // LDR (immediate) + if(opcode1 == Arm64.OP_LOAD_IMM_64) { + rval = ld8((int)rval); + } else trap = (2+1); + break; + } + default: trap = (2+1); + } + break; + } + + case 0xFC: { + // LDR/STR (register + index) float 64 bits + int opc = ir >> 21 & 0x7; + int rn = (ir >> 5) & 0x1F; + int rm = (ir >> 16) & 0x1F; + is_f = true; + // full opcode + opcode1 = (ir >> 21) & 0x7FF; + switch(opc) { + case 0x1: { + if(opcode1 == Arm64.OPF_STORE_R_64) { + st8((int)regs[rn] + (int)regs[rm], Double.doubleToRawLongBits(fregs[rdid])); + }else { + trap = (2+1); + } + // store(expected to store the length here) + + rdid = -1; + break; + } + case 0x3: { + // load + if(opcode1 == Arm64.OPF_LOAD_R_64) { + frval = ld8f((int) (regs[rn] + regs[rm])); + } else { + trap = (2+1); + } + break; + } + default: trap = (2+1); + } + + break; + } + case 0xFD: { + // LDR/STR(immediate) 64 bits floats + int base = ir >> 5 & 0x1F; + int imm = (ir >> 10) & 0xFFF; + + imm *= 8; + is_f = true; + rval = regs[base] + imm; + int opc = (ir >> 22) & 0x3; + // full opcode + opcode1 = (ir >> 22) & 0x3FF; + switch(opc) { + case 0x0: { + // store + if(opcode1 == Arm64.OPF_STORE_IMM_64) { + st8((int)rval, Double.doubleToRawLongBits(fregs[rdid])); + } else trap = (2+1); + + rdid = -1; + break; + } + case 0x1: { + // LDR (immediate) + if(opcode1 == Arm64.OPF_LOAD_IMM_64) { + frval = ld8f((int)rval); + } else trap = (2+1); + break; + } + default: trap = (2+1); + } + break; + } + + default: trap = (2+1); + } + + if(trap != 0) { + _pc = pc; + break; + } + if(rdid != -1) { + if(is_f) fregs[rdid] = frval; + else regs[rdid] = rval; + } + + pc += 4; + } + // Handle traps and interrupts. + if(trap != 0) + return trap; + _cycle = cycle; + _pc = pc; + return 0; + } + + +} diff --git a/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalRisc5.java b/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalRisc5.java new file mode 100644 index 00000000..f618075d --- /dev/null +++ b/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalRisc5.java @@ -0,0 +1,370 @@ + package com.seaofnodes.isa.eval; + +/** Simple RISC5 CPU Emulator + * + * Original works credit Charles Lohr. + * This is a complete rewrite in Java. + * + * Load code into memory and go! + */ + +import java.io.ByteArrayOutputStream; +import java.util.Arrays; + +public class EvalRisc5 { + + // Memory image, always 0-based + public final byte[] _buf; + + // GPRs + public final long[] regs; + // FRs + public final double[] fregs; + // PC + public int _pc; + + // Start of free memory for allocation + public int _heap; + + // Standard out and err streams + public ByteArrayOutputStream _stdout, _stderr; + + // Cycle counters + public int _cycle; + + // Default test-runtime entry points; callers can supply another link convention. + private final int _calloc, _write; + public EvalRisc5(byte[] buf, int stackSize) { this(buf,stackSize,-4,-8); } + public EvalRisc5(byte[] buf, int stackSize, int calloc, int write) { + _calloc = calloc; + _write = write; + _buf = buf; + regs = new long[32]; + fregs = new double[32]; + // Stack grows down, heap grows up + regs[2] = _heap = stackSize; + _pc = 0; + } + + // Little endian + public int ld1s(int x) { return _buf[x ] ; } + public int ld1z(int x) { return _buf[x ]&0xFF; } + public int ld2s(int x) { return ld1z(x) | ld1s(x+1)<< 8; } + public int ld2z(int x) { return ld1z(x) | ld1z(x+1)<< 8; } + public int ld4s(int x) { return ld2z(x) | ld2s(x+2)<<16; } + public long ld8 (int x) { return (long)ld4s(x)&0xFFFFFFFFL | ((long)ld4s(x+4))<<32; } + + public float ld4f(int x) { return Float.intBitsToFloat(ld4s(x)); } + public double ld8f(int x) { return Double.longBitsToDouble(ld8(x)); } + + + public void st1(int x, int val) { _buf[x] = (byte)val; } + public void st2(int x, int val) { st1(x,val); st1(x+1,val>> 8); } + public void st4(int x, int val) {st2(x,val); st2(x+2,val>>16); } + public void st8(int x, long val) { st4(x,(int)val); st4(x+4,(int)(val>>32)); } + + // Note: only a few bits are used. (Machine = 3, User = 0) + // Bits 0..1 = privilege. + // Bit 2 = WFI (Wait for interrupt) + // Bit 3+ = Load/Store reservation LSBs. + public int _extraflags; + + public int step( int maxops ) { + int trap = 0; + long rval = 0; + double frval = 0; + int pc = _pc; + int cycle = _cycle; + boolean is_f = false; + outer: + for( int icount = 0; icount < maxops; icount++ ) { + int ir = 0; + rval = 0; + cycle++; + + if( pc >= _buf.length ) { + trap = 1 + 1; // Handle access violation on instruction read. + break; + } + if( (pc & 3)!=0 ) { + trap = 1; // Handle PC-misaligned access + break; + } + // Load instruction from image buffer at PC + ir = ld4s( pc ); + int rdid = (ir >> 7) & 0x1f; + + switch( ir & 0x7f ) { + case 0x37: // LUI (0b0110111) + rval = ( ir & 0xfffff000 ); + break; + case 0x17: // AUIPC (0b0010111) + rval = pc + ( ir & 0xfffff000 ); + break; + case 0x6F: { // JAL (0b1101111) + int reladdy = ((ir & 0x80000000)>>11) | ((ir & 0x7fe00000)>>20) | ((ir & 0x00100000)>>9) | ((ir&0x000ff000)); + if( (reladdy & 0x00100000)!=0 ) reladdy |= 0xffe00000; // Sign extension. + rval = pc + 4; + pc = pc + reladdy - 4; + if( pc+4 == _write ) { + ByteArrayOutputStream baos = switch((int)regs[10]) { + case 1 -> _stdout==null ? (_stdout = new ByteArrayOutputStream()) : _stdout; + case 2 -> _stderr==null ? (_stderr = new ByteArrayOutputStream()) : _stderr; + default -> throw new IllegalArgumentException(); + }; + baos.write(_buf,(int)regs[11],(int)regs[12]); + regs[10] = regs[12]; + rdid = 0; + pc = (int)(rval - 4); // Unwind PC, as-if returned from write + } + break; + } + case 0x67: { // JALR (0b1100111) + int imm_se = ir >> 20; // Sign extend 12 bits + rval = pc + 4; + pc = ((int)regs[ (ir >> 15) & 0x1f ] + imm_se) & ~1; + // Return from top-level ; exit sim + if( pc==0 ) { + if( rdid!=0 ) regs[rdid] = rval; // Write PC into register before exit + break outer; + } + // Inline CALLOC effect + if( pc == _calloc ) { + assert (_heap&7) == 0; // 8-byte aligned + int size = (int)(regs[10]*regs[11]); + size = (size+7) & -8; // 8-byte aligned + regs[10] = _heap; // Return next free address + _heap += size; + pc = (int)(rval - 4); // Unwind PC, as-if returned from calloc + } else + pc -= 4; + break; + } + case 0x63: { // Branch (0b1100011) + int immm4 = ((ir & 0xf00)>>7) | ((ir & 0x7e000000)>>20) | ((ir & 0x80) << 4) | ((ir >> 31)<<12); + if( (immm4 & 0x1000)!=0 ) immm4 |= 0xffffe000; + long rs1 = regs[(ir >> 15) & 0x1f]; + long rs2 = regs[(ir >> 20) & 0x1f]; + immm4 = pc + immm4 - 4; + rdid = 0; + if( switch( (ir >> 12) & 0x7 ) { + case 0 -> rs1 == rs2; + case 1 -> rs1 != rs2; + case 4 -> rs1 < rs2; + case 5 -> rs1 >= rs2; + case 6 -> Long.compareUnsigned(rs1,rs2) < 0; //BLTU + case 7 -> Long.compareUnsigned(rs1,rs2) >= 0; //BGEU + default -> { trap = (2+1); yield false; } + } ) pc = immm4; + break; + } + case 0x03: { // Load (0b0000011) + int rs1 = (int)regs[(ir >> 15) & 0x1f]; // Address chop to 32b + int imm = ir >> 20; + int imm_se = imm | (( (imm & 0x800)!=0 ) ? 0xfffff000 : 0 ); + int rsval = rs1 + imm_se; + + if( rsval >= _buf.length-3 ) { + trap = (5+1); + rval = rsval; + } else { + switch( ( ir >> 12 ) & 0x7 ) { + //LB, LH, LW, LBU, LHU + case 0: rval = ld1s( rsval ); break; + case 1: rval = ld2s( rsval ); break; + case 2: rval = ld4s( rsval ); break; + case 3: rval = ld8 ( rsval ); break; + case 4: rval = ld1z( rsval ); break; + case 5: rval = ld2z( rsval ); break; + case 6: rval = ld4s( rsval ); break; + default: trap = (2+1); + } + } + break; + } + // floats + case 0x7: { + is_f = true; + int rs1 = (int)regs[(ir >> 15) & 0x1f]; // Address chop to 32b + int imm = ir >> 20; + int imm_se = imm | (( (imm & 0x800)!=0 ) ? 0xfffff000 : 0 ); + int rsval = rs1 + imm_se; + + if( rsval >= _buf.length-3 ) { + trap = (5+1); + rval = rsval; + } else { + switch( ( ir >> 12 ) & 0x7 ) { + case 2: frval = ld4f( rsval ); break; + case 3: frval = ld8f( rsval ); break; + default: trap = (2+1); + } + } + break; + } + + case 0x23: { // Store 0b0100011 + int rs1 = (int)regs[(ir >> 15) & 0x1f]; // Address chop to 32b + long rs2 = regs[(ir >> 20) & 0x1f]; + int addy = ( ( ir >> 7 ) & 0x1f ) | ( ( ir & 0xfe000000 ) >> 20 ); + if( (addy & 0x800)!=0 ) addy |= 0xfffff000; + addy += rs1; + rdid = 0; + if( addy >= _buf.length-3 ) { + trap = (7+1); // Store access fault. + rval = addy; + } else { + switch( ( ir >> 12 ) & 0x7 ) { //SB, SH, SW + case 0: st1( addy, (int)rs2 ); break; + case 1: st2( addy, (int)rs2 ); break; + case 2: st4( addy, (int)rs2 ); break; + case 3: st8( addy, rs2 ); break; + default: trap = (2+1); + } + } + break; + } + case 0x13: // Op-immediate 0b0010011 + case 0x33: { // Op 0b0110011 + int imm = ir >> 20; + imm = imm | (( (imm & 0x800)!=0 ) ? 0xfffff000 : 0); + long rs1 = regs[(ir >> 15) & 0x1f]; + boolean is_reg = (ir & 0x20)!=0; + long rs2 = is_reg ? regs[imm & 0x1f] : imm; + + // Insert the detection here + int rs1id = (ir >> 15) & 0x1f; + int funct3 = (ir >> 12) & 0x7; + + if( is_reg && ( ir & 0x02000000 )!=0 ) { + switch( (ir>>12)&7 ) { //0x02000000 = RV32M + case 0: rval = rs1 * rs2; break; // MUL + case 1: rval = (int)((long)((int)rs1) * (long)((int)rs2)) >> 32; break; // MULH + case 2: rval = (int)((long)((int)rs1) * (long)rs2) >> 32; break; // MULHSU + case 3: rval = (int)((long)rs1 * (long)rs2) >> 32; break; // MULHU + case 4: if( rs2 == 0 ) rval = -1; else rval = ((int)rs1 == Integer.MIN_VALUE && (int)rs2 == -1) ? rs1 : ((int)rs1 / (int)rs2); break; // DIV + case 5: if( rs2 == 0 ) rval = 0xffffffff; else rval = rs1 / rs2; break; // DIVU + case 6: if( rs2 == 0 ) rval = rs1; else rval = ((int)rs1 == Integer.MIN_VALUE && (int)rs2 == -1) ? 0 : ((int)((int)rs1 % (int)rs2)); break; // REM + case 7: if( rs2 == 0 ) rval = rs1; else rval = rs1 % rs2; break; // REMU + } + } else { + rval = switch( (ir >> 12) & 7 ) { // These could be either op-immediate or op commands. Be careful. + case 0 -> (is_reg && (ir & 0x40000000) != 0) ? (rs1 - rs2) : (rs1 + rs2); + case 1 -> rs1 << (rs2 & 0x3F); + case 2 -> (rs1 < rs2) ? 1 : 0; + case 3 -> (rs1 < rs2) ? 1 : 0; + case 4 -> rs1 ^ rs2; + case 5 -> (ir & 0x40000000) != 0 ? (rs1 >> (rs2 & 0x3F)) : (rs1 >>> (rs2 & 0x3F)); + case 6 -> rs1 | rs2; + case 7 -> rs1 & rs2; + default -> rval; + }; + } + break; + } + case 0x0f: // 0b0001111 + rdid = 0; // fencetype = (ir >> 12) & 0b111; We ignore fences in this impl. + break; + case 0x73: // Zifencei+Zicsr (0b1110011) + trap = (2+1); // Note micrrop 0b100 == undefined. + break; + + case 0x2f: { // RV32A (0b00101111) + long rs1 = regs[(ir >> 15) & 0x1f]; + long rs2 = regs[(ir >> 20) & 0x1f]; + int irmid = ( ir>>27 ) & 0x1f; + + // We don't implement load/store from UART or CLNT with RV32A here. + if( rs1 >= _buf.length-3 ) { + trap = (7+1); //Store/AMO access fault + rval = rs1; + } else { + rval = ld4s( (int)rs1 ); + + // Referenced a little bit of https://github.com/franzflasch/riscv_em/blob/master/src/core/core.c + boolean dowrite = true; + switch( irmid ) { + case 2: //LR.W (0b00010) + dowrite = false; + _extraflags = (_extraflags & 0x07) | (int)(rs1<<3); + break; + case 3: { //SC.W (0b00011) (Make sure we have a slot, and, it's valid) + boolean xflag = _extraflags >> 3 != ( rs1 & 0x1fffffff ); + rval = xflag ? 1 : 0; // Validate that our reservation slot is OK. + dowrite = !xflag; // Only write if slot is valid. + break; + } + case 0: rs2 += rval; break; // AMOADD.W (0b00000) + case 1: break; // AMOSWAP.W (0b00001) + case 4: rs2 ^= rval; break; // AMOXOR.W (0b00100) + case 8: rs2 |= rval; break; // AMOOR.W (0b01000) + case 12: rs2 &= rval; break; // AMOAND.W (0b01100) + case 16: rs2 = Math.min( rs2, rval ); break; //AMOMIN.W (0b10000) + case 20: rs2 = Math.max( rs2, rval ); break; //AMOMAX.W (0b10100) + case 24: rs2 = Math.min( rs2, rval ); break; //AMOMINU.W (0b11000) + case 28: rs2 = Math.max( rs2, rval ); break; //AMOMAXU.W (0b11100) + default: trap = (2+1); dowrite = false; break; //Not supported. + } + if( dowrite ) st4( (int)rs1, (int)rs2 ); + } + break; + } + + case 0x53: { + is_f = true; + int rs1 = (ir >> 15) & 0x1F; + int rs2 = (ir >> 20) & 0x1F; + double lhs = fregs[rs1]; + double rhs = fregs[rs2]; + int func5 = (ir >> 27) & 0x1F; + int func2 = (ir >> 25) & 0x3; + if( func2 != 1 ) throw new UnsupportedOperationException(); // 1==double + switch( func5 ) { + case 0: frval = lhs + rhs; break; // fadd.d + case 1: frval = lhs - rhs; break; // fsub.d + case 2: frval = lhs * rhs; break; // fmul.d + case 3: frval = lhs / rhs; break; // fdiv.d + case 5: frval = Math.min(lhs,rhs); break; // fmin.d, also used for move + case 20: + is_f = false; // Output into a GPR + rval = switch( (ir >> 12) & 7 ) { + case 0 -> lhs <= rhs ? 1 : 0; + case 1 -> lhs == rhs ? 1 : 0; + case 2 -> lhs < rhs ? 1 : 0; + default -> throw new UnsupportedOperationException(); + }; + break; + case 26: frval = (double)regs[rs1]; break; // fcvt.w.d + default: + trap = (2+1); // Fault: Invalid opcode. + throw new UnsupportedOperationException(); + } + break; + } + + default: trap = (2+1); // Fault: Invalid opcode. + } + + // If there was a trap, do NOT allow register writeback. + if( trap!=0 ) { + _pc = pc; + break; + } + + if( rdid!=0 || is_f) { + if(is_f) {fregs[rdid] = frval; is_f = false;} // Write back register. + else regs[rdid] = rval; // Write back register. + } + + pc += 4; + } + + // Handle traps and interrupts. + if( trap!=0 ) + return trap; + + _cycle = cycle; + _pc = pc; + return 0; + } +} |
