-- ddr_memctrl.vhd (Pipelined DDR memory controller) -- 'intersect' ray-triangle intersection soft core -- -- Copyright (C) 2007 Wenzel Jakob -- -- 01000000000000100000000 -- 00001000011111000001000 -- 00000001100000111000000 -- 01000010001110000101001 -- 00000110010001000100010 -- 00100110010011001100000 -- 10000100011000011000100 -- 00000110001111100000000 -- 00100011100000000010000 -- 00010000111111110000000 -- 00010000000000000001000 -- -- This program is free software; you can redistribute it and/or -- modify it under the terms of the GNU General Public License -- as published by the Free Software Foundation; either version 2 -- of the License, or (at your option) any later version. -- -- This program is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- GNU General Public License for more details. -- -- You should have received a copy of the GNU General Public License -- along with this program; if not, write to the Free Software Foundation -- Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -- -- ============================================================= -- -- DDR controller state machine - takes care of refresh cycles, -- command timeouts, data strobes, open rows and delay/skew issues. -- Will achieve 3,2Gb/s (381 MiB/s) at 100 Mhz library ieee, work; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.common.all; use work.ddr_memctrl_pkg.all; use work.techmap.all; entity ddr_memctrl is generic ( PERIOD : natural; -- Clock cycle period in picoseconds CAS_LATENCY : real; -- Selected DDR CAS latency TECH : integer; RDPIPE_TAP : integer; -- Read pipeline tap HOST_ADDRWIDTH : natural; -- 8M 32-bit words DDR_NROWS : natural; -- 8192x256 organization DDR_NCOLS : natural; -- DDR column count * 2 DDR_NBANKS : natural; -- 4 separate memory banks DDR_BANK_WIDTH : integer; -- Number bank pins DDR_DQ_WIDTH : integer; -- Number of data bus pins DDR_CMDBIT : natural; -- Special address pin index DDR_ADDR_WIDTH : natural; -- Number of DDR address inputs DDR_RFSHCOUNT : natural; -- Force a refresh once 8 rows are pending CYCLES_DLL : natural; -- Number of cycles for DLL calibration TIME_INITIAL : natural; -- 200 us initial timeout TIME_tRP : natural; -- Precharge execution time TIME_tWR : natural; -- Write recovery time TIME_tRCD : natural; -- Active to read/write TIME_tMRD : natural; -- Load mode register ex. time TIME_tRAS : natural; -- Active to precharge timeout TIME_tRFSHROW : natural; -- One row refresh every 7.8125 us TIME_tRFC : natural -- Autorefresh execution time ); port ( -- Internal clock input clk0 : in std_ulogic; -- Synchronous global reset input rst : in std_ulogic; -- DDR / Control signals pad_o : out ddr_pad_t; -- DDR / DDR bank out input, registered on rising edge bank_o : out std_logic_vector(DDR_BANK_WIDTH - 1 downto 0); -- DDR / DDR address out input, registered on rising edge addr_o : out std_logic_vector(DDR_ADDR_WIDTH - 1 downto 0); -- DDR / DDR Data out input, registered on rising edge dqo_o : out std_logic_vector(DDR_DQ_WIDTH*2 - 1 downto 0); -- DDR / DDR Data in output dqi_i : in std_logic_vector(DDR_DQ_WIDTH*2 - 1 downto 0); -- Host / Address input h_addr_i : in std_logic_vector(HOST_ADDRWIDTH - 1 downto 0); -- Host / DDR Data out input h_data_i : in std_logic_vector(DDR_DQ_WIDTH*2 - 1 downto 0); -- Host / DDR Data in output h_data_o : out std_logic_vector(DDR_DQ_WIDTH*2 - 1 downto 0); -- Host / Operation input h_op_i : in std_logic_vector(1 downto 0); -- Host / Command acknowledge (async.) h_earlyAck_o : out std_ulogic; -- Host / Command acknowledge (registered) h_ack_o : out std_ulogic; -- Host / Read acknowledge (registered) h_rdAck_o : out std_ulogic ); end ddr_memctrl; architecture rtl of ddr_memctrl is ------------------------------------------------------------ -- Cycle-accurate timing calculations and other constants ------------------------------------------------------------ constant CYCLES_INITIAL : natural := round((TIME_INITIAL * 1000) / PERIOD + 1); constant CYCLES_PCHG : natural := round((TIME_tRP * 1000) / PERIOD + 1); constant CYCLES_MODE : natural := round((TIME_tMRD * 1000) / PERIOD + 1); constant CYCLES_OPEN : natural := round((TIME_tRCD * 1000) / PERIOD + 1); constant CYCLES_RFSH : natural := round((TIME_tRFC * 1000) / PERIOD + 1); constant CYCLES_RFSHROW : natural := round((TIME_tRFSHROW * 1000) / PERIOD + 1); constant CYCLES_RAS : natural := round((TIME_tRAS * 1000) / PERIOD + 1); -- Two additional cycles are needed because of the DQ offset(1) and the fact -- that this delay is relative to the last DQS strobe and not the write command -- This extended tWR delay also satisfies the tWTR delay. constant CYCLES_WR : natural := 2 + round((TIME_tWR * 1000) / PERIOD + 1); constant LOG2_BANKS : natural := log2(DDR_NBANKS); constant LOG2_ROWS : natural := log2(DDR_NROWS); constant LOG2_COLS : natural := log2(DDR_NCOLS); ------------------------------------------------------------ -- DDR Controller FSM states and data types ------------------------------------------------------------ type fsmState is ( STATE_PWRUP, STATE_INIT_1, STATE_INIT_2, STATE_INIT_3, STATE_INIT_4, STATE_INIT_5, STATE_INIT_6, STATE_INIT_7, STATE_INIT_8, STATE_IDLE, STATE_RFSH_ROW, STATE_OPEN_ROW ); type openRowType is array (0 to DDR_NBANKS-1) of std_logic_vector(LOG2_ROWS - 1 downto 0); ------------------------------------------------------------ -- sdram command word (ras_n, cas_n, we_n) ------------------------------------------------------------ subtype ddrCommand is std_logic_vector(2 downto 0); constant CMD_NOP : ddrCommand := "111"; constant CMD_OPEN : ddrCommand := "011"; constant CMD_READ : ddrCommand := "101"; constant CMD_WRITE : ddrCommand := "100"; constant CMD_TERM : ddrCommand := "110"; constant CMD_PCHG : ddrCommand := "010"; constant CMD_RFSH : ddrCommand := "001"; constant CMD_MODE : ddrCommand := "000"; ------------------------------------------------------------ -- FSM internal state ------------------------------------------------------------ signal state_r, state_x : fsmState := STATE_PWRUP; signal flag_ack_r, flag_ack_x : std_ulogic := '0'; ------------------------------------------------------------ -- Open row state ------------------------------------------------------------ -- Lists the currently open row for each memory bank signal openRows_r, openRows_x : openRowType := (others => (others => '0')); signal openFlag_r, openFlag_x : std_logic_vector(DDR_NBANKS - 1 downto 0) := (others => '0'); ------------------------------------------------------------ -- Timers / counters ------------------------------------------------------------ signal cycles_r, cycles_x : integer range 0 to CYCLES_INITIAL - 1 := 0; signal busy_r, busy_x : boolean := false; signal rfshTimer_r, rfshTimer_x : integer range 0 to CYCLES_RFSHROW - 1 := 0; signal rfshCounter_r, rfshCounter_x : integer range 0 to 8 := 0; signal rasTimer_r, rasTimer_x : integer range 0 to CYCLES_RAS - 1 := 0; signal rasFlag_r, rasFlag_x : boolean := false; signal wrTimer_r, wrTimer_x : integer range 0 to CYCLES_WR - 1 := 0; signal wrFlag_r, wrFlag_x : boolean := false; signal rdFlag_r, rdFlag_x : boolean := false; signal rdPipe_r, rdPipe_x : std_logic_vector(RDPIPE_TAP downto 0) := (others => '0'); ------------------------------------------------------------ -- I/O control registers ------------------------------------------------------------ signal cmd_o : std_logic_vector(2 downto 0) := (others => '0'); signal dqo_r, dqo_x : std_logic_vector(DDR_DQ_WIDTH*2 - 1 downto 0) := (others => '0'); signal dqsen_r, dqsen_x : std_ulogic := '0'; signal addr_x : std_logic_vector(DDR_ADDR_WIDTH - 1 downto 0) := (others => '0'); signal bank_x : std_logic_vector(DDR_BANK_WIDTH - 1 downto 0) := (others => '0'); begin ----------------------------------------------------------- -- Interface to outside world, DDR chip is always selected ----------------------------------------------------------- (pad_o.ras_n, pad_o.cas_n, pad_o.we_n) <= cmd_o; h_ack_o <= flag_ack_r; h_earlyAck_o <= flag_ack_x; h_data_o <= dqi_i; pad_o.dqsen <= dqsen_r; pad_o.dqsdrv <= dqsen_r or dqsen_x; pad_o.dqen <= dqsen_r; pad_o.cke <= '0' when state_r = STATE_PWRUP or state_r = STATE_INIT_1 else '1'; addr_o <= addr_x; bank_o <= bank_x; dqo_o <= dqo_r; h_rdAck_o <= rdPipe_r(RDPIPE_TAP); ----------------------------------------------------------- -- DDR controller main FSM process (step response function) ----------------------------------------------------------- fsm: process (state_r, cycles_r, busy_r, rfshTimer_r, rfshCounter_r, openRows_r, openFlag_r, h_addr_i, h_op_i, h_data_i, rasTimer_r, wrTimer_r, rdPipe_r, rasFlag_r, rdFlag_r, wrFlag_r) -- Address decoder intermediate variables / Bank index variable dec_bank : std_logic_vector(LOG2_BANKS - 1 downto 0); -- Address decoder intermediate variables / Bank index as integer value variable dec_bank_idx : natural range 0 to DDR_NBANKS - 1; -- Address decoder intermediate variables / Row index variable dec_row : std_logic_vector(LOG2_ROWS - 1 downto 0); -- Address decoder intermediate variables / Column index variable dec_col : std_logic_vector(LOG2_COLS - 1 downto 0); -- Open row comparison result for each bank variable dec_open_bank : std_logic_vector(DDR_NBANKS-1 downto 0); -- Open row comparison for the requested bank variable dec_open : std_logic; begin -- Defaults: Keep the current state and execute NOPs state_x <= state_r; openRows_x <= openRows_r; openFlag_x <= openFlag_r; dqsen_x <= '0'; flag_ack_x <= '0'; cmd_o <= CMD_NOP; addr_x <= (others => '0'); bank_x <= (others => '0'); busy_x <= busy_r; dqo_x <= h_data_i; ----------------------------------------------------------- -- Read pipeline flag generator ----------------------------------------------------------- rdPipe_x <= rdPipe_r(RDPIPE_TAP - 1 downto 0) & '0'; if unsigned(rdPipe_r(RDPIPE_TAP - 3 downto 0)) = 0 then rdFlag_x <= false; else rdFlag_x <= true; end if; ------------------------------------------------------------ -- Instruction cycle timer / busy flag generator ------------------------------------------------------------ if cycles_r = 1 or cycles_r = 0 then busy_x <= false; cycles_x <= 0; else cycles_x <= cycles_r - 1; end if; ----------------------------------------------------------- -- Row 'refresh pending' timer and counter ----------------------------------------------------------- if rfshTimer_r /= 0 then rfshTimer_x <= rfshTimer_r - 1; rfshCounter_x <= rfshCounter_r; else rfshTimer_x <= CYCLES_RFSHROW - 1; -- Needed for simulation if rfshCounter_r = 8 then rfshCounter_x <= 0; else rfshCounter_x <= rfshCounter_r + 1; end if; end if; ----------------------------------------------------------- -- Open-to-precharge (tRAS) timer ----------------------------------------------------------- if rasTimer_r = 0 or rasTimer_r = 1 then rasTimer_x <= 0; rasFlag_x <= false; else rasTimer_x <= rasTimer_r - 1; rasFlag_x <= true; end if; ----------------------------------------------------------- -- Write recovery (tWR) timer ----------------------------------------------------------- if wrTimer_r = 1 or wrTimer_r = 0 then wrTimer_x <= 0; wrFlag_x <= false; else wrTimer_x <= wrTimer_r - 1; wrFlag_x <= true; end if; ----------------------------------------------------------- -- Write recovery (tWR) timer ----------------------------------------------------------- if wrTimer_r = 1 or wrTimer_r = 0 then wrTimer_x <= 0; wrFlag_x <= false; else wrTimer_x <= wrTimer_r - 1; wrFlag_x <= true; end if; ----------------------------------------------------------- -- Decode the address input (bank, row and column location) ----------------------------------------------------------- dec_row := h_addr_i(LOG2_COLS + LOG2_BANKS + LOG2_ROWS - 2 downto LOG2_BANKS + LOG2_COLS - 1); dec_bank := h_addr_i(LOG2_COLS + LOG2_BANKS - 2 downto LOG2_COLS - 1); dec_col := h_addr_i(LOG2_COLS - 2 downto 0) & '0'; dec_bank_idx := to_integer(unsigned(dec_bank)); ----------------------------------------------------------- -- Check whether the row is open ----------------------------------------------------------- for i in 0 to DDR_NBANKS - 1 loop -- Infers comparators and a wide OR gate if openFlag_r(i) & openRows_r(i) = '1' & dec_row then dec_open_bank(i) := '1'; else dec_open_bank(i) := '0'; end if; end loop; dec_open := dec_open_bank(dec_bank_idx); -- If an uninterruptible unstruction is running, wait -- before issuing other instructions if busy_r = false then case state_r is ------------------------------------------------------------ -- Power-up state, keep clock enable down for >= 200 us ------------------------------------------------------------ when STATE_PWRUP => cycles_x <= CYCLES_INITIAL - 1; busy_x <= CYCLES_INITIAL > 1; state_x <= STATE_INIT_1; ------------------------------------------------------------ -- Send 1 NOP, Assert CKE (=> CKE will become an SSTL input) ------------------------------------------------------------ when STATE_INIT_1 => state_x <= STATE_INIT_2; cycles_x <= 1; busy_x <= true; ------------------------------------------------------------ -- Precharge all banks and wait for the command to complete ------------------------------------------------------------ when STATE_INIT_2 => cmd_o <= CMD_PCHG; addr_x(DDR_CMDBIT) <= '1'; cycles_x <= CYCLES_PCHG - 1; busy_x <= CYCLES_PCHG > 1; state_x <= STATE_INIT_3; ------------------------------------------------------------ -- Load the ext. mode register (DLL enabled, normal drive) ------------------------------------------------------------ when STATE_INIT_3 => cmd_o <= CMD_MODE; bank_x(0) <= '1'; addr_x <= (others => '0'); cycles_x <= CYCLES_MODE - 1; busy_x <= CYCLES_MODE > 1; state_x <= STATE_INIT_4; ------------------------------------------------------------ -- Load mode register (Burst=2, CAS Latency=X, Reset DLL) ------------------------------------------------------------ when STATE_INIT_4 => cmd_o <= CMD_MODE; if CAS_LATENCY = 2.0 then addr_x(8 downto 0) <= "100100001"; elsif CAS_LATENCY = 2.5 then addr_x(8 downto 0) <= "101100001"; elsif CAS_LATENCY = 3.0 then addr_x(8 downto 0) <= "100110001"; end if; cycles_x <= CYCLES_MODE - 1; busy_x <= CYCLES_MODE > 1; state_x <= STATE_INIT_5; ------------------------------------------------------------ -- Precharge all banks and wait for the command to complete ------------------------------------------------------------ when STATE_INIT_5 => cmd_o <= CMD_PCHG; addr_x(DDR_CMDBIT) <= '1'; cycles_x <= CYCLES_PCHG - 1; busy_x <= CYCLES_PCHG > 1; state_x <= STATE_INIT_6; ------------------------------------------------------------ -- Start the first autorefresh cycle ------------------------------------------------------------ when STATE_INIT_6 => cmd_o <= CMD_RFSH; cycles_x <= CYCLES_RFSH - 1; busy_x <= CYCLES_RFSH > 1; state_x <= STATE_INIT_7; ------------------------------------------------------------ -- Start the second autorefresh cycle ------------------------------------------------------------ when STATE_INIT_7 => cmd_o <= CMD_RFSH; cycles_x <= CYCLES_RFSH - 1; busy_x <= CYCLES_RFSH > 1; rfshTimer_x <= CYCLES_RFSHROW - 1; rfshCounter_x <= 0; state_x <= STATE_INIT_8; ------------------------------------------------------------ -- Clear the DLL reset bit and wait for it to lock ------------------------------------------------------------ when STATE_INIT_8 => cmd_o <= CMD_MODE; if CAS_LATENCY = 2.0 then addr_x(8 downto 0) <= "000100001"; elsif CAS_LATENCY = 2.5 then addr_x(8 downto 0) <= "001100001"; elsif CAS_LATENCY = 3.0 then addr_x(8 downto 0) <= "000110001"; end if; cycles_x <= CYCLES_MODE + CYCLES_DLL - 1; busy_x <= CYCLES_MODE + CYCLES_DLL > 1; state_x <= STATE_IDLE; openFlag_x <= (others => '0'); ------------------------------------------------------------ -- Idle state, process commands and perform refresh cycles ------------------------------------------------------------ when STATE_IDLE => if rfshCounter_r = DDR_RFSHCOUNT then -- Eight rows need to be refreshed, this has priority -- over read/writes. First, all banks will be precharged. if rasFlag_r = false and wrFlag_r = false and rdFlag_r = false then cmd_o <= CMD_PCHG; addr_x(DDR_CMDBIT) <= '1'; cycles_x <= CYCLES_PCHG - 1; state_x <= STATE_RFSH_ROW; busy_x <= CYCLES_PCHG > 1; openFlag_x <= (others => '0'); end if; elsif h_op_i = DDR_MEMCTRL_WRITE then if dec_open = '0' then -- If the row is not open, precharge/open it once all timings are met if rasFlag_r = false and wrFlag_r = false and rdFlag_r = false then cmd_o <= CMD_PCHG; bank_x <= dec_bank; cycles_x <= CYCLES_PCHG - 1; busy_x <= CYCLES_PCHG > 1; state_x <= STATE_OPEN_ROW; end if; elsif rdFlag_r = false then -- Write and acknowledge cmd_o <= CMD_WRITE; bank_x <= dec_bank; addr_x <= (others => '0'); addr_x(dec_col'range) <= dec_col; dqsen_x <= '1'; flag_ack_x <= '1'; wrTimer_x <= CYCLES_WR - 1; wrFlag_x <= CYCLES_WR > 1; end if; elsif h_op_i = DDR_MEMCTRL_READ then if dec_open = '0' then -- If the row is not open, precharge/open it once all timings are met if rasFlag_r = false and wrFlag_r = false and rdFlag_r = false then cmd_o <= CMD_PCHG; bank_x <= dec_bank; cycles_x <= CYCLES_PCHG - 1; busy_x <= CYCLES_PCHG > 1; state_x <= STATE_OPEN_ROW; end if; elsif wrFlag_r = false then -- Read and acknowledge, insert '1' into -- the read status pipeline cmd_o <= CMD_READ; bank_x <= dec_bank; addr_x <= (others => '0'); addr_x(dec_col'range) <= dec_col; flag_ack_x <= '1'; rdPipe_x(0) <= '1'; rdFlag_x <= true; end if; end if; ------------------------------------------------------------ -- Refresh one or more rows using the auto refresh feature ------------------------------------------------------------ when STATE_RFSH_ROW => cmd_o <= CMD_RFSH; cycles_x <= CYCLES_RFSH - 1; busy_x <= CYCLES_RFSH > 1; if rfshTimer_r /= 0 then rfshCounter_x <= rfshCounter_r - 1; else -- Extremely unlikely, but who knows rfshCounter_x <= rfshCounter_r; end if; if rfshCounter_r = 1 then state_x <= STATE_IDLE; end if; ------------------------------------------------------------ -- Open a row after its bank has been precharged ------------------------------------------------------------ when STATE_OPEN_ROW => cmd_o <= CMD_OPEN; cycles_x <= CYCLES_OPEN - 1; busy_x <= CYCLES_OPEN > 1; state_x <= STATE_IDLE; rasTimer_x <= CYCLES_RAS - 1; rasFlag_x <= CYCLES_RAS > 1; -- Send the bank/row address bank_x <= dec_bank; addr_x <= (others => '0'); addr_x(dec_row'range) <= dec_row; -- Mark the row as open openFlag_x(dec_bank_idx) <= '1'; openRows_x(dec_bank_idx) <= dec_row; end case; end if; end process fsm; update: process(clk0) begin if rising_edge(clk0) then if rst = '1' then -- Perform a synchronous reset and put -- this core into the power-up state state_r <= STATE_PWRUP; cycles_r <= 0; busy_r <= false; wrTimer_r <= 0; wrFlag_r <= false; rdFlag_r <= false; rasTimer_r <= 0; rasFlag_r <= false; rfshTimer_r <= 0; rfshCounter_r <= 0; openRows_r <= (others => (others => '0')); openFlag_r <= (others => '0'); flag_ack_r <= '0'; rdPipe_r <= (others => '0'); dqsen_r <= '0'; dqo_r <= (others => '0'); else state_r <= state_x; cycles_r <= cycles_x; busy_r <= busy_x; wrTimer_r <= wrTimer_x; wrFlag_r <= wrFlag_x; rdFlag_r <= rdFlag_x; rasTimer_r <= rasTimer_x; rasFlag_r <= rasFlag_x; rfshTimer_r <= rfshTimer_x; rfshCounter_r <= rfshCounter_x; openRows_r <= openRows_x; openFlag_r <= openFlag_x; flag_ack_r <= flag_ack_x; rdPipe_r <= rdPipe_x; dqsen_r <= dqsen_x; dqo_r <= dqo_x; end if; end if; end process update; end architecture;