简单的UVM验证平台——来源于张强UVM实战

典型的UVM验证平台
在这里插入图片描述
==phase:==UVM由phase来管理验证平台的运行,这些phase统一以xxx_phase来命名,且都有一个类型为uvm_phase、名字为phase的参数。

objection 机制:uvm中通过objection机制来控制验证平台的关闭。在每个phase中,uvm会检查是否有objection被提起(raise_objection),如果有,那么等待这个objection被撤销(drop_objection)后停止仿真,如果没有,则马上结束当前phase。

raise_objection必须在main_phase中第一个消耗仿真时间的语句之前

config_db机制:在config_db机制中,分为set和get两步操作。set可以理解为寄信,get相当于收信。config_db的set和get函数都有四个参数,

(1)set函数的第四个参数表示要传递哪一个interface。get函数的第四个参数表示要把得到的interface传递给哪一个变量。

(2)set函数的第二个参数表示的时路径索引,uvm通过run_test创建的实例名字为uvm_test_top,路径索引的名字为uvm_test_top

(3)第三个参数为一个字符串,为定义好的标签,发送和接收端应该保持一致

简单的DUT
module  dut(clk,rst_n,rxd,rx_dv,txd,tx_en);
    input clk;
    input rst_n;
    input [7:0] rxd;
    input rx_dv;
    output reg [7:0] txd;
    output reg tx_en;
    
    always @(posedge clk) begin
        if (!rst_n)begin
            txd <= 8'b0;
            tx_en <= 1'b0;
        end
        else begin
            txd <= rxd;
            tx_en <= rx_dv;
        end
    end
endmodule
简单的接口
interface my_if(input clk, input rst_n);
    logic [7:0] data;
    logic valid;
endinterface
顶层top_tb的搭建

通过include 将uvm_macros.svh文件包含进来,里面包含了众多宏定义。

通过import语句将整个uvm_pkg导入到验证平台,只有导入这个库编译器在编译my_driver.sv文件时才会认识其中的uvm_driver等类名。

`timescale 1ps/1ps
`include "uvm_macros.svh"  // include the definations of the pakages in uvm

import uvm_pkg::*;  // including uvm environment
`include "my_transaction.sv"
`include "driver.sv"
`include "my_monitor.sv"
`include "my_scoreboard.sv"
`include "my_sequence.sv"
`include "my_sequencer.sv"
`include "my_agent.sv"
`include "my_model.sv"
`include "my_env.sv"
`include "base_test.sv"

module top_tb;
    reg clk;
    reg rst_n;
    my_if input_if(clk,rst_n);
    my_if output_if(clk,rst_n);
    dut my_dut(
        .clk(clk),
        .rst_n(rst_n),
        .rxd(input_if.data),
        .rx_dv(input_if.valid),
        .txd(output_if.data),
        .tx_en(output_if.valid)
    );
    // three threads in parallel
    initial begin
        run_test("base_test");
    end
    initial begin
        uvm_config_db #(virtual my_if)::set(null,"uvm_test_top.env.i_agt.drv","vif",input_if); // connect the input_if
        uvm_config_db #(virtual my_if)::set(null,"uvm_test_top.env.i_agt.mon","vif",input_if);
        uvm_config_db #(virtual my_if)::set(null,"uvm_test_top.env.o_agt.mon","vif",output_if);
    end
    
    initial begin
        clk = 0;
        forever begin
            # 100 clk = ~clk;
        end
    end

    initial begin
        rst_n =1'b0;
        #1000;
        rst_n = 1'b1;
    end
endmodule

run_test 创建一个名字为uvm_test_top的实例。只有使用了uvm_component_utils宏注册过之后才能使用该功能。

uvm_config_db :用来连接接口

(1)transaction

transaction实际上就是一个包,包含许多参数和变量。

post_randomize是system verilog中提供的一个函数,当某个类的实例的randomize函数被调用后,post_randomize会紧随其后无条件被调用。

