cpu: implement LB/LH/LW/LBU/LHU

This commit is contained in:
mappu 2022-12-28 14:27:12 +13:00
parent 49b78a4b75
commit 4654a5bc72
3 changed files with 103 additions and 1 deletions

71
cpu.go
View File

@ -163,6 +163,77 @@ func (c *CPUState) Step() error {
case 0b0000011:
// LB/LH/LW/LBU/LHU
// The effective address is obtained by adding register rs1
// to the sign-extended 12-bit offset. Loads copy a value from memory to register rd.
var imm uint32 = (opcode >> 20) & 0b111111111111
if imm&0b100000000000 != 0 { // 12th position
imm |= 0b11111111111111111111000000000000 // sign extension: if MSB is set in imm, extend with 1's instead of 0's
}
memAddr := imm + c.Registers[opcode_rs1(opcode)]
// The LW instruction loads a 32-bit value from memory into rd. LH loads a 16-bit value from memory,
// then sign-extends to 32-bits before storing in rd. LHU loads a 16-bit value from memory but then
// zero extends to 32-bits before storing in rd. LB and LBU are defined analogously for 8-bit values.
funct3 := (opcode >> 12) & 0b111
switch funct3 {
case 0b000:
// LB
memVal, err := c.w.ReadByte(memAddr)
if err != nil {
return err
}
storeVal := uint32(memVal)
if storeVal&0b10000000 != 0 { // sign extend / 8th position
storeVal |= 0b11111111111111111111111100000000
}
c.Registers[opcode_rd(opcode)] = storeVal
case 0b001:
// LH
memVal, err := c.w.Read16(memAddr)
if err != nil {
return err
}
storeVal := uint32(memVal)
if storeVal&0b1000000000000000 != 0 { // sign extend / 16th position
storeVal |= 0b11111111111111110000000000000000
}
c.Registers[opcode_rd(opcode)] = storeVal
case 0b010:
// LW
memVal, err := c.w.Read32(memAddr)
if err != nil {
return err
}
c.Registers[opcode_rd(opcode)] = memVal
case 0b100:
// LBU
memVal, err := c.w.ReadByte(memAddr)
if err != nil {
return err
}
c.Registers[opcode_rd(opcode)] = uint32(memVal)
case 0b101:
// LHU
memVal, err := c.w.Read16(memAddr)
if err != nil {
return err
}
c.Registers[opcode_rd(opcode)] = uint32(memVal)
default:
return ErrInvalidOpcode{}
}
panic("todo")
case 0b0100011:

View File

@ -4,8 +4,11 @@ package riscvemu
// code is run.
type EEI interface {
ReadByte(addr uint32) (byte, error)
Read16(addr uint32) (uint16, error)
Read32(addr uint32) (uint32, error)
WriteByte(addr uint32, value byte) error
Write16(addr uint32, value uint16) error
Write32(addr, value uint32) error
Syscall()

View File

@ -20,6 +20,15 @@ func (ve *VirtualEEI) ReadByte(addr uint32) (byte, error) {
return page[addr&0b111111111111], nil
}
func (ve *VirtualEEI) Read16(addr uint32) (uint16, error) {
// n.b. will panic on overflow
page, _ := ve.memPages[addr>>12]
inPageAddr := addr & 0b111111111111
return (uint16(page[inPageAddr]) << 8) | uint16(page[inPageAddr+1]), nil
}
func (ve *VirtualEEI) Read32(addr uint32) (uint32, error) {
// n.b. will panic on overflow
// RISC-V allows 32-bit load/stores to be implemented as either little-endian
@ -44,8 +53,25 @@ func (ve *VirtualEEI) WriteByte(addr uint32, value byte) error {
return nil
}
func (ve *VirtualEEI) Write16(addr uint32, value uint16) error {
// n.b. will panic on split page boundary
page, ok := ve.memPages[addr>>12]
inPageAddr := addr & 0b111111111111
// Slices are reference-types, this will write-through into ve.memPages
page[inPageAddr] = byte((value >> 8) & 0b11111111)
page[inPageAddr+1] = byte(value & 0b11111111)
if !ok {
ve.memPages[addr>>12] = page
}
return nil
}
func (ve *VirtualEEI) Write32(addr, value uint32) error {
// n.b. will panic on overflow
// n.b. will panic on split page boundary
page, ok := ve.memPages[addr>>12]
inPageAddr := addr & 0b111111111111
@ -89,3 +115,5 @@ func (ve *VirtualEEI) Syscall() {
func (ve *VirtualEEI) Trap() (bool, error) {
panic("trap")
}
var _ EEI = &VirtualEEI{} // interface assertion