/*!
 * @file x393_tasks01.vh
 * @date 2015-02-07  
 * @author Andrey Filippov     
 *
 * @brief Simulation tasks for the x393 (low level)
 *
 * @copyright Copyright (c) 2015 Elphel, Inc.
 *
 * <b>License:</b>
 *
 * x393_tasks01.vh 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.
 *
 * x393_tasks01.vh 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/> .
 *
 * Additional permission under GNU GPL version 3 section 7:
 * If you modify this Program, or any covered work, by linking or combining it
 * with independent modules provided by the FPGA vendor only (this permission
 * does not extend to any 3-rd party modules, "soft cores" or macros) under
 * different license terms solely for the purpose of generating binary "bitstream"
 * files and/or simulating the code, the copyright holders of this Program give
 * you the right to distribute the covered work without those independent modules
 * as long as the source code for them is available from the FPGA vendor free of
 * charge, and there is no dependence on any encrypted modules for simulating of
 * the combined code. This permission applies to you if the distributed code
 * contains all the components and scripts required to completely simulate it
 * with at least one of the Free Software programs.
 */
 // Low-level tasks 
// alternative way to check for empty read queue (without a separate counter)

    task   write_contol_register;
        input [29:0] reg_addr;
//        input integer reg_addr;
        input [31:0] data;
        begin
            axi_write_single_w(CONTROL_ADDR+reg_addr, data);
        end
    endtask   

    task   read_contol_register;
        input [29:0] reg_addr;
        begin
            read_and_wait_w(CONTROL_RBACK_ADDR+reg_addr);
        end
    endtask   

    task wait_read_queue_empty;
        begin
        wait (~rvalid && rready && (rid==LAST_ARID)); // nothing left in read queue?   
        SIMUL_AXI_FULL<=1'b0;
        end
    endtask
    task axi_set_rd_lag;
        input [3:0] lag;
        begin
            @(posedge CLK);
            RD_LAG <= lag;
        end
    endtask

    task axi_set_b_lag;
        input [3:0] lag;
        begin
            @(posedge CLK);
            B_LAG <= lag;
        end
    endtask

    task read_and_wait_w;
    input [29:0] address;
        begin
            read_and_wait ({address,2'b0});
        end
    endtask


    task read_and_wait;
        input      [31:0] address;
        begin
            IRQ_EN = 0;
            wait (CLK);
            while (!MAIN_GO) begin
                wait (!CLK);
                wait (CLK);
            end
            axi_read_addr_inner(
                GLOBAL_READ_ID,    // id
                address & 32'hfffffffc, // addr
                4'h0, // len - single
                1     // burst type - increment
                );
            GLOBAL_READ_ID <= GLOBAL_READ_ID+1;
            wait (!CLK && rvalid && rready);
            wait (CLK);
            registered_rdata <= rdata;
            wait (!CLK); // registered_rdata should be valid on exit
            IRQ_EN = 1;
            if (IRQS) begin
                @(posedge CLK);
                @(negedge CLK);
            end
        end
    endtask
    
    task axi_write_single_w; // address in dwords
        input [29:0] address;
        input [31:0] data;
        begin
`ifdef DEBUG_WR_SINGLE
          $display("axi_write_single_w %h:%h @ %t",address,data,$time);
`endif                
            axi_write_single ({address,2'b0},data);
        end
    endtask

    task axi_write_single; // address in bytes, not words
        input [31:0] address;
        input [31:0] data;
        begin
          axi_write_addr_data(
                    GLOBAL_WRITE_ID,    // id
//                    address << 2, // addr
                    address & 32'hfffffffc, // addr
                    data,
                    4'h0, // len - single
                    1,    // burst type - increment
                    1'b1, // data_en
                    4'hf, // wstrb
                    1'b1 // last
                );
          GLOBAL_WRITE_ID <= GLOBAL_WRITE_ID+1;
          #0.1; // without this delay axi_write_addr_data() used old value of GLOBAL_WRITE_ID
        end
    endtask
   
    task axi_write_addr_data;
        input [11:0] id;
        input [31:0] addr;
        input [31:0] data;
        input [ 3:0] len;
        input [ 1:0] burst;
        input        data_en; // if 0 - do not send data, only address
        input [ 3:0] wstrb;
        input        last;
        begin
            IRQ_EN = 0;
            wait (CLK);
            while (!MAIN_GO) begin
                wait (!CLK);
                wait (CLK);
            end
            axi_write_addr_data_inner (id, addr, data, len, burst, data_en, wstrb, last);
            IRQ_EN = 1;
            if (IRQS) begin
                @(posedge CLK);
                @(negedge CLK);
            end
        end
    endtask

    task axi_write_data;
        input [11:0] id;
        input [31:0] data;
        input [ 3:0] wstrb;
        input        last;
        begin
            IRQ_EN = 0;
            wait (CLK);
            while (!MAIN_GO) begin
                wait (!CLK);
                wait (CLK);
            end
            axi_write_data_inner (id, data, wstrb, last);
            IRQ_EN = 1;
            if (IRQS) begin
                @(posedge CLK);
                @(negedge CLK);
            end
        end
    endtask

// Tasks called from ISR

    task   read_contol_register_irq;
        input  [29:0] reg_addr;
        output [31:0] rslt;
        begin
            read_and_wait_w_irq(CONTROL_RBACK_ADDR+reg_addr, rslt);
        end
    endtask   

 task read_status_irq;
    input [STATUS_DEPTH-1:0] address;
    output [31:0] rslt;
    begin
        read_and_wait_w_irq(STATUS_ADDR + address , rslt);
    end
 endtask

    task read_and_wait_w_irq;
        input [29:0] address;
        output [31:0] rslt;
        begin
            read_and_wait_irq ({address,2'b0},rslt);
        end
    endtask

    task read_and_wait_irq;
        input      [31:0] address;
        output reg [31:0] rslt;
        begin
            axi_read_addr_irq(
                GLOBAL_READ_ID,    // id
                address & 32'hfffffffc, // addr
                4'h0, // len - single
                1     // burst type - increment
                );
            GLOBAL_READ_ID <= GLOBAL_READ_ID+1;
            wait (!CLK && rvalid && rready);
            wait (CLK);
            rslt <= rdata;
            wait (!CLK); // registered_rdata should be valid on exit
        end
    endtask

    task axi_read_addr_irq; // called ferom the main loop, not from interrupts
        input [11:0] id;
        input [31:0] addr;
        input [ 3:0] len;
        input [ 1:0] burst;
        begin
//            IRQ_EN = 0;
//            wait (CLK);
//            while (!MAIN_GO) begin
//                wait (!CLK);
//                wait (CLK);
//            end
            axi_read_addr_inner (id, addr, len, burst);
//            IRQ_EN = 1;
        end
    endtask

    task   write_contol_register_irq;
        input [29:0] reg_addr;
//        input integer reg_addr;
        input [31:0] data;
        begin
            axi_write_single_w_irq(CONTROL_ADDR+reg_addr, data);
        end
    endtask   

    task axi_write_single_w_irq; // address in dwords
        input [29:0] address;
        input [31:0] data;
        begin
`ifdef DEBUG_WR_SINGLE
          $display("axi_write_single_w %h:%h @ %t",address,data,$time);
`endif                
            axi_write_single_irq ({address,2'b0},data);
        end
    endtask

    task axi_write_single_irq; // address in bytes, not words
        input [31:0] address;
        input [31:0] data;
        begin
          axi_write_addr_data_irq(
                    GLOBAL_WRITE_ID,    // id
                    address & 32'hfffffffc, // addr
                    data,
                    4'h0, // len - single
                    1,    // burst type - increment
                    1'b1, // data_en
                    4'hf, // wstrb
                    1'b1 // last
                );
          GLOBAL_WRITE_ID <= GLOBAL_WRITE_ID+1;
          #0.1; // without this delay axi_write_addr_data() used old value of GLOBAL_WRITE_ID
        end
    endtask

    task axi_write_addr_data_irq;
        input [11:0] id;
        input [31:0] addr;
        input [31:0] data;
        input [ 3:0] len;
        input [ 1:0] burst;
        input        data_en; // if 0 - do not send data, only address
        input [ 3:0] wstrb;
        input        last;
        begin
//            IRQ_EN = 0;
//            wait (CLK);
//            while (!MAIN_GO) begin
//                wait (!CLK);
//                wait (CLK);
//            end
            axi_write_addr_data_inner (id, addr, data, len, burst, data_en, wstrb, last);
//            IRQ_EN = 1;
        end
    endtask


// Tasks common for main ind ISR

    task axi_write_addr_data_inner;
        input [11:0] id;
        input [31:0] addr;
        input [31:0] data;
        input [ 3:0] len;
        input [ 1:0] burst;
        input        data_en; // if 0 - do not send data, only address
        input [ 3:0] wstrb;
        input        last;
        reg          data_sent;
//        wire         data_sent_d;
//        assign #(.1) data_sent_d= data_sent;
        begin
            wait (!CLK && AW_READY);
            AWID_IN_r    <= id;
            AWADDR_IN_r  <= addr;
            AWLEN_IN_r   <= len;
            AWSIZE_IN_r  <= 2'b10;
            AWBURST_IN_r <= burst;
            AW_SET_CMD_r <= 1'b1;
            if (data_en && W_READY) begin
                WID_IN_r <= id;
                WDATA_IN_r <= data;
                WSTRB_IN_r <= wstrb;
                WLAST_IN_r <= last;
                W_SET_CMD_r <= 1'b1; 
                data_sent <= 1'b1;
            end else begin
                data_sent <= 1'b0;
            end
            DEBUG1 <=1'b1;
            wait (CLK);
            DEBUG1 <=1'b0;
            AWID_IN_r    <= 'hz;
            AWADDR_IN_r  <= 'hz;
            AWLEN_IN_r   <= 'hz;
            AWSIZE_IN_r  <= 'hz;
            AWBURST_IN_r <= 'hz;
            AW_SET_CMD_r <= 1'b0;
            DEBUG2 <=1'b1;
            if (data_sent) begin
                WID_IN_r    <= 'hz;
                WDATA_IN_r  <= 'hz;
                WSTRB_IN_r  <= 'hz;
                WLAST_IN_r  <= 'hz;
                W_SET_CMD_r <= 1'b0; 
            end
// Now sent data if it was not sent simultaneously with the address
            if (data_en && !data_sent) begin
                DEBUG3 <=1'b1;
                wait (!CLK && W_READY);
                DEBUG3 <=1'b0;
                WID_IN_r    <= id;
                WDATA_IN_r  <= data;
                WSTRB_IN_r  <= wstrb;
                WLAST_IN_r  <= last;
                W_SET_CMD_r <= 1'b1; 
                wait (CLK);
                DEBUG3 <=1'bx;
                WID_IN_r    <= 'hz;
                WDATA_IN_r  <= 'hz;
                WSTRB_IN_r  <= 'hz;
                WLAST_IN_r  <= 'hz;
                W_SET_CMD_r <= 1'b0; 
            end
            DEBUG2 <=1'b0;
            #0.1;
            data_sent <= 1'b0;
            #0.1;
        end
    endtask

    task axi_write_data_inner;
        input [11:0] id;
        input [31:0] data;
        input [ 3:0] wstrb;
        input        last;
        begin
            wait (!CLK && W_READY);
            WID_IN_r    <= id;
            WDATA_IN_r  <= data;
            WSTRB_IN_r  <= wstrb;
            WLAST_IN_r  <= last;
            W_SET_CMD_r <= 1'b1; 
            wait (CLK);
            WID_IN_r    <= 12'hz;
            WDATA_IN_r  <= 'hz;
            WSTRB_IN_r  <= 4'hz;
            WLAST_IN_r  <= 1'bz;
            W_SET_CMD_r <= 1'b0;
            #0.1;
        end
    endtask


    task axi_read_addr_inner; // regardless of main loop/interrupts
        input [11:0] id;
        input [31:0] addr;
        input [ 3:0] len;
        input [ 1:0] burst;
        begin
            wait (!CLK && AR_READY);
            ARID_IN_r    <= id;
            ARADDR_IN_r  <= addr;
            ARLEN_IN_r   <= len;
            ARSIZE_IN_r  <= 2'b10;
            ARBURST_IN_r <= burst;
            AR_SET_CMD_r <= 1'b1;
            wait (CLK);
            ARID_IN_r    <= 12'hz;
            ARADDR_IN_r  <= 'hz;
            ARLEN_IN_r   <= 4'hz;
            ARSIZE_IN_r  <= 2'hz;
            ARBURST_IN_r <= 2'hz;
            AR_SET_CMD_r <= 1'b0;
            LAST_ARID <= id;
            NUM_WORDS_EXPECTED <= NUM_WORDS_EXPECTED+len+1;
        end
    endtask

