本文為明德揚原創錄用文章,轉載請注明出處!
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地址總線 |
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指示信號,此信號用于其它模塊的復位信號。我們可以理解為,在時鐘穩定之前,其它模塊都處于復位狀態。
當初始化完成之后仲裁模塊發出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
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資料,關注明德揚