perilus

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

commit f736439ba08a3a78179e0c41c99482d4edc2b579
parent ad41caa65641049219432f45c0dca84337261018
Author: Bradley Gannon <bradley@bradleygannon.com>
Date:   Mon, 29 Dec 2025 22:12:09 -0500

Add control unit FSM, add several enums, add J-type sign extension, bump nixpkgs

Diffstat:
Mshell.nix | 2+-
Msrc/main/scala/alu.scala | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Msrc/main/scala/controlUnit.scala | 203+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Msrc/main/scala/extendUnit.scala | 56++++++++++++++++++++++++++++++++++++--------------------
Msrc/main/scala/main.scala | 60+++++++++++++++++++++++++++++++++++++-----------------------
Msrc/test/scala/perilus/alu.scala | 33+++++----------------------------
Msrc/test/scala/perilus/extendUnit.scala | 18+++++++++---------
7 files changed, 327 insertions(+), 114 deletions(-)

diff --git a/shell.nix b/shell.nix @@ -1,5 +1,5 @@ let - nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-25.05"; + nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-25.11"; pkgs = import nixpkgs { config = {}; overlays = []; }; in diff --git a/src/main/scala/alu.scala b/src/main/scala/alu.scala @@ -1,26 +1,67 @@ package com.rinthyAi.perilus.alu import chisel3._ +import chisel3.util._ class Alu extends Module { val io = IO(new Bundle { - val aluControl = Input(UInt(3.W)) + val aluControl = Input(AluControl()) val srcA, srcB = Input(UInt(32.W)) - val aluResult = Output(UInt(32.W)) + val zero = Output(Bool()) }) - when(io.aluControl === "b000".U) { - io.aluResult := io.srcA + io.srcB - }.elsewhen(io.aluControl === "b001".U) { - io.aluResult := io.srcA - io.srcB - }.elsewhen(io.aluControl === "b010".U) { - io.aluResult := io.srcA & io.srcB - }.elsewhen(io.aluControl === "b011".U) { - io.aluResult := io.srcA | io.srcB - }.elsewhen(io.aluControl === "b101".U) { - io.aluResult := io.srcA < io.srcB - }.otherwise { - io.aluResult := 0.U + io.aluResult := DontCare + + switch(io.aluControl) { + is(AluControl.add) { + io.aluResult := io.srcA + io.srcB + } + is(AluControl.sub) { + io.aluResult := io.srcA - io.srcB + } + is(AluControl.and) { + io.aluResult := io.srcA & io.srcB + } + is(AluControl.or) { + io.aluResult := io.srcA | io.srcB + } + is(AluControl.slt) { + io.aluResult := io.srcA < io.srcB + } } + + io.zero := io.aluResult === 0.U +} + +object AluControl extends ChiselEnum { + val add = Value("b000".U) + val sub = Value("b001".U) + val and = Value("b010".U) + val or = Value("b011".U) + val slt = Value("b101".U) +} + +object AluSrcA extends ChiselEnum { + val pc = Value("b00".U) + val oldPc = Value("b01".U) + val rd1 = Value("b10".U) +} + +object AluSrcB extends ChiselEnum { + val rd2 = Value("b00".U) + val immExt = Value("b01".U) + val four = Value("b10".U) +} + +object AluOp extends ChiselEnum { + val memory = Value("b00".U) + val branch = Value("b01".U) + val arithmetic = Value("b10".U) +} + +object ResultSrc extends ChiselEnum { + val aluOutBuf = Value("b00".U) + val readDataBuf = Value("b01".U) + val aluResult = Value("b10".U) } diff --git a/src/main/scala/controlUnit.scala b/src/main/scala/controlUnit.scala @@ -1,30 +1,195 @@ package com.rinthyAi.perilus.controlUnit import chisel3._ +import chisel3.util._ + +import com.rinthyAi.perilus.alu._ +import com.rinthyAi.perilus.extendUnit._ -// Figure 7.28 (page 423) class ControlUnit extends Module { + // Figure 7.28 (page 423) val io = IO(new Bundle { - val op = Input(UInt(7.W)) + val op = Input(Opcode()) val funct3 = Input(UInt(3.W)) val funct7_5 = Input(Bool()) - - val adrSrc, branch, irWrite, memWrite, pcUpdate, pcWrite, regWrite = Output(Bool()) - val aluSrcA, aluSrcB, aluOp, immSrc, resultSrc = Output(UInt(2.W)) - val aluControl = Output(UInt(3.W)) + val zero = Input(Bool()) + val adrSrc, irWrite, memWrite, pcWrite, regWrite = Output(Bool()) + val aluSrcA = Output(AluSrcA()) + val aluSrcB = Output(AluSrcB()) + val resultSrc = Output(ResultSrc()) + val immSrc = Output(ImmSrc()) + val aluControl = Output(AluControl()) }) - io.adrSrc := 0.U - io.aluControl := 0.U - io.aluOp := 0.U - io.aluSrcA := 0.U - io.aluSrcB := 0.U - io.branch := 0.U - io.immSrc := 0.U - io.irWrite := 0.U - io.memWrite := 0.U - io.pcUpdate := 0.U - io.pcWrite := 0.U - io.regWrite := 0.U - io.resultSrc := 0.U + val state = RegInit(ControlUnitState.fetch) + + val aluOp = WireDefault(AluOp.memory) + val branch, pcUpdate = WireDefault(false.B) + + io.adrSrc := DontCare + io.aluControl := DontCare + io.aluSrcA := DontCare + io.aluSrcB := DontCare + io.immSrc := DontCare + io.irWrite := DontCare + io.memWrite := DontCare + io.regWrite := DontCare + io.resultSrc := DontCare + + // Main FSM (Figure 7.45, page 436) + switch(state) { + is(ControlUnitState.fetch) { + io.adrSrc := false.B + io.irWrite := true.B + io.aluSrcA := AluSrcA.pc + io.aluSrcB := AluSrcB.four + aluOp := AluOp.memory + io.resultSrc := ResultSrc.aluResult + pcUpdate := true.B + state := ControlUnitState.decode + } + is(ControlUnitState.decode) { + io.aluSrcA := AluSrcA.oldPc + io.aluSrcB := AluSrcB.immExt + aluOp := AluOp.memory + when(io.op === Opcode.load || io.op === Opcode.store) { + state := ControlUnitState.memAdr + }.elsewhen(io.op === Opcode.rType) { + state := ControlUnitState.executeR + }.elsewhen(io.op === Opcode.immediate) { + state := ControlUnitState.executeI + }.elsewhen(io.op === Opcode.jal) { + state := ControlUnitState.jal + }.elsewhen(io.op === Opcode.branch) { + state := ControlUnitState.beq + } + } + is(ControlUnitState.memAdr) { + io.aluSrcA := AluSrcA.rd1 + io.aluSrcB := AluSrcB.immExt + aluOp := AluOp.memory + switch(io.op) { + is(Opcode.load) { + state := ControlUnitState.memRead + } + is(Opcode.store) { + state := ControlUnitState.memWrite + } + } + } + is(ControlUnitState.memRead) { + io.resultSrc := ResultSrc.aluOutBuf + io.adrSrc := true.B + state := ControlUnitState.memWb + } + is(ControlUnitState.memWb) { + io.resultSrc := ResultSrc.readDataBuf + io.regWrite := true.B + state := ControlUnitState.fetch + } + is(ControlUnitState.memWrite) { + io.resultSrc := ResultSrc.aluOutBuf + io.adrSrc := true.B + io.memWrite := true.B + state := ControlUnitState.fetch + } + is(ControlUnitState.executeR) { + io.aluSrcA := AluSrcA.rd1 + io.aluSrcB := AluSrcB.rd2 + aluOp := AluOp.arithmetic + state := ControlUnitState.aluWb + } + is(ControlUnitState.aluWb) { + io.resultSrc := ResultSrc.aluOutBuf + io.regWrite := true.B + state := ControlUnitState.fetch + } + is(ControlUnitState.executeI) { + io.aluSrcA := AluSrcA.rd1 + io.aluSrcB := AluSrcB.immExt + aluOp := AluOp.arithmetic + state := ControlUnitState.aluWb + } + is(ControlUnitState.jal) { + io.aluSrcA := AluSrcA.oldPc + io.aluSrcB := AluSrcB.four + io.resultSrc := ResultSrc.aluOutBuf + aluOp := AluOp.memory + pcUpdate := true.B + state := ControlUnitState.aluWb + } + is(ControlUnitState.beq) { + io.aluSrcA := AluSrcA.rd1 + io.aluSrcB := AluSrcB.rd2 + io.resultSrc := ResultSrc.aluOutBuf + aluOp := AluOp.branch + branch := true.B + state := ControlUnitState.fetch + } + } + + io.pcWrite := (io.zero && branch) || pcUpdate + + // ALU Decoder (Table 7.3, page 409) + switch(aluOp) { + is(AluOp.memory) { + io.aluControl := AluControl.add + } + is(AluOp.branch) { + io.aluControl := AluControl.sub + } + is(AluOp.arithmetic) { + when( + io.funct3 === "b000".U && ((!io.op.asUInt(5) && !io.funct7_5) || (!io.op.asUInt( + 5 + ) && io.funct7_5) || (io.op.asUInt(5) && !io.funct7_5)) + ) { + io.aluControl := AluControl.add + }.elsewhen(io.funct3 === "b000".U && io.op.asUInt(5) && io.funct7_5) { + io.aluControl := AluControl.sub + }.elsewhen(io.funct3 === "b010".U) { + io.aluControl := AluControl.slt + }.elsewhen(io.funct3 === "b110".U) { + io.aluControl := AluControl.or + }.elsewhen(io.funct3 === "b111".U) { + io.aluControl := AluControl.and + } + } + } + + // Instruction Decoder (Table 7.6, page 413) + switch(io.op) { + is(Opcode.load) { + io.immSrc := ImmSrc.iType + } + is(Opcode.store) { + io.immSrc := ImmSrc.sType + } + is(Opcode.branch) { + io.immSrc := ImmSrc.bType + } + is(Opcode.immediate) { + io.immSrc := ImmSrc.iType + } + is(Opcode.jal) { + io.immSrc := ImmSrc.jType + } + } +} + +object ControlUnitState extends ChiselEnum { + val fetch, decode, memAdr, memRead, memWb, memWrite, executeR, aluWb, executeI, jal, beq = Value +} + +object Opcode extends ChiselEnum { + val load = Value("b0000011".U) + val immediate = Value("b0010011".U) + val auipc = Value("b0010111".U) + val store = Value("b0100011".U) + val rType = Value("b0110011".U) + val lui = Value("b0110111".U) + val branch = Value("b1100011".U) + val jalr = Value("b1100111".U) + val jal = Value("b1101111".U) + val env = Value("b1110011".U) } diff --git a/src/main/scala/extendUnit.scala b/src/main/scala/extendUnit.scala @@ -6,28 +6,44 @@ import chisel3.util._ class ExtendUnit extends Module { val io = IO(new Bundle { val input = Input(UInt(25.W)) - val immSrc = Input(UInt(2.W)) - + val immSrc = Input(ImmSrc()) val immExt = Output(UInt(32.W)) }) - // Table 7.1 (page 406) - when(io.immSrc === "b00".U) { - // I-type instruction - io.immExt := Cat(Fill(20, io.input(24)), io.input(24, 13)) - }.elsewhen(io.immSrc === "b01".U) { - // S-type instruction - io.immExt := Cat(Fill(20, io.input(24)), io.input(24, 18), io.input(4, 0)) - }.elsewhen(io.immSrc === "b10".U) { - // B-type instruction - io.immExt := Cat( - Fill(20, io.input(24)), - io.input(0), - io.input(23, 18), - io.input(4, 1), - 0.U(1.W) - ) - }.otherwise { - io.immExt := 0.U + io.immExt := 0.U + + // Table 7.5 (page 412) + switch(io.immSrc) { + is(ImmSrc.iType) { + io.immExt := Cat(Fill(20, io.input(24)), io.input(24, 13)) + } + is(ImmSrc.sType) { + io.immExt := Cat(Fill(20, io.input(24)), io.input(24, 18), io.input(4, 0)) + } + is(ImmSrc.bType) { + io.immExt := Cat( + Fill(20, io.input(24)), + io.input(0), + io.input(23, 18), + io.input(4, 1), + 0.U(1.W) + ) + } + is(ImmSrc.jType) { + io.immExt := Cat( + Fill(12, io.input(24)), + io.input(12, 5), + io.input(13), + io.input(23, 14), + 0.U(1.W) + ) + } } } + +object ImmSrc extends ChiselEnum { + val iType = Value("b00".U) + val sType = Value("b01".U) + val bType = Value("b10".U) + val jType = Value("b11".U) +} diff --git a/src/main/scala/main.scala b/src/main/scala/main.scala @@ -8,6 +8,7 @@ package com.rinthyAi.perilus.main import chisel3._ +import chisel3.util._ import _root_.circt.stage.ChiselStage import com.rinthyAi.perilus.alu._ @@ -56,35 +57,48 @@ class Perilus extends Module { aluOutBuf := alu.io.aluResult val result = WireDefault(0.U(width)) - when(controlUnit.io.resultSrc === "b00".U) { - result := aluOutBuf - }.elsewhen(controlUnit.io.resultSrc === "b01".U) { - result := readDataBuf - }.elsewhen(controlUnit.io.resultSrc === "b10".U) { - result := alu.io.aluResult + switch(controlUnit.io.resultSrc) { + is(ResultSrc.aluOutBuf) { + result := aluOutBuf + } + is(ResultSrc.readDataBuf) { + result := readDataBuf + } + is(ResultSrc.aluResult) { + result := alu.io.aluResult + } } alu.io.aluControl := controlUnit.io.aluControl - when(controlUnit.io.aluSrcA === "b00".U) { - alu.io.srcA := pc - }.elsewhen(controlUnit.io.aluSrcA === "b01".U) { - alu.io.srcA := oldPc - }.elsewhen(controlUnit.io.aluSrcA === "b10".U) { - alu.io.srcA := rd1Buf - }.otherwise { - alu.io.srcA := 0.U + alu.io.srcA := DontCare + alu.io.srcB := DontCare + switch(controlUnit.io.aluSrcA) { + is(AluSrcA.pc) { + alu.io.srcA := pc + } + is(AluSrcA.oldPc) { + alu.io.srcA := oldPc + } + is(AluSrcA.rd1) { + alu.io.srcA := rd1Buf + } } - when(controlUnit.io.aluSrcB === "b00".U) { - alu.io.srcB := rd2Buf - }.elsewhen(controlUnit.io.aluSrcB === "b01".U) { - alu.io.srcB := extendUnit.io.immExt - }.elsewhen(controlUnit.io.aluSrcB === "b10".U) { - alu.io.srcB := 4.U - }.otherwise { - alu.io.srcB := 0.U + switch(controlUnit.io.aluSrcB) { + is(AluSrcB.rd2) { + alu.io.srcB := rd2Buf + } + is(AluSrcB.immExt) { + alu.io.srcB := extendUnit.io.immExt + } + is(AluSrcB.four) { + alu.io.srcB := 4.U + } } + controlUnit.io.zero := alu.io.zero - controlUnit.io.op := instr(6, 0) + val (opcode, opcodeValid) = Opcode.safe(instr(6, 0)) + assert(opcodeValid, "Got invalid opcode: 0b%b", instr(6, 0)) + controlUnit.io.op := opcode controlUnit.io.funct3 := instr(14, 12) controlUnit.io.funct7_5 := instr(30) diff --git a/src/test/scala/perilus/alu.scala b/src/test/scala/perilus/alu.scala @@ -9,7 +9,7 @@ class AluTests extends AnyFunSpec with ChiselSim { it("adds") { simulate(new Alu) { alu => { - alu.io.aluControl.poke("b000".U) + alu.io.aluControl.poke(AluControl.add) alu.io.srcA.poke("hb589e285".U) alu.io.srcB.poke("hc4289503".U) @@ -28,7 +28,7 @@ class AluTests extends AnyFunSpec with ChiselSim { it("subtracts") { simulate(new Alu) { alu => { - alu.io.aluControl.poke("b001".U) + alu.io.aluControl.poke(AluControl.sub) alu.io.srcA.poke("hceab249".U) alu.io.srcB.poke("h124a55c3".U) @@ -47,7 +47,7 @@ class AluTests extends AnyFunSpec with ChiselSim { it("computes bitwise AND") { simulate(new Alu) { alu => { - alu.io.aluControl.poke("b010".U) + alu.io.aluControl.poke(AluControl.and) alu.io.srcA.poke("h9ae8ddd".U) alu.io.srcB.poke("ha8387b0c".U) @@ -66,7 +66,7 @@ class AluTests extends AnyFunSpec with ChiselSim { it("computes bitwise OR") { simulate(new Alu) { alu => { - alu.io.aluControl.poke("b011".U) + alu.io.aluControl.poke(AluControl.or) alu.io.srcA.poke("hf4f16d52".U) alu.io.srcB.poke("h2f6464a1".U) @@ -85,7 +85,7 @@ class AluTests extends AnyFunSpec with ChiselSim { it("computes set if less than (SLT)") { simulate(new Alu) { alu => { - alu.io.aluControl.poke("b101".U) + alu.io.aluControl.poke(AluControl.slt) alu.io.srcA.poke("h8a831306".U) alu.io.srcB.poke("h6e7ec46f".U) @@ -101,28 +101,5 @@ class AluTests extends AnyFunSpec with ChiselSim { } } } - it("outputs zero when aluControl is invalid") { - simulate(new Alu) { alu => - { - alu.io.aluControl.poke("b100".U) - - alu.io.srcA.poke("hbe4fb61f".U) - alu.io.srcB.poke("h1ea6db5b".U) - alu.io.aluResult.expect(0.U) - - alu.io.aluControl.poke("b110".U) - - alu.io.srcA.poke("h9ba3e41f".U) - alu.io.srcB.poke("h33abf507".U) - alu.io.aluResult.expect(0.U) - - alu.io.aluControl.poke("b111".U) - - alu.io.srcA.poke("h6cd9a65b".U) - alu.io.srcB.poke("h104e5235".U) - alu.io.aluResult.expect(0.U) - } - } - } } } diff --git a/src/test/scala/perilus/extendUnit.scala b/src/test/scala/perilus/extendUnit.scala @@ -9,7 +9,7 @@ class ExtendUnitTests extends AnyFunSpec with ChiselSim { it("correctly extends I-type immediates") { simulate(new ExtendUnit) { extendUnit => { - extendUnit.io.immSrc.poke("b00".U) + extendUnit.io.immSrc.poke(ImmSrc.iType) extendUnit.io.input.poke("hdc4691".U) // immediate value is 0xe9b extendUnit.io.immExt.expect("h6e2".U) @@ -22,7 +22,7 @@ class ExtendUnitTests extends AnyFunSpec with ChiselSim { it("correctly extends S-type immediates") { simulate(new ExtendUnit) { extendUnit => { - extendUnit.io.immSrc.poke("b01".U) + extendUnit.io.immSrc.poke(ImmSrc.sType) extendUnit.io.input.poke("h65b22a".U) // immediate value is 0x32a extendUnit.io.immExt.expect("h32a".U) @@ -35,7 +35,7 @@ class ExtendUnitTests extends AnyFunSpec with ChiselSim { it("correctly extends B-type immediates") { simulate(new ExtendUnit) { extendUnit => { - extendUnit.io.immSrc.poke("b10".U) + extendUnit.io.immSrc.poke(ImmSrc.bType) extendUnit.io.input.poke("hcbee9".U) // immediate value is 0x868 extendUnit.io.immExt.expect("h868".U) @@ -45,16 +45,16 @@ class ExtendUnitTests extends AnyFunSpec with ChiselSim { } } } - it("outputs zero when immSrc is invalid") { + it("correctly extends J-type immediates") { simulate(new ExtendUnit) { extendUnit => { - extendUnit.io.immSrc.poke("b11".U) + extendUnit.io.immSrc.poke(ImmSrc.jType) - extendUnit.io.input.poke("hb2a945".U) - extendUnit.io.immExt.expect(0.U) + extendUnit.io.input.poke("h8cfe3".U) // immediate value is 0x7f046 + extendUnit.io.immExt.expect("h7f046".U) - extendUnit.io.input.poke("h1f800d2".U) - extendUnit.io.immExt.expect(0.U) + extendUnit.io.input.poke("h11c2c1c".U) // immediate value is 0x1608e0 + extendUnit.io.immExt.expect("hfff608e0".U) } } }