From 647998df5e2a5b14bc855517d649d6fe993a2dfc Mon Sep 17 00:00:00 2001 From: mappu Date: Wed, 28 Dec 2022 11:47:14 +1300 Subject: [PATCH] initial commit --- .gitignore | 2 + go.mod | 3 + main.go | 214 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 219 insertions(+) create mode 100644 .gitignore create mode 100644 go.mod create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6309b28 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +riscvemu +riscvemu.debug diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..75c306f --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module riscvemu + +go 1.15 diff --git a/main.go b/main.go new file mode 100644 index 0000000..b96c34b --- /dev/null +++ b/main.go @@ -0,0 +1,214 @@ +package main + +import ( + "fmt" +) + +type ErrInvalidOpcode struct{} + +func (e ErrInvalidOpcode) Error() string { return "invalid opcode" } + +type CPUState struct { + Registers [32]uint32 + Pc uint32 + w World +} + +func opcode_rd(opcode uint32) uint32 { return (opcode >> 7) & 0b11111 } +func opcode_rs1(opcode uint32) uint32 { return (opcode >> 15) & 0b11111 } // 2^5 is 32 for 32 registers +func opcode_rs2(opcode uint32) uint32 { return (opcode >> 19) & 0b11111 } + +func (c *CPUState) ReadRegister(r uint32) uint32 { + if r == 0 { + return 0 // Read from the zero register always returns 0 + } + + return c.Registers[r] +} + +func (c *CPUState) Step() error { + opcode, err := c.w.ReadMemory(c.Pc) + if err != nil { + return fmt.Errorf("ReadMemory: %w", err) + } + + switch opcode & 0b1111111 { + case 0b0110111: + // LUI + panic("todo") + + case 0b0010111: + // AUIPC + panic("todo") + + case 0b01101111: + // JAL + panic("todo") + + case 0b1100111: + // JALR + panic("todo") + + case 0b1100011: + // BEQ/BNE/BLT/BGE/BLTU/BGEU + panic("todo") + + case 0b0000011: + // LB/LH/LW/LBU/LHU + panic("todo") + + case 0b0100011: + // SB/SH/SW + panic("todo") + + case 0b0010011: + // ADDI/SLTI/SLTIU/XORI/ORI/ANDI/SLLI/SLRI/SRAI + funct3 := (opcode >> 12) & 0b111 + + var imm uint32 = (opcode >> 20) & 0b111111111111 + if imm&0b100000000000 != 0 { + imm |= 0b11111111111111111111000000000000 // sign extension: if MSB is set in imm, extend with 1's instead of 0's + } + + switch funct3 { + case 0b000: + // ADDI + // ADDI adds the sign-extended 12-bit immediate to register rs1. Arithmetic overflow is ignored and + // the result is simply the low XLEN bits of the result. ADDI rd, rs1, 0 is used to implement the MV + // rd, rs1 assembler pseudoinstruction. + c.Registers[opcode_rd(opcode)] = c.ReadRegister(opcode_rs1(opcode)) + imm + + case 0b010: + // SLTI + // SLTI (set less than immediate) places the value 1 in register rd if register rs1 is less than the + // signextended immediate when both are treated as signed numbers, else 0 is written to rd. SLTIU is + // similar but compares the values as unsigned numbers (i.e., the immediate is first sign-extended to + // XLEN bits then treated as an unsigned number). Note, SLTIU rd, rs1, 1 sets rd to 1 if rs1 equals + // zero, otherwise sets rd to 0 (assembler pseudoinstruction SEQZ rd, rs). + if int32(c.ReadRegister(opcode_rs1(opcode))) < int32(imm) { + c.Registers[opcode_rd(opcode)] = 1 + } else { + c.Registers[opcode_rd(opcode)] = 0 + } + + case 0b011: + // SLTIU + if c.ReadRegister(opcode_rs1(opcode)) < imm { + c.Registers[opcode_rd(opcode)] = 1 + } else { + c.Registers[opcode_rd(opcode)] = 0 + } + + case 0b100: + // XORI + // ANDI, ORI, XORI are logical operations that perform bitwise AND, OR, and XOR on register rs1 + // and the sign-extended 12-bit immediate and place the result in rd. Note, XORI rd, rs1, -1 performs + // a bitwise logical inversion of register rs1 (assembler pseudoinstruction NOT rd, rs). + c.Registers[opcode_rd(opcode)] = c.ReadRegister(opcode_rs1(opcode)) ^ imm + + case 0b110: + // ORI + c.Registers[opcode_rd(opcode)] = c.ReadRegister(opcode_rs1(opcode)) | imm + + case 0b111: + // ANDI + c.Registers[opcode_rd(opcode)] = c.ReadRegister(opcode_rs1(opcode)) & imm + + case 0b001: + // SLLI + // Shifts by a constant are encoded as a specialization of the I-type format. The operand to be shifted + // is in rs1, and the shift amount is encoded in the lower 5 bits of the I-immediate field. The right + // shift type is encoded in bit 30. SLLI is a logical left shift (zeros are shifted into the lower bits); + // SRLI is a logical right shift (zeros are shifted into the upper bits); and SRAI is an arithmetic right + // shift (the original sign bit is copied into the vacated upper bits). + + shamt := (opcode >> 20) & 0b1111 + c.Registers[opcode_rd(opcode)] = c.ReadRegister(opcode_rs1(opcode)) << shamt + + case 0b101: + // SRLI/SRAI share the same `funct3` + shamt := (opcode >> 20) & 0b1111 + distinguish := (opcode >> 25) & 0b1111111 + if distinguish == 0b0000000 { + // SRLI + c.Registers[opcode_rd(opcode)] = c.ReadRegister(opcode_rs1(opcode)) >> shamt + + } else if distinguish == 0b0100000 { + // SRAI + // Go's shift operator implements arithmetic shifts if the left operand is a signed integer and logical shifts if it is an unsigned integer. + c.Registers[opcode_rd(opcode)] = uint32(int32(c.ReadRegister(opcode_rs1(opcode))) >> shamt) + + } else { + return ErrInvalidOpcode{} + } + + default: + return ErrInvalidOpcode{} + } + + case 0b0110011: + // ADD/SUB/SLL/SLT/SLTU/XOR/SRL/SRA/OR/AND + panic("todo") + + case 0b0001111: + // FENCE/FENCE.TSO/PAUSE + panic("todo") + + case 0b1110011: + // ECALL/EBREAK + panic("todo") + + default: + return ErrInvalidOpcode{} + } + + return nil +} + +type World interface { + ReadMemory(addr uint32) (uint32, error) + WriteMemory(addr, value uint32) error + Syscall() + Trap() (bool, error) +} + +type SampleWorld struct { + ram [1024]uint32 +} + +func (sw *SampleWorld) ReadMemory(addr uint32) (uint32, error) { + if addr >= uint32(len(sw.ram)) { + panic("out of bounds") + } + + return sw.ram[addr], nil +} + +func (sw *SampleWorld) WriteMemory(addr, value uint32) error { + if addr >= uint32(len(sw.ram)) { + panic("out of bounds") + } + + sw.ram[addr] = value + return nil +} + +func (sw *SampleWorld) Syscall() { + panic("syscall") +} + +func (sw *SampleWorld) Trap() (bool, error) { + panic("trap") +} + +func main() { + sw := SampleWorld{} + sw.ram[0] = 0b00000000000100000000000001110011 // EBREAK + + c := CPUState{ + w: &sw, + } + + err := c.Step() + panic(err) +}