// See README.md for license details.

package ethcontroller.design

import Chisel.{Module, Tester, chiselMainTest}
import ethcontroller.protocols.{EthernetFrame, EthernetTesting}
import ocp.OcpCmd

import scala.language.postfixOps
import scala.sys.process._

class EthTxControllerTester(dut: EthTxController, frame: EthernetFrame, injectNoFrames: Int) extends Tester(dut) {

  val MII_TICKS_TO_CLKS: Int = 2

  var miiClkValue: Int = 0
  var numberOfSysSteps: Int = 0

  def testSingleFrameTransmission(frame: EthernetFrame, initTime: Long): Unit = {
    initPHY2MII(4)
    println("Testing OCP write to buffer")
    ocpLoadFrameToBuffer()
    println("Testing EthTx transmission")
    println("Testing preamble")
    //    for (nibble <- frame.preambleNibbles) {
    //      stepWithMIIClock(6)
    //      peek(dut.miiTx.serializeByteToNibble.doneReg)
    //      peek(dut.miiTx.serializeByteToNibble.shiftReg)
    //      peekEthTxStatus()
    //    }
    //    println("Testing MAC")
    //    for (nibble <- frame.dstMacNibbles ++ frame.srcMacNibbles ++ frame.ethTypeNibbles) {
    //      stepWithMIIClock(6)
    //      peekEthTxStatus()
    //    }
    //    println("Testing Raw Ethernet frame II payload")
    //    for (payloadData <- 0 until 45) {
    //      for (nibble <- frame.dataNibbles) {
    //        stepWithMIIClock(6)
    //        peekEthTxStatus()
    //      }
    //    }
    println("Testing EoF")
    stepWithMIIClock()
  }

  def initPHY2MII(steps: Int): Unit = {
    poke(dut.io.miiChannel.col, 0)
    poke(dut.io.miiChannel.crs, 0)
    poke(dut.io.miiChannel.clk, 0)
    step(steps)
  }

  def ocpLoadFrameToBuffer(): Unit = {
    val totalFrame = frame.dstMac ++ frame.srcMac ++ frame.ethType ++ frame.data
    var ocpAddr = 0x0
    var byteCnt = 0x1
    for (byte <- totalFrame) {
      poke(dut.io.ocp.M.Addr, ocpAddr)
      poke(dut.io.ocp.M.ByteEn, byteCnt)
      poke(dut.io.ocp.M.Data, byte)
      poke(dut.io.ocp.M.Cmd, OcpCmd.WR.litValue())
      stepWithMIIClock()

    poke(dut.io.ocp.M.Cmd, OcpCmd.IDLE.litValue())
    stepWithMIIClock()
    poke(dut.io.ocp.M.Addr, 1 << dut.constRamAddrWidth | 0x18)
    poke(dut.io.ocp.M.ByteEn, 0xF)
    poke(dut.io.ocp.M.Data, 0x1)
    poke(dut.io.ocp.M.Cmd, OcpCmd.WR.litValue())
    stepWithMIIClock()
    poke(dut.io.ocp.M.Cmd, OcpCmd.IDLE.litValue())
    stepWithMIIClock()
    expect(dut.fifoPushReg, true)
  }

  def peekEthTxStatus(): Unit = {
    peek(dut.stateReg)
    peek(dut.byteCntReg)
    peek(dut.rdRamIdReg)
    peek(dut.wrRamIdReg)
    peek(dut.fifoCountReg)
    peek(dut.fifoEmptyReg)
    peek(dut.fifoFullReg)
    peek(dut.txFrameSizeReg)
    peek(dut.miiTx.transmittingReg)
  }

  def stepWithMIIClock(): Unit = {
    poke(dut.io.miiChannel.clk, miiClkValue)
    step(1)
    if (numberOfSysSteps > MII_TICKS_TO_CLKS) {
      miiClkValue = 1 - miiClkValue
      numberOfSysSteps = 0
    } else {
      numberOfSysSteps += 1
    }
  }

  /**
    * Testing EthernetRxController
    */
  println("Test Starting...")
  var time: Long = 0x53C38A1000000000L
  for (i <- 0 until injectNoFrames) {
    println("...")
    println("TEST FRAME ITERATION #" + i + " at t = " + time.toHexString)
    testSingleFrameTransmission(frame, time)
    println("END TEST FRAME ITERATION #" + i + " at t = " + time.toHexString)
    stepWithMIIClock()
    stepWithMIIClock()
    println("...")
    println("...")
  }

}

object EthTxControllerTester extends App {
  private val pathToVCD = "generated/" + this.getClass.getSimpleName.dropRight(1)
  private val nameOfVCD = this.getClass.getSimpleName.dropRight(7) + ".vcd"

  chiselMainTest(Array("--genHarness", "--test", "--backend", "c",
    "--compile", "--vcd", "--targetDir", pathToVCD),
    () => Module(new EthTxController(4, 64))) {
    dut => new EthTxControllerTester(dut, EthernetTesting.mockupTTEPCFFrame, 5)
  }

  var waveHandle = "gtkwave " + pathToVCD + "/" + nameOfVCD + " " + pathToVCD + "/" + "view.sav" !

  println("gtkwave running as PID:" + waveHandle)
}

