------------------------------------------------------
-- FSM for a SDRAM controller
--
-- Version 0.1 - Ready to simulate
--
-- Author: Mike Field (hamster@snap.net.nz)
--
-- Feel free to use it however you would like, but
-- just drop me an email to say thanks.
-------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;

entity sdram_controller is
   PORT (
   CLOCK_50      : IN STD_LOGIC;
   
   -- Signals to/from the SDRAM chip
   DRAM_ADDR   : OUT   STD_LOGIC_VECTOR (12 downto 0);
   DRAM_BA      : OUT   STD_LOGIC_VECTOR (1 downto 0);
   DRAM_CAS_N   : OUT   STD_LOGIC;
   DRAM_CKE      : OUT   STD_LOGIC;
   DRAM_CLK      : OUT   STD_LOGIC;
   DRAM_CS_N   : OUT   STD_LOGIC;
   DRAM_DQ      : INOUT STD_LOGIC_VECTOR(15 downto 0);
   DRAM_DQM      : OUT   STD_LOGIC_VECTOR(1 downto 0);
   DRAM_RAS_N   : OUT   STD_LOGIC;
   DRAM_WE_N    : OUT   STD_LOGIC;
   
   --- Inputs from rest of the system
   address      : IN     STD_LOGIC_VECTOR (23 downto 0);
   req_read      : IN     STD_LOGIC;
   req_write   : IN     STD_LOGIC;
   data_out      : OUT     STD_LOGIC_VECTOR (31 downto 0);
   data_out_valid : OUT     STD_LOGIC;
   data_in      : IN     STD_LOGIC_VECTOR (31 downto 0)
   );
