HDMI Capture

From Hamsterworks Wiki!

Jump to: navigation, search

This FPGA Project was completed in August 2015.

You might be able to use this to capture some repetitive signal.

I needed to capture HDMI data as sometimes my decoded VSYNC signal was accidentally being asserted during the middle of a video frame. Once I captured the raw data it can be used as a (very large) test vector for simulation to expose the root cause of flaw. Because normally the video keeps changing, I paused the video on a frame that triggered the error, and then capture the HDMI entire frame, in small blocks of 1024 sets of symbols at a time.

These 1024 symbols were then transferred out the serial port, after which the following block of data is then captured. As I was sending out the data as 8 digit hex strings (followed with a new line) a 1080p frame would require about 2,500 capture and send cycles - requiring about 40 minutes to transfer at 115,200 baud!

Contents

VHDL source

Here is the code for the VHDL module - it isn't written to be efficient, just to work!

The process in the 'clk' domain captures 1024 cycles worth of data and then signals the other process. This other process runs in the 'clk100' domain them sends the data out as a set of 8-character ASCII strings. Once the data is sent the 'clk' domain waits until it sees symbol_sync, waits until it gets back to where the last capture ended, and then captures the next set of 1024 symbols.

All that is needed is a place to anchor the start of the captured. To do this, symbol_sync was asserted when the falling edge of the VSYNC signal was seen.

