☰
硬件设计中的outstanding是什么?一文讲清总线在途事务与性能优化
2026/10/22 6:27:05 网站建设 项目流程

先说结论:在硬件设计文档里看到outstanding,八成和“杰出”没关系,它指的是“已经发出、但还在等回应”的总线事务。你做芯片也好,做FPGA的IP也好,只要涉及总线接口、DMA、存储控制器,这个词就躲不掉。

我第一次碰到它是在改一个AXI总线的DMA代码时,代码注释里写着outstanding_depth=8,我当时一个头两个大:outstanding不是“优秀”的意思吗,这个深度是什么鬼?查了一下午资料才弄明白,原来在协议和验证领域,outstanding的意思更接近“未结清的”,跟你信用卡上outstanding balance(未还余额)是同一个用法。一旦理解了这层,原本云里雾里的总线行为就变得非常清晰。你会在两种典型场景下反复见到它:一是总线协议层,二是验证环境层。这一篇我按自己的理解把这两块都拆开讲一遍,再附上一些调优和排坑经验,希望能帮同样被这个词卡住的人省点时间。

1. 到底什么叫outstanding:从词源到技术含义

1.1 一次词义溯源:outstanding并不总代表“优秀”

英文outstanding有两个常用义项:最著名的是“杰出的、出众的”,另一个是“未完成的、未偿付的”。在工程文档里,用的几乎都是后者。金融领域的outstanding debt(未偿还债务)、法律领域的outstanding warrant(尚未执行的拘捕令),用的都是同一个语义:挂在那里还没有了结。

硬件设计里的outstanding,是指一个总线事务(bus transaction)已经从发起端发出,但还没有收到对应的完成响应,处于“在途(in-flight)”状态。这个定义在RTL代码、协议spec、验证环境里是统一的。我曾经在某高校的一个接口项目中看到一条注释,写的是“outstanding read request count”,当时组里的新人盯着“outstanding”看了半天,以为这是某种“优秀读请求”的计数器,后来我带他看完波形,他才意识到这就是“还没返回数据的读请求数量”。

这个词之所以容易让人困惑,是因为同一个单词在不同领域意思差别太大。做一个简单的对照:

语境含义实例
金融未偿清outstanding debt
法律未执行outstanding warrant
硬件协议在途事务outstanding transaction
日常英语杰出outstanding performance

所以以后在spec里看到outstanding,第一反应应该是“账上还挂着的”,而不是“做得优秀的”。

1.2 从流水线心智模型理解outstanding

如果觉得“未完成事务”还是太抽象,可以用流水线来理解。你到窗口办业务,如果你一定要等上一笔业务完全办完才递下一份材料,那效率非常低;如果你一次递出三份材料,让窗口陆续给你出结果,这三份材料对窗口来说,就都是outstanding。

总线也是同理:master发出一个读请求后,不等数据返回,又发出第二个、第三个读请求,这些请求在slave侧就是outstanding事务。它们数量越多,说明总线上同时在途的事越多。

这里有一个反直觉的点必须说清楚:outstanding=1是最小有效值,不是0。很多新人以为“不支持outstanding”就是outstanding=0,其实不是。outstanding=0意味着一个事务都不能发起,这显然不合理。不支持outstanding的实际含义是“一次只有一个在途”,也就是outstanding=1。命名虽然反直觉,但工程文档里就是这么用的,习惯就好。

1.3 在RTL、协议和寄存器里,outstanding通常出现在哪

  • 总线接口IP里,AXI/AHB这类AMBA总线尤其常见。AXI的设计初衷就是支持多个outstanding事务,很多AXI slave的datasheet里会写“supports up to N outstanding transactions”,这个N就是它能承受的在途事务上限。
  • DMA控制器、网络接口、存储器控制器。一个高性能DMA几乎必然带有outstanding配置寄存器或深度参数。有些IP还分开配置读outstanding和写outstanding,因为读写路径的缓冲结构不同,需要单独调。
  • 片上网络(NoC)中也有outstanding深度的概念,用于控制注入网络的数据量,防止请求过多把网络堵死。
  • 验证平台里面,UVM的driver、scoreboard、protocol agent,处处都涉及outstanding建模。验证工程师写monitor时,经常要计算“还有几个事务没回来”,这个数就是outstanding数量。

实际项目中,outstanding_depth这个参数往往直接写在寄存器配置表里,驱动代码往寄存器里写个值,IP核的行为就变了。我见过不少硬件工程师只负责把寄存器值填上,并不清楚内部机制,等到性能不达标才回头看,一改配置性能翻了好几倍。这个参数的重要性,一点不亚于时钟频率。

