10b8b decoding/encoding

This is a DVI (Digital Video) standard 10b8b implementation using control words. Example only, unverified variant.

I believe the original encoder part is coming from here.

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:

In [1]:
def half_adder(a, b, q, c):
    return [
        q.set(a ^ b), c.set(a & b)
    ]

def full_adder(a, b, cin, q, c):
    x = a ^ b
    return [
        q.set(x ^ cin), c.set((a & b) | (cin & x))
    ]

from myirl import *

Data8 = Signal.Type(intbv, 8)
Sum4 = Signal.Type(intbv, 4)

@block
def bit_count8(v : Data8, sum_ones : Sum4.Output):

    s = [ Signal(bool()) for _ in range(8)] 
    c = [ Signal(bool()) for _ in range(8)] 

    logic = [
    #                                  (0)   (1)   (2)
    #                                   |     |     |
        *full_adder(v[0], v[1], v[2], s[0], c[0]),
        *full_adder(v[3], v[4], v[5], s[1], c[1]),
        *half_adder(v[6], v[7],       s[2], c[2]),
        #                               |     |
        #                              (0)   (1)
        *full_adder(s[0], s[1], s[2], s[3], c[3]),  # (0)
        #                               |     |     |
        *full_adder(c[0], c[1], c[2],       s[4], c[4]),  # (1)
        #                               |     |     |    
        *half_adder(c[3], s[4],             s[5], c[5]),
        #                               |                 |
        *half_adder(c[4], c[5],                   s[6], c[6]),

    ]
    #                                   |     |     |     |
    bits = [                          s[3], s[5], s[6], c[6]    ]
        
    logic += [ sum_ones.set(concat(*reversed(bits))) ]
    return logic
In [2]:
from myirl.kernel.components import LibraryModule
from myirl.kernel.sensitivity import process as logic_process
from myirl.emulation.myhdl import *

Bool = Signal.Type(bool)
Data10 = Signal.Type(intbv()[10:])
Data8  = Signal.Type(intbv()[8:])
Ctrl   = Signal.Type(intbv()[2:])


# Reusable cascaded XOR/XNOR generator
def gen_scrambler(q, a, b, xnor):
    @logic_process()
    def assign_data(logic):
        R = range(len(b)-1)
        gen_xor = [ q[i+1].set(a[i] ^ b[i+1]) for i in R ]
        gen_xnor = [ q[i+1].set(~(a[i] ^ b[i+1])) for i in R ]
        
        logic += [
            logic.If(xnor == True).Then(
                *gen_xnor
            ).Else(
                *gen_xor
            )
        ]

    return assign_data

