-------------------------------------------------------------------------------
-- Title      : multiport linked list
-- Project    : WhiteRabbit switch
-------------------------------------------------------------------------------
-- File       : swc_multiport_linked_list.vhd
-- Author     : Maciej Lipinski, Grzegorz Daniluk
-- Company    : CERN BE-CO-HT
-- Created    : 2010-10-26
-- Last update: 2016-08-26
-- Platform   : FPGA-generic
-- Standard   : VHDL'87
-------------------------------------------------------------------------------
-- Description: 
-------------------------------------------------------------------------------
--
-- Copyright (c) 2010 - 2016 CERN / BE-CO-HT
--
-- This source file is free software; you can redistribute it   
-- and/or modify it under the terms of the GNU Lesser General   
-- Public License as published by the Free Software Foundation; 
-- either version 2.1 of the License, or (at your option) any   
-- later version.                                               
--
-- This source 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 Lesser General Public License for more 
-- details.                                                     
--
-- You should have received a copy of the GNU Lesser General    
-- Public License along with this source; if not, download it   
-- from http://www.gnu.org/licenses/lgpl-2.1.html
--
-------------------------------------------------------------------------------
-- Revisions  :
-- Date        Version  Author   Description
-- 2010-10-26  1.0      mlipinsk Created
-- 2012-02-02  2.0      mlipinsk generic-azed
-- 2012-02-16  3.0      mlipinsk speeded up & adapted to cut-through & adapted to new MPM
-- 2016-08-26  4.0      greg.d   rewritten to use multiport RAM with multiple IB write ports
--                               also cleaned up IB write arbiter, now uses simple FSM
-------------------------------------------------------------------------------


library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.std_logic_misc.all;
use ieee.math_real.CEIL;
use ieee.math_real.log2;

library work;
use work.swc_swcore_pkg.all;
use work.genram_pkg.all;
use work.gencores_pkg.all;

entity swc_multiport_linked_list is
  generic ( 
    g_num_ports                        : integer := 7; --:= c_swc_num_ports
    g_addr_width                       : integer := 10; --:= c_swc_page_addr_width;
    g_page_num                         : integer := 1024;  --:= c_swc_packet_mem_num_pages
    -- new stuff
    g_size_width                       : integer := 10;
    g_partial_select_width             : integer := 1;
    g_data_width                       : integer := 18  --:= c_swc_packet_mem_num_pages    + 2

  ----------------------------------------------------------------------------------------
  -- the following relation is needed for the things to work
  -- g_data_width >= 2 + g_addr_width
  -- g_data_width >= 2 + g_partial_select_width + g_size_width
  -- 
  -- this is because the format of the LL entry (data) is the following (ML: 4/03/2012 - not valid!!!):
  -- 
  -- |---------------------------------------------------------------------------------------|
  -- |1[bit]|1[bit]|                      g_data_width - 2 [bits]                            |
  -- |---------------------------------------------------------------------------------------|
  -- |      |      |                   next_page_addr (g_addr_width bits)                    | eof=0
  -- |valid | eof  |                                                                         |
  -- |      |      |dsel(g_partial_select_width [bits]) | words in page (g_size_width [bits])| eof=1
  ---|---------------------------------------------------------------------------------------|
  );
  port (
    rst_n_i               : in std_logic;
    clk_i                 : in std_logic;

    ib_i  : in  t_ib2ll_array(g_num_ports-1 downto 0);
    ib_o  : out t_ll2ib_array(g_num_ports-1 downto 0);

    ------------ reading from the Linked List by freeing module ----------
    free_pck_i  : in  t_fp2ll_array(g_num_ports-1 downto 0);
    free_pck_o  : out t_ll2fp_array(g_num_ports-1 downto 0);
    
    -------- reading by Multiport Memory (direct access) -------
    -- requested address,  needs to be valid till write_done_o=HIGH
    mpm_rpath_addr_i      : in  std_logic_vector(g_addr_width - 1 downto 0);
    -- requested data
    mpm_rpath_data_o      : out std_logic_vector(g_data_width - 1 downto 0)
    );

end swc_multiport_linked_list;

