commit fe59fd001fad39f32451291684008c4331e40720
parent 212101e59ff315cae71f90ad037335e20e210c10
Author: Bradley Gannon <bradley@bradleygannon.com>
Date: Fri, 27 Feb 2026 17:17:50 -0500
Add support for all load and store instructions in RV32I
Diffstat:
5 files changed, 175 insertions(+), 72 deletions(-)
diff --git a/src/main/scala/controlUnit.scala b/src/main/scala/controlUnit.scala
@@ -5,6 +5,7 @@ import chisel3.util._
import com.rinthyAi.perilus.alu._
import com.rinthyAi.perilus.extendUnit._
+import com.rinthyAi.perilus.memory._
class ControlUnit(withDebug: Boolean = false) extends Module {
// Figure 7.28 (page 423)
@@ -19,6 +20,7 @@ class ControlUnit(withDebug: Boolean = false) extends Module {
val resultSrc = Output(ResultSrc())
val immSrc = Output(ImmSrc())
val aluControl = Output(AluControl())
+ val dataMask = Output(DataMask())
val debug = if (withDebug) Some(Output(new Bundle {
val state = ControlUnitState()
}))
@@ -45,6 +47,18 @@ class ControlUnit(withDebug: Boolean = false) extends Module {
io.regWrite := false.B
io.resultSrc := ResultSrc.aluOutBuf
+ when(io.op === Opcode.load || io.op === Opcode.store) {
+ when(io.funct3 === 0.U || io.funct3 === 4.U) {
+ io.dataMask := DataMask.byte
+ }.elsewhen(io.funct3 === 1.U || io.funct3 === 5.U) {
+ io.dataMask := DataMask.half
+ }.otherwise {
+ io.dataMask := DataMask.word
+ }
+ }.otherwise {
+ io.dataMask := DataMask.word
+ }
+
// 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
@@ -153,6 +153,7 @@ class Perilus(
}
io.memory.io.writeData := rd2Buf
io.memory.io.writeEnable := controlUnit.io.memWrite
+ io.memory.io.dataMask := controlUnit.io.dataMask
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
@@ -15,6 +15,7 @@ class Memory(
val writeData = Input(UInt(width))
val writeEnable = Input(Bool())
val readData = Output(UInt(width))
+ val dataMask = Input(DataMask())
val debug =
if (withDebug) Some(new Bundle {
val memAddr = Input(UInt(width))
@@ -35,8 +36,14 @@ class Memory(
})
when(io.writeEnable) {
- memory.write(io.address >> AddressShift, io.writeData)
+ memory.write(io.address >> AddressShift, io.writeData & io.dataMask.asUInt)
}
- io.readData := memory.read(io.address >> AddressShift)
+ io.readData := memory.read(io.address >> AddressShift) & io.dataMask.asUInt
+}
+
+object DataMask extends ChiselEnum {
+ val byte = Value("h000000ff".U)
+ val half = Value("h0000ffff".U)
+ val word = Value("hffffffff".U)
}
diff --git a/src/test/scala/perilus/main.scala b/src/test/scala/perilus/main.scala
@@ -13,26 +13,36 @@ import com.rinthyAi.perilus.controlUnit.ControlUnitState
class PerilusTests extends AnyFunSpec with ChiselSim {
describe("Perilus") {
describe("executes I-type RV32I instructions") {
- def testIType(
+ def assembleIType(
funct3: Int,
funct7_5: Boolean,
rd: Int,
rs1: Int,
rs1Value: Int,
imm: Int,
- operation: (Int, Int) => Int
- ): Unit = {
+ op: Int
+ ): Int = {
val immWithFunct7_5Set = if (funct7_5) imm | (1 << 10) else imm
val immShifted = (immWithFunct7_5Set & 0xfff) << 20
val rs1Shifted = (rs1 & 0x1f) << 15
val funct3Shifted = (funct3 & 0x7) << 12
val rdShifted = (rd & 0x1f) << 7
- val op = 19
- val instr = immShifted | rs1Shifted | funct3Shifted | rdShifted | op
-
+ val opMasked = op & 0x7f
+ immShifted | rs1Shifted | funct3Shifted | rdShifted | opMasked
+ }
+ def testIType(
+ funct3: Int,
+ funct7_5: Boolean,
+ rd: Int,
+ rs1: Int,
+ rs1Value: Int,
+ imm: Int,
+ operation: (Int, Int) => Int
+ ): Unit = {
var registerFile = ArrayBuffer.fill(32)(0x00000000)
registerFile(rs1) = rs1Value
val memory = ArrayBuffer.fill(64)(0x00000000)
+ val instr = assembleIType(funct3, funct7_5, rd, rs1, rs1Value, imm, 19)
memory(0) = instr
val expected = operation(rs1Value, imm)
@@ -66,24 +76,20 @@ class PerilusTests extends AnyFunSpec with ChiselSim {
}
}
}
- it("lb") {
- cancel("Not yet implemented")
- }
- it("lh") {
- cancel("Not yet implemented")
- }
- it("lw") {
- val rd = 21
- val rs1 = 11
- val base = 0x30
- val imm = 0xa8
- val memAddr = base + imm
- val memData = 0x0f3f9400
-
+ def testLoad(
+ funct3: Int,
+ rd: Int,
+ rs1: Int,
+ rs1Value: Int,
+ imm: Int,
+ memData: Int,
+ dataMask: Int
+ ): Unit = {
+ val memAddr = rs1Value + imm
var registerFile = ArrayBuffer.fill(32)(0x00000000)
- registerFile(rs1) = 0x00000030
+ registerFile(rs1) = rs1Value
var memory = ArrayBuffer.fill(64)(0x00000000)
- memory(0) = 0x0a85aa83 // lw x21, 0xa8(x11)
+ memory(0) = assembleIType(funct3, false, rd, rs1, rs1Value, imm, 3)
memory(memAddr / 4) = memData
simulate(
@@ -96,36 +102,76 @@ class PerilusTests extends AnyFunSpec with ChiselSim {
{
val perilusDebug = perilus.io.debug.get
- // rs1 contains the base pointer
perilusDebug.reg.poke(rs1)
- perilusDebug.regData.expect(base)
-
- // rd is empty
+ perilusDebug.regData.expect(toULong(rs1Value))
perilusDebug.reg.poke(rd)
- perilusDebug.regData.expect(0.U)
-
- // memAddr contains memData
+ perilusDebug.regData.expect(if (rd == rs1) toULong(rs1Value) else 0)
perilusDebug.memAddr.poke(memAddr)
- perilusDebug.memData.expect(memData)
+ perilusDebug.memData.expect(toULong(memData))
- // execute the instruction
perilus.clock.step(5)
- // rd contains memData
perilusDebug.reg.poke(rd)
- perilusDebug.regData.expect(memData)
-
- // contents of memAddr have not changed
+ perilusDebug.regData.expect(toULong(memData & dataMask))
perilusDebug.memAddr.poke(memAddr)
- perilusDebug.memData.expect(memData)
+ perilusDebug.memData.expect(toULong(memData))
}
}
}
+ it("lb") {
+ testLoad(
+ funct3 = 0,
+ rd = 10,
+ rs1 = 10,
+ rs1Value = 0x14,
+ imm = -0x07,
+ memData = 0x5b9ccada,
+ dataMask = 0x000000ff
+ )
+ }
+ it("lh") {
+ testLoad(
+ funct3 = 1,
+ rd = 31,
+ rs1 = 14,
+ rs1Value = 0x0e,
+ imm = 0x14,
+ memData = 0x8b9d4da4,
+ dataMask = 0x0000ffff
+ )
+ }
+ it("lw") {
+ testLoad(
+ funct3 = 2,
+ rd = 21,
+ rs1 = 11,
+ rs1Value = 0x30,
+ imm = 0xa8,
+ memData = 0x0f3f9400,
+ dataMask = 0xffffffff
+ )
+ }
it("lbu") {
- cancel("Not yet implemented")
+ testLoad(
+ funct3 = 4,
+ rd = 7,
+ rs1 = 13,
+ rs1Value = 0x3b,
+ imm = 0x07,
+ memData = 0x55c9a074,
+ dataMask = 0x000000ff
+ )
}
it("lhu") {
- cancel("Not yet implemented")
+ testLoad(
+ funct3 = 5,
+ rd = 20,
+ rs1 = 4,
+ rs1Value = 0x25,
+ imm = 0x2f,
+ memData = 0xb3fecaab,
+ dataMask = 0x0000ffff
+ )
}
it("addi") {
testIType(
@@ -239,25 +285,29 @@ class PerilusTests extends AnyFunSpec with ChiselSim {
}
}
describe("executes S-type RV32I instructions") {
- it("sb") {
- cancel("Not yet implemented")
- }
- it("sh") {
- cancel("Not yet implemented")
- }
- it("sw") {
- val rs1 = 9
- val rs2 = 6
- val base = 0x38
- val imm = -4
- val memAddr = base + imm
- val memData = 0x01830169
+ def testSType(
+ funct3: Int,
+ rs1: Int,
+ rs1Value: Int,
+ rs2: Int,
+ rs2Value: Int,
+ imm: Int,
+ dataMask: Int
+ ): Unit = {
+ val immUpper = (imm & 0xfe0) << 20
+ val rs2Shifted = (rs2 & 0x1f) << 20
+ val rs1Shifted = (rs1 & 0x1f) << 15
+ val funct3Shifted = (funct3 & 0x7) << 12
+ val immLower = (imm & 0x1f) << 7
+ val op = 0x23
+ val instr = immUpper | rs2Shifted | rs1Shifted | funct3Shifted | immLower | op
var registerFile = ArrayBuffer.fill(32)(0x00000000)
- registerFile(rs1) = base
- registerFile(rs2) = memData
+ registerFile(rs1) = rs1Value
+ registerFile(rs2) = rs2Value
var memory = ArrayBuffer.fill(64)(0x00000000)
- memory(0) = 0xfe64ae23 // sw x6, -4(x9)
+ memory(0) = instr
+ val memAddr = rs1Value + imm
simulate(
new Perilus(
@@ -269,27 +319,53 @@ class PerilusTests extends AnyFunSpec with ChiselSim {
{
val perilusDebug = perilus.io.debug.get
- // rs1 contains the base pointer
perilusDebug.reg.poke(rs1)
- perilusDebug.regData.expect(base)
-
- // rs2 contains data to be stored
+ perilusDebug.regData.expect(toULong(rs1Value))
perilusDebug.reg.poke(rs2)
- perilusDebug.regData.expect(memData)
-
- // memAddr doesn't have memData yet
+ perilusDebug.regData.expect(toULong(rs2Value))
perilusDebug.memAddr.poke(memAddr)
- assert(perilusDebug.memData.peek().litValue != memData)
+ assert(perilusDebug.memData.peek().litValue != toULong(rs2Value & dataMask))
- // execute the instruction
perilus.clock.step(4)
- // contents of memAddr have not changed
perilusDebug.memAddr.poke(memAddr)
- perilusDebug.memData.expect(memData)
+ perilusDebug.memData.expect(toULong(rs2Value & dataMask))
}
}
}
+ it("sb") {
+ testSType(
+ funct3 = 0,
+ rs1 = 17,
+ rs1Value = 0x1c,
+ rs2 = 21,
+ rs2Value = 0x52ba341a,
+ imm = -0x18,
+ dataMask = 0x000000ff
+ )
+ }
+ it("sh") {
+ testSType(
+ funct3 = 1,
+ rs1 = 4,
+ rs1Value = 0x21,
+ rs2 = 6,
+ rs2Value = 0x02d3b5bd,
+ imm = -0x9,
+ dataMask = 0x0000ffff
+ )
+ }
+ it("sw") {
+ testSType(
+ funct3 = 2,
+ rs1 = 9,
+ rs1Value = 0x38,
+ rs2 = 6,
+ rs2Value = 0x01830169,
+ imm = -0x4,
+ dataMask = 0xffffffff
+ )
+ }
}
describe("executes R-type RV32I instructions") {
def testRType(
@@ -329,19 +405,21 @@ class PerilusTests extends AnyFunSpec with ChiselSim {
val perilusDebug = perilus.io.debug.get
perilusDebug.memAddr.poke(0)
- perilusDebug.memData.expect(instr)
+ perilusDebug.memData.expect(toULong(instr))
perilusDebug.reg.poke(rs1)
perilusDebug.regData.expect(toULong(rs1Value))
perilusDebug.reg.poke(rs2)
perilusDebug.regData.expect(toULong(rs2Value))
perilusDebug.reg.poke(rd)
- perilusDebug.regData.expect(if (rd == rs1) rs1Value else if (rd == rs2) rs2Value else 0)
+ perilusDebug.regData.expect(
+ if (rd == rs1) toULong(rs1Value) else if (rd == rs2) toULong(rs2Value) else 0
+ )
perilus.clock.step(4)
perilus.io.pc.expect(4)
perilusDebug.memAddr.poke(0)
- perilusDebug.memData.expect(instr)
+ perilusDebug.memData.expect(toULong(instr))
perilusDebug.reg.poke(rs1)
perilusDebug.regData.expect(if (rd == rs1) toULong(expected) else toULong(rs1Value))
perilusDebug.reg.poke(rs2)
@@ -514,7 +592,7 @@ class PerilusTests extends AnyFunSpec with ChiselSim {
// pc should be at address 1 because x3 != x6
val expected_1 = pc_t.litValue + 4
- perilus.io.pc.expect(expected_1.U)
+ perilus.io.pc.expect(expected_1)
pc_t = perilus.io.pc.peek()
// execute the second beq instruction:
@@ -523,7 +601,7 @@ class PerilusTests extends AnyFunSpec with ChiselSim {
// pc should be at address 5 because x6 == x9
val expected_2 = pc_t.litValue + offset.litValue
- perilus.io.pc.expect(expected_2.U)
+ perilus.io.pc.expect(expected_2)
}
}
}
diff --git a/src/test/scala/perilus/memory.scala b/src/test/scala/perilus/memory.scala
@@ -15,14 +15,17 @@ class MemoryTests extends AnyFunSpec with ChiselSim {
simulate(new Memory(numWords, 32.W, initMemFile(ArrayBuffer.fill(numWords)(0x00000000)))) {
memory =>
{
+ // TODO add more tests for different dataMask configurations
val addr1 = "h4".U
val addr2 = "h18".U
val data1 = "he7d857de".U
val data2 = "h38cc122f".U
+ val dataMask = 0xffffffff
memory.io.address.poke(addr1)
memory.io.writeData.poke(data1)
memory.io.writeEnable.poke(true.B)
+ memory.io.dataMask.poke(dataMask)
memory.io.readData.expect(0.U)
memory.clock.step(1)
memory.io.readData.expect(data1)