perilus

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

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:
Aassets/mem-lw-64x32.hex | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aassets/mem-zeros-8x32.hex | 8++++++++
Aassets/reg-lw-x32.hex | 32++++++++++++++++++++++++++++++++
Aassets/reg-zeros-x32.hex | 32++++++++++++++++++++++++++++++++
Mshell.nix | 1+
Msrc/main/scala/alu.scala | 2+-
Msrc/main/scala/controlUnit.scala | 18+++++++++---------
Msrc/main/scala/main.scala | 79++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Msrc/main/scala/memory.scala | 29++++++++++++++++++++++++-----
Msrc/main/scala/registerFile.scala | 25+++++++++++++++++++------
Asrc/test/scala/perilus/main.scala | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/test/scala/perilus/memory.scala | 57+++++++++++++++++++++++++++++----------------------------
Msrc/test/scala/perilus/registerFile.scala | 8++++++--
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)