马上注册,结交更多好友,享用更多功能,让你轻松玩转社区
您需要 登录 才可以下载或查看,没有账号?立即注册
×
SDRAM控制器设计的主要功能是能对SDRAM进行读写操作,本工程实现了SDRAM的初始化和自动刷新两个功能。
初始化功能在前一章的分享中已经进行了比较详细的描述,感兴趣的同学可以搜索学习下,文后历史文章里有链接。今天我们主要讨论SDRAM的自动刷新的功能以及实现。
我们都知道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实现
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地址总线 |
| 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
|
my_pll模块产生SDRAM和控制器工作时钟。 输入的50M时钟,经过PLL模块后,会产生两个100M、相位相差180度的时钟。其中一个用于输出给外部SDRAM,另一个用于其它模块的工作时钟。关于此模块的原理,可以参考《基于FPGA的SDRAM控制器设计—初始化设计》中的“SDRAM中心对齐原则”部分进行学习。 另外,本模块锁定输入时钟后,将产生LOCK指示信号,此信号用于其它模块的复位信号。我们可以理解为,在时钟稳定之前,其它模块都处于复位状态。
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
|
sdr_init初始化模块,在《基于FPGA的SDRAM控制器设计—初始化设计》中我们有比较详细的介绍,可以认真学习一下。 |
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设计研究,更多FPGA资料可以进入论坛进行学习交流。 |