Forum Discussion

Altera_Forum's avatar
Altera_Forum
Icon for Honored Contributor rankHonored Contributor
11 years ago

Digitize RGB video using DE0-Nano

Hi all,

I'm building a 15Khz RGB to VGA converter using the DE0-Nano, to be used with old computers from the 80's. I know there are some boards in the market like GBS8200 but I'm not happy with them, then I decided to make my very own and learn FPGA.

So far I was able to digitize the signal into an internal RAM array of 640x204 pixels using 1-bit color, thus 8 colors for now. Once I solve all the problems I will go using an external static RAM (the SDRAM seemed to slow in my tests) for more color depth. Look the video, it's already working:

https://www.youtube.com/watch?v=pcyzlljwaea

Basically I digitize every incoming pixel using a 12.58Mhz PLL and output it using a 25.175Mhz PLL (vga pixel clock). It's working but I get jitter on the image due to my 12.58Mhz clocks is not synched with the external HSYNC input signal. HSYNC is not a clock signal, but a pulse that happens each 15Khz.

My question is how could I sync my PLL to the same HSYNC phase ? What could I do ? I don't want to use any other video IC, I want to build my own digitizer and implement very special features for each type of computer.

Here the top schematic for little more understanding:

http://i.imgur.com/hrbqyup.png?1

Thanks,

