基于FPGA DDR3设计之仿真调试

基于FPGA DDR3设计之仿真调试

平台:vivado 2017.4

FPGA芯片:XC7K325T-FFG900

DDR3芯片:SCB13H4G160AF

一、MIG创建

        在进行DDR3读写测试之前先创建对应的FGPA型号,这里我选用的FPGA型号是复旦微的JFM7K325T,兼容Xilinx公司的 XC7K325T-FFG900。

       

         创建工程后,需要添加mig来进行DDR配置,在左边IP Catalog中进行mig添加,搜索出来后双击进行配置。

        

        双击打开后可以在首页查看你所选择的FPGA信号、平台、以及使用的语言。

       

        第一次创建可以修改工程名,关于AXI接口主要是在使用ZYNQ时ARM端进行控制,在K7系列上的DDR接口一般不使用它。

        

        该配置界面可以选择与当前FPGA引脚兼容的其他型号,主要是针对一些器件升级的应用。

        

        选择使用的外部存储芯片是使用的哪个版本,本次选用的是DDR3 SDRAM。

       

         在Controller Options配置页面对DDR3使用频率及芯片型号选择进行配置。

        Clock Period:设置DDR3的IO时钟频率,根据芯片上实际DDR3支持的最大时钟频率进行设置,MT41K256M16RE-125最大支持800MHz的接口时钟,但在测试中发现800MHz速率下有丢数据的问题,因此使用400MHz。

        PHY to Controller Clock Ratlo:FPGA给DDR3芯片提供的时钟频率为Clock Period,这个参数就是用来设置DDR3接口时钟与MIG IP提供给FPGA内部其他模块的时钟(ui_clk)频率比值,上面400MHz比率4:1则ui_clk 频率为100MHz。

        Vccaux_io:设置bank的供电电压,一般都是锁死的,会根据DDR3时钟频率变化而变化。

        Memory Type:一般选择Components即可,其余几种与内存条有关吧。

        Memory Voltage:选择芯片引脚的电平标准,DDR3一般只有1.5V和1.35V两种,根据原理图选择支持的电平的类型即可。

        Data Width:DDR3读写数据位宽,根据设计选择DDR3位宽,本设计使用了两片16bit的芯片,因此选择32bit位宽。

        ECC:数据线多余72为才能使用的功能。

        Data Mask:数据掩码功能,可以通过一个掩膜信号去控制DDR3写入数据,本质就是去控制DDR3的DM引脚。

        Number of Bank Machies:选择bank数量,DDR3最大可选择8个bank,为了降低功耗,节约资源,使用4个bank即可。

        ORDERING:DDR控制器调度命令的顺序的配置,当选择strict时,严格按照命令先后顺序执行;选择normal时,为了得到更高的效率,可能对命令重排序。

        在Memory Options配置页面进行配置,该界面默认即可。

        Input Clock Period:这个时钟是提供给MIG IP的时钟sys_clk;

        Read Burst Type and Length:设置为顺序读写Sequential,从SDRAM到DDR3都支持该功能,该参数在模式寄存器中进行设置,两种读写方式的区别在SDRAM的文章中详细分析过,需要了解的可以查看;

        Output Drive Impedance Control:输出驱动阻抗控制选择RZQ/7;

        On Die Termination:片上终端电阻设置为R ZQ/4;

        Controller Chip Select Pin:片选信号设置为Enable,如果板卡上DDR3的CS管脚未连接到FPGA,设置为Disable;

        Memory Address Mapping Selection:DDR和AXI总线之间的地址映射存储器地址映射选择。默认选择后者,在调用IP时,其实不会关心bank地址和行、列地址;

        FPGA Options配置如下:

       System Clock:设置系统时钟sys_clk的来源,图7设置为200MHz。由FPGA内部提供,不由管脚输入,选择No Buffer。如果实际硬件管脚有提供200MHz时钟,也可以选择Differential(差分输入)或Signal-Ended(单端输入);

       Reference Clock:设置图8中IDELAY参考时钟200MHz的来源,由于在图7中将系统时钟设置为200MHz,可以选择Use System Clock,将两个输入时钟合并一个共用的200MH输入。如果图7中系统时钟设置不是200MHz,这里配置选项就没有“Use System Clock”,只能由管脚端口输入时钟或者FPGA内部产生这个200MHz时钟;

       System Reset Polarity:系统复位极性设置为ACTIVE LOW(低电平);

       Debug Signal for Memory Controller:关闭存储器控制器的调试信号,如果选择ON,就会在IP中添加ILA作为调试,本文并不需要,关闭即可;

       Sample Data Depth:只有4设为ON,此处才能设置,就是设置ILA采集数据的深度;

       勾选internal Verf,允许把参考引脚作为正常的IO引脚使用;

       IO Power Reduction:相当于不操作时进入休眠状态降低功耗,这个一般关闭;

       XADC Instantiation:使能MIG IP通过XADC来获取温度,实现温度补偿,没有单独使用XADC时启用该功能;

        Extended  FPGA Options配置如下:

       在IO Planning Options中上面是使用官方固定引脚,下面是自己根据原理图信息进行引脚分配,该设计我选用下面一个进行:

        在验证完成后一直点击下一步即可,后面可默认,最后生成ip即可使用;

        

        如下图生成完成后,DDR3关于IP的创建就结束了,就可以进行仿真调试和上板验证了;

       在IP核创建过程中有几个需要注意的点:第一个是几个时钟的设置,哪个时钟是怎么用的别搞混了;第二个就是选用的DDR型号一定要是兼容的,不然上板会有问题。

