//
// Copyright 2020 Ettus Research, A National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: axis_packetize
//
// Description:
//
//   This module takes in an axi_stream without packet boundaries (i.e.,
//   without tlast) and groups the data into packets by adding tlast at the
//   appropriate time. The size of the packet is controlled by the "size"
//   input, which is sampled at the beginning of each packet to be output. The
//   packet_size input indicates the number of i_tdata words to group into a
//   packet. The i_tlength output indicates the length of the packet being
//   output.
//
//   The gate input causes data transfers to be stopped at the end of the
//   current packet when the gate input is asserted. It is not legal to
//   deassert tvalid once it has been asserted until the next transfer is
//   completed, so this module monitors the state of the AXI-Stream protocol
//   so that no protocol violations occur.
//
//   Note that the current transfer may still complete after gate has been
//   asserted, so the downstream logic must be able to account for at least
//   one more transfer.
//
// Parameters:
//
//   DATA_W       : Width of the tdata signals.
//   SIZE_W       : The width of the packet size port. This dictates the
//                  maximum packet size.
//   FLUSH        : Controls whether or not the input should be stalled or
//                  flushed. That is, when FLUSH=0, the input data is stalled
//                  whenever the gate is on (i_tready becomes 0). When
//                  FLUSH=1, the input data is dropped whenever the gate is on
//                  (i_tready becomes 1).
//   DEFAULT_SIZE : The default packet size to use, if it doesn't need to be
//                  changed at run time.
//


module axis_packetize #(
  parameter DATA_W       = 32,
  parameter SIZE_W       = 16,
  parameter FLUSH        = 0,
  parameter DEFAULT_SIZE = 2**SIZE_W-1
) (
  input wire clk,
  input wire rst,

  input wire              gate,       // Stop or "gate" packet output
  input wire [SIZE_W-1:0] size,       // Size to use for the next packet

  // Input data stream
  input  wire [DATA_W-1:0] i_tdata,
  input  wire              i_tvalid,
  output wire              i_tready,

  // Output data stream
  output wire [DATA_W-1:0] o_tdata,
  output reg               o_tlast,
  output wire              o_tvalid,
  input  wire              o_tready,
  output wire [SIZE_W-1:0] o_tuser     // Current packet's size
);

  reg              start_of_packet = 1;    // Next sample is start of a packet
  reg [SIZE_W-1:0] word_count      = 0;    // Count of output words
  reg [SIZE_W-1:0] current_size    = DEFAULT_SIZE;  // Current packet size

  reg gating     = 1'b0;     // Indicate if output is blocked
  reg mid_packet = 1'b0;     // Indicate if we're in the middle of a packet

  //---------------------------------------------------------------------------
  // Packet Size Logic
  //---------------------------------------------------------------------------

  assign o_tuser = current_size;

  always @(posedge clk) begin
    if (rst) begin
      start_of_packet <= 1'b1;
      current_size    <= DEFAULT_SIZE;
      word_count      <= 0;
      o_tlast         <= (DEFAULT_SIZE == 1);
    end else begin
      if (gating) begin
        // Wait until we're enabled. Setup for the start of the next packet.
        start_of_packet <= 1'b1;
        current_size    <= size;
        word_count      <= size;
        o_tlast         <= (size == 1);
      end else if (o_tvalid && o_tready) begin
        start_of_packet <= 1'b0;
        word_count      <= word_count - 1;
        if (o_tlast) begin
          // This is the last sample, so restart everything for a new packet.
          start_of_packet <= 1'b1;
          current_size    <= size;
          word_count      <= size;
          o_tlast         <= (size == 1);
        end else if (word_count == 2) begin
          // This is the second to last sample, so we assert tlast for the
          // last sample.
          o_tlast <= 1'b1;
        end
      end else if (start_of_packet) begin
        // We're waiting for the start of the next packet. Keep checking the
        // size input until the next packet starts.
        current_size <= size;
        word_count   <= size;
        o_tlast      <= (size == 1);
      end
    end
  end

  //---------------------------------------------------------------------------
  // Handshake Monitor
  //---------------------------------------------------------------------------
  
  // Monitor the state of the handshake so we know when it's OK to
  // enable/disable data transfer.
  always @(posedge clk) begin
    if (rst) begin
      gating     = 1'b0;
      mid_packet = 1'b0;
    end else begin
      // Keep track of if we are in the middle of a packet or not. Note that
      // mid_packet will be 0 for the first transfer of a packet.
      if (o_tvalid && o_tready) begin
        if (o_tlast) begin
          mid_packet = 1'b0;
        end else begin
          mid_packet = 1'b1;
        end
      end

      if (gating) begin
        // We can stop gating any time
        if (!gate) gating <= 0;
      end else begin
        // Only start gating between packets when the output is idle, or after
        // the output transfer completes at the end of packet.
        if ((!mid_packet && !o_tvalid) || (o_tvalid && o_tready && o_tlast)) begin
          gating <= gate;
        end
      end
    end
  end

  //---------------------------------------------------------------------------
  // Data Pass-Through
  //---------------------------------------------------------------------------

  // Note that "gating" only asserts when a transfer completes at the end of a
  // packet, or between packets when the output is idle. This ensures that
  // o_tvalid won't deassert during a transfer and cause a handshake protocol
  // violation.

  assign o_tdata  = i_tdata;
  assign o_tvalid = i_tvalid && !gating;
  assign i_tready = FLUSH ? (o_tready || gating) : (o_tready && !gating);

endmodule