第 5 章 · 共 10 章
config_db 与虚接口传递
为什么 uvm_component 固定的 factory 构造函数签名排除了把虚接口作为构造函数参数传递的可能,以及 uvm_config_db::set()/get() 解决这个问题的完整机制——context、实例路径、通配符,还有为什么第 2 章 build_phase 的执行顺序正是它能生效的关键。
第 3 章已经用 uvm_config_db::set()/get() 把 vif 从顶层 module 传进了 driver,并承诺以后会解释它为什么有效。这一章就是那个承诺的兑现——之所以放在第 4 章之后立刻讲,而不是拖到系列更后面,是因为它依赖的东西已经全部就位:第 2 章的组件树和 build_phase 执行顺序。这里完全不需要用到 factory(第 6 章)或 sequence(第 7 章),所以没有理由让第 3 章埋下的这个欠条一直等到那两章才还。
为什么构造函数参数这条路走不通
SV 基础系列 capstone 里的 driver 类有一个自定义的构造函数:new(virtual mux2_if.tb_mp vif, mailbox #(mux_txn) mbx)。这行得通,是因为它是一个纯 SystemVerilog 类,用一个完全由作者自己掌控的 new(...) 调用来构造。
uvm_component 没有这个自由。第 3 章用 my_driver::type_id::create("drv", this) 而不是 new(...) 来构造 driver——这正是 UVM 的 factory(完整解释在第 6 章),它内部的工作方式是对最终实际被构造出来的那个类型调用 new(name, parent),而且严格只有这两个参数。这个固定签名里没有空间再塞一个像 vif 这样的第三个参数。这不是风格偏好——而是一个硬性约束:任何想要支持 factory 替换(override)的 uvm_component,都必须保持标准的双参数构造函数,这就意味着像 virtual interface 这样的配置数据必须走别的路径。
uvm_config_db 就是这条别的路径:一张全局的、关联式的查找表,任何组件都可以往里发布,也可以从里查询,完全独立于构造函数之外。
set() 与 get():四个参数
两个调用的形状是一样的:
uvm_config_db#(T)::set(cntxt, inst_path, field_name, value);
uvm_config_db#(T)::get(cntxt, inst_path, field_name, value); // get() 返回一个 bitcntxt——一个组件,作为inst_path的参照点。在组件内部,这几乎总是this。从一个纯 module 里调用(它根本不是一个组件——比如第 3 章顶层的initial块),没有组件可用,所以传null,意思是"把inst_path当作从层次结构最顶端算起的绝对路径来解析"。inst_path——一个字符串,描述这条记录适用于哪些组件,相对于cntxt而言。支持*/?通配符。空字符串""意味着"就是cntxt自己,别的都不算"。field_name——一个你自己选的任意字符串键,比如"vif"。只有当这个字符串在set()和get()两边完全一致时才会匹配——这里如果打错字,效果和从没调用过set()完全一样分辨不出来,而这正是第 3 章那个`uvm_fatal检查专门用来抓住的失败情形。value——数据本身(get()通过引用写入它,成功返回1,没有匹配上返回0)。
解码第 3 章的调用
// 顶层 module -- 不是组件,所以 cntxt 是 null
uvm_config_db#(virtual mux2_if.tb_mp)::set(null, "*", "vif", bus_if);null 意味着"从最顶端算起的绝对路径"。"*" 是一个通配符,匹配任意深度的任意组件路径——所以这一次调用就能覆盖任何现在或将来会查询字段名 "vif" 的组件,不管它在层次结构里有多深。
// my_driver 的 build_phase 内部 -- cntxt 就是这个组件自己
uvm_config_db#(virtual mux2_if.tb_mp)::get(this, "", "vif", vif);this 加上一个空的 inst_path("")意思是"在我自己确切的路径上,有没有字段 "vif" 的值可见?"——因为 set() 调用用的是 "*" 通配符,答案是有,不管 my_driver 实际在树里的哪个位置。
为什么这样能扩展,而构造函数参数不能
假设 driver 不是 test 的直接子组件,而是深了三层——my_test → my_env → my_agent → my_driver。如果用构造函数参数,vif 就必须一层一层地穿过每个中间层的构造函数,即使 my_env 和 my_agent 自己根本用不到它——纯粹是管道传递,而且只要这些层里任何一层要在别处以不同的深度被复用,这套管道就会崩掉。用 config_db 的话,什么都不用变:顶层那条带 "*" 通配符的 set() 依然能到达 my_driver,中间那些组件完全不需要知道 vif 这个东西的存在。
这之所以安全,靠的是第 2 章的 phase 执行顺序:build_phase 是自顶向下执行的,所以顶层 module 里的 set()——它甚至发生在 run_test() 启动整个 phase 引擎之前——一定会在任何深度的任何组件的 build_phase 调用 get() 之前就已经存在。
再多知道一点,虽然这个系列的例子从来不会真正碰到它:如果同一个
get()可能被不止一个set()匹配上,config_db会优先选择更具体的inst_path(精确路径胜过通配符),在同样具体的情况下,最后执行的那次set()胜出。读别人的 testbench 时知道这一点会有用;这里不需要专门为它设计什么。
小结
uvm_component通过 factory 构造出来的构造函数固定是(name, parent)——没有空间再塞额外的配置参数比如virtual interface,这正是config_db存在的真正原因。set(cntxt, inst_path, field_name, value)/get(cntxt, inst_path, field_name, value):cntxt是参照点(组件内部用this,纯 module 里用null),inst_path是相对cntxt的路径(支持*/?通配符,""表示就是cntxt自己),field_name是一个字符串键,set()和get()两边必须完全一致。- 顶层 module 里带
"*"通配符的set(),能覆盖任何深度、任何查询这个字段名的组件,完全不需要把这个值一层层穿过任何中间组件的构造函数。 - 这一切都依赖第 2 章那条保证:
build_phase自顶向下执行——顶层的set()(甚至在run_test()启动之前)一定会在任何组件的build_phase调用get()之前就已经存在。
为什么虚接口不能像 SV capstone 里那个纯类 driver 一样,直接作为 uvm_component 构造函数的第三个参数传进去?
在 uvm_config_db#(virtual mux2_if.tb_mp)::set(null, '*', 'vif', bus_if); 里,'*' 这个参数到底做了什么?
某个 driver 调用 uvm_config_db#(virtual mux2_if.tb_mp)::get(this, '', 'VIF', vif)(大写 VIF),但顶层 module 调用的是 set(null, '*', 'vif', bus_if)(小写 vif)。会发生什么?
为什么顶层 module 的 uvm_config_db::set() 调用必须在 run_test() 之前发生,具体是靠第 2 章的哪个 phase 机制保证的?