Minimal HDMI

From Hamsterworks Wiki!

Jump to: navigation, search

This FPGA Project was completed in March 2015.

After completing my Minimal DVI-D project I wanted to get a Minimal HDMI output working.

Here is the project running in RGB mode (i.e. with a NULL data packet) - Note how the TV now detects it as HDMI (on DVI-D designs it shows "DVI-D"):

Hdmi-RGB.jpg

And here is the project, using the same test pattern, but the display is now running in YCC mode (i.e. with the YCC AVI Infoframe in the data packet) - oh and sorry about it being a little washed out in the photo, it is really vibrant in real life...:

Hdmi-YCC.jpg


Please note that these are my notes about implementing a minimally functional HDMI-compatible output stream, and isn't a serious attempt at implementing HDMI. It includes many short cuts and assumptions and most likely a few errors. It is here purely for educational purposes, and might be of use to somebody doing a proper job of implementing HDMI.

Contents

So what is different about HDMI compared with DVI-D?

HDMI has special blocks called 'data islands' inserted in the data stream. These data islands can carry additional things like metadata about the video stream and audio packets. Each data island is 32 words long, and transfers four header bytes (3 bytes data + 1 byte ECC) and four eight-byte subpackets (7 bytes data, one byte ECC), along with the HSYNC and VSYNC signals. The sync and header is sent on channel 0, and the subpackets are sent on channels 1 and 2.

When placed on the wire, data Islands have a 8-word preamble of standard CTL period codes to announce their immanent arrive, a 2-word leading guard band, followed by one ore more blocks of data Island data, and then another 2-word guard band to close the data island:

 [preamble - 8 cycles] [leading guard band - 2 cycles] [ one or more data blocks - 32 cycles each] [trailing guard bland - 2 cycles] 

Data islands can be inserted almost anywhere in the video stream that isn't sending video data, as long as there are four 10-bit frames of standard DVI-D-like CTL words before the preamble codes, and another four 10-bit frames of standard DVI-D-like CTL words before the next data island or video preamble.

Video streams are also a little different too. The pixel data is announced with a preamble and leading guard band, but no trailing guard band is used for video data.

 [video preamble 8 cycles] [video guard band - 2 cycles] [pixel data - 800 cycles for 800x600]

NOTE: The full HDMI specification can be found in many different public places on the web, just by using the appropriate Google-fu. Please don't ask me for it if your Google-fu is weak and you can't find it.

The NULL data packet

The standard allows for a NULL data island, that can be used as padding. The great thing about the NULL data island is all of the payload's bits are zero, including the ECC bits. This makes it a very useful option for when you want to tightly schedule when you send a data island, but may not always be using it (for example, you could have a set of pre-scheduled data islands where audio samples could be packed, or if no sample data is available a null packet could be sent instead.

Any data packet is sent using the TERC4 coding scheme where each HDMI channel sends one of sixteen 10-bit symbols that allows encoding of 4 bits of data. Channel 0 carries a framing bit, a packet header bit, and the HSYNC and VSYNC signals. Channel 1 carries the even bits of the subpackets (bits 0,2,4 and 6), and channel 2 carries the odd bits of the subpackets (bits 1,3,5,7).

      Channel 0      Channel 1            Channel2
      MSB   LSB      MSB        LSB       MSB        LSB
 0:   0  h0 V H      d0  c0  b0  a0       D1  C1  B1  A1
 1:   1  h1 V H      d2  c2  b2  a2       D3  C3  B3  A3
 2:   1  h2 V H      d4  c4  b4  a4       D5  C5  B5  A5
 3:   1  h3 V H      d6  c6  b6  a6       D6  C7  B7  A7
...
31:   1 h31 V H      d62 c62 b62 a62      D63 C63 B63 A63

  h = header bit
  V = Vertical Sync
  H = Horizontal Sync
  A = An odd bit from subpacket 0
  B = An odd bit from subpacket 1
  C = An odd bit from subpacket 2
  D = An odd bit from subpacket 3
  a = An even bit from subpacket 0
  b = An even bit from subpacket 1
  c = An even bit from subpacket 2
  d = An even bit from subpacket 3

So if transmitted where HSYNC is '0' and VSYNC is '1' a NULL data packet can be sent as follows:

    TERC4 data:
    Channel0 Channel1 Channel2
 0:   0010     0000      0000
 1:   1010     0000      0000
 2:   1010     0000      0000
 3:   1010     0000      0000
...
30:   1010     0000      0000
31:   1010     0000      0000

Mapping those through to the 10-bit TERC4 symbols, here are the 10-bit patterns that need to be sent (excluding the preamble and guard bands, and once again where HSYNC is '0' and VSYNC is '1' ):

    TERC4 data:
     Channel0   Channel1   Channel2
 0: 1011100100 1010011100 1010011100
 1: 0110011100 1010011100 1010011100
 2: 0110011100 1010011100 1010011100
 3: 0110011100 1010011100 1010011100
...
30: 0110011100 1010011100 1010011100
31: 0110011100 1010011100 1010011100

The information above, along with the symbols for TMDS symbols for used pixel data, the DVI-D CTL period symbols, the preamble symbols (which are DVI-D CTL symbols) and the guard band symbols is enough to construct a stream of bits that will be recognized as a HDMI signal by most devices - all you need to do is send a NULL data island during the vertical blanking period.

Building the AVI Infoframe data packet

For video data, the main reason to use the more complex HDMI over DVI-D is so you can send data in other colour spaces than RGB, the most useful being YCbCr.

There needs to be a way to tell the HDMI sink what format the data is being sent in, and this is a data island packet called an Auxiliary Video information (AVI) Video Infoframe. As expected, this describes that format and attributes of the pixels that are going over the wire, including the colourspace the data is in.

