【SystemVerilog】UVM+TLM1.0 文章目录概念和知识点sformatfsformatrun_test()Factory获取当前层次从哪个类派生uvm_info宏控制冗级度的方式与其他报告宏的区别phaseObjection 机制Build Phases构建阶段Run Phases运行阶段Cleanup Phases清理阶段常见陷阱与最佳实践uvm_config_db#(T)TLM 1.0 通信概念必须实现函数表格实现建议代码示例函数签名analysisuvm_tlm_fifo #(T)​uvm_tlm_analysis_fifo #(T)UVM仿真示例-hellohello.sv文件do文件验证环境目录结构内容来自AI生成不保证正确性。记分板scoreboard也被称为checker概念和知识点sformatf功能按指定格式生成一个字符串并返回不执行任何打印。不会自动换行string msg;int addr 32’hA5A5_A5A5;int data 123;msg $sformatf(“ADDR0x%08h DATA%0d”, addr, data);%08h十六进制8位宽左补零%0dd表示十进制。0在这里不是填充符而是抑制前导零或指定宽度为 0即按实际宽度输出。sformat功能将格式化后的字符串写入一个字符串变量中。$sformat(dest_string, format_string, arg1, arg2, …);string s;$sformat(s, “Value%0d”, 100);// s “Value100”run_test()run_test(“xx”)是 UVM 的全局启动函数它创建 uvm_rootuvm_top根据字符串 xx通过 factory 创建指定的 test并驱动整个 phase 机制运行。initial beginrun_test(“my_test”);endFactory不用 Factory直接 new 顾客冲进厨房自己做菜用 Factory正确姿势 正规餐厅流程第一步登记菜单注册uvm_component_utils(my_driver)第二步点餐createmy_driver::type_id::create(“drv”, this);第三步厨房决定谁来做Factory 接管下单 my_driver - factory 查表 -发现今天用 my_driver_eco - my_driver_eco做Override 临时换菜不改菜单类型替换Type Override“今天所有 my_driver一律换成 my_driver_eco。”my_driver_eco::type_id::set_type_override( my_driver::get_type() );- 精准换菜Instance Overrideset_inst_override(uvm_top.env.agent.drv);“只有 3 号桌的 my_driver 换成 eco 版。”获取当前层次this.get_full_name()从哪个类派生“UVM 的一切要么是 component组件要么是 object对象。”验证平台中所有的组件必须派生自 uvm_component所有的数据对象必须派生自 uvm_object。如果组件不继承 uvm_component进不了 UVM 树没有 build / run / connect phaseconfig_db 找不到你factory override 失效如果对象不继承 uvm_object每个 transaction 都会占一个树节点仿真内存爆炸phase 行为混乱完全违背 UVM 设计哲学继承component组件类型必须继承的类testuvm_test → uvm_componentenvuvm_env → uvm_componentagentuvm_agent → uvm_componentdriveruvm_driver → uvm_componentmonitoruvm_monitor → uvm_componentsequenceruvm_sequencer → uvm_componentscoreboarduvm_scoreboard → uvm_componentcoverageuvm_subscriber → uvm_component继承object对象类型正确继承transactionuvm_sequence_itemsequenceuvm_sequenceconfigurationuvm_objectstatus/统计信息uvm_object每一个派生自uvm_component或其派生类的类在其new函数中要指明两个参数name和parent。uvm_info宏uvm_info(ID, MSG, VERBOSITY)uvm_info(“DRV_XMIT”, $sformatf(“Driving transaction: addr%h data%h”, tr.addr, tr.data), UVM_HIGH)参数类型说明IDstring消息标识符通常用模块名或功能标签如 “DRV”, “MON”, “SCOREBOARD”便于过滤和定位。MSGstring要输出的具体信息可以是静态字符串或动态拼接的字符串使用 $sformatf等。VERBOSITYUVM_VERBOSITY冗级度枚举决定该消息在何种冗级度设置下可见。VERBOSITY枚举数值典型用途UVM_NONE0始终显示即使关闭所有信息实际很少使用因为会覆盖所有控制UVM_LOW100关键流程信息如仿真开始/结束、主要配置UVM_MEDIUM200常规操作信息如事务发送、接收确认UVM_HIGH300详细调试信息如内部状态变化UVM_FULL400最详细的数据转储如波形点、全寄存器值UVM_DEBUG500开发者调试专用通常只在开发阶段启用控制冗级度的方式命令行全局控制将所有组件的默认冗级度设为 UVM_HIGH此时只有 VERBOSITY UVM_HIGH的 uvm_info才会输出。simv UVM_VERBOSITYUVM_HIGH按组件/ID精细控制simv uvm_set_verbosity“my_env,ALL,UVM_FULL,time,0”更灵活的方式是在 test 中动态设置function void start_of_simulation_phase(uvm_phase phase);uvm_root::get().set_report_verbosity_level(UVM_HIGH);env.scoreboard.set_report_id_verbosity(“SB_CHECK”, UVM_FULL);endfunction运行时动态调整可通过 uvm_cmdline_processor或 TCL 接口在仿真过程中改变冗级度。与其他报告宏的区别宏作用是否终止仿真uvm_info普通信息无副作用否uvm_warning警告提示潜在问题否但会累计 warning 计数uvm_error错误表明功能失败否但累计 error 计数通常测试会因此失败uvm_fatal致命错误立即退出仿真是MSGuvm_info(“MON”, sformatf(“Received packet #%0d, length%0d”, pkt_count, pkt.length), UVM_LOW)陷阱忘记使用 sformatf 而直接用 psprintf psprintf是遗留函数在 SystemVerilog 中应优先使用 sformatf。phaseUVM Phase 定义了验证组件生命周期的各个阶段确保所有组件按照统一的时间顺序执行特定任务如构建、连接、运行、报告等。Phase 机制解决了传统 Testbench 中组件初始化顺序不可控的问题并提供了同步点。所有 phase 都是虚函数/任务可以在组件中重写。Phase 顺序 Buildtop-down→ Runbottom-up, 并行→ Cleanuptop-down保持简单尽量只用 run_phase确保 objection 正确善用 uvm_info调试 phase 流程Objection 机制只有当所有组件都 drop 了 objectionrun phase 才会结束进入 cleanup 阶段。task run_phase(uvm_phase phase); phase.raise_objection(this); repeat(10) begin // 发送10个事务 end phase.drop_objection(this); endtask重要规则必须在 run_phase或子 phase 中调用否则无效。每个 raise 必须对应一个 drop否则仿真不会结束超时。推荐使用 phase.raise_objection(this, “message”)添加描述以便调试。Build Phases构建阶段从上到下执行Top-down不消耗仿真时间。build_phase创建子组件、配置对象、设置工厂覆盖。connect_phase连接 TLM 端口、export 等。end_of_elaboration_phase检查拓扑结构是否正确。start_of_simulation_phase仿真开始前的最后配置如设置冗级度。Run Phases运行阶段从下到上执行Bottom-up消耗仿真时间支持并行任务。注意子 phase 和 run_phase是并行执行的run_phase会贯穿整个运行阶段。通常建议要么只用 run_phase要么只用子 phase避免混淆run_phase主运行阶段所有组件同时运行。12个细分 phase按时间顺序pre_reset_phasereset_phasepost_reset_phasepre_configure_phaseconfigure_phasepost_configure_phasepre_main_phasemain_phasepost_main_phasepre_shutdown_phaseshutdown_phasepost_shutdown_phaseCleanup Phases清理阶段从上到下执行Top-down不消耗仿真时间。extract_phase从监视器收集最终数据。check_phase进行协议检查和记分板比较。report_phase生成测试报告。final_phase最后的清理工作。常见陷阱与最佳实践忘记调用 super.build_phase()会导致父类行为丢失如自动创建 sequencer等。务必在所有 phase 重写中调用 super。Objection 放置错误不能在 build_phase中 raise objection它是 function不消耗时间。不能在 run_phase之外的任务中 raise objection。如果使用子 phaseobjection 必须在对应的子 phase 中 raise/drop而不是在 run_phase。子 phase 与 run_phase 混合使用如果同时在 run_phase和 main_phase中 raise objection可能导致 phase 提前结束或永不结束。建议选择一种风格。仿真超时timeout当某个组件忘记 drop objection仿真会一直挂起直到达到 UVM_TIMEOUT默认 9200s。可以通过命令行 UVM_TIMEOUTtime_in_ns调整但根本解决是确保 objection 配对。uvm_config_db#(T)它不是存在某个component里的所有component共享同一张表。主要用途把配置对象、虚接口(virtual interface)、开关变量等送到层次化验证平台里避免直接用 tb_top.env…硬引用。set/get地址 cntxt inst_name field_nameuvm_config_db#(int)::set(this, “env.agent”, “mode”, 1);最终路径“this.env.agent.mode”uvm_config_db存储结构高级视角UVM 1.2 内部维护的是一张 路径 字段名 的哈希表“uvm_top.test.env.agent.mode” - 1null什么作用–忽略null表示“不使用任何 uvm_component 作为路径起点”此时 inst_name被当作一个“绝对路径模式字符串”直接使用。set(null, “, …)set(null, ”, …) 匹配所有人通配全盘set(null, “uvm_test_top.env.agt”, …) 精准打到这个实例set(this, “” , …) 打到 this本身set(this, “*”, …) 打到this的所有子子孙孙set发布配置static function void set( uvm_component cntxt, // 上下文组件决定从哪开始算路径 string inst_name, // 相对路径/通配符拼接到cntxt后面 string field_name, // 键名标识这条配置叫什么 T value // 要存的值 );get获取配置static function bit get( uvm_component cntxt, // 调用者自己通常是 this string inst_name, // 相对路径通常 string field_name, // 要取的键名 inout T value // 输出成功则被覆盖 ); // return 1 找到并写入value0 没找到TLM 1.0 通信概念作用把class A的transaction传给class B的函数/任务。A_port.put(transaction)最终调用 B.put(transaction)连接拓扑port→export→imp三级。port和export只是一道门不具备存储功能需要imp来实现存储。在UVM中只有IMP才能作为连接关系的终点。【方向put操作port输出、get操作port输入、transport操作(先put后get)】 x 【阻塞、非阻塞】可以简单理解成A.A_port.put(transaction)这个任务会调用B.B_export的putB.B_export的put(transaction)又会调用B.B_imp的put(transaction)而B_imp.put最终又会调用B的相关任务如B.put(transaction)。名字的解释uvm_blocking_put只是类型不是相应类型的操作。port阻塞模式输出。端口的互连:UVM 中使用connect 函数来建立连接关系。发起者才能调用connect 函数。如A要和B通信A 是发起者那么可以这么写A.port.connect(B.export)但是不能写成B.export.connect(A.port)。举例class A 内部定义uvm_blocking_put_portclass B 内部定义uvm_blocking_put_export在env内进行连接 A_inst.A_port.connect(B_inst.B_export);对比put/getuvm_blocking_put_port用于 “推送者” (Pusher)。组件产生数据并主动将其发送出去。生产者 (Producer)uvm_blocking_get_port用于 “拉取者” (Puller)。组件消耗数据并主动向外界请求数据。消费者 (Consumer)Task vs Function:Blocking (阻塞): 必须是 task因为它们可能会消耗仿真时间等待时钟或握手信号。Nonblocking (非阻塞): 必须是 function并且必须立即返回零耗时通常返回一个 bit指示操作是否成功。关于 FIFO:在 UVM 中我们通常不直接在 Component 中实现 imp。相反我们使用 uvm_tlm_fifo。如果你的 A_port是 uvm_blocking_put_port你连接到 uvm_tlm_fifo的 blocking_put_export。FIFO 内部已经实现了上述所有的 put, get, try_put等方法无需手动编写。Analysis Port 的特殊性:它只有 write方法没有 put或 get。它是 广播 的一对多而普通的 Put/Get 是 点对点 的一对一。Analysis Imp 只需要实现 write(T t)函数。Combined Ports (如 uvm_put_port):虽然标准定义了 Combined Ports但在实际工程中为了代码清晰和避免死锁强烈建议只使用 blocking或 nonblocking中的一种除非你明确知道接收端支持两者。必须实现函数表格Port 类型 (发起端)Imp/Export 必须实现的方法 (接收端)说明uvm_blocking_put_porttask put(T t)阻塞式发送。发送方会等待直到接收方准备好接收数据。uvm_nonblocking_put_portfunction bit try_put(T t)function bit can_put()非阻塞式发送。try_put尝试立即发送返回 1 表示成功can_put查询是否可以发送。uvm_put_port(Combined)task put(T t)function bit try_put(T t)function bit can_put()混合模式。必须同时实现阻塞和非阻塞的方法。uvm_blocking_get_porttask get(output T t)阻塞式获取。请求方会等待直到数据可用。uvm_nonblocking_get_portfunction bit try_get(output T t)function bit can_get()非阻塞式获取。try_get尝试立即获取数据。uvm_get_port(Combined)task get(output T t)function bit try_get(output T t)function bit can_get()混合模式。必须同时实现阻塞和非阻塞的获取方法。uvm_blocking_peek_porttask peek(output T t)窥视数据。获取数据但不将其从 FIFO/Buffer 中移除。uvm_nonblocking_peek_portfunction bit try_peek(output T t)function bit can_peek()非阻塞窥视。uvm_peek_port(Combined)task peek(...)function bit try_peek(...)function bit can_peek(...)混合模式窥视。uvm_blocking_get_peek_porttask get(output T t)task peek(output T t)阻塞式获取与窥视。uvm_nonblocking_get_peek_portfunction bit try_get(...)function bit can_get(...)function bit try_peek(...)function bit can_peek(...)非阻塞获取与窥视。uvm_get_peek_port(Combined)(上述所有 Get 和 Peek 方法)最全的 Get/Peek 接口集合。uvm_analysis_portfunction void write(T t)广播/分析端口。这是单向的不关心接收方是否准备好通常用于 Coverage 或 Scoreboard。uvm_blocking_transport_porttask transport(input T req, output T rsp)阻塞式传输。常用于 Request-Response 协议如总线读写。uvm_nonblocking_transport_portfunction bit nb_transport(input T req, output T rsp)非阻塞式传输。uvm_transport_port(Combined)task transport(...)function bit nb_transport(...)混合传输。实现建议工程实践建议 首选 FIFO 作为中介 在实际工程中永远不要让两个组件直接通过 Port/Export 相连。请务必使用 uvm_tlm_fifo。 Producer 使用 blocking_put_port- 连接到 fifo.blocking_put_export Consumer 使用 blocking_get_port- 连接到 fifo.blocking_get_export 好处解耦生产者和消费者的速度Rate Decoupling。Driver 可以快Scoreboard 可以慢互不影响。 避免死锁 如果一个组件同时使用了 put_port和 get_port连接到同一个 FIFO 或另一个组件极易造成死锁。 规则一个组件最好只扮演一种角色要么全是 Put要么全是 Get或者使用 Analysis Port单向广播无阻塞来替代。 Monitor 的特殊性 Monitor 通常是数据的源头。它监测 DUT 信号并打包成 Transaction。因此Monitor 通常使用 uvm_analysis_portwrite函数来广播数据给 Scoreboard 或其他组件而不是 put或 get。代码示例class A extends uvm_component; uvm_blocking_put_port#(my_transaction) A_port; function void build_phase(uvm_phase phase); super.build_phase(phase); A_port new(A_port, this); endfunction task A::main_phase(uvm_phase phase); my_transaction tr; repeat(10) begin #10; tr new(tr); assert(tr.randomize()); A_port.put(tr); end endtask endclass class B extends uvm_component; uvm_blocking_put_export#(my_transaction) B_export; uvm_blocking_put_imp#(my_transaction, B) B_imp; function void build_phase(uvm_phase phase); super.build_phase(phase); B_export new(B_export, this); endfunction function void connect_phase(uvm_phase phase); super.connect_phase(phase); B_export.connect(B_imp); endfunction function void put(my_transaction tr);//必须实现put任务/函数 uvm_info(B, receive a transaction, UVM_LOW) tr.print(); endfunction endclass //env内连接 class my_env extends uvm_env; function void connect_phase(uvm_phase phase); super.connect_phase(phase); A_inst.A_port.connect(B_inst.B_export); endfunction endclass函数签名//port uvm_blocking_put_port#(T); uvm_nonblocking_put_port#(T); uvm_put_port#(T); uvm_blocking_get_port#(T); uvm_nonblocking_get_port#(T); uvm_get_port#(T); uvm_blocking_peek_port#(T); uvm_nonblocking_peek_port#(T); uvm_peek_port#(T); uvm_blocking_get_peek_port#(T); uvm_nonblocking_get_peek_port#(T); uvm_get_peek_port#(T); uvm_blocking_transport_port#(REQ, RSP); uvm_nonblocking_transport_port#(REQ, RSP); uvm_transport_port#(REQ, RSP); //export uvm_blocking_put_export#(T); uvm_nonblocking_put_export#(T); uvm_put_export#(T); uvm_blocking_get_export#(T); uvm_nonblocking_get_export#(T); uvm_get_export#(T); uvm_blocking_peek_export#(T); uvm_nonblocking_peek_export#(T); uvm_peek_export#(T); uvm_blocking_get_peek_export#(T); uvm_nonblocking_get_peek_export#(T); uvm_get_peek_export#(T); uvm_blocking_transport_export#(REQ, RSP); uvm_nonblocking_transport_export#(REQ, RSP); uvm_transport_export#(REQ, RSP); //imp uvm_blocking_put_imp#(T, IMP); uvm_nonblocking_put_imp#(T, IMP); uvm_put_imp#(T, IMP); uvm_blocking_get_imp#(T, IMP); uvm_nonblocking_get_imp#(T, IMP); uvm_get_imp#(T, IMP); uvm_blocking_peek_imp#(T, IMP); uvm_nonblocking_peek_imp#(T, IMP); uvm_peek_imp#(T, IMP); uvm_blocking_get_peek_imp#(T, IMP); uvm_nonblocking_get_peek_imp#(T, IMP); uvm_get_peek_imp#(T, IMP); uvm_blocking_transport_imp#(REQ, RSP, IMP); uvm_nonblocking_transport_imp#(REQ, RSP, IMP); uvm_transport_imp#(REQ, RSP, IMP);analysisanalysis_port、analysis_export、analysis_imp广播1对多当有2个port都.write()广播由于接收到的两路数据应该做不同的处理所以这个新的IMP也要有一个write任务与其对应。但是write只有一个怎么办需要使用 uvm_analysis_imp_decl(_monitor) 宏加个后缀区分。当与monitor_imp相连接的analysis_port执行write函数时会自动调用write_monitor函数。port侧还是使用write()。uvm_analysis_imp_decl(_monitor) uvm_analysis_imp_decl(_model) class my_scoreboard extends uvm_scoreboard; uvm_analysis_imp_monitor#(my_transaction, my_scoreboard) monitor_imp; uvm_analysis_imp_model#(my_transaction, my_scoreboard) model_imp; extern function void write_monitor(my_transaction tr); extern function void write_model(my_transaction tr); extern virtual task main_phase(uvm_phase phase); endclass虚基类uvm_tlm_fifo_base #(T) 定义 FIFO 的端口骨架 方法原型(put/get/peek/flush/…主类 uvm_tlm_fifo #(T)​ 点对点缓冲 FIFO解耦生产者速度 ≠ 消费者速度主类 uvm_tlm_analysis_fifo #(T)​ extends uvm_tlm_fifo #(T) 在uvm_tlm_fifo的基础上 加了 analysis_exportwrite()入口专用于 analysis广播→缓冲​uvm_tlm_fifo #(T)​function new(string name, uvm_component parent null, int size 1);size 0无界Export / Imp 名字用途典型连接方put_export内部put_imp写入侧put()/try_put()/can_put()生产者通过uvm_blocking_put_port或uvm_put_port连接get_peek_export内部get_peek_imp读取侧get()/try_get()/peek()/try_peek()消费者通过uvm_blocking_get_port或uvm_get_peek_port连接blocking_put_exportnonblocking_put_export…可按需选择“最小接口”粒度取决于端口设计粒度analysis_export❌ 不支持这是它与analysis_fifo的关键区别—uvm_tlm_fifo的 nonblocking_get_export本身无法“保证”一定有数据。你必须手动调用 can_get()进行预检查或者在更高层级的 run_phase逻辑中处理“无数据”的情况。nonblocking_get接口包含两个函数函数返回值行为try_get(output T t)bit(1成功,0失败)尝试立即取出数据。若 FIFO 为空返回0且不修改t。can_get()bit(1可以,0不可以)查询 FIFO 当前是否有数据可读。if (nb_get_port.try_get(tr)) begin // 只有返回 1 时tr 才是有效的 uvm_info(GET_OK, $sformatf(Got transaction: %s, tr.convert2string()), UVM_LOW) process_transaction(tr); end两个常用辅助端口偏观测/追踪不用于主数据流仲裁put_apuvm_analysis_port #(T)每 put 成功后自动 put_ap.write(t)一次get_apuvm_analysis_port #(T)每 get/peek 取出后自动 get_ap.write(t)一次常用方法size()/ used()/ is_empty()/ is_full()/ flush()// generator / driver / whatever producer uvm_blocking_put_port #(my_tr) put_port; // somewhere in env uvm_tlm_fifo #(my_tr) fifo new(fifo, this, 8); // depth8 // connect_phase producer.put_port.connect(fifo.put_export); consumer.get_port.connect(fifo.get_peek_export);uvm_tlm_analysis_fifo #(T)如果是 Monitor 到 Scoreboard用 analysis_fifo try_get​ 是最优雅的解法。上图中所有以圆圈表示的EXPORT虽然名字中有export但是本质上都是IMP。当get任务被调用时FIFO内部缓存中会少一个transaction而peek被调用时FIFO会把transaction复制一份发送出去其内部缓存中的transaction数量并不会减少。analysis_fifo 就是一个 uvm_tlm_fifo 无界 带 write()入口新增analysis_export类型是uvm_analysis_imp #(T, this_type)让 uvm_analysis_port #(T).write(t)​ 能连进来它的 write()实现本质上就是 try_put(t)所以不会因为广播阻塞——这是 analysis 语义的要求而且因为构造时把 size 固定为 unbounded0基本不会出现analysis 写入丢事件的争议但你仍要考虑内存增长风险。什么时候用它经验法则Monitor →analysis_port→ Scoreboard / CoverageMonitor 侧天然是 analysis 广播语义一对多、不阻塞、fire-and-forget。如果你希望 Scoreboard 侧仍然用 get()/try_get()按自己节奏消费而不是被迫实现一个 analysis_imp::write()里立刻处理就把 uvm_tlm_analysis_fifo放在 Scoreboard 里当缓冲邮箱// scoreboard uvm_tlm_analysis_fifo #(mon_tr) act_fifo new(act_fifo, this); // env connect_phase mon.a_port.connect(sb.act_fifo.analysis_export); // uvm_analysis_port 连到 analysis_exportimp // scoreboard run_phase act_fifo.get(tr); // 按自己节奏取UVM仿真示例-hellohello.sv文件include uvm_pkg.sv module hello; import uvm_pkg::*; include uvm_macros.svh initial begin uvm_info(info1,Hello UVM!,UVM_LOW) end endmoduledo文件quit -sim cd F:/UVM/prj00 # 设置UVM DPI库路径用于UVM与C/C的交互 set UVM_DPI_HOME D:/app/questasim2020/uvm-1.1d/win64 # 设置UVM源代码路径 setenv UVM_HOME D:/app/questasim2020/verilog_src/uvm-1.1d if [file exists work] { vdel -all } vlib work # -L mtiAvm链接到 Mentor Graphics 提供的 AVMAdvanced Verification Methodology库。允许在设计中使用 AVM 类库。 # -L mtiOvm链接到 OVMOpen Verification Methodology库。允许使用 OVM 基类。 # -L mtiUvm链接到 UVMUniversal Verification Methodology库。这是最常用的选项使编译后的代码能调用 UVM 类如 uvm_component, uvm_sequence等。 # -L mtiUPF链接到 UPFUnified Power Format库。用于低功耗设计的电源意图描述如果设计中包含 UPF 约束则需要此库。 vlog -L mtiAvm -L mtiOvm -L mtiUvm -L mtiUPF incdir${UVM_HOME}/src incdir${UVM_HOME}/src/uvm_pkg.sv -f rtl_file.f -f vrf_file.f # -c命令行模式Console mode不启动图形界面GUI。适用于自动化回归测试或批处理运行。 # notimingchecks 禁用时序检查 # UVM_TESTNAME$TEST 指定UVM测试用例 # UVM_VERBOSITYUVM_FULL 设置UVM消息详细级别 # -t 1ps 时间精度为1ps # -voptargsacc 优化参数保留所有信号可见性 # -solvefaildebug 启用调试功能 # -uvmcontrolall 启用所有UVM控制功能 # -classdebug 启用类调试 # -l sim.log 指定日志文件 # -pli $PLI PLI接口 # -sv_lib $UVM_DPI_HOME/uvm_dpi 加载UVM DPI库.dll/ .so vsim -c -sv_lib $UVM_DPI_HOME/uvm_dpi work.hello # 也有这么写的 set TEST my_case0 set PLI vsim -c \ notimingchecks \ UVM_TESTNAME$TEST \ UVM_VERBOSITYUVM_FULL \ -t 1ps \ -voptargsacc \ -solvefaildebug \ -uvmcontrolall \ -classdebug \ -l sim.log \ -pli $PLI \ -sv_lib $UVM_DPI_HOME/uvm_dpi \ work.hello run 100注意事项Tcl 的环境变量如 ${UVM_HOME}在 filelist.f 中不会自动展开。验证环境目录结构UVM_TEMPLATE/ │ ├── RTL/ # RTL设计代码 │ └── dut.sv # 被测设计(DUT)文件 │ └── Verification/ │ ├── sim/ # 仿真相关文件 │ ├── dut.f # DUT文件列表 │ ├── filelist.f # 验证环境文件列表 │ ├── run.bat # 启动仿真的批处理脚本 │ ├── sim.do # Questa仿真脚本 │ ├── sim.log # 仿真日志 │ └── transcript # 仿真输出记录 │ └── tb/ # UVM测试平台文件 ├── base_test.sv # UVM基础测试类 ├── my_agent.sv # UVM agent组件 ├── my_case0.sv # 测试用例0 ├── my_case1.sv # 测试用例1 ├── my_driver.sv # UVM driver组件 ├── my_env.sv # UVM环境类 └── top_tb.sv # 测试顶层文件https://zhuanlan.zhihu.com/p/1901245163285751111