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