// Copyright © 2019-2023
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

`include "vortex_afu.vh"

module VX_afu_wrap #( 
	  parameter C_S_AXI_CTRL_ADDR_WIDTH = 8,
	  parameter C_S_AXI_CTRL_DATA_WIDTH	= 32,
	  parameter C_M_AXI_MEM_ID_WIDTH    = 16,
	  parameter C_M_AXI_MEM_ADDR_WIDTH  = 32,
	  parameter C_M_AXI_MEM_DATA_WIDTH  = 512
) (
    // System signals
    input wire ap_clk,
    input wire ap_rst_n,

    // AXI4 master interface
	`REPEAT (`M_AXI_MEM_NUM_BANKS, GEN_AXI_MEM, REPEAT_COMMA),

    // AXI4-Lite slave interface
    input  wire                                 s_axi_ctrl_awvalid,
    output wire                                 s_axi_ctrl_awready,
    input  wire [C_S_AXI_CTRL_ADDR_WIDTH-1:0]   s_axi_ctrl_awaddr,
    input  wire                                 s_axi_ctrl_wvalid,
    output wire                                 s_axi_ctrl_wready,
    input  wire [C_S_AXI_CTRL_DATA_WIDTH-1:0]   s_axi_ctrl_wdata,
    input  wire [C_S_AXI_CTRL_DATA_WIDTH/8-1:0] s_axi_ctrl_wstrb,
    input  wire                                 s_axi_ctrl_arvalid,
    output wire                                 s_axi_ctrl_arready,
    input  wire [C_S_AXI_CTRL_ADDR_WIDTH-1:0]   s_axi_ctrl_araddr,
    output wire                                 s_axi_ctrl_rvalid,
    input  wire                                 s_axi_ctrl_rready,
    output wire [C_S_AXI_CTRL_DATA_WIDTH-1:0]   s_axi_ctrl_rdata,
    output wire [1:0]                           s_axi_ctrl_rresp,
    output wire                                 s_axi_ctrl_bvalid,
    input  wire                                 s_axi_ctrl_bready,
    output wire [1:0]                           s_axi_ctrl_bresp,
    
    output wire                                 interrupt 
);
	localparam C_M_AXI_MEM_NUM_BANKS = `M_AXI_MEM_NUM_BANKS;

	localparam STATE_IDLE = 0;
    localparam STATE_RUN  = 1;

	wire                                 m_axi_mem_awvalid_a [C_M_AXI_MEM_NUM_BANKS];
    wire                                 m_axi_mem_awready_a [C_M_AXI_MEM_NUM_BANKS];
    wire [C_M_AXI_MEM_ADDR_WIDTH-1:0]    m_axi_mem_awaddr_a [C_M_AXI_MEM_NUM_BANKS];
    wire [C_M_AXI_MEM_ID_WIDTH-1:0]      m_axi_mem_awid_a [C_M_AXI_MEM_NUM_BANKS];
    wire [7:0]                           m_axi_mem_awlen_a [C_M_AXI_MEM_NUM_BANKS];
    wire                                 m_axi_mem_wvalid_a [C_M_AXI_MEM_NUM_BANKS];
    wire                                 m_axi_mem_wready_a [C_M_AXI_MEM_NUM_BANKS];
    wire [C_M_AXI_MEM_DATA_WIDTH-1:0]    m_axi_mem_wdata_a [C_M_AXI_MEM_NUM_BANKS];
    wire [C_M_AXI_MEM_DATA_WIDTH/8-1:0]  m_axi_mem_wstrb_a [C_M_AXI_MEM_NUM_BANKS];
    wire                                 m_axi_mem_wlast_a [C_M_AXI_MEM_NUM_BANKS];	
    wire                                 m_axi_mem_bvalid_a [C_M_AXI_MEM_NUM_BANKS];
    wire                                 m_axi_mem_bready_a [C_M_AXI_MEM_NUM_BANKS];
    wire [C_M_AXI_MEM_ID_WIDTH-1:0]      m_axi_mem_bid_a [C_M_AXI_MEM_NUM_BANKS];
    wire [1:0]                           m_axi_mem_bresp_a [C_M_AXI_MEM_NUM_BANKS];
    wire                                 m_axi_mem_arvalid_a [C_M_AXI_MEM_NUM_BANKS];
    wire                                 m_axi_mem_arready_a [C_M_AXI_MEM_NUM_BANKS];
    wire [C_M_AXI_MEM_ADDR_WIDTH-1:0]    m_axi_mem_araddr_a [C_M_AXI_MEM_NUM_BANKS];
    wire [C_M_AXI_MEM_ID_WIDTH-1:0]      m_axi_mem_arid_a [C_M_AXI_MEM_NUM_BANKS];
    wire [7:0]                           m_axi_mem_arlen_a [C_M_AXI_MEM_NUM_BANKS];
    wire                                 m_axi_mem_rvalid_a [C_M_AXI_MEM_NUM_BANKS];
    wire                                 m_axi_mem_rready_a [C_M_AXI_MEM_NUM_BANKS];
    wire [C_M_AXI_MEM_DATA_WIDTH-1:0]    m_axi_mem_rdata_a [C_M_AXI_MEM_NUM_BANKS];
    wire                                 m_axi_mem_rlast_a [C_M_AXI_MEM_NUM_BANKS];
    wire [C_M_AXI_MEM_ID_WIDTH-1:0]      m_axi_mem_rid_a [C_M_AXI_MEM_NUM_BANKS];
    wire [1:0]                           m_axi_mem_rresp_a [C_M_AXI_MEM_NUM_BANKS];

	// convert memory interface to array
	`REPEAT (`M_AXI_MEM_NUM_BANKS, AXI_MEM_TO_ARRAY, REPEAT_SEMICOLON);

	wire clk = ap_clk;
	wire reset = ~ap_rst_n;

	reg [`CLOG2(`RESET_DELAY+1)-1:0] vx_reset_ctr;
	reg [15:0] vx_pending_writes;
	reg vx_busy_wait;
	reg vx_running;
	
	wire vx_busy;

	wire [63:0] mem_base [C_M_AXI_MEM_NUM_BANKS];        

	wire                          dcr_wr_valid;
	wire [`VX_DCR_ADDR_WIDTH-1:0] dcr_wr_addr;
	wire [`VX_DCR_DATA_WIDTH-1:0] dcr_wr_data;

	reg state;

	wire ap_reset;
	wire ap_start;
	wire ap_idle  = ~vx_running;
	wire ap_done  = ~(state == STATE_RUN || vx_pending_writes != 0);
	wire ap_ready = 1'b1;

