{ "cells": [ { "cell_type": "markdown", "id": "040970f5", "metadata": {}, "source": [ "# Advanced Generators\n", "\n", "For complicated testbench setups, you might want to reuse certain sequences from a macro.\n", "\n", "The PEP380 `yield from` construct makes this look nicer:" ] }, { "cell_type": "code", "execution_count": 1, "id": "90ac3f67", "metadata": {}, "outputs": [], "source": [ "from myirl import *\n", "import myirl.simulation as sim\n", "from myirl import targets\n", "\n", "def toggle(p, m):\n", " yield [m.set(False)] \n", " for i in range(4):\n", " yield [ p.set(~p), sim.wait('20 ns') ]\n", " \n", "def data(clk, d, p, m):\n", " a = 1\n", " yield [m.set(True)] \n", " for i in range(8):\n", " yield [ sim.wait(clk.posedge), p.set((d & a) != 0) ]\n", " a <<= 1" ] }, { "cell_type": "code", "execution_count": 2, "id": "6d4820e5", "metadata": {}, "outputs": [], "source": [ "@block\n", "def tb():\n", " p = Signal(bool(), name='p')\n", " marker = Signal(bool(), name = 'mark')\n", " \n", " d = Signal(intbv()[8:], name = 'data')\n", " clk = ClkSignal(name='clk')\n", " \n", " @bulk_delay(5)\n", " def clkgen():\n", " yield [\n", " clk.set(~clk)\n", " ]\n", " \n", " @sim.generator\n", " def seq():\n", " yield [\n", " p.set(False), sim.wait('5 ns'), marker.set(False), sim.wait(clk.posedge),\n", " marker.set(True), sim.wait(clk.posedge)\n", " ]\n", " yield from toggle(p, marker)\n", " yield from data(clk, 0xa5, p, marker)\n", " yield from toggle(p, marker)\n", " \n", " yield [ sim.raise_(sim.StopSimulation)]\n", " return [clkgen, seq]" ] }, { "cell_type": "code", "execution_count": 3, "id": "74c3ce09", "metadata": {}, "outputs": [], "source": [ "from myirl.test.common_test import run_ghdl\n", "\n", "def test():\n", " t = tb()\n", " f = t.elab(targets.VHDL)\n", " run_ghdl(f, tb, vcdfile=\"/tmp/tb1.vcd\", debug = False)\n", " return t" ] }, { "cell_type": "code", "execution_count": 4, "id": "65e567bc", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "FALLBACK: UNHANDLED ROOT CLASS , create new context\n", " Writing 'tb' to file /tmp/myirl_tb_w7i57vwf/tb.vhdl \n" ] } ], "source": [ "t = test()" ] }, { "cell_type": "markdown", "id": "32498501", "metadata": {}, "source": [ "### Waveform" ] }, { "cell_type": "code", "execution_count": 5, "id": "eee8c6fd", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/javascript": [ "new Promise(function(resolve, reject) {\n", "\tvar script = document.createElement(\"script\");\n", "\tscript.onload = resolve;\n", "\tscript.onerror = reject;\n", "\tscript.src = \"https://wavedrom.com/wavedrom.min.js\";\n", "\tdocument.head.appendChild(script);\n", "}).then(() => {\n", "new Promise(function(resolve, reject) {\n", "\tvar script = document.createElement(\"script\");\n", "\tscript.onload = resolve;\n", "\tscript.onerror = reject;\n", "\tscript.src = \"https://wavedrom.com/skins/narrow.js\";\n", "\tdocument.head.appendChild(script);\n", "}).then(() => {\n", "WaveDrom.ProcessAll();\n", "});\n", "});" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import wavedraw; import nbwavedrom\n", "TB = t.name;\n", "\n", "waveform = wavedraw.vcd2wave(\"/tmp/tb1.vcd\", TB + '.clk', None)\n", "nbwavedrom.draw(waveform)" ] }, { "cell_type": "markdown", "id": "d5d64347", "metadata": {}, "source": [ "## Bidirectional generator function\n", "\n", "The `yield from` mechanisms can be taken further to generate testbenches from existing hardware descriptions.\n", "Here, a simple 'software-style' generator FSM is constructed that reacts to a value sent to it via `(yield)`:" ] }, { "cell_type": "code", "execution_count": 6, "id": "0da6a3f0", "metadata": {}, "outputs": [], "source": [ "from collections import namedtuple\n", "\n", "t_state = namedtuple('state', ['A', 'B', 'C', 'END'])\n", "enum = t_state(*range(4))\n", "\n", "def fsm(p, m):\n", " print(\"STARTED\")\n", " \n", " state = enum.A\n", "\n", " while True:\n", " if state == enum.A:\n", " print(\"State A\")\n", " b = (yield)\n", " if b == 1:\n", " state = enum.B\n", " yield [m.set(True)]\n", " elif b == 0:\n", " pass\n", " else:\n", " raise ValueError(\"Value can not be\", b)\n", "\n", " elif state == enum.B:\n", " print(\"State B\")\n", " b = (yield)\n", " if b == 1:\n", " state = enum.C \n", " yield [m.set(False)]\n", " elif b == 0:\n", " pass\n", " else:\n", " raise ValueError(\"Value can not be\", b)\n", "\n", " elif state == enum.C:\n", " print(\"State C\")\n", " b = (yield)\n", " if b == 1:\n", " for i in range(8):\n", " # TOGGLING (see below)\n", " yield [ p.set(~p) ]\n", " state = enum.A\n", " elif b == 0:\n", " state = enum.END\n", " yield [ sim.wait('40 ns'), p.set(False), m.set(True) ]\n", " else:\n", " raise ValueError(\"Value can not be\", b)\n", "\n", " elif state == enum.END:\n", " print(\"STATE END\")\n", " break\n", " else:\n", " raise ValueError(\"Bad state\")" ] }, { "cell_type": "markdown", "id": "37c86258", "metadata": {}, "source": [ "The wrapper only translates by `yield from`: " ] }, { "cell_type": "code", "execution_count": 7, "id": "aca615c2", "metadata": {}, "outputs": [], "source": [ "def wrapper(routine):\n", " print(\"CALL WRAPPER\")\n", " yield from routine" ] }, { "cell_type": "code", "execution_count": 8, "id": "c8e0c18b", "metadata": {}, "outputs": [], "source": [ "@block\n", "def tb():\n", " p = Signal(bool(), name='p')\n", " marker = Signal(bool(), name = 'mark')\n", " \n", " d = Signal(intbv()[8:], name = 'data')\n", " clk = ClkSignal(name='clk')\n", " \n", " @bulk_delay(5)\n", " def clkgen():\n", " yield [\n", " clk.set(~clk)\n", " ]\n", " \n", " @sim.generator\n", " def seq():\n", " yield [\n", " p.set(False), sim.wait('10 ns'), marker.set(True), sim.wait(clk.posedge),\n", " marker.set(False), sim.wait(clk.posedge)\n", " ]\n", " # Construct wrapper for FSM:\n", " w = wrapper(fsm(p, marker))\n", " # Prime it:\n", " w.send(None)\n", " X = None # Don't care, allowed only during the 'TOGGLING' sequence\n", " for b in [0, 0, 1, 0, 0, 1, 1, 1, X, X, X, X, X, X, X, X, 0, 1, 1, 1, 0, 0, 0]:\n", " try:\n", " it = w.send(b)\n", "# print(\"#\", it)\n", " if it is not None:\n", " yield it\n", " yield [sim.wait(clk.posedge)]\n", " except StopIteration:\n", " break\n", "\n", " yield [ sim.wait('50 ns'), sim.raise_(sim.StopSimulation)]\n", " return [clkgen, seq]" ] }, { "cell_type": "code", "execution_count": 9, "id": "0641a6a1", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "FALLBACK: UNHANDLED ROOT CLASS , create new context\n", "CALL WRAPPER\n", "STARTED\n", "State A\n", "State A\n", "State A\n", "State B\n", "State B\n", "State C\n", "State A\n", "State A\n", "State B\n", "State C\n", "STATE END\n", " Writing 'tb' to file /tmp/myirl_tb_14zgpfqs/tb.vhdl \n" ] }, { "data": { "text/plain": [ "[Instance tb I/F: [// ID: tb_0 to tb]]" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from myirl.test.common_test import run_ghdl\n", "\n", "def test():\n", " t = tb()\n", " f = t.elab(targets.VHDL)\n", " run_ghdl(f, tb, vcdfile=\"/tmp/tb2.vcd\", debug = False)\n", " return t\n", "test()" ] }, { "cell_type": "code", "execution_count": 10, "id": "bff0d4e8", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/javascript": [ "new Promise(function(resolve, reject) {\n", "\tvar script = document.createElement(\"script\");\n", "\tscript.onload = resolve;\n", "\tscript.onerror = reject;\n", "\tscript.src = \"https://wavedrom.com/wavedrom.min.js\";\n", "\tdocument.head.appendChild(script);\n", "}).then(() => {\n", "new Promise(function(resolve, reject) {\n", "\tvar script = document.createElement(\"script\");\n", "\tscript.onload = resolve;\n", "\tscript.onerror = reject;\n", "\tscript.src = \"https://wavedrom.com/skins/narrow.js\";\n", "\tdocument.head.appendChild(script);\n", "}).then(() => {\n", "WaveDrom.ProcessAll();\n", "});\n", "});" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "TB = tb.name;\n", "\n", "waveform = wavedraw.vcd2wave(\"/tmp/tb2.vcd\", TB + '.clk', None)\n", "nbwavedrom.draw(waveform)" ] }, { "cell_type": "markdown", "id": "987f1e6b", "metadata": {}, "source": [ "This may appear extra complicated, but helps *a lot* with verification of known models against a hardware implementation. Also, it allows to loop in other co-simulation objects/pipes for step-wise verification." ] }, { "cell_type": "code", "execution_count": 11, "id": "71964e87", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "-- File generated from source:\r\n", "-- /tmp/ipykernel_30190/2187491613.py\r\n", "-- (c) 2016-2022 section5.ch\r\n", "-- Modifications may be lost, edit the source file instead.\r\n", "\r\n", "library IEEE;\r\n", "use IEEE.std_logic_1164.all;\r\n", "use IEEE.numeric_std.all;\r\n", "\r\n", "library work;\r\n", "\r\n", "use work.txt_util.all;\r\n", "use work.myirl_conversion.all;\r\n", "\r\n", "entity tb is\r\n", "end entity tb;\r\n", "\r\n", "architecture myIRL of tb is\r\n", " -- Local type declarations\r\n", " -- Signal declarations\r\n", " signal clk : std_ulogic := '0';\r\n", " signal p : std_ulogic;\r\n", " signal mark : std_ulogic;\r\n", "begin\r\n", " \r\n", "clkgen:\r\n", " clk <= not clk after 5.000000 ns;\r\n", " \r\n", " \r\n", "seq:\r\n", " process\r\n", " begin\r\n", " p <= '0';\r\n", " wait for 10 ns;\r\n", " mark <= '1';\r\n", " wait until rising_edge(clk);\r\n", " mark <= '0';\r\n", " wait until rising_edge(clk);\r\n", " wait until rising_edge(clk);\r\n", " wait until rising_edge(clk);\r\n", " mark <= '1';\r\n", " wait until rising_edge(clk);\r\n", " wait until rising_edge(clk);\r\n", " wait until rising_edge(clk);\r\n", " mark <= '0';\r\n", " wait until rising_edge(clk);\r\n", " wait until rising_edge(clk);\r\n", " p <= not p;\r\n", " wait until rising_edge(clk);\r\n", " p <= not p;\r\n", " wait until rising_edge(clk);\r\n", " p <= not p;\r\n", " wait until rising_edge(clk);\r\n", " p <= not p;\r\n", " wait until rising_edge(clk);\r\n", " p <= not p;\r\n", " wait until rising_edge(clk);\r\n", " p <= not p;\r\n", " wait until rising_edge(clk);\r\n", " p <= not p;\r\n", " wait until rising_edge(clk);\r\n", " p <= not p;\r\n", " wait until rising_edge(clk);\r\n", " wait until rising_edge(clk);\r\n", " wait until rising_edge(clk);\r\n", " mark <= '1';\r\n", " wait until rising_edge(clk);\r\n", " wait until rising_edge(clk);\r\n", " mark <= '0';\r\n", " wait until rising_edge(clk);\r\n", " wait until rising_edge(clk);\r\n", " wait for 40 ns;\r\n", " p <= '0';\r\n", " mark <= '1';\r\n", " wait until rising_edge(clk);\r\n", " wait for 50 ns;\r\n", " std.env.stop;\r\n", " wait;\r\n", " end process;\r\n", "end architecture myIRL;\r\n", "\r\n" ] } ], "source": [ "!cat {tb.ctx.path_prefix}/tb.vhdl" ] }, { "cell_type": "code", "execution_count": null, "id": "c53afbfe-8f5e-4072-94dd-6c962728215a", "metadata": {}, "outputs": [], "source": [] } ], "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": 5 }