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教程

  • 相关阅读:
    POJ 1401 Factorial
    POJ 2407 Relatives(欧拉函数)
    POJ 1730 Perfect Pth Powers(唯一分解定理)
    POJ 2262 Goldbach's Conjecture(Eratosthenes筛法)
    POJ 2551 Ones
    POJ 1163 The Triangle
    POJ 3356 AGTC
    POJ 2192 Zipper
    POJ 1080 Human Gene Functions
    POJ 1159 Palindrome(最长公共子序列)
  • 原文地址:https://www.cnblogs.com/xianyufpga/p/13080819.html
Copyright © 2011-2022 走看看