//
// Copyright 2014-2016 Ettus Research
// Copyright 2018 Ettus Research, a National Instruments Company
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
//  This block passes the in_* AXI port to the out_* AXI port only when it has
//  enough flow control credits. Data is held when there are not enough credits.
//  Credits are replenished with extension context packets which update the 
//  last_consumed packet register. Max credits are controlled by settings regs.
//  The 2nd line of the packet contains the sequence number in the low 12 bits.
//  These packets should not have a time value, but if they do it will be ignored.

module source_flow_control #(
   parameter SR_FLOW_CTRL_WINDOW_SIZE = 0,
   parameter SR_FLOW_CTRL_WINDOW_EN = 1
) (
   input clk, input reset, input clear,
   input set_stb, input [7:0] set_addr, input [31:0] set_data,
   input [63:0] fc_tdata, input fc_tlast, input fc_tvalid, output fc_tready,
   input [63:0] in_tdata, input in_tlast, input in_tvalid, output in_tready,
   output [63:0] out_tdata, output out_tlast, output out_tvalid, input out_tready,
   output busy,
   output [31:0] debug
);
   reg [31:0]     last_seqnum_consumed;
   wire [31:0]    window_size;
   wire [31:0]    go_until_seqnum = last_seqnum_consumed + window_size + 1;
   reg [31:0]     current_seqnum;
   wire           window_reset;
   wire           window_enable;

   //Sets the size of the flow control window
   setting_reg #(.my_addr(SR_FLOW_CTRL_WINDOW_SIZE)) sr_window_size
     (.clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr),.in(set_data),
      .out(window_size),.changed());

   //Setting to enable/disable the flow control window
   //When this register is hit, the window will reset.
   //As a part of the reset process, all FC blocked data upstream will be
   //dropped by this module and it will reset to the SFC_HEAD head state.
   //The reset sequence can take more than one cycle during which this
   //module will hold off all flow control data.
   setting_reg #(.my_addr(SR_FLOW_CTRL_WINDOW_EN), .width(1)) sr_window_enable
     (.clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr),.in(set_data),
      .out(window_enable),.changed(window_reset));

   reg         go;
   reg         window_reseting;
   reg [11:0]  window_reset_cnt; //Counter to make reset tolerant to bubble cycles
   reg [1:0]   sfc_state;
   
   always @(posedge clk) begin
      if (reset | clear) begin
         window_reseting  <= 1'b0;
      end else if (window_reset) begin                  //Reset start
         window_reseting  <= 1'b1;
         window_reset_cnt <= 12'd0;
      end else if (window_reseting & ~in_tvalid) begin  //Reset end
         window_reset_cnt <= window_reset_cnt + 12'd1;
         window_reseting  <= (window_reset_cnt == 12'hFFF);
      end
   end
   
   localparam SFC_HEAD = 2'd0;
   localparam SFC_TIME = 2'd1;
   localparam SFC_BODY = 2'd2;
   localparam SFC_DUMP = 2'd3;

   always @(posedge clk)
     if (reset | clear | window_reset) begin
         last_seqnum_consumed <= 32'hFFFFFFFF;
         sfc_state <= SFC_HEAD;
     end else if (fc_tvalid & fc_tready)
         case(sfc_state)
         SFC_HEAD :
            if(fc_tlast)
               sfc_state <= SFC_HEAD; // Error. CHDR packet with only a header is an error.
            else if(fc_tdata[63:62] != 2'b01)  // Is this NOT a flow control packet?
               sfc_state <= SFC_DUMP; // Error. Only extension context packets should come in on this interface.
            else if(fc_tdata[61])    // Does this packet have time?
               sfc_state <= SFC_TIME;
            else
               sfc_state <= SFC_BODY;

         SFC_TIME :
            if(fc_tlast)
               sfc_state <= SFC_HEAD; // Error, CHDR packet with only header and time is an error.
            else
               sfc_state <= SFC_BODY;
         
         SFC_BODY :
         begin
            last_seqnum_consumed <= fc_tdata[31:0]; // Sequence number is in lower 32bits.
            if(fc_tlast)
               sfc_state <= SFC_HEAD;
            else
               sfc_state <= SFC_DUMP; // Error. Not expecting any more data in a CHDR packet.
         end

         SFC_DUMP :   // shouldn't ever need to be here, this is an error condition
           if(fc_tlast)
             sfc_state <= SFC_HEAD;

         endcase // case (sfc_state)
      
   assign busy       = window_reseting;
   assign fc_tready  = ~window_reseting;      // Consume FC if not in reset
   assign out_tdata  = in_tdata;              // CHDR data flows through combinatorially.
   assign out_tlast  = in_tlast;
   assign in_tready  = (go ? out_tready : 1'b0) | window_reseting;
   assign out_tvalid = (go & ~window_reseting) ? in_tvalid : 1'b0;

   // TODO: Fix to increment sequence number only on data packets.
   reg first_line = 1'b1;
   reg is_data_pkt;
   always @(posedge clk) begin
     if (reset) begin
       first_line  <= 1'b1;
       is_data_pkt <= 1'b0;
     end else begin
       if (in_tvalid & in_tready & first_line) begin
         first_line  <= 1'b0;
         is_data_pkt <= (in_tdata[63:62] == 2'b0);
       end
       if (in_tvalid & in_tready & in_tlast) begin
         first_line  <= 1'b1;
       end
     end
   end

   //
   // Each time we receive the end of an IF data packet increment the current_seqnum.
   // We bravely assume that no packets go missing...or at least that they will be detected elsewhere
   // and then handled appropriately.
   // The SEQNUM needs to be initialized every time we start a new stream. In new_rx_framer this is done
   // as a side effect of writing a new SID value to the setting reg.
   //
   // By incrementing current_seqnum on the last signal we get the nice effect that packet flow is 
   // always suspended between packets rather than within a packet.
   //
   always @(posedge clk)
      if(reset | clear | window_reseting)
         current_seqnum <= 32'd0;
      else if (in_tvalid && in_tready && in_tlast && is_data_pkt)
         current_seqnum <= current_seqnum + 32'd1;

   always @(posedge clk)
      if(reset | clear) begin
         go <= 1'b0;
      end else begin
         if(~window_enable)
            go <= 1'b1;
         else
            case(go)
            1'b0:
               // Note: "!=" works even with 32-bit wrap around. If a fc packet with an invalid seqnum
               //       is received, this could get into a bad state.
               if(in_tvalid & (go_until_seqnum != current_seqnum))
                  go <= 1'b1;

            1'b1:
               if(in_tvalid & in_tready & in_tlast)
                  go <= 1'b0;
            endcase // case (go)
      end

   assign debug = { window_enable, go, go_until_seqnum[5:0], last_seqnum_consumed[11:0], current_seqnum[11:0] };
      
endmodule // source_flow_control
