文章目录
- 前言
- 一、接口转换模块设计
- 二、fifo2mig_axi 模块
- 二、接口转换模块仿真
- 四、fifo2mig_axi_tb
- 五、仿真展示
前言
结合串口接收模块和 tft 显示屏控制模块,设计一个基于 DDR3 的串口传图帧缓存系统。
提示:以下是本篇文章正文内容,下面案例可供参考
一、接口转换模块设计
fifo2mig_axi 模块是系统中相对比较重要的模块,涉及到与 DDR 控制器接口对接。该模块的主要是实现接口的转换,将普通的 FIFO 接口转换成 AXI 接口,用于将 FIFO 里的数据读出然后存储在 DDR 存储器以及将 DDR 存储器读出的数据存放到 FIFO 缓存。
AXI 接口包括 5 个通道,分为写事务和读事务。考虑模块设计实现的简单性(AXI 协议支持复杂的乱序读写操作等,这里就不做考虑),将一次完整的写事务流程规定为○1 主机向写地址通道写入地址和控制信息——>○2 写数据通道突发写入数据——>○3 收到设备的写数据响应。一次完整的读事务流程规定为○1 主机向读地址通道写入地址和控制信息——>○2 收到设备的读数据响应和读的数据。对于 DDR 控制器 mig_7series_0 模块,需要等到 init_calib_complete 为高后,才能进行
读/写操作。读/写操作不可同时进行,对读/写操作就需要有一个判断仲裁的过程,fifo2mig_axi 模块状态机设计如下图所示。
上电初始状态为 IDLE 状态,当 DDR 完成初始化和校准(即 init_calib_complete 变为高电平)后进入读/写仲裁状态 ARB;在该状态根据是否有读/写操作请求跳转到读/写流程的各个状态;完成一次读/写流程后,状态回到 ARB 状态进行下一次的操作。状态机采用三段式,第一二段的代码如下。
always@(posedge ui_clk or posedge ui_clk_sync_rst)
begin
if(ui_clk_sync_rst)
curr_state <= S_IDLE;
else
curr_state <= next_state;
end
always@(*)
begin
case(curr_state)
//具体状态转移见下
endcase
end
用户侧逻辑的时钟和复位信号采用的 DDR 控制器 mig_7series_0 模块输出的 ui_clk 和
ui_clk_sync_rst。状态机在上电复位处于初始状态 IDLE,当 DDR 完成初始化和校准后进入
读/写仲裁状态 ARB,具体代码如下。
S_IDLE:
begin
if(init_calib_complete)
next_state = S_ARB;
else
next_state = S_IDLE;
end
在读/写仲裁状态 ARB,根据当前的读/写请求跳转到读/写操作流程中的地址通道的操作。这里为了设计简单化,将写操作优先级高于读优先级。
S_ARB:
begin
if(wr_ddr3_req == 1'b1)
next_state = S_WR_ADDR;
else if(rd_ddr3_req == 1'b1)
next_state = S_RD_ADDR;
else
next_state = S_ARB;
end
当在 ARB 状态出现写操作请求后,进入到 AXI 写地址通道的操作状态 S_WR_ADDR,在该状态,传输写操作的地址和控制信息,当 awready 和 awvalid 同时为高时表明地址已经传输完成,进入到写操作的写数据通道的操作。
S_WR_ADDR:
begin
if(m_axi_awready && m_axi_awvalid)
next_state = S_WR_DATA;
else
next_state = S_WR_ADDR;
end
在对写操作的写数据通道的操作中,当主机写完最后一个数据后,进入到等待写响应的状态。
S_WR_DATA:
begin
if(m_axi_wready && m_axi_wvalid && m_axi_wlast)
next_state = S_WR_RESP;
else
next_state = S_WR_DATA;
end
在等待写响应状态,当主机接收到设备的写响应后,一次完整的写操作流程完成,状态回到仲裁状态进行下一次的操作,bresp 不同值表示不同的响应结果,bresp 为 2’b00 表示写数据成功,bid 需要与写地址通道传输的 awbid 一致。
S_WR_RESP:
begin
if(m_axi_bready && m_axi_bvalid && (m_axi_bresp == 2'b00) && (m_axi_bid == AXI_ID))
next_state = S_ARB;
else
next_state = S_WR_RESP;
end
当在 ARB 状态出现读操作请求(此时无写请求)后,进入到 AXI 读地址通道的操作状态 S_RD_ADDR,在该状态,传输读操作的地址和控制信息,当 arready 和 arvalid 同时为高
时表明地址已经传输完成,进入到读操作的读响应通道的操作。
S_RD_ADDR:
begin
if(m_axi_arready && m_axi_arvalid)
next_state = S_RD_RESP;
else
next_state = S_RD_ADDR;
end
在等待读响应状态,当主机接收到设备的读响应后,一次完整的读操作流程完成,状态
回到仲裁状态进行下一次的操作,bresp 不同值表示不同的响应结果,bresp 为 2’b00 表示读
数据成功,last 表示读取的最后一个数据的标识,rid 需要与读地址通道传输的 arbid 一致。
S_RD_RESP:
begin
if(m_axi_rready && m_axi_rvalid && m_axi_rlast && (m_axi_rresp == 2'b00) && (m_axi_rid ==
AXI_ID))
next_state = S_ARB;
else
next_state = S_RD_RESP;
end
状态机设计完成后,剩下的就是在各个状态中产生各种信号。对于 AXI 接口的一些信号,在写操作的写地址通道比较关键的是产生 awaddr 和 awvalid。其中,awaddr 除了在复位和清除时变为起始地址外,在完成一次写操作流程后,地址就需要增加一次突发写入的数据量,需要注意的是这里的地址是以字节为单位的,则每次地址增加量应该是突发写数据个数*每个数据的字节数。这里每次突发长读为 AWLEN 加 1,每个数据是 16 字节(数据位宽是128bit),所以每完成一次写操作,地址增加(m_axi_awlen + 1’b1)*16。
always@(posedge ui_clk or posedge ui_clk_sync_rst)
begin
if(ui_clk_sync_rst)
m_axi_awaddr <= WR_DDR_ADDR_BEGIN;
else if(wr_addr_clr)
m_axi_awaddr <= WR_DDR_ADDR_BEGIN;
else if(m_axi_awaddr >= WR_DDR_ADDR_END)
m_axi_awaddr <= WR_DDR_ADDR_BEGIN;
else if((curr_state == S_WR_RESP) && m_axi_bready && m_axi_bvalid && (m_axi_bresp == 2'b00)
&& (m_axi_bid == AXI_ID))
m_axi_awaddr <= m_axi_awaddr + ((m_axi_awlen + 1'b1)<<4);
else
m_axi_awaddr <= m_axi_awaddr;
end
对于 awvalid 产生就相对简单些,在进入 WR_ADDR 状态到就将其输出为高,等到awready 和 awvalid 同时高的时候,就将 awvalid 输出为低,保证 awready 和 awvalid 信号只有一个时钟周期的同时高。
always@(posedge ui_clk or posedge ui_clk_sync_rst)
begin
if(ui_clk_sync_rst)
m_axi_awvalid <= 1'b0;
else if((curr_state == S_WR_ADDR) && m_axi_awready && m_axi_awvalid)
m_axi_awvalid <= 1'b0;
else if(curr_state == S_WR_ADDR && wr_fifo_empty)
m_axi_awvalid <= 1'b1;
else
m_axi_awvalid <= m_axi_awvalid;
end
在写操作的写数据通道比较关键的是产生 wvalid 和 wlast 信号。wvalid 在进入到WR_DATA 状态就变为高电平,在发送完最后一个数据后变为低电平。(期间,在主机给出的 wvalid 与设备给出的 wready 信号同时为高的情况下,数据写入到设备)
always@(posedge ui_clk or posedge ui_clk_sync_rst)
begin
if(ui_clk_sync_rst)
m_axi_wvalid <= 1'b0;
else if((curr_state == S_WR_DATA) && m_axi_wready && m_axi_wvalid && m_axi_wlast)
m_axi_wvalid <= 1'b0;
else if(curr_state == S_WR_DATA)
m_axi_wvalid <= 1'b1;
else
m_axi_wvalid <= m_axi_wvalid;
end
wlast 信号是主机向设备传输最后一个数据的标识信号,这个信号的产生依赖于一次突发写入数据个数和当前已经传输了几个数据,主机在传输最后一个数据同时将其输出为高,在发送完最后一个数据后立马将其输出为低。这个过程首先需要对传输数据个数进行计数,当 wready 和 m_axi_wvalid 同时为高时代表传输一个数据,传输数据个数计数器代码如下。
always@(posedge ui_clk or posedge ui_clk_sync_rst)
begin
if(ui_clk_sync_rst)
wr_data_cnt <= 1'b0;
else if(curr_state == S_ARB)
wr_data_cnt <= 1'b0;
else if(curr_state == S_WR_DATA && m_axi_wready && m_axi_wvalid)
wr_data_cnt <= wr_data_cnt + 1'b1;
else
wr_data_cnt <= wr_data_cnt;
end
在产生 wlast 时,分两种情况,一是当突发写数据个数为 1,也就是 wlen 等于 0 时,那么传输的第一个数就是传输的最后一个数据,这种情况下,一进入到 WR_DATA 状态就将wlast 变为高电平;二是当突发写数据个数大于 1,也就是 wlen 等于等于 1 时,就在传输完倒数第二个数(即 wr_data_cnt 为 m_axi_awlen -1’b1)后将 wlast 变为高电平。当最后一个数据传输完成(m_axi_wready、m_axi_wvalid 和 m_axi_wlast 同时为高电平)后将 wlast 变为低,具体代码如下。
always@(posedge ui_clk or posedge ui_clk_sync_rst)
begin
if(ui_clk_sync_rst)
m_axi_wlast <= 1'b0;
else if(curr_state == S_WR_DATA && m_axi_wready && m_axi_wvalid && m_axi_wlast)
m_axi_wlast <= 1'b0;
else if(curr_state == S_WR_DATA && m_axi_awlen == 8'd0)
m_axi_wlast <= 1'b1;
else if(curr_state == S_WR_DATA && m_axi_wready && m_axi_wvalid && (wr_data_cnt ==
m_axi_awlen -1'b1))
m_axi_wlast <= 1'b1;
else
m_axi_wlast <= m_axi_wlast;
end
在读操作的写地址通道信号的产生上与写操作类似。比较关键的是产生 araddr 和 arvalid;
产生过程与 awaddr 和 awvalid 类似。awaddr 除了在复位和清除时变为起始地址外,在完成
一次读操作流程后,地址就需要增加一次突发写入的数据量。即每完成一次读操作,地址增
加(m_axi_arlen + 1’b1)*16,计算与写操作一样。具体代码如下。
//m_axi_araddr
always@(posedge ui_clk or posedge ui_clk_sync_rst)
begin
if(ui_clk_sync_rst)
m_axi_araddr <= RD_DDR_ADDR_BEGIN;
else if(rd_addr_clr)
m_axi_araddr <= RD_DDR_ADDR_BEGIN;
else if(m_axi_araddr >= RD_DDR_ADDR_END)
m_axi_araddr <= RD_DDR_ADDR_BEGIN;
else if((curr_state == S_RD_RESP) && m_axi_rready && m_axi_rvalid && m_axi_rlast &&
(m_axi_rresp == 2'b00) && (m_axi_rid == AXI_ID))
m_axi_araddr <= m_axi_araddr + ((m_axi_awlen + 1'b1)<<4);
else
m_axi_araddr <= m_axi_araddr;
end
//m_axi_arvalid
always@(posedge ui_clk or posedge ui_clk_sync_rst)
begin
if(ui_clk_sync_rst)
m_axi_arvalid <= 1'b0;
else if((curr_state == S_RD_ADDR) && m_axi_arready && m_axi_arvalid)
m_axi_arvalid <= 1'b0;
else if(curr_state == S_RD_ADDR)
m_axi_arvalid <= 1'b1;
else
m_axi_arvalid <= m_axi_arvalid;
end
对于 AXI 接口信号,除了上述信号外,还有一些信号相对比较简单,基本就是给固定值就可以,这里直接给出代码。
assign m_axi_awid = AXI_ID ; //output [3:0] m_axi_awid
assign m_axi_awsize = 3'b100 ; //output [2:0] m_axi_awsize
assign m_axi_awburst = 2'b01 ; //output [1:0] m_axi_awburst
assign m_axi_awlock = 1'b0 ; //output [0:0] m_axi_awlock
assign m_axi_awcache = 4'b0000 ; //output [3:0] m_axi_awcache
assign m_axi_awprot = 3'b000 ; //output [2:0] m_axi_awprot
assign m_axi_awqos = 4'b0000 ; //output [3:0] m_axi_awqos
assign m_axi_awlen = AXI_LEN ;
assign m_axi_wstrb = 16'hffff ; //output [15:0] m_axi_wstrb
assign m_axi_wdata = wr_fifo_rddata;
assign m_axi_bready = 1'b1 ; //output m_axi_bready
assign m_axi_arid = AXI_ID ; //output [3:0] m_axi_arid
assign m_axi_arsize = 3'b100 ; //output [2:0] m_axi_arsize
assign m_axi_arburst = 2'b01 ; //output [1:0] m_axi_arburst
assign m_axi_arlock = 1'b0 ; //output [0:0] m_axi_arlock
assign m_axi_arcache = 4'b0000 ; //output [3:0] m_axi_arcache
assign m_axi_arprot = 3'b000 ; //output [2:0] m_axi_arprot
assign m_axi_arqos = 4'b0000 ; //output [3:0] m_axi_arqos
assign m_axi_arlen = AXI_LEN ;
assign m_axi_rready = ~rd_fifo_alfull; //output m_axi_rready
关于 AXI 接口的信号设计完后,剩下的就是与读写 FIFO 之间的接口,包括读写使能和读写数据。
assign wr_fifo_rdreq = m_axi_wvalid && m_axi_wready;
assign rd_fifo_wrreq = m_axi_rvalid && m_axi_rready;
assign rd_fifo_wrdata = m_axi_rdata;
控制状态机条状的读写 DDR 请求信号是根据当前 FIFO 中数据量进行判断产生,当写FIFO 中的数据量超过一个阈值(这里阈值使用 WLEN,)就产生写 DDR 请求;当读 FIFO中的数据量低于一个阈值(这里阈值使用 RLEN,也可以设置为其他值)就产生读 DDR 请求,信号产生需要满足在 DDR 初始化完成之后并且当前 FIFO 不处于复位。
assign wr_ddr3_req = (init_calib_complete == 1'b1) && (wr_fifo_rst_busy == 1'b0) &&
(wr_fifo_rd_cnt >= m_axi_awlen);
assign rd_ddr3_req = (init_calib_complete == 1'b1) && (rd_fifo_rst_busy == 1'b0) && (rd_fifo_wr_cnt <
m_axi_arlen);
至此关于 fifo2mig_axi 模块的设计就完成,上述设计的完整代码可参考 fifo2mig_axi 模块。
二、fifo2mig_axi 模块
代码如下(示例):
/
// Module Name : fifo2mig_axi
// Description : fifo接口到MIG IP AXI接口的转换模块
// Revision:vivado 2018.3
// designer: 小王在努力...
//
/
module fifo2mig_axi
#(
parameter WR_DDR_ADDR_BEGIN = 0 ,
parameter WR_DDR_ADDR_END = 200 ,
parameter RD_DDR_ADDR_BEGIN = 0 ,
parameter RD_DDR_ADDR_END = 200 ,
parameter AXI_ID = 4'b0000,
parameter AXI_LEN = 8'd31 //burst length = 32
)
(
//FIFO Interface ports
input wr_addr_clr ,//sync ui_clk(rd_ddr3_fifo中的rst)
output wr_fifo_rdreq ,//wr_ddr3_fifo(rd_en)
input [127:0] wr_fifo_rddata ,//wr_ddr3_fifo(dout)
input wr_fifo_empty ,
input [8:0] wr_fifo_rd_cnt ,
input wr_fifo_rst_busy ,//wr_ddr3_fifo(wrfifo_wr_rst_busy | wrfifo_rd_rst_busy)
input rd_addr_clr ,//rst
output rd_fifo_wrreq ,//rd_ddr3_fifo(wr_en)
output [127:0] rd_fifo_wrdata ,
input rd_fifo_alfull ,//rd_ddr3_fifo.full
input [8:0] rd_fifo_wr_cnt ,
input rd_fifo_rst_busy ,
//以下是MIG IP的接口
// Application interface ports
input ui_clk ,
input ui_clk_sync_rst ,
input mmcm_locked ,
input init_calib_complete ,
// Slave Interface Write Address Ports
output [3:0] m_axi_awid ,
output reg[27:0] m_axi_awaddr ,
output [7:0] m_axi_awlen ,
output [2:0] m_axi_awsize ,
output [1:0] m_axi_awburst ,
output [0:0] m_axi_awlock ,
output [3:0] m_axi_awcache ,
output [2:0] m_axi_awprot ,
output [3:0] m_axi_awqos ,
output reg m_axi_awvalid ,
input m_axi_awready ,
// Slave Interface Write Data Ports
output [127:0] m_axi_wdata ,
output [15:0] m_axi_wstrb ,
output reg m_axi_wlast ,
output reg m_axi_wvalid ,
input m_axi_wready ,
// Slave Interface Write Response Ports
input [3:0] m_axi_bid ,
input [1:0] m_axi_bresp ,
input m_axi_bvalid ,
output m_axi_bready ,
// Slave Interface Read Address Ports
output [3:0] m_axi_arid ,
output reg[27:0] m_axi_araddr ,
output [7:0] m_axi_arlen ,
output [2:0] m_axi_arsize ,
output [1:0] m_axi_arburst ,
output [0:0] m_axi_arlock ,
output [3:0] m_axi_arcache ,
output [2:0] m_axi_arprot ,
output [3:0] m_axi_arqos ,
output reg m_axi_arvalid ,
input m_axi_arready ,
// Slave Interface Read Data Ports
input [3:0] m_axi_rid ,
input [127:0] m_axi_rdata ,
input [1:0] m_axi_rresp ,
input m_axi_rlast ,
input m_axi_rvalid ,
output m_axi_rready
);
//------------------------------------------
//状态机参数
localparam S_IDLE = 7'b0000001,
S_ARB = 7'b0000010,
S_WR_ADDR = 7'b0000100,
S_WR_DATA = 7'b0001000,
S_WR_RESP = 7'b0010000,
S_RD_ADDR = 7'b0100000,
S_RD_RESP = 7'b1000000;
//------------------------------------------
wire[7:0]wr_req_cnt_thresh;
wire[7:0]rd_req_cnt_thresh;
wire wr_ddr3_req ;
wire rd_ddr3_req ;
reg [6:0]curr_state ;
reg [6:0]next_state ;
reg wr_rd_poll ; //0:allow wr 1:allow rd
reg [7:0]wr_data_cnt ;
assign m_axi_awid = AXI_ID ; //output [3:0] m_axi_awid
//每写一个数据为2^m_axi_awsize个字节即此处为16字节128位
assign m_axi_awsize = 3'b100 ; //output [2:0] m_axi_awsize
//------------------------------------------
//m_axi_awburst label meaning
// 2'b00 FIXED 每次传输的地址是固定的,即为 S_AXI_AWADDR.该类型主要用于读或者清空 FIFO.
// 2'b01 INCR 每次传输的地址等于上一次传输的地址加上传输的大小(即S_AXI_AWSIZE信号所给出的传输的字节数)
assign m_axi_awburst = 2'b01 ; //output [1:0] m_axi_awburst
//-------------------------------------------
//-------------------------------------------
assign m_axi_awlock = 1'b0 ; //output [0:0] m_axi_awlock
assign m_axi_awcache = 4'b0000 ; //output [3:0] m_axi_awcache
assign m_axi_awprot = 3'b000 ; //output [2:0] m_axi_awprot
assign m_axi_awqos = 4'b0000 ; //output [3:0] m_axi_awqos
//上述接口为本地接口维护命令信号,这几个信号可以不用使用,输入信号直接给 0,输出信号不连接其他信号.
//---------------------------------------------
assign m_axi_awlen = AXI_LEN ;
//wstrb 等于 16'hffff,表示写入的 16 个字节全部有效.
assign m_axi_wstrb = 16'hffff ; //output [15:0] m_axi_wstrb
//ddr写数据128位由写fifo的读数据128直接给
assign m_axi_wdata = wr_fifo_rddata;
//接受写响应就绪.该信号表示主机已经能够接受响应信息. 1 = 主机就绪 0 = 主机未就绪
assign m_axi_bready = 1'b1 ; //output m_axi_bready
//rid 需要与读地址通道传输的 arbid 一致
assign m_axi_arid = AXI_ID ; //output [3:0] m_axi_arid
//每读一个数据为2^m_axi_arsize个字节即此处为16字节128位
assign m_axi_arsize = 3'b100 ; //output [2:0] m_axi_arsize
//每次传输的地址等于上一次传输的地址加上传输的大小(即S_AXI_ARSIZE信号所给出的传输的字节数)
assign m_axi_arburst = 2'b01 ; //output [1:0] m_axi_arburst
//---------------------------------------------
assign m_axi_arlock = 1'b0 ; //output [0:0] m_axi_arlock
assign m_axi_arcache = 4'b0000 ; //output [3:0] m_axi_arcache
assign m_axi_arprot = 3'b000 ; //output [2:0] m_axi_arprot
assign m_axi_arqos = 4'b0000 ; //output [3:0] m_axi_arqos
//上述接口为本地接口维护命令信号,这几个信号可以不用使用,输入信号直接给 0,输出信号不连接其他信号.
//---------------------------------------------
//数据突发长度即一次写入的数据为m_axi_arlen+1个 字节数为(m_axi_arlen+1)*2^m_axi_arsize
assign m_axi_arlen = AXI_LEN ;
//rd_fifo_alfull = rd_ddr3_fifo.full
assign m_axi_rready = ~rd_fifo_alfull; //output m_axi_rready
//wr_fifo_rdreq = wr_ddr3_fifo(rd_en)
assign wr_fifo_rdreq = m_axi_wvalid && m_axi_wready;
//rd_fifo_wrreq =rd_ddr3_fifo(wr_en)
assign rd_fifo_wrreq = m_axi_rvalid && m_axi_rready;
//读fifo写入数据由ddr3读数据直接给入
assign rd_fifo_wrdata = m_axi_rdata;
assign wr_req_cnt_thresh = (m_axi_awlen == 1'b0)? 1'b0 : AXI_LEN-1'b1;
assign rd_req_cnt_thresh = AXI_LEN;
assign wr_ddr3_req = (wr_fifo_rst_busy == 1'b0) && (wr_fifo_rd_cnt >= wr_req_cnt_thresh) ? 1'b1:1'b0;
assign rd_ddr3_req = (rd_fifo_rst_busy == 1'b0) && (rd_fifo_wr_cnt <= rd_req_cnt_thresh) ? 1'b1:1'b0;
//m_axi_awaddr
always@(posedge ui_clk or posedge ui_clk_sync_rst)
begin
if(ui_clk_sync_rst)
m_axi_awaddr <= WR_DDR_ADDR_BEGIN; //WR_DDR_ADDR_BEGIN = 0;
else if(wr_addr_clr) //rd_ddr3_fifo中的rst
m_axi_awaddr <= WR_DDR_ADDR_BEGIN;
else if(m_axi_awaddr >= WR_DDR_ADDR_END)
m_axi_awaddr <= WR_DDR_ADDR_BEGIN;
else if((curr_state == S_WR_RESP) && m_axi_bready && m_axi_bvalid && (m_axi_bresp == 2'b00) && (m_axi_bid == AXI_ID))
m_axi_awaddr <= m_axi_awaddr + ((m_axi_awlen + 1'b1)<<4);
else
m_axi_awaddr <= m_axi_awaddr;
end
//m_axi_awvalid
always@(posedge ui_clk or posedge ui_clk_sync_rst)
begin
if(ui_clk_sync_rst)
m_axi_awvalid <= 1'b0;
else if((curr_state == S_WR_ADDR) && m_axi_awready && m_axi_awvalid)
m_axi_awvalid <= 1'b0;
else if(curr_state == S_WR_ADDR)
m_axi_awvalid <= 1'b1;
else
m_axi_awvalid <= m_axi_awvalid;
end
//m_axi_wvalid
always@(posedge ui_clk or posedge ui_clk_sync_rst)
begin
if(ui_clk_sync_rst)
m_axi_wvalid <= 1'b0;
else if((curr_state == S_WR_DATA) && m_axi_wready && m_axi_wvalid && m_axi_wlast)
m_axi_wvalid <= 1'b0;
else if(curr_state == S_WR_DATA)
m_axi_wvalid <= 1'b1;
else
m_axi_wvalid <= m_axi_wvalid;
end
//wr_data_cnt
//用于计数传输的数据,控制m_axi_wlast的产生
always@(posedge ui_clk or posedge ui_clk_sync_rst)
begin
if(ui_clk_sync_rst)
wr_data_cnt <= 1'b0;
else if(curr_state == S_ARB)
wr_data_cnt <= 1'b0;
else if(curr_state == S_WR_DATA && m_axi_wready && m_axi_wvalid)
wr_data_cnt <= wr_data_cnt + 1'b1;
else
wr_data_cnt <= wr_data_cnt;
end
//m_axi_wlast
//--------------------
//在产生 wlast 时,分两种情况,一是当突发写数据个数为 1,也就是 wlen 等于 0 时,
//那么传输的第一个数就是传输的最后一个数据,这种情况下,一进入到 WR_DATA 状态就将
//wlast 变为高电平;二是当突发写数据个数大于 1,也就是 wlen 等于等于 1 时,就在传输完
//倒数第二个数(即 wr_data_cnt 为 m_axi_awlen -1'b1)后将 wlast 变为高电平.当最后一个
//数据传输完成(m_axi_wready、m_axi_wvalid 和 m_axi_wlast 同时为高电平)后将 wlast 变为低
//--------------------
always@(posedge ui_clk or posedge ui_clk_sync_rst)
begin
if(ui_clk_sync_rst)
m_axi_wlast <= 1'b0;
else if(curr_state == S_WR_DATA && m_axi_wready && m_axi_wvalid && m_axi_wlast)
m_axi_wlast <= 1'b0;
else if(curr_state == S_WR_DATA && m_axi_awlen == 8'd0)
m_axi_wlast <= 1'b1;
else if(curr_state == S_WR_DATA && m_axi_wready && m_axi_wvalid && (wr_data_cnt == m_axi_awlen -1'b1))
m_axi_wlast <= 1'b1;
else
m_axi_wlast <= m_axi_wlast;
end
//m_axi_araddr
always@(posedge ui_clk or posedge ui_clk_sync_rst)
begin
if(ui_clk_sync_rst)
m_axi_araddr <= RD_DDR_ADDR_BEGIN;
else if(rd_addr_clr)
m_axi_araddr <= RD_DDR_ADDR_BEGIN;
else if(m_axi_araddr >= RD_DDR_ADDR_END)
m_axi_araddr <= RD_DDR_ADDR_BEGIN;
else if((curr_state == S_RD_RESP) && m_axi_rready && m_axi_rvalid && m_axi_rlast && (m_axi_rresp == 2'b00) && (m_axi_rid == AXI_ID))
m_axi_araddr <= m_axi_araddr + ((m_axi_awlen + 1'b1)<<4);
else
m_axi_araddr <= m_axi_araddr;
end
//m_axi_arvalid
always@(posedge ui_clk or posedge ui_clk_sync_rst)
begin
if(ui_clk_sync_rst)
m_axi_arvalid <= 1'b0;
else if((curr_state == S_RD_ADDR) && m_axi_arready && m_axi_arvalid)
m_axi_arvalid <= 1'b0;
else if(curr_state == S_RD_ADDR)
m_axi_arvalid <= 1'b1;
else
m_axi_arvalid <= m_axi_arvalid;
end
always@(posedge ui_clk or posedge ui_clk_sync_rst)
begin
if(ui_clk_sync_rst)
wr_rd_poll <= 1'b0;
else if(curr_state == S_ARB)
wr_rd_poll <= ~wr_rd_poll;
else
wr_rd_poll <= wr_rd_poll;
end
//**********************************
//state machine
//**********************************
always@(posedge ui_clk or posedge ui_clk_sync_rst)
begin
if(ui_clk_sync_rst)
curr_state <= S_IDLE;
else
curr_state <= next_state;
end
always@(*)
begin
case(curr_state)
S_IDLE:
begin
if(mmcm_locked && init_calib_complete)
next_state = S_ARB;
else
next_state = S_IDLE;
end
//-------------
//wr_rst_busy 和 rd_rst_busy 两个信
//号输出,这两个信号分别表示的是写/读时钟域复位忙信号(为 1 表示忙,处于复位中,为 0
//表示复位完),所以每次给一个异步复位信号对 FIFO 进行复位时,需要等到 wr_rst_busy 从
//1 变为 0 后才能对 FIFO 进行写数据操作(在 FIFO 非满情况下,这个是任何写操作时候都
//需要满足的),在 wr_rst_busy 为 1 时进行读是不允许的;同样的要等到 rd_rst_busy 从 1 变
//为 0 后才能对 FIFO 进行读操作,在 rd_rst_busy 为 1 时进行读是不允许的
//--------------
//assign wr_ddr3_req = (wr_fifo_rst_busy == 1'b0) && (wr_fifo_rd_cnt >= wr_req_cnt_thresh) ? 1'b1:1'b0;
//wr_fifo_rst_busy = wrfifo_wr_rst_busy | wrfifo_rd_rst_busy
//wr_fifo_rd_cnt = {3'd0,wrfifo_rd_cnt}
//wrfifo_rd_cnt = wr_ddr3_fifo.rd_data_count
//wr_req_cnt_thresh = (m_axi_awlen == 1'b0)? 1'b0 : AXI_LEN-1'b1;
//AXI_LEN = m_axi_arlen
//-------------
//rd_ddr3_req = (rd_fifo_rst_busy == 1'b0) && (rd_fifo_wr_cnt <= rd_req_cnt_thresh) ? 1'b1:1'b0;
//rd_fifo_rst_busy = rdfifo_wr_rst_busy | rdfifo_rd_rst_busy
//rd_fifo_wr_cnt = {3'd0,rdfifo_wr_cnt}
//rdfifo_wr_cnt = rd_ddr3_fifo.wr_data_count
//rd_req_cnt_thresh = AXI_LEN;
//--------------
S_ARB:
begin
if((wr_ddr3_req == 1'b1) && (wr_rd_poll == 1'b0))
next_state = S_WR_ADDR;
else if((rd_ddr3_req == 1'b1) && (wr_rd_poll == 1'b1))
next_state = S_RD_ADDR;
else
next_state = S_ARB;
end
S_WR_ADDR:
begin
if(m_axi_awready && m_axi_awvalid)
next_state = S_WR_DATA;
else
next_state = S_WR_ADDR;
end
S_WR_DATA:
begin
if(m_axi_wready && m_axi_wvalid && m_axi_wlast)
next_state = S_WR_RESP;
else
next_state = S_WR_DATA;
end
//m_axi_arid = AXI_ID
//m_axi_awid = AXI_ID
S_WR_RESP:
begin
if(m_axi_bready && m_axi_bvalid && (m_axi_bresp == 2'b00) && (m_axi_bid == AXI_ID))
next_state = S_ARB;
else if(m_axi_bready && m_axi_bvalid)
next_state = S_IDLE;
else
next_state = S_WR_RESP;
end
S_RD_ADDR:
begin
if(m_axi_arready && m_axi_arvalid)
next_state = S_RD_RESP;
else
next_state = S_RD_ADDR;
end
S_RD_RESP:
begin
if(m_axi_rready && m_axi_rvalid && m_axi_rlast && (m_axi_rresp == 2'b00) && (m_axi_rid == AXI_ID))
next_state = S_ARB;
else if(m_axi_rready && m_axi_rvalid && m_axi_rlast)
next_state = S_IDLE;
else
next_state = S_RD_RESP;
end
default: next_state = S_IDLE;
endcase
end
endmodule
二、接口转换模块仿真
将上面 fifo2mig_axi 模块设计内容进行分析和综合直至没有错误以及警告。新建名称为
fifo2mig_axi_tb 的仿真文件。为了使仿真更加贴近我们的系统设计,仿真 testbench 设计的结
构框图如下。
这里仿真文件中除了需要例化 fifo2mig_axi 模块,还需要分别例化 mig_7series_0 模块、
wr_ddr3_fifo 模块、rd_ddr3_fifo 模块以及 DDR3 仿真模型。其中 3 个 IP 在前面工程建立后
就已经创建好。DDR3 的仿真模型可以在 DDR 控制器 Example Design 工程中 Source 窗口中
Simulation Sources 下找到 ddr3_model.sv 和其对应的参数配置文件 ddr3_model_parameters.vh,到工程目录下对应文件目录下找到这两个文件并复制到本节的工程中并进行添加。
除此之外,只需产生时钟和复位、数据产生和数据读取的激励。为了简单处理,仿真时
钟除了 DDR 控制器时钟采用产生 200MHz 时钟 sys_clk_i,其他模块时钟就统一使用 DDR
控制器输出的供用户侧使用的 ui_clk 时钟。200MHz 时钟 sys_clk_i 的时钟周期 5ns,即每
2.5ns翻转一次,时钟产生代码如下。注意这里仿真的时间单位和精度分别使用1ns和100ps,
即使用`timescale 1ns/100ps。
initial sys_clk_i = 1'b1;
always #2.5 sys_clk_i = ~sys_clk_i;
数据产生的激励设计上将其封装成任务 task 形式,方便调用。具体代码如下。该任务的
具体功能是在调用这个任务和给定的输入参数 data_begin 和 wr_data_cnt 时,向 wr_ddr3_fifo
中写入以起始数据 data_begin 开始递增的 wr_data_cnt 个数据。
task wr_data;
input [15:0]data_begin;
input [15:0]wr_data_cnt;
begin
wrfifo_wren = 1'b0;
wrfifo_din = data_begin;
@(posedge ui_clk);
#1 wrfifo_wren = 1'b1;
repeat(wr_data_cnt)
begin
@(posedge ui_clk);
wrfifo_din = wrfifo_din + 1'b1;
end
#1 wrfifo_wren = 1'b0;
end
endtask
数据读取的激励设计上采用与数据产生类似的方式,封装成任务 task 形式,具体代码
如下。任务的具体功能是在调用这个任务和给定的输入参数 rd_data_cnt 时,向 rd_ddr3_fifo
中读出个 rd_data_cnt 数据。
task rd_data;
input [15:0]rd_data_cnt;
begin
rdfifo_rden = 1'b0;
@(posedge ui_clk);
#1 rdfifo_rden = 1'b1;
repeat(rd_data_cnt)
begin
@(posedge ui_clk);
end
#1 rdfifo_rden = 1'b0;
end
endtask
仿真设计上整体流程是,先产生 DDR 控制器的复位以及 FIFO 的复位。等 DDR 控制器
内部锁相环锁定后,延时 200ns 后对 FIFO 解复位。等待 DDR 初始化校准完成后,延时 200n
往 wr_ddr3_fifo 写入 1024 个数据,数据写完后,对 rd_ddr3_fifo 进行复位,清空里面的缓
存,等 FIFO 复位结束一段时间后,开始对 rd_ddr3_fifo 进行读取数据,读取 1024 个数据。
具体代码如下。
initial begin
sys_rst = 1'b0;
aresetn = 1'b0;
wrfifo_clr = 1'b1;
wrfifo_wren = 1'b0;
wrfifo_din = 8'd0;
rdfifo_clr = 1'b1;
rdfifo_rden = 1'b0;
#201;
sys_rst = 1'b1;
aresetn = 1'b1;
@(posedge mmcm_locked);
#200;
wrfifo_clr = 1'b0;
rdfifo_clr = 1'b0;
@(posedge init_calib_complete);
#200;
wr_data(16'd100,16'd1024);
#2000;
rdfifo_clr = 1'b1;
#20;
rdfifo_clr = 1'b0;
#2000;
rd_data(16'd1024);
#5000;
$stop;
end
至此关于 fifo2mig_axi _tb模块的设计就完成,上述设计的完整代码可参考 fifo2mig_axi_tb 模块。
四、fifo2mig_axi_tb
/
// Company : 武汉芯路恒科技有限公司
// http://xiaomeige.taobao.com
// Web : http://www.corecourse.cn
//
// Create Date : 2019/05/01 00:00:00
// Module Name : fifo2mig_axi_tb
// Description : fifo2mig_axi模块仿真文件
//
// Dependencies :
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
/
`timescale 1ns/100ps
module fifo2mig_axi_tb;
reg aresetn;
reg sys_clk_i;
reg sys_rst;
//wr_fifo Interface
reg wrfifo_clr;
reg [15:0] wrfifo_din;
reg wrfifo_wren;
wire wrfifo_rden;
wire [127:0] wrfifo_dout;
wire [5 : 0] wrfifo_rd_cnt;
wire wrfifo_empty;
wire wrfifo_wr_rst_busy;
wire wrfifo_rd_rst_busy;
//rd_fifo Interface
reg rdfifo_clr;
wire rdfifo_wren;
wire [127:0] rdfifo_din;
reg rdfifo_rden;
wire [15 :0] rdfifo_dout;
wire [5 : 0] rdfifo_wr_cnt;
wire rdfifo_full;
wire rdfifo_wr_rst_busy;
wire rdfifo_rd_rst_busy;
//mig Interface
wire ui_clk;
wire ui_clk_sync_rst;
wire mmcm_locked;
wire init_calib_complete;
wire[3:0] s_axi_awid;
wire[27:0] s_axi_awaddr;
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;
wire s_axi_awvalid;
wire s_axi_awready;
wire[127:0] s_axi_wdata;
wire[15:0] s_axi_wstrb;
wire s_axi_wlast;
wire s_axi_wvalid;
wire s_axi_wready;
wire [3:0] s_axi_bid;
wire [1:0] s_axi_bresp;
wire s_axi_bvalid;
wire s_axi_bready;
wire[3:0] s_axi_arid;
wire[27: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;
wire s_axi_arvalid;
wire s_axi_arready;
wire [3:0] s_axi_rid;
wire [127:0] s_axi_rdata;
wire [1:0] s_axi_rresp;
wire s_axi_rlast;
wire s_axi_rvalid;
wire s_axi_rready;
wire [13:0] ddr3_addr;
wire [2:0] ddr3_ba;
wire ddr3_cas_n;
wire [0:0] ddr3_ck_n;
wire [0:0] ddr3_ck_p;
wire [0:0] ddr3_cke;
wire ddr3_ras_n;
wire ddr3_reset_n;
wire ddr3_we_n;
wire [15:0] ddr3_dq;
wire [1:0] ddr3_dqs_n;
wire [1:0] ddr3_dqs_p;
wire [0:0] ddr3_cs_n;
wire [1:0] ddr3_dm;
wire [0:0] ddr3_odt;
//200M时钟输入
initial sys_clk_i = 1'b1;
always #2.5 sys_clk_i = ~sys_clk_i;
initial begin
sys_rst = 1'b0;
aresetn = 1'b0;
wrfifo_clr = 1'b1;
wrfifo_wren = 1'b0;
wrfifo_din = 8'd0;
rdfifo_clr = 1'b1;
rdfifo_rden = 1'b0;
#201;
sys_rst = 1'b1;
aresetn = 1'b1;
@(posedge mmcm_locked);
#200;
wrfifo_clr = 1'b0;
rdfifo_clr = 1'b0;
@(posedge init_calib_complete);
#200;
wr_data(16'd100,16'd1024);
#2000;
rdfifo_clr = 1'b1;
#20;
rdfifo_clr = 1'b0;
#2000;
rd_data(16'd1024);
#5000;
$stop;
end
task wr_data;
input [15:0]data_begin;
input [15:0]wr_data_cnt;
begin
wrfifo_wren = 1'b0;
wrfifo_din = data_begin;
@(posedge ui_clk);
#1 wrfifo_wren = 1'b1;
repeat(wr_data_cnt)
begin
@(posedge ui_clk);
wrfifo_din = wrfifo_din + 1'b1;
end
#1 wrfifo_wren = 1'b0;
end
endtask
task rd_data;
input [15:0]rd_data_cnt;
begin
rdfifo_rden = 1'b0;
@(posedge ui_clk);
#1 rdfifo_rden = 1'b1;
repeat(rd_data_cnt)
begin
@(posedge ui_clk);
end
#1 rdfifo_rden = 1'b0;
end
endtask
wr_ddr3_fifo wr_ddr3_fifo
(
.rst (wrfifo_clr ), // input wire rst
.wr_clk (ui_clk ), // input wire wr_clk
.rd_clk (ui_clk ), // input wire rd_clk
.din (wrfifo_din ), // input wire [15 : 0] din
.wr_en (wrfifo_wren ), // input wire wr_en
.rd_en (wrfifo_rden ), // input wire rd_en
.dout (wrfifo_dout ), // output wire [127 : 0] dout
.full ( ), // output wire full
.empty (wrfifo_empty ), // output wire empty
.rd_data_count (wrfifo_rd_cnt ), // output wire [5 : 0] rd_data_count
.wr_data_count ( ), // output wire [8 : 0] wr_data_count
.wr_rst_busy (wrfifo_wr_rst_busy ), // output wire wr_rst_busy
.rd_rst_busy (wrfifo_rd_rst_busy ) // output wire rd_rst_busy
);
rd_ddr3_fifo rd_ddr3_fifo
(
.rst (rdfifo_clr ), // input wire rst
.wr_clk (ui_clk ), // input wire wr_clk
.rd_clk (ui_clk ), // input wire rd_clk
.din (rdfifo_din ), // input wire [127 : 0] din
.wr_en (rdfifo_wren ), // input wire wr_en
.rd_en (rdfifo_rden ), // input wire rd_en
.dout (rdfifo_dout ), // output wire [15 : 0] dout
.full (rdfifo_full ), // output wire full
.empty ( ), // output wire empty
.rd_data_count ( ), // output wire [8 : 0] rd_data_count
.wr_data_count (rdfifo_wr_cnt ), // output wire [5 : 0] wr_data_count
.wr_rst_busy (rdfifo_wr_rst_busy ), // output wire wr_rst_busy
.rd_rst_busy (rdfifo_rd_rst_busy ) // output wire rd_rst_busy
);
assign wr_addr_clr = wrfifo_clr;
assign rd_addr_clr = rdfifo_clr;
fifo2mig_axi
#(
.WR_DDR_ADDR_BEGIN (0 ),
.WR_DDR_ADDR_END (1024*2 ),
.RD_DDR_ADDR_BEGIN (0 ),
.RD_DDR_ADDR_END (1024*2 ),
.AXI_ID (4'b0000 ),
.AXI_LEN (8'd31 ) //axi burst length = 32
)fifo2mig_axi
(
//FIFO Interface ports
.wr_addr_clr (wr_addr_clr ), //1:clear sync ui_clk
.wr_fifo_rdreq (wrfifo_rden ),
.wr_fifo_rddata (wrfifo_dout ),
.wr_fifo_empty (wrfifo_empty ),
.wr_fifo_rd_cnt ({3'd0,wrfifo_rd_cnt}),
.wr_fifo_rst_busy (wrfifo_wr_rst_busy | wrfifo_rd_rst_busy),
.rd_addr_clr (rd_addr_clr ), //1:clear sync ui_clk
.rd_fifo_wrreq (rdfifo_wren ),
.rd_fifo_wrdata (rdfifo_din ),
.rd_fifo_alfull (rdfifo_full ),
.rd_fifo_wr_cnt ({3'd0,rdfifo_wr_cnt}),
.rd_fifo_rst_busy (rdfifo_wr_rst_busy | rdfifo_rd_rst_busy),
// Application interface ports
.ui_clk (ui_clk ),
.ui_clk_sync_rst (ui_clk_sync_rst ),
.mmcm_locked (mmcm_locked ),
.init_calib_complete (init_calib_complete ),
// Slave Interface Write Address Ports
.m_axi_awid (s_axi_awid ),
.m_axi_awaddr (s_axi_awaddr ),
.m_axi_awlen (s_axi_awlen ),
.m_axi_awsize (s_axi_awsize ),
.m_axi_awburst (s_axi_awburst ),
.m_axi_awlock (s_axi_awlock ),
.m_axi_awcache (s_axi_awcache ),
.m_axi_awprot (s_axi_awprot ),
.m_axi_awqos (s_axi_awqos ),
.m_axi_awvalid (s_axi_awvalid ),
.m_axi_awready (s_axi_awready ),
// Slave Interface Write Data Ports
.m_axi_wdata (s_axi_wdata ),
.m_axi_wstrb (s_axi_wstrb ),
.m_axi_wlast (s_axi_wlast ),
.m_axi_wvalid (s_axi_wvalid ),
.m_axi_wready (s_axi_wready ),
// Slave Interface Write Response Ports
.m_axi_bid (s_axi_bid ),
.m_axi_bresp (s_axi_bresp ),
.m_axi_bvalid (s_axi_bvalid ),
.m_axi_bready (s_axi_bready ),
// Slave Interface Read Address Ports
.m_axi_arid (s_axi_arid ),
.m_axi_araddr (s_axi_araddr ),
.m_axi_arlen (s_axi_arlen ),
.m_axi_arsize (s_axi_arsize ),
.m_axi_arburst (s_axi_arburst ),
.m_axi_arlock (s_axi_arlock ),
.m_axi_arcache (s_axi_arcache ),
.m_axi_arprot (s_axi_arprot ),
.m_axi_arqos (s_axi_arqos ),
.m_axi_arvalid (s_axi_arvalid ),
.m_axi_arready (s_axi_arready ),
// Slave Interface Read Data Ports
.m_axi_rid (s_axi_rid ),
.m_axi_rdata (s_axi_rdata ),
.m_axi_rresp (s_axi_rresp ),
.m_axi_rlast (s_axi_rlast ),
.m_axi_rvalid (s_axi_rvalid ),
.m_axi_rready (s_axi_rready )
);
mig_7series_0 u_mig_7series_0 (
// Memory interface ports
.ddr3_addr (ddr3_addr ), // output [13: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 [15:0] ddr3_dq
.ddr3_dqs_n (ddr3_dqs_n ), // inout [1:0] ddr3_dqs_n
.ddr3_dqs_p (ddr3_dqs_p ), // inout [1:0] ddr3_dqs_p
.init_calib_complete (init_calib_complete ), // output init_calib_complete
.ddr3_cs_n (ddr3_cs_n ), // output [0:0] ddr3_cs_n
.ddr3_dm (ddr3_dm ), // output [1: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
.app_sr_req (1'b0 ), // input app_sr_req
.app_ref_req (1'b0 ), // input app_ref_req
.app_zq_req (1'b0 ), // input app_zq_req
.app_sr_active ( ), // output app_sr_active
.app_ref_ack ( ), // output app_ref_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 [27: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 [127:0] s_axi_wdata
.s_axi_wstrb (s_axi_wstrb ), // input [15: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 [27: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 [127: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 (sys_clk_i ),
.sys_rst (sys_rst ) // input sys_rst
);
ddr3_model ddr3_model
(
.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 ),
.ba (ddr3_ba ),
.addr (ddr3_addr ),
.dq (ddr3_dq ),
.dqs (ddr3_dqs_p ),
.dqs_n (ddr3_dqs_n ),
.tdqs_n ( ),
.odt (ddr3_odt )
);
endmodule
五、仿真展示
可以看到如下图所示的波形。
在初始化校准完成信号变高后,就立马产生了一次读 DDR 操作,同时读出数据写入到
rd_ddr3_fifo,这个时候从 DDR 读出的数据是无效的,因为在此之前还没有往 DDR 里存入
数据。这就是为什么在仿真上在往 DDR 存储数据后,读数据之前先对 rd_ddr3_fifo 进行一
次复位清空缓存的操作,同时对把 DDR 的地址也复位到起始地址。这样可保证读取的数据
都是从 DDR 起始地址开始,同时保证读之前读 FIFO 没有无效的缓存数据。在串口传图系
统上使用也是采用仿真类似的方式进行的,为了达到显示屏显示图像的帧同步(不出现显示
偏移问题),会在显示每帧数据前将读 FIFO 模块 rd_ddr3_fifo 进行一次复位清空缓存和读
DDR 操纵的地址复位到起始。保证每次屏幕显示图像数据都是从 DDR 内存中起始地址开
始读出的数据。
之后在往写 FIFO 写入 1024 个数据(位宽为 16bit)过程中,AXI 总线上产生了 4 次突
发写,每次突发写是 32 个数据(位宽为 128bit),这个与设置的突发写长度和预期的突发写
次数(写 FIFO 数据个数FIFO 写数据位宽/AXI 数据位宽/AXI 一次突发写数据个数 =
102416/128/32 =4,根据这个公式,写 FIFO 数据个数不是随意设置,否则会出现不能满足
写入数据正好是 AXI 突发写数据个数的整数被)一致。
对第一次突发写 DDR 操作部分 AXI 写事务波形放大来看。
从波形图可以看到一次 AXI 写数据操作流程如下:
(1)在地址通道写入接下来要写入数据的起始地址和写突发长度等信息,在 awready
和 awvalid 同时为高时,这些信息被传输给 DDR 控制器;
(2)在写数据通道写入指定突发长度的数据,当 wready 和 wvalid 同时为高时表示写
入数据被传输给 DDR 控制器,在写最后一个数据时,wlast 变为高,写完最后一个 wlast 变
为低;
(3)在写响应通道等待设备的写响应,当 bready 和 bvalid 同时为高时表示响应的到来。
一次操作过程中,数据写入流程与我们设计预期是一致的。同时可以对写入的数据波形
放大了看,写入的每个数据是由写 FIFO 写入 8 个 16bit 数据拼接而成的 128bit 数据,具体
拼接过程的位宽转换是由写 FIFO 完成。可以通过类似方法去对其他几次的写操作波形进行
观察,从而验证设计的正确性。
将 1024 个数据(位宽为 16bit)写完之后,对读 FIFO 进行一次清零,清零的作用前面
已经说过。等待一段时间后,对读 FIFO 进行读数据操作,读取前面写入的 1024 个数据。读
数据过程中,AXI 总线上就会产生突发读的操作。
对红色圈中第一次突发读 DDR 操作部分 AXI 读事务波形放大来看。
(1)在地址通道写入接下来要读取数据的起始地址和读突发长度等信息,在 arready 和
arvalid 同时为高时,这些信息被传输给 DDR 控制器;
(2)在读响应通道,当 rready 和 rvalid 同时为高时表示读出的有效数据,当 rlast 变为
高时表示读出的最后一个数据。图中波形 rdata 有部分地方数据是未知,这些地方并非是有
效数据地方,可不用关心。
从整体上看,写 FIFO 中写入数据是 100~1123 的 1024 个连续递增数据,波形与仿真代
码是一致的,下图是整个写入数据和写入第一个数据与最后一个数据的波形图。
读 FIFO 中读出的数据同样也是 100~1123 的 1024 个连续递增数据,注意读 FIFO 的输
出模式是 First Word Fall Through。
从写入数据与最后读出数据的一致性说明 fifo2mig_axi 模块功能是正常的。
【附件:】链接:https://pan.baidu.com/s/1UhXO3PCYuJyESSTmCPrVrw?pwd=8dr7
提取码:8dr7