{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\"Chisel" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Module 3.6: Generators: Types\n", "**Prev: [Object Oriented Programming](3.5_object_oriented_programming.ipynb)**
\n", "**Next: [Introduction to FIRRTL](4.1_firrtl_ast.ipynb)**\n", "\n", "## Motivation\n", "Scala is a strongly-typed programming language.\n", "This is a two-edged sword; on one hand, many programs that would compile and execute in Python (a dynamically-typed language) would fail at compile time in Scala.\n", "On the other hand, programs that compile in Scala will contain many fewer runtime errors than a similar Python program.\n", "\n", "In this section, our goal is to familiarize you with types as a first class citizen in Scala.\n", "While initially you may feel you have limited productivity, you will soon learn to understand compile-time error messages and how to architect your programs with the type system in mind to catch more errors for you. \n", "\n", "\n", "## Setup" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "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": { "collapsed": false }, "outputs": [], "source": [ "import chisel3._\n", "import chisel3.util._\n", "import chisel3.iotesters.{ChiselFlatSpec, Driver, PeekPokeTester}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# Static Types\n", "\n", "## Types in Scala\n", "\n", "All objects in Scala have a type, which is usually the object's class.\n", "Let's see some:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "println(10.getClass)\n", "println(10.0.getClass)\n", "println(\"ten\".getClass)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When you declare your own class, it has an associated type." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class MyClass {\n", " def myMethod = ???\n", "}\n", "println(new MyClass().getClass)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "While not required, it is HIGHLY recommended that you **define input and output types for all function declarations**.\n", "This will let the Scala compiler catch improper use of a function." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "def double(s: String): String = s + s\n", "// Uncomment the code below to test it\n", "// double(\"hi\") // Proper use of double\n", "// double(10) // Bad input argument!\n", "// double(\"hi\") / 10 // Inproper use of double's output!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Functions that don't return anything return type `Unit`." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "var counter = 0\n", "def increment(): Unit = {\n", " counter += 1\n", "}\n", "increment()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Scala vs. Chisel Types\n", "\n", "Recap: Module 2.2 discussed the difference between Chisel types and Scala types, for example the fact that\n", "```scala\n", "val a = Wire(UInt(4.W))\n", "a := 0.U\n", "```\n", "is legal because `0.U` is of type `UInt` (a Chisel type), whereas\n", "```scala\n", "val a = Wire(UInt(4.W))\n", "a := 0\n", "```\n", "is illegal because 0 is type `Int` (a Scala type).\n", "\n", "This is also true of `Bool`, a Chisel type which is distinct from `Boolean`.\n", "```scala\n", "val bool = Wire(Bool())\n", "val boolean: Boolean = false\n", "// legal\n", "when (bool) { ... }\n", "if (boolean) { ... }\n", "// illegal\n", "if (bool) { ... }\n", "when (boolean) { ... }\n", "```\n", "\n", "If you make a mistake and mix up `UInt` and `Int` or `Bool` and `Boolean`, the Scala compiler will generally catch it for you.\n", "This is because of Scala's static typing.\n", "At compile time, the compiler is able to distinguish between Chisel and Scala types and understand that `if ()` expects a `Boolean` and `when ()` expects a `Bool`.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Scala Type Coercion\n", "\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "### asInstanceOf\n", "\n", "`x.asInstanceOf[T]` casts the object `x` to the type `T`. It throws an exception if the given object cannot be cast to type `T`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "val x: UInt = 3.U\n", "try {\n", " println(x.asInstanceOf[Int])\n", "} catch {\n", " case e: java.lang.ClassCastException => println(\"As expected, we can't cast UInt to Int\")\n", "}\n", "\n", "// But we can cast UInt to Data since UInt inherits from Data.\n", "println(x.asInstanceOf[Data])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "### Type Casting in Chisel\n", "\n", "The code below will give an error if you try to run it without removing the comment.\n", "What's the problem?\n", "It is trying to assign a `UInt` to an `SInt`, which is illegal.\n", "\n", "Chisel has a set of type casting functions.\n", "The most general is `asTypeOf()`, which is shown below.\n", "Some chisel objects also define `asUInt()` and `asSInt()` as well as some others.\n", "\n", "If you remove the `//` from the code block below, the example should work for you.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class TypeConvertDemo extends Module {\n", " val io = IO(new Bundle {\n", " val in = Input(UInt(4.W))\n", " val out = Output(SInt(4.W))\n", " })\n", " io.out := io.in//.asTypeOf(io.out)\n", "}\n", "\n", "Driver(() => new TypeConvertDemo) { c =>\n", " new PeekPokeTester(c) {\n", " poke(c.io.in, 3)\n", " expect(c.io.out, 3)\n", " poke(c.io.in, 15)\n", " expect(c.io.out, -1)\n", " }}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# Type Matching\n", "\n", "## Match Operator\n", "Recall that in 3.1 the match operator was introduced.\n", "Type matching is especially useful when trying to write type-generic generators.\n", "The following example shows an example of a \"generator\" that can add two literals of type `UInt` or `SInt`.\n", "Later sections will talk more about writing type-generic generators.\n", "\n", "**Note: there are much better and safer ways to write type-generic generators in Scala**." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class ConstantSum(in1: Data, in2: Data) extends Module {\n", " val io = IO(new Bundle {\n", " val out = Output(in1.cloneType)\n", " })\n", " (in1, in2) match {\n", " case (x: UInt, y: UInt) => io.out := x + y\n", " case (x: SInt, y: SInt) => io.out := x + y\n", " case _ => throw new Exception(\"I give up!\")\n", " }\n", "}\n", "println(getVerilog(dut = new ConstantSum(3.U, 4.U)))\n", "println(getVerilog(dut = new ConstantSum(-3.S, 4.S)))\n", "println(getVerilog(dut = new ConstantSum(3.U, 4.S)))\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It is good to remember that Chisel types generally should not be value matched.\n", "Scala's match executes during circuit elaboration, but what you probably want is an post-elaboration comparison.\n", "The following gives a syntax error:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class InputIsZero extends Module {\n", " val io = IO(new Bundle {\n", " val in = Input(UInt(16.W))\n", " val out = Output(Bool())\n", " })\n", " io.out := (io.in match {\n", " // note that case 0.U is an error\n", " case (0.U) => true.B\n", " case _ => false.B\n", " })\n", "}\n", "println(getVerilog(new InputIsZero))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Unapply\n", "What's actually going on when you do a match?\n", "Why can you do fancy value matching with case classes like this:\n", "```scala\n", "case class Something(a: String, b: Int)\n", "val a = Something(\"A\", 3)\n", "a match {\n", " case Something(\"A\", value) => value\n", " case Something(str, 3) => 0\n", "}\n", "```\n", "\n", "As it turns out, the companion object that is created for every case class also contains an **unapply** method, in addition to an **apply** method.\n", "What is an **unapply** method?\n", "\n", "Scala unapply methods are another form of syntactic sugar that give match statements the ability to both match on types and **extract values** from those types during the matching.\n", "\n", "Let's look at the following example.\n", "For some reason, let's say that if the generator is being pipelined, the delay is `3*totalWidth`, otherwise the delay is `2*someOtherWidth`.\n", "Because case classes have **unapply** defined, we can match values inside the case class, like so:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "def delay(p: SomeGeneratorParameters): Int = p match {\n", " case sg @ SomeGeneratorParameters(_, _, true) => sg.totalWidth * 3\n", " case SomeGeneratorParameters(_, sw, false) => sw * 2\n", "}\n", "\n", "println(delay(SomeGeneratorParameters(10, 10)))\n", "println(delay(SomeGeneratorParameters(10, 10, true)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you look at the `delay` function, you should note that addition to matching on the type of each character, we are also:\n", "- Directly reference internal values of the parameters\n", "- Sometimes, are matching directly on the internal values of the parameters\n", "\n", "These are possible due to the compiler implementing an `unapply` method. Note that unapplying the case is just syntactic sugar; e.g. the following two cases examples are equivalent:\n", "```scala\n", "case p: SomeGeneratorParameters => p.sw * 2\n", "case SomeGeneratorParameters(_, sw, _) => sw * 2\n", "```\n", "\n", "In addition, there are more syntaxes and styles of matching. The following two cases are also equivalent, but the second allows you to match on internal values while still reference the parent value:\n", "```scala\n", "case SomeGeneratorParameters(_, sw, true) => sw\n", "case sg@SomeGeneratorParameters(_, sw, true) => sw\n", "```\n", "\n", "Finally, you can directly embed condition checking into match statements, as demonstrated by the third of these equivalent examples:\n", "```scala\n", "case SomeGeneratorParameters(_, sw, false) => sw * 2\n", "case s@SomeGeneratorParameters(_, sw, false) => s.sw * 2\n", "case s: SomeGeneratorParameters if s.pipelineMe => s.sw * 2\n", "```\n", "\n", "All these syntaxes are enabled by a Scala unapply method contained in a class's companion object. If you want to unapply a class but do not want to make it a case class, you can manually implement the unapply method. The following example demonstrates how one can manually implement a class's apply and unapply methods:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class Boat(val name: String, val length: Int)\n", "object Boat {\n", " def unapply(b: Boat): Option[(String, Int)] = Some((b.name, b.length))\n", " def apply(name: String, length: Int): Boat = new Boat(name, length)\n", "}\n", "\n", "def getSmallBoats(seq: Seq[Boat]): Seq[Boat] = seq.filter { b =>\n", " b match {\n", " case Boat(_, length) if length < 60 => true\n", " case Boat(_, _) => false\n", " }\n", "}\n", "\n", "val boats = Seq(Boat(\"Santa Maria\", 62), Boat(\"Pinta\", 56), Boat(\"Nina\", 50))\n", "println(getSmallBoats(boats).map(_.name).mkString(\" and \") + \" are small boats!\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Partial Functions\n", "This is a brief overview; [this guide](https://twitter.github.io/scala_school/pattern-matching-and-functional-composition.html#PartialFunction) has a more detailed overview.\n", "\n", "Partial functions are functions that are only defined on a subset of their inputs.\n", "Like an option, a partial function may not have a value for a particular input.\n", "This can be tested with `isDefinedAt(...)`.\n", "\n", "Partial functions can be chained together with `orElse`.\n", "\n", "Note that calling a `PartialFunction` with a undefined input will result in a runtime error. This can happen, for example, if the input to the `PartialFunction` is user-defined. To be more type-safe, we recommend writing functions that return an `Option` instead." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "// Helper function to make this cell a bit less tedious.\n", "def printAndAssert(cmd: String, result: Boolean, expected: Boolean): Unit = {\n", " println(s\"$cmd = $result\")\n", " assert(result == expected)\n", "}\n", "\n", "// Defined for -1, 2, 5, etc.\n", "val partialFunc1: PartialFunction[Int, String] = {\n", " case i if (i + 1) % 3 == 0 => \"Something\"\n", "}\n", "printAndAssert(\"partialFunc1.isDefinedAt(2)\", partialFunc1.isDefinedAt(2), true)\n", "printAndAssert(\"partialFunc1.isDefinedAt(5)\", partialFunc1.isDefinedAt(5), true)\n", "printAndAssert(\"partialFunc1.isDefinedAt(1)\", partialFunc1.isDefinedAt(1), false)\n", "printAndAssert(\"partialFunc1.isDefinedAt(0)\", partialFunc1.isDefinedAt(0), false)\n", "println(s\"partialFunc1(2) = ${partialFunc1(2)}\")\n", "try {\n", " println(partialFunc1(0))\n", "} catch {\n", " case e: scala.MatchError => println(\"partialFunc1(0) = can't apply PartialFunctions where they are not defined\")\n", "}\n", "\n", "// Defined for 1, 4, 7, etc.\n", "val partialFunc2: PartialFunction[Int, String] = {\n", " case i if (i + 2) % 3 == 0 => \"Something else\"\n", "}\n", "printAndAssert(\"partialFunc2.isDefinedAt(1)\", partialFunc2.isDefinedAt(1), true)\n", "printAndAssert(\"partialFunc2.isDefinedAt(0)\", partialFunc2.isDefinedAt(0), false)\n", "println(s\"partialFunc2(1) = ${partialFunc2(1)}\")\n", "try {\n", " println(partialFunc2(0))\n", "} catch {\n", " case e: scala.MatchError => println(\"partialFunc2(0) = can't apply PartialFunctions where they are not defined\")\n", "}\n", "\n", "val partialFunc3 = partialFunc1 orElse partialFunc2\n", "printAndAssert(\"partialFunc3.isDefinedAt(0)\", partialFunc3.isDefinedAt(0), false)\n", "printAndAssert(\"partialFunc3.isDefinedAt(1)\", partialFunc3.isDefinedAt(1), true)\n", "printAndAssert(\"partialFunc3.isDefinedAt(2)\", partialFunc3.isDefinedAt(2), true)\n", "printAndAssert(\"partialFunc3.isDefinedAt(3)\", partialFunc3.isDefinedAt(3), false)\n", "println(s\"partialFunc3(1) = ${partialFunc3(1)}\")\n", "println(s\"partialFunc3(2) = ${partialFunc3(2)}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# Type Safe Connections\n", "\n", "Chisel can check the type for many connections, including:\n", "* Bool/UInt to Clock\n", "\n", "For other types, Chisel will let you connect them, but may truncate/pad bits as appropriate.\n", "* Bool/UInt to Bool/UInt\n", "* Bundle to Bundle" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class Bundle1 extends Bundle {\n", " val a = UInt(8.W)\n", " override def cloneType = (new Bundle1).asInstanceOf[this.type]\n", "}\n", "\n", "class Bundle2 extends Bundle1 {\n", " val b = UInt(16.W)\n", " override def cloneType = (new Bundle2).asInstanceOf[this.type]\n", "}\n", "\n", "class BadTypeModule extends Module {\n", " val io = IO(new Bundle {\n", " val c = Input(Clock())\n", " val in = Input(UInt(2.W))\n", " val out = Output(Bool())\n", "\n", " val bundleIn = Input(new Bundle2)\n", " val bundleOut = Output(new Bundle1)\n", " })\n", " \n", " //io.out := io.c // won't work due to different types\n", "\n", " // Okay, but Chisel will truncate the input width to 1 to match the output.\n", " io.out := io.in\n", "\n", " // Compiles; Chisel will connect the common subelements of the two Bundles (in this case, 'a').\n", " io.bundleOut := io.bundleIn\n", "}\n", "\n", "println(getVerilog(new BadTypeModule))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# Type Generics\n", "Scala's generic types (also known as polymorphism) is very complicated, especially when coupling it with inheritance.\n", "\n", "This section will just get your toes wet; to understand more, check out [this tutorial](https://twitter.github.io/scala_school/type-basics.html).\n", "\n", "Classes can be polymorphic in their types. One good example are sequences, which require knowing what the type of the elements it contains." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "val seq1 = Seq(\"1\", \"2\", \"3\") // Type is Seq[String]\n", "val seq2 = Seq(1, 2, 3) // Type is Seq[Int]\n", "val seq3 = Seq(1, \"2\", true) // Type is Seq[Any]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Sometimes, the Scala compiler needs help determining a polymorphic type, which requires the user to explicitly put the type:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "//val default = Seq() // Error!\n", "val default = Seq[String]() // User must tell compiler that default is of type Seq[String]\n", "Seq(1, \"2\", true).foldLeft(default){ (strings, next) =>\n", " next match {\n", " case s: String => strings ++ Seq(s)\n", " case _ => strings\n", " }\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Functions can also be polymorphic in their input or output types. The following example defines a function that times how long it takes to run a block of code. It is parameterized based on the return type of the block of code. *Note that the `=> T` syntax encodes an anonymous function that does not have an argument list, e.g. `{ ... }` versus `{ x => ... }`.*" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "def time[T](block: => T): T = {\n", " val t0 = System.nanoTime()\n", " val result = block\n", " val t1 = System.nanoTime()\n", " val timeMillis = (t1 - t0) / 1000000.0\n", " println(s\"Block took $timeMillis milliseconds!\")\n", " result\n", "}\n", "\n", "// Adds 1 through a million\n", "val int = time { (1 to 1000000).reduce(_ + _) }\n", "println(s\"Add 1 through a million is $int\")\n", "\n", "// Finds the largest number under a million that, in hex, contains \"beef\"\n", "val string = time {\n", " (1 to 1000000).map(_.toHexString).filter(_.contains(\"beef\")).last\n", "}\n", "println(s\"The largest number under a million that has beef: $string\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Chisel Type Hierarchy\n", "To write type generic code with Chisel, it is helpful to know a bit about the type hierarchy of Chisel.\n", "\n", "`chisel3.Data` is the base class for Chisel hardware types.\n", "`UInt`, `SInt`, `Vec`, `Bundle`, etc. are all instances of `Data`.\n", "`Data` can be used in IOs and support `:=`, wires, regs, etc.\n", "\n", "Registers are a good example of polymorphic code in Chisel.\n", "Look at the implementation of `RegEnable` (a register with a `Bool` enable signal) [here](https://github.com/freechipsproject/chisel3/blob/v3.0.0/src/main/scala/chisel3/util/Reg.scala#L10).\n", "The apply function is templated for `[T <: Data]`, which means `RegEnable` will work for all Chisel hardware types.\n", "\n", "Some operations are only defined on subtypes of `Bits`, for example `+`.\n", "This is why you can add `UInt`s or `SInt`s but not `Bundle`s or `Vec`s." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Example: Type Generic ShiftRegister**
\n", "In Scala, objects and functions aren't the only things we can treat as parameters.\n", "We can also treat types as parameters.\n", "\n", "We usually need to provide a type constraint.\n", "In this case, we want to be able to put objects in a bundle, connect (:=) them, and create registers with them (RegNext).\n", "These operations cannot be done on arbitrary objects; for example wire := 3 is illegal because 3 is a Scala Int, not a Chisel UInt.\n", "If we use a type constraint to say that type T is a subclass of Data, then we can use := on any objects of type T because := is defined for all Data.\n", "\n", "Here is an implementations of a shift register that take types as a parameter.\n", "*gen* is an argument of type T that tells what width to use, for example new ShiftRegister(UInt(4.W)) is a shift register for 4-bit UInts.\n", "*gen* also allows the Scala compiler to infer the type T- you can write new ShiftRegister[UInt](UInt(4.W)) if you want to to be more specific, but the Scala compiler is smart enough to figure it out if you leave out the [UInt]." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class ShiftRegisterIO[T <: Data](gen: T, n: Int) extends Bundle {\n", " require (n >= 0, \"Shift register must have non-negative shift\")\n", " \n", " val in = Input(gen.cloneType)\n", " val out = Output(Vec(n + 1, gen.cloneType)) // + 1 because in is included in out\n", " override def cloneType: this.type = (new ShiftRegisterIO(gen, n)).asInstanceOf[this.type]\n", "}\n", "\n", "class ShiftRegister[T <: Data](gen: T, n: Int) extends Module {\n", " val io = IO(new ShiftRegisterIO(gen, n))\n", " \n", " io.out.foldLeft(io.in) { case (in, out) =>\n", " out := in\n", " RegNext(in)\n", " }\n", "}\n", "\n", "class ShiftRegisterTester[T <: Bits](c: ShiftRegister[T]) extends PeekPokeTester(c) {\n", " println(s\"Testing ShiftRegister of type ${c.io.in} and depth ${c.io.out.length}\")\n", " for (i <- 0 until 10) {\n", " poke(c.io.in, i)\n", " println(s\"$i: ${peek(c.io.out)}\")\n", " step(1)\n", " }\n", "}\n", "\n", "Driver(() => new ShiftRegister(UInt(4.W), 5)) { c => new ShiftRegisterTester(c) }\n", "Driver(() => new ShiftRegister(SInt(6.W), 3)) { c => new ShiftRegisterTester(c) }" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We generally recommend avoiding to use inheritance with type generics.\n", "It can be very tricky to do properly and can get frustrating quickly." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Type Generics with Typeclasses\n", "\n", "The example above was limited to simple operations that could be performed on any instance of `Data` such as `:=` or `RegNext()`.\n", "When generating DSP circuits, we would like to do mathematical operations like addition and multiplication.\n", "The `dsptools` library provides tools for writing type parameterized DSP generators.\n", "\n", "Here is an example of writing a multiply-accumulate module.\n", "It can be used to generate a multiply-accumulate (MAC) for `FixedPoint`, `SInt`, or even `DspComplex[T]` (the complex number type provided by `dsptools`).\n", "The syntax of the type bound is a little different because `dsptools` uses typeclasses.\n", "They are beyond the scope of this notebook.\n", "Read the `dsptools` readme and documentation for more information on using typeclasses.\n", "\n", "`T <: Data : Ring` means that `T` is a subtype of `Data` and is also a `Ring` .\n", "`Ring` is defined in `dsptools` as a number with `+` and `*` (among other operations).\n", "\n", "_An alternative to `Ring` would be `Real`, but that would not allow us to make a MAC for `DspComplex()` because complex numbers are not `Real`._\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import chisel3.experimental._\n", "import dsptools.numbers._\n", "\n", "class Mac[T <: Data : Ring](genIn : T, genOut: T) extends Module {\n", " val io = IO(new Bundle {\n", " val a = Input(genIn.cloneType)\n", " val b = Input(genIn.cloneType)\n", " val c = Input(genIn.cloneType)\n", " val out = Output(genOut.cloneType)\n", " })\n", " io.out := io.a * io.b + io.c\n", "}\n", "\n", "println(getVerilog(new Mac(UInt(4.W), UInt(6.W)) ))\n", "println(getVerilog(new Mac(SInt(4.W), SInt(6.W)) ))\n", "println(getVerilog(new Mac(FixedPoint(4.W, 3.BP), FixedPoint(6.W, 4.BP))))\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Exercise: Mac as Object**
\n", "\n", "The Mac `Module` has a small number of inputs and just one output.\n", "It might be convenient for other Chisel generators to write code like\n", "```scala\n", "val out = Mac(a, b, c)\n", "```\n", "\n", "Implement an `apply` method in the `Mac` companion object below that implements the `Mac` functionality." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "object Mac {\n", " def apply[T <: Data : Ring](a: T, b: T, c: T): T = {\n", " }\n", "}\n", "\n", "class MacTestModule extends Module {\n", " val io = IO(new Bundle {\n", " val uin = Input(UInt(4.W))\n", " val uout = Output(UInt())\n", " val sin = Input(SInt(4.W))\n", " val sout = Output(SInt())\n", " //val fin = Input(FixedPoint(16.W, 12.BP))\n", " //val fout = Output(FixedPoint())\n", " })\n", " // for each IO pair, do out = in * in + in\n", " io.uout := Mac(io.uin, io.uin, io.uin)\n", " io.sout := Mac(io.sin, io.sin, io.sin)\n", " //io.fout := Mac(io.fin, io.fin, io.fin)\n", "}\n", "println(getVerilog(new MacTestModule))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "\n", "
\n", "
\n",
    "object Mac {\n",
    "    def apply\\[T <: Data : Ring\\](a: T, b: T, c: T): T = {\n",
    "        // you can also instantiate the Mac from above and connect the IOs to arguments\n",
    "        a * b + c\n",
    "    }\n",
    "}\n",
    "\n",
    "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Exercise: Integrator**
\n", "Implement an integrator as pictured below. $n_1$ is the width of `genReg` and $n_2$ is the width of `genIn`.\n", "\n", "Don't forget that `Reg`, `RegInit`, `RegNext`, `RegEnable`, etc. are templated for types `T <: Data`.\n", "\n", "\"Integrator\"" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class Integrator[T <: Data : Ring](genIn: T, genReg: T) extends Module {\n", " val io = IO(new Bundle {\n", " val in = Input(genIn.cloneType)\n", " val out = Output(genReg.cloneType)\n", " })\n", " \n", " val reg = RegInit(genReg, Ring[T].zero) // init to zero\n", " reg := reg + io.in\n", " io.out := reg\n", "}\n", "\n", "class IntegratorSIntTester(c: Integrator[SInt]) extends PeekPokeTester(c) {\n", " poke(c.io.in, 3)\n", " expect(c.io.out, 0)\n", " step(1)\n", " poke(c.io.in, -4)\n", " expect(c.io.out, 3)\n", " step(1)\n", " poke(c.io.in, 6)\n", " expect(c.io.out, -1)\n", " step(1)\n", " expect(c.io.out, 5)\n", "}\n", "\n", "chisel3.iotesters.Driver(() => new Integrator(SInt(4.W), SInt(8.W))) { c => new IntegratorSIntTester(c) }" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "\n", "
\n", "
\n",
    "\n",
    "class Integrator\\[T <: Data : Ring\\](genIn: T, genReg: T) extends Module {\n",
    "    val io = IO(new Bundle {\n",
    "        val in  = Input(genIn.cloneType)\n",
    "        val out = Output(genReg.cloneType)\n",
    "    })\n",
    "    \n",
    "    val reg = RegInit(genReg, Ring[T].zero) // init to zero\n",
    "    reg := reg + io.in\n",
    "    io.out := reg\n",
    "}\n",
    "\n",
    "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# Creating a Custom Type\n", "\n", "One of the things that makes Chisel powerful is its extensibility.\n", "You can add your own types that have their own operations and representations that are tailored to your application.\n", "This section will introduce ways to make custom types." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Example: DspComplex**
\n", "`DspComplex` is a custom data type defined in **dsptools** [here](https://github.com/ucb-bar/dsptools/blob/v1.0.0/src/main/scala/dsptools/numbers/chisel_concrete/DspComplex.scala#L59).\n", "The key line to understand is this:\n", "```scala\n", "class DspComplex[T <: Data:Ring](val real: T, val imag: T) extends Bundle { ... }\n", "```\n", "\n", "`DspComplex` is a type-generic container.\n", "That means the real and imaginary parts of a complex number can be any type as long as they satisfy the type constraints, given by `T <: Data : Ring`.\n", "\n", "`T <: Data` means `T` is a subtype of `chisel3.Data`, the base type for Chisel objects.\n", "This means that `DspComplex` only works for objects that are Chisel types and not arbitrary Scala types.\n", "\n", "`T : Ring` means that a Ring typeclass implementation for `T` exists.\n", "`Ring` typeclasses define `+` and `*` operators as well as additive and multiplicative identities (see [this Wikipedia article](https://en.wikipedia.org/wiki/Ring_(mathematics)) for details about rings).\n", "**dsptools** defines typeclasses for commonly used Chisel types [here](https://github.com/ucb-bar/dsptools/tree/v1.0.0/src/main/scala/dsptools/numbers/chisel_types).\n", "\n", "**dsptools** also defines a `Ring` typeclass for `DspComplex`, so we can reuse our MAC generator with complex numbers:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "println(getVerilog(new Mac(DspComplex(SInt(4.W), SInt(4.W)), DspComplex(SInt(6.W), SInt(6.W))) ))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Exercise: Sign-magnitude Numbers**
\n", "Suppose you wanted to use a sign-magnitude representation and want to reuse all of your DSP generators.\n", "Typeclasses enable this kind of ad-hoc polymorphism.\n", "The following example gives the beggining of an implementation of a SignMagnitude type as well as an implementation of a `Ring` typeclass that will allow the type to be used with the Mac generator.\n", "\n", "Fill in implementations for `+` and `*`.\n", "You should pattern them after the implementation for `unary_-()`.\n", "The next block contains a test that checks the correctness of a `Mac` that uses `SignMagnitude`." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class SignMagnitude(val magnitudeWidth: Option[Int] = None) extends Bundle {\n", " val sign = Bool()\n", " val magnitude = magnitudeWidth match {\n", " case Some(w) => UInt(w.W)\n", " case None => UInt()\n", " }\n", " def +(that: SignMagnitude): SignMagnitude = {\n", " // Implement this!\n", " }\n", " def -(that: SignMagnitude): SignMagnitude = {\n", " this.+(-that)\n", " }\n", " def unary_-(): SignMagnitude = {\n", " val result = Wire(new SignMagnitude())\n", " result.sign := !this.sign\n", " result.magnitude := this.magnitude\n", " result\n", " }\n", " def *(that: SignMagnitude): SignMagnitude = {\n", " // Implement this!\n", " }\n", " override def cloneType: this.type = new SignMagnitude(magnitudeWidth).asInstanceOf[this.type]\n", "}\n", "trait SignMagnitudeRing extends Ring[SignMagnitude] {\n", " def plus(f: SignMagnitude, g: SignMagnitude): SignMagnitude = {\n", " f + g\n", " }\n", " def times(f: SignMagnitude, g: SignMagnitude): SignMagnitude = {\n", " f * g\n", " }\n", " def one: SignMagnitude = {\n", " val one = Wire(new SignMagnitude(Some(1)))\n", " one.sign := false.B\n", " one.magnitude := 1.U\n", " one\n", " }\n", " def zero: SignMagnitude = {\n", " val zero = Wire(new SignMagnitude(Some(0)))\n", " zero.sign := false.B\n", " zero.magnitude := 0.U\n", " zero\n", " }\n", " def negate(f: SignMagnitude): SignMagnitude = {\n", " -f\n", " }\n", " \n", " // Leave unimplemented for this example\n", " def minusContext(f: SignMagnitude, g: SignMagnitude): SignMagnitude = ???\n", " def negateContext(f: SignMagnitude): SignMagnitude = ???\n", " def plusContext(f: SignMagnitude,g: SignMagnitude): SignMagnitude = ???\n", " def timesContext(f: SignMagnitude,g: SignMagnitude): SignMagnitude = ???\n", "}\n", "implicit object SignMagnitudeRingImpl extends SignMagnitudeRing" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class SignMagnitudeMACTester(c: Mac[SignMagnitude]) extends PeekPokeTester(c) {\n", " // 3 * 3 + 2 = 11\n", " poke(c.io.a.sign, 0)\n", " poke(c.io.a.magnitude, 3)\n", " poke(c.io.b.sign, 0)\n", " poke(c.io.b.magnitude, 3)\n", " poke(c.io.c.sign, 0)\n", " poke(c.io.c.magnitude, 2)\n", " expect(c.io.out.sign, 0)\n", " expect(c.io.out.magnitude, 11)\n", " // 3 * 3 - 2 = 7\n", " poke(c.io.c.sign, 1)\n", " expect(c.io.out.sign, 0)\n", " expect(c.io.out.magnitude, 7)\n", " // 3 * (-3) - 2 = -11\n", " poke(c.io.b.sign, 1)\n", " expect(c.io.out.sign, 1)\n", " expect(c.io.out.magnitude, 11)\n", "}\n", "val works = iotesters.Driver(() => new Mac(new SignMagnitude(Some(4)), new SignMagnitude(Some(5)))) {\n", " c => new SignMagnitudeMACTester(c)\n", "}\n", "assert(works) // Scala Code: if works == false, will throw an error\n", "println(\"SUCCESS!!\") // Scala Code: if we get here, our tests passed!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Look at the verilog to see if the output looks reasonable:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "println(getVerilog(new Mac(new SignMagnitude(Some(4)), new SignMagnitude(Some(5)))))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`SignMagnitude` even works with `DspComplex`!" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "println(getVerilog(new Mac(DspComplex(new SignMagnitude(Some(4)), new SignMagnitude(Some(4))), DspComplex(new SignMagnitude(Some(5)), new SignMagnitude(Some(5))))))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "\n", "
\n", "
\n",
    "    // implementations for class SignMagnitude\n",
    "\n",
    "    def +(that: SignMagnitude): SignMagnitude = {\n",
    "      val result = Wire(new SignMagnitude())\n",
    "      val signsTheSame = this.sign === that.sign\n",
    "      when (signsTheSame) {\n",
    "        result.sign      := this.sign\n",
    "        result.magnitude := this.magnitude + that.magnitude\n",
    "      } .otherwise {\n",
    "        when (this.magnitude > that.magnitude) {\n",
    "          result.sign      := this.sign\n",
    "          result.magnitude := this.magnitude - that.magnitude\n",
    "        } .otherwise {\n",
    "          result.sign      := that.sign\n",
    "          result.magnitude := that.magnitude - this.magnitude\n",
    "        }   \n",
    "      }   \n",
    "      result\n",
    "    }\n",
    "    def \\*(that: SignMagnitude): SignMagnitude = {\n",
    "        val result = Wire(new SignMagnitude())\n",
    "        result.sign := this.sign ^ that.sign\n",
    "        result.magnitude := this.magnitude \\* that.magnitude\n",
    "        result\n",
    "    }\n",
    "\n",
    "\n",
    "
" ] } ], "metadata": { "kernelspec": { "display_name": "Scala", "language": "scala", "name": "scala" }, "language_info": { "codemirror_mode": "text/x-scala", "file_extension": ".scala", "mimetype": "text/x-scala", "name": "scala211", "nbconvert_exporter": "script", "pygments_lexer": "scala", "version": "2.11.11" } }, "nbformat": 4, "nbformat_minor": 1 }