资讯动态

AXI VIP interleaving配置全解:参数关联与避坑实践

发布时间:2026/10/18 19:17:52 来源:尧图企业网站定制
简介针对AXI协议验证中的乱序返回与交错读取需求这套项目代码面向使用AXI slave VIP的验证工程师重点解决out of order read data和interleaved read data的高级配置问题。资源内提供read_data_reordering_depth、read_data_interleave_size等属性的设置思路并给出enable_interleave、interleave_pattern及SVT_AXI_MAX_READ_DATA_REORDERING_DEPTH宏定义示例便于快速搭建多通道并发访问的验证环境。压缩包共3个文件包含HTML说明文档、inscode工程入口及gitignore配置包体仅7KB轻量且便于直接参考。目前已有273人学习/下载适合熟悉AXI总线协议、希望优化存储或内存控制器验证场景的软件开发与验证人员。1. AXI VIP 配置 interleaving为什么一上来就要问深度和边界AXI VIP 配置 interleaving 这件事看着是改几个参数实际上决定了你的验证环境能不能发现总线互联里的数据乱序问题。大多数验证人员默认把 interleaving 当成一个布尔开关打开就完事结果仿真跑了几万周期DUT 里真正会发生的交织场景在环境里根本没被激励过。我在一个总线互联验证项目里就翻过车A 同学把 ID 宽度从 8 改成 12 之后仿真在 3 万周期附近直接死锁排了三天才发现 VIP 默认不交织读数据永远按发出顺序返回outstanding 配得再大也只是排队不是并发。这篇文章会把 interleaving 背后的协议约束、UVM 环境里的配置方法、参数之间的关联关系一次讲透。你会在第 3 章看到可直接改用的代码第 4 章是四条常见的翻车现场和对应解法。适合被 VIP 玄学卡过的验证工程师也适合刚接手 AXI 总线验证、想少走弯路的新手。配置 interleaving 不是让环境变得更复杂而是让激励更接近硬件真实行为这一步绕不开。2. 先分清协议语义再动手AXI interleaving 改的是数据与响应的顺序2.1 读数据交织与写数据交织两条通道的不同约束AXI 协议里的 interleaving 出现在数据通道和响应通道地址通道没有这个概念。读数据通道是协议明确允许交织的主设备可以同时发出多笔读事务从设备返回读数据时允许在不同事务的 beat 之间穿插返回。比如事务 A 和事务 B 都是 4 拍突发合法的返回顺序可以是 A0、B0、B1、A1、A2、B2、A3、B3只要每个事务内部 beat 序号保持递增就不算违例。但注意一个硬约束同一事务 ID 的所有 beat 必须按照 burst 顺序返回不能把某个 ID 内部的顺序打乱。如果 DUT 把同一 ID 的两笔事务数据交叉返回那不是合法的交织属于协议违例。写通道的情况不太一样。写数据依靠 WSTRB 区分每字节的有效性所以不同写事务的 WDATA beat 理论上可以穿插发送但 AXI 规范对写交织的允许程度比读通道保守得多大多数 VIP 默认也不开启写数据交织。写响应通道则是按事务返回每个事务一个 WRESP交织体现在响应返回顺序上与读数据交织的粒度完全不同。很多初次配置的人把读数据交织和写响应交织混在一起设置结果 DUT 行为与预期完全对不上。所以配置 interleaving 前先问自己一句要验证的 DUT 是读通道支持交织还是写通道支持交织还是两者都支持你需要分别在 VIP 配置里控制读写通道的交织能力。如果开关没分开后续功能覆盖率的收集和违例上报都会乱掉。这里还有一层容易被忽视的细节交织只对 INCR 和 WRAP 类型的突发有意义FIXED 突发因为地址固定交织后数据顺序的检查逻辑要单独处理否则 scoreboard 会把合法的交织误判为数据错位。2.2 与 outstanding、乱序返回的关系配置前必须搞懂的三组参数Interleaving 不是孤立存在的开关它和另外两组参数强耦合。第一组是 outstanding 事务数指主设备在收到响应之前最多可以发出的未完成事务数量。只有 outstanding 大于 1交织才有存在的前提。如果 outstanding 配成 1任意时刻总线上只有一笔事务数据通道天然不可能交织。许多环境为了简化建模默认把 outstanding 设成 1这时候你去打开 interleaving 开关sequence 产生的事务仍然是顺序返回白配。第二组是 ID 深度。AXI 事务依靠 ID 区分乱序和交织如果 ID 位宽只有 2 位就算 outstanding 配了 8从设备最多也只能维护 4 个不同的 ID。VIP 配置里通常有一个并发 ID 数量参数它决定 driver 能否同时跟踪多笔不同 ID 的事务。这个参数如果小于发起的 outstanding 数量部分事务会被 VIP 内部排队不会真正同时出现在总线上交织场景也就测不出来。这里有一个经验公式方便估算max_interleaving_depth max_outstanding 2^id_width。第三组是数据缓存深度。VIP 内部需要用 RAM 缓存未返回的读数据当交织允许的 beat 数超过缓存深度VIP 为了不丢数据可能强行把某些事务按顺序发送。商用 VIP 的配置项里通常会暴露 data_buffer_depth它和 max_interleaving_depth 通常是一对。配置原则是 max_interleaving_depth 必须同时小于等于 data_buffer_depth 和 outstanding 数量。把这三组参数用约束写死在配置类里比逐个字段手填更安全因为手填最容易出现 depth 大于缓存深度这种配置合法但行为退化的组合。三组参数的关系如下表所示参数作用配错时的表现outstanding控制未决事务数为 1 时交织不可能发生ID 位宽决定可并行事务数量太小导致 VIP 内部排队数据缓存深度暂存未返回的读数据过小退化为顺序返回2.3 VIP 里 interleaving 能力的建模不是开关而是一组约束商用的 AXI VIP 和常见的 UVM AXI agent对 interleaving 的建模方式并不统一但一般都会提供几个公共配置项enable_read_interleaving、enable_write_interleaving、max_interleaving_depth。有些实现还提供 interleaving_mode用来区分随机交织还是固定深度交织。这个 mode 会在第 3 章讲随机化策略时展开这里先强调一个容易踩的坑不要只把 enable 开关当成唯一入口。我之前排查过一个仿真空洞就是典型例子。某开发者把 enable_read_interleaving 设成 1但 max_interleaving_depth 用的默认值是 0。翻开源码才看到driver 判断是否交织的条件是 enable 为真且 depth 大于 0。depth 为 0 时每次切换事务最多只能发一个 beat数据返回顺序被强制频繁打散反而制造了大量协议违例。这说明 depth 不是单纯描述“最多能交织多少笔”它还参与决定交织是否实际生效。更深一层VIP 对 interleaving 的建模本质是把协议里的三条约束翻译成 driver 和 scoreboard 的限制条件。driver 侧控制发出和返回事务的时序scoreboard 侧根据 ID 和 burst 顺序检查数据完整性。配置时要同时告诉 VIP 允许交织到什么程度、以及检查端应该怎么处理乱序数据。如果只开了前者的开关后者通常默认是顺序检查模式交织产生的乱序数据会被误报为违例。这个对应关系非常重要建议在配置时直接查找 VIP 的 data_check_mode 或 compare_mode 字段把它设置成支持交织的模式才算真正配完。3. AXI VIP interleaving 配置实操UVM 环境中的最小改动3.1 创建 VIP 配置对象并打开读通道交织树的叶子要传到位在 UVM 环境里配置 AXI VIP 的标准路径是实例化一个配置类把交织参数填好再通过 uvm_config_db 设置到环境树上的指定节点。下面是一个最小可用的配置类把三组参数的约束关系直接写进了 constraintclass axi_vip_cfg extends uvm_object; uvm_object_utils(axi_vip_cfg) bit enable_read_interleaving; bit enable_write_interleaving; int unsigned max_interleaving_depth; int unsigned max_outstanding_reqs; int unsigned data_buffer_depth; constraint c_interleave_cfg { max_interleaving_depth inside {[0:16]}; max_outstanding_reqs inside {[1:8]}; enable_read_interleaving 1 - max_interleaving_depth 0; max_interleaving_depth data_buffer_depth; max_interleaving_depth max_outstanding_reqs; } function new(string name axi_vip_cfg); super.new(name); enable_read_interleaving 1; enable_write_interleaving 0; max_interleaving_depth 4; max_outstanding_reqs 4; data_buffer_depth 8; endfunction endclass这里三个约束条件就是第 2 章提到的三组关系。打开读交织时必须保证深度大于 0否则开了一个实际不生效的开关交织深度不能超过数据缓存交织深度不能超过 outstanding 数。把约束写在同一个配置类里比在 test 里分开赋值更不容易出错。写通道交织我在构造函数里默认关掉了因为大多数 DUT 的写通道并不支持数据交织开启反而会产生从设备永远不会响应的合法激励。然后在 base_test 里把它传到环境树上。这里的关键是路径必须和实际层级一致用通配符*确实省事但如果环境里有多个 AXI 实例通配符会把读交织开关错误传给写侧 agent。我一般写显式路径class base_test extends uvm_test; uvm_component_utils(base_test) axi_vip_cfg m_axi_cfg; function void build_phase(uvm_phase phase); super.build_phase(phase); m_axi_cfg axi_vip_cfg::type_id::create(m_axi_cfg); uvm_config_db#(axi_vip_cfg)::set(this, env.mst_agent.*, vip_cfg, m_axi_cfg); endfunction endclass这两行配置的路径包含.*表示在env.mst_agent下所有组件都能 get 到同一份配置。注意 set 的路径是相对当前组件的test 层级不同路径就要调整。如果路径配错VIP 内部组件 get 不到配置通常会静默使用默认值表现为交织开关设了但行为完全没变。遇到这种情况第一步先检查 config_db 的 set 和 get 路径是不是真的对上了而不要急着怀疑 VIP 本身。3.2 配置交织深度max_interleaving_depth 怎么取才不踩协议红线交织深度是配置里最容易被拍脑袋填的参数。常见做法是打开交织开关后用默认值或者随手填一个 16但深度不仅影响行为还影响仿真时间和覆盖率收集的质量。取值的核心逻辑是让它接近 DUT 的真实能力同时不超出协议合法范围。VIP 侧本身的限制有三条缓存深度、outstanding 数、并发 ID 数。这三条之外还要考虑 DUT 是什么类型的模块。下面给出一个可配置的 agent 从 config_db 获取配置并传递给内部组件的完整示例class axi_mst_agent extends uvm_agent; uvm_component_utils(axi_mst_agent) axi_vip_cfg cfg; axi_mst_driver drv; axi_mst_monitor mon; function void build_phase(uvm_phase phase); super.build_phase(phase); if (!uvm_config_db#(axi_vip_cfg)::get(this, , vip_cfg, cfg)) uvm_fatal(AXI_CFG_MISS, axi vip cfg not set to agent) drv axi_mst_driver::type_id::create(drv, this); mon axi_mst_monitor::type_id::create(mon, this); endfunction function void connect_phase(uvm_phase phase); super.connect_phase(phase); drv.interleave_depth cfg.enable_read_interleaving ? cfg.max_interleaving_depth : 0; mon.check_data_order cfg.enable_read_interleaving; endfunction endclass这里在 connect_phase 里把深度传给 driver同时把 monitor 的数据顺序检查模式与交织开关绑定。这样配置类负责约束和校验agent 负责分发driver 和 monitor 各管各的职责。后续如果要加交织相关的覆盖率收集直接在 agent 里再引一次 cfg 就行不需要动 VIP 内部源码。这种分层处理是调试效率的关键因为当 bug 出现时你可以快速定位是配置传错了还是检查端配置错了。深度取值方面如果 DUT 是简单的 AXI 到 AHB 桥通常不支持读数据交织把深度配成 4 会让 VIP 产生从设备永远不会支持的激励等于给 testbench 加戏。如果验证的是带深度缓冲的 AXI 互联核建议从头递进depth2 验证基本穿插depth4 验证多笔穿插depth8 以上配合延迟模型使用。如下表所示是一个可供参考的取值对照DUT 类型建议 depth说明AXI 到 AHB 桥0 或 2桥内缺乏数据缓冲高深度无意义单级互联核2 到 4验证基本穿插带缓存的多级互联4 到 8覆盖深缓冲场景压力测试模式最大支持值专门跑边界3.3 用 sequence 产生交织事务让 VIP 真正跑起来的最小示例配置参数只是搭好了舞台真正让交织发生的是 sequence。最简单的方式是发起多笔不同 ID 的读事务由 VIP 内部分配返回顺序更可控的方式是通过uvm_do_with显式指定每笔事务的关系。这里给出一个最小可跑的 sequenceclass interleave_read_seq extends uvm_sequence #(axi_transaction); uvm_object_utils(interleave_read_seq) int unsigned num_reqs 4; task body(); axi_transaction req; for (int i 0; i num_reqs; i) begin req axi_transaction::type_id::create(req); start_item(req); if (!req.randomize() with { addr (i * 32h100); burst_len 3; // 4 beats per burst id i; // use distinct ID outstanding_ok 1; }) uvm_fatal(RAND_FAIL, req randomize failed); finish_item(req); end endtask endclass核心是id i生成了 4 笔不同 ID 的读事务每笔地址间隔固定且每笔都是 4 拍突发。VIP 驱动在这 4 笔事务发出后会根据配置里的交织深度决定返回顺序是穿插还是顺序。如果 depth 为 4它可能在事务 0 的第 0 拍返回后把事务 1 的 4 个 beat 全部返回再继续返回事务 0 的后 3 拍这是一种极端交叠。具体哪一拍切换由 VIP 内部的仲裁机制决定你可能无法精确控制每一拍但可以控制事务的 ID 和突发长度保证交织候选者存在。outstanding_ok 1是这里假设 transaction 里存在的一个字段用来允许该事务在未收到响应的情况下继续发出下一笔。如果实际 VIP 接口不提供这个字段要么在 sequence 里用 fork 并发发送要么在 driver 里预先把多笔事务压入队列。不同 VIP 的 transaction 字段差异很大但核心一致只有多笔事务同时处于飞行状态交织才有机会发生。写完 sequence 后建议先跑一个 1000 周期的小用例在波形里看读数据通道是否真的有不同 ID 的 beat 穿插。如果完全顺序返回优先查 outstanding 是否大于 1再查 ID 位宽是否足够最后查 depth 有没有成功传到 driver。4. AXI VIP 配置 interleaving 的常见问题与避坑清单4.1 现象一配置了交织实测 transaction 仍然顺序返回这是最常遇到的问题。enable_read_interleaving 设成 1max_interleaving_depth 配了 4仿真跑完看波形读数据还是按事务发出顺序依次返回看不到任何穿插。把 config_db 的打印打开值也正确一度怀疑是 VIP 的 bug。原因排查下来基本落在三个位置。第一outstanding_reqs 被设成了 1VIP 每次只放行一笔事务到总线第二笔在队列里等前一笔完全结束才发出交织自然不可能发生。第二sequence 发的多笔事务用了同一个 IDAXI 约束同一 ID 的数据必须保持顺序返回VIP 不会产生协议违例于是顺序返回。第三config_db 路径不对set 到了 env 层agent 内部 get 时字段名不一致VIP 用的是默认值。解决方法是做一个诊断打印 VIP 内部实际生效的配置而不是打印 test 里 set 进去的配置。很多 VIP 都有 print_config 或者 protocol checker 相关的接口在 build_phase 结束后调用就能看到。把 outstanding 改到 4sequence 里的 ID 改成每笔不同再开打印确认。这三步做完交织基本就出现了。如果还没有看 VIP 是否还有额外的门控条件比如 transaction 里的 no_ordering 标志这类字段在 sequence 里最容易漏。4.2 现象二打开交织后出现大量协议违例误报还是真错打开交织后仿真跑到两百个周期附近报了一堆违例主要落在地址通道现象是多个事务地址重叠。第一反应是 VIP 配置有问题把交织关掉就安静了于是怀疑交织让 VIP 产生了非法激励。真实原因往往是 DUT 本来就是按顺序接收地址的VIP 发出多笔重叠地址的并发事务后触发了从设备内部的地址仲裁断言。AXI 协议允许不同 ID 的事务并行存在但 DUT 不一定支持。这种违例不是 VIP 的 bug而是环境把 VIP 配得比 DUT 更强了。换句话说VIP 的行为合法但 DUT 的约束模型不完整需要靠 sequence 约束来限制。处理原则是区分违例来源。如果检查来自 AXI 协议规则比如同一 ID 读数据顺序错乱那通常是 scoreboard 的检查逻辑没跟上交织模式需要在 monitor 里关闭顺序检查或改用 per-ID 检查。如果检查来自 DUT 断言比如地址不允许重叠应该增加地址约束让地址间隔大于最大 burst 长度而不是关闭 VIP 的交织。VIP 配置只是协议允许的上限sequence 的地址约束才是测试场景的边界两者要配合调整。直接把交织关掉绕开问题等于把 DUT 的真实行为也绕过了后面还会在别的地方爆。4.3 现象三随机化后 deadlock长时间挂起无响应配置交织后仿真跑到某处就停住了波形停在读数据通道上没有新的数据返回也没有新的请求发出。这种 deadlock 在加入交织后出现概率明显升高排查时最容易慌。典型原因是 outstanding 数量与响应通道的返回能力不匹配。VIP 发出的未完成事务数量超过了从设备能同时处理的 ID 数量从设备挂起等待某些条件VIP 因为数据缓存占满不肯释放新的响应两边互相等。另一种常见原因是 sequence 用阻塞式get_response等待响应而交织配置让响应顺序与发出顺序不同。sequence 按发出顺序等第一笔事务的响应但第一笔事务的数据被交织排到了最后返回前一笔没响应后一笔没法继续死锁就出现了。解决方法是别用阻塞式get_response等待交织事务。sequence 发出所有事务后在 scoreboard 里通过非阻塞方式收集响应或者用带 ID 过滤的 get_base_response。同时检查数据缓存深度确保 buffer 大于交织深度加突发长度的和。还有一个实践技巧把 VIP 的 timeout 机制打开死锁出现时自动打印所有未完成事务的 ID 和状态定位速度快很多。看波形在这种场景下效率很低看 VIP 内部维护的事务队列状态更能说明问题。4.4 现象四交织覆盖率 100%关键边界却完全没碰到功能覆盖率里定义了交织深度的 bin从深度 2 到 16 都配了回归跑完覆盖率报告显示 100%但代码审查发现深度 16 的路径根本没被真正执行过。这是个典型问题覆盖率收集点和 VIP 实际行为之间隔了一层。原因在于覆盖率采样点选错了。常见的写法是 coverpoint 直接采cfg.max_interleaving_depth配置阶段这个值就是 16所以覆盖率必然显示被覆盖。但实际仿真里 outstanding 数量只有 4VIP 真正产生的交织深度最多到 3深度 8 和 16 的场景一次都没发生过。这种覆盖率没有任何意义它描述的是配置意图而不是总线行为。解决方法是把采样点从配置参数改成总线行为。具体做法是在 monitor 里记录每次返回数据切换 ID 时的 beat 计数统计连续同一 ID 的 beat 数作为实际交织深度。覆盖率模型采集的是从总线上抓到的实际穿插宽度而不是配置里的 depth。配置代码里加一个 debug 打印在每次返回数据切换 ID 时打印当前 beat 序号就能快速核对覆盖率模型是否采到了真实数据。覆盖率报告的 100% 从来不是终点关键是确认它真的覆盖到了硬件行为。5. 用响应回调验证交织边界让 scoreboard 真正抓住乱序错误5.1 注册 response handler 并检查 per-ID 顺序配置交织只是第一步最终要验证的是乱序行为下数据能否正确拼回。我常用的做法是在 VIP 的响应回调里注册一个 per-ID 检查器它只关注同一 ID 内的 beat 顺序不关心不同 ID 之间的返回顺序。这样既能验证交织合法性又不会被交织现象本身干扰。下面的代码是一个简化了的回调检查类可以直接套用到自己的 monitor 数据通路里class interleave_data_checker extends uvm_component; uvm_component_utils(interleave_data_checker) int last_beat_idx[int unsigned]; bit seen_first_beat[int unsigned]; function void check_read_data(int unsigned id, int unsigned beat_idx); if (!seen_first_beat.exists(id) || !seen_first_beat[id]) begin seen_first_beat[id] 1; last_beat_idx[id] beat_idx; return; end if (beat_idx ! last_beat_idx[id] 1) uvm_error(DATA_ORDER, $sformatf( ID %0d beat order broken: got %0d expect %0d, id, beat_idx, last_beat_idx[id] 1)) last_beat_idx[id] beat_idx; endfunction endclass回调逻辑核心就一点每收到一笔读数据解析出 ID 和 beat index用 ID 做 key 查表。如果当前 beat index 不等于上次记录的 beat index 加 1报错。初始化时等每笔事务第一个 beat 到达后才置有效状态避免把合法交织误报成顺序错误。这个检查器在未开启交织时也能工作它天然兼容两种模式。对写通道则是另一套逻辑写响应通道每个 ID 只能有一个未决响应同一 ID 连续出现两个未决 WRESP 就是违例实现上用队列保存未决响应即可。5.2 把交织深度压到最大再放开回归里的边界扫描最后建议配置一个专门的压力测试模式把 max_interleaving_depth 设到 VIP 支持的最大值outstanding 设到最大同时把 sequence 的 ID 数量设为 outstanding 的一半。这样每个 ID 都有至少两笔未决事务交织深度才可能逼近上限。跑通之后再逐级缩小 depth找出 DUT 在哪个深度开始出现数据错误。那个深度就是 DUT 的真实边界这个数字值得写进验证报告。后续回归保留两个模式。一个是随机交织模式让 depth 在 2 到 8 之间随机覆盖常规场景另一个是压力模式固定最大 depth每周跑一次长回归。这样既不会让日常回归太慢又能保证交织边界始终有人照看。我在某个总线互联项目里用这套配置正是压力模式抓到了一个缓存未命中时数据错位的 bug而随机模式已经跑了几千次都没暴露。配置 interleaving 说穿了就是让激励符合硬件真实行为我的习惯是每次改完参数先打印 VIP 实际生效值再跑 1000 周期的小用例看波形里是否真的出现了不同 ID 的 beat 穿插确认之后再调 scoreboard 的数据顺序检查。这样后面出了问题你至少能分清是配置错了还是 DUT 行为错了。希望这份清单能帮到你少走一些我走过的弯路。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