-- EMACS settings: -*-  tab-width: 2; indent-tabs-mode: t -*-
-- vim: tabstop=2:shiftwidth=2:noexpandtab
-- kate: tab-width 2; replace-tabs off; indent-width 2;
-- =============================================================================
-- Authors:					Martin Zabel
--
-- Entity:					Physical layer of SDRAM-Controller for Spartan-3E Starter Kit
--
-- Description:
-- 
-- Physical layer used by module :ref:`sdram_ctrl_s3esk <IP:sdram_ctrl_s3esk>`.
--
-- Instantiates input and output buffer components and adjusts the timing for
-- the Spartan-3E Starter Kit Board.
--
-- Clock and Reset Signals
-- ***********************
--
-- +-----------+-----------------------------------------------------------+
-- | Port      | Description                                               |
-- +===========+===========================================================+
-- |clk        | Base clock for command and write data path.               |
-- +-----------+-----------------------------------------------------------+
-- |clk_n      | ``clk`` phase shifted by 180 degrees.                     |
-- +-----------+-----------------------------------------------------------+
-- |clk90      | ``clk`` phase shifted by  90 degrees.                     |
-- +-----------+-----------------------------------------------------------+
-- |clk90_n    | ``clk`` phase shifted by 270 degrees.                     |
-- +-----------+-----------------------------------------------------------+
-- |clk_fb     | Driven by external feedback (sd_ck_fb) of DDR-SDRAM clock |
-- |(on PCB)   | (sd_ck_p). Actually unused, just referenced below.        |
-- +-----------+-----------------------------------------------------------+
-- |clk_fb90   | ``clk_fb`` phase shifted by 90 degrees.                   |
-- +-----------+-----------------------------------------------------------+
-- |clk_fb90_n | ``clk_fb`` phase shifted by 270 degrees.                  |
-- +-----------+-----------------------------------------------------------+
-- |rst        | Reset for ``clk``.                                        |
-- +-----------+-----------------------------------------------------------+
-- |rst180     | Reset for ``clk_n``                                       |
-- +-----------+-----------------------------------------------------------+
-- |rst90      | Reset for ``clk90``.                                      |
-- +-----------+-----------------------------------------------------------+
-- |rst270     | Reset for ``clk270``.                                     |
-- +-----------+-----------------------------------------------------------+
-- |rst_fb90   | Reset for ``clk_fb90``.                                   |
-- +-----------+-----------------------------------------------------------+
-- |rst_fb90_n | Reset for ``clk_fb90_n``.                                 |
-- +-----------+-----------------------------------------------------------+
--
--
-- Operation
-- *********
--
-- Command signals and write data are sampled with the rising edge of ``clk``.
--
-- Read data is aligned with ``clk_fb90_n``. Either process data in this clock
-- domain, or connect a FIFO to transfer data into another clock domain of your
-- choice.  This FIFO should capable of storing at least one burst (size BL/2)
-- + start of next burst (size 1).
--
-- Write and read enable (``wren_nxt``, ``rden_nxt``) must be hold for:
--
-- * 1 clock cycle  if BL = 2,
-- * 2 clock cycles if BL = 4, or
-- * 4 clock cycles if BL = 8.
--
-- They must be first asserted with the read and write command. Proper delay is
-- included in this unit.
--
-- The first word to write must be asserted with the write command. Proper
-- delay is included in this unit.
--
-- The SDRAM clock is regenerated in this module. The following timing is
-- chosen for minimum latency (should work up to 100 MHz):
--
-- * ``rising_edge(clk90)``   triggers ``rising_edge(sd_ck_p)``,
-- * ``rising_edge(clk90_n)`` triggers ``falling_edge(sd_ck_p)``.
--
-- XST options: Disable equivalent register removal.
--
-- Synchronous resets are used. Reset must be hold for at least two cycles.
--
-- License:
-- =============================================================================
-- Copyright 2007-2016 Technische Universitaet Dresden - Germany,
--										 Chair of VLSI-Design, Diagnostics and Architecture
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--		http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
-- =============================================================================
--
library ieee;
use ieee.std_logic_1164.all;