To send this we need to be able to send something other than a NULL packet, and that involves us running up against the very weakly documented ECC scheme used in HDMI data packets. The scheme used is BCH(32,24) and BCH(64,56), and both methods use the same algorithm but with different ratios of user data to ECC.

The easiest way to show how to do this is to build a packet with pen and paper.

Building the packet header

From the documentation:

 Header Byte 0, Packet Type 0x82, 
 Header Byte 1, Version x02
 Header Byte 2, Length 0x0D       
 Header Byte 3, ECC

So in Hex, the four byte header looks like this:

 [ECC goes here]0D0282

Which in binary looks like this:

 MSB                          LSB
 ????????000010110000001010000010

Computing the ECC

The algorithm is really simple.

0. Set an 8-bit register to 0.

Then for each data bit, starting from LSB:

1. XOR the current data bit with the LSB of the 8-bit register.

2. Shift the register right one bit.

3. If the result of step 1 was '1' then we XOR with 10000011, otherwise do nothing

And finally

4. The state of the shift register is the ECC value

Here is the hand-calculated packet header:

Byte 0:
        XORed  Shifted   Value to   Updated   
 Data	with   LFSR      XOr into   Register
 Bit    LSB    Register  Register   State
+-----+-----+----------+----------+----------
|  0  |  0  | 00000000 | 00000000 | 00000000
+-----+-----+----------+----------+----------
|  1  |  0  | 00000000 | 10000011 | 10000011
+-----+-----+----------+----------+----------
|  0  |  1  | 01000001 | 10000011 | 11000010
+-----+-----+----------+----------+----------
|  0  |  1  | 01100001 | 00000000 | 01100001
+-----+-----+----------+----------+----------
|  0  |  0  | 00110000 | 10000011 | 10110011
+-----+-----+----------+----------+----------
|  0  |  1  | 01011001 | 10000011 | 11011010
+-----+-----+----------+----------+----------
|  0  |  1  | 01101101 | 00000000 | 01101101
+-----+-----+----------+----------+----------
|  1  |  1  | 00110110 | 00000000 | 00110110
+-----+-----+----------+----------+----------

Byte 1:
+-----+-----+----------+----------+----------
|  0  |  1  | 00011011 | 10000011 | 00011011
+-----+-----+----------+----------+----------
|  1  |  1  | 00001101 | 00000000 | 00001101
+-----+-----+----------+----------+----------
|  0  |  1  | 00000110 | 10000011 | 10000101
+-----+-----+----------+----------+----------
|  0  |  1  | 01000010 | 10000011 | 11000001
+-----+-----+----------+----------+----------
|  0  |  1  | 01100000 | 10000011 | 11100011
+-----+-----+----------+----------+----------
|  0  |  1  | 01110001 | 10000011 | 11110010
+-----+-----+----------+----------+----------
|  0  |  0  | 01111001 | 00000000 | 01111001
+-----+-----+----------+----------+----------
|  0  |  1  | 00111100 | 10000011 | 10111111
+-----+-----+----------+----------+----------

Byte 2:
+-----+-----+----------+----------+----------
|  1  |  0  | 01011111 | 00000000 | 01011111
+-----+-----+----------+----------+----------
|  0  |  1  | 00101111 | 10000011 | 10101100
+-----+-----+----------+----------+----------
|  1  |  1  | 01010110 | 10000011 | 11010101
+-----+-----+----------+----------+----------
|  1  |  0  | 01101010 | 00000000 | 01101010
+-----+-----+----------+----------+----------
|  0  |  0  | 00110101 | 00000000 | 00110101
+-----+-----+----------+----------+----------
|  0  |  1  | 00011010 | 10000011 | 10011001
+-----+-----+----------+----------+----------
|  0  |  1  | 01001100 | 10000011 | 11001111
+-----+-----+----------+----------+----------
|  0  |  1  | 01100111 | 10000011 | 11100100
+-----+-----+----------+----------+----------

The ECC is the value of the status register, 11100100

This can now be inserted in the packet header
to form 
  MSB                             LSB
  11100100 00001101 00000010 10000010

Building the subpacket data

As mentioned, the packet includes four data subpackets, each with 7 bytes of user data and one byte of ECC.

For the AVI Video Infoframe, all the non-zero data is in subpacket0, with the other subpackets carrying zeros. That makes it quite easy as once again the ECC for a stream of zeros is zeros.

The calculation of the ECC for the subpackets uses exactly the same algorithm, apart from the data packets being seven bytes long, so isn't worth showing again.

So now we need to build the subpackets:

Subpacket0:
  Byte 0 - [Checksum of none-ECC bytes of the data and header bytes]
  Byte 1 - 01000000 - Bits 5 and 6 Select Video format (RGB (00) vs YCC (10))
  Byte 2 - 00000000 - Colourspace, aspect ratio and scaling factors
  Byte 3 - 00000000 - Finer details of the colourspace
  Byte 4 - 00000000 - Video format VIC - haven't got one for 800x600
  Byte 5 - 00000000 - Reserved bits and pixel repetition factor
  Byte 6 - 00000000 - start of bar info fields - can be zeros
  Byte 7 - [  ECC   ]
Subpacket1:
  Byte 0 - 00000000
  Byte 1 - 00000000
  Byte 2 - 00000000
  Byte 3 - 00000000
  Byte 4 - 00000000
  Byte 5 - 00000000   - end of bar info fields 
  Byte 6 - 00000000   - Last byte of AVI infoframe data
  Byte 7 - [  ECC   ] - As the data is zeros we know this already 00000000

Subpacket2 & Subpacket3 are all zeros.


The checksum is selected so that the the checksum and all the data bytes (in both header and subpackets, excluding the ECC bytes) add up to 00000000 - in this case 00101111 is what is required - in this case the header and data are 0x82 + 0x40 + 0xD0 + 0x40 = D1, so the checksum is 0x2F

