Nuke
This commit is contained in:
commit
932413bb3d
61 changed files with 7249 additions and 0 deletions
357
src/test/scala/RISCV/DataTypes.scala
Normal file
357
src/test/scala/RISCV/DataTypes.scala
Normal file
|
@ -0,0 +1,357 @@
|
|||
package FiveStage
|
||||
import cats.data.Writer
|
||||
import cats._
|
||||
import cats.data._
|
||||
import cats.implicits._
|
||||
|
||||
import fansi._
|
||||
import PrintUtils._
|
||||
|
||||
import fileUtils.say
|
||||
|
||||
/**
|
||||
* Types and extension methods go here.
|
||||
* Maybe it's a litte arbitrary to put VM types here, but op types in Ops.scala
|
||||
* Maybe these could be separated to somewhere else.
|
||||
*/
|
||||
object Data {
|
||||
type Label = String
|
||||
|
||||
case class Reg(value: Int)
|
||||
case class Imm(value: Int)
|
||||
case class Addr(value: Int){
|
||||
def +(that: Addr) = Addr(value + that.value)
|
||||
def -(that: Addr) = Addr(value - that.value)
|
||||
def step = Addr(value + 4)
|
||||
}
|
||||
|
||||
object Reg{ def apply(s: String): Reg = Reg(lookupReg(s).get) }
|
||||
|
||||
type SourceInfo[A] = Writer[List[String], A]
|
||||
object SourceInfo { def apply[A](s: String, a: A) = Writer(List(s), a) }
|
||||
|
||||
trait ExecutionEvent
|
||||
import PrintUtils._
|
||||
case class RegUpdate(reg: Reg, word: Int) extends ExecutionEvent
|
||||
case class MemWrite(addr: Addr, word: Int) extends ExecutionEvent
|
||||
case class MemRead(addr: Addr, word: Int) extends ExecutionEvent
|
||||
|
||||
// addr is the target address
|
||||
case class PcUpdateJALR(addr: Addr) extends ExecutionEvent
|
||||
case class PcUpdateJAL(addr: Addr) extends ExecutionEvent
|
||||
case class PcUpdateB(addr: Addr) extends ExecutionEvent
|
||||
case class PcUpdate(addr: Addr) extends ExecutionEvent
|
||||
|
||||
case class ExecutionTraceEvent(pc: Addr, event: ExecutionEvent*){ override def toString(): String = s"$pc: " + event.toList.mkString(", ") }
|
||||
type ExecutionTrace[A] = Writer[List[ExecutionTraceEvent], A]
|
||||
|
||||
object ExecutionTrace {
|
||||
def apply(vm: VM, event: ExecutionTraceEvent*) = Writer(event.toList, vm)
|
||||
}
|
||||
|
||||
|
||||
sealed trait ChiselEvent
|
||||
case class ChiselRegEvent(pcAddr: Addr, reg: Reg, word: Int) extends ChiselEvent
|
||||
case class ChiselMemWriteEvent(pcAddr: Addr, memAddr: Addr, word: Int) extends ChiselEvent
|
||||
|
||||
type CircuitTrace = (Addr, List[ChiselEvent])
|
||||
|
||||
|
||||
/**
|
||||
* Not sure these should be defined here instead of in the VM
|
||||
*/
|
||||
case class Regs(repr: Map[Reg, Int]) {
|
||||
def +(a: (Reg, Int)): (Option[RegUpdate], Regs) =
|
||||
if(a._1.value == 0) (None, this)
|
||||
else (Some(RegUpdate(a._1, a._2)), copy(repr + a))
|
||||
|
||||
def arith(rd: Reg, operand1: Reg, operand2: Reg, op: (Int, Int) => Int): (Option[RegUpdate], Regs) =
|
||||
this + (rd -> op(repr(operand1), repr(operand2)))
|
||||
|
||||
def arithImm(rd: Reg, operand1: Reg, operand2: Imm, op: (Int, Int) => Int): (Option[RegUpdate], Regs) =
|
||||
this + (rd -> op(repr(operand1), operand2.value))
|
||||
|
||||
def compare(operand1: Reg, operand2: Reg, comp: (Int, Int) => Boolean): Boolean =
|
||||
comp(repr(operand1), repr(operand2))
|
||||
|
||||
def apply(setting: TestSetting): Regs = setting match {
|
||||
case setting: REGSET => Regs(repr + (setting.rd -> setting.word))
|
||||
case _ => this
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
case class DMem(repr: Map[Addr, Int]) {
|
||||
def read(addr: Addr): Either[String, (MemRead, Int)] =
|
||||
if(addr.value >= 4096)
|
||||
Left(s"attempted to read from illegal address ${addr.show}")
|
||||
else {
|
||||
val readResult = repr.lift(addr).getOrElse(0)
|
||||
Right((MemRead(addr, readResult), readResult))
|
||||
}
|
||||
|
||||
def write(addr: Addr, word: Int): Either[String, (MemWrite, DMem)] =
|
||||
if(addr.value >= 4096)
|
||||
Left(s"attempted to write to illegal address ${addr.show}")
|
||||
else {
|
||||
Right((MemWrite(addr, word)), DMem(repr + (addr -> word)))
|
||||
}
|
||||
|
||||
def apply(setting: TestSetting): DMem = setting match {
|
||||
case setting: MEMSET => {
|
||||
DMem(repr + (setting.addr -> setting.word))
|
||||
}
|
||||
case _ => this
|
||||
}
|
||||
}
|
||||
|
||||
object Regs{
|
||||
def empty: Regs = Regs((0 to 31).map(x => (Reg(x) -> 0)).toMap)
|
||||
def apply(settings: List[TestSetting]): Regs = settings.foldLeft(empty){
|
||||
case(acc, setting) => acc(setting)
|
||||
}
|
||||
}
|
||||
object DMem{
|
||||
def empty: DMem = DMem(Map[Addr, Int]())
|
||||
def apply(settings: List[TestSetting]): DMem = settings.foldLeft(empty){
|
||||
case(acc, setting) => acc(setting)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
implicit class IntOps(i: Int) {
|
||||
// Needs backticks to not conflict with xml
|
||||
def `u>`(that: Int): Boolean = {
|
||||
if((i >= 0) && (that >= 0))
|
||||
i > that
|
||||
else if((i < 0) && (that < 0))
|
||||
i > that
|
||||
else if((i < 0))
|
||||
true
|
||||
else
|
||||
false
|
||||
}
|
||||
|
||||
def nBitsS: Int = i match {
|
||||
case i if (i < 0) => (math.log(math.abs(i.toLong))/math.log(2)).ceil.toInt + 1
|
||||
case i if (i == 0) => 0
|
||||
case i if (i > 0) => (math.log((i + 1).toLong)/math.log(2)).ceil.toInt + 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Yes, a negative number technically has a unsigned size, but that depends on integer width,
|
||||
* so it is better left as an option
|
||||
*/
|
||||
def nBitsU: Option[Int] = i match {
|
||||
case i if (i < 0) => None
|
||||
case i if (i == 0) => Some(0)
|
||||
case i if (i > 0) => Some((math.log(i)/math.log(2)).ceil.toInt)
|
||||
}
|
||||
|
||||
def field(firstBit: Int, size: Int): Int = {
|
||||
val bitsLeft = 31 - firstBit
|
||||
val bitsRight = 32 - size
|
||||
val leftShifted = i << bitsLeft
|
||||
val rightShifted = leftShifted >> bitsRight
|
||||
rightShifted
|
||||
}
|
||||
|
||||
def splitLoHi(loBits: Int): (Int, Int) = {
|
||||
val hiBits = 32 - loBits
|
||||
val sep = 31 - loBits
|
||||
val lo = i.field(31, loBits)
|
||||
val hi = i.field(sep, hiBits)
|
||||
(lo, hi)
|
||||
}
|
||||
|
||||
def log2: Int = math.ceil(math.log(i.toDouble)/math.log(2.0)).toInt
|
||||
}
|
||||
|
||||
implicit class StringOps(s: String) {
|
||||
def binary: Int = {
|
||||
s.reverse.foldLeft((0, 0)){
|
||||
case((acc, pow), char) if char == '0' => (acc, pow + 1)
|
||||
case((acc, pow), char) if char == '1' => (acc + (1 << pow), pow + 1)
|
||||
case((acc, pow), char) => assert(false, "malformed binary conversion"); (0, 0)
|
||||
}._1
|
||||
}
|
||||
}
|
||||
|
||||
implicit class ListOps[A](xs: List[A]) {
|
||||
def mkStringN = xs.mkString("\n","\n","\n")
|
||||
def splitAtPred(p: (A, A) => Boolean): List[List[A]] = {
|
||||
val splitPoints = xs.tail.foldLeft((1, List[Int](), xs.head)){
|
||||
case((idx, acc, pA), a) if(p(pA, a)) => (1, idx :: acc, a)
|
||||
case((idx, acc, pA), a) => (idx + 1, acc, a)
|
||||
}._2.reverse
|
||||
|
||||
val (blocks, rem) = splitPoints.foldLeft((List[List[A]](), xs)){
|
||||
case((acc, rem), point) => {
|
||||
val(block, remainder) = rem.splitAt(point)
|
||||
(block :: acc, remainder)
|
||||
}
|
||||
}
|
||||
(rem :: blocks).reverse
|
||||
}
|
||||
def showN(sep: String)(implicit ev: Fancy[A]): fansi.Str =
|
||||
xs.foldLeft(fansi.Str("")){ case(acc, a) => acc ++ a.show ++ fansi.Str(sep) }
|
||||
def showN(sep1: String, sep2: String, sep3: String)(implicit ev: Fancy[A]): fansi.Str =
|
||||
Str(sep1) ++ xs.foldLeft(fansi.Str("")){ case(acc, a) => acc ++ a.show ++ fansi.Str(sep2) } ++ Str(sep3)
|
||||
def shuffle(shuffler: scala.util.Random): List[A] = shuffler.shuffle(xs)
|
||||
}
|
||||
implicit class NestedListOps[A](xs: List[List[A]]) {
|
||||
def zipWithIndexNested: List[List[(A, Int)]] = {
|
||||
val startingPoints = xs.scanLeft(0){ case(acc, xs) => acc + xs.size }
|
||||
(xs.map(_.zipWithIndex) zip startingPoints).map{ case(withIndex, offset) =>
|
||||
withIndex.map{ case(a, idx) => (a, idx + offset) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
import Ops._
|
||||
|
||||
sealed trait TestSetting
|
||||
case class REGSET(rd: Reg, word: Int) extends TestSetting
|
||||
case class MEMSET(addr: Addr, word: Int) extends TestSetting
|
||||
|
||||
implicit class ListEitherOps[E,A](es: List[Either[E,A]]) {
|
||||
import cats.data.Validated
|
||||
def separateXOR: Either[List[E], List[A]] = {
|
||||
val (errors, as) = es.map(_.toValidated).separate
|
||||
Either.cond(errors.isEmpty, as, errors)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Represents the result of parsing a program, with built in convenience methods for running a VM
|
||||
* and assembling the program.
|
||||
*/
|
||||
case class Program(
|
||||
ops : List[SourceInfo[Op]],
|
||||
settings : List[TestSetting],
|
||||
labelMap : Map[Label, Addr],
|
||||
maxSteps : Int = 5000
|
||||
){
|
||||
|
||||
def imem: Map[Addr, Op] =
|
||||
ops.map(_.run._2).zipWithIndex.map{ case(op, idx) => (Addr(idx*4), op) }.toMap
|
||||
|
||||
|
||||
/**
|
||||
* Loads a VM which can be run to get a trace.
|
||||
*/
|
||||
def vm: VM =
|
||||
VM(settings, imem, labelMap)
|
||||
|
||||
|
||||
/**
|
||||
* A convenient lookup for every instruction, allowing the test runner to check what source line
|
||||
* caused an error to happen.
|
||||
*/
|
||||
val sourceMap: Map[Addr, String] =
|
||||
ops.map(_.run._1).zipWithIndex.map{ case(info, idx) => (Addr(idx*4), info.map(_.trim).mkString(", ")) }.toMap
|
||||
|
||||
|
||||
/**
|
||||
* The assembled program
|
||||
*/
|
||||
def machineCode: Either[String, Map[Addr, Int]] =
|
||||
imem.toList
|
||||
.sortBy(_._1.value).map{ case(addr, op) => assembler.assembleOp(op, addr, labelMap).map(x => (addr, x)) }
|
||||
.sequence
|
||||
.map(_.toMap)
|
||||
.left.map{ case(error, addr) => s"Assembler error: $error, corresponding to source:\n${sourceMap(addr)}" }
|
||||
|
||||
|
||||
/**
|
||||
* Returns the binary code and the execution trace or an error for convenient error checking.
|
||||
*/
|
||||
def validate: Either[String, (Map[Addr, Int], ExecutionTrace[VM])] = machineCode.flatMap{ binary =>
|
||||
val uk = "UNKNOWN"
|
||||
val (finish, trace) = VM.run(maxSteps, vm)
|
||||
finish match {
|
||||
case Failed(s, addr) => Left(s"VM failed with error $s at address $addr\nSource line:\n${sourceMap.lift(addr).getOrElse(uk)}")
|
||||
case Timeout => Left(s"VM timed out after $maxSteps steps. This should not happen with the supplied tests")
|
||||
case Success => Right(binary, trace)
|
||||
}
|
||||
}
|
||||
|
||||
def labelMapReverse = labelMap.toList.map(_.swap).toMap
|
||||
}
|
||||
|
||||
|
||||
def lookupReg(s: String): Option[Int] = {
|
||||
val regMap = Map(
|
||||
"x0" -> 0,
|
||||
"x1" -> 1,
|
||||
"x2" -> 2,
|
||||
"x3" -> 3,
|
||||
"x4" -> 4,
|
||||
"x5" -> 5,
|
||||
"x6" -> 6,
|
||||
"x7" -> 7,
|
||||
"x8" -> 8,
|
||||
"x9" -> 9,
|
||||
"x10" -> 10,
|
||||
"x11" -> 11,
|
||||
"x12" -> 12,
|
||||
"x13" -> 13,
|
||||
"x14" -> 14,
|
||||
"x15" -> 15,
|
||||
"x16" -> 16,
|
||||
"x17" -> 17,
|
||||
"x18" -> 18,
|
||||
"x19" -> 19,
|
||||
"x20" -> 20,
|
||||
"x21" -> 21,
|
||||
"x22" -> 22,
|
||||
"x23" -> 23,
|
||||
"x24" -> 24,
|
||||
"x25" -> 25,
|
||||
"x26" -> 26,
|
||||
"x27" -> 27,
|
||||
"x28" -> 28,
|
||||
"x29" -> 29,
|
||||
"x30" -> 30,
|
||||
"x31" -> 31,
|
||||
"zero" -> 0,
|
||||
"ra" -> 1,
|
||||
"sp" -> 2,
|
||||
"gp" -> 3,
|
||||
"tp" -> 4,
|
||||
"t0" -> 5,
|
||||
"t1" -> 6,
|
||||
"t2" -> 7,
|
||||
"s0" -> 8,
|
||||
"fp" -> 8,
|
||||
"s1" -> 9,
|
||||
"a0" -> 10,
|
||||
"a1" -> 11,
|
||||
"a2" -> 12,
|
||||
"a3" -> 13,
|
||||
"a4" -> 14,
|
||||
"a5" -> 15,
|
||||
"a6" -> 16,
|
||||
"a7" -> 17,
|
||||
"s2" -> 18,
|
||||
"s3" -> 19,
|
||||
"s4" -> 20,
|
||||
"s5" -> 21,
|
||||
"s6" -> 22,
|
||||
"s7" -> 23,
|
||||
"s8" -> 24,
|
||||
"s9" -> 25,
|
||||
"s10" -> 26,
|
||||
"s11" -> 27,
|
||||
"t3" -> 28,
|
||||
"t4" -> 29,
|
||||
"t5" -> 30,
|
||||
"t6" -> 31)
|
||||
|
||||
regMap.lift(s)
|
||||
}
|
||||
}
|
184
src/test/scala/RISCV/LogParser.scala
Normal file
184
src/test/scala/RISCV/LogParser.scala
Normal file
|
@ -0,0 +1,184 @@
|
|||
package FiveStage
|
||||
import Data._
|
||||
import fileUtils.say
|
||||
import PrintUtils._
|
||||
|
||||
/**
|
||||
* Helpers for comparing VM and chisel logs
|
||||
*/
|
||||
object LogParser {
|
||||
|
||||
|
||||
/**
|
||||
* Peeks ahead at the chisel log to see if an expected jump is taken.
|
||||
*/
|
||||
def fetchUntilJumpTarget(addr: Addr, chiselLog: List[CircuitTrace]): Option[List[CircuitTrace]] = {
|
||||
val (head, tail) = chiselLog.splitAt(4) // very arbitrary choice
|
||||
val pruned: List[CircuitTrace] = head.dropWhile{ case(myAddr, _) => myAddr != addr }
|
||||
pruned.headOption.map(_ => pruned ::: tail)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Fetches a basic block of VM execution trace
|
||||
*/
|
||||
def splitToBlocks(vmTrace: List[ExecutionTraceEvent]): List[List[ExecutionTraceEvent]] =
|
||||
vmTrace.splitAtPred{ case(current, next) =>
|
||||
!(next.pc == current.pc + Addr(4))
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetches a basic block of chisel execution trace
|
||||
*/
|
||||
def splitToBlocksChisel(chiselTrace: List[CircuitTrace]): List[List[CircuitTrace]] =
|
||||
chiselTrace.splitAtPred{ case((current, _), (next, _)) =>
|
||||
!((next == current + Addr(4)) || (next == current))
|
||||
}
|
||||
|
||||
|
||||
def guessBlockName(trace: List[Addr], labelMap: Map[Addr, Label]): String = trace.headOption.map(x => labelMap.lift(x)
|
||||
.getOrElse(labelMap.lift(trace.head - Addr(4)).getOrElse("UNKNOWN (return jump or misjump)"))).getOrElse("UNKNOWN")
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to merge blocks, patching up when mismatches occur
|
||||
* Fails when branchpredictor misjumps, feel free to send a PR
|
||||
*/
|
||||
type BlockList = List[(List[ExecutionTraceEvent], List[CircuitTrace])]
|
||||
|
||||
def mergeTraces(vmTrace: List[ExecutionTraceEvent], chiselTrace: List[CircuitTrace]): BlockList = {
|
||||
def helper(acc: BlockList, blocs: (List[List[ExecutionTraceEvent]], List[List[CircuitTrace]])): BlockList = blocs match {
|
||||
|
||||
case (vmBlock :: vmTail, chiselBlock :: chiselTail) if (vmBlock.head.pc == chiselBlock.head._1) =>
|
||||
helper((vmBlock, chiselBlock) :: acc, ((vmTail, chiselTail)))
|
||||
|
||||
case (vmBlock :: vmTail, chiselBlock :: chiselTail) =>
|
||||
helper((Nil, chiselBlock) :: acc, ((vmBlock :: vmTail, chiselTail)))
|
||||
|
||||
case (Nil, chiselBlock :: chiselTail) =>
|
||||
helper((Nil, chiselBlock) :: acc, ((Nil, chiselTail)))
|
||||
|
||||
case (vmBlock :: vmTail, Nil) =>
|
||||
helper((vmBlock, Nil) :: acc, ((vmTail, Nil)))
|
||||
|
||||
case _ => acc.reverse
|
||||
}
|
||||
helper(Nil, (splitToBlocks(vmTrace), splitToBlocksChisel(chiselTrace)))
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compares register update logs
|
||||
*/
|
||||
def compareRegs(vmTrace: List[ExecutionTraceEvent], chiselTrace: List[CircuitTrace]): Option[String] = {
|
||||
val vmRegUpdates = vmTrace.zipWithIndex
|
||||
.flatMap{ case (e, step) => e.event.toList.map(y => (step, e.pc, y))}
|
||||
.collect{ case (step: Int, addr: Addr, x: RegUpdate) => (step, addr, x) }
|
||||
|
||||
val chiselRegUpdates = chiselTrace.zipWithIndex
|
||||
.flatMap{ case (e, step) => e._2.map(y => (step, e._1, y))}
|
||||
.collect{ case (step: Int, addr: Addr, x: ChiselRegEvent) => (step, addr, x) }
|
||||
|
||||
val errors = (vmRegUpdates zip chiselRegUpdates).map{
|
||||
case((_, _, vmUpdate), (_, _, chiselUpdate)) if ((vmUpdate.reg == chiselUpdate.reg) && (vmUpdate.word == chiselUpdate.word)) => {
|
||||
None
|
||||
}
|
||||
|
||||
case((vmStep, vmAddr, vmUpdate), (chiselStep, chiselAddr, chiselUpdate)) => {
|
||||
val errorString = s"Register update mismatch.\n" ++
|
||||
s"VM: At step $vmStep, at address ${vmAddr.show}, the VM got ${vmUpdate.show}\n" ++
|
||||
s"Circuit: At step $chiselStep, at address ${chiselAddr.show}, the circuit got ${chiselUpdate.show}"
|
||||
Some(errorString)
|
||||
}
|
||||
}
|
||||
|
||||
val error = errors.collect{ case Some(x) => x }.headOption
|
||||
|
||||
|
||||
val lengthMismatch: Option[String] = (vmRegUpdates, chiselRegUpdates) match {
|
||||
case (h :: t, Nil) => Some(s"Your design performed no reg updates. First expected update was at VM step ${h._1}, PC: ${h._2.show}, ${h._3.show}")
|
||||
case (Nil, h :: t) => Some(s"Your design performed reg updates, but the VM did not. First update was at step ${h._1}, PC: ${h._2.show}, ${h._3.show}")
|
||||
case (hVM :: tVM, hC :: tC) if (tVM.size > tC.size) => {
|
||||
|
||||
val VMremainder = tVM.drop(tC.size)
|
||||
|
||||
val errorString =
|
||||
s"VM performed more reg updates than your design.\n" ++
|
||||
s"Your design performed ${chiselRegUpdates.size} updates before terminating, while the VM performed ${vmRegUpdates.size} updates.\n" ++
|
||||
s"The first update your design missed happened at VM step ${VMremainder.head._1}, PC: ${VMremainder.head._2.show} and was ${VMremainder.head._3.show}"
|
||||
|
||||
Some(errorString)
|
||||
}
|
||||
case (hVM :: tVM, hC :: tC) if (tVM.size < tC.size) => {
|
||||
val ChiselRemainder = tC.drop(tVM.size)
|
||||
|
||||
val errorString =
|
||||
s"Your design performed more reg updates than the VM.\n" ++
|
||||
s"Your design performed ${chiselRegUpdates.size} updates before terminating, while the VM performed ${vmRegUpdates.size} updates.\n" ++
|
||||
s"The first spurious update your design did happened at cycle ${ChiselRemainder.head._1}, PC: ${ChiselRemainder.head._2.show} and was ${ChiselRemainder.head._3.show}"
|
||||
|
||||
Some(errorString)
|
||||
}
|
||||
|
||||
case _ => None
|
||||
}
|
||||
|
||||
|
||||
(error :: lengthMismatch :: Nil).flatten.headOption
|
||||
}
|
||||
|
||||
|
||||
def compareMem(vmTrace: List[ExecutionTraceEvent], chiselTrace: List[CircuitTrace]): Option[String] = {
|
||||
val vmMemUpdates = vmTrace.zipWithIndex
|
||||
.flatMap{ case (e, step) => e.event.toList.map(y => (step, e.pc, y))}
|
||||
.collect{ case (step: Int, addr: Addr, x: MemWrite) => (step, addr, x) }
|
||||
|
||||
val chiselMemUpdates = chiselTrace.zipWithIndex
|
||||
.flatMap{ case (e, step) => e._2.map(y => (step, e._1, y))}
|
||||
.collect{ case (step: Int, addr: Addr, x: ChiselMemWriteEvent) => (step, addr, x) }
|
||||
|
||||
val error = (vmMemUpdates zip chiselMemUpdates).map{
|
||||
case((_, _, vmUpdate), (_, _, chiselUpdate)) if ((vmUpdate.addr == chiselUpdate.memAddr) && (vmUpdate.word == chiselUpdate.word)) =>
|
||||
None
|
||||
case((vmStep, vmAddr, vmUpdate), (chiselStep, chiselAddr, chiselUpdate)) => {
|
||||
val errorString = s"Mem update mismatch.\n" ++
|
||||
s"VM: At step $vmStep, at address ${vmAddr.show}, the VM got ${vmUpdate.show}\n" ++
|
||||
s"Circuit: At step $chiselStep, at address ${chiselAddr.show}, the circuit got ${chiselUpdate.show}"
|
||||
Some(errorString)
|
||||
}
|
||||
}.collect{ case Some(x) => x }.headOption
|
||||
|
||||
|
||||
val lengthMismatch = (vmMemUpdates, chiselMemUpdates) match {
|
||||
case (h :: t, Nil) => Some(s"Your design performed no mem updates. First expected update was at VM step ${h._1}, PC: ${h._2}, ${h._3}")
|
||||
case (Nil, h :: t) => Some(s"Your design performed mem updates, but the VM did not. First spurious update was at step ${h._1}, PC: ${h._2}, ${h._3}")
|
||||
case (hVM :: tVM, hC :: tC) if (tVM.size > tC.size) => {
|
||||
|
||||
val VMremainder = tVM.drop(tC.size)
|
||||
|
||||
val errorString =
|
||||
s"VM performed more mem updates than your design.\n" ++
|
||||
s"Your design performed ${chiselMemUpdates.size} updates before terminating, while the VM performed ${vmMemUpdates.size} updates.\n" ++
|
||||
s"The first update your design missed happened at VM step ${VMremainder.head._1}, PC: ${VMremainder.head._2} and was ${VMremainder.head._3}"
|
||||
|
||||
Some(errorString)
|
||||
}
|
||||
case (hVM :: tVM, hC :: tC) if (tVM.size < tC.size) => {
|
||||
val ChiselRemainder = tC.drop(tVM.size)
|
||||
|
||||
val errorString =
|
||||
s"Your design performed more mem updates than the VM.\n" ++
|
||||
s"Your design performed ${chiselMemUpdates.size} updates before terminating, while the VM performed ${vmMemUpdates.size} updates.\n" ++
|
||||
s"The first spurious update your design did happened at cycle ${ChiselRemainder.head._1}, PC: ${ChiselRemainder.head._2} and was ${ChiselRemainder.head._3}"
|
||||
|
||||
Some(errorString)
|
||||
}
|
||||
|
||||
case _ => None
|
||||
}
|
||||
|
||||
(error :: lengthMismatch :: Nil).flatten.headOption
|
||||
}
|
||||
}
|
124
src/test/scala/RISCV/Ops.scala
Normal file
124
src/test/scala/RISCV/Ops.scala
Normal file
|
@ -0,0 +1,124 @@
|
|||
package FiveStage
|
||||
import cats.implicits._
|
||||
import fileUtils._
|
||||
|
||||
import Data._
|
||||
import PrintUtils._
|
||||
|
||||
object Ops {
|
||||
|
||||
sealed trait Op extends RegLayout
|
||||
|
||||
sealed trait RegLayout
|
||||
sealed trait RType extends RegLayout { def rd: Reg; def rs1: Reg; def rs2: Reg }
|
||||
sealed trait IType extends RegLayout { def rd: Reg; def rs1: Reg; }
|
||||
sealed trait SType extends RegLayout { def rs1: Reg; def rs2: Reg }
|
||||
sealed trait UType extends RegLayout { def rd: Reg; }
|
||||
|
||||
sealed trait ImmType
|
||||
sealed trait NoImmediate extends ImmType
|
||||
sealed trait IImmediate extends ImmType
|
||||
sealed trait SImmediate extends ImmType
|
||||
sealed trait BImmediate extends ImmType
|
||||
sealed trait UImmediate extends ImmType
|
||||
sealed trait JImmediate extends ImmType
|
||||
sealed trait ShiftImmediate extends ImmType
|
||||
|
||||
|
||||
sealed trait Comparison {
|
||||
def run(rs1Val: Int, rs2Val: Int): Boolean
|
||||
}
|
||||
case object EQ extends Comparison { def run(rs1Val: Int, rs2Val: Int): Boolean = rs1Val == rs2Val }
|
||||
case object NE extends Comparison { def run(rs1Val: Int, rs2Val: Int): Boolean = rs1Val != rs2Val }
|
||||
case object GE extends Comparison { def run(rs1Val: Int, rs2Val: Int): Boolean = rs1Val >= rs2Val }
|
||||
case object LT extends Comparison { def run(rs1Val: Int, rs2Val: Int): Boolean = rs1Val < rs2Val }
|
||||
case object GEU extends Comparison { def run(rs1Val: Int, rs2Val: Int): Boolean = !(rs1Val `u>` rs2Val) }
|
||||
case object LTU extends Comparison { def run(rs1Val: Int, rs2Val: Int): Boolean = rs1Val `u>` rs2Val }
|
||||
|
||||
case class Branch(rs1: Reg, rs2: Reg, dst: Label, comp: Comparison) extends Op with SType
|
||||
object Branch{
|
||||
def beq( rs1: Int, rs2: Int, dst: Label) = Branch(Reg(rs1), Reg(rs2), dst, EQ)
|
||||
def bne( rs1: Int, rs2: Int, dst: Label) = Branch(Reg(rs1), Reg(rs2), dst, NE)
|
||||
def blt( rs1: Int, rs2: Int, dst: Label) = Branch(Reg(rs1), Reg(rs2), dst, LT)
|
||||
def bge( rs1: Int, rs2: Int, dst: Label) = Branch(Reg(rs1), Reg(rs2), dst, GE)
|
||||
def bltu(rs1: Int, rs2: Int, dst: Label) = Branch(Reg(rs1), Reg(rs2), dst, LTU)
|
||||
def bgeu(rs1: Int, rs2: Int, dst: Label) = Branch(Reg(rs1), Reg(rs2), dst, GEU)
|
||||
|
||||
def ble( rs1: Int, rs2: Int, dst: Label) = Branch(Reg(rs2), Reg(rs1), dst, GE)
|
||||
def bgt( rs1: Int, rs2: Int, dst: Label) = Branch(Reg(rs2), Reg(rs1), dst, LT)
|
||||
def bleu(rs1: Int, rs2: Int, dst: Label) = Branch(Reg(rs2), Reg(rs1), dst, GEU)
|
||||
def bgtu(rs1: Int, rs2: Int, dst: Label) = Branch(Reg(rs2), Reg(rs1), dst, LTU)
|
||||
|
||||
def beqz(rs1: Int, dst: Label) = Branch(Reg(rs1), Reg(0), dst, EQ)
|
||||
def bnez(rs1: Int, dst: Label) = Branch(Reg(rs1), Reg(0), dst, NE)
|
||||
def blez(rs1: Int, dst: Label) = Branch(Reg(rs1), Reg(0), dst, LT)
|
||||
}
|
||||
|
||||
sealed trait someDecorator
|
||||
sealed trait ArithOp {
|
||||
def run(operand1: Int, operand2: Int): Int
|
||||
}
|
||||
case object ADD extends ArithOp { def run(operand1: Int, operand2: Int): Int = operand1 + operand2 }
|
||||
case object SUB extends ArithOp { def run(operand1: Int, operand2: Int): Int = operand1 - operand2 }
|
||||
case object OR extends ArithOp { def run(operand1: Int, operand2: Int): Int = operand1 | operand2 }
|
||||
case object XOR extends ArithOp { def run(operand1: Int, operand2: Int): Int = operand1 ^ operand2 }
|
||||
case object AND extends ArithOp { def run(operand1: Int, operand2: Int): Int = operand1 & operand2 }
|
||||
case object SLL extends ArithOp { def run(operand1: Int, operand2: Int): Int = operand1 << operand2 }
|
||||
case object SRL extends ArithOp { def run(operand1: Int, operand2: Int): Int = operand1 >>> operand2 }
|
||||
case object SRA extends ArithOp { def run(operand1: Int, operand2: Int): Int = operand1 >> operand2 }
|
||||
case object SLT extends ArithOp { def run(operand1: Int, operand2: Int): Int = if(operand2 > operand1) 1 else 0 }
|
||||
case object SLTU extends ArithOp { def run(operand1: Int, operand2: Int): Int = if(operand2 `u>` operand1) 1 else 0 }
|
||||
|
||||
case class Arith(rd: Reg, rs1: Reg, rs2: Reg, op: ArithOp) extends Op with RType
|
||||
object Arith {
|
||||
def add( rd: Int, rs1: Int, rs2: Int) = Arith(Reg(rd), Reg(rs1), Reg(rs2), ADD)
|
||||
def sub( rd: Int, rs1: Int, rs2: Int) = Arith(Reg(rd), Reg(rs1), Reg(rs2), SUB)
|
||||
def or( rd: Int, rs1: Int, rs2: Int) = Arith(Reg(rd), Reg(rs1), Reg(rs2), OR)
|
||||
def xor( rd: Int, rs1: Int, rs2: Int) = Arith(Reg(rd), Reg(rs1), Reg(rs2), XOR)
|
||||
def and( rd: Int, rs1: Int, rs2: Int) = Arith(Reg(rd), Reg(rs1), Reg(rs2), AND)
|
||||
def sll( rd: Int, rs1: Int, rs2: Int) = Arith(Reg(rd), Reg(rs1), Reg(rs2), SLL)
|
||||
def srl( rd: Int, rs1: Int, rs2: Int) = Arith(Reg(rd), Reg(rs1), Reg(rs2), SRL)
|
||||
def sra( rd: Int, rs1: Int, rs2: Int) = Arith(Reg(rd), Reg(rs1), Reg(rs2), SRA)
|
||||
def slt( rd: Int, rs1: Int, rs2: Int) = Arith(Reg(rd), Reg(rs1), Reg(rs2), SLT)
|
||||
def sltu(rd: Int, rs1: Int, rs2: Int) = Arith(Reg(rd), Reg(rs1), Reg(rs2), SLTU)
|
||||
}
|
||||
|
||||
def NOP = ArithImm.nop
|
||||
|
||||
|
||||
|
||||
case class ArithImm(rd: Reg, rs1: Reg, imm: Imm, op: ArithOp) extends Op with IType
|
||||
object ArithImm {
|
||||
def add( rd: Int, rs1: Int, imm: Int) = ArithImm(Reg(rd), Reg(rs1), Imm(imm), ADD)
|
||||
def or( rd: Int, rs1: Int, imm: Int) = ArithImm(Reg(rd), Reg(rs1), Imm(imm), OR)
|
||||
def xor( rd: Int, rs1: Int, imm: Int) = ArithImm(Reg(rd), Reg(rs1), Imm(imm), XOR)
|
||||
def and( rd: Int, rs1: Int, imm: Int) = ArithImm(Reg(rd), Reg(rs1), Imm(imm), AND)
|
||||
def sll( rd: Int, rs1: Int, imm: Int) = ArithImm(Reg(rd), Reg(rs1), Imm(imm), SLL)
|
||||
def srl( rd: Int, rs1: Int, imm: Int) = ArithImm(Reg(rd), Reg(rs1), Imm(imm), SRL)
|
||||
def sra( rd: Int, rs1: Int, imm: Int) = ArithImm(Reg(rd), Reg(rs1), Imm(imm), SRA)
|
||||
def slt( rd: Int, rs1: Int, imm: Int) = ArithImm(Reg(rd), Reg(rs1), Imm(imm), SLT)
|
||||
def sltu(rd: Int, rs1: Int, imm: Int) = ArithImm(Reg(rd), Reg(rs1), Imm(imm), SLTU)
|
||||
def nop = add(0, 0, 0)
|
||||
}
|
||||
|
||||
|
||||
case class LUI(rd: Reg, imm: Imm) extends Op with UType
|
||||
case class AUIPC(rd: Reg, imm: Imm) extends Op with UType
|
||||
case class SW(rs2: Reg, rs1: Reg, offset: Imm) extends Op with SType
|
||||
case class LW(rd: Reg, rs1: Reg, offset: Imm) extends Op with IType
|
||||
|
||||
case class JALR(rd: Reg, rs1: Reg, dst: String) extends Op with IType
|
||||
case class JAL(rd: Reg, dst: String) extends Op with UType
|
||||
|
||||
|
||||
object LUI { def apply(rd: Int, imm: Int): LUI = LUI(Reg(rd), Imm(imm)) }
|
||||
object AUIPC { def apply(rd: Int, imm: Int): AUIPC = AUIPC(Reg(rd), Imm(imm)) }
|
||||
object SW { def apply(rs2: Int, rs1: Int, offset: Int): SW = SW(Reg(rs2), Reg(rs1), Imm(offset)) }
|
||||
object LW { def apply(rd: Int, rs1: Int, offset: Int): LW = LW(Reg(rd), Reg(rs1), Imm(offset)) }
|
||||
|
||||
object JAL{ def apply(rd: Int, dst: String): JAL = JAL(Reg(rd), dst) }
|
||||
object JALR{ def apply(rd: Int, rs1: Int, dst: String): JALR = JALR(Reg(rd), Reg(rs1), dst) }
|
||||
|
||||
// This op should not be assembled, but will for the sake of simplicity be rendered as a NOP
|
||||
case object DONE extends Op with IType { val rd = Reg(0); val rs1 = Reg(0) }
|
||||
}
|
357
src/test/scala/RISCV/Parser.scala
Normal file
357
src/test/scala/RISCV/Parser.scala
Normal file
|
@ -0,0 +1,357 @@
|
|||
package FiveStage
|
||||
import atto._, Atto._, syntax.refined._
|
||||
import eu.timepit.refined.numeric._
|
||||
import fileUtils.say
|
||||
|
||||
import Ops._
|
||||
import Data._
|
||||
|
||||
import cats._
|
||||
import cats.data.{ Op => _ }
|
||||
import cats.implicits._
|
||||
|
||||
object Parser {
|
||||
|
||||
def hex : Parser[Int] = string("0x") ~> many1(hexDigit).map{ ds =>
|
||||
val bi = Integer.parseUnsignedInt(new String(ds.toList.toArray), 16)
|
||||
bi.toInt
|
||||
}
|
||||
|
||||
def labelDest : Parser[Label] = (takeWhile(_ != ':') <~ char(':'))
|
||||
def label : Parser[Label] = takeWhile(_ != ' ')
|
||||
def reg : Parser[Int] = takeWhile(x => (x != ',' && x != ')')).map(lookupReg).attempt
|
||||
def sep : Parser[Unit] = many(whitespace) *> char(',') *> many(whitespace).void
|
||||
|
||||
def branch : (Parser[Int], Parser[Int], Parser[String]) = (reg <~ sep, reg <~ sep, label)
|
||||
def branchZ : (Parser[Int], Parser[String]) = (reg <~ sep, label)
|
||||
|
||||
def arith : (Parser[Int], Parser[Int], Parser[Int]) = (reg <~ sep, reg <~ sep, reg)
|
||||
def arithImm : (Parser[Int], Parser[Int], Parser[Int]) = (reg <~ sep, reg <~ sep, hex | int)
|
||||
|
||||
def stringWs(s: String) : Parser[String] = many(whitespace) ~> string(s) <~ many1(whitespace)
|
||||
|
||||
val singleInstruction: Parser[Op] = List(
|
||||
////////////////////////////////////////////
|
||||
//// Branches
|
||||
stringWs("beq") ~> branch.mapN{Branch.beq},
|
||||
stringWs("bne") ~> branch.mapN{Branch.bne},
|
||||
stringWs("blt") ~> branch.mapN{Branch.blt},
|
||||
stringWs("bge") ~> branch.mapN{Branch.bge},
|
||||
stringWs("bltu") ~> branch.mapN{Branch.bltu},
|
||||
stringWs("bgeu") ~> branch.mapN{Branch.bgtu},
|
||||
|
||||
// pseudos:
|
||||
stringWs("ble") ~> branch.mapN{Branch.ble},
|
||||
stringWs("bgt") ~> branch.mapN{Branch.bgt},
|
||||
stringWs("bleu") ~> branch.mapN{Branch.bleu},
|
||||
stringWs("bgtu") ~> branch.mapN{Branch.bgtu},
|
||||
|
||||
// Introduce zero
|
||||
stringWs("bnez") ~> branchZ.mapN{Branch.bnez},
|
||||
stringWs("beqz") ~> branchZ.mapN{Branch.beqz},
|
||||
stringWs("blez") ~> branchZ.mapN{Branch.blez},
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////
|
||||
//// Arith
|
||||
stringWs("add") ~> arith.mapN{Arith.add},
|
||||
stringWs("sub") ~> arith.mapN{Arith.sub},
|
||||
stringWs("or") ~> arith.mapN{Arith.or},
|
||||
stringWs("xor") ~> arith.mapN{Arith.xor},
|
||||
stringWs("and") ~> arith.mapN{Arith.and},
|
||||
|
||||
stringWs("sll") ~> arith.mapN{Arith.sll},
|
||||
stringWs("srl") ~> arith.mapN{Arith.srl},
|
||||
stringWs("sra") ~> arith.mapN{Arith.sra},
|
||||
|
||||
stringWs("slt") ~> arith.mapN{Arith.slt},
|
||||
stringWs("sltu") ~> arith.mapN{Arith.sltu},
|
||||
|
||||
// pseudos
|
||||
stringWs("mv") ~> (reg <~ sep, reg, ok(0)).mapN{Arith.add},
|
||||
stringWs("nop") ~> (ok(0), ok(0), ok(0)).mapN{Arith.add},
|
||||
|
||||
// Check if rs1 is not equal to 0.
|
||||
// snez rd, rs1 => sltu rd, zero, rs1
|
||||
stringWs("snez") ~> (reg <~ sep, ok(0), reg).mapN{Arith.sltu},
|
||||
|
||||
|
||||
////////////////////////////////////////////
|
||||
//// Arith Imm
|
||||
stringWs("addi") ~> arithImm.mapN{ArithImm.add},
|
||||
stringWs("ori") ~> arithImm.mapN{ArithImm.or},
|
||||
stringWs("xori") ~> arithImm.mapN{ArithImm.xor},
|
||||
stringWs("andi") ~> arithImm.mapN{ArithImm.and},
|
||||
|
||||
stringWs("slli") ~> arithImm.mapN{ArithImm.sll},
|
||||
stringWs("srli") ~> arithImm.mapN{ArithImm.srl},
|
||||
stringWs("srai") ~> arithImm.mapN{ArithImm.sra},
|
||||
|
||||
stringWs("slti") ~> arithImm.mapN{ArithImm.slt},
|
||||
stringWs("sltui") ~> arithImm.mapN{ArithImm.sltu},
|
||||
|
||||
// pseudos
|
||||
stringWs("not") ~> (reg <~ sep, reg, ok(-1)).mapN{ArithImm.xor},
|
||||
|
||||
// Check if rs1 is less than 1. Only 0 is less than 1 when using unsigned comparison
|
||||
// seqz rd, rs1 => sltiu rd, rs1, 1
|
||||
stringWs("seqz") ~> (reg <~ sep, reg, ok(1)).mapN{ArithImm.sltu},
|
||||
|
||||
stringWs("li") ~> (reg ~ sep ~ int).collect{
|
||||
case((a, b), c) if (c.nBitsS <= 12) => ArithImm.add(a, 0, c)
|
||||
},
|
||||
|
||||
|
||||
////////////////////////////////////////////
|
||||
//// Jumps
|
||||
stringWs("jalr") ~> (reg <~ sep, reg <~ sep, label).mapN{JALR.apply},
|
||||
stringWs("jal") ~> (reg <~ sep, label).mapN{JAL.apply},
|
||||
|
||||
// pseudos
|
||||
// JAL with ra as rd automatically chosen.
|
||||
stringWs("call") ~> label.map(label => JAL(regNames.ra, label)),
|
||||
|
||||
// For jr we don't care about where we jumped from.
|
||||
stringWs("jr") ~> reg.map(r => JALR(0, r, "zero")),
|
||||
|
||||
// As jr, but with a label rather than a register.
|
||||
stringWs("j") ~> label.map(label => JAL(0, label)),
|
||||
many(whitespace) ~> string("ret") ~> ok(JALR(0, regNames.ra, "zero")),
|
||||
|
||||
|
||||
////////////////////////////////////////////
|
||||
//// load/store
|
||||
stringWs("sw") ~> (reg <~ sep, int <~ char('('), reg <~ char(')')).mapN{case (rs2, offset, rs1) => SW(rs2, rs1, offset)},
|
||||
stringWs("lw") ~> (reg <~ sep, int <~ char('('), reg <~ char(')')).mapN{case (rd, offset, rs1) => LW(rd, rs1, offset)},
|
||||
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////
|
||||
//// others
|
||||
stringWs("auipc") ~> (reg <~ sep, int).mapN{AUIPC.apply},
|
||||
stringWs("lui") ~> (reg <~ sep, int).mapN{LUI.apply},
|
||||
|
||||
many(whitespace) ~> string("nop") ~> ok(Arith.add(0, 0, 0)),
|
||||
many(whitespace) ~> string("done") ~> ok(DONE),
|
||||
// stringWs("done") ~> ok(DONE),
|
||||
|
||||
).map(_.widen[Op]).reduce(_|_)
|
||||
|
||||
|
||||
// def getShiftsHalfWord(offset: Int): (Int, Int) = (offset % 4) match {
|
||||
// case 0 => (16, 16)
|
||||
// case 1 => (
|
||||
// }
|
||||
|
||||
val multipleInstructions: Parser[List[Op]] = List(
|
||||
stringWs("li") ~> (reg <~ sep, int.map(_.splitLoHi(12))).mapN{ case(rd, (lo, hi)) => List(
|
||||
LUI(rd, hi),
|
||||
ArithImm.add(rd, 0, lo)
|
||||
)}.map(_.widen[Op]),
|
||||
|
||||
// NOTE: THESE ARE NOT PSEUDO-OPS IN RISV32I!
|
||||
// NOTE: USES A SPECIAL REGISTER
|
||||
stringWs("lh") ~> (reg <~ sep, int <~ char('('), reg <~ char(')')).mapN{
|
||||
case (rd, offset, rs1) if (offset % 4 == 3) => {
|
||||
val placeHolder = if(rd == Reg("a0").value) Reg("a1").value else Reg("a0").value
|
||||
List(
|
||||
SW(placeHolder, 0, 2048),
|
||||
LW(placeHolder, rs1.value, (offset & 0xFFFFFF1C)),
|
||||
LW(rd.value, rs1.value, (offset & 0xFFFFFF1C) + 4),
|
||||
ArithImm.sra(placeHolder, placeHolder, 24),
|
||||
ArithImm.sll(rd.value, rd.value, 24),
|
||||
ArithImm.sra(rd.value, rd.value, 16),
|
||||
Arith.add(rd, rd, placeHolder),
|
||||
LW(placeHolder, 0, 2048)).reverse
|
||||
}
|
||||
case (rd, offset, rs1) if (offset % 4 == 2) => {
|
||||
List(
|
||||
LW(rd, rs1, (offset & 0xFFFFFF1C)),
|
||||
ArithImm.sra(rd, rd, 16)
|
||||
).reverse
|
||||
}
|
||||
|
||||
case (rd, offset, rs1) => {
|
||||
val leftShift = if((offset % 4) == 0) 16 else 8
|
||||
List(
|
||||
LW(rd, rs1, (offset & 0xFFFFFF1C)),
|
||||
ArithImm.sll(rd, rd, leftShift),
|
||||
ArithImm.sra(rd, rd, 16),
|
||||
).reverse
|
||||
}
|
||||
}.map(_.widen[Op]),
|
||||
).reduce(_|_)
|
||||
|
||||
|
||||
val instruction = singleInstruction.map(List(_)) | multipleInstructions
|
||||
|
||||
val setting = List(
|
||||
char('#') ~> string("regset") ~> many1(whitespace) ~> (reg.map(Reg.apply) <~ sep, hex | int).mapN{REGSET.apply},
|
||||
char('#') ~> string("memset") ~> many1(whitespace) ~> ((hex | int).map(Addr.apply) <~ sep, hex | int).mapN{MEMSET.apply}
|
||||
).map(_.widen[TestSetting]).reduce(_|_)
|
||||
|
||||
|
||||
def parseProgram(p: List[String], testOptions: TestOptions): Either[String, Program] = {
|
||||
|
||||
val all = setting || (instruction || labelDest)
|
||||
|
||||
/**
|
||||
* The foldhelper represents a traversal through a RISC-V program.
|
||||
*
|
||||
* When it sees an op it records the operation and appends the source line and its location.
|
||||
* If it is in nopPad mode it will also insert NOPs between the parsed ops.
|
||||
* After appending ops the address counter is bumbed accordingly
|
||||
*
|
||||
* When it sees a label destination it checks what the current addres counter is at and creates
|
||||
* a link to this address.
|
||||
*
|
||||
* When it sees a parse error it simply stores the error and keeps going, allowing you to get every error
|
||||
* (This works for an ASM program since each line is independent)
|
||||
*
|
||||
* Lastly, when it sees a test setting it appends that test setting.
|
||||
*
|
||||
* The reason everything is treated all-in-one is to make it easier to ensure that everything is parsed.
|
||||
* If there were separate parsers for ops, labels and settings it would be difficult to find out if errors
|
||||
* were simply of the wrong type or a legit error.
|
||||
* This is not set in stone, if you're re-architecturing the code maybe it's better to separate parsers?
|
||||
* Or maybe have multiple passes? Up to you!
|
||||
*/
|
||||
case class FoldHelper(
|
||||
settings : List[TestSetting],
|
||||
ops : List[SourceInfo[Op]],
|
||||
labelMap : Map[Label, Addr],
|
||||
errors : List[String],
|
||||
addrCount : Int){
|
||||
def addSettings (t: TestSetting): FoldHelper = copy(settings = t :: settings)
|
||||
def addErrors (t: String): FoldHelper = copy(errors = t :: errors)
|
||||
def addLabelMap (t: Label): FoldHelper = copy(labelMap = labelMap + (t -> Addr(addrCount)))
|
||||
def addOps (t: List[SourceInfo[Op]]): FoldHelper = {
|
||||
if(testOptions.nopPadded){
|
||||
copy(
|
||||
ops = t.flatMap(x => (x :: List.fill(4)(SourceInfo("inserted NOP", NOP).widen[Op]))).reverse ::: ops,
|
||||
addrCount = addrCount + t.size*4*5)
|
||||
}
|
||||
else {
|
||||
copy(ops = t ::: ops, addrCount = addrCount + t.size*4)
|
||||
}
|
||||
}
|
||||
def program: Either[String, (List[TestSetting], List[SourceInfo[Op]], Map[Label, Addr])] = {
|
||||
/**
|
||||
* There are two possible ways for a program to successfully terminate, either by explicitly executing
|
||||
* a DONE instruction, or by returning from the main method.
|
||||
* In the latter case it is necessary to preload the return address register such that the return instruction
|
||||
* jumps to a predetermined special done address
|
||||
*/
|
||||
val hasDONEinstruction = ops.map(_.run._2).contains(DONE)
|
||||
val done = copy(settings = REGSET(Reg("sp"), 1024) :: settings, ops = ops.reverse, errors = errors.reverse)
|
||||
|
||||
val withReturnAddress = if(hasDONEinstruction){
|
||||
done
|
||||
}
|
||||
else
|
||||
done.copy(settings = REGSET(Reg("ra"), 0xEB1CEB1C) :: done.settings) // now that's what I call EPIC
|
||||
|
||||
Either.cond(errors.isEmpty, (withReturnAddress.settings, done.ops, labelMap), done.errors).left.map(errors =>
|
||||
s"Parser errors in ${testOptions.testName}:\n" + errors.mkString("\n"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def foldHelper(
|
||||
acc: FoldHelper,
|
||||
program: List[(Int, String)]): FoldHelper = program match {
|
||||
case Nil => acc
|
||||
case (lineNo, line) :: t => {
|
||||
if(line.isEmpty)
|
||||
foldHelper(acc, t)
|
||||
else {
|
||||
val next = all.parse(line).done.either match {
|
||||
case Left(parseError) => acc.addErrors(f"$lineNo%3d" +s":$line\t$parseError")
|
||||
case Right(Left(setting)) => acc.addSettings(setting)
|
||||
case Right(Right(Right(label))) => acc.addLabelMap(label)
|
||||
case Right(Right(Left(ops))) => acc.addOps(ops.map(op => SourceInfo(s"${lineNo.toString.padTo(3, ' ')}:\t$line", op)))
|
||||
}
|
||||
foldHelper(next, t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val results = foldHelper(FoldHelper(Nil, Nil, Map("zero" -> Addr(0)), Nil, 0), p.zipWithIndex.map(_.swap))
|
||||
|
||||
results.program.map{ case(settings, ops, labelMap) => Program(ops, settings, labelMap) }
|
||||
}
|
||||
|
||||
|
||||
def lookupReg(s: String): Int = {
|
||||
val regMap = Map(
|
||||
"x0" -> 0,
|
||||
"x1" -> 1,
|
||||
"x2" -> 2,
|
||||
"x3" -> 3,
|
||||
"x4" -> 4,
|
||||
"x5" -> 5,
|
||||
"x6" -> 6,
|
||||
"x7" -> 7,
|
||||
"x8" -> 8,
|
||||
"x9" -> 9,
|
||||
"x10" -> 10,
|
||||
"x11" -> 11,
|
||||
"x12" -> 12,
|
||||
"x13" -> 13,
|
||||
"x14" -> 14,
|
||||
"x15" -> 15,
|
||||
"x16" -> 16,
|
||||
"x17" -> 17,
|
||||
"x18" -> 18,
|
||||
"x19" -> 19,
|
||||
"x20" -> 20,
|
||||
"x21" -> 21,
|
||||
"x22" -> 22,
|
||||
"x23" -> 23,
|
||||
"x24" -> 24,
|
||||
"x25" -> 25,
|
||||
"x26" -> 26,
|
||||
"x27" -> 27,
|
||||
"x28" -> 28,
|
||||
"x29" -> 29,
|
||||
"x30" -> 30,
|
||||
"x31" -> 31,
|
||||
"zero" -> 0,
|
||||
"ra" -> 1,
|
||||
"sp" -> 2,
|
||||
"gp" -> 3,
|
||||
"tp" -> 4,
|
||||
"t0" -> 5,
|
||||
"t1" -> 6,
|
||||
"t2" -> 7,
|
||||
"s0" -> 8,
|
||||
"fp" -> 8,
|
||||
"s1" -> 9,
|
||||
"a0" -> 10,
|
||||
"a1" -> 11,
|
||||
"a2" -> 12,
|
||||
"a3" -> 13,
|
||||
"a4" -> 14,
|
||||
"a5" -> 15,
|
||||
"a6" -> 16,
|
||||
"a7" -> 17,
|
||||
"s2" -> 18,
|
||||
"s3" -> 19,
|
||||
"s4" -> 20,
|
||||
"s5" -> 21,
|
||||
"s6" -> 22,
|
||||
"s7" -> 23,
|
||||
"s8" -> 24,
|
||||
"s9" -> 25,
|
||||
"s10" -> 26,
|
||||
"s11" -> 27,
|
||||
"t3" -> 28,
|
||||
"t4" -> 29,
|
||||
"t5" -> 30,
|
||||
"t6" -> 31)
|
||||
|
||||
regMap(s)
|
||||
}
|
||||
}
|
171
src/test/scala/RISCV/VM.scala
Normal file
171
src/test/scala/RISCV/VM.scala
Normal file
|
@ -0,0 +1,171 @@
|
|||
package FiveStage
|
||||
import cats._
|
||||
import cats.data.{ Op => _ }
|
||||
import cats.implicits._
|
||||
import fileUtils._
|
||||
|
||||
import Data._
|
||||
import Ops._
|
||||
|
||||
import PrintUtils._
|
||||
|
||||
sealed trait Finished
|
||||
case object Success extends Finished
|
||||
case object Timeout extends Finished
|
||||
case class Failed(s: String, addr: Addr) extends Finished
|
||||
|
||||
case class VM(
|
||||
dmem : DMem,
|
||||
imem : Map[Addr, Op],
|
||||
regs : Regs,
|
||||
pc : Addr,
|
||||
labelMap : Map[Label, Addr]){
|
||||
def stepInstruction: Either[Finished, ExecutionTrace[VM]] = {
|
||||
if (pc.value == 0xEB1CEB1C) Left(Success)
|
||||
else getOp flatMap {
|
||||
case op: Branch => executeBranch(op)
|
||||
case op: Arith => executeArith(op)
|
||||
case op: ArithImm => executeArithImm(op)
|
||||
case op: AUIPC => executeAUIPC(op)
|
||||
case op: LUI => executeLUI(op)
|
||||
case op: JALR => executeJALR(op)
|
||||
case op: JAL => executeJAL(op)
|
||||
case op: LW => executeLW(op)
|
||||
case op: SW => executeSW(op)
|
||||
case DONE => Left(Success)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private def executeBranch(op: Branch) = {
|
||||
getAddr(op.dst).map{ addr =>
|
||||
val takeBranch = regs.compare(op.rs1, op.rs2, op.comp.run)
|
||||
if(takeBranch){
|
||||
val nextVM = copy(pc = addr)
|
||||
jump(nextVM, PcUpdateB(nextVM.pc))
|
||||
}
|
||||
else {
|
||||
step(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The weird :_* syntax is simply a way to pass a list to a varArgs function.
|
||||
*
|
||||
* To see why, consider def printMany(a: Any*); printMany(List(1,2,3))
|
||||
* It is now ambiguous if it should print "1, 2, 3" or List(1, 2, 3)
|
||||
*
|
||||
* This is disambiguated by appending :_*, thus
|
||||
* printMany(List(1,2,3):_*) == printMany(1, 2, 3)
|
||||
*/
|
||||
private def executeArith(op: Arith) = {
|
||||
val (regUpdate, nextRegs) = regs.arith(op.rd, op.rs1, op.rs2, op.op.run)
|
||||
val nextVM = this.copy(regs = nextRegs)
|
||||
Right(step(nextVM, regUpdate.toList:_*))
|
||||
}
|
||||
|
||||
|
||||
private def executeArithImm(op: ArithImm) = {
|
||||
val (regUpdate, nextRegs) = regs.arithImm(op.rd, op.rs1, op.imm, op.op.run)
|
||||
val nextVM = this.copy(regs = nextRegs)
|
||||
Right(step(nextVM, regUpdate.toList:_*))
|
||||
}
|
||||
|
||||
|
||||
private def executeLUI(op: LUI) = {
|
||||
val (regUpdate, nextRegs) = regs + (op.rd -> (op.imm.value << 12))
|
||||
val nextVM = this.copy(regs = nextRegs)
|
||||
Right(step(nextVM, regUpdate.toList:_*))
|
||||
}
|
||||
|
||||
|
||||
private def executeAUIPC(op: AUIPC) = {
|
||||
val (regUpdate, nextRegs) = regs + (op.rd -> (pc.value << 12))
|
||||
val nextVM = this.copy(regs = nextRegs)
|
||||
Right(step(nextVM, regUpdate.toList:_*))
|
||||
}
|
||||
|
||||
|
||||
|
||||
private def executeJALR(op: JALR) = getAddr(op.dst).map{ targetAddr =>
|
||||
val nextPc = Addr((regs.repr(op.rs1).value + targetAddr.value) & 0xFFFFFFFE)
|
||||
val (regUpdate, nextRegs) = regs + (op.rd -> (pc.step.value))
|
||||
val nextVM = this.copy(regs = nextRegs, pc = nextPc)
|
||||
jump(nextVM, (PcUpdateJALR(nextPc) :: regUpdate.toList):_*)
|
||||
}
|
||||
|
||||
|
||||
private def executeJAL(op: JAL) = getAddr(op.dst).map{ targetAddr =>
|
||||
val nextPc = targetAddr
|
||||
val (regUpdate, nextRegs) = regs + (op.rd -> (pc.step.value))
|
||||
val nextVM = this.copy(regs = nextRegs, pc = nextPc)
|
||||
jump(nextVM, (PcUpdateJAL(nextPc) :: regUpdate.toList):_*)
|
||||
}
|
||||
|
||||
|
||||
private def executeLW(op: LW) = dmem.read(Addr(regs.repr(op.rs1) + op.offset.value)).map{ case(event, result) =>
|
||||
val (regUpdate, nextRegs) = regs + (op.rd -> result)
|
||||
val nextVM = this.copy(regs = nextRegs)
|
||||
step(nextVM, (event :: regUpdate.toList):_*)
|
||||
}.left.map(x => Failed(x, pc))
|
||||
|
||||
private def executeSW(op: SW) = {
|
||||
val writeAddress = Addr(regs.repr(op.rs1) + op.offset.value)
|
||||
val writeData = regs.repr(op.rs2)
|
||||
dmem.write(writeAddress, writeData).map{ case(event, nextDmem) =>
|
||||
val nextVM = this.copy(dmem = nextDmem)
|
||||
step(nextVM, event)
|
||||
}
|
||||
}.left.map(x => Failed(x, pc))
|
||||
|
||||
private def step(nextVM: VM, event: ExecutionEvent*) =
|
||||
ExecutionTrace(nextVM.stepPC, ExecutionTraceEvent(pc, event:_*))
|
||||
|
||||
// Same as above, but no stepping
|
||||
private def jump(nextVM: VM, event: ExecutionEvent*) =
|
||||
ExecutionTrace(nextVM, ExecutionTraceEvent(pc, event:_*))
|
||||
|
||||
|
||||
private def stepPC: VM = copy(pc = this.pc.step)
|
||||
|
||||
private def getAddr(dst: Label): Either[Failed, Addr] =
|
||||
labelMap.lift(dst).toRight(Failed(s"Label $dst missing", pc))
|
||||
|
||||
private def getOp: Either[Failed, Op] =
|
||||
imem.lift(pc).toRight(Failed(s"Attempted to fetch instruction at illegal address ${pc.show}", pc))
|
||||
}
|
||||
|
||||
|
||||
object VM {
|
||||
|
||||
val init = VM(DMem.empty, Map[Addr, Op](), Regs.empty, Addr(0), Map[Label, Addr]())
|
||||
|
||||
def apply(settings: List[TestSetting], imem: Map[Addr, Op], labelMap: Map[Label, Addr]): VM = {
|
||||
val (dmem, regs) = settings.foldLeft((DMem.empty, Regs.empty)){ case((dmem, regs), setting) => setting match {
|
||||
case setting: REGSET => (dmem, regs(setting))
|
||||
case setting: MEMSET => (dmem(setting), regs)
|
||||
}
|
||||
}
|
||||
VM(dmem, imem, regs, Addr(0), labelMap)
|
||||
}
|
||||
|
||||
def run(maxSteps: Int, vm: VM) = {
|
||||
def helper(state: ExecutionTrace[VM], step: Int): (Finished, ExecutionTrace[VM]) = {
|
||||
if(step > 0){
|
||||
val (log, vm) = state.run
|
||||
val next = vm.stepInstruction
|
||||
next match {
|
||||
case Left(stopped) => (stopped, state)
|
||||
case Right(trace) => helper((state >> trace), step - 1)
|
||||
}
|
||||
}
|
||||
else{
|
||||
(Timeout, state)
|
||||
}
|
||||
}
|
||||
helper(ExecutionTrace(vm), maxSteps)
|
||||
}
|
||||
}
|
269
src/test/scala/RISCV/assembler.scala
Normal file
269
src/test/scala/RISCV/assembler.scala
Normal file
|
@ -0,0 +1,269 @@
|
|||
package FiveStage
|
||||
import cats.implicits._
|
||||
|
||||
import Data._
|
||||
import Ops._
|
||||
import fileUtils._
|
||||
import PrintUtils._
|
||||
|
||||
object assembler {
|
||||
|
||||
type InstructionFragment = Either[(String, Addr), Int]
|
||||
|
||||
/**
|
||||
* Will only be called by applyImmedate, thus error propagation is not needed
|
||||
* Kind of a kludge, but it works, don't touch!
|
||||
*/
|
||||
def setField(firstBit: Int, size: Int, field: Int): Int => Int = instruction => {
|
||||
val shiftedField = field << firstBit
|
||||
val mask = (1 << size) - 1
|
||||
val shiftedMask = (mask << firstBit)
|
||||
val masked = ((~instruction) | shiftedMask)
|
||||
val maskedInv = ~(masked)
|
||||
|
||||
val ret = (shiftedField & shiftedMask) | maskedInv
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
|
||||
def getSubField(firstBit: Int, size: Int): Int => Int = word => {
|
||||
val bitsLeft = 32 - firstBit
|
||||
val bitsRight = 32 - size
|
||||
val leftShifted = word << bitsLeft
|
||||
val rightShifted = leftShifted >> bitsRight
|
||||
rightShifted
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Splits the immediate value into fields given by points.
|
||||
The order of points is important!
|
||||
points is of type idx, size
|
||||
*/
|
||||
def applyImmediate(immediateBits: Int, immediate: Int, points: List[(Int, Int)]): Int => Int = instruction => {
|
||||
|
||||
def go(instruction: Int, immediateIndex: Int, points: List[(Int,Int)]): Int = points match {
|
||||
case h :: t => {
|
||||
val (immFirstBit, size) = h
|
||||
val firstBit = (immFirstBit - size) + 1
|
||||
val immSubField = getSubField(immediateIndex, size)(immediate)
|
||||
val nextImmIndex = immediateIndex - size
|
||||
val nextInstruction = setField(firstBit, size, immSubField)(instruction)
|
||||
go(nextInstruction, nextImmIndex, points.tail)
|
||||
}
|
||||
case _ => {
|
||||
instruction
|
||||
}
|
||||
}
|
||||
|
||||
go(instruction, immediateBits, points)
|
||||
}
|
||||
|
||||
|
||||
def applyImmediateU(immediate: Int, points: List[(Int, Int)], addr: Addr): Int => InstructionFragment = instruction => {
|
||||
def totalBits = points.foldLeft(0){ case(acc, (first, size)) => acc + size }
|
||||
totalBits.nBitsU.toRight("Negative number used as unsigned immediate", addr).flatMap { bits =>
|
||||
Either.cond(bits < totalBits, applyImmediate(totalBits, immediate, points)(instruction), ("Immediate unsigned too large", addr))
|
||||
}
|
||||
}
|
||||
|
||||
def applyImmediateS(immediate: Int, points: List[(Int, Int)], addr: Addr): Int => InstructionFragment = instruction => {
|
||||
def totalBits = points.foldLeft(0){ case(acc, (first, size)) => acc + size }
|
||||
if(totalBits < immediate.nBitsS) Left((s"Immediate signed too large. immedate: $immediate, immediate.nBitsS = ${immediate.nBitsS}, total bits: $totalBits", addr))
|
||||
else Right(applyImmediate(totalBits, immediate, points)(instruction))
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used by JALR, LW and arithmetic immediate ops.
|
||||
* JALR is sort of the odd man out here as it should be unsigned.
|
||||
* This issue should not surface at the very limited address space
|
||||
* for your design. (I hope)
|
||||
*/
|
||||
def setItypeImmediate(immediate: Int, addr: Addr): Int => InstructionFragment = {
|
||||
val points = List((31, 12))
|
||||
val withField = applyImmediateS(immediate, points, addr)
|
||||
withField
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by SW
|
||||
*/
|
||||
def setStypeImmediate(immediate: Int, addr: Addr): Int => InstructionFragment = {
|
||||
val points = List((31, 7), (11, 5))
|
||||
val withField = applyImmediateS(immediate, points, addr)
|
||||
withField
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by Branches. PC relative jump, thus signed
|
||||
* Last bit is not used, hence the shift
|
||||
*/
|
||||
def setBtypeImmediate(immediate: Int, addr: Addr): Int => InstructionFragment = {
|
||||
val points = List((31, 1), (7, 1), (30, 6), (11, 4))
|
||||
val withField = applyImmediateS((immediate >> 1), points, addr)
|
||||
withField
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by LUI and AUIPC. Unsigned
|
||||
*/
|
||||
def setUtypeImmediate(immediate: Int, addr: Addr): Int => InstructionFragment = {
|
||||
val points = List((31, 20))
|
||||
val withField = applyImmediateU(immediate, points, addr)
|
||||
withField
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by JAL. PC relative jump, thus signed
|
||||
* The last bit is not used, hence the shift
|
||||
*/
|
||||
def setJtypeImmediate(immediate: Int, addr: Addr): Int => InstructionFragment = {
|
||||
val points = List((31, 1), (19, 8), (20, 1), (30, 10))
|
||||
applyImmediateU((immediate >> 1), points, addr)
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently not used, thus we allow too larg shifts.
|
||||
*/
|
||||
def setShiftTypeImmediate(instruction: Int, immediate: Int): Int = {
|
||||
val points = List((24, 5))
|
||||
val withField = applyImmediate(5, immediate, points)(instruction)
|
||||
withField
|
||||
}
|
||||
|
||||
def setOpCode(opcode: Int): Int => Int = setField(0, 7, opcode)
|
||||
def setFunct7(funct7: Int): Int => Int = setField(25, 7, funct7)
|
||||
def setFunct3(funct3: Int): Int => Int = setField(12, 3, funct3)
|
||||
def setRs1(rs1: Int): Int => Int = setField(15, 5, rs1)
|
||||
def setRs2(rs2: Int): Int => Int = setField(20, 5, rs2)
|
||||
def setRd(rd: Int): Int => Int = setField(7, 5, rd)
|
||||
|
||||
|
||||
def setOpCode(op: Op): Int => Int = op match {
|
||||
case x: Branch => setOpCode("1100011".binary)
|
||||
case x: Arith => setOpCode("0110011".binary)
|
||||
case x: ArithImm => setOpCode("0010011".binary)
|
||||
case x: LW => setOpCode("0000011".binary)
|
||||
case x: SW => setOpCode("0100011".binary)
|
||||
case x: JALR => setOpCode("1100111".binary)
|
||||
case x: JAL => setOpCode("1101111".binary)
|
||||
case x: AUIPC => setOpCode("0110111".binary)
|
||||
case x: LUI => setOpCode("0010111".binary)
|
||||
case DONE => setOpCode("0010011".binary) // done is turned into a NOP in the assembler.
|
||||
}
|
||||
|
||||
def setComparisonFunct(cmp: Comparison): Int => Int = cmp match {
|
||||
case EQ => setFunct3("000".binary)
|
||||
case NE => setFunct3("001".binary)
|
||||
case GE => setFunct3("101".binary)
|
||||
case LT => setFunct3("100".binary)
|
||||
case GEU => setFunct3("111".binary)
|
||||
case LTU => setFunct3("110".binary)
|
||||
}
|
||||
|
||||
def setBranchDestination(labelMap: Map[Label, Addr], op: Branch, opAddr: Addr): Int => InstructionFragment = instruction => {
|
||||
labelMap.lift(op.dst).toRight((s"destination ${op.dst} not found", opAddr)).flatMap{ dstAddr =>
|
||||
setBtypeImmediate(dstAddr.value - opAddr.value, opAddr)(instruction)
|
||||
}
|
||||
}
|
||||
|
||||
def setArithFunct(op: ArithOp): Int => Int = op match {
|
||||
case ADD => setFunct7("0000000".binary) andThen setFunct3("000".binary)
|
||||
case SUB => setFunct7("0100000".binary) andThen setFunct3("000".binary)
|
||||
case SLL => setFunct7("0000000".binary) andThen setFunct3("001".binary)
|
||||
case SLT => setFunct7("0000000".binary) andThen setFunct3("010".binary)
|
||||
case SLTU => setFunct7("0000000".binary) andThen setFunct3("011".binary)
|
||||
case XOR => setFunct7("0000000".binary) andThen setFunct3("100".binary)
|
||||
case SRL => setFunct7("0000000".binary) andThen setFunct3("101".binary)
|
||||
case SRA => setFunct7("0100000".binary) andThen setFunct3("101".binary)
|
||||
case OR => setFunct7("0000000".binary) andThen setFunct3("110".binary)
|
||||
case AND => setFunct7("0000000".binary) andThen setFunct3("111".binary)
|
||||
}
|
||||
|
||||
|
||||
def assembleRegLayout(op: RegLayout): Int => Int = {
|
||||
|
||||
def assembleRType(op: RType): Int => Int =
|
||||
setRd(op.rd.value) andThen
|
||||
setRs1(op.rs1.value) andThen
|
||||
setRs2(op.rs2.value)
|
||||
|
||||
def assembleIType(op: IType): Int => Int =
|
||||
setRd(op.rd.value) andThen
|
||||
setRs1(op.rs1.value)
|
||||
|
||||
def assembleSType(op: SType): Int => Int = {
|
||||
// say("stype")
|
||||
instruction =>
|
||||
(setRs1(op.rs1.value) andThen
|
||||
setRs2(op.rs2.value))(instruction)
|
||||
}
|
||||
|
||||
def assembleUType(op: UType): Int => Int =
|
||||
setRd(op.rd.value)
|
||||
|
||||
op match {
|
||||
case op: RType => assembleRType(op)
|
||||
case op: IType => assembleIType(op)
|
||||
case op: SType => assembleSType(op)
|
||||
case op: UType => assembleUType(op)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def assembleImmediate(op: Op, addr: Addr, labelMap: Map[Label, Addr]): Int => Either[(String, Addr), Int] = op match {
|
||||
case DONE => instruction => Right(instruction)
|
||||
case op: Arith => instruction => Right(instruction)
|
||||
case op: ArithImm => setItypeImmediate(op.imm.value, addr)
|
||||
case op: Branch => setBranchDestination(labelMap, op, addr)
|
||||
case op: JALR => instruction => labelMap.lift(op.dst).toRight(s"label ${op.dst} not found", addr).flatMap(addr => setItypeImmediate(addr.value, addr)(instruction))
|
||||
case op: AUIPC => setUtypeImmediate(op.imm.value, addr)
|
||||
case op: LUI => setUtypeImmediate(op.imm.value, addr)
|
||||
case op: LW => setItypeImmediate(op.offset.value, addr)
|
||||
case op: SW => setStypeImmediate(op.offset.value, addr)
|
||||
case op: JAL => instruction => {
|
||||
val addressDistance = labelMap
|
||||
.lift(op.dst).toRight(s"label ${op.dst} not found", addr)
|
||||
.map(absoluteAddr => absoluteAddr - addr)
|
||||
|
||||
import PrintUtils._
|
||||
|
||||
addressDistance.flatMap(distance =>
|
||||
setJtypeImmediate(distance.value, addr)(instruction))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def assembleOp(
|
||||
op : Op with RegLayout,
|
||||
opAddr : Addr,
|
||||
labelMap : Map[Label, Addr]): Either[(String, Addr), Int] = {
|
||||
|
||||
val layout = assembleRegLayout(op)
|
||||
val immediate = assembleImmediate(op, opAddr, labelMap)
|
||||
val opcode = setOpCode(op)
|
||||
|
||||
val extras: Int => Int = (instruction: Int) => op match {
|
||||
case op: Branch => setComparisonFunct(op.comp)(instruction)
|
||||
case op: ArithImm => setArithFunct(op.op)(instruction)
|
||||
case op: Arith => setArithFunct(op.op)(instruction)
|
||||
case op: JALR => setFunct3("000".binary)(instruction)
|
||||
case op: LW => setFunct3("010".binary)(instruction)
|
||||
case op: SW => setFunct3("010".binary)(instruction)
|
||||
case DONE => (setFunct3("000".binary) andThen setFunct7("0000000".binary))(instruction)
|
||||
case _ => instruction
|
||||
}
|
||||
|
||||
val withOp = opcode(0)
|
||||
val withLayout = layout(withOp)
|
||||
val withImmediates = immediate(withLayout)
|
||||
val withExtras = withImmediates.map(extras)
|
||||
|
||||
|
||||
val finalOp = (opcode andThen layout andThen extras andThen immediate)(0)
|
||||
|
||||
finalOp
|
||||
}
|
||||
}
|
178
src/test/scala/RISCV/deleteMe.scala
Normal file
178
src/test/scala/RISCV/deleteMe.scala
Normal file
|
@ -0,0 +1,178 @@
|
|||
package FiveStage
|
||||
import cats.data.Writer
|
||||
import cats._
|
||||
import cats.data.{ Op => _ }
|
||||
import cats.implicits._
|
||||
|
||||
object DTree {
|
||||
|
||||
// opaques WHEN
|
||||
type Feature = String
|
||||
type Value = String
|
||||
type Cls = String
|
||||
|
||||
case class TrainingData(values: Map[Feature, Value], cls: Cls)
|
||||
|
||||
type TestData = Map[Feature, Value]
|
||||
|
||||
// Base
|
||||
// def predict(data: TestData): Cls = "Died"
|
||||
|
||||
// Gendered
|
||||
def predictStatic(data: TestData): Cls =
|
||||
if(data("gender") == "female")
|
||||
"survived"
|
||||
else "died"
|
||||
|
||||
|
||||
sealed trait Tree
|
||||
case class Node(feature: Feature, children: Map[Value, Tree]) extends Tree
|
||||
case class Leaf(cls: Cls) extends Tree
|
||||
|
||||
val genderBased: Tree = Node(
|
||||
"gender", Map(
|
||||
"female" -> Leaf("survived"),
|
||||
"male" -> Leaf("died"),
|
||||
))
|
||||
|
||||
|
||||
def predict(data: TestData)(tree: Tree): Cls =
|
||||
tree match {
|
||||
case Leaf(cls) => cls
|
||||
case Node(feature, children) => predict(data)(children(data(feature)))
|
||||
}
|
||||
|
||||
val data = Map("gender" -> "female", "family size" -> "0", "ticket" -> "1")
|
||||
|
||||
predict(data)(genderBased) // true
|
||||
|
||||
|
||||
def entropy(classes: List[Cls]): Double = {
|
||||
val total = classes.size
|
||||
classes.groupBy(identity)
|
||||
.mapValues { group =>
|
||||
val prop = group.size / total
|
||||
prop * math.log(1.0 / prop)
|
||||
}.values.sum
|
||||
}
|
||||
|
||||
def bucketedEntropy(data: List[TrainingData], feature: Feature): Double = {
|
||||
val total = data.size
|
||||
val bucketed = data.groupBy(_.values(feature))
|
||||
.mapValues(_.map(_.cls))
|
||||
.toMap
|
||||
bucketed.values.map { classes =>
|
||||
val prop = classes.size / total
|
||||
prop * entropy(classes)
|
||||
}.sum
|
||||
}
|
||||
|
||||
def best(data: List[TrainingData], features: Set[Feature]): Feature = features.minBy(bucketedEntropy(data, _))
|
||||
|
||||
def mostCommonCls(data: List[TrainingData]): Cls = ???
|
||||
|
||||
def build(data: List[TrainingData], features: Set[Feature]): Tree = {
|
||||
if(features.nonEmpty) {
|
||||
val feature = best(data, features)
|
||||
val buckets = data.groupBy(_.values(feature))
|
||||
Node(feature, buckets.mapValues(build(_, features - feature)))
|
||||
} else {
|
||||
Leaf(mostCommonCls(data))
|
||||
}
|
||||
}
|
||||
|
||||
def withHKT {
|
||||
|
||||
sealed trait Tree[A]
|
||||
case class Node[A](feature: Feature, children: Map[Value, A]) extends Tree[A]
|
||||
case class Leaf[A](cls: Cls) extends Tree[A]
|
||||
|
||||
// import matryoshka._
|
||||
// import matryoshka.data.Fix
|
||||
// import matryoshka.implicits._
|
||||
|
||||
case class Fix[F[_]](unfix: F[Fix[F]])
|
||||
case class Cofree[F[_], A](head: A, tail: F[Cofree[F, A]]){
|
||||
def counit: A = head
|
||||
def map[B](f: A => B)(implicit ev: Functor[F]): Cofree[F, B] = Cofree(f(head), tail.map(_.map(f)))
|
||||
|
||||
/**
|
||||
* Coflatmap alters the value of the node based on its context, then recursively
|
||||
* alters its tail independently (which makes sense as it's the only thing Cofree[F, A] => B can do.
|
||||
*/
|
||||
def coflatMap[B](fa: Cofree[F, A] => B)(implicit ev: Functor[F]): Cofree[F, B] = {
|
||||
val b = fa(this)
|
||||
val fb = tail.map(_.coflatMap(fa))
|
||||
Cofree(b, fb)
|
||||
}
|
||||
}
|
||||
|
||||
implicit val treeFunctor: Functor[Tree] = new Functor[Tree] {
|
||||
def map[A, B](fa: Tree[A])(f: A => B): Tree[B] = fa match {
|
||||
case Node(name, children) => Node(name, children.mapValues(f))
|
||||
case Leaf(cls) => Leaf(cls)
|
||||
}
|
||||
}
|
||||
|
||||
val genderBased: Fix[Tree] =
|
||||
Fix(Node(
|
||||
"gender",
|
||||
Map(
|
||||
"female" -> Fix(Leaf[Fix[Tree]]("survived")),
|
||||
"male" -> Fix(Leaf[Fix[Tree]]("died"))
|
||||
)))
|
||||
|
||||
def build: ((List[TrainingData], Set[Feature])) => Tree[(List[TrainingData], Set[Feature])] = {
|
||||
case (data, features) =>
|
||||
if(features.nonEmpty) {
|
||||
val feature = best(data, features)
|
||||
val buckets = data.groupBy(_.values(feature))
|
||||
val next = buckets.mapValues { subset => (subset, features - feature) }
|
||||
Node(feature, next)
|
||||
} else {
|
||||
Leaf(mostCommonCls(data))
|
||||
}
|
||||
}
|
||||
|
||||
def explore(testData: TestData): Fix[Tree] => Cls Either Fix[Tree] =
|
||||
fix => fix.unfix match {
|
||||
case Leaf(cls) => Left(cls)
|
||||
case Node(feature, children) => Right(children.get(testData(feature)).get)
|
||||
}
|
||||
|
||||
// Anamorphism: Generalized unfold, builds structures
|
||||
def ana[F[_]: Functor, A](f: A => F[A])(a: A): Fix[F] =
|
||||
Fix( (f(a)).map(ana(f)) )
|
||||
|
||||
// Catamorphism: Generalized fold, tears structures down.
|
||||
def cata[F[_]: Functor, A](fa: F[A] => A)(f: Fix[F]): A = {
|
||||
fa(f.unfix.map(cata(fa)))
|
||||
}
|
||||
|
||||
// def hyloSimple[F[_] : Functor, A, B](f: F[B] => B)(g: A => F[A]): A => B
|
||||
def hyloSimple[F[_]: Functor, A, B](f: F[B] => B)(g: A => F[A])(a: A): B =
|
||||
cata(f)(ana(g)(a))
|
||||
|
||||
// A more powerful cata
|
||||
def para[F[_]: Functor, A](f: F[(Fix[F], A)] => A)(fa: Fix[F]): A =
|
||||
f(fa.unfix.map(x => (x, para(f)(x))))
|
||||
|
||||
// A more powerful ana
|
||||
def apo[F[_]: Functor, A](f: A => F[Either[Fix[F], A]])(a: A): Fix[F] = {
|
||||
Fix(f(a).map{
|
||||
case Right(a) => apo(f)(a)
|
||||
case Left(fix) => fix
|
||||
})
|
||||
}
|
||||
|
||||
// When we have cofree
|
||||
def histo[F[_]: Functor, A](f: F[Cofree[F, A]] => A)(fix: Fix[F]): A = {
|
||||
def toCofree(fix: Fix[F]): Cofree[F, A] =
|
||||
Cofree(histo(f)(fix), fix.unfix.map(toCofree))
|
||||
|
||||
f(fix.unfix.map(toCofree))
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
164
src/test/scala/RISCV/deleteMe2.scala
Normal file
164
src/test/scala/RISCV/deleteMe2.scala
Normal file
|
@ -0,0 +1,164 @@
|
|||
package FiveStage
|
||||
import cats.data.Writer
|
||||
import cats._
|
||||
import cats.data.{ Op => _ }
|
||||
import cats.implicits._
|
||||
|
||||
import fileUtils.say
|
||||
|
||||
object DeletDis {
|
||||
|
||||
def delet = {
|
||||
|
||||
case class Fix[F[_]](unfix: F[Fix[F]])
|
||||
case class Cofree[F[_], A](head: A, tail: F[Cofree[F, A]]){
|
||||
def counit: A = head
|
||||
def map[B](f: A => B)(implicit ev: Functor[F]): Cofree[F, B] = Cofree(f(head), tail.map(_.map(f)))
|
||||
|
||||
/**
|
||||
* Coflatmap alters the value of the node based on its context, then recursively
|
||||
* alters its tail independently (which makes sense as it's the only thing Cofree[F, A] => B can do.
|
||||
*/
|
||||
def coflatMap[B](fa: Cofree[F, A] => B)(implicit ev: Functor[F]): Cofree[F, B] = {
|
||||
val b = fa(this)
|
||||
val fb = tail.map(_.coflatMap(fa))
|
||||
Cofree(b, fb)
|
||||
}
|
||||
}
|
||||
|
||||
// Anamorphism: Generalized unfold, builds structures
|
||||
def ana[F[_]: Functor, A](f: A => F[A])(a: A): Fix[F] =
|
||||
Fix( (f(a)).map(ana(f)) )
|
||||
|
||||
// Catamorphism: Generalized fold, tears structures down.
|
||||
def cata[F[_]: Functor, A](fa: F[A] => A)(f: Fix[F]): A = {
|
||||
fa(f.unfix.map(cata(fa)))
|
||||
}
|
||||
|
||||
// def hyloSimple[F[_] : Functor, A, B](f: F[B] => B)(g: A => F[A]): A => B
|
||||
def hylo[F[_]: Functor, A, B](f: F[B] => B)(g: A => F[A])(a: A): B =
|
||||
cata(f)(ana(g)(a))
|
||||
|
||||
// A more powerful cata
|
||||
def para[F[_]: Functor, A](f: F[(Fix[F], A)] => A)(fa: Fix[F]): A =
|
||||
f(fa.unfix.map(x => (x, para(f)(x))))
|
||||
|
||||
// A more powerful ana
|
||||
def apo[F[_]: Functor, A](f: A => F[Either[Fix[F], A]])(a: A): Fix[F] = {
|
||||
Fix(f(a).map{
|
||||
case Right(a) => apo(f)(a)
|
||||
case Left(fix) => fix
|
||||
})
|
||||
}
|
||||
|
||||
// When we have cofree
|
||||
def histo[F[_]: Functor, A](f: F[Cofree[F, A]] => A)(fix: Fix[F]): A = {
|
||||
def toCofree(fix: Fix[F]): Cofree[F, A] =
|
||||
Cofree(histo(f)(fix), fix.unfix.map(toCofree))
|
||||
|
||||
f(fix.unfix.map(toCofree))
|
||||
}
|
||||
|
||||
sealed trait StackR
|
||||
final case class DoneR(result: Int = 1) extends StackR
|
||||
final case class MoreR(stack: StackR, next: Int) extends StackR
|
||||
|
||||
def unfoldStackR(n: Int): StackR =
|
||||
if(n > 0) MoreR(unfoldStackR(n-1), n) else DoneR()
|
||||
|
||||
say(unfoldStackR(5))
|
||||
|
||||
sealed trait Stack[A]
|
||||
final case class Done[A](result: Int) extends Stack[A]
|
||||
final case class More[A](a: A, next: Int) extends Stack[A]
|
||||
|
||||
object Stack {
|
||||
implicit val stackFunctor: Functor[Stack] = new Functor[Stack] {
|
||||
def map[A, B](fa: Stack[A])(f: A => B): Stack[B] = fa match {
|
||||
case Done(result) => Done(result)
|
||||
case More(a, next) => More(f(a), next)
|
||||
}
|
||||
}
|
||||
|
||||
def done[A](result: Int = 1): Stack[A] = Done(result)
|
||||
def more[A](a: A, next: Int): Stack[A] = More(a, next)
|
||||
}
|
||||
|
||||
import Stack._
|
||||
|
||||
val stackCoalgebra: Int => Stack[Int] =
|
||||
n => if(n > 0) more(n - 1, n) else done()
|
||||
|
||||
say(ana(stackCoalgebra)(5))
|
||||
|
||||
val stackAlgebra: Stack[Int] => Int = {
|
||||
case Done(result) => result
|
||||
case More(acc, next) => acc * next
|
||||
}
|
||||
|
||||
say(cata(stackAlgebra)(ana(stackCoalgebra)(5)))
|
||||
say(hylo(stackAlgebra)(stackCoalgebra)(5))
|
||||
|
||||
|
||||
sealed trait Nat[A]
|
||||
final case class Zero[A]() extends Nat[A]
|
||||
final case class Succ[A](prev: A) extends Nat[A]
|
||||
|
||||
object Nat {
|
||||
implicit val natFunctor: Functor[Nat] = new Functor[Nat] {
|
||||
override def map[A, B](na: Nat[A])(f: A => B): Nat[B] =
|
||||
na match {
|
||||
case Zero() => Zero()
|
||||
case Succ(a) => Succ(f(a))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val natAlgebra: Nat[Int] => Int = {
|
||||
case Zero() => 1
|
||||
case Succ(n) => {
|
||||
say(s"nat alg succ $n")
|
||||
n + 1
|
||||
}
|
||||
}
|
||||
|
||||
val natAlgebraS: Nat[String] => String = {
|
||||
case Zero() => "N"
|
||||
case Succ(n) => n match {
|
||||
case "N" => "NI"
|
||||
case "NI" => "NIG"
|
||||
case "NIG" => "NIGG"
|
||||
case "NIGG" => "NIGGE"
|
||||
case "NIGGE" => "NIGGER :-"
|
||||
case s => s + "D"
|
||||
}
|
||||
}
|
||||
|
||||
val natCoalgebra: Int => Nat[Int] =
|
||||
n => if (n == 0) Zero() else Succ(n - 1)
|
||||
|
||||
val b = ana(natCoalgebra)(9)
|
||||
val c = cata(natAlgebraS)(b)
|
||||
say(c)
|
||||
|
||||
|
||||
val natAlgebraPara: Nat[(Fix[Nat], Int)] => Int = {
|
||||
case Zero() => 1
|
||||
case Succ((fix, acc)) => {
|
||||
say(s"nat para alg succ $fix, $acc")
|
||||
cata(natAlgebra)(fix) * acc
|
||||
}
|
||||
}
|
||||
|
||||
val build = ana(natCoalgebra)(_)
|
||||
say("built")
|
||||
val tear = para(natAlgebraPara)(_)
|
||||
say(tear(build(5)))
|
||||
// say(ana(natCoalgebra)(5))
|
||||
|
||||
val lastThreeSteps: Fix[Stack] = Fix(More(Fix(More(Fix(More(Fix(Done(1)),1)),2)),3))
|
||||
|
||||
val stackCoalgebraApo: Int => Stack[Either[Fix[Stack], Int]] =
|
||||
n => if(n > 3) more(n - 1, n).map(_.asRight) else lastThreeSteps.unfix.map(_.asLeft)
|
||||
}
|
||||
}
|
352
src/test/scala/RISCV/printUtils.scala
Normal file
352
src/test/scala/RISCV/printUtils.scala
Normal file
|
@ -0,0 +1,352 @@
|
|||
package FiveStage
|
||||
import cats.data.Writer
|
||||
import cats._
|
||||
import cats.data.{ Op => _ }
|
||||
import cats.implicits._
|
||||
|
||||
import fansi._
|
||||
|
||||
import Ops._
|
||||
import Data._
|
||||
import VM._
|
||||
import fileUtils._
|
||||
|
||||
object PrintUtils {
|
||||
|
||||
// Minimal typeclass def for being fancy
|
||||
trait Fancy[-A] { def s(a: A): fansi.Str }
|
||||
implicit class FancyOps[-A](a: A)(implicit ev: Fancy[A]){ def show: fansi.Str = ev.s(a) }
|
||||
implicit val DoubleFancy = new Fancy[fansi.Str]{ def s(a: fansi.Str) = a }
|
||||
implicit val FancyMonoid = new Monoid[fansi.Str] {
|
||||
def empty: fansi.Str = fansi.Str("")
|
||||
def combine(self: fansi.Str, that: fansi.Str): fansi.Str = self ++ that
|
||||
}
|
||||
|
||||
implicit class ExtraFancy(a: fansi.Str) {
|
||||
def leftPad(length: Int): fansi.Str = Str((0 until length).map(_ => ' ').mkString) ++ a
|
||||
def replace(from: Char, to: Char): fansi.Str = fansi.Str(a.toString.replace(from, to))
|
||||
def padTo(length: Int, pad: Char = ' '): fansi.Str = a ++ (0 until length - a.length).map(_ => pad).mkString
|
||||
def trim: fansi.Str = fansi.Str(a.toString.trim)
|
||||
}
|
||||
|
||||
implicit val RegFancy = new Fancy[Reg] { def s(r: Reg) = fansi.Str(regNames.canonicalName.lift(r.value).getOrElse("ERROR")) }
|
||||
implicit val ImmFancy = new Fancy[Imm] { def s(r: Imm) = Str(r.value.hs) }
|
||||
implicit val AddrFancy = new Fancy[Addr]{ def s(r: Addr) = Str(r.value.hs) }
|
||||
|
||||
implicit val ExecutionEventFancy = new Fancy[ExecutionEvent]{
|
||||
def s(a: ExecutionEvent): fansi.Str = a match {
|
||||
case RegUpdate(reg, word) => Str(s"R(${reg.show}) <- 0x${word.hs}")
|
||||
case MemWrite(addr, word) => fansi.Color.Blue(s"M[${addr.show}] <- 0x${word.hs}")
|
||||
case MemRead(addr, word) => fansi.Color.Red(f"M[${addr.show}] -> 0x${word.hs}")
|
||||
|
||||
// addr is the target address
|
||||
case PcUpdateJALR(addr) => fansi.Color.Green(s"PC updated to ${addr.show} via JALR")
|
||||
case PcUpdateJAL(addr) => fansi.Color.Magenta(s"PC updated to ${addr.show} via JAL")
|
||||
case PcUpdateB(addr) => fansi.Color.Yellow(s"PC updated to ${addr.show} via Branch")
|
||||
}
|
||||
}
|
||||
|
||||
implicit val ExecutionTraceEventFancy = new Fancy[ExecutionTraceEvent]{
|
||||
def s(a: ExecutionTraceEvent): fansi.Str = a.event.toList match {
|
||||
case (h: ExecutionEvent) :: (t: RegUpdate) :: Nil => t.show.padTo(25) ++ h.show
|
||||
case (h: MemWrite) :: t :: Nil => t.show.padTo(25) ++ h.show
|
||||
case (h: MemRead) :: t :: Nil => t.show.padTo(25) ++ h.show
|
||||
case (h: MemWrite) :: Nil => h.show.leftPad(25)
|
||||
case (h: MemRead) :: Nil => h.show.leftPad(25)
|
||||
case h :: t :: Nil => h.show ++ t.show
|
||||
case (h: RegUpdate) :: Nil => h.show
|
||||
case h :: Nil => h.show.leftPad(25)
|
||||
case _ => Str("")
|
||||
}
|
||||
}
|
||||
|
||||
implicit val ChiselEventFancy = new Fancy[ChiselEvent]{
|
||||
def s(e: ChiselEvent) = e match {
|
||||
case ChiselRegEvent(addr, reg, word) => fansi.Str(s"R(${reg.show}) <- ${word.hs}")
|
||||
case ChiselMemWriteEvent(addr, memAddr, word) => fansi.Str(s"M[${memAddr.show}] <- ${word.hs}")
|
||||
}
|
||||
}
|
||||
|
||||
implicit val RegsFancy = new Fancy[Regs]{
|
||||
def s(e: Regs) = {
|
||||
(0 to 9).map{ idx =>
|
||||
val cols = List.range(idx, 32, 10)
|
||||
cols.map{ reg =>
|
||||
(fansi.Color.Yellow(Reg(reg).show.padTo(5)) ++ Str(e.repr.lift(Reg(reg)).getOrElse(0).hs)).padTo(16)
|
||||
}.showN("| ")
|
||||
}.toList.showN("\n", "\n", "\n")
|
||||
}
|
||||
}
|
||||
|
||||
val UNKNOWN = "UNKNOWN"
|
||||
|
||||
def printInstruction(op: Ops.Op, labelMap: Map[Label, Addr]): fansi.Str = op match {
|
||||
case op: Branch => fansi.Color.Red(s"B${op.comp}\t${op.rs1.show}, ${op.rs2.show}, ${op.dst.show}\t[${labelMap.lift(op.dst).getOrElse(UNKNOWN)}]")
|
||||
case op: Arith => s"${op.op}\t${op.rd.show}, ${op.rs1.show}, ${op.rs2.show}"
|
||||
case op: ArithImm => s"${op.op}I\t${op.rd.show}, ${op.rs1.show}, ${op.imm.show}"
|
||||
case op: JALR => fansi.Color.Green(s"JALR\t${op.rd.show}, ${op.rs1.show}, ${op.dst.show}\t[${labelMap.lift(op.dst).getOrElse(UNKNOWN)}]")
|
||||
case op: JAL => fansi.Color.Magenta(s"JAL\t${op.rd.show}, ${op.dst.show} [${labelMap.lift(op.dst).getOrElse(UNKNOWN)}]")
|
||||
case op: LW => s"LW\t${op.rd.show}, ${op.offset.show}(${op.rs1.show})"
|
||||
case op: SW => s"SW\t${op.rs2.show}, ${op.offset.show}(${op.rs1.show})"
|
||||
case op: LUI => s"LUI\t${op.rd.show}, ${op.imm.show}"
|
||||
case op: AUIPC => s"AUIPC\t${op.rd.show}, ${op.imm.show}"
|
||||
case DONE => s"DONE"
|
||||
}
|
||||
|
||||
|
||||
implicit class IntPrinters(i: Int) {
|
||||
def hs: String = if(i > 65535) f"0x$i%08X" else f"0x$i%04X"
|
||||
def binary: String = String.format("%" + 32 + "s", i.toBinaryString)
|
||||
.replace(' ', '0').grouped(4)
|
||||
.map(x => x + " ").mkString
|
||||
}
|
||||
|
||||
|
||||
|
||||
def printProgram(vm: VM): String = {
|
||||
val withLabels: List[Either[(Addr, Op), (Label, Addr)]] = (vm.imem.toList.map(Left(_)) ::: vm.labelMap.toList.map(Right(_)))
|
||||
.sortBy { case Left(x) => x._1.value.toDouble + 0.1; case Right(x) => x._2.value.toDouble }
|
||||
|
||||
withLabels.map{
|
||||
case Left((addr, op)) => s"$addr:\t\t${printInstruction(op, vm.labelMap)}"
|
||||
case Right((label, addr)) => s"$addr:\t$label:"
|
||||
}.mkString("\n","\n","\n")
|
||||
}
|
||||
|
||||
|
||||
def printProgram(p: Program): String = printProgram(p.vm)
|
||||
|
||||
|
||||
def printBinary(bin: Map[Addr, Int]): String = {
|
||||
bin.toList.sortBy(_._1.value).map{ case(addr, op) => s"$addr: ${op.hs}" }.mkString("\n","\n","\n")
|
||||
}
|
||||
|
||||
|
||||
def printChiselLogEntry(event: (Addr, List[ChiselEvent])): fansi.Str = {
|
||||
val (addr, log) = event
|
||||
val events = log match {
|
||||
case (h: ChiselRegEvent) :: (t: ChiselMemWriteEvent) :: Nil => h.show.padTo(25, ' ') ++ t.show
|
||||
case (h: ChiselMemWriteEvent) :: Nil => h.show.leftPad(25)
|
||||
case (h: ChiselRegEvent) :: Nil => h.show
|
||||
case _ => fansi.Str("")
|
||||
}
|
||||
events
|
||||
}
|
||||
|
||||
|
||||
def printVMtrace(trace: List[ExecutionTraceEvent], program: Program): String = {
|
||||
val blocks: List[List[(ExecutionTraceEvent, Int)]] = LogParser.splitToBlocks(trace).zipWithIndexNested
|
||||
blocks.map{ block =>
|
||||
val name = LogParser.guessBlockName(block.map(_._1.pc), program.labelMapReverse)
|
||||
val blockString = block.map{ case(event, idx) =>
|
||||
Str(s"Step: $idx,").padTo(12) ++
|
||||
Str("PC: ") ++
|
||||
event.pc.show ++
|
||||
Str("\t") ++
|
||||
event.show.padTo(70) ++
|
||||
printSourceLine(event.pc, program)
|
||||
}
|
||||
fansi.Color.Yellow(name) ++ Str("\n") ++ blockString.showN("\n")
|
||||
}.mkStringN
|
||||
}
|
||||
|
||||
def printSourceLine(addr: Addr, program: Program): fansi.Str = program.sourceMap.lift(addr).map(fansi.Str(_)).getOrElse(fansi.Str("???"))
|
||||
|
||||
def printMergedTraces(
|
||||
mt: (List[ExecutionTraceEvent], List[CircuitTrace]),
|
||||
program: Program
|
||||
): List[String] = {
|
||||
|
||||
|
||||
/**
|
||||
* For branch predicting processors we may end up with a spurious block causing a transient
|
||||
* desynchronization. Therefore it is necessary to have a certain tolerance before calling a divergence.
|
||||
*/
|
||||
// TODO: Implement a synchronization test to pinpoint where a desync happens
|
||||
// This is not straight forward in the case of processors which perform branch prediction.
|
||||
def isSynchronized(desyncs: Int, traces: (List[ExecutionTraceEvent], List[CircuitTrace]) ): Boolean = ???
|
||||
|
||||
|
||||
def helper(mt: (List[ExecutionTraceEvent], List[CircuitTrace])): List[List[fansi.Str]] = mt match {
|
||||
/**
|
||||
* VM trace and execution log synchronized, step both
|
||||
*/
|
||||
case (hVM :: tVM, hC :: tC) if (hVM.pc == hC._1) => {
|
||||
val address = hVM.pc
|
||||
val chiselLogEntry = printChiselLogEntry(hC)
|
||||
val vmEntry = hVM.show
|
||||
val sourceLine = printSourceLine(address, program)
|
||||
|
||||
val line = List(address.show, chiselLogEntry, vmEntry, sourceLine)
|
||||
|
||||
line :: helper((tVM, tC))
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* VM trace and execution log unsynchronized, step only chisel trace
|
||||
* Helper is called with (hVM :: tVM) to only step the chisel log.
|
||||
*/
|
||||
case (hVM :: tVM, hC :: tC) => {
|
||||
val address = hC._1
|
||||
val chiselLogEntry = printChiselLogEntry(hC)
|
||||
val vmEntry = Str("")
|
||||
val sourceLine = printSourceLine(address, program)
|
||||
|
||||
val line = List(address.show, chiselLogEntry, vmEntry, sourceLine)
|
||||
|
||||
line :: helper((hVM :: tVM, tC))
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The Block contains no more instructions performed by the VM. This
|
||||
* happens naturally since 5-stage pipelines will fetch spurious instructions.
|
||||
*/
|
||||
case (Nil, hC :: tC) => {
|
||||
val address = hC._1
|
||||
val chiselLogEntry = printChiselLogEntry(hC)
|
||||
val vmEntry = fansi.Str("")
|
||||
val sourceLine = printSourceLine(address, program)
|
||||
|
||||
val line = List(fansi.Color.LightBlue(address.show), chiselLogEntry, vmEntry, sourceLine)
|
||||
|
||||
line :: helper((Nil, tC))
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The Block contains no more instructions performed by the circuit.
|
||||
* This happens when the circuit takes a jump the VM does not.
|
||||
* This is an error *unless* the circuit has a branch predictor.
|
||||
*
|
||||
* If you want to you can splice the logs when this happens, but it's typically easy to spot.
|
||||
*/
|
||||
case (hVM :: tVM, Nil) => {
|
||||
val address = hVM.pc
|
||||
val chiselLogEntry = fansi.Str("")
|
||||
val vmEntry = hVM.show
|
||||
val sourceLine = printSourceLine(address, program)
|
||||
|
||||
val line = List(fansi.Color.LightRed(address.show), chiselLogEntry, vmEntry, sourceLine)
|
||||
|
||||
line :: helper((tVM, Nil))
|
||||
}
|
||||
|
||||
case (Nil, Nil) => Nil
|
||||
}
|
||||
|
||||
|
||||
def format(triplet: List[fansi.Str]): String = {
|
||||
// Ahh, the GNU toolchain and its tabs
|
||||
val annoyingTabCharacter = ' '
|
||||
triplet match {
|
||||
case address :: ch :: vm :: source :: Nil => {
|
||||
val vmLine: fansi.Str = vm.padTo(60, ' ')
|
||||
val chiselLine: fansi.Str = ch.padTo(50, ' ')
|
||||
val sourceLines: fansi.Str = source
|
||||
((address ++ Str(":")).padTo(14, ' ') ++ vmLine ++ fansi.Str("| ") ++ chiselLine ++ Str("| ") ++ sourceLines).toString
|
||||
}
|
||||
case _ => ""
|
||||
}
|
||||
}
|
||||
|
||||
val blockName = LogParser.guessBlockName(mt._1.map(_.pc), program.labelMapReverse)
|
||||
|
||||
(fansi.Color.Yellow(s"$blockName").padTo(74, ' ').toString ++ "|".padTo(55, ' ') ++ "|") :: helper(mt).map(format)
|
||||
}
|
||||
|
||||
|
||||
def printChiselError(e: Throwable): String = {
|
||||
val rawError = """
|
||||
|This typically occurs when you forget to wrap a module
|
||||
|e.g
|
||||
|
|
||||
|class MyBundle extends Bundle {
|
||||
| val signal = UInt(32.W)
|
||||
|}
|
||||
|val mySignal = new MyBundle
|
||||
| ^^^^ Wrong!
|
||||
|should be
|
||||
|val mySignal = Wire(new MyBundle)
|
||||
""".stripMargin
|
||||
|
||||
def getFiveStageStacktrace(strace: Array[StackTraceElement]): String =
|
||||
strace.map(_.toString).filter(_.contains("FiveStage")).toList.take(5).mkStringN
|
||||
|
||||
e match {
|
||||
case e: firrtl.passes.CheckInitialization.RefNotInitializedException => {
|
||||
s"""
|
||||
|##########################################################
|
||||
|##########################################################
|
||||
|##########################################################
|
||||
|Your design has unconnected wires!"
|
||||
|error:\n"
|
||||
|${e.getMessage}
|
||||
|
|
||||
|
|
||||
|##########################################################
|
||||
|##########################################################
|
||||
|##########################################################
|
||||
""".stripMargin
|
||||
}
|
||||
case e: chisel3.core.Binding.ExpectedHardwareException => {
|
||||
s"""
|
||||
|##########################################################
|
||||
|##########################################################
|
||||
|##########################################################
|
||||
|Your design is using raw chisel types!
|
||||
|error:\n
|
||||
|${e.getMessage}
|
||||
|
|
||||
|$rawError
|
||||
|The error should be here somewhere:
|
||||
|${getFiveStageStacktrace(e.getStackTrace)}
|
||||
|##########################################################
|
||||
|##########################################################
|
||||
|##########################################################
|
||||
""".stripMargin
|
||||
}
|
||||
case e: firrtl.passes.PassExceptions => {
|
||||
val rawErrorMsg = if(e.getMessage.contains("raw"))
|
||||
s"One of the errors was use of raw chisel types. $rawError"
|
||||
else
|
||||
""
|
||||
|
||||
s"""
|
||||
|##########################################################
|
||||
|##########################################################
|
||||
|##########################################################
|
||||
|Your design has multiple errors!
|
||||
|error:
|
||||
|${e.getMessage}
|
||||
|$rawErrorMsg
|
||||
|##########################################################
|
||||
|##########################################################
|
||||
|##########################################################
|
||||
""".stripMargin
|
||||
}
|
||||
case e: Exception => {
|
||||
s"""
|
||||
|##########################################################
|
||||
|##########################################################
|
||||
|##########################################################
|
||||
|Unexpected Chisel tester error (could be a chisel error I havent accounted for, or a bug in the tester like index out of bounds.)
|
||||
|error:
|
||||
|${e.getMessage}
|
||||
|reduced stacktrace:
|
||||
|${getFiveStageStacktrace(e.getStackTrace)}
|
||||
|##########################################################
|
||||
|##########################################################
|
||||
|##########################################################
|
||||
""".stripMargin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def printLogSideBySide(trace: List[ExecutionTraceEvent], chiselTrace: List[CircuitTrace], program: Program): String = {
|
||||
import LogParser._
|
||||
val traces = mergeTraces(trace, chiselTrace).map(x => printMergedTraces((x), program))
|
||||
traces.map(_.mkString("\n")).mkString("\n", "\n--------------------------------------------------------------------------+------------------------------------------------------+-------------------------------\n", "\n")
|
||||
}
|
||||
}
|
114
src/test/scala/RISCV/regNames.scala
Normal file
114
src/test/scala/RISCV/regNames.scala
Normal file
|
@ -0,0 +1,114 @@
|
|||
package FiveStage
|
||||
object regNames {
|
||||
val x0 = 0
|
||||
val x1 = 1
|
||||
val x2 = 2
|
||||
val x3 = 3
|
||||
val x4 = 4
|
||||
val x5 = 5
|
||||
val x6 = 6
|
||||
val x7 = 7
|
||||
val x8 = 8
|
||||
val x9 = 9
|
||||
|
||||
val x10 = 10
|
||||
val x11 = 11
|
||||
val x12 = 12
|
||||
val x13 = 13
|
||||
val x14 = 14
|
||||
val x15 = 15
|
||||
val x16 = 16
|
||||
val x17 = 17
|
||||
val x18 = 18
|
||||
val x19 = 19
|
||||
|
||||
val x20 = 20
|
||||
val x21 = 21
|
||||
val x22 = 22
|
||||
val x23 = 23
|
||||
val x24 = 24
|
||||
val x25 = 25
|
||||
val x26 = 26
|
||||
val x27 = 27
|
||||
val x28 = 28
|
||||
val x29 = 29
|
||||
|
||||
val x30 = 30
|
||||
val x31 = 31
|
||||
|
||||
|
||||
val zero = 0
|
||||
val ra = 1
|
||||
val sp = 2
|
||||
val gp = 3
|
||||
val tp = 4
|
||||
val t0 = 5
|
||||
val t1 = 6
|
||||
val t2 = 7
|
||||
val s0 = 8
|
||||
val fp = 8
|
||||
val s1 = 9
|
||||
|
||||
val a0 = 10
|
||||
val a1 = 11
|
||||
val a2 = 12
|
||||
val a3 = 13
|
||||
val a4 = 14
|
||||
val a5 = 15
|
||||
val a6 = 16
|
||||
val a7 = 17
|
||||
val s2 = 18
|
||||
val s3 = 19
|
||||
|
||||
val s4 = 20
|
||||
val s5 = 21
|
||||
val s6 = 22
|
||||
val s7 = 23
|
||||
val s8 = 24
|
||||
val s9 = 25
|
||||
val s10 = 26
|
||||
val s11 = 27
|
||||
val t3 = 28
|
||||
val t4 = 29
|
||||
|
||||
val t5 = 30
|
||||
val t6 = 31
|
||||
|
||||
val canonicalName = Map(
|
||||
0 -> "zero",
|
||||
1 -> "ra",
|
||||
2 -> "sp",
|
||||
3 -> "gp",
|
||||
4 -> "tp",
|
||||
5 -> "t0",
|
||||
6 -> "t1",
|
||||
7 -> "t2",
|
||||
8 -> "s0",
|
||||
8 -> "fp",
|
||||
9 -> "s1",
|
||||
10 -> "a0",
|
||||
11 -> "a1",
|
||||
12 -> "a2",
|
||||
13 -> "a3",
|
||||
14 -> "a4",
|
||||
15 -> "a5",
|
||||
16 -> "a6",
|
||||
17 -> "a7",
|
||||
18 -> "s2",
|
||||
19 -> "s3",
|
||||
20 -> "s4",
|
||||
21 -> "s5",
|
||||
22 -> "s6",
|
||||
23 -> "s7",
|
||||
24 -> "s8",
|
||||
25 -> "s9",
|
||||
26 -> "s10",
|
||||
27 -> "s11",
|
||||
28 -> "t3",
|
||||
29 -> "t4",
|
||||
30 -> "t5",
|
||||
31 -> "t6",
|
||||
)
|
||||
|
||||
}
|
||||
|
103
src/test/scala/RISCV/testRunner.scala
Normal file
103
src/test/scala/RISCV/testRunner.scala
Normal file
|
@ -0,0 +1,103 @@
|
|||
package FiveStage
|
||||
import org.scalatest.{Matchers, FlatSpec}
|
||||
import cats._
|
||||
import cats.implicits._
|
||||
import fileUtils._
|
||||
|
||||
import chisel3.iotesters._
|
||||
import scala.collection.mutable.LinkedHashMap
|
||||
|
||||
import fansi.Str
|
||||
|
||||
import Ops._
|
||||
import Data._
|
||||
import VM._
|
||||
|
||||
import PrintUtils._
|
||||
import LogParser._
|
||||
|
||||
case class TestOptions(
|
||||
printIfSuccessful : Boolean,
|
||||
printErrors : Boolean,
|
||||
printParsedProgram : Boolean,
|
||||
printVMtrace : Boolean,
|
||||
printVMfinal : Boolean,
|
||||
printMergedTrace : Boolean,
|
||||
nopPadded : Boolean,
|
||||
breakPoints : List[Int], // Not implemented
|
||||
testName : String
|
||||
)
|
||||
|
||||
case class TestResult(
|
||||
regError : Option[String],
|
||||
memError : Option[String],
|
||||
program : String,
|
||||
vmTrace : String,
|
||||
vmFinal : String,
|
||||
sideBySide : String
|
||||
)
|
||||
|
||||
object TestRunner {
|
||||
|
||||
def run(testOptions: TestOptions): Boolean = {
|
||||
|
||||
val testResults = for {
|
||||
lines <- fileUtils.readTest(testOptions)
|
||||
program <- FiveStage.Parser.parseProgram(lines, testOptions)
|
||||
(binary, (trace, finalVM)) <- program.validate.map(x => (x._1, x._2.run))
|
||||
(termitationCause, chiselTrace) <- ChiselTestRunner(
|
||||
binary.toList.sortBy(_._1.value).map(_._2),
|
||||
program.settings,
|
||||
finalVM.pc,
|
||||
1500)
|
||||
} yield {
|
||||
val traces = mergeTraces(trace, chiselTrace).map(x => printMergedTraces((x), program))
|
||||
|
||||
val programString = printProgram(program)
|
||||
val vmTraceString = printVMtrace(trace, program)
|
||||
val vmFinalState = finalVM.regs.show
|
||||
val traceString = printLogSideBySide(trace, chiselTrace, program)
|
||||
|
||||
val regError = compareRegs(trace, chiselTrace)
|
||||
val memError = compareMem(trace, chiselTrace)
|
||||
|
||||
TestResult(
|
||||
regError,
|
||||
memError,
|
||||
programString,
|
||||
vmTraceString,
|
||||
vmFinalState.toString,
|
||||
traceString)
|
||||
}
|
||||
|
||||
testResults.left.foreach{ error =>
|
||||
say(s"Test was unable to run due to error: $error")
|
||||
}
|
||||
|
||||
testResults.map{ testResults =>
|
||||
val successful = List(testResults.regError, testResults.memError).flatten.headOption.map(_ => false).getOrElse(true)
|
||||
if(successful)
|
||||
say(s"${testOptions.testName} succesful")
|
||||
else
|
||||
say(s"${testOptions.testName} failed")
|
||||
|
||||
if(testOptions.printIfSuccessful && successful){
|
||||
if(testOptions.printParsedProgram) say(testResults.program)
|
||||
if(testOptions.printVMtrace) say(testResults.vmTrace)
|
||||
if(testOptions.printVMfinal) say(testResults.vmFinal)
|
||||
if(testOptions.printMergedTrace) say(testResults.sideBySide)
|
||||
}
|
||||
else{
|
||||
if(testOptions.printErrors){
|
||||
say(testResults.regError.map(_.show.toString).getOrElse("no reg errors"))
|
||||
say(testResults.memError.map(_.show.toString).getOrElse("no mem errors"))
|
||||
}
|
||||
if(testOptions.printParsedProgram) say(testResults.program)
|
||||
if(testOptions.printVMtrace) say(testResults.vmTrace)
|
||||
if(testOptions.printVMfinal) say(testResults.vmFinal)
|
||||
if(testOptions.printMergedTrace) say(testResults.sideBySide)
|
||||
}
|
||||
successful
|
||||
}.toOption.getOrElse(false)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue