-------------------------------------------------------------------------
-- 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		: 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_p		: out STD_LOGIC;
		part_clk_n		: out STD_LOGIC;
		part_dm			: out unsigned(PART_DM_WIDTH-1 downto 0);
		part_dqs		: inout unsigned(PART_DQS_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 dqs_drive		: std_logic;
	signal drive			: std_logic;
	signal dqs				: unsigned(PART_DQS_WIDTH-1 downto 0);  
	signal dqs_zen			: unsigned(PART_DQS_WIDTH-1 downto 0);  
	signal dqs_rst			: std_logic;
	signal wr_data			: unsigned(PART_DATA_WIDTH-1 downto 0);
	signal data_r			: unsigned(BUS_DATA_WIDTH-1 downto 0);
	signal data_reg_r		: unsigned(BUS_DATA_WIDTH-1 downto 0);
	signal drive270			: unsigned(PART_DATA_WIDTH-1 downto 0);
	signal part_ctrl_reg	: part_ctrl_t;
	
	signal clk0				: std_logic;
	signal clk0_temp		: std_logic;
	signal rst0				: std_logic;
	signal clk0_rd			: std_logic;
	signal clk270_rd		: std_logic;
	signal clk270			: std_logic;


	signal locked			: std_logic;  
	signal error			: std_logic;  

	signal we_cl_pipe		: unsigned(TCAS-1 downto 0);
	signal re_cl_pipe		: unsigned(TCAS+2 downto 0);	-- +2 = number of pipe registers used here
	
	type u_tag_array_t is array (natural range 0 to TCAS+3) 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;

	phy_out.wr_data_re	<= we_cl_pipe(we_cl_pipe'left);
	phy_out.rd_data_we	<= re_cl_pipe(re_cl_pipe'left);
	phy_out.tag_rd		<= u_tag_pipe(u_tag_pipe'high);
	phy_out.tag_wr		<= u_tag_pipe(1);

------------------------------------------------------------------------------------------------------------------------------------------------
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	=> clk270,  -- System clock #0, dcm#0 output 270°
		clk1_0_out		=> clk0_rd,  -- System clock #1 (e.g. clock for DDR-SDRAM data capture), dcm#1 output 0°
		clk1_270_out	=> clk270_rd,  -- 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_pipe:
	process(clk0)
	begin
		if rising_edge(clk0) then
			if rst0 = '1' then
				we_cl_pipe	<= (others => '0');
			else
				for i in we_cl_pipe'length-1 downto 1 loop
					we_cl_pipe(i) <= we_cl_pipe(i-1);
				end loop;	
				we_cl_pipe(0) <= phy_ctrl.we;
			end if;
		end if;
	end process;

re_pipe:
	process(clk0)
	begin
		if rising_edge(clk0) then
			if rst0 = '1' then
				re_cl_pipe	<= (others => '0');
			else
				for i in re_cl_pipe'length-1 downto 1 loop
					re_cl_pipe(i) <= re_cl_pipe(i-1);
				end loop;	
				re_cl_pipe(0) <= phy_ctrl.re;
			end if;
		end if;
	end process;

DATA_DRIVE_GEN:
	process(clk0)
	begin
		if falling_edge(clk0) then
			dqs_rst		<= not we_cl_pipe(we_cl_pipe'left);
			drive 		<= we_cl_pipe(we_cl_pipe'left);
		end if;
	end process;

DQS_DRIVE_GEN:
	process(clk0)
	variable p : unsigned(1 downto 0);
	begin
		if rising_edge(clk0) then
			if phy_ctrl.we = '1' then
				p := (others => '1');
			else
				p := p(p'left-1 downto 0) & '0';
			end if;
		end if;
		dqs_drive <= p(p'left);
	end process;

------------------------------------------------------------------------------------------------------------------------------------------------
-- SDRAM Clock
ODDR_clk_p : ODDR
	generic map
	(
		DDR_CLK_EDGE => "OPPOSITE_EDGE",	-- "OPPOSITE_EDGE" or "SAME_EDGE" 
		INIT => '0',						-- Initial value for Q port ('1' or '0')
		SRTYPE => "SYNC"					-- Reset Type ("ASYNC" or "SYNC")
	) 										
	port map (
		Q	=> part_clk_p,	-- 1-bit DDR output
		C	=> clk0,		-- 1-bit clock input
		CE	=> '1',			-- 1-bit clock enable input
		D1	=> '1',			-- 1-bit data input (positive edge)
		D2	=> '0',			-- 1-bit data input (negative edge)
		R	=> '0',			-- 1-bit reset input
		S	=> '0' 			-- 1-bit set input
	);

ODDR_clk_n : ODDR
	generic map
	(
		DDR_CLK_EDGE => "OPPOSITE_EDGE",	-- "OPPOSITE_EDGE" or "SAME_EDGE" 
		INIT => '0',						-- Initial value for Q port ('1' or '0')
		SRTYPE => "SYNC"					-- Reset Type ("ASYNC" or "SYNC")
	) 										
	port map (
		Q	=> part_clk_n,	-- 1-bit DDR output
		C	=> clk0,		-- 1-bit clock input
		CE	=> '1',			-- 1-bit clock enable input
		D1	=> '0',			-- 1-bit data input (positive edge)
		D2	=> '1',			-- 1-bit data input (negative edge)
		R	=> '0',			-- 1-bit reset input
		S	=> '0' 			-- 1-bit set input
	);

------------------------------------------------------------------------------------------------------------------------------------------------
-- Data OUT DDR-FFs
gen_ddr_data_out:
	for n in 0 to PART_DATA_WIDTH-1 generate
	begin 
	ODDR_data : ODDR
		generic map
		(
			DDR_CLK_EDGE => "SAME_EDGE",				-- "OPPOSITE_EDGE" or "SAME_EDGE" 
			INIT => '0',								-- Initial value for Q port ('1' or '0')
			SRTYPE => "SYNC"							-- Reset Type ("ASYNC" or "SYNC")
		) 										
		port map (
			Q	=> wr_data(n),						-- 1-bit DDR output
			C	=> clk270,							-- 1-bit clock input
			CE	=> '1',									-- 1-bit clock enable input
			D1	=> phy_in.wr_data(n + PART_DATA_WIDTH),		-- 1-bit data input (positive edge)
			D2	=> phy_in.wr_data(n),						-- 1-bit data input (negative edge)
			R	=> '0',									-- 1-bit reset input
			S	=> '0' 									-- 1-bit set input
		);
	end generate gen_ddr_data_out;

-- Sample tristate on clock90
	process (clk270)
	begin  
		if rising_edge(clk270) then
			for n in 0 to PART_DATA_WIDTH-1 loop
               drive270(n) <= drive;
			end loop;
		end if;
	end process;

-- Output mux
out_mux_data:
	for n in 0 to PART_DATA_WIDTH-1 generate
		part_data(n) <= wr_data(n) when drive270(n) = '1' else 'Z';
	end generate;

------------------------------------------------------------------------------------------------------------------------------------------------
-- Part DM OUT DDR-FFs
gen_ddr_dm_out:
	for n in 0 to PART_DM_WIDTH-1 generate
	begin 
	ODDR_dm : ODDR
		generic map
		(
			DDR_CLK_EDGE => "SAME_EDGE",				-- "OPPOSITE_EDGE" or "SAME_EDGE" 
			INIT => '0',								-- Initial value for Q port ('1' or '0')
			SRTYPE => "SYNC"							-- Reset Type ("ASYNC" or "SYNC")
		) 										
		port map (
			Q	=> part_dm(n),							-- 1-bit DDR output
			C	=> clk270,								-- 1-bit clock input
			CE	=> '1',									-- 1-bit clock enable input
			D1	=> phy_in.wr_dm(n + PART_DM_WIDTH),			-- 1-bit data input (positive edge)
			D2	=> phy_in.wr_dm(n),							-- 1-bit data input (negative edge)
			R	=> '0',									-- 1-bit reset input
			S	=> '0' 									-- 1-bit set input
		);
	end generate gen_ddr_dm_out;

------------------------------------------------------------------------------------------------------------------------------------------------
-- DQS OUT DDR-FFs
gen_dqs_out:
	for n in 0 to PART_DQS_WIDTH-1 generate
	begin 
	ODDR_dqs : ODDR
		generic map
		(
			DDR_CLK_EDGE => "OPPOSITE_EDGE",			-- "OPPOSITE_EDGE" or "SAME_EDGE" 
			INIT => '0',								-- Initial value for Q port ('1' or '0')
			SRTYPE => "SYNC"							-- Reset Type ("ASYNC" or "SYNC")
		) 										
		port map (
			Q	=> dqs(n),								-- 1-bit DDR output
			C	=> clk0,								-- 1-bit clock input
			CE	=> '1',									-- 1-bit clock enable input
			D2	=> '0',									-- 1-bit data input (positive edge)
			D1	=> '1',									-- 1-bit data input (negative edge)
			R	=> dqs_rst,								-- 1-bit reset input
			S	=> '0' 									-- 1-bit set input
		);
	end generate gen_dqs_out;

-- Part DQS Z-Control  
proc_dqs_zen:
	process (clk0) is
	begin
		if rising_edge(clk0) then
			dqs_zen <= (others => not dqs_drive);
		end if;
	end process;
	
gen_out_mux_dqs:
	for n in 0 to PART_DQS_WIDTH-1 generate
		part_dqs(n) <= 'Z' when dqs_zen(n)='1' else dqs(n);
	end generate gen_out_mux_dqs;

-- Part Control signals
	process (clk0) is
	begin  
		if falling_edge(clk0) then
			part_ras_n		<= part_ctrl_reg.cmd.ras_n;
			part_cas_n		<= part_ctrl_reg.cmd.cas_n; 
			part_we_n 		<= part_ctrl_reg.cmd.we_n; 
			part_cs_n 		<= part_ctrl_reg.cmd.cs_n; 
			part_ba   		<= part_ctrl_reg.ba; 
			part_addr    	<= part_ctrl_reg.addr;
			part_cke  		<= part_ctrl_reg.cke;
		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;

-----------------------------------------------------------------
-- READ DATA Processing
-----------------------------------------------------------------
gen_ddr_data_in:
	for n in 0 to PART_DATA_WIDTH-1 generate
	begin 
	IDDR_data : IDDR 
		generic map
		(
			DDR_CLK_EDGE	=> "SAME_EDGE",					-- "OPPOSITE_EDGE", "SAME_EDGE" or "SAME_EDGE_PIPELINED" 
			INIT_Q1			=> '0', 						-- Initial value of Q1: '0' or '1'
			INIT_Q2			=> '0',							-- Initial value of Q2: '0' or '1'
			SRTYPE			=> "SYNC"						-- Set/Reset type: "SYNC" or "ASYNC" 
		)
		port map
		(
			Q1				=> data_r(n),					-- 1-bit output for positive edge of clock 
			Q2				=> data_r(n + PART_DATA_WIDTH),	-- 1-bit output for negative edge of clock
			C				=> clk270_rd, 						-- 1-bit clock input
			CE				=> re_cl_pipe(TCAS),						-- 1-bit clock enable input
			D				=> part_data(n),				-- 1-bit DDR data input
			R				=> '0',							-- 1-bit reset
			S				=> '0' 							-- 1-bit set
		);
	end generate;


data_sample_stage:
	process (clk0_rd)
	begin
		if falling_edge(clk0_rd) then
			data_reg_r	<= data_r;
		end if;
	end process;

data_out_register:
	process (clk0)
	begin
		if rising_edge(clk0) then
			phy_out.rd_data		<= data_reg_r;
		end if;
	end process;

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