目录
前言
这篇文章主要讲述agent中各个组件的模板的通用写法,具体的环境还是要依靠DUT的环境。读者可以细致阅读代码注释以体会其中的设计思想。
以编写一个agent为例,简单的描述一下agent的内容,其基本概念已经在 UVM入门基础 文章里做了简单介绍,这里不在赘述了。
若其中含有错误还恳请读者指正。
参考:绿皮书,验证公众号
一个成熟的agent里面,势必包含了很多可重用性组件,不过这些组件通常也是照搬其他环境的组件然后自己做改动的。
下面做一些简单的组件编写介绍。
interface:
接口的编写很简单,通常是直接看DUT的接口,然后进行分类后就可以写出来,因为很简单,可以先写接口。
interface op_in_if#(
DATA_WIDTH = 4 //参数申明
) (input clk, input rst_n);
logic start;
logic ed;
logic[DATA_WIDTH-1 : 0] data1;
logic[DATA_WIDTH-1 : 0] data2;
logic data_en;
parameter setup_time = 0.1ns;
parameter hold_time = 0.1ns;
clocking drv_cb @(posedge clk); //为了能从波形上看出延时
default input #setup_time output #hold_time;
output start;
output ed; //接口方向是从driver视角看的
output data1;
output data2;
output data_en;
input rst_n;
endclocking: drv_cb
clocking mon_cb @(posedge clk);
default input #setup_time output #hold_time;
input start;
input ed; //接口方向是从monitor视角看的
input data1;
input data2;
input data_en;
input rst_n;
endclocking: mon_cb
modoport drv(clocking drv_cb);
modoport mon(clocking mon_cb);
endinterface: op_in_if
sequence item :
在编写该组件时,通常会排除掉interface里的控制信号以及使能信号,只是单纯的提取数据和配置接口。这样做的原因是DUT的控制信号以及使能信号在整个环境中流通其实没有太大意义,这些控制信号可以在tb里直接配置,或者是由其他VIP控制的。
如果当前的transacion还需要用于对比,那就需要注意将其注册变量,以及添加一些相关约束,(如果不需要对比则可以将变量加入 UVM_NOCOMPARE 内)。
class op_in_seq_item#(
DATA_WIDTH = 4
) extends uvm_sequence_item;
//-------------Data Member--------------
rand bit[DATA_WIDTH-1:0] data1;
rand bit[DATA_WIDTH-1:0] data2; //monitor采样,采样到数据后存放到队列中
rand bit[DATA_WIDTH-1:0] data1_q[$];
rand bit[DATA_WIDTH-1:0] data2_q[$]; //用于临时存储数据
rand bit data_num; //用于计数数据有多少个,通常用来控制数据要发多少
`uvm_object_param_utils_begin(op_in_seq_item`PARAM_LIST)
`uvm_field_int(data1 , UVM_ALL_ON)
`uvm_field_int(data2 , UVM_ALL_ON)
`uvm_field_queue_int(data1_q , UVM_ALL_ON)
`uvm_field_queue_int(data1_q , UVM_ALL_ON)
`uvm_field_int(data_num , UVM_ALL_ON)
`uvm_object_utils_end //存在生命周期的用object注册
//-------------------------------------------------
//Methods
//-------------------------------------------------
extern function new(string name = "op_in_seq_item");
endclass:op_in_seq_item
function op_in_seq_item::new(string name = "op_in_seq_item"); //在类的外面申明函数以供类调用
super.new(name);
`uvm_info("TRACE",$sformatf(%m), UVM_HIGH)
endfunction:new
driver:
编写driver通常是根据已有的时序图编写,这个时序图来自设计文档,也可以是自己对DUT的理解,通常在main_phase里执行发送流程,而具体的数据内容则是自己定义函数编写实现。
`define DRV_VIF vif.drv_cb //为了方面书写使用宏定义来替代接口的始终块
typedef class op_in_driver;
class op_in_driver_callback#(
DATA_WIDTH = 4
)extends uvm_callback; //为了在driver中不修改原值的情况下进行额外的逻辑操作,通常使用callback机制
function new(string name = "op_in_driver_callback");
super.new(name);
endfunction
virtual task pre_send(op_in_driver`PARAM_LIST drv, op_in_seq_item`PARAM_LIST tr);
endtask //定义具体的回调方法
virtual task post_send(op_in_driver`PARAM_LIST drv, op_in_seq_item`PARAM_LIST tr);
endtask //同样是在回调基类中定义了具体的回调方法
endclass //定义了回调基类,也就是定义了回调函数的入口,在里面可以找到具体的回调方法
class op_in_driver_example_callback#(
DATA_WIDTH = 4
)extends op_in_driver_callback`PARAM_LIST; //继承上一个callback类,这只是个具体的使用示例
virtual task pre_send(op_in_driver`PARAM_LIST drv, op_in_seq_item`PARAM_LIST tr);
`uvm_info("OP_IN_DRIVER_CALLBACK", "callback test", UVM_MEDIUM)
endtask
endclass //这里演示的只是一个追踪环境功能,读者可以自行修改逻辑。
class op_in_driver#(
DATA_WIDTH = 4
) extends uvm_driver #(op_in_seq_item`PARAM_LIST);
`uvm_component_param_utils(op_in_driver`PARAM_LIST)
`uvm_register_cb(op_in_driver, op_in_driver_callback)
virtual op_in_if `PARAM_LIST vif ;
//------------------------------------------------------------
//Methods
//------------------------------------------------------------
extern function new(string name,uvm_component parent);
extern function void build_phase(uvm_phase phase);
extern virtual function void start_of_simulation_phase(uvm_phase phase);
extern virtual task main_phase(uvm_phase phase);
extern virtual task pre_reset_phase(uvm_phase phase);
extern virtual task reset_phase(uvm_phase phase);
task shutdown_phase(uvm_phase phase); //为了将最后一笔数据发出去,这里就是构造一个脉冲发出去
super.shutdown_phase(phase);
phase.raise_objection(this);
@(`DRV_VIF)
`DRV_VIF.ed <= 1;
@(`DRV_VIF)
`DRV_VIF.ed <= 0;
phase.drop_objection(this);
endtask
//用户自定义的任务
extern virtual task send(op_in_seq_item`PARAM_LIST tr);
endclass: op_in_driver
function op_in_driver::new(string name, uvm_component parent)
super.new(name , parent);
`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH)
endfunction : new
function void op_in_driver::build_phase(uvm_phase phase); //也可以在build_phase里面实例化
super.build_phase(phase)
`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH)
endfunction: build_phase
function void op_in_driver::start_of_simulation_phase(uvm_phase phase);
super.start_of_simulation_phase(phase);
`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH)
endfunction: start_of_simulation_phase
//在main_phase里正常的发驱动即可,上面的phase主要是用来追踪环境的,可以写也可以不写
task op_in_driver::main_phase(uvm_phase phase);
op_in_seq_item`PARAM_LIST req ;
`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH)
forever begin
seq_item_port.get_next_item(req) ;
`uvm_info("OP_IN_DRIVER_TRANS", req.sprint(), UVM_MEDIUM )
`uvm_do_callbacks(op_in_driver`PARAM_LIST, op_in_driver_callback`PARAM_LIST, pre_send(this,req)); //调用回调方法,也就是调用之前定义的回调函数pre_send,将回调函数里的值赋值给当前的对象req。
send(req);
repeat(20) @(`DRV_VIF);
`uvm_do_callbacks(op_in_driver`PARAM_LIST, op_in_driver_callback`PARAM_LIST, pre_send(this,req));
seq_item_port.item_done();
end
endtask : main_phase
//模拟reset之前的信号输入X态
task op_in_driver::pre_reset_phase(uvm_phase phase);
super.pre_reset_phase(phase)
`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH)
phase.raise_objection(this);
`DRV_IF.start <= 'bx;
`DRV_IF.data_en <= 'bx;
`DRV_IF.data1 <= 'bx;
`DRV_IF.data2 <= 'bx;
`DRV_IF.ed <= 'bx;
phase.drop_objection(this);
endtask: pre_reset_phase
//等待复位后X态变成复位值
task op_in_driver::reset_phase(uvm_phase phase);
super.reset_phase(phase);
`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH)
phase.raise_objection(this);
wait(vif.rst_n = 1) ; //等待事件触发
`DRV_IF.start <= 'b0;
`DRV_IF.data_en <= 'b0;
`DRV_IF.data1 <= 'b0;
`DRV_IF.data2 <= 'b0;
`DRV_IF.ed <= 'b0;
wait(vif.rst_n = 0) ;
while(!vif.rst_n) begin
@(`DRV_VIF);
end
phase.drop_objection(this) ;
endtask : reset_phase
//根据设计文档中的时序图逻辑编写即可。
task op_in_driver:: send(op_in_seq_item`PARAM_LIST tr);
@(`DRV_VIF);
`DRV_VIF.start <= 1;
@(`DRV_VIF);
`DRV_VIF.start <= 0;
//repeat(2) @(`DRV_VIF)
for(int i=0 ; i< tr.data_num; i++) begin
`DRV_VIF.data1 <= tr.data1_q[i] ;
`DRV_VIF.data2 <= tr.data2_q[i] ;
@(`DRV_VIF);
end
`DRV_VIF.data_en <= 'b0;
`DRV_VIF.ed <= 1;
@(`DRV_VIF);
`DRV_VIF.ed <= 0;
endtask :send
`undef DRV_VIF
上述的driver描写的各个phase算得上详细了,实际上的driver编写通常也懒得追加环境,读者阅读代码时注意后面的注释语句就好。
monitor:
编写时要对应着driver的驱动时序,同样是根据已有的时序图来编写,不过相对而言会更复杂一点,因为它需要使用forever语句一直保持采样,原因在于monitor不知道何时采样才能停止。
`define MON_VIF vif.mon_cb ; //同driver一样,定义宏代替时钟块
class op_in_monitor#(
DATA_WIDTH = 4
)extends uvm_monitor;
`uvm_component_param_utils(op_in_monitor`PARAM_LIST) //组件注册
virtual op_in_if`PARAM_LIST vif; //声明接口句柄
uvm_analysis_port #(op_in_seq_item`PARAM_LIST) analysis_port; //端口声明
//-----------------------------------------------------------
//Methods
//-----------------------------------------------------------
//standard UVM methods
extern function new(string name, uvm_component parent);
extern function build_phase(uvm_phase phase);
extern virtual task run_phase(uvm_phase phase);
//用户自定义函数
endclass: op_in_monitor
function op_in_monitor::new(string name, uvm_component parent)
super.new(name, parent) ;
`uvm_info("TRACE", $sformatf("%m") , UVM_HIGH)
endfunction: new
function void op_in_monitor::build_phase(uvm_phase phase)
super.build_phase(phase) ;
`uvm_info("TRACE", $sformatf("%m") , UVM_HIGH)
analysis_port = new("analysis_port", this );
endfunction:build_phase
//为了向读者展示另一种常见的写法,这里就不像driver一样细分很多phase,而是统一使用run_phasse写
task op_in_monitor::run_phase(uvm_phase phase);
op_in_seq_item`PARAM_LIST tr_clone ; //采样后赋值
op_in_seq_item`PARAM_LIST tr ;
bit mon_flag = 0 ; //采样标志
`uvm_info("TRACE", $sformatf("%m") , UVM_HIGH)
tr = op_in_seq_item`PARAM_LIST::type_id::create("tr", this);
fork
forever begin
while(`MON_VIF.rst_n !== 1) begin
@(`MON_VIF);
end
@(`MON_VIF);
if(`MON_VIF.start) begin
tr.begin_tr(); //记录事务的开始时间 ,begin_tr() 和 end_tr() 是sequence类自带的方法
mon_flag = 1;
end
else if(`MON_VIF.ed) begin
tr.end_tr(); //记录事务的结束时间
mon_flag = 0;
if(tr.data1_q.size()!=0) begin
`uvm_info("MONITOR_TRANS" ,{"\n", tr.sprint()} , UVM_MEDIUM)
$cast(tr_clone,tr.clone); //采样赋值后类型转换
tr_clone.begin_tr(tr.get_begin_time);
tr_clone.end_tr(tr.get_end_time); //获取事务的持续时间,用于分析时序
analysis_port.write(tr_clone);
tr = op_in_seq_item`PARAM_LIST::type_id::create("tr", this); //发出去后更新数值
end
end
end
forever begin
if(mon_flag && ~`MON_VIF.start && `MON_VIF.data_en) begin
tr.data1 = `MON_VIF.data1 ; //采样到数据
tr.data2 = `MON_VIF.data2;
tr.data1_q.push_back(`MON_VIF.data1); //压入队列
tr.data2_q.push_back(`MON_VIF.data2);
@(`MON_VIF);
end
else begin
@(`MON_VIF);
end
end
join
endtask : run_phase
`undef MON_VIF
sequence:
sequence可以当作是sequence_item(transaction) 的延伸,transaction 是只包含事务性信息的包,而sequence是对包的具体事务做划分,规定了其变量要做什么动作,以及怎样划分等。
在编写sequence通常是对应着要操作的那个transaction,对transaction内的信号进行一些列的操作,在将数值赋值给transaction。
不过有些代码设计时也会先编写一个base_seq出来,所有其他的seq都从这个base_seq扩展出来,共用一套基层代码,类似于base_teset一样。
class op_in_seq_base#(
DATA_WIDTH = 4
) extends uvm_sequence #(op_in_seq_item`PARAM_LIST);
`uvm_object_param_utils_begin(op_in_seq_base`PARAM_LIST)
// ......
`uvm_object_utils_end
//constrains
constrain vaild{
//data inside {[8'h0: 8'hf0]};
};
// ----------------------------------------------------------
// Methods
//-----------------------------------------------------------
//standard UVM Methods
extern function new(string name = "op_in_seq_base");
extern function void pre_randomize();
extern task pre_body(); //uvm_sequence自带的任务 , 用于控制sequence的行为
extern task post_body(); //uvm_sequence自带的任务 ,用于控制sequence的行为
endclass: op_in_seq_base
function op_in_seq_base::new(string name = "op_in_seq_base");
super.new(name);
`uvm_info("TRACE", $sformatf("%m") , UVM_HIGH )
endfunction
function void op_in_seq_base::pre_randomize();
//......空载
endfunction
task op_in_seq_base::pre_body();
`ifdef UVM_VERSION_1_1
if(get_parent_sequence() == null && starting_phase != null) begin //如果是顶层序列并且是在某个phase里运行
uvm_objection objection = starting_phase.get_objection(); //获取当前序列启动phase的objection
objection.set_drain_time(this,25ns); //设置objection的排水时间为25ns
starting_phase.raise_objection(this);
end
`endif
endtask
task op_in_seq_base::post_body();
`ifdef UVM_VERSION_1_1
if(get_parent_sequence() == null && starting_phase != null) begin
starting_phase.drop_objection(this);
end
`endif
endtask
class op_in_seq#(
DATA_WIDTH = 4
) extends op_in_seq_base`PARAM_LIST; //扩展出子sequence
`uvm_objection_param_utils(op_in_seq`PARAM_LIST)
//data member
//......
//constrain
//......
//---------------------------------------------
//Methods
//----------------------------------------------
//standard UVM Methods
extern function new(string name = " op_in_seq");
extern virtual task body();
endclass: op_in_seq
function op_in_seq::new(string name = "op_in_seq");
super.new(name)
`uvm_info("TRACE", $sformatf("%m") , UVM_HIGH)
endfunction : new
task op_in_seq::body();
op_in_seq_item`PARAM_LIST req;
`uvm_info("TRACE", $sformatf("%m") , UVM_HIGH)
`uvm_do(req)
//如果需要细分sequence,可以写成uvm_do_with(req,{data_num inside {[2:30]}; })
endtask
注释:
if (get_parent_sequence() == null && starting_phase != null) 这段代码是检查当前当前序列是否有父序列,如果为null则表明是顶层序列启动,也就是直接由sequencer启动,否则就表明是由其他序列调用,后面的是检查当前序列是否关联了UVM的phase,如果非null,则表明 它是在某个phase 里启动的。
因此这行代码的解释为:检查该序列是否为顶层启动且关联了phase。
task my_sequence::body();
// 如果是顶层序列且在某个phase中运行
if (get_parent_sequence() == null && starting_phase != null) begin
`uvm_info("SEQ_START", "This is a top-level sequence running in a phase", UVM_LOW)
// 执行仅顶层序列需要的操作(如配置DUT、启动监控等)
end
// 正常序列逻辑
forever begin
my_transaction tr;
`uvm_create(tr)
// ... 填充事务并发送 ...
end
endtask
uvm_objection objection = starting_phase.get_objection();这段代码是获取该序列的对应的objection对象,简单的说,如果序列内置的starting_phase是耗时的,那么就会获取到活动的objection对象,该phase就不会结束。
task my_sequence::body();
// 获取当前sequence中关联的phase的objection对象
uvm_objection objection = starting_phase.get_objection();
// 如果有objection对象(即当前序列关联了phase)
if (objection != null) begin
// 发起objection,阻止phase结束
objection.raise_objection(this, "Sequence started");
// 执行序列逻辑(例如发送多个事务)
repeat (10) begin
`uvm_do(req)
end
// 撤销objection,允许phase结束
objection.drop_objection(this, "Sequence completed");
end
endtask
sequencer:
sequencer通常不需要我们加任何东西进去,因此它和interface一样很好编写,为了避免后续还有某些需求,也可以写一个框架出来。
class op_in_sequencer#(
DATA_WIDTH = 4
) extends uvm_sequencer #(op_in_seq_item`PARAM_LIST);
`uvm_component_param_utils(op_in_sequencer`PARAM_LIST)
//-------------------------------------------------------
//Methods
//-------------------------------------------------------
//Standard UVM Methods
extern function new(string name = "op_in_sequencer,uvm_component parent = null);
endclass
function op_in_sequencer::new(string name = "op_in_sequencer,uvm_component parent = null);
super.new(name,parent)
endfunction: new
agent:
agent的编写相信读者已经很熟悉了,其内部也只是单纯的例化一下driver , monitor, sequencer组件,再get一下接口配置连接一下罢了。
每个人的写法都不一定相同,个人的写法是集成和连接外面的内容都放到 agent 的uvm_config_db里。
class op_in_agent #(
DATA_WIDTH = 4
) extends uvm_agent;
`uvm_component_param_utils(op_in_agent`PARAM_LIST)
//virtual op_in_if vif;
//获取接口的方式是从顶层get到,不是实例化得到,因此我用了注释
//Component Members
op_in_sequencer`PARAM_LIST seqr;
op_in_driver`PARAM_LIST drv;
op_in_monitor`PARAM_LIST mon;
uvm_analysis_port#(op_in_seq_item`PARAM_LIST) analysis_port;
op_in_agent_config`PARAM_LIST agt_cfg; //获取配置
//-----------------------------------------------------------
//Methods
//-----------------------------------------------------------
//Standard UVM Methods
extern function new(string name,uvm_component parent);
extern function void build_phase(uvm_phase phase);
extern virtual function void connect_phase(uvm_phase phase);
extern virtual function void start_of_simulation_phase(uvm_phase phase);
endclass: op_in_agent
function op_in_agent::new(string name, uvm_component parent);
super.new(name, parent);
`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH)
endfunction: new
function void op_in_agent::build_phase(uvm_phase phase);
super.build_phase(phase);
`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH)
//从上级环境中获取agent_config来使用
if(!uvm_config_db#(op_in_agent_config`PARAM_LIST)::get(this, "", "op_in_agent_config", agt_cfg))
begin
`uvm_error("build_phase",$sformatf("agent config not found"))
end
//另一种常用做法,从上一级 的env_config里面获取接口到agent_config当中
// uvm_config_db#(virtual op_in_if)::get(this, "" ,"op_in_if" , vif);
// if(vif == null) begin
// `uvm_fatal("cfg_err",$sformatf(" interface for agent config not found"))
// end
// agt_cfg.vif = vif;
if(agt_cfg.active == UVM_ACTIVE) begin
seqr = op_in_sequencer`PARAM_LIST::type_id::create("seqr", this);
drv = op_in_sequencer`PARAM_LIST::type_id::create("drv", this);
drv.vif = agt_cfg.vif;
end
mon = op_in_monitor`PARAM_LIST::type_id::create("mon", this);
mon.vif = agt_cfg.vif;
endfunction: build_phase
function void op_in_agent::connect_phase(uvm_phase phase);
super.connect_phase(phase);
`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH)
if(agt_cfg.active == UVM_ACTIVE) begin
drv.seq_item_port.connect(seqr.seq_item_export);
end
this.analysis_port = mon.analysis_port;
endfunction: connect_phase
function void op_in_agent::start_of_simulation_phase(uvm_phase phase);
super.start_of_simulation_phase(phase);
`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH)
endfunction: start_of_simulation_phase
agent_config :
里面通常加入一些可配置的变量和接口,用于灵活的配置当前的agent模式和接口。
class op_in_agent_config #(
DATA_WIDTH = 4
) extends uvm_object;
`uvm_object_param_utils(op_in_agent_config`PARAM_LIST)
//Virtual Interface
virtual op_in_if`PARAM_LIST vif ;
//Is the agent active or passsive
uvm_active_passive_enum active = UVM_PASSIVE ; //定义了一个枚举变量active,初始化配置为passive模式。
//-------------------------------------------------------
//Methods
//--------------------------------------------------------
//Standard UVM Methods
extern function new(string name = "op_in_agent_config");
endclass: op_in_agent_config
function op_in_agent_config::new(string name = "op_in_agent_config");
super.new(name);
endfunction
agent_packet:
agent_packet主要是为了集成而做的,用到的地方只需要import即可。即使当前agent有新文件添加或者代码改动也不会影响所有环境的编译,因为所有的改动都存放于agent_packet里了。
//为了参数化使用,定义了参数列表
`define PARAM_LIST #( //定义了一个宏 PARAM_LIST ,传递DATA_WIDTH
DATA_WIDTH \ //宏定义以 \ 换行
)
package op_in_agent_pkg ; //包定义
import uvm_pkg::* ; //导入基础库
`include "op_in_agent_config.svh"
`include "op_in_seq_item.svh"
`include "op_in_driver.svh"
`include "op_in_monitor.svh"
`include "op_in_sequencer.svh"
`include "op_in_agent.svh"
//adapter here
`include "op_in_seq_lib.svh"
endpackage
`include "op_in_if.sv"
`undef PARAM_LIST //取消宏定义,避免污染全局
总结
通用的UVM环境编写有很多种方式,但是思想不变,读者在编写agent时可以主要关注driver ,monitor 和 sequence的编写思想上。