走进UVM:通用验证方法学一文通介绍&快速入门指南

        源自 Easier UVM for Functional Verification by Mainstream Users Updated and Extended for UVM 1.0,有错在所难免,恳求大家指正。

        本文描述了一种使用UVM,即通用验证方法,来由主流用户进行功能验证的方法。其目标是确定一个足够的最小概念集,用于约束随机覆盖驱动的验证,以便减轻来自硬件设计背景、没有广泛的面向对象编程技能的工程师的学习经验。我们描述了解决UVM组件和UVM事务的规范结构、UVM组件层次结构的构建、与待测试设计的接口、UVM序列的使用,以及工厂和配置机制的使用。(注:版本为uvm1.0)

1.介绍

        本文描述了一种使用UVM,由主流用户进行功能验证而不是高技能的验证专家的验证方法。它源于Doulos在向硬件设计和验证社区的工程师教授系统验证和功能验证方法方面的经验。虽然大量的研究和开发功能验证方法是正确地关注超级用户的需求,因为他们解决最困难的验证问题,我们发现大多数主流用户有不同的焦点,即,如何使高效与最小的延迟和专业编程专业知识。sv和UVM提供了机制来创建用于检查、覆盖收集和刺激生成的验证组件,并为特定的测试修改这些组件的行为。事实上,学习曲线对非专家来说可能是令人生畏的。

        本文的目标是通过让具有Verilog或VHDL经验的工程师学习少量新的编码习语,在UVM中发挥生产力,以最小化他们必须处理的概念混乱。当用户能够熟练地使用这组基本习惯语时,他们就可以在需要时扩展到接受UVM的完整特性集。

        我们描述了解决UVM组件和UVM事务的规范结构的编码指南、UVM组件层次结构的构建、正在测试设计的接口、UVM序列的使用以及工厂和工厂的使用、配置机制。从这些简单的指导方针开始,工程师可以以面向对象的编码风格创建受约束的随机验证环境,这完全符合UVM标准,因此可以与来自其他来源的UVM验证IP进行互操作。

2.更容易实现的UVM?

        更容易实现的UVM并不是另一种验证方法。它仍然是UVM。关键是在某种程度上限制正在使用的特性的范围,以便使新手的生活更容易。这一练习主要是一个教学练习。其目的是简化学习UVM的任务,而不是拒绝用户充分利用UVM类库的能力。我们在这里给出的编码指南并不是使用UVM的唯一方法,也不一定是使用经验丰富的验证工程师的最佳方法。

        更容易实现的UVM并不意味着这是个容易的的UVM。下面介绍的概念集仍然相当广泛和非常丰富。人们越来越认识到,系统建模和功能验证都需要高水平的软件编程技能,除此之外,UVM还需要对覆盖化验证和事务级建模的深入理解。

        本文并没有明确地列出UVM的每个特性。许多有用的特性没有被提到,其中一些对于高级UVM的使用是必不可少的,重要的例子是报告处理和测试结束机制。更容易的UVM仅仅提供了一个建立更深入的UVM知识的概念基础。

3.SV和UVM

        在过去的几年中,约束随机覆盖驱动的验证被越来越多地作为基于模拟的功能验证的选择方法,并被广泛应用于最大的ASIC项目。SystemVerilog作为唯一的行业标准硬件验证语言,支持三大EDA供应商中的每一个,它已经在许多公司中取代了其竞争对手的单供应商解决方案。但sv并非没有问题。尽管当前的sv实现在许多方面既成熟又健壮,但sv作为一种语言仍然没有被充分指定。sv项目是一个极其雄心勃勃的标准化项目,是在开发任何完整的概念验证实施之前进行的。sv语言的某些领域确实很突出,因为在所有主要的模拟器中都有明确的定义和一致的实现。这些包括:

        sv和UVM现在形成了一个良性循环。支持约束随机验证的基于类的sv特性定义良好,实现良好,允许开发健壮和可移植的验证类库,并且这些库的广泛使用确保了工具供应商对必要的语言特性的持续支持。除了SystemVerilog的verilog特性和c语言特性外,我们还使用了类、约束、覆盖组、包、接口和虚拟接口。UVM对类大量使用了类型参数化,幸运的是,现在所有主要的模拟器实现都接受了这一领域的语义。

4.面向对象的概念

        面向对象(OO)的编程概念是当代约束随机验证方法的关键,因为它们支持重用,但这些技术是最难学习的技术之一。OO技术允许验证组件专门针对特定的测试台或测试的需要,而不修改它们的源代码,并允许使用函数调用在这些组件之间实现结构良好的通信。

        在UVM中,类被用作表示组件、事务、序列、测试和配置的容器。让我们来看看这个组件。与VHDL设计实体或系统验证器模块不同,组件代表了跨整个可能的结构构建块家族的抽象。一个组件挑选出了几个这样的构建块中常见的东西,但一个组件不是具体的,这意味着它不是这个东西的一劳永逸的最终定义。VHDL设计实体或系统版本模块可以描述一系列相关组件,但只有在预先预期变量,并通过通用参数和生成语句等语言特性显式地捕获在源代码中时。因为它是一个类,所以UVM组件可以以任意的方式进行事后扩展。扩展可以添加新特性或修改现有特性。特别是,我们需要这种扩展功能,以便测试可以扩展事务或序列以添加约束,然后使用工厂机制来覆盖这些事务或序列的生成。

5.UVM概念

        本文的目标是确定一个足够进行约束随机覆盖驱动验证的最小概念集,以减轻来自硬件设计背景、没有广泛的面向对象编程技能的工程师的学习经验。与此同时,我们也不想将概念框架剥离到它失去了面向对象范式的所有表达能力的地步。其他一些向硬件设计者展示验证类库的尝试,最终只是使用类重新展示VHDL或Verilog的语义,这是我们希望避免的一个陷阱。根据我们的经验,硬件设计人员转向一种新的验证语言,即sv,确实希望从新范式提供的增强的表达能力和灵活性中获益。下面列出了我们的概念性词汇表。这些术语将在本文后面详细阐述,扩展的定义可以在UVM发行版附带的文档中找到。

5.1 components

        component(组件)用于构建组件层次结构,它在概念上非常类似于VHDL中的设计层次结构或Verilog中的模块层次结构。在这种情况下,component层次结构是验证环境的一部分,而不是设计,并且component表示刺激生成器、驱动程序、监视器、覆盖收集器和检查器。component表示验证环境的可重用单元,因此具有如何定制它的标准结构和约定。component在模拟开始时以准静态方式创建。

        这是一个骨架component。uvm_component_utils宏和new函数应该被视为样板代码,并完全按照如图所示编写。

class my_comp extends uvm_component;
 `uvm_component_utils(my_comp)
 function new(string name, uvm_component parent);
 super.new(name, parent);
 endfunction
 ...
endclass

5.2 transactions

        transaction(事务)是在component之间传递的基本数据对象。与VHDL信号和Verilog的wire相比比,transaction在抽象层次上表示通信。为了将刺激驱动到DUT中,所谓的driver component将transaction转换为pin wiggles,而所谓的monitor component执行反向操作,将pin wiggles转换为transaction。

        这是一个基本transaction。同样,uvm_object_utils宏和new函数应该被视为样板代码。

class my_tx extends uvm_sequence_item;
 `uvm_object_utils(my_tx)
 
 function new (string name = "");
 super.new(name);
 endfunction
 ...
endclass

        对于transcation和sequence,唯一的构造函数参数需要一个默认值。

5.3 sequences

        sequence(序列)是由transaction组装而成的,并被用来构建真实的刺激集。sequence可以生成一组特定的预定transaction、一组随机事务或介于两者之间的任何内容。sequence可以运行其他sequence,可能会选择随机运行哪个sequence。sequence可以分层,以便高级sequence将transaction发送到协议栈中的低级序列。

        这是一个骨架sequence。它类似于大纲中的transactions,但是基类uvm_sequence是由组成sequence的transaction的类型参数化的。此外,每个sequence都包含一个主体task,当它执行时,将生成这些transaction或运行其他sequence。

class my_seq extends uvm_sequence #(my_tx);
 `uvm_object_utils(my_seq)
 function new (string name = "");
 super.new(name);
 endfunction
 task body;
 ...
 endtask
 ...
endclass

        transaction和sequence一起表示验证环境中的动态数据域

5.4 phase

        每个component都实现了相同的phase,这些phase在模拟期间以预定义的顺序运行,以便同步component的行为。与VHDL或Verilog相比,UVM在模拟运行中提供了更多的时间结构。标准phase过程如下:

1. build

create child component instances

(创建子类component实例)

2. connect

connect ports to exports on the child components

(将端口连接到子类component上的导出端口)

3. end of elabrationhousekeeping
4. start of simulationhousekeeping
5. runrun simulation -decomposed into several run phases
(运行仿真-又分为几个run phase)
6. extractpost processing
7. checkpost processing
8. reportpost processing
9. finalback stop

        除run phase外,每个phase都由component内的一个函数表示。(run phase使用的是任务因为需要耗时)。如果缺少函数,则该component将在给定的phase中处于不活动状态。

        从上面的列表可以推断,phase之间的主要区别是构建component层次结构、连接端口和运行模拟的阶段之间,额外的管理阶段预处理并附加到simulation phase。

5.5 factory

        UVM工厂机制是在OO文献中描述的所谓工厂模式的一种实现。UVM工厂可以制作component、transaction和sequence。使用工厂可以从测试中覆盖object类型的选择,尽管给定的component、transaction或sequence只能用扩展原始类的component来覆盖。这是可根据当前环境定制可重用验证component的主要机制之一。

        下面是一个在构建阶段中创建子类component的组件示例:

class A extends uvm_component;
 `uvm_component_utils(A)
 
 B b; // Child component
 C c; // Child component
 
 function new(string name, uvm_component parent);
 super.new(name, parent);
 endfunction
 
 function void build_phase(uvm_phase phase);
 super.build_phase(phase);
 // Factory calls to create child components
 b = B::type_id::create("b", this);
 c = C::type_id::create("c", this);
 endfunction
 ... 
endclass

        为了实例化完整的component层次结构,构建函数本身被称为自上而下的。您可以在每个单独component的构建函数中调用创建来实例化其子类component。

        同样的工厂机制也在一个sequence中用于创建transaction:

my_tx tx;
tx = my_tx::type_id::create("tx");

        工厂的另一个方面是从一个特定的测试中覆盖工厂的行为。例如:

my_tx::type_id::set_type_override(alt::get_type());

        上面的语句将导致工厂创建的transaction类型my_tx的所有实例被替换为transaction alt的实例。每个实例也可以进行覆盖:

my_tx::type_id::set_inst_override(alt::get_type(),
 "inst", this);

5.6 port and export

        port和export类似于VHDL或Verilog中的端口,但它们被用于事务级通信,而不是信号级通信。component可以通过port发送事务,或者通过export接收传入的事务。事务作为参数传递给函数调用,这些调用可能是非阻塞(立即返回)或阻塞(暂停并等待某些事件后再返回),这足以实现验证环境中的基本同步。所有详细的定时信息都应该被下推到连接到DUT的driver和monitor component中,以便DUT接口可以确定定时,DUT接口通常被锁定在低电平时钟和其他同步信号上。        

        在验证环境中,控制流从DUT向外辐射,当driver准备好迎接进一步刺激时,sequence调用可以请求transaction,并且monitor调用write以在验证环境中分发transaction以进行分析。如果刺激生成器将其活动与验证环境的其他部分进行协调,那么来自driver的呼叫可能会阻塞。不允许阻止monitor进行写入的调用,因为DUT不能停止,等待分析活动。

        下面的示例显示了一个包含两个子类components B和 C. connect function 连接B的p_port与C的q_export。

class A extends uvm_component;
 `uvm_component_utils(A)
 
 B b; // Child component having p_port
 C c; // Child component having q_export
 
 function new(string name, uvm_component parent);
 super.new(name, parent);
 endfunction
 
 function void build_phase(uvm_phase phase);
 super.build_phase(phase);
 b = B::type_id::create("b", this);
 c = C::type_id::create("c", this);
 endfunction
 
 function void connect_phase(uvm_phase phase);
 b.p_port.connect( c.q_export );
 endfunction
 
endclass

        最常见的port之一是所谓的analysis port,它作为一种广播机制,用于发送事务到多个被动验证components。analysis port可以连接到任意数量的analysis export。transaction实际上是通过使用OO函数调用的port发送的,例如:

class my_comp extends uvm_component;
 `uvm_component_utils(my_comp)
 uvm_analysis_port #(my_tx) aport;
...
 function void build_phase(uvm_phase phase);
 super.build_phase(phase);
 aport = new("aport", this);
endfunction
 task run_phase(uvm_phase phase);
 my_tx tx;
 tx = my_tx::type_id::create("tx");
 ...
 aport.write(tx);
 endtask
endclass

        你可能会从上面注意到,analysis port的类型是使用transaction #(my_tx)的类型参数化的,analysis port必须在build phase显式构建,并且调用写入的通过分析端口发送的事务本身就是使用工厂构建的。

5.7 generation

        generation利用了sv的随机化和约束。component生成发生在所谓的UVM构建阶段,此时component能够访问其配置对象(如果有的话),以控制低级component的生成。这类似于VHDL或Verilog中的构建阶段。sequence generation是动态发生的。对最终到达DUT的精确事务序列的控制可以分布在预定义的序列生成组件和测试中,从而能够扩展和约束现有的序列。

        下面是一个显示在一个sequence的主体任务中的transaction generation生成的例子:

class my_seq extends uvm_sequence #(my_tx);
 ... 
 task body;
 my_tx tx;
 tx = my_tx::type_id::create("tx");
 start_item(tx);
 assert( tx.randomize() with { cmd == 0; } );
 finish_item(tx);
 ...
 endtask
endclass

        在上面的示例中,transaction受到约束,因为它是随机的,以便在发送下游transaction之前将命令字段的值设置为特定的值。start_item和fint_item功能与从sequencer中提取事务的组件同步,后者可以是driver或其他sequencer。start_item等待下游组件请求事务,finish_item等待下游组件表明它已经完成了事务。对于它来说,下游组件调用可以获取事务,如果需要发送响应,可以调用put。

        同样的机制可以用于生成sequence本身,从而在序列中嵌套序列:

task body;
 repeat(n)
 begin
 my_seq seq;
 seq = my_seq::type_id::create("seq");
 start_item(seq);
 assert( seq.randomize() );
 finish_item(seq);
 end
endtask

        sequence的体函数只是一个执行过程代码的常规函数。

5.8 tests

        test是用于控制生成的顶层component,即自定义包含验证环境的compoennt的准静态行为,以及在这些组件之间传递的事务和序列的动态行为。test可以:

        一个事务或序列可以通过两种方式进行定制:通过在调用随机函数时使用inline约束(如上所示),或者通过声明一个扩展类来添加或覆盖原始类中的约束或函数。例如,一个扩展的事务处理:

class alt_tx extends my_tx;
 `uvm_object_utils(alt_tx)
 function new(string name = "");
 super.new(name);
 endfunction
 constraint my_constraint { data < 128; }
endclass

        通过扩展一个特定的类uvm_test来创建一个用户定义的测试,如下图所示:

 `uvm_component_utils(my_test)
 ... 
 my_env env; 
 ...
 function void build_phase(uvm_phase phase);
 super.build_phase(phase);
 // Factory override replaces my_tx with alt_tx
 my_tx::type_id::set_type_override(
 alt_tx::get_type() );
 env = my_env::type_id::create("env", this);
 endfunction
 
 task run_phase(uvm_phase phase);
 my_seq seq;
 seq = my_seq::type_id::create("seq");
 assert( seq.randomize() with { n = 22; } );
 seq.start( env.sequencer );
 endtask