Here are the four subpackets, including the ECC bytes.

              MSB                                                                 LSB
Subpacket 0:  01110001 00000000 00000000 00000000 00000000 00000000 01000000 00101111
Subpacket 1:  00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
Subpacket 2:  00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
Subpacket 3:  00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

Putting the pieces of the Data Island together

Just to recap we have the following data for the packet header and subpackets:

              MSB                             LSB
Header:       11100100 00001101 00000010 10000010

              MSB                                                                 LSB
Subpacket 0:  01110001 00000000 00000000 00000000 00000000 00000000 01000000 00101111
Subpacket 1:  00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
Subpacket 2:  00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
Subpacket 3:  00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

This then needs to be switched into the (somewhat problematic) layout used for conversion to the three channels of 10-bit symbols that are sent (as always assuming VSYNC is asserted and HSYNC is not):

     Ch0  Ch1  Ch2
      hVH dcba DCBA
 0:  0010 0001 0001
 1:  1110 0001 0001
 2:  1010 0000 0001
 3:  1010 0000 0000
 4:  1010 0000 0000
 5:  1010 0000 0000
 6:  1010 0000 0000
 7:  1110 0001 0000
 8:  1010 0000 0000
 9:  1110 0000 0000
10:  1010 0000 0000
11:  1010 0000 0000
12:  1010 0000 0000
13:  1010 0000 0000
14:  1010 0000 0000
15:  1010 0000 0000
16:  1110 0000 0000
17:  1010 0000 0000
18:  1110 0000 0000
19:  1110 0000 0000
20:  1010 0000 0000
21:  1010 0000 0000
22:  1010 0000 0000
23:  1010 0000 0000
24:  1010 0000 0000
25:  1010 0000 0000
26:  1110 0000 0000
27:  1010 0000 0000
28:  1010 0001 0000
29:  1110 0000 0000
30:  1110 0001 0001
31;  1110 0001 0000

And there are the nibbles to be encoded and sent during the only data island, which is the AVI Infoframe to switch to YCC 444 mode, as used in my proof of concept project!

On with the source!

So on with the source. It's in 6 parts:

  1. hdmi_output_test.vhd - The top level module tying these three together
  2. vga_gen.vhd - A 800x600 VGA test pattern generator
  3. vga_clocking.vhd - Generating the 40MHz pixel clock from the 50MHz external clock
  4. Minimal_HDMI_Symbols.vhd, which converts the VGA data into three streams of 10-bit symbols. It also inserts a single HDMI data island after the first falling edge of hsync during period when vsync is active.
  5. Serializers.vhd - converts the 10-bit symbols to three serial bitstreams, using DDR registers at clocked at 5x the pixel clock.
  6. miniSpartan6.ucf - The implementation constraints for Scarab Hardware's miniSpartan6+ FPGA board

The full design requires 67 slices, 36 of which are the VGA generator and the other 31 implement the HDMI output.

hdmi_output_test.vhd

----------------------------------------------------------------------------------
-- Engineer: Mike Field <hamster@snap.net.nz>
-- 
-- Top level design for my minimal HDMI output project
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity hdmi_output_test is
    Port ( clk50         : in  STD_LOGIC;

           hdmi_out_p : out  STD_LOGIC_VECTOR(3 downto 0);
           hdmi_out_n : out  STD_LOGIC_VECTOR(3 downto 0);
                      
           leds       : out std_logic_vector(7 downto 0));
end hdmi_output_test;

architecture Behavioral of hdmi_output_test is

   COMPONENT vga_gen
   PORT(
      clk50           : IN std_logic;          
      pixel_clock     : OUT std_logic;
      red_p           : OUT std_logic_vector(7 downto 0);
      green_p         : OUT std_logic_vector(7 downto 0);
      blue_p          : OUT std_logic_vector(7 downto 0);
      blank           : OUT std_logic;
      hsync           : OUT std_logic;
      vsync           : OUT std_logic
      );
   END COMPONENT;

   COMPONENT Minimal_hdmi_symbols
   PORT(
      clk : IN std_logic;
      blank : IN std_logic;
      hsync : IN std_logic;
      vsync : IN std_logic;
      red   : IN std_logic;
      green : IN std_logic;
      blue  : IN std_logic;          
      c0    : OUT std_logic_vector(9 downto 0);          
      c1    : OUT std_logic_vector(9 downto 0);          
      c2    : OUT std_logic_vector(9 downto 0)         
      );
   END COMPONENT;
 
	COMPONENT serializers
	PORT(
		clk : IN std_logic;
		c0 : IN std_logic_vector(9 downto 0);
		c1 : IN std_logic_vector(9 downto 0);
		c2 : IN std_logic_vector(9 downto 0);          
		hdmi_p : OUT std_logic_vector(3 downto 0);
		hdmi_n : OUT std_logic_vector(3 downto 0)
		);
	END COMPONENT;
 
   signal pixel_clock     : std_logic;

   signal red_p   : std_logic_vector(7 downto 0);
   signal green_p : std_logic_vector(7 downto 0);
   signal blue_p  : std_logic_vector(7 downto 0);
   signal blank   : std_logic;
   signal hsync   : std_logic;
   signal vsync   : std_logic;          

   signal c0, c1, c2 : std_logic_vector(9 downto 0);
begin
   leds <= x"AA";
   
---------------------------------------
-- Generate a 1280x720 VGA test pattern
---------------------------------------
Inst_vga_gen: vga_gen PORT MAP(
      clk50 => clk50,
      pixel_clock     => pixel_clock,      
      red_p           => red_p,
      green_p         => green_p,
      blue_p          => blue_p,
      blank           => blank,
      hsync           => hsync,
      vsync           => vsync
   );

