Stuff I forgot to commit.

This commit is contained in:
peteraa 2020-06-29 16:17:24 +02:00
parent b8ae0092c1
commit 9f47433501
14 changed files with 517 additions and 249 deletions

View file

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

View file

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

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

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

View file

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

View file

@ -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
*/

View file

@ -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