{ "cells": [ { "cell_type": "markdown", "id": "98ef76db-885d-4e99-92f0-790bea597115", "metadata": {}, "source": [ "# Composite signal classes\n", "\n", "Composite classes explicitely act as a **local** custom signal type in the foreground, but can implicitely generate hardware instances. Obviously, a composite class element can not be passed through the interface.\n", "\n", "For example, one might want to instance many `Counter` signals with incrementing/reset logic spelled out explicitely. Later, it might be decided to swap them out against a Gray coded variant. In this case, the increment logic is internally a different one, however, we can handle this within the class \n", "that we still can write `counter.next = counter + 1`. \n", "\n", "Another use case is when it is desired to create logic elements with one main output signal and a few input signals, e.g. instead of\n", "\n", "`inst = unit(clk, en, val, WIDTH=8)`\n", "\n", "we'd spell\n", "\n", "`val = Unit(clk, en, WIDTH=8)`\n", "\n", "where `val` is again a signal instance. However it might be wise to check if a `@blackbox_inline` implementation is the better option, when `val` is driven from within `unit`.\n", "\n", "Some libraries may choose to use lower caps for the classic instanced units whereas the class style is used for a Composite generator class.\n", "Due to VHDL not being case sensitive, those two styles should not be mixed." ] }, { "cell_type": "markdown", "id": "549f9da2-8b7d-44d7-9c98-a01fc3ed8e44", "metadata": {}, "source": [ "## Counter example" ] }, { "cell_type": "markdown", "id": "93374264-4cc5-4fc4-b1c7-93af8e8fb95a", "metadata": {}, "source": [ "Let's try a simple counter scenario. The idea is, to swap the `c` Signal against an extended Gray counter later on." ] }, { "cell_type": "code", "execution_count": 1, "id": "7afe35c2-d5ba-4337-a718-4c122f3cde75", "metadata": {}, "outputs": [], "source": [ "from myirl.emulation.myhdl import *\n", "\n", "from myirl.library.basictypes import *\n", "\n", "@block\n", "def counter_unit(clk : ClkSignal, reset: ResetSignal, en : Bool, finished : Bool.Output, COUNT_END, WIDTH = 8):\n", " c = Signal(intbv(-1)[WIDTH:])\n", " \n", " @always_seq(clk.posedge, reset)\n", " def worker():\n", " if c == COUNT_END:\n", " finished.next = True\n", " elif en:\n", " c.next = c + 1\n", " finished.next = False\n", " \n", "\n", " return instances()" ] }, { "cell_type": "code", "execution_count": 2, "id": "4856630f-4e3b-4c41-9e91-2d9dbc200b82", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "FALLBACK: UNHANDLED ROOT CLASS , create new context\n", "Using default for WIDTH: 8\n", " WIDTH: use default 8 \n", " Writing 'counter_unit' to file /tmp/myirl_counter_unit_0o0w51yp/counter_unit.vhdl \n", "Warning: Implicit truncation of ADD(c, C:1) result\n" ] } ], "source": [ "clk = ClkSignal()\n", "rst = ResetSignal(0, 1)\n", "en = Bool()\n", "fin = Bool()\n", "uut = counter_unit(clk, rst, en, fin, COUNT_END = 144)\n", "\n", "f = uut.elab(targets.VHDL)" ] }, { "cell_type": "markdown", "id": "d3d28558-0cbc-456c-86e9-812f91e02862", "metadata": {}, "source": [ "## Custom counter class: Gray coding\n", "\n", "We now swap out the counter signal against a gray counter with minimal changes in the actual RTL description.\n", "A bit of derivation framework has to be added below.\n", "\n", "First, we create an assignment generator class for the gray counter signal class:" ] }, { "cell_type": "code", "execution_count": 3, "id": "37205a27-a19c-4596-97a0-1bc84ee4e33f", "metadata": {}, "outputs": [], "source": [ "from myirl.emulation.myhdl import *\n", "\n", "class GCAssign(base.SigAssign):\n", " def __init__(self, sig, other):\n", " print(\"INIT GCASSIGN\")\n", "\n", " self._assignments = [\n", " sig.toggle.set(~sig.toggle),\n", " sig.reg_code.set(sig.next_code)\n", " ]\n", " super().__init__(sig, other) \n", "\n", " def emit(self, ctx):\n", " for a in self._assignments:\n", " a.emit(ctx)" ] }, { "cell_type": "markdown", "id": "cff5a6f0-32aa-4510-aa9d-02984a1e6575", "metadata": {}, "source": [ "Like the `@blackbox_component` decorator, the `@Composite.block` creates IRL objects from inside a class." ] }, { "cell_type": "code", "execution_count": 4, "id": "0a547ca0-df64-4227-a3ff-cd77ab75f7c9", "metadata": {}, "outputs": [], "source": [ "from examples.lib_blackbox import blackbox_component\n", "from myirl.composite import Composite\n", "\n", "import myirl\n", "\n", "# Not a container, we don't pass this through the hierarchy\n", "class GrayCounter(Composite):\n", " def __init__(self, n):\n", " self.n = n\n", " self.toggle = Signal(bool(1), name = \"toggle\")\n", " self.work, self.reg_code, self.next_code = [ Signal(intbv(0)[n:]) for _ in range(3) ]\n", " self.work.rename(\"work\")\n", " self.flags = [ Signal(bool()) for _ in range(n + 1) ]\n", " self.gbits = [ Signal(bool(), name=\"u%d\" % i) for i in range(n) ]\n", " \n", " instances = [\n", " self.bb_gc(self.reg_code, self.toggle, self.next_code )\n", " ]\n", "\n", " super().__init__(instances)\n", "\n", " def get(self):\n", " return self.next_code\n", " \n", " def set(self, other):\n", " if isinstance(other, int):\n", " return base.GenAssign(self.reg_code, other)\n", " elif isinstance(other, base.Add):\n", " return GCAssign(self, other)\n", " else:\n", " raise ValueError(\"Trying to assign to %s\" % type(other))\n", "\n", " def size(self, effective = None):\n", " return self.n\n", "\n", " \n", " def evaluate(self):\n", " self.toggle.evaluate()\n", " return self.next_code.evaluate()\n", "\n", " # Manual setting of source and drivers,\n", " # better would be to obtain it automatically from the logic\n", " def get_sources(self, sigs):\n", " for s in self.toggle, self.reg_code, self.next_code:\n", " sigs[s.identifier] = s\n", "\n", " def get_drivers(self, sigs):\n", " for s in self.toggle, self.reg_code:\n", " sigs[s.identifier] = s\n", "\n", " \n", " @Composite.block\n", " def bb_gc(self,\n", " cur_code\t: Signal,\n", " toggle\t: Signal.Type(bool),\n", " next_code : Signal.Output):\n", "\n", "\n", " connections = self.logic(toggle, cur_code)\n", "\n", " connections += [\n", " next_code.set(myirl.concat(*reversed(self.gbits)))\n", " ]\n", "\n", " return connections\n", "\n", " \n", " def logic(self, toggle, cur_code):\n", " connections = [\n", " self.flags[0] .wireup(False),\n", " self.work\t .wireup(\n", " base.Concat(\"1\", *reversed(self.gbits[:self.n-2]), toggle))\n", " ] \n", "\n", " for i in range(self.n):\n", " v = self.work[i] & ~self.flags[i]\n", " connections += [\n", " self.gbits[i]\t .wireup (v ^ cur_code[i]),\n", " self.flags[i + 1] .wireup (self.flags[i] | v )\n", " ]\n", "\n", " return connections\n" ] }, { "cell_type": "markdown", "id": "0a6fff82-bc88-4f6a-80cd-d2830c47a18f", "metadata": {}, "source": [ "**Note**: Instead of instancing an owned `@Composite.block`, we may also instance external `@block` units, likewise.\n", "\n", "Now our gray counter instance looks like this:" ] }, { "cell_type": "code", "execution_count": 5, "id": "ad9c66f1-e0d8-43c5-b8e1-917b8a645d1b", "metadata": {}, "outputs": [], "source": [ "from cyrite.library.counter.gray import graycode\n", "\n", "@block\n", "def counter_unit_gray(clk : ClkSignal, reset : ResetSignal, en : Bool, finished : Bool.Output,\n", " COUNT_END, WIDTH = 8):\n", " c = GrayCounter(WIDTH)\n", " \n", " # Need to translate the end value to gray code:\n", " endval = int(graycode(COUNT_END, WIDTH))\n", " \n", " @always_seq(clk.posedge, reset)\n", " def worker():\n", " if c == endval:\n", " finished.next = True\n", " elif en:\n", " c.next = c + 1\n", " finished.next = False\n", "\n", " return instances()" ] }, { "cell_type": "markdown", "id": "e6a9fc54-7768-4330-9829-5f72568141ad", "metadata": {}, "source": [ "All combinatorial logic is actually buried in the associated inline component, however we have to explicitely call the `graycode()` function to translate the binary value into the corresponding gray code." ] }, { "cell_type": "code", "execution_count": 6, "id": "f6fe9a31-63f8-4685-9480-5094f34f54e1", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/home/testing/.local/lib/python3.9/site-packages/myirl-0.0.0-py3.9-linux-x86_64.egg/myirl/emulation/myhdl2irl.py:655: UserWarning: /tmp/ipykernel_26086/1827286769.py:tb_counter():18 Replacing logical `not` by inversion\n", " warnings.warn(self.get_location(node) + \\\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "FALLBACK: UNHANDLED ROOT CLASS , create new context\n", "FALLBACK: UNHANDLED ROOT CLASS , create new context\n", "Using default for WIDTH: 8\n", " WIDTH: use default 8 \n", "\u001b[7;35m Declare obj 'bb_gc' in context '(EmulationModule 'tb_counter')' \u001b[0m\n", "\u001b[32m DEBUG Inline instance [_inline 'bb_gc/bb_gc'] \u001b[0m\n", " Writing 'bb_gc' to file /tmp/bb_gc.vhdl \n", "INIT GCASSIGN\n", " Writing 'counter_unit_gray' to file /tmp/counter_unit_gray.vhdl \n", " Writing 'tb_counter' to file /tmp/tb_counter.vhdl \n", "Warning: Implicit truncation of ADD(counter, C:1) result\n", " Creating library file /tmp/module_defs.vhdl \n", "==== COSIM stdout ====\n", "analyze /home/testing/.local/lib/python3.9/site-packages/myirl-0.0.0-py3.9-linux-x86_64.egg/myirl/targets/../test/vhdl/txt_util.vhdl\n", "analyze /home/testing/.local/lib/python3.9/site-packages/myirl-0.0.0-py3.9-linux-x86_64.egg/myirl/targets/libmyirl.vhdl\n", "analyze /tmp/bb_gc.vhdl\n", "analyze /tmp/counter_unit_gray.vhdl\n", "analyze /tmp/tb_counter.vhdl\n", "elaborate tb_counter\n", "\n", "==== COSIM stderr ====\n", "/tmp/bb_gc.vhdl:27:12:warning: declaration of \"work\" hides library \"tb_counterded7\" [-Whide]\n", "\n", "==== COSIM stdout ====\n", "DONE\n", "simulation stopped @141ns\n", "\n" ] }, { "data": { "text/plain": [ "0" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from myirl.test.ghdl import GHDL\n", "\n", "@sim.testbench(GHDL, 'ns')\n", "@block\n", "def tb_counter(unit):\n", " clk = ClkSignal()\n", " rst = ResetSignal(0, 1)\n", " en, finished = [ Bool() for _ in range(2) ]\n", " \n", " counter = Signal(intbv(0)[8:])\n", " \n", " @always(delay(3))\n", " def clkgen():\n", " clk.next = ~clk\n", " \n", " @always_seq(clk.posedge, rst)\n", " def worker():\n", " if en and not finished:\n", " counter.next = counter + 1\n", " \n", " @instance\n", " def main():\n", " rst.next = True\n", " yield delay(20)\n", " rst.next = False\n", " en.next = True\n", " \n", " while finished == False:\n", " yield clk.posedge\n", " \n", " assert counter == 20\n", " \n", " print(\"DONE\")\n", " \n", " raise StopSimulation\n", " \n", " uut = unit(clk, rst, en, finished, COUNT_END = 20)\n", " \n", " return instances()\n", " \n", "tb = tb_counter(counter_unit_gray)\n", "tb.run(-1, wavetrace = 'test_counter.vcd')" ] }, { "cell_type": "markdown", "id": "48e846a2-75c9-4bf0-9912-f1f7fb9d073a", "metadata": {}, "source": [ "## Application notes\n", "\n", "In particular when designing FIFOs, the instances of specific counters might be a design choice.\n", "It makes then sense to configure the counter type (Binary, LFSR, Gray, ...) during initialization of a factory class as a `self.Counter` member.\n", "Eventually, start and end values may have to be known or set to a specific value.\n", "\n", "For Gray counters, there is a conversion function `graycode()`, however for LFSR sequences, there is no such thing due to the non-deterministic sequence. For efficient search algorithms, you may want to implement your own depending on the counter size." ] } ], "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 }