commit 2e22e0deeb797bbe0737d9647500a604d9ee3b83
parent 81c4e9227f1fb8c382d3db522c071a736ae843e8
Author: Bradley Gannon <bradley@bradleygannon.com>
Date: Sun, 26 Apr 2026 17:30:51 -0400
Fix several problems with the memory module
Diffstat:
7 files changed, 408 insertions(+), 107 deletions(-)
diff --git a/src/main/scala/controlUnit.scala b/src/main/scala/controlUnit.scala
@@ -14,7 +14,7 @@ class ControlUnit(withDebug: Boolean = false) extends Module {
val funct3 = Input(UInt(3.W))
val funct7_5 = Input(Bool())
val zero, lessThan = Input(Bool())
- val adrSrc, irWrite, memWrite, pcWrite, regWrite = Output(Bool())
+ val adrSrc, irWrite, memWrite, pcWrite, regWrite, signExtData = Output(Bool())
val aluSrcA = Output(AluSrcA())
val aluSrcB = Output(AluSrcB())
val resultSrc = Output(ResultSrc())
@@ -59,6 +59,12 @@ class ControlUnit(withDebug: Boolean = false) extends Module {
io.dataMask := DataMask.word
}
+ when(io.op === Opcode.load && (io.funct3 === 0.U || io.funct3 === 1.U)) {
+ io.signExtData := true.B
+ }.otherwise {
+ io.signExtData := false.B
+ }
+
// Main FSM (Figure 7.45, page 436)
switch(state) {
is(ControlUnitState.fetch) {
diff --git a/src/main/scala/main.scala b/src/main/scala/main.scala
@@ -38,7 +38,7 @@ class Perilus(
val reg = Input(UInt(5.W))
val regData = Output(UInt(width))
val memAddr = Input(UInt(width))
- val memData = Output(UInt(width))
+ val memData = Output(Vec(width.get / 8, UInt(8.W)))
val state = Output(ControlUnitState())
})
else None
@@ -158,6 +158,7 @@ class Perilus(
io.memory.io.writeData := rd2Buf
io.memory.io.writeEnable := controlUnit.io.memWrite
io.memory.io.dataMask := controlUnit.io.dataMask
+ io.memory.io.signExtData := controlUnit.io.signExtData
io.registerFile.io.a1 := instr(19, 15)
io.registerFile.io.a2 := instr(24, 20)
diff --git a/src/main/scala/memory.scala b/src/main/scala/memory.scala
@@ -1,7 +1,7 @@
package com.rinthyAi.perilus.memory
import chisel3._
-import chisel3.util.log2Ceil
+import chisel3.util._
import chisel3.util.experimental.loadMemoryFromFile
class Memory(
@@ -16,17 +16,21 @@ class Memory(
val writeEnable = Input(Bool())
val readData = Output(UInt(width))
val dataMask = Input(DataMask())
+ val signExtData = Input(Bool())
val debug =
if (withDebug) Some(new Bundle {
val memAddr = Input(UInt(width))
- val memData = Output(UInt(width))
+ val memData = Output(Vec(width.get / 8, UInt(8.W)))
})
else None
})
- private val AddressShift = log2Ceil(width.get / 8)
+ private val BytesPerWord = width.get / 8
+ private val AddressShift = log2Ceil(BytesPerWord)
- val memory = Mem(numWords, UInt(width))
+ // TODO: change this into a SyncReadMem, which is apparently more easily mapped to hardware
+ // but will probably require changes to the control FSM due to synchronous ops
+ val memory = Mem(numWords, Vec(BytesPerWord, UInt(8.W)))
if (initMem.nonEmpty) {
loadMemoryFromFile(memory, initMem)
}
@@ -35,15 +39,92 @@ class Memory(
d.memData := memory.read(d.memAddr >> AddressShift)
})
+ val byteOffset = Wire(UInt(2.W))
+ byteOffset := io.address & 3.U
+
+ // TODO unaligned accesses should raise an exception or something...
+ // for now they just round down to the nearest boundary
+ val maskValue =
+ WireDefault(Vec(BytesPerWord, Bool()), VecInit(false.B, false.B, false.B, false.B))
+ val writeData = WireDefault(Vec(BytesPerWord, UInt(8.W)), VecInit(0.U, 0.U, 0.U, 0.U))
+ when(io.dataMask === DataMask.byte) {
+ when(byteOffset === 0.U) {
+ maskValue(0) := true.B
+ writeData(0) := io.writeData(7, 0)
+ }.elsewhen(byteOffset === 1.U) {
+ maskValue(1) := true.B
+ writeData(1) := io.writeData(7, 0)
+ }.elsewhen(byteOffset === 2.U) {
+ maskValue(2) := true.B
+ writeData(2) := io.writeData(7, 0)
+ }.elsewhen(byteOffset === 3.U) {
+ maskValue(3) := true.B
+ writeData(3) := io.writeData(7, 0)
+ }
+ }.elsewhen(io.dataMask === DataMask.half) {
+ when(byteOffset === 0.U || byteOffset === 1.U) {
+ maskValue(0) := true.B
+ maskValue(1) := true.B
+ writeData(0) := io.writeData(7, 0)
+ writeData(1) := io.writeData(15, 8)
+ }.elsewhen(byteOffset === 2.U || byteOffset === 3.U) {
+ maskValue(2) := true.B
+ maskValue(3) := true.B
+ writeData(2) := io.writeData(7, 0)
+ writeData(3) := io.writeData(15, 8)
+ }
+ }.elsewhen(io.dataMask === DataMask.word) {
+ maskValue(0) := true.B
+ maskValue(1) := true.B
+ maskValue(2) := true.B
+ maskValue(3) := true.B
+ writeData(0) := io.writeData(7, 0)
+ writeData(1) := io.writeData(15, 8)
+ writeData(2) := io.writeData(23, 16)
+ writeData(3) := io.writeData(31, 24)
+ }
+
when(io.writeEnable && !reset.asBool) {
- memory.write(io.address >> AddressShift, io.writeData & io.dataMask.asUInt)
+ memory.write(io.address >> AddressShift, writeData, maskValue)
}
- io.readData := memory.read(io.address >> AddressShift) & io.dataMask.asUInt
+ val readBytes = Wire(Vec(BytesPerWord, UInt(8.W)))
+ readBytes := memory.read(io.address >> AddressShift)
+ val returnByte = WireDefault(UInt(8.W), 0.U)
+ val halfOffset = Wire(UInt(1.W))
+ halfOffset := io.address(1)
+ val returnHalf = WireDefault(UInt(16.W), 0.U)
+ when(io.dataMask === DataMask.byte) {
+ when(byteOffset === 3.U) {
+ returnByte := readBytes(3)
+ }.elsewhen(byteOffset === 2.U) {
+ returnByte := readBytes(2)
+ }.elsewhen(byteOffset === 1.U) {
+ returnByte := readBytes(1)
+ }.otherwise {
+ returnByte := readBytes(0)
+ }
+ when(io.signExtData) {
+ io.readData := Cat(Fill(24, returnByte(7)), returnByte)
+ }.otherwise {
+ io.readData := returnByte
+ }
+ }.elsewhen(io.dataMask === DataMask.half) {
+ when(halfOffset === 1.U) {
+ returnHalf := Cat(readBytes(3), readBytes(2))
+ }.otherwise {
+ returnHalf := Cat(readBytes(1), readBytes(0))
+ }
+ when(io.signExtData) {
+ io.readData := Cat(Fill(16, returnHalf(15)), returnHalf(15, 8), returnHalf(7, 0))
+ }.otherwise {
+ io.readData := Cat(returnHalf(15, 8), returnHalf(7, 0))
+ }
+ }.otherwise {
+ io.readData := Cat(readBytes(3), readBytes(2), readBytes(1), readBytes(0))
+ }
}
object DataMask extends ChiselEnum {
- val byte = Value("h000000ff".U)
- val half = Value("h0000ffff".U)
- val word = Value("hffffffff".U)
+ val byte, half, word = Value
}
diff --git a/src/main/scala/registerFile.scala b/src/main/scala/registerFile.scala
@@ -26,8 +26,16 @@ class RegisterFile(width: Width, initRegs: String = "", withDebug: Boolean = fal
d.regData := registerFile.read(d.reg)
})
- io.rd1 := registerFile.read(io.a1)
- io.rd2 := registerFile.read(io.a2)
+ when(io.a1 === 0.U) {
+ io.rd1 := 0.U
+ }.otherwise {
+ io.rd1 := registerFile.read(io.a1)
+ }
+ when(io.a2 === 0.U) {
+ io.rd2 := 0.U
+ }.otherwise {
+ io.rd2 := registerFile.read(io.a2)
+ }
when(io.writeEnable3 && io.a3 =/= 0.U && !reset.asBool) {
registerFile.write(io.a3, io.writeData3)
diff --git a/src/test/scala/perilus/main.scala b/src/test/scala/perilus/main.scala
@@ -60,7 +60,7 @@ class PerilusTests extends AnyFunSpec with ChiselSim {
val perilusDebug = perilus.io.debug.get
perilusDebug.memAddr.poke(0)
- perilusDebug.memData.expect(toULong(instr))
+ assert(perilusDebug.memData.peek().asUInt.litValue.toLong == toULong(instr))
perilusDebug.reg.poke(rs1)
perilusDebug.regData.expect(toULong(rs1Value))
perilusDebug.reg.poke(rd)
@@ -70,7 +70,7 @@ class PerilusTests extends AnyFunSpec with ChiselSim {
perilus.io.pc.expect(toULong(pc))
perilusDebug.memAddr.poke(0)
- perilusDebug.memData.expect(toULong(instr))
+ assert(perilusDebug.memData.peek().asUInt.litValue.toLong == toULong(instr))
perilusDebug.reg.poke(rs1)
perilusDebug.regData.expect(if (rs1 == rd) toULong(expected) else toULong(rs1Value))
perilusDebug.reg.poke(rd)
@@ -85,7 +85,7 @@ class PerilusTests extends AnyFunSpec with ChiselSim {
rs1Value: Int,
imm: Int,
memData: Int,
- dataMask: Int
+ expected: Int
): Unit = {
val memAddr = rs1Value + imm
var registerFile = ArrayBuffer.fill(32)(0x00000000)
@@ -109,71 +109,190 @@ class PerilusTests extends AnyFunSpec with ChiselSim {
perilusDebug.reg.poke(rd)
perilusDebug.regData.expect(if (rd == rs1) toULong(rs1Value) else 0)
perilusDebug.memAddr.poke(memAddr)
- perilusDebug.memData.expect(toULong(memData))
+ assert(perilusDebug.memData.peek().asUInt.litValue.toLong == toULong(memData))
perilus.clock.step(5)
perilusDebug.reg.poke(rd)
- perilusDebug.regData.expect(toULong(memData & dataMask))
+ perilusDebug.regData.expect(toULong(expected))
perilusDebug.memAddr.poke(memAddr)
- perilusDebug.memData.expect(toULong(memData))
+ assert(perilusDebug.memData.peek().asUInt.litValue.toLong == toULong(memData))
}
}
}
- it("lb") {
- testLoad(
- funct3 = 0,
- rd = 10,
- rs1 = 10,
- rs1Value = 0x14,
- imm = -0x07,
- memData = 0x5b9ccada,
- dataMask = 0x000000ff
- )
+ describe("lb") {
+ it("byte 0") {
+ testLoad(
+ funct3 = 0,
+ rd = 24,
+ rs1 = 17,
+ rs1Value = 0x17,
+ imm = 0x25,
+ memData = 0xcdcf5b13,
+ expected = 0x00000013
+ )
+ }
+ it("byte 1") {
+ testLoad(
+ funct3 = 0,
+ rd = 31,
+ rs1 = 11,
+ rs1Value = 0x13,
+ imm = -0xe,
+ memData = 0x9447969e,
+ expected = 0xffffff96
+ )
+ }
+ it("byte 2") {
+ testLoad(
+ funct3 = 0,
+ rd = 25,
+ rs1 = 2,
+ rs1Value = 0x12,
+ imm = 0x20,
+ memData = 0x9cd40f55,
+ expected = 0xffffffd4
+ )
+ }
+ it("byte 3") {
+ testLoad(
+ funct3 = 0,
+ rd = 10,
+ rs1 = 10,
+ rs1Value = 0x14,
+ imm = -0x05,
+ memData = 0x5b9ccada,
+ expected = 0x0000005b
+ )
+ }
}
- it("lh") {
- testLoad(
- funct3 = 1,
- rd = 31,
- rs1 = 14,
- rs1Value = 0x0e,
- imm = 0x14,
- memData = 0x8b9d4da4,
- dataMask = 0x0000ffff
- )
+ describe("lh") {
+ it("lower") {
+ testLoad(
+ funct3 = 1,
+ rd = 31,
+ rs1 = 14,
+ rs1Value = 0x0c,
+ imm = 0x14,
+ memData = 0x8c501df9,
+ expected = 0x00001df9
+ )
+ }
+ it("upper") {
+ testLoad(
+ funct3 = 1,
+ rd = 15,
+ rs1 = 8,
+ rs1Value = 0x1f,
+ imm = 0x13,
+ memData = 0x8b9d4da4,
+ expected = 0xffff8b9d
+ )
+ }
+ it("unaligned (byte 1)") {
+ cancel("Not yet implemented")
+ }
+ it("unaligned (byte 3)") {
+ cancel("Not yet implemented")
+ }
}
- it("lw") {
- testLoad(
- funct3 = 2,
- rd = 21,
- rs1 = 11,
- rs1Value = 0x30,
- imm = 0xa8,
- memData = 0x0f3f9400,
- dataMask = 0xffffffff
- )
+ describe("lw") {
+ it("aligned") {
+ testLoad(
+ funct3 = 2,
+ rd = 21,
+ rs1 = 11,
+ rs1Value = 0x30,
+ imm = 0xa8,
+ memData = 0x0f3f9400,
+ expected = 0x0f3f9400
+ )
+ }
+ it("unaligned (byte 1)") {
+ cancel("Not yet implemented")
+ }
+ it("unaligned (byte 2)") {
+ cancel("Not yet implemented")
+ }
+ it("unaligned (byte 3)") {
+ cancel("Not yet implemented")
+ }
}
- it("lbu") {
- testLoad(
- funct3 = 4,
- rd = 7,
- rs1 = 13,
- rs1Value = 0x3b,
- imm = 0x07,
- memData = 0x55c9a074,
- dataMask = 0x000000ff
- )
+ describe("lbu") {
+ it("byte 0") {
+ testLoad(
+ funct3 = 4,
+ rd = 19,
+ rs1 = 25,
+ rs1Value = 0x1d,
+ imm = 0x07,
+ memData = 0xb240f499,
+ expected = 0x00000099
+ )
+ }
+ it("byte 1") {
+ testLoad(
+ funct3 = 4,
+ rd = 1,
+ rs1 = 29,
+ rs1Value = 0x12,
+ imm = -0x9,
+ memData = 0xaad61de7,
+ expected = 0x0000001d
+ )
+ }
+ it("byte 2") {
+ testLoad(
+ funct3 = 4,
+ rd = 7,
+ rs1 = 13,
+ rs1Value = 0x3b,
+ imm = 0x7,
+ memData = 0x55c9a074,
+ expected = 0x000000c9
+ )
+ }
+ it("byte 3") {
+ testLoad(
+ funct3 = 4,
+ rd = 23,
+ rs1 = 18,
+ rs1Value = 0x12,
+ imm = 0x19,
+ memData = 0xd39dd395,
+ expected = 0x000000d3
+ )
+ }
}
- it("lhu") {
- testLoad(
- funct3 = 5,
- rd = 20,
- rs1 = 4,
- rs1Value = 0x25,
- imm = 0x2f,
- memData = 0xb3fecaab,
- dataMask = 0x0000ffff
- )
+ describe("lhu") {
+ it("lower") {
+ testLoad(
+ funct3 = 5,
+ rd = 20,
+ rs1 = 4,
+ rs1Value = 0x25,
+ imm = 0x2f,
+ memData = 0xb3fecaab,
+ expected = 0x0000caab
+ )
+ }
+ it("upper") {
+ testLoad(
+ funct3 = 5,
+ rd = 16,
+ rs1 = 25,
+ rs1Value = 0x14,
+ imm = 0x6,
+ memData = 0x92b494c8,
+ expected = 0x000092b4
+ )
+ }
+ it("unaligned (byte 1)") {
+ cancel("Not yet implemented")
+ }
+ it("unaligned (byte 3)") {
+ cancel("Not yet implemented")
+ }
}
it("addi") {
testIType(
@@ -316,7 +435,7 @@ class PerilusTests extends AnyFunSpec with ChiselSim {
{
val perilusDebug = perilus.io.debug.get
perilusDebug.memAddr.poke(test_pc)
- perilusDebug.memData.expect(toULong(instr))
+ assert(perilusDebug.memData.peek().asUInt.litValue.toLong == toULong(instr))
perilus.clock.step(6)
perilusDebug.reg.poke(rd)
perilusDebug.regData.expect(toULong(expected))
@@ -346,7 +465,8 @@ class PerilusTests extends AnyFunSpec with ChiselSim {
rs2: Int,
rs2Value: Int,
imm: Int,
- dataMask: Int
+ memData: Int,
+ expected: Int
): Unit = {
val immUpper = (imm & 0xfe0) << 20
val rs2Shifted = (rs2 & 0x1f) << 20
@@ -362,6 +482,7 @@ class PerilusTests extends AnyFunSpec with ChiselSim {
var memory = ArrayBuffer.fill(64)(0x00000000)
memory(0) = instr
val memAddr = rs1Value + imm
+ memory(memAddr / 4) = memData
simulate(
new Perilus(
@@ -378,47 +499,119 @@ class PerilusTests extends AnyFunSpec with ChiselSim {
perilusDebug.reg.poke(rs2)
perilusDebug.regData.expect(toULong(rs2Value))
perilusDebug.memAddr.poke(memAddr)
- assert(perilusDebug.memData.peek().litValue != toULong(rs2Value & dataMask))
+ assert(perilusDebug.memData.peek().asUInt.litValue.toLong == toULong(memData))
perilus.clock.step(4)
perilusDebug.memAddr.poke(memAddr)
- perilusDebug.memData.expect(toULong(rs2Value & dataMask))
+ assert(perilusDebug.memData.peek().asUInt.litValue.toLong == toULong(expected))
}
}
}
- it("sb") {
- testSType(
- funct3 = 0,
- rs1 = 17,
- rs1Value = 0x1c,
- rs2 = 21,
- rs2Value = 0x52ba341a,
- imm = -0x18,
- dataMask = 0x000000ff
- )
+ describe("sb") {
+ it("byte 0") {
+ testSType(
+ funct3 = 0,
+ rs1 = 17,
+ rs1Value = 0x1c,
+ rs2 = 21,
+ rs2Value = 0x52ba341a,
+ imm = -0x18,
+ memData = 0x92de8d7b,
+ expected = 0x92de8d1a
+ )
+ }
+ it("byte 1") {
+ testSType(
+ funct3 = 0,
+ rs1 = 23,
+ rs1Value = 0x1e,
+ rs2 = 25,
+ rs2Value = 0xd3ceccf2,
+ imm = -0x9,
+ memData = 0x9c912a5e,
+ expected = 0x9c91f25e
+ )
+ }
+ it("byte 2") {
+ testSType(
+ funct3 = 0,
+ rs1 = 12,
+ rs1Value = 0x24,
+ rs2 = 13,
+ rs2Value = 0x52ba341a,
+ imm = -0x12,
+ memData = 0x92de8d7b,
+ expected = 0x921a8d7b
+ )
+ }
+ it("byte 3") {
+ testSType(
+ funct3 = 0,
+ rs1 = 4,
+ rs1Value = 0x16,
+ rs2 = 17,
+ rs2Value = 0xa456c21f,
+ imm = -0x3,
+ memData = 0xd3a77bd8,
+ expected = 0x1fa77bd8
+ )
+ }
}
- it("sh") {
- testSType(
- funct3 = 1,
- rs1 = 4,
- rs1Value = 0x21,
- rs2 = 6,
- rs2Value = 0x02d3b5bd,
- imm = -0x9,
- dataMask = 0x0000ffff
- )
+ describe("sh") {
+ it("lower") {
+ testSType(
+ funct3 = 1,
+ rs1 = 4,
+ rs1Value = 0x21,
+ rs2 = 6,
+ rs2Value = 0x02d3b5bd,
+ imm = 0xb,
+ memData = 0xf511e1c4,
+ expected = 0xf511b5bd
+ )
+ }
+ it("upper") {
+ testSType(
+ funct3 = 1,
+ rs1 = 12,
+ rs1Value = 0x1e,
+ rs2 = 31,
+ rs2Value = 0xba1a82e4,
+ imm = 0x8,
+ memData = 0xb79581b6,
+ expected = 0x82e481b6
+ )
+ }
+ it("unaligned (byte 1)") {
+ cancel("Not yet implemented")
+ }
+ it("unaligned (byte 3)") {
+ cancel("Not yet implemented")
+ }
}
- it("sw") {
- testSType(
- funct3 = 2,
- rs1 = 9,
- rs1Value = 0x38,
- rs2 = 6,
- rs2Value = 0x01830169,
- imm = -0x4,
- dataMask = 0xffffffff
- )
+ describe("sw") {
+ it("aligned") {
+ testSType(
+ funct3 = 2,
+ rs1 = 9,
+ rs1Value = 0x38,
+ rs2 = 6,
+ rs2Value = 0x01830169,
+ imm = -0x4,
+ memData = 0xbf8cde8b,
+ expected = 0x01830169
+ )
+ }
+ it("unaligned (byte 1)") {
+ cancel("Not yet implemented")
+ }
+ it("unaligned (byte 2)") {
+ cancel("Not yet implemented")
+ }
+ it("unaligned (byte 3)") {
+ cancel("Not yet implemented")
+ }
}
}
describe("executes R-type RV32I instructions") {
@@ -459,7 +652,7 @@ class PerilusTests extends AnyFunSpec with ChiselSim {
val perilusDebug = perilus.io.debug.get
perilusDebug.memAddr.poke(0)
- perilusDebug.memData.expect(toULong(instr))
+ assert(perilusDebug.memData.peek().asUInt.litValue.toLong == toULong(instr))
perilusDebug.reg.poke(rs1)
perilusDebug.regData.expect(toULong(rs1Value))
perilusDebug.reg.poke(rs2)
@@ -473,7 +666,7 @@ class PerilusTests extends AnyFunSpec with ChiselSim {
perilus.io.pc.expect(4)
perilusDebug.memAddr.poke(0)
- perilusDebug.memData.expect(toULong(instr))
+ assert(perilusDebug.memData.peek().asUInt.litValue.toLong == toULong(instr))
perilusDebug.reg.poke(rs1)
perilusDebug.regData.expect(if (rd == rs1) toULong(expected) else toULong(rs1Value))
perilusDebug.reg.poke(rs2)
@@ -639,7 +832,7 @@ class PerilusTests extends AnyFunSpec with ChiselSim {
val perilusDebug = perilus.io.debug.get
perilusDebug.memAddr.poke(0)
- perilusDebug.memData.expect(toULong(instr))
+ assert(perilusDebug.memData.peek().asUInt.litValue.toLong == toULong(instr))
perilusDebug.reg.poke(rs1)
perilusDebug.regData.expect(toULong(rs1Value))
perilusDebug.reg.poke(rs2)
diff --git a/src/test/scala/perilus/memory.scala b/src/test/scala/perilus/memory.scala
@@ -25,7 +25,7 @@ class MemoryTests extends AnyFunSpec with ChiselSim {
memory.io.address.poke(addr1)
memory.io.writeData.poke(data1)
memory.io.writeEnable.poke(true.B)
- memory.io.dataMask.poke(dataMask)
+ memory.io.dataMask.poke(DataMask.word)
memory.io.readData.expect(0.U)
memory.clock.step(1)
memory.io.readData.expect(data1)
diff --git a/src/test/scala/perilus/registerFile.scala b/src/test/scala/perilus/registerFile.scala
@@ -49,6 +49,18 @@ class RegisterFileTests extends AnyFunSpec with ChiselSim {
}
}
}
+ it("always reads zero on x0") {
+ simulate(
+ new RegisterFile(32.W, initMemFile(ArrayBuffer.fill(32)(0xffffffff)))
+ ) { registerFile =>
+ {
+ registerFile.io.a1.poke(0.U)
+ registerFile.io.rd1.expect(0.U)
+ registerFile.io.a2.poke(0.U)
+ registerFile.io.rd2.expect(0.U)
+ }
+ }
+ }
it("doesn't write to x0") {
simulate(
new RegisterFile(32.W, initMemFile(ArrayBuffer.fill(32)(0x00000000)))