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

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

View 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

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