      if (byteCnt == 8) {
        byteCnt = 1
        ocpAddr += 0x1
      } else {


// See README.md for license details.

package ethcontroller.design

import Chisel.{Module, Tester, chiselMainTest}
import ethcontroller.protocols.{EthernetFrame, EthernetTesting}

import scala.language.postfixOps
import scala.sys.process._

class MIIRxTester(dut: MIIRx, frame: EthernetFrame) extends Tester(dut) {
  // Shifting of discrete numbers for clarity
  poke(dut.io.miiChannel.col, 0)
  poke(dut.io.miiChannel.crs, 0)
  poke(dut.io.rxEn, true)
  poke(dut.io.miiChannel.dv, 1)
  poke(dut.io.miiChannel.clk, 0)
  step(1)
  println("(Testing preamble)")
  for (nibble <- frame.preambleNibbles) {
    poke(dut.io.miiChannel.data, nibble)
    poke(dut.io.miiChannel.clk, 0)
    step(3)
    peek(dut.deserializePHYByte.countReg)
    peek(dut.deserializePHYByte.doneReg)
    peek(dut.deserializePHYByte.shiftReg)
    peek(dut.regBuffer)
    poke(dut.io.miiChannel.clk, 1)
    step(3)
  }
  poke(dut.io.miiChannel.clk, 0)
  step(3)
  poke(dut.io.miiChannel.clk, 1)
  expect(dut.io.ethSof, true, "--checking for start-of-frame detected")
  expect(dut.regBuffer, BigInt(frame.preamble), "--checking for preamble registered")
  step(3)
  println("Testing EoF")
  poke(dut.io.miiChannel.clk, 0)

  poke(dut.io.miiChannel.clk, 1)
  step(3)
  poke(dut.io.miiChannel.clk, 0)
  step(3)
}

object MIIRxTester extends App {
  private val pathToVCD = "generated/" + this.getClass.getSimpleName.dropRight(1)
  private val nameOfVCD = this.getClass.getSimpleName.dropRight(7) + ".vcd"

  try {
    chiselMainTest(Array("--genHarness", "--test", "--backend", "c",
      "--compile", "--vcd", "--targetDir", "generated/" + this.getClass.getSimpleName.dropRight(1)),
      () => Module(new MIIRx())) {
      dut => new MIIRxTester(dut, EthernetTesting.mockupPTPEthFrameOverIpUDP)
    }
  } finally {
    "gtkwave " + pathToVCD + "/" + nameOfVCD + " " + pathToVCD + "/" + "view.sav &" !
  }
}

  poke(dut.io.miiChannel.dv, 0)
  step(3)


// See README.md for license details.

package ethcontroller.design

import Chisel.{Module, Tester, chiselMainTest}
import ethcontroller.protocols.{EthernetFrame, EthernetTesting}

import scala.language.postfixOps
import scala.sys.process._

class MIITxTester(dut: MIITx, frame: EthernetFrame) extends Tester(dut) {
  // Shifting of discrete numbers for clarity
  var byteAddr = 0
  //  poke(dut.io.startTx, true)
  poke(dut.io.miiChannel.clk, 0)
  step(3)
  //  poke(dut.io.startTx, false)
  println("-- Testing the preamble")
  while(byteAddr < frame.preamble.length){
    if(peek(dut.io.ready) == 1){
      poke(dut.io.macDataDv, true)
      poke(dut.io.macData, BigInt(frame.preamble(byteAddr)))
      byteAddr+=1
    }
    peek(dut.serializeByteToNibble.selHi)
    peek(dut.serializeByteToNibble.selLo)
    poke(dut.io.miiChannel.clk, 0)
    step(1)
    poke(dut.io.macDataDv, false)
    step(2)
    poke(dut.io.miiChannel.clk, 1)
    step(3)
  }
  while(peek(dut.io.ready) == 0){
    poke(dut.io.miiChannel.clk, 0)
    step(3)
    poke(dut.io.miiChannel.clk, 1)
    step(3)
  }
  //  poke(dut.io.endTx, true)
  poke(dut.io.miiChannel.clk, 0)
  step(1)
  //  poke(dut.io.endTx, true)
  step(2)
  poke(dut.io.miiChannel.clk, 1)
  step(3)
  poke(dut.io.miiChannel.clk, 0)
  step(3)
  poke(dut.io.miiChannel.clk, 1)
  step(3)
  expect(dut.io.miiChannel.dv, false, "Expecting an idle bus")
  expect(dut.serializeByteToNibble.io.done, true, "Expecting the serializer to have finished")
}

object MIITxTester extends App {
  private val pathToVCD = "generated/" + this.getClass.getSimpleName.dropRight(1)
  private val nameOfVCD = this.getClass.getSimpleName.dropRight(7) + ".vcd"

  try {
    chiselMainTest(Array("--genHarness", "--test", "--backend", "c",
      "--compile", "--vcd", "--targetDir", "generated/" + this.getClass.getSimpleName.dropRight(1)),

      () => Module(new MIITx())) {
      dut => new MIITxTester(dut, EthernetTesting.mockupPTPEthFrameOverIpUDP)
    }
  } finally {


// See README.md for license details.

package ethcontroller.protocols

object EthernetTesting {

  val mockupPTPEthFrameDHCP = new DefaultEthFrame {
    override val dstMac: Array[Byte] = EthernetUtils.toBytes(0xff, 0xff, 0xff, 0xff, 0xff, 0xff)
    override val srcMac: Array[Byte] = EthernetUtils.toBytes(0x80, 0xce, 0x62, 0xd8, 0xc7, 0x39)
    override val ethType: Array[Byte] = EthernetUtils.toBytes(0x08, 0x00)
    override val ipHeader: Array[Byte] = EthernetUtils.toBytes(0x45, 0x10, 0x01, 0x48, 0x00, 0x00, 0x00, 0x00,
      0x80, 0x11, 0x39, 0x96, 0x00, 0x00, 0x00, 0x00,
      0xff, 0xff, 0xff, 0xff)
    override val udpHeader: Array[Byte] = EthernetUtils.toBytes(0x00, 0x44, 0x00, 0x43, 0x01, 0x34, 0x5c, 0xec)
    //Should be ignored
    override val ptpHeader: Array[Byte] = EthernetUtils.toBytes(PTP_FOLLOW_UP_TYPE, 0x02, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0xE4, 0xAF, 0xA1, 0x30,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x55, 0x55,
      0x00, 0x00)
    override val ptpBody: Array[Byte] = EthernetUtils.toBytes(0x54, 0x32, 0x11, 0x11, 0x55, 0x55, 0xAA, 0xAA)
    override val ptpSuffix: Array[Byte] = EthernetUtils.toBytes(0x00)
    override val fcs: Array[Byte] = EthernetUtils.toBytes(0x00)
    override val igp: Array[Byte] = EthernetUtils.toBytes(0x00)
    override val data: Array[Byte] = ipHeader ++ udpHeader ++ ptpHeader ++ ptpBody ++ ptpSuffix ++ fcs ++ igp
  }
  val mockupPTPVLANFrameOverIpUDP = new DefaultEthFrame {
    override val dstMac: Array[Byte] = EthernetUtils.toBytes(0x01, 0x00, 0x5E, 0x00, 0x01, 0x81)
    override val srcMac: Array[Byte] = EthernetUtils.toBytes(0x01, 0x00, 0x5E, 0x00, 0x01, 0x81)
    override val ethType: Array[Byte] = EthernetUtils.toBytes(0x81, 0x00, 0x01, 0x11, 0x08, 0x00)
    override val ipHeader: Array[Byte] = EthernetUtils.toBytes(0x45, 0x00, 0x00, 0x48, 0x00, 0x5D, 0x40, 0x00,
      0x01, 0x11, 0x29, 0x68, 0xC0, 0xA8, 0x01, 0x01,
      0xE0, 0x00, 0x01, 0x81)
    override val udpHeader: Array[Byte] = EthernetUtils.toBytes(0x01, 0x3F, 0x01, 0x3F, 0x00, 0x34, 0x00, 0x00)
    override val ptpHeader: Array[Byte] = EthernetUtils.toBytes(PTP_FOLLOW_UP_TYPE, 0x02, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0xE4, 0xAF, 0xA1, 0x30,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x55, 0x55,
      0x00, 0x00)
    override val ptpBody: Array[Byte] = EthernetUtils.toBytes(0x54, 0x32, 0x11, 0x11, 0x55, 0x55, 0xAA, 0xAA)
    override val ptpSuffix: Array[Byte] = EthernetUtils.toBytes(0x00)
    override val fcs: Array[Byte] = EthernetUtils.toBytes(0x00)
    override val igp: Array[Byte] = EthernetUtils.toBytes(0x00)
    override val data: Array[Byte] = ipHeader ++ udpHeader ++ ptpHeader ++ ptpBody ++ ptpSuffix ++ fcs ++ igp
  }
  val mockupPTPEthFrameOverIpUDP = new DefaultEthFrame {
    override val dstMac: Array[Byte] = EthernetUtils.toBytes(0x01, 0x00, 0x5E, 0x00, 0x01, 0x81)
    override val srcMac: Array[Byte] = EthernetUtils.toBytes(0x01, 0x00, 0x5E, 0x00, 0x01, 0x81)
    override val ethType: Array[Byte] = EthernetUtils.toBytes(0x08, 0x00)
    override val ipHeader: Array[Byte] = EthernetUtils.toBytes(0x45, 0x00, 0x00, 0x48, 0x00, 0x5D, 0x40, 0x00,
      0x01, 0x11, 0x29, 0x68, 0xC0, 0xA8, 0x01, 0x01,
      0xE0, 0x00, 0x01, 0x81)
    override val udpHeader: Array[Byte] = EthernetUtils.toBytes(0x01, 0x3F, 0x01, 0x3F, 0x00, 0x34, 0x00, 0x00)
    override val ptpHeader: Array[Byte] = EthernetUtils.toBytes(PTP_FOLLOW_UP_TYPE, 0x02, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0xE4, 0xAF, 0xA1, 0x30,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x55, 0x55,
      0x00, 0x00)
    override val ptpBody: Array[Byte] = EthernetUtils.toBytes(0x54, 0x32, 0x11, 0x11, 0x55, 0x55, 0xAA, 0xAA)
    override val ptpSuffix: Array[Byte] = EthernetUtils.toBytes(0x00)
    override val fcs: Array[Byte] = EthernetUtils.toBytes(0x00)
    override val igp: Array[Byte] = EthernetUtils.toBytes(0x00)
    override val data: Array[Byte] = ipHeader ++ udpHeader ++ ptpHeader ++ ptpBody ++ ptpSuffix ++ fcs ++ igp
  }
  val mockupTTEPCFFrame = new DefaultEthFrame {
    override val dstMac: Array[Byte] = EthernetUtils.toBytes(0xAB, 0xAD, 0xBA, 0xBE, 0x0F, 0xCE)
    override val srcMac: Array[Byte] = EthernetUtils.toBytes(0x00, 0x11, 0x22, 0x33, 0x44, 0x55)
    override val ethType: Array[Byte] = EthernetUtils.toBytes(0x89, 0x1D)
    override val data: Array[Byte] = EthernetUtils.toBytes(0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1b, 0xc0, 0x00, 0x00)
  }

  class DefaultEthFrame extends EthernetFrame {
    override val preamble: Array[Byte] = EthernetUtils.toBytes(0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xD5)
    override val dstMac: Array[Byte] = Array.emptyByteArray
    override val srcMac: Array[Byte] = Array.emptyByteArray
    override val ethType: Array[Byte] = Array.emptyByteArray
    override val ipHeader: Array[Byte] = Array.emptyByteArray
    override val udpHeader: Array[Byte] = Array.emptyByteArray
    override val ptpHeader: Array[Byte] = Array.emptyByteArray
    override val ptpBody: Array[Byte] = Array.emptyByteArray

    override val ptpSuffix: Array[Byte] = Array.emptyByteArray
    override val fcs: Array[Byte] = Array.emptyByteArray
    override val igp: Array[Byte] = Array.emptyByteArray


// See README.md for license details.

package ethcontroller.utils

import Chisel._
import ethcontroller.protocols.{EthernetFrame, EthernetTesting}

import scala.language.postfixOps
import scala.sys.process._

class DeserializerTester(dut: Deserializer, frame: EthernetFrame) extends Tester(dut) {
  // Shifting of discrete numbers for clarity
  poke(dut.io.en, true)
  for (i <- frame.preambleNibbles) {
    poke(dut.io.shiftIn, i)

  expect(dut.io.shiftOut, frame.PREAMBLE_END)
  poke(dut.io.en, false)
  step(1)
  poke(dut.io.clr, true)
  step(1)
  expect(dut.countReg, 8 / 4 - 1)
  expect(dut.shiftReg, 0)
  expect(dut.doneReg, false)
}

object DeserializerTester extends App {
  private val pathToVCD = "generated/" + this.getClass.getSimpleName.dropRight(1)
  private val nameOfVCD = this.getClass.getSimpleName.dropRight(7) + ".vcd"

  try {
    chiselMainTest(Array("--genHarness", "--test", "--backend", "c",
      "--compile", "--vcd", "--targetDir", "generated/" + this.getClass.getSimpleName.dropRight(1)),
      () => Module(new Deserializer(false, 4, 8))) {
      dut => new DeserializerTester(dut, EthernetTesting.mockupPTPEthFrameOverIpUDP)
    }
  } finally {
    "gtkwave " + pathToVCD + "/" + nameOfVCD + " " + pathToVCD + "/" + "view.sav" !
  }
}

    peek(dut.io.shiftOut)
    peek(dut.io.done)
    step(1)


// See README.md for license details.

package ethcontroller.utils

import Chisel._
import ethcontroller.protocols.{EthernetFrame, EthernetTesting}

import scala.language.postfixOps
import scala.sys.process._

class SerializerTester(dut: Serializer, frame: EthernetFrame) extends Tester(dut) {
  // Shifting of discrete numbers for clarity
  poke(dut.io.en, true)
  for (i <- frame.preamble) {
    step(1)
    poke(dut.io.load, true)
    poke(dut.io.shiftIn, i.toInt)
    step(1)
    poke(dut.io.load, false)
    do {
      step(1)
    } while (peek(dut.io.done) == 0)
    peek(dut.io.shiftOut)
  }
  expect(dut.io.done, true)
  expect(dut.io.shiftOut, frame.PREAMBLE_END)
  step(1)
  poke(dut.io.en, false)
  expect(dut.countReg, 8 / 4 - 1)
  expect(dut.shiftReg, 0)
  expect(dut.doneReg, false)
}

object SerializerTester extends App {
  private val pathToVCD = "generated/" + this.getClass.getSimpleName.dropRight(1)
  private val nameOfVCD = this.getClass.getSimpleName.dropRight(7) + ".vcd"

  try {
    chiselMainTest(Array("--genHarness", "--test", "--backend", "c",
      "--compile", "--vcd", "--targetDir", "generated/" + this.getClass.getSimpleName.dropRight(1)),

      () => Module(new Serializer(false, 8, 4))) {
      dut => new SerializerTester(dut, EthernetTesting.mockupPTPEthFrameOverIpUDP)
    }


package chiselv

import chisel3._
import chisel3.util.{is, switch}
import chiselv.Instruction._

class ALUPort(bitWidth: Int = 32) extends Bundle {
  val inst = Input(Instruction())
  val a    = Input(UInt(bitWidth.W))
  val b    = Input(UInt(bitWidth.W))
  val x    = Output(UInt(bitWidth.W))
}
class ALU(bitWidth: Int = 32) extends Module {
  val io  = IO(new ALUPort(bitWidth))
  val out = WireDefault(0.U(bitWidth.W))

  // For RV32I the shift amount is 5 bits, for RV64I is 6 bits

  switch(io.inst) {
    // Arithmetic
    is(ADD, ADDI)(out := io.a + io.b)
    is(SUB)(out       := io.a - io.b)
    // Shifts
    is(SRA, SRAI)(out := (io.a.asSInt >> io.b(shamt, 0)).asUInt) // Signed
    is(SRL, SRLI)(out := io.a >> io.b(shamt, 0))
    is(SLL, SLLI)(out := io.a << io.b(shamt, 0))
    // Logical
    is(AND, ANDI)(out := io.a & io.b)
    is(OR, ORI)(out   := io.a | io.b)
    is(XOR, XORI)(out := io.a ^ io.b)
    // Compare
    is(SLT, SLTI)(out   := Mux(io.a.asSInt < io.b.asSInt, 1.U, 0.U)) // Signed
    is(SLTU, SLTIU)(out := Mux(io.a < io.b, 1.U, 0.U))
    // Auxiliary
    is(EQ)(out   := Mux(io.a === io.b, 1.U, 0.U))
    is(NEQ)(out  := Mux(io.a =/= io.b, 1.U, 0.U))
    is(GTE)(out  := Mux(io.a.asSInt >= io.b.asSInt, 1.U, 0.U))
    is(GTEU)(out := Mux(io.a >= io.b, 1.U, 0.U))
  }

  io.x := out
}

val shamt = (if (bitWidth == 32) 5 else if (bitWidth == 64) 6 else 0) - 1


package chiselv

import chisel3._
import chisel3.util.Counter

/**
 * The blinking LED module.
 *
 * @constructor
 *   creates a new blinking LED module that flashes every second.
 * @param freq
 *   the frequency of the core in Hertz.
 * @param startOn
 *   if true, the LED is initially on.
 * @param io_led0
 *   (Output) is the led output
 */
class Blinky(freq: Int, startOn: Boolean = false) extends Module {
  val io = IO(new Bundle {
    val led0 = Output(Bool())
  })

  // Blink leds every second (start on)
  val led              = RegInit(startOn.B)
  val (_, counterWrap) = Counter(true.B, freq / 2)

  when(counterWrap) {
    led := ~led
  }


package chiselv

import chisel3._
import chisel3.experimental.Analog
import chisel3.util.{Cat, Fill, is, switch}
import chiselv.Instruction._

class CPUSingleCycle(
    cpuFrequency:          Int,
    entryPoint:            Long,
    bitWidth:              Int = 32,
    instructionMemorySize: Int = 1 * 1024,
    dataMemorySize:        Int = 1 * 1024,
    numGPIO:               Int = 8,
  ) extends Module {
  val io = IO(new Bundle {
    val GPIO0External = Analog(numGPIO.W) // GPIO external port

    val UART0Port          = Flipped(new UARTPort) // UART0 data port
    val SysconPort         = Flipped(new SysconPort(bitWidth))
    val instructionMemPort = Flipped(new InstructionMemPort(bitWidth, instructionMemorySize))
    val dataMemPort        = Flipped(new MemoryPortDual(bitWidth, dataMemorySize))
  })

  val stall = WireDefault(false.B)

  // Instantiate and initialize the Register Bank
  val registerBank = Module(new RegisterBank(bitWidth))
  registerBank.io.writeEnable := false.B
  registerBank.io.regwr_data  := 0.U
  registerBank.io.stall       := stall

  // Instantiate and initialize the Program Counter
  val PC = Module(new ProgramCounter(bitWidth, entryPoint))
  PC.io.writeEnable := false.B
  PC.io.dataIn      := 0.U
  PC.io.writeAdd    := false.B

  // Instantiate and initialize the ALU
  val ALU = Module(new ALU(bitWidth))
  ALU.io.inst := ERR_INST
  ALU.io.a    := 0.U
  ALU.io.b    := 0.U

  // Instantiate and initialize the Instruction Decoder
  val decoder = Module(new Decoder(bitWidth))
  decoder.io.op := 0.U

  // Instantiate and initialize the Memory IO Manager
  val memoryIOManager = Module(new MemoryIOManager(bitWidth, dataMemorySize))
  memoryIOManager.io.MemoryIOPort.readRequest  := false.B
  memoryIOManager.io.MemoryIOPort.writeRequest := false.B
  memoryIOManager.io.MemoryIOPort.readAddr     := 0.U
  memoryIOManager.io.MemoryIOPort.writeAddr    := 0.U
  memoryIOManager.io.MemoryIOPort.writeData    := 0.U
  memoryIOManager.io.MemoryIOPort.dataSize     := 0.U
  memoryIOManager.io.MemoryIOPort.writeMask    := 0.U
  // Connect MMIO to the devices
  memoryIOManager.io.DataMemPort <> io.dataMemPort
  memoryIOManager.io.UART0Port <> io.UART0Port
  memoryIOManager.io.SysconPort <> io.SysconPort

  // Instantiate and connect GPIO
  val GPIO0 = Module(new GPIO(bitWidth, numGPIO))
  memoryIOManager.io.GPIO0Port <> GPIO0.io.GPIOPort
  if (numGPIO > 0) {
    GPIO0.io.externalPort <> io.GPIO0External
  }

  // Instantiate and connect the Timer
  val timer0 = Module(new Timer(bitWidth, cpuFrequency))
  memoryIOManager.io.Timer0Port <> timer0.io

  // --------------- CPU Control --------------- //
  // State of the CPU Stall
  stall := memoryIOManager.io.stall
  when(!stall) {
    // If CPU is stalled, do not advance PC
    PC.io.writeEnable := true.B
    PC.io.dataIn      := PC.io.PC4
  }

  // Connect PC output to instruction memory
  when(io.instructionMemPort.ready) {
    io.instructionMemPort.readAddr := PC.io.PC
  }.otherwise(
    io.instructionMemPort.readAddr := DontCare
  )

  // Connect the instruction memory to the decoder
  decoder.io.op := io.instructionMemPort.readData

  // Connect the decoder output to register bank inputs
  registerBank.io.regwr_addr := decoder.io.rd
  registerBank.io.rs1_addr   := decoder.io.rs1
  registerBank.io.rs2_addr   := decoder.io.rs2

  // ----- CPU Operations ----- //

  // ALU Operations
  when(decoder.io.toALU) {
    ALU.io.inst := decoder.io.inst
    ALU.io.a    := registerBank.io.rs1
    ALU.io.b := Mux(
      decoder.io.use_imm,
      decoder.io.imm.asUInt,
      registerBank.io.rs2,
    )

    registerBank.io.writeEnable := true.B
    registerBank.io.regwr_data  := ALU.io.x
  }

  // Branch Operations
  when(decoder.io.branch) {
    ALU.io.a := registerBank.io.rs1
    ALU.io.b := registerBank.io.rs2
    switch(decoder.io.inst) {
      is(BEQ)(ALU.io.inst  := EQ)
      is(BNE)(ALU.io.inst  := NEQ)
      is(BLT)(ALU.io.inst  := SLT)
      is(BGE)(ALU.io.inst  := GTE)
      is(BLTU)(ALU.io.inst := SLTU)
      is(BGEU)(ALU.io.inst := GTEU)
    }
    when(ALU.io.x === 1.U) {
      PC.io.writeEnable := true.B
      PC.io.writeAdd    := true.B
      PC.io.dataIn      := decoder.io.imm.asUInt
    }
  }

  // Jump Operations
  when(decoder.io.jump) {
    // Write next instruction address to rd
    registerBank.io.writeEnable := true.B
    // Use the ALU to get the result
    ALU.io.inst                := ADD
    ALU.io.a                   := PC.io.PC
    ALU.io.b                   := 4.U
    registerBank.io.regwr_data := ALU.io.x

    PC.io.writeEnable := true.B
    when(decoder.io.inst === JAL) {
      // Set PC to jump address
      PC.io.writeAdd := true.B
      PC.io.dataIn   := decoder.io.imm.asUInt
    }
    when(decoder.io.inst === JALR) {
      // Set PC to jump address
      PC.io.dataIn := Cat(
        (registerBank.io.rs1 + decoder.io.imm.asUInt)(31, 1),
        0.U,
      )
    }
  }

  // LUI
  when(decoder.io.inst === LUI) {
    registerBank.io.writeEnable := true.B
    registerBank.io.regwr_data  := decoder.io.imm.asUInt
  }

  // AUIPC
  when(decoder.io.inst === AUIPC) {
    registerBank.io.writeEnable := true.B
    ALU.io.inst                 := ADD
    ALU.io.a                    := PC.io.PC
    ALU.io.b :=
      decoder.io.imm.asUInt
    registerBank.io.regwr_data := ALU.io.x
  }

  // Loads & Stores
  when(decoder.io.is_load || decoder.io.is_store) {
    // Use the ALU to get the resulting address
    ALU.io.inst := ADD
    ALU.io.a    := registerBank.io.rs1
    ALU.io.b    := decoder.io.imm.asUInt

    memoryIOManager.io.MemoryIOPort.writeAddr := ALU.io.x
    memoryIOManager.io.MemoryIOPort.readAddr  := ALU.io.x
  }

  when(decoder.io.is_load) {
    val dataSize = WireDefault(0.U(2.W)) // Data size, 1 = byte, 2 = halfword, 3 = word
    val dataOut  = WireDefault(0.U(32.W))

    // Load Word

      dataOut := Cat(
        Fill(16, memoryIOManager.io.MemoryIOPort.readData(15)),
        memoryIOManager.io.MemoryIOPort.readData(15, 0),
      )
    }
    // Load Halfword Unsigned
    when(decoder.io.inst === LHU) {
      dataSize := 2.U
      dataOut  := Cat(Fill(16, 0.U), memoryIOManager.io.MemoryIOPort.readData(15, 0))
    }
    // Load Byte
    when(decoder.io.inst === LB) {
      dataSize := 1.U
      dataOut := Cat(
        Fill(24, memoryIOManager.io.MemoryIOPort.readData(7)),
        memoryIOManager.io.MemoryIOPort.readData(7, 0),
      )
    }
    // Load Byte Unsigned
    when(decoder.io.inst === LBU) {
      dataSize := 1.U
      dataOut  := Cat(Fill(24, 0.U), memoryIOManager.io.MemoryIOPort.readData(7, 0))
    }
    memoryIOManager.io.MemoryIOPort.readRequest := decoder.io.is_load
    memoryIOManager.io.MemoryIOPort.dataSize    := dataSize
    registerBank.io.writeEnable                 := true.B
    registerBank.io.regwr_data                  := dataOut
  }

  when(decoder.io.is_store) {
    // Define if operation is a load or store
    memoryIOManager.io.MemoryIOPort.writeRequest := decoder.io.is_store

    // Stores
    val dataOut  = WireDefault(0.U(32.W))
    val dataSize = WireDefault(0.U(2.W)) // Data size, 1 = byte, 2 = halfword, 3 = word

    // Store Word
    when(decoder.io.inst === SW) {
      dataOut  := registerBank.io.rs2
      dataSize := 3.U
    }
    // Store Halfword
    when(decoder.io.inst === SH) {
      dataOut  := Cat(Fill(16, 0.U), registerBank.io.rs2(15, 0))
      dataSize := 2.U
    }
    // Store Byte
    when(decoder.io.inst === SB) {
      dataOut  := Cat(Fill(24, 0.U), registerBank.io.rs2(7, 0))
      dataSize := 1.U
    }
    memoryIOManager.io.MemoryIOPort.dataSize  := dataSize
    memoryIOManager.io.MemoryIOPort.writeData := dataOut
  }
}

    when(decoder.io.inst === LW) {
      dataSize := 3.U
      dataOut  := memoryIOManager.io.MemoryIOPort.readData
    }


package chiselv

import chisel3.ChiselEnum

object Instruction extends ChiselEnum {
  val ERR_INST,
  // RV32I
  ADD, ADDI, SUB, LUI, AUIPC,                  // Arithmetic
  SLL, SLLI, SRL, SRLI, SRA, SRAI,             // Shifts
  AND, ANDI, OR, ORI, XOR, XORI,               // Logical
  SLT, SLTI, SLTU, SLTIU,                      // Compare
  BEQ, BNE, BLT, BGE, BLTU, BGEU,              // Branches
  JAL, JALR,                                   // Jump & Link
  FENCE, FENCEI,                               // Sync
  ECALL, EBREAK,                               // Environment
  CSRRW, CSRRS, CSRRC, CSRRWI, CSRRSI, CSRRCI, // CSR
  LB, LH, LBU, LHU, LW,                        // Loads
  SB, SH, SW,                                  // Stores
  EQ, NEQ, GTE, GTEU                           // Not instructions but auxiliaries
  = Value
}

object InstructionType extends ChiselEnum {
  val IN_ERR, INST_R, INST_I, INST_S, INST_B, INST_U, INST_J, INST_Z = Value
}


package chiselv

import chisel3._
import chisel3.util.experimental.loadMemoryFromFileInline
import chisel3.util.log2Ceil

class MemoryPortDual(val bitWidth: Int, val addressSize: Long) extends Bundle {
  val readAddress  = Input(UInt(log2Ceil(addressSize).W))
  val readData     = Output(UInt(bitWidth.W))
  val writeAddress = Input(UInt(log2Ceil(addressSize).W))
  val writeData    = Input(UInt(bitWidth.W))
  val writeMask    = Input(UInt((bitWidth / 8).W))
  val dataSize     = Input(UInt(2.W))
  val writeEnable  = Input(Bool())
}
class DualPortRAM(
    bitWidth:   Int = 32,
    sizeBytes:  Long = 1,
    memoryFile: String = "",
    debugMsg:   Boolean = false,
  ) extends Module {
  val words = sizeBytes / bitWidth
  val io    = IO(new MemoryPortDual(bitWidth, sizeBytes))

  if (debugMsg) {
    println(s"Dual-port Memory Parameters:")
    println(s"  Words: $words")
    println(s"  Size: " + words * bitWidth + " bytes")
    println(s"  Bit width: $bitWidth bit")
    println(s"  Addr Width: " + io.readAddress.getWidth + " bit")
  }

  val mem = SyncReadMem(words, UInt(bitWidth.W))

  // Divide memory address by 4 to get the word due to pc+4 addressing
  val readAddress  = io.readAddress >> 2
  val writeAddress = io.writeAddress >> 2

  if (memoryFile.trim().nonEmpty) {

    loadMemoryFromFileInline(mem, memoryFile)
  }

  io.readData := mem.read(readAddress)

  when(io.writeEnable === true.B) {
    mem.write(writeAddress, io.writeData)
  }
}

if (debugMsg) println(s"  Load memory file: " + memoryFile)


package chiselv

import chisel3._
import chisel3.util._
import chiselv.Instruction._
import chiselv.InstructionType._

class DecoderPort(bitWidth: Int = 32) extends Bundle {
  val op       = Input(UInt(bitWidth.W))  // Op is the 32 bit instruction read received for decoding
  val inst     = Output(Instruction())    // Instruction is the decoded instruction
  val rd       = Output(UInt(5.W))        // Rd is the 5 bit destiny register
  val rs1      = Output(UInt(5.W))        // Rs1 is the 5 bit source register 1
  val rs2      = Output(UInt(5.W))        // Rs2 is the 5 bit source register 2
  val imm      = Output(SInt(bitWidth.W)) // Imm is the 32 bit immediate
  val toALU    = Output(Bool())           // ToALU is a flag to indicate if the instruction is to be executed in the ALU
  val branch   = Output(Bool())           // Branch is a flag to indicate if the instruction should jump and link. Update PC
  val use_imm  = Output(Bool())           // Use_imm is a flag to indicate if the instruction has an immediate
  val jump     = Output(Bool())           // jump is a flag to indicate if the instruction has is a jump
  val is_load  = Output(Bool())           // is_load is a flag to indicate if the instruction has a load from memory
  val is_store = Output(Bool())           // is_store is a flag to indicate if the instruction has a store to memory
}

class Decoder(bitWidth: Int = 32) extends Module {
  val io = IO(new DecoderPort(bitWidth))

  val signals =
    ListLookup(
      io.op,
      // format: off
      List(                                                  IN_ERR, ERR_INST, false.B,  false.B, false.B,  false.B,  false.B,  false.B), // Default values
      Array(
        /*                                                inst_type,     inst   to_alu   branch   use_imm   jump      is_load   is_store */
        // Arithmetic
        BitPat("b0000000??????????000?????0110011")  -> List(INST_R,      ADD,  true.B,  false.B, false.B,  false.B,  false.B,  false.B),
        BitPat("b?????????????????000?????0010011")  -> List(INST_I,     ADDI,  true.B,  false.B, true.B,   false.B,  false.B,  false.B),
        BitPat("b0100000??????????000?????0110011")  -> List(INST_R,      SUB,  true.B,  false.B, false.B,  false.B,  false.B,  false.B),
        BitPat("b?????????????????????????0110111")  -> List(INST_U,      LUI, false.B,  false.B, true.B,   false.B,  false.B,  false.B),
        BitPat("b?????????????????????????0010111")  -> List(INST_U,    AUIPC, false.B,  false.B, true.B,   false.B,  false.B,  false.B),
        // Shifts
        BitPat("b0000000??????????001?????0110011")  -> List(INST_R,      SLL,  true.B,  false.B, false.B,  false.B,  false.B,  false.B),
        BitPat("b0000000??????????001?????0010011")  -> List(INST_I,     SLLI,  true.B,  false.B, true.B,   false.B,  false.B,  false.B),
        BitPat("b0000000??????????101?????0110011")  -> List(INST_R,      SRL,  true.B,  false.B, false.B,  false.B,  false.B,  false.B),
        BitPat("b0000000??????????101?????0010011")  -> List(INST_I,     SRLI,  true.B,  false.B, true.B,   false.B,  false.B,  false.B),
        BitPat("b0100000??????????101?????0110011")  -> List(INST_R,      SRA,  true.B,  false.B, false.B,  false.B,  false.B,  false.B),
        BitPat("b0100000??????????101?????0010011")  -> List(INST_I,     SRAI,  true.B,  false.B, true.B,   false.B,  false.B,  false.B),
        // Logical
        BitPat("b0000000??????????100?????0110011")  -> List(INST_R,      XOR, true.B,   false.B, false.B,  false.B,  false.B,  false.B),
        BitPat("b?????????????????100?????0010011")  -> List(INST_I,     XORI, true.B,   false.B, true.B,   false.B,  false.B,  false.B),
        BitPat("b0000000??????????110?????0110011")  -> List(INST_R,       OR, true.B,   false.B, false.B,  false.B,  false.B,  false.B),
        BitPat("b?????????????????110?????0010011")  -> List(INST_I,      ORI, true.B,   false.B, true.B,   false.B,  false.B,  false.B),
        BitPat("b0000000??????????111?????0110011")  -> List(INST_R,      AND, true.B,   false.B, false.B,  false.B,  false.B,  false.B),
        BitPat("b?????????????????111?????0010011")  -> List(INST_I,     ANDI, true.B,   false.B, true.B,   false.B,  false.B,  false.B),
        // Compare
        BitPat("b0000000??????????010?????0110011")  -> List(INST_R,     SLT,  true.B,   false.B, false.B,  false.B,  false.B,  false.B),
        BitPat("b?????????????????010?????0010011")  -> List(INST_I,    SLTI,  true.B,   false.B, true.B,   false.B,  false.B,  false.B),
        BitPat("b0000000??????????011?????0110011")  -> List(INST_R,    SLTU,  true.B,   false.B, false.B,  false.B,  false.B,  false.B),
        BitPat("b?????????????????011?????0010011")  -> List(INST_I,   SLTIU,  true.B,   false.B, true.B,   false.B,  false.B,  false.B),
        // Branches
        BitPat("b?????????????????000?????1100011")  -> List(INST_B,     BEQ, false.B,    true.B, true.B,   false.B,  false.B,  false.B),
        BitPat("b?????????????????001?????1100011")  -> List(INST_B,     BNE, false.B,    true.B, true.B,   false.B,  false.B,  false.B),
        BitPat("b?????????????????100?????1100011")  -> List(INST_B,     BLT, false.B,    true.B, true.B,   false.B,  false.B,  false.B),
        BitPat("b?????????????????101?????1100011")  -> List(INST_B,     BGE, false.B,    true.B, true.B,   false.B,  false.B,  false.B),
        BitPat("b?????????????????110?????1100011")  -> List(INST_B,    BLTU, false.B,    true.B, true.B,   false.B,  false.B,  false.B),
        BitPat("b?????????????????111?????1100011")  -> List(INST_B,    BGEU, false.B,    true.B, true.B,   false.B,  false.B,  false.B),
        // Jump & link
        BitPat("b?????????????????????????1101111")  -> List(INST_J,     JAL, false.B,   false.B, true.B,    true.B,  false.B,  false.B),
        BitPat("b?????????????????000?????1100111")  -> List(INST_I,    JALR, false.B,   false.B, true.B,    true.B,  false.B,  false.B),
        // Sync
        BitPat("b0000????????00000000000000001111")  -> List(INST_I,   FENCE, false.B,   false.B, false.B,   false.B, false.B,  false.B),
        BitPat("b00000000000000000000001000001111")  -> List(INST_I,  FENCEI, false.B,   false.B,  true.B,   false.B, false.B,  false.B),
        // Environment
        BitPat("b00000000000000000000000001110011")  -> List(INST_I,   ECALL, false.B,   false.B, false.B,   false.B, false.B,  false.B),
        BitPat("b00000000000100000000000001110011")  -> List(INST_I,  EBREAK, false.B,   false.B, false.B,   false.B, false.B,  false.B),
        // CSR
        BitPat("b?????????????????001?????1110011")  -> List(INST_I,   CSRRW, false.B,   false.B, false.B,   false.B, false.B,  false.B),
        BitPat("b?????????????????010?????1110011")  -> List(INST_I,   CSRRS, false.B,   false.B, false.B,   false.B, false.B,  false.B),
        BitPat("b?????????????????011?????1110011")  -> List(INST_I,   CSRRC, false.B,   false.B, false.B,   false.B, false.B,  false.B),
        BitPat("b?????????????????101?????1110011")  -> List(INST_I,  CSRRWI, false.B,   false.B, true.B,    false.B, false.B,  false.B),
        BitPat("b?????????????????110?????1110011")  -> List(INST_I,  CSRRSI, false.B,   false.B, true.B,    false.B, false.B,  false.B),
        BitPat("b?????????????????111?????1110011")  -> List(INST_I,  CSRRCI, false.B,   false.B, true.B,    false.B, false.B,  false.B),
        // Loads
        BitPat("b?????????????????000?????0000011")  -> List(INST_I,      LB, false.B,   false.B, true.B,    false.B,  true.B,  false.B),
        BitPat("b?????????????????001?????0000011")  -> List(INST_I,      LH, false.B,   false.B, true.B,    false.B,  true.B,  false.B),
        BitPat("b?????????????????100?????0000011")  -> List(INST_I,     LBU, false.B,   false.B, true.B,    false.B,  true.B,  false.B),
        BitPat("b?????????????????101?????0000011")  -> List(INST_I,     LHU, false.B,   false.B, true.B,    false.B,  true.B,  false.B),
        BitPat("b?????????????????010?????0000011")  -> List(INST_I,      LW, false.B,   false.B, true.B,    false.B,  true.B,  false.B),
        // Stores
        BitPat("b?????????????????000?????0100011")  -> List(INST_S,      SB, false.B,   false.B, true.B,    false.B, false.B,   true.B),
        BitPat("b?????????????????001?????0100011")  -> List(INST_S,      SH, false.B,   false.B, true.B,    false.B, false.B,   true.B),
        BitPat("b?????????????????010?????0100011")  -> List(INST_S,      SW, false.B,   false.B, true.B,    false.B, false.B,   true.B),
      )
    ) // format: on

  io.rd       := 0.U
  io.rs1      := 0.U
  io.rs2      := 0.U
  io.imm      := 0.S
  io.inst     := signals(1)
  io.toALU    := signals(2)
  io.branch   := signals(3)
  io.use_imm  := signals(4)
  io.jump     := signals(5)
  io.is_load  := signals(6)
  io.is_store := signals(7)

  switch(signals(0)) {
    is(INST_R) {
      io.rd  := io.op(11, 7)
      io.rs1 := io.op(19, 15)
      io.rs2 := io.op(24, 20)
    }
    is(INST_I) {
      io.rd  := io.op(11, 7)
      io.rs1 := io.op(19, 15)
      io.imm := ImmGenerator(INST_I, io.op)
    }
    is(INST_S) {
      io.rs1 := io.op(19, 15)
      io.rs2 := io.op(24, 20)
      io.imm := ImmGenerator(INST_S, io.op)
    }
    is(INST_B) {
      io.rs1 := io.op(19, 15)
      io.rs2 := io.op(24, 20)
      io.imm := ImmGenerator(INST_B, io.op)
    }
    is(INST_U) {
      io.rd  := io.op(11, 7)
      io.imm := ImmGenerator(INST_U, io.op)
    }
    is(INST_J) {
      io.rd  := io.op(11, 7)
      io.imm := ImmGenerator(INST_J, io.op)
    }
  }

  /**
   * ImmGenerator generates the immadiate value depending on the instruction
   * type
   *
   * @param regType
   *   is the instruction type from `Constants.scala`
   * @return
   *   the imm value
   */
  def ImmGenerator(regType: InstructionType.Type, inst: UInt): SInt = regType match {

    case INST_Z => Cat(Fill(27, 0.U), inst(19, 15)).asSInt // for csri
    case _      => 0.S
  }
}

    case INST_R => 0.S
    case INST_I => Cat(Fill(20, inst(31)), inst(31, 20)).asSInt
    case INST_S => Cat(Fill(20, inst(31)), inst(31, 25), inst(11, 7)).asSInt
    case INST_B => Cat(Fill(19, inst(31)), inst(31), inst(7), inst(30, 25), inst(11, 8), 0.U(1.W)).asSInt


package chiselv

import chisel3._
import chisel3.experimental.Analog
import chisel3.util.HasBlackBoxInline // For Analog type

class GPIOPort(bitWidth: Int = 32) extends Bundle {
  val dataIn         = Input(UInt(bitWidth.W))
  val valueOut       = Output(UInt(bitWidth.W))
  val directionOut   = Output(UInt(bitWidth.W))
  val writeValue     = Input(Bool())
  val writeDirection = Input(Bool())
  val stall          = Output(Bool()) // >1 => Stall, 0 => Run
}

class GPIO(bitWidth: Int = 32, numGPIO: Int = 8) extends Module {
  require(
    numGPIO <= bitWidth,
    "Number of GPIOs must be less than or equal to the bitwidth, if needed, create another GPIO bank.",
  )
  val io = IO(new Bundle {
    val GPIOPort     = new GPIOPort(bitWidth)
    val externalPort = Analog(numGPIO.W)
  })

  val GPIO      = RegInit(0.U(bitWidth.W))
  val direction = RegInit(0.U(bitWidth.W)) // 0 = input, 1 = output

  io.GPIOPort.stall        := false.B
  io.GPIOPort.directionOut := direction

  // Only instantiate GPIO external interface if needed
  // this avoids using Verilator backend on tests that don't need it
  if (numGPIO > 0) {
    val InOut = Module(new GPIOInOut(numGPIO))
    io.GPIOPort.valueOut := InOut.io.dataOut
    InOut.io.dataIn      := GPIO
    InOut.io.dir         := direction
    io.externalPort <> InOut.io.dataIO
  } else {
    io.GPIOPort.valueOut := 0.U
    io.externalPort      := DontCare
  }

  when(io.GPIOPort.writeValue) {
    GPIO := io.GPIOPort.dataIn
  }
  when(io.GPIOPort.writeDirection) {
    direction := io.GPIOPort.dataIn
  }
}

class GPIOInOut(numGPIO: Int) extends BlackBox with HasBlackBoxInline {
  val io = IO(new Bundle {
    val dataIn  = Input(UInt(numGPIO.W))
    val dataOut = Output(UInt(numGPIO.W))
    val dir     = Input(UInt(numGPIO.W))
    val dataIO  = Analog(numGPIO.W)
  })
  setInline(
    "GPIOInOut.v",
    s"""|// This module is inspired by Lucas Teske's Riscow digital port
        |// https://github.com/racerxdl/riskow/blob/main/devices/digital_port.v
        |//
        |module GPIOInOut (
        |  inout   [${numGPIO - 1}:0] dataIO,
        |  input   [${numGPIO - 1}:0] dataIn,

        |  input   [${numGPIO - 1}:0] dir);
        |
        |  generate
        |    genvar idx;
        |    for(idx = 0; idx < $numGPIO; idx = idx+1) begin: register
        |      `ifdef SIMULATION
        |      assign dataIO[idx] = dir[idx] ? dataIn[idx] : 1'b0;
        |      `else
        |      assign dataIO [idx]= dir[idx] ? dataIn[idx] : 1'bZ;
        |      `endif
        |     end
        |  endgenerate
        |  assign dataOut = dataIO;
        |
        |endmodule
        |""".stripMargin,
  )
}


package chiselv

import chisel3._
import chisel3.util.experimental.loadMemoryFromFileInline
import chisel3.util.log2Ceil

class InstructionMemPort(val bitWidth: Int, val sizeBytes: Long) extends Bundle {
  val readAddr = Input(UInt(log2Ceil(sizeBytes).W))
  val readData = Output(UInt(bitWidth.W))
  val ready    = Output(Bool())
}

class InstructionMemory(
    bitWidth:   Int = 32,
    sizeBytes:  Long = 1,
    memoryFile: String = "",
  ) extends Module {
  val words = sizeBytes / bitWidth
  val io    = IO(new InstructionMemPort(bitWidth, sizeBytes))

  val mem = Mem(words, UInt(bitWidth.W))
  // Divide memory address by 4 to get the word due to pc+4 addressing
  val readAddress = io.readAddr >> 2

  if (memoryFile.trim().nonEmpty) {
    loadMemoryFromFileInline(mem, memoryFile)
  }


package chiselv

import chisel3._
import chisel3.util.{Cat, Fill, is, log2Ceil, switch}

/* Memory Map
 *
 * 0x0000_0000 - 0x0000_00FF: Reserved
 * 0x0000_0100 - 0x0000_0FFF: Debug
 * 0x0000_1000 - 0x0000_1FFF: Syscon
 * 0x0000_2000 - 0x0000_FFFF: Reserved
 * 0x0003_0000 - 0x0003_FFFF: ROM (64KB)
 * 0x0001_0000 - 0x2FFF_FFFF: Reserved
 * 0x3000_0000 - 0x3000_0FFF: UART0
 *                 0x00 (TX Write)
 *                 0x04 (RX Read)
 *                 0x0C (Status Read) [txFull|rxFull|txEmpty|rxEmpty]
 *                 0x10 (Clock Divisor Read/Write)
 * 0x3000_1000 - 0x3000_1FFF: GPIO0
 *                 0x00 (direction - 0: input, 1: output)
 *                 0x04 (value     - 0: low, 1: high)
 * 0x3000_2000 - 0x3000_2FFF: PWM0
 * 0x3000_3000 - 0x3000_3FFF: Timer0
 *                 0x00 (32 bit value in miliseconds)
 * 0x3000_4000 - 0x3FFF_FFFF: Reserved
 * 0x4000_0000 - 0x4FFF_FFFF: Reserved
 * 0x5000_0000 - 0x7000_0000: Reserved
 * 0x8000_0000 - 0x8FFF_FFFF: On-chip memory RAM
 * 0x9000_0000 - 0x9FFF_FFFF: Reserved
 */

class MMIOPort(val bitWidth: Int, val addressSize: Long) extends Bundle {
  val writeRequest = Input(Bool())
  val readRequest  = Input(Bool())
  val readAddr     = Input(UInt(log2Ceil(addressSize).W))
  val readData     = Output(UInt(bitWidth.W))
  val writeAddr    = Input(UInt(log2Ceil(addressSize).W))
  val writeData    = Input(UInt(bitWidth.W))
  val writeMask    = Input(UInt((bitWidth / 8).W))
  val dataSize     = Input(UInt(2.W))
}

class MemoryIOManager(bitWidth: Int = 32, sizeBytes: Long = 1024) extends Module {
  val io = IO(new Bundle {
    val MemoryIOPort = new MMIOPort(bitWidth, scala.math.pow(2, bitWidth).toLong)
    val GPIO0Port    = Flipped(new GPIOPort(bitWidth))
    val Timer0Port   = Flipped(new TimerPort(bitWidth))
    val UART0Port    = Flipped(new UARTPort)
    val DataMemPort  = Flipped(new MemoryPortDual(bitWidth, sizeBytes))
    val SysconPort   = Flipped(new SysconPort(bitWidth))
    val stall        = Output(Bool())
  })

  val dataOut      = WireDefault(0.U(bitWidth.W))
  val readAddress  = io.MemoryIOPort.readAddr
  val writeAddress = io.MemoryIOPort.writeAddr

  // Initialize IO
  io.GPIO0Port.dataIn         := 0.U
  io.GPIO0Port.writeValue     := false.B
  io.GPIO0Port.writeDirection := false.B

  io.Timer0Port.dataIn      := 0.U
  io.Timer0Port.writeEnable := false.B

  io.UART0Port.txQueue.valid      := false.B
  io.UART0Port.txQueue.bits       := 0.U
  io.UART0Port.rxQueue.ready      := false.B
  io.UART0Port.clockDivisor.bits  := 0.U
  io.UART0Port.clockDivisor.valid := false.B

  io.DataMemPort.writeEnable  := false.B
  io.DataMemPort.writeData    := 0.U
  io.DataMemPort.readAddress  := 0.U
  io.DataMemPort.writeAddress := 0.U
  io.DataMemPort.dataSize     := 0.U
  io.DataMemPort.writeMask    := 0.U

  io.SysconPort.Address := 0.U

  // Stall Management
  val stallLatency = WireDefault(0.U(4.W))
  val stallEnable  = WireDefault(false.B)
  val DACK         = RegInit(0.U(2.W))
  DACK := Mux(
    DACK > 0.U,
    DACK - 1.U,
    Mux(io.MemoryIOPort.readRequest || io.MemoryIOPort.writeRequest, stallLatency, 0.U),
  )
  io.stall := (io.MemoryIOPort.readRequest || io.MemoryIOPort.writeRequest) && DACK =/= 1.U && stallEnable

  /* --- Syscon --- */
  when(readAddress(31, 12) === 0x0000_1L.U && io.MemoryIOPort.readRequest) {
    io.SysconPort.Address := readAddress(11, 0)
    dataOut               := io.SysconPort.DataOut
  }

  /* --- UART0 --- */
  when(readAddress(31, 12) === 0x3000_0L.U || writeAddress(31, 12) === 0x3000_0L.U) {
    // Reads
    when(io.MemoryIOPort.readRequest) {
      when(readAddress(7, 0) === 0x04.U) {
        /* RX */
        when(io.UART0Port.rxQueue.valid) {
          io.UART0Port.rxQueue.ready := true.B
          dataOut                    := io.UART0Port.rxQueue.bits
        }
      }
        /* Status */
        .elsewhen(readAddress(7, 0) === 0x0c.U) {
          dataOut := Cat(io.UART0Port.txFull, io.UART0Port.rxFull, io.UART0Port.txEmpty, io.UART0Port.rxEmpty)
        }
        /* Invalid */
        .otherwise(dataOut := 0.U)
    }
    // Writes (TX)
    when(io.MemoryIOPort.writeRequest) {
      when(writeAddress(7, 0) === 0x00.U) {
        /* TX */
        io.UART0Port.txQueue.valid := true.B
        io.UART0Port.txQueue.bits  := io.MemoryIOPort.writeData(7, 0)
      }
        /* clock divisor */
        .elsewhen(writeAddress(7, 0) === 0x10.U) {
          io.UART0Port.clockDivisor.valid := true.B
          io.UART0Port.clockDivisor.bits  := io.MemoryIOPort.writeData(7, 0)
        }
    }
  }

  // GPIO0
  when(readAddress(31, 12) === 0x3000_1L.U || writeAddress(31, 12) === 0x3000_1L.U) {
    // Reads
    when(io.MemoryIOPort.readRequest) {
      // -- Direction
      when(readAddress(7, 0) === 0x00.U) {
        dataOut := io.GPIO0Port.directionOut
      }
        // -- Value
        .elsewhen(readAddress(7, 0) === 0x04.U) {
          dataOut := io.GPIO0Port.valueOut
        }
        .otherwise(dataOut := 0.U)
    }
    // Writes
    when(io.MemoryIOPort.writeRequest) {
      // -- Direction
      when(writeAddress(7, 0) === 0x00.U) {
        io.GPIO0Port.writeDirection := io.MemoryIOPort.writeRequest
      }
        // -- Value
        .elsewhen(writeAddress(7, 0) === 0x04.U) {
          io.GPIO0Port.writeValue := io.MemoryIOPort.writeRequest
        }
      io.GPIO0Port.dataIn := io.MemoryIOPort.writeData
    }

  }

  /* --- PWM0 --- */
  when(readAddress(31, 12) === 0x3000_2L.U || writeAddress(31, 12) === 0x3000_2L.U) {
    dataOut := 0.U
  }

  /* --- Timer0 --- */
  when(readAddress(31, 12) === 0x3000_3L.U || writeAddress(31, 12) === 0x3000_3L.U) {
    when(io.MemoryIOPort.writeRequest) {
      io.Timer0Port.writeEnable := io.MemoryIOPort.writeRequest
      io.Timer0Port.dataIn      := io.MemoryIOPort.writeData
    }
    when(io.MemoryIOPort.readRequest) {
      dataOut := io.Timer0Port.dataOut
    }
  }

  /* --- Data Memory --- */
  when(readAddress(31, 28) === 0x8.U || writeAddress(31, 28) === 0x8.U) {
    // Stall core for 1 cycle
    stallLatency               := 1.U
    stallEnable                := true.B
    io.DataMemPort.readAddress := Cat(Fill(4, 0.U), readAddress(27, 0))

    switch(io.MemoryIOPort.dataSize) {
      is(3.U)(dataOut := io.DataMemPort.readData) // Read word
      is(2.U) { // Read halfword
        switch(io.DataMemPort.readAddress(1).asUInt) {
          is(1.U)(dataOut := Cat(Fill(16, 0.U), io.DataMemPort.readData(31, 16).asUInt)) // Read half word 1
          is(0.U)(dataOut := Cat(Fill(16, 0.U), io.DataMemPort.readData(15, 0).asUInt))  // Read half word 0
        }
      }
      is(1.U) { // Write byte
        switch(io.DataMemPort.readAddress(1, 0)) {
          is(3.U)(dataOut := Cat(Fill(24, 0.U), io.DataMemPort.readData(31, 24).asUInt)) // Read byte 3
          is(2.U)(dataOut := Cat(Fill(24, 0.U), io.DataMemPort.readData(23, 16).asUInt)) // Read byte 2
          is(1.U)(dataOut := Cat(Fill(24, 0.U), io.DataMemPort.readData(15, 8).asUInt))  // Read byte 1
          is(0.U)(dataOut := Cat(Fill(24, 0.U), io.DataMemPort.readData(7, 0).asUInt))   // Read byte 0
        }
      }
    }

    when(io.MemoryIOPort.writeRequest) {
      when(!io.stall) {
        val dataToWrite = WireDefault(0.U(bitWidth.W))
        val writeMask   = WireDefault(0.U(4.W))

        io.DataMemPort.writeAddress := Cat(Fill(4, 0.U), writeAddress(27, 0))
        io.DataMemPort.writeEnable  := io.MemoryIOPort.writeRequest

        switch(io.MemoryIOPort.dataSize) {
          is(3.U) { // Write word
            dataToWrite := io.MemoryIOPort.writeData
            writeMask   := "b1111".U

                writeMask   := "b0011".U
              }
            }
          }
          is(1.U) { // Write byte
            switch(io.DataMemPort.writeAddress(1, 0)) {
              is(3.U) { // Write byte 3
                dataToWrite := Cat(io.MemoryIOPort.writeData(7, 0).asUInt, Fill(24, 0.U))
                writeMask   := "b1000".U
              }
              is(2.U) { // Write byte 2
                dataToWrite := Cat(Fill(8, 0.U), io.MemoryIOPort.writeData(7, 0).asUInt, Fill(16, 0.U))
                writeMask   := "b0100".U
              }
              is(1.U) { // Write byte 2
                dataToWrite := Cat(Fill(16, 0.U), io.MemoryIOPort.writeData(7, 0).asUInt, Fill(8, 0.U))
                writeMask   := "b0010".U
              }
              is(0.U) { // Write byte 0
                dataToWrite := Cat(Fill(24, 0.U), io.MemoryIOPort.writeData(7, 0).asUInt)
                writeMask   := "b0001".U
              }
            }
          }
        }

        val dataIn = Cat(
          Mux(writeMask(3), dataToWrite(3 * 8 + 7, 3 * 8), io.DataMemPort.readData(3 * 8 + 7, 3 * 8)),
          Mux(writeMask(2), dataToWrite(2 * 8 + 7, 2 * 8), io.DataMemPort.readData(2 * 8 + 7, 2 * 8)),
          Mux(writeMask(1), dataToWrite(1 * 8 + 7, 1 * 8), io.DataMemPort.readData(1 * 8 + 7, 1 * 8)),
          Mux(writeMask(0), dataToWrite(0 * 8 + 7, 0 * 8), io.DataMemPort.readData(0 * 8 + 7, 0 * 8)),
        )
        io.DataMemPort.writeData := dataIn
        dataOut                  := dataIn
      }
    }
  }

  io.MemoryIOPort.readData := dataOut
}

          }
          is(2.U) { // Write halfword
            switch(io.DataMemPort.writeAddress(1).asUInt) {
              is(1.U) { // Write half word 1
                dataToWrite := Cat(io.MemoryIOPort.writeData(15, 0).asUInt, Fill(16, 0.U))


package chiselv

import scala.io.Source

import chisel3._
import chisel3.util.HasBlackBoxInline

class PLL0(board: String) extends BlackBox with HasBlackBoxInline {
  val io = IO(new Bundle {
    val clki = Input(Clock())
    val clko = Output(Clock());

    val lock = Output(Clock())
  })

  val filename = "pll_" + board + ".v"
  val verilog  = Source.fromResource(filename).getLines().mkString("\n")


package chiselv

import chisel3._

class PCPort(bitWidth: Int = 32) extends Bundle {
  val dataIn      = Input(UInt(bitWidth.W))

  val writeAdd    = Input(Bool()) // 1 => Add dataIn to PC, 0 => Set dataIn to PC
}

class ProgramCounter(regWidth: Int = 32, entryPoint: Long = 0) extends Module {
  val io = IO(new PCPort(regWidth))

  val pc = RegInit(entryPoint.U(regWidth.W))
  when(io.writeEnable) {
    pc := Mux(io.writeAdd, (pc.asSInt + io.dataIn.asSInt).asUInt, io.dataIn)
  }
  io.PC4 := pc + 4.U
  io.PC  := pc
}

  val PC          = Output(UInt(bitWidth.W))
  val PC4         = Output(UInt(bitWidth.W))


package chiselv

import chisel3._
import chisel3.util.{is, switch}
import circt.stage.ChiselStage
import mainargs.{Leftover, ParserForMethods, arg, main}

// Chisel Bundle implementation of RISC-V Formal Interface (RVFI)
class RVFIPort extends Bundle {
  val valid     = Output(Bool())
  val order     = Output(UInt(64.W))
  val insn      = Output(UInt(32.W))
  val trap      = Output(Bool())
  val halt      = Output(Bool())
  val intr      = Output(Bool())
  val ixl       = Output(UInt(2.W))
  val mode      = Output(UInt(2.W))
  val rs1_addr  = Output(UInt(5.W))
  val rs1_rdata = Output(UInt(32.W))
  val rs2_addr  = Output(UInt(5.W))
  val rs2_rdata = Output(UInt(32.W))
  val rd_addr   = Output(UInt(5.W))
  val rd_wdata  = Output(UInt(32.W))
  val pc_rdata  = Output(UInt(32.W))
  val pc_wdata  = Output(UInt(32.W))
  val mem_addr  = Output(UInt(32.W))
  val mem_rmask = Output(UInt(4.W))
  val mem_wmask = Output(UInt(4.W))
  val mem_rdata = Output(UInt(32.W))
  val mem_wdata = Output(UInt(32.W))
}

class RVFICPUWrapper(
    cpuFrequency:          Int = 50000000,
    bitWidth:              Int = 32,
    instructionMemorySize: Int = 64 * 1024,
    dataMemorySize:        Int = 64 * 1024,
  ) extends CPUSingleCycle(
      cpuFrequency          = cpuFrequency,
      entryPoint            = 0x0,
      bitWidth              = bitWidth,
      instructionMemorySize = instructionMemorySize,
      dataMemorySize        = dataMemorySize,
      numGPIO               = 0,
    ) {
  val rvfi = IO(new RVFIPort) // RVFI interface for RISCV-Formal

  // RVFI Interface
  val rvfi_valid = RegInit(false.B)
  val rvfi_order = RegInit(0.U(64.W))
  val rvfi_insn  = RegInit(0.U(32.W))

  val rvfi_halt = RegInit(false.B)

  val rvfi_intr = RegInit(false.B)
  val rvfi_mode = RegInit(3.U(2.W))

  val rvfi_mem_size_mask = WireDefault(0.U(2.W))
  switch(memoryIOManager.io.DataMemPort.dataSize) {
    is(1.U)(rvfi_mem_size_mask := 1.U)
    is(2.U)(rvfi_mem_size_mask := 3.U)
    is(3.U)(rvfi_mem_size_mask := 15.U)
  }

  // Connect RVFI interface outputs
  rvfi_valid := !reset.asBool && !stall
  when(rvfi_valid) {
    rvfi_order := rvfi_order + 1.U
  }
  rvfi.valid := rvfi_valid
  rvfi.order := rvfi_order

  rvfi.rs1_rdata := registerBank.io.rs1
  rvfi.rs2_addr  := registerBank.io.rs2_addr
  rvfi.rs2_rdata := registerBank.io.rs2
  rvfi.rd_addr   := registerBank.io.regwr_addr
  rvfi.rd_wdata  := registerBank.io.regwr_data

  rvfi.pc_rdata := PC.io.PC
  rvfi.pc_wdata := Mux(
    PC.io.writeAdd,
    (PC.io.PC.asSInt + PC.io.dataIn.asSInt).asUInt,
    PC.io.PC4,
  )

  rvfi.mem_addr  := Mux(decoder.io.is_load || decoder.io.is_store, ALU.io.x, 0.U)
  rvfi.mem_rdata := Mux(decoder.io.is_load, memoryIOManager.io.DataMemPort.readData, 0.U)
  rvfi.mem_rmask := Mux(decoder.io.is_load, rvfi_mem_size_mask, 0.U)

  rvfi.mem_wdata := Mux(decoder.io.is_store, memoryIOManager.io.DataMemPort.writeData, 0.U)
  rvfi.mem_wmask := Mux(decoder.io.is_store, rvfi_mem_size_mask, 0.U)

  rvfi.mode := rvfi_mode
  rvfi.trap := false.B
  when(decoder.io.inst === Instruction.ERR_INST) {
    rvfi.trap := true.B
  }

  rvfi.halt := rvfi_halt
  rvfi.intr := rvfi_intr
  rvfi.ixl  := 1.U
}

// This is the Topmodule used in RISCV-Formal
class RVFI(
    bitWidth: Int = 32
  ) extends Module {
  val io = IO(new Bundle {
    val imem_addr  = Output(UInt(bitWidth.W))
    val imem_ready = Input(Bool())
    val imem_rdata = Input(UInt(bitWidth.W))

    val dmem_rdata = Input(UInt(bitWidth.W))
    val dmem_raddr = Output(UInt(bitWidth.W))
    val dmem_waddr = Output(UInt(bitWidth.W))
    val dmem_wdata = Output(UInt(bitWidth.W))
    val dmem_wen   = Output(Bool())
  })
  val rvfi = IO(new RVFIPort) // RVFI interface for RISCV-Formal

  // Instantiate the wrapped CPU with RVFI interface
  val CPU = Module(
    new RVFICPUWrapper(bitWidth)
  )

  // Initialize unused IO
  CPU.io.UART0Port.rxQueue.bits  := 0.U
  CPU.io.UART0Port.rxEmpty       := true.B
  CPU.io.UART0Port.txQueue.ready := true.B
  CPU.io.UART0Port.txFull        := false.B
  CPU.io.UART0Port.txEmpty       := true.B
  CPU.io.UART0Port.rxFull        := false.B
  CPU.io.UART0Port.rxQueue.valid := false.B
  CPU.io.SysconPort.DataOut      := 0.U

  // Connect RVFI port
  rvfi <> CPU.rvfi

  // Connect instruction memory
  io.imem_addr                       := CPU.io.instructionMemPort.readAddr
  CPU.io.instructionMemPort.readData := io.imem_rdata
  CPU.io.instructionMemPort.ready    := io.imem_ready

  // Connect data memory
  io.dmem_waddr := CPU.io.dataMemPort.writeAddress
  io.dmem_wdata := CPU.io.dataMemPort.writeData
  io.dmem_wen   := CPU.io.dataMemPort.writeEnable

  io.dmem_raddr               := CPU.io.dataMemPort.readAddress
  CPU.io.dataMemPort.readData := io.dmem_rdata
}

object RVFI {
  @main
  def run(@arg(short = 'c', doc = "Chisel arguments") chiselArgs: Leftover[String]) =
    // Generate SystemVerilog
    ChiselStage.emitSystemVerilogFile(
      new RVFI,
      chiselArgs.value.toArray,
      Array(
        // Removes debug information from the generated Verilog
        "--strip-debug-info",
        // Disables reg and memory randomization on initialization
        "--disable-all-randomization",
        // Creates memories with write masks in a single reg. Ref. https://github.com/llvm/circt/pull/4275
        "--lower-memories",
        // Avoids "unexpected TOK_AUTOMATIC" errors in Yosys. Ref. https://github.com/llvm/circt/issues/4751
        "--lowering-options=disallowLocalVariables,disallowPackedArrays",
        // Splits the generated Verilog into multiple files
        // "--split-verilog",
        // Generates the Verilog files in the specified directory
        "-o=./generated/Toplevel_RVFI.sv",
      ),
    )

  def main(args: Array[String]): Unit =
    ParserForMethods(this).runOrExit(args.toIndexedSeq)
}


package chiselv

import chisel3._
import chisel3.util.log2Ceil

class RegisterBankPort(bitWidth: Int = 32) extends Bundle {
  val rs1         = Output(UInt(bitWidth.W))
  val rs2         = Output(UInt(bitWidth.W))
  val rs1_addr    = Input(UInt(log2Ceil(bitWidth).W))
  val rs2_addr    = Input(UInt(log2Ceil(bitWidth).W))

  val stall       = Input(Bool()) // >1 => Stall, 0 => Run
}

class RegisterBank(numRegs: Int = 32, regWidth: Int = 32) extends Module {
  val io = IO(new RegisterBankPort(regWidth))

  val regs = RegInit(VecInit(Seq.fill(numRegs)(0.U(regWidth.W))))
  regs(0) := 0.U // Register x0 is always 0

  io.rs1 := regs(io.rs1_addr)
  io.rs2 := regs(io.rs2_addr)
  when(io.writeEnable && io.regwr_addr =/= 0.U && !io.stall) {
    regs(io.regwr_addr) := io.regwr_data
  }
}

  val regwr_addr  = Input(UInt(log2Ceil(bitWidth).W))
  val regwr_data  = Input(UInt(bitWidth.W))


package chiselv

import chisel3._
import chisel3.experimental.Analog

class SOC(
    cpuFrequency:          Int,
    entryPoint:            Long,
    bitWidth:              Int = 32,
    instructionMemorySize: Int = 1 * 1024,
    dataMemorySize:        Int = 1 * 1024,
    memoryFile:            String = "",
    ramFile:               String = "",
    numGPIO:               Int = 8,
  ) extends Module {
  val io = IO(new Bundle {
    val led0            = Output(Bool())     // LED 0 is the heartbeat
    val GPIO0External   = Analog(numGPIO.W)  // GPIO external port
    val UART0SerialPort = new UARTSerialPort // UART0 serial port
  })

  // Heartbeat LED - Keep on if reached an error
  val blink = Module(new Blinky(cpuFrequency))
  io.led0 := blink.io.led0

  // Instantiate and initialize the Instruction memory
  val instructionMemory = Module(new InstructionMemory(bitWidth, instructionMemorySize, memoryFile))
  instructionMemory.io.readAddr := 0.U

  // Instantiate and initialize the Data memory
  val dataMemory = Module(new DualPortRAM(bitWidth, dataMemorySize, ramFile))
  dataMemory.io.writeEnable  := false.B
  dataMemory.io.writeData    := 0.U
  dataMemory.io.readAddress  := 0.U
  dataMemory.io.writeAddress := 0.U
  dataMemory.io.dataSize     := 0.U
  dataMemory.io.writeMask    := 0.U

  // Instantiate and connect the UART
  val fifoLength  = 128
  val rxOverclock = 16
  val UART0       = Module(new Uart(fifoLength, rxOverclock))
  UART0.io.serialPort <> io.UART0SerialPort

  // Instantiate the Syscon Module
  val syscon = Module(new Syscon(32, cpuFrequency, numGPIO, entryPoint, instructionMemorySize, dataMemorySize))

  // Instantiate our core
  val core = Module(
    new CPUSingleCycle(cpuFrequency, entryPoint, bitWidth, instructionMemorySize, dataMemorySize, numGPIO)
  )

  // Connect the core to the devices
  core.io.instructionMemPort <> instructionMemory.io
  core.io.dataMemPort <> dataMemory.io
  core.io.UART0Port <> UART0.io.dataPort
  core.io.SysconPort <> syscon.io

  if (numGPIO > 0) {
    core.io.GPIO0External <> io.GPIO0External
  }


package chiselv

import chisel3._
import chisel3.util.{is, switch}

    clockFreq: Long,
    numGPIO0:  Int,
    bootAddr:  Long,
    romSize:   Int,
    ramSize:   Int,
  ) extends Module {
  val io = IO(new SysconPort(bitWidth))

  val dataOut = WireDefault(0.U(bitWidth.W))

  switch(io.Address) {
    is(0x0L.U)(dataOut := 0xbaad_cafeL.U)
    // Clock frequency - (0x0000_1008)
    is(0x8L.U)(dataOut := clockFreq.asUInt)
    // Has UART0 - (0x0000_1010)
    is(0x10L.U)(dataOut := 1.U)
    // Has GPIO0 - (0x0000_1018)
    is(0x18L.U)(dataOut := 1.U)
    // Has PWM0 - (0x0000_1020)
    is(0x20L.U)(dataOut := 0.U)
    // Has Timer0 - (0x0000_1024)
    is(0x24L.U)(dataOut := 1.U)
    // Num GPIOs in GPIO0 - (0x0000_1028)
    is(0x28L.U)(dataOut := numGPIO0.asUInt)
    // Boot io.address - (0x0000_102c)
    is(0x2cL.U)(dataOut := bootAddr.asUInt)
    // ROM Size - (0x0000_1030)
    is(0x30L.U)(dataOut := romSize.asUInt)
    // RAM Size - (0x0000_1034)
    is(0x34L.U)(dataOut := ramSize.asUInt)
  }

  io.DataOut := dataOut
}

class SysconPort(val bitWidth: Int) extends Bundle {
  val Address = Input(UInt(12.W))
  val DataOut = Output(UInt(bitWidth.W))
}


package chiselv

import chisel3._
import chisel3.util.Counter

class TimerPort(bitWidth: Int = 32) extends Bundle {
  val dataIn      = Input(UInt(bitWidth.W))
  val dataOut     = Output(UInt(bitWidth.W))
  val writeEnable = Input(Bool())
  val stall       = Output(Bool()) // >1 => Stall, 0 => Run
}

class Timer(bitWidth: Int = 32, cpuFrequency: Int) extends Module {
  val io = IO(new TimerPort(bitWidth))

  val counter = RegInit(0.U(bitWidth.W))

  when(io.writeEnable) {
    counter := io.dataIn
  }.otherwise {
    // Count up in milliseconds
    val (_, counterWrap) = Counter(true.B, cpuFrequency / 1000)

    when(counterWrap) {
      counter := counter + 1.U
    }


package chiselv

import chisel3._
import chisel3.experimental.Analog
import circt.stage.ChiselStage
import mainargs.{Leftover, ParserForMethods, arg, main}

// Project Top level
class Toplevel(
    board:        String,
    invReset:     Boolean = true,
    cpuFrequency: Int,
  ) extends Module {
  val io = FlatIO(new Bundle {
    val led0  = Output(Bool())     // LED 0 is the heartbeat
    val UART0 = new UARTSerialPort // UART 0
    val GPIO0 = Analog(8.W)        // GPIO 0
  })

  // Instantiate PLL module based on board
  val pll = Module(new PLL0(board))
  pll.io.clki := clock
  // Define if reset should be inverted based on board switch

  // Instantiate the Core connecting using the PLL clock
  withClockAndReset(pll.io.clko, customReset) {
    val bitWidth              = 32
    val instructionMemorySize = 64 * 1024
    val dataMemorySize        = 64 * 1024
    val numGPIO               = 8

    val SOC =
      Module(
        new SOC(
          cpuFrequency          = cpuFrequency,
          entryPoint            = 0x00000000,
          bitWidth              = bitWidth,
          instructionMemorySize = instructionMemorySize,
          dataMemorySize        = dataMemorySize,
          memoryFile            = "progload.mem",
          ramFile               = "progload-RAM.mem",
          numGPIO               = numGPIO,
        )
      )

    // Connect IO
    io.led0 <> SOC.io.led0
    io.GPIO0 <> SOC.io.GPIO0External
    io.UART0 <> SOC.io.UART0SerialPort
  }
}

// The Main object extending App to generate the Verilog code.
object Toplevel {
  @main
  def run(
      // Parse command line arguments and extract required parameters
      @arg(short = 'b', doc = "FPGA Board to use") board:                 String = "bypass",
      @arg(short = 'r', doc = "FPGA Board have inverted reset") invreset: Boolean = false,
      @arg(short = 'f', doc = "CPU Frequency to run core") cpufreq:       Int = 50000000,
      @arg(short = 'c', doc = "Chisel arguments") chiselArgs:             Leftover[String],
    ) =
    // Generate SystemVerilog
    ChiselStage.emitSystemVerilogFile(
      new Toplevel(board, invreset, cpufreq),
      chiselArgs.value.toArray,
      Array(
        // Removes debug information from the generated Verilog
        "--strip-debug-info",
        // Disables reg and memory randomization on initialization
        "--disable-all-randomization",
        // Creates memories with write masks in a single reg. Ref. https://github.com/llvm/circt/pull/4275
        "--lower-memories",
        // Avoids "unexpected TOK_AUTOMATIC" errors in Yosys. Ref. https://github.com/llvm/circt/issues/4751
        "--lowering-options=disallowLocalVariables,disallowPackedArrays",
      ),
    )

  def main(args: Array[String]): Unit =
    ParserForMethods(this).runOrExit(args.toIndexedSeq)
}

val customReset = if (invReset) ~reset.asBool else reset


package chiselv

import chisel3._
import chisel3.util._

/*
 * A simple TTL serial module. Idle is high. Start bits are low, stop bits
 * are high and we send each byte out LSB first. We only implement 8n1,
 * ie 1 start bit, no parity and 1 stop bit. There is no flow control, so
 * we might overflow the RX fifo if the rxQueue consumer can't keep up.
 *
 * We oversample RX. clockDivisor is set as follows:
 *
 * clockDivisor = (clock_freq / (baudrate * rxOverclock)) - 1
 *
 * When programming the divider, software currently assumes we overclock 16x.
 *
 * Also note that if the input clock frequency is low enough then the errors
 * introduced by oversampling might be significant. Eg at 10MHz, 115200 baud,
 * 16x oversampling we have almost 8% error and the UART fails to work.
 *
 * This file has been created by Anton Blanchard on Chiselwatt repository at:
 * https://github.com/antonblanchard/chiselwatt
 */

class UARTSerialPort extends Bundle {
  val rx = Input(UInt(1.W))
  val tx = Output(UInt(1.W))
}

class UARTPort extends Bundle {
  val rxQueue      = Decoupled(UInt(8.W))
  val txQueue      = Flipped(Decoupled(UInt(8.W)))
  val rxEmpty      = Output(Bool())
  val txEmpty      = Output(Bool())
  val rxFull       = Output(Bool())
  val txFull       = Output(Bool())
  val clockDivisor = Flipped(Valid(UInt(8.W)))
}

class Uart(val fifoLength: Int, val rxOverclock: Int) extends Module {
  val io = IO(new Bundle {
    val serialPort = new UARTSerialPort
    val dataPort   = new UARTPort
  })

  require(isPow2(rxOverclock))

  val sampleClk        = RegInit(0.U(1.W))
  val sampleClkCounter = RegInit(0.U(8.W))
  val clockDivisor     = RegInit(0.U(8.W))

  when(io.dataPort.clockDivisor.valid) {
    clockDivisor := io.dataPort.clockDivisor.bits
  }

  val txQueue = Module(new Queue(UInt(8.W), fifoLength))
  val rxQueue = Module(new Queue(UInt(8.W), fifoLength))

  io.dataPort.rxEmpty := rxQueue.io.count === 0.U
  io.dataPort.txEmpty := txQueue.io.count === 0.U
  io.dataPort.rxFull  := rxQueue.io.count === fifoLength.U
  io.dataPort.txFull  := txQueue.io.count === fifoLength.U

  val uartEnabled = clockDivisor.orR

  when(uartEnabled) {
    when(sampleClkCounter === clockDivisor) {
      sampleClk        := 1.U
      sampleClkCounter := 0.U
    }.otherwise {
      sampleClk        := 0.U
      sampleClkCounter := sampleClkCounter + 1.U
    }
  }.otherwise {
    sampleClk        := 0.U
    sampleClkCounter := 0.U
  }

  /*
   * Our TX clock needs to match our baud rate. This means we need to
   * further divide sampleClk by rxOverclock.
   */
  val (txCounterValue, txCounterWrap) = Counter(sampleClk === 1.U, rxOverclock)
  val txClk                           = txCounterWrap

  /* TX */
  val sTxIdle :: sTxTx :: sTxStop :: Nil = Enum(3)
  val txState                            = RegInit(sTxIdle)
  val txByte                             = Reg(UInt(8.W))
  val txByteBit                          = Reg(UInt(3.W))
  /* RS232 idle state is high */
  val tx = RegInit(1.U(1.W))

  txQueue.io.deq.ready := false.B

  /* We run the tx state machine off the txClk */
  when(txClk === 1.U) {
    switch(txState) {
      is(sTxIdle) {
        /* Does the FIFO contain data? */
        when(txQueue.io.deq.valid) {
          txQueue.io.deq.ready := true.B
          txByte               := txQueue.io.deq.bits
          txByteBit            := 0.U
          txState              := sTxTx
          /* Send start bit */
          tx := 0.U
        }.otherwise {
          tx := 1.U
        }
      }

      is(sTxTx) {
        when(txByteBit === 7.U) {
          txState := sTxStop
        }
        tx        := txByte(txByteBit)
        txByteBit := txByteBit + 1.U
      }

      is(sTxStop) {
        txState := sTxIdle
        /* Send stop bit */
        tx := 1.U
      }
    }
  }

  io.serialPort.tx := tx

  /* RX */
  val sRxIdle :: sRxStart :: sRxRx :: sRxStop :: sRxError1 :: sRxError2 :: Nil =
    Enum(6)
  val rxState   = RegInit(sRxIdle)
  val rxCounter = RegInit(0.U(log2Ceil(rxOverclock).W))
  val rxByte    = RegInit(VecInit(Seq.fill(8)(0.U(1.W))))
  val rxByteBit = RegInit(0.U(3.W))

  rxQueue.io.enq.bits  := 0.U
  rxQueue.io.enq.valid := false.B

  /* Synchronize the input to avoid any metastability issues */
  val rx = RegNext(RegNext(RegNext(io.serialPort.rx)))

  when(sampleClk === 1.U) {
    switch(rxState) {
      is(sRxIdle) {
        when(rx === 0.U) {
          rxState   := sRxStart
          rxCounter := 1.U
          rxByte    := Seq.fill(8)(0.U)
          rxByteBit := 0.U
        }
      }

      is(sRxStart) {
        rxCounter := rxCounter + 1.U
        /* We should be at the middle of the start bit */
        when(rxCounter === (rxOverclock / 2).U) {
          rxCounter := 1.U
          rxState   := sRxRx
        }
      }

      is(sRxRx) {
        rxCounter := rxCounter + 1.U
        when(rxCounter === 0.U) {
          when(rxByteBit === 7.U) {
            rxState := sRxStop
          }
          rxByte(rxByteBit) := rx
          rxByteBit         := rxByteBit + 1.U
        }
      }

      is(sRxStop) {
        rxCounter := rxCounter + 1.U
        when(rxCounter === 0.U) {
          /* Ensure the stop bit is high */

        }
      }

      /*
       * We might not have synchronised on a start bit. Delay 2 bits on
       * each error so we eventually line up with the start bit.
       */
      is(sRxError1) {
        /* Delay one bit */
        rxCounter := rxCounter + 1.U
        when(rxCounter === 0.U) {
          rxState := sRxError2
        }
      }

      is(sRxError2) {
        /* Delay another bit */
        rxCounter := rxCounter + 1.U
        when(rxCounter === 0.U) {
          rxState := sRxIdle
        }
      }
    }
  }

  rxQueue.io.deq <> io.dataPort.rxQueue
  txQueue.io.enq <> io.dataPort.txQueue
}

          when(rx === 1.U) {
            /* We might overflow the queue if we can't keep up */
            rxQueue.io.enq.bits  := rxByte.reverse.reduce(_ ## _)
            rxQueue.io.enq.valid := true.B
            rxState              := sRxIdle


package chiselv

import chisel3._
import chiseltest._
import com.carlosedp.riscvassembler.ObjectUtils.NumericManipulation
import org.scalatest._

import Instruction._
import flatspec._
import matchers._

class ALUSpec extends AnyFlatSpec with ChiselScalatestTester with should.Matchers {
  val one        = BigInt(1)
  val max        = (one << 32) - one
  val min_signed = one << 32 - 1
  val max_signed = (one << 32 - 1) - one
  val cases =
    Array[BigInt](1, 2, 4, 123, -1, -2, -4, 0, 0x7fffffffL, 0x80000000L, max, min_signed, max_signed) ++ Seq.fill(10)(
      BigInt(scala.util.Random.nextInt())
    )

  it should "ADD" in {
    testCycle(ADD)
  }
  it should "ADDI" in {
    testCycle(ADDI)
  }
  it should "SUB" in {
    testCycle(SUB)
  }
  it should "AND" in {
    testCycle(AND)
  }
  it should "ANDI" in {
    testCycle(ANDI)
  }
  it should "OR" in {
    testCycle(OR)
  }
  it should "ORI" in {
    testCycle(ORI)
  }
  it should "XOR" in {
    testCycle(XOR)
  }
  it should "XORI" in {
    testCycle(XORI)
  }
  it should "SRA" in {
    testCycle(SRA)
  }
  it should "SRAI" in {
    testCycle(SRAI)
  }
  it should "SRL" in {
    testCycle(SRL)
  }
  it should "SRLI" in {
    testCycle(SRLI)
  }
  it should "SLL" in {
    testCycle(SLL)
  }
  it should "SLLI" in {
    testCycle(SLLI)
  }
  it should "SLT" in {
    testCycle(SLT)
  }
  it should "SLTI" in {
    testCycle(SLTI)
  }
  it should "SLTU" in {
    testCycle(SLTU)
  }
  it should "SLTIU" in {
    testCycle(SLTIU)
  }
  it should "EQ" in {
    testCycle(EQ)
  }
  it should "NEQ" in {
    testCycle(NEQ)
  }
  it should "GT" in {
    testCycle(GTE)
  }
  it should "GTU" in {
    testCycle(GTEU)
  }
  // --------------------- Test Helpers ---------------------
  def aluHelper(
      a:  BigInt,
      b:  BigInt,
      op: Type,
    ): BigInt =
    op match {
      case ADD | ADDI   => (a + b).to32Bit
      case SUB          => a - b
      case AND | ANDI   => a & b
      case OR | ORI     => a | b
      case XOR | XORI   => a ^ b
      case SRA | SRAI   => a.toInt >> (b.toInt & 0x1f)
      case SRL | SRLI   => a.toInt >>> b.toInt
      case SLL | SLLI   => a.toInt << b.toInt
      case SLT | SLTI   => if (a.toInt < b.toInt) 1 else 0
      case SLTU | SLTIU => if (a.to32Bit < b.to32Bit) 1 else 0
      case EQ           => if (a.to32Bit == b.to32Bit) 1 else 0
      case NEQ          => if (a.to32Bit != b.to32Bit) 1 else 0

      case GTEU         => if (a.to32Bit >= b.to32Bit) 1 else 0
      case _            => 0 // Never happens
    }

  def testDut(
      i:   BigInt,
      j:   BigInt,
      out: BigInt,
      op:  Type,
      dut: ALU,
    ) = {
    // print(s"Inputs: $i $op $j | Test result should be ${aluHelper(i, j, op)} | ")
    dut.io.inst.poke(op)
    dut.io.a.poke(i.to32Bit)
    dut.io.b.poke(j.to32Bit)
    dut.clock.step()
    dut.io.x.peekInt() should be(out)
  }
  def testCycle(
      op: Type
    ) =
    test(new ALU) { c =>
      cases.foreach { i =>
        cases.foreach { j =>
          testDut(i, j, aluHelper(i, j, op).to32Bit, op, c)
        }
      }
    }

  def toUInt(
      i: BigInt
    ) = i.to32Bit.asUInt(32.W)
}

case GTE          => if (a.toInt >= b.toInt) 1 else 0


package chiselv

import chiseltest._
import chiseltest.experimental._
import org.scalatest._

import flatspec._
import matchers._

// Extend the SOC module to add the observer for sub-module signals
class SOCWrapperDemo(
    cpuFrequency:          Int,
    bitWidth:              Int,
    instructionMemorySize: Int,
    memorySize:            Int,
    memoryFile:            String,
    ramFile:               String,
    numGPIO:               Int,
    // baudRate: Int,
  ) extends SOC(
      cpuFrequency,
      entryPoint = 0,
      bitWidth,
      instructionMemorySize,
      memorySize,
      memoryFile,
      ramFile,
      numGPIO,
    ) {
  val registers = expose(core.registerBank.regs)
  val pc        = expose(core.PC.pc)
}

class CPUDemoAppsSpec extends AnyFlatSpec with ChiselScalatestTester with should.Matchers {
  val cpuFrequency          = 10000
  val bitWidth              = 32
  val instructionMemorySize = 64 * 1024
  val memorySize            = 64 * 1024

  def defaultDut(
      memoryfile: String,
      ramFile:    String = "",
    ) =
    test(
      new SOCWrapperDemo(
        cpuFrequency,
        bitWidth,
        instructionMemorySize,
        memorySize,
        memoryfile,
        ramFile,
        8,
        // 1200,
      )
    )
      .withAnnotations(
        Seq(
          WriteVcdAnnotation,
          VerilatorBackendAnnotation,
        )
      )

  behavior of "GPIODemo"
  it should "lit a LED connected to GPIO from gcc program" in {
    val filename = "./gcc/simpleLED/main.mem"
    defaultDut(filename) { c =>
      c.clock.setTimeout(10000)
      c.clock.step(1) // Step from initial PC
      while (c.pc.peekInt() != 0x0)
        c.clock.step(1)
    }
  }

  it should "blink LED connected to GPIO0 from gcc program" in {
    val filename = "./gcc/blinkLED/main-rom.mem"

  it should "print to UART0 from gcc program" in {
    val filename     = "./gcc/helloUART/main-rom.mem"
    val filename_ram = "./gcc/helloUART/main-ram.mem"
    defaultDut(filename, filename_ram) { c =>
      c.clock.setTimeout(30000)
      c.clock.step(25000)
      // while (c.pc.peekInt() != 0x90) { // 0x90 is the address of _halt
      //   c.clock.step(1)
      // }
    }
  }
}

    defaultDut(filename) { c =>
      c.clock.setTimeout(3000)
      c.clock.step(2000)
    }


package chiselv

import chiseltest._
import chiseltest.experimental._
import org.scalatest._

import flatspec._
import matchers._

// Extend the Control module to add the observer for sub-module signals
class CPUSingleCycleWrapperApps(memoryFile: String) extends SOC(
      cpuFrequency          = 25000000,
      entryPoint            = 0,
      bitWidth              = 32,
      instructionMemorySize = 1 * 1024,
      dataMemorySize        = 1 * 1024,
      memoryFile            = memoryFile,
      numGPIO               = 0,
    ) {
  val registers    = expose(core.registerBank.regs)
  val pc           = expose(core.PC.pc)
  val memWriteAddr = expose(core.memoryIOManager.io.MemoryIOPort.writeAddr)
  val memWriteData = expose(core.memoryIOManager.io.MemoryIOPort.writeData)
  val memReadAddr  = expose(core.memoryIOManager.io.MemoryIOPort.readAddr)
  val memReadData  = expose(core.memoryIOManager.io.MemoryIOPort.readData)
}

class CPUSingleCycleAppsSpec extends AnyFlatSpec with ChiselScalatestTester with should.Matchers {
  val writeLatency = 2
  val readLatency  = 1

  def defaultDut(memoryfile: String) =
    test(new CPUSingleCycleWrapperApps(memoryFile = memoryfile))
      .withAnnotations(
        Seq(
          WriteVcdAnnotation
        )
      )

  it should "load instructions from file to write to all registers with ADDI" in {
    val filename = "./gcc/test/test_addi.mem"
    defaultDut(filename) { c =>
      c.clock.setTimeout(0)
      val results = List.fill(8)(List(0, 1000, 3000, 2000)).flatten
      for ((i, r) <- 0 until 31 zip results) {
        c.registers(i).peekInt() should be(r)
        c.clock.step(1)
      }
      c.clock.step(20)
    }
  }

  it should "load program and end with 25 (0x19) in mem address 100 (0x64)" in {
    val filename = "./gcc/test/test_book.mem"
    defaultDut(filename) { c =>
      c.clock.setTimeout(0)
      c.clock.step(1) // addi
      c.registers(2).peekInt() should be(5)
      c.clock.step(1) // addi
      c.registers(3).peekInt() should be(12)
      c.clock.step(1) // addi
      c.registers(7).peekInt() should be(3)
      c.clock.step(1) // lui
      c.registers(6).peekInt() should be(0x80000000L)
      c.clock.step(1) // or
      c.registers(4).peekInt() should be(7)
      c.clock.step(1) // and
      c.registers(5).peekInt() should be(4)
      c.clock.step(1) // add
      c.registers(5).peekInt() should be(11)
      c.clock.step(1) // beq (skip)
      c.clock.step(1) // slt
      c.registers(4).peekInt() should be(0)
      c.clock.step(1) // beq (skip next addi)
      c.clock.step(1) // slt
      c.registers(4).peekInt() should be(1)
      c.clock.step(1) // add
      c.registers(7).peekInt() should be(12)
      c.clock.step(1) // sub
      c.registers(7).peekInt() should be(7)
      // Check Memory write at address 0x80000000
      c.memWriteAddr.peekInt() should be(0x80000000L)
      c.memWriteData.peekInt() should be(7)

      c.memReadData.peekInt() should be(7)
      c.clock.step(1 + readLatency) // lw
      c.registers(2).peekInt() should be(7)
      c.clock.step(1) // add
      c.registers(9).peekInt() should be(18)
      c.clock.step(1) // jal (skip next addi)
      c.clock.step(1) // add
      c.registers(2).peekInt() should be(25)
      // // Check Memory address 0x80000000 + 100 (0x64)
      c.memWriteAddr.peekInt() should be(0x80000000L + 0x64)
      c.memWriteData.peekInt() should be(25)
      c.clock.step(1 + writeLatency) // sw

      // Add some padding at the end
      c.clock.step(10)
    }
  }

  it should "loop thru ascii table writing to 0x3000_0000 region" in {
    val filename = "./gcc/test/test_ascii.mem"
    defaultDut(filename) { c =>
      c.clock.setTimeout(0)
      c.clock.step(1)
      c.registers(1).peekInt() should be(40)
      c.clock.step(1)
      c.registers(2).peekInt() should be(33)
      c.clock.step(1)
      c.registers(3).peekInt() should be(126)
      c.clock.step(1)
      c.registers(4).peekInt() should be(0x3000_0000)

      for (i <- 33 until 127) {
        // Check memory write at address 0x30000000
        c.memWriteAddr.peekInt() should be(0x30000000L)
        c.memWriteData.peekInt() should be(i)
        c.clock.step(1) // sb (no latency to write 0x30000000 region)
        c.clock.step(1) // addi
        c.clock.step(1) // bge
      }
      c.clock.step(10)
    }
  }
}

      c.clock.step(1 + writeLatency) // sw
      // Check Memory read at address 0x80000000


package chiselv

import chiseltest._
import chiseltest.experimental._
import com.carlosedp.riscvassembler.RISCVAssembler
import org.scalatest._

import flatspec._
import matchers._

// Extend the Control module to add the observer for sub-module signals
class CPUSingleCycleIOWrapper(memoryFile: String) extends SOC(
      cpuFrequency          = 25000000,
      entryPoint            = 0,
      bitWidth              = 32,
      instructionMemorySize = 1 * 1024,
      dataMemorySize        = 1 * 1024,
      memoryFile            = memoryFile,
      numGPIO               = 8,
    ) {
  val registers    = expose(core.registerBank.regs)
  val pc           = expose(core.PC.pc)
  val memWriteAddr = expose(core.memoryIOManager.io.MemoryIOPort.writeAddr)
  val memWriteData = expose(core.memoryIOManager.io.MemoryIOPort.writeData)
  val memReadAddr  = expose(core.memoryIOManager.io.MemoryIOPort.readAddr)
  val memReadData  = expose(core.memoryIOManager.io.MemoryIOPort.readData)

  val GPIO0_value     = expose(core.GPIO0.GPIO)
  val GPIO0_direction = expose(core.GPIO0.direction)
  val timerCounter    = expose(core.timer0.counter)
}

class CPUSingleCycleIOSpec
    extends AnyFlatSpec
    with ChiselScalatestTester
    with BeforeAndAfterEach
    with BeforeAndAfterAll
    with should.Matchers {
  val cpuFrequency = 25000000
  val ms           = cpuFrequency / 1000
  var memoryfile: os.Path = _
  val tmpdir = os.pwd / "tmphex"

  override def beforeAll(): Unit =
    os.makeDir.all(tmpdir)
  override def afterAll(): Unit =
    scala.util.Try(os.remove(tmpdir))
  override def beforeEach(): Unit =
    memoryfile = tmpdir / (scala.util.Random.alphanumeric.filter(_.isLetter).take(15).mkString + ".hex")
  override def afterEach(): Unit =
    os.remove.all(memoryfile)

  def defaultDut(prog: String) = {
    val hex = RISCVAssembler.fromString(prog)
    os.write(memoryfile, hex)
    test(new CPUSingleCycleIOWrapper(memoryFile = memoryfile.toString))
      .withAnnotations(
        Seq(
          WriteVcdAnnotation,
          VerilatorBackendAnnotation, // GPIO needs Verilator backend
        )
      )
  }

  it should "write to GPIO0" in {
    val prog = """
    lui x1, 0x30001
    addi x5, x0, -1
    addi x3, x3, 1
    addi x4, x0, 7
    sw x5, 0(x1)
    add x2, x2, x3
    sw x2, 4(x1)
    jal x0, -8
    """
    defaultDut(prog) { c =>
      c.clock.setTimeout(0)
      c.registers(1).peekInt() should be(0)
      c.registers(2).peekInt() should be(0)
      c.registers(3).peekInt() should be(0)
      c.registers(4).peekInt() should be(0)
      c.clock.step(1) // lui
      c.registers(1).peekInt() should be(0x30001000L)
      c.clock.step(1) // addi
      c.registers(5).peekInt() should be(0xffffffffL)
      c.clock.step(1) // addi
      c.registers(3).peekInt() should be(1)
      c.clock.step(1) // addi
      c.registers(4).peekInt() should be(7)
      // Check memory address 0x30001000L for direction (GPIO0)
      c.memWriteAddr.peekInt() should be(0x30001000L)
      c.memWriteData.peekInt() should be(0xffffffffL)
      c.clock.step(1) // sw
      c.clock.step(1) // add
      c.GPIO0_direction.peekInt() should be(0xffffffffL)
      c.registers(2).peekInt() should be(1)
      c.memWriteAddr.peekInt() should be(0x30001004L)
      c.memWriteData.peekInt() should be(1)
      c.clock.step(1) // sw
      c.GPIO0_value.peekInt() should be(1)
      c.clock.step(1) // jal
      c.clock.step(1) // add
      c.registers(2).peekInt() should be(2)
      c.memWriteAddr.peekInt() should be(0x30001004L)
      c.memWriteData.peekInt() should be(2)
      c.clock.step(1) // sw
      c.GPIO0_value.peekInt() should be(2)
    }
  }

  behavior of "Timer"
  it should "read timer and wait for 2 ms" in {
    val prog = """
    main:   lui x1, 0x30003
            addi x2, x0, 2
    wait:   lw x3, 0(x1)
            bne x2, x3, -4
    cont:   addi x4, x0, 1
    """
    defaultDut(prog) { c =>
      c.clock.setTimeout(0)
      c.registers(1).peekInt() should be(0)
      c.registers(2).peekInt() should be(0)
      c.registers(3).peekInt() should be(0)
      c.timerCounter.peekInt() should be(0)
      c.clock.step(1) // lui
      c.registers(1).peekInt() should be(0x30003000L)
      c.clock.step(1) // addi
      c.registers(2).peekInt() should be(2)
      // Check read from memory address 0x30003000L
      c.memReadAddr.peekInt() should be(0x30003000L)
      c.memReadData.peekInt() should be(0)
      c.clock.step(1) // lw

  it should "reset timer after 1ms and wait for 1 ms" in {
    val prog = """
    main:   lui x1, 0x30003
            addi x2, x0, 1
    wait:   lw x3, 0(x1)
            bne x2, x3, -4
    cont:   sw x0, 0(x1)
    wait2:  lw x3, 0(x1)
            bne x2, x3, -4
    cont2:  addi x3, x0, 2
    """
    defaultDut(prog) { c =>
      c.clock.setTimeout(0)
      c.registers(1).peekInt() should be(0)
      c.registers(2).peekInt() should be(0)
      c.registers(3).peekInt() should be(0)
      c.timerCounter.peekInt() should be(0)
      c.clock.step(1) // lui
      c.registers(1).peekInt() should be(0x30003000L)
      c.clock.step(1) // addi
      c.registers(2).peekInt() should be(1)
      // Check read from memory address 0x30003000L
      c.memReadAddr.peekInt() should be(0x30003000L)
      c.memReadData.peekInt() should be(0)
      c.registers(3).peekInt() should be(0)
      c.clock.step(ms) // wait 1ms
      c.timerCounter.peekInt() should be(1)
      c.registers(3).peekInt() should be(1)
      c.clock.step(1)
      c.clock.step(1)
      c.clock.step(1) // sw
      // Check write to memory address 0x30003000L (reset)
      c.memWriteAddr.peekInt() should be(0x30003000L)
      c.memWriteData.peekInt() should be(0)
      c.timerCounter.peekInt() should be(0)
      c.registers(3).peekInt() should be(0)
      c.clock.step(ms) // wait 1ms
      c.timerCounter.peekInt() should be(1)
      c.clock.step(1) // lw
      c.clock.step(1) // bne
      c.clock.step(1) // addi
      c.registers(3).peekInt() should be(2)
    }
  }
}

      c.registers(3).peekInt() should be(0)
      c.clock.step(1)      // bne
      c.clock.step(2 * ms) // wait 2ms
      c.timerCounter.peekInt() should be(2)
      c.registers(3).peekInt() should be(2)