Xilinx MIG IP之APP接口仿真
1 MIG APP接口简介
MIG(Memory Interface Generators) IP 是Xilinx提供给7系列及以上的 DDR 读写控制器。MIG有AXI接口版本以及app接口的版本,其中app的接口系统框图如下图所示:
框图右边是DDR的物理接口信号,左边是用户接口信号,其中用户接口描述如下:
信号 | I/O | 描述 |
ui_clk | O | MIG输出的用户时钟,必须是DDR3 IO时钟的1/2或者1/4 |
ui_clk_sync_rst | O | 用户时钟复位信号,高电平有效 |
init_calib_complete | O | DDR初始化完成信号,高电平有效 |
app_addr[ADDR_WIDTH – 1:0] | I | 用户地址输入,地址的位宽ADDR_WIDTH等于RANK位宽+BANK位宽+ROW位宽+COL位宽 |
app_en | I | IP核命令写入使能,高电平有效。写命令时需要拉高该信号 |
app_cmd[2:0] | I | IP核读写控制命令信号;读:001;写:000。其他值保留 |
app_rdy | O | IP核准备接收读写命令,高电平有效。 |
app_wdf_wren | I | MIG IP核数据写使能,高电平有效 |
app_wdf_mask[APP_MASK_WIDTH – 1:0] | I | 数据字节屏蔽信号,置1指示对应字节写入无效 |
app_wdf_rdy | O | IP核准备接收数据信号,高电平有效。 |
app_wdf_end | I | 指示当前写入下是最后一个数据,高电平有效。 |
app_wdf_data [APP_DATA_WIDTH-1:0] | I | DDR写数据 |
app_rd_data[APP_DATA_WIDTH – 1:0] | O | DDR读数据 |
app_rd_data_valid | O | 读数据有效信号,高电平有效 |
app_rd_data_end | O | 指示当前读出的数据是最后一个数据,高电平有效 |
app_sz | I | 保留信号,置0 |
app_sr_req | I | 保留信号,置0 |
app_sr_active | O | 保留信号 |
app_ref_req | I | 刷新请求信号 |
app_ref_ack | O | 刷新请求响应信号 |
app_zq_req | O | 请求校准阻抗匹配信号 |
2 APP接口操作时序
2.1命令操作时序
2.2写数据操作时序
2.3读数据操作时序
3 MIG仿真
配置好MIG IP后,可以在vivado直接打开MIG IP的example 例程,该例程是完整的示例的工程,有完整的仿真的工程。xilinx提供的仿真工程比较完整,但同时代码量也很大,所以本次自己建立testbench进行仿真。
回看最开始的接口框图,写出一个能进行仿真的testbench是不难的,只需要对app接口进行操作即可。问题在于如何写出框图右端的物理接口,其实这个也不用自己来做,xilinx提供了一个ddr仿真模型(该模型文件在IP生成目录下的example design/sim目录下),只需要调用该模型与MIG IP的物理接口对接接口。
仿真的流程是先对一段地址空间写入数据,然后再读出该地址空间的数据进行比较,若比较错误,则将err_flag信号置1,并且重复此流程。
代码如下:

`timescale 1ps / 1ps `define clk_period 9900 module ddr3_app_test_tb(); wire [15:0] ddr3_dq ; wire [1:0] ddr3_dqs_n ; wire [1:0] ddr3_dqs_p ; wire [14:0] ddr3_addr ; wire [2:0] ddr3_ba ; wire ddr3_ras_n ; wire ddr3_cas_n ; wire ddr3_we_n ; wire ddr3_reset_n ; wire ddr3_ck_p ; wire ddr3_ck_n ; wire ddr3_cke ; wire ddr3_cs_n ; wire [1:0] ddr3_dm ; wire ddr3_odt ; reg sys_clk_p ; reg sys_clk_n ; // differential iodelayctrl clk (reference clock) reg clk_ref_p ; reg clk_ref_n ; // user interface signals reg [28:0] app_addr ; reg [2:0] app_cmd ; reg app_en ; reg [127:0] app_wdf_data ; reg app_wdf_end ; reg [15:0] app_wdf_mask = 32'h0000 ; reg app_wdf_wren ; wire [127:0] app_rd_data ; wire app_rd_data_end ; wire app_rd_data_valid ; wire app_rdy ; wire app_wdf_rdy ; reg app_sr_req = 0 ; reg app_ref_req = 0 ; reg app_zq_req = 0 ; wire app_sr_active ; wire app_ref_ack ; wire app_zq_ack ; wire ui_clk ; wire ui_clk_sync_rst ; wire init_calib_complete; wire [11:0] device_temp ; reg sys_rst ; reg ddr_init_done0 ; reg ddr_init_done1 ; wire ddr_init_done_pos ; reg [11:0] gap_cnt ; reg [127:0] rd_data_cnt ; reg rd_data_err ; parameter REFCLK_FREQ = 200.0; localparam real REFCLK_PERIOD = (1000000.0/(2*REFCLK_FREQ));//200Mhz used to ddr_ip iodelayctrl initial sys_clk_p = 1; always #(`clk_period/2) sys_clk_p = ~sys_clk_p; initial sys_clk_n = 0; always #(`clk_period/2) sys_clk_n = ~sys_clk_n; initial clk_ref_p = 1; always #REFCLK_PERIOD clk_ref_p = ~clk_ref_p; initial clk_ref_n = 0; always #REFCLK_PERIOD clk_ref_n = ~clk_ref_n; initial begin sys_rst = 0; #1000001;//ps sys_rst = 1; #10000000000; $display("Simulation Time Overflow %0t", $time); $stop; end always @(posedge ui_clk or posedge ui_clk_sync_rst) begin if(ui_clk_sync_rst) gap_cnt <= 'd0; else if(init_calib_complete) gap_cnt <= gap_cnt + 1'b1; end always @(posedge ui_clk or posedge ui_clk_sync_rst) begin if(ui_clk_sync_rst) begin ddr_init_done0 <= 1'b0; ddr_init_done1 <= 1'b0; end else begin ddr_init_done0 <= init_calib_complete; ddr_init_done1 <= ddr_init_done0; end end assign ddr_init_done_pos = ddr_init_done0 && ~ddr_init_done1; always @(posedge ui_clk or posedge ui_clk_sync_rst) begin if(ui_clk_sync_rst) begin app_en <= 'd0; app_cmd <= 'd0; app_addr <= 'd0; end else if(app_en && ~app_rdy) begin app_en <= app_en ; app_cmd <= app_cmd ; app_addr <= app_addr; end else if(ddr_init_done0 && gap_cnt > 0 && gap_cnt <= 1000) //write if(gap_cnt == 1) begin app_en <= 1'b1; app_cmd <= 3'b000; //write app_addr <= 'd0; end else if(app_wdf_end && app_addr <= 400) begin app_en <= 1'b1; app_cmd <= 3'b000; //write app_addr <= app_addr + 8; end else begin app_en <= 1'b0; app_cmd <= app_cmd ; app_addr <= app_addr; end else if(ddr_init_done0 && gap_cnt > 2000 && gap_cnt <= 3000) //read if(gap_cnt == 2001) begin app_en <= 1'b1; app_cmd <= 3'b001; //read app_addr <= 'd0; end else if(gap_cnt[3:0] == 15 && app_addr <= 400) begin app_en <= 1'b1; app_cmd <= 3'b001; //read app_addr <= app_addr + 8; end else begin app_en <= 1'b0; app_cmd <= app_cmd ; app_addr <= app_addr; end else begin app_en <= 1'b0; app_cmd <= app_cmd ; app_addr <= app_addr; end end always @(posedge ui_clk or posedge ui_clk_sync_rst) begin if(ui_clk_sync_rst) begin app_wdf_wren <= 'd0; app_wdf_end <= 'd0; app_wdf_data <= 'd0; end else if(app_wdf_wren && ~app_wdf_rdy) begin app_wdf_wren <= app_wdf_wren; app_wdf_end <= app_wdf_end ; app_wdf_data <= app_wdf_data; end else if(app_en && app_rdy && app_cmd == 3'b000) begin app_wdf_wren <= 1'b1; app_wdf_end <= 1'b1; app_wdf_data <= app_wdf_data + 1; end else begin app_wdf_wren <= 1'b0; app_wdf_end <= 1'b0; app_wdf_data <= app_wdf_data; end end always @(posedge ui_clk or posedge ui_clk_sync_rst) begin if(ui_clk_sync_rst) rd_data_cnt <= 'd1; else if(app_rd_data_valid) rd_data_cnt <= rd_data_cnt + 1'b1; end always @(posedge ui_clk or posedge ui_clk_sync_rst) begin if(ui_clk_sync_rst) rd_data_err <= 1'b0; else if(app_rd_data_valid && rd_data_cnt != app_rd_data) // data compare err --> err_flag lock high rd_data_err <= 1'b1; end ddr3_ip_core u_ddr3_ip_core ( // Memory interface ports .ddr3_addr (ddr3_addr ), .ddr3_ba (ddr3_ba ), .ddr3_cas_n (ddr3_cas_n ), .ddr3_ck_n (ddr3_ck_n ), .ddr3_ck_p (ddr3_ck_p ), .ddr3_cke (ddr3_cke ), .ddr3_ras_n (ddr3_ras_n ), .ddr3_reset_n (ddr3_reset_n ), .ddr3_we_n (ddr3_we_n ), .ddr3_dq (ddr3_dq ), .ddr3_dqs_n (ddr3_dqs_n ), .ddr3_dqs_p (ddr3_dqs_p ), .ddr3_cs_n (ddr3_cs_n ), .ddr3_dm (ddr3_dm ), .ddr3_odt (ddr3_odt ), // Application interface ports .app_addr (app_addr ), .app_cmd (app_cmd ), .app_en (app_en ), .app_wdf_data (app_wdf_data ), .app_wdf_end (app_wdf_end ), .app_wdf_wren (app_wdf_wren ), .app_rd_data (app_rd_data ), .app_rd_data_end (app_rd_data_end ), .app_rd_data_valid (app_rd_data_valid ), .app_rdy (app_rdy ), .app_wdf_rdy (app_wdf_rdy ), .app_sr_req (app_sr_req ), .app_ref_req (app_ref_req ), .app_zq_req (app_zq_req ), .app_sr_active (app_sr_active ), .app_ref_ack (app_ref_ack ), .app_zq_ack (app_zq_ack ), .ui_clk (ui_clk ), .ui_clk_sync_rst (ui_clk_sync_rst ), .app_wdf_mask (app_wdf_mask ), // System Clock Ports .sys_clk_p (sys_clk_p ), .sys_clk_n (sys_clk_n ), // Reference Clock Ports .clk_ref_p (clk_ref_p ), .clk_ref_n (clk_ref_n ), .init_calib_complete (init_calib_complete ), .device_temp (device_temp ), .sys_rst (sys_rst ) ); ddr3_model u_ddr3_model( .rst_n (ddr3_reset_n ), .ck (ddr3_ck_p ), .ck_n (ddr3_ck_n ), .cke (ddr3_cke ), .cs_n (ddr3_cs_n ), .ras_n (ddr3_ras_n ), .cas_n (ddr3_cas_n ), .we_n (ddr3_we_n ), .dm_tdqs (ddr3_dm ), .ba (ddr3_ba ), .addr (ddr3_addr ), .dq (ddr3_dq ), .dqs (ddr3_dqs_p ), .dqs_n (ddr3_dqs_n ), .tdqs_n ( ), .odt (ddr3_odt ) ); endmodule
仿真结果:(init_calib_complete信号大概在136us左右置1)
由仿真结果可知,rd_data_err信号一直为0,说明仿真写入与读出数据正确。
过程记录:
1、第一次由于在MIG配置中选择的DDR型号是MT41J256m16XX-125,数据位宽选择为32,也就是两块DDR3共用一组控制线,然而仿真Testbench只例化了一个ddr3_module,所以init_calib_complete一直未能置1。后面为了简化仿真,就将数据位宽选择为16,只需要例化一个ddr3_module。
2、sys_rst这个MIG IP复位信号,按照信号命名规则,开始以为是高电平复位有效,结果重新进入MIG IP配置页面查看,这个复位有效电平由用户决定的,默认是低电平有效。
3、其实基本所有的要点都可以在xilinx的官方文档ug586用户手册找到。
4 简谈MIG之AXI4接口
其实操作app的时序并不复杂,但是DDR是典型的突发型读写操作,而AXI4接口允许数据突发读写,而且AXI4有五个通道,使用AXI4,用户不需要去做读和写之间仲裁。因此,使用AXI4也变得更加傻瓜式操作,当然,我认为xilinx引入AXI4接口是为了提高系统互联简洁性。
AXI4接口的MIG相当于在APP接口套了一层接口转换逻辑,实际使用中,到底是使用AXI4还是APP接口更好,这个得根据实际需求实际分析,无疑使用AXI4会让DDR用户读写设计更加简单,而使用APP接口,在读写逻辑不庞大的情况下,APP接口可以省下一定的资源面积,当然如果资源有冗余,其实就看个人的使用习惯了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人