天天看點

FPGA實作SDRAM驅動SDRAM控制器

SDRAM控制器

此文章僅用于自己的學習筆記,參考小梅哥FPGA系統設計與驗證明戰指南中的SDRAM控制器設計章節

sdram類型及容量

名字:MT47H64M16HR

FPGA實作SDRAM驅動SDRAM控制器

容量: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] 資料總線 資料輸入輸出複用

指令詳解

FPGA實作SDRAM驅動SDRAM控制器

{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 執行一次;

模式設定:

FPGA實作SDRAM驅動SDRAM控制器

{A7,A8}=2’b00 标準模式,隻有這一個可選項

A9 突發模式設定位,為0時則寫和讀都為突發模式,為1則隻突發讀

A0~A2 突發長度

A3 突發類型,為0則為順序,為1則為交錯,常用0

操作時序:

FPGA實作SDRAM驅動SDRAM控制器

上電初始化:

  1. 加載電源( VDD 和 VDDQ);
  2. CKE 設定為低低電平( LVTTL 邏輯低電平);
  3. 加載穩定的時鐘信号;
  4. 等待至少 100us 的時間,此過程中的指令保持為禁止指令或空操作指令;
  5. 在步驟 4 的 100us 中的某個時刻,将 CKE 設定為高;
  6. 步驟 4 的 100us 等待時間結束後,随即可發出一個全部 BANK 的預充電指令;
  7. 等待時間 tRP,此過程中指令保持為禁止指令或空操作指令;
  8. 步驟 7 鐘的等待時間 tRP 結束時,發出一個自動重新整理指令;
  9. 等待時間 tRFC(Auto refresh period),此過程中指令僅允許是禁止指令或空操作指令;
  10. 步驟 9 中的等待時間 tRFC 結束時,再發出一個自動重新整理指令;
  11. 再等待時間 tRFC,此過程中指令僅允許是禁止指令或空操作指令
  12. 步驟 11 中的等待時間 tRFC 結束時,發出裝載模式寄存器指令設定模式寄存器,具體模式寄存器的值由 A0~A11 傳輸;
  13. 等待時間 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
           

自重新整理與自動重新整理:

時序圖

FPGA實作SDRAM驅動SDRAM控制器

差別

自動重新整理模式 自重新整理模式
作用 保證資料不丢失
是否需要控制器控制重新整理 需要 不需要
使用場合 對 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
           

寫操作:

時序:

FPGA實作SDRAM驅動SDRAM控制器

流程:

​ 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;
           

讀操作:

時序:

FPGA實作SDRAM驅動SDRAM控制器

流程:

​ 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;
           

控制器:

狀态轉移:

FPGA實作SDRAM驅動SDRAM控制器

代碼:

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