From c4490168350b5fa44c02ff42ee2091f73700ba58 Mon Sep 17 00:00:00 2001 From: MaxP Date: Thu, 27 Mar 2025 16:00:08 +0000 Subject: [PATCH] Add VGA Timing Generator implementation and testbench files Test @ 1080P --- src/VGATimingGenerator.vhd | 203 +++++++++++++++++++++++++++ src/VGATimingGenerator_pb.ucf | 3 + src/VGATimingGenerator_pb.vhd | 68 +++++++++ src/VGATimingGenerator_test.ucf | 15 ++ src/VGATimingGenerator_test.vhd | 136 ++++++++++++++++++ tests/VGATimingGenerator_tb.vhd | 45 ++++++ tests/VGATimingGenerator_test_tb.vhd | 33 +++++ tests/default.wcfg | 41 ++++++ 8 files changed, 544 insertions(+) create mode 100644 src/VGATimingGenerator.vhd create mode 100644 src/VGATimingGenerator_pb.ucf create mode 100644 src/VGATimingGenerator_pb.vhd create mode 100644 src/VGATimingGenerator_test.ucf create mode 100644 src/VGATimingGenerator_test.vhd create mode 100644 tests/VGATimingGenerator_tb.vhd create mode 100644 tests/VGATimingGenerator_test_tb.vhd create mode 100644 tests/default.wcfg diff --git a/src/VGATimingGenerator.vhd b/src/VGATimingGenerator.vhd new file mode 100644 index 0000000..9608888 --- /dev/null +++ b/src/VGATimingGenerator.vhd @@ -0,0 +1,203 @@ +---------------------------------------------------------------------------------- +--@ - Name: **VGA Timing Generator** +--@ - Version: 0.0.1 +--@ - Author: _Maximilian Passarello ([Blog](mpassarello.de))_ +--@ - License: [MIT](LICENSE) +--@ +--@ The VGA Timing Generator is a simple module that generates the horizontal and vertical sync signals for a VGA display. +--@ +--@ ## Generics +--@ **Default values** are set for a 640x480@60Hz display with a 25.175 MHz pixel clock. +--@ +--@ ## History +--@ - 0.0.1 (2025-03-26) Initial version +---------------------------------------------------------------------------------- +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; +use ieee.math_real.all; + +entity VGATimingGenerator is + generic ( + --@ Horizontal Front Porch + G_HFront : integer := 16; + --@ Horizontal Sync Pulse + G_HSync : integer := 96; + --@ Horizontal Back Porch + G_HBack : integer := 48; + --@ Horizontal Total resolution + G_HTotal : integer := 800; + --@ Vertical Front Porch + G_VFront : integer := 10; + --@ Vertical Sync Pulse + G_VSync : integer := 2; + --@ Vertical Back Porch + G_VBack : integer := 33; + --@ Vertical Total resolution + G_VTotal : integer := 525 + ); + port ( + --@ Clock signal; **Rising edge** triggered + I_CLK : in std_logic; + --@ Clock Enable signal + I_CE : in std_logic; + --@ Synchronous reset signal + I_RST : in std_logic; + --@ Ready signal (AXI like) to indicate that the pixel data is ready to be displayed + O_PixelReady : out std_logic; + --@ @virtualbus VGA-Timing-Signals @dir out VGA timing signals + --@ Horizontal Sync signal; **Active low** + O_HSync : out std_logic; + --@ Vertical Sync signal; **Active low** + O_VSync : out std_logic + --@ @end + ); +end entity VGATimingGenerator; + +architecture RTL of VGATimingGenerator is + --@ Horizontal Counter; max value = G_HTotal + signal R_HorizontalCounter : unsigned(integer(ceil(log2(real(G_HTotal)))) downto 0) := (others => '0'); + --@ Vertical Counter; max value = G_VTotal + signal R_VerticalCounter : unsigned(integer(ceil(log2(real(G_VTotal)))) downto 0) := (others => '0'); + + --@ Counter Enable signal for Vertical Counter + signal C_VerticalCE : std_logic := '0'; + + --@ Flag to indicate if the horizontal counter is in the visible area + signal C_HorizontalVisible : std_logic := '0'; + --@ Flag to indicate if the vertical counter is in the visible area + signal C_VerticalVisible : std_logic := '0'; + + --@ Horizontal Sync signal shift register + signal R_HSync : std_logic_vector(1 downto 0) := (others => '0'); + --@ Vertical Sync signal register + signal R_VSync : std_logic := '1'; + --@ Pixel Ready signal + signal C_PixelReady : std_logic := '0'; +begin + --@ Horizontal Pixel Counter. + --@ Overflows at G_HTotal. + P_HorizontalCounter : process (I_CLK) + begin + if rising_edge(I_CLK) then + if I_RST = '1' then + R_HorizontalCounter <= (others => '0'); + elsif I_CE = '1' then + if R_HorizontalCounter = G_HTotal - 1 then + R_HorizontalCounter <= (others => '0'); + else + R_HorizontalCounter <= R_HorizontalCounter + 1; + end if; + end if; + end if; + end process; + + --@ Horizontal Sync signal shift register. + --@ Bit 0: HSync; Bit 1: HSync delayed by one clock cycle + P_HSyncRegister : process (I_CLK) + begin + if rising_edge(I_CLK) then + if I_RST = '1' then + R_HSync <= (others => '1'); + elsif I_CE = '1' then + if R_HorizontalCounter < G_HSync then + R_HSync <= R_HSync(R_HSync'high - 1 downto R_HSync'low) & '0'; + else + R_HSync <= R_HSync(R_HSync'high - 1 downto R_HSync'low) & '1'; + end if; + end if; + end if; + end process; + + --@ Flag generator for horizontal visible area. + P_HorizontalVisible : process (R_HorizontalCounter) + begin + if R_HorizontalCounter >= G_HSync + G_HBack and R_HorizontalCounter <= G_HTotal - G_HFront - 1 then + C_HorizontalVisible <= '1'; + else + C_HorizontalVisible <= '0'; + end if; + end process; + + --@ CE signal generator for vertical counter. + --@ Activated one cycle after the horizontal sync signal. + P_VerticalCE : process (R_HSync) + begin + if R_HSync = "01" then + C_VerticalCE <= '1'; + else + C_VerticalCE <= '0'; + end if; + end process; + + --@ Vertical Pixel Counter. + --@ Overflows at G_VTotal. + P_VerticalCounter : process (I_CLK) + begin + if rising_edge(I_CLK) then + if I_RST = '1' then + R_VerticalCounter <= (others => '0'); + elsif C_VerticalCE = '1' then + if R_VerticalCounter = G_VTotal - 1 then + R_VerticalCounter <= (others => '0'); + else + R_VerticalCounter <= R_VerticalCounter + 1; + end if; + end if; + end if; + end process; + + --@ Vertical Sync signal generator. + P_VSyncRegister : process (I_CLK) + begin + if rising_edge(I_CLK) then + if I_RST = '1' then + R_VSync <= '1'; + elsif C_VerticalCE = '1' then + if R_VerticalCounter < G_VSync then + R_VSync <= '0'; + else + R_VSync <= '1'; + end if; + end if; + end if; + end process; + + --@ Flag generator for vertical visible area. + P_VerticalVisible : process (R_VerticalCounter) + begin + if R_VerticalCounter >= G_VSync + G_VBack and R_VerticalCounter <= G_VTotal - G_VFront - 1 then + C_VerticalVisible <= '1'; + else + C_VerticalVisible <= '0'; + end if; + end process; + + --@ Pixel Ready signal generator. + --@ Active when both horizontal and vertical counters are in the visible area. + P_PixelReady : process (C_HorizontalVisible, C_VerticalVisible) + begin + if C_HorizontalVisible = '1' and C_VerticalVisible = '1' then + C_PixelReady <= '1'; + else + C_PixelReady <= '0'; + end if; + end process; + + --@ Output signals synchronization. + P_SyncSignals : process (I_CLK) + begin + if rising_edge(I_CLK) then + if I_RST = '1' then + O_HSync <= '1'; + O_VSync <= '1'; + O_PixelReady <= '0'; + elsif I_CE = '1' then + O_HSync <= R_HSync(0); + O_VSync <= R_VSync; + O_PixelReady <= C_PixelReady; + end if; + end if; + end process; + +end architecture RTL; \ No newline at end of file diff --git a/src/VGATimingGenerator_pb.ucf b/src/VGATimingGenerator_pb.ucf new file mode 100644 index 0000000..606c607 --- /dev/null +++ b/src/VGATimingGenerator_pb.ucf @@ -0,0 +1,3 @@ +NET I_CLK LOC = B8; +NET I_CLK TNM_NET = CLOCK; +TIMESPEC TS_CLOCK = PERIOD CLOCK 30 MHz HIGH 50 %; \ No newline at end of file diff --git a/src/VGATimingGenerator_pb.vhd b/src/VGATimingGenerator_pb.vhd new file mode 100644 index 0000000..225e307 --- /dev/null +++ b/src/VGATimingGenerator_pb.vhd @@ -0,0 +1,68 @@ +---------------------------------------------------------------------------------- +--@ - Name: **Pipeline Register** +--@ - Version: 0.0.1 +--@ - Author: _Maximilian Passarello ([Blog](mpassarello.de))_ +--@ - License: [MIT](LICENSE) +--@ +--@ The VGA Timing Generator is a simple module that generates the horizontal and vertical sync signals for a VGA display. +--@ +--@ ## History +--@ - 0.0.1 (2024-03-24) Initial version +---------------------------------------------------------------------------------- +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; +use ieee.math_real.all; + +entity VGATimingGenerator_pb is + port ( + --@ Clock signal; **Rising edge** triggered + I_CLK : in std_logic; + --@ Clock Enable signal + I_CE : in std_logic; + --@ Synchronous reset signal + I_RST : in std_logic; + --@ Ready signal to indicate that the pixel data is ready to be displayed + O_PixelReady : out std_logic; + --@ @virtualbus VGA-Timing-Signals @dir out VGA timing signals + --@ Horizontal Sync signal; **Active low** + O_HSync : out std_logic; + --@ Vertical Sync signal; **Active low** + O_VSync : out std_logic + --@ @end + ); +end entity VGATimingGenerator_pb; + +architecture RTL of VGATimingGenerator_pb is + signal R_RST : std_logic; + signal R_CE : std_logic; + signal C_PixelReady : std_logic; + signal R_HSync : std_logic; + signal R_VSync : std_logic; +begin + + BenchmarkEnvironmentFFs : process (I_CLK) + begin + if rising_edge(I_CLK) then + -- General Interace + R_CE <= I_CE; + R_RST <= I_RST; + + -- Output Interface + O_PixelReady <= C_PixelReady; + O_HSync <= R_HSync; + O_VSync <= R_VSync; + end if; + end process; + + VGATimingGenerator : entity work.VGATimingGenerator + port map + ( + I_CLK => I_CLK, + I_CE => R_CE, + I_RST => R_RST, + O_PixelReady => C_PixelReady, + O_HSync => R_HSync, + O_VSync => R_VSync + ); +end architecture RTL; \ No newline at end of file diff --git a/src/VGATimingGenerator_test.ucf b/src/VGATimingGenerator_test.ucf new file mode 100644 index 0000000..19419ae --- /dev/null +++ b/src/VGATimingGenerator_test.ucf @@ -0,0 +1,15 @@ +NET I_CLK LOC = B8; +NET I_CLK TNM_NET = CLOCK; +TIMESPEC TS_CLOCK = PERIOD CLOCK 50 MHz HIGH 50 %; + +NET O_HSync LOC = T4 | IOSTANDARD = LVCMOS25 | SLEW = FAST | DRIVE = 12; +NET O_VSync LOC = U3 | IOSTANDARD = LVCMOS25 | SLEW = FAST | DRIVE = 12; + +NET O_Red<0> LOC = R9 | IOSTANDARD = LVCMOS25 | SLEW = FAST | DRIVE = 12; +NET O_Red<1> LOC = T8 | IOSTANDARD = LVCMOS25 | SLEW = FAST | DRIVE = 12; +NET O_Red<2> LOC = R8 | IOSTANDARD = LVCMOS25 | SLEW = FAST | DRIVE = 12; +NET O_Green<0> LOC = N8 | IOSTANDARD = LVCMOS25 | SLEW = FAST | DRIVE = 12; +NET O_Green<1> LOC = P8 | IOSTANDARD = LVCMOS25 | SLEW = FAST | DRIVE = 12; +NET O_Green<2> LOC = P6 | IOSTANDARD = LVCMOS25 | SLEW = FAST | DRIVE = 12; +NET O_Blue<0> LOC = U5 | IOSTANDARD = LVCMOS25 | SLEW = FAST | DRIVE = 12; +NET O_Blue<1> LOC = U4 | IOSTANDARD = LVCMOS25 | SLEW = FAST | DRIVE = 12; \ No newline at end of file diff --git a/src/VGATimingGenerator_test.vhd b/src/VGATimingGenerator_test.vhd new file mode 100644 index 0000000..c961859 --- /dev/null +++ b/src/VGATimingGenerator_test.vhd @@ -0,0 +1,136 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; +use ieee.math_real.all; +library UNISIM; +use UNISIM.vcomponents.all; + +entity VGATimingGenerator_test is + port ( + --@ Clock signal; **Rising edge** triggered + I_CLK : in std_logic; + --@ @virtualbus VGA-Signals @dir out VGA signals + --@ Horizontal Sync signal; **Active low** + O_HSync : out std_logic; + --@ Vertical Sync signal; **Active low** + O_VSync : out std_logic; + --@ VGA Red Channel + O_Red : out std_logic_vector(2 downto 0); + --@ VGA Green Channel + O_Green : out std_logic_vector(2 downto 0); + --@ VGA Blue Channel + O_Blue : out std_logic_vector(1 downto 0) + --@ @end + ); +end entity VGATimingGenerator_test; + +architecture RTL of VGATimingGenerator_test is + signal R_PixelReady : std_logic_vector(1 downto 0); + + signal R_LineCounter : unsigned(19 downto 0) := (others => '0'); + + signal R_VSync : std_logic; + + signal CLK_FB : std_logic; + signal PixelCLK : std_logic; +begin + + ClockManager : DCM_SP + generic map( + CLKDV_DIVIDE => 2.0, + CLKFX_DIVIDE => 10, + CLKFX_MULTIPLY => 30, + CLKIN_DIVIDE_BY_2 => FALSE, + CLKIN_PERIOD => 10.0, + CLKOUT_PHASE_SHIFT => "NONE", + CLK_FEEDBACK => "1X", + DESKEW_ADJUST => "SYSTEM_SYNCHRONOUS", + DFS_FREQUENCY_MODE => "LOW", + DLL_FREQUENCY_MODE => "LOW", + DUTY_CYCLE_CORRECTION => TRUE, + FACTORY_JF => X"C080", + PHASE_SHIFT => 0, + STARTUP_WAIT => FALSE) + port map + ( + CLK0 => CLK_FB, + CLKFX => PixelCLK, + CLKFB => CLK_FB, + CLKIN => I_CLK + ); + + VGAColorGenerator : process (PixelCLK) + variable R_SignalCounter : unsigned(3 downto 0) := (others => '0'); + begin + if rising_edge(PixelCLK) then + if R_VSync = '0' then + --R_LineCounter <= (others => '0'); + --R_SignalCounter <= (others => '0'); + --O_Red <= "000"; + --O_Green <= "000"; + --O_Blue <= "00"; + else + R_PixelReady(1) <= R_PixelReady(0); + + if R_PixelReady = "10" then + R_LineCounter <= R_LineCounter + 1; + end if; + + if R_PixelReady(0) = '1' then + if R_LineCounter = 10 then + R_LineCounter <= (others => '0'); + + if R_SignalCounter = 2 then + R_SignalCounter := (others => '0'); + else + R_SignalCounter := R_SignalCounter + 1; + end if; + end if; + + if R_SignalCounter = 0 then + O_Red <= "111"; + O_Green <= "000"; + O_Blue <= "00"; + elsif R_SignalCounter = 1 then + O_Red <= "000"; + O_Green <= "111"; + O_Blue <= "00"; + elsif R_SignalCounter = 2 then + O_Red <= "000"; + O_Green <= "000"; + O_Blue <= "11"; + end if; + else + O_Red <= "000"; + O_Green <= "000"; + O_Blue <= "00"; + end if; + end if; + end if; + end process; + + VGATimingGenerator : entity work.VGATimingGenerator + generic map( + G_HFront => 88, + G_HSync => 44, + G_HBack => 148, + G_HTotal => 2200, + G_VFront => 4, + G_VSync => 5, + G_VBack => 36, + G_VTotal => 1125 + ) + + port map + ( + I_CLK => PixelCLK, + I_CE => '1', + I_RST => '0', + O_PixelReady => R_PixelReady(0), + O_HSync => O_HSync, + O_VSync => R_VSync + ); + + O_VSync <= R_VSync; + +end architecture RTL; \ No newline at end of file diff --git a/tests/VGATimingGenerator_tb.vhd b/tests/VGATimingGenerator_tb.vhd new file mode 100644 index 0000000..edc7927 --- /dev/null +++ b/tests/VGATimingGenerator_tb.vhd @@ -0,0 +1,45 @@ + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +entity VGATimingGenerator_tb is +end; + +architecture bench of VGATimingGenerator_tb is + -- Clock period + constant clk_period : time := 40 ns; + -- Ports + signal I_CLK : std_logic := '0'; + signal I_CE : std_logic := '1'; + signal I_RST : std_logic := '1'; + signal O_PixelReady : std_logic; + signal O_HSync : std_logic; + signal O_VSync : std_logic; +begin + + VGATimingGenerator_inst : entity work.VGATimingGenerator + port map + ( + I_CLK => I_CLK, + I_CE => I_CE, + I_RST => I_RST, + O_PixelReady => O_PixelReady, + O_HSync => O_HSync, + O_VSync => O_VSync + ); + + I_CLK <= not I_CLK after clk_period/2; + + process + begin + wait for 100 ms; + I_RST <= '0'; + wait for 500 ms; + I_RST <= '1'; + wait for 100 ms; + I_RST <= '0'; + wait; + end process; + +end; \ No newline at end of file diff --git a/tests/VGATimingGenerator_test_tb.vhd b/tests/VGATimingGenerator_test_tb.vhd new file mode 100644 index 0000000..0ec52c5 --- /dev/null +++ b/tests/VGATimingGenerator_test_tb.vhd @@ -0,0 +1,33 @@ + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +entity VGATimingGenerator_test_tb is +end; + +architecture bench of VGATimingGenerator_test_tb is + -- Clock period + constant clk_period : time := 20 ns; + -- Generics + -- Ports + signal I_CLK : std_logic := '0'; + signal O_HSync : std_logic; + signal O_VSync : std_logic; + signal O_Red : std_logic_vector(2 downto 0); + signal O_Green : std_logic_vector(2 downto 0); + signal O_Blue : std_logic_vector(1 downto 0); +begin + + VGATimingGenerator_test_inst : entity work.VGATimingGenerator_test + port map ( + I_CLK => I_CLK, + O_HSync => O_HSync, + O_VSync => O_VSync, + O_Red => O_Red, + O_Green => O_Green, + O_Blue => O_Blue + ); + I_CLK <= not I_CLK after clk_period/2; + +end; \ No newline at end of file diff --git a/tests/default.wcfg b/tests/default.wcfg new file mode 100644 index 0000000..9ea8e2c --- /dev/null +++ b/tests/default.wcfg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + i_clk + i_clk + + + i_ce + i_ce + + + i_rst + i_rst + + + o_pixelready + o_pixelready + + + o_hsync + o_hsync + + + o_vsync + o_vsync + +