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) }