zoukankan      html  css  js  c++  java
  • DDR2(4):对DDR2 IP再次封装

      生成 DDR2 IP 后就可以使用了,网络上也很多直接对 DDR2 IP 操作的例程,但其实这样还不够好,我们可以对这个 DDR2 IP 进行再次封装,让它变得更加好用。现在试着封装一下,之前的 DDR2 IP 名字就是 DDR2.v,这个封装就命名为 DDR2_burst,其主要作用是完成一次 DDR2 的突发读写,即外界可以任意设置突发长度,在这个模块将这个任意的突发长度转换为突发长度 4 写进 DDR2 IP 里。

      封装 DDR2 IP 有常见的两种方式,一种是设计 DDR2_burst 和 DDR2 IP 外部互联,再用第三个 .v 文件将这两者连线,如下所示:

      另一种是直接将 DDR2_IP 放到 DDR2_burst 代码里面例化,如下所示:

      显然,第二种比较简洁,文件较少。

    一、参数集

      先给出参数集,方便移植。命名为 DDR2_param.v,内容如下:

    //**************************************************************************
    // *** 名称 : DDR2_param.v
    // *** 作者 : xianyu_FPGA
    // *** 博客 : https://www.cnblogs.com/xianyufpga/
    // *** 日期 : 2020年6月
    // *** 描述 : DDR2参数,PLL 100Mhz,DDR2 166.7Mhz,Full Rate
    //**************************************************************************
    `define     MEM_ADDR_W          13      //DDR2 地址位宽
    `define     MEM_BANK_W          3       //DDR2 bank位宽
    `define     MEM_DM_W            4       //DDR2 dm位宽
    `define     MEM_DQ_W            32      //DDR2 数据位宽,一片16两片32
    `define     MEM_DQS_W           4       //DDR2 DQS位宽
    
    `define     LOCAL_DATA_W        64      //DDR2 IP核全速率数据位宽
    `define     LOCAL_ADDR_W        25      //DDR2 IP核全速率地址位宽
    `define     LOCAL_SIZE_W        3       //DDR2 IP核全速率local_size位宽
    `define     LOCAL_BE_W          8       //DDR2 IP核全速率local_be位宽
    
    `define     BURST_W             14      //burst长度位宽,burst_len + BURST_SIZE

      burst 长度位宽为什么是 14,下一篇博客会说明,现在记住这个数字。

    二、端口和信号

    `include "DDR2_param.v"
    //**************************************************************************
    // *** 名称 : DDR2_burst.v
    // *** 作者 : xianyu_FPGA
    // *** 博客 : https://www.cnblogs.com/xianyufpga/
    // *** 日期 : 2020年6月
    // *** 描述 : 完成一次DDR2的突发读写
    //**************************************************************************
    
    module DDR2_burst
    //============================< 端口 >======================================
    (
    //DDR2 IP核接口 -------------------------------------
    input                           pll_ref_clk         ,   //DDR2 参考时钟
    input                           global_reset_n      ,   //全局复位信号,连接外部复位
    output                          phy_clk             ,   //DDR2 IP核工作时钟
    output                          reset_phy_clk_n     ,   //DDR2 IP核同步后的复位信号
    output                          local_init_done     ,   //DDR2 IP核初始化完成信号
    //突发读写接口 --------------------------------------
    input                           burst_rdreq         ,   //突发读请求
    input                           burst_wrreq         ,   //突发写请求
    input   [`BURST_W       -1:0]   burst_rdlen         ,   //突发读长度
    input   [`BURST_W       -1:0]   burst_wrlen         ,   //突发写长度
    input   [`LOCAL_ADDR_W  -1:0]   burst_rdaddr        ,   //突发读地址
    input   [`LOCAL_ADDR_W  -1:0]   burst_wraddr        ,   //突发写地址
    output  [`LOCAL_DATA_W  -1:0]   burst_rddata        ,   //突发读数据
    input   [`LOCAL_DATA_W  -1:0]   burst_wrdata        ,   //突发写数据
    output                          burst_rdack         ,   //突发读应答,连接FIFO
    output                          burst_wrack         ,   //突发写应答,连接FIFO
    output                          burst_rddone        ,   //突发读完成信号
    output                          burst_wrdone        ,   //突发写完成信号
    //DDR2端口 ------------------------------------------
    output                          mem_odt             ,   //DDR2片上终结信号
    output                          mem_cs_n            ,   //DDR2片选信号
    output                          mem_cke             ,   //DDR2时钟使能信号
    output  [`MEM_ADDR_W    -1:0]   mem_addr            ,   //DDR2地址总线
    output  [`MEM_BANK_W    -1:0]   mem_ba              ,   //DDR2BANK信号
    output                          mem_ras_n           ,   //DDR2行地址选择信号
    output                          mem_cas_n           ,   //DDR2列地址选择信号
    output                          mem_we_n            ,   //DDR2写使能信号
    output  [`MEM_DM_W      -1:0]   mem_dm              ,   //DDR2数据掩膜信号
    inout                           mem_clk             ,   //DDR2时钟信号
    inout                           mem_clk_n           ,   //DDR2时钟反相信号
    inout   [`MEM_DQ_W      -1:0]   mem_dq              ,   //DDR2数据总线
    inout   [`MEM_DQS_W     -1:0]   mem_dqs                 //DDR2数据源同步信号
    );
    //============================< 信号 >======================================
    wire                            rst_n               ;   //本模块复位信号
    reg     [ 3:0]                  fsm_cs              ;   //状态机的当前状态
    reg     [ 3:0]                  fsm_ns              ;   //状态机的下一个状态
    reg     [ `BURST_W      -1:0]   rdaddr_cnt          ;   //读地址计数器
    reg     [ `BURST_W      -1:0]   rddata_cnt          ;   //读数据计数器
    reg     [ `BURST_W      -1:0]   wrdata_cnt         ;   //一次突发写内的计数器
    reg     [ `BURST_W      -1:0]   wraddr_cnt          ;   //写地址计数器
    reg     [ `BURST_W      -1:0]   rdlen               ;   //写突发长度
    reg     [ `BURST_W      -1:0]   wrlen               ;   //读突发长度
    wire                            local_burstbegin    ;   //DDR2 IP核突发起始信号
    reg     [`LOCAL_SIZE_W  -1:0]   local_size          ;   //DDR2 IP核突发大小
    reg     [`LOCAL_ADDR_W  -1:0]   local_address       ;   //DDR2 IP核地址总线
    wire                            local_write_req     ;   //DDR2 IP核写请求信号
    wire                            local_read_req      ;   //DDR2 IP核读请求信号
    wire    [`LOCAL_BE_W    -1:0]   local_be            ;   //DDR2 IP核字节使能信号
    wire    [`LOCAL_DATA_W  -1:0]   local_wdata         ;   //DDR2 IP核写数据总线
    wire    [`LOCAL_DATA_W  -1:0]   local_rdata         ;   //DDR2 IP核读数据总线
    wire                            local_ready         ;   //DDR2 IP核准备好信号
    wire                            local_rdata_valid   ;   //DDR2 IP核读数据有效信号
    //============================< 参数 >======================================
    parameter   WRBURST_SIZE        = `BURST_W'd2       ;   //总线写突发大小
    parameter   RDBURST_SIZE        = `BURST_W'd2       ;   //总线读突发大小
    parameter   FSM_IDLE            = 4'h0              ;   //空闲状态
    parameter   FSM_WR_RDY          = 4'h1              ;   //写准备状态
    parameter   FSM_WR              = 4'h2              ;   //写状态
    parameter   FSM_RD              = 4'h3              ;   //读状态
    parameter   FSM_RD_WAIT         = 4'h4              ;   //读等待状态

      本次设计的 local_size 的暂定数值 WRBURST_SIZE 和 RDBURST_SIZE 为 2,实际的 local_size 还会根据情况进行改变,且看下文。

    三、DDR2 IP 例化

    //==========================================================================
    //==    DDR2 IP核,PLL 100Mhz,DDR2 166.7Mhz,Full Rate
    //==========================================================================
    DDR2 u_DDR2
    (
        .pll_ref_clk            (pll_ref_clk            ),  //DDR2 参考时钟
        .global_reset_n         (global_reset_n         ),  //全局复位信号
        .soft_reset_n           (1'b1                   ),  //软复位信号
        .local_address          (local_address          ),  //DDR2 IP核地址总线
        .local_write_req        (local_write_req        ),  //DDR2 IP核写请求信号
        .local_read_req         (local_read_req         ),  //DDR2 IP核读请求信号
        .local_burstbegin       (local_burstbegin       ),  //DDR2 IP核突发起始信号
        .local_wdata            (local_wdata            ),  //DDR2 IP核写数据总线
        .local_be               (local_be               ),  //DDR2 IP核字节使能信号
        .local_size             (local_size             ),  //DDR2 IP核突发大小
        .local_ready            (local_ready            ),  //DDR2 IP核准备好信号
        .local_rdata            (local_rdata            ),  //DDR2 IP核读数据总线
        .local_rdata_valid      (local_rdata_valid      ),  //DDR2 IP核读数据有效信号
        .local_refresh_ack      (                       ),  //DDR2 IP核自刷新应答信号
        .local_init_done        (local_init_done        ),  //DDR2 IP核初始化完成信号
        //---------------------------------------------------
        .mem_odt                (mem_odt                ),  //DDR2片上终结信号
        .mem_cs_n               (mem_cs_n               ),  //DDR2片选信号
        .mem_cke                (mem_cke                ),  //DDR2时钟使能信号
        .mem_addr               (mem_addr               ),  //DDR2地址总线
        .mem_ba                 (mem_ba                 ),  //DDR2组地址信号
        .mem_ras_n              (mem_ras_n              ),  //DDR2行地址选择信
        .mem_cas_n              (mem_cas_n              ),  //DDR2列地址选择信
        .mem_we_n               (mem_we_n               ),  //DDR2写使能信号
        .mem_dm                 (mem_dm                 ),  //DDR2数据掩膜信号
        .mem_clk                (mem_clk                ),  //DDR2时钟信号
        .mem_clk_n              (mem_clk_n              ),  //DDR2时钟反相信号
        .mem_dq                 (mem_dq                 ),  //DDR2数据总线
        .mem_dqs                (mem_dqs                ),  //DDR2数据源同步信号
        .phy_clk                (phy_clk                ),  //DDR2 IP核工作时钟
        .reset_phy_clk_n        (reset_phy_clk_n        ),  //DDR2 IP核同步后的复位信号
        .reset_request_n        (                       ),  //DDR2 IP核复位请求信号
        .aux_full_rate_clk      (                       ),  //DDR2 IP核全速率时钟
        .aux_half_rate_clk      (                       )   //DDR2 IP核半速率时钟
    );

      此外,由于加入了 DDR2 的 IP 核,本模块的复位信号需要重新设计一下,即将 DDR2 IP 输出的复位信号和模块端口输入的那个复位信号进行合并:

    //本模块复位信号
    assign rst_n = reset_phy_clk_n && local_init_done;

    四、状态机

    //==========================================================================
    //==    状态机
    //==========================================================================
    always @ (posedge phy_clk or negedge rst_n) begin
        if(!rst_n)
            fsm_cs <= FSM_IDLE;
        else
            fsm_cs <= fsm_ns;
    end
    
    always @ (*) begin
        case(fsm_cs)
            //--------------------------------------------------- 空闲状态,等待进入读或写状态
            FSM_IDLE:
                    if(burst_wrreq && burst_wrlen != `BURST_W'd0)
                        fsm_ns = FSM_WR_RDY;
                    else if(burst_rdreq && burst_rdlen != `BURST_W'd0)
                        fsm_ns = FSM_RD;
                    else
                        fsm_ns = fsm_cs;
            //--------------------------------------------------- 写准备状态,插入这一状态是为了提前一拍发出数据请求,随后进入写状态
            FSM_WR_RDY:
                        fsm_ns = FSM_WR;
            //--------------------------------------------------- 写状态,进行写操作,写操作完成后进入空闲状态
            FSM_WR:
                    if(wraddr_cnt + wrburst_cnt >= wrlen - `BURST_W'd1 && local_ready)
                        fsm_ns = FSM_IDLE;
                    else
                        fsm_ns = fsm_cs;
            //--------------------------------------------------- 读状态,进行读操作,读操作完成后进入读等待状态
            FSM_RD:
                    if(rdaddr_cnt + RDBURST_SIZE >= rdlen && local_ready)
                        fsm_ns = FSM_RD_WAIT;
                    else
                        fsm_ns = fsm_cs;
            //--------------------------------------------------- 读等待状态,等待读完数据,读完数据则进入空闲状态
            FSM_RD_WAIT:
                    if(rddata_cnt >= rdlen - `BURST_W'h1 && local_rdata_valid)
                        fsm_ns = FSM_IDLE;
                    else
                        fsm_ns = fsm_cs;
            default:
                        fsm_ns = FSM_IDLE;
        endcase
    end

    五、锁存读写突发长度

      为了避免这次还没写完读完,可是外部的读写长度和命令已经到下一次了,导致程序出错,因此先锁存一下。

    //==========================================================================
    //==    在进入读写状态前锁存读写突发长度
    //==========================================================================
    always @ (posedge phy_clk or negedge rst_n) begin
        if(!rst_n) begin
            rdlen <= `BURST_W'h0;
        end
        else if(fsm_cs == FSM_IDLE && burst_rdreq && burst_rdlen != `BURST_W'd0) begin
            rdlen <= burst_rdlen;
        end
    end
    
    always @ (posedge phy_clk or negedge rst_n) begin
        if(!rst_n) begin
            wrlen <= `BURST_W'h0;
        end
        else if(fsm_cs == FSM_IDLE && burst_wrreq && burst_wrlen != `BURST_W'd0) begin
            wrlen <= burst_wrlen;
        end
    end

    六、写设计

    1、在一个突发内进行个数计数,因此写数据个数计数器在写期间不断的0、1计数,其目的产生该信号,供后续程序的使用。

    //==========================================================================
    //==    在一次写突发内,写数据个数计数器不断递增1:01010101
    //==========================================================================
    always @ (posedge phy_clk or negedge rst_n) begin
        if(!rst_n) begin
            wrdata_cnt <= `BURST_W'h0;
        end
        else if(fsm_cs == FSM_WR && local_ready && wrdata_cnt == WRBURST_SIZE - `BURST_W'h1) begin
            wrdata_cnt <= `BURST_W'h0;
        end
        else if(fsm_cs == FSM_WR && local_ready) begin
            wrdata_cnt <= wrdata_cnt + `BURST_W'h1;
        end
        else if(fsm_cs == FSM_WR) begin
            wrdata_cnt <= wrdata_cnt;
        end
        else begin
            wrdata_cnt <= `BURST_W'h0;
        end
    end

    2、完成一次突发写,写地址计数器就递增一个突发,该计数器最终落实到写地址上。

    //==========================================================================
    //==    当完成一次突发写,写地址递增一个突发
    //==========================================================================
    always @ (posedge phy_clk or negedge rst_n) begin
        if(!rst_n) begin
            wraddr_cnt <= `BURST_W'h0;
        end
        else if(fsm_cs == FSM_WR && wrdata_cnt == WRBURST_SIZE - `BURST_W'h1 && local_ready) begin
            wraddr_cnt <= wraddr_cnt + WRBURST_SIZE;    
        end
        else if(fsm_cs == FSM_WR) begin
            wraddr_cnt <= wraddr_cnt;
        end
        else begin
            wraddr_cnt <= `BURST_W'h0;
        end
    end

    六、读设计

    1、每读出一个数据时,数据个数计数器递增1,本次读完成时则清0,其目的产生该信号,供后续程序的使用。

    //==========================================================================
    //==    每读出一个数据时,数据个数递增1
    //==========================================================================
    always @ (posedge phy_clk or negedge rst_n) begin
        if(!rst_n) begin
            rddata_cnt <= `BURST_W'h0;
        end
        else if((fsm_cs == FSM_RD || fsm_cs == FSM_RD_WAIT) && local_rdata_valid) begin
            rddata_cnt <= rddata_cnt + `BURST_W'h1;
        end
        else if((fsm_cs == FSM_RD || fsm_cs == FSM_RD_WAIT) && !local_rdata_valid) begin
            rddata_cnt <= rddata_cnt;
        end
        else begin
            rddata_cnt <= `BURST_W'h0;
        end
    end

    2、每次给出读指令时,读地址递增一个突发

    //==========================================================================
    //==    每次给出读指令时,读地址递增一个突发
    //==========================================================================
    always @ (posedge phy_clk or negedge rst_n) begin
        if(!rst_n) begin
            rdaddr_cnt <= `BURST_W'h0;
        end
        else if(fsm_cs == FSM_RD && local_ready) begin
            rdaddr_cnt <= rdaddr_cnt + RDBURST_SIZE;
        end
        else if(fsm_cs == FSM_RD && !local_ready) begin
            rdaddr_cnt <= rdaddr_cnt;
        end
        else begin
            rdaddr_cnt <= `BURST_W'h0;
        end
    end

    七、local_size

      首先要做的是锁存数据,即把前面暂定的 WRBURST_SIZE 和 RDBURST_SIZE 根据情况赋值过去,并且要再最后一次突发读写时进行判断,如果这时的实际突发大小不足了(即不是2),则改为1,否则保持为前面的 2。

    //==========================================================================
    //==    锁存local_size,不足突发大小则更改local_size为1
    //==========================================================================
    always @ (posedge phy_clk or negedge rst_n) begin
        if(!rst_n) begin
            local_size <= `LOCAL_SIZE_W'h0;
        end
        else if(fsm_cs == FSM_IDLE && burst_rdreq && burst_rdlen != `BURST_W'd0) begin
            local_size <= (burst_rdlen >= RDBURST_SIZE) ? RDBURST_SIZE : burst_rdlen;
        end
        else if(fsm_cs == FSM_IDLE && burst_wrreq && burst_wrlen != `BURST_W'd0) begin
            local_size <= (burst_wrlen >= WRBURST_SIZE) ? WRBURST_SIZE : burst_wrlen;
        end
        else if(fsm_cs == FSM_RD && rdaddr_cnt + RDBURST_SIZE > rdlen && local_ready) begin
            local_size <= `LOCAL_SIZE_W'h1;
        end
        else if(fsm_cs == FSM_WR && wraddr_cnt + WRBURST_SIZE > wrlen && local_ready && wrdata_cnt == WRBURST_SIZE - `BURST_W'h1) begin
            local_size <= `LOCAL_SIZE_W'h1;
        end
    end

    八、local_address

      有选择的将 wraddr 和 rdaddr 赋值过去就行。

    //==========================================================================
    //==    锁存local_address,并且在完成一次突发读写时递增读写地址
    //==========================================================================
    always @ (posedge phy_clk or negedge rst_n) begin
        if(!rst_n) begin
            local_address <= `LOCAL_ADDR_W'h0;
        end
        else if(fsm_cs == FSM_IDLE && burst_wrreq && burst_wrlen != `BURST_W'd0) begin
            local_address <= burst_wraddr;
        end
        else if(fsm_cs == FSM_IDLE && burst_rdreq && burst_rdlen != `BURST_W'd0) begin
            local_address <= burst_rdaddr;
        end
        else if(fsm_cs == FSM_WR && wrdata_cnt == WRBURST_SIZE - `BURST_W'h1 && local_ready) begin
            local_address <= local_address + WRBURST_SIZE;
        end
        else if(fsm_cs == FSM_RD && local_ready) begin
            local_address <= local_address + RDBURST_SIZE;
        end
    end

    九、其他信号

    //==========================================================================
    //==    其他信号
    //==========================================================================
    ///一直拉高,不屏蔽数据
    assign local_be = ~`LOCAL_BE_W'h0;
    
    //在写状态期间拉高
    assign local_write_req = (fsm_cs == FSM_WR) ? 1'b1 : 1'b0;
    
    //在读状态期间拉高
    assign local_read_req = (fsm_cs == FSM_RD) ? 1'b1 : 1'b0;
    
    //在写突发起始时或给读指令时生成burstbegin信号
    assign local_burstbegin = ((fsm_cs == FSM_WR && wrdata_cnt == `BURST_W'h0) || fsm_cs == FSM_RD) ? 1'b1 : 1'b0;
    
    //应答信号用于从FIFO请求数据,提前数据一拍
    assign burst_wrack = (fsm_cs == FSM_WR_RDY || (fsm_cs == FSM_WR && wraddr_cnt + wrdata_cnt < wrlen - `BURST_W'd1 && local_ready)) ? 1'b1 : 1'b0;
    
    //用于FIFO的写使能
    assign burst_rdack = local_rdata_valid;
    
    //写数据通道
    assign local_wdata = burst_wrdata;
    
    //读数据通道
    assign burst_rddata = local_rdata;
    
    //在写最后一个数据时拉高
    assign burst_wrdone = (fsm_cs == FSM_WR && wraddr_cnt + wrdata_cnt >= wrlen - `BURST_W'd1 && local_ready) ? 1'b1 : 1'b0;
    
    //在读最后一个数据时拉高
    assign burst_rddone = (fsm_cs == FSM_RD_WAIT && rddata_cnt >= rdlen - `BURST_W'h1 && local_rdata_valid) ? 1'b1 : 1'b0;

      本代码修改自锆石科技FPGA教程,如果有需要的同学,直接将本模块复制成一个 DDR2_param.v 文件和 DDR2_burst.v 文件即可使用。如果用不了也没办法,反正我的能用。

    参考资料:锆石科技FPGA教程

  • 相关阅读:
    windows下用python转换markdown到html
    windows下安装pip和easy_install
    Getting Real 摘记
    使iis支持asp.net扩展
    Linux IO模型
    kdissert:linux下的自由脑图软件
    debian下配置双核cpu
    内核:为了fan的健康,我的重新编译记录
    科研在每个人的生活中
    Donald Ervin Knuth:最年轻的图灵奖高德纳
  • 原文地址:https://www.cnblogs.com/xianyufpga/p/13080819.html
Copyright © 2011-2022 走看看