pengyu2006jnu 发表于 2020-5-4 10:39:50

基于FPGA的SDRAM控制器设计—自动刷新设计

SDRAM控制器设计的主要功能是能对SDRAM进行读写操作,本工程实现了SDRAM的初始化和自动刷新两个功能。
初始化功能在前一章的分享中已经进行了比较详细的描述,感兴趣的同学可以搜索学习下,文后历史文章里有链接。今天我们主要讨论SDRAM的自动刷新的功能以及实现。
一、原理功能1、为什么刷新我们都知道SDRAM是使用电容保存信息的,随着使用时间的增长,电容的电量会有损失,因此在操作SDRAM时要进行刷新。SDRAM的刷新分为两种,分别是Auto Refresh和Self Refresh。本次实验采用的是Auto Refresh。
2、刷新间隔查询器件手册得到(64ms, 8192-cycle (commercial and industrial)),对8192行全部进行一次刷新时间是64ms。一次刷新操作是对4个bank的同一行进行刷新,所以一次刷新间隔是64ms/8192=7.813us。但是当刷新时间到来时,SDRAM可能正在进行读写,那么需要本次读突发或者写突发完成之后才能进行刷新操作;那么一次读突发为7拍,写突发为6拍,时间是60ns或者70ns (SDRAM工作时钟是100MHz,1拍是10ns),同时考虑到读写命令都是仲裁模块发出,会有一定的延时,所以本次实验刷新间隔设为7.5us,留出足够的时间。
3、刷新时序https://bbs.21ic.com/data/attachment/forum/202004/17/173602vbe7u1b8ub1kgu90.jpg刷新时序如上图所示。这里需要注意,此时序图发了两次Auto Refresh命令,这种被称为背靠背技术;但其实背靠背技术并不是必须的,可以只发一次命令。
二、FPGA实现1、模块架构https://bbs.21ic.com/data/attachment/forum/202004/17/173603jnldjl0rnxa4x72r.jpg
2、信号说明
信号说明clk刷新模块工作时钟(100MHz)rst_n复位信号ref_en刷新使能信号,由仲裁模块发出ref_done刷新完成信号ref_bus刷新数据总线,由SDRAM信号组成rt_flag计数到最大值信号rt_clearrt_flag清除信号rt_en计数器使能信号init_done初始化完成信号sel_sm选择SDRAM输出信号ref_en刷新使能信号sdr_bus顶层模块数据总线sdr_clkSDRAM工作时钟sdr_cke时钟使能sdr_cs_n片选信号sdr_cas_n行选通sdr_ras_n列选通sdr_we_n写使能sdr_babank地址sdr_aSDRAM地址总线

3、顶层模块参考代码

