前言:
这篇系列将从0开始搭建一个UVM验证平台,来帮助一些学习了SV和UVM知识,但对搭建完整的验证环境没有概念的朋友。
UVM前置基础:
2.UVM基础-组件(driver、monitor、agent...)
...还在更新
从零搭建一个UVM验证平台:
...还在更新
目录
① 验证平台要模拟DUT的各种真实使用情况,这意味着要给DUT施加各种激励,激励的功能是由driver来实现的。
② 验证平台要能够根据DUT的输出来判断DUT的行为是否与预期相符合,完成这个功能的是计分板(scoreboard,在SV中被称为checker)。
③ 验证平台要收集DUT的输出并把它们传递给scoreboard,完成这个功能的是monitor。
④ 验证平台要能够给出预期结果。假设DUT是一个加法器,那么当在它的加数和被加数中分别输入1,即输入1+1时,期望DUT输出2。当DUT在计算1+1的结果时,验证平台也必须相应完成同样的过程,也计算一次1+1。在验证平台中,完成这个过程的是参考模型(reference model)。

一个简单的验证平台框图如图所示。
driver是验证平台最基本的组件,是整个验证平台数据流的源泉。
假设有一个DUT(design under test)被如下描述:
module dut(
input clk ,
input rstn ,
input [7:0] data_i ,
input data_i_valid ,
output reg [7:0] data_o ,
output reg data_o_valid
);
always @(posedge clk)begin
if(!rstn)begin
data_o <= 8'd0;
data_o_valid <= 1'b0;
end
else begin
data_o <= data_i;
data_o_valid <= data_i_valid;
end
end
endmodule
有如上一个DUT需要被我们验证,上面这个设计的功能非常简单,输入数据data_i和其数据有效信号data_i_valid,打一拍输出。data_o_valid是输出数据有效信号。
UVM是一个库,在这个库中所有的东西几乎都是用类(class)实现的。类是System verilog 面向对象编程语言中最伟大的发明之一,是面向对象的精髓。
类有函数(function)和任务(task),通过这些函数和任务可以完成driver的输出激励功能、完成monitor的监测功能、完成参考模型的计算功能、完成scoreboard的比较功能。
类的三要素封装、继承和多态,继承是类最总要的特性之一,我们在搭建验证环境时,要保证验证平台中所有的组件应该继承于UVM中的类。
UVM验证平台中的driver应该派生自uvm_driver,一个简单的driver可以如下所示。
`include "uvm_macros.svh"
import uvm_pkg::*;
class my_driver extends uvm_driver;
function new(string name = "my_driver", uvm_component parent = null);
super.new(name, parent);
endfunction
extern virtual task main_phase(uvm_phase phase);
endclass
task my_driver::main_phase(uvm_phase phase);
top_tb.data_i <= 8'd0;
top_tb.data_i_valid <= 1'b0;
while(!top_tb.rstn)
@(posedge top_tb.clk);
for(int i = 0; i < 256; i = i+1)begin
@(posedge top_tb.clk)
top_tb.data_i <= $urandom_range(0,255);
top_tb.data_i_valid <= 1'b1;
`uvm_info("my_driver", "data is drived", UVM_LOW)
end
@(posedge top_tb.clk);
top_tb.data_i_valid <= 1'b0;
endtask
这个driver的功能就是向data_i发送256个随机数据,同时将输入数据有效信号data_i_valid拉高,当数据发送完毕后,将data_i_valid信号拉低。在这个driver中有两点需要注意:
① 所有派生自uvm_driver的类的new函数有两个参数,一个是string类型的name,一个是uvm_component类型的parent。这两个参数是必不可少的是uvm_component所要求的。每一个派生自uvm_component或其派生的类在其new函数中需要指明两个参数:name和parent,这是uvm_componet类的一大特征。uvm_driver是一个派生自uvm_component的类,所以自然也会有name和parent两个参数。

② driver所做的事几乎都在main_phase中完成。UVM由phase来管理验证平台的运行,这些phase统一咦xxxx_phase来命名,且都有一个类型为uvm_phase、名字为phase的参数。main_phase是uvm_driver中预先定义好的一个任务。因此几乎可以简单地认为,实现一个driver等于实现其main_phase。

