diff --git a/oppgavetekst.org b/oppgavetekst.org index f0d5bdb..973c951 100644 --- a/oppgavetekst.org +++ b/oppgavetekst.org @@ -108,7 +108,6 @@ } #+end_src - ** Scala and chisel The code for these snippets can be found in Example.scala in the test directory. You can run them using sbt by running ./sbt in your project root which will open @@ -579,7 +578,84 @@ Before you continue you should have a good grasp on the difference between scala and chisel. For instance, what is the difference between ~=~ and ~:=~? If ~a~ is the input for a module, and ~b~ is the output, should it be ~a := b~ or ~b := a~? + What's the difference between + ~if( ... ) ... else ...~ + and + ~when( ... ){ ... }.elsewhen( ... ){ ... }.otherwise{ ... }~ + ? + +** Debugging + A rather nasty pain point in chisel is the debuggability. + In order to inspect our circuits we have two main tools, the peekPokeTester and trusty + old printf, however both have huge flaws. + +*** Printf + Printf statements will be executed once per clock cycle if the surrounding block is executed. + This means we can put a printf statement in a module and have it print some state every + cycle, and we can put it inside a when block in order to conditionally print. + + Other than quickly creating a tremendous amount of noise, printf has a tendency to fool you + since it often reports values that are one clock cycle off. + + To see this in action, try running EvilPrintfSpec + +*** PeekPoke + The good thing about PeekPokeTester is that it won't lie to you, but it's not a very + flexible tester either. + + The most annoying flaw is that it cannot inspect the value of a submodule. + + Consider the following module + #+begin_src scala + class Outer() extends Module { + val io = IO( + new Bundle { + val dataIn = Input(UInt(32.W)) + val dataOut = Output(UInt(32.W)) + } + ) + + val inner = Module(new Inner).io + + inner.dataIn := io.dataIn + io.dataOut := inner.dataOut + } + #+end_src + + It would be nice if we could use the peekPokeTester to inspect what goes on inside + Inner, however this information gets removed before the peekPokeTester is run. + + The way I deal with this is using a multiIOModule. + In this example I have done the same for inner, using a special debug IO bundle to + separate the modules interface and whatever debug signals I'm interested in. + + MultiIOModule can do everything Module can, so if you want to you can use it everywhere. + #+begin_src scala + import chisel3.experimental.MultiIOModule + + class Outer() extends MultiIOModule { + val io = IO( + new Bundle { + val dataIn = Input(UInt(32.W)) + val dataOut = Output(UInt(32.W)) + } + ) + + val debug = IO( + new Bundle { + val innerState = Output(UInt(32.W)) + } + ) + + val inner = Module(new Inner) + + inner.io.dataIn := io.dataIn + io.dataOut := inner.io.dataOut + + debug.innerState := inner.debug.frobnicatorState + } + #+end_src * Matrix matrix multiplication For your first foray into chisel you will design a matrix matrix multiplication unit. @@ -641,6 +717,7 @@ When performing matrix multiplication on a computer transposing the second matrix can help us reduce complexity by quite a lot. To examplify, consider + #+begin_src | 2, 5 | A = | 7, -1 | | 0, 4 | @@ -648,14 +725,43 @@ B = | 1, 1, 2 | | 0, 4, 0 | + #+end_src + + It would be much simpler to just have two modules with the same dimensions, and we + can do this by transposing B so we get + #+begin_src + | 2, 5 | + A = | 7, -1 | + | 0, 4 | -** Bonus exercise - Introspection on code quality and design choices - This "exercise" has no deliverable, but you should spend some time thinking about - what parts gave you most trouble and what you can do to change your approach. + | 1, 0 | + BT = | 1, 4 | + | 2, 0 | + #+end_src - In addition, the implementation you were railroaded into has a flaw that lead to - unescessary code duplication when going from a vector matrix multiplier to a matrix - matrix multiplier. + Now all we need to do is calculate the dot products for the final matrix: + + #+begin_src + A*B = C then + + | A[0] × BT[0], A[0] × BT[1], A[0] × BT[2] | + C = | A[1] × BT[0], ... , ... | + | ... , ... , A[2] × BT[2] | + + #+end_src + + Because of this, the input for matrix B will be supplied transposed, thus you do not + have to worry about this. For B the input would be [1, 0, 1, 4, 2, 0] - Why did this happen, and how could this have been avoided? + The skeleton code for the matrix multiplier is less detailed, with only one test. + You're encouraged to write your own tests to make this easier. + Additionally, if you feel like you're getting stuck you can take a look at + MatMulTips.org + +** Bonus exercise - Introspection on code quality and design choices + This last exercise has no deliverable, but you should spend some time thinking about + where you spent most of your efforts. + + A common saying is "A few hours of work can save you from several minutes of planning", + and this holds especially true for writing chisel!! diff --git a/src/main/scala/MatMul.scala b/src/main/scala/MatMul.scala index 1b6f69f..d2eaaba 100644 --- a/src/main/scala/MatMul.scala +++ b/src/main/scala/MatMul.scala @@ -55,6 +55,7 @@ class MatMul(val rowDimsA: Int, val colDimsA: Int) extends MultiIOModule { // io.outputValid := false.B + /** * LF */ diff --git a/src/test/scala/Example.scala b/src/test/scala/Example.scala index a5294f7..fd8a28f 100644 --- a/src/test/scala/Example.scala +++ b/src/test/scala/Example.scala @@ -210,3 +210,47 @@ class DPCsimulatorSpec extends FlatSpec with Matchers { } } + +class EvilPrintfSpec extends FlatSpec with Matchers { + + class CountTo3() extends Module { + val io = IO( + new Bundle { + val dataOut = Output(UInt(32.W)) + val validOutput = Output(Bool()) + } + ) + val count = RegInit(UInt(32.W), 0.U) + io.dataOut := count + + printf(p"according to printf output is: ${io.dataOut}\n") + + when(count != 3.U){ + count := count + 1.U + io.validOutput := false.B + io.dataOut := 0.U + }.otherwise{ + io.validOutput := true.B + io.dataOut := 1.U + } + + } + + + class CountTo3Test(c: CountTo3) extends PeekPokeTester(c) { + for(ii <- 0 until 5){ + println(s"\nIn cycle $ii the output of counter is: ${peek(c.io.dataOut)}") + step(1) + } + } + + behavior of "EvilPrintf" + + it should "tell a lie and hurt you" in { + wrapTester( + chisel3.iotesters.Driver(() => new CountTo3) { c => + new CountTo3Test(c) + } should be(true) + ) + } +} diff --git a/src/test/scala/MatMulTips.org b/src/test/scala/MatMulTips.org new file mode 100644 index 0000000..b1aeba9 --- /dev/null +++ b/src/test/scala/MatMulTips.org @@ -0,0 +1,53 @@ +The most important part of this task is keeping track of which row and column should be +accessed in each matrix. + +In short, there are three values you need to have control over: +Column select, Row select A and Row select B + +Further, it is useful to separate input phase and calculation phase. + +In the input phase Row select A should be the same as Row select B since +you're simply inputting values. +For a 3x2 matrix you want to first input the first vector (that is, row 0), +so for cycle 0, 1 and 2 rowSelect should be 0, whereas column select should be +the same as the (cycle % columns) + +After Inputting data all three values should be reset to 0. +This is a fairly typical task, so chisel.util has you covered with the Counter class +https://chisel.eecs.berkeley.edu/api/3.0.1/chisel3/util/Counter.html +https://chisel.eecs.berkeley.edu/api/3.0.1/chisel3/util/Counter$.html + +(The second link links to the Counter object rather than the class. Typically we put +constructors and static methods in the companion object of a class) + +In the Counter object there is a method called apply: +#+begin_src scala + def apply(cond: Bool, n: Int): (UInt, Bool) +#+end_src + +From the signature we see (well, I see because I happen to know that scala bools are called Boolean) +that the input is a chisel.data.Bool and a scala Int, and the output is a tuple of +chisel.data.UInt and chisel.data.Bool + +The return values for the call to Counter.apply is a wire containing the current value of the counter, +and a wire that is toggled whenever the clock rolls over. + +The arguments are a scala int, specifying how many ticks it takes for the clock to roll over, and a +wire whose signal toggles the clock on or off. + +In our matrix multiplier this is pretty handy: + +#+begin_src scala +val (colCounter, colCounterWrap) = Counter(true.B, colDimsA) +val (rowSelA, rowSelAWrap) = Counter(colCounterWrap, ???) +#+end_src + +Here we have defined two counters. The column counter always ticks, wrapping around when it reaches colDimsA. +When the column counter wraps, the colCounterWrap wire is toggled, and the rowSelect clock makes a single tick. + +If you can get row select A, B and col select to work in both phases you're very close to done, so these +should be your priority. +To test them, write your own test for MatMul that simply runs your MatMul unit and peeks at these values each +timestep until you're confident that they're correct. +As described in the debugging section, you should make a separate debug IO port with debug signals enabling you +to read out these values. Attempting to read them directly will throw an error.