DVI link simulation
With my friend cqql, we held a stream last week on simulating DVI encoding and decoding in order to produce fun glitched images.
DVI encoding
DVI (for Digital Visual Interface) is a video cable specification; it specifies how graphical data is to be encoded by the graphics card and decoded by the monitor.
Each color channel is encoded individually (channels 0, 1 and 2), as well as the clock (channel C). In dual link configuration, every even pixel is sent on the second link instead (channels 3, 4 and 5) (the first pixel is at index 1, not 0, so the first pixel is sent over the first link). But let's assume a single link is used for now.
Each color channel has four inputs as such :
| Input | Channel 0 | Channel 1 | Channel 2 |
|---|---|---|---|
| D | Blue value | Green value | Red value |
| C0 | HSYNC | CTL0 | CTL2 |
| C1 | VSYNC | CTL1 | CTL3 |
| DE | Data enable | Data enable | Data enable |
A few more variables are available:
DE: "data enable", a boolean signal outside of the channels that indicates whether pixel data or control signals are being sentcnt: a register that tracks disparity, aka the difference from a perfect balance of 0s and 1s. A positive number represents the number of extra 1s and a negative number the number of extra 0s. Accessed aspreviousCntfor the previous disparity or ascntfor the new disparity.q_out: The output channel value (10 bits)n1(x)$: the number of 1s inx`n0(x)$: the number of 0s inx`
Then the DVI specification gives us the encoding algorithm in the form of a lovely diagram which I turned into pseudo code:
def encode(DE, D, C0, C1, previousCnt, cnt):
if DE:
set_bit(q_m, 0, get_bit(D, 0))
if n1(D) > 4 or (n1(D) == 4 and get_bit(D, 0) == False):
set_bit(q_m, 1, xnor(get_bit(q_m, 0), get_bit(D, 1)))
set_bit(q_m, 2, xnor(get_bit(q_m, 1), get_bit(D, 2)))
set_bit(q_m, 3, xnor(get_bit(q_m, 2), get_bit(D, 3)))
set_bit(q_m, 4, xnor(get_bit(q_m, 3), get_bit(D, 4)))
set_bit(q_m, 5, xnor(get_bit(q_m, 4), get_bit(D, 5)))
set_bit(q_m, 6, xnor(get_bit(q_m, 5), get_bit(D, 6)))
set_bit(q_m, 7, xnor(get_bit(q_m, 6), get_bit(D, 7)))
set_bit(q_m, 8, False)
else:
set_bit(q_m, 1, get_bit(q_m, 0) ^ get_bit(D, 1))
set_bit(q_m, 2, get_bit(q_m, 1) ^ get_bit(D, 2))
set_bit(q_m, 3, get_bit(q_m, 2) ^ get_bit(D, 3))
set_bit(q_m, 4, get_bit(q_m, 3) ^ get_bit(D, 4))
set_bit(q_m, 5, get_bit(q_m, 4) ^ get_bit(D, 5))
set_bit(q_m, 6, get_bit(q_m, 5) ^ get_bit(D, 6))
set_bit(q_m, 7, get_bit(q_m, 6) ^ get_bit(D, 7))
set_bit(q_m, 8, True)
if not previousCnt or n1(get_bits(q_m, 0, 7)) == n0(get_bits(q_m, 0, 7)):
set_bit(q_out, 9, not get_bit(q_m, 8))
set_bit(q_out, 8, get_bit(q_m, 8))
if get_bit(q_m, 8):
q_out = get_bits(q_out, 8, 9) | get_bits(q_m, 0, 7)
cnt = previousCnt + n1(get_bits(q_m, 0, 7)) - n0(get_bits(q_m, 0, 7));
else:
q_out = get_bits(q_out, 8, 9) | ~get_bits(q_m, 0, 7)
cnt = previousCnt + n0(get_bits(q_m, 0, 7)) - n1(get_bits(q_m, 0, 7));
else:
if (previousCnt > 0 and n1(get_bits(q_m, 0, 7)) > n0(get_bits(q_m, 0, 7))) or (previousCnt < 0 and n0(get_bits(q_m, 0, 7)) > n1(get_bits(q_m, 0, 7))):
set_bit(q_out, 9, True)
set_bit(q_out, 8, get_bit(q_m, 8))
q_out = get_bits(q_out, 8, 9) | ~get_bits(q_m, 0, 7)
cnt = previousCnt + 2 * get_bit(q_m, 8) + n0(get_bits(q_m, 0, 7)) - n1(get_bits(q_m, 0, 7));
else:
set_bit(q_out, 9, True)
set_bit(q_out, 8, get_bit(q_m, 8))
q_out = get_bits(q_out, 8, 9) | get_bits(q_m, 0, 7)
cnt = previousCnt + 2 * get_bit(q_m, 8) + n1(get_bits(q_m, 0, 7)) - n0(get_bits(q_m, 0, 7));
else:
cnt = False
if C1 == False and C0 == False:
q_out = 0b0010101011
elif C1 == False and C0 == True:
q_out = 0b1101010100
elif C1 == True and C0 == False:
q_out = 0b0010101010
elif C1 == True and C0 == True:
q_out = 0b1101010101
return q_out
Decoding is easier; it takes as input DE and D (the q_out returned from encoding) and returns Q, C0 and C1
def decode(DE, D):
if DE:
if get_bit(D, 9):
D = get_bits(D, 8, 9) | ~get_bits(D, 0, 7)
set_bit(Q, 0, get_bit(D, 0))
if get_bit(D, 8):
set_bit(Q, 1, get_bit(D, 1) ^ get_bit(D, 0))
set_bit(Q, 2, get_bit(D, 1) ^ get_bit(D, 0))
set_bit(Q, 3, get_bit(D, 1) ^ get_bit(D, 0))
set_bit(Q, 4, get_bit(D, 1) ^ get_bit(D, 0))
set_bit(Q, 5, get_bit(D, 1) ^ get_bit(D, 0))
set_bit(Q, 6, get_bit(D, 1) ^ get_bit(D, 0))
set_bit(Q, 7, get_bit(D, 1) ^ get_bit(D, 0))
else:
set_bit(Q, 1, xnor(get_bit(D, 1), get_bit(D, 0)))
set_bit(Q, 2, xnor(get_bit(D, 2), get_bit(D, 1)))
set_bit(Q, 3, xnor(get_bit(D, 3), get_bit(D, 2)))
set_bit(Q, 4, xnor(get_bit(D, 4), get_bit(D, 3)))
set_bit(Q, 5, xnor(get_bit(D, 5), get_bit(D, 4)))
set_bit(Q, 6, xnor(get_bit(D, 6), get_bit(D, 5)))
set_bit(Q, 7, xnor(get_bit(D, 7), get_bit(D, 6)))
else:
if D == 0b0010101011:
C1 = False
C0 == False
elif D == 0b1101010100:
C1 = False
C0 == True
elif D == 0b0010101010:
C1 = True
C0 == False
elif D == 0b1101010101:
C1 = True
C0 == True
return (Q, C0, C1)
As for data enable, it only goes low during inactive or blank regions. So it sounds pretty easy :p.
For example, a sequence of a white then a black pixel (FF FF FF 00 00 00) would translate to ()