--------------------------------------------------------------------------------
--
-- 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/>.
--
--------------------------------------------------------------------------------

-- Author: Eric Kooistra, 19 june 2017
-- Purpose: Functional simulation model of both the DDR driver and the DDR memory.
-- Description:
--   This DDR model supports different types of DDR, so DDR3 and DDR4.
--
--   During a write burst the user still controls the access rate by means of
--   ctrl_mosi.wr. During a read burst the memory determines the access rate.
--   Therefore a new write or read burst request will not occure during a write
--   burst, but they can occur during a read burst. The ctrl_mosi.address and
--   ctrl_mosi.burstsize are then captured with the pending_rd and prnding wr 
--   state and the ctlr_miso.waitrequest_n is pulled low because the model only
--   supports one pending burst request.
--   
--   The internal state of the DDR model is maintained in a variable of type
--   t_mem_state. For debugging purposes this state is also assigned to a 
--   signal to be able to view it together with ctrl_mosi and ctrl_miso in the
--   wave window.
-- 
-- * About other DDR features:
--   The sim_ddr only models the memory contents of the DDR, which is sufficient
--   for now, because that is the main function of DDR and initially it is best
--   to keep the model as simple as possible. If necessary, then possible extra
--   future features could be to also model the latency caused by a periodical
--   DRAM refresh cycle.
--
-- * About fixed size DDR memory:
--   The sim_ddr uses a fixed size simulation memory that cannot be too large,
--   because it as to fit in the PC memory. 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. A way around this would be to use a simulation memory that
--   grows dynamically for each address that is actually used (like a
--   dictionary). An example of such a dynamic DDR memory model is available at:
--
--      https://svn.astron.nl/UniBoard_FP7/UniBoard/trunk/Firmware/modules/MegaWizard/ddr3/testbench/ddr3_test_mem_model.vhd
--
--   However this dynamic model will simulate slower the more it gets filled.
--   Furthermore the simulation time of such larger blocks of data will
--   become unfeasible. Instead it is better to scale the parameters of the
--   application such that the fixed size sim_ddr memory is as small as
--   possible.


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);

  TYPE t_mem_state IS RECORD
    address            : NATURAL;
    burst_size         : NATURAL;
    burst_cnt          : NATURAL;   -- = 0
    wr_bursting        : BOOLEAN;   -- = FALSE
    rd_bursting        : BOOLEAN;   -- = FALSE
    pending_wr         : BOOLEAN;   -- = FALSE
    pending_rd         : BOOLEAN;   -- = FALSE
    pending_address    : NATURAL;
    pending_burst_size : NATURAL;
  END RECORD;

  SIGNAL sim_clk     : STD_LOGIC;
  SIGNAL sim_rst     : STD_LOGIC;

  SIGNAL mem_state   : t_mem_state;

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';

  p_mem_access : PROCESS(sim_clk)
    -- Process variables get initalized once and then they keep their state
    VARIABLE v_mem_arr : t_mem_arr := (OTHERS=>(OTHERS=>'0'));
    VARIABLE v         : t_mem_state := (0, 0, 0, FALSE, FALSE, FALSE, FALSE, 0, 0);

  BEGIN

    IF rising_edge(sim_clk) THEN

      -- Burst begin
      IF ctlr_mosi.burstbegin='1' THEN
        IF ctlr_mosi.wr='1' THEN
          IF v.rd_bursting=FALSE THEN
            v.wr_bursting := TRUE;
            v.address     := TO_UINT(ctlr_mosi.address);
            v.burst_size  := TO_UINT(ctlr_mosi.burstsize);
            v.burst_cnt   := 0;
          ELSE
            v.pending_wr         := TRUE;
            v.pending_address    := TO_UINT(ctlr_mosi.address);
            v.pending_burst_size := TO_UINT(ctlr_mosi.burstsize);
          END IF;
        ELSIF ctlr_mosi.rd='1' THEN
          IF v.rd_bursting=FALSE THEN
            v.rd_bursting := TRUE;
            v.address     := TO_UINT(ctlr_mosi.address);
            v.burst_size  := TO_UINT(ctlr_mosi.burstsize);
            v.burst_cnt   := 0;
            ctlr_miso.waitrequest_n <= '0';
          ELSE
            v.pending_rd         := TRUE;
            v.pending_address    := TO_UINT(ctlr_mosi.address);
            v.pending_burst_size := TO_UINT(ctlr_mosi.burstsize);
          END IF;
        END IF;
      END IF;

      -- Pending write burst begin, after read burst
      IF v.pending_wr=TRUE AND v.rd_bursting=FALSE THEN
        v.pending_wr := FALSE;
        IF ctlr_mosi.wr='1' THEN  -- require that user has kept wr still active
          v.wr_bursting   := TRUE;
          v.address       := v.pending_address;
          v.burst_size    := v.pending_burst_size;
          v.burst_cnt     := 0;
        END IF;
      END IF;

      -- Pending read burst begin, after read burst
      IF v.pending_rd=TRUE AND v.rd_bursting=FALSE THEN
        v.pending_rd := FALSE;
        IF ctlr_mosi.rd='1' THEN  -- require that user has kept rd still active
          v.rd_bursting   := TRUE;
          v.address       := v.pending_address;
          v.burst_size    := v.pending_burst_size;
          v.burst_cnt     := 0;
          ctlr_miso.waitrequest_n <= '0';
        END IF;
      END IF;

      -- Write access
      IF v.wr_bursting=TRUE AND ctlr_mosi.wr='1' THEN
        v_mem_arr(v.address) := ctlr_mosi.wrdata(c_dat_w-1 DOWNTO 0);
        v.address   := v.address + 1;
        v.burst_cnt := v.burst_cnt + 1;
      END IF;

      -- Read access
      ctlr_miso.rdval <= '0';
      IF v.rd_bursting=TRUE THEN
        ctlr_miso.rddata(c_dat_w-1 DOWNTO 0) <= v_mem_arr(v.address);
        ctlr_miso.rdval <= '1';
        v.address   := v.address + 1;
        v.burst_cnt := v.burst_cnt + 1;
      END IF;

      -- Burst size count
      IF v.burst_cnt = v.burst_size THEN
        v.wr_bursting := FALSE;
        v.rd_bursting := FALSE;
        ctlr_miso.waitrequest_n <= '1';
      END IF;

      -- Show DDR memory state in wave window
      mem_state <= v;
    END IF;

  END PROCESS;

END str;