【基于FPGA和IS42S16320D的SDRAM控制器设计】

记录一下自己的毕设项目,当时第一次接触FPGA,踩了很多坑,希望此贴可以帮助大家设计SDRAM控制器。

本文提出了一种基于FPGA的SDRAM控制器的设计方法。根据SDRAM的操作时序,使用Verilog语言采用自顶向下的设计方法将控制器划分成多个模块,并对该控制器进行RTL级仿真和板级验证。
在此基础上,本文利用DE10-Lite开发板的组件提供一种便捷的验证该SDRAM控制器的方法。

一、硬件环境

SDRAM芯片是ISSI生产的IS42S16320D。该芯片使用管道架构实现高速的数据传输,每64ms需要刷新8k次。
常用的命令真值表,如下表所示。
在这里插入图片描述
芯片IS42S16320D的引脚的定义与功能描述如下表所示。
在这里插入图片描述
IS4216320D的主要时钟参数如下表所示。
在这里插入图片描述

二、模块设计

该SDRAM控制器使用Verilog HDL(Verilog)语言设计。
SDRAM控制器的模块划分采用自顶向下的设计方法。为了提高SDRAM控制器代码的可读性,模块的设计采用**多段式有限状态机(Finite State Machine,FSM)**描述。具体方法如下:通过使用always块,一个always块利用同步时序逻辑描述状态转移;一个always块利用同步时序逻辑描述状态输出(有些模块会再使用一个always块利用组合逻辑描述状态转移条件)。

SDRAM状态转换

根据SDRAM的时序,我们可以将SDRAM的操作流程用状态转换图表示出来
在这里插入图片描述
该SDRAM控制器整体框架如图所示。
在这里插入图片描述

顶层模块

状态转换

在这里插入图片描述

状态机分析

SDRAM控制器开始运行时,需要初始一个上电复位信号。上电复位是十分重要的,如果没有复位,会导致一些寄存器的初始值变得未知,如果此时SDRAM控制器就开始工作的话,极易出现错误。复位完成后进入初始化状态Init_state,初始化内部的状态将在初始化模块进行介绍。当初始化完成后(init_end_flag为高电位),进入空闲状态idle_state。
SDRAM大多数时间都会处在空闲状态,它相当于一个顶层模块,用来判断SDRAM下一步进行读,写或刷新操作。优先级顺序为刷新 > 读 > 写。
刷新操作,由刷新信号refresh_flag来进行判断,如果refresh_flag为高电位,则进入刷新状态Auto_Refresh_state。刷新信号怎么获得呢?该SDRAM每64ms需要刷新8192次,所以我们需要对64ms进行计数,由于该SDRAM为100MHz,相当于1个周期10ns,所以需要计数6400000个周期。但是由于一次刷新8192次,所以需要预留一定的时间进行刷新。这里设置在63.4ms处开始刷新,将刷新信号拉高,等到进入刷新状态后再将刷新信号拉低,计数器需要每隔64ms重新清零。
然后是判断读写操作,由读使能信号RE和写使能信号WR进行判断,当两者任何一个为高电位且refresh_flag为低电位的时候,进入激活状态activate_state。
激活状态需要tRCD(15ns)个时间来进入读/写操作。对于短时间的延迟,我们直接以状态替代延时,比如15ns需要执行2个时钟周期,就可以在activate_state后设置1个activate_NOP_state状态来满足要求。然后分别根据读写使能信号来进入读状态read_state或写状态write_state。读写操作完成后仍回到空闲状态。

状态机操作

首先是对命令进行操作。在Init_Load_mode_register_state、Init_PALL_state和Init_ Auto_Refresh_state分别赋值模式寄存器配置、预充电命令和自动刷新命令,其余时刻执行NOP命令。
然后是对Bank和A0-A12地址进行操作。在Init_PALL_state将init_ba和init_addr赋值全为1;在Init_Load_mode_register_state需要设置模式寄存器,其余时刻都为0。关于模式寄存器设置,由于本次实验是对小数据进行单次测试,这里我们设置突发长度为1,顺序突发,列选潜伏期为2。

主程序
///主程序
always @(posedge MAX10_CLK1_100)//每64ms自动刷新一次标志

		begin
				
					if(refresh_count_timer < REFRESH_64ms_COUNT)
						begin
							refresh_count_timer <=refresh_count_timer+1;
							if( (refresh_count_timer > REFRESH_CYCLE_PULSE_WIDTH_1 && refresh_count_timer < REFRESH_CYCLE_PULSE_WIDTH_2))
								begin
									refresh_flag <=1'b1;//在接近64ms的时候宣布刷新
								end
							else
								refresh_flag<=1'b0;
						end
					else
						begin
							refresh_count_timer <=23'd1;//重新64ms的计数
							refresh_flag<=1'b0;
						end
	
		end

always @(posedge MAX10_CLK1_100)//状态机
		begin
			
			if(!reset_n)	//判断复位信号		
                begin
					current_state=Init_state;
				end
			else
				begin
                    case (current_state)
                        Init_state:	//初始化
								begin
									  			
									if (init_end_flag==1'b1)//初始化结束
										begin
											current_state=idle_state;   //进入空闲状态
										end
									else
										current_state=Init_state;//继续初始化
																  
								end
                        idle_state:
                                if(refresh_flag && !refresh_flag_ack)	//判断刷新
									begin
										refresh_flag_ack<=1'b1;
										current_state=Auto_Refresh_state;			
										
									end
								else if(!RE && read_flag_ack) //判断读
									begin
										read_flag_ack=1'b0;
									end
								else if(RE && !read_flag_ack)
									begin
										read_flag_ack<=1'b1;
										current_state=activate_state;
									end
									
								else if(!WR && write_flag_ack) //判断写
									begin
										write_flag_ack=1'b0;
									end
								else if(WR && !write_flag_ack)
									begin
										write_flag_ack<=1'b1;
										current_state=activate_state;
									end
                        Auto_Refresh_state://刷新
                                if(refresh_end_flag==1'b1)
                                    begin
										refresh_flag_ack<=1'b0;
										current_state=idle_state;
                                    end
                                else
                                    current_state=Auto_Refresh_state;
                        activate_state://激活
                                begin
										current_state=activate_NOP_state;
								end
								activate_NOP_state: 
									begin
										if(read_flag_ack)	
											begin
												current_state=read_state;
											end
										else if(write_flag_ack)
											begin
												current_state=write_state;
											end
										
									end
                         write_state://写
                                begin
                                    if(write_end_flag==1'b1)
                                        begin
                                            write_flag_ack=1'b0;
                                            current_state=idle_state;
                                        end
                                    else
                                        current_state=write_state;
                                end                           
                         read_state://读
                                begin
                                    if(read_end_flag==1'b1)
                                        begin
                                            read_flag_ack=1'b0;
                                            current_state=idle_state;
                                        end
                                    else
                                        current_state=read_state;
                                end
                        default : 
                            current_state<=idle_state;
					endcase
				end	
        end

//CKE,CS
assign      DRAM_CKE=1'b1; 
assign      DRAM_CS_N=1'b0;

//命令
assign      {DRAM_RAS_N, DRAM_CAS_N, DRAM_WE_N} = CMD;
assign CMD =(current_state==Init_state)?init_cmd:
(current_state==activate_state)?CMD_ACTIVE:
(current_state==read_state)?read_cmd:
(current_state==write_state)?write_cmd:
(current_state==Auto_Refresh_state)?refresh_cmd:CMD_NOP;

//SDRAM时钟
assign      DRAM_CLK=MAX10_CLK2_100_3ns;    

//用户反馈
assign      RD_REQUEST_APPROVED = read_flag_ack;    
assign      WR_REQUEST_APPROVED = write_flag_ack;

//数据掩码
assign {DRAM_UDQM,DRAM_LDQM} = (current_state==write_state)?write_dqm:(current_state==read_state)?read_dqm:2'b11;

//地址
assign {DRAM_BA,DRAM_ADDR}=(current_state==activate_state)? ADDR[24:10]:
       (current_state==read_state)? {read_ba,read_addr}:
       (current_state==write_state)?{write_ba,write_addr}:
       (current_state==Init_state)? {init_ba,init_addr}:{15{1'b0}};	

初始化模块

状态转换

在这里插入图片描述

状态机分析

首先需要1个100us的上电延迟。这里我们设置1个计数器,由于该SDRAM为100MHz,相当于1个周期10ns,所以需要计数10000个周期,在计数期间,令其一直处在Init_NOP_state。在计数结束时将标志拉高powerup_complete_flag,并进入初始化预充电状态Init_PALL_state。初始化预充电需要tRP(15ns)个时间,所以还需要设置Init_PALL_state_NOP1_state。
预充电后进入初始化的刷新状态Init_Auto_Refresh_state。初始化的刷新至少需要2次,每次需要tRC(60ns)个时间。这里我们还需要设置6个时钟周期,以预防一些意外情况发生。(在实际设计中,最初加上Init_Auto_Refresh_state只设计了6个时钟,但是在仿真过程中出现了tRC时序冲突)所以设置Init_Auto_Refresh_NOP(1-6)state。每次自动刷新都会使计数器count_auto refresh_init加1。当到达状态6时,判断是否完成2次刷新,如果只完成一次,则返回Init_Auto_Refresh_state。完成2次刷新后进入模式寄存器配置状态Init_Load_mode_register_state并将计数器清零。
模式寄存器配置状态需要tMRD(14ns)个时间,所以还需要额外设置1个时钟周期Init_Load_mode_register_NOP_ state。结束后,完成SDRAM的初始化,将init_end_flag拉高。

状态机操作

首先是对命令进行操作。在Init_Load_mode_register_state、Init_PALL_state和Init_ Auto_Refresh_state分别赋值模式寄存器配置、预充电命令和自动刷新命令,其余时刻执行NOP命令。
然后是对Bank和A0-A12地址进行操作。在Init_PALL_state将init_ba和init_addr赋值全为1;在Init_Load_mode_register_state需要设置模式寄存器,其余时刻都为0。关于模式寄存器设置,由于本次实验是对小数据进行单次测试,这里我们设置突发长度为1,顺序突发,列选潜伏期为2。

主程序
///主程序
always @(posedge clk)//100us上电延迟

		begin
				if( current_state==Init_NOP_state)
					begin
						
						if (count_init_powerup_delay<POWERUP_DELAY-2)//100 us
							begin
								powerup_complete_flag =1'b0;
								count_init_powerup_delay = count_init_powerup_delay + 1;
							end
						else
							powerup_complete_flag =1'b1;
							
					end
				else
					count_init_powerup_delay=0;
		end
					

always @(posedge clk)//状态机
begin
		
		case (current_state)
			  
			  Init_NOP_state:	//上电延迟100us.
			  begin
				  			
							if (powerup_complete_flag==1'b1)//100us延迟后开始预充电
								begin
									current_state=Init_PALL_state;
								end
							else
								current_state=Init_NOP_state;//等待100us计数
											  
			  end
			  			  
			  Init_PALL_state:
			  begin
						current_state=Init_PALL_state_NOP1_state;
												  
			  end
			  
			  Init_PALL_state_NOP1_state: //TRP 15ns,两个时钟周期
			  begin
					current_state=Init_Auto_Refresh_state;
			  end
			  
			  Init_Auto_Refresh_state: //初始化的刷新需要两次
			  begin
						count_auto_refresh_init<=count_auto_refresh_init+1;
						current_state=Init_Auto_Refresh_NOP1_state;
			  end
				
				Init_Auto_Refresh_NOP1_state:		//tRC 60ns 设置70ns以防意外
				begin
					current_state=Init_Auto_Refresh_NOP2_state;
				end

				Init_Auto_Refresh_NOP2_state:
				begin
					current_state=Init_Auto_Refresh_NOP3_state;
				end

				Init_Auto_Refresh_NOP3_state:
				begin
					current_state=Init_Auto_Refresh_NOP4_state;
				end

				Init_Auto_Refresh_NOP4_state:
				begin
					current_state=Init_Auto_Refresh_NOP5_state;
								
				end
				
				Init_Auto_Refresh_NOP5_state:
				begin
					current_state=Init_Auto_Refresh_NOP6_state;
								
				end
				
                Init_Auto_Refresh_NOP6_state:
				begin
						if (count_auto_refresh_init < AUTO_REFRESH_INIT) 
							current_state=Init_Auto_Refresh_state;
						else
							current_state=Init_Load_mode_register_state;
				end
            Init_Load_mode_register_state:
			begin
					current_state=Init_Load_mode_register_NOP_state;
			end

			Init_Load_mode_register_NOP_state:
			begin
					current_state=idle_state;	//初始化结束
                    init_end_flag=1'b1;
			end    
            idle_state:
            begin
                if(init_end_flag==1'b1)
                    current_state=idle_state;
                else
                    current_state<=Init_NOP_state;
            end
            default : current_state<=idle_state;
        endcase
 end                                   

//命令
assign cmd =(current_state==Init_Load_mode_register_state)?CMD_LOADMODE:
(current_state==Init_PALL_state)?CMD_PRECHARGE:
(current_state==Init_Auto_Refresh_state)?CMD_AUTO_REFRESH:CMD_NOP;

//地址
assign {init_ba,init_addr}=(current_state==Init_PALL_state)? {15{1'b1}}:				//令所有BA[1]+A[12]为1,其中A10必须为高
		 (current_state==Init_Load_mode_register_state)? MODE_REG:{15{1'b0}};			//模式寄存器配置. 

刷新模块

状态转换

在这里插入图片描述

状态机分析

每次自动刷新需要执行8192次,每次需要tRC(60ns)个时间,其步骤与初始化刷新类似。在自动刷新状态Auto_Refresh_64ms_state,其后设置6个状态Auto_Refresh_NOP(1-6)_state的延时来满足tRC,在状态6判断计数器是否完成8192次。如果没有完成,则返回Auto_Refresh_64ms_state,如果全部完成则将刷新结束信号refresh_end_flag拉高,计数器清零,刷新操作,结束进入空闲状态idle_state。
在空闲状态时,当刷新使能信号refresh_flag为高时,才会进入自动刷新状态。

状态机操作

此模块只需要对状态命令进行设置,在Init_Auto_Refresh_state赋值自动刷新命令,其余时刻执行NOP命令。

主程序
///主程序
always @(posedge clk)//状态机
    begin
		
		case (current_state)

            Auto_Refresh_64ms_state: 
					  begin
                            refresh_end_flag<=1'b0;
							current_state=Auto_Refresh_NOP1_state;
					  end
				
				Auto_Refresh_NOP1_state:				// tRC 60ns.
				begin
                    count_auto_refresh<=count_auto_refresh+1;
					current_state=Auto_Refresh_NOP2_state;
				end
													
				Auto_Refresh_NOP2_state:
				begin
					current_state=Auto_Refresh_NOP3_state;
				end
				
				Auto_Refresh_NOP3_state:
				begin
					current_state=Auto_Refresh_NOP4_state;
				end
				
				Auto_Refresh_NOP4_state:
				begin
					current_state=Auto_Refresh_NOP5_state;			
				end
				
				Auto_Refresh_NOP5_state:
				begin
					current_state=Auto_Refresh_NOP6_state;			
				end
				
                Auto_Refresh_NOP6_state:			 
				begin
					 
					if (count_auto_refresh<REFRESH_CYCLE) 
						current_state=Auto_Refresh_64ms_state;
					else begin
							count_auto_refresh=14'd0;
							refresh_end_flag<=1'b1;
							current_state=idle_state;
						end
				end

            idle_state:
                begin
                if(refresh_flag==1'b1)
                    current_state<=Auto_Refresh_64ms_state;
                else
                    current_state=idle_state;
                end
			default : current_state<=idle_state;
        endcase
    end
    
assign cmd =(current_state==Auto_Refresh_64ms_state)?CMD_AUTO_REFRESH:CMD_NOP;

读模块

状态转换

在这里插入图片描述

状态机分析

进入读状态read_state,在CAS latency为2的时候,需要持续四个时钟周期,所以这里还需设置NOP_read_state1、NOP_read_state2和NOP_read_state3。完成后,进入预充电状态Precharge_state。
预充电状态到下一次操作前需要tRP(15ns)个时间,需要两个时钟周期,所以还需设置Precharge_NOP_state。预充电完成后返回空闲状态idle_state。
在空闲状态,对刷新使能refresh_flag和读使能re进行判断。如果re为高电位且refresh_flag为低电位才能进入读状态,其他时候则保持空闲状态。

状态机操作

首先是对命令进行操作。在read_state和Precharge_state赋值读命令和预充电命令,其余时刻执行NOP命令。
然后是对Bank和A0-A12地址进行操作。在read_state时,将用户选择的Bank和列地址addr赋值到read_ba和read_addr;在Precharge_state将所有Bank设置为1全部预充电,其余时刻为0。
在读取的任何时刻,数据掩码dqm都需要拉低,其余时刻将其拉高。数据的读出,在clk_3ns时钟下,只有当读取的最后一个状态,才能读取数据,所以在NOP_read_state3时,将SDRAM数据赋值给data。

主程序
///主程序
always @(posedge clk)//状态机
    begin
		
		case (current_state)
            idle_state:
                begin
                    if(re && !refresh_flag)
                        current_state=read_state;
                    else
                        current_state=idle_state;
                end
            read_state:
                begin
                    read_end_flag=1'b0;
                    current_state=NOP_read_state1;
                end
                NOP_read_state1://CAS_LATENCY = 2
                    begin
                        current_state=NOP_read_state2;
                    end
                NOP_read_state2://需要3个nop操作
                    begin
                        current_state=NOP_read_state3;
                    end
                NOP_read_state3:
                    begin					  
                            if(re && !refresh_flag)
                            begin
                                current_state=read_state;
                            end
                        else
                            begin
                                current_state=Precharge_state;
                            end 
                    end
            Precharge_state:
                begin
                    current_state=Precharge_NOP_state;
                end
                Precharge_NOP_state:// tRP 15ns
                    begin
                        read_end_flag=1'b1;
                        current_state=idle_state;
                    end
            default : current_state<=idle_state;
        endcase
    end		

//SDRAM时钟
always @(posedge clk_3ns )
begin
	if(current_state==NOP_read_state2)
		begin
			data<=dq;
		end
end

//命令
assign cmd =(current_state==read_state)?CMD_READ:(current_state==Precharge_state)?CMD_PRECHARGE:CMD_NOP;

//地址
assign {read_ba,read_addr}=(current_state==read_state)?{addr[24:23],3'b000,addr[9:0]}:(current_state==Precharge_state)? {15{1'b1}}:{15{1'b0}};

//数据掩码
assign dqm = (current_state==read_state|current_state==NOP_read_state1|current_state==NOP_read_state2|current_state==NOP_read_state3)?2'b00:2'b11; 

写模块

状态转换
在这里插入图片描述

状态机分析

进入写状态write_state,因为写操作没有列选潜伏期,所以根据3.2.4的时序图只需要持续3个时钟周期,所以这里还需设置NOP_write_state1和NOP_write_state2。完成后,进入预充电状态Precharge_state。
预充电状态到下一次操作前需要tRP(15ns)个时间,需要两个时钟周期,所以还需设置Precharge_NOP_state。预充电完成后返回空闲状态idle_state。
在空闲状态,对刷新使能refresh_flag和写使能wr进行判断。如果wr为高电位且refresh_flag为低电位才能进入写状态,其他时候则保持空闲状态。

状态机操作

首先是对命令进行操作。在write_state和Precharge_state赋值写命令和预充电命令,其余时刻执行NOP命令。
然后是对Bank和A0-A12地址进行操作。在write_state时,将用户选择的Bank和列地址addr赋值到write_ba和write_addr;在Precharge_state将所有Bank全部预充电,其余时刻为0。
在写入操作下,只有在write_state时才能进行数据写入,所以在write_state时将数据掩码dqm拉低,其余时刻将其拉高。数据的写入,write_state时,将data数据写入SDRAM中。

主程序
///主程序

always @(posedge clk)//状态机
    begin
		
		case (current_state)
            idle_state:
                begin
                    if(wr && !refresh_flag)
                        current_state=write_state;
                    else
                        current_state=idle_state;
                end
            write_state:
                begin
                    write_end_flag=1'b0;
                    current_state=NOP_write_state1;
                end
                NOP_write_state1:
                    begin
                        current_state=NOP_write_state2;
                    end
                NOP_write_state2:
                    begin					  
                            if(wr && !refresh_flag)
                            begin
                                current_state=write_state;
                            end
                        else
                            begin
                                current_state=Precharge_state;
                            end 
                    end
            Precharge_state:
                begin
                    current_state=Precharge_NOP_state;
                end
                Precharge_NOP_state:// tRP 15ns
                    begin
                        write_end_flag=1'b1;
                        current_state=idle_state;
                    end
            default : current_state<=idle_state;
        endcase
    end		

//数据
assign dq=(current_state==write_state)?data:{16{1'bz}};

//命令
assign cmd =(current_state==write_state)?CMD_WRITE:
            (current_state==Precharge_state)?CMD_PRECHARGE:CMD_NOP;

//地址 
assign {write_ba,write_addr}=(current_state==write_state)?{addr[24:23],3'b000,addr[9:0]}:
                              (current_state==Precharge_state)? {15{1'b1}}:{15{1'b0}};
//数据掩码
assign dqm = (current_state==write_state)?2'b00:2'b11;

模块优化

连续读和连续写

上文中所提及的读写操作采用的是单个读写操作,每次读写完成后,回到预充电状态,然后再回到空闲状态。如果进行下一次读写操作,需要重新进行激活,然后再进行读写操作。如果是采用连续读和连续写操作,每次读写操作完成后,只需要读写使能信号(RE / WR)依旧为高电位且刷新信号为低电位时,便可以直接进行下一次读写操作,从而跳过了预充电和激活状态。大大提高SDRAM控制器的读写效率。
具体操作是在读写操作的最后一个状态,预充电的前一个状态添加判断条件,如果读写使能信号依旧为高电位,刷新信号为低电位的时候,继续执行读/写操作,否则跳到预充电状态。

反馈机制

SDRAM控制器可以看成是一个综合的内部系统。当用户需要写入数据后,向SDRAM控制器输入写使能信号WR,然后SDRAM控制器控制SDRAM进行写入。但是用户不知道系统有没有完成这次写入。因此,需要相应的响应信号,来帮助SDRAM控制器系统对用户进行反馈。
在sdram_controller模块添加2个引脚,RD_REQUEST_APPROVED和WR_REQUEST_APPROVED为输出信号。在每次读写操作开始的时候将其拉高,在读写操作完成后将其拉低。

刷新模式优化

市面上大多数SDRAM控制器的刷新模式都是采用间隔一段时间刷新一次。以该SDRAM为例,每64ms刷新8192次,64ms / 8192=7.812us,也就是说需要每7.812us刷新一次。但是,如果当时状态是处于读写操作,因为读写操作与刷新操作是同级的,所以刷新操作需要预留出一定时间。这个时间是由突发长度决定的,以该SDRAM控制器为例,读写操作至少为40ns,预充电操作至少为20ns,每个操作前后预留一个周期的时间,这里设置一共预留100ns。此外,从空闲状态到刷新状态需要tRP(15ns),从刷新状态跳出需要tRC(15ns),所以至少4个周期40ns。这些时间看起来并不大,但是每64ms乘以8192次就是一个比较大的数字。所以本文设计的控制器系统将其一次刷新完,会大大提高SDRAM控制器的存储和写入效率。

三、仿真验证

SDRAM控制器程序的编译与仿真是基于ModelSim SE 10.4。

TestBench

仿真测试(TestBench)相当于计算机替代用户对SDRAM控制器进行具体的操作,目的是为了对电路进行仿真验证,测试设计电路的功能、性能与设计的预期是否相符。
仿真的TestBench具体操作描述如下:SDRAM控制器系统运行后,间隔100ns进行复位。(复位条件是必要的,如果没有复位,会导致寄存器的初始值变得未知,如果此时SDRAM控制器就开始工作的话,极易导致错误)复位完成后,延时101us,测试初始化模块。
测试连续写模块:延时10ns(SDRAM控制器1个周期为10ns),将WR信号拉高;延时10ns,地址位输入0,写入数据16’d8;延时20ns,因为写操作至少需要3个周期。延时10ns,令地址位为1,写入数据16’d1;延时20ns,写操作延时;延时10ns,将WR信号拉低。
测试连续读模块:接上述操作后,延时200ns,将RE信号拉高,地址位输入0;延时40ns,读操作至少持续4个周期;延时10ns,令地址位为1;延时40ns,读操作延时;延时10ns,将RE信号拉低。
编写完成后,例化一个虚拟SDRAM的仿真模型sdram_model_plus。
运行如下图所示。
在这里插入图片描述
在这里插入图片描述

主程序

initial begin
	clk_c0<=1;
	reset_DRAM_CTRL<=0;
    //#3;
    //clk_c1<=1;
	#100;
	reset_DRAM_CTRL<=1;
end

always #5 clk_c0=~clk_c0;
always @clk_c0 #3 clk_c1=clk_c0;

initial begin
	#101000;
    #10 write_pulse=1;
    #10 DATA_WRITE=16'd8;
    #20 ;//nop
	#10 ADDR_COUNTER_WRITE=ADDR_COUNTER_WRITE+1; 
        DATA_WRITE=16'd1;
    #20
    #10 ADDR_COUNTER_WRITE=ADDR_COUNTER_WRITE+1;
        write_pulse=0;
    #200 read_pulse=1;
    #40; 
    #10 ADDR_COUNTER_READ=ADDR_COUNTER_READ+1;
    #40;
    #10 read_pulse=0;
    
end

initial begin
    #63300000   read_pulse=1;
    #700000     read_pulse=0;
end

仿真波形

初始化模块仿真波形

在这里插入图片描述

写模块仿真波形

在这里插入图片描述

读模块仿真波形

在这里插入图片描述

刷新模块仿真波形

在这里插入图片描述

四、板级验证

本文板级验证选取的开发板为DE10-Lite。
软件使用Altera公司的Quartus II。

搭建环境

对于DE10-Lite用户,Intel提供System Builder程序来快速创建De10-Lite所需要的环境工程。
在这里插入图片描述
本次板级验证工程需要用到的引脚有CLOCK、LED、Button和Switch。

模块设计

为了简化用户操作,使用DE10-Lite的组件进行操控:SW开关控制SDRAM的写入,LED灯表示SDRAM的读取,Button按钮控制读取和写入操作的选择。
在这里插入图片描述

按键消抖模块

由于机械按键的物理特性,按键被按下的过程中,存在一段时间的抖动,同时,在按键释放的过程中也存在抖动,这就导致在识别的时候可能检测为多次的按键按下。通常检测到一次按键输入信号的状态为低电平,按键释放信号的状态为高电平,系统就会认为按键被按下。所以在使用按键时往往需要消抖,以确保按键每被按下一次只检测到一次低电平,释放的时候只检测到一次高电平。

锁相环模块

PLL(Phase Locked Loop):为锁相回路或锁相环,是一种反馈控制电路,其特点是利用外部输入的参考信号控制环路内部振荡信号的频率和相位。

顶层模块

例化三个子模块,并写入一个测试程序。
首先,在2s后,对整个系统进行上电复位。上电复位是十分重要的,如果没有复位,会导致一些寄存器的初始值变得未知,如果此时SDRAM控制器就开始工作的话,极易出现错误。
复位完成后,可进行读写操作的选择。按下KEY0,进行读操作。按下KEY1,进行写操作。
写操作,该系统使用De-10Lite的10个SW开关进行写入数据,写入的数据存储在Bank为0和地址为0的位置。注意,每个SW开关代表二进制,但是DQ为16位数据,所以写入的数据是将2进制转化为16进制。
读操作,该系统使用De10-Lite的10个LED灯进行读取数据,每个LED灯对应相应的SW开关。读取数据位置是在Bank为0和地址为0。

主程序
// 上电复位
always @ (posedge clk_c0)
begin
	if(reset_release<28'h1DCD6500)//2s
		reset_release<=reset_release+1;
	
end
assign reset=(reset_release==28'h1DCD6500)?1:0;

//读写地址为0
assign ADDRESS=(write_pulse==1)?ADDR_COUNTER_WRITE:ADDR_COUNTER_READ;
assign LEDR = DATA_READ[9:0];

/读
integer sm_read_state=0;
	
always @ (posedge MAX10_CLK1_50)
begin
	if(reset==1'b1 & KEY_0==1'b1 & sm_read_state==0) begin
			read_pulse = 1'b1;
			sm_read_state = 1;
	end
	else if (sm_read_state==1)	begin
			sm_read_state = 2;
	end
		
	else if(sm_read_state==2) begin
			sm_read_state = 3;
	end
	else if(sm_read_state==3) begin
			if(RD_REQUEST_APPROVED) begin
				read_pulse = 1'b0;
				sm_read_state = 4;
			end 
	end
	else if(sm_read_state==4) begin
			if(!WR_REQUEST_APPROVED) begin
				sm_read_state = 0;
			end
	end
end



/写
integer sm_write_state=0;

always @ (posedge MAX10_CLK1_50)
begin
	if(reset==1'b1 & KEY_1==1'b1 & sm_write_state==0) begin
			write_pulse = 1'b1;
			DATA_WRITE=SW;
			sm_write_state = 1;
	end
	else if (sm_write_state==1)	begin
			sm_write_state = 2;
	end
	else if(sm_write_state==2) begin
			if(WR_REQUEST_APPROVED) begin
				write_pulse = 1'b0;
				sm_write_state = 3;
			end 
	end
	else if(sm_write_state==3) begin
			if(!WR_REQUEST_APPROVED) begin
				sm_write_state = 0;
			end
	end
		
end

验证

Intel对于De10-Lite用户提供了一个便于测试和验证的Control Panel程序。
在这里插入图片描述

验证方法

开发板验证

首先,整个板级工程在Quartus编译完成后,下载到De10-Lite开发板上。待其上电稳定后,通过板上的KEY1和SW开关,将数据写入到SDRAM中。然后按下KEY0,观察LED亮灯的位置与SW开关是否一一对应。

Control Panel验证

按照上述操作将数据输入进SDRAM后,将Control Panel与De10-Lite进行连接,并读取地址为0的数据。将读取的十六进制数据转化为2进制,并与De10-Lite上的SW开关进行比较。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值