{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Example 2: A Counter with Ripple Carry Adder." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This next example shows how you make stateful things with registers\n", "and more complex hardware structures with functions. We generate\n", "a **3-bit ripple carry adder** building off of the 1-bit adder from\n", "the prior example, and then hook it to a register to count up modulo 8." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import pyrtl" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "pyrtl.reset_working_block()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A **function in PyRTL** is nothing special -- it just so happens that the statements\n", "it encapsulate tell PyRTL to build some hardware." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def one_bit_add(a, b, carry_in):\n", " assert len(a) == len(b) == 1 # len returns the bitwidth\n", " sum = a ^ b ^ carry_in\n", " carry_out = a & b | a & carry_in | b & carry_in\n", " return sum, carry_out" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we call *one_bit_add*\n", "above with the arguments *x*, *y*, and *z* it will make a **one-bit adder to add\n", "those values together** and return the wires for sum and carry_out as applied to *x*,\n", "*y*, and *z*. If I call it again on *i*, *j*, and *k* it will build a **new one-bit\n", "adder** for those inputs and return the resulting sum and carry_out for that adder." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "While PyRTL actually provides an \"+\" operator for wirevectors which generates\n", "adders, a **ripple carry adder** is something people can understand easily but has\n", "enough structure to be mildly interesting. Let's **define an adder of arbitrary\n", "length** recursively and (hopefully) pythonically. More comments after the code." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def ripple_add(a, b, carry_in=0):\n", " a, b = pyrtl.match_bitwidth(a, b)\n", " # this function is a function that allows us to match the bitwidth of multiple\n", " # different wires. By default, it zero extends the shorter bits\n", " if len(a) == 1:\n", " sumbits, carry_out = one_bit_add(a, b, carry_in)\n", " else:\n", " lsbit, ripplecarry = one_bit_add(a[0], b[0], carry_in)\n", " msbits, carry_out = ripple_add(a[1:], b[1:], ripplecarry)\n", " sumbits = pyrtl.concat(msbits, lsbit)\n", " return sumbits, carry_out" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### The above code breaks down into two cases:\n", "* If the size of the inputs is one-bit just do one_bit_add.\n", "* if they are more than one bit, do a one-bit add on the least significant bits, a ripple carry on the rest, and then stick the results back together into one WireVector." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### A couple interesting features of PyRTL can be seen here:\n", "* WireVectors can be indexed like lists, with [0] accessing the least significant bit and [1:] being an example of the use of Python slicing syntax.\n", "* While you can add two lists together in python a WireVector + Wirevector means \"make an adder\" so to concatenate the bits of two vectors one need to use \"concat\".\n", "* If we look at \"cin\" it seems to have a default value of the integer \"0\" but is a WireVector at other times.Python supports polymorphism throughout and PyRTL will cast integers and some other types to WireVectors when it can." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now let's **build a 3-bit counter** from our N-bit ripple carry adder." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "counter = pyrtl.Register(bitwidth=3, name='counter')\n", "sum, carry_out = ripple_add(counter, pyrtl.Const(\"1'b1\"))\n", "counter.next <<= sum" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### A couple new things in the above code:\n", "* The two remaining types of basic WireVectors, Const and Register, both appear. Const, unsurprisingly, is just for holding constants (such as the 0 in ripple_add), but here we create one directly from a Verilog-like string which includes both the value and the bitwidth.\n", "* Registers are just like wires, except their updates are delayed to the next clock cycle. This is made explicit in the syntax through the property '.next' which should always be set for registers.\n", "* In this simple example, we take counter next cycle equal to counter this cycle plus one." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now let's **run the bugger**. No need for inputs, it doesn't have any, but let's\n", "**throw in an assert** to check that it really counts up modulo 8. Finally we'll\n", "**print the trace** to the screen." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "sim_trace = pyrtl.SimulationTrace()\n", "sim = pyrtl.Simulation(tracer=sim_trace)\n", "for cycle in range(15):\n", " sim.step({})\n", " assert sim.value[counter] == cycle % 8\n", "sim_trace.render_trace()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.3" } }, "nbformat": 4, "nbformat_minor": 2 }