diff --git a/TODOs.org b/TODOs.org index dcecdb3..3f75bc5 100644 --- a/TODOs.org +++ b/TODOs.org @@ -5,7 +5,7 @@ Finn ut hvordan bundles, defs etc burde fungere. * Now -** TODO Port Babby to chisel3 +** DONE Port Babby to chisel3 *** DONE compile and run *** DONE fix deprecation diff --git a/ov0/src/main/scala/Tile.scala b/ov0/src/main/scala/Tile.scala index 03b7a6f..067766b 100644 --- a/ov0/src/main/scala/Tile.scala +++ b/ov0/src/main/scala/Tile.scala @@ -6,12 +6,9 @@ import chisel3.iotesters.PeekPokeTester object CoreMain { def main(args: Array[String]): Unit = { - // chiselMainTest(args, () => Module(new daisyVector(4, 32))) { c => new daisyVectorTest(c) } - // chiselMainTest(args, () => Module(new daisyGrid(4, 3, 32))) { c => new daisyGridTest(c) } - // chiselMainTest(args, () => Module(new daisyMultiplier(3, 2, 2, 3, 32))) { c => new daisyMultiplierTest(c) } - iotesters.Driver.execute(args, () => new daisyMultiplier(3, 2, 2, 3, 32)) { - c => new daisyMultiplierTest(c) + iotesters.Driver.execute(args, () => new mySelector(10)){ + c => new mySelectorTest(c) } } } @@ -59,7 +56,7 @@ object Extras { val vecA = List(1, 2, 4) val vecB = List(2, -3, 1) - val dotProductForLoop = { + def dotProductForLoop(vecA: List[Int], vecB: List[Int]) = { var dotProduct = 0 for(i <- 0 until vecA.length){ dotProduct = dotProduct + (vecA(i) * vecB(i)) @@ -81,4 +78,25 @@ object Extras { // This is not good code!!! val tooFancyDotProduct = (0 /: (vecA zip vecB)){ case(acc, ab) => acc + (ab._1 * ab._2) } + + + type Matrix[A] = List[List[A]] + def vectorMatrixMultiply(vec: List[Int], matrix: Matrix[Int]): List[Int] = { + val transposed = matrix.transpose + + val outputVector = Array.ofDim[Int](vec.length) + for(ii <- 0 until matrix.length){ + outputVector(ii) = dotProductForLoop(vec, transposed(ii)) + } + outputVector.toList + } + + + val vec = List(1, 0, 1) + val matrix = List( + List(2, 1, 2), + List(3, 2, 3), + List(4, 1, 1) + ) + println(vectorMatrixMultiply(vec, matrix)) } diff --git a/ov0/src/main/scala/basics.scala b/ov0/src/main/scala/basics.scala new file mode 100644 index 0000000..ae778de --- /dev/null +++ b/ov0/src/main/scala/basics.scala @@ -0,0 +1,157 @@ + +package Core +import chisel3._ +import chisel3.core.Input +import chisel3.iotesters.PeekPokeTester + + +class myIncrement(incrementBy: Int) extends Module { + val io = IO( + new Bundle { + val dataIn = Input(UInt(32.W)) + val dataOut = Output(UInt(32.W)) + } + ) + + io.dataOut := io.dataIn + incrementBy.U +} + + +class myIncrementTwice(incrementBy: Int) extends Module { + val io = IO( + new Bundle { + val dataIn = Input(UInt(32.W)) + val dataOut = Output(UInt(32.W)) + } + ) + + val first = Module(new myIncrement(incrementBy)) + val second = Module(new myIncrement(incrementBy)) + + first.io.dataIn := io.dataIn + second.io.dataIn := first.io.dataOut + + io.dataOut := second.io.dataOut +} + + +class myIncrementN(incrementBy: Int, numIncrementors: Int) extends Module { + val io = IO( + new Bundle { + val dataIn = Input(UInt(32.W)) + val dataOut = Output(UInt(32.W)) + } + ) + + val incrementors = Array.fill(numIncrementors){ Module(new myIncrement(incrementBy)) } + + for(ii <- 1 until numIncrementors){ + incrementors(ii).io.dataIn := incrementors(ii - 1).io.dataOut + } + + incrementors(0).io.dataIn := io.dataIn + io.dataOut := incrementors(numIncrementors).io.dataOut +} + + + + +class myDelay() extends Module { + val io = IO( + new Bundle { + val dataIn = Input(UInt(32.W)) + val dataOut = Output(UInt(32.W)) + } + ) + + val reg = RegInit(UInt(32.W), 0.U) + reg := io.dataIn + io.dataOut := reg +} + + +class myDelayN(steps: Int) extends Module { + val io = IO( + new Bundle { + val dataIn = Input(UInt(32.W)) + val dataOut = Output(UInt(32.W)) + } + ) + + val delayers = Array.fill(steps){ Module(new myDelay()) } + + for(ii <- 1 until steps){ + delayers(ii).io.dataIn := delayers(ii - 1).io.dataOut + } + + delayers(0).io.dataIn := io.dataIn + io.dataOut := delayers(steps).io.dataOut +} + + +class mySelector(numValues: Int) extends Module { + val io = IO( + new Bundle { + val next = Input(Bool()) + val dataOut = Output(UInt(32.W)) + val newOutput = Output(Bool()) + } + ) + + val counter = RegInit(UInt(Chisel.log2Up(numValues).W), 0.U) + val nextOutputIsFresh = RegInit(Bool(), true.B) + + // Generate random values. Using the when keyword we choose which random + // value should drive the dataOut signal + io.dataOut := 0.U + List.fill(numValues)(scala.util.Random.nextInt(100)).zipWithIndex.foreach { + case(rand, idx) => + when(counter === idx.U){ + if(rand < 50) + io.dataOut := rand.U + else + io.dataOut := (rand + 100).U + } + } + + // While chisel comes with an inbuilt Counter, we implement ours the old fashion way + // There are far more elegant ways of implementing this, read the chisel docs, discuss + // best practice among yourselves and experiment! + nextOutputIsFresh := true.B + when(io.next === true.B){ + when(counter < (numValues - 1).U){ + counter := counter + 1.U + }.otherwise { + counter := 0.U + } + }.otherwise { + nextOutputIsFresh := false.B + } + io.newOutput := nextOutputIsFresh +} + + +class mySelectorTest(c: mySelector) extends PeekPokeTester(c) { + poke(c.io.next, true.B) + for(ii <- 0 until 10){ + val wasStale = peek(c.io.newOutput) == 0 + val output = peek(c.io.dataOut).toString() + println(s"at step $ii:") + println(s"data out is $output") + println(s"was the output fresh? ${!wasStale}") + println() + step(1) + } + + poke(c.io.next, false.B) + + for(ii <- 0 until 3){ + val wasStale = peek(c.io.newOutput) == 0 + val output = peek(c.io.dataOut).toString() + println(s"at step $ii:") + println(s"data out is $output") + println(s"was the output fresh? ${!wasStale}") + println() + step(1) + } +} diff --git a/ov0/src/main/scala/daisyDot.scala b/ov0/src/main/scala/daisyDot.scala new file mode 100644 index 0000000..3215784 --- /dev/null +++ b/ov0/src/main/scala/daisyDot.scala @@ -0,0 +1,40 @@ +package Core +import chisel3._ +import chisel3.core.Input +import chisel3.iotesters.PeekPokeTester +import chisel3.util.Counter + +/** + DaisyVectors are not indexed. They have no control inputs or outputs, only data. + */ +class daisyDot(elements: Int, dataWidth: Int) extends Module{ + + val io = IO(new Bundle { + val dataInA = Input(UInt(dataWidth.W)) + val dataInB = Input(UInt(dataWidth.W)) + + val dataOut = Output(UInt(dataWidth.W)) + val outputValid = Output(Bool()) + }) + + val counter = Counter(elements) + val accumulator = RegInit(UInt(dataWidth.W), 0.U) + + /** + Your implementation here + */ + + /** + LF + */ + val product = io.dataInA * io.dataInB + when(counter.inc()){ + io.outputValid := true.B + accumulator := 0.U + }.otherwise{ + io.outputValid := false.B + accumulator := accumulator + product + } + + io.dataOut := accumulator + product +} diff --git a/ov0/src/main/scala/daisyGrid.scala b/ov0/src/main/scala/daisyGrid.scala index bb04f42..c29abe0 100644 --- a/ov0/src/main/scala/daisyGrid.scala +++ b/ov0/src/main/scala/daisyGrid.scala @@ -13,7 +13,7 @@ class daisyGrid(rows: Int, cols: Int, dataWidth: Int) extends Module{ val readEnable = Input(Bool()) val dataIn = Input(UInt(dataWidth.W)) - val readRow = Input(UInt(8.W)) + val rowSelect = Input(UInt(8.W)) val dataOut = Output(UInt(dataWidth.W)) }) @@ -25,6 +25,13 @@ class daisyGrid(rows: Int, cols: Int, dataWidth: Int) extends Module{ val elements = rows*cols + /** + Your implementation here + */ + + /** + LF + */ io.dataOut := 0.U for(ii <- 0 until rows){ @@ -32,27 +39,9 @@ class daisyGrid(rows: Int, cols: Int, dataWidth: Int) extends Module{ memRows(ii).readEnable := 0.U memRows(ii).dataIn := io.dataIn - when(io.readRow === ii.U ){ + when(io.rowSelect === ii.U ){ memRows(ii).readEnable := io.readEnable io.dataOut := memRows(ii).dataOut } } } - -class daisyGridTest(c: daisyGrid) extends PeekPokeTester(c) { - - poke(c.io.readEnable, 1) - for(ii <- 0 until 12){ - poke(c.io.dataIn, ii) - poke(c.io.readRow, ii/3) - step(1) - println("////////////////////") - } - poke(c.io.readEnable, 0) - for(ii <- 0 until 12){ - peek(c.io.dataOut) - poke(c.io.readRow, ii/3) - step(1) - println("////////////////////") - } -} diff --git a/ov0/src/main/scala/daisyMatMul.scala b/ov0/src/main/scala/daisyMatMul.scala index 57f4449..71fbd11 100644 --- a/ov0/src/main/scala/daisyMatMul.scala +++ b/ov0/src/main/scala/daisyMatMul.scala @@ -32,7 +32,6 @@ class daisyMultiplier(val rowsA: Int, val colsA: Int, val rowsB: Int, val colsB: val resultReady = RegInit(Bool(), false.B) - println(s"rowsA: $rowsA, colsA: $colsA, rowsB: $rowsB, colsB: $colsB") //////////////////////////////////////// //////////////////////////////////////// @@ -85,12 +84,12 @@ class daisyMultiplier(val rowsA: Int, val colsA: Int, val rowsB: Int, val colsB: //////////////////////////////////////// /// set up reading patterns depending on if we are in calculating state or not when(calculating === true.B){ - matrixA.readRow := rowOutputCounter + matrixA.rowSelect := rowOutputCounter }.otherwise{ - matrixA.readRow := rowCounter + matrixA.rowSelect := rowCounter } - matrixB.readRow := rowCounter + matrixB.rowSelect := rowCounter diff --git a/ov0/src/main/scala/daisyVec.scala b/ov0/src/main/scala/daisyVec.scala index c387b8d..23cc38a 100644 --- a/ov0/src/main/scala/daisyVec.scala +++ b/ov0/src/main/scala/daisyVec.scala @@ -17,7 +17,7 @@ class daisyVector(elements: Int, dataWidth: Int) extends Module{ val currentIndex = RegInit(UInt(8.W), 0.U) - val memory = Array.fill(elements)(Reg(UInt(dataWidth.W))) + val memory = Array.fill(elements)(RegInit(UInt(dataWidth.W), 0.U)) when(currentIndex === (elements - 1).U ){ currentIndex := 0.U @@ -37,21 +37,3 @@ class daisyVector(elements: Int, dataWidth: Int) extends Module{ } } } - -class daisyVectorTest(c: daisyVector) extends PeekPokeTester(c) { - - poke(c.io.readEnable, 1) - step(1) - - for(ii <- 0 until 4){ - poke(c.io.dataIn, ii) - println("////////////////////") - step(1) - } - - poke(c.io.readEnable, 0) - for(ii <- 0 until 4){ - peek(c.io.dataOut) - step(1) - } -} diff --git a/ov0/src/main/scala/daisyVecMat.scala b/ov0/src/main/scala/daisyVecMat.scala new file mode 100644 index 0000000..f01e90e --- /dev/null +++ b/ov0/src/main/scala/daisyVecMat.scala @@ -0,0 +1,117 @@ + +package Core +import Core.daisyVector +import chisel3._ +import chisel3.core.Input +import chisel3.iotesters.PeekPokeTester +import chisel3.util.Counter + +/** + The daisy multiplier creates two daisy grids, one transposed, and multiplies them. + */ +class daisyVecMat(val lengthA: Int, val rowsB: Int, val colsB: Int, val dataWidth: Int) extends Module { + + val io = IO(new Bundle { + + val dataInA = Input(UInt(dataWidth.W)) + val readEnableA = Input(Bool()) + + val dataInB = Input(UInt(dataWidth.W)) + val readEnableB = Input(Bool()) + + val dataOut = Output(UInt(dataWidth.W)) + val dataValid = Output(Bool()) + val done = Output(Bool()) + }) + + // How many cycles does it take to fill the matrices with data? + + + //////////////////////////////////////// + //////////////////////////////////////// + /// We transpose matrix B. + val vecA = Module(new daisyVector(lengthA, dataWidth)).io + val matrixB = Module(new daisyGrid(colsB, rowsB, dataWidth)).io + val dotProductCalculator = Module(new daisyDot(lengthA, dataWidth)).io + val dataIsLoaded = RegInit(Bool(), false.B) + + /** + Your implementation here + */ + + /** + LF + */ + val dataValid = Wire(Bool()) + + + //////////////////////////////////////// + //////////////////////////////////////// + /// Wire components + vecA.dataIn := io.dataInA + vecA.readEnable := io.readEnableA + + matrixB.dataIn := io.dataInB + matrixB.readEnable := io.readEnableB + + io.dataOut := dotProductCalculator.dataOut + + // allows us to use dataValid internally + io.dataValid := dataValid + + dotProductCalculator.dataInA := vecA.dataOut + dotProductCalculator.dataInB := matrixB.dataOut + dataValid := dotProductCalculator.outputValid & dataIsLoaded + + //////////////////////////////////////// + //////////////////////////////////////// + /// Select the correct row + val (currentCol, colDone) = Counter(true.B, colsB) + val (rowSel, _) = Counter(colDone, rowsB) + matrixB.rowSelect := rowSel + + + //////////////////////////////////////// + //////////////////////////////////////// + /// Check if data is loaded + val aReady = RegInit(Bool(), false.B) + val bReady = RegInit(Bool(), false.B) + + val (inputCounterA, counterAWrapped) = Counter(io.readEnableA, lengthA - 1) + when(counterAWrapped){ aReady := true.B } + + val (inputCounterB, counterBWrapped) = Counter(io.readEnableB, colsB*rowsB) + when(counterBWrapped){ bReady := true.B } + + dataIsLoaded := aReady & bReady + + + //////////////////////////////////////// + //////////////////////////////////////// + /// Check if we're done + val isDone = RegInit(Bool(), false.B) + val (numOutputted, numOutputtedWrapped) = Counter(dataValid, lengthA) + + when(numOutputtedWrapped){ isDone := true.B } + + + // printf(p"dataInA = ${io.dataInA}\n") + // printf(p"validA = ${io.readEnableA}\n") + // printf(p"dataInB = ${io.dataInB}\n") + // printf(p"validB = ${io.readEnableB}\n") + // printf(p"validOut = ${io.dataValid}\n") + // printf(p"data loaded = ${dataIsLoaded}\n") + // printf(p"aReady = ${aReady}\n") + // printf(p"bReady = ${bReady}\n") + + // printf(p"counter A = ${inputCounterA}\n") + // printf(p"counter B = ${inputCounterB}\n") + + // printf(p"out = ${dotProductCalculator.dataOut}\n\n") + + + + + io.done := isDone + +} diff --git a/ov0/src/main/scala/daisyVecVec.scala b/ov0/src/main/scala/daisyVecVec.scala new file mode 100644 index 0000000..4096bf0 --- /dev/null +++ b/ov0/src/main/scala/daisyVecVec.scala @@ -0,0 +1,40 @@ +package Core +import chisel3._ +import chisel3.core.Input +import chisel3.iotesters.PeekPokeTester +import chisel3.util.Counter + +/** + DaisyVectors are not indexed. They have no control inputs or outputs, only data. + */ +class daisyVecVec(elements: Int, dataWidth: Int) extends Module{ + + val io = IO(new Bundle { + val dataInA = Input(UInt(dataWidth.W)) + val dataInB = Input(UInt(dataWidth.W)) + + val dataOut = Output(UInt(dataWidth.W)) + val outputValid = Output(Bool()) + }) + + val counter = Counter(elements) + val accumulator = RegInit(UInt(dataWidth.W), 0.U) + + /** + Your implementation here + */ + + /** + LF + */ + val product = io.dataInA * io.dataInB + when(counter.inc()){ + io.outputValid := true.B + accumulator := 0.U + }.otherwise{ + io.outputValid := false.B + accumulator := accumulator + product + } + + io.dataOut := accumulator + product +} diff --git a/ov0/src/main/scala/oppgavetekst.org b/ov0/src/main/scala/oppgavetekst.org index 5b6acd8..d2a6fc1 100644 --- a/ov0/src/main/scala/oppgavetekst.org +++ b/ov0/src/main/scala/oppgavetekst.org @@ -4,12 +4,142 @@ In this exercise you will implement a circuit capable of performing matrix matrix multiplication in the chisel hardware description language. +* Your first component + There are two types of digital components: Combinatorial and stateful. + The first component we will consider is a simple combinatorial incrementor: + + #+begin_src scala + class myIncrement(incrementBy: Int) extends Module { + val io = IO( + new Bundle { + val dataIn = Input(UInt(32.W)) + val dataOut = Output(UInt(32.W)) + } + ) + + io.dataOut := io.dataIn + incrementBy.U + #+end_src + + Let's break the code down down. First, myIncrement is a Module, meaning that + this class can be instantiated as a hardware circuit. + Figure [rm3] shows the model that you have just declared. + A 32 bit signal, data_in goes in, and another 32 bit signal goes out. + + Apart from the IO, there is only one statement, assigning dataOut to dataIn + + incrementBy. + + In RTL the component looks like fig [rm4] + + Let's see how we can use our module: + #+begin_src scala + class myIncrementTwice(incrementBy: Int) extends Module { + val io = IO( + new Bundle { + val dataIn = Input(UInt(32.W)) + val dataOut = Output(UInt(32.W)) + } + ) + + val first = Module(new myIncrement(incrementBy)) + val second = Module(new myIncrement(incrementBy)) + + first.io.dataIn := io.dataIn + second.io.dataIn := first.io.dataOut + + io.dataOut := second.io.dataOut + } + #+end_src + + Fig [rm5] shows the RTL design, as expected it's just two incrementors + chained. + + The following code shows off how you can use for loops to instantiate an + arbitrary amount of modules. + #+begin_src scala + class myIncrementN(incrementBy: Int, numIncrementors: Int) extends Module { + val io = IO( + new Bundle { + val dataIn = Input(UInt(32.W)) + val dataOut = Output(UInt(32.W)) + } + ) + + val incrementors = Array.fill(numIncrementors){ Module(new myIncrement(incrementBy)) } + + for(ii <- 1 until numIncrementors){ + incrementors(ii).io.dataIn := incrementors(ii - 1).io.dataOut + } + + incrementors(0).io.dataIn := io.dataIn + io.dataOut := incrementors(numIncrementors).io.dataOut + } + #+end_src + Keep in mind that the for-loop only exists at design time, just like a for loop + generating a table in HTML will not be part of the finished HTML. + + So, what does combinatorial mean? + To answer that, let's create a stateful circuit first. + + #+begin_src scala + class myDelay() extends Module { + val io = IO( + new Bundle { + val dataIn = Input(UInt(32.W)) + val dataOut = Output(UInt(32.W)) + } + ) + val delayReg = RegInit(UInt(32.W), 0.U) + + delayReg := io.dataIn + io.dataOut := delayReg + } + #+end_src + + This circuit seems rather pointless, it simply assigns the input to the output. + However, the register has another input, as seen in the RTL: fig [rm6]. + The register can only change value during rising edges on the clock! + + To examplify, assume at step 0 data in is 0x45. + delayReg will now have 0x45 as its data in, but data out will still be 0. + Only when the clock ticks will delayReg.dataOut take on the value 0x45. + + You should now be able to implement myDelayN following the same principles as + myIncrementN + + #+begin_src scala + class myDelayN(delay: Int) extends Module { + val io = IO( + new Bundle { + val dataIn = Input(UInt(32.W)) + val dataOut = Output(UInt(32.W)) + } + ) + + ??? + } + #+end_src + + This should answer the initial question of combinatorial vs stateful: + The output of a combinatorial circuit will be available instantly, while + a stateful circuit will only update its output during rising edges on the + clock. + + Before you continue it is recommended that you check out the chisel3 + tutorials. + + In the basics.scala there is one more module, a basic selector. + At compile time this component builds n random numbers, to see which we can + cycle through them. + The component comes with a test, this test will be run when you do sbt.run + You should study this component. What is the difference between if/else and + when/otherwise? + * Matrix matrix multiplication When designing digital logic you should always start with decomposition. Your first task is therefore to implement a dot product calculator, since a matrix matrix multiplication is essentially a series of these. -* Dot Product calculator +* Dot Prod First, let's consider how a dot product calculator would look like in regular scala: @@ -25,20 +155,69 @@ } dotProduct } - - // Scala has rich support for functional programming - val dotProductFP = (vecA zip vecB) - .map{ case(a, b) => a*b } - .sum #+end_src - In the for loop version you can see how the dot product is sequentially + In the for loop you can see how the dot product is sequentially calculated by multiplying vector values of the same indice and summing the result. - The dot product for loop works in a similar fashion to your first design, but - before you get there some basics are in order. - -* Your first component - There are two types of digital components: Combinatorial and stateful. + To implement this logic in hardware the first thing you need is some way to + represent a vector which is your first task. +** Task 1 - Vector + The first component you should implement is a register bank for storing a vector. + This module works as follows: + + let dataOut(T) = if (T - vectorLength) < 0 then 0 else + if enableIn(T - vectorLength) + then dataIn(T - vectorLength) + else + dataOut(T - vectorLength) + + From the figure the principle of operation becomes clearer [inkskape drawing, rm sketch] + + To test your implementation you can run testOnly Core.daisyVecSpec in sbt + + +** Task 2 - Dot Product + Your next task is to implement daisyDot. + daisyDot should calculate the dot product of two vectors, inA and inB. Ensure that validOut + is only asserted when you have a result. Ensure that your accumulator gets flushed after + calculating your dot product. + +** Task 3 - Vector Matrix multiplication + Having implemented a dot product calculator, a vector matrix multiplier is not that different. + In imperative code we get something like this: + + #+begin_src scala + type Matrix[A] = List[List[A]] + def vectorMatrixMultiply(vec: List[Int], matrix: Matrix[Int]): List[Int] = { + val transposed = matrix.transpose + + val outputVector = Array.ofDim[Int](vec.length) + for(ii <- 0 until matrix.length){ + outputVector(ii) = dotProductForLoop(vec, transposed(ii)) + } + outputVector.toList + } + #+end_src scala + + This is just repeated application of dotProduct. + Since vector matrix multiplication is the dotproduct of the vector and the rows of the matrix, + the matrix must be transposed. + +*** Subtask 1 - representing a matrix + Like the dot product calculator, the first step is to implement a register bank for storing a matrix. + This can be done by creating n vectors from Task 1 and then select which row is the 'current' row. + + The matrix representation you have created in this task allows you to select which row to read, but + not which column. This isn't very efficient when you want to read an entire column since you would have + to wait a full cycle for each row. + The way we deal with this is noticing that when multiplying two matrices we work on a row basis in + matrix A, and column basis on matrix B. If we simply transpose matrix B, then accessing its rows is + the same as accessing the columns of matrix B. + + A consequence of this is that the API exposed by your matrix multiplier requires matrix B to be transposed. + +*** Subtask 2 - vector matrix multiplication + diff --git a/ov0/src/test/scala/tests.scala b/ov0/src/test/scala/tests.scala new file mode 100644 index 0000000..deb501d --- /dev/null +++ b/ov0/src/test/scala/tests.scala @@ -0,0 +1,317 @@ +package Core +import chisel3._ +import chisel3.util._ +import chisel3.core.Input +import chisel3.iotesters._ +import org.scalatest.{Matchers, FlatSpec} + + +class daisyVectorTest(c: daisyVector, inputs: List[(Int, Int, Int)]) extends PeekPokeTester(c) { + + (inputs).foreach { + case(enIn, dataIn, dataOut) => { + poke(c.io.readEnable, enIn) + poke(c.io.dataIn, dataIn) + expect(c.io.dataOut, dataOut) + step(1) + } + } + +} + +class daisyDotTest(c: daisyDot, inputs: List[(Int, Int, Option[Int], Int)]) extends PeekPokeTester(c) { + + (inputs).foreach { + case(inA, inB, dataOut, dataValid) => { + poke(c.io.dataInA, inA) + poke(c.io.dataInB, inB) + dataOut.foreach { expect(c.io.dataOut, _) } + expect(c.io.outputValid, dataValid) + step(1) + } + } + +} + +class daisyGridTest(c: daisyGrid, inputs: List[(Int, Int, Int, Int)]) extends PeekPokeTester(c) { + + (inputs).foreach { + case(readEnable, dataIn, readRow, dataOut) => { + poke(c.io.readEnable, readEnable) + poke(c.io.dataIn, dataIn) + poke(c.io.rowSelect, readRow) + expect(c.io.dataOut, dataOut) + step(1) + } + } + +} + + +class daisyVecMatTest(c: daisyVecMat, inputs: List[(Int,Int,Int,Int,Option[Int],Int,Int)]) extends PeekPokeTester(c) { + + (inputs).foreach { + case(dataInA, readEnableA, dataInB, readEnableB, dataOutExpect, dataValidExpect, doneExpect) => { + poke(c.io.dataInA, dataInA) + poke(c.io.dataInB, dataInB) + poke(c.io.readEnableA, readEnableA) + poke(c.io.readEnableB, readEnableB) + expect(c.io.dataValid, dataValidExpect) + expect(c.io.done, doneExpect) + dataOutExpect.foreach { expect(c.io.dataOut, _) } + step(1) + } + } + +} + + +class daisyVecSpec extends FlatSpec with Matchers { + + val input1 = List.fill(10)((0, 0x45, 0)) + + val input2 = input1 ++ List( + // enableIn, dataIn, expected + (1, 2, 0), + (1, 2, 0), + (1, 2, 0), + (1, 2, 0), + (0, 0, 2), + (0, 0, 2), + (0, 0, 2), + (0, 0, 2), + (0, 0, 2), + (0, 0, 2), + (0, 0, 2), + (0, 0, 2)) + + + val input3 = { + val inputs = List.fill(100)(scala.util.Random.nextInt(10000)) + val withExpected = (List.fill(4)(0) ++ inputs) zip inputs + val withEnabled = withExpected.map{ case(expected, in) => (1, in, expected) } + + withEnabled + } + + behavior of "daisy vector" + + it should "not read when read enable is low" in { + iotesters.Driver.execute(() => new daisyVector(4, 32), new TesterOptionsManager) { c => + new daisyVectorTest(c, input1) + } should be(true) + } + + + it should "read only when read enable is asserted" in { + iotesters.Driver.execute(() => new daisyVector(4, 32), new TesterOptionsManager) { c => + new daisyVectorTest(c, input2) + } should be(true) + } + + + it should "Work in general" in { + iotesters.Driver.execute(() => new daisyVector(4, 32), new TesterOptionsManager) { c => + new daisyVectorTest(c, input3) + } should be(true) + } +} + + +class daisyDotSpec extends FlatSpec with Matchers { + behavior of "daisy vector" + + val input1 = List( + (0, 0, None, 0), + (0, 0, None, 0), + (0, 0, None, 1), + (0, 0, None, 0), + (0, 0, None, 0), + (0, 0, None, 1), + (0, 0, None, 0), + (0, 0, None, 0), + (0, 0, None, 1)) + + it should "Only signal valid output at end of calculation" in { + iotesters.Driver.execute(() => new daisyDot(3, 32), new TesterOptionsManager) { c => + new daisyDotTest(c, input1) + } should be(true) + } + + + val input2 = List( + (1, 0, None, 0), + (1, 0, None, 0), + (1, 0, Some(3), 1), + (1, 0, None, 0), + (1, 0, None, 0), + (1, 0, Some(3), 1), + (1, 0, None, 0), + (1, 0, None, 0), + (1, 0, Some(3), 1)) + + it should "Be able to count to 3" in { + iotesters.Driver.execute(() => new daisyDot(3, 32), new TesterOptionsManager) { c => + new daisyDotTest(c, input1) + } should be(true) + } + + def createProblem(vecLen: Int): List[(Int, Int, Option[Int], Int)] = { + val in1 = List.fill(vecLen)(scala.util.Random.nextInt(10)) + val in2 = List.fill(vecLen)(scala.util.Random.nextInt(10)) + + val dotProduct = (in1, in2).zipped.map(_*_).sum + + (in1, in2, (0 to vecLen)).zipped.map{ + case(a, b, idx) => + val dpExpect = if(idx == (vecLen - 1)) Some(dotProduct) else None + val outExpect = if(idx == (vecLen - 1)) 1 else 0 + + (a, b, dpExpect, outExpect) + } + } + + + def createProblems(vecLen: Int): List[(Int, Int, Option[Int], Int)] = + List.fill(10)(createProblem(vecLen)).flatten + + + it should "Be able to calculate dot products" in { + iotesters.Driver.execute(() => new daisyDot(10, 32), new TesterOptionsManager) { c => + new daisyDotTest(c, createProblems(10)) + } should be(true) + } +} + + +class daisyGridSpec extends FlatSpec with Matchers { + type Matrix[A] = List[List[A]] + + behavior of "daisy grid" + + def genMatrix(dims: (Int,Int)): Matrix[Int] = + List.fill(dims._1)( + List.fill(dims._2)(scala.util.Random.nextInt(100)) + ) + + def readRowCheck(dims: (Int,Int)): List[(Int,Int,Int,Int)] = { + // readEn, dataIn, readRow, expected dataOut + List.fill(dims._1 - 1)(( 1, 1, 0, 0)) ++ + List.fill(dims._1 - 1)((0, 0, 0, 1)) ++ + List.fill(dims._1 - 1)((0, 0, 0, 1)) + } + + def readRow2Check(dims: (Int,Int)): List[(Int,Int,Int,Int)] = { + // readEn, dataIn, readRow, expected dataOut + List.fill(dims._1 - 1)(( 1, 1, 1, 0)) ++ + List.fill(dims._1 - 1)((0, 0, 1, 1)) ++ + List.fill(dims._1 - 1)((0, 0, 1, 1)) + } + + def readMatrix(dims: (Int,Int)): List[(Int,Int,Int,Int)] = { + val m = genMatrix(dims) + val input = m.zipWithIndex.map{ case(row, rowIdx) => + row.zipWithIndex.map{ case(a, colIdx) => + // readEn, dataIn, readRow, expected dataOut + ( 1, a, rowIdx, 0) + } + }.flatten + + val output = m.zipWithIndex.map{ case(row, rowIdx) => + row.zipWithIndex.map{ case(a, colIdx) => + // readEn, dataIn, readRow, expected dataOut + ( 0, 0, rowIdx, a) + } + }.flatten + + input ++ output + } + + + it should "work like a regular daisyVec when row select is fixed to 0" in { + iotesters.Driver.execute(() => new daisyGrid(5, 4, 32), new TesterOptionsManager) { c => + new daisyGridTest(c, readRowCheck((5,4))) + } should be(true) + } + + + it should "work like a regular daisyVec when row select is fixed to 1" in { + iotesters.Driver.execute(() => new daisyGrid(5, 4, 32), new TesterOptionsManager) { c => + new daisyGridTest(c, readRow2Check((5,4))) + } should be(true) + } + + + it should "be able to read a matrix" in { + iotesters.Driver.execute(() => new daisyGrid(5, 4, 32), new TesterOptionsManager) { c => + new daisyGridTest(c, readMatrix((5,4))) + } should be(true) + } +} + + +class daisyVecMatSpec extends FlatSpec with Matchers { + type Matrix[A] = List[List[A]] + def genMatrix(dims: (Int,Int)): Matrix[Int] = + List.fill(dims._1)( + List.fill(dims._2)(scala.util.Random.nextInt(4)) + ) + + + def generateInputs(dims: (Int,Int)): List[(Int,Int,Int,Int,Option[Int],Int,Int)] = { + + val matrixB = genMatrix(dims) + val vecA = genMatrix((1, (dims._1))).head + + println("multiplying: ") + println(vecA.mkString("[","\t","]")) + println("matrix:") + matrixB.foreach { row => + println(row.mkString("[","\t","]")) + } + + def answers: List[Int] = matrixB.transpose.map( col => + (col, vecA).zipped.map(_*_).sum + ) + + println("should equal") + println(answers.mkString("[","\t","]")) + + + val vecAndMatrixInput = (matrixB.head zip vecA).map{ + case(m, v) => + (v, 1, m, 1, None, 0, 0) + } + + val matrixInput = matrixB.tail.flatten.map{ m => + (0, 0, m, 1, None, 0, 0) + } + + val checkOutput = answers.map( a => + { + val filler = List.fill(dims._2 - 1)((0, 0, 0, 0, None, 0, 0)) + val check = List((0, 0, 0, 0, Some(a), 0, 0)) + filler ++ check + }).flatten + + vecAndMatrixInput ::: matrixInput ::: checkOutput + } + + + + behavior of "vec mat multiplier" + + it should "compile" in { + iotesters.Driver.execute(() => new daisyVecMat(5, 4, 5, 32), new TesterOptionsManager) { c => + new daisyVecMatTest(c, Nil) + } should be(true) + } + + + + it should "Not assert valid output when loading data" in { + iotesters.Driver.execute(() => new daisyVecMat(5, 4, 5, 32), new TesterOptionsManager) { c => + new daisyVecMatTest(c, generateInputs((5,4))) + } should be(true) + } +}