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

-- This testbench tests the different type of DDR controllers.
--
-- The DUT can be selected, using the g_technology and g_tech_ddr constants. 
--
-- Testbench is selftesting: 
--
-- > as 10
-- > run -all 
--

LIBRARY IEEE, technology_lib, tech_ddr_lib, common_lib, dp_lib, diagnostics_lib;                   
USE IEEE.STD_LOGIC_1164.ALL;    
USE IEEE.numeric_std.ALL;
USE common_lib.common_pkg.ALL;
USE common_lib.common_mem_pkg.ALL;
USE common_lib.tb_common_pkg.ALL;
USE common_lib.tb_common_mem_pkg.ALL;
USE dp_lib.dp_stream_pkg.ALL;
USE technology_lib.technology_pkg.ALL;
USE technology_lib.technology_select_pkg.ALL;
USE tech_ddr_lib.tech_ddr_pkg.ALL;

ENTITY tb_io_ddr IS
  GENERIC (
    g_technology            : NATURAL := c_tech_select_default;
    g_tech_ddr3             : t_c_tech_ddr := c_tech_ddr3_4g_800m_master;
    g_tech_ddr4             : t_c_tech_ddr := c_tech_ddr4_4g_1600m;
    g_tb_end                : BOOLEAN := TRUE;   -- when TRUE then tb_end ends this simulation, else a higher multi-testbench will end the simulation
    g_use_ddr_memory_model  : BOOLEAN := FALSE;  -- when TRUE use the internal DDR memory model, else use the DDR model in this tb.
    g_cross_domain_dvr_ctlr : BOOLEAN := FALSE;  -- when TRUE insert clock cross domain logic
    g_ctlr_ref_clk_period   : TIME := 5000 ps;   -- 200 MHz
    g_dvr_clk_period        : TIME := 5000 ps;   -- 50 MHz
    g_dp_clk_period         : TIME := 5000 ps;   -- 200 MHz
    g_mm_clk_period         : TIME := 8000 ps;   -- 125 MHz
    g_dp_factor             : NATURAL := 4;      -- 1 or power of 2, c_dp_data_w = c_ctlr_data_w / g_dp_factor
    g_rd_fifo_depth         : NATURAL := 512;    -- default 256 because 32b*256 fits in 1 M9K, use larger to fit more read bursts eg. in case g_dp_factor>1
    g_block_len             : NATURAL := 2500;   -- block length for a DDR write access and read back access in number of c_ctlr_data_w words
    g_nof_block             : NATURAL := 2;      -- number of blocks that will be written to DDR and readback from DDR
    g_nof_wr_per_block      : NATURAL := 1;      -- number of write accesses per block
    g_nof_rd_per_block      : NATURAL := 1;      -- number of read accesses per block
    g_nof_repeat            : NATURAL := 1;      -- number of stimuli repeats with write flush after each repeat
    g_wr_flush_mode         : STRING := "VAL"    -- "VAL", "SOP", "SYN"
  );
  PORT (
    tb_end : OUT STD_LOGIC
  );
END ENTITY tb_io_ddr;

