peteraa 6 роки тому
джерело
коміт
81a927f516
4 змінених файлів з 212 додано та 8 видалено
  1. +114
    -8
      oppgavetekst.org
  2. +1
    -0
      src/main/scala/MatMul.scala
  3. +44
    -0
      src/test/scala/Example.scala
  4. +53
    -0
      src/test/scala/MatMulTips.org

+ 114
- 8
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!!

+ 1
- 0
src/main/scala/MatMul.scala Переглянути файл

@@ -55,6 +55,7 @@ class MatMul(val rowDimsA: Int, val colDimsA: Int) extends MultiIOModule {
// io.outputValid := false.B



/**
* LF
*/


+ 44
- 0
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)
)
}
}

+ 53
- 0
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.

Завантаження…
Відмінити
Зберегти