DDR4读写测试(二):基本读写测试

2 篇文章 1 订阅

上次基本讲了怎么配置MIG的IP,这次继续翻译手册PG150,根据其提供用户端的app接口的读写模式,针对每种模式进行最基本的读写测试。


MIG核用户app接口信号定义

写RTL前需要了解些什么?

  • 还需了解什么?知道个app接口定义不就好了????

但似乎在(一):MIG IP核配置中并未提地址的事情,所以本文再探讨下地址的问题。

我们知道,一个RAM中一个地址对应存储一个数据。但是问题来了,在KCU116评估板上有两颗DDR4的颗粒,都是256Mb*16的,也就是总容量为256Mb*16*2=8Gb,但是app接口提供的地址位宽为28bit,数据位宽256,也就是说对应的数据量为(2^28)*256bit=64Gb,显然64不等于8,那岂不是地址长了?高位地址都没用到?

综上,我们需要了解使用的这个IP的app接口的地址的含义。

  • 遇事不决,Product Guide!

在文档PG150中的123-128页中特地说明地址的定义,由于KCU116评估板上采用两颗DDR4的颗粒,并且在之前的MIG的配置中采用“ROW COLUMN BANK”映射形式(当然可以按照需要选择不同的映射形式),故可以直接看PG150的124页[1]

DDR4 “ROW_COLUMN_BANK” Mapping

由上图可以知道,app_addr包括{rank, logical_rank, row, column-3, bank, bank_group, 3},那么问题又来了,以上这些是指什么意思?

这就和DDR的结构有关系了,笔者简单画了个DDR4颗粒的结构图(当然画的比较简单抽象),如下所示:

DDR4颗粒的简单结构

由上图可以很清楚的看到,一颗DDR4的颗粒由若干Bank Group组成,每个Bank Group又由若干Bank组成,然后一个Bank由若干行列的矩阵组成,某行某列对应存储某个数据。当然DDR4颗粒会采用3DS(3-Dimensional Stack)堆叠方式来提升单颗的容量,上图只表示为一层时的情况,即logical_rank为1。当然对于某些DDR4内存条来说不只一颗DDR4颗粒,所以又会出现多个颗粒组成一个RANK,比如假定四颗数据位宽为16的DDR4颗粒组成一个RANK,但DDR4内存条上有八颗DDR4颗粒,那么RANK就是2。

了解这些之后,再查询DDR4的颗粒手册,板子采用MT40A256M16GE-075E,参数如下图所示[2]

MT40A256M16GE-075E参数

由上可以得知,各参数位宽为:rank = 1,logical_rank = 1,row = 15,column = 10, bank = 2,bank_group = 1。由于rank和logical_rank均为1,按照图“DDR4 “ROW_COLUMN_BANK” Mapping”可知app_addr位宽不包括rank和logical_rank,那么累加其余位宽可得28,正好和app_addr位宽一致!

app接口的地址定义清楚了,那么为什么数据位宽为256,为颗粒16*2的两倍,这时需要注意的是,由两个DDR4颗粒组成一个rank,数据总位宽为32bit,即上述app_addr每个地址对应数据位宽即为32bit,而该MIG中采用Burst Length 8,即app接口的数据位宽为256bit,但经过MIG后分成八次32bit存入相应的地址中,简单计算可得(2^28)*32bit = 8Gb,正好是两个DDR4颗粒的总容量

  • 破案了!

由上可知,并非说app接口的每个地址对应256bit的数据,而是只对应32bit,所以在读写数据的过程中,故需要注意地址的对齐,即app_addr[addr_width-1:3]中的地址可作为256bit数据的读写地址,而低三位[2:0]作为每个256bit分为8个32bit数据的存储地址。

  • 在什么情况下读写DDR4的效率最高?

前文说过DDR4相较于DDR3的最大改进之处即提出Bank Group结构,而每个Group可以独立工作,即如果前后对DDR的操作位于不同的Group里,那么后者不必等带前者执行完即可独立执行读写操作。那么本文采用的这颗DDR具有2个Bank Group,也就是说在最理想的情况下只要前后两次读操作或者写操作在不同的Group,即可获得最大性能。

上文得到的app_addr = {row, column-3, bank, bank_group, 3},即保证相邻两次读或写操作的app_addr[3]不一致,即可获得最高的读写效率。

上仿真?

本文暂不进行DDR4读写的仿真试验,具体仿真可详见:

测试一:简单顺序写读测试

接下来就是进行简单的读写测试,读写的时许图如下图所示[3],RTL按照时许图写即可。

DDR4 写操作时序图

