当前位置: 首页 > article >正文

【UVM】寄存器模型

寄存器模型的优势

  1. sequence复用性高,方便对 DUT 中寄存器进行读写;
  2. 提供了后门访问方式,可以不耗时的获取寄存器的值;
  3. 可以很方便的对寄存器的 coverage 验证点的收集

寄存器模型基本概念

寄存器模型概念作用
uvm_reg_field寄存器模型中的最小单位
uvm_reg比uvm_reg_field高一个级别,一个寄存器中至少包含一个uvm_reg_field
uvm_reg_block比uvm_reg高一个级别,可以加入许多的uvm_reg,也可以加入其他的uvm_reg_block。一个寄存器模
型中至少包含一个uvm_reg_block。
uvm_reg_map

存储寄存器的偏移地址,并将其转换成可以访问的物理地址

在每个reg_block内部,至少有一个(通常也只有一个)uvm_reg_map。

uvm_reg_file用于区分不同的hdl路径

简单的寄存器模型

实例

  • uvm_reg:
class my_reg extends uvm_reg;
    rand uvm_reg_field data;
    `uvm_object_utils(my_reg)
    virtual function void build();
        data = uvm_reg_field::type_id::create("data");
        //parameter:    parent, size, lsb_pos, access, volatile, reset value, has_reset, is_rand, individually accessible
        data.configure(this,  16,  0,  "RW",  1,   0,   1,    1,    0);
    endfunction
    function  new(input string name="my_reg");
        //parameter: name, size, has_coverage
        super.new(name, 16, UVM_NO_COVERAGE);
    endfunction
endclass
  • uvm_reg_block:
class my_regmodel extends uvm_reg_block;
    rand my_reg version;
    function void build();
        default_map = create_map("default_map", 0, 2, UVM_LITTLE_ENDIAN, 0);
        version = my_reg::type_id::create("version", , get_full_name());
        version.configure(this, null, "version");
        version.build();
        default_map.add_reg(version, 16'h47, "RW");
    endfunction
    `uvm_object_utils(my_regmodel)
    function new(input string name="my_regmodel");
        super.new(name, UVM_NO_COVERAGE);
    endfunction
endclass 

