//
// Copyright 2016 Ettus Research
// Copyright 2018 Ettus Research, a National Instruments Company
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Drop packets that are larger or smaller than the allowed packet size.
//

module axi_drop_partial_packet #(
  parameter WIDTH = 32,
  parameter MAX_PKT_SIZE = 1024,
  parameter HOLD_LAST_WORD = 0,       // Hold off sending last word until next full packet arrives
  parameter SR_PKT_SIZE_ADDR = 1,
  parameter DEFAULT_PKT_SIZE = 1
)(
  input clk, input reset, input clear,
  input flush,  // If using HOLD_LAST_WORD, will forcibly release all words in FIFO
  input set_stb, input [7:0] set_addr, input [31:0] set_data,
  input [WIDTH-1:0] i_tdata, input i_tlast, input i_tvalid, output i_tready,
  output [WIDTH-1:0] o_tdata, output o_tlast, output o_tvalid, input o_tready
);

  localparam MAX_PKT_SIZE_LOG2 = $clog2(MAX_PKT_SIZE+1);

  generate
    // Packet size of 1 means it is impossible to form a partial packet, so this module does nothing...
    if (MAX_PKT_SIZE == 1) begin
      assign o_tdata  = i_tdata;
      assign o_tlast  = i_tlast;
      assign o_tvalid = i_tvalid;
      assign i_tready = o_tready;
    // All other packet sizes
    end else begin
      // Settings register
      wire [MAX_PKT_SIZE_LOG2-1:0] sr_pkt_size;
      setting_reg #(.my_addr(SR_PKT_SIZE_ADDR), .width(MAX_PKT_SIZE_LOG2), .at_reset(DEFAULT_PKT_SIZE)) set_pkt_size (
        .clk(clk), .rst(reset), .strobe(set_stb), .addr(set_addr), .in(set_data),
        .out(sr_pkt_size), .changed());

      // Do not change n unless block is not active
      reg active;
      reg [MAX_PKT_SIZE_LOG2-1:0] pkt_size = 1;
      reg pkt_size_is_one = 1;
      always @(posedge clk) begin
        if (reset | clear) begin
          active <= 1'b0;
        end else begin
          if (i_tready & i_tvalid) begin
            active <= 1'b1;
          end
        end
        if (clear | ~active) begin
          pkt_size <= (sr_pkt_size == 0) ? 1 : sr_pkt_size;
        end
        pkt_size_is_one <= (pkt_size == 1);
      end

      wire [WIDTH-1:0] int_tdata;
      wire int_tlast, int_tvalid, int_tready;
      wire i_tlast_int, i_terror;

      reg small_pkt, large_pkt;
      wire hold_last_sample;
      reg release_last;
      reg [MAX_PKT_SIZE_LOG2-1:0] in_cnt;
      reg [15:0] in_pkt_cnt, in_pkt_cnt_hold, out_pkt_cnt, out_pkt_cnt_p1;
      always @(posedge clk) begin
        if (reset | clear) begin
          small_pkt             <= 1'b0;
          large_pkt             <= 1'b0;
          release_last          <= 1'b0;
          in_cnt                <= 1;
          in_pkt_cnt            <= 0;
          in_pkt_cnt_hold       <= 0;
          out_pkt_cnt           <= 0;
          out_pkt_cnt_p1        <= 0;
        end else begin
          if (i_tvalid & i_tready) begin
            if (in_cnt == pkt_size | i_tlast_int) begin
              in_cnt     <= 1;
            end else begin
              in_cnt     <= in_cnt + 1;
            end
          end
          if (pkt_size == 1) begin
            small_pkt      <= 1'b0;
            large_pkt      <= 1'b0;
          end else begin
            if (i_tvalid & i_tready) begin
              if ((in_cnt == pkt_size-1'b1) & ~i_tlast) begin
                small_pkt  <= 1'b0;
              end else begin
                small_pkt  <= 1'b1;
              end
              if ((in_cnt == pkt_size) & ~i_tlast) begin
                large_pkt  <= 1'b1;
              end
              if (large_pkt) begin
                large_pkt  <= 1'b0;
              end
            end
          end
          if (i_tvalid & i_tready & i_tlast & ~i_terror) begin
            in_pkt_cnt     <= in_pkt_cnt + 1'b1;
          end
          if (int_tvalid & int_tready & int_tlast & ~hold_last_sample) begin
            out_pkt_cnt    <= out_pkt_cnt + 1'd1;
            out_pkt_cnt_p1 <= out_pkt_cnt + 2'd2;
          end
          if ((i_tvalid & i_tready & i_terror) | flush) begin
            release_last         <= 1'b1;
            in_pkt_cnt_hold      <= in_pkt_cnt;
          end else if (in_pkt_cnt_hold == out_pkt_cnt) begin
            release_last         <= 1'b0;
          end
        end
      end

      // This signal is difficult to meet timing on. All input should be registered.
      assign hold_last_sample = ((in_pkt_cnt == out_pkt_cnt) | ((in_pkt_cnt == out_pkt_cnt_p1) & ~release_last)) & ~pkt_size_is_one;

      assign i_tlast_int = i_tlast | large_pkt;
      assign i_terror    = i_tlast & i_tvalid & (small_pkt | large_pkt);

      // FIFO with ability to rewind write pointer back if input packet is flagged as bad
      axi_packet_gate #(.WIDTH(WIDTH+1), .SIZE(MAX_PKT_SIZE_LOG2), .USE_AS_BUFF(1)) pkt_gate_i (
        .clk(clk), .reset(reset), .clear(clear),
        .i_tdata({i_tlast,i_tdata}), .i_tvalid(i_tvalid), .i_tlast(i_tlast_int), .i_terror(i_terror), .i_tready(i_tready),
        .o_tdata({int_tlast,int_tdata}), .o_tvalid(int_tvalid), .o_tlast(), .o_tready(int_tready & ~(hold_last_sample & int_tlast)));

      // Generate output register to hold on to last word
      if (HOLD_LAST_WORD) begin
        axi_fifo_flop2 #(.WIDTH(WIDTH+1)) axi_fifo_flop2 (
          .clk(clk), .reset(reset), .clear(clear),
          .i_tdata({int_tlast,int_tdata}), .i_tvalid(int_tvalid & ~(hold_last_sample & int_tlast)), .i_tready(int_tready),
          .o_tdata({o_tlast,o_tdata}), .o_tvalid(o_tvalid), .o_tready(o_tready),
          .space(), .occupied());
      end else begin
        assign o_tdata    = int_tdata;
        assign o_tlast    = int_tlast;
        assign o_tvalid   = int_tvalid;
        assign int_tready = o_tready;
      end
    end
  endgenerate

endmodule