ARCHITECTURE str of tb_io_ddr IS

  -- Select DDR3 or DDR4 dependent on the technology
  CONSTANT c_tech_ddr                 : t_c_tech_ddr := func_tech_sel_ddr(g_technology, g_tech_ddr3, g_tech_ddr4);
  
  CONSTANT c_cross_domain_dvr_ctlr    : BOOLEAN := g_cross_domain_dvr_ctlr OR g_ctlr_ref_clk_period/=g_dvr_clk_period;

  CONSTANT c_ctlr_address_w           : NATURAL := func_tech_ddr_ctlr_address_w(c_tech_ddr);
  CONSTANT c_ctlr_data_w              : NATURAL := func_tech_ddr_ctlr_data_w(c_tech_ddr);
  
  CONSTANT c_dp_data_w                : NATURAL := c_ctlr_data_w/g_dp_factor;
  
  CONSTANT c_queue_nof_rd             : NATURAL := sel_a_b(c_tech_ddr.name="DDR3", 1, 3);   -- derived empirically from simulation, seems to match (c_tech_ddr.command_queue_depth-1)/2
  
  CONSTANT c_wr_fifo_depth            : NATURAL := 256;
  CONSTANT c_rd_fifo_depth            : NATURAL := g_rd_fifo_depth;
  CONSTANT c_rd_fifo_af_margin        : NATURAL := 4 + c_queue_nof_rd*c_tech_ddr.maxburstsize;  -- sufficient to fit one or more rd burst accesses of g_tech_ddr.maxburstsize each
  
  -- Frame size for sop/eop
  CONSTANT c_wr_frame_size            : NATURAL := 32;
  -- Sync period
  CONSTANT c_wr_sync_period           : NATURAL := 512;
 
  -- Typical DDR access stimuli
  -- . write block of words in 1 write access and then readback in 4 block read accesses
  -- . use appropriate c_len to access across a DDR address column (a_col_w=10)
  CONSTANT c_nof_access_per_block     : NATURAL := g_nof_wr_per_block + g_nof_rd_per_block;
  CONSTANT c_nof_access               : NATURAL := g_nof_block * c_nof_access_per_block;
  
  FUNCTION func_ctlr_address_lo_arr RETURN t_nat_natural_arr IS
    CONSTANT c_wr  : NATURAL := g_block_len/g_nof_wr_per_block;
    CONSTANT c_rd  : NATURAL := g_block_len/g_nof_rd_per_block;
    VARIABLE v_arr : t_nat_natural_arr(0 TO c_nof_access-1);
  BEGIN
    FOR R IN 0 TO g_nof_block-1 LOOP
      -- Write block in g_nof_wr_per_block accesses
      FOR I IN 0 TO g_nof_wr_per_block-1 LOOP
        v_arr(R*c_nof_access_per_block+I) := R*g_block_len+I*c_wr;
      END LOOP;
      -- Read back block in g_nof_rd_per_block accesses
      FOR I IN 0 TO g_nof_rd_per_block-1 LOOP
        v_arr(R*c_nof_access_per_block+g_nof_wr_per_block+I) := R*g_block_len+I*c_rd;
      END LOOP;
    END LOOP;
    RETURN v_arr;
  END;
    
  FUNCTION func_ctlr_nof_address_arr RETURN t_nat_natural_arr IS
    CONSTANT c_wr      : NATURAL := g_block_len/g_nof_wr_per_block;
    CONSTANT c_rd      : NATURAL := g_block_len/g_nof_rd_per_block;
    CONSTANT c_wr_last : NATURAL := g_block_len - c_wr*(g_nof_wr_per_block-1);
    CONSTANT c_rd_last : NATURAL := g_block_len - c_rd*(g_nof_rd_per_block-1);
    VARIABLE v_arr     : t_nat_natural_arr(0 TO c_nof_access-1);
  BEGIN
    FOR R IN 0 TO g_nof_block-1 LOOP
      -- Write block in g_nof_wr_per_block accesses
      FOR I IN 0 TO g_nof_wr_per_block-1 LOOP
        v_arr(R*c_nof_access_per_block+I) := c_wr;
      END LOOP;
      v_arr(R*c_nof_access_per_block+g_nof_wr_per_block-1) := c_wr_last;
      -- Read back block in g_nof_rd_per_block accesses
      FOR I IN 0 TO g_nof_rd_per_block-1 LOOP
        v_arr(R*c_nof_access_per_block+g_nof_wr_per_block+I) := c_rd;
      END LOOP;
      v_arr(R*c_nof_access_per_block+g_nof_wr_per_block+g_nof_rd_per_block-1) := c_rd_last;
    END LOOP;
    RETURN v_arr;
  END;
  
  FUNCTION func_ctlr_wr_not_rd_arr RETURN STD_LOGIC_VECTOR IS
    VARIABLE v_arr : STD_LOGIC_VECTOR(0 TO c_nof_access-1);
  BEGIN
    FOR R IN 0 TO g_nof_block-1 LOOP
      -- Write block in g_nof_wr_per_block accesses
      FOR I IN 0 TO g_nof_wr_per_block-1 LOOP
        v_arr(R*c_nof_access_per_block+I) := '1';
      END LOOP;
      -- Read back block in g_nof_rd_per_block accesses
      FOR I IN 0 TO g_nof_rd_per_block-1 LOOP
        v_arr(R*c_nof_access_per_block+g_nof_wr_per_block+I) := '0';
      END LOOP;
    END LOOP;
    RETURN v_arr;
  END;
  
  CONSTANT c_ctlr_address_lo_arr      : t_nat_natural_arr(0 TO c_nof_access-1) := func_ctlr_address_lo_arr;
  CONSTANT c_ctlr_nof_address_arr     : t_nat_natural_arr(0 TO c_nof_access-1) := func_ctlr_nof_address_arr;
  CONSTANT c_ctlr_wr_not_rd_arr       : STD_LOGIC_VECTOR(0 TO c_nof_access-1)  := func_ctlr_wr_not_rd_arr;
                                                      
  SIGNAL dbg_c_ctlr_address_lo_arr    : t_nat_natural_arr(0 TO c_nof_access-1) := c_ctlr_address_lo_arr;
  SIGNAL dbg_c_ctlr_nof_address_arr   : t_nat_natural_arr(0 TO c_nof_access-1) := c_ctlr_nof_address_arr;
  SIGNAL dbg_c_ctlr_wr_not_rd_arr     : STD_LOGIC_VECTOR(0 TO c_nof_access-1)  := c_ctlr_wr_not_rd_arr;
  
  SIGNAL dbg_c_tech_ddr               : t_c_tech_ddr := c_tech_ddr;
  SIGNAL dbg_c_dp_data_w              : NATURAL := c_dp_data_w;
  SIGNAL dbg_c_wr_fifo_depth          : NATURAL := c_wr_fifo_depth;
  SIGNAL dbg_c_rd_fifo_depth          : NATURAL := c_rd_fifo_depth;
  SIGNAL dbg_c_rd_fifo_af_margin      : NATURAL := c_rd_fifo_af_margin;
  
  SIGNAL i_tb_end             : STD_LOGIC := '0';
  SIGNAL ctlr_ref_clk         : STD_LOGIC := '0';
  SIGNAL ctlr_ref_rst         : STD_LOGIC;
  SIGNAL ctlr_clk             : STD_LOGIC;
  SIGNAL ctlr_rst             : STD_LOGIC;
  SIGNAL dvr_clk              : STD_LOGIC := '0';
  SIGNAL dvr_rst              : STD_LOGIC;
  SIGNAL dp_clk               : STD_LOGIC := '0';
  SIGNAL dp_rst               : STD_LOGIC;
  SIGNAL mm_clk               : STD_LOGIC := '0';
  SIGNAL mm_rst               : STD_LOGIC;

  SIGNAL dvr_miso             : t_mem_ctlr_miso;
  SIGNAL dvr_mosi             : t_mem_ctlr_mosi;   
  
  SIGNAL reg_io_ddr_mosi      : t_mem_mosi := c_mem_mosi_rst;           
  SIGNAL reg_io_ddr_miso      : t_mem_miso := c_mem_miso_rst;           
  
  SIGNAL dvr_done             : STD_LOGIC;
  SIGNAL dvr_en               : STD_LOGIC;
  SIGNAL dvr_wr_not_rd        : STD_LOGIC;
  SIGNAL dvr_start_address    : STD_LOGIC_VECTOR(c_ctlr_address_w-1 DOWNTO 0); 
  SIGNAL dvr_nof_data         : STD_LOGIC_VECTOR(c_ctlr_address_w-1 DOWNTO 0);
  SIGNAL dvr_wr_flush_en      : STD_LOGIC;
  
  SIGNAL diag_wr_src_in       : t_dp_siso;
  SIGNAL diag_wr_src_out      : t_dp_sosi;
  
  SIGNAL wr_fifo_usedw        : STD_LOGIC_VECTOR(ceil_log2(c_wr_fifo_depth * g_dp_factor)-1 DOWNTO 0);    
  SIGNAL wr_src_out           : t_dp_sosi;
  SIGNAL wr_val_cnt           : NATURAL := 0;

  SIGNAL diag_rd_snk_out      : t_dp_siso;
  SIGNAL diag_rd_snk_in       : t_dp_sosi;
  SIGNAL rd_fifo_usedw        : STD_LOGIC_VECTOR(ceil_log2(c_rd_fifo_depth * g_dp_factor)-1 DOWNTO 0);    

  SIGNAL dbg_wr_data          : STD_LOGIC_VECTOR(c_dp_data_w-1 DOWNTO 0);
  SIGNAL dbg_wr_val           : STD_LOGIC;
  SIGNAL dbg_rd_data          : STD_LOGIC_VECTOR(c_dp_data_w-1 DOWNTO 0);
  SIGNAL dbg_rd_val           : STD_LOGIC;
  
  SIGNAL src_diag_en          : STD_LOGIC;
  SIGNAL src_val_cnt          : STD_LOGIC_VECTOR(31 DOWNTO 0);

  SIGNAL snk_diag_en          : STD_LOGIC;
  SIGNAL snk_diag_res         : STD_LOGIC;
  SIGNAL snk_diag_res_val     : STD_LOGIC;
  SIGNAL snk_val_cnt          : STD_LOGIC_VECTOR(31 DOWNTO 0);
  SIGNAL expected_cnt         : NATURAL;
  
  -- PHY interface
  SIGNAL phy_in               : t_tech_ddr_phy_in;
  SIGNAL phy_io               : t_tech_ddr_phy_io;
  SIGNAL phy_ou               : t_tech_ddr_phy_ou;
  
