FPGA實戰操作(1) -- SDRAM(Verilog實現)


對SDRAM基本概念的介紹以及芯片手冊說明,請參考上一篇文章SDRAM操作說明

1. 說明

如圖所示為狀態機的簡化圖示,過程大概可以描述為:SDRAM(IS42S16320D)上電初始化完成后,進入“空閑”狀態,此時一直監控外部控制模塊給予的控制信號。初始化完成后,外部定時器開始定時,定時周期為SDRAM刷新周期(7.7us),一旦計數到刷新周期后,向狀態機發送auto_ref_req(自動刷新請求),此時狀態機進入“刷新”狀態,這樣就確保在無任何操作時,SDRAM能正常完成刷新。刷新完成后回到“空閑”狀態。

當處於空閑狀態時,接收到寫命令(wr_en),進入“寫”狀態(有效接收讀寫命令的時刻有特殊要求,后面再詳細說明),在full_page下連續寫600個數據(100MHz,恰好耗時6us多一點,這樣方便不用考慮定時刷新),寫完之后,發送wr_done命令,進入“刷新”狀態,相對於每次連續寫完成后,提前刷新一次。此時,定時刷新的計數器清零,重新開始計數。
讀多過程跟寫過程類似,讀完600個數據之后,手動完成刷新。

現在就來說一說,“空閑”狀態接收讀寫命令的特殊要求。理論上充電周期為7.8125us,為保證600次讀寫在充電周期內完成,並且前后預留一些其他命令的時間,所以推薦在0~1us這個時間內接受讀寫命令,這樣讀寫的時候專注讀寫就可以了。當然這是我的設計方式,如有更好的設計方式,那更好,歡迎分享。

2. 代碼實現

