Parcourir la source

Wrote a lot of stuff

master
peteraa il y a 6 ans
Parent
révision
2c7d3d58c1
6 fichiers modifiés avec 665 ajouts et 484 suppressions
  1. +278
    -283
      oppgavetekst.org
  2. +26
    -183
      src/test/scala/Example.scala
  3. +58
    -0
      src/test/scala/Examples/invalidDesigns.scala
  4. +41
    -18
      src/test/scala/Examples/myVector.scala
  5. +217
    -0
      src/test/scala/Examples/printing.scala
  6. +45
    -0
      src/test/scala/Examples/stateful.scala

+ 278
- 283
oppgavetekst.org Voir le fichier

@@ -8,9 +8,9 @@
** Prerequisites
+ *You should have some idea of how digital logic circuits work.*

Do you know what a NAND gate is?
Do you know how many wires you need to address 64kb of memory?
If so you should be able to pick it up :)
You should have a basic overview on digital circuits.
It is assumed that you know what a multiplexer is and how it works, and
how a register works.

+ *You must be able to run scala programs.*

@@ -169,7 +169,7 @@
*** Running a peek poke tester
The test runner class we have defined requires a MyIncrement module that can be simulated.
However, by writing ~~val inc3 = Module(new MyIncrement(3))~~ the return value is a *chisel graph*,
However, by writing ~val inc3 = Module(new MyIncrement(3))~ the return value is a *chisel graph*,
i.e a schematic for a circuit.
In order to interact with a circuit the schematic must be interpreted, resulting in a simulated
circuit which the peek poke tester can interact with.
@@ -185,8 +185,8 @@

Unfortunately this code might be a little hard to parse if you're new to scala.
Understanding it is not necessary, it is sufficient to simply swap
~~MyIncrement(3)~~ in ~~(() => MyIncrement(3))~~ with the module you want to test, and
~~TheTestRunner(c)~~ with the test runner you want to run.
~MyIncrement(3)~ in ~(() => MyIncrement(3))~ with the module you want to test, and
~TheTestRunner(c)~ with the test runner you want to run.

