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

  -- Debug signals for view in Wave window
  signal dbg_g_tech_ddr : t_c_tech_ddr;
  signal dbg_c_nof_addr : natural;
  signal dbg_c_dat_w    : natural;
  signal mem_state      : t_mem_state;

begin

  dbg_g_tech_ddr <= g_tech_ddr;
  dbg_c_nof_addr <= c_nof_addr;
  dbg_c_dat_w    <= c_dat_w;

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