2. 总线协议里的outstanding:吞吐是怎么被撑起来的

2.1 AHB和AXI对outstanding的态度截然不同

AHB协议通过地址流水线(pipelining)实现近似outstanding的能力,但它对outstanding的支持相当有限。AHB的地址阶段和数据阶段是相邻的,一次transfer最多只能有一个地址在译码阶段、一个数据在读取阶段,加上实现上的限制,实际能达到的outstanding深度非常浅。而且AHB没有事务ID的概念,不支持乱序完成,一旦要做深度outstanding,AHB的结构就不合适了。

AXI从根本上解决了这个问题。AXI把地址、数据、响应拆成独立通道:读地址通道(AR)、读数据通道(R)、写地址通道(AW)、写数据通道(W)、写响应通道(B)。master发出一个读地址后,完全可以在AR通道再发出多个读地址,而数据随后通过R通道按ID匹配返回。这就让outstanding能力可以做得非常深。AXI的ID字段就是为了支持多个in-flight事务和乱序返回而设计的,读通道允许乱序返回,写响应通道则要求保序。

这个结构差异可以直接列个表感受一下:

维度AHBAXI
地址/数据通道地址与数据阶段交错地址、数据、响应通道独立
outstanding深度极浅(通常1~2)可做深(几十甚至上百)
ID机制无ID有ARID/RID/AWID/BID
乱序返回不支持读通道支持
典型场景中低性能互联高性能片上互联

做总线集成时,如果你发现某个数据通路性能上不去,先别急着怀疑时钟频率,大概率是没发挥出AXI的outstanding能力。

2.2 带宽延迟积:算一算就知道outstanding有多重要

用公式来理解为什么需要outstanding。总线有效吞吐率受两个因素制约:事务延迟(latency,从发出请求到收到数据的周期数)和outstanding数量(N)。理想情况下,总线利用率上限约为:

利用率 ≈ N / (延迟周期数)

假设某个存储接口的读延迟是100个时钟周期。如果outstanding=1,利用率上限只有1%;如果outstanding=8,上限约为8%;如果outstanding=64,可以到64%,再算上其他开销,实际会略低一些。这就是为什么高性能系统的DDR控制器通常把outstanding做到32或64,而不是1。

举个可复现的例子:某个DMA要连续搬运大量数据,配置outstanding_depth=1时,仿真测得总线利用率一直在1%~2%徘徊;把outstanding_depth改成16,利用率一下子跳到70%以上,传输时间缩短了一个数量级。这个调参前后对比,是理解outstanding价值最快的方式。

注意这里的延迟不是单次寄存器读写的延迟,而是整套握手链路的往返延迟:master发地址、slave译码、访问内部存储、数据返回,每一步都消耗周期。链路越长,outstanding的价值越明显。反过来,如果延迟只有几个周期,outstanding再大也不会带来质的改变,这种情况下新增资源只是浪费。

2.3 outstanding深度与buffer资源不是一回事

这是设计里最容易混淆的一点。outstanding是事务层(transaction-level)的概念,衡量的是“一个master可以在未收到完成响应前发起多少个事务”。而数据缓冲(data buffer)是beat层面的概念,它衡量的是“可以暂存多少个数据拍”。一个事务可能包含多个数据拍,所以outstanding深度=4,不代表数据缓冲只要4个entry,数据缓冲深度通常要按outstanding事务数乘以每个事务的最大beat数来估算。

举个具体场景:AXI读事务,outstanding=8,每个事务burst length=16,那么R通道最多可能有8×16=128个数据拍在途。如果slave的读数据缓冲只做了16个entry,数据一到就塞不下了,要么反压,要么丢数据。所以设计时一定要区分清楚:你在提高outstanding深度的同事,读写数据通道的buffer深度也要同步评估。否则outstanding加得越大,缓冲越容易爆,性能反而下降。

2.4 多少outstanding才够用

实际工程中,outstanding的配置要综合带宽需求、存储资源、功耗来权衡。常规做法是先按系统延迟和带宽目标估算一个下限,再用性能仿真逐步验证,找到一个“再加深度性能提升已经不明显”的拐点。

我做过一个存储控制器,outstanding从16加到32,性能只增长了3%,面积和功耗却增加明显,那这个设计点选16就比32更合理。还有一种情况是系统里有多个master共享同一个DDR控制器,每个master分到的outstanding配额要整体统筹,某个master吃掉太多,其他master的延迟就跟着恶化。这类系统里的outstanding配置,本质是一个资源分配问题,不能只盯着单条通路看。