library unisim;
use unisim.VComponents.all;

entity [docs]sdram_ctrl_phy_s3esk is
  port (
    clk     : in std_logic;
    clk_n   : in std_logic;
    clk90   : in std_logic;
    clk90_n : in std_logic;
    rst     : in std_logic;
    rst90   : in std_logic;
    rst180  : in std_logic;
    rst270  : in std_logic;

    clk_fb90   : in std_logic;
    clk_fb90_n : in std_logic;
    rst_fb90   : in std_logic;
    rst_fb270  : in std_logic;

    sd_cke_nxt : in std_logic;
    sd_cs_nxt  : in std_logic;
    sd_ras_nxt : in std_logic;
    sd_cas_nxt : in std_logic;
    sd_we_nxt  : in std_logic;
    sd_ba_nxt  : in std_logic_vector(1 downto 0);
    sd_a_nxt   : in std_logic_vector(12 downto 0);

    wren_nxt  : in std_logic;
    wdata_nxt : in std_logic_vector(31 downto 0);

    rden_nxt : in  std_logic;
    rdata    : out std_logic_vector(31 downto 0);
    rstb     : out std_logic;

    sd_ck_p : out   std_logic;
    sd_ck_n : out   std_logic;
    sd_cke  : out   std_logic;
    sd_cs   : out   std_logic;
    sd_ras  : out   std_logic;
    sd_cas  : out   std_logic;
    sd_we   : out   std_logic;
    sd_ba   : out   std_logic_vector(1 downto 0);
    sd_a    : out   std_logic_vector(12 downto 0);
    sd_ldqs : out   std_logic;
    sd_udqs : out   std_logic;
    sd_dq   : inout std_logic_vector(15 downto 0));

end sdram_ctrl_phy_s3esk;

architecture [docs]rtl of sdram_ctrl_phy_s3esk is
  -- memory command: domain clk
  signal sd_cke_r : std_logic := '0';
  signal sd_cs_r  : std_logic := '1';
  signal sd_ras_r : std_logic;
  signal sd_cas_r : std_logic;
  signal sd_we_r  : std_logic;
  signal sd_ba_r  : std_logic_vector(1 downto 0);
  signal sd_a_r   : std_logic_vector(12 downto 0);

  -- control / data signals for write
  signal wren_r_n : std_logic := '1';

  signal dqs_en0_nxt_n : std_logic;
  signal dqs_en0_r_n   : std_logic_vector(1 downto 0) := (others => '1');
  signal dqs_en1_r_n   : std_logic_vector(1 downto 0);  -- inited below
  signal dqs_o_r       : std_logic_vector(1 downto 0);

  signal dq_en0_n   : std_logic;
  signal dq_en1_r_n : std_logic_vector(15 downto 0);  -- inited below
  signal dq_o_r     : std_logic_vector(15 downto 0);
  signal dq_i       : std_logic_vector(15 downto 0);

  signal wdata_r        : std_logic_vector(31 downto 0);
  signal wdata_fal_r    : std_logic_vector(15 downto 0);

  -- control / data signals for read
  signal rden_r  : std_logic := '0';
  signal rden1_r : std_logic := '0';
  signal rden2_r : std_logic := '0';
  signal rden3_r : std_logic := '0';
  signal rstb_r  : std_logic := '0';

  signal rdata_ris_r : std_logic_vector(15 downto 0);
  signal rdata_r     : std_logic_vector(31 downto 0);

  attribute keep : string;
  attribute keep of rden1_r : signal is "true";