----------------------------------------------------------------------------------
-- Engineer: Mike Field <hamster@snap.net.nz> 
-- 
-- Module Name: symbol_dump - Behavioral
--
-- Description: Create a trace of HDMI symbols - a 1024 word memory block is filled 
--              and then transmitted over rs232. Then refilled again, but this time
--              waiting an extra 1024 cycles from when symbol_sync is asserted.
--              
--             If the video source is paused, then the entire frame can be capbured
--             (excluding ADP data periods, which might get broken on the boundary.
--
--             The captured data can then be analysed by hand or used to drive 
--             simulations.
-- 
----------------------------------------------------------------------------------


library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity symbol_dump is
    Port ( clk : in STD_LOGIC;
           clk100 : in STD_LOGIC;
           symbol_sync : in STD_LOGIC;
           symbol_ch0 : in STD_LOGIC_VECTOR (9 downto 0);
           symbol_ch1 : in STD_LOGIC_VECTOR (9 downto 0);
           symbol_ch2 : in STD_LOGIC_VECTOR (9 downto 0);
           rs232_tx : out STD_LOGIC);
end symbol_dump;

architecture Behavioral of symbol_dump is
    type array_hex is array(0 to 15) of std_logic_vector(9 downto 0);
    signal hex : array_hex := (
            "1001100000", "1001100010", "1001100100", "1001100110",
            "1001101000", "1001101010", "1001101100", "1001101110",
            "1001110000", "1001110010", "1010000010", "1010000100",
            "1010000110", "1010001000", "1010001010", "1010001100");

    type array_memory is array(0 to 1023) of std_logic_vector(29 downto 0);
    signal memory : array_memory := (others => (others =>'0')); 
    signal position      : unsigned(23 downto 0) := (others => '0');
    signal capture_point : unsigned(23 downto 0) := (others => '0');
    signal write_address : unsigned(9 downto 0)  := (others => '0');
    signal write_enable  : std_logic := '0';
    signal write_data    : std_logic_vector(29 downto 0) := (others => '0');
    
    ---  For signaling into the 100MHz domain
    signal ready_to_send        : std_logic := '0';
    signal ready_to_send_meta   : std_logic := '0';
    signal ready_to_send_synced : std_logic := '0';
    ---  For signaling into the pixel clock domain
    signal sending_data : std_logic := '0';
    signal sending_data_meta   : std_logic := '0';
    signal sending_data_synced : std_logic := '0';

    signal rd_address   : unsigned(9 downto 0)  := (others => '0');
    signal rd_data      : std_logic_vector(29 downto 0) := (others => '0');
    signal tx_data      : std_logic_vector(89 downto 0)     := (others => '1');
    signal tx_count     : unsigned(7 downto 0)      := (others => '0');
    signal baud_counter : unsigned(12 downto 0)     := (others => '0');
    signal baud_counter_max : unsigned(12 downto 0) := to_unsigned(100000000/115200,13);
begin

process(clk)
    begin
        if rising_edge(clk) then
            if write_enable = '1' then
                memory(to_integer(write_address)) <= symbol_ch2 & symbol_ch1 & symbol_ch0;
            end if;
            -- track where we are in the frame.
            if symbol_sync = '1' then
                position <= (others => '0');
            else
                position <= position+1;
            end if;
            
            -- If we are capturing remember where we have got up to
            -- and see if we have captured our full amount.
            if write_enable = '1' then
                capture_point <= position;
                write_data <= symbol_ch2 & symbol_ch1 & symbol_ch0;
                write_data <= symbol_ch2 & symbol_ch1 & symbol_ch0;
                if write_address = 1023 then
                    write_enable <= '0';
                    ready_to_send <= '1';
                end if;
                write_address <= write_address+1;
            end if;

            -- Do we start capturing at this point? 
            -- (write address resets itself to 0, so we don't
            -- have to do it here) 
            if position = capture_point and ready_to_send = '0' and sending_data_synced = '0' then
                write_enable <= '1';
            end if;
            
            -- Do we need to re-arm ready for the next capture
            if sending_data_synced = '1' then
               ready_to_send <= '0';
            end if;
            
            
            -- Bring data_sent into this clock domain
            sending_data_synced <= sending_data_meta; 
            sending_data_meta   <= sending_data; 

        end if;
    end process;
    
process(clk100)
    begin
        if rising_edge(clk100) then
            
            if baud_counter = 0 then
                rs232_tx <= tx_data(0);
                tx_data <= '1' & tx_data(89 downto 1);
                baud_counter <= baud_counter_max;
                if(tx_count > 0) then
                  tx_count <= tx_count-1;
                end if;
            else
                baud_counter <= baud_counter -1;    
            end if;
            
            if sending_data = '1' or ready_to_send_synced = '1' then            
                if tx_count = 0 then
                    tx_data(89 downto 80) <= hex(to_integer(unsigned(rd_data( 3 downto  0))));
                    tx_data(79 downto 70) <= hex(to_integer(unsigned(rd_data( 7 downto  4))));
                    tx_data(69 downto 60) <= hex(to_integer(unsigned(rd_data(11 downto  8))));
                    tx_data(59 downto 50) <= hex(to_integer(unsigned(rd_data(15 downto 12))));
                    tx_data(49 downto 40) <= hex(to_integer(unsigned(rd_data(19 downto 16))));
                    tx_data(39 downto 30) <= hex(to_integer(unsigned(rd_data(23 downto 20))));
                    tx_data(29 downto 20) <= hex(to_integer(unsigned(rd_data(27 downto 24))));
                    tx_data(19 downto 10) <= hex(to_integer(unsigned(rd_data(29 downto 28))));
                    tx_data( 9 downto  0) <= "1000010100"; -- New line
                    tx_count <= to_unsigned(90,8);

                    rd_data <= memory(to_integer(rd_address));
                    rd_address <= rd_address+1;
                    if rd_address = 1023 then
                        sending_data <= '0';
                    else
                        sending_data <= '1';
                    end if;
                end if;
            end if;
            
            -- Bring the ready to send signal into this clock domain    
            ready_to_send_synced <= ready_to_send_meta; 
            ready_to_send_meta   <= ready_to_send; 
        end if;
    end process;
end Behavioral;

Example output

Here is an example of what is captured using Putty's log to file abilities - it is and ADP data packet, including preamble and guard band:

15455354
15455354
15455354
15455354
15455354
15455354
15455354
15455354
15455354
15455354
0AB2AF54
0AB2AF54
0AB2AF54
0AB2AF54
0AB2AF54
0AB2AF54
0AB2AF54
0AB2AF54
1334CE8E
1334CE8E
29CA729C
29CA728E
29CA72CC
29CA72CC
29CA72CC
29CA72CC
29CA72CC
29CA72CC
29CA728E
29CA72CC
29CA72CC
29CA72CC
29CA72CC
29CA72CC
29CA72CC
29CA72CC
29CA72CC
29CA72CC
29CA72CC
29CA72CC
29CA72CC
29CA72CC
29CA72CC
29CA72CC
29CA728E
29CA72CC
29CA728E
29CA72CC
29CA72CC
29CA728E
29CA728E
29CA72CC
1334CE8E
1334CE8E
15455354
15455354
15455354

C source

Here is the C source for decoding the CTL and TERC4 data:

/*****************************************
 * hdmi_decode.c
 *
 * AUthor:  Mike Field <hamster@snap.net.nz<
 *
 * A Little utility to convert hex strings
 * into HDMI CTL and TERC4 symbols for
 * further analysis.
 ***************************************/
#include <stdio.h>

void ctl_decode(unsigned s) {
  switch(s) {
     case 0x354: printf(" CTL0"); return;
     case 0x0AB: printf(" CTL1"); return;
     case 0x154: printf(" CTL2"); return;
     case 0x2AB: printf(" CTL3"); return;
  }
  printf("     ");
}

void terc4_decode(unsigned s) {
  switch(s) {
     case 0x29C: printf(" 0"); return;
     case 0x263: printf(" 1"); return;
     case 0x2E4: printf(" 2"); return;
     case 0x2E2: printf(" 3"); return;

     case 0x171: printf(" 4"); return;
     case 0x11E: printf(" 5"); return;
     case 0x18E: printf(" 6"); return;
     case 0x13C: printf(" 7"); return;

     case 0x2CC: printf(" 8"); return;
     case 0x139: printf(" 9"); return;
     case 0x19C: printf(" A"); return;
     case 0x2C6: printf(" B"); return;

     case 0x28E: printf(" C"); return;
     case 0x271: printf(" D"); return;
     case 0x163: printf(" E"); return;
     case 0x2C3: printf(" F"); return;
  }
  printf(" ");
}

int main(int argc, char *argv[])
{
  FILE *f;
  unsigned value;
  unsigned n = 0;

  f = fopen(argv[1], "rb");
  if(f == NULL) {
     fprintf(stderr,"Unable to open file\n");
     return 1;
  }

  while(fscanf(f,"%x",&value) == 1)
  {
    unsigned ch2, ch1, ch0;
    ch2 = (value>>20)&0x3FF;
    ch1 = (value>>10)&0x3FF;
    ch0 = (value>> 0)&0x3FF;
    printf("%8i: %03X %03X %03X   ",n, ch2, ch1, ch0);
    ctl_decode(ch2);
    ctl_decode(ch1);
    ctl_decode(ch0);
    terc4_decode(ch2);
    terc4_decode(ch1);
    terc4_decode(ch0);
    printf("\n");
    n++;
  }
  fclose(f);
  return 0;
}

Sample output

Here is a ADP data island, partially decoded.

   3330: 0AB 0AB 354    CTL1 CTL1 CTL0
   3331: 0AB 0AB 354    CTL1 CTL1 CTL0
   3332: 0AB 0AB 354    CTL1 CTL1 CTL0
   3333: 0AB 0AB 354    CTL1 CTL1 CTL0
   3334: 133 133 28E                     C
   3335: 133 133 28E                     C
   3336: 29C 29C 29C                   0 0 0
   3337: 29C 29C 28E                   0 0 C
   3338: 29C 29C 2CC                   0 0 8
   3339: 29C 29C 2CC                   0 0 8
   3340: 29C 29C 2CC                   0 0 8
   3341: 29C 29C 2CC                   0 0 8
   3342: 29C 29C 2CC                   0 0 8
   3343: 29C 29C 2CC                   0 0 8
   3344: 29C 29C 28E                   0 0 C
   3345: 29C 29C 2CC                   0 0 8
   3346: 29C 29C 2CC                   0 0 8
   3347: 29C 29C 2CC                   0 0 8
   3348: 29C 29C 2CC                   0 0 8
   3349: 29C 29C 2CC                   0 0 8
   3350: 29C 29C 2CC                   0 0 8
   3351: 29C 29C 2CC                   0 0 8
   3352: 29C 29C 2CC                   0 0 8
   3353: 29C 29C 2CC                   0 0 8
   3354: 29C 29C 2CC                   0 0 8
   3355: 29C 29C 2CC                   0 0 8
   3356: 29C 29C 2CC                   0 0 8
   3357: 29C 29C 2CC                   0 0 8
   3358: 29C 29C 2CC                   0 0 8
   3359: 29C 29C 2CC                   0 0 8
   3360: 29C 29C 28E                   0 0 C
   3361: 29C 29C 2CC                   0 0 8
   3362: 29C 29C 28E                   0 0 C
   3363: 29C 29C 2CC                   0 0 8
   3364: 29C 29C 2CC                   0 0 8
   3365: 29C 29C 28E                   0 0 C
   3366: 29C 29C 28E                   0 0 C
   3367: 29C 29C 2CC                   0 0 8
   3368: 133 133 28E                     C
   3369: 133 133 28E                     C
   3370: 154 154 354    CTL2 CTL2 CTL0
   3371: 154 154 354    CTL2 CTL2 CTL0
   3372: 154 154 354    CTL2 CTL2 CTL0

Although it didn't show any issues with the data that was captured, it did allow me to find the bug - if the ADP guard band characters turned up in the video stream then the top two bits of channel 0's TERC4 code was assigned to the VSYNC and HSYNC signals, causing runt pulses on the VSYNC line.

Personal tools