//
// Copyright 2018-2019 Ettus Research, A National Instruments Company
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: axis_ctrl_master
// Description:
//  This module implements an AXIS-Control master (and a Control-Port
//  slave). Requests are accepted on the slave Control-Port, converted
//  to AXIS-Control requests, then sent over the master AXI-Stream port.
//  Responses are received on the AXI-Stream slave port, and converted
//  to Control-Port responses.
//  NOTE: Transactions are not buffered so there is no need for flow
//        control or throttling.
//
// Parameters:
//   - THIS_PORTID      : The local port-ID of this control port
//   - SKIP_WR_ACK_WAIT : When set to 1, write requests will ACK on CtrlPort
//                        immediately without waiting for the AXIS-Ctrl
//                        response. The response packets will be discarded.
//                        Set to 0 to wait for the response before
//                        ACK'ing on CtrlPort, which is the normal behavior.
//
// Signals:
//   - s_axis_ctrl_*   : Input control stream (AXI-Stream) for responses
//   - m_axis_ctrl_*   : Output control stream (AXI-Stream) for requests
//   - ctrlport_req_*  : Control-port master request port
//   - ctrlport_resp_* : Control-port master response port

module axis_ctrl_master #(
  parameter [9:0] THIS_PORTID      = 10'd0,
  parameter       SKIP_WR_ACK_WAIT = 0
)(
  // Clock and reset
  input  wire         clk,
  input  wire         rst,
  // AXIS-Control Bus (Response)
  input  wire [31:0]  s_axis_ctrl_tdata,
  input  wire         s_axis_ctrl_tlast,
  input  wire         s_axis_ctrl_tvalid,
  output wire         s_axis_ctrl_tready,
  // AXIS-Control Bus (Request)
  output reg  [31:0]  m_axis_ctrl_tdata,
  output wire         m_axis_ctrl_tlast,
  output wire         m_axis_ctrl_tvalid,
  input  wire         m_axis_ctrl_tready,
  // Control Port Endpoint (Request)
  input  wire         ctrlport_req_wr,
  input  wire         ctrlport_req_rd,
  input  wire [19:0]  ctrlport_req_addr,
  input  wire [9:0]   ctrlport_req_portid,
  input  wire [15:0]  ctrlport_req_rem_epid,
  input  wire [9:0]   ctrlport_req_rem_portid,
  input  wire [31:0]  ctrlport_req_data,
  input  wire [3:0]   ctrlport_req_byte_en,
  input  wire         ctrlport_req_has_time,
  input  wire [63:0]  ctrlport_req_time,
  // Control Port Endpoint (Response)
  output wire         ctrlport_resp_ack,
  output wire [1:0]   ctrlport_resp_status,
  output wire [31:0]  ctrlport_resp_data
);

  // ---------------------------------------------------
  //  RFNoC Includes
  // ---------------------------------------------------
  `include "rfnoc_chdr_utils.vh"
  `include "rfnoc_axis_ctrl_utils.vh"

  // ---------------------------------------------------
  //  State Machine
  // ---------------------------------------------------
  localparam [3:0] ST_IDLE          = 4'd0;   // Waiting for a request on slave ctrlport
  localparam [3:0] ST_REQ_HDR_LO    = 4'd1;   // Sending AXIS-Control request header (low bits)
  localparam [3:0] ST_REQ_HDR_HI    = 4'd2;   // Sending AXIS-Control request header (high bits)
  localparam [3:0] ST_REQ_TS_LO     = 4'd3;   // Sending AXIS-Control request timestamp (low bits)
  localparam [3:0] ST_REQ_TS_HI     = 4'd4;   // Sending AXIS-Control request timestamp (high bits)
  localparam [3:0] ST_REQ_OP_WORD   = 4'd5;   // Sending AXIS-Control request operation word
  localparam [3:0] ST_REQ_OP_DATA   = 4'd6;   // Sending AXIS-Control request data word
  localparam [3:0] ST_RESP_HDR_LO   = 4'd7;   // Receiving AXIS-Control response header (low bits)
  localparam [3:0] ST_RESP_HDR_HI   = 4'd8;   // Receiving AXIS-Control response header (high bits)
  localparam [3:0] ST_RESP_TS_LO    = 4'd9;   // Receiving AXIS-Control response timestamp (low bits)
  localparam [3:0] ST_RESP_TS_HI    = 4'd10;  // Receiving AXIS-Control response timestamp (high bits)
  localparam [3:0] ST_RESP_OP_WORD  = 4'd11;  // Receiving AXIS-Control response operation word
  localparam [3:0] ST_RESP_OP_DATA  = 4'd12;  // Receiving AXIS-Control response data word
  localparam [3:0] ST_SHORT_PKT_ERR = 4'd13;  // Response was too short. Send a dummy response on ctrlport
  localparam [3:0] ST_DROP_PKT      = 4'd14;  // Drop rest of packet (too long or unexpected ACK)
  localparam [3:0] ST_IMMEDIATE_ACK = 4'd15;  // Immediate ACK for write when SKIP_WR_ACK_WAIT=1

  // State variables
  reg [3:0]   state = ST_IDLE;    // Current state for FSM
  reg [5:0]   seq_num = 6'd0;     // Expected seqnum for response
  // Request state
  reg [3:0]   req_opcode;         // Cached opcode for transaction request
  reg [19:0]  req_addr;           // Cached address for transaction request
  reg [9:0]   req_portid;         // Cached port ID for transaction request
  reg [15:0]  req_rem_epid;       // Cached remote endpoint ID for transaction request
  reg [9:0]   req_rem_portid;     // Cached remote port ID for transaction request
  reg [31:0]  req_data;           // Cached data word for transaction request
  reg [3:0]   req_byte_en;        // Cached byte enable for transaction request
  reg         req_has_time;       // Cached has_time bit for transaction request
  reg [63:0]  req_time;           // Cached timestamp for transaction request
  // Response state
  reg         resp_has_time;      // Does the response have a timestamp?
  reg [1:0]   resp_status;        // The status in the response
  reg         resp_seq_err, resp_cmd_err; // Error bits for the response

  always @(posedge clk) begin
    if (rst) begin
      state <= ST_IDLE;
      seq_num <= 6'd0;
    end else begin
      case (state)

        // Ready to receive a request on ctrlport
        // ------------------------------------
        ST_IDLE: begin
          if (SKIP_WR_ACK_WAIT && s_axis_ctrl_tvalid) begin
            // When SKIP_WR_ACK_WAIT is enabled, parse the response header to
            // determine the next step. This may be a delayed response for a
            // write request that we ACK'ed immediately, or it may be a normal
            // response to a read request.
            state <= ST_RESP_HDR_LO;
          end else if (ctrlport_req_wr | ctrlport_req_rd) begin
            // A transaction was posted on the slave ctrlport...
            // Cache the opcode
            if (ctrlport_req_wr & ctrlport_req_rd)
              req_opcode   <= AXIS_CTRL_OPCODE_WRITE_READ;
            else if (ctrlport_req_rd)
              req_opcode   <= AXIS_CTRL_OPCODE_READ;
            else
              req_opcode   <= AXIS_CTRL_OPCODE_WRITE;
            // Cache transaction info
            req_addr       <= ctrlport_req_addr;
            req_portid     <= ctrlport_req_portid;
            req_rem_epid   <= ctrlport_req_rem_epid;
            req_rem_portid <= ctrlport_req_rem_portid;
            req_data       <= ctrlport_req_data;
            req_byte_en    <= ctrlport_req_byte_en;
            req_has_time   <= ctrlport_req_has_time;
            req_time       <= ctrlport_req_time;
            // Start sending out AXIS-Ctrl packet
            state <= ST_REQ_HDR_LO;
          end
        end

        // Send a request AXIS command
        // (a state for each stage in the packet)
        // ------------------------------------
        ST_REQ_HDR_LO: begin
          if (m_axis_ctrl_tready)
            state <= ST_REQ_HDR_HI;
        end
        ST_REQ_HDR_HI: begin
          if (m_axis_ctrl_tready)
            state <= req_has_time ? ST_REQ_TS_LO : ST_REQ_OP_WORD;
        end
        ST_REQ_TS_LO: begin
          if (m_axis_ctrl_tready)
            state <= ST_REQ_TS_HI;
        end
        ST_REQ_TS_HI: begin
          if (m_axis_ctrl_tready)
            state <= ST_REQ_OP_WORD;
        end
        ST_REQ_OP_WORD: begin
          if (m_axis_ctrl_tready)
            state <= ST_REQ_OP_DATA;
        end
        ST_REQ_OP_DATA: begin
          if (m_axis_ctrl_tready) begin
            if (SKIP_WR_ACK_WAIT && req_opcode == AXIS_CTRL_OPCODE_WRITE) begin
              state <= ST_IMMEDIATE_ACK;
            end else begin
              state <= ST_RESP_HDR_LO;
            end
          end
        end

        // Receive a response AXIS command
        // (a state for each stage in the packet)
        // ------------------------------------
        ST_RESP_HDR_LO: begin
          if (s_axis_ctrl_tvalid) begin
            // Remeber if the packet is supposed to have a timestamp
            resp_has_time <= axis_ctrl_get_has_time(s_axis_ctrl_tdata);
            // Check for a sequence error
            resp_seq_err <= (axis_ctrl_get_seq_num(s_axis_ctrl_tdata) != seq_num);
            // Assert a command error if:
            // - The port ID does not match
            // - The response was too short (the next check)
            resp_cmd_err <= (axis_ctrl_get_dst_port(s_axis_ctrl_tdata) != THIS_PORTID);
            if (!s_axis_ctrl_tlast) begin
              state <= ST_RESP_HDR_HI;
            end else begin
              // Response was too short
              resp_cmd_err <= 1'b1;
              state <= ST_SHORT_PKT_ERR;
            end
          end
        end
        ST_RESP_HDR_HI: begin
          if (s_axis_ctrl_tvalid) begin
            if (!s_axis_ctrl_tlast) begin
              state <= resp_has_time ? ST_RESP_TS_LO : ST_RESP_OP_WORD;
            end else begin
              // Response was too short
              resp_cmd_err <= 1'b1;
              state <= ST_SHORT_PKT_ERR;
            end
          end
        end
        ST_RESP_TS_LO: begin
          if (s_axis_ctrl_tvalid) begin
            if (!s_axis_ctrl_tlast) begin
              state <= ST_RESP_TS_HI;
            end else begin
              // Response was too short
              resp_cmd_err <= 1'b1;
              state <= ST_SHORT_PKT_ERR;
            end
          end
        end
        ST_RESP_TS_HI: begin
          if (s_axis_ctrl_tvalid) begin
            if (!s_axis_ctrl_tlast) begin
              state <= ST_RESP_OP_WORD;
            end else begin
              // Response was too short
              resp_cmd_err <= 1'b1;
              state <= ST_SHORT_PKT_ERR;
            end
          end
        end
        ST_RESP_OP_WORD: begin
          if (s_axis_ctrl_tvalid) begin
            if (!s_axis_ctrl_tlast) begin
              // Assert a command error if opcode and addr in request does not match response
              resp_cmd_err <= resp_cmd_err ||
                              (axis_ctrl_get_opcode(s_axis_ctrl_tdata) != req_opcode) ||
                              (axis_ctrl_get_address(s_axis_ctrl_tdata) != req_addr);
              resp_status <= axis_ctrl_get_status(s_axis_ctrl_tdata);
              if (SKIP_WR_ACK_WAIT &&
                 (axis_ctrl_get_opcode(s_axis_ctrl_tdata) == AXIS_CTRL_OPCODE_WRITE)) begin
                // For write operations when SKIP_WR_ACK_WAIT is enabled, we
                // can just drop the rest of the packet.
                state <= ST_DROP_PKT;
              end else begin
                state <= ST_RESP_OP_DATA;
              end
            end else begin
              // Response was too short
              resp_cmd_err <= 1'b1;
              state <= ST_SHORT_PKT_ERR;
            end
          end
        end
        ST_RESP_OP_DATA: begin
          if (s_axis_ctrl_tvalid) begin
            // If the packet was too long then just drop the rest without complaining
            state <= s_axis_ctrl_tlast ? ST_IDLE : ST_DROP_PKT;
            seq_num <= seq_num + 6'd1;
          end
        end

        // Error handling states
        // ------------------------------------
        ST_SHORT_PKT_ERR: begin
          state <= ST_IDLE;
        end
        ST_DROP_PKT: begin
          if (s_axis_ctrl_tvalid && s_axis_ctrl_tlast)
            state <= ST_IDLE;
        end

        // SKIP_WR_ACK_WAIT states
        // ------------------------------------
        ST_IMMEDIATE_ACK: begin
          // Provide immediate ACK for write operation
          state <= ST_IDLE;
          seq_num <= seq_num + 6'd1;
        end

        default: begin
          // We should never get here
          state <= ST_IDLE;
        end
      endcase
    end
  end

  // Logic to drive m_axis_ctrl_*
  // ------------------------------------
  always @(*) begin
    case (state)
      ST_REQ_HDR_LO: begin
        m_axis_ctrl_tdata = axis_ctrl_build_hdr_lo(
          1'b0 /* is_ack*/, req_has_time, seq_num,
          4'd1 /* num_data */, THIS_PORTID, req_portid);
      end
      ST_REQ_HDR_HI: begin
        m_axis_ctrl_tdata = axis_ctrl_build_hdr_hi(
          req_rem_portid, req_rem_epid);
      end
      ST_REQ_TS_LO: begin
        m_axis_ctrl_tdata = req_time[31:0];
      end
      ST_REQ_TS_HI: begin
        m_axis_ctrl_tdata = req_time[63:32];
      end
      ST_REQ_OP_WORD: begin
        m_axis_ctrl_tdata = axis_ctrl_build_op_word(
          AXIS_CTRL_STS_OKAY, req_opcode, req_byte_en, req_addr);
      end
      ST_REQ_OP_DATA: begin
        m_axis_ctrl_tdata = req_data;
      end
      default: begin
        m_axis_ctrl_tdata = 32'h0;
      end
    endcase
  end
  assign m_axis_ctrl_tvalid = (state == ST_REQ_HDR_LO)  ||
                              (state == ST_REQ_HDR_HI)  ||
                              (state == ST_REQ_TS_LO)   ||
                              (state == ST_REQ_TS_HI)   ||
                              (state == ST_REQ_OP_WORD) ||
                              (state == ST_REQ_OP_DATA);
  assign m_axis_ctrl_tlast  = (state == ST_REQ_OP_DATA);

  // Logic to backpressure responses
  // ------------------------------------
  assign s_axis_ctrl_tready = (state == ST_RESP_HDR_LO)  ||
                              (state == ST_RESP_HDR_HI)  ||
                              (state == ST_RESP_TS_LO)   ||
                              (state == ST_RESP_TS_HI)   ||
                              (state == ST_RESP_OP_WORD) ||
                              (state == ST_RESP_OP_DATA) ||
                              (state == ST_DROP_PKT);

  // Logic to drive Control-port response
  // ------------------------------------
  assign ctrlport_resp_ack    = (state == ST_RESP_OP_DATA && s_axis_ctrl_tvalid) ||
                                (state == ST_SHORT_PKT_ERR) ||
                                (state == ST_IMMEDIATE_ACK);
  assign ctrlport_resp_status = (state == ST_IMMEDIATE_ACK) ? AXIS_CTRL_STS_OKAY :
                                resp_cmd_err                ? AXIS_CTRL_STS_CMDERR :
                                resp_seq_err                ? AXIS_CTRL_STS_WARNING :
                                                              resp_status;
  assign ctrlport_resp_data   = ((state == ST_SHORT_PKT_ERR) ||
                                 (state == ST_IMMEDIATE_ACK)) ? 32'h0 : s_axis_ctrl_tdata;

endmodule // axis_ctrl_master
