-------------------------------------------------------------------------------
--
-- Copyright 2022
-- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
-- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
--
-- 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.
--
-------------------------------------------------------------------------------
-- Author: Job van Wee
-- Purpose: Folding a stream of data into a mm data configuration so it can be
-- stored in a DDR RAM-stick.
--
-- Description:
--  First the data from the sosi array gets collected into one data vector.
--  After that this data vector gets resized to the right size data vector in 
--  order to make it storable in a DDR RAM-stick.
--  After that a address gets assigned to the data so the data can be found back.
--
-- Remark:
--  Use VHDL coding template from:
--  https://support.astron.nl/confluence/display/SBe/VHDL+design+patterns+for+RTL+coding
--  The maximum value of the address is determend by g_tech_ddr.

LIBRARY IEEE, technology_lib, tech_ddr_lib, common_lib, dp_lib, io_ddr_lib;
USE IEEE.std_logic_1164.ALL;
USE IEEE.numeric_std.ALL;
USE IEEE.MATH_REAL.ALL;
USE technology_lib.technology_pkg.ALL;
USE technology_lib.technology_select_pkg.ALL;
USE tech_ddr_lib.tech_ddr_pkg.ALL;
USE common_lib.common_pkg.ALL;
USE common_lib.common_mem_pkg.ALL;
USE dp_lib.dp_stream_pkg.ALL;
USE io_ddr_lib.ALL;


ENTITY ddrctrl IS
  GENERIC (
    g_tech_ddr        : t_c_tech_ddr;                                                                                         -- type of memory
    g_sim_model       : BOOLEAN                                             := TRUE;                                          -- determens if this is a simulation
    g_technology      : NATURAL                                             := c_tech_select_default;
    g_nof_streams     : NATURAL                                             := 12;                                            -- number of input streams
    g_data_w          : NATURAL                                             := 14;                                            -- data with of input data vectors
    g_stop_percentage : NATURAL                                             := 50;
    g_block_size      : NATURAL                                             := 1024
  );
  PORT (
    clk               : IN  STD_LOGIC                                       := '0';
    rst               : IN  STD_LOGIC;
    ctlr_ref_clk      : IN  STD_LOGIC;
    ctlr_ref_rst      : IN  STD_LOGIC;
    mm_clk            : IN  STD_LOGIC                                       := '0';
    mm_rst            : IN  STD_LOGIC                                       := '0';
    in_sosi_arr       : IN  t_dp_sosi_arr;                                                                                    -- input data
    stop_in           : IN  STD_LOGIC                                       := '0';

    out_sosi_arr      : OUT t_dp_sosi_arr(g_nof_streams-1 DOWNTO 0)         := (OTHERS => c_dp_sosi_rst);
    out_siso          : IN  t_dp_siso                                       := c_dp_siso_rst; 
    ddrctrl_ctrl_state: OUT STD_LOGIC_VECTOR(32-1 DOWNTO 0);

    term_ctrl_out     : OUT   t_tech_ddr3_phy_terminationcontrol;
    term_ctrl_in      : IN    t_tech_ddr3_phy_terminationcontrol            := c_tech_ddr3_phy_terminationcontrol_rst;

    -- IO_DDR monitor data
    reg_io_ddr_mosi   : IN    t_mem_mosi  := c_mem_mosi_rst; 
    reg_io_ddr_miso   : OUT   t_mem_miso;

    -- DDR3 PHY external interface
    phy3_in           : IN    t_tech_ddr3_phy_in                            := c_tech_ddr3_phy_in_x;
    phy3_io           : INOUT t_tech_ddr3_phy_io;
    phy3_ou           : OUT   t_tech_ddr3_phy_ou;

    -- DDR4 PHY external interface
    phy4_in           : IN    t_tech_ddr4_phy_in                            := c_tech_ddr4_phy_in_x;
    phy4_io           : INOUT t_tech_ddr4_phy_io;
    phy4_ou           : OUT   t_tech_ddr4_phy_ou
  );
