Zedboard HDMI

From Hamsterworks Wiki!

Jump to: navigation, search

This FPGA Project was started in January 2013.

See Zedboard_HDMI_v2 for an improved version, with working colour conversion

It is always fun to play with new interface, and the Zedboard has a ADV7511 HDMI transmitter on it. Rather than using the reference design available from the vendor I wanted to get HDMI going the hardware. I'm aiming for 1280x720 video mode.

The pins connected on the Zedboard are actually D8 through D23 (verified on schematic). The forces the use of the YCrCb colout space.

Zed hdmi screen.jpg

Zed hdmi fine.jpg

It isn't 100% yet, but getting there!

Contents

Source files

The clean project is File:Zedboard hdmi.zip. If you build the project yourself, be sure to set "pack outputs into IOB".

zedboard_hdmi.vhd

----------------------------------------------------------------------------------
-- Engineer: Mike Field <hamster@snap.net.nz>
-- 
-- Create Date:    06:01:06 01/23/2013 
--
-- Description: 
--      Drive the ADV7511 HDMI encoder directly from the PL fabric
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

Library UNISIM;
use UNISIM.vcomponents.all;

entity zedboard_hdmi is
    Port ( clk_100       : in  STD_LOGIC;
           hdmi_clk      : out  STD_LOGIC;
           hdmi_hsync    : out  STD_LOGIC;
           hdmi_vsync    : out  STD_LOGIC;
           hdmi_d        : out  STD_LOGIC_VECTOR (15 downto 0);
           hdmi_de       : out  STD_LOGIC;
           hdmi_int      : in   STD_LOGIC;
           hdmi_scl      : out  STD_LOGIC;
           hdmi_sda      : inout  STD_LOGIC);
end zedboard_hdmi;

architecture Behavioral of zedboard_hdmi is
   COMPONENT i2c_sender
   PORT(
      clk    : IN std_logic;
      resend : IN std_logic;    
      siod   : INOUT std_logic;      
      sioc   : OUT std_logic
   );
   END COMPONENT;

   signal blanking       : std_logic := '0';
   signal hsync          : std_logic := '0';
   signal vsync          : std_logic := '0';
   signal edge           : std_logic := '0';
   signal Y              : STD_LOGIC_VECTOR (11 downto 0);
   signal Cr             : STD_LOGIC_VECTOR (11 downto 0);
   signal Cb             : STD_LOGIC_VECTOR (11 downto 0);
   signal hdmi_clk_bits  : STD_LOGIC_VECTOR (1 downto 0);
   
   signal   hcounter    : unsigned(10 downto 0) := (others => '0');
   signal   vcounter    : unsigned(10 downto 0) := (others => '0');
   
   constant ZERO        : unsigned(10 downto 0) := (others => '0');
   signal   hVisible    : unsigned(10 downto 0);
   signal   hStartSync  : unsigned(10 downto 0);
   signal   hEndSync    : unsigned(10 downto 0);
   signal   hMax        : unsigned(10 downto 0);
   signal   hSyncActive : std_logic := '1';
   
   signal   vVisible    : unsigned(10 downto 0);
   signal   vStartSync  : unsigned(10 downto 0);
   signal   vEndSync    : unsigned(10 downto 0);
   signal   vMax        : unsigned(10 downto 0);
   signal   vSyncActive : std_logic := '1';
   
   signal clk_vgax2   : std_logic;
   signal clkfb       : std_logic;
   signal clk         : std_logic;
   
begin   
   -- Set the video mode to 1280x720x60Hz (75MHz pixel clock needed)
   hVisible    <= ZERO + 1280;
   hStartSync  <= ZERO + 1280+72;
   hEndSync    <= ZERO + 1280+72+80;
   hMax        <= ZERO + 1280+72+80+216-1;
   vSyncActive <= '1';

   vVisible    <= ZERO + 720;
   vStartSync  <= ZERO + 720+3;
   vEndSync    <= ZERO + 720+3+5;
   vMax        <= ZERO + 720+3+5+22-1;
   hSyncActive <= '1';

   Y   <= std_logic_vector(hcounter(9 downto 0)) & "00";
   Cr  <= (std_logic_vector(hcounter(9 downto 0)+vcounter(9 downto 0))) & "00";
   Cb  <= std_logic_vector(vcounter(9 downto 0)) & "00";
      
