188 lines
5.3 KiB
Scala
188 lines
5.3 KiB
Scala
package FiveStage
|
|
import chisel3.iotesters._
|
|
import scala.collection.mutable.LinkedHashMap
|
|
|
|
import fileUtils.say
|
|
|
|
import Data._
|
|
import PrintUtils._
|
|
|
|
private class ChiselTestRunner (
|
|
instructions : List[Int],
|
|
settings : List[TestSetting],
|
|
c : Tile,
|
|
d : PeekPokeTester[Tile],
|
|
terminalAddress : Addr,
|
|
maxSteps : Int) {
|
|
|
|
|
|
/**
|
|
* Currently unused as it is quite nontrivial to figure out whether read data is actually used.
|
|
* Still, with the necessary scaffolding these can be correlated with reg writes to figure out
|
|
* if they are valid or not.
|
|
*/
|
|
case class ChiselMemReadEvent(pcAddr: Addr, memAddr: Addr, word: Int)
|
|
|
|
private def setup = {
|
|
|
|
d.poke(d.dut.io.setup, 1)
|
|
|
|
val initDMem = DMem(settings).repr.toList
|
|
val initRegs = Regs(settings).repr.toList
|
|
|
|
setupImem(instructions)
|
|
setupRegs(initRegs)
|
|
setupDmem(initDMem)
|
|
|
|
// flush
|
|
d.step(1)
|
|
disableTestSignals
|
|
d.step(5)
|
|
d.poke(d.dut.io.setup, 0)
|
|
|
|
def disableTestSignals: Unit = {
|
|
d.poke(d.dut.io.DMEMWriteData, 0)
|
|
d.poke(d.dut.io.DMEMAddress, 0)
|
|
d.poke(d.dut.io.DMEMWriteEnable, 0)
|
|
d.poke(d.dut.io.regsWriteData, 0)
|
|
d.poke(d.dut.io.regsAddress, 0)
|
|
d.poke(d.dut.io.regsWriteEnable, 0)
|
|
d.poke(d.dut.io.IMEMWriteData, 0)
|
|
d.poke(d.dut.io.IMEMAddress, 4092)
|
|
}
|
|
|
|
def setupRegs(regs: List[(Reg, Int)]) = {
|
|
regs.foreach{ case(reg, word) =>
|
|
d.poke(d.dut.io.setup, 1)
|
|
d.poke(d.dut.io.regsWriteEnable, 1)
|
|
d.poke(d.dut.io.regsWriteData, BigInt(word))
|
|
d.poke(d.dut.io.regsAddress, reg.value)
|
|
d.step(1)
|
|
}
|
|
d.poke(d.dut.io.regsWriteEnable, 0)
|
|
d.poke(d.dut.io.regsWriteData, 0)
|
|
d.poke(d.dut.io.regsAddress, 0)
|
|
}
|
|
|
|
def setupDmem(mem: List[(Addr, Int)]) = {
|
|
mem.foreach { case (addr, word) =>
|
|
d.poke(d.dut.io.setup, 1)
|
|
d.poke(d.dut.io.DMEMWriteEnable, 1)
|
|
d.poke(d.dut.io.DMEMWriteData, word)
|
|
d.poke(d.dut.io.DMEMAddress, addr.value)
|
|
d.step(1)
|
|
}
|
|
d.poke(d.dut.io.DMEMWriteEnable, 0)
|
|
d.poke(d.dut.io.DMEMWriteData, 0)
|
|
d.poke(d.dut.io.DMEMAddress, 0)
|
|
|
|
}
|
|
|
|
def setupImem(instructions: List[Int]) = {
|
|
(0 until instructions.length).foreach{ ii =>
|
|
d.poke(d.dut.io.IMEMAddress, ii*4)
|
|
d.poke(d.dut.io.IMEMWriteData, instructions(ii).toInt)
|
|
d.step(1)
|
|
}
|
|
d.poke(d.dut.io.IMEMAddress, 4092) // Ensures that we don't overwrite an instruction. Bandaid for lack of IMEM.writeEnable
|
|
d.poke(d.dut.io.IMEMWriteData, 0)
|
|
}
|
|
}
|
|
|
|
|
|
private def getPC = Addr(d.peek(d.dut.io.currentPC).toInt)
|
|
private def getDMemWriteAddress = Addr(d.peek(d.dut.io.memDeviceWriteAddress).toInt)
|
|
private def getRegWriteAddress = Reg(d.peek(d.dut.io.regsDeviceWriteAddress).toInt)
|
|
|
|
private def getRegUpdate: Option[ChiselRegEvent] = {
|
|
if(
|
|
(d.peek(d.dut.io.regsDeviceWriteEnable) == 1) &&
|
|
(getRegWriteAddress.value != 0)
|
|
){
|
|
val regWriteAddress = getRegWriteAddress
|
|
val regWriteData = d.peek(d.dut.io.regsDeviceWriteData).toInt
|
|
val regUpdate = ChiselRegEvent(getPC, regWriteAddress, regWriteData)
|
|
Some(regUpdate)
|
|
}
|
|
else
|
|
None
|
|
}
|
|
|
|
private def getMemWrite: Option[ChiselMemWriteEvent] = {
|
|
if(d.peek(d.dut.io.memDeviceWriteEnable) == 1) {
|
|
val memWriteAddress = getDMemWriteAddress
|
|
val memWriteData = d.peek(d.dut.io.memDeviceWriteData).toInt
|
|
val memUpdate = ChiselMemWriteEvent(getPC, memWriteAddress, memWriteData)
|
|
Some(memUpdate)
|
|
}
|
|
else
|
|
None
|
|
}
|
|
|
|
private def stepOne: CircuitTrace = {
|
|
val state = (getPC, List(getRegUpdate, getMemWrite).flatten)
|
|
// say(d.peek(d.dut.io).toList.mkStringN)
|
|
d.step(1)
|
|
state
|
|
}
|
|
|
|
|
|
private def go(log: List[CircuitTrace], timeOut: Int): (Option[String], List[CircuitTrace]) = {
|
|
if(timeOut == 0){
|
|
(Some("Chisel tester timed out before reaching termination address"), log.reverse)
|
|
}
|
|
else if(getPC == terminalAddress){
|
|
(None, (flush ::: log).reverse)
|
|
}
|
|
else {
|
|
val step = stepOne
|
|
go(step :: log, timeOut - 1)
|
|
}
|
|
}
|
|
|
|
// After finishing, let the circuit run until all updates can be committed.
|
|
private def flush: List[CircuitTrace] =
|
|
(0 to 3).map(_ => stepOne).reverse.toList
|
|
|
|
/**
|
|
* Run the entire shebang
|
|
*/
|
|
def run: (Option[String], List[CircuitTrace]) = {
|
|
setup
|
|
go(Nil, maxSteps)
|
|
}
|
|
}
|
|
|
|
object ChiselTestRunner {
|
|
|
|
def apply(
|
|
binary : List[Int],
|
|
settings : List[TestSetting],
|
|
terminalAddress : Addr,
|
|
maxSteps : Int): Either[String, (Option[String], List[CircuitTrace])] = {
|
|
|
|
var sideEffectExtravaganza: Option[(Option[String], List[CircuitTrace])] = None
|
|
|
|
val error: Either[String, Boolean] = scala.util.Try {
|
|
chisel3.iotesters.Driver(() => new Tile(), "treadle") { c =>
|
|
new PeekPokeTester(c) {
|
|
val testRunner = new ChiselTestRunner(
|
|
binary,
|
|
settings,
|
|
c,
|
|
this,
|
|
terminalAddress,
|
|
maxSteps
|
|
)
|
|
|
|
val log = testRunner.run
|
|
sideEffectExtravaganza = Some(log)
|
|
}
|
|
}
|
|
}.toEither.left.map{printChiselError}
|
|
|
|
error.flatMap{ e =>
|
|
sideEffectExtravaganza.toRight("Unknown test failure, please let me know")
|
|
}
|
|
}
|
|
}
|