| @@ -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) | 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 { | 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 = { | def wrapTester(test: => Unit): Unit = { | ||||
| try { test } | try { test } | ||||
| catch { | 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) | |||||
| } | |||||
| } | |||||
| } | |||||