UVM(Universal Verification Methodology)可以理解为形而上的东西,可以理解为是基于System verilog的一个库,提供一些API调用。
其实没必要把UVM抬的那么高,上升到形而上的层次。因为在实际的IC验证时,对某个复杂DUT的验证时,一般都会拆分成那几个模块。
要对DUT测试,肯定需要先产生测试用例,就是UVM中的sequencer和sequence概念。
产生测试用例之后,肯定需要把测试用例送到DUT端口上,作为激励,就是UVM中的driver的概念。
既然是验证,就是比较DUT是不是对的,既然要比较对与错,就需要DUT的行为模型,就是UVM中的reference model的概念。
既然要对比,可能对比算法比较复杂,一般需要单独出来,就是UVM中scoreboard的概念。
有了DUT的行为模型,要给行为模型激励啊,怎么给呢?直接采集DUT的端口信息就行了,这个工作也独立出来,就是UVM中monitor的概念。
monitor(软件部分)怎么采集DUT(RTL部分)的端口信息呢?用PLI,VPI,DPI,都可以。UVM将这个内容封装了一下,就是uvm_config_db。
这么多拆开独立的部分,他们之间肯定需要通信啊,怎么办呢?用消息队列,有名管道,socket等进行间通信方式都可以,UVM又封装了一下,美其名曰TLM。
从上面可以看出,UVM带给我们的不是形而上的高达上概念(当然,如果你之前在验证时,没有自觉的将不同功能模块分开的话,可能会感觉UVM很高达上,哈哈),而是实实在在的代码,提供了一组API,使我们在做verification时少敲了几行代码。用UVM也有不好的地方,个人感觉运行效率有点低,仿真时间加长。
总之,UVM是无数验证工程师经验的结晶,仿真工具也都支持,所以在做IC验证时尽量用UVM中的API,也可以自己实现TLM,config机制,实现起来也很简单(我实现过)。
之前我们熟悉了一下SVUNIT(http://blog.csdn.net/rill_zhen/article/details/45342999),本小节我们通过一个实验来熟悉一下UVM。
仔细体会本实验中的代码,领会其背后含义之后,UVM就入门了。
要进行验证,必须要有个靶子,就是DUT。本例DUT很简单:“左耳进,右耳出”,吃什么吐什么。
/* * uvm test example * Rill 2015-05-02 */ module Mdut # ( parameter DATA_L = 8 ) ( input clk, input rst_n, IFrx.S rx_if, output tx_dv, output [DATA_L-1:0] txd ); logic tx_dv_r; logic [DATA_L-1:0] txd_r; always_ff @(posedge clk) if(~rst_n) begin tx_dv_r <= 1'b0; txd_r <= DATA_L'b0; end else if(rx_if.vld) begin tx_dv_r <= rx_if.vld; txd_r <= rx_if.data; end assign tx_dv = tx_dv_r; assign txd = txd_r; endmodule //Mdut
/* * uvm test example * Rill 2015-05-02 */ interface IFrx #( parameter DATA_L=8 ) (); logic vld; logic [DATA_L-1:0] data; modport M ( output vld, output data ); modport S ( input vld, input data ); endinterface:IFrx
有了DUT,用UVM那一套东西怎么对DUT进行测试呢?
根据引言中的分析,UVM中的概念不是凭空产生的,一定要搞明白为什么会出现那个概念,是什么需求促使了那个概念的产生。
此外,下面我将直接将代码清单列出,要仔细体会,每一行代码的作用,为什么那么写,换个写法行不行。要做UVM的主人,千万不要被UVM的那些概念牵着鼻子走,否则会很被动。要理解TLM,config机制对RTL design人员可能会有点难度,可以请教公司的软件人员。UVM用多了之后,会发现那些概念都是水到渠成事。
/* * uvm test example * Rill 2015-05-02 */ `ifndef MY_TRANSACTION_SV `define MY_TRANSACTION_SV import uvm_pkg::*; `define DATA_MAX 255 typedef struct packed { logic vld; logic [7:0] data; }trans_t; class my_transaction extends uvm_sequence_item; `uvm_object_utils(my_transaction) rand bit vld; rand bit [7:0] data; constraint c_trans0 { vld dist { 0 :/ 50, 1 :/ 50 }; data dist { 0 :/ 0, [1:100] :/ 50, [101:`DATA_MAX] :/ 50 }; } constraint c_trans1 { data >= 5; } extern function new(string name = "my_transaction"); extern function trans_t gen_tr; endclass function my_transaction::new(string name = "my_transaction"); super.new(); endfunction function trans_t my_transaction::gen_tr; trans_t tr; tr.vld = vld; tr.data = data; return tr; endfunction `endif //MY_TRANSACTION_SV
/* * uvm test example * Rill 2015-05-02 */ `ifndef MY_DRIVER_SV `define MY_DRIVER_SV `include "my_transaction.sv" class my_driver extends uvm_driver; `uvm_component_utils(my_driver) virtual IFrx.M rxif; extern function new(string name = "my_driver",uvm_component parent); extern virtual function void build_phase(uvm_phase phase); extern virtual task main_phase(uvm_phase phase); extern task send_pkg(trans_t pkg); endclass function my_driver::new(string name = "my_driver",uvm_component parent); super.new(nam,parent); endfunction function void my_driver::build_phase(uvm_phase phase); super.build_phase(phase); uvm_config_db #(virtual IFrx.M)::get(this,"","abcdefg",rxif); endfunction task my_driver::main_phase(uvm_phase phase); int i = 0; my_transaction tr; trans_t trans; phase.raise_objection(this); tr = new("tr"); rxif.vld = 1'b0; rxif.data = 8'b0; while(!Ttb.rst_n) @(posedge Ttb.clk); repeat(10) @(posedge Ttb.clk); for(i=0;i<10;i++) begin assert(tr.randomize()); trans = tr.gen_tr(); send_pkg(trans); #0.1 if(rxif.vld) begin `uvm_info("my_driver",$psprintf("%t,data[%d]:0x%h is drived",$time,i,rxif.data),UVM_LOW); end end repeat(10) @(posedge Ttb.clk); rxif.vld = 1'b0; repeat(10) @(posedge Ttb.clk); phase.drop_objection(this); endtask task my_driver::send_pkg(trans_t pkg); @(posedge Ttb.clk); rxif.vld <= pkg.vld; rxif.data <= pkg.data; endtask `endif //MY_DRIVER_SV
/* * uvm test example * Rill 2015-05-02 */ `ifndef MY_MONITOR_I_SV `define MY_MONITOR_I_SV class my_monitor_i extends uvm_monitor; `uvm_component_utils(my_monitor_i) virtual IFrx.S rxif; uvm_analysis_port #(trans_t) port_to_rm;//port to reference model extern function new(string name = "my_monitor_i",uvm_component parent); extern virtual function void build_phase(uvm_phase phase); extern task main_phase(uvm_phase phase); extern task collect_pkg(ref trans_t pkg); endclass function my_monitor_i::new(string name = "my_monitor_i",uvm_component parent); super.new(name,parent); endfunction function void my_monitor_i::build_phase(uvm_phase phase); super.build_phase(phase); uvm_config_db #(virtual IFrx.S)::get(this,"","1234567",rxif); port_to_rm = new("port_to_rm",this); endfunction task my_monitor_i::main_phase(uvm_phase phase); trans_t pkg; while(1) begin collect_pkg(pkg); if(pkg.vld) begin port_to_rm.write(pkg); end end endtask task my_monitor_i::collect_pkg(ref trans_t pkg); @(posedge Ttb.clk); #0.1 if(rxif.vld) begin pkg.vld = 1; pkg.data = rxif.data; `uvm_info("my_monitor_i",$psprintf("mon_i::0x%h",pkg.data),UVM_LOW); end endtask `endif //MY_MONITOR_I_SV
/* * uvm test example * Rill 2015-05-02 */ `ifndef MY_AGENT_I_SV `define MY_AGENT_I_SV `include "driver.sv" `include "monitor_i.sv" class my_agent_i extends uvm_agent; `uvm_component_utils(my_agent_i) my_driver drv; my_monitor_i mon; uvm_analysis_port #(trans_t) port_from_mon; extern function new(string name = "my_agent_i",uvm_component parent); extern virtual function void build_phase(uvm_phase phase); extern virtual function void connect_phase(uvm_phase phase); endclass function my_agent_i::new(string name = "my_agent_i",uvm_component parent); super.new(name,parent); endfunction function void my_agent_i::build_phase(uvm_phase phase); super.build_phase(phase); drv = my_driver::type_id::create("drv0",this); mon = my_monitor_i::type_id::create("mon_i",this); endfunction function void my_agent_i::connect_phase(uvm_phase phase); super.connect_phase(phase); port_from_mon = mon.port_to_rm; endfunction `endif //MY_AGENT_I_SV
/* * uvm test example * Rill 2015-05-02 */ `ifndef MY_MONITOR_O_SV `define MY_MONITOR_O_SV class my_monitor_o extends uvm_monitor; `uvm_component_utils(my_monitor_o) virtual IFrx.M txif; uvm_analysis_port #(trans_t) port_to_sb;//port to scoreboard extern function new(string name = "my_monitor_o",uvm_component parent); extern virtual function void build_phase(uvm_phase phase); extern task main_phase(uvm_phase phase); extern task collect_pkg(ref trans_t pkg); endclass function my_monitor_o::new(string name = "my_monitor_o",uvm_component parent); super.new(name,parent); endfunction function void my_monitor_o::build_phase(uvm_phase phase); super.build_phase(phase); uvm_config_db #(virtual IFrx.M)::get(this,"","7654321",txif); port_to_sb = new("port_to_sb",this); endfunction task my_monitor_o::main_phase(uvm_phase phase); trans_t pkg; while(1) begin collect_pkg(pkg); if(pkg.vld) begin port_to_sb.write(pkg); end end endtask task my_monitor_o::collect_pkg(ref trans_t pkg); @(posedge Ttb.clk); #0.1 if(txif.vld) begin pkg.vld = 1; pkg.data = txif.data; `uvm_info("my_monitor_o",$psprintf("mon_o::0x%h",pkg.data),UVM_LOW); end endtask `endif //MY_MONITOR_O_SV
/* * uvm test example * Rill 2015-05-02 */ `ifndef MY_AGENT_O_SV `define MY_AGENT_O_SV `include "driver.sv" `include "monitor_o.sv" class my_agent_o extends uvm_agent; `uvm_component_utils(my_agent_o) my_monitor_o mon; uvm_analysis_port #(trans_t) port_from_mon; extern function new(string name = "my_agent_o",uvm_component parent); extern virtual function void build_phase(uvm_phase phase); extern virtual function void connect_phase(uvm_phase phase); endclass function my_agent_o::new(string name = "my_agent_o",uvm_component parent); super.new(name,parent); endfunction function void my_agent_i::build_phase(uvm_phase phase); super.build_phase(phase); mon = my_monitor_o::type_id::create("mon_o",this); endfunction function void my_agent_o::connect_phase(uvm_phase phase); super.connect_phase(phase); port_from_mon = mon.port_to_sb; endfunction `endif //MY_AGENT_O_SV
/* * uvm test example * Rill 2015-05-02 */ `ifndef MY_REF_MODEL_SV `define MY_REF_MODEL_SV class my_ref_model extends uvm_component; `uvm_component_utils(my_ref_model) uvm_blocking_get_port #(trans_t) port_in; //from monitor_i uvm_analysiss_port #(trans_t) port_out; //to monitor_o extern function new(string name = "my_ref_model",uvm_component parent); extern function void build_phase(uvm_phase phase); extern task main_phase(uvm_phase phase); endclass function my_ref_model::new(string name = "my_ref_model",uvm_component parent); super.new(name,parent); endfunction function void my_ref_model::build_phase(uvm_phase phase); super.build_phase(phase); port_in = new("port_in",this); port_out = new("port_out",this); endfunction task my_ref_model::main_phase(uvm_phase phase); trans_t tr; port_in.get(tr); `uvm_info("ref_model",$psprintf("rm_from_mon_i:0x%h",tr.data),UVM_LOW); port_out.write(tr); endtask `endif //MY_REF_MODEL_SV
/* * uvm test example * Rill 2015-05-02 */ `ifndef MY_SCOREBOARD_SV `define MY_SCOREBOARD_SV class my_scoreboard extends uvm_scoreboard; `uvm_component_utils(my_scoreboard) uvm_blocking_get_port #(trans_t) port_dut;//from dut port uvm_blocking_get_port #(trans_t) port_rm;//from reference model port extern function new(string name = "my_scoreboard",uvm_component parent); extern function void build_phase(uvm_phase phase); extern task main_phase(uvm_phase phase); endclass function my_scoreboard::new(string name = "my_scoreboard",uvm_component parent); super.new(name,parent); endfunction function void my_scoreboard::build_phase(uvm_phase phase); super.build_phase(phase); port_dut = new("port_dut",this); port_rm = new("port_rm",this); endfunction task my_ref_model::main_phase(uvm_phase phase); trans_t pkg_dut; trans_t pkg_rm; fork while(1) begin port_rm.get(pkg_rm); `uvm_info("sb",%psprintf(sb from rm:0x%h",pkg_rm.data),UVM_LOW); end //==================// while(1) begin port_dut.get(pkg_dut); `uvm_info("sb",%psprintf(sb from dut:0x%h",pkg_dut.data),UVM_LOW); end join endtask `endif //MY_SCOREBOARD_SV
/* * uvm test example * Rill 2015-05-02 */ `ifndef MY_ENV_SV `define MY_ENV_SV `include "agent_i.sv" `include "agent_o.sv" `include "ref_model.sv" `include "scoreboard.sv" import uvm_pkg::*; class my_env extends uvm_env; `uvm_component_utils(my_env) my_agent_i agent_i; my_agent_o agent_o; my_ref_model ref_model; my_scoreboard scoreboard; uvm_tlm_analysis_fifo #(trans_t) agent_i_to_ref_model_fifo; uvm_tlm_analysis_fifo #(trans_t) ref_model_to_scoreboard_fifo; uvm_tlm_analysis_fifo #(trans_t) agent_o_to_scoreboard_fifo; extern function new(string name = "my_env",uvm_component parent); extern function void build_phase(uvm_phase phase); extern function void connect_phase(uvm_phase phase); endclass function my_env::new(string name = "my_env",uvm_component parent); super.new(name,parent); endfunction function void my_env::build_phase(uvm_phase phase); super.build_phase(phase); agent_i = my_agent_i::type_id::create("agent_i0",this); agent_o = my_agent_o::type_id::create("agent_o0",this); scoreboard = my_scoreboard::type_id::create("scoreboard0",this); ref_model = my_ref_model::type_id::create("ref_model",this); agent_i.is_active = UVM_ACTIVE; agent_o.is_active = UVM_PASSIVE; agent_i_to_ref_model_fifo = new("agent_i_to_ref_model_fifo",this); ref_model_to_scoreboard_fifo = new("ref_model_to_scoreboard_fifo",this); agent_o_to_scoreboard_fifo = new("agent_o_to_scoreboard_fifo",this); endfunction function void my_env::connect_phase(uvm_phase phase); super.connect_phase(phase); agent_i.port_from_mon.connect(agent_i_to_ref_model_fifo.analysis_export); ref_model.port_in.connect(agent_i_to_ref_model_fifo.blocking_get_export); ref_model.port_out.connext(ref_model_to_scoreboard_fifo.analysis_export); scoreboard.port_rm.connect(ref_model_to_scoreboard_fifo.blocking_get_export); agent_o.port_from_mon.connect(agent_o_to_scoreboard_fifo.analysis_export); scoreboard.port_dut.connect(agent_o_to_scoreboard_fifo.blocking_get_export); endfunction `endif
/* * uvm test example * Rill 2015-05-02 */ `include "uvm_macros.svh" import uvm_pkg::*; module Ttb; logic clk; logic rst_n; logic tx_dv; logic [7:0] txd; IFrx #(.DATA_L(8)) rxif(); IFrx #(.DATA_L(8)) txif(); Mdut dut0 ( .clk (clk), .rst_n (rst_n), .rx_if (rxif.S), .tx_dv (txif.M.vld), .txd (txif.M.data) ); initial begin clk = 0; rst_n = 0; fork forver #5 clk = ~clk; join_none repeat(10) @(posedge clk); rst_n = 1'b1; uvm_config_da #(virtual IFrx.M)::set(null,"uvm_test_top.agent_i0.drv0","abcdefg",rxif.M); uvm_config_da #(virtual IFrx.S)::set(null,"uvm_test_top.agent_i0.mon_i","1234567",rxif.S); uvm_config_da #(virtual IFrx.M)::set(null,"uvm_test_top.agent_o0.mon_o","7654321",txif.M); run_test("my_env"); end endmodule
Makefile:
/* * uvm test example * Rill 2015-05-02 */ UVM_VERBOSITY = UVM_LOW all:run export UVM_HOME=/tools/home/INC13.10.001/tools/uvm/uvm_lib/uvm_sv IUS = irun -cdslib ./cds.lib -hdlvar ./hdl.var -access rwc -uvm -uvmhome ${UVM_HOME} -f vflist -l irun.log -input run.tcl +UVM_NO_RELNOTES -timascale 1ns/10ps -quiet run: $(IUS) clean: rm -rf INCA* simv* *.log ucli.key
run.tcl:
database -open waves -into waves.shm -default; probe -create -shm -all -variable -depth all; run exit
define myuvm_lib /home/openrisc/my_uvm
define work myuvm_lib
vflist:
-incdir ./src ./src/env.sv ./src/dut.sv ./src/tb.sv ./src/driver.sv ./src/transaction.sv ./src/interface.sv ./src/agent_i.sv ./sec/agent_o.sv ./src/monitor_i.sv ./src/monitor_o.sv ./src/ref_model.sv ./src/scoreboard.sv
一切准备就绪,执行make run,即可看到仿真结果,也可以看到仿真波形(simvision waves.shm/waves.trn &)。
本小节,我们熟悉了UVM的基本使用方法,在以后的使用中可能会逐渐有新的疑问,比如,怎么向driver发送不同类型的transaction?由于DUT行为太复杂(乱序执行),reference model难道也要搞成乱序吗?也要做成周期精确的吗?如果不是乱序的,monitor怎么收集DUT的信息呢?既然是乱序的,scoreboard怎么比较呢?有没有必要用sequencer呢?怎么产生覆盖率高的transaction呢?UVM的不同DUT之间怎么复用呢?怎么让不同transaction自动运行呢?等等这些问题都需要实际解决,那时你会发现,UVM做的工作有点少,还可能再需要增强一点。