---------------------------------------------------------------------------------- -- Engineer: Mike Field -- -- Create Date: 14:09:12 09/15/2013 -- Module Name: SDRAM_Controller - Behavioral -- Description: Simple SDRAM controller for a Micron 48LC16M16A2-7E -- or Micron 48LC4M16A2-7E @ 100MHz -- Revision: -- Revision 0.1 - Initial version -- Revision 0.2 - Removed second clock signal that isn't needed. -- Revision 0.3 - Added back-to-back reads and writes. -- Revision 0.4 - Allow refeshes to be delayed till next PRECHARGE is issued, -- Unless they get really, really delayed. If a delay occurs multiple -- refreshes might get pushed out, but it will have avioded about -- 50% of the refresh overhead -- Revision 0.5 - Add more paramaters to the design, allowing it to work for both the -- Papilio Pro and Logi-Pi -- -- Worst case performance (single accesses to different rows or banks) is: -- Writes 16 cycles = 6,250,000 writes/sec = 25.0MB/s (excluding refresh overhead) -- Reads 17 cycles = 5,882,352 reads/sec = 23.5MB/s (excluding refresh overhead) -- -- For 1:1 mixed reads and writes into the same row it is around 88MB/s -- For reads or wries to the same it is can be as high as 184MB/s ---------------------------------------------------------------------------------- library IEEE; use IEEE.STD_LOGIC_1164.ALL; library UNISIM; use IEEE.NUMERIC_STD.ALL; library altera_mf; use altera_mf.altera_mf_components.all; entity SDRAM_Controller is generic ( sdram_address_width : natural; sdram_column_bits : natural; sdram_startup_cycles: natural; cycles_per_refresh : natural ); Port ( clk : in STD_LOGIC; reset : in STD_LOGIC; -- Interface to issue reads or write data cmd_ready : out STD_LOGIC; -- '1' when a new command will be acted on cmd_enable : in STD_LOGIC; -- Set to '1' to issue new command (only acted on when cmd_read = '1') cmd_wr : in STD_LOGIC; -- Is this a write? cmd_address : in STD_LOGIC_VECTOR(sdram_address_width-2 downto 0); -- address to read/write cmd_byte_enable : in STD_LOGIC_VECTOR(3 downto 0); -- byte masks for the write command cmd_data_in : in STD_LOGIC_VECTOR(31 downto 0); -- data for the write command data_out : out STD_LOGIC_VECTOR(31 downto 0); -- word read from SDRAM data_out_ready : out STD_LOGIC; -- is new data ready? -- SDRAM signals SDRAM_CLK : out STD_LOGIC; SDRAM_CKE : out STD_LOGIC; SDRAM_CS : out STD_LOGIC; SDRAM_RAS : out STD_LOGIC; SDRAM_CAS : out STD_LOGIC; SDRAM_WE : out STD_LOGIC; SDRAM_DQM : out STD_LOGIC_VECTOR( 1 downto 0); SDRAM_ADDR : out STD_LOGIC_VECTOR(12 downto 0); SDRAM_BA : out STD_LOGIC_VECTOR( 1 downto 0); SDRAM_DATA : inout STD_LOGIC_VECTOR(15 downto 0)); end SDRAM_Controller; architecture Behavioral of SDRAM_Controller is -- From page 37 of MT48LC16M16A2 datasheet -- Name (Function) CS# RAS# CAS# WE# DQM Addr Data -- COMMAND INHIBIT (NOP) H X X X X X X -- NO OPERATION (NOP) L H H H X X X -- ACTIVE L L H H X Bank/row X -- READ L H L H L/H Bank/col X -- WRITE L H L L L/H Bank/col Valid -- BURST TERMINATE L H H L X X Active -- PRECHARGE L L H L X Code X -- AUTO REFRESH L L L H X X X -- LOAD MODE REGISTER L L L L X Op-code X -- Write enable X X X X L X Active -- Write inhibit X X X X H X High-Z -- Here are the commands mapped to constants constant CMD_UNSELECTED : std_logic_vector(3 downto 0) := "1000"; constant CMD_NOP : std_logic_vector(3 downto 0) := "0111"; constant CMD_ACTIVE : std_logic_vector(3 downto 0) := "0011"; constant CMD_READ : std_logic_vector(3 downto 0) := "0101"; constant CMD_WRITE : std_logic_vector(3 downto 0) := "0100"; constant CMD_TERMINATE : std_logic_vector(3 downto 0) := "0110"; constant CMD_PRECHARGE : std_logic_vector(3 downto 0) := "0010"; constant CMD_REFRESH : std_logic_vector(3 downto 0) := "0001"; constant CMD_LOAD_MODE_REG : std_logic_vector(3 downto 0) := "0000"; constant MODE_REG : std_logic_vector(12 downto 0) := -- Reserved, wr bust, OpMode, CAS Latency (2), Burst Type, Burst Length (2) "000" & "0" & "00" & "010" & "0" & "001"; signal iob_command : std_logic_vector( 3 downto 0) := CMD_NOP; signal iob_address : std_logic_vector(12 downto 0) := (others => '0'); signal iob_data : std_logic_vector(15 downto 0) := (others => '0'); signal iob_dqm : std_logic_vector( 1 downto 0) := (others => '0'); signal iob_cke : std_logic := '0'; signal iob_bank : std_logic_vector( 1 downto 0) := (others => '0'); attribute IOB: string; attribute IOB of iob_command: signal is "true"; attribute IOB of iob_address: signal is "true"; attribute IOB of iob_dqm : signal is "true"; attribute IOB of iob_cke : signal is "true"; attribute IOB of iob_bank : signal is "true"; attribute IOB of iob_data : signal is "true"; signal iob_data_next : std_logic_vector(15 downto 0) := (others => '0'); signal captured_data : std_logic_vector(15 downto 0) := (others => '0'); signal captured_data_last : std_logic_vector(15 downto 0) := (others => '0'); signal sdram_din : std_logic_vector(15 downto 0); attribute IOB of captured_data : signal is "true"; type fsm_state is (s_startup, s_idle_in_6, s_idle_in_5, s_idle_in_4, s_idle_in_3, s_idle_in_2, s_idle_in_1, s_idle, s_open_in_2, s_open_in_1, s_write_1, s_write_2, s_write_3, s_read_1, s_read_2, s_read_3, s_read_4, s_precharge ); signal state : fsm_state := s_startup; attribute FSM_ENCODING : string; attribute FSM_ENCODING of state : signal is "ONE-HOT"; -- dual purpose counter, it counts up during the startup phase, then is used to trigger refreshes. constant startup_refresh_max : unsigned(13 downto 0) := (others => '1'); signal startup_refresh_count : unsigned(13 downto 0) := startup_refresh_max-to_unsigned(sdram_startup_cycles,14); -- logic to decide when to refresh signal pending_refresh : std_logic := '0'; signal forcing_refresh : std_logic := '0'; -- The incoming address is split into these three values signal addr_row : std_logic_vector(12 downto 0) := (others => '0'); signal addr_col : std_logic_vector(12 downto 0) := (others => '0'); signal addr_bank : std_logic_vector( 1 downto 0) := (others => '0'); signal dqm_sr : std_logic_vector( 3 downto 0) := (others => '1'); -- an extra two bits in case CAS=3 -- signals to hold the requested transaction before it is completed signal save_wr : std_logic := '0'; signal save_row : std_logic_vector(12 downto 0); signal save_bank : std_logic_vector( 1 downto 0); signal save_col : std_logic_vector(12 downto 0); signal save_data_in : std_logic_vector(31 downto 0); signal save_byte_enable : std_logic_vector( 3 downto 0); -- control when new transactions are accepted signal ready_for_new : std_logic := '0'; signal got_transaction : std_logic := '0'; signal can_back_to_back : std_logic := '0'; -- signal to control the Hi-Z state of the DQ bus signal iob_dq_hiz : std_logic := '1'; -- signals for when to read the data off of the bus signal data_ready_delay : std_logic_vector( 4 downto 0); -- bit indexes used when splitting the address into row/colum/bank. constant start_of_col : natural := 0; constant end_of_col : natural := sdram_column_bits-2; constant start_of_bank : natural := sdram_column_bits-1; constant end_of_bank : natural := sdram_column_bits; constant start_of_row : natural := sdram_column_bits+1; constant end_of_row : natural := sdram_address_width-2; constant prefresh_cmd : natural := 10; begin -- Indicate the need to refresh when the counter is 2048, -- Force a refresh when the counter is 4096 - (if a refresh is forced, -- multiple refresshes will be forced until the counter is below 2048 pending_refresh <= startup_refresh_count(11); forcing_refresh <= startup_refresh_count(12); -- tell the outside world when we can accept a new transaction; cmd_ready <= ready_for_new; ---------------------------------------------------------------------------- -- Seperate the address into row / bank / address ---------------------------------------------------------------------------- addr_row(end_of_row-start_of_row downto 0) <= cmd_address(end_of_row downto start_of_row); -- 12:0 <= 22:10 addr_bank <= cmd_address(end_of_bank downto start_of_bank); -- 1:0 <= 9:8 addr_col(sdram_column_bits-1 downto 0) <= cmd_address(end_of_col downto start_of_col) & '0'; -- 8:0 <= 7:0 & '0' --addr_row(12 downto 0) <= cmd_address(22 downto 10); -- 12:0 <= 22:10 --addr_bank <= cmd_address( 9 downto 8); -- 1:0 <= 9:8 --addr_col(8 downto 0) <= cmd_address( 7 downto 0) & '0'; -- 8:0 <= 7:0 & '0' ----------------------------------------------- --!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --!! Ensure that all outputs are registered. !! --!! Check the pinout report to be sure !! --!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ----------------------------------------------- sdram_cke <= iob_cke; sdram_CS <= iob_command(3); sdram_RAS <= iob_command(2); sdram_CAS <= iob_command(1); sdram_WE <= iob_command(0); sdram_dqm <= iob_dqm; sdram_ba <= iob_bank; sdram_addr <= iob_address; sdram_din <= sdram_data; sdram_data <= iob_data when iob_dq_hiz = '0' else (others => 'Z'); --------------------------------------------------------------- -- Explicitly set up the tristate I/O buffers on the DQ signals --------------------------------------------------------------- --iob_dq_g: for i in 0 to 15 generate -- begin --iob_dq_iob: altiobuf_bidir -- generic map (number_of_channels => 1) -- port map ( dataout(0) => sdram_din(i), dataio(0) => sdram_data(i), datain(0) => iob_data(i), oe(0) => iob_dq_hiz); --end generate; capture_proc: process(clk) begin if rising_edge(clk) then captured_data <= sdram_din; end if; end process; main_proc: process(clk) begin if rising_edge(clk) then captured_data_last <= captured_data; ------------------------------------------------ -- Default state is to do nothing ------------------------------------------------ iob_command <= CMD_NOP; iob_address <= (others => '0'); iob_bank <= (others => '0'); ------------------------------------------------ -- countdown for initialisation & refresh ------------------------------------------------ startup_refresh_count <= startup_refresh_count+1; ------------------------------------------------------------------- -- It we are ready for a new tranasction and one is being presented -- then accept it. Also remember what we are reading or writing, -- and if it can be back-to-backed with the last transaction ------------------------------------------------------------------- if ready_for_new = '1' and cmd_enable = '1' then if save_bank = addr_bank and save_row = addr_row then can_back_to_back <= '1'; else can_back_to_back <= '0'; end if; save_row <= addr_row; save_bank <= addr_bank; save_col <= addr_col; save_wr <= cmd_wr; save_data_in <= cmd_data_in; save_byte_enable <= cmd_byte_enable; got_transaction <= '1'; ready_for_new <= '0'; end if; ------------------------------------------------ -- Handle the data coming back from the -- SDRAM for the Read transaction ------------------------------------------------ data_out_ready <= '0'; if data_ready_delay(0) = '1' then data_out <= captured_data & captured_data_last; data_out_ready <= '1'; end if; ---------------------------------------------------------------------------- -- update shift registers used to choose when to present data to/from memory ---------------------------------------------------------------------------- data_ready_delay <= '0' & data_ready_delay(data_ready_delay'high downto 1); iob_dqm <= dqm_sr(1 downto 0); dqm_sr <= "11" & dqm_sr(dqm_sr'high downto 2); case state is when s_startup => ------------------------------------------------------------------------ -- This is the initial startup state, where we wait for at least 100us -- before starting the start sequence -- -- The initialisation is sequence is -- * de-assert SDRAM_CKE -- * 100us wait, -- * assert SDRAM_CKE -- * wait at least one cycle, -- * PRECHARGE -- * wait 2 cycles -- * REFRESH, -- * tREF wait -- * REFRESH, -- * tREF wait -- * LOAD_MODE_REG -- * 2 cycles wait ------------------------------------------------------------------------ iob_CKE <= '1'; -- All the commands during the startup are NOPS, except these if startup_refresh_count = startup_refresh_max-31 then -- ensure all rows are closed iob_command <= CMD_PRECHARGE; iob_address(prefresh_cmd) <= '1'; -- all banks iob_bank <= (others => '0'); elsif startup_refresh_count = startup_refresh_max-23 then -- these refreshes need to be at least tREF (66ns) apart iob_command <= CMD_REFRESH; elsif startup_refresh_count = startup_refresh_max-15 then iob_command <= CMD_REFRESH; elsif startup_refresh_count = startup_refresh_max-7 then -- Now load the mode register iob_command <= CMD_LOAD_MODE_REG; iob_address <= MODE_REG; end if; ------------------------------------------------------ -- if startup is coomplete then go into idle mode, -- get prepared to accept a new command, and schedule -- the first refresh cycle ------------------------------------------------------ if startup_refresh_count = 0 then state <= s_idle; ready_for_new <= '1'; got_transaction <= '0'; startup_refresh_count <= to_unsigned(2048 - cycles_per_refresh+1,14); end if; when s_idle_in_6 => state <= s_idle_in_5; when s_idle_in_5 => state <= s_idle_in_4; when s_idle_in_4 => state <= s_idle_in_3; when s_idle_in_3 => state <= s_idle_in_2; when s_idle_in_2 => state <= s_idle_in_1; when s_idle_in_1 => state <= s_idle; when s_idle => -- Priority is to issue a refresh if one is outstanding if pending_refresh = '1' or forcing_refresh = '1' then ------------------------------------------------------------------------ -- Start the refresh cycle. -- This tasks tRFC (66ns), so 6 idle cycles are needed @ 100MHz ------------------------------------------------------------------------ state <= s_idle_in_3; iob_command <= CMD_REFRESH; startup_refresh_count <= startup_refresh_count - cycles_per_refresh+1; elsif got_transaction = '1' then -------------------------------- -- Start the read or write cycle. -- First task is to open the row -------------------------------- state <= s_open_in_1; iob_command <= CMD_ACTIVE; iob_address <= save_row; iob_bank <= save_bank; end if; -------------------------------------------- -- Opening the row ready for reads or writes -------------------------------------------- when s_open_in_2 => state <= s_open_in_1; when s_open_in_1 => -- still waiting for row to open if save_wr = '1' then state <= s_write_1; iob_dq_hiz <= '0'; iob_data <= save_data_in(15 downto 0); -- get the DQ bus out of HiZ early else iob_dq_hiz <= '1'; state <= s_read_1; end if; -- we will be ready for a new transaction next cycle! ready_for_new <= '1'; got_transaction <= '0'; ---------------------------------- -- Processing the read transaction ---------------------------------- when s_read_1 => state <= s_read_2; iob_command <= CMD_READ; iob_address <= save_col; iob_bank <= save_bank; iob_address(prefresh_cmd) <= '0'; -- A10 actually matters - it selects auto precharge -- Schedule reading the data values off the bus data_ready_delay(data_ready_delay'high) <= '1'; -- Set the data masks to read all bytes iob_dqm <= (others => '0'); dqm_sr(1 downto 0) <= (others => '0'); when s_read_2 => state <= s_read_3; if forcing_refresh = '0' and got_transaction = '1' and can_back_to_back = '1' then if save_wr = '0' then state <= s_read_1; ready_for_new <= '1'; -- we will be ready for a new transaction next cycle! end if; end if; when s_read_3 => state <= s_read_4; if forcing_refresh = '0' and got_transaction = '1' and can_back_to_back = '1' then if save_wr = '0' then state <= s_read_1; ready_for_new <= '1'; -- we will be ready for a new transaction next cycle! end if; end if; when s_read_4 => state <= s_precharge; -- can we do back-to-back read? if forcing_refresh = '0' and got_transaction = '1' and can_back_to_back = '1' then if save_wr = '0' then state <= s_read_1; ready_for_new <= '1'; -- we will be ready for a new transaction next cycle! else state <= s_open_in_2; -- we have to wait for the read data to come back before we swutch the bus into HiZ end if; end if; ------------------------------------------------------------------ -- Processing the write transaction ------------------------------------------------------------------- when s_write_1 => state <= s_write_2; iob_command <= CMD_WRITE; iob_address <= save_col; iob_address(prefresh_cmd) <= '0'; -- A10 actually matters - it selects auto precharge iob_bank <= save_bank; iob_dqm <= NOT save_byte_enable(1 downto 0); dqm_sr(1 downto 0) <= NOT save_byte_enable(3 downto 2); iob_data <= save_data_in(15 downto 0); iob_data_next <= save_data_in(31 downto 16); when s_write_2 => state <= s_write_3; iob_data <= iob_data_next; when s_write_3 => -- must wait tRDL, hence the extra idle state iob_dq_hiz <= '1'; state <= s_precharge; ------------------------------------------------------------------- -- Closing the row off (this closes all banks) ------------------------------------------------------------------- when s_precharge => state <= s_idle_in_3; iob_command <= CMD_PRECHARGE; iob_address(prefresh_cmd) <= '1'; -- A10 actually matters - it selects all banks or just one ------------------------------------------------------------------- -- We should never get here, but if we do then reset the memory ------------------------------------------------------------------- when others => state <= s_startup; ready_for_new <= '0'; startup_refresh_count <= startup_refresh_max-to_unsigned(sdram_startup_cycles,14); end case; if reset = '1' then -- Sync reset state <= s_startup; ready_for_new <= '0'; startup_refresh_count <= startup_refresh_max-to_unsigned(sdram_startup_cycles,14); end if; end if; end process; end Behavioral;