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

原文地址:https://www.cnblogs.com/xianyufpga/p/13080819.html