perilus

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

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:
Msrc/main/scala/alu.scala | 13++++++++++---
Msrc/main/scala/controlUnit.scala | 16+++++++++++++---
Msrc/main/scala/extendUnit.scala | 7+++++--
Msrc/test/scala/perilus/controlUnit.scala | 6++++--
Msrc/test/scala/perilus/extendUnit.scala | 3++-
Msrc/test/scala/perilus/main.scala | 284++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
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)) - } - } - } }