首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
Stimulus与Checker职责分离:SoC验证边界的核心设计
📅 2026/10/21 10:15:01
✍️ 爱科研究院
👁 阅读 3,247
1. 为什么要画这张图职责纠缠带来的第一次翻车1.1 一次失败的定位过程暴露了三方混战的局面做SoC验证做到第三年我遇到过一次特别抓狂的debug。随机测试跑到第2147个事务时挂了按照惯性流程第一反应是查DUT波形——结果主线上干干净净看不到任何违例。第二反应是查激励发现产生这个事务的sequence里居然写死了一段期望结果比较它根据回读的寄存器值直接判断对错还在中间动过内部信号。第三反应才是查checker结果scoreboard又依赖sequence的生成顺序来推算预期等于两边都嵌了对方的逻辑。那次定位花了将近两天。最后问题虽然锁定在DUT某个边角状态下的地址译码错误但真正让人后怕的是如果sequence不写那段期望比较scoreboard不依赖生成顺序这个bug在第一轮回归里就能被干净地报出来根本不需要我翻两天波形。从那次起我开始认真画一张图把什么算Stimulus、什么算Checker的责任边界写死在纸上。1.2 职责分离图到底在画什么这张图要做的事情非常简单把验证环境里所有组件分成两大阵营左边是Stimulus激励侧右边是Checker检查侧中间画一条粗线线两边不允许互相渗透。Stimulus侧解决的是怎么让DUT动起来的问题——造什么样的事务、按什么顺序发送、加什么约束、驱动到哪个端口、什么时候撤掉。一条完整的长链是sequence产生transactionsequencer负责仲裁路由driver把transaction变成引脚上的时序波形。这一整条链的目标只有一个就是把DUT请进一个又一个测试场景里。Checker侧解决的是DUT动得对不对的问题——从引脚或总线上采集实际行为和参考模型或协议规则做比对对越界行为进行报告。monitor负责观测scoreboard或reference model负责比较SVA并发断言负责盯住时序协议。这一侧的目标也只有一个就是对DUT的每一次响应给出对/错的裁决。两边之间只允许通过定义好的接口通信transaction对象、analysis端口、事件、上报通道。除此之外stimulus不得触碰任何比较逻辑checker不得反向控制生成逻辑。这张图画完挂在墙上意义在于任何一个新同事接手环境时第一眼就能看出某个组件属于哪一边出了问题也知道该去哪里翻代码。1.3 为什么SoC验证尤其需要这张图有人可能会说模块级验证不也需要分离吗没错但SoC级别有它特有的复杂度。一个芯片里可能同时跑着多个CPU核、DMA引擎、中断控制器、低功耗状态机激励来源不只是一条sequence链还可能是boot代码、固件、Host模型、外设模型。检查手段也不只是一层的协议断言而是从片内总线到片外接口、从数据完整性到跨时钟域同步的多层检查网。在这种体量下如果激励和检查互相纠缠规模会呈指数级恶化。我曾经在一个子系统里见过一个诡异现象checker依赖激励侧配置的DMA描述符结构来推算数据传输方向而激励side又通过checker的反馈来调整下一次发送的burst长度。两者形成闭环之后任何一次随机的时序扰动都会被双方放大跑出来的失败现场根本无法判断是DUT的问题还是环境自身的振荡。这张职责分离图在SoC层面的意义就是把这种递归混乱从根上掐断。2. Stimulus侧的职责清单把DUT请进状态2.1 三个核心动作造事务、编排场景、按时序驱动我习惯把Stimulus侧的职责拆成三个动作造出来、排好序、送出去。造出来指的是transaction的生成和随机化。一个DMA描述符一个AXI写事务一个中断请求都是transaction。它的字段、约束、随机策略决定了这一次测试会走到哪个场景。约束是纯生成侧的利器比如让地址对齐到64字节的约束或者让burst长度只取1到4的约束。约束本质上是生成规则不是检查规则。排好序指的是sequence层做的事情。一个真实的SoC使用场景往往是多步骤的先配电源域再喂时钟配置DMA描述符等中断再读回数据。sequence的工作就是把这些步骤按正确的顺序编排成一个可重用的场景同时允许随机插入异常和边界情况。序列里可以做握手可以发同步事件可以做分支选择但不可以对DUT的响应做正确性判断。送出去是driver的工作把事务翻译成精确的引脚时序。DUT不在乎你的事务对象长什么样它只在乎时钟沿上看到的数据和握手信号对不对。所以driver的职责是忠实翻译它负责适配DUT接口协议和时序但绝不应该在这一层偷偷检查数据内容是否和预期一致——那是checker的事。2.2 约束与随机约束不是检查一个常见误解是既然约束能限制数据范围那我能不能在约束里隐含一些检查比如让写数据永远等于读数据那不就不用检查了吗这种思路非常危险。约束是生成侧的筛选它只保证被生成的数据满足某些条件。一旦你为了省事把检查逻辑融进约束里当测试失败时你分不清是约束本身放宽了生成空间导致没测到边角还是DUT真的错了。更麻烦的是约束耦合了检查意图之后你很难迁移到另一个场景里复用因为约束已经隐性地绑定了上一场景的验收标准。我见过最极端的例子是有人直接在sequence里写了assert (data_out expected)还把结果打印到了日志里。表面上看确实做了检查但随机回归的时候这个断言散落在几十个sequence里既没有统一的报告渠道也没有覆盖率统计。真正出问题的时候日志里全是看起来像报错但不影响运行的打印人眼根本筛不过来。2.3 不该出现在Stimulus侧的四种东西根据我的观察Stimulus侧最容易混入的越界物有四种第一种是预期结果比较逻辑。sequence或driver里直接对DUT输出做断言。第二种是内部信号探头。sequence为了等某个内部状态直接去采样RTL内部寄存器或信号。这在模块级偶尔能跑通但在SoC级会严重破坏可移植性因为综合、跨时钟域处理之后内部信号名根本不稳定。第三种是参考模型调用。stimulus调用了参考模型来生成期望值等于把checker的逻辑悄悄混进了生成侧。第四种是反向控制接口。刺激侧直接去改写DUT内部寄存器或强制信号跳过正常接口路径。这四类东西只要出现图上的那条线就被撕开了。正确的做法是如果sequence需要知道某个内部状态来决定下一步那应该通过monitor采样对外可见的协议信号再通过事件或信箱把信息传给sequence。如果sequence确实需要知道期望值那也应该由checker侧统一维护参考模型通过明确定义的接口把预期分发回来。3. Checker侧的职责清单守护正确性的地盘3.1 协议型检查对规矩的监控Checker侧的第一类职责是协议型检查。它盯着接口上的信号时序看它们是否符合协议规范。比如AXI总线的AW、W、B通道握手是否严格按照协议时序READY与VALID是否满足协议要求的重叠时机地址是否在合法区间内跳变。这类检查最常见的载体是SystemVerilog Assertion也就是SVA。一个经验是协议断言最好放在monitor层且完全不依赖激励侧的任何配置。断言只是观测规则它不关心这次事务是被哪个sequence发起的也不关心配置寄存器写了什么值。它只关心如果总线出现握手那么后续行为必须满足协议。这样写出来的断言可以在任何场景、任何复用环境里直接挂载。有一个曾经踩过的坑是把协议断言直接写进了driver的事务发送代码里。Driver发送一个事务的同时顺手检查上一个事务的响应时序。这在单master的场景下没问题但一旦系统里多个master同时访问同一总线driver根本看不到全局时序断言就开始随机产生假失败。最后把断言全部挪到独立monitor问题当场消失。3.2 数据型检查参考模型与计分板Checker的第二类职责是数据型检查。这通常由一个参考模型加上一个scoreboard组成。参考模型接收与DUT相同的输入激励按照规范推算出期望响应scoreboard把DUT的实际响应和期望响应做逐字段比对。这里的关键设计原则是参考模型必须独立于stimulus的实现细节。也就是说scoreboard不关心激励是来自sequence还是直连的向量文件它只消费monitor从接口上采下来的事务对象。输入侧的事务对象进入参考模型输出侧的实际事务进入scoreboard比对。通道两侧的数据格式可以不一样但比对逻辑必须依据事务内容本身而不是依据激励生成的历史信息。实践里有个值得注意的细节scoreboard比对建议使用事务级别的字段比较而不是信号级别的逐个bit比较。因为SoC级的接口常有流水线、乱序返回、跨时钟域延迟在信号级做严格时序对齐非常痛苦而在事务级做内容比对则天然容忍合理的乱序。乱序逻辑在scoreboard内部通过队列、标签和匹配实现这也是数据型检查最有技术含量的部分。3.3 覆盖率与断言属于Checker的软手段除了硬性的错误判定Checker侧还承担两类软手段功能覆盖率和覆盖驱动的回归分析。功能覆盖率可以被理解为测试到底测到了什么的度量。它需要定义覆盖点、交叉覆盖组、以及和验证计划对应的场景覆盖。从这个角度看覆盖率收集逻辑天然属于Checker阵营因为它是对测试充分性的检查而不是对DUT的激励。值得注意的是有一种叫覆盖率驱动生成的机制覆盖率与激励侧确实会有交互——覆盖组收集到未覆盖点后会反馈给sequence修改约束或种子。但这不等于把覆盖率逻辑混进Stimulus侧而是通过明确定义的反馈接口交互。关于断言和覆盖率的边界我倾向于一句话总结断言抓的是当时发生的错覆盖率记的是测试到过哪它们都是正确的观测不是生成。只要把握住这个根本——Checker侧永远不做生成——这条线就不会乱。4. 中间那条线事务、端口和同步的边界机制4.1 事务对象是两边的合同如果把Stimulus和Checker比作两个团队那么事务对象transaction item就是两边的合同文档。Stimulus侧的sequence创建事务、driver发送事务Checker侧的monitor从接口上采集事务、scoreboard消费事务。两边不直接见面却共享同一份关于事务长什么样的定义。事务字段设计直接影响边界质量。你可以在事务里包含地址、数据、burst类型、通道ID、元信息甚至时间戳这些字段是两边都能理解和使用的公共语言。但有一个原则事务字段不应包含任何一方的私有逻辑。我在代码评审时多次见过事务里掺入expected_result字段这是典型的越界痕迹——字段一旦被用作预期就隐含了生成侧在替checker做裁决。4.2 Analysis端口与TLM机制UVM里的analysis port是边界的标准传送带。monitor把观测到的事务通过analysis port广播出去scoreboard、参考模型、覆盖率收集器各自作为analysis imp订阅。这个机制的最大好处是单向性数据永远从观测方流向比较方不存在反向请求的需求。在SoC验证环境里我通常会为每个接口设计独立的路由拓扑。比如总线monitor广播到协议断言组、参考模型、覆盖率收集器三个目标外设接口monitor广播到它自己的scoreboard。拓扑越清晰图上边界越干净。如果两个不同接口的scoreboard之间有数据交换需求那是Checker侧内部的通信不涉及Stimulus因此不会破坏边界。4.3 事件同步与错误上报Stimulus需要等待某个同步点比如DMA做完一笔传输。这时Checker侧通过event或mailbox向Stimulus侧发送通知。这种通知是状态信息不是预期结果。语义上要严格区分sequence收到DMA完成事件后做下一步操作是合理的sequence收到传输数据与预期一致这个结论再做下一步就越界了。错误上报也是一个边界机制。Checker侧发现错误后通过统一的report机制或者UVM的report server向上传播。Stimulus侧永远不会直接消费错误判别信息来决定自己的下一笔事务内容。如果sequence真的需要根据一定条件做决策那也应该基于状态事件而不是checker的裁决结果。这个区分看着微妙实际影响巨大一旦sequence开始拿checker的结论反馈生成环境就从线性验证退化成了闭环博弈。5. 四类越界事故复盘边界被打破的现场5.1 Sequence里写死预期结果事故现场某块GPU子系统的随机中断测试里sequence在发起中断后直接采样了中断状态寄存器并在序列代码里断言状态值和预期一致。代码大部分时间是绿的直到某次回归中因为有一个更早的错误没有被清理状态寄存器残留在非预期值sequence的断言立刻报错。问题核心是双重报告sequence的断言和checker的中断协议检查都报告了同一件事但时序点不一样。最终排查时要对两份日志做时间对齐才能判断谁先触发。如果sequence没写这个断言checker侧的逻辑会在更合适的同步点上报日志会干净得多。更严重的是sequence里写预期意味着预期逻辑被复制进了几十个sequence一旦参考模型修正所有序列都得改。这是典型的一次错误全局爆炸。5.2 Scoreboard制造反向激励事故现场某多核互连系统的重构测试中scoreboard发现DUT输出的数据处理顺序与自己期望不符没有直接报错而是生成了一笔纠正性事务通过一个私有的后门接口发回DUT期望把状态扳回正轨。结果DUT在这种强扭状态下产生了隐藏的死锁而scoreboard反而把这理解为环境自身的纠偏逻辑没有上报。问题核心是反馈回路。Checker一旦具备生成能力它就不再是裁判而是参与者。裁判下场踢球比赛结果自然失真。正确的做法是scoreboard发现异常时立即上报并且保持这次失败的事实原封不动交给验证团队分析。所有试图通过反向激励修正DUT状态的行为都会掩盖真实的bug定位点。5.3 Driver内联断言导致的环境熵增事故现场某个外设控制器的验证环境中driver在发送写命令时顺手检查了前一个读事务的返回时序并且用了全局时钟做时序比较。单对单场景下没问题但当环境同时挂载多个虚拟外设后driver之间的调度序列被打乱断言开始误报。这类问题的根源在于driver的视角局限。驱动器的视角是点对点的它知道自己的那一条命令通道却不知道完整的总线仲裁结果。协议级时序应该由独立monitor从总线级视角观测。轮询式的时序检查不适合放在逐事务的driver里。从图谱上看这正是发生器和观测器角色混淆的典型形态。5.4 Monitor越界驱动信号事故现场某子系统验证中monitor为了加速某个状态转移直接操作了DUT内部寄存器把状态机强制置位。结果功能覆盖率数据出现了一批不可能发生的状态组合分析覆盖率时浪费了大量时间。这类越界看似省事实则会污染整个验证数据。monitor一旦具备驱动能力就破坏了观测的独立性。观测应该被动、无侵入任何主动改写DUT状态的行为都让观测结果失真。后续团队在修复这个问题时把所有状态迁移加速逻辑全部移到Stimulus侧通过正常接口配置寄存器来达到同样的状态目标覆盖率数据才恢复可信。6. UVM环境里落地一个干净的骨架长什么样6.1 最小可用环境的结构拆分在一个典型的UVM SoC验证环境中我建议把testbench从物理和逻辑上严格拆成四个区域接口区、激励区、观测区、裁决区。接口区是UVM agent里的driver和sequencer但这里有个关键习惯agent里同时包含driver和monitor而这两个组件天然属于不同阵营。我通常把agent里的monitor单独列出来在结构图里画在Checker一侧把driver画在Stimulus侧。这样agent虽然代码文件在一起但责任归属在图上已经分开评审时一眼可见。裁决区包含scoreboard和参考模型。观测区包含各接口的monitor、协议断言集合、覆盖率收集器。激励区包含sequence库、虚拟sequence和约束配置。四个区域之间的依赖关系只能是单向的激励区依赖于接口区的sequencer观测区依赖于接口区的monitor裁决区同时依赖观测区的多个monitor但裁决区绝不依赖激励区的sequence实例。6.2 关键代码让分离落地到文件级落地到代码层面我一般会在文件组织上就把边界做出来。一个典型的环境目录结构长这样tb/ ├── stim/ # Stimulus侧 │ ├── seq_lib/ # 所有sequence │ ├── vseq/ # 虚拟sequence场景编排 │ └── constraints/ # 约束文件 ├── agent/ # 接口区跨边界按组件拆分 │ ├── xxx_driver.sv # Stimulus侧接口区左半 │ └── xxx_monitor.sv # Checker侧接口区右半 ├── check/ # Checker侧 │ ├── ref_model/ # 参考模型 │ ├── scoreboard/ # 计分板 │ └── coverage/ # 覆盖率收集器 ├── asserts/ # SVA断言放Checker侧 └── test/ # 测试用例入口在此基础上一段干净的sequence和一段干净的scoreboard代码分别长这样。Sequence侧职责只到生成为止。它发起事务、等待status事件、根据状态决定下一步。它不直接对DUT的输出数据做比较class dma_simple_seq extends uvm_sequence #(dma_txn); uvm_object_utils(dma_simple_seq) task body(); dma_txn txn dma_txn::type_id::create(txn); // 只做生成配置描述符发起传输 for (int i 0; i num_blocks; i) begin start_item(txn); txn.cmd WRITE_ONLY; txn.addr[i] base_addr (i * block_size); txn.data[i] $urandom_range(0, 2**32-1); finish_item(txn); end // 等待DUT完成事件但不校验结果 (dma_done_evt); endtask endclassScoreboard侧职责到裁决为止。它消费事务经过参考模型比对上报结果。它不反向生成任何激励class dma_scoreboard extends uvm_scoreboard; uvm_component_utils(dma_scoreboard) uvm_analysis_imp#(dma_txn, dma_scoreboard) mon_export; function void write(dma_txn txn); // 从reference model取期望 bit [31:0] exp_data ref_model.predict(txn); // 与实际数据比较 if (txn.data_out ! exp_data) begin uvm_error(DMA_SCB, $sformatf(data mismatch: got %0h, exp %0h, txn.data_out, exp_data)); end endfunction endclass这两段代码的边界感非常直观sequence里没有任何uvm_error的判断分支scoreboard里没有任何start_item或者finish_item。如果评审时在某段代码里同时看到这两类调用就可以直接判定越界。6.3 评审检查清单十秒钟看出谁越界我把代码评审时的检查清单总结成几个快速判断原则分享出来第一看文件的依赖方向。Stimulus侧的文件是否import了Checker侧的类如果sequence里出现scoreboard或coverage类型直接标记越界。第二看函数调用。sequence里出现uvm_error用于比较目的或者scoreboard里出现start_item、finish_item都视为犯规。第三看事务字段。事务类里是否有expected、predict、golden这类字段名有的话要问清楚这是谁写进去的写在哪个阶段第四看monitor的接口。monitor是否只有analysis port出口没有seq_item port或driver的入口如果monitor能主动发起事务边界已破。第五看事件语义。sequence等待的事件名若是xxx_done_evt、xxx_req_evt这类状态型可接受若事件名是xxx_pass_evt、xxx_match_evt这类结论型就危险了。7. 我在这张图上收获的几条实践经验画过几次Stimulus / Checker职责分离图之后最大的感受是这张图的价值不在图本身而在画图过程中对每个组件归属的追问。每次把一个组件分成左半边、右半边的时候都需要重新审视它的成员变量、方法调用、端口连接。往往逼着自己画完一遍环境里的隐患就暴露得差不多了。还有一个很实用的经验是这条边界线其实也是代码评审和新人培训的入口。新同事上手时先给他看这张图让他把每个文件放到对应阵营里比讲十页UVM语法管用得多。因为验证环境在SoC级别真正难的不是语法而是组织方式。最后分享一个小技巧。如果环境已经混乱不要试图一次性重构所有代码。先花两个晚上把现存每个组件画到图里按越界程度排序优先修复最严重的那几个——通常是sequence里的内联断言和scoreboard里的反向控制。拔掉这几根暗线环境的可调试性会立刻上一个台阶。之后每提交一次代码路过这张图时瞄一眼问自己一句这个文件会同时出现在图的左右两侧吗如果会那多半是又该停下来重新设计了。
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/10/21 10:15:01
WebDriverAgent源码深度解析:从HTTP服务器到iOS自动化测试底层机制
2026/10/21 10:15:01
MCP协议详解:AI工具之间统一交互的标准接口
2026/10/21 10:09:59
MELSEC iQ-R编程手册:模块专用指令详解与实战避坑指南
2026/10/21 11:25:37
聊聊 Agent 工程化:Demo 很好写,生产却处处是坑
2026/10/21 11:25:37
Agentic Awesome Skills 工作流实战:用 Antigravity 执行手册编排多技能完成端到端目标
2026/10/21 11:25:37
转:文化变革
2026/10/21 11:25:37
AWS Serverless 生产级实战指南:基于 AAS Core 技能库的 Lambda、API Gateway 与事件驱动模式详解
2026/10/21 11:25:37
【C++】进阶篇 STL map和set使用
2026/10/21 11:20:29
A-29P宽压供电架构:4V~16.5V电压域的功耗与权衡A-59F是一款重新设计并全面升级的多功能语音处理模组,集成了本地扩音防啸叫、AI智能降噪、全双工回音消除以及双麦波束成形四大核心功能。该模组
2026/10/21 0:01:44
ClickHouse实战:从列式存储到亿级数据秒级查询
2026/10/21 0:01:44
高效筛选arXiv机器学习论文:四步法实战指南
2026/10/21 0:01:44
Python爬取百度迁徙数据:接口签名、反爬与SQLite存储实战
2026/10/20 16:07:12
OpenResearch实战:Claude Code、Codex、OpenCode、Cursor 工作流搭建与故障排查
2026/10/20 7:57:25
知识管理核弹:用Obsidian构建第二大脑的工程化方案(TaoToken 统一 Key 接入版)
2026/10/20 16:49:38
金融AI落地指南:从调研报告到技术选型与工程实践
2026/10/20 2:24:03
我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
2026/10/20 2:23:57
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026/10/20 2:23:53
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)