{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Example 3: A State Machine built with ConditionalUpdate" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this example we describe how **ConditionalUpdate** works in the context of\n", "a vending machine that will dispense an item when it has received 4 tokens.\n", "If a refund is requested, it returns the tokens." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import pyrtl\n", "pyrtl.reset_working_block()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "token_in = pyrtl.Input(1, 'token_in')\n", "req_refund = pyrtl.Input(1, 'req_refund')\n", "dispense = pyrtl.Output(1, 'dispense')\n", "refund = pyrtl.Output(1, 'refund')\n", "state = pyrtl.Register(3, 'state')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First new step, let's **enumerate a set of constant to serve as our states**" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "WAIT, TOK1, TOK2, TOK3, DISPENSE, REFUND = [pyrtl.Const(x, bitwidth=3) for x in range(6)]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we could build a state machine using just the registers and logic discussed\n", "in the earlier examples, but doing operations *conditional* on some input is a pretty\n", "fundamental operation in hardware design. **PyRTL provides a class \"ConditionalUpdate\"**\n", "to provide a predicated update to a registers, wires, and memories.\n", "\n", "**Conditional assignments** are specified with a *\"|=\"* instead of a *\"<<=\"* operator. The\n", "conditional assignment is only value in the context of a condition, and update to those\n", "values only happens when that condition is true. In hardware this is implemented\n", "with a simple mux -- for people coming from software it is important to remember that this\n", "is describing a big logic function NOT an \"if-then-else\" clause. All of these things will\n", "execute straight through when *build_everything* is called. More comments after the code.\n", "\n", "**One more thing:** ConditionalUpdate might not always be the best item to use.\n", "if the update is simple, a regular mux(sel_wire, falsecase=f_wire, truecase=t_wire)\n", "can be sufficient." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "with pyrtl.conditional_assignment:\n", " with req_refund: # signal of highest precedence\n", " state.next |= REFUND\n", " with token_in: # if token received, advance state in counter sequence\n", " with state == WAIT:\n", " state.next |= TOK1\n", " with state == TOK1:\n", " state.next |= TOK2\n", " with state == TOK2:\n", " state.next |= TOK3\n", " with state == TOK3:\n", " state.next |= DISPENSE # 4th token received, go to dispense\n", " with pyrtl.otherwise: # token received but in state where we can't handle it\n", " state.next |= REFUND\n", " # unconditional transition from these two states back to wait state\n", " # NOTE: the parens are needed because in Python the \"|\" operator is lower precedence\n", " # than the \"==\" operator!\n", " with (state == DISPENSE) | (state == REFUND):\n", " state.next |= WAIT" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "dispense <<= state == DISPENSE\n", "refund <<= state == REFUND" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### A couple of other things to note:\n", "* A condition can be nested within another condition and the implied hardware is that the left-hand-side should only get that value if ALL of the encompassing conditions are satisfied.\n", "* Only one conditional at each level can be true meaning that all conditions are implicitly also saying that none of the prior conditions at the same level also have been true. The highest priority condition is listed first, and in a sense you can think about each other condition as an \"elif\".\n", "* If not every condition is enumerated, the default value for the register under those cases will be the same as it was the prior cycle (\"state.next |= state\" in this example). The default for a wirevector is 0.\n", "* There is a way to specify something like an \"else\" instead of \"elif\" and that is with an \"otherwise\" (as seen on the line above \"state.next <<= REFUND\"). This condition will be true if none of the other conditions at the same level were also true (for this example specifically state.next will get REFUND when req_refund==0, token_in==1, and state is not in TOK1, TOK2, TOK3, or DISPENSE.\n", "* Not shown here, you can update multiple different registers, wires, and memories all under the same set of conditionals." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### A more artificial example might make it even more clear how these rules interact:\n", "```python \n", "with a:\n", " r.next |= 1 <-- when a is true\n", " with d:\n", " r2.next |= 2 <-- when a and d are true\n", " with otherwise:\n", " r2.next |= 3 <-- when a is true and d is false\n", "with b == c:\n", " r.next |= 0 <-- when a is not true and b == c is true\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now let's **build and test our state machine**." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "sim_trace = pyrtl.SimulationTrace()\n", "sim = pyrtl.Simulation(tracer=sim_trace)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Rather than just give some random inputs, let's **specify some specific 1 bit values**. Recall\n", "that the sim.step method takes a dictionary mapping inputs to their values. We could just\n", "specify the input set directly as a dictionary but it gets pretty ugly -- let's use some python\n", "to parse them up." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "sim_inputs = {\n", " 'token_in': '0010100111010000',\n", " 'req_refund': '1100010000000000'\n", " }\n", "\n", "for cycle in range(len(sim_inputs['token_in'])):\n", " sim.step({w: int(v[cycle]) for w, v in sim_inputs.items()})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Also, to make our input/output easy to reason about let's **specify an order to the traces**\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "sim_trace.render_trace(trace_list=['token_in', 'req_refund', 'state', 'dispense', 'refund'])" ] } ], "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 }