HelloWorld | SV实验0 | SV实验1

UVM入门和进阶实验0

介绍

UVM系列实验学习流程:

  • 如何搭建验证框架
  • 验证组件之间的连接和通信
  • 如何编写测试用例,继而完成复用和覆盖率收敛

UVM实验0与SV实验0思想一样,让大家通过简单 的实验要求,在轻松愉悦的气氛下,踏出 UVM 世界的第一步,大体步骤如下:

  • 懂得如何编译UVM代码
  • 理解 SV 和 UVM 之间的关系
  • 了解 UVM 验证顶层盒子与SV验证顶层盒子之间的联系
  • 掌握启动 UVM 验证的必要步骤

上面四个步骤对应四个独立的文件

内容

1 UVM编译

本节视频内容主要介绍了uvm编译相关的内容,使用到的源码为:uvm_compile.sv

1)直接编译uvm_compile.sv

image-20230226204159218

2)运行仿真

image-20230226204748662

发现Loading了sv语言标准和uvm_pkg

image-20230226202510715

为什么不用编译uvm_pkg?

因为Questasim,默认编译到Library里面了,如下图所示,mtiUvm(默认版本uvm1.1,mti意思是mentor)

mtiUvm里面包括两个库:

  • uvm开源库:uvm_pkg(我们uvm_compile.sv中import的是这个 )
  • Questa的UVM定制部分库:questa_uvm_pkg

image-20230226204407682

uvm_pkg编译

在qustasim帮你编译好了,其他eda中类似vcs你需要自己编译一次

3)run -all

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
run -all
# ----------------------------------------------------------------
# UVM-1.1d
# (C) 2007-2013 Mentor Graphics Corporation
# (C) 2007-2013 Cadence Design Systems, Inc.
# (C) 2006-2013 Synopsys, Inc.
# (C) 2011-2013 Cypress Semiconductor Corp.
# ----------------------------------------------------------------
#
# *********** IMPORTANT RELEASE NOTES ************
#
# You are using a version of the UVM library that has been compiled
# with `UVM_NO_DEPRECATED undefined.
# See http://www.eda.org/svdb/view.php?id=3313 for more details.
#
# You are using a version of the UVM library that has been compiled
# with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
# See http://www.eda.org/svdb/view.php?id=3770 for more details.
#
# (Specify +UVM_NO_RELNOTES to turn off this notice)
#
# UVM_INFO verilog_src/questa_uvm_pkg-1.2/src/questa_uvm_pkg.sv(215) @ 0: reporter [Questa UVM] QUESTA_UVM-1.2.3
# UVM_INFO verilog_src/questa_uvm_pkg-1.2/src/questa_uvm_pkg.sv(217) @ 0: reporter [Questa UVM] questa_uvm::init(+struct)
# UVM_INFO D:/Projects/Learn/luke/v2.2-uvm-project/lab0/uvm_compile.sv(9) @ 0: reporter [UVM] Hello, welcome to RKV UVM training!
# UVM_INFO D:/Projects/Learn/luke/v2.2-uvm-project/lab0/uvm_compile.sv(11) @ 1000: reporter [UVM] Bye, and more gifts waiting for you!

2 SV与UVM之间的关系:ClassBrowser

本节视频内容主要演示了View->ClassBrowser中各个工具的使用效果,使用到的源码为:sv_class_inst.sv、uvm_class_inst.sv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

module uvm_class_inst;

import uvm_pkg::*;
`include "uvm_macros.svh"

class top extends uvm_component;
`uvm_component_utils(top)
function new(string name = "top", uvm_component parent = null);
super.new(name, parent);
`uvm_info("UVM_TOP", "SV TOP creating", UVM_LOW)
endfunction
endclass


initial begin
top t;
`uvm_info("UVM_TOP", "test started", UVM_LOW)
t = new("t", null);
`uvm_info("UVM_TOP", "test finished", UVM_LOW)
end

endmodule

locals中的值:@top@1

  • @top:该实例的类
  • @1:该实例类的第几次实例化

image-20230226205431953

class interface:

image-20230226213003007

class tree:

image-20230226212506169

class graph:

image-20230226212923590

  • graph比较大,不仅包括了文件中定义的top类
  • 一般查看类的继承关系用class interface就够了

3 UVM验证顶层与SV验证顶层的对比

使用到的源码为:uvm_test_inst.sv

更刚才没有太多的变化,就是加了个package,再用uvm_test和run_test()改为uvm的建立方式,并使用objection机制

主要内容:

  • objection机制与所有phase结束则退出仿真
  • 仿真退出会打印统计的uvm_info内容
  • 查看内部成员的另外一种方法

1)raise_objection:

image-20230226220354013

执行这个没有一开始就raise_objection的例子,则执行到14行断点后会自动退出仿真,最终显示0时刻就结束了仿真。所以run_phase必须一开始就执行raise_objection

image-20230226220458215

2)仿真退出会打印统计的uvm_info内容

打印统计报告

3)可以通过点击示例,直接在objects里面看内部成员,而不用locals了(路科大哥你是不是一开始说的就有问题,什么locals只能看软件)

image-20230226220745101

要点总结

1.双顶层:

  • 验证环境顶层:uvm_root
  • 硬件顶层:uvm_test_inst

image-20230226220637976

2.timescale缺失的情况

没有设置timescale默认时间单位为1ns/1ns,所以ps在仿真里面不被识别了,等同于#0

image-20230226220214415

3.questasim中的uvm_pkg的编译(见本节1.1 uvm_pkg的编译)

questasim默认编译,其他eda(如vcs)中需要手动编译uvm_pkg

4.uvm_pkg的导入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//uvm_test_inst.sv
package test_pkg;
import uvm_pkg::*;
`include "uvm_macros.svh"

class top extends uvm_test;
`uvm_component_utils(top)
function new(string name = "top", uvm_component parent = null);
super.new(name, parent);
`uvm_info("UVM_TOP", "SV TOP creating", UVM_LOW)
endfunction
task run_phase(uvm_phase phase);
phase.raise_objection(this);
`uvm_info("UVM_TOP", "test is running", UVM_LOW)
phase.drop_objection(this);
endtask
endclass

endpackage

module uvm_test_inst;

import uvm_pkg::*;
`include "uvm_macros.svh"
import test_pkg::*;


initial begin
`uvm_info("UVM_TOP", "test started", UVM_LOW)
run_test("top");
`uvm_info("UVM_TOP", "test finished", UVM_LOW)
end

endmodule
  • 无论在什么地方,我们的验证顶层都需要下面两句,来将UVM包导入进来:
1
2
import uvm_pkg::*;
`include "uvm_macros.svh"
  • 我们可以看到验证顶层是在一个package里面的,并且分别定义了一个测试用例和run_test()的testbench

UVM入门和进阶实验1

介绍

将带领大家了解下面主要几个部分:

  • 工厂的注册、创建和覆盖机制
  • 域自动化以及 uvm object 的常用方法
  • phase 机制(uvm_comp相对于uvm_object独有的)
  • config 机制
  • 消息管理

uvn实验1代码相对简单,讲的是一些机制的内容(类似sv实验0~3的部分),uvm实验2进入主线学习uvm结构

内容

1 工厂的注册、创建和覆盖机制

1)基本内容

factory_mechanism.sv与factory_mechanism_ref.sv

命令:vsim -novopt -classdebug +UVM_TESTNAME=object_create work.factory_mechanism

内容:comp/object的工厂的注册、创建和覆盖机制

2)注册

UVM世界中的注册一共有哪几种方式呢?只有两种用来注册的宏,请你记住它们:

  • `uvm_object_utils(T)
  • `uvm_component_utils(T)

3)创建

uvm的工厂创建方式有三种:

  • (推荐)通用方法:类名::type_id::create("t2", this);
  • uvm_factory方法:
    • f.create_object_by_type()f.create_component_by_type()
    • f为uvm_factory句柄,获得方式:uvm_factory f = uvm_factory::get();
  • object/component自带方法:
    • create_object()create_component()

uvm的工厂创建代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class object_create extends top;
trans t1, t2, t3, t4;
`uvm_component_utils(object_create)
function new(string name = "object_create", uvm_component parent = null);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
uvm_factory f = uvm_factory::get(); // get singleton factory
super.build_phase(phase);
t1 = new("t1"); // direct construction
t2 = trans::type_id::create("t2", this); // common method
void'($cast(t3,f.create_object_by_type(trans::get_type(), get_full_name(), "t3"))); // factory method
void'($cast(t4,create_object("trans", "t4"))); // pre-defined method inside component
endfunction
endclass

class component_create extends top;
unit u1, u2, u3, u4;
`uvm_component_utils(component_create)
function new(string name = "component_create", uvm_component parent = null);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
uvm_factory f = uvm_factory::get(); // get singleton factory
super.build_phase(phase);
u1 = new("u1"); // direct construction
u2 = unit::type_id::create("u2", this); // common method
void'($cast(u3,f.create_component_by_type(unit::get_type(), get_full_name(), "u3", this))); // factory method
void'($cast(u4,create_component("unit", "u4"))); // pre-defined method inside component
endfunction
endclass

4)factory重载

uvm_object->trans->bad_trans

uvm_component->unit->big_unit

factory重载:回去看UVM实战第八章!

重载和创建的顺序:一定要先重载,后创建!否则重载不发挥作用!

object的重载(只能set_type_override_by_type系列,我猜的)

1
2
3
4
5
6
7
8
9
10
class object_override extends object_create;
`uvm_component_utils(object_override)
function new(string name = "object_override", uvm_component parent = null);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
set_type_override_by_type(trans::get_type(), bad_trans::get_type());
super.build_phase(phase);
endfunction
endclass

component的重载(能set_type_override_by_type又能set_type_override,我猜的)

1
2
3
4
5
6
7
8
9
10
class component_override extends component_create;
`uvm_component_utils(component_override)
function new(string name = "component_override", uvm_component parent = null);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
set_type_override("unit", "big_unit");
super.build_phase(phase);
endfunction
endclass

2 域自动化以及 uvm object 的常用方法

1)基本内容

uvm_object_methods.sv与uvm_object_methods_ref.sv

有很多小的实验点:

  • 实验 2.1:学习使用域自动化的宏方法,可参考红宝书表 10.2 和表 10.3
  • 实验 2.2:请学习uvm_object:compare()方法
  • 实验 2.3:请尝试掌握uvm_pkg中常见的一些全局控制对象,例如 uvm_default_comparer,该对象可参考`uvm_comparer类提供的方法。具体还包括哪 些全局对象,可以参考红宝书图 10.7 uvm_pkg 的全局对象
  • 实验 2.4:请学习自定义uvm object的一些回调函数,例如do. compare(), 并且理解预定义函数compare()do. compare()的调用顺序和关系
  • 实验 2.5、2.6,请学习uvm_object:print()uvm_object::copy()函数,再结合 之前的compare()函数,理解域自动化的意义以及它带来的便捷性

2)具体要求

TODO

  • 2.1 做类的声明

image-20230226231621070

  • 2.2 使用域了可以进行compare(不相等则直接停下来,不会告诉你其他不相等的内部成员)

image-20230226231656428

  • 2.3 改变compare效果,使其比较的不同结果更多一点:使用全局控制compare的实例uvm_default_comparer

image-20230226232120279

  • 2.4 自定义compare函数:do_compare()

image-20230226232132432

  • 2.5 掌握print()作用,在调用copy之前先把对象的内容打印处理啊

image-20230227134521248

  • 2.6 掌握copy()作用

image-20230228201815077

3 phase机制

phase_order.sv与phase_order_ref.sv

uvm独有的9个大phase

