update
This commit is contained in:
parent
3850c27440
commit
b21dab032d
5 changed files with 157 additions and 58 deletions
15
README.txt
15
README.txt
|
@ -2,3 +2,18 @@ This is just for me to understand how all this works, and to learn something new
|
||||||
|
|
||||||
For example, I learned that endianness is not per bit but per byte. My whole life I've been in misinformation.
|
For example, I learned that endianness is not per bit but per byte. My whole life I've been in misinformation.
|
||||||
So little endian is not that first bit is the LSB, but it's MSB... and after 8 bits it becomes larger, which makes no point
|
So little endian is not that first bit is the LSB, but it's MSB... and after 8 bits it becomes larger, which makes no point
|
||||||
|
|
||||||
|
And I learned about sign extension, which is pretty cool
|
||||||
|
|
||||||
|
And I learned that Java is bad because it doesn't have unsigned numbers
|
||||||
|
|
||||||
|
To compile stuff:
|
||||||
|
0. Get the toolchain obviously
|
||||||
|
1. riscv32-unknown-elf-gcc -c -Oz program.c
|
||||||
|
2. riscv32-unknown-elf-objcopy -O binary program.o program.bin
|
||||||
|
program.bin is the binary file with the program
|
||||||
|
3. Encode to Base64: cat program.bin | base64
|
||||||
|
|
||||||
|
rv32i, ilp32 compatible toolchain for 64bit Linux: https://lfs.m724.eu/toolchain.tar.zst
|
||||||
|
Or just the stuff necessary to make a binary file: https://lfs.m724.eu/toolchainlite.tar.zst
|
||||||
|
Those were compiled with `./configure --prefix=$(pwd)/../toolchain --with-arch=rv32i --with-abi=ilp32` and `make`
|
|
@ -1,8 +1,5 @@
|
||||||
package eu.m724;
|
package eu.m724;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Base64;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an address space
|
* Represents an address space
|
||||||
*
|
*
|
||||||
|
@ -31,40 +28,33 @@ public class AddressSpace {
|
||||||
if (isRomInit) throw new RuntimeException("ROM already initialized");
|
if (isRomInit) throw new RuntimeException("ROM already initialized");
|
||||||
isRomInit = true;
|
isRomInit = true;
|
||||||
|
|
||||||
for (int i=0; i<program.length; i++) {
|
if (program.length > romSize) {
|
||||||
rom[i] = program[i];
|
throw new RuntimeException("Program is larger than ROM");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
System.arraycopy(program, 0, rom, 0, program.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* read {@code n} bytes from ROM<br>
|
* read {@code n} bytes<br>
|
||||||
*
|
*
|
||||||
* @param address address of first byte
|
* @param address address of first byte
|
||||||
* @param n number of bytes, max 8
|
* @param n number of bytes, max 8
|
||||||
* @return the unsigned bytes
|
* @return the unsigned bytes
|
||||||
*/
|
*/
|
||||||
long readRom(int address, int n) {
|
long read(int address, int n) {
|
||||||
long v = 0;
|
long v = 0;
|
||||||
|
|
||||||
for (int i=0; i<n; i++) {
|
if (address < romSize) {
|
||||||
v |= (long) (rom[address + i] & 0xFF) << (8 * i);
|
for (int i=0; i<n; i++) {
|
||||||
}
|
v |= (long) (rom[address + i] & 0xFF) << (8 * i);
|
||||||
|
}
|
||||||
return v;
|
} else if (address < romSize + ramSize) {
|
||||||
}
|
for (int i=0; i<n; i++) {
|
||||||
|
v |= (long) (ram[address + i] & 0xFF) << (8 * i);
|
||||||
/**
|
}
|
||||||
* read {@code n} bytes from RAM
|
} else {
|
||||||
*
|
// TODO read from IO
|
||||||
* @param address address of first byte
|
|
||||||
* @param n number of bytes, max 8
|
|
||||||
* @return the unsigned bytes
|
|
||||||
*/
|
|
||||||
long readRam(int address, int n) {
|
|
||||||
long v = 0;
|
|
||||||
|
|
||||||
for (int i=0; i<n; i++) {
|
|
||||||
v |= (long) (ram[address + i] & 0xFF) << (8 * i);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
|
@ -78,8 +68,14 @@ public class AddressSpace {
|
||||||
* @param value the value
|
* @param value the value
|
||||||
*/
|
*/
|
||||||
void writeRam(int address, int n, long value) {
|
void writeRam(int address, int n, long value) {
|
||||||
for (int i=0; i<n; i++) {
|
if (address < romSize) {
|
||||||
ram[address + i] = (byte) (value >> (8 * i) & 0xFF);
|
throw new RuntimeException("Cannot write to Read Only Memory");
|
||||||
|
} else if (address < romSize + ramSize) {
|
||||||
|
for (int i=0; i<n; i++) {
|
||||||
|
ram[address + i] = (byte) (value >> (8 * i) & 0xFF);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO write to IO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,31 +4,29 @@ package eu.m724;
|
||||||
* Represents a rv32i CPU
|
* Represents a rv32i CPU
|
||||||
*/
|
*/
|
||||||
public class CPU {
|
public class CPU {
|
||||||
private final int xsize = 32;
|
|
||||||
private final int[] register = new int[32];
|
private final int[] register = new int[32];
|
||||||
|
|
||||||
private AddressSpace addressSpace;
|
private AddressSpace addressSpace;
|
||||||
private int programCounter;
|
private int programCounter;
|
||||||
|
|
||||||
public void init(AddressSpace addressSpace) {
|
public void init(AddressSpace addressSpace) {
|
||||||
this.addressSpace = addressSpace; // TODO should addressspace really be linked
|
this.addressSpace = addressSpace; // TODO should addressspace really be here
|
||||||
|
|
||||||
register[2] = addressSpace.romSize + addressSpace.ramSize;
|
register[2] = addressSpace.romSize + addressSpace.ramSize;
|
||||||
programCounter = addressSpace.romBase;
|
programCounter = addressSpace.romBase;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getNextInstruction() {
|
private int getCurrentInstruction() {
|
||||||
if (programCounter > addressSpace.romSize - 1)
|
if (programCounter > addressSpace.romSize - 1)
|
||||||
throw new NullPointerException();
|
throw new NullPointerException();
|
||||||
|
|
||||||
int inst = (int) addressSpace.readRom(programCounter, 4);
|
return (int) addressSpace.read(programCounter, 4);
|
||||||
programCounter += 4;
|
|
||||||
return inst;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean executeNextInstruction() {
|
public boolean executeNextInstruction() {
|
||||||
try {
|
try {
|
||||||
executeInstruction(getNextInstruction());
|
executeInstruction(getCurrentInstruction());
|
||||||
|
programCounter += 4;
|
||||||
} catch (NullPointerException e) {
|
} catch (NullPointerException e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -42,6 +40,7 @@ public class CPU {
|
||||||
|
|
||||||
if (opcode != 0) {
|
if (opcode != 0) {
|
||||||
System.out.println();
|
System.out.println();
|
||||||
|
System.out.println("PC: " + programCounter);
|
||||||
System.out.println("Instruction: " + Integer.toBinaryString(instruction));
|
System.out.println("Instruction: " + Integer.toBinaryString(instruction));
|
||||||
System.out.println("Opcode: " + Integer.toBinaryString(opcode));
|
System.out.println("Opcode: " + Integer.toBinaryString(opcode));
|
||||||
System.out.println("Destination register: " + Integer.toBinaryString(rd));
|
System.out.println("Destination register: " + Integer.toBinaryString(rd));
|
||||||
|
@ -80,7 +79,10 @@ public class CPU {
|
||||||
} else if (opcode == 0b0110111) { // LUI load upper immediate (U type)
|
} else if (opcode == 0b0110111) { // LUI load upper immediate (U type)
|
||||||
|
|
||||||
} else if (opcode == 0b0010111) { // AUIPC (add upper immediate to pc) (U type)
|
} else if (opcode == 0b0010111) { // AUIPC (add upper immediate to pc) (U type)
|
||||||
|
int imm = instruction & 0xFFFFF000;
|
||||||
|
register[rd] = imm + programCounter;
|
||||||
|
|
||||||
|
System.out.printf("AUIPC: pc %d + imm %d = %d -> x%d\n", programCounter, imm, register[rd], rd);
|
||||||
} else if (opcode == 0b0110011) { // OP for Integer Register-Register Operations (R type)
|
} else if (opcode == 0b0110011) { // OP for Integer Register-Register Operations (R type)
|
||||||
int funct3 = instruction >> 12 & 0x7;
|
int funct3 = instruction >> 12 & 0x7;
|
||||||
|
|
||||||
|
@ -93,7 +95,7 @@ public class CPU {
|
||||||
|
|
||||||
int sum = register[rs1] + register[rs2] & 0xFF; // to overflow
|
int sum = register[rs1] + register[rs2] & 0xFF; // to overflow
|
||||||
|
|
||||||
System.out.printf("ADD: %d #%d + %d #%d = %d #%d\n", register[rs1], rs1, register[rs2], rs2, sum, rd);
|
System.out.printf("ADD: %d x%d + %d x%d = %d x%d\n", register[rs1], rs1, register[rs2], rs2, sum, rd);
|
||||||
|
|
||||||
register[rd] = sum;
|
register[rd] = sum;
|
||||||
} else if (funct7 == 0b0100000) { // SUB
|
} else if (funct7 == 0b0100000) { // SUB
|
||||||
|
@ -121,48 +123,131 @@ public class CPU {
|
||||||
|
|
||||||
}
|
}
|
||||||
} else if (opcode == 0b1101111) { // JAL for unconditional jump (J type)
|
} else if (opcode == 0b1101111) { // JAL for unconditional jump (J type)
|
||||||
|
System.out.println("JAL");
|
||||||
|
int imm = (instruction >> 31) << 20; // Extract imm[20] and sign-extend
|
||||||
|
imm |= (instruction >> 21) & 0x3FF; // Extract imm[10:1]
|
||||||
|
imm |= (instruction >> 20) & 0x1; // Extract imm[11]
|
||||||
|
imm |= (instruction >> 12) & 0xFF; // Extract imm[19:12]
|
||||||
|
imm <<= 1; // Left-shift by 1 to account for the implicit 0
|
||||||
|
|
||||||
|
register[rd] = programCounter; // program counter is always incremented after executing instruction
|
||||||
|
programCounter += imm;
|
||||||
|
|
||||||
|
System.out.printf("Jumped to %d + %d = %d (inst %d)\n", register[rd], imm, programCounter, programCounter / 4);
|
||||||
} else if (opcode == 0b1100111) { // JALR for unconditional jump and link register (I type)
|
} else if (opcode == 0b1100111) { // JALR for unconditional jump and link register (I type)
|
||||||
|
int funct3 = instruction >> 12 & 0x7;
|
||||||
|
|
||||||
|
if (funct3 != 0) {
|
||||||
|
// TODO throw an exception
|
||||||
|
}
|
||||||
|
|
||||||
|
int rs1 = instruction >> 15 & 0x1F;
|
||||||
|
int imm = instruction >> 20; // sign extends automatically
|
||||||
|
|
||||||
|
int result = register[rs1] + imm;
|
||||||
|
result &= ~1; // set LSB to 0
|
||||||
|
|
||||||
|
register[rd] = programCounter + 4;
|
||||||
|
programCounter = result - 4; // program counter is always incremented after executing instruction
|
||||||
|
|
||||||
|
System.out.printf("JALR: %d x%d + %d = %d (inst %d) -> x%d\n", register[rs1], rs1, imm, result, result / 4, rd);
|
||||||
} else if (opcode == 0b1100011) { // BRANCH instruction (B type)
|
} else if (opcode == 0b1100011) { // BRANCH instruction (B type)
|
||||||
int funct3 = instruction >> 12 & 0x1F;
|
int funct3 = instruction >> 12 & 0x7;
|
||||||
|
int rs1 = instruction >> 15 & 0x1F;
|
||||||
|
int rs2 = instruction >> 20 & 0x1F;
|
||||||
|
|
||||||
|
int imm = (instruction >> 31) << 12; // imm[12] and sign extend
|
||||||
|
imm |= ((instruction >> 7) & 0x1) << 11; // imm[11]
|
||||||
|
imm |= ((instruction >> 25) & 0x3f) << 5; // imm[10:5]
|
||||||
|
imm |= ((instruction >> 8) & 0xf) << 1; // imm[4:1]
|
||||||
|
// imm[0] is implicitly 0
|
||||||
|
|
||||||
|
boolean branch = false;
|
||||||
|
|
||||||
if (funct3 == 0b000) { // BEQ
|
if (funct3 == 0b000) { // BEQ
|
||||||
|
System.out.printf("BEQ: #%d %d == #%d %d?\n", rs1, register[rs1], rs2, register[rs2]);
|
||||||
|
branch = register[rs1] == register[rs2];
|
||||||
} else if (funct3 == 0b001) { // BNE
|
} else if (funct3 == 0b001) { // BNE
|
||||||
|
System.out.printf("BNE: #%d %d != #%d %d?\n", rs1, register[rs1], rs2, register[rs2]);
|
||||||
|
branch = register[rs1] != register[rs2];
|
||||||
} else if (funct3 == 0b100) { // BLT
|
} else if (funct3 == 0b100) { // BLT
|
||||||
|
System.out.printf("BLT: #%d %d < #%d %d?\n", rs1, register[rs1], rs2, register[rs2]);
|
||||||
|
branch = register[rs1] < register[rs2];
|
||||||
} else if (funct3 == 0b101) { // BGE
|
} else if (funct3 == 0b101) { // BGE
|
||||||
|
System.out.printf("BGE: #%d %d > #%d %d?\n", rs1, register[rs1], rs2, register[rs2]);
|
||||||
|
branch = register[rs1] > register[rs2];
|
||||||
} else if (funct3 == 0b110) { // BLTU
|
} else if (funct3 == 0b110) { // BLTU
|
||||||
|
System.out.printf("BLTU: #%d %d < #%d %d?\n", rs1, Integer.toUnsignedLong(register[rs1]), rs2, Integer.toUnsignedLong(register[rs2]));
|
||||||
|
branch = Integer.compareUnsigned(register[rs1], register[rs2]) < 0;
|
||||||
} else if (funct3 == 0b111) { // BGEU
|
} else if (funct3 == 0b111) { // BGEU
|
||||||
|
System.out.printf("BGEU: #%d %d > #%d %d?\n", rs1, Integer.toUnsignedLong(register[rs1]), rs2, Integer.toUnsignedLong(register[rs2]));
|
||||||
|
branch = Integer.compareUnsigned(register[rs1], register[rs2]) > 0;
|
||||||
|
} else {
|
||||||
|
// TODO illegal instruction
|
||||||
|
}
|
||||||
|
|
||||||
|
if (branch) {
|
||||||
|
programCounter += imm - 4; // program counter is always incremented after executing instruction so -4
|
||||||
}
|
}
|
||||||
} else if (opcode == 0b0000011) { // LOAD instruction (I type)
|
} else if (opcode == 0b0000011) { // LOAD instruction (I type)
|
||||||
int funct3 = instruction >> 12 & 0x1F;
|
int funct3 = instruction >> 12 & 0x1F;
|
||||||
|
int rs1 = instruction >> 15 & 0x1F;
|
||||||
|
int imm = instruction >> 20; // sign extends automatically
|
||||||
|
int addr = register[rs1] + imm;
|
||||||
|
|
||||||
if (funct3 == 0b000) { // LB
|
if (funct3 == 0b000) { // LB
|
||||||
|
// LB loads an 8-bit value from memory, then sign-extends to 32-bits.
|
||||||
|
register[rd] = (byte) addressSpace.read(addr, 1) >> 24;
|
||||||
|
|
||||||
|
System.out.printf("LB: addr: %d x%d + imm %d = %d | val: %d -> x%d\n", register[rs1], rs1, imm, addr, register[rd], rd);
|
||||||
} else if (funct3 == 0b001) { // LH
|
} else if (funct3 == 0b001) { // LH
|
||||||
|
// LH loads a 16-bit value from memory, then sign-extends to 32-bits.
|
||||||
|
register[rd] = (short) addressSpace.read(addr, 2) >> 16;
|
||||||
|
|
||||||
|
System.out.printf("LH: addr: %d x%d + imm %d = %d | val: %d -> x%d\n", register[rs1], rs1, imm, addr, register[rd], rd);
|
||||||
} else if (funct3 == 0b010) { // LW
|
} else if (funct3 == 0b010) { // LW
|
||||||
|
// LW instruction loads a 32-bit value from memory into rd.
|
||||||
|
register[rd] = (int) addressSpace.read(addr, 4);
|
||||||
|
|
||||||
|
System.out.printf("LW: addr: %d x%d + imm %d = %d | val: %d -> x%d\n", register[rs1], rs1, imm, addr, register[rd], rd);
|
||||||
} else if (funct3 == 0b100) { // LBU
|
} else if (funct3 == 0b100) { // LBU
|
||||||
|
// LBU loads an 8-bit value from memory but then zero extends to 32-bits.
|
||||||
|
register[rd] = (int) addressSpace.read(addr, 1);
|
||||||
|
|
||||||
|
System.out.printf("LBU: addr: %d x%d + imm %d = %d | val: %d -> x%d\n", register[rs1], rs1, imm, addr, register[rd], rd);
|
||||||
} else if (funct3 == 0b101) { // LHU
|
} else if (funct3 == 0b101) { // LHU
|
||||||
|
// LHU loads a 16-bit value from memory but then zero extends to 32-bits.
|
||||||
|
register[rd] = (int) addressSpace.read(addr, 2);
|
||||||
|
|
||||||
|
System.out.printf("LHU: addr: %d x%d + imm %d = %d | val: %d -> x%d\n", register[rs1], rs1, imm, addr, register[rd], rd);
|
||||||
|
} else {
|
||||||
|
// TODO illegal instruction
|
||||||
}
|
}
|
||||||
} else if (opcode == 0b0100011) { // STORE instruction (S type)
|
} else if (opcode == 0b0100011) { // STORE instruction (S type)
|
||||||
int funct3 = instruction >> 12 & 0x1F;
|
int funct3 = instruction >> 12 & 0x7;
|
||||||
|
int rs1 = instruction >> 15 & 0x1F;
|
||||||
|
int rs2 = instruction >> 20 & 0x1F;
|
||||||
|
int imm = instruction >> 20 & 0xffffffe0 | rd; // sign extends automatically
|
||||||
|
int addr = register[rs1] + imm;
|
||||||
|
System.out.println(addr);
|
||||||
|
|
||||||
if (funct3 == 0b000) { // SB
|
if (funct3 == 0b000) { // SB
|
||||||
|
// SB stores an 8-bit value from the low bits of register rs2 to memory.
|
||||||
|
addressSpace.writeRam(addr, 1, register[rs2] & 0xFF);
|
||||||
|
|
||||||
|
System.out.printf("SB: x%d %d -> %d\n", rs2, register[rs2] & 0xFF, addr);
|
||||||
} else if (funct3 == 0b001) { // SH
|
} else if (funct3 == 0b001) { // SH
|
||||||
|
// SH stores a 16-bit value from the low bits of register rs2 to memory.
|
||||||
|
addressSpace.writeRam(addr, 2, register[rs2] & 0xFFFF);
|
||||||
|
|
||||||
|
System.out.printf("SH: x%d %d -> %d\n", rs2, register[rs2] & 0xFFFF, addr);
|
||||||
} else if (funct3 == 0b010) { // SW
|
} else if (funct3 == 0b010) { // SW
|
||||||
|
// SW stores a 32-bit value from the low bits of register rs2 to memory.
|
||||||
|
addressSpace.writeRam(addr, 4, register[rs2]);
|
||||||
|
|
||||||
|
System.out.printf("SW: x%d %d -> %d\n", rs2, register[rs2], addr);
|
||||||
|
} else {
|
||||||
|
// TODO illegal instruction
|
||||||
}
|
}
|
||||||
} else if (opcode == 0b0001111) { // MISC-MEM for Memory Ordering Instructions (I type)
|
} else if (opcode == 0b0001111) { // MISC-MEM for Memory Ordering Instructions (I type)
|
||||||
|
|
||||||
|
|
|
@ -3,17 +3,29 @@ package eu.m724;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
|
||||||
public class Main {
|
public class Main {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) throws InterruptedException {
|
||||||
AddressSpace addressSpace = new AddressSpace(1024, 1024); // 1 KiB
|
AddressSpace addressSpace = new AddressSpace(1024, 1024 * 1024); // 1 KiB
|
||||||
byte[] program = Base64.getDecoder().decode("kw5QABMPUAKzD98B");
|
//byte[] program = Base64.getDecoder().decode("kw5QABMPUAKzD98B"); // simple addition program
|
||||||
|
//byte[] program = Base64.getDecoder().decode("EwUQAGeAAAA="); // return value program
|
||||||
|
//byte[] program = Base64.getDecoder().decode("EwWgABcDAABnAAMAIyghASMmMQEjLhEAEwQFAJMEBQATCQAAkwkQAGP+mQAThfT/lwAAAOeAAACT" +
|
||||||
|
// "hOT/MwmpAG/wn/6DIMEBE3UUAAMkgQGDJEEBgynBADMFJQEDKQEBEwEBAmeAAAA="); // 10th value of fibonacci
|
||||||
|
//byte[] program = Base64.getDecoder().decode("EwXwAhcDAABnAAMAIyghASMmMQEjLhEAEwQFAJMEBQATCQAAkwkQAGP+mQAThfT/lwAAAOeAAACT" +
|
||||||
|
// "hOT/MwmpAG/wn/6DIMEBE3UUAAMkgQGDJEEBgynBADMFJQEDKQEBEwEBAmeAAAA="); // 47th value of fibonacci
|
||||||
|
//byte[] program = Base64.getDecoder().decode("EwUAAxcDAABnAAMAIyghASMmMQEjLhEAEwQFAJMEBQATCQAAkwkQAGP+mQAThfT/lwAAAOeAAACT" +
|
||||||
|
// "hOT/MwmpAG/wn/6DIMEBE3UUAAMkgQGDJEEBgynBADMFJQEDKQEBEwEBAmeAAAA="); // 48th value of fibonacci (overflow)
|
||||||
|
byte[] program = Base64.getDecoder().decode("EwEB/iMuEQAjLIEAEwQBAiMmBP5vAAABgyfE/pOHFwAjJvT+AyfE/pMHkADj1uf+gyfE/hOFBwCD" +
|
||||||
|
"IMEBAySBARMBAQJngAAA"); // returns 10 after looping
|
||||||
addressSpace.initRom(program);
|
addressSpace.initRom(program);
|
||||||
|
|
||||||
CPU cpu = new CPU();
|
CPU cpu = new CPU();
|
||||||
cpu.init(addressSpace);
|
cpu.init(addressSpace);
|
||||||
|
cpu.setRegister(1, addressSpace.romSize); // make jumping to x1 end the program
|
||||||
|
|
||||||
while (cpu.executeNextInstruction()) { }
|
while (cpu.executeNextInstruction()) {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
}
|
||||||
|
|
||||||
System.out.println("\nEnd of program");
|
System.out.println("\nEnd of program. Return value (x10): " + cpu.getRegister(10));
|
||||||
cpu.dumpRegisters();
|
cpu.dumpRegisters();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,9 +0,0 @@
|
||||||
package eu.m724;
|
|
||||||
|
|
||||||
public class Memory {
|
|
||||||
private byte[] memory;
|
|
||||||
|
|
||||||
public Memory(int sizeBytes) {
|
|
||||||
memory = new byte[sizeBytes];
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue