-------------------------------------------------------------------------
-- Project: SDRAM controller
-- This file: DDR physical layer (Virtex-4 specific)
--
-- 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;

Library UNISIM;
use UNISIM.vcomponents.all;

entity sdram_phy is
	Generic
	(
		F_SDRCLK_IN		: real		:= 100.0;
		F_SDRCLK		: real		:= 100.0;
		TPD_BOARD_SIM	: time		:= 0 ns
	);
	Port
	(
		rst				: in  STD_LOGIC;
		clk				: in  STD_LOGIC;
		clk_fb			: in  STD_LOGIC;
		clk0_out		: out STD_LOGIC;
		rst0_out		: out STD_LOGIC;
		phy_in			: in  phy_in_t;
		phy_out			: out phy_out_t;
		phy_ctrl		: in  phy_ctrl_t;
		part_clk		: out STD_LOGIC;
		part_dqm		: out unsigned(PART_DM_WIDTH-1 downto 0);
		part_data		: inout unsigned(PART_DATA_WIDTH-1 downto 0);
		part_ba			: out unsigned(PART_BANK_WIDTH-1 downto 0);
		part_addr		: out unsigned(PART_ADDR_WIDTH-1 downto 0);
		part_cs_n		: out STD_LOGIC;
		part_we_n		: out STD_LOGIC;
		part_cas_n		: out STD_LOGIC;
		part_ras_n		: out STD_LOGIC;
		part_cke		: out STD_LOGIC
	);
end sdram_phy;

architecture tech of sdram_phy is

	signal drive			: std_logic;
	signal data_reg_r		: unsigned(BUS_DATA_WIDTH-1 downto 0);
	signal part_ctrl_reg	: part_ctrl_t;
	signal we_reg			: std_logic;
	
	signal clk0				: std_logic;
	signal clk0_temp		: std_logic;
	signal rst0				: std_logic;
	signal clk0_rd			: std_logic;

	signal locked			: std_logic;  
	signal error			: std_logic;  

	type u_tag_array_t is array (natural range 0 to 4) of user_tag_t;
	signal u_tag_pipe : u_tag_array_t;
	
	attribute KEEP : string;
	attribute KEEP of clk0		: signal is "TRUE";

begin

	clk0_out	<= clk0_temp;
	clk0		<= clk0_temp after 10 ps;		-- fix delta delay
	rst0_out	<= rst0;
	clk0_rd		<= clk_fb;
	
------------------------------------------------------------------------------------------------------------------------------------------------
inst_sdram_clk : entity work.sdram_clk
	GENERIC MAP
	(
		clk_in_freq			=> F_SDRCLK,
		clk0_out_phaseshift	=> 0,
		clk1_out_phaseshift	=> 0
	)
	PORT MAP 
	(
		-- Clocks and Reset
		rst				=> rst,   -- external async reset, low active
		clk_in			=> clk,   -- system clock (e.g. 100MHz), from board
		clk_fb_in		=> clk_fb,   -- feedback clock
		clk0_0_out		=> clk0_temp,  -- System clock #0, dcm#0 output 0°
		clk0_270_out	=> open,  -- System clock #0, dcm#0 output 270°
		clk1_0_out		=> open,  -- System clock #1 (e.g. clock for DDR-SDRAM data capture), dcm#1 output 0°
		clk1_270_out	=> open,  -- System clock #1 (e.g. clock for DDR-SDRAM data capture), dcm#1 output 270°
		locked_out		=> locked,  -- DCM locked status
		error_out		=> error
	);

rst0_gen:
	process(clk0)
	begin
		if rising_edge(clk0) then
			rst0	<= not locked;
		end if;
	end process;
	
------------------------------------------------------------------------------------------------------------------------------------------------
utag_pipe:
	process(clk0)
	begin
		if rising_edge(clk0) then
			for i in u_tag_pipe'length-1 downto 1 loop
				u_tag_pipe(i) <= u_tag_pipe(i-1);
			end loop;	
			if phy_ctrl.utag_we = '1' then
				u_tag_pipe(0) <= phy_ctrl.u_tag;
			end if;	
		end if;
	end process;

WE_REGISTER:
	process(clk0)
	begin
		if rising_edge(clk0) then
			if rst0 = '1' then
				we_reg <= '0';
			else
				we_reg	<= phy_ctrl.we;
			end if;
		end if;
	end process;
	
DATA_DRIVE_GEN:
	process(clk0)
	begin
		if falling_edge(clk0) then
			drive 		<= phy_ctrl.drive_en;
		end if;
	end process;

------------------------------------------------------------------------------------------------------------------------------------------------
-- SDRAM Clock
part_clk		<= clk0 after TPD_BOARD_SIM;

------------------------------------------------------------------------------------------------------------------------------------------------
-- Data OUT FFs
	process (clk0) is
	begin  
		if falling_edge(clk0) then
			part_data <= (others => 'Z') after TPD_BOARD_SIM;
			if (phy_ctrl.drive_en = '1') then
				part_data	<= phy_in.wr_data after TPD_BOARD_SIM;
			end if;
		end if;
	end process;

------------------------------------------------------------------------------------------------------------------------------------------------
-- Data-mask OUT DDR-FFs
	process (clk0) is
	begin  
		if falling_edge(clk0) then
			part_dqm	<= phy_in.wr_dm after TPD_BOARD_SIM;
		end if;
	end process;

------------------------------------------------------------------------------------------------------------------------------------------------
	process (clk0) is
	begin  
		if rising_edge(clk0) then
			if rst0 = '1' then
				part_ctrl_reg.cmd		<= COMMAND(SD_DESELECT); 
				part_ctrl_reg.ba		<= (others=>'0');
				part_ctrl_reg.addr		<= (others=>'0');
				part_ctrl_reg.cke		<= '0';
			else
				part_ctrl_reg 	<= phy_ctrl.part;
			end if;
		end if;
	end process;

	process (clk0) is
	begin  
		if falling_edge(clk0) then
			part_ras_n		<= part_ctrl_reg.cmd.ras_n after TPD_BOARD_SIM;
			part_cas_n		<= part_ctrl_reg.cmd.cas_n after TPD_BOARD_SIM;
			part_we_n 		<= part_ctrl_reg.cmd.we_n after TPD_BOARD_SIM;
			part_cs_n 		<= part_ctrl_reg.cmd.cs_n after TPD_BOARD_SIM;
			part_ba   		<= part_ctrl_reg.ba after TPD_BOARD_SIM;
			part_addr    	<= part_ctrl_reg.addr after TPD_BOARD_SIM;
			part_cke  		<= part_ctrl_reg.cke after TPD_BOARD_SIM;
		end if;
	end process;

-----------------------------------------------------------------
-- READ DATA Processing
-----------------------------------------------------------------
data_sample_stage:
	process (clk0_rd)
	begin
		if rising_edge(clk0_rd) then
			data_reg_r	<= part_data;
		end if;
	end process;

misc_flags_and_data_out:
	process (clk0)
	variable p : unsigned(3 downto 0);
	begin
		if rising_edge(clk0) then
			if rst0 = '1' then
				p					:= (others => '0');
				phy_out.rd_data_we	<= '0';
				phy_out.wr_data_re	<= '0';
			else
				phy_out.rd_data		<= data_reg_r;
				if p(3) = '1' then
					phy_out.tag_rd		<= u_tag_pipe(4);
				end if;	
				if phy_ctrl.we = '1' then
					phy_out.tag_wr		<= u_tag_pipe(0);
				end if;	
				phy_out.wr_data_re	<= phy_ctrl.drive_en;
				phy_out.rd_data_we	<= p(2);
				if phy_ctrl.re = '1' then
					p := p(p'left-1 downto 0) & '1';
				else
					p := p(p'left-1 downto 0) & '0';
				end if;	
			end if;	
		end if;
	end process;

------------------------------------------------------------------------------------------
end tech;
