From 236337c21982df4465d38f5925ca4d6608a55520 Mon Sep 17 00:00:00 2001 From: mappu Date: Wed, 28 Dec 2022 13:05:57 +1300 Subject: [PATCH] cpu: implement JAL/JALR --- cpu.go | 46 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/cpu.go b/cpu.go index a8f0327..98f0473 100644 --- a/cpu.go +++ b/cpu.go @@ -53,11 +53,46 @@ func (c *CPUState) Step() error { case 0b01101111: // JAL - panic("todo") + // The jump and link (JAL) instruction uses the J-type format, where the J-immediate encodes a + // signed offset in multiples of 2 bytes. The offset is sign-extended and added to the address of the + // jump instruction to form the jump target address. Jumps can therefore target a ±1 MiB range. + // JAL stores the address of the instruction that follows the JAL (pc+4) into register rd. The standard + // software calling convention uses x1 as the return address register and x5 as an alternate link register. + // Plain unconditional jumps (assembler pseudoinstruction J) are encoded as a JAL with rd=x0. + + var imm uint32 = (opcode >> 12) & 0b11111111111111111111 + if imm&0b10000000000000000000 != 0 { + imm |= 0b11111111111100000000000000000000 // sign extension: if MSB is set in imm, extend with 1's instead of 0's + } + + // 2^20 has ~1MiB unsigned range, but it's in multiples of two bytes + offset := int32(imm) * 2 + + c.Registers[opcode_rd(opcode)] = c.Pc + 4 + c.Pc = uint32(int32(c.Pc) + offset) + return nil // Don't fallthrough, or else we increment Pc again case 0b1100111: // JALR - panic("todo") + funct3 := (opcode >> 12) & 0b111 + if funct3 != 0b000 { + return ErrInvalidOpcode{} + } + + // The indirect jump instruction JALR (jump and link register) uses the I-type encoding. The target + // address is obtained by adding the sign-extended 12-bit I-immediate to the register rs1, then setting + // the least-significant bit of the result to zero. The address of the instruction following the jump + // (pc+4) is written to register rd. Register x0 can be used as the destination if the result is not + // required. + + 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 + } + + c.Registers[opcode_rd(opcode)] = c.Pc + 4 + c.Pc = (c.Registers[opcode_rs1(opcode)] + imm) & 0b11111111111111111111111111111110 + return nil // Don't fallthrough, or else we increment Pc again case 0b1100011: // BEQ/BNE/BLT/BGE/BLTU/BGEU @@ -82,10 +117,13 @@ func (c *CPUState) Step() error { switch funct3 { case 0b000: - // ADDI + // ADDI/NOP // 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. + // + // The NOP instruction does not change any architecturally visible state, except for advancing the + // pc and incrementing any applicable performance counters. NOP is encoded as ADDI x0, x0, 0. c.Registers[opcode_rd(opcode)] = c.ReadRegister(opcode_rs1(opcode)) + imm case 0b010: @@ -274,7 +312,7 @@ func (c *CPUState) Step() error { } // Step program-counter forward - c.Pc++ + c.Pc += 4 return nil }