@factory
class DVI10b8b(LibraryModule):
    
    def __init__(self, CONTROL_WORDS = [
        0x354, 0x0ab, 0x154, 0x2ab
    ]):
        self.CONTROL_WORDS = CONTROL_WORDS
        super().__init__("top", targets.VHDL)
    
    @block_component
    def decoder(self,
        clk    : ClkSignal,
        enable : Bool,
        din    : Data10,
        dout   : Data8.Output,
        ctrl   : Ctrl.Output,
        de     : Bool.Output
    ):

        data = Data8()
        data1 = Signal(intbv()[8:])

        d = [ Bool() for _ in range(8) ]
        
        wires = [
            data1.wireup((concat(*reversed(d)))),
            d[0].wireup(data[0])
        ]

        @always_comb
        def assign():
            if din[9] == 1:
                data.next = ~din[8:]
            else:
                data.next = din[8:]
                
        # Create a gen_assign instance:
        a = gen_scrambler(d, data, data, False)
        
        @always(clk.posedge)
        def worker():
            if enable == True:
                if din == self.CONTROL_WORDS[0]:
                    ctrl.next = 0
                    de.next = 0
                elif din == self.CONTROL_WORDS[1]:
                    ctrl.next = 1
                    de.next = 0
                elif din == self.CONTROL_WORDS[2]:
                    ctrl.next = 2
                    de.next = 0
                elif din == self.CONTROL_WORDS[3]:
                    ctrl.next = 3
                    de.next = 0
                else:
                    if din[8] == True:
                        dout.next = data1
                    else:
                        dout.next = concat(~data1[:1], d[0])
                    de.next = 1
            else: # not enable
                dout.next = 0
                ctrl.next = 0
                de.next = 0

        return instances()
    
    @block_component
    def encoder(self,
        clk    : ClkSignal,
        enable : Bool,
        din    : Data8,
        dout   : Data10.Output,
        ctrl   : Ctrl,
        de     : Bool.Output
    ):
 
        ones_din = Signal(intbv(0, min=0, max=9))
        ones_tmp = Signal(intbv(0, min=0, max=9))
        diff_tmp = Signal(intbv(0, min=-8, max=9))
        disp = Signal(modbv(0, min=-16, max=16))
        switch = Bool()
        
        dm, dp = [ Signal(intbv(min=-16, max=16)) for _ in range(2) ]
        
        tmp = Signal(intbv(0)[9:])

        msb, nmsb = [ Bool() for _ in range(2) ]

        t = [ Bool() for _ in range(9) ]
        
        wires = [
            t[0].wireup(din[0]),
            nmsb.wireup(~tmp[8]),
            msb.wireup(tmp[8]),        
            tmp.wireup(concat(*reversed(t)))
        ]
        
        # Generate another XOR/XNOR scrambler:
        a = gen_scrambler(t, t, din, switch)
        
        @always_comb
        def switch_xnor():
            if ones_din > 4 or (ones_din == 4 and t[0] == 0):
                switch.next = True
                t[8].next = False
            else:
                switch.next = False
                t[8].next = True
        
        # Instance '1' counters for 8 bit of the work register and data in
        c1 = bit_count8(tmp[8:], ones_tmp)
        c2 = bit_count8(din, ones_din)

        @always(ones_tmp)
        def count_ones_tmp():
            diff_tmp.next = ones_tmp + (ones_tmp - 8).signed()

        @always_comb
        def diff():
            dp.next = disp + diff_tmp
            dm.next = disp - diff_tmp
        
        @always(clk.posedge)
        def worker():
            de.next = enable
            if enable == True:
                if disp == 0 or ones_tmp == 4:
                    if t[8] == True:
                        dout.next = concat(nmsb, tmp[9:])
                        disp.next = dp
                    else:
                        dout.next = concat(nmsb, msb, ~tmp[8:])
                        disp.next = dm
                else:
                    if (disp > 0 and ones_tmp > 4) or (disp < 0 and ones_tmp < 4):
                        dout.next = concat(True, msb, ~tmp[8:])
                        if t[8] == 1:
                            disp.next = (dm + 2)
                        else:
                            disp.next = dm
                    else:
                        dout.next = concat(False, tmp[9:])
                        if t[8] == 1:
                            disp.next = dp
                        else:
                            disp.next = (dp - 2)
            else:
                if ctrl == 0:
                    dout.next = self.CONTROL_WORDS[0]
                elif ctrl == 1:
                    dout.next = self.CONTROL_WORDS[1]
                elif ctrl == 2:
                    dout.next = self.CONTROL_WORDS[2]
                elif ctrl == 3:
                    dout.next = self.CONTROL_WORDS[3]
                disp.next = 0

        return instances()
In [3]:
def test(Library):

    lib = Library()

    clk    = ClkSignal()
    enable = Bool()
    din    = Data8()
    dout   = Data10()
    ctrl   = Ctrl()
    de     = Bool()

    libfiles = []
    
    enc = lib.encoder(clk = clk, enable = enable, din = din, dout = dout, ctrl = ctrl, de = de)
    libfiles += enc.elab(targets.VHDL, elab_all = True)
    dec = lib.decoder(clk = clk, enable = enable, din = dout, dout = din, ctrl = ctrl, de = de)
    libfiles += dec.elab(targets.VHDL, elab_all = True)
    
    return libfiles
In [4]:
libfiles = test(DVI10b8b)
Using default for CONTROL_WORDS: [852, 171, 340, 683]
 Declare obj 'encoder' in context '(LIB: DVI10b8b 'top')' 