---------------------------------------------------
-- Convert 9 bits of the VGA signals to the DVI-D/TMDS output 
---------------------------------------------------
i_Minimal_hdmi_symbols: Minimal_hdmi_symbols PORT MAP(
      clk    => pixel_clock,
      blank  => blank,
      hsync  => hsync,
      vsync  => vsync,
      red    => red_p(7),
      green  => green_p(7),
      blue   => blue_p(7),
      c0     => c0,
      c1     => c1,
      c2     => c2
   );

i_serializers : serializers PORT MAP (
      clk    => pixel_clock,
      c0     => c0,
      c1     => c1,
      c2     => c2,
      hdmi_p => hdmi_out_p,
      hdmi_n => hdmi_out_n);
      
end Behavioral;


vga_gen.vhd

----------------------------------------------------------------------------------
-- Engineer: Mike Field <hamster@snap.net.nz>
-- 
-- Description: Generates a test 800x600 signal 
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity vga_gen is
    Port ( clk50           : in  STD_LOGIC;
		     pixel_clock     : out std_logic;

           red_p   : out STD_LOGIC_VECTOR (7 downto 0) := (others => '0');
           green_p : out STD_LOGIC_VECTOR (7 downto 0) := (others => '0');
           blue_p  : out STD_LOGIC_VECTOR (7 downto 0) := (others => '0');
           blank   : out STD_LOGIC := '0';
           hsync   : out STD_LOGIC := '0';
           vsync   : out STD_LOGIC := '0');
end vga_gen;

architecture Behavioral of vga_gen is
	COMPONENT vga_clocking
	PORT( clk50           : IN  std_logic;          
         pixel_clock     : OUT std_logic);
	END COMPONENT;

   constant h_rez        : natural := 800;
   constant h_sync_start : natural := 800+40;
   constant h_sync_end   : natural := 800+40+128;
   constant h_max        : natural := 1056;
   signal   h_count      : unsigned(11 downto 0) := (others => '0');
   signal   h_offset     : unsigned(7 downto 0) := (others => '0');

   constant v_rez        : natural := 600;
   constant v_sync_start : natural := 600+1;
   constant v_sync_end   : natural := 600+1+4;
   constant v_max        : natural := 628;
   signal   v_count      : unsigned(11 downto 0) := x"250";
   signal   v_offset     : unsigned(7 downto 0) := (others => '0');
   signal clk40 : std_logic;
begin

Inst_clocking: vga_clocking PORT MAP(
		clk50           => clk50,
		pixel_clock     => clk40
	);
   pixel_clock <= clk40;

   
process(clk40)
   begin
      if rising_edge(clk40) then
         if h_count < h_rez and v_count < v_rez then
            red_p   <= std_logic_vector(h_count(7 downto 0)+h_offset);
            green_p <= std_logic_vector(v_count(7 downto 0)+v_offset);
            blue_p  <= std_logic_vector(h_count(7 downto 0)+v_count(7 downto 0));
            blank   <= '0';
            if h_count = 0 or h_count = h_rez-1 then
               red_p   <= (others => '1');
               green_p <= (others => '1');
               blue_p  <= (others => '1');
            end if;
            if v_count = 0 or v_count = v_rez-1 then
               red_p   <= (others => '1');
               green_p <= (others => '1');
               blue_p  <= (others => '1');
            end if;
         else
            red_p   <= (others => '0');
            green_p <= (others => '0');
            blue_p  <= (others => '0');
            blank   <= '1';
         end if;

         if h_count >= h_sync_start and h_count < h_sync_end then
            hsync <= '1';
         else
            hsync <= '0';
         end if;
         
         if v_count >= v_sync_start and v_count < v_sync_end then
            vsync <= '1';
         else
            vsync <= '0';
         end if;
         
         if h_count = h_max then
            h_count <= (others => '0');
            if v_count = v_max then
               h_offset <= h_offset + 1;
               v_offset <= v_offset + 1;
               v_count <= (others => '0');
            else
               v_count <= v_count+1;
            end if;
         else
            h_count <= h_count+1;
         end if;

      end if;
   end process;

end Behavioral;



vga_clocking.vhd

----------------------------------------------------------------------------------
-- Engineer: Mike Field <hamster@snap.net.nz<
-- 
-- Description: Generate a 40Mhz Pixel clock from the 50Mhz input
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
library UNISIM;
use UNISIM.VComponents.all;

entity vga_clocking is
    Port ( clk50           : in  STD_LOGIC;
           pixel_clock     : out STD_LOGIC);
end vga_clocking;

architecture Behavioral of vga_clocking is
   signal clock_x1             : std_logic;
   signal clock_x1_unbuffered  : std_logic;
   signal clk_feedback         : std_logic;
   signal clk50_buffered       : std_logic;
   signal pll_locked           : std_logic;
begin
   pixel_clock     <= clock_x1;
   
   -- Multiply clk50m by 15, then divide by 10 for the 75 MHz pixel clock
   -- Because the all come from the same PLL the will all be in phase 
   PLL_BASE_inst : PLL_BASE
   generic map (
      CLKFBOUT_MULT => 16,                  
      CLKOUT0_DIVIDE => 20,       CLKOUT0_PHASE => 0.0,  -- Output pixel clock, 1.5x original frequency
      CLK_FEEDBACK => "CLKFBOUT",                        -- Clock source to drive CLKFBIN ("CLKFBOUT" or "CLKOUT0")
      CLKIN_PERIOD => 20.0,                              -- IMPORTANT! 20.00 => 50MHz
      DIVCLK_DIVIDE => 1                                 -- Division value for all output clocks (1-52)
   )
      port map (
      CLKFBOUT => clk_feedback, 
      CLKOUT0  => clock_x1_unbuffered,
      CLKOUT1  => open,
      CLKOUT2  => open,
      CLKOUT3  => open,
      CLKOUT4  => open,
      CLKOUT5  => open,
      LOCKED   => pll_locked,      
      CLKFBIN  => clk_feedback,    
      CLKIN    => clk50_buffered, 
      RST      => '0'              -- 1-bit input: Reset input
   );