6 Replies

  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    You won't be able to use HSYNC to directly sync your PLL.

    I assume (with your current implementation) you are adjusting the outgoing data, by a full clock cycle, every time you detect a 'change' to the incoming HSYNC pulse timing.

    You need to 'hide' these timing changes in the VSYNC inter frame gap - the dead time between frames. Providing you deliver a full frame of video without adjusting your outgoing data and HSYNC timing your jitter should disappear.

    Cheers,

    Alex
  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    --- Quote Start ---

    I assume (with your current implementation) you are adjusting the outgoing data, by a full clock cycle, every time you detect a 'change' to the incoming HSYNC pulse timing.

    --- Quote End ---

    I will copy the full code here later when I get home. But basically I have a framebuffer and I control the incoming hcount and vcount by waiting the hsync and vsync (they are positive pulses in this case) like

    process(CLOCK_25)

    begin

    if (rising_edge(CLOCK_25)) then

    hblank <= '1';

    if (HSYNC_IN = '1' and hcount >= 655) then

    hblank <= '0';

    end if;

    end if;

    end process;

    process(CLOCK_25)

    begin

    if (rising_edge(CLOCK_25)) then

    if (hblank = '0') then

    hcount <= 0;

    else

    hcount <= hcount + 1;

    end if;

    end if;

    end process;

    Being CLOCK_25 the 12Mhz clock. Then I use hcount together the vcount to calculate the offset in the pixel array to store it.

    The VGA output has it's own 25Mhz clock and since it's buffered, it doesn't rely on the incoming hsync, vsync. It just pumps out whatever is in the buffer.
  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    here my code. Look that I force the HSYNC to match the end of the line: 655 (for the sync) and the vsync to match the last scanline 262.5. CLOCK_50 = 25.17Mhz and CLOCK_25=12.58Mhz. I got less jitter using 25Mhz for the horizontal counter:

    library IEEE;

    use IEEE.STD_LOGIC_1164.ALL;

    use IEEE.NUMERIC_STD.ALL;

    entity vgaoutput is

    generic(

    front_porch : integer := 100;

    top_border : integer := 35

    );

    port(CLOCK_50 : in std_logic;

    CLOCK_25 : in std_logic;

    VSYNC_IN : in std_logic;

    HSYNC_IN : in std_logic;

    RGB_IN : in unsigned(2 downto 0); -- r, g, b

    VGA_OUT : out unsigned(4 downto 0) -- r, g, b, hsync, vsync

    );

    end vgaoutput;

    architecture behavioral of vgaoutput is

    signal hcount : unsigned(9 downto 0) := (others => '0');

    signal vcount : unsigned(9 downto 0) := (others => '0');

    signal pixelcolor : unsigned(2 downto 0) := "000";

    signal videoon, videov, videoh, hsync, vsync : std_ulogic := '0';

    signal reset : std_ulogic := '1';

    signal vblank : std_ulogic := '1';

    signal hblank : std_ulogic := '1';

    signal hcount_in : unsigned(10 downto 0) := (others => '0');

    signal vcount_in : unsigned(9 downto 0) := (others => '0');

    type line_memory_t is array (0 to 640*204-1) of unsigned(2 downto 0);

    shared variable line_memory : line_memory_t;

    begin

    -----------------------------------------

    -- RGB INPUT

    -----------------------------------------

    vsyncin: process(CLOCK_25)

    begin

    if (rising_edge(CLOCK_25)) then

    vblank <= '1';

    if (VSYNC_IN = '1' and vcount_in >= 262 and hcount_in(10 downto 1) >= 399) then

    vblank <= '0';

    end if;

    end if;

    end process;

    hsyncin: process(CLOCK_50)

    begin

    if (rising_edge(CLOCK_50)) then

    hblank <= '1';

    if (HSYNC_IN = '1' and hcount_in(10 downto 1) >= 655) then

    hblank <= '0';

    end if;

    end if;

    end process;

    hcounter_in: process(CLOCK_50)

    begin

    if (rising_edge(CLOCK_50)) then

    if hblank = '0' then

    hcount_in <= (others => '0');

    else

    hcount_in <= hcount_in + 1;

    end if;

    end if;

    end process;

    vcounter_in: process (CLOCK_25)

    begin

    if(rising_edge(CLOCK_25)) then

    if vblank = '0' then

    vcount_in <= (others => '0');

    elsif hblank = '0' then

    vcount_in <= vcount_in + 1;

    end if;

    end if;

    end process;

    pixel_in: process(CLOCK_25)

    variable addr: integer range 0 to 153600;

    variable row, col: integer range 0 to 153600;

    begin

    if (rising_edge(CLOCK_25)) then

    if (hcount_in(10 downto 1) >= front_porch and hcount_in(10 downto 1) < front_porch+640 and vcount_in >= top_border and vcount_in < top_border+204) then

    col := to_integer(hcount_in(10 downto 1)) - front_porch;

    row := to_integer(vcount_in) - top_border;

    addr := (row * 640) + col;

    line_memory(addr) := RGB_IN;

    end if;

    end if;

    end process;

    -----------------------------------------

    -- VGA OUTPUT

    -----------------------------------------

    vcounter: process (CLOCK_50, hcount) --, vblank)

    begin

    if(rising_edge(CLOCK_50)) then

    if hcount = 799 then

    vcount <= vcount + 1;

    end if;

    if vcount = 525 then

    vcount <= (others => '0');

    end if;

    end if;

    end process;

    v_sync: process(CLOCK_50, vcount)

    begin

    if(rising_edge(CLOCK_50)) then

    vsync <= '1';

    if (vcount <= 492 and vcount >= 490) then

    vsync <= '0';

    end if;

    end if;

    end process;

    hcounter: process (CLOCK_50)

    begin

    if (rising_edge(CLOCK_50)) then

    hcount <= hcount + 1;

    if hcount=799 then

    hcount <= (others => '0');

    end if;

    end if;

    end process;

    h_sync: process (CLOCK_50, hcount)

    begin

    if (rising_edge(CLOCK_50)) then

    hsync <= '1';

    if (hcount <= 752 and hcount >= 655) then

    hsync <= '0';

    end if;

    end if;

    end process;

    pixel_out: process (CLOCK_50)

    variable addr: integer range 0 to 153600;

    variable row, col: integer range 0 to 153600;

    begin

    if (rising_edge(CLOCK_50)) then

    if (hcount < 640 and vcount < 408) then

    col := to_integer(hcount);

    row := to_integer(vcount(9 downto 1));

    addr := (row * 640) + col;

    pixelcolor(2 downto 0) <= line_memory(addr)(2 downto 0);

    end if;

    end if;

    end process;

    process (vcount)

    begin

    videov <= '1';

    if vcount > 479 then

    videov <= '0';

    end if;

    end process;

    process (hcount)

    begin

    videoh <= '1';

    if hcount > 639 then

    videoh <= '0';

    end if;

    end process;

    videoon <= videoh and videov;

    VGA_OUT(4 downto 2) <= pixelcolor and videoon&videoon&videoon;

    VGA_OUT(1 downto 0) <= hsync & vsync;

    end behavioral;

  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    I dont really like the way you've done your frame buffer - I dont see this actually inferring a ram because of your unregistered read and write addresses (not to mention potential poor timing performance, but at 25MHz you can probably get away with it).

    As for the jitter, rather than syncing the input to the output you might be better off just free running the output and inputs and using the frame buffer in the middle to get the data accross. You might get the odd tearing frame but to mitigate you should be able to make the output repeat a frame if the output catches the input.
  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    Your code is re-syncing the outgoing HSYNC with every incoming HSYNC pulse. So, you are seeing inter-row jitter - i.e. the entire row is moving with respect to an adjacent row. This you can see in the video you posted.

    You must re-sync your counters once and only once per VSYNC cycle only. Having done this you must play the data out of your buffer using your internal timers only. This is the 'free running' that Tricky refers to. Don't reset them until the next VSYNC pulse. Any difference in the clock rate will be hidden in the VSYNC dead time, where it doesn't matter.

    Cheers,

    Alex
  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    I managed to get a pixel perfect genlock. The main problem was that my pixel clock wasn't a NTSC clock multiple. I'm running now at 114.54Mhz. I also changed the RAM access using altram 2 port and implemented a SDRAM module to capture the full frame. It's working well as you can see in the video:

    https://www.youtube.com/watch?v=p8typuh1hqq

    But I'm getting noise, specially with some colors red or cyan. I'm grounding the computer, the altera and the VGA output together. Is there anything else I can do ? I tried to space the input pins away across the GPIO_0 banks to avoid cross talk but still the problem remains (maybe less noisy now).

    I also tried to use LVDS inputs to implement fast 2-bit ADC but what I get is even more noise. I was using a resistor ladder for different voltage levels: 1.65, 1.0, 0.4 but LVDS inputs seemed to oscillate. When I outputted the LVDS inputs to the LEDs and I could see the LEDs with a dim light where it was supposed to be off due to the applied voltage.