-------------------------------------------------------------------------------
--
-- 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: Self checking and self-stopping tb for ddrctrl_input.vhd
-- Usage:
-- > run -a

LIBRARY IEEE, common_lib, technology_lib, tech_ddr_lib, dp_lib;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.NUMERIC_STD.ALL;
USE IEEE.MATH_REAL.ALL;
USE technology_lib.technology_pkg.ALL;
USE tech_ddr_lib.tech_ddr_pkg.ALL;
USE dp_lib.dp_stream_pkg.ALL;
USE common_lib.common_mem_pkg.ALL;
USE common_lib.common_pkg.ALL;

ENTITY tb_ddrctrl_input IS
  GENERIC (

    g_tech_ddr                : t_c_tech_ddr                                          := c_tech_ddr4_8g_1600m;                    -- type of memory
    g_nof_streams             : POSITIVE                                              := 12;                                      -- number of input streams
    g_data_w                  : NATURAL                                               := 14;                                      -- data with of input data vectors
    g_sim_length              : NATURAL                                               := 52

  );
END tb_ddrctrl_input;

ARCHITECTURE tb OF tb_ddrctrl_input IS

  -- constants for testbench
  CONSTANT  c_sim_model       : BOOLEAN                                               := TRUE;                                    -- determens if this is a simulation
  CONSTANT  c_clk_freq        : NATURAL                                               := 200;                                     -- clock frequency in MHz
  CONSTANT  c_clk_period      : TIME                                                  := (10**6 / c_clk_freq) * 1 ps;             -- clock priod, 5 ns

  -- constants for readability
  CONSTANT  c_in_data_w       : NATURAL                                               := g_nof_streams * g_data_w;                -- output data with, 168
  CONSTANT  c_out_data_w      : NATURAL                                               := func_tech_ddr_ctlr_data_w( g_tech_ddr ); -- output data vector with, 576
  CONSTANT  c_adr_w           : NATURAL                                               := func_tech_ddr_ctlr_address_w( g_tech_ddr ) ;                                       -- address with in simulation
  CONSTANT  c_adr_size        : NATURAL                                               := 2**c_adr_w;                              -- address size in simulation

  -- function for making total data vector
  FUNCTION  c_total_vector_init RETURN STD_LOGIC_VECTOR IS
    VARIABLE v_total_vector   : STD_LOGIC_VECTOR(c_in_data_w*g_sim_length-1 DOWNTO 0);
  BEGIN
    FOR I IN 0 TO g_sim_length*g_nof_streams-1 LOOP
      v_total_vector(g_data_w*(I+1)-1 DOWNTO g_data_w*I) := TO_UVEC(I, g_data_w);
    END LOOP;
    RETURN v_total_vector;
  END FUNCTION c_total_vector_init;

  -- constant for running the test
  CONSTANT  c_total_vector    : STD_LOGIC_VECTOR(c_in_data_w*g_sim_length-1 DOWNTO 0) := c_total_vector_init;                     -- vector which contains all input data vectors to make it easy to fill ctr_vector


  -- input signals for ddrctrl_input.vhd
  SIGNAL    clk               : STD_LOGIC                                             := '1';
  SIGNAL    rst               : STD_LOGIC                                             := '0';
  SIGNAL    q_rst             : STD_LOGIC                                             := '0';
  SIGNAL    q_q_rst           : STD_LOGIC                                             := '0';
  SIGNAL    in_sosi_arr       : t_dp_sosi_arr(g_nof_streams-1 DOWNTO 0)               := (OTHERS => c_dp_sosi_init);              -- input data signal for ddrctrl_pack.vhd 

  -- output singals from ddrctrl_input.vhd
  SIGNAL    out_of            : NATURAL                                               := 0;                                       -- output signal from ddrctrl_repack to determen how high the overflow is
  SIGNAL    out_sosi          : t_dp_sosi                                             := c_dp_sosi_init;                     -- output signal from ddrctrl_pack.vhd
  SIGNAL    out_adr           : NATURAL                                               := 0;

  -- testbench signal
  SIGNAL    tb_end            : STD_LOGIC                                             := '0';                                     -- signal to turn the testbench off

  -- signals for running test
  SIGNAL    in_data_cnt       : NATURAL                                               := 0;                                       -- signal which contains the amount of times there has been input data for ddrctrl_repack.vhd
  SIGNAL    q_in_data_cnt     : NATURAL                                               := 0;                                       -- signal which contains the amount of times there has been input data for ddrctrl_repack.vhd with a delay of 1 clockcycle
  SIGNAL    q_q_in_data_cnt   : NATURAL                                               := 0;                                       -- signal which contains the amount of times there has been input data for ddrctrl_repack.vhd with a delay of 2 clockcycles
  SIGNAL    test_running      : STD_LOGIC                                             := '0';                                     -- signal to tell wheter the testing has started
  SIGNAL    q_test_running    : STD_LOGIC                                             := '0';                                     -- signal to tell wheter the testing has started with a delay of 1 clockcycle
  SIGNAL    q_q_test_running  : STD_LOGIC                                             := '0';                                     -- signal to tell wheter the testing has started with a delay of 2 clockcycles
  SIGNAL    lag_due_reset     : NATURAL                                               := 0;                                       -- signal to hold the address lag after a rest
  SIGNAL    q_lag_due_reset   : NATURAL                                               := 0;                                       -- signal to hold the address lag after a rest with a delay of 1 clockcycle

