UVM实战_5_UVM中的寄存器模型

本文深入介绍了UVM中的寄存器模型,包括寄存器模型的基本概念、前门与后门访问的区别、后门访问的实现方式,以及如何在验证平台中集成和使用寄存器模型。通过实例展示了如何创建和操作寄存器模型,强调了后门访问在提高效率和应对复杂场景中的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

本章节主要介绍UVM实战第7章寄存器模型的内容


一、寄存器模型简介

通常来说,DUT中会有一组控制端口,通过控制端口,可以配置DUT中的寄存器,DUT可以根据寄存器的值来改变行为,这组控制端口就是寄存器配置总线。
暂时以uvm实战附录B代码为例,后续要改成apb总线控制的rtl代码

module dut(clk,rst_n,bus_cmd_valid,bus_op,bus_addr,bus_wr_data,bus_rd_data,rxd,rx_dv,txd,tx_en);
input          clk;
input          rst_n;
input          bus_cmd_valid;
input          bus_op;
input  [15:0]  bus_addr;
input  [15:0]  bus_wr_data;
output [15:0]  bus_rd_data;
input  [7:0]   rxd;
input          rx_dv;
output [7:0]   txd;
output         tx_en;

reg[7:0] txd;
reg tx_en;
reg invert;

always @(posedge clk) begin
   if(!rst_n) begin
      txd <= 8'b0;
      tx_en <= 1'b0;
   end
   else if(invert) begin
      txd <= ~rxd;
      tx_en <= rx_dv;
   end
   else begin
      txd <= rxd;
      tx_en <= rx_dv;
   end
end

always @(posedge clk) begin
   if(!rst_n) 
      invert <= 1'b0;
   else if(bus_cmd_valid && bus_op) begin
      case(bus_addr)
         16'h9: begin
            invert <= bus_wr_data[0];
         end
         default: begin
         end
      endcase
   end
end

