{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\"Chisel" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# WARNING. THIS FILE IS NOT PART OF THE BOOTCAMP \n", "# TEACHING STEPS. IT IS A MID TO END LEVEL EXAMPLE\n", "# OF THE THINGS YOU WILL LEARN. IF YOU ARE DOING THE\n", "# BOOTCAMP TO LEARN CHISEL DON'T START HERE\n", "# START AT [Introduction to Scala](1_intro_to_scala.ipynb)\n", "\n", "# Chisel Demo\n", "**Next: [Introduction to Scala](1_intro_to_scala.ipynb)**\n", "\n", "Welcome! Perhaps you're an interested student who heard the name \"Chisel\" tossed about, or maybe you're a seasoned hardware design veteran who has been tasked by your manager to explore Chisel as a new HDL alternative. Either way if you are new to Chisel, you want to figure out as fast as possible what all the fuss is about. Look no futher - let's see what Chisel has to offer!\n", "\n", "## Setup\n", "Before we start, we need to download and imports the dependencies needed for the demo. \n", "\n", "**Please run the following two cell blocks by either pressing SHIFT+ENTER on your keyboard or the Run button in the menu**." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": 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": {}, "outputs": [], "source": [ "import chisel3._\n", "import chisel3.util._\n", "import chisel3.iotesters.{ChiselFlatSpec, Driver, PeekPokeTester}" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "## Hardware Generators: Type-Safe Meta-Programming for RTL\n", "\n", "All hardware description languages support writing single instances of an RTL design - Chisel is no different.\n", "In fact, most Verilog/VHDL digital logic designs can be directly transcribed into Chisel!\n", "While Chisel provides other awesome features that we will get to, we want to emphasize that users switching to Chisel will retain the exact same degree of control over their design as any other hardware language.\n", "\n", "Take the following example of a 3-point moving average implemented in the style of a FIR filter.\n", "\n", "\n", "\n", "Chisel provides similar base primitives as synthesizable Verilog and *can* be used as such! Run next cell to declare our Chisel module." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "// 3-point moving average implemented in the style of a FIR filter\n", "class MovingAverage3(bitWidth: Int) extends Module {\n", " val io = IO(new Bundle {\n", " val in = Input(UInt(bitWidth.W))\n", " val out = Output(UInt(bitWidth.W))\n", " })\n", "\n", " val z1 = RegNext(io.in) // Create a register whose input is connected to the argument io.in\n", " val z2 = RegNext(z1) // Create a register whose input is connected to the argument z1\n", "\n", " io.out := (io.in * 1.U) + (z1 * 1.U) + (z2 * 1.U) // `1.U` is an unsigned literal with value 1\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "After defining `class MovingAverage3`, let's instantiate it and take a look at its structure:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "// same 3-point moving average filter as before\n", "visualize(() => new MovingAverage3(8))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this visualization of the Chisel instance, the inputs on the left, and the z1 and z2 registers in gold. Both registers and io_in are multiplied by their coefficients and which are then added successively. The `tail` and `bits` elements are used to keep the additions from growing.\n", "\n", "You may now ask: \"Oh well and good - you can do stuff in Verilog in Chisel, but then why would I want to use Chisel?\"\n", "\n", "We are so glad you asked! The real power of Chisel comes from the ability to create **generators, not instances**. Suppose instead of only a `MovingAverage3` module, we wanted to create a generic `FIRFilter` module that is parameterized by a list of coefficients.\n", "\n", "Below we have rewritten `MovingAverage3` to accept into a sequence of coefficients. The number of coefficients will determine the size of the filter." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "// Generalized FIR filter parameterized by the convolution coefficients\n", "class FirFilter(bitWidth: Int, coeffs: Seq[UInt]) extends Module {\n", " val io = IO(new Bundle {\n", " val in = Input(UInt(bitWidth.W))\n", " val out = Output(UInt())\n", " })\n", " // Create the serial-in, parallel-out shift register\n", " val zs = Reg(Vec(coeffs.length, UInt(bitWidth.W)))\n", " zs(0) := io.in\n", " for (i <- 1 until coeffs.length) {\n", " zs(i) := zs(i-1)\n", " }\n", "\n", " // Do the multiplies\n", " val products = VecInit.tabulate(coeffs.length)(i => zs(i) * coeffs(i))\n", "\n", " // Sum up the products\n", " io.out := products.reduce(_ +& _)\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now by changing our `coeffs` parameters during instantiation, our `FIRFilter` module can be used to instantiate an endless number of different hardware modules! Below we create three different instances of `FIRFiler`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "// same 3-point moving average filter as before\n", "visualize(() => new FirFilter(8, Seq(1.U, 1.U, 1.U)))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "// 1-cycle delay as a FIR filter\n", "visualize(() => new FirFilter(8, Seq(0.U, 1.U)))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "// 5-point FIR filter with a triangle impulse response\n", "visualize(() => new FirFilter(8, Seq(1.U, 2.U, 3.U, 2.U, 1.U)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Without this powerful parameterization, we would need many more module definitions, likely one for each of these FIR filters. Ideally, we want our generators to be (1) composable, (2) powerful, and (3) enable fine-grained control over the generated design.\n", "\n", "The benefits of Chisel are in how you use it, not in the language itself.\n", "If you decide to write instances instead of generators, you will see fewer advantages of Chisel over Verilog.\n", "But if you take the time to learn how to write generators, then the power of Chisel will become apparent and you will realize you can never go back to writing Verilog.\n", "Learning to write generators is difficult, but we hope this tutorial will pave the way for you to become a better hardware designer, programmer, and thinker!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# All done!\n", "\n", "[Return to the top.](#top)" ] } ], "metadata": { "anaconda-cloud": {}, "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.8" } }, "nbformat": 4, "nbformat_minor": 2 }