资讯动态

Synopsys AXI VIP集成实战:从UVM环境搭建到AXI总线验证避坑指南

发布时间:2026/10/6 5:56:38 来源:尧图企业网站定制
做IC验证这几年凡是涉及SoC全芯片验证的项目基本绕不开AXI总线。前端RTL搭好之后第一件事就是要在仿真环境里把AXI主从设备都能驱动起来、把读写事务跑通。手撸一套AXI model不是不行但你要在一个月内搞定协议细节、outstanding乱序、QoS仲裁、cache一致这些点十有八九会想撞墙。所以业界普遍直接用Synopsys的AXI VIP它本身就是基于UVM的验证IP集成了协议检查器、功能覆盖率模型和一套可以直接调用的sequence库相当于帮你把AXI协议工程师的工作提前做完了。这篇就围绕VIP-2021.09版本从零开始讲怎么把Synopsys AXI VIP配起来、连上自己的DUT跑通第一个burst事务再把我实际踩过的坑按场景列出来免得你在同一个地方卡两周。1. 环境准备与工程目录规划1.1 搭建环境前先确认编译器、License和VIP包版本很多新人一上来就急着翻VIP源码结果编译直接报错原因多半是VCS版本太老、License没开对应feature或者VIP目录本身不完整。我的习惯是先把三样东西确认好编译器版本、License关键字、VIP包目录。编译器这里以VCS为主。VIP-2021.09这个版本要求VCS至少M-2017之后2020版本以上最稳。你在命令行敲一下vcs -ID看版本号如果版本偏老后面compile VIP自带的apb、axi组件时大概率会出现语法不认识的问题比如let、randsequence这些SystemVerilog新特性。接着检查License运行lmstat -a -c $VCS_LICENSE或者snpslmd -v重点看启动VCS时会不会报Feature expired。Synopsys VIP在License里对应的Feature名通常是Synopsys_VIP或VIP_AXI具体名称以你买License时的license file为准。如果lmstat查不到对应Feature后面仿真跑到$svt_axi包初始化的位置就会当场退出。VIP包本身一般安装在/home/eda/synopsys/vip_2021.09这类目录里面包含vip_common、axi、apb等子目录。安装完成后先看一下axi下是否包含svt_axi.sv、svt_axi_base.sv这些实际代码再确认sim目录下有没有可用的脚本。有的渠道拿到的包只有文档没有RTL源码那种基本没法直接用。把这三点全部确认通过再往工程里引路径否则后面每跑一步都会返工。1.2 规划工程目录和编译顺序我习惯把VIP工程按模块拆开目录层级可以这样rtl/存放被测模块代码tb/存放testbench顶层、接口、模块封装env/UVM环境包括sequence、test case、配置类sim/编译和仿真脚本目录vip_tb/专门放VIP创建、连接相关代码这么拆分的好处是当VIP版本升级时只动vip_tb/不用牵连其他模块。编译顺序上有个容易踩坑的细节VIP自带代码必须先编译然后编译testbench而且要把SVN文件按依赖顺序摆好。Synopsys AXI VIP基于UVM所有组件都从UVM类继承所以VCS编译时一般用-uvm或-ntb_opts uvm来加载UVM库。命令大致是vcs -sverilog \ -ntb_opts uvm \ -timescale1ns/1ps \ -full64 \ -debug_accessall \ -f vip.f \ -f tb.f \ -o simv \ incdir$VIP_HOME/axi \ incdir$VIP_HOME/vip_common其中vip.f文件里按顺序写入VIP相关源文件tb.f里写testbench文件和环境文件。编译顺序、include路径和宏定义在VIP自带的install.sim或testcase样例里通常有现成写法复制过来改路径比自己从零写靠谱。注意-ntb_opts uvm和手动-uvmhome不要同时使用除非你确实知道UVM库版本否则容易冲突。2. AXI VIP的结构与关键配置逻辑2.1 搞清master、slave、monitor这三个组件的职责Synopsys AXI VIP的基本架构是基于UVM的内部主要提供三类可配置组件svt_axi_master、svt_axi_slave和svt_axi_system_monitor。svt_axi_master跑在DUT的AXI主接口上用来模拟主机行为比如发送读命令、写命令、处理读数据。svt_axi_slave反过来挂在DUT从接口上模拟从设备响应收到读写请求后返回响应数据。svt_axi_system_monitor用来做协议检查和功能覆盖率收集它监听总线信号不主动驱动。实际项目中我们通常会在DUT两侧各放一个master和slave甚至同一个从接口上放多个master来模拟多主场景。这个分配逻辑决定了后续configuration的配置方式。需要注意的是VIP的master并不一定只出现在主机侧。比如DUT内部有一个AXI从接口需要通过VIP去驱动激励这个从接口本应该接master因为要发起读写的方向是从主机发起的。很多新人会把DUT接口是from DUT角度看的和VIP是from agent角度看的搞混。我常用的判断方法是只要看信号方向VIP实例化在哪个端口上就按端口方向决定是master还是slave。svt_axi_system_monitor可以带多个可以只挂一个在所有总线上。它的好处是一旦挂在接口上你就不用再自己写协议检查器了而是在仿真的任何时间点自动检查AXI协议规则。具体到VIP-2021.09版本monitor对象需要在build_phase里创建然后通过uvm_config_db传给它要监测的接口。2.2 配置类里的关键参数宽度、协议版本、ID和outstandingAXI VIP的行为完全由配置类svt_axi_configuration控制这相当于VIP的总开关。配置类拆开看最关键的几组参数如下数据总线宽度data_width和地址总线宽度addr_width这两个要和DUT接口完全一致。通常设为32、64或128在AXI3/AXI4协议中最大可以到1024但DUT不一定会支持那么宽。此处不要只依赖默认值必须从RTL参数中获取并保持编译期一致。协议版本protocol_versionAXI3和AXI4在突发支持、写响应、id宽度要求等方面不同。VIP-2021.09的配置类里一般有SVT_AXI_3和SVT_AXI_4枚举通过赋值设置。ID宽度id_widthID位宽决定outstanding能力。AXI4协议要求同一ID的一组事务不能乱序完成但不同ID可以乱序。Vip侧为了检查这一点需要知道ID位宽。如果设置过小仿真中会出现ID位宽断言失败设置过大则容易造成VIP内部队列资源浪费。稳妥做法是读RTL里的ID_WIDTH参数。读写outstanding能力read_outstanding和write_outstanding这组参数表示VIP在收到多个未完成事务后可以同时维护多少笔读写事务。如果设置比DUT实际能力大仿真中VIP可能发出DUT处理不了的突发最终超时。实际配置时我喜欢把VIP的outstanding能力设置成和DUT的MAX_OUTSTANDING参数一致或者比DUT能力略低让DUT不至于被压满。事务时间参数clock_freq、reset_assert_period等这些影响VIP在仿真中生成时钟和复位的时序不必须每个都改但建议明确设为DUT时钟周期。配置对象创建好之后建议把所有参数统一放在一个function里初始化不要散落在各个test里。实际项目里我会为每个test建立专门的config只覆盖该test关心的参数其余沿用base配置。3. 把AXI VIP接入Testbench的完整步骤3.1 编写AXI接口并连接到DUT顶层接入VIP的第一步是搭接口。Synopsys VIP提供现成的接口类但实际接入时我一般选择在顶层模块中手动定义AXI总线信号再通过interface绑定到VIP。这样RTL替换和调试都方便。一个典型写法如下interface axi_if #( parameter ADDR_WIDTH 32, parameter DATA_WIDTH 64, parameter ID_WIDTH 4 )(input clk, input rst_n); logic [ID_WIDTH-1:0] awid; logic [ADDR_WIDTH-1:0] awaddr; logic [7:0] awlen; logic [2:0] awsize; logic [1:0] awburst; logic awvalid; logic awready; // 其余写数据通道、读通道信号省略按AXI协议补充完整 ... endinterface接口里需要注意awready和awvalid的时序。如果直接在interface内部用clocking blockVIP配置时需要把clocking勾上。一般项目里我更喜欢不用clocking block而直接使用普通logic信号因为这样波形里看得清楚debug也更直观。连接时在testbench顶层中例化DUT和interface然后使用SystemVerilog的bind或者直接端口连接。直接端口连接更清晰module tb_top; reg clk; reg rst_n; axi_if my_axi_if(.clk(clk), .rst_n(rst_n)); dut_wrapper u_dut ( .axi_awid (my_axi_if.awid), .axi_awaddr (my_axi_if.awaddr), ... ); initial begin uvm_config_db#(virtual axi_if)::set(null, uvm_test_top.env.mst_agent.*, vif, my_axi_if); end endmodule3.2 在UVM环境中创建和连接VIP实例当接口已经接好后下一步就是在UVM环境中实例化VIP。这一步通常在build_phase中完成代码示例如下class my_env extends uvm_env; svt_axi_master mst; svt_axi_slave slv; svt_axi_system_monitor sys_mon; svt_axi_configuration mst_cfg; svt_axi_configuration slv_cfg; function void build_phase(uvm_phase phase); super.build_phase(phase); mst_cfg svt_axi_configuration::type_id::create(mst_cfg); mst_cfg.data_width 64; mst_cfg.addr_width 32; mst_cfg.id_width 4; mst_cfg.protocol_version svt_axi_configuration::SVT_AXI_4; mst_cfg.read_outstanding 8; mst_cfg.write_outstanding 8; uvm_config_db#(svt_axi_configuration)::set(this, mst.*, cfg, mst_cfg); mst svt_axi_master::type_id::create(mst, this); slv svt_axi_slave::type_id::create(slv, this); sys_mon svt_axi_system_monitor::type_id::create(sys_mon, this); endfunction function void connect_phase(uvm_phase phase); super.connect_phase(phase); mst.add_subscriber(sys_mon); slv.add_subscriber(sys_mon); endfunction endclass这里有两个关键点值得展开。第一配置类传的层级必须是mst.*而不是mst。因为VIP内部的agent、sequencer等组件查配置时用的是组件路径匹配。传错层次会导致VIP拿到默认配置宽度、ID宽度全部按默认值来翻DUT接口对不上报出一堆协议错误。第二svt_axi_system_monitor默认不是直接挂在信号上的它需要接收master和slave分析端口上的数据。所以要在connect_phase中把master和slave的ap端口连到monitor。如果不连monitor不会自动监测到DUT信号协议检查自然也就失效了。有的版本里monitor需要直接拿到vif可以在创建后手动把monitor的vif用config_db设置过去。3.3 用自带sequence发送一笔最简单的读写事务环境建好之后跑一个最简单的write/read测试是验证环境连通性的最好方法。Synopsys VIP自带的sequence类型有很多最常用的包括svt_axi_master_burst_sequence和svt_axi_slave_burst_sequence。前者用于master发起读写突发后者用于模拟从设备主动进行的操作。实际测试中一般focus在master sequence。一个最小化的test case长这样class axi_rw_test extends uvm_test; uvm_component_utils(axi_rw_test) my_env env; function void build_phase(uvm_phase phase); super.build_phase(phase); env my_env::type_id::create(env, this); endfunction task run_phase(uvm_phase phase); svt_axi_master_burst_sequence seq; phase.raise_objection(this); seq svt_axi_master_burst_sequence::type_id::create(seq); seq.start(env.mst.sequencer); #1us; phase.drop_objection(this); endtask endclass这个sequence默认会走在配置好的地址范围内随机产生一笔burst事务。如果在跑之前对地址没要求这种方式最简单。如果希望固定地址做定向测试可以约束sequence里面的addr字段。示例class axi_fixed_addr_seq extends svt_axi_master_burst_sequence; uvm_object_utils(axi_fixed_addr_seq) constraint c_addr { addr 32h0000_0000; burst_length 16; burst_type svt_axi_transaction::INCR; } endclass注意burst_type枚举名要根据版本手册确认不同版本可能叫BURST_INCR或INCR。启动sequence后一定要先等VIP自带的初始化sequence把总线的上电复位流程走完。很多第一次跑FAIL在SVT_AXI_BUS_IS_NOT_RESET这种错误上就是因为跳过VIP的初始化sequence了。Synopsys AXI VIP大部分testbench会默认执行一个sys_seq来reset总线所以你的test的第一个动作应该是svt_axi_sys_seq这类系统sequence而不是直接发事务。4. 仿真脚本、打印控制与调试技巧4.1 写一个能一键编译仿真的Makefile环境接完后尽力把编译和仿真命令固化成脚本这样后面跑回归和定位问题都能省时间。我会写一个简单的Makefile便于敲一个make run就搞定。VIP_HOME : /home/eda/synopsys/vip_2021.09 VCS_OPTS : -sverilog -ntb_opts uvm -timescale1ns/1ps -full64 -debug_accessall SRC_LIST : -f vip.f -f tb.f all: compile ./simv UVM_TESTNAMEaxi_rw_test UVM_VERBOSITYUVM_MEDIUM -l run.log compile: vcs $(VCS_OPTS) $(SRC_LIST) incdir$(VIP_HOME)/axi incdir$(VIP_HOME)/vip_common -o simv clean: rm -rf simv simv.daidir csrc *.log ucli.key真正跑之前务必要把UVM_TESTNAME换成你的test类名并且确保该test在run_test之前被编译。如果没指定test名而环境里又没有写默认值仿真会停在UVM的No test object specified阶段。这里再说一个常见问题UVM_VERBOSITYUVM_MEDIUM是整体UVN打印控制但对VIP打印的控制能力很有限。Synopsys VIP内部许多打印并不走uvm_report_info或者即便走了也用独立的宏和全局变量控制。所以你会发现当UVM verbosity降下来后VIP的transaction打印还是刷屏。这就引出下面这个每个项目都躲不过的问题。4.2 彻底关闭AXI VIP的transaction打印很多人在跑AXI VIP时被疯狂刷屏的transaction打印搞到崩溃。这个问题在VIP-2021.09里几乎一定有每次master发一笔burstVIP就把通道上的每个beat的信息打到终端跑一个千级事务的用例日志文件轻松膨胀到十几GB最可怕的是仿真速度被打印拖慢3倍以上。关打印的第一个入口在svt_axi_configuration里的打印等级字段。不同版本的字段名不太一样常见包括log_verbosity、verbose_level或max_verbosity。以VIP-2021.09为例配置类中会有一个对象控制消息打印级别你可以在build_phase里显式设置成“quiet”或“low”之类的低级别。这样大部分VIP内部debug信息就被抑制住了。代码示例大致是这样mst_cfg.log_verbosity svt_axi_configuration::LO_LEVEL_QUIET; slv_cfg.log_verbosity svt_axi_configuration::LO_LEVEL_QUIET;有些版本里的枚举不叫这个名字但含义类似。如果你在手册里找不到具体枚举那就按下面更通用的方式来方案一把uvm_report_object的verbosity调低对VIP内部组件逐级设置set_report_verbosity_level。因为VIP的顶层组件可能不逐级继承最稳妥的办法是在环境中遍历所有子组件并统一设置。代码function void set_vip_report_level(uvm_component comp, int level); comp.set_report_verbosity_level(level); for (int i 0; i comp.get_num_children(); i) begin uvm_component child comp.get_child(i); set_vip_report_level(child, level); end endfunction方案二对于不走UVM report框架而直接打印的部分采用编译期宏关闭。Synopsys VIP维护着一些全局宏在编译VIP源码时如果没有定义默认会输出所有信息。你可以查VIP安装目录下的svt_axi_defines.svh里面会列举所有控制开关的宏名。一般包含SVT_AXI_DISABLE_REPORT_LOG这类在编译命令里加define即可。不过不同版本宏名差异较大最好以当前安装版本源码为准。方案三更简单粗暴也是我最后的选择在仿真脚本里把终端日志重定向到文件再配合grep -v过滤。比如跑完仿真后直接grep -v SVT_AXI_TRANSACTION run.log clean.log。这种方法不影响仿真性能但只适合事后整理日志无法解决仿真中打印IO过多拖慢速度的问题。务必注意不要误把VIP的错误信息也一起关掉。关闭打印的粒度最好控制在info级别把error、fatal保留否则出问题时日志看起来一片干净找问题反而是噩梦。最怕的是VIP已经报了死锁还认为环境跑得好好的所以调打印量时一定要明确过滤的是哪些消息类型。4.3 使用Verdi和dump波形的几个实用设置AXI VIP运行时如果遇到协议问题别急着改代码先把波形dump下来看协议时序。Synopsys VIP在编译时只要开了-debug_accessallVerdi这边就能看到层次树。通常我在testbench顶层加两行initial begin $fsdbDumpfile(top.fsdb); $fsdbDumpvars(0, tb_top, all); enddump完波形之后最有效的调试手段是在Verdi里直接打开VIP自带的protocol window或者transaction window。在VIP-2021.09版本里Verdi可以把VIP的transaction信息显示在波形上直接看到burst起始地址、长度、大小和ID。这个方法比盯着信号波形分析快得多尤其是乱序传输的场景。如果你希望每次运行后自动打开Verdi可以在Makefile中加一个目标verdi: verdi -f vip.f -f tb.f -top tb_top -ssf top.fsdb 这里注意-top必须指定为testbench顶层模块名否则Verdi会找出多个顶层模块。5. 常见报错与避坑速查表5.1 编译阶段的经典问题Linux下缺失tcl/tk、VCS版本匹配我见过不少人在Linux上装完VCS运行vcs命令直接报动态库缺失比如libtcl.so、libtk.so找不到。这种情况通常不是VCS安装本身坏了而是系统环境变量LD_LIBRARY_PATH没把VCS自带依赖库路径加进去或者系统缺少tcl/tk的32位/64位开发库。排查方式ldd $(which vcs) | grep not found看到具体缺失哪个库再对症下药。如果缺libtcl8.5.so用find / -name libtcl*查一下系统里有没有其他版本然后把对应路径加入LD_LIBRARY_PATH。不建议贸然apt安装一套新版tcl/tk因为VCS不同版本对tcl/tk的版本有严格绑定装了新版可能和VCS内置expect脚本不兼容。更稳妥的做法是检查安装文档里要求的依赖列表。编译时报Syntax error通常是以下三种原因编译器版本不满足要求导致SystemVerilog新特性识别不了include目录没有正确添加VIP内部文件互相include时找不到头文件编译顺序错误vip_common必须在axi之前编译否则axi源码里引用的基础类找不到。5.2 仿真阶段的常见失败原因接口连接错位。常见表现为起点地址正确但burst路径上某些数据一直为X。这种情况多半是interface里信号名和DUT的引脚名没有一一对应VIP激活的接口信号实际没有驱动到DUT上。建议dump波形后在Verdi里搜索awready、wready、arready三个最关键信号逐个打标记看它们由谁驱动。超时和死锁。AXI读写事务超时优先检查awready和arready是否确实回到DUTVIP master发请求后slave如果不拉高ready总线永远不会继续。另一个常见坑是配置里的read_outstanding和write_outstanding比DUT参数大VIP连续发出多笔事务而DUT内部队列只能接受一笔就会一直不拉高ready。此时把配置里的outstanding参数调小到和DUT一致问题一般立刻消失。复位时序不对。VIP启动时如果检测到复位信号和配置手册要求的时序不一致会卡在初始化等待中表现为仿真跑一段时间后没有任何transaction打印。解决办法是在testbench的initial块中先拉低复位等待数个时钟周期后再释放VIP的复位标志。Synopsys VIP对一个完整的复位过程有严格定义比如复位信号至少保持多少个周期。在svt_axi_sys_seq里能看到对应参数。ID宽度不匹配导致的断言失败。VIP的配置类中设置的ID width如果和总线实际位宽不一致仿真跑到第一个事务就会被monitor抓包报错。这种报错可以在log里搜ID_WIDTH或axi_protocol_checker字样定位后在配置类里修正。5.3 问题排查速查表场景现象排除方向参考解法编译过不去提示找不到svt_axi_defines.svh环境变量路径检查incdir是否覆盖vip_common和axi目录仿真启动即退出报告UVM_NO_OBJECTUVM_TESTNAME指定test名或换用factory override仿真无响应长时间卡在初始化复位检查检查rst_n时序、VIP复位标志释放大量transaction打印日志巨大、仿真缓慢VIP print开关降低log_verbosity或禁用对应宏协议检查failAWID/WID不对齐ID位宽配置修正配置类id_width参数数据一直为XDUT不被驱动信号连接dump波形看接口上信号是否有效翻转运行一步就超时burst不响应outstanding参数把VIP outstanding调小到DUT能力内死锁出现但无报错所有ready拉低时钟/复位确认时钟一直翻转复位在VIP初始化前完成5.4 通过模拟真实场景来验证VIP配置是否正确真实项目里当VIP配置完成后不代表跑通一笔读写就万事大吉了。我一般还会做三样常规验证第一跑一笔read-after-write场景确认VIP master能正确接收slave的写响应再发起读请求避免配置中写响应通道相关问题被隐藏第二开启协议monitor的完整检查跑一批随机sequence让VIP自行把各种burst类型、size、len组合都跑到确认配置中的addr_width、data_width能覆盖总线上的所有组合第三手动在sequence里设置较长的burst长度和较大的outstanding观察DUT在极限场景下是否会死锁。这里如果awlen被设成255而DUT实际支持的最大burst只有64仿真必然报错这也是检查DUT参数和VIP配置是否一致的好办法。跑完这些场景后基本上VIP环境就算稳定了。后续加断言和功能覆盖率模型也会顺手很多。6. 最后补充一点关于版本迁移的实话说句实在话Synopsys AXI VIP是个功能很全但也很重的组件。我见过不少同事在VIP-2021.09上把环境跑通结果项目组要求换到比较新的版本或换到别的工具链代码里到处都是编译错误尤其是配置类里的字段名和枚举名几乎每次都变。所以在把VIP集成到自己工程中的时候强烈建议把用到的配置参数、宏、枚举值都记录到一个单独的说明文档里写明从SVN下载日期和VIP版本号再写上你实际用到的配置内容。否则两个月后你回来看代码只记得当时能跑却怎么也想不起来为什么当初要用LO_LEVEL_QUIET而不是SVT_AXI_VERBOSE_OFF。像很多验证工程师刚开始接触VIP时一样我也会先翻它的源码和example目录因为在example里它其实已经给了一个可以直接跑的master-slave最小环境比任何外部教程都靠谱。我的建议是第一次使用VIP时先在VIP安装目录的examples/axi下把官方样例原封不动编译一遍确认基线可用后再一步步替换成自己的DUT。这样能明显缩短排错时间。这套流程从我第一次用VIP一直用到现在一直是最快的手把手通关路径。

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

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

免费获取报价 →
↑