BUFG_clk    : BUFG port map ( I => clk50,                O => clk50_buffered);
BUFG_pclock : BUFG port map ( I => clock_x1_unbuffered,  O => clock_x1);

end Behavioral;

minimal_HDMI_symbols.vhd

This is the key file in the whole design, where the VGA data is first mapped to one of 12 code values, and the preambles, guard bands and HDMI data island are included, requiring one of 32 symbols to represent all three channels.

The following cycle these 32 symbols are expanded out into the three 10-bit symbols that will actually be sent out on the wire.

As mentioned earlier, the data packet is inserted in the middle of the VSYNC pulse, after the first falling edge of HSYNC. I've done this because I'm guaranteed to have at least 800 cycles before any of the sync signals change or and video data needs to be transmitted.

----------------------------------------------------------------------------------
-- Engineer: Mike Field <hamster@snap.net.nz<
-- 
-- Description: Convert a 3-bit VGA signal into the 10-bit symbols required
-- for HDMI
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity minimal_hdmi_symbols is
    Port ( clk : in  STD_LOGIC;
           hsync, vsync,  blank : in  STD_LOGIC;
           red,   green,  blue  : in  STD_LOGIC;
           c0,    c1,     c2    : out STD_LOGIC_VECTOR (9 downto 0));
end minimal_hdmi_symbols;

architecture Behavioral of minimal_hdmi_symbols is
   type a_symbol_queue is array (0 to 10) of STD_LOGIC_VECTOR (4 downto 0);
   
   signal symbol_queue : a_symbol_queue                  := (others => (others => '0'));
   signal symbols      :  STD_LOGIC_VECTOR (29 downto 0) := (others => '0');
   
   signal last_blank : std_logic := '0';
   signal last_vsync : std_logic := '0';
   signal last_hsync : std_logic := '0';
   
   signal data_island_armed : std_logic := '0';
   signal data_island_index : unsigned(5 downto 0) := (others => '1');
begin
   c0 <= symbols(29 downto 20);
   c1 <= symbols(19 downto 10);
   c2 <= symbols( 9 downto  0);

