--------------------------------------------------------------------------------
--
-- Copyright (C) 2015
-- 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/>.
--
--------------------------------------------------------------------------------

-- Purpose: Functional simulation model of both the DDR driver and the DDR memory.
-- Description:
--   The component also supports different types of DDR, so DDR3 and DDR4.
-- Remark:
-- . It is assumed that the user only performs burst reads/writes!
-- . Modelsim raises warning 3391 when the DDR memory set by c_nof_addr is to big to fit in PC RAM.
--   Use 'verror 3391' to display the help. This means that the computer becomes too slow due to
--   page swapping to disk. Even loading the simulation becomes slow.

LIBRARY IEEE, common_lib, technology_lib;
USE IEEE.STD_LOGIC_1164.ALL;
USE common_lib.common_pkg.ALL;
USE common_lib.common_mem_pkg.ALL;
USE technology_lib.technology_pkg.ALL;
USE technology_lib.technology_select_pkg.ALL;
USE work.tech_ddr_pkg.ALL;

ENTITY sim_ddr IS
  GENERIC (
    g_tech_ddr        : t_c_tech_ddr
  );
  PORT (
    -- PLL reference clock
    ref_clk           : IN    STD_LOGIC;
    ref_rst           : IN    STD_LOGIC;

    -- Controller user interface
    ctlr_gen_clk      : OUT   STD_LOGIC;
    ctlr_gen_rst      : OUT   STD_LOGIC;
    ctlr_gen_clk_2x   : OUT   STD_LOGIC;
    ctlr_gen_rst_2x   : OUT   STD_LOGIC;

    ctlr_mosi         : IN    t_mem_ctlr_mosi;
    ctlr_miso         : OUT   t_mem_ctlr_miso
  );
END sim_ddr;


ARCHITECTURE str OF sim_ddr IS

  -- DDR size and controller data width
  CONSTANT c_nof_addr : NATURAL := 2**func_tech_ddr_ctlr_address_w(g_tech_ddr);  --8192;
  CONSTANT c_dat_w    : NATURAL := func_tech_ddr_ctlr_data_w(g_tech_ddr);  --256;

  -- DDR memory
  TYPE t_mem_arr IS ARRAY(0 TO c_nof_addr-1) OF STD_LOGIC_VECTOR(c_dat_w-1 DOWNTO 0);

  SIGNAL sim_clk       : STD_LOGIC;
  SIGNAL sim_rst       : STD_LOGIC;
  SIGNAL sim_ctlr_mosi : t_mem_ctlr_mosi;

  SIGNAL address       : NATURAL;

BEGIN

  -- Prevent delta delay issues by using a re-assigned clk both internally (sim_clk) and externally (ctrl_gen_clk)
  ctlr_gen_clk <= ref_clk;
  ctlr_gen_rst <= ref_rst;
  sim_clk      <= ref_clk;
  sim_rst      <= ref_rst;

  ctlr_miso.done     <= '0' , '1' AFTER 1 ns;
  ctlr_miso.cal_ok   <= '0' , '1' AFTER 1 ns;
  ctlr_miso.cal_fail <= '0';

  -- Delay the control one cycle here so p_mem_access can update its outputs immediately
  p_clk : PROCESS(sim_clk)
  BEGIN
    IF rising_edge(sim_clk) THEN
      sim_ctlr_mosi <= ctlr_mosi;
    END IF;
  END PROCESS;

  p_mem_access : PROCESS(sim_clk, sim_ctlr_mosi)
    VARIABLE v_enable_model  : BOOLEAN := FALSE;
    VARIABLE v_mem_arr       : t_mem_arr := (OTHERS=>(OTHERS=>'0'));
    VARIABLE v_address       : NATURAL := 0;
    VARIABLE v_wr_bursting   : BOOLEAN := FALSE;
    VARIABLE v_rd_bursting   : BOOLEAN := FALSE;
    VARIABLE v_burst_cnt     : NATURAL := 0;
    VARIABLE v_burst_size    : NATURAL := 0;
  BEGIN

    -- Don't waste simulation time when user does not access the model anyway
    IF v_enable_model = FALSE THEN
      ctlr_miso.waitrequest_n <= '1'; 
      IF sim_ctlr_mosi.burstbegin='1' THEN
        v_enable_model := TRUE;
      END IF;
    END IF;

    IF v_enable_model = TRUE THEN
      IF rising_edge(sim_clk) THEN
  
        ctlr_miso.rdval <= '0'; 
        ctlr_miso.waitrequest_n <= '1'; 
  
        -- Access: burst begin
        IF sim_ctlr_mosi.burstbegin='1' THEN
          IF sim_ctlr_mosi.wr='1' THEN
            v_wr_bursting := TRUE;
          ELSIF sim_ctlr_mosi.rd='1' THEN
            v_rd_bursting := TRUE;
          END IF;
          v_address     := TO_UINT(sim_ctlr_mosi.address);
          v_burst_size  := TO_UINT(sim_ctlr_mosi.burstsize);
          v_burst_cnt   := 0;
        END IF;
    
        IF v_wr_bursting=TRUE OR v_rd_bursting=TRUE THEN
          IF sim_ctlr_mosi.wr='1' THEN -- Write
            v_mem_arr(v_address) := sim_ctlr_mosi.wrdata(c_dat_w-1 DOWNTO 0);
            v_address   := v_address+1;
            v_burst_cnt := v_burst_cnt +1;
          ELSIF v_rd_bursting=TRUE THEN -- Read
            ctlr_miso.rddata(c_dat_w-1 DOWNTO 0) <= v_mem_arr(v_address);  
            ctlr_miso.rdval <= '1';
            ctlr_miso.waitrequest_n <= '0';
            v_address   := v_address+1;
            v_burst_cnt := v_burst_cnt +1;
          END IF;
    
          IF v_burst_cnt = v_burst_size THEN
            v_wr_bursting := FALSE;
            v_rd_bursting := FALSE;
          END IF;
        END IF;
    
        address <= v_address;
  
      END IF;
    END IF;

  END PROCESS;
 
END str;