{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\"Chisel" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Module 3 Interlude: Chisel Standard Library\n", "**Prev: [Generators: Collections](3.2_collections.ipynb)**
\n", "**Next: [Higher-Order Functions](3.3_higher-order_functions.ipynb)**\n", "\n", "## Motivation\n", "Chisel is all about re-use, so it only makes sense to provide a standard library of interfaces (encouraging interoperability of RTL) and generators for commonly-used hardware blocks.\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.iotesters.{ChiselFlatSpec, Driver, PeekPokeTester}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# The Cheatsheet\n", "The [Chisel3 cheatsheet](https://github.com/freechipsproject/chisel-cheatsheet/releases/latest/download/chisel_cheatsheet.pdf) contains a summary of all the major hardware construction APIs, including some of the standard library utilities that we'll introduce below.\n", "\n", "# Decoupled: A Standard Ready-Valid Interface\n", "One of the commonly used interfaces provided by Chisel is `DecoupledIO`, providing a ready-valid interface for transferring data. The idea is that the source drives the `bits` signal with the data to be transferred and the `valid` signal when there is data to be transferred. The sink drives the `ready` signal when it is ready to accept data, and data is considered transferred when both `ready` and `valid` are asserted on a cycle.\n", "\n", "This provides a flow control mechanism in both directions for data transfer, including a backpressure mechanism.\n", "\n", "Note: `ready` and `valid` should not be combinationally coupled, otherwise this may result in unsynthesizable combinational loops. `ready` should only be dependent on whether the sink is able to receive data, and `valid` should only be dependent on whether the source has data. Only after the transaction (on the next clock cycle) should the values update.\n", "\n", "Any Chisel data can be wrapped in a `DecoupledIO` (used as the `bits` field) as follows:\n", "\n", "```scala\n", "val myChiselData = UInt(8.W)\n", "// or any Chisel data type, such as Bool(), SInt(...), or even custom Bundles\n", "val myDecoupled = Decoupled(myChiselData)\n", "```\n", "\n", "The above creates a new `DecoupledIO` Bundle with fields\n", "- `valid`: Output(Bool)\n", "- `ready`: Input(Bool)\n", "- `bits`: Output(UInt(8.W))\n", "___\n", "\n", "The rest of the section will be structured somewhat differently from the ones before: instead of giving you coding exercises, we're going to give some code examples and testcases that print the circuit state. Try to predict what will be printed before just running the tests.\n", "\n", "## Queues\n", "\n", "`Queue` creates a FIFO (first-in, first-out) queue with Decoupled interfaces on both sides, allowing backpressure. Both the data type and number of elements are configurable." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Driver(() => new Module {\n", " // Example circuit using a Queue\n", " val io = IO(new Bundle {\n", " val in = Flipped(Decoupled(UInt(8.W)))\n", " val out = Decoupled(UInt(8.W))\n", " })\n", " val queue = Queue(io.in, 2) // 2-element queue\n", " io.out <> queue\n", " }) { c => new PeekPokeTester(c) {\n", " // Example testsequence showing the use and behavior of Queue\n", " poke(c.io.out.ready, 0)\n", " poke(c.io.in.valid, 1) // Enqueue an element\n", " poke(c.io.in.bits, 42)\n", " println(s\"Starting:\")\n", " println(s\"\\tio.in: ready=${peek(c.io.in.ready)}\")\n", " println(s\"\\tio.out: valid=${peek(c.io.out.valid)}, bits=${peek(c.io.out.bits)}\")\n", " step(1)\n", " \n", " poke(c.io.in.valid, 1) // Enqueue another element\n", " poke(c.io.in.bits, 43)\n", " // What do you think io.out.valid and io.out.bits will be?\n", " println(s\"After first enqueue:\")\n", " println(s\"\\tio.in: ready=${peek(c.io.in.ready)}\")\n", " println(s\"\\tio.out: valid=${peek(c.io.out.valid)}, bits=${peek(c.io.out.bits)}\")\n", " step(1)\n", " \n", " poke(c.io.in.valid, 1) // Read a element, attempt to enqueue\n", " poke(c.io.in.bits, 44)\n", " poke(c.io.out.ready, 1)\n", " // What do you think io.in.ready will be, and will this enqueue succeed, and what will be read?\n", " println(s\"On first read:\")\n", " println(s\"\\tio.in: ready=${peek(c.io.in.ready)}\")\n", " println(s\"\\tio.out: valid=${peek(c.io.out.valid)}, bits=${peek(c.io.out.bits)}\")\n", " step(1)\n", " \n", " poke(c.io.in.valid, 0) // Read elements out\n", " poke(c.io.out.ready, 1)\n", " // What do you think will be read here?\n", " println(s\"On second read:\")\n", " println(s\"\\tio.in: ready=${peek(c.io.in.ready)}\")\n", " println(s\"\\tio.out: valid=${peek(c.io.out.valid)}, bits=${peek(c.io.out.bits)}\")\n", " step(1)\n", " \n", " // Will a third read produce anything?\n", " println(s\"On third read:\")\n", " println(s\"\\tio.in: ready=${peek(c.io.in.ready)}\")\n", " println(s\"\\tio.out: valid=${peek(c.io.out.valid)}, bits=${peek(c.io.out.bits)}\")\n", " step(1)\n", "} }" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Arbiters\n", "Arbiters routes data from _n_ `DecoupledIO` sources to one `DecoupledIO` sink, given a prioritization.\n", "There are two types included in Chisel:\n", "- `Arbiter`: prioritizes lower-index producers\n", "- `RRArbiter`: runs in round-robin order\n", "\n", "Note that Arbiter routing is implemented in combinational logic.\n", "\n", "The below example will demonstrate the use of the priority arbiter (which you will also implement in the next section):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Driver(() => new Module {\n", " // Example circuit using a priority arbiter\n", " val io = IO(new Bundle {\n", " val in = Flipped(Vec(2, Decoupled(UInt(8.W))))\n", " val out = Decoupled(UInt(8.W))\n", " })\n", " // Arbiter doesn't have a convenience constructor, so it's built like any Module\n", " val arbiter = Module(new Arbiter(UInt(8.W), 2)) // 2 to 1 Priority Arbiter\n", " arbiter.io.in <> io.in\n", " io.out <> arbiter.io.out\n", " }) { c => new PeekPokeTester(c) {\n", " poke(c.io.in(0).valid, 0)\n", " poke(c.io.in(1).valid, 0)\n", " println(s\"Start:\")\n", " println(s\"\\tin(0).ready=${peek(c.io.in(0).ready)}, in(1).ready=${peek(c.io.in(1).ready)}\")\n", " println(s\"\\tout.valid=${peek(c.io.out.valid)}, out.bits=${peek(c.io.out.bits)}\")\n", " poke(c.io.in(1).valid, 1) // Valid input 1\n", " poke(c.io.in(1).bits, 42)\n", " // What do you think the output will be?\n", " println(s\"valid input 1:\")\n", " println(s\"\\tin(0).ready=${peek(c.io.in(0).ready)}, in(1).ready=${peek(c.io.in(1).ready)}\")\n", " println(s\"\\tout.valid=${peek(c.io.out.valid)}, out.bits=${peek(c.io.out.bits)}\")\n", " poke(c.io.in(0).valid, 1) // Valid inputs 0 and 1\n", " poke(c.io.in(0).bits, 43)\n", " // What do you think the output will be? Which inputs will be ready?\n", " println(s\"valid inputs 0 and 1:\")\n", " println(s\"\\tin(0).ready=${peek(c.io.in(0).ready)}, in(1).ready=${peek(c.io.in(1).ready)}\")\n", " println(s\"\\tout.valid=${peek(c.io.out.valid)}, out.bits=${peek(c.io.out.bits)}\")\n", " poke(c.io.in(1).valid, 0) // Valid input 0\n", " // What do you think the output will be?\n", " println(s\"valid input 0:\")\n", " println(s\"\\tin(0).ready=${peek(c.io.in(0).ready)}, in(1).ready=${peek(c.io.in(1).ready)}\")\n", " println(s\"\\tout.valid=${peek(c.io.out.valid)}, out.bits=${peek(c.io.out.bits)}\")\n", "} }" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Misc Function Blocks\n", "Chisel Utils has some helpers that perform stateless functions.\n", "\n", "## Bitwise Utilities\n", "### PopCount\n", "PopCount returns the number of high (1) bits in the input as a `UInt`.\n", "\n", "### Reverse\n", "Reverse returns the bit-reversed input." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Driver(() => new Module {\n", " // Example circuit using Reverse\n", " val io = IO(new Bundle {\n", " val in = Input(UInt(8.W))\n", " val out = Output(UInt(8.W))\n", " })\n", " io.out := PopCount(io.in)\n", " }) { c => new PeekPokeTester(c) {\n", " // Integer.parseInt is used create an Integer from a binary specification\n", " poke(c.io.in, Integer.parseInt(\"00000000\", 2))\n", " println(s\"in=0b${peek(c.io.in).toInt.toBinaryString}, out=${peek(c.io.out)}\")\n", " \n", " poke(c.io.in, Integer.parseInt(\"00001111\", 2))\n", " println(s\"in=0b${peek(c.io.in).toInt.toBinaryString}, out=${peek(c.io.out)}\")\n", " \n", " poke(c.io.in, Integer.parseInt(\"11001010\", 2))\n", " println(s\"in=0b${peek(c.io.in).toInt.toBinaryString}, out=${peek(c.io.out)}\")\n", " \n", " poke(c.io.in, Integer.parseInt(\"11111111\", 2))\n", " println(s\"in=0b${peek(c.io.in).toInt.toBinaryString}, out=${peek(c.io.out)}\")\n", "} }" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Driver(() => new Module {\n", " // Example circuit using Reverse\n", " val io = IO(new Bundle {\n", " val in = Input(UInt(8.W))\n", " val out = Output(UInt(8.W))\n", " })\n", " io.out := Reverse(io.in)\n", " }) { c => new PeekPokeTester(c) {\n", " // Integer.parseInt is used create an Integer from a binary specification\n", " poke(c.io.in, Integer.parseInt(\"01010101\", 2))\n", " println(s\"in=0b${peek(c.io.in).toInt.toBinaryString}, out=0b${peek(c.io.out).toInt.toBinaryString}\")\n", " \n", " poke(c.io.in, Integer.parseInt(\"00001111\", 2))\n", " println(s\"in=0b${peek(c.io.in).toInt.toBinaryString}, out=0b${peek(c.io.out).toInt.toBinaryString}\")\n", " \n", " poke(c.io.in, Integer.parseInt(\"11110000\", 2))\n", " println(s\"in=0b${peek(c.io.in).toInt.toBinaryString}, out=0b${peek(c.io.out).toInt.toBinaryString}\")\n", " \n", " poke(c.io.in, Integer.parseInt(\"11001010\", 2))\n", " println(s\"in=0b${peek(c.io.in).toInt.toBinaryString}, out=0b${peek(c.io.out).toInt.toBinaryString}\")\n", "} }" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## OneHot encoding utilities\n", "OneHot is an encoding of integers where there is one wire for each value, and exactly one wire is high. This allows the efficient creation of some functions, for example muxes. However, behavior may be undefined if the one-wire-high condition is not held.\n", "\n", "The below two functions provide conversion between binary (`UInt`) and OneHot encodings, and are inverses of each other:\n", "- UInt to OneHot: `UIntToOH`\n", "- OneHot to UInt: `OHToUInt`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Driver(() => new Module {\n", " // Example circuit using UIntToOH\n", " val io = IO(new Bundle {\n", " val in = Input(UInt(4.W))\n", " val out = Output(UInt(16.W))\n", " })\n", " io.out := UIntToOH(io.in)\n", " }) { c => new PeekPokeTester(c) {\n", " poke(c.io.in, 0)\n", " println(s\"in=${peek(c.io.in)}, out=0b${peek(c.io.out).toInt.toBinaryString}\")\n", "\n", " poke(c.io.in, 1)\n", " println(s\"in=${peek(c.io.in)}, out=0b${peek(c.io.out).toInt.toBinaryString}\")\n", " \n", " poke(c.io.in, 8)\n", " println(s\"in=${peek(c.io.in)}, out=0b${peek(c.io.out).toInt.toBinaryString}\")\n", " \n", " poke(c.io.in, 15)\n", " println(s\"in=${peek(c.io.in)}, out=0b${peek(c.io.out).toInt.toBinaryString}\")\n", "} }" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Driver(() => new Module {\n", " // Example circuit using OHToUInt\n", " val io = IO(new Bundle {\n", " val in = Input(UInt(16.W))\n", " val out = Output(UInt(4.W))\n", " })\n", " io.out := OHToUInt(io.in)\n", " }) { c => new PeekPokeTester(c) {\n", " poke(c.io.in, Integer.parseInt(\"0000 0000 0000 0001\".replace(\" \", \"\"), 2))\n", " println(s\"in=0b${peek(c.io.in).toInt.toBinaryString}, out=${peek(c.io.out)}\")\n", "\n", " poke(c.io.in, Integer.parseInt(\"0000 0000 1000 0000\".replace(\" \", \"\"), 2))\n", " println(s\"in=0b${peek(c.io.in).toInt.toBinaryString}, out=${peek(c.io.out)}\")\n", " \n", " poke(c.io.in, Integer.parseInt(\"1000 0000 0000 0001\".replace(\" \", \"\"), 2))\n", " println(s\"in=0b${peek(c.io.in).toInt.toBinaryString}, out=${peek(c.io.out)}\")\n", " \n", " // Some invalid inputs:\n", " // None high\n", " poke(c.io.in, Integer.parseInt(\"0000 0000 0000 0000\".replace(\" \", \"\"), 2))\n", " println(s\"in=0b${peek(c.io.in).toInt.toBinaryString}, out=${peek(c.io.out)}\")\n", " \n", " // Multiple high\n", " poke(c.io.in, Integer.parseInt(\"0001 0100 0010 0000\".replace(\" \", \"\"), 2))\n", " println(s\"in=0b${peek(c.io.in).toInt.toBinaryString}, out=${peek(c.io.out)}\")\n", "} }" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Muxes\n", "These muxes take in a list of values with select signals, and output the value associated with the lowest-index select signal.\n", "\n", "These can either take a list of (select: Bool, value: Data) tuples, or corresponding lists of selects and values as arguments. For simplicity, the examples below only demonstrate the second form.\n", "\n", "### Priority Mux\n", "A `PriorityMux` outputs the value associated with the lowest-index asserted select signal.\n", "\n", "### OneHot Mux\n", "An `Mux1H` provides an efficient implementation when it is guaranteed that exactly one of the select signals will be high. Behavior is undefined if the assumption is not true." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Driver(() => new Module {\n", " // Example circuit using PriorityMux\n", " val io = IO(new Bundle {\n", " val in_sels = Input(Vec(2, Bool()))\n", " val in_bits = Input(Vec(2, UInt(8.W)))\n", " val out = Output(UInt(8.W))\n", " })\n", " io.out := PriorityMux(io.in_sels, io.in_bits)\n", " }) { c => new PeekPokeTester(c) {\n", " poke(c.io.in_bits(0), 10)\n", " poke(c.io.in_bits(1), 20)\n", " \n", " // Select higher index only\n", " poke(c.io.in_sels(0), 0)\n", " poke(c.io.in_sels(1), 1)\n", " println(s\"in_sels=${peek(c.io.in_sels)}, out=${peek(c.io.out)}\")\n", " \n", " // Select both - arbitration needed\n", " poke(c.io.in_sels(0), 1)\n", " poke(c.io.in_sels(1), 1)\n", " println(s\"in_sels=${peek(c.io.in_sels)}, out=${peek(c.io.out)}\")\n", " \n", " // Select lower index only\n", " poke(c.io.in_sels(0), 1)\n", " poke(c.io.in_sels(1), 0)\n", " println(s\"in_sels=${peek(c.io.in_sels)}, out=${peek(c.io.out)}\")\n", "} }" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Driver(() => new Module {\n", " // Example circuit using Mux1H\n", " val io = IO(new Bundle {\n", " val in_sels = Input(Vec(2, Bool()))\n", " val in_bits = Input(Vec(2, UInt(8.W)))\n", " val out = Output(UInt(8.W))\n", " })\n", " io.out := Mux1H(io.in_sels, io.in_bits)\n", " }) { c => new PeekPokeTester(c) {\n", " poke(c.io.in_bits(0), 10)\n", " poke(c.io.in_bits(1), 20)\n", " \n", " // Select index 1\n", " poke(c.io.in_sels(0), 0)\n", " poke(c.io.in_sels(1), 1)\n", " println(s\"in_sels=${peek(c.io.in_sels)}, out=${peek(c.io.out)}\")\n", " \n", " // Select index 0\n", " poke(c.io.in_sels(0), 1)\n", " poke(c.io.in_sels(1), 0)\n", " println(s\"in_sels=${peek(c.io.in_sels)}, out=${peek(c.io.out)}\")\n", " \n", " // Select none (invalid)\n", " poke(c.io.in_sels(0), 0)\n", " poke(c.io.in_sels(1), 0)\n", " println(s\"in_sels=${peek(c.io.in_sels)}, out=${peek(c.io.out)}\")\n", " \n", " // Select both (invalid)\n", " poke(c.io.in_sels(0), 1)\n", " poke(c.io.in_sels(1), 1)\n", " println(s\"in_sels=${peek(c.io.in_sels)}, out=${peek(c.io.out)}\")\n", "} }" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "## Counter\n", "`Counter` is a counter that can be incremented once every cycle, up to some specified limit, at which point it overflows. Note that it is **not** a Module, and its value is accessible." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Driver(() => new Module {\n", " // Example circuit using Mux1H\n", " val io = IO(new Bundle {\n", " val count = Input(Bool())\n", " val out = Output(UInt(2.W))\n", " })\n", " val counter = Counter(3) // 3-count Counter (outputs range [0...2])\n", " when(io.count) {\n", " counter.inc()\n", " }\n", " io.out := counter.value\n", " }) { c => new PeekPokeTester(c) {\n", " poke(c.io.count, 1)\n", " println(s\"start: counter value=${peek(c.io.out)}\")\n", " \n", " step(1)\n", " println(s\"step 1: counter value=${peek(c.io.out)}\")\n", " \n", " step(1)\n", " println(s\"step 2: counter value=${peek(c.io.out)}\")\n", " \n", " poke(c.io.count, 0)\n", " step(1)\n", " println(s\"step without increment: counter value=${peek(c.io.out)}\")\n", " \n", " poke(c.io.count, 1)\n", " step(1)\n", " println(s\"step again: counter value=${peek(c.io.out)}\")\n", "} }" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# You're 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": "scala211", "nbconvert_exporter": "script", "pygments_lexer": "scala", "version": "2.11.11" } }, "nbformat": 4, "nbformat_minor": 1 }