{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\"Chisel" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Module 2.4: Sequential Logic\n", "**Prev: [Control Flow](2.3_control_flow.ipynb)**
\n", "**Next: [FIR Filter](2.5_exercise.ipynb)**\n", "\n", "## Motivation\n", "You can't write any meaningful digital logic without state. You can't write any meaningful digital logic without state. You can't write any meaningful digital logic....\n", "\n", "Get it? Because without storing intermediate results, you can't get anywhere.\n", "\n", "Ok, that bad joke aside, this module will describe how to express common sequential patterns in Chisel. By the end of the module, you should be able to implement and test a shift register in Chisel.\n", "\n", "It's important to emphasize that this section will probably not dramatically impress you. Chisel's power is not in new sequential logic patterns, but in the parameterization of a design. Before we demonstrate that capability, we have to learn what these sequential patterns are. Thus, this section will show you that Chisel can do pretty much what Verilog can do - you just need to learn the Chisel syntax.\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", "# Registers\n", "The basic stateful element in Chisel is the register, denoted `Reg`.\n", "A `Reg` holds its output value until the rising edge of its clock, at which time it takes on the value of its input.\n", "By default, every Chisel `Module` has an implicit clock that is used by every register in the design.\n", "This saves you from always specifying the same clock all over your code.\n", "\n", "**Example: Using a Register**
\n", "The following code block implements a module that takes the input, adds 1 to it, and connects it as the input of a register.\n", "*Note: The implicit clock can be overridden for multi-clock designs. See the appendix for an example.*" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class RegisterModule extends Module {\n", " val io = IO(new Bundle {\n", " val in = Input(UInt(12.W))\n", " val out = Output(UInt(12.W))\n", " })\n", " \n", " val register = Reg(UInt(12.W))\n", " register := io.in + 1.U\n", " io.out := register\n", "}\n", "\n", "test(new RegisterModule) { c =>\n", " for (i <- 0 until 100) {\n", " c.io.in.poke(i.U)\n", " c.clock.step(1)\n", " c.io.out.expect((i + 1).U)\n", " }\n", "}\n", "println(\"SUCCESS!!\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The register is created by calling `Reg(tpe)`, where `tpe` is a variable that encodes the type of register we want.\n", "In this example, `tpe` is a 12-bit `UInt`.\n", "\n", "Look at what the tester above is doing.\n", "Between calls to `poke()` and `expect`, there is a call to `step(1)`.\n", "This tells the test harness to tick the clock once, which will cause the register to pass its input to its output.\n", "\n", "Calling `step(n)` will tick the clock `n` times.\n", "\n", "The astute observer will notice that previous testers testing combinational logic did not call `step()`. This is because calling `poke()` on an input immediately propagates the updated values through combinational logic. Calling `step()` is only needed to update state elements in sequential logic. \n", "\n", "The code block below will show the verilog generated by `RegisterModule`.\n", "\n", "Note:\n", "* The module has an input for clock (and reset) that you didn't add- this is the implicit clock\n", "* The variable `register` shows up as `reg [11:0]`, as expected\n", "* There is a block sectioned off by `ifdef Randomize` that initialized the register to some random variable before simulation starts\n", "* `register` is updated on `posedge clock`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "println(getVerilog(new RegisterModule))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "One important note is that Chisel distinguishes between types (like `UInt`) and hardware nodes (like the literal `2.U`, or the output of `myReg`). While\n", "```scala\n", "val myReg = Reg(UInt(2.W))\n", "```\n", "is legal because a Reg needs a data type as a model,\n", "```scala\n", "val myReg = Reg(2.U)\n", "```\n", "is an error because `2.U` is already a hardware node and can't be used as a model." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Example: RegNext**
\n", "Chisel has a convenience register object for registers with simple input connections. The previous `Module` can be shortened to the following `Module`. Notice how we didn't need to specify the register bitwidth this time. It gets inferred from the register's output connection, in this case `io.out`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class RegNextModule extends Module {\n", " val io = IO(new Bundle {\n", " val in = Input(UInt(12.W))\n", " val out = Output(UInt(12.W))\n", " })\n", " \n", " // register bitwidth is inferred from io.out\n", " io.out := RegNext(io.in + 1.U)\n", "}\n", "\n", "test(new RegNextModule) { c =>\n", " for (i <- 0 until 100) {\n", " c.io.in.poke(i.U)\n", " c.clock.step(1)\n", " c.io.out.expect((i + 1).U)\n", " }\n", "}\n", "println(\"SUCCESS!!\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The Verilog looks almost the same as before, though the register name is generated instead of explicity defined." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "println(getVerilog(new RegNextModule))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# `RegInit`\n", "\n", "The register in `RegisterModule` was initialized to random data for simulation.\n", "Unless otherwised specified, registers do not have a reset value (or a reset).\n", "The way to create a register that resets to a given value is with `RegInit`.\n", "\n", "For instance, a 12-bit register initialized to zero can be created with the following.\n", "Both versions below are valid and do the same thing:\n", "```scala\n", "val myReg = RegInit(UInt(12.W), 0.U)\n", "val myReg = RegInit(0.U(12.W))\n", "```\n", "\n", "The first version has two arguments.\n", "The first argument is a type node that specified the datatype and its width.\n", "The second argument is a hardware node that specified the reset value, in this case 0.\n", "\n", "The second version has one argument.\n", "It is a hardware node that specifies the reset value, but normally `0.U`.\n", "\n", "**Example: Initialized Register**
\n", "The following demonstrates using `RegInit()`, initialized to zero." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class RegInitModule extends Module {\n", " val io = IO(new Bundle {\n", " val in = Input(UInt(12.W))\n", " val out = Output(UInt(12.W))\n", " })\n", " \n", " val register = RegInit(0.U(12.W))\n", " register := io.in + 1.U\n", " io.out := register\n", "}\n", "\n", "println(getVerilog(new RegInitModule))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that the generated verilog now has a block that checks `if (reset)` to reset the register to 0.\n", "Also note that this is inside the `always @(posedge clock)` block.\n", "Chisel's implicit reset is active high and synchronous.\n", "The register is still initialized to random junk before reset is called.\n", "The `PeekPokeTesters` always call reset before running your test, but you can manually call reset as well using the `reset(n)` function, where reset is high for `n` cycles." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# Control Flow\n", "Registers are very similar to wires in terms of control flow.\n", "They have last connect semantics and can be assigned to conditionally with `when`, `elsewhen`, and `otherwise`.\n", "\n", "**Example: Register Control Flow**
\n", "The following example finds the maximum value in a sequence of inputs using conditional register assignments." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class FindMax extends Module {\n", " val io = IO(new Bundle {\n", " val in = Input(UInt(10.W))\n", " val max = Output(UInt(10.W))\n", " })\n", "\n", " val max = RegInit(0.U(10.W))\n", " when (io.in > max) {\n", " max := io.in\n", " }\n", " io.max := max\n", "}\n", "\n", "test(new FindMax) { c =>\n", " c.io.max.expect(0.U)\n", " c.io.in.poke(1.U)\n", " c.clock.step(1)\n", " c.io.max.expect(1.U)\n", " c.io.in.poke(3.U)\n", " c.clock.step(1)\n", " c.io.max.expect(3.U)\n", " c.io.in.poke(2.U)\n", " c.clock.step(1)\n", " c.io.max.expect(3.U)\n", " c.io.in.poke(24.U)\n", " c.clock.step(1)\n", " c.io.max.expect(24.U)\n", "}\n", "println(\"SUCCESS!!\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# Other Register Examples\n", "\n", "Operations called on a register are performed on the **output** of the register, and the kind of operations depend on the register's type.\n", "That means that you can write\n", "```scala\n", "val reg: UInt = Reg(UInt(4.W))\n", "```\n", "which means the value `reg` is of type `UInt` and you can do things you can normally do with `UInt`s, like `+`, `-`, etc.\n", "\n", "\n", "You aren't restricted to using `UInt`s with registers, you can use any subclass of the base type `chisel3.Data`. This includes `SInt` for signed integers and a lot of other things.\n", "\n", "**Example: Comb Filter**
\n", "The following example shows a comb filter." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class Comb extends Module {\n", " val io = IO(new Bundle {\n", " val in = Input(SInt(12.W))\n", " val out = Output(SInt(12.W))\n", " })\n", "\n", " val delay: SInt = Reg(SInt(12.W))\n", " delay := io.in\n", " io.out := io.in - delay\n", "}\n", "println(getVerilog(new Comb))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# Exercises\n", "**Exercise: Shift Register**
\n", "Given your new-found registering knowledge, build a module that implements a shift register for a LFSR. Specifically:\n", "- Each element is a single bit wide.\n", "- Has a 4-bit output signal.\n", "- Takes a single input bit, which is the next value into the shift register.\n", "- Outputs the parallel output of the shift register, with the most significant bit being the last element of the shift register and the least significant bit being the first element of the shift register. `Cat` may come in handy.\n", "- **The output initializes at `b0001`.**\n", "- Shifts each clock cycle (no enable signal).\n", "- **Note in Chisel, subword assignment IS ILLEGAL**; something like `out(0) := in` will not work.\n", "\n", "\"shift\n", "\n", "A basic Module skeleton, testvector, and Driver invocation is provided below. The first register has been provided for you." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class MyShiftRegister(val init: Int = 1) extends Module {\n", " val io = IO(new Bundle {\n", " val in = Input(Bool())\n", " val out = Output(UInt(4.W))\n", " })\n", "\n", " val state = RegInit(UInt(4.W), init.U)\n", "\n", " ???\n", "}\n", "\n", "test(new MyShiftRegister()) { c =>\n", " var state = c.init\n", " for (i <- 0 until 10) {\n", " // poke in LSB of i (i % 2)\n", " c.io.in.poke(((i % 2) != 0).B)\n", " // update expected state\n", " state = ((state * 2) + (i % 2)) & 0xf\n", " c.clock.step(1)\n", " c.io.out.expect(state.U)\n", " }\n", "}\n", "println(\"SUCCESS!!\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "\n", "
\n", "
\n",
    "  val nextState = (state << 1) | io.in\n",
    "  state := nextState\n",
    "  io.out := state\n",
    "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Exercise: Parameterized Shift Register (Optional)**
\n", "Write a shift register that is parameterized by its delay (`n`), its initial value (`init`), and also has an enable input signal (`en`)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "// n is the output width (number of delays - 1)\n", "// init state to init\n", "class MyOptionalShiftRegister(val n: Int, val init: BigInt = 1) extends Module {\n", " val io = IO(new Bundle {\n", " val en = Input(Bool())\n", " val in = Input(Bool())\n", " val out = Output(UInt(n.W))\n", " })\n", "\n", " val state = RegInit(init.U(n.W))\n", "\n", " ???\n", "}\n", "\n", "// test different depths\n", "for (i <- Seq(3, 4, 8, 24, 65)) {\n", " println(s\"Testing n=$i\")\n", " test(new MyOptionalShiftRegister(n = i)) { c =>\n", " val inSeq = Seq(0, 1, 1, 1, 0, 1, 1, 0, 0, 1)\n", " var state = c.init\n", " var i = 0\n", " c.io.en.poke(true.B)\n", " while (i < 10 * c.n) {\n", " // poke in repeated inSeq\n", " val toPoke = inSeq(i % inSeq.length)\n", " c.io.in.poke((toPoke != 0).B)\n", " // update expected state\n", " state = ((state * 2) + toPoke) & BigInt(\"1\"*c.n, 2)\n", " c.clock.step(1)\n", " c.io.out.expect(state.U)\n", "\n", " i += 1\n", " }\n", " }\n", "}\n", "println(\"SUCCESS!!\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "\n", "
\n", "
\n",
    "  val nextState = (state << 1) | io.in\n",
    "  when (io.en) {\n",
    "    state  := nextState\n",
    "  }\n",
    "  io.out := state\n",
    "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# Appendix: Explicit clock and reset\n", "Chisel modules have a default clock and reset that are implicitly used by every register created inside them.\n", "There are times where you want to be able to override this default behavior; perhaps you have a black box that generates a clock or reset signal, or you have a multi-clock design.\n", "\n", "Chisel provides constructs for dealing with these cases.\n", "Clocks and resets can be overridden separately or together with `withClock() {}`, `withReset() {}`, and `withClockAndReset() {}`.\n", "The following code blocks will give examples of using these functions.\n", "\n", "One thing to be aware of is that `reset` (as of this tutorial's writing) is always synchronous and of type `Bool`.\n", "Clocks have their own type in Chisel (`Clock`) and should be declared as such.\n", "*`Bool`s can be converted to `Clock`s by calling `asClock()` on them, but you should be careful that you aren't doing something silly.*\n", "\n", "Also note that `chisel-testers` do not currently have complete support for multi-clock designs.\n", "\n", "**Example: Multi-Clock Module**
\n", "A module with multiple clocks and reset signals." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "// we need to import multi-clock features\n", "import chisel3.experimental.{withClock, withReset, withClockAndReset}\n", "\n", "class ClockExamples extends Module {\n", " val io = IO(new Bundle {\n", " val in = Input(UInt(10.W))\n", " val alternateReset = Input(Bool())\n", " val alternateClock = Input(Clock())\n", " val outImplicit = Output(UInt())\n", " val outAlternateReset = Output(UInt())\n", " val outAlternateClock = Output(UInt())\n", " val outAlternateBoth = Output(UInt())\n", " })\n", "\n", " val imp = RegInit(0.U(10.W))\n", " imp := io.in\n", " io.outImplicit := imp\n", "\n", " withReset(io.alternateReset) {\n", " // everything in this scope with have alternateReset as the reset\n", " val altRst = RegInit(0.U(10.W))\n", " altRst := io.in\n", " io.outAlternateReset := altRst\n", " }\n", "\n", " withClock(io.alternateClock) {\n", " val altClk = RegInit(0.U(10.W))\n", " altClk := io.in\n", " io.outAlternateClock := altClk\n", " }\n", "\n", " withClockAndReset(io.alternateClock, io.alternateReset) {\n", " val alt = RegInit(0.U(10.W))\n", " alt := io.in\n", " io.outAlternateBoth := alt\n", " }\n", "}\n", "\n", "println(getVerilog(new ClockExamples))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# Wrap Up\n", "Great job completing this section!! You've now learned how to create registers and write sequential logic in Chisel, which means you have enough basic building blocks to write real circuits.\n", "\n", "The next section will combine everything we've learned into one example! If you need a little more encouragement, just remember these words from an expert Chisel user:\n", "\n", "![BobRoss](http://i.qkme.me/3qbd5u.jpg)" ] }, { "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": 2 }