module n64_flashram (
    input clk,
    input reset,

    n64_reg_bus.flashram reg_bus,

    n64_scb.flashram n64_scb
);

    localparam [31:0] FLASH_TYPE_ID     = 32'h1111_8001;
    localparam [31:0] FLASH_MODEL_ID    = 32'h00C2_001D;

    typedef enum bit [7:0] {
        CMD_STATUS_MODE     = 8'hD2,
        CMD_READID_MODE     = 8'hE1,
        CMD_READ_MODE       = 8'hF0,
        CMD_ERASE_SECTOR    = 8'h4B,
        CMD_ERASE_CHIP      = 8'h3C,
        CMD_BUFFER_MODE     = 8'hB4,
        CMD_ERASE_START     = 8'h78,
        CMD_WRITE_START     = 8'hA5
    } e_cmd;

    typedef enum bit [1:0] {
        STATE_STATUS,
        STATE_ID,
        STATE_READ,
        STATE_BUFFER
    } e_state;

    typedef enum bit [1:0] {
        WRITE_BUSY,
        ERASE_BUSY,
        WRITE_DONE,
        ERASE_DONE
    } e_status_bits;

    e_state state;
    logic [3:0] status;
    logic [7:0] cmd;
    logic erase_enabled;

    always_comb begin
        n64_scb.flashram_read_mode = (state == STATE_READ);

        reg_bus.rdata = 16'd0;
        if (state == STATE_ID) begin
            case (reg_bus.address[2:1])
                0: reg_bus.rdata = FLASH_TYPE_ID[31:16];
                1: reg_bus.rdata = FLASH_TYPE_ID[15:0];
                2: reg_bus.rdata = FLASH_MODEL_ID[31:16];
                3: reg_bus.rdata = FLASH_MODEL_ID[15:0];
            endcase
        end else if (reg_bus.address[1]) begin
            reg_bus.rdata = {12'd0, status};
        end
    end

    always_ff @(posedge clk) begin
        if (reset) begin
            state <= STATE_STATUS;
            status <= 4'b0000;
            erase_enabled <= 1'b0;
            n64_scb.flashram_pending <= 1'b0;
        end else begin
            if (n64_scb.flashram_done) begin
                n64_scb.flashram_pending <= 1'b0;
                if (n64_scb.flashram_write_or_erase) begin
                    status[ERASE_BUSY] <= 1'b0;
                    status[ERASE_DONE] <= 1'b1;
                end else begin
                    status[WRITE_BUSY] <= 1'b0;
                    status[WRITE_DONE] <= 1'b1;
                end
            end

            if (reg_bus.write && !n64_scb.flashram_pending) begin
                if (reg_bus.address[16]) begin
                    if (!reg_bus.address[1]) begin
                        cmd <= reg_bus.wdata[15:8];
                    end else begin
                        erase_enabled <= 1'b0;

                        case (cmd)
                            CMD_STATUS_MODE: begin
                                state <= STATE_STATUS;
                            end

                            CMD_READID_MODE: begin
                                state <= STATE_ID;
                            end

                            CMD_READ_MODE: begin
                                state <= STATE_READ;
                            end

                            CMD_ERASE_SECTOR: begin
                                state <= STATE_STATUS;
                                erase_enabled <= 1'b1;
                                n64_scb.flashram_sector <= reg_bus.wdata[9:0];
                                n64_scb.flashram_sector_or_all <= 1'b0;
                            end

                            CMD_ERASE_CHIP: begin
                                state <= STATE_STATUS;
                                erase_enabled <= 1'b1;
                                n64_scb.flashram_sector <= 10'd0;
                                n64_scb.flashram_sector_or_all <= 1'b1;
                            end

                            CMD_BUFFER_MODE: begin
                                state <= STATE_BUFFER;
                            end

                            CMD_ERASE_START: begin
                                state <= STATE_STATUS;
                                if (erase_enabled) begin
                                    status[ERASE_BUSY] <= 1'b1;
                                    status[ERASE_DONE] <= 1'b0;
                                    n64_scb.flashram_pending <= 1'b1;
                                    n64_scb.flashram_write_or_erase <= 1'b1;
                                end
                            end

                            CMD_WRITE_START: begin
                                state <= STATE_STATUS;
                                status[WRITE_BUSY] <= 1'b1;
                                status[WRITE_DONE] <= 1'b0;
                                n64_scb.flashram_sector <= reg_bus.wdata[9:0];
                                n64_scb.flashram_pending <= 1'b1;
                                n64_scb.flashram_write_or_erase <= 1'b0;
                                n64_scb.flashram_sector_or_all <= 1'b0;
                            end
                        endcase
                    end
                end else begin
                    if (reg_bus.address[1] && state != STATE_BUFFER) begin
                        status[ERASE_BUSY] <= reg_bus.wdata[ERASE_BUSY];
                        status[WRITE_BUSY] <= reg_bus.wdata[WRITE_BUSY];
                    end
                end
            end
        end
    end

    always_comb begin
        n64_scb.flashram_write = reg_bus.write && !reg_bus.address[16] && state == STATE_BUFFER;
        n64_scb.flashram_address = reg_bus.address[6:1];
        n64_scb.flashram_wdata = reg_bus.wdata;
    end

endmodule
