/*******************************************************************************
 * Module: axi_hp_abort
 * Date:2016-02-07  
 * Author: Andrey Filippov     
 * Description: Trying to gracefully reset AXI HP after aborted transmission
 * For read channel - just keep afi_rready on until RD FIFO is empty (afi_rcount ==0)
 * For write - keep track aof all what was sent so far, assuming aw is always ahead of w
 * Reset only by global reset (system POR) - probably it is not possible to just
 * reset PL or relaod bitfile, 
 *
 * Copyright (c) 2016 Elphel, Inc .
 * axi_hp_abort.v is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 *  axi_hp_abort.v is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/> .
 *******************************************************************************/
`timescale 1ns/1ps

module  axi_hp_abort(
    input             hclk,
    input             hrst,  // just disables processing inputs
    input             abort,
    output            busy, // should disable control of afi_wvalid, afi_awid
    output reg        done,
    input             afi_awvalid, // afi_awready is supposed to be always on when afi_awvalid (caller uses fifo counetrs) ?
    input             afi_awready, //
    input      [ 5:0] afi_awid, 
    input       [3:0] afi_awlen, 
    input             afi_wvalid_in,
    input             afi_wready,
    output            afi_wvalid,
    output reg [ 5:0] afi_wid,
    input             afi_arvalid,
    input             afi_arready,
    input      [ 3:0] afi_arlen,
    input             afi_rready_in,
    input             afi_rvalid,
    output            afi_rready,
    output            afi_wlast,
// TODO:  Try to resolve problems when afi_racount, afi_wacount afi_wcount do not match expected
    input      [ 2:0] afi_racount,
    input      [ 7:0] afi_rcount,
    input      [ 5:0] afi_wacount,
    input      [ 7:0] afi_wcount,
    output reg        dirty,     // single bit to be sampled in different clock domain to see if flushing is needed
    output reg        axi_mismatch,   // calculated as 'dirty' but axi hp counters are 0
    output     [21:0] debug
);
    reg               busy_r;
    wire              done_w = busy_r && !dirty ;
    reg         [3:0] aw_lengths_ram[0:31];
    reg         [4:0] aw_lengths_waddr = 0;
    reg         [4:0] aw_lengths_raddr = 0;
    reg         [5:0] aw_count = 0;
    reg         [7:0] w_count = 0;
    reg         [7:0] r_count = 0;
    reg               adav = 0;
    wire              arwr = !hrst && afi_arvalid && afi_arready;
    wire              drd =  !hrst && afi_rvalid && afi_rready_in;
    wire              awr = !hrst && afi_awvalid && afi_awready;
    reg               ard_r = 0; // additional length read if not much data
    wire              ard = adav && ((|w_count[7:4]) || ard_r);
    wire              wwr = !hrst && afi_wready && afi_wvalid_in;
    reg               afi_rready_r;
    reg               afi_wlast_r; // wait one cycle after last in each burst (just to ease timing)
    reg               busy_aborting; // actually aborting
    wire              reset_counters = busy_r && !busy_aborting;
    assign busy = busy_r;
    
    assign afi_rready = busy_aborting && (|r_count) && ((|afi_rcount[7:1]) || (!afi_rready_r &&  afi_rcount[0]));
    assign afi_wlast =  busy_aborting && adav && (w_count[3:0] == aw_lengths_ram[aw_lengths_raddr]);
    assign afi_wvalid = busy_aborting && adav && !afi_wlast_r;
    assign debug = {aw_count[5:0], w_count[7:0], r_count[7:0]};
    
    // Watch for transactios performed by others (and this one too)
    always @ (posedge hclk) begin
        // read channel
        if (reset_counters) r_count <= 0;
        else if (drd)
             if (arwr)      r_count <= r_count + {4'b0, afi_arlen};
             else           r_count <= r_count - 1;
        else
             if (arwr)      r_count <= w_count  + {4'b0, afi_arlen} + 1;
    
        // write channel
    
        if (awr) afi_wid <= afi_awid; // one command is supposed to use just one awid/wid

        if (awr) aw_lengths_ram [aw_lengths_waddr] <= afi_awlen;

        if (reset_counters) aw_lengths_waddr <= 0;
        else if (awr)       aw_lengths_waddr <= aw_lengths_waddr + 1;

        if (reset_counters) aw_lengths_raddr <= 0;
        else if (ard)       aw_lengths_raddr <= aw_lengths_raddr + 1;
        
        if (reset_counters)    aw_count <= 0;
        else if ( awr && !ard) aw_count <= aw_count + 1;
        else if (!awr &&  ard) aw_count <= aw_count - 1;
        
        adav <= !reset_counters && (|aw_count[5:1]) || ((awr || aw_count[0]) && !ard) || (awr && aw_count[0]);
        
        ard_r <= !ard && adav && (w_count[3:0] > aw_lengths_ram[aw_lengths_raddr]);
        
        if (reset_counters) w_count <= 0;
        else if (wwr)
             if (ard)       w_count <= w_count - {4'b0, aw_lengths_ram[aw_lengths_raddr]};
             else           w_count <= w_count + 1;
        else
             if (ard)       w_count <= w_count - {4'b0, aw_lengths_ram[aw_lengths_raddr]} - 1;
        
        dirty <= (|r_count) || (|aw_count); // assuming w_count can never be non-zero? - no
    end
    
    // flushing part
    always @ (posedge hclk) begin
    
        if      (abort)  busy_r <= 1;
        else if (done_w) busy_r <= 0;

        if      (abort && ((|afi_racount) || (|afi_rcount) || (|afi_wacount) || (|afi_wcount)))  busy_aborting <= 1;
        else if (done_w) busy_aborting <= 0;

        
        done <=  done_w;
        afi_rready_r <= afi_rready;
        afi_wlast_r <=  afi_wlast;
        
        axi_mismatch <= busy && !busy_aborting && dirty;  //  
    end 
    

endmodule

