UVM寄存器模型:reg adapter实现和集成
目录
4. bus2reg()和reg2bus() 是如何被调用?
1. 概要
UVM寄存器模型有内建的抽象的read()和write()等寄存器访问命令,实际的寄存器访问需要通过对用于寄存器访问的总线接口进行驱动来完成。典型的总线接口有AMAB总线家族、I2C总线、SPI总线等都可以用作寄存器访问的总线接口。
在UVM验证平台中,要完成寄存器访问,需要进行寄存器模型的抽象的read/write访问动作与具体种类总线的bus transaction之间的转换,这就是adapter(between reg and bus)所要扮演的角色。adapter的实现,更具体一点说是reg2bus和bus2reg两个任务的实现随实际使用的总线协议不同而不同。UVM有一个专门的类uvm_reg_adapter用作reg/bus adapter实现模板,用户实现的reg/bus adapter class必须继承于uvm_reg_adapter。
2. reg/bus adapter的实现
reg/bus adapter的实现包含以下两个要点:
- uvm_reg_bus_op与总线transaction中各自的数据映射。
- 实现reg2bus()和bus2reg()两个函数,这两个函数即实现了两种transaction的数据映射,函数名则表明了它们的转换方向。这两个函数原型是预定义的,验证开发者必须去实现其具体处理。
reg2bus()和bus2reg()两个函数是uvm_reg_adapter class中定义的纯虚函数(pure virtual function) ,如下所示(uvm_reg_adapter.svh):
virtual class uvm_reg_adapter extends uvm_object;
...
// Function: reg2bus
//
// Extensions of this class ~must~ implement this method to convert the specified
// <uvm_reg_bus_op> to a corresponding <uvm_sequence_item> subtype that defines the bus
// transaction.
//
// The method must allocate a new bus-specific <uvm_sequence_item>,
// assign its members from
// the corresponding members from the given generic ~rw~ bus operation, then
// return it.
pure virtual function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
// Function: bus2reg
//
// Extensions of this class ~must~ implement this method to copy members
// of the given bus-specific ~bus_item~ to corresponding members of the provided
// ~bus_rw~ instance. Unlike <reg2bus>, the resulting transaction
// is not allocated from scratch. This is to accommodate applications
// where the bus response must be returned in the original request.
pure virtual function void bus2reg(uvm_sequence_item bus_item,
ref uvm_reg_bus_op rw);
...
此外,如果总线支持byte访问,可以使能supports_byte_enable;如果总线UVC要返回response数据,则应当使能provides_response。参考以下UVM代码(uvm_reg_adapter.svh):
virtual class uvm_reg_adapter extends uvm_object;
// Function: new
//
// Create a new instance of this type, giving it the optional ~name~.
function new(string name="");
super.new(name);
endfunction
// Variable: supports_byte_enable
//
// Set this bit in extensions of this class if the bus protocol supports
// byte enables.
bit supports_byte_enable;
// Variable: provides_responses
//
// Set this bit in extensions of this class if the bus driver provides
// separate response items.
bit provides_responses;
...
以下为一个基于APB总线的寄存器访问接口的adapter类的实现例。
class apb_reg_adapter extends uvm_reg_adapter;
`uvm_object_utils(apb_reg_adapter)
function new(string name = "apb_reg_adapter");
super.new(name);
provides_responses = 1;
endfunction
function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
apb_transfer t = apb_transfer::type_id::create("t");
t.trans_kind = (rw.kind == UVM_WRITE) ? WRITE : READ;
t.addr = rw.addr;
t.data = rw.data;
t.idle_cycles = 1;
return t;
endfunction
function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
apb_transfer t;
if (!$cast(t, bus_item)) begin
`uvm_fatal("CASTFAIL","Provided bus_item is not of the correct type")
return;
end
rw.kind = (t.trans_kind == WRITE) ? UVM_WRITE : UVM_READ;
rw.addr = t.addr;
rw.data = t.data;
rw.status = t.trans_status == OK ? UVM_IS_OK : UVM_NOT_OK;
endfunction
endclass
其中,uvm_reg_bus_op为UVM预定义类,包含以下成员:
如以上例码所示,一般来说,adapter的实现其实就是bus2reg和reg2bus两个函数的实现而已。reg2bus()完成的桥接场景是,如果用户在寄存器级别做了操作,那么寄存器级别操作的信息uvm_reg_bus_op会被记录,同时调用uvm_reg_adapter::reg2bus()函数。在完成了将uvm_reg_bus_op的信息映射到bus_trans(即以上代码例中的apb_transfer)之后,函数将bus_trans实例返回。而在返回bus_trans之后,该实例将通过bus_seqeuncer传入到bus_driver。这里的transaction传输是后台隐式调用的,不需要主动发起。
bus2reg()函数的功能与reg2bus()相反,完成了从bus_trans到uvm_reg_bus_op的内容映射。在完成映射之后,更新的uvm_reg_bus_op数据最终返回至寄存器操作场景层。
对于寄存器操作,无论读操作还是写操作,都需要经历调用reg2bus(),继而发起总线事务,而完成总线事务发回反馈之后,又需要调用bus2reg(),将总线的数据返回至寄存器操作层面。
3. reg/bus adapter的集成
图1 UVM寄存器环境系统模型(取自[2])
上图为UVM寄存器环境系统模型示意图,但是有一点容易被误解的地方是,adapter其实是一个相对独立的组件,regmodel和predictor分别通过不同的方式间接引用同一个adapter的对象,而不是像上图一样在regmodel和predictor中各有一个adapter。
adapter通常是放在env中,然后predictor通过句柄的方式引用adapter的对象,regmodel则是通过map.set_sequencer()来建立与adapter对象的关联,如下图所示:
以下为env中集成寄存器模型的代码示例:
class i2c_env extends uvm_env;
// top configuration and virtual interface
i2c_config cfg;
virtual i2c_if vif;
...
// top register model and related components
ral_block_i2c rgm;
apb_reg_adapter adapter;
uvm_reg_predictor #(apb_transfer) predictor;
`uvm_component_utils(i2c_env)
function new (string name = "i2c_env", uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
...
if(!uvm_config_db #(ral_block_i2c)::get(this, "", "rgm", rgm)) begin
`uvm_info("build_phase", "Unable to get ral_block_i2c from uvm_config_db and create a RGM locally", UVM_LOW)
rgm = ral_block_i2c::type_id::create("rgm", this);
rgm.build();
rgm.lock_model();
end
...
adapter = apb_reg_adapter::type_id::create("adapter", this);
predictor = uvm_reg_predictor#(apb_transfer)::type_id::create("predictor", this);
endfunction: build_phase
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
...
// register model integration
rgm.default_map.set_sequencer(apb_mst.sequencer, adapter);
apb_mst.monitor.item_collected_port.connect(predictor.bus_in);
predictor.map = rgm.default_map;
predictor.adapter = adapter;
endfunction: connect_phase
endclass
4. bus2reg()和reg2bus() 是如何被调用?
UVM初学者很容易感到困惑的一个地方是,UVM验证平台中很多函数只需要用户实现它们,并不需要对其进行调用,那实现它们干吗呢?原因是UVM已经偷偷地帮助你在需要的时候进行了调用,所以用户只管实现不管调用。回调函数都是属于这一类。
具体一点,bus2reg()和reg2bus()的调用是如何发生的呢?
如前所述,一旦发生了寄存器访问操作,reg2bus()会被隐式地调用用以将抽象的寄存器操作(read(), write(),etc)转换为bus transaction,然后该bus transaction将由对应的sequencer转发给bus driver,最后由bus driver生成接口波形驱动到DUT接口上去,这一切都因为以下这条语句建立的关联为基础,UVM自动地在后台为你把所有其它的事情都干了。
rgm.default_map.set_sequencer(apb_mst.sequencer, adapter);
同样,发生寄存器访问操作时,bus monitor会将总线上观测到的信号变化采样并打包成bus transaction传给predictor,然后preditor会调用adapter::bus2reg()将bus transaction再变回寄存器模型的抽象操作。为什么要这么做呢?这是因为predictor需要根据总线上监测到的信号变化状况来预测接下来DUT中的真实的寄存器值(actual value)将会发生什么变化,并将这个变化反馈给寄存器模型,寄存器模型则据此更新mirror value,使得mirror value保持与actual value同步。关于UVM寄存器模型的mirror value、actual value与desired value三者之间关系、区别以及同步等也是比较把初学者搞昏头的,需要另文专门解释。
4.1 bus2reg() 在哪里被调用?
查阅UVM库的源代码可以找到bus2reg在以下三个地方被调用(不知道还有没有其它地方?待进一步确认):
bus2reg() called in uvm_reg_map:
task uvm_reg_map::do_bus_write (uvm_reg_item rw,
uvm_sequencer_base sequencer,
uvm_reg_adapter adapter);
...
if (adapter.provides_responses) begin
uvm_sequence_item bus_rsp;
uvm_access_e op;
// TODO: need to test for right trans type, if not put back in q
rw.parent.get_base_response(bus_rsp);
adapter.bus2reg(bus_rsp,rw_access);
end
else begin
adapter.bus2reg(bus_req,rw_access);
end
...
task uvm_reg_map::do_bus_read (uvm_reg_item rw,
uvm_sequencer_base sequencer,
uvm_reg_adapter adapter);
...
if (adapter.provides_responses) begin
uvm_sequence_item bus_rsp;
uvm_access_e op;
// TODO: need to test for right trans type, if not put back in q
rw.parent.get_base_response(bus_rsp);
adapter.bus2reg(bus_rsp,rw_access);
end
else begin
adapter.bus2reg(bus_req,rw_access);
end
...
bus2reg() called in predictor:
class uvm_reg_predictor #(type BUSTYPE=int) extends uvm_component;
`uvm_component_param_utils(uvm_reg_predictor#(BUSTYPE))
...
// Variable: adapter
//
// The adapter used to convey the parameters of a bus operation in
// terms of a canonical <uvm_reg_bus_op> datum.
// The <uvm_reg_adapter> must be configured before the run phase.
//
uvm_reg_adapter adapter;
...
// Function- write
//
// not a user-level method. Do not call directly. See documentation
// for the ~bus_in~ member.
//
virtual function void write(BUSTYPE tr);
uvm_reg rg;
uvm_reg_bus_op rw;
if (adapter == null)
`uvm_fatal("REG/WRITE/NULL","write: adapter handle is null")
// In case they forget to set byte_en
rw.byte_en = -1;
adapter.bus2reg(tr,rw);
rg = map.get_reg_by_offset(rw.addr, (rw.kind == UVM_READ));
...
uvm_reg_predictor(UVM寄存器环境中的preditor为该class的直接实例)中有一个adapter类型的句柄(该句柄将在env中指向真正的adapter对象),而adapter.bus2reg()就在uvm_reg_predictor::write()中被调用。而如以上函数说明所示,uvm_reg_predictor::write()本身不是一个user-level方法,不能直接调用。这也印证了以上bus2reg()不需要用户显式调用的描述(甚至predictor中连调用bus2reg()的函数都不需要用户直接调用。。。再往前会追溯到哪儿去呢?这里就暂时放一下,有空时再回头来彻底刨根究底地追追看)
4.2 reg2bus()在哪里调用?
查阅UVM库的源代码可以找到reg2bus在以下地方被调用(不知道还有没有其它地方?待进一步确认):
reg2bus() called in uvm_reg_map:
task uvm_reg_map::do_bus_write (uvm_reg_item rw,
uvm_sequencer_base sequencer,
uvm_reg_adapter adapter);
...
// perform accesses
foreach(accesses[i]) begin
uvm_reg_bus_op rw_access=accesses[i];
uvm_sequence_item bus_req;
adapter.m_set_item(rw);
bus_req = adapter.reg2bus(rw_access);
adapter.m_set_item(null);
...
...
task uvm_reg_map::do_bus_read (uvm_reg_item rw,
uvm_sequencer_base sequencer,
uvm_reg_adapter adapter);
...
// perform accesses
foreach(accesses[i]) begin
uvm_reg_bus_op rw_access=accesses[i];
uvm_sequence_item bus_req;
uvm_reg_data_logic_t data;
int unsigned curr_byte_;
curr_byte_=rw_access.data;
rw_access.data='0;
adapter.m_set_item(rw);
bus_req = adapter.reg2bus(rw_access);
...
...
4.3 小结
总之,如上所述,UVM在后台偷偷地把很多的事情干了。
一方面,这也正是UVM的便捷和强大之处,所有这些UVM在后台偷偷地干了的事情其实都是标准化的操作处理,不需要开发者再一一显式地去处理,避免了无谓的重新发明轮子,使得验证开发者可以更加聚焦于高级事务。
另一方面,这会使得初学者很不习惯,毕竟习惯了“所见即所得”,没有看到的东西心里没底啊,谁知道到底有没有被执行啊。从心里没底到慢慢成竹在胸泰然自若,这是成长的必经之路。
主要参考文献:
【1】路科验证2022春季V2X课件
【2】UVM Register Environment (chipverify.com)
相关文章
- cdh集成Spark2.2后spark-shell启动报错解决
- springboot集成jsp
- 银行核心系统之应用集成
- “全”事件触发:阿里云函数计算与事件总线产品完成全面深度集成
- OpenYurt 与 FabEdge 集成验证——云边数据面通信初试
- Cilium 首次集成国内云服务,阿里云 ENI 被纳入新版本特性
- jenkins 持续集成和交付 —— git hook(七)
- jenkins 持续集成和交付——pipeline(五)
- Python视觉深度学习系列教程 第二卷 第5章 使用神经网络集成提高准确性
- SAP成都研究院大卫哥:SAP C4C中国本地化之微信小程序集成
- 〖Python WEB 自动化测试实战篇⑮〗 实战 - 自动化测试的持续集成
- 华为云新一代iPaaS全域融合集成平台全新升级