关于Xilinx 2020.1新建工程时配置MIG核的完整步骤,请参阅:https://blog.csdn.net/ZLK1214/article/details/111349678

MIG核里面有两个通道:命令通道和数据通道。这两个通道是相互独立的,互不影响。
命令通道:要发送的命令由ddr3_app_cmd指定(0号命令是写内存,1号命令是读内存),ddr3_app_en拉高就开始发送命令。
数据通道:要发送的数据由ddr3_app_wdf_data指定,ddr3_app_wdf_wren拉高就开始往FIFO里面送入数据。
MIG里面是严格一个时钟(ddr3_ui_clk)发送一个命令(或写一个数据)。ddr3_app_en如果一直拉高,那么就一直重复发送命令。拉高了多少个时钟周期,就发送多少条命令。同样,ddr3_app_wdf_wren如果一直拉高,就一直重复往FIFO里面写入数据。编写程序的时候要特别注意这一点。

如下图所示是MIG连续写DDR3内存时的时序。ILA的采样周期刚好就是ddr3_ui_clk(100MHz),横向时间单位为10ns。
从红竖线(8192时刻)开始写内存,发送写命令(ddr3_app_cmd=0且ddr3_app_en=1),写的内存地址是ddr3_app_addr=0,同时往FIFO里面送入要写的数据(ddr3_app_wdf_wren=1,wdf的全称为write data FIFO)。第8193时刻,读到ddr3_app_rdy为1说明写命令发送成功,读到ddr3_app_wdf_rdy=1说明数据成功送入FIFO。这两位同时为1表示写内存成功。
第8193时刻又发出了写8号地址的写命令,往FIFO里面塞入数据0x0008。然而,第8194时刻读到ddr3_app_rdy为0,说明写命令发送失败了!!同时读到ddr3_app_wdf_rdy为1,说明往FIFO里面送入数据成功了。这个时候就要注意了,写命令发送失败但FIFO送入数据成功,接下来写命令应该重新发送,但数据不能再往FIFO里面送了!!!也就是图中红框的部分,在第8194时刻必须把ddr3_app_wdf_wren拉低,停止送入数据,不然的话,这种情况下就会连续往FIFO里面送入三个0x0008,导致后面的地址0x0000010(16号地址),0x0000018(24号地址)都被写入0x0008这个数据!!!
如果不懂的话,是非常容易犯这个错误的。
写8号地址的命令发了两次都失败了,第三次终于发送成功了,ddr3_app_rdy为1。在8196时刻可以发送新的写命令了,向FIFO里面写入新的数据。

连续读内存也是类似的。如下图所示是一种错误的做法。只要ddr3_app_en为1,就一直在发命令,ddr3_app_addr一直为0就发了一大串读0号地址的命令。

那改成发一个读命令,等到数据读出来后(ddr3_app_rd_data_valid=1),再发下一个命令,这样就能读出正确的结果,但是抓了波形图一看,中间的等待时间很长,每读一个地址都要等待很久,非常浪费时间。

于是我们可以边发命令,边接收数据。一开始就从0地址开始一直往后发命令:0,8,16,24,36,48……
当ddr3_app_rd_data_valid=1时,我们已经发到0xb0(176)地址了,这个时候0号地址的数据才读出来,于是一个一个接收,如下图所示。

这样连续读数据就会比刚才快很多很多。值得注意的是,接收完最后一个数据后,ddr3_app_rd_data_valid会变为0,表明数据已接收完毕。

我们来看看最终的代码:

这里程序有一个失误,就是ddr3_ui_clk_sync_rst是一个同步复位信号,不应该是异步复位。代码里面写成了always @(posedge ddr3_ui_clk, posedge ddr3_ui_clk_sync_rst) begin,把ddr3_ui_clk_sync_rst用成了一个异步的复位信号,这是不对的。应该把always敏感列表里面的posedge ddr3_ui_clk_sync_rst去掉,写成always @(posedge ddr3_ui_clk) begin才对。不过笔者在实际运行时并没有发现有什么不对的地方。

