第 6 章 · 共 10 章
Factory 机制:注册、create() 与替换
`uvm_component_utils`/`uvm_object_utils` 到底注册了什么,为什么 type_id::create() 是一次查找而不是单纯构造一个对象,以及 set_type_override() 如何在不碰结构代码的情况下替换实现——补上第 3 章埋下的那笔最早的欠条。
第 3 章用过 `uvm_component_utils(my_driver) 和 my_driver::type_id::create("drv", this),并承诺以后会解释它们。这一章就是那个承诺的兑现——同时也是第 1 章第二条承诺的直接兑现:一种标准化的方式,不碰结构性代码就能替换某个实现。
这个宏到底注册了什么
`uvm_component_utils(my_driver)(以及它的对象版本 `uvm_object_utils,第 4 章的 mux_transaction 已经用过)做了两件事:
- 声明一个嵌套的
type_id——一个知道怎么专门构造my_driver的小助手类型。 - 把
my_driver注册到 factory:一张全局的、整个仿真共享的表,把"某个类型(或名字)"映射到"实际应该构造出什么"。
任何能通过 type_id::create() 构造的 UVM 类,都必须先经过这次注册——正是它让接下来的事情成为可能。
type_id::create():一次查找,不只是构造调用
my_driver::type_id::create("drv", this) 做的事情,不只是像 new("drv", this) 那样单纯构造一个 my_driver。它向 factory 提了一个问题:"现在有人要一个 my_driver,实际应该构造出什么?" 通常情况下,答案就是 my_driver 自己——所以 create() 的行为和 new() 完全一样。但这个答案是可以被改变的,全局生效,而且不需要修改调用 create() 那行代码的任何一个字。
这正是第 3 章一开始就用 create() 而不是 new() 的全部原因:new() 永远构造你写下的那个确切类型。create() 构造的是 factory 当前说该构造的类型——这正是下一节要讲的东西成为可能的原因。
替换:set_type_override()
class my_driver_alt extends my_driver;
`uvm_component_utils(my_driver_alt)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
task drive(mux_transaction tr);
`uvm_info("DRV_ALT", $sformatf("(alt driver) %s", tr.convert2string()), UVM_MEDIUM)
super.drive(tr);
endtask
endclassmy_driver_alt 只重写了第 4 章的 drive() task——其余的东西(build_phase 里的 config_db::get()、run_phase)都原样继承,super.drive(tr) 依然做真正的工作,重写只是多加了一行日志来宣告自己的存在。现在,在 test 的 build_phase 里、构造 drv 之前,告诉 factory 用它来代替 my_driver:
function void build_phase(uvm_phase phase);
super.build_phase(phase);
my_driver::type_id::set_type_override(my_driver_alt::get_type());
drv = my_driver::type_id::create("drv", this);
endfunctionget_type() 是 `uvm_component_utils 自动生成的另一个方法——它返回一个 factory 用来识别类型的句柄。这一行代码执行之后,testbench 里任何地方的 my_driver::type_id::create(...) 调用——不只是这一处——都会构造出一个 my_driver_alt。drv 声明的类型依然是 my_driver,构造 driver 的代码也一个字都没变;变的只是它底下实际构造出来的东西。把那一行删掉,drv 就变回一个普通的 my_driver——不管哪种情况,别处的代码都不用跟着改。
这正是第 1 章第二条承诺的直接兑现:capstone 那个手写的 driver 类只能通过直接修改代码来改变行为。这里,为某个 test 替换行为只需要一行代码,只出现在一个地方,而且不碰 my_driver、my_test,或者任何其他结构性代码。
和 SV 第 11 章多态是同一个想法,只是查找方式不同
SV 第 11 章(oop-inheritance-polymorphism)已经讲过通过继承来替换行为:一个基类句柄可以指向一个子类对象,调用一个 virtual 方法时,实际执行的是句柄当时真正指向的那个子类版本。factory 背后是同一个想法——在运行时分发到真正应该执行的那个子类——只是这次是由你配置的一次类型查找来决定,而不是由你恰好为哪个子类写了 new。type_id::create() 本质上就是"由一张你可以从外部修改的表来决定的多态",而不是"由哪一行代码调用了 new 来决定的多态"。
一个更精准的兄弟机制:instance override
set_type_override() 会影响该类型所有的 create() 调用。有时候只想改变某一个特定实例——为此,set_inst_override() 接收的是一条层次化路径,而不是整个类型:
my_driver::type_id::set_inst_override(my_driver_alt::get_type(), "uvm_test_top.drv");只有真正在那条确切路径上构造出来的组件才会被替换;树里其他地方任何一个 my_driver::type_id::create(...) 调用都不受影响。这个系列的例子只需要用到全类型的替换,但知道这个更精准的版本存在也是有用的。
小结
`uvm_component_utils/`uvm_object_utils把一个类注册到 factory——一张全局的表,把"被要求构造的类型"映射到"实际该构造的类型"。type_id::create(...)在真正构造之前会先查这张表;默认情况下答案就是这个类型本身,和new(...)完全一样——直到某个替换改变了这个答案。set_type_override()(通常在 test 的build_phase早期、在受影响的create()调用之前执行)会让之后所有针对某个类型的create()调用都改成构造另一个类型,而不需要碰调用create()的那些代码。set_inst_override()做同样的事,但只针对一个特定的实例路径,那个类型的其他实例都不受影响。- 这其实还是 SV 第 11 章基于继承的多态,只是这次通过一张可配置的表来分发,而不是由代码里某一行恰好写了哪个子类的
new来决定。
`` `uvm_component_utils(my_driver) `` 到底做了什么?
my_driver::type_id::set_type_override(my_driver_alt::get_type()); 执行之后,下一次调用 my_driver::type_id::create('drv', this) 会发生什么?
set_type_override() 和 set_inst_override() 在实际使用上有什么区别?
factory 的替换机制和 SV 第 11 章的多态材料是什么关系?