module verilog6502_internal_memory(
    input                   i_clk,
    input                   i_rst,

    taxi_axi_if.rd_slv      s_axi_rd,
    taxi_axi_if.wr_slv      s_axi_wr,

    input  logic [15:0]     i_addr,
    input  logic [7:0]      i_data,
    output logic [7:0]      o_data,
    input  logic            i_rd,
    input  logic            i_we,
    output logic            o_rdy
);

localparam ID_W = 8;
localparam ADDR_W = 32;
localparam AUSER_W = 1;
localparam RUSER_W = 1;
localparam WUSER_W = 1;
localparam DATA_W = 32;
localparam STRB_W = 4;

logic [ID_W-1:0]     ram_cmd_id;
logic [ADDR_W-1:0]   ram_cmd_addr;
logic [DATA_W-1:0]   ram_cmd_wr_data;
logic [STRB_W-1:0]   ram_cmd_wr_strb;
logic                ram_cmd_wr_en;
logic                ram_cmd_rd_en;
logic                ram_cmd_last;
logic                ram_cmd_ready;
logic [DATA_W-1:0]   ram_rd_resp_data;
logic                ram_rd_resp_last;
logic                ram_rd_resp_valid;
logic                ram_rd_resp_ready;

taxi_axi_ram_if_rdwr #(
    .DATA_W(DATA_W),
    .ADDR_W(ADDR_W),
    .STRB_W(STRB_W),
    .ID_W(ID_W),
    .AUSER_W(AUSER_W),
    .WUSER_W(WUSER_W),
    .RUSER_W(RUSER_W)
) axi_ram_if_rdwr (
    .clk                (i_clk),
    .rst                (i_rst),

    .s_axi_wr           (s_axi_wr),
    .s_axi_rd           (s_axi_rd),

    .ram_cmd_id         (ram_cmd_id),
    .ram_cmd_addr       (ram_cmd_addr),
    .ram_cmd_lock       (),
    .ram_cmd_cache      (),
    .ram_cmd_prot       (),
    .ram_cmd_qos        (),
    .ram_cmd_region     (),
    .ram_cmd_auser      (),
    .ram_cmd_wr_data    (ram_cmd_wr_data),
    .ram_cmd_wr_strb    (ram_cmd_wr_strb),
    .ram_cmd_wr_user    (),
    .ram_cmd_wr_en      (ram_cmd_wr_en),
    .ram_cmd_rd_en      (ram_cmd_rd_en),
    .ram_cmd_last       (ram_cmd_last),
    .ram_cmd_ready      (ram_cmd_ready),
    .ram_rd_resp_id     (),
    .ram_rd_resp_data   (ram_rd_resp_data),
    .ram_rd_resp_last   (ram_rd_resp_last),
    .ram_rd_resp_user   (),
    .ram_rd_resp_valid  (ram_rd_resp_valid),
    .ram_rd_resp_ready  (ram_rd_resp_ready)
);


logic [7:0] mem [4][14*1024];


enum logic {CPU, EXT} sel, sel_next;

logic o_rdy_next;

logic [15:0] ram_addr;
logic [31:0] ram_wdata;
logic [3:0] ram_wstrb;
logic ram_we;

logic ram_re;
logic [31:0] ram_rdata;
logic ram_rdata_valid;

logic [1:0] latched_byte_select;

logic [15:0]     pending_addr;
logic [7:0]      pending_data;
logic            pending_rd;
logic            pending_we;


always_ff @(posedge i_clk) begin
    if (i_rst) begin
        sel <= CPU;
        o_rdy <= '0;
    end else begin
        sel <= sel_next;
        o_rdy <= o_rdy_next;

        ram_rdata_valid <= '0;
        ram_rd_resp_last <= '0;

        if (ram_we) begin
            for (int i = 0; i < 4; i++) begin
                if (ram_wstrb[i]) begin
                    mem[i][ram_addr[15:2]] <= ram_wdata[8*i +: 8];
                end
            end
        end

        if (ram_re) begin
            for (int i = 0; i < 4; i++) begin
                ram_rdata[8*i +: 8] <= mem[i][ram_addr[15:2]];
            end
            ram_rdata_valid <= '1;
            ram_rd_resp_last <= ram_cmd_last;

            latched_byte_select <= ram_addr[1:0];
        end

        if (sel == EXT) begin
            if (i_we) begin
                pending_we <= '1;
                pending_addr <= i_addr;
                pending_data <= i_data;
            end

            if (i_rd) begin
                pending_rd <= '1;
                pending_addr <= i_addr;
            end
        end else begin
            pending_we <= '0;
            pending_rd <= '0;
            pending_addr <= '0;
            pending_data <= '0;
        end
    end
end

always_comb begin
    sel_next = sel;

    ram_cmd_ready = '0;

    ram_addr = '0;
    ram_we = '0;
    ram_wdata = '0;
    ram_we = '0;
    ram_re = '0;

    ram_rd_resp_valid = '0;
    ram_rd_resp_data = '0;

    o_rdy_next = '0;
    o_data = '0;

    case (sel)
        CPU: begin
            // if there was no CPU op, then give external bus a chance
            if (~(i_rd | i_we | pending_rd | pending_we)) begin
                sel_next = EXT;
            end

            if (i_we) begin
                ram_addr = i_addr;
                ram_wstrb = 4'b1 << i_addr[1:0];
                ram_wdata = {24'b0, i_data} << (i_addr[1:0] * 8);
                ram_we = '1;
                o_rdy_next = '1;
            end
            if (pending_we) begin
                ram_addr = pending_addr;
                ram_wstrb = 4'b1 << pending_addr[1:0];
                ram_wdata = {24'b0, pending_data} << (pending_addr[1:0] * 8);
                ram_we = '1;
                o_rdy_next = '1;
            end

            if (i_rd) begin
                ram_addr = i_addr;
                ram_re = '1;
                o_rdy_next = '1;
            end
            if (pending_rd) begin
                ram_addr = pending_addr;
                ram_re = '1;
                o_rdy_next = '1;
            end

            o_data = ram_rdata[8*latched_byte_select +: 8];

        end

        EXT: begin
            ram_cmd_ready = '1;

            if (ram_cmd_wr_en) begin
                ram_addr = ram_cmd_addr[15:0];
                ram_wdata = ram_cmd_wr_data;
                ram_wstrb = ram_cmd_wr_strb;
                ram_we = '1;
            end else if (ram_cmd_rd_en) begin
                ram_addr = ram_cmd_addr[15:0];
                ram_re = '1;

            end else begin
                sel_next = CPU;
            end

            ram_rd_resp_valid = ram_rdata_valid;
            ram_rd_resp_data = ram_rdata;
        end
    endcase
end


endmodule