commit cfed332f30f293de15b5252f469aa070eb3aa5ed
parent 65f791334ce5c7e1fe89cd0e5428e37ef5c3b448
Author: Bradley Gannon <bradley@bradleygannon.com>
Date: Fri, 30 Jan 2026 17:34:36 -0500
Add support for add, sub, slt, or, and and instructions
Diffstat:
1 file changed, 344 insertions(+), 127 deletions(-)
diff --git a/src/test/scala/perilus/main.scala b/src/test/scala/perilus/main.scala
@@ -2,6 +2,7 @@ package com.rinthyAi.perilus.test.main
import chisel3._
import chisel3.simulator.scalatest.ChiselSim
+import java.lang.Integer.{toUnsignedLong => toULong}
import org.scalatest.funspec.AnyFunSpec
import scala.collection.mutable.ArrayBuffer
@@ -10,153 +11,369 @@ import com.rinthyAi.perilus.test.utils.TestUtils.initMemFile
class PerilusTests extends AnyFunSpec with ChiselSim {
describe("Perilus") {
- it("executes lw") {
- val rd = 21
- val rs1 = 11
- val base = 0x30
- val imm = 0xa8
- val memAddr = base + imm
- val memData = 0x0f3f9400
-
- var registerFile = ArrayBuffer.fill(32)(0x00000000)
- registerFile(rs1) = 0x00000030
- var memory = ArrayBuffer.fill(64)(0x00000000)
- memory(0) = 0x0a85aa83 // lw x21, 0xa8(x11)
- memory(memAddr / 4) = memData
-
- simulate(
- new Perilus(
- initRegs = initMemFile(registerFile),
- initMem = initMemFile(memory),
- withDebug = true
- )
- ) { perilus =>
- {
- val perilusDebug = perilus.io.debug.get
+ describe("executes I-type RV32I instructions") {
+ 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
+
+ var registerFile = ArrayBuffer.fill(32)(0x00000000)
+ registerFile(rs1) = 0x00000030
+ var memory = ArrayBuffer.fill(64)(0x00000000)
+ memory(0) = 0x0a85aa83 // lw x21, 0xa8(x11)
+ memory(memAddr / 4) = memData
- // rs1 contains the base pointer
- perilusDebug.reg.poke(rs1)
- perilusDebug.regData.expect(base)
+ simulate(
+ new Perilus(
+ initRegs = initMemFile(registerFile),
+ initMem = initMemFile(memory),
+ withDebug = true
+ )
+ ) { perilus =>
+ {
+ val perilusDebug = perilus.io.debug.get
- // rd is empty
- perilusDebug.reg.poke(rd)
- perilusDebug.regData.expect(0.U)
+ // rs1 contains the base pointer
+ perilusDebug.reg.poke(rs1)
+ perilusDebug.regData.expect(base)
- // memAddr contains memData
- perilusDebug.memAddr.poke(memAddr)
- perilusDebug.memData.expect(memData)
+ // rd is empty
+ perilusDebug.reg.poke(rd)
+ perilusDebug.regData.expect(0.U)
- // execute the instruction
- perilus.clock.step(5)
+ // memAddr contains memData
+ perilusDebug.memAddr.poke(memAddr)
+ perilusDebug.memData.expect(memData)
- // rd contains memData
- perilusDebug.reg.poke(rd)
- perilusDebug.regData.expect(memData)
+ // execute the instruction
+ perilus.clock.step(5)
- // contents of memAddr have not changed
- perilusDebug.memAddr.poke(memAddr)
- perilusDebug.memData.expect(memData)
+ // rd contains memData
+ perilusDebug.reg.poke(rd)
+ perilusDebug.regData.expect(memData)
+
+ // contents of memAddr have not changed
+ perilusDebug.memAddr.poke(memAddr)
+ perilusDebug.memData.expect(memData)
+ }
}
}
+ it("lbu") {
+ cancel("Not yet implemented")
+ }
+ it("lhu") {
+ cancel("Not yet implemented")
+ }
+ it("addi") {
+ cancel("Not yet implemented")
+ }
+ it("slli") {
+ cancel("Not yet implemented")
+ }
+ it("slti") {
+ cancel("Not yet implemented")
+ }
+ it("sltiu") {
+ cancel("Not yet implemented")
+ }
+ it("xori") {
+ cancel("Not yet implemented")
+ }
+ it("srli") {
+ cancel("Not yet implemented")
+ }
+ it("srai") {
+ cancel("Not yet implemented")
+ }
+ it("ori") {
+ cancel("Not yet implemented")
+ }
+ it("andi") {
+ cancel("Not yet implemented")
+ }
+ it("jalr") {
+ cancel("Not yet implemented")
+ }
}
- it("executes sw") {
- val rs1 = 9
- val rs2 = 6
- val base = 0x38
- val imm = -4
- val memAddr = base + imm
- val memData = 0x01830169
-
- var registerFile = ArrayBuffer.fill(32)(0x00000000)
- registerFile(rs1) = base
- registerFile(rs2) = memData
- var memory = ArrayBuffer.fill(64)(0x00000000)
- memory(0) = 0xfe64ae23 // sw x6, -4(x9)
-
- simulate(
- new Perilus(
- initRegs = initMemFile(registerFile),
- initMem = initMemFile(memory),
- withDebug = true
- )
- ) { perilus =>
- {
- val perilusDebug = perilus.io.debug.get
+ describe("executes U-type RV32I instructions") {
+ it("auipc") {
+ cancel("Not yet implemented")
+ }
+ it("lui") {
+ cancel("Not yet implemented")
+ }
+ }
+ 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
+
+ var registerFile = ArrayBuffer.fill(32)(0x00000000)
+ registerFile(rs1) = base
+ registerFile(rs2) = memData
+ var memory = ArrayBuffer.fill(64)(0x00000000)
+ memory(0) = 0xfe64ae23 // sw x6, -4(x9)
- // rs1 contains the base pointer
- perilusDebug.reg.poke(rs1)
- perilusDebug.regData.expect(base)
+ simulate(
+ new Perilus(
+ initRegs = initMemFile(registerFile),
+ initMem = initMemFile(memory),
+ withDebug = true
+ )
+ ) { perilus =>
+ {
+ val perilusDebug = perilus.io.debug.get
- // rs2 contains data to be stored
- perilusDebug.reg.poke(rs2)
- perilusDebug.regData.expect(memData)
+ // rs1 contains the base pointer
+ perilusDebug.reg.poke(rs1)
+ perilusDebug.regData.expect(base)
- // memAddr doesn't have memData yet
- perilusDebug.memAddr.poke(memAddr)
- assert(perilusDebug.memData.peek().litValue != memData)
+ // rs2 contains data to be stored
+ perilusDebug.reg.poke(rs2)
+ perilusDebug.regData.expect(memData)
- // execute the instruction
- perilus.clock.step(4)
+ // memAddr doesn't have memData yet
+ perilusDebug.memAddr.poke(memAddr)
+ assert(perilusDebug.memData.peek().litValue != memData)
- // contents of memAddr have not changed
- perilusDebug.memAddr.poke(memAddr)
- perilusDebug.memData.expect(memData)
+ // execute the instruction
+ perilus.clock.step(4)
+
+ // contents of memAddr have not changed
+ perilusDebug.memAddr.poke(memAddr)
+ perilusDebug.memData.expect(memData)
+ }
}
}
}
- it("executes beq") {
- val rs1 = 3
- val rs2 = 6
- val rs3 = 9
- val offset = 16.U
-
- var registerFile = ArrayBuffer.fill(32)(0x00000000)
- registerFile(rs1) = 0xabcdef01
- registerFile(rs2) = 0xabcde012
- registerFile(rs3) = 0xabcde012
- var memory = ArrayBuffer.fill(64)(0x00000000)
- memory(0) = 0x02618563 // beq x3, x6, 0x4
- memory(1) = 0x00930863 // beq x6, x9, 0x4
-
- simulate(
- new Perilus(
- initRegs = initMemFile(registerFile),
- initMem = initMemFile(memory),
- withDebug = true
+ describe("executes R-type RV32I instructions") {
+ it("add") {
+ testRType(
+ funct3 = 0,
+ funct7_5 = false,
+ rd = 7,
+ rs1 = 27,
+ rs2 = 17,
+ rs1Value = 0xb8505d59,
+ rs2Value = 0x6719e7a9,
+ operation = (rs1, rs2) => rs1 + rs2
+ )
+ }
+ it("sub") {
+ testRType(
+ funct3 = 0,
+ funct7_5 = true,
+ rd = 18,
+ rs1 = 14,
+ rs2 = 15,
+ rs1Value = 0xdd729ea2,
+ rs2Value = 0x2da48860,
+ operation = (rs1, rs2) => rs1 - rs2
+ )
+ }
+ it("sll") {
+ cancel("Not yet implemented")
+ }
+ it("slt") {
+ testRType(
+ funct3 = 2,
+ funct7_5 = false,
+ rd = 20,
+ rs1 = 31,
+ rs2 = 11,
+ rs1Value = 0xe680ac63,
+ rs2Value = 0x33afcd8f,
+ operation = (rs1, rs2) => if (toULong(rs1) < toULong(rs2)) 1 else 0
+ )
+ }
+ it("sltu") {
+ cancel("Not yet implemented")
+ }
+ it("xor") {
+ cancel("Not yet implemented")
+ }
+ it("srl") {
+ cancel("Not yet implemented")
+ }
+ it("sra") {
+ cancel("Not yet implemented")
+ }
+ it("or") {
+ testRType(
+ funct3 = 6,
+ funct7_5 = false,
+ rd = 8,
+ rs1 = 21,
+ rs2 = 12,
+ rs1Value = 0x4e39fafd,
+ rs2Value = 0xf8df0e20,
+ operation = (rs1, rs2) => rs1 | rs2
+ )
+ }
+ it("and") {
+ testRType(
+ funct3 = 7,
+ funct7_5 = false,
+ rd = 9,
+ rs1 = 6,
+ rs2 = 10,
+ rs1Value = 0xe9ac164c,
+ rs2Value = 0xe279cb88,
+ operation = (rs1, rs2) => rs1 & rs2
)
- ) { perilus =>
- {
- val perilusDebug = perilus.io.debug.get
-
- // Verify that registers are/aren't equal as expected
- perilusDebug.reg.poke(rs1)
- val rs1_t = perilusDebug.regData.peek()
- perilusDebug.reg.poke(rs2)
- val rs2_t = perilusDebug.regData.peek()
- perilusDebug.reg.poke(rs3)
- val rs3_t = perilusDebug.regData.peek()
- assert(rs1_t.litValue != rs2_t.litValue)
- assert(rs2_t.litValue == rs3_t.litValue)
-
- var pc_t = perilus.io.pc.peek()
- // execute the first beq instruction:fe64ae23
- // beq x3, x6, 0x4
- perilus.clock.step(3)
-
- // pc should be at address 1 because x3 != x6
- val expected_1 = pc_t.litValue + 4
- perilus.io.pc.expect(expected_1.U)
- pc_t = perilus.io.pc.peek()
-
- // execute the second beq instruction:
- // beq x6, x9, 0x4
- perilus.clock.step(3)
-
- // pc should be at address 5 because x6 == x9
- val expected_2 = pc_t.litValue + offset.litValue
- perilus.io.pc.expect(expected_2.U)
+ }
+ }
+ describe("executes B-type RV32I instructions") {
+ it("beq") {
+ val rs1 = 3
+ val rs2 = 6
+ val rs3 = 9
+ val offset = 16.U
+
+ var registerFile = ArrayBuffer.fill(32)(0x00000000)
+ registerFile(rs1) = 0xabcdef01
+ registerFile(rs2) = 0xabcde012
+ registerFile(rs3) = 0xabcde012
+ var memory = ArrayBuffer.fill(64)(0x00000000)
+ memory(0) = 0x02618563 // beq x3, x6, 0x4
+ memory(1) = 0x00930863 // beq x6, x9, 0x4
+
+ simulate(
+ new Perilus(
+ initRegs = initMemFile(registerFile),
+ initMem = initMemFile(memory),
+ withDebug = true
+ )
+ ) { perilus =>
+ {
+ val perilusDebug = perilus.io.debug.get
+
+ // Verify that registers are/aren't equal as expected
+ perilusDebug.reg.poke(rs1)
+ val rs1_t = perilusDebug.regData.peek()
+ perilusDebug.reg.poke(rs2)
+ val rs2_t = perilusDebug.regData.peek()
+ perilusDebug.reg.poke(rs3)
+ val rs3_t = perilusDebug.regData.peek()
+ assert(rs1_t.litValue != rs2_t.litValue)
+ assert(rs2_t.litValue == rs3_t.litValue)
+
+ var pc_t = perilus.io.pc.peek()
+ // execute the first beq instruction:fe64ae23
+ // beq x3, x6, 0x4
+ perilus.clock.step(3)
+
+ // pc should be at address 1 because x3 != x6
+ val expected_1 = pc_t.litValue + 4
+ perilus.io.pc.expect(expected_1.U)
+ pc_t = perilus.io.pc.peek()
+
+ // execute the second beq instruction:
+ // beq x6, x9, 0x4
+ perilus.clock.step(3)
+
+ // pc should be at address 5 because x6 == x9
+ val expected_2 = pc_t.litValue + offset.litValue
+ perilus.io.pc.expect(expected_2.U)
+ }
}
}
+ it("bne") {
+ cancel("Not yet implemented")
+ }
+ it("blt") {
+ cancel("Not yet implemented")
+ }
+ it("bge") {
+ cancel("Not yet implemented")
+ }
+ it("bltu") {
+ cancel("Not yet implemented")
+ }
+ it("bgeu") {
+ cancel("Not yet implemented")
+ }
+ }
+ it("executes jal") {
+ cancel("Not yet implemented")
+ }
+ }
+ def testRType(
+ funct3: Int,
+ funct7_5: Boolean,
+ rd: Int,
+ rs1: Int,
+ rs2: Int,
+ rs1Value: Int,
+ rs2Value: Int,
+ operation: (Int, Int) => Int
+ ): Unit = {
+ val funct7 = (if (funct7_5) 1 << 5 else 0) << 25
+ val rs2Shifted = (rs2 & 0x1f) << 20
+ val rs1Shifted = (rs1 & 0x1f) << 15
+ val funct3Shifted = (funct3 & 0x7) << 12
+ val rdShifted = (rd & 0x1f) << 7
+ val op = 51
+ val instr = funct7 | rs2Shifted | rs1Shifted | funct3Shifted | rdShifted | op
+
+ var registerFile = ArrayBuffer.fill(32)(0x00000000)
+ registerFile(rs1) = rs1Value
+ registerFile(rs2) = rs2Value
+ var memory = ArrayBuffer.fill(64)(0x00000000)
+ memory(0) = instr
+
+ val expected = operation(rs1Value, rs2Value)
+
+ simulate(
+ new Perilus(
+ initRegs = initMemFile(registerFile),
+ initMem = initMemFile(memory),
+ withDebug = true
+ )
+ ) { perilus =>
+ {
+ val perilusDebug = perilus.io.debug.get
+
+ perilusDebug.memAddr.poke(0)
+ perilusDebug.memData.expect(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(0)
+
+ perilus.clock.step(4)
+ perilus.io.pc.expect(4)
+
+ perilusDebug.memAddr.poke(0)
+ perilusDebug.memData.expect(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(toULong(expected))
+ }
}
}
}