module main(input clock, // 50MHz外部晶振// DDR3引脚inout [15:0] ddr3_dq,inout [1:0] ddr3_dqs_n,inout [1:0] ddr3_dqs_p,output [13: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 ddr3_ck_p,output ddr3_ck_n,output ddr3_cke,output ddr3_cs_n,output [1:0] ddr3_dm,output ddr3_odt,input [3:0] keys,output [3:0] leds, // 4个LED灯output uart_tx // 串口发送引脚);parameter SYSCLK = 50000000;wire nrst;Reset #(SYSCLK) reset(clock, !keys[0], nrst);wire clock200;wire locked;clk_wiz_0 clk_wiz_0(.reset(!nrst),.clk_in1(clock), // 输入50MHz时钟.clk_out1(clock200), // 输出200MHz时钟.locked(locked) // 该信号表示输出时钟是否已稳定);reg [27:0] ddr3_app_addr;reg [2:0] ddr3_app_cmd;reg ddr3_app_en;reg [127:0] ddr3_app_wdf_data; // 因为burst=8, data_width=16, 所以wdf_data的宽度为8*16=128reg ddr3_app_wdf_end;reg ddr3_app_wdf_wren;wire [127:0] ddr3_app_rd_data; // 这个也是128位wire ddr3_app_rd_data_end;wire ddr3_app_rd_data_valid;wire ddr3_app_rdy;wire ddr3_app_wdf_rdy;wire ddr3_app_sr_active;wire ddr3_app_ref_ack;wire ddr3_app_zq_ack;wire ddr3_ui_clk;wire ddr3_ui_clk_sync_rst;wire ddr3_init_calib_complete;wire [11:0] ddr3_device_temp;mig_7series_0 mig_7series_0(.ddr3_dq(ddr3_dq),.ddr3_dqs_n(ddr3_dqs_n),.ddr3_dqs_p(ddr3_dqs_p),.ddr3_addr(ddr3_addr),.ddr3_ba(ddr3_ba),.ddr3_ras_n(ddr3_ras_n),.ddr3_cas_n(ddr3_cas_n),.ddr3_we_n(ddr3_we_n),.ddr3_reset_n(ddr3_reset_n),.ddr3_ck_p(ddr3_ck_p), // DDR3内存时钟输出: 400MHz.ddr3_ck_n(ddr3_ck_n),.ddr3_cke(ddr3_cke),.ddr3_cs_n(ddr3_cs_n),.ddr3_dm(ddr3_dm),.ddr3_odt(ddr3_odt),.sys_clk_i(clock200), // 系统时钟输入: 200MHz.clk_ref_i(clock200), // 参考时钟输入: 200MHz.app_addr(ddr3_app_addr),.app_cmd(ddr3_app_cmd),.app_en(ddr3_app_en),.app_wdf_data(ddr3_app_wdf_data),.app_wdf_end(ddr3_app_wdf_end),.app_wdf_mask(16'h0), // 8突发*每个数据2字节=16字节, 所以mask有16位.app_wdf_wren(ddr3_app_wdf_wren),.app_rd_data(ddr3_app_rd_data),.app_rd_data_end(ddr3_app_rd_data_end),.app_rd_data_valid(ddr3_app_rd_data_valid),.app_rdy(ddr3_app_rdy),.app_wdf_rdy(ddr3_app_wdf_rdy),.app_sr_req(1'b0),.app_ref_req(1'b0),.app_zq_req(1'b0),.app_sr_active(ddr3_app_sr_active),.app_ref_ack(ddr3_app_ref_ack),.app_zq_ack(ddr3_app_zq_ack),.ui_clk(ddr3_ui_clk), // 用户时钟输出: 因为选的是4:1, 所以ddr3_ck_p:ddr3_ui_clk=4:1, ddr3_ui_clk是100MHz.ui_clk_sync_rst(ddr3_ui_clk_sync_rst), // 用户程序复位输出.init_calib_complete(ddr3_init_calib_complete),.device_temp(ddr3_device_temp),.sys_rst(locked) // 复位输入: 当倍频器时钟未稳定时, 使MIG处于复位状态);// FPGA内部的分布式RAM// Depth: 112, Data Width: 128, Memory Type: Single Port RAMreg [6:0] dist_mem_a;reg [127:0] dist_mem_d;reg dist_mem_we; // 写使能wire [127:0] dist_mem_spo;dist_mem_gen_0 dist_mem_gen_0(.clk(ddr3_ui_clk),.a(dist_mem_a),.d(dist_mem_d),.spo(dist_mem_spo),.we(dist_mem_we));// 串口单字节发送wire uart_tx_request;wire [7:0] uart_tx_data;wire uart_tx_ready;wire uart_sent;UARTTransmitter #(SYSCLK, 115200) uart_transmitter(clock, locked, uart_tx, uart_tx_request, uart_tx_data, uart_tx_ready, uart_sent);// 串口多字节组合发送reg uart_bytearray_tx_mode;reg [159:0] uart_bytearray_tx_data;reg uart_bytearray_tx_request;reg [7:0] uart_bytearray_tx_size;wire uart_bytearray_tx_ready;wire uart_bytearray_sent;ByteArrayTransmitter #(20) uart_bytearray_transmitter(clock, locked, uart_tx_request, uart_tx_data, uart_tx_ready, uart_sent, uart_bytearray_tx_mode, uart_bytearray_tx_request, uart_bytearray_tx_data, uart_bytearray_tx_size, uart_bytearray_tx_ready, uart_bytearray_sent);reg [3:0] i;reg uart_state;assign leds = i;always @(posedge ddr3_ui_clk, posedge ddr3_ui_clk_sync_rst) beginif (ddr3_ui_clk_sync_rst) beginddr3_app_en <= 0;ddr3_app_wdf_wren <= 0;uart_bytearray_tx_request <= 0;i <= 0;uart_state <= 0;endelse if (!uart_state) beginif (uart_bytearray_tx_ready) begin// 串口数据未开始发送if (!uart_bytearray_tx_request) begin// 串口空闲case (i)0, 1: begin// 写数据测试if (ddr3_app_rdy && ddr3_app_wdf_rdy) begin// 送入新数据if (i == 0) begin// [第1批数据]// MIG配置选择了4:1关系, 所以wdf_end信号应该一直为高// 且wdf_data的宽度为16字节 (一次性写8个地址)ddr3_app_cmd <= 0; // WRITEddr3_app_addr <= 0; // 写的地址ddr3_app_en <= 1; // 写命令使能ddr3_app_wdf_data <= 128'h01234567_89abcdef_5555aaaa_bbbb0000; // 写的数据ddr3_app_wdf_wren <= 1; // 数据送入FIFO使能ddr3_app_wdf_end <= 1; // 是本突发最后一组数据// 这是因为, ddr3_ui_clk的频率是100MHz, ddr3_ck_p的频率是400MHz, DDR内存是上升沿和下降沿都要写数据// 一个ddr3_ui_clk周期内, ddr3_ck_p的上升沿和下降沿一共出现了8次// 每出现一次, 就要往ddr3_dq[15:0]上送入2个字节的数据// 因此, 每个ddr3_ui_clk时钟周期, 刚好要写8*2=16个字节的数据i <= 1;endelse if (ddr3_app_addr < 100 * 8) begin// [第2~101批数据]ddr3_app_addr <= ddr3_app_addr + 8;ddr3_app_en <= 1;ddr3_app_wdf_data[127:16] <= 112'h76543210_fedcba98_bbbb6666_cccc;ddr3_app_wdf_data[15:0] <= ddr3_app_addr[15:0] + 8;ddr3_app_wdf_wren <= 1;endelse begin// 没有数据要写了, 写数据结束ddr3_app_en <= 0;ddr3_app_wdf_wren <= 0;ddr3_app_wdf_end <= 0;i <= 2;endendelse if (!ddr3_app_rdy && ddr3_app_wdf_rdy) begin// ddr3_app_rdy=0, 是写命令没有发送成功// ddr3_app_wdf_rdy=1, 是数据已成功送入FIFO// 数据已送入FIFO, 但写命令发送失败, 必须撤销数据送入FIFO的请求, 保留写命令请求// 不能再往FIFO里面送入数据了, 否则多送入的数据会被写入到后续的地址上ddr3_app_wdf_wren <= 0;endelse if (ddr3_app_rdy && !ddr3_app_wdf_rdy)ddr3_app_en <= 0; // 写命令发送成功了, 但数据没有成功送入FIFO, 则需要撤销写命令, 保留数据送入FIFO的请求end2, 3: begin// 读数据测试// 边发读命令, 边接收数据, 只要ddr3_app_rd_data_valid=1就可以接收数据, 这样读起来才快// 而不是发一个命令, 收到数据后再发下一个命令if (ddr3_app_rdy) beginif (i == 2) begin// 发送第1条读命令ddr3_app_addr <= 0;ddr3_app_cmd <= 1; // READddr3_app_en <= 1;i <= 3;endelse if (ddr3_app_addr < 100 * 8)ddr3_app_addr <= ddr3_app_addr + 8; // 发送第2~101条读命令elseddr3_app_en <= 0; // 读命令发送结束// addr=0*8是第1条命令// addr=1*8是第2条命令// addr=100*8是第101条命令endif (ddr3_app_rd_data_valid) begin// 数据读出来了, 将读到的16字节数据暂存到分布式RAMif (!dist_mem_we)dist_mem_a <= 0; // 第1个数据else if (dist_mem_a < 100) begindist_mem_a <= dist_mem_a + 1'b1; // 第2~101个数据if (dist_mem_a + 1'b1 == 100) // dist_mem_a的现态+1(即dist_mem_a的次态)==100i <= 4; // 最后一个数据读完, 进入串口发送模式enddist_mem_d <= ddr3_app_rd_data;dist_mem_we <= 1;endend4: begin// 将分布式RAM中暂存的数据通过串口发送出来// 注意: 把地址赋给dist_mem_a后, 要下一个时钟周期才能从dist_mem_spo取出数据if (dist_mem_we) begindist_mem_we <= 0;dist_mem_a <= 0;endelse if (dist_mem_a <= 100) beginuart_bytearray_tx_request <= 1;uart_bytearray_tx_mode <= 0;uart_bytearray_tx_data <= {dist_mem_a, 3'b0, dist_mem_spo};uart_bytearray_tx_size <= 20;dist_mem_a <= dist_mem_a + 1'b1;endelsei <= 5;endendcaseendendelseuart_state <= 1; // 串口数据已开始发送endelse beginif (uart_bytearray_tx_ready && uart_bytearray_tx_request) begin// 串口数据已发送uart_bytearray_tx_request <= 0; // 关闭发送请求uart_state <= 0;endendendila_0 ila_0(.clk(ddr3_ui_clk),.probe0(ddr3_ui_clk_sync_rst),.probe1(ddr3_app_addr), // [27:0].probe2(ddr3_app_cmd), // [2:0].probe3(ddr3_app_en),.probe4(ddr3_app_wdf_data[15:0]), // [15:0].probe5(ddr3_app_wdf_end),.probe6(ddr3_app_wdf_wren),.probe7(ddr3_app_rd_data[15:0]), // [15:0].probe8(ddr3_app_rd_data_end),.probe9(ddr3_app_rd_data_valid),.probe10(ddr3_app_rdy),.probe11(ddr3_app_wdf_rdy),.probe12(ddr3_app_sr_active),.probe13(ddr3_app_ref_ack),.probe14(ddr3_app_zq_ack),.probe15(ddr3_init_calib_complete),.probe16(ddr3_device_temp) // [11:0]);endmodule

我们选用的DDR3内存型号为MT41K128M16,内存容量为256MB。FPGA是米联客XC7A35TFGG484-2的开发板,开发板上接的晶振是50MHz,由Clock Wizard倍频到200MHz后(clock200)用作MIG的系统时钟(sys_clk_i)和参考时钟(clk_ref_i),由MIG再次倍频到400MHz后(ddr3_ck_p和ddr3_ck_n)驱动DDR3内存,分频到100MHz后(ddr3_ui_clk)拿给程序使用,ddr3_ui_clk_sync_rst用作程序的复位信号。ILA抓取信号也是用的ddr3_ui_clk这个时钟。
内存的位宽为16位,一次读写2个字节,时钟的上升沿和下降沿都要读写数据。100MHz的每个时钟周期,DDR3内存的400MHz时钟共出现了4对上升/下降沿,写了8次数据,每次2个字节,所以每个100MHz的时钟周期(10ns),要写16字节。
每10ns写16字节的话,1μs要写1600字节,1ms要写1600000字节,1秒钟要写1600000000字节。也就是说,理论上这片内存的读写速率是1.49GB/s。
ddr3_app_wdf_data和ddr3_app_rd_data都是128位(16字节)。ILA抓信号的时候,为了节约BRAM,只抓data的末16位。
一次性要写8个地址,MIG核配置的突发数也是8,所以,写内存的时候ddr3_app_wdf_end一直为高,每次都是写的突发的最后一组数据。

程序运行后,串口的输出结果如下:

串口输出的是读出来的内存数据,可以看到DDR3内存读写是完全正确的。

引脚配置文件pins.xdc内容如下:

set_property PACKAGE_PIN V4 [get_ports clock]
set_property IOSTANDARD LVCMOS15 [get_ports clock]
set_property PACKAGE_PIN R17 [get_ports uart_tx]
set_property IOSTANDARD LVCMOS33 [get_ports uart_tx]
set_property PACKAGE_PIN D22 [get_ports {leds[3]}]
set_property PACKAGE_PIN E22 [get_ports {leds[2]}]
set_property PACKAGE_PIN D21 [get_ports {leds[1]}]
set_property PACKAGE_PIN E21 [get_ports {leds[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {leds[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {leds[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {leds[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {leds[0]}]set_property PACKAGE_PIN R14 [get_ports {keys[3]}]
set_property PACKAGE_PIN P14 [get_ports {keys[2]}]
set_property PACKAGE_PIN N14 [get_ports {keys[1]}]
set_property PACKAGE_PIN N13 [get_ports {keys[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {keys[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {keys[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {keys[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {keys[0]}]

DDR3内存的引脚已经在MIG的GUI配置界面配置了,所以我们不需要再在pins.xdc里面配置了。
配置DDR3内存引脚是单独一个xdc文件,是在MIG核的文件夹里面:sources_1\ip\mig_7series_0\mig_7series_0\user_design\constraints\mig_7series_0.xdc

Vivado工程下载地址:
(自己新建的,没有用MIG核的example工程)
https://pan.baidu.com/s/1KrmD7qbhHhRX7BFalfsn-A(提取码:b9x9)

实际上,写内存也可以边发命令边发数据。write data FIFO(WDF)里面最多可以放16个数据。用16个时钟周期把16个数据放进去,直到ddr3_app_wdf_rdy为0就说明把FIFO占满了,然后再来慢慢地发写命令,就可以达到最快的写速度。

MIG时序要求的是数据可以到的比写命令早,也可以滞后,但最晚只能比写命令晚一个周期。

接下来,我们可以用FPGA将DDR3模拟成一个W25Q256存储器,用杜邦线和STM32F103C8小开发板连接起来,做一个U盘,读写DDR3整个256MB的空间,进行全片测试:
https://blog.csdn.net/ZLK1214/article/details/111416038

Xilinx MIG核读写DDR3内存,连续读写内存的正确方法(时序)及代码相关推荐

  1. DDR2 MIG核与DDR3 MIG核使用区别

    本文中所讲DDR2 MIG核是V5芯片的MIG核,DDR3 MIG核是K7芯片的MIG核. 一.建核区别 DDR2 MIg核的建立过程与DDR3 Mig核的建立过程基本内容都是一致.具体建核教程在相应 ...

  2. Xilinx 2020.1 MIG核读写DDR3内存,新建工程时配置MIG核的完整步骤

    本文以XC7A35TFGG484-2这款芯片为例,采用米联客FPGA开发板,用MIG核驱动DDR3内存.FPGA外接的晶振大小为50MHz,DDR3内存的驱动频率(ddr3_ck_p和ddr3_ck_ ...

  3. 快速上手Xilinx DDR3 IP核(2)----MIG IP核的官方例程与读写测试模块(Native接口)

    写在前面 接上一篇文章(配置MIG IP过程): 快速上手Xilinx DDR3 IP核(1)----MIG IP核的介绍及配置(Native接口) DDR3系列文章: 快速上手Xilinx DDR3 ...

  4. Xilinx 7系列例化MIG IP core DDR3读写

    DDR3读写在工程上多是通过例化MIG,调用生成IPcore的HDL FunctionalModel.DDR读写数据可以用到状态机,后期再添砖加瓦吧,当下先对比一下网上找的一段程序和自己例化后的程序. ...

  5. 基于Vivado MIG IP核的DDR3读写实验(top_rom_ddr/ddr_top)

    一.前言 关于Vivado MIG IP核详细配置可以参考我之前的文章:基于Vivado MIG IP核的DDR3控制器(DDR3_CONTROL) 关于MIG IP核的用户端的接口时序可以参考这篇文 ...

  6. XIlinx MIG 控制DDR3 SO-DIMM内存条(二):MIG IP核学习

    目录 1 简介 2 IP核自定义 2.1 设置IP核参数 2.1.1 Pin Compatible FPGAs 2.1.2 Memory Selection 2.1.3 Controller Opti ...

  7. 使用VIVADO中的MIG控制DDR3(AXI接口)四——MIG配置及DDR3读写测试

    在之前的内容里,讲述了AXI和DDR3的基本知识,也做了一个用AXI IP核读写BRAM的测试实验.接下来,我们就将这些部分结合在一起,做一个用AXI IP核对DDR3进行读写测试的实验.因为DDR3 ...

  8. 基于MIG控制器的DDR3读写控制详解

    基于MIG控制器的DDR3读写控制详解 目的:详细介绍FPGA中基于MIG IP核控制的DDR3详细控制及内部逻辑 平台:AX7350-Xilinx 软件:Vivado 2017.4 1.MIG IP ...

  9. 基于FPGA的DDR3多端口读写存储管理系统设计

    机载视频图形显示系统主要实现2D图形的绘制,构成各种飞行参数画面,同时叠加实时的外景视频.由于FPGA具有强大逻辑资源.丰富IP核等优点,基于 FPGA的嵌入式系统架构是机载视频图形显示系统理想的架构 ...

最新文章

  1. 【iCore3 双核心板】例程二十一:LAN_TCPS实验——以太网数据传输
  2. CXF发布restful WebService的入门例子(服务器端)
  3. GitHub开源新命令行工具:在终端里创建、管理PR成现实
  4. unicode环境下用CFile读取txt的若干疑惑,该如何处理
  5. java 拉姆表达式_Java8 lambda表达式10个示例
  6. AMD规范:简单而优雅的动态载入JavaScript代码
  7. matlab将数扩大为整数,MATLAB如何完成大整数运算问题?
  8. STL9-vector容器
  9. react native 0.50 源码解析 再出发 持续更新
  10. thinkphp框架环境部署
  11. JSK-23223 数字反转【进制】
  12. 各种语言的模块导入导出形式
  13. 再谈指标体系建设的3点建议
  14. 你真的要收下这份大礼包!!
  15. C++简介(5)STL
  16. 半桥驱动器芯片 TPS28225 中文资料
  17. 网站图片如何批量下载教程
  18. 两个60后大叔的新能源战争:王传福与曾毓群的万亿赌局
  19. 下三角形行列式证明推导
  20. GSMA公布MWC20巴塞罗那最新进展

热门文章

  1. GEO数据库中搜索数据
  2. 词典api,根据成语查询详细信息
  3. 计算机专业课改理念,课改新理念
  4. CCNA与CCNP的路该如何走?
  5. 环境问题:fatal error LNK1318: 非意外的 PDB 错误
  6. 关于计算机语言的知识正确的是,2019微软认证考试精选模拟题及答案(1.17)
  7. JS判断安卓端或者苹果端并下载
  8. 【拓扑 字符串还原 + 线段树维护】奇洛金卡达(father)
  9. WebSecurity
  10. Sony S1512S2C加装内存