BEGIN

  -- generating clock
  clk               <= NOT clk OR tb_end AFTER c_clk_period/2;

  -- excecuting test
  p_test : PROCESS
  BEGIN

    -- start the test
    tb_end          <= '0';
    WAIT UNTIL rising_edge(clk);                                                                                                  -- align to rising edge
    WAIT FOR c_clk_period*4;
    rst <= '1';
    WAIT FOR c_clk_period*1;
    rst <= '0';
    test_running <= '1';

    -- filling the input data vectors with the corresponding numbers
    make_data : FOR J IN 0 TO g_sim_length-1 LOOP
      in_data_cnt     <= in_data_cnt+1;
      fill_in_sosi_arr_rest : FOR I IN 0 TO g_nof_streams-1 LOOP
        in_sosi_arr(I).data(g_data_w-1 DOWNTO 0)   <= c_total_vector(g_data_w*(I+1)+J*c_in_data_w-1 DOWNTO g_data_w*I+J*c_in_data_w);
      END LOOP;
      WAIT FOR c_clk_period*1;
    END LOOP;
    test_running      <= '0';

    -- testing reset
    --FOR I IN 0 TO g_sim_length-1 LOOP
      --rst <= '1';
      --WAIT FOR c_clk_period*1;
      --rst <= '0';
      --WAIT FOR c_clk_period*((((c_out_data_w/c_in_data_w)+1)*c_adr_size)+4);
    --END LOOP;


    -- stopping the testbench
    WAIT FOR c_clk_period*4;
    tb_end <= '1';
    ASSERT FALSE                                                                                                                          REPORT "Test: OK"                                                                                                                             SEVERITY FAILURE;
  END PROCESS;

  -- generating compare data for out_sosi
  p_out_sosi : PROCESS
  BEGIN
    WAIT UNTIL rising_edge(clk);
    if rising_edge(clk) THEN
      q_q_rst           <= q_rst;
      q_lag_due_reset   <= lag_due_reset;
      q_rst             <= rst;
    END IF;
    IF q_rst = '1' THEN
      IF lag_due_reset+out_adr+2 >= c_adr_size THEN
        lag_due_reset <= lag_due_reset+out_adr+2-c_adr_size;
      ELSE
        lag_due_reset <= lag_due_reset+out_adr+2;
      END IF;
    END IF;
  END PROCESS;

  -- verifying if the address is correct by keeping track of the address
  p_verify_address : PROCESS

  VARIABLE v_adr  : NATURAL range 0 to c_adr_size-1 := c_adr_size-2;

  BEGIN
    WAIT UNTIL rst = '1';
    WAIT UNTIL rst = '0';
    FOR I IN 0 TO g_sim_length-1 LOOP
      IF v_adr >= q_lag_due_reset THEN
        ASSERT v_adr-q_lag_due_reset              = out_adr                      REPORT "Wrong address, 1, v_adr = " & NATURAL'image(v_adr-q_lag_due_reset) & ", address = " & NATURAL'image(out_adr)                       SEVERITY ERROR;
      ELSE
        ASSERT (v_adr-q_lag_due_reset)+c_adr_size = out_adr                      REPORT "Wrong address, 2, v_adr = " & NATURAL'image((v_adr-q_lag_due_reset)+c_adr_size) & ", address = " & NATURAL'image(out_adr)          SEVERITY ERROR;
      END IF;
      WAIT UNTIL out_sosi.valid = '1';     
      IF q_q_rst = '1' THEN
        WAIT UNTIL out_sosi.valid = '1';
      END IF;
      IF v_adr = c_adr_size-1 THEN
        v_adr := 0;
      ELSE
        v_adr := v_adr+1;
      END IF;
    END LOOP;
    WAIT;
  END PROCESS;

  -- verification by checking if the input vectors are correctly put into the output vector and the amount of overflow is as expected
  p_verify : PROCESS

  VARIABLE  ctr_of            : NATURAL                                                := 0;
  VARIABLE  out_data_cnt      : NATURAL                                                := 0;

  BEGIN
  WAIT UNTIL rising_edge(clk);
  IF q_q_test_running = '1' AND out_sosi.valid = '1' THEN
    out_data_cnt      := out_data_cnt+1;
    IF out_data_cnt mod 2 = 0 THEN
      ctr_of := c_in_data_w*(q_q_in_data_cnt)-c_out_data_w*out_data_cnt;
      ASSERT ctr_of                               = out_of                                                                                REPORT "The amount of overflow does not match, ctr_of = " & NATURAL'image(ctr_of) & ", out_of = " & NATURAL'image(out_of)                     SEVERITY ERROR;
    END IF;
    ASSERT out_sosi.data(c_out_data_w-1 DOWNTO 0) = c_total_vector(c_out_data_w*out_data_cnt-1 DOWNTO c_out_data_w*(out_data_cnt-1))      REPORT "Data does not match, out_data_cnt = " & NATURAL'image(out_data_cnt)                                                                   SEVERITY ERROR;
  END IF;
  END PROCESS;


  -- DUT
  u_ddrctrl_input : ENTITY work.ddrctrl_input
  GENERIC MAP (
    g_tech_ddr        => g_tech_ddr,
    g_sim_model       => c_sim_model,
    g_nof_streams     => g_nof_streams,
    g_data_w          => g_data_w
  )
  PORT MAP (
    clk               => clk,
    rst               => rst,
    in_sosi_arr       => in_sosi_arr,
    out_of            => out_of,
    out_sosi          => out_sosi,
    out_adr           => out_adr
  );

END tb;