概述
前言:
这篇系列将从0开始搭建一个UVM验证平台,来帮助一些学习了SV和UVM知识,但对搭建完整的验证环境没有概念的朋友。
UVM前置基础:
1.UVM基础-factory机制、phase机制
2.UVM基础-组件(driver、monitor、agent...)
3.UVM基础-TLM通信机制(一)
4.UVM基础-TLM通信机制(二)
...还在更新
从零搭建一个UVM验证平台:
从零开始,搭建一个简单的UVM验证平台(一)
从零开始,搭建一个简单的UVM验证平台(二)
从零开始,搭建一个简单的UVM验证平台(三)
从零开始,搭建一个简单的UVM验证平台(四)
...还在更新
目录
interface & virtual interface
config_db机制
阶段代码总结
dut.sv
driver.sv
my_if.sv
top_tb.sv
添加transaction
transaction定义
基于transaction的driver
在上个上个文章中,已经搭建了一个只有driver的验证平台,涉及到了类的继承与派生、factory工厂机制、phase机制、objection机制。下面我们要对之前的平台进行进一步完善。
interface & virtual interface
在前面的代码中,driver的等待时钟事件@(posedge top_tb.clk)以及DUT中输入端口赋值(top_tb.data_i_valid <= 1'b1)都是使用的绝对路径。假如clk信号的层次从top_tb.clk变成了top_tb.clk_inst.clk,那么就需要对原来的代码进行大量修改。
避免使用绝对路径的方式就是interface。在SystemVerilog中使用interface来链接验证平台与DUT的端口,interface的定义如下:
interface my_if(input clk, input rstn);
logic [7:0] data;
logic valid;
endinterface
定义了interface之后,在top_tb中例化DUT时,就可以直接使用:
my_if input_if(clk,rstn);
my_if output_if(clk,rstn);
dut my_dut(
.clk (clk) ,
.data_i (input_if.data_if) ,
.data_o (output_if.data_if) ,
.data_i_valid (input_if.valid) ,
.data_o_valid (output_if.valid)
);
在module中可以声明interface,但是如果要在driver中使用interface,因为driver是一个类,所以不能直接声明interface(语法报错),在类中使用的是virtual interface。
class my_driver extends uvm_driver;
virtual my_if vif;
因为driver类定义了interface,所以我们还需要将my_driver类中的main_phase任务中的data和valid信号用interface中的信号,修改后的含有interface的driver.sv为:
`include "uvm_macros.svh"
import uvm_pkg::*;
class my_driver extends uvm_driver;
`uvm_component_utils(my_driver) //注册
virtual my_if vif;
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);
vif.data_if <= 8'd0;
vif.valid <= 1'b0;
while(!vif.rstn)
@(posedge vif.clk);
for(int i = 0; i < 256; i = i+1)begin
@(posedge vif.clk)
vif.data_if <= $urandom_range(0,255);
vif.valid <= 1'b1;
`uvm_info("my_driver", "data is drived", UVM_LOW)
end
@(posedge vif.clk);
vif.valid <= 1'b0;
phase.drop_objection(this);
endtask
现在my_drive中的绝对路径就消除了,提高了代码的复用性。但是目前还存在一个问题:如何将top_tb中的input_if和my_driver中的vif对应起来呢?
config_db机制
在top_tb里,我们生成激励是用driver类来生成,driver生成激励后送到driver中的virtual interface中的vif.data_if,如何把driver中的vif.data_if赋值给top_tb中的input_if.data_if呢?如果直接赋值:
input_if.data_if = top_tb.driver.vif.data_if;
会直接报错,因为driver是UVM通过run_test语句实例化了一个脱离top_tb层次结构的实例,建立了一个新的结构。对于这种脱离top_tb层次结构,又希望在top_tb中对其进行某些操作的实例,需要用到UVM的config_db机制。
config_db机制分为set和get两个操作。
在top_tb中执行set操作:
initial begin
uvm_config_db#(virtual my_if)::set(null, "uvm_test_top", "vif", input_if);
end
在my_driver中执行get操作:
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
`uvm_info("my_driver", "build phase is called", UVM_LOW);
if(`uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
`uvm_fatal("my_driver", "virtual interface must be set for vif")
//若执行uvm_fatal,则打印完第二个参数的信息后,会自动调用finish函数来终止仿真
endfunction
这里引用了build_phase,与main_phase一样,是UVM内建的一个phase,当UVM启动后,会自动执行build_phase。build_phase在new函数之后,main_phase之前执行。
build_phase主要的作用就是通过config_db的set和get操作来做一些数据的配置和传递,以及实例化成员变量等。值得注意的是,super.build_phase这条语句是必须的,在父类uvm_build_phase 中执行了一些必要的操作,必须显式地调用并执行它。
build_phase和main_phase的区别
build_phase与main_phase的不同点在于,build_phase是一个function,而main_phase是一个task。我们知道task和function的最大区别就在于function是没有延时的,即不消耗仿真时间,在仿真开始的瞬间就执行了;而task类型的main_phase中则可以添加一些带有延时的操作,譬如@(posedge clk)、wait等操作。
在build_phase中出现的uvm_fatal宏,这个宏和uvm_info的功能是差不多的,都是打印一些信息,它们的区别在于:uvm_fatal在打印第二个参数所示的信息后,会直接调用verilog 的finish函数来终止仿真。uvm_fatal的出现,表示验证平台出现了重大问题而无法继续下去,必须停止仿真并做相应的检查,只要是uvm_fatal打印的信息,就一定是非常关键的。
uvm_config_db#(virtual my_if)::set(null, "uvm_test_top", "vif", input_if)
uvm_config_db#(virtual my_if)::get(this, "" , "vif", vif)
config_db的set和get都有四个参数,在set/get之前,有一个#(virtual my_if),这里跟的是我们需要传输的数据类型,因为我们要传输的是一个接口类型,所以填virtual my_if,如果要传输一个整型则写config_db#(int)。
set和get的第一和第二个参数后面再说,先讲第三第四个参数。set的第二个参数是路径索引,索引到操作的目标所在的实例名称;set和get的第三个参数必须完全一致(可以为变量名);set的第四个参数表示要把哪个interface传递给my_driver;get的第四个参数表示要把接收到的interface传递给my_driver的哪个成员变量。
阶段代码总结
到此为止,我们已经写了四个模块,分别是:driver.sv、my_if.sv、top_tb.sv和dut.sv,代码分别为:
dut.sv
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
driver.sv
`include "uvm_macros.svh"
import uvm_pkg::*;
class my_driver extends uvm_driver;
`uvm_component_utils(my_driver) //注册
virtual my_if vif;
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
`uvm_info("my_driver", "build phase is called", UVM_LOW);
if(!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
`uvm_fatal("my_driver", "virtual interface must be set for vif");
endfunction
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);
vif.data_if <= 8'd0;
vif.valid <= 1'b0;
while(!vif.rstn)
@(posedge vif.clk);
for(int i = 0; i < 256; i = i+1)begin
@(posedge vif.clk)
vif.data_if <= $urandom_range(0,255);
vif.valid <= 1'b1;
`uvm_info("my_driver", "data is drived", UVM_LOW)
end
@(posedge vif.clk);
vif.valid <= 1'b0;
phase.drop_objection(this);
endtask
my_if.sv
interface my_if(input clk, input rst_n);
logic [7:0] data_if;
logic valid;
endinterface
top_tb.sv
`timescale 1ns/1ps
`include "uvm_macros.svh"
import uvm_pkg::*;
`include "my_driver.sv"
`include "my_if.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;
my_if input_if(clk,rstn);
my_if output_if(clk,rstn);
dut my_dut(
.clk (clk) ,
.data_i (input_if.data_if) ,
.data_o (output_if.data_if) ,
.data_i_valid (input_if.valid) ,
.data_o_valid (output_if.valid)
);
initial begin
run_test("my_driver");
end
initial begin
uvm_config_db#(virtual my_if)::set(null, "uvm_test_top", "vif", input_if);
end
initial begin
clk = 0;
forever begin
#100 clk = ~clk;
end
end
initial begin
rstn = 1'b0;
#1000
rstn = 1'b1;
end
endmodule
下面我们在上面的基础上进行进一步的扩充和拓展。
添加transaction
接下来我们还要为我们的验证平台添加monitor、scoreboard、reference model等验证平台组件。这些组件之间的信息传递是基于transaction的,因此我们在添加组件之前,先要添加transaction。
transaction是一个抽象的概念。一般来说,物理协议中的数据交换都是以帧或者包为单位的,通常在一帧或者一个包中要定义好各项参数,一笔transactiin就是一个包。在不同的验证平台中会有不同的transaction。
transaction定义
class my_transaction extends uvm_sequence_item;
rand bit [63:0] password; //assume there is a 64bits password
`uvm_object_utils(my_transaction);
function new(string name = "my_transaction");
super.new(name);
endfunction
endclass
在transaction定义中,有两点值得注意:一是my_transaction的基类是uvm_sequence_item。在UVM中,所有的transaction都要从uvm_sequence_item派生,只有从uvm_sequence_item派生的transaction才可以使用UVM中强大的sequence机制;二是transaciton在使用factory机制时是使用uvm_object_utils,my_transaction和my_driver是由区别的:在整个仿真期间,driver是一直存在的,transaction不同,它在仿真的某一时间产生,经过driver驱动,再经过reference model处理,最终由scoreboard比较完成后,其生命周期就结束了。一般来说,这种类都是派生自uvm_object或者uvm_object的派生类。UVM中具有这种特征的类都要使用uvm_object_utils宏来注册。
基于transaction的driver
完成transaction.sv的定义后,就可以在my_driver中实现基于transaction的驱动。
`include "uvm_macros.svh"
`include "my_transaction.sv"
import uvm_pkg::*;
class my_driver extends uvm_driver;
`uvm_component_utils(my_driver) //注册
virtual my_if vif;
function new(string name = "my_driver", uvm_component parent = null);
super.new(name, parent);
`uvm_info("my_driver", "new is called", UVM_LOW)
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
`uvm_info("my_driver", "build phase is called", UVM_LOW);
if(!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
`uvm_fatal("my_driver", "virtual interface must be set for vif");
endfunction
extern task main_phase(uvm_phase phase);
extern task drive_password_trans(my_transaction tr);
endclass
task my_driver::main_phase(uvm_phase phase);
my_transaction tr;
phase.raise_objection(this);
vif.data_if <= 8'd0;
vif.valid <= 1'b0;
for (int i=0; i<8; i=i+1)begin
tr = new("tr");
assert(tr.randomize());
drive_password_trans(tr);
end
repeat(5) @(posedge vif.clk); //delay
phase.drop_objection(this);
endtask
task my_driver::drive_password_trans(my_transaction tr);
bit [7:0] data_q[$];
bit [63:0] temp_password;
//push password to data_q
temp_password = tr.password;
for (int i=0; i<8; i++)begin
data_q.push_back(temp_password[7:0]);
temp_password = (temp_password >> 8);
end
`uvm_info("my_driver", "main phase is called", UVM_LOW);
repeat(3) @(posedge vif.clk); //delay
while(data_q.size()>0)begin
@(posedge vif.clk);
vif.valid <= 1'b1;
vif.data_if <= data_q.pop_front();
end
@(posedge vif.clk);
vif.valid <= 1'b0;
`uvm_info("my_driver", "end drive one_serial password", UVM_LOW);
endtask
仿真结果:
在transaction中循环了8次drive_password_trans任务。
for (int i=0; i<8; i=i+1)begin
tr = new("tr");
assert(tr.randomize());
drive_password_trans(tr);
end
在drive_password_trans任务中,我们传输一次password
task my_driver::drive_password_trans(my_transaction tr);
bit [7:0] data_q[$];
bit [63:0] temp_password;
//push password to data_q
temp_password = tr.password;
for (int i=0; i<8; i++)begin
data_q.push_back(temp_password[7:0]);
temp_password = (temp_password >> 8);
end
`uvm_info("my_driver", "main phase is called", UVM_LOW);
repeat(3) @(posedge vif.clk); //delay
while(data_q.size()>0)begin
@(posedge vif.clk);
vif.valid <= 1'b1;
vif.data_if <= data_q.pop_front();
end
drive_password_trans任务功能描述:
这里因为data_q设置的是8bit,而password一次是64bit的,所以设置data_q为队列,需要入队8次才能把password完整的存下来,所以我们for循环循环8次,每次让8bit数据从队列的尾部进入,也即push_back。
之后等待三个时钟上升沿,然后把队列中的首部数据(pop_front)排出送给my_driver的interface “vif”,直到队列排空。
至此,我们就完成了添加transaction的driver设计,涉及到的知识有interface、virtual interface、config_db机制、transaction设计等内容。至此不要忘了,我们加入transaction的目的是因为monitor、scoreboard等验证组件之间的交互是通过transaction的,因此,接下来我们就要添加UVM的其他组件了。
最后
以上就是危机冰淇淋为你收集整理的从零开始,搭建一个简单的UVM验证平台(二)interface & virtual interfaceconfig_db机制阶段代码总结添加transaction的全部内容,希望文章能够帮你解决从零开始,搭建一个简单的UVM验证平台(二)interface & virtual interfaceconfig_db机制阶段代码总结添加transaction所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复