END ddrctrl;


ARCHITECTURE str OF ddrctrl IS

  -- constant for readability
  CONSTANT  c_io_ddr_data_w     : NATURAL                                   := func_tech_ddr_ctlr_data_w(g_tech_ddr);         -- 576
  CONSTANT  c_wr_fifo_depth     : NATURAL                                   := 256;                                           -- defined at DDR side of the FIFO, >=16 and independent of wr burst size, default >= 256 because 32b*256 fits in 1 M9K so c_ctlr_data_w=256b will require 8 M9K
  CONSTANT  c_rd_fifo_depth     : NATURAL                                   := 256;                                           -- defined at DDR side of the FIFO, >=16 AND > max number of rd burst sizes (so > c_rd_fifo_af_margin), default >= 256 because 32b*256 fits in 1 M9K so c_ctlr_data_w=256b will require 8 M9K
  CONSTANT  c_wr_fifo_uw_w      : NATURAL                                   := ceil_log2(c_wr_fifo_depth*(func_tech_ddr_ctlr_data_w(g_tech_ddr)/c_io_ddr_data_w));
  CONSTANT  c_rd_fifo_uw_w      : NATURAL                                   := ceil_log2(c_rd_fifo_depth*(func_tech_ddr_ctlr_data_w(g_tech_ddr)/c_io_ddr_data_w));

  CONSTANT  c_burstsize         : NATURAL                                   := g_tech_ddr.maxburstsize;
  CONSTANT  c_adr_w             : NATURAL                                   := func_tech_ddr_ctlr_address_w(g_tech_ddr);      -- the lengt of the address vector, for simulation this is smaller, otherwise the simulation would take to long, 27
  CONSTANT  c_max_adr           : NATURAL                                   := 2**(c_adr_w)-1;                                -- the maximal address that is possible within the vector length of the address
  CONSTANT  c_adr_per_b         : NATURAL                                   := ((g_block_size*g_nof_streams*g_data_w)/c_io_ddr_data_w)+1; -- rounding error removes the amount of extra addresses.
  CONSTANT  c_bim               : NATURAL                                   := NATURAL(floor(REAL(c_max_adr)/REAL(c_adr_per_b)));

  SIGNAL    s_adr_per_b         : NATURAL                                   := c_adr_per_b;

  -- the amount of addresses used
  CONSTANT  c_nof_adr           : NATURAL                                   := c_bim*c_adr_per_b;

  SIGNAL    s_nof_adr           : NATURAL                                   := c_nof_adr;

  -- the amount of overflow after one block is written
  CONSTANT  c_of_pb             : NATURAL                                   := (g_block_size*g_nof_streams*g_data_w)-(((g_block_size*g_nof_streams*g_data_w)/c_io_ddr_data_w)*c_io_ddr_data_w); -- amount of overflow after one block is written to memory

  CONSTANT  c_aof_full_burst    : NATURAL                                   := c_nof_adr/c_burstsize;
  CONSTANT  c_last_burstsize    : NATURAL                                   := c_nof_adr-(c_aof_full_burst*c_burstsize);

  SIGNAL    s_last_burstsize    : NATURAL                                   := c_last_burstsize;

  -- signals for connecting the components
  SIGNAL    ctrl_clk              : STD_LOGIC;
  SIGNAL    ctrl_rst              : STD_LOGIC;
  SIGNAL    rst_ddrctrl_input_ac  : STD_LOGIC;
  SIGNAL    out_of                : NATURAL                                     := 0;
  SIGNAL    out_sosi              : t_dp_sosi                                   := c_dp_sosi_init;
  SIGNAL    out_adr               : NATURAL                                     := 0;
  SIGNAL    dvr_mosi              : t_mem_ctlr_mosi                             := c_mem_ctlr_mosi_rst;
  SIGNAL    dvr_miso              : t_mem_ctlr_miso                             := c_mem_ctlr_miso_rst;
  SIGNAL    wr_sosi               : t_dp_sosi                                   := c_dp_sosi_init;
  SIGNAL    rd_siso               : t_dp_siso                                   := c_dp_siso_rst;
  SIGNAL    rd_sosi               : t_dp_sosi                                   := c_dp_sosi_init;
  SIGNAL    stop                  : STD_LOGIC;
  SIGNAL    wr_fifo_usedw         : STD_LOGIC_VECTOR(c_wr_fifo_uw_w-1 DOWNTO 0);
  SIGNAL    rd_fifo_usedw         : STD_LOGIC_VECTOR(c_rd_fifo_uw_w-1 DOWNTO 0);
  SIGNAL    rd_ready              : STD_LOGIC;
  SIGNAL    inp_bsn_adr           : NATURAL;
  SIGNAL    bsn_co                : STD_LOGIC_VECTOR(c_dp_stream_bsn_w-1 DOWNTO 0);
  SIGNAL    data_stopped          : STD_LOGIC;

  SIGNAL    state_vec             : STD_LOGIC_VECTOR(1 DOWNTO 0);
  SIGNAL    ddrctrl_ctrl_state_local  : STD_LOGIC_VECTOR(32-1 DOWNTO 0);