上图为一个完整的phase全过程。
driver中还出现了uvm_info宏(除此之外还有uvm_error宏和uvm_warning宏),它有三个参数,第一个参数是字符串,用于打印的信息归类;第二个参数也是字符串,是具体需要打印的信息;第三个参数是冗余级别,表示这个命令的紧急程度。针对第三点,在验证平台中,某些信息是非常关键的,这样的信息可以设置为UVM_LOW,而有些信息可有可无就可以设置为UVM_HIGH,介于两者之间则是UVM_MEDIUM。UVM默认只显示UVM_MEDIUM或者UVM_LOW的信息。
uvm_info宏非常强大,它包含了打印信息的物理文件来源、逻辑节点信息(在UVM树种的路径索引)、打印时间、对信息的分类组织及打印的信息。因此在搭建验证平台时应该尽量使用uvm_info宏取代display语句。
定义了my_driver类后还需要将其实例化。类的定义和类的实例化是存在区别的,类的定义是class-end块,定义一个类,告诉类有哪些功能。而类的实例化是使用new()函数来创建一个实例。
class A;
...
endclass
A a_inst;
a_inst = new();
仿真器接到new的指令后,就会在内存中划分出一块空间,在划分前,会首先检查是否已经预先定义过这个类,在已经定义过的情况下,按照定义中所指示的“条文”分配空间,并且把这块空间的指针返回给a_inst,之后就可以通过a_inst来查看类中的各个成员变量,调用成员函数/任务等。对于大部分类来说,如果只定义而不实例化,是没有任何意义的;而如果不定义就直接实例化,仿真器就会报错。
对my_driver实例化并且最终搭建的验证平台如下:
`timescale 1ns/1ps
`include "uvm_macros.svh" //这是UVM中的一个文件,包含了众多宏定义
import uvm_pkg::*; //只有导入了这个库,编译器在编译my_driver.sv文件时才会认识其中继承的uvm_driver等类名
`include "my_driver.sv"
module top_tb;
reg clk,rstn;
reg [7:0] data_i;
reg data_i_valid;
wire [7:0] data_o;
wire data_o_valid;
dut my_dut(
.clk (clk) ,
.data_i (data_i) ,
.data_o (data_o) ,
.data_i_valid (data_i_valid) ,
.data_o_valid (data_o_valid)
);
initial begin
my_driver drv; // instance
drv = new("drv", null);
drv.main_phase(null);
$finish();
end
initial begin
clk = 0;
forever begin
#100 clk = ~clk;
end
end
initial begin
rstn = 1'b0;
#1000
rstn = 1'b1;
end
endmodule
仿真结果:

在仿真窗口中可以看到,data is drived 被输出了256次。
到这里我们实现了使用driver给DUT送激励的验证操作,但其实用简单的SV也能实现这样的功能,使用UVM的特性需要引入factory机制,实现:自动创建一个类的实例并调用其中的函数(function)和任务(task)。
在我们原来写的生成激励的driver.sv上做些修改:
`include "uvm_macros.svh"
import uvm_pkg::*;
class my_driver extends uvm_driver;
`uvm_component_utils(my_driver) //注册
function new(string name = "my_driver", uvm_component parent = null);
super.new(name, parent);
`uvm_info("my_driver", "new is called", UVM_LOW)
endfunction
extern virtual task main_phase(uvm_phase phase);
endclass
task my_driver::main_phase(uvm_phase phase);
`uvm_info("my_driver", "main phase is called", UVM_LOW);
top_tb.data_i <= 8'd0;
top_tb.data_i_valid <= 1'b0;
while(!top_tb.rstn)
@(posedge top_tb.clk);
for(int i = 0; i < 256; i = i+1)begin
@(posedge top_tb.clk)
top_tb.data_i <= $urandom_range(0,255);
top_tb.data_i_valid <= 1'b1;
`uvm_info("my_driver", "data is drived", UVM_LOW)
end
@(posedge top_tb.clk);
top_tb.data_i_valid <= 1'b0;
endtask
和之前的driver不同的是在class定义的中间加入了注册宏,uvm_component_utils(my_driver)
引入了工厂机制之后,就可以自动化的运行类中的function和task,非常方便。在top_tb.sv文件中将my_driver的实例化和main_phase的调用替换为run_test("my_driver")
initial begin
my_driver drv;
drv = new("drv", null);
drv.main_phase(null);
$finish();
end
//将上述initial替换为下面的initial语句块
initial begin
run_test("my_driver");
end
之所以会自动运行是因为,在注册了工厂之后,run_test语句会自动创建一个my_driver的实例,并且会自动调用my_driver的main_phase。传递给run_test的是一个字符串,UVM会根据这个字符串创建了其所代表的一个实例。
注意:所有派生自uvm_component及其派生类的类,注册的时候都应该使用uvm_componet_utils()宏注册。

但从仿真的结果我们会发现,输出了main phase is called但是没有输出256次data is drived,而main_phase是一个完整的任务,没理由只执行第一句,而后面的代码不执行。这就牵扯到了UVM的objection机制。
在上面的代码中,我们并没有使用finish函数来终止验证平台,但验证平台确实是关闭了。在UVM中通过objection机制来控制验证平台的关闭。
在每个phase中,UVM会检查是否有objection被提起(raise_objection),如果有,那么等待这个objection被撤销(drop_objection)后停止仿真;如果没有,则马上结束当前phase。
我们在前面使用工厂机制自动创建并执行main_phase的实例my_driver中没有提起objection,但是还是执行了my_driver的第一条语句
`uvm_info("my_driver", "main_phase is called", null);
是不是和我们这里说的“如果没有提起objection,则马上结束当前phase”矛盾呢?其实不是的,这涉及到一个仿真时间片的概念,仿真器首先要进入到my_driver,才能检测本实例是否把objection提起,在进入my_driver的0时刻,宏定义uvm_info就已经执行了,与此同时,仿真器没检测到objection被提起,继而退出,这并不与uvm_info被执行排斥。
加入objection机制在my_driver的main_phase首尾,在头部raise_objection,在task尾部,drop_objection。
修改my_driver.sv的代码为:
`include "uvm_macros.svh"
import uvm_pkg::*;
class my_driver extends uvm_driver;
`uvm_component_utils(my_driver) //注册
function new(string name = "my_driver", uvm_component parent = null);
super.new(name, parent);
`uvm_info("my_driver", "new is called", UVM_LOW)
endfunction
extern virtual task main_phase(uvm_phase phase);
endclass
task my_driver::main_phase(uvm_phase phase);
phase.raise_objection(this);
`uvm_info("my_driver", "main phase is called", UVM_LOW);
top_tb.data_i <= 8'd0;
top_tb.data_i_valid <= 1'b0;
while(!top_tb.rstn)
@(posedge top_tb.clk);
for(int i = 0; i < 256; i = i+1)begin
@(posedge top_tb.clk)
top_tb.data_i <= $urandom_range(0,255);
top_tb.data_i_valid <= 1'b1;
`uvm_info("my_driver", "data is drived", UVM_LOW)
end
@(posedge top_tb.clk);
top_tb.data_i_valid <= 1'b0;
phase.drop_objection(this);
endtask
在执行drop_objection语句之前,必须先调用raise_objection语句,这两者总是成对出现。

加入了raise_objection和drop_objection之后我们会发现"data is drived"按照预期输出了256次。
另外再提一点,UVM检查是否提起objection,就是在进入main_phase的瞬间,因此,在进入main_phase之后就必须立刻提起objection,而不能经过任何带有延时的语句譬如@(posedge clk)或者 #1,但凡在raise_objection之前带有延时语句,那么这个objection就不会被检测到,进而导致main_phase立即退出。
至此,我们的一个仅含有driver的验证平台就搭建完了,到此为止我们总共涉及了几个知识点:类的继承与派生、factory工厂机制、phase机制、objection机制。后面我们还会在这基础之上添加virtual interface、添加 transaction、monitor组件、agent组件以及sequence组件等等。
给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru
我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..
使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta
我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何
我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>
我希望我的UserPrice模型的属性在它们为空或不验证数值时默认为0。这些属性是tax_rate、shipping_cost和price。classCreateUserPrices8,:scale=>2t.decimal:tax_rate,:precision=>8,:scale=>2t.decimal:shipping_cost,:precision=>8,:scale=>2endendend起初,我将所有3列的:default=>0放在表格中,但我不想要这样,因为它已经填充了字段,我想使用占位符。这是我的UserPrice模型:classUserPrice回答before_val
我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss
如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象
关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?
我有一些非常大的模型,我必须将它们迁移到最新版本的Rails。这些模型有相当多的验证(User有大约50个验证)。是否可以将所有这些验证移动到另一个文件中?说app/models/validations/user_validations.rb。如果可以,有人可以提供示例吗? 最佳答案 您可以为此使用关注点:#app/models/validations/user_validations.rbrequire'active_support/concern'moduleUserValidationsextendActiveSupport: