AXI4
AXI总线是计算机内部的一种高速总线,主要用于主机(master)和从机(slave)低延迟、高速的数据传输,是由RAM公司设计的为了代替AHB、APB总线而存在的总线标准。AXI可以细分为AXI4、AXI_lite、AXI_stream。
三种总线标准的特点应用为:
AXI4(AXI_full):拥有5个数据通道(注意是通道,每个通道里面都有一系列的信号线),可以进行多次带有地址和数据的突发(burst)传输,满足高性能的存储器映射,常应用于DDR的数据读写。
AXI_lite:也拥有5个数据通道,但是不能进行多次的突发传输,或者说只能进行一次突发传输,可以用于简单的低吞吐率的存储映射,例如内存的配置。
AXI_stream:算是AXI4的简化版,传输高数数据流,没有地址线,能够无次数限制的突发传输,可以应用于FIFO、PCIE、AD/DA的数据传输。
注释:突发(burst)对于新手来说可能理解有点困难,我的理解就是传输一次数据就是一次突发,不管你的数据线或者地址线的线宽/位数是多少。后面会涉及到burst size和burst length会具体讲到。还有在AXI的传输中,数据都是以字节(8bit)的方式进行理解,就是说数据的位宽都是字节的倍数。
AXI4的接口分为5个通道
AXI4的接口分为5个通道,接下来我们详细讲。这5个通道在图中从上往下分解,分别为AW,W,B,AR,R,也是写地址通道、写数据通道、写响应通道、读地址通道、读数据通道。
读到这儿可能有的同学就疑惑了,为什么有写响应通道没有读响应通道呢?
其实是有的,只是在官方的设计中把读响应通道和读数据通道(W)合并了,也就是上图中的s_axi_rresp[1:0],为什么写操作不合并呢,简单说一下,对于主机来说,写是输出通道,而响应是需要从机产生响应,所以对主机来说是输入通道,一个输入一个输出显然不能合并,而读是输入通道,自然就可以合并了,同时减少了接线数量。
至于为什么不放在读地址通道(AW)而是放在读数据通道(W),大家可以想一想(我的理解是一方面为了不破坏读地址通道的完整性,另一方面是读数据本身就少了一根字节选通信号(strb[*]))。
valid、ready:用于实现双向握手机制,仅当valid和ready信号均为高电平时,传输的数据有效。例如在写通道中,主端检测到从端ready信号为高时,表示从端可以接受写命令,此时主端将valid信号拉高并保持一个时钟周期,为发出一个写命令。
写地址通道:信号以AW为开头,该通道用于主端向从端发起写命令,并规定写入地址、突发的大小、长度、类型等信息。信号包括s_axi_awaddr、s_axi_awburst、s_axi_awcache、s_axi_awlen、s_axi_awlock、s_axi_awprot、s_axi_awready、s_axi_awsize、s_axi_awvalid。
s_axi_awaddr:起始写入地址,随写命令一同发出,固定了本次写入数据的起始存储地址。AXI协议采用字节寻址,例如一组32位的数据,写入后,则会占用4个地址。(测试的时候没注意,被小坑了一把)
s_axi_awlen:AXI中数据以突发的形式传输,每个突发分为多个transfer,该信号决定了本次突发中transfer的个数,可以理解为该突发中数据的组数,例如s_axi_awlen为1时,表示该突发中包含两组数据。
s_axi_awsize:该信号规定了突发中数据的位宽,但位宽的设置不能超过总线位宽(可以简单理解为不能超过调用的ram的位宽)。如下表所示:
s_axi_awsize 位宽(字节)
3'b000 1
3'b001 2
3'b010 4
3'b011 8
3'b100 16
3'b101 32
3'b110 64
3'b111 128
s_axi_awburst:该信号规定了突发的三种传输类型:fixed、incr、wrap。如下图所示:
awburst type
2'b00 fixed
2'b01 incr
2'b10 wrap
2'b11 reversed
fixed:该类型中,burst中的所有数据都将使用初始地址,该类型适合对某一地址进行多次数据更新。
incr:该类型中,burst中的后续数据的存储地址会在初始地址的基础上进行递增,递增幅度与传输宽度相同,常用于ram等通过地址映射的存储器进行数据读写。
wrap:该类型中,burst中的数据首先根据起始地址得到绕回边界地址(wrap boundary)与最高地址。当前地址小于最高地址时,WRAP 与 INCR 类型完全相同,地址递增。但到递增后的地址到达最高地址后,地址直接回到绕回边界地址,再进行递增,就这样循环往复。
s_axi_awburst、s_axi_awlen、s_axi_awsize:这三个信号一起,规定了数据写入时的位宽、长度以及存储形式,同时要注意的是,一次突发的地址范围不能超过4KB(为什么?不知道,没有具体去了解)。
写数据通道:信号以W开头,该通道用于主端往从端写入数据、选通信号等信息,信号包括s_axi_wdata、s_axi_wlast、s_axi_wready、s_axi_wstrb、s_axi_wvalid。
s_axi_wdata:传输写入数据。
s_axi_wstrb:在突发位宽与总线位宽不同时,使用该信号指示写入数据中的有效字节,当二者位宽相同时,该信号全部拉高即可。
s_axi_wlast:指示本次突发传输的最后一组数据。
写响应通道:信号以B开头,该通道用于主端完成对从端的数据写入后,从端发给主端的反馈信息,信号包括:s_axi_bready、s_axi_bresp、s_axi_bvalid。
s_axi_bresp:该信号由目的端反馈数据写入情况,源端根据该信号判断数据是否写入成功。(一般都是成功的,无视就好了,大不了丢包),如下表所示:
resp 反馈情况
2‘b00 OKAY/常规访问成功
2‘b01 EXOKEY/独占访问成功
2‘b10 SLVERR/从端错误
2‘b11 DECERR/解码错误
读地址通道:信号以AR开头,该通道用于主端向从端发起读命令,并规定读出地址、突发的大小、长度等信息,信号包括s_axi_araddr、s_axi_arburst、s_axi_arcache、s_axi_arlen、s_axi_arlock、s_axi_arprot、s_axi_arready、s_axi_arsize、s_axi_arvalid。
读数据通道:信号以R开头,该通道用于从端向主端发送读出的数据,并在数据全部读出后,发出反馈信息,信号包括s_axi_rdata、s_axi_rlast、s_axi_rready、s_axi_rvalid。
读地址通道、读数据通道的关键信号与写操作的关键信号使用方式相同,不再重复说明。这里要注意的是,读操作将读反馈信号放入了读数据通道中,没有单独的读响应通道。
除了上面介绍到的数据之外,AXI4接口还包括cache、user等信号线,看手册上讲,应该是可以实现些更多的功能,但是对于大部分我这样的同志来讲,应该是用不上了,悬空放那儿就行了,不用管他。
注释:下面的所有input和output都是站在主机的角度,跟上图中的从机接口图是相反的。
(1)写地址通道/AW
awaddr[n:0]:output,写地址信号,在每次突发传输中只发送首地址即可。
awprot[2:0]:output,写安全信号,每一位都代表两种类型,标识着访问方式是否正常安全。
awvalid:output,写有效信号,由数据发送方输出给数据接收方,代表当前地址有效。
awready:input,写准备信号,数据接收方输出给数据发送方,代表接收方准备好接收地址了。
注释:
在主机当中来说,当写的时候valid就是主到从是output,ready就是从到主是input;
当读的时候主机就是接收方valid就是从到主input,ready就是主到从output。
上面的截图是从机(slave)AXI接口,大家不要看错了,从机反过来就行了,
只要记住valid是数据发送方产生,ready是接收方产生,后面就不做解释了。
(2)写数据通道/W
wdata[n:0]:output,写数据通道,位宽可以根据需求设置,但是都是8的倍数,也就是一个字节。
wstrb[((n+1)/8)-1:0]:output,字节选通信号,每一位代表wdata中的一个字节,当为1的时候,代表wdata对应字节有效,比如wstrb[N]=1,则代表wdata[(N+1)*8-1:N*8]这个字节有效。
wvalid:output,写有效信号,由数据发送方产生输出给数据接收方,代表当前数据有效。
wready:input,写准备信号,数据接收方产生输出给数据发送方,代表接收方准备好接收数据了。
(3)写响应信号/B
bresp[1:0]:input,写数据响应信号,由数据接收方产生,代表数据的接收是否正常,有4种状态。后面系列会补充。
bvalid:input,响应有效信号,由数据发送方产生输出给数据接收方,代表当前响应有效。
bready:output,响应准备信号,数据接收方产生输出给数据发送方,代表接收方准备好接收响应。
(4)读地址通道/AR
araddr[n:0]:output,读地址信号,在每次突发传输中只发送首地址即可。
arprot[2:0]:output,读安全信号,每一位都代表两种类型,标识着访问方式是否正常安全。
arvalid:output,读有效信号,由数据发送方输出给数据接收方,代表当前地址有效。
arready:input,读准备信号,数据接收方输出给数据发送方,代表接收方准备好接收地址了。
(5)读数据通道/R
rdata[n:0]:input,读数据通道,位宽可以根据需求设置,但是都是8的倍数,也就是一个字节。
rresp[1:0]:input,读数据响应信号,由数据接收方产生,代表数据的接收是否正常。
rvalid:input,读有效信号,由数据发送方产生输出给数据接收方,代表当前数据有效。
rready:output,读准备信号,数据接收方产生输出给数据发送方,代表接收方准备好接收数据了。
BRESP[1:0]:
定义了突发的类型 2'b00=>OKAY
,2'b01=>EXOKAY
, 2'b10=>SLVERR
,2'b11=>DECERR
。
- OKAY:正常访问成功,还可以指示独占访问失败。
- EXOKAY:指示独占访问的部分已成功。
- SLVERR:主机正常发送但是从机没有正常接收。
- DECERR:主机没找到从机。
AWBURST[1:0]:
定义了突发的类型 2'b00=>FIXED
,2'b01=>INCR
,2'b10=>WRAP
。
-
- FIXED :固定突发模式,burst 中的每一个 transfer 的地址都相同。
- INCR:递增突发模式,burst 中的每一个 transfer 的地址都是在前一个传输地址的增量上增加一个 transfer 的大小。例如在一个 transfer 的 Bytes = 4 的突发中,每个 transfer 的传输的地址都是前一个地址加 4。
- WRAP:环绕突发模式类似于递增突发模式。不同的是,如果达到了地址上限,地址会重新回到起始地址。实际上这种方式和数据结构中的用数组构建的循环链表是很类似的。
WSTRB 信号用于标记传输数据中有效的字节,每个 WSTRB 位对应一个字节的位宽,比如数据位宽为 64 位,那么 WSTRB 信号的位宽就是 1 个字节,共 8 位。
AWSIZE[2:0]
AXI4 支持 INCR 突发类型的突发长度在 1 ~ 256 之间,其余类型的突发长度在 1~16 之间。突发长度 Burst_Length = AxLEN[7:0] + 1,需要注意的是突发不能超过 4KB 的地址界限,
WRAP类型的突发长度仅能取 2, 4, 8, 或者 16。
AWSIZE[2:0]:表示一个 transfer 中的 Bytes 个数。 3'b000=>1
,3'b001=>2
,3'b010=>4
,3'b011=>8
,3'b100=>16
,3'b101=>32
,3'b110=>64
,3'b111=>128
。
写数据
在位置 1 的时候主机发送了地址 AWADDR,并且 AWVALID 以及 AWREADY 为高,此时就完成了地址的传送。
在位置 2、3、4 的时候主机发送了数据 WDATA,并且 WVALID 以及 WREADY 为高,此时就完成了数据的传送。在位置 5 的时候主机发送了数据 WDATA 并且由于是最后一个数据因此还发送了 WLAST, WVALID 以及 WREADY 为高,此时就完成了最后一个数据的传送。
在位置 6 的时候从机发送了反馈 OKAY,并且 BVALID 以及 BREADY 为高,此时就完成了全部的数据写过程。
读数据
在位置 1 、2 的时候主机发送了地址 ARADDR,并且 ARVALID 以及 ARREADY 为高,此时就完成了地址的传送。
在位置 2、3、4 、5、6的时候从机发送了数据 RDATA,并且RVALID 以及 RREADY 为高,此时就完成了数据的读取。
在位置 4、6 的时候由于是最后一个数据因此还发送了 RLAST,此时就完成了全部的数据读过程。上面的波形示意图中实际上省略了RRESP这个信号。
操作举例1
// axi4
localparam RESP = 2'b01;
localparam ID = 4'b0000;
// addr
wire C1_S1_AXI_arready;
wire C1_S1_AXI_arvalid;
wire [28:0]C1_S1_AXI_araddr;
wire [7:0]C1_S1_AXI_arlen;
wire C1_S1_AXI_awready;
wire C1_S1_AXI_awvalid;
wire [28:0]C1_S1_AXI_awaddr;
wire [7:0]C1_S1_AXI_awlen;
// respond
wire C1_S1_AXI_bready;//
wire C1_S1_AXI_bvalid;
// data
wire C1_S1_AXI_rready;
wire C1_S1_AXI_rvalid;
wire C1_S1_AXI_rlast;
wire [127:0]C1_S1_AXI_rdata;
wire C1_S1_AXI_wready;
wire C1_S1_AXI_wvalid;
wire C1_S1_AXI_wlast;
wire [127:0]C1_S1_AXI_wdata;
wire [1:0]C1_S1_AXI_arburst;
wire [2:0]C1_S1_AXI_arsize;
wire [3:0]C1_S1_AXI_arid;
wire [3:0]C1_S1_AXI_arcache;
wire C1_S1_AXI_arlock;
wire [2:0]C1_S1_AXI_arprot;
wire [3:0]C1_S1_AXI_arqos;
assign C1_S1_AXI_arburst = 2'b01;//
assign C1_S1_AXI_arsize = 3'b111;// or 3'b001;
assign C1_S1_AXI_arid = ID;//
assign C1_S1_AXI_arcache = 4'b0011;
assign C1_S1_AXI_arlock = 1'b0;//unused
assign C1_S1_AXI_arprot = 3'b000;
assign C1_S1_AXI_arqos = 4'b0000;//unused
wire [1:0]C1_S1_AXI_awburst;
wire [2:0]C1_S1_AXI_awsize;
wire [3:0]C1_S1_AXI_awid;
wire [3:0]C1_S1_AXI_awcache;
wire C1_S1_AXI_awlock;
wire [2:0]C1_S1_AXI_awprot;
wire [3:0]C1_S1_AXI_awqos;
assign C1_S1_AXI_awburst = 2'b01;//
assign C1_S1_AXI_awsize = 3'b111;// or 3'b001;
assign C1_S1_AXI_awid = ID;//
assign C1_S1_AXI_awcache = 4'b0011;
assign C1_S1_AXI_awlock = 1'b0;//unused
assign C1_S1_AXI_awprot = 3'b000;
assign C1_S1_AXI_awqos = 4'b0000;//unused
wire [1:0]C1_S1_AXI_bresp;
wire [3:0]C1_S1_AXI_bid;
assign C1_S1_AXI_bresp = RESP;// ?
assign C1_S1_AXI_bid = ID;//
wire [1:0]C1_S1_AXI_rresp;
wire [3:0]C1_S1_AXI_rid;
assign C1_S1_AXI_rresp = RESP;// ?
assign C1_S1_AXI_rid = ID;//
wire [15:0]C1_S1_AXI_wstrb;
assign C1_S1_AXI_wstrb = 16'hFFFF;//
操作举例2
// axi4
localparam RESP = 2'b01;
localparam ID = 4'b0000;
// addr
wire C1_S1_AXI_arready;
wire C1_S1_AXI_arvalid;
wire [28:0]C1_S1_AXI_araddr;
wire [7:0]C1_S1_AXI_arlen;
wire C1_S1_AXI_awready;
wire C1_S1_AXI_awvalid;
wire [28:0]C1_S1_AXI_awaddr;
wire [7:0]C1_S1_AXI_awlen;
// respond
wire C1_S1_AXI_bready;//
wire C1_S1_AXI_bvalid;
// data
wire C1_S1_AXI_rready;
wire C1_S1_AXI_rvalid;
wire C1_S1_AXI_rlast;
wire [127:0]C1_S1_AXI_rdata;
wire C1_S1_AXI_wready;
wire C1_S1_AXI_wvalid;
wire C1_S1_AXI_wlast;
wire [127:0]C1_S1_AXI_wdata;
wire [1:0]C1_S1_AXI_arburst;
wire [2:0]C1_S1_AXI_arsize;
wire [3:0]C1_S1_AXI_arid;
wire [3:0]C1_S1_AXI_arcache;
wire C1_S1_AXI_arlock;
wire [2:0]C1_S1_AXI_arprot;
wire [3:0]C1_S1_AXI_arqos;
assign C1_S1_AXI_arburst = 2'b01;//
assign C1_S1_AXI_arsize = 3'b111;// or 3'b001;
assign C1_S1_AXI_arid = ID;//
assign C1_S1_AXI_arcache = 4'b0011;
assign C1_S1_AXI_arlock = 1'b0;//unused
assign C1_S1_AXI_arprot = 3'b000;
assign C1_S1_AXI_arqos = 4'b0000;//unused
wire [1:0]C1_S1_AXI_awburst;
wire [2:0]C1_S1_AXI_awsize;
wire [3:0]C1_S1_AXI_awid;
wire [3:0]C1_S1_AXI_awcache;
wire C1_S1_AXI_awlock;
wire [2:0]C1_S1_AXI_awprot;
wire [3:0]C1_S1_AXI_awqos;
assign C1_S1_AXI_awburst = 2'b01;//
assign C1_S1_AXI_awsize = 3'b111;// or 3'b001;
assign C1_S1_AXI_awid = ID;//
assign C1_S1_AXI_awcache = 4'b0011;
assign C1_S1_AXI_awlock = 1'b0;//unused
assign C1_S1_AXI_awprot = 3'b000;
assign C1_S1_AXI_awqos = 4'b0000;//unused
wire [1:0]C1_S1_AXI_bresp;
wire [3:0]C1_S1_AXI_bid;
assign C1_S1_AXI_bresp = RESP;// ?
assign C1_S1_AXI_bid = ID;//
wire [1:0]C1_S1_AXI_rresp;
wire [3:0]C1_S1_AXI_rid;
assign C1_S1_AXI_rresp = RESP;// ?
assign C1_S1_AXI_rid = ID;//
wire [15:0]C1_S1_AXI_wstrb;
assign C1_S1_AXI_wstrb = 16'hFFFF;//