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
View Code
复制代码

 仿真结果:(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接口可以省下一定的资源面积,当然如果资源有冗余,其实就看个人的使用习惯了。

5 参考链接

详解SDRAM基本原理以及FPGA实现读写控制-CSDN博客

详解DDR3原理以及使用Xilinx MIG IP核(app 接口)实现DDR3读写测试-CSDN博客

posted @   KD_one  阅读(590)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示