{ "cells": [ { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "# Gray counter\n", "\n", "There are cases where you construct a logic out of a chain of primitive elements.\n", "Take a gray counter, for example. It is used often for cross-clock-domain negotations\n", "(dual clock FIFO fill counters).\n", "\n", "Note that most of the instancing below is done by logical gate connections without using a process." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from cyhdl import *\n", "\n", "\n", "@block\n", "def gray_counter1(clk : ClkSignal,\n", " enable : Signal,\n", " reset : ResetSignal,\n", " gray_count :Signal.Output):\n", "\n", " n = len(gray_count)\n", " gray_bits = [ Signal(bool(), name=\"u%d\" % i) for i in range(n) ]\n", " code, work = [ Signal(modbv()[n:]) for _ in range(2) ]\n", " flags = [ Signal(bool(), name=\"flags%d\" % i) for i in range(n + 1) ]\n", " toggle = Signal(bool(1))\n", " \n", " grc = gray_count.clone()\n", "\n", " # This creates connection instances:\n", " connections = [\n", " flags[0].wireup(False),\n", " code.wireup(concat(*reversed(gray_bits))),\n", " work.wireup(concat(\"1\", code[n-2:], toggle)),\n", " gray_count.wireup(grc)\n", " ] \n", " \n", " for i in range(n):\n", " v = work[i] & ~flags[i]\n", " connections += [\n", " gray_bits[i].wireup(v ^ grc[i]),\n", " flags[i + 1].wireup(flags[i] | v )\n", " ]\n", " \n", " @always_seq(clk.posedge, reset)\n", " def worker():\n", " if enable == True:\n", " grc.next = code\n", " toggle.next = ~toggle\n", "\n", " return instances()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Simulation\n", "\n", "This particular implementation translates via the yosys' CXXRTL simulator backend, using the 'new style' factory class.\n", "\n", "The above code is elaborated implicitely using direct RTL transfer, compiled into C++ code and executed. The result of this is a trace dump into a VCD file.\n", "\n", "For fun, we create a `Waveform` class from an Iterator first:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "from myirl.kernel.loops import Iterator\n", "\n", "class Waveform(Iterator):\n", " def __init__(self, val, name = 'waveform'):\n", " it = [ True if i == '1' else False for i in val]\n", " super().__init__(it, name)\n", " def convert(self, tgt, tsz = None):\n", " return \"True\" if self.val else \"False\"\n", " def resolve_type(self):\n", " return bool" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "from cyhdl import *\n", "\n", "class example_design(cyrite_factory.Module):\n", " # Note: in ipython, it is mandatory for a class to have an __init__ function\n", " # in order to retrieve the source for compilation\n", " def __init__(self, name, simclass, SIZE, *args):\n", " super().__init__(name, simclass, *args)\n", " self.size = SIZE\n", "\n", " @cyrite_factory.testbench('ns')\n", " def tb_gray1(self):\n", " SIZE = self.size\n", " clk = self.ClkSignal(name = 'clk')\n", " ce = self.Signal(bool(), name='ce')\n", "\n", " reset = self.ResetSignal(0, 1, isasync = True)\n", "\n", " dout = self.Signal(intbv()[SIZE:]) \n", "\n", " @self.always(delay(2))\n", " def clkgen():\n", " clk.next = ~clk\n", "\n", " gc = gray_counter1(clk, ce, reset, dout)\n", "\n", " @self.sequence\n", " def stim():\n", " ce.next = False\n", " reset.next = True\n", " yield delay(6)\n", " reset.next = False\n", " for i in Waveform(\"0011111111111111111111111111111111000\"):\n", " ce.next = i \n", " yield clk.negedge\n", "\n", " raise StopSimulation\n", "\n", " return instances() \n", " \n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Run the test bench for a few cycles." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "DEBUG LIB ALL ELEM (example_design 'myhdl_sim')\n", "DEBUG MAIN ELAB [Instance gray_counter1 I/F: [// ID: gray_counter1_0 ]]\n", " DEBUG components ['gray_counter1u_1u_1u_1u_5'] (example_design 'myhdl_sim') \n", "\u001b[32m Adding module with name `gray_counter1` \u001b[0m\n", "\u001b[7;34m FINALIZE implementation `gray_counter1` of `gray_counter1` \u001b[0m\n", "Compiling /tmp/myirl_myhdl_sim_ns4ba0x1/gray_counter1.pyx because it changed.\n", "[1/1] Cythonizing /tmp/myirl_myhdl_sim_ns4ba0x1/gray_counter1.pyx\n", "running build_ext\n", "building 'runtime.gray_counter1' extension\n", "creating build/temp.linux-x86_64-3.10/tmp/myirl_myhdl_sim_ns4ba0x1\n", "gcc -pthread -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -DCOSIM_NAMESPACE=gray_counter1 -Iruntime -I/tmp/myirl_myhdl_sim_ns4ba0x1/ -I/usr/share/yosys/include/backends/cxxrtl/runtime -I/usr/local/include/python3.10 -c /tmp/myirl_myhdl_sim_ns4ba0x1/gray_counter1.cpp -o build/temp.linux-x86_64-3.10/tmp/myirl_myhdl_sim_ns4ba0x1/gray_counter1.o\n", "gcc -pthread -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -DCOSIM_NAMESPACE=gray_counter1 -Iruntime -I/tmp/myirl_myhdl_sim_ns4ba0x1/ -I/usr/share/yosys/include/backends/cxxrtl/runtime -I/usr/local/include/python3.10 -c /tmp/myirl_myhdl_sim_ns4ba0x1/gray_counter1_rtl.cpp -o build/temp.linux-x86_64-3.10/tmp/myirl_myhdl_sim_ns4ba0x1/gray_counter1_rtl.o\n", "g++ -pthread -shared -Wl,--strip-all build/temp.linux-x86_64-3.10/tmp/myirl_myhdl_sim_ns4ba0x1/gray_counter1.o build/temp.linux-x86_64-3.10/tmp/myirl_myhdl_sim_ns4ba0x1/gray_counter1_rtl.o -L/usr/local/lib -o build/lib.linux-x86_64-3.10/runtime/gray_counter1.cpython-310-x86_64-linux-gnu.so\n", "copying build/lib.linux-x86_64-3.10/runtime/gray_counter1.cpython-310-x86_64-linux-gnu.so -> runtime\n", "Open for writing: testbench.vcd\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "\u001b[32mUsing '/tmp/myirl_myhdl_sim_ns4ba0x1/' for output\u001b[0m\n", "\u001b[7;34mSTOP SIMULATION @152\u001b[0m\n" ] }, { "data": { "text/plain": [ "0" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from yosys.simulator import CXXRTL\n", "\n", "# Alternatively, we can fall back to GHDL:\n", "from myirl.test.ghdl import GHDL\n", "\n", "SIM = CXXRTL\n", "\n", "\n", "d = example_design(\"myhdl_sim\", SIM, 5)\n", "t = d.tb_gray1()\n", "t.run(180, wavetrace = t.name + \".vcd\")\n", " " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Waveform display\n", "\n", "The resulting waveform is displayed using a signal filter configuration:" ] }, { "cell_type": "code", "execution_count": 5, "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": [ "waveconfig = { '.reset' : None, '.clk' : None, '.enable' : None }\n", "for i in range(5):\n", " waveconfig[\".u%d\" % i] = None\n", "\n", "from cyrite import waveutils\n", "waveutils.draw_wavetrace(t, 'testbench.vcd', 'clk', cfg = waveconfig)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Notes\n", "\n", "This is not so much a recommended design style, as the above `gray_counter1` implementation can be owned by several contexts, as it's defined as a global function.\n", "If several different contexts are referring to it as a top level object, unwanted effects may occur.\n", "\n", "Therefore it is better to stick all kind of parametrizable top level objects into a design context class." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## HDL translation\n", "\n", "To explicitely convert to HDL, we create an instance and elaborate:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[32m Module gray_counter1: Existing instance gray_counter1, rename to gray_counter1_1 \u001b[0m\n", " Writing 'gray_counter1_1' to file /tmp/myirl_gray_counter1_g0ecyrbn/gray_counter1_1.v \n" ] } ], "source": [ "def convert(SIZE):\n", " clk = ClkSignal()\n", " ce = Signal(bool())\n", "\n", " reset = ResetSignal(0, 1, isasync = True)\n", "\n", " dout = Signal(intbv()[SIZE:]) \n", "\n", " gc = gray_counter1(clk, ce, reset, dout)\n", " \n", " f = gc.elab(targets.Verilog)\n", " return f\n", "\n", "f = convert(8)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "// File generated from source:\r\n", "// /tmp/ipykernel_1472/4268148147.py\r\n", "// (c) 2016-2022 section5.ch\r\n", "// Modifications may be lost, edit the source file instead.\r\n", "\r\n", "`timescale 1 ns / 1 ps\r\n", "`include \"aux.v\"\r\n", "// Architecture cyriteHDL\r\n", "\r\n", "module gray_counter1_1\r\n", " (\r\n", " input wire /* std_ulogic */ clk,\r\n", " input wire /* std_ulogic */ enable,\r\n", " input wire /* std_ulogic */ reset,\r\n", " output wire [7:0] gray_count\r\n", " );\r\n", " // Local type declarations\r\n", " // Signal declarations\r\n", " reg [7:0] grc;\r\n", " reg /* std_ulogic */ toggle;\r\n", " wire [7:0] code;\r\n", " wire /* std_ulogic */ flags0;\r\n", " wire /* std_ulogic */ u7;\r\n", " wire /* std_ulogic */ u6;\r\n", " wire /* std_ulogic */ u5;\r\n", " wire /* std_ulogic */ u4;\r\n", " wire /* std_ulogic */ u3;\r\n", " wire /* std_ulogic */ u2;\r\n", " wire /* std_ulogic */ u1;\r\n", " wire /* std_ulogic */ u0;\r\n", " wire [7:0] work;\r\n", " wire /* std_ulogic */ flags1;\r\n", " wire /* std_ulogic */ flags2;\r\n", " wire /* std_ulogic */ flags3;\r\n", " wire /* std_ulogic */ flags4;\r\n", " wire /* std_ulogic */ flags5;\r\n", " wire /* std_ulogic */ flags6;\r\n", " wire /* std_ulogic */ flags7;\r\n", " wire /* std_ulogic */ flags8;\r\n", " \r\n", " always @ (posedge clk or posedge reset) begin : WORKER\r\n", " if (reset == 1'b1) begin\r\n", " grc <= 8'h00; /* default */\r\n", " toggle <= 1'b1; /* default */\r\n", " end else begin\r\n", " if ((enable == 1'b1)) begin\r\n", " grc <= code;\r\n", " toggle <= ~toggle;\r\n", " end\r\n", " end\r\n", " end\r\n", " assign flags0 = 1'b0;\r\n", " assign code = {u7, u6, u5, u4, u3, u2, u1, u0};\r\n", " assign work = {1'b1, code[6-1:0], toggle};\r\n", " assign gray_count = grc;\r\n", " assign u0 = ((work[0] & ~flags0) ^ grc[0]);\r\n", " assign flags1 = (flags0 | (work[0] & ~flags0));\r\n", " assign u1 = ((work[1] & ~flags1) ^ grc[1]);\r\n", " assign flags2 = (flags1 | (work[1] & ~flags1));\r\n", " assign u2 = ((work[2] & ~flags2) ^ grc[2]);\r\n", " assign flags3 = (flags2 | (work[2] & ~flags2));\r\n", " assign u3 = ((work[3] & ~flags3) ^ grc[3]);\r\n", " assign flags4 = (flags3 | (work[3] & ~flags3));\r\n", " assign u4 = ((work[4] & ~flags4) ^ grc[4]);\r\n", " assign flags5 = (flags4 | (work[4] & ~flags4));\r\n", " assign u5 = ((work[5] & ~flags5) ^ grc[5]);\r\n", " assign flags6 = (flags5 | (work[5] & ~flags5));\r\n", " assign u6 = ((work[6] & ~flags6) ^ grc[6]);\r\n", " assign flags7 = (flags6 | (work[6] & ~flags6));\r\n", " assign u7 = ((work[7] & ~flags7) ^ grc[7]);\r\n", " assign flags8 = (flags7 | (work[7] & ~flags7));\r\n", "endmodule // gray_counter1_1\r\n" ] } ], "source": [ "!cat {f[0]}" ] }, { "cell_type": "code", "execution_count": null, "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.10.0" } }, "nbformat": 4, "nbformat_minor": 4 }