module memory_sdram (
    input clk,
    input reset,

    mem_bus.memory mem_bus,

    output logic sdram_cs,
    output logic sdram_ras,
    output logic sdram_cas,
    output logic sdram_we,
    output logic [1:0] sdram_ba,
    output logic [12:0] sdram_a,
    output logic [1:0] sdram_dqm,
    inout [15:0] sdram_dq
);

    localparam [2:0] CAS_LATENCY = 3'd2;

    localparam real T_INIT      = 100_000.0;
    localparam real T_RC        = 60.0;
    localparam real T_RP        = 15.0;
    localparam real T_RCD       = 15.0;
    localparam real T_MRD       = 14.0;
    localparam real T_REF       = 7_800.0;

    localparam real T_CLK       = (1.0 / 100_000_000) * 1_000_000_000.0;
    localparam int C_INIT       = int'((T_INIT + T_CLK - 1) / T_CLK);
    localparam int C_RC         = int'((T_RC + T_CLK - 1) / T_CLK);
    localparam int C_RP         = int'((T_RP + T_CLK - 1) / T_CLK);
    localparam int C_RCD        = int'((T_RCD + T_CLK - 1) / T_CLK);
    localparam int C_MRD        = int'((T_MRD + T_CLK - 1) / T_CLK);
    localparam int C_REF        = int'((T_REF + T_CLK - 1) / T_CLK);

    localparam INIT_PRECHARGE   = 4'd0;
    localparam INIT_REFRESH_1   = C_RP;
    localparam INIT_REFRESH_2   = C_RP + C_RC;
    localparam INIT_MODE_REG    = C_RP + (2 * C_RC);
    localparam INIT_DONE        = C_RP + (2 * C_RC) + C_MRD;

    typedef enum bit [3:0] {
        CMD_DESL    = 4'b1111,
        CMD_NOP     = 4'b0111,
        CMD_READ    = 4'b0101,
        CMD_WRITE   = 4'b0100,
        CMD_ACT     = 4'b0011,
        CMD_PRE     = 4'b0010,
        CMD_REF     = 4'b0001,
        CMD_MRS     = 4'b0000
    } e_sdram_cmd;

    e_sdram_cmd sdram_next_cmd;
    logic [15:0] sdram_dq_input;
    logic [15:0] sdram_dq_output;
    logic sdram_dq_output_enable;

    logic [14:0] current_active_bank_row;
    logic request_in_current_active_bank_row;

    always_ff @(posedge clk) begin
        {sdram_cs, sdram_ras, sdram_cas, sdram_we} <= 4'(sdram_next_cmd);

        {sdram_ba, sdram_a} <= 15'd0;
        sdram_dqm <= 2'b00;

        sdram_dq_input <= sdram_dq;
        sdram_dq_output <= mem_bus.wdata;
    
        sdram_dq_output_enable <= 1'b0;

        case (sdram_next_cmd)
            CMD_READ, CMD_WRITE: begin
                {sdram_ba, sdram_a} <= {mem_bus.address[25:24], 3'b000, mem_bus.address[10:1]};
                sdram_dqm <= (sdram_next_cmd == CMD_WRITE) ? (~mem_bus.wmask) : 2'b00;
                sdram_dq_output_enable <= (sdram_next_cmd == CMD_WRITE);
            end

            CMD_ACT: begin
                {sdram_ba, sdram_a} <= mem_bus.address[25:11];
                sdram_dqm <= 2'b00;
                current_active_bank_row <= mem_bus.address[25:11];
            end

            CMD_PRE: begin
                {sdram_ba, sdram_a} <= {2'b00, 2'b00, 1'b1, 10'd0};
                sdram_dqm <= 2'b00;
            end

            CMD_MRS: begin
                {sdram_ba, sdram_a} <= {2'b00, 1'b0, 1'b0, 2'b00, CAS_LATENCY, 1'b0, 3'b000};
                sdram_dqm <= 2'b00;
            end
        endcase
    end

    assign sdram_dq = sdram_dq_output_enable ? sdram_dq_output : 16'hZZZZ;

    always_comb begin
        mem_bus.rdata = sdram_dq_input;
        request_in_current_active_bank_row = mem_bus.address[25:11] == current_active_bank_row;
    end

    typedef enum bit [2:0] {
        S_POWERUP,
        S_INIT,
        S_IDLE,
        S_ACTIVATING,
        S_ACTIVE,
        S_BUSY,
        S_PRECHARGE,
        S_REFRESH
    } e_state;

    e_state state;
    e_state next_state;

    always_ff @(posedge clk) begin
        if (reset) begin
            state <= S_POWERUP;
        end else begin
            state <= next_state;
        end
    end

    logic [13:0] powerup_coutner;
    logic powerup_done;
    logic [4:0] wait_counter;
    logic [9:0] refresh_counter;
    logic pending_refresh;

    always_ff @(posedge clk) begin
        if (reset) begin
            powerup_coutner <= 14'd0;
            powerup_done <= 1'b0;
        end else if (powerup_coutner < C_INIT) begin
            powerup_coutner <= powerup_coutner + 1'd1;
        end else begin
            powerup_done <= 1'b1;
        end

        if (reset || state != next_state) begin
            wait_counter <= 5'd0;
        end else begin
            wait_counter <= wait_counter + 1'd1;
        end

        if (sdram_next_cmd == CMD_REF) begin
            refresh_counter <= 10'd0;
            pending_refresh <= 1'b0;
        end else if (refresh_counter < C_REF) begin
            refresh_counter <= refresh_counter + 1'd1;
        end else begin
            pending_refresh <= 1'b1;
        end
    end

    logic [(CAS_LATENCY):0] read_cmd_ack_delay;

    always_ff @(posedge clk) begin
        mem_bus.ack <= 1'b0;
        read_cmd_ack_delay <= {sdram_next_cmd == CMD_READ, read_cmd_ack_delay[(CAS_LATENCY):1]};

        if (sdram_next_cmd == CMD_WRITE || read_cmd_ack_delay[0]) begin
            mem_bus.ack <= 1'b1;
        end
    end

    always_comb begin
        sdram_next_cmd = CMD_NOP;
        next_state = state;

        case (state)
            S_POWERUP: begin
                sdram_next_cmd = CMD_DESL;
                if (powerup_done) begin
                    next_state = S_INIT;
                end
            end

            S_INIT: begin
                if (wait_counter == INIT_PRECHARGE) begin
                    sdram_next_cmd = CMD_PRE;
                end
                if (wait_counter == INIT_REFRESH_1 || wait_counter == INIT_REFRESH_2) begin
                    sdram_next_cmd = CMD_REF;
                end
                if (wait_counter == INIT_MODE_REG) begin
                    sdram_next_cmd = CMD_MRS;
                end
                if (wait_counter == INIT_DONE) begin
                    next_state = S_IDLE;
                end
            end

            S_IDLE: begin
                if (pending_refresh) begin
                    next_state = S_REFRESH;
                    sdram_next_cmd = CMD_REF;
                end else if (mem_bus.request) begin
                    next_state = S_ACTIVATING;
                    sdram_next_cmd = CMD_ACT;
                end
            end

            S_ACTIVATING: begin
                if (wait_counter == C_RCD) begin
                    next_state = S_ACTIVE;
                end
            end

            S_ACTIVE: begin
                if (pending_refresh) begin
                    next_state = S_PRECHARGE;
                    sdram_next_cmd = CMD_PRE;
                end else if (mem_bus.request) begin
                    if (request_in_current_active_bank_row) begin
                        next_state = S_BUSY;
                        sdram_next_cmd = mem_bus.write ? CMD_WRITE : CMD_READ;
                    end else begin
                        next_state = S_PRECHARGE;
                        sdram_next_cmd = CMD_PRE;
                    end
                end
            end

            S_BUSY: begin
                if (mem_bus.ack) begin
                    next_state = S_ACTIVE;
                end
            end

            S_PRECHARGE: begin
                if (wait_counter == C_RP) begin
                    if (pending_refresh) begin
                        next_state = S_REFRESH;
                        sdram_next_cmd = CMD_REF;
                    end else begin
                        next_state = S_IDLE;
                    end
                end
            end

            S_REFRESH: begin
                if (wait_counter == C_RC) begin
                    next_state = S_IDLE;
                end
            end

            default: begin
                next_state = S_IDLE;
            end
        endcase
    end

endmodule