vga_clkx2_process: process (clk_vgax2)
   begin
      if rising_edge(clk_VGAx2) then 
         ---------------------------------------------------------------------------
         -- signal generation for the HDMI encoder
         --
         -- Transfer on rising edge  of clock Y
         --          on falling edge of clock Either Cr or Cb 
         --
         -- Because I am a wimp I don't use any DDR except for generating the DDR clk.
         ----------------------------------------------------------------------------
         if edge =  '0' then
            edge <= '1';
            hdmi_clk_bits <= "00";
            if blanking = '1' then 
               hdmi_d <= (others => '0');
               hdmi_de <= '0';
            else
               hdmi_d <= Y & "0000";
               hdmi_de <= '1';
            end if;
         else
            edge <= '0';
            hdmi_clk_bits <= "11";
            if blanking = '1' then 
               hdmi_d <= (others => '0');
               hdmi_de <= '0';
            else
               if hcounter(0) = '0' then 
                  hdmi_d <= Cr & "0000";
               else
                  hdmi_d <= Cb & "0000";
               end if;
               hdmi_de <= '1';
            end if;
         end if;
         hdmi_hsync <= hsync;
         hdmi_vsync <= vsync;


         ------------------------------------------------------------------------
         -- VGA Signal Generation
         -- We only update when the second clock edge has been sent 
         --- to the HDMI encoder
         ------------------------------------------------------------------------
         if edge =  '1' then
            if vcounter >= vVisible then 
               blanking <= '1';
            elsif hcounter >= hVisible then 
               blanking <= '1';
            else
               blanking <= '0';
            end if;
            
            -- Generate the sync Pulses
            if vcounter = vStartSync then 
               vSync <= vSyncActive;
            elsif vCounter = vEndSync then
               vSync <= not(vSyncActive);
            end if;

            if hcounter = hStartSync then 
               hSync <= hSyncActive;
            elsif hCounter = hEndSync then
               hSync <= not(hSyncActive);
            end if;

            -- Advance the position counters
            IF hCounter = hMax  THEN
               -- starting a new line
               hCounter <= (others => '0');
               IF vCounter = vMax THEN
                  vCounter <= (others => '0');
               ELSE
                  vCounter <= vCounter + 1;
               END IF;
            ELSE
               hCounter <= hCounter + 1;
            END IF;
         end if;
      end if;
   end process;
   

   ODDR_inst : ODDR
   generic map(
      DDR_CLK_EDGE => "OPPOSITE_EDGE", INIT => '0',SRTYPE => "SYNC") 
   port map (
      Q => hdmi_clk, 
      C => clk_VGAx2,
      D1 => hdmi_clk_bits(0),
      D2 => hdmi_clk_bits(1),
      CE => '1', R => '0', S => '0'
   );
   
   Inst_i2c_sender: i2c_sender PORT MAP(
      clk => clk,
      resend => '0',
      sioc => hdmi_scl,
      siod => hdmi_sda
   );
   
   -- Generate a 130MHz and 100Mhz clock from the input.
   PLLE2_BASE_inst : PLLE2_BASE
   generic map (
      BANDWIDTH => "OPTIMIZED",  -- OPTIMIZED, HIGH, LOW
      CLKFBOUT_MULT  => 9,       -- Multiply value for all CLKOUT, (2-64)
      CLKFBOUT_PHASE => 0.0,     -- Phase offset in degrees of CLKFB, (-360.000-360.000).
      CLKIN1_PERIOD  => 10.0,    -- Input clock period in ns to ps resolution (i.e. 33.333 is 30 MHz).
      -- CLKOUT0_DIVIDE - CLKOUT5_DIVIDE: Divide amount for each CLKOUT (1-128)
      CLKOUT0_DIVIDE => 9,
      CLKOUT1_DIVIDE => 6,
      CLKOUT2_DIVIDE => 1,
      CLKOUT3_DIVIDE => 1,
      CLKOUT4_DIVIDE => 1,
      CLKOUT5_DIVIDE => 1,
      -- CLKOUT0_DUTY_CYCLE - CLKOUT5_DUTY_CYCLE: Duty cycle for each CLKOUT (0.001-0.999).
      CLKOUT0_DUTY_CYCLE => 0.5,
      CLKOUT1_DUTY_CYCLE => 0.5,
      CLKOUT2_DUTY_CYCLE => 0.5,
      CLKOUT3_DUTY_CYCLE => 0.5,
      CLKOUT4_DUTY_CYCLE => 0.5,
      CLKOUT5_DUTY_CYCLE => 0.5,
      -- CLKOUT0_PHASE - CLKOUT5_PHASE: Phase offset for each CLKOUT (-360.000-360.000).
      CLKOUT0_PHASE => 0.0,
      CLKOUT1_PHASE => 0.0,
      CLKOUT2_PHASE => 0.0,
      CLKOUT3_PHASE => 0.0,
      CLKOUT4_PHASE => 0.0,
      CLKOUT5_PHASE => 0.0,
      DIVCLK_DIVIDE => 1,        -- Master division value, (1-56)
      REF_JITTER1 => 0.0,        -- Reference input jitter in UI, (0.000-0.999).
      STARTUP_WAIT => "FALSE"    -- Delay DONE until PLL Locks, ("TRUE"/"FALSE")
   )
   port map (
      -- Clock Outputs: 1-bit (each) output: User configurable clock outputs
      CLKOUT0  => clk,
      CLKOUT1  => clk_VGAx2,
      CLKOUT2  => open,
      CLKOUT3  => open,
      CLKOUT4  => open,
      CLKOUT5  => open,
      CLKFBOUT => clkfb,   -- 1-bit output: Feedback clock
      LOCKED   => open,    -- 1-bit output: LOCK
      CLKIN1   => clk_100, -- 1-bit input: Input clock
      PWRDWN   => '0',     -- 1-bit input: Power-down
      RST      => '0',     -- 1-bit input: Reset
      CLKFBIN  => clkfb    -- 1-bit input: Feedback clock
   );
end Behavioral;

zedboard_.ucf

NET CLK_100       LOC = Y9   |IOSTANDARD=LVCMOS33;  # "GCLK"
NET "clk_100" TNM_NET = clk_100;
TIMESPEC TS_clk_100 = PERIOD "clk_100" 10 ns HIGH 50%;

NET HDMI_CLK        LOC = W18  | IOSTANDARD=LVCMOS33;  # "HD-CLK"
NET HDMI_D<0>       LOC = Y13  | IOSTANDARD=LVCMOS33;  # "HD-D0"
NET HDMI_D<1>       LOC = AA13 | IOSTANDARD=LVCMOS33;  # "HD-D1"
NET HDMI_D<2>       LOC = AA14 | IOSTANDARD=LVCMOS33;  # "HD-D2"
NET HDMI_D<3>       LOC = Y14  | IOSTANDARD=LVCMOS33;  # "HD-D3"
NET HDMI_D<4>       LOC = AB15 | IOSTANDARD=LVCMOS33;  # "HD-D4"
NET HDMI_D<5>       LOC = AB16 | IOSTANDARD=LVCMOS33;  # "HD-D5"
NET HDMI_D<6>       LOC = AA16 | IOSTANDARD=LVCMOS33;  # "HD-D6"
NET HDMI_D<7>       LOC = AB17 | IOSTANDARD=LVCMOS33;  # "HD-D7"
NET HDMI_D<8>       LOC = AA17 | IOSTANDARD=LVCMOS33;  # "HD-D8"
NET HDMI_D<9>       LOC = Y15  | IOSTANDARD=LVCMOS33;  # "HD-D9"
NET HDMI_D<10>      LOC = W13  | IOSTANDARD=LVCMOS33;  # "HD-D10"
NET HDMI_D<11>      LOC = W15  | IOSTANDARD=LVCMOS33;  # "HD-D11"
NET HDMI_D<12>      LOC = V15  | IOSTANDARD=LVCMOS33;  # "HD-D12"
NET HDMI_D<13>      LOC = U17  | IOSTANDARD=LVCMOS33;  # "HD-D13"
NET HDMI_D<14>      LOC = V14  | IOSTANDARD=LVCMOS33;  # "HD-D14"
NET HDMI_D<15>      LOC = V13  | IOSTANDARD=LVCMOS33;  # "HD-D15"
NET HDMI_DE         LOC = U16  | IOSTANDARD=LVCMOS33;  # "HD-DE"
NET HDMI_HSYNC      LOC = V17  | IOSTANDARD=LVCMOS33;  # "HD-HSYNC"
NET HDMI_VSYNC      LOC = W17  | IOSTANDARD=LVCMOS33;  # "HD-VSYNC"
NET HDMI_INT        LOC = W16  | IOSTANDARD=LVCMOS33;  # "HD-INT"
NET HDMI_SCL        LOC = AA18 | IOSTANDARD=LVCMOS33;  # "HD-SCL"
NET HDMI_SDA        LOC = Y16  | IOSTANDARD=LVCMOS33 | PULLUP;  # "HD-SDA"
#NET HDMI_SPDIF      LOC = U15  | IOSTANDARD=LVCMOS33;  # "HD-SPDIF"
#NET HDMI_SPDIFO     LOC = Y18  | IOSTANDARD=LVCMOS33;  # "HD-SPDIFO"

i2c_sender.vhd

----------------------------------------------------------------------------------
-- Engineer: <mfield@concepts.co.nz
-- 
-- Description: Send register writes over an I2C-like interface
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity i2c_sender is
    Port ( clk    : in    STD_LOGIC;    
           resend : in    STD_LOGIC;
           sioc   : out   STD_LOGIC;
           siod   : inout STD_LOGIC
    );
end i2c_sender;

architecture Behavioral of i2c_sender is
   signal   divider           : unsigned(7 downto 0)  := (others => '0'); 
    -- this value gives nearly 200ms cycles before the first register is written
   signal   initial_pause     : unsigned(22 downto 0) := (others => '0');
   signal   finished          : std_logic := '0';
   signal   address           : std_logic_vector(7 downto 0)  := (others => '0');
   signal   clk_first_quarter : std_logic_vector(28 downto 0) := (others => '1');
   signal   clk_last_quarter  : std_logic_vector(28 downto 0) := (others => '1');
   signal   busy_sr           : std_logic_vector(28 downto 0) := (others => '1');
   signal   data_sr           : std_logic_vector(28 downto 0) := (others => '1');
   signal   tristate_sr       : std_logic_vector(28 downto 0) := (others => '0');
   signal   reg_value         : std_logic_vector(15 downto 0)  := (others => '0');
   constant i2c_wr_addr       : std_logic_vector(7 downto 0)  := x"72";
begin

registers: process(clk)
   begin
      if rising_edge(clk) then
         case address is
            when x"00" => reg_value  <= x"4100"; -- Powerup please
            when x"01" => reg_value  <= x"9803"; -- Must be set to this
            when x"02" => reg_value  <= x"9AE0"; -- Must be set to this
            when x"03" => reg_value  <= x"9C30"; -- Must be set to this
            when x"04" => reg_value  <= x"9D61"; -- Must be set to this
            when x"05" => reg_value  <= x"A2A4"; -- Must be set to this
            when x"06" => reg_value  <= x"A3A4"; -- Must be set to this
            when x"07" => reg_value  <= x"E0D0"; -- Must be set to this
            when x"08" => reg_value  <= x"5512"; -- Must be set to this
            when x"09" => reg_value  <= x"F900"; -- Must be set to this
            when x"0a" => reg_value  <= x"9d01"; -- pixel divider
            when x"0b" => reg_value  <= x"1506"; -- YCbCr 422, DDR, External sync
            when x"0c" => reg_value  <= x"4810"; -- Right aligned data (D23 downto 8)
            when x"0d" => reg_value  <= x"1636"; -- 8 bit style 2, 1st half on rising edge - RGB output
            when x"0e" => reg_value  <= x"1700"; -- output aspect ratio 16:9, external DE 
            when x"0f" => reg_value  <= x"1800"; -- CSC off
            when x"10" => reg_value  <= x"AF00"; -- DVI mode
            when x"11" => reg_value  <= x"4c04"; -- Deep colour (HDMI only?)
            when x"12" => reg_value  <= x"4000"; -- Turn off additional data packets
            when others => reg_value <= x"ffff";
         end case;
      end if;
   end process;

i2c_tristate: process(data_sr, tristate_sr)
   begin
      if tristate_sr(tristate_sr'length-1) = '0' then
         siod <= data_sr(data_sr'length-1);
      else
         siod <= 'Z';
      end if;
   end process;
   
   with divider(divider'length-1 downto divider'length-2) 
      select sioc <= clk_first_quarter(clk_first_quarter'length -1) when "00",
                     clk_last_quarter(clk_last_quarter'length -1)   when "11",
                     '1' when others;
                     
i2c_send:   process(clk)
   begin
      if rising_edge(clk) then
         if resend = '1' then 
            address           <= (others => '0');
            clk_first_quarter <= (others => '1');
            clk_last_quarter  <= (others => '1');
            busy_sr           <= (others => '0');
            divider           <= (others => '0');
            initial_pause     <= (others => '0');
            finished <= '0';
         end if;

         if busy_sr(busy_sr'length-1) = '0' then
            if initial_pause(initial_pause'length-1) = '0' then
               initial_pause <= initial_pause+1;
            elsif finished = '0' then
               if divider = "11111111" then
                  divider <= (others =>'0');
                  if reg_value(15 downto 8) = "11111111" then
                     finished <= '1';
                  else
                     -- move the new data into the shift registers
                     clk_first_quarter <= (others => '0'); clk_first_quarter(clk_first_quarter'length-1) <= '1';
                     clk_last_quarter <= (others => '0');  clk_last_quarter(0) <= '1';
                     
                     --             Start    Address        Ack        Register           Ack          Value         Ack    Stop
                     tristate_sr <= "0" & "00000000"  & "1" & "00000000"             & "1" & "00000000"             & "1"  & "0";
                     data_sr     <= "0" & i2c_wr_addr & "1" & reg_value(15 downto 8) & "1" & reg_value( 7 downto 0) & "1"  & "0";
                     busy_sr     <= (others => '1');
                     address     <= std_logic_vector(unsigned(address)+1);
                  end if;
               else
                  divider <= divider+1; 
               end if;
            end if;
         else
            if divider = "11111111" then   -- divide clkin by 255 for I2C
               tristate_sr       <= tristate_sr(tristate_sr'length-2 downto 0) & '0';
               busy_sr           <= busy_sr(busy_sr'length-2 downto 0) & '0';
               data_sr           <= data_sr(data_sr'length-2 downto 0) & '1';
               clk_first_quarter <= clk_first_quarter(clk_first_quarter'length-2 downto 0) & '1';
               clk_last_quarter  <= clk_last_quarter(clk_first_quarter'length-2 downto 0) & '1';
               divider           <= (others => '0');
            else
               divider <= divider+1;
            end if;
         end if;
      end if;
   end process;
end Behavioral;

Personal tools