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:
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)
}
}
}