UVM项目笔记——通过寄存器模型实现AHB接口的寄存器读写(内含源码)

目录

1.前言

2.DRIVER实现

2.1 AHB二级流水时序

2.2 “队列错位法”实现driver

2.3 driver代码

2.4 仿真log与波形

2.5 多级流水拓展方法

3.ADAPTER实现

4.SEQUENCE实现

4.1 vseq的实现与代码

4.2 vseqr的实现与代码

4.3 reg_cfg_base_seq父类的实现

4.4 common_reg_cfg_seq子类的实现

4.4 reg_cfg_seq子类的实现

4.4 仿真结果&波形


1.前言

UVM driver在接口协议的实现中起着非常重要的作用,因为它一端处理基于类的事务级sequence,另一端处理基于时钟的信号/引脚级的总线行为。因此,如何实现 UVM driver及其与sequence的同步对于 DUT 和 UVM 环境之间的交互以及避免 UVM driver和sequence之间的任何死锁情况都是至关重要的。

而UVM reg model则提供了强大的前/后门访问寄存器的方式以便于对寄存器进行高效地配置和读取,主要是通过UVM源码中所提供的uvm_reg_map::do_bus_read、uvm_reg_map::do_bus_write方法实现,该方法的实现依赖于sequencer和adapter这2个组件。

其中adpter完全处理基于类的事务级sequence,它能够将uvm_reg_item类和uvm_sequence_item类做相互转译。通过reg2bus方法将寄存器模型能够读懂的uvm_reg_bus_op翻译为总线bus_item事务级sequence,如源码第2009行,调用adapter.reg2bus(rw_access),这一步相当于adapter充当了sequence产生bus_req transaction。

由于环境中指定了寄存器模型使用的sequencer,因此源码中第2014行将bus_req transaction交给该sequencer,随后调用start_item(),finish_item(),从而完成sequencer对sequence的仲裁及传输,确保driver能够井然有序地拿到这些transaction。

环境中打开了auto_predict功能,因此寄存器模型会根据driver返回的读取值,更新寄存器的期望值和镜像值。因此driver中要完成对读写寄存器的反馈逻辑,这一部分通常都是通过driver中的seq_item_port.item_done(bus_req)来完成的,前提是未使用adapter.provides_responses功能,在低速、简单的寄存器操作接口比如I2C、SPI、APB等,这种方式较为常见,因为对寄存器的操作不会涉及到复杂的总线行为,driver只要按顺序调用seq_item_port.get_next_item(bus_req)从sequencer拿到sequence,再将bus_req按照时序驱动到总线上,随后按顺序调用seq_item_port.item_done(bus_req)即可,这样我们是可以直接把返回信息通过req返回的。

但对于复杂的总线协议,例如AHB、AXI等,driver就必须要用put_response(bus_rsp)来返回信息。比如AHB时序中,因为读数据有可能在多拍之后才能从总线上获取,此时master早已经将发送了下一笔transaction,如果采用bus_req来返回信息,那么driver没办法模拟真实的总线行为,不能完成诸如burst类型的传输,此时必须要开启adapter.provides_responses功能。从源码第2024~2030行可以看出,一旦开启该功能,adapter的bus2reg方法会将bus_rsp而非bus_req转译为uvm_reg_bus_op类型,从而使得寄存器模型能够根据读数据正确地更新镜像值和期望值的同时,driver还能模拟真实的AHB总线行为。

本文就是从UVM的源码do_bus_read/do_bus_write出发,采用adapter.provides_responses()功能,结合rm.default_map.set_auto_predict(1)方法,通过reg_model->adapter->sequencer->driver这样的通路,实现了通过寄存器模型读写,产生AHB时序的pin级接口时序的寄存器操作接口方法。

本文将分为几个部分,分别阐述reg_model,sequence,adapter,driver的具体实现方式。

具体的环境架构如下:

2.DRIVER实现

2.1 AHB二级流水时序

