module axi_async_stream #(
  parameter WIDTH            = 32,
  parameter HEADER_WIDTH     = 128,
  parameter HEADER_FIFO_SIZE = 5,
  parameter MAX_TICK_RATE    = 2**16-1)
(
  input clk,
  input reset,
  input clear,
  input [15:0] src_sid,
  input [15:0] dst_sid,
  input [$clog2(MAX_TICK_RATE)-1:0] tick_rate,
  output header_fifo_full,
  // From AXI Wrapper
  input [WIDTH-1:0] s_axis_data_tdata,
  input [HEADER_WIDTH-1:0] s_axis_data_tuser,
  input s_axis_data_tlast,
  input s_axis_data_tvalid,
  output s_axis_data_tready,
  // To AXI Wrapper
  output [WIDTH-1:0] m_axis_data_tdata,
  output [HEADER_WIDTH-1:0] m_axis_data_tuser,
  output m_axis_data_tlast,
  output m_axis_data_tvalid,
  input m_axis_data_tready,
  // To User
  output [WIDTH-1:0] o_tdata,
  output o_tlast,
  output o_tvalid,
  input o_tready,
  // From User
  input [WIDTH-1:0] i_tdata,
  input i_tlast,
  input i_tvalid,
  input i_tkeep,
  output i_tready
);

  wire [WIDTH-1:0] i_reg_tdata;
  wire i_reg_tvalid, i_reg_tlast, i_reg_tkeep, i_reg_tready;

  reg [WIDTH-1:0] pipe_tdata;
  reg pipe_tvalid, pipe_tlast, pipe_tkeep;
  wire pipe_tready;

  /********************************************************
  ** Register user input
  ** - The output logic in some cases needs to wait for
  **   i_tvalid to assert before asserting i_tready.
  **   However, users may implement logic that waits for
  **   i_tready to assert before asserting i_tvalid.
  **   Without this register, that would result in a
  **   deadlock.
  ** - Note: Technically, the user waiting for i_tready
  **   violates the AXI specification that a master cannot
  **   wait for ready from the slave. However, it is common
  **   for users to accidentally break this rule and this is
  **   a cheap workaround.
  ********************************************************/
  axi_fifo_flop #(.WIDTH(WIDTH+2)) axi_fifo_flop (
    .clk(clk), .reset(reset), .clear(clear),
    .i_tdata({i_tkeep,i_tlast,i_tdata}), .i_tvalid(i_tvalid), .i_tready(i_tready),
    .o_tdata({i_reg_tkeep,i_reg_tlast,i_reg_tdata}), .o_tvalid(i_reg_tvalid), .o_tready(i_reg_tready));

  /********************************************************
  ** Keep track of headers for user
  ********************************************************/
  wire header_in_tready, header_in_tvalid, header_out_tvalid, header_out_tready;
  wire [HEADER_WIDTH-1:0] header_in_tdata, header_out_tdata;

  reg first_word = 1'b1;
  reg [15:0] word_cnt;
  reg [16+$clog2(MAX_TICK_RATE)-1:0] time_cnt; // 16 bit payload length + max tick rate increment

  wire [63:0] vita_time;
  wire [15:0] payload_length;

  // Track first word to make sure header is read only once per packet
  always @(posedge clk) begin
    if (reset | clear) begin
      first_word <= 1'b1;
    end else begin
      if (s_axis_data_tvalid & s_axis_data_tready) begin
        if (s_axis_data_tlast) begin
          first_word <= 1'b1;
        end else if (first_word) begin
          first_word <= 1'b0;
        end
      end
    end
  end

  // Header FIFO
  axi_fifo #(.WIDTH(HEADER_WIDTH), .SIZE(HEADER_FIFO_SIZE)) axi_fifo (
    .clk(clk), .reset(reset), .clear(clear),
    .i_tdata(header_in_tdata), .i_tvalid(header_in_tvalid), .i_tready(header_in_tready),
    .o_tdata(header_out_tdata), .o_tvalid(header_out_tvalid), .o_tready(header_out_tready),
    .space(), .occupied());

  assign header_in_tdata   = s_axis_data_tuser;
  assign header_in_tvalid  = s_axis_data_tvalid & o_tready & first_word;
  assign header_out_tready = i_reg_tvalid & i_reg_tready & (word_cnt >= payload_length);
  assign header_fifo_full  = ~header_in_tready;

  // Track VITA time offset and word count for emptying header FIFO
  always @(posedge clk) begin
    if (reset | clear) begin
      word_cnt       <= WIDTH/8;
      time_cnt       <= 0;
    end else begin
      if (pipe_tvalid & pipe_tready) begin
        if (word_cnt >= payload_length) begin
          word_cnt <= WIDTH/8;
          time_cnt <= 0;
        end else begin
          word_cnt <= word_cnt + WIDTH/8;
          time_cnt <= time_cnt + tick_rate;
        end
      end
    end
  end

  // Form output header
  cvita_hdr_decoder cvita_hdr_decoder (
    .header(header_out_tdata),
    .pkt_type(), .eob(), .has_time(),
    .seqnum(), .payload_length(payload_length),
    .src_sid(), .dst_sid(),
    .vita_time(vita_time));

  cvita_hdr_modify cvita_hdr_modify (
    .header_in(header_out_tdata),
    .header_out(m_axis_data_tuser),
    .use_pkt_type(1'b0),       .pkt_type(),
    .use_has_time(1'b0),       .has_time(),
    .use_eob(1'b0),            .eob(),
    .use_seqnum(1'b0),         .seqnum(), // AXI Wrapper handles this
    .use_length(1'b0),         .length(), // AXI Wrapper handles this
    .use_payload_length(1'b0), .payload_length(),
    .use_src_sid(1'b1),        .src_sid(src_sid),
    .use_dst_sid(1'b1),        .dst_sid(dst_sid),
    .use_vita_time(1'b1),      .vita_time(vita_time + time_cnt));

  /********************************************************
  ** Data to user from AXI Wrapper
  ** - Throttles if header FIFO is full
  ********************************************************/
  assign o_tdata            = s_axis_data_tdata;
  assign o_tvalid           = s_axis_data_tvalid & header_in_tready;
  assign o_tlast            = s_axis_data_tlast;
  assign s_axis_data_tready = o_tready & header_in_tready;

  /********************************************************
  ** Data from user to AXI Wrapper
  ** - Handles asserting tlast
  ** - Asserts tlast in three cases:
  **   1) User asserts tlast
  **   2) End of a burst of samples (i.e. when tkeep deasserts).
  **   3) End of a packet, in case VITA is different between packets
  ********************************************************/
  wire ready;
  always @(posedge clk) begin
    if (reset | clear) begin
      pipe_tdata      <= 'd0;
      pipe_tvalid     <= 1'b0;
      pipe_tlast      <= 1'b0;
      pipe_tkeep      <= 1'b0;
    end else begin
      if (pipe_tready) begin
        pipe_tdata    <= i_reg_tdata;
        pipe_tvalid   <= i_reg_tvalid;
        pipe_tlast    <= i_reg_tlast;
        pipe_tkeep    <= i_reg_tkeep;
      end
    end
  end

  assign pipe_tready        = ~pipe_tvalid | (m_axis_data_tready & header_out_tvalid & (i_reg_tvalid | (m_axis_data_tvalid & m_axis_data_tlast)));
  assign i_reg_tready       = pipe_tready;
  assign m_axis_data_tdata  = pipe_tdata;
  assign m_axis_data_tvalid = pipe_tvalid & pipe_tkeep & (i_reg_tvalid | m_axis_data_tlast) & header_out_tvalid;
  assign m_axis_data_tlast  = pipe_tlast | (i_reg_tvalid & ~i_reg_tkeep) | (word_cnt >= payload_length);

endmodule