BEGIN

  rd_siso.ready <= rd_ready;
  rd_siso.xon   <= '1';
  ddrctrl_ctrl_state(5 DOWNTO 0)  <= ddrctrl_ctrl_state_local(5 DOWNTO 0);
  ddrctrl_ctrl_state(7 DOWNTO 6)  <= state_vec(1 DOWNTO 0);
  ddrctrl_ctrl_state(32-1 DOWNTO 8)  <= ddrctrl_ctrl_state_local(32-1 DOWNTO 8);

  -- input to io_ddr
  u_ddrctrl_input : ENTITY work.ddrctrl_input
  GENERIC MAP(
    g_tech_ddr                => g_tech_ddr,
    g_nof_streams             => g_nof_streams,
    g_data_w                  => g_data_w,
    g_max_adr                 => c_nof_adr,
    g_bim                     => c_bim,
    g_of_pb                   => c_of_pb,
    g_block_size              => g_block_size
  )
  PORT MAP(
    clk                       => clk,
    rst                       => rst,
    rst_ddrctrl_input_ac      => rst_ddrctrl_input_ac,
    in_sosi_arr               => in_sosi_arr,
    in_stop                   => stop,
    out_sosi                  => out_sosi,
    out_adr                   => out_adr,
    out_bsn_adr               => inp_bsn_adr,
    out_data_stopped          => data_stopped
  );

  -- functions as a fifo buffer for input data into the sdram stick. also manages input to sdram stick.
  u_io_ddr : ENTITY io_ddr_lib.io_ddr
  GENERIC MAP(
    g_sim_model               => g_sim_model,
    g_technology              => g_technology,
    g_tech_ddr                => g_tech_ddr,
    g_cross_domain_dvr_ctlr   => FALSE,
    g_wr_data_w               => c_io_ddr_data_w,
    g_wr_fifo_depth           => c_wr_fifo_depth,
    g_rd_fifo_depth           => c_rd_fifo_depth,
    g_rd_data_w               => c_io_ddr_data_w,
    g_wr_flush_mode           => "VAL",
    g_wr_flush_use_channel    => FALSE,
    g_wr_flush_start_channel  => 0,
    g_wr_flush_nof_channels   => 1
    )
    PORT MAP(

    -- DDR reference clock
    ctlr_ref_clk              => ctlr_ref_clk,
    ctlr_ref_rst              => ctlr_ref_rst,

    -- DDR controller clock domain
    ctlr_clk_out              => ctrl_clk,
    ctlr_rst_out              => ctrl_rst,
    
    ctlr_clk_in               => ctrl_clk,
    ctlr_rst_in               => ctrl_rst,
    
    -- MM clock + reset
    mm_rst                    => mm_rst,                                           
    mm_clk                    => mm_clk, 
    
    -- MM interface
    reg_io_ddr_mosi           => reg_io_ddr_mosi,
    reg_io_ddr_miso           => reg_io_ddr_miso,
    state_vec                 => state_vec,
    
    -- Driver clock domain
    dvr_clk                   => clk,
    dvr_rst                   => rst,
    
    dvr_miso                  => dvr_miso,
    dvr_mosi                  => dvr_mosi,
    
    -- Write FIFO clock domain
    wr_clk                    => clk,
    wr_rst                    => rst,

    wr_fifo_usedw             => wr_fifo_usedw,
    wr_sosi                   => wr_sosi,
    wr_siso                   => open,
  
    -- Read FIFO clock domain
    rd_clk                    => clk,
    rd_rst                    => rst,
    
    rd_fifo_usedw             => rd_fifo_usedw,
    rd_sosi                   => rd_sosi,
    rd_siso                   => rd_siso,

    term_ctrl_out             => term_ctrl_out,
    term_ctrl_in              => term_ctrl_in,
    
    -- DDR3 PHY external interface
    phy3_in                   => phy3_in,
    phy3_io                   => phy3_io,
    phy3_ou                   => phy3_ou,
    
    -- DDR4 PHY external interface
    phy4_in                   => phy4_in,
    phy4_io                   => phy4_io,
    phy4_ou                   => phy4_ou
  );

  -- reading ddr memory
  u_ddrctrl_output : ENTITY work.ddrctrl_output
  GENERIC MAP(
    g_technology              => g_technology,
    g_tech_ddr                => g_tech_ddr,
    g_sim_model               => g_sim_model,
    g_in_data_w               => c_io_ddr_data_w,
    g_nof_streams             => g_nof_streams,
    g_data_w                  => g_data_w,
    g_block_size              => g_block_size,
    g_bim                     => c_bim
  )
  PORT MAP(
    clk                       => clk,
    rst                       => rst,

    in_sosi                   => rd_sosi,
    in_bsn                    => bsn_co,

    out_sosi_arr              => out_sosi_arr,
    out_siso                  => out_siso,
    out_ready                 => rd_ready
  );

  -- controller of ddrctrl
  u_ddrctrl_controller : ENTITY work.ddrctrl_controller
  GENERIC MAP(
    g_tech_ddr                => g_tech_ddr,
    g_stop_percentage         => g_stop_percentage,
    g_nof_streams             => g_nof_streams,
    g_out_data_w              => g_data_w,
    g_wr_data_w               => c_io_ddr_data_w,
    g_rd_fifo_depth           => c_rd_fifo_depth,
    g_rd_data_w               => c_io_ddr_data_w,
    g_block_size              => g_block_size,
    g_wr_fifo_uw_w            => c_wr_fifo_uw_w,
    g_rd_fifo_uw_w            => c_rd_fifo_uw_w,
    g_max_adr                 => c_nof_adr,
    g_burstsize               => c_burstsize,
    g_last_burstsize          => c_last_burstsize,
    g_adr_per_b               => c_adr_per_b,
    g_bim                     => c_bim
  )
  PORT MAP(
    clk                       => clk,
    rst                       => rst,

    -- ddrctrl_input
    inp_of                    => out_of,
    inp_sosi                  => out_sosi,
    inp_adr                   => out_adr,
    inp_bsn_adr               => inp_bsn_adr,
    inp_data_stopped          => data_stopped,
    rst_ddrctrl_input_ac      => rst_ddrctrl_input_ac,

    -- io_ddr
    dvr_mosi                  => dvr_mosi,
    dvr_miso                  => dvr_miso,
    wr_sosi                   => wr_sosi,
    wr_fifo_usedw             => wr_fifo_usedw,
    rd_fifo_usedw             => rd_fifo_usedw,

    -- ddrctrl_output
    outp_bsn                  => bsn_co,

    -- ddrctrl_controller
    stop_in                   => stop_in,
    stop_out                  => stop,
    ddrctrl_ctrl_state        => ddrctrl_ctrl_state_local
  );

END str;