Skip to content

SDR SDRAM Controller

SDR SDRAM Controller

SDRAM controller signals

  • output logic [12:0] o_dram_addr: Mainly used to give the column address or the row address to SDRAM.
  • inout [15:0] io_dram_dq: Data being written to or read from SDRAM.
  • output logic [1:0] o_dram_ba: SDRAM bank address.
  • output logic [1:0] o_dram_dqm: Data masking signals. Sometimes also called byte enable. Used to read or write less than 16 bits of data.
  • output logic o_dram_ras_n: Read address strobe (negated).
  • output logic o_dram_cas_n: Column address strobe (negated).
  • output logic o_dram_cke: Clock enable. Always high, i.e. 1, in our case.
  • output logic o_dram_we_n: Write enable (negated).
  • output logic o_dram_cs_n: Chip select (negated).

SDRAM controller FSM

The FSM of the SDRAM controller can be viewed as a hierarchical FSM (H-FSM), somewhat mirroring the FSM inside the SDRAM. At the higher level we have the following states:

  • S_IDLE: Idle state.
  • S_INIT_SEQ: SDRAM initialization sequence. This sequence must be executed once after reset in order to bring the SDRAM into operational state.
  • S_READ_SEQ: Data read sequence.
  • S_WRITE_SEQ: Data write sequence.
  • S_AUTO_REFRESH_SEQ: Auto refresh sequence. This sequence should be executed periodically in order to keep the data in SDRAM alive.

This top-level view is depicted in the diagram below:

Low-level FSM diagrams of individual sequences are given below:

Controller for SDR SDRAM found on Terasic DE0-Nano FPGA board.

Datasheet: https://www.issi.com/WW/pdf/42-45S83200J-16160J.pdf

import defines::*;

module ctrl_sdram
  #(parameter DATA_WIDTH         = 16,
    parameter ROW_ADDR_WIDTH     = 13,
    parameter COL_ADDR_WIDTH     = 9,
    parameter BANK_ADDR_WIDTH    = 2,
    parameter SDRAM_CLK_FREQ     = 100,
    parameter SDRAM_BURST_LENGTH = 1,
    parameter SDRAM_BURST_TYPE   = "Sequential",
    parameter SDRAM_LATENCY_MODE = 2)
   (input logic                i_clk,
    output logic [12:0]        o_dram_addr,
    inout [(DATA_WIDTH - 1):0] io_dram_dq,
    output logic [1:0]         o_dram_ba,
    output logic [1:0]         o_dram_dqm,
    output logic               o_dram_ras_n,
    output logic               o_dram_cas_n,
    output logic               o_dram_cke,
    output logic               o_dram_we_n,
    output logic               o_dram_cs_n,
    intf_axi4.slave            axi_bus);

   localparam SDRAM_BURST_IDX_WIDTH = $clog2(SDRAM_BURST_LENGTH);
   localparam NUM_BANKS             = 4;
   localparam MEMORY_SIZE           = (1 << (ROW_ADDR_WIDTH + COL_ADDR_WIDTH)) * NUM_BANKS * (DATA_WIDTH / 8);
   localparam INTERNAL_ADDR_WIDTH   = ROW_ADDR_WIDTH + COL_ADDR_WIDTH + $clog2(NUM_BANKS);
   localparam SDRAM_ADDR_WIDTH      = $size(o_dram_addr);
   localparam SDRAM_MODE            = SDRAM_ADDR_WIDTH'('b000_0_00_010_0_000);
   localparam TIMER_WIDTH           = 24;

   localparam T_POWERON            = 10000;                  // 100us
   localparam T_REFRESH            = 6400000;                // 64ms
   localparam T_ROW_PRECHARGE      = 2 - 1;                  // t_RP
   localparam T_AUTO_REFRESH_CYCLE = 6 - 1;                  // t_RC
   localparam T_RAS_CAS_DELAY      = SDRAM_LATENCY_MODE - 1; // t_RCD
   localparam T_CAS_LATENCY        = SDRAM_LATENCY_MODE - 1; // t_CAS

   typedef enum {
      S_IDLE,
      S_INIT_SEQ_POWERON,
      S_INIT_SEQ_PRECHARGE,
      S_INIT_SEQ_AUTO_REFRESH0,
      S_INIT_SEQ_AUTO_REFRESH1,
      S_INIT_SEQ_MODE_REGISTER_SET,
      S_READ_SEQ_ROW_DEACTIVATE,
      S_READ_SEQ_ROW_ACTIVATE,
      S_READ_SEQ_CAS_WAIT,
      S_READ_SEQ_READ,
      S_WRITE_SEQ_ROW_DEACTIVATE,
      S_WRITE_SEQ_ROW_ACTIVATE,
      S_WRITE_SEQ_WRITE,
      S_AUTO_REFRESH_SEQ_PRECHARGE,
      S_AUTO_REFRESH_SEQ_AUTO_REFRESH
   } state_t;

   typedef enum logic [3:0] {
      CMD_DEVICE_DESELECT   = 4'b1xxx,
      CMD_NOP               = 4'b0111,
      CMD_BURST_STOP        = 4'b0110,
      CMD_READ              = 4'b0101,
      CMD_WRITE             = 4'b0100,
      CMD_ROW_ACTIVATE      = 4'b0011,
      CMD_PRECHARGE         = 4'b0010,
      CMD_AUTO_REFRESH      = 4'b0001,
      CMD_MODE_REGISTER_SET = 4'b0000
   } cmd_t;

   logic [(TIMER_WIDTH - 1):0]         refresh_timer, refresh_timer_nxt;
   logic [(TIMER_WIDTH - 1):0]         delay_timer, delay_timer_nxt;
   logic [(ROW_ADDR_WIDTH - 1):0]      bank_active_row [NUM_BANKS];
   logic [(NUM_BANKS - 1):0]           bank_is_active;
   logic                               wt_en;
   logic [(INTERNAL_ADDR_WIDTH - 1):0] wt_addr;
   logic [7:0]                         wt_length;
   logic                               wt_pending;
   logic [($clog2(NUM_BANKS) - 1):0]   wt_bank_addr;
   logic [(COL_ADDR_WIDTH - 1):0]      wt_col_addr;
   logic [(ROW_ADDR_WIDTH - 1):0]      wt_row_addr;
   logic [(INTERNAL_ADDR_WIDTH - 1):0] rd_addr;
   logic [7:0]                         rd_length;
   logic                               rd_pending;
   logic [($clog2(NUM_BANKS) - 1):0]   rd_bank_addr;
   logic [(COL_ADDR_WIDTH - 1):0]      rd_col_addr;
   logic [(ROW_ADDR_WIDTH - 1):0]      rd_row_addr;
   cmd_t                               cmd;
   state_t                             state, state_nxt;

   always_ff @(posedge i_clk)
     begin: MANAGEMENT
        delay_timer <= delay_timer_nxt;
        refresh_timer <= refresh_timer_nxt;
        state <= state_nxt;
        case (state)
          S_READ_SEQ_ROW_ACTIVATE:
            begin
               bank_active_row[rd_bank_addr] <= rd_row_addr;
               bank_is_active[rd_bank_addr] <= 1;
            end
          S_WRITE_SEQ_ROW_ACTIVATE:
            begin
               bank_active_row[wt_bank_addr] <= wt_row_addr;
               bank_is_active[wt_bank_addr] <= 1;
            end
          S_AUTO_REFRESH_SEQ_PRECHARGE:
            begin
               for (int i = 0; i < NUM_BANKS; i++) begin
                  bank_is_active[i] <= 0;
               end
            end
          default:;
        endcase
     end

   always_ff @(posedge i_clk)
     begin: AXI4
        if (wt_pending && state == S_WRITE_SEQ_WRITE && state_nxt != S_WRITE_SEQ_WRITE) begin
           // The bus transfer may be longer than the SDRAM burst.
           // Determine if we are done yet.
           wt_length <= wt_length - 8'(SDRAM_BURST_LENGTH);
           wt_addr <= wt_addr + INTERNAL_ADDR_WIDTH'(SDRAM_BURST_LENGTH);
           if (wt_length == SDRAM_BURST_LENGTH - 1) begin
              wt_pending <= 0;
           end
        end else if (axi_bus.m_awvalid && !wt_pending) begin
           // Start a write burst
           // axi_bus.m_awaddr is in terms of bytes.  Convert to # of transfers.
           wt_addr <= INTERNAL_ADDR_WIDTH'(axi_bus.m_awaddr[AXI_ADDR_WIDTH - 1:$clog2(DATA_WIDTH / 8)]);
           wt_length <= axi_bus.m_awlen;
           wt_pending <= 1'b1;
        end

        if (rd_pending && state == S_READ_SEQ_READ && state_nxt != S_READ_SEQ_READ) begin
           rd_length <= rd_length - 8'(SDRAM_BURST_LENGTH);
           rd_addr <= rd_addr + INTERNAL_ADDR_WIDTH'(SDRAM_BURST_LENGTH);
           if (rd_length == SDRAM_BURST_LENGTH - 1) begin
              rd_pending <= 0;
           end
        end else if (axi_bus.m_arvalid && !rd_pending) begin
           // Start a read burst
           // axi_bus.m_araddr is in terms of bytes.  Convert to # of transfers.
           rd_addr <= INTERNAL_ADDR_WIDTH'(axi_bus.m_araddr[AXI_ADDR_WIDTH - 1:$clog2(DATA_WIDTH / 8)]);
           rd_length <= axi_bus.m_arlen;
           rd_pending <= 1'b1;
        end
     end

   always_comb
     begin: FSM
        cmd = CMD_NOP;
        o_dram_ba = 0;
        o_dram_addr = 0;
        o_dram_dqm = 2'b11;
        wt_en = 0;
        delay_timer_nxt = 0;
        state_nxt = state;

        if (refresh_timer != 0) begin
           refresh_timer_nxt = refresh_timer - TIMER_WIDTH'(1);
        end else begin
           refresh_timer_nxt = 0;
        end

        if (delay_timer != 0) begin
           delay_timer_nxt = delay_timer - TIMER_WIDTH'(1);
        end else begin
           case (state)
             S_IDLE:
               begin
                  if (refresh_timer == 0) begin
                     state_nxt = |bank_is_active ? S_AUTO_REFRESH_SEQ_PRECHARGE : S_AUTO_REFRESH_SEQ_AUTO_REFRESH;
                  end else if (rd_pending && (!wt_pending || wt_addr != rd_addr)) begin
                     if (!bank_is_active[rd_bank_addr]) begin
                        state_nxt = S_READ_SEQ_ROW_ACTIVATE;
                     end else if (rd_row_addr != bank_active_row[rd_bank_addr]) begin
                        state_nxt = S_READ_SEQ_ROW_DEACTIVATE;
                     end else begin
                        state_nxt = S_READ_SEQ_CAS_WAIT;
                     end
                  end else if (wt_pending && (!rd_pending || wt_addr == rd_addr)) begin
                     if (!bank_is_active[wt_bank_addr]) begin
                        state_nxt = S_WRITE_SEQ_ROW_ACTIVATE;
                     end else if (wt_row_addr != bank_active_row[wt_bank_addr]) begin
                        state_nxt = S_WRITE_SEQ_ROW_DEACTIVATE;
                     end else begin
                        state_nxt = S_WRITE_SEQ_WRITE;
                     end
                  end
               end
             S_INIT_SEQ_POWERON:
               begin
                  delay_timer_nxt = TIMER_WIDTH'(T_POWERON);
                  state_nxt = S_INIT_SEQ_PRECHARGE;
               end
             S_INIT_SEQ_PRECHARGE:
               begin
                  cmd = CMD_PRECHARGE;
                  o_dram_addr = SDRAM_ADDR_WIDTH'('b00_1_0000000000);
                  delay_timer_nxt = TIMER_WIDTH'(T_ROW_PRECHARGE);
                  state_nxt = S_INIT_SEQ_AUTO_REFRESH0;
               end
             S_INIT_SEQ_AUTO_REFRESH0:
               begin
                  cmd = CMD_AUTO_REFRESH;
                  delay_timer_nxt = TIMER_WIDTH'(T_AUTO_REFRESH_CYCLE);
                  refresh_timer_nxt = TIMER_WIDTH'(T_REFRESH);
                  state_nxt = S_INIT_SEQ_AUTO_REFRESH1;
               end
             S_INIT_SEQ_AUTO_REFRESH1:
               begin
                  cmd = CMD_AUTO_REFRESH;
                  delay_timer_nxt = TIMER_WIDTH'(T_AUTO_REFRESH_CYCLE);
                  refresh_timer_nxt = TIMER_WIDTH'(T_REFRESH);
                  state_nxt = S_INIT_SEQ_MODE_REGISTER_SET;
               end
             S_INIT_SEQ_MODE_REGISTER_SET:
               begin
                  cmd = CMD_MODE_REGISTER_SET;
                  o_dram_ba = 2'b00;
                  o_dram_addr = SDRAM_MODE;
                  state_nxt = S_IDLE;
               end
             S_READ_SEQ_ROW_DEACTIVATE:
               begin
                  cmd = CMD_PRECHARGE;
                  o_dram_ba = rd_bank_addr;
                  o_dram_addr = 0;
                  delay_timer_nxt = TIMER_WIDTH'(T_ROW_PRECHARGE);
                  state_nxt = S_READ_SEQ_ROW_ACTIVATE;
               end
             S_READ_SEQ_ROW_ACTIVATE:
               begin
                  cmd = CMD_ROW_ACTIVATE;
                  o_dram_ba = rd_bank_addr;
                  o_dram_addr = SDRAM_ADDR_WIDTH'(rd_row_addr);
                  delay_timer_nxt = TIMER_WIDTH'(T_RAS_CAS_DELAY);
                  state_nxt = S_READ_SEQ_CAS_WAIT;
               end
             S_READ_SEQ_CAS_WAIT:
               begin
                  cmd = CMD_READ;
                  o_dram_ba = rd_bank_addr;
                  o_dram_addr = SDRAM_ADDR_WIDTH'(rd_col_addr);
                  o_dram_dqm = 2'b00;
                  delay_timer_nxt = TIMER_WIDTH'(T_CAS_LATENCY);
                  state_nxt = S_READ_SEQ_READ;
               end
             S_READ_SEQ_READ:
               begin
                  state_nxt = S_IDLE;
               end
             S_WRITE_SEQ_ROW_DEACTIVATE:
               begin
                  cmd = CMD_PRECHARGE;
                  o_dram_ba = wt_bank_addr;
                  o_dram_addr = 0;
                  delay_timer_nxt = TIMER_WIDTH'(T_ROW_PRECHARGE);
                  state_nxt = S_WRITE_SEQ_ROW_ACTIVATE;
               end
             S_WRITE_SEQ_ROW_ACTIVATE:
               begin
                  cmd = CMD_ROW_ACTIVATE;
                  o_dram_ba = wt_bank_addr;
                  o_dram_addr = SDRAM_ADDR_WIDTH'(wt_row_addr);
                  delay_timer_nxt = TIMER_WIDTH'(T_RAS_CAS_DELAY);
                  state_nxt = S_WRITE_SEQ_WRITE;
               end
             S_WRITE_SEQ_WRITE:
               begin
                  cmd = CMD_WRITE;
                  o_dram_ba = wt_bank_addr;
                  o_dram_addr = SDRAM_ADDR_WIDTH'(wt_col_addr);
                  o_dram_dqm = 2'b00;
                  wt_en = 1;
                  state_nxt = S_IDLE;
               end
             S_AUTO_REFRESH_SEQ_PRECHARGE:
               begin
                  cmd = CMD_PRECHARGE;
                  o_dram_addr = SDRAM_ADDR_WIDTH'('b00_1_0000000000);
                  delay_timer_nxt = TIMER_WIDTH'(T_ROW_PRECHARGE);
                  state_nxt = S_AUTO_REFRESH_SEQ_AUTO_REFRESH;
               end
             S_AUTO_REFRESH_SEQ_AUTO_REFRESH:
               begin
                  cmd = CMD_AUTO_REFRESH;
                  delay_timer_nxt = TIMER_WIDTH'(T_AUTO_REFRESH_CYCLE);
                  refresh_timer_nxt = TIMER_WIDTH'(T_REFRESH);
                  state_nxt = S_IDLE;
               end
             default:
               begin
                  state_nxt = S_IDLE;
               end
           endcase
        end
     end

   assign {wt_row_addr, wt_bank_addr, wt_col_addr} = wt_addr;
   assign {rd_row_addr, rd_bank_addr, rd_col_addr} = rd_addr;

   assign {o_dram_cs_n, o_dram_ras_n, o_dram_cas_n, o_dram_we_n} = cmd;
   assign io_dram_dq = wt_en ? axi_bus.m_wdata : {DATA_WIDTH{1'hZ}};
   assign o_dram_cke = 1'b1;

   assign axi_bus.s_arready = !rd_pending;
   assign axi_bus.s_awready = !wt_pending;
   assign axi_bus.s_rvalid = (state == S_READ_SEQ_READ && state_nxt == S_IDLE) ? 1 : 0;
   assign axi_bus.s_wready = (state == S_IDLE && state_nxt == S_IDLE) ? 1 : 0;
   assign axi_bus.s_bvalid = 1;
   assign axi_bus.s_rdata = (axi_bus.m_rready && axi_bus.s_rvalid) ? io_dram_dq : DATA_WIDTH'(0);

   initial
     begin
        for (int i = 0; i < NUM_BANKS; i++) begin
           bank_active_row[i] = 0;
           bank_is_active[i] = 0;
        end

        state = S_INIT_SEQ_POWERON;
        delay_timer = '0;
        refresh_timer = TIMER_WIDTH'(T_REFRESH);

        rd_addr = '0;
        rd_length = '0;
        rd_pending = '0;

        wt_addr = '0;
        wt_length = '0;
        wt_pending = '0;
     end
endmodule