reg [15:0]  bus_rd_data;
always @(posedge clk) begin
   if(!rst_n)
      bus_rd_data <= 16'b0;
   else if(bus_cmd_valid && !bus_op) begin
      case(bus_addr)
         16'h9: begin
            bus_rd_data <= {
   
   15'b0, invert};
         end
         default: begin
            bus_rd_data <= 16'b0; 
         end
      endcase
   end
end
endmodule

如上述代码,控制总线即为bus_op,bus_addr,bus_rd_data,bus_wr_data,bus_cmd_valid等,相同的控制总线如apb总线,而寄存器是invert,也就是通过控制总线来配置寄存器的值,从而改变dut的行为(上述代码33-45)

1.引入寄存器模型

上述的rtl代码,invert寄存器用于控制DUT是否将输入的激励按位取反。在取反的情况下,参考模型需要读取此寄存器的值,如果为1,那么其输出的transaction也需要进行反转。参考模型如何去读此寄存器的值呢?

  • 假设没有寄存器模型:利用config机制分别为virtual sequencer和scoreboard设置一个config_object,在此object设置一个事件,然后在scoreboard中触发,随后启动一个sequence,这个sequence发送一个transaction给bus_driver。sequence读取到寄存器后,再通过config_db传递给参考模型,还需要使用wait_modified来更新数据。--------相当麻烦!!!
  • 有寄存器模型:整个过程可以简化为:reg_model.INVERT_REG.read(status, value, UVM_FRONTDOOR);
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/8f5ae324720e4418a5d10a4d689f202c.png

可以看出寄存器模型的优点:

  1. 可以在任何耗费时间的phase中使用寄存器模型以前门或后门进行访问,还可以在不耗费时间的phase中使用后门访问的方式来读取寄存器的值;(前门后门的概念后面会描述)
  2. 寄存器模型提供一些任务,如mirror,updata,批量完成寄存器模型与DUT中相关寄存器的交互;
  3. 重新定义验证平台与DUT的寄存器接口,使验证人员更好地组织及配置寄存器,简化流程、减少工作量;

2.寄存器模型中的基本概念

在这里插入图片描述
在这里插入图片描述

uvm_reg_field:寄存器模型中的最小单位,个人理解就是域,如上的状态寄存器共有四个域,分别是empty、full、overflow、underflow,注意reserved不是一个域,是预留的空位。
uvm_reg:它比uvm_reg_field高一个级别,但是依然是比较小的单位。这里我理解就是上面的FIFO_STATUS,就是常说的寄存器名字,它包含了好多的域。
uvm_reg_block:它是一个比较大的单位,在其中可以加入许多的uvm_reg,也可以加入其他的uvm_reg_block。一个寄存器模型中至少包含一个uvm_reg_block。
uvm_reg_map:每个寄存器在加入寄存器模型时都有其地址,uvm_reg_map就是存储这些地址,并将其转换成可以访问的物理地址(因为加入寄存器模型中的寄存器地址一般都是偏移地址,而不是绝对地址)。当寄存器模型使用前门访问方式来实现读或写操作时,uvm_reg_map就会将地址转换成绝对地址,启动一个读或写的sequence,并将读或写的结果返回。每个reg_block内部,至少有一个(通常也只有一个)uvm_reg_map.

3.简单的寄存器模型

为上节的dut建立一个简单的寄存器模型,虽然只有一个寄存器invert。建造寄存器模型首先要从uvm_reg派生一个invert类

class reg_invert extends uvm_reg;
    rand uvm_reg_field reg_data;
    virtual function void build();
        reg_data = uvm_reg_field::type_id::create("reg_data");
        // parameter: parent, size, lsb_pos, access, volatile, reset value, has_reset, is_rand, individually accessible
        reg_data.configure(this, 1, 0, "RW", 1, 0, 1, 1, 0);
    endfunction
    `uvm_object_utils(reg_invert)
    function new(input string name="reg_invert");
        //parameter: name, size, has_coverage
        super.new(name, 16, UVM_NO_COVERAGE);//16:这个宽度指的是寄存器中总共的位数。这个数字一般与系统总线的宽度一致。
    endfunction
endclass
  • new函数的写法:第二个参数是要将invert寄存器的宽度作为参数传递给super.new函数。第三个参数是是否要加入覆盖率的支持
  • build函数的写法:1) 这个build不会自动执行,需要手动调用;2) 所有的uvm_reg_field都在这里实例化; 3) reg_data实例化后,要调用reg_data.configure函数来配置这个字段,configure的参数解释如下:

1.第一个参数是此域(uvm_reg_field)的父辈,也就是此域位于哪个寄存器中,这里就是填this
2.第二个参数是此域的宽度,在上节的dut中,invert的宽度为1,所以这里就是1
3.第三个参数是此域的最低为在整个寄存器中的位置,从0开始计数
4.第四个参数表示字段的存取方式,一般有这几种:RO-只读,RW-读写,RC-读清,WC-写清,WO-只写
5.第五个参数表示是否易失的(volatile),这个参数一般不会使用
6.第六个参数表示此域上电复位后的默认值
7.第七个参数表示此域是否有复位,一般的寄存器或者寄存器的域都有上电复位值,因此这里一般都填1
8.第八个参数表示这个域是否可以随机化。主要用于对寄存器进行随机写测试
9.第九哥参数表示这个域是否可以单独存取

上面代码定义好invert寄存器后,需要在一个有reg_block派生的类中将invert类实例化

class reg_model extends uvm_reg_block;
   rand reg_invert invert;
   
   virtual function void build();
      //parameter:parameter name, base_addr, bus width(byte),large/small end,address by byte
      default_map = create_map("default_map", 0, 2, UVM_BIG_ENDIAN, 0);//这里必须用create_map来实现

      invert = reg_invert::type_id::create("invert", , get_full_name());
      invert.configure(this, null, "");
      invert.build(); //手动调用invert的build函数,将invert中的域实例化
      default_map.add_reg(invert, 'h9, "RW");//将此寄存器加入default_map中
   endfunction

   `uvm_object_utils(reg_model)
    function new(input string name="reg_model");
        super.new(name, UVM_NO_COVERAGE);
    endfunction 
endclass
  • build函数的写法:1) 实例化default_map(一个uvm_reg_block中一定要对应一个uvm_reg_map,系统已经有一个声明好的default_map);2) 在此build函数中对所有的寄存器进行实例化;

第一步实例化default map

  1. 第一个参数是名字
  2. 第二个参数是基地址
  3. 第三个参数是系统总线的宽度,这里的单位是byte而不是bit
  4. 第四个参数是大小端
  5. 第五个参数表示能否按照byte寻址

第二步实例化寄存器并调用invert.configure函数,目的是指定寄存器进行后门访问操作时的路径

  1. 第一个参数是此寄存器所在uvm_reg_block的指针,这里填this
  2. 第二个参数是reg_file的指针
  3. 第三个参数是此寄存器的后门访问路径

第三步将此寄存器加入default_map中,uvm_reg_map的作用就是存储所有寄存器的地址,因此必读加入default_map

  1. 第一个参数是要加入的寄存器
  2. 第二个参数是寄存器的地址,这里是’h9
  3. 第三个参数是此寄存器的存取方式

总结:uvm_reg_file是最小的单位,是具体存储寄存器数值的变量,可以直接使用这个类(就是上面提的reg_invert),它得继承于uvm_reg,uvm_reg是一个空壳子,就是纯虚类,不能直接使用,所以它需要派生一个新类(reg_invert)。uvm_reg_block则用于组织大量uvm_reg的大容器。

4.将寄存器模型集成到验证平台中

1) 加入adapter
寄存器的读和写本质是需要通过sequence产生一个uvm_reg_bus_op的变量,这个变量里面包含这读写类型、读写地址、写入的数据等信息,然后这些信息要交给bus_sequencer,随后交给driver,由bus_driver实现最终的前门访问读写操作。但是,这个产生的uvm_reg_bus_op是不能直接给到bus_sequencer的,需要转换一下,这就引入一个转换器:adapter

class my_adapter extends uvm_reg_adapter;
    string tID = get_type_name();

    `uvm_object_utils(my_adapter)

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

   function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
      bus_transaction tr;
      tr = new("tr"); 
      tr.addr = rw.addr;
      tr.bus_op = (rw.kind == UVM_READ) ? BUS_RD: BUS_WR;
      if (tr.bus_op == BUS_WR)
         tr.wr_data = rw.data; 
      return tr;
   endfunction : reg2bus

   function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
      bus_transaction tr;
      if(!$cast(tr, bus_item)) begin
         `uvm_fatal(tID,
          "Provided bus_item is not of the correct type. Expecting bus_transaction")
          return;
      end
      rw.kind = (tr.bus_op == BUS_RD) ? UVM_READ : UVM_WRITE;
      rw.addr = tr.addr;
      rw.byte_en = 'h3;
      rw.data = (tr.bus_op == BUS_RD) ? tr.rd_data : tr.wr_data;
      rw.status = UVM_IS_OK;
   endfunction : bus2reg

endclass : my_adapter

这个转换器定义了两个函数:reg2bus和bus2reg
reg2bus:将寄存器模型通过sequence发出的uvm_reg_bus_op型的变量转换成bus_sequencer能够接受的形式;
bus2reg:为当检测到总线上有操作时,它将收集来的transaction转换成寄存器模型能够接受的形式,以便寄存器模型能够更新相应的寄存器的值
在这里插入图片描述
实际寄存器的读和写流程如上图所示,写流程比较简单,暂且不说;读操作中,读到的数值是如何返回到寄存器模型的呢?
由于总线的特殊性,bus_driver在驱动总线进行读操作时,它也能顺便获取要读的数值,如果它将此值放入从bus_sequencer获得的bus_transaction中时,那么bus_transaction中就会有读取的值,此值经过adapter的bus2reg函数的传递,最终被寄存器模型获取,如上图读操作的虚线所示,完整流程如下:

一,参考模型调用寄存器模型的读任务;
二,寄存器模型产生sequence,并产生uvm_reg_item:rw;
三,产生driver能够接受的transaction:bus_req=adapter.reg2bus(rw);
四,把bus_req交给bus_sequencer;
五,driver得到bus_req后驱动它,得到读取的值,并将读取值放入bus_req[rep]中,调用item_done;
六,寄存器模型调用adapter.bus2reg(bus_req[rep], rw)将bus_req中的读取值传递给rw;
七,将rw中的读数据返回参考模型

如果driver一直发送应答而sequence不收集应答,那么将会导致sequencer的应答队列溢出,UVM在adpter中设置了provide_responses选项,寄存器在调用bus2reg将目标transaction转换成uvm_reg_item时,其传入的参数是rep,而不是req,见上述步骤中括号内[ ];

2) 加入寄存器模型
在base_test中加入寄存器模型:

class base_test extends uvm_test;

   my_env         env;
   my_vsqr        v_sqr;
   reg_model      rm;  //加入的寄存器模型
   my_adapter     reg_sqr_adapter;  //加入的adapter

   function new(string name = "base_test", uvm_component parent = null);
      super.new(name,parent)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值