这一部分主要介绍一些UVM的基础知识,其实《UVM实战》中已经有了足够的涉猎。所以这一章着重加注一些UVM的使用哲学探讨。
Testbench基础
UVM采用分层的、面向对象的方法进行testbench开发,允许在不同的团队成员之间进行“关注点分离”。UVM testbench中的每个组件都有特定的用途,以及和testbench其余部分的接口,以提高生产力并促进重用。把这些组件加入到testbench中,就得到一个模块化的可重用验证环境,它使编写测试用例的人在transaction级思考,专注于必须验证的功能,而testbench架构师则专注于test如何与DUT交互。
DUT被连接到一些事务器(driver、monitor、responder)上。这些事务器通过驱动和采样DUT信号在pin级与DUT通信,并通过传递transaction对象与UVM testbench的其余部分通信。它们在接口信号和transaction之间转换数据。在这些事务处理器之上的testbench的部分由专门在事务级进行交互的组件组成,如scoreboard, coverage collector, sequencer等。UVM testbench中的所有组件都是从uvm_component基类扩展而来。
UVM testbench的最低级别是特定于接口的。对每个接口而言,UVM提供一个uvm_agent,其中包括 driver, monitor, sequencer和coverage collector。因此,agent体现了与DUT的所有特定于协议的通信。agent和其他特定于设计的组件封装在uvm_env 组件中,该组件由顶层的uvm_test组件例化并配置。uvm_sequence_item是一个uvm_object,它包含实现协议和与DUT通信所需的数据字段。uvm_driver负责将sequence_item转换为信号级接口上的“pin wiggle”,以向DUT发送或从DUT接收数据。sequence_item由一个或多个uvm_sequence对象提供,这些对象在transaction级定义激励,并在agent的uvm_sequencer组件上执行。sequencer负责执行、仲裁sequences,并在sequences和driver之间传递sequence item。
uvm_sequence_item习惯上被称为transaction,实际上是由从uvm_object派生出的uvm_transaction类派生而来。
uvm_monitor负责观察DUT接口上的信号级行为,并将其转换为sequence items,并将这些sequence items提供给agent或testbench上其他地方(如coverage collectors或scoreboard)中的analysis组件。UVM agent还有一个config object,该对象允许test编写人员在组装和执行testbench时配置agent。
通过为testbench提供统一的接口,UVM agent将testbench和UVM sequence与接口实现的细节隔离开来。例如,提供数据包的sequence可以在不同的UVM agent中重用,这些agent可能实现AHB、PCI或其他协议。UVM testbench通常为DUT的每个接口编写一个对应的agent。
对于给定的设计,UVM Agents和其他组件一起被封装在uvm_env组件中,该组件通常是特定于具体设计的。与agent一样,env通常也有一个与之相关联的config object,该对象允许test对env进行配置,包括在env中例化的agent。由于env本身就是UVM组件,所以也可以将其组装到更高级别的env中。模块级设计被组装成子系统和系统时,模块相关的模块级env可以作为子系统级env中的组件重用,而子系统级env本身也可以在系统级testbench上重用。
一旦定义了env,uvm_test将例化、配置并构建env,包括自定义整个testbench的关键方面,包括选择在env中使用的组件的变化。选择UVM sequences在后台运行或是作为test的主要部分。定义testbench上的env、sub_env(如果有)和agent的config object。
sequence包括验证环境执行的主体sequence以及一些用于响应DUT的挂在后台待调用的sequence。
UVM test通过在顶层initial块中调用run_test()启动。
UVM 组件
UVM testbench由扩展自uvm_component基类的组件对象构成。当创建uvm_component派生类的对象时,其成为testbench层次结构的一部分,会在整个仿真期间持续存在。这就与UVM类层次结构的sequence分支形成了对比,而后者涉及到瞬态对象(动态的)
,一旦解除引用,就会创建、使用和销毁的对象。
动态的: 笔者认为原文含义为,创建、使用的瞬态对象会在解除使用后(没有句柄指向该对象时,即原对象的句柄指向另一个新对象时)自动销毁。 例如,不同的sequencer上可能会挂载过多个sequence,而同一时间,只有一个sequence对象,执行过的sequence就会被自动销毁。同样地,sequence产生过的transaction(seq item),传递出去之后,就自动销毁。
UVM消息机制使用(准)静态uvm_component层次结构来打印发出报告消息的组件的作用域,配置过程使用它来确定哪些组件可以访问配置对象,UVM工厂使用它来应用工厂覆盖。这个组件层次结构由一个在创建每个组件时递增构建的链表表示。每个组件的层次位置由在构造时传递给其create方法的name和parent参数决定。
例如,在下面的代码片段中,在spi_env中创建了一个apb_agent组件。假设spi_env在顶层test组件中以名称“m_env”实例化,则agent的分层路径名是与spi_env组件名称“uvm_test_top.m_env”,(".")操作符,以及作为第一个参数传递给create()
方法的name的拼接,最终agent的层次名称为"uvm_test_top.m_env.m_apb_agent"。对agent的任何引用都需要使用这个字符串名称。
即这个分层路径名由顶层test实例化名至当前组件的实例化名用(".")操作符逐级拼接。
// Hierarchical name example
class spi_env extends uvm_env;
apb_agent m_apb_agent;
// Declaration of the apb agent handle
// ...
function void build_phase(uvm_phase phase);
// Create the apb_agent:
//
// Name string argument is the same as the handle name
// The parent argument is 'this' - i.e. the spi_env
//
// The spi_env has a hierarchical path string "uvm_test_top.m_env"
// is concatenated with the name string to arrive at
// "uvm_test_top.m_env.m_apb_agent" as the
// hierarchical reference string for the apb_agent
m_apb_agent = apb_agent::type_id::create("m_apb_agent", this);
// ...
endfunction: build_phase
// ...
endclass: spi_env
uvm_component类继承了uvm_report_object类,而该类位于UVM消息传递基础结构的核心。消息机制使用组件静态层次结构将组件的分层路径名添加到报告消息字符串中(即这条消息是由哪个组件(对象)报告的)。
uvm_component基类模板的每个UVM phase都有一个虚方法,用户可以根据需要实现这些方法。未实现的phase级虚方法将导致组件无法有效地参与该phase。
uvm_component基类中还嵌入了对配置表的支持,以存储与testbench层次结构中组件的子节点相关的配置对象。当使用uvm_config_db API时,这个静态层次结构(分层路径名)被用作路径机制的一部分,以控制哪些组件可以访问给定的配置对象。
为了在配置中提供灵活性并允许以智能的方式构建UVM testbench的层次结构,uvm_components需要被注册到UVM工厂。当在构建阶段创建UVM组件时,将会使用工厂来构造组件对象。UVM工厂允许使用工厂覆盖来把一个组件替换为它的一个兼容的派生类型。这种方式可以在不直接更改testbench源代码的情况下更改testbench的功能(直接更改testbench源代码需要重新编译并会阻碍重用)。工厂工作需要许多编码约定,在关于UVM工厂的内容中对这些约定做了概述。
UVM package包含许多uvm_component基类的扩展(即派生类),用于公共testbench组件。这些扩展中的大多数类都非常“瘦”,也就是说,它们实际上只是uvm_component类的一个小扩展,主要是用来添加一个新的名称空间。虽然这些不是很关键,原则上我们也仍然可以使用 uvm_component 类,但重要的是这些扩展类有助于实现组件的“自我分类”,因为其清楚地表明了实际表示的组件类型,例如driver或monitor。
通俗说就是是UVM强制为环境组件分类做引导,笔者认为UVM倡导的验证环境构建方式趋向于让单个组件处理单一类型的事务,这样环境结构清晰且易于分类维护
此外,还有一些可用的分析工具,它们也使用这些无关的基类来扩展,以帮助建立testbench层次结构的图像。另一方面,一些预构建的uvm_component扩展实际上是结构块,其通过实例化具体的子组件,来实现一组复杂的功能。下表总结了直接从uvm_component基类派生的可用的UVM组件类:
UVM 工厂
UVM工厂的目的是允许用派生类型的对象替换其基类的对象,且不需要更改testbench结构甚至testbench代码。这种机制称为覆盖(override)(按type或实例)。此功能对于更改sequence行为或用组件的一个版本替换另一个版本来说十分方便。但是,要交换的任何两个组件必须是多态兼容的。这就要求所有相同的TLM接口句柄和TLM对象必须由用来替换的派生组件声明及例化。此外,为了利用工厂的优势,我们必须遵循某些编码规范。
override的两种类型:替换这种class的实例,还是只替换该class的某个实例
工厂编码规范1:注册
组件或对象必须包含由以下元素组成的工厂注册代码:
- 一个uvm_component_registry wrapper,typedef为type_id
- 获取type_id的静态函数
- 获取类名的函数
例如:
class my_component extends uvm_component;
// Wrapper class around the component class that is used within the factory
typedef uvm_component_registry #(my_component, "my_component") type_id;
// Used to get the type_id wrapper
static function type_id get_type();
return type_id::get();
endfunction
// Used to get the type_name as a string
function string get_type_name();
return "my_component";
endfunction
...
endclass: my_component
注册代码有一个规则的模式,可以安全地用四个工厂注册宏中的一个生成:
// For a component
class my_component extends uvm_component;
// Component factory registration macro
`uvm_component_utils(my_component)
// For a parameterized component
class my_param_component #(int ADD_WIDTH=20, int DATA_WIDTH=23) extends uvm_component;
typedef my_param_component #(ADD_WIDTH, DATA_WIDTH) this_t;
// Parameterized component factory registration macro
`uvm_component_param_utils(this_t)
// For a class derived from an object (i.e. uvm_object, uvm_transaction,
// uvm_sequence_item, uvm_sequence etc.)
class my_item extends uvm_sequence_item;
`uvm_object_utils(my_item)
// For a parameterized object class
class my_item #(int ADD_WIDTH=20, int DATA_WIDHT=20) extends uvm_sequence_item;
typedef my_item #(ADD_WIDTH, DATA_WIDTH) this_t
`uvm_object_param_utils(this_t)
工厂编码规范2:构造函数默认值
uvm_component和uvm_object构造函数是带有原型模板的虚方法,用户必须遵守。为了在构建阶段支持延迟构造,工厂构造函数应该包含构造函数参数的默认值。这允许使用默认值在工厂内构建一个工厂注册类,然后可以将类属性重新赋值给通过uvm_component_registry wrapper类的create方法传递的参数。组件和对象的默认值是不同的:
// For a component:
class my_component extends uvm_component;
function new(string name = "my_component", uvm_component parent = null);
super.new(name, parent);
endfunction
// For an object:
class my_item extends uvm_sequence_item;
function new(string name = "my_item");
super.new(name);
endfunction