Papilio Plus/SRAM test

From Hamsterworks Wiki!

Jump to: navigation, search

This Papilio Plus project is the second most complex of the test suite - it generates an 800x600x256 colour signal on the VGA port of the Arcade Megawing.

The memory timing implemented does now has a spare cycles for reading - allowing one byte write and one byte read every 50us (for 40MB/sec) in addition to the 40MB/s for the Video.

Contents

Important note

Timing is very tricky - you need to have 'FAST' set on the MEM_nWE constraint to ensure that the WE pulse finished before the data or address buses change (they should be set to slow). You should also enable the "Pack the I/O Latches/Registers into IOBs" option to get crisp signals.

VHDL Source

memory_video.vhd

This is the combined VGA/memory controller.

Supply with a 80MHz on 'clk' and its inverse on clkn.

Connect hsync, vsync and colour to the display (you may want to pass colour through a 8bit to 12bit colour pallet lookup)

Present the write_addr and write_byte in the same cycle, and hold until "write_taken" is set for one cycle.


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

Library UNISIM;
use UNISIM.vcomponents.all;


entity memory_video is
    Port ( clk : in  STD_LOGIC;
           clkn : in  STD_LOGIC;
           
           base_addr_word : in STD_LOGIC_VECTOR (17 downto 0);
           
           -- Interface for writes
           write_ready : in  STD_LOGIC;
           write_addr  : in  STD_LOGIC_VECTOR (18 downto 0);
           write_byte  : in  STD_LOGIC_VECTOR (7 downto 0);
           write_taken : out  STD_LOGIC;
           
           -- Video interface
           hsync       : out  STD_LOGIC;
           vsync       : out  STD_LOGIC;
           colour      : out  STD_LOGIC_VECTOR (7 downto 0);
           
           -- Memory Interface
           mem_nCE      : out   STD_LOGIC;
           mem_nWE      : out   STD_LOGIC;
           mem_nOE      : out   STD_LOGIC;
           mem_addr     : out   STD_LOGIC_VECTOR (17 downto 0);
           mem_data     : inout STD_LOGIC_VECTOR (15 downto 0));
end memory_video;

architecture Behavioral of memory_video is
   -- State vectors and memory controll signals                  
   constant state_0    : STD_LOGIC_VECTOR(1 downto 0) := "00";
   constant state_25   : STD_LOGIC_VECTOR(1 downto 0) := "01";
   constant state_50   : STD_LOGIC_VECTOR(1 downto 0) := "10";
   constant state_75   : STD_LOGIC_VECTOR(1 downto 0) := "11";

   type regv is record
      colour      : std_logic_vector( 7 downto 0);
      hCounter    : std_logic_vector(10 downto 0); -- 0 -> hMax-1
      vCounter    : std_logic_vector( 9 downto 0); -- 0 -> vMax-1
      read_addr   : std_logic_vector(17 downto 0);
      increment   : std_logic;
      display     : std_logic;
      hSync       : std_logic;
      vSync       : std_logic;
   end record;

   signal nv : regv;
   signal rv : regv := ((others => '1'),(others => '0'),(others => '0'),(others => '0'),'0','0','0','0');

   type regm is record
      state       : std_logic_vector( 1 downto 0);
      address     : std_logic_vector(17 downto 0);
      newValue    : std_logic_vector(15 downto 0);
      latch : std_logic_vector(15 downto 0);
      hold_byte   : std_logic_vector( 7 downto 0);
      hold_addr   : std_logic_vector(18 downto 0);
      write_taken : std_logic;
      nOE         : std_logic;
      nWE         : std_logic_vector(1 downto 0);
   end record;

   signal nm : regm;
   signal rm : regm := (state_0,(others => '0'),(others => '0'),(others => '0'),(others => '0'),(others => '0'), '0', '0', "11");
   signal data_latch : std_logic_vector(15 downto 0);

   -- VGA timings for 800x600 @ 60Hz
   constant hVisible    : natural := 800;
   constant hFrameWidth : natural := 828;
   constant hStartSync  : natural := 840;
   constant hEndSync    : natural := 928;
   constant hMax        : natural := 1056;

   constant vVisible   : natural := 600;
   constant vStartSync : natural := 601;
   constant vEndSync   : natural := 605;
   constant vMax       : natural := 628;
begin
   -- Mapping through output signals
   mem_nCE     <= '0';
   mem_nOE     <= rm.nOE;
   write_taken <= rm.write_taken;
   mem_addr    <= rm.address; 

   hsync       <= rv.hsync;
   vsync       <= rv.vsync;
   colour      <= rv.colour+1;
  
   ODDR2_nWE : ODDR2
   generic map(
      DDR_ALIGNMENT => "C0",    -- Sets output alignment to "NONE", "C0", "C1" 
      INIT          => '1',     -- Sets initial state of the Q output to '0' or '1'
      SRTYPE        => "ASYNC") -- Specifies "SYNC" or "ASYNC" set/reset
   port map (
      Q  => mem_nWE,     -- 1-bit output data
      C0 => clk,         -- 1-bit clock input
      C1 => clkn,        -- 1-bit clock input
      CE => '1',         -- 1-bit clock enable input
      D0 => nm.nWE(0),   -- 1-bit data input (associated with C0)
      D1 => nm.nWE(1),   -- 1-bit data input (associated with C1)
      R  => '0',         -- 1-bit reset input
      S  => '0'          -- 1-bit set input
   );
   
   -- This process controls when data gets presented to the data bus
