perilus

git clone https://git.bradleygannon.com/perilus.git
Log | Files | Refs | Submodules | README

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:
Msrc/main/scala/controlUnit.scala | 8+++++++-
Msrc/main/scala/main.scala | 3++-
Msrc/main/scala/memory.scala | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Msrc/main/scala/registerFile.scala | 12++++++++++--
Msrc/test/scala/perilus/main.scala | 379+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Msrc/test/scala/perilus/memory.scala | 2+-
Msrc/test/scala/perilus/registerFile.scala | 12++++++++++++
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)))