DDR4 读操作时序图

  • 万物皆可状态机!

笔者采用状态机简单写了DDR4的写读的RTL如后附录所示,大概逻辑是:每次先向连续八个地址写入八个数据,再从这八个地址将数据读出,然后这个流程一直循环!

之后就是综合加Debug布局布线生成bit下载到 KCU116 评估板上观察结果!

  • 直接看测试结果!

下图即循环写读流程中一次写读数据过程,分为1-4步可见下图。

一次写读DDR4的Debug结果

依次向28'h8F81178 - 28'h8F811B0中八个地址写入111111... - 88888.....等八个数据。

写命令和写数据结果

之后再依次向28'h8F81178 - 28'h8F811B0中八个地址读出111111... - 88888.....等八个数据。

读命令和读数据结果

  • 上板测试成功!

由上测试可知,在地址对齐的情况下,连续向多个地址写入数据并再次读出,读写一致,即完成对DDR4的读写测试。同时可以发现,读出数据的顺序与读地址的顺序一致。

  • 读写8个太少,我再多点呢?

代码逻辑不变,只是将单次写读长度设为64,即单次写读256bit * 64 = 2048 bytes数据,结果如下图。

连续写读64*256bit数据结果

对DDR4连续进行64次读写操作,部分结果如下两张图所示,经过写读比较发现,两者数据一致,似乎日常使用问题不大。

写数据通道

读数据通道

对于该MIG的性能在文档PG150已有说明,如下图即该控制器对总线利用率的情况[4],或许在其他设计中,可以根据下图情况进行合理设计以达到最大利用率。当然,在文档中还有很多性能参数的介绍,这里不再赘述。

DDR总线效率

测试二:读写延时等测试

对于用户app接口来说,写数据的时候,命令通道和写数据通道的前后延时最大不超过2个时钟周期,在文档PG150已有说明,若下图所示[5],即在允许的范围内,写命令通道可以不与写数据通道一一对齐。

用户app接口写时许图

那么问题来了,我写数据输出到数据真正写入DDR的延时是多少?以及读数据的命令输出到该地址的数据被读出,中间又有多长的延时?

似乎问题在IP的配置过程中已有回答,如下图所示,即写延时14个时钟周期,读延时18个时钟周期,那么果真如此吗?

MIG核的Basic界面

由于写延时14,读延时18,在现在的代码逻辑下不能进行测试,故只测试读延时。

下图即读延时的测试结果,的确如IP配置中所说,从读命令发出到数据被读出,中间历经18个时钟周期,诚不欺我!

读延时测试结果

测试三:问题测试

  • 前面说了,用户app接口的命令通道的地址是根据DDR4的存储结构映射而来的,在使用app接口进行数据的读写时需要注意地址对齐,但是,我就不对齐,你拿我怎样?

如果地址不对齐(从低位地址直接累加),测试结果如下,光这锯齿般的读写就说明效率不高,更不用说数据不对。

地址不对齐,试试就逝世

  • 那我对齐地址,但是无视Bank Group、Bank、Row、Column等,读写的地址反复横跳、变换无常又会怎样?

地址反复横跳的最差测试结果如下图所示(同一Bank里读写),虽说写的数据和读出来的数据一致,但是可以明显的看到命令通道的ready信号会拉低,说明由于读写地址在同一bank group里等问题会出现一定的延时,DDR控制器处理不过来,从而导致读写效率的降低。

最差读写测试情况

总结

使用MIG的app接口进行读写操作时,需注意地址的映射关系,需注意地址对齐,顺序读写DDR4的效率最高!

注:上述顺序读写即选择“ROW COLUMN BANK”映射,以app_addr[n-1:3] <= app_addr[n-1:3] + 1顺序地址情况下,这样即可做到前后读写数据的地址位于不同的Bank Group里,以最大效率利用DDR的带宽。


以上即DDR4基本读写测试的全部内容,似乎前面关于地址的问题放到(一)中更为合适,不过先就这样放吧。可能上述测试比较简单,之后有时间再深入研究下DDR的其他问题。

因为目前只会使用MIG对DDR进行读写操作,对于DDR的原理结构等尚未非常了解,如有不对之处,还望批评指正~


