{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\"Chisel" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Module 3.1: Generators: Parameters\n", "**Prev: [ChiselTest (was chisel-testers2)](2.6_chiseltest.ipynb)**
\n", "**Next: [Generators: Collections](3.2_collections.ipynb)**\n", "\n", "## Motivation\n", "For Chisel modules to be code generators, there must be something that tells the generator how it should go about its job.\n", "In this section we discuss module parameterization, the various methodologies and Scala language features.\n", "The richness of the parameter passing implementation is directly proportional to the richness of the circuits generated.\n", "Parameters should provide useful default values, be easy to set, and protect against illegal or non-sensical values.\n", "For more complicated system it is very useful if they can be locally overriden in a way that does not inadvertantly affect other modules usages.\n", "\n", "## Setup" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "val path = System.getProperty(\"user.dir\") + \"/source/load-ivy.sc\"\n", "interp.load.module(ammonite.ops.Path(java.nio.file.FileSystems.getDefault().getPath(path)))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import chisel3._\n", "import chisel3.util._\n", "import chisel3.tester._\n", "import chisel3.tester.RawTester.test" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# Parameter Passing\n", "Chisel provides powerful constructs for writing hardware generators.\n", "Generators are programs that take some circuit parameters and produce a circuit description.\n", "In this section, we'll start with discussing how Chisel generators get their parameters.\n", "\n", "**Example: Parameterized Scala Object**
\n", "Every Chisel `Module`s is a Scala class just like any other.\n", "Recall that Scala classes can be parameterized like so:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class ParameterizedScalaObject(param1: Int, param2: String) {\n", " println(s\"I have parameters: param1 = $param1 and param2 = $param2\")\n", "}\n", "val obj1 = new ParameterizedScalaObject(4, \"Hello\")\n", "val obj2 = new ParameterizedScalaObject(4 + 2, \"World\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Example: Parameterized Chisel Object**
\n", "Chisel modules can be parameterized the same way.\n", "The following module has parameters for the widths of all its inputs and outputs.\n", "Running the code block will print generated Verilog.\n", "Play with the parameters and check that the output changes to reflect new parameters." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class ParameterizedWidthAdder(in0Width: Int, in1Width: Int, sumWidth: Int) extends Module {\n", " require(in0Width >= 0)\n", " require(in1Width >= 0)\n", " require(sumWidth >= 0)\n", " val io = IO(new Bundle {\n", " val in0 = Input(UInt(in0Width.W))\n", " val in1 = Input(UInt(in1Width.W))\n", " val sum = Output(UInt(sumWidth.W))\n", " })\n", " // a +& b includes the carry, a + b does not\n", " io.sum := io.in0 +& io.in1\n", "}\n", "\n", "println(getVerilog(new ParameterizedWidthAdder(1, 4, 6)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The above code block has some `require(...)` statements.\n", "These are pre-elaboration assertions, which are useful when your generator only works with certain parameterizations or when some parameterizations are mutually exclusive or nonsensical.\n", "The above code block checks that widths are non-negative.\n", "\n", "There is a separate construct for simulation-time assertions called `assert(...)`.\n", "\n", "## Sorting with Parameterized Modules\n", "The following code block is a parameterized sort similar to `Sort4` from module 2.3.\n", "Unlike the previous example of an adder with parameterized width IOs, this example has a fixed IO.\n", "The parameter controls what hardware is generated inside the module.\n", "![Sort4](images/Sorter4.png)\n", "**Example: Parameterized 4-Input Sort**
\n", "Unlike 2.3, this implementation is parameterized to be either a descending or an ascending sort." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "/** Sort4 sorts its 4 inputs to its 4 outputs */\n", "class Sort4(ascending: Boolean) extends Module {\n", " val io = IO(new Bundle {\n", " val in0 = Input(UInt(16.W))\n", " val in1 = Input(UInt(16.W))\n", " val in2 = Input(UInt(16.W))\n", " val in3 = Input(UInt(16.W))\n", " val out0 = Output(UInt(16.W))\n", " val out1 = Output(UInt(16.W))\n", " val out2 = Output(UInt(16.W))\n", " val out3 = Output(UInt(16.W))\n", " })\n", " \n", " // this comparison funtion decides < or > based on the module's parameterization\n", " def comp(l: UInt, r: UInt): Bool = {\n", " if (ascending) {\n", " l < r\n", " } else {\n", " l > r\n", " }\n", " }\n", "\n", " val row10 = Wire(UInt(16.W))\n", " val row11 = Wire(UInt(16.W))\n", " val row12 = Wire(UInt(16.W))\n", " val row13 = Wire(UInt(16.W))\n", "\n", " when(comp(io.in0, io.in1)) {\n", " row10 := io.in0 // preserve first two elements\n", " row11 := io.in1\n", " }.otherwise {\n", " row10 := io.in1 // swap first two elements\n", " row11 := io.in0\n", " }\n", "\n", " when(comp(io.in2, io.in3)) {\n", " row12 := io.in2 // preserve last two elements\n", " row13 := io.in3\n", " }.otherwise {\n", " row12 := io.in3 // swap last two elements\n", " row13 := io.in2\n", " }\n", "\n", " val row21 = Wire(UInt(16.W))\n", " val row22 = Wire(UInt(16.W))\n", "\n", " when(comp(row11, row12)) {\n", " row21 := row11 // preserve middle 2 elements\n", " row22 := row12\n", " }.otherwise {\n", " row21 := row12 // swap middle two elements\n", " row22 := row11\n", " }\n", "\n", " val row20 = Wire(UInt(16.W))\n", " val row23 = Wire(UInt(16.W))\n", " when(comp(row10, row13)) {\n", " row20 := row10 // preserve the first and the forth elements\n", " row23 := row13\n", " }.otherwise {\n", " row20 := row13 // swap the first and the forth elements\n", " row23 := row10\n", " }\n", "\n", " when(comp(row20, row21)) {\n", " io.out0 := row20 // preserve first two elements\n", " io.out1 := row21\n", " }.otherwise {\n", " io.out0 := row21 // swap first two elements\n", " io.out1 := row20\n", " }\n", "\n", " when(comp(row22, row23)) {\n", " io.out2 := row22 // preserve first two elements\n", " io.out3 := row23\n", " }.otherwise {\n", " io.out2 := row23 // swap first two elements\n", " io.out3 := row22\n", " }\n", "}\n", "\n", "\n", "\n", "// Here are the testers\n", "test(new Sort4(true)) { c => \n", " c.io.in0.poke(3.U)\n", " c.io.in1.poke(6.U)\n", " c.io.in2.poke(9.U)\n", " c.io.in3.poke(12.U)\n", " c.io.out0.expect(3.U)\n", " c.io.out1.expect(6.U)\n", " c.io.out2.expect(9.U)\n", " c.io.out3.expect(12.U)\n", "\n", " c.io.in0.poke(13.U)\n", " c.io.in1.poke(4.U)\n", " c.io.in2.poke(6.U)\n", " c.io.in3.poke(1.U)\n", " c.io.out0.expect(1.U)\n", " c.io.out1.expect(4.U)\n", " c.io.out2.expect(6.U)\n", " c.io.out3.expect(13.U)\n", "\n", " c.io.in0.poke(13.U)\n", " c.io.in1.poke(6.U)\n", " c.io.in2.poke(4.U)\n", " c.io.in3.poke(1.U)\n", " c.io.out0.expect(1.U)\n", " c.io.out1.expect(4.U)\n", " c.io.out2.expect(6.U)\n", " c.io.out3.expect(13.U)\n", "}\n", "test(new Sort4(false)) { c =>\n", " c.io.in0.poke(3.U)\n", " c.io.in1.poke(6.U)\n", " c.io.in2.poke(9.U)\n", " c.io.in3.poke(12.U)\n", " c.io.out0.expect(12.U)\n", " c.io.out1.expect(9.U)\n", " c.io.out2.expect(6.U)\n", " c.io.out3.expect(3.U)\n", "\n", " c.io.in0.poke(13.U)\n", " c.io.in1.poke(4.U)\n", " c.io.in2.poke(6.U)\n", " c.io.in3.poke(1.U)\n", " c.io.out0.expect(13.U)\n", " c.io.out1.expect(6.U)\n", " c.io.out2.expect(4.U)\n", " c.io.out3.expect(1.U)\n", "\n", " c.io.in0.poke(1.U)\n", " c.io.in1.poke(6.U)\n", " c.io.in2.poke(4.U)\n", " c.io.in3.poke(13.U)\n", " c.io.out0.expect(13.U)\n", " c.io.out1.expect(6.U)\n", " c.io.out2.expect(4.U)\n", " c.io.out3.expect(1.U)\n", "}\n", "println(\"SUCCESS!!\") // Scala Code: if we get here, our tests passed!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# Option and Default Arguments\n", "\n", "There are times when a function sometimes returns a value, and sometimes does not. Instead of erroring when it cannot return a value, Scala has a mechanism to encode this in the type system.\n", "\n", "**Example: Erroneous Map Index Call**
\n", "In the following example, we have a map containing several key/value pairs. If we try to access a missing key/value pair, then we get a runtime error:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "val map = Map(\"a\" -> 1)\n", "val a = map(\"a\")\n", "println(a)\n", "val b = map(\"b\")\n", "println(b)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Example: Getting Uncertain Indices**
\n", "However, `Map` provides another way to access a key's value, through the **get** method. Using this returns a value of abstract class `Option`. `Option` has two subclasses, `Some` and `None`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "val map = Map(\"a\" -> 1)\n", "val a = map.get(\"a\")\n", "println(a)\n", "val b = map.get(\"b\")\n", "println(b)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As you'll see in later sections, `Option` is extremely important because it lets users use a match statement to check Scala types and values.\n", "\n", "**Example: Get Or Else!**
\n", "Like `Map`, `Option` also has a `get` method, which errors if called on `None`. For these instances, we can provide a default using **`getOrElse`**." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "val some = Some(1)\n", "val none = None\n", "println(some.get) // Returns 1\n", "// println(none.get) // Errors!\n", "println(some.getOrElse(2)) // Returns 1\n", "println(none.getOrElse(2)) // Returns 2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Options for Parameters with Defaults\n", "\n", "When objects or functions have a lot of parameters, it can be tedious and error-prone to fully specify them all the time.\n", "In module 1, you were introduced to named arguments and parameter defaults.\n", "Sometimes, a parameter doesn't have a good default value.\n", "`Option` can be used with a default value of `None` in these situations.\n", "\n", "**Example: Optional Reset**
\n", "The following shows a block that delays its input by one clock cycle.\n", "If `resetValue = None`, which is the default, the register will have no reset value and be initialized to garbage.\n", "This avoids the common but ugly case of using values outside the normal range to indicate \"none\", like using -1 as the reset value to indicate that this register is not reset." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class DelayBy1(resetValue: Option[UInt] = None) extends Module {\n", " val io = IO(new Bundle {\n", " val in = Input( UInt(16.W))\n", " val out = Output(UInt(16.W))\n", " })\n", " val reg = if (resetValue.isDefined) { // resetValue = Some(number)\n", " RegInit(resetValue.get)\n", " } else { //resetValue = None\n", " Reg(UInt())\n", " }\n", " reg := io.in\n", " io.out := reg\n", "}\n", "\n", "println(getVerilog(new DelayBy1))\n", "println(getVerilog(new DelayBy1(Some(3.U))))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# Match/Case Statements\n", "The Scala *matching* concept is used throughout Chisel and needs to be part of any Chisel programmer's basic understanding. Scala provides the match operator which supports:\n", "- Simple testing for alternatives, something like a C *switch* statement\n", "- More complex testing of ad-hoc combinations of values\n", "- Taking actions based on the type of a variable when its type is unknown or underspecified, for example when\n", " - variable is taken from a heterogeneous list ```val mixedList = List(1, \"string\", false)```\n", " - or variable is known to be a member of a super-class but not which specific sub-class it is.\n", "- Extraction of sub strings of a string that are specified with a *regular expression*\n", "\n", "\n", "**Example: Value Matching**
\n", "The following example, depending on the **value** of the variable we **match** on, we execute a different **case** statement:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "// y is an integer variable defined somewhere else in the code\n", "val y = 7\n", "/// ...\n", "val x = y match {\n", " case 0 => \"zero\" // One common syntax, preferred if fits in one line\n", " case 1 => // Another common syntax, preferred if does not fit in one line.\n", " \"one\" // Note the code block continues until the next case\n", " case 2 => { // Another syntax, but curly braces are not required\n", " \"two\"\n", " }\n", " case _ => \"many\" // _ is a wildcard that matches all values\n", "}\n", "println(\"y is \" + x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The match operator checks possible values and for each case returns a string. A couple of things to note:\n", "- Each code block that follows a ```=>``` operator continues until it reaches either the ending brace of the match or the next case statement.\n", "- A match is searched in the order of the case statements, once a case statement has been matched, no other\n", "checks against other case statements are made.\n", "- The use of underscore as a wildcard, to handle any value not found.\n", "\n", "**Example: Multiple Value Matching**
\n", "Also, multiple variables can be matched at the same time. Here's a simple example of a truth table implemented with a match statement and tuple of values:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def animalType(biggerThanBreadBox: Boolean, meanAsCanBe: Boolean): String = {\n", " (biggerThanBreadBox, meanAsCanBe) match {\n", " case (true, true) => \"wolverine\"\n", " case (true, false) => \"elephant\"\n", " case (false, true) => \"shrew\"\n", " case (false, false) => \"puppy\"\n", " }\n", "}\n", "println(animalType(true, true))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Example: Type Matching**
\n", "Scala is a strongly typed language, so the types of all objects are known during runtime. We can use **match statements** to use this type information to dictate control flow:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "val sequence = Seq(\"a\", 1, 0.0)\n", "sequence.foreach { x =>\n", " x match {\n", " case s: String => println(s\"$x is a String\")\n", " case s: Int => println(s\"$x is an Int\")\n", " case s: Double => println(s\"$x is a Double\")\n", " case _ => println(s\"$x is an unknown type!\")\n", " }\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Example: Multiple Type Matching**
\n", "If you want to match on whether a value has one of many types, use the following syntax. *Note that you **must** use an `_` when matching.*" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "val sequence = Seq(\"a\", 1, 0.0)\n", "sequence.foreach { x =>\n", " x match {\n", " case _: Int | _: Double => println(s\"$x is a number!\")\n", " case _ => println(s\"$x is an unknown type!\")\n", " }\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Example: Type Matching and Erasure**
\n", "Type matching has some limitations. Because Scala runs on the JVM, and the JVM does not maintain polymorphic types, you cannot match on them at runtime (because they are all erased). Note that the following example always matches the first case statement, because the `[String]`, `[Int]`, and `[Double]` polymorphic types are erased, and the case statements are **actually** matching on just a `Seq`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "val sequence = Seq(Seq(\"a\"), Seq(1), Seq(0.0))\n", "sequence.foreach { x =>\n", " x match {\n", " case s: Seq[String] => println(s\"$x is a String\")\n", " case s: Seq[Int] => println(s\"$x is an Int\")\n", " case s: Seq[Double] => println(s\"$x is a Double\")\n", " }\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that Scala compilers will usually give a warning if you implement code like the example above.\n", "\n", "**Example: Optional Reset Matching**
\n", "The following code block shows the same `DelayBy1` module with the match construct instead of `if/else`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class DelayBy1(resetValue: Option[UInt] = None) extends Module {\n", " val io = IO(new Bundle {\n", " val in = Input( UInt(16.W))\n", " val out = Output(UInt(16.W))\n", " })\n", " val reg = resetValue match {\n", " case Some(r) => RegInit(r)\n", " case None => Reg(UInt())\n", " }\n", " reg := io.in\n", " io.out := reg\n", "}\n", "\n", "println(getVerilog(new DelayBy1))\n", "println(getVerilog(new DelayBy1(Some(3.U))))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# IOs with Optional Fields\n", "\n", "Sometimes we want IOs to be optionally included or excluded.\n", "Maybe there's some internal state that's nice to be able to look at for debugging, but you want to hide it when the generator is being used in a system.\n", "Maybe your generator has some inputs that don't need to be connected in every situation because there is a sensible default.\n", "\n", "**Example: Optional IO with Option**
\n", "Optional bundle fields are one way to get this functionality.\n", "In the following example, we show a one-bit adder that optionally takes in a carry.\n", "If the carry is included, `io.carryIn` will have type `Some[UInt]` and be included in the IO bundle.\n", "If the carry is not included, `io.carryIn` will have type `None` and will be excluded from the IO bundle." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class HalfFullAdder(val hasCarry: Boolean) extends Module {\n", " val io = IO(new Bundle {\n", " val a = Input(UInt(1.W))\n", " val b = Input(UInt(1.W))\n", " val carryIn = if (hasCarry) Some(Input(UInt(1.W))) else None\n", " val s = Output(UInt(1.W))\n", " val carryOut = Output(UInt(1.W))\n", " })\n", " val sum = io.a +& io.b +& io.carryIn.getOrElse(0.U)\n", " io.s := sum(0)\n", " io.carryOut := sum(1)\n", "}\n", "\n", "test(new HalfFullAdder(false)) { c =>\n", " require(!c.hasCarry, \"DUT must be half adder\")\n", " // 0 + 0 = 0\n", " c.io.a.poke(0.U)\n", " c.io.b.poke(0.U)\n", " c.io.s.expect(0.U)\n", " c.io.carryOut.expect(0.U)\n", " // 0 + 1 = 1\n", " c.io.b.poke(1.U)\n", " c.io.s.expect(1.U)\n", " c.io.carryOut.expect(0.U)\n", " // 1 + 1 = 2\n", " c.io.a.poke(1.U)\n", " c.io.s.expect(0.U)\n", " c.io.carryOut.expect(1.U)\n", " // 1 + 0 = 1\n", " c.io.b.poke(0.U)\n", " c.io.s.expect(1.U)\n", " c.io.carryOut.expect(0.U)\n", "}\n", "\n", "test(new HalfFullAdder(true)) { c =>\n", " require(c.hasCarry, \"DUT must be half adder\")\n", " c.io.carryIn.get.poke(0.U)\n", " // 0 + 0 + 0 = 0\n", " c.io.a.poke(0.U)\n", " c.io.b.poke(0.U)\n", " c.io.s.expect(0.U)\n", " c.io.carryOut.expect(0.U)\n", " // 0 + 0 + 1 = 1\n", " c.io.b.poke(1.U)\n", " c.io.s.expect(1.U)\n", " c.io.carryOut.expect(0.U)\n", " // 0 + 1 + 1 = 2\n", " c.io.a.poke(1.U)\n", " c.io.s.expect(0.U)\n", " c.io.carryOut.expect(1.U)\n", " // 0 + 1 + 0 = 1\n", " c.io.b.poke(0.U)\n", " c.io.s.expect(1.U)\n", " c.io.carryOut.expect(0.U)\n", "\n", " c.io.carryIn.get.poke(1.U)\n", " // 1 + 0 + 0 = 1\n", " c.io.a.poke(0.U)\n", " c.io.b.poke(0.U)\n", " c.io.s.expect(1.U)\n", " c.io.carryOut.expect(0.U)\n", " // 1 + 0 + 1 = 2\n", " c.io.b.poke(1.U)\n", " c.io.s.expect(0.U)\n", " c.io.carryOut.expect(1.U)\n", " // 1 + 1 + 1 = 3\n", " c.io.a.poke(1.U)\n", " c.io.s.expect(1.U)\n", " c.io.carryOut.expect(1.U)\n", " // 1 + 1 + 0 = 2\n", " c.io.b.poke(0.U)\n", " c.io.s.expect(0.U)\n", " c.io.carryOut.expect(1.U)\n", "}\n", "\n", "println(\"SUCCESS!!\") // Scala Code: if we get here, our tests passed!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Example: Optional IO with Zero-Width Wires**
\n", "Another way to achieve similar functionality to `Option`s is with zero-width wires.\n", "Chisel types are allowed to have widths of zero.\n", "An IO with width zero is pruned from the emitted Verilog, and anything that tries to use the value of a zero-width wire gets a constant zero.\n", "If zero is a sensible default value, zero-width wires can be nice because they obviate the need for matching on an option or calling `getOrElse`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class HalfFullAdder(val hasCarry: Boolean) extends Module {\n", " val io = IO(new Bundle {\n", " val a = Input(UInt(1.W))\n", " val b = Input(UInt(1.W))\n", " val carryIn = Input(if (hasCarry) UInt(1.W) else UInt(0.W))\n", " val s = Output(UInt(1.W))\n", " val carryOut = Output(UInt(1.W))\n", " })\n", " val sum = io.a +& io.b +& io.carryIn\n", " io.s := sum(0)\n", " io.carryOut := sum(1)\n", "}\n", "println(\"Half Adder:\")\n", "println(getVerilog(new HalfFullAdder(false)))\n", "println(\"\\n\\nFull Adder:\")\n", "println(getVerilog(new HalfFullAdder(true)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# Implicits\n", "There are often times when you are programming that requires a lot of boilerplate code. To handle this use case, Scala introduced the notion of **implicits**, which allow the compiler to do some syntactic sugar for you. Because lots of things happen behind the scenes, implicits can appear very magical. This section breaks down some basic examples to explain what they are and where they are commonly used.\n", "\n", "## Implicit Arguments\n", "At times, your code will require accessing a top-level variable of some sort from deep within a series of function calls. Instead of manually threading this variable through every function call, you can use implicit arguments to do it for you.\n", "\n", "**Example: Implicit Cats**
\n", "In the following example, we can pass the number of cats implicitly or explicitly." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "object CatDog {\n", " implicit val numberOfCats: Int = 3\n", " //implicit val numberOfDogs: Int = 5\n", "\n", " def tooManyCats(nDogs: Int)(implicit nCats: Int): Boolean = nCats > nDogs\n", " \n", " val imp = tooManyCats(2) // Argument passed implicitly!\n", " val exp = tooManyCats(2)(1) // Argument passed explicitly!\n", "}\n", "CatDog.imp\n", "CatDog.exp" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "What's happening here? First, we define an implicit value **numberOfCats**. In a given scope, **there can only be one implicit value of a given type**. Then, we define a function that takes two argument lists; the first is any explicit parameters, and the second are any implicit parameters. When we call **tooManyCats**, we either omit the second implicit argument list (letting the compiler find it for us), or explicitly provide an argument (which can be different than the implicit value).\n", "\n", "The following are ways implicit arguments can *fail*:\n", "- Two or more implicit values of a given type are defined in a scope\n", "- If the compiler cannot find an implicit value necessary for a function call\n", "\n", "**Example: Implicit Logging**
\n", "The next code block shows how you might use implicit arguments to implement logging in a Chisel generator.\n", "\n", "***Note: there are better ways to do logging in Scala!***" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sealed trait Verbosity\n", "implicit case object Silent extends Verbosity\n", "case object Verbose extends Verbosity\n", "\n", "class ParameterizedWidthAdder(in0Width: Int, in1Width: Int, sumWidth: Int)(implicit verbosity: Verbosity)\n", "extends Module {\n", " def log(msg: => String): Unit = verbosity match {\n", " case Silent =>\n", " case Verbose => println(msg)\n", " }\n", " require(in0Width >= 0)\n", " log(s\"in0Width of $in0Width OK\")\n", " require(in1Width >= 0)\n", " log(s\"in1Width of $in1Width OK\")\n", " require(sumWidth >= 0)\n", " log(s\"sumWidth of $sumWidth OK\")\n", " val io = IO(new Bundle {\n", " val in0 = Input(UInt(in0Width.W))\n", " val in1 = Input(UInt(in1Width.W))\n", " val sum = Output(UInt(sumWidth.W))\n", " })\n", " log(\"Made IO\")\n", " io.sum := io.in0 + io.in1\n", " log(\"Assigned output\")\n", "}\n", "\n", "println(getVerilog(new ParameterizedWidthAdder(1, 4, 5)))\n", "println(getVerilog(new ParameterizedWidthAdder(1, 4, 5)(Verbose)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Implicit Conversions\n", "Like implicit arguments, implicit functions (also known as **implicit conversions**) are used to reduce boilerplate code. More specifically, they are used to automatically convert one Scala object into another.\n", "\n", "**Example: Implicit Conversion**
\n", "In the following example, we have two classes, `Animal` and `Human`. `Animal` has a `species` field, but `Human` does not. However, by implementing an implicit conversion, we can call `species` on a `Human`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class Animal(val name: String, val species: String)\n", "class Human(val name: String)\n", "implicit def human2animal(h: Human): Animal = new Animal(h.name, \"Homo sapiens\")\n", "val me = new Human(\"Adam\")\n", "println(me.species)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Generally, implicits can make your code confusing, so we recommend you use them as a last resort. First try inheritance, traits, or method overloading." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# Generator Example\n", "The following example shows a generator for a 1-bit input Mealy machine.\n", "It has a test based on the example from [Wikipedia](https://en.wikipedia.org/wiki/Mealy_machine#/media/File:Mealy.png).\n", "Read through the code and try to follow what's going on.\n", "\n", "**Example: Mealy Machine**
\n", "Try making your own parameterizations of the Mealy machine generator and writing your own tests in the code block below." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "// Mealy machine has\n", "case class BinaryMealyParams(\n", " // number of states\n", " nStates: Int,\n", " // initial state\n", " s0: Int,\n", " // function describing state transition\n", " stateTransition: (Int, Boolean) => Int,\n", " // function describing output\n", " output: (Int, Boolean) => Int\n", ") {\n", " require(nStates >= 0)\n", " require(s0 < nStates && s0 >= 0)\n", "}\n", "\n", "class BinaryMealy(val mp: BinaryMealyParams) extends Module {\n", " val io = IO(new Bundle {\n", " val in = Input(Bool())\n", " val out = Output(UInt())\n", " })\n", "\n", " val state = RegInit(UInt(), mp.s0.U)\n", "\n", " // output zero if no states\n", " io.out := 0.U\n", " for (i <- 0 until mp.nStates) {\n", " when (state === i.U) {\n", " when (io.in) {\n", " state := mp.stateTransition(i, true).U\n", " io.out := mp.output(i, true).U\n", " }.otherwise {\n", " state := mp.stateTransition(i, false).U\n", " io.out := mp.output(i, false).U\n", " }\n", " }\n", " }\n", "}\n", "\n", "// example from https://en.wikipedia.org/wiki/Mealy_machine\n", "val nStates = 3\n", "val s0 = 2\n", "def stateTransition(state: Int, in: Boolean): Int = {\n", " if (in) {\n", " 1\n", " } else {\n", " 0\n", " }\n", "}\n", "def output(state: Int, in: Boolean): Int = {\n", " if (state == 2) {\n", " return 0\n", " }\n", " if ((state == 1 && !in) || (state == 0 && in)) {\n", " return 1\n", " } else {\n", " return 0\n", " }\n", "}\n", "\n", "val testParams = BinaryMealyParams(nStates, s0, stateTransition, output)\n", "\n", "test(new BinaryMealy(testParams)) { c =>\n", " c.io.in.poke(false.B)\n", " c.io.out.expect(0.U)\n", " c.clock.step(1)\n", " c.io.in.poke(false.B)\n", " c.io.out.expect(0.U)\n", " c.clock.step(1)\n", " c.io.in.poke(false.B)\n", " c.io.out.expect(0.U)\n", " c.clock.step(1)\n", " c.io.in.poke(true.B)\n", " c.io.out.expect(1.U)\n", " c.clock.step(1)\n", " c.io.in.poke(true.B)\n", " c.io.out.expect(0.U)\n", " c.clock.step(1)\n", " c.io.in.poke(false.B)\n", " c.io.out.expect(1.U)\n", " c.clock.step(1)\n", " c.io.in.poke(true.B)\n", " c.io.out.expect(1.U)\n", " c.clock.step(1)\n", " c.io.in.poke(false.B)\n", " c.io.out.expect(1.U)\n", " c.clock.step(1)\n", " c.io.in.poke(true.B)\n", " c.io.out.expect(1.U)\n", "}\n", "\n", "println(\"SUCCESS!!\") // Scala Code: if we get here, our tests passed!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# You're done!\n", "\n", "[Return to the top.](#top)" ] } ], "metadata": { "kernelspec": { "display_name": "Scala", "language": "scala", "name": "scala" }, "language_info": { "codemirror_mode": "text/x-scala", "file_extension": ".scala", "mimetype": "text/x-scala", "name": "scala", "nbconvert_exporter": "script", "version": "2.12.10" } }, "nbformat": 4, "nbformat_minor": 1 }