end entity;
   
   
architecture rtl of sdram_controller is

   
   type reg is record
      state       : std_logic_vector(8 downto 0);

      address     : std_logic_vector(12 downto 0);
      bank         : std_logic_vector( 1 downto 0);

      init_counter: std_logic_vector(14 downto 0);
      rf_counter   : std_logic_vector( 9 downto 0);
      rf_pending    : std_logic;

      rd_pending    : std_logic;
      wr_pending    : std_logic;
      act_row       : std_logic_vector(12 downto 0);

      data_out_low: std_logic_vector(15 downto 0);
      data_out_valid : std_logic;

      dq_masks      : std_logic_vector(1 downto 0);
   end record;
   component sdram_clk_gen
   PORT
   (
      inclk0: IN  STD_LOGIC;
      c0      : OUT STD_LOGIC;
      c1      : OUT STD_LOGIC
   );
   end component;

   -- note to self - this constant should be "(others => '0')" when not simulating!!!
   signal r : reg := ((others => '0'), (others => '0'), 
                      (others => '0'), "000000000001000", (others => '0'), 
                      '0', '0', '0', (others => '0'), (others => '0'), '0', (others => '0'));
   signal n : reg;
   
   -- Vectors for each SDRAM 'command'
   --- CS_N, RAS_N, CAS_N, WE_N 
   constant cmd_nop   : std_logic_vector(3 downto 0) := "0111";
   constant cmd_read  : std_logic_vector(3 downto 0) := "0101";   -- Must be sure A10 is low.
   constant cmd_write : std_logic_vector(3 downto 0) := "0100";
   constant cmd_act   : std_logic_vector(3 downto 0) := "0011";
   constant cmd_pre   : std_logic_vector(3 downto 0) := "0010";  -- Must set A10 to '1'.
   constant cmd_ref   : std_logic_vector(3 downto 0) := "0001";
   constant cmd_mrs     : std_logic_vector(3 downto 0) := "0000"; -- Mode register set
   -- State assignments
   constant s_init_nop  : std_logic_vector(8 downto 0) := "00000" & cmd_nop;
   constant s_init_pre  : std_logic_vector(8 downto 0) := "00000" & cmd_pre;
   constant s_init_ref  : std_logic_vector(8 downto 0) := "00000" & cmd_ref;
   constant s_init_mrs  : std_logic_vector(8 downto 0) := "00000" & cmd_mrs;
   
   constant s_idle  : std_logic_vector(8 downto 0) := "00001" & cmd_nop;
   
   constant s_rf0   : std_logic_vector(8 downto 0) := "00010" & cmd_ref;
   constant s_rf1   : std_logic_vector(8 downto 0) := "00011" & cmd_nop;
   constant s_rf2   : std_logic_vector(8 downto 0) := "00100" & cmd_nop;
   constant s_rf3   : std_logic_vector(8 downto 0) := "00101" & cmd_nop;
   constant s_rf4   : std_logic_vector(8 downto 0) := "00110" & cmd_nop;
   constant s_rf5   : std_logic_vector(8 downto 0) := "00111" & cmd_nop;

   constant s_ra0   : std_logic_vector(8 downto 0) := "01000" & cmd_act;
   constant s_ra1   : std_logic_vector(8 downto 0) := "01001" & cmd_nop;
   constant s_ra2   : std_logic_vector(8 downto 0) := "01010" & cmd_nop;

   constant s_dr0   : std_logic_vector(8 downto 0) := "01011" & cmd_pre;
   constant s_dr1   : std_logic_vector(8 downto 0) := "01100" & cmd_nop;

   constant s_wr0   : std_logic_vector(8 downto 0) := "01101" & cmd_write;
   constant s_wr1   : std_logic_vector(8 downto 0) := "01110" & cmd_nop;
   constant s_wr2   : std_logic_vector(8 downto 0) := "01111" & cmd_nop;
   constant s_wr3   : std_logic_vector(8 downto 0) := "10000" & cmd_nop;

   constant s_rd0   : std_logic_vector(8 downto 0) := "10001" & cmd_read;
   constant s_rd1   : std_logic_vector(8 downto 0) := "10010" & cmd_nop;
   constant s_rd2   : std_logic_vector(8 downto 0) := "10011" & cmd_nop;
   constant s_rd3   : std_logic_vector(8 downto 0) := "10100" & cmd_nop;
   constant s_rd4   : std_logic_vector(8 downto 0) := "10101" & cmd_read;
   constant s_rd5   : std_logic_vector(8 downto 0) := "10110" & cmd_nop;
   constant s_rd6   : std_logic_vector(8 downto 0) := "10111" & cmd_nop;
   constant s_rd7   : std_logic_vector(8 downto 0) := "11000" & cmd_nop;
   constant s_rd8   : std_logic_vector(8 downto 0) := "11001" & cmd_nop;
   constant s_rd9   : std_logic_vector(8 downto 0) := "11011" & cmd_nop;

   constant s_drdr0 : std_logic_vector(8 downto 0) := "11101" & cmd_pre;
   constant s_drdr1 : std_logic_vector(8 downto 0) := "11110" & cmd_nop;
   constant s_drdr2 : std_logic_vector(8 downto 0) := "11111" & cmd_nop;
   
   signal addr_row : std_logic_vector(12 downto 0);
   signal addr_bank: std_logic_vector(1 downto 0);
   signal addr_col : std_logic_vector(9 downto 0);

   signal captured : std_logic_vector(15 downto 0);
   
   signal clock_100          : std_logic;
   signal clock_100_delayed_3ns : std_logic;
begin
   -- Addressing is in 32 bit words - twice that of the DRAM width,
   -- so each burst of four access two system words.
   addr_row  <= address(23 downto 11);
   addr_bank <= address(10 downto 9);
   addr_col  <= address(8 downto  1) & "00";
   
