ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AXI总线乱序与交织机制详解:从Transaction到Transfer的硬件实现与验证避坑指南

AXI总线乱序与交织机制详解:从Transaction到Transfer的硬件实现与验证避坑指南 几年前我排查过一个特别诡异的失败用例多主AXI互联环境里读数据偶尔对不上波形里明明R通道每个beat的数据都正确但scoreboard比对时总在某个ID上报错。查了两天最后定位到问题根本不在数据通路而是互联把两个master的响应按ID合并时发生了碰撞——两个master恰好用了相同的ID而互联内部又没做端口前缀扩展。那一次经历让我彻底意识到乱序和交织这种协议层的概念落到RTL和验证环境里到处都是暗坑。这篇文章想把这些年积累的经验系统梳理一遍。围绕AXI总线的乱序out-of-order与交织interleaving机制从transaction和transfer两个粒度的区别讲起再深入到硬件实现、验证配置和实际排错。适合正在读AMBA AXI协议但觉得规范太抽象的同学也适合已经在写AXI互联或搭验证环境的工程师希望能帮你们少踩几个我踩过的坑。1. 乱序为什么存在协议给的是许可证不是说明书1.1 “顺序错觉”从哪来很多刚接触AXI的人会默认认为master发出去的一系列事务会按顺序完成。这个错觉不难理解——AHB/APB时代就是严格顺序总线读完一笔再发下一笔总线天然替你维护了顺序。AXI引入独立通道和ID机制之后顺序保证被有意识地拆掉了。AMBA AXI协议包括AXI3和AXI4并没有要求master发出去的不同ID事务按顺序返回。它只是给所有组件发了一张“许可证”允许总线同时有多个事务在飞outstanding允许从设备或互联对事务重新排序返回。协议真正保证的是每笔事务内部的数据传输顺序以及同ID读事务之间的顺序约束。换句话说AXI不承诺“你按什么顺序发我按什么顺序回”它承诺的是“你一定能认出哪笔响应属于哪个事务”。这个设计决策不是拍脑袋。总线是片内性能的核心瓶颈如果所有事务都强顺序执行任何一个慢速从设备都会堵住后面所有无关事务。允许乱序等于把“顺序保险”从协议层交还给了体系结构设计者让他们根据实际场景去权衡。1.2 没有乱序时的性能损失用一个具体场景说明。假设master向DDR控制器发起两笔读事务A是8拍的长突发事务B是1拍的短突发。如果总线严格按顺序处理B只能排在A后面而DDR内部对bank的调度可能恰恰让B对应的地址可以立刻返回但总线不让它“插队”master只能白等A的全部数据回来。乱序之后B可以先回来master的处理单元能提前开始干活整体时延明显下降。写方向也有类似收益。多个master同时往从设备写数据如果从设备有多个写缓冲区它完全可以在内部并行完成写请求然后按完成的先后顺序把B响应发回去而不是被迫按各个master发地址的先后排队。对master来说它关心的是“我的写到底进没进存储”至于总线怎么调度反而是其次。所以乱序本质上是用“完成顺序的不确定性”换“总线利用率”属于典型的以时间换吞吐。1.3 乱序的代价责任转移乱序的收益不是白拿的。协议把顺序保证的责任从总线转移到了组件设计者身上Master侧必须有办法区分返回数据属于哪笔事务靠ID、靠缓冲队列。互联侧必须为每笔in-flight事务维护元数据包括ID、地址、长度、保护位等。从设备侧要能应对多笔事务并行到达并对响应提交做重排序。这个“责任转移”正是很多设计里bug的根源。很多RTL工程师写单通道逻辑很熟练一到多ID事务管理就出问题原因就是没把乱序的代价想清楚。后面第4节我会详细讲硬件实现里的坑。2. transaction与transfer别把两层粒度混为一谈2.1 一次握手还是一笔事务协议里有两个词经常被混用transaction和transfer。实际上它们的粒度完全不同。transfer一次完整的握手。在某个通道上发生一次地址或数据的传输比如AR通道上valid/ready同时拉高的那个周期。transaction一次完整的总线操作。包含地址阶段、数据阶段可能包含多个transfer和响应阶段。举个例子master发起一笔长度为16的INCR读AR通道上只有1次地址transferR通道上会有16次数据transfer最后以RLAST标记结束之后还要处理对应的响应语义。这整个过程中的每一次R握手都是一个transfer而整笔操作才算一个transaction。理解这个区分对后面讨论乱序和交织非常关键。乱序发生在transaction粒度上交织发生在transfer粒度上。把这两个粒度搞混看协议规范时就会越看越糊涂。2.2 读通道RID决定响应归属读事务的地址在AR通道发出数据在R通道返回。多个读事务可以同时outstanding返回通道上每一拍R数据都必须携带RID。master靠RID判定这拍数据属于哪笔事务的哪个beat。一个特别容易记错的地方是AXI4中相同ID的多个读事务虽然可以outstanding但R数据返回的顺序必须和AR通道发出这些事务的顺序一致。也就是说同ID下读事务不允许乱序返回真正的乱序只发生不同ID之间。从设备或互联如果做不到这个约束master很容易把数据写到错误的缓冲里。而一旦同ID读事务乱序协议层面的行为直接就是未定义的连救的机会都没有。2.3 写通道三个阶段的独立与耦合写事务比读事务拆得更散AW通道发地址W通道发数据B通道返回写响应。三个通道各有valid/ready握手意味着地址、数据、响应是三个独立的流水阶段。AXI3时代W数据通过WID与地址关联允许多个写事务在W通道上交织。比如事务A和事务B的地址都已经发出W通道可以交替发A的第0个beat、B的第0个beat、A的第1个beat……从设备靠WID把这些beat重新归类。AXI4直接移除了WID端口。协议不再支持写数据交织W通道必须按AW发出的顺序连续发送每个事务的数据。这个约束放在master侧从设备就不需要为乱序写数据做复杂的重新对齐。一个直观理解是写通道的数据方向是固定的master到slavemaster完全可以在自己这一侧把顺序排好从设备端承受乱序的成本不划算。3. 交织interleaving乱序的另一种面孔3.1 交织与乱序的边界交织和乱序经常被放在一起说但它们是两个维度乱序out-of-order事务的完成顺序与发起顺序不同体现在响应或返回数据的顺序上。交织interleaving同一通道上来自不同事务的数据beat交替出现。可以这么记乱序是“事务级”的重排交织是“beat级”的重排。交织可以作为乱序的一种实现手段比如读返回时把耗时短的burst先发出去但它本身不等于乱序。一个系统可以完全顺序完成事务但支持数据通道交织以减少气泡反之一个系统也可以事务乱序完成但每个事务内部的beat可能连续输出并不交织。3.2 读交织是怎么发生的当多笔读事务同时in-flight时R通道的仲裁器需要决定当前周期输出哪笔事务的哪个beat。一种常见的公平调度是轮询round-robin各ID的返回队列于是一个周期输出ID0的beat下一个周期输出ID1的beat看起来就是“你一拍我一拍”。这种在R通道上交替输出不同事务数据的行为就是读交织。对master来说接收交织数据比接收乱序数据要宽裕一些——它只要按RID把每个beat送到对应事务的缓冲即可。真正需要花代价的是缓冲深度master必须同时容纳多笔事务的部分数据不能简单地用一块先到先得的FIFO否则当一个事务的缓冲被占满而另一个事务还没开始返回时总线就得停下来等。3.3 为什么AXI4干脆砍掉写交织AXI3的WID为写交织提供了协议基础但实际硬件收益并不高。写通道方向固定master可以在自己这一侧把顺序排好而让从设备支持写交织意味着它的写数据缓冲必须按WID做多路分类面积和复杂度都直线上升。AXI4发布时从设备设计者们普遍希望接口更简单于是规范直接删掉WID约定写数据在数据通道上不交织同一ID的写事务按地址发出顺序连续完成。这个取舍非常现实用少量总线利用率的损失换取从设备面积、验证难度的显著下降。给各位一个判断经验在验证环境里看到R通道交织不用慌张这是常见优化但如果在AXI4接口上看到W通道交织那不是VIP配置错误就是设计违反了协议——这种问题必须当作协议违例处理而不是当作性能特性放过。4. 硬件实现中的乱序窗口五个绕不开的坑4.1 乱序窗口到底有多大做设计或验证时经常要回答一个问题“这个总线最多能乱序多少笔”答案取决于两个因素ID空间大小ID位宽是N bit理论上最多有2^N个不同ID在总线上去重区分。每个ID的outstanding深度系统允许同一个ID同时存在多少笔未完成事务。更严格说AXI允许同一个ID多笔outstanding读返回必须按序所以总乱序窗口不是简单等于2^N而是受缓冲资源限制后的所有outstanding事务总和。互联设计里常见的做法是为每笔事务分配一个slot记录ID、地址、burst长度等slot数量就是系统实际能承受的乱序深度上限。在设计评审时我一般会建议团队先画一张表master数量、ID位宽、每端口outstanding上限、互联内部slot总数四个数字放在一起看。任何一个数字过低都会成为系统性能瓶颈过高则面积和时序压力陡增。4.2 ID复用看似正确实则是定时炸弹“ID复用”指在一笔未完成事务还占着某个ID时master就用同一个ID发新事务。协议并不禁止这个行为同ID多笔outstanding是允许的但硬件必须能区分同一ID下的不同事务实例。问题在于很多互联内部用ID做索引去查存储如果只存了一个ID的状态新事务一到就把旧事务的状态覆盖旧事务响应返回时数据就错配了。这种bug在仿真里很难直接暴露因为只有当两笔同ID事务的时间间隔小于乱序窗口时才会触发。跑一万次随机可能都碰不到一到真实业务流量几微秒之内就出错。我的建议是设计阶段就给每个master规划ID分配策略避免同ID事务扎堆验证环境里专门构造“同ID背靠背”的序列定向打击这类实现缺陷。4.3 缓冲深度与反压乱序的代价是master和互联必须提供足够缓冲。举个具体数字master发出8笔outstanding读每笔长度是4理论上它可能同时收到最多32拍R数据。如果master的R缓冲只能装16拍那么RVALID必然被周期性拉低反压随之出现而反压会传导回从设备原本想优化的时延反而更差。我在实测里见过不少性能问题根因不是乱序策略而是乱序窗口开太大但下游缓冲没跟上。所以做AXI性能调优时第一件事永远是看缓冲深度图每个通道、每个ID队列、每个slot的占用率。哪里先打满哪里就是瓶颈。4.4 死锁不是危言耸听死锁最容易出现在读和写的交叉依赖上。典型场景master向从设备发起写事务A从设备的写队列已满并开始反压master又发起读事务B而从设备的调度规定读请求必须排在已接收写请求之后完成。这样一来B永远排不上队。如果master在发出B之前需要等总线上的空间释放而空间又被满的写队列占着整个系统就卡死。验证时不能只盯着单通道打流量。我习惯在随机用例里专门注入读写混合压力并让写队列深度刻意调小让读事务穿插其中。死锁一旦触发波形里最大的特征是“所有通道的valid都高但ready全部为低”看到这个画面基本就是陷入等待循环了。4.5 Outstanding不等于乱序这是最常见的概念混淆。一个AXI接口可以支持32笔outstanding但如果返回顺序永远按AR/AW发出顺序来那它是“高深度的顺序完成”不是乱序。乱序的关键指标是同类事务的完成顺序与发起顺序不同。验证环境如果假设“支持乱序”或“不支持乱序”必须和实际的实现对齐。互联如果支持乱序而scoreboard按顺序比较会报一堆假错反过来互联实现只做了部分乱序比如只读乱序、写不乱序随机用例又覆盖不到那真正的乱序bug就会被漏掉。所以验证环境里一定要把期望顺序模型显式地配置出来并且用断言把它绑住。5. 验证里的乱序实战VIP配置、打印管控与定向打击5.1 把VIP的乱序能力打开Synopsys AXI VIP以及其他主流AXI VIP一般默认都支持outstanding和乱序但需要显式配置才有效果。配置项通常围绕三个方向outstanding深度允许AR/AW通道上同时挂着多少笔未完成事务。返回策略从设备是否允许R通道按非AR顺序返回。数据交织使能是否允许不同事务的数据beat在数据通道上交织输出。以Synopsys AXI VIP为例配置思路类似axi_env_cfg cfg new(); cfg.num_outstanding_reads 16; cfg.enable_read_interleaving 1; cfg.enable_write_interleaving 0; uvm_config_db#(axi_env_cfg)::set(null, *, axi_env_cfg, cfg);注意具体配置项名称会随VIP版本变化不建议照抄网上的代码。打开VIP文档搜索interleave和outstanding两个关键字定位到对应字段后再结合协议需求设置。配置完之后先跑一个最简单的读写测试确认VIP行为符合预期再放开随机约束。5.2 关闭transaction打印让回归跑起来很多Synopsys AXI VIP默认会对每一笔transaction打印大量信息。跑少量用例没问题长时间回归时打印量能让仿真慢好几倍日志文件膨胀到几个GB。网上经常有人问“怎么关闭transaction打印”我实际试过三种有效手段把VIP相关组件的verbosity整体调低用UVM的set_report_verbosity_level_hier(UVM_NONE)或者UVM_LOW能压掉大部分常规打印。关闭transaction recording。VIP内部通常有类似enable_transaction_recording的开关置0后不仅不打印波形数据库里也不会生成transaction记录能节省不少存储。如果只是嫌R通道beat打印太多可以单独把monitor和scoreboard的报告级别调高保留地址通道的概要打印调试时还能看到事务边界。不过要提醒一点关闭打印只是权宜之计。更好的做法是把打印级别降低但保留错误打印和协议违例报告。否则回归一失败日志里什么都没有你还得重新打开全量打印再跑一遍时间成本翻倍。5.3 如何定向构造乱序压力要真正把乱序bug逼出来光靠VIP默认随机往往不够。我常用的定向手段有几种让不同burst长度的事务并发。长burst延迟重短burst容易后发先至天然制造乱序。随机拉低R通道的ready或者随机加大从设备端rvalid之间的延迟模拟不同从设备的返回速度差异。对同ID事务做背靠背发送专门打击“同ID多实例”处理逻辑。在sequence里fork多个读请求并发发出通过response queue把它们的返回顺序打乱再检查scoreboard的ID分组逻辑。这里的核心思路是不要让总线轻轻松松工作。验证的目标是把总线逼到乱序窗口的极限而不是让它在舒适区里跑流水。5.4 scoreboard的乱序匹配与断言写scoreboard时我习惯为每个ID单独分配一个期望队列而不是用一个全局FIFO。参考结构如下class sb_scoreboard; // key: ID, value: queue of expected transactions protected axi_transaction expect_queue[int]; function void put_expect(axi_transaction txn); if (!expect_queue.exists(txn.id)) expect_queue[txn.id] new; expect_queue[txn.id].push_back(txn); endfunction function void check_rdata(axi_transaction rsp); axi_transaction exp expect_queue[rsp.id].pop_front(); // 同ID必须FIFO顺序返回不同ID之间只看各自的队列 if (exp.data ! rsp.data) uvm_error(SCOREBOARD, $sformatf(ID[%0d] data mismatch, rsp.id)) endfunction endclass同时建议加两类断言同ID读事务的返回顺序必须与AR顺序一致一旦违反立即报错。如果设计声明支持写乱序B响应允许乱序如果声明不支持B响应必须与AW发出顺序保持一致。这类断言是验证环境里“对齐协议语义”的最后一道防线。很多RTL bug并不是被直接测试用例抓到的而是先被覆盖率空洞发现再被断言精确捕捉。乱序场景尤其依赖这种组合打法。最后分享一个个人习惯。每接手一个新AXI设计我都会先把几个问题写在笔记本最前面ID位宽多少每个ID允许几笔outstanding读返回是否乱序写响应是否乱序数据通道是否允许交织这五个问题一问完整个总线的行为模型基本就清楚了后面写用例和调波形都会少走很多弯路。AXI的乱序和交织说白了就是协议给你自由但自由越大硬件和验证要承担的责任越大。希望这篇关于transaction到transfer的笔记能帮你把这块啃得更透一点。
返回列表