Professional Documents
Culture Documents
[1] 将下列十进制数转换为有符号二进制,八进制和十六进制,使用尽可能少的比特位
a) 17
b)-17
a: 对17辗转相除,得到其二进制为010001,八进制为21,十六进制为0x11。注意二进制必须是010001,而不是
10001,不能缺少符号位
b: 根据17的二进制,-17的二进制数原码位110001,取反加一得到补码为101111,因此八进制为57,十六进制为0x2f
二进制转八进制,取三合一,二进制转十六进制,取四合一
[2] 0x3A的十进制数是多少?
ans=3*16^1+10*16^0=58
[3] 什么是格雷码,格雷码相对于普通二进制码有什么好处?
格雷码是一种二值编码,相邻的编码之间只有一位的区别。因此与普通二进制码相比,在递增时出错概率更加小。下
表是3bit的格雷码编码:
0 000 000
1 001 001
2 010 011
3 011 010
4 100 110
5 101 111
6 110 101
7 111 100
此外,由于格雷码中比特位变化比较少,与二进制码相比,使用格雷码的功耗更加低
[4] 什么是奇偶校验位,如何计算?
奇偶校验位是在一串二进制码的最后添加的一位,它使得整个二进制串的1的个数为奇数或者偶数。因此奇偶校验分为
两种,奇校验和偶校验。
计算校验位需要对二进制码中的1进行计数。如果1的数量为奇数,并且使用偶校验,则校验位为1,使得整体1的个数
为偶数。如果1的数量为偶数,并且使用偶校验,则校验位为0,使得整体1的个数为偶数。奇校验类似。奇偶校验位可以
通过对所有的比特位进行异或得到。
[5] 计算二进制数111001的奇校验位
111001中1的个数为4个,所以校验位为1,带上校验位以后为1110011,1的个数为5,为奇数
[6] 什么是BCD码,他和二进制码有什么区别?十进制27的二进制码和BCD码是什么?
BCD码也称二进码十进数,BCD码可分为有权码和无权码两类。其中,常见的有权BCD码有8421码、2421码、5421
码,无权BCD码有余3码、余3循环码、格雷码。8421BCD码是最基本和最常用的BCD码,它和四位自然二进制码相似,各
位的权值为8、4、2、1,故称为有权BCD码。
2的二进制位0010,7的二进制位0111,十进制27的8421BCD码为,00100111,二进制码为11011
Basic Gates
[7] 以下哪个是通用门?为什么?
AND
NAND
OR
NOR
XOR
通用门是可以实现任何布尔函数而无需使用任何其他门类型的门。 与非门或非门是通用门。
[8] 如何使用两个两输入与非门实现,两输入与门,两输入或门,非门?
与门:
或门:
非门:
[9] 如何使用两个两输入或非门实现,两输入与门,两输入或门,非门?
与门:
或门:
非门:
[10] 用一个2:1MUX构成下面的门
非门
两输入与门
两输入或门
两输入或非门
两输入与非门
两输入异或门
非门:
与门:
或门:
或非门:用或门和非门组成
与非门:用与门和非门组成
异或门:
[11] 异或门在数据通信中的典型应用是什么?
通常被用于错误检测,例如,奇偶校验,CRC校验,ECC。异或门也可以用于伪随机数生成。
[12] 三输入与非门的输出何时为0?
所有输入都为1
[13] 如何使用异或门实现一个非门
[14] 请用2:1选择器实现4:1选择器
[15] 什么是环形振荡器?如果每个门的延迟是2ps,使用三个非门的环形振荡器的频率是什么?
环形振荡器可以由奇数个非门组成,非门或者反相器连接成链后,最后一个输出反馈回第一个反相器。
[16] 同步电路和异步电路有什么不同?
时序电路分为两种,同步时序电路和异步时序电路
同步时序电路在适中的上升沿或者下降沿改变状态和输出值。常见的例子是flip-flop,在时钟边沿根据输入改变输出。
异步时序电路的状态和输出值是根据使能信号进行控制,这更加类似于一个带有反馈的组合逻辑。
[17] 阐述建立时间和保持时间
建立时间是在时钟进行有效转换前数据信号应该保持稳定的最短时间。
保持时间是在时钟进行有效转换后数据信号应该保持稳定的最短时间。
时钟信号到达两个FF的时间差称之为clock skew(时钟偏斜)
例如图中两个FF的时钟,虽然是同一个时钟源,但是由于走线的延迟,导致a的时钟比b的快。
建立时间约束为 ,即 ,最大工作频率为40Mhz
[20] 触发器和锁存器的区别什么?
触发器和锁存器都是存储信息的基本单元。一个触发器或者锁存器能够存储一bit的信息。两者的主要不同点是,触发
器只在时钟上升沿或者下降沿根据采样改变输出,而锁存器在enable信号拉高期间都会跟随输入。
[21] 什么是竞争?什么时候会出现?如何避免?
当输出取决于不同信号的顺序或者时序时,被称为竞争。竞争可以分为两种
1. 实际的硬件中的竞争
2. 仿真行为中的竞争
实际硬件中的竞争:以SR锁存器为例,当SR都是1的时候,输出为1,此时如果SR同时变成0,那么Q和Q'就会进入竞
争的情况。可以通过添加合适的逻辑避免。
仿真行为中的竞争:例如下面的代码
由于使用了阻塞赋值,便会发生竞争的情况,通过改为非阻塞赋值可以解决
[22] 用2:1mux实现D触发器
[23] 用D触发器实现T触发器
T触发器,T为0时输出不变,1时翻转。写出真值表就能看出来,将输入和Q异或再输入到D端。
[24] 用JK触发器实现D触发器
J=D,K=D'
[25] 行波进位加法器和超前进位加法器的区别是?
行波进位加法器:
结构类似于我们拿笔在纸上做加法的方法。从最低位开始做加法,将进位结果送到下一级做和。由于本级的求和需要
等待前一级的进位结果才可以得到,所以对于两个N-bit的求和。即使有N个一位的全加器,也需要N个延迟。
超前进位加法器:
事实上,在以下两种情况中,Ci=1:
1. Ai和Bi都为1
2. Ai和Bi有一个为1,且Ci-1为1
其对应的表达式为
递归后
可以看出每一级的进位信号可以不通过上一级的结果产生,只与输入有关系。因此减少了时间。
[26] 实现一个32bit寄存器需要几个flip-flop?
一个FF存储一bit信息,因此需要32个FF。
[27] mealy型FSM和moore型FSM有什么区别?
mealy型FSM的输出和当前的状态以及当前的输入有关系。
moore型FSM的输出只和当前的状态有关系。
[28] 九个状态的记录最少需要几个FF?
[29] 使用尽可能少的DFF实现二分频和四分频
二分频:
四分频:
Computer Architecture
[30] RISC和CISC的区别是什么?
RSIC:精简指令集。CISC:复杂指令集
RISC结构具有比较少的指令,这些指令是简单的指令(固定长度的指令和较少的寻址模式)。CISC结构具有更
多的指令,更加复杂,可变长度指令和更加多的寻址方式
RISC具有较小的指令,硬件上相对没有那么复杂。CISC使用更加复杂的硬件来解码和分解复杂的指令。因此
RISC更加侧重软件,CSIC更加侧重硬件
CISC具有更加复杂的指令,因此可以使用更加少的RAM来存储编程指令。相对的,RISC需要更加多的RAM来存
储指令
RISC的指令通常需要一个周期完成,而CISC可以能需要多个周期才能完成,具体取决于指令的复杂性。因此在
RISC中可以进行流水线化。
RISC通过减少每条指令的周期数来提高性能,而CISC通过减少每条程序的指令数来提高细嫩那个。CISC支持单
条指令完成从内存读取,执行操作,写回内存的操作(mem2mem操作)。而RISC完成则需要三个指令。
[31] 冯诺依曼结构和哈佛结构有什么区别?
冯诺依曼结构中,指令和数据存储在同一个存储器中。CPU读取数据和指令使用同一条总线,具有存储数据和
指令的统一缓存。
哈佛结构中,数据和指令是分开存储的,可以使用两条不同的总线同时访问数据和指令,指令和数据都具有单
独的缓存。
字节顺序是指字节在内存中的存储顺序。数据在内存中通常是按照字节寻址的,但是大多数计算即通常采用32位(4
个字节)存储操作数的。因此,为了将一个字节存在存储器中,由两种方式:
1. 高字节存在低地址中,这称之为Big Endian
2. 低字节存在低地址中,这称之为Little Endian
例如,当cpu向地址0x1000中写入0xDDCCBBAA时,两种方式就会出现差异
[33] SRAM和DRAM的区别是什么?
SRAM速度快,成本搞,常用于高速缓存。DRAM密度小,速度慢,常用于主存储器
[34] 对于256KB的内存,如果按字节寻址,需要多少位的地址线?
256KB=2^8*2^10Bytes,需要18位地址线
[35] CPU中不同类型的寄存器有什么区别?
[36] 解释计算机架构中的流水线
流水线技术是在单个处理器中实现指令集并行的技术。将基本的指令周期拆分为多个阶段,无需等待每条指令完成,
并行执行不同的步骤,在一条指令结束之前开始下一条指令。流水线能投提高指令的吞吐率,但是并不能减小指令的指令
时间。
pipeline hazard是指由于某些原因下一条指令无法执行的情况。有三种类型的hazard:
1. Structural Hazards:由于硬件资源不足产生的。比如如果设计结构中只有一个浮点执行单元,每次执行都需要
两个周期的时间,那么当程序中出现背靠背的浮点指令时会导致流水线停止。除此之外内存和缓存的访问也会
导致Structural Hazards
2. Data Hazards:由于前后指令数据的依赖性而造成的hazard
3. Control Hazards:当碰到跳转指令时,processor会stall一个cycle。
因为processor在处理指令时会分两个stage:取指令和解码指令。
当一条指令进入到解码阶段时,才会被发现需要跳转,所以在取指令阶段
的那条指令会被废掉,故浪费掉一个cycle。
[39] 如果一个流水线由十个阶段,每个阶段需要1ns执行。假设没有hazards,那么处理100个数
据需要多久?
第一个数据需要10ns完成,此后1ns完成一个数据的处理,因此总时间位10+99=109ns
[40] 指令有多少种寻址方式?
1. 立即数寻址,操作数作为指令的一部分
2. 直接寻址,操作数的地址直接出现在指令中
3. 寄存器寻址,操作数被存在寄存器中,寄存器的名字出现在指令中
4. 偏移量寻址,操作数的地址由一个寄存器的数据加上一个立即数的偏移量得到
[41] 什么是时间局域性和空间局域性?
局域性原理:程序常常重复使用它们最近用过的数据和指令。一条广泛适用的经验规律是:一个程序90%的执行时间
花费在仅10%的代码中。局域性意味着我们可以根据一个程序最近访问的指令和数据,比较准确地预测它近期会使用哪些
内容。局域性的原理也适应于数据访问,不过不像代码访问那样明显。
时间局域性是指最近访问过的内容很可能会在短期内被再次访问。
空间局域性是指地址相互临近的项目很可能会在短时间内都被用到。
[42] 计算机系统中有哪些存储?
1. Register
2. Cache
3. Main Memory/Primary Memory
4. Secondary Memory (Magnetic/Optical)
[43] 什么是cache?
cache是小型的快速存储。通常在Main Memory和CPU之间,有时放置于CPU内。
[44] 概述cache操作,cache功能的原理是?
每当CPU请求存储位置的内容时,都会首先检查cache中是否有此数据。如果cache中存在数据,则CPU直接从cache中
获取数据。 由于不需要CPU进入该数据的Main Memory,因此速度更快。 如果缓存中不存在数据,则将一块数据从Main
Memory读取到cache中,然后以字块的形式从cache中传输到CPU。
[45] 什么是cache的miss和hit?
[46] 如果一台机器存在cache,那么在链表和向量中搜索一个值的性能表现有什么差异?
链表是一种将其元素存储在非连续存储位置中的数据结构,而向量是一种将元素存储在连续位置中的数据结构。 对于
具有cache的设计:如果cache中存在一块数据,则很可能在cache中也存在后续的连续数据,因为通常是从主存储器到高
速缓冲存储器的任何取指令 根据缓存行(通常为64或128字节)获取。 因此,在拥有cache的机器上,通过向量进行搜索
将比链表进行搜索更快。
[47] 将内存映射到cache有哪些机制?请比较他们的优缺点
一共有三种主要的映射方法。这三种映射中,主存储器和cache都被划分为存储块(blocks of memory,也称cache
line,每个64bytes),这是映射时的最小单位。
直接映射(Direct Mapping):直接映射中,主内存和cache中始终存在一对一的映射,一组只有一个数据块。例
如:在下图中,cache大小为128个块,而主内存中有4096个块。设计实现是基于模数计算得到的,i为cache的
块序号,j是主内存的块编号,m是cache的行数,i = j mod m。如果块的大小为64B而地址为32为,则地址[5:
0]称为字节偏移量,它说明字节在块中的位置,地址[12:6]称为组位,说明地址地址映射到哪一组,剩下的地
址位作为标志位,标志说明cache映射了哪些内存中的地址。这是最简单的映射,并且可以通过内存地址可以轻
松计算cache中的数据在内存中的位置,并且只需要一个标记为进行比较就能知道是否命中。这种映射的缺点是
命中率低, Cache的存储空间利用率低。
全局关联映射(Fully Associate Mapping):任何的内存块都能映射到cache的任何块中,使用和上面的图一样的例
子,地址[5:0],作为块内部的索引,剩下的所有位都用于和cache中的所有标记为进行比较,这需要很大的比较
器。虽然这种方式命中率较高,Cache的存储空间利用率高,但是线路复杂,成本高,速度低
组关联映射(Set Associate Mapping):将cache分成u组,每组v行,主存块存放到哪个组是固定的,至于存到该组
哪一行是灵活的,即有如下函数关系:cache总行数m=u×v,组号q=j mod u组间采用直接映射,组内为全相
联。例如,下图显示了128个块的相同高速缓存,这些高速缓存组织为64个集合,每个集合具有2个块。硬件较
简单,速度较快,命中率较高,但是与分组有关系。
[48] 更高关联性的缓存有什么缺点?
更高关联性的cache意味着需要更加大的比较器,用于将传入的地址和标签进行对比,会导致更加大的硬件需求和功
耗。
cache的索引位有4位,因此cache包含2^4=16个块,每个块只有1byte所以不需要块内索引,剩下的16-4=12位全部作
为标记位
[51] 直写式缓存和回写式缓存有什么区别?优缺点是什么?
直写式缓存方式: 当CPU要将数据写入内存时,除了更新缓冲内存上的数据外,也将数据写在SDRAM中以维持主存
与缓冲内存的一致性,当要写入内存的数据多起来的话,速度自然就慢了下来.
回写式缓存方式: 当CPU要将数据写入内存时,只会先更新缓冲内存上的数据,随后再让缓冲内存在总线不塞车的时
候才把数据写回SDRAM,所以速度自然快得多
回写缓存在内存带宽利用方面更好,因为仅在需要时才回写数据。 如果系统中存在多个可以缓存同一地址的cache,
则维护数据一致性非常复杂,因为内存可能并不总是具有最新数据。
cache的inclusive和exclusive属性适用于具备多级缓存的设计。
当CPU试图从某地址load数据时,首先从L1 cache中查询是否命中,如果命中则把数据返回给CPU。如果L1
cache缺失,则继续从L2 cache中查找。当L2 cache命中时,数据会返回给L1 cache以及CPU。如果L2 cache也缺
失,很不幸,我们需要从主存中load数据,将数据返回给L2 cache、L1 cache及CPU。这种多级cache的工作方
式称之为inclusive cache。某一地址的数据可能存在多级缓存中。
与inclusive cache对应的是exclusive cache,这种cache保证某一地址的数据缓存只会存在于多级cache其中一
级。也就是说,任意地址的数据不可能同时在L1和L2 cache中缓存。
exclusive cache的优点之一是多级cache可以一起存储更多的数据。一般使用inclusive cache类型,每次cache miss,可
以从下一级的cache中寻找,load。而exclusive cache,每次cache miss,只能去main memory中load。但是exclusive
cache比较节省cache size。
以下是一些可用于替换cache line的算法
[54] 缓存一致性的问题是什么?
在多个处理器拥有自己的cache的共享多处理器系统中,相同数据(相同地址)的多个副本可能会同时存在于不同的
cache中。 如果允许每个处理器自由更新cache,则可能导致内存视图不一致。 这称为高速缓存一致性问题。 例如:如果
允许两个处理器将值写入同一地址,则在不同处理器上读取同一地址可能会看到不同的值。
[55] 基于监听和基于目录的缓存一致性协议之间有什么区别?
对于小的系统来说,如果带宽足够,基于监听的协议速度会更 基于目录的协议需要使用查找表,这将会导致
加快 较长的时延
基于监听的协议不适合大型系统,因为它需要将将每一个请求 由于不需要广播,基于目录的协议更加适合大
信息进行广播 型系统
[56] 什么是MESI协议?
状态 描述 监听任务
该Cache line有效,数据和内存中
S 共享 缓存行也必须监听其它缓存使该缓存行无效或者独享该缓存
的数据一致,数据存在于很多
(Shared) 行的请求,并将该缓存行变成无效(Invalid)。
Cache中。
I 无效
该Cache line无效。 无
(Invalid)
[57] 什么是MESIF和MOESIF协议?
是对于MESI协议的扩展,引入了两个新状态“F”“O”:
F (Forward): 在MOESI协议中,S状态的定义发生了细微的变化。当一个Cache行状态为S时,其包含的数据并不
一定与存储器一致。如果在其他CPU的Cache中不存在状态为O的副本时,该Cache行中的数据与存储器一致;
如果在其他CPU的Cache中存在状态为O的副本时,Cache行中的数据与存储器不一致。
O (Owned): O位为1表示在当前Cache 行中包含的数据是当前处理器系统最新的数据拷贝,而且在其他CPU中一
定具有该Cache行的副本,其他CPU的Cache行状态为S。如果主存储器的数据在多个CPU的Cache中都具有副本
时,有且仅有一个CPU的Cache行状态为O,其他CPU的Cache行状态只能为S。与MESI协议中的S状态不同,状
态为O的Cache行中的数据与存储器中的数据并不一致。
[57] 什么是虚拟内存?
虚拟内存是一致内存管理技术,及时实际的物理内存很小,虚拟处理器也允许处理器查看地址的虚拟连续空间。操作
系统管理虚拟地址空间以及从辅助设备(如磁盘)到物理主内存的内存分配。CPU中的地址转换硬件(memory
management unit,MMU)将虚拟地址转换为物理地址。 此地址转换使用分页的概念,其中将连续的内存地址块(称为
页)用于虚拟内存和实际物理内存之间的映射。
[58] 虚拟内存地址和物理内存地址的区别是什么?
软件程序或进程用来访问其地址空间中存储位置的地址称为虚拟地址。 然后,操作系统连同硬件将其转换为另一个地
址,该地址可用于实际访问DRAM上的主内存位置,该地址称为物理地址。 地址转换是使用分页的概念完成的,如果主内
存或DRAM没有此位置,则在OS的协助下,数据将从辅助内存(如磁盘)移至主内存。
[59] 什么是页的概念?
所有虚拟内存都将虚拟地址空间划分为页,页内的虚拟内存地址是连续的。页是内存从辅助存储移动到物理内存以管
理虚拟内存的最小单位。大多数计算机系统的页至少为4KB.当需要更大的实际内存是,某些结构还支持更大的页。页表用
于将应用程序看到的虚拟地址转换为物理地址,是一种数据结构,用于多页情况下,在内存中虚拟地址到物理地址的映
射。
[60] 什么是TLB?
当程序访问映射到虚拟地址空间但未加载到主存储器中的内存页时,计算机硬件[内存管理单元(MMU)]会发起中
断。 此中断称为页面错误。
[62] 如果CPU正在执行一个任务,如何停止他并运行另一个任务?
可以使用外部中断源来中断CPU上的程序执行。
[63] 什么是中断和异常,它们有何不同?
中断是一个异步事件
通常由外部硬件(I / O设备或其他外围设备)生成,并且不会与指令执行边界同步。例如:键盘,存储设备或USB端
口可能会发生中断。当前指令执行结束后,总是对中断进行服务,并且CPU跳转到执行中断服务程序。
异常是一个同步事件
异常当处理器在执行指令时检测到任何预定义条件时生成的同步事件。例如:当程序遇到被零除或未定义的指令时,
它将生成异常。异常又分为三种类型,程序流的改变方式取决于类型:
1. 故障:在发生故障的指令之前由处理器检测到故障并对其进行维修
2. 陷阱:在导致陷阱的指令之后对陷阱进行维修。最常见的陷阱是用于调试的用户定义中断。
3. 中止:中止仅用于在执行不再继续的情况下发出严重的系统问题信号
[64] 什么是向量中断?
向量中断是一种中断,终端设备使用该中断特有的代码将处理器定向到正确的中断服务程序中,该代码由中断设备与
该中断一起发送给处理器。
而非向量中断,中断服务程序需要读取中断寄存器,解码出导致中断的中断源,然后执行特定的中断服务程序
[65] 有哪些技术可以提高从内存提取指令的性能?
指令缓存和预取
指令缓存和预取算法将在实际的指令解码和执行阶段之前继续提取指令,这可以较小设计中指令提取阶段的存储等待
时间延迟。
分支预测和分支目标预测
分支预测基于历史指令预测是否将发生条件分支,而分支目标预测将有助于在处理器计算之前预测目标。 这能最大程
度地减少指令提取停顿,因为提取算法可以根据预测保持提取指令。
[67] 独热码在设计中有什么好处?
独热码中,状态转换时,会有两位改变,一位清零,一位置一。它的优点时,不需要进行解码就能知道当前的状态。
独热码会使用更多的触发器,但是更加少的组合逻辑,在时序电路中不需要用解码逻辑进行区分状态。
Programming Basics
[68] 在任何一种编程语言中,静态(static)变量和自动(automatic)变量,局部(local)变量和全局
(global)变量之间有什么区别?
区分这些名词需要两个概念,作用域(scope)和存储持续时间(storage duration),前者定义了在何处可以访问变量,后
者定义了在何时可以访问变量。
按照变量的作用域可以区分局部(local)和全局(global)变量。局部变量的作用范围有限,尽在声明它们的代码块
中可见。而全局变量在声明后在程序的任何位置都可见。
存储持续时间可以区分自动(automatic)变量和静态(static)变量。静态变量的生命周其一直持续到程序结束,因
此可以始终访问。自动变量具有有限的生命周期,只能持续到程序离开定义的块或者作用域为止。
例如:在以下的systemverilog代码中,global_int被声明为类成员,并且在整个类中具有全局作用域,而当取消引用该
类的对象时,其生命期结束。global_static变量被声明为静态变量并具有全局作用域整个类以及整个程序的生命周期,即使
取消引用类的对象也存在。sum变量对于函数compute()是局部的,并且仅在函数内部可见,并且仅在compute执行时存
在, count变量在函数compute()中是局部变量,仅在函数范围内可见,但由于它是静态的,因此即使在多次执行函数
compute()之后,它也只有单个副本并保留该值.
1 class test_class;
2 int global_int; //automatic by default
3 static global_static; //global static variable
4
5 void function compute()
6 begin
7 static int count; //local static variable
8 local int sum; //local automatic variable
9 sum = sum +1;
10 count = count +sum;
11 end
12 endfunction
13
14 endclass
[69] 什么是内联(inline)函数?
内联函数时调用时会进行展开内联的函数,即编译器会将函数调用替换为相应函数代码。如果函数非常小并且在多个
地方使用,使用内联函数会更有优势。这么做会提高运行速度,没有调用函数和从函数返回的开销。
例如:在C语言中,定义一个名为max的内联函数,在main内部的每次调用都会通过替换代码实现,而不是函数调用
实现。
[70] 什么是正则表达式?
正则表达式是特殊的字符序列,可以使用特殊的语法帮助用户匹配或查找其他字符串(或字符串集)。 它是用于字符
串内模式匹配的最强大的概念之一,广泛用于Perl,Python,Tcl等语言。
[71] 堆和栈的区别是什么?
栈是内存的一块特殊区域,用于存储由函数创建的临时变量。每次函数声明一个新的自动变量时,它将被压入栈,并
且每次函数退出时,会删除压入栈的所有变量。所有局部变量都使用栈进行存储,并且时自动管理的,也有大小限制。如
果栈的空间不足,则会出现栈溢出错误。
堆则是需要管理的内存区域,程序员需要分配和释放内存,某些语言中是自动完成的。堆通常用于存储静态变量和对
象。与栈相比,堆略慢,并且是通过指针应用的,并且可以在任何位置应用堆的变量。堆的大小也是可以更改的,当可用
内存是不连续的块时,堆可能会出现碎片问题。
[72] a++和++a的区别是?
a++首先返回值“a”(当前值为“a”),然后“a”自增。因此,如果将“a ++”分配给变量,则将在分配中使用旧值“a”。
[73] 什么是内存泄漏?
[74] 编译器和解释器的区别是什么?
机器(例如计算机)理解代码是通过二进制的,机器可以理解的二进制代码称之为“机器码”。程序员通常使用高级编
程语言(C,C++,Perl,Python)变写计算机程序或者代码。编译器和解释器就是将这些源代码转换为机器代码的程序。
编译器 解释器
扫描整个程序并将整个源代码转换为机器代码 一次扫描并转化一行源代码
需要大量的时间去分析源代码 只需要少量时间用于分析源代码
输出机器专用的二进制码 输出代码是某种中间代码,由另一个程序解释
执行速度更快(计算机硬件运行) 执行更慢(由另一个程序执行)
扫描整个程序后报告错误 一直运行,直到遇到第一个错误,并且停止程序
[75] 静态语言和动态语言的区别是什么?
静态语言:静态语言是一种在编译时固定类型的语言。 这意味着您需要在使用它们之前声明所有变量及其数据类型。
例如:Java,C和SystemVerilog是静态类型的语言。
动态语言:动态语言是一种在执行时确定类型的语言。这与静态类型的语言相反。 例如:VBScript和Python是动态类
型的,因此在使用之前不需要声明所有变量及其数据类型。 他们会在首次为变量分配值时弄清楚变量的类型。
[76] 下面关于栈的观点哪个是错的?
1. 栈只能push或者pop
2. 可以使用栈实现FIFO
3. 栈对于嵌套循环,子程序调用很有用
4. 队长堆算术表达式计算有用
选项2是错的,栈是LIFO而非FIFO,先入后出。
[77] Perl中“use”和“require”的主要区别是?
use在编译时起作用,require在运行时起作用
use隐式调用了将要加载的模块,require则没有
use引入的名称不需要后缀名,而require需要
use引入模块的同时,也引入了模块的子模块。而require则不能引入,要再重新声明
[78] 静态内存分配和动态内存分配有什么区别?
静态内存分配 动态内存分配
内存在编译时分配 内存在运行时分配
内存被分配到栈上或者程序的其他部分 内存分配到堆上
不需要释放内存,静态变量的生命周期就是程序的生命周期 需要及时释放内存
固定大小,一旦分配以后内存大小就不能改变 能够改变大小
执行更加快 执行更加慢
[79] 什么是编译预处理命令?
在代码中,预处理器指令是以#开头的行。它们充当预处理程序的指令,预处理程序在代码编译开始之前检查代码。
其结果就是替换了源代码中的某些代码。例如:预处理程序指令的常规语法为:#define标识符值每当预处理程序在源代码
中遇到“标识符”时,它将用“值”替换,并在编译之前生成新的源代码。
namespace是指标识符的各种可见范围。命名空间用关键字namespace 来定义。命名空间是C++的一种机制,用来把
单个标识符下的大量有逻辑联系的程序实体(例如类、对象和函数)组合到一起。"std"是"standard"一词的缩写。 standard
namespace (std namespace)是一种特殊类型的名称空间,其中保留了所有内置的C ++库(例如字符串,cin,cout,
vector等)。 因此,"using namespace std"告诉C ++编译器使用标准C ++库。
const关键字告诉编译器,该变量或对象一旦进行初始化便不可更改。所以,int a 声明后,后续可以对变量a进行更
改,而const int a,后续不可更改
[82] C语言中的关键词volatile是什么意思?
volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,
都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,
如果这个变量由别的程序更新了的话,将出现不一致的现象。volatile关键字主要在与内存映射的输入输出(硬件)接口时
使用。 变量声明为volatile之后,编译器将无法执行任何优化,例如:删除内存分配,将变量缓存在寄存器中或更改分配的
执行顺序。
[83] 解释指针的概念
指针是一个变量,其值是另一个变量的地址。星号*表示指针,int * p 告诉编译器变量“p”是一个指针,其值是存储整
数变量的存储位置的地址。 同样,float * f; 告诉编译器变量“ f”是一个指针,其值是存储浮点变量的存储位置的地址。以下
列代码为例
1 int a = 10;
2 int *b;
3 int c;
4 b = &a;
5 c = *b;
6 printf(“b=%d and c=%d\n”,b,c);
值传递:在这种情况下,函数会用一块新的内存去存储变量,将参数的值复制进来,并且函数内部对参数的修
改,不会影响到外部。下例中,在调用Exchg1(a,b)时最开始做的两个隐含动作是:int x=a;int y=b; 及 x=a;y=b;
原来函数在调用时是隐含地把参数a,b的值分别赋值给了x,y。之后在函数体内一直是对形参x,y进行操作。并没有
对a,b进行任何操作。函数只是把a,b的值通过赋值传递将值传递给了x,y。函数里操作的只是x,y的值,并不是a,b
的值。这就是所谓的值传递
地址传递:地址传递的参数为指针,函数内部实际上是通过指针实现的,通过指针的方式寻址,这种修改会对
外部的值产生影响。下例中:在调用Exchg2(&a,&b)时最开始做的两个隐含动作是:int *px=&a;int *py=&b;.及
px=&a;py=&b; 原来函数在调用时是隐含地把参数a,b的地址分别传递给了指针px,py。之后在函数体内一直是对
指针px,py进行操作。也就是对a,b的地址进行的操作。
引用传递:这种情况下会将参数的地址复制进来,函数内对参数的修改会反映到外部。通常通过这种方式减小
对内存的消耗,例如数组的传递,使用引用穿的可以减小内存消耗。下例中:与值传递相比,代码上只有只有
一处不同,即函数定义处:void Exchg3(int &x, int &y) Exchg3函数的定义处Exchg3(int&x, int &y)。调用时我们
可以像值传递(如: Exchg1(a, b); )一样调用函数(如: Exchg3(a,b);)。但是x、y前都有一个取地址符号
“&”。有了这个,调用Exchg3时函数会将a、b 分别代替了x、y了,我们称:x、y分别引用了a、b变量。这样函
数里操作的其实就是实参a、b本身了,因此函数的值可在函数里被修改
[85] NULL指针的值和大小是多少?
[86] 什么是链表?一共有几种类型的链表?
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存
储数据元素的数据域,另一个是存储下一个结点地址的指针域。
一共有三种不同类型的链表:
1. 单向链表
2. 双向链表
3. 循环链表
[87] 以下算法的“最坏情况”时间复杂度是多少?
1. 线性搜索
2. 二进制搜索
3. 插入排序
4. 合并排序
5. 桶排序
算法的时间复杂度代表了算法的运行时间,n代表输入算法的参数数量。通常使用big O算法进行评估,例如某算法隐
形时间为5n^4 + 6n^2 + 1,取最高阶为n^4,那么其算法复杂度为O(n^4)。所以以上算法的算法复杂度为:
1. O(N)
2. O(log(N))
3. O(N2)
4. O(N*log(N))
5. O(N)
[88] 以下算法的空间复杂度是多少?
1. 线性搜索
2. 二进制搜索
3. 插入排序
4. 合并排序
5. 桶排序
空间复杂度的概念类似于时间复杂度,但是衡量的值是算法运行时所需要的内存空间。以上算法的空间复杂度为:
1. O(1)
2. O(1)
3. O(N)
4. O(N)
5. O(N)
[89] C/C++中,"&"和"&&"有什么区别?
Struct分配足够的空间来存储结构中的所有字段/成员。 第一个存储在Struct的开头,第二个存储在Struct的开头,依此
类推。
Union仅分配足够的空间来存储列出的最大字段,并且所有字段都存储在同一空间中。 这是因为在Union中,一次只
能使用一种类型的封闭变量,而不是可以引用所有封闭变量的struct。
[91] 下面这个结构体需要多大的内存进行存储?
1 struct ID {
2 int IntID;
3 char CharID[8];
4 };
需要12个字节,int需要4个字节,char数组需要8个字节。
[92] 下面这个联合体需要多大的内存进行存储?
1 union ID {
2 int IntID;
3 char CharID[8];
4 };
需要8个字节,数组CharID需要8个字节。
[93] 什么是内核(kernel)?
内核是一种计算机程序,它用于管理来自软件的输入/输出请求,并将这些请求转换为CPU指令或其他指令。
[94] perl代表什么意思?
[95] perl中有多少种不同类型的变量?
标量(scalars):标量用$定义,标量是perl中最简单的变量。 标量可以是数字,也可以是字符串或引用。
数组(arrays):数组用@定义,数组是标量的有序列表,数组的索引是从0开始的。
哈希(hashes):哈希用%定义,哈希是键/值对的无序集合,可以将键用作下标来访问。
[98] C/C++中"\0"字符的用途是什么?
字符串总是以'\0'作为串的结束符。因此当把一个字符串存入一个数组时,也把结束符 '\0'存入数组,并以此作为该字
符串是否结束的标志。
[99] 什么是二叉树?
[100] 什么是正则表达式中的特殊字符、量词和锚点?
特殊字符是为正则表达式用于搜索的,具备特殊含义的元字符。 示例:\,^,$,(),[],|,&
量词用于指定匹配前面的正则表达式的“频率”。 示例:*, +, ?, {}
锚点指正则匹配时的匹配的位置。锚点允许用户指定文本搜索的位置。示例:^, $, <, >
[101] 类和对象有什么区别?
[102] C++的类和结构体有什么区别?
最初,在C中定义了一个“结构体”,以将不同的数据类型组合在一起以执行某些已定义的功能。 但是,在C++中,这
种结构体也扩展为包括函数的结构。 “类”也是一种数据类型,可以将不同的数据类型和其对应的方法进行分类。 C++中两
者的区别之一是,类的所有成员默认情况下都是私有的,而结构的所有成员默认情况下都是公共的。
[103] Systemverilog中的类和结构体有什么区别?
在SystemVerilog中,基于要执行的某些功能,类和结构都用于定义一堆数据类型。 但是,结构是整体式的类型,在声
明结构时会分配必要的内存。 类是动态类型,一旦声明了一个类,就只能将一个类句柄引用为null。 内存分配仅在创建该
类的实际对象时发生。
这三者是类成员的不同访问属性
类的private成员只能从该类内部访问。 这些数据成员在派生类中将不可见。
public成员可以从该类内部也可以在类外部访问。
protected数据成员与private成员类似,因为它们只能在该类中访问。 但是,与private成员不同,这些成员在派
生类中也可见。
[105] 什么是多态
[107] 什么是运算符重载?
在面向对象的编程中,运算符重载是多态的一种特殊情况,可以重新定义或重载可用的不同内置运算符。 因此,程序
员也可以将运算符与用户定义的类型一起使用。 C++支持此功能,而SystemVerilog不支持此功能。 以下示例显示了一个
Testclass,其中运算符+被重载,从而可以把两个类型为“Testclass”的类对象相加。 然后,实现将来自两个对象的数据成员
相加,并将其分配给结果类的数据成员。
1 #include <iostream>
2 class Testclass{
3 public:
4 int a;
5 int b;
6 Testclass operator+(const Testclass& obj);
7 }
8
9 Testclass Testclass::operator+(const Testclass& obj2){
10 Testclass tmp_obj = *this;
11 tmp_obj.a = tmp_obj.a + obj2.a;
12 tmp_obj.b = tmp_obj.b + obj2.b;
13 return tmp_obj;
14 }
15
16 int main(void){
17 Testclass obj1, obj2, obj3;
18 obj1.a = 1;
19 obj1.b = 1;
20 obj2.a = 2;
21 obj2.b = 2;
22 obj3.a = 0;
23 obj3.b = 0;
24 obj3 = obj1 + obj2;
25 std::cout<<obj3.a<<" "<<obj3.b<<"\n";
26 return 0;
27 }
[108] 什么是构造函数?
构造函数是类的特殊成员函数,每当创建该类的实例时,构造函数就会自动调用。 在C++中,它与类具有相同的名
称。 在SystemVerilog中,它作为new()函数实现。
[109] 什么是析构函数?
与构造函数相反,当对象结束其生命周期,如对象所在的函数已调用完毕时,系统会自动执行析构函数。 在C++中,
它与类具有相同的名称,并带有波浪号字符前缀,而在SystemVerilog中,由于该语言支持自动垃圾收集,因此没有析构函
数。
[110] OOP中的组合(composition)和继承(inheritance)之间有什么区别?
在浅拷贝中,将创建一个新对象,该对象具有与原始对象中的值完全相同的副本。 如果对象的任何字段是对其他对象
的引用,则仅复制引用地址(句柄)。在深拷贝中,将创建一个新对象,该对象具有与原始对象相同的值的精确副本。 如
果任何对象都引用了其他对象,则还将复制属于该对象的所有值的副本,而不仅仅是内存地址或句柄。因此,称为深拷
贝。
例如,对比如下两个类。
1 class A;
2 int a;
3 int b;
4 endclass
5
6 class B;
7 int c;
8 A objA;
9 endclass
[112] 什么是OOP的虚方法?
虚方法是在基类中声明的成员方法,并且可以由派生类重新定义。 要创建虚方法,在基类中的方法声明之前要加上关
键字virtual。 在派生类中重新定义基类方法的这种方式也称为方法重写。使得调用方法时,是根据对象类型而不是句柄类
型调用函数。
[113] 什么是多重继承?
多重继承是某些面向对象的计算机编程语言的功能,其中对象或类可以从多个父对象或父类继承特征和功能。 它不同
于单一继承,在单一继承中,一个对象或类只能从一个特定的对象或类继承。注意:C++支持多重继承,而SystemVerilog
语言则不支持。
[114] 什么是抽象类?
[115] 什么是类的静态方法?
[116] 类的this指针是什么意思?
该指针是一个特殊的指针,可用于在类范围内引用该类的当前对象。
[117] type conversion 和 type casting的区别是?
比
较
type casting type conversion
内
容
意 一个数据类型由用户分配给另一个数据类型,使用强制 编译器自动将一种数据类型转换为另一种数据
义 转换运算符,称为"type casting"。 类型称为"type conversion"。
应 仅当两个数据类型"兼容"时,才能实现类型转
类型强制转换也可以应用于两个"不兼容"的数据类型。
用 换。
算 要将数据类型强制转换到另一个数据类型,需要强制转
无需操作符。
子 换运算符"()"。
实
它在程序设计过程中完成。 它在编译时显式完成。
现
1 int a;
2 byte b;
3 ...
4 ...
5 b= (byte) a;
6 ////////////////////////////////////////
7 int a=3;
8 float b;
9 b=a; // value in b=3.000.
UNIX/Linux
[118] 如何找到有关UNIX/Linux命令做什么的详细信息?
[119] 编写UNIX/Linux命令完成以下任务,假设文件名为file.txt
1. 显示文件的前10行
2. 显示文件的第10行
3. 从文件中删除第13行
4. 从文件中删除最后一行
5. 反转字符串(例如:“ Hello” )
6. 检查上一条命令是否成功
7. 查找文件中的行数
8. 查找文件中的字符数
9. 查找文件中第17行的字符数
10. 获取第三个单词文件中第17行的内容
11. 将所有用户的文件权限更改为“读取”和“可执行”。
12. 将文件的组访问权限更改为组。(假设新的组名称为“ new_group”)
13. 将两个文件(file1.txt和file2.txt)的内容移动到一个文件(file.txt)
14. 显示本账号下的所有进程
15. uniquely排序文件(file1.txt)的内容并将其复制到另一个文件(file2.txt)
16. 检查用户名
17. 登录到远程主机(例如“远程服务器”)
1 任意一种:
2 a) head -10 file.txt
3 b) cat file.txt | head -10
4 c) sed “11,$ d” file.txt
5 head -10 file.txt | tail -1
6 sed -i “13 d” file.txt
7 sed -i “$ d” file.txt
8 echo “Hello” | rev
9 echo $?
10 cat file.txt | wc -l
11 cat file.txt | wc -c
12 head -17 file.txt | tail -1 | wc -c
13 head -17 file.txt | tail -1 | cut -f3 -d’ ‘
14 chmod 555 file.txt
15 chgrp new_group file.txt
16 cat file1.txt file2.txt > file.txt
17 ps -aef
18 sort -u file1.txt > file2.txt
19 whoami
20 ssh username@remote-server
[120] 编写UNIX/Linux命令,按照要求显示文件内容,假设文件名为file.txt
1. 所有匹配“cat”的行
2. 所有单词“ cat”的行
3. 所有不包含“cat”的行
4. 所有包含单词“ cat”的行(不区分大小写)
5. 所有以“cat”开头的行
6. 所有以“ cat”结尾的行
7. 所有包含“cat”和“123”的行(“cat”出现在“123”之前)
[121] 编写UNIX/Linux命令以列出目录中所有文件的名称(例如/usr/bin/dir/)(及其子目
录),文件应该包含不区分大小写的“I am preparing for Interview”。
[123] 编写UNIX/Linux命令,该命令将所有非空白行从文件(file1.txt)移至另一个文件
(file2.txt)
1. 查找当前目录或其子目录中是否存在某个文件
2. 查找某个文件是否在目录“/usr/bin/DIR”或其子目录中
3. 查找某个文件是否仅存在于当前目录中
4. 查找当前目录或其子目录中是否包含名称中包含特定单词“dummy”的文件
5. 查找当前目录或其子目录中是否存在不区分大小写的文件“file”
6. 查找所有名称不是“file.txt”且存在于当前目录或其子目录中的文件
7. 重新运行以前执行的find命令
[125] 编写一个UNIX/Linux命令:
1. 列出在计算机上以你的名字设置的所有Cron Jobs
2. 列出用户在计算机上设置的所有Cron Jobs
3. 删除计算机上以你的名字设置的所有Cron Jobs
4. 删除用户在计算机上的所有Cron Jobs(如果你有权这样做)
5. 在计算机上以您的名字编辑Cron Job。
6. 设置每天下午6:30运行的Cron Jobs
7. 设置每分钟运行一次的Cron Jobs。
8. 设置一个Cron Jobs,该作业在每个月的前20天上午6:30运行
9. 设置仅在每月的星期五的6:30 AM和6:30 PM运行的Cron Jobs
1 crontab -l
2 crontab -u <user_name> -l
3 crontab -r
4 crontab -u <user_name> -r
5 crontab -e
6 30 18 * * * <command_to_invoke_your_process>
7 * * * * * <command_to_invoke_your_process>
8 30 6 1-20 * * <command_to_invoke_your_process>
9 30 6 18 * * 6 <command_to_invoke_your_process> (assuming Sunday is represented by 0)
[126] 列出下列shell中的快捷键
1. 杀死进程
2. 将在终端上运行的进程移至后台
3. 将光标移至Shell上命令的开头
4. 将光标移至Shell上命令的结尾
5. Ctrl + c
6. Ctrl + z
7. Ctrl + a
8. Ctrl + e
Programming in C/C++
什么是大小端请参考问题[32]
1 #include <stdio.h>
2 int main() {
3 unsigned int i = 1;
4 char *c = (char*)&i;
5 if (*c)
6 printf("Little Endian \n");
7 else
8 printf("Big Endian \n");
9 return 0;
10 }
[128] 经过下列代码后,b和c的值是多少?
1 a = 10;
2 b = a++;
3 c = ++a;
b等于10,而c等于12。后置自增运算符仅在赋值后才进行自增,因此b得到的是自增前的值。 前置增量运算符将首先
进行自增,因此a将从11(在b = a++后变为11)增加到12
[129] 下列代码的输出是什么?
1 #include<stdio.h>
2 int xyz=10;
3 int main() {
4 int xyz=20;
5 printf("%d",xyz);
6 return 0;
7 }
变量xyz定义了全局变量和局部变量,而在函数中,优先调用的是局部变量,所以将为打印出20.
[130] 下列代码中,y的值是多少?
1 int main() {
2 int x=4;
3 float y = * (float *) &x;
4 return 0;
5 }
[131] 下列C程序的输出是什么?
1 #include<stdio.h>
2 int main() {
3 int i=0;
4 for(i=0;i<20;i++) {
5 switch(i) {
6 case 0:i+=5;
7 case 1:i+=2;
8 case 5:i+=5;
9 default: i+=4;
10 break;
11 }
12 printf("%d\n",i);
13 }
14 return 0;
15 }
输出是16,21。
注意两点,i在循环内进行了修改,case后没有跟着break。第一次进入循环,i将一次加5 2 5 4,然后打印输出16,最
后再加1。第二次直接进入default,加4,然后输出21。
[132] 编写一个递归函数求n的阶乘,n为正整数
[133] 编写一个递归函数求斐波纳契数列
[134] 下列代码在64位机上的输出是什么?
1 #include <stdio.h>
2 int main() {
3 int x = 10000;
4 double y = 56;
5 int *p = &x;
6 double *q = &y;
7 printf("p and q are %d and %d", sizeof(p), sizeof(q));
8 return 0;
9 }
由于“p”和“q”是指针,因此它们只不过是64位计算机中的地址。 无论它们指向整数还是双精度数据类型,两者的大小
均为64位(8字节)。
[135] 什么是链表?何时使用链表?
链表是由一组节点组成的数据结构,这些节点一起代表一个序列。链表是由一组节点组成的数据结构,这些节点一起
代表一个序列。如果我们不知道要存储的数据量,则首选链表。 例如:我们可以在员工管理系统中使用链接列表,在这里
我们可以轻松地添加新员工的记录(添加新节点-动态内存分配),删除旧员工的记录(删除节点),编辑 员工记录(在
节点中编辑数据)。
在[136]-[140]中,使用下列变量和定义:
1 struct node;
2 typedef struct node NODE;
3 typedef int Element;
4
5 // A pointer to a node structure
6 typedef NODE *LINK;
7
8 // A node defined as having an element of data
9 // and a pointer to another node
10 struct node { Element elem; LINK next; };
11
12 // The Head or start of the List
13 typedef struct { int size; LINK start; } ListHead;
[136] 编写一个C程序用于创建单链表
要创建单链表,我们需要:
1. 创建链表的HEAD(h)
2. 初始化链表的大小(为零)
3. 将起始指针指向NULL(在创建时为空)。
请参考以下函数来创建单链表:
1 ListHead createList() {
2 ListHead h;
3 h.size = 0;
4 h.start = NULL;
5 return h;
6 }
[137] 编写一个C程序用于在单链表的头部插入一个元素
在链表(h)的头部插入元素(e)时,我们需要:
1. 为新节点动态分配内存。
2. 为新节点中的元素分配值。
3. 将新节点中的“next”指针指向HEAD先前指向的节点。
4. 在链接列表HEAD中,增大“size”变量(随着添加了新节点),然后将“start”指针指向新节点。
[138] 编写一个C程序用于在单链表的尾部插入一个元素
在链接列表(h)的末尾插入元素(e)时,我们需要:
1. 为新节点动态分配内存。
2. 为新节点中的元素分配值。
3. 将新节点中的“next”指针指向NULL(因为新节点代表链表的尾部)。
4. 如果链表最初为空,则将HEAD中的“start”指针指向新节点,否则遍历链接列表以找出链接列表中的最后一个节
点,并将最后一个节点中的“next”指针指向新节点。
5. 在链表HEAD中增大“size”变量(随着添加了新节点)。
[139] 编写一个C程序用于在单链表的pos处插入一个元素
在链表(h)中的pos处插入元素(e)时,我们需要:
1. 为新节点动态分配内存,
2. 为新节点中的元素分配值。
3. 如果“pos”大于链表的大小,则返回错误消息(因为这是不可能的)。 否则,如果“ pos”为“ 0”,则将元素插入头
部(如上所示)。 否则,将链表遍历到“ pos”之前的节点。 将新节点中的“next”指针指向“pos-1”处的节点所指
向的节点,并将节点中“pos-1”处的“next”指针指向新节点。
4. 在链表HEAD中增大“size”变量(随着添加了新节点)。
[140] 编写一个C程序用于删除单链表的一个元素
从链表(h)中删除元素(e)时,我们需要:
1.检查链表是否为空。 如果为空,则无需删除任何内容。
2.如果链表不为空,则需要遍历链表以找到包含元素(e)的节点。 找到节点之后,我们需要在要删除的节点之前更
改节点中的“next”指针,以指向要删除的节点的“next”指针中存的值。
3.减小链表HEAD中的“size”变量(因为删除了节点)。
Programming in PERL
[141] 下列Perl代码的输出是什么?
1 my @value_array = ("Index0","Index1");
2 my $value;
3 foreach $value (@value_array){
4 $value =~ s/Index//;
5 }
6 print "@value_array\n";
结果:0 1
在foreach中使用$value索引,将会改变数组的值
[142] 下列Perl代码的输出是什么?
1 my @value_array = ("Index0","Index1");
2 my $value;
3 for(my $i=0; $i<@value_array; $i++) {
4 $value = $value_array[$i];
5 $value =~ s/Index//;
6 }
7 print "@value_array\n"
$value对于for循环来说是局部的,不会影响数组内容
-w是用于标记warning,对潜在的歧义代码进行警告。
use strict是Perl中编译指令,是提供给Perl编译器的指令,告诉编译器,如果perl代码中有不好的编码风格,那么提示
编译失败。也就是说,加上use strict后,我们的Perl代码的编写必须遵循一些规范,否则编译器会报错。
[144] 下列Perl代码的输出是什么?
[145] 下列Perl代码的输出是什么?
[146] 在Perl中如何将两个字符串进行拼接?在空白处填充
Perl使用“.”进行填充。因此空白处应该填写$string1.$string2
[147] 下面程序的输出是什么?
1 #!/usr/bin/perl
2 use warnings;
3 use strict;
4 my $scalar =0;
5 my @array = ("A","B","C","D");
6 $scalar = @array;
7 print "Scalar is $scalar\n";
标量会存储数组的元素数量,因此打印出来的值是4
[148] 正则匹配的特殊字符有哪些?解释他们的作用
1. \转义字符。 使元字符成为文字
2. ^匹配字符串/行开头的字符
3. $匹配字符串/行结尾的字符
4. .匹配除换行符以外的所有字符
5. *匹配0或更多次
6. +匹配1或更多次
7. -表示字符类中的范围(如a-z)
8. &匹配到的字符,通过$&引用
9. ()分组字符
10. []字符类以匹配单个字符
11. {}指定量词范围
12. <>指定单词锚点
13. ?匹配0或1次
14. |从多个模式中选择一个
[149] 正则表达式中的量词有哪些?他们的用法是?
1. *匹配0或更多次
2. +匹配1或更多次
3. ?匹配0或1次
4. {N}匹配N次
5. {N,}匹配至少N次
6. {N,M}匹配至少N次之多M次
[150] 正则表达式的锚位有哪些?他们的用法是?
1. ^匹配字符串开头
2. $匹配字符串结尾
3. <单词开头
4. >单词结尾
5. \b单词与非单词的边界
6. \B匹配\b所不能匹配的位置
在[151]-[155]中使用如下代码,针对问题再下面的空白处填空
1 #!/usr/bin/perl
2 use warnings;
3 use strict;
4 my $input_file = "input_file.txt";
5 my $output_file = "output_file.txt";
6 my @input_array;
7
8 open(OUTPUT_FILE,'>',$output_file) or die "Cannot Open $output_file file for writing\n$!\n";
open(INPUT_FILE,'<',$input_file) or die "Cannot Open $input_file for reading\n$!\n";
9
10 while(<INPUT_FILE>){
11 if($_ =~ /__?__/){
12 print OUTPUT_FILE $_;
13 }
14 }
15
16 close INPUT_FILE;
17 close OUTPUT_FILE;
[151] 将input_file.txt中仅包含小写字母(a到z)的所有行复制到output_file.txt
^([a-z]+)$
[152] 将input_file.txt中仅包含字母(a到z或者A-Z)的所有行复制到output_file.txt
^([a-zA-Z]+)$
[153] 将input_file.txt中仅包含字母或数字(a到z或者A-Z或者0-9)的所有行复制到
output_file.txt
^([a-zA-Z0-9]+)$
[154] 将input_file.txt中的所有行复制到output_file.txt
\$
[155] 将input_file.txt中的仅包含“\”或者“$”的所有行复制到output_file.txt
^([\\\$]+)$
[156] Perl中chop和chomp的作用是什么?
chop:删除字符串的最后一个字符,并返回该字符
chomp:删除字符串结尾的换行符,并返回删除的字符数
[157] 下列Perl代码的输出是什么?
1 #!/usr/bin/perl
2 use warnings;
3 use strict;
4 my $example_1 = "chop_example";
5 my $example_2 = "chop_example";
6 chop($example_1);
7 my $b = chop($example_2);
8 print "$example_1 AND $b\n";
chop_exampl AND e
参考[157]
[158] 下列Perl代码的输出是什么?
1 #!/usr/bin/perl
2 use warnings;
3 use strict;
4 my $example_1 = "chomp_example\n";
5 my $example_2 = "chomp_example\n";
6 chomp($example_1);
7 my $b = chomp($example_2);
8 print "$example_1 AND $b\n";
chomp_example AND 1
参考[157]
Verilog
[159] verilog中的阻塞赋值和非阻塞赋值有什么区别?
verilog支持阻塞与非阻塞两种赋值方式。使用阻塞赋值时,计算和赋值会立刻执行。因此但是顺序执行块中由多个阻
塞赋值,他们会按照阻塞的方式顺序执行。如下所示。
在这个例子中,所有赋值都采用的时阻塞赋值,执行时会立刻进行计算,然后完成赋值操作。因此在第三条语句中,
会将前两条语句中更新后的x和y用于第三条语句的计算并赋值。
在非阻塞赋值中,所有的赋值都将在当前的仿真时刻的最后执行。首先对右侧的表达式进行计算,然后才对左侧进行
赋值,因此在下面的例子中,三条语句都先进行右侧语句的计算,然后再一起对左侧进行赋值操作。结果就是,第三条语
句所采用的x和y是一个旧值,并不是前两条语句在这个仿真时刻改变后的值。
[160] 下面的两个例子综合时需要多少个Flip-Flop?
1 1)
2 always @(posedge clk)
3 begin
4 B = A;
5 C = B;
6 end
7 2)
8 always @(posedge clk)
9 begin
10 B <= A;
11 C <= B;
12 end
1)一个;2)两个
第一种情况下,A的值赋予B,B更新后的值在同一个周期将赋予C,因此只需要一个触发器即可
第二种情况下,B更新后的值,在下一个周期才能赋予C,需要两个触发器实现
[161] 下列代码的输出是什么?
由于非阻塞赋值只能在周期结束生效,而display语句打印的是当前值,所以结果是a=0。
[162] 编写verilog代码,交换两个寄存器的值,并且不使用中间寄存器
[163] 下列代码的输出是?
1 module test;
2 int alpha,beta;
3 initial begin
4 alpha = 4;
5 beta = 3;
6 beta <= beta + alpha;
7 alpha <= alpha + beta;
8 alpha = alpha - 1;
9 $display("Alpha=%0d Beta=%0d", alpha,beta);
10 end
11 endmodule
这些赋值在一个没有任何时钟的initial块中,所以非阻塞赋值是不起作用的,因此只关心阻塞赋值。结果是:Alpha=3
Beta=3
[164] 下列两种情况下c的值会是多少(五个时间单位后)?
1 1)
2 initial begin
3 a=0;
4 b=1;
5 c = #5 a+b;
6 end
7 2)
8 initial begin
9 a=0;
10 b=1;
11 #5 c = a+b;
12 end
13 initial begin
14 a=0;
15 #3 a=1;
16 end
1. c=1
2. c=2
第一种情况下,c=a+b,但是需要五个时间单位的延迟以后才完成赋值
第二种情况下,在c=a+b赋值完成之前,另一个initial块中,第三个时间单位时,修改了a的值,所以在计算a+b时,
a=1,因此最终结果为2
[165] 分析下面的代码,找出代码的错误
1 bit a, b, c, d, e;
2 always @(a, b, c) begin
3 e = a & b & c & d;
4 end
敏感列表缺失了d,这会导致仿真结果错误,而综合结果可能是正确的
[166] 编写verilog模块,使用“?:”运算符实现3:1mux
[167] 用下列两段代码进行建模,这两种代码风格有什么问题?
verilog仿真器并不能保证always块的执行顺序,在上面的代码中,由于使用了阻塞赋值,因此会导致竞争现象。如果
我们使用不同的仿真器,always块的执行顺序不同可能会导致不同的结果。推荐使用非阻塞赋值。
[168] 同步复位和异步复位之间有什么区别?如何使用verilog进行同步复位和异步复位建模?
上电以后,使用复位进行状态设定为一个确定状态。如果对复位在时钟的边沿进行采样,那么就是同步复位。如果不
依赖于时钟边沿进行复位采用,则为异步复位。
下面的代码为同步复位
下面的代码为异步复位
[169] “==”和“===”有什么区别?
两者都是相等或比较运算符。“==”测检查二值逻辑相等,而“===”运算符测试四值逻辑相等。
使用“==”比较四值逻辑,如果出现X或者Z,则结果为X。
使用“===”比较四值逻辑,如果出现X或Z,则结果为0或1,能够正确的进行比较。
1. A==B只能判断非X/Z,出现X或Z,最后的结果为X
2. A===B能够判断X/Z,结果为1
[171] 用verilog建模Latch和Flip-Flop,并解释他们的不同
Flip-Flop,在时钟上下沿进行采样。Latch,在使能时,一直进行采样,输出跟随输入
D触发器
锁存器
[172] 编写verilog代码,检测序列10110
首先设计状态机。
1. 没有检测到序列
2. 检测到1
3. 检测到10
4. 检测到101
5. 检测到1011
状态机如下
1 module seq_detector(z,x,clock,reset);
2 output z;
3 input x,clock;
4 input reset; //active high
5 reg [2:0] state,nextstate;
6 parameter s0=3'b000,s1=3'b001,s2=3'b010,s3=3'b011,s4=3'b100;
7
8 always @ (posedge clock) begin
9 if(reset) begin
10 state <=s0;
11 nextstate<=s0;
12 end else begin
13 state<=nextstate;
14 end
15 end
16
17 always @ (x or state)
18 case(state)
19 s0: if(x) nextstate=s1; else nextstate=s0;
20 s1: if(x) nextstate=s1; else nextstate=s2;
21 s2: if(x) nextstate=s3; else nextstate=s0;
22 s3: if(x) nextstate=s4; else nextstate=s2;
23 s4: if(x) nextstate=s1; else nextstate=s2;
24 endcase
25
26 always @ (x or state)
27 case(state)
28 s4: if(x) z=1'b0; else z=1'b1;
29 s0,s1,s2,s3: z=1'b0;
30 endcase
31 endmodule
[173] 写一段verilog代码,根据输入的n计算斐波那契数列
斐波那契数列是一种数列,每一项是通过将前两项相加而得到的。 从0和1开始,顺序为0、1、1、2、3、5、8、13、
21、34,依此类推。 通常,表达式为xn = xn-1 + xn-2。 假设最大值n = 256,以下代码将生成第n个斐波那契数。 值“n”
作为输入传递给模块(nth_number)
1 module fibonacci(input clock, reset, input [7:0] nth_number, output [19:0] fibonacci_number,
output number_ready);
2 reg [19:0] previous_value, current_value;
3 reg [7:0] internal_counter;
4 reg number_ready_r;
5
6 always @(posedge clock or posedge reset) begin
7 if(reset) begin
8 previous_value <='d0; //1st Fibonacci Number
9 current_value <='d1; //2nd Fibonacci Number
10 internal_counter <='d1;
11 number_ready_r <= 0;
12 end else begin
13 if (internal_counter == (nth_number-2)) begin
14 number_ready_r <= 1;
15 end else begin
16 internal_counter <= internal_counter + 1;
17 current_value <= current_value + previous_value;
18 previous_value <= current_value;
19 number_ready_r <= 0;
20 end
21 end
22 end
23
24 assign fibonacci_number = current_value;
25 assign number_ready = number_ready_r
26
27 endmodule
[174] 写一段verilog代码,用半加器组成全加器
[175] verilog中的task和function有什么区别?
1. function不能使用任何延时语句,task可以
2. 由于1中的原因,function可以调用function,但是不能调用task。task可以调用task和funtion
3. funtion可以综合,task不可以
4. function需要有通过返回参数作为输出,但是有多个输入输出参数。task不能返回任何值,但是具有多个输入或
者输出参数。
SystemVerilog
[176] systemverilog中的reg,wire和logic有什么区别?
reg和wire是Verilog中就存在的两种数据类型,而logic是SystemVerilog中引入的新数据类型。
1. wire是一种数据类型,可以对物理导线进行建模以连接两个元素。 导线只能由连续赋值语句驱动,如果不驱
动,则无法保持值。 因此,wire只能用于对组合逻辑进行建模。
2. reg是可以为存储数据或状态建模的数据类型。 它们需要由always块驱动,而不能由连续赋值语句驱动。 reg可
用于建模顺序逻辑和组合逻辑。
3. logic是SystemVerilog中的一种新数据类型,可用于wire和reg建模,也是四值逻辑,可以被用作reg也可以
wire。
[177] bit和logic有什么区别?
bit是只能存储0和1的二值逻辑,而logic能够储存0、1、X和Z的四值逻辑。
二值逻辑能够加速仿真速度,而如果用二值逻辑用于驱动或者采样来自RTL的信号,会导致错误采样X和Z
byte是有符号类型,最大为127,而logic可以被声明为无符号,最大可以达到255.
[179] 动态数组和关联数组,哪个更加适合模拟大型数组?例如32KB的巨大内存数组
关联数组更加适合用于大型数组的建模,因为只有在讲元素写入数组时才分配内存。而动态数组需要在使用之前分配
和初始化内存。例如:如果要使用动态数组对32KB的内存数组进行建模,则首先需要分配32K的空间用于读/写。但是由于
关联数组内部使用哈希进行元素的搜索,所以速度也是最慢的。
[180] 有一个动态数组通过下列代码进行初始化,写一段代码找出数组中所有大于3的元素
[181] systemverilog中的union和struct有什么区别?
struct表示不同数据类型的集合。 例如:在下面的示例中,我们定义了一个名为instruction_s的struct,该struct由24位
地址和8位操作码构成。
1 typedef struct {
2 bit [7:0] opcode;
3 bit [23:0] addr;
4 } instruction_s;
5 instruction_s current_instruction;
6 current_instruction.addr='h100;
可以直接引用instruction_s,也可以单独引用成员。存储struct所需的内存空间为成员之和,例如instruction_s需要
32bit的空间。
union是一种数据类型,可以使用有且只有一种命名成员数据类型来访问它。 与struct不同,不能访问所有成员数据类
型。 分配给union的内存将是成员数据类型所需的最大内存。 如果要对诸如寄存器之类的硬件资源建模,该寄存器可以存
储不同类型的值,则union很有用。 例如:如果寄存器可以存储整数和实数值,则可以按以下方式定义union:
1 typedef union {
2 int data;
3 real f_data;
4 }state_u;
5 state_u reg_state;
6 reg_state.f_data = 'hFFFF_FFFF_FFFF_FFFF;
7 $display(" int_data =%h", reg_state.data);
在此示例中,state_u可以保存32位整数数据,也可以保存64位实数数据。 因此,为reg_state分配的内存将为
64bit(两种数据类型中的较大者)。 由于所有成员数据类型都有共享内存,因此在上面的示例中,如果我们将64位值分
配给reg_state.f_data,则我们也可以使用其他数据类型引用相同的32位。
例如:这是一个CRC函数的示例,该函数需要一个大数据包作为参数来计算CRC。 通过作为参考传递,每次调用CRC
函数都不需要在存储器上创建数据包的副本。
const关键字用于禁止函数修改传递的参数。例如:在同一个CRC函数中,可以将参数声明为“const ref”参数,如下所
示,以确保原始数据包内容不会被CRC函数意外修改。
[183] 下面代码中参数a和b的方向是什么?
function和task的每一个参数都有他的方向,input,ouput,inout或者ref。如果没有显式声明,则默认与前面的参数
保持一致,如果前面没有参数,则默认为输入。上述代码中,第一个参数array的方向为ref,而后续a和b没有显式声明,所
以将延续前者的方向,即为ref。
[184] 压缩数组和非压缩数组的区别是?
压缩数组表示一组连续的bit,而非压缩数组不是。在声明上,有下面的区别
压缩数组只能由一位数据类型(bit,logic,reg)或枚举类型组成。非压缩数组能够由任意类型组成
压缩结构体是一种可以将压缩位向量作为结构成员进行访问的方法。 换句话说,如果struct的所有成员仅由位字段组
成并且可以无间隙地打包在内存中,则它可以是压缩结构。 例如:在下面的struct定义中,所有成员都可以表示为位向量
(int等于32位,short int到16位,byte到8位),并且一个struct可以打包到单个连续内存56bit。
1 struct packed {
2 int a;
3 short int b;
4 byte c;
5 } pack1_s;
非压缩结构体不需要打包到连续的bit位中,因此在不同成员之间可以存在空隙。下面是一个无法压缩的结构体。
1 struct {
2 string name;
3 int age;
4 string parent;
5 } record_s
[186] 下面哪个是对的?
1. function不能消耗仿真时间
2. task不能消耗仿真时间
3. 正确
4. 错误
[187] 现有一个动态数组的大小为100,如何把他的大小定义为200,并且前100个元素例子原来
的数组?
动态数组使用new[]进行保留元素的内存分配。
[188] systemverilog中case,casex和casez的区别是?
case语句是选择语句,匹配表达式并执行对应的语句。下面是一个3:1MUX
1 case (select[1:0])
2 2'b00: out_sig = in0;
3 2'b01: out_sig = in1;
4 2'b10: out_sig = in2;
5 default: out_sig = 'x;
6 endcase
上面的例子中,如果表达式与指定的内容完全匹配,则执行后续语句,如果出现x或者z,将执行默认语句。
casez是case的一个特殊版本,通常在处理较少位的译码器中使用。下面的示例中,将3位中断申请队列解码位三个独
立的中断引脚,只关心对应位,而其他位无关紧要。带有优先级。
1 casez (irq)
2 3'b1?? : int2 = 1'b1;
3 3'b?1? : int1 = 1'b1;
4 3'b??1 : int0 = 1'b1;
5 endcase
“ casex”是另一个特殊版本,除了无关项,在比较中它也忽略X和Z值。
[189] 在case、casez、casex中使用的是==还是===?
三者使用的都是===
1. $display:执行时立刻打印内容
2. $strobe:在当前的timestep结束时打印内容
3. $monitor:在timestep结束时,如果内容改变了,则进行打印。如果多次调用,则新的覆盖旧的。
4. $write:和$display一样,但是不会在结尾打印换行符
[191] 下面的systemverilog代码中有什么错误?
1 task wait_packet;
2 Packet packet;
3 event packet_received;
4 @packet_received;
5 packet = new();
6 endtask
7 function void do_print();
8 wait_packet();
9 $display("packet received");
10 endfunction
function中不能使用任何延时语句。上面的例子中,function调用了一个耗时的task,这是非法的。
[192] systemverilog中new()和new[]有什么区别?
new()时systemverilog中类的构造函数。他在类中定义,并初始化对象。
new[]用于动态数组的内存分配。
[193] 什么是systemverilog中的前置声明?
有时候,一个类有可能引用另一个尚未编译的类,这会导致编译错误。例如,如果按照下面的顺序编译Statistics和
Packet,由于在编译Statistics时,Packet尚未定义,编译器将为报错。
1 class Statistics;
2 Packet p1;
3 endclass
4
5 class Packet; //full definition here
6 endclass
为了避免这个问题,类在完全定义之前,可以先进行前置声明。
[194] 下面代码有什么问题?
这段代码在运行时,会产生空指针错误。task传入pkt句柄,而在内部为句柄创建了一个对象。在initial块中,调用了
gen_packet,并修改了pkt.dest,但是对于task来说这些都是局部的。task的默认方向是input,在内部的修改句柄的指向并
不能影响外部,尽管在task内部进行了对象例化并且修改了值,而实际上外部的pkt始终是空句柄。
[195] systemverilog中,类成员的private、public和protect属性是什么意思?
1. private成员只允许在class内部进行访问。这些成员在派生类中也是不可见的。
2. public成员在class内外都可以进行访问。这些成员在派生类中是可见的。
3. protect成员只允许在class内部进行访问。这些成员在派生类中是可见的。
[196] systemverilog的类中,成员变量默认是public还是private?
与C++和Java中不一样,systemverilog的类成员默认为public。
[197] 什么是嵌套类?何时使用他?
当一个类的定义包含另一个类的定义,则该类为嵌套类。例如,下面代码中,StringList类定义包含另一个类Node的定
义。
1 class StringList;
2 class Node; // Nested class for a node in a linked list.
3 string name;
4 Node link;
5 endclass
6 endclass
嵌套允许隐藏本地名称和资源的本地分配。 当需要新的类作为类实现的一部分时很有用。
[198] systemverilog中的interface是什么?
systemverilog中的接口用于将多个线网进行捆绑,有助于封装设计块之间的接口通信。接口可以在设计中实例化,并
且可以使用单个名称进行连接,不需要显式声明所有的端口和连接。除了用于连接,接口内部还可以定义function,可以
通过实例化接口调用function。接口还支持过程块和assign,这些对于协议检查和断言非常有用。下例是一个接口的简单示
例:
[199] 什么是modport?
modport(模块端口的缩写)是接口中的一种构造,可用于分组信号并指定方向。 以下是如何使用Modport将接口进
一步分组以连接到不同组件的示例。
上面的示例中,相同的信号在不同的modport中具有不同的方向。
[200] interface是可综合的吗?
是可综合的。
[201] 什么是时钟块?在interface中使用时钟块有什么好处?
时钟块类似于modport,除了具备modport的信号方向指定,还能够建模信号的时序行为。下面是一个时钟块的例
子。
在上面的示例中,定义了一个名为sample_cb的时钟块,关联的时钟为clk。default关键字定义默认的时钟偏斜,输入
为2ns,输出为3ns。输入偏斜定义了时钟采样在时钟边沿前多少个时间单位。 输出偏斜定义了时钟驱动在时钟边沿后的多
少个时间单位。
时钟块只能在module或者interface中定义
[202] 下面两种定义时钟偏斜的方式有什么不同?
定义时钟偏斜有两种方式
1. 以time step为单位,这是由全局的时间精度定义的,即`timescale
2. 之间指明具体的时间长度
[203] systemveirlog仿真里在一个timestep中有哪些主要的阶段?
systemverilog仿真器是事件驱动的仿真器,明确定义了不同的阶段来计划和执行所有事件。在仿真中,所有的事件都
以timeslot为单位进行。timeslot被划分为一组有序阶段,提供设计和测试代码之间的可预测交互。如下图所示,一个
timeslot可以划分为五个主要阶段,每个阶段都可以进一步的被划分为细分的子阶段。
1. Prepone:这是timeslot最先执行的阶段,并且只执行一次。来自测试平台对设计信号的采样发生在这个阶段。
2. Active:Active阶段包括三个子阶段:Active,Inactive和NBA(Nonblocking assignment)阶段.RTL代码和行为代码
在Active阶段执行。阻塞赋值在Active阶段执行。非阻塞赋值,RHS的计算在Active阶段执行,赋值操作在NBA
阶段执行。如果由#0的赋值行为,则在inactive阶段执行。
3. Observed:Observed用于计算property(并发断言中)表达式的触发行为。在property计算期间,通过或者失败的
调度会在当前timeslot的Reactive阶段进行。
4. Reactive:Reactive阶段包括三个子阶段:Re-Active,Re-Inactive和Re-NBA(Re-Nonblocking assignment)阶
段。用于执行systemverilog中program块的阻塞赋值,#0阻塞赋值,非阻塞赋值。这个独立的响应阶段确保在
测试代码执行之前,设计代码的状态已经稳定。使用OVM/UVM不需要程序块,因此Reactive阶段可能使用得不
多。
5. Postponed:Postponed阶段是当前timeslot的最后一个阶段。$monitor,$strobe和其他类似的事件在此区域执
行。而$dislpay事件在Active 和 Reactive(如果在program块中调用)阶段执行
[204] 根据下面的约束,哪一个选项是错误的?
将约束内容取交集,c可以取27-30之间的任意值。
[205] 什么是systemverilog中的unique约束?
unique约束会令一组成员两两各不相同。下面是一个示例。
1 class Test;
2 rand byte a[5];
3 rand byte b;
4 constraint ab_cons { unique {b, a[0:5]}; }
5 endclass
[206] 如何选择性的激活或者关闭一个类中的约束?
1 //<object>.constraint_mode(0) :: 关闭对象中的所有约束
2 //<constraint>.constraint_mode(0) :: 选择性关闭某一个约束
3 class ABC;
4 rand int length;
5 rand byte SA;
6 constraint c_length { length inside [1:64];}
7 constraint c_sa {SA inside [1:16];}
8 endclass ABC abc = new();
9 abc.constraint_mode(0) // 关闭所有约束
10 abc.c_length.constraint_mode(0) // 只关闭约束c_length
[207] 现有下面的一个类,如何生成addr大于200的Packet对象?
1 class Packet;
2 rand bit[31:0] addr;
3 constraint c_addr { addr inside [0:100];}
4 endclass
大于200与内置约束冲突,所以需要先关闭内置约束,然后通过内联约束进行随机化
1 Packet p = new();
2 p.c_addr.constraint_mode(0);
3 p.randomize with {addr > 200;};
[208] 什么是pre_randomize()和post_randomize()函数?
这显示systemverilog内建的回调函数。在调用randomize之前会自动调用pre_randomize函数,之后会自动调用
post_randomize函数。可以通过定义这两个函数,完成在随机化之前或者之后进行某些操作。
[209] 编写一个约束,为下面对象中的动态数组生成约束,使得每个元素都小于10,数组大小也
小于10
1 class dynamic_array;
2 rand unsigned int abc[];
3 endclass
1 constraint c_abc_len {
2 abc.size() < 10;
3 foreach (abc[i])
4 abc[i] < 10;
5 }
[210] 编写约束,创建随机整数数组,使数组大小在10-16之间,并且数组按照降序排列
1 class array_abc;
2 rand unsigned int myarray[];
3 endclass
1 constraint c_abc_val {
2 myarray.size inside { [10:16] };
3 foreach (myarray[i])
4 if (i>0) myarray[i] < myarray[i-1];
5 }
[211] 如何对一个生成一个随机动态数组,并且元素两两各不相同?参考下面的代码
1 class TestClass;
2 rand bit[3:0] my_array[];//dynamic array of bit[3:0]
3 endclass
1. 使用unique进行约束
1 constraint c_rand_array_uniq {
2 my_array.size == 6; //or any size constraint
3 unique {my_array}; //unique array values
4 }
2. 不适用unique进行约束,使用post_randomize回调函数完成
1 constraint c_rand_array_inc {
2 my_array.size == 6 ;// or any size constraint
3 foreach (my_array[i])
4 if(i >0)
5 my_array[i] > my_array[i-1];
6 }
7 function post_randomize();
8 my_array.shuffle();
9 endfunction
systemverilog支持三种类型的动态进程,可以在运行时创建,并作为独立线程执行。
1 initial begin
2 fork
3 task1; // Process 1
4 task2; // Process 2
5 task3; // Process 3
6 join $display(“All tasks finished”);
7 end
2. fork-join_any:使用“ fork…join_any”创建的进程作为单独的进程运行,但是父进程会在任何一个子进程完成后
继续。其余进程和父进程可以并行运行。 如果我们看下面的示例:有三个进程-task1,task2和task3将并行运
行。 当task1 / task2 / task3之一完成时,join_any将完成,并在其他线程可能仍在运行时执行$ display()。
1 initial begin
2 fork
3 task1; // Process 1
4 task2; // Process 2
5 task3; // Process 3
6 join_any
7 $display(“Any one of task1/2/3 finished”);
8 end
3. fork-join_none:使用“ fork…join_none”创建的进程将作为单独的进程运行,但是父进程不会停滞并且也将并行
进行。 参考以下示例,其中有三个进程-task1,task2和task3,它们将与父进程并行运行。
1 initial begin
2 fork
3 task1; // Process 1
4 task2; // Process 2
5 task3; // Process 3
6 join_none
7 $display(“All tasks launched and running”);
8 end
1 initial begin
2 fork task1; // Process 1
3 task2; // Process 2
4 join_none
5 $display(“All tasks launched and running”);
6 wait fork;
7 $display(“All sub-tasks finished now”);
8 end
类似的,disable fork可以提前将子进程停止。
1 initial begin
2 fork
3 task1; // Process 1
4 task2; // Process 2
5 join_any
6 $display(“One of task1/2 completed ”);
7 disable fork;
8 $display(“All other tasks disable now”);
9 end
[214] 硬约束和软约束有什么区别?
systemverilog中编写的常规约束为硬约束,对成员的随机化必须始终满足约束,如果约束无解,则会导致错误。如果
将约束定义为软约束,在没有外部约束的条件下,和硬约束一样,外部约束的优先级比软约束高。软约束通常用于指定随
机变量的默认值和分布,并且可以被外部特定的约束覆盖。
1 class Packet;
2 rand int length;
3 constraint length_default_c {
4 soft length inside {32,1024};
5 }
6 endclass
7
8 Packet p = new();
9 p.randomize() with { length == 1512; }
上例中,如果约束没有定义为软约束,则随机化会失败。
[215] 下面每个线程的输出是什么?
1 initial begin
2 for (int j=0; j<3; j++) begin
3 fork
4 automatic int result;
5 begin
6 result= j*j;
7 $display("Thread=%0d value=%0d", j, value);
8 end
9 join_none
10 wait
11 fork;
12 end
13 end
由于“ j”不是每个线程的动态变量,因此在生成每个线程后它会不断递增,并且当所有线程开始执行时,每个线程将看
到的j都是3。因此,每个线程都将输出9。 如果每个线程打算使用不同的值,则应将“ j”的值复制到一个自动变量,如下所
示:
1 automatic int k = j;
2 begin
3 result = k*k;
4 end
[216] 下面的代码会产生多少个并行的线程?
1 fork
2 for (int i=0; i < 10; i++ ) begin
3 ABC();
4 end
5 join
for循环在fork join内,所以只有一个线程。
[217] 下面的约束有什么错误?
1 class packet;
2 rand bit [15:0] a, b, c;
3 constraint pkt_c { 0 < a < b < c; }
4 endclass
约束表达式中,至多只有一个关系运算符(<,<=,==,>=或>)。如果要实现变量的顺序约束,需要多个表达式。
在类中将方法定义为虚方法,则在派生类中可以重写这个方法。基类可以定义具有实现或不实现的虚函数,在派生类
中也可以选择覆盖或不覆盖。而纯虚函数只具备函数声明,没有具体实现,在派生类中必须有具体实现。纯虚函数常用在
抽象类中使用,下面是一个示例。
[219] 什么是Semaphores?何时使用?
Semaphores是用于控制对公用资源的机制。Semaphores可以视为在创建时具有多个钥匙的存储池,使用Semaphores
访问资源时,首先要取得要是,然后才能够继续执行。通过这种机制,可以确保没有要是的进程一直等待到获取钥匙。
Semaphores通常用于相互排斥,对公用资源进行访问控制以及简单同步。下面是简单的Semaphores的创建方法。
1 semaphore smTx;
2 smTx = new(1); //create the semaphore with 1 keys.
get()和try_get()分别是阻塞和非阻塞的获取钥匙的方法,put()用于返还钥匙。
[220] 下面两个约束有什么不同?
1 1)
2 class ABSolveBefore;
3 rand bit A;
4 rand bit [1:0] B;
5 constraint c_ab { (A==0) -> B==0; solve A before B; }
6 endclass
7 2)
8 class ABSolveBefore;
9 rand bit A;
10 rand bit [1:0] B;
11 constraint c_ab { (A==0) -> B==0; solve B before A; }
12 endclass
两种情况下,A都能取到0和1,B能取到0123,并且A为0时B都为0。但是求解顺序会导致两者的分布会不一致。
如果先求解A再求解B,那么概率分布表为
A B 概率
0 0 0.5
0 1 0
0 2 0
0 3 0
1 0 0.5*0.25
1 1 0.5*0.25
1 2 0.5*0.25
1 3 0.5*0.25
如果先求解B再求解A,那么概率分布表为
A B 概率
0 0 0.5*0.25
0 1 0
0 2 0
0 3 0
1 0 0.5*0.25
1 1 0.25
1 2 0.25
1 3 0.25
[221] 什么是mailbox?如何使用mailbox?
mailbox是一种通信机制,用于线程之间的数据交换。数据在一个线程存入mailbox中,在另一个线程中检索。下面是
mailbox的声明与创建的示例:
1 mailbox mbxRcv;
2 mbxRcv = new();
将数据存入mailbox中可以使用put(阻塞)和peek(非阻塞)实现,从mailbox中取出数据可以使用get(阻塞)和try_get(非阻
塞)方法,查询mailbox中的数据数量可以使用num()方法。
[222] 有限容量和无限容量的mailbox有什么区别?如何创建?
有限容量的mailbox是指邮箱在创建时就指定容量。
1 mailbox mbxRcv;
2 mbxRcv = new(10); //size bounded to 10
无限容量的mailbox是指邮箱再创建时不指定容量。
1 mailbox mbxRcv;
2 mbxRcv = new(); //size is unbounded or infinite
有限容量的mailbox如果存满,线程将无法继续存入数据,只到mailbox有空间。
[223] 什么是systemverilog中的event?如何触发event?
event类型的变量不用于存储数据,用于线程同步。可以使用"->"显式触发事件,而线程可以通过"@"来等待事件的触
发,阻断线程只到事件被触发。event为两个或者多个同时运行的进程的同步提供强大而有效的手段。
下面的示例中,两个线程通过event进行同步。一旦发送请求,send_req()将会触发一个事件,而receive_response()检
测到事件被触发以后就会继续执行任务。
1 module test;
2 event req_send;
3
4 initial begin
5 fork
6 send_req();
7 receive_response);
8 join
9 end
10
11 task send_req(); //create and send a req
12 -> req_send; //trigger event
13 endtask
14
15 task receive_response();
16 @req_send; //wait until a send event is triggered
17 //collect response
18 endtask
19 endmodule
[224] 如何合并两个event?
可以指将将一个event变量赋值给另一个event变量,此时两个event变量都指向同一个同步对象,可以认为两者合并。
[225] 什么是systemverilog中的std::randomize()方法?何时使用它?
std::randomize()是作用域随机化函数,无需定义类或者实例化类对象仅能对当前作用域中的数据进行随机化。如果某
些需要随机化的变量不是类的成员,则需要使用std::randomize()。下面是一个示例。
1 module stim;
2 bit [15:0] addr;
3 bit [31:0] data;
4 function bit gen_stim();
5 bit success, rd_wr;
6 success = std::randomize(addr, data, rd_wr);
7 return rd_wr ;
8 endfunction
9 …
10 endmodule
std::randomize()和类的randomize()具有相似的功能,也可以使用约束,下面是一个通过with添加约束的示例。
1 success = std::randomize( addr, data, rd_wr ) with {rd_wr -> addr > 'hFF00;};
[226] 在派生类中可以覆盖基类中的约束嘛?如果可以,如何实现?
可以通过使用相同的约束名称在派生类中重写基类定义的约束。下面是一个示例
1 class Base;
2 rand int a ;
3 rand int b;
4 constraint c_a_b_const {a < b;}
5 endclass
6
7 class Derived extends Base;
8 constraint c_a_b_const {a > b;}
9 endclass
[227] 下面的systemverilog代码的调用有什么问题?
在约束中,不允许调用方向为ref的函数,除非使用“const ref”,这保证函数在内部不会修改参数。
[228] 下面两个派生类有什么不同?
1 class Base;
2 virtual function printA();
3 endfunction
4 endclass
5 1)
6 class Derived extends Base;
7 function printA();
8 //new print implementation
9 endfunction
10 endclass
11 2)
12 class Derived extends Base;
13 virtual function printA();
14 //new print implementation
15 endfunction
16 endclass
两者并没有区别,在基类中如果定义了virtual关键字,那么派生类也会继承该属性,无论有没有显式的二次声明。
[229] 找出下面代码中的问题(如果有的话)
1 class Packet;
2 bit [31:0] addr;
3 endclass
4
5 class ErrPacket extends Packet;
6 bit err;
7 endclass
8
9 module Test;
10 initial begin
11 Packet p;
12 ErrPacket ep;
13 ep = new();
14 p = ep;
15 $display("packet addr=%h err=%b", p.addr, p.err);
16 end
17 endmodule
没有问题,基类句柄可以指向派生类对象,但是反过来是不允许的。
[230] 现有下面两个类,请问在示例代码中compute_crc函数的调用顺序是?
示例
1 Packet pkt;
2 BadPacket badPkt;
3 initial begin
4 pkt = new;
5 pkt.compute_crc; // 1) Which of compute_crc() gets called?
6 badPkt = new;
7 badPkt.compute_crc; // 2) Which of compute_crc() gets called?
8 pkt = badPkt; // Base handle points to ext obj
9 pkt.compute_crc; // 3) Which of compute_crc() gets called ?
10 end
1. 调用了基类的compute_crc
2. 调用了派生类的compute_crc
3. 调用了派生类的compute_crc,虽然使用的是基类的句柄,但是方法定义为虚方法,所以要根据对象的类型进行
调用
[231] 下面两种代码风格哪种更加好?为什么?
1 1)
2 for (i=0; i < length*count; i++) begin
3 a[i] = b[i];
4 end
5 2)
6 l_end = length * count;
7 for (i=0; i < l_end; i++) begin
8 a[i] = b[i]
9 end
2比1更好,在1中,每次迭代都需要计算length*count,2中只需要计算一次
[232] 下面的代码有什么错误?
1 class ABC;
2 local int var;
3 endclass
4
5 class DEF extends ABC;
6 function new();
7 var = 10;
8 endfunction
9 endclass
变量var具备local关键字,在派生类中是不可用的。
[233] 什么是虚接口,何时使用它?
虚接口是指向实际结构的变量。他在类中用于提供接口的连接点,通过虚接口可以访问接口中的信号。在下面的示例
中,接口bus_if将多个信号整合起来。然后,BusTransactor类中定义了这一接口类型的虚接口,这个虚接口用于访问来自
this.b_if的所有驱动或检测。实例化物理接口以后,通过构造函数将句柄传递给BusTransactor类。
[234] 工厂和工厂模式的意思是?
在面向对象编程中,工厂是用于创建原型或类的不同对象的方法或函数。不同的类在工厂中注册后,工厂方法可以通
过调用相应的构造函数来创建任何已注册类类型的对象。创建对象不直接调用构造函数的模式称为工厂模式。使用基于工
厂的对象创建而不是直接调用构造函数,允许在对象创建中使用多态性。这个概念是在UVM (Univers)中实现的。
[235] 回调函数(callback)的意义是什么?
“回调”是由另一个函数调用的任何函数,它以第一个函数为参数。大多数情况下,当某个“事件”发生时,会调用回调函
数。在验证平台中,回调函数很多优点:
1. 注入从驱动程序发送的事务错误
2. 当一个模拟阶段准备结束时,调用一个函数来关闭所有序列/驱动程序中所有挂起的事务。
3. 在一个特定的事件上调用一个覆盖率采样函数。
大多数情况下,回调函数是通过将它们注册到一个组件/对象中来实现的,该组件/对象会在某些定义的条件下回调。
UVM中的phase_ready_to_end()就是回调函数,它在基类中实现,并注册到UVM_component类中。当当前仿真阶段准备结
束时,将调用该函数。因此,用户可以通过覆盖此函数定义来实现需要在仿真阶段结束时执行的任何功能。
[236] 什么是DPI调用?
DPI是直接编程接口的缩写,它是SystemVerilog和C/C++等外语编程语言之间的接口。DPI允许在接口两边的语言之间
直接进行跨语言函数调用。在C语言中实现的函数可以在SystemVerilog中调用(import),在SystemVerilog中实现的函数可
以使用DPI层在C语言中调用(export)。DPI支持跨语言边界的function(零时间执行)和task(耗时执行)。SystemVerilog数据类
型是惟一能够在任何方向上跨越SystemVerilog和外部语言之间的边界的数据类型。
import的DPI函数是用C语言实现并在SystemVerilog代码中调用的函数。
export的DPI函数是用SystemVerilog语言实现并导出到C语言的函数,这样就可以从C语言调用它。
函数和任务都可以导入或导出。
[238] 什么是系统函数?举例说明他们的作用
SystemVerilog语言支持许多不同的内置系统任务和函数,通常在任务/函数名称前加上“$”前缀。此外,语言还支持添
加用户定义的系统任务和功能。下面是一些系统任务和功能的例子(根据功能分类)。对于完整的列表,可以参考LRM。
Fundamentals of Verification
[239] 定向测试和受约束的随机测试有什么区别?两者有什么优缺点?
定向测试是一种编写定向测试来验证设计中的每个特性的方法。约束随机测试是一种使用约束随机生成器自动生成激
励的方法,该生成器根据设计规范生成激励。下表比较了两者的优缺点。推荐的方法是混合使用这两种方法——约束随机
覆盖大部分验证空间,然后指导测试覆盖难以到达的边界条件。
定向测试 约束随机测试
针对每个功能点需要编写一个或者多个
使用激励发生器根据功能点,自动生成符合功能规范的测试向量
测试向量
每次测试都能很简单的进行追踪,具有 测试是自动生成的,因此只能通过收集覆盖率,并观察覆盖率确保功
很好的可视化和可预测性 能的验证
当设计特征被充分了解后,定向测试的 开发约束随机测试平台更加复杂,也更加需要经验。需要更多的事件
编写会更加简单 来设计验证平台。
对于复杂的设计,定向测试的编写会变
与大型测试套件相比,约束随机生成器在开发后更容易维护
得非常困难并且事件消耗会很大
定向测试编写仅限于通过理解设计规范 约束随机生成器可以结合随机配置来覆盖更多的场景和特性,从而更
确定的场景 好地强调设计,并覆盖手动识别可能遗漏的一些场景
自检测试是指在测试结束时通过某种方式来检测测试结果的测试。在测试中,可以通过计算某些内存操作的结果或从
DUT(如状态寄存器或任何其他信息)收集结果来预测结果。
[241] 什么是覆盖率驱动的验证?
在覆盖率驱动的验证方法中,验证计划是通过将每个特性或场景映射到一个覆盖率监视器来实现的,该监视器在仿真
期间收集覆盖率信息。
1. 覆盖率可以是基于样本的覆盖组和基于属性的覆盖的组合。
2. 在基于覆盖率的验证中,测试通常使用约束的随机激励生成器生成,测试正确性由功能检查器确保,并为实现
的所有监视器收集覆盖率。
3. 通常在设计上对随机生成器的多个测试或多个种子进行回归,并合并从每个测试中收集到的单个覆盖率,从而
获得累积覆盖率。有时,在设计的一个角边界情况可能不容易被覆盖,使用约束随机激励和可能更好地完成使
用一个有指导的测试。
4. 覆盖率信息还为测试的质量和生成器中的约束提供反馈,并帮助对约束进行微调,从而有效地随机生成刺激
励。
5. 由于在这种方法中,覆盖率定义是跟踪验证执行以获得进展和完成的关键步骤,因此确保根据验证计划和设计
规范审查覆盖率定义和实现的完整性和正确性是很重要的。
[243] 功能验证中的测试分级是什么概念?
设计的功能验证是通过创建定向测试以及对激励进行不同控制的约束随机激励生成器来完成的。
通过设计验证项目,开发一组测试,该测试套件用于验证设计正确性、发现设计中的bug和收集覆盖率等。
测试分级是一个过程,在这个过程中,单个测试根据不同的标准(如功能覆盖率、发现的bug、仿真运行时、维护的容
易程度等)对质量进行分级。
这个过程有助于从测试套件中识别出有效的测试,从而为设计验证开发出最有效的测试套件。
[244] 什么是基于断言的验证方法?
基于断言的验证(ABV)是一种用于捕获特定设计意图的方法。这些断言用于仿真、形式验证,以验证设计实现是否正
确。ABV方法可以通过断言的优点来补充其他功能验证方法,从而实现有效的验证。
断言的一些好处如下:
1. 断言从源头上检测设计错误,从而提高可观察性和减少调试时间。
2. 相同的断言可以用于仿真和形式分析,甚至可以用于仿真。
3. 在断言库中有很多通用设计的断言,可以很容易地移植到任何验证环境中。
4. 作为属性编写的SystemVerilog断言也可以用于覆盖率(使用覆盖属性),因此有助于基于覆盖率的验证方法。
[245] 2*2的分组交换器的spec如下,你将如何验证设计?如何设计激励和检查器?哪些是你需要验
证的case?
SPEC:有两个输入和输出端口A和B,如上所示。每个端口可以接收大小在64到1518字节之间的可变数据包。每个包将
有一个4字节的源地址(Source Address)和4字节的目标地址(Destination Address),以及跨包计算的数据和一个4字节的
CRC,如下所示。数据包将根据目标地址(Destination Address)被切换到一个输出端口。
对于这类有设计说明的问题,第一步是理解设计说明,并向面试官阐述问题。下一步是确定要验证的场景,并提出验
证计划和策略文档。这应该列出要验证的特性/场景,可以使用什么方法来验证(定向/约束随机、覆盖、断言,等等),如何
检查正确性等等。此外,还应详细说明如何产生激励以及如何进行检查。
另一个方面是考虑所有的设计特性,并确定需要验证的关键case。现在,让我们试着列出如何验证这个简单的路由器
设计
1. 以下是一些需要验证的场景:
1. 根据目的地地址测试数据包从a端口到两个输出端口的正确切换。
2. 测试不同的数据包大小-最小尺寸,最大尺寸和之间的随机尺寸。
3. 测试源地址和目标地址的所有可能值。
4. 测试不同的数据模式。
5. 流包测试(背对背无延迟、周期延迟少、周期延迟大)、大小相同的包流或大小不同的包流。
6. 测试CRC功能。
7. SA/DA或数据甚至CRC的某些位被损坏的测试。
8. 你还能想到什么
2. 现在,为了验证上述场景,我们需要设计一个约束随机数据包生成器,我们还需要一个计分板/检查器来检查数
据包的正确性和正确的交换行为。如果测试是随机的,我们还需要编写一些覆盖率监视器,以确保上面提到的
所有重要场景都得到了覆盖。
3. 如果面试官想对你进行更多的测试,那么他也可以继续问你一些问题,要求你写一个SystemVerilog数据包生成
器代码或一个检查程序或驱动程序等。
[246] 对于一个单端口读写RAM,有哪些场景需要进行测试?
单端口RAM只有一个读和写端口。因此,它只能在任何给定的时间点进行读或写操作。其他需要考虑验证的设计规范
包括RAM大小、地址和数据总线的宽度。基于此,以下是一些需要验证的场景:
1. 正确读写行为
2. 背靠背读取或写入相同的地址和不同的地址
3. 背靠背先读后写同一地址
4. 背靠背先写后读同一地址
5. 验证内存大小的边界——读和写
6. 验证写入数据的所有可能,全0,全1,01交替等。
如果您被进一步要求定义一个验证环境,您可以考虑像上面这样的场景,并定义一个有向或有约束的随机环境是否会
更好地工作,以及如何设计激励生成器和检查器。
[247] 单端口和双端口RAM有什么区别?
单个端口RAM只有一个读和写端口。所以它只能在任何给定的时间点进行读或写操作。一个双端口RAM有2个读/写端
口,因此允许同时读写。
[248] 一个简单的带有如下所示的方框图的ALU支持两个4位操作数、一个4位结果总线和进位溢
出。ALU支持最多8条指令,使用3位操作码或选择行(S2、S1、S0),解码如下。
解释所有需要验证的场景,以确保ALU按照下面的SPEC工作:
以下是需要对这个给定的ALU设计进行验证的场景:
1. 通过驱动两个操作数A和B,以及驱动每个操作的选择行,来验证所有单独的操作是否工作(Add、Sub、
Increment、AND和OR)。
2. 验证如果select行在110-111之间,不发生操作。
3. 对于以上每个指令,选择A和B的最小值和最大值以及组合。假设A和B是4位,最大值可能是4 ' b1111
4. 验证加法和减法的溢出和下溢情况。如果A和B都是4'b1111,则A加法发生溢出,而如果B的值大于A,则减法发
生下溢。
5. 验证自增指令的溢出。如果A= 4'b1111,增量应该产生一个0值。
6. 一旦验证了各个场景,创建随机的操作码序列,验证一个操作的效果不会影响到下面的操作。检查相同的操作
码重复超过一次或不同的操作码以不同的模式重复的序列。
7. 为了创建激励,你可以设计一个随机的操作码和操作数生成器以及一个简单的驱动程序。为了检查结果,可以
编写一个简单的模型或ALU,并与相同的结果进行比较。
[249] 事件驱动和循环驱动的仿真器有何不同?
事件驱动仿真器对每个事件进行设计评估,采用每个事件并通过设计传播变化,直到达到稳定状态。事件是设计元素
的任何输入激励的更改。由于输入和下游设计的信号反馈的到达时间不同,一个设计可能在一个周期内被评估多次。
例如:考虑在时钟上运行的两个触发器之间的逻辑路径。组合逻辑路径可以有多个门和反馈路径。在时钟变化时,当第
一个触发器的输出发生变化时,它将应用于组合逻辑的输入,并进一步应用于组合逻辑中不同阶段输入的任何变化,这会
触发要评估的特定设计。在这个值稳定下来并且不再在那个时钟周期中变化之前,可能需要进行几次评估。大多数业界广
泛使用的模拟器都是事件驱动的,比如:来自Mentor的Questa、来自Synopsys的VCS或来自Cadence的Incisive模拟器。这是
因为事件驱动模拟器提供了准确的模拟环境。
基于循环的模拟器没有时钟周期内的时间概念。它们一次性评估状态元素或端口之间的逻辑。这有助于显著提高仿真
速度,因为每个逻辑元素在每个周期中只计算一次。缺点是它不能真正地检测信号中的任何小故障,而且它只在完全同步
的逻辑设计上表现正常。由于在仿真期间没有考虑设计的时间安排,因此需要再所有的静态时序分析工具对时序进行单独
的验证。基于循环的模拟器在一般设计中不太受欢迎,但在一些开发大型设计(如微处理器)的公司中可以定制和使用。
[250] 什么是事务(transaction?)?基于事务的验证有什么有点?
事务是一组低层信息(如一组信号)的高级抽象。当设计在信号级信息上运行时,testbench需要在信号级与设计接口驱
动程序和监视器,而testbench的所有其他方面都可以抽象为事务级。在基于事务的验证方法中,testbench以分层的方式
进行架构,其中只有较低层的组件在信号级进行操作,而所有其他组件都基于事务进行操作和通信,如下所示。
1. 基于事务的验证的主要优点是在一个项目内或跨不同项目的不同验证环境中重复使用事务性接口开发的组件。
例如:参考上面的图表,只有driver, monitor 和 responder需要信号级接口。一旦这些组件将信号级信息分组到一
个事务中,其他组件(如stimulus generators, slave models 和 scoreboards)都可以对事务进行操作。
2. 由于事务性组件需要由模拟器在事务性边界上进行评估,而不是在每个信号变化上进行评估,因此模拟可能会
快一些。
3. 如果一个设计改变了接口时序,那么只有驱动和监控组件需要改变,其他组件不受影响。
[251] 你使用或者熟悉的仿真调试工具是什么?
这是测试你对不同工具的意识的一个普遍问题。 根据你对各种工具的回答和经验,还可能会询问你在使用这些工具时
可能遇到的难易程度/局限性方面的观点。 没有固定的答案,但是常用的模拟器是Mentor Graphics的Questa,Synopsys的
VCS和Cadence的Incisive模拟器。 Synopsys的Verdi还是与DVE一起调试的常用工具。 正式工具包括来自Cadence的Jasper
和来自Mentor graphics的QuestaFormal。
[253] 什么是总线功能模型?
传统上,总线功能模型(BFM)是用高级编程语言(如C / SystemVerilog)编写的不可综合模型,该模型可对总线接
口的功能进行建模,并可连接到用于仿真设计的设计接口。 在BFM的一侧,将是一个在信号级别上实现总线协议的接口,
另一侧将具有一个接口,以支持发送或接收事务。 随着时间的流逝,这个定义已经演变,在诸如UVM之类的方法中,没有
像BFM这样的实际组件,他的功能是由一系列组件(如驱动程序,监视器和接收器)实现的。
有很多指标用于跟踪针对计划的验证进度。 验证计划根据定向测试或针对详细方案和特殊情况的功能覆盖率监视器,
捕获要验证的功能。 该计划还包括了有关验证环境开发的详细信息,其中包括激励产生和检查方法。 通过跟踪环境开发
(激励发生器,检查器,监视器等),测试开发和功能覆盖率监视器开发的完整性,可以在项目的早期阶段跟踪进度。 一
旦开发了大多数测试和受约束的随机数发生器,通常就可以在服务器场中以回归方式运行测试,然后根据回归通过率,错
误率和功能覆盖率来监视进度。
[255] 如何衡量验证的完整性,或者说何时/如何验证已完成?
当设计的表现与设计规范相匹配而没有任何错误时,可以将功能验证称为完成。为此,我们需要对设计施加激励,以
涵盖所有可能的输入可能,并验证设计是否符合规格要求,并且没有任何错误。但是,随着设计复杂性的不断提高,实际
上不可能定义所有可能的输入激励方案。此外,资源和时间的限制也使得这种完整的理想定义不切实际。
因此,在大多数项目中,验证的完整性与通过一组度量和过程获得的信心有关,该度量和过程使设计缺陷的风险降至
最低。以下是为实现对验证完整性的高度信任而遵循的一些度量和过程:
1. 审查验证计划和设计规范,以确保理解并捕获所有细节。
2. 确保环境开发方面的适当完整性,测试开发,功能覆盖率根据已有的计划开发。
3. 审查测试平台刺激生成器和约束,检查器,断言和覆盖率监视器的实现。
4. 确保以回归模式启用所有测试,并且在数周内始终没有失败,所有覆盖率指标均得到满足和理解。
5. 确保错误率和未解决的错误为零或者能够追溯,对设计没有影响。
6. 重要场景的波形审查。
7. 确保进行形式验证(尽可能)。
8. 将传入的漏洞和漏洞趋势与过去复杂程度类似的成功项目进行比较。
[256] 什么是GLS?为什么GLS很重要?
1. 验证DFT扫描链。
2. 验证异步设计中的关键时序路径(STA未完成)。
3. 验证复位和上电流程。
4. 分析RTL中的X-Sate乐观度。
5. 收集开关活动以进行功率估算。
[257] 什么是功率和性能的权衡?
功率和性能是成功产品的两个重要设计要点。 尽管大多数设计在理想情况下都希望以尽可能低的功耗获得尽可能高的
性能,但实际上并非总是如此。 动态功耗与CV^2f成正比,其中f为频率,V为电压,C为电容。 因此,通常:
1. 降低电压会降低功耗,但会降低性能(随着延迟的增加)
2. 降低频率会降低功耗,但会降低性能(时钟变慢)
因此,为了获得最佳性能和功率目标,设计需要选择正确的电压和频率值。
注意:下一章(验证方法)的“电源和时钟”部分(6.3)中提供了有关电源和时钟的更多问题。
Verification Methodologies
[258] UVM的优点有哪些?
[259] UVM的缺点有哪些?
显然,随着UVM方法在验证行业中的采用越来越广泛,UVM的优点会盖过缺点。
1. 对于任何不熟悉该方法的人,要了解所有细节的学习曲线就非常陡峭。
2. 方法论仍在发展中,并且有很多开销,有时可能会导致仿真缓慢或出现一些错误。
[260] 事务级建模的概念是什么?
上面是一个简单的示例,演示了生产者如何使用一个简单的TLM port与消费者通信。生产者可以创建一个事务并将其
“put”到TLM port,而“put”方法(也称为TLM export)的实现将在读取生产者创建的事务的使用者中进行,从而建立一个通信
通道。
如果生产组件和消费组件都需要独立操作,则使用TLM FIFO进行事务性通信。在这种情况下(如下所示),生产组件生
成事务并将其“放入”FIFO,而消费组件每次从FIFO获取一个事务并对其进行处理。
get()操作将从TLM FIFO中返回一个事务(如果可用),并从FIFO中删除该事务。如果FIFO中没有可用的事务,它将阻塞
并等待,直到FIFO至少有一个事务。peek()操作将从TLM FIFO中返回一个事务(如果可用),而不会实际从FIFO中删除该项
目。它也是一个阻塞调用,如果FIFO没有可用的条目,它将等待。
get()是一个从TLM FIFO获取事务的阻塞调用。因为它是阻塞的,所以如果FIFO中没有事务,任务get()将等待。
try_get()是一个非阻塞调用,即使FIFO中没有可用的事务,它也会立即返回。try_get()的返回值指示是否返回成功。下面是
使用get()和try_get()的两个等效实现
阻塞方法,get
非阻塞方法,try_get
1 class consumer extends uvm_component;
2 uvm_get_port #(simple_trans) get_port;
3 task run;
4 for(int i=0; i<10; i++)
5 begin
6 //Try get is nonblocking. So keep attempting
7 //on every cycle until you get something
8 //when it returns true
9 while(!get_port.try_get(t))
10 begin
11 wait_cycle(1); //Task that waits one clock cycle
12 end //Do something with it
13 end
14 endtask
15 endclass
TLM port/FIFOs用于具有使用put/get方法建立的通信通道的两个组件之间的事务级通信。
analysis port/FIFOs是另一种事务性通信通道,用于组件将事务广播给多个组件。
sequence item是对在两个组件之间传输的信息进行建模的对象(有时它也可以称为事务,transaction)。例如:考虑从
CPU到主内存的内存访问,在主内存中CPU可以执行内存读或内存写,每个事务都有一些信息,比如地址、数据和读/写类
型。sequence可以被认为是一个已定义的sequence item模式,它可以被发送到驱动程序以注入到设计中。sequence item
的模式是由如何按顺序实现body()方法定义的。例如:扩展上面的例子,我们可以定义一个从10个事务中读取增量内存地址
的sequence。在这种情况下,body()方法将被实现来生成10次sequence item,并将它们发送到驱动程序,同时在下一个项
之前递增或随机地址。
[267] uvm_transaction和uvm_sequence_item的区别是什么?
uvm_transaction 派生自uvm_object。uvm_sequence_item不仅是一个事务,还组织了一些信息在一起,增加了一些其他
信息,如:sequence id,和transaction id等等。建议使用uvm_sequence_item实现基于sequence的激励。
[268] component类中的copy,clone和create方法有什么区别?
1. create()方法用于构造对象。
2. copy()方法用于将一个对象复制到另一个对象。
3. clone()方法是一个一步的命令来创建和复制一个现有的对象到一个新的对象句柄。它首先通过调用create()方法
创建一个对象,然后调用copy()方法将现有对象复制到新句柄。
[269] 解释UVM方法学中的agent的概念
UVM agent是一个组件,它将一组其他uvm_components集中在DUT的特定pin级接口上。大多数dut具有多个逻辑接
口,并且使用一个agent来对所有组件进行分组:driver, sequencer, monitor, 和其他组件,这些组件在特定的接口上进行操
作。使用这种层次结构组织组件有助于跨具有相同接口的不同验证环境和项目重用“agent”。
下图是一个典型的agent组织方式。
[270] UVM agent中有多少种不同的组件?
如前所述,agent是基于DUT的逻辑接口进行分组的组件集合。一个agent通常有一个driver和一个sequencer来驱动它
所操作的接口上的被DUT的激励。它还具有一个monitor和一个分析组件(如记分牌或覆盖率收集器)来分析该接口上的活
动。此外,它还可以拥有配置agent及其组件的配置对象。
[271] 在uvm_object类中,get_name和get_full_name方法有什么区别?
get_name返回对象的名称,由new构造函数或set_name()方法中的名称参数提供。get_full_name返回对象的完整层次
结构名称。对于uvm_components,在打印语句中使用时非常有用,因为它显示了组件的完整层次结构。对于没有层次结
构的sequence或config对象,它将输出与get_name相同的值
ACTIVE agent是能够在其操作的pin级接口上生成激励的代理。这意味着,像driver和sequencer这样的组件将被连接,
并且会有一个sequence在其上运行以生成激励。
PASSIVE agent是不生成任何激励,但能监视接口上发生的活动的代理。这意味着,在被动代理中不会创建driver和
sequencer。
在需要生成激励的模块级验证环境中,agent通常被配置为ACTIVE 。当我们从模块级转移到芯片级验证环境时,可以
配置相同的agent,在这种环境中不需要生成激励,但是我们仍然可以在调试或覆盖率方面使用相同的agent来监视信号。
[273] 如何将agent配置为ACTIVE或者PASSIVE?
UVM agent有一个类型为UVM_ACTIVE_PASSIVE_e的变量,该变量定义了agent是ACTIVE的(UVM_ACTIVE),它构造了
sequencer和driver,还是PASSIVE的(UVM_PASSIVE),它既不构造driver,也不构造sequencer。这个参数被称为active,默
认情况下它被设置为UVM_ACTIVE。
当在environment类中创建agent时,可以使用set_config_int()更改这一点。然后,agent的build阶段应该具有如下代
码,以有选择地构造driver和sequencer。
1 function void build_phase(uvm_phase phase);
2 if(m_cfg.active == UVM_ACTIVE)
3 begin //create driver, sequencer
4 end
5 endfunction
[274] 什么是sequencer和driver?为什么需要他们?
driver是将transaction或sequence item转换为一组基于信号接口协议的pin级别切换的组件。
sequencer是一个组件,它将sequence item从sequencer路由到driver,并将响应从driver路由回sequence。还负责多
个sequence(如果存在)之间的仲裁。
在像UVM这样的TLM方法中,这些组件是必需的,激励产生是根据事务进行抽象的,而sequencer和driver是路由它们
并将它们转换为实际的信号行为的组件。
[275] UVM中的monitor和scoreboard有什么区别?
monitor是观察pin级活动并将其观察结果转换为事务或sequence_items的组件。它还通过analysis port将这些事务发送
到分析组件。
scoreboard是一个分析组件,用来检查DUT是否正常工作。UVM scoreboard使用来自agent内部实现的monitor来分析
事务。
[276] 如何启动UVM?
run_test方法(一个静态方法)可以激活UVM测试平台。它通常在顶级测试模块的“initial begin…end”块中调用,它接受
要运行的测试类的参数。然后它触发测试类的构造,build_phase()将在层次结构中执行并进一步构造
Env/Agent/Driver/Sequencer 对象。
[277] 运行一个sequence需要哪些步骤?
运行一个sequence需要三个步骤:
1. 创建一个sequence。使用factory create方法创建一个序列,如下所示:
1 my_sequence_c seq;
2 seq = my_sequence_c:: type_id::create(“my_seq”)
2. 配置或随机化一个sequence。一个sequence可能有几个需要配置或随机化的数据成员。因此,要么配置值,要
么调用seq.randomize()
3. 开始一个sequence。sequence使用sequence.start()方法启动。start方法接受一个参数,该参数是指向sequencer
的句柄。一旦sequence启动,sequence中的body()方法就会被执行,它定义了序列的操作方式。start()方法是阻
塞的,只在sequence执行完成后返回。
[278] 解释sequencer和driver中的握手协议
在sequence端,有两种方法:
1. start_item(<item>):请求sequencer访问sequence item的driver,并在sequencer授予访问权时返回。
2. finish_item(<item>):该方法使driver接收sequencer item,是一个阻塞方法,仅在driver调用item_done()方法后
返回。
在driver侧
1. get_next_item(req):这是一个在driver中的阻塞方法,它阻塞直到一个sequence item在连接到sequencer的端口
上被接收。该方法返回sequence item,sequence item可由driver转换为pin级协议。
2. item_done(req):driver使用这个非阻塞方法向sequencer发出信号,表明它可以解除对sequence中finish_item()方
法的阻塞,无论是在driver接受sequence请求时,还是在它已经执行sequence请求时。
下图说明了sequencer和driver之间的协议握手,这是在sequencer和driver之间传输请求和响应时最常用的握手。
还存在一些其他替代方法:在driver中使用get()方法,它相当于调用get_next_item()和item_done()。
有时,如果从driver到sequence的响应包含比请求类中封装的信息更多的信息,那么还需要一个单独的响应端口。
在这种情况下,sequencer将使用get_response()阻塞方法,当driver使用put()方法在该端口上发送单独的响应时,该方法
将被解除阻塞。如下图所示。
[279] 什么是sequence中的pre_body()和post_body()函数?他们通常会被调用吗?
pre_body()是sequence类中的一个方法,它在调用sequence的body()方法之前被调用。在调用body()方法之后,依次
调用post_body()方法。并不总是调用pre_body()和post_body()方法。uvm_sequence::start()有一个可选参数,如果将其设
置为0,将导致这些方法不被调用。下面是一个sequence中的start()方法的形式参数。
1 virtual task start (
2 uvm_sequencer_base sequencer, // Pointer to sequencer
3 uvm_sequence_base parent_sequencer = null, // parent sequencer
4 integer this_priority = 100, // Priority on the sequencer
5 bit call_pre_post = 1); // pre_body and post_body called
[280] sequence中的start方法使阻塞还是非阻塞方法?
start方法是一个阻塞方法,阻塞进程直到sequence的body方法执行完毕。
[281] sequencer有哪些仲裁机制?
多个sequence可以与同一个接口的driver并发交互。sequencer支持多种仲裁机制,以确保在任何时间点只有一个
sequence可以访问driver。哪个sequence可以发送sequence_item取决于用户选择的仲裁机制。在UVM中实现了五种内置
的仲裁机制。还有一个附加的回调函数可以实现用户定义的算法。sequencer具有一种称为set_arbitration()的方法,可以调
用该方法来选择sequencer应使用哪种算法进行仲裁。可以选择的六种算法如下:
1. SEQ_ARB_FIFO(默认值)。如果指定了此仲裁模式,那么sequencer将使用一个FIFO选择sequence。例如:如
果seq1,seq2和seq3在sequencer上运行,它将首先从seq1中选择一个item,然后从seq2中选择一个item,然
后从seq3中选择一个item(如果可用),然后继续。
2. SEQ_ARB_WEIGHTED:如果选择此仲裁模式,则始终首先选择优先级最高的sequence,直到没有可用为止,然
后再选择下一个优先级sequence,依此类推。如果两个sequence具有相同的优先级,则以随机顺序从中选择它
们。
3. SEQ_ARB_RANDOM:如果选择此仲裁模式,忽略所有优先级以随机顺序选择sequence。
4. SEQ_ARB_STRICT_FIFO:与SEQ_ARB_WEIGHTED相似,不同之处在于,如果两个sequence具有相同的优先级,
则来自这些sequence的项顺序是按FIFO顺序而不是随机顺序选择的。
5. SEQ_ARB_STRICT_RANDOM:与SEQ_ARB_RANDOM相似,只是不忽略优先级。首先从优先级最高的序列中随
机选择sequence,然后依次选择下一个和最高顺序。
6. SEQ_ARB_USER:此算法允许用户定义用于sequence之间仲裁的自定义算法。这是通过扩展uvm_sequencer类并
覆盖user_priority_arbitration()方法来完成的。
[282] 在sequencer上启动sequence时,如何指定其优先级?
[283] sequence如何才能独占sequencer的访问权限?
当在sequencer上运行多个sequence时,sequencer进行仲裁并授予sequence的访问权限。 有时,一个sequence可能
希望独占sequencer的访问权限,直到将其所有sequence_item送到driver为止(例如:如果您希望在不中断的情况下激发
确定性模式)。 有两种机制允许sequence获得对sequencer的独占访问权。
1. 使用lock()和unlock():sequence可以调用在其上运行的sequencer的lock方法。当sequence通过sequencer仲裁
机制获得下一个访问权限时,将授予该sequence对driver的独占访问权限。如果还有其他标记为更高优先级的
sequence,则此序列需要等待,直到获得授权。权限锁定后,其他sequence将无法访问driver,直到该序列在
sequencer上调用unlock()后,权限将被释放。 lock方法是一个阻塞方法,直到授予锁后才返回。
2. 使用grab()和ungrab():grab方法类似于lock方法,可以在需要独占访问时由sequence调用。grab和lock之间的
区别在于,调用grab()时,它将立即生效,并且该sequence将100%获得下一个sequencer的授权机会,从而高于
所有当前的sequence优先级。阻止sequence通过grab独占sequencer的唯一方法是sequencer上已经存在的
lock()或grab()。
[284] sequencer上的lock和grab有什么区别?
在sequencer上运行的sequence使用sequencer的grab()和lock()方法来获得对该序列器的独占访问权,直到调用了相应
的unlock()或ungrab()。 grab和lock之间的区别在于,当调用sequencer上的某个grab()时,它将立即生效,并且该
sequence将获得下一个sequencer的授权,从而高于所有当前的sequence优先级。 但是,对lock()的调用将需要等待,直到
sequence根据设置的优先级和仲裁机制获得其下一个授权为止。 在用法方面,可以使用lock来对优先级中断进行建模,并
使用grab来对不可屏蔽的中断进行建模,在别的建模场景中也很有用。
[285] 流水线和非流水线sequencer-driver模型有什么区别?
根据需要如何通过interface发送激励,在UVM driver类中可以实现两种模式。
1. 非流水线模型:如果driver一次仅对一个事务进行建模,则称为非流水线模型。 在这种情况下,sequence可以
将一个事务发送给driver,并且driver可能需要几个周期(基于接口协议)才能完成驱动该事务。 只有在那之
后,驱动程序才会接受sequencer的新事务.
2. 流水线模型:如果driver一次建模多个活动事务,则称为流水线模型。 在这种情况下,sequence可以继续向
driver发送新事务,而无需等待driver完成之前的事务。 在这种情况下,对于从该sequence发送的每个事务,
driver都会派生一个单独的进程来基于该事务驱动接口信号,不会等到它完成后再接受新事务。 如果我们要在接
口上回送请求而不等待设计的响应,应该采用这种建模。
[286] 如何确保在sequencer-driver上运行多个sequnce时,响应会从driver发送回正确的
sequence?
如果从driver返回了几个序列之一的响应,则sequencer将sequence中的sequenceID字段用于将响应路由回正确的
sequence。 driver中的响应处理代码应调用set_id_info(),以确保任何响应项都具有与其原始请求相同的sequenceID。 下
面是driver中的一个示例代码,该代码获取sequencer_item的id并发送回响应(请注意,这是用于说明的参考伪代码,并且
假定某些功能在其他地方进行了编码)
启动sequence时,它始终与启动sequencer相关联。 m_sequencer句柄指向了当前sequence挂载的sequencer。 使用
此句柄,序列可以访问UVM组件层次结构中的任何信息和其他资源句柄。
[288] m_sequencer和p_sequencer有什么区别?
UVM sequence是寿命有限的对象,与sequencer,driver或monitor不同,后者是UVM的组件,并且在整个仿真时间内
都存在。 因此,如果需要从测试平台层次结构(组件层次结构)访问任何成员或句柄,sequence需要sequencer的句柄。
m_sequencer是uvm_sequencer_base类型的句柄,默认情况下在uvm_sequence中可用。 但是,要访问在其上运行
sequence的真实sequencer,我们需要将m_sequencer转换为真实sequencer,通常称为p_sequencer(可以自定义为任何
名字,不仅仅是p_sequencer)。 下面是一个简单的示例,其中sequence要访问clock monitor组件的句柄,该组件可在
sequencer中用作句柄。
[289] 生成sequence时,早期随机化和后期随机化有什么区别?
在早期随机化中,首先使用randomize()对sequence进行随机化,然后使用start_item()来请求对sequencer的访问,这
是一个阻塞调用,根据sequencer的繁忙程度可能会花费一些时间。 下面的示例显示一个对象(req)首先被随机化,然后
sequence等待仲裁。
1 task body()
2 assert(req.randomize());
3 start_item(req); //Can consume time based on sequencer arbitration
4 finish_item(req);
5 endtask
在后期随机化中,sequence首先调用start_item(),等待直到sequencer批准仲裁,然后在将事务发送给
sequencer/driver之前,调用randomize。 这样做的好处是,可以将item及时地随机化,并且可以在将item发送给driver之
前使用来自设计或其他组件的任何反馈。 以下代码中使用的就是后期随机化(req)
1 task body()
2 start_item(req); //Can consume time based on sequencer arbitration
3 assert(req.randomize());
4 finish_item(req);
5 endtask
[290] 什么是subsequence?
subsequence是从另一个sequence开始的sequence。 从sequence的body()中,如果调用了另一个sequence的start(),
则通常将其称为subsequence。
item_done()方法是driver中的一种非阻塞方法,用于在get_next_item()或try_next_item()成功之后与sequencer完成握
手。 如果不需要发回响应,则不带参数调用item_done(),它将完成握手,而无需在sequencer响应FIFO中放置任何内容。
如果希望将响应发送回去,则将item_done()与指向响应sequence_item的句柄作为参数一起传递。 该响应句柄将放置在
sequencer响应FIFO中,sequence通过这种方式进行响应。
[296] 下面哪些方法是阻塞的,哪些是非阻塞的?
1. get()
2. get_next_item()
3. item_done()
4. put()
5. try_next_item()
6. peek()
1 1)
2 function get_drive_req();
3 forever begin
4 req = get();
5 req = get();
6 end
7 endfunction
8 2)
9 function get_drive_req();
10 forever begin
11 req = get_next_item();
12 req = get_next_item();
13 item_done();
14 end
15 endfunction
16 3)
17 function get_drive_req();
18 forever begin
19 req = peek();
20 req = peek();
21 item_done();
22 req = get();
23 end
24 endfunction
2是错的,因为不能在调用item_done之前两次调用get_next_item,它无法完成与sequencer的握手。
[298] 如何停止sequencer上的所有sequence?
sequencer具有stop_sequences()方法,可用于停止所有sequence。 但是,此方法不检查driver当前是否正在处理任何
sequence_items。 因此,如果driver调用item_done()或put(),则可能会出现致命错误,因为sequence指针可能无效。 因
此,用户需要注意,一旦调用stop_sequence(),就禁用了sequencer线程(如果在fork中启动)。
[299] 用户调用sequence.print()方法时,将调用sequence中的哪个方法?
convert2string():建议实现此函数,该函数返回对象的字符串表示形式(其数据成员的值)。 这对于将调试信息打印
到模拟器脚本或日志文件很有用。
1 task body();
2 seq_item_c req;
3 start_item(req);
4 #10 ns;
5 assert(req.randomize());
6 finish_item(req);
7 endtask
应该避免在start_item和finish_item之间添加延迟。 start_item返回后,该sequence将赢得仲裁并可以访问driver-
sequencer。 从那时起直到finish_item的任何延迟都将阻塞driver-sequencer,并且使得任何其他sequence都不能访问
driver-sequencer。 如果在一个接口上运行多个sequence,并且延迟很大,设计接口上有很多空闲时,这会造成很大的麻
烦。
生成上述类型的10个事务并发送给driver的sequence:
driver代码,从sequence中接收上述事务并根据SRAM协议进行驱动。
[303] 什么是工厂?
UVM中的“工厂”是一个特殊的查找表,其中记录了所有UVM组件和事务。 在UVM中创建组件和事务对象的推荐方法
是使用工厂方法create()。 使用工厂创建对象可以很方便地实现类型覆盖,而不必更改验证环境的结构或修改验证环境的
代码。
[304] 使用new()和create()创建对象有什么区别?
UVM中用于创建组件或事务对象的推荐方法是使用内置方法::type_id::create(),而不是直接调用构造函数new()。
create方法在内部调用工厂,查找所请求的类型,然后调用构造函数new()来创建对象。 这样可以轻松地重写类型,可
以指定类的类型(基类,一个或派生类),并且所有其他测试平台组件将能够创建该类类型的对象而无需任何代码更改。
new()构造函数将仅创建给定类型的对象,因此使用new()将不允许在运行时更改类类型。 因此,使用new()意味着测试
平台代码将需要根据要使用的不同类型进行更改。
[305] 如何在工厂中注册uvm_component类和uvm_sequence类?
使用uvm_object_utils()宏注册uvm_sequence类,uvm_component_utils()宏注册uvm_component类,下面是示例代码
[306] 为什么要将类注册到工厂?
工厂是UVM中使用的一种特殊查找表,用于创建组件或事务类型的对象。 使用工厂创建对象的好处是,测试平台构建
可以在运行时决定创建哪种类型的对象。 因此,一个类可以用另一个派生类替换,而无需任何实际代码更改。 为确保此功
能,建议所有类都在工厂注册。 如果不注册到工厂,则将无法使用工厂方法::type_id::create()构造对象。
[307] 工厂覆盖(override)的意思是?
UVM工厂允许在构造时将一个类替换为另一个派生类。 通过将一个类替换为另一个类而不需要编辑或重新编译测试平
台代码,这对于控制测试平台的行为很有用。
类型覆盖意味着每次在测试平台层次结构中创建组件类类型时,都会在其位置创建替代类型。 这适用于该组件类型的
所有实例。
另一方面,实例覆盖意味着仅覆盖组件类的特定实例。 组件的特定实例由该组件在UVM组件层次结构中的位置进行索
引。
由于只有UVM组件类可以在UVM测试平台中具有层次结构,因此实例覆盖只能作用于组件类,而sequence(或者说
object)只能覆盖类型。
[309] 实例覆盖和类型覆盖都可以作用于UVM_component和transaction吗?
不,只有UVM_component类是UVM测试平台层次结构的一部分,从而可以使用实例覆盖。 sequence_item或
sequence不是UVM测试平台层次结构的一部分,因此只能使用类型覆盖来覆盖,类型覆盖将覆盖该类型的所有对象。
[310] uvm_obejction是什么?在哪里使用它们?
uvm_objection类提供了一种在多个组件和sequence之间共享计数器的方法。 每个组件/sequence可以异步
地"raise"和"drop" objections,这会增加或减少计数器值。 当计数器达到零(从非零值开始)时,将发生"all dropped"情
况。
下面是一个示例,说明如何在sequencer(my_sequencer)上启动sequence(my_test_sequence)并在sequence执行后drop
objections
1 task run_phase( uvm_phase phase);
2 phase.raise_objection( this );
3 my_test_sequence.start(my_sequencer);
4 phase.drop_objection( this );
5 endtask
[311] 如何在UVM中实现仿真超时机制?
如果由于超出最大时间的某些错误而导致测试无法进行,那么仿真超时机制有助于停止仿真。 在UVM中,
set_global_timeout(timeout)是一个便捷函数,用于将uvm_top.phase_timeout变量设置为超时值。 如果run()阶段在该这个
时间内之前没有结束,则仿真将停止并报告错误。
在顶层模块中调用此函数,该模块会按以下方式启动测试
1 module test;
2 initial begin
3 set_global_timeout(1000ns);
4 end
5
6 initial begin
7 run_test();
8 end
9 endmodule
[312] uvm中的phase机制是什么意思?
与基于module的测试平台(所有module静态地存在于层次结构中)不同,基于类的测试平台需要管理不同对象的创
建以及这些对象中各种task和function的执行。 phase是基于类的测试平台中重要的概念,它具有一致的测试平台执行流
程。 从概念上讲,测试执行可以分为以下阶段-配置,创建测试平台组件,运行时激励和测试结束。 UVM为每一个阶段中
定义了标准phase。
[313] uvm_component有哪些phase?UVM的run_phase有哪些子phase?
UVM使用标准phase来排序仿真过程中发生的主要步骤。 有三组阶段,按以下顺序执行。
start_of_simulation()
run_phase(),run_phase()进一步分为以下12个子phase:
pre_reset
reset
post_reset
pre_configure
configure
post_configure
pre_main
main
post_main
pre_shutdown
shutdown
post_shutdown
Clean up phase -此phase 在测试结束后执行,用于收集并报告测试的结果和统计信息。 包括以下子phase :
extract()
check()
report()
final()
[314] 为什么build_phase是自顶向下执行的?
在UVM中,所有组件(例如test,Env,Agent,Driver,Sequencer)都基于uvm_component类,并且组件始终具有
层次结构。 build_phase()方法是uvm_component类的一部分,用于从父组件构造所有子组件。 因此,要构建测试平台层
次结构,始终需要先拥有一个父对象,然后才能构造其子对象,并可以使用build_phase进一步构造其子对象。 因此,
build_phase()总是自顶向下执行。
例如:uvm_test类调用build_phase,构造该test的所有uvm_env组件,而每个uvm_env类的build_phase()应该构造属于
该uvm_env的所有uvm_agent组件,然后继续。 对于所有其他phase,调用顺序实际上并不重要。 所有组件的run_phase()
是并行运行的。
[315] uvm_component中的phase_ready_to_end()的作用是?
phase_ready_to_end(uvm_phase phase)是组件类的回调方法,当相应phase的所有objection均被放下并且该phase
将要结束时,会调用该方法。 组件类可以使用此回调方法来定义phase即将结束时需要执行的任何功能。
例如,如果某个组件希望将phase结束延迟到某个条件,甚至在所有objections均被放下之后,也可以使用此回调方法
来完成。
再比如,如果一个激励或应答sequence正在运行,在主sequence结束之前,则可以使用main_phase()中的
phase_ready_to_end()回调方法来停止那些激励或应答sequence。
[316] 什么是uvm_config_db?它的作用是?
uvm_config_db机制支持在不同的测试平台组件之间共享配置和参数。 用名为uvm_config_db的配置数据库启用该功
能。 任何测试台组件都可以使用变量,参数,对象句柄等填充配置数据库。
其他测试平台组件可以从配置数据库访问这些变量,参数,对象句柄,而无需真正知道其在层次结构中的位置。
例如,顶层测试平台模块可以通过uvm_config_db储存虚接口句柄。 然后,任何uvm_driver或uvm_monitor组件都可
以查询uvm_config_db获取此虚接口的句柄,并将其用于通过接口实际访问信号。
[317] 如何使用uvm_config_db的get()和set()方法?
get()和set()是用于从uvm_config_db存储或检索信息的主要方法。任何验证组件都可以使用set()方法为config_db存储
一些配置信息,还可以控制哪些其他组件对相同信息具有可见性。可以将其设置为具有全局可见性,或者仅对一个或多个
特定测试平台组件可见。 get()函数从数据库中检查与参数匹配的共享配置。
get()和set()方法的语法如下:
[319] 在UVM中,将虚接口分配给不同组件的最佳方法是什么?
实例化DUT和接口的顶级testbench模块在uvm_config_db中例化虚接口。 然后,测试类或UVM组件层次结构中的任何
其他组件可以使用get()方法查询uvm_config_db,获得此虚接口的句柄并将其用于访问信号。
下面栈是了如何进行此操作。 为APB总线master实例化了DUT和物理接口,然后,将虚接口句柄设置到
uvm_config_db。
1 module test;
2 logic pclk;
3 logic [31:0] paddr;
4
5 //Instantiate an APB bus master DUT
6 apb_master apb_master(.pclk(pclk),*);
7
8 //Instantiate a physical interface for APB interface
9 apb_if apb_if(.pclk(pclk), *);
10
11 initial begin //Pass this physical interface to test class top //which will further pass it
down to env->agent->drv/sqr/mon
12 uvm_config_db#(virtual apb_if)::set(null, "uvm_test_top", "vif",apb_if);
13 end
14
15 endmodule
下面是APB Env类,该类使用uvm_config_db中的get()方法检索在顶层测试模块中设置的虚接口。
[320] 在UVM中,如何结束仿真?
UVM具有phase机制,由一组构建阶段,运行阶段和检查阶段组成。 在run()阶段进行实际的测试仿真,并且在此
phase中,每个组件都可以在开始时提出raise_objection和drop_objection。 一旦所有组件都drop_objection,则run_phase
完成,然后所有组件的check_phase执行,然后测试结束。
这是正常仿真结束的方式,但是如果某些组件由于设计或测试平台中的错误而挂起,则仿真超时也可以终止
run_phase。 当run_phase启动时,并行超时计时器也会启动。 如果在run_phase完成之前超时计时器达到指定的超时限
制,则将发出一条错误消息,然后将执行run_phase之后的所有phase,最后测试结束。
uvm_callback类是用于实现回调的基类,这些回调通常用于在不更改组件类的情况下修改或增强组件的行为。 通常,
组件开发人员会定义一个专用于应用程序的回调类,该类扩展并定义一个或多个虚方法,称为回调接口。 这些方法用于实
现组件类行为的重写。
一种常见用法是在driver将错误发送到DUT之前将错误注入到生成的数据包中。 以下伪代码展示了如何实现。
1. 定义一个具有错误位的数据包类
2. 定义一个从sequence中提取数据包,输入到DUT的driver类
3. 定义从基类uvm_callback派生的driver回调类,并添加一个虚方法,该方法可用于注入错误或翻转数据包中的某
个位。
4. 用`uvm_register_cb()宏注册回调类
5. 在接收和发送数据包到DUT的driver的run_phase()方法中,基于概率执行回调以导致数据包损坏
1 class Packet_c;
2 byte[4] src_addr, dst_addr;
3 byte[] data;
4 byte[4] crc;
5 endclass
6 //User defined callback class extended from base class
7 class PktDriver_Cb extends uvm_callback;
8 function new (string name = "PktDriver_Cb");
9 super.new(name);
10 endfunction
11
12 virtual task corrupt_packet (Packet_c pkt);
13 //Implement how to corrupt packet
14 //example - flip one bit of byte 0 in CRC
15 pkt.crc[0][0] = ~pkt.crc[0][0]
16 endtask
17 endclass : PktDriver_Cb
18
19 //Main Driver Class
20 class PktDriver extends uvm_component;
21 `uvm_component_utils(PktDriver)
22
23 //Register callback class with driver
24 `uvm_register_cb(PktDriver,PktDriver_Cb)
25
26 function new (string name, uvm_component parent=null);
27 super.new(name,parent);
28 endfunction
29
30 virtual task run();
31 forever begin
32 seq_item_port.get_next_item(pkt);
33 `uvm_do_callbacks(PktDriver,PktDriver_Cb, corrupt_packet(pkt))
34 //other code to derive to DUT etc
35 end
36 endtask
37 endclass
[323] 什么是uvm_root类?
[324] uvm_test的父级类是什么?
uvm_test类是用户可以实现的顶级类,并且没有显式父类。 但是,UVM有一个称为uvm_top的特殊组件,它被指定为
测试类的父级。
形式验证
[325] 什么是形式验证?
形式验证是使用数学建模来验证设计实现是否符合spec的方法。 形式验证使用数学推理和算法来证明设计符合spec。
在形式验证中,该工具隐式地涵盖了所有情况(输入和状态),而无需开发任何激励生成器或预期输出。 该工具需要以
property或更高级别的模型形式对spec进行形式描述,以详尽地涵盖所有输入组合,证明功能的正确性。 SystemVerilog的
property也可用于形式化描述spec。
[326] 形式验证是静态仿真还是动态仿真?
是静态仿真
[327] 形式验证有哪些方法?
1. 模型检查
2. 形式等效
[328] 解释模型检查
在“模型检查”方法中,将要验证的模型描述为从设计规范中提取的一组property。 因此要详尽搜索设计的状态空间,
检查所有property是否在所有状态下均成立。 如果在任何状态下违反了property,则会引发错误。 下图是一个示意图:
[329] 什么是形式等效
形式等效用于验证两个具有相同或不同抽象的模型在功能上是否一致的方法。 此方法无法确定模型在功能上是否正
确,但是可以确定两个模型在功能上是否相同。 常用于比较RTL设计和综合网表的功能。 它也可以用来检查两个RTL模型
或两个门级模型的一致性。 下图是一个示意图:
[330] 列出一些可以使用形式等效的场景
1. RTL设计与综合网表
2. RTL设计与参考模型
3. 两个RTL设计
4. 两个门级模型
5. 两个参考模型
[331] 与动态仿真相比,形式验证有什么优势?
1. 动态仿真不可能进完全的验证,因为输入激励是使用生成器或testcase来实现的。 但是,形式验证会覆盖所有的
状态空间,因为该工具会自动生成激励来验证所有的spec。
2. 由于工具会自动生成完备的激励,因此无需自行生成激励。 用户可以专注于使用属性来映射形式spec。
3. 无需生成预期的输出序列,并且在数学上保证了设计的正确性。
[332] 形式验证有什么局限性?
1. 可拓展性是形式验证的最大限制之一。 形式验证仅限于较小的设计,因为即使添加一个触发器也会将设计状态
空间增加2倍(这意味着每个触发器的输入场景都会加倍)。
2. 它可以确保设计相对于spec的正确性。 它不能保证设计是否正常工作(例如spec本身是否有错误)。
3. 对于模型检查,spec需要使用property来描述、编码
[333] 如果设计中的某个模块经过形式验证可以正常工作,我们是否还需要收集该模块的覆盖
率?
不,我们不需要通过了形式验证的模块的覆盖率。 因为形式验证在数学上保证了可以在所有可能的输入条件下都符合
spec。
功耗和时钟
[334] CMOS电路功耗由哪些部分组成?
1. 动态功耗:这部分是由晶体管电容充放电产生的
2. 静态功耗:这部分是由开关的漏电流产生的
[335] 什么是动态功耗?它与哪些参数有关?
其中,
活 性 因 子 进 行 状 态 转 换 的 开 关 比 例
内 部 电 容
负 载 电 容
电 源 电 压
工 作 频 率
[336] 什么是静态功耗?它与哪些参数有关?
[337] 什么是多电压域?为什么要使用它?
[338] 什么是“动态电压频率调节”(DVFS)?何时使用?
动态电压频率调节是一种低功耗设计技术,通过动态调整频率降低功耗。 在DVFS中,工作频率或电压以某种方式进
行调节,使得设计在正常运行的同时使用最小的频率或电压。 这个过程发生在设计的运行过程中,因此称为“动态”。 传统
的低功耗方法中,设计以工作频率运行,然后在空闲时关闭电源来进行定期调度。DVFS技术利用了CMOS芯片的特性:
CMOS芯片的能量消耗正比于电压的平方和时钟频率。DVFS技术是以延长任务执行时间为代价来达到减少系统能量消耗的
目的,体现了功耗与性能之间的权衡。可以通过减少时钟频率来降低通用处理器功耗的。然而,仅仅降低时钟频率并不节
约能量,因为性能的降低会带来任务执行时间的增加。调节电压需要以相同的比例调节频率以满足信号传播延迟要求。然
而不管是电压调节还是频率调节,都会造成系统性能的损失,并增加系统的响应延迟。
为了尽量减少可感知的系统性能负面影响同时又能最大程度地降低系统能耗,策略必须估计未来的工作负载并选择最
合适的频率。准确地预测未来的工作负载对广泛使用的策略是至关重要的。预测错误可能会导致设置的频率太高降低节省
能耗,或设置频率过低造成系统响应延迟过高。所以,要想降低功耗,需要选择合适的供电电压和时钟频率。
所以安全的调节机制是:
当需要提升功率时,应先提升供电电压,然后提升时钟频率。
当需要降低功率时,应先降低时钟频率,再降低供电电压。
制定调整策略前,需要先找出系统中的耗电大的部件,如CPU、GPU、DSP等硬件算法加速模块(结合逻辑规模);
然后统计出这些模块的负载情况,基本的策略当然是工作负载增加则升频升压,工作负载降低则降频降压。工作负载的粗
略模型是在一个时间窗口内,统计模块工作的时间长度,设定不同阈值,高阈值对应高电压高频率,低阈值对应低电压低
频率。每次统计值穿过阈值边界,触发DVFS转换。
[339] 什么是UPF?
其主要是由Synopsys推出的专门用于描述电路电源功耗意图的一种语言标准,它是Tcl语言的扩展,并且现在已经成为
IEEE-1801标准且被三大EDA厂商(Synopsys、Cadence、Mentor)支持。 传统的数字芯片设计均是采用Verilog或者VHDL
语言对电路进行描述,但是这种方式描述出的电路并没有包含任何的芯片的供电网络信息,这会导致后续的流程如功耗验
证和后端实现很难处理或者极易出错。UPF标准正好可以很好的解决这个问题,因为UPF标准本身包含了大量的用于描述电
源网络的Tcl命令,直接使用这些命令可以很方便的创建电源域和功耗控制的特殊单元等,用UPF编写的统一功耗格式文件
不仅可以在RTL级,同时还可以被后端工具使用,这在一定意义上保证了整个芯片设计过程中功耗流程的一致性,在后端工
具进行处理之后也会生成相应的UPF文件,此时前端工具可以使用该UPF文件对网表进行Power仿真分析
[341] 电源域的意思是?
电源域是共享一个主要电源的设计元素的集合,根据通用电源策略(例如工作电压,电源网络,上电/断电条件等)进
行分组。
[342] 什么是亚稳态?亚稳态是如何产生的?它有什么影响?
亚稳态可能会导致:
1. 不可预测的系统行为。
2. 不同的扇出可能得到不同的信号值,从而导致设计进入未知状态。
3. 如果不稳定的数据(“ 0”或“ 1”)传播到设计中的不同部分,则可能导致高电流并最终芯片烧坏。
[343] 如何避免亚稳态?
[344] 同步器的构成是怎样的?
以下是一个同步器电路的例子。 这是一个两个触发器同步器,第一个触发器等待一个时钟周期,使输入端的亚稳态稳
定下来/逐渐消失,然后第二个触发器在输出端提供稳定的信号。
值得注意的是,在信号输入第二级时,第一触发器的输出仍然可能不稳定(并导致第二级输出信号变为亚稳态)。 在
这种情况下,我们可以使用三个触发器同步器电路。 但是,通常两个触发器同步器电路足以消除亚稳态,使用三个触发器
的情况比较少。
[345] 什么是时钟门控?
时钟门控是一种低功耗技术,通过关闭了设计中某些部分的时钟达到降低功耗的目的。 它是一种被用于控制时钟网络
耗散功率的技术,通过避免不必要的开关活动,减少设计消耗的动态功耗。
[346] 什么是电源门控,为什么要使用它?
电源门控是一种低功耗,可以关闭设计中不工作的部分。 当不工作时,电源门控可关闭电源,减少漏电功耗,从而降
低了功耗。 时钟门控有助于降低动态功耗,而时钟门控有助于降低静态功耗。
[347] 多时钟域设计会遇到哪些问题?
亚稳性导致的同步失败:时钟在不同的时钟域中以不同的频率运行,并且在一个时钟域中生成的信号在非常接
近第二个时钟域中时钟有效沿的位置采样时,输出可能进入亚稳态状态,在设计中出现同步失败。
数据不一致:如果设计不合理,目标时钟域可能会接收错误的数据。 例如:如果多个信号从一个时钟域传输到
另一个时钟域,所有这些信号同时变化,并且源和目标时钟沿彼此接近,那么这些信号中的某些可能会在一个
时钟中捕获,而有一些信号可能在另一个时钟周期中被捕获,从而导致数据不一致。 注意:这只是数据不一致
的一个例子, 数据不一致的产生还有很多原因。
数据丢失:如果设计不合理,则数据可能会在CDC边界丢失。 例如:如果信号从较快的时钟域送到较慢的时钟
域,并且该信号的宽度仅等于一个时钟周期(较快的时钟),则可能会丢失以下信息: 信号在较慢的时钟域中
的采样沿前就变化了。 注意:这只是数据丢失的一个例子, 数据丢失的产生还有很多原因。
[348] 如何处理跨时钟域信号?
跨时钟域处理有很多方法,具体取决于我们需要在不同的时钟域之间传递1位还是多位。 假设以下情况:多个信号从
一个时钟域传输到另一时钟域,所有信号同时变化,并且源和目标活动时钟沿彼此接近。 在这种情况下,这些信号中的某
些信号可能在目标时钟域的一个时钟周期中被捕获,而另一些信号在目标时钟域中的另一个时钟周期中被捕获,从而导致
数据不一致性。 可以使用下面方法在两个时钟域之间同步信号。
对于单bit跨时钟域:
两级或者三级同步器
使用握手信号进行同步
对于多bit跨时钟域:
使用多周期路径的方法进行同步,将未经同步的信号和同步控制信号一起发射到目标时钟域
对信号进行格雷码编码,由于相邻的格雷码计数只会变化1bit,亚稳态的发生会大大减小
使用异步FIFO
将多比特信号合并成1bit,然后再通过多级同步器进行传输
[349] 举例信号从快时钟域到慢时钟域可能发生的问题
信号只持续一个时钟周期(快时钟域),可能导致慢时钟域漏采样。
[350] 异步复位的优缺点有哪些?
优点:
异步复位具有最高优先级。
保证数据路径干净。
在有或没有时钟信号的情况下都能生效。
缺点:
如果在时钟的有效沿(或附近)撤销异步复位,则触发器的输出可能进入亚稳态。
它对毛刺很敏感,可能导致虚假的复位。
[351] 同步复位的优缺点有哪些?
优点:
整个电路都是同步的
更加容易仿真
综合以后可能会更加节省面积
缺点
需要脉冲扩展,让复位脉冲足够长,保证能够正确地被采样
会添加额外的组合逻辑
同步复位需要时钟才能复位。 如果电路具有内部三态总线,则需要单独的异步复位,以防止内部三态总线上的
总线冲突。
[354] 什么是PLL?
鉴相器
环路滤波器
压控振荡器
[355] 画出PLL的框图
[356] 代码覆盖率与功能覆盖率的区别是什么?
代码覆盖率:代码覆盖率是一种度量,用于度量给定测试case对设计代码(HDL模型)进行测试的程度。 启用
后,模仿真器会自动提取代码覆盖率。
功能覆盖率:功能覆盖率是用户定义的度量标准,用于度量已执行了多少spec(如测试计划中的功能所列举
的)。 它可以用来衡量对于spec的测试充分性。 它是用户定义的,不会自动生成。 它也不依赖于设计代码,因
为它是根据spec实现的
[357] 代码覆盖率有哪几种?
Branch/Decision coverage:分支覆盖率评估HDL代码中的条件,例如if-else,case语句和三元运算符(?:)
语句,并检测是否同时包含真假情况。 在上面的示例中,只有一个分支(if A> B),分支覆盖率会检查是否真
假两个分支都被触发了。
Conditional Coverage and Expression coverage:条件覆盖率会检查HDL中的所有布尔表达式,并计算该表达式
为真或假的次数。 表达式覆盖率检查语句的右侧,统计所有可能组成的真值表的覆盖程度。 以下是包含3个布
尔变量的表达式,它们决定了Result变量为true或false
针对A,B和C的所有可能情况,如下创建真值表。 条件覆盖率可以衡量此真值表的所有行是否都被覆盖。
Toggle coverage:翻转覆盖率可衡量仿真运行期间设计中信号和端口的翻转率。 这有助于识别哪些信号一直没
有翻转。
FSM coverage:状态机覆盖衡量仿真期间是否所有状态机的状态都被覆盖到。
[358] 如果功能覆盖率接近100%而代码覆盖率不足60%,说明了什么?
仿真器会基于testcase提取代码覆盖率,而功能覆盖率则是用户定义的指标。低代码覆盖率表明并非设计代码的所有部
分都经过了测试。高功能覆盖率表明,用户从测试计划中捕获的所有功能都得到了测试。如果覆盖率指标显示低代码覆盖
率和高功能覆盖率,原因可能是:
1. 可能有许多设计代码未按照spec用于实现的功能。 (无效的代码)
2. 用户定义的功能覆盖率量中存在一些错误。测试计划未捕获所有设计功能/场景/边界,或者缺少功能覆盖率监视
器。代码覆盖率中未覆盖的设计代码可能会映射到这些功能上。
3. 在实现功能覆盖率监视器时可能存在潜在的错误,导致它们收集了错误的覆盖率。因此,在验证项目中,对用
户定义的功能覆盖率指标及其实现进行适当的检查很重要。
[359] 如果代码覆盖率接近100%而功能覆盖率不足60%,说明了什么?
1. 没有按照spec在设计中实现了所有功能。 因此,设计代码无法实现所有功能
2. 在功能覆盖率监视器中可能存在潜在的错误,即使设计代码实现了功能,也无法覆盖它们。
3. 功能正确,但是由于发送的激励不正确,对应的功能覆盖率无法收集。
[360] 覆盖组可以在类内部定义和使用吗??
是的,可以在类内部定义覆盖组。 这对于基于测试平台结构(例如事务,序列,检查器,监视器等)实现功能覆盖率
非常有用。
[361] 什么是covergroups和bins?
覆盖点(coverpoint)是用于指定需要收集覆盖率的目标。 Covergroup可以具有多个覆盖点以覆盖不同的表达式或变
量。 每个覆盖点还包括一组bin,这些bin是该覆盖点不同采样值。 bin可以由用户定义,也可以缺省自动创建。 在下面的
示例中,有两个变量a和b,covergroup有两个coverpoint,他们会检查a和b的值。 Coverpoint cp_a是用户定义的,bins
values_a检测a是否覆盖到特定的值。 Coverpoint cp_b是自动的,bin是自动生成的,会检测b是否覆盖到所有的可能性
1 bit [2:0] a;
2 bit [3:0] b;
3
4 covergroup cg @(posedge clk);
5 cp_a coverpoint a {
6 bins values_a = { [0,1,3,5,7 };
7 }
8 cp_b coverpoint b;
9 endgroup
1 bit[3:0] var_a;
2
3 covergroup test_cg @(posedge clk);
4 cp_a : coverpoint var_a {
5 bins low_bins[] = {[0:3]};
6 bins med_bins = {[4:12]};
7 }
8 endgroup
lowbins[]创建了四个bin,对应检查是否覆盖到0,1,2,3,med_bins创建里一个仓,检查是否覆盖到4-12之间的
值。因此,一共创建了5个仓
ignore_bins用于指定与覆盖点关联的一组值或者翻转行为,这些值或者翻转行为可以明确从覆盖范围中排除。 例如,
以下将忽略变量a的所有采样值7和8。
1 coverpoint a {
2 ignore_bins ignore_vals = {7,8};
3 }
illegal_bins用于指定与覆盖点关联的一组值或者翻转行为,这些值或者翻转行为被标记为非法。 例如,以下会将所有
1、2、3采样值标记为非法。
1 covergroup cg3;
2 coverpoint b {
3 illegal_bins bad_vals = {1,2,3};
4 }
5 endgroup
当采样到illegal_bins时,仿真会报错,并且illegal_bins的优先级高于其他bin,即使其他bin和illegal_bins的范围有重
叠,也会导致报错。
[364] 如何编译一个coverpoint来覆盖一个翻转行为?
[365] 下面的语句覆盖了什么样的翻转行为?
1 coverpoint my_variable {
2 bins trans_bin[] = ( a,b,c => x, y);
3 }
[366] 下面的bin覆盖了哪些范围?
[* N]指的是连续的重复操作。 因此,上面的bin覆盖的是连续4次采样都是3的翻转覆盖率
wildcard bins可以让bin在定义时使用x、z和?作为0或者1的通配符。下面的例子中,并不关心低两位是多少,只要高
两位为11就在覆盖范围内。
1 coverpoint a[3:0] {
2 wildcard bins bin_12_to_15 = { 4'b11?? };
3 }
cp_a有10个bin,cp_b有16个bin,因此cc_a_b有160个bin。
交叉覆盖率通常用于不同功能或者事件同时发生的情况,去验证这些事件是否同时发生了。
[369] 下面的交叉覆盖率有多少个bin?
1 bit[1:0] cmd;
2 bit[3:0] sub_cmd;
3
4 covergroup abc_cg @(posedge clk);
5 a_cp: coverpoint cmd;
6 cmd_x_sub: cross cmd, sub_cmd;
7 endgroup
cmd和sub_cmd都是二值变量,a_cp有4个bin,sub_cmd默认有16个bin,因此,交叉覆盖率具有64个bin。
[370] 下面的覆盖率代码有什么错误?
1 int var_a;
2 covergroup test_cg @(posedge clk);
3 cp_a: coverpoint var_a {
4 bins low = {0,1};
5 bins other[] = default;
6 }
7 endgroup
代码对int类型进行覆盖率收集,low的bin数为2,而通过default所创建的数量为 个,数量十分巨大,这会导致
仿真器崩溃或者仿真速度下降。应该尽量避免使用default或者不要使用default。
[371] covergroup有几种采样方式?
有两种采样方式
定义covergroup时指定时钟事件,下面是一个例子
显式地调用sample()方法
1 class packet;
2 byte[4] dest_addr;
3 byte[4] src_addr;
4
5 covergroup pkt_cg;
6 coverpoint dest_addr;
7 endgroup
8
9 function new();
10 pkt_cg =new();
11 endfunction;
12 endclass
13
14 module test;
15 initial begin
16 packet pkt =new();
17 pkt.pkt_cg.sample();
18 end
19 endmodule
[372] 如何给covergroup传递参数,何时用它?
像定义方法一样,covergroup也可以通过类似的语法进行参数传递,主要使用ref,以便随时检测信号的变化。当我们
要对多个信号进行相同类型的覆盖率组定义时,我们可以通过定义参数传递的方法改变采样的信号,而覆盖率的定义只需
要进行一次即可。下面是一个例子。
1 module test;
2 covergroup xy_cg ( ref int x , ref int y, input string name);
3 cp_x: coverpoint x;
4 cp_y: coverpoint y;
5 cc_x_y: cross cp_x, cp_y;
6 endgroup
7
8 initial begin
9 xy_cg xy_cg_mod1 = new( top.mod1.x, top.mod1.y, "mod1_cvg");
10 xy_cg xy_cg_mod2 = new( top.mod2.x, top.mod2.y, "mod2_cvg");
11 end
12
13 endmodule
[373] covergroup可以引用DUT中的层次信号吗?
可以
[374] 能够对不同covergroup的coverpoint进行交叉覆盖率定义吗?
不可以,只能对当前covergroup的coverpoint定义交叉覆盖率
[375] per_instance和per_type的区别是什么?如何使用覆盖率选项控制它们?
Covergroup可以定义和实例化多次。 如果一个covergroup有多个实例,则默认情况下,SystemVerilog的coverage报
告是所有实例的累计coverage。 这就是默认的per_type。 但是,可以在covergroup中设置一个per_instance选项,那么
SystemVerilog将分别报告该Covergroup的每个实例的coverage。
断言
[376] 什么是断言?在验证中使用断言的好处是什么?
断言是根据规范对设计属性的property,用于验证设计的行为。 如果在仿真中检查的property未按照规范运行,则断
言失败。 类似地,如果禁止在设计中发生的行为,而在仿真期间发生了,则断言也会失败。
使用断言的好处有:
断言在错误发生是会立刻捕获,改善了检测错误的能力
断言在设计中能够提供更好的可观察性,因此有助于更轻松地调试
断言既可以用于动态仿真,也可以用于设计的形式验证
断言还可以用于提供对输入激励的功能覆盖,并确保设计属性确实进行了验证。
[377] 有多少种断言?
systemvrilog中有两种断言,立即断言和并发断言
[378] 立即断言和并发断言的区别是什么?
立即断言使用表达式进行评估,并且像在过程块中的语句一样执行。,执行后会立即进行评估。 立即断言仅用于动态
仿真中。 以下是一个简单的立即断言的示例,该断言检查“a和b是否始终相等”:
[379] 简单立即断言和延迟立即断言之间有什么区别?
[380] 与使用过程式SystemVerilog代码编写检查程序相比,使用SVA(SystemVerilog断言)编
写checker有什么优势?
最好使用SVA而非程序代码编写某些类型的checker。 SVA具备sequence和property规范的丰富构造,这比使用过程代
码或编写基于类的检查器更容易。 另一个额外的好处是,相同的断言也可以在静态检查工具(如形式验证工具)中使用,
也可以用于提供功能覆盖率。
下面是一些推荐使用SVA的例子:
检查内部设计结构,例如FIFO的上溢或下溢。
使用设计中的嵌入式断言可以更轻松地检查模块之间的内部信号和接口
使用时间表达式也可以轻松开发标准接口协议(如PCIE,AMBA,以太网等)的checker。
仲裁,资源匮乏,协议死锁等检查是很多设计中形式验证的检查内容,因此编写断言将有助于它们在静态和动
态仿真中同时使用。
[381] 有几种方式为一个设计编写断言?
可以直接在module内部编写断言。 如果断言是由设计工程师为设计中的某些内部信号或接口编写的,则通常会
采用这种方式
断言也可以编写在单独的interface或module或program中,然后可以绑定到特定的module或实例,在断言中引
用来自该特定module或实例的信号。 这是使用SystemVerilog中的bind构造完成的。 如果断言是由验证工程师
编写的,会采用这种方式。
[382] SVA中的sequence的作用是什么?
sequence是编写property或断言的基本构建块。 sequence可以认为是在单个时钟边沿求值的简单布尔表达式,也可以
是在多个周期内求值的事件sequence。 property可能涉及检查在不同时间开始的一个或多个sequence行为。 因此,可以
使用逻辑或sequence组合的多个sequence来构造property。
sequence的基本语法是:
1 sequence name_of_sequence;
2 <boolean expression >
3 endsequence
例如,下面的sequence在检查每个周期的上升沿是否a和b始终相等
1 sequence s_a_eq_b;
2 @posedge(clk) (a ==b);
3 endsequence
[383] $rose(tst_signal)和@posedge(tst_signal)有什么区别?
@posedge(tst_signal)会在每个上升沿进行采样,检查tst_signal是否为1。 但是,$rose()是一个系统任务,它检查信号
的采样值在先前采样和当前采样之间(先前采样可能是0 / x / z)是否变为1。 因此,$rose()需要两个采样值进行判断,从
原来的非1变成1。 例如:在下面的sequence中,仅当信号“a”在时钟的两个正沿之间从0/x/ z的值变为1时,$rose(a)的计算
结果为true
1 sequence S1;
2 @(posedge clk) $rose(a)
3 endsequence
[384] 下面的sequence在以下那种情况下会捕获成功?
1 sequence S1;
2 @(posedge clk) $rose(a);
3 endsequence
1. 当信号a”从0变为1时。
2. 当信号“a”在clk的一个上升沿采样的值为“0”,而在下一个上升沿采样的值变为“1”。
3. 当信号“a”在clk的一个上升沿采样的值为“1”,而在下一个上升沿采样的值变为“0”。
第二种情况下会捕获成功,$rose()检查的是两次采样,第一次非1,第二次为1,@(posedge clk)表示在上升沿时采
样。
[385] sequence可以在下面那些地方定义?
1. Module
2. Interface
3. Program
4. Clocking Block
5. Package
以上所有地方都可以
[386] 在类中是否可以实现并发断言?
不能,并发断言不能再类中实现
[387] 下面的sequence会匹配事件?
当gnt信号在req信号为高电平后的两个周期变为高电平,然后一个周期后req信号被置为零时,该sequence的值为
真。
连续重复([*const_or_range_expression]):如果sequence从一个迭代结束起以一个时钟单位进行有限次数的迭
代,可以使用连续重复运算符。在下面的例子中,描述的是如果a为高,一个周期后,b连续五个周期为高的事
件。
1 a ##1 b [*5]
跟随重复([->const_or_range_expression]):和连续重复类似,不一样的是,并不要求重复是连续的,间断的也
可以,并且要求在最后一次匹配时,立刻匹配后续表达式。下面的例子中,描述的是如果a为高,一个周期后b
连续至少2个至多10个周期为高,然后一个周期后c为高的事件
非连续重复([=const_or_range_expression] ):和跟随重复类似,但是重复匹配后,并不要求后续立刻跟上,只要
在之后能够匹配到即可。例如下面的例子中,可以在c有效匹配前一个时钟,并不需要b进行有效匹配。
[389] 下面的断言有什么错误?
立即断言只能在程序块中使用
[390] 写一个断言,检查信号最少2个最多6个周期内为高电平
1 property a_min_2_max_6:
2 @(posedge clk) $rose(a) |-> a[*2:6] ##1 (a==0)
3 endproperty
4
5 assert property (a_min_2_max_6);
[391] 什么是蕴含操作符?
蕴含操作符用于指定检查的先决条件,描述当先决条件发生后,再继续匹配。蕴含操作符只能再property这一级别使
用。蕴含操作符有两种
交叠蕴含操作符
非交叠蕴含操作符
当蕴含操作符左侧发生后,才会匹配右侧
[392]交叠蕴含操作符和非交叠蕴含操作符有什么不同?
交叠蕴含操作符,先决条件的起点和后继事件的起点是同一个时刻。例如下面的例子中,再时钟上升沿是,a要
为1,同时开始检查b是否为1,如果不是那么匹配不成功,不会进行后续的评估
1 assert property abc_overlap (@posedge clk (a==1) |-> b ##1 c )
非交叠运算符,先决条件的终点是后继事件的起点,在下一个周期才会匹配后续。例如下面的例子中,时钟上
升沿a为1,下个周期要匹配b为1,而不是同一时刻进行匹配。
[393] 蕴含操作符可以在sequence中使用吗?
不能,只能在property中使用
[394] 下面的两个断言是等效的吗?
是的,参考[392]
[395] SVA中允许嵌套蕴含吗?
允许,下面就是一个例子
1 a |=> b |=> c
匹配的就是a为高,下个周期b为高,再下个周期c为高
[396] 系统函数$past()的作用是什么?
这个系统函数能够从之前的时钟周期中获得信号
[397] 写一个断言,检查一个信号永远不会变成X
使用系统函数$isunknown(signal)可以进行此项检查。
[398] 写一个断言,检查一个变量保持独热码状态
使用系统函数$isonehot()或者$countones()可以进行此项检查
[399] 写一个断言,检查主设备是否在发出有效请求后就在2到5个时钟周期内提供授权
1 property p_req_grant; @(posedge clk) $rose (req) |-> ##[2:5] $rose (gnt); endproperty
[400] 如何再复位期间禁止进行断言检查?
使用“disable iff”可以实现
1 assert property (@(posedge clk) disable iff (reset) a |=> b);
[401] systemverilog中的bind构造是什么意思?
SystemVerilog中的bind构造用于将checker于模块、模块的示例或者一个模块的多个示例进行绑定。通过绑定可以分
离断言和设计diamagnetic。下面是示例语法
1 bind <target module/instance> <module/interface to be instantiated> <instance name with port map>
当我们有下面的一个接口,并且内嵌断言
绑定到模块
绑定到模块的某个实例
[402] 如何关掉所有的断言?
使用$assertoff()系统函数可以实现,缺省情况下会关掉所有断言。也可以指定关闭哪些断言。
1 $assertoff[(levels[, list])]
第一个参数指定关闭哪个层次的断言,第二个参数指定关闭具体哪些property
[403] 有哪些方式为property指定时钟?
sequence中显式使用时钟,继承到property中
1 sequence seq1;
2 @(posedge clk) a ##1 b;
3 endsequence
4
5 property prop1;
6 not seq1;
7 endproperty
8
9 assert property (prop1);
property中显示使用时钟
1 property prop1;
2 @(posedge clk) not (a ##1 b);
3 endproperty
4
5 assert property (prop1);
从过程块中推断当前使用的时钟
使用当前时钟块的时钟
直接定义默认时钟,在没有上述方法指定时钟的条件下会使用默认时钟。
1 assert property (@(posedge clk) disable iff (!rst_n) (wordcnt>31 |-> fifo_full));
2 assert property (@(posedge clk) disable iff (!rst_n) (wordcnt==31 && write_en && !read_en |=>
fifo_full));
注意第二条,使用的是非交叠蕴含操作符,满标志位要在下个周期拉高。