#!/usr/bin/env python # coding: utf-8 # # 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](https://forum.digikey.com/t/tmds-encoder-vhdl/12653https://forum.digikey.com/t/tmds-encoder-vhdl/12653). # 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) # 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]: get_ipython().system('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() # In[ ]: