-------------------------------------------------------------------------------
--
-- Project name: UDP_100G_SWITCH
--
-- (C) Copyright 2019 DFC Design, s.r.o., Brno, Czech Republic
-- Author: Jakub Hadamek (j.hadamek@dfcdesign.cz)
--
-------------------------------------------------------------------------------
-- 
-------------------------------------------------------------------------------




library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

Library xpm;
use xpm.vcomponents.all;

-- Uncomment the following library declaration if using
-- arithmetic functions with Signed or Unsigned values
use IEEE.NUMERIC_STD.ALL;

-- Uncomment the following library declaration if instantiating
-- any Xilinx leaf cells in this code.
--library UNISIM;
--use UNISIM.VComponents.all; 

entity udp_100g_switch is
   generic (
      g_ports_cnt             : natural range 1 to 8 := 1
           );
   port (
      -- Core reset
      RESET                   : in  std_logic := '0';
      CLK                     : in  std_logic := '0';

      ------------   UDP 100G Interface    ---------
      -- RX Datapath
      S_AXIS_UDP_TVALID       : in  std_logic := '0';
      S_AXIS_UDP_TREADY       : out std_logic := '0';
      S_AXIS_UDP_TLAST        : in  std_logic := '0';
      S_AXIS_UDP_TKEEP        : in  std_logic_vector(63 downto 0) := (others => '0');
      S_AXIS_UDP_TDATA        : in  std_logic_vector(511 downto 0) := (others => '0');
      -- RX net info
      RX_DST_UDP              : in std_logic_vector(15 downto 0) := (others => '0');
      RX_SRC_UDP              : in std_logic_vector(15 downto 0) := (others => '0');
      RX_SRC_IP               : in std_logic_vector(31 downto 0) := (others => '0');
      RX_SRC_MAC              : in std_logic_vector(47 downto 0) := (others => '0');
      RX_PACKET_LENGTH        : in std_logic_vector(15 downto 0) := (others => '0');
      -- TX Datapath
      M_AXIS_UDP_TVALID       : out std_logic := '0';
      M_AXIS_UDP_TREADY       : in  std_logic := '0';
      M_AXIS_UDP_TLAST        : out std_logic := '0';
      M_AXIS_UDP_TKEEP        : out std_logic_vector(63 downto 0) := (others => '0');
      M_AXIS_UDP_TDATA        : out std_logic_vector(511 downto 0) := (others => '0');
      -- TX net info
      TX_SRC_UDP              : out  std_logic_vector(15 downto 0) := (others => '0');
      TX_DST_UDP              : out  std_logic_vector(15 downto 0) := (others => '0');
      TX_DST_IP               : out  std_logic_vector(31 downto 0) := (others => '0');
      TX_DST_MAC              : out  std_logic_vector(47 downto 0) := (others => '0');
      -- Packet divided user signal 
      PACKET_DIVIDED          : in  std_logic := '0';

      ------------   USER Interface    ---------
      -- TX Datapath
      APP_S_AXIS_TVALID       : in  std_logic_vector(g_ports_cnt - 1 downto 0) := (others => '0');
      APP_S_AXIS_TREADY       : out std_logic_vector(g_ports_cnt - 1 downto 0) := (others => '0');
      APP_S_AXIS_TLAST        : in  std_logic_vector(g_ports_cnt - 1 downto 0) := (others => '0');
      APP_S_AXIS_TKEEP        : in  std_logic_vector(64 * g_ports_cnt - 1 downto 0) := (others => '0');
      APP_S_AXIS_TDATA        : in  std_logic_vector(512 * g_ports_cnt - 1 downto 0) := (others => '0');
      -- tx net info
      APP_TX_SRC_UDP          : in  std_logic_vector(g_ports_cnt * 16 - 1 downto 0) := (others => '0');
      APP_TX_DST_UDP          : in  std_logic_vector(g_ports_cnt * 16 - 1 downto 0) := (others => '0');
      APP_TX_DST_IP           : in  std_logic_vector(g_ports_cnt * 32 - 1 downto 0) := (others => '0');
      APP_TX_DST_MAC          : in  std_logic_vector(g_ports_cnt * 48 - 1 downto 0) := (others => '0');
      -- Packet divided user signal 
      APP_PACKET_DIVIDED      : out std_logic_vector(g_ports_cnt - 1 downto 0) := (others => '0');
      -- RX Datapath
      APP_M_AXIS_TVALID       : out std_logic_vector(g_ports_cnt - 1 downto 0) := (others => '0');
      APP_M_AXIS_TREADY       : in  std_logic_vector(g_ports_cnt - 1 downto 0) := (others => '0');
      APP_M_AXIS_TLAST        : out std_logic_vector(g_ports_cnt - 1 downto 0) := (others => '0');
      APP_M_AXIS_TKEEP        : out std_logic_vector(64 * g_ports_cnt - 1 downto 0) := (others => '0'); 
      APP_M_AXIS_TDATA        : out std_logic_vector(512 * g_ports_cnt - 1 downto 0) := (others => '0');
      -- RX net info
      APP_RX_DST_UDP          : in  std_logic_vector(g_ports_cnt * 16 - 1 downto 0) := (others => '0');
      APP_RX_SRC_UDP          : out std_logic_vector(g_ports_cnt * 16 - 1 downto 0) := (others => '0');
      APP_RX_SRC_IP           : out std_logic_vector(g_ports_cnt * 32 - 1 downto 0) := (others => '0');
      APP_RX_SRC_MAC          : out std_logic_vector(g_ports_cnt * 48 - 1 downto 0) := (others => '0');
      APP_RX_PACKET_LENGTH    : out std_logic_vector(g_ports_cnt * 16 - 1 downto 0) := (others => '0');
      -- TX net info

      ------------   COMMON status    ---------
      DUPLICATE_PORTS         : out std_logic_vector(g_ports_cnt - 1 downto 0)
      
   );
end;

architecture Behavioral of udp_100g_switch is

   function vec_or (vec : std_logic_vector)
      return std_logic is
      variable ret : std_logic := '0';
   begin
      for i in 0 to vec'left loop
         ret := ret or vec(i);
      end loop;
      return ret;
   end vec_or;

   type std_logic_vector_array is array (natural range <>) of std_logic_vector(DUPLICATE_PORTS'range);
   signal duplicate_ports_tmp : std_logic_vector_array(g_ports_cnt-1 downto 0) := (others => (others => '0'));
   signal duplicate_ports_i   : std_logic_vector(DUPLICATE_PORTS'range);
   
   type std_logic_vector16_array is array (natural range <>) of std_logic_vector(15 downto 0);
   signal udp_ports           : std_logic_vector16_array(g_ports_cnt - 1 downto 0);
   signal tx_active_port      : natural range 0 to g_ports_cnt-1 := g_ports_cnt-1;
   signal tx_port_assigned    : std_logic;
      
   signal rx_active_port      : natural range 0 to g_ports_cnt-1 := g_ports_cnt-1;
   signal rx_port_discard     : std_logic;
   
   signal tx_change           : std_logic;
   
begin

   DUPLICATE_PORTS <= duplicate_ports_i;

   ports_array_gen: for i in 0 to g_ports_cnt - 1 generate
      udp_ports(i) <= APP_RX_DST_UDP((i+1)*16 - 1 downto i*16);
   end generate;

   duplicate_ports_tmp_gen: for i in 0 to g_ports_cnt-1 generate
      duplicate_ports_j_gen: for j in 0 to g_ports_cnt-1 generate
         duplicate_ports_tmp(i)(j) <= '1' when i /= j and udp_ports(i) = udp_ports(j) else '0';
      end generate;
   end generate;

   duplicate_ports_i_gen: for i in 0 to duplicate_ports_i'left generate
      duplicate_ports_i(i) <= vec_or(duplicate_ports_tmp(i));
   end generate;
   
   
   tx_change <= (APP_S_AXIS_TVALID(tx_active_port) and APP_S_AXIS_TLAST(tx_active_port) and M_AXIS_UDP_TREADY);


   -- change of tx_active_port    
   change_tx_active_port: process(CLK)
      begin
         if rising_edge(CLK) then
            if RESET = '1' then 
               tx_active_port <= 0;
               tx_port_assigned <= '0';
            else
               -- change active port
               if tx_change = '1' then
                  tx_port_assigned <= '0';
                  for i in 0 to g_ports_cnt-1 loop
                     if i /= tx_active_port and APP_S_AXIS_TVALID(i) = '1' and
                        duplicate_ports_i(i) = '0' then
                        tx_active_port <= i;
                        tx_port_assigned <= '1';
                     end if;
                  end loop;
               elsif tx_port_assigned = '0' then
                  if (APP_S_AXIS_TVALID(tx_active_port) and M_AXIS_UDP_TREADY) = '1' then 
                     tx_port_assigned <= '1';
                  else
                     for i in 0 to g_ports_cnt-1 loop
                        if (APP_S_AXIS_TVALID(i) and not duplicate_ports_i(i)) = '1' then
                           tx_active_port <= i;
                           tx_port_assigned <= '1';
                        end if;
                     end loop;
                  end if;
               end if;
            end if;
         end if; 
      end process;
      
   -- change of rx_active_port    
   change_rx_active_port: process(RX_DST_UDP, udp_ports, duplicate_ports_i)
      begin
         rx_port_discard <= '1';
         rx_active_port <= 0;
         for i in 0 to g_ports_cnt-1 loop
            if RX_DST_UDP = udp_ports(i) and duplicate_ports_i(i) = '0' then
               rx_active_port <= i;
               rx_port_discard <= '0';
            end if;
         end loop;
      end process;     

   -- TX Datapath --
   M_AXIS_UDP_TVALID <= APP_S_AXIS_TVALID(tx_active_port);
   tx_gen: for i in 0 to g_ports_cnt-1 generate   
      APP_S_AXIS_TREADY(i) <= M_AXIS_UDP_TREADY when i = tx_active_port else '0';
      -- Packet divided user signal 
      APP_PACKET_DIVIDED(i) <= PACKET_DIVIDED when i = tx_active_port else '0';
   end generate;
   M_AXIS_UDP_TLAST  <= APP_S_AXIS_TLAST(tx_active_port);
   M_AXIS_UDP_TKEEP  <= APP_S_AXIS_TKEEP((tx_active_port+1)*M_AXIS_UDP_TKEEP'length-1 downto tx_active_port*M_AXIS_UDP_TKEEP'length);
   M_AXIS_UDP_TDATA  <= APP_S_AXIS_TDATA((tx_active_port+1)*M_AXIS_UDP_TDATA'length-1 downto tx_active_port*M_AXIS_UDP_TDATA'length);
   -- TX net info
   TX_SRC_UDP <= APP_TX_SRC_UDP((tx_active_port+1)*TX_SRC_UDP'length-1 downto tx_active_port*TX_SRC_UDP'length);
   TX_DST_UDP <= APP_TX_DST_UDP((tx_active_port+1)*TX_DST_UDP'length-1 downto tx_active_port*TX_DST_UDP'length);
   TX_DST_IP  <= APP_TX_DST_IP((tx_active_port+1)*TX_DST_IP'length-1 downto tx_active_port*TX_DST_IP'length);
   TX_DST_MAC <= APP_TX_DST_MAC((tx_active_port+1)*TX_DST_MAC'length-1 downto tx_active_port*TX_DST_MAC'length);
      
   -- RX Datapath --
   rx_gen: for i in 0 to g_ports_cnt-1 generate  
      APP_M_AXIS_TVALID(i) <= S_AXIS_UDP_TVALID and not rx_port_discard when i = rx_active_port else '0';
      APP_M_AXIS_TLAST(i)  <= S_AXIS_UDP_TLAST and not rx_port_discard when i = rx_active_port else '0';
      APP_M_AXIS_TKEEP((i+1)*S_AXIS_UDP_TKEEP'length-1 downto i*S_AXIS_UDP_TKEEP'length) <= S_AXIS_UDP_TKEEP 
         when i = rx_active_port and rx_port_discard = '0' else (others => '0');
      APP_M_AXIS_TDATA((i+1)*S_AXIS_UDP_TDATA'length-1 downto i*S_AXIS_UDP_TDATA'length) <= S_AXIS_UDP_TDATA 
         when i = rx_active_port and rx_port_discard = '0' else (others => '0');
      -- RX net info
      APP_RX_SRC_UDP((i+1)*RX_SRC_UDP'length-1 downto i*RX_SRC_UDP'length) <= RX_SRC_UDP when i = rx_active_port else (others => '0');
      APP_RX_SRC_IP((i+1)*RX_SRC_IP'length-1 downto i*RX_SRC_IP'length) <= RX_SRC_IP when i = rx_active_port else (others => '0');
      APP_RX_SRC_MAC((i+1)*RX_SRC_MAC'length-1 downto i*RX_SRC_MAC'length) <= RX_SRC_MAC when i = rx_active_port else (others => '0');
      APP_RX_PACKET_LENGTH((i+1)*RX_PACKET_LENGTH'length-1 downto i*RX_PACKET_LENGTH'length) <= RX_PACKET_LENGTH when i = rx_active_port else (others => '0');
   end generate;
   S_AXIS_UDP_TREADY <= (APP_M_AXIS_TREADY(rx_active_port) and not rx_port_discard) or rx_port_discard;
   
end;
