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