简介:这份AXI4总线协议学习文档面向SOC设计、FPGA开发及AMBA总线入门者,围绕AXI4的五个独立通道、READY/VALID握手机制、通道信号定义、突发传输、outstanding地址与乱序收发等核心知识点展开,并结合ZYNQ中ARM与FPGA交互场景说明其应用价值。压缩包内仅含1个docx文件,约3.27MB,内容以协议梳理和要点解析为主,适合作为查阅与复习材料。已有431人学习下载,文档对读写地址、读写数据及写回应通道的信号方向与ID标签机制有较完整整理,并讨论了写回应通道独立的原因和握手时序关系,便于读者建立AXI4整体框架、理解关键信号含义,并为后续仿真验证与接口设计打基础。
1. 从快递分拣中心说起:AXI4 总线协议到底是什么
芯片内部几十上百个模块要互相传数据,场面很像一个大型快递分拣中心:处理器核是发货方,各种外设、存储器是收货方,中间得有套规矩说清楚“货单怎么填、包裹怎么放、丢件怎么回话”。AXI4 总线协议就是这套规矩,它规定了发起方和响应方之间用几组信号线交接地址、数据和回应,以及双方在什么时刻才算把一件货交出去。
值得花时间搞懂它,是因为只要碰 SoC 设计、FPGA 加速、IP 核集成这类活,几乎绕不开它。它把读写彻底拆开、允许一次只报一个首地址就连搬一串数据、还允许好几笔任务同时在路上跑——这几条特性直接决定了系统的吞吐能到哪。
2. 五条通道各管一摊:AXI4 为什么要把读写拆开
AXI4 最显眼的特征,是把一次传输拆成五条互不干扰的通道。这五条通道各自负责一件事,方向明确,谁也不用等谁把话说完。
2.1 五条通道分别运什么货
写操作占三条:写地址通道负责告诉从机“这批货送到哪个门牌”,写数据通道负责把货本身搬过去,写回应通道负责事后回一句“收到没、有没有问题”。读操作占两条:读地址通道报门牌,读数据通道既送货又顺便捎回状态。
| 通道 | 方向 | 主要信号 | 干什么 |
|---|---|---|---|
| 写地址通道 | 主→从 | AWID、AWADDR、AWLEN、AWSIZE、AWBURST、AWVALID、AWREADY | 报写门牌和这批货的规格 |
| 写数据通道 | 主→从 | WDATA、WSTRB、WLAST、WVALID、WREADY | 搬写数据,标出哪几个字节有效 |
| 写回应通道 | 从→主 | BID、BRESP、BVALID、BREADY | 一次写任务结束后回一句总账 |
| 读地址通道 | 主→从 | ARID、ARADDR、ARLEN、ARSIZE、ARBURST、ARVALID、ARREADY | 报读门牌和要读多少 |
| 读数据通道 | 从→主 | RID、RDATA、RRESP、RLAST、RVALID、RREADY | 搬读数据,顺带捎回状态 |
把这张表竖着看会发现一个规律:除了 READY,每条通道里其余信号的方向都一致,都是从主设备指向从设备,或者反过来。也就是说,通道内部几乎是单行道,只有 READY 这一根是逆着方向回来的。这种“单向为主 + 一根回流线”的结构,正是后面握手机制能做得这么简洁的原因。
2.2 拆开之后换来了什么:流水线、outstanding 和乱序
地址和数据从一开始就被放进了不同的通道,这意味着它们之间没有固定的相位关系。主设备完全可以先把三笔写任务的地址一口气全报出去,再慢慢一个一个送数据。这种“地址先跑、数据后到”的做法,就是流水线操作的基础。
由此衍生出两个能力。第一个是待执行地址(outstanding):从机如果有缓冲能力,可以在上一笔还没处理完的时候继续接收新的地址。假设从机的 outstanding 是 4,那它就能一边处理第一笔,一边跟后面三笔的地址通道完成握手。这个数字直接决定了总线上能压多少活。
第二个是乱序收发。每条通道里都带 ID 标签,同一个 ID 的任务必须按顺序完成,不同 ID 之间则没有任何顺序约束。于是从机可以让耗时短的先返回,耗时长的慢慢来,主设备靠 ID 就能把回来的数据对上号。打个比方,五个人同时去窗口办事,办得快的先走,只要每张号票上的号码对得上,谁先谁后没人计较。
还有一个常被忽略的优点:AXI4 允许在通道中间插入寄存器。长走线的时序收敛在深亚微米工艺下是老大难问题,而通道是单向的,插一级寄存器只增加一拍延迟,不会破坏协议语义,相当于给设计者留了调节时序的余地。
2.3 写回应为什么被单独拎出来
读数据通道里带着读回应,写回应却单独占一条通道,这个不对称常让人犯嘀咕,本质上是一个频率问题。
写数据如果是一次 16 拍的突发传输,从机没有必要每收一拍就回一句,因为只有收齐最后一拍、WLAST 拉高之后,才能判断这批数据完整不完整,这时候回应的意义才成立。所以一次写任务只回一次,16 拍里回应的使用频率是十六分之一;突发越长,这个比例越低。
读操作不一样。读数据每一拍都带着自己的状态,从机可能前 8 拍状态正常,后 8 拍因为自身原因想告诉主设备“这段数据有问题”,可主设备明明成功访问到了它,并没有走错门。既然状态是逐拍产生的,回应就必须逐拍捎回去,16 拍就是十六分之十六的使用率。把写回应单独拎出来做一条低频通道,比在写数据通道里塞一堆闲置的回应线要节省得多。
3. READY 和 VALID 的握手:一次传输到底怎么算数
通道拆得再漂亮,最后都要落在两根信号上:VALID 和 READY。VALID 为高表示发起方已经把地址、数据或控制信息摆稳了,READY 为高表示接收方这一拍能接住。这两根信号,就是 AXI4 全部时序的骨架。
3.1 握手成功的唯一标准:同一拍上双方都点头
握手的判定条件只有一条:在某个时钟上升沿,VALID 和 READY 同时为高。缺一个都不算数。VALID 高而 READY 低,说明货已经摆出来了但对面腾不出手,货和 VALID 必须一直保持不动,等到 READY 起来的那一拍才算交出去;READY 高而 VALID 低,说明对面早早张开了手,但货还没到,这一拍就空过。
有两种做法在实际设计里要避免。一种是把 VALID 写成“看 READY 的脸色”才敢拉高,这会造成组合环路,而且违反协议精神——VALID 一旦拉高就不能因为 READY 没来而撤回。另一种是把 READY 做成只在某一拍有效,错过了就再也不给,这会让发起方永远卡死。
用一句通俗的话收束:握手是两只手同时伸出来的那一瞬间才算握上,谁先伸手都行,但握上之前谁都不能把手缩回去。
3.2 通道之间的先后顺序:哪些能抢跑,哪些必须等
单条通道内部的规则很简单,通道之间的先后关系才是容易翻车的地方。官方文档用单箭头和双箭头来标注:单箭头表示被指向的信号可以在源头信号有效之前或之后有效,双箭头表示被指向的信号只能在源头信号有效之后才有效。
读过程涉及 ARVALID、ARREADY、RVALID、RREADY 四根信号。读地址通道的两根之间没有指定顺序,谁先来都行;但 RVALID 必须等 ARVALID 和 ARREADY 都完成握手之后才能拉高——道理很直白,门牌都没报,从机凭什么给你送数据。RVALID 和 RREADY 之间同样没有强制先后。
写过程稍复杂,涉及 AWVALID、AWREADY、WVALID、WLAST、WREADY、BVALID、BREADY。写地址通道的握手和写数据通道的握手之间没有必须的时间关系,数据可以先于地址到达,地址也可以先于数据到达。但 BVALID 有硬性前提:写地址通道和写数据通道都必须握手完成,并且写数据通道的最后一拍必须带着 WLAST。换句话说,回应是总账,账没结完不能回。
提示:BREADY 可以在 BVALID 之前拉高,也可以在其之后拉高,两条路径都合法。这一点和很多人想象的“必须等回应来了才能表示准备好”相反。
3.3 从 awready 开始写:一段最小可用的从机握手逻辑
对着一长串信号不知道从哪下手是常态。一个可行的切入方式是反过来:先盯住输出信号,把 output 一个个实现掉,实现过程中自然需要的中间变量也就跟着出来了。从写地址通道的 awready 开始最顺。
// 最朴素的 awready 生成:见到 valid 就回一个周期的 ready always @(posedge aclk or negedge aresetn) begin if (!aresetn) awready <= 1'b0; // 复位期间一律不接地址 else if (awvalid && !awready) awready <= 1'b1; // 这一拍给出 ready,下一拍完成握手 else awready <= 1'b0; // 握手完成后立即撤销,等待下一次 end这段代码能跑通最简单的仿真,但它藏着一个问题:只要 awvalid 有效,从机就无条件响应。可从机真的有这个能力吗?如果内部只有一级地址寄存器,第二笔地址进来就会把第一笔覆盖掉,仿真里看着波形对,上板就出玄学问题。
修正的办法是把 outstanding 能力显式写进判断条件里:
// 带缓冲上限的版本:从机最多同时挂两笔未完成的写任务 wire can_accept = (outstanding_cnt < 2); // outstanding_cnt 记录在途任务数 always @(posedge aclk or negedge aresetn) begin if (!aresetn) awready <= 1'b0; else awready <= awvalid && can_accept && !awready; end // outstanding_cnt 在地址握手时加一,在写回应握手时减一 always @(posedge aclk or negedge aresetn) begin if (!aresetn) outstanding_cnt <= 2'd0; else if (aw_hs && !b_hs) outstanding_cnt <= outstanding_cnt + 2'd1; else if (!aw_hs && b_hs) outstanding_cnt <= outstanding_cnt - 2'd1; end这里的aw_hs是awvalid && awready的握手成功标志,b_hs是bvalid && bready。参数2就是从机自己声明的 outstanding 深度,写死在代码里也行,做成模块参数更利于复用。改动之后,从机在缓冲满了的时候会主动把 awready 压住,主设备自然就等着,不会出现地址被覆盖的情况。
4. 突发传输的三个旋钮:LEN、SIZE、BURST 各调什么
突发传输是 AXI4 效率的主要来源:只报一个首地址,后面一串数据按规则自动推算地址,省掉了每拍都报地址的开销。要把它用对,得先弄清三个旋钮各自管什么。
4.1 LEN:一趟搬几个包裹,以及为什么是 8 位
AWLEN 和 ARLEN 描述这一趟传多少拍。它是 8 位,但表示的是“拍数减一”,LEN=0 意味着传 1 拍。这个减一的约定初学者最容易记错,写激励时多写一拍、少写一拍都算错。
不同突发类型下能取的范围还不一样。递增类型(INCR)可以传 1 到 256 拍,这是唯一能跑满 256 的长度;固定类型和回环类型最多只能到 16 拍;回环类型更严格,只能是 2、4、8、16 这四个值。设计从机时如果偷懒不检查这个范围,遇到不合规的主设备就可能做出错误行为。
4.2 SIZE 与 WSTRB:包裹多大,哪几个字节算数
AWSIZE 和 ARSIZE 是 3 位,表示每一拍传多少个字节,具体字节数是 2 的 SIZE 次方。
| AxSIZE | 每拍字节数 | AxSIZE | 每拍字节数 |
|---|---|---|---|
| 3'b000 | 1 | 3'b100 | 16 |
| 3'b001 | 2 | 3'b101 | 32 |
| 3'b010 | 4 | 3'b110 | 64 |
| 3'b011 | 8 | 3'b111 | 128 |
当 SIZE 小于总线位宽时,就出现了窄传输:数据线明明有 32 位宽,但这一趟只传 8 位,剩下的位怎么办?靠写选通信号 WSTRB 标出来。WSTRB 的每一位对应数据总线上的一个字节,32 位总线就有 4 位 WSTRB,WSTRB[3] 管的是 WDATA[31:24] 这一段。
举个具体例子:32 位总线,SIZE 设为 1 字节,递增类型,那么连续四拍的 WSTRB 会依次是 4'b0001、4'b0010、4'b0100、4'b1000,每拍只点亮一个字节的位置。同样的机制也用于非对齐传输:假设起始地址是 0x07,落在 0x04 到 0x07 这个字的最后一个字节上,那么首拍的 WSTRB 就只点亮对应那个字节,后续拍再按正常节奏走。这条特性让主设备不必为了对齐而额外拆包,省了不少麻烦。
4.3 BURST 与 4KB 边界:地址怎么走才不出事
AWBURST 和 ARBURST 是 2 位,三种取值对应三种地址推进方式。
| AxBURST | 类型 | 地址行为 |
|---|---|---|
| 2'b00 | 固定 | 每一拍都写同一个地址 |
| 2'b01 | 递增 | 每拍地址按 SIZE 递增 |
| 2'b10 | 回环 | 递增到边界后回到起始地址继续 |
| 2'b11 | 保留 | 不使用 |
固定类型适合反复写同一个寄存器,比如往一个 FIFO 入口连续灌数据。递增是最常用的,地址一路往上走。回环稍微绕一点:地址递增到某个回环边界后,会跳回起始地址重新走。假设传输长度 8 拍、每拍 2 字节、起始地址 0x02,那么地址会在一个 16 字节的窗口内循环,走到窗口顶端就折回起点。算回环边界有个公式,通俗地说就是把起始地址向下取整到“每拍字节数乘以拍数”这个块的边界上,走到这个块的上沿就折返。
比回环更容易踩坑的是一条硬约束:一次突发不能跨越 4KB 边界。原因是地址译码的最小粒度就是 4KB,主设备地址的低 12 位以上才用来选从机。如果一趟突发从 0x0FF8 开始连传 16 拍、每拍 4 字节,后半段就跑到 0x1000 之后去了,那已经属于另一个从机的地盘,数据会被错误地写进别人家。这个坑在仿真里未必暴露,因为很多验证环境只挂了一个从机;等集成到真实系统里才炸,那时候排查成本就高了。
5. 易混概念辨析:AXI4 里最容易踩坑的几组区别
信号看得懂、握手也背下来了,真到自己写或者审别人代码时还是容易混淆。下面几组是笔者见过频率最高的。
5.1 五个通道不等于五条独立总线
易混点:把“五通道”理解成芯片里有五组物理线束各跑各的。
为什么混:通道这个词本身有歧义,网络里的通道往往指完全独立的链路。
怎么区分:五条通道共享同一套时钟和复位,共享同一对主从设备的端口,只是把不同类型的信号按功能分组。写地址和写数据虽然分在两条通道,但它们服务的是同一个写任务,靠 AWID 之类的信号建立关联;从机的写数据通路必须知道当前这拍数据属于哪个地址,这份对应关系是由设计者维护的,不是协议自动送过来的。判断方法很简单:看端口声明,一个 AXI4 从机模块只有一组时钟复位输入,五条通道是在同一个模块边界上并列展开的。
5.2 AXI4 不等于 AXI4-Lite,也不等于 AXI3
易混点:看到“AXI4”就以为所有 AXI 接口都支持突发和乱序。
为什么混:名字前缀一样,日常交流里又常被简称为“AXI”。
怎么区分:AXI4-Lite 是精简版,砍掉了突发,每笔传输只有一拍,也没有 ID 标签和乱序能力,适合挂寄存器配置这类小流量场景。AXI3 是老版本,写数据通道里还有 WID,支持写数据交织,也就是说来自不同任务的写数据可以穿插着送;AXI4 取消了 WID,写数据必须连续,一个写任务的数据送完才能开下一个。所以看到端口里有 WID,基本可以判定这是 AXI3 风格的接口。
5.3 VALID 先拉高还是 READY 先拉高,不是对错问题
易混点:以为协议规定了 VALID 必须先于 READY,或者反过来。
为什么混:很多教程的时序图只画了其中一种情况,看多了就以为是唯一解。
怎么区分:协议对这两根信号的先后没有强制要求,三种情况都合法——VALID 先来、READY 先来、同时来。真正有约束的是:VALID 一旦拉高就必须保持到握手完成,中间不能因为对方没响应就撤回;READY 则可以由接收方按自己的节奏给出。审查代码时该盯的是“VALID 有没有在中途掉下去”,而不是“谁先起来”。
5.4 突发长度看 LEN 还是看拍数,差了一个一
易混点:把 AWLEN 直接当成拍数用。
为什么混:多数信号的名字和含义是一一对应的,唯独这个做了偏移。
怎么区分:AWLEN 的值等于拍数减一。LEN=0 是一次单拍传输,LEN=3 是四拍。写验证用例时如果按 LEN 的数值去数拍数,永远会少传一拍,波形上表现为数据没送完就从机先回了回应,或者主设备一直等 WLAST 等不到。建议在代码里统一用burst_len = awlen + 1这个中间变量,后面全用 burst_len,从源头上掐掉这个错。
5.5 outstanding 深度不等于突发长度
易混点:把“能挂几笔任务”和“一笔任务能传几拍”混为一谈。
为什么混:两者都跟“一次处理多少”有关,数值上又都常见 4、8、16 这类数字。
怎么区分:突发长度描述的是一笔任务内部搬多少拍数据,由 LEN 决定,上限 256;outstanding 描述的是从机能同时容纳多少笔尚未完成的任务,由从机内部的缓冲深度决定,协议本身不规定上限。一个 outstanding 为 2 的从机,完全可以接收长度 256 的突发,只是第二笔得等第一笔腾出缓冲。反过来,outstanding 很深的从机也可能只处理长度 1 的传输。调性能时这两个参数要分开看:突发长度影响单笔的效率,outstanding 影响并发的效率。
6. 什么时候该上 AXI4,什么时候别硬上
协议本身的特性摆在那里,但选型不是越强越好。满配 AXI4 的信号数量相当可观,一个从机端口动辄上百根线,对小模块来说这份开销未必值得。
| 场景 | 建议 | 理由 |
|---|---|---|
| 大批量数据搬运,如加速器与内存之间 | AXI4 | 突发加乱序,吞吐能拉满 |
| 寄存器配置、状态读取 | AXI4-Lite | 每笔一拍,省掉突发和 ID 逻辑 |
| 低速外设挂载 | 更轻量的总线 | AXI4 的握手开销与收益不匹配 |
| 需要多笔任务并发 | AXI4,关注 outstanding 深度 | 深度不够,乱序优势发挥不出来 |
| 长走线、时序吃紧 | AXI4 加中间寄存器 | 单向通道允许插拍而不破坏语义 |
判断标准可以简化成三个问题。第一,数据是不是成批走?如果每次只读写一个 32 位寄存器,突发的价值等于零,AXI4-Lite 足够。第二,带宽是不是瓶颈?如果模块本身每秒就产生几兆数据,给它配一条满配总线纯属浪费端口和逻辑资源。第三,从机的缓冲能力跟不跟得上?主设备支持很深的 outstanding,从机却只有一级缓冲,等于高速路口接了个单车道,最后还是要堵。
有三种常见用法会造成问题。一是把 ID 全部接成常数,乱序能力直接作废,多笔任务的返回顺序被强行串行化,本来能并发的场景又变回排队。二是为了省事不给 AWREGION 做译码,把多个分离的地址空间硬塞进一个连续地址段,后面想扩地址空间就得改动所有上层代码。三是地址位宽随手设得很大,看着不占多少逻辑,但对小容量的从机来说,多出来的高位全在做无意义的比较,反而增加时序压力。
有一次笔者图省事,把一个只读写四个寄存器的低速模块直接挂上了满配 AXI4 从机,理由是“复用现成的模板,改起来快”。功能仿真一路绿灯,综合出来面积比模块本身的逻辑还大,时序报告里那几条绕来绕去的地址比较路径特别扎眼。后来改成精简接口,面积掉了一大截,联调时间也短了。从那以后我每次接一个新模块,都先问一句:它是真的要搬大批数据,还是只是被“AXI4 听着高级”这句话带偏了?把这三个旋钮和那五个通道的脾气摸清楚,剩下的就是按需裁剪。希望帮到你。
本文还有配套的精品资源,点击获取