//
// Copyright 2021 Ettus Research, A National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: x4xx_qsfp_wrapper
//
// Description:
//
//   Consolidates the logic necessary for a QSFP port, depending on the
//   requested protocol.
//
// Parameters:
//
//     PROTOCOL       : Indicates the protocol to use for each of the 4 QSFP
//                      lanes. See x4xx_mgt_types.vh for possible values.
//     CPU_W          : Width of CPU interface
//     CHDR_W         : CHDR bus width
//     BYTE_MTU       : Transport MTU in bytes
//     PORTNUM        : Port number to distinguish multiple QSFP ports
//     RFNOC_PROTOVER : RFNoC protocol version for IPv4 interface
//

`include "./x4xx_mgt_types.vh"


module x4xx_qsfp_wrapper #(
  // Must be a value defined in x4xx_mgt_types.vh
  parameter integer PROTOCOL [3:0] = {`MGT_Disabled,
                                      `MGT_Disabled,
                                      `MGT_Disabled,
                                      `MGT_Disabled},
  parameter         CPU_W          = 64,
  parameter         CHDR_W         = 64,
  parameter         BYTE_MTU       = $clog2(8*1024),
  parameter  [ 7:0] PORTNUM        = 8'd0,
  parameter  [15:0] RFNOC_PROTOVER = {8'd1, 8'd0}
)(
  // Resets
  input logic areset,
  input logic bus_rst,
  input logic clk40_rst,

  // Clocks
  input logic refclk_p,
  input logic refclk_n,
  input logic clk100,
  input logic bus_clk,

  // AXI-Lite register access
  AxiLiteIf.slave s_axi,

  // Ethernet DMA AXI to PS memory
  AxiIf.master axi_hp,

  // MGT high-speed IO
  output logic [3:0] tx_p,
  output logic [3:0] tx_n,
  input  logic [3:0] rx_p,
  input  logic [3:0] rx_n,

  // CHDR router interface
  AxiStreamIf.master e2v [4],
  AxiStreamIf.slave  v2e [4],

  // ETH DMA IRQs
  output logic [3:0] eth_rx_irq,
  output logic [3:0] eth_tx_irq,

  // Misc.
  output logic        rx_rec_clk_out,
  input  logic [15:0] device_id,

  output logic [3:0][31:0] port_info,

  output logic [3:0] link_up,
  output logic [3:0] activity
);

  import PkgAxiLite::*;

  localparam REG_BASE_SFP_IO      = 14'h0;
  localparam REG_BASE_ETH_SWITCH  = 14'h1000;
  localparam CPU_USER_W           = $clog2(CPU_W/8)+1;
  localparam CHDR_USER_W          = $clog2(CHDR_W/8);
  localparam REG_DWIDTH           = 32;
  localparam REG_AWIDTH_MISC      = 14;
  localparam logic [3:0] DISABLED = { PROTOCOL[3] == `MGT_Disabled,
                                      PROTOCOL[2] == `MGT_Disabled,
                                      PROTOCOL[1] == `MGT_Disabled,
                                      PROTOCOL[0] == `MGT_Disabled };
  localparam logic [3:0] IS10GBE  = { PROTOCOL[3] == `MGT_10GbE,
                                      PROTOCOL[2] == `MGT_10GbE,
                                      PROTOCOL[1] == `MGT_10GbE,
                                      PROTOCOL[0] == `MGT_10GbE };
  localparam logic [3:0] IS100GBE = { 3'b0,PROTOCOL[0] == `MGT_100GbE };
  localparam logic [3:0] ISAURORA = { 3'b0,PROTOCOL[0] == `MGT_Aurora };


  `include "../../lib/axi4_sv/axi.vh"
  `include "../../lib/axi4lite_sv/axi_lite.vh"


  //---------------------------------------------------------------------------
  // Interfaces
  //---------------------------------------------------------------------------

  // AXI-Lite interface
  AxiLiteIf #(REG_DWIDTH,40)
    m_axi_dma[3:0] (s_axi.clk, s_axi.rst);

  // 0x0000-0x3FFF - Bottom goes to XGE top goes to UIO
  AxiLiteIf #(REG_DWIDTH,40)
    m_axi_misc[3:0] (s_axi.clk, s_axi.rst);
  AxiLiteIf_v #(REG_DWIDTH,REG_AWIDTH_MISC)
    m_axi_misc_v[3:0] (s_axi.clk, s_axi.rst);

  // 0x4000-0x5FFF - Goes to 100G Mac
  AxiLiteIf #(REG_DWIDTH,40)
    m_axi_mac[3:0] (s_axi.clk, s_axi.rst);

  // AXI (Full) for DMA back to CPU memory
  AxiIf #(128,49)
    axi_hp_dma[3:0] (s_axi.clk, s_axi.rst);


  //---------------------------------------------------------------------------
  // AXI Interconnect
  //---------------------------------------------------------------------------
  //
  // Break the incoming register request into 12 different spaces:
  //
  //   0x0_0000 - dma0
  //   0x0_8000 - misc0 - +0x0000 NIXGE
  //                      +0x2000 UIO
  //   0x0_C000 - mac0
  //
  //   0x1_0000 - dma1
  //   0x1_8000 - misc1 - +0x0000 NIXGE
  //                      +0x2000 UIO
  //   0x1_C000 - mac1
  //
  //   0x2_0000 - dma2
  //   0x2_8000 - misc2 - +0x0000 NIXGE
  //                      +0x2000 UIO
  //   0x2_C000 - mac2
  //
  //   0x3_0000 - dma3
  //   0x3_8000 - misc3 - +0x0000 NIXGE
  //                      +0x2000 UIO
  //  0x3_C000 - mac3
  //
  //---------------------------------------------------------------------------

  axi_interconnect_eth axi_interconnect_eth_i (
    .s_axi_eth  (s_axi),
    .m_axi_dma  (m_axi_dma),
    .m_axi_misc (m_axi_misc),
    .m_axi_mac  (m_axi_mac)
  );


  //---------------------------------------------------------------------------
  // Map DMA Engine Masters to CPU Memory Port
  //---------------------------------------------------------------------------

  // Everything Disabled
  if (DISABLED == 4'b1111) begin : axi_hp_noconnect
    always_comb begin
      axi_hp.drive_read_idle();
      axi_hp.drive_aw_idle();
      axi_hp.drive_w_idle();
      axi_hp.bready = 1'b0;
      axi_hp.rready = 1'b0;
    end
  end : axi_hp_noconnect else
  // Only port0 Enabled
  if (DISABLED == 4'b1110) begin : axi_hp_directconnect
    always_comb begin
      `AXI4_ASSIGN(axi_hp,axi_hp_dma[0])
      axi_hp_dma[1].wready  = 1'b0;
      axi_hp_dma[2].wready  = 1'b0;
      axi_hp_dma[3].wready  = 1'b0;
      axi_hp_dma[1].awready = 1'b0;
      axi_hp_dma[2].awready = 1'b0;
      axi_hp_dma[3].awready = 1'b0;
      axi_hp_dma[1].arready = 1'b0;
      axi_hp_dma[2].arready = 1'b0;
      axi_hp_dma[3].arready = 1'b0;
      axi_hp_dma[1].bvalid  = 1'b0;
      axi_hp_dma[2].bvalid  = 1'b0;
      axi_hp_dma[3].bvalid  = 1'b0;
      axi_hp_dma[1].rvalid  = 1'b0;
      axi_hp_dma[2].rvalid  = 1'b0;
      axi_hp_dma[3].rvalid  = 1'b0;
    end
  // All other cases
  end : axi_hp_directconnect else begin : axi_hp_interconnect
    axi_interconnect_dma axi_interconnect_dma_i (
      .m_axi_hp     (axi_hp),
      .s_axi_hp_dma (axi_hp_dma)
    );
  end : axi_hp_interconnect


  //---------------------------------------------------------------------------
  // 10 Gigabit Ethernet
  //---------------------------------------------------------------------------

  logic       refclk;   // 156 Mhz Ref 10 GbE
  logic [0:0] qpll0_reset;
  logic [3:0] qpll0_reset_i;
  logic [0:0] qpll0_lock;
  logic [0:0] qpll0_clk;
  logic [0:0] qpll0_refclk;
  logic [0:0] qpll1_reset;
  logic [3:0] qpll1_reset_i;
  logic [0:0] qpll1_lock;
  logic [0:0] qpll1_clk;
  logic [0:0] qpll1_refclk;

  assign qpll0_reset[0] = qpll0_reset_i[0] || qpll0_reset_i[1] ||
                          qpll0_reset_i[2] || qpll0_reset_i[3];
  assign qpll1_reset[0] = qpll1_reset_i[0] || qpll1_reset_i[1] ||
                          qpll1_reset_i[2] || qpll1_reset_i[3];

  // The following logic is shared amongst potentially 4X10GBE interfaces
  if (IS10GBE != 0) begin : xge_common

    // Clocking signals for MGTs
    IBUFDS_GTE4 ibufds_gte4_refclk (
      .I     (refclk_p),
      .IB    (refclk_n),
      .CEB   (1'b0),
      .O     (refclk),
      .ODIV2 ()
    );

    xge_pcs_pma_common_wrapper xge_pcs_pma_common_wrapper_i (
      .refclk         (refclk),
      .qpll0reset     (qpll0_reset),
      .qpll0lock      (qpll0_lock),
      .qpll0outclk    (qpll0_clk),
      .qpll0outrefclk (qpll0_refclk),
      .qpll1reset     (qpll1_reset),
      .qpll1lock      (qpll1_lock),
      .qpll1outclk    (qpll1_clk),
      .qpll1outrefclk (qpll1_refclk)
    );

  end : xge_common


  //---------------------------------------------------------------------------
  // Generate QSFP Lanes
  //---------------------------------------------------------------------------

  logic [3:0] rx_rec_clk_out_i;
  assign rx_rec_clk_out = rx_rec_clk_out_i[0];

  generate
    genvar lane;
    begin : mgt_lanes
      // Repeat logic for up to 4 QSFP lanes
      for(lane = 0; lane < 4; lane++) begin : lane_loop

        //---------------------------------------
        // AXI-Lite to RegPort Bridge
        //---------------------------------------

        // Map to 0x4000 space
        always_comb begin
          `AXI4LITE_ASSIGN(m_axi_misc_v[lane],m_axi_misc[lane])
           m_axi_misc_v[lane].araddr = 0;
           m_axi_misc_v[lane].araddr[13:0] = m_axi_misc[lane].araddr[13:0];
           m_axi_misc_v[lane].awaddr = 0;
           m_axi_misc_v[lane].awaddr[13:0] = m_axi_misc[lane].awaddr[13:0];
        end

        // AXI4-Lite to RegPort (PS to PL Register Access)
        // NOTE: We always have a register interface even if the block is
        //       unused, so that the driver can query the status.
        typedef logic [REG_AWIDTH_MISC-1:0] reg_addr_t;
        typedef logic [REG_DWIDTH-1:0]      reg_data_t;

        logic      reg_wr_req;
        reg_addr_t reg_wr_addr;
        reg_data_t reg_wr_data;
        logic      reg_rd_req;
        reg_addr_t reg_rd_addr;
        logic      reg_rd_resp, reg_rd_resp_io, reg_rd_resp_eth_if;
        reg_data_t reg_rd_data, reg_rd_data_io, reg_rd_data_eth_if;

        axil_regport_master #(
          .DWIDTH  (REG_DWIDTH),      // Width of the AXI4-Lite data bus (must be 32 or 64)
          .AWIDTH  (REG_AWIDTH_MISC), // Width of the address bus
          .WRBASE  (0),               // Write address base
          .RDBASE  (0),               // Read address base
          .TIMEOUT (10)               // log2(timeout). Read will timeout after (2^TIMEOUT - 1) cycles
        ) axil_regport_master_i (
          // Clock and reset
          .s_axi_aclk    (m_axi_misc_v[lane].clk),
          .s_axi_aresetn (!m_axi_misc_v[lane].rst),
          `AXI4LITE_PORT_ASSIGN_NR(s_axi,m_axi_misc_v[lane])
          // Register port: Write port (domain: reg_clk)
          .reg_clk       (bus_clk),
          .reg_wr_req    (reg_wr_req),
          .reg_wr_addr   (reg_wr_addr),
          .reg_wr_data   (reg_wr_data),
          .reg_wr_keep   (/*unused*/),
          // Register port: Read port (domain: reg_clk)
          .reg_rd_req    (reg_rd_req),
          .reg_rd_addr   (reg_rd_addr),
          .reg_rd_resp   (reg_rd_resp),
          .reg_rd_data   (reg_rd_data)
        );

        // Regport Mux for response
        regport_resp_mux #(
          .WIDTH      (REG_DWIDTH),
          .NUM_SLAVES (2)
        ) regport_resp_mux_i (
          .clk(bus_clk), .reset(bus_rst),
          .sla_rd_resp({reg_rd_resp_eth_if, reg_rd_resp_io}),
          .sla_rd_data({reg_rd_data_eth_if, reg_rd_data_io}),
          .mst_rd_resp(reg_rd_resp), .mst_rd_data(reg_rd_data)
        );


        //---------------------------------------
        // MGT IO Core
        //---------------------------------------

        localparam MGT_W      = (IS100GBE) ? 512 : 64;
        localparam MGT_USER_W = $clog2(MGT_W/8)+1;

        // The Clocking for the MGT interfaces comes from the MGT Wrapper
        // depending on the bus it may change.
        logic mgt_rst, mgt_clk;
        AxiStreamIf #(.DATA_WIDTH(MGT_W),.USER_WIDTH(MGT_USER_W))
          mgt_tx(mgt_clk, mgt_rst);
        AxiStreamIf #(.DATA_WIDTH(MGT_W),.USER_WIDTH(MGT_USER_W),.TKEEP(0))
          mgt_rx(mgt_clk, mgt_rst);

        logic mgt_pause_req;
        logic [3:0] tx_p_lane;
        logic [3:0] tx_n_lane;

        if (IS10GBE[lane]) begin
          // Single lane case:
          assign tx_p[lane] = tx_p_lane[lane];
          assign tx_n[lane] = tx_n_lane[lane];
        end else if (IS100GBE[lane] || ISAURORA[lane]) begin
          // Multi lane case:
          assign tx_p = tx_p_lane;
          assign tx_n = tx_n_lane;
        end

        x4xx_mgt_io_core #(
          .PROTOCOL   (PROTOCOL[lane]),
          .REG_BASE   (REG_BASE_SFP_IO),
          .REG_DWIDTH (REG_DWIDTH),      // Width of the AXI4-Lite data bus (must be 32 or 64)
          .REG_AWIDTH (REG_AWIDTH_MISC), // Width of the address bus
          .PORTNUM    (PORTNUM),
          .LANENUM    (lane)
        ) x4xx_mgt_io_core_i (
          // Must reset all channels on quad when QSFP GTX core is reset
          .areset         (areset),

          .mgt_rst        (mgt_rst),
          .mgt_clk        (mgt_clk),

          .clk100         (clk100),

          .bus_rst        (bus_rst),
          .bus_clk        (bus_clk),

          .refclk_p       (refclk_p),
          .refclk_n       (refclk_n),
          .tx_p           (tx_p_lane),
          .tx_n           (tx_n_lane),
          .rx_p           (rx_p),
          .rx_n           (rx_n),

          // Common signals (for single lane instances)
          .qpll0_reset    (qpll0_reset_i[lane]),
          .qpll0_lock     (qpll0_lock),
          .qpll0_clk      (qpll0_clk),
          .qpll0_refclk   (qpll0_refclk),
          .qpll1_reset    (qpll1_reset_i[lane]),
          .qpll1_lock     (qpll1_lock),
          .qpll1_clk      (qpll1_clk),
          .qpll1_refclk   (qpll1_refclk),

          // RegPort
          .reg_wr_req     (reg_wr_req),
          .reg_wr_addr    (reg_wr_addr),
          .reg_wr_data    (reg_wr_data),
          .reg_rd_req     (reg_rd_req),
          .reg_rd_addr    (reg_rd_addr),
          .reg_rd_resp    (reg_rd_resp_io),
          .reg_rd_data    (reg_rd_data_io),
          // AxiLite
          .m_axi_mac      (m_axi_mac[lane]),
          // Pause
          .mgt_pause_req  (mgt_pause_req),
          // Data
          .mgt_tx         (mgt_tx),
          .mgt_rx         (mgt_rx),

          // Misc.
          .rx_rec_clk_out (rx_rec_clk_out_i[lane]),
          .port_info      (port_info[lane]),
          .link_up        (link_up[lane]),
          .activity       (activity[lane])
        );


        if (IS100GBE[lane] || IS10GBE[lane]) begin : eth_port

          //---------------------------------------
          // Ethernet IPv4 Interface for CHDR
          //---------------------------------------

          // Option to use a bigger FIFO for 100GBe.
          // This is address width so +1 doubles the size +2 quadruples it.
          localparam CHDR_FIFO_SIZE = (IS100GBE[lane]) ? BYTE_MTU+2 : BYTE_MTU;

          AxiStreamIf #(.DATA_WIDTH(CPU_W), .USER_WIDTH(CPU_USER_W), .TUSER(0))
            c2e (s_axi.clk, s_axi.rst);
          AxiStreamIf #(.DATA_WIDTH(CPU_W), .USER_WIDTH(CPU_USER_W), .TUSER(0))
            e2c (s_axi.clk, s_axi.rst);

          localparam PAUSE_EN = (IS100GBE[lane]) ? 1 : 0;

          // Ethernet interface
          // (1) routes the packet to CHDR/CPU
          // (2) implements a wrap back (eth_tx/eth_rx)
          eth_ipv4_interface #(
            .PROTOVER       (RFNOC_PROTOVER),
            .CPU_FIFO_SIZE  (BYTE_MTU),
            .CHDR_FIFO_SIZE (CHDR_FIFO_SIZE),
            .NODE_INST      (0),
            .BASE           (REG_BASE_ETH_SWITCH),
            .PREAMBLE_BYTES (0),
            .ADD_SOF        (0),
            .SYNC           (0),  // c2e/e2c don't use the same clock as eth_tx/eth_rx
            .PAUSE_EN       (PAUSE_EN),
            .ENET_W         (MGT_W),
            .CPU_W          (CPU_W),
            .CHDR_W         (CHDR_W)
          ) eth_ipv4_interface_i (
            .bus_clk          (bus_clk),
            .bus_rst          (bus_rst),
            .device_id        (device_id),
            .reg_wr_req       (reg_wr_req),
            .reg_wr_addr      (reg_wr_addr),
            .reg_wr_data      (reg_wr_data),
            .reg_rd_req       (reg_rd_req),
            .reg_rd_addr      (reg_rd_addr),
            .reg_rd_resp      (reg_rd_resp_eth_if),
            .reg_rd_data      (reg_rd_data_eth_if),
            .eth_pause_req    (mgt_pause_req),
            .eth_tx           (mgt_tx),
            .eth_rx           (mgt_rx),
            .e2v              (e2v[lane]),
            .v2e              (v2e[lane]),
            .e2c              (e2c),
            .c2e              (c2e),
            .my_udp_chdr_port (/* unused */),
            .my_ip            (/* unused */),
            .my_mac           (/* unused */)
          );

          axi_eth_dma axi_eth_dma_i (
            .c2e           (c2e),
            .e2c           (e2c),
            .s_axi_eth_dma (m_axi_dma[lane]),
            .axi_hp        (axi_hp_dma[lane]),
            .eth_tx_irq    (eth_tx_irq[lane]),
            .eth_rx_irq    (eth_rx_irq[lane])
          );

        end : eth_port else begin : not_eth

          //---------------------------------------
          // Terminate DMA for Unused Ethernet
          //---------------------------------------

          // Set unused ETH_DMA ports to default value
          always_comb begin
            m_axi_dma[lane].drive_read_resp(.resp(SLVERR),.data(0));
            m_axi_dma[lane].drive_write_resp(.resp(SLVERR));
            m_axi_dma[lane].arready = 1'b1;
            m_axi_dma[lane].awready = 1'b1;
            m_axi_dma[lane].wready  = 1'b1;

            axi_hp_dma[lane].drive_read_idle();
            axi_hp_dma[lane].drive_aw_idle();
            axi_hp_dma[lane].drive_w_idle();
            axi_hp_dma[lane].bready  = 1'b0;
            axi_hp_dma[lane].rready  = 1'b0;

            mgt_pause_req = 0'b0;

            eth_rx_irq[lane] = 1'b0;
            eth_tx_irq[lane] = 1'b0;

            reg_rd_resp_eth_if = 1'b0;
            reg_rd_data_eth_if = 'h0;
          end

          if (ISAURORA[lane]) begin : aurora_port

            //---------------------------------------
            // Aurora
            //---------------------------------------

            Aurora_not_yet_supported();

            // if MGT_W and CHDR_W mismatch figure out what to do
            always_comb begin
              e2v[lane].tdata  = mgt_rx.tdata;
              e2v[lane].tuser  = 'b0;
              e2v[lane].tkeep  = 'b1;
              e2v[lane].tlast  = mgt_rx.tlast;
              e2v[lane].tvalid = mgt_rx.tvalid;
              mgt_rx.tready    = e2v[lane].tready;

              mgt_tx.tdata     = v2e[lane].tdata;
              mgt_tx.tuser     = 'b0;
              mgt_tx.tkeep     = 'b1;
              mgt_tx.tlast     = v2e[lane].tlast;
              mgt_tx.tvalid    = v2e[lane].tvalid;
              v2e[lane].tready = mgt_tx.tready;
            end

          end else begin : inactive_port

            //---------------------------------------
            // Disabled Port
            //---------------------------------------

            always_comb begin
              e2v[lane].tdata  =  'b0;
              e2v[lane].tuser  =  'b0;
              e2v[lane].tkeep  =  'b1;
              e2v[lane].tlast  = 1'b0;
              e2v[lane].tvalid = 1'b0;
              mgt_rx.tready    = 1'b1;

              mgt_tx.tdata     =  'b0;
              mgt_tx.tuser     =  'b0;
              mgt_tx.tkeep     =  'b1;
              mgt_tx.tlast     = 1'b0;
              mgt_tx.tvalid    = 1'b0;
              v2e[lane].tready = 1'b1;
            end

          end : inactive_port
        end : not_eth
      end : lane_loop
    end : mgt_lanes
  endgenerate

endmodule