architecture syn of swc_multiport_linked_list is

  constant c_MPRAM_WPORTS : integer := 3;
  constant c_MPRAM_RPORTS : integer := 2;

  component mpram_lvt
    generic (
      MEMD : integer;
      DATW : integer;
      nRPF : integer;
      nWPF : integer;
      nRPS : integer;
      nWPS : integer;
      LVTA : string;
      WAWB : integer;
      RAWB : integer;
      RDWB : integer);
    port (
      clk   : in std_logic;
      rdWr  : in std_logic;
      WEnb  : in std_logic_vector(nWPF+nWPS-1 downto 0);
      WAddr : in std_logic_vector(f_log2_size(MEMD)*(nWPF+nWPS)-1 downto 0);
      WData : in std_logic_vector(DATW*(nWPF+nWPS)-1 downto 0);
      RAddr : in std_logic_vector(f_log2_size(MEMD)*(nRPF+nRPS)-1 downto 0);
      RData : out std_logic_vector(DATW*(nRPF+nRPS)-1 downto 0));
  end component;

  function ll_split_writes(write_i:std_logic_vector; g_num_ports:integer; idx_h:integer; idx_l:integer) return std_logic_vector is
    variable write_split : std_logic_vector(g_num_ports-1 downto 0);
    variable nidx : integer range 0 to g_num_ports;
  begin
    if idx_h > g_num_ports-1 then
      nidx := g_num_ports-1;
    else
      nidx := idx_h;
    end if;
    if idx_l > g_num_ports-1 then
      write_split := (others=>'0');
    else
      write_split(nidx downto idx_l) := write_i(nidx downto idx_l);
      write_split(idx_l-1 downto 0)  := (others=>'0');
      write_split(g_num_ports-1 downto nidx+1) := (others=>'0');
    end if;

    return write_split;
  end function;

  --------------------------------------
  -- packing interfaces into records  --
  --------------------------------------
  type t_llports_array is array (natural range <>) of std_logic_vector(g_num_ports-1 downto 0);
  -- IB write interface
  signal write_done_ored : t_llports_array(c_MPRAM_WPORTS-1 downto 0);

  --------------------------------------
  -- MPRAM signals                    --
  --------------------------------------
  -- packing i/f into records
  signal mpram_wadr   : std_logic_vector(c_MPRAM_WPORTS*g_addr_width-1 downto 0);
  signal mpram_wdat   : std_logic_vector(c_MPRAM_WPORTS*g_data_width-1 downto 0);
  signal mpram_radr   : std_logic_vector(c_MPRAM_RPORTS*g_addr_width-1 downto 0);
  signal mpram_rdat   : std_logic_vector(c_MPRAM_RPORTS*g_data_width-1 downto 0);
  signal x_mpram_wadr, x_mpram_wadr_d0 : t_lladr_array(c_MPRAM_WPORTS-1 downto 0);
  signal x_mpram_wdat, x_mpram_wdat_d0 : t_lldat_array(c_MPRAM_WPORTS-1 downto 0);
  signal x_mpram_wdone: t_llports_array(c_MPRAM_WPORTS-1 downto 0);
  signal x_mpram_radr : t_lladr_array(c_MPRAM_RPORTS-1 downto 0);
  signal x_mpram_rdat : t_lldat_array(c_MPRAM_RPORTS-1 downto 0);
  signal mpram_write  : std_logic_vector(c_MPRAM_WPORTS-1 downto 0);

  --------------------------------------
  -- writing process                  --
  --------------------------------------
  type t_mll_fsm is (WRITE, WRITE_NEXT);
  type t_mll_fsm_array is array (natural range <>) of t_mll_fsm;
  type t_int_array is array (natural range <>) of integer range 0 to g_num_ports-1;

  signal mll_state : t_mll_fsm_array(c_MPRAM_WPORTS-1 downto 0);
  signal write_i_vec    : std_logic_vector(g_num_ports-1 downto 0);
  signal write_next_vec : std_logic_vector(g_num_ports-1 downto 0);
  signal write_req      : t_llports_array(c_MPRAM_WPORTS-1 downto 0);
  signal write_i_split  : t_llports_array(c_MPRAM_WPORTS-1 downto 0);
  signal write_grant    : t_llports_array(c_MPRAM_WPORTS-1 downto 0);
  signal write_grant_d0 : t_llports_array(c_MPRAM_WPORTS-1 downto 0);
  signal write_idx  : t_int_array(c_MPRAM_WPORTS-1 downto 0);
  signal write_done : std_logic_vector(c_MPRAM_WPORTS-1 downto 0);
  signal wadr_sel   : std_logic_vector(c_MPRAM_WPORTS-1 downto 0);

  
  ---------------------- reading process (free pck module) --------------------------------
  -- arbitrating access
  signal free_pck_request_vec     : std_logic_vector(g_num_ports-1 downto 0);
  signal free_pck_request_noempty : std_logic;
  signal free_pck_grant_vec       : std_logic_vector(g_num_ports-1 downto 0); 
  signal free_pck_grant_vec_d0    : std_logic_vector(g_num_ports-1 downto 0);
  signal free_pck_grant_vec_d1    : std_logic_vector(g_num_ports-1 downto 0);
  signal free_pck_grant_index     : integer range 0 to g_num_ports-1;
  signal free_pck_grant_index_d0  : integer range 0 to g_num_ports-1;
  signal free_pck_grant_valid     : std_logic;
  signal free_pck_grant_valid_d0  : std_logic;
  signal free_pck_read                : std_logic_vector(g_num_ports -1 downto 0);
  -- interface to DPRAM
  signal ll_free_pck_ena         : std_logic;
  signal ll_free_pck_data        : std_logic_vector(g_data_width - 1 downto 0);
  -- output
  signal free_pck_data          : t_lldat_array(g_num_ports-1 downto 0);
  signal free_pck_data_out      : t_lldat_array(g_num_ports-1 downto 0);

begin

  SWITCHED_MPRAM: mpram_lvt
    generic map (
      MEMD => g_page_num,
      DATW => g_data_width,
      nRPF => c_MPRAM_RPORTS,
      nWPF => c_MPRAM_WPORTS,
      nRPS => 0,
      nWPS => 0,
      LVTA => "LVTREG",
      WAWB => 0,
      RAWB => 0,
      RDWB => 1)
    port map (
      clk   => clk_i,
      rdWr  => '0',
      WEnb  => mpram_write,
      WAddr => mpram_wadr,
      WData => mpram_wdat,
      RAddr => mpram_radr,
      RData => mpram_rdat);
  -- pack MPRAM interfaces into types
  GEN_MPRAM_WIF: for I in 0 to c_MPRAM_WPORTS-1 generate
    mpram_wadr((I+1)*g_addr_width-1 downto I*g_addr_width) <= x_mpram_wadr_d0(I);
    mpram_wdat((I+1)*g_data_width-1 downto I*g_data_width) <= x_mpram_wdat_d0(I);
  end generate;

  GEN_MPRAM_RIF: for I in 0 to c_MPRAM_RPORTS-1 generate
    mpram_radr((I+1)*g_addr_width-1 downto I*g_addr_width) <= x_mpram_radr(I);
    x_mpram_rdat(I) <= mpram_rdat((I+1)*g_data_width-1 downto I*g_data_width);
  end generate;

  -- MPM read interface
  x_mpram_radr(0)  <= mpm_rpath_addr_i;
  mpm_rpath_data_o <= x_mpram_rdat(0);
  -- Free PCK read interface
  x_mpram_radr(1)  <= free_pck_i(free_pck_grant_index).adr;
  ll_free_pck_data <= x_mpram_rdat(1);
   
  --------------------------------------
  -- Serving write requests from IBs  --
  --------------------------------------
  GEN_WRITE_VEC: for I in 0 to g_num_ports-1 generate
    write_i_vec(I) <= ib_i(I).write;
    ib_o(I).done   <= write_done_ored(c_MPRAM_WPORTS-1)(I);
  end generate;
  -- we split IBs into write ports
  -- IB port ranges for each write port are hardcoded to have similar
  -- performance (IBs-per-write port) for 8 and 18 port version.
  write_i_split(0) <= ll_split_writes(write_i_vec, g_num_ports, 5, 0);
  write_i_split(1) <= ll_split_writes(write_i_vec, g_num_ports, 11, 6);
  write_i_split(2) <= ll_split_writes(write_i_vec, g_num_ports, g_num_ports-1, 12);

  -- combine together write_done signals from multiple MPRAM write ports
  write_done_ored(0) <= x_mpram_wdone(0);
  GEN_WDONE: for I in 1 to c_MPRAM_WPORTS-1 generate
    write_done_ored(I) <= write_done_ored(I-1) or x_mpram_wdone(I);
  end generate;


  GEN_WRFSM: for I in 0 to c_MPRAM_WPORTS-1 generate

    write_idx(I) <= to_integer(unsigned(f_onehot_decode(write_grant(I), g_num_ports)));

    -- select address to be written to MPRAM. For requests with
    -- write_next_addr_valid = 1 we prefer to write first the "next address" to
    -- invalidate this page as soon as possible. Otherwise the output or freeing
    -- module could fetch incorrect data for that page.
    x_mpram_wadr(I) <= ib_i(write_idx(I)).adr_next when(ib_i(write_idx(I)).next_valid = '1' and wadr_sel(I)='0') else
                       ib_i(write_idx(I)).adr;

    -- the same way we select data to be written with the above address. It's
    -- either data passed with the request from the IB or zeros to invalidate
    -- next page (next addr from the request).
    x_mpram_wdat(I) <= (others=>'0') when (ib_i(write_idx(I)).next_valid = '1' and wadr_sel(I)='0') else
                       ib_i(write_idx(I)).data;

    x_mpram_wdone(I) <= write_grant_d0(I) when(write_done(I) = '1') else
                        (others=>'0');

    f_rr_arbitrate(write_req(I), write_grant_d0(I), write_grant(I));

    process(clk_i)
    begin
      if rising_edge(clk_i) then
        if rst_n_i='0' then
          mll_state(I)      <= WRITE;
          wadr_sel(I)       <= '0';
          write_grant_d0(I) <= (others=>'0');
          write_req(I)      <= (others=>'0');
          write_done(I)     <= '0';
          x_mpram_wadr_d0(I)<= (others=>'0');
          x_mpram_wdat_d0(I)<= (others=>'0');
        else

          -- address and data delayed by 1 cycle to align with mpram_write from
          -- the state machine.
          x_mpram_wadr_d0(I)<= x_mpram_wadr(I);
          x_mpram_wdat_d0(I)<= x_mpram_wdat(I);

          case(mll_state(I)) is
            when WRITE =>

              if (or_reduce(write_grant(I)) = '1' and ib_i(write_idx(I)).next_valid = '1') then
                mll_state(I)   <= WRITE_NEXT;
                wadr_sel(I)    <= '1';  -- switch the address to be written
                mpram_write(I) <= '1';
                write_done(I)  <= '0';
              elsif (or_reduce(write_grant(I)) = '1') then
                wadr_sel(I)    <= '0';
                mpram_write(I) <= '1';
                write_done(I)  <= '1';
                write_grant_d0(I) <= write_grant(I);
                write_req(I) <= write_i_split(I) and (not write_grant(I)) and (not write_grant_d0(I));
              else
                wadr_sel(I)    <= '0';
                mpram_write(I) <= '0';
                write_done(I)  <= '0';
                write_grant_d0(I) <= write_grant(I);
                write_req(I) <= write_i_split(I) and (not write_grant(I)) and (not write_grant_d0(I));
              end if;

            when WRITE_NEXT =>
              mpram_write(I) <= '1';
              wadr_sel(I)    <= '0';
              write_done(I)  <= '1';
              write_grant_d0(I) <= write_grant(I);
              write_req(I) <= write_i_split(I) and (not write_grant(I)) and (not write_grant_d0(I));
              mll_state(I)   <= WRITE;

            when others =>
              mll_state(I) <= WRITE;
          end case;

        end if;
      end if;
    end process;
  end generate;


  ---------------------------------------
  -- Scheduling free requests from the --
  -- freeing module.                   --
  ---------------------------------------

  gen_free_pck_request_vec : for i in 0 to g_num_ports - 1 generate
    free_pck_request_vec(i) <= free_pck_read(i) and (not (free_pck_grant_vec_d0(i) or free_pck_grant_vec_d1(i)));
  end generate;

  -- reading
  f_rr_arbitrate(req      => free_pck_request_vec,
                 pre_grant=> free_pck_grant_vec_d0,
                 grant    => free_pck_grant_vec);

  free_pck_grant_index     <= to_integer(unsigned(f_onehot_decode(free_pck_grant_vec_d0, g_num_ports)));
  free_pck_request_noempty <= '1' when (or_reduce(free_pck_request_vec) /= '0') else '0';

  process(clk_i, rst_n_i)
  begin
    if rising_edge(clk_i) then
      if(rst_n_i = '0') then
        free_pck_grant_vec_d0        <= (others => '0' );
        free_pck_grant_vec_d1        <= (others => '0' );
        free_pck_grant_valid         <= '0';
        free_pck_grant_valid_d0      <= '0';
        free_pck_grant_index_d0      <=  0;
      else
        free_pck_grant_vec_d0        <= free_pck_grant_vec;
        free_pck_grant_vec_d1        <= free_pck_grant_vec_d0;   
        free_pck_grant_index_d0      <= free_pck_grant_index;
        free_pck_grant_valid         <= free_pck_request_noempty; -- we always get grant in 1-cycle
        free_pck_grant_valid_d0      <= free_pck_grant_valid;
      end if;
    end if;
  end process;

  read_valid: for i in 0 to g_num_ports -1 generate

    free_pck_data(i) <= ll_free_pck_data  when  (free_pck_grant_index_d0 = i) else (others => '0' );

    read_data_valid: swc_ll_read_data_validation
    generic map(
      g_addr_width => g_addr_width,
      g_data_width => g_data_width,
      g_wports     => c_MPRAM_WPORTS
    )
    port map(
      clk_i                => clk_i, 
      rst_n_i              => rst_n_i,  

      read_req_i           => free_pck_i(i).rd_req,
      read_req_o           => free_pck_read(i),
      
      read_addr_i          => free_pck_i(i).adr,
      read_data_i          => free_pck_data(i),
      read_data_valid_i    => free_pck_data(i)(g_data_width - 1), 
      read_data_ready_i    => free_pck_grant_vec_d1(i),  
      
      -- eavesdropping
      write_addr_i         => x_mpram_wadr_d0,
      write_data_i         => x_mpram_wdat_d0,
      write_data_ready_i   => mpram_write,
      -- end of eavesdropping

      read_data_o          => free_pck_o(i).data,
      read_data_valid_o    => free_pck_o(i).rd_done
    );

   end generate;


  ll_free_pck_ena       <= free_pck_grant_valid_d0;

end syn;