module sdram_top(clk    ,sys_rst_n,//其它信号,举例doutlocal_addr,local_data,local_q,local_rdreq,local_wrreq,local_reday,local_rdata_vaild,init_done,sdr_cke,sdr_cs_n,sdr_ras_n,sdr_cas_n,sdr_we_n,sdr_ba,sdr_a,sdr_dq,sdr_dqm,sdr_clk);
input clk;input sys_rst_n;input local_addr;input local_data;output local_q;input local_rdreq;input local_wrreq;output local_reday;output local_rdata_vaild;output init_done;output sdr_cke;output sdr_cs_n;output sdr_ras_n;output sdr_cas_n;output sdr_we_n;output sdr_ba;output sdr_a;output sdr_dq;output sdr_dqm;output sdr_clk;
wire phy_clk;wire rst_n;wire rt_flag;wire rt_clear;wire rt_en;wire ref_en;wire ref_done;wire sel_sm;wire sdr_bus;wire init_bus;wire ref_bus;
assign {sdr_cke, sdr_cs_n, sdr_ras_n, sdr_cas_n, sdr_we_n, sdr_ba, sdr_a} = sdr_bus;assign sdr_dqm = 2'b00;
sdram_init sdram_init_inst(.clk            (phy_clk)       ,.rst_n          (rst_n)       ,//其它信号,举例dout.init_done      (init_done)       ,.init_bus       (init_bus));
arbitrate arbitrate_inst(.clk(phy_clk),.rst_n(rst_n),.rt_en(rt_en),.rt_flag(rt_flag),.init_done(init_done),.ref_done(ref_done),.ref_en(ref_en),.sel_sm(sel_sm),.rt_clear(rt_clear));
ref_timer ref_timer_inst(.clk(phy_clk),.rst_n(rst_n),.rt_en(rt_en),.rt_clear(rt_clear),.rt_flag(rt_flag));
sdram_ref sdram_ref_inst(.clk(phy_clk),.rst_n(rst_n),.ref_en(ref_en),.ref_done(ref_done),.ref_bus(ref_bus));
sdram_mux sdram_mux_inst(.clk(phy_clk),.rst_n(rst_n),.init_bus(init_bus),.ref_bus(ref_bus),.sdr_bus(sdr_bus),.sel_sm(sel_sm));
my_pll PLL(.areset      (~sys_rst_n)      ,.inclk0      (clk)      ,.c0      (phy_clk)      ,.c1      (sdr_clk)      ,.locked      (rst_n));
endmodule

4、模块功能

(1)PLL模块my_pll模块产生SDRAM和控制器工作时钟。输入的50M时钟,经过PLL模块后,会产生两个100M、相位相差180度的时钟。其中一个用于输出给外部SDRAM,另一个用于其它模块的工作时钟。关于此模块的原理,可以参考《基于FPGA的SDRAM控制器设计—初始化设计》中的“SDRAM中心对齐原则”部分进行学习。另外,本模块锁定输入时钟后,将产生LOCK指示信号,此信号用于其它模块的复位信号。我们可以理解为,在时钟稳定之前,其它模块都处于复位状态。
(2)仲裁模块
arbitrate即仲裁模块,因为SDRAM控制时可能进行刷新或者读写操作(后续介绍),但是刷新时不能进行读写操作,因此需要一个仲裁模块,对这些控制命令进行管理,使刷新命令优先级最高。

当初始化完成之后仲裁模块发出rt_en信号,当仲裁模块收刷新定时器计时到最大值时的标志信号rt_flag后,发出刷新使能信号ref_en,并发出rt_clear信号。
其代码如下所示:
module arbitrate(clk, rst_n, rt_en, rt_flag, init_done, ref_done, ref_en, sel_sm, rt_clear);
input clk;input rst_n;input rt_flag;input init_done;input ref_done;output reg rt_en;output reg ref_en;output reg sel_sm;output reg rt_clear;
localparam SM_INIT = 1'b0;localparam SM_REF= 1'b1;
always @(posedge clk or negedge rst_n)beginif(!rst_n)beginrt_en <= 1'b0;endelse if(init_done)beginrt_en <= 1'b1;endelse beginrt_en <= rt_en;endend
always @(posedge clk or negedge rst_n)beginif(!rst_n)beginref_en <= 1'b0;endelse if(rt_flag)beginref_en <= 1'b1;endelse if(ref_done)beginref_en <= 1'b0;endelse beginref_en <= ref_en;endend
always @(posedge clk or negedge rst_n)beginif(!rst_n)beginsel_sm <= SM_INIT;endelse if(init_done)beginsel_sm <= SM_REF;endelse beginsel_sm <= sel_sm;endend
always @(posedge clk or negedge rst_n)beginif(!rst_n)beginrt_clear <= 1'b0;endelse if(rt_flag)beginrt_clear <= 1'b1;endelse beginrt_clear <= 1'b0;endend
endmodule

(3)刷新定时器模块
ref_timer即刷新定时器模块,主要是计数刷新间隔时间,当计数到最大值时拉高rt_flag信号。当收到rt_clear信号时将rt_flag信号拉低。
代码如下所示:


module ref_timer(clk, rst_n, rt_en, rt_clear, rt_flag);
input clk;input rst_n;input rt_en;input rt_clear;output reg rt_flag;
parameter CNT_MAX = 750;
reg cnt;
wire add_cnt;wire end_cnt;
always @(posedge clk or negedge rst_n)beginif(!rst_n)begincnt <= 0;endelse if(add_cnt)beginif(end_cnt)cnt <= 0;elsecnt <= cnt + 1;endend
assign add_cnt = rt_en;assign end_cnt = add_cnt && cnt==CNT_MAX - 1 ;
always @(posedge clk or negedge rst_n)beginif (!rst_n)rt_flag <= 0;else if (add_cnt && cnt == CNT_MAX - 1)rt_flag <= 1;else if (rt_clear)rt_flag <= 0;elsert_flag <= rt_flag;end
endmodule

(4)初始化模块sdr_init初始化模块,在《基于FPGA的SDRAM控制器设计—初始化设计》中我们有比较详细的介绍,可以认真学习一下。

(5)刷新模块sdr_ref刷新模块,收到刷新使能信号后进行刷新操作,在前文中“刷新时序”一节有讲述原因。本代码通过一个计数器cnt对时序进行计数,并产生了两个刷新命令;刷新完成后,让ref_done信号置1个时钟的高电平,表示刷新完成。
代码如下:

module sdram_ref(clk, rst_n, ref_en, ref_done, ref_bus);
input clk;input rst_n;input ref_en;output reg ref_done;output ref_bus;
parameter CNT_MAX = 9;// parameter TRP = 2;// parameter TRFC = 7;parameter NOP = 4'b0111;parameter PRE = 4'b0010;parameter REF = 4'b0001;
reg cnt;reg sdr_cmd;reg sdr_ba;reg sdr_a;
wire add_cnt;wire end_cnt;
assign sdr_cke = 1'b1;assign ref_bus = {sdr_cke, sdr_cmd, sdr_ba, sdr_a};
always @(posedge clk or negedge rst_n)beginif(!rst_n)begincnt <= 0;endelse if(add_cnt)beginif(end_cnt)cnt <= 0;elsecnt <= cnt + 1;endend
assign add_cnt = ref_en;assign end_cnt = add_cnt && cnt==CNT_MAX - 1 ;
always @(posedge clk or negedge rst_n)beginif(!rst_n)beginsdr_cmd <= NOP;endelse if(ref_en && add_cnt && cnt == 0)beginsdr_cmd <= PRE;endelse if(add_cnt && cnt == 2 - 1)beginsdr_cmd <= REF;endelse beginsdr_cmd <= NOP;endend
always @(posedge clk or negedge rst_n)beginif(!rst_n)beginsdr_a <= 13'd0;endelse if(ref_en && add_cnt && cnt == 0)beginsdr_a <= 1'b1;endelse beginsdr_a <= 13'd0;endend
always @(posedge clk or negedge rst_n)beginif(!rst_n)beginref_done <= 1'b0;endelse if(add_cnt && cnt == CNT_MAX - 1)beginref_done <= 1'b1;endelse beginref_done <= 1'b0;endend
endmodule

6、选择模块
sdr_mux模块,由于初始化模块和刷新模块都会发出SDRAM的信号,所以需要一个多路器来进行选择。由仲裁模块的sel_sm来控制输出init_bus信号还是ref_bus;当初始化没完成时输出init_bus,初始化完成时输出ref_bus。
代码如下所示:
module sdram_mux(clk, rst_n, init_bus, ref_bus, sdr_bus, sel_sm);
input clk;input rst_n;input init_bus;input ref_bus;output reg sdr_bus;input sel_sm;
localparam SM_INIT = 1'b0;localparam SM_REF= 1'b1;
always @(posedge clk or negedge rst_n)beginif(!rst_n)beginsdr_bus <= init_bus;endelse if(sel_sm == SM_INIT)beginsdr_bus <= init_bus;endelse if(sel_sm == SM_REF)beginsdr_bus <= ref_bus;endelse beginsdr_bus <= sdr_bus;endend
endmodule
三、仿真测试
最后对代码进行仿真,仿真文件参考:sdram_top_tb.v。modelsim生成的报告如下所示,出现如下LOG信息,说明成功。
https://bbs.21ic.com/data/attachment/forum/202004/17/173604tmg6y7jjsc44zww6.jpghttps://bbs.21ic.com/data/attachment/forum/202004/17/173604gmzg033i030dc040.jpg
以上就是SDRAM控制器的完整设计,明德扬专注FPGA设计研究,更多FPGA资料可以进入论坛进行学习交流。

bidezhi7777 发表于 2020-5-4 12:29:09

ldd1840 发表于 2020-5-4 12:47:04

收藏收藏

william_d_87 发表于 2020-5-4 13:05:38

地沟油 发表于 2020-5-4 13:19:04

nj20044 发表于 2020-5-12 21:40:14

至芯陈老师 发表于 2020-5-13 17:42:26

页: [1]
查看完整版本: 基于FPGA的SDRAM控制器设计—自动刷新设计