UVM寄存器模型与通道机制
接续UVM基础入门文章。
前言
重点讲述UVM常用的接口连接方式。
寄存器模型:
UVM寄存器模型(Register Model)是一组高级抽象的类,用于对DUT(Design Under Test)中具有地址映射的寄存器和存储器进行建模,通过提供寄存器操作的抽象接口进行读写操作,这样简化了对寄存器的验证。
寄存器模型模拟了DUT内部的存储器件,一个简单的寄存器模型如图:
上述的图中可以看出寄存器模型的大致框架,内部含有众多寄存器和存储器,这些元件通过地址图与DUT内部的寄存器形成映射关系。
不过寄存器的保留域可以不用管,无法写入,读出的数据是复位值,在提取寄存器功能点时也常常忽略保留域。
寄存器读写:
寄存器模型与DUT的数据交互复杂多样,整体方式其实就是通过前门访问和后门访问。
前门访问过程:
1.测试用例或者参考模型会调用寄存器模型的read() 或者 write() 方法,指定为前门访问方式。一旦寄存器模型被上层调用后,这个寄存器模型就会产生一个有关读写操作事务。
2.寄存器模型通过调用adapter的reg2bus函数将寄存器读写事务转换成总线事务。
3.该总线事务会交给关联的sequencer发送给driver,然后再驱动给DUT。
4.monitor监测响应,然后给到端口传出去,这个端口可以是analysis_port端口。
5.寄存器模型通过analysis_port端口获取响应后调用adapter的bus2reg函数将事务转换成寄存器操作
6.寄存器模型自动更新镜像值
后门访问过程:
1. 在寄存器模型中,通过 add_hdl_path()
或 add_hdl_path_slice()
为内部的每个寄存器或字段域指定对应的RTL信号路径,这个过程理解为映射。
2. 调用寄存器模型的read() 或者 write() 函数,方式指定为后门路径。
3.通过DPI-C接口或者force/release直接修改或者读取对应路径的寄存器值.
4.写入的数值或者读出的数值都会自动更新到寄存器镜像里。
如图所示:
红色线条表示的是参考模型写数据到DUT内部的路径,绿色线条表示参考模型从DUT寄存器里读出数据,黑色线条表示后门访问。
参考模型一般都不会写数据,想要读寄存器数据可以通过前门后门或者读取镜像值来获取数据。
通道机制:
在UVM的端口连接机制中,最为常用的有3种端口连接机制。
一对一接口:
1.put_port:在完成transaction的打包后,通过port.put(trans)将事务发送出去。(blocking_put_port接口也属于put_port的一种,属于阻塞接口,不支持反向)
2.get_port:在需要transaction的时候,通过port.get(trans)获取事务。(get_blocking_prot就是get_port的一种,它属于阻塞端口)
一对多接口:
analysis_port接口为广播接口,可以向所有组件发送事务信息。
接口转接:
tlm_fifo作为中间传输的缓存器,可以用于不同接口之间的连接。当存在两个接口的定义不相同时,通过将接口连接到fifo对应的接口上实现转接。
tlm_fifo分为有ap端口的fifo 和没有ap端口的fifo。
通常agent发送的transaction不止一个组件使用,因此在agent中通常使用的analysis_port实现广播,而在参考模型或者记分板中并不需要广播功能。
因此在不需要广播功能的组件中,通常可以不用analysis_port端口。
以agent到scoreboard的端口连接为例简单的讲述一下常用的接口连接机制:
1.多对一连接:
analysis_port------env.tlm_fifo------scoreboard.get_blocking_port。
这属于不同类型的接口连接,由于scoreboard不是时刻都需要事务的,因此需要一个缓冲功能的接口。
analysis_port--------scoreboard.tlm_fifo
由于fifo中自带了get()函数,通常也可以省略get_blocking_port接口,直接在scoreboard里面实例化两个fifo存储数据用于对比。
虽然使用fifo简单易懂,但是很多时候一个组件需要获取多组数据的情况,这种情况下例化很多fifo是不现实的。
原因在于如果不同agent之间发送事务的phase不同,而scoreboard里面没有选择合适的phase,那么环境的调试将会变得十分困难。
而且想要从fifo里面获取数据需要在特定的phase里面执行。
analysis_port ------------- scoreboard.imp
imp,我在B站上看到的视频解释为通道的意思,如果组件中有多个imp接口的话,需要使用·uvm_analysis_imp_decl宏定义创建多个imp类去分别对应不同的通道,还要再组件中定义具有相同后缀名的函数。
2.一对一连接:
常见一对一的端口连接方式有:
阻塞式单向传输:uvm_blocking_put_port(发送)
+ uvm_blocking_put_imp(接收)
阻塞式双向传输:uvm_blocking_transport_port(发送)
+ uvm_blocking_transport_imp(接收)
非阻塞式单向传输:uvm_nonblocking_put_port(发送)
+ uvm_nonblocking_put_imp(接收)
例如:
class scoreboard extends uvm_component;`uvm_component_param_utils(scoreboard #(transaction))// 定义两个IMP端口:一个接收预期数据,一个接收实际数据uvm_blocking_put_imp #(transaction, scoreboard #(transaction)) expected_imp;uvm_blocking_put_imp #(transaction, scoreboard #(transaction)) actual_imp;// 存储预期和实际数据的队列transaction expected_queue[$];transaction actual_queue[$];function new(string name = "scoreboard", uvm_component parent = null);super.new(name, parent);expected_imp = new("expected_imp", this);actual_imp = new("actual_imp", this);endfunction// 实现expected_imp的put方法virtual task put(transaction tr);`uvm_info("SCOREBOARD", $sformatf("Received Expected Data: %0h", tr.data), UVM_MEDIUM);expected_queue.push_back(tr);endtask// 实现actual_imp的put方法virtual task put(transaction tr);`uvm_info("SCOREBOARD", $sformatf("Received Actual Data: %0h", tr.data), UVM_MEDIUM);actual_queue.push_back(tr);endtask// 比对逻辑(可在run_phase或单独的任务中调用)task compare();if (expected_queue.size() != actual_queue.size()) begin`uvm_error("SCOREBOARD", "Mismatch in number of transactions!");end else beginforeach (expected_queue[i]) beginif (expected_queue[i].data !== actual_queue[i].data) begin`uvm_error("SCOREBOARD", $sformatf("Data mismatch at index %0d!", i));endendendexpected_queue.delete();actual_queue.delete();endtask
endclass
总结
不同种类的端口一般不能直接相连,需要FIFO转接,或者将两个要相连的端口类型声明为一致的。