#+begin_src scala
chisel3.iotesters.Driver(() => new MyIncrement(3)) { c =>
@@ -227,12 +227,12 @@
By creating this test it is now possible to run it from sbt.
There are two ways to test. By simply writing "test" in the sbt console as so:
~~sbt:chisel-module-template> test~~ every single test will be run.
~sbt:chisel-module-template> test~ every single test will be run.
Since this creates a lot of noise it's more useful to run "testOnly":
~~sbt:chisel-module-template> testOnly Examples.MyIncrementTest~~ where "Examples"
~sbt:chisel-module-template> testOnly Examples.MyIncrementTest~ where "Examples"
is the name of the package and MyIncrementTest is the name of the test.
The tests for the exercise itself is located in Ex0, so for instance
~~sbt:chisel-module-template> testOnly Ex0.MatrixSpec~~ will run the matrix test.
~sbt:chisel-module-template> testOnly Ex0.MatrixSpec~ will run the matrix test.
Note: by running "test" once you can use tab completion in the sbt shell to find tests with testOnly.
using testOnly <TAB>
@@ -282,7 +282,7 @@
The RTL diagram now looks like this:
Note how the modules ~~first~~ and ~~second~~ are now drawn as black boxes.
Note how the modules ~first~ and ~second~ are now drawn as black boxes.
When drawing RTL diagrams we're not interested in the internals of submodules.

However, what if we want to instantiate an arbitrary amount of incrementors and chain them?
@@ -355,7 +355,7 @@
and in hardware.


*** Troubleshooting a vector design
*** Troubleshooting scala and chisel mixups
Here's a rather silly example highligthing the confusion that can happen when mixing
scala and chisel.
@@ -387,12 +387,11 @@
[error] ^
#+end_src
This error tells us that io.idx was of the wrong type, namely a chisel UInt.
The List is a scala construct, it only exists when your design is synthesized, so
attempting to index using a chisel type would be like HTML attempting to index the
generating scala code which is nonsensical.
Let's try again:
This error tells you that io.idx was of the wrong type, namely a ~chisel3.core.UInt~.
The List is a scala construct, it only exists when your design is synthesized, thus
attempting to index it with a chisel type does not make sense.

Let's try again using a chisel Vec which can be indexed by chisel values:
#+begin_src scala
class MyVector() extends Module {
val io = IO(
@@ -409,7 +408,7 @@
}
#+end_src
Egads, now we get this instead
Now you will get the following error instead:
#+begin_src scala
[error] /home/peteraa/datateknikk/TDT4255_EX0/src/main/scala/Tile.scala:30:16: inferred type arguments [Int] do not conform to macro method apply's type parameter bounds [T <: chisel3.Data]
[error] val values = Vec(1, 2, 3, 4)
@@ -421,17 +420,16 @@
...
#+end_src
What is going wrong here? In the error message we see that the type Int cannot be constrained to a
type T <: chisel3.Data, but what does that mean?
The error states that the type ~Int~ cannot be constrained to a ~type T <: chisel3.Data~ which needs a
little unpacking:
The <: symbol means subtype, meaning that the compiler expected the Vec to contain a chisel data type
The ~<:~ symbol means subtype, meaning that the compiler expected the Vec to contain a chisel data type
such as chisel3.Data.UInt or chisel3.Data.Boolean, and Int is not one of them!
A scala int represent 32 bits in memory, whereas a chisel UInt represents a bundle of wires that we
interpret as an unsigned integer, thus they are not interchangeable although they represent roughly
the same thing.
Let's fix this
#+begin_src scala
class MyVector() extends Module {
val io = IO(
@@ -449,10 +447,9 @@
io.out := values(io.idx)
}
#+end_src
This works!
So, it's impossible to access scala collections with chisel types, but can we do it the other way around?
Which compiles.

You might be suprised to see that it is possible to index a Vec with an integer as such:
#+begin_src scala
class MyVector() extends Module {
val io = IO(
@@ -467,13 +464,10 @@
io.out := values(3)
}
#+end_src
...turns out we can?
This is nonsensical, however thanks to behind the scenes magic the 3 is changed
to 3.U, much like [] can be a boolean in javascript.
In this case 3 gets automatically changed to 3.U.
It's not a great idea to abuse implicit conversions, so you should refrain from doing this too much.
To get acquainted with the (rather barebones) testing environment, let's test this.

#+begin_src scala
class MyVecSpec extends FlatSpec with Matchers {
behavior of "MyVec"
@@ -487,7 +481,6 @@
}
}
class MyVecTester(c: MyVector) extends PeekPokeTester(c) {
for(ii <- 0 until 4){
poke(c.io.idx, ii)
@@ -497,7 +490,7 @@
#+end_src
#+begin_src
sbt:chisel-module-template> testOnly Ex0.MyVecSpec
sbt:chisel-module-template> testOnly Examples.MyVecSpec
...
...
[info] Compiling 1 Scala source to /home/peteraa/datateknikk/TDT4255_EX0/target/scala-2.12/test-classes ...
@@ -521,146 +514,125 @@
Great!

** Compile time and synthesis time
In the HTML example, assume that we omitted the last </ul> tag. This would not
create valid HTML, however the code will happily compile. Likewise, we can easily
create invalid chisel:
#+begin_src scala
class Invalid() extends Module {
val io = IO(new Bundle{})
val myVec = Module(new MyVector)
}
#+end_src
This code will happily compile!
Turns out that when compiling, we're not actually generating any chisel at all!
Let's create a test that builds chisel code for us:
#+begin_src scala
class InvalidSpec extends FlatSpec with Matchers {
behavior of "Invalid"
it should "Probably fail in some sort of way" in {
chisel3.iotesters.Driver(() => new Invalid) { c =>
// chisel tester expects a test here, but we can use ???
// which is shorthand for throw new NotImplementedException.
//
// This is OK, because it will fail during building.
???
} should be(true)
}
}
#+end_src
This gives us the rather scary error:
#+begin_src scala
sbt:chisel-module-template> compile
...
[success] Total time: 3 s, completed Apr 25, 2019 3:15:15 PM
...
sbt:chisel-module-template> testOnly Ex0.InvalidSpec
...
firrtl.passes.CheckInitialization$RefNotInitializedException: @[Example.scala 25:21:@20.4] : [module Invalid] Reference myVec is not fully initialized.
: myVec.io.idx <= VOID
at firrtl.passes.CheckInitialization$.$anonfun$run$6(CheckInitialization.scala:83)
at firrtl.passes.CheckInitialization$.$anonfun$run$6$adapted(CheckInitialization.scala:78)
at scala.collection.TraversableLike$WithFilter.$anonfun$foreach$1(TraversableLike.scala:789)
at scala.collection.mutable.HashMap.$anonfun$foreach$1(HashMap.scala:138)
at scala.collection.mutable.HashTable.foreachEntry(HashTable.scala:236)
at scala.collection.mutable.HashTable.foreachEntry$(HashTable.scala:229)
at scala.collection.mutable.HashMap.foreachEntry(HashMap.scala:40)
at scala.collection.mutable.HashMap.foreach(HashMap.scala:138)
at scala.collection.TraversableLike$WithFilter.foreach(TraversableLike.scala:788)
at firrtl.passes.CheckInitialization$.checkInitM$1(CheckInitialization.scala:78)
#+end_src
While scary, the actual error is only this line:
#+begin_src scala
firrtl.passes.CheckInitialization$RefNotInitializedException: @[Example.scala 25:21:@20.4] : [module Invalid] Reference myVec is not fully initialized.
: myVec.io.idx <= VOID
#+end_src
Which tells us that myVec has unInitialized wires!
While our program is correct, it produces an incorrect design, in other words, the scala part
of the code is correct as it compiled, but the chisel part is incorrect because it does not synthesize.
Let's fix it:
#+begin_src scala
class Invalid() extends Module {
val io = IO(new Bundle{})
val myVec = Module(new MyVector)
myVec.io.idx := 0.U
}
#+end_src
Hooray, now we get ~scala.NotImplementedError: an implementation is missing~
as expected, along with an enormous stacktrace..
The observant reader may have observed that it is perfectly legal to put chisel types in scala
collection, how does that work?
A scala collection is just a collection of references, or pointers if you will.
If it happens to contain values of chisel types then these will exist in the design, however the
collection will not, so we cannot index based on the collection.
This can be seen in ~MyIncrementN~ where an array of incrementors is used.
The array is only used help the scala program wire the components together, and once this is
done the array is not used.
We could do the same with MyVector, but it's not pretty:
#+begin_src scala
class MyVector2() extends Module {
val io = IO(
new Bundle {
val idx = Input(UInt(32.W))
val out = Output(UInt(32.W))
}
)
val values = Array(0.U, 1.U, 2.U, 3.U)
io.out := values(0)
for(ii <- 0 until 3){
when(io.idx === ii.U){
io.out := values(ii)
}
}
}
#+end_src
Note that it is nescessary to specify a default for io.out even though it will never be
selected.
While it looks ugly, the generated hardware should, at least in theory, not take up any
more space or run any slower than the Vec based implementation, save for one difference
as we will see in the next section.
In order to get some insight into how a chisel Vec works, let's see how we can implement
myVector without Vec:
#+begin_src scala
class MyVectorAlt() extends Module {
val io = IO(
new Bundle {
val idx = Input(UInt(32.W))
val out = Output(UInt(32.W))
}
)
val values = Array(0.U, 1.U, 2.U, 3.U)
io.out := values(0)
for(ii <- 0 until 4){
when(io.idx(1, 0) === ii.U){
io.out := values(ii)
}
}
}
#+end_src
The for-loop creates 4 conditional blocks boiling down to
when 0: output the value in values(0)
when 1: output the value in values(1)
when 2: output the value in values(2)
when 3: output the value in values(3)
otherwise: output 0.U

The otherwise clause will never occur, chisel is unable to inferr this
(however the synthesizer will likely be able to)
In the conditional block the following syntax is used:
~io.idx(1, 0) === ii.U)~
which indicates that only the two low bits of idx will be used to index, which is
how chisel Vec does it.

*** Troubleshooting build time errors
In the HTML example, assume that the the last </ul> tag was ommited. This would not
be valid HTML, however the code will happily compile. Likewise, you can easily
create a valid scala program producing an invalid chisel graph:
** Bit Widths
What happens if we attempt to index the 6th element in our 4 element vector?
In MyVector we get 1, and in MyVector2 we get 0, so they're not exactly the same.
In MyVector the Vec has 4 elements, thus only two wires are necessary (00, 01, 10, 11),
thus the remaining 28 wires of io.idx are not used.
In MyVector2 on the other hand we have specified a default value for io.out, so for any
index higher than 3 the output will be 0.
#+begin_src scala
class Invalid() extends Module {
val io = IO(new Bundle{})
val myVec = Module(new MyVector)
}
#+end_src
What about the values in the Vec?
0.U can be represented by a single wire, whereas 3.U must be represented by at
least two wires.
In this case it is easy for chisel to see that they must both be of width 32 since they will
be driving the output signal which is specified as 32 bit wide.
This code will happily compile, however when you attempt to create a simulator from the
schematic chisel will throw an exception.
#+begin_src scala
class InvalidSpec extends FlatSpec with Matchers {
behavior of "Invalid"
it should "fail" in {
chisel3.iotesters.Driver(() => new Invalid) { c =>
In theory specifying widths should not be necessary other than at the very endpoints of your
design, however this would quickly end up being intractable, so we specify widths at module
endpoints.
// chisel tester expects a test here, but we can use ???
// which is shorthand for throw new NotImplementedException.
//
// This is OK, because it will fail during building.
???
} should be(true)
}
}
#+end_src
Running the test throws an error:
#+begin_src scala
sbt:chisel-module-template> compile
...
[success] Total time: 3 s, completed Apr 25, 2019 3:15:15 PM
...
sbt:chisel-module-template> testOnly Examples.InvalidSpec
...
firrtl.passes.CheckInitialization$RefNotInitializedException: @[Example.scala 25:21:@20.4] : [module Invalid] Reference myVec is not fully initialized.
: myVec.io.idx <= VOID
at firrtl.passes.CheckInitialization$.$anonfun$run$6(CheckInitialization.scala:83)
at firrtl.passes.CheckInitialization$.$anonfun$run$6$adapted(CheckInitialization.scala:78)
at scala.collection.TraversableLike$WithFilter.$anonfun$foreach$1(TraversableLike.scala:789)
at scala.collection.mutable.HashMap.$anonfun$foreach$1(HashMap.scala:138)
at scala.collection.mutable.HashTable.foreachEntry(HashTable.scala:236)
at scala.collection.mutable.HashTable.foreachEntry$(HashTable.scala:229)
at scala.collection.mutable.HashMap.foreachEntry(HashMap.scala:40)
at scala.collection.mutable.HashMap.foreach(HashMap.scala:138)
at scala.collection.TraversableLike$WithFilter.foreach(TraversableLike.scala:788)
at firrtl.passes.CheckInitialization$.checkInitM$1(CheckInitialization.scala:78)
#+end_src
While scary, the actual error is only this line:
#+begin_src scala
firrtl.passes.CheckInitialization$RefNotInitializedException: @[Example.scala 25:21:@20.4] : [module Invalid] Reference myVec is not fully initialized.
: myVec.io.idx <= VOID
#+end_src
Which tells you that myVec.io.idx needs a driver.
#+begin_src scala
// Now actually valid...
class Invalid() extends Module {
val io = IO(new Bundle{})
val myVec = Module(new MyVector)
myVec.io.idx := 0.U
}
#+end_src
After fixing the invalid circuit and running the test you will insted get a large error
stack trace where you will see that:
~- should fail *** FAILED ***~
Which in some respect indicates success.


** Stateful circuits
Until now every circuit we have consider has been a combinatory circuit.
Consider the following circuit:
#+begin_src scala
class SimpleDelay() extends Module {
val io = IO(
@@ -675,153 +647,93 @@
io.dataOut := delayReg
}
#+end_src

This circuit stores its input in delayReg and drives its output with delayRegs output.
Registers are driven by a clock signal in addition to the input value, and it is only
capable of updating its value at a clock pulse.

In some HDL languages it is necessary to include the clock signal in the modules IO, but
for chisel this happens implicitly.

When testing we use the ~step(n)~ feature of peek poke tester which runs the clock signal n times.
This circuit seems rather pointless, it simply assigns the input to the output.
However, unlike the previous circuits, the simpleDelay circuit stores its value
in a register, causing a one cycle delay between input and output.
Lets try it!
Test this by running ~testOnly Examples.DelaySpec~
#+begin_src scala
class DelaySpec extends FlatSpec with Matchers {
behavior of "SimpleDelay"
it should "Delay input by one timestep" in {
chisel3.iotesters.Driver(() => new SimpleDelay) { c =>
chisel3.iotesters.Driver(() => new SimpleDelay, verbose = true) { c =>
new DelayTester(c)
} should be(true)
}
}
class DelayTester(c: SimpleDelay) extends PeekPokeTester(c) {
for(ii <- 0 until 10){
val input = scala.util.Random.nextInt(10)
poke(c.io.dataIn, input)
step(1)
expect(c.io.dataOut, input)
}
}
#+end_src
We then run the test:
In order to make it extra clear the Driver has the optional "verbose" parameter set to true.
This yields the following:
#+begin_src
sbt:chisel-module-template> testOnly Ex0.DelaySpec
DelaySpec:
SimpleDelay
...
[info] [0.001] Elaborating design...
[info] [0.071] Done elaborating.
Total FIRRTL Compile Time: 144.7 ms
Total FIRRTL Compile Time: 9.4 ms
End of dependency graph
Circuit state created
[info] [0.001] SEED 1556196281084
[info] [0.002] EXPECT AT 0 io_dataOut got 0 expected 7 FAIL
[info] [0.002] EXPECT AT 0 io_dataOut got 0 expected 6 FAIL
[info] [0.003] EXPECT AT 0 io_dataOut got 0 expected 1 FAIL
[info] [0.003] EXPECT AT 0 io_dataOut got 0 expected 2 FAIL
[info] [0.003] EXPECT AT 0 io_dataOut got 0 expected 7 FAIL
[info] [0.003] EXPECT AT 0 io_dataOut got 0 expected 4 FAIL
[info] [0.003] EXPECT AT 0 io_dataOut got 0 expected 8 FAIL
[info] [0.003] EXPECT AT 0 io_dataOut got 0 expected 8 FAIL
[info] [0.003] EXPECT AT 0 io_dataOut got 0 expected 7 FAIL
#+end_src
Oops, the tester doesn't advance the clock befor testing output, totally didn't
make an error on purpose to highlight that...
#+begin_src scala
class DelayTester(c: SimpleDelay) extends PeekPokeTester(c) {
for(ii <- 0 until 10){
val input = scala.util.Random.nextInt(10)
poke(c.io.dataIn, input)
step(1)
expect(c.io.dataOut, input)
}
}
#+end_src
Much better..
You should now be able to implement myDelayN following the same principles as
MyIncrementN
#+begin_src scala
class myDelayN(delay: Int) extends Module {
val io = IO(
new Bundle {
val dataIn = Input(UInt(32.W))
val dataOut = Output(UInt(32.W))
}
)
???
}
[info] [0.001] SEED 1556898121698
[info] [0.002] POKE io_dataIn <- 7
[info] [0.002] STEP 0 -> 1
[info] [0.002] EXPECT AT 1 io_dataOut got 7 expected 7 PASS
[info] [0.002] POKE io_dataIn <- 8
[info] [0.002] STEP 1 -> 2
[info] [0.003] EXPECT AT 2 io_dataOut got 8 expected 8 PASS
[info] [0.003] POKE io_dataIn <- 2
...
[info] [0.005] STEP 9 -> 10
[info] [0.005] EXPECT AT 10 io_dataOut got 7 expected 7 PASS
test SimpleDelay Success: 10 tests passed in 15 cycles taking 0.010393 seconds
[info] [0.005] RAN 10 CYCLES PASSED
#+end_src
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{ ... }~
?
Following the output you can see how at step 0 the input is 7, then at step 1
the expected (and observed) output is 7.


** Debugging
A rather difficult aspect in HDLs, including chisel is debugging.
When debugging it is necessary to inspect how the state of the circuit evolves, which
leaves us with two options, peekPokeTester and printf, however both have flaws.

*** Printf
Printf statements will be executed once per clock cycle if the surrounding block is executed.
The way printf statements work is that they create a special abstract hardware element that
sends a message to your console whenever it is enabled (i.e. when it is either at the top
level of a module, or in a conditional block whose condition has been met).


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
The peek poke tester should always give a correct result, if not it's a bug, not a quirk.
Sadly peek poke testing is rather limited in that it cannot be used to access internal state.
Consider the following module:
#+begin_src scala
class Outer() extends Module {
class Inner() 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
val innerState = RegInit(0.U)
when(io.dataIn % 2.U === 0.U){
innerState := io.dataIn
}

io.dataOut := innerState
}
#+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 {
class Outer() extends Module {
val io = IO(
new Bundle {
val dataIn = Input(UInt(32.W))
@@ -829,20 +741,103 @@
}
)
val debug = IO(
new Bundle {
val innerState = Output(UInt(32.W))
}
)
val outerState = RegInit(0.U)
val inner = Module(new Inner)
inner.io.dataIn := io.dataIn
io.dataOut := inner.io.dataOut
outerState := io.dataIn
inner.io.dataIn := outerState
io.dataOut := inner.io.dataOut
}
#+end_src
It would be nice if we could use the peekPokeTester to inspect what goes on inside
Inner, however this information is no longer available once Outer is synthesize into a
runnable circuit.
To see this, run ~testOnly Example.PeekInternalSpec~

In the test an exception is thrown when either of the two peek statements underneath are
run:
#+begin_src scala
class OuterTester(c: Outer) extends PeekPokeTester(c) {
val inner = peek(c.inner.innerState)
val outer = peek(c.outerState)
}
#+end_src
The only way to deal with this hurdle is to expose the state we are interested in as signals.
An example of this can be seen in
~/Examples/printing.scala~
This approach leads to a lot of annoying clutter in your modules IO, so to separate business-logic
from debug signals it is useful to use a MultiIOModule where debug signals can be put in a separate
io bundle. This approach is used in the skeleton code for the exercises.

*** printf
~printf~ and ~println~ must not be mixed!
println behaves as expected in most languages, when executed it simply prints the argument.
In the tests so far it has only printed the value returned by peek.

a printf statement on the other hand does not immediately print anything to the console. Instead it creates
a special chisel element which only exists during simulation and prints to your console each cycle,
thus helping us peer into the internal state of a circuit!
Additionally, a printf statement in a conditional block will only execute if the condiditon is met,
allowing us to reduce noise.
#+begin_src scala
class PrintfExample() extends Module {
val io = IO(new Bundle{})
debug.innerState := inner.debug.frobnicatorState
val counter = RegInit(0.U(8.W))
counter := counter + 1.U
printf("Counter is %d\n", counter)
when(counter % 2.U === 0.U){
printf("Counter is even\n")
}
}

class PrintfTest(c: PrintfExample) extends PeekPokeTester(c) {
for(ii <- 0 until 5){
println(s"At cycle $ii:")
step(1)
}
}
#+end_src
When you run this test with ~testOnly Examples.PrintfExampleSpec~, did you get what you expected?
As it turns out printf can be rather misleading when using stateful circuits.
To see this in action, try running ~testOnly Examples.EvilPrintfSpec~ which yields the following
#+begin_src
In cycle 0 the output of counter is: 0
according to printf output is: 0
[info] [0.003]
In cycle 1 the output of counter is: 0
according to printf output is: 0
[info] [0.003]


In cycle 2 the output of counter is: 0
according to printf output is: 1
^^^^^^^^

[info] [0.004]
In cycle 3 the output of counter is: 1
according to printf output is: 1
[info] [0.004]
In cycle 4 the output of counter is: 1
according to printf output is: 1
#+end_src
When looking at the circuits design it is pretty obvious that the peek poke tester is giving the
correct result, whereas the printf statement is printing the updated state of the register which
should not be visible before next cycle.
In conclusion, do not use printf to debug timing issues, and if you do be extremely methodical.
(It is possible to use a different simulator, treadle, which from what I have seen gives correct
printf results, it can be used by supplying an extra argument in the peek poke constructor like so:
~chisel3.iotesters.Driver(() => new Outer, "treadle") { c =>~)

* Matrix matrix multiplication
For your first foray into chisel you will design a matrix matrix multiplication unit.
@@ -927,7 +922,7 @@
| 2, 0 |
#+end_src
Now all we need to do is calculate the dot products for the final matrix:
Now we need to do is calculate the dot products for the final matrix:

#+begin_src
if A*B = C then


+ 26
- 183
src/test/scala/Example.scala Voir le fichier

@@ -1,199 +1,42 @@


class Invalid() extends Module {
val io = IO(new Bundle{})

val myVec = Module(new MyVector)

// Uncomment line below to make the circuit valid
// myVec.io.idx := 0.U
}


/**
* This goes a little beyond the example in exercise.org.
* WrapTest is a simple wrapper that catches Unconnected wires
* and prints them with a less scary stacktrace.
* Additionally, we throw a RunTimeException instead of ??? for
* similar reasons
*
*/
class InvalidSpec extends FlatSpec with Matchers {
behavior of "Invalid"

it should "Fail with a RefNotInitializedException" in {
try {
wrapTester(
chisel3.iotesters.Driver(() => new Invalid) { c =>
// class DPCsimulatorSpec extends FlatSpec with Matchers {

// Just a placeholder so it compiles
throw new RuntimeException with scala.util.control.NoStackTrace
} should be(true)
)
}
catch {
case e: RuntimeException => println("all good!")
case e: Exception => throw e
}
}
}


class SimpleDelay() extends Module {
val io = IO(
new Bundle {
val dataIn = Input(UInt(32.W))
val dataOut = Output(UInt(32.W))
}
)
val delayReg = RegInit(UInt(32.W), 0.U)

delayReg := io.dataIn
io.dataOut := delayReg
}
// 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")

class DelaySpec extends FlatSpec with Matchers {
behavior of "SimpleDelay"
// (ts + 1, nextDPC)
// }.take(20)

it should "Delay input by one timestep" in {
wrapTester(
chisel3.iotesters.Driver(() => new SimpleDelay) { c =>
new DelayTester(c)
} should be(true)
)
}
}

// behavior of "Dot product simulator"

// class DelayTester(c: SimpleDelay) extends PeekPokeTester(c) {
// for(ii <- 0 until 10){
// val input = scala.util.Random.nextInt(10)
// poke(c.io.dataIn, input)
// expect(c.io.dataOut, input)
// it should "Be shoehorned into a test" in {
// dpcStream.last
// }
// }

class DelayTester(c: SimpleDelay) extends PeekPokeTester(c) {
for(ii <- 0 until 10){
val input = scala.util.Random.nextInt(10)
poke(c.io.dataIn, input)
step(1)
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
}
}


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



class PrintfExampleSpec extends FlatSpec with Matchers {

class PrintfExample() extends Module {
val io = IO(new Bundle{})
val counter = RegInit(0.U(8.W))
counter := counter + 1.U
printf("Counter is %d\n", counter)
when(counter % 2.U === 0.U){
printf("Counter is even\n")
}
}

class PrintfTest(c: PrintfExample) extends PeekPokeTester(c) {
for(ii <- 0 until 5){
println(s"At cycle 0:")
step(1)
}
}


behavior of "Printf Example"

it should "print" in {
wrapTester(
chisel3.iotesters.Driver(() => new PrintfExample) { c =>
new PrintfTest(c)
} should be(true)
)
}
}

+ 58
- 0
src/test/scala/Examples/invalidDesigns.scala Voir le fichier

@@ -0,0 +1,58 @@
/**
* This code supplements instructions.org
*/
package Examples
import Ex0._


import chisel3._
import chisel3.iotesters.PeekPokeTester
import org.scalatest.{Matchers, FlatSpec}
import TestUtils._

class Invalid() extends Module {
val io = IO(new Bundle{})

val myVec = Module(new MyVector)

// Uncomment line below to make the circuit valid
// myVec.io.idx := 0.U
}

class InvalidSpec extends FlatSpec with Matchers {
behavior of "Invalid"

it should "fail" in {
chisel3.iotesters.Driver(() => new Invalid) { c =>

// chisel tester expects a test here, but we can use ???
// which is shorthand for throw new NotImplementedException.
//
// This is OK, because it will fail during building.
???
} should be(true)
}
}


/**
* Chisel errors are just regular scala Exceptions that can be caught.
* The test underneath shows this in practice, and is used in the tests for
* the exercises via the wrapTester method.
*/
// class InvalidSpec extends FlatSpec with Matchers {
// behavior of "Invalid"

// it should "Fail with a RefNotInitializedException" in {
// val passes = try {
// chisel3.iotesters.Driver(() => new Invalid) { c =>
// ???
// }
// }
// catch {
// case e: firrtl.passes.CheckInitialization.RefNotInitializedException => true
// case _: Throwable => false
// }
// passes should be(true)
// }
// }

+ 41
- 18
src/test/scala/Examples/myVector.scala Voir le fichier

@@ -10,6 +10,7 @@ import chisel3.iotesters.PeekPokeTester
import org.scalatest.{Matchers, FlatSpec}
import TestUtils._

// Does not compile
// class MyVector() extends Module {
// val io = IO(
// new Bundle {
@@ -23,6 +24,8 @@ import TestUtils._
// io.out := values(io.idx)
// }


// Does not compile
// class MyVector() extends Module {
// val io = IO(
// new Bundle {
@@ -31,12 +34,12 @@ import TestUtils._
// }
// )

// // val values: List[Int] = List(1, 2, 3, 4)
// val values = Vec(1, 2, 3, 4)

// io.out := values(io.idx)
// }


class MyVector() extends Module {
val io = IO(
new Bundle {
@@ -51,42 +54,62 @@ class MyVector() extends Module {
}


class MyVector2() extends Module {
class MyVecSpec extends FlatSpec with Matchers {

class MyVecTester(c: MyVector) extends PeekPokeTester(c) {
for(ii <- 0 until 4){
poke(c.io.idx, ii)
expect(c.io.out, ii)
}
}

behavior of "MyVec"

it should "Output whatever idx points to" in {
chisel3.iotesters.Driver(() => new MyVector) { c =>
new MyVecTester(c)
} should be(true)
}
}





class MyVectorAlt() extends Module {
val io = IO(
new Bundle {
val idx = Input(UInt(2.W))
val idx = Input(UInt(32.W))
val out = Output(UInt(32.W))
}
)

val values = Array(0.U, 1.U, 2.U, 3.U)

val myWire = Wire(UInt(4.W))
io.out := values(0)
for(ii <- 0 until 4){
when(io.idx === ii.U){
when(io.idx(1, 0) === ii.U){
io.out := values(ii)
}
}
}


class MyVecSpec extends FlatSpec with Matchers {
behavior of "MyVec"
class MyVecAltSpec extends FlatSpec with Matchers {

it should "Output whatever idx points to" in {
wrapTester(
chisel3.iotesters.Driver(() => new MyVector2) { c =>
new MyVecTester(c)
} should be(true)
)
class MyVecTester(c: MyVectorAlt) extends PeekPokeTester(c) {
for(ii <- 0 until 4){
poke(c.io.idx, ii)
expect(c.io.out, ii)
}
}
}

behavior of "MyVec"

class MyVecTester(c: MyVector2) extends PeekPokeTester(c) {
for(ii <- 0 until 4){
poke(c.io.idx, ii)
expect(c.io.out, ii)
it should "Output whatever idx points to" in {
chisel3.iotesters.Driver(() => new MyVectorAlt) { c =>
new MyVecTester(c)
} should be(true)
}

}

+ 217
- 0
src/test/scala/Examples/printing.scala Voir le fichier

@@ -0,0 +1,217 @@
/**
* This code supplements instructions.org
*/
package Examples
import Ex0._


import chisel3._
import chisel3.iotesters.PeekPokeTester
import org.scalatest.{Matchers, FlatSpec}
import TestUtils._


class PrintfExampleSpec extends FlatSpec with Matchers {

class PrintfExample() extends Module {
val io = IO(new Bundle{})
val counter = RegInit(0.U(8.W))
counter := counter + 1.U
printf("Counter is %d\n", counter)
when(counter % 2.U === 0.U){
printf("Counter is even\n")
}
}

class PrintfTest(c: PrintfExample) extends PeekPokeTester(c) {
for(ii <- 0 until 5){
println(s"At cycle $ii:")
step(1)
}
}


behavior of "Printf Example"

it should "print" in {
wrapTester(
chisel3.iotesters.Driver(() => new PrintfExample) { c =>
new PrintfTest(c)
} should be(true)
)
}
}


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, "verilator") { c =>
new CountTo3Test(c)
} should be(true)
)
}
}


class PeekInternalSpec extends FlatSpec with Matchers {

class Inner() extends Module {
val io = IO(
new Bundle {
val dataIn = Input(UInt(32.W))
val dataOut = Output(UInt(32.W))
}
)
val innerState = RegInit(0.U)
when(io.dataIn % 2.U === 0.U){
innerState := io.dataIn
}

io.dataOut := innerState
}


class Outer() extends Module {
val io = IO(
new Bundle {
val dataIn = Input(UInt(32.W))
val dataOut = Output(UInt(32.W))
}
)
val outerState = RegInit(0.U)
val inner = Module(new Inner)
outerState := io.dataIn
inner.io.dataIn := outerState
io.dataOut := inner.io.dataOut
}

class OuterTester(c: Outer) extends PeekPokeTester(c) {
val inner = peek(c.inner.innerState)
val outer = peek(c.outerState)
}

behavior of "peek poke internal"

it should "Throw an exception" in {
val success = try {
chisel3.iotesters.Driver(() => new Outer) { c =>
new OuterTester(c)
} should be(true)
}
catch {
case e: java.util.NoSuchElementException => true
case e: Throwable => throw e
}
}
}


/**
* Inner state has been exposed manually
*
* This creates a lot of extra signals in the IO module, and it's a hassle to do the wiring.
*/
class PeekInternalExposedSpec extends FlatSpec with Matchers {

class Inner() extends Module {
val io = IO(
new Bundle {
val dataIn = Input(UInt(32.W))
val dataOut = Output(UInt(32.W))
val stateDebug = Output(UInt(32.W))
}
)
val innerState = RegInit(0.U)
innerState := io.dataIn

when(innerState % 2.U === 0.U){
io.dataOut := io.dataIn
}.otherwise{
io.dataOut := innerState
}

io.stateDebug := innerState
}


class Outer() extends Module {
val io = IO(
new Bundle {
val dataIn = Input(UInt(32.W))
val dataOut = Output(UInt(32.W))

val innerStateDebug = Output(UInt(32.W))
val outerStateDebug = Output(UInt(32.W))
}
)
val outerState = RegInit(0.U)
val inner = Module(new Inner)
outerState := io.dataIn
inner.io.dataIn := outerState
io.dataOut := inner.io.dataOut

io.innerStateDebug := inner.io.stateDebug
io.outerStateDebug := outerState
}

class OuterTester(c: Outer) extends PeekPokeTester(c) {
for(ii <- 0 until 10){
poke(c.io.dataIn, ii)
val inner = peek(c.io.innerStateDebug)
val outer = peek(c.io.outerStateDebug)
println(s"$inner")
println(s"$outer")
println()
step(1)
}
}

behavior of "peek poke internal"

it should "peek exposed signals" in {
chisel3.iotesters.Driver(() => new Outer) { c =>
new OuterTester(c)
} should be(true)
}
}

+ 45
- 0
src/test/scala/Examples/stateful.scala Voir le fichier

@@ -0,0 +1,45 @@
/**
* This code supplements instructions.org
*/
package Examples
import Ex0._


import chisel3._
import chisel3.iotesters.PeekPokeTester
import org.scalatest.{Matchers, FlatSpec}
import TestUtils._

class SimpleDelay() extends Module {
val io = IO(
new Bundle {
val dataIn = Input(UInt(32.W))
val dataOut = Output(UInt(32.W))
}
)
val delayReg = RegInit(UInt(32.W), 0.U)

delayReg := io.dataIn
io.dataOut := delayReg
}


class DelaySpec extends FlatSpec with Matchers {
behavior of "SimpleDelay"

it should "Delay input by one timestep" in {
chisel3.iotesters.Driver(() => new SimpleDelay) { c =>
new DelayTester(c)
} should be(true)
}
}


class DelayTester(c: SimpleDelay) extends PeekPokeTester(c) {
for(ii <- 0 until 10){
val input = scala.util.Random.nextInt(10)
poke(c.io.dataIn, input)
step(1)
expect(c.io.dataOut, input)
}
}

Chargement…
Annuler
Enregistrer