run_phase以及与其平行的12个小phase是task phase

  • 建议要么用run_phase要么用12个phase其中几个,最好不要同时使用,本例只做学习使用所以同时使用了

内容:

  • 理解不同层次/相同层次,phase的执行顺序
  • build_phase自顶向下、connect_phase自底向上
  • run_phase和12个小phase之间的并行关系
  • 仿真一共执行的时间(1us?2us?…)你如何解释

实验二,组件的phase如何自动执行中对不同组建的run_phase进行了解释,实际上顶层uvm_root对所有的组件都是fork_join_none遍历的启动run_phase。不同于SV的类似点火一样一层一层调用run()

打印信息:通过打印信息,理解不同phase的执行顺序

image-20230228202011270

  • “CONNECT”——id
  • “comp1 connect phase entered”——message body
  • UVM_LOW——冗余度
  • 通过打印信息,理解不同phase的执行顺序

4 config机制

1)基本内容

uvm_config.sv与uvm_config_ref.sv

内容:

  • 接口传递
  • 单一变量传递
  • 对象传递

    2)具体要求

  • 4.1,4.2,4.3:获得接口、变量、config object句柄

    • 其中interface,只有在顶层tb才有,所以要在顶层tb中set

image-20230228202123647

  • 4.4 思考问题:
    • 接口传递与run_test之间是否存在顺序
    • 在uvm_config_test::build_phase()中,如果讲从的例化提前到刚进入build_phase()中时,而将后续的config_db传递操作放置于其后,是否可行?为什么
    • 上面两个问题,你认为config_db在使用中应注意什么地方

5 消息管理

1)基本内容

uvm_message.sv与uvm_message_ref.sv,与uvm_config.sv结构类似,不过是增加了对消息的练习

2)具体要求

5.1:组件消息过滤,使用set_report_verbosity_level_hier(),可参考类的手册的原型进行深入学习,或者直接参考答案

5.2:ID的消息过滤,注释5.1,使用set_report_id_verbosity_level_hier()

5.3:发现5.1、5.2仍然有些消息无法被屏蔽,如config_obj的”CREATE”以及uvm_message的”TOPTB”,请思考为什么?如何屏蔽这些消息呢

  • 使用uvm_root::get()来获取最顶层(即uvm_message_test的顶层)来控制过过滤”CREATE”和”TOPTB”的消息

lab1 object创建名字传入问题

问题

image-20230228140716742

原因

Qustasim默认uvm1.1d,而vcs的Makefile使用的是uvm1.2,不同uvm版本导致执行不一样

image-20230228140746975

如何修改Questasim为uvm1.2

找到配置目录:

image-20230228140241102

修改modelsim.ini:

image-20230228134357986

再创建project

UVM入门和进阶实验2

介绍

uvm实验2的验证结构与sv保持一致,几乎全部是svlab5一致过来的,绝大多数都没有改变(因为还没有学到),包括:

  • SV验证环境结构
  • SV组件之间的通信管道(mailbox,不是uvm的通信管道)
  • SV的激励产生和发送模式(svlab5)
  • SV的数据检查和报告(svlab5)
  • SV的测试开始与结束方式(svlab5)
  • SV的配置方式(svlab5)

uvm实验2到实验5,逐步移植为纯粹的UVM环境:

  • SV实验我们做的是加法,做的纯软件的验证环境
  • UVM我们做的是减法,不断剔除SV的特性,替换为UVM的环境,并进行比较与优势
  • 实验不会暴增了,思考SV与UVM在验证环境上不同的实现方式孰优孰劣

本次实验现需要将之前学习到的内容进行充分应用和理解,包括:

  • 各个验证组件的使用(实验二帮大家了解验证组件如何过度的)
  • 验证组件之间的层次关系(实验二帮大家了解SV和UVM层次关系的不同)
  • 工厂的””注册”和”创建”(uvm实验一了解了)
  • “域自动化”和uvm.object的预定义方法(uvm实验一了解了)
  • “phase”的自动执行和顺序关系(uvm实验一了解了)
  • 消息宏的简单使用(uvm实验一了解了)
  • 通过config_db对接口的传递(uvm实验一了解了)
  • 测试的选择和开始,以及对仿真结束的控制(uvm实验一了解了)+

顾大局而不拘小节,掌握大思路,路科已经对上面的内容改造好了,鼓励大家看如何改造的

内容

讲解思路,左边SV代码右边UVM代码

验证组件和层次构建

主要内容:SV组件替换为UVM组件

讲解思路:左边SV代码右边UVM代码,跟着视频了解如何构建的

替换规则:

  • 实现组件对应原则
    • sv的transaction类对应uvm_sequence_item
    • sv的driver类对应uvm_driver
    • sv的generator类我们稍后会替换为uvm_sequence + uvm_sequencer
    • sv的monitor对应uvm_monitor
    • sv的agent对应uvm_agent
    • sv的env对应uvm_env
    • sv的checker对应uvm_scoreboard
    • sv的reference model和coverage model均对应uvm_component
    • sv的test对应uvm_test
  • 对应的同时要注意:
    • 什么时候需要域自动化?我们需要使用一些函数的时候,如:copy(),clone(),compare()
    • 对谁进行域的自动化?对一些数据类型,在这里是uvm_sequence_item和generator里面的成员变量,也就是使用到传递的数据类型的组件(以后就是uvm_sequence_item与uvm_sequence了)

讲解内容

看:chnl_pkg.sv, tb.sv, reg_pkg.sv, mcdf_pkg.sv fmt_pkg.sv(report_pkg.sv我们不需要了,uvm自带相关宏和函数)

