DVI link simulation

2 min read (339 words)

#dev #glitch art

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 :

InputChannel 0Channel 1Channel 2
DBlue valueGreen valueRed value
C0HSYNCCTL0CTL2
C1VSYNCCTL1CTL3
DEData enableData enableData 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 sent
  • cnt: 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 as previousCnt for the previous disparity or as cnt for the new disparity.
  • q_out: The output channel value (10 bits)
  • n1(x)$: the number of 1s in x`
  • n0(x)$: the number of 0s in x`

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 ()