-------------------------------------------------------------------------------
--
-- Copyright (C) 2014
-- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
-- JIVE (Joint Institute for VLBI in Europe) <http://www.jive.nl/>
-- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
--
-- 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/>.
--
-------------------------------------------------------------------------------

LIBRARY IEEE, common_lib, technology_lib;
USE IEEE.STD_LOGIC_1164.ALL;
USE common_lib.common_pkg.ALL;
USE technology_lib.technology_pkg.ALL;

PACKAGE tech_ddr_pkg IS  
  
  -- Gather all DDR parameters in one record
  TYPE t_c_tech_ddr IS RECORD
    -- PHY variant within a technology
    name                              : STRING(1 TO 4);  -- = "DDR3" or "DDR4"
    mts                               : NATURAL;  -- = 800   access rate in mega transfers per second
    master                            : BOOLEAN;  -- = TRUE  TRUE = uniphy master, FALSE = uniphy slave regarding OCT and terminationcontrol for DDR3
    rank                              : STRING(1 TO 6);  -- = "SINGLE" or "DUAL  "
    -- PHY external FPGA IO
    a_w                               : NATURAL;  -- = 16
    a_row_w                           : NATURAL;  -- = 16     = a_w, row address width, via a_w lines
    a_col_w                           : NATURAL;  -- = 10    <= a_w, col address width, via a_w lines
    ba_w                              : NATURAL;  -- = 3
    dq_w                              : NATURAL;  -- = 64
    dqs_w                             : NATURAL;  -- = 8      = dq_w / nof_dq_per_dqs
    dm_w                              : NATURAL;  -- = 8
    dbi_w                             : NATURAL;  -- = 8
    bg_w                              : NATURAL;  -- = 2
    ck_w                              : NATURAL;  -- = 2
    cke_w                             : NATURAL;  -- = 2
    cs_w                              : NATURAL;  -- = 2      = number of chip select lines
    cs_w_w                            : NATURAL;  -- = 1      = true_log2(cs_w), use when the number of chip select lines is converted to a logical address
    odt_w                             : NATURAL;  -- = 2
    -- PHY internal FPGA IO
    terminationcontrol_w              : NATURAL;  -- = 14     internal bus in FPGA
    -- Controller
    rsl                               : NATURAL;  -- = 4      = 2 (use both PHY clock edges) * 2 (PHY transfer at double rate), resolution
    rsl_w                             : NATURAL;  -- = 2      = ceil_log2(rsl)
    command_queue_depth               : NATURAL;  -- = 8
    maxburstsize                      : NATURAL;  -- = 64
    maxburstsize_w                    : NATURAL;  -- = 7      = ceil_log2(maxburstsize+1)
  END RECORD;
      
  FUNCTION func_tech_sel_ddr(g_technology : NATURAL; g_ddr3, g_ddr4 : t_c_tech_ddr) RETURN t_c_tech_ddr;  -- Select DDR3 or DDR4 dependent on the technology
  FUNCTION func_tech_sel_ddr(g_sel        : BOOLEAN;      a,      b : t_c_tech_ddr) RETURN t_c_tech_ddr;  -- Select DDR dependent on the boolean
  
  FUNCTION func_tech_ddr_dq_address_w(  c_ddr : t_c_tech_ddr) RETURN NATURAL;  -- return DDR address width for the DQ data at the PHY mts rate
  FUNCTION func_tech_ddr_ctlr_address_w(c_ddr : t_c_tech_ddr) RETURN NATURAL;  -- return DDR address width for the controller data at the by rsl=4 reduced rate
  FUNCTION func_tech_ddr_ctlr_data_w(   c_ddr : t_c_tech_ddr) RETURN NATURAL;  -- return DDR    data width for the controller data at the by rsl=4 reduced rate
  FUNCTION func_tech_ddr_module_size(   c_ddr : t_c_tech_ddr) RETURN NATURAL;  -- return DDR module size in GByte
  
  --                                                                                                    a   a
  --                                                                 name    mts   master rank      a   row col ba dq  dqs dm dbi bg ck cke cs cs_w odt term rsl rsl_w cqd burst burst_w
  CONSTANT c_tech_ddr3_max                        : t_c_tech_ddr := ("none",  800,  TRUE, "DUAL  ", 16, 16, 10, 3, 64, 8,  8, 0,  0, 2, 2,  2, 1,   2,  14,  4,  2,    4,  64,   7);  -- maximum ranges for record field definitions
  
  CONSTANT c_tech_ddr3_sim_8k                     : t_c_tech_ddr := ("DDR3",  800,  TRUE, "DUAL  ", 10,  1, 10, 3, 64, 8,  8, 0,  0, 2, 2,  2, 1,   2,  14,  4,  2,    4,  64,   7);  -- use a_row to set nof ctrl addr = 2**(cs_w + ba + a_row + a_col - rsl_w)
  CONSTANT c_tech_ddr3_sim_16k                    : t_c_tech_ddr := ("DDR3",  800,  TRUE, "DUAL  ", 10,  2, 10, 3, 64, 8,  8, 0,  0, 2, 2,  2, 1,   2,  14,  4,  2,    4,  64,   7);  -- use a_row to set nof ctrl addr = 2**(cs_w + ba + a_row + a_col - rsl_w)
  CONSTANT c_tech_ddr3_sim_128k                   : t_c_tech_ddr := ("DDR3",  800,  TRUE, "DUAL  ", 10,  5, 10, 3, 64, 8,  8, 0,  0, 2, 2,  2, 1,   2,  14,  4,  2,    4,  64,   7);  -- use a_row to set nof ctrl addr = 2**(cs_w + ba + a_row + a_col - rsl_w)
  CONSTANT c_tech_ddr3_sim_1m                     : t_c_tech_ddr := ("DDR3",  800,  TRUE, "DUAL  ", 10,  8, 10, 3, 64, 8,  8, 0,  0, 2, 2,  2, 1,   2,  14,  4,  2,    4,  64,   7);  -- use a_row to set nof ctrl addr = 2**(cs_w + ba + a_row + a_col - rsl_w)
  
  CONSTANT c_tech_ddr3_4g_800m_master             : t_c_tech_ddr := ("DDR3",  800,  TRUE, "DUAL  ", 15, 15, 10, 3, 64, 8,  8, 0,  0, 2, 2,  2, 1,   2,  14,  4,  2,    4,  64,   7);
  CONSTANT c_tech_ddr3_4g_800m_slave              : t_c_tech_ddr := ("DDR3",  800, FALSE, "DUAL  ", 15, 15, 10, 3, 64, 8,  8, 0,  0, 2, 2,  2, 1,   2,  14,  4,  2,    4,  64,   7);
  CONSTANT c_tech_ddr3_4g_single_rank_800m_master : t_c_tech_ddr := ("DDR3",  800,  TRUE, "SINGLE", 16, 16, 10, 3, 64, 8,  8, 0,  0, 2, 1,  1, 0,   1,  14,  4,  2,    4,  64,   7);
  CONSTANT c_tech_ddr3_4g_single_rank_800m_slave  : t_c_tech_ddr := ("DDR3",  800, FALSE, "SINGLE", 16, 16, 10, 3, 64, 8,  8, 0,  0, 2, 1,  1, 0,   1,  14,  4,  2,    4,  64,   7);
  
  CONSTANT c_tech_ddr4_max                        : t_c_tech_ddr := ("none", 1600,  TRUE, "DUAL  ", 17, 15, 10, 2, 72, 9,  0, 9,  2, 1, 1,  1, 0,   1,   0,  8,  3,    8,  64,   7);  -- maximum ranges for record field definitions
  
  CONSTANT c_tech_ddr4_sim_4k                     : t_c_tech_ddr := ("DDR4",  800,  TRUE, "DUAL  ", 10,  1, 10, 2, 72, 9,  0, 9,  2, 1, 1,  1, 0,   1,   0,  8,  3,    8,  64,   7);  -- use a_row to set nof ctrl addr = 2**(cs_w + ba + a_row + a_col + bg_w - rsl_w)
  CONSTANT c_tech_ddr4_sim_8k                     : t_c_tech_ddr := ("DDR4",  800,  TRUE, "DUAL  ", 10,  2, 10, 2, 72, 9,  0, 9,  2, 1, 1,  1, 0,   1,   0,  8,  3,    8,  64,   7);  -- use a_row to set nof ctrl addr = 2**(cs_w + ba + a_row + a_col + bg_w - rsl_w)
  CONSTANT c_tech_ddr4_sim_16k                    : t_c_tech_ddr := ("DDR4",  800,  TRUE, "DUAL  ", 10,  3, 10, 2, 72, 9,  0, 9,  2, 1, 1,  1, 0,   1,   0,  8,  3,    8,  64,   7);  -- use a_row to set nof ctrl addr = 2**(cs_w + ba + a_row + a_col + bg_w - rsl_w)
  CONSTANT c_tech_ddr4_sim_128k                   : t_c_tech_ddr := ("DDR4",  800,  TRUE, "DUAL  ", 10,  6, 10, 2, 72, 9,  0, 9,  2, 1, 1,  1, 0,   1,   0,  8,  3,    8,  64,   7);  -- use a_row to set nof ctrl addr = 2**(cs_w + ba + a_row + a_col + bg_w - rsl_w)
  CONSTANT c_tech_ddr4_sim_1m                     : t_c_tech_ddr := ("DDR4",  800,  TRUE, "DUAL  ", 10,  9, 10, 2, 72, 9,  0, 9,  2, 1, 1,  1, 0,   1,   0,  8,  3,    8,  64,   7);  -- use a_row to set nof ctrl addr = 2**(cs_w + ba + a_row + a_col + bg_w - rsl_w)
  
  CONSTANT c_tech_ddr4_4g_1600m                   : t_c_tech_ddr := ("DDR4", 1600,  TRUE, "DUAL  ", 17, 15, 10, 2, 72, 9,  0, 9,  2, 1, 1,  1, 0,   1,   0,  8,  3,    8,  64,   7);

  -- PHY in, inout and out signal records
  TYPE t_tech_ddr3_phy_in IS RECORD                                                                 -- DDR3 Description
    evt                        : STD_LOGIC;                                                         -- event signal is Not Connected to DDR3 PHY
    oct_rup                    : STD_LOGIC;                                                         -- only master DDR3 PHY has On Chip Termination OCT inputs
    oct_rdn                    : STD_LOGIC;                                                         -- only master DDR3 PHY has On Chip Termination OCT inputs
  END RECORD;
  
  TYPE t_tech_ddr4_phy_in IS RECORD                                                                 -- DDR4 Description
    evt                        : STD_LOGIC;                                                         -- event signal
    alert_n                    : STD_LOGIC;                                                         -- alert signal
    oct_rzqin                  : STD_LOGIC;                                                         -- PHY has On Chip Termination OCT inputs
  END RECORD;

  TYPE t_tech_ddr3_phy_io IS RECORD                                                                 -- DDR3 Description
    dq               : STD_LOGIC_VECTOR(c_tech_ddr3_max.dq_w-1 DOWNTO 0);                           -- data bus
    dqs              : STD_LOGIC_VECTOR(c_tech_ddr3_max.dqs_w-1 DOWNTO 0);                          -- data strobe bus
    dqs_n            : STD_LOGIC_VECTOR(c_tech_ddr3_max.dqs_w-1 DOWNTO 0);                          -- data strobe bus negative
    scl              : STD_LOGIC;                                                                   -- I2C clock
    sda              : STD_LOGIC;                                                                   -- I2C data
  END RECORD;
  
  TYPE t_tech_ddr4_phy_io IS RECORD                                                                 -- DDR4 Description
    dq               : STD_LOGIC_VECTOR(c_tech_ddr4_max.dq_w-1 DOWNTO 0);                           -- data bus
    dqs              : STD_LOGIC_VECTOR(c_tech_ddr4_max.dqs_w-1 DOWNTO 0);                          -- data strobe bus
    dqs_n            : STD_LOGIC_VECTOR(c_tech_ddr4_max.dqs_w-1 DOWNTO 0);                          -- data strobe bus negative
    dbi_n            : STD_LOGIC_VECTOR(c_tech_ddr4_max.dbi_w-1 DOWNTO 0);                          -- data bus inversion
  END RECORD;
  
  TYPE t_tech_ddr3_phy_ou IS RECORD                                                                 -- DDR3 Description
    a                          : STD_LOGIC_VECTOR(c_tech_ddr3_max.a_w-1 DOWNTO 0);                  -- row and column address
    ba                         : STD_LOGIC_VECTOR(c_tech_ddr3_max.ba_w-1 DOWNTO 0);                 -- bank address
    dm                         : STD_LOGIC_VECTOR(c_tech_ddr3_max.dm_w-1 DOWNTO 0);                 -- data mask bus
    ras_n                      : STD_LOGIC;                                                         -- row address strobe
    cas_n                      : STD_LOGIC;                                                         -- column address strobe
    we_n                       : STD_LOGIC;                                                         -- write enable signal
    reset_n                    : STD_LOGIC;                                                         -- reset signal
    ck                         : STD_LOGIC_VECTOR(c_tech_ddr3_max.ck_w-1 DOWNTO 0);                 -- clock, positive edge clock
    ck_n                       : STD_LOGIC_VECTOR(c_tech_ddr3_max.ck_w-1 DOWNTO 0);                 -- clock, negative edge clock
    cke                        : STD_LOGIC_VECTOR(c_tech_ddr3_max.cke_w-1 DOWNTO 0);                -- clock enable
    cs_n                       : STD_LOGIC_VECTOR(c_tech_ddr3_max.cs_w-1 DOWNTO 0);                 -- chip select
    odt                        : STD_LOGIC_VECTOR(c_tech_ddr3_max.odt_w-1 DOWNTO 0);                -- on-die termination control signal
  END RECORD;

  TYPE t_tech_ddr4_phy_ou IS RECORD                                                                 -- DDR4  Description
    a                          : STD_LOGIC_VECTOR(c_tech_ddr4_max.a_w-1 DOWNTO 0);                  -- row and column address (note eg. a[16]=ras_n, a[15]=cas_n, a[14]=we_n)
    ba                         : STD_LOGIC_VECTOR(c_tech_ddr4_max.ba_w-1 DOWNTO 0);                 -- bank address
    bg                         : STD_LOGIC_VECTOR(c_tech_ddr4_max.bg_w-1 DOWNTO 0);                 -- bank group
    act_n                      : STD_LOGIC;                                                         -- activate signal
    par                        : STD_LOGIC;                                                         -- parity signal
    reset_n                    : STD_LOGIC;                                                         -- reset signal
    ck                         : STD_LOGIC_VECTOR(c_tech_ddr4_max.ck_w-1 DOWNTO 0);                 -- clock, positive edge clock
    ck_n                       : STD_LOGIC_VECTOR(c_tech_ddr4_max.ck_w-1 DOWNTO 0);                 -- clock, negative edge clock
    cke                        : STD_LOGIC_VECTOR(c_tech_ddr4_max.cke_w-1 DOWNTO 0);                -- clock enable
    cs_n                       : STD_LOGIC_VECTOR(c_tech_ddr4_max.cs_w-1 DOWNTO 0);                 -- chip select
    odt                        : STD_LOGIC_VECTOR(c_tech_ddr4_max.odt_w-1 DOWNTO 0);                -- on-die termination control signal
  END RECORD;
  
  TYPE t_tech_ddr3_phy_in_arr IS ARRAY(NATURAL RANGE <>) OF t_tech_ddr3_phy_in;
  TYPE t_tech_ddr3_phy_io_arr IS ARRAY(NATURAL RANGE <>) OF t_tech_ddr3_phy_io; 
  TYPE t_tech_ddr3_phy_ou_arr IS ARRAY(NATURAL RANGE <>) OF t_tech_ddr3_phy_ou;

  TYPE t_tech_ddr4_phy_in_arr IS ARRAY(NATURAL RANGE <>) OF t_tech_ddr4_phy_in;
  TYPE t_tech_ddr4_phy_io_arr IS ARRAY(NATURAL RANGE <>) OF t_tech_ddr4_phy_io; 
  TYPE t_tech_ddr4_phy_ou_arr IS ARRAY(NATURAL RANGE <>) OF t_tech_ddr4_phy_ou;
  
  TYPE t_tech_ddr3_phy_terminationcontrol IS RECORD                                                 -- DDR3 Termination control
    seriesterminationcontrol   : STD_LOGIC_VECTOR(c_tech_ddr3_max.terminationcontrol_w-1 DOWNTO 0); -- termination control from master to slave DDR3 PHY (internal signal in FPGA)
    parallelterminationcontrol : STD_LOGIC_VECTOR(c_tech_ddr3_max.terminationcontrol_w-1 DOWNTO 0); -- termination control from master to slave DDR3 PHY (internal signal in FPGA)
  END RECORD;

  CONSTANT c_tech_ddr3_phy_terminationcontrol_x   : t_tech_ddr3_phy_terminationcontrol := ((OTHERS=>'X'), (OTHERS=>'X'));
  CONSTANT c_tech_ddr3_phy_terminationcontrol_rst : t_tech_ddr3_phy_terminationcontrol := ((OTHERS=>'0'), (OTHERS=>'0'));

  CONSTANT c_tech_ddr3_phy_in_x     : t_tech_ddr3_phy_in := ('X', 'X', 'X');
  CONSTANT c_tech_ddr4_phy_in_x     : t_tech_ddr4_phy_in := ('X', 'X', 'X');
  CONSTANT c_tech_ddr3_phy_ou_x     : t_tech_ddr3_phy_ou := ((OTHERS=>'X'), (OTHERS=>'X'), (OTHERS=>'X'), 'X', 'X', 'X', 'X', (OTHERS=>'X'), (OTHERS=>'X'), (OTHERS=>'X'), (OTHERS=>'X'), (OTHERS=>'X'));
  CONSTANT c_tech_ddr4_phy_ou_x     : t_tech_ddr4_phy_ou := ((OTHERS=>'X'), (OTHERS=>'X'), (OTHERS=>'X'), 'X', 'X', 'X', (OTHERS=>'X'), (OTHERS=>'X'), (OTHERS=>'X'), (OTHERS=>'X'), (OTHERS=>'X'));

