Skip to content

Instantly share code, notes, and snippets.

@hackfin
Created April 9, 2024 08:52
Show Gist options
  • Select an option

  • Save hackfin/6b53ceec5f6213cc36abe7c9b9f9172c to your computer and use it in GitHub Desktop.

Select an option

Save hackfin/6b53ceec5f6213cc36abe7c9b9f9172c to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"id": "4ca3db65-15ca-4bf6-b75c-571311419137",
"metadata": {},
"source": [
"# Design methods: Contexts, modules, functions\n",
"\n",
"For verification purposes, a reusable construct that is proven valid is most worthy in complex projects. Therefore a classic HDL developer would write a configureable module and integrate that into his hierarchy.\n",
"\n",
"VHDL, for instance, supports function calls, but does not distinguish between synthesizeable and simulation constructs. Here, the story is different: Function calls can just execute and return a result, or they can yield hardware elements (the generator way) that are synthesizeable."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "fc4bc83a-f313-4b49-ba11-c8c5db140349",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"import sys\n",
"sys.path.insert(0, \"../..\")"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "d9ab874e-8c4d-4994-a84f-b39dd74f8a42",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"from cyhdl import *"
]
},
{
"cell_type": "markdown",
"id": "120c2380-10a4-435f-92b4-d2d38bfc64ff",
"metadata": {},
"source": [
"We can create a hardware generator function in cyHDL notation. Its differences from a `@block`:\n",
" * It will not infer into a module, thus, has no interface\n",
" * It can not contain a process (`@always..`)\n",
"\n",
"Unlike a `@hdlmacro`, it can contain conditional (if..else) statements. It is therefore context sensitive which is represented by the `rtl` parameter. It is mandatory that the first argument is spelled `rtl`.\n",
"\n",
"Due to reasons with translation, it is currently necessary to wrap such a decorated function inside a class. This is not a bad idea anyhow, to create some structure:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "96234941-cf0b-40a9-945a-bb869aefc3e1",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"class primitives:\n",
" @cyrite_function\n",
" def muxer(rtl, EN, A, B, Q):\n",
" if EN:\n",
" Q.next = A\n",
" else:\n",
" Q.next = B"
]
},
{
"cell_type": "markdown",
"id": "62289e55-99fe-46ab-82d5-53e34003ed3b",
"metadata": {},
"source": [
"We could also consider using a `@hdlmacro`, however there's a catch:\n",
"\n",
"* IRL notation requirement: We must return structures using yield using IRL elements\n",
"* We would have to determine the `If` architecture context beforehand.\n",
"\n",
"The `@cyrite_function` does the selection for us at elaboration or evaluation time. Its internal logic generator inherits the architecture from its calling process.\n",
"\n",
"Note:\n",
"\n",
"* A `@cyrite_function` is an **unbound** function that is internally passed a `rtl` context.\n",
"Therefore, the `primitive` class is not instanced, but merely a container structure.\n",
"* It **prohibits** access to global definitions. Everything you use inside this function\n",
" must be either taken from the `rtl` context (normally, the calling process) or passed as argument.\n",
"* Do not forget to use `rtl` as first argument. You can not use 'self' or another notation.\n",
"* It is a dynamic dual use function that is possibly transpiled to IRL, depending on the context"
]
},
{
"cell_type": "markdown",
"id": "f6bf772b-ed91-420a-a955-c8125b62bdda",
"metadata": {},
"source": [
"A test unit making use of this construct may look as follows:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "c48c7f73-6361-43b2-b305-48d659105b29",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"@block\n",
"def unit1(clk : ClkSignal, a : Signal, b : Signal, en : Signal.Type(bool),\n",
" q : Signal.Output, MODE : int):\n",
" \n",
" # This is a simple expression that can be assigned to a signal:\n",
" expr_and = a & b\n",
" expr_or = a | b\n",
" # This results in a logic generator, where the result\n",
" # is a parameter:\n",
" \n",
" logic = []\n",
" \n",
" if MODE == 1:\n",
" logic += [ q @assign@ expr_and ]\n",
" elif MODE == 2:\n",
" logic = [ q @assign@ expr_or ]\n",
" else:\n",
" @always(clk.posedge)\n",
" def ff():\n",
" primitives.muxer(en, a, b, q)\n",
" \n",
" logic += [ ff ]\n",
"\n",
" return logic"
]
},
{
"cell_type": "markdown",
"id": "5f6df3f7-d488-4dd8-b15e-945bda0d223d",
"metadata": {},
"source": [
"We note a few things:\n",
" * The `@cyrite_function` funclet does not allow us to specify a process element\n",
" * The hardware generator function is called **from within** an `@always` process."
]
},
{
"cell_type": "markdown",
"id": "8f4a6bb6-b80f-4146-9ec0-25d99d65db98",
"metadata": {},
"source": [
"Let's create a dummy target context for this module to evaluate the `@hdlmacro` in.\n",
"Then we instance a few signals:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "60c0bb0e-c356-47fc-afa6-6bf5c7271681",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"from myirl.targets.dummy import DummyTargetModule\n",
"context = DummyTargetModule()\n",
"\n",
"a, b, q = [ Signal(intbv()[8:], name = n) for n in \"abc\" ]\n",
"en = Signal(bool(), name = 'en')"
]
},
{
"cell_type": "markdown",
"id": "342e9de0-8a13-4cc6-b302-2f8593abf6d6",
"metadata": {},
"source": [
"We create a local `muxer` instance, initialize the signals and evaluate each of the actions manually. Note two things:\n",
"* The muxer is created in the first line, but no decision on the context was made yet\n",
"* During `.evaluate()`, the function is actually called with the required context."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "6ce86a60-c03f-464c-a6e6-648ceee1b673",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"m = primitives.muxer(en, a, b, q)\n",
"m.evaluate(context)"
]
},
{
"cell_type": "markdown",
"id": "6aa49a8a-ecb4-44c5-bc68-63fd5ca09f96",
"metadata": {},
"source": [
"After evaluation, we can actually inspect the true Python code behind the function.\n",
"This is only valid, if we call it from a hardware generation context."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "fd02328b-1f08-456f-8e19-219b82548145",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"class __wrapper_muxer__():\n",
"\n",
" @cyrite_function\n",
" def muxer(rtl, EN, A, B, Q):\n",
" (yield [rtl.If(EN).Then(Q.set(A)).Else(Q.set(B))])\n",
"\n"
]
}
],
"source": [
"print(m.unparse())"
]
},
{
"cell_type": "markdown",
"id": "4c77675d-ba18-4a0e-aa37-97dcc7b513c1",
"metadata": {},
"source": [
"We cycle through the possible `en` values (only digital '0' and '1' for Bool signals) and check the result:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "af33e3b0-cde3-4ad9-991b-ff9b9d052097",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"01\n",
"02\n"
]
}
],
"source": [
"for val, res in (True, a), (False, b):\n",
" init = [ en.set(val), a.set(1), b.set(2) ]\n",
" [ i.evaluate() for i in init ]\n",
" m.evaluate(context)\n",
" assert q.evaluate() == res.evaluate()\n",
" print(q.evaluate())"
]
},
{
"cell_type": "markdown",
"id": "57d1b752-f455-4d65-b6e5-69d6c5a99ce4",
"metadata": {},
"source": [
"Using this technique, we evaluate if the muxer function is doing the right thing for our signal type. The same should then happen in the hardware and corresponding simulation."
]
},
{
"cell_type": "markdown",
"id": "46413499-65e0-47c6-9f68-66075ce640a9",
"metadata": {},
"source": [
"## Creating a test bench\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "259d0330-b16f-4e40-abe0-ac4534cc59e1",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"from myirl.test.icarus import ICARUS\n",
"from cyrite.simulation import sim\n",
"\n",
"@sim.testbench(ICARUS, 'ns')\n",
"@block\n",
"def tb_unit1():\n",
" a, b, q = [ Signal(intbv()[8:]) for _ in range(3) ]\n",
" en = Signal(bool())\n",
" clk = ClkSignal(name = 'clk')\n",
" \n",
" uut = unit1(clk = clk, en = en, a = a, b = b, q = q, MODE = 3)\n",
" \n",
" @always(delay(1))\n",
" def clkgen():\n",
" clk.next = ~clk\n",
" \n",
" @sequence\n",
" def main():\n",
" \"\"\"Yes, we call the main stimulation routine 'main' to\n",
"denote the sequence creating a wave trace\"\"\"\n",
" en.next = False\n",
" yield delay(1)\n",
" a.next = 12\n",
" b.next = 4\n",
" en.next = True\n",
" for i in range(4):\n",
" yield delay(1)\n",
" # assert q == 4\n",
" yield clk.posedge\n",
" en.next = ~en\n",
" # assert q == 12\n",
" \n",
" yield delay(20)\n",
" \n",
" raise StopSimulation\n",
" \n",
" return instances()\n"
]
},
{
"cell_type": "markdown",
"id": "0d4f2c20-1b17-43d1-9841-0988c3f62704",
"metadata": {},
"source": [
"## Running the test bench"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "b9db1017-27d8-47d9-bd00-7bc284c51ba7",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" Writing 'unit1' to file /tmp/unit1.v \n",
" Writing 'tb_unit1' to file /tmp/tb_unit1.v \n",
" Note: Changing library path prefix to /tmp/ \n",
" Creating library file /tmp/module_defs.v \n",
"DEBUG FILES ['/tmp/unit1.v', '/tmp/tb_unit1.v']\n",
"==== COSIM stdout ====\n",
"VCD info: dumpfile tb_unit1.vcd opened for output.\n",
"Stop Simulation\n",
"\n"
]
},
{
"data": {
"text/plain": [
"0"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"tb = tb_unit1()\n",
"tb.run(80)"
]
},
{
"cell_type": "markdown",
"id": "51065f65-ac86-445d-afbf-705113d75324",
"metadata": {},
"source": [
"### Displaying the trace"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "9a2c1bac-ca80-42a4-8f4d-6c57afcd68af",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div><script type=\"WaveDrom\">{\"signal\": [{\"name\": \"tb_unit1.q[7:0]\", \"wave\": \"x=...=.=.=.=..................\", \"data\": \"0c 04 0c 04 0c \"}, {\"name\": \"tb_unit1.a[7:0]\", \"wave\": \"x=............................\", \"data\": \"0c \"}, {\"name\": \"tb_unit1.b[7:0]\", \"wave\": \"x=............................\", \"data\": \"04 \"}, {\"name\": \"tb_unit1.clk\", \"wave\": \"010101010101010101010101010101\", \"data\": \"010101010101010101010101010101\"}, {\"name\": \"tb_unit1.en\", \"wave\": \"01.0.1.0.1....................\", \"data\": \"010101\"}, {\"name\": \"tb_unit1.unit1_0.a[7:0]\", \"wave\": \"x=............................\", \"data\": \"0c \"}, {\"name\": \"tb_unit1.unit1_0.b[7:0]\", \"wave\": \"x=............................\", \"data\": \"04 \"}, {\"name\": \"tb_unit1.unit1_0.q[7:0]\", \"wave\": \"x=...=.=.=.=..................\", \"data\": \"0c 04 0c 04 0c \"}, {\"name\": \"tb_unit1.MAIN.i[31:0]\", \"wave\": \"x=.=.=.=.=....................\", \"data\": \"00 01 02 03 04 \"}]}</script></div>"
]
},
"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": [
"from cyrite import waveutils\n",
"waveutils.draw_wavetrace(tb, 'tb_unit1.vcd', 'clk')"
]
},
{
"cell_type": "markdown",
"id": "057888c6-8adc-44b8-9198-9c502cd39fca",
"metadata": {},
"source": [
"### RTL specific signals\n",
"\n",
"A `@cyrite_function` may create internal auxiliary signals that are also depending on the target architecture."
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "fdd40c0b-40bd-4880-b299-2d1b79f94604",
"metadata": {},
"outputs": [],
"source": [
"class primi_x(primitives):\n",
" @cyrite_function\n",
" def muxer2(rtl, EN, A, B, Q, wire):\n",
" q = rtl.Signal(rtl.intbv()[8:])\n",
" \n",
" if EN:\n",
" q.next = A\n",
" else:\n",
" q.next = B\n",
" \n",
" Q.next = q\n",
" "
]
},
{
"cell_type": "markdown",
"id": "acba85ac-4280-4f90-8639-aab58fb0d3fc",
"metadata": {},
"source": [
"Unlike a sequential process that would create a variable, a `@cyrite_function` allows to create an internal signal, as if it was outside the process.\n",
"\n",
"However, there are dragons:\n",
"* When called from a clock synchronous process, `Q` will receive the value of `q` with a delay of one clock period, so this is not a continuous assignment. This is a feature, not a bug!"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "2b45950a-6b31-44fb-bc05-bb20ae1769c3",
"metadata": {},
"outputs": [],
"source": [
"@block\n",
"def tb(muxer):\n",
" a, b, q = [ Signal(intbv()[8:]) for _ in range(3) ]\n",
" en = Signal(bool())\n",
" clk = ClkSignal(name = 'clk')\n",
" \n",
" @always(clk.posedge)\n",
" def ff():\n",
" muxer(en, a, b, q, intbv)\n",
" \n",
" ff.intbv = intbv\n",
" return instances()"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "4c456dd7-4b90-4130-bc37-72b3816c4253",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"../../myirl/kernel/components.py:118: UserWarning: Fallback: Pass through other argument for arg muxer (<class 'function'>)\n",
" base.warnings.warn(msg)\n"
]
}
],
"source": [
"t = tb(primi_x.muxer2)"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "162a704c-8d0a-4d6c-98a8-b10133485fd4",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"class __wrapper_muxer2__():\n",
"\n",
" @cyrite_function\n",
" def muxer2(rtl, EN, A, B, Q, wire):\n",
" q = rtl.Signal(rtl.intbv()[8:])\n",
" (yield [rtl.If(EN).Then(q.set(A)).Else(q.set(B)), Q.set(q)])\n",
"\n"
]
}
],
"source": [
"a, b, q = [ Signal(intbv()[8:]) for _ in range(3) ]\n",
"en = Signal(bool())\n",
"clk = ClkSignal(name = 'clk')\n",
"\n",
"context.Signal = Signal\n",
"\n",
"# Examine translated IRL:\n",
"m = primi_x.muxer2(en, a, b, q, intbv)\n",
"m.this = context\n",
"m._run(context)\n",
"print(m.unparse())"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "71021796-2ada-4090-9f43-1020ecb0c884",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" Writing 'tb' to file /tmp/myirl_tb_lx_tfzkt/tb.vhdl \n"
]
}
],
"source": [
"f = t.elab(targets.VHDL)"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "3e85d0fd-a396-4526-a66b-63c13abb68ef",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"-- File generated from source:\n",
"-- /tmp/ipykernel_22424/235411190.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 tb is\n",
"end entity tb;\n",
"\n",
"architecture cyriteHDL of tb is\n",
" -- Local type declarations\n",
" -- Signal declarations\n",
" signal s_19c7 : unsigned(7 downto 0);\n",
" signal q : unsigned(7 downto 0);\n",
" signal b : unsigned(7 downto 0);\n",
" signal a : unsigned(7 downto 0);\n",
" signal en : std_ulogic;\n",
"begin\n",
" \n",
"ff:\n",
" process(clk)\n",
" begin\n",
" if rising_edge(clk) then\n",
" if (en = '1') then\n",
" s_19c7 <= a;\n",
" else\n",
" s_19c7 <= b;\n",
" end if;\n",
" q <= s_19c7;\n",
" end if;\n",
" end process;\n",
"end architecture cyriteHDL;\n",
"\n"
]
}
],
"source": [
"!cat {f[0]}"
]
}
],
"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
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment