-------------------------------------------------------------------------
-- Project: SDRAM controller
-- This file: SDRAM main controller and user I/F
--
-- 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;
use work.utils_pkg.all;

entity sdram_ctrl is
	Generic
	(
		F_SYSCLK		: real		:= 100.0
	);
	Port (
		rst				: in  STD_LOGIC;
		clk				: in  STD_LOGIC;
		u_busy			: out STD_LOGIC;
		u_tag_in		: in  user_tag_t;
		u_tag_out		: out user_tag_t;
		u_addr			: in  user_addr_t;
		u_cmd			: in  user_cmd_t;
		u_cmd_we		: in  STD_LOGIC;
		col_addr		: out col_addr_t;
		sdr_cmd_busy	: in  STD_LOGIC;
		sdr_cmd			: out  sdr_cmd_t;
		sdr_cmd_we		: out  STD_LOGIC;
		sdr_mode		: out  mode_word_t
	);
end sdram_ctrl;

architecture behaviour of sdram_ctrl is

	type ctrl_state_t is (RESET, POWER_WAIT, INIT, INIT_WAIT, USER_READY, USER_WRITE_PRE, USER_WRITE_ACT, USER_WRITE, USER_READ_PRE, USER_READ_ACT, USER_READ, REFRESH_PRE, REFRESH);
	signal st_ctrl, st_ctrl_next : ctrl_state_t;
	
	constant PWR_UP_CLOCK_INTERVAL : natural := to_cycles(PWR_UP_WAIT, F_SYSCLK);
	signal pwr_up_cnt : natural range 0 to PWR_UP_CLOCK_INTERVAL-1;
	signal pwr_up_cnt_rst : std_logic;
	signal pwr_up_finished : std_logic;

	signal cycle_cnt : natural range 0 to 255;
	signal cc_preset : natural range 0 to 255;
	signal cc_load_en : std_logic;
	signal cycle_finished : std_logic;

	signal seq_cnt : natural range 0 to 31;
	signal seq_rst_en : std_logic;
	signal seq_cnt_en : std_logic;
	
	constant REFRESH_CLOCK_INTERVAL : natural := to_cycles(REFRESH_INTERVAL, F_SYSCLK);
	signal refresh_cnt : natural range 0 to REFRESH_CLOCK_INTERVAL-1;
	signal refresh_request : std_logic;
	signal refresh_cnt_rst : std_logic;

	signal addr_reg_load_en : std_logic;

	signal u_tag_reg : user_tag_t;
	signal bank_addr_reg : bank_addr_t;
	signal row_addr_reg : row_addr_t;
	signal col_addr_reg : col_addr_t;
	signal bank_addr_last : bank_addr_t;
	signal row_addr_last : row_addr_t;
	signal col_addr_last : col_addr_t;

	signal u_bank_addr : bank_addr_t;
	signal u_row_addr : row_addr_t;
	signal u_col_addr : col_addr_t;

	signal bank_active_reg : unsigned(0 to 3);
	signal bank_is_active : std_logic;
	signal bank_activate : std_logic;
	signal bank_clr : std_logic;

	type init_seq_rom_t is array (0 to 14) of init_seq_t;
	
	constant init_seq_rom : init_seq_rom_t :=
	(
		(
			cmd			=> SD_NOP,
			mode_word	=> (others => '0'),
			wait_cycle	=> 0
		),
		(
			cmd			=> SD_PRE,
			mode_word	=> "000010000000000",
			wait_cycle	=> 0
		),
		(
			cmd			=> SD_NOP,
			mode_word	=> (others => '0'),
			wait_cycle	=> 0
		),
		(
			cmd			=> SD_LMR,
			mode_word	=> to_unsigned(LMR_REG_EXTENDED, 2) & "0000000000010",
			wait_cycle	=> 0
		),
		(
			cmd			=> SD_NOP,
			mode_word	=> (others => '0'),
			wait_cycle	=> 0
		),
		(
			cmd			=> SD_LMR,
			mode_word	=> to_unsigned(LMR_REG_BASE, 2) & to_unsigned(LMR_OP_RES_DLL, 6) & to_unsigned(LMR_CL2, 3) & '0' & to_unsigned(LMR_BL_CURR, 3),
			wait_cycle	=> 200
		),
		(
			cmd			=> SD_NOP,
			mode_word	=> (others => '0'),
			wait_cycle	=> 0
		),
		(
			cmd			=> SD_PRE,
			mode_word	=> "000010000000000",
			wait_cycle	=> 0
		),
		(
			cmd			=> SD_NOP,
			mode_word	=> (others => '0'),
			wait_cycle	=> 0
		),
		(
			cmd			=> SD_AR,
			mode_word	=> (others => '0'),
			wait_cycle	=> 0
		),
		(
			cmd			=> SD_NOP,
			mode_word	=> (others => '0'),
			wait_cycle	=> 0
		),
		(
			cmd			=> SD_AR,
			mode_word	=> (others => '0'),
			wait_cycle	=> 0
		),
		(
			cmd			=> SD_NOP,
			mode_word	=> (others => '0'),
			wait_cycle	=> 0
		),
		(
			cmd			=> SD_LMR,
			mode_word	=> to_unsigned(LMR_REG_BASE, 2) & to_unsigned(LMR_OP_NORMAL, 6) & to_unsigned(LMR_CL2, 3) & '0' & to_unsigned(LMR_BL_CURR, 3),
			wait_cycle	=> 200
		),
		(
			cmd			=> SD_NOP,
			mode_word	=> (others => '0'),
			wait_cycle	=> 0
		)
	
	);
		
begin

	u_row_addr		<= u_addr(user_addr_t'left downto user_addr_t'left-row_addr_t'length+1);
	u_bank_addr		<= u_addr(user_addr_t'left-row_addr_t'length downto user_addr_t'left-row_addr_t'length-bank_addr_t'length+1);
	u_col_addr		<= u_addr(user_addr_t'left-row_addr_t'length-bank_addr_t'length downto 0);
	bank_is_active	<= bank_active_reg(to_integer(u_bank_addr));

------------------------------------------------------------------------------------------
fsm_ctrl_state:
	process (st_ctrl, u_tag_in, u_tag_reg, u_cmd, u_cmd_we, sdr_cmd_busy, pwr_up_finished, seq_cnt, cycle_finished, u_bank_addr, u_row_addr, bank_addr_reg, row_addr_reg, u_col_addr, col_addr_reg, bank_addr_last, row_addr_last, refresh_request, bank_is_active)
	begin

		u_busy 				<= '1';
		pwr_up_cnt_rst		<= '0';
		cc_preset			<= init_seq_rom(seq_cnt).wait_cycle;
		cc_load_en			<= '0';
		sdr_cmd				<= init_seq_rom(seq_cnt).cmd;
		sdr_cmd_we			<= '0';
		sdr_mode			<= bank_addr_reg & row_addr_reg;
		col_addr			<= col_addr_reg;
		seq_cnt_en			<= '0';
		seq_rst_en			<= '0';
		addr_reg_load_en	<= '0';
		refresh_cnt_rst		<= '0';
		bank_activate		<= '0';
		bank_clr			<= '0';
		u_tag_out			<= u_tag_reg;
		
		st_ctrl_next		<= st_ctrl;
		case st_ctrl is 
		when RESET =>
			pwr_up_cnt_rst	<= '1';
			st_ctrl_next	<= POWER_WAIT;
			
		when POWER_WAIT =>
			if pwr_up_finished = '1' then
				seq_rst_en		<= '1';
				st_ctrl_next	<= INIT;
			end if;	

		when INIT =>
			sdr_mode	<= init_seq_rom(seq_cnt).mode_word;
			sdr_cmd		<= init_seq_rom(seq_cnt).cmd;
			cc_preset	<= init_seq_rom(seq_cnt).wait_cycle;
			if seq_cnt = init_seq_rom_t'high then
				bank_clr		<= '1';
				refresh_cnt_rst	<= '1';
				st_ctrl_next	<= USER_READY;
			elsif sdr_cmd_busy = '0' then
				cc_load_en		<= '1';
				sdr_cmd_we		<= '1';
				st_ctrl_next	<= INIT_WAIT;
			end if;	
				
		when INIT_WAIT =>
			if sdr_cmd_busy = '0' and cycle_finished = '1' then
				seq_cnt_en		<= '1';
				st_ctrl_next	<= INIT;
			end if;	
			
		when USER_READY =>
			if refresh_request = '1' then
				st_ctrl_next	<= REFRESH_PRE;
			else	
				u_busy		<= '0';
				col_addr				<= u_col_addr;
				u_tag_out				<= u_tag_in;
				sdr_mode				<= u_bank_addr & u_row_addr;
				sdr_mode(BIT_AUTO_PRE)	<= ENABLE_AUTO_PRE;
				if u_cmd_we = '1' then
					addr_reg_load_en		<= '1';
					case u_cmd is
					when UCMD_NOP =>
						sdr_cmd		<= SD_NOP;
					when UCMD_LMR =>
						sdr_cmd		<= SD_NOP;
					when UCMD_WRITE =>
						sdr_cmd					<= SD_WRITE;
						if (u_row_addr /= row_addr_reg) then
							st_ctrl_next		<= USER_WRITE_PRE;
						elsif bank_is_active = '0' then
							st_ctrl_next		<= USER_WRITE_ACT;
						elsif sdr_cmd_busy = '1' then
							st_ctrl_next		<= USER_WRITE;
						else
							sdr_cmd_we			<= '1';
						end if;
					when UCMD_READ =>
						sdr_cmd					<= SD_READ;
						if (u_row_addr /= row_addr_reg) then
							st_ctrl_next		<= USER_READ_PRE;
						elsif bank_is_active = '0' then
							st_ctrl_next		<= USER_READ_ACT;
						elsif sdr_cmd_busy = '1' then
							st_ctrl_next		<= USER_READ;
						else
							sdr_cmd_we			<= '1';
						end if;
					when others => null;
					end case;
				end if;	
			end if;	

		when USER_WRITE_PRE =>
		bank_clr		<= '1';
		sdr_cmd					<= SD_PRE;
		sdr_mode				<= bank_addr_last & row_addr_last;
		sdr_mode(BIT_PRE_ALL)	<= ENABLE_PRE_ALL;
		if sdr_cmd_busy = '0' then
			sdr_cmd_we		<= '1';
			st_ctrl_next	<= USER_WRITE_ACT;
		end if;

		when USER_WRITE_ACT =>
		bank_activate	<= '1';
		sdr_cmd				<= SD_ACT;
		if sdr_cmd_busy = '0' then
			sdr_cmd_we		<= '1';
			st_ctrl_next	<= USER_WRITE;
		end if;

		when USER_WRITE =>
		sdr_cmd					<= SD_WRITE;
		sdr_mode(BIT_AUTO_PRE)	<= ENABLE_AUTO_PRE;
		if sdr_cmd_busy = '0' then
			sdr_cmd_we		<= '1';
			st_ctrl_next	<= USER_READY;
		end if;

		when USER_READ_PRE =>
		bank_clr		<= '1';
		sdr_cmd			<= SD_PRE;
		sdr_mode				<= bank_addr_last & row_addr_last;
		sdr_mode(BIT_PRE_ALL)	<= ENABLE_PRE_ALL;
		if sdr_cmd_busy = '0' then
			sdr_cmd_we		<= '1';
			st_ctrl_next	<= USER_READ_ACT;
		end if;

		when USER_READ_ACT =>
		bank_activate	<= '1';
		sdr_cmd					<= SD_ACT;
		if sdr_cmd_busy = '0' then
			sdr_cmd_we		<= '1';
			st_ctrl_next	<= USER_READ;
		end if;

		when USER_READ =>
		sdr_cmd					<= SD_READ;
		sdr_mode(BIT_AUTO_PRE)	<= ENABLE_AUTO_PRE;
		if sdr_cmd_busy = '0' then
			sdr_cmd_we		<= '1';
			st_ctrl_next	<= USER_READY;
		end if;

		when REFRESH_PRE =>
		bank_clr		<= '1';
		sdr_cmd					<= SD_PRE;
--		sdr_mode				<= bank_addr_last & row_addr_last;
		sdr_mode(BIT_PRE_ALL)	<= ENABLE_PRE_ALL;
		if sdr_cmd_busy = '0' then
			sdr_cmd_we		<= '1';
			st_ctrl_next	<= REFRESH;
		end if;

		when REFRESH =>
		sdr_cmd			<= SD_AR;
		if sdr_cmd_busy = '0' then
			sdr_cmd_we		<= '1';
			st_ctrl_next	<= USER_READY;
			refresh_cnt_rst	<= '1';
		end if;
		
		when others =>
			st_ctrl_next <= RESET;
		end case;
		
	end process;
	
fsm_ctrl_state_next:
	process (clk)
	begin
		if rising_edge(clk) then
			if rst = '1' then
				st_ctrl		<= RESET;
			else
				st_ctrl		<= st_ctrl_next;
			end if;
		end if;
	end process;
	
------------------------------------------------------------------------------------------
bank_active_register:
	process (clk)
	begin
		if rising_edge(clk) then
			if bank_clr = '1' then
				bank_active_reg <= (others => '0');		-- assumes PRE_ALL !!!!!!!!!!!!!
			elsif bank_activate = '1' then	
				bank_active_reg(to_integer(bank_addr_reg)) <= '1';
			end if;	
		end if;
	end process;


------------------------------------------------------------------------------------------
user_addr_register:
	process (clk)
	begin
		if rising_edge(clk) then
			if rst = '1' then
				bank_addr_reg <= (others => '0');
				row_addr_reg <= (others => '0');
				col_addr_reg <= (others => '0');
				u_tag_reg <= (others => '0');
			elsif addr_reg_load_en = '1' then
				bank_addr_last <= bank_addr_reg;
				row_addr_last <= row_addr_reg;
				col_addr_last <= col_addr_reg;
				bank_addr_reg <= u_bank_addr;
				row_addr_reg <= u_row_addr;
				col_addr_reg <= u_col_addr;
				u_tag_reg <= u_tag_in;
			end if;	
		end if;
	end process;

------------------------------------------------------------------------------------------
power_up_counter:
	process (clk)
	begin
		if rising_edge(clk) then
			pwr_up_finished <= '0';
			if pwr_up_cnt_rst = '1' then
				pwr_up_cnt	<= PWR_UP_CLOCK_INTERVAL-1;
			elsif pwr_up_cnt /= 0 then
				pwr_up_cnt <= pwr_up_cnt - 1;
			else
				pwr_up_finished <= '1';
			end if;	
		end if;
	end process;


------------------------------------------------------------------------------------------
refresh_counter:
	process (clk)
	begin
		if rising_edge(clk) then
			refresh_request <= '0';
			if refresh_cnt_rst = '1' then
				refresh_cnt	<= REFRESH_CLOCK_INTERVAL-1;
			elsif refresh_cnt /= 0 then
				refresh_cnt <= refresh_cnt - 1;
			else
				refresh_request <= '1';
			end if;	
		end if;
	end process;

------------------------------------------------------------------------------------------
cycle_counter:
	process (clk)
	begin
		if 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;

------------------------------------------------------------------------------------------
seq_counter:
	process (clk)
	begin
		if rising_edge(clk) then
			if seq_rst_en = '1' then
				seq_cnt <= 0;
			elsif seq_cnt_en = '1' then
				if seq_cnt /= init_seq_rom_t'high then
					seq_cnt <= seq_cnt + 1;
				end if;	
			end if;	
		end if;
	end process;

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