IC验证基础知识系列随笔
一、断言 and 和 和 intersect 区别
-
And
指的是两个序列具有相同的起始点,终点可以不同。 -
Intersect
指的是两个序列具有相同的起始点和终点。 -
Or
指的是两个序列只要满足一个就可以 -
Throughout
指的是满足前面要求才能执行后面的序列
二、Break;continue;return的含义,return之后,function里剩下的语句会执行吗
-
break
语句结束整个循环。 -
continue
立即结束本次循环,继续执行下一次循环。 -
return
语句会终止函数的执行并返回函数的值(如果有返回值的话)。 -
return
之后,function
里剩下的语句不能执行,其是终止函数的执行,并返回函数的值。
三、组件之间的通信机制,analysis port和其它的区别
-
通信分为,单向通信,双向通信和多向通信
-
单向通信:指的是从
initiator
到target
之间的数据流向是单一方向的 -
双向通信:双向通信的两端也分为
initiator
和target
,但是数据流向在端对端之间是双向的 -
多向通信:仍然是两个组件之间的通信,是指
initiator
与target
之间的相同TLM端口数目超过一个时的处理解决办法。
-
blocking阻塞传输的方法包含:
-
Put():
initiator
先生成数据Tt
,同时将该数据传送至target
。 -
Get():
initiator
从target
获取数据Tt
,而target
中的该数据Tt
则应消耗。 -
Peek():
initiator
从target
获取数据Tt
,而target
中的该数据Tt
还应保留。
-
通信管道:
-
TLM FIFO
:可以进行数据缓存,功能类似于mailbox
,不同的地方在于uvm_tlm_fifo
提供了各种端口(put、get、peek)
供用户使用 -
analysis port
:一端对多端,用于多个组件同时对一个数据进行处理,如果这个数据是从同一个源的TLM
端口发出到达不同组件,则要求该端口能够满足一端到多端,如果数据源端发生变化需要通知跟它关联的多个组件时,我们可以利用软件的设计模式之一观察者模式实现,即广播模式 -
analysis TLM FIFO
a. 由于analysis
端口提出实现了一端到多端的TLM
数据传输,而一个新的数据缓存组件类uvm_tlm_analysis_fifo
为用户们提供了可以搭配uvm_analysis_port
端口uvm_analysis_imp
端口和write()
函数。
b.uvm_tlm_analysis_fifo
类继承于uvm_tlm_fifo
,这表明它本身具有面向单一TLM
端口的数据缓存特性,而同时该类又有一个uvm_analysis_imp
端口analysis_export
并且实现了write()
函数:
-
request & response通信管道 双向通信端口
transport
,即通过在target
端实现transport()
方法可以在一次传输中既发送request
又可以接收response
。
四、类的public、protected和local的区别
-
如果没有指明访问类型,那么成员的默认类型是public,子类和外部均可以访问成员。
-
如果指明了访问类型是
protected
,那么只有该类或者子类可以访问成员,而外部无法访问。 -
如果指明了访问类型是
local
,那么只有该类可以访问成员,子类和外部均无法访问。
五、Uvm_component_utils有什么作用
UVM(Universal Verification Methodology)是一种用于验证集成电路设计的标准方法学,它提供了一套面向对象的验证框架和工具库,简化了验证环境的搭建和管理。UVM_component_utils是UVM中的一个宏定义,它的作用是简化UVM组件(component)的声明和注册流程。
在UVM中,组件是验证环境中的基本单元,可以是顶层环境、测试用例、序列等。UVM_component_utils宏定义了一个名为"uvm_component_registry"的类静态成员,该类用于将组件自动注册到验证环境中。通过在组件的定义中使用UVM_component_utils宏,可以省略手动编写的注册代码,提高验证环境代码的简洁性和可读性。
使用UVM_component_utils宏的示例代码如下:
class my_component extends uvm_component;
// 组件定义
// 使用UVM_component_utils宏进行组件注册
`uvm_component_utils(my_component)
// 其他组件代码
endclass
上述代码中,my_component继承自uvm_component类,并使用了UVM_component_utils宏来注册my_component类到验证环境中。
通过使用UVM_component_utils宏,验证工程师可以更方便地声明和管理UVM组件,提高了代码的可重用性和可维护性,进而提高了验证环境的开发效率。
五、为什么要配置is_active呢?
比如你在模块级做验证时,拿验APB接口为例,你需要用一个APB VIP,包含master agent和 slave agent。master通过APB的接口进行读写操作,slave是通过APB接口去响应操作,所以需要 master agent和slave agent里面都要有driver,maser_driver来模拟从APB总线上发起读或写的操作(控制paddr、pwrite和pwdata等),slave_driver模拟从APB总线上做响应,返回predata,pready和pslverr信号。
但是当你这个APB接口的模块验证环境被集成到一个更大的模块时,有真正的master模块设计连接着APB总线,设计里面自己会去对总线做操作,也有slave模块通过APB总线做响应。那这个时候,我就不需要你APB VIP里面的master agent 里面的 driver 和 sequencer 来模拟master去发送激励了,因为这个时候我有真正的master连在总线上。同样,我也有真正的slave模块来响应。但这个时候,我们仍然想监测APB总线上数据的来来回回,但又不需要发送激励,这时候我们就可以通过对APB VIP的 master agent 和 slave agent 配置UVM_PASSIVE,也就是不例化 driver 和 sequencer ,只例化monitor,实现了模块级环境复用到更高级别的环境中。
六、在数字验证工作中,常见的一些bug包括:
- 时序问题:时钟和时序路径错误,导致数据传输不完整或乱序。
- 数据不匹配:期望值与实际值不匹配,可能是因为设计问题或验证环境问题。
- 边界条件错误:未正确处理各种边界条件,导致错误情况未覆盖或处理不正确。
- 接口问题:接口配置错误或接口信号连接错误,导致数据无法正确传输。
- 错误的时序约束:时序约束设置错误,导致无法满足设计的时序要求。
- 数据溢出或截断:数据宽度错误导致数据溢出或截断,结果不正确。
- 波形形状错误:波形形状错误或时序不一致,导致验证失败。
- 任务或进程协作错误:任务或进程之间的通信或同步问题导致错误的行为。
- 配置错误:配置参数或寄存器设置错误,导致不正确的配置行为。
- 仿真工具或工作流问题:仿真工具或工作流本身的问题导致错误结果。
这些是数字验证工作中常见的一些bug,当进行验证时需要注意并仔细排查以确保验证正确性。
七、你在做验证时的流程是怎么样的,你是怎么做的。
对于流程的话
-
第一步先去查看
spec
文档,将模块的功能和接口总线时序搞明白,尤其是工作的时序,这对于后续写TB
非常重要; 这一步一般和designer一起,可以加快理解,如果有分歧可以找有经验的同事一起review。 -
第二步我会根据功能点去划分我的
TB
应该怎么搭建,我的case
大致会有哪些,这些功能点我应该如何去覆盖,时序应该如何去检查,总结列出这样的一个清单;此时进行testplan和verification plan的编写,并同designer一起review,检查功能点是否全部考虑到,确保验证计划的完备。 -
第三步开始去搭建我们的
TB
,包括各种组件和VIP(例化VIP接口,集成到环境中,确保后面可以正常工作),和一些基础的sequence
还有test
,暂时先就写一两个基础的sequence
,然后还有一些环境配置参数的确定等,最后能够将TB
正常运行,保证无误; -
第四步就是根据清单去编写
sequence
和case
,然后去仿真,保证仿真正确性,收集覆盖率;验证工程师大部分时间在反复调试测试用例,发现RTL的bug,report给designer并协助designer去debug。 -
第五步就是分析收集的覆盖率,然后查看覆盖率报告去分析还有哪些没有被覆盖,去写一些定向
case
,和更换不同的seed
去仿真;在收集覆盖率前,一般要根据设计功能去编写func coverage,然后通过regression收集code coverage 和func coverage。 -
第六步就是回归测试
regression
,通过不同的seed
去跑,收集覆盖率和检测是否有其它bug
; -
第七步就是review验证工作,找有经验的工程师前辈,一起查看验证tb和testcase,一方面查看是否有验证遗漏,另一方面可以借鉴前辈的经验优化验证环境。
八、在寄存器模型中加入后门访问
在IP验证工作中,我们通常会引入寄存器模型,针对寄存器模型的功能是否正确,我们会有专门的case进行测试,通常测试寄存器的读写功能,采用寄存器前门访问先写后读的方式,但是有些寄存器的偏移地址错误的情况,这种先写后读的方式并不能检查出来,此时需要换一种方式,比如采用前门写入,后门读出,进行检查。为此我们需要在寄存器模型中加入后门访问路径:
在寄存器模型中加入后门访问的一种常见方式是通过添加特殊的控制寄存器或位来实现。后门访问可以是一种特权访问,可以用于调试、测试或特殊操作。
以下是一种可能的实现方式:
- 定义一个特殊的控制寄存器或位,用于表示后门访问的使能或状态。
- 修改寄存器模型的访问逻辑,使用这个特殊的控制寄存器或位来判断是否允许后门访问。
- 在访问寄存器的逻辑中添加额外的逻辑判断,以便在后门访问使能时执行相应的操作。
- 根据后门访问的需求,可能需要在寄存器模型中添加一些特殊的操作,例如读取或写入特定的寄存器值,执行特殊的操作序列等。
需要注意的是,后门访问是一种特权操作,需要根据设计要求和安全考虑来谨慎使用。同时,在寄存器模型中加入后门访问时,需要确保相关的验证和仿真工作也能正确处理后门访问的逻辑。
假设我们有一个简单的寄存器模型,其中包含一个32位的控制寄存器 control_reg
,我们希望为管理员用户添加后门访问权限。
首先,我们可以在寄存器模型的寄存器类中添加一个额外的配置字段 admin_access
,用于标识是否具有管理员访问权限。这可以通过修改寄存器类的定义来实现:
class my_register extends uvm_reg;
bit admin_access;
function new(string name = "my_register");
super.new(name, 32, UVM_NO_COVERAGE);
endfunction
virtual function bit can_read(uvm_reg_map map = null, uvm_reg_item item = null);
if (admin_access && !uvm_reg::default_map.has_write_access(UVM_BACKDOOR))
return 0;
return 1;
endfunction
virtual function bit can_write(uvm_reg_map map = null, uvm_reg_item item = null);
if (admin_access && !uvm_reg::default_map.has_write_access(UVM_BACKDOOR))
return 0;
return 1;
endfunction
endclass
在上面的示例中,我们重写了 can_read()
和 can_write()
函数,以检查是否具有管理员访问权限,并且不仅检查默认映射的访问权限,还检查是否具有后门访问权限。
然后,在使用寄存器模型的环境中,我们可以根据需要设置 admin_access
字段,为管理员用户启用后门访问权限。示例代码如下:
my_register my_reg;
initial begin
// 创建寄存器模型实例
my_reg = new();
// 模拟环境中启用管理员后门访问权限
my_reg.admin_access = 1;
// 环境中的其他代码...
end
在上面的示例中,我们创建了 my_register
的实例 my_reg
并将 admin_access
字段设置为 1,从而启用了管理员用户的后门访问权限。
这只是一个简单的示例,实际情况中可能涉及更复杂的权限控制和访问策略。根据具体需求,可以根据寄存器模型的设计灵活地实现后门访问机制。