Vivado中MIG核中DDR的读写控制

本文使用Vivado 2015.4在Nexys4 DDR(以下简称N4DDR)开发板上实现DDR的读写。
· FPGA如果需要对DDR进行读写,则需要一个DDR的控制器。根据官方的文档(UG586,下载链接在文末),DDR控制器的时序主要有三:
(1)首先是控制信号,如下图:

· 从上图可以看出,只有当app_rdy信号有效时,程序所发出的读写命令才会被控制器接收。这点必须注意。
(2)然后是写操作时序,如下图:
这里写图片描述
· 由图可知,在向DDR写数据时,需要提供写命令app_cmd、地址app_addr、数据app_wdf_data等信号,且写入的数据最多可以比app_cmd提前一个时钟周期有效,最迟可以比app_cmd晚两个时钟周期有效。
【特别注意】在写数据的时候必须检测 app_rdy和app_wdf_rdy信号是否同时有效,否则写入命令无法成功写入到DDR控制器的命令FIFO中,从而导致写操作失败。
(3)最后是读操作时序,如下图所示:
这里写图片描述
· 读操作的时序比较简单,只需要 注意app_rdy是否有效即可,其余不再赘述。

· Xilinx在Vivado中提供的Memory Interface Generator的IP核就是我们需要的DDR控制器,如下图所示。
Vivado中的MIG核
· 这里我们可以直接双击上面的MIG的IP核,开始例化我们所需的DDR控制器。(此时Win7以后的Windows版本(不含Win7)打开此IP核会报错,解决方法见http://blog.csdn.net/qq_20091945/article/details/53862467
· 打开后是如下图所示的界面,点Next。
打开MIG
· 给模块起个名字,根据实际情况选择控制器数量(这里笔者选择1),继续Next,如下图所示。
给MIG IP核起名字
· 在开发板芯片型号所对应的方框前打勾,如下图所示。
选择芯片型号
· 根据开发板上的DDR芯片选择DDR的种类,如N4DDR的开发板上的DDR芯片是DDR2的,因此如下图选择。
选择DDR类型
· 然后在Clock Period中输入合适的时钟周期长度(N4DDR的官方文档建议DDR的时钟为325MHz,故此处填3077ps);
· 接着在Memory Part中选择开发板上的DDR芯片的具体型号(N4DDR官方文档上说明为MT47H64M16HR-25E);
· 然后输入Data Width,此处以16为例。如下图所示。
这里写图片描述
· 选择Input Clock Period,这里填开发板的系统时钟(N4DDR为100MHz)。根据应用需要选择地址映射方式(这里保持默认的Bank-Row-Column)。
选择时钟和地址映射方式
· 然后,这里的System ClockReference Clock建议选择No Buffer,System Reset Polarity则根据应用需要灵活选择(这里设置为低电平有效),如下图所示。
这里写图片描述
· Internal Termination Impedence的选取应当参考开发板的官方文档说明,这里选50欧姆即可,继续Next。
这里写图片描述
· 选择Fixed Pin Out
这里写图片描述
· 接下来是DDR芯片的引脚分配。官网应该能找到,这里直接给出。文末会给出与此对应的引脚约束文件(n4ddr_ddr2_io_assign.ucf)。
这里写图片描述
这里写图片描述
这里写图片描述
· 耐心填完之后点击Validate按钮,没有错误的话会弹出一个对话框提示“Current Pinout is valid.”
· 然后的3个信号建议选择No connect,后面由我们自己根据需要连接到板上的相应引脚。
这里写图片描述
· 后面一直Next下去,点Accept,然后就可以点击Generate了。后面会再弹出一个对话框,直接点默认选中的按钮即可。


· 好了,下面是笔者自己编写的测试DDR2读写的程序。文末将提供对应工程的下载链接。

//*****************************************************************************
// Author           : Z.M.J. @ CSE, SEU
// Application      : MIG v2.4
// Filename         : example_top.v
// Date Created     : Fri Dec 30 2016
//
// Device           : 7 Series (Nexys 4 DDR)
// Design Name      : DDR2 SDRAM
// Purpose          : A demo of DDR2's read and write
// Reference        : ug586_7Series_MIS_v2.4.pdf
//*****************************************************************************

`timescale 1ps/1ps

module example_top (
    // system signals
    input                   sys_rst,
    input                   sys_clk_i,
    // application signals
    input  [15:0]           switch_i,
    output [15:0]           led,
    output [7:0]            an,
    output [7:0]            select_seg,
    // DDR2 chip signals
    inout [15:0]            ddr2_dq,
    inout [1:0]             ddr2_dqs_n,
    inout [1:0]             ddr2_dqs_p,
    output [12:0]           ddr2_addr,
    output [2:0]            ddr2_ba,
    output                  ddr2_ras_n,
    output                  ddr2_cas_n,
    output                  ddr2_we_n,
    output [0:0]            ddr2_ck_p,
    output [0:0]            ddr2_ck_n,
    output [0:0]            ddr2_cke,
    output [0:0]            ddr2_cs_n,
    output [1:0]            ddr2_dm,
    output [0:0]            ddr2_odt
);

    parameter DQ_WIDTH          = 16;
    parameter ECC_TEST          = "OFF";
    parameter ADDR_WIDTH        = 27;
    parameter nCK_PER_CLK       = 4;

    localparam DATA_WIDTH       = 16;
    localparam PAYLOAD_WIDTH    = (ECC_TEST == "OFF") ? DATA_WIDTH : DQ_WIDTH;
    localparam APP_DATA_WIDTH   = 2 * nCK_PER_CLK * PAYLOAD_WIDTH;
    localparam APP_MASK_WIDTH   = APP_DATA_WIDTH / 8;

    // Wire declarations
    reg app_en, app_wdf_wren, app_wdf_end;
    reg [2:0] app_cmd;
    reg [ADDR_WIDTH-1:0] app_addr;
    reg [APP_DATA_WIDTH-1:0] app_wdf_data;
    wire [APP_DATA_WIDTH-1:0] app_rd_data;
    wire [APP_MASK_WIDTH-1:0] app_wdf_mask;
    wire app_rdy, app_rd_data_end, app_rd_data_valid, app_wdf_rdy;



    //***************************************************************************
    wire [7:0] an;
    wire [7:0] select_seg;
    reg [31:0] digit_data;
    always@ (posedge sys_clk_i) begin
        if (switch_i[3])
            digit_data <= app_addr;
        else case (switch_i[1:0])
            2'b00 : digit_data <= read_data[31:0];
            2'b01 : digit_data <= read_data[63:32];
            2'b10 : digit_data <= read_data[95:64];
            2'b11 : digit_data <= read_data[127:96];
        endcase
    end

    digit U2(
        .wb_clk_i(sys_clk_i),
        .wb_rst_i(~sys_rst),
        .wb_dat_i(digit_data),
        .an(an),
        .select_seg(select_seg)
    );

    reg [1:0] read_valid = 2'b0;
    reg [127:0] read_data = 128'h0;
    always@ (posedge app_rd_data_valid) begin
        read_data = app_rd_data;
        read_valid[0] = (app_rd_data == data0);
        read_valid[1] = (app_rd_data == data1);
    end

    assign led[15] = app_en;
    assign led[14] = init_calib_complete;
    assign led[13] = app_rdy;
    assign led[12] = app_wdf_rdy;
    assign led[4] = sys_rst ? read_valid[1] : 1'b0;
    assign led[3] = sys_rst ? read_valid[0] : 1'b0;
    assign led[2] = stop_w[1];
    assign led[1] = stop_w[0];
    assign led[0] = app_cmd[0];

    reg [15:0] counter = 16'h0;
    parameter cnt_init = 16'h1; // minimum: 1
    reg [26:0] addr0 = 27'h000_0008;
    reg [26:0] addr1 = 27'h003_0100;
    reg [127:0] data0 = 128'h1111_2222_3333_4444_5555_6666_7777_8888;
    reg [127:0] data1 = 128'h9999_0000_aaaa_bbbb_cccc_dddd_eeee_ffff;
    reg [1:0] stop_w = 2'b00;
    always@ (posedge sys_clk_i or negedge sys_rst) begin
        if (sys_rst == 1'b0) begin
            counter = 12'b0;
            stop_w = 2'b0;
            app_en = 1'b0;
            app_addr = 27'h0;
            app_cmd = 3'b1;
            app_wdf_data = 128'h0;
            app_wdf_end = 1'b0;
            app_wdf_wren = 1'b0;
        end else begin
            if (counter == cnt_init && ~stop_w[0])
                if (app_rdy & app_wdf_rdy) begin
                    app_wdf_data = data0;
                    app_addr = addr0;
                    app_cmd = 3'b0;
                    app_wdf_wren = 1'b1;
                    app_wdf_end = 1'b1;
                    app_en = 1'b1;
                end else        // Hold specific signals until app_wdf_rdy is asserted.
                    counter = counter - 16'h1;
            else if (counter == cnt_init + 1 && ~stop_w[0])
                if (app_rdy & app_wdf_rdy) begin
                    app_wdf_end = 1'b0;
                    app_wdf_wren = 1'b0;
                    app_en = 1'b0;
                    app_cmd = 3'b1;
                    stop_w[0] = 1'b1;
                end else        // Hold specific signals until app_wdf_rdy is asserted.
                    counter = counter - 16'h1;
            else if (counter == cnt_init + 8 && ~stop_w[1])
                if (app_rdy & app_wdf_rdy) begin
                    app_wdf_data = data1;
                    app_addr = addr1;
                    app_cmd = 3'b0;
                    app_wdf_wren = 1'b1;
                    app_wdf_end = 1'b1;
                    app_en = 1'b1;
                end else        // Hold specific signals until app_wdf_rdy is asserted.
                    counter = counter - 16'h1;
            else if (counter == cnt_init + 9 && ~stop_w[1])
                if (app_rdy & app_wdf_rdy) begin
                    app_wdf_end = 1'b0;
                    app_wdf_wren = 1'b0;
                    app_en = 1'b0;
                    app_cmd = 3'b1;
                    stop_w[1] = 1'b1;
                end else        // Hold specific signals until app_wdf_rdy is asserted.
                    counter = counter - 16'h1;
            else if (counter == cnt_init + 88) begin
                app_addr = switch_i[2] ? addr1 : addr0;
                app_en = 1'b1;
                if (~app_rdy) counter = counter - 16'h1;
            end else if (counter == cnt_init + 89)
                app_en = 1'b0;

            counter = counter + 16'h1;
        end
    end

    // Start of User Design top instance
    //***************************************************************************
    // The User design is instantiated below. The memory interface ports are
    // connected to the top-level and the application interface ports are
    // connected to the traffic generator module. This provides a reference
    // for connecting the memory controller to system.
    //***************************************************************************
    my_ddr u_my_ddr (
        // Memory interface ports
        .ddr2_cs_n                  (ddr2_cs_n),
        .ddr2_addr                  (ddr2_addr),
        .ddr2_ba                    (ddr2_ba),
        .ddr2_we_n                  (ddr2_we_n),
        .ddr2_ras_n                 (ddr2_ras_n),
        .ddr2_cas_n                 (ddr2_cas_n),
        .ddr2_ck_n                  (ddr2_ck_n),
        .ddr2_ck_p                  (ddr2_ck_p),
        .ddr2_cke                   (ddr2_cke),
        .ddr2_dq                    (ddr2_dq),
        .ddr2_dqs_n                 (ddr2_dqs_n),
        .ddr2_dqs_p                 (ddr2_dqs_p),
        .ddr2_dm                    (ddr2_dm),
        .ddr2_odt                   (ddr2_odt),
        // Application interface ports
        .app_addr                   (app_addr),
        .app_cmd                    (app_cmd),
        .app_en                     (app_en),
        .app_wdf_rdy                (app_wdf_rdy),
        .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_sr_req                 (1'b0),
        .app_ref_req                (1'b0),
        .app_zq_req                 (1'b0),
        .app_wdf_mask               (16'h0000),
        .init_calib_complete        (init_calib_complete),
        // System Clock Ports
        .sys_clk_i                  (sys_clk_i),
        // Reference Clock Ports
        .clk_ref_i                  (sys_clk_i),
        .sys_rst                    (sys_rst)
    );

endmodule
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218

· 保存后直接生成比特流就可以下板验证了。
· 在摸索过程中笔者发现,写入了数据之后最快要到发出写命令的第8个系统时钟才能读出所写入的数据,且读操作必须在写操作后经过8的整数倍个时钟后进行。有时将比特流下载到N4DDR上面之后读写的数据有误,但是重启开发板再重新下载即可解决问题,知道个中缘由的朋友欢迎在评论中告知笔者,笔者在此先行谢过。
· 需要说明的是,此处突发长度(BL)为8,因此app_addr必须是8对齐的地址。同时,由于前面选择的Data Width为16,因此每次读写数据的长度为8*16bit==128bit。

  • 19
    点赞
  • 127
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值