IP验证学习之agent编写
目录
前言
interface:
sequence item :
driver:
monitor:
sequence:
注释:
sequencer:
agent:
agent_config :
agent_packet:
总结
前言
这篇文章主要讲述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_cbclocking 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_cbmodoport 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_itemfunction 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);endfunctionvirtual 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_driverfunction op_in_driver::new(string name, uvm_component parent)super.new(name , parent);`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH)
endfunction : newfunction void op_in_driver::build_phase(uvm_phase phase); //也可以在build_phase里面实例化super.build_phase(phase)`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH)
endfunction: build_phasefunction 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 beginseq_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();endendtask : 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);endphase.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_monitorfunction op_in_monitor::new(string name, uvm_component parent)super.new(name, parent) ;`uvm_info("TRACE", $sformatf("%m") , UVM_HIGH)
endfunction: newfunction 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);forkforever beginwhile(`MON_VIF.rst_n !== 1) begin@(`MON_VIF);end@(`MON_VIF);if(`MON_VIF.start) begintr.begin_tr(); //记录事务的开始时间 ,begin_tr() 和 end_tr() 是sequence类自带的方法mon_flag = 1;endelse if(`MON_VIF.ed) begintr.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); //发出去后更新数值endendendforever beginif(mon_flag && ~`MON_VIF.start && `MON_VIF.data_en) begintr.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);endelse begin@(`MON_VIF);endendjoin
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//constrainsconstrain 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_basefunction op_in_seq_base::new(string name = "op_in_seq_base");super.new(name);`uvm_info("TRACE", $sformatf("%m") , UVM_HIGH )
endfunctionfunction void op_in_seq_base::pre_randomize();//......空载endfunctiontask op_in_seq_base::pre_body();`ifdef UVM_VERSION_1_1if(get_parent_sequence() == null && starting_phase != null) begin //如果是顶层序列并且是在某个phase里运行uvm_objection objection = starting_phase.get_objection(); //获取当前序列启动phase的objectionobjection.set_drain_time(this,25ns); //设置objection的排水时间为25nsstarting_phase.raise_objection(this);end`endif
endtasktask op_in_seq_base::post_body();`ifdef UVM_VERSION_1_1if(get_parent_sequence() == null && starting_phase != null) beginstarting_phase.drop_objection(this);end`endif
endtaskclass 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_seqfunction op_in_seq::new(string name = "op_in_seq");super.new(name)`uvm_info("TRACE", $sformatf("%m") , UVM_HIGH)
endfunction : newtask 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 beginmy_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 Methodsextern function new(string name = "op_in_sequencer,uvm_component parent = null);endclassfunction 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 Membersop_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 Methodsextern 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_agentfunction op_in_agent::new(string name, uvm_component parent);super.new(name, parent);`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH)
endfunction: newfunction 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) beginseqr = 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;endmon = op_in_monitor`PARAM_LIST::type_id::create("mon", this);mon.vif = agt_cfg.vif;endfunction: build_phasefunction 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) begindrv.seq_item_port.connect(seqr.seq_item_export);endthis.analysis_port = mon.analysis_port;
endfunction: connect_phasefunction 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 Interfacevirtual op_in_if`PARAM_LIST vif ;//Is the agent active or passsiveuvm_active_passive_enum active = UVM_PASSIVE ; //定义了一个枚举变量active,初始化配置为passive模式。//-------------------------------------------------------//Methods//--------------------------------------------------------//Standard UVM Methodsextern function new(string name = "op_in_agent_config");endclass: op_in_agent_configfunction 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_WIDTHDATA_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的编写思想上。