BEGIN

  ctlr_ref_clk <= NOT ctlr_ref_clk OR i_tb_end AFTER g_ctlr_ref_clk_period/2; 
  
  dvr_clk  <= NOT dvr_clk OR i_tb_end AFTER g_dvr_clk_period/2; 
  dvr_rst  <= '1', '0' AFTER 100 ns;
  
  dp_clk   <= NOT dp_clk OR i_tb_end AFTER g_dp_clk_period/2; 
  dp_rst   <= '1', '0' AFTER 100 ns;

  mm_clk   <= NOT mm_clk OR i_tb_end AFTER g_mm_clk_period/2; 
  mm_rst   <= '1', '0' AFTER 100 ns;

  tb_end <= i_tb_end;
  
  p_stimuli : PROCESS
  BEGIN
    i_tb_end          <= '0';
    dvr_en            <= '0';
    dvr_wr_flush_en   <= '0';
    dvr_wr_not_rd     <= '0';
    dvr_start_address <= (OTHERS=>'0');
    dvr_nof_data      <= (OTHERS=>'0');
    src_diag_en       <= '0';
    snk_diag_en       <= '0';
    expected_cnt      <= 0;
    ctlr_ref_rst      <= '1';
    WAIT FOR 100 ns;
    ctlr_ref_rst      <= '0';

    proc_common_wait_until_high(dvr_clk, dvr_done);
    
    -- Start diagnostics source for write and sink for verify read
    proc_common_wait_some_cycles(dp_clk, 1);
    src_diag_en <= '1';
    snk_diag_en <= '1';
    
    -- After reset the write FIFO is flushed until the first write access is started, even when dvr_wr_flush_en='0'
    proc_common_wait_some_cycles(ctlr_clk, 1000);

    FOR R IN 0 TO g_nof_repeat-1 LOOP
      proc_common_wait_some_cycles(dvr_clk, 1);    
      FOR I IN c_ctlr_address_lo_arr'RANGE LOOP
        dvr_start_address <= TO_UVEC(c_ctlr_address_lo_arr(I) , c_ctlr_address_w);
        dvr_nof_data      <= TO_UVEC(c_ctlr_nof_address_arr(I), c_ctlr_address_w);

        -- START ACCESS
        dvr_wr_not_rd <= c_ctlr_wr_not_rd_arr(I);
        dvr_en        <= '1';
        proc_common_wait_some_cycles(dvr_clk, 1);
        dvr_en        <= '0'; 
        
        -- ACCESS DONE
        proc_common_wait_until_lo_hi(dvr_clk, dvr_done);

        IF c_ctlr_wr_not_rd_arr(I)='0' THEN
          expected_cnt <= expected_cnt + c_ctlr_nof_address_arr(I)*g_dp_factor;
        END IF;
      END LOOP;
      
      -- Stop diagnostics source
      proc_common_wait_some_cycles(dp_clk, 1);
      src_diag_en <= '0';
      
      -- Flush the wr fifo
      proc_common_wait_some_cycles(dvr_clk, 1);
      dvr_wr_flush_en <= '1';
      proc_common_wait_some_cycles(dvr_clk, 1);
      dvr_wr_flush_en <= '0';
      
      -- Wait until the wr fifo has been flushed and the rd fifo has been read empty
      proc_common_wait_some_cycles(ctlr_clk, c_tech_ddr.command_queue_depth*c_tech_ddr.maxburstsize);  -- rd FIFO may still get filled some more
      proc_common_wait_some_cycles(ctlr_clk, largest(TO_UINT(wr_fifo_usedw)/g_dp_factor, TO_UINT(rd_fifo_usedw)));
      proc_common_wait_some_cycles(ctlr_clk, 10);                       -- some extra margin
      
      ASSERT UNSIGNED(wr_fifo_usedw) < g_dp_factor  REPORT "[ERROR] Write FIFO is flushed but not empty!" SEVERITY FAILURE;
      ASSERT UNSIGNED(rd_fifo_usedw) = 0            REPORT "[ERROR] Read FIFO is not empty!" SEVERITY FAILURE;
      ASSERT UNSIGNED(snk_val_cnt)   = expected_cnt REPORT "[ERROR] Unexpected number of read data!" SEVERITY FAILURE;
      
      -- Check diagnostics sink after the rd fifo has been read empty
      proc_common_wait_some_cycles(dp_clk, 1);
      ASSERT snk_diag_res_val = '1' REPORT "[ERROR] DIAG_RES INVALID!"  SEVERITY FAILURE;
      ASSERT snk_diag_res = '0'     REPORT "[ERROR] NON-ZERO DIAG_RES!" SEVERITY FAILURE;
          
      -- Stop diagnostics sink
      snk_diag_en <= '0';
      
      -- Restart diagnostics source and sink
      proc_common_wait_some_cycles(dp_clk, 1);
      src_diag_en <= '1';
      snk_diag_en <= '1';
    END LOOP;
    
    -- If the test failed then it would have stopped already, so it the test has passed
    REPORT "[OK] Test passed." SEVERITY NOTE;

    -- Stop the simulation
    -- . Stopping the clocks via tb_end does end the tb for the DDR3 IP, but is not sufficient to stop the tb for the DDR4 IP.
    -- . Making ctlr_ref_rst <= '1'; also does not stop the tb with the DDR4 IP (apparently some loop remains running in the DDR4 model), so therefore force simulation stop
    i_tb_end <= '1';

    ctlr_ref_rst <= '1';
    IF g_tb_end=FALSE THEN
      REPORT "Tb Simulation finished." SEVERITY NOTE;
    ELSE
      REPORT "Tb Simulation finished." SEVERITY FAILURE;
    END IF;
    WAIT;
  END PROCESS;

  u_diagnostics: ENTITY diagnostics_lib.diagnostics 
  GENERIC MAP (
    g_dat_w             => c_dp_data_w,
    g_nof_streams       => 1
     ) 
  PORT MAP (
    rst                 => dp_rst,
    clk                 => dp_clk,

    snk_out_arr(0)      => diag_rd_snk_out,
    snk_in_arr(0)       => diag_rd_snk_in,
    snk_diag_en(0)      => snk_diag_en,
    snk_diag_md(0)      => '1',
    snk_diag_res(0)     => snk_diag_res,
    snk_diag_res_val(0) => snk_diag_res_val,
    snk_val_cnt(0)      => snk_val_cnt,

    src_out_arr(0)      => diag_wr_src_out,
    src_in_arr(0)       => diag_wr_src_in,
    src_diag_en(0)      => src_diag_en,
    src_diag_md(0)      => '1',
    src_val_cnt(0)      => src_val_cnt
  );

  dbg_wr_data <= diag_wr_src_out.data(c_dp_data_w-1 DOWNTO 0);
  dbg_wr_val  <= diag_wr_src_out.valid;
  dbg_rd_data <= diag_rd_snk_in.data(c_dp_data_w-1 DOWNTO 0);
  dbg_rd_val  <= diag_rd_snk_in.valid;
  
  wr_val_cnt <= wr_val_cnt + 1 WHEN rising_edge(dp_clk) AND diag_wr_src_out.valid='1';
  
  p_sop_eop : PROCESS (diag_wr_src_out, wr_val_cnt)
  BEGIN
    -- Default, fits g_wr_flush_mode="VAL"
    wr_src_out <= diag_wr_src_out;
    
    IF g_wr_flush_mode="SOP" THEN
      wr_src_out.sop <= '0';
      wr_src_out.eop <= '0';
      IF wr_val_cnt MOD c_wr_frame_size = 0 THEN
        wr_src_out.sop <= diag_wr_src_out.valid;
      ELSIF wr_val_cnt MOD c_wr_frame_size = c_wr_frame_size-1 THEN
        wr_src_out.eop <= diag_wr_src_out.valid;
      END IF;
    END IF;
    
    IF g_wr_flush_mode="SYN" THEN
      wr_src_out.sync <= '0';
      IF wr_val_cnt MOD c_wr_sync_period = 0 THEN
        wr_src_out.sync <= diag_wr_src_out.valid;
      END IF;
    END IF;
  END PROCESS;
  
  
  -- Map original dvr interface signals to t_mem_ctlr_mosi/miso
  dvr_done              <= dvr_miso.done;           -- Requested wr or rd sequence is done
  dvr_mosi.burstbegin   <= dvr_en;
  dvr_mosi.wr           <= dvr_wr_not_rd;           -- No need to use dvr_mosi.rd
  dvr_mosi.address      <= RESIZE_MEM_CTLR_ADDRESS(dvr_start_address);
  dvr_mosi.burstsize    <= RESIZE_MEM_CTLR_BURSTSIZE(dvr_nof_data);
  dvr_mosi.flush        <= dvr_wr_flush_en;
    
  u_io_ddr: ENTITY work.io_ddr
  GENERIC MAP(
    g_technology             => g_technology,
    g_tech_ddr               => c_tech_ddr,
    g_use_ddr_memory_model   => g_use_ddr_memory_model,   -- when TRUE use internal DDR memory model
    g_cross_domain_dvr_ctlr  => c_cross_domain_dvr_ctlr,
    g_cross_domain_delay_len => c_meta_delay_len,
    g_wr_data_w              => c_dp_data_w,
    g_wr_fifo_depth          => c_wr_fifo_depth,  -- >=16 AND >g_tech_ddr.maxburstsize, defined at DDR side of the FIFO.
    g_rd_fifo_depth          => c_rd_fifo_depth,  -- >=16 AND >g_tech_ddr.maxburstsize, defined at DDR side of the FIFO. 
    g_rd_fifo_af_margin      => c_rd_fifo_af_margin,
    g_rd_data_w              => c_dp_data_w,
    g_wr_flush_mode          => g_wr_flush_mode,
    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       => ctlr_clk,
    ctlr_rst_out       => ctlr_rst,
    
    ctlr_clk_in        => ctlr_clk,  -- connect ctlr_clk_out to ctlr_clk_in at top level to avoid potential delta-cycle differences between the same clock
    ctlr_rst_in        => ctlr_rst,
    
    -- MM clock domain
    mm_clk             => mm_clk,    
    mm_rst             => mm_rst,                                          
    
    -- MM register map for DDR controller status info
    reg_io_ddr_mosi    => reg_io_ddr_mosi,
    reg_io_ddr_miso    => reg_io_ddr_miso,
    
    -- Driver clock domain
    dvr_clk            => dvr_clk,
    dvr_rst            => dvr_rst,
    
    dvr_miso           => dvr_miso,
    dvr_mosi           => dvr_mosi,
    
    -- Write FIFO clock domain
    wr_clk             => dp_clk,
    wr_rst             => dp_rst,

    wr_fifo_usedw      => wr_fifo_usedw,
    wr_sosi            => wr_src_out, 
    wr_siso            => diag_wr_src_in,
  
    -- Read FIFO clock domain
    rd_clk             => dp_clk,
    rd_rst             => dp_rst,

    rd_fifo_usedw      => rd_fifo_usedw,
    rd_sosi            => diag_rd_snk_in,
    rd_siso            => diag_rd_snk_out,

    -- DDR PHY external interface
    phy_ou             => phy_ou,
    phy_io             => phy_io,
    phy_in             => phy_in
  );
  
  external_ddr_memory_model : IF g_use_ddr_memory_model=FALSE GENERATE
    u_tech_ddr_memory_model : ENTITY tech_ddr_lib.tech_ddr_memory_model
    GENERIC MAP (
      g_sim      => TRUE,
      g_tech_ddr => c_tech_ddr
    )
    PORT MAP (
      mem_in => phy_ou,
      mem_io => phy_io
    );
  END GENERATE;
     
END ARCHITECTURE str;