Stuff I forgot to commit.
This commit is contained in:
parent
b8ae0092c1
commit
9f47433501
14 changed files with 517 additions and 249 deletions
15
src/test/resources/tests/programs/constants.s
Normal file
15
src/test/resources/tests/programs/constants.s
Normal file
|
@ -0,0 +1,15 @@
|
|||
main:
|
||||
li x0, 0x0
|
||||
nop
|
||||
li x1, 0xABCDEF0
|
||||
nop
|
||||
li x1, 32
|
||||
li x1, 0x800
|
||||
li x1, 0x7FF
|
||||
nop
|
||||
nop
|
||||
done
|
||||
#regset t0,10
|
||||
#regset t1,23
|
||||
#regset t2,43
|
||||
#regset t3,-11
|
18
src/test/resources/tests/programs/halfwords.s
Normal file
18
src/test/resources/tests/programs/halfwords.s
Normal file
|
@ -0,0 +1,18 @@
|
|||
main:
|
||||
li x1, 0x11223344
|
||||
li x2, 0x55667788
|
||||
li x10, 0x100
|
||||
sw x1, 0(x10)
|
||||
sw x2, 4(x10)
|
||||
lw x3, 2(x10)
|
||||
lh x4, 3(x10)
|
||||
lb x5, 3(x10)
|
||||
lhu x6, 3(x10)
|
||||
lbu x7, 3(x10)
|
||||
sw x1, 8(x10)
|
||||
sw x1, 9(x10)
|
||||
sh x2, 9(x10)
|
||||
sb x2, 11(x10)
|
||||
lw x12, 8(x10)
|
||||
lw x13, 12(x10)
|
||||
done
|
|
@ -1,4 +1,5 @@
|
|||
package FiveStage
|
||||
|
||||
import org.scalatest.{Matchers, FlatSpec}
|
||||
import cats._
|
||||
import cats.implicits._
|
||||
|
@ -20,7 +21,7 @@ object Manifest {
|
|||
|
||||
val singleTest = "forward2.s"
|
||||
|
||||
val nopPadded = true
|
||||
val nopPadded = false
|
||||
|
||||
val singleTestOptions = TestOptions(
|
||||
printIfSuccessful = true,
|
||||
|
@ -53,18 +54,15 @@ object Manifest {
|
|||
|
||||
class ProfileBranching extends FlatSpec with Matchers {
|
||||
it should "profile some branches" in {
|
||||
TestRunner.profileBranching(
|
||||
Manifest.singleTestOptions.copy(testName = "branchProfiling.s", maxSteps = 50000)
|
||||
BranchProfiler.profileBranching(
|
||||
Manifest.singleTestOptions.copy(testName = "branchProfiling.s", maxSteps = 150000)
|
||||
) should be(true)
|
||||
}
|
||||
}
|
||||
|
||||
class ProfileCache extends FlatSpec with Matchers {
|
||||
it should "profile a cache" in {
|
||||
say("Warning, this test takes forever to run! 2 minutes on my machine at least.")
|
||||
say("This happens due to the less than optimal way of storing the update log. Sorry I guess")
|
||||
say("You probably want to debug this with a smaller program")
|
||||
TestRunner.profileCache(
|
||||
CacheProfiler.profileCache(
|
||||
Manifest.singleTestOptions.copy(testName = "convolution.s", maxSteps = 150000)
|
||||
) should be(true)
|
||||
}
|
||||
|
|
|
@ -176,8 +176,16 @@ object Data {
|
|||
val bitsRight = 32 - slots.log2
|
||||
val leftShifted = i << bitsLeft
|
||||
val rightShifted = leftShifted >>> bitsRight
|
||||
// say(i)
|
||||
// say(rightShifted)
|
||||
rightShifted
|
||||
}
|
||||
|
||||
// To get the entire word call with from = 31, to = 0
|
||||
def bits(from: Int, to: Int): Int = {
|
||||
val bitsLeft = 31 - from
|
||||
val bitsRight = bitsLeft + to
|
||||
val leftShifted = i << bitsLeft
|
||||
val rightShifted = leftShifted >>> bitsRight
|
||||
|
||||
rightShifted
|
||||
}
|
||||
}
|
||||
|
|
147
src/test/scala/RISCV/branchProfiler.scala
Normal file
147
src/test/scala/RISCV/branchProfiler.scala
Normal file
|
@ -0,0 +1,147 @@
|
|||
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._
|
||||
|
||||
object BranchProfiler {
|
||||
|
||||
def profileBranching(testOptions: TestOptions): Boolean = {
|
||||
|
||||
val testResults = for {
|
||||
lines <- fileUtils.readTest(testOptions)
|
||||
program <- FiveStage.Parser.parseProgram(lines, testOptions)
|
||||
(binary, (trace, finalVM)) <- program.validate(testOptions.maxSteps).map(x => (x._1, x._2.run))
|
||||
} yield {
|
||||
|
||||
sealed trait BranchEvent
|
||||
case class Taken(from: Int, to: Int) extends BranchEvent { override def toString = s"Taken ${from.hs}\t${to.hs}" }
|
||||
case class NotTaken(addr: Int) extends BranchEvent { override def toString = s"Not Taken ${addr.hs}" }
|
||||
|
||||
val events: List[BranchEvent] = trace.flatMap(_.event).collect{
|
||||
case PcUpdateBranch(from, to) => Taken(from.value, to.value)
|
||||
case PcUpdateNoBranch(at) => NotTaken(at.value)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is a sample profiler for a rather unrealistic branch predictor which has an unlimited amount
|
||||
* of slots
|
||||
*/
|
||||
def OneBitInfiniteSlots(events: List[BranchEvent]): Int = {
|
||||
|
||||
// Uncomment to take a look at the event log
|
||||
// say(events.mkString("\n","\n","\n"))
|
||||
|
||||
// Helper inspects the next element of the event list. If the event is a mispredict the prediction table is updated
|
||||
// to reflect this.
|
||||
// As long as there are remaining events the helper calls itself recursively on the remainder
|
||||
def helper(events: List[BranchEvent], predictionTable: Map[Int, Boolean]): Int = {
|
||||
events match {
|
||||
|
||||
// Scala syntax for matching a list with a head element of some type and a tail
|
||||
// `case h :: t =>`
|
||||
// means we want to match a list with at least a head and a tail (tail can be Nil, so we
|
||||
// essentially want to match a list with at least one element)
|
||||
// h is the first element of the list, t is the remainder (which can be Nil, aka empty)
|
||||
|
||||
// `case Constructor(arg1, arg2) :: t => `
|
||||
// means we want to match a list whose first element is of type Constructor, giving us access to its internal
|
||||
// values.
|
||||
|
||||
// `case Constructor(arg1, arg2) :: t => if(p(arg1, arg2))`
|
||||
// means we want to match a list whose first element is of type Constructor while satisfying some predicate p,
|
||||
// called an if guard.
|
||||
case Taken(from, to) :: t if( predictionTable(from)) => helper(t, predictionTable)
|
||||
case Taken(from, to) :: t if(!predictionTable(from)) => 1 + helper(t, predictionTable.updated(from, true))
|
||||
case NotTaken(addr) :: t if( predictionTable(addr)) => 1 + helper(t, predictionTable.updated(addr, false))
|
||||
case NotTaken(addr) :: t if(!predictionTable(addr)) => helper(t, predictionTable)
|
||||
case Nil => 0
|
||||
}
|
||||
}
|
||||
|
||||
// Initially every possible branch is set to false since the initial state of the predictor is to assume branch not taken
|
||||
def initState = events.map{
|
||||
case Taken(from, addr) => (from, false)
|
||||
case NotTaken(addr) => (addr, false)
|
||||
}.toMap
|
||||
|
||||
helper(events, initState)
|
||||
}
|
||||
|
||||
|
||||
def twoBitPredictor(events: List[BranchEvent], slots: Int): Int = {
|
||||
|
||||
case class nBitPredictor(
|
||||
values : List[Int],
|
||||
predictionRules : List[Boolean],
|
||||
transitionRules : Int => Boolean => Int,
|
||||
){
|
||||
val slots = values.size
|
||||
|
||||
def predict(pc: Int): Boolean = predictionRules(values(pc.getTag(slots)))
|
||||
|
||||
def update(pc: Int, taken: Boolean): nBitPredictor = {
|
||||
val current = values(pc.getTag(slots))
|
||||
val next = copy(values = values.updated(pc.getTag(slots), transitionRules(current)(taken)))
|
||||
next
|
||||
}
|
||||
|
||||
override def toString = values.map(x => x.binary(2)).mkString("[","][","]")
|
||||
}
|
||||
|
||||
val initPredictor = nBitPredictor(
|
||||
List.fill(slots)(0),
|
||||
List(
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
),
|
||||
r => r match {
|
||||
case 0 => taken => if(taken) 1 else 0
|
||||
case 1 => taken => if(taken) 2 else 0
|
||||
case 2 => taken => if(taken) 3 else 1
|
||||
case 3 => taken => if(taken) 3 else 2
|
||||
}
|
||||
)
|
||||
|
||||
events.foldLeft((0, initPredictor)){ case(((acc, bp), event)) =>
|
||||
println()
|
||||
say(s"total misses: $acc")
|
||||
say(event)
|
||||
event match {
|
||||
case Taken(pc, _) => say(s"taken at tag: ${pc.getTag(slots)}")
|
||||
case NotTaken(pc) => say(s"not taken at tag: ${pc.getTag(slots)}")
|
||||
}
|
||||
say(bp)
|
||||
event match {
|
||||
case Taken(pc, _) if bp.predict(pc) => {say("HIT!"); (acc, bp.update(pc, true))}
|
||||
case Taken(pc, _) => {say("MISS!"); (acc + 1, bp.update(pc, true))}
|
||||
case NotTaken(pc) if !bp.predict(pc) => {say("HIT!"); (acc, bp.update(pc, false))}
|
||||
case NotTaken(pc) => {say("MISS!"); (acc + 1, bp.update(pc, false))}
|
||||
}
|
||||
}._1
|
||||
}
|
||||
|
||||
say(events.mkString("\n","\n","\n"))
|
||||
say(twoBitPredictor(events, 8))
|
||||
}
|
||||
|
||||
|
||||
true
|
||||
}
|
||||
}
|
204
src/test/scala/RISCV/cacheProfiler.scala
Normal file
204
src/test/scala/RISCV/cacheProfiler.scala
Normal file
|
@ -0,0 +1,204 @@
|
|||
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._
|
||||
|
||||
object CacheProfiler {
|
||||
|
||||
def profileCache(testOptions: TestOptions): Boolean = {
|
||||
|
||||
val testResults = for {
|
||||
lines <- fileUtils.readTest(testOptions)
|
||||
program <- FiveStage.Parser.parseProgram(lines, testOptions)
|
||||
(binary, (trace, finalVM)) <- program.validate(testOptions.maxSteps).map(x => (x._1, x._2.run))
|
||||
} yield {
|
||||
|
||||
import TestUtils._
|
||||
|
||||
sealed trait MemoryEvent
|
||||
case class Write(addr: Int) extends MemoryEvent
|
||||
case class Read(addr: Int) extends MemoryEvent
|
||||
|
||||
val events = trace.flatMap(_.event).collect{
|
||||
case MemWrite(addr, _) => Write(addr.value)
|
||||
case MemRead(addr, _) => Read(addr.value)
|
||||
}
|
||||
|
||||
|
||||
class CacheProfiler(setCount: Int, setSize: Int, blockSize: Int){
|
||||
|
||||
// If we set counter to 0 we risk evicting the first allocated block.
|
||||
var counter = 1
|
||||
var misses = 0
|
||||
var mostRecent = 0
|
||||
var wasMiss = false
|
||||
|
||||
implicit class AddrOps(i: Int){
|
||||
val blockOffsetBits = blockSize.log2
|
||||
val lineBits = setSize.log2
|
||||
|
||||
def lineIdx: Int = {
|
||||
i.bits(2 + blockOffsetBits + lineBits - 1, 2 + blockOffsetBits)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
case class CacheLine(tag: Int, lastUsed: Int){
|
||||
def matches(addr: Int): Boolean = List.fill(blockSize)(tag)
|
||||
.zipWithIndex
|
||||
.map{ case(btag, idx) => btag + idx*4 }
|
||||
.map(_ == addr)
|
||||
.foldLeft(false)(_ || _)
|
||||
|
||||
def renderContent(addr: Int): String = (addr == mostRecent, wasMiss) match {
|
||||
case (true, true) => Console.RED + addr.hs + Console.RESET
|
||||
case (true, false) => Console.GREEN + addr.hs + Console.RESET
|
||||
case _ => addr.hs
|
||||
}
|
||||
|
||||
def render: String = {
|
||||
val blockContents = List.fill(blockSize)(tag)
|
||||
.zipWithIndex
|
||||
.map{ case(btag, idx) => renderContent(btag + idx*4) }
|
||||
.mkString("Contents: || ", " | ", " |")
|
||||
|
||||
s"Base: ${tag.hs} LRU: $lastUsed\t" + blockContents
|
||||
}
|
||||
}
|
||||
object CacheLine {
|
||||
def truncateTag(addr: Int) = addr - (addr % (blockSize*4))
|
||||
}
|
||||
|
||||
|
||||
case class CacheSet(blocks: Array[CacheLine]){
|
||||
def lineIdx(addr: Int): Int = addr.lineIdx
|
||||
def contains(addr: Int): Boolean = blocks.map(_.matches(addr)).foldLeft(false)(_ || _)
|
||||
|
||||
def updateLRU(addr: Int): Unit = {
|
||||
val idx = lineIdx(addr)
|
||||
val next = blocks(idx).copy(lastUsed = counter)
|
||||
blocks(idx) = next
|
||||
}
|
||||
|
||||
def render: String = {
|
||||
blocks.map(_.render).mkString("\n", "\n", "\n")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
case class Cache(sets: Array[CacheSet]){
|
||||
|
||||
/** returns the index of set if hit */
|
||||
def checkHit(addr: Int): Option[Int] = sets
|
||||
.zipWithIndex
|
||||
.map{ case(set, idx) => Option.when(set.contains(addr))(idx) }
|
||||
.flatten.headOption
|
||||
|
||||
|
||||
/** Updates the LRU counter */
|
||||
def updateLRU(addr: Int, setIdx: Int): Unit = sets(setIdx).updateLRU(addr)
|
||||
|
||||
|
||||
/** Gets set with least recently used */
|
||||
def getLRU(addr: Int): Int = sets
|
||||
.map( set => set.blocks(set.lineIdx(addr)).lastUsed)
|
||||
.zipWithIndex
|
||||
.sortBy(_._1)
|
||||
.map(_._2)
|
||||
.head
|
||||
|
||||
|
||||
/** Entry point */
|
||||
def handleAccess(addr: Int): Unit = {
|
||||
mostRecent = addr
|
||||
counter += 1
|
||||
|
||||
checkHit(addr) match {
|
||||
|
||||
case Some(setIdx) => {
|
||||
wasMiss = false
|
||||
updateLRU(addr, setIdx)
|
||||
// say(s"${addr.hs} HIT")
|
||||
}
|
||||
|
||||
case None => {
|
||||
val set = sets(getLRU(addr))
|
||||
val nextTag = CacheLine.truncateTag(addr)
|
||||
set.blocks(set.lineIdx(addr)) = set.blocks(set.lineIdx(addr)).copy(
|
||||
tag = nextTag,
|
||||
lastUsed = counter
|
||||
)
|
||||
misses += 1
|
||||
|
||||
wasMiss = true
|
||||
// say(s"${addr.hs} MISS")
|
||||
// say(s"BLOCK ${addr.lineIdx} IN SET ${getLRU(addr)} EVICTED. BYE BYE")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Pretty pictures! */
|
||||
def render: String = {
|
||||
sets.map(_.render).mkString("\n", "\n", "\n")
|
||||
}
|
||||
}
|
||||
|
||||
object Cache {
|
||||
def init: Cache = Cache(Array.fill(setCount)(
|
||||
CacheSet(Array.fill(setSize)(CacheLine(57005, 0))))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
for{
|
||||
sets <- List(2, 4, 8)
|
||||
blockSize <- List(4, 8)
|
||||
lines <- List(2, 4, 8)
|
||||
} yield {
|
||||
|
||||
val myTest = new CacheProfiler(sets, lines, blockSize)
|
||||
val myCache = myTest.Cache.init
|
||||
events.foreach{
|
||||
case Write(addr) => myCache.handleAccess(addr)
|
||||
case Read(addr) => myCache.handleAccess(addr)
|
||||
}
|
||||
|
||||
say(s"sets: $sets, lines: $lines, blockSize: $blockSize yields ${myTest.misses} misses")
|
||||
}
|
||||
|
||||
// val myTest = new CacheProfiler(2, 4, 4)
|
||||
// val myCache = myTest.Cache.init
|
||||
// events.foreach{
|
||||
// case Write(addr) => {
|
||||
// say(addr.hs)
|
||||
// myCache.handleAccess(addr)
|
||||
// say(myCache.render)
|
||||
// }
|
||||
// case Read(addr) => {
|
||||
// say(addr.hs)
|
||||
// myCache.handleAccess(addr)
|
||||
// say(myCache.render)
|
||||
// }
|
||||
// }
|
||||
|
||||
// say(myTest.misses)
|
||||
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
|
@ -101,98 +101,4 @@ object TestRunner {
|
|||
successful
|
||||
}.toOption.getOrElse(false)
|
||||
}
|
||||
|
||||
def profileBranching(testOptions: TestOptions): Boolean = {
|
||||
|
||||
val testResults = for {
|
||||
lines <- fileUtils.readTest(testOptions)
|
||||
program <- FiveStage.Parser.parseProgram(lines, testOptions)
|
||||
(binary, (trace, finalVM)) <- program.validate(testOptions.maxSteps).map(x => (x._1, x._2.run))
|
||||
} yield {
|
||||
|
||||
sealed trait BranchEvent
|
||||
case class Taken(from: Int, to: Int) extends BranchEvent { override def toString = s"Taken ${from.hs}\t${to.hs}" }
|
||||
case class NotTaken(addr: Int) extends BranchEvent { override def toString = s"Not Taken ${addr.hs}" }
|
||||
|
||||
val events: List[BranchEvent] = trace.flatMap(_.event).collect{
|
||||
case PcUpdateBranch(from, to) => Taken(from.value, to.value)
|
||||
case PcUpdateNoBranch(at) => NotTaken(at.value)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is a sample profiler for a rather unrealistic branch predictor which has an unlimited amount
|
||||
* of slots
|
||||
*/
|
||||
def OneBitInfiniteSlots(events: List[BranchEvent]): Int = {
|
||||
|
||||
// Uncomment to take a look at the event log
|
||||
// say(events.mkString("\n","\n","\n"))
|
||||
|
||||
// Helper inspects the next element of the event list. If the event is a mispredict the prediction table is updated
|
||||
// to reflect this.
|
||||
// As long as there are remaining events the helper calls itself recursively on the remainder
|
||||
def helper(events: List[BranchEvent], predictionTable: Map[Int, Boolean]): Int = {
|
||||
events match {
|
||||
|
||||
// Scala syntax for matching a list with a head element of some type and a tail
|
||||
// `case h :: t =>`
|
||||
// means we want to match a list with at least a head and a tail (tail can be Nil, so we
|
||||
// essentially want to match a list with at least one element)
|
||||
// h is the first element of the list, t is the remainder (which can be Nil, aka empty)
|
||||
|
||||
// `case Constructor(arg1, arg2) :: t => `
|
||||
// means we want to match a list whose first element is of type Constructor, giving us access to its internal
|
||||
// values.
|
||||
|
||||
// `case Constructor(arg1, arg2) :: t => if(p(arg1, arg2))`
|
||||
// means we want to match a list whose first element is of type Constructor while satisfying some predicate p,
|
||||
// called an if guard.
|
||||
case Taken(from, to) :: t if( predictionTable(from)) => helper(t, predictionTable)
|
||||
case Taken(from, to) :: t if(!predictionTable(from)) => 1 + helper(t, predictionTable.updated(from, true))
|
||||
case NotTaken(addr) :: t if( predictionTable(addr)) => 1 + helper(t, predictionTable.updated(addr, false))
|
||||
case NotTaken(addr) :: t if(!predictionTable(addr)) => helper(t, predictionTable)
|
||||
case Nil => 0
|
||||
}
|
||||
}
|
||||
|
||||
// Initially every possible branch is set to false since the initial state of the predictor is to assume branch not taken
|
||||
def initState = events.map{
|
||||
case Taken(from, addr) => (from, false)
|
||||
case NotTaken(addr) => (addr, false)
|
||||
}.toMap
|
||||
|
||||
helper(events, initState)
|
||||
}
|
||||
|
||||
say(OneBitInfiniteSlots(events))
|
||||
}
|
||||
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
|
||||
def profileCache(testOptions: TestOptions): Boolean = {
|
||||
|
||||
val testResults = for {
|
||||
lines <- fileUtils.readTest(testOptions)
|
||||
program <- FiveStage.Parser.parseProgram(lines, testOptions)
|
||||
(binary, (trace, finalVM)) <- program.validate(testOptions.maxSteps).map(x => (x._1, x._2.run))
|
||||
} yield {
|
||||
|
||||
sealed trait MemoryEvent
|
||||
case class Write(addr: Int) extends MemoryEvent
|
||||
case class Read(addr: Int) extends MemoryEvent
|
||||
|
||||
val events: List[MemoryEvent] = trace.flatMap(_.event).collect{
|
||||
case MemWrite(x,_) => Write(x.value)
|
||||
case MemRead(x,_) => Read(x.value)
|
||||
}
|
||||
|
||||
// Your cache here
|
||||
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,10 @@ import PrintUtils._
|
|||
|
||||
object TestUtils {
|
||||
|
||||
implicit class OptionBackport(t: Option.type){
|
||||
def when[T](b: Boolean)(t: => T) = if(b) Some(t) else None
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate and serialize BTrees for the test runner
|
||||
*/
|
||||
|
|
|
@ -142,7 +142,7 @@ private class ChiselTestRunner (
|
|||
|
||||
// After finishing, let the circuit run until all updates can be committed.
|
||||
private def flush: List[CircuitTrace] =
|
||||
(0 to 3).map(_ => stepOne).reverse.toList
|
||||
(0 to 4).map(_ => stepOne).reverse.toList
|
||||
|
||||
/**
|
||||
* Run the entire shebang
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue