#!/usr/bin/env python3

import random

def popcount(x):
	n = 0
	while x:
		n += 1
		x = x & (x - 1)
	return n

# Equivalent to N1(q) - N0(q) in the DVI spec
def byteimbalance(x):
	return 2 * popcount(x) - 8

# This is a direct translation of "Figure 3-5. T.M.D.S. Encode Algorithm" on
# page 29 of DVI 1.0 spec

class TMDSEncode:
	ctrl_syms = {
		0b00: 0b0010101011,
		0b01: 0b1101010100,
		0b10: 0b0010101010,
		0b11: 0b1101010101
	}
	def __init__(self):
		self.imbalance = 0

	def encode(self, d, c, de):
		assert(self.imbalance % 2 == 0)
		if not de:
			self.imbalance = 0
			return self.ctrl_syms[c]
		# Minimise transitions
		q_m = d & 0x1
		if popcount(d) > 4 or (popcount(d) == 4 and not d & 0x1):
			for i in range(7):
				q_m = q_m | (~(q_m >> i ^ d >> i + 1) & 0x1) << i + 1
		else:
			for i in range(7):
				q_m = q_m | ( (q_m >> i ^ d >> i + 1) & 0x1) << i + 1
			q_m = q_m | 0x100
		# Correct DC balance
		inversion_mask = 0x2ff
		q_out = 0
		if self.imbalance == 0 or byteimbalance(q_m & 0xff) == 0:
			q_out = q_m ^ (0 if q_m & 0x100 else inversion_mask)
			if q_m & 0x100:
				self.imbalance += byteimbalance(q_m & 0xff)
			else:
				self.imbalance -= byteimbalance(q_m & 0xff)
		elif (self.imbalance > 0) == (byteimbalance(q_m & 0xff) > 0):
			q_out = q_m ^ inversion_mask
			self.imbalance += ((q_m & 0x100) >> 7) - byteimbalance(q_m & 0xff)
		else:
			q_out = q_m
			self.imbalance += byteimbalance(q_m & 0xff) - ((~q_m & 0x100) >> 7)
		return q_out

enc = TMDSEncode()
vec = []
random.seed(1)

for i in range(1000):
	d = random.randrange(256)
	c = random.randrange(4)
	de = random.random() > 0.02
	vec.append((d, c, de, enc.encode(d, c, de)))

of = open("testvec.v", "w")
of.write("// Generated by tmds_vector_gen.py\n");

for i, d in enumerate(vec):
	of.write("vec_input_d_c_de[{:05d}] = 11'b{:08b}_{:02b}_{:01b};\n".format(i, d[0], d[1], d[2]))

of.write("\n")

for i, d in enumerate(vec):
	of.write("vec_output[{:05d}] = 10'b{:010b};\n".format(i, d[3]))

of.close()
