cpu: implement JAL/JALR

This commit is contained in:
mappu 2022-12-28 13:05:57 +13:00
parent 77a155fd79
commit 236337c219
1 changed files with 42 additions and 4 deletions

46
cpu.go
View File

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