module sdram (
        input clk,
        input rst,

        // these signals go directly to the IO pins
        output sdram_clk,
        output sdram_cle,
        output sdram_cs,
        output sdram_cas,
        output sdram_ras,
        output sdram_we,
        output sdram_dqm,
        output [1:0] sdram_ba,
        output [12:0] sdram_a,
        inout [15:0] sdram_dq,

        // User interface
        input [24:0] addr,      // address to read/write
        input rw,               // 1 = write, 0 = read
        input [31:0] data_in,   // data from a read
        output [31:0] data_out, // data for a write
        output busy,            // controller is busy when high
        input in_valid,         // pulse high to initiate a read/write
        output out_valid,       // pulses high when data from read is valid
		  output doing_refresh
    );

    // Commands for the SDRAM
    localparam CMD_UNSELECTED    = 4'b1000;
    localparam CMD_NOP           = 4'b0111;
    localparam CMD_ACTIVE        = 4'b0011;
    localparam CMD_READ          = 4'b0101;
    localparam CMD_WRITE         = 4'b0100;
    localparam CMD_TERMINATE     = 4'b0110;
    localparam CMD_PRECHARGE     = 4'b0010;
    localparam CMD_REFRESH       = 4'b0001;
    localparam CMD_LOAD_MODE_REG = 4'b0000;

    localparam STATE_SIZE = 4;
    localparam INIT = 0,
               WAIT = 1,
               PRECHARGE_INIT = 2,
               REFRESH_INIT_1 = 3,
               REFRESH_INIT_2 = 4,
               LOAD_MODE_REG = 5,
               IDLE = 6,
               REFRESH = 7,
               ACTIVATE = 8,
               READ = 9,
               READ_RES = 10,
               WRITE = 11,
               PRECHARGE = 12;

    wire sdram_clk_ddr;

    // This is used to drive the SDRAM clock
    ODDR2 #(
        .DDR_ALIGNMENT("NONE"),
        .INIT(1'b0),
        .SRTYPE("SYNC")
    ) ODDR2_inst (
        .Q(sdram_clk_ddr), // 1-bit DDR output data
        .C0(clk), // 1-bit clock input
        .C1(~clk), // 1-bit clock input
        .CE(1'b1), // 1-bit clock enable input
        .D0(1'b0), // 1-bit data input (associated with C0)
        .D1(1'b1), // 1-bit data input (associated with C1)
        .R(1'b0), // 1-bit reset input
        .S(1'b0) // 1-bit set input
    );

    IODELAY2 #(
        .IDELAY_VALUE(0),
        .IDELAY_MODE("NORMAL"),
        .ODELAY_VALUE(100), // value of 100 seems to work at 100MHz
        .IDELAY_TYPE("FIXED"),
        .DELAY_SRC("ODATAIN"),
        .DATA_RATE("SDR")
    ) IODELAY_inst (
        .IDATAIN(1'b0),
        .T(1'b0),
        .ODATAIN(sdram_clk_ddr),
        .CAL(1'b0),
        .IOCLK0(1'b0),
        .IOCLK1(1'b0),
        .CLK(1'b0),
        .INC(1'b0),
        .CE(1'b0),
        .RST(1'b0),
        .BUSY(),
        .DATAOUT(),
        .DATAOUT2(),
        .TOUT(),
        .DOUT(sdram_clk)
    );

    // registers for SDRAM signals
    reg cle_d, dqm_d;
    reg [3:0] cmd_d;
    reg [1:0] ba_d;
    reg [12:0] a_d;
    reg [15:0] dq_d;
    reg [15:0] dqi_d;

	 reg doing_refresh_out = 1'b0;

    // We want the output/input registers to be embedded in the
    // IO buffers so we set IOB to "TRUE". This is to ensure all
    // the signals are sent and received at the same time.
    (* IOB = "TRUE" *)
    reg cle_q, dqm_q;
    (* IOB = "TRUE" *)
    reg [3:0] cmd_q;
    (* IOB = "TRUE" *)
    reg [1:0] ba_q;
    (* IOB = "TRUE" *)
    reg [12:0] a_q;
    (* IOB = "TRUE" *)
    reg [15:0] dq_q;
    (* IOB = "TRUE" *)
    reg [15:0] dqi_q;
    reg dq_en_d, dq_en_q;

    assign doing_refresh = doing_refresh_out;
    // Output assignments
    assign sdram_cle = cle_q;
    assign sdram_cs = cmd_q[3];
    assign sdram_ras = cmd_q[2];
    assign sdram_cas = cmd_q[1];
    assign sdram_we = cmd_q[0];
    assign sdram_dqm = dqm_q;
    assign sdram_ba = ba_q;
    assign sdram_a = a_q;
    assign sdram_dq = dq_en_q ? dq_q : 16'hZZ; // only drive when dq_en_q is 1

    reg [STATE_SIZE-1:0] state_d, state_q = INIT;
    reg [STATE_SIZE-1:0] next_state_d, next_state_q;

    reg [24:0] addr_d, addr_q;
    reg [31:0] data_d, data_q;
    reg out_valid_d, out_valid_q;

    assign data_out = data_q;
    assign busy = !ready_q;
    assign out_valid = out_valid_q;

    reg [15:0] delay_ctr_d, delay_ctr_q;
    reg [1:0] byte_ctr_d, byte_ctr_q;

    reg [9:0] refresh_ctr_d, refresh_ctr_q;
    reg refresh_flag_d, refresh_flag_q;

    reg ready_d, ready_q;
    reg saved_rw_d, saved_rw_q;
    reg [24:0] saved_addr_d, saved_addr_q;
    reg [31:0] saved_data_d, saved_data_q;

    reg rw_op_d, rw_op_q;

    reg [3:0] row_open_d, row_open_q;
    reg [12:0] row_addr_d[3:0], row_addr_q[3:0];

    reg [2:0] precharge_bank_d, precharge_bank_q;
    integer i;

    always @* begin
        // Default values
        dq_d = dq_q;
        dqi_d = sdram_dq;
        dq_en_d = 1'b0; // normally keep the bus in high-Z
        cle_d = cle_q;
        cmd_d = CMD_NOP; // default to NOP
        dqm_d = 1'b0;
        ba_d = 2'd0;
        a_d = 25'd0;
        state_d = state_q;
        next_state_d = next_state_q;
        delay_ctr_d = delay_ctr_q;
        addr_d = addr_q;
        data_d = data_q;
        out_valid_d = 1'b0;
        precharge_bank_d = precharge_bank_q;
        rw_op_d = rw_op_q;
        byte_ctr_d = 2'd0;

        row_open_d = row_open_q;

        // row_addr is a 2d array and must be coppied this way
        for (i = 0; i < 4; i = i + 1)
            row_addr_d[i] = row_addr_q[i];

        // The data in the SDRAM must be refreshed periodically.
        // This conter ensures that the data remains intact.
        refresh_flag_d = refresh_flag_q;
        refresh_ctr_d = refresh_ctr_q + 1'b1;
        if (refresh_ctr_q > 10'd750) begin
            refresh_ctr_d = 10'd0;
            refresh_flag_d = 1'b1;
        end

        saved_rw_d = saved_rw_q;
        saved_data_d = saved_data_q;
        saved_addr_d = saved_addr_q;
        ready_d = ready_q;

        // This is a queue of 1 for read/write operations.
        // When the queue is empty we aren't busy and can
        // accept another request.
        if (ready_q && in_valid) begin
            saved_rw_d = rw;
            saved_data_d = data_in;
            saved_addr_d = addr;
            ready_d = 1'b0;
        end

        case (state_q)
            ///// INITALIZATION /////
            INIT: begin
                ready_d = 1'b0;
                row_open_d = 4'b0;
                out_valid_d = 1'b0;
                a_d = 13'b0;
                ba_d = 2'b0;
                cle_d = 1'b1;
                state_d = WAIT;
                delay_ctr_d = 16'd10100; // wait for 101us
                next_state_d = PRECHARGE_INIT;
                dq_en_d = 1'b0;
            end
            WAIT: begin
                delay_ctr_d = delay_ctr_q - 1'b1;
                if (delay_ctr_q == 13'd0) begin
                    state_d = next_state_q;
                    if (next_state_q == WRITE) begin
                        dq_en_d = 1'b1; // enable the bus early
                        dq_d = data_q[15:0];
                    end
                end
            end
            PRECHARGE_INIT: begin
                cmd_d = CMD_PRECHARGE;
                a_d[10] = 1'b1; // all banks
                ba_d = 2'd0;
                state_d = WAIT;
                next_state_d = REFRESH_INIT_1;
                delay_ctr_d = 13'd0;
            end
            REFRESH_INIT_1: begin
                cmd_d = CMD_REFRESH;
                state_d = WAIT;
                delay_ctr_d = 13'd7;
                next_state_d = REFRESH_INIT_2;
            end
            REFRESH_INIT_2: begin
                cmd_d = CMD_REFRESH;
                state_d = WAIT;
                delay_ctr_d = 13'd7;
                next_state_d = LOAD_MODE_REG;
            end
            LOAD_MODE_REG: begin
                cmd_d = CMD_LOAD_MODE_REG;
                ba_d = 2'b0;
                // Reserved, Burst Access, Standard Op, CAS = 2, Sequential, Burst = 4
                a_d = {3'b000, 1'b0, 2'b00, 3'b010, 1'b0, 3'b010}; //010
                state_d = WAIT;
                delay_ctr_d = 13'd1;
                next_state_d = IDLE;
                refresh_flag_d = 1'b0;
                refresh_ctr_d = 10'b1;
                ready_d = 1'b1;
            end

            ///// IDLE STATE /////
            IDLE: begin
				    doing_refresh_out = 1'b0;
                if (refresh_flag_q) begin // we need to do a refresh
                    state_d = PRECHARGE;
                    next_state_d = REFRESH;
                    precharge_bank_d = 3'b100; // all banks
                    refresh_flag_d = 1'b0; // clear the refresh flag
                end else if (!ready_q) begin // operation waiting
                    ready_d = 1'b1; // clear the queue
                    rw_op_d = saved_rw_q; // save the values we'll need later
                    addr_d = saved_addr_q;

                    if (saved_rw_q) // Write
                        data_d = saved_data_q;

                    // if the row is open we don't have to activate it
                    if (row_open_q[saved_addr_q[24:23]]) begin
                        if (row_addr_q[saved_addr_q[24:23]] == saved_addr_q[22:10]) begin
                            // Row is already open
                            if (saved_rw_q)
                                state_d = WRITE;
                            else
                                state_d = READ;
                        end else begin
                            // A different row in the bank is open
                            state_d = PRECHARGE; // precharge open row
                            precharge_bank_d = {1'b0, saved_addr_q[24:23]};
                            next_state_d = ACTIVATE; // open current row
                        end
                    end else begin
                        // no rows open
                        state_d = ACTIVATE; // open the row
                    end
                end
            end

            ///// REFRESH /////
            REFRESH: begin
				    doing_refresh_out = 1'b1;
                cmd_d = CMD_REFRESH;
                state_d = WAIT;
                delay_ctr_d = 13'd6; // gotta wait 7 clocks (66ns)
                next_state_d = IDLE;
            end

            ///// ACTIVATE /////
            ACTIVATE: begin
                cmd_d = CMD_ACTIVE;
					 //=================//
                a_d = addr_q[22:10];
                ba_d = addr_q[24:23];
                //=================//
					 delay_ctr_d = 13'd0;
                state_d = WAIT;

                if (rw_op_q)
                    next_state_d = WRITE;
                else
                    next_state_d = READ;

                row_open_d[addr_q[24:23]] = 1'b1; // row is now open
                row_addr_d[addr_q[24:23]] = addr_q[22:10];
            end

            ///// READ /////
            READ: begin
                cmd_d = CMD_READ;
                a_d = {2'b0,addr_q[9:0]};
                ba_d = addr_q[24:23];
                state_d = WAIT;
                delay_ctr_d = 13'd2; // wait for the data to show up
                next_state_d = READ_RES;

            end
            READ_RES: begin
                byte_ctr_d = byte_ctr_q + 1'b1; // we need to read in 2 sets of 16
                data_d = {dqi_q, data_q[31:16]}; // shift the data in
                if (byte_ctr_q == 2'd1) begin
                    out_valid_d = 1'b1;
                    state_d = IDLE;
                end
            end

            ///// WRITE /////
            WRITE: begin
                byte_ctr_d = byte_ctr_q + 1'b1; // send out 4 bytes

                if (byte_ctr_q == 2'd0) // first byte send write command
                    cmd_d = CMD_WRITE;

                dq_d = data_q[15:0];
                data_d = {16'h00, data_q[31:16]}; // shift the data out
                dq_en_d = 1'b1; // enable out bus
                a_d = {2'b0,addr_q[9:0]};
                ba_d = addr_q[24:23];

                if (byte_ctr_q == 2'd1) begin
                    state_d = IDLE;
                end
            end

            ///// PRECHARGE /////
            PRECHARGE: begin
                cmd_d = CMD_PRECHARGE;
                a_d[10] = precharge_bank_q[2]; // all banks
                ba_d = precharge_bank_q[1:0];
                state_d = WAIT;
                delay_ctr_d = 13'd0;

                if (precharge_bank_q[2]) begin
                    row_open_d = 4'b0000; // closed all rows
                end else begin
                    row_open_d[precharge_bank_q[1:0]] = 1'b0; // closed one row
                end
            end

            default: state_d = INIT;
        endcase

    end

    always @(posedge clk) begin
        if(rst) begin
            cle_q <= 1'b0;
            dq_en_q <= 1'b0;
            state_q <= INIT;
            ready_q <= 1'b0;
        end else begin
            cle_q <= cle_d;
            dq_en_q <= dq_en_d;
            state_q <= state_d;
            ready_q <= ready_d;
        end

        saved_rw_q <= saved_rw_d;
        saved_data_q <= saved_data_d;
        saved_addr_q <= saved_addr_d;

        cmd_q <= cmd_d;
        dqm_q <= dqm_d;
        ba_q <= ba_d;
        a_q <= a_d;
        dq_q <= dq_d;
        dqi_q <= dqi_d;

        next_state_q <= next_state_d;
        refresh_flag_q <= refresh_flag_d;
        refresh_ctr_q <= refresh_ctr_d;
        data_q <= data_d;
        addr_q <= addr_d;
        out_valid_q <= out_valid_d;
        row_open_q <= row_open_d;
        for (i = 0; i < 4; i = i + 1)
            row_addr_q[i] <= row_addr_d[i];
        precharge_bank_q <= precharge_bank_d;
        rw_op_q <= rw_op_d;
        byte_ctr_q <= byte_ctr_d;
        delay_ctr_q <= delay_ctr_d;
    end
endmodule