由于直接对 DDR3 进行控制很复杂,因此一般使用 MIG IP 来实现,同时为了更简单地使用 MIG IP,我们采用 AXI4 总线协议进行控制。下面首先介绍 MIG IP 的配置,然后看看官方 demo (里面包含一个仿真要用到的 DDR3 模型)及其仿真结果,最后进行我们自己的控制代码实现。
MIG IP 生成
在 IP Catalog 里搜索 MIG,如下
第一步里,勾选 AXI4 Interface 选项,使用 AXI4 接口
下一步是选择 FPGA 型号,读者自行选择自己的 FPGA 型号;下一步是选择控制器类型,我们直接选择 DDR3 SDRAM;这几步很简单,就不放图了。再下一步,Controller Options,这里要重点介绍下
首先配置 DDR3 的工作时钟,这个时钟也就是 DDR3_CLK_P/N,下一个 PHY to Controller Clock Ratio 是 FPGA 与 MIG 间通信使用的时钟(ui_clk)与 DDR3 时钟的比值,笔者使用 200MHz,因此这里设置为 2:1;Memory Type 需读者按照自己的 DDR 型号进行选择;Data Width 是 DDR3 用到的数据位宽,比如笔者用到 4 片 DDR,每片位宽 16bit,菊花链连接,因此这里需要设为 64。
下一步,这里的 Data Width 是 FPGA 与 MIG 通信用到的数据位宽,为方便起见,我使用了与 DDR 数据同位宽;下一个是设置读写优先级的,TDM 是读写同级,也可修改为读/写优先、循环读写等。
下一步,Input Clock Period 是设置 sys_clk_i 的频率,MIG IP 会使用这个时钟生成 DDR3_CK 信号以及供用户使用的 ui_clk,所有关于 MIG 的用户操作均应在 ui_clk 下进行;最下面的 Memory Address Mapping Selection 是 MIG 与 DDR 进行读写通信时的地址写入顺序。
下一步,System Clock 设为 No Buffer,因为我们用到的 sys_clk_i 是从 FPGA 内部提供的,如果是从外部引脚提供,则需要修改这里(有单引脚时钟,也有差分时钟信号);参考时钟直接使用 sys_clk_i;System Reset Polarity 设置 sys_rst 的极性,这里我设为低电平有效。
下一步是设置引脚阻抗;再下一步,勾选 Fixed Pin Out;再下一步,这里配置 DDR3 的引脚,可以手动配置也可以 read XDC 来配置,然后 Validate 一下,如果通过了,就可以下一步了(不 validate 那个 next 无法点击)。
下一步,由于这三个信号我均做为 FPGA 内部信号,因此都 No connect
再之后就是 summary 啥的,一路 next 到底就行。
- 注意事项
在使用 MIG IP 时,mmcm_locked 信号指示是否稳定给出 ui_clk,为高表示给出了稳定的 ui_clk;init_calib_complete 信号表示会否完成 DDR3 的初始化,为高表示完成初始化,在这之后才可以进行 DDR3 的读写。
注意 MIG IP 的 app_sr_req、app_ref_req、app_zq_req 要置零,否则无法完成 DDR 初始化,init_calib_complete 将无法拉高。
此外,MIG IP 有三个 reset 信号,分别是 sys_rst、ui_clk_sync_rst 和 aresetn 。ui_clk_sync_rst 是同步于 ui_clk 的 sys_rst,高电平有效;但 MIG 并不会因为 sys_rst 而 reset,而是通过 aresetn 进行 reset 的,且 aresetn 应当同步于 ui_clk。因此应当如下书写三者的关系
always @(posedge ui_clk) begin
aresetn <= ~ui_clk_sync_rst;
end
官方 demo
在生成的 MIG IP 上右击,Open IP Example Design,会新建一个名为 mig_7series_0_ex.xpr 的工程,打开工程,可以看到一个顶层文件 example_top.v 和一个仿真文件 sim_tb_top.v
可以直接运行仿真,结果如下
在工程文件夹下, ./imports 文件夹中,有两个文件,ddr3_model.sv 和 ddr3_model_parameters.vh,前者是 ddr3 的模型,引用了后边这个 parameter 文件,parameter 文件里是关于 ddr3 模型的参数配置,在 ddr3_model 中修改 `define 值,可以配置 ddr3_model 的各类参数。后面仿真我们自己写的控制代码就要用到这个 DDR3 模型文件。
自己的FPGA实现
MIG IP 用到 AXI 总线协议,因此建议读者首先对该总线协议进行初步了解,具体可见我之前的文章。这里也推荐一个知乎大佬写的 AXI 协议介绍,链接在此。
其余不多说,代码如下,该模块自动完成从 W_FIFO 向 DDR 的写入,以及从 DDR 读数据到 R_FIFO,用户只需要读写两个 FIFO 即可
/*
* file : DDR3_top.v
* author : 今朝无言
* Lab : WHU-EIS-LMSWE
* date : 2023-04-21
* version : v1.0
* description : DDR3控制模块
*/
module DDR3_top(
input clk_200M,
input rst_n,
output rst_busy,
//--------------------ddr3------------------------
inout [63:0] ddr3_dq, //DQ,4片 x16 DDR的数据线
output [14:0] ddr3_addr, //Address
output [2:0] ddr3_ba, //Bank Address
inout [7:0] ddr3_dqs_p, //DQ Select,0,2,4,6分别为四片DDR的DQSL(Lower Byte),
inout [7:0] ddr3_dqs_n, //1,3,5,7分别为四片DDR的DQSU(Upper Byte)
//Output with read data. Edge-aligned with read data.
//Input with write data. Center-aligned to write data.
output [0:0] ddr3_ck_p, //4片DR3共用ck、cke、odt、cs信号,故[0:0]
output [0:0] ddr3_ck_n,
//differential clock inputs. All control and address input signals are sampled
//on the crossing of the positive edge of CK and the negative edge of CK#
output [7:0] ddr3_dm, //Input Data Mask,0,2,4,6为DML,1,3,5,7为DMU
output [0:0] ddr3_cke, //Clock Enable
output [0:0] ddr3_cs_n, //Chip Select
output ddr3_ras_n, //Row Address Enable
output ddr3_cas_n, //Column Address Enable
output ddr3_we_n, //Write Enable
output [0:0] ddr3_odt, //On-die termination enable
output ddr3_reset_n,
//-------------FPGA FIFO Control-------------------
input wr_en, //高电平有效
input [63:0] wrdat,
output full, //fifo_w full
input rd_en, //高电平有效
output [63:0] rddat,
output empty //fifo_r empty
);
//本方案采用4片 MT41K256M16TW-107 芯片,每片 256M * 16bit = 512MB 容量
//8个Bank,故 BA 为 3 位;行地址用到全部的 15 根地址线,列地址用到 log2(256M/2^15/2^3)=10 根地址线
parameter AXI_ID = 4'h00; //读写事务ID
parameter DATA_NUM = 32; //一次突发传输的数据个数,1~256
localparam AXI_LEN = DATA_NUM - 1'b1;
localparam FIFO_LEN = 1024; //w_fifo、r_fifo的长度
//----------------------------------state define-----------------------------------------
localparam S_IDLE = 8'h01; //等待初始化
localparam S_ARB = 8'h02; //判决接下来进行WR还是RD
localparam S_WR_ADDR = 8'h04; //写地址
localparam S_WR_DATA = 8'h08; //写数据
localparam S_WR_RESP = 8'h10; //写回复
localparam S_RD_ADDR = 8'h20; //读地址
localparam S_RD_DATA = 8'h40; //读数据
localparam S_RD_RESP = 8'h80; //读回复
//---------------------------------------------------------------------------------------
reg [7:0] state = S_IDLE;
reg [7:0] next_state = S_IDLE;
//尽管 AXI 的读、写可以独立,然而 DDR 读写是分时复用,因此这里的读写也必须分时进行
//FIFO
wire w_fifo_full;
wire w_fifo_empty;
wire r_fifo_full;
wire r_fifo_empty;
reg w_fifo_rden = 1'b0;
reg r_fifo_wren = 1'b0;
wire fifo_rst;
wire [9:0] w_fifo_rddat_cnt;
wire [9:0] r_fifo_wrdat_cnt;
wire w_fifo_wr_rst_busy;
wire w_fifo_rd_rst_busy;
wire r_fifo_wr_rst_busy;
wire r_fifo_rd_rst_busy;
wire w_fifo_rst_busy;
wire r_fifo_rst_busy;
//Application interface ports
wire ui_clk; //MIG输出的时钟,一切关于mig的操作均应在此时钟域下进行
wire ui_clk_sync_rst; //mig输出的rst信号,同步于ui_clk,高电平有效
wire mmcm_locked;
reg aresetn;
wire init_calib_complete; //指示MIG是否完成初始化
wire app_sr_req;
wire app_ref_req;
wire app_zq_req;
wire app_sr_active;
wire app_ref_ack;
wire app_zq_ack;
//Slave Interface Write Address Ports
wire [3:0] s_axi_awid;
reg [30:0] s_axi_awaddr; //4*512MB=2GB,对应 2^31 Byte,故 AXI 地址线位宽为 31
wire [7:0] s_axi_awlen;
wire [2:0] s_axi_awsize;
wire [1:0] s_axi_awburst;
wire [0:0] s_axi_awlock;
wire [3:0] s_axi_awcache;
wire [2:0] s_axi_awprot;
wire [3:0] s_axi_awqos;
reg s_axi_awvalid;
wire s_axi_awready;
//Slave Interface Write Data Ports
wire [63:0] s_axi_wdata;
wire [7:0] s_axi_wstrb;
reg s_axi_wlast;
reg s_axi_wvalid;
wire s_axi_wready;
//Slave Interface Write Response Ports
wire [3:0] s_axi_bid;
wire [1:0] s_axi_bresp;
wire s_axi_bvalid;
reg s_axi_bready;
//Slave Interface Read Address Ports
wire [3:0] s_axi_arid;
reg [30:0] s_axi_araddr;
wire [7:0] s_axi_arlen;
wire [2:0] s_axi_arsize;
wire [1:0] s_axi_arburst;
wire [0:0] s_axi_arlock;
wire [3:0] s_axi_arcache;
wire [2:0] s_axi_arprot;
wire [3:0] s_axi_arqos;
reg s_axi_arvalid;
wire s_axi_arready;
//Slave Interface Read Data Ports
wire [3:0] s_axi_rid;
wire [63:0] s_axi_rdata;
wire [1:0] s_axi_rresp;
wire s_axi_rlast;
wire s_axi_rvalid;
reg s_axi_rready;
//ddr3读写请求
reg wr_ddr3_req;
reg rd_ddr3_req;
//突发读写的已传输数据计数
reg [8:0] data_cnt;
//------------------------------------write FIFO------------------------------------------
//异步FIFO 采用 First Word Fall Through 模式 1024*64
fifo_generator_DDR_W fifo_w(
.rst (fifo_rst),
.wr_clk (clk_200M),
.rd_clk (ui_clk),
.din (wrdat),
.wr_en (wr_en),
.rd_en (w_fifo_rden),
.dout (s_axi_wdata),
.full (w_fifo_full),
.empty (w_fifo_empty),
.rd_data_count (w_fifo_rddat_cnt), //仿真显示,w_fifo_rddat_cnt 和 r_fifo_wrdat_cnt 计数不可信,...IP 在做什么!!!
.wr_data_count (),
.wr_rst_busy (w_fifo_wr_rst_busy),
.rd_rst_busy (w_fifo_rd_rst_busy)
);
//------------------------------------read FIFO-------------------------------------------
//异步FIFO
fifo_generator_DDR_R fifo_r(
.rst (fifo_rst),
.wr_clk (ui_clk),
.rd_clk (clk_200M),
.din (s_axi_rdata),
.wr_en (r_fifo_wren),
.rd_en (rd_en),
.dout (rddat),
.full (r_fifo_full),
.empty (r_fifo_empty),
.rd_data_count (),
.wr_data_count (r_fifo_wrdat_cnt),
.wr_rst_busy (r_fifo_wr_rst_busy),
.rd_rst_busy (r_fifo_rd_rst_busy)
);
//----------------------------------MIG IP, AXI4-----------------------------------------
mig_7series_0 u_mig_7series_0 (
// Memory interface ports
.ddr3_addr (ddr3_addr), // output [14:0] ddr3_addr
.ddr3_ba (ddr3_ba), // output [2:0] ddr3_ba
.ddr3_cas_n (ddr3_cas_n), // output ddr3_cas_n
.ddr3_ck_n (ddr3_ck_n), // output [0:0] ddr3_ck_n
.ddr3_ck_p (ddr3_ck_p), // output [0:0] ddr3_ck_p
.ddr3_cke (ddr3_cke), // output [0:0] ddr3_cke
.ddr3_ras_n (ddr3_ras_n), // output ddr3_ras_n
.ddr3_reset_n (ddr3_reset_n), // output ddr3_reset_n
.ddr3_we_n (ddr3_we_n), // output ddr3_we_n
.ddr3_dq (ddr3_dq), // inout [63:0] ddr3_dq
.ddr3_dqs_n (ddr3_dqs_n), // inout [7:0] ddr3_dqs_n
.ddr3_dqs_p (ddr3_dqs_p), // inout [7:0] ddr3_dqs_p
.ddr3_cs_n (ddr3_cs_n), // output [0:0] ddr3_cs_n
.ddr3_dm (ddr3_dm), // output [7:0] ddr3_dm
.ddr3_odt (ddr3_odt), // output [0:0] ddr3_odt
// Application interface ports
.ui_clk (ui_clk), // output ui_clk
.ui_clk_sync_rst (ui_clk_sync_rst), // output ui_clk_sync_rst
.mmcm_locked (mmcm_locked), // output mmcm_locked
.aresetn (aresetn), // input aresetn
.init_calib_complete (init_calib_complete), // output init_calib_complete
.app_sr_req (app_sr_req), // input app_sr_req
.app_ref_req (app_ref_req), // input app_ref_req
.app_zq_req (app_zq_req), // input app_zq_req
.app_sr_active (app_sr_active), // output app_sr_active
.app_ref_ack (app_ref_ack), // output app_ref_ack
.app_zq_ack (app_zq_ack), // output app_zq_ack
// Slave Interface Write Address Ports
.s_axi_awid (s_axi_awid), // input [3:0] s_axi_awid
.s_axi_awaddr (s_axi_awaddr), // input [30:0] s_axi_awaddr
.s_axi_awlen (s_axi_awlen), // input [7:0] s_axi_awlen
.s_axi_awsize (s_axi_awsize), // input [2:0] s_axi_awsize
.s_axi_awburst (s_axi_awburst), // input [1:0] s_axi_awburst
.s_axi_awlock (s_axi_awlock), // input [0:0] s_axi_awlock
.s_axi_awcache (s_axi_awcache), // input [3:0] s_axi_awcache
.s_axi_awprot (s_axi_awprot), // input [2:0] s_axi_awprot
.s_axi_awqos (s_axi_awqos), // input [3:0] s_axi_awqos
.s_axi_awvalid (s_axi_awvalid), // input s_axi_awvalid
.s_axi_awready (s_axi_awready), // output s_axi_awready
// Slave Interface Write Data Ports
.s_axi_wdata (s_axi_wdata), // input [63:0] s_axi_wdata
.s_axi_wstrb (s_axi_wstrb), // input [7:0] s_axi_wstrb
.s_axi_wlast (s_axi_wlast), // input s_axi_wlast
.s_axi_wvalid (s_axi_wvalid), // input s_axi_wvalid
.s_axi_wready (s_axi_wready), // output s_axi_wready
// Slave Interface Write Response Ports
.s_axi_bid (s_axi_bid), // output [3:0] s_axi_bid
.s_axi_bresp (s_axi_bresp), // output [1:0] s_axi_bresp
.s_axi_bvalid (s_axi_bvalid), // output s_axi_bvalid
.s_axi_bready (s_axi_bready), // input s_axi_bready
// Slave Interface Read Address Ports
.s_axi_arid (s_axi_arid), // input [3:0] s_axi_arid
.s_axi_araddr (s_axi_araddr), // input [30:0] s_axi_araddr
.s_axi_arlen (s_axi_arlen), // input [7:0] s_axi_arlen
.s_axi_arsize (s_axi_arsize), // input [2:0] s_axi_arsize
.s_axi_arburst (s_axi_arburst), // input [1:0] s_axi_arburst
.s_axi_arlock (s_axi_arlock), // input [0:0] s_axi_arlock
.s_axi_arcache (s_axi_arcache), // input [3:0] s_axi_arcache
.s_axi_arprot (s_axi_arprot), // input [2:0] s_axi_arprot
.s_axi_arqos (s_axi_arqos), // input [3:0] s_axi_arqos
.s_axi_arvalid (s_axi_arvalid), // input s_axi_arvalid
.s_axi_arready (s_axi_arready), // output s_axi_arready
// Slave Interface Read Data Ports
.s_axi_rid (s_axi_rid), // output [3:0] s_axi_rid
.s_axi_rdata (s_axi_rdata), // output [63:0] s_axi_rdata
.s_axi_rresp (s_axi_rresp), // output [1:0] s_axi_rresp
.s_axi_rlast (s_axi_rlast), // output s_axi_rlast
.s_axi_rvalid (s_axi_rvalid), // output s_axi_rvalid
.s_axi_rready (s_axi_rready), // input s_axi_rready
// System Clock Ports
.sys_clk_i (clk_200M), // input sys_clk_i 200M
.sys_rst (rst_n) // input sys_rst
);
//---------------------------------------FSM----------------------------------------------
always @(posedge ui_clk or posedge ui_clk_sync_rst) begin
if(ui_clk_sync_rst) begin
state <= S_IDLE;
end
else begin
state <= next_state;
end
end
always @(*) begin
case(state)
S_IDLE: begin
if(mmcm_locked && init_calib_complete) begin
next_state <= S_ARB;
end
else begin
next_state <= S_IDLE;
end
end
S_ARB: begin
if(wr_ddr3_req) begin
next_state <= S_WR_ADDR;
end
else if(rd_ddr3_req && (s_axi_araddr != s_axi_awaddr)) begin //DDR3不空,才可读取
next_state <= S_RD_ADDR;
end
else begin
next_state <= S_ARB;
end
end
S_WR_ADDR: begin
if(s_axi_awvalid && s_axi_awready) begin
next_state <= S_WR_DATA;
end
else begin
next_state <= S_WR_ADDR;
end
end
S_WR_DATA: begin
if(data_cnt >= DATA_NUM) begin
next_state <= S_WR_RESP;
end
else begin
next_state <= S_WR_DATA;
end
end
S_WR_RESP: begin
if(s_axi_bvalid && s_axi_bready) begin
next_state <= S_ARB;
end
else begin
next_state <= S_WR_RESP;
end
end
S_RD_ADDR: begin
if(s_axi_arvalid && s_axi_arready) begin
next_state <= S_RD_DATA;
end
else begin
next_state <= S_RD_ADDR;
end
end
S_RD_DATA: begin
if(data_cnt >= DATA_NUM) begin //m_axi_rready && m_axi_rvalid && m_axi_rlast
next_state <= S_RD_RESP;
end
else begin
next_state <= S_RD_DATA;
end
end
S_RD_RESP: begin
next_state <= S_ARB;
end
default: begin
next_state <= S_IDLE;
end
endcase
end
//-------------------------------------Control--------------------------------------------
assign s_axi_awid = AXI_ID;
assign s_axi_arid = AXI_ID;
assign s_axi_awlen = AXI_LEN;
assign s_axi_arlen = AXI_LEN;
assign s_axi_awsize = 3'd3; //每个数据的大小为2^awsize = 2^3 = 8Bytes = 64bit
assign s_axi_awburst = 2'b01; //突发传输模式为INCR,地址一直增加,递增的值为(2^Burst_size)
assign s_axi_awlock = 1'b0; //正常传输,非独占传输
assign s_axi_awcache = 4'b0000; //指明了总线中的存储类型
assign s_axi_awprot = 3'b000; //指明访问是否被允许的信号
assign s_axi_awqos = 4'b0000;
assign s_axi_wstrb = 8'hff; //写字节通道选通
assign s_axi_arsize = 3'd3;
assign s_axi_arburst = 2'b01; //突发传输模式为INCR
assign s_axi_arlock = 1'b0;
assign s_axi_arcache = 4'b0000;
assign s_axi_arprot = 3'b000;
assign s_axi_arqos = 4'b0000;
assign full = w_fifo_full;
assign empty = r_fifo_empty;
always @(posedge ui_clk) begin
aresetn <= ~ui_clk_sync_rst;
end
assign fifo_rst = ~mmcm_locked;
//fifo的rst必须同时存在rd_clk和wr_clk,否则将初始化失败,一直处在rst_busy阶段
assign app_sr_req = 1'b0; //此三者要置零,否则 refresh DDR
assign app_ref_req = 1'b0;
assign app_zq_req = 1'b0;
assign w_fifo_rst_busy = w_fifo_wr_rst_busy | w_fifo_rd_rst_busy;
assign r_fifo_rst_busy = r_fifo_wr_rst_busy | r_fifo_rd_rst_busy;
assign rst_busy = w_fifo_rst_busy | r_fifo_rst_busy | (~init_calib_complete);
//-----------s_axi_awvalid--------------
always @(*) begin
case(state)
S_WR_ADDR: begin
s_axi_awvalid <= 1'b1;
end
default: begin
s_axi_awvalid <= 1'b0;
end
endcase
end
//-----------s_axi_wvalid---------------
always @(*) begin
case(state)
S_WR_DATA: begin
if(~w_fifo_empty && data_cnt < DATA_NUM) begin
s_axi_wvalid <= 1'b1;
end
else begin
s_axi_wvalid <= 1'b0;
end
end
default: begin
s_axi_wvalid <= 1'b0;
end
endcase
end
//-----------s_axi_wlast---------------
always @(*) begin
case(state)
S_WR_DATA: begin
if(data_cnt == DATA_NUM - 1) begin
s_axi_wlast <= 1'b1;
end
else begin
s_axi_wlast <= 1'b0;
end
end
default: begin
s_axi_wlast <= 1'b0;
end
endcase
end
//-----------s_axi_bready---------------
always @(*) begin
case(state)
S_WR_RESP: begin
s_axi_bready <= 1'b1;
end
default: begin
s_axi_bready <= 1'b0;
end
endcase
end
//-----------s_axi_arvalid--------------
always @(*) begin
case(state)
S_RD_ADDR: begin
s_axi_arvalid <= 1'b1;
end
default: begin
s_axi_arvalid <= 1'b0;
end
endcase
end
//-----------s_axi_rready---------------
always @(*) begin
case(state)
S_RD_DATA: begin
if(~r_fifo_full && data_cnt < DATA_NUM) begin
s_axi_rready <= 1'b1;
end
else begin
s_axi_rready <= 1'b0;
end
end
S_RD_RESP: begin
s_axi_rready <= 1'b1;
end
default: begin
s_axi_rready <= 1'b0;
end
endcase
end
//------------w_fifo_rden---------------
always @(*) begin
case(state)
S_WR_DATA: begin
w_fifo_rden <= s_axi_wvalid & s_axi_wready;
end
default: begin
w_fifo_rden <= 1'b0;
end
endcase
end
//------------r_fifo_wren---------------
always @(*) begin
case(state)
S_RD_DATA: begin
r_fifo_wren <= s_axi_rready & s_axi_rvalid;
end
default: begin
r_fifo_wren <= 1'b0;
end
endcase
end
//-------------data_cnt-----------------
always @(posedge ui_clk) begin
case(state)
S_IDLE, S_ARB: begin
data_cnt <= 9'd0;
end
S_WR_DATA: begin
if(s_axi_wvalid && s_axi_wready) begin
data_cnt <= data_cnt + 1'b1;
end
else begin
data_cnt <= data_cnt;
end
end
S_RD_DATA: begin
if(s_axi_rvalid && s_axi_rready) begin
data_cnt <= data_cnt + 1'b1;
end
else begin
data_cnt <= data_cnt;
end
end
default: begin
data_cnt <= data_cnt;
end
endcase
end
//------------s_axi_awaddr--------------
always @(posedge ui_clk) begin
case(state)
S_IDLE: begin
s_axi_awaddr <= 31'd0;
end
S_WR_RESP: begin
if(s_axi_bvalid && s_axi_bready) begin
s_axi_awaddr <= s_axi_awaddr + DATA_NUM * 8; //s_axi_awaddr为字节地址,每次写NUM个64bit数据
end //超出最大范围后自动回到0
else begin
s_axi_awaddr <= s_axi_awaddr;
end
end
default: begin
s_axi_awaddr <= s_axi_awaddr;
end
endcase
end
//------------s_axi_araddr--------------
always @(posedge ui_clk) begin
case(state)
S_IDLE: begin
s_axi_araddr <= 31'd0;
end
S_RD_RESP: begin
if(s_axi_rvalid && s_axi_rready) begin
s_axi_araddr <= s_axi_araddr + DATA_NUM * 8;
end
else begin
s_axi_awaddr <= s_axi_awaddr;
end
end
default: begin
s_axi_araddr <= s_axi_araddr;
end
endcase
end
//------------wr_ddr3_req---------------
always @(posedge ui_clk) begin
case(state)
S_ARB: begin
if(w_fifo_rddat_cnt >= DATA_NUM) begin
wr_ddr3_req <= 1'b1; //w_FIFO中数据足够一次突发传输,发起写请求
end
else begin
wr_ddr3_req <= 1'b0;
end
end
default: begin
wr_ddr3_req <= 1'b0;
end
endcase
end
//------------rd_ddr3_req---------------
always @(posedge ui_clk) begin
case(state)
S_ARB: begin
if((FIFO_LEN - r_fifo_wrdat_cnt) > DATA_NUM) begin
rd_ddr3_req <= 1'b1; //r_FIFO中空闲位置足够一次突发传输,发起读请求
end
else begin
rd_ddr3_req <= 1'b0;
end
end
default: begin
rd_ddr3_req <= 1'b0;
end
endcase
end
endmodule
testbench 如下
`timescale 100ps/100ps
module DDR3_tb();
reg clk_200M = 1'b1;
always #25 begin
clk_200M <= ~clk_200M;
end
reg rst_n;
wire rst_busy;
//--------------------ddr3------------------------
wire [63:0] ddr3_dq; //DQ,4片 x16 DDR的数据线
wire [14:0] ddr3_addr; //Address
wire [2:0] ddr3_ba; //Bank Address
wire [7:0] ddr3_dqs_p; //DQ Select,0,2,4,6分别为四片DDR的DQSL(Lower Byte),
wire [7:0] ddr3_dqs_n; //1,3,5,7分别为四片DDR的DQSU(Upper Byte)
//Output with read data. Edge-aligned with read data.
//Input with write data. Center-aligned to write data.
wire [0:0] ddr3_ck_p; //4片DR3共用ck、cke、odt、cs信号,故[0:0]
wire [0:0] ddr3_ck_n;
//differential clock inputs. All control and address input signals are sampled
//on the crossing of the positive edge of CK and the negative edge of CK#
wire [7:0] ddr3_dm; //Input Data Mask,0,2,4,6为DML,1,3,5,7为DMU
wire [0:0] ddr3_cke; //Clock Enable
wire [0:0] ddr3_cs_n; //Chip Select
wire ddr3_ras_n; //Row Address Enable
wire ddr3_cas_n; //Column Address Enable
wire ddr3_we_n; //Write Enable
wire [0:0] ddr3_odt; //On-die termination enable
wire ddr3_reset_n;
//-------------FPGA FIFO Control-------------------
reg wr_en; //高电平有效
reg [63:0] wrdat = 64'd0;
wire full; //fifo_w full
reg rd_en; //高电平有效
wire [63:0] rddat;
wire empty; //fifo_r empty
//--------------------DDR3 Control-----------------------------------
DDR3_top DDR3_top_inst(
.clk_200M (clk_200M),
.rst_n (rst_n),
.rst_busy (rst_busy),
//--------------------ddr3------------------------
.ddr3_dq (ddr3_dq),
.ddr3_addr (ddr3_addr),
.ddr3_ba (ddr3_ba),
.ddr3_dqs_p (ddr3_dqs_p),
.ddr3_dqs_n (ddr3_dqs_n),
.ddr3_ck_p (ddr3_ck_p),
.ddr3_ck_n (ddr3_ck_n),
.ddr3_dm (ddr3_dm),
.ddr3_cke (ddr3_cke),
.ddr3_cs_n (ddr3_cs_n),
.ddr3_ras_n (ddr3_ras_n),
.ddr3_cas_n (ddr3_cas_n),
.ddr3_we_n (ddr3_we_n),
.ddr3_odt (ddr3_odt),
.ddr3_reset_n (ddr3_reset_n),
//-------------FPGA FIFO Control-------------------
.wr_en (wr_en),
.wrdat (wrdat),
.full (full),
.rd_en (rd_en),
.rddat (rddat),
.empty (empty)
);
//--------------------DDR3 Model-----------------------------------
ddr3_model ddr3_b1 (
.rst_n (ddr3_reset_n),
.ck (ddr3_ck_p),
.ck_n (ddr3_ck_n),
.cke (ddr3_cke),
.cs_n (ddr3_cs_n),
.ras_n (ddr3_ras_n),
.cas_n (ddr3_cas_n),
.we_n (ddr3_we_n),
.dm_tdqs (ddr3_dm[1:0]),
.ba (ddr3_ba),
.addr (ddr3_addr),
.dq (ddr3_dq[15:0]),
.dqs (ddr3_dqs_p[1:0]),
.dqs_n (ddr3_dqs_n[1:0]),
.tdqs_n (),
.odt (ddr3_odt)
);
ddr3_model ddr3_b2 (
.rst_n (ddr3_reset_n),
.ck (ddr3_ck_p),
.ck_n (ddr3_ck_n),
.cke (ddr3_cke),
.cs_n (ddr3_cs_n),
.ras_n (ddr3_ras_n),
.cas_n (ddr3_cas_n),
.we_n (ddr3_we_n),
.dm_tdqs (ddr3_dm[3:2]),
.ba (ddr3_ba),
.addr (ddr3_addr),
.dq (ddr3_dq[31:16]),
.dqs (ddr3_dqs_p[3:2]),
.dqs_n (ddr3_dqs_n[3:2]),
.tdqs_n (),
.odt (ddr3_odt)
);
ddr3_model ddr3_b3 (
.rst_n (ddr3_reset_n),
.ck (ddr3_ck_p),
.ck_n (ddr3_ck_n),
.cke (ddr3_cke),
.cs_n (ddr3_cs_n),
.ras_n (ddr3_ras_n),
.cas_n (ddr3_cas_n),
.we_n (ddr3_we_n),
.dm_tdqs (ddr3_dm[5:4]),
.ba (ddr3_ba),
.addr (ddr3_addr),
.dq (ddr3_dq[47:32]),
.dqs (ddr3_dqs_p[5:4]),
.dqs_n (ddr3_dqs_n[5:4]),
.tdqs_n (),
.odt (ddr3_odt)
);
ddr3_model ddr3_b4 (
.rst_n (ddr3_reset_n),
.ck (ddr3_ck_p),
.ck_n (ddr3_ck_n),
.cke (ddr3_cke),
.cs_n (ddr3_cs_n),
.ras_n (ddr3_ras_n),
.cas_n (ddr3_cas_n),
.we_n (ddr3_we_n),
.dm_tdqs (ddr3_dm[7:6]),
.ba (ddr3_ba),
.addr (ddr3_addr),
.dq (ddr3_dq[63:48]),
.dqs (ddr3_dqs_p[7:6]),
.dqs_n (ddr3_dqs_n[7:6]),
.tdqs_n (),
.odt (ddr3_odt)
);
//--------------------wrdat, rddat-----------------------------------
always @(posedge clk_200M) begin
if(wr_en) begin
wrdat <= wrdat + 1'b1;
end
else begin
wrdat <= wrdat;
end
end
//-----------------------tb----------------------------------------
initial begin
wr_en <= 1'b0;
rd_en <= 1'b0;
rst_n <= 1'b1;
#100;
rst_n <= 1'b0;
#100;
rst_n <= 1'b1;
wait(~rst_busy);
#200;
fork
begin: w_r
write(16);
#100;
write(16);
#100;
write(16);
#100;
// #10000;
read(16);
#100;
write(16);
#100;
read(64);
#2000;
disable shut_down;
$stop;
end
begin: shut_down
#100000;
disable w_r;
$stop;
end
join
end
task write;
input [7:0] num;
integer i;
begin
for(i=0; i<num; i=i) begin
wait(clk_200M);
if(~full) begin
wr_en <= 1'b1;
i <= i+1;
end
else begin
wr_en <= 1'b0;
end
wait(~clk_200M);
end
wait(clk_200M);
wr_en <= 1'b0;
end
endtask
task read;
input [7:0] num;
integer i;
begin
for(i=0; i<num; i=i) begin
wait(clk_200M);
if(~empty) begin
rd_en <= 1'b1;
i <= i+1;
end
else begin
rd_en <= 1'b0;
end
wait(~clk_200M);
end
wait(clk_200M);
rd_en <= 1'b0;
end
endtask
endmodule
仿真结果如下
放大 FIFO 读写部分的结果如下