二、相关信号分析

       在创建IP后,可以打开查看顶层各个控制信号,下图是7系列FPGA关于DDR的接口情况,左边是我们需要控制的信号,右边是连接到phy的信号;

       在进行对控制信号设计之前需要了解各个信号的作用是什么,这里简单的介绍一下,具体各个信号的作用可以在UG_586文档中做详细理解;

app_addr[28:0]

Input

此输入指示当前请求的地址。

app_cmd[2:0]

Input

该输入选择当前请求的命令,0写1读。

app_en

Input

这是app_addrI、app_cmd[2:0]、app_sz和app hi pri输入的高电平脉冲。

app_rdy

Output

这个输出表明UI已经准备好接受命令了。如果在启用app_en时信号不断言,则必须重试当前app_cmd和app_addr,直到断言app_rdy。

app_hi_pri

Input

此高主动输入提高了当前请求的优先级。

app_rd_data[255:0]

Output

它提供读取命令的输出数据。

app_rd_data_end

Output

这个高电平输出表明当前时钟周期是app rd_data[]上输出数据的最后一个周期。

app_rd_data_valid

Output

这个高电平输出表明app_rd_data[]是有效的。

app_sz

Input

这个输入是保留的,应该绑定到0。

app_wdf_data [255:0]

Input

这为写命令提供了数据

app_wdf_end

Input

这个高电平输入表明当前时钟周期是应用程序wdf data[]上输入数据的最后一个周期。

app_wdf_mask [31 :0]

Input

这为app_wdf_data[]提供了掩码。

app_wdf_rdy

Output

这个输出表明写数据FIFO已经准备接收数据。当app wdf rdy =1’b1, app wdf wren=1’b1时,可写入数据。

app_wdf_wren

Input

这是app_wdf_data高电平有效。

app_correct_en_i

Input

当断言,这主动高信号校正单位数据错误。只有在GUI中启用ECC时,此输入才有效。在示例设计中,该信号始终与1绑定。

app_sr_req

Input

这个输入是保留的,应该绑定到0。

app_sr_active

Output

此输出是保留的

app_ref_req

Input

此高电平输入请求向DRAM发出refresh命令。

app_ref_ack

Output

这个高电平输出表明存储器控制器已经发送了请求的刷新命令到PHY接口。

app_zq_req

Input

此高电平输入请求向DRAM发出ZQ校准命令。

app_zq_ack

Output

这个高电平输出表明存储器控制器已经发送请求的ZQ校准命令到PHY接口

clk

Input

这个UI时钟是DRAM时钟的四分之一。

init_calib_complete

Output

置1表示DDR3初始化成功。

rst

Input

根据mig中设置的高或者低电平有效。

       关于数据位宽,在mig设置时用户时钟频率为100MHz,MIG IP设置的DDR3端口时钟频率为400MHz,且双边沿传输32位数据,那么MIG用户侧读写数据位宽则需要256位。数据掩膜信号1位可以控制8位数据的写入,一次写入MIG IP256位数据,所以需要32位的数据掩膜信号。

       下图是非背靠背模式的写数据与写命令的时序图,写数据可以与写命令对齐,写数据也可以提前写命令,写数据也可以滞后写命令,写数据滞后写命令不应该超过2个时钟的限制。

       读时序如下图所示,当读命令发出后,等待读数据有效指示信号拉高,表示此时读数据线上的数据有效。

三、仿真设计

       仿真设计有两种方式,一是直接使用官方生成的tb文件和官方控制代码进行仿真,二是自己设计控制代码和官方的tb文件结合来进行仿真,可以很好的模拟出自己的逻辑设计,便于上板调试,这里我们使用第二种方法,使用自己设计的控制部分加上官方的tb文件结合进行仿真;

     下面几个文件中修改example_top顶层,加上自己设计的data_burst控制DDR写入读出,ddr3_control控制数据的产生写入fifo和将DDR读出来的数据放入到RAM当中便于读取;

3.1、代码设计

       关于data_burst控制部分代码入下所示:

//------------------------------------
//assign define
//------------------------------------
assign app_cmd 			= app_cmd_r;
assign app_addr 		= app_addr_r;
assign app_en 			= app_en_r;
assign app_wdf_end 		= app_wdf_end_r;
assign app_wdf_data 	= wr_burst_data;

assign app_wdf_wren 	= app_wdf_wren_r & app_wdf_rdy;
assign rd_burst_finish 	= (state == READ_END);
assign wr_burst_finish 	= (state == WRITE_END);
assign burst_finish 	= rd_burst_finish | wr_burst_finish;
assign rd_burst_data 	= app_rd_data;
assign rd_burst_data_valid = app_rd_data_valid;
assign wr_burst_data_req = (state == MEM_WRITE) & app_wdf_rdy ;
//----------------------------------
//----------------------------------

always@(posedge clk or posedge rst)
begin
		if(rst)
		begin
				app_wdf_wren_r	<= 1'b0;
		end
		else if(app_wdf_rdy)
				app_wdf_wren_r	<= wr_burst_data_req;
end

always@(posedge clk or posedge rst)
begin
		if(rst)
		begin
				state 	<= IDLE;
                app_cmd_r <= 3'b000;
                app_addr_r <= 0;
                app_en_r <= 1'b0;
                rd_addr_cnt <= 0;
                rd_data_cnt <= 0;
                wr_addr_cnt <= 0;
                wr_data_cnt <= 0;
                app_wdf_end_r <= 1'b0;
                rd_current_addr <= 0;
                wr_current_addr <= 0;
		end
		else if(init_calib_complete === 1'b1)
		begin
				case(state)
						IDLE:
						begin
								if(rd_burst_req)								begin
										state <= MEM_READ;
										app_cmd_r <= 3'b001;
										app_addr_r	<= rd_burst_first_addr;
										app_en_r <= 1'b1;
								end
								else if(wr_burst_req)								begin
										state <= MEM_WRITE;
										app_cmd_r <= 3'b000;
										app_addr_r	<= wr_burst_first_addr;
										app_en_r <= 1'b1;
										wr_addr_cnt <= 0;
										app_wdf_end_r <= 1'b1;
										wr_data_cnt <= 0;
								end
						end

						MEM_READ:						
begin
								if(app_rdy)								
begin
										app_addr_r <= app_addr_r +8;
										if(rd_addr_cnt == rd_burst_len -1)
										begin
												state <= MEM_READ_WAIT;
												rd_addr_cnt	<= 0;
												app_en_r <= 1'b0;
										end
										else
												rd_addr_cnt <= rd_addr_cnt +1;
								end

								if(app_rd_data_valid)														begin
										if(rd_data_cnt == rd_burst_len -1)
										begin
												rd_data_cnt <= 0;
												state	<= READ_END;
										end
										else
										begin
												rd_data_cnt <= rd_data_cnt +1;
										end
								end
						end

						MEM_READ_WAIT:
						begin
								if(app_rd_data_valid)
								begin
										if(rd_data_cnt == rd_burst_len -1)
										begin
												rd_data_cnt <= 0;
												state <= READ_END;
										end
										else
										begin
												rd_data_cnt <= rd_data_cnt +1;
										end
								end
						end

						MEM_WRITE_FIRST_READ:
						begin
								app_en_r <= 1'b1;
								state <= MEM_WRITE;
								wr_data_cnt <= 0;
						end

						MEM_WRITE:
						begin
								if(app_rdy)
								begin
										app_addr_r <= app_addr_r +8;
										if(wr_addr_cnt == wr_burst_len -1)
										begin
												app_wdf_end_r <= 1'b0;
												app_en_r <= 1'b0;
										end
										else
										begin
												wr_addr_cnt <= wr_addr_cnt +1;
										end
								end

								if(wr_burst_data_req)
								begin
										if(wr_data_cnt == wr_burst_len -1)
										begin
												state <= MEM_WRITE_WAIT;
										end
										else
										begin
												wr_data_cnt <= wr_data_cnt +1;
										end
								end
						end

						READ_END:
                        begin
								state <= IDLE;
                                rd_current_addr <= app_addr_r;
                        end

						MEM_WRITE_WAIT:
						begin
								if(app_rdy)
								begin
										app_addr_r <= app_addr_r + 'b1000;
										if(wr_addr_cnt == wr_burst_len -1)
										begin
												app_wdf_end_r <= 1'b0;
												app_en_r <= 1'b0;
												if(app_wdf_rdy)
														state <= WRITE_END;
										end
										else
										begin
												wr_addr_cnt <= wr_addr_cnt +1;
										end
								end
								else if(~app_en_r & app_wdf_rdy)
										state <= WRITE_END;
						end

						WRITE_END:
                        begin
								state <= IDLE;
                                wr_current_addr <= app_addr_r;
                        end
                                
						default:
								state <= IDLE;
				endcase
		end
end

 

3.2、仿真调试

       本次设计将写突发长度参数设置为8,每次fifo中写入8个数据,则将fifo中的数据写入DDR当中,测试时在一定时间后将DDR中的数据读出来放到RAM当中,上板设计是在想读时给一个读请求将DDR中的数据读出来放到RAM中;

       下图是每次累加写入DDR的8个突发数据,由于产生的数据是从1开始,因此第一次突发到10结束(在设计中产生的数据是128bit,但写入DDR的数据是256bit,所以一次是两个数据);

       在给出读地址后,在一定的周期后会读出数据,可以看到读出的第一个数据是1,和写入的数据一致;

       下图可以看到读出的最后一个数据是0x100,转换为10进制是256,一次读出的数据是两个,所以读出的长度是128,满足我设置的读长度是128;

四、上板调试注意事项

DDR3板级调试注意事项:

  1. 在进行设计时需要特别注意app_rdy信号,准备好信号不会像仿真那样规则出现;
  2. 在设置时钟比率2:1和4:1时序存在差异,可通过文档查看两种不同的时序;
  3. 在出现读出数据有丢失时,可以降低速度再查看是否丢数据;
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值