`ifdef SCOPE
	wire scope_bus_in;
	wire scope_bus_out;
  	wire scope_reset = reset; 
`endif

	always @(posedge ap_clk) begin
		if (reset || ap_reset) begin
			state <= STATE_IDLE;
			vx_busy_wait  <= 0;
			vx_running    <= 0;
		end else begin
			case (state)
			STATE_IDLE: begin
				if (ap_start) begin	
				`ifdef DBG_TRACE_AFU
					`TRACE(2, ("%d: STATE RUN\n", $time));
				`endif				
					state <= STATE_RUN;
					vx_running <= 0;
				end
			end
			STATE_RUN: begin			
				if (vx_running) begin
					if (vx_busy_wait) begin
						// wait until processor goes busy
						if (vx_busy) begin
							vx_busy_wait <= 0;
						end
					end else begin
						// wait until the processor is not busy
						if (~vx_busy) begin
							state <= STATE_IDLE;
						`ifdef DBG_TRACE_AFU
							`TRACE(2, ("%d: AFU: End execution\n", $time));
							`TRACE(2, ("%d: STATE IDLE\n", $time));
						`endif
						end
					end
				end else begin
					// wait until the reset sequence is complete
					if (vx_reset_ctr == (`RESET_DELAY-1)) begin
					`ifdef DBG_TRACE_AFU
						`TRACE(2, ("%d: AFU: Begin execution\n", $time));
					`endif
						vx_running    <= 1;					
						vx_busy_wait  <= 1;
					end
				end
			end
			endcase
		end
	end

	reg m_axi_mem_wfire;
	reg m_axi_mem_bfire;

	always @(*) begin
		m_axi_mem_wfire = 0;
		m_axi_mem_bfire = 0;
		for (integer i = 0; i < C_M_AXI_MEM_NUM_BANKS; ++i) begin
			m_axi_mem_wfire |= m_axi_mem_wvalid_a[i] && m_axi_mem_wready_a[i];
			m_axi_mem_bfire |= m_axi_mem_bvalid_a[i] && m_axi_mem_bready_a[i];
		end
	end

	always @(posedge ap_clk) begin
		if (reset || ap_reset) begin
			vx_pending_writes <= '0;
		end else begin
			if (m_axi_mem_wfire && ~m_axi_mem_bfire)
				vx_pending_writes <= vx_pending_writes + 1;
			if (~m_axi_mem_wfire && m_axi_mem_bfire)
				vx_pending_writes <= vx_pending_writes - 1;
		end
	end

	always @(posedge ap_clk) begin
		if (state == STATE_RUN) begin
			vx_reset_ctr <= vx_reset_ctr + 1;			
		end else begin
			vx_reset_ctr <= '0;
		end
	end

	VX_afu_ctrl #(
		.AXI_ADDR_WIDTH (C_S_AXI_CTRL_ADDR_WIDTH),
		.AXI_DATA_WIDTH (C_S_AXI_CTRL_DATA_WIDTH),
		.AXI_NUM_BANKS  (C_M_AXI_MEM_NUM_BANKS)
	) afu_ctrl (
		.clk       		(ap_clk),
		.reset     		(reset || ap_reset),	
		.clk_en         (1'b1),
		
		.s_axi_awvalid  (s_axi_ctrl_awvalid),
		.s_axi_awready  (s_axi_ctrl_awready),
		.s_axi_awaddr   (s_axi_ctrl_awaddr),
		.s_axi_wvalid   (s_axi_ctrl_wvalid),
		.s_axi_wready   (s_axi_ctrl_wready),
		.s_axi_wdata    (s_axi_ctrl_wdata),
		.s_axi_wstrb    (s_axi_ctrl_wstrb),
		.s_axi_arvalid  (s_axi_ctrl_arvalid),
		.s_axi_arready  (s_axi_ctrl_arready),
		.s_axi_araddr   (s_axi_ctrl_araddr),
		.s_axi_rvalid   (s_axi_ctrl_rvalid),
		.s_axi_rready   (s_axi_ctrl_rready),
		.s_axi_rdata    (s_axi_ctrl_rdata),
		.s_axi_rresp    (s_axi_ctrl_rresp),
		.s_axi_bvalid   (s_axi_ctrl_bvalid),
		.s_axi_bready   (s_axi_ctrl_bready),
		.s_axi_bresp    (s_axi_ctrl_bresp),

		.ap_reset  		(ap_reset),
		.ap_start  		(ap_start),
		.ap_done     	(ap_done),
		.ap_ready       (ap_ready),
		.ap_idle     	(ap_idle),
		.interrupt 		(interrupt),

	`ifdef SCOPE
		.scope_bus_in   (scope_bus_out),	
		.scope_bus_out  (scope_bus_in),
	`endif	

		.mem_base       (mem_base),

		.dcr_wr_valid	(dcr_wr_valid),
		.dcr_wr_addr	(dcr_wr_addr),
		.dcr_wr_data	(dcr_wr_data)
	);

	wire [`XLEN-1:0] m_axi_mem_awaddr_w [C_M_AXI_MEM_NUM_BANKS];
	wire [`XLEN-1:0] m_axi_mem_araddr_w [C_M_AXI_MEM_NUM_BANKS];

	for (genvar i = 0; i < C_M_AXI_MEM_NUM_BANKS; ++i) begin
		assign m_axi_mem_awaddr_a[i] = C_M_AXI_MEM_ADDR_WIDTH'(m_axi_mem_awaddr_w[i]) + C_M_AXI_MEM_ADDR_WIDTH'(mem_base[i]);
		assign m_axi_mem_araddr_a[i] = C_M_AXI_MEM_ADDR_WIDTH'(m_axi_mem_araddr_w[i]) + C_M_AXI_MEM_ADDR_WIDTH'(mem_base[i]);
	end

	`SCOPE_IO_SWITCH (2)

	Vortex_axi #(
		.AXI_DATA_WIDTH (C_M_AXI_MEM_DATA_WIDTH),
		.AXI_ADDR_WIDTH (`XLEN),
		.AXI_TID_WIDTH  (C_M_AXI_MEM_ID_WIDTH),
		.AXI_NUM_BANKS  (C_M_AXI_MEM_NUM_BANKS)
	) vortex_axi (
		`SCOPE_IO_BIND  (1)

		.clk			(ap_clk),
		.reset			(reset || ap_reset || ~vx_running),
		
		.m_axi_awvalid	(m_axi_mem_awvalid_a),
		.m_axi_awready	(m_axi_mem_awready_a),
		.m_axi_awaddr	(m_axi_mem_awaddr_w),
		.m_axi_awid		(m_axi_mem_awid_a),
		.m_axi_awlen    (m_axi_mem_awlen_a),
		`UNUSED_PIN (m_axi_awsize),
		`UNUSED_PIN (m_axi_awburst),
		`UNUSED_PIN (m_axi_awlock),
		`UNUSED_PIN (m_axi_awcache),
		`UNUSED_PIN (m_axi_awprot),
		`UNUSED_PIN (m_axi_awqos),		  
    	`UNUSED_PIN (m_axi_awregion),

		.m_axi_wvalid	(m_axi_mem_wvalid_a),
		.m_axi_wready	(m_axi_mem_wready_a),
		.m_axi_wdata	(m_axi_mem_wdata_a),
		.m_axi_wstrb	(m_axi_mem_wstrb_a),
		.m_axi_wlast	(m_axi_mem_wlast_a),

		.m_axi_bvalid	(m_axi_mem_bvalid_a),
		.m_axi_bready	(m_axi_mem_bready_a),
		.m_axi_bid		(m_axi_mem_bid_a),
		.m_axi_bresp	(m_axi_mem_bresp_a),		

		.m_axi_arvalid	(m_axi_mem_arvalid_a),
		.m_axi_arready	(m_axi_mem_arready_a),
		.m_axi_araddr	(m_axi_mem_araddr_w),
		.m_axi_arid		(m_axi_mem_arid_a),
		.m_axi_arlen	(m_axi_mem_arlen_a),
		`UNUSED_PIN (m_axi_arsize),
		`UNUSED_PIN (m_axi_arburst),
		`UNUSED_PIN (m_axi_arlock),
		`UNUSED_PIN (m_axi_arcache),
		`UNUSED_PIN (m_axi_arprot),
		`UNUSED_PIN (m_axi_arqos),		
        `UNUSED_PIN (m_axi_arregion),

		.m_axi_rvalid	(m_axi_mem_rvalid_a),
		.m_axi_rready	(m_axi_mem_rready_a),
		.m_axi_rdata	(m_axi_mem_rdata_a),
		.m_axi_rlast	(m_axi_mem_rlast_a),
		.m_axi_rid    	(m_axi_mem_rid_a),
		.m_axi_rresp	(m_axi_mem_rresp_a),

		.dcr_wr_valid	(dcr_wr_valid),
		.dcr_wr_addr	(dcr_wr_addr),
		.dcr_wr_data	(dcr_wr_data),

		.busy			(vx_busy)
	);

    // SCOPE //////////////////////////////////////////////////////////////////////

`ifdef DBG_SCOPE_AFU
`ifdef SCOPE
	`define TRIGGERS { \
		reset, \
		ap_start, \
		ap_done, \
		ap_idle, \
		interrupt, \
		vx_busy_wait, \
		vx_busy, \
		vx_running \
	}

	`define PROBES { \
		vx_pending_writes \
	}

    VX_scope_tap #(
        .SCOPE_ID (0),
        .TRIGGERW ($bits(`TRIGGERS)),
        .PROBEW   ($bits(`PROBES))
    ) scope_tap (
        .clk(clk),
        .reset(scope_reset_w[0]),
        .start(1'b0),
        .stop(1'b0),
        .triggers(`TRIGGERS),
        .probes(`PROBES),
        .bus_in(scope_bus_in_w[0]),
        .bus_out(scope_bus_out_w[0])
    );
`endif
`ifdef CHIPSCOPE
    ila_afu ila_afu_inst (
      	.clk (ap_clk),
		.probe0 ({
        	ap_start,
        	ap_done,
			ap_idle,
			interrupt
		}),
		.probe1 ({
        	vx_pending_writes,
			vx_busy_wait,
			vx_busy,
			vx_running
		})
    );
`endif
`else
    `SCOPE_IO_UNUSED_W(0)
`endif

`ifdef SIMULATION
`ifndef VERILATOR
	// disable assertions until full reset
	reg [`CLOG2(`RESET_DELAY+1)-1:0] assert_delay_ctr;
	reg assert_enabled;
	initial begin
		$assertoff(0, vortex_axi);  
	end	
	always @(posedge ap_clk) begin
		if (reset) begin
			assert_delay_ctr <= '0;
			assert_enabled   <= 0;
		end else begin			
			if (~assert_enabled) begin
				if (assert_delay_ctr == (`RESET_DELAY-1)) begin
					assert_enabled <= 1;
					$asserton(0, vortex_axi); // enable assertions
				end else begin
					assert_delay_ctr <= assert_delay_ctr + 1;
				end
			end
		end
	end
`endif
`endif

`ifdef DBG_TRACE_AFU
    always @(posedge ap_clk) begin
		for (integer i = 0; i < C_M_AXI_MEM_NUM_BANKS; ++i) begin
			if (m_axi_mem_awvalid_a[i] && m_axi_mem_awready_a[i]) begin
				`TRACE(2, ("%d: AFU Wr Req [%0d]: addr=0x%0h, tag=0x%0h\n", $time, i, m_axi_mem_awaddr_a[i], m_axi_mem_awid_a[i]));                
			end
			if (m_axi_mem_wvalid_a[i] && m_axi_mem_wready_a[i]) begin
				`TRACE(2, ("%d: AFU Wr Req [%0d]: data=0x%0h\n", $time, i, m_axi_mem_wdata_a[i]));                
			end
			if (m_axi_mem_arvalid_a[i] && m_axi_mem_arready_a[i]) begin   
				`TRACE(2, ("%d: AFU Rd Req [%0d]: addr=0x%0h, tag=0x%0h\n", $time, i, m_axi_mem_araddr_a[i], m_axi_mem_arid_a[i]));
			end
			if (m_axi_mem_rvalid_a[i] && m_axi_mem_rready_a[i]) begin
				`TRACE(2, ("%d: AVS Rd Rsp [%0d]: data=0x%0h, tag=0x%0h\n", $time, i, m_axi_mem_rdata_a[i], m_axi_mem_rid_a[i]));
			end
		end
  	end
`endif
	
endmodule