process(clk) 
   begin
      if rising_edge(clk) then
         case symbol_queue(0) is  
            ---------------------------------------------------------------
            -- Eight TMDS encoded colours for testing
            ---------------------------------------------------------------
            when "00000" => symbols <= "0111110000" & "0111110000" & "0111110000"; -- RGB 0x101010 - Black
            when "00001" => symbols <= "0111110000" & "0111110000" & "1011110000"; -- RGB 0xEF1010 - Red
            when "00010" => symbols <= "0111110000" & "1011110000" & "0111110000"; -- RGB 0x10EF10 - Green
            when "00011" => symbols <= "0111110000" & "1011110000" & "1011110000"; -- RGB 0xEFEF10 - Cyan
            when "00100" => symbols <= "1011110000" & "0111110000" & "0111110000"; -- RGB 0x1010EF - Blue
            when "00101" => symbols <= "1011110000" & "0111110000" & "1011110000"; -- RGB 0xEF10EF - Magenta
            when "00110" => symbols <= "1011110000" & "1011110000" & "0111110000"; -- RGB 0x10EFEF - Yellow
            when "00111" => symbols <= "1011110000" & "1011110000" & "1011110000"; -- RGB 0xEFEFEF - White
            ---------------------------------------------------------------
            -- control symbols from 5.4.2 - part of the DVI-D standard
            ---------------------------------------------------------------
            when "01000" => symbols <= "1101010100" & "1101010100" & "1101010100"; -- CTL periods
            when "01001" => symbols <= "0010101011" & "1101010100" & "1101010100"; -- Hsync
            when "01010" => symbols <= "0101010100" & "1101010100" & "1101010100"; -- vSync
            when "01011" => symbols <= "1010101011" & "1101010100" & "1101010100"; -- vSync+hSync
            ---------------------------------------------------------------
            -- Symbols to signal the start of a HDMI feature 
            ---------------------------------------------------------------
            when "01100" => symbols <= "0101010100" & "0010101011" & "0010101011"; -- DataIslandPeamble, with VSYNC - 5.2.1.1
            when "01101" => symbols <= "0101100011" & "0100110011" & "0100110011"; -- DataIslandGuardBand, with VSYNC - 5.2.3.3	
            when "01110" => symbols <= "1101010100" & "0010101011" & "1101010100"; -- VideoPramble 5.2.1.1
            when "01111" => symbols <= "1011001100" & "0100110011" & "1011001100"; -- VideoGuardBand 5.2.2.1

            ---------------------------------------------------------------
            -- From TERC4 codes in 5.4.3, and data data layout from 5.2.3.1
            --
            -- First nibble  is used for the nFirstWordOfPacket (MSB) Header Bit, VSYNC, HSYNC (LSB).
            -- The packet is sent where VSYNC = '1' and HSYNC = '0', so we are left with 4 options            
            -- Second nibble is used for the odd bits the four data sub-packets
            -- Third nibble  is used for the even bits the four data sub-packets
            --
            -- These can be used to contruct a data island with any header
            -- and any data in subpacket 0, but all other subpackets 
            -- must be 0s.
            ---------------------------------------------------------------
            when "10000" => symbols <= "1011100100" & "1010011100" & "1010011100"; -- 0010 0000 0000, TERC4 coded
            when "10001" => symbols <= "1011100100" & "1010011100" & "1001100011"; -- 0010 0000 0001, TERC4 coded
            when "10010" => symbols <= "1011100100" & "1001100011" & "1010011100"; -- 0010 0000 0000, TERC4 coded
            when "10011" => symbols <= "1011100100" & "1001100011" & "1001100011"; -- 0010 0001 0001, TERC4 coded
            when "10100" => symbols <= "0110001110" & "1010011100" & "1010011100"; -- 0110 0000 0000, TERC4 coded
            when "10101" => symbols <= "0110001110" & "1010011100" & "1001100011"; -- 0110 0000 0001, TERC4 coded
            when "10110" => symbols <= "0110001110" & "1001100011" & "1010011100"; -- 0110 0001 0000, TERC4 coded
            when "10111" => symbols <= "0110001110" & "1001100011" & "1001100011"; -- 0110 0001 0001, TERC4 coded
            when "11000" => symbols <= "0110011100" & "1010011100" & "1010011100"; -- 1010 0000 0000, TERC4 coded
            when "11001" => symbols <= "0110011100" & "1010011100" & "1001100011"; -- 1010 0000 0001, TERC4 coded
            when "11010" => symbols <= "0110011100" & "1001100011" & "1010011100"; -- 1010 0001 0000, TERC4 coded
            when "11011" => symbols <= "0110011100" & "1001100011" & "1001100011"; -- 1010 0001 0001, TERC4 coded
            when "11100" => symbols <= "0101100011" & "1010011100" & "1010011100"; -- 1110 0000 0000, TERC4 coded
            when "11101" => symbols <= "0101100011" & "1010011100" & "1001100011"; -- 1110 0000 0001, TERC4 coded
            when "11110" => symbols <= "0101100011" & "1001100011" & "1010011100"; -- 1110 0001 0000, TERC4 coded
            when "11111" => symbols <= "0101100011" & "1001100011" & "1001100011"; -- 1110 0001 0001, TERC4 coded

            when others => symbols <= (others => '0');
         end case;
   
         if blank = '0' then
            -- Are we being asked to send video data? If so we need to send a peramble
            if last_blank = '1' then
               symbol_queue(10) <= "00" & blue & green & red;
               symbol_queue(9) <= "01111";  -- Video Guard Band
               symbol_queue(8) <= "01111"; 
               symbol_queue(7) <= "01110";  -- Video Preamble
               symbol_queue(6) <= "01110";
               symbol_queue(5) <= "01110";
               symbol_queue(4) <= "01110";
               symbol_queue(3) <= "01110";
               symbol_queue(2) <= "01110";
               symbol_queue(1) <= "01110";
               symbol_queue(0) <= "01110";
            else
               symbol_queue(0 to 9) <= symbol_queue(1 to 10);
               symbol_queue(10) <= "00" & blue & green & red;
            end if;
         else
           -- Either end the data packet or just send 
           -- the syncs using CTL frames
           case data_island_index is
               when "000000" => symbol_queue(10) <= "01100"; -- Data island preamble
               when "000001" => symbol_queue(10) <= "01100"; -- Data island preamble
               when "000010" => symbol_queue(10) <= "01100"; -- Data island preamble
               when "000011" => symbol_queue(10) <= "01100"; -- Data island preamble
               when "000100" => symbol_queue(10) <= "01100"; -- Data island preamble
               when "000101" => symbol_queue(10) <= "01100"; -- Data island preamble
               when "000110" => symbol_queue(10) <= "01100"; -- Data island preamble
               when "000111" => symbol_queue(10) <= "01100"; -- Data island preamble
               when "001000" => symbol_queue(10) <= "01101"; -- Data island Guard Band
               when "001001" => symbol_queue(10) <= "01101"; -- Data island Guard Band


               -------------------------
               -- For a YCC mode AVI Infoframe Data Island
               -------------------------
                  -- Data Island (0-7)
               when "001010" => symbol_queue(10) <= "10011"; -- First word 
               when "001011" => symbol_queue(10) <= "11111";
               when "001100" => symbol_queue(10) <= "11001";
               when "001101" => symbol_queue(10) <= "11000";
               when "001110" => symbol_queue(10) <= "11000";
               when "001111" => symbol_queue(10) <= "11000";
               when "010000" => symbol_queue(10) <= "11000";
               when "010001" => symbol_queue(10) <= "11110";
                  -- Data Island (8-15)
               when "010010" => symbol_queue(10) <= "11000";
               when "010011" => symbol_queue(10) <= "11100";
               when "010100" => symbol_queue(10) <= "11000";
               when "010101" => symbol_queue(10) <= "11000";
               when "010110" => symbol_queue(10) <= "11000";
               when "010111" => symbol_queue(10) <= "11000";
               when "011000" => symbol_queue(10) <= "11000";
               when "011001" => symbol_queue(10) <= "11000";
                 -- Data Island (16-23)
               when "011010" => symbol_queue(10) <= "11100";
               when "011011" => symbol_queue(10) <= "11000";
               when "011100" => symbol_queue(10) <= "11100";
               when "011101" => symbol_queue(10) <= "11100";
               when "011110" => symbol_queue(10) <= "11000";
               when "011111" => symbol_queue(10) <= "11000";
               when "100000" => symbol_queue(10) <= "11000";
               when "100001" => symbol_queue(10) <= "11000";
                  -- Data Island (24-31)
               when "100010" => symbol_queue(10) <= "11000";
               when "100011" => symbol_queue(10) <= "11000";
               when "100100" => symbol_queue(10) <= "11100";
               when "100101" => symbol_queue(10) <= "11000";
               when "100110" => symbol_queue(10) <= "11010";
               when "100111" => symbol_queue(10) <= "11100";
               when "101000" => symbol_queue(10) <= "11111";
               when "101001" => symbol_queue(10) <= "11110";

               -------------------------
               -- For a NULL Data Island
               -------------------------
               -- Data Island (0-7)