uvm的导入:

  • 每一个用到uvm的pkg都要import uvm_pkg::*;`incldude "uvm_macros.svh"
  • tb.sv没用到uvm,所以不用导入
chnl_pkg.sv

1.do_driver

1)run_phase和run

run_phase就是把run的内容放进来了,别的没变

image-20230228214746190

2)有个细节

do_reset没有变化,do_driver有个细节变化

  • sv中的req.clone都是相同transacation类型的
  • uvm中返回的都是uvm_sequence_item父类句柄,需要转换

image-20230228214901760

3)chnl_write不用$display了

image-20230228215224753

4)clone()和sprint()直接注释了

原因:我们用域的自动化里面有了

image-20230228221101656

2.generator

1)没大改

这里还没改成sequencer和sequence,我们在环境四进行改造,我们学到哪里,改到哪里

image-20230228215401766

2)成员变量,域的自动化

image-20230228215432019

3)send_trans()创建trans

  • 创建用type_id(大多数uvm类可以type_id创建,如uvm_obj和uvm_comp;但有一些类不能用type_id创建如port类,port类继承于uvm_void。原因在实验3?)
  • 随机化没变

image-20230228220351151

4)super.sprint()

sprint()只打印了一个头,具体内容直接调用了super.sprint()

image-20230228220654219

也可以直接sprint不用super,比如你把函数改成gen_sprint()这样就不会有歧义了,编译直接去父类找

image-20230228220914647

5)new和build_phase

什么时候放到new,什么时候放到build_phase?

  • 需要用到override且用type_id创建的放在build_phase里,以方便工厂override,进行类型转换
  • new创建的放在new或者build_phase里都ok
  • 最简单,所有放在build_phase,绝对错不了

image-20230228224547314

send_trans中创建的req就直接type_id创建了,因为我们不需要用到工厂override机制

image-20230228224851322

创建组件一定要要用type_id,因为需要形成uvm层次结构

3.monitor

没有变化

4.agent

1)例化

以前的例化连接放在new里面

现在例化放在build_phase

2)接口的传递没有config_db,仍然set_interface

image-20230228222803219

3)重要!run的调用

  • 以前run需要调用子一级的run
  • 现在uvm_root对run_phase统一调度,因此不需要调用了

image-20230228223824368

其他package思路和chnl_pkg一样
mcdf_pkg.sv(sv顶层环境)

image-20230228225500933

1.mcdf_checker

refmod就是加了个extends uvm_component;

1)组件间的连接

组件间mailbox的连接从new函数转移到connect_phase

image-20230228230232251

2)set_interface没有变化

3)do_compare

为了避免与uvm的回调函数冲突,改了do_compare的名字为do_data_compare,里面内容一样只不过打印信息用了uvm宏

2.mcdf_coverage

没有变化,就相当于加了extends uvm_component;

3.mcdf_env

1)组件间的例化与连接

例化放到了build_phase

组件间mailbox连接放到了connect_phase

2)run

不用调用内部组件了,直接run

image-20230228232313562

3)do_report

sv放到do_report

uvm放到report_phase,其中env不需要调用了,因此直接删掉就行,checker则是把do_report内容转移到了report_phase中

image-20230228232411136

4.mcdf_base_test

1)改为继承于uvm_test了

image-20230228232657772

2)添加了许多vif

image-20230228232729297

获取?在build_phase中,这些vif通过config_db::get从外部顶层获取

image-20230228233857741

分发?在connect_phase进行分发

image-20230228233928453

set_interface里面是啥?调用内部组件的set_interface

image-20230228234253525

3)重要!run_phase与raise_objection

这里的测试用例调用了内部定义的一些任务,并且使用了objection机制

image-20230228234604690

mcdf_data_consistence_basic_test

继承于mcdf_base_test同sv一样,其他do任务(如发送产生激励等)一点没变化,因为没学到,实验四会讲

tb.sv

image-20230301000108740

1.interface传递!只能使用config_db

使用config_db形式传递interface,而不是调用set_interface

image-20230301000309489

为什么不能使用set_interface了?因为这个时候你的test还没创建,而是在run_test的时候创建

如果先run_test呢?也不行,进入build_phase,还是没拿到interface指针

为什么SV可以?因为sv的时候把所有test已经例化了,此时所有的层次结构已经有了,这个时候你再对所有的test执行set_interface是合理的。这就是为什么SV需要例化所有test的原因,如果你不全部例化了,此时你的set_interface是失败的

2.run_test

直接调用run_test()或者run_test(默认测试用例的字符串),不用写TESTNAME的获取逻辑了

UVM的命令行的run_test参数为:+UVM_TESTNAME=xxx

如果没有参数则执行run_test的参数代表的测试用例,不指定则没有(路科也没说会不会报错)

小总结

命令:vsim -novopt work.tb -classdebug +UVM_TESTNAME=mcdf_data_consistence_basic_test

使用UVM的主要特性:

  • run_phase、report_phase
  • build_phase、connect_phase、
  • config_db传递interface
  • 域自动化来声明generator和transaction中的变量,不再定义clone(),看情况定义sprint()(如trans中不定义了,gen中调用了super.sprint())
  • uvm_test中使用objection机制控制仿真结束
  • 不再用$display而是用`uvm_info, `uvm_warning, `uvm_error, `uvm_fatal,删除了rpt_pkg.sv
  • run_test代替+TESTNAME获取逻辑

目前涉及到组件间连接主要在以下三处地方:

  • mcdf_pkg.mcdf_checker
  • mcdf_pkg.mcdf_env
  • mcdf_pkg.mcdf_base_test

不需要层层调用了,只需要在当前phase里面调用使用到的内容:

  • run_phase(大多数情况可以因为层层调用而删掉)
  • report_phase(大多数情况可以因为层层调用而删掉)

其他

void'

当你使用一个有返回值的函数,但你不需要使用返回值时使用,如果不加void'则编译器会提醒警告

image-20230301201325273

先配置再创建

问题:

image-20230301201805226

结论:此处不会出错,但不确保以后这种情况不会出错。应该遵循先配置后创建的原则。

原因:不容易出错,可以避雷。这里在build_phase可以,但当不同phase遵循不同创建配置顺序时,则创建的先后顺序会控制不住

UVM入门和进阶实验3

介绍

在实验3中,我们将SV环境移植到UVM的重点将主要在以下几个方面:

  • TLM单向通信端口和多向通信端口的使用。
  • TLM的通信管道
  • UVM的回调类型uvm_calback
  • UVM的一些仿真控制函数

内容

TLM单向通信和多向通信

之前的都是通过mailbox以及在上层进行句柄传递实现的:

  • monitor到checker
  • checker与reference model

内容:我们需要大家使用TLM端口进行通信,做通信元素和方法的替换

最终的更新方案(你需要围绕这个方案更新接口):

image-20230301214308783

如图所示我们要修改的内容:

1.各个agent的monitor(chnl、reg、fmt)

具体内容:请将在monitor中的用来与checker中的mailbox通信的mon_mb句柄替换为对应的uvm blocking_put_port类型

实现方案:

  • 端口句柄添加

image-20230301215230451

  • 端口例化(要用new而不是create,因为它不是object)

image-20230301215308389

  • 端口发送数据给checker

image-20230301215337861

2.checker

具体内容:

  • 在checker中声明与monitor通信的import端口类型,以及与reference model 通信的import端口类型。具体类型可以参考代码中的注释,需要注意的是,由于checker与多个monitor以及reference model通信,是典型的多方向通信类型,因此,我们需要使用多端口通信的宏声明方法,请参考红宝书12.2.3的实例。在使用了宏声明端口类型之后,再在checker中声明其句柄,并且完成例化
  • 根据声明的import端口类型,分别实现其对应方法

实现方法:

  • 外部5种端口宏声明(1)

image-20230301215731874

image-20230301215659586

  • 内部与refmod的4端口宏声明(2)
    • 思考问题:get端口与get_peek不同是否可以不用宏声明?路科这里为了统一统一进行了宏声明

image-20230301215932058

image-20230301215920286

  • 以上几个端口的声明

image-20230301220033865

  • 以上几个端口的例化

image-20230301221933217

  • 以上几个端口的方法实现(1.3)

image-20230301221325961

image-20230301221334697

3.refmod(1.4)

image-20230301221500455

  • 左侧4端口声明

image-20230301221547061

  • 左侧4端口例化

image-20230301221705886

  • 原有的fifo使用tlm_fifo替代(2.1为了练习通信管道)

image-20230301221620254

  • 通过port与验证环境进行交互

image-20230301222052522

image-20230301222134019

4.连接

  • monitor与checker
    • 1.5请在mcdf_env的connect_phase()阶段,完成monitor的TLM port与mcdf_checker TLM import的连接
    • 可以看到左边是agent,因为是agent发起的;右侧是checker的五个inport

image-20230301222733535

  • checker与refmod
    • 1.6请在checker的connect_phase()阶段, 完成mcdf_refmod的TLM port与mcdf_checker的TLM import的连接
    • refmodel在左边,checker的inport在右边

image-20230301222914186

TLM通信管道

在完成了上述实验之后,你可能会抱怨,看起来工作量增加不少了呢!怎么会说,TLM通信有它的好处呢?那路桑再阐述几个TLM通信的优点:

  • 通信函数可以定制化,例如你可以定制put()/get()/peek()的内容和参数,这其实比mailbox的通信更加灵活
  • 将组件实现了完全的隔离,可以参考红宝书图12.4,因为只有通过层次化的TLM端口连接,我们就可以很好地避免直接将不同层次的数据缓存对象的句柄进行“空中传递”。而TLM 端口按照层次的连接,虽然看起来有点繁复,但也正因为这一点,可以使得组件之间保持很好的独立性呢

如何不实现自己的put()/get()/peek()方法呢?当然有啦!依然可以参考红宝书12.3.1 节,关于uvm tm fifo 类的使用。请按照以下要求,完成本实验:

1.将原本在mcdf_refmod中的out_mb替换为uvm_tlm_fifo类型,并且完成例化,以及对应的变量名替换

image-20230301225046664

  • 你不需要再为这些tlm_fifo再例化inport了

image-20230301225154997

  • 例化tlm_fifo,可以new也可以create,因为他是组件,且你不做override

image-20230301225248889

  • mailbox的put改为tlm_fifo的put

image-20230301225401576

2.将原本在mcdf_checker中的exp_mbs[3]的邮箱柄数组,替换为uvm_blocking_get_port类型柄数组,并且做相应的例化以及变量名替换

  • 内容同上

image-20230301231302483

3.在mcdf_checker中,完成在mcdf_checker中的TLM port端口到mcdf_refmod中的uvm_tlm_fifo自带的blocking_get_expot端口的连接

image-20230301231325920

image-20230301231540071

  • 注意端口对应

在完成这个实验环节之后,请开始编译原有的仿真测试,进行仿真,检查仿真结果是否与实验2的结构保持一致。另外,请再思考,上述两个实验环节中,针对一般的数据存储和TLM端口连接,哪一种方式更为简便?

补充:为什么没有把这一部分也改成tlm_fifo?

image-20230301231718385

  • 因为要练习mailbox做buffer如何自己实现方法

UVM回调类

image-20230301232021561

内容:原有的测试用例是继承于mcdf_base_test,现在像用回调方式实现

原有的测试用例基于mcdf_base_test,自定义了:do_reg()do_formatter()do_data()

1.定义callback类,并预定义虚函数/任务

image-20230301232336974

2.对base_test注册(绑定)

image-20230301232535566

3.对base_test插入(3.2)

  • 把callback里三个对应函数,插入进mcdf_base_test

image-20230301232609463

4.定义测试用例的回调函数类,并预定义虚函数/任务

  • 继承于第一步定义callback类
  • 移植原有mcdf_data_consistence_basic中的三个do_xxx()方法

image-20230301232911584

5.定义测试用例类,例化并添加回调类

  • 父类为#参数
  • 不用实现原有mcdf_data_consistence_basic中的三个do_xxx()方法

image-20230301233645432

UVM仿真控制方法

image-20230301232135797

end_of_elaboration_phase九大phase之一,build、connect之后,run之前,作用是在环境运行之前再进行一些配置

查看mcdf_basic_test的end_of_elaboration_phase

image-20230301233853554

  • 配置打印消息的冗余度
  • 配置error最大退出消息次数
  • timeout:代替do_watchdog(),watchdog()是检查仿真时间,如果超过设定阈值测退出,watch_dog()代码:

image-20230301234134144

下一节

目前环境generator与driver仍然是mailbox,下一节讨论这几个通信:sequencer与driver、sequencer与sequence

lab3 TLM端口通信缓存与信箱的比较

TLM端口通信相比较信箱的三个优点:

1.可以不用缓存

考虑以下验证方案,其中monitor对scb和coverage model都是用analysis连接

image-20230302192602063

  • 对于scb来说,需要从monitor来的数据进行缓存
  • 对于coverage model来说,可能不需要进行缓存,而是调用write函数,write函数触发一个event,这个event让不同的covergounp进行采样

image-20230302192439753

2.TLM端口支持空发送

同样以上面的验证环境为例,如果你不需要scb和coverage model大可以删除,monitor的端口可以空发;而mailbox不行,它的句柄为空会报错

image-20230302192922045

3.跨层次问题

mailbox仍然是在顶层进行句柄的连接,这样如果sub组件外面嵌套另外一个组件时,需要改变顶层调用的路径

image-20230302193119609

对sub组件添加一层组件

使用TLM组件,一层一层进行端口连接则不存在顶层修改路径的问题,只需要对新嵌套的组件进行连接即可

image-20230302193758152

image-20230302193840574

lab3 回调与继承的应用区别

错误的插入,讲的很抽象

一般的错误插入方法,实际上我们不这么做:写错误的SEQ,其中涂橙色的是错误的transaction,SEQ1用于发送正常的trans,SEQ2用于发送错误的trans

image-20230302195041460

实际的错误插入方法,更省事更易于复用的错误插入:通过使用uvm_callback

image-20230302200238308

话说为什么我们要插入错误?当我们对安全性能要求较高的时候

其他体现回调的例子我没仔细听,路科也就随便一带

什么时候用callback?我需要做一些局部手术,不需要子类继承父类,去override写一大段,只是在原类添加代码

我们的实验例子没有体现callback的魅力

UVM入门和进阶实验4

介绍

在实验4中,我们将主要完成以下内容:

  • 将产生transactian并且发送至 driver的generator组件,拆分为 sequence与sequencer
    • 原generator产生+发送trans
    • 现sequence产生trans/item,sequencer发送trans/item
  • 在拆分的基础上,实现底层的sequence
  • 完成sequencer与driver的连接和通信工作
  • 构建顶层的virtual sequencer
  • 将原有的mcdf base test拆分为mcdf_base_virtual_sequence与mcdf_base_test前者发挥产生序列的工作,后者只完成挂载序列的工作
  • 将原有的mcdf_data_consistencebasic_test和mcdf_full_random_test继续拆分为对应的virtual sequence和轻量化的顶层test

内容

1.1-1.3 集中讲reg_pkg.sv,fmt_pkg与chnl_pkg类似

1.4主要在mcdf_pkg中

1.1 driver与sequencer的改建

image-20230302220851441

driver不再需要mailbox句柄,而是seq_item_port的一个port(uvm_driver内部成员,自带)

image-20230302214346606

以前利用req_mailbox拿到req,并通过rsp_mailbox发送rsq;
现在通过seq_item_port.get_next_item(req)拿到,通过item_done(rsq);返回

image-20230302214450531

1.2 底层sequence的提取

image-20230302220912158

原有的xxx_reg()是调用的generator来进行的操作,现在我们没有generator而是sequence,所以要有对应的sequence而移除generator

image-20230302221008709

把generator修改为sqr与对应的seq

reg_sequencer

image-20230302221138842

  • sqr很简单,注意额外的参数类reg_trans

reg_base_sequence

image-20230302221206816

image-20230302221221499

image-20230302221246137

  • seq包含随机的变量、约束、发送,实际上就是把generator的产生数据的部分搬过来了,只不过不再使用句柄创建mailbox了
  • 以前数据的握手是通过req = new(),req.randomize(),req_mb.put(req)与rsq_mb.get(rsq)
  • 现在数据握手非常简单,通过uvm_do_with,即创建、又随机化、又发送,get_response获取响应

image-20230302221616291

  • post_randomize()没变

idle_reg_sequence

image-20230302221751565

  • 继承于reg_base_sequence
  • 就是对cmd增加了`IDLE限制

