数字验证面试笔试

数字验证面试笔试


文章目录


主要记录面试遇到的问题


一、面试

Q1.定宽数组、动态数组、关联数组、队列各自的特点和使用方式

  • 定宽数组:属于静态数组,编译时便已经确定大小。其可以分为压缩定宽数组和非压缩定宽数组:主要依靠维度进行区分,压缩数组的维度在名字右边,非压缩数组的维度在名字左边;从数据的存取速度来看,存放是在连续的存储空间中,访问任何的元素时间都相同,与数组大小无关;
  • 动态数组:其内存空间在运行时才能够确定,使用前需要用new[]进行空间分配,从数据的存取速度来看,存放是在连续的存储空间中,访问任何的元素时间都相同,与数组大小无关;
  • 关联数组:其主要针对需要超大空间但又不是全部需要所有数据的时候使用,类似于hash,通过一个索引值和一个数据组成,索引值必须是唯一的,关联数组的存取速度是最慢的,因为过程中要有大量算法实现
  • 队列:队列结合了链表和数组的优点,可以在一个队列的任何位置进行增加或者删除元素,从数据的存取速度来看,队列在收尾存取数据几乎没有任何开销,但是在队列中间插入或删除元素需要对其他元素进行搬移以腾出空间。在很长的队列中间插入新的元素,会需要很长的时间

使用情况:
1. 使用灵活性–看索引
索引是连续的非负整数,采用定宽或动态数组
索引不规则,且稀疏分布,采用关联数组
2. 排序方式
如果元素一次性全部加入,选择定宽或者动态数组,只需要对数组进行一次分配
如果元素一个一个加入,选择队列
如果数组的值不连续且互异,可以使用关联数组,把元素值本身作为索引
3. 占用系统的存储空间
小于1000个元素的数组,各种类型对存储器用量影响不大
1000 ~ 100万个元素的数组,定宽和动态数组具有最高的存储器使用效率
大于100万个元素的数组,重新检查算法是否有问题
对兆字节量级的存储器建模应该使用关联数组,但是注意指针带来的额外消耗,关联数组中每个元素站的空间可能会比定宽和动态数组大好几倍

Q2.fork…join/fork…join_any/fork…join_none 之间的异同

  • fork join:内部 begin end块并行运行,直到所有线程运行完毕才会进入下一个阶段。
  • fork join_any:内部 begin end块并行运行,任意一个begin end块运行结束就可以进入下一个阶段。
  • fork join_none:内部 begin end块并行运行,无需等待可以直接进入下一个阶段。
  • wait fork:会引起调用进程阻塞,直到它的所有子进程结束,一般用来确保所有子进程(调用进程产生的进程,也即一级子进程)执行都已经结束。
  • disable fork:用来终止调用进程 的所有活跃进程, 以及进程的所有子进程。

Q3.mailbox、event、semaphore 之间的异同

多线程之间同步主要由mailbox、event、 semaphore三种进行一个通信交互。
  • mailbox邮箱:和队列queue有相近之处,主要用于两个线程之间的数据通信,通过put函数和get 函数进行数据的发送和获取。
  • event事件:主要用于两个线程之间的一个同步运行,通过事件触发和事件等待进行两个线程间的运行同步。使用@(event)或者 wait(event.trigger)进行等待,->进行触发。
  • semaphore旗语:主要是用于对资源访问的一个交互,通过key的获取和返回实现一个线程对资源的一个访问。使用put和 get函数获取返回key。一次可以多个。

Q4.时间触发和事件等待:@(event_handle)和 wait(event_handle.triggered)区别

  • 用来触发事件时,使用->;
  • 用来等待事件使用@或者wait

@(event handle):边沿触发,事件会一直等待,直到触发为止,阻塞型;
wait(event_handle,triggered):电平触发,如果事件在当前已经被触发,则不会引起阻塞,非阻塞型,当event被多次触发时,避免使用wait;

Q5. Task和function的区别

  • function函数能调用另一个函数,但不能调用任务,task任务能调用另一个任务,也能调用另一个函数
  • 函数总是在仿真时刻0就开始执行,任务可以在非零时刻执行
  • 函数一定不能包含任何延迟、事件或者时序控制声明语句,任务可以包含延迟、事件或者时序控制声明语句
  • 函数至少有一个输入变量,可以有多个输入变量,任务可以没有或者多个输入(input)、输出(output)和双向(inout)变量
  • 函数只能返回一个值,函数不能有输出(output)或者双向(inout)变量,任务不返回任何值,任务可以通过输出(output)或者双向(inout)变量传递多个值

