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

Library UNISIM;
use UNISIM.vcomponents.all;

entity ddr_phy is
	Port (
		sys_rst			: in  STD_LOGIC;
		sys_clk0		: in  STD_LOGIC;
		sys_clk270		: in  STD_LOGIC;
		u_tag_in		: in  user_tag_t;
		u_tag_rd		: out user_tag_t;
		u_tag_wr		: out user_tag_t;
		read_clk		: in  STD_LOGIC;
		phy_ctrl		: in  phy_ctrl_t;
		part_ctrl		: in  part_ctrl_t;
		sdr_data_w		: in  unsigned(SDR_DATA_WIDTH-1 downto 0);
		sdr_dm_wr_in	: in  unsigned(SDR_DM_WIDTH-1 downto 0);
		sdr_dm_rd_in	: in  unsigned(SDR_DM_WIDTH-1 downto 0);
		sdr_dm_rd_out	: out unsigned(SDR_DM_WIDTH-1 downto 0);
		sdr_data_r		: out unsigned(SDR_DATA_WIDTH-1 downto 0);
		sdr_data_vld	: out STD_LOGIC;
		sdr_data_req_w	: out STD_LOGIC;
		sdr_data_req_r	: out STD_LOGIC;
		part_clk_p		: out STD_LOGIC;
		part_clk_n		: out STD_LOGIC;
		part_dm			: out unsigned(DDR_DM_WIDTH-1 downto 0);
		part_dqs		: inout unsigned(DDR_DQS_WIDTH-1 downto 0);
		part_data		: inout unsigned(DDR_DATA_WIDTH-1 downto 0);
		part_ba			: out unsigned(DDR_BANK_WIDTH-1 downto 0);
		part_addr		: out unsigned(DDR_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 ddr_phy;

architecture tech of ddr_phy is

	signal dqs_drive		: std_logic;
	signal drive			: std_logic;
	signal dqs				: unsigned(DDR_DQS_WIDTH-1 downto 0);  
	signal dqs_zen			: unsigned(DDR_DQS_WIDTH-1 downto 0);  
	signal dqs_rst			: std_logic;
	signal ddr_data_w		: unsigned(DDR_DATA_WIDTH-1 downto 0);
	signal data_r			: unsigned(SDR_DATA_WIDTH-1 downto 0);
	signal data_reg_r		: unsigned(SDR_DATA_WIDTH-1 downto 0);
	signal drive270			: unsigned(DDR_DATA_WIDTH-1 downto 0);
	signal part_ctrl_reg	: part_ctrl_t;
	signal we_reg			: std_logic;
	signal read_en			: 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;
	
	type dm_out_array_t is array (natural range 0 to 4) of unsigned(SDR_DM_WIDTH-1 downto 0);
	signal dm_out_pipe : dm_out_array_t;

begin

------------------------------------------------------------------------------------------------------------------------------------------------
utag_pipe:
	process(sys_rst, sys_clk0)
	begin
		if rising_edge(sys_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) <= u_tag_in;
			end if;	
		end if;
	end process;

dmout_pipe:
	process(sys_rst, sys_clk0)
	begin
		if rising_edge(sys_clk0) then
			for i in dm_out_pipe'length-1 downto 1 loop
				dm_out_pipe(i) <= dm_out_pipe(i-1);
			end loop;	
			if phy_ctrl.utag_we = '1' then
				dm_out_pipe(0) <= sdr_dm_rd_in;
			end if;	
		end if;
	end process;

WE_REGISTER:
	process(sys_rst, sys_clk0)
	begin
		if sys_rst = '1' then
			we_reg <= '0';
		elsif rising_edge(sys_clk0) then
			we_reg	<= phy_ctrl.we;
		end if;
	end process;
	
DATA_DRIVE_GEN:
	process(sys_clk0)
	begin
		if falling_edge(sys_clk0) then
			dqs_rst		<= not we_reg;
			drive 		<= we_reg;
		end if;
	end process;

DQS_DRIVE_GEN:
	process(sys_clk0)
	variable p : unsigned(1 downto 0);
	begin
		if rising_edge(sys_clk0) then
			if phy_ctrl.drive_en = '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	=> sys_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	=> sys_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 DDR_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	=> ddr_data_w(n),						-- 1-bit DDR output
			C	=> sys_clk270,							-- 1-bit clock input
			CE	=> '1',									-- 1-bit clock enable input
			D1	=> sdr_data_w(n + DDR_DATA_WIDTH),		-- 1-bit data input (positive edge)
			D2	=> sdr_data_w(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 (sys_clk270)
	begin  
		if rising_edge(sys_clk270) then
			for n in 0 to DDR_DATA_WIDTH-1 loop
               drive270(n) <= drive;
			end loop;
		end if;
	end process;

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

------------------------------------------------------------------------------------------------------------------------------------------------
-- Data-mask OUT DDR-FFs
gen_ddr_dm_out:
	for n in 0 to DDR_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	=> sys_clk270,							-- 1-bit clock input
			CE	=> '1',									-- 1-bit clock enable input
			D1	=> sdr_dm_wr_in(n + DDR_DM_WIDTH),		-- 1-bit data input (positive edge)
			D2	=> sdr_dm_wr_in(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 DDR_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	=> sys_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;

-- Tristate-Control fuer dqs      
	process (sys_clk0) is
	variable zctrl : boolean; 
	begin
		if rising_edge(sys_clk0) then
			if dqs_drive = '1' then
				zctrl := false;
			else
				zctrl := true;
			end if;
			if zctrl then 
				dqs_zen <= (others => '1');
			else
				dqs_zen <= (others => '0');
			end if;	
		end if;
	end process;

-- Tristate-Buffer fuer dqs      
gen_out_mux_dqs:
	for n in 0 to DDR_DQS_WIDTH-1 generate
		part_dqs(n) <= 'Z' when dqs_zen(n)='1' else dqs(n);
	end generate gen_out_mux_dqs;

------------------------------------------------------------------------------------------------------------------------------------------------
	process (sys_rst, sys_clk0) is
	begin  
		if sys_rst = '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';
		elsif rising_edge(sys_clk0) then
			part_ctrl_reg <= part_ctrl;
		end if;
	end process;

	process (sys_clk0) is
	begin  
		if falling_edge(sys_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;

-----------------------------------------------------------------
-- READ DATA Processing
-----------------------------------------------------------------
gen_ddr_data_in:
	for n in 0 to DDR_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 + DDR_DATA_WIDTH),	-- 1-bit output for negative edge of clock
			C				=> read_clk, 					-- 1-bit clock input
			CE				=> read_en,						-- 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 (sys_clk0)
	begin
		if falling_edge(sys_clk0) then
			data_reg_r	<= data_r;
		end if;
	end process;

misc_flags_and_data_out:
	process (sys_rst, sys_clk0)
	variable p : unsigned(3 downto 0);
	begin
		if sys_rst = '1' then
			p				:= (others => '0');
			read_en			<= '0';
			sdr_data_vld	<= '0';
			sdr_data_req_w	<= '0';
			sdr_data_req_r	<= '0';
		elsif rising_edge(sys_clk0) then
			sdr_data_r		<= data_reg_r;
			if p(3) = '1' then
				u_tag_rd		<= u_tag_pipe(4);
				sdr_dm_rd_out	<= dm_out_pipe(4);
			end if;	
			if phy_ctrl.we = '1' then
				u_tag_wr		<= u_tag_pipe(0);
			end if;	
			sdr_data_req_w	<= phy_ctrl.we;
			sdr_data_req_r	<= phy_ctrl.re;
			sdr_data_vld	<= p(3);
			read_en			<= p(1);
			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 process;

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