-------------------------------------------------------------------------
-- Project: SDRAM controller
-- This file: SDRAM command controller
--
-- Copyright (C) 2007 J. Ahrensfeld
--
-- 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 3 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, see <http://www.gnu.org/licenses/>.
--
-- For questions and ideas, please contact the author at jens@jayfield.org
--
--------------------------------------------------------------------------

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.numeric_std.ALL;

use work.sdram_const.all;
use work.sdram_config.all;
use work.sdram_types.all;

entity sdram_cmd is
	Generic 
	(
		F_SDRCLK		: real		:= 100.0
	);
	Port
	(
		rst				: in  STD_LOGIC;
		clk				: in  STD_LOGIC;
		tag				: in user_tag_t;
		col_addr		: in col_addr_t;
		mode_word		: in  mode_word_t;
		cmd				: in  sdr_cmd_t;
		cmd_we			: in  STD_LOGIC;
		cmd_ack			: out STD_LOGIC;
		phy_ctrl		: out phy_ctrl_t
	);
end sdram_cmd;

architecture behaviour of sdram_cmd is

	subtype cycle_cnt_t is natural range 0 to 15;
	subtype burst_cnt_t is natural range 0 to 2**(LMR_BL_CURR)-1;

	signal cc_load_en : std_logic;
	signal cycle_finished : std_logic;
	signal cycle_cnt : cycle_cnt_t;

	signal burst_finished : std_logic;
	signal burst_cnt : burst_cnt_t;

	signal st_sdr, st_sdr_next : sdr_state_t;

	type part_timing_array_t is array (sdr_cmd_t) of cycle_cnt_t;
	constant TIMING : part_timing_array_t :=
	--	 command                 cycle_cnt
	(	SD_DESELECT         =>   (0        ),
		SD_NOP              =>   (0        ),
		SD_LMR              =>   (TMRD-1   ),
		SD_ACT              =>   (to_cycles(TRCD, F_SDRCLK)-1),
		SD_READ             =>   (TCAS-1   ),
		SD_WRITE            =>   (to_cycles(TWR, F_SDRCLK)-1),
		SD_PRE              =>   (to_cycles(TRP, F_SDRCLK)-1),
		SD_BST              =>   (0        ),
		SD_AR               =>   (to_cycles(TRFC, F_SDRCLK)-1),
		SD_SR               =>   (to_cycles(TRFC, F_SDRCLK)-1)
	);

begin

------------------------------------------------------------------------------------------

fsm_sdr_state:
	process (st_sdr, cmd, cmd_we, cycle_finished, burst_finished, mode_word, col_addr, tag)
	begin
		st_sdr_next			<= st_sdr;
		phy_ctrl.part.cmd	<= COMMAND(SD_NOP);
		phy_ctrl.part.cke	<= '1';
		phy_ctrl.u_tag		<= tag;
		cc_load_en			<= '0';
		cmd_ack				<= '0';
		phy_ctrl.re			<= '0';
		phy_ctrl.we			<= '0';
		phy_ctrl.utag_we	<= '0';
		phy_ctrl.part.addr	<= mode_word(phy_ctrl.part.addr'left downto phy_ctrl.part.addr'right);
		phy_ctrl.part.ba	<= mode_word(mode_word_t'left downto mode_word_t'left-1);
		
		case st_sdr is 
		
		when PWR_DOWN =>
			phy_ctrl.part.cke <= '0';
			phy_ctrl.part.cmd <= COMMAND(SD_DESELECT);
			if cmd_we = '1' then
				cmd_ack <= '1';
				cc_load_en <= '1';
				if cmd = SD_NOP then
					st_sdr_next <= IDLE_WAIT;
				end if;
			end if;
			
		when IDLE_WAIT =>
			if cycle_finished = '1' then
				st_sdr_next <= IDLE;
			end if;	
			
		when IDLE =>
			if cmd_we = '1' then
				cmd_ack <= '1';
				cc_load_en <= '1';
				phy_ctrl.part.cmd <= COMMAND(cmd);
				case cmd is
					when SD_PRE =>
						st_sdr_next <= PRECHARGE;
					when SD_LMR =>
						st_sdr_next <= MODE;
					when SD_ACT =>
						st_sdr_next <= ROW_ACT;
					when SD_AR =>
						st_sdr_next <= AUTO_REF;
					when SD_READ =>
						phy_ctrl.utag_we <= '1';
						st_sdr_next <= READ;
						phy_ctrl.part.addr(col_addr_t'range) <= col_addr;
					when SD_WRITE =>
						phy_ctrl.utag_we <= '1';
						phy_ctrl.we <= '1';
						st_sdr_next <= WRITE;
						phy_ctrl.part.addr(col_addr_t'range) <= col_addr;
					when others	=> null;
					end case;	
			end if;	
				
		when WRITE =>
			phy_ctrl.we <= '1';
			phy_ctrl.part.cmd <= COMMAND(SD_NOP);
			phy_ctrl.part.addr(col_addr_t'range) <= col_addr;
			if burst_finished = '1' then
				if cmd_we = '1' and cmd = SD_WRITE then
					cmd_ack <= '1';
					cc_load_en <= '1';
					phy_ctrl.part.cmd <= COMMAND(cmd);
					phy_ctrl.utag_we <= '1';
				else	
					phy_ctrl.we <= '0';
					st_sdr_next <= IDLE_WAIT;
				end if;
			end if;

		when READ =>
			phy_ctrl.re <= '1';
			phy_ctrl.part.cmd <= COMMAND(SD_NOP);
			phy_ctrl.part.addr(col_addr_t'range) <= col_addr;
			if burst_finished = '1' then
				if cmd_we = '1' and cmd = SD_READ then
					cmd_ack <= '1';
					cc_load_en <= '1';
					phy_ctrl.part.cmd <= COMMAND(cmd);
					phy_ctrl.utag_we <= '1';
				else
					st_sdr_next <= IDLE_WAIT;
				end if;
			end if;

		when PRECHARGE =>
			st_sdr_next <= IDLE_WAIT;

		when MODE =>
			st_sdr_next <= IDLE_WAIT;

		when ROW_ACT =>
			st_sdr_next <= IDLE_WAIT;

		when WRITE_A =>			-- not implemented yet
			st_sdr_next <= IDLE_WAIT;

		when READ_A =>			-- not implemented yet
			st_sdr_next <= IDLE_WAIT;

		when BURST_STOP =>		-- not implemented yet
			st_sdr_next <= IDLE_WAIT;
			
		when SELF_REF =>		-- not implemented yet
			st_sdr_next <= IDLE_WAIT;
			
		when PRE_PWR_DOWN =>	-- not implemented yet
			st_sdr_next <= IDLE_WAIT;

		when ACT_PWR_DOWN =>	-- not implemented yet
			st_sdr_next <= IDLE_WAIT;
		
		when AUTO_REF =>
			st_sdr_next <= IDLE_WAIT;
			
		when others =>
			st_sdr_next <= IDLE;
		end case;
		
	end process;
	
fsm_sdr_state_next:
	process (clk)
	begin
		if rising_edge(clk) then
			if rst = '1' then
				st_sdr		<= PWR_DOWN;
			else
				st_sdr		<= st_sdr_next;
			end if;
		end if;
	end process;
	
------------------------------------------------------------------------------------------
cycle_counter:
	process (clk)
	begin
		if rising_edge(clk) then
			if cc_load_en = '1' then
				cycle_finished	<= '0';
				cycle_cnt <= TIMING(cmd);
			elsif cycle_cnt /= 0 then
				cycle_cnt <= cycle_cnt - 1;
			else
				cycle_finished	<= '1';
			end if;	
		end if;
	end process;

------------------------------------------------------------------------------------------
burst_counter:
	process (clk)
	begin
		if rising_edge(clk) then
			if cc_load_en = '1' then
				burst_finished	<= '0';
				burst_cnt <= burst_cnt_t'high;
				if 2**(LMR_BL_CURR) = DATA_RATE_FACTOR then
					burst_finished	<= '1';
				end if;
			elsif burst_cnt <= DATA_RATE_FACTOR then
				burst_finished	<= '1';
			else
				burst_cnt <= burst_cnt - DATA_RATE_FACTOR;
			end if;	
		end if;
	end process;

------------------------------------------------------------------------------------------
end behaviour;
