commit 212101e59ff315cae71f90ad037335e20e210c10
parent 04a30bbe98c1ee6f8e97ac3dcacd54faf1dadedd
Author: Bradley Gannon <bradley@bradleygannon.com>
Date: Thu, 26 Feb 2026 22:02:35 -0500
Add support for slli, sltiu, xori, srli, and srai
Also make various improvements to test fixtures. The unsigned
set-less-than instruction required a modification to the extend unit for
unsigned I-type immediates. I expect this to be useful for lbu and lhu.
Diffstat:
6 files changed, 199 insertions(+), 130 deletions(-)
diff --git a/src/main/scala/alu.scala b/src/main/scala/alu.scala
@@ -13,6 +13,13 @@ class Alu extends Module {
io.aluResult := 0.U
+ val srcBShift = WireInit(0.U)
+ when(io.srcB(5)) {
+ srcBShift := 32.U
+ }.otherwise {
+ srcBShift := io.srcB(4, 0)
+ }
+
switch(io.aluControl) {
is(AluControl.add) {
io.aluResult := io.srcA + io.srcB
@@ -21,7 +28,7 @@ class Alu extends Module {
io.aluResult := io.srcA - io.srcB
}
is(AluControl.sll) {
- io.aluResult := io.srcA << io.srcB(5, 0)
+ io.aluResult := io.srcA << srcBShift
}
is(AluControl.slt) {
io.aluResult := (io.srcA.asSInt < io.srcB.asSInt).asUInt
@@ -33,10 +40,10 @@ class Alu extends Module {
io.aluResult := io.srcA ^ io.srcB
}
is(AluControl.srl) {
- io.aluResult := io.srcA >> io.srcB(5, 0)
+ io.aluResult := io.srcA >> srcBShift
}
is(AluControl.sra) {
- io.aluResult := (io.srcA.asSInt >> io.srcB(5, 0)).asUInt
+ io.aluResult := (io.srcA.asSInt >> srcBShift).asUInt
}
is(AluControl.or) {
io.aluResult := io.srcA | io.srcB
diff --git a/src/main/scala/controlUnit.scala b/src/main/scala/controlUnit.scala
@@ -148,6 +148,7 @@ class ControlUnit(withDebug: Boolean = false) extends Module {
io.aluControl := aluDecoder.io.aluControl
instrDecoder.io.op := io.op
+ instrDecoder.io.funct3 := io.funct3
io.immSrc := instrDecoder.io.immSrc
}
@@ -202,14 +203,19 @@ class AluDecoder extends Module {
class InstructionDecoder extends Module {
val io = IO(new Bundle {
val op = Input(Opcode())
+ val funct3 = Input(UInt(3.W))
val immSrc = Output(ImmSrc())
})
- io.immSrc := ImmSrc.iType
+ io.immSrc := ImmSrc.iTypeSigned
switch(io.op) {
is(Opcode.load) {
- io.immSrc := ImmSrc.iType
+ when(io.funct3 === 4.U || io.funct3 === 5.U) {
+ io.immSrc := ImmSrc.iTypeUnsigned
+ }.otherwise {
+ io.immSrc := ImmSrc.iTypeSigned
+ }
}
is(Opcode.store) {
io.immSrc := ImmSrc.sType
@@ -218,7 +224,11 @@ class InstructionDecoder extends Module {
io.immSrc := ImmSrc.bType
}
is(Opcode.immediate) {
- io.immSrc := ImmSrc.iType
+ when(io.funct3 === 3.U) {
+ io.immSrc := ImmSrc.iTypeUnsigned
+ }.otherwise {
+ io.immSrc := ImmSrc.iTypeSigned
+ }
}
is(Opcode.jal) {
io.immSrc := ImmSrc.jType
diff --git a/src/main/scala/extendUnit.scala b/src/main/scala/extendUnit.scala
@@ -14,9 +14,12 @@ class ExtendUnit extends Module {
// Table 7.5 (page 412)
switch(io.immSrc) {
- is(ImmSrc.iType) {
+ is(ImmSrc.iTypeSigned) {
io.immExt := Cat(Fill(20, io.input(24)), io.input(24, 13))
}
+ is(ImmSrc.iTypeUnsigned) {
+ io.immExt := Cat(Fill(20, 0.U), io.input(24, 13))
+ }
is(ImmSrc.sType) {
io.immExt := Cat(Fill(20, io.input(24)), io.input(24, 18), io.input(4, 0))
}
@@ -42,5 +45,5 @@ class ExtendUnit extends Module {
}
object ImmSrc extends ChiselEnum {
- val iType, sType, bType, jType = Value
+ val iTypeSigned, iTypeUnsigned, sType, bType, jType = Value
}
diff --git a/src/test/scala/perilus/controlUnit.scala b/src/test/scala/perilus/controlUnit.scala
@@ -112,13 +112,15 @@ class ControlUnitTests extends AnyFunSpec with ChiselSim {
simulate(new InstructionDecoder) { instrDecoder =>
{
instrDecoder.io.op.poke(Opcode.load)
- instrDecoder.io.immSrc.expect(ImmSrc.iType)
+ // TODO include iTypeUnsigned as well
+ instrDecoder.io.immSrc.expect(ImmSrc.iTypeSigned)
instrDecoder.io.op.poke(Opcode.store)
instrDecoder.io.immSrc.expect(ImmSrc.sType)
instrDecoder.io.op.poke(Opcode.branch)
instrDecoder.io.immSrc.expect(ImmSrc.bType)
instrDecoder.io.op.poke(Opcode.immediate)
- instrDecoder.io.immSrc.expect(ImmSrc.iType)
+ // TODO include iTypeUnsigned as well
+ instrDecoder.io.immSrc.expect(ImmSrc.iTypeSigned)
instrDecoder.io.op.poke(Opcode.jal)
instrDecoder.io.immSrc.expect(ImmSrc.jType)
}
diff --git a/src/test/scala/perilus/extendUnit.scala b/src/test/scala/perilus/extendUnit.scala
@@ -11,7 +11,8 @@ class ExtendUnitTests extends AnyFunSpec with ChiselSim {
it("correctly extends I-type immediates") {
simulate(new ExtendUnit) { extendUnit =>
{
- extendUnit.io.immSrc.poke(ImmSrc.iType)
+ // TODO include iTypeUnsigned as well
+ extendUnit.io.immSrc.poke(ImmSrc.iTypeSigned)
extendUnit.io.input.poke("hdc4691".U) // immediate value is 0xe9b
extendUnit.io.immExt.expect("h6e2".U)
diff --git a/src/test/scala/perilus/main.scala b/src/test/scala/perilus/main.scala
@@ -13,6 +13,59 @@ import com.rinthyAi.perilus.controlUnit.ControlUnitState
class PerilusTests extends AnyFunSpec with ChiselSim {
describe("Perilus") {
describe("executes I-type RV32I instructions") {
+ def testIType(
+ funct3: Int,
+ funct7_5: Boolean,
+ rd: Int,
+ rs1: Int,
+ rs1Value: Int,
+ imm: Int,
+ operation: (Int, Int) => Int
+ ): Unit = {
+ 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
+
+ var registerFile = ArrayBuffer.fill(32)(0x00000000)
+ registerFile(rs1) = rs1Value
+ val memory = ArrayBuffer.fill(64)(0x00000000)
+ memory(0) = instr
+
+ val expected = operation(rs1Value, imm)
+
+ 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(toULong(instr))
+ perilusDebug.reg.poke(rs1)
+ perilusDebug.regData.expect(toULong(rs1Value))
+ perilusDebug.reg.poke(rd)
+ perilusDebug.regData.expect(if (rs1 == rd) toULong(rs1Value) else 0)
+
+ perilus.clock.step(4)
+ perilus.io.pc.expect(4)
+
+ perilusDebug.memAddr.poke(0)
+ perilusDebug.memData.expect(toULong(instr))
+ perilusDebug.reg.poke(rs1)
+ perilusDebug.regData.expect(if (rs1 == rd) toULong(expected) else toULong(rs1Value))
+ perilusDebug.reg.poke(rd)
+ perilusDebug.regData.expect(toULong(expected))
+ }
+ }
+ }
it("lb") {
cancel("Not yet implemented")
}
@@ -77,6 +130,7 @@ class PerilusTests extends AnyFunSpec with ChiselSim {
it("addi") {
testIType(
funct3 = 0,
+ funct7_5 = false,
rd = 17,
rs1 = 27,
rs1Value = 0x60f49452,
@@ -85,33 +139,75 @@ class PerilusTests extends AnyFunSpec with ChiselSim {
)
}
it("slli") {
- cancel("Not yet implemented")
+ testIType(
+ funct3 = 1,
+ funct7_5 = false,
+ rd = 12,
+ rs1 = 20,
+ rs1Value = 0x27a091d1,
+ imm = 0x009,
+ operation = (rs1, imm) => rs1 << imm.min(32)
+ )
}
it("slti") {
testIType(
funct3 = 2,
+ funct7_5 = false,
rd = 27,
rs1 = 7,
rs1Value = 0x451753fc,
imm = 0x24e,
- operation = (rs1, imm) => if (toULong(rs1) < toULong(imm)) 1 else 0
+ operation = (rs1, imm) => if (rs1 < imm) 1 else 0
)
}
it("sltiu") {
- cancel("Not yet implemented")
+ testIType(
+ funct3 = 3,
+ funct7_5 = false,
+ rd = 1,
+ rs1 = 24,
+ rs1Value = 0x9efde28d,
+ imm = 0xdf6,
+ operation = (rs1, imm) => if (toULong(rs1) < toULong(imm)) 1 else 0
+ )
}
it("xori") {
- cancel("Not yet implemented")
+ testIType(
+ funct3 = 4,
+ funct7_5 = false,
+ rd = 4,
+ rs1 = 4,
+ rs1Value = 0x1649835d,
+ imm = 0x9fa,
+ operation = (rs1, imm) => rs1 ^ (if ((imm & (1 << 11)) != 0) 0xfffff000 | imm else imm)
+ )
}
it("srli") {
- cancel("Not yet implemented")
+ testIType(
+ funct3 = 5,
+ funct7_5 = false,
+ rd = 2,
+ rs1 = 3,
+ rs1Value = 0x2d178ee5,
+ imm = 26,
+ operation = (rs1, imm) => rs1 >>> imm.min(32)
+ )
}
it("srai") {
- cancel("Not yet implemented")
+ testIType(
+ funct3 = 5,
+ funct7_5 = true,
+ rd = 21,
+ rs1 = 19,
+ rs1Value = 0xb2cb537c,
+ imm = 7,
+ operation = (rs1, imm) => rs1 >> imm.min(32)
+ )
}
it("ori") {
testIType(
funct3 = 6,
+ funct7_5 = true,
rd = 28,
rs1 = 18,
rs1Value = 0xc5212c23,
@@ -122,6 +218,7 @@ class PerilusTests extends AnyFunSpec with ChiselSim {
it("andi") {
testIType(
funct3 = 7,
+ funct7_5 = true,
rd = 12,
rs1 = 1,
rs1Value = 0x275157ac,
@@ -195,6 +292,65 @@ class PerilusTests extends AnyFunSpec with ChiselSim {
}
}
describe("executes R-type RV32I instructions") {
+ 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(if (rd == rs1) rs1Value else if (rd == rs2) rs2Value else 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(if (rd == rs1) toULong(expected) else toULong(rs1Value))
+ perilusDebug.reg.poke(rs2)
+ perilusDebug.regData.expect(if (rd == rs2) toULong(expected) else toULong(rs2Value))
+ perilusDebug.reg.poke(rd)
+ perilusDebug.regData.expect(toULong(expected))
+ }
+ }
+ }
it("add") {
testRType(
funct3 = 0,
@@ -228,7 +384,7 @@ class PerilusTests extends AnyFunSpec with ChiselSim {
rs2 = 25,
rs1Value = 0xd021eb67,
rs2Value = 17,
- operation = (rs1, rs2) => rs1 << rs2
+ operation = (rs1, rs2) => rs1 << rs2.min(32)
)
}
it("slt") {
@@ -276,7 +432,7 @@ class PerilusTests extends AnyFunSpec with ChiselSim {
rs2 = 25,
rs1Value = 0x3fa60574,
rs2Value = 29,
- operation = (rs1, rs2) => rs1 >>> rs2
+ operation = (rs1, rs2) => rs1 >>> rs2.min(32)
)
}
it("sra") {
@@ -288,7 +444,7 @@ class PerilusTests extends AnyFunSpec with ChiselSim {
rs2 = 5,
rs1Value = 0xee91cf2d,
rs2Value = 10,
- operation = (rs1, rs2) => rs1 >> rs2
+ operation = (rs1, rs2) => rs1 >> rs2.min(32)
)
}
it("or") {
@@ -490,114 +646,4 @@ class PerilusTests extends AnyFunSpec with ChiselSim {
}
}
}
- def testIType(
- funct3: Int,
- rd: Int,
- rs1: Int,
- rs1Value: Int,
- imm: Int,
- operation: (Int, Int) => Int
- ): Unit = {
- val immShifted = (imm & 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
-
- var registerFile = ArrayBuffer.fill(32)(0x00000000)
- registerFile(rs1) = rs1Value
- val memory = ArrayBuffer.fill(64)(0x00000000)
- memory(0) = instr
-
- val expected = operation(rs1Value, imm)
-
- 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(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(rd)
- perilusDebug.regData.expect(toULong(expected))
- }
- }
- }
- 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))
- }
- }
- }
}