hash
,通过一个索引值和一个数据组成,索引值必须是唯一的。Fork join
:内部 begin end
块并行运行,直到所有线程运行完毕才会进入下一个阶段。Fork join_any
:内部 begin end
块并行运行,任意一个begin end
块运行结束就可以进入下一个阶段。Fork join_none
:内部 begin end
块并行运行,无需等待可以直接进入下一个阶段。wait fork
:会引起调用进程阻塞,直到它的所有子进程结束,一般用来确保所有子进程(调用进程产生的进程,也即一级子进程)执行都已经结束。disable fork
:用来终止调用进程 的所有活跃进程, 以及进程的所有子进程。多线程之间同步主要由mailbox、event、 semaphore
三种进行一个通信交互。
mailbox邮箱
:主要用于两个线程之间的数据通信,通过put函数和 get 函数还有peek函数进行数据的发送和获取。Event
:事件主要用于两个线程之间的一个同步运行,通过事件触发和事件等待进行两个线程间的运行同步。使用@(event)或者 wait(event.trigger)进行等待,->进行触发。Semaphore
:旗语主要是用于对资源访问的一个交互,通过key的获取和返回实现一个线程对资源的一个访问。使用put和 get函数获取返回key。一次可以多个。Interface
是一组接口,用于对信号进行一个封装,捆扎起来。如果像verilog
中对各个信号进行连接,每一层我们都需要对接口信号进行定义,若信号过多,很容易出现人为错误,而且后期的可重用性不高。因此使用interface
接口进行连接,不仅可以简化代码,而且提高可重用性,除此之外,interface
内部提供了其他一些功能,用于测试平台与DUT之间的同步和避免竞争。Clocking block
:在interface
内部我们可以定义clocking
块,可以使得信号保持同步,对于接口的采样vrbg和驱动有详细的设置操作,从而避免TB
与 DUT
的接口竞争,减少我们由于信号竞争导致的错误。采样提前,驱动落后,保证信号不会出现竞争。封装、继承和多态
virtual
声明,这样当调用基类句柄指向扩展类时,方法 会根据对象去识别,调用扩展类的方法,而不是基类中的。而基类和扩展类中方法有着同样的名字,但能够准确调用,叫做多态。Factory
机制也叫工厂机制,其存在的意义就是为了能够方便的替换TB中的实例或者已注册的类型。一般而言,在搭建完TB后,我们如果需要对TB进行更改配置或者相关的类信息,我们可以通过使用factory
机制进行覆盖,达到替换的效果,从而大大提高TB的可重用性和灵活性。Interface
是一组接口,用于对信号进行一个封装,捆扎起来。如果像 verilog
中对各个信号进行连接,每一层我们都需要对接口信号进行定义,若信号过多,很容易出现人为错误,而且后期的可重用性不高。因此使用interface
接口进行连接,不仅可以简化代码,而且提高可重用性,除此之外,interface
内部提供了其他一些功能,用于测试平台与DUT
之间的同步和避免竞争。Clocking block
:在interface
内部我们可以定义clocking
块,可以使得信号保持同步,对于接口的采样和驱动有详细的设置操作,从而避免TB
与 DUT
的接口竞争,减少我们由于信号竞争导致的错误。采样提前,驱动落后,保证信号不会出现竞争。UVM的启动总结:
a) 虽然SV可以通过层次化的interface的索引完成传递,但是这种传递方式不利于软件环境的封装和复用。通过使用uvm_config_db配置机制来传递接口,可以将接口的传递与获取彻底分离开。
b) 接口传递从硬件世界到UVM环境可以通过uvm_config_db来实现,在实现过程中应当注意:
c) 接口传递应发生在run_test()之前。这保证了在进入build_phase之前,virtual interface已经被传递到uvm_config_db中。
d) 用户应当把interface与virtual interface区分开来,在传递过程中的类型应当为virtual interface,即实际接口的句柄。
UVM
其实就是SV
的一个封装,将我们在搭建测试平台过程中的一些重复性和重要的工作进行封装,从而使我们能够快速的搭建一个需要的测试平台,并且可重用性还高。但是UVM
又不仅仅是封装。
Ref参数类型是引用
ref
获取最佳性能,如果不希望子程序改变数组的值,可以使用const ref
类型UVM中component
也是由object
派生出来的,不过相比于object
, component
有很多其没有的属性,例如phase
机制和树形结构等。在UVM
中,不仅仅需要component
这种较为复杂的类,进行TB
的层次化搭建,也需要object
这种基础类进行TB的事务搭建和一些环境配置等。Item是object
Sequencer
:负责将数据转给driver
driver
负责数据的发送;driver
有时钟/时序的概念。Agent
:其实只是简单的把driver
,monitor
和sequencer
封装在一起。Agent
:对应的是物理接口协议,不同的接口协议对应不同的agent
,一个平台通常会有多个 agent
。Env
:则相当于是一个特大的容器,将所有成员包含进去。Virtual sequencer
主要用于对不同的agent
进行协调时,需要有一定顶层的sequencer
对内部各个agent
中的sequencer
进行协调virtual sequencer
是面向多个sequencer
的多个sequence
群,而sequencer
是面向一个sequencer
的sequence
群。Virtual sequencer
桥接着所有底层的sequencer
的句柄,其本身也不需要传递item
,不需要和driver
连接。只需要将其内部的底层sequencer
句柄和sequencer
实体对象连接。在多个sequence
同时向sequencer
发送item
时,需要有ID信息表明该item
从哪个sequence
来,ID信息在sequence
创建item
时就赋值了。
spec
比较来发现,design
是否行为正确,需要按verification plan
来比较进度。用来衡量哪些设计特征已经被测试程序测试过的一个指标这个问题很重要,建议好好准备,面试的时候经常会问~
芯片架构-RTL设计-功能仿真-综合&扫描链的插入(DFT)-等价性检查-形式验证-静态时序分析(STA)-布局规划-布局布线-布线图和原理图比较-设计规则检查-GDII
find的队列应该是返回队列的值,一般的话是和with配合使用,find index应该是返回索引值
review
验证功能点,测试用例,以及特殊情况下的波形等。严格意义上有2种:
用来触发事件时,使用->;用来等待事件使用@或者wait。
除了driver、monitor、agent、model、scoreboard、env、test之外全部用uvm_object。
get_next_item()
是一个阻塞调用,直到存在可供驱动的sequence item
为止,并返回指向sequence item
的指针。try_next_item()
是非阻塞调用,如果没有可供驱动的sequence item
,则返回空指针。And
指的是两个序列具有相同的起始点,终点可以不同。Intersect
指的是两个序列具有相同的起始点和终点。Or
指的是两个序列只要满足一个就可以Throughout
指的是满足前面要求才能执行后面的序列break
语句结束整个循环。continue
立即结束本次循环,继续执行下一次循环。return
语句会终止函数的执行并返回函数的值(如果有返回值的话)。return
之后,function
里剩下的语句不能执行,其是终止函数的执行,并返回函数的值。主要是编写sequence
,然后在body
里面根据测试功能要求写相应的激励,然后再通过ref_model
和checker
判断功能是否实现?
可以写脚本让它们自动执行,例如makefile...
例如让你写abcd四个信号在时钟沿处监测,当cd同时为1时,在时钟的前两个周期要ab同时为1的断言
constraint_mode(0)
关闭默认范围的约束块constraint_mode(1)
是打开约束soft
关键字修饰特定的约束语句,这样既可以让变量在一般的情况下取默认值,也可以直接给变量赋默认值范围外的取值。队列的使用方法:insert,delete,push_back和pop_front
Push
插入,pop
取出Front
前边,back
后边
rand
修饰符:rand
修饰的变量,每次随机时,都在取值范围内随机取一个值,每个值被随机到的概率是一样的,就想掷骰 子一样。randc
修饰符:randc
表示周期性随机,即所有可能的值都取到过后,才会重复取值initiator
到target
之间的数据流向是单一方向的initiator
和target
,但是数据流向在端对端之间是双向的initiator
与target
之间的相同TLM端口数目超过一个时的处理解决办法。initiator
先生成数据Tt
,同时将该数据传送至target
。initiator
从target
获取数据Tt
,而target
中的该数据Tt
则应消耗。initiator
从target
获取数据Tt
,而target
中的该数据Tt
还应保留。TLM FIFO
:可以进行数据缓存,功能类似于mailbox
,不同的地方在于uvm_tlm_fifo
提供了各种端口(put、get、peek)
供用户使用analysis port
:一端对多端,用于多个组件同时对一个数据进行处理,如果这个数据是从同一个源的TLM
端口发出到达不同组件,则要求该端口能够满足一端到多端,如果数据源端发生变化需要通知跟它关联的多个组件时,我们可以利用软件的设计模式之一观察者模式实现,即广播模式analysis TLM FIFO
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
。copy()
函数(如list.copy()
),或者使用copy
模块的copy()
函数。深拷贝只能使用copy
模块的deepcopy()
,所以使用前要导入:from copy import deepcopy
protected
,那么只有该类或者子类可以访问成员,而外部无法访问。local
,那么只有该类可以访问成员,子类和外部均无法访问。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)build_pase
connect_phase
end_of_elaboration_phase
start_of_simulation_phase
run_pase
extract_phase
check_phase
report_phase
final_phase
其中,run_phase按照以下顺序自上而下执行:
pre_reset_phase
reset_phase
post_reset_phase
pre_configure_phase
configure_phase
post_configure_phase
pre_main_phase
main_phase
post_main_phase
pre_shutdown_phase
shutdown_phase
post_shutdown_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
删除。都可以。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
消化之后,它的生命一般来讲才会结束。sequence
可以包含一些有序组织起来的item
实例,考虑到item
在创建后需要被随机化,sequence
在声明时也需要预留一些可供外部随机化的变量,这些随机变量一部分是用来通过层级传递约束来最终控制item
对象的随机变量,一部分是用来对item
对象之间加以组织和时序控制的。(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
全部发送完毕后,它的生命周期也就结束了。仲裁特性
锁定机制
Virtual
含义就是其sequencer
并不需要传递item
,也不会与driver
连接,其只是一个去协调各个sequencer
的中央路由器。通过virtual sequencer
我们可以实现多个agent
的多个sequencer
他们的 sequence
的调度和可重用。Virtual sequence
可以组织不同sequencer
的sequence
群落。
UVM
中有sequence
机制,以往如果我们使用SV
进行TB
搭建时,我们一般会采用driver
一个类进行数据的参数,转换,发送,或者使用genetor
和driver
两个进行,这种方式可重用性很低,而且代码臃肿;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
TB
之外,我们可以搭建一个测试寄存器的agent
,去通过前门或者后门访问去控制DUT
的寄存器,使得 DUT
按照我们的要求去运行。UVM
中内建了很多RAL
的sequence
,用于帮助我们去检测寄存器,除此之外,还有一些其他的类和变量去帮助我们搭建,以提高RAL
的可重用性和便捷性还有更全的覆盖率。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
) 。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
高速度、高带宽,管道化互联,单向通道,只需要首地址,读写并行,支持乱序,支持非对齐操作,有效支持初始延迟较高的外设,连线非常多。Master:
能够发起读写操作,提供地址和控制信号,同一时间只有1个Master
会被激活。Slave:
在给定的地址范围内对读写操作作响应,并对Master
返回成功、失败或者等待状态。Arbiter:
负责保证总线上一次只有1个Master
在工作。仲裁协议是规定的,但是仲裁算法可以根据应用决定。Decoder:
负责对地址进行解码,并提供片选信号到各Slave
。每个AHB
都需要1个仲裁器和1个中央解码器。2. AHB基本信号(经常会问Htrans和Hburst,以及AHB的边界地址怎么确定)
3. APB读操作
Assertion
可以分为立即断言和并发断言。check
并不能完全检测多个时序信号之间的关系,但是并发断言却可以使用简洁的语言去监测,除此之外,还可以进行覆盖率检测。sequence
编写,将多个信号的关系用断言中 特定的操作符进行表示;property
的编写,它可以将多个sequence
和多个property
进行嵌套,外加上触发事件;assert
的编写,调用property
就可以。编写完断言后我们可以将它用在很多地方,比如DUT
内部,或者在top
层嵌入DUT
中,还可以在interface
处进行编写,基本能够检测到信号的地方都可以进行断言检测。fsm
覆盖率、跳转覆盖率、分支覆盖率,他们是否都是运行到的,比如 fsm
,是否各个状态都运行到了,然后不同状态之间的跳转是否也都运行到了。covergroup
和coverpoint
去覆盖我们想要覆盖的数据和地址或者其他控制信号。Condition
又称为条件覆盖率,当条件覆盖率未被覆盖时,我们需要通过查看覆盖率报告去定位哪些条件没有被覆盖到,是因为没有满足该条件的前提条件还是因为根本就遗漏了这些情况,根据这个我们去编写相应的case
,进而将其覆盖到。
spec
文档中功能点的覆盖检测-code
覆盖率主要是针对RTL
设计代码的运行完备度的体现,其包括行覆盖率、条件覆盖率、FSM
覆盖率、跳转覆盖率、分支覆盖率(只要仿真就可以,看看DUT
的哪些代码没有动,如果有一部分代码一直没动,看一下是不是case
没写到)。covergroup
是不是写少了,case
写少了;或者代码冗余。当功能覆盖率很低而代码覆盖率高时,表示代码设计是不是全面,功能点遗漏;covergroup
写的是不是冗余了。只有当两者覆盖率都高的时候才表明我们验证的大部分是可靠的。对于流程的话
spec
文档,将模块的功能和接口总线时序搞明白,尤其是工作的时序,这对于后续写TB
非常重要;TB
应该怎么搭建,我的case
大致会有哪些,这些功能点我应该如何去覆盖,时序应该如何去检查,总结列出这样的一个清单;TB
,包括各种组件,和一些基础的 sequence
还有test
,暂时先就写一两个基础的sequence
,然后还有一些环境配置参数的确定等,最后能够将TB
正常运行,保证无误;sequence
和 case
,然后去仿真,保证仿真正确性,收集覆盖率;case
,和更换不同的seed
去仿真;regression
,通过不同的 seed
去跑,收集覆盖率和检测是否有其它bug
;driver
和monitor
还有sequence
和 assertion
的编写至关重要,只有正确理解时序才能编写出正确的TB
。case
好写,也比较快就可以达到较高的覆盖率,但是那些边边角角的case
需要自己去琢磨,去分析还需要写什么case
。这些难点就是重点,还要能够自动化监测判断是否正确。这个问题面试的时候经常问,建议面试之前考虑一下,再做决定
我是使用UVM
验证方法学搭建的TB
,然后在VCS
平台进行仿真的。目录结构的话:主要由RTL
文件、doc
文件、tb
文件、sim
文件、script
文件这几部分。
test sequence
和验证平台是隔离独立的,可以更好的控制激励而不需要重新设计agent
. 改变测试sequence
可以简单高效提高代码覆盖率。UVM
支持工业标准,这会促进验证平台标准化。此外,UVM
通过OOP
(面向对象编程)的特点(例如继承)以及使用覆盖组件提高了重复使用率。因此UVM环境方便移植,架构清晰,组件连接方便,有利于进行大规模的验证。parrot
)还是被重载的类(bird
),都要在定义时注册到factory
机制中。bird
)在实例化时,要使用factory
机制式的实例化方式,而不能使用传统的new
方式。parrot
)要与被重载的类(bird
)有派生关系。重载的类必须派生自被重载的类,被重载的类必须是重载类的父类。UVM
更高的层次更接近用户,为了让用户少和底层组件打交道,所以层次越高优先级越高,高层次的set
会覆盖底层次的set
,如果是层次相同再看时间先后顺序,谁发生的晚谁有效,时间靠后的会覆盖之前的。
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)
component
。