在流水线总线协议中,数据传输被分解为两个或多个不同的阶段,这些阶段一个接一个地执行。通常,这些相位涉及总线上不同的信号集。以二级流水为例,driver要驱动的时序如上图。

2.2 “队列错位法”实现driver

要实现driver驱动二级流水,可以巧妙利用队列错位的方式实现。具体实现的流程图如下:

主程序主要由2个forever线程构成:

其中thread1:seq_item_port.try_next_item(req)采用非阻塞的方式从seq不断地得到数据包,得到非null数据包后将其装入队列中,因为seq产生的数据包其地址和控制信号以及写数据信号全部同相位,因此drv从seq得到的数据包其地址相和数据相是对齐的,需要拆包后分离其地址相和数据相,并且将地址和控制信号装入一个队列,数据相单独装入另一个队列。同时要将得到的req打上标签(set_id_info),克隆为rsp,用于反馈寄存器模型读数据hrdata,如果是写操作,当拍反馈rsp(即调用seq_item_port.put_response(rsp)函数),如果是读操作,需要等到vif上正确的hrdata到来后,修改rsp.hrdata,随后反馈rsp。因此,一笔由寄存器发起的read操作,最快也要2拍才能完成(hready为高时)。

thread2:drv_pkt_item(req)负责将得到的数据包按流水线的规则以及HREADY信号的高低发出,并采集读数据hrdata。当该线程被触发后,会根据3种情况判断走不同的分支:

  1. 当前trans是否为第一笔传输;
  2. 当前trans是否为一系列传输的中间传输;
  3. 当前trans是否为一系列传输的最后一笔传输;

如果为第一笔传输,则将其地址和控制相发送至总线上,数据相不发。此刻就完成了地址相和数据相的错位操作。同时将sop_cnt从1改为2,用于标记非第一笔trans。

如果为中间传输或者是最后一笔传输,则根据HREADY信号的高低将错位后的地址和控制相一并发送至总线上。

当数据队列的size>0并且地址队列的size=0时表示此时只剩下HWDATA信号没有驱动,即为最后一笔传输,只需要将写数据hwdata驱动至总线上即可,并且将sop_cnt从2改为1,用于标记下次传输为第一笔trans。

2.3 driver代码

driver的具体实现代码如下:

1.宏定义:

2.driver class:class中定义了成员变量和方法

3.main_phase:在main phase中,有2个forever进程get_pkt_item(got_pkt);drv_pkt_item();;在begin end中顺序执行。

4. thread1: 在get_pkt_item进程中,第一步,首先driver采用try_get非阻塞方法,反复从seqr获取数据,每当得到一笔非空数据包req,利用clone函数将其深复制为rsp,随后采用set_id_info方法将rsp打上标签,放入rsp_q队列中,这一步是为了将每一笔得到的数据反馈给seqr或是寄存器模型的read/write方法。第二步,将数据包拆包,其地址和控制相放入队列haddr_hctrl_q中,同时识别当前数据包是读还是写,如果是写操作,则将其写数据放入数据相队列hwdata_q中,同时立刻返还rsp(调用seq_item_port.put_response),如果是读操作,暂时不返还rsp。上述过程重复执行共2次,队列中最多存放2笔未完成的数据。

5. thread2:在drv_pkt_item()进程中,根据sop_cnt、haddr_hctrl_q队列和hwdata_q队列的size大小来识别当前trans属于第几笔传输,在第一笔传输时,只需将地址和控制相输出至总线上,在随后的传输过程中,根据HREADY信号高低决定地址控制相保留还是更新。在最后一笔传输时,只将数据相传输至总线上,并将hsel拉低。

在thread2的整个过程中,若在地址相发现为有效的读操作,则在数据相将读数据从总线上取得,并更改rsp中的hrdata,随后put_response返回给寄存器 第163~168行的代码对应UVM源码中的2028行此时若不返回rsp,则会造成do_bus_read和driver之间的死锁,仿真会在执行源码的2028行时卡死

