{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# MyHDL Legacy code migration notes\n", "\n", "*Some scenarios might be duplicates of [myhdl_changes.ipynb](notebooks/myhdl_changes.ipynb)*\n", "\n", "## Terminology\n", "\n", "As *hardware generators* in general are considered functions that are decorated using a\n", "\n", "* `@always`, `@always_comb`, `@always_seq`\n", "\n", "as opposed to simulation constructs not generating hardware in particular: `@instance`\n", "\n", "## Thumb rules\n", "\n", "* **Hierarchical resolution and (optional) strict typing in place**\n", "* **Everything outside a *hardware generator* is executed and 'pythonic'**:\n", " * Internal representation constructs allow references to logical combinations, like:\n", " `a = c ^ b`\n", " * Everything instanced by a variable in the `@block` context can be digital logic, signal, or reference and may be resolved into\n", " hardware\n", "* Hardware generators are AST-translated to IRL (Intermediate Representation Language) and then executed for translation/transpilation.\n", " \n", "With this in mind, you'll have to:\n", " \n", "* Turn global variables determining conditional compilation/translation into function parameters (before the `*` PEP570 construct)\n", "* Eliminate loop constructs (`for ...`) inside generators, see [Loop issues](#Loop-issues).\n", "* Clean up boolean constructs (see [Boolean Logic operations](../notebooks/myhdl_changes.ipynb#Logic-operations)):\n", " * Make sure to use boolean logic only within conditional statements\n", " * Translate to binary logic operations when assigning to a signal (**MIND THE OPERATOR PRECEDENCE!**)\n", "* Turn `sig.next[i]` into `sig[i].next`, see [Member assignment](../notebooks/member_assignment.ipynb)\n", "* Clean up variable usage (see [Variable usage](#Variable-usage)):\n", " * First occurence of a variable assignment to a static type (within a generator context only) defines its data type\n", " * Avoid using variables where possible, rather reserve an auxiliary signal or use a reference (outside the generator)\n", "* Function interface:\n", " * `@block`s only allow dedicated inputs or outputs, no inout signals (except [TristateSignals](../notebooks/tristate.ipynb))\n", " * Signals passed as output can not be read from\n", " * Optional: strict typing for port types and generics that should resolve to HDL, see [Interfaces](../notebooks/interfaces.ipynb)\n", "* Handle class derival hierarchies:\n", " * Classes containing signals only are resolved as in/out upon their driver state\n", " * Strictly interface-typed classes must be decorated, see [Classes](#Classes)\n", "* Revisit function calls (see [Functions](../notebooks/myhdl_changes.ipynb#Functions)):\n", " * Keep in mind that undecorated functions are **called** and their logic constructs will unroll entirely, i.e. no function definition is created in the resulting HDL\n", " * Turn function calls into HDL `@block` wherever possible\n", "* Arithmetics: Handle bit sizes according to translation errors/warnings. See also [Arithmetic Pitfalls](examples/arith_pitfalls.ipynb).\n", "\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Test unit\n", "\n", "The following block is a simple example to start from. Modifications of this base will exhibit a few pitfalls during migration. First, we import from `emulation`:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from myirl.emulation.myhdl import *" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then we define an auxiliary for cheap unit testing using VHDL-93 dialect and GHDL for reference testing:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "from myirl.test.common_test import Simulator\n", "from myirl.test import ghdl, icarus\n", "\n", "def test(uut, param = (), debug = False):\n", " inst = uut(*param)\n", " vhdl93 = targets.vhdl.VHDL93()\n", " s = Simulator(vhdl93)\n", " s.run(inst, 20, debug = True)\n", " f = inst.elab(targets.VHDL, elab_all = True)\n", " return f" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "FALLBACK: UNHANDLED ROOT CLASS , create new context\n", " Writing 'unit' to file /tmp/unit.vhdl \n", " Creating library file /tmp/module_defs.vhdl \n", "==== COSIM stdout ====\n", "DONE\n", "/tmp/unit.vhdl:36:9:@1ns:(assertion failure): Stop Simulation\n", "/tmp/unit:error: assertion failed\n", "in process .unit(myhdl_emulation).stim\n", "/tmp/unit:error: simulation failed\n", "\n", " Writing 'unit' to file /tmp/myirl_unit_9jtgp2n0/unit.vhdl \n", " Creating library file /tmp/myirl_module_defs_or225shu/module_defs.vhdl \n" ] }, { "data": { "text/plain": [ "['/tmp/myirl_unit_9jtgp2n0/unit.vhdl',\n", " '/tmp/myirl_module_defs_or225shu/module_defs.vhdl']" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "@block\n", "def unit():\n", " a = Signal(intbv(0xaa)[8:])\n", " a.init = True\n", " q = Signal(bool())\n", "\n", " @instance\n", " def stim():\n", " q.next = False\n", " if a[0] == True and a[1] == False and a[7] == False: # True boolean evaluation\n", " q.next = True\n", " \n", " yield delay(1)\n", " assert q == False\n", " \n", " print(\"DONE\")\n", " raise StopSimulation\n", " \n", " return instances()\n", "\n", "test(unit)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Note** GHDL using the VHDL-93 standard will throw a 'failure' upon `StopSimulation`, causing GHDL to exit with a return code other than 0. This is handled better with VHDL-08" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Variations\n", "\n", "Instead of creating an auxiliary signal, we can create references to signal combinations inside the `@block` context.\n", "However, this may create redundant code when resolving to a HDL, as the reference is a true Python IRL object." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "FALLBACK: UNHANDLED ROOT CLASS , create new context\n", " Writing 'unit' to file /tmp/unit.vhdl \n", " Creating library file /tmp/module_defs.vhdl \n", " Writing 'unit' to file /tmp/myirl_unit_j1ptaa4n/unit.vhdl \n", " Creating library file /tmp/myirl_module_defs_y9qpo7a6/module_defs.vhdl \n" ] } ], "source": [ "@block\n", "def unit():\n", " a = Signal(intbv(0xaa)[8:])\n", " a.init = True\n", " p, q = [ Signal(bool()) for _ in range(2) ]\n", " \n", " z = (a[7] == True) & (a[6] == False) & (a[0] == True) # Reference to binary combination of boolean expressions\n", " \n", " zb = a[7] & ~a[6] & a[0] # True binary combination\n", " \n", " zs = Signal(bool())\n", " \n", " # New wiring 'generator' construct:\n", " wires = [\n", " zs.set(zb)\n", " ]\n", "\n", " @instance\n", " def stim():\n", " q.next = False\n", " p.next = True\n", " \n", " yield delay(1)\n", " if a[7] == True and a[6] == False and a[0] == True: # True boolean evaluation\n", " q.next = True\n", " \n", " yield delay(1)\n", " \n", " if z: # Evaluate reference\n", " q.next = True\n", " \n", " yield delay(1)\n", "\n", " if zb: # Evaluate binary op reference\n", " q.next = True\n", " \n", " if zs == True: # Check signal\n", " q.next = True\n", " \n", " yield delay(1)\n", " assert q == False\n", "\n", " a.next = 0xa1\n", " yield delay(1)\n", " p.next = z\n", " yield delay(1)\n", "\n", " assert p == True\n", " \n", " return instances()\n", "\n", "\n", "f = test(unit, debug = True)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " 1\t-- File generated from source:\n", " 2\t-- /tmp/ipykernel_90982/3287474469.py\n", " 3\t-- (c) 2016-2022 section5.ch\n", " 4\t-- Modifications may be lost, edit the source file instead.\n", " 5\t\n", " 6\tlibrary IEEE;\n", " 7\tuse IEEE.std_logic_1164.all;\n", " 8\tuse IEEE.numeric_std.all;\n", " 9\t\n", " 10\tlibrary work;\n", " 11\t\n", " 12\tuse work.txt_util.all;\n", " 13\tuse work.myirl_conversion.all;\n", " 14\t\n", " 15\tentity unit is\n", " 16\tend entity unit;\n", " 17\t\n", " 18\tarchitecture myhdl_emulation of unit is\n", " 19\t -- Local type declarations\n", " 20\t -- Signal declarations\n", " 21\t signal q : std_ulogic;\n", " 22\t signal p : std_ulogic;\n", " 23\t signal a : unsigned(7 downto 0) := x\"aa\";\n", " 24\t signal zs : std_ulogic;\n", " 25\tbegin\n", " 26\t \n", " 27\tstim:\n", " 28\t process\n", " 29\t begin\n", " 30\t q <= '0';\n", " 31\t p <= '1';\n", " 32\t wait for 1 ns;\n", " 33\t if (((a(7) = '1') and (a(6) = '0')) and (a(0) = '1')) then\n", " 34\t q <= '1';\n", " 35\t end if;\n", " 36\t wait for 1 ns;\n", " 37\t if (((a(7) = '1') and (a(6) = '0')) and (a(0) = '1')) then\n", " 38\t q <= '1';\n", " 39\t end if;\n", " 40\t wait for 1 ns;\n", " 41\t if (((a(7) and not a(6)) and a(0))) = '1' then\n", " 42\t q <= '1';\n", " 43\t end if;\n", " 44\t if (zs = '1') then\n", " 45\t q <= '1';\n", " 46\t end if;\n", " 47\t wait for 1 ns;\n", " 48\t assert (q = '0')\n", " 49\t report \"Failed in /tmp/ipykernel_90982/3287474469.py:unit():41\" severity failure;\n", " 50\t a <= x\"a1\";\n", " 51\t wait for 1 ns;\n", " 52\t p <= from_bool((((a(7) = '1') and (a(6) = '0')) and (a(0) = '1')));\n", " 53\t wait for 1 ns;\n", " 54\t assert (p = '1')\n", " 55\t report \"Failed in /tmp/ipykernel_90982/3287474469.py:unit():48\" severity failure;\n", " 56\t wait;\n", " 57\t end process;\n", " 58\t zs <= ((a(7) and not a(6)) and a(0));\n", " 59\tend architecture myhdl_emulation;\n", " 60\t\n" ] } ], "source": [ "!cat -n {f[0]}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Variable usage" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Under scrutiny. For now, avoid complicated variable scenarios. Variable usage in hardware generation will be unsupported for non-VHDL targets, however it is safe to use them for simulation constructs.\n", "\n", "* bool() types may not resolve to std_logic when mixed with signals\n", "* A `Variable` type assigned to `False` yields a boolean type in the resulting HDL, whereas\n", " a `Signal(bool())` type assigned to a Python bool results in a `std_logic` output.\n", " \n", "\n", "**Note**: In general, do not try generating hardware with variables. Use signals where possible, or use references to logic combinations." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Configuration variables\n", "\n", "Make sure to put variables that are supposed to be generic parameters past the `*` and ensure they are given either a default (when desired in the HDL output) or a type hint:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "@block\n", "def unit1(a : Signal, b: Signal.Output, * , PARAM : bool = False):\n", " @always_comb\n", " def worker():\n", " if a == 5:\n", " b.next = 0\n", " elif PARAM:\n", " b.next = 1\n", " return instances()\n", "\n", "@block\n", "def tb(unit):\n", " a, b = (Signal(intbv()[5:]) for _ in range(2))\n", " uut = unit(a, b, PARAM = True)\n", " \n", " return instances()" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "FALLBACK: UNHANDLED ROOT CLASS , create new context\n", " Writing 'unit1' to file /tmp/unit1.vhdl \n", " Writing 'tb' to file /tmp/tb.vhdl \n", " Creating library file /tmp/module_defs.vhdl \n", "==== COSIM stdout ====\n", "../../src/ieee/v93/numeric_std-body.vhdl:1613:7:@0ms:(assertion warning): NUMERIC_STD.\"=\": metavalue detected, returning FALSE\n", "\n", " Writing 'unit1' to file /tmp/myirl_tb_tdcl5fj1/unit1.vhdl \n", " Writing 'tb' to file /tmp/myirl_tb_tdcl5fj1/tb.vhdl \n", " Creating library file /tmp/myirl_module_defs_u61egm2f/module_defs.vhdl \n" ] } ], "source": [ "f = test(tb, (unit1, ), debug = False)" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " generic (\n", " PARAM: boolean := FALSE\n", " );\n", " port (\n", " a : in unsigned(4 downto 0);\n", " b : out unsigned(4 downto 0)\n" ] } ], "source": [ "!grep -A 5 generic {f[0]}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Run-time parameter variant\n", "\n", "When `PARAM` is not separated by a `*`, it will not be inferred to HDL but resolved. This is used for conditional compilation." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "@block\n", "def unit2(a : Signal, b: Signal.Output, PARAM : bool = False):\n", " @always_comb\n", " def worker():\n", " if a == 5:\n", " b.next = 0\n", " elif PARAM:\n", " b.next = 1\n", " return instances()" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "FALLBACK: UNHANDLED ROOT CLASS , create new context\n", "\u001b[32m Module tb: Existing instance tb, rename to tb_1 \u001b[0m\n", " Writing 'unit2' to file /tmp/unit2.vhdl \n", " Writing 'tb_1' to file /tmp/tb_1.vhdl \n", " Creating library file /tmp/module_defs.vhdl \n", "==== COSIM stdout ====\n", "../../src/ieee/v93/numeric_std-body.vhdl:1613:7:@0ms:(assertion warning): NUMERIC_STD.\"=\": metavalue detected, returning FALSE\n", "\n", " Writing 'unit2' to file /tmp/myirl_tb_z0asncyn/unit2.vhdl \n", " Writing 'tb_1' to file /tmp/myirl_tb_z0asncyn/tb_1.vhdl \n", " Creating library file /tmp/myirl_module_defs__5t2ll0j/module_defs.vhdl \n" ] } ], "source": [ "f = test(tb, (unit2, ), debug = False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that `PARAM` is not converted into a `generic`, and occurences in the HDL code are resolved statically." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "-- File generated from source:\n", "-- /tmp/ipykernel_90982/3063955800.py\n", "-- (c) 2016-2022 section5.ch\n", "-- Modifications may be lost, edit the source file instead.\n", "\n", "library IEEE;\n", "use IEEE.std_logic_1164.all;\n", "use IEEE.numeric_std.all;\n", "\n", "library work;\n", "\n", "use work.txt_util.all;\n", "use work.myirl_conversion.all;\n", "\n", "entity unit2 is\n", " port (\n", " a : in unsigned(4 downto 0);\n", " b : out unsigned(4 downto 0)\n", " );\n", "end entity unit2;\n", "\n", "architecture myhdl_emulation of unit2 is\n", " -- Local type declarations\n", " -- Signal declarations\n", "begin\n", " \n", "worker:\n", " process(a)\n", " begin\n", " if (a = \"00101\") then\n", " b <= \"00000\";\n", " elsif TRUE then\n", " b <= \"00001\";\n", " end if;\n", " end process;\n", "\n", "end architecture myhdl_emulation;\n", "\n" ] } ], "source": [ "!cat {f[0]}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Loop issues\n", "\n", "MyHDL allows loops inside hardware descriptions and creates generate statements in the resulting HDL." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "Bool = Signal.Type(bool)\n", "\n", "@block\n", "def unit_loop0(b : Bool, N : int = 5):\n", " a = [ Signal(bool()) for _ in range(N) ]\n", " \n", " @always_comb\n", " def worker():\n", " a[0].next = b\n", " for i in range(1, 5):\n", " a[i].next = ~a[i-1]\n", " \n", " return instances()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is no longer supported, except in simulation constructs implemented inside `@instance` functions.\n", "Migration strategy:\n", "* Move loop to the `@block` level, use procedural instancing\n", "* Implement using `@process` or `@genprocess` constructs in the IRL" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Replace by:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "@block\n", "def unit_loop1(b : Bool):\n", " a = [ Signal(bool()) for _ in range(5) ]\n", " \n", " wires = [ a[0].set(b) ]\n", " wires += [ a[i].set(a[i-1]) for i in range(1, 5)] \n", " return instances()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "or within a library, using IRL:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "import myirl\n", "\n", "\n", "@myirl.block\n", "def unit_loop2(b: Bool, N = 5):\n", " a = [ Signal(bool()) for _ in range(N) ]\n", " \n", " @myirl.genprocess()\n", " def worker():\n", " yield [ a[0].set(b) ]\n", " yield [ a[i].set(~a[i-1]) for i in range(1, N) ]\n", " \n", " return instances()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Examine VHDL output:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "FALLBACK: UNHANDLED ROOT CLASS , create new context\n", " Writing 'unit_loop2' to file /tmp/myirl_unit_loop2_sair1n13/unit_loop2.vhdl \n" ] } ], "source": [ "def test_loop(unit):\n", " b = Bool()\n", " uut = unit(b, 8)\n", " f = uut.elab(targets.VHDL)\n", " return f\n", " \n", "f = test_loop(unit_loop2)" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "architecture myIRL of unit_loop2 is\n", " -- Local type declarations\n", " -- Signal declarations\n", " signal a0 : std_ulogic;\n", " signal a1 : std_ulogic;\n", " signal a2 : std_ulogic;\n", " signal a3 : std_ulogic;\n", " signal a4 : std_ulogic;\n", " signal a5 : std_ulogic;\n", " signal a6 : std_ulogic;\n", " signal a7 : std_ulogic;\n", "begin\n", " \n", "worker:\n", " process(b, a0, a1, a2, a3, a4, a5, a6)\n", " begin\n", " a0 <= b;\n", " a1 <= not a0;\n", " a2 <= not a1;\n", " a3 <= not a2;\n", " a4 <= not a3;\n", " a5 <= not a4;\n", " a6 <= not a5;\n", " a7 <= not a6;\n", " end process;\n", "\n", "end architecture myIRL;\n", "\n" ] } ], "source": [ "! grep -A 30 architecture {f[0]}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Obviously, loop statements unroll explicitely. Thus, this is not intended for iterating through a large array.\n", "See also [Loops](../notebooks/myhdl_changes.ipynb/#Loops)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Classes\n", "\n", "Recommended approach for porting legacy class constructs containing signals:\n", "\n", "* Derive a class from your existing, undecorated signal class\n", "* Decorate this derived class, depending on desired interface resolving:\n", " * `@container()` for a bidirectional class (creates container structures)\n", " * `@container(mode=LEGACY_CLASS)` for legacy class behaviour (resolves each member of the signal)\n", " * `@bulkwrapper(target_list)` for unidirectional internal types supported by specific targets only\n", "\n", "For bidirectional classes, you need to define input, output and auxiliary ports (always inputs).\n", "\n", "This allows to still use your legacy class constructs from older myHDL code.\n", "\n", "The derived classes can be used for type specification in the interface." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Further details:\n", "* [Class signals](../notebooks/myhdl_changes.ipynb#Class-signals)\n", "* [Class method tricks](../notebooks/class_methods.ipynb)" ] } ], "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 }