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

  • 相关阅读:
    并不对劲的网络流
    并不对劲的[noi2006]网络收费
    并不对劲的spoj1812
    48.孩子们的游戏(圆圈中最后剩下的数)
    47.扑克牌顺子
    46.翻转单词顺序
    45.左旋转字符串
    44.和为S的两个数字
    43.和为S的连续正数序列
    42.数组中只出现一次的数字
  • 原文地址:https://www.cnblogs.com/rouwawa/p/7339102.html
Copyright © 2020-2023  润新知