【提要】收集整理了一些网络上和我自己在秋招、实习时遇到的题目,适合数字验证方向求职的同学进行差缺补漏或者应对八股时的速成。 对于时间比较充裕并且有条件的同学,还是强烈建议找个实习来提升自己的能力以及校招竞争性,独立完成了一两个真实项目后,能大大加深对验证工作的整体理解,应对八股问题自然也是更游刃有余。
1、什么是UVM?它的优势是什么?
UVM(Universal Verification Methodology)是一个标准化的用于验证设计的方法学。其优势包括:重用性、VIP即插即用、通用性、独立于仿真器、支持CDV(coverage driven verification)、支持CRV(constraint random verification)等等。是一种对SV的封装,方便验证人员使用具有复用性的验证平台对DUT进行完备的测试验证。
2、uvm_component和uvm_object有什么区别?
uvm_component: 在build_phase之后就一直存在于整个仿真周期。通过interface连接到DUT或者通过TLM port连接到其他uvm_component。通过configure机制和phase机制控制uvm_component的层次结构和仿真行为。component用于构建环境的树状结构,特性更丰富。
uvm_object: 从一个uvm_component生成,然后传递到另一个uvm_component之后就会消失。不会连接到任何组件,也不存在phase机制。
3、为什么需要phase机制,不同的phase有什么区别?
phase机制用来控制和同步不同uvm_component的仿真行为,解决代码块顺序对代码执行的影响,简单来说就是在不同时间做不同的事情,可以很清晰地将UVM仿真阶段层次化。可以根据是否消耗仿真时间区分为function phase和task phase,共九个phase,只有run_phase是task phase。
4、uvm phase仿真是怎么开始启动的?
通过在顶层调用run_test,run_test任务会首先创建test top,然后调用所有的phase,开始仿真。
5、为什么build_phase是top-down phase,而connect_phase是bottom-up phase?
build_phase需要验证平台根据高层次组件的配置来决定建立低层次的组件,所以其是top-down phase。
connect_phase需要在build_phase之后完成验证组件之间TLM连接。
6、什么是factory automation?
工厂机制是UVM的真正魅力所在,也是软件的一种设计模式。工厂的存在就是为了更方便的覆盖替换验证环境中的实例或注册了的类型,从而避免了在原有代码上的改动,便于复用。注册——创建——覆盖:uvm_component/object_utils——create——set_type_override
7、什么是事物级建模(Transaction Level Modelling)?
TLM(Transaction Level Modeling)是component之间的一种通信方式,这种通信方式每次传输一个transaction,一个transaction是指将一组信息(如地址,数据等等)封装成一个包发送出去。
TLM通信步骤分为:
1、定义TLM传输中的数据类型,定义req类和rsp类。
2、分别在各个层次的component中声明和创建TLM端口对象。
3、在imp端口类中要实现需要提供给initiator的可调用方法。
4、通过connect()函数完成端口之间的连接。
需要注意的是,必须在imp端口类中实现对应方法,否则端口即使连接也无法实现数据传输。
8、什么是TLM ports和exports?
port: 经常作为initiator的发起端,initiator凭借port才可以访问target中的TLM方法;
export:作为initiator和target的中间层次;
imp:只能作为target接收request的末端。
9、什么是TLM FIFOs?
如果producer组件和consumer组件需要独立运行,则可以使用TLM FIFO进行事务通信。在这种情况下,producer组件生成事务并将其“puts”到FIFO,而consumer组件一次从FIFO获取一个事务并对其进行处理。
10、TLM的get()和peek()操作之间有什么区别?
get()操作将从TLM FIFO返回一个事务(如果存在的话),并且还会从FIFO中删除该事务。如果FIFO中没有可用的事务,它将阻塞并等待,直到FIFO至少有一个事务。
peek()操作将从TLM FIFO返回事务(如果存在的话),从FIFO中复制该事务。这也是一个阻塞调用,如果FIFO没有可用事务,它将等待。
11、TLM fifo上的get()和try_get()操作之间有什么区别?
get() 是从TLM FIFO获取事务的阻塞调用。因此如果FIFO中没有可用的事务,则任务get()将等待。
try_get() 是一个非阻塞调用,即使FIFO中没有可用的事务,也会立即返回。try_get()的返回值指示是否返回有效事务项。
12、analysis ports和TLM ports之间有什么区别?analysis FIFOs和TLM FIFO有什么区别?analysis ports/ FIFO在哪里使用?
TLM ports/FIFO用于两个组件之间的事务级别通信,这两个组件具有使用put/get方法建立的通信通道。
Analysis ports/FIFOs是另一种事务通信通道,用于组件将事务广播到多个组件。
TLM ports /FIFO常用于driver和sequencer之间的连接。
analysis ports/FIFOs常用于monitor将接收的事务广播到scoreboard或reference model。
13、sequence和sequence item有什么区别?
sequence item是一个对象,其建模了两个验证组件之间传输的信息(有时也可以称为transaction)。例如:读操作和写操作中的地址和数据信息。
sequence是由driver驱动的sequence item序列模式,由其body()方法实现。例如:连续读取10次事务。
14、uvm_transaction和uvm_sequence_item有什么区别?
uvm_transaction是更基础的类,提供基本的事务功能,通常作为其他事务类的基类使用。
uvm_sequence_item继承自uvm_transaction,增加了与 UVM 序列相关的功能,专用于UVM 序列中的事务数据项,例如支持 UVM 序列中的事务调度和同步。
15、copy()、clone()和create()方法之间有什么区别?
create()方法用于构造一个对象。
copy()方法用于将一个对象复制到另一个对象。
clone()方法同时完成对象的创建和复制。
16、ACTIVE agent与PASSIVE agent有何不同?
ACTIVE agent是可以在其操作的接口上生成激励,其包含driver和sequencer、monitor。
PASSIVE agent不生成激励,只能监视接口,这意味着在PASSIVE agent中将不会创建driver和sequencer,只含有monitor。
ACTIVE和PASSIVE的配置通过类型为uvm_active_passive_enum的变量设置IS_ACTIVE或IS_PASSIVE完成。
17、什么是driver和sequencer,为什么需要它们?
driver的作用是按照接口协议将事务对象驱动到总线,driver从sequencer中获取数据。
sequencer生成激励数据并将其传递给driver,将事务(sequence_items)从sequence发送到Driver,并将Driver的响应反馈给sequence。 同时尝试访问Driver以激励设计接口的多个sequences进行仲裁。UVM这样划分的目的是把各个component要实现的功能分解的更加清晰、易复用。
18、哪个方法可以激活UVM验证平台,如何调用它?
run_test(静态方法)用来激活UVM验证平台。
通常在顶层的initial begin…end中调用,并且它使用一个参数指定要运行的test case。run_test()在build_phase()中执行test class的构造函数,并进一步构造层次化的env/agent/driver/sequencer对象。
19、运行sequence需要什么步骤?
启动sequence需要执行三个步骤:
1、创建一个序列。使用工厂创建方法创建一个序列:my_sequence_c seq;seq = my_sequence_c ::type_id :: create(“ my_seq”);
2、配置或随机化序列。seq.randomize();
3、开始一个sequence。使用sequence.start()方法启动序列。start方法需要输入一个指向sequencer的参数。
19、sequence中的pre_body()和post_body()函数是什么?它们总是被调用么?
pre_body()是sequence类中的方法,该方法在body()方法之前调用,然后调用post_body()方法。uvm_sequence:: start()有一个可选参数,如果将其设置为0,将不调用这些方法。
20、在sequencer上启动sequence时,如何指定sequence的优先级?
通过将优先级相对值参数传递给sequence的start()方法来指定优先级,第三个参数指定优先级,数值越大优先级越高。
21、sequence如何才能独占访问sequencer?
有两种机制允许sequence获得对sequencer的独占访问权:
lock()和unlock(): sequence可以调用sequencer的lock方法,以通过sequencer的仲裁机制获得对driver的独占访问权限。如果还有其他标记为更高优先级的sequence,则此sequence需要等待。
授予lock权限后,其他sequence将无法访问driver,直到该sequence在sequencer上调用unlock()方法。lock方法是阻塞调用,直到获得lock权限才返回。
grab()和ungrab():grab()方法类似于lock()方法,用于申请独占访问。grab()和lock()之间的区别在于,调用grab()时将立即生效,不考虑其他sequence的优先级,除非已经存在sequence调用了lock()或grab()。
22、什么是m_sequencer句柄?
m_sequencer是uvm_base_sequencer类型的句柄,定义在uvm_sequence_item/uvm_sequence中,其本质是item/seq和sequencer之间的桥梁。
seq/seq_item一旦挂载到某个sequencer上,那么该sequencer的句柄即被赋值给m_sequencer。启动sequence时,它始终与启动sequencer相关联。m_sequencer句柄包含该sequencer的引用。使用此句柄,sequence可以访问UVM组件层次结构中的信息。任何sequence都会自带一个m_sequencer。当我们无论是通过`uvm_do_on宏还是seq.start(xxx_sequencer)的方式,UVM都会将用户传入的sequencer绑定到该sequence的m_sequencer。
23、什么是p_sequencer句柄,与m_sequencer相比有什么不同?
m_sequencer作为sequence和sequencer之间的桥梁,使得sequence可以与sequencer进行交互,例如发送事务(transaction)给driver。但是,由于它的类型限制,它不能直接访问特定于派生自uvm_sequencer_base的sequencer子类中的成员变量。
p_sequencer是在sequence类中通过宏 uvm_declare_p_sequencer(user_sequencer)
声明的,其类型为用户定义的sequencer类型。这个宏会在sequence的 pre_body()阶段之前自动将 m_sequencer通过$cast转换为p_sequencer类型,从而允许sequence访问其挂载的sequencer中的特定成员变量。p_sequencer的引入解决了sequence访问sequencer变量的问题,因为它与sequencer是同一类型,可以访问所有sequencer的成员变量。
24、get_next_item()和try_next_item()有什么区别
get_next_item()是一个阻塞调用,直到存在可供驱动的sequence item为止,并返回指向sequence item的指针。
try_next_item()是非阻塞调用,如果没有可供驱动的sequence item,则返回空指针。
25、UVM driver类中的get_next_item()和get()方法之间有什么区别?
get_next_item()是一个阻塞调用,用于从sequencer FIFO获取sequence item。driver驱动完sequence item后需要先使用item_done()完成握手,然后再使用get_next_item()请求新的sequence item。
get()也是一个阻塞调用,同样用于从sequencer FIFO获取sequence item。但是在使用get()时,由于get()方法隐式完成了握手,因此无需显式调用item_done()。
26、driver中带和不带有参数的item_done()调用有什么区别?
item_done()方法是driver中的一种非阻塞方法,用于在get_next_item()或try_next_item()成功之后与sequencer完成握手。
如果不需要发回响应,则调用不带参数的item_done()。
如果需要发回响应,则将指向sequence_item的指针作为item_done()参数。
27、如何停止在sequencer上运行的所有sequences?
sequencer具有stop_sequences()方法,可用于停止所有sequences。但是此方法不检查driver当前是否正在驱动任何sequence_items,如果driver调用item_done()或put(),则可能会出现Fatal Error,因为sequences指针可能无效。
28、什么是virtual sequence,在哪里使用?有什么好处
virtual sequence是控制多个sequencer中激励生成的sequence。
由于sequence sequencer和driver都只针对单个接口,几乎所有测试平台都需要virtual sequence来协调不同接口之间的激励。virtual sequence在子系统或系统级测试平台上也很有用,可以使模块级的sequence协调地运行。
29、Virtual sequencer 和sequencer的区别
同样的,virtual_sequencer也不生成item,而是和virtual sequence一起对sequence到sequencer的挂载进行调度。virtual_sequencer桥接着所有底层的sequencer的句柄,只需要将其内部的底层sequencer句柄和sequencer实体对象连接。
30、instance override和type override之间有什么区别?
type override适用于该组件类型所在层次结构中的所有实例。
instance override仅覆盖类的特定实例,即该组件在UVM组件层次结构中的位置。
31、什么是uvm_config_db?它的作用是什么?
UVM configure机制使用uvm_config_db配置数据支持在不同的测试平台组件之间共享配置参数。任何测试平台组件都可以配置参数,其他组件可以从配置数据库访问这些参数,而无需知道其在层次结构中的位置。
例如,测试平台顶层可以通过uvm_config_db存储virtual interface句柄。然后任何uvm_driver或uvm_monitor组件都可以查询uvm config_db以获取此virtual_interface的句柄,并将其用于获取实际接口信号。注意要是先set后get,如果只get而没有set的话会报错。
32、验证平台中较低层次结构的组件是否可以使用get/set config方法将句柄传递给较高层次结构中的组件?
建议不要以这种方式在UVM中传递配置对象。通常,较高级别的组件set配置数据;较低级别的组件使用get方法获取它们。
33、在TB中使用interface和clocking blocking的好处?
Interface是一组接口,用于对信号进行一个封装,捆扎起来。如果像verilog中对各个信号进行连接,每一层我们都需要对接口信号进行定义,若信号过多,很容易出现人为错误,而且后期的可重用性不高。因此使用interface接口进行连接,不仅可以简化代码,而且提高可重用性,除此之外,interface内部提供了其他一些功能,用于测试平台与DUT之间的同步和避免竞争。
Clocking block:在interface内部我们可以定义clocking块,可以使得信号保持同步,对于接口的采样vrbg和驱动有详细的设置操作,从而避免TB与 DUT的接口竞争,减少我们由于信号竞争导致的错误。采样提前,驱动落后,保证信号不会出现竞争。
34、OPP(面向对象)的特性?
封装:通过将一些数据和使用这些数据的方法封装在一个集合里,成为一个类。
继承:允许通过现有类去得到一个新的类,且其可以共享现有类的属性和方法。现有类叫做基类,新类叫做派生类或扩展类。
多态:得到扩展类后,有时我们会使用基类句柄去调用扩展类对象,这时候调用的方法如何准确去判断是想要调用的方法呢?通过对类中方法进行virtual声明,这样当调用基类句柄指向扩展类时,方法会根据对象去识别,调用扩展类的方法,而不是基类中的。而基类和扩展类中方法有着同样的名字,但能够准确调用,叫做多态。
35、断言的种类有哪些?
立即断言和并行断言。其中立即断言非时序性,在过程块中使用;并行断言具有时序性,关键词为property。
36、断言一般写在uvm环境中的哪里?
interface中。常用并行断言来检查DUT的时序。
37、断言and和intersect的区别
and两者的起点相同,终点可以不同;intersect则两者必须同时开始结束。
38、启动sequence的方式?
一般在case中通过sequence.start显式启动;
通过default sequence来隐式启动。
39、测试用例怎么编写?
编写sequence,主要是在body里面根据测试功能要求写相应的激励和约束,然后在case中启动sequence,通过ref model和scb比较功能是否实现。
40、如何关闭约束?
constraint_mode(0)关闭约束;constraint_mode(1)打开约束。
还可以soft关键字修饰特定的约束语句,这样既可以让变量在一般的情况下取默认值,也可以直接给变量赋默认值范围外的取值。
41、说出几种约束形式
权重约束:=n表示每一个值取值都为n; :/n表示每一个取值权重为n/num;
条件约束if else 和->(case)if else 就是和正常使用一样;->通过前面条件满足后可以触发后面事件的发生。
范围约束inside:inside{[min:max]};范围操作符,也可以直接使用大于小于符号进行,但是不可以连续使用
42、rand和randc的区别是什么
rand修饰的变量,每次随机时,都在取值范围内随机取一个值,每个值被随机到的概率是一样的;randc表示周期性随机,即所有可能的值都取到过后,才会重复取值。
43、定宽数组、动态数组、关联数组、队列各自特点和使用
队列结合了链表和数组的优点,可以在一个队列的任何位置进行增加或者删除元素;
定宽数组:属于静态数组,编译时便已经确定大小。其可以分为压缩定宽数组和非压缩定宽数组:压缩数组是定义在类型后面,名字前面;非压缩数组定义在名字后面;
动态数组其内存空间在运行时才能够确定,使用前需要用new[]进行空间分配;
关联数组主要针对需要超大空间但又不是全部需要所有数据的时候使用,类似于hash
,通过一个索引值和一个数据组成,索引值必须是唯一的。
44、3种fork join的差异
fork join内部并行运行,线程全部执行完毕才会执行join后语句;fork join_any中的线程执行完一个就可以执行join_any后语句;fork join_none中的语句与join_none后语句并行执行不阻塞。
45、多线程的同步调度方法?
主要有event、semaphore、mailbox三种。event主要用于两个线程之间的一个同步运行,通过事件触发和事件等待进行两个线程间的运行同步;
semaphore主要是用于对资源访问的一个交互,通过key的获取和返回实现一个线程对资源的一个访问;
mailbox用于两个线程之间的数据通信,通过put函数和get函数还有peek函数进行数据的发送和获取。
46、ref类型的作用是什么
ref参数类型是引用,在任务里可以修改变量而且修改结果对调用它的函数随时可见。