{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Fun stuff\n", "\n", "This notebook is a collection of a few **WTF** moments you could have as Python enthusiast.\n", "Bottomline: This is a debatable code style." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## HDL style signal assignment\n", "\n", "To mimic VHDL or Verilog assignment style, you *could* be tempted to redefine your assignments to emulate:\n", "\n", "```\n", "a <= x - 1\n", "```\n", "\n", "This is simply done by inheriting from the `Signal` class:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from myirl import *\n", "from myirl import targets\n", "from myhdl import intbv\n", "\n", "class HDLSignal(Signal):\n", " def __le__(self, other):\n", " return base.GenAssign(self, other)\n" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "a, b = [ HDLSignal(intbv()[5:], name=n) for n in ['a', 'b'] ]\n", "\n", "logic = kernel.sensitivity.LogicContext()\n", "logic += [\n", " a <= 5,\n", " b <= a & b\n", "]" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[94ma <= \"00101\";\n", "\u001b[0m\u001b[94mb <= (a and b);\n", "\u001b[0m" ] } ], "source": [ "d = DummyVHDLModule()\n", "for stmt in logic:\n", " stmt.emit(d)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However, there's a catch: You will not be able to use the `<=` operator for instancing of a comparator.\n", "With this abuse of the new `@` (matrix multiplication) operator, we could get some remedy (with side effects):" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "class MyOp(base.ConvertibleExpr):\n", " def __init__(self, func):\n", " self.func = func\n", " def __rmatmul__(self, other):\n", " return MyOp(lambda t, self=self, other=other : self.func(other, t))\n", " def __matmul__(self, other):\n", " return self.func(other)\n", " \n", "ge = MyOp(lambda x, y: base.Ge(x, y)) # Generate an operator\n", "le = MyOp(lambda x, y: base.Ge(y, x)) # swapped" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "b >= a" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a @le@ b" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "expr = a @ge@ b" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To verify it converts correctly, check:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(a >= b, '(a >= b)')" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "expr, expr._convert(targets.VHDL, in_condition = False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However, we could also alter the `<=` operator into `>=` by this cheap hack:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "class Chameleon(base.BoolOp):\n", " _opid = \"ge\"\n", " def __init__(self, lhs, rhs):\n", " super().__init__(rhs, lhs) # Note swapped RHS/LHS, because we use '>=' instead '<=':\n", " \n", " def emit(self, ctx):\n", " tmp = base.GenAssign(self.right, self.left)\n", " tmp.emit(ctx)\n", " \n", "class HDLSignal(Signal):\n", " def __le__(self, other):\n", " return Chameleon(self, other)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To make sure it combines with boolean expressions, too:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "a, b = [ HDLSignal(intbv()[5:], name=n) for n in ['a', 'b'] ]\n", "\n", "logic = kernel.sensitivity.LogicContext()\n", "logic.If = targets.vhdl.VHDLIf\n", "\n", "# Here's the user's RTL:\n", "logic += [\n", " a <= 5,\n", " b <= a & b,\n", " logic.If((a > 3) & (a <= 5)).Then(\n", " b <= 1\n", " ).Else(\n", " b <= (a <= 2)\n", " )\n", "]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that `a <= 2` works as expression, because it is internally swapped to a `>=`." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[94ma <= to_unsigned(5, 5);\n", "\u001b[0m\u001b[94mb <= (a and b);\n", "\u001b[0m\u001b[94mif ((a > \"00011\") and (\"00101\" >= a))\u001b[0m\u001b[94m then\n", "\u001b[0m\u001b[94m b <= to_unsigned(1, 5);\n", "\u001b[0m\u001b[94melse\n", "\u001b[0m\u001b[94m b <= from_bool((\"00010\" >= a));\n", "\u001b[0m\u001b[94mend if;\n", "\u001b[0m" ] } ], "source": [ "d = DummyVHDLModule()\n", "for stmt in logic:\n", " stmt.emit(d)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Note**: Earlier kernel versions were unable to properly collect the operands respectively source and destination from this construct. This is now solved." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Example from the 'library'\n", "\n", "To use this style, we must make sure to use the import the `style_hdl` module as follows (this overrides `Signal` and `process` by a derived functionality)." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "from myirl.library.style_hdl import *\n", "from myirl.test.common_test import run_ghdl, clkgen\n", "from myirl import targets, simulation\n", "\n", "@block\n", "def unit1():\n", " a, b = [ Signal(intbv()[8:]) for _ in range(2) ]\n", " q = Signal(intbv(19)[9:])\n", " clk = ClkSignal(name = \"master_clock\")\n", " rst = ResetSignal(ResetSignal.NEG_ASYNC)\n", " en = Signal(bool())\n", "\n", " thresh = Signal(bool(True))\n", "\n", " oscillator = clkgen(clk, 2)\n", "\n", " @genprocess(clk, EDGE=clk.POS, RESET=rst)\n", " def worker1():\n", " yield [\n", " worker1.If(en == True).Then(\n", " q <= a + b\n", " )\n", " ]\n", "\n", " @genprocess(clk, EDGE=clk.POS, RESET=rst)\n", " def worker2():\n", " yield [\n", " worker2.If(q <= 4).Then(\n", " thresh <= '1'\n", " ).Else(\n", " thresh <= '0'\n", " )\n", " ]\n", "\n", " @simulation.generator\n", " def seq1():\n", " yield [\n", " rst.set(False),\n", " simulation.wait('1 ns'),\n", " rst.set(True),\n", " simulation.assert_(thresh == True, \"#1 '<=' test failed\"),\n", "\n", " simulation.wait(clk.posedge),\n", " simulation.assert_(q == 19, \"failed to reset\"),\n", " a.set(2), b.set(1), en.set(True),\n", " simulation.wait(clk.posedge),\n", " simulation.wait(clk.posedge),\n", " simulation.wait('1 ns'),\n", " simulation.assert_(thresh == True, \"#2 '<=' test failed\"),\n", " a.set(2), b.set(3), en.set(True),\n", " simulation.wait(clk.posedge),\n", " simulation.wait(clk.posedge),\n", " simulation.assert_(q == 5, \"failed to add\"),\n", " simulation.wait('1 ns'),\n", " simulation.assert_(thresh == False, \"#3 '<=' test failed\"),\n", " simulation.print_(a, b, q)\n", " ]\n", "\n", " return instances()" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "FALLBACK: UNHANDLED ROOT CLASS , create new context\n", " Writing 'clkgen' to file /tmp/myirl_unit1_av1hgd9z/clkgen.vhdl \n", " Writing 'unit1' to file /tmp/myirl_unit1_av1hgd9z/unit1.vhdl \n", " Creating library file /tmp/myirl_module_defs_82epzx_8/module_defs.vhdl \n", "==== COSIM stdout ====\n", "0x02 0x03 0x005\n", "/tmp/unit1:info: simulation stopped by --stop-time @1us\n", "\n" ] } ], "source": [ "def test_unit1():\n", " inst = unit1()\n", "\n", " files = inst.elab(targets.VHDL, elab_all = True)\n", " run_ghdl(files, inst, debug = True, vcdfile=\"unit1.vcd\")\n", "\n", "test_unit1()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Two way association\n", "\n", "To make complex wirings for interface bulk signal types more readable, we define a different operator class in particular for connections.\n", "For example, a Port class may be in/out from the source, out/in ('reverse') from the destination. Within a module though, we may have to distribute the signals of a Port to several instances." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "class MyOpX(base.ConvertibleExpr):\n", " def __init__(self, func):\n", " self.func = func\n", " def __rlshift__(self, other):\n", " return MyOpX(lambda t, self=self, other=other : self.func(other, t))\n", " def __rshift__(self, other):\n", " return self.func(other)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Create a `SpecialOps` derivative and pass that as `TYPE` parameter to your bulk signal class.\n", "The `@hdlmacro` generates the connections between `self` and the `other` port class." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "from myirl.library.bulksignals import *\n", "\n", "class SpecialOps(ContainerGen):\n", " twoway = MyOpX(lambda x, y: x.assign(y)) # Generate an operator\n", "\n", "@bulkwrapper(targets.vhdl, TYPE=SpecialOps)\n", "class Port:\n", " _inputs = ['input']\n", " _outputs = ['output']\n", " _other = []\n", " \n", " def __init__(self):\n", " self.input = Signal(bool())\n", " self.output = Signal(bool())\n", " \n", " @hdlmacro\n", " def assign(self, other):\n", " \"Do the two way connection between peers\"\n", " yield [\n", " self.output.set(other.input),\n", " other.output.set(self.input)\n", " ]\n", " \n", " @hdlmacro \n", " def __le__(self, other):\n", " \"Wire signals members one way to peer\"\n", " yield [\n", " self.input.set(other.input),\n", " self.output.set(other.output)\n", " ]" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "p, q0, q1 = [ Port(name=n) for n in ['p', 'p0', 'p1'] ]\n", "quiet = p.rename('p'), q0.rename('q0'), q1.rename('q1')" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "connections = [\n", " p <> q0,\n", " q1 <= p\n", "]" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "----------------\n", "\u001b[94mp_out.output <= p0_in.input;\n", "\u001b[0m\u001b[94mp0_out.output <= p_in.input;\n", "\u001b[0m----------------\n", "\u001b[94mp1_in.input <= p_in.input;\n", "\u001b[0m\u001b[94mp1_out.output <= p_out.output;\n", "\u001b[0m" ] } ], "source": [ "for stmt in connections:\n", " print(\"----------------\")\n", " stmt.emit(d)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Coding style issues\n", "\n", "Derived classes may redefine the `__le__` method to implement custom assignments, such as `flexbv` types performing latency and precision verification behind the curtains. The `.set` Method may also be overriden by some BulkSignal types internally. So there are a few ways to shoot yourself into the foot.\n", "\n", "A guideline to keep it clean:\n", "\n", "* Use `.set` for 1:1 assignment in synchronous or asynchronous processes\n", "* Use `.wireup` for direct connections (outside process)\n", "* Use `<=` for one way assignments only\n", "* Use `<>` style for two way custom connections between signal containers\n", "* Pipeline signals may inherit specific properties using custom setters" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Pitfalls\n", "\n", "The `<=` style assignment, when used on vector data types and tuple notation, exhibits a pitfall:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(, ADD(v_9c63, v_9c63))" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from myirl.vector import VectorSignal\n", "\n", "v, w = [ VectorSignal(2, intbv()[5:]) for _ in range(2) ]\n", "\n", "v <= w[0] + w[1], v[0] + v[1]\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This results in the last expression being a `tuple` instead of type assignment, plus not do what's desired, effectively it is: `v.set(w[0] + w[1]` and a new expression is created after the `,`.\n", "\n", "Correct would be:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "v <= (w[0] + w[1], v[0] + v[1])" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "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.9.2" } }, "nbformat": 4, "nbformat_minor": 4 }