// ----------------------------------------------------------------------- // // Copyright 2010-2021 H. Peter Anvin - All Rights Reserved // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, // Boston MA 02110-1301, USA; either version 2 of the License, or // (at your option) any later version; incorporated herein by reference. // // ----------------------------------------------------------------------- // // Simple SDRAM controller // // Very simple non-parallelizing SDRAM controller. // // // Two ports are provided: // Port 1 does aligned 4-byte accesses with byte enables. // Port 2 does aligned 8-byte accesses, write only, with no byte // enables; it supports streaming from a FIFO. // // Port 1 is multiplexed via an arbiter, which receives a bus // defined by the sdram_bus interface. // // All signals are in the sdram clock domain. // // [rw]ack is asserted at the beginning of a read- or write cycle and // deasserted afterwards; rready is asserted once all data is read and // the read data (rdX port) is valid; it remains asserted after the // transaction is complete and rack is deasserted. // // // The interface to the port modules. The read data is 16 bits // at a time, and is only valid in the cycle rstrb[x] is asserted. // // The only output signal that is unique to this port // is "start". All other signals are broadcast. // interface dram_bus; logic [1:0] prio; // Priority vs refresh logic rst_n; logic clk; logic [24:0] addr; logic addr0; // addr[0] latched at transaction start logic [15:0] rd; logic req; logic [1:0] rstrb; // Data read strobe logic [31:0] wd; logic [3:0] wstrb; logic start; // Transaction start logic wrack; // Transaction is a write // Upstream direction modport ustr ( input prio, output rst_n, output clk, input addr, output addr0, output rd, input req, output rstrb, input wd, input wstrb, output start, output wrack ); // Downstream direction modport dstr ( output prio, input rst_n, input clk, output addr, input addr0, input rd, output req, input rstrb, output wd, output wstrb, input start, input wrack ); endinterface // dram_bus // Port into the DRAM module dram_port #(parameter width = 32) ( dram_bus.dstr bus, input [1:0] prio, input [24:0] addr, output reg [width-1:0] rd, input valid, output reg ready, input [width-1:0] wd, input [(width >> 3)-1:0] wstrb ); reg started; assign bus.prio = prio; assign bus.addr = addr; assign bus.req = valid & ~started; always_comb begin bus.wd = 32'hxxxx_xxxx; bus.wstrb = 4'b0000; if (width == 8) begin bus.wd[15:0] = { wd, wd }; bus.wstrb[1:0] = { wstrb[0] & addr[0], wstrb[0] & ~addr[0] }; end else begin bus.wd[width-1:0] = wd; bus.wstrb[(width >> 3)-1:0] = wstrb; end end always @(negedge bus.rst_n or posedge bus.clk) if (~bus.rst_n) begin ready <= 1'b0; started <= 1'b0; end else begin if (~valid) begin ready <= 1'b0; started <= 1'b0; end else if (bus.start) begin started <= 1'b1; ready <= bus.wrack; end else if (started & ~ready) begin ready <= bus.rstrb[(width - 1) >> 4]; end end // else: !if(~bus.rst_n) genvar i; generate for (i = 0; i < ((width + 15) >> 4); i++) begin : w always @(posedge bus.clk) if (started & ~ready & bus.rstrb[i]) begin if (width == 8) rd <= bus.addr0 ? bus.rd[15:8] : bus.rd[7:0]; else rd[i*16+15:i*16] <= bus.rd; end end endgenerate endmodule // dram_port module dram_arbiter #(parameter port_count = 1) ( dram_bus.ustr ustr [1:port_count], dram_bus.dstr dstr, input [1:0] rfsh_prio, output logic do_rfsh ); logic [31:0] u_wd[1:port_count]; logic [3:0] u_wstrb[1:port_count]; logic [24:0] u_addr[1:port_count]; logic [port_count:0] grant; assign grant[0] = 1'b0; // Dummy to make the below logic simpler reg [port_count:0] grant_q; always @(negedge dstr.rst_n or posedge dstr.clk) if (~dstr.rst_n) grant_q <= 'b0; else grant_q <= grant; generate genvar i; for (i = 1; i <= port_count; i++) begin : u assign ustr[i].rst_n = dstr.rst_n; assign ustr[i].clk = dstr.clk; assign ustr[i].addr0 = dstr.addr0; assign ustr[i].rd = dstr.rd; assign ustr[i].rstrb = dstr.rstrb; assign ustr[i].wrack = dstr.wrack; assign grant[i] = ~|grant[i-1:0] & ustr[i].req & (ustr[i].prio >= rfsh_prio); assign u_addr[i] = ustr[i].addr; assign u_wd[i] = ustr[i].wd; assign u_wstrb[i] = ustr[i].wstrb; // Note: start indicates that the requestor from the *previous* // cycle was started. assign ustr[i].start = grant_q[i] & dstr.start; end // block: u endgenerate always_comb begin dstr.addr = 'bx; dstr.wd = 'bx; dstr.wstrb = 4'b0; dstr.req = 1'b0; do_rfsh = |rfsh_prio; for (int j = 1; j <= port_count; j++) if (grant[j]) begin dstr.addr = u_addr[j]; dstr.wd = u_wd[j]; dstr.wstrb = u_wstrb[j]; dstr.req = 1'b1; do_rfsh = 1'b0; end end // always_comb endmodule // dram_arbiter module sdram #( parameter port1_count = 1, // Timing parameters // The parameters are hardcoded for Micron MT48LC16M16A2-6A, // per datasheet: // 100 MHz 167 MHz // ---------------------------------------------------------- // CL 2 3 READ to data out // tRCD 18 ns 2 3 ACTIVE to READ/WRITE // tRFC 60 ns 6 10 REFRESH to ACTIVE // tRP 18 ns 2 3 PRECHARGE to ACTIVE/REFRESH // tRAS 42 ns 5 7 ACTIVE to PRECHARGE // tRC 60 ns 6 10 ACTIVE to ACTIVE (same bank) // tRRD 12 ns 2 2 ACTICE to ACTIVE (different bank) // tWR 12 ns 2 2 Last write data to PRECHARGE // tMRD 2 2 MODE REGISTER to ACTIVE/REFRESH // // These parameters are set by power of 2: // tREFi 64/8192 ms 781 1302 Refresh time per row (max) // tP 100 us 10000 16667 Time until first command (min) t_cl = 3, t_rcd = 3, t_rfc = 10, t_rp = 3, t_ras = 7, t_rc = 10, t_rrd = 2, t_wr = 2, t_mrd = 2, t_refi_lg2 = 10, // 1024 cycles t_p_lg2 = 15, // 32768 cycles burst_lg2 = 1 // log2(burst length) ) ( // Reset and clock input rst_n, input clk, input init_tmr, // tRP timer input rfsh_tmr, // tREFI/2 timer // SDRAM hardware interface output sr_cs_n, // SDRAM CS# output sr_ras_n, // SDRAM RAS# output sr_cas_n, // SDRAM CAS# output sr_we_n, // SDRAM WE# output [1:0] sr_dqm, // SDRAM DQM (per byte) output [1:0] sr_ba, // SDRAM bank selects output [12:0] sr_a, // SDRAM address bus inout [15:0] sr_dq, // SDRAM data bus // Port 1 dram_bus.ustr port1 [1:port1_count], // Port 2 input [24:1] a2, input [15:0] wd2, input [1:0] wrq2, output reg wacc2 // Data accepted, advance data & addr ); `include "functions.sv" // For modelsim // Mode register data wire mrd_wburst = 1'b1; // Write bursts enabled wire [2:0] mrd_cl = t_cl; wire [2:0] mrd_burst = burst_lg2; wire mrd_interleave = 1'b0; // Interleaved bursts wire [12:0] mrd_val = { 3'b000, // Reserved ~mrd_wburst, // Write burst disable 2'b00, // Normal operation mrd_cl, // CAS latency mrd_interleave, // Interleaved bursts mrd_burst }; // Burst length // Where to issue a PRECHARGE when we only want to read one word // (terminate the burst as soon as possible, but no sooner...) localparam t_pre_rd_when = max(t_ras, t_rcd + 1); // Where to issue a PRECHARGE when we only want to write one word // (terminate the burst as soon as possible, but no sooner...) localparam t_pre_wr_when = max(t_ras, t_rcd + t_wr); // Actual burst length (2^burst_lg2) localparam burst_n = 1 << burst_lg2; // Command opcodes and attributes (is_rfsh, CS#, RAS#, CAS#, WE#) localparam cmd_desl = 5'b0_1111; // Deselect (= NOP) localparam cmd_nop = 5'b0_0111; // NO OPERATION localparam cmd_bst = 5'b0_0110; // BURST TERMINATE localparam cmd_rd = 5'b0_0101; // READ localparam cmd_wr = 5'b0_0100; // WRITE localparam cmd_act = 5'b0_0011; // ACTIVE localparam cmd_pre = 5'b0_0010; // PRECHARGE localparam cmd_ref = 5'b1_0001; // AUTO REFRESH localparam cmd_mrd = 5'b0_0000; // LOAD MODE REGISTER reg [4:0] dram_cmd; wire is_rfsh = dram_cmd[4]; assign sr_cs_n = dram_cmd[3]; assign sr_ras_n = dram_cmd[2]; assign sr_cas_n = dram_cmd[1]; assign sr_we_n = dram_cmd[0]; // SDRAM output signal registers reg [12:0] dram_a; assign sr_a = dram_a; reg [1:0] dram_ba; assign sr_ba = dram_ba; reg [1:0] dram_dqm; assign sr_dqm = dram_dqm; reg [15:0] dram_d; // Data to DRAM reg [15:0] dram_q; // Data from DRAM (I/O buffers) reg dram_d_en; // Drive data out assign sr_dq = dram_d_en ? dram_d : 16'hzzzz; // Refresh timer logic reg rfsh_tmr_q; reg [1:0] rfsh_prio; // Refresh priority (0-3) // Port1 and refresh arbiter dram_bus p1 (); wire do_rfsh; assign p1.rst_n = rst_n; assign p1.clk = clk; dram_arbiter #(.port_count(port1_count)) arbiter ( .ustr ( port1 ), .dstr ( p1.dstr ), .rfsh_prio ( rfsh_prio ), .do_rfsh ( do_rfsh ) ); // The actual values are unimportant; the compiler will optimize // the state machine implementation. typedef enum logic [3:0] { st_reset, // Reset until init timer expires st_init_rfsh, // Refresh cycles during initialization st_init_mrd, // MRD register write during initialization st_ready, // Ready to issue command in the next cycle st_rfsh, // Refresh cycle st_rd_wr_act, // Port 1 ACT command st_rd_wr, // Port 1 transaction st_wr2_act, // Port 2 write ACT command st_wr2 // Port 2 write (burstable) } state_t; state_t state = st_reset; always @(posedge clk or negedge rst_n) if (~rst_n) begin rfsh_tmr_q <= 1'b0; rfsh_prio <= 2'b00; end else begin rfsh_tmr_q <= rfsh_tmr; // Edge detect // Refresh priority management: saturating 2-bit counter if (is_rfsh) rfsh_prio <= 2'b00; // This is a refresh cycle else if (rfsh_tmr & ~rfsh_tmr_q) rfsh_prio <= rfsh_prio + (~&rfsh_prio); end // else: !if(~rst_n) reg [5:0] op_ctr; // Cycle into the current state wire [3:0] op_cycle = op_ctr[3:0]; // Cycle into the current command wire [1:0] init_op_ctr = op_ctr[5:4]; // Init operation counter reg op_zero; // op_cycle wrap around (init_op_ctr changed) reg [31:0] wdata_q; reg [ 3:0] be_q; reg [24:0] addr; reg wrq2_more; wire [13:0] row_addr = addr[24:12]; wire [1:0] bank_addr = addr[11:10]; wire [8:0] col_addr = addr[9:1]; assign p1.addr0 = addr[0]; assign p1.rd = dram_q; // // Careful with the timing here... there is one cycle between // registers and wires, and the DRAM observes the clock 1/2 // cycle from the internal logic. This affects read timing. // // Note that rready starts out as 1. This allows a 0->1 detection // on the rready line to be used as cycle termination signal. // always @(posedge clk or negedge rst_n) if (~rst_n) begin dram_cmd <= cmd_desl; dram_a <= 13'hxxxx; dram_ba <= 2'bxx; dram_dqm <= 2'b00; dram_d <= 16'hxxxx; dram_q <= 16'hxxxx; dram_d_en <= 1'b1; // Don't float except during read op_ctr <= 6'h0; op_zero <= 1'b0; state <= st_reset; p1.start <= 1'b0; p1.wrack <= 1'bx; p1.rd <= 16'hxxxx; p1.rstrb <= 2'b00; wacc2 <= 1'b0; wrq2_more <= 1'bx; wdata_q <= 32'hxxxx_xxxx; be_q <= 4'bxxxx; addr <= 25'bx; end else begin // Default values dram_a <= 13'b0; dram_ba <= bank_addr; dram_dqm <= 2'b00; dram_d <= { 8'hAA, 3'b000, dram_cmd }; dram_cmd <= cmd_nop; dram_d_en <= 1'b1; // Don't float except during read dram_q <= sr_dq; p1.rstrb <= 2'b00; wacc2 <= 1'b0; op_ctr <= op_ctr + 1'b1; op_zero <= &op_cycle; // About to wrap around p1.start <= 1'b0; case (state) st_reset: begin op_ctr <= 6'b0; op_zero <= 1'b0; dram_a[10] <= 1'b1; // Precharge all banks dram_cmd <= cmd_nop; if (init_tmr) begin dram_cmd <= cmd_pre; state <= st_init_rfsh; end end st_init_rfsh: begin if (op_zero) begin dram_cmd <= cmd_ref; if (init_op_ctr == 2'b11) state <= st_init_mrd; end end st_init_mrd: begin dram_a <= mrd_val; dram_ba <= 2'b00; if (op_zero) if (init_op_ctr[0]) state <= st_ready; else dram_cmd <= cmd_mrd; end st_ready: begin op_ctr <= 6'b0; op_zero <= 1'b0; dram_cmd <= cmd_desl; p1.wrack <= 1'bx; be_q <= 4'bxxxx; wdata_q <= 32'hxxxx_xxxx; addr <= 25'bx; dram_a <= 13'h1bb; dram_d <= 16'hbbbb; // Port 1 and refresh have priority over port 2; // the various port 1 instances and refresh have // priorities set by the arbiter block. if (do_rfsh) begin state <= st_rfsh; end else if (p1.req) begin addr <= p1.addr; p1.wrack <= |p1.wstrb; wdata_q <= p1.wd; be_q <= p1.wstrb; state <= st_rd_wr_act; p1.start <= 1'b1; end // if (p1.req) else if (wrq2[0]) begin // Begin port 2 write addr <= { a2, 1'b0 }; state <= st_wr2_act; end end // case: st_ready st_rfsh: begin if (op_cycle == 0) dram_cmd <= cmd_ref; else if (op_cycle == t_rfc-2) state <= st_ready; end st_rd_wr_act: begin op_ctr <= 6'b0; op_zero <= 1'b0; dram_cmd <= cmd_act; dram_a <= row_addr; dram_ba <= bank_addr; state <= st_rd_wr; end st_rd_wr: begin dram_d_en <= p1.wrack; dram_dqm <= {2{p1.wrack}}; dram_d <= 16'hcccc ^ {16{p1.wrack}}; // Commands // // This assumes: // tRCD = 3 // rRRD = 2 // CL = 3 // tRC = 10 // tRAS = 7 // tWR = 2 // tRP = 3 // case (op_cycle) 2: begin dram_a[10] <= 1'b0; // No auto precharge dram_a[8:0] <= col_addr; dram_cmd <= p1.wrack ? cmd_wr : cmd_rd; dram_d <= wdata_q[15:0]; dram_dqm <= {2{p1.wrack}} & ~be_q[1:0]; end 3: begin dram_d <= wdata_q[31:16]; dram_dqm <= {2{p1.wrack}} & ~be_q[3:2]; end 6: begin // Earliest legal cycle to precharge // It seems auto precharge violates tRAS(?) // so do it explicitly. dram_a[10] <= 1'b1; // One bank dram_cmd <= cmd_pre; end // CL+2 cycles after the read command // The +2 accounts for internal and I/O delays 7: begin p1.rstrb[0] <= ~p1.wrack; end 8: begin p1.rstrb[1] <= ~p1.wrack; state <= st_ready; end default: begin // Do nothing end endcase // case (op_cycle) end // case: st_rd_wr st_wr2_act: begin op_ctr <= 6'b0; op_zero <= 1'b0; dram_a <= row_addr; dram_ba <= bank_addr; dram_cmd <= cmd_act; state <= st_wr2; end st_wr2: begin // Streamable write from flash ROM // Note: wacc is asserted in the cycle *before* the // data and address is latched/consumed. dram_d <= wd2; dram_a[10] <= 1'b0; // No auto precharge/precharge one bank dram_a[8:0] <= a2[9:1]; dram_dqm <= 2'b11; case (op_cycle) 0, 1: begin wacc2 <= 1'b1; dram_dqm <= 2'b00; end 2: begin dram_cmd <= cmd_wr; wacc2 <= 1'b1; dram_dqm <= 2'b00; wrq2_more <= wrq2[1] & (~&a2[9:3]); end 3: begin wacc2 <= 1'b1; dram_dqm <= 2'b00; end 4: begin dram_cmd <= cmd_wr; dram_dqm <= 2'b00; if (wrq2_more & ~(p1.req | do_rfsh)) begin // Burst can continue wacc2 <= 1'b1; op_ctr[3:0] <= 4'd1; end end 5: begin dram_dqm <= 2'b00; end 6: begin // Nothing end 7: begin // tWR completed dram_cmd <= cmd_pre; end 8: begin // tRP will be complete before the next ACT state <= st_ready; end default: begin // Do nothing end endcase // case (op_cycle) end // case: st_wr2 endcase // case(state) end // else: !if(~rst_n) endmodule // dram