ASCIIMath creating images

Friday, October 7, 2011

Playing with VHDL

On my old website there are two projects using VHDL on a Spartan-3e FPGA.  Recently, I decided to get back to playing with that again.  The project goal this time was to produce a VGA output - in FPGA circles, this is by now the equivalent of a "Hello, World!" program.  Nothing new, but I felt it was important to do from scratch without copying any bits so I see how the whole thing fits together.
The Spartan 3e "S3E Sample Pack" board, a freebie I picked up a few years back.
The picture shows the board and the VGA connector: with only 4 I/O pins on one connector, I made it a monochrome (green) only interface.  The pins used are horizontal sync, vertical sync, and 2 bits of video signal.  With a bit of soldering I could make it monochrome grey, or I could add another connector to get more output lines to get real color.  For playing with synchronization, the quick and dirty adapter giving 4 intensities of green (well, 3 green plus black) is quite sufficient.  (...more...)

The design is very straightforward.  I wanted it to be modular and configurable, with the least duplication of code.  With minimal modifications it should be able to generate 800x600 instead of 640x480.  The only inputs (for now) are a clock signal and a reset signal.  I did want to have the current row and column available to me so I can generate a pattern.
System overview, reset signal not shown.

The column and row sync generators are basically the same.  For a given number of input clock cycles (for the row or vertical sync generator, the horizontal sync is the clock), pull the sync signal low, wait for a short time (the back porch), then have the signal displayed, then allow for a short time before going to the next line/frame (the front porch).

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity subsync is
  Generic ( 
    sync : integer;
    bporch : integer;
    active : integer;
    total : integer );
  Port ( 
    clock : in  STD_LOGIC;
    reset : in  STD_LOGIC;
    outsig : out  STD_LOGIC;
    inhibit : out  STD_LOGIC;
    outcount : out  STD_LOGIC_VECTOR (9 downto 0));
end subsync;

architecture Behavioral of subsync is

  signal counter : integer range 0 to total;
  signal subcount : std_logic_vector (9 downto 0);

begin
  process (reset, clock, counter)
  begin
    if reset = '1' then
      counter <= 0;
      inhibit <= '1';
      outsig <= '1';
    else
      if rising_edge(clock) then
        if counter < sync then
          outsig <= '0';
          counter <= counter+1;
        elsif counter < sync+bporch-1 then
          outsig <= '1';
          subcount <= "1111111111";  -- start at -1
          counter <= counter+1;
        elsif counter < sync+bporch+active-1 then
          subcount <= subcount+'1';
          counter <= counter+1;
          inhibit <= '0';
        elsif counter < total-1 then
          counter <= counter+1;
          inhibit <= '1';
        else
          counter <= 0;
        end if;
      end if;
    end if;
  end process;
  
  outcount <= subcount;
end Behavioral;

Note the sync signal is negative, for standard VGA resolutions.  Also note that the "inhibit" signal could be called "NOT active".  This sybsync block is instantiated twice in the higher block, which also defines the timing; for the horizontal sync, timing is in clock cycles, for the vertical sync, in lines.

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity VGAsync is
  Generic ( 
    h_sync : integer := 95;
    h_bporch : integer := 10;
    h_active : integer := 640;
    h_total : integer := 800;
    v_sync : integer := 2;
    v_bporch : integer := 25;
    v_active : integer := 480;
    v_total : integer := 525);      
  Port ( 
    clock : in  STD_LOGIC;
    Hsync : out  STD_LOGIC;
    Vsync : out  STD_LOGIC;
    reset : in  STD_LOGIC;
    inhibit : out  STD_LOGIC;
    row : out  STD_LOGIC_VECTOR (9 downto 0);
    col : out  STD_LOGIC_VECTOR (9 downto 0));
end VGAsync;

architecture Behavioral of VGAsync is

  signal local_hsync : std_logic;
  signal h_inhibit : std_logic;
  signal v_inhibit : std_logic;

  component subsync is
    generic ( 
      sync : integer;
      bporch : integer;
      active : integer;
      total : integer );
    port ( 
      clock : in std_logic;
      reset : in std_logic;
      outsig : out std_logic;
      inhibit : out std_logic;
      outcount : out std_logic_vector (9 downto 0));
  end component;

begin

  hs : subsync 
    generic map ( 
      sync => h_sync, bporch => h_bporch, active => h_active, total => h_total )
    port map ( 
      clock => clock, reset => reset, outsig => local_hsync, 
      inhibit => h_inhibit, outcount => col );

  vs : subsync 
    generic map ( 
      sync => v_sync, bporch => v_bporch, active => v_active, total => v_total )
    port map (  
      clock => local_hsync, reset => reset, outsig => Vsync, 
      inhibit => v_inhibit, outcount => row );

  inhibit <= h_inhibit or v_inhibit;
  Hsync <= local_hsync;

