This commit is contained in:
peteraa 2019-06-07 17:43:33 +02:00
commit 932413bb3d
61 changed files with 7249 additions and 0 deletions

View 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)
}
}

View 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
}
}

View 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) }
}

View 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)
}
}

View 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)
}
}

View 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
}
}

View 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))
}
}
}

View 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)
}
}

View 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")
}
}

View 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",
)
}

View 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)
}
}