I'm trying to make sense of the VHDL code from this github project. In particular, I try to understand this snippet:
-- author: Furkan Cayci, 2018
-- description: video timing generator
-- choose between hd1080p, hd720p, svga, and vga
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity timing_generator is
generic (
RESOLUTION : string := "HD720P"; -- hd1080p, hd720p, svga, vga
GEN_PIX_LOC : boolean := true;
OBJECT_SIZE : natural := 16
);
port(
clk : in std_logic;
hsync, vsync : out std_logic;
video_active : out std_logic;
pixel_x : out std_logic_vector(OBJECT_SIZE-1 downto 0);
pixel_y : out std_logic_vector(OBJECT_SIZE-1 downto 0)
);
end timing_generator;
architecture rtl of timing_generator is
type video_timing_type is record
H_VIDEO : integer;
H_FP : integer;
H_SYNC : integer;
H_BP : integer;
H_TOTAL : integer;
V_VIDEO : integer;
V_FP : integer;
V_SYNC : integer;
V_BP : integer;
V_TOTAL : integer;
H_POL : std_logic;
V_POL : std_logic;
ACTIVE : std_logic;
end record;
-- HD1080p timing
-- screen area 1920x1080 @60 Hz
-- horizontal : 1920 visible + 88 front porch (fp) + 44 hsync + 148 back porch = 2200 pixels
-- vertical : 1080 visible + 4 front porch (fp) + 5 vsync + 36 back porch = 1125 pixels
-- Total area 2200x1125
-- clk input should be 148.5 MHz signal (2200 * 1125 * 60)
-- hsync and vsync are positive polarity
constant HD1080P_TIMING : video_timing_type := (
H_VIDEO => 1920,
H_FP => 88,
H_SYNC => 44,
H_BP => 148,
H_TOTAL => 2200,
V_VIDEO => 1080,
V_FP => 4,
V_SYNC => 5,
V_BP => 36,
V_TOTAL => 1125,
H_POL => '1',
V_POL => '1',
ACTIVE => '1'
);
-- HD720p timing
-- screen area 1280x720 @60 Hz
-- horizontal : 1280 visible + 72 front porch (fp) + 80 hsync + 216 back porch = 1648 pixels
-- vertical : 720 visible + 3 front porch (fp) + 5 vsync + 22 back porch = 750 pixels
-- Total area 1648x750
-- clk input should be 74.25 MHz signal (1650 * 750 * 60)
-- hsync and vsync are positive polarity
constant HD720P_TIMING : video_timing_type := (
H_VIDEO => 1280,
H_FP => 72,
H_SYNC => 80,
H_BP => 216,
H_TOTAL => 1648,
V_VIDEO => 720,
V_FP => 3,
V_SYNC => 5,
V_BP => 22,
V_TOTAL => 750,
H_POL => '1',
V_POL => '1',
ACTIVE => '1'
);
-- SVGA timing
-- screen area 800x600 @60 Hz
-- horizontal : 800 visible + 40 front porch (fp) + 128 hsync + 88 back porch = 1056 pixels
-- vertical : 600 visible + 1 front porch (fp) + 4 vsync + 23 back porch = 628 pixels
-- Total area 1056x628
-- clk input should be 40 MHz signal (1056 * 628 * 60)
-- hsync and vsync are positive polarity
constant SVGA_TIMING : video_timing_type := (
H_VIDEO => 800,
H_FP => 40,
H_SYNC => 128,
H_BP => 88,
H_TOTAL => 1056,
V_VIDEO => 600,
V_FP => 1,
V_SYNC => 4,
V_BP => 23,
V_TOTAL => 628,
H_POL => '1',
V_POL => '1',
ACTIVE => '1'
);
-- VGA timing
-- screen area 640x480 @60 Hz
-- horizontal : 640 visible + 16 front porch (fp) + 96 hsync + 48 back porch = 800 pixels
-- vertical : 480 visible + 10 front porch (fp) + 2 vsync + 33 back porch = 525 pixels
-- Total area 800x525
-- clk input should be 25 MHz signal (800 * 525 * 60)
-- hsync and vsync are negative polarity
constant VGA_TIMING : video_timing_type := (
H_VIDEO => 640,
H_FP => 16,
H_SYNC => 96,
H_BP => 48,
H_TOTAL => 800,
V_VIDEO => 480,
V_FP => 10,
V_SYNC => 2,
V_BP => 33,
V_TOTAL => 525,
H_POL => '0',
V_POL => '0',
ACTIVE => '1'
);
-- horizontal and vertical counters
signal hcount : unsigned(OBJECT_SIZE-1 downto 0) := (others => '0');
signal vcount : unsigned(OBJECT_SIZE-1 downto 0) := (others => '0');
signal timings : video_timing_type := HD720P_TIMING;
begin
timings <= HD1080P_TIMING when RESOLUTION = "HD1080P" else
HD720P_TIMING when RESOLUTION = "HD720P" else
SVGA_TIMING when RESOLUTION = "SVGA" else
VGA_TIMING when RESOLUTION = "VGA";
-- pixel counters
process (clk) is
begin
if rising_edge(clk) then
if (hcount = timings.H_TOTAL) then
hcount <= (others => '0');
if (vcount = timings.V_TOTAL) then
vcount <= (others => '0');
else
vcount <= vcount + 1;
end if;
else
hcount <= hcount + 1;
end if;
end if;
end process;
-- generate video_active, hsync, and vsync signals based on the counters
video_active <= timings.ACTIVE when (hcount < timings.H_VIDEO) and (vcount < timings.V_VIDEO ) else not timings.ACTIVE;
hsync <= timings.H_POL when (hcount >= timings.H_VIDEO + timings.H_FP) and (hcount < timings.H_TOTAL - timings.H_BP) else not timings.H_POL;
vsync <= timings.V_POL when (vcount >= timings.V_VIDEO + timings.V_FP) and (vcount < timings.V_TOTAL - timings.V_BP) else not timings.V_POL;
g0: if GEN_PIX_LOC = true generate
begin
-- send pixel locations
pixel_x <= std_logic_vector(hcount) when hcount < timings.H_VIDEO else (others => '0');
pixel_y <= std_logic_vector(vcount) when vcount < timings.V_VIDEO else (others => '0');
end generate;
g1: if GEN_PIX_LOC = false generate
begin
-- send pixel locations
pixel_x <= (others => '0');
pixel_y <= (others => '0');
end generate;
end rtl;
My interpretation of the code (taking into consideration what I know about HDMI protocol and vhdl) is given in the commented version below:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity timing_generator is
-- this are pased and used at compile time only
generic (
RESOLUTION : string := "HD720P";
GEN_PIX_LOC : boolean := true;
OBJECT_SIZE : natural := 16
);
-- this are signals which will be instantiated in hardware
port(
clk : in std_logic;
hsync, vsync : out std_logic;
video_active : out std_logic;
pixel_x : out std_logic_vector(OBJECT_SIZE-1 downto 0);
pixel_y : out std_logic_vector(OBJECT_SIZE-1 downto 0)
);
end timing_generator;
architecture rtl of timing_generator is
-- this is just a struct definition, only relevant at compile time
type video_timing_type is record
H_VIDEO : integer;
H_FP : integer;
H_SYNC : integer;
H_BP : integer;
H_TOTAL : integer;
V_VIDEO : integer;
V_FP : integer;
V_SYNC : integer;
V_BP : integer;
V_TOTAL : integer;
H_POL : std_logic;
V_POL : std_logic;
ACTIVE : std_logic;
end record;
-- everything below are specifications of region sizes for different resolutions
-- they are probably specified in HDMI protocol tech spec
-- they are only defined here for one to be instantiated at compile time
constant HD1080P_TIMING : video_timing_type := (
H_VIDEO => 1920,
H_FP => 88,
H_SYNC => 44,
H_BP => 148,
H_TOTAL => 2200,
V_VIDEO => 1080,
V_FP => 4,
V_SYNC => 5,
V_BP => 36,
V_TOTAL => 1125,
H_POL => '1',
V_POL => '1',
ACTIVE => '1'
);
constant HD720P_TIMING : video_timing_type := (
H_VIDEO => 1280,
H_FP => 72,
H_SYNC => 80,
H_BP => 216,
H_TOTAL => 1648,
V_VIDEO => 720,
V_FP => 3,
V_SYNC => 5,
V_BP => 22,
V_TOTAL => 750,
H_POL => '1',
V_POL => '1',
ACTIVE => '1'
);
constant SVGA_TIMING : video_timing_type := (
H_VIDEO => 800,
H_FP => 40,
H_SYNC => 128,
H_BP => 88,
H_TOTAL => 1056,
V_VIDEO => 600,
V_FP => 1,
V_SYNC => 4,
V_BP => 23,
V_TOTAL => 628,
H_POL => '1',
V_POL => '1',
ACTIVE => '1'
);
constant VGA_TIMING : video_timing_type := (
H_VIDEO => 640,
H_FP => 16,
H_SYNC => 96,
H_BP => 48,
H_TOTAL => 800,
V_VIDEO => 480,
V_FP => 10,
V_SYNC => 2,
V_BP => 33,
V_TOTAL => 525,
H_POL => '0',
V_POL => '0',
ACTIVE => '1'
);
-- this will be a register storing OBJECT_SIZE bits. It will be interpreted as unsigned int.
-- it is zeroed out at the start
signal hcount : unsigned(OBJECT_SIZE-1 downto 0) := (others => '0');
-- likewise
signal vcount : unsigned(OBJECT_SIZE-1 downto 0) := (others => '0');
-- it's likely set here to arbitrary timing because it cannot be left unset.
signal timings : video_timing_type := HD720P_TIMING;
begin
-- this is where preproc sets timing based on generic param
timings <= HD1080P_TIMING when RESOLUTION = "HD1080P" else
HD720P_TIMING when RESOLUTION = "HD720P" else
SVGA_TIMING when RESOLUTION = "SVGA" else
VGA_TIMING when RESOLUTION = "VGA";
-- this are basically two for loops iterating over x and y values
-- since each process is executed on it's own, it's synced on clock and
-- provides coordinates of currently rendered pixel for other components
process (clk) is
begin
if rising_edge(clk) then
if (hcount = timings.H_TOTAL) then -- if end of row
hcount <= (others => '0'); -- reset x coordinate
if (vcount = timings.V_TOTAL) then -- if end of frame
vcount <= (others => '0'); -- reset y coordinate
else
vcount <= vcount + 1; -- else, increment y coordinate
end if;
else
hcount <= hcount + 1; -- else, increment x coordinate
end if;
end if;
end process;
-- since not every "pixel" is actual graphical data flags are set depending on what region is being rendered
-- video active is set when x is less the visible frame width and y is less then visible frame height
video_active <= timings.ACTIVE when (hcount < timings.H_VIDEO) and (vcount < timings.V_VIDEO ) else not timings.ACTIVE;
-- hsync and vsync are set when horz_sync region is traversed and vert_sync region is traversed respectively
hsync <= timings.H_POL when (hcount >= timings.H_VIDEO + timings.H_FP) and (hcount < timings.H_TOTAL - timings.H_BP) else not timings.H_POL;
vsync <= timings.V_POL when (vcount >= timings.V_VIDEO + timings.V_FP) and (vcount < timings.V_TOTAL - timings.V_BP) else not timings.V_POL;
-- other modules may use the locations in the visible frame to determine the rgb data
-- broadcast those coordinates is conditionaly enabled at compile time
g0: if GEN_PIX_LOC = true generate
begin
-- broadcast x coordinate when x is less then visible frame width, otherwise broadcast zero
pixel_x <= std_logic_vector(hcount) when hcount < timings.H_VIDEO else (others => '0');
-- broadcast y coordinate when y is less then visible frame height, otherwise broadcast zero
pixel_y <= std_logic_vector(vcount) when vcount < timings.V_VIDEO else (others => '0');
end generate;
g1: if GEN_PIX_LOC = false generate
begin
pixel_x <= (others => '0');
pixel_y <= (others => '0');
end generate;
end rtl;
From the comments in the original code I assumed that transmittion of an individual frame looks as follows:
In horizontal direction: Front Porch, Rendered Video, Sync Region, Back Porch.
In vertical direction: Front Porch, Rendered Video, Sync Region, Back Porch.
However, few things confuse me, forcing me to doubt whether this is indeed the order in which data is send over the link.
If front porch perceeds rendered video both vertically and horizontally and hcount and vcount span all the data space, not just the rendered frame, why video_active flag is set with
video_active <= timings.ACTIVE when (hcount < timings.H_VIDEO) and (vcount < timings.V_VIDEO ) else not timings.ACTIVE;
and not with
video_active <= timings.ACTIVE when (hcount > timings.H_FP) and (hcount < timings.H_VIDEO + timings.H_FP) and (vcount > timings.V_FP) and (vcount < timings.V_VIDEO + V_FP) else not timings.ACTIVE;
Similarly, why when broadcasting coordinates of currently rendered pixel, the values are not offset to take front porch into consideration. Moreover, why are conditions for broadcasting x and y only depend on the respective coordinate. That would imply that x will be boradcast during the vertical front and back porches and vice versa. In the module which makes use of the coordinates no offsets are applied either.
Finally, location and purpose of sync region is unclear to me. While HDMI protocol specifies that porches should be used to place data and control islands, I do not understand the purpose of sync regions. Sync flags are only transmit during control islands, hence receiver can only decode it after receiving a preambule notifying it that now begins the data island. But if the receiver knows that now begins the data island, what is the point of notifying it about completing row or frame?
To illustrate first two of my doubts, here is a diagram:
