基于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板级调试注意事项:
- 在进行设计时需要特别注意app_rdy信号,准备好信号不会像仿真那样规则出现;
- 在设置时钟比率2:1和4:1时序存在差异,可通过文档查看两种不同的时序;
- 在出现读出数据有丢失时,可以降低速度再查看是否丢数据;