--               when "001010" => symbol_queue(10) <= "10000"; -- First word 
--               when "001011" => symbol_queue(10) <= "11000";
--               when "001100" => symbol_queue(10) <= "11000";
--               when "001101" => symbol_queue(10) <= "11000";
--               when "001110" => symbol_queue(10) <= "11000";
--               when "001111" => symbol_queue(10) <= "11000";
--               when "010000" => symbol_queue(10) <= "11000";
--               when "010001" => symbol_queue(10) <= "11000";
                  -- Data Island (8-15)
--               when "010010" => symbol_queue(10) <= "11000";
--               when "010011" => symbol_queue(10) <= "11000";
--               when "010100" => symbol_queue(10) <= "11000";
--               when "010101" => symbol_queue(10) <= "11000";
--               when "010110" => symbol_queue(10) <= "11000";
--               when "010111" => symbol_queue(10) <= "11000";
--               when "011000" => symbol_queue(10) <= "11000";
--               when "011001" => symbol_queue(10) <= "11000";
                 -- Data Island (16-23)
--               when "011010" => symbol_queue(10) <= "11000";
--               when "011011" => symbol_queue(10) <= "11000";
--               when "011100" => symbol_queue(10) <= "11000";
--               when "011101" => symbol_queue(10) <= "11000";
--               when "011110" => symbol_queue(10) <= "11000";
--               when "011111" => symbol_queue(10) <= "11000";
--               when "100000" => symbol_queue(10) <= "11000";
--               when "100001" => symbol_queue(10) <= "11000";
                  -- Data Island (24-31)
--               when "100010" => symbol_queue(10) <= "11000";
--               when "100011" => symbol_queue(10) <= "11000";
--               when "100100" => symbol_queue(10) <= "11000";
--               when "100101" => symbol_queue(10) <= "11000";
--               when "100110" => symbol_queue(10) <= "11000";
--               when "100111" => symbol_queue(10) <= "11000";
--               when "101000" => symbol_queue(10) <= "11000";
--               when "101001" => symbol_queue(10) <= "11000";

                  -- Trailing guard band
               when "101010" => symbol_queue(10) <= "01101"; -- Data island Guard Band
               when "101011" => symbol_queue(10) <= "01101"; -- Data island Guard Band
                  -- There has to be four CTL symbols before the next block of video our data,
                  -- But that won't be a problem for us, we will have the rest of the vertical 
                  -- Blanking interval
               when others   => symbol_queue(10) <= "010" & Vsync & Hsync;
           end case;
           
           symbol_queue(0 to 9) <= symbol_queue(1 to 10);
         end if;

         if data_island_index /= "111111" then
            data_island_index  <= data_island_index  + 1;
         end if;
         
         -- If we see the rising edge of vsync we need to send 
         -- a data island the next time we see the hsync signal
         -- drop.
         if last_vsync = '0' and vsync = '1' then
            data_island_armed <= '1';
         end if;

         if data_island_armed = '1' and last_hsync = '1' and hsync = '0' then
            data_island_index <= (others => '0');
            data_island_armed <= '0';
         end if;
         
         last_blank <= blank;
         last_hsync <= hsync;
         last_vsync <= vsync;
      end if;
   end process;

end Behavioral;

serializers.vhd

----------------------------------------------------------------------------------
-- Engineer: Mike Field <hamster@snap.net.nz>
-- 
-- Convert 3x 10-bit symbols to three serial channels and the clock channel.
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
library UNISIM;
use UNISIM.VComponents.all;

entity serializers is
    Port ( clk : in  STD_LOGIC;
           c0 : in  STD_LOGIC_VECTOR (9 downto 0);
           c1 : in  STD_LOGIC_VECTOR (9 downto 0);
           c2 : in  STD_LOGIC_VECTOR (9 downto 0);
           hdmi_p : out  STD_LOGIC_VECTOR (3 downto 0);
           hdmi_n : out  STD_LOGIC_VECTOR (3 downto 0));
end serializers;

architecture Behavioral of serializers is
   -- For holding the outward bound TMDS symbols in the slow and fast domain
   signal c0_high_speed   : std_logic_vector(9 downto 0) := (others => '0');
   signal c1_high_speed   : std_logic_vector(9 downto 0) := (others => '0');
   signal c2_high_speed   : std_logic_vector(9 downto 0) := (others => '0');   
   signal clk_high_speed  : std_logic_vector(9 downto 0) := (others => '0');
   signal c2_output_bits  : std_logic_vector(1 downto 0) := "00";
   signal c1_output_bits  : std_logic_vector(1 downto 0) := "00";
   signal c0_output_bits  : std_logic_vector(1 downto 0) := "00";
   signal clk_output_bits : std_logic_vector(1 downto 0) := "00";

   -- Controlling the transfers into the high speed domain
   signal latch_high_speed : std_logic_vector(4 downto 0) := "00001";
   
   -- From the DDR outputs to the output buffers
   signal c0_serial, c1_serial, c2_serial, clk_serial : std_logic;

   -- For generating the x5 clocks
   signal clk_x5,  clk_x5_unbuffered  : std_logic;
   signal clk_feedback    : std_logic;

   -- To glue the HSYNC and VSYNC into the control character.
   signal syncs           : std_logic_vector(1 downto 0);
begin

