IP验证学习之env集成编写
目录
前言
寄存器同步机制:
env集成思想:
env_config:
reference model:
DPI:
总结
前言
env中包含的组件比较多,读者对于其中的组件想必也是有大概的了解的,有关概念在前文的UVM入门基础中阐述了,这里就直接进入话题了。
本文重点讲述的是寄存器同步的思想以及env常规集成方法,读者可以细致体会一下。
寄存器同步机制:
编写env之前我们首先要确定内的含有的组件以及连接方式,接着就可以着手先写一个粗略的框架再细致的打磨细节了。
现在假设我们的DUT内部存在两个寄存器,一个是apb配置寄存器,通过apb总线来配置DUT的工作模式以及参数等。
另一个是DUT内部实际工作的寄存器,这个寄存器与apb配置寄存器属于不同的时钟域,DUT通过这个实际工作寄存器来进行配置。
那么DUT实际同步过程可能是:
APB配置阶段:UVM测试平台通过APB总线写入到UDT的apb配置寄存器DUT会从APB从机接口(Slave)接收写入的数据,将其更新内部配置寄存器。
同步触发:可能通过以下方式触发同步:
软件触发:CPU写入一个特定的 update 寄存器中,DUT采样update寄存器。
硬件触发:模块在特定时钟沿(如 posedge clk
)自动采样配置寄存器。同步逻辑将配置寄存器的值复制到实际工作寄存器中
模块中使用工作寄存器:DUT的功能模块从工作寄存器读取配置值,而不是直接读取APB配置寄存器(这样可以避免跨时钟域问题或中间状态)。
为了验证这种寄存器同步机制,同步寄存器孕育而生,其具体的验证机制为:
同步寄存器模型:
在 UVM中的建立同步寄存器模型,用于与DUT的实际工作寄存器比较,这样可以验证同步逻辑的正确性。
同时跟踪APB配置寄存器的值,模拟同步过程,生成预期的工作寄存器值,这是寄存器同步 参考模型的作用。
好了,了解完上述的同步流程后,我们可以开始编写env环境了,有关内部的组件这里不做介绍了。
如图是一个常见的env内部常见组件。
env集成思想:
集成架构简图:
其中agt_cfg可以在agent里例化,也可以在上层例化,包括virtual sequencer等组件,需要读者自行考量合适的UV,验证架构。
下述代码中并没有实例化 env_cfg,其原因在于env_cfg通常在test层实例化,在代码中仅仅只是声明了句柄,并没有创建该实例。
class logic_op_env extends uvm_env;`uvm_component_utils(logic_op_env)//--------------------------------------//Data Member//--------------------------------------apb_shared_cfg apb_cfg ; //SVT APB 的config ,作为备份也将它放到env中。
logic_op_env_config env_cfg; //管理整个环境的组件配置和变量//----------------------------------------// Component Member//----------------------------------------//scb fifo inst//refm fifo inst//tlm inst//env inst//agent inst
op_in_agent#(`DATA_WIDTH) input_agt;
apb_basic_env apb_env; //例化apb的环境,作为子env使用
op_out_agent#(`DATA_WIDTH) output_agt;//virtual sequencer inst
logic_op_vsequencer logic_op_vseqr; //例化一个virtual sequencer,控制全局的sequencer,和virtual sequence 搭配使用//scb inst
logic_op_scoreboard#(`DATA_WIDTH,`DATA_WIDTH) logic_op_scb; //定义了一个带参数的记分板实例//参考模型
logic_sync_reg_refm logic_sync_reg_rm ; //同步寄存器机制的参考模型实例
logic_op_refm#(`DATA_WIDTH,`DATA_WIDTH) logic_op_rm; //DUT的参考模型实例//function cov inst
logic_op_func_cov cov; //例化覆盖率组件,覆盖率组件需要自己编写//寄存器模型
string hdl_path = "logic_op_tb_top.u_logic_op" ; //定义了一个字符串变量hdl_path,用于存储DUT的寄存器路径
logic_reg_model rgm; //定义寄存器模型的实例//--------------------------------------------------------//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);endclass: logic_op_envfunction logic_op_env::new(string name, uvm_component parent);super.new(name, parent);`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH)
endfunction: newfunction void logic_op_env::build_phase(uvm_phase phase);super.build_phase(phase);`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH)if(!uvm_config_db #(logic_op_env_config):: get(this, " " , "logic_op_env_config", env_cfg))begin //从base_test或者上级env中获取当前env的env_cfg`uvm_error("build_phase", "environment config not found")end//获取VIP的configif (env_cfg.apb_cfg!=null) begin //如果检测到env_cfg里面没有apb_cfguvm_config_db#(apb_shared_cfg)::set(this, "apb_env", "cfg" , env_cfg.apb_cfg) ; //就在当前env里为子环境apb_env设置apb_cfg(如果当前env是主env的情况)endelse beginapb_cfg = apb_shared_cfg::type_id_create("apb_cfg" , this); //如果当前env_cfg检测到了apb_cfg,就需要实例化uvm_config_db#(apb_shared_cfg)::set(this, "apb_env", "cfg" , apb_cfg) ; //实例化完成后为子环境apb_env设置apb_cfgend//建立寄存器模型if(!uvm_config_db#(logic_reg_model)::get(this, "" , "logic_reg_model", rgm) && env_cfg.rgm == null) //test或者上级env中获取不到寄存器模型或者env_config里的寄存器模型是空指针,就要自己做实例化begin`uvm_info("GETRGM" , " no top_down RGM hangle is assigned" ,UVM_LOW)rgm = logic_reg_model::type_id_create({get_full_name(), ".logic_reg_model"}, this) ; //实例化寄存器模型env_cfg.rgm = rgm ; //保证env_cfg里的寄存器模型和当前实例化的一样`uvm_info("NEWRGM", "created rgm instance locally" , UVM_LOW)endelse if(env_cfg.rgm != null) begin //检测到env_cfg里面有寄存器模型rgm = env_cfg.rgm; //保证env_cfg里的寄存器模型与当前实例化的一样end//建立子寄存器模型,这个子寄存器模型用于同步检测,读者可以理解为同步后的寄存器模型if(env_cfg.sync_rgm == null) begin //检测到env_cfg里没有同步寄存器模型就要自己实例化env_cfg.sync_rgm = logic_reg_model::type_id_create({get_full_name(), ".sync_rgm"}, this) ; env_cfg.sync_rgm.build(); //调用寄存器模型的build()方法完成寄存器模型的构建env_cfg.sync_rgm.lock_model(); //锁定寄存器模型,禁止再修改endif(rgm.get_parent() == null) begin //如果当前寄存器模型是顶层寄存器模型,不是子寄存器模型rgm.build() ;rgm.add_hdl_path(hdl_path) ;rgm.lock_model() ;rgm.reset();rgm.amp.set_auto_predict(0) ;enduvm_config_db#(uvm_reg_block)::set(this, "apb_env.apb_master_env.master", " apb_regmodel1", rgm) ; //因为是集成vip,所以连接寄存器模型使用uvm_config_db,如果是自己手写的,还需要关联sequencer并使用adapter与DUT连接。//sub_env reg_model1 connect
//Example:
//if(env_cfg.has_sub_env) begin
// env_cfg.sub_env_cfg.rgm = rgm.sub_env_blk; //将寄存器模型的子块sub_env_blk赋值给sub_env_cfg里面的寄存器模型
// env_cfg.sub_env_cfg.sync_rgm = env_cfg.sync_rgm.sub_env_blk;
//end//scb fifo new//refm fifo new//viltual sequencer 实例化
if(env_cfg.has_logic_op_vseqr) beginlogic_op_vseqr = logic_op_vsequencer::type_id::create("logic_op_vseqr", this);
end//env exists or not //agent exists or not
if(env_cfg.has_input_agt) beginuvm_config_db #(op_in_agent_config#(DATA_WIDTH))::set(this, $sformatf("input_agt"), "op_in_agent_config", env_cfg.input_agt_cfg); //在当前env环境中配置agt_cfg到agent里input_agt = op_in_agent#(`DATA_WIDTH)::type_id::create($sformatf("input_agt"), this);
endif(env_cfg.has_output_agt) beginuvm_config_db #(op_out_agent_config#(DATA_WIDTH))::set(this, $sformatf("output_agt"), "op_out_agent_config", env_cfg.output_agt_cfg); //在当前env环境中配置agt_cfg到agent里output_agt = op_out_agent#(`DATA_WIDTH)::type_id::create($sformatf("output_agt"), this);
endif(env_cfg.has_apb_agt) beginapb_env = apb_basic_env::type_id::create("apb_env" , this); //实例化子环境apb_env
end//参考模型实例化
if(env_cfg.has_logic_op_refm) beginlogic_op_rm = logic_op_refm#(`DATA_WIDTH,`DATA_WIDATH)::type_id_create($sformatf("logic_op_rm"), this) ;
endlogic_sync_reg_rm = logic_sync_reg_refm::type_id_create($sformatf("logic_sync_reg_rm"), this);//scoreboard 实例化
if(env_cfg.has_logic_op_scoreboard) beginlogic_op_scb = logic_op_scoreboard#(`DATA_WIDTH,`DATA_WIDTH)::type_id_create($sformatf("logic_op_scb"), this) ;
end//refm config//function coverage config
if(env_cfg.has_functional_coverage) begincov = logic_op_func_cov::type_id::create("cov" , this);
endendfunction: build_phasefunction void logic_op_env::connect_phase(uvm_phase phase);super.connect_phase(phase);`uvm_info("TRACE" , $sformatf("%m") , UVM_HIGH)// reg_model connect//function coverage connect
//if(env_cfg.has_functional_coverage) begin
// xxx.agt.analysis_port.connect(cov.cov_imp);
//end//v_seqr connect //tlm connect //v_seqr config
if(env_cfg.has_logic_op_vseqr && env_cfg.has_input_agt == 1 && env_cfg.input_agt_cfg.active == UVM_ACTIVE) beginlogic_op_vseqr.input_seqr = input_agt.seqr;
endif(env_cfg.has_logic_op_vseqr && env_cfg.has_apb_agt == 1) beginlogic_op_vseqr.apb_seqr = apb_env.apb_master_env.master.sequencer;
endif(env_cfg.has_logic_op_vseqr && env_cfg.has_output_agt == 1 && env_cfg.output_agt_cfg.active == UVM_ACTIVE) beginlogic_op_vseqr.output_seqr = output_agt.seqr;
end//连接apb_vip的接口到寄存器模型的接口
if(env_cfg.has_apb_agt) begin apb_env.apb_master_env.slave[0].monitor.item_observed_port.connect(logic_sync_reg_rm.reg_ fifo.analysis_export)
end //refm connect
if(env_cfg.has_logic_op_refm) begininput_agt.analysis_port.connect(logic_op_rm.op_in1_fifo.analysis);
end//scoreboard
if(env_cfg.has_logic_op_scoreboard) beginoutput_agt.analysis_port.connect(logic_op_scb.acutal_op_out1_fifo.analysis_export) ;logic_op_rm.op_out1_ap.connect(logic_op_scb.golden_op_out1_fifo.analysis_export) ;
endendfunction: connect_phase
看以看到上述的env环境中使用了两个参考模型和一个寄存器模型,这个寄存器模型还包括了子寄存器模型。不过上述的env环境主要是为了集成,因此寄存器模型实例化写得复杂,实际上一般的env环境只需集成好各组件即可,所有的配置通常都会在env_config中配置。
此外有的env里还会例化上adapter(转换器),用于寄存器模型传输数据,详细的传输过程在前文中已有讲述。
env_config:
env_config里面存放了所有有关env和agent的配置,有的还包括了接口配置等。所有的配置都放到env_config里面主要是为了方便集成者修改环境。
class logic_op_env_config extends uvm_object;`uvm_object_utils(logic_op_env_config)//virtual interfacevirtual clock_if#(300 , 0) sys_clk; //同步时钟,给dut使用virtual clock_if#(100 , 0) apb_clk; //apb总线时钟,给apb_vip使用virtual reset_if#(100) sys_rst; //同步复位接口,给dut使用virtual reset_if#(100) apb_rst; //apb复位接口,给apb_vip使用//用户自定义中断程序process main_process; //定义了一个进程句柄//func_cov activebit has_functional_coverage = 0;//refm activebit has_logic_op_refm = 1;//scoreboard activebit has_logic_op_scoreboard = 1;//virtual sequencer activebit has_logic_op_vseqr = 1 ;//have env or not //have agent or notbit has_input_agt = 1;bit has_output_agt = 1;bit has_apb_agt = 1;//add env_config//add agent configop_in_agent_config#(`DATA_WIDTH) input_agt_cfg ; //在env_config里面例化agent_config,实现env_config全局控制apb_shared_cfg apb_cfg;op_out_agent_config#(`DATA_WIDTH) output_agt_cfg;//register modellogic_reg_model rgm;logic_reg_model sync_rgm;extern function new(string name = "logic_op_env_config");extern virtual function config_env(string name = "LOGIC_OP");//vip config functionextern virtual function config_apb(); //针对VIP的配置函数endclassfunction logic_op_env_config::new(string name = "logic_op_env_config");super.new(name) ;//create env config//create agent configinput_agt_cfg = op_in_agent_config#(`DATA_WIDTH)::type_id::create("in[ut_agt_cfg") ;apb_cfg = apb_shared_cfg::type_id::create("apb_cfg") ;output_agt_cfg = op_out_agent_config#(`DATA_WIDTH)::tyep_id::create("output_agt_cfg");endfunction: new function logic_op_env_config::config_env(string name = "LOGIC_op");`uvm_info("CONFIG_ENV", "starting config environment", UVM_MEDIUM)//get interface to agent configif(!uvm_resource_db #(virtual op_in_if#(`DATA_WIDTH))::read_by_name("interface_pool", {name,"_input_if"}, input_agt_cfg.vif)) begin //从全局资源库中读取接口,这个资源库在顶层命名为interface_pool`uvm_fatal("VIF_NOT_FOUND",{"Fail to get ", name , "_input_if from resource_db: interface_pool"})endif(!uvm_resource_db #(virtual op_out_if#(`DATA_WIDTH))::read_by_name("interface_pool", {name,"_output_if"}, output_agt_cfg.vif)) begin`uvm_fatal("VIF_NOT_FOUND",{"Fail to get ", name , "_output_if from resource_db: interface_pool"})endif(!uvm_resource_db #(virtual reset_if#(100))::read_by_name("interface_pool", {name,"_sys_rst"}, sys_rst)) begin`uvm_fatal("VIF_NOT_FOUND",{"Fail to get ", name , "_sys_rst from resource_db: interface_pool"})endif(!uvm_resource_db #(virtual reset_if#(100))::read_by_name("interface_pool", {name,"_apb_rst"}, apb_rst)) begin`uvm_fatal("VIF_NOT_FOUND",{"Fail to get ", name , "_apb_rst from resource_db: interface_pool"})endif(name =="LOGIC_OP") begin //如果当前env是主env//agent configurationinput_agt_cfg.active = UVM_ACTIVE;output_agt_cfg.active = UVM_PASSIVE;//End agent configurationend//call vip config functionconfig_apb(); //调用定义好的apb配置函数`indef PREFIX`define PREFIX LOGIC_OP`endif//sub environment configuration`undef PREFIXendfunction//vip function
function logic_op_env_config::config_apb()apb_cfg.master_cfg.uvm_reg_enable = 1;apb_cfg.master_cfg.apb4_enable = 0;apb_cfg.master_cfg.apb3_enable = 0;endfunction
reference model:
参考模型未必都是用sv编写的,更多复杂的参考模型是用外部函数导入的,导入一般使用DPI接口。
基本的参考模型需要实现:
1.等待agent里传输过来的transaction
2.获取寄存器模型的配置信息(前后门访问)
3.根据获取到的数据和配置,实现DUT的功能。
4.进行数据输出,将要输出的数据通过tlm接口传输出去到其他组件。
class logic_op_refm extends uvm_component;`uvm_component_utils(logic_op_refm)//获取env_cfg里的配置信息logic_op_env_config env_cfg;op_in_seq_item op_in1_trans; //接受到的数据流,也就是agent输入的transactionop_out_seq_item op_out1_exp_trans; //用于输出transaction,这个transaction和out_agent输出的事务格式是一致的。op_out_seq_item op_out1_exp_trans_clone; //用于将处理出来的结果在复制一份后输出,起到延时作用,可用可不用。uvm_tlm_analysis_fifo #(op_in_seq_item) op_in1_fifo; //例化tlm接口,这里使用的是fifo,主要用于获取输入uvm_analysis_port #(op_out_seq_item) op_out1_ap; //例化ap接口,这里使用广播接口,也可以使用其他接口。......endclassfunction logic_op_refm::new(string name = "logic_op_refm" , uvm_component parent);super.new(name,parent);op_in1_fifo = new("op_in1_fifo", this);op_out1_ap = new("op_out1_ap", this) ;op_in1_trans = op_in_seq_item::type_id::create("op_in1_trans");op_out1_exp_trans = op_out_seq_item::type_id::create("op_out1_exp_trans");endfunction: newfunction void logic_op_refm::build_phase(uvm_phase phase);super.build_phase(phase);//获取env_config里面的配置if(~uvm_config_db#(logic_op_env_config)::get(this, "*", "logic_op_env_config", env_cfg))`uvm_error("CFGERR" , "environment config is not set to refm! ")endfunction: bulid_phasetask logic_op_refm::run_phase(uvm_phase phase);...forever begin
] op_in1_fifo.get(op_in1_trans) ; //获取输入数据op_out1_exp_trans.data = op_in1_trans.data1 & op_in1_trans.data2; //实现DUT的功能,这里假设DUT的功能就是将两个输入数据与一下$cast(op_out1_exp_trans_clone, op_out1_exp_trans.clone()); //复制一份在传出去,防止复制前数据被修改导致传出去的数据错误op_out1_ap.write(op_out1_exp_trans_clone) ; //传出数据endendtask
DPI:
实际中的参考模型不一定非得要用sv,更多的会用其他语言,比如C语言,那么如何将C语言编写的模型导入到我们的参考模型中使用呢?
首先需要在C语言侧定义cmodel函数,这个过程中需要注意接口的数据类型。
下面是一个极为简单的C模型代码,include用于交互使用。
#include <svdpi.h>
void logic_op_dpi (int data1 , int data2, int sel , int *data) {if(sel == 0) *data = data1 & data2;else if(sel == 1)*data = data1 | data2;else if(sel == 2)*data = data1 ^ data2;else if(sel == 3)*data = data1 ^~ data2;}
在sv侧需要定义对应名字的函数,这个函数需要和C模型一样有相同的返回值和接口类型
import "DPI-C" function void logic_op_dpi(input int data1, input int data2, input int sel, output int data) ;
如何引入仿真?
1.可以随着sv一起加入到编译中,若每次都要修改C模型都需要编译。
2. 使用gcc编译成xxx.so文件,仿真选项加入-sv_ilb xxx。默认路径可以任意选择。
总结
env环境的集成主要是通过env_config配置集成的,读者拿到一个自己不熟悉的环境时首先看config里的东西有哪些,这样可以快速了解当前验证环境并集成出来。