Pārlūkot izejas kodu

Fix and add stuff

master
peteraa pirms 6 gadiem
vecāks
revīzija
8f319428ce
21 mainītis faili ar 271 papildinājumiem un 81 dzēšanām
  1. Binārs
      Images/DotProd.png
  2. Binārs
      Images/MatMul.png
  3. Binārs
      Images/Source/MatMul.png
  4. +51
    -0
      Images/Source/waveform.tex
  5. +11
    -0
      TODOs.org
  6. +2
    -2
      circuitRendering.org
  7. +79
    -5
      exercise.org
  8. +9
    -0
      introduction.org
  9. +0
    -0
      oppgavetekst.org
  10. +2
    -0
      src/main/scala/DotProd.scala
  11. +0
    -0
      src/main/scala/fileUtils.scala
  12. +10
    -0
      src/main/scala/main.scala
  13. Binārs
      src/test/resources/svgs/SumOrSquare.png
  14. +14
    -5
      src/test/scala/DotProdSpec.scala
  15. +0
    -0
      src/test/scala/Example.scala
  16. +42
    -0
      src/test/scala/Examples/softwareDotProd.scala
  17. +4
    -2
      src/test/scala/MatMulSpec.scala
  18. +0
    -58
      src/test/scala/MatMulTips.org
  19. +19
    -7
      src/test/scala/MatrixSpec.scala
  20. +27
    -1
      src/test/scala/TestUtils.scala
  21. +1
    -1
      src/test/scala/VectorSpec.scala

Binārs
Images/DotProd.png Parādīt failu

Pirms Pēc
Platums: 1133  |  Augstums: 221  |  Izmērs: 30KB

Binārs
Images/MatMul.png Parādīt failu

Pirms Pēc
Platums: 863  |  Augstums: 1232  |  Izmērs: 33KB

Binārs
Images/Source/MatMul.png Parādīt failu

Pirms Pēc
Platums: 863  |  Augstums: 1232  |  Izmērs: 33KB

+ 51
- 0
Images/Source/waveform.tex Parādīt failu

@@ -0,0 +1,51 @@
\documentclass{article}
\usepackage[utf8]{inputenc}
\usepackage{tikz-timing}
\usetikztiminglibrary[rising arrows]{clockarrows}
\usepackage{xparse} % NewDocumentCommand, IfValueTF, IFBooleanTF

