-------------------------------------------------------------------------------
-- Title      : PTP Support Unit
-- Project    : White Rabbit
-------------------------------------------------------------------------------
-- File       : xwrsw_psu.vhd
-- Author     : Maciej Lip0inski
-- Company    : CERN BE-CO-HT
-- Created    : 2015-03-17
-- Last update: 2015-03-17
-- Platform   : FPGA-generic
-- Standard   : VHDL'93
-------------------------------------------------------------------------------
-- Description: This is a top entity of a PTP Support Unit (PSU) 
-- 
-- This module was developed to speed up the propagation of information to the downstream
-- PTP nodes about the holdover. This is done by speaking directly to rt_subsystem
-- (SoftPLL) and doing two things:
-- 1. injecting Announce with the holdover clockClass, as soon as SoftPLL goes into holdover 
-- 2. recognizing Announce messages wiht holdover clockClass, this information is provided
--    directly to SoftPLL, the PLL then switches to backup, if available. otherwise, the 
--    injection is repeated 
-- 
-- This unit provides
-- * snooping of transmitted frames, recognition of PTP Announce frames, remembering of
--   announce frames and dumping of Announce frames with preconfigured params detected
-- * ultra-fast transmission of Announce messages with configured ClockClass
--   to configured ports (PTP Masters) by injecting a remebered Announce
-- * snooping of received frames, recognition of Announce messages with configured 
--   ClockClass and other characteristics and providing informaiton about the recognized
--   Announces
-- * interface with rt_subsystem, the communication includes
--   -> rt_subsystem to PSU
--      1. information about holdover
--      2. informaiton about current active port
--      3. command to clear the high bit about detection of preconfigured Announce message
--   -> PSU to rt_subsystem
--      1. information that a preconfigured Announce was detected

-- Transmission of Announce messages:
-- - The module snoops all transmitted frames and recognizes PTP announce messages,
--   it is possible to recognize Announce send over 
--   1) raw Ethernet frames (802.1) with and without VLAN TAG
--   2) IPv4-UDP
-- - it remembers the frame in the RAM (assuming that the only difference
--   between Annonce messages sent to different port are:
--   1) sequence number
--   2) port number
--   the above are stored per-port in RAM, the rest of the frame is stored 
--   for all frames. the RAM memory is devided into 3 parts:
--   * bank 1
--   * bank 2
--   * per-port info
--   Always, one bank is the one being read, the second is the one being written.
--   When the frame is validated (rendered OK), two things happen:
--   1. banks are swapped
--   2. the sequence ID is written to the "per-port info" addres
-- - the transmitted Announce message can be configured to be dumped if some
--   of the following is wrong (this is evaluated only if in Holdover):
--   1. the message is duplicate, so the sequence number is the same as of the
--      previous Anounce on that port
--   2. sequence number is wrong, i.e.different than: current = previous+1
--   3. sourcePortID does not match the previous Announce
--   4. clock class is not equal to the holdover ClockClass, configured
--
--
-- Reception of Announce messaeges
-- - The module snoops all received frames and recognizes PTP announce messages,
--   it is possible to recognize Announce send over 
--   1) raw Ethernet frames (802.1) with and without VLAN TAG
--   2) IPv4-UDP
-- - [not implemented but prepared for] the sourcePortID is remembered
-- - preconfigured announce messages are recognized, in particular the module looks for
--   Announce messages with configured ClockClass (the same config as transmission). 
--   additionally, it can check whether the SourcePort of the announce message with 
--   configured ClockClass has the same sourcePort as the previous one [if support implemented].
--   The sequence number can be checked as well.
--
-- NOTE: RAM is shared between psu_anounce_snooper and 
-------------------------------------------------------------------------------

-- Copyright (c) 2015 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
--
-------------------------------------------------------------------------------
-- FIXME:
-------------------------------------------------------------------------------
-- Revisions  :
-- Date        Version  Author          Description
-- 2015-03-17  1.0      mlipinsk	    Created
-------------------------------------------------------------------------------

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

library work;

use work.wr_fabric_pkg.all;
use work.genram_pkg.all;
use work.wishbone_pkg.all;
use work.psu_wbgen2_pkg.all;
use work.psu_pkg.all;

entity xwrsw_psu is
  generic(
    g_port_number   : integer := 18;
    g_port_mask_bits: integer := 32;
    g_interface_mode     : t_wishbone_interface_mode      := PIPELINED;
    g_address_granularity: t_wishbone_address_granularity := BYTE);
  port (
    clk_sys_i : in std_logic;
    rst_n_i   : in std_logic;

    -- interface with NIC: tx path
    tx_snk_i : in  t_wrf_sink_in;
    tx_snk_o : out t_wrf_sink_out;
    tx_rtu_dst_port_mask_i :  in std_logic_vector(g_port_mask_bits-1 downto 0);
    tx_rtu_prio_i          :  in std_logic_vector(2 downto 0);
    tx_rtu_drop_i          :  in std_logic;
    tx_rtu_rsp_valid_i     :  in std_logic;
    tx_rtu_rsp_ack_o       : out std_logic;

    -- interface with NIC: rx path
    rx_src_i : in  t_wrf_source_in;
    rx_src_o : out t_wrf_source_out;

    -- interface with RTU/SWcore: tx path
    tx_src_i : in  t_wrf_source_in;
    tx_src_o : out t_wrf_source_out;
    tx_rtu_dst_port_mask_o : out std_logic_vector(g_port_mask_bits-1 downto 0);
    tx_rtu_prio_o          : out std_logic_vector(2 downto 0);
    tx_rtu_drop_o          : out std_logic;
    tx_rtu_rsp_valid_o     : out std_logic;
    tx_rtu_rsp_ack_i       : in  std_logic;

    -- interface with SWcore: rx path
    rx_snk_i : in  t_wrf_sink_in;
    rx_snk_o : out t_wrf_sink_out;

    -- communciation with rt_subsystem
    selected_ref_clk_i  : in  std_logic_vector(g_port_number-1 downto 0); 
    holdover_on_i       : in  std_logic;
    rx_holdover_msg_o   : out  std_logic;
    rx_holdover_clr_i   : in std_logic;
    -- config via WB
    wb_i                : in  t_wishbone_slave_in;
    wb_o                : out t_wishbone_slave_out

    );

end xwrsw_psu;

architecture behavioral of xwrsw_psu is

    signal wb_in                  : t_wishbone_slave_in;
    signal wb_out                 : t_wishbone_slave_out;
    signal s_regs_towb            : t_psu_in_registers;
    signal s_regs_fromwb          : t_psu_out_registers;

    signal tx_detected_mask       : std_logic_vector(g_port_number-1 downto 0);
    signal rx_detected_mask       : std_logic_vector(g_port_number-1 downto 0);
    signal tx_detected_announce   : std_logic;
    signal rx_detected_announce   : std_logic;

    signal tx_snoop_ports_mask       : std_logic_vector(g_port_number-1 downto 0);
    signal rx_snoop_ports_mask       : std_logic_vector(g_port_number-1 downto 0);

    signal tx_wr_ram_addr         : std_logic_vector( 9 downto 0);
    signal tx_wr_ram_data         : std_logic_vector(65 downto 0);
    signal tx_wr_ram_ena          : std_logic;
    signal tx_rd_ram_addr         : std_logic_vector( 9 downto 0);
    signal tx_rd_ram_data         : std_logic_vector(65 downto 0);
    signal tx_snoop_ram_addr      : std_logic_vector( 9 downto 0);
    signal tx_snoop_ram_data      : std_logic_vector(65 downto 0);
    signal tx_snoop_ram_ena       : std_logic;
    signal tx_rd_ram_sel          : std_logic;

    signal rx_wr_ram_addr         : std_logic_vector( 9 downto 0);
    signal rx_wr_ram_data         : std_logic_vector(65 downto 0);
    signal rx_wr_ram_ena          : std_logic;
    signal rx_rd_ram_addr         : std_logic_vector( 9 downto 0);
    signal rx_rd_ram_data         : std_logic_vector(65 downto 0);

    signal tx_src_in              : t_wrf_source_in;
    signal tx_src_out             : t_wrf_source_out;
    
    signal inject_req             : std_logic;
    signal inject_ready           : std_logic;
    signal inject_port_mask       : std_logic_vector(g_port_number-1 downto 0);

    -- notification about holdover from external source (i.e. rt_subsystem)
    signal holdover_on_ext        : std_logic;
    signal holdover_on_int        : std_logic;

    -- internal holdover info,i.e. it is HIGH only after acknowledging the holdover by u_tx_ctrl
    signal hodlover_on_int        : std_logic;
    signal tx_rtu_dst_port_mask   : std_logic_vector(g_port_number-1 downto 0);

    signal tx_snoop_config        : t_snooper_config;
    signal rx_snoop_config        : t_snooper_config;

    signal rx_holdover_msg        : std_logic;
begin

  U_TX_CTRL: psu_tx_ctrl
    generic map(
      g_port_number         => g_port_number)
    port map(
      clk_sys_i             => clk_sys_i,
      rst_n_i               => rst_n_i,
      inject_req_o          => inject_req,
      inject_ready_i        => inject_ready,
      inject_port_mask_o    => inject_port_mask,
      tx_port_mask_i        => tx_snoop_config.snoop_ports_mask(g_port_number-1 downto 0),
      tx_ann_detect_mask_i  => tx_detected_mask,
      holdover_on_i         => holdover_on_ext,
      holdover_on_o         => holdover_on_int
    );

  holdover_on_ext <= '1' when( s_regs_fromwb.ptd_dbg_holdover_on_o = '1' or holdover_on_i ='1') else 
                 '0' ;
  s_regs_towb.psr_holdover_on_i <= holdover_on_ext;
  

  U_TX_SNOOPER: psu_announce_snooper
    generic map(
      g_port_number         => g_port_number,
      g_snoop_mode          => TX_SEQ_ID_MODE)
    port map(
      clk_sys_i             => clk_sys_i,
      rst_n_i               => rst_n_i,
      snk_i                 => tx_src_out,
      snk_o                 => tx_src_in,

      src_i                 => tx_src_i,
      src_o                 => tx_src_o,

      rtu_dst_port_mask_i   => tx_rtu_dst_port_mask,
      holdover_on_i         => holdover_on_int,

      detected_announce_o   => tx_detected_announce,
      detected_port_mask_o  => tx_detected_mask,

      wr_ram_ena_o          => tx_wr_ram_ena,
      wr_ram_data_o         => tx_wr_ram_data,
      wr_ram_addr_o         => tx_wr_ram_addr,

      rd_ram_data_i         => tx_snoop_ram_data,
      rd_ram_ena_o          => tx_snoop_ram_ena,
      rd_ram_addr_o         => tx_snoop_ram_addr,
      config_i              => tx_snoop_config);

  tx_snoop_config.seqID_duplicate_det     <= s_regs_fromwb.ptcr_seqid_dup_drop_o;
  tx_snoop_config.seqID_wrong_det         <= s_regs_fromwb.ptcr_seqid_wrg_drop_o;
  tx_snoop_config.clkCl_mismatch_det      <= s_regs_fromwb.ptcr_clkcl_wrg_drop_o;
  tx_snoop_config.prtID_mismatch_det      <= s_regs_fromwb.ptcr_prtid_wrg_drop_o;
  tx_snoop_config.holdover_clk_class      <= s_regs_fromwb.pcr_holdover_clk_class_o;
  tx_snoop_config.snoop_ports_mask        <= s_regs_fromwb.txpm_port_mask_o;
                                         

  U_TX_INJECTOR: psu_packet_injection
    generic map (
      g_port_number        => g_port_number
    )
    port map(
      clk_sys_i             => clk_sys_i,
      rst_n_i               => rst_n_i,
      src_i                 => tx_src_in,
      src_o                 => tx_src_out,
      snk_i                 => tx_snk_i,
      snk_o                 => tx_snk_o,

      rtu_dst_port_mask_o   => tx_rtu_dst_port_mask,
      rtu_prio_o            => tx_rtu_prio_o,
      rtu_drop_o            => tx_rtu_drop_o,
      rtu_rsp_valid_o       => tx_rtu_rsp_valid_o,
      rtu_rsp_ack_i         => tx_rtu_rsp_ack_i,

      rtu_dst_port_mask_i   => tx_rtu_dst_port_mask_i(g_port_number-1 downto 0),
      rtu_prio_i            => tx_rtu_prio_i,
      rtu_drop_i            => tx_rtu_drop_i,
      rtu_rsp_valid_i       => tx_rtu_rsp_valid_i,
      rtu_rsp_ack_o         => tx_rtu_rsp_ack_o,

      inject_req_i          => inject_req,
      inject_ready_o        => inject_ready,
      inject_clockClass_i   => s_regs_fromwb.pcr_holdover_clk_class_o,
      inject_port_mask_i    => inject_port_mask,
      inject_pck_prio_i     => s_regs_fromwb.pcr_inj_prio_o,

      rd_ram_data_i         => tx_rd_ram_data);

      tx_rtu_dst_port_mask_o(g_port_number-1 downto 0)                <= tx_rtu_dst_port_mask;
      
      tx_rtu_dst_port_mask_o(g_port_mask_bits-1 downto g_port_number) <= 
      tx_rtu_dst_port_mask_i(g_port_mask_bits-1 downto g_port_number);

  U_TX_RAM : generic_dpram
    generic map (
      g_data_width          => 66,
      g_size                => 1024,
      g_dual_clock          => false)
    port map (
      rst_n_i               => rst_n_i,
      clka_i                => clk_sys_i,
      clkb_i                => '0',
      wea_i                 => '0',
      aa_i                  => tx_rd_ram_addr,
      qa_o                  => tx_rd_ram_data,
      web_i                 => tx_wr_ram_ena,
      ab_i                  => tx_wr_ram_addr,
      db_i                  => tx_wr_ram_data);

  tx_rd_ram_addr                     <= tx_snoop_ram_addr when(tx_snoop_ram_ena ='1') else 
                                        s_regs_fromwb.ptd_tx_ram_rd_adr_o;
  tx_snoop_ram_data                  <= tx_rd_ram_data;
  s_regs_towb.ptd_tx_ram_rd_dat_i    <= tx_rd_ram_data(17 downto 0);
  s_regs_towb.ptd_tx_ram_dat_valid_i <= not tx_snoop_ram_ena;


  U_RX_SNOOPER: psu_announce_snooper
    generic map(
      g_port_number         => g_port_number,
      g_snoop_mode          => RX_CLOCK_CLASS_MODE)
    port map(
      clk_sys_i             => clk_sys_i,
      rst_n_i               => rst_n_i,

      snk_i                 => rx_snk_i,
      snk_o                 => rx_snk_o,

      src_i                 => rx_src_i,
      src_o                 => rx_src_o,

      rtu_dst_port_mask_i   => (others => '0'),
      holdover_on_i         => holdover_on_int, 
      detected_announce_o   => rx_detected_announce,
      detected_port_mask_o  => rx_detected_mask,

      wr_ram_ena_o          => rx_wr_ram_ena,
      wr_ram_data_o         => rx_wr_ram_data,
      wr_ram_addr_o         => rx_wr_ram_addr,

      rd_ram_data_i         => (others =>'0'),
      rd_ram_ena_o          => open,
      rd_ram_addr_o         => open,
      
      config_i              => rx_snoop_config
      );

  rx_snoop_config.seqID_duplicate_det <= s_regs_fromwb.prcr_seqid_dup_det_o;
  rx_snoop_config.seqID_wrong_det     <= s_regs_fromwb.prcr_seqid_wrg_det_o;
  rx_snoop_config.clkCl_mismatch_det  <= '1';
  rx_snoop_config.prtID_mismatch_det  <= s_regs_fromwb.prcr_prtid_wrg_det_o;
  rx_snoop_config.holdover_clk_class  <= s_regs_fromwb.pcr_holdover_clk_class_o;
  rx_snoop_config.snoop_ports_mask    <= s_regs_fromwb.rxpm_port_mask_o;


  U_RX_RAM : generic_dpram
    generic map (
      g_data_width          => 66,
      g_size                => 1024,
      g_dual_clock          => false)
    port map (
      rst_n_i               => rst_n_i,
      clka_i                => clk_sys_i,
      clkb_i                => '0',
      wea_i                 => '0',
      aa_i                  => rx_rd_ram_addr,
      qa_o                  => rx_rd_ram_data,
      web_i                 => rx_wr_ram_ena,
      ab_i                  => rx_wr_ram_addr,
      db_i                  => rx_wr_ram_data);

  U_WB_ADAPTER : wb_slave_adapter
    generic map (
      g_master_use_struct  => true,
      g_master_mode        => CLASSIC,
      g_master_granularity => WORD,
      g_slave_use_struct   => true,
      g_slave_mode         => g_interface_mode,
      g_slave_granularity  => g_address_granularity)
    port map (
      clk_sys_i => clk_sys_i,
      rst_n_i   => rst_n_i,
      slave_i   => wb_i,
      slave_o   => wb_o,
      master_i  => wb_out,
      master_o  => wb_in);

  U_WB_CTRL:  psu_wishbone_controller
    port map(
      rst_n_i          => rst_n_i,
      clk_sys_i        => clk_sys_i,
      wb_adr_i         => wb_in.adr(2 downto 0),
      wb_dat_i         => wb_in.dat,
      wb_dat_o         => wb_out.dat,
      wb_cyc_i         => wb_in.cyc,
      wb_sel_i         => wb_in.sel,
      wb_stb_i         => wb_in.stb,
      wb_we_i          => wb_in.we,
      wb_ack_o         => wb_out.ack,
      wb_stall_o       => wb_out.stall,
      regs_i           => s_regs_towb,
      regs_o           => s_regs_fromwb
  );

  p_rx_ctr : process(clk_sys_i)
  begin
    if rising_edge(clk_sys_i) then
      if rst_n_i = '0' then
        rx_holdover_msg <= '0';
      else
        if(rx_holdover_clr_i = '1' or s_regs_fromwb.pcr_psu_clr_tx_msg_o='1') then
          rx_holdover_msg <= '0';
        elsif(rx_detected_announce = '1') then
          rx_holdover_msg <= '1';
        end if;
      end if;
    end if;
  end process;
  rx_holdover_msg_o                  <= rx_holdover_msg;
  s_regs_towb.psr_hd_msg_rx_i        <= rx_holdover_msg;
  s_regs_towb.psr_active_ref_spll_i(g_port_number-1 downto 0)  <= selected_ref_clk_i;
  s_regs_towb.psr_active_ref_spll_i(23 downto g_port_number)   <= (others =>'0');
  s_regs_towb.psr_hd_on_spll_i                                 <= holdover_on_i;
end behavioral;