3. 验证环境里的outstanding:从RTL到UVM的闭环

3.1 验证平台怎么描述“在途事务”

仿真验证时,RTL的行为是:driver发出请求,DUT在若干周期后给出响应。验证环境要能回答一个问题:当前到底是哪些事务还没完成?这通常用一个队列或关联数组来跟踪。

在UVM中,driver从sequencer拿到一个sequence item后,把它驱动到接口上。如果DUT支持outstanding,driver不应等响应回来再拿下一个item,而是可以继续向sequencer请求新的item,同时把已发出但未响应的item记为outstanding。关键点是driver返回给sequencer的时机,不能与DUT响应对齐,而应该与“事务成功发出到接口”对齐。只有在接口上完成了valid-ready握手,事务才算真正发出,之后就可以继续next。

要让driver支持outstanding激励,有两个关键改变:一是每个item驱动完成后立即调用item_done并获取下一个item,而不是等DUT响应;二是在agent内部维护一个outstanding队列,把item分发和响应接收解耦。具体实现时,driver可以使用fork/join_none在后台等待DUT响应,这样主流程就能继续发新事务。

如果验证环境没有建模好outstanding,最常见的现象就是验证效率极低:一个简单的多事务测试,仿真器在那里串行等响应,跑一个case要半天,而且激励的并发度完全不够,根本测不到总线在真实场景下可能出现的竞争问题。

3.2 用SystemVerilog实现一个按ID跟踪的scoreboard

当支持乱序返回时,scoreboard里不能只用FIFO保存期望值,而必须按ID索引。我这里写了一个最小可运行的思路,供参考:

class scoreboard extends uvm_scoreboard; // 用关联数组保存期望的响应,ID 作为 key protected transaction expect_txn[int]; // reference model 产生一个事务后,登记期望 function void set_expect(transaction t); expect_txn[t.id] = t; endfunction // monitor 抓到实际响应时,按 ID 查找并比对 function void check_response(transaction r); transaction exp; if (!expect_txn.exists(r.id)) begin `uvm_error("SCB", $sformatf("id %0d 无对应期望", r.id)) return; end exp = expect_txn[r.id]; expect_txn.delete(r.id); // 匹配后删除 if (exp.addr !== r.addr || exp.data !== r.data) begin `uvm_error("SCB", $sformatf("id %0d 数据不匹配", r.id)) end endfunction endclass

这段代码的核心是:每一个发出的事务,以ID为键保存期望值;响应返回时,同样以ID为键取回期望值并比较。如果协议规定保序返回,用FIFO也能凑合,但如果协议允许乱序,仍然用FIFO,就会频繁误报不匹配。我第一次写的时候就吃过这个亏,误报了大半天,最后才发现是scoreboard的数据结构用错了。

3.3 最容易踩的乱序坑

AXI读通道允许乱序返回,这是验证中最容易出问题的地方之一。常见错误有两种:一是monitor在agent里没有正确采集ID,导致RID无法与期望值匹配;二是scoreboard误用FIFO。排查时,先在仿真波形里看RID的先后顺序,再对照scoreboard里的查找逻辑,基本能定位。

另一个容易忽视的点是ID资源耗尽。AXI的ID位宽有限,如果发出的outstanding事务ID与未完成事务的ID重叠,slave返回时可能无法区分,随之而来的是数据错位、协议违规。验证环境必须在ID不够分配时给出告警,避免在测试中就埋下隐患。

我印象最深的一次是某个图像处理模块的验证,测试用例跑到一半就报错,报错位置全在数据比对阶段。后来抓波形一看,一个master同时发了8个读请求,但ID只有3种,轮着复用,slave返回时两个数据带同一个ID,scoreboard根本分不清谁是谁。这种问题在写RTL时不会暴露,只有在验证环境中把outstanding和ID一起压测到极限才发现。

4. 设计调优与问题排查:outstanding相关的典型坑

4.1 一个最常用的RTL计数实现

RTL层面,控制outstanding数量最常见的方法是维护一个计数器:发请求时加1,收到响应时减1,当计数达到上限时,必须暂缓新请求。用valid-ready握手写起来大概是:

logic [WIDTH-1:0] outstanding_cnt; always_ff @(posedge clk or negedge rst_n) begin if (!rst_n) begin outstanding_cnt <= '0; end else begin unique case ({req_handshake, resp_handshake}) 2'b10: outstanding_cnt <= outstanding_cnt + 1'b1; // 只发请求,无响应 2'b01: outstanding_cnt <= outstanding_cnt - 1'b1; // 只有响应 default: outstanding_cnt <= outstanding_cnt; // 同时发生或都不发生 endcase end end assign req_ready = (outstanding_cnt < MAX_OUTSTANDING);