process(clk_x5)
   begin
      ---------------------------------------------------------------
      -- Now take the 10-bit words and take it into the high-speed
      -- clock domain once every five cycles. 
      -- 
      -- Then send out two bits every clock cycle using DDR output
      -- registers.
      ---------------------------------------------------------------   
      if rising_edge(clk_x5) then
         c0_output_bits  <= c0_high_speed(1 downto 0);
         c1_output_bits  <= c1_high_speed(1 downto 0);
         c2_output_bits  <= c2_high_speed(1 downto 0);
         clk_output_bits <= clk_high_speed(1 downto 0);

         if latch_high_speed(0) = '1' then
            c0_high_speed   <= c0;
            c1_high_speed   <= c1;
            c2_high_speed   <= c2;
            clk_high_speed  <= "0000011111";
         else
            c0_high_speed   <= "00" & c0_high_speed(9 downto 2);
            c1_high_speed   <= "00" & c1_high_speed(9 downto 2);
            c2_high_speed   <= "00" & c2_high_speed(9 downto 2);
            clk_high_speed  <= "00" & clk_high_speed(9 downto 2);
         end if;
         latch_high_speed <= latch_high_speed(0) & latch_high_speed(4 downto 1);
      end if;
   end process;

   ------------------------------------------------------------------
   -- Convert the TMDS codes into a serial stream, two bits at a time
   ------------------------------------------------------------------
c0_to_serial: ODDR2
   generic map(DDR_ALIGNMENT => "C0", INIT => '0', SRTYPE => "ASYNC") 
   port map (C0 => clk_x5,  C1 => not clk_x5, CE => '1', R => '0', S => '0',
             D0 => C0_output_bits(0), D1 => C0_output_bits(1), Q => c0_serial);
OBUFDS_c0  : OBUFDS port map ( O  => hdmi_p(0), OB => hdmi_n(0), I => c0_serial);

c1_to_serial: ODDR2
   generic map(DDR_ALIGNMENT => "C0", INIT => '0', SRTYPE => "ASYNC") 
   port map (C0 => clk_x5,  C1 => not clk_x5, CE => '1', R => '0', S => '0',
             D0 => C1_output_bits(0), D1 => C1_output_bits(1), Q  => c1_serial);
OBUFDS_c1  : OBUFDS port map ( O  => hdmi_p(1), OB => hdmi_n(1), I => c1_serial);
   
c2_to_serial: ODDR2
   generic map(DDR_ALIGNMENT => "C0", INIT => '0', SRTYPE => "ASYNC") 
   port map (C0 => clk_x5,  C1 => not clk_x5, CE => '1', R => '0', S => '0',
             D0 => C2_output_bits(0), D1 => C2_output_bits(1), Q  => c2_serial);
OBUFDS_c2  : OBUFDS port map ( O  => hdmi_p(2), OB => hdmi_n(2), I => c2_serial);

clk_to_serial: ODDR2
   generic map(DDR_ALIGNMENT => "C0", INIT => '0', SRTYPE => "ASYNC") 
   port map (C0 => clk_x5,  C1 => not clk_x5, CE => '1', R => '0', S => '0',
             D0 => Clk_output_bits(0), D1 => Clk_output_bits(1), Q  => clk_serial);
OBUFDS_clk : OBUFDS port map ( O  => hdmi_p(3), OB => hdmi_n(3), I => clk_serial);
   
   ------------------------------------------------------------------
   -- Use a PLL to generate a x5 clock, which is used to drive 
   -- the DDR registers.This allows 10 bits to be sent for every 
   -- pixel clock
   ------------------------------------------------------------------
PLL_BASE_inst : PLL_BASE
   generic map (
      CLKFBOUT_MULT => 10,                  
      CLKOUT0_DIVIDE => 2,       CLKOUT0_PHASE => 0.0,   -- Output 5x original frequency
      CLK_FEEDBACK => "CLKFBOUT",
      CLKIN_PERIOD => 13.33,
      DIVCLK_DIVIDE => 1
   )
      port map (
      CLKFBOUT => clk_feedback, 
      CLKOUT0  => clk_x5_unbuffered,
      CLKFBIN  => clk_feedback,    
      CLKIN    => clk, 
      RST      => '0'
   );

BUFG_pclkx5  : BUFG port map ( I => clk_x5_unbuffered,  O => clk_x5);

end Behavioral;

miniSpartan6.ucf

#Created by Constraints Editor (xc6slx25-ftg256-3) - 2015/01/02
NET "clk50" PERIOD = 20 ns | LOC = "K3"; # IO_L42P_GCLK25_TRDY2_M3UDM
#NET "clk32" PERIOD = 31.25 ns | LOC = "J4"; # IO_L42N_GCLK24_M3LDM_3
NET "LEDS<0>" LOC="P11";
NET "LEDS<1>" LOC="N9";
NET "LEDS<2>" LOC="M9";
NET "LEDS<3>" LOC="P9";
NET "LEDS<4>" LOC="T8";
NET "LEDS<5>" LOC="N8";
NET "LEDS<6>" LOC="P8";
NET "LEDS<7>" LOC="P7";

NET "hdmi_out_p<0>" LOC="C13" | IOSTANDARD="TMDS_33"; # IO_L63P_SCP7_0
NET "hdmi_out_n<0>" LOC="A13" | IOSTANDARD="TMDS_33"; # IO_L63N_SCP6_0
NET "hdmi_out_p<1>" LOC="B12" | IOSTANDARD="TMDS_33"; # IO_L62P_0
NET "hdmi_out_n<1>" LOC="A12" | IOSTANDARD="TMDS_33"; # IO_L62N_VREF_0
NET "hdmi_out_p<2>" LOC="C11" | IOSTANDARD="TMDS_33"; # IO_L39P_0
NET "hdmi_out_n<2>" LOC="A11" | IOSTANDARD="TMDS_33"; # IO_L39N_0
NET "hdmi_out_p<3>" LOC="B14" | IOSTANDARD="TMDS_33"; # IO_L65P_SCP3_0
NET "hdmi_out_n<3>" LOC="A14" | IOSTANDARD="TMDS_33"; # IO_L65N_SCP2_0

Personal tools