AXI4+DDR学习
我用的小梅哥的7010的开发板,这个板子无法直接在PL这边使用DDR存储,必须通过AXI4总线。
High Performamce PORTS就是HP接口,为AXI接口,通常用于大量数据的高速传输。
AXI总线介绍
AXI是基于burst的传输,burst传输是一种适用于AMBA协议的规则形式,通过这种规则,我们可以控制AMBA进行具体的数据传输活动,在这种规则下,主设备发送控制信息和首地址信息,从设备根据这些信号计算接下来的地址信息。
换言之,在这种规则下,主设备只需要发送一拍信息,就可以通过计算,决定未来数拍,写入或读出的地址。下图中的读操作就生动形象的描述了这种Burst传输,一拍的地址和控制信号,操控了四拍的读出数据。通过这种形式,控制信号的效率大大提升。
下面这就是主设备发了一拍信息,决定了后面的读数据的任务。
五个独立的传输通道:
读地址通道(Read Address Channel,AR)
读数据通道(Read Data Channel,R)
写地址通道(Write Address Channel,AW)
写数据通道(Write Data Channel,W)
写响应通道(Write Response Channel,B)
地址通道内传递的数据是地址信息和控制信息,这些都是表示R或者W(数据通道)内的真实数据的一些特性。
写数据行为发生,写相应通道B告诉主设备,写行为完成。(读数据没有单独的读相应通道)
1、这五个通道都包含一个双路的valid、ready握手机制。信息源通过valid信号来指示通道中的数据和控制信息什么时候有效。目的源用ready信号来表示什么时候准备好接收数据。传输地址信息和数据都是在这俩信号同时为高时生效。
2、读写数据通道都包括一个last信号,用来标志最后一个数据。
3、读、写事务都有自己的地址通道,地址通道携带着传输事务所必须得地址和控制信息。
4、读数据通道传送者从设备到主机的读数据和读相应信息(没有读相应通道,所以在这)。
5、写数据通路传送着主机向从设备的写数据。写响应通道提供了设备响应写事务的一种方式。每一次突发式写会产生一个完成信号。
这是设计结构框图,说实话,这一章因为视频讲的一般,所以啃的很难,基本上是对着源代码和文档一点点自己弄的。
读写FIFO+AXI4接口转换模块。写FIFO数据读出放DDR,读DDR数据放读侧FIFO缓存。
fifo2axi4接口转换模块设计
一次完整的写事务流程:
1.主机向写地址通道写入地址和控制信息。
2.写数据通道突发写入数据
3.收到设备的写数据相应
一次完整的读事务流程
1.主机向读地址通道写入地址和控制信息
2.收到设备的读数据相应和读的数据。
IDLE是上电初始状态,先是复位就为上电初始状态。
我直接在代码中注释学习。
fifo2axi4.v
module fifo2axi4
#(
parameter WR_AXI_BYTE_ADDR_BEGIN = 0 ,
parameter WR_AXI_BYTE_ADDR_END = 200 ,
parameter RD_AXI_BYTE_ADDR_BEGIN = 0 ,
parameter RD_AXI_BYTE_ADDR_END = 200 ,
parameter AXI_DATA_WIDTH = 128 ,
parameter AXI_ADDR_WIDTH = 28 ,
parameter AXI_ID_WIDTH = 4 ,
parameter AXI_ID = 4'b0000,
parameter AXI_BURST_LEN = 8'd31 //burst length = 32
)
(
// clock reset
input clk ,
input reset ,
// wr_fifo wr Interface
input wr_addr_clr ,//sync clk
output wr_fifo_rdreq ,//写FIFO请求
input [AXI_DATA_WIDTH-1:0] wr_fifo_rddata ,//写FIFO数据,写FIFO的输出,这边的输入
input wr_fifo_empty , //写FIFO将空
input [5:0] wr_fifo_rd_cnt , //写FIFO计数
input wr_fifo_rst_busy , //写复位忙信号,生效时说明整个写时钟域处于复位状态
// rd_fifo rd Interface
input rd_addr_clr ,
output rd_fifo_wrreq ,
output [AXI_DATA_WIDTH-1:0] rd_fifo_wrdata ,
input rd_fifo_alfull ,
input [5:0] rd_fifo_wr_cnt ,
input rd_fifo_rst_busy ,
// Master Interface Write Address Ports
//写地址通道信号
output [AXI_ID_WIDTH-1:0] m_axi_awid ,//写地址ID,写地址信号组的ID Tag
output reg [AXI_ADDR_WIDTH-1:0] m_axi_awaddr ,//写地址
output [7:0] m_axi_awlen ,//突发式写的长度,突发式写所传输的数据个数=AWLEN + 1
output [2:0] m_axi_awsize ,//突发式写的大小,给出每个突发传输数据的字节数
output [1:0] m_axi_awburst ,//突发式写的类型,具体看表
output [0:0] m_axi_awlock ,//锁类型
output [3:0] m_axi_awcache ,//cache类型
output [2:0] m_axi_awprot ,//保护类型
output [3:0] m_axi_awqos ,//质量服务
output [3:0] m_axi_awregion ,//区域标识符
output reg m_axi_awvalid ,//写地址有效
input m_axi_awready ,//写地址准备好
// Master Interface Write Data Ports
//写数据通道信号
output [AXI_DATA_WIDTH-1:0] m_axi_wdata ,//写的数据
output [AXI_DATA_WIDTH/8-1:0] m_axi_wstrb ,//写数据有效的字节门阀(用来表明哪8bits数据有效)
output reg m_axi_wlast ,//此次传输是最后一个数据传输
output reg m_axi_wvalid ,//写有效
input m_axi_wready ,//写准备好,就绪
// Master Interface Write Response Ports
//写响应通道信号
input [AXI_ID_WIDTH-1:0] m_axi_bid ,//写响应ID,这个数值必须与AWID的数据一样
input [1:0] m_axi_bresp ,//写响应,4种状态
input m_axi_bvalid ,//写响应有效
output m_axi_bready ,//写响应就绪
// Master Interface Read Address Ports
//读地址通道信号
output [AXI_ID_WIDTH-1:0] m_axi_arid ,//读地址ID,用来标志一组读信号
output reg [AXI_ADDR_WIDTH-1:0] m_axi_araddr ,//读地址,一次读突发传输的读地址
output [7:0] m_axi_arlen ,//突发式读长度,突发传输数据的个数,数据个数=ARLEN + 1
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 [3:0] m_axi_arregion ,//读事务的区域指示符
output reg m_axi_arvalid ,//读地址通道有效
input m_axi_arready ,//读地址通道传输就绪
// Master Interface Read Data Ports
//读数据通道信号
input [AXI_ID_WIDTH-1:0] m_axi_rid ,//读数据ID 与ARID一样
input [AXI_DATA_WIDTH-1:0] m_axi_rdata ,//读数据
input [1:0] m_axi_rresp ,//读响应
input m_axi_rlast ,//读事务传送的最后一个数据
input m_axi_rvalid ,//读数据有效
output m_axi_rready //读数据已经就绪 1主机就绪 0未就绪
);
localparam S_IDLE = 6'b000001,
S_WR_ADDR = 6'b000010,
S_WR_DATA = 6'b000100,
S_WR_RESP = 6'b001000,
S_RD_ADDR = 6'b010000,
S_RD_RESP = 6'b100000;
localparam DATA_SIZE = clogb2(AXI_DATA_WIDTH/8-1);
wire [7:0] wr_req_cnt_thresh;
wire [7:0] rd_req_cnt_thresh;
wire wr_ddr3_req;
wire rd_ddr3_req;
reg axi_awaddr_clr;
reg axi_araddr_clr;
reg [5:0] curr_wr_state;
reg [5:0] next_wr_state;
reg [5:0] curr_rd_state;
reg [5:0] next_rd_state;
reg [7:0] wr_data_cnt;
assign m_axi_awid = AXI_ID[AXI_ID_WIDTH-1:0];
assign m_axi_awsize = DATA_SIZE;
assign m_axi_awburst = 2'b01;
assign m_axi_awlock = 1'b0;
assign m_axi_awcache = 4'b0000;
assign m_axi_awprot = 3'b000;
assign m_axi_awqos = 4'b0000;
assign m_axi_awregion= 4'b0000;
assign m_axi_awlen = AXI_BURST_LEN[7:0]; //突发式写的长度,突发式写所传输的数据的个数。数据个数=AWLEN+1
assign m_axi_wstrb = 16'hffff;
assign m_axi_wdata = wr_fifo_rddata;
assign m_axi_bready = 1'b1;
assign m_axi_arid = AXI_ID[AXI_ID_WIDTH-1:0];
assign m_axi_arsize = DATA_SIZE;
assign m_axi_arburst = 2'b01;
assign m_axi_arlock = 1'b0;
assign m_axi_arcache = 4'b0000;
assign m_axi_arprot = 3'b000;
assign m_axi_arqos = 4'b0000;
assign m_axi_arregion= 4'b0000;
assign m_axi_arlen = AXI_BURST_LEN[7:0];
assign m_axi_rready = ~rd_fifo_alfull; //读数据通道ready的前提 是读FIFO这边还没读满
//读写使能 读写数据
//写FIFO使能信号= AXI写地址通道(AW)不清除复位 写数据通道valid 写数据通道ready
assign wr_fifo_rdreq = (~axi_awaddr_clr) && m_axi_wvalid && m_axi_wready;
assign rd_fifo_wrreq = (~axi_araddr_clr) && m_axi_rvalid && m_axi_rready;
assign rd_fifo_wrdata = m_axi_rdata; //读端FIFO的数据= AXI读数据通道数据
//控制状态机的读写DDR请求信号是根据当前FIFO中数据量进行判断产生,当写FIFO中的数据量超过一个阈值
//wr_req_cnt_thresh就产生写DDR请求。
//当读FIFO中的数据量低于一个阈值rd_req_cnt_thresh就产生读DDR请求
//当写FIFO中的数据量高于一个阈值wr_req_cnt_thresh就产生写DDR请求
//上面两句的意思就是,写FIFO中的数据快爆了,赶紧往DDR里面写
//读FIFO中的数据快空了,赶紧从DDR里面读。
assign wr_req_cnt_thresh = (m_axi_awlen == 'd0)? 1'b1 : (AXI_BURST_LEN[7:0]+1'b1-2'd2);//计数比实际数量少2
assign rd_req_cnt_thresh = AXI_BURST_LEN[7:0];//获取其值
//写DDR3请求的数值判断=写FIFO复位忙信号不生效&&写FIFO计数大于阈值
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;
always@(posedge clk or posedge reset)
begin
if(reset)
axi_awaddr_clr <= 1'b0;
else if(m_axi_wready && m_axi_wvalid && m_axi_wlast) //写数据通道ready valid 且在传最后一个数据之后
axi_awaddr_clr <= 1'b0; //AXI写地址通道清除复位=0,不生效
else if(wr_addr_clr && (m_axi_awvalid || m_axi_wvalid))//写地址清除复位&&写地址通道valid 或者写数据通道valid
axi_awaddr_clr <= 1'b1;//响应外部请求来清除与写地址相关的某些状态或标志
else
axi_awaddr_clr <= axi_awaddr_clr;
end
//m_axi_awaddr
always@(posedge clk or posedge reset)
begin
if(reset)
//写操作的写地址通道比较关键的是awaddr和awvalid
//awaddr初始复位为0,清除后也是起始地址0
m_axi_awaddr <= WR_AXI_BYTE_ADDR_BEGIN;
else if(wr_addr_clr || axi_awaddr_clr) //写地址清除复位 或者 写地址通道清除复位
m_axi_awaddr <= WR_AXI_BYTE_ADDR_BEGIN;
else if(m_axi_awaddr >= WR_AXI_BYTE_ADDR_END) //如果写地址通道大于200
m_axi_awaddr <= WR_AXI_BYTE_ADDR_BEGIN; //清零
//当前写数据状态为resp等待,写响应通道valid和ready高电平,写响应成功,bid一致
//即完成一次写操作,地址就需要增加一次突发写入的数据量
//这里的地址是以字节为单位的,每次增加的量=突发写数据个数*每个数据的字节数。
//这里每次突发长度为AWLEN+1,每数据字节数为128/8=16
else if((curr_wr_state == S_WR_RESP) && m_axi_bready && m_axi_bvalid && (m_axi_bresp == 2'b00) && (m_axi_bid == AXI_ID[AXI_ID_WIDTH-1:0]))
m_axi_awaddr <= m_axi_awaddr + ((m_axi_awlen + 1'b1)*(AXI_DATA_WIDTH/8));
else
m_axi_awaddr <= m_axi_awaddr;
end
//m_axi_awvalid
always@(posedge clk or posedge reset)
begin
if(reset)
m_axi_awvalid <= 1'b0;
//当在S_WR_ADDR状态,写地址通道,且ready和valid高电平生效。
//这边输出为0是为了让ready和valid信号,只有一个时钟周期的同时高。
else if((curr_wr_state == S_WR_ADDR) && m_axi_awready && m_axi_awvalid)
m_axi_awvalid <= 1'b0;
//写地址通道状态时,valid为1
else if(curr_wr_state == S_WR_ADDR)
m_axi_awvalid <= 1'b1;
else
m_axi_awvalid <= m_axi_awvalid;
end
//m_axi_awvalid
always@(posedge clk or posedge reset)
begin
if(reset)
m_axi_wvalid <= 1'b0;
//在进行到WR_DATA,写数据状态,在发完最后一个数据后,为低电平
else if((curr_wr_state == S_WR_DATA) && m_axi_wready && m_axi_wvalid && m_axi_wlast)
m_axi_wvalid <= 1'b0;
//刚到WR_DATA状态时为高电平
else if(curr_wr_state == S_WR_DATA)
m_axi_wvalid <= 1'b1;
else
m_axi_wvalid <= m_axi_wvalid;
end
//wr_data_cnt
always@(posedge clk or posedge reset)
begin
if(reset)
wr_data_cnt <= 1'b0;
else if(curr_wr_state == S_IDLE)
//wlast信号是主机向设备传输最后一个数据的标识信号
//这个信号的产生依赖于单次突发写入数据个数和当前已经传输的数据个数
//传输最后一个数据同时将其输出为高,发送完最后一个数据后立马将其输出为低
//所以要先对传输数据的个数计数
wr_data_cnt <= 1'b0;
//当在写地址通道写数据,ready和valid同时为高的时候,就是一位数据传输,计数+1
else if(curr_wr_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
always@(posedge clk or posedge reset)
begin
if(reset)
m_axi_wlast <= 1'b0;
//传输完最后一个,wlast就变低电平
else if(curr_wr_state == S_WR_DATA && m_axi_wready && m_axi_wvalid && m_axi_wlast)
m_axi_wlast <= 1'b0;
//突发写数据个数为1,突发写数据个数-1=wlen
//也就是只传一个,那么这一个就是最后一个
else if(curr_wr_state == S_WR_DATA && m_axi_awlen == 8'd0)
m_axi_wlast <= 1'b1;
//要么传输完倒数第二个数,wlast变为高电平
else if(curr_wr_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
always@(posedge clk or posedge reset)
begin
if(reset)
axi_araddr_clr <= 1'b0;
//读地址通道清除
else if(m_axi_rready && m_axi_rvalid && m_axi_rlast)
axi_araddr_clr <= 1'b0;
else if(rd_addr_clr && (m_axi_arvalid || m_axi_rvalid))
axi_araddr_clr <= 1'b1;
else
axi_araddr_clr <= axi_araddr_clr;
end
//m_axi_araddr
always@(posedge clk or posedge reset)
begin
if(reset)
m_axi_araddr <= RD_AXI_BYTE_ADDR_BEGIN;
else if(rd_addr_clr || axi_araddr_clr)
m_axi_araddr <= RD_AXI_BYTE_ADDR_BEGIN;
else if(m_axi_araddr >= RD_AXI_BYTE_ADDR_END)
m_axi_araddr <= RD_AXI_BYTE_ADDR_BEGIN;
//在完成一次读操作流程后,地址需要增加一次突发写 入 的 数 据 量 。
//即 每 完 成 一 次 读 操 作 , 地 址 增 加(m_axi_arlen + 1'b1)*(AXI_DATA_WIDTH/8),计算与写操作一样。
else if((curr_rd_state == S_RD_RESP) && m_axi_rready && m_axi_rvalid && m_axi_rlast && (m_axi_rresp == 2'b00) && (m_axi_rid == AXI_ID[AXI_ID_WIDTH-1:0]))
m_axi_araddr <= m_axi_araddr + ((m_axi_arlen + 1'b1)*(AXI_DATA_WIDTH/8));
else
m_axi_araddr <= m_axi_araddr;
end
//m_axi_arvalid
always@(posedge clk or posedge reset)
begin
if(reset)
m_axi_arvalid <= 1'b0;
else if((curr_rd_state == S_RD_ADDR) && m_axi_arready && m_axi_arvalid)
m_axi_arvalid <= 1'b0;
else if(curr_rd_state == S_RD_ADDR)
m_axi_arvalid <= 1'b1;
else
m_axi_arvalid <= m_axi_arvalid;
end
//**********************************
//write state machine
//**********************************
always@(posedge clk or posedge reset)
begin
if(reset)
curr_wr_state <= S_IDLE;
else
curr_wr_state <= next_wr_state;
end
//状态机在上电复位时处于初始状态IDLE,在初始状态IDLE下,如果有请求到来,则进入AXI写地址通道的操作状态S_WR_ADDR
always@(*)
begin
case(curr_wr_state)
S_IDLE:
begin
if(wr_ddr3_req == 1'b1) //写请求为1 ,有写请求
next_wr_state = S_WR_ADDR;
else
next_wr_state = S_IDLE;
end
S_WR_ADDR:
begin
if(m_axi_awready && m_axi_awvalid)
//AW写地址通道的ready和valid信号都为高时生效,传输地址信息和数据传输成功
//开始进入到写操作的写数据通道操作
next_wr_state = S_WR_DATA;
else
next_wr_state = S_WR_ADDR;
end
S_WR_DATA:
begin
//W写数据通道的操作中,当主机写完最后一个数据,last信号高的时候,进入到等待写相应状态
if(m_axi_wready && m_axi_wvalid && m_axi_wlast)
next_wr_state = S_WR_RESP;
else
next_wr_state = S_WR_DATA;
end
S_WR_RESP:
begin
//B 在等待写响应状态,当主机接收到设备的写响应之后,一次完整的写操作流程完成,状态回到仲裁状态进行下一次操作
//bresp不同值表示不同相应结果,00表示写数据成功
//bid需要与写地址通道传输的awbid一致。(写响应通道的BID信号,也就是写响应ID,这个数值与AWID数值匹配)
//AWID[3:0] BID[3:0],下面那个AXI_ID_WIDTH上面定义的4
if(m_axi_bready && m_axi_bvalid && (m_axi_bresp == 2'b00) && (m_axi_bid == AXI_ID[AXI_ID_WIDTH-1:0]))
next_wr_state = S_IDLE;
else
next_wr_state = S_WR_RESP;
end
default: next_wr_state = S_IDLE;
endcase
end
//**********************************
//read state machine
//**********************************
always@(posedge clk or posedge reset)
begin
if(reset)
curr_rd_state <= S_IDLE;
else
curr_rd_state <= next_rd_state;
end
always@(*)
begin
case(curr_rd_state)
S_IDLE:
begin
//读操作请求为1,进入读地址通道
if(rd_ddr3_req == 1'b1)
next_rd_state = S_RD_ADDR;
else
next_rd_state = S_IDLE;
end
S_RD_ADDR:
begin
//当读地址通道的ready和valid同时为高时,表示地址已经传输完成,进入到读相应通道
if(m_axi_arready && m_axi_arvalid)
next_rd_state = S_RD_RESP;
else
next_rd_state = S_RD_ADDR;
end
S_RD_RESP:
begin
//当主机接收到设备的读响应,且bresp 00为成功,rid和arbid一致
if(m_axi_rready && m_axi_rvalid && m_axi_rlast && (m_axi_rresp == 2'b00) && (m_axi_rid == AXI_ID[AXI_ID_WIDTH-1:0]))
next_rd_state = S_IDLE;
else
next_rd_state = S_RD_RESP;
end
default: next_rd_state = S_IDLE;
endcase
end
现在发现vivado版本问题还是有一些影响因素的,不仅是IP核的版本,在跑后面一章的串口传图的时候,vivado2020配合vitis就是没办法成功,TFT一瞬间就没反映了。
而使用Vivado2018配合SDK就没这个问题。暂时不想花时间去解决,两个版本暂时都跑一遍程序。
本文来自博客园,作者:祈愿树下,转载请注明原文链接:https://www.cnblogs.com/cjl520/p/18220651