commit 5323af8d64851749ec9a5d85694730a49adf8a9d
parent 9b22240b1911a643a2e893fde4be9f4bb85302c9
Author: Bradley Gannon <bradley@bradleygannon.com>
Date: Thu, 1 Jan 2026 14:06:36 -0500
Add memory/reg init from file, add optional debug interfaces, and execute first instruction
Diffstat:
13 files changed, 336 insertions(+), 74 deletions(-)
diff --git a/assets/mem-lw-64x32.hex b/assets/mem-lw-64x32.hex
@@ -0,0 +1,64 @@
+02a5aa83
+b88829b6
+5ee7809a
+947924f4
+fc24525a
+6c858974
+4996dd8c
+50415755
+b329e2c1
+99a84ca8
+ca029ae4
+a57954e9
+a7b0a4b1
+5f1c57ea
+bab5ee54
+e63aec6d
+7afaad60
+334c7d51
+bc83bea3
+63b83aab
+e1940fda
+4812b9fa
+7c224e0a
+305c08cc
+85e21635
+6e0cd50c
+d976069b
+f7f1ab0f
+24d8e524
+0f15069c
+1fa3eed3
+f73d90fa
+bd46aca5
+a519ad77
+70cd4ed3
+a722ba58
+cec41109
+9955f8a8
+ba5eb4f8
+cb32fd9d
+4f3fc791
+43bcbe26
+c1600d35
+0b5fc381
+dddfa0c3
+0331db73
+1e088fee
+9a0cdee4
+29580f62
+043925f7
+653647af
+d5e9eb13
+3e559887
+f098fa6f
+0f3f9400
+07163d81
+b37a3f8e
+0c11b791
+b3fa9fcf
+9d6ae141
+a34ef0d5
+3e850e1a
+2a410325
+8fce4057
diff --git a/assets/mem-zeros-8x32.hex b/assets/mem-zeros-8x32.hex
@@ -0,0 +1,8 @@
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
diff --git a/assets/reg-lw-x32.hex b/assets/reg-lw-x32.hex
@@ -0,0 +1,32 @@
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+0000000c
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
diff --git a/assets/reg-zeros-x32.hex b/assets/reg-zeros-x32.hex
@@ -0,0 +1,32 @@
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
+00000000
diff --git a/shell.nix b/shell.nix
@@ -6,6 +6,7 @@ in
pkgs.mkShellNoCC {
packages = with pkgs; [
act
+ coursier
jdk21
metals
sbt
diff --git a/src/main/scala/alu.scala b/src/main/scala/alu.scala
@@ -11,7 +11,7 @@ class Alu extends Module {
val zero = Output(Bool())
})
- io.aluResult := DontCare
+ io.aluResult := 0.U
switch(io.aluControl) {
is(AluControl.add) {
diff --git a/src/main/scala/controlUnit.scala b/src/main/scala/controlUnit.scala
@@ -37,13 +37,13 @@ class ControlUnit(withDebug: Boolean = false) extends Module {
val aluOp = WireDefault(AluOp.memory)
val branch, pcUpdate = WireDefault(false.B)
- io.adrSrc := DontCare
- io.aluSrcA := DontCare
- io.aluSrcB := DontCare
- io.irWrite := DontCare
- io.memWrite := DontCare
- io.regWrite := DontCare
- io.resultSrc := DontCare
+ io.adrSrc := false.B
+ io.aluSrcA := AluSrcA.pc
+ io.aluSrcB := AluSrcB.rd2
+ io.irWrite := false.B
+ io.memWrite := false.B
+ io.regWrite := false.B
+ io.resultSrc := ResultSrc.aluOutBuf
// Main FSM (Figure 7.45, page 436)
switch(state) {
@@ -159,7 +159,7 @@ class AluDecoder extends Module {
val aluControl = Output(AluControl())
})
- io.aluControl := DontCare
+ io.aluControl := AluControl.add
switch(io.aluOp) {
is(AluOp.memory) {
@@ -195,7 +195,7 @@ class InstructionDecoder extends Module {
val immSrc = Output(ImmSrc())
})
- io.immSrc := DontCare
+ io.immSrc := ImmSrc.iType
switch(io.op) {
is(Opcode.load) {
diff --git a/src/main/scala/main.scala b/src/main/scala/main.scala
@@ -10,6 +10,8 @@ package com.rinthyAi.perilus.main
import chisel3._
import chisel3.util._
import _root_.circt.stage.ChiselStage
+import scala.collection.mutable.ArrayBuffer
+import scala.io.Source
import com.rinthyAi.perilus.alu._
import com.rinthyAi.perilus.extendUnit._
@@ -17,17 +19,49 @@ import com.rinthyAi.perilus.memory._
import com.rinthyAi.perilus.registerFile._
import com.rinthyAi.perilus.controlUnit._
-class Perilus extends Module {
- val io = IO(new Bundle {})
-
+class Perilus(
+ initRegs: String = "",
+ initMem: String = "",
+ withDebug: Boolean = false
+) extends Module {
val width = 32.W
- val memorySizeWords = 1024
+ val memorySizeWords = if (initMem.nonEmpty) {
+ Source.fromFile(initMem).getLines().size
+ } else { 1024 }
+
+ val io = IO(new Bundle {
+ val memory = Module(new Memory(memorySizeWords, width, initMem, withDebug))
+ val registerFile = Module(new RegisterFile(width, initRegs, withDebug))
+ val debug =
+ if (withDebug) Some(new Bundle {
+ val reg = Input(UInt(5.W))
+ val regData = Output(UInt(width))
+ val memAddr = Input(UInt(width))
+ val memData = Output(UInt(width))
+ })
+ else None
+ })
+
+ io.registerFile.io.debug.foreach(r => {
+ r.reg := 0.U
+ })
+ io.memory.io.debug.foreach(m => {
+ m.memAddr := 0.U
+ })
+ io.debug.foreach(d => {
+ io.registerFile.io.debug.foreach(r => {
+ r.reg := d.reg
+ d.regData := r.regData
+ })
+ io.memory.io.debug.foreach(m => {
+ m.memAddr := d.memAddr
+ d.memData := m.memData
+ })
+ })
val alu = Module(new Alu())
val controlUnit = Module(new ControlUnit())
val extendUnit = Module(new ExtendUnit())
- val memory = Module(new Memory(memorySizeWords, width))
- val registerFile = Module(new RegisterFile(width))
val pc = RegInit(0.U(width))
val pcNext = WireDefault(UInt(width), 0.U)
@@ -42,16 +76,16 @@ class Perilus extends Module {
val instr = RegInit(0.U(width))
when(controlUnit.io.irWrite) {
- instr := memory.io.readData
+ instr := io.memory.io.readData
}
val readDataBuf = RegInit(0.U(width))
- readDataBuf := memory.io.readData
+ readDataBuf := io.memory.io.readData
val rd1Buf = RegInit(0.U(width))
- rd1Buf := registerFile.io.rd1
+ rd1Buf := io.registerFile.io.rd1
val rd2Buf = RegInit(0.U(width))
- rd2Buf := registerFile.io.rd2
+ rd2Buf := io.registerFile.io.rd2
val aluOutBuf = RegInit(0.U(width))
aluOutBuf := alu.io.aluResult
@@ -70,8 +104,8 @@ class Perilus extends Module {
}
alu.io.aluControl := controlUnit.io.aluControl
- alu.io.srcA := DontCare
- alu.io.srcB := DontCare
+ alu.io.srcA := 0.U
+ alu.io.srcB := 0.U
switch(controlUnit.io.aluSrcA) {
is(AluSrcA.pc) {
alu.io.srcA := pc
@@ -97,7 +131,6 @@ class Perilus extends Module {
controlUnit.io.zero := alu.io.zero
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)
@@ -106,18 +139,18 @@ class Perilus extends Module {
extendUnit.io.immSrc := controlUnit.io.immSrc
when(controlUnit.io.adrSrc) {
- memory.io.address := result
+ io.memory.io.address := result
}.otherwise {
- memory.io.address := pc
+ io.memory.io.address := pc
}
- memory.io.writeData := rd2Buf
- memory.io.writeEnable := controlUnit.io.memWrite
-
- registerFile.io.a1 := instr(19, 15)
- registerFile.io.a2 := instr(24, 20)
- registerFile.io.a3 := instr(11, 7)
- registerFile.io.writeData3 := result
- registerFile.io.writeEnable3 := controlUnit.io.regWrite
+ io.memory.io.writeData := rd2Buf
+ io.memory.io.writeEnable := controlUnit.io.memWrite
+
+ io.registerFile.io.a1 := instr(19, 15)
+ io.registerFile.io.a2 := instr(24, 20)
+ io.registerFile.io.a3 := instr(11, 7)
+ io.registerFile.io.writeData3 := result
+ io.registerFile.io.writeEnable3 := controlUnit.io.regWrite
}
object Perilus extends App {
diff --git a/src/main/scala/memory.scala b/src/main/scala/memory.scala
@@ -2,26 +2,45 @@ package com.rinthyAi.perilus.memory
import chisel3._
import chisel3.util.log2Ceil
+import chisel3.util.experimental.loadMemoryFromFile
-class Memory(numWords: Int, width: Width) extends Module {
+class Memory(
+ numWords: Int,
+ width: Width,
+ initMem: String = "",
+ withDebug: Boolean = false
+) extends Module {
val io = IO(new Bundle {
val address = Input(UInt(width))
val writeData = Input(UInt(width))
val writeEnable = Input(Bool())
-
val readData = Output(UInt(width))
+ val debug =
+ if (withDebug) Some(new Bundle {
+ val memAddr = Input(UInt(width))
+ val memData = Output(UInt(width))
+ })
+ else None
})
- val memory = RegInit(VecInit(Seq.fill(numWords)(0.U(width))))
+ val memory = Mem(numWords, UInt(width))
+ if (initMem.nonEmpty) {
+ loadMemoryFromFile(memory, initMem)
+ }
// this fixes warnings related to width being too high or low to index memory
// see https://www.chisel-lang.org/docs/cookbooks/cookbook#how-do-i-resolve-dynamic-index--is-too-widenarrow-for-extractee-
val correctWidth = log2Ceil(numWords)
val correctWidthAddress = io.address.pad(correctWidth)(correctWidth - 1, 0)
+ io.debug.foreach(d => {
+ val correctWidthDebugAddress = d.memAddr.pad(correctWidth)(correctWidth - 1, 0)
+ d.memData := memory.read(correctWidthDebugAddress)
+ })
+
when(io.writeEnable) {
- memory(correctWidthAddress) := io.writeData
+ memory.write(correctWidthAddress, io.writeData)
}
- io.readData := memory(correctWidthAddress)
+ io.readData := memory.read(correctWidthAddress)
}
diff --git a/src/main/scala/registerFile.scala b/src/main/scala/registerFile.scala
@@ -1,22 +1,35 @@
package com.rinthyAi.perilus.registerFile
import chisel3._
+import chisel3.util.experimental.loadMemoryFromFile
-class RegisterFile(width: Width) extends Module {
+class RegisterFile(width: Width, initRegs: String = "", withDebug: Boolean = false) extends Module {
val io = IO(new Bundle {
val a1, a2, a3 = Input(UInt(5.W))
val writeData3 = Input(UInt(width))
val writeEnable3 = Input(Bool())
-
val rd1, rd2 = Output(UInt(width))
+ val debug =
+ if (withDebug) Some(new Bundle {
+ val reg = Input(UInt(5.W))
+ val regData = Output(UInt(width))
+ })
+ else None
})
- val registerFile = RegInit(VecInit(Seq.fill(32)(0.U(width))))
+ val registerFile = Mem(32, UInt(width))
+ if (initRegs.nonEmpty) {
+ loadMemoryFromFile(registerFile, initRegs)
+ }
+
+ io.debug.foreach(d => {
+ d.regData := registerFile.read(d.reg)
+ })
- io.rd1 := registerFile(io.a1)
- io.rd2 := registerFile(io.a2)
+ io.rd1 := registerFile.read(io.a1)
+ io.rd2 := registerFile.read(io.a2)
when(io.writeEnable3 && io.a3 =/= 0.U) {
- registerFile(io.a3) := io.writeData3
+ registerFile.write(io.a3, io.writeData3)
}
}
diff --git a/src/test/scala/perilus/main.scala b/src/test/scala/perilus/main.scala
@@ -0,0 +1,55 @@
+import chisel3._
+import chisel3.simulator.scalatest.ChiselSim
+import org.scalatest.funspec.AnyFunSpec
+import scala.collection.mutable.ArrayBuffer
+
+import com.rinthyAi.perilus.main._
+
+class PerilusTests extends AnyFunSpec with ChiselSim {
+ describe("Perilus") {
+ it("executes lw") {
+ simulate(
+ new Perilus(
+ initRegs = "/home/bradley/Projects/perilus/assets/reg-lw-x32.hex",
+ initMem = "/home/bradley/Projects/perilus/assets/mem-lw-64x32.hex",
+ withDebug = true
+ )
+ ) { perilus =>
+ {
+ val perilusDebug = perilus.io.debug.get
+
+ // lw x21, 0x2a(x11)
+ val rd = 21
+ val rs1 = 11
+ val base = 0xc
+ val imm = 0x2a
+ val memAddr = (base + imm).U
+ val memData = 0x0f3f9400
+
+ // rs1 contains the base pointer
+ perilusDebug.reg.poke(rs1)
+ perilusDebug.regData.expect(base)
+
+ // rd is empty
+ perilusDebug.reg.poke(rd)
+ perilusDebug.regData.expect(0.U)
+
+ // memAddr contains memData
+ perilusDebug.memAddr.poke(memAddr)
+ perilusDebug.memData.expect(memData)
+
+ // execute the instruction
+ perilus.clock.step(6)
+
+ // 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)
+ }
+ }
+ }
+ }
+}
diff --git a/src/test/scala/perilus/memory.scala b/src/test/scala/perilus/memory.scala
@@ -7,37 +7,38 @@ import com.rinthyAi.perilus.memory._
class MemoryTests extends AnyFunSpec with ChiselSim {
describe("Memory") {
it("stores and retrieves data") {
- simulate(new Memory(1024, 32.W)) { memory =>
- {
- val addr1 = "h12e".U
- val addr2 = "h0d7".U
- val data1 = "he7d857de".U
- val data2 = "h38cc122f".U
+ simulate(new Memory(8, 32.W, System.getProperty("user.dir") + "/assets/mem-zeros-8x32.hex")) {
+ memory =>
+ {
+ val addr1 = "h12e".U
+ val addr2 = "h0d7".U
+ val data1 = "he7d857de".U
+ val data2 = "h38cc122f".U
- memory.io.address.poke(addr1)
- memory.io.writeData.poke(data1)
- memory.io.writeEnable.poke(true.B)
- memory.io.readData.expect(0.U)
- memory.clock.step(1)
- memory.io.readData.expect(data1)
+ memory.io.address.poke(addr1)
+ memory.io.writeData.poke(data1)
+ memory.io.writeEnable.poke(true.B)
+ memory.io.readData.expect(0.U)
+ memory.clock.step(1)
+ memory.io.readData.expect(data1)
- memory.io.address.poke(addr2)
- memory.io.writeData.poke(data2)
- memory.io.writeEnable.poke(true.B)
- memory.io.readData.expect(0.U)
- memory.clock.step(1)
- memory.io.readData.expect(data2)
+ memory.io.address.poke(addr2)
+ memory.io.writeData.poke(data2)
+ memory.io.writeEnable.poke(true.B)
+ memory.io.readData.expect(0.U)
+ memory.clock.step(1)
+ memory.io.readData.expect(data2)
- memory.io.writeData.poke(0.U)
- memory.io.writeEnable.poke(false.B)
- memory.clock.step(1)
- memory.io.address.poke(addr1)
- memory.io.readData.expect(data1)
- memory.io.address.poke(addr2)
- memory.io.readData.expect(data2)
- memory.io.address.poke(0.U)
- memory.io.readData.expect(0.U)
- }
+ memory.io.writeData.poke(0.U)
+ memory.io.writeEnable.poke(false.B)
+ memory.clock.step(1)
+ memory.io.address.poke(addr1)
+ memory.io.readData.expect(data1)
+ memory.io.address.poke(addr2)
+ memory.io.readData.expect(data2)
+ memory.io.address.poke(0.U)
+ memory.io.readData.expect(0.U)
+ }
}
}
}
diff --git a/src/test/scala/perilus/registerFile.scala b/src/test/scala/perilus/registerFile.scala
@@ -7,7 +7,9 @@ import com.rinthyAi.perilus.registerFile._
class RegisterFileTests extends AnyFunSpec with ChiselSim {
describe("RegisterFile") {
it("stores and retrieves data") {
- simulate(new RegisterFile(32.W)) { registerFile =>
+ simulate(
+ new RegisterFile(32.W, System.getProperty("user.dir") + "/assets/reg-zeros-x32.hex")
+ ) { registerFile =>
{
val reg1 = 5.U
val reg2 = 19.U
@@ -44,7 +46,9 @@ class RegisterFileTests extends AnyFunSpec with ChiselSim {
}
}
it("doesn't write to x0") {
- simulate(new RegisterFile(32.W)) { registerFile =>
+ simulate(
+ new RegisterFile(32.W, System.getProperty("user.dir") + "/assets/reg-zeros-x32.hex")
+ ) { registerFile =>
{
registerFile.io.a3.poke(0.U)
registerFile.io.writeData3.poke("h500bc902".U)