赞
踩
多线程之间同步主要由mailbox、event、 semaphore三种进行一个通信交互。
封装、继承和多态
Factory机制也叫工厂机制,其存在的意义就是为了能够方便的替换TB中的实例或者已注册的类型。一般而言,在搭建完TB后,我们如果需要对TB进行更改配置或者相关的类信息,我们可以通过使用factory机制进行覆盖,达到替换的效果,从而大大提高TB的可重用性和灵活性。
要使用factory机制先要进行:
UVM的启动
总结:
依次执行uvm_test容器中的各个component组件中的phase机制,按照顺序:
传递virtual interface到环境中;
配置单一变量值,例如int、string、enum等;
传递配置对象(config_object)到环境;
传递virtual interface到环境中;
配置单一变量值,例如int、string、enum等;
传递配置对象(config_object)到环境;
UVM其实就是SV的一个封装,将我们在搭建测试平台过程中的一些重复性和重要的工作进行封装,从而使我们能够快速的搭建一个需要的测试平台,并且可重用性还高。但是UVM又不仅仅是封装。
ref参数类型是引用
在多个sequence同时向sequencer发送item时,需要有ID信息表明该item从哪个sequence来,ID信息在sequence创建item时就赋值了。
首要的选择是使用更多的种子来运行现有的测试程序;
其次是建立新的约束,只有在确实需要的时候才会求助于定向测试,改进功能覆盖率最简单的方法是仅仅增加仿真时间或者尝试新的随机种子。
验证的目的就是确保设计在实际环境中的行为正确。设计规范里详细说明了设备应该如何运行,而验证计划里则列出了相应的功能应该如何激励、验证和测量
这个问题很重要,建议好好准备,面试的时候经常会问~
芯片架构-RTL设计-功能仿真-综合&扫描链的插入(DFT)-等价性检查-形式验证-静态时序分析(STA)-布局规划-布局布线-布线图和原理图比较-设计规则检查-GDII。具体详细的设计流程(含各流程EDA工具)可参考以下链接:
find的队列应该是返回队列的值,一般的话是和with配合使用,find index应该是返回索引值
- property a_high_then_b_high; //a和b同时为高
- @(posedge clk)
- a|->b;
- endproperty
-
- property a_high_then_b_high;
- @(posedge clk) //a为高,下一个周期b为高
- a|=>b;
- endproperty
-
- a:assert property(a_high_then_b_high);
可以将断言分为两种常见的类型:
立即断言(immediate assertion) .
并行断言( concurrent assertion)
严格意义上有2种:
也可以通过‘uvm_do系列宏启动
除了driver、monitor、agent、model、scoreboard、env、test之外全部用uvm_object。
Q36. 一个简单的UVM验证平台
主要是编写sequence,然后在body里面根据测试功能要求写相应的激励,然后再通过ref_model和checker判断功能是否实现?
可以写脚本让它们自动执行,例如makefile…
例如让你写abcd四个信号在时钟沿处监测,当cd同时为1时,在时钟的前两个周期要ab同时为1的断言
队列的使用方法:insert,delete, push_back 和 pop_front
Push插入,pop取出
Front前边,back后边
randc为什么不能随机化产生具有唯一元素值的数组,请参考:
1、通信分为,单向通信,双向通信和多向通信
2、blocking阻塞传输的方法包含:
3、通信管道:
a. 由于analysis端口提出实现了一端到多端的TLM数据传输,而一个新的数据缓存组件类uvm_tlm_analysis_fifo为用户们提供了可以搭配uvm_analysis_port端口uvm_analysis_imp端口和write()函数。
b.uvm_tlm_analysis_fifo类继承于uvm_tlm_fifo,这表明它本身具有面向单一TLM端口的数据缓存特性,而同时该类又有一个uvm_analysis_imp端口analysis_export并且实现了write()函数:
双向通信端口transport,即通过在target端实现transport()方法可以在一次传输中既发送request又可以接收response。
刚开始接触的时候,我认为UVM
其实就是SV
的一个封装,将我们在搭建测试平台过程中的一些重复性和重要的工作进行封装,从而使我们能够快速的搭建一个需要的测试平台,并且可重用性还高。因此我当时觉得它就是一个库。
不过,随着学习的不断深入,当我深入理解UVM
中各种机制和模型的构造和相互之间关系之后,我觉得其实UVM
方法学对于使用何种语言其实并不重要,重要的是他的思想,比如:在UVM
中有sequence
机制,以往如果我们使用SV
进行TB
搭建时,我们一般会采用driver
一个类进行数据的产生,转换,发送,或者使用generator
和driver
两个进行,这种方式可重用性很低,而且代码臃肿;但是在UVM中我们通过将sequence、sequencer、driver、sequence_item
拆开,相互独立而又有联系,因此我们只需关注每一个类需要做的工作就可以,可重用性高。我在学习sequence
时,我经常把sequence
比作蓄水池,sequence_item
就是水,sequencer
就是一个调度站,driver
就是总工厂,通过这种方式进行处理,我们的总工厂不需要管其他,只需处理运送过来的水资源就可以,而sequencer
只需要调度水资源,sequence
只需要产生不同的水资源。而这种处理方式和现实世界中的生产模式又是基本吻合的。除此之外,还有好多好多,其实UVM
方法学中很多思想就是来源于经验,来源于现实生活,而不在乎是何种语言。
画出UVM
的验证环境结构,如图所示
首先,UVM
测试平台基本是由object
和 component
组成的,其中 component
搭建了TB
的一个树形结构,其基本包含了driver、monitor、sequencer、agent、scoreboard、model、env、test、top
;然后object
一般包含sequence_item、config
和一些其他需要的类。各个组件相互独立,又通过TLM
事务级传输进行通信,除此之外,DUT
与driver
和 monitor
又通过interface
进行连接,实现驱动和采集,最后在top
层进行例化调用test
进行测试。
UVM
中有很多非常有趣的机制,例如factory
机制,field_automation
机制,phase
机制,打印机制,sequence
机制,config_db
机制等,这些机制使得我们搭建的UVM
能够有很好的可重用性和使得我们平台运行有秩序稳定。
例如phase
机制,phase
机制主要是使得UVM
的运行仿真层次化,使得各种例化先后次序正确。UVM
的phase
机制主要有9个,外加12个小phase
。主要的 phase
有build phase、connect phase、run phase、report phase、final phase
等,其中除了run phase
是** task**
,其余都是function
,然后build phase
和final phase
都是自顶向下运行,其余都是自底向上运行。Run phase
和12个小phase
( reset phase、configure phase、main phase、shutdown phase
)是并行运行的,有这12个小phase
主要是进一步将run phase
中的事务划分到不同的phase
进行,简化代码。注意,run phase
和 12个小phase
最好不要同时使用。从运行上来看,9个phase
顺序执行,不同组件中的同一个phase
执行有顺序,build phase
为自顶向下,只有同一个phase
全部执行完毕才会执行下一个phase
。
所有的phase
按照以下顺序自上而下自动执行:(九大phase,其中run phase又分为12个小phase)
Domain
是用来组织不同组件,实现独立运行的概率。默认情况下,UVM
的9个phase
属于 common_domain
,12个小phase
属于uvm_domain
。例如,如果我们有两个dirver
类,默认情况下,两个driver
类中的复位phase
和 main phase
必须同时执行,但是我们可以设置两个driver
属于不同的domain
,这样两个dirver
就是独立运行的了,相当于处于不同的时钟域(只针对12个小phase
有效)。
run_phase
和main phase
(动态运行)都是task phase
,且是并行运行的,后者称为动态运行(run-time
)的phase
。
如果想执行一些耗费时间的代码,那么要在此phase
下任意一个component
中至少提起一次objection
,这个结论只适用于12个run-time
的phase
。对于run_phase
则不适用,由于run_phase
与动态运行的phase
是并行运行的,如果12个动态运行的phase
有objection
被提起,那么run_phase
根本不需要raise_objection
就可以自动执行。
在main_phase
执行过程中,突然遇到reset
信号被置起,可以用jump()
实现从mian_phase
到reset_phase
的跳转:
UVM
中采用事务级传输机制进行组件间的通信,可以大大提高仿真的速度和使得我们简化组件间的数据传输,简化工作,TLM
独立于组件之外,降低组件间的依赖关系。UVM
接口主要由port、export、imp
;驱动这些接口方式有put、get、peek、transport、analysis
等。
其中peek
是查看端口内部的数据事务但是不删除,get
是获取后立即删除。我们一般会先使用peek
进行获取数据,但不删除(保证put
端不会立马又发送一个数据),处理完毕后再用get
删除。
lmp只能作为终点接口,transport表示双向通信,analysis可以连接多个imp(类似于广播)。
都可以。Analysis port
类似于广播,其可以同时对多个imp
进行事务通信,只需要在每一个对应的imp
端口申明write()
函数即可。对比 put,get,peek port,
他们都只能进行一对一传输,且也必须申明对应的函数如 put()、get()、peek()、can_put()/do_put()
等。Fifo
是可以不用申明操作函数的,其内部封装了很多的通信端口,如analysis_export
等,我们只需要将端口与其连接即可实现通信。
item
是基于uvm_object
类,这表明了它具备UVM
核心基类所必要的数据操作方法,例如copy、 clone、compare、record
等。
item
对象的生命应该开始于sequence
的body()
方法,而后经历了随机化并穿越sequencer
最终到达driver
,直到被driver
消化之后,它的生命一般来讲才会结束。
item与sequence的关系 一个sequence
可以包含一些有序组织起来的item
实例,考虑到item
在创建后需要被随机化,sequence
在声明时也需要预留一些可供外部随机化的变量,这些随机变量一部分是用来通过层级传递约束来最终控制item
对象的随机变量,一部分是用来对item
对象之间加以组织和时序控制的。
Sequence的分类:
扁平类(flat sequence)
:这一类往往只用来组织更细小的粒度,即item实例构成的组织。
层次类( hierarchical sequence)
:这一类是由更高层的sequence用来组织底层的sequence
,进而让这些sequence
或者按照顺序方式,或者按照并行方式,挂载到同一个sequencer
上。
虚拟类(virtual sequence)
:这一类则是最终控制整个测试场景的方式,鉴于整个环境中往往存在不同种类的sequencer
和其对应的sequence
,我们需要一个虚拟的sequence
来协调顶层的测试场景。之所以称这个方式为virtual sequence
,是因为该序列本身并不会固定挂载于某一种sequencer
类型上,而是将其内部不同类型sequence
最终挂载到不同的目标sequencer
上面。这也是virtual sequence
不同于hierarchical sequence
的最大一点。
sequence
机制用于产生激励,它是UVM
中最重要的机制之一。sequence
机制有两大组成部分:sequence
和sequencer
。
在整个验证平台中sequence
处于一个比较特殊的位置。sequence
不属于验证平台的任何一部分,但是它与sequencer
之间有着密切的关系。
只有在sequencer
的帮助下,sequence
产生的transaction
才能最终送给driver
;同样,sequencer
只有在sequence
出现的情况下才能体现出其价值,如果没有sequence
,sequencer
几乎没有任何作用。
除此之外,sequence
与sequencer
还有显著的区别。从本质上说,sequencer
是一个uvm_component
,而sequence
是一个uvm_object
。与my_transaction
一样,sequence
也有其生命周期。它的生命周期比my_transaction
要更长一点,其内部的transaction
全部发送完毕后,它的生命周期也就结束了。
1、仲裁特性:
在实际使用中,我们可以通过
uvm, _sequencer:set _arbitration(UVM _SEQ ARB _TYPE val)函数来设置仲裁模式,
这里的仲裁模式UVM_ SEQ ARB. _TYPE有下面几种值可以选择:
2、锁定机制
uvm_sequencer提供了两种锁定机制,分别通过lock()和grab()方法实现,这两种方法的区别在于:
Virtual
含义就是其sequencer
并不需要传递item
,也不会与driver
连接,其只是一个去协调各个sequencer
的中央路由器。通过virtual sequencer
我们可以实现多个agent
的多个sequencer
他们的 sequence
的调度和可重用。Virtual sequence
可以组织不同sequencer
的sequence
群落。
在UVM
中有sequence
机制,以往如果我们使用SV
进行TB
搭建时,我们一般会采用driver
一个类进行数据的参数,转换,发送,或者使用genetor
和driver
两个进行,这种方式可重用性很低,而且代码臃肿;
但是在UVM中我们通过将sequence、sequencer、driver、sequence_item
拆开,相互独立而又有联系,因此我们只需关注每一个类需要做的工作就可以,可重用性高。我在学习sequence
时,我经常把sequence
比作蓄水池,sequence_item
就是水,sequencer
就是一个调度站,driver
就是总工厂,通过这种方式进行处理,我们的总工厂不需要管其他,只需处理运送过来的水资源就可以,而sequencer
只需要调度水资源,sequence
只需要产生不同的水资源。
Interface
如果不进行virtual
声明的话是不能直接使用在dirver
中的,会报错,因为interface
声明的是一个实际的物理接口。一般在dirver
中使用virtual interface
进行申明接口,然后通过config_db
进行接口参数传递,这样我们可以从上层组件获得虚拟的interface
接口进行处理。
Config_db
传递时只能传递virtual
接口,即interface
的句柄,否则传递的是一个实际的物理接口,这在 driver
中是不能实现的,且这样的话不同组件中的接口一一对应一个物理接口,那么操作就没有意义了。
Factory
机制也叫工厂机制,其存在的意义就是为了能够方便的替换TB
中的实例或者已注册的类型。一般而言,在搭建完TB
后,我们如果需要对TB
进行更改配置或者相关的类信息,我们可以通过使用factory
机制进行覆盖,达到替换的效果,从而大大提高TB
的可重用性和灵活性。要使用factory
机制先要进行:
将类注册到factory
表中
创建对象,使用对应的语句 (type_id::create)
编写相应的类对基类进行覆盖。
Callback
机制其作用是提高TB
的可重用性,其还可进行特殊激励的产生等,与factory
类似,两者可以有机结合使用。与factory
不同之处在于 callback
的类还是原先的类,只是内部的callback
函数变了,而factory
是产生一个新的扩展类进行替换。
UVM
组件中内嵌callback
函数或者任务
定义一个常见的uvm_callbacks class
从UVM callback
空壳类扩展uvm_callback
类
在验证环境中创建并登记uvm_callback
field_automation
机制:可以自动实现copy、compare、print
等三个函数。当使用uvm_field
系列相关宏注册之后,可以直接调用以上三个函数,而无需自己定义。这极大的简化了验证平台的搭建,尤其是简化了driver
和monitor
,提高了效率。
UVM
中通过objection
机制来控制验证平台的关闭,需要在drop_objection
之前先raise_objection
。验证在进入到某一phase
时,UVM
会收集此phase
提出的所有objection
,并且实时监测所有objection
是否已经被撤销了,当发现所有都已经撤销后,那么就会关闭此phase
,开始进入下一个phase
。当所有的phase
都执行完毕后,就会调用$finish
来将整个验证平台关掉。如果UVM
发现此phase
没有提起任何objection
,那么将会直接跳转到 下一个phase
中。
UVM
的设计哲学就是全部由sequence
来控制激励生成,因此一般情况下只在sequence
中控制objection
。另外还需注意的是,raise_objection
语句必须在main_phase
中第一个消耗仿真时间的语句之前。
Config_db
机制主要作用就是传递参数使得TB
的可配置性高,更加灵活。Config_db
机制主要传递的有三种类型:
一种是interface
虚拟接口,通过传递virtual interface
使得dirver
和 monitor
能够与DUT
连接,并驱动接口和采集接口信号。
第二种是单一变量参数,如int,string,enum
等,这些主要就是为了配置某些循环次数,id
号是多少等等。
第三种是object
类,这种主要是当配置参数较多时,我们可以将其封装成一个object
类,去包含这些属性和相关的处理方法,这样传递起来就比较简单明朗,不易出错。
Config_db
的参数主要由四个参数组成,如下所示,第一个参数为父的根parent
,第二个参数为接下来的路径,对应的组件,第三个是传递时的名字(必须保持一致),第四个是变量名。uvm_config_db #(virtual interface) :: set(uvm_root:.get(),"uvm_test_top.c1",'vif",vif); uvm_config_db #(virtual interface) :: get(this,"”,"vif",vif);
Component
之间通过在new
函数创建时指定parent
参数指定子关系,通过这种方法来将TB
形成一个树形结构。UVM
中运行是通过Phase
机制进行层次化仿真的。从组件来看各个组件并行运行,从phase
上看是串行运行,有层次化的。Phase
机制的9个phase
是串行运行的,不同组件中的同一个phase
都运行完毕后才能进入下一个phase
运行,同一个phase
在不同组件中的运行也是由一定顺序的,build
和 final
是自顶向下。
启动sequence
有很多的方法:常用的方法有使用default sequence
进行调用,其会将对应的sequence
与 sequencer
绑定,当dirver
请求获得req
时,sequencer
就会调用对应的sequence
去运行body
函数,从而产生req
。
除此之外,还可以使用start
函数进行,其参数主要就是对应的需要绑定的sequencer
和该类的上层sequence
。如此,就可以实现启动sequence
的功能。
注意:一般仿真开始结束会在sequence
中 raise objection
和 drop objection
首先,我们要了解寄存器对于设计的重要性,其是模块间交互的窗口,我们可以通过读寄存器值去观察模块的运行状态,通过写寄存器去控制模块的配置和功能改变。
然后,为什么我们需要RAL呢?由于前面寄存器的重要性,我们可以知道,如果我们不能先保证我们寄存器的读写正确,那么就不用谈后续 DUT是否正确了,因此,寄存器的验证是排在首要位置的。
那么我们应该用什么方法去读写和验证寄存器呢?采用RAL寄存器模型去测试验证,是目前最成功的方法吧,寄存器模型独立于TB
之外,我们可以搭建一个测试寄存器的agent
,去通过前门或者后门访问去控制DUT
的寄存器,使得 DUT
按照我们的要求去运行。
除此之外,UVM
中内建了很多RAL
的sequence
,用于帮助我们去检测寄存器,除此之外,还有一些其他的类和变量去帮助我们搭建,以提高RAL
的可重用性和便捷性还有更全的覆盖率。
前门访问和后门访问的比较
前门访问,顾名思义指的是在寄存器模型上做的读写操作,最终会通过总线UVC来实现总线上的物理时序访问,因此是真实的物理操作。
后门访问,指的是利用UVM DPI (uvm_hdl_read()、uvm_hdl_deposit())
,将寄存器的操作直接作用到DUT内的寄存器变量,而不通过物理总线访问。
前门访问在使用时需要将path
设置为UVM_FRONTDOOR
在进行后门访问时,用户首先需要确保寄存器模型在建立时,是否将各个寄存器映射到了DUT
一侧的HDL
路径:使用add_hdl_path
5. 从上面的差别可以看出,后门访问较前门访问更便捷更快一些,但如果单纯依赖后门访问也不能称之为“正道”。
6. 实际上,利用寄存器模型的前门访问和后门访问混合方式,对寄存器验证的完备性更有帮助。
后门访问
在通过前门配置寄存器A之后,再通过后门访问来判断HDL地址映射的寄存器A变量值是否改变,最后通过前门访问来读取寄存器A的值。
mirror、desired、actual value()
我们在应用寄存器模型的时候,除了利用它的寄存器信息,也会利用它来跟踪寄存器的值。寄存器有很多域,每一个域都有两个值。
寄存器模型中的每一个寄存器,都应该有两个值,一个是镜像值( mirrored value
) , 一个是期望值(desired value
) 。
期望值是先利用寄存器模型修改软件对象值,而后利用该值更新硬件值;镜像值是表示当前硬件的已知状态值。
镜像值往往由模型预测给出,即在前门访问时通过观察总线或者在后门访问时通过自动预测等方式来给出镜像值
镜像值有可能与硬件实际值不一致
UVM提供了两种用来跟踪寄存器值的方式,我们将其分为自动预测(auto prediction
)和显式预测( explicit
)。
如果用户想使用自动预测的方式,还需要调用函数uvm_reg_map::set_auto predict()
两种预测方式的显著差别在于,显式预测对寄存器数值预测更为准确,我们可以通过下面对两种模式的分析得出具体原因。自动预测
如果用户没有在环境中集成独立的predictor
,而是利用寄存器的操作来自动记录每一次寄存器的读写数值,并在后台自动调用predict()
方法的话,这种方式被称之为自动预测。
这种方式简单有效,然而需要注意,如果出现了其它一些sequence
直接在总线层面上对寄存器进行操作(跳过寄存器级别的write/read
操作,或者通过其它总线来访问寄存器等这些额外的情况,都无法自动得到寄存器的镜像值和预期值。显式预测
更为可靠的一种方式是在物理总线上通过监视器来捕捉总线事务,并将捕捉到的事务传递给外部例化的predictor
,该predictor
由UVM
参数化类uvm_reg_predictor
例化并集成在顶层环境中。
在集成的过程中需要将adapter
与map
的句柄也一并传递给predictor
,同时将monitor
采集的事务通过analysis port
接入到predictor
一侧。
这种集成关系可以使得,monitor
一旦捕捉到有效事务,会发送给predictor
,再由其利用adapter
的桥接方法,实现事务信息转换,并将转化后的寄存器模型有关信息更新到map
中。
默认情况下,系统将采用显式预测的方式,这就要求集成到环境中的总线UVC monitor
需要具备捕捉事务的功能和对应的analysis port
,以便于同predictor
连接。
AHB(Advanced High-performance Bus)
高级高性能总线。APB(Advanced Peripheral Bus)
高级外围总线AXI (Advanced eXtensible Interface)
高级可拓展接口
AHB
主要是针对高效率、高频宽及快速系统模块所设计的总线,它可以连接如微处理器、芯片上或芯片外的内存模块和DMA
等高效率模块。
APB
主要用在低速且低功率的外围,可针对外围设备作功率消耗及复杂接口的最佳化。APB
在AHB
和低带宽的外围设备之间提供了通信的桥梁,所以APB
是AHB
的二级拓展总线。
AXI
高速度、高带宽,管道化互联,单向通道,只需要首地址,读写并行,支持乱序,支持非对齐操作,有效支持初始延迟较高的外设,连线非常多。
AHB协议
1. AHB的组成
Master:
能够发起读写操作,提供地址和控制信号,同一时间只有1个Master
会被激活。
Slave:
在给定的地址范围内对读写操作作响应,并对Master
返回成功、失败或者等待状态。
Arbiter:
负责保证总线上一次只有1个Master
在工作。仲裁协议是规定的,但是仲裁算法可以根据应用决定。
Decoder:
负责对地址进行解码,并提供片选信号到各Slave
。每个AHB
都需要1个仲裁器和1个中央解码器。
2. AHB基本信号(经常会问Htrans和Hburst,以及AHB的边界地址怎么确定
HADDR:32位系统地址总线。
HTRANS:M指示传输状态,NONSEQ、SEQ、IDLE、BUSY。
HWRITE:传输方向1-写,0-读。
HSIZE:传输单位。
HBURST:传输的burst类型,SINGLE、INCR、WRAP4、INCR4等。
HWDATA:写数据总线,从M写到S。
HREADY:S应答M是否读写操作传输完成,1-传输完成,0-需延长传输周期。
HRESP:S应答当前传输状态,OKAY、ERROR、RETRY、SPLIT。
HRDATA:读数据总线,从S读到M。
APB协议及读写操作:
1. APB的状态转移
2. APB写操作
3. APB读操作
Assertion
可以分为立即断言和并发断言。
立即断言的话就是和时序无关,比如我们在对激励随机化时,我们会使用立即断言,如果随机化出错我们就会触发断言报错。
并发断言的话主要是用来检测时序关系的,由于在很多模块或者总线中,单纯使用覆盖率或者事务check
并不能完全检测多个时序信号之间的关系,但是并发断言却可以使用简洁的语言去监测,除此之外,还可以进行覆盖率检测。
并发断言的用法的话,主要是有三个层次:
序列sequence
编写,将多个信号的关系用断言中特定的操作符进行表示;
属性property
的编写,它可以将多个sequence
和多个property
进行嵌套,外加上触发事件;
assert
的编写,调用property
就可以。编写完断言后我们可以将它用在很多地方,比如DUT
内部,或者在top
层嵌入DUT
中,还可以在interface
处进行编写,基本能够检测到信号的地方都可以进行断言检测。
a[*3]指的是:重复3次a,且其与前后其他序列不能有间隔,a中间也不可有间隔。
a[->3]指的是:重复3次,其 a中间可以有间隔,但是其后面的序列与a之间不可以有间隔。
a[=3]指的是:只要重复3次,中间可随意间隔。
主要会考虑三个方面吧,代码覆盖率,功能覆盖率,断言覆盖率。
代码覆盖率,主要由行覆盖率、条件覆盖率、fsm
覆盖率、跳转覆盖率、分支覆盖率,他们是否都是运行到的,比如 fsm
,是否各个状态都运行到了,然后不同状态之间的跳转是否也都运行到了。
功能覆盖率的话主要是自己编写covergroup
和coverpoint
去覆盖我们想要覆盖的数据和地址或者其他控制信号。
断言覆盖率主要检测我们的时序关系是否都运行到了,比如总线的地址数据读写时序关系是否都有实现。
Condition
又称为条件覆盖率,当条件覆盖率未被覆盖时,我们需要通过查看覆盖率报告去定位哪些条件没有被覆盖到,是因为没有满足该条件的前提条件还是因为根本就遗漏了这些情况,根据这个我们去编写相应的case
,进而将其覆盖到。
功能覆盖率主要是针对spec
文档中功能点的覆盖检测 -code
覆盖率主要是针对RTL
设计代码的运行完备度的体现,其包括行覆盖率、条件覆盖率、FSM
覆盖率、跳转覆盖率、分支覆盖率(只要仿真就可以,看看DUT
的哪些代码没有动,如果有一部分代码一直没动,看一下是不是case
没写到)。
功能覆盖率和代码覆盖率两者缺一不可,功能覆盖率表示着代设计是否具备这些功能,代码覆盖率表示我们的测试是否完备,代码是否冗余。当功能覆盖率高而代码覆盖率低时,表示covergroup
是不是写少了,case
写少了;或者代码冗余。当功能覆盖率很低而代码覆盖率高时,表示代码设计是不是全面,功能点遗漏;covergroup
写的是不是冗余了。只有当两者覆盖率都高的时候才表明我们验证的大部分是可靠的。
代码覆盖率很难达到100%,一般情况下达到90%多已经非常不错了,如果有一部分代码没有被触动到,需要有经验的验证工程师去分析,如果确实没啥问题,就可以签字通过了
对于流程的话
首先第一步我会先去查看spec
文档,将模块的功能和接口总线时序搞明白,尤其是工作的时序,这对于后续写TB
非常重要;
第二步我会根据功能点去划分我的TB
应该怎么搭建,我的case
大致会有哪些,这些功能点我应该如何去覆盖,时序应该如何去检查,总结列出这样的一个清单;
第三步开始去搭建我们的TB
,包括各种组件,和一些基础的 sequence
还有test
,暂时先就写一两个基础的sequence
,然后还有一些环境配置参数的确定等,最后能够将TB
正常运行,保证无误;
第四步就是根据清单去编写sequence
和 case
,然后去仿真,保证仿真正确性,收集覆盖率;
第五步就是分析收集的覆盖率,然后查看覆盖率报告去分析还有哪些没有被覆盖,去写一些定向case
,和更换不同的seed
去仿真;
第六步就是回归测试regression
,通过不同的 seed
去跑,收集覆盖率和检测是否有其它bug
;
第七步就是总结
刚开始的难点还是TB的搭建,想要搭建出一个可重用性很高的TB,配置灵活的TB还是有一定困难,对于哪些参数应该放在配置类,哪些参数应该放在事务类的抉择,哪些单独配置。
除此之外,还有就是时序的理解,这对于driver
和monitor
还有sequence
和 assertion
的编写至关重要,只有正确理解时序才能编写出正确的TB
。
最后就是实现覆盖率的尽可能高,这也是比较困难的,刚开始的case
好写,也比较快就可以达到较高的覆盖率,但是那些边边角角的case
需要自己去琢磨,去分析还需要写什么case
。这些难点就是重点,还要能够自动化监测判断是否正确。
这个问题面试的时候经常问,建议面试之前考虑一下,再做决定
我是使用UVM
验证方法学搭建的TB
,然后在VCS
平台进行仿真的。目录结构的话:主要由RTL
文件、doc
文件、tb
文件、sim
文件、script
文件这几部分。
UVM的优点:UVM有各个机制、促进验证平台的标准化,UVM中test sequence
和验证平台是隔离独立的,可以更好的控制激励而不需要重新设计agent
. 改变测试sequence
可以简单高效提高代码覆盖率。UVM
支持工业标准,这会促进验证平台标准化。此外,UVM
通过OOP
(面向对象编程)的特点(例如继承)以及使用覆盖组件提高了重复使用率。因此UVM环境方便移植,架构清晰,组件连接方便,有利于进行大规模的验证。
UVM的缺点:代码冗余,工作量大,运行速度有缺失
无论是重载的类(parrot
)还是被重载的类(bird
),都要在定义时注册到factory
机制中。
被重载的类(bird
)在实例化时,要使用factory
机制式的实例化方式,而不能使用传统的new
方式。
最重要的是,重载的类(parrot
)要与被重载的类(bird
)有派生关系。重载的类必须派生自被重载的类,被重载的类必须是重载类的父类。
UVM
更高的层次更接近用户,为了让用户少和底层组件打交道,所以层次越高优先级越高,高层次的set
会覆盖底层次的set
,如果是层次相同再看时间先后顺序,谁发生的晚谁有效,时间靠后的会覆盖之前的。
阶段1(定义)。
功能特性提取
特性覆盖率创建及映射
VIP的架构
阶段2(VIP基本搭建)
driver,sequencer,monitor (少量特性实现)。
实现基本的端到端的sequence
阶段3(完成monitor与scoreboard)
完成monitor -100%实现(checkers,assertions)
完成scoreboard -100%实现(数据完整性检查)
在monitor中,完成监测到的transaction与function coverage实现映射。
为映射更多的基本功能覆盖率,创建其它sequences。
阶段4(扩充test和sequence阶段)
实现更多sequences,从而获得80%的功能覆盖率
阶段5(完成标准)
Sequence最终可以实现100%的功能覆盖率。
回归测试结果和最终的总结报告。
验证流程:
看spec
文档和协议,将DUT
的功能和接口总线时序搞明白
制定验证计划和测试点分解
写VIP
或者是用别人给的VIP
,搭建验证环境和TB
,包括各种组件,各个模块的pkg
,基础的 sequence
还有test
,暂时先就写一两个基础的 sequence
,然后还有一些环境配置参数的确定等,最后能够将TB
正常运行,保证无误;
根据测试点编写sequence
和 case
,然后去仿真,保证仿真正确性,收集覆盖率;
分析收集的覆盖率,然后查看覆盖率报告去分析还有哪些没有被覆盖,去写一些定向case
,和更换不同的seed
去仿真;
回归测试regression
,通过不同的seed
去跑,收集覆盖率和检测是否有其它bug
;
总结
验证环境的搭建:
driver
给 DUT
发送激励,montior
监测 DUT
输出的数据,参考模型( reference model
)能实现与 DUT
相同的功能,scoreboard
把 monitor
接受到的数据和 reference model
的输出数据进行比对,如果比对成功就表示 DUT
能完成设计的功能。
factory
机制的实现被集成在了一个宏中:uvm_component_utils
。
这个宏最主要的任务是,将字符串登记在UVM
内部的一张表中,这张表是factory
功能实现的基础。只要在定义一个新的类时使用这个宏,就相当于把这个类注册到了这张表中。这样,factory
机制可以实现:根据一个字符串自动创建一个类的实例,并且调用其中的函数(function
)和任务(task
),这个类的main_phase
就会被自动调用。
TLM
通信的步骤:
分辨出initiator
和target,producer
和consumer
。
在target
中实现tlm
通信方法。
在俩个对象中创建tlm
端口。
在更高层次中将俩个对象进行连接。
端口类型有三种:
port
,一般是initiator
的发起端。
export
,作为initiator
和target
的中间端口。
imp
,只能作为target
接受request
的末端。
多个port
可以连接同一个export
或imp
,但是单个port
或export
不能连接多个imp
。
端口的连接:通过connect
函数进行连接,例如A(initiator)
与B
进行连接,可以使用A.port.connect(B.export)
uvm_*_imp#(T,IMP);IMP定义中第一个参数T是这个IMP传输的数据类型,第二个参数IMP是实现这个接口所在的component
。
转载于:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。