-------------------------------------------------------------------------
-- 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_config.all;
use work.sdram_types.all;

entity sdram_cmd is
	Generic (BL 		: natural := 2);
	Port (
		rst				: in  STD_LOGIC;
		clk				: in  STD_LOGIC;
		enable			: in  STD_LOGIC;
		u_tag_in		: in  user_tag_t;
		u_tag_out		: out user_tag_t;
		phy_ctrl		: out phy_ctrl_t;
		cmd				: in  sdr_cmd_t;
		cmd_we			: in  STD_LOGIC;
		cmd_ack			: out STD_LOGIC;
		col_addr		: in col_addr_t;
		mode_word		: in  mode_word_t;
		sdr_cmd_ctrl	: out sdr_cmd_lines_t;
		sdr_addr		: out  sdr_addr_t;
		sdr_ba			: out  sdr_ba_t
	);
end sdram_cmd;

architecture behaviour of sdram_cmd is

	signal st_sdr, st_sdr_next : sdr_state_t;
	
	signal cc_preset : natural range 0 to 10;
	signal cc_load_en : std_logic;
	signal cycle_finished : std_logic;

	signal burst_preset : natural range 0 to 3;
	signal burst_load_en : std_logic;
	signal burst_finished : std_logic;

begin

	u_tag_out <= u_tag_in;
	
------------------------------------------------------------------------------------------

fsm_sdr_state:
	process (st_sdr, cmd, cmd_we, cycle_finished, burst_finished, mode_word, enable, col_addr)
	begin
		st_sdr_next		<= st_sdr;
		sdr_cmd_ctrl <= COMMAND(SD_NOP);
		
		cc_load_en <= '0';
		cc_preset <= TIMING(cmd);
		burst_load_en <= '0';
		cmd_ack	<= '0';
		burst_preset <= BL/2-1;
		phy_ctrl.re <= '0';
		phy_ctrl.drive_en <= '0';
		phy_ctrl.we <= '0';
		phy_ctrl.utag_we <= '0';
		sdr_addr <= mode_word(sdr_addr_t'left downto sdr_addr_t'right);
		sdr_ba	 <= mode_word(mode_word_t'left downto mode_word_t'left-1);
		
		case st_sdr is 
		when PWR_DOWN =>
			sdr_cmd_ctrl <= COMMAND(SD_DESELECT);
			if enable = '1' then
				st_sdr_next <= IDLE;
			end if;	
			
		when PRECHARGE =>
			if cycle_finished = '1' then
				st_sdr_next <= IDLE;
			end if;	

		when MODE =>
			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';
				cc_preset <= TIMING(cmd);
				sdr_cmd_ctrl <= COMMAND(cmd);
				case cmd is
					when SD_PRE =>
						st_sdr_next <= PRECHARGE;
						sdr_addr(BIT_PRE_ALL)	<= mode_word(BIT_PRE_ALL);
					when SD_LMR =>
						st_sdr_next <= MODE;
					when SD_ACT =>
						st_sdr_next <= ROW_ACT;
					when SD_AR =>
						st_sdr_next <= AUTO_REF;
					when others	=> null;
					end case;	
			end if;	
				
		when ROW_ACT =>
			if cycle_finished = '1' then
				if cmd_we = '1' then
					cmd_ack <= '1';
					burst_load_en <= '1';
					cc_load_en <= '1';
					cc_preset <= TIMING(cmd);
					sdr_cmd_ctrl <= COMMAND(cmd);
					case cmd is
					when SD_PRE =>
						st_sdr_next <= PRECHARGE;
					when SD_READ =>
						phy_ctrl.utag_we <= '1';
						st_sdr_next <= READ;
						sdr_addr(col_addr_t'range) <= col_addr;
					when SD_WRITE =>
						phy_ctrl.utag_we <= '1';
						phy_ctrl.drive_en <= '1';
						st_sdr_next <= WRITE;
						sdr_addr(col_addr_t'range) <= col_addr;
					when others	=> null;
					end case;	
				end if;	
			end if;	

		when WRITE =>
			phy_ctrl.drive_en <= '1';
			phy_ctrl.we <= '1';
			if burst_finished = '1' then
				if cmd_we = '1' and cmd = SD_WRITE then
					sdr_addr(col_addr_t'range) <= col_addr;
					cmd_ack <= '1';
					burst_load_en <= '1';
					sdr_cmd_ctrl <= COMMAND(cmd);
					phy_ctrl.utag_we <= '1';
				else	
					phy_ctrl.drive_en <= '0';
					cc_load_en <= '1';
					cc_preset <= TIMING(SD_WRITE)+1;
					st_sdr_next <= ROW_ACT;
				end if;
			end if;	

		when READ =>
			phy_ctrl.re <= '1';
			if burst_finished = '1' then
				if cmd_we = '1' and cmd = SD_READ then
					sdr_addr(col_addr_t'range) <= col_addr;
					cmd_ack <= '1';
					burst_load_en <= '1';
					sdr_cmd_ctrl <= COMMAND(cmd);
					phy_ctrl.utag_we <= '1';
				else
					cc_load_en <= '1';
					cc_preset <= TIMING(SD_READ);
					st_sdr_next <= ROW_ACT;
				end if;
			end if;	

		when WRITE_A =>			-- not implemented yet
			cmd_ack <= '1';
			st_sdr_next <= IDLE;

		when READ_A =>			-- not implemented yet
			cmd_ack <= '1';
			st_sdr_next <= IDLE;

		when BURST_STOP =>		-- not implemented yet
			cmd_ack <= '1';
			st_sdr_next <= IDLE;
			
		when SELF_REF =>		-- not implemented yet
			cmd_ack <= '1';
			st_sdr_next <= IDLE;
			
		when PRE_PWR_DOWN =>	-- not implemented yet
			cmd_ack <= '1';
			st_sdr_next <= IDLE;

		when ACT_PWR_DOWN =>	-- not implemented yet
			cmd_ack <= '1';
			st_sdr_next <= IDLE;
		
		when AUTO_REF =>
			if cycle_finished = '1' then
				st_sdr_next <= IDLE;
			end if;	
			
		when others =>
			st_sdr_next <= IDLE;
		end case;
		
	end process;
	
fsm_sdr_state_next:
	process (rst, clk)
	begin
		if rst = '1' then
			st_sdr		<= PWR_DOWN;
		elsif rising_edge(clk) then
			st_sdr		<= st_sdr_next;
		end if;
	end process;
	
------------------------------------------------------------------------------------------
cycle_counter:
	process (rst, clk)
	variable cycle_cnt : natural range 0 to 10;
	begin
		if rst = '1' then
			cycle_cnt	:= 0;
			cycle_finished	<= '0';
		elsif rising_edge(clk) then
			cycle_finished	<= '0';
			if cc_load_en = '1' then
				cycle_cnt := cc_preset;
			elsif cycle_cnt /= 0 then
				cycle_cnt := cycle_cnt - 1;
			else
				cycle_finished	<= '1';
			end if;	
		end if;
	end process;

------------------------------------------------------------------------------------------
burst_counter:
	process (rst, clk)
	variable burst_cnt : natural range 0 to 3;
	begin
		if rst = '1' then
			burst_cnt	:= 0;
		elsif rising_edge(clk) then
			burst_finished	<= '0';
			if burst_load_en = '1' then
				burst_cnt := burst_preset;
			elsif burst_cnt /= 0 then
				burst_cnt := burst_cnt - 1;
			end if;	
			if burst_cnt = 0 then
				burst_finished	<= '1';
			end if;	
		end if;
	end process;

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