//
// Copyright 2016-2017 Ettus Research LLC
// Copyright 2017 Ettus Research, a National Instruments Company
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// sfpp_io_core
// - mdio_master
// - One Gige phy + MAC
// - Ten Gige phy + MAC
// - Aurora phy + MAC
//
//////////////////////////////////////////////////////////////////////

module x300_sfpp_io_core #(
   parameter PROTOCOL = "10GbE",    // Must be {10GbE, 1GbE, Aurora}
   parameter PORTNUM  = 8'd0
)(
   input             areset,
   input             bus_rst,
   input             bus_rst_div2,

   input             gt_refclk,
   input             gb_refclk,
   input             misc_clk,
   input             bus_clk,
   input             bus_clk_div2,

   output            txp,
   output            txn,
   input             rxp,
   input             rxn,

   input             sfpp_rxlos,
   input             sfpp_tx_fault,
   output            sfpp_tx_disable,

   input  [63:0]     s_axis_tdata,
   input  [3:0]      s_axis_tuser,
   input             s_axis_tlast,
   input             s_axis_tvalid,
   output            s_axis_tready,

   output [63:0]     m_axis_tdata,
   output [3:0]      m_axis_tuser,
   output            m_axis_tlast,
   output            m_axis_tvalid,
   input             m_axis_tready,

   input  [7:0]      wb_adr_i,
   input             wb_cyc_i,
   input  [31:0]     wb_dat_i,
   input             wb_stb_i,
   input             wb_we_i,
   output            wb_ack_o,
   output [31:0]     wb_dat_o,
   output            wb_int_o,

   output [15:0]     phy_status,
   output            link_up,
   output            activity
);

   wire mdc, mdio_in, mdio_out;

    // Flush logic: If the link is not up, we will flush all packets coming from
    // the device. This avoids the MAC backpressuring when the PHY is down.
    // The device will always send discovery packets to the transports during
    // initialization, and they have no way of knowing if it's safe to travel
    // down this route. c2mac == "CHDR to MAC"
    wire  [63:0]     c2mac_tdata;
    wire  [3:0]      c2mac_tuser;
    wire             c2mac_tlast;
    wire             c2mac_tvalid;
    wire             c2mac_tready;

    axis_packet_flush #(
       .WIDTH(64+4), // tdata + tuser
       .TIMEOUT_W(1), // Not using timeout
       .FLUSH_PARTIAL_PKTS(0),
       .PIPELINE("NONE")
   ) linkup_flush (
       .clk(bus_clk),
       .reset(bus_rst),
       .enable(~link_up), // enable flushing when link down
       .timeout(1'b0),
       .flushing(/* not required */),
       .done(/* not required */),
       // Input from device/crossbar
       .s_axis_tdata  ({s_axis_tuser, s_axis_tdata}),
       .s_axis_tlast  (s_axis_tlast),
       .s_axis_tvalid (s_axis_tvalid),
       .s_axis_tready (s_axis_tready),
       // Output to MAC
       .m_axis_tdata  ({c2mac_tuser, c2mac_tdata}),
       .m_axis_tlast  (c2mac_tlast),
       .m_axis_tvalid (c2mac_tvalid),
       .m_axis_tready (c2mac_tready)
   );


generate
   if (PROTOCOL == "10GbE") begin
      //-----------------------------------------------------------------
      // 10 Gigabit Ethernet
      //-----------------------------------------------------------------
      wire [63:0] xgmii_txd;
      wire [7:0]  xgmii_txc;
      wire [63:0] xgmii_rxd;
      wire [7:0]  xgmii_rxc;
      wire [7:0]  xgmii_status;
      wire        xge_phy_resetdone;

      ten_gige_phy ten_gige_phy_i
      (
         // Clocks and Reset
         .areset(areset),                 // Asynchronous reset for entire core.
         .refclk(gt_refclk),              // Transciever reference clock: 156.25MHz
         .clk156(gb_refclk),              // Globally buffered core clock: 156.25MHz
         .dclk(misc_clk),                 // Management/DRP clock: 78.125MHz
         .sim_speedup_control(1'b0),
         // GMII Interface (client MAC <=> PCS)
         .xgmii_txd(xgmii_txd),          // Transmit data from client MAC.
         .xgmii_txc(xgmii_txc),          // Transmit control signal from client MAC.
         .xgmii_rxd(xgmii_rxd),          // Received Data to client MAC.
         .xgmii_rxc(xgmii_rxc),          // Received control signal to client MAC.
         // Tranceiver Interface
         .txp(txp),                       // Differential +ve of serial transmission from PMA to PMD.
         .txn(txn),                       // Differential -ve of serial transmission from PMA to PMD.
         .rxp(rxp),                       // Differential +ve for serial reception from PMD to PMA.
         .rxn(rxn),                       // Differential -ve for serial reception from PMD to PMA.
         // Management: MDIO Interface
         .mdc(mdc),                      // Management Data Clock
         .mdio_in(mdio_in),              // Management Data In
         .mdio_out(mdio_out),            // Management Data Out
         .mdio_tri(),                     // Management Data Tristate
         .prtad(5'd4),                    // MDIO address is 4
         // General IO's
         .core_status(xgmii_status),     // Core status
         .resetdone(xge_phy_resetdone),
         .signal_detect(~sfpp_rxlos),     // Input from PMD to indicate presence of optical input. (Undocumented, but it seems Xilinx expect this to be inverted.)
         .tx_fault(sfpp_tx_fault),
         .tx_disable(sfpp_tx_disable)
      );

      xge_mac_wrapper #(
        .PORTNUM(PORTNUM),
        .WISHBONE(1)
      ) xge_mac_wrapper_i (
         // XGMII
         .xgmii_clk(gb_refclk),
         .xgmii_txd(xgmii_txd),
         .xgmii_txc(xgmii_txc),
         .xgmii_rxd(xgmii_rxd),
         .xgmii_rxc(xgmii_rxc),
         // MDIO
         .mdc(mdc),
         .mdio_in(mdio_in),
         .mdio_out(mdio_out),
         // Wishbone I/F
         .wb_clk_i(bus_clk_div2),
         .wb_rst_i(bus_rst_div2),
         .wb_adr_i(wb_adr_i),
         .wb_cyc_i(wb_cyc_i),
         .wb_dat_i(wb_dat_i),
         .wb_stb_i(wb_stb_i),
         .wb_we_i(wb_we_i),
         .wb_ack_o(wb_ack_o),
         .wb_dat_o(wb_dat_o),
         .wb_int_o(wb_int_o),
         // Client FIFO Interfaces
         .sys_clk(bus_clk),
         .sys_rst(bus_rst),
         .rx_tdata(m_axis_tdata),
         .rx_tuser(m_axis_tuser),
         .rx_tlast(m_axis_tlast),
         .rx_tvalid(m_axis_tvalid),
         .rx_tready(m_axis_tready),
         .tx_tdata(c2mac_tdata),
         .tx_tuser(c2mac_tuser),                // Bit[3] (error) is ignored for now.
         .tx_tlast(c2mac_tlast),
         .tx_tvalid(c2mac_tvalid),
         .tx_tready(c2mac_tready),
         // Other
         .phy_ready(xge_phy_resetdone)
      );

      assign phy_status  = {8'h00, xgmii_status};

      // Use the PCS Block Lock signal to drive the link_up LED on the FP.
      // For further details, see Xilinx' PG068, Table 2-11 (core_status[0] signal).
      synchronizer #(.INITIAL_VAL(1'b0)) link_up_sync (
         .clk(bus_clk), .rst(1'b0 /* no reset */), .in(xgmii_status[0]), .out(link_up));


   end else if (PROTOCOL == "1GbE") begin

      //-----------------------------------------------------------------
      // 1 Gigabit Ethernet
      //-----------------------------------------------------------------
      wire [7:0]  gmii_txd, gmii_rxd;
      wire        gmii_tx_en, gmii_tx_er, gmii_rx_dv, gmii_rx_er;
      wire        gmii_clk;

      assign sfpp_tx_disable = 1'b0; // Always on.

      one_gige_phy one_gige_phy_i
      (
         .reset(areset),                  // Asynchronous reset for entire core.
         .independent_clock(bus_clk),
         // Tranceiver Interface
         .gtrefclk(gt_refclk),            // Reference clock for MGT: 125MHz, very high quality.
         .gtrefclk_bufg(gb_refclk),       // Reference clock routed through a BUFG
         .txp(txp),                       // Differential +ve of serial transmission from PMA to PMD.
         .txn(txn),                       // Differential -ve of serial transmission from PMA to PMD.
         .rxp(rxp),                       // Differential +ve for serial reception from PMD to PMA.
         .rxn(rxn),                       // Differential -ve for serial reception from PMD to PMA.
         // GMII Interface (client MAC <=> PCS)
         .gmii_clk(gmii_clk),            // Clock to client MAC.
         .gmii_txd(gmii_txd),            // Transmit data from client MAC.
         .gmii_tx_en(gmii_tx_en),        // Transmit control signal from client MAC.
         .gmii_tx_er(gmii_tx_er),        // Transmit control signal from client MAC.
         .gmii_rxd(gmii_rxd),            // Received Data to client MAC.
         .gmii_rx_dv(gmii_rx_dv),        // Received control signal to client MAC.
         .gmii_rx_er(gmii_rx_er),        // Received control signal to client MAC.
         // Management: MDIO Interface
         .mdc(mdc),                      // Management Data Clock
         .mdio_i(mdio_in),               // Management Data In
         .mdio_o(mdio_out),              // Management Data Out
         .mdio_t(),                       // Management Data Tristate
         .configuration_vector(5'd0),     // Alternative to MDIO interface.
         .configuration_valid(1'b1),      // Validation signal for Config vector (MUST be 1 for proper functionality...undocumented)
         // General IO's
         .status_vector(phy_status),    // Core status.
         .signal_detect(1'b1 /*Optical module not supported*/) // Input from PMD to indicate presence of optical input.
      );

      simple_gemac_wrapper #(.RX_FLOW_CTRL(0), .PORTNUM(PORTNUM)) simple_gemac_wrapper_i
      (
         .clk125(gmii_clk),
         .reset(areset),

         .GMII_GTX_CLK(),
         .GMII_TX_EN(gmii_tx_en),
         .GMII_TX_ER(gmii_tx_er),
         .GMII_TXD(gmii_txd),
         .GMII_RX_CLK(gmii_clk),
         .GMII_RX_DV(gmii_rx_dv),
         .GMII_RX_ER(gmii_rx_er),
         .GMII_RXD(gmii_rxd),

         .sys_clk(bus_clk),
         .rx_tdata(m_axis_tdata),
         .rx_tuser(m_axis_tuser),
         .rx_tlast(m_axis_tlast),
         .rx_tvalid(m_axis_tvalid),
         .rx_tready(m_axis_tready),
         .tx_tdata(c2mac_tdata),
         .tx_tuser(c2mac_tuser),
         .tx_tlast(c2mac_tlast),
         .tx_tvalid(c2mac_tvalid),
         .tx_tready(c2mac_tready),
         // MDIO
         .mdc(mdc),
         .mdio_in(mdio_in),
         .mdio_out(mdio_out),
         .mdio_tri(),
         // Wishbone I/F
         .wb_clk_i(bus_clk_div2),
         .wb_rst_i(bus_rst_div2),
         .wb_adr_i(wb_adr_i),
         .wb_cyc_i(wb_cyc_i),
         .wb_dat_i(wb_dat_o),
         .wb_stb_i(wb_stb_i),
         .wb_we_i(wb_we_i),
         .wb_ack_o(wb_ack_o),
         .wb_dat_o(wb_dat_i),
         .wb_int_o(wb_int_o),
         // Debug
         .debug_tx(), .debug_rx()
      );

      // Use the Link Status signal to drive the link_up LED on the FP.
      // For further details, see Xilinx' PG047, Table 2-76 (status_vector[0]).
      synchronizer #(.INITIAL_VAL(1'b0)) link_up_sync (
         .clk(bus_clk), .rst(1'b0 /* no reset */), .in(phy_status[0]), .out(link_up));


   end else if (PROTOCOL == "Aurora") begin

      //-----------------------------------------------------------------
      // Aurora
      //-----------------------------------------------------------------
      wire        au_user_clk, au_user_rst, phy_areset;
      wire [63:0] i_tdata, o_tdata;
      wire        i_tvalid, i_tready, o_tvalid;
      wire        channel_up, hard_err, soft_err;
      wire        mac_clear;

      assign sfpp_tx_disable = 1'b0; // Always on.

      aurora_phy_x1 aurora_phy_i (
         // Resets
         .areset(areset | phy_areset),
         // Clocks
         .refclk(gt_refclk),
         .init_clk(misc_clk),
         .user_clk(au_user_clk),
         .user_rst(au_user_rst),
         // GTX Serial I/O
         .tx_p(txp),
         .tx_n(txn),
         .rx_p(rxp),
         .rx_n(rxn),
         // AXI4-Stream TX Interface
         .s_axis_tdata(i_tdata),
         .s_axis_tvalid(i_tvalid),
         .s_axis_tready(i_tready),
         // AXI4-Stream RX Interface
         .m_axis_tdata(o_tdata),
         .m_axis_tvalid(o_tvalid),
         // AXI4-Lite Config Interface: TODO: Hook up to WB->AXI4Lite converter
         .s_axi_awaddr(32'h0),
         .s_axi_araddr(32'h0),
         .s_axi_awvalid(1'b0),
         .s_axi_awready(),
         .s_axi_wdata(32'h0),
         .s_axi_wvalid(1'b0),
         .s_axi_wstrb(1'b0),
         .s_axi_wready(),
         .s_axi_bvalid(),
         .s_axi_bresp(),
         .s_axi_bready(1'b1),
         .s_axi_arready(),
         .s_axi_arvalid(1'b0),
         .s_axi_rdata(),
         .s_axi_rvalid(),
         .s_axi_rresp(),
         .s_axi_rready(1'b1),
         // Status and Error Reporting Interface
         .channel_up(channel_up),
         .hard_err(hard_err),
         .soft_err(soft_err)
      );

      assign phy_status = {14'd0, hard_err, channel_up};

      wire           bist_gen_en, bist_checker_en, bist_loopback_en;
      wire           bist_checker_locked;
      wire [5:0]     bist_gen_rate;
      wire [47:0]    bist_checker_samps, bist_checker_errors;
      wire [31:0]    overruns, checksum_errors;

      aurora_axis_mac #(
         .PHY_ENDIANNESS ("LITTLE"),
         .PACKET_MODE    (1),
         .MAX_PACKET_SIZE(1024),
         .BIST_ENABLED   (1)
      ) aurora_mac_i (
         // Clocks and resets
         .phy_clk(au_user_clk), .phy_rst(au_user_rst),
         .sys_clk(bus_clk), .sys_rst(bus_rst),
         .clear(mac_clear),
         // PHY Interface (Synchronous to phy_clk)
         .phy_s_axis_tdata(o_tdata),
         .phy_s_axis_tvalid(o_tvalid),
         .phy_m_axis_tdata(i_tdata),
         .phy_m_axis_tvalid(i_tvalid),
         .phy_m_axis_tready(i_tready),
         // User Interface (Synchronous to sys_clk)
         .s_axis_tdata(c2mac_tdata),
         .s_axis_tlast(c2mac_tlast),
         .s_axis_tvalid(c2mac_tvalid),
         .s_axis_tready(c2mac_tready),
         .m_axis_tdata(m_axis_tdata),
         .m_axis_tlast(m_axis_tlast),
         .m_axis_tvalid(m_axis_tvalid),
         .m_axis_tready(m_axis_tready),
         // PHY Status Inputs (Synchronous to phy_clk)
         .channel_up(channel_up),
         .hard_err(hard_err),
         .soft_err(soft_err),
         // Status and Error Outputs (Synchronous to sys_clk)
         .overruns(overruns),
         .soft_errors(),
         .checksum_errors(checksum_errors),
         .critical_err(mac_crit_err),
         // BIST Interface (Synchronous to sys_clk)
         .bist_gen_en(bist_gen_en),
         .bist_gen_rate(bist_gen_rate),
         .bist_checker_en(bist_checker_en),
         .bist_loopback_en(bist_loopback_en),
         .bist_checker_locked(bist_checker_locked),
         .bist_checker_samps(bist_checker_samps),
         .bist_checker_errors(bist_checker_errors)
      );

       reg mac_crit_err_latch;
       always @(posedge bus_clk) begin
          if (bus_rst | mac_clear) begin
             mac_crit_err_latch <= 1'b0;
          end else begin
             if (mac_crit_err_bclk)
                mac_crit_err_latch <= 1'b1;
          end
       end

      assign m_axis_tuser = 4'd0;

      wire        set_stb;
      wire [3:0]  set_addr, rb_addr;
      wire [31:0] set_data;
      reg  [31:0] rb_data;

      settings_bus #(.AWIDTH(8), .DWIDTH(32), .SWIDTH(4)) settings_bus_i (
         .wb_clk(bus_clk), .wb_rst(bus_rst),
         .wb_adr_i(wb_adr_i), .wb_dat_i(wb_dat_i),
         .wb_stb_i(wb_stb_i), .wb_we_i(wb_we_i), .wb_ack_o(wb_ack_o),
         .strobe(set_stb), .addr(set_addr), .data(set_data)
      );

      settings_readback #(.AWIDTH(8),.DWIDTH(32), .RB_ADDRW(4)) settings_readback_i (
         .wb_clk(bus_clk), .wb_rst(bus_rst),
         .wb_adr_i(wb_adr_i), .wb_stb_i(wb_stb_i), .wb_we_i(wb_we_i), .wb_dat_o(wb_dat_o),
         .rb_data(rb_data), .rb_addr(rb_addr), .rb_rd_stb()
      );

      setting_reg #(.my_addr(4'd0), .awidth(4), .width(11), .at_reset(11'h000)) set_core_control_i (
         .clk(bus_clk), .rst(bus_rst),
         .strobe(set_stb), .addr(set_addr), .in(set_data),
         .out({mac_clear, phy_areset, bist_gen_rate, bist_loopback_en, bist_gen_en, bist_checker_en}), .changed()
      );

      wire channel_up_bclk, hard_err_bclk, soft_err_bclk, mac_crit_err_bclk;
      synchronizer #(.INITIAL_VAL(1'b0)) channel_up_sync (
         .clk(bus_clk), .rst(1'b0 /* no reset */), .in(channel_up), .out(channel_up_bclk));
      synchronizer #(.INITIAL_VAL(1'b0)) hard_err_sync (
         .clk(bus_clk), .rst(1'b0 /* no reset */), .in(hard_err), .out(hard_err_bclk));
      synchronizer #(.INITIAL_VAL(1'b0)) soft_err_sync (
         .clk(bus_clk), .rst(1'b0 /* no reset */), .in(soft_err), .out(soft_err_bclk));
      synchronizer #(.INITIAL_VAL(1'b0)) mac_crit_err_sync (
        .clk(bus_clk), .rst(1'b0 /* no reset */), .in(mac_crit_err), .out(mac_crit_err_bclk));

      reg [19:0]  bist_lock_latency;
      always @(posedge bus_clk) begin
         if (!bist_checker_en && !bist_checker_locked)
            bist_lock_latency <= 20'd0;
         else if (bist_checker_en && !bist_checker_locked)
            bist_lock_latency <= bist_lock_latency + 20'd1;
      end

       reg mac_crit_err_latch;
       always @(posedge bus_clk) begin
          if (bus_rst | mac_clear) begin
             mac_crit_err_latch <= 1'b0;
          end else begin
             if (mac_crit_err_bclk)
                mac_crit_err_latch <= 1'b1;
          end
       end

      wire [31:0] core_status = {
         6'h0,                      //[31:26]
         mac_crit_err_latch,        //[25]
         1,                         //[24] mmcm_locked_bclk
         1,                         //[23] gt_pll_locked_bclk
         0,                         //[22] qpll_refclklost_bclk
         1,                         //[21] qpll_lock_bclk
         0,                         //[20] qpll_reset_bclk
         bist_lock_latency[19:4],   //[19:4]
         bist_checker_locked,       //[3]
         soft_err_bclk,             //[2]
         hard_err_bclk,             //[1]
         channel_up_bclk            //[0]
      };

      assign link_up = channel_up_bclk;

      always @(*)
         case (rb_addr)
            4'd0:    rb_data = core_status;
            4'd1:    rb_data = overruns;
            4'd2:    rb_data = checksum_errors;
            4'd3:    rb_data = bist_checker_samps[47:16];   //Scale num sample by 2^16
            4'd4:    rb_data = bist_checker_errors[31:0];   //Dont scale errors
            default: rb_data = 32'h0;
         endcase // case (rb_addr)

   end else begin

      //Invalid protocol

   end
endgenerate

   //-----------------------------------------------------------------
   // Activity detector
   //-----------------------------------------------------------------

   pulse_stretch act_pulse_str_i (
     .clk(bus_clk),
     .rst(bus_rst | ~link_up),
     .pulse((s_axis_tvalid & s_axis_tready) | (m_axis_tvalid & m_axis_tready)),
     .pulse_stretched(activity)
   );

endmodule

