module verilog6502_addr_decoder(
    input                   i_clk,
    input                   i_rst,

    input  logic [15:0]     i_cpu_addr,
    input  logic [7:0]      i_cpu_data,
    output logic [7:0]      o_cpu_data,
    input  logic            i_cpu_we,
    input  logic            i_cpu_rdy,
    output logic            o_cpu_rdy,


    output logic [15:0]     o_mem_addr,
    output logic [7:0]      o_mem_data,
    input  logic [7:0]      i_mem_data,
    output logic            o_mem_rd,
    output logic            o_mem_we,
    input  logic            i_mem_rdy,

    output logic [15:0]     o_external_addr,
    output logic [7:0]      o_external_data,
    input  logic [7:0]      i_external_data,
    output logic            o_external_rd,
    output logic            o_external_we,
    input  logic            i_external_rdy,

    output logic [15:0]     o_io_addr,
    output logic [7:0]      o_io_data,
    input  logic [7:0]      i_io_data,
    output logic            o_io_rd,
    output logic            o_io_we,
    input  logic            i_io_rdy
);

enum logic [1:0] {NONE, MEM, EXT, IO} prev_addr, prev_addr_next;

always_ff @(posedge i_clk) begin
    if (i_rst) begin
        prev_addr <= NONE;
    end else begin
        prev_addr <= prev_addr_next;
    end
end

always_comb begin
    prev_addr_next = prev_addr;

    o_mem_addr = '0;
    o_mem_data = '0;
    o_mem_rd = '0;
    o_mem_we = '0;

    o_external_addr = '0;
    o_external_data = '0;
    o_external_rd = '0;
    o_external_we = '0;

    o_io_addr = '0;
    o_io_data = '0;
    o_io_rd = '0;
    o_io_we = '0;

    case (prev_addr)
        NONE: begin
            o_cpu_rdy = '1;
            o_cpu_data = '0;
        end

        MEM: begin
            o_cpu_rdy = i_mem_rdy;
            o_cpu_data = i_mem_data;
        end

        EXT: begin
            o_cpu_rdy = i_external_rdy;
            o_cpu_data = i_external_data;
        end

        IO: begin
            o_cpu_rdy = i_io_rdy;
            o_cpu_data = i_io_data;
        end

        default: begin
            o_cpu_rdy = '1;
            o_cpu_data = '0;
        end
    endcase

    if (o_cpu_rdy) begin
        if (i_cpu_addr < 16'hE000) begin
            o_mem_addr  = i_cpu_addr;
            o_mem_data  = i_cpu_data;
            o_mem_we    = i_cpu_we & o_cpu_rdy;
            o_mem_rd    = ~i_cpu_we & o_cpu_rdy;
            prev_addr_next = MEM;
        end else if (i_cpu_addr < 16'hF000) begin
            o_external_addr = {4'b0, i_cpu_addr[11:0]};
            o_external_data = i_cpu_data;
            o_external_we   = i_cpu_we & o_cpu_rdy;
            o_external_rd   = ~i_cpu_we & o_cpu_rdy;
            prev_addr_next = EXT;
        end else begin
            o_io_addr  = {4'b0, i_cpu_addr[11:0]};
            o_io_data  = i_cpu_data;
            o_io_we    = i_cpu_we & o_cpu_rdy;
            o_io_rd    = ~i_cpu_we & o_cpu_rdy;
            prev_addr_next = IO;
        end
    end

    if (i_rst | ~i_cpu_rdy) begin
        prev_addr_next = NONE;
        o_mem_rd = 0;
        o_mem_we = 0;
        o_external_rd = 0;
        o_external_we = 0;
        o_io_rd = 0;
        o_io_we = 0;
    end

end

endmodule