---------------------------------------------------------------------------
-- SRAM memory controller
---------------------------------------------------------------------------
-- This file is a part of "Aeon Lite" project
-- Dmitriy Schapotschkin aka ILoveSpeccy '2014
-- ilovespeccy@speccyland.net
-- Project homepage: www.speccyland.net
---------------------------------------------------------------------------

LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.numeric_std.all;

ENTITY sram_statemachine IS
PORT ( 
   CLK               : in     std_logic;
   RESET_N           : in     std_logic;
	
   DATA_IN           : in     std_logic_vector(31 downto 0);
   ADDRESS_IN        : in     std_logic_vector(22 downto 0);
   WRITE_EN          : in     std_logic;
   REQUEST           : in     std_logic;
   BYTE_ACCESS       : in     std_logic; -- ldqm/udqm set based on a(0) - if 0=0111, if 1=1011. Data fields valid:7 downto 0.
   WORD_ACCESS       : in     std_logic; -- ldqm/udqm set based on a(0) - if 0=0011, if 1=1001. Data fields valid:15 downto 0.
   LONGWORD_ACCESS   : in     std_logic; -- a(0) ignored. lqdm/udqm mask is 0000
   COMPLETE          : out    std_logic;
   DATA_OUT          : out    std_logic_vector(31 downto 0);

   SRAM_ADDR         : out    std_logic_vector(17 downto 0);
   SRAM_DQ           : inout  std_logic_vector(15 downto 0);
   SRAM_WE_N         : out    std_logic;
   SRAM_OE_N         : out    std_logic;
   SRAM_UB_N         : out    std_logic;
   SRAM_LB_N         : out    std_logic;
   SRAM_CE0_N        : out    std_logic;
   SRAM_CE1_N        : out    std_logic );
END sram_statemachine;

ARCHITECTURE vhdl OF sram_statemachine IS

	function REPEAT(N: natural; B: std_logic) 
      return std_logic_vector
	is
      variable RESULT: std_logic_vector(1 to N);
	begin
      for i in 1 to N loop
         RESULT(i) := B;
      end loop;
      return RESULT;
	end;
   
   signal SRAM_DI          : std_logic_vector(15 downto 0);
   signal SRAM_DO          : std_logic_vector(15 downto 0);
   signal DATA_OUT_REG     : std_logic_vector(31 downto 0);
   signal MASK             : std_logic_vector(3 downto 0);
   signal ADDRESS_IN_NEXT  : std_logic_vector(18 downto 0);
   
   type STATES is (ST_IDLE, ST_READ0, ST_READ1, ST_READ2, ST_WRITE0, ST_WRITE1, ST_WRITE2);
   signal STATE : STATES;

BEGIN

   SRAM_DQ <= SRAM_DI;
   SRAM_DO <= SRAM_DQ;

   DATA_OUT <= DATA_OUT_REG;
   ADDRESS_IN_NEXT <= std_logic_vector(unsigned(ADDRESS_IN(19 downto 1)) + 1);
   
   COMPLETE <= '1' when STATE = ST_IDLE and REQUEST = '0' else '0';

   process(CLK, RESET_N)
   begin
      if RESET_N = '0' then
         SRAM_DI <= (OTHERS=>'Z');
         SRAM_WE_N <= '1';
         SRAM_OE_N <= '1';
         SRAM_CE0_N <= '1';
         SRAM_CE1_N <= '1';
         SRAM_LB_N <= '1';
         SRAM_UB_N <= '1';
         STATE <= ST_IDLE;
      else
         if rising_edge(CLK) then
         
            case STATE is
               when ST_IDLE =>

                  SRAM_DI <= (OTHERS=>'Z');
                  SRAM_WE_N <= '1';
                  SRAM_OE_N <= '1';
                  SRAM_LB_N <= '1';
                  SRAM_UB_N <= '1';

                  if REQUEST = '1' then
                  
                     MASK(0) <= (BYTE_ACCESS or WORD_ACCESS) and ADDRESS_IN(0);      -- masked on misaligned byte or word
                     MASK(1) <= (BYTE_ACCESS) and not(address_in(0));                -- masked on aligned byte only
                     MASK(2) <= BYTE_ACCESS or (WORD_ACCESS and not(ADDRESS_IN(0))); -- masked on aligned word or byte
                     MASK(3) <= not(LONGWORD_ACCESS);                                -- masked for everything except long word access                     

                     SRAM_ADDR <= ADDRESS_IN(18 downto 1);
                     
                     SRAM_CE0_N <= ADDRESS_IN(19);
                     SRAM_CE1_N <= not ADDRESS_IN(19);

                     if WRITE_EN = '1' then
                        STATE <= ST_WRITE0;
                     else
                        STATE <= ST_READ0;
                     end if;
                  end if;  

               when ST_WRITE0 =>
                  SRAM_LB_N <= MASK(0);
                  SRAM_UB_N <= MASK(1);
                  SRAM_DI(7 downto 0) <= DATA_IN(7 downto 0);
                  SRAM_DI(15 downto 8) <= (DATA_IN(15 downto 8) and not(repeat(8,MASK(0)))) or (DATA_IN(7 downto 0) and repeat(8,MASK(0)));
                  SRAM_WE_N <= '0';
                  STATE <= ST_WRITE1;             

               when ST_WRITE1 =>
                  SRAM_WE_N <= '1';
                  STATE <= ST_WRITE2;             

               when ST_WRITE2 =>
                  SRAM_ADDR <= ADDRESS_IN_NEXT(17 downto 0);
                  SRAM_CE0_N <= ADDRESS_IN_NEXT(18);
                  SRAM_CE1_N <= not ADDRESS_IN_NEXT(18);
                  SRAM_DI(7 downto 0) <= (DATA_IN(23 downto 16) and not(repeat(8,MASK(0)))) or (DATA_IN(15 downto 8) and repeat(8,MASK(0)));
                  SRAM_DI(15 downto 8) <= DATA_IN(31 downto 24);
                  SRAM_LB_N <= MASK(2);
                  SRAM_UB_N <= MASK(3);
                  SRAM_WE_N <= '0';
                  STATE <= ST_IDLE;
   
               when ST_READ0 =>
                  SRAM_LB_N <= MASK(0);
                  SRAM_UB_N <= MASK(1);
                  SRAM_OE_N <= '0';
                  STATE <= ST_READ1;

               when ST_READ1 =>
                  DATA_OUT_REG(7 downto 0) <= (SRAM_DO(7 downto 0) and not(repeat(8,MASK(0)))) or (SRAM_DO(15 downto 8) and repeat(8,MASK(0)));
                  DATA_OUT_REG(15 downto 8) <= SRAM_DO(15 downto 8); 
                  SRAM_ADDR <= ADDRESS_IN_NEXT(17 downto 0);
                  SRAM_CE0_N <= ADDRESS_IN_NEXT(18);
                  SRAM_CE1_N <= not ADDRESS_IN_NEXT(18);
                  SRAM_LB_N <= MASK(2);
                  SRAM_UB_N <= MASK(3);
                  STATE <= ST_READ2;

               when ST_READ2 =>
                  DATA_OUT_REG(15 downto 8 ) <= (SRAM_DO(7 downto 0) and repeat(8,MASK(0))) or (DATA_OUT_REG(15 downto 8) and not(repeat(8,MASK(0))));
                  DATA_OUT_REG(31 downto 16) <= SRAM_DO(15 downto 0);
                  STATE <= ST_IDLE;

               when OTHERS =>
                  STATE <= ST_IDLE;
               
            end case;
         end if;
      end if;
   end process;

END vhdl;