Q6.简述在TB中使用interface和clocking blocking的好处

  • Interface是一组接口,用于对信号进行一个封装,捆扎起来。如果像verilog中对各个信号进行连接,每一层我们都需要对接口信号进行定义,若信号过多,很容易出现人为错误,而且后期的可重用性不高。因此使用interface接口进行连接,不仅可以简化代码,而且提高可重用性,除此之外,interface内部提供了其他一些功能,用于测试平台与DUT之间的同步和避免竞争。
  • clocking block:在interface内部我们可以定义clocking块,可以使得信号保持同步,对于接口的采样和驱动有详细的设置操作(default input #1ns output 1ns;),从而避免TB与DUT的接口竞争,减少我们由于信号竞争导致的错误。采样提前,驱动落后,保证信号不会出现竞争。

Q7.同步 FIFO 和异步 FIFO 的作用和区别

FIFO在硬件上是一种地址依次自增的Simple Dual Por RAM,按读数据和写数据工作的时钟域是否相同分为同步FIFO和异步FIFO
同步FIFO:是指读时钟和写时钟为同步时钟,常用于数据缓存和数据位宽转换
异步FIFO:通常情况下是指读时钟和写时钟频率有差异,即由两个异步时钟驱动的FIFO,由于读写操作是独立的,故常用于多比特数据跨时钟域处理;

Q8. SystemVerilog 中 OOP 的三大特性

    封装、继承和多态

封装:通过将一些数据和使用这些数据的方法封装在一个集合里,成为一个类。
继承:允许通过现有类去得到一个新的类,且其可以共享现有类的属性和方法。现有类叫做基类,新类叫做派生类或扩展类。
多态:得到扩展类后,有时我们会使用基类句柄去调用扩展类对象,这时候调用的方法如何准确去判断是想要调用的方法呢?通过对类中方法进行virtual声明,这样当调用基类句柄指向扩展类时,方法会根据对象去识别,调用扩展类的方法,而不是基类中的。而基类和扩展类中方法有着同样的名字,但能够准确调用,叫做多态。

Q9.详述对于 ref 类型的理解

ref参数类型是引用
向子程序传递数组时应尽量使用reí获取最佳性能,如果不希望子程序改变数组的值,可以使用const ref类型;
在任务里可以修改变量而且修改结果对调用它的函数随时可见。

Q10.外部约束如何使用,有哪几种方式

最常用的是assert(acc.randomize() with {arr.seze()==3;});

约束常见的几种方式
权重约束dist::=n表示每一个取值的权重都是n,:/=n表示每一个取值的权重都是n/num;
条件约束if-else或->:->表示前面的条件满足情况下,才会触发后面的条件;
范围约束inside:约束在一个范围;

Q11.代码覆盖率、功能覆盖率、SVA 覆盖率都是衡量什么的

代码覆盖率:是针对RTL设计代码的运行完备度的体现,包括行覆盖率、条件覆盖率、FSM覆盖率、跳转覆盖率、分支覆盖率,只要仿真就可以收集,可以看DUT的哪部分代码没有动,如果有一部分代码一直没动看一下是不是case没有写到。
功能覆盖率:与spec比较来发现,design是否行为正确,需要按testplan来比较进度,用来衡量哪些设计特征已经被测试程序测试过的一个指标,首要的选择是使用更多的种子来运行现有的测试程序;其次是建立新的约束,只有在确实需要的时候才会求助于定向测试,改进功能覆盖率最简单的方法是仅仅增加仿真时间或者尝试新的随机种子。验证的目的就是确保设计在实际环境中的行为正确。设计规范里详细说明了设备应该如何运行,而验证计划里则列出了相应的功能应该如何激励、验证和测量;
断言覆盖率:用于检査几个信号之间的关系,常用在査找错误,主要是检査时序上的错误,测是断言被触发的频繁程度;

Q12.立即断言和并发断言的特点

立即断言的话就是和时序无关,比如我们在对激励随机化时,我们会使用立即断言,如果随机化出错我们就会触发断言报错。
并发断言的话主要是用来检测时序关系的,由于在很多模块或者总线中,单纯使用覆盖率或者事务check 并不能完全检测多个时序信号之间的关系,但是并发断言却可以使用简洁的语言去监测。除此之外,还可以进行覆盖率检测。

井发断言的用法的话,主要是有三个层次:

  • 第一是布尔表达式,布尔表达式是组成断言的最小单元,断言可以由多个逻辑事件组成,这些逻辑事件可以是简单的布尔表达式,在SVA中,信号或事件可以使用常用的操作符,如:&&,||,!,^,&等;
  • 第二个序列sequence编写,sequence是布尔表达式更高一层的单元,一个sequence中可以包含若干个布尔表达式,同时在sequence中可以使用一些新的操作符,如 ##、重复操作符、序列操作符;
  • 第三个是属性property的编写,property是比sequence更高一层的单元,也是构成断言最常用的模块,其中最重要的性质是可以在property中使用蕴含操作符(|-> |=>);

立即断言(immediate assertion)

  • 非时序的。
  • 执行时如同过程语句。
  • 可以Einitia/alwaysi过程块或者task/function中使用。

并行断言( concurrent assertion)

  • 时序性的。
  • 关键词property用来区分立即断言和并行断言。
  • 之所以称之为并行,是因为它们与设计模块一同并行执行。

Q13.如何保证验证的完备性

  • 首先不可能百分百完全完备,即遍历所有信号的组合,这既不经济也不现实。
  • 所以只能通过多种验证方法一起验证尽可能减少潜在风险,一般有这些验证流程:ip级验证、子系统级验证、soc级验证,除这些以外,还有upf验证、fpga原型验证等多种手段。
  • 前端每走完一个阶段都需要跟设计以及系统一起review验证功能点,测试用例,以及特殊情况下的波形等;
  • 芯片后端也会做一些检查,像STA、formality、DFM、DRC检查等,也会插入一些DFT逻辑供流片回来测试用。流片归来进行测试,有些bug可以软件规避,有些不能规避,只能重新投片。

Q14.function 中 return 语句执行之后,function 里剩下的代码语句还会执行吗

return之后,function里剩下的语句不能执行,return是终止函数的执行,并返回函数的值

Q15. 简述UVM的工厂机制

Factory机制也叫工厂机制,其存在的意义就是为了能够方便的替换TB中的实例或者已注册的类型。一般而言,在搭建完TB后,我们如果需要对TB进行更改配置或者相关的类信息,我们可以通过使用factory机制进行覆盖,达到替换的效果,从而大大提高TB的可重用性和灵活性。

要使用factory机制先要进行:

  • 将类注册到factory表中
  • 创建对象,使用对应的语句 (type_id::create)
  • 编写相应的类对基类进行覆盖。

Q16. UVM从哪里启动

UVM的启动

  • 在导入uvm_pkg文件时,会自动创建uvm_root所例化的对象uvm_top,UVM顶层的类会提供run_test()方法充当UVM世界的核心角色,通过uvm_top调用run_test()方法.
  • 在环境中输入run_test来启动UVM验证平台,run_test语句会创建一个my_case0的实例,得到正确的test_name

依次执行uvm_test容器中的各个component组件中的phase机制,按照顺序:

  • build-phase(自顶向下构建UVM 树)
  • connet_phase(自低向上连接各个组件)
  • end_of_elaboration_phase
  • start_of_simulation_phase
  • run_phase() objection机制仿真挂起,通过start启动sequence(每个sequence都有一个body任务。当一个sequence启动后,会自动执行sequence的body任务),等到sequence发送完毕则关闭objection,结束run_phase()(UVM_objection提供component和sequence共享的计数器,当所有参与到objection机制中的组件都落下objection时,计数器counter才会清零,才满足run_phase()退出的条件)
  • 执行后面的phase

Q17. 接口怎么传递到验证环境中

传递virtual interface到环境中:uvm_config_db

  • 通过使用uvm_config_db配置机制来传递接口,可以将接口的传递与获取彻底分离开。
  • 接口传递应发生在run_test()之前。这保证了在进入build_phase之前,virtual interface已经被传递到uvm_config_db中。
  • 用户应当把interface与virtual interface区分开来,在传递过程中的类型应当为virtual interface,即实际接口的句柄。

为什么要用virtual?
首先看下面两段代码

(1)
interface intfa;    |第一段代码中,只要module里面定义了intfa0,那么编译的时候就知道这是一个硬件结构,
  logic a;			|是一种数据结构,需要分配内存空间来对应。可以抽象理解interface定义出来的是一种
  ...				|“静态存在”的数据结构。
endinterface		|
module a();			|
    intfa intfa0;	|
endmodule			|
(2) 				|第二段代码中,如果我们也使用intfa intfa1,那么就意味着class a中有一个静态存在的接口,
class a;			|需要分配内存来对应,但是问题来了,class a本身还不存在,因为只有a这个类只有被实例化出
     intfa intfa1;	|来,才会被分配内存空间,通常是在别的地方被new这种方法(或者create方法)实例化出来。
endclass			|
function  xxx		|
    a a1;			|
    a1 = new();		|
endfunction			|

第二段代码中,只有在代码执行到new()这一行的时候,a1这个对象才存在,才会被分配内存空间,请注意:此时一定是处于运行态,因为代码已经执行起来了,那么这就有了一个矛盾:在运行态才存在的a1里面有一个在静态就存在的interface,这个明显逻辑就不通。

所以给class里面的interface加上virtual,含义就是:这个interface静态不存在!
只有当class的对象被创建出来以后,这个interface才存在。此时在运行态再把这个动态的产生的interface和module里面定义的静态interface连接起来。这逻辑上就通了,编译器和仿真器也知道该怎么干活了。

Q18.说一下component和object的区别,item是component还是object

  • UVM中component也是由object派生出来的,不过相比于object,component有很多其没有的属性,例如phase机制和树形结构等。在UVM中&#
  • 31
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值