四. 刷新模块
由于sdram的特殊结构,sdram在使用的过程中,需要每隔一段时间,对所有的存储区域进行一次刷新操作(充电),否则内部存储的数据会丢失,这将会成为后面设计的一大难点。
根据手册得知每64ms需要完成8192次刷新操作,也就是下面的时序图需要在64ms内运行8192次,平均下来7us就要进行一次,这个时间需要记住非常重要。
同样也是根据手册给出的时序图进行编写代码,一共需要发送三个命令
代码实现如下,也是非常容易实现的。
从图中可以看出,每个7090ns进行一次刷新,满足要求。
至此,初始化和刷新模块编写完成,这两部分只需要按照手册上给出的时序图来编写代码即可,比较容易即可完成,后面的读写模块会复杂一些。
五. 写模块
读模块的时序图如下,截取的是没有auto precharge操作的,也就是在数据写完后,需要手动发送一个precharge命令。同样可以让sdram自动完成这个操作,只需要在发送write命令的时候,将A10拉高即可,这样在发送完数据后,就可以直接结束了,不用发送precharge命令。本次介绍的是需要发送precharge命令。
设计是需要清楚以下两个问题
发送过程中,需要切换行地址或者bank的时候,应该怎样操作
发送过程中,突然来了刷新请求时,该如何处理
先对第一个问题进行说明一下,在sdram中,行地址和bank是发送ACTIVE命令时指定的,发送write命令时,就可以指定列地址了,如下BL=1。也就是说切换行地址或bank时需要重新发送ACTIVE命令。ps: 写操作是没有潜伏期的。
手册中也给出了这部分的时序图,如下。需要注意的一点是,它这是使能了auto precharge,所以数据发送完成后,没有发送precharge命令,就发送了ACTIVE命令来切换行地址或bank了。没有使能的情况下,需要加上precharge地址,然后再延时tRCD,发送ACTIVE命令,这点需要注意。
第二个问题,当刷新请求来时,这个时候当然是要暂停发送数据,需要保存已经发送数据的个数,以及当前发送的地址和bank。然后在刷新结束后,继续发送数据。
模块框图和状态机如下。在write_data_en使能的情况下,外部输入数据进来,其余时刻输入的数据无效,相当于一个握手信号。sdram模式寄存器配置的是突发长度为1,所以这里单次写突发长度是没有大小限制的。
sdram_write ( input sdram_clk, input rst_n, input sdram_write_req, //写请求 output sdram_write_ack, //写响应 input sdram_write_pause, //写暂停信号,转去刷新操作 output reg sdram_write_pause_ack, //写暂停响应,成功暂停 input[24:0] write_addr, //写入地址 input[15:0] write_data, //写入数据 {bank[1:0s],row[12:0],clo[9:0]} input[9:0] write_burst_length, //单次写突发长度 //可以为sdram大小 output write_data_en, //写入数据有效输出 //sdram接口 output[3:0] sdram_write_cmd, output[12:0] sdram_write_addr, output[1:0] sdram_write_ba, output[15:0] sdram_write_data ); localparam S_IDEL = 'd0; //空闲态 localparam S_ACTIVE = 'd1; //激活态 localparam S_WRITE = 'd2; //写数据 localparam S_PAUSE = 'd3; //写暂停 localparam S_ALTERNATE = 'd4; //换行换bank缓存 localparam S_PRECHARGE = 'd5; //写结束后或切换行列地址,发送precharge命令 localparam S_END = 'd6; //写结束 always@(*) begin case(state) S_IDEL: if( sdram_write_req == 1'b1 ) next_state <= S_ACTIVE; else next_state <= S_IDEL; S_ACTIVE: if( sdram_write_pause == 1'b1 ) //写暂停信号,转去刷新操作 next_state <= S_PAUSE; else if( time_cnt == `tRCD ) next_state <= S_WRITE; else next_state <= S_ACTIVE; S_WRITE: if( sdram_write_pause == 1'b1 ) //写暂停信号,转去刷新操作 next_state <= S_PAUSE; else if( alternating_bank_row_en == 1'b1) next_state <= S_PRECHARGE; else if( write_burst_length_cnt == write_burst_length ) next_state <= S_PRECHARGE; else next_state <= S_WRITE; S_PAUSE: if( sdram_write_pause == 1'b0 ) //刷新操作结束 next_state <= S_ACTIVE; else next_state <= S_PAUSE; S_ALTERNATE: if( time_cnt == `tRCD) next_state <= S_ACTIVE; else next_state <= S_ALTERNATE; S_PRECHARGE: if( time_cnt == `tRP + `tWR) if( write_burst_length_cnt >= write_burst_length) next_state <= S_END; else next_state <= S_ALTERNATE; else next_state <= S_PRECHARGE; S_END: next_state <= S_IDEL; default : next_state <= S_IDEL; endcase end
最后通过仿真,确认实现正确,第一幅图是写过程进行刷新操作,第二幅图是,写过程切换行地址
六. 读模块
读模块过程的编写和写模块是一模一样的,不过需要注意的是读模块有潜伏期,命令发送和数据输出相差CL个时钟周期,读数据的时候,需要将这个延时加入其中,可以看到接口信号和写模块是一样。不过对数据进行采样的时候,需要使用输入到sdram中的时钟,这需要注意。
sdram_read ( input sdram_clk, input rst_n, input sdram_read_req, //读请求 output sdram_read_ack, //读响应 input sdram_read_pause, //读暂停信号,转去刷新操作 output reg sdram_read_pause_ack, //读暂停响应,成功暂停 input[24:0] read_addr, //读入地址 output[15:0] read_data, //读出数据 {bank[1:0s],row[12:0],clo[9:0]} input[9:0] read_burst_length, //单次读突发长度 //可以为sdram大小 output read_data_en, //读数据有效输出 //sdram接口 output[3:0] sdram_read_cmd, output[12:0] sdram_read_addr, output[1:0] sdram_read_ba, input[15:0] sdram_read_data );
通过仿真输出,确定突发读期间,换行以及刷新完全正确
至此SDRAM模块的编写就完成了,顶层框图如下,至于在外部如何进行封装,那就看不同的需求了。
有任何设计疑问可以关注 公众号 FPGA之旅 与我交流。
关注微信 公众号 FPGA之旅 回复 FPGA之旅设计99例之第二十例 获取全部工程文件 包括sdram仿真模型。
SDRAM_TOP( input sys_clk, //sdram的系统时钟 100M input rst_n, //异步复位信号 //读接口 input read_req, output read_ack, input[24:0] read_addr, input[9:0] read_burst_length, output[15:0] read_data, output read_data_en, //写接口 input write_req, output write_ack, input[24:0] write_addr, input[9:0] write_burst_length, input[15:0] write_data, output write_data_en, //sdram接口 output sdram_clk, //sdram clock output sdram_cke, //sdram clock enable output sdram_cs_n, //sdram chip select output sdram_we_n, //sdram write enable output sdram_cas_n, //sdram column address strobe output sdram_ras_n, //sdram row address strobe output[1:0] sdram_dqm, //sdram data enable output[1:0] sdram_ba, //sdram bank address output[12:0] sdram_addr, //sdram address inout[15:0] sdram_dq //sdram data );
公众号:FPGA之旅