AXI(Advanced eXtensible Interface)总线是AMBA总线架构中,最新并且性能最好的一个总线标准。AXI的设计目标是可以在高时钟频率下运行,并在延滞时间长的状况下仍可达成高数据吞吐率。AXI总线将读/写请求与读/写结果相互分离、将数据写入和数据读出的信号相分离,可以同时进行写入和读出动作,从而最大限度地提高总线的数据吞吐率。
由若干master设备和slave设备通过一些形式的interconnect组成的典型的系统如下图所示,AXI总线即可作为其中的Interface,实现数据通信。
amba_axi_protocol_spec的下载地址:
https://static.docs.arm.com/ihi0022/g/IHI0022G_amba_axi_protocol_spec.pdf?_ga=2.83582867.447931395.1591251023-1939436544.1591251023
Clock
每个AXI interface都有一个时钟信号ACLK,所有的输入信号在ACLK的上升沿采样,所有的输出信号在ACLK的上升沿之后发生变化。
在master和slave interface上,输入与输出信号之间必须没有组合逻辑路径。
Reset
AXI协议使用低电平有效的复位信号ARESETn。复位信号可以被异步置位,但是复位的释放必须与ACLK的上升沿是同步的。
During reset,需要遵循下面的interface requirement:
(1)A master interface must drive ARVALID, AWVALID, and WVALID LOW.
(2)A slave interface must drive RVALID and BVALID LOW.
(3)All other signals can be driven to any value.
在复位之后,master被允许驱动ARVALID, AWVALID, or WVALID 为高电平的最早的时刻是ARESETn为高电平之后的一个ACLK的上升沿,如下图所示。
AXI总线的master和slave的端口分为5个双向流量控制的通道,如下图所示。所谓双向流量控制是指发送端用valid表示数据是有效的,接收端用ready表示可以接受数据;只有在vaild和ready同时为1时,数据才成功被传送。vaild/ready机制可以使发送接收双方都有能力控制传输速率。
AXI规定:
(1)A source is not permitted to wait until READY is asserted before asserting VALID .
(2)When VALID is asserted, it must remain asserted until the handshake occurs.
(1)master通过写地址通道发出写入请求;
(2)master通过写数据通道发送写入的数据;
(3)slave在完成写入动作后(写数据通道last),通过写响应通道发回确认信息。
(1)master通过读地址通道发出读取请求;
(2)slave通过读数据通道将读取的数据传给master。
在下面的依赖关系图中:单箭头信号表示其指向的信号可以在箭头起始信号置起之前或之后置起;双箭头信号表示其指向的信号必须在箭头起始信号置起之后置起。
(1)读传输事务依赖关系
下图表示,RVALID必须等到ARVALID和ARREADY握手后才可以置位。
(2)AXI3写传输事务依赖关系
slave必须等待master的WLAST信号置位,才能将BVALID置位。
(3)AXI4和AXI5写传输事务依赖关系
slave必须等待master的WLAST信号置位,才能将BVALID置位。
无论是读写操作,AXI 总线支持,或者说基于突发传输(Burst Transaction)。
突发传输的流程:
a.主机在读/写地址通道写入起始地址(AxADDR)以及突发传输的长度(AxLEN)、宽度(AxSIZE)、类型(AxBURST)等信息;
b.从机将在起始地址开始,依次接收主机传输的写数据,或者读取连续地址上的数据,作为读数据传输给主机。
注意:单次 burst 传输中的数据,其地址不能跨越 4KB 边界。
(地址数值的1代表1个字节,如32bit数据线,AxSIZE没有限制,则每次burst地址增加4)
(1)突发传输长度
突发传输长度(burst length),指一次突发传输中包含的数据传输(transfer)数量,在协议中使用AxLEN信号控制(AWLEN和ARLEN)。
突发传输长度在不同的模式下有一些限制,包括:
a.对于WRAP模式,突发传输长度仅能为2,4,8,16
b.在一次突发传输中,地址不能跨越4KB地址边界
c.一次突发传输不能在完成所有数据传输前提前结束
(协议中多次强调,通信双方都不能在传输事务的所有 Transfer 完成前提前结束,哪怕发生错误,也要走完整个传输事务的流程。但是主机也有办法减少传输的数据。在写传输事务中,发送方可以通过置低所有的写有效位(WSTRB),使写数据无效。在读传输事务中,主机可以直接丢弃读取到的数据。)
(2)突发传输宽度
突发传输宽度(burst size),指传输中的数据位宽,具体地,是每周期传输数据的字节数量,在协议中使用AxSIZE信号控制(AWSIZE和ARSIZE)。
突发传输数据宽度不能超过数据总线本身的位宽。而当数据总线位宽大于突发传输宽度时,将根据协议的相关规定,将数据在部分数据线上传输(A3.4.3章节Narrow transfers)。
(3)突发传输类型
突发传输类型(burst type),类型共有 3 种,分别为 FIXED,INCR 以及 WRAP。使用 2 位二进制表示,在协议中使用 AxBURST信号控制(AWBURST和ARBURST)。
FIXED 类型: burst 中所有数据都使用起始地址。该模式适合对某个固定地址进行多次数据更新,比如读写一个FIFO时,读写地址就是固定的。
INCR 类型最为常用:后续数据的地址在初始地址的基础上进行递增,递增幅度与传输宽度相同。适合对于RAM、DDR等通过地址映射(mapped memory)的存储介质进行读写操作。
WRAP 类型:比较特殊,首先根据起始地址得到绕回边界地址(wrap boundary)与最高地址。当前地址小于最高地址时,WRAP 与 INCR 类型完全相同,地址递增。但到递增后的地址到达最高地址后,地址直接回到绕回边界地址,再进行递增,就这样循环往复。最高地址由绕回边界地址计算得到:wrap boundary + (N_bytes x burst_len)。根据协议手册上表示,WRAP 适合对 cache 的访问。
AXI协议提供读/写事务的响应信号:对于读操作,响应信号在读地址通道RRESP[1:0];对于写操作,响应信号在写响应通道BRESP[1:0]。
OKAY 常规访问成功
EXOKAY 独占访问成功
SLVERR 从机错误
DECERR 解码错误
注意:在写传输事务中,单个写回复针对的是整个 burst,而不是 burst 中单个 transfer。但是在读传输事务中,从机可以为突发传输中每一个读传输数据产生不同的读回复信号。
Outstanding:master 不必等待命令执行结束就可以发送下一命令
Out-of-Order:对于相同ID的指令,必须要顺序完成;对于不同ID的指令,可以乱序完成。
Interleaving:乱序传输时不同ID之间的数据可以内插,但是要保证每个ID的数据顺序。
(注意:AXI4的WID信号被去掉了,因此AXI4的写数据通道不能实现Out-of-Order和Interleaving)
Outstanding是指正在进行中的,未完成的,形象表达就是说“在路上”。相比于正常情况下的主机和从机的读写操作,如果outstanding能力N>1,写操作:主机可以连续发出N组写地址和写数据,这期间如果没有写响应返回,则等待,如果有写响应返回,则返回了几个就可以接着发几组。也就是说在路上的写响应最多是N。对于读操作是同样的道理。
当一个master向一个或多个slave发出数据块读/写请求时,AXI总线要求在同一个数据块内,每一个数据的读/写的完成必须按照顺序。但数据块之间,或读取与写入之间,AXI总线并不要求完成读/写的顺序与发出读/写请求的顺序保持一致,也就是允许读/写事务的乱序(Out-of-Order)。AXI总线的每个通道上的每一个读/写请求、数据结果,以及写入回应都包括一个4bit的读/写事务识别序号ID[3:0]。根据识别序号,主控模块可以辨别接收到的读取数据或是写入回应与发出的读/写请求之间的对应关系。不同ID的数据可以内插(Interleaving),通过ID号可以对数据进行识别。
由于Vivado软件中包含很多AXI协议的IP核,因此在Vivado中进行仿真。
在Vivado2019.1中,调用AXI BRAM Controller (4.1) IP核。
设置Memory Depth 为262144。
BRAM Instance 选择Internal。
tb_axi_bram.v
`timescale 1ns / 1ps
// Company:
// Engineer:
//
// Create Date: 2020/12/22
// Author Name: Sniper
// Module Name: tb_axi_bram
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
module tb_axi_bram;
// parameter
parameter ADDR_WIDTH = 20;
parameter DATA_WIDTH = 32;
// global
reg aclk;
reg aresetn;
// write address channel
wire [0:0] m_axi_awid;
wire [ADDR_WIDTH-1:0] m_axi_awaddr;
wire [7:0] m_axi_awlen; // Burst Length: 0-255
wire [2:0] m_axi_awsize; // Burst Size: Fixed 3'b011
wire [1:0] m_axi_awburst; // Burst Type: Fixed 2'b01(Incremental Burst)
wire m_axi_awlock; // Lock: Fixed 1'b0
wire [3:0] m_axi_awcache; // Cache: Fiex 4'b0011
wire [2:0] m_axi_awprot; // Protect: Fixed 3'b000
wire [3:0] m_axi_awqos; // QoS: Fixed 4'b0000
wire [0:0] m_axi_awuser; // User: Fixed 32'd0
wire m_axi_awvalid;
wire m_axi_awready;
// write data channel
wire [DATA_WIDTH-1:0] m_axi_wdata;
wire [DATA_WIDTH/8-1:0] m_axi_wstrb;
wire m_axi_wlast;
wire [0:0] m_axi_wuser;
wire m_axi_wvalid;
wire m_axi_wready;
// write response channel
wire [0:0] m_axi_bid;
wire [1:0] m_axi_bresp;
wire [0:0] m_axi_buser;
wire m_axi_bvalid;
wire m_axi_bready;
// read address channel
wire [0:0] m_axi_arid;
wire [ADDR_WIDTH-1:0] m_axi_araddr;
wire [7:0] m_axi_arlen;
wire [2:0] m_axi_arsize;
wire [1:0] m_axi_arburst;
wire m_axi_arlock;
wire [3:0] m_axi_arcache;
wire [2:0] m_axi_arprot;
wire [3:0] m_axi_arqos;
wire [0:0] m_axi_aruser;
wire m_axi_arvalid;
wire m_axi_arready;
// read data channel
wire [0:0] m_axi_rid;
wire [DATA_WIDTH-1:0] m_axi_rdata;
wire [1:0] m_axi_rresp;
wire m_axi_rlast;
wire [0:0] m_axi_ruser;
wire m_axi_rvalid;
wire m_axi_rready;
// User-interface
reg master_rst;
reg wr_start;
reg [ADDR_WIDTH-1:0] wr_addrs;
reg [7:0] wr_len;
reg [DATA_WIDTH-1:0] wr_data;
wire wr_handshake;
wire wr_busy;
wire wr_done;
reg rd_start;
reg [ADDR_WIDTH-1:0] rd_addrs;
reg [7:0] rd_len;
wire [DATA_WIDTH-1:0] rd_data;
wire rd_handshake;
wire rd_busy;
wire rd_done;
initial
begin
aresetn = 0;
aclk = 0;
master_rst = 0;
wr_start = 0;
wr_addrs[ADDR_WIDTH-1:0] = 0;
wr_len[7:0] = 0;
wr_data[DATA_WIDTH-1:0] = 0;
#100;
@(posedge aclk);
aresetn <= 1;
@(posedge aclk);
wr_addrs <= 0;
wr_len[7:0] <= 15;
@(posedge aclk);
if(!wr_busy)
wr_start <= 1;
@(posedge aclk);
wr_start <= 0;
repeat(30) @(posedge aclk);
wr_addrs <= 1024;
wr_len[7:0] <= 255;
@(posedge aclk);
if(!wr_busy)
wr_start <= 1;
@(posedge aclk);
wr_start <= 0;
end
initial
begin
rd_start = 0;
rd_addrs[ADDR_WIDTH-1:0] = 0;
rd_len[7:0] = 0;
repeat(500) @(posedge aclk);
@(posedge aclk);
rd_addrs <= 1024;
rd_len[7:0] <= 255;
@(posedge aclk);
if(!rd_busy)
rd_start <= 1;
@(posedge aclk);
rd_start <= 0;
repeat(300) @(posedge aclk);
@(posedge aclk);
rd_addrs <= 0;
rd_len[7:0] <= 15;
@(posedge aclk);
if(!rd_busy)
rd_start <= 1;
@(posedge aclk);
rd_start <= 0;
end
//clock
always #5 aclk = ~aclk;
always @(posedge aclk)
if(wr_handshake)
wr_data = wr_data + 1;
// AXI WRITE //
localparam W_IDLE = 3'd0;
localparam WA_WAIT = 3'd1;//write address
localparam WA_START = 3'd2;
localparam WD_WAIT = 3'd3;//write data
localparam WD_PROC = 3'd4;
localparam WR_WAIT = 3'd5;//write response
localparam WR_DONE = 3'd6;
reg [2:0] wr_state;
reg [ADDR_WIDTH-1:0] reg_wr_addrs;
reg [7:0] reg_wr_len;
reg reg_awvalid;
reg reg_wvalid;
reg reg_w_last;
// Write State
always @(posedge aclk or negedge aresetn)
begin
if(!aresetn)
begin
wr_state <= W_IDLE;
reg_wr_addrs <= 0;
reg_wr_len <= 0;
reg_awvalid <= 0;
reg_wvalid <= 0;
reg_w_last <= 0;
end
else
begin
if(master_rst)
begin
wr_state <= W_IDLE;
end
else
begin
case(wr_state)
W_IDLE:
begin
if(wr_start)
begin
wr_state <= WA_WAIT;
reg_wr_addrs <= wr_addrs;
reg_wr_len <= wr_len;
end
reg_awvalid <= 0;
reg_wvalid <= 0;
reg_w_last <= 0;
end
WA_WAIT:
begin
wr_state <= WA_START;
end
WA_START:
begin
wr_state <= WD_WAIT;
reg_awvalid <= 1;
end
WD_WAIT:
begin
if(m_axi_awvalid & m_axi_awready)
begin
wr_state <= WD_PROC;
reg_awvalid <= 0;
reg_wvalid <= 1;
end
end
WD_PROC:
begin
if(m_axi_wvalid & m_axi_wready)
begin
if(reg_wr_len == 0)
begin
wr_state <= WR_WAIT;
reg_wvalid <= 0;
end
else
reg_wr_len <= reg_wr_len - 1;
if(reg_wr_len == 1)
reg_w_last <= 1;
else
reg_w_last <= 0;
end
end
WR_WAIT:
begin
if(m_axi_bvalid & m_axi_bready)
begin
//reg_wr_status[1:0] <= m_axi_bresp[1:0];
//if(reg_w_last)
//begin
wr_state <= WR_DONE;
//end
//else
//begin
//wr_state <= WA_WAIT;
//reg_wr_addrs <= reg_wr_addrs + 32'd2048;
//end
end
end
WR_DONE:
begin
wr_state <= W_IDLE;
end
default:
begin
wr_state <= W_IDLE;
end
endcase
end//if(master_rst)
end//if(!aresetn)
end
assign m_axi_awid = 1'b0;
assign m_axi_awaddr[ADDR_WIDTH-1:0] = reg_wr_addrs[ADDR_WIDTH-1:0];
assign m_axi_awlen[7:0] = reg_wr_len[7:0];
assign m_axi_awsize[2:0] = $clog2(DATA_WIDTH/8);//3'b010;
assign m_axi_awburst[1:0] = 2'b01;
assign m_axi_awlock = 1'b0;
assign m_axi_awcache[3:0] = 4'b0011;
assign m_axi_awprot[2:0] = 3'b000;
assign m_axi_awqos[3:0] = 4'b0000;
assign m_axi_awuser[0] = 1'b0;
assign m_axi_awvalid = reg_awvalid;
assign m_axi_wdata[DATA_WIDTH-1:0] = wr_data[DATA_WIDTH-1:0];
assign m_axi_wstrb[DATA_WIDTH/8-1:0] = {(DATA_WIDTH/8){1'b1}};
assign m_axi_wlast = reg_w_last;
assign m_axi_wuser = 0;
assign m_axi_wvalid = reg_wvalid;
assign m_axi_bready = m_axi_bvalid;
assign wr_handshake = m_axi_wvalid & m_axi_wready;
assign wr_busy = (wr_state == W_IDLE)? 1'b0 : 1'b1;
assign wr_done = (wr_state == WR_DONE);
// AXI READ //
localparam R_IDLE = 3'd0;
localparam RA_WAIT = 3'd1;
localparam RA_START = 3'd2;
localparam RD_WAIT = 3'd3;
localparam RD_PROC = 3'd4;
localparam RD_DONE = 3'd5;
reg [2:0] rd_state;
reg [ADDR_WIDTH-1:0] reg_rd_addrs;
reg [7:0] reg_rd_len;
reg reg_arvalid;
reg reg_r_last;
// Read State
always @(posedge aclk or negedge aresetn)
begin
if(!aresetn)
begin
rd_state <= R_IDLE;
reg_rd_addrs <= 0;
reg_rd_len <= 0;
reg_arvalid <= 0;
end
else
begin
case(rd_state)
R_IDLE:
begin
if(rd_start)
begin
rd_state <= RA_WAIT;
reg_rd_addrs <= rd_addrs;
reg_rd_len <= rd_len;
end
reg_arvalid <= 0;
end
RA_WAIT:
begin
rd_state <= RA_START;
end
RA_START:
begin
rd_state <= RD_WAIT;
reg_arvalid <= 1;
end
RD_WAIT:
begin
if(m_axi_arvalid & m_axi_arready)
begin
rd_state <= RD_PROC;
reg_arvalid <= 0;
end
end
RD_PROC:
begin
if(m_axi_rvalid & m_axi_rready)
begin
if(m_axi_rlast & reg_r_last)
rd_state <= RD_DONE;
else
reg_rd_len <= reg_rd_len - 1;
if(reg_rd_len == 1)
reg_r_last <= 1;
else
reg_r_last <= 0;
end
end
RD_DONE:
begin
rd_state <= R_IDLE;
end
endcase
end//if(!aresetn)
end
// Master Read Address
assign m_axi_arid = 1'b0;
assign m_axi_araddr[ADDR_WIDTH-1:0] = reg_rd_addrs;
assign m_axi_arlen[7:0] = reg_rd_len[7:0];
assign m_axi_arsize[2:0] = $clog2(DATA_WIDTH/8);//3'b010;
assign m_axi_arburst[1:0] = 2'b01;
assign m_axi_arlock = 1'b0;
assign m_axi_arcache[3:0] = 4'b0011;
assign m_axi_arprot[2:0] = 3'b000;
assign m_axi_arqos[3:0] = 4'b0000;
assign m_axi_aruser[0] = 1'b0;
assign m_axi_arvalid = reg_arvalid;
assign m_axi_rready = m_axi_rvalid;
assign rd_handshake = m_axi_rvalid & m_axi_rready;
assign rd_busy = (rd_state == R_IDLE)? 1'b0 : 1'b1;
assign rd_done = (rd_state == RD_DONE);
//DUT
axi_bram_ctrl_0 U_axi_bram_ctrl_0
(
//global signal
.s_axi_aclk(aclk),
.s_axi_aresetn(aresetn),
//write address channel
.s_axi_awaddr(m_axi_awaddr),//[19:0]
.s_axi_awburst(m_axi_awburst),//[1:0]
.s_axi_awcache(m_axi_awcache),//[3:0]
.s_axi_awlen(m_axi_awlen),//[7:0]
.s_axi_awlock(m_axi_awlock),
.s_axi_awprot(m_axi_awprot),//[2:0]
.s_axi_awready(m_axi_awready),
.s_axi_awsize(m_axi_awsize),//[2:0]
.s_axi_awvalid(m_axi_awvalid),
//write data channel
.s_axi_wdata(m_axi_wdata),//[31:0]
.s_axi_wlast(m_axi_wlast),
.s_axi_wready(m_axi_wready),
.s_axi_wstrb(m_axi_wstrb),//[3:0]
.s_axi_wvalid(m_axi_wvalid),
//write response channel
.s_axi_bready(m_axi_bready),
.s_axi_bresp(m_axi_bresp),//[1:0]
.s_axi_bvalid(m_axi_bvalid),
//read address channel
.s_axi_araddr(m_axi_araddr),//[19:0]
.s_axi_arburst(m_axi_arburst),//[1:0]
.s_axi_arcache(m_axi_arcache),//[3:0]
.s_axi_arlen(m_axi_arlen),//[7:0]
.s_axi_arlock(m_axi_arlock),
.s_axi_arprot(m_axi_arprot),//[2:0]
.s_axi_arready(m_axi_arready),
.s_axi_arsize(m_axi_arsize),//[2:0]
.s_axi_arvalid(m_axi_arvalid),
//read data channel
.s_axi_rdata(m_axi_rdata),//[31:0]
.s_axi_rlast(m_axi_rlast),
.s_axi_rready(m_axi_rready),
.s_axi_rvalid(m_axi_rvalid)
);
/*
initial
begin
$dumpfile("tb_axi_bram.vcd");
$dumpvars(0,tb_axi_bram);
end
initial #1000 $finish;
*/
endmodule
仿真结果:
(1)burst写入数据
仿真波形与“写数据的流程”是一致的。
(2)burst读取数据
仿真波形与“读数据的流程”是一致的。
在Vivado2019.1中,调用AXI Uartlite (2.0) IP核。配置如下。
写数据时序如下所示:(可以看出AWREADY和WREADY是同时反馈的,表示地址和数据是同时写入;BVALID在下一个时钟返回)
AXI4-Stream协议比较简单就不做实例仿真了,只要valid和ready同时为1,就一直写入数据,直到valid & ready & last。