数字IC验证成长录--其他技能介绍
以下内容来源牛客特邀专刊《数字IC验证成长录》,作者@竹秋一
接下来介绍一些验证方向之外的知识,原因有两点:一方面验证人员要对一些设计结构有个大概了解,不能什么都不知道,另一方面是笔试会经常考这些东西。
跨时钟域
当两个不同时钟频率的模块进行通信时,需要进行跨时钟域处理,否则会导致亚稳态、数据丢失等问题。
这里我直接推荐学习资料:知乎搜索“李虹江”老师,进入主页找他的文章“CDC的那些事”,这是个系列文章,看完之后基本就能搞清楚跨时钟域,我当时就是跟着这个文章学会的。或者腾讯课堂搜索“异步电路设计”,老师叫“goodman2046”,课程很便宜,一顿饭钱。
静态时序分析
笔试题经常会给出一个电路图,让你计算最大频率或者问是否存在保持时间违例。STA的知识并不难,只要记住公式的推导过程以及一些概念性的东西,基本都能做全对。
首先推荐“IC创新学院”搜索“静态时序分析”,邸志雄老师的课程,讲的非常细,看完一遍基本遇到STA的笔试题都不怕了。不过这个网站最近不知道出了什么问题一直进不去,没关系,B站也有同款视频,搜索“数字集成电路静态时序分析基础”,up主“讲芯片的邸老师”。另外一个就是腾讯课堂里的“FPGA静态时序分析精讲”,V3学院的免费课,讲的也是很不错的。
总线协议
总线协议是验证工程师必会的知识点,在实际项目中经常接触,并且也是面试官常考的问题。
先总结:AMBA总线必须学,至少要会AHB和APB,有能力的就把AXI也掌握了,其次UART、SPI、I2C、I2S、CAN协议比较简单,可以选择性的学习,最后如果有机会的话,USB、PCIe协议也可以了解一下。
AMBA协议文档方面我优先推荐的是看官方的文档,其次是网上的中文资料。原因是英文原版表达出来的意思不会偏差,而网上中文翻译的文档都会多少带着翻译人自己的理解,而且看英文文档能锻炼阅读能力。同时找一些带着AMBA接口的项目做一下,跑一跑波形,对着波形图去理解,在实践中学习。
UART、I2C等协议也是一样的方法。
记住实践很重要,只对着文档看而不去操作,是无法深入理解的。
题库
这里是一些我亲身经历的IC验证岗位的笔试面试题,并附上了我的实际回答情况,可能有错误或不全面的地方,如果发现欢迎指正。
由于笔试题存在很多非验证题目,而且笔者没有怎么整理过笔试题,所以最终选择的合适的笔试题数量不多,更多的题目可以在牛客网搜索。面试题目也只是整理了一些适合读者借鉴的题目,并且进行了筛选,和我个人简历或者项目相关的题目我都省略了。
笔试题
-
SV中类默认的成员属性是
A. Local
B. Private
C. Public
D. Automatic
答案:C。声明成员时如果没有指明,则默认类型为Public,子类和外部都可以访问;如果是local,那么只有该类可以访问,子类和外部无法访问;如果是protected,那么该类和其子类可以访问,外部无法访问。
-
一段程序如下,请问在45这个时刻上,A B的值各是多少
A. 1,1
B. 0,1
C. 0,0
D. 1,0
答案:C。begin end块里顺序执行,因此每个延迟都是叠加的,不是具体的时刻。fork join块里并行执行,因此这两个begin end块同时开始,并且没有握手通信,互不干扰。所以在45时刻,两个begin end块都执行了“#20 A/B = 0;”,直到50时刻才被赋1,所以结果都是0。
-
在SV中,调用$write可以自动地在输出后进行换行
A. 正确
B. 错误
答案:B。$display打印会自动换行,$write不会。
-
以下SV程序的运行结果为
A. 32’hFFFFFFFF
B. 32’h00000000
C. X
D. 32’h00000001
答案:B。该例中定义了一个Test类,并例化出句柄t,调用new函数创建对象,并传入了一个参数值“32'h1”,这个“32'h1”赋值给了new函数中的addr,将addr的值“32'h1”赋值给“另一个”addr,但这并不是另一个addr,不是Test类中定义的addr,而是new函数自己的参数。知识点:在函数中索引一个变量名,会优先“就近寻找”,比如该例中的new函数中的addr就是形式参数addr,想要指向类中的变量,是需要this关键词指示的,比如“this.addr”,因此t句柄指向对象中的addr仍然为初始值,bit是二值逻辑,因此addr的初始值为0。在打印消息时,调用了display_addr方法,该方法中的addr同样就近寻找,发现function中没有定义addr,则继续向上一层寻找,找到类中的addr,并进行打印。因此最终打印结果为0。
-
关于uvm sequence常用宏,以下说法不正确的是
A. uvm_do_on_pri可指定transaction发送时采用的sequencer,同时指定优先级
B. uvm_do_on可指定transaction发送时采用的sequencer
C. uvm_do_with可添加激励约束
D. uvm_do可指定transaction发送时采用的sequencer,同时添加激励约束
答案:D。宏里有“on”表示可以选择采用的sequencer,有“with”表示可以添加约束,带“pri”表示可以指定该trans的优先级。
面试题
-
模块级和系统级验证的关注点有什么不一样
模块级验证更加注重模块接口及内部的细节,包括接口时序、代码覆盖率、边界情况等,例如模块能否处理错误的接口时序、工作时复位状态机能否正常跳回、未执行的代码分支的原因等,并且测试用例多为SV或UVM用例;系统级验证关注整个SoC的场景以及各模块的协同工作,例如数据的吞吐率、中断信号是否正确交给CPU等,并且测试用例多为C测试。
-
UVM工厂机制的好处
使用工厂创建的类,成员可以使用UVM提供的高效方法,如克隆、打印,对象也可以在创建前被新的类型覆盖,保证环境的封闭性。
-
如何划分验证功能点
可以根据接口、寄存器、中断以及配置流程划分。接口可以根据时序和协议划分一系列功能,如同时读写、连续读写、非法地址访问、不同时钟频率不同的工作状态等;寄存器可以根据每个寄存器域的功能划分各种工作场景,并将它们cross在一起;中断可以划分为触发中断、清除中断、屏蔽中断等;配置流程方面,比如连续配置寄存器使模块不断切换模式进行运作、复位后重新使能等。最后加上边界情况,大致功能点就划分好了。当然一个人的想法是有限的,划分出的功能点需要和设计人员以及leader进行多次review,不断补充。
-
功能覆盖率怎么定义(手撕代码),采样的信号从哪里来
class my_coverage_model extends uvm_component;
`uvm_component_utils(my_coverage_model)
covergroup cg with function sample(bit value);
mode: coverpoint value
{
bins value_0 = 0;
bins value_1 = 1;
}
……
endgroup: cg
……
function new(string name = "my_coverage_model", uvm_component parent);
super.new(name, parent);
cg = new();
……
endfunction
task build_phase(uvm_phase phase);
super.build_phase(phase);
……
endtask
task run_phase(uvm_phase phase);
super.run_phase(phase);
fork
this.do_sample();
join_none
endtask
task do_sample();
forever begin
@(posedge clk iff rstn);
cg.sample(value);
end
endtask
endclass
大概写一下就可以了,说清楚细节。采样的信号可以从接口上获取,也可以从寄存器模型里get,如果有必要,可以从trans获取随机后的值。
-
写一个driver class基本的结构
class my_driver extends uvm_driver #(my_trans);
virtual my_interface vif;
`uvm_component_utils(my_driver)
function new (string name = "my_driver", uvm_component parent);
super.new(name, parent);
endfunction
function void set_interface(virtual my_interface vif);
if(vif == null)
`uvm_error("GETVIF","interface handle is NULL, please check if target interface has been intantiated")
else
this.vif = vif;
endfunction
task run_phase(uvm_phase phase);
fork
this.drive();
this.reset();
join
endtask
task reset();
forever
begin
……
end
endtask
task drive();
my_trans req, rsp;
@(posedge vif.rstn);
forever begin
seq_item_port.get_next_item(req);
this.do_driver(req);
void'($cast(rsp, req.clone()));
rsp.rsp = 1;
rsp.set_sequence_id(req.get_sequence_id());
seq_item_port.item_done(rsp);
end
endtask
task do_driver(my_trans t);
……
endtask
endclass
driver的写法有很多种,这里只举一个简单的例子。但同样也要讲清楚每一行代码的意义。
-
sequence怎么挂载到sequencer上,激励怎么发出来
顶层sequence在test中创建并start到virtual_sequencer上,子sequence在顶层sequence中声明,使用`uvm_do_on_with等宏实现创建、随机和挂载,sequence里的transaction被发送到对应的sequencer上,传递给driver,通过driver向interface驱动发送出来。
-
Interface怎么传递到环境里
使用uvm_config_db的set和get方法,在tb的initial里set,在环境中get。
-
两个task怎么并行执行
使用fork join/join_any/join_none调用两个task。
-
`ifndef,`define,`endif有什么用
编译代码时工具会记录这些参数,当遇到同名的参数时,就不会编译`define中的内容,减少编译时间。
-
uvm_object和uvm_component的区别
简单来说,uvm_conponent及其子类始终存在于环境中,而transaction、sequence等激励属于object,只有在仿真过程中被创建,并在完成发送激励的使命后消失。
-
如何使用set_factory_override
该方法用于在不改变原始代码的情况下,完成在类被创建前进行类型覆盖,需要满足以下条件:被覆盖类是覆盖类的父类;都使用工厂机制注册过;该方法需要在build阶段之前使用。
-
如何在一个sequence中发送两种不同类型的item
在sequence中声明两种item,使用带有“on”的宏方法挂载到不同的sequencer上。
-
monitor将数据交给了谁。
scoreboard、参考模型、覆盖率模型。
-
讲一下virtual_sequence和virtual_sequencer的内容和用法。
virtual_sequencer中声明了环境中所有sequencer的句柄。virtual_sequence中使用宏`uvm_declare_p_sequencer声明一个virtual_sequencer类型的p_sequencer,继承于virtual_sequence的top_sequence中定义的sequence和item就可以使用宏方法通过p_sequencer句柄找到自己的sequencer进行挂载。
-
m_sequencer和p_sequencer之间的关系。
m_sequencer是p_sequencer的父类。m_sequencer存在于uvm_sequence中,类型为uvm_sequencer,当该sequence挂载到一个sequencer上时,m_sequencer指向该sequencer。p_sequencer在virtual_sequence中声明,类型为virtual_sequencer,用于让各个sequence能够在编译和仿真阶段找到自己的sequencer。
-
top_seq一般在哪里挂载到virtual_sequencer。
在每个test的run_phase中挂载。
原文链接:数字IC验证成长录-其他技能篇