summaryrefslogtreecommitdiff
path: root/chapter21/src/test/java/com
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2025-04-08 11:55:35 -0700
committerCliff Click <cliffc@acm.org>2025-04-08 11:55:35 -0700
commit22cb2b4555b6b539a5204c2e2b60748c8138d8dc (patch)
tree3b12d23e7db6f82364422acc598b84b2a0274776 /chapter21/src/test/java/com
parenta0f70ef9b432fdad06879c333c59e3f3a4dbfe54 (diff)
downloadSimple-22cb2b4555b6b539a5204c2e2b60748c8138d8dc.tar.gz
Simple-22cb2b4555b6b539a5204c2e2b60748c8138d8dc.zip
Add 32b risc5 emulator
Diffstat (limited to 'chapter21/src/test/java/com')
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/Chapter21Test.java5
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/EvalRisc5.java273
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/TestRisc5.java42
3 files changed, 320 insertions, 0 deletions
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter21Test.java
index 65acc50f..0ff43899 100644
--- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter21Test.java
+++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter21Test.java
@@ -1,6 +1,7 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.cpus.riscv.riscv;
import com.seaofnodes.simple.print.ASMPrinter;
import java.io.IOException;
import java.nio.file.Files;
@@ -96,6 +97,10 @@ public class Chapter21Test {
@Test public void testFibExport() throws IOException {
String fib = "[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]";
TestC.run("fib", fib);
+
+ EvalRisc5 R5 = TestRisc5.run("fib", 1000, 8);
+ // Return register A0 holds 55
+ assertEquals(55,R5.regs[riscv.A0]);
}
@Test public void testBrainFuck() throws IOException {
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/EvalRisc5.java b/chapter21/src/test/java/com/seaofnodes/simple/EvalRisc5.java
new file mode 100644
index 00000000..bced2505
--- /dev/null
+++ b/chapter21/src/test/java/com/seaofnodes/simple/EvalRisc5.java
@@ -0,0 +1,273 @@
+ package com.seaofnodes.simple;
+
+/** Simple RISC5 CPU Emulator
+ *
+ * Original works credit Charles Lohr.
+ * This is a complete rewrite in Java.
+ *
+ * Load code into memory and go!
+ */
+
+public class EvalRisc5 {
+
+ // Memory image, always 0-based
+ public final byte[] _buf;
+
+ EvalRisc5( byte[] buf ) { _buf=buf; }
+
+ //#ifndef MINIRV32_CUSTOM_MEMORY_BUS
+ // #define MINIRV32_STORE4( ofs, val ) *(int*)(_buf + ofs) = val
+ // #define MINIRV32_STORE2( ofs, val ) *(uint16_t*)(_buf + ofs) = val
+ // #define MINIRV32_STORE1( ofs, val ) *(uint8_t*)(_buf + ofs) = val
+ // #define MINIRV32_LOAD4( ofs ) *(int*)(_buf + ofs)
+ // #define MINIRV32_LOAD2( ofs ) *(uint16_t*)(_buf + ofs)
+ // #define MINIRV32_LOAD1( ofs ) *(uint8_t*)(_buf + ofs)
+ // #define MINIRV32_LOAD2_SIGNED( ofs ) *(int16_t*)(_buf + ofs)
+ // #define MINIRV32_LOAD1_SIGNED( ofs ) *(int8_t*)(_buf + ofs)
+ //#endif
+
+ // 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 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); }
+
+
+ // GPRs
+ final int[] regs = new int[32];
+
+ // PC
+ int _pc;
+
+ // Cycle counters
+ int _cycle;
+
+ // 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.
+ int _extraflags;
+
+ public int step( int maxops ) {
+ int trap = 0;
+ int rval = 0;
+ int pc = _pc;
+ int cycle = _cycle;
+
+ 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;
+ }
+
+ 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;
+ break;
+ }
+ case 0x67: { // JALR (0b1100111)
+ int imm = ir >> 20;
+ int imm_se = imm | (( (imm & 0x800)!=0 ) ? 0xfffff000 : 0);
+ rval = pc + 4;
+ pc = ( (regs[ (ir >> 15) & 0x1f ] + imm_se) & ~1) - 4;
+ // Return from top-level ; exit sim
+ if( pc+4==0 ) {
+ if( rdid!=0 ) regs[rdid] = rval;
+ break outer;
+ }
+ 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;
+ int rs1 = regs[(ir >> 15) & 0x1f];
+ int rs2 = regs[(ir >> 20) & 0x1f];
+ immm4 = pc + immm4 - 4;
+ rdid = 0;
+ switch( ( ir >> 12 ) & 0x7 ) {
+ // BEQ, BNE, BLT, BGE, BLTU, BGEU
+ case 0: if( rs1 == rs2 ) pc = immm4; break;
+ case 1: if( rs1 != rs2 ) pc = immm4; break;
+ case 4: if( rs1 < rs2 ) pc = immm4; break;
+ case 5: if( rs1 >= rs2 ) pc = immm4; break; //BGE
+ case 6: if( (int)rs1 < (int)rs2 ) pc = immm4; break; //BLTU
+ case 7: if( (int)rs1 >= (int)rs2 ) pc = immm4; break; //BGEU
+ default: trap = (2+1);
+ }
+ break;
+ }
+ case 0x03: { // Load (0b0000011)
+ int rs1 = regs[(ir >> 15) & 0x1f];
+ int imm = ir >> 20;
+ int imm_se = imm | (( (imm & 0x800)!=0 ) ? 0xfffff000 : 0 );
+ int rsval = rs1 + imm_se;
+
+ rsval -= 0;
+ 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 4: rval = ld1z( rsval ); break;
+ case 5: rval = ld2z( rsval ); break;
+ default: trap = (2+1);
+ }
+ }
+ break;
+ }
+ case 0x23: { // Store 0b0100011
+ int rs1 = regs[(ir >> 15) & 0x1f];
+ int rs2 = regs[(ir >> 20) & 0x1f];
+ int addy = ( ( ir >> 7 ) & 0x1f ) | ( ( ir & 0xfe000000 ) >> 20 );
+ if( (addy & 0x800)!=0 ) addy |= 0xfffff000;
+ addy += rs1 - 0;
+ 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, rs2 ); break;
+ case 1: st2( addy, rs2 ); break;
+ case 2: st4( 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);
+ int rs1 = regs[(ir >> 15) & 0x1f];
+ boolean is_reg = (ir & 0x20)!=0;
+ int rs2 = is_reg ? regs[imm & 0x1f] : imm;
+
+ 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 & 0x1F);
+ case 2 -> (rs1 < rs2) ? 1 : 0;
+ case 3 -> (rs1 < rs2) ? 1 : 0;
+ case 4 -> rs1 ^ rs2;
+ case 5 -> (ir & 0x40000000) != 0 ? (((int) rs1) >> (rs2 & 0x1F)) : (rs1 >> (rs2 & 0x1F));
+ 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)
+ int rs1 = regs[(ir >> 15) & 0x1f];
+ int 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( 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) | (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( rs1, rs2 );
+ }
+ 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 )
+ regs[rdid] = rval; // Write back register.
+
+ pc += 4;
+ }
+
+ // Handle traps and interrupts.
+ if( trap!=0 )
+ return trap;
+
+ _cycle = cycle;
+ _pc = pc;
+ return 0;
+ }
+}
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/TestRisc5.java b/chapter21/src/test/java/com/seaofnodes/simple/TestRisc5.java
new file mode 100644
index 00000000..29be1814
--- /dev/null
+++ b/chapter21/src/test/java/com/seaofnodes/simple/TestRisc5.java
@@ -0,0 +1,42 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.cpus.riscv.riscv;
+import junit.framework.Assert;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.io.IOException;
+
+// Runs 32-bit R5 code in an emulator
+public abstract class TestRisc5 {
+
+ public static EvalRisc5 run( String file, int steps, int arg ) throws IOException {
+ return run("src/test/java/com/seaofnodes/simple/progs",file,steps, arg);
+ }
+
+ // Compile and run a simple program
+ public static EvalRisc5 run( String dir, String file, int steps, int arg ) throws IOException {
+ // Compile and export Simple
+ String src = Files.readString(Path.of(dir+"/"+file+".smp"));
+ CodeGen code = new CodeGen(src).parse().opto().typeCheck().instSelect( "riscv", "SystemV").GCM().localSched().regAlloc().encode();
+
+ // Image
+ byte[] image = new byte[1<<20]; // A megabyte
+ EvalRisc5 R5 = new EvalRisc5(image);
+ // Code at offset 0
+ System.arraycopy(code._encoding.bits(), 0, image, 0, code._encoding.bits().length);
+ // Program start
+ R5._pc = 0;
+ // Stack starts at 64K and goes down (runs into code)
+ R5.regs[riscv.SP] = 1<<16;
+ // Initial incoming int arg
+ R5.regs[riscv.A0] = arg;
+ // malloc memory starts at 64K and goes up
+
+ // Run until stopped or steps
+ int trap = R5.step(steps);
+ Assert.assertEquals(0,trap); // Exit for steps, not trap
+
+ return R5;
+ }
+}