赞
踩
不论是DDR3颗粒还是DDR3内存条,xilinx都是通过MIG IP核实现FPGA与DDR的读写。本文区别于DDR颗粒,记录几个与颗粒配置不同的地方。关于DDR的原理与MIG IP的简介,请查看前面文章,链接在文末。本文提供了配套的工程源码,链接在文末,本文用的内存条为MT16KTF1G64HZ-1G6,FPGA芯片为xc7k325tffg900 -2。请按照顺序循序渐进阅读本系列的文章。
如果需要修改这段程序,需要注意MIG的接口各信号的位宽应该保持一致,另外程序中设计了两个LED灯,读写测试正确的时候,指示灯led1常亮,反之则闪烁。LED2只是容量,当测试到所设置的容量的时候常量。代码中TEST_LENGTH指示的含义是突发次数,也可以说是容量。每一次突发是512bit数据,使用内存的容量除以512bit,即为最大的突发次数。当然使用的芯片的物理位宽不同,例如只有一片16bit位宽的DDR颗粒,那一次突发的数据为8*16bit=128bit,那最大的突发次数就要用量除以128bit了。
module dimm_top( input sys_clk_p, input sys_clk_n, inout [63:0] ddr3_dq , inout [7:0] ddr3_dqs_n , inout [7:0] ddr3_dqs_p , output [15:0] ddr3_addr , output [2:0] ddr3_ba , output ddr3_ras_n , output ddr3_cas_n , output ddr3_we_n , output ddr3_reset_n , output [1:0] ddr3_ck_p , output [1:0] ddr3_ck_n , output [1:0] ddr3_cke , output [1:0] ddr3_cs_n , output [7:0] ddr3_dm , output [1:0] ddr3_odt , output reg led1, output reg led2 ); wire clk_rst; wire clk_200; reg [29:0] app_addr_begin=0; wire app_en; //写命令使能 wire [2:0] app_cmd; //用户读写命令 wire app_wdf_wren; //DDR3写使能 wire app_wdf_end; //突发写最后一个数标识 wire [29:0] app_addr; //用户平面地址 wire app_rdy; //设备接收准备就绪 wire app_wdf_rdy; //写响应 wire [511:0] app_rd_data; //用户读数据 wire app_rd_data_end; //突发读当前时钟最后一个数据 wire app_rd_data_valid; //读数据有效 wire [511:0] app_wdf_data; //用户写数据 wire app_sr_active; //保留 wire app_ref_ack; //刷新请求 wire app_zq_ack; //ZQ 校准请求 wire init_calib_complete; //校准完成信号 wire ui_clk ; //用户时钟 wire ui_clk_sync_rst; clk_wiz_0 u_clk_wiz_0(.clk_out1(clk_200), .reset(1'b0), .locked(clk_rst), .clk_in1_p(sys_clk_p),.clk_in1_n(sys_clk_n)); mig_7series_0 mig_JC ( // Memory interface ports .ddr3_addr (ddr3_addr), // output [15:0] .ddr3_ba (ddr3_ba), // output [2:0] .ddr3_cas_n (ddr3_cas_n), // output .ddr3_ck_n (ddr3_ck_n), // output [1:0] .ddr3_ck_p (ddr3_ck_p), // output [1:0] .ddr3_cke (ddr3_cke), // output [1:0] .ddr3_ras_n (ddr3_ras_n), // output .ddr3_reset_n (ddr3_reset_n), // output .ddr3_we_n (ddr3_we_n), // output .ddr3_dq (ddr3_dq), // inout [63:0] .ddr3_dqs_n (ddr3_dqs_n), // inout [7:0] .ddr3_dqs_p (ddr3_dqs_p), // inout [7:0] .init_calib_complete (init_calib_complete), // output .ddr3_cs_n (ddr3_cs_n), // output [1:0] .ddr3_dm (ddr3_dm), // output [7:0] .ddr3_odt (ddr3_odt), // output [1:0] // Application interface ports .app_addr (app_addr), // input [29:0] .app_cmd (app_cmd), // input [2:0] .app_en (app_en), // input .app_wdf_data (app_wdf_data), // input [511:0] .app_wdf_end (app_wdf_end), // input .app_wdf_wren (app_wdf_wren), // input .app_rd_data (app_rd_data), // output [511:0] .app_rd_data_end (app_rd_data_end), // output .app_rd_data_valid (app_rd_data_valid), // output .app_rdy (app_rdy), // output .app_wdf_rdy (app_wdf_rdy), // output .app_sr_req (1'b0), // input .app_ref_req (1'b0), // input .app_zq_req (1'b0), // input .app_sr_active (app_sr_active), // output .app_ref_ack (app_ref_ack), // output .app_zq_ack (app_zq_ack), // output .ui_clk (ui_clk), // output用户时钟输出,其实是通过IP配置自己配出来的 .ui_clk_sync_rst (ui_clk_sync_rst), // output .app_wdf_mask (64'b0), // input [63:0] //写数据屏蔽 .sys_clk_i (clk_200),//输入IP的时钟 // Reference Clock Ports .clk_ref_i (clk_200),//参考时钟 .sys_rst (clk_rst) // input sys_rst ); parameter TEST_LENGTH = 27'd134200000; //每一次突发是512bit 8GB可以支持134217728次突发 99.98% // 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 [26:0] wr_addr_cnt; reg [26: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 <= 27'd0; rd_addr_cnt <= 27'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 <= 27'd0; rd_addr_cnt <= 27'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 <= 27'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 <= 27'd0; rd_addr_cnt <= 27'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 [26: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'd134200000-1) 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 endmodule
DDR在FPGA系统中的作用主要是作为存储器使用,用于存储数据和程序。DDR存储器通常被用作FPGA系统中的主存储器,用于存储采集数据和中间结果。DDR3作为高速缓存与FPGA相连,在不同领域均发挥着重要作用。在高性能计算领域,DDR用于存储大规模数据集、模型参数、数据计算结果,从而充分发挥FPGA并行计算的能力,完成计算任务;在图像处理领域,用于匹配图像采集接口与传输接口之间的速度,完成图像采集;在通信领域,DDR用于存储大量数据包,实现数据的缓存和处理,提高数据传输速度和处理效率。总之,DDR在不同领域扮演着重要角色。以采集摄像头数据为例,采用乒乓操作的思想是在DDR中开辟两块大小为1帧图像的缓冲区,如果读取速度大于写入速度的时候,需要采用乒乓操作的方式发挥DDR弹性缓冲的作用。往缓冲区写的时候,1号缓冲区写满之后,切换到2号缓冲区写,2号写满之后,在往1号去写,如此往复 。由于读取速度大于写入速度,因此,读一定是在与当前写不同的另一块缓冲区去读,在底层,可能会对同一块缓冲区的数据读取很多次,但是这并不影响人在视觉上对于画面流畅的影响。这就是通过乒乓操作实现了数据缓冲,匹配了读写两端的速度。如下图所示,
说明:1.每一个bank存储一帧图像数据,bank中每一行为图像的一行数据(即每一次读写的突发长度是一行像素数据,这个突发长度可以自己定义,并不必须要是一行数据),读和写彼此独立进行;2.bank之间的切换由状态机实现,由于读速度大于写速度,则每写完一个bank切换另一个bank去写;每读完一个bank,判断当前写bank,选择不同于写bank的bank进行读。3.由于读速度大于写速度,因此永远不会发生冲突,只是可能某一帧会被重复播放,但在视频应用中,这对用户的视觉不产生任何影响。同理,如果是读的速度小于写的速度,那让写操作刷新缓冲区即选择与读相排斥的缓冲区写,让读操作按照顺序读即可。 上面描述的只是一种乒乓缓冲的思想,实际操作中,可以通过设置三缓存,四缓存的方式让图像更为平滑。另外可以考虑基于这种思想为DDR写一个消息队列,让DDR仲裁控制器的通用性和适配性更强。
END |
本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。