sdram_clk_pll: sdram_clk_gen

   -- Generate the 100MHz clock and the same phase shifted by 3ns
   PORT MAP
   (
      inclk0    => CLOCK_50,
      c0         => clock_100,
      c1         => clock_100_delayed_3ns
   );

   DRAM_CLK         <= clock_100_delayed_3ns;
   DRAM_CKE       <= '1';
   DRAM_CS_N       <= r.state(3);
   DRAM_RAS_N       <= r.state(2);
   DRAM_CAS_N       <= r.state(1);
   DRAM_WE_N       <= r.state(0);
   DRAM_ADDR      <= r.address;
   DRAM_BA          <= r.bank;
   DATA_OUT         <= captured & r.data_out_low;
   DRAM_DQM       <= r.dq_masks;
   data_out_valid <= r.data_out_valid;

   process (r, address, req_read, req_write, addr_row, addr_bank, addr_col, data_in, captured)
   begin
      -- copy the existing values
      n <= r;
      if req_read = '1' then
         n.rd_pending <= '1';
      end if;
      
      if req_write = '1' then
         n.wr_pending <= '1';
      end if;
      
      n.dq_masks     <= "11";
      
      -- first off, do we need to perform a refresh cycle ASAP?
      if r.rf_counter = 770 then -- 781 = 64,000,000ns / 8192 / 10ns
         n.rf_counter <= (others => '0');
         n.rf_pending <= '1';
      else
         -- only start looking for refreshes outside of the initialisation state.
         if not(r.state(8 downto 4) = s_init_nop(8 downto 4)) then
            n.rf_counter <= r.rf_counter + 1;
         end if;
      end if;
      
      -- Set the data bus into HIZ, high and low bytes masked
      DRAM_DQ    <= (others => 'Z');

      n.init_counter <= r.init_counter-1;
      
      -- Process the FSM
      case r.state(8 downto 4) is
         when s_init_nop(8 downto 4) =>
            n.state     <= s_init_nop;
            n.address <= (others => '0');
            n.bank    <= (others => '0');
            n.rf_counter   <= (others => '0');
            n.data_out_valid <= '1';
            
            -- T-130, precharge all banks.
            if r.init_counter = "000000010000010" then
               n.state     <= s_init_pre;
               n.address(10)   <= '1';
            end if;

            -- T-127, T-111, T-95, T-79, T-63, T-47, T-31, T-15, the 8 refreshes
            
            if r.init_counter(14 downto 7) = 0 and r.init_counter(3 downto 0) = 15 then
               n.state     <= s_init_ref;
            end if;
            
            -- T-3, the load mode register 
            if r.init_counter = 3 then
               n.state     <= s_init_mrs;
                           -- Mode register is as follows:
                           -- resvd   wr_b   OpMd   CAS=3   Seq   bust=4
                n.address   <= "000" & "0" & "00" & "011" & "0" & "010";
                           -- resvd
               n.bank      <= "00";
            end if;

            
            -- T-1 The switch to the FSM (first command will be a NOP
            if r.init_counter = 1 then
               n.state          <= s_idle;
            end if;

         ------------------------------
         -- The Idle section
         ------------------------------
         when s_idle(8 downto 4) =>
            n.state <= s_idle;

            -- do we have to activate a row?
            if r.rd_pending = '1' or r.wr_pending = '1' then
               n.state        <= s_ra0;
               n.address     <= addr_row;
               n.act_row    <= addr_row;
            end if;

            -- refreshes take priority over everything
            if r.rf_pending = '1' then
               n.state        <= s_rf0;
               n.rf_pending <= '0';
            end if;
         ------------------------------
         -- Row activation
         -- s_ra2 is also the "idle with active row" state and provides
         -- a resting point between operations on the same row
         ------------------------------
         when s_ra0(8 downto 4) =>
            n.state        <= s_ra1;
         when s_ra1(8 downto 4) =>
            n.state        <= s_ra2;
         when s_ra2(8 downto 4) =>
            -- we can stay in this state until we have something to do
            n.state       <= s_ra2;

            -- If there is a read pending, deactivate the row
            if r.rd_pending = '1' or r.wr_pending = '1' then
               n.state     <= s_dr0;
               n.address(10) <= '1';
            end if;
            
            -- unless we have a read to perform on the same row? do that instead
            if r.rd_pending = '1' and r.act_row = addr_row then
               n.state     <= s_rd0;
               n.address <= "000" & addr_col;
               n.bank    <= addr_bank;
               n.dq_masks <= "00";
               n.rd_pending <= '0';
            end if;
            
            -- unless we have a write on the same row? writes take priroty over reads
            if r.wr_pending = '1' and r.act_row = addr_row then
               n.state     <= s_wr0;
               n.address <= "000" & addr_col;
               n.bank    <= addr_bank;
               n.dq_masks<= "00";
               n.wr_pending <= '0';
            end if;
            
            -- But refreshes take piority over everything!
            if r.rf_pending = '1' then
               n.state     <= s_dr0;
               n.address(10) <= '1';
            end if;
            
         ------------------------------------------------------
         -- Deactivate the current row and return to idle state
         ------------------------------------------------------
         when s_dr0(8 downto 4) =>
            n.state <= s_dr1;
         when s_dr1(8 downto 4) =>
            n.state <= s_idle;

         ------------------------------
         -- The Refresh section
         ------------------------------
         when s_rf0(8 downto 4) =>
            n.state <= s_rf1;
         when s_rf1(8 downto 4) =>
            n.state <= s_rf2;
         when s_rf2(8 downto 4) =>
            n.state <= s_rf3;
         when s_rf3(8 downto 4) =>
            n.state <= s_rf4;
         when s_rf4(8 downto 4) =>
            n.state <= s_rf5;
         when s_rf5(8 downto 4) =>
            n.state <= s_idle;
         ------------------------------
         -- The Write section
         ------------------------------
         when s_wr0(8 downto 4) =>
            n.state    <= s_wr1;
            n.address <= "000" & addr_col;
            n.bank    <= addr_bank;
            DRAM_DQ     <= data_in(15 downto 0);
            n.dq_masks<= "00";
         when s_wr1(8 downto 4) =>
            n.state    <= s_wr2;
            DRAM_DQ     <= data_in(31 downto 16);
            n.dq_masks<= "00";
         when s_wr2(8 downto 4) =>
            DRAM_DQ     <= data_in(15 downto 0);
            n.state     <= s_wr3;
            n.dq_masks<= "00";
         when s_wr3(8 downto 4) =>
            -- Default to the idle+row active state
            n.state     <= s_ra2;
            DRAM_DQ     <= data_in(31 downto 16);
            n.dq_masks<= "11";
            
            -- If there is a read or write then deactivate the row
            if r.rd_pending = '1' or r.wr_pending = '1' then
               n.state         <= s_dr0;
               n.address(10) <= '1';
            end if;

            -- But if there is a read pending in the same row, do that
            if r.rd_pending = '1' and r.act_row = addr_row then
               n.state     <= s_rd0;
               n.address <= "000" & addr_col;
               n.bank    <= addr_bank;
               n.dq_masks <= "00";
               n.rd_pending <= '0';
            end if;

            -- unless there is a write pending in the same row, do that
            if r.wr_pending = '1' and r.act_row = addr_row then
               n.state     <= s_wr0;
               n.address <= "000" & addr_col;
               n.bank    <= addr_bank;
               n.dq_masks<= "00";
               n.wr_pending <= '0';
            end if;

            -- But always try and refresh if one is pending!
            if r.rf_pending = '1' then
               n.state       <= s_dr0;
               n.address(10) <= '1';
            end if;
         
         ------------------------------
         -- The Read section
         ------------------------------
         when s_rd0(8 downto 4) =>
            n.state <= s_rd1;
            n.dq_masks <= "00";
         when s_rd1(8 downto 4) =>
            n.state <= s_rd2;
            n.dq_masks <= "00";
         when s_rd2(8 downto 4) =>
            n.state <= s_rd3;
            n.dq_masks <= "00";
         when s_rd3(8 downto 4) =>
            -- default is to end the read with the row open
            n.state <= s_rd7;

            -- otherwise if there is a read or write prepare to deactivate the row.
            -- (This is overridden if the read/write is to the same page)
            if r.rd_pending = '1' or r.wr_pending = '1' then
               n.state       <= s_drdr0;
               n.address(10) <= '1';
            end if;

            -- override if the write is from the same row
            if r.wr_pending = '1' and r.act_row = addr_row then
               n.state <= s_rd7;
            end if;
         
            -- override if the read is from the same row
            if r.rd_pending = '1' and r.act_row = addr_row then
               n.state     <= s_rd4;
               n.address <= "000" & addr_col;
               n.bank    <= addr_bank;
               n.dq_masks<= "00";
            end if;

               -- If a refresh is pending then always deactivate the row
            if r.rf_pending = '1' then 
               n.state <= s_drdr0;
               n.address(10) <= '1';
            end if;
            n.data_out_low <= captured;
            n.data_out_valid <= '1';   
         when s_rd4(8 downto 4) =>
            n.state <= s_rd5;
            n.dq_masks<= "00";
         when s_rd5(8 downto 4) =>
            n.state <= s_rd6;
            n.data_out_low <= captured;
            n.data_out_valid <= '1';   
            n.dq_masks<= "00";
         when s_rd6(8 downto 4) =>
            n.state <= s_rd3;
            n.dq_masks<= "00";
         when s_rd7(8 downto 4) =>
            n.state <= s_rd8;
            n.data_out_low <= captured;
            n.data_out_valid <= '1';   
         when s_rd8(8 downto 4) =>
            n.state <= s_rd9;
         when s_rd9(8 downto 4) =>
            -- by default go to the idle-with-row-active state
            n.state <= s_ra2;
            n.data_out_low <= captured;
            n.data_out_valid <= '1';   
            
            -- otherwise if there is a read or write prepare to deactivate the row.
            -- (This is overridden if the read/write is to the same row)
            if r.rd_pending = '1' or r.wr_pending = '1' then
               n.state <= s_dr0;
               n.address(10) <= '1';
            end if;
            
            -- this is to catch if a read has turned up since the choices at state s_dr3
            if r.rd_pending = '1' and r.act_row = addr_row then
               n.state <= s_rd0;
               n.address <= "000" & addr_col;
               n.bank    <= addr_bank;
               n.dq_masks <= "00";
               n.rd_pending <= '0';
            end if;

            -- this is to catch if a read has turned up since the choices at state s_dr3
            if r.wr_pending = '1' and r.act_row = addr_row then
               n.state <= s_wr0;
               n.address <= "000" & addr_col;
               n.bank    <= addr_bank;
               n.dq_masks<= "00";
               n.wr_pending <= '0';
            end if;
            
            if r.rf_pending = '1' then
               n.state <= s_dr0;
               n.address(10) <= '1';
            end if;
            
         ------------------------------
         -- The Deactivate row during read section
         ------------------------------
         when s_drdr0(8 downto 4) =>
            n.state <= s_drdr1;
         when s_drdr1(8 downto 4) =>
            n.state <= s_drdr2;
            n.data_out_low <= captured;
            n.data_out_valid <= '1';   
         when s_drdr2(8 downto 4) =>
            n.state <= s_idle;

            if r.rf_pending = '1' then
               n.state <= s_rf0;
            end if;
            
            if r.rd_pending = '1' or r.wr_pending = '1' then
               n.state       <= s_ra0;
               n.address    <= addr_row;
               n.act_row    <= addr_row;
               n.bank       <= addr_bank;
            end if;

         when others =>
            n.state <= s_init_nop;
      end case;
   end process;
   
   --- The clock driven logic
   process (clock_100, n)
   begin
      if clock_100'event and clock_100 = '1' then
         r <= n;
      end if;
   end process;

   process (clock_100_delayed_3ns, dram_dq)
   begin
      if clock_100_delayed_3ns'event and clock_100_delayed_3ns = '1' then
         captured <= dram_dq;
      end if;
   end process;

end rtl;