tristate_proc: process(rm)
   begin
      if rm.state = state_25 then 
         mem_data <= rm.newValue;
      else
         mem_data <= "ZZZZZZZZZZZZZZZZ";
      end if;
   end process;

  -- This process does all the VGA output
vga_proc: process(rv, rm.latch, rm.state,  base_addr_word)
   begin
      nv           <= rv;
      nv.hsync     <= '0';
      nv.vsync     <= '0';
      nv.increment <= '0';
      
      -- Sync pulse generation
      if rv.hCounter >= hStartSync and rv.hCounter < hEndSync  then
         nv.hsync <= '1';   
      end if;
      
      if rv.vCounter >= vStartSync and rv.vCounter < vEndSync  then
         nv.vsync <= '1';
      end if;

      -- Will the next pass thorugh be in the blanking interval?
      if rv.hcounter < hFrameWidth and rv.vcounter < vVisible then 
         nv.increment  <= '1';
      end if;

      if rm.state = state_25 or rm.state = state_75 then
         -- Update the counters every other cycle
         if rv.hCounter = hMax-1 then
            nv.hCounter <= (others => '0');
            if rv.vcounter = vMax-1 then 
               nv.vCounter  <= (others => '0');
               nv.read_addr <= base_addr_word + (14 * hFrameWidth + 14)/2;
            else
               nv.vCounter <= rv.vCounter+1;
            end if;
         else
            nv.hCounter <= rv.hCounter + 1; 
         end if;
      else
         -- Update the display RGB values every other cycle
         if rv.display = '1' then
            if rm.state = state_0 or rm.state = state_25 then 
               nv.colour  <= rm.latch(15 downto 8);
            else
              nv.colour  <= rm.latch(7 downto 0);
            end if;
         else
            nv.colour  <= (others => '0');
         end if;
      end if;

      -- Decide if we display colour this time
      if rm.state(1 downto 0) = "11" then
         if rv.hCounter < hVisible and rv.vCounter < vVisible then
            nv.display    <= '1';
         else
            nv.display    <= '0';         
         end if;
         
         if rv.increment = '1' then
            nv.read_addr  <= rv.read_addr+1;
         end if;
      end if;
   end process;

   -- This process updates the SRAM
mem_proc: process(rm, rv.read_addr, write_ready, write_addr, write_byte, data_latch, base_addr_word)
   begin
      nm             <= rm;
      nm.write_taken <= '0';  -- Only set in one state
      nm.nWE         <= "11";
      nm.noe         <= '0';

      case rm.state is 
      when state_0  => nm.state <= state_25;
      when state_25 => nm.state <= state_50;
      when state_50 => nm.state <= state_75;
      when others   => nm.state <= state_0;
      end case;

      case rm.state is 
      when state_0 =>
         -- SETUP : the write of the byte to be updated
         nm.address  <= rm.hold_addr(18 downto 1);
         nm.nWE      <= "01";
         nm.nOE      <= '0';

         -- In progress : Video read

         -- ENDING : the read before write
         if rm.hold_addr(0) = '0' then
            nm.newValue <= data_latch(15 downto 8) & rm.hold_byte;
         else
            nm.newValue <= rm.hold_byte & data_latch(7 downto 0);
         end if;

      when state_25 =>
         -- SETUP : Idle after read
         nm.nOE      <= '0';
         nm.address  <= (others => '0');

         -- In progress : Write 1
         
         -- ENDING : Video read
           nm.latch     <=  data_latch; 

      when state_50 =>
         -- SETUP : Read before write
         if write_ready = '0' then
            nm.address     <= write_Addr(18 downto 1)+ base_addr_word;
            nm.hold_addr   <= write_Addr+(base_addr_word&'0');
            nm.hold_byte   <= write_byte;
            nm.write_taken <= '1';
         else
            -- Just repeat the prior write
            nm.address  <= rm.hold_addr(18 downto 1);         
         end if;
         
         -- In progress : Idle

         -- ENDING : Write

      when others => -- state_75
         -- SETUP Video read
         nm.address    <= rv.read_addr;

         -- In progress : Read before write

         -- ENDING Idle

      end case;
   end process;
   
clk_proc: process (clk)
   begin
      if rising_edge(clk) then
         data_latch <= mem_data;
         rm <= nm;
         rv <= nv;
      end if;
   end process;
end Behavioral;

memory_tester.vhd

Here is a simple test module - it writes write the lowest 8 bits of the horizontal address to all the bytes from 0 through 0x3FFFF.

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