begin  -- rtl

  -----------------------------------------------------------------------------
  -- SDRAM clock generation
  --
  -- DO NOT RESET THESE! Reset is hold until clk_fb gets stable which is driven
  -- externally by sd_ck_p.
  -----------------------------------------------------------------------------

  sd_ck_p_off : ODDR2
    generic map (
      INIT   => '0',
      SRTYPE => "SYNC")
    port map (
      C0 => clk90,
      C1 => clk90_n,
      CE => '1',
      D0 => '1',
      D1 => '0',
      Q  => sd_ck_p,
      R  => '0',
      S  => '0');

  sd_ck_n_off : ODDR2
    generic map (
      INIT   => '1',
      SRTYPE => "SYNC")
    port map (
      C0 => clk90,
      C1 => clk90_n,
      CE => '1',
      D0 => '0',
      D1 => '1',
      Q  => sd_ck_n,
      R  => '0',
      S  => '0');

  -----------------------------------------------------------------------------
  -- SDRAM command & address
  -----------------------------------------------------------------------------

  process (clk)
  begin  -- process
    if rising_edge(clk) then
      if rst = '1' then
        sd_cke_r <= '0';
        sd_cs_r  <= '1';                -- Deselect
      else
        sd_cke_r <= sd_cke_nxt;
        sd_cs_r  <= sd_cs_nxt;
      end if;

      sd_ras_r <= sd_ras_nxt;
      sd_cas_r <= sd_cas_nxt;
      sd_we_r  <= sd_we_nxt;
      sd_ba_r  <= sd_ba_nxt;
      sd_a_r   <= sd_a_nxt;
    end if;
  end process;

  sd_cke <= sd_cke_r;
  sd_cs  <= sd_cs_r;
  sd_ras <= sd_ras_r;
  sd_cas <= sd_cas_r;
  sd_we  <= sd_we_r;
  sd_ba  <= sd_ba_r;
  sd_a   <= sd_a_r;

  -----------------------------------------------------------------------------
  -- Write data
  -----------------------------------------------------------------------------
  --
  -- Write Enable, DQS Enable, DQ Enable
  -- NOTE: Do not optimize too much, otherwise timing constraints can't be met.
  --
  dqs_en0_nxt_n <= not (wren_nxt or (not wren_r_n));

  process (clk)
  begin  -- process
    if rising_edge(clk) then
      if rst = '1' then
        wren_r_n    <= '1';
        dqs_en0_r_n <= (others => '1');
      else
        wren_r_n    <= not wren_nxt;
        dqs_en0_r_n <= (others => dqs_en0_nxt_n);
      end if;
    end if;
  end process;

  dq_en0_n <= wren_r_n;

  --
  -- DQS output
  -- NOTE: clock domain change. Timing is critical because destination clock
  -- is only 90 degrees after source clock.
  --

  dqs_out : for i in 0 to 1 generate
    -- Both "tff" and "off" must be placed in the IOB. Thus, due to placement
    -- constraints, the "off" must be reset with rst90.

    tff : FDRSE
      generic map (
        INIT => '1')                    -- disable output
      port map (
        Q    => dqs_en1_r_n(i),
        C    => clk90,
        CE   => '1',
        D    => dqs_en0_r_n(i),
        R    => '0',
        S    => rst90);                 -- disable output

    off    : ODDR2
      generic map (
        SRTYPE => "SYNC")
      port map (
        Q      => dqs_o_r(i),
        C0     => clk90,
        C1     => clk90_n,
        CE     => '1',
        D0     => '1',                  -- same as sd_ck_p
        D1     => '0',                  -- dito
        R      => rst90,                -- only due to placement constraints
        S      => '0');

  end generate;

	-- Explicit instantiation of I/O buffers. Required if entity is part of a
	-- netlist, which is used in another design.
	ldqs_obuf : OBUFT
		port map (
			I => dqs_o_r(0),
			T => dqs_en1_r_n(0),
			O => sd_ldqs);

	udqs_obuf : OBUFT
		port map (
			I => dqs_o_r(1),
			T => dqs_en1_r_n(1),
			O => sd_udqs);

  --
  -- Write Data
  --
  process (clk)
  begin  -- process
    if rising_edge(clk) then
      -- prevent unnecessary toggling
      if wren_nxt = '1' then
        wdata_r <= wdata_nxt;
      end if;
    end if;
  end process;

  process (clk_n)
  begin  -- process
    if rising_edge(clk_n) then
      wdata_fal_r <= wdata_r(31 downto 16);
    end if;
  end process;

  dq_out: for i in 0 to 15 generate
    -- A reset can't be applied to the "tff" because then it must also be
    -- applied to the "iff" which is driven be another clock domain.
    -- (Applying reset to "off" is not a problem.)
    -- Thus, the "tff" is reset only due to reset of dq_en0_n.

    tff : FDRSE
      generic map (
        INIT => '1')                    -- disable output
      port map (
        Q    => dq_en1_r_n(i),
        C    => clk,
        CE   => '1',
        D    => dq_en0_n,
        R    => '0',
        S    => '0');

    off : ODDR2
      generic map (
        SRTYPE => "SYNC")
      port map (
        Q      => dq_o_r(i),
        C0     => clk,
        C1     => clk_n,
        CE     => '1',
        D0     => wdata_r(i),           -- for rising_edge(dqs)
        D1     => wdata_fal_r(i),       -- for falling_edge(dqs)
        R      => '0',
        S      => '0');

  end generate;

  -----------------------------------------------------------------------------
  -- DQ I/O Buffers
  -----------------------------------------------------------------------------

	-- Explicit instantiation of I/O buffers. Required if entity is part of a
	-- netlist, which is used in another design.
	dq_iobuf : for i in 0 to 15 generate
		b : IOBUF
			port map (
				I => dq_o_r(i),
				T => dq_en1_r_n(i),
				IO => sd_dq(i),
				O  => dq_i(i));
	end generate;

  -----------------------------------------------------------------------------
  -- Read data capture
  -----------------------------------------------------------------------------
  -- Register read enable
  -- rden1_r is a separate signal for simple identification in constraints.
  -- This signal must be kept.
  process (clk)
  begin  -- process
    if rising_edge(clk) then
      if rst = '1' then
        rden_r <= '0';
        rden1_r <= '0';
      else
        rden_r  <= rden_nxt;
        rden1_r <= rden_r;
      end if;
    end if;
  end process;

  -- Register read enable with clk_fb90_n which equals sd_ck_p + 270 degrees
  -- delayed by external feedback.
  -- NOTE: Timing constraints must ensure, that read enable is captured with
  -- the rising edge of clk_fb90_n directly following rising edge of clk.
  process (clk_fb90_n)
  begin  -- process
    if rising_edge(clk_fb90_n) then
      if rst_fb270 = '1' then
        rden2_r <= '0';
      else
        rden2_r <= rden1_r;
      end if;
    end if;
  end process;

  -- Delay once more
  -- If timing is critical, then rden3_r should be duplicated before it used as
  -- clock enable for input FFs.
  process (clk_fb90_n)
  begin
    if rising_edge(clk_fb90_n) then
      if rst_fb270 = '1' then
        rden3_r <= '0';
      else
        rden3_r <= rden2_r;
      end if;
    end if;
  end process;

  dq_in : for i in 0 to 15 generate
    iff   : IDDR2
      generic map (
        SRTYPE => "SYNC")
      port map (
        D      => dq_i(i),
        C0     => clk_fb90,
        C1     => clk_fb90_n,
        CE     => rden3_r,
        Q0     => rdata_ris_r(i),
        Q1     => rdata_r(16+i),
        R      => '0',
        S      => '0');
  end generate;

  process (clk_fb90_n)
  begin  -- process
    if rising_edge(clk_fb90_n) then
      -- align both parts of data word
      rdata_r(15 downto 0) <= rdata_ris_r;

      -- align strobe with data
      if rst_fb270 = '1' then
        rstb_r <= '0';
      else
        rstb_r <= rden3_r;
      end if;
    end if;
  end process;

  rdata <= rdata_r;
  rstb  <= rstb_r;

end rtl;