7.完整代码:

/*
--==============================================================
-- File name        : reg_cfg_driver.sv
-- Author           : shzhang

-- Date             : Sat Jan  7 10:36:57 CST 2023

-- Abstract         : description of this reg_cfg_driver.sv
--==============================================================
*/
`ifndef _reg_cfg_driver_sv_
`define _reg_cfg_driver_sv_

`define _HREADY hready_m
`define _HRDATA hrdata

`define haddr_hctrl_drv2bus(_HSEL,_HTRANS,_HSIZE,_HWRITE,_HADDR) \
    vif.``_HSEL``   <= temp_haddr_hctrl.``_HSEL``  ; \
    vif.``_HTRANS`` <= temp_haddr_hctrl.``_HTRANS``; \
    vif.``_HSIZE``  <= temp_haddr_hctrl.``_HSIZE`` ; \
    vif.``_HWRITE`` <= temp_haddr_hctrl.``_HWRITE``; \
    vif.``_HADDR``  <= temp_haddr_hctrl.``_HADDR`` ; \

`define hwdata_drv2bus(_HWDATA) \
    vif.``_HWDATA`` <= temp_hwdata; \

`define sample_rdata_func(_HWRITE) \
    this.got_rsp_tmp = this.got_rsp_q.pop_front(); \
    if(this.got_rsp_tmp.``_HWRITE`` == 'h0)begin \
        this.sample_rdata = 1; \
    end \

typedef class reg_cfg_driver;

virtual class reg_cfg_driver_cb extends uvm_callback;//drv callback
    virtual task before_drv_item(reg_cfg_driver drv,reg_cfg_drv_pkt pkt);
    endtask:before_drv_item

    virtual task after_drv_item(reg_cfg_driver drv,reg_cfg_drv_pkt pkt);
    endtask:after_drv_item

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

endclass:reg_cfg_driver_cb

class reg_cfg_driver extends uvm_driver #(reg_cfg_drv_pkt);
    int                      sop_cnt             ;
    uvm_event                reg_cfg_drv_finished;//used for ctrl next seq in vseq 
    virtual reg_cfg_if       vif                 ;//used for data transferring from driver to DUT
    bit                      sample_rdata        ;
    reg_cfg_config           cfg                 ;
    reg_cfg_drv_pkt          got_pkt             ;//pkt that got from seq
    reg_cfg_drv_pkt          got_rsp_q[$]        ;//pkt that got from seq
    reg_cfg_drv_pkt          got_rsp             ;//pkt that got from seq
    reg_cfg_drv_pkt          got_rsp_tmp         ;//pkt that got from seq
    reg_cfg_drv_pkt          haddr_hctrl_q[$]    ;//addr and ctrl signal queue
    logic             [31:0] hwdata_q[$]         ;//wdata queue

    reg_cfg_drv_pkt          temp_haddr_hctrl    ;
    logic             [31:0] temp_hwdata         ;

    `uvm_component_utils_begin(reg_cfg_driver)
        `uvm_field_object(cfg,UVM_ALL_ON)
        `uvm_field_object(got_pkt,UVM_ALL_ON)
        `uvm_field_object(got_rsp,UVM_ALL_ON)
        `uvm_field_object(haddr_hctrl_q[$],UVM_ALL_ON)
        `uvm_field_object(temp_haddr_hctrl,UVM_ALL_ON)
    `uvm_component_utils_end

    `uvm_register_cb(reg_cfg_driver,reg_cfg_driver_cb)


    function new(string name = "reg_cfg_driver",uvm_component parent);
        super.new(name,parent);
    endfunction:new

    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        if (!uvm_config_db #(virtual reg_cfg_if) :: get(this,"","vif",vif)) begin
            `uvm_fatal("NOVIF",{"virtual interface must be set for:",get_full_name(),".vif"})
        end
        if (!uvm_config_db #(reg_cfg_config) :: get(this,"","cfg",cfg)) begin
            `uvm_fatal("NOCFG",{"reg_cfg_config must be set for:",get_full_name(),".cfg"})
        end
        reg_cfg_drv_finished = uvm_event_pool::get_global("reg_cfg_drv_finished");
    endfunction:build_phase

    task reset_phase(uvm_phase phase);
        super.reset_phase(phase);
        reset_process();
    endtask:reset_phase

    extern virtual task main_phase(uvm_phase phase);
    extern virtual function void report_phase(uvm_phase phase);
    extern virtual task reset_process();
    extern virtual task get_pkt_item(reg_cfg_drv_pkt got_pkt);//get pkt from seq
    extern virtual task drv_pkt_item();//drive pkt to AHB bus
    extern virtual task delay_n_cyc(int N);
endclass:reg_cfg_driver

task reg_cfg_driver::main_phase(uvm_phase phase);
    process job_id;
    super.main_phase(phase);
    wait(vif.rstn === 1); // wait reset end,rst is low active
    forever begin
        fork
            begin
                job_id = process::self();
                forever begin
                    get_pkt_item(got_pkt);//forever get_pkt loop
                    drv_pkt_item();//forever drv_pkt loop
                    delay_n_cyc(1);
                end
            end
            begin
                wait(vif.rstn === 0); //detected reset
            end
        join_any //only wait rst could finish
        //kill other threads and wait reset end
        if (job_id != null) begin
            job_id.kill();
        end
        reset_process();
        if (got_pkt != null) begin
            seq_item_port.item_done();
            got_pkt = null;
        end
        wait (vif.rstn === 1);
        repeat (10) @(posedge vif.clk);
    end // forever begin
endtask:main_phase

task reg_cfg_driver::get_pkt_item(reg_cfg_drv_pkt got_pkt);
    repeat(2)begin
        if(hwdata_q.size() < 2)begin
            seq_item_port.try_next_item(got_pkt);
            //seq_item_port.get_next_item(got_pkt);
            if(got_pkt != null)begin
                $cast(this.got_rsp,got_pkt.clone());
                this.got_rsp.set_id_info(got_pkt);
                this.got_rsp_q.push_back(this.got_rsp);

                haddr_hctrl_q.push_back(got_pkt);
                if(got_pkt.hwrite == 1'b1)begin//only recieve the hwdata when hwrite == 1'b1
                    hwdata_q.push_back(got_pkt.hwdata);
                end else begin
                    hwdata_q.push_back('h0);
                end
                `uvm_info("REG_CFG_DRV",$sformatf("thread1: get pkt seq! haddr_hctrl_q.size() = %0d, hwdata_q.size() = %0d", haddr_hctrl_q.size(), hwdata_q.size()),UVM_HIGH)
                seq_item_port.item_done();
                if(this.got_rsp.hwrite == 1)begin
                    seq_item_port.put_response(this.got_rsp);
                end
                got_pkt=null;        
            end
        end
    end
endtask:get_pkt_item

task reg_cfg_driver::drv_pkt_item();
    if(this.sample_rdata == 1)begin
        this.got_rsp_tmp.`_HRDATA = vif.`_HRDATA;
        seq_item_port.put_response(this.got_rsp_tmp);
        this.sample_rdata = 0;
    end
    if((this.sop_cnt == 1) && (haddr_hctrl_q.size() > 0))begin//the first data has been drived
        temp_haddr_hctrl = haddr_hctrl_q.pop_front(); 
        `haddr_hctrl_drv2bus(hsel,htrans,hsize,hwrite,haddr)
        `uvm_info("REG_CFG_DRV","thread2: drv first seq!",UVM_HIGH)
        this.sop_cnt ++;
        `uvm_info("REG_CFG_DRV",$sformatf("thread2: sop_cnt = %0d",sop_cnt),UVM_HIGH)
    end else if((haddr_hctrl_q.size() > 0) && (haddr_hctrl_q.size() < hwdata_q.size()))begin//the middle data has been drived
        `uvm_info("REG_CFG_DRV",$sformatf("thread2: vif.hready = %0d",vif.`_HREADY),UVM_HIGH)
        if(vif.`_HREADY == 1'b1)begin
            temp_haddr_hctrl  = haddr_hctrl_q.pop_front(); 
            temp_hwdata       = hwdata_q.pop_front();
            `sample_rdata_func(hwrite)
        end
        `haddr_hctrl_drv2bus(hsel,htrans,hsize,hwrite,haddr)
        `hwdata_drv2bus(hwdata)
        `uvm_info("REG_CFG_DRV","thread2: drv middle seq!",UVM_HIGH)
    end else if((haddr_hctrl_q.size() == 0) && (hwdata_q.size() == 1))begin//the last data has been drived.
        `uvm_info("REG_CFG_DRV",$sformatf("thread2: vif.hready = %0d",vif.`_HREADY),UVM_HIGH)
        if(vif.`_HREADY == 1'b1)begin
            temp_haddr_hctrl.hsel   = 'h0;
            temp_haddr_hctrl.haddr  = 'h0;
            temp_haddr_hctrl.htrans = 'h0;
            temp_haddr_hctrl.hwrite = 'h0;
            temp_haddr_hctrl.hsize  = 'h0;
            temp_hwdata             = hwdata_q.pop_front() ;
            `sample_rdata_func(hwrite)
        end
        `haddr_hctrl_drv2bus(hsel,htrans,hsize,hwrite,haddr)
        `hwdata_drv2bus(hwdata)
        `uvm_info("REG_CFG_DRV","thread2: drv last seq!",UVM_HIGH)
        this.sop_cnt = 'h1;
        reg_cfg_drv_finished.trigger();
    end
endtask:drv_pkt_item

task reg_cfg_driver::reset_process();
    vif.hsel   <= 'h0;
    vif.haddr  <= 'h0;
    vif.htrans <= 'h0;
    vif.hwrite <= 'h0;
    vif.hsize  <= 'h0;
    vif.hwdata <= 'h0;

    sop_cnt = 'h1;
endtask:reset_process

function void reg_cfg_driver::report_phase(uvm_phase phase);
endfunction:report_phase

task reg_cfg_driver::delay_n_cyc(int N);
    repeat (N) begin @(posedge vif.clk); end
endtask:delay_n_cyc

`undef _HREADY
`undef _HRDATA
`undef haddr_hctrl_drv2bus
`undef hwdata_drv2bus
`undef sample_rdata_func
`endif

2.4 仿真log与波形

2.5 多级流水拓展方法

多级流水同样可以采取该种方法,举个例子,如果是3级流水,只需要在代码中根据haddr_hctrl_q队列和hwdata_q队列的size大小识别到第一笔传输、第二笔传输、中间传输、倒数第二笔传输、最后一笔传输,然后根据HREADY的高低决定是否更新地址和数据相即可。

3.ADAPTER实现

adpter完全处理基于类的事务级sequence,它能够将uvm_reg_item类和uvm_sequence_item类做相互转译。通过reg2bus方法将寄存器模型能够读懂的uvm_reg_bus_op翻译为总线bus_item事务级sequence, 通过bus2reg方法将总线bus_item事务级sequence翻译为寄存器模型能够读懂的uvm_reg_bus_op类型。

adapter的实现就是将reg2bus这个寄存器操作总线的函数和bus2reg这个总线操作读寄存器的函数定义好即可。

4.SEQUENCE实现

在该项目中(如上架构图所示所示),一个标准的testcase执行的流程如下:

  1. 采用sequence -> adapter -> sequencer -> driver -> interface -> cpto_rbus_top 的通路,通过前门的方式配置DUT中的cpto_rbus_top寄存器,从而使CPTO模块工作在某种特定的工作模式;
  2. 配置寄存器完成后,通过sequence -> suquencer -> driver -> interface -> sram 的通路,向DUT的sram中写入cpto待处理(加/解密)的源头数据;
  3. 采用sequence -> adapter -> sequencer -> driver -> interface -> cpto_rbus_top 的通路,通过前门的方式配置DUT中的cpto_rbus_top寄存器中的trigger寄存器,从而让CPTO模块开始启动;

为实现上述标准流程,采用vseq协调3个seq顺序工作,reg_cfg_seq0用于配置cpto_rbus_top中除了trigger以外的寄存器(对应步骤(1)),ahb_sctrl_seq0用于随机或定向地产生CPTO待计算的源头数据(对应步骤(2)),reg_cfg_seq1用于配置cpto_rbus_top中的trigger寄存器(对应步骤(1))。其中reg_cfg_seq0和reg_cfg_seq1的实现需要rm.write/rm.read/rm.set/rm.update等方法结合adapter,将寄存器操作的事务级包转译为AHB事务级包,从而实现对CPTO寄存器的配置,ahb_sctrl_seq0则直接产生AHB事务级包。

4.1 vseq的实现与代码

vseq派生自uvm_sequence,主要在其内例化各个具体的sequence,vseq负责仲裁各个sequence的执行顺序,起指挥、调度的作用。vseq具有seq的基本特性,因此也需要通过`uvm_declare_p_sequencer宏定义一个指向vseqr的p_sequencer句柄。vseq含有一个body任务,但由于vseq本身不产生seq,它只负责协调例化在其中的seq的执行顺序,因此在body函数中,只对3个seq起调度作用。由于3个seq需要对应的drv全部数据都驱动结束后才动作,因此在vseq中声明了uvm_event句柄,负责drv与seq之间的交互问题。

4.2 vseqr的实现与代码

vseqr派生自uvm_sequencer,属于component类型,vseqr中需要例化的seqr句柄类型需要与vseq中例化的sequence类型对应。并在env的connect_phase中通过句柄赋值,将vseqr的句柄指向agent中的seqr。本例中vseq共例化3个sequence,分别为(common_reg_cfg_seq、ahb_sctrl_base_seq、trigger_reg_cfg_seq),但common_reg_cfg_seq和trigger_reg_cfg_seq派生于一个reg_cfg_base_seq,二者采用同一个sequencer——reg_cfg_seqr,而ahb_sctrl_base_seq则采用ahb_sctrl_seqr作为seqr,因此在vseqr中只需要例化这两个句柄即可。此外,vseqr中还需例化一个uvm_reg_block类型的p_rm句柄,用于指向env中的寄存器模型句柄rm。

4.3 reg_cfg_base_seq父类的实现

reg_cfg_base_seq父类中定义了所有子类使用的成员变量和方法包含:

  1. 最重要的用于启动start phase的body()任务;
  2. 配置common寄存器域段并实现随机化的common_reg_exp_randomize()任务;
  3. 配置trigger寄存器的trigger_reg_exp_write()任务;
  4. 实现common寄存器update操作的common_reg_update()任务;
  5. 实现寄存器的exp值和mirror值打印的print_exp_reg_value()和print_mirror_reg_value()函数;

其中,父类中的body()任务什么都不做,只是定义了各子类会用的方法和成员变量。

从该reg_cfg_base_seq父类又派生了两个子类common_reg_cfg_seq和trigger_reg_cfg_seq,两个子类主要对父类的body()方法进行了重写。

代码如下:

1.宏定义:

2.base seq class:

其中reg_cfg_rand_var变量比较关键,其中通过constraint机制实现了对所有寄存器的随机化约束。

3.body:body()中什么都不做。

4:reg_test ();UVM内建方法,内建测试序列,对寄存器进行扫描测试。

5:common_reg_exp_randomize();对寄存器的每个域段执行randomize操作。

6. common_reg_set();对寄存器的每个域段执行set操作。

7.对所有common寄存器进行update;

8.trigger_reg_write();

对trigger寄存器执行写操作。

9. 打印函数print_exp_reg_value();print_mirror_reg_value();

4.4 common_reg_cfg_seq子类的实现

该子类对父类的body()函数进行了重写,主要实现了以下操作:

  1. 通过配置clr寄存器给cpto中的3个中断状态清零;
  2. common寄存器的域段值随机化;
  3. 对common寄存器的域段的exp值进行set;
  4. 打印exp值或mirror值;
  5. 对common寄存器的值进行update;

代码如下:由于派生自父类,因此代码较为整洁。

4.4 reg_cfg_seq子类的实现

该子类只写trigger寄存器。

4.4 仿真结果&波形

一笔标准的seq执行流程如下:

(1)首先通过UVM寄存器模型结合reg_cfg_seq,随机配置cpto除了tigger以外的rbus寄存器,并通过driver以前门的方式驱动到cpto的rbus AHB接口从而写入cpto的寄存器;(如红框中所示)

(2)根据寄存器的配置,ahb_sctrl_seq产生对应模式的激励,即key,ciphertext、plaintext原始数据,并通过driver以前门访问的方式驱动到sram_ctrl的slv0的AHB接口,从而写入sram;(如绿框中所示)

(3)通过reg_cfg_seq,将trigger寄存器写为1,driver会以前门访问的方式将tigger信号写入cpto的rbus寄存器,从而使能cpto;(如蓝框所示)

(4)DUT被trigger后,cpto开始运算,通过sram_ctrl的slv1接口从sram中读取在步骤(2)时写入sram的key、plaintext、ciphertext;(如白框中所示)

(5)cpto根据寄存器配置进行运算,reference model根据寄存器配置进行运算,并将两者的运算结果进行比对。

(6)cpto运算完成,拉高cpto_cal_done中断信号,环境等待cpto运算完成后,结束该次seq。(如黄框中所示)

(7)整个验证流程就是不断重复上述步骤(1)-(6)

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
UVM提供了uvm_reg_backdoor类,用于在测试中访问寄存器的内部实现。这个类可以让我们在测试中使用不同的方式来访问寄存器,以验证寄存器的功能和性能。 uvm_reg_backdoor类主要有两个方法: - `void read(uvm_reg_item rw)`:读取寄存器的值,将结果存储在rw.value中。 - `void write(uvm_reg_item rw)`:写入寄存器的值,将值存储在rw.value中。 其中,`uvm_reg_item`是一个包含寄存器地址、写入/读取值等信息的uvm序列化对象。 要使用uvm_reg_backdoor类,我们需要创建一个新类,继承自uvm_reg_backdoor。在新类的构造函数中,我们需要调用基类的构造函数,并通过该函数将要访问的寄存器作为参数传递。 下面是一个使用uvm_reg_backdoor类的示例: ```systemverilog class my_reg_backdoor extends uvm_reg_backdoor; `uvm_object_utils(my_reg_backdoor) function new(string name = "my_reg_backdoor"); super.new(name); endfunction virtual function void read(uvm_reg_item rw); // 从寄存器中读取值 endfunction virtual function void write(uvm_reg_item rw); // 将值写入寄存器 endfunction endclass ``` 在测试中,我们可以使用uvm_reg_backdoor类的实例来访问寄存器。例如: ```systemverilog my_reg_backdoor my_bd = new; uvm_reg_item rw = new; rw.element = my_reg; rw.kind = UVM_REG; rw.path = UVM_FRONTDOOR; rw.offset = 0; rw.value[0] = 0x1234; my_bd.write(rw); // 从寄存器中读取值 my_bd.read(rw); $display("value = %h", rw.value[0]); ``` 使用uvm_reg_backdoor类可以方便地访问寄存器的内部实现,从而进行更全面和深入的验证

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值