狀態機的代碼如下所示,清晰的描述了各狀態之間的跳變及其跳變條件。其中信號ctrl_valid即為上圖中命令有效期的時間區間。在各狀態描述的時序邏輯模塊中,只是產生了讀、寫或刷新執行模塊的使能信號,即在“寫”狀態的時候,使能寫模塊,完成相信的寫操作。

    always @ (posedge clk or negedge rst_n)
		begin
			if(rst_n == 1'b0)
				begin
					current_status <= IDLE;
				end
			else if(init_ing == 1'b0)
				begin
					current_status <= next_status;
				end
			else
				begin
					current_status <= IDLE;
				end
		end
		
	always @ (rst_n or current_status or sdram_wrreq or sdram_rdreq or ref_req_auto or wr_done or rd_done or ref_done or ctrl_valid)
		begin
			next_status = 5'dx;
			case(current_status)
				IDLE:
					begin
						if(ref_req_auto == 1'b1)						//收到自動刷新請求
							begin
								next_status = AUTO_REF;
							end
						else if(ctrl_valid == 1'b1 && sdram_wrreq == 1'b1)//在讀寫控制有效區內收到寫請求
							begin
								next_status = WRITE;
							end
						else if(ctrl_valid == 1'b1 && sdram_rdreq == 1'b1)	     //在讀寫控制有效區內收到讀請求
							begin
								next_status = READ;
							end
						else
							begin
								next_status = IDLE;
							end
					end
				WRITE:
					begin
						if(wr_done == 1'b1)
							begin
								next_status = AUTO_REF;
							end
						else
							begin
								next_status = WRITE;
							end	
					end
				READ:
					begin
						if(rd_done == 1'b1)
							begin
								next_status = AUTO_REF;
							end
						else
							begin
								next_status = READ;
							end	
					end
				AUTO_REF:
					begin
						if(ref_done == 1'b1)
							begin
								next_status = IDLE;
							end
						else
							begin
								next_status = AUTO_REF;
							end
					end
				default:
					begin
						next_status = IDLE;
					end	
			endcase
		end
	//各個狀態下的使能信號,以控制相應的模塊執行相應的操作	
	always @ (posedge clk or negedge rst_n)
		begin
			if(rst_n == 1'b0)
				begin
					wr_start <= 1'b0;
					rd_start <= 1'b0;
					ref_start <= 1'b0;
				end
			else
				begin
					case(next_status)
						IDLE:
							begin
								wr_start <= 1'b0;
								rd_start <= 1'b0;
								ref_start <= 1'b0;
							end
						WRITE:
							begin
								wr_start <= 1'b1;
								rd_start <= 1'b0;
								ref_start <= 1'b0;
							end
						READ:
							begin
								wr_start <= 1'b0;
								rd_start <= 1'b1;
								ref_start <= 1'b0;
							end
						AUTO_REF:
							begin
								wr_start <= 1'b0;
								rd_start <= 1'b0;
								ref_start <= 1'b1;
							end
						default:
							begin
								wr_start <= 1'b0;
								rd_start <= 1'b0;
								ref_start <= 1'b0;
							end
				
					endcase
				end
		
		end

以下給出寫操作模塊的部分代碼,讀操作和刷新同理。中間有些信號是我工程需要,參考一下思路即可。

        always @(posedge clk or negedge rst_n)
		begin
			if(rst_n == 1'b0)
				begin
					cke_wr <= 1'b0;
					cmd_wr <= NOP;
					dqm_wr <= DQM_DIS;
					bank_addr_wr <= BANK0;
					addr_wr <= DONT_CARE_ADDR;
					wr_done <= 1'b0;
					wr_first_flag_r <= 1'b0;
					status_wr <= 4'd0;
				end
			else if(wr_start == 1'b1)
				begin
					case(status_wr)
						4'd0:
							begin									
								cke_wr <= 1'b1;
								cmd_wr <= NOP;
								dqm_wr <= DQM_EN;
								bank_addr_wr <= BANK0;
								addr_wr <= DONT_CARE_ADDR;
								
								wr_done <= 1'b0;
								wr_first_flag_r <= 1'b0;
								status_wr <= status_wr + 4'd1;		
							end
						4'd1:
							begin
								cke_wr <= 1'b1;
								cmd_wr <= ACT;
								dqm_wr <= DQM_EN;
								bank_addr_wr <= BANK0;
								addr_wr <= row_addr;	//行地址
								
								wr_done <= 1'b0;
								wr_first_flag_r <= 1'b0;
								status_wr <= status_wr + 4'd1;
							end
						4'd2:									//4'd2和4'd3是為了延時T_RCD,即兩個時鍾
							begin
								
								cke_wr <= 1'b1;
								cmd_wr <= NOP;
								dqm_wr <= DQM_EN;
								bank_addr_wr <= BANK0;
								addr_wr <= DONT_CARE_ADDR;
								
								wr_done <= 1'b0;
								wr_first_flag_r <= 1'b0;			
								status_wr <= status_wr + 4'd1;
									
							end
						4'd3:											
							begin
								
								cke_wr <= 1'b1;
								cmd_wr <= NOP;
								dqm_wr <= DQM_EN;
								bank_addr_wr <= BANK0;
								addr_wr <= DONT_CARE_ADDR;
								
								wr_done <= 1'b0;
								wr_first_flag_r <= 1'b0;				
								status_wr <= status_wr + 4'd1;
									
							end
						4'd4:
							begin
								cke_wr <= 1'b1;
								cmd_wr <= NOP;
								dqm_wr <= DQM_EN;
								bank_addr_wr <= BANK0;
								addr_wr <= DONT_CARE_ADDR;
								
								wr_done <= 1'b0;
								wr_first_flag_r <= 1'b1;                       //用於寫入第一個數據的時序標記
								status_wr <= status_wr + 4'd1;
							end
						4'd5:
							begin
								cke_wr <= 1'b1;
								cmd_wr <= WR;
								dqm_wr <= DQM_EN;
								bank_addr_wr <= BANK0;
								addr_wr <= column_addr;		//{A12A11,A10,column_address}
								
								wr_done <= 1'b0;
								wr_first_flag_r <= 1'b0;
								status_wr <= status_wr + 4'd1;
							end
						4'd6:
							begin
								if(sdram_wr_done == 1'b1)		//用於增加NOP持續周期
									begin
										cke_wr <= 1'b1;
										cmd_wr <= NOP;
										dqm_wr <= DQM_DIS;
										bank_addr_wr <= BANK0;
										addr_wr <= DONT_CARE_ADDR;
										
										wr_done <= 1'b1;
										wr_first_flag_r <= 1'b0;
										status_wr <= status_wr + 4'd1;
									end
								else
									begin
										cke_wr <= 1'b1;
										cmd_wr <= NOP;
										dqm_wr <= DQM_EN;
										bank_addr_wr <= BANK0;
										addr_wr <= DONT_CARE_ADDR;
										
										wr_done <= 1'b0;
										wr_first_flag_r <= 1'b0;
										status_wr <= status_wr;
									end
							end
						4'd7:
							begin
								cke_wr <= 1'b1;
								cmd_wr <= NOP;
								dqm_wr <= DQM_DIS;
								bank_addr_wr <= BANK0;
								addr_wr <= DONT_CARE_ADDR;
								
								wr_done <= 1'b0;
								wr_first_flag_r <= 1'b0;
								status_wr <= 4'd0;
							end
						default:
							begin
								cke_wr <= 1'b1;
								cmd_wr <= NOP;
								dqm_wr <= DQM_EN;
								bank_addr_wr <= BANK0;
								addr_wr <= DONT_CARE_ADDR;
								
								wr_done <= 1'b0;
								wr_first_flag_r <= 1'b0;
								status_wr <= 4'd0;
							end
					endcase
				end
			else
				begin
					cke_wr <= 1'b1;
					cmd_wr <= NOP;
					dqm_wr <= DQM_EN;
					bank_addr_wr <= BANK0;
					addr_wr <= DONT_CARE_ADDR;
					
					wr_done <= 1'b0;
					wr_first_flag_r <= 1'b0;
					status_wr <= 4'd0;
				end
		end

參考文獻

SDRAM驅動篇之簡易SDRAM控制器的verilog代碼實現


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM