SDRAM interface slashes pin count
Many designs need deep buffering but don't require ultrahigh-memory bandwidth. Examples include image and audio processing, as well as some deep-FIFO applications. These designs often use a single ×8 SDRAM device that connects to an FPGA or ASIC. This approach solves the buffering problem but also burns a lot of valuable pins, which can be as many as 27 for a single SDRAM device. The design inFigure 1 takes advantage of the burst counter inside the SDRAM to reduce this pin count to 18 by multiplexing the lower eight address lines with the data. The efficiency loss is low; the design requires only one extra clock during the write burst. Figure 1uses an 8Mx8, 125-MHz SDRAM, but this technique works with any SDRAM.
The read- and write-cycle timing diagrams reveal the secret (Figure 2). The figureshows a burst of 4, but any power-of-2 burst works. These diagrams assume a 50-MHz system clock, a read latency of 2, and a full-page burst. During the read cycle (Figure 2a), the data bus is inactive during the initial portion of the cycle (when the row and column addresses are presented), so there's no problem with using the data bus to carry address data. A precharge command ends the burst and prepares the RAM for the next access.
For the write cycle, however, some trickery is necessary (Figure 2b). Normally, the first byte of write data is presented to the SDRAM with the Write command, along with the starting column address for that burst. By asserting the DQM (data-mask) signal, the SDRAM ignores the data lines during this phase, thus allowing them to be used for the column address.
Note that the DQM signal does not prevent the internal column address from incrementing, however. Thus, the write-column address presented with the Write command must be one less than the desired burst starting address. For FIFO designs, this requirement is trivial because you can initialize the write-address column counter to –1 rather than 0. The column-address counter in the SDRAM wraps around at the end of the column, so this approach works even at the beginning of a column.
You can download a simplified version of a FIFO controller that uses this technique, described in the Verilog language. The listing omits some of the details, such as SDRAM refresh/init cycles, and FIFO flags, to highlight the portions relevant to this design. This controller uses a simple eight-state finite-state machine to generate the SDRAM control signals and uses a pair of row/column counters to keep track of the FIFO put/get pointers. The special initialization and incrementing of the write row and column pointers satisfies the requirement that the write column start off one behind the desired write address. The code occupies 35% of a small Xilinx SpartanXL-S10 device, and runs at 50 MHz. For the sake of example, all of the outputs are combinatorial, but a true high-speed design should use registered I/O.
You can extend this idea to ×16 SDRAMs and to multiplex a few more of the address lines while getting a boost in memory bandwidth. If you do extend the idea, be careful with SDRAM line A10 because this line has special meaning during some SDRAM commands. You can also use this technique with double-data-rate SDRAMs.
;********************************************************************************* ; di2659.txt ; ; LISTING 1 - FIFO CONTROLLER ; ; "SDRAM interface slashes pin count," EDN, March 29, 2001, pg 132 ; http://www.ednmag.com/ednmag/reg/2001/03292001/designideas.htm#07di4 ;********************************************************************************* `timescale 1 ns / 1 ns /* ** Minimal-pin SDRAM controller with FIFO address counters. This ** has a simple do_write/wr_ok, do_read/rd_ok handshake for data input & ** output. The 'rst_fifo' signal syncronously resets the fifo ** ** Author: Tim Hellman, M&M Consulting */ module sdctl(clk, reset_, rst_fifo, do_write, do_read, wr_data_in, /* Fifo I/O */ wr_ok, rd_ok, rd_data_out, sd_ras_, sd_cas_, sd_we_, sd_dqm, sd_bank, /* SDRAM signals */ sd_addr_hi, sd_data ); input clk, reset_, do_write, do_read, rst_fifo; input [7:0] wr_data_in; output wr_ok, rd_ok, sd_ras_, sd_cas_, sd_we_, sd_dqm; output [7:0] rd_data_out; output [1:0] sd_bank; output [11:8] sd_addr_hi; inout [7:0] sd_data; /* ** States */ parameter StIdle = 0, StActive = 1, StRdWr = 2, StData1 = 3, StData2 = 4, StData3 = 5, StRdPrech = 6, StWrPrech = 7; reg [2:0] State; reg [13:0] RdRow, WrRow; reg [8:2] RdCol, WrCol; reg sd_data_ena, doing_write; always @(posedge clk or negedge reset_) if (!reset_) begin RdRow <= 0; WrRow <= 0; RdCol <= 0; WrCol <= 7'h7f; sd_data_ena <= 0; State <= StIdle; doing_write <= 0; end else begin /* ** State machine. Once kicked off, run through all states */ if (State == StIdle && (do_write | do_read)) begin doing_write <= #1 do_write; State <= #1 State + 1; end else if (State != StIdle) State <= #1 State + 1; /* ** Read/write counters (note that write col counter starts ** one behind read). The Write row also increments when ** WrCol == '7e (because WrCol is always one behind) */ if (rst_fifo) {RdRow,RdCol} <= #1 0; else if (State == StWrPrech && !doing_write) {RdRow,RdCol} <= #1 {RdRow,RdCol} + 1; if (rst_fifo) WrCol <= #1 7'h7f; else if (State == StWrPrech && doing_write) WrCol <= #1 WrCol + 1; if (rst_fifo) WrRow <= #1 0; else if (State == StWrPrech && doing_write && WrCol == 7'h7e) WrRow <= #1 WrRow + 1; if (State == StIdle && (do_read | do_write)) sd_data_ena <= #1 1; else if (doing_write && (State == StRdPrech) || !doing_write && (State == StRdWr)) sd_data_ena <= #1 0; end /* ** Generate the SDRAM 'command' bits (combinatorially) */ reg [2:0] SdCmd; always @(State or doing_write) begin case (State) StActive: SdCmd = 3; StRdWr: SdCmd = doing_write ? 4 : 5; StRdPrech: SdCmd = doing_write ? 7 : 2; StWrPrech: SdCmd = doing_write ? 2 : 7; default: SdCmd = 7; // NOP endcase end assign {sd_ras_, sd_cas_, sd_we_} = SdCmd; /* ** Data output is either address or data */ wire [8:0] ColAddr = doing_write ? {WrCol, 2'b11} : {RdCol, 2'b00}; wire [13:0] RowAddr = doing_write ? WrRow : RdRow; assign wr_ok = (State >= StData1 && State <= StRdPrech) & doing_write; assign rd_ok = (State >= StData2 && State <= StWrPrech) & !doing_write; /* Upper bits of column. address must equal 0 */ wire [11:0] MuxAddr = (State == StActive) ? RowAddr : {3'b0,ColAddr}; assign sd_bank = RowAddr[13:12]; assign sd_addr_hi = MuxAddr[11:8]; wire [7:0] DataOut = (State == StActive || State == StRdWr) ? MuxAddr[7:0] : wr_data_in; assign sd_data = sd_data_ena ? DataOut : 'hz; assign rd_data_out = sd_data; assign sd_dqm = doing_write ? (State == StRdWr | State == StWrPrech) : (State == StRdPrech); endmodule
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本