<jupyter_start><jupyter_text>Module 2.1: Your First Chisel Module**Prev: [Introduction to Scala](1_intro_to_scala.ipynb)****Next: [Combinational Logic](2.2_comb_logic.ipynb)** MotivationNow that you are familiar with Scala, let's start carving out some hardware! Chisel stands for **C**onstructing **H**ardware **I**n a **S**cala **E**mbedded **L**anguage. That means it is a DSL in Scala, allowing you to take advantage of both Scala and Chisel programming within the same code. It is important to understand which code is "Scala" and which code is "Chisel", but we will discuss that more later. For now, think of Chisel and the code in Module 2 as a better way to write Verilog. This module throws an entire Chisel `Module` and tester at you. Just get the gist of it for now. You'll see plenty more examples later.  SetupThe following cell downloads the dependencies needed for Chisel. You will see it in all future notebooks. **Run this cell now**.<jupyter_code>val path = System.getProperty("user.dir") + "/source/load-ivy.sc"
interp.load.module(ammonite.ops.Path(java.nio.file.FileSystems.getDefault().getPath(path)))<jupyter_output><empty_output><jupyter_text>As mentioned in the last module, these statements are needed to import Chisel. **Run this cell now** before running any future code blocks.<jupyter_code>import chisel3._
import chisel3.util._
import chisel3.iotesters.{ChiselFlatSpec, Driver, PeekPokeTester}<jupyter_output><empty_output><jupyter_text>--- Your First ModuleThis section will present your first hardware module, a test case, and how to run it. It will contain many things that you will not understand, and that is OK. We want you to take away the broad strokes, so you can continually return to this complete and working example to reinforce what you've learned.**Example: A Module**Like Verilog, we can declare module definitions in Chisel. The following example is a Chisel `Module`, `Passthrough`, that has one 4-bit input, `in`, and one 4-bit output, `out`. The module combinationally connects `in` and `out`, so `in` drives `out`.<jupyter_code>// Chisel Code: Declare a new module definition
class Passthrough extends Module {
  val io = IO(new Bundle {
    val in = Input(UInt(4.W))
    val out = Output(UInt(4.W))
  })
  io.out := io.in
}<jupyter_output><empty_output><jupyter_text>There's a lot here! The following explains how to think of each line in terms of the hardware we are describing.```scalaclass Passthrough extends Module {```We declare a new module called `Passthrough`. `Module` is a built-in Chisel class that all hardware modules must extend.```scala val io = IO(...)```We declare all our input and output ports in a special `io` `val`. It must be called `io` and be an `IO` object or instance, which requires something of the form `IO(_instantiated_bundle_)`.```scalanew Bundle {    val in = Input(...)    val out = Output(...)}```We declare a new hardware struct type (Bundle) that contains some named signals `in` and `out` with directions Input and Output, respectively.```scalaUInt(4.W)```We declare a signal's hardware type. In this case, it is an unsigned integer of width 4.```scalaio.out := io.in```We connect our input port to our output port, such that `io.in` *drives* `io.out`. Note that the `:=` operator is a ***Chisel*** operator that indicates that the right-hand signal drives the left-hand signal. It is a directioned operator.The neat thing about hardware construction languages (HCLs) is that we can use the underlying programming language as a scripting language. For example, after declaring our Chisel module, we then use Scala to call the Chisel compiler to translate Chisel `Passthrough` into Verilog `Passthrough`. This process is called ***elaboration***.<jupyter_code>// Scala Code: Elaborate our Chisel design by translating it to Verilog
// Don't worry about understanding this code; it is very complicated Scala
println(getVerilog(new Passthrough))<jupyter_output>[[35minfo[0m] [0.001] Elaborating design...
[[35minfo[0m] [0.044] Done elaborating.
Total FIRRTL Compile Time: 226.2 ms

module cmd2WrapperHelperPassthrough( // @[:@3.2]
  input        clock, // @[:@4.4]
  input        reset, // @[:@5.4]
  input  [3:0] io_in, // @[:@6.4]
  output [3:0] io_out // @[:@6.4]
);
  assign io_out = io_in;
endmodule<jupyter_text>Note that the Name of our module is `cmdWrapperHelperPassthrough`, which is an artifact of running this tutorial in Jupyter. In your normal code, its name should just be `Passthrough`. This is an important lesson though - although Chisel does its best to preserve the names of your modules and other hardware components, sometimes it fails to do so.**Example: A Module Generator**If we apply what we learned about Scala to this example, we can see that a Chisel module is implemented as a Scala class. Just like any other Scala class, we could make a Chisel module take some construction parameters. In this case, we make a new class `PassthroughGenerator` which will accept an integer `width` that dictates the widths of its input and output ports:<jupyter_code>// Chisel Code, but pass in a parameter to set widths of ports
class PassthroughGenerator(width: Int) extends Module { 
  val io = IO(new Bundle {
    val in = Input(UInt(width.W))
    val out = Output(UInt(width.W))
  })
  io.out := io.in
}

// Let's now generate modules with different widths
println(getVerilog(new PassthroughGenerator(10)))
println(getVerilog(new PassthroughGenerator(20)))<jupyter_output>[[35minfo[0m] [0.000] Elaborating design...
[[35minfo[0m] [0.003] Done elaborating.
Total FIRRTL Compile Time: 8.9 ms

module cmd4WrapperHelperPassthroughGenerator( // @[:@3.2]
  input        clock, // @[:@4.4]
  input        reset, // @[:@5.4]
  input  [9:0] io_in, // @[:@6.4]
  output [9:0] io_out // @[:@6.4]
);
  assign io_out = io_in;
endmodule

[[35minfo[0m] [0.000] Elaborating design...
[[35minfo[0m] [0.001] Done elaborating.
Total FIRRTL Compile Time: 6.7 ms

module cmd4WrapperHelperPassthroughGenerator( // @[:@3.2]
  input         clock, // @[:@4.4]
  input         reset, // @[:@5.4]
  input  [19:0] io_in, // @[:@6.4]
  output [19:0] io_out // @[:@6.4]
);
  assign io_out = io_in;
endmodule<jupyter_text>Notice that the generated Verilog uses different bitwidths for the input/output depending on the value assigned to the `width` parameter. Let's dig into how this works. Because Chisel Modules are normal Scala classes, we can use the power of Scala's class constructors to parameterize the elaboration of our design.You may notice that this parameterization is enabled by *Scala*, not *Chisel*; Chisel has no extra APIs for parameterization, but a designer can simply leverage Scala features to parameterize his/her designs.Because `PassthroughGenerator` no longer describes a single Module, but instead describes a family of modules parameterized by `width`, we refer to this `Passthrough` as a ***generator***. --- Testing Your HardwareNo hardware module or generator should be complete without a tester. Chisel has built-in test features that you will explore throughout this bootcamp. The following example is a Chisel test harness that passes values to an instance of `Passthrough`'s input port `in`, and checks that the same value is seen on the output port `out`.**Example: A Tester**There is a ***LOT*** of crazy Scala going on here, so no need to understand anything except the `poke` and `expect` commands. You can think of the rest of the code as simply boilerplate to write these simple tests.<jupyter_code>// Scala Code: Calling Driver to instantiate Passthrough + PeekPokeTester and execute the test.
// Don't worry about understanding this code; it is very complicated Scala.
// Think of it more as boilerplate to run a Chisel test.
val testResult = Driver(() => new Passthrough()) {
  c => new PeekPokeTester(c) {
    poke(c.io.in, 0)     // Set our input to value 0
    expect(c.io.out, 0)  // Assert that the output correctly has 0
    poke(c.io.in, 1)     // Set our input to value 1
    expect(c.io.out, 1)  // Assert that the output correctly has 1
    poke(c.io.in, 2)     // Set our input to value 2
    expect(c.io.out, 2)  // Assert that the output correctly has 2
  }
}
assert(testResult)   // Scala Code: if testResult == false, will throw an error
println("SUCCESS!!") // Scala Code: if we get here, our tests passed!<jupyter_output>[[35minfo[0m] [0.000] Elaborating design...
[[35minfo[0m] [0.001] Done elaborating.
Total FIRRTL Compile Time: 8.4 ms
Total FIRRTL Compile Time: 5.3 ms
End of dependency graph
Circuit state created
[[35minfo[0m] [0.001] SEED 1535428091008
test cmd2WrapperHelperPassthrough Success: 3 tests passed in 5 cycles taking 0.011168 seconds
[[35minfo[0m] [0.003] RAN 0 CYCLES PASSED
SUCCESS!!<jupyter_text>What's going on? The test accepts a `Passthrough` module, assigns values to the module's inputs, and checks its outputs. To set an input, we call `poke`. To check an output, we call `expect`. If we don't want to compare the output to an expected value (no assertion), we can `peek` the output instead.If all `expect` statements are true, then our boilerplate code will return true (see `testResult`). **Exercise: Writing Your Own Testers**Write and execute two tests, one that tests `PassthroughGenerator` for a width of 10 and a second that tests `PassthroughGenerator` for a width of 20. Check at least two values for each: zero and the maximum value supported by the specified width. Note that the triple question mark has a special meaning in Scala. You may see it frequently in these bootcamp exercises. Running code with the `???` will produce the `NotImplementedError`. Replace `???` with your testers.<jupyter_code>val test10result = Driver (() => new PassthroughGenerator(width = 10)) {
    c => new PeekPokeTester(c) {
        poke(c.io.in, 0)    // Checking zero
        expect(c.io.out, 0)
        poke(c.io.in, 1023) // Max value of a 10-bit int, 2^10-1 = 1023
        expect(c.io.out, 1023)
    }
}

val test20result = Driver (() => new PassthroughGenerator(width = 20)) {
    c => new PeekPokeTester(c) {
        poke(c.io.in, 0)       // Checking zero
        expect(c.io.out, 0)
        poke(c.io.in, 1048575) // Max value of a 20-bit int, 2^20-1 = 1048575
        expect(c.io.out, 1048575)
    }
}

assert((test10result == true) && (test20result == true))
println("SUCCESS!!") // Scala Code: if we get here, our tests passed!<jupyter_output>[[35minfo[0m] [0.000] Elaborating design...
[[35minfo[0m] [0.001] Done elaborating.
Total FIRRTL Compile Time: 4.7 ms
Total FIRRTL Compile Time: 4.0 ms
End of dependency graph
Circuit state created
[[35minfo[0m] [0.000] SEED 1535428165144
test cmd4WrapperHelperPassthroughGenerator Success: 2 tests passed in 5 cycles taking 0.003022 seconds
[[35minfo[0m] [0.001] RAN 0 CYCLES PASSED
[[35minfo[0m] [0.000] Elaborating design...
[[35minfo[0m] [0.001] Done elaborating.
Total FIRRTL Compile Time: 4.4 ms
Total FIRRTL Compile Time: 4.2 ms
End of dependency graph
Circuit state created
[[35minfo[0m] [0.000] SEED 1535428165171
test cmd4WrapperHelperPassthroughGenerator Success: 2 tests passed in 5 cycles taking 0.001749 seconds
[[35minfo[0m] [0.001] RAN 0 CYCLES PASSED
SUCCESS!!<jupyter_text>Solution (click to toggle displaying it)val test10result = Driver(() => new PassthroughGenerator(10)) {  c => new PeekPokeTester(c) {    poke(c.io.in, 0)    expect(c.io.out, 0)    poke(c.io.in, 1023)    expect(c.io.out, 1023)  }}val test20result = Driver(() => new PassthroughGenerator(20)) {  c => new PeekPokeTester(c) {    poke(c.io.in, 0)    expect(c.io.out, 0)    poke(c.io.in, 1048575)    expect(c.io.out, 1048575)  }} --- Looking at Generated Verilog/FIRRTLIf you are having trouble understanding the generated hardware and are comfortable with reading structural Verilog and/or FIRRTL (Chisel's IR which is comparable to a synthesis-only subset of Verilog), then you can try looking at the generated Verilog to see the result of Chisel execution.Here is an example of generating the Verilog (which you've seen already) and the FIRRTL.<jupyter_code>// Viewing the Verilog for debugging
println(getVerilog(new Passthrough))
// Viewing the firrtl for debugging
println(getFirrtl(new Passthrough))<jupyter_output>[[35minfo[0m] [0.000] Elaborating design...
[[35minfo[0m] [0.001] Done elaborating.

circuit cmd2WrapperHelperPassthrough : 
  module cmd2WrapperHelperPassthrough : 
    input clock : Clock
    input reset : UInt<1>
    output io : {flip in : UInt<4>, out : UInt<4>}
    
    io.out <= io.in @[cmd2.sc 6:10]<jupyter_text>--- You're done![Return to the top.](top)   Appendix: A Note on "printf" Debugging[Debugging with print statements](https://stackoverflow.com/a/189570) is not always the best way to debug, but is often an easy first step to see what's going on when something doesn't work the way you expect.Because Chisel generators are programs generating hardware, there are some extra subtleties about printing generator and circuit state.It is important to remember when your print statement executes and what is being printed.The three common scenarios where you might want to print have some important differences:* Chisel generator prints during circuit generation* Circuit prints during circuit simulation* Tester prints during testing`println` is a built-in Scala function that prints to the console. It **cannot** be used to print during circuit simulation because the generated circuit is FIRRTL or Verilog- not Scala.The following code block shows different styles of printing.<jupyter_code>class PrintingModule extends Module {
    val io = IO(new Bundle {
        val in = Input(UInt(4.W))
        val out = Output(UInt(4.W))
    })
    io.out := io.in

    printf("Print during simulation: Input is %d\n", io.in)
    // chisel printf has its own string interpolator too
    printf(p"Print during simulation: IO is $io\n")

    println(s"Print during generation: Input is ${io.in}")
}

class PrintingModuleTester(c: PrintingModule) extends PeekPokeTester(c) {
    poke(c.io.in, 3)
    step(5) // circuit will print
    
    println(s"Print during testing: Input is ${peek(c.io.in)}")
}
chisel3.iotesters.Driver( () => new PrintingModule ) { c => new PrintingModuleTester(c) }<jupyter_output>[[35minfo[0m] [0.000] Elaborating design...
Print during generation: Input is chisel3.core.UInt@7
[[35minfo[0m] [0.024] Done elaborating.
Total FIRRTL Compile Time: 25.4 ms
Total FIRRTL Compile Time: 8.2 ms
End of dependency graph
Circuit state created
[[35minfo[0m] [0.000] SEED 1535428328501
Print during simulation: Input is 3
Print during simulation: IO is Bundle(in -> 3, out -> 3)
Print during simulation: Input is 3
Print during simulation: IO is Bundle(in -> 3, out -> 3)
Print during simulation: Input is 3
Print during simulation: IO is Bundle(in -> 3, out -> 3)
Print during simulation: Input is 3
Print during simulation: IO is Bundle(in -> 3, out -> 3)
Print during simulation: Input is 3
Print during simulation: IO is Bundle(in -> 3, out -> 3)
[[35minfo[0m] [0.004] Print during testing: Input is 3
test cmd10WrapperHelperPrintingModule Success: 0 tests passed in 10 cycles taking 0.008968 seconds
[[35minfo[0m] [0.004] RAN 5 CYCLES PASSED
{'fraction_non_alphanumeric': 0.10315637509022901, 'fraction_numerical': 0.03366362622219306, 'mean_word_length': 4.339873861247372, 'pattern_counts': {'":': 0, '<': 35, '<?xml version=': 0, '>': 56, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '501544', 'license': 'BSD-3-Clause', 'n_tokens_mistral': 5170, 'n_tokens_neox': 4654, 'n_words': 1945, 'path': '2.1_first_module.ipynb', 'repo_name': 'jamesdunn/generator-bootcamp', 'size': '15239'}