Creating process 'encoder/count_ones_tmp' with sensitivity (<s_4021>,)
Creating process 'encoder/worker' with sensitivity (clk'rising,)
 Elaborating component bit_count8_s8_s4 
 Writing 'bit_count8' to file /tmp/myirl_top_wb85ua3o/bit_count8.vhdl 
 Elaborating component encoder__DVI10b8b_s1_s1_s8_s10_s2_s1 
 Writing 'encoder' to file /tmp/myirl_top_wb85ua3o/encoder.vhdl 
Warning: Implicit truncation of ADD(ones_tmp, SGN(SUB(ones_tmp, C:8))) result
Warning: Implicit truncation of ADD(disp, diff_tmp) result
Warning: Implicit truncation of SUB(disp, diff_tmp) result
Warning: Implicit truncation of ADD(dm, C:2) result
Warning: Implicit truncation of SUB(dp, C:2) result
 Creating library file /tmp/myirl_module_defs_f2ox4b9b/module_defs.vhdl 
 Declare obj 'decoder' in context '(LIB: DVI10b8b 'top')' 
Creating process 'decoder/worker' with sensitivity (clk'rising,)
 Elaborating component decoder__DVI10b8b_s1_s1_s10_s8_s2_s1 
 Writing 'decoder' to file /tmp/myirl_top_wb85ua3o/decoder.vhdl 
 Elaborating component bit_count8_s8_s4 
 Writing 'bit_count8' to file /tmp/myirl_top_wb85ua3o/bit_count8.vhdl 
 Elaborating component encoder__DVI10b8b_s1_s1_s8_s10_s2_s1 
 Writing 'encoder' to file /tmp/myirl_top_wb85ua3o/encoder.vhdl 
Warning: Implicit truncation of ADD(ones_tmp, SGN(SUB(ones_tmp, C:8))) result
Warning: Implicit truncation of ADD(disp, diff_tmp) result
Warning: Implicit truncation of SUB(disp, diff_tmp) result
Warning: Implicit truncation of ADD(dm, C:2) result
Warning: Implicit truncation of SUB(dp, C:2) result
 Creating library file /tmp/myirl_module_defs_xul2qrqv/module_defs.vhdl 
In [5]:
# !cat {libfiles[1]}

Loopback testing

To loop in a known good encoder unit in VHDL, we create a black box stub:

In [6]:
@blackbox
def tmds_encoder(
        clk        : ClkSignal,
        disp_ena   : Bool,
        control    : Ctrl,
        d_in       : Data8,
        q_out      : Data10.Output
):

    return []

The test bench for the loop back:

In [7]:
@block
def test_tmds(PCLK_DURATION_NS = 1, VERIFY = True):
    
    lib = DVI10b8b()
    
    clk = ClkSignal()
    enable = Signal(bool(0))
    de_enc, de_dec = [Signal(bool(0)) for i in range(2) ]
    data10 = Data10()
    data8 = Data8(0xff)
    data8.init = True
    ctrl_in, ctrl_out = [Ctrl() for _ in range(2)]

    data8_return = Data8()

    data10_ref = Data10()

    data_delay0, data_delay1 = [ Data8() for _ in range(2) ]
    
    enc_inst0 = lib.encoder(clk, enable, data8, data10, ctrl_in, de_enc)
    
    if VERIFY:
        enc_inst = tmds_encoder(clk = clk, disp_ena = enable, d_in = data8,
                                 q_out = data10_ref, control = ctrl_in)

    # Verified:
    dec_inst = lib.decoder(clk, de_enc, data10, data8_return, ctrl_out, de_dec)

    @always(delay(1))
    def clkgen():
        clk.next = ~clk

    @always(clk.posedge)
    def stimulate():
        data8.next = (data8 + 43)
        
        data_delay1.next = data_delay0
        data_delay0.next = data8

    @always(clk.posedge)
    def verify():
        if de_dec == True:
            # print(data_delay0, data_delay1, data8)
            assert data_delay1 == data8_return

    @instance
    def start():
        enable.next = 0
        ctrl_in.next = 0
        yield delay(5)
        
        yield delay(100)
        enable.next = True

        yield delay(10)
        yield clk.negedge

        yield delay(600)
        enable.next = 0

    return instances()
In [8]:
!rm -f /tmp/*.cf

The black box unit needs to be specified explicitely:

In [9]:
import os
pwd = os.getcwd()
from myirl.targets import pyosys
from myirl.test.common_test import run_ghdl

def run_tb():
    lib = DVI10b8b()
    c = test_tmds.ctx
    if c:
        c.clear()
    tb = test_tmds(lib)
    target = targets.VHDL
    # target = pyosys.RTLIL("gna")
    f = tb.elab(target, elab_all = True)
    f += (libfiles)
    f.append( pwd + '/video/tmds_encoder.vhd' )
    run_ghdl(f, tb, debug = True, vcdfile = "test_tmds.vcd")
    return lib
In [10]:
lib = run_tb()
Using default for CONTROL_WORDS: [852, 171, 340, 683]
Using default for VERIFY: True
 VERIFY: use default True 
Using default for CONTROL_WORDS: [852, 171, 340, 683]
 Declare obj 'encoder' in context '(LIB: DVI10b8b 'top')' 
 Module top: Existing instance bit_count8, rename to bit_count8_1 
Creating process 'encoder/count_ones_tmp' with sensitivity (<s_35e8>,)
Creating process 'encoder/worker' with sensitivity (clk'rising,)
 Instancing blackbox.. 
 Declare obj 'decoder' in context '(LIB: DVI10b8b 'top')' 
Creating process 'decoder/worker' with sensitivity (clk'rising,)
Creating process 'test_tmds/clkgen' with sensitivity ([ DeltaT 1 ns ],)
Creating process 'test_tmds/stimulate' with sensitivity (clk_bb42'rising,)
Creating process 'test_tmds/verify' with sensitivity (clk_bb42'rising,)
Creating sequential 'test_tmds/start' 
 Elaborating component test_tmds__DVI10b8b_1 
 Writing 'test_tmds' to file /tmp/myirl_top_test_tmds__dewb5di/test_tmds.vhdl 
Warning: Implicit truncation of ADD(data8, C:43) result
 Creating library file /tmp/myirl_module_defs_v7novovj/module_defs.vhdl 
==== COSIM stdout ====

==== COSIM stderr ====
/tmp/myirl_module_defs_f2ox4b9b/module_defs.vhdl:6:1:warning: package "module_defs" was also defined in file "/tmp/myirl_module_defs_v7novovj/module_defs.vhdl" [-Wlibrary]
/tmp/myirl_module_defs_xul2qrqv/module_defs.vhdl:6:1:warning: package "module_defs" was also defined in file "/tmp/myirl_module_defs_f2ox4b9b/module_defs.vhdl" [-Wlibrary]

==== COSIM stdout ====
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
analyze /home/testing/.local/lib/python3.9/site-packages/myirl-0.0.0-py3.9-linux-x86_64.egg/myirl/targets/libmyirl.vhdl
analyze /tmp/myirl_top_wb85ua3o/bit_count8.vhdl
analyze /tmp/myirl_top_wb85ua3o/encoder.vhdl
analyze /home/testing/src/myhdl2/myhdl.v2we/examples/video/tmds_encoder.vhd
analyze /tmp/myirl_top_wb85ua3o/decoder.vhdl
analyze /tmp/myirl_top_test_tmds__dewb5di/test_tmds.vhdl
elaborate test_tmds

==== COSIM stderr ====

==== COSIM stdout ====
../../src/ieee2008/numeric_std-body.vhdl:1168:7:@0ms:(assertion warning): NUMERIC_STD.">": metavalue detected, returning FALSE
../../src/ieee2008/numeric_std-body.vhdl:1776:7:@0ms:(assertion warning): NUMERIC_STD."=": metavalue detected, returning FALSE
../../src/ieee2008/numeric_std-body.vhdl:1168:7:@0ms:(assertion warning): NUMERIC_STD.">": metavalue detected, returning FALSE
../../src/ieee2008/numeric_std-body.vhdl:1776:7:@0ms:(assertion warning): NUMERIC_STD."=": metavalue detected, returning FALSE
../../src/ieee2008/numeric_std-body.vhdl:1168:7:@0ms:(assertion warning): NUMERIC_STD.">": metavalue detected, returning FALSE
../../src/ieee2008/numeric_std-body.vhdl:1776:7:@0ms:(assertion warning): NUMERIC_STD."=": metavalue detected, returning FALSE
../../src/ieee2008/numeric_std-body.vhdl:1168:7:@0ms:(assertion warning): NUMERIC_STD.">": metavalue detected, returning FALSE
../../src/ieee2008/numeric_std-body.vhdl:1776:7:@0ms:(assertion warning): NUMERIC_STD."=": metavalue detected, returning FALSE
/tmp/test_tmds:info: simulation stopped by --stop-time @1us

==== COSIM stderr ====

In [ ]: