Dvid test
From Hamsterworks Wiki!
There is no denying it, Analogue VGA is on the way out. Which is a shame really, because it is so easy to get an FPGA to drive a few resistors and put a picture up on the screen. It's a great 'stretch' project for the FPGA first-timer. But it does have a downside. Each 'bit' of colour resolution required 1 I/O pin and 2 resistors, and then there is an additional couple of sync pins to. Most FPGA boards only have 8 bit VGA - it is like being back in the 80s! Moving to an all digital interface gives you access to full 32 bit colour, and uses less bits and bobs on the PCB, so should be cheaper to manufacture too.
Contents |
What is involved in implementing HDMI or DVI-D?
Underneath the covers it is the same as VGA - you feed in color bits, hsync, vsync and a "blanking" signal into a converter, with exactly the same timing as used for VGA.
Inside the converter each 8 bits of colour gets expanded to 10 bits using a 8b/10b function, and then these bits are sent out at ten times the pixel clock rate (each colour down a TMDS pair), along with the original pixel clock (down its own pair). The hsync and vsync signals are sent during the blanking periods using special codewords that don't normal occur during transmission of pixel data.
HDMI differs from DVI-D as it has extra codewords that can be used to all sending of audio and other data when the display isn't sending pixel data.
HDMI and DVI-D also have a low bandwidth channel that allows them to read a serial ROM in the display, allowing the source to query the display's information and supported display modes. I haven't looked at this in this project.
FPGA resources needed to implement
- A good clock signal that can be used to generate the pixel clock
- Four pairs of I/O pins that support TMDS signalling. I have verified that 720p can work using LVDS_33 signalling over a 1.5M HDMI cable
- A way to send bits at 10x the pixel clock - I used the Digital Clock Manager to generate a 5x clock for driving DDR outputs.
My implementation
Here is my implemention of a DVID source based on the Digital Display Working group's specs that are at available at http://www.ddwg.org/lib/dvi_10.pdf. This has been written without referring to any other implementations - well, other than using the one that was flashed into my board when I received it to check that the output did indeed work.
Unlike using Xilinx's XAPP495 source, this code has no rights issues. Searching for DVI-D "dvi-d vhdl source" and looking through OpenCores didn't give anything similar, so maybe it is the first open implementation on the web!
This is targeted at Magnus Karlsson's awesome homebuilt Pipistrello Spartan 6 board which has an on-PCB HDMI socket. Because DVID is a subset of the HDMI protocol it can be used with either interface HDMI or DIVD devices.
It is not best implementation for the device as it uses DDR outputs rather than the Spartan 6's high speed serialisers, but in doing this it makes it very portable to other vendor's FPGAs.
The use of the DDR outputs requires two clocks that are 5x the pixel clock to drive the ODDR components. For this project it a problem - at the demo runs at 640x480 so the clocks are a sedate 25MHz are 125MHz.
The whole thing is also quite tiny - whole project uses about 164 logic slices.
Source
Constraints used
VCCAUX = 3.3; NET "CLK_50" LOC = "H17" | IOSTANDARD = LVCMOS33 | PERIOD = 50 MHz; NET "TMDS(0)" LOC = "T6" | IOSTANDARD = TMDS_33 ; # Blue NET "TMDSB(0)" LOC = "V6" | IOSTANDARD = TMDS_33 ; NET "TMDS(1)" LOC = "U7" | IOSTANDARD = TMDS_33 ; # Red NET "TMDSB(1)" LOC = "V7" | IOSTANDARD = TMDS_33 ; NET "TMDS(2)" LOC = "U8" | IOSTANDARD = TMDS_33 ; # Green NET "TMDSB(2)" LOC = "V8" | IOSTANDARD = TMDS_33 ; NET "TMDS(3)" LOC = "U5" | IOSTANDARD = TMDS_33 ; # Clock NET "TMDSB(3)" LOC = "V5" | IOSTANDARD = TMDS_33 ;
dvid_test.vhd
----------------------------------------------------------------------------------
-- Engineer: Mike Field <hamster@snap.net.nz>
--
-- Description: dvid_test
-- Top level design for testing my DVI-D interface
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
Library UNISIM;
use UNISIM.vcomponents.all;
entity dvid_test is
Port ( clk_50 : in STD_LOGIC;
tmds : out STD_LOGIC_VECTOR(3 downto 0);
tmdsb : out STD_LOGIC_VECTOR(3 downto 0));
end dvid_test;
architecture Behavioral of dvid_test is
component clocking
port (
-- Clock in ports
CLK_50 : in std_logic;
-- Clock out ports
CLK_DVI : out std_logic;
CLK_DVIn : out std_logic;
CLK_VGA : out std_logic
);
end component;
COMPONENT dvid
PORT(
clk : IN std_logic;
clk_n : IN std_logic;
clk_pixel: IN std_logic;
red_p : IN std_logic_vector(7 downto 0);
green_p : IN std_logic_vector(7 downto 0);
blue_p : IN std_logic_vector(7 downto 0);
blank : IN std_logic;
hsync : IN std_logic;
vsync : IN std_logic;
red_s : OUT std_logic;
green_s : OUT std_logic;
blue_s : OUT std_logic;
clock_s : OUT std_logic
);
END COMPONENT;
COMPONENT vga
generic (
hRez : natural;
hStartSync : natural;
hEndSync : natural;
hMaxCount : natural;
hsyncActive : std_logic;
vRez : natural;
vStartSync : natural;
vEndSync : natural;
vMaxCount : natural;
vsyncActive : std_logic
);
PORT(
pixelClock : IN std_logic;
Red : OUT std_logic_vector(7 downto 0);
Green : OUT std_logic_vector(7 downto 0);
Blue : OUT std_logic_vector(7 downto 0);
hSync : OUT std_logic;
vSync : OUT std_logic;
blank : OUT std_logic
);
END COMPONENT;
signal clk_dvi : std_logic := '0';
signal clk_dvin : std_logic := '0';
signal clk_vga : std_logic := '0';
signal red : std_logic_vector(7 downto 0) := (others => '0');
signal green : std_logic_vector(7 downto 0) := (others => '0');
signal blue : std_logic_vector(7 downto 0) := (others => '0');
signal hsync : std_logic := '0';
signal vsync : std_logic := '0';
signal blank : std_logic := '0';
signal red_s : std_logic;
signal green_s : std_logic;
signal blue_s : std_logic;
signal clock_s : std_logic;
begin
clocking_inst : clocking port map (
CLK_50 => clk_50,
-- Clock out ports
CLK_DVI => clk_dvi, -- for 640x480@60Hz : 125MHZ
CLK_DVIn => clk_dvin, -- for 640x480@60Hz : 125MHZ, 180 degree phase shift
CLK_VGA => clk_vga -- for 640x480@60Hz : 25MHZ
);
Inst_dvid: dvid PORT MAP(
clk => clk_dvi,
clk_n => clk_dvin,
clk_pixel => clk_vga,
red_p => red,
green_p => green,
blue_p => blue,
blank => blank,
hsync => hsync,
vsync => vsync,
-- outputs to TMDS drivers
red_s => red_s,
green_s => green_s,
blue_s => blue_s,
clock_s => clock_s
);
OBUFDS_blue : OBUFDS port map ( O => TMDS(0), OB => TMDSB(0), I => blue_s );
OBUFDS_red : OBUFDS port map ( O => TMDS(1), OB => TMDSB(1), I => green_s );
OBUFDS_green : OBUFDS port map ( O => TMDS(2), OB => TMDSB(2), I => red_s );
OBUFDS_clock : OBUFDS port map ( O => TMDS(3), OB => TMDSB(3), I => clock_s );
-- generic map ( IOSTANDARD => "DEFAULT")
Inst_vga: vga GENERIC MAP (
hRez => 640, hStartSync => 656, hEndSync => 752, hMaxCount => 800, hsyncActive => '0',
vRez => 480, vStartSync => 490, vEndSync => 492, vMaxCount => 525, vsyncActive => '1'
) PORT MAP(
pixelClock => clk_vga,
Red => red,
Green => green,
Blue => blue,
hSync => hSync,
vSync => vSync,
blank => blank
);
end Behavioral;
dvid.vhd
--------------------------------------------------------------------------------
-- Engineer: Mike Field <hamster@snap.net.nz>
-- Description: Converts VGA signals into DVID bitstreams.
--
-- 'clk' and 'clk_n' should be 5x clk_pixel.
--
-- 'blank' should be asserted during the non-display
-- portions of the frame
--------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
Library UNISIM;
use UNISIM.vcomponents.all;
entity dvid is
Port ( clk : in STD_LOGIC;
clk_n : in STD_LOGIC;
clk_pixel : in STD_LOGIC;
red_p : in STD_LOGIC_VECTOR (7 downto 0);
green_p : in STD_LOGIC_VECTOR (7 downto 0);
blue_p : in STD_LOGIC_VECTOR (7 downto 0);
blank : in STD_LOGIC;
hsync : in STD_LOGIC;
vsync : in STD_LOGIC;
red_s : out STD_LOGIC;
green_s : out STD_LOGIC;
blue_s : out STD_LOGIC;
clock_s : out STD_LOGIC);
end dvid;
architecture Behavioral of dvid is
COMPONENT TDMS_encoder
PORT(
clk : IN std_logic;
data : IN std_logic_vector(7 downto 0);
c : IN std_logic_vector(1 downto 0);
blank : IN std_logic;
encoded : OUT std_logic_vector(9 downto 0)
);
END COMPONENT;
signal encoded_red, encoded_green, encoded_blue : std_logic_vector(9 downto 0);
signal latched_red, latched_green, latched_blue : std_logic_vector(9 downto 0) := (others => '0');
signal shift_red, shift_green, shift_blue : std_logic_vector(9 downto 0) := (others => '0');
signal shift_clock : std_logic_vector(9 downto 0) := "0000011111";
constant c_red : std_logic_vector(1 downto 0) := (others => '0');
constant c_green : std_logic_vector(1 downto 0) := (others => '0');
signal c_blue : std_logic_vector(1 downto 0);
begin
c_blue <= vsync & hsync;
TDMS_encoder_red: TDMS_encoder PORT MAP(clk => clk_pixel, data => red_p, c => c_red, blank => blank, encoded => encoded_red);
TDMS_encoder_green: TDMS_encoder PORT MAP(clk => clk_pixel, data => green_p, c => c_green, blank => blank, encoded => encoded_green);
TDMS_encoder_blue: TDMS_encoder PORT MAP(clk => clk_pixel, data => blue_p, c => c_blue, blank => blank, encoded => encoded_blue);
ODDR2_red : ODDR2 generic map( DDR_ALIGNMENT => "C0", INIT => '0', SRTYPE => "ASYNC")
port map (Q => red_s, D0 => shift_red(0), D1 => shift_red(1), C0 => clk, C1 => clk_n, CE => '1', R => '0', S => '0');
ODDR2_green : ODDR2 generic map( DDR_ALIGNMENT => "C0", INIT => '0', SRTYPE => "ASYNC")
port map (Q => green_s, D0 => shift_green(0), D1 => shift_green(1), C0 => clk, C1 => clk_n, CE => '1', R => '0', S => '0');
ODDR2_blue : ODDR2 generic map( DDR_ALIGNMENT => "C0", INIT => '0', SRTYPE => "ASYNC")
port map (Q => blue_s, D0 => shift_blue(0), D1 => shift_blue(1), C0 => clk, C1 => clk_n, CE => '1', R => '0', S => '0');
ODDR2_clock : ODDR2 generic map( DDR_ALIGNMENT => "C0", INIT => '0', SRTYPE => "ASYNC")
port map (Q => clock_s, D0 => shift_clock(0), D1 => shift_clock(1), C0 => clk, C1 => clk_n, CE => '1', R => '0', S => '0');
process(clk_pixel)
begin
if rising_edge(clk_pixel) then
latched_red <= encoded_red;
latched_green <= encoded_green;
latched_blue <= encoded_blue;
end if;
end process;
process(clk)
begin
if rising_edge(clk) then
if shift_clock = "0000011111" then
shift_red <= latched_red;
shift_green <= latched_green;
shift_blue <= latched_blue;
else
shift_red <= "00" & shift_red (9 downto 2);
shift_green <= "00" & shift_green(9 downto 2);
shift_blue <= "00" & shift_blue (9 downto 2);
end if;
shift_clock <= shift_clock(1 downto 0) & shift_clock(9 downto 2);
end if;
end process;
end Behavioral;
TDMS_encoder.vhd
----------------------------------------------------------------------------------
-- Engineer: Mike Field <hamster@snap.net.nz>
--
-- Description: TDMS Encoder
-- 8 bits colour, 2 control bits and one blanking bits in
-- 10 bits of TDMS encoded data out
-- Clocked at the pixel clock
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity TDMS_encoder is
Port ( clk : in STD_LOGIC;
data : in STD_LOGIC_VECTOR (7 downto 0);
c : in STD_LOGIC_VECTOR (1 downto 0);
blank : in STD_LOGIC;
encoded : out STD_LOGIC_VECTOR (9 downto 0));
end TDMS_encoder;
architecture Behavioral of TDMS_encoder is
signal xored : STD_LOGIC_VECTOR (8 downto 0);
signal xnored : STD_LOGIC_VECTOR (8 downto 0);
signal ones : STD_LOGIC_VECTOR (3 downto 0);
signal data_word : STD_LOGIC_VECTOR (8 downto 0);
signal data_word_inv : STD_LOGIC_VECTOR (8 downto 0);
signal data_word_disparity : STD_LOGIC_VECTOR (3 downto 0);
signal dc_bias : STD_LOGIC_VECTOR (3 downto 0) := (others => '0');
begin
-- Work our the two different encodings for the byte
xored(0) <= data(0);
xored(1) <= data(1) xor xored(0);
xored(2) <= data(2) xor xored(1);
xored(3) <= data(3) xor xored(2);
xored(4) <= data(4) xor xored(3);
xored(5) <= data(5) xor xored(4);
xored(6) <= data(6) xor xored(5);
xored(7) <= data(7) xor xored(6);
xored(8) <= '1';
xnored(0) <= data(0);
xnored(1) <= data(1) xnor xnored(0);
xnored(2) <= data(2) xnor xnored(1);
xnored(3) <= data(3) xnor xnored(2);
xnored(4) <= data(4) xnor xnored(3);
xnored(5) <= data(5) xnor xnored(4);
xnored(6) <= data(6) xnor xnored(5);
xnored(7) <= data(7) xnor xnored(6);
xnored(8) <= '0';
-- Count how many ones are set in data
ones <= "0000" + data(0) + data(1) + data(2) + data(3)
+ data(4) + data(5) + data(6) + data(7);
-- Decide which encoding to use
process(ones, data(0), xnored, xored)
begin
if ones > 4 or (ones = 4 and data(0) = '0') then
data_word <= xnored;
data_word_inv <= NOT(xnored);
else
data_word <= xored;
data_word_inv <= NOT(xored);
end if;
end process;
-- Work out the DC bias of the dataword;
data_word_disparity <= "1100" + data_word(0) + data_word(1) + data_word(2) + data_word(3)
+ data_word(4) + data_word(5) + data_word(6) + data_word(7);
-- Now work out what the output should be
process(clk)
begin
if rising_edge(clk) then
if blank = '1' then
-- In the control periods, all values have and have balanced bit count
case c is
when "00" => encoded <= "1101010100";
when "01" => encoded <= "0010101011";
when "10" => encoded <= "0101010100";
when others => encoded <= "1010101011";
end case;
dc_bias <= (others => '0');
else
if dc_bias = "00000" or data_word_disparity = 0 then
-- dataword has no disparity
if data_word(8) = '1' then
encoded <= "01" & data_word(7 downto 0);
dc_bias <= dc_bias + data_word_disparity;
else
encoded <= "10" & data_word_inv(7 downto 0);
dc_bias <= dc_bias - data_word_disparity;
end if;
elsif (dc_bias(3) = '0' and data_word_disparity(3) = '0') or
(dc_bias(3) = '1' and data_word_disparity(3) = '1') then
encoded <= '1' & data_word(8) & data_word_inv(7 downto 0);
dc_bias <= dc_bias + data_word(8) - data_word_disparity;
else
encoded <= '0' & data_word;
dc_bias <= dc_bias - data_word_inv(8) + data_word_disparity;
end if;
end if;
end if;
end process;
end Behavioral;
vga.vhd
----------------------------------------------------------------------------------
-- Engineer: Mike Field <hamster@snap.net.nz>
-- Module Name: ColourTest - Behavioral
-- Description: Generates an 640x480 VGA showing all colours
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity vga is
generic (
hRez : natural := 640;
hStartSync : natural := 656;
hEndSync : natural := 752;
hMaxCount : natural := 800;
hsyncActive : std_logic := '0';
vRez : natural := 480;
vStartSync : natural := 490;
vEndSync : natural := 492;
vMaxCount : natural := 525;
vsyncActive : std_logic := '1'
);
Port ( pixelClock : in STD_LOGIC;
Red : out STD_LOGIC_VECTOR (7 downto 0);
Green : out STD_LOGIC_VECTOR (7 downto 0);
Blue : out STD_LOGIC_VECTOR (7 downto 0);
hSync : out STD_LOGIC;
vSync : out STD_LOGIC;
blank : out STD_LOGIC);
end vga;
architecture Behavioral of vga is
type reg is record
hCounter : std_logic_vector(11 downto 0);
vCounter : std_logic_vector(11 downto 0);
red : std_logic_vector(7 downto 0);
green : std_logic_vector(7 downto 0);
blue : std_logic_vector(7 downto 0);
hSync : std_logic;
vSync : std_logic;
blank : std_logic;
end record;
signal r : reg := ((others=>'0'), (others=>'0'),
(others=>'0'), (others=>'0'), (others=>'0'),
'0', '0', '0');
signal n : reg;
begin
-- Assign the outputs
hSync <= r.hSync;
vSync <= r.vSync;
Red <= r.red;
Green <= r.green;
Blue <= r.blue;
blank <= r.blank;
process(r,n)
begin
n <= r;
n.hSync <= not hSyncActive;
n.vSync <= not vSyncActive;
-- Count the lines and rows
if r.hCounter = hMaxCount-1 then
n.hCounter <= (others => '0');
if r.vCounter = vMaxCount-1 then
n.vCounter <= (others => '0');
else
n.vCounter <= r.vCounter+1;
end if;
else
n.hCounter <= r.hCounter+1;
end if;
if r.hCounter < hRez and r.vCounter < vRez then
n.red <= n.hCounter(5 downto 0) & n.hCounter(5 downto 4);
n.green <= n.hCounter(7 downto 0);
n.blue <= n.vCounter(7 downto 0);
n.blank <= '0';
else
n.red <= (others => '0');
n.green <= (others => '0');
n.blue <= (others => '0');
n.blank <= '1';
end if;
-- Are we in the hSync pulse?
if r.hCounter >= hStartSync and r.hCounter < hEndSync then
n.hSync <= hSyncActive;
end if;
-- Are we in the vSync pulse?
if r.vCounter >= vStartSync and r.vCounter < vEndSync then
n.vSync <= vSyncActive;
end if;
end process;
process(pixelClock,n)
begin
if rising_edge(pixelClock)
then
r <= n;
end if;
end process;
end Behavioral;
