{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# True dual port memories\n", "\n", "Describing dual port memory and getting it synthesized correctly is cumbersome on some targets.\n", "Therefore, this will only demonstrate a simple approach to generate memory implementations that simulate correctly.\n", "Using [Signal arrays](arrays.ipynb), a simple `r1w1` simplex dual port memory can be inferred to HDL.\n", "This modification enables shared variable output for VHDL to support true dual port behaviour.\n", "The general strategy with the MyIRL synthesis is to use a blackbox element (rely on the library)" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from myirl import *\n", "import random" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Dual port memories for VHDL\n", "\n", "We can not use a `SigArray` for the RAM, because a true dual port RAM would provoke unresolved multiple drivers.\n", "Therefore we hack the `myirl.lists.SigArray` class by deriving and choosing a different element in return of the `__getitem__` member which uses a shared variable.\n", "\n", "**Note** Not working in VHDL-2008" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "from myirl import lists\n", "from myirl.kernel import extensions, utils\n", "\n", "from myirl import targets\n", "\n", "class ArrayElem(lists.SigIndexed):\n", " decl_type_vhdl = \"shared variable\"\n", "\n", " def set(self, val):\n", " w = self.size()\n", " return CellAssign(self.seq, self.index, val, w)\n", "\n", "class CellAssign: # extensions.ElemAssign\n", " def __init__(self, parent, portion, val, width):\n", " self._vref = parent\n", " self.portion = portion\n", " self.value = val\n", " self.width = width\n", " \n", " def emit(self, context):\n", " tgt = context.target\n", " n, v = self._vref.identifier, self.value\n", " p = self.portion\n", " sz = self.width\n", " if tgt.lang == 'VHDL':\n", " context.output(\"%s(to_integer(%s)) := %s;\\n\" % (n, p, base.convert(v, tgt, sz))) \n", " elif tgt.lang == 'Verilog':\n", " context.output(\"%s[%s] <= %s;\\n\" % (n, p, base.convert(v, tgt, sz))) \n", " else:\n", " raise TypeError(\"Unsupported target %s\" % type(tgt))\n", "\n", "\n", " def get_sources(self, srcs):\n", " if isinstance(self.value, base.Sig):\n", " self.value.get_sources(srcs)\n", " \n", " def get_drivers(self, drvs):\n", " pass\n", " \n", "\n", "class RamBuffer(lists.SigArray): \n", " init = True\n", " sigclass = targets.Verilog.REG\n", " def __getitem__(self, item):\n", " if isinstance(item, (base.Sig, int)):\n", " # We can not just return self.val[item], as the iterator\n", " # has not initialized yet.\n", " return ArrayElem(self, item)\n", " else:\n", " raise TypeError(\"Multi item slicing of iterator not supported\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then define a RAM port (legacy class constructs will suffice) and the actual RAM implementation:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "@container()\n", "class RamPort:\n", " _inputs = ['we', 'wa', 'ra', 'wd']\n", " _outputs = ['rd']\n", " _other = ['clk']\n", " def __init__(self, AWIDTH, DWIDTH):\n", " self.clk = ClkSignal()\n", " self.we = Signal(bool())\n", " self.ra, self.wa = [ Signal(intbv()[AWIDTH:]) for i in range(2) ]\n", " self.rd, self.wd = [ Signal(intbv()[DWIDTH:]) for i in range(2) ] \n", " \n", "@block\n", "def tdp_ram(pa : RamPort, pb : RamPort, INITDATA : PassThrough(list)):\n", " inst = []\n", " \n", " def gen_logic(p, i):\n", " \"Generate port mechanics inline\"\n", " \n", " rd = p.rd.clone(\"rd%d\" % i)\n", " \n", " @genprocess(p.clk, EDGE=p.clk.POS)\n", " def proc():\n", " yield [\n", " proc.If(p.we == True).Then(\n", " buf[p.wa].set(p.wd)\n", " ),\n", " rd.set(buf[p.ra])\n", " ]\n", " proc.rename(\"proc%d\" % i)\n", " wirings = [\n", " p.rd.wireup(rd)\n", " ]\n", " return proc, wirings\n", "\n", " buf = RamBuffer(INITDATA)\n", " \n", " for i, p in enumerate([pa, pb]):\n", " inst += (gen_logic(p, i))\n", " \n", " return instances()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Basic checks\n", "\n", "Verify we can evaluate the content:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "intbv(138)" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a = RamBuffer([intbv(i + 0x80)[8:] for i in range(200) ])\n", "b = Signal(intbv(10)[5:])\n", "a[b].evaluate()" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "intbv(128)" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a[0].wire" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "from myirl import targets\n", "from myirl.test.common_test import run_ghdl, run_icarus\n", "\n", "random.seed(0)\n", "\n", "def test_vhdl():\n", " RAM_CONTENT = [ intbv(random.randint(0, 2 ** 9))[8:] for i in range(2 ** 9) ]\n", " pa, pb = [ RamPort(AWIDTH=9, DWIDTH=8) for i in range(2) ]\n", " inst = tdp_ram(pa, pb, RAM_CONTENT)\n", " \n", " f = inst.elab(targets.VHDL, elab_all = True)\n", " run_ghdl(f, inst, std = \"93\", debug = True) # Note we run with std '93'\n", " return f" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " Writing 'tdp_ram' to file /tmp/myirl_tdp_ram_kh2vq5q2/tdp_ram.vhdl \n", " Creating library file module_defs.vhdl \n", "WORK DIR of instance [Instance tdp_ram I/F: [// ID: tdp_ram_0 ]] /tmp/myirl_tdp_ram_kh2vq5q2/\n", "==== COSIM stderr ====\n", "/tmp/myirl_tdp_ram_kh2vq5q2/tdp_ram.vhdl:46:17: signal \"v_ce16\" is not a variable to be assigned\n", "/tmp/myirl_tdp_ram_kh2vq5q2/tdp_ram.vhdl:58:17: signal \"v_ce16\" is not a variable to be assigned\n", "\n" ] } ], "source": [ "f = test_vhdl()" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "! grep -A 20 MyIRL {f[0]}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Verilog variant\n", "\n", "The same design translated to Verilog, run through the icarus simulator." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "def test_verilog():\n", " RAM_CONTENT = [ intbv(random.randint(0, 2 ** 9))[8:] for i in range(2 ** 9) ]\n", " pa, pb = [ RamPort(AWIDTH=9, DWIDTH=8) for i in range(2) ]\n", " inst = tdp_ram(pa, pb, RAM_CONTENT)\n", " \n", " f = inst.elab(targets.Verilog)\n", " run_icarus(f, inst, debug = True)\n", " return f" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[32m Module tdp_ram: Existing instance tdp_ram, rename to tdp_ram_1 \u001b[0m\n", " Writing 'tdp_ram_1' to file /tmp/myirl_tdp_ram_xwmtjh_b/tdp_ram_1.v \n", "DEBUG: Source 'v_6288' is logic: \n", "ICARUS FILES ['/tmp/myirl_tdp_ram_xwmtjh_b/tdp_ram_1.v']\n" ] } ], "source": [ "f = test_verilog()" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "// File generated from source:\r\n", "// /tmp/ipykernel_5446/1352711214.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 myIRL\r\n", "\r\n", "module tdp_ram_1\r\n", " (\r\n", " input wire pa_clk,\r\n", " input wire pa_we,\r\n", " input wire [8:0] pa_ra,\r\n", " input wire [8:0] pa_wa,\r\n", " output wire [7:0] pa_rd,\r\n", " input wire [7:0] pa_wd,\r\n", " input wire pb_clk,\r\n", " input wire pb_we,\r\n", " input wire [8:0] pb_ra,\r\n", " input wire [8:0] pb_wa,\r\n", " output wire [7:0] pb_rd,\r\n", " input wire [7:0] pb_wd\r\n", " );\r\n", " // Local type declarations\r\n", " // Signal declarations\r\n", " reg [7:0] rd0;\r\n", " reg [7:0] v_6288[511:0];\r\n", " initial begin\r\n", " end\r\n", " reg [7:0] rd1;\r\n", " \r\n", " always @ (posedge pa_clk ) begin : PROC0\r\n", " if ((pa_we == 1'b1)) begin\r\n", " v_6288[pa_wa] <= pa_wd;\r\n", " end\r\n", " rd0 <= v_6288[pa_ra];\r\n", " end\r\n", " assign pa_rd = rd0;\r\n", " \r\n", " always @ (posedge pb_clk ) begin : PROC1\r\n", " if ((pb_we == 1'b1)) begin\r\n", " v_6288[pb_wa] <= pb_wd;\r\n", " end\r\n", " rd1 <= v_6288[pb_ra];\r\n", " end\r\n", " assign pb_rd = rd1;\r\n", "endmodule // tdp_ram_1\r\n" ] } ], "source": [ "!cat {f[0]}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# TDPRAM verification\n", "\n", "TDPRAM verification can be done on various levels:\n", "\n", "* Functional verification without collision detection via CXXRTL\n", "* HDL based model simulation against a Verilog or VHDL primitive vendor model with collision detection\n", "* Post-map simulation after mapping using a synthesis step\n", "\n", "The fine detail in the above HDL models: depending on the RAM instance (as VHDL shared variable or a Verilog signal), as well as the specific assignment mode (blocking or non-blocking) in the Verilog inference do have a **crucial impact** on the read/write priorities of such RAMs.\n", "\n", "Most TDP RAM models allow the following priority modes.\n", "* TRANSPARENCY: When a value is written on a port, it is available on the data output on the next clock cycle\n", "* READ_BEFORE_WRITE: The previous value of the memory cell at the given address is available on the output on the next clock cycle\n", "\n", "For concurrent accesses from two ports, even more complexity is introduced. For example, a WRITE action on one port and a READ on the other from the same address can also be subject to the priority configuration, moreover, different clock domains are getting into the play.\n", "\n", "## Different data I/O widths\n", "\n", "Several combinations of input and output port widths are supported by TDPRAM architectures in general. If both data widths are in an integer ratio, only address translation and mapping is relevant for either bitwise slicing or concatenation. If not, alignment rules may apply.\n", "\n", "As these properties are very technology dependent, it turns out to be complex to generalize test routines.\n", "This is addressed by a generic `tdp_tester` base class, containing a few common test routines. By derivation, customized TDP RAM testers are created that run a customized test for a particular architecture.\n", "\n", "**Note** This may require a particular installation of the cyrite tdpram library" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "from cyrite.library.ram.tdpram import TDPRamGenerator\n", "\n", "from myirl.test.ghdl import GHDL93\n", "from myirl.test.icarus import ICARUS\n", "\n", "class my_tester(TDPRamGenerator):\n", " def __init__(self, SIMULATOR = ICARUS, delta = 0.2,\n", " name = \"tb_priorities\"):\n", " SIZE = 320*64\n", "\n", " super().__init__(name, SIMULATOR, size = SIZE)\n", "\n", " # Enable strict transparency check\n", " self.STRICT_XTRANSPARENCY_CHECK = True\n", " # Use generic wrapper as Unit Under Test:\n", " self.uut = self.tdpram_wrapper\n", " self.MODE_RAW = 'NORMAL'\n", " self.MODE_WT = 'WRITETHROUGH'\n", " self.undefined = Undefined\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Priority tests\n", "\n", "The `tb_priorities` test uses two particularely detuned clocks for each RAM port and a small DELTA window for coincidence checks. It verifies that the RAM model reports an `Undefined` signal value when simultaneous writes/reads occur in `WRITETHROUGH` (Transparency) mode." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[7;35m Declare obj 'tb_priorities' in context '(my_tester 'tb_priorities')'() \u001b[0m\n", " DSIZE: use default 16 \n", " ASIZE: use default 10 \n", " CLKA_PERIOD: use default 3.02 \n", " CLKB_PERIOD: use default 3.61 \n", " OUT_REG: use default False \n", "\u001b[7;35m Declare obj 'trigger_pulse' in context '(my_tester 'tb_priorities')'() \u001b[0m\n", "\u001b[7;35m Declare obj 'coincidence_prio' in context '(my_tester 'tb_priorities')'() \u001b[0m\n", " DELTA: use default 0.2 \n", "\u001b[7;35m Declare obj 'clkpulse' in context '(my_tester 'tb_priorities')'() \u001b[0m\n", "DEBUG: INSTANCING uut with wmode = WRITETHROUGH\n", "\u001b[7;35m Declare obj 'tdpram_wrapper' in context '(my_tester 'tb_priorities')'() \u001b[0m\n", "\u001b[7;35m Declare obj 'tdpram' in context '(my_tester 'tb_priorities')'() \u001b[0m\n", " INITDATA: use default None \n", " DELTA: use default 0.2 \n", " DELTA: use default 0.2 \n", " DELTA: use default 0.2 \n", " Writing 'gray_counter' to file /tmp/gray_counter.v \n", " Writing 'tdpram' to file /tmp/tdpram.v \n", "DEBUG: Source 'v_0bf9' is logic: \n", " Writing 'tdpram_wrapper' to file /tmp/tdpram_wrapper.v \n", " Writing 'clkpulse' to file /tmp/clkpulse.v \n", " Writing 'coincidence_prio' to file /tmp/coincidence_prio.v \n", " Writing 'trigger_pulse' to file /tmp/trigger_pulse.v \n", " Writing 'tb_priorities' to file /tmp/tb_priorities.v \n", "Warning: Implicit truncation of ADD(a_addr_auto, C:1) result\n", "Warning: Implicit truncation of ADD(b_addr_auto, C:1) result\n", " Note: Changing library path prefix to /tmp/ \n", " Creating library file /tmp/module_defs.v \n", "DEBUG FILES ['/tmp/gray_counter.v', '/tmp/tdpram.v', '/tmp/tdpram_wrapper.v', '/tmp/clkpulse.v', '/tmp/coincidence_prio.v', '/tmp/trigger_pulse.v', '/tmp/tb_priorities.v']\n", "==== COSIM stdout ====\n", "VCD info: dumpfile tb_priorities.vcd opened for output.\n", "is: 0x640f wants: 0x640f \n", "is: 0xab82 wants: 0xab82 \n", "is: 0xa957 wants: 0xa957 \n", "RUN WRITE SEQUENCE... \n", "====== REPORT ====== \n", "Coincidence occured. GOOD. \n", "Coincidence detected. GOOD. \n", "Stop Simulation\n", "\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "/home/cyrite/.local/lib/python3.10/site-packages/cyritehdl-0.1b0-py3.10-linux-x86_64.egg/myirl/kernel/components.py:116: UserWarning: Fallback: Pass through other argument for arg WMODE ()\n", " base.warnings.warn(msg)\n", "/home/cyrite/.local/lib/python3.10/site-packages/cyritehdl-0.1b0-py3.10-linux-x86_64.egg/myirl/kernel/components.py:116: UserWarning: Fallback: Pass through other argument for arg DELTA ()\n", " base.warnings.warn(msg)\n", "/home/cyrite/.local/lib/python3.10/site-packages/cyritehdl-0.1b0-py3.10-linux-x86_64.egg/myirl/kernel/components.py:116: UserWarning: Fallback: Pass through other argument for arg wmode ()\n", " base.warnings.warn(msg)\n", "\u001b[32mDEBUG IGNORING SET INIT FOR CLKSIGNAL\u001b[0m\n", "\u001b[32mDEBUG IGNORING SET INIT FOR CLKSIGNAL\u001b[0m\n", "/home/cyrite/.local/lib/python3.10/site-packages/cyritehdl-0.1b0-py3.10-linux-x86_64.egg/myirl/test/icarus.py:50: UserWarning: Ignoring wavetrace argument for Verilog simulator\n", " warnings.warn(\"Ignoring wavetrace argument for Verilog simulator\")\n", "\u001b[32mDEBUG IGNORING SET INIT FOR CLKSIGNAL\u001b[0m\n", "/home/cyrite/.local/lib/python3.10/site-packages/cyritehdl-0.1b0-py3.10-linux-x86_64.egg/myirl/targets/verilog.py:1710: UserWarning: Possible clock forwarding `bc_8233_clk`\n", " warnings.warn(\"Possible clock forwarding `%s`\" % n)\n", "\u001b[32mDEBUG IGNORING SET INIT FOR CLKSIGNAL\u001b[0m\n", "/home/cyrite/.local/lib/python3.10/site-packages/cyritehdl-0.1b0-py3.10-linux-x86_64.egg/myirl/targets/verilog.py:1710: UserWarning: Possible clock forwarding `bc_f268_clk`\n", " warnings.warn(\"Possible clock forwarding `%s`\" % n)\n", "/home/cyrite/.local/lib/python3.10/site-packages/cyritehdl-0.1b0-py3.10-linux-x86_64.egg/myirl/targets/verilog.py:1626: UserWarning: Renaming reserved identifier: reg -> reg_8ef0\n", " warnings.warn(\"Renaming reserved identifier: %s -> %s\" % \\\n", "/home/cyrite/.local/lib/python3.10/site-packages/cyritehdl-0.1b0-py3.10-linux-x86_64.egg/myirl/targets/verilog.py:1626: UserWarning: Renaming reserved identifier: reg -> reg_f03d\n", " warnings.warn(\"Renaming reserved identifier: %s -> %s\" % \\\n" ] }, { "data": { "text/plain": [ "0" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\n", "d = my_tester()\n", "tb = d.tb_priorities(WMODE = \"WRITETHROUGH\")\n", "\n", "tb.run(2000, debug = True, wavetrace = \"test_ghdl.vcd\")\n" ] }, { "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 }