write_reg_sequenceread_reg_sequence

image-20230302221854844

image-20230302221859297

  • 继承于reg_base_sequence
  • 就是对cmd增加了限制
  • 为什么没对addr增加约束?因为我们要在外部,调用write_reg_sequence与read_reg_sequence时,加以做约束

idle_reg_sequence,write_reg_sequence与read_reg_sequence

image-20230302222031401

  • 实际上就是generator中的这三个函数对应的sequence,进行了转化

1.3 sequencer的创建连接

image-20230302222336534

在agent里面声明sqr句柄

image-20230302222406164

在agent里面sqr的创建和连接

image-20230302222431573

1.4 移除generator的踪迹

image-20230302223116082

接下来的改动主要在mcdf_pkg中

以前的generator都在test里面,现在的sqr都在agent里面,所有的seq会被组合到vseq里面

image-20230302222744774

  • 删除句柄(以前的generator都在test里面,现在的sqr都在agent里面,所有的seq会被组合到vseq里面)

image-20230302222826603

  • 删除例化

image-20230302222843039

  • 删除连接

1.5 移除uvm_base_test的transaction发送方法

image-20230302223130527

以前uvm_base_test

  • 既具有容器的性质例化env,例化各个generator
  • 又具有例化场景描述的作用,协调各个generator进行do_reg、do_data,do_formatter

