| @@ -0,0 +1,39 @@ | |||
| package Ex0 | |||
| import chisel3._ | |||
| import chisel3.util.Counter | |||
| class DotProd(val elements: Int) extends Module { | |||
| val io = IO( | |||
| new Bundle { | |||
| val dataInA = Input(UInt(32.W)) | |||
| val dataInB = Input(UInt(32.W)) | |||
| val dataOut = Output(UInt(32.W)) | |||
| val outputValid = Output(Bool()) | |||
| } | |||
| ) | |||
| /** | |||
| * Your code here | |||
| */ | |||
| val counter = Counter(elements) | |||
| val accumulator = RegInit(UInt(32.W), 0.U) | |||
| val product = io.dataInA * io.dataInB | |||
| /** | |||
| * LF | |||
| */ | |||
| when(counter.inc()){ | |||
| io.outputValid := true.B | |||
| accumulator := 0.U | |||
| }.otherwise{ | |||
| io.outputValid := false.B | |||
| accumulator := accumulator + product | |||
| } | |||
| io.dataOut := accumulator + product | |||
| } | |||
| @@ -0,0 +1,111 @@ | |||
| package Ex0 | |||
| import chisel3._ | |||
| import chisel3.util.Counter | |||
| import chisel3.experimental.MultiIOModule | |||
| class MatMul(val rowDimsA: Int, val colDimsA: Int) extends MultiIOModule { | |||
| val io = IO( | |||
| new Bundle { | |||
| val dataInA = Input(UInt(32.W)) | |||
| val dataInB = Input(UInt(32.W)) | |||
| val dataOut = Output(UInt(32.W)) | |||
| val outputValid = Output(Bool()) | |||
| } | |||
| ) | |||
| val debug = IO( | |||
| new Bundle { | |||
| val ready = Output(Bool()) | |||
| val dpValid = Output(Bool()) | |||
| val rowSelA = Output(UInt(32.W)) | |||
| val rowSelB = Output(UInt(32.W)) | |||
| val colSel = Output(UInt(32.W)) | |||
| val ma2dp = Output(UInt(32.W)) | |||
| val mb2dp = Output(UInt(32.W)) | |||
| } | |||
| ) | |||
| /** | |||
| * Your code here | |||
| */ | |||
| val matrixA = Module(new Matrix(rowDimsA, colDimsA)).io | |||
| val matrixB = Module(new Matrix(rowDimsA, colDimsA)).io | |||
| val dotProdCalc = Module(new DotProd(colDimsA)).io | |||
| // matrixA.dataIn := 0.U | |||
| // matrixA.rowIdx := 0.U | |||
| // matrixA.colIdx := 0.U | |||
| // matrixA.readEnable := false.B | |||
| // matrixB.rowIdx := 0.U | |||
| // matrixB.colIdx := 0.U | |||
| // matrixB.dataIn := 0.U | |||
| // matrixB.readEnable := false.B | |||
| // dotProdCalc.dataInA := 0.U | |||
| // dotProdCalc.dataInB := 0.U | |||
| // io.dataOut := 0.U | |||
| // io.outputValid := false.B | |||
| /** | |||
| * LF | |||
| */ | |||
| // Get the data in | |||
| val ready = RegInit(false.B) | |||
| val (colCounter, colCounterWrap) = Counter(true.B, colDimsA) | |||
| val (rowSelA, rowSelAWrap) = Counter(colCounterWrap, rowDimsA) | |||
| val (rowSelB, _) = Counter(rowSelAWrap & ready, rowDimsA * colDimsA) | |||
| when(!ready){ | |||
| ready := rowSelAWrap | |||
| matrixA.readEnable := true.B | |||
| matrixB.readEnable := true.B | |||
| matrixA.colIdx := colCounter | |||
| matrixA.rowIdx := rowSelA | |||
| matrixB.colIdx := colCounter | |||
| matrixB.rowIdx := rowSelA | |||
| }.otherwise{ | |||
| matrixA.readEnable := false.B | |||
| matrixB.readEnable := false.B | |||
| matrixA.colIdx := colCounter | |||
| matrixA.rowIdx := rowSelB | |||
| matrixB.colIdx := colCounter | |||
| matrixB.rowIdx := rowSelA | |||
| } | |||
| matrixA.dataIn := io.dataInA | |||
| matrixB.dataIn := io.dataInB | |||
| dotProdCalc.dataInA := matrixA.dataOut | |||
| dotProdCalc.dataInB := matrixB.dataOut | |||
| io.dataOut := dotProdCalc.dataOut | |||
| io.outputValid := dotProdCalc.outputValid & ready | |||
| debug.ready := ready | |||
| debug.dpValid := dotProdCalc.outputValid | |||
| debug.rowSelA := rowSelA | |||
| debug.rowSelB := rowSelB | |||
| debug.colSel := colCounter | |||
| debug.ma2dp := matrixA.dataOut | |||
| debug.mb2dp := matrixB.dataOut | |||
| } | |||
| @@ -0,0 +1,53 @@ | |||
| package Ex0 | |||
| import chisel3._ | |||
| // This import statement makes the scala vector invisible, reducing confusion | |||
| import scala.collection.immutable.{ Vector => _ } | |||
| class Matrix(val rowsDim: Int, val colsDim: Int) extends Module { | |||
| val io = IO( | |||
| new Bundle { | |||
| val colIdx = Input(UInt(32.W)) | |||
| val rowIdx = Input(UInt(32.W)) | |||
| val dataIn = Input(UInt(32.W)) | |||
| val readEnable = Input(Bool()) | |||
| val dataOut = Output(UInt(32.W)) | |||
| } | |||
| ) | |||
| /** | |||
| * Your code here | |||
| */ | |||
| // Creates a vector of zero-initialized registers | |||
| val rows = Vec.fill(rowsDim)(Module(new Vector(colsDim)).io) | |||
| // placeholders | |||
| io.dataOut := 0.U | |||
| for(ii <- 0 until rowsDim){ | |||
| rows(ii).dataIn := 0.U | |||
| rows(ii).readEnable := false.B | |||
| rows(ii).idx := 0.U | |||
| } | |||
| /** | |||
| * LF | |||
| */ | |||
| for(ii <- 0 until rowsDim){ | |||
| rows(ii).dataIn := io.dataIn | |||
| rows(ii).idx := io.colIdx | |||
| when(ii.U === io.rowIdx){ | |||
| rows(ii).readEnable := io.readEnable | |||
| }.otherwise{ | |||
| rows(ii).readEnable := false.B | |||
| } | |||
| } | |||
| io.dataOut := rows(io.rowIdx).dataOut | |||
| } | |||
| @@ -0,0 +1,36 @@ | |||
| package Ex0 | |||
| import chisel3._ | |||
| class Vector(val elements: Int) extends Module { | |||
| val io = IO( | |||
| new Bundle { | |||
| val idx = Input(UInt(32.W)) | |||
| val dataIn = Input(UInt(32.W)) | |||
| val readEnable = Input(Bool()) | |||
| val dataOut = Output(UInt(32.W)) | |||
| } | |||
| ) | |||
| /** | |||
| * Your code here | |||
| */ | |||
| // Creates a vector of zero-initialized registers | |||
| val contents = RegInit(VecInit(List.fill(elements)(0.U(32.W)))) | |||
| // placeholder | |||
| io.dataOut := 0.U | |||
| /** | |||
| * LF | |||
| */ | |||
| io.dataOut := contents(io.idx) | |||
| when(io.readEnable){ | |||
| contents(io.idx) := io.dataIn | |||
| } | |||
| } | |||
| @@ -0,0 +1,109 @@ | |||
| package Ex0 | |||
| import chisel3._ | |||
| import chisel3.iotesters.PeekPokeTester | |||
| import org.scalatest.{Matchers, FlatSpec} | |||
| import TestUtils._ | |||
| class DotProdSpec extends FlatSpec with Matchers { | |||
| import DotProdTests._ | |||
| val elements = scala.util.Random.nextInt(5) + 2 | |||
| behavior of "DotProd" | |||
| it should "Only signal valid output at end of calculation" in { | |||
| wrapTester( | |||
| chisel3.iotesters.Driver(() => new DotProd(elements)) { c => | |||
| new SignalsWhenDone(c) | |||
| } should be(true) | |||
| ) | |||
| } | |||
| it should "Calculate the correct output" in { | |||
| wrapTester( | |||
| chisel3.iotesters.Driver(() => new DotProd(elements)) { c => | |||
| new CalculatesCorrectResult(c) | |||
| } should be(true) | |||
| ) | |||
| } | |||
| it should "Calculate the correct output and signal when appropriate" in { | |||
| wrapTester( | |||
| chisel3.iotesters.Driver(() => new DotProd(elements)) { c => | |||
| new CalculatesCorrectResultAndSignals(c) | |||
| } should be(true) | |||
| ) | |||
| } | |||
| } | |||
| object DotProdTests { | |||
| class SignalsWhenDone(c: DotProd) extends PeekPokeTester(c) { | |||
| for(ii <- 0 until c.elements - 1){ | |||
| expect(c.io.outputValid, false) | |||
| step(1) | |||
| } | |||
| expect(c.io.outputValid, true) | |||
| step(1) | |||
| for(ii <- 0 until c.elements - 1){ | |||
| expect(c.io.outputValid, false) | |||
| step(1) | |||
| } | |||
| expect(c.io.outputValid, true) | |||
| step(1) | |||
| } | |||
| class CalculatesCorrectResult(c: DotProd) extends PeekPokeTester(c) { | |||
| val inputsA = List.fill(c.elements)(scala.util.Random.nextInt(10)) | |||
| val inputsB = List.fill(c.elements)(scala.util.Random.nextInt(10)) | |||
| val expectedOutput = (for ((a, b) <- inputsA zip inputsB) yield a * b) sum | |||
| for(ii <- 0 until c.elements){ | |||
| poke(c.io.dataInA, inputsA(ii)) | |||
| poke(c.io.dataInB, inputsB(ii)) | |||
| if(ii == c.elements - 1) | |||
| expect(c.io.dataOut, expectedOutput) | |||
| step(1) | |||
| } | |||
| } | |||
| class CalculatesCorrectResultAndSignals(c: DotProd) extends PeekPokeTester(c) { | |||
| val inputsA = List.fill(c.elements)(scala.util.Random.nextInt(10)) | |||
| val inputsB = List.fill(c.elements)(scala.util.Random.nextInt(10)) | |||
| val expectedOutput = (for ((a, b) <- inputsA zip inputsB) yield a * b) sum | |||
| for(ii <- 0 until c.elements){ | |||
| poke(c.io.dataInA, inputsA(ii)) | |||
| poke(c.io.dataInB, inputsB(ii)) | |||
| if(ii == c.elements - 1){ | |||
| expect(c.io.dataOut, expectedOutput) | |||
| expect(c.io.outputValid, true) | |||
| } | |||
| else | |||
| expect(c.io.outputValid, false) | |||
| step(1) | |||
| } | |||
| for(ii <- 0 until c.elements){ | |||
| poke(c.io.dataInA, inputsA(ii)) | |||
| poke(c.io.dataInB, inputsB(ii)) | |||
| if(ii == c.elements - 1){ | |||
| expect(c.io.dataOut, expectedOutput) | |||
| expect(c.io.outputValid, true) | |||
| } | |||
| else | |||
| expect(c.io.outputValid, false) | |||
| step(1) | |||
| } | |||
| } | |||
| } | |||
| @@ -174,3 +174,39 @@ class DelayTester(c: SimpleDelay) extends PeekPokeTester(c) { | |||
| expect(c.io.dataOut, input) | |||
| } | |||
| } | |||
| class DPCsimulatorSpec extends FlatSpec with Matchers { | |||
| case class DotProdCalculator(vectorLen: Int, timeStep: Int = 0, accumulator: Int = 0){ | |||
| def update(inputA: Int, inputB: Int): (Int, Boolean, DotProdCalculator) = { | |||
| val product = inputA * inputB | |||
| if(((timeStep + 1) % vectorLen) == 0) | |||
| (accumulator + product, true, this.copy(timeStep = 0, accumulator = 0)) | |||
| else | |||
| (accumulator + product, false, this.copy(timeStep = this.timeStep + 1, accumulator = accumulator + product)) | |||
| } | |||
| } | |||
| val myDPC = DotProdCalculator(4) | |||
| val dpcStream = Stream.iterate((0, myDPC)){ case(ts, dpc) => | |||
| val a = scala.util.Random.nextInt(4) | |||
| val b = scala.util.Random.nextInt(4) | |||
| val (output, valid, nextDPC) = dpc.update(a, b) | |||
| val validString = if(valid) "yes" else "no" | |||
| println(s"at timestep $ts:") | |||
| println(s"INPUTS:") | |||
| println(s"inputA: $a, inputB: $b") | |||
| println(s"OUTPUTS:") | |||
| println(s"output: $output, valid: $validString\n\n") | |||
| (ts + 1, nextDPC) | |||
| }.take(20) | |||
| behavior of "Dot product simulator" | |||
| it should "Be shoehorned into a test" in { | |||
| dpcStream.last | |||
| } | |||
| } | |||
| @@ -0,0 +1,73 @@ | |||
| package Ex0 | |||
| import chisel3._ | |||
| import chisel3.iotesters.PeekPokeTester | |||
| import org.scalatest.{Matchers, FlatSpec} | |||
| import TestUtils._ | |||
| class MatMulSpec extends FlatSpec with Matchers { | |||
| import MatMulTests._ | |||
| val rowDims = scala.util.Random.nextInt(5) + 3 | |||
| val colDims = scala.util.Random.nextInt(5) + 3 | |||
| behavior of "MatMul" | |||
| it should "Do shit" in { | |||
| wrapTester( | |||
| chisel3.iotesters.Driver(() => new MatMul(rowDims, colDims)) { c => | |||
| new FullMatMul(c) | |||
| } should be(true) | |||
| ) | |||
| } | |||
| } | |||
| object MatMulTests { | |||
| class TestExample(c: MatMul) extends PeekPokeTester(c) { | |||
| val mA = genMatrix(c.rowDimsA, c.colDimsA) | |||
| val mB = genMatrix(c.rowDimsA, c.colDimsA) | |||
| val mC = matrixMultiply(mA, mB.transpose) | |||
| } | |||
| class FullMatMul(c: MatMul) extends PeekPokeTester(c) { | |||
| val mA = genMatrix(c.rowDimsA, c.colDimsA) | |||
| val mB = genMatrix(c.rowDimsA, c.colDimsA) | |||
| val mC = matrixMultiply(mA, mB.transpose) | |||
| println("Multiplying") | |||
| println(printMatrix(mA)) | |||
| println("With") | |||
| println(printMatrix(mB.transpose)) | |||
| println("Expecting") | |||
| println(printMatrix(mC)) | |||
| // Input data | |||
| for(ii <- 0 until c.colDimsA * c.rowDimsA){ | |||
| val rowInputIdx = ii / c.colDimsA | |||
| val colInputIdx = ii % c.colDimsA | |||
| poke(c.io.dataInA, mA(rowInputIdx)(colInputIdx)) | |||
| poke(c.io.dataInB, mB(rowInputIdx)(colInputIdx)) | |||
| expect(c.io.outputValid, false, "Valid output during initialization") | |||
| step(1) | |||
| } | |||
| // Perform calculation | |||
| for(ii <- 0 until (c.rowDimsA * c.rowDimsA)){ | |||
| for(kk <- 0 until c.colDimsA - 1){ | |||
| expect(c.io.outputValid, false, "Valid output mistimed") | |||
| step(1) | |||
| } | |||
| expect(c.io.outputValid, true, "Valid output timing is wrong") | |||
| expect(c.io.dataOut, mC(ii / c.rowDimsA)(ii % c.rowDimsA), "Wrong value calculated") | |||
| step(1) | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,101 @@ | |||
| package Ex0 | |||
| import chisel3._ | |||
| import chisel3.iotesters.PeekPokeTester | |||
| import org.scalatest.{Matchers, FlatSpec} | |||
| import TestUtils._ | |||
| import scala.collection.immutable.{ Vector => _ } | |||
| class MatrixSpec extends FlatSpec with Matchers { | |||
| import MatrixTests._ | |||
| behavior of "Matrix" | |||
| val rowDims = scala.util.Random.nextInt(5) + 3 | |||
| val colDims = scala.util.Random.nextInt(5) + 3 | |||
| it should "Update its contents" in { | |||
| wrapTester( | |||
| chisel3.iotesters.Driver(() => new Matrix(10,10)) { c => | |||
| new UpdatesData(c) | |||
| } should be(true) | |||
| ) | |||
| } | |||
| it should "Retain its contents when readEnable is low" in { | |||
| wrapTester( | |||
| chisel3.iotesters.Driver(() => new Matrix(10,10)) { c => | |||
| new UpdatesData(c) | |||
| } should be(true) | |||
| ) | |||
| } | |||
| } | |||
| object MatrixTests { | |||
| class UpdatesData(c: Matrix) extends PeekPokeTester(c) { | |||
| val inputs = List.fill(c.colsDim){ | |||
| List.fill(c.rowsDim)(scala.util.Random.nextInt(20) + 1) | |||
| } | |||
| poke(c.io.readEnable, true) | |||
| for(col <- 0 until c.colsDim){ | |||
| for(row <- 0 until c.rowsDim){ | |||
| poke(c.io.colIdx, col) | |||
| poke(c.io.rowIdx, row) | |||
| poke(c.io.dataIn, inputs(col)(row)) | |||
| step(1) | |||
| } | |||
| } | |||
| for(col <- 0 until c.colsDim){ | |||
| for(row <- 0 until c.rowsDim){ | |||
| poke(c.io.colIdx, col) | |||
| poke(c.io.rowIdx, row) | |||
| expect(c.io.dataOut, inputs(col)(row)) | |||
| step(1) | |||
| } | |||
| } | |||
| } | |||
| class RetainsData(c: Matrix) extends PeekPokeTester(c) { | |||
| val inputs = List.fill(c.colsDim){ | |||
| List.fill(c.rowsDim)(scala.util.Random.nextInt(20) + 1) | |||
| } | |||
| poke(c.io.readEnable, true) | |||
| for(col <- 0 until c.colsDim){ | |||
| for(row <- 0 until c.rowsDim){ | |||
| poke(c.io.colIdx, col) | |||
| poke(c.io.rowIdx, row) | |||
| poke(c.io.dataIn, inputs(col)(row)) | |||
| step(1) | |||
| } | |||
| } | |||
| poke(c.io.readEnable, false) | |||
| for(col <- 0 until c.colsDim){ | |||
| for(row <- 0 until c.rowsDim){ | |||
| poke(c.io.colIdx, col) | |||
| poke(c.io.rowIdx, row) | |||
| poke(c.io.dataIn, 0) | |||
| step(1) | |||
| } | |||
| } | |||
| for(col <- 0 until c.colsDim){ | |||
| for(row <- 0 until c.rowsDim){ | |||
| poke(c.io.colIdx, col) | |||
| poke(c.io.rowIdx, row) | |||
| expect(c.io.dataOut, inputs(col)(row)) | |||
| step(1) | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -6,6 +6,23 @@ import org.scalatest.{Matchers, FlatSpec} | |||
| object TestUtils { | |||
| def genMatrix(rows: Int, cols: Int) = List.fill(rows)( | |||
| List.fill(cols)(scala.util.Random.nextInt(5)) | |||
| ) | |||
| def printVector(v: List[Int]): String = | |||
| v.map(x => "%3d".format(x)).mkString("[",",","]") | |||
| def printMatrix(m: List[List[Int]]): String = | |||
| "\n" + m.map(printVector).mkString("\n") | |||
| def dotProduct(xs: List[Int], ys: List[Int]): Int = | |||
| (for ((x, y) <- xs zip ys) yield x * y) sum | |||
| def matrixMultiply(ma: List[List[Int]], mb: List[List[Int]]): List[List[Int]] = | |||
| ma.map(mav => mb.transpose.map(mbv => dotProduct(mav,mbv))) | |||
| def wrapTester(test: => Unit): Unit = { | |||
| try { test } | |||
| catch { | |||
| @@ -0,0 +1,108 @@ | |||
| package Ex0 | |||
| import chisel3._ | |||
| import chisel3.iotesters.PeekPokeTester | |||
| import org.scalatest.{Matchers, FlatSpec} | |||
| import TestUtils._ | |||
| import scala.collection.immutable.{ Vector => _ } | |||
| class VectorSpec extends FlatSpec with Matchers { | |||
| import VectorTests._ | |||
| val elements = scala.util.Random.nextInt(5) + 2 | |||
| behavior of "Vector" | |||
| it should "Not read data when read enable is false" in { | |||
| wrapTester( | |||
| chisel3.iotesters.Driver(() => new Vector(elements)) { c => | |||
| new ReadEnable(c) | |||
| } should be(true) | |||
| ) | |||
| } | |||
| it should "Update its registers when read enable is true" in { | |||
| wrapTester( | |||
| chisel3.iotesters.Driver(() => new Vector(elements)) { c => | |||
| new UpdatesData(c) | |||
| } should be(true) | |||
| ) | |||
| } | |||
| it should "Retain its data once read enable is set to false" in { | |||
| wrapTester( | |||
| chisel3.iotesters.Driver(() => new Vector(elements)) { c => | |||
| new UpdatesData(c) | |||
| } should be(true) | |||
| ) | |||
| } | |||
| } | |||
| object VectorTests { | |||
| class ReadEnable(c: Vector) extends PeekPokeTester(c) { | |||
| poke(c.io.dataIn, 123) | |||
| poke(c.io.readEnable, false) | |||
| for(ii <- 0 until c.elements){ | |||
| poke(c.io.idx, ii) | |||
| step(1) | |||
| expect(c.io.dataOut, 0) | |||
| } | |||
| poke(c.io.dataIn, 124) | |||
| for(ii <- 0 until c.elements){ | |||
| poke(c.io.idx, ii) | |||
| step(1) | |||
| expect(c.io.dataOut, 0) | |||
| } | |||
| } | |||
| class UpdatesData(c: Vector) extends PeekPokeTester(c) { | |||
| poke(c.io.readEnable, true) | |||
| for(ii <- 0 until c.elements){ | |||
| poke(c.io.idx, ii) | |||
| poke(c.io.dataIn, ii) | |||
| step(1) | |||
| } | |||
| for(ii <- 0 until c.elements){ | |||
| poke(c.io.idx, ii) | |||
| expect(c.io.dataOut, ii) | |||
| step(1) | |||
| } | |||
| } | |||
| class RetainsData(c: Vector) extends PeekPokeTester(c) { | |||
| poke(c.io.readEnable, true) | |||
| for(ii <- 0 until c.elements){ | |||
| poke(c.io.idx, ii) | |||
| poke(c.io.dataIn, ii) | |||
| step(1) | |||
| } | |||
| poke(c.io.readEnable, false) | |||
| for(ii <- 0 until c.elements){ | |||
| poke(c.io.idx, ii) | |||
| expect(c.io.dataOut, ii) | |||
| step(1) | |||
| } | |||
| for(ii <- 0 until c.elements){ | |||
| poke(c.io.idx, ii) | |||
| expect(c.io.dataOut, ii) | |||
| step(1) | |||
| } | |||
| } | |||
| } | |||