附录1-DDR4写读测试源码

    //代码写的粗糙,仅作循环写读测试使用
    //--------信号定义------------
    localparam          WR_LENGTH   =   8'd8; //每次循环写读测试中数据长度 8*256bit

    reg     [15:0]      state_ns;
    reg     [15:0]      state_cs;
    localparam          IDLE        =   16'h1,
                        WR_DDR      =   16'h2,
                        WAIT        =   16'h4,
                        RD_DDR      =   16'h8,
                        PROC_END    =   16'h10;

    reg                 process_start;
    wire                write_ddr_end;
    wire                wait_ddr_end;
    wire                read_ddr_end;
    wire                process_end;
    reg     [7:0]       write_ddr_cnt;
    reg     [7:0]       wait_ddr_cnt;
    reg     [7:0]       read_ddr_cnt;
    reg     [15:0]      process_cnt;

    //命令通道
    reg                 ddr4_app_en;
    reg     [27:0]      ddr4_app_addr;
    reg     [2:0]       ddr4_app_cmd;

    //写数据通道
    reg                 ddr4_app_wdf_wren;
    reg                 ddr4_app_wdf_end;
    reg     [255:0]     ddr4_app_wdf_data;
    reg     [31:0]      ddr4_app_wdf_mask;

    //----------CMD--------------

    always @ (posedge ddr4_clk or negedge ddr4_rst_n) begin
        if(!ddr4_rst_n) begin
            state_cs    <=  IDLE;
        end
        else begin
            state_cs    <=  state_ns;
        end
    end

    always @ (*) begin
        case(state_cs)
            IDLE:begin
                if (process_start) begin
                    state_ns    =   WR_DDR;
                end
                else begin
                    state_ns    =   IDLE;
                end
            end

            WR_DDR:begin
                if (write_ddr_end) begin
                    state_ns    =   WAIT;
                end
                else begin
                    state_ns    =   WR_DDR;
                end
            end

            WAIT:begin
                if (wait_ddr_end) begin
                    state_ns    =   RD_DDR;
                end
                else begin
                    state_ns    =   WAIT;
                end
            end

            RD_DDR:begin
                if (read_ddr_end) begin
                    state_ns    =   PROC_END;
                end
                else begin
                    state_ns    =   RD_DDR;
                end
            end

            PROC_END:begin
                if (process_end) begin
                    state_ns    =   IDLE;
                end
                else begin
                    state_ns    =   PROC_END;
                end
            end

            default:begin
                state_ns        =   IDLE;
            end
        endcase
    end
    
    //流程计数
    always @ (posedge ddr4_clk or negedge ddr4_rst_n) begin
        if(!ddr4_rst_n) begin
            process_start   <=  1'b0;
        end
        else if (state_ns == IDLE) begin
            process_start   <=  c0_ddr4_init_complete_i & c0_ddr4_app_rdy_i & c0_ddr4_app_wdf_rdy_i;
        end
        else begin
            process_start   <=  1'b0;
        end
    end

    always @ (posedge ddr4_clk or negedge ddr4_rst_n) begin
        if(!ddr4_rst_n) begin
            write_ddr_cnt   <=  8'h0;
        end
        else if ((state_ns == WR_DDR) && c0_ddr4_app_rdy_i && ddr4_app_en) begin
            write_ddr_cnt   <=  write_ddr_cnt + 8'h1;
        end
        else if (state_ns == PROC_END) begin
            write_ddr_cnt   <=  8'h0;
        end
    end

    assign write_ddr_end = (write_ddr_cnt == (WR_LENGTH - 8'h1));

    always @ (posedge ddr4_clk or negedge ddr4_rst_n) begin
        if(!ddr4_rst_n) begin
            wait_ddr_cnt    <=  8'h0;
        end
        else if (state_ns == WAIT) begin
            wait_ddr_cnt    <=  wait_ddr_cnt + 8'h1;
        end
        else if (state_ns == PROC_END) begin
            wait_ddr_cnt    <=  8'h0;
        end
    end

    assign wait_ddr_end = (wait_ddr_cnt == 8'hF);

    always @ (posedge ddr4_clk or negedge ddr4_rst_n) begin
        if(!ddr4_rst_n) begin
            read_ddr_cnt    <=  8'h0;
        end
        else if ((state_ns == RD_DDR) && c0_ddr4_app_rdy_i && ddr4_app_en) begin
            read_ddr_cnt    <=  read_ddr_cnt + 8'h1;
        end
        else if (state_ns == PROC_END) begin
            read_ddr_cnt    <=  8'h0;
        end
    end

    assign read_ddr_end = (read_ddr_cnt == (WR_LENGTH - 8'h1));

    always @ (posedge ddr4_clk or negedge ddr4_rst_n) begin
        if(!ddr4_rst_n) begin
            process_cnt     <=  16'h0;
        end
        else if (state_ns == PROC_END) begin
            process_cnt     <=  process_cnt + 16'h1;
        end
        else if (state_ns == IDLE) begin
            process_cnt     <=  16'h0;
        end
    end

    assign process_end = (process_cnt == 16'hFF);

    //读写指令
    always @ (posedge ddr4_clk or negedge ddr4_rst_n) begin
        if(!ddr4_rst_n) begin
            ddr4_app_en                 <=  1'b0;
            ddr4_app_cmd                <=  3'b000;
        end
        else begin
            case(state_ns)
                WR_DDR:begin
                    if (c0_ddr4_app_rdy_i) begin
                        ddr4_app_en     <=  1'b1;
                        ddr4_app_cmd    <=  3'b000;
                    end
                    else begin
                        ddr4_app_en     <=  1'b0;
                        ddr4_app_cmd    <=  3'b000;
                    end
                end

                RD_DDR:begin
                    if (c0_ddr4_app_rdy_i) begin
                        ddr4_app_en     <=  1'b1;
                        ddr4_app_cmd    <=  3'b001;
                    end
                    else begin
                        ddr4_app_en     <=  1'b0;
                        ddr4_app_cmd    <=  3'b001;
                    end
                end

                default:begin
                    ddr4_app_en         <=  1'b0;
                    ddr4_app_cmd        <=  3'b000;
                end
            endcase
        end
    end

    //读写地址
    always @ (posedge ddr4_clk or negedge ddr4_rst_n) begin
        if(!ddr4_rst_n) begin
            ddr4_app_addr               <=  28'h0;
        end
        else begin
            case(state_ns)
                IDLE:begin
                    ddr4_app_addr       <=  28'h0;
                end

                WR_DDR:begin
                    if (c0_ddr4_app_rdy_i && ddr4_app_en) begin
                        ddr4_app_addr   <=  ddr4_app_addr + 28'h8;     //地址对齐
                    end
                    else begin
                        ddr4_app_addr   <=  ddr4_app_addr;
                    end
                end

                WAIT:begin
                    ddr4_app_addr       <=  28'h0;
                end

                RD_DDR:begin
                    if (c0_ddr4_app_rdy_i && ddr4_app_en) begin
                        ddr4_app_addr   <=  ddr4_app_addr + 28'h8;     //地址对齐
                    end
                    else begin
                        ddr4_app_addr   <=  ddr4_app_addr;
                    end
                end

                PROC_END:begin
                    ddr4_app_addr       <=  28'h0;
                end

                default:begin
                    ddr4_app_addr       <=  28'h0;
                end
            endcase
        end
    end

    //写数据
    always @ (posedge ddr4_clk or negedge ddr4_rst_n) begin
        if(!ddr4_rst_n) begin
            ddr4_app_wdf_wren           <=  1'b0;
            ddr4_app_wdf_end            <=  1'b0;
            ddr4_app_wdf_mask           <=  32'h0;
        end
        else begin
            case(state_ns)
                WR_DDR:begin
                    if (c0_ddr4_app_wdf_rdy_i) begin
                        ddr4_app_wdf_wren   <=  1'b1;
                        ddr4_app_wdf_end    <=  1'b1;
                        ddr4_app_wdf_mask   <=  32'h0;
                    end
                    else begin
                        ddr4_app_wdf_wren   <=  1'b0;
                        ddr4_app_wdf_end    <=  1'b0;
                        ddr4_app_wdf_mask   <=  32'h0;
                    end                end

                default:begin
                    ddr4_app_wdf_wren   <=  1'b0;
                    ddr4_app_wdf_end    <=  1'b0;
                    ddr4_app_wdf_mask   <=  32'h0;
                end
            endcase
        end
    end

    always @ (posedge ddr4_clk or negedge ddr4_rst_n) begin
        if(!ddr4_rst_n) begin
            ddr4_app_wdf_data       <=  256'h0;
        end
        else if (state_ns == IDLE) begin
            ddr4_app_wdf_data       <=  {(64){4'b1}};
        end
        else if((state_ns == WR_DDR) && c0_ddr4_app_wdf_rdy_i && ddr4_app_wdf_wren) begin
            ddr4_app_wdf_data       <=  ddr4_app_wdf_data + {(64){4'b1}};
        end
        else if (state_ns == WAIT) begin
            ddr4_app_wdf_data       <=  256'h0;
        end
    end

参考

  1. ^Xilinx手册PG150的第124页
  2. ^"MT40A256M16-075E" datasheet 第2页 MT40A256M16GE-075E AUT | Micron Technologies, Inc
  3. ^Xilinx手册PG150的第133页、第136页
  4. ^Xilinx手册PG150的第20页
  5. ^Xilinx手册PG150的第133页
  • 12
    点赞
  • 81
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值