`ifndef MY_TRANSACTION_SV
`define MY_TRANSACTION_SV
class my_transaction extends uvm_sequence_item;
    rand bit [47:0] dmac ;          // mac destination address 
    rand bit [47:0] smac ;          // mac source address
    rand bit [15:0] ether_type ;    // the type of mac   
    rand byte  pload[];             // the data it carry
    rand bit [31:0] crc ; 
    // `uvm_object_utils(my_transaction)
    //field_automation 
    `uvm_object_utils_begin(my_transaction)
        `uvm_field_int(dmac,UVM_ALL_ON)
        `uvm_field_int(smac,UVM_ALL_ON)
        `uvm_field_int(ether_type,UVM_ALL_ON)
        `uvm_field_array_int(pload,UVM_ALL_ON)
        `uvm_field_int(crc,UVM_ALL_ON)
    `uvm_object_utils_end
    //constraint 
    constraint pload_cons{
        pload.size >= 46;
        pload.size <= 1500;
    }
   
    function bit [31:0] calc_crc();
        return 32'h0;
    endfunction

    function void post_randomize(); // after the randomize
        crc = calc_crc;
    endfunction
    

    function new(string name = "my_transaction ");
        super.new(name);
    endfunction

endclass
`endif 

transaction 的基类是uvm_sequence_item,应该使用uvm_object_utils来实现factory机制。

field_automation机制: 使用该机制,可以直接调用transaction的copy、compare、print函数,无需自己定义。可以先使用uvm_object_utils_begin 和uvm_object_utils_end来实现my_transaction的factory注册。在这两个宏中间,使用uvm_field宏注册所有字段。pack_bytes可以将transaction中所有的字段变成byte流。使用unpack_bytes函数可以将byte流转换为tr中的各个字段。

(2)driver类的搭建

uvm中的driver应该派生自uvm_driver。uvm_driver的类的new函数有两个参数,一个是string 类型的name,一个是uvm_component类型的parent。

每一个派生自uvm_component或其派生类的类在其new函数中要指明两个参数name和parent。

driver所作的事情几乎都在main_phase中完成。

`ifndef MY_DRIVER_SV
`define MY_DRIVER_SV
class my_driver extends uvm_driver #(my_transaction);
    `uvm_component_utils(my_driver)
    virtual my_if vif;
    function new(string name = "my_driver",uvm_component parent = null); //what is the function of the uvm_parent
        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)) //interface connect
            `uvm_fatal("my_driver","virtual interface must be set for vif!!!")
    endfunction
    extern virtual task main_phase(uvm_phase phase);
    extern virtual task drive_one_pkt(my_transaction tr);
endclass 

task my_driver::main_phase(uvm_phase phase);
    vif.data <= 8'b0;
    vif.valid <= 1'b0;
    while(!vif.rst_n)
        @(posedge vif.clk); 
    while(1)begin
        seq_item_port.get_next_item(req);
        drive_one_pkt(req);
        seq_item_port.item_done();
    end
endtask

task my_driver::drive_one_pkt(my_transaction tr);
    byte unsigned data_q[];
    int data_size;

    data_size = tr.pack_bytes(data_q) /8;
    `uvm_info("my_driver","begin to drive one pkt",UVM_LOW);
    repeat(3) @(posedge vif.clk);
    for(int i =0;i< data_size;i++)begin
        @(posedge vif.clk);
        vif.valid <= 1'b1;
        vif.data <= data_q[i];
    end
    @(posedge vif.clk);
    vif.valid <=1'b0;
    `uvm_info("my_driver","end drive one pkt",UVM_LOW)
endtask


`endif

`uvm_info宏的功能与display语句的功能类似。有三个参数,第一个是字符串,用于把打印的信息归类。第二个参数也是字符串,是具体需要打印的信息。第三个参数则是冗余级别,信息非常重要则设置为UVM_LOW,信息可有可无则设置为UVM_HIGH,介于两者中间则为UVM_MEDIUM。

