// 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_ctrl #(
    parameter AXI_ADDR_WIDTH = 8,
    parameter AXI_DATA_WIDTH = 32,
    parameter AXI_NUM_BANKS  = 1
) (
    // axi4 lite slave signals
    input  wire                         clk,
    input  wire                         reset,
    input  wire                         clk_en,
    
    input  wire                         s_axi_awvalid,
    input  wire [AXI_ADDR_WIDTH-1:0]    s_axi_awaddr,    
    output wire                         s_axi_awready,

    input  wire                         s_axi_wvalid,
    input  wire [AXI_DATA_WIDTH-1:0]    s_axi_wdata,
    input  wire [AXI_DATA_WIDTH/8-1:0]  s_axi_wstrb,    
    output wire                         s_axi_wready,

    output wire                         s_axi_bvalid,
    output wire [1:0]                   s_axi_bresp,    
    input  wire                         s_axi_bready,

    input  wire                         s_axi_arvalid,
    input  wire [AXI_ADDR_WIDTH-1:0]    s_axi_araddr,    
    output wire                         s_axi_arready,

    output wire                         s_axi_rvalid,
    output wire [AXI_DATA_WIDTH-1:0]    s_axi_rdata,
    output wire [1:0]                   s_axi_rresp,    
    input  wire                         s_axi_rready,    
    
    output wire                         ap_reset,
    output wire                         ap_start,
    input  wire                         ap_done,
    input  wire                         ap_ready,
    input  wire                         ap_idle,  
    output wire                         interrupt,

`ifdef SCOPE
    input wire                          scope_bus_in,
    output wire                         scope_bus_out,
`endif

    output wire [63:0]                  mem_base [AXI_NUM_BANKS],

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

    // Address Info
    // 0x00 : Control signals
    //        bit 0  - ap_start (Read/Write/COH)
    //        bit 1  - ap_done (Read/COR)
    //        bit 2  - ap_idle (Read)
    //        bit 3  - ap_ready (Read)
    //        bit 4  - ap_reset (Write)
    //        bit 7  - auto_restart (Read/Write)
    //        others - reserved
    // 0x04 : Global Interrupt Enable Register
    //        bit 0  - Global Interrupt Enable (Read/Write)
    //        others - reserved
    // 0x08 : IP Interrupt Enable Register (Read/Write)
    //        bit 0  - Channel 0 (ap_done)
    //        bit 1  - Channel 1 (ap_ready)
    //        others - reserved
    // 0x0c : IP Interrupt Status Register (Read/TOW)
    //        bit 0  - Channel 0 (ap_done)
    //        bit 1  - Channel 1 (ap_ready)
    //        others - reserved
    // 0x10 : Low 32-bit Data signal of DEV_CAPS
    // 0x14 : High 32-bit Data signal of DEV_CAPS
    // 0x18 : Control signal of DEV_CAPS
    // 0x1C : Low 32-bit Data signal of ISA_CAPS
    // 0x20 : High 32-bit Data signal of ISA_CAPS
    // 0x24 : Control signal of ISA_CAPS
    // 0x28 : Low 32-bit Data signal of DCR
    // 0x2C : High 32-bit Data signal of DCR
    // 0x30 : Control signal of DCR
    // 0x34 : Low 32-bit Data signal of SCP
    // 0x38 : High 32-bit Data signal of SCP
    // 0x3C : Control signal of SCP
    // 0x40 : Low 32-bit Data signal of MEM
    // 0x44 : High 32-bit Data signal of MEM
    // 0x48 : Control signal of MEM
    // (SC = Self Clear, COR = Clear on Read, TOW = Toggle on Write, COH = Clear on Handshake)

    // Parameters    
    localparam
        ADDR_AP_CTRL    = 8'h00,
        ADDR_GIE        = 8'h04,
        ADDR_IER        = 8'h08,
        ADDR_ISR        = 8'h0C,

        ADDR_DEV_0      = 8'h10,
        ADDR_DEV_1      = 8'h14,
        ADDR_DEV_CTRL   = 8'h18,
        
        ADDR_ISA_0      = 8'h1C,
        ADDR_ISA_1      = 8'h20,
        ADDR_ISA_CTRL   = 8'h24,
        
        ADDR_DCR_0      = 8'h28,
        ADDR_DCR_1      = 8'h2C,
        ADDR_DCR_CTRL   = 8'h30,

        ADDR_SCP_0      = 8'h34,
        ADDR_SCP_1      = 8'h38,
        ADDR_SCP_CTRL   = 8'h3C,

        ADDR_MEM_0      = 8'h40,
        ADDR_MEM_1      = 8'h44,
        ADDR_MEM_CTRL   = 8'h48,
        
        ADDR_BITS       = 8;

    localparam
        WSTATE_IDLE     = 2'd0,
        WSTATE_DATA     = 2'd1,
        WSTATE_RESP     = 2'd2;
    
    localparam
        RSTATE_IDLE     = 2'd0,
        RSTATE_DATA     = 2'd1;

    // device caps
    wire [63:0] dev_caps = {16'b0,
                            8'(`SM_ENABLED ? `SMEM_LOG_SIZE : 0),
                            16'(`NUM_CORES * `NUM_CLUSTERS), 
                            8'(`NUM_WARPS), 
                            8'(`NUM_THREADS), 
                            8'(`IMPLEMENTATION_ID)};

    wire [63:0] isa_caps = {32'(`MISA_EXT), 
                            2'(`CLOG2(`XLEN)-4), 
                            30'(`MISA_STD)};

    reg [1:0]   wstate;
    reg [ADDR_BITS-1:0] waddr;
    wire [31:0] wmask;
    wire        s_axi_aw_fire;
    wire        s_axi_w_fire;
    
    reg [1:0]   rstate;
    reg [31:0]  rdata;
    wire [ADDR_BITS-1:0] raddr;
    wire        s_axi_ar_fire;

    reg         ap_reset_r;
    reg         ap_start_r;
    reg         auto_restart_r;
    reg         gie_r;
    reg [1:0]   ier_r;
    reg [1:0]   isr_r;
    reg [63:0]  mem_r [AXI_NUM_BANKS];
    reg [31:0]  dcra_r;
    reg [31:0]  dcrv_r;
    reg         dcr_wr_valid_r;   
      
`ifdef SCOPE

    reg [63:0] scope_bus_wdata;
    reg [63:0] scope_bus_rdata;    
    reg [5:0] scope_bus_ctr;

    reg cmd_scope_reading;
    reg cmd_scope_writing;
    reg scope_bus_out_r;

    always @(posedge clk) begin
        if (reset) begin
            cmd_scope_reading <= 0;
            cmd_scope_writing <= 0;            
            scope_bus_ctr <= '0;
            scope_bus_out_r <= 0;
        end else if (clk_en) begin
            if (s_axi_w_fire && waddr == ADDR_SCP_0) begin
                scope_bus_wdata[31:0] <= (s_axi_wdata & wmask) | (scope_bus_wdata[31:0] & ~wmask);
            end
            if (s_axi_w_fire && waddr == ADDR_SCP_1) begin
                scope_bus_wdata[63:32] <= (s_axi_wdata & wmask) | (scope_bus_wdata[63:32] & ~wmask);           
                cmd_scope_writing <= 1;
                scope_bus_out_r   <= 1;    
                scope_bus_ctr     <= 63;
            end             
            if (scope_bus_in) begin
                cmd_scope_reading <= 1;
                scope_bus_ctr     <= 63;
            end     
            if (cmd_scope_reading) begin
                scope_bus_rdata <= {scope_bus_rdata[62:0], scope_bus_in};
                scope_bus_ctr   <= scope_bus_ctr - 1;
                if (scope_bus_ctr == 0) begin
                    cmd_scope_reading <= 0;
                end
            end            
            if (cmd_scope_writing) begin
                scope_bus_out_r <= 1'(scope_bus_wdata >> scope_bus_ctr);
                scope_bus_ctr <= scope_bus_ctr - 1;
                if (scope_bus_ctr == 0) begin
                    cmd_scope_writing <= 0;
                end
            end          
        end
    end

    assign scope_bus_out = scope_bus_out_r;

`endif

    // AXI Write       

    assign s_axi_awready = (wstate == WSTATE_IDLE);
    assign s_axi_wready  = (wstate == WSTATE_DATA);
    assign s_axi_bvalid  = (wstate == WSTATE_RESP);
    assign s_axi_bresp   = 2'b00;  // OKAY

    assign s_axi_aw_fire = s_axi_awvalid && s_axi_awready;
    assign s_axi_w_fire  = s_axi_wvalid && s_axi_wready;

    for (genvar i = 0; i < 4; ++i) begin
        assign wmask[8 * i +: 8] = {8{s_axi_wstrb[i]}};
    end

    // wstate
    always @(posedge clk) begin
        if (reset) begin
            wstate <= WSTATE_IDLE;
        end else if (clk_en) begin
            case (wstate)
            WSTATE_IDLE: wstate <= s_axi_awvalid ? WSTATE_DATA : WSTATE_IDLE;
            WSTATE_DATA: wstate <= s_axi_wvalid ? WSTATE_RESP : WSTATE_DATA;
            WSTATE_RESP: wstate <= s_axi_bready ? WSTATE_IDLE : WSTATE_RESP;
            default:     wstate <= WSTATE_IDLE;
            endcase
        end
    end

    // waddr
    always @(posedge clk) begin
        if (clk_en) begin
            if (s_axi_aw_fire)
                waddr <= s_axi_awaddr[ADDR_BITS-1:0];
        end
    end
    
    // wdata
    always @(posedge clk) begin
        if (reset) begin
            ap_start_r <= 0;
            ap_reset_r <= 0;
            auto_restart_r <= 0;
            
            gie_r <= 0;
            ier_r <= '0;
            isr_r <= '0;

            dcra_r <= '0;
            dcrv_r <= '0;
            dcr_wr_valid_r <= 0;

            for (integer i = 0; i < AXI_NUM_BANKS; ++i) begin
                mem_r[i] <= '0;
            end
        end else if (clk_en) begin
            if (ap_ready)
                ap_start_r <= auto_restart_r;

            dcr_wr_valid_r <= 0;

            if (s_axi_w_fire) begin
                case (waddr)
                ADDR_AP_CTRL: begin
                    if (s_axi_wstrb[0]) begin 
                        if (s_axi_wdata[0])
                            ap_start_r <= 1;
                        if (s_axi_wdata[4])
                            ap_reset_r <= 1;
                        if (s_axi_wdata[7])
                            auto_restart_r <= 1;
                    end
                end
                ADDR_GIE: begin
                    if (s_axi_wstrb[0])
                        gie_r <= s_axi_wdata[0];
                end
                ADDR_IER: begin
                    if (s_axi_wstrb[0])
                        ier_r <= s_axi_wdata[1:0];
                end
                ADDR_ISR: begin
                    if (s_axi_wstrb[0])
                        isr_r <= isr_r ^ s_axi_wdata[1:0];
                end
                ADDR_DCR_0: begin
                    dcra_r <= (s_axi_wdata & wmask) | (dcra_r & ~wmask);
                end
                ADDR_DCR_1: begin
                    dcrv_r <= (s_axi_wdata & wmask) | (dcrv_r & ~wmask);
                    dcr_wr_valid_r <= 1;
                end
                default: begin
                    for (integer i = 0; i < AXI_NUM_BANKS; ++i) begin
                        if (waddr == (ADDR_MEM_0 + i * 12)) begin
                            mem_r[i][31:0] <= (s_axi_wdata & wmask) | (mem_r[i][31:0] & ~wmask);
                        end
                        if (waddr == (ADDR_MEM_1 + i * 12)) begin
                            mem_r[i][63:32] <= (s_axi_wdata & wmask) | (mem_r[i][63:32] & ~wmask);
                        end
                    end
                end
                endcase
                
                if (ier_r[0] & ap_done)
                    isr_r[0] <= 1'b1;
                if (ier_r[1] & ap_ready)
                    isr_r[1] <= 1'b1;
            end
        end
    end

    // AXI Read

    assign s_axi_arready = (rstate == RSTATE_IDLE);
    assign s_axi_rvalid  = (rstate == RSTATE_DATA);
    assign s_axi_rdata   = rdata;
    assign s_axi_rresp   = 2'b00;  // OKAY
    
    assign s_axi_ar_fire = s_axi_arvalid && s_axi_arready;
    assign raddr = s_axi_araddr[ADDR_BITS-1:0];
    
    // rstate
    always @(posedge clk) begin
        if (reset) begin
            rstate <= RSTATE_IDLE;
        end else if (clk_en) begin
            case (rstate)
            RSTATE_IDLE: rstate <= s_axi_arvalid ? RSTATE_DATA : RSTATE_IDLE;
            RSTATE_DATA: rstate <= (s_axi_rready & s_axi_rvalid) ? RSTATE_IDLE : RSTATE_DATA;
            default:     rstate <= RSTATE_IDLE;
            endcase
        end
    end

    // rdata
    always @(posedge clk) begin
        if (clk_en) begin
            if (s_axi_ar_fire) begin
                rdata <= '0;
                case (raddr)
                    ADDR_AP_CTRL: begin
                        rdata[0] <= ap_start_r;
                        rdata[1] <= ap_done;
                        rdata[2] <= ap_idle;
                        rdata[3] <= ap_ready;
                        rdata[7] <= auto_restart_r;
                    end
                    ADDR_GIE: begin
                        rdata <= 32'(gie_r);
                    end
                    ADDR_IER: begin
                        rdata <= 32'(ier_r);
                    end
                    ADDR_ISR: begin
                        rdata <= 32'(isr_r);
                    end
                    ADDR_DEV_0: begin
                        rdata <= dev_caps[31:0];
                    end
                    ADDR_DEV_1: begin
                        rdata <= dev_caps[63:32];
                    end
                    ADDR_ISA_0: begin
                        rdata <= isa_caps[31:0];
                    end
                    ADDR_ISA_1: begin
                        rdata <= isa_caps[63:32];
                    end
                `ifdef SCOPE
                    ADDR_SCP_0: begin
                        rdata <= scope_bus_rdata[31:0];
                    end
                    ADDR_SCP_1: begin
                        rdata <= scope_bus_rdata[63:32];
                    end
                `endif
                    default:;
                endcase
            end
        end
    end

    assign ap_reset  = ap_reset_r;
    assign ap_start  = ap_start_r;
    assign interrupt = gie_r & (| isr_r);

    assign mem_base  = mem_r;

    assign dcr_wr_valid = dcr_wr_valid_r;
    assign dcr_wr_addr  = `VX_DCR_ADDR_WIDTH'(dcra_r);
    assign dcr_wr_data  = `VX_DCR_DATA_WIDTH'(dcrv_r);    

endmodule