这个实现虽然简单,但要注意两点:一是req_ready不要用“outstanding_cnt != MAX_OUTSTANDING”这种减法判断,容易在极端时钟下产生组合逻辑过长;二是尽量把满判断做成独立的比较信号,方便综合器优化。计数器的位宽要留够,至少能表示0到MAX_OUTSTANDING,避免溢出。

另外我建议把超时机制也纳入设计。如果outstanding事务长时间不返回,很可能后端卡死了。RTL里可以加一个超时计数器,超过预设周期数还未收到响应就触发异常上报,这在系统联调时非常有帮助。

4.2 死锁、丢数据、性能不达标怎么查

实际调试中,outstanding相关的问题可以归成三类:

第一类是死锁。master一直在等响应,slave一直在等请求,互相等导致仿真卡住或系统挂起。多数原因是outstanding能力配置过大,而某个中间模块的缓冲深度不够,请求被堵在中间层。排查时先看请求是否到达slave,再看响应是否发出,最后检查中间每个缓冲节点是否有满标志。死锁问题在验证环境里往往是仿真时间无限拉长,波形里看握手信号长期不变化,顺着请求路径一级一级查缓冲状态,很快能定位到堵点。

第二类是丢数据。如果RTL在乱序返回时没有按ID分配数据缓冲区,数据可能会被写错位置。这类问题在波形上表现为同一总线上后返回的数据覆盖了先返回的数据。排查时重点看接收端的写使能和写地址信号,验证环境中则要核对scoreboard里的ID匹配情况。丢数据往往不是单一信号错误,而是设计结构层面的问题,一旦出现,要回到协议层面重新梳理ID与缓冲区的映射关系。

第三类是性能不达标。最常见的配置错误是把outstanding参数漏配或配成1。某些IP默认值只有1,直接拿来用,总线利用率自然难看。先核对寄存器配置,再做传输耗时测量,性能问题大多迎刃而解。还有一种情况是outstanding配置没有生效,写寄存器失败或者配置在错误的通道上,这种问题在带调试总线接口的系统中经常出现,查起来既要注意软件侧写值,也要看硬件侧实际生效的值。

4.3 快速自查清单

结合过往经验,整理一份自检清单:

  • 确认设计要求的outstanding深度是否与协议支持一致。比如slave只支持4,master硬配成16,协议层面就会出问题。
  • 确认RTL中outstanding计数器的满判断与握手逻辑没有遗漏。特别是reset期间和通道空闲时,计数器不应该产生毛刺。
  • 确认每个outstanding事务都有唯一可用的ID,且ID释放逻辑正确。ID释放要在响应真正接收之后,不能提前。
  • 验证环境中,确认scoreboard使用按ID索引的结构而非FIFO(乱序场景)。如果协议保序,也要确认协议规定的顺序一致。
  • 确认对外接口的outstanding深度参数可配置,便于后续调优。硬件参数最好不要写死,留一个寄存器位给软件控制,性能调优时很有用。
  • 性能测试时,从outstanding=1逐步往上加,记录总线利用率的变化曲线,找到拐点。这个拐点附近就是性价比最高的配置。

我在一个跨平台系统里实际调过一次:同一套RTL,outstanding从1调到16,总线利用率从不到5%升到70%,整机功耗反而因为任务时间缩短而下降了。这个参数的影响幅度,有时候比换工艺库还明显。

再补充一点关于AXI写响应通道的细节。很多新手以为写事务也有乱序返回,其实AXI规范要求写响应(B通道)必须按写地址发出的顺序返回。换句话说,写outstanding可以很深,但完成顺序是固定的,B通道的ID必须保序。RTL里如果放任写响应乱序,验证环境立刻会报协议违规。读通道和写通道的这个差异,是设计AXI接口时必背的规则。

回想起来,outstanding这个词其实特别传神:“挂在账上还没有了结”的意思用在总线上再合适不过。我在实际项目里从被这个词绕晕,到慢慢用熟,最大的体会是:谈总线性能时,真正拉开差距的不是时钟频率,而是outstanding这类结构参数。如果下次有人问你outstanding是什么意思,你可以告诉他不只是“杰出”,更是硬件设计里那些在途的、等待完成的事务。它们能把总线的利用率拉满,也藏着大量的调试坑。希望这篇文章能帮你少走一些弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询