现在的uvm_base_test变成了容器的性质,它主要由mcdf_env,将来要添加的mcdf_config,以及被用来挂载的顶层virtual sequence构成。场景的描述由挂载的顶层virtual sequence实现

image-20230302223504114

  • 删除idle_reg,write_reg,read_reg(我们已经改造为对应的seq了)

image-20230302223611149

  • run不一样了,不需要控制了,而是增加了一个run_top_virtual_sequence方法
  • run_top_virtual_sequence作用就是挂载
    • vsqr挂载、vseq挂载

image-20230302223704117

  • 三个do移除了

1.6-1.7 添加顶层的virtual sequencer

image-20230302224410753

mcdf_virtual_sequencer

image-20230302224442493

  • 就是添加了各个sqr句柄

image-20230302224510515

image-20230302224528803

image-20230302224536902

  • mcdf_env中做声明、例化和连接
  • 把各个底层的sqr句柄都赋值给顶层vsqr的句柄

1.8 重构mcdf_base_test

image-20230302224738156

实际上是对mcdf_base_test的拆解:

  • 我们之前把do_data,do_reg,do_config都删除,只定义了一个run_top_virtual_sequence()
  • 场景描述主要在run_top_virtual_sequence里面

mcdf_base_virtual_sequence

image-20230302225230757

  • 对各个底层的seq进行声明

