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代码实现

原文地址:https://www.cnblogs.com/rouwawa/p/7339102.html