475 lines
23 KiB
VHDL
475 lines
23 KiB
VHDL
----------------------------------------------------------------------------------
|
|
-- Engineer: Mike Field <hamster@snap.net.nz>
|
|
--
|
|
-- 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;
|