image-20230302225509637

  • body和base_test的run的任务一样
  • 为什么mcdf_base_virtual_sequence的do_reg、do_formatter、do_data都是空的?因为要被继承的seq镜像描述

1.9 重构mcdf_data_consistence_basic_test

image-20230302225653202

实际上是对mcdf_data_consistence_basic_test的拆解

mcdf_data_consistence_basic_virtual_sequence

image-20230302230904818

  • 以前,分别调用了base_test的三个任务

image-20230302225812013

  • 现在,使用uvm_do_on_with,on挂载到对应sqr,并发送with的数据

mcdf_data_consistence_basic_virtual_sequence分别发送了属于不同sqr的seq

mcdf_data_consistence_basic_test

image-20230302230337974

  • 顶层的测试用例会非常清爽
  • 做了例化:top_sqe = new()
  • 做了挂载:top_seq.start(env.virt_sqr)

我们发现:往往一个virtual sequence会跟着一个名称一致的test

1.10 重构mcdf_full_random_test

mcdf_data_random_virtual_sequence

image-20230302230537896

mcdf_data_random_test

image-20230302230531567

小总结

实验4主要是把各个测试用例功能拆分,分为容器与场景的描述,其中

  • 容器为uvm_test衍生类
  • 场景描述为virual sequence,名称与uvm_test衍生类类似

virtual sequence不再使用write_reg、read_reg而是使用uvm_do_on_with

实验5主要是把以前对寄存器读写测试的部分,改造为利用寄存器模型,利用lay sequence不再利用reg的bus transaction,通过高抽象级的读写测试

结构小总结

目前结构:

顶层为衍生于base_test的测试用例,可以看作一个容器

  • base_test主要内容:
    • 创建env
    • 获取intf,并连接到env内部各个组件
    • 控制phase(run_phase,connect_phase,end_of_elaboration_phase)
  • 特定测试用例功能
    • 创建并启动特定的top_seq(vseq)

特定的top_seq,是一种vseq,衍生于base_vseq,主要是对验证场景进行描述

  • base_vseq主要内容

    • 定义body结构,通过运行do_reg、do_config、do_data任务的结构完成整个验证场景
    • 设置对应的vsqr
    • 声明各种seq句柄
  • 特定的top_seq主要内容

    • 重载do_reg、do_config、do_data来实现具体内容
    • 重载时需要启动不同的seq,并给这些seq指定sqr
      • 这些seq,通过这种方式完成了从seq->vseq->vsqr->sqr的连接,最终由验证环境中的driver获取到并发送到总线接口

seq,vseq,vsqr,sqr分别在哪?

  • seq在vseq中启动(同时也创建了)
  • vseq在特定测试用例中启动(同时也创建了)
  • vsqr在env中例化
  • sqr在各个agnet中例化

如何连接的?

  • vseq与vsqr:vseq在特定测试用例的启动中被指定了vsqr
  • vsqr与sqr:在env中通过句柄进行连接
  • seq与sqr:seq在vseq的启动中被指定了sqr
  • sqr与drv:在agnent通过seq_item_port.connect(sequencer.seq_item_export);进行连接
  • drv与dut:通过接口连接

为什么在两个地方调用了`uvm_xxx系列宏

  • 在vseq中调用了:`uvm_do_on_with,主要是实现嵌套的启动seq,并且给这个seq添加了新的约束并绑定了p_seq
  • 在seq中调用了:`uvm_do_with,主要是为了发送trans,并给这个trans添加了基础的约束

注意这两个约束的关系,vseq中的约束影响到的是seq中的rand变量约束,而这些变量又有一部分参与到seq中对trans约束的计算中了,从而完成了约束的传递

为什么声明了这么多seq?

1
2
3
4
5
6
7
8
9
10
11
//现在
class mcdf_base_virtual_sequence extends uvm_sequence;
idle_reg_sequence idle_reg_seq;
write_reg_sequence write_reg_seq;
read_reg_sequence read_reg_seq;
chnl_data_sequence chnl_data_seq;
fmt_config_sequence fmt_config_seq;


...
//原来

主要为了,通过sequence,对实现原来base_test的发送激励/获取响应行为,进行抽象

这些行为,有些是具备特定功能的如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

virtual task idle_reg();
void'(reg_gen.randomize() with {cmd == `IDLE; addr == 0; data == 0;});
reg_gen.start();
endtask