get_full_name函数可以获得路径索引。

factory机制的实现被集成在了一个宏中:uvm_component_utils。该宏所做的事情非常多,其一就是my_driver登记在UVM内部的一张表。

所有派生自uvm_component及其派生类的类都应该使用uvm_component_utils宏注册。

build_phase也是UVM中内建的一个phase,其在new函数之后main_phase之前执行。在build_phase中主要通过config_db的set和get操作来传递一些数据以及实例化成员变量等。(build_phase的执行顺序遵照从树根到树叶的顺序,当把整棵树的build_phase都执行完毕后,再执行后面的phase)

`uvm_fatal宏是一个类似于uvm_info的宏,只有两个参数,与uvm_info宏的前两个参数的意义一样。

(3)monitor

所有的monitor类应该派生自uvm_monitor,其应该使用uvm_component_utils宏注册。

`ifndef MY_MONITOR_SV
`define MY_MONITOR_SV
class my_monitor extends uvm_monitor;
    virtual my_if vif;
    uvm_analysis_port #(my_transaction) ap;
    `uvm_component_utils(my_monitor)
    function new(string name = "my_monitor",uvm_component parent = null);
        super.new(name,parent);
    endfunction

    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        if(!uvm_config_db#(virtual my_if)::get(this,"","vif",vif))
            `uvm_fatal("my_monitor","virtual interface must be set for vif!!!")
        ap = new("ap",this);
    endfunction

    extern virtual task main_phase(uvm_phase phase);
    extern task collect_one_pkt(my_transaction tr);
endclass

    task my_monitor:: main_phase(uvm_phase phase);
        my_transaction tr;
        while(1) begin
            tr = new("tr");
            collect_one_pkt(tr);
            ap.write(tr);
        end
    endtask

    task my_monitor:: collect_one_pkt(my_transaction tr);
        byte unsigned data_q[$];
        byte unsigned data_array[];
        logic [7:0] data;
        logic valid = 0 ;
        int data_size;
        while(1)begin
            @(posedge vif.clk);
            if(vif.valid)break;
        end
        `uvm_info("my_monitor","begin to collect one pkt",UVM_LOW)
        while(vif.valid)begin
            data_q.push_back(vif.data);
            @(posedge vif.clk);
        end
        data_size = data_q.size();
        data_array = new[data_size];

        for (int i =0;i<data_size;i++)begin
            data_array[i] = data_q[i];
        end
        
        tr.pload = new[data_size - 18];
        data_size = tr.unpack_bytes(data_array)/8;
        `uvm_info("my_monitor","end collect one pkt,print it:",UVM_LOW)
        tr.print();
    endtask
`endif 
(4)agent

所有的agent都要派生自uvm_agent类,且其本身是一个component,应该使用uvm_component_utils宏来实现factory注册。

is_active是uvm_agent的一个成员变量,其有两个值UVM_PASSIVE和UVM_ACTIV。默认为UVM_ACTIVE。UVM_PASSIVE表示不需要驱动信号。

`ifndef MY_AGENT_SV
`define MY_AGENT_SV
class my_agent extends uvm_agent; // encapsulated driver and monitor to represent a protocol
    my_driver   drv;
    my_monitor  mon; 
    my_sequencer sqr;
    uvm_analysis_port #(my_transaction) ap;
    function new(string name,uvm_component parent);
        super.new(name,parent);
    endfunction

    extern virtual function void build_phase(uvm_phase phase);
    extern virtual function void connect_phase(uvm_phase phase);
    `uvm_component_utils(my_agent)
endclass

function void my_agent::build_phase (uvm_phase phase);
    super.build_phase(phase);
    if(is_active == UVM_ACTIVE)begin
        sqr = my_sequencer::type_id::create("sqr",this);
        drv = my_driver::type_id::create("drv",this);
    end
    mon = my_monitor::type_id::create("mon",this);
endfunction

function void my_agent::connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    if(is_active == UVM_ACTIVE)begin
        drv.seq_item_port.connect(sqr.seq_item_export);
    end
    ap = mon.ap;
endfunction
`endif 

在agent中主要任务是在build_phase实例化各个组件,并且在connect将driver和sequencer联系起来。

(5)reference model

在UVM中,通常使用TLM(Transaction Level Modeling)实现component 之间transaction级别的通信。

思考两点,数据是如何发送的?数据是如何接收的?参考systemverilog中的信箱

数据的发送:使用uvm_analysis_port ,这是一个参数化的类,其参数就是需要传递的数据的类型。在声明完之后,还需要在build_phase中将其实例化。write是uvm_analysis_port的一个内建函数,可以使用write函数将数据写入到实例中。

数据的接收:使用uvm_blocking_get_port,这也是一个参数化的类。在声明完成之后也需要在build_phase中进行实例化。通过get任务可以接收传来的transaction。

`ifndef MY_MODEL_SV
`define MY_MODEL_SV
class my_model extends uvm_component;
    
    uvm_blocking_get_port #(my_transaction)  port;
    
    uvm_analysis_port #(my_transaction) ap;
    
    extern  function new(string name,uvm_component parent);
    extern  virtual function void build_phase(uvm_phase phase);
    extern virtual task main_phase(uvm_phase phase);
    `uvm_component_utils(my_model)
endclass

function my_model::new(string name,uvm_component parent);
    super.new(name,parent);
endfunction

function void my_model::build_phase(uvm_phase phase);
    super.build_phase(phase);
    port = new("port",this);
    ap = new("ap",this);
endfunction

task my_model::main_phase(uvm_phase phase);
    my_transaction tr;
    my_transaction new_tr;
    super.main_phase(phase);
    while (1) begin
        port.get(tr);
        new_tr = new("new_tr");
        new_tr.copy(tr);
        `uvm_info("my_model","get one transaction, copy and print it:",UVM_LOW)
        new_tr.print();
        ap.write(new_tr);
    end

endtask
`endif 

在定义好各自端口之后,还需要再my_env中使用FIFO将两个端口联系在一起。

FIFO的类型是uvm_tlm_analysis_fifo,其本身也是一个参数化的类。之后,再connect_phase中将FIFO分别与my_monitor中的analysis_port和my_model中的blocking_get_port相连。

(6)env

所有的env应该派生自uvm_env,使用uvm_component_utils宏来实现factory的注册。

在组件中的实例化都应该使用factory机制,即使用type_name::type_id::create方式。

`ifndef MY_ENV_SV
`define MY_ENV_SV
class my_env extends uvm_env;
    my_agent i_agt;
    my_agent o_agt;
    my_model mdl;
    my_scoreboard scb;
    uvm_tlm_analysis_fifo #(my_transaction) agt_mdl_fifo;
    uvm_tlm_analysis_fifo #(my_transaction) mdl_scb_fifo;
    uvm_tlm_analysis_fifo #(my_transaction) agt_scb_fifo;
    `uvm_component_utils(my_env)
    function new (string name = "my_env",uvm_component parent);
        super.new(name,parent);
    endfunction
    
    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        agt_mdl_fifo = new("agt_mdl_fifo",this);
        mdl_scb_fifo = new("mdl_scb_fifo",this);
        agt_scb_fifo = new("agt_scb_fifo",this);
        i_agt = my_agent::type_id::create("i_agt",this);
        o_agt = my_agent::type_id::create("o_agt",this);
        i_agt.is_active = UVM_ACTIVE;
        o_agt.is_active = UVM_PASSIVE;
        mdl = my_model:: type_id::create("mdl",this);
        scb = my_scoreboard::type_id::create("scb",this);
    endfunction

    function void connect_phase(uvm_phase phase);
        super.connect_phase(phase);
        i_agt.ap.connect(agt_mdl_fifo.analysis_export);
        mdl.port.connect(agt_mdl_fifo.blocking_get_export);

        mdl.ap.connect(mdl_scb_fifo.analysis_export);
        scb.exp_port.connect(mdl_scb_fifo.blocking_get_export);

        o_agt.ap.connect(agt_scb_fifo.analysis_export);
        scb.act_port.connect(agt_scb_fifo.blocking_get_export);
    endfunction

endclass 

`endif

uvm_tlm_analysis_fifo #(my_transaction) agt_mdl_fifo;定义一个FIFO,之后在connect_phase中,将各个组件的通信端口进行连接。

(7)Scoreboard

所有的scoreboard类应该派生自uvm_scoreboard类。

`ifndef MY_SCOREBOARD_SV
`define MY_SCOREBOARD_SV
class my_scoreboard extends uvm_scoreboard;
    my_transaction expect_queue[$];
    uvm_blocking_get_port #(my_transaction) exp_port; //expected value
    uvm_blocking_get_port #(my_transaction) act_port; // actual value

    `uvm_component_utils(my_scoreboard)

    extern function new(string name,uvm_component parent = null);
    extern virtual function void build_phase(uvm_phase phase);
    extern virtual task main_phase(uvm_phase phase);
endclass

function my_scoreboard::new(string name,uvm_component parent);
    super.new(name,parent);
endfunction

function void my_scoreboard::build_phase(uvm_phase phase);
    super.build_phase(phase);
    exp_port = new("exp_port",this);
    act_port = new("act_port",this);
endfunction

task my_scoreboard::main_phase(uvm_phase phase);
    my_transaction get_expect,get_actual,tmp_tran;
    bit result;
    super.main_phase(phase);
    fork
        while(1)begin
            exp_port.get(get_expect);
            expect_queue.push_back(get_expect);
        end
        while(1) begin
            act_port.get(get_actual);
            if(expect_queue.size()>0)begin
                tmp_tran = expect_queue.pop_front();
                result  = get_actual.compare(tmp_tran);
                if(result) begin
                    `uvm_info("my_scoreboard","Compare Successfully",UVM_LOW)
                end
                else begin
                    `uvm_info("my_scoreboard","compare failed",UVM_LOW)
                    $display("the expect pkt is");
                    tmp_tran.print();
                    $display("the actual pkt is");
                    get_actual.print();
                end
            end
            else begin
                `uvm_error("my_scoreboard","received from DUT ,while expect que us is empty");
                $display("the unexpected pkt is");
                get_actual.print();
            end            
        end
    join
endtask
`endif 
(8)sequence机制

sequence机制用于产生激励,在一个规范化的UVM验证平台中,driver只负责驱动transaction,而不负责产生Transaction。sequence机制有两大组成部分,一是sequence,二是sequencer。

sequencer派生自uvm_sequencer,并且使用uvm_component_utils宏来注册到factory中

sequence不属于验证平台的任何一部分,但其与sequencer之间有密切的联系。只有在sequencer的帮助下,sequence产生的transaction才能最终送给driver。

每一个sequence都派生自uvm_sequence。每一个sequence都有一个body任务,当一个sequence启动时,会自动执行body中的代码。

`uvm_do宏,有如下作用(1)创建一个my_transaction实例my_trans (2)将其随机化 (3)最终将其送个sequencer

如何连接driver和sequencer之间的联系通道?uvm_driver中有成员变量seq_item_port。uvm_sequencer中有成员变量,seq_item_export。在agent中使用connect函数将其联系在一起。之后,在driver中可以通过get_next_item任务向sequencer申请新的transaction。

以default_sequence的方式启动sequence:在某个component的build_phase中使用uvm_config_db::set进行设置。

 uvm_config_db #(uvm_object_wrapper)::set(this,"i_agt.sqr.main_phase","default_sequence",my_sequence::type_id::get());
//该函数的第一个和第二个参数是启动sequence的路径,后面两个参数是已经规定好的

在命令行加上+UVM_TEST_NAME= my_case1可以选择仿真的测试案例。

`ifndef MY_SEUQENCE_SV
`define MY_SEUQENCE_SV
class my_sequence extends uvm_sequence #(my_transaction);
    my_transaction my_trans;

    function new(string name = "my_sequence");
        super.new(name);
    endfunction

    virtual task body();
        if(starting_phase != null)
            starting_phase.raise_objection(this);
        repeat(10)begin 
            `uvm_do(my_trans);
        end
        #1000;
        if(starting_phase != null);
            starting_phase.drop_objection(this);
    endtask

    `uvm_object_utils(my_sequence)
endclass
`endif
`ifndef MY_SEQUENCER_SV
`define MY_SEQUENCER_SV
class my_sequencer extends uvm_sequencer #(my_transaction);
    function new (string name,uvm_component parent);
        super.new(name,parent);
    endfunction
    `uvm_component_utils(my_sequencer)
endclass

`endif 
(9)base_test
`ifndef BASE_TEST_SV
`define BASE_TEST_SV
class base_test extends uvm_test;
    my_env env;

    function new(string name = "base_test",uvm_component parent = null );
        super.new(name,parent);
    endfunction

    extern virtual function void build_phase(uvm_phase phase);
    extern virtual function void report_phase(uvm_phase phase);
    `uvm_component_utils(base_test)
endclass

function void base_test::build_phase(uvm_phase phase);
    super.build_phase(phase);
    env = my_env::type_id::create("env",this);
    uvm_config_db #(uvm_object_wrapper)::set(this,"env.i_agt.sqr.main_phase","default_sequence",my_sequence::type_id::get());//start sqeuence
endfunction

function void base_test::report_phase(uvm_phase phase);
    uvm_report_server server;
    int err_num;
    super.report_phase(phase);
    server = get_report_server();
    err_num = server.get_severity_count(UVM_ERROR);

    if(err_num !=0 )begin
        $display("test case failed");
    end
    else begin
        $display("test case passed");
    end
endfunction
`endif 
Makefile
#Makefile for lab1
RTL 	=	../rtl/dut.v
SVTB    =  	./top_tb.sv ./my_if.sv
SEED = 1
# code coverage command
CM = -cm line+cond+fsm+branch+tgl
CM_NAME = -cm_name simv
CM_DIR = -cm_dir ./covdir.vdb
#-cm < coveragetype > :打开对应类型覆盖率,例如 -cm cond+tgl+lin+fsm+path为统计所有覆盖率。
#-cm_name:统计覆盖率文件名字。
#-cm_dir:指定生成.vdb文件目录。
#-cm_log+filename.log:记录仿真过程中log信息。
#-cm_nocasedef: 在统计case语句的条件覆盖率时,不考虑default条件未达到的情况。
#-cm_hier xxx.cfg:通过.cfg文件选择要查看覆盖率的模块或文件。

test: compile run

run: 
	./simv -l simv.log +ntb_random_seed =$(SEED) ${CM} ${CM_NAME} ${CM_DIR}


compile:
	vcs -full64 -cpp g++-4.8 -cc gcc-4.8 -LDFLAGS  -Wl,--no-as-needed -ntb_opts uvm -timescale=1ns/100ps  -sverilog -debug_all  $(SVTB) $(RTL) ${CM} ${CM_NAME} ${CM_DIR}

dve:
	dve &

debug:
	./simv -l simv.log -gui -tbug +ntb_random_seed =$(SEED)

clean:
	rm -rf simv* csrc* *.tmp *.vpd *.key *.log  DVE*  covdir*  vc_*
nuke:clean
	rm -rf *.v* *.sv include .*.lock .*.old DVE* *.tcl *.h
cov:
	dve -full64 -covdir *.vdb &  
urg:
	urg -dir covdir.vdb -report both

  • 14
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

都依爱妃之见

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值