END tech_ddr_pkg;

PACKAGE BODY tech_ddr_pkg IS

  FUNCTION func_tech_sel_ddr(g_technology : NATURAL; g_ddr3, g_ddr4 : t_c_tech_ddr) RETURN t_c_tech_ddr IS
  BEGIN
    CASE g_technology IS
      WHEN c_tech_stratixiv => RETURN g_ddr3;
      WHEN c_tech_arria10   => RETURN g_ddr4;
      WHEN OTHERS           => RETURN g_ddr3;
    END CASE;
  END;
  
  FUNCTION func_tech_sel_ddr(g_sel : BOOLEAN; a, b : t_c_tech_ddr) RETURN t_c_tech_ddr IS
  BEGIN
    IF g_sel=TRUE THEN RETURN a; ELSE RETURN b; END IF;
  END;
  
  FUNCTION func_tech_ddr_dq_address_w(c_ddr : t_c_tech_ddr) RETURN NATURAL IS
  BEGIN
    IF c_ddr.name="DDR3" THEN RETURN c_ddr.cs_w_w + c_ddr.ba_w + c_ddr.a_row_w + c_ddr.a_col_w;              END IF;          -- PHY address
    IF c_ddr.name="DDR4" THEN RETURN c_ddr.cs_w_w + c_ddr.ba_w + c_ddr.a_row_w + c_ddr.a_col_w + c_ddr.bg_w; END IF;          -- PHY address
  END;
  
  FUNCTION func_tech_ddr_ctlr_address_w(c_ddr : t_c_tech_ddr) RETURN NATURAL IS
    CONSTANT c_dq_address_w : NATURAL := func_tech_ddr_dq_address_w(c_ddr);
  BEGIN
    RETURN c_dq_address_w-c_ddr.rsl_w;                                          -- CTLR address
  END;
  
  FUNCTION func_tech_ddr_ctlr_data_w(c_ddr : t_c_tech_ddr) RETURN NATURAL IS
  BEGIN
    RETURN c_ddr.dq_w*c_ddr.rsl;                                                -- CTLR data
  END;
  
  FUNCTION func_tech_ddr_module_size(c_ddr : t_c_tech_ddr) RETURN NATURAL IS
    CONSTANT c_dq_address_w       : NATURAL := func_tech_ddr_dq_address_w(c_ddr);
    CONSTANT c_dq_nof_bytes       : NATURAL := 8;  -- both dw_q = 64 and 72 are regarded as having 8 bytes (either with 8 or 9 bits per byte)
    CONSTANT c_dq_nof_bytes_w     : NATURAL := ceil_log2(c_dq_nof_bytes);
    CONSTANT c_module_nof_bytes_w : NATURAL := c_dq_address_w + c_dq_nof_bytes_w;
    CONSTANT c_1GB_w              : NATURAL := 30;
  BEGIN
    IF c_module_nof_bytes_w < c_1GB_w THEN
      RETURN 0;
    ELSE
      RETURN 2**(c_module_nof_bytes_w-c_1GB_w);
    END IF;
  END;
  
END tech_ddr_pkg;