entity mem_tester is
    Port ( clk : in  STD_LOGIC;
           address : out  STD_LOGIC_VECTOR (18 downto 0);
           data : out  STD_LOGIC_VECTOR (7 downto 0);
           write_taken : in  STD_LOGIC);
end mem_tester;

architecture Behavioral of mem_tester is
   signal counter: std_logic_vector(18 downto 0) := (others => '0');
   signal value: std_logic_vector(9 downto 0) := (others => '0');
begin
   address <= counter;
   data    <= value(7 downto 0);
   
   process (clk)
   begin
      if rising_edge(clk) then
         if write_taken = '1' and not(counter  = "111" & x"FFFF") then
            counter <= counter+1;
            if value = 827 then 
               value <= (others => '0');
            else
               value <= value+1;
            end if;
         end if;
      end if;
   end process;
end Behavioral;

mem_tester_top.vhd

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity mem_tester_top is
    Port ( clk_32 : in  STD_LOGIC;
           hsync  : out  STD_LOGIC;
           vsync  : out  STD_LOGIC;
           red    : out  STD_LOGIC_VECTOR (3 downto 0);
           green  : out  STD_LOGIC_VECTOR (3 downto 0);
           blue   : out  STD_LOGIC_VECTOR (3 downto 0);
                      -- Memory Interface
           mem_nBE      : out   STD_LOGIC;
           mem_nCE      : out   STD_LOGIC;
           mem_nWE      : out   STD_LOGIC;
           mem_nOE      : out   STD_LOGIC;
           mem_addr     : out   STD_LOGIC_VECTOR (17 downto 0);
           mem_data     : inout STD_LOGIC_VECTOR (15 downto 0);
           -- User constrols
         btn_up            : in  STD_LOGIC;
         btn_down        : in  STD_LOGIC;
         btn_left        : in  STD_LOGIC;
         btn_right        : in  STD_LOGIC;
         btn_zoom     : in  STD_LOGIC);
end mem_tester_top;

architecture Behavioral of mem_tester_top is
component clocking
   port
   (-- Clock in ports
      CLK_32           : in     std_logic;
      -- Clock out ports
      CLK_mem          : out    std_logic; -- Memory / Video clock (40MHz)
      CLK_memn         : out    std_logic; -- Inverted Memory / Video clock (40MHz)
      CLK_core          : out    std_logic -- Clock for other use.
   );
   end component;

   COMPONENT mem_tester
   PORT(
      clk : IN std_logic;
      write_taken : IN std_logic;          
      address : OUT std_logic_vector(18 downto 0);
      data : OUT std_logic_vector(7 downto 0)
      );
   END COMPONENT;

   COMPONENT memory_video
   PORT(
      clk : IN std_logic;
      clkn : IN std_logic;
      base_addr_word : in STD_LOGIC_VECTOR (17 downto 0);

      write_ready : IN std_logic;
      write_addr : IN std_logic_vector(18 downto 0);
      write_byte : IN std_logic_vector(7 downto 0);    
      mem_data : INOUT std_logic_vector(15 downto 0);      
      write_taken : OUT std_logic;
      hsync : OUT std_logic;
      vsync : OUT std_logic;
      colour : OUT std_logic_vector(7 downto 0);
      mem_nCE : OUT std_logic;
      mem_nWE : OUT std_logic;
      mem_nOE : OUT std_logic;
      mem_addr : OUT std_logic_vector(17 downto 0)
      );
   END COMPONENT;


   signal write_address: std_logic_vector(18 downto 0);
   signal write_data:    std_logic_vector(7 downto 0);
   signal colour:        std_logic_vector(7 downto 0);
   signal write_taken:   std_logic;
   signal clk_mem:        std_logic;
   signal clk_memn:       std_logic;
   signal clk_core:       std_logic;
   
begin
   mem_nBE <= '0';
   red   <= colour(7 downto 5) & '0';
   green <= colour(4 downto 2) & '0';
   blue  <= colour(1 downto 0) & "00";

   clocking_inst : clocking port map (
      -- Clock in ports
      CLK_32  => CLK_32,
      -- Clock out ports
      CLK_mem  => CLK_mem,
      CLK_memn => CLK_memn,
      CLK_core  => CLK_core
   );
   
   Inst_mem_tester: mem_tester PORT MAP(
      clk         => clk_mem,
      address     => write_address,
      data        => write_data,
      write_taken => write_taken
   );

   Inst_memory_video: memory_video PORT MAP(
      clk  => clk_mem,
      clkn  => clk_memn,
      base_addr_word => (others => '0'),
      write_ready => '0',
      write_addr => write_address,
      write_byte => write_data,
      write_taken => write_taken,
      hsync => hsync,
      vsync => vsync,
      colour => colour,
      mem_nCE => mem_nCE,
      mem_nWE => mem_nWE,
      mem_nOE => mem_nOE,
      mem_addr => mem_addr,
      mem_data => mem_data
   );

end Behavioral;

Personal tools