virtual task write_reg(bit[7:0] addr, bit[31:0] data);
void'(reg_gen.randomize() with {cmd == `WRITE; addr == local::addr; data == local::data;});
reg_gen.start();
endtask

virtual task read_reg(bit[7:0] addr, output bit[31:0] data);
void'(reg_gen.randomize() with {cmd == `READ; addr == local::addr;});
reg_gen.start();
data = reg_gen.data;
endtask

有些则是单纯对generator的随机化与启动(其实这两个也可以封装成为像上面对reg的一样的函数,只不过源码都直接放在do_formatter与do_data任务里面了

1
2
void'(fmt_gen.randomize() with {...;});
fmt_gen.start();
1
2
3
4
5
6
7
8
9
void'(chnl_gens[0].randomize() with {ntrans==100; ch_id==0; data_nidles==0; pkt_nidles==1; data_size==8; });
void'(chnl_gens[1].randomize() with {ntrans==100; ch_id==1; data_nidles==1; pkt_nidles==4; data_size==16;});
void'(chnl_gens[2].randomize() with {ntrans==100; ch_id==2; data_nidles==2; pkt_nidles==8; data_size==32;});
fork
chnl_gens[0].start();
chnl_gens[1].start();
chnl_gens[2].start();
join
#10us; // wait until all data haven been transfered through MCDF

为什么?因为我们现在的发送激励是通过seq进行的,seq承担了trans生成与发送的任务

lab4 测试场景为什么在序列而不在test

lab4 从SEQ到DRV的item传输类型转换

create_item

UVM入门和进阶实验5

介绍

寄存器模型是为我们提供便利的,因为对于硬件来说寄存器是结构化的,一定可以生成model,结构化的东西做一些内建化的测试序列,方便我们做测试,收集覆盖率,方便我们生成一些更好复用的sequence,也就是说现在没掌握好

在实验5中,我们将主要完成以下内容:

  • 对uwm_reg的定义,以及uvm_reg_block的组织
  • 对uvm_reg_adapter的定义,以及它与uvm_reg_block之间的关系
  • 对uwm_reg_predictor的使用,以及它与uvm_reg_adapter和uvm_reg_block之间的关系
  • 改造之前的寄存器发送序列,并以uvm_reg的操作方式去取代
  • 应用内建的寄存器序列,做全面的寄存器测试

与之前实验内容的不同?

image-20230304183307941

  • 增加了mcdf_rgm_pkg
  • 为了集成寄存器模型而对mcdf_pkg进行了修改

rgm的优先:

  • 最主要是通过减少原来实现过程中sequence的使用,从而以一种更简单的方式访问寄存器
  • rgm相比较其他访问寄存器的方式,提供了更多的功能,比如访问保证了可以在外部实时同步寄存器状态、同时不需要提供绝对路径进行后门访问,更规范化的路径和地址空间映射
  • UVM其他的一些特性,使用UVM的内建方法进行寄存器测试使用UVM提供的方法收集寄存器覆盖率

内容

寄存器模型的完善和嵌入

1.0 uvm_reg和uvm_reg_block的定义已经完成

image-20230304183533825

image-20230304225945964

image-20230304230041064

image-20230304230104957

定义ctrl_reg、stat_reg:

  • field
  • covergroup
  • 创建与config
  • sample()与sample_values()

image-20230304230143028

image-20230304230210194

image-20230304230314884

定义block:

  • 6个reg
  • build,调用每一个reg的创建与配置
  • 添加default_map,与地址添加,field与block添加hdl路径

1.1 实现reg2bus以及bus2reg

image-20230304183533825

image-20230304230644987

  • reg2bus

image-20230304231151622

  • bus2reg

1.2 mcdf_env集成

image-20230304183533825

image-20230304231526551

  • 声明rgm、adapter、predictor

image-20230304231732290

  • 创建

image-20230304232011622

  • 连接
    • adapter需要map、sqr
    • predictor需要ap
    • predictor需要rgm
    • predictor需要adapter

寄存器模型的使用

image-20230304235538415

2.1 rgm句柄传递

image-20230304235706202

image-20230305000156718

  • vsqr需要rgm句柄(在env中连接获得)
  • vseq需要rgm(从p_sequencer获取)

2.2 mcdf_data_consistence_basic_virtual_sequence改写为rgm实现寄存器读写

image-20230305000404779

  • 以前使用sequence

image-20230305000419712

  • 现在直接read/write

2.3 mcdf_full_random_virtual_sequence改写为rgm实现寄存器读写

image-20230305000542201

  • 2.3有点不一样,是为了让大家做更多的练习

image-20230305000638784

  • 以前使用sequence

image-20230305000729231

  • 复位
  • 设置期望值
  • 更所有寄存器值
  • 比较

image-20230305000757818

  • 具体实现如上,注意检查是从后门直接检查
  • 为什么只mirror控制寄存器,而不是状态寄存器?因为状态寄存器往往和rgm里面的不一样

image-20230305000947525

  • 两种mirror方式:
    • 第一种对寄存器mirror
    • 第二种直接对block(这里是rgm)mirror

寄存器内建序列的应用

image-20230305001131838

3.1 在自定义的新建sequence中使用rgm的几个内建seq完成检查

image-20230305001229197

  • 因为是对寄存器的检查,只需要实现do_reg()即可

image-20230305001324275

  • 例化
  • 复位
  • rgm句柄传递
  • 挂载到对应reg_sqr句柄上

image-20230305001836554

  • 第二次seq,同时在总线上对硬件进行了复位,也对rgm进行了复位

image-20230305001924566

  • 第三次seq

额外内容

有基础的同学可以在此基础上收集寄存器覆盖率

UVM新兵结束了!

image-20230305002051811

全局实例

uvm_root

uvm_test_top

uvm_factory f = uvm_factory::get();

uvm_default_comparer

一些小函数

get_type_name():

image-20230228234833483


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!