赞
踩
以四颗MT41K512M16HA-125AIT颗粒为例,介绍如何在一块新制板卡上做关于DDR3的器件测试。前面两篇介绍了什么是DDR,并介绍了xilinx给出的FPGA与DDR完美结合的方案MIG IP核,请按照顺序阅读DDR相关文章,链接在文末。DDR3颗粒,DDR3内存条,DDR4颗粒,DDR4内存条都可以与FPGA相连,DDR芯片选型以及链接形式选型与系统对于数据带宽的要求,存储容量的要求,对结构的要求息息相关,同时不同形式不同代DDR对FPGA选型提出了要求,本文介绍DDR3颗粒与FPGA相连,实现读写测试。
提供了两套DDR3颗粒测试工程,工程文件名是DDR3_2PCS800MHz,DDR3_4PCS400MHz,对应2片DDR的800Mhz读写测试,4片DDR的400Hz读写测试。此外还有DDR4颗粒测试工程和DDR3内存条测试工程。两套DDR3颗粒测试工程主控芯片是xilinx xc7k325tffg900-2,颗粒是MT41K512M16HA-125AIT,vivado版本是2020.2。不同的速度测试实际上只需要修改IP里面的配置即可。2片与4片的区别是,一次DDR引脚时钟,写入DDR的数据量是32bit和64bit的区别;MIG IP是按照8倍突发工作,即一个ui_clk用户时钟,MIG读取8个地址的数据,所以2片与4片会导致MIG IP的数据总线是256bit和512bit的区别。为了读者能够快速应用到自己的场景,提供的工程做了区分。需要说明的是,工程应用在本地需要注意,切换相应的时钟输入,切换DDR的UCF文件,切换指示灯的链接,如果有更多的指示灯,则可以接TEST_LENGTH相关的计数器指示一次测试结束,或者指示MIG IP的初始化结果。如果FPGA的型号不同或者DDR颗粒不同,建议重新创建工程进行测试,并且根据对应芯片重新计算TEST_LENGTH。例程都是按照app接口进行控制,在DDR3_4PCS400MHz中提供了一个block design,可以置顶编译综合布线生成bit后,使用tcl的命令便捷读写指定位置,用在定位某个确定位置的读写,这个block design用的是DDR 的AXI接口。
需要注意,本例的DDR颗粒不在MIG IP所直接支持的列表中,因为通过查询手册自定义了一个DDR型号,如下图所示。
通过写入和读出判断读写过程是否正确;此外,工程实践中会遇到当工作频率高的时候DDR读写不稳定, 因此必要进行速度测试。此外还应进行容量进行测试。一个测试工程,对上述三项全部测试;在MIG的平面地址接口中,按照地址顺序写入确定的已知值,然后在按照相同顺序读出这些已知值做比较,相同则常亮led表示测试通过,否则led闪烁表示测试失败。
附verilog测试代码,省略了模块例化部分,程序步骤解读如下:
①例化clk ip产生200MMIG参考电压输入
②例化MIG ip通过app接口读写ddr数据
③写两段式状态机,为进行容量测试,写至满容量的90%即可。写完切换至读状态,若读写无误则一直读写。Wr_addr_cnt或者rd_addr_cnt每计数一次,app_addr_begin自增8,这是因为,当工作频率配置为800MHz且用户时钟ui_clk与工作频率的比值配置为4:1时,ui_clk为200MHz。四片DDR3的数据位宽为64bit,由于”“DDR(double data rate)”,所以在每一个800MHz周期应该提供128bit数据,因此每一个200MHz周期应该向MIG提供512bit数据。而在单一内核中,每一个平面地址存储位数为16bit,四片即64bit,那512bit/64bit=8,即一个200MHz周期的512bit数据写入了8个平面地址,因此此处一次突发(即Wr_addr_cnt或者rd_addr_cnt每计数一次),app_addr_begin自增8。
该颗粒平面地址空间有16+10+3=29bit,上一段的描述提到一次突发需要8个地址,那么满容量可以进行多少次突发?即2^29(满地址)/8=67108864,将TEST_LENGTH设置为32’d60000000,即写了60000000/67108864=90%的空间,这样可以满足容量测试的要求。
④结合状态机运行状态和MIG返回的指示信号为app接口信号赋值,此处应结合各信号含义和接口时序核准。
⑤用户判错逻辑,写入和读出的都是从0开始递增的数据,当出错时指示灯闪烁。
parameter TEST_LENGTH = 32'd60000000; //***********1.先写后读状态机state machine parameter IDLE = 2'd0; parameter WRITE = 2'd1; parameter WAIT = 2'd2; parameter READ = 2'd3; reg [511:0]my_512_data; reg [25:0] wr_addr_cnt; reg [25:0] rd_addr_cnt; reg [1:0] state; always @(posedge ui_clk or negedge rst_n) begin if((~rst_n)||(error_flag)) begin state <= IDLE; my_512_data <= 512'd0; wr_addr_cnt <= 26'd0; rd_addr_cnt <= 26'd0; app_addr_begin<= 30'd0; end else if(init_calib_complete)begin //MIG IP核初始化完成 case(state) IDLE:begin state <= WRITE; my_512_data <= 512'd0; wr_addr_cnt <= 26'd0; rd_addr_cnt <= 26'd0; app_addr_begin <= 30'd0; end WRITE:begin if((wr_addr_cnt == TEST_LENGTH-1) &&(app_rdy && app_wdf_rdy)) state <= WAIT; //写到设定的长度跳到等待状态 else if(app_rdy && app_wdf_rdy)begin //写条件满足 my_512_data <= my_512_data + 1; //写数据自增 wr_addr_cnt <= wr_addr_cnt + 1; //写计数自增 app_addr_begin<= app_addr_begin + 8; //DDR3 地址自增 end else begin //写条件不满足,保持当前状态 my_512_data <= my_512_data; wr_addr_cnt <= wr_addr_cnt; app_addr_begin<= app_addr_begin; end end WAIT:begin state <= READ; //下一个时钟,跳到读状态 rd_addr_cnt <= 26'd0; //读地址复位 app_addr_begin<= 30'd0; //DDR3读从地址0 end READ:begin //读到设定的地址长度 if((rd_addr_cnt == TEST_LENGTH -1 ) && app_rdy) state <= IDLE; //则跳到空闲状态 else if(app_rdy)begin //若MIG已经准备就绪,则开始读 rd_addr_cnt <= rd_addr_cnt + 1'd1; //用户地址每次加一 app_addr_begin <= app_addr_begin + 8; //DDR3地址加8 end else begin //若MIG没准备好,则保持原 rd_addr_cnt <= rd_addr_cnt; app_addr_begin <= app_addr_begin; end end default:begin state <= IDLE; my_512_data <= 512'd0; wr_addr_cnt <= 26'd0; rd_addr_cnt <= 26'd0; app_addr_begin <= 30'd0; end endcase end end //********2.根据状态机与MIG指示信号为app信号赋值 assign app_en =((state == WRITE && (app_rdy && app_wdf_rdy))||(state == READ && app_rdy)) ? 1'b1:1'b0; assign app_cmd =(state == READ) ? 3'd1 :3'd0; assign app_wdf_wren=(state == WRITE && (app_rdy && app_wdf_rdy)) ? 1'b1:1'b0; assign app_wdf_end =app_wdf_wren; assign app_addr =app_addr_begin; assign app_wdf_data=my_512_data; //****************3.用户判错逻辑 reg [25:0] rd_cnt; wire rst_n; //复位,低有效 reg error_flag; parameter L_TIME = 28'd200_000_000; reg [27:0] led_cnt; //led计数 wire error; //读写错误标记 assign rst_n = ~ui_clk_sync_rst;//&&myrst always @(posedge ui_clk or negedge rst_n) begin if(~rst_n) rd_cnt <= 0; //若计数到读写长度,且读有效,地址计数器则�?0 else if(app_rd_data_valid&&(rd_cnt == TEST_LENGTH - 1)) rd_cnt <= 0; //其他条件只要读有效,每个时钟自增1 else if (app_rd_data_valid) rd_cnt <= rd_cnt + 1; end //判断错误,读出数据应为计数递增数据 assign error = (app_rd_data_valid && (rd_cnt!=app_rd_data)); always @(posedge ui_clk or negedge rst_n) begin if(~rst_n) led2<=0; else if(rd_cnt==32'd50000000) led2<=1; end always @(posedge ui_clk or negedge rst_n) begin if(~rst_n) error_flag <= 0; else if(error) error_flag <= 1; end //读写测试正确,指示灯led1常亮,反之则闪烁 always @(posedge ui_clk or negedge rst_n) begin if((~rst_n) || (~init_calib_complete )) begin led_cnt <= 28'd0; led1 <= 1'b0; end else begin if(~error_flag) led1 <= 1'b1; else begin led_cnt <= led_cnt + 28'd1; if(led_cnt == L_TIME - 1'b1) begin led_cnt <= 25'd0; led1 <= ~led1; end end end end
正常结果可以看到与上一节MIG IP给出的时序是一致的。
●写开始时序:
●写结束时序:
●读开始时序:
●读结束时序:
测试的结果最好通过读写一定量并且读写指示灯亮确认。当测试异常时,应按照DDR是否初始化成功、MIG app接口时序是否正确、MIG ip配置是否正确、状态机运行状态是否异常方面入手分析。DDR初始化不成功init_calib_complete为0可能的原因是,时钟输入与MIG IP配置不一致,MIG端口没有正确连接,DDR的UCF约束有问题。
如下原理图,可以清楚的看到,两颗DDR3的相同的控制线连在了一个控制器的相同引脚,而数据线是各自连的。在PCB布线的时候通常用fly_by的方式连接DDR3。对于FPGA而言,把外部的位宽为16bit的两颗DDR当成了一个位宽为32bit的DDR3来控制。这种往往是为了扩大容量;或者为了匹配rank位宽;或者为了提升带宽,将两片或者四片甚至更多片DDR3放在一起。对于这种场景,只需将上一篇IP配置第5步中的data width设置为实际芯片加起来的位宽即可。如两片16bit位宽颗粒相连,则设置位宽为32bit即可。其余的用户逻辑且把他当作一片位宽为32bit的DDR3即可。实际上,用户存入的32bit数据在实际存放时,高位16bit和低位16bit的数据被放到不同的两片DDR3中,唯一的关联就是,这两个位置的物理值是相等的而已。
END |
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。