官方论坛
官方淘宝
官方博客
微信公众号
点击联系吴工 点击联系周老师
您的当前位置:主页 > 教程中心 > FPGA技术教程 > SDRAM和DDR >

【文章】SDRAM控制器_自动刷新设计

发布时间:2021-07-01   作者:admin 浏览量:

本文为明德扬原创录用文章,转载请注明出处!


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、刷新时序




刷新时序如上图所示。这里需要注意,此时序图发了两次Auto Refresh命令,这种被称为背靠背技术;但其实背靠背技术并不是必须的,可以只发一次命令。

二、FPGA实现

1、模块架构


2、信号说明


信号

说明

clk

刷新模块工作时钟(100MHz)

rst_n

复位信号

ref_en

刷新使能信号,由仲裁模块发出

ref_done

刷新完成信号

ref_bus

刷新数据总线,由SDRAM信号组成

rt_flag

计数到最大值信号

rt_clear

rt_flag清除信号

rt_en

计数器使能信号

init_done

初始化完成信号

sel_sm

选择SDRAM输出信号

ref_en

刷新使能信号

sdr_bus

顶层模块数据总线

sdr_clk

SDRAM工作时钟

sdr_cke

时钟使能

sdr_cs_n

片选信号

sdr_cas_n

行选通

sdr_ras_n

列选通

sdr_we_n

写使能

sdr_ba

bank地址

sdr_a

SDRAM地址总线

3、顶层模块参考代码

module sdram_top(

clk    ,

sys_rst_n  ,

//其它信号,举例dout

local_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 [24:0] local_addr;

input [63:0] local_data;

output [63:0] 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 [1:0] sdr_ba;

output [12:0] sdr_a;

output [15:0] sdr_dq;

output [1:0] 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 [19:0] sdr_bus;

wire [19:0] init_bus;

wire [19:0] 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)begin

if(!rst_n)begin

rt_en <= 1'b0;

end

else if(init_done)begin

rt_en <= 1'b1;

end

else begin

rt_en <= rt_en;

end

end

always @(posedge clk or negedge rst_n)begin

if(!rst_n)begin

ref_en <= 1'b0;

end

else if(rt_flag)begin

ref_en <= 1'b1;

end

else if(ref_done)begin

ref_en <= 1'b0;

end

else begin

ref_en <= ref_en;

end

end

always @(posedge clk or negedge rst_n)begin

if(!rst_n)begin

sel_sm <= SM_INIT;

end

else if(init_done)begin

sel_sm <= SM_REF;

end

else begin

sel_sm <= sel_sm;

end

end

always @(posedge clk or negedge rst_n)begin

if(!rst_n)begin

rt_clear <= 1'b0;

end

else if(rt_flag)begin

rt_clear <= 1'b1;

end

else begin

rt_clear <= 1'b0;

end

end

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 [9:0] cnt;

wire add_cnt;

wire end_cnt;


always @(posedge clk or negedge rst_n)begin

if(!rst_n)begin

cnt <= 0;

end

else if(add_cnt)begin

if(end_cnt)

cnt <= 0;

else

cnt <= cnt + 1;

end

end


assign add_cnt = rt_en;

assign end_cnt = add_cnt && cnt==CNT_MAX - 1 ;

always @(posedge clk or negedge rst_n)begin

if (!rst_n)

rt_flag <= 0;

else if (add_cnt && cnt == CNT_MAX - 1)

rt_flag <= 1;

else if (rt_clear)

rt_flag <= 0;

else

rt_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 [19:0] 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 [3:0] cnt;

reg [3:0] sdr_cmd;

reg [1:0] sdr_ba;

reg [12:0] 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)begin

if(!rst_n)begin

cnt <= 0;

end

else if(add_cnt)begin

if(end_cnt)

cnt <= 0;

else

cnt <= cnt + 1;

end

end


assign add_cnt = ref_en;

assign end_cnt = add_cnt && cnt==CNT_MAX - 1 ;

always @(posedge clk or negedge rst_n)begin

if(!rst_n)begin

sdr_cmd <= NOP;

end

else if(ref_en && add_cnt && cnt == 0)begin

sdr_cmd <= PRE;

end

else if(add_cnt && cnt == 2 - 1)begin

sdr_cmd <= REF;

end

else begin

sdr_cmd <= NOP;

end

end

always @(posedge clk or negedge rst_n)begin

if(!rst_n)begin

sdr_a <= 13'd0;

end

else if(ref_en && add_cnt && cnt == 0)begin

sdr_a[10] <= 1'b1;

end

else begin

sdr_a <= 13'd0;

end

end

always @(posedge clk or negedge rst_n)begin

if(!rst_n)begin

ref_done <= 1'b0;

end

else if(add_cnt && cnt == CNT_MAX - 1)begin

ref_done <= 1'b1;

end

else begin

ref_done <= 1'b0;

end

end

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 [19:0] init_bus;

input [19:0] ref_bus;

output reg [19:0] sdr_bus;

input sel_sm;

localparam SM_INIT = 1'b0;

localparam SM_REF  = 1'b1;

always @(posedge clk or negedge rst_n)begin

if(!rst_n)begin

sdr_bus <= init_bus;

end

else if(sel_sm == SM_INIT)begin

sdr_bus <= init_bus;

end

else if(sel_sm == SM_REF)begin

sdr_bus <= ref_bus;

end

else begin

sdr_bus <= sdr_bus;

end

end


endmodule


三、仿真测试


最后对代码进行仿真,仿真文件参考:sdram_top_tb.v。

modelsim生成的报告如下所示,出现如下LOG信息,说明成功。



以上就是SDRAM控制器的完整设计,更多FPGA资料,关注明德扬

   拓展阅读