% Reference a bus.
%
% Usage:
%
% \busref[3::0]{C/BE} -> C/BE[3::0]
% \busref*{AD} -> AD#
% \busref*[3::0]{C/BE} -> C/BE[3::0]#
%
\NewDocumentCommand{\busref}{som}{\texttt{%
#3%
\IfValueTF{#2}{[#2]}{}%
\IfBooleanTF{#1}{\#}{}%
}}

\title{Waveform drawing}
\author{peteraa }
\date{June 2019}

\begin{document}

\begin{tikztimingtable}[%
timing/dslope=0.1,
timing/.style={x=5ex,y=2ex},
x=5ex,
timing/rowdist=3ex,
timing/name/.style={font=\sffamily\scriptsize}
]
\busref{CLK} & 32{c} \\
\busref{A} & 1D{5} 1D{0} 1D{4} 1D{8} 1D{1} 1D{6} 1D{6} 1D{2} 1D{9} 1D{0} 1D{8} 1D{3} 1D{9} 1D{3} 2U \\
\busref{B} & 1D{8} 1D{3} 1D{3} 1D{2} 1D{7} 1D{6} 1D{7} 1D{8} 1D{5} 1D{2} 1D{7} 1D{1} 1D{8} 1D{2} 2U \\
\busref{VALID} & 6U 1H 6U 1H 2U\\
\busref{Output} & 1D{40} 1D{40} 1D{52} 1D{68} 1D{75} 1D{111} 1D{153} 1D{16} 1D{61} 1D{61} 1D{117} 1D{120} 1D{192} 1D{198} 2U\\
\extracode
\begin{pgfonlayer}{background}
\begin{scope}[semitransparent ,semithick]
\vertlines[darkgray,dotted]{0.5,1.5 ,...,8.0}
\end{scope}
\end{pgfonlayer}
\end{tikztimingtable}

\maketitle

\section{Introduction}

\end{document}

+ 11
- 0
TODOs.org Parādīt failu

@@ -49,3 +49,14 @@
bi-directional busses. Otherwise, the value is simply outputted and
the module receiving the output simply ignores it if it didn’t ask
for anything.
* Jahre comments
** DONE Block diagrams
** DONE Specify timing constraints
*** DONE For dot prod
*** DONE -ish For mat mul
** DONE Make dot prod sim easier to find
It was gone. Oops
** DONE Direct link to chisel docs
** DONE Modularize MatMul
** DONE Unrandomize tests

+ 2
- 2
circuitRendering.org Parādīt failu

@@ -6,7 +6,7 @@
drawing programs.
As an example we will use a very simple circuit:
[[./Images/inkscape.jpg]]
[[./Images/inkscape.png]]
This circuit has a register, and we want to see how its state evolves, thus we add a label.
The name of the register is "Reg_A", which will be replaced by the actual value as the circuit
@@ -82,7 +82,7 @@

** Draw the circuit in inkscape
You can add as much detail as you want here, the only thing the parser looks for is
text fields that are postfixed with "_field"
text fields that are postfixed with "_field".
The fields I would be interested in are the row and column counters, and the dot product
accumulator state.
Save the svg as


+ 79
- 5
exercise.org Parādīt failu

@@ -40,19 +40,59 @@
case class DotProdCalculator(vectorLen: Int, timeStep: Int, accumulator: Int){
def update(inputA: Int, inputB: Int): (Int, Boolean, DotProdCalculator) = {
val product = inputA * inputB
if(((timeStep + 1) % vectorLen) == 0){
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))
}
}
}
#+end_src

To see it in action run ~testOnly Ex0.DPCsimulatorSpec~ in your sbt console.
To see it in action run ~testOnly Examples.SoftwareDotProdSpec~ in your sbt console.
As with the previous tasks, the dot product calculator must pass the tests with
~testOnly Ex0.DotProdSpec~
*** Timing
As shown in the timing diagram below, the dot product calculator should deliver the result as
soon as possible.
This means you will have to drive the output with the sum of the accumulator and the product of
the two inputs.
If you choose to drive the output only by the value of the accumulator your circuit will
lag behind by one cycle, which while good for pipelining purposes is not good for passing the test
purposes.
#+CAPTION: The expected output of the dot product calculator
[[./Images/counter.png]]


*** Chisel counters and a short detour on scala documentation
Doing an action for a set amount of timesteps is a very common task in hardware design, so this
functionality is included in chisel via the Counter class.
In order to understand how this counter works you can google "chisel counter" and get the following
https://chisel.eecs.berkeley.edu/api/3.0.1/chisel3/util/Counter.html as first result.
However, this is for the counter Class, what you actually want is the counter Object:
https://chisel.eecs.berkeley.edu/api/3.0.1/chisel3/util/Counter$.html
This can be very confusing when new to scala, but it is simply convention:
When a class and an object share name this is just a convenience for keeping static methods, such
as constructors, separated from the non-static methods.

In the Counter object (the second link) there is an apply method:
#+begin_src scala
def apply(cond: Bool, n: Int): (UInt, Bool)
#+end_src
The type signature tells you that the input is a regular scala integer, and a chisel boolean
(scala booleans are of type ~Boolean~, rather than ~Bool~) and the output is a UInt and a chisel
boolean.
This means that upon instantiating a Counter with its apply method you only get the outputs from
the counter, not the object itself.
The result is a convenient method of making a counter, simply supply how many ticks it takes for the
counter to roll over, as well as an input signal for enabling the clock, and receive a tuple with the
signal for the counters value, as well as a boolean signal that toggles whenever the clock rolls over.

