{ "cells": [ { "cell_type": "markdown", "id": "707cd31c-841d-4358-a562-3d0171df4a7d", "metadata": {}, "source": [ "# 10b8b decoding/encoding\n", "\n", "This is a DVI (Digital Video) standard 10b8b implementation using control words.\n", "Example only, unverified variant.\n", "\n", "I believe the original encoder part is coming from [here](https://forum.digikey.com/t/tmds-encoder-vhdl/12653https://forum.digikey.com/t/tmds-encoder-vhdl/12653)." ] }, { "cell_type": "markdown", "id": "f055719b-04f1-4e6a-9f55-410cde2aa03e", "metadata": {}, "source": [ "For the 8b -> 10b encoder, we need to count `1` bits in the data word. For fun, we implement the 'gate level' model using half and full adders as shown below:" ] }, { "cell_type": "code", "execution_count": 1, "id": "5895a15b-6d61-43e0-93a3-f10647afabf4", "metadata": {}, "outputs": [], "source": [ "def half_adder(a, b, q, c):\n", " return [\n", " q.set(a ^ b), c.set(a & b)\n", " ]\n", "\n", "def full_adder(a, b, cin, q, c):\n", " x = a ^ b\n", " return [\n", " q.set(x ^ cin), c.set((a & b) | (cin & x))\n", " ]\n", "\n", "from myirl import *\n", "\n", "Data8 = Signal.Type(intbv, 8)\n", "Sum4 = Signal.Type(intbv, 4)\n", "\n", "@block\n", "def bit_count8(v : Data8, sum_ones : Sum4.Output):\n", "\n", " s = [ Signal(bool()) for _ in range(8)] \n", " c = [ Signal(bool()) for _ in range(8)] \n", "\n", " logic = [\n", " # (0) (1) (2)\n", " # | | |\n", " *full_adder(v[0], v[1], v[2], s[0], c[0]),\n", " *full_adder(v[3], v[4], v[5], s[1], c[1]),\n", " *half_adder(v[6], v[7], s[2], c[2]),\n", " # | |\n", " # (0) (1)\n", " *full_adder(s[0], s[1], s[2], s[3], c[3]), # (0)\n", " # | | |\n", " *full_adder(c[0], c[1], c[2], s[4], c[4]), # (1)\n", " # | | | \n", " *half_adder(c[3], s[4], s[5], c[5]),\n", " # | |\n", " *half_adder(c[4], c[5], s[6], c[6]),\n", "\n", " ]\n", " # | | | |\n", " bits = [ s[3], s[5], s[6], c[6] ]\n", " \n", " logic += [ sum_ones.set(concat(*reversed(bits))) ]\n", " return logic" ] }, { "cell_type": "code", "execution_count": 2, "id": "87b1d435-d1a7-4bad-8724-73e4b70e6e6a", "metadata": {}, "outputs": [], "source": [ "from myirl.kernel.components import LibraryModule\n", "from myirl.kernel.sensitivity import process as logic_process\n", "from myirl.emulation.myhdl import *\n", "\n", "Bool = Signal.Type(bool)\n", "Data10 = Signal.Type(intbv()[10:])\n", "Data8 = Signal.Type(intbv()[8:])\n", "Ctrl = Signal.Type(intbv()[2:])\n", "\n", "\n", "# Reusable cascaded XOR/XNOR generator\n", "def gen_scrambler(q, a, b, xnor):\n", " @logic_process()\n", " def assign_data(logic):\n", " R = range(len(b)-1)\n", " gen_xor = [ q[i+1].set(a[i] ^ b[i+1]) for i in R ]\n", " gen_xnor = [ q[i+1].set(~(a[i] ^ b[i+1])) for i in R ]\n", " \n", " logic += [\n", " logic.If(xnor == True).Then(\n", " *gen_xnor\n", " ).Else(\n", " *gen_xor\n", " )\n", " ]\n", "\n", " return assign_data\n", "\n", "@factory\n", "class DVI10b8b(LibraryModule):\n", " \n", " def __init__(self, CONTROL_WORDS = [\n", " 0x354, 0x0ab, 0x154, 0x2ab\n", " ]):\n", " self.CONTROL_WORDS = CONTROL_WORDS\n", " super().__init__(\"top\", targets.VHDL)\n", " \n", " @block_component\n", " def decoder(self,\n", " clk : ClkSignal,\n", " enable : Bool,\n", " din : Data10,\n", " dout : Data8.Output,\n", " ctrl : Ctrl.Output,\n", " de : Bool.Output\n", " ):\n", "\n", " data = Data8()\n", " data1 = Signal(intbv()[8:])\n", "\n", " d = [ Bool() for _ in range(8) ]\n", " \n", " wires = [\n", " data1.wireup((concat(*reversed(d)))),\n", " d[0].wireup(data[0])\n", " ]\n", "\n", " @always_comb\n", " def assign():\n", " if din[9] == 1:\n", " data.next = ~din[8:]\n", " else:\n", " data.next = din[8:]\n", " \n", " # Create a gen_assign instance:\n", " a = gen_scrambler(d, data, data, False)\n", " \n", " @always(clk.posedge)\n", " def worker():\n", " if enable == True:\n", " if din == self.CONTROL_WORDS[0]:\n", " ctrl.next = 0\n", " de.next = 0\n", " elif din == self.CONTROL_WORDS[1]:\n", " ctrl.next = 1\n", " de.next = 0\n", " elif din == self.CONTROL_WORDS[2]:\n", " ctrl.next = 2\n", " de.next = 0\n", " elif din == self.CONTROL_WORDS[3]:\n", " ctrl.next = 3\n", " de.next = 0\n", " else:\n", " if din[8] == True:\n", " dout.next = data1\n", " else:\n", " dout.next = concat(~data1[:1], d[0])\n", " de.next = 1\n", " else: # not enable\n", " dout.next = 0\n", " ctrl.next = 0\n", " de.next = 0\n", "\n", " return instances()\n", " \n", " @block_component\n", " def encoder(self,\n", " clk : ClkSignal,\n", " enable : Bool,\n", " din : Data8,\n", " dout : Data10.Output,\n", " ctrl : Ctrl,\n", " de : Bool.Output\n", " ):\n", " \n", " ones_din = Signal(intbv(0, min=0, max=9))\n", " ones_tmp = Signal(intbv(0, min=0, max=9))\n", " diff_tmp = Signal(intbv(0, min=-8, max=9))\n", " disp = Signal(modbv(0, min=-16, max=16))\n", " switch = Bool()\n", " \n", " dm, dp = [ Signal(intbv(min=-16, max=16)) for _ in range(2) ]\n", " \n", " tmp = Signal(intbv(0)[9:])\n", "\n", " msb, nmsb = [ Bool() for _ in range(2) ]\n", "\n", " t = [ Bool() for _ in range(9) ]\n", " \n", " wires = [\n", " t[0].wireup(din[0]),\n", " nmsb.wireup(~tmp[8]),\n", " msb.wireup(tmp[8]), \n", " tmp.wireup(concat(*reversed(t)))\n", " ]\n", " \n", " # Generate another XOR/XNOR scrambler:\n", " a = gen_scrambler(t, t, din, switch)\n", " \n", " @always_comb\n", " def switch_xnor():\n", " if ones_din > 4 or (ones_din == 4 and t[0] == 0):\n", " switch.next = True\n", " t[8].next = False\n", " else:\n", " switch.next = False\n", " t[8].next = True\n", " \n", " # Instance '1' counters for 8 bit of the work register and data in\n", " c1 = bit_count8(tmp[8:], ones_tmp)\n", " c2 = bit_count8(din, ones_din)\n", "\n", " @always(ones_tmp)\n", " def count_ones_tmp():\n", " diff_tmp.next = ones_tmp + (ones_tmp - 8).signed()\n", "\n", " @always_comb\n", " def diff():\n", " dp.next = disp + diff_tmp\n", " dm.next = disp - diff_tmp\n", " \n", " @always(clk.posedge)\n", " def worker():\n", " de.next = enable\n", " if enable == True:\n", " if disp == 0 or ones_tmp == 4:\n", " if t[8] == True:\n", " dout.next = concat(nmsb, tmp[9:])\n", " disp.next = dp\n", " else:\n", " dout.next = concat(nmsb, msb, ~tmp[8:])\n", " disp.next = dm\n", " else:\n", " if (disp > 0 and ones_tmp > 4) or (disp < 0 and ones_tmp < 4):\n", " dout.next = concat(True, msb, ~tmp[8:])\n", " if t[8] == 1:\n", " disp.next = (dm + 2)\n", " else:\n", " disp.next = dm\n", " else:\n", " dout.next = concat(False, tmp[9:])\n", " if t[8] == 1:\n", " disp.next = dp\n", " else:\n", " disp.next = (dp - 2)\n", " else:\n", " if ctrl == 0:\n", " dout.next = self.CONTROL_WORDS[0]\n", " elif ctrl == 1:\n", " dout.next = self.CONTROL_WORDS[1]\n", " elif ctrl == 2:\n", " dout.next = self.CONTROL_WORDS[2]\n", " elif ctrl == 3:\n", " dout.next = self.CONTROL_WORDS[3]\n", " disp.next = 0\n", "\n", " return instances()" ] }, { "cell_type": "code", "execution_count": 3, "id": "93ebc5d1-7631-468c-8e31-713e24454fcd", "metadata": {}, "outputs": [], "source": [ "def test(Library):\n", "\n", " lib = Library()\n", "\n", " clk = ClkSignal()\n", " enable = Bool()\n", " din = Data8()\n", " dout = Data10()\n", " ctrl = Ctrl()\n", " de = Bool()\n", "\n", " libfiles = []\n", " \n", " enc = lib.encoder(clk = clk, enable = enable, din = din, dout = dout, ctrl = ctrl, de = de)\n", " libfiles += enc.elab(targets.VHDL, elab_all = True)\n", " dec = lib.decoder(clk = clk, enable = enable, din = dout, dout = din, ctrl = ctrl, de = de)\n", " libfiles += dec.elab(targets.VHDL, elab_all = True)\n", " \n", " return libfiles" ] }, { "cell_type": "code", "execution_count": 4, "id": "b36206b0-3da9-4822-8551-d87d40c761af", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Using default for CONTROL_WORDS: [852, 171, 340, 683]\n", "\u001b[7;35m Declare obj 'encoder' in context '(LIB: DVI10b8b 'top')' \u001b[0m\n", " Writing 'bit_count8' to file /tmp/myirl_top_bwwxtndp/bit_count8.vhdl \n", " Writing 'encoder' to file /tmp/myirl_top_bwwxtndp/encoder.vhdl \n", "Warning: Implicit truncation of ADD(ones_tmp, SGN(SUB(ones_tmp, C:8))) result\n", "Warning: Implicit truncation of ADD(disp, diff_tmp) result\n", "Warning: Implicit truncation of SUB(disp, diff_tmp) result\n", "Warning: Implicit truncation of ADD(dm, C:2) result\n", "Warning: Implicit truncation of SUB(dp, C:2) result\n", " Creating library file /tmp/myirl_module_defs_slqcnj7_/module_defs.vhdl \n", "\u001b[7;35m Declare obj 'decoder' in context '(LIB: DVI10b8b 'top')' \u001b[0m\n", " Writing 'decoder' to file /tmp/myirl_top_bwwxtndp/decoder.vhdl \n", " Writing 'bit_count8' to file /tmp/myirl_top_bwwxtndp/bit_count8.vhdl \n", " Writing 'encoder' to file /tmp/myirl_top_bwwxtndp/encoder.vhdl \n", "Warning: Implicit truncation of ADD(ones_tmp, SGN(SUB(ones_tmp, C:8))) result\n", "Warning: Implicit truncation of ADD(disp, diff_tmp) result\n", "Warning: Implicit truncation of SUB(disp, diff_tmp) result\n", "Warning: Implicit truncation of ADD(dm, C:2) result\n", "Warning: Implicit truncation of SUB(dp, C:2) result\n", " Creating library file /tmp/myirl_module_defs_8oqhy3py/module_defs.vhdl \n" ] } ], "source": [ "libfiles = test(DVI10b8b)" ] }, { "cell_type": "code", "execution_count": 5, "id": "af8e5aec-5f3e-4201-8d79-6f01c054d5d5", "metadata": {}, "outputs": [], "source": [ "# !cat {libfiles[1]}" ] }, { "cell_type": "markdown", "id": "c478ccca-90a0-4e24-9626-85e47a51ce30", "metadata": {}, "source": [ "## Loopback testing\n", "\n", "To loop in a known good encoder unit in VHDL, we create a black box stub:" ] }, { "cell_type": "code", "execution_count": 6, "id": "56e60bb3-76b3-40bc-87db-e81ee4842d57", "metadata": {}, "outputs": [], "source": [ "@blackbox\n", "def tmds_encoder(\n", " clk : ClkSignal,\n", " disp_ena : Bool,\n", " control : Ctrl,\n", " d_in : Data8,\n", " q_out : Data10.Output\n", "):\n", "\n", " return []" ] }, { "cell_type": "markdown", "id": "2e539c7c-1394-4ec5-8f39-a18901880c8b", "metadata": {}, "source": [ "The test bench for the loop back:" ] }, { "cell_type": "code", "execution_count": 7, "id": "95baf1dc-2f41-43c1-b70e-78b621856572", "metadata": {}, "outputs": [], "source": [ "@block\n", "def test_tmds(PCLK_DURATION_NS = 1, VERIFY = True):\n", " \n", " lib = DVI10b8b()\n", " \n", " clk = ClkSignal()\n", " enable = Signal(bool(0))\n", " de_enc, de_dec = [Signal(bool(0)) for i in range(2) ]\n", " data10 = Data10()\n", " data8 = Data8(0xff)\n", " data8.init = True\n", " ctrl_in, ctrl_out = [Ctrl() for _ in range(2)]\n", "\n", " data8_return = Data8()\n", "\n", " data10_ref = Data10()\n", "\n", " data_delay0, data_delay1 = [ Data8() for _ in range(2) ]\n", " \n", " enc_inst0 = lib.encoder(clk, enable, data8, data10, ctrl_in, de_enc)\n", " \n", " if VERIFY:\n", " enc_inst = tmds_encoder(clk = clk, disp_ena = enable, d_in = data8,\n", " q_out = data10_ref, control = ctrl_in)\n", "\n", " # Verified:\n", " dec_inst = lib.decoder(clk, de_enc, data10, data8_return, ctrl_out, de_dec)\n", "\n", " @always(delay(1))\n", " def clkgen():\n", " clk.next = ~clk\n", "\n", " @always(clk.posedge)\n", " def stimulate():\n", " data8.next = (data8 + 43)\n", " \n", " data_delay1.next = data_delay0\n", " data_delay0.next = data8\n", "\n", " @always(clk.posedge)\n", " def verify():\n", " if de_dec == True:\n", " # print(data_delay0, data_delay1, data8)\n", " assert data_delay1 == data8_return\n", "\n", " @instance\n", " def start():\n", " enable.next = 0\n", " ctrl_in.next = 0\n", " yield delay(5)\n", " \n", " yield delay(100)\n", " enable.next = True\n", "\n", " yield delay(10)\n", " yield clk.negedge\n", "\n", " yield delay(600)\n", " enable.next = 0\n", "\n", " return instances()" ] }, { "cell_type": "code", "execution_count": 8, "id": "5c21578b-85a2-4122-9afb-eb72c236c18c", "metadata": {}, "outputs": [], "source": [ "!rm -f /tmp/*.cf" ] }, { "cell_type": "markdown", "id": "c4f8fcb8-13b7-4e44-8180-c1d41b6e9063", "metadata": {}, "source": [ "The black box unit needs to be specified explicitely:" ] }, { "cell_type": "code", "execution_count": 9, "id": "f5e21ad9-09eb-4018-b4d9-2597e27842ba", "metadata": {}, "outputs": [], "source": [ "import os\n", "pwd = os.getcwd()\n", "from myirl.targets import pyosys\n", "from myirl.test.common_test import run_ghdl\n", "\n", "def run_tb():\n", " lib = DVI10b8b()\n", " c = test_tmds.ctx\n", " if c:\n", " c.clear()\n", " tb = test_tmds(lib)\n", " target = targets.VHDL\n", " # target = pyosys.RTLIL(\"gna\")\n", " f = tb.elab(target, elab_all = True)\n", " f += (libfiles)\n", " f.append( pwd + '/video/tmds_encoder.vhd' )\n", " run_ghdl(f, tb, debug = True, vcdfile = \"test_tmds.vcd\")\n", " return lib" ] }, { "cell_type": "code", "execution_count": 10, "id": "755f87ac-ff56-4a54-90e9-d034a62dbbdc", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Using default for CONTROL_WORDS: [852, 171, 340, 683]\n", "FALLBACK: UNHANDLED ROOT CLASS , create new context\n", "Using default for VERIFY: True\n", " VERIFY: use default True \n", "Using default for CONTROL_WORDS: [852, 171, 340, 683]\n", "\u001b[7;35m Declare obj 'encoder' in context '(LIB: DVI10b8b 'top')' \u001b[0m\n", "\u001b[32m Module top: Existing instance bit_count8, rename to bit_count8_1 \u001b[0m\n", "\u001b[7;35m Declare obj 'decoder' in context '(LIB: DVI10b8b 'top')' \u001b[0m\n", " Writing 'test_tmds' to file /tmp/myirl_test_tmds_ho_vext7/test_tmds.vhdl \n", "Warning: Implicit truncation of ADD(data8, C:43) result\n", " Creating library file /tmp/myirl_module_defs_e97_lti5/module_defs.vhdl \n", "==== COSIM stderr ====\n", "/tmp/myirl_module_defs_slqcnj7_/module_defs.vhdl:6:1:warning: package \"module_defs\" was also defined in file \"/tmp/myirl_module_defs_e97_lti5/module_defs.vhdl\" [-Wlibrary]\n", "/tmp/myirl_module_defs_8oqhy3py/module_defs.vhdl:6:1:warning: package \"module_defs\" was also defined in file \"/tmp/myirl_module_defs_slqcnj7_/module_defs.vhdl\" [-Wlibrary]\n", "\n", "==== COSIM stdout ====\n", "../../src/ieee2008/numeric_std-body.vhdl:1168:7:@0ms:(assertion warning): NUMERIC_STD.\">\": metavalue detected, returning FALSE\n", "../../src/ieee2008/numeric_std-body.vhdl:1776:7:@0ms:(assertion warning): NUMERIC_STD.\"=\": metavalue detected, returning FALSE\n", "../../src/ieee2008/numeric_std-body.vhdl:1168:7:@0ms:(assertion warning): NUMERIC_STD.\">\": metavalue detected, returning FALSE\n", "../../src/ieee2008/numeric_std-body.vhdl:1776:7:@0ms:(assertion warning): NUMERIC_STD.\"=\": metavalue detected, returning FALSE\n", "../../src/ieee2008/numeric_std-body.vhdl:1168:7:@0ms:(assertion warning): NUMERIC_STD.\">\": metavalue detected, returning FALSE\n", "../../src/ieee2008/numeric_std-body.vhdl:1776:7:@0ms:(assertion warning): NUMERIC_STD.\"=\": metavalue detected, returning FALSE\n", "../../src/ieee2008/numeric_std-body.vhdl:1168:7:@0ms:(assertion warning): NUMERIC_STD.\">\": metavalue detected, returning FALSE\n", "../../src/ieee2008/numeric_std-body.vhdl:1776:7:@0ms:(assertion warning): NUMERIC_STD.\"=\": metavalue detected, returning FALSE\n", "/tmp/test_tmds:info: simulation stopped by --stop-time @1us\n", "\n" ] } ], "source": [ "lib = run_tb()" ] }, { "cell_type": "code", "execution_count": null, "id": "c71f2de3-f480-429e-b156-1b867de6ef32", "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 }