步骤总结

  1. 从uvm_reg派生一个寄存器类:
    1. 声明build函数,并在其实例化uvm_reg_field类;

      build函数负责所有uvm_reg_field的实例化

    2. 调用configure函数,配置上述域

      configure字段的含义:
      参数一,是此域的父辈,也就是此域位于哪个寄存器中,即是 this;
      参数二,是此域的宽度;
      参数三,是此域的最低位在整个寄存器的位置,从0开始计数;
      参数四,表示此字段的存取方式,共支持25种;
      参数五,表示是否是易失的(volatile),这个参数一般不会使用;
      参数六,表示此域上电复位后的默认值;
      参数七,表示此域时都有复位;
      参数八,表示这个域是否可以随机化;
      参数九,表示这个域是否可以单独存取

  2. 在一个由reg_block派生的类中将上述寄存器配置:
    1. 声明一个build函数

      build函数作用:实现所有寄存器的实例化

    2. default_map实例化;

      调用 create_map 函数完成 default_map 的实例化,default_map = create_map(“default_map”, 0, 2, UVM_LITTLE_ENDIAN, 0)
      第一个参数,表示名字;
      第二个参数,表示该 reg block 的基地址;
      第三个参数,表示寄存器所映射到的总线的宽度(单位是 byte,不是 bit );
      第四个参数,表示大小端模式;
      第五个参数,表示该寄存器能否按 byte 寻址。

    3. 调用configure函数;

      configure ( uvm_reg_block blk_parent, uvm_reg_file regfile_parent = null, string hdl_path = "" )
      第一个参数,表示所在 reg block 的指针;
      第二个参数,表示 reg_file 指针;
      第三个参数,表示寄存器后面访问路径 - string 类型

    4. 调用build函数将域实例化;
    5. 将此寄存器加入default_map

      default_map.add_reg (version, 16'h47, "RW") ;
      第一个参数,表示要添加的寄存器名;
      第二个参数,表示地址;
      第三个参数,表示寄存器的读写属性。

复杂的寄存器模型

 实例

class global_blk extends uvm_reg_block;
  ...
endclass

class buf_blk extends uvm_reg_block;
  ...
endclass

class mac_blk extends uvm_reg_block;
  ...
endclass

class reg_model extends uvm_reg_block;

  rand global_blk gb_ins;
  rand buf_blk bb_ins;
  rand mac_blk mb_ins;

  virtual function void build();
    default_map = create_map("default_map", 0, 2, UVM_BIG_ENDIAN, 0);
    gb_ins = global_blk::type_id::create("gb_ins");
    gb_ins.configure(this, "");
    gb_ins.build();
    gb_ins.lock_model();
    default_map.add_submap(gb_ins.default_map, 16'h0);

    bb_ins = buf_blk::type_id::create("bb_ins");
    bb_ins.configure(this, "");
    bb_ins.build();
    bb_ins.lock_model();
    default_map.add_submap(bb_ins.default_map, 16'h1000);

    mb_ins = mac_blk::type_id::create("mb_ins");
    mb_ins.configure(this, "");
    mb_ins.build();
    mb_ins.lock_model();
    default_map.add_submap(mb_ins.default_map, 16'h2000);
  endfunction

  `uvm_object_utils(reg_model)

  function new(input string name="reg_model");
    super.new(name, UVM_NO_COVERAGE);
  endfunction

endclass

步骤总结

  •  第一步是先实例化子reg_block。

  • 第二步是调用子reg_block的configure函数(如果需要使用后门访问,则在这个函数中要说明子reg_block的路径,这个路径不是绝对路径,而是相对于父reg_block来说的路径)

  • 第三步是调用子reg_block的build函数。

  • 第四步是调用子reg_block的lock_model函数。

  • 第五步则是将子reg_block的default_map以子map的形式加入父reg_block的default_map中

寄存器模型集成到环境

寄存器模型转换到总线上操作

  •  adapter的作用:
    • reg2bus:其作用为将寄存器模型通过sequence发出的uvm_reg_bus_op型的变量转换成bus_sequencer能够接受的形式,
    • bus2reg:其作用为当监测到总线上有操作时,它将收集来的transaction转换成寄存器模型能够接受的形式,以便寄存器模型能够更新相应的寄存器的值
class 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_trans action")
            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 : adapter

在验证平台中使用寄存器模型

 实例

  • base_test:加入REG_MODEL
class base_test extends uvm_test;
        my_env env;
        my_vsqr v_sqr;
        reg_model rm;
        adapter reg_sqr_adapter;
        …
    endclass
    function void base_test::build_phase(uvm_phase phase);
        super.build_phase(phase);
        env = my_env::type_id::create("env", this);
        v_sqr = my_vsqr::type_id::create("v_sqr", this);
        rm = reg_model::type_id::create("rm", this);
        rm.configure(null, "");
        rm.build();
        rm.lock_model();
        rm.reset();
        rm.set_hdl_path_root("top_tb.my_dut");
        reg_sqr_adapter = new("reg_sqr_adapter");
        env.p_rm = this.rm;
     endfunction
    function void base_test::connect_phase(uvm_phase phase);
        super.connect_phase(phase);
        v_sqr.p_my_sqr = env.i_agt.sqr;
        v_sqr.p_bus_sqr = env.bus_agt.sqr;
        v_sqr.p_rm = this.rm;
        rm.default_map.set_sequencer(env.bus_agt.sqr, reg_sqr_adapter);
        rm.default_map.set_auto_predict(1);
    endfunction

步骤总结

  1. 定义 reg_model和reg_sqr_adapter;
  2. 在build_phase中将其实例化
  3. 配置reg_model

    第一是调用configure函数,其第一个参数是parent block,由于是最顶层的reg_block,因此填写null,第二个参数是后门访问路径
    第二是调用build函数,将所有的寄存器实例化。
    第三是调用lock_model函数,调用此函数后,reg_model中就不能再加入新的寄存器了
    第四是调用reset函数,如果不调用此函数,那么reg_model中所有寄存器的值都是0,调用此函数后,所有寄存器的值都将变为设置的复位值

  4. 在connect_phase中,通过set_sequencer函数设置adapter的bus_sequencer,并将default_map设置为自动预测状态

寄存器模型中的访问

前门访问

定义

  • 前门访问操作就是通过寄存器配置总线(如APB协议、OCP协议、I2C协议等)来对DUT进行操作。前门访问操作只有两种:读操作和写操作。

方法函数

  • read
    extern virtual task read(output uvm_status_e            status,
                             output uvm_reg_data_t          value,
                             input uvm_path_e               path = UVM_DEFAULT_PATH,
                             input uvm_reg_map              map = null,
                             input uvm_sequence_base        parent = null,
                             input int                      prior = -1,
                             input uvm_object               extension = null,
                             input string                   fname = "",
                             input int                      lineno = 0);
  • write
    extern virtual task write(output uvm_status_e           status,
                             input uvm_reg_data_t           value, 
                             input uvm_path_e               path = UVM_DEFAULT_PATH,
                             input uvm_reg_map              map = null,
                             input uvm_sequence_base        parent = null,
                             input int                      prior = -1,
                             input uvm_object               extension = null,
                             input string                   fname = "",
                             input int                      lineno = 0);
  • 参数说明:
    • 第一个是uvm_status_e型的变量,这是一个输出,用于表明操作是否成功;
    • 第二个是读/写的数值;
    • 第三个是读/写的方式,可选UVM_FRONTDOOR和UVM_BACKDOOR 

后门访问

定义

不消耗仿真时间(即$time打印的时间)而只消耗运行时间的操作为后门访问

实现方式

  1. 使用interface进行后门访问操作
  2. DPI+VPI实现后门访问
    函数作用
    uvm_hdl_read(string path, uvm_hdl_data_t value);后门读
    uvm_hdl_deposit(string path, uvm_hdl_data_t value);

    后门写

    可被信值覆盖

    uvm_hdl_force(string path, uvm_hdl_data_t value);

    force信号

    在release前不能被覆盖

    uvm_hdl_release(string path);release信号
    uvm_hdl_check_path(string path);检查HDL路径是否存在
    uvm_hdl_release_and_read(string path, uvm_hdl_data_t value);更新信号release后的值
    uvm_hdl_force_time(string path, uvm_hdl_data_t value, time force_time);force某个信号为特定值之后一段时间后再释放
  3. 寄存器模型中的后门访问函数
  • 实现前提:设置好configure函数的第三个路径参数;设置好根路径hdl_root
    class reg_model extends uvm_reg_block;
        …
        virtual function void build();
            counter_high.configure(this, null, "counter[31:16]");
            counter_low.configure(this, null, "counter[15:0]");
        …
        endfunction
    …
    endclass
    
    function void base_test::build_phase(uvm_phase phase);
        rm = reg_model::type_id::create("rm", this);
        rm.configure(null, "");
        rm.build();
        rm.lock_model();
        rm.reset();
        rm.set_hdl_path_root("top_tb.my_dut");
    endfunction
  • UVM_BACKDOOR形式的read和write:会在进行操作时模仿DUT的行为
  • peek/pook:完全不管DUT的行为
task uvm_reg::poke(output uvm_status_e status,
                input uvm_reg_data_t value,
                input string kind = "",
                input uvm_sequence_base parent = null,
                input uvm_object extension = null,
                input string fname = "",
                input int lineno = 0);

task uvm_reg::peek(output uvm_status_e status,
                output uvm_reg_data_t value,
                input string kind = "",
                input uvm_sequence_base parent = null,
                input uvm_object extension = null,
                input string fname = "",
                input int lineno = 0);

前后门访问区别

区别前门访问后门访问
时间上由于是真实的物理操作,因此会 消耗仿真时间不消耗仿真时间
调试上任何前门访问都会 有波形文件,方便调试没有波形文件 的记录,调试难度增加
应用上能验证 总线协议本身 是否正确

大规模寄存器的 快速初始化

能够操作 只读寄存器

注入故障

寄存器模型对DUT的模拟

期望值与镜像值

  • 镜像值(mirrored value):与DUT保持同步的值
  • 期望值(desired value):期望DUT改变的值
/*设置期望值*/
p_sequencer.p_rm.invert.set(16'h1);
/*获取期望值*/
value = p_sequencer.p_rm.invert.get();
/*获取镜像值*/
value = p_sequencer.p_rm.invert.get_mirrored_value();
/*检查1镜像值和期望值是否一致,不一致则更新*/
p_sequencer.p_rm.invert.update(status,UVM_FRONTDOOR);

 常用操作及其对期望值和镜像值的影响

操作影响
read&write更新期望值和镜像值(二者相等)
peek&poke更新期望值和镜像值(二者相等)
get&set

set操作会更新期望值,但是镜像值不会改变

get操作会返回寄存器模型中当前寄存器的期望值

update检查寄存器的期望值和镜像值是否一致,如果不一致,那么就会将期望值写入DUT中,并且更新镜像值,使其与期望值一致
randomize

期望值将会变为随机出的数值,镜像值不会改变。一般和update一起使用

只有当configure时将其第八个参数设置为1时支持此功能

mirror更新期望值和镜像值
predict更新镜像值和期望值

寄存器模型的高级用法

  • auto predict:寄存器模型会更新寄存器的镜像值和期望值
    rm.default_map.set_auto_predict(1);
  • mirror:读取DUT中寄存器的值并将它们更新到寄存器模型;可以在uvm_reg和uvm_reg_block级别被调用
    task uvm_reg::mirror(output uvm_status_e status,
        input uvm_check_e check = UVM_NO_CHECK,
        input uvm_path_e path = UVM_DEFAULT_PATH,
    …);
    //第二个参数可选:UVM_CHECK和UVM_NO_CHECK,代表若镜像值与期望值不一致,是否更新前给出错误提示
  • predict:更新镜像值,但是同时又不对DUT进行任何操作
    function bit uvm_reg::predict (uvm_reg_data_t value,
        uvm_reg_byte_en_t be = -1,
        uvm_predict_e kind = UVM_PREDICT_DIRECT,
        uvm_path_e path = UVM_FRONTDOOR,
    …);
    //第一个参数表示要预测的值
    //第二个参数是byte_en,默认-1的意思是全部有效
    //第三个参数是预测的类型,可选参数:
    typedef enum {
        UVM_PREDICT_DIRECT,
        UVM_PREDICT_READ,
        UVM_PREDICT_WRITE
    } uvm_predict_e;
    //第四个参数是后门访问或者是前门访问
  • get_root_blocks
  • get_reg_by_offset

寄存器模型内建sequence

内建reg测试seq:

  1. uvm_reg_hw_reset_seq:
    • 功能:检查reg_model中寄存器的复位值与实际RTL是否一致
    • 测试级别:uvm_reg_block/uvm_reg
    • 原理:
      • 先reset reg_modle 。将reg_modle中的镜像值和期望值复位
      • 判断是否在外部设置了哪些 reg_block /reg不需要进行 reset 测试
      • 对所有需要进行测试的 reg 通过前门的方式读回DUT的硬件值,再与 reg_model 的 mirror 值进行对比,如果不一致,证明DUT的硬件复位值与reg_modle 的寄存器复位值不一致
    • 使用方式:
      uvm_reg_hw_reset_seq hw_reset_seq; //声明
      
      hw_reset_seq = new("hw_reset_seq"); //例化
      hw_reset_seq.model = XXX_RAL.xxx_reg; //与要被检查的寄存器模型相连
      hw_reset_seq.start(null); //启动
      /*这些内建sequence本质上并没有放到某个具体的sequencer执行,它只是用了sequence执行时调用body()的机制而已,因而我们传递任何的参数给sequence的start()只需要满足参数类型需求就行了,具体传递的值是多少并不重要*/
      
      
    • 设置reg不测试:
      uvm_resource_db#(bit)::set({"REG::",regmodel.blk.get_full_name(),".*"},
                                 "NO_REG_TESTS", 1, this);
      uvm_resource_db#(bit)::set({"REG::",regmodel.blk.reg.get_full_name(),".*"},
                                 "NO_REG_HW_RESET_TEST", 1, this);
      //reg_block/reg均可使用NO_REG_TESTS和NO_REG_HW_RESET_TEST
      //NO_REG_TESTS和NO_REG_HW_RESET_TEST区别在于前者针对所有的内建sequence都排除,而NO_REG_HW_RESET_TEST仅仅针对的是uvm_reg_hw_reset_seq这一单一sequence
  2. uvm_reg_single_bit_bash_seq
    • 功能:通过前门读写实现对寄存器读写访问域每个bit的遍历操作
    • 测试级别:uvm_reg
    • 原理:对每个可读可写域分别写入1和0并读出后座比较
    • 设置reg不测试:
  3. uvm_reg_bit_bash_seq
    • 功能:通过前门读写实现对所有寄存器读写访问域每个bit的遍历操作
    • 测试级别:uvm_reg_block
    • 原理:对包含所有uvm_reg都执行 <uvm_reg_bit_bash_seq> 序列
    • 注意事项:仅支持RW类型,对于其他类型如RO,RC这些需要exclude掉
    • 设置reg不测试:
  4. uvm_reg_single_access_seq
    • 功能:用来检查寄存器映射的有效性
    • 测试级别:uvm_reg
    • 原理:先从前门写入寄存器,而后从后门读回值对比;然后反过来后门写入再用前门读回,确保得到的结果值与镜像值匹配
    • 注意事项:
      • 要求寄存器的hdl路径完成映射
      • 没有可用后门的寄存器,或者只包含只读字段的寄存器,或者字段具有未知访问策略的寄存器,都不能被测试
    • 设置reg不测试:
  5. uvm_reg_access_seq
    • 功能:检查寄存器的读写
    • 测试级别:uvm_reg_block
    • 原理:对包含所有uvm_reg都执行 <uvm_reg_single_access_seq> 序列,来验证一个块内所有寄存器的可访问性
    • 注意事项: 需要在配置ral时,添加寄存器的层级路径,方便seq找到后门。
      //二者地址拼接
      XXX_RAL.set_hdl_path_root("xxx")
      xxx.configure(this,"")
    • 设置reg不测试:

  6.  uvm_reg_share_access_seq
    • 功能:用来检查所有可能访问寄存器路径的有效性
    • 测试级别:uvm_reg
    • 原理:先在一个地址映射中写入共享寄存器,然后通过其他所有可读的地址映射以及后门接口读取它,确保得到的值与镜像值一致
    • 设置reg不测试:
  7. uvm_reg_mem_hdl_paths_seq
    • 功能:检查hdl路径正确性

内建mem测试seq:

  1. uvm_mem_single_walk_seq:
  2. uvm_mem_walk_seq:
  3. uvm_mem_single_access_seq:
    1. 注意事项:需要存储模型的hdl路径已经指定
  4. uvm_mem_access_seq:
    1. 功能:
    2. 设置mem不测试:
      uvm_resource_db#(bit)::set({"REG::",rm.get_full_name(),".*"}, "NO_REG_TESTS", 1, this);
      uvm_resource_db#(bit)::set({"REG::",rm.get_full_name(),".*"}, "NO_MEM_TESTS", 1, this);
      uvm_resource_db#(bit)::set({"REG::",rm.invert.get_full_name(),".*"},"NO_MEM_ACCESS_TEST", 1, this);
  5. uvm_mem_shared_access_seq:


http://www.kler.cn/a/540446.html

相关文章:

  • git如何把多个commit合成一个
  • AMD 8845HS 780M核显部署本地deepseek大模型的性能
  • 数据结构——【二叉树模版】
  • 了解网络层
  • Json-RPC框架项目(一)
  • Gitlab中如何进行仓库迁移
  • opencv:基于暗通道先验(DCP)的内窥镜图像去雾
  • fastjson2学习大纲
  • init的service 启动顺序
  • 基于 gitee 的 CI/CD
  • 球弹跳高度的计算(信息学奥赛一本通-1085)
  • 【JavaScript】this 指向由入门到精通
  • HTML标题标签(<h1>、<h2>、<h3>)的正确使用策略与SEO优化指南
  • 网络安全 — 安全架构
  • 实现双向数据绑定
  • 局域网使用Ollama(Linux)
  • 智慧校园与理工大学:信息技术在高等教育中的应用
  • 使用Python爬虫获取淘宝商品评论API接口数据
  • 前瞻技术解密:未来生活的改变与机遇
  • 1-portal认证功能
  • CPLD实现SPI通信
  • 使用XMLHttpRequest发送带查询参数的 GET 请求并动态展示数据
  • [LLM面试题] 指示微调(Prompt-tuning)与 Prefix-tuning区别
  • ndk 编译opencv(去除libandroid.so mediandk依赖)
  • 单片机复杂项目的软件分层设计
  • 构建jdk17包含maven的基础镜像