A special property of apply methods are that they can be called directly on the object.
~Counter.apply(cCond, 10)~ does the same as ~Counter(cCond, 10)~.
To call the class constructor, use the ~new~ keyword.


** Task 4 - Matrix Matrix multiplication
@@ -106,9 +146,43 @@
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
*** Structuring your circuit
It is very easy to get bogged down with details in this exercise, so it's useful to take
a few moments to plan ahead.
A natural way to break down the task is to split it into two phases: setup and execution.
For setup you simply want to shuffle data from the input signals to your two matrix modules.
The next task is to actually perform the calculation.
This is a little more complex, seeing as the read patterns are different from matrix A and B.

To make this simpler a good idea is to introduce a control module.
This module should keep track of which state the multiplier is in, setup or execution, and
provide the appropriate row and column select signals.
You may also choose to split the control module into an init controller and an execution
controller if you see fit.

A suggested design is shown underneath:
[[./Images/MatMul.png]]
*** Timing
The timing for your matrix multiplier is straight forward. For a 3x4 matrix it takes
12 cycles to input data (cycles 0 to 11), and execution should proceed on cycle 12.
While you can technically start execution sooner than this the tests expect you to
not start executing before all data is loaded.
As long as you start executing just as data has been loaded your dot prod design will
take care of the rest.
*** Testing
In order to make testing easier, consider testing your row and column select signals
first.
The actual values stored in the matrixes are just noise, the important part is that
you select the correct rows and columns at the correct times for the correct matrixes,
and if you do this the rest is comparatively easy.

** 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.


+ 9
- 0
introduction.org Parādīt failu

@@ -852,9 +852,18 @@
Just don't bank your money on the correctness, it might fail in rare circumstances making debugging
a nightmare)

** Visualizing generated circuits
While limited, it is possible to visualize your generated circuit using [[https://github.com/freechipsproject/diagrammer][diagrammer]].
The necessary code to generate .fir file is in the main.scala file, just comment it out to generate
these.
I encourage you to give it a fair shake to see if you find it useful or not.

** Visualizing circuit state (Optional)
In order to make debugging easier it is helpful to render the state of the circuit to see where
errors happen.
A prototype for this is included in this project, read more about it here
[[./circuitRendering.org][Here]]

** Resources
Chisel cheat sheet
https://chisel.eecs.berkeley.edu/doc/chisel-cheatsheet3.pdf

+ 0
- 0
oppgavetekst.org Parādīt failu


+ 2
- 0
src/main/scala/DotProd.scala Parādīt failu

@@ -21,6 +21,8 @@ class DotProd(val elements: Int) extends Module {
*/
val counter = Counter(elements)
val accumulator = RegInit(UInt(32.W), 0.U)

// Please don't manually implement product!
val product = io.dataInA * io.dataInB

// placeholder


+ 0
- 0
src/main/scala/fileUtils.scala Parādīt failu


+ 10
- 0
src/main/scala/main.scala Parādīt failu

@@ -1,12 +1,22 @@
package Ex0
import chisel3._
import java.io.File

object main {
def main(args: Array[String]): Unit = {
val s = """
| Attempting to "run" a chisel program is rather meaningless.
| Instead, try running the tests, for instance with "test" or "testOnly Examples.MyIncrementTest
|
| If you want to create chisel graphs, simply remove this message and comment in the code underneath
| to generate the modules you're interested in.
""".stripMargin
println(s)
}

// Uncomment to dump .fir file
// val f = new File("MatMul.fir")
// chisel3.Driver.dumpFirrtl(chisel3.Driver.elaborate(() => new MatMul(5, 4)), Option(f))

}


Binārs
src/test/resources/svgs/SumOrSquare.png Parādīt failu

Pirms Pēc
Platums: 2181  |  Augstums: 2208  |  Izmērs: 78KB

+ 14
- 5
src/test/scala/DotProdSpec.scala Parādīt failu

@@ -8,7 +8,8 @@ import TestUtils._
class DotProdSpec extends FlatSpec with Matchers {
import DotProdTests._

val elements = scala.util.Random.nextInt(5) + 2
val rand = new scala.util.Random(100)
val elements = 7

behavior of "DotProd"

@@ -41,6 +42,8 @@ class DotProdSpec extends FlatSpec with Matchers {

object DotProdTests {

val rand = new scala.util.Random(100)

class SignalsWhenDone(c: DotProd) extends PeekPokeTester(c) {

for(ii <- 0 until c.elements - 1){
@@ -61,8 +64,11 @@ object DotProdTests {

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 inputsA = List.fill(c.elements)(rand.nextInt(10))
val inputsB = List.fill(c.elements)(rand.nextInt(10))
println("runnign dot prod calc with inputs:")
println(inputsA.mkString("[", "] [", "]"))
println(inputsB.mkString("[", "] [", "]"))
val expectedOutput = (for ((a, b) <- inputsA zip inputsB) yield a * b) sum

for(ii <- 0 until c.elements){
@@ -77,8 +83,11 @@ object DotProdTests {

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 inputsA = List.fill(c.elements)(rand.nextInt(10))
val inputsB = List.fill(c.elements)(rand.nextInt(10))
println("runnign dot prod calc with inputs:")
println(inputsA.mkString("[", "] [", "]"))
println(inputsB.mkString("[", "] [", "]"))
val expectedOutput = (for ((a, b) <- inputsA zip inputsB) yield a * b) sum

for(ii <- 0 until c.elements){


+ 0
- 0
src/test/scala/Example.scala Parādīt failu


+ 42
- 0
src/test/scala/Examples/softwareDotProd.scala Parādīt failu

@@ -0,0 +1,42 @@
/**
* This code supplements instructions.org
*/
package Examples
import Ex0._
import org.scalatest.{Matchers, FlatSpec}

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))
}
}

class SoftwareDotProdSpec extends FlatSpec with Matchers {
import DotProdTests._

val elements = scala.util.Random.nextInt(5) + 2

behavior of "DotProdSim"

it should "Simulate dot product calculation" in {

println("Running a simulated dot product calculator with input vector size = 5")
println("Note how output is only valid on cycles divisible by 5, and how the accumulator flushes\n\n")
((0 to 15).map(_ => util.Random.nextInt(8)) zip
(0 to 15).map(_ => util.Random.nextInt(8)))
.foldLeft(DotProdCalculator(5)){ case(dotProd, (inputA, inputB)) =>
val (output, valid, nextDotProd) = dotProd.update(inputA, inputB)
val inputString = s"At timestep ${dotProd.timeStep} inputs A: " + Console.YELLOW + s"$inputA " + Console.RESET + "B: " + Console.YELLOW + s"$inputB" + Console.RESET
val outputString = s"the output was: output: " + Console.YELLOW + s"$output" + Console.RESET + ", valid: " + (if(valid) Console.GREEN else Console.RED) + s"$valid\n" + Console.RESET
println(inputString)
println(outputString)
nextDotProd
}

true
}
}

+ 4
- 2
src/test/scala/MatMulSpec.scala Parādīt failu

@@ -8,8 +8,8 @@ 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
val rowDims = 3
val colDims = 7


behavior of "MatMul"
@@ -25,6 +25,8 @@ class MatMulSpec extends FlatSpec with Matchers {

object MatMulTests {

val rand = new scala.util.Random(100)

class TestExample(c: MatMul) extends PeekPokeTester(c) {
val mA = genMatrix(c.rowDimsA, c.colDimsA)
val mB = genMatrix(c.rowDimsA, c.colDimsA)


+ 0
- 58
src/test/scala/MatMulTips.org Parādīt failu

@@ -1,58 +0,0 @@
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.
Object methods named apply can be called directly from the object, which means
~MyObject.apply(123)~ is the same as ~MyObject(123)~
Very convenient once you're used to it, but a little odd first time you see it.

In the Counter object there is an apply method:
#+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.

The best tip of course is to actually show up during lab hours.

+ 19
- 7
src/test/scala/MatrixSpec.scala Parādīt failu

@@ -12,12 +12,22 @@ class MatrixSpec extends FlatSpec with Matchers {

behavior of "Matrix"

val rowDims = scala.util.Random.nextInt(5) + 3
val colDims = scala.util.Random.nextInt(5) + 3
val rand = new scala.util.Random(100)
val rowDims = 5
val colDims = 3

it should "Update its contents" in {
it should "Update its contents with a square shape" in {
wrapTester(
chisel3.iotesters.Driver(() => new Matrix(10,10)) { c =>
chisel3.iotesters.Driver(() => new Matrix(rowDims,rowDims)) { c =>
new UpdatesData(c)
} should be(true)
)
}


it should "Update its contents with a rectangular shape" in {
wrapTester(
chisel3.iotesters.Driver(() => new Matrix(rowDims,colDims)) { c =>
new UpdatesData(c)
} should be(true)
)
@@ -26,7 +36,7 @@ class MatrixSpec extends FlatSpec with Matchers {

it should "Retain its contents when writeEnable is low" in {
wrapTester(
chisel3.iotesters.Driver(() => new Matrix(10,10)) { c =>
chisel3.iotesters.Driver(() => new Matrix(rowDims, colDims)) { c =>
new UpdatesData(c)
} should be(true)
)
@@ -35,10 +45,12 @@ class MatrixSpec extends FlatSpec with Matchers {

object MatrixTests {

val rand = new scala.util.Random(100)

class UpdatesData(c: Matrix) extends PeekPokeTester(c) {

val inputs = List.fill(c.colsDim){
List.fill(c.rowsDim)(scala.util.Random.nextInt(20) + 1)
List.fill(c.rowsDim)(rand.nextInt(20) + 1)
}

poke(c.io.writeEnable, true)
@@ -65,7 +77,7 @@ object MatrixTests {
class RetainsData(c: Matrix) extends PeekPokeTester(c) {

val inputs = List.fill(c.colsDim){
List.fill(c.rowsDim)(scala.util.Random.nextInt(20) + 1)
List.fill(c.rowsDim)(rand.nextInt(20) + 1)
}

poke(c.io.writeEnable, true)


+ 27
- 1
src/test/scala/TestUtils.scala Parādīt failu

@@ -7,8 +7,10 @@ import org.scalatest.{Matchers, FlatSpec}

object TestUtils {

val rand = new scala.util.Random(100)

def genMatrix(rows: Int, cols: Int) = List.fill(rows)(
List.fill(cols)(scala.util.Random.nextInt(5))
List.fill(cols)(rand.nextInt(5))
)

def printVector(v: List[Int]): String =
@@ -40,6 +42,30 @@ object TestUtils {
println("##########################################################")
println("##########################################################")
}
case e: chisel3.core.Binding.ExpectedHardwareException => {
println("##########################################################")
println("##########################################################")
println("##########################################################")
println("Your design is using raw chisel types!")
println("error:\n")
println(e.getMessage)
println("")
println("")
println("This typically occurs when you forget to wrap a module")
println("e.g")
println("""
class MyBundle extends Bundle {
val signal = UInt(32.W)
}
val mySignal = new MyBundle
^^^^ Wrong!
should be
val mySignal = Wire(new MyBundle)
""")
println("##########################################################")
println("##########################################################")
println("##########################################################")
}
case e: Exception => throw e
}
}


+ 1
- 1
src/test/scala/VectorSpec.scala Parādīt failu

@@ -11,7 +11,7 @@ import scala.collection.immutable.{ Vector => _ }
class VectorSpec extends FlatSpec with Matchers {
import VectorTests._

val elements = scala.util.Random.nextInt(5) + 2
val elements = 7

behavior of "Vector"



Notiek ielāde…
Atcelt
Saglabāt