endclass

        类uvm_test本身扩展了类uvm_component。这是本身作为component的内置类示例之一,包括uvm_sequencer、uvm_driver、uvm_monitor、uvm_subscriber和uvm_env。使用这些所谓的方法基类而不是使用原始的uvm_components是一个好主意,因为这样做可以使用户的意图更清晰。

        请注意测试如何使用工厂创建包含验证环境env的固定部分的component实例,该部分本身不是特定于测试的,但将从my_test类自定义。每个测试只需要包含对验证环境的少数特定修改,以便将此测试与默认情况区分开来。这个特定的测试使用工厂来创建序列my_seq的一个实例,并在名为env.sequencer的验证环境中的一个特定component上启动该sequence。

        从顶层模块中的进程中运行特定的测试:

initial
 run_test("my_test");

        测试名称可以设置为如图所示,也可以作为命令行参数传递,以便选择一个测试,而不需要重新编译。

5.9 configuration

        configuration是与特定component实例关联的object或描述符。configuration在生成期间由test填充,然后在构建阶段由组件层次结构中的组件进行检查。层次结构中的组件还可以创建供其子节点单独使用的local configuration。单个configuration类类型对许多组件实例可能是通用的,或者如果需要,每个组件实例都可以有自己的唯一configuration实例。每个组件实例不需要有自己唯一的configuration object;组件可以检查其父辈之一的configuration object。

         configuration的定义与transaction有一些通用性:

class my_config extends uvm_object;
 `uvm_object_utils(my_config)
 
 rand bit param1;
 rand int param2;
 string param3;
 // Other configuration parameters
 
 function new (string name = "");
 super.new(name);
 endfunction
endclass

        configuration object通常从test填充:

class my_test extends uvm_test;
 `uvm_component_utils(my_test)
 ...
 my_env env;
 ...
 function void build_phase(uvm_phase phase);
 super.build_phase(phase);
 begin
 my_config config = new;
 // Can randomize the configuration
 assert( config.randomize() );
 // Can set individual members
 config.param2 = 3;
 config.param3 = "filename";
 uvm_config_db #(my_config)::set(
 this, "*.*producer*", "config", config);
 end
 env = top::type_id::create("env", this);
 endfunction
endclass

        在上面的示例中,在使用工厂创建测试的构建阶段之前,实例化配置对象并填充验证环境的其余部分,因此能够根据配置对象中设置的值生成。然后,使用要设置的调用将该配置存储在UVM配置数据库中。

        已配置的component应在构建阶段检查配置对象:

class producer extends uvm_component;
 `uvm_component_utils(producer)
 ...
 my_config config;
 // Configuration parameters
 bit param1 = 0;
 int param2 = 0;
 string param3;
 ...
 function void build_phase(uvm_phase phase);
 super.build_phase(phase);
 begin
 if ( uvm_config_db #(my_config)::get(
 this, "", "config", config) )
 begin
 param1 = config.param1;
 param2 = config.param2;
 param3 = config.param3;
 end
 ...
 end
 endfunction
 ...
endclass

        在上面的示例中,component调用将从UVM配置数据库中检索由字符串“config”(由调用设置设置)标识的配置对象,并从配置中提取参数值。这些参数可用于控制该component内的本地生成。

5.10 sequencer

        sequencer是运行sequence并将其发送到下游的driver或其他sequencer的各种组件。最简单地说,sequencer看起来与任何其他component一样,除了它有一个用来连接到driver的隐式事务级export。我们现在可以得到更多的信心,展示一个例子,包括sequence,sequencer,和从sequence中启动test:

// A SEQUENCER is a component
class my_sqr extends uvm_sequencer #(my_tx);
 `uvm_component_utils(my_sqr)
 
 function new(string name, uvm_component parent);
 super.new(name, parent);
 endfunction
endclass
// A SEQUENCE is generated dynamically
class my_seq extends uvm_sequence #(my_tx);
 `uvm_object_utils(my_seq)
 function new (string name = "");
 super.new(name);
 endfunction
 task body;
 my_tx tx;
 tx = my_tx::type_id::create("tx");
 start_item(tx);
 assert( tx.randomize() );
 finish_item(tx); 
 endtask
endclass
class my_test extends uvm_test;
 `uvm_component_utils(my_test)
 ...
 my_env env;
 ... 
 task run_phase(uvm_phase phase);
 my_seq seq;
 // Create the sequence
 seq = my_seq::type_id::create("seq");
 // randomize it
 assert( seq.randomize() );
 // and start it on the sequencer
 seq.start( env.agent.sqr );
 endtask
endclass

        sequencer也可以从其他sequencer那里接收transaction。这里有一个例子:

class ano_sqr extends uvm_sequencer #(ano_tx);
 `uvm_component_utils(ano_sqr)
 
 uvm_seq_item_pull_port #(my_tx) seq_item_port;
 ...
 function void build_phase(uvm_phase phase);
 super.build_phase(phase);
 seq_item_port = new("seq_item_port", this);
 endfunction
endclass

        这个sequnecer可以连接到前面的sequnencer,它通过port-export对发送my_tx类型的事务。

class my_env extends uvm_env;
 `uvm_component_utils(my_env)
 
 my_sqr sqr1;
 ano_sqr sqr2;
 ...
 function void build_phase(uvm_phase phase);
 super.build_phase(phase);
 sqr1 = my_sqr::type_id::create("sqr1", this);
 sqr2 = ano_sqr::type_id::create("sqr2", this);
 ...
 endfunction
 
function void connect_phase(uvm_phase phase);
 sqr2.seq_item_port.connect(
 sqr1.seq_item_export );
 ...
 endfunction
endclass

        如前所述,两个sequnecer之间的通信将通过在两个sequnecer之间进行事务级调用来完成,在sqr2从sqr1中提取事务的情况下。在sqr2上运行的实际序列如下所示:   

class ano_seq extends uvm_sequence #(ano_tx);
 `uvm_object_utils(ano_seq)
 `uvm_declare_p_sequencer(ano_sqr)
 ... 
 task body;
 ...
 my_tx tx_from_1;
 p_sequencer.seq_item_port.get(tx_from_1);
 ...

        从概念上讲,这里发生的都是一个运行在sequencer上的sequence,通过sequencer上的port输入事务。为了做到这一点,它需要直接访问它正在运行的sequnecer对象,它是由预定义的变量p_sequencer提供的。通过p_sequencer,一个序列可以引用在sequnecer类中声明的变量,包括端口和对其他外部组件的引用。事实证明,为了在另一个sequencer上有一个sequence开始子类sequence,也就是说,在一个sequencer上,而不是它本身运行的那个。这样的sequence被称为虚拟sequence,因为它本身并不生成事务,而是控制着在其他sequencer上运行的其他sequence的执行。

5.11 driver

        driver是总是位于sequencer下游的各种组件。driver从其sequencer中提取事务,并控制到DUT的信号级接口。driver和sequencer之间的事务级接口是UVM的一个固定特性,它的不同寻常之处在于,事务级通信所需的port和export都是隐式的。

class my_driver extends uvm_driver #(my_tx);
 `uvm_component_utils(my_driver)
 virtual dut_if dut_vi;
 function new(string name, uvm_component parent);
 super.new(name, parent);
 endfunction
 
 function void build_phase(uvm_phase phase);
 super.build_phase(phase);
 begin
 my_config config;
 uvm_config_db #(virtual dut_if)::get(
 this, "", "dut_vi", dut_vi);
 end
 endfunction
 
 task run_phase(uvm_phase phase);
 forever
 begin
 my_tx tx;
 seq_item_port.get(tx);
 
 // Wiggle pins of DUT
 dut_vi.cmd = tx.cmd;
 dut_vi.addr = tx.addr;
 dut_vi.data = tx.data;
 end
 endtask
endclass

        在上面的示例中,driver通过隐式的seq_item_port通过调用get(tx)与sequencer进行通信。在获得一个事务后,它然后通过分配给sv接口的成员来驱动信号级接口到DUT,这是通过虚拟接口dut_vi来完成的。虚拟接口是sv语言机制,用于在结构性Verilog模块和基于类的验证环境之间传递数据。

        上面的示例展示了如何在构建阶段使用配置对象将虚拟接口向下传递给驱动程序。

5.12 monitor

        monitor是被限制于对DUT的信号级接口进行被动访问的各种组件。monitor监视进出DUT的流量,它从DUT中组装事务,并通过一个或多个分析端口分发到验证环境的其他部分。

        所有必要的概念都已经在上面讨论过了。下面是一个示例:

class my_monitor extends uvm_monitor;
 `uvm_component_utils(my_monitor)
 uvm_analysis_port #(my_tx) aport;
 
 virtual dut_if dut_vi;
 ...
 task run_phase(uvm_phase phase);
 forever
 begin
 my_tx tx;
 // Sense the DUT pins on a clock edge 
 @(posedge dut_vi.clock);
 tx = my_tx::type_id::create("tx");
 tx.cmd = dut_vi.cmd;
 tx.addr = dut_vi.addr;
 tx.data = dut_vi.data;
 
 aport.write(tx);
 end
 endtask
endclass

5.13 coverage and checking

        sv本身有许多语言特性来支持功能覆盖数据的收集和检查功能的正确性。在UVM中,这样的代码通常会被放置在从monitor接收事务的验证组件中。覆盖和检查最好与monitor分开,以便每个都可以更容易地重用;monitor组件通常特定于特定的协议,但独立于应用程序。相比之下,功能覆盖和检查代码通常是高度特定于应用程序的,但在某些情况下可能独立于用于与DUT通信的协议。

       一个analysis port可以不连接,也可以连接到一个或多个分析导出。subscriber是具有一个内置analysis port的组件:

class my_subscriber extends uvm_subscriber #(my_tx);
 `uvm_component_utils(my_subscriber)
 
 // Coverage registers
 bit cmd;
 int addr;
 int data;
 
 covergroup cover_bus;
 coverpoint cmd;
 coverpoint addr;
 coverpoint data;
 endgroup
 ...
 // Function called through analysis port
 function void write(my_tx t);
 cmd = t.cmd;
 addr = t.addr;
 data = t.data;
 cover_bus.sample();
 endfunction
endclass

        可以使用在连接阶段建立的工厂和port-export连接,在组件层次结构的下一级上生成monitor和subscriber:

class my_env extends uvm_env;
`uvm_component_utils(my_env)
 
 my_monitor monitor;
 my_subscriber subscriber;
 ...
 function void build_phase(uvm_phase phase);
 super.build_phase(phase);
 monitor = my_monitor::type_id::create(
 "monitorh" , this);
 subscriber = my_subscriber::type_id::create(
 "subscriber", this);
 endfunction
 
 function void connect_phase(uvm_phase phase);
 monitor.aport.connect(
 subscriber.analysis_export );
 endfunction
endclass

        请注意对订阅者的隐式analysis export的引用。

        有时,一个检查组件可能需要接收两个或多个传入的事务流,在这种情况下,uvm_subscriber的单一隐式analysis export是不够的,并且需要显式地声明analysis export:

class A extends uvm_component;
 ...
 uvm_analysis_imp #(tx1, A) analysis_export;
 ...
 function void write(tx1 t);
 ...
class B extends uvm_component;
 ...
 uvm_analysis_imp #(tx2, B) analysis_export;
 ...
 function void write(tx2 t);
 ...
class my_checker extends uvm_component;
 ...
 // Two incoming transaction streams
 uvm_analysis_export #(tx1) tx1_export;
 uvm_analysis_export #(tx2) tx2_export;
 ...
 A a;
 B b;
 ...
 function void connect_phase(uvm_phase phase);
 // Bind exports to two separate children
 tx1_export.connect( a.analysis_export );
 tx2_export.connect( b.analysis_export );
 endfunction

        sv不支持函数重载,所以一个类只能有一个写函数。因此,最终每个analysis export都需要与一个单独的类中的写函数相关联,在上面的示例中是类A和类B。

5.14 transaction operations

        通常需要对事务执行操作,例如打印出事务的内容、制作事务的副本,或对两个等价事务进行比较。例如,上述subscriber可能希望记录来自事务的数据,或者将来自DUT的事务与代表预期行为的另一个事务进行比较。UVM为以下目的提供了一组标准的函数,如下图所示:

function void write(my_tx t);
 ...
 my_tx tx;
 tx.copy(t)
 history.push_back(tx);
 if ( !t.compare(expected))
 `uvm_error("mismatch", 
 $sformatf("Bad transaction = %s",
 t.convert2string()));
endfunction

        首先,复制函数将传递的事务的一个完整副本作为参数,在这种情况下,将该副本存储在过去事务的一个队列中。其次,比较函数对两个不同的事务进行等价性比较。最后,转换字符串函数返回一个字符串,表示可打印格式的事务内容。

        一次transaction比我们想象中看到的要多。除了包含表示被建模协议属性的数据字段外,事务对象还可能包含时间戳、日志和时间戳诊断等管理信息。在执行复制、比较或转换字符串操作时,通常需要区别处理此次要信息,并且用户在声明事务类时需要考虑这种差异。例如:

class my_tx extends uvm_sequence_item;
 `uvm_object_utils(my_tx)
 rand bit cmd;
 rand int addr;
 rand int data;
 ... 
 function string convert2string;
 return $sformatf(...);
 endfunction
 
 function void do_copy(uvm_object rhs);
 my_tx rhs_;
 super.do_copy(rhs);
 $cast(rhs_, rhs);
 cmd = rhs_.cmd;
 addr = rhs_.addr;
 data = rhs_.data;
 endfunction
 
 function bit do_compare(uvm_object rhs,
 uvm_comparer comparer);
 my_tx rhs_;
 bit status = 1;
 status &= super.do_compare(rhs, comparer);
 $cast(rhs_, rhs);
 status &= comparer.compare_field("cmd", cmd,
 rhs_.cmd, $bits(cmd));
 status &= comparer.compare_field("addr", addr,
 rhs_.addr, $bits(addr));
 status &= comparer.compare_field("data", data,
 rhs_.data, $bits(data));
 return(status);
 endfunction
endclass

5.15 reporting

        上面使用的`uvm_error宏是消息报告的四个标准宏之一,其他的分别是`uvm_info、`uvm_warning和`uvm_fatal。第一个宏参数是消息类型,在自定义报表处理程序的行为时,它可用于对报表进行分类。第二个宏参数是报告的文本。在信息报告的特殊情况下,有第三个宏参数指定一个冗长级别,例如:

`uvm_info("message_type", "message", UVM_LOW)

        冗长级别大于某个最大级别的report将被过滤掉。 

5.16 end of test mechanism

        检测测试何时结束现在依赖于每个component和sequence提出和删除所谓的反对意见。component或sequence应该在忙于生成或处理某些活动时提出异议,并且在等待下一个事务或事件之前,应该在完成下一个事务或事件之前放弃异议。

        以下是一个在一个顺序中提出和删除反对意见的例子:

task body;
 uvm_test_done.raise_objection(this);
 repeat(n)
 begin
 ...
 end
 uvm_test_done.drop_objection(this);
endtask

        uvm_test_done是一个可用于结束测试的全局对象。也可以提出和降低对结束个别阶段的反对意见,例如:

task run_phase(uvm_phase phase);
 phase.raise_objection(this);
 ...

        当前阶段将在最后一个反对意见被放弃后完成,即使有其他活动组件,如时钟生成器仍在运行。唯一的问题是,必须在调度程序的第一个非阻塞分配区域之前调用提出异议,否则运行阶段将以模拟时间仍然为零结束。

6. Conclusion

        UVM是一个丰富而强大的类库,多年来从大量的实际验证项目的经验发展而来,sv本身是一种大型而复杂的语言。因此,尽管UVM为验证专家提供了许多强大的功能,但它对那些想要从测试台重用中获益的Verilog和VHDL设计者来说是一个艰巨的挑战。本文中提出的指南旨在简化从HDL向UVM的过渡。

  • 68
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值