SDRAM控制器
此文章僅用于自己的學習筆記,參考小梅哥FPGA系統設計與驗證明戰指南中的SDRAM控制器設計章節
sdram類型及容量
名字:MT47H64M16HR
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiIXZ05WZj91YpB3IwczX0xiRGZkRGZ0Xy9GbvNGL2EzXlpXazxSNkdVZxgmMMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL4ITOyMDO0kTM5ADNwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
容量:8192 * 256 * 64=128MByte=1GBit
共24位位址
引腳詳解:
CLK | 系統時鐘 | 時鐘上升沿對所有輸入進行采樣 |
---|---|---|
CKE | 時鐘使能 | 屏蔽系統時鐘,來當機目前操作,當該引腳為高電平時,所 有引腳電平才能被正确送入 SDRAM 晶片。 |
CS_N | 片選 | 用于屏蔽和使能所有輸入端口,但不包括 CLK、 CKE 和 DQM,低電平有效 |
RAS_N | 行位址選通 | 該信号為低時,在時鐘的上升沿鎖存行位址,使能行通路和 預充電 |
CAS_N | 列位址選通 | 該信号為低時,在時鐘的上升沿鎖存列位址,使能列通路 |
WE_N | 寫使能 | 該信号為低時,使能寫操作和預充電 |
BA[1:0] | Bank 位址 | 用于選擇不同的 Bank 進行讀寫操作 |
A[12:0] | 位址總線 | 關于位址線上的資料,在不同的指令中有不同的意義,詳見 下文對 A[12:0]的功能描述 |
DQM[1:0] | 資料掩碼 | L(U)DQM,低(高)位元組俺媽,當其為高時,下一時鐘的 上升沿時資料總線的低(高)位元組為高阻态 |
DQ[15:0] | 資料總線 | 資料輸入輸出複用 |
指令詳解
{CS_N, RAS_N, CAS_N, WE}=4’b0111 空指令,無作用,可用作初始化等待指令
{CS_N, RAS_N, CAS_N, WE}=4’b0000 加載模式指令,在bank idle狀态可使用,使用此指令後要等待tMRD時間可使用其他指令
{CS_N, RAS_N, CAS_N, WE}=4’b0011 激活指令,激活完畢後可對該行進行讀寫,在下一個PRECHARGE後激活狀态失效,當需要對不同行執行激活時,需要先PRECHARGE後進行激活
{CS_N, RAS_N, CAS_N, WE}=4’b0101 讀指令,A0~A9決定讀起始位址,A10為 1則讀完畢後自動充電,在被DQM指定的位址不可讀,為高阻态
{CS_N, RAS_N, CAS_N, WE}=4’b0100 寫指令,A10和DQM的作用與讀相同
{CS_N, RAS_N, CAS_N, WE}=4’b0010 PRECHARGE,A10為高電平則同時對所有bankPRRCHARGE,充電完畢後等tRP可進行其他操作
{CS_N, RAS_N, CAS_N, WE}=4’b0110 突發中斷指令
{CS_N, RAS_N, CAS_N, WE}=4’b0001 自動重新整理指令,每 64ms 内需要執行 8192 次自動重新整理操作,即每 7.813us 執行一次;
模式設定:
{A7,A8}=2’b00 标準模式,隻有這一個可選項
A9 突發模式設定位,為0時則寫和讀都為突發模式,為1則隻突發讀
A0~A2 突發長度
A3 突發類型,為0則為順序,為1則為交錯,常用0
操作時序:
上電初始化:
- 加載電源( VDD 和 VDDQ);
- CKE 設定為低低電平( LVTTL 邏輯低電平);
- 加載穩定的時鐘信号;
- 等待至少 100us 的時間,此過程中的指令保持為禁止指令或空操作指令;
- 在步驟 4 的 100us 中的某個時刻,将 CKE 設定為高;
- 步驟 4 的 100us 等待時間結束後,随即可發出一個全部 BANK 的預充電指令;
- 等待時間 tRP,此過程中指令保持為禁止指令或空操作指令;
- 步驟 7 鐘的等待時間 tRP 結束時,發出一個自動重新整理指令;
- 等待時間 tRFC(Auto refresh period),此過程中指令僅允許是禁止指令或空操作指令;
- 步驟 9 中的等待時間 tRFC 結束時,再發出一個自動重新整理指令;
- 再等待時間 tRFC,此過程中指令僅允許是禁止指令或空操作指令
- 步驟 11 中的等待時間 tRFC 結束時,發出裝載模式寄存器指令設定模式寄存器,具體模式寄存器的值由 A0~A11 傳輸;
- 等待時間 tMRD(LOAD MODE REGISTER command to ACTIVE orREFRESH command),此過程中指令僅允許是禁止指令或空操作指令。
tRP 不小于 20ns ,tRP 不小于 20ns, tMRD 不小于 2 個時鐘周期 SDRAM 控制器的時鐘采用 100MHz 時鐘
代碼
`include "Sdram_Params.v"
module sdram_init(
input clk , //系統時鐘
input rst_n , //複位信号,低電平有效
output sdram_cs_n , //讀SDRAM請求信号
output sdram_cke , //寫SDRAM響應信号
output sdram_ras_n , //突發寫SDRAM位元組數(1-512個)
output sdram_cas_n , //突發讀SDRAM位元組數(1-512個)
output sdram_init_done , //SDRAM系統初始化完畢信号
output reg[11:0] sdram_addr ,
output wire sdram_we_n //sdram we_n
);
reg [3:0] command;
reg [15:0] cnt_init;
assign sdram_cke=rst_n;
assign {sdram_cs_n,sdram_ras_n,sdram_cas_n,sdram_we_n}=command;
parameter ref_PRE_TIME=`INIT_PRE;
parameter ref_PRE1_TIME=`INIT_PRE+`REF_PRE;
parameter ref_PRE2_TIME=`REF_PRE+`INIT_PRE+`REF_REF;
parameter lmr_TIME=`REF_PRE+10+10+`INIT_PRE;
parameter ref_END=lmr_TIME+`LMR_ACT;
assign sdram_init_done = (cnt_init == ref_END);
always @(posedge clk or negedge rst_n)
if(!rst_n)
cnt_init<=16'd0;
else if(cnt_init==ref_END)
cnt_init<=cnt_init;
else
cnt_init<=cnt_init+1'd1;
always @(posedge clk or negedge rst_n)
if(!rst_n)begin
command <=`C_NOP;
sdram_addr <=12'd0;
end
else begin
case(cnt_init)
ref_PRE_TIME:begin
command<=`C_PRE;
sdram_addr[10]<=1'd1;
end
ref_PRE1_TIME:begin
command<=`C_AREF;
end
ref_PRE2_TIME:begin
command<=`C_AREF;
end
lmr_TIME:begin
command<=`C_MSET;
sdram_addr<= {2'b0,1'b0,2'b00,`SDR_CL,`SDR_BT,`SDR_BL};
end
default:begin
command<=`C_NOP;
sdram_addr<=12'd0;
end
endcase
end
endmodule
自重新整理與自動重新整理:
時序圖
差別
自動重新整理模式 | 自重新整理模式 | |
---|---|---|
作用 | 保證資料不丢失 | |
是否需要控制器控制重新整理 | 需要 | 不需要 |
使用場合 | 對 SDRAM 正常操作過 程 | 掉電/低功耗模式 |
是否需要外部時鐘信号 | 需要 | 不需要 |
重新整理行位址自動控制 | 是 | 是 |
重新整理機制:
每 64ms 内需要執行 8192 次自動重新整理操作,即每 7.813us 執行一次;
在100m時鐘的情況下,相當于每780個時鐘周期,執行一次自動重新整理
重新整理指令:{CS_N, RAS_N, CAS_N, WE}=4’b0001
重新整理優先級:最高
流程:
PRECHARGE->AREF->AREF->END
代碼:
localparam
aref_start =1'd1,
aref_aref =aref_start+`REF_PRE,
aref_aref2 =aref_aref+`REF_REF,
aref_end =aref_aref2+`REF_REF;
reg [7:0] cnt_aref;
reg FF;
reg aref_req;
reg aref_flag;
reg aref_done;
always @(posedge clk or negedge rst_n)
if(!rst_n)
cnt_aref<=8'd0;
else begin
if(cnt_aref==aref_end)
cnt_aref<=8'd0;
else if(cnt_aref>1'd0||aref_req)
cnt_aref<=cnt_aref+1'd1;
else
cnt_aref<=cnt_aref;
end
task t_auto_ref;
begin
case(cnt_aref)
aref_start:begin
command<=`C_PRE;
sdram_addr[10]<=1'd1;
end
aref_aref:
command<=`C_AREF;
aref_aref2:
command<=`C_AREF;
aref_end:begin
command<=`C_NOP;
FF<=1'd1;
end
default:
command<=`C_NOP;
endcase
end
endtask
always @(posedge clk or negedge rst_n)
if(!rst_n)
aref_done<=1'd0;
else if(cnt_aref==aref_end)
aref_done<=1'd1;
else
aref_done<=1'd0;
always @(posedge clk or negedge rst_n)
if(!rst_n)begin
aref_flag<=1'd0;
end
else begin
if(aref_req)
aref_flag<=1'd1;
else if(aref_done)
aref_flag<=1'd0;
end
寫操作:
時序:
流程:
ACTICE->等待TRCD->WRITE->等待T-BL->等待T-WR->PRECHARGE->等待T-PRE
代碼:
localparam
t_Write_active =1'b1,
t_Write_cmd =1'b1+`SC_RCD,
t_Write_Pre_precharge =1'b1+`SC_RCD+`WR_PRE+`SC_BL,
t_Write_precharge =1'b1+`SC_RCD+`WR_PRE+`REF_PRE+`SC_BL;
reg [7:0] cnt_write;
reg [1:0] b_addr_r;
reg [11:0] r_addr_r;
reg [8:0] c_addr_r;
reg wr_req;
reg wr_flag;
wire wr_once_done;
always @(posedge clk or negedge rst_n)
if(!rst_n)begin
cnt_write<=8'd0;
end
else begin
if(cnt_write==t_Write_precharge)
cnt_write<=8'd0;
else if(wr_req||cnt_write>1'd0)
cnt_write<=cnt_write+1'd1;
else
cnt_write<=1'd0;
end
task t_write_data;
begin
case(cnt_write)
t_Write_active:begin
command <=`C_ACT;
sdram_addr[11:0] <=r_addr_r;
sdram_bank <=b_addr_r;
end
t_Write_cmd:begin
command <=`C_WR;
sdram_addr <={1'b0,c_addr_r};
sdram_bank <=b_addr_r;
end
t_Write_Pre_precharge:begin
command <=`C_PRE;
sdram_addr[10]<=1'd1;
end
t_Write_precharge:begin
command <=`C_NOP;
FF <=1'd1;
end
default:begin
command<=`C_NOP;
end
endcase
end
endtask
always @(posedge clk or negedge rst_n)
if(!rst_n)begin
wr_done<=1'd0;
end
else begin
if(cnt_write==t_Write_precharge)
wr_done<=1'd1;
else
wr_done<=1'd0;
end
always @(posedge clk or negedge rst_n)
if(!rst_n)begin
wr_flag<=1'd0;
end
else begin
if(wr_req==1'd1)
wr_flag<=1'd1;
else if(wr_done)
wr_flag<=1'd0;
else
wr_flag<=wr_flag;
end
always @(posedge clk or negedge rst_n)
if(!rst_n)begin
wr_data_valid<=1'd0;
end
else begin
if(cnt_write>`SC_RCD&&cnt_write<=`SC_RCD+`SC_BL)
wr_data_valid<=1'd1;
else
wr_data_valid<=1'd0;
end
assign wr_once_done=(cnt_write==`SC_RCD+`SC_BL)?1:0;
assign sdram_dq=(wr_data_valid)?write_data:16'dz;
讀操作:
時序:
流程:
ACTICE->等待TRCD->READ->等待T-BL->等待T-CL>PRECHCLARGE->等待T-PRE
代碼:
localparam
t_read_active =1'b1,
t_read_cmd =1'b1+`SC_RCD,
t_read_pre_prechare =1'b1+`SC_RCD+`SC_CL+`SC_BL,
t_read_precharge =1'b1+`SC_RCD+`SC_CL+`SC_BL+`REF_PRE;
reg [7:0] cnt_read;
reg rd_req;
reg rd_flag;
always @(posedge clk or negedge rst_n)
if(!rst_n)begin
cnt_read<=8'd0;
end
else begin
if(cnt_read==t_read_precharge)
cnt_read<=8'd0;
else if(cnt_read>1'd0||rd_req)
cnt_read<=cnt_read+1'd1;
else
cnt_read<=1'd0;
end
task t_read_data;
begin
case(cnt_read)
t_read_active:begin
command<=`C_ACT;
sdram_addr<=r_addr_r;
sdram_bank<=b_addr_r;
end
t_read_cmd:begin
command<=`C_RD;
sdram_addr<={1'd0,c_addr_r};
sdram_bank<=b_addr_r;
end
t_read_pre_prechare:begin
command<=`C_PRE;
sdram_addr[10]<=1'd1;
end
t_read_precharge:begin
command<=`C_NOP;
FF<=1'd1;
end
default:begin
command<=`C_NOP;
end
endcase
end
endtask
always @(posedge clk or negedge rst_n)
if(!rst_n)begin
rd_done<=1'd0;
end
else begin
if(cnt_read==t_read_precharge)
rd_done<=1'd1;
else
rd_done<=1'd0;
end
always @(posedge clk or negedge rst_n)
if(!rst_n)begin
rd_flag<=1'd0;
end
else begin
if(rd_req)
rd_flag<=1'd1;
else if(rd_done)
rd_flag<=1'd0;
else
rd_flag<=rd_flag;
end
always @(posedge clk or negedge rst_n)
if(!rst_n)begin
rd_data_valid<=1'd0;
end
else begin
if(cnt_read>`SC_CL+`SC_RCD&&cnt_read<`SC_CL+`SC_RCD+`SC_BL)
rd_data_valid<=1'd1;
else
rd_data_valid<=1'd0;
end
assign read_data=sdram_dq;
控制器:
狀态轉移:
代碼:
reg [3:0] state_main;
localparam
IDLE = 4'b0001,
AREF = 4'b0010,
WRITE = 4'b0100,
READ = 4'b1000;
always @(posedge clk or negedge rst_n)
if(!rst_n)begin
state_main <= IDLE;
command <= `C_NOP;
sdram_addr <= 0;
end
else case(state_main)
IDLE:begin
sdram_addr <= 0;
t_init;
if(sdram_init_done)
state_main <= AREF;
else
state_main <= IDLE;
end
AREF:begin
if(FF==1'b0)
t_auto_ref;
else begin
if(aref_req)begin
FF<=1'd0;
state_main<=AREF;
end
else if(wr_req)begin
FF<=1'd0;
state_main<=WRITE;
end
else if(rd_req)begin
FF<=1'd0;
state_main<=READ;
end
else
state_main<=AREF;
end
end
WRITE:begin
if(FF==1'd0)
t_write_data;
else if(aref_req)begin
state_main<=AREF;
FF<=1'd0;
end
else if(wr_done&&wr_req)begin
state_main<=WRITE;
FF<=1'd0;
end
else if(wr_done&&!wr_req&&!rd_req)begin
state_main<=AREF;
end
else if(wr_done&&rd_req&&!wr_req)begin
FF<=1'd0;
state_main<=READ;
end
else
state_main<=WRITE;
end
READ:begin
if(FF==1'd0)
t_read_data;
else if(aref_req)begin
state_main<=AREF;
FF<=1'd0;
end
else if(rd_done&&!wr_req&&!rd_req)begin
state_main<=AREF;
FF<=1'd0;
end
else if(rd_done&&rd_req)begin
state_main<=READ;
FF<=1'd0;
end
else if(rd_done&&wr_req)begin
state_main<=WRITE;
FF<=1'd0;
end
else
state_main<=READ;
end
endcase
自動重新整理産生:
64ms内要産生4096次重新整理,即每15.625us産生一次重新整理,選擇15us産生一次
對于100Mhz時鐘,即1500個時鐘周期産生一次重新整理
代碼:
reg [10:0] cnt_time_ref;
wire ref_after_wr,ref_after_rd;
wire wr_after_ref,rd_after_rd;
wire ref_time;
always @(posedge clk or negedge rst_n)
if(!rst_n)begin
cnt_time_ref<=11'd0;
end
else begin
if(cnt_time_ref==`AUTO_REF)
cnt_time_ref<=11'd1;
else if(cnt_time_ref>1'd0||sdram_init_done)
cnt_time_ref<=cnt_time_ref+1'd1;
else
cnt_time_ref<=cnt_time_ref;
end
assign ref_time=(cnt_time_ref==`AUTO_REF)?1:0;
always @(posedge clk or negedge rst_n)
if(!rst_n)begin
r_addr_r<=12'd0;
c_addr_r<=8'd0;
b_addr_r<=2'd0;
end
else begin
if(rd_req||wr_req)begin
r_addr_r<=r_addr;
c_addr_r<=c_addr;
b_addr_r<=b_addr;
end
else begin
r_addr_r<=r_addr_r;
b_addr_r<=b_addr_r;
c_addr_r<=c_addr_r;
end
end
assign ref_after_rd=(ref_time&&rd_flag)?1:(~rd_flag)?1'b0:ref_after_rd;
assign ref_after_wr=(ref_time&&wr_flag)?1:(~wr_flag)?1'b0:ref_after_wr;
[email protected](*)
begin
case(state_main)
AREF:
if(ref_time)
aref_req<=1'd1;
else
aref_req<=1'd0;
WRITE:
if(ref_after_wr&&wr_done)
aref_req<=1'd1;
else
aref_req<=1'd0;
READ:
if(ref_after_rd&&rd_done)
aref_req<=1'd1;
else
aref_req<=1'd0;
default:
aref_req<=1'd0;
endcase
end
assign wr_after_ref=(wr_en&&aref_flag)?1'd1:(!aref_flag)?1'b0:wr_after_ref;
assign rd_after_ref=(rd_en&&aref_flag)?1'd1:(!aref_flag)?1'b0:rd_after_ref;
always @(*)
case(state_main)
AREF:
if(wr_en&&!aref_flag&&~wr_after_ref)
wr_req<=1'd1;
else if(wr_after_ref&&aref_done)
wr_req<=1'd1;
else
wr_req<=1'd0;
WRITE:
if(wr_done&&wr_en&&~ref_after_wr)
wr_req<=1'd1;
else
wr_req<=1'd0;
READ:
if(rd_done&&wr_en&&~ref_after_rd)
wr_req<=1'd1;
else
wr_req<=1'd0;
default:
wr_req<=1'd0;
endcase
always @(*)
case(state_main)
AREF:
if(rd_en&&!aref_flag&&~rd_after_ref&&!wr_en&&~wr_after_ref)
rd_req<=1'd1;
else if(rd_after_ref&&aref_done&&~wr_after_ref)
rd_req<=1'd1;
else
rd_req<=1'd0;
WRITE:
if(wr_done&&rd_en&&~ref_after_wr)
rd_req<=1'd1;
else
rd_req<=1'd0;
READ:
if(rd_done&&rd_en&&~ref_after_rd&&~wr_en)
rd_req<=1'd1;
else
rd_req<=1'd0;
default:
rd_req<=1'd0;
endcase
wr_req<=1'd1;
else
wr_req<=1'd0;
READ:
if(rd_done&&wr_en&&~ref_after_rd)
wr_req<=1'd1;
else
wr_req<=1'd0;
default:
wr_req<=1'd0;
endcase
always @(*)
case(state_main)
AREF:
if(rd_en&&!aref_flag&&rd_after_ref&&!wr_en&&wr_after_ref)
rd_req<=1’d1;
else if(rd_after_ref&&aref_done&&~wr_after_ref)
rd_req<=1’d1;
else
rd_req<=1’d0;
WRITE:
if(wr_done&&rd_en&&~ref_after_wr)
rd_req<=1’d1;
else
rd_req<=1’d0;
READ:
if(rd_done&&rd_en&&ref_after_rd&&wr_en)
rd_req<=1’d1;
else
rd_req<=1’d0;
default:
rd_req<=1’d0;
endcase