end Behavioral;

The only logic in this block is to combine the two inhibit signals.  Finally, there is the top block, with a quickly thrown together test pattern generator:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
use IEEE.NUMERIC_STD.ALL;

entity VGAtop is
  Port ( 
    MemAddress : out  STD_LOGIC_VECTOR (22 downto 1);
    MemData : in  STD_LOGIC_VECTOR (15 downto 0);
    MemOE : out  STD_LOGIC;
    VGA_HSync : out  STD_LOGIC;
    VGA_VSync : out  STD_LOGIC;
    VGA_out : out  STD_LOGIC_VECTOR (1 downto 0);
    clock_in : in  STD_LOGIC;
    reset : in  STD_LOGIC);
end VGAtop;

architecture Behavioral of VGAtop is

component VGAsync
  generic ( 
    h_sync : integer := 95;
    h_bporch : integer := 10;
    h_active : integer := 640;
    h_total : integer := 800;
    v_sync : integer := 2;
    v_bporch : integer := 25;
    v_active : integer := 480;
    v_total : integer := 525);
  port ( 
    clock : in  STD_LOGIC;
    Hsync : out  STD_LOGIC;
    Vsync : out  STD_LOGIC;
    reset : in  STD_LOGIC;
    inhibit : out  STD_LOGIC;
    row : out  STD_LOGIC_VECTOR (9 downto 0);
    col : out  STD_LOGIC_VECTOR (9 downto 0));
end component;

signal clk_25: std_logic;
signal col: std_logic_vector (9 downto 0);
signal row: std_logic_vector (9 downto 0);
signal sub_row: std_logic_vector (3 downto 0);
signal inhibit: std_logic; 
signal VGA: std_logic_vector (1 downto 0);

begin

  -- instantiate the sync block that outputs row and column
  sync_gen : VGAsync 
  generic map (
    h_bporch => 30
  )
  port map (
    clock => clk_25, Hsync => VGA_HSync,
    Vsync => VGA_Vsync, reset => reset,
    inhibit => inhibit, row => row,
    col => col 
  );

  -- divide external clock 50 MHz to 25 MHz.
  process (clock_in, reset)
  begin
    if reset = '1' then
      clk_25 <= '0';
    else
      if rising_edge(clock_in) then
        clk_25 <= not clk_25;
      end if;
    end if;
  end process;

  -- modify the VGA output signal based on row and col counters
  sub_row <= row(3 downto 0);
  process(row, col)
  begin
    if row(7 downto 6) = "01" then
      VGA <= col(6 downto 5);
    else
      case sub_row is
        when "0000" => VGA(1) <= '1';
        when "0001" => VGA(1) <= col(0);
        when "0010" => VGA(1) <= col(1);
        when "0011" => VGA(1) <= col(2);
        when "0100" => VGA(1) <= col(3);
        when "0101" => VGA(1) <= col(4);
        when "0110" => VGA(1) <= col(5);
        when "0111" => VGA(1) <= col(6);
        when "1000" => VGA(1) <= col(7);
        when "1001" => VGA(1) <= col(8);
        when "1010" => VGA(1) <= '0';
        when others => VGA(1) <= col(0) xor row(0);
      end case;
      VGA(0) <= row(4);
    end if;
  end process;
  
  VGA_out(1) <= VGA(1) and not inhibit;
  VGA_out(0) <= VGA(0) and not inhibit;
  
end Behavioral;

The final result looks like this.
test setup, the FPGA generating a pattern on a LCD screen.
Note that in the instantiation of the VGAsync component, I override the horizontal back porch value to make it a bit longer.  I got the VGA timing information from this site, but found it didn't work too well with LCDs, cutting the image off at the left-hand side.  Increasing that value fixed it, moving the active time a little bit out from the sync pulse.

The major problem I found with my setup is a very jittery image.  I don't have sensitive enough equipment to be completely sure of the cause, but it may be due to the power supply (I can reduce the jitter by using a battery instead) and hum from all the equipment around.  It's an annoying artefact, but does not invalidate the design.

Next, I want to use this design to display bitmaps from the built-in flash memory.  As it turns out the column counter may not be so useful for this. To display a pixel, I need to latch its value BEFORE the beam reaches the position; it is probably easier to use the "inhibit" and clk_25 signals to clock out pixels directly.  The row counter can still be useful to load the appropriate address at the beginning of the line.

No comments:

Post a Comment