附:
IC面试常考题
以下内容搬运自 数字芯片实验室 公众号,安利一波良心博主
(本人已为知识付费,只是搬运学习用作整理,希望大家也去付费支持一波)
原博文链接
**UVM(Universal Verification Methodology)**是一个标准化的用于验证设计的方法学。其优势包括:
重用性、VIP即插即用、通用性、独立于仿真器、支持CDV(coverage driven verification)、支持CRV(constraint random verification)等等
build_phase是top-down phase,run phase等task phase是 parallel phase, 其他都是bottom-up phase。
通过在顶层调用run_test,run_test任务会首先创建test top,然后调用所有的phase,开始仿真。
在顶层调用run_test(),在仿真选项中使用
+UVM_TESTNAME = testname
factory是一种在验证环境中实例化组件或生成激励时提供更大灵活性的机制。factory机制允许在不修改原始源代码的条件下覆盖现有验证代码创建的对象类型。
super class是当前类所**“扩展(extends)”**的类。例如,my_test的super class是uvm_test
在事务级别建模中,不同的组件或模块使用事务对象进行通信。TLM port定义了用于连接的一组方法(API),而这些方法的具体实现称为TLM exports。
TLM port和export之间的连接建立了两个组件之间的通信机制。producer可以创建事务并将其“put”到TLM端口,而“put”方法实现(也称为TLM export)将在consumer中读取producer创建的事务,从而建立通信渠道。
如果producer组件和consumer组件需要独立运行,则可以使用TLM FIFO进行事务通信。在这种情况下,producer组件生成事务并将其“puts”到FIFO,而consumer组件一次从FIFO获取一个事务并对其进行处理。
sequence item是一个对象,其建模了两个验证组件之间传输的信息(有时也可以称为事务(transaction))。例如:读操作和写操作中的地址和数据信息。
sequence是由driver驱动的sequence item序列模式,由其body()方法实现。例如:连续读取10次事务。
UVM agent集合了围绕DUT的其他uvm_components。大多数DUT具有多个interfaces,并且一个Agent用于组合driver、sequencer和monitor。使用此层次结构有助于在不同验证环境和项目之间重用Agent。
ACTIVE agent是可以在其操作的接口上生成激励,其包含driver和sequencer。
PASSIVE agent不生成激励,只能监视接口,这意味着在PASSIVE agent中将不会创建driver和sequencer。
UVM agents具有类型为uvm_active_passive_enum的变量,该变量定义agents是否是active (UVM_ACTIVE)(构建了sequencer和driver)或者passive(UVM_PASSIVE) (不构建sequencer和driver)。
此参数默认情况下设置为UVM_ACTIVE,在environment类中创建agents时,可以使用**set_config_int()**进行更改。然后,agent的build phase阶段应具有以下代码以选择性地构建driver和sequencer。
function void build_phase(uvm_phase phase);
if(m_cfg.active == UVM_ACTIVE) begin
//create driver, sequencer
end
endfunction
Driver是根据接口协议将事务转换为一组信号级切换的组件。Sequencer是一个将事务(sequence items)从sequence发送到Driver,并将Driver的响应反馈给sequence的组件。
sequencer也会对同时尝试访问Driver以激励设计接口的多个sequences进行仲裁。sequence和sequencer在事务级抽象,Driver在信号级对设计接口进行驱动,即单一设计模式。
monitor是一个监测引脚级别的信号活动并将其转换为事务或sequence_items的组件。monitor还通过analysis port将这些事务发送到其他组件。
scoreboard用于检查DUT的行为是否正确。
**run_test()方法(静态方法)**用来激活UVM验证平台。通常在顶层的“ initial begin…end”块中调用,并且它使用一个参数指定要运行的test case。run_test()方法在build_phase()中执行test class的构造函数,并进一步构造层次化的Env / Agent / Driver / Sequencer对象。
启动sequence需要执行三个步骤,如下所示:
my_sequence_c seq;
seq = my_sequence_c ::type_id :: create(“ my_seq”)
seq.randomize()
UVM将sequence item从sequence传输到driver并收集来自driver的响应。
在sequence端:
在driver端:
virtual task start (
uvm_sequencer_base sequencer, // Pointer to sequencer
uvm_sequence_base parent_sequencer = null, // parent sequencer
integer this_priority = 100, // Priority on the sequencer
bit call_pre_post = 1); // pre_body and post_body called
多个sequences可以同时连接到单个接口的driver上。Sequencer支持仲裁机制,以确保在任何时间点只有一个sequences可以访问driver。哪个sequence_item可以发送取决于用户选择的Sequencer仲裁算法。
在UVM中实现了五种内置的Sequencer仲裁机制,也可以实现用户自定义的仲裁算法。Sequencer有一个set_arbitration()的方法,调用该方法可以选择使用哪种算法进行仲裁。可以选择的六种算法如下:
通过将优先级相对值参数传递给sequence的start()方法来指定优先级,第三个参数指定优先级。
seq_1.start(m_sequencer, this, 500); //Highest priority
seq_2.start(m_sequencer, this, 300); //Next Highest priority
seq_3.start(m_sequencer, this, 100); //Lowest priority among threesequences
当在sequencer上运行多个sequence时,sequencer对sequence中的每个sequence item 的访问权限进行仲裁。有时一个sequence可能希望独占访问sequencer,直到将其所有的sequence item都发送完毕为止(例如:你希望施加一个定向的激励,在此过程中不被打断)。
有两种机制允许sequence获得对sequencer的独占访问权。
lock()和unlock( ): sequence可以调用sequencer的lock方法,以通过sequencer的仲裁机制获得对driver的独占访问权限。如果还有其他标记为更高优先级的sequences,则此sequences需要等待。授予lock权限后,其他sequences将无法访问driver,直到该sequences在sequencer上调用unlock()方法。lock方法是阻塞调用,直到获得lock权限才返回。
grab()和ungrab():grab()方法类似于lock()方法,用于申请独占访问。grab()和lock()之间的区别在于,调用grab()时将立即生效,不考虑其他sequences的优先级,除非已经存在sequences调用了lock()或grab()。
根据设计接口如何激励,可以分为两种sequence-driver模式:
class nonpipe_driver extends uvm_driver #(req_c);
task run_phase(uvm_phase phase);
req_c req;
forever begin
get_next_item(req); // Item from sequence via sequencer
// drive request to DUT which can take more clocks
// Sequence is blocked to send new items til then
item_done(); // ** Unblocks finish_item() in sequence
end
endtask: run_phase
endclass: nonpipe_driver
class pipeline_driver extends uvm_driver #(req_c);
task run_phase(uvm_phase phase);
req_c req;
forever begin
get_next_item(req);// Item from sequence via sequencer
fork
begin
//drive request toDUT which can take more clocks
//separate threadthat doesn’t block sequence
//driver can acceptmore items without waiting
end
join_none
item_done(); // **Unblocks finish_item() in sequence
end
endtask: run_phase
endclass:pipeline_driver
class my_driver extends uvm_driver;
//function that gets item from sequence port and
//drives response back
function drive_and_send_response();
forever begin
seq_item_port.get(req_item);
//function that takes req_item and drives pins
drive_req(req_item);
//create a new response item
rsp_item = new();
//some function that monitors response signals from dut
rsp_item.data = m_vif.get_data();
//copy id from req back to response
rsp.set_id_info(req_item);
//write response on rsp port
rsp_port.write(rsp_item);
end
endfunction
endclass
启动sequence时,它始终与启动sequencer相关联。m_sequencer句柄包含该sequencer的引用。使用此句柄,sequence可以访问UVM组件层次结构中的信息
与sequencer,driver或monitor等UVM组件在整个仿真周期内都存在不同,UVM sequence是生命周期有限的对象。因此,如果在sequence从测试平台层次结构(组件层次结构)访问任何成员或句柄,则需要运行该sequence的sequencer的句柄。
m_sequencer是uvm_sequencer_base类型的句柄,默认情况下在uvm_sequence中可用。但是要访问在真实的sequencer,我们需要对m_sequencer进行转换(typecast),通常称为p_sequencer。下面是一个简单的示例,其中sequence要访问clock monitor组件的句柄。
class test_sequence_c extends uvm_sequence;
test_sequencer_c p_sequencer;
clock_monitor_c my_clock_monitor;
task pre_body();
if(!$cast(p_sequencer, m_sequencer)) begin
`uvm_fatal(“Sequencer Type Mismatch:”, ” Wrong Sequencer”);
end
my_clock_monitor = p_sequencer.clk_monitor;
endtask
endclass
class test_Sequencer_c extends uvm_sequencer;
clock_monitor_c clk_monitor;
endclass
在early randomization中,先调用randomize()方法对sequence对象进行随机化,然后再使用start_item()来请求对sequencer的访问,如下例所示:
task body()
assert(req.randomize());
start_item(req);//Can consume time based on sequencer arbitration
finish_item(req);
endtask
在late randomization中,先调用start_item(),直到从sequencer授予仲裁,然后在将事务发送到sequencer/driver之前调用randomize。
task body()
start_item(req); //Can consume time based on sequencer arbitration
assert(req.randomize());
finish_item(req);
endtask
从sequence的body()任务中,如果调用了另一个sequence的start(),则通常将其称为subsequence。
1)
function get_drive_req();
forever begin
req = get();
req = get();
end
endfunction
2)
function get_drive_req();
forever begin
req =get_next_item();
req =get_next_item();
item_done();
end
endfunction
3)
function get_drive_req();
forever begin
req = peek();
req = peek();
item_done();
req = get();
end
endfunction
2)错误,因为不能在调用item_done()之前两次调用get_next_item(),无法完成与sequencer的握手。
sequencer具有stop_sequences()方法,可用于停止所有sequences。但是此方法不检查driver当前是否正在驱动任何sequence_items,如果driver调用item_done()或put(),则可能会出现Fatal Error,因为sequences指针可能无效。
convert2string():建议实现此函数,该函数返回对象的字符串形式(其数据成员的值),这对于将调试信息打印到仿真器界面或日志文件很有用。
task body();
seq_item_c req;
start_item(req);
#10 ns;
assert(req.randomize());
finish_item(req);
endtask
应该避免在start_item和finish_item之间添加延迟。 start_item返回后,该sequence将赢得仲裁并可以访问sequencer/driver 。从那时起直到finish_item的任何延迟都将使得sequencer/driver 暂停,不能被其他sequence使用。
uvm_sequence类使用uvm object utils()宏在factory中注册,并将类名作为参数传递:
class test seq c extends uvm sequence;
`uvm_object utils(test_seq_c)
uvm_component类使用uvm_component utils()宏在factory中注册,并将类名作为参数传递:
class test_driver_c extends uvm_component;
`uvm_component_utils(test_driver_c)
task main_phase(uvm phase phase);
phase. raise_objection(this );
my_test sequence. start(my sequencer);
phase. drop_objection(this);
endtask
如果由于某些错误导致超出最大仿真时间,仿真超时机制可以帮助停止仿真。在UVM中有一个很方便的函数set global timeout(timeout)用于将uvm top.phase timeout变量设置为超时值。如果run()phase在该超时值之前没有结束,则仿真将停止并报告错误。
module test;
initial begin
set_global timeout(100ons);
end
initial begin
run test();
end
endmodule
1)build_phase()
2)connect_phase()
3)end of elaboration().
1. start_of_simulation()
2. run-phase(),进一步分为12个子phases:
1. pre_reset
2. reset
3. post reset
4. pre_configure
5. configure
6. post_configure
7. pre_main
8. main
9. post_main
10. pre _shutdown
11. shutdown
12. post_shutdown
1)extract()
2)check()
3)report()
4)final()
uvm_config_db#(<type>)::set (uvm_componentcontext, string inst_name, string field_name, <type> value)
uvm config_db#(<type>)::get(uvm_componentcontext, string inst_name, string field_name, ref value).
context指定调用get/set的当前类或组件。
inst_name是调用get/set的组件实例的名称。
field_name是在config-db中get/set的参数的名称。
****标识get/set的配置信息的类型。
class Packet_c;
byte[4] src addr, dst addr;
byte[] data;
byte[4] crc;
endclass
//Userdefined callback class extended from base class
class PktDriver_Cb extends uvm_callback:
function new (string name = "pktDriver cb");
super. new(name);
endfunction
virtual task corrupt_packet (Packet_c pkt);
//Implement how to corrupt packet
//example -flip one bit of byte o in CRC
pkt.crc[0][0] = ~pkt. crc[0][0]
endtask
endclass: PktDriver_Cb
//Main Driver class
class PktDriver extends uvm_component;
`uvm_component_utils(pktDriver)
//Register callback class with driver
`uvm_register_cb(PktDriver, PktDriver_Cb)
function new (string name, uvm_component parent=null);
super. new(name, parent);
endfunction
virtual task run();
forever begin
seq_item_port. get_next_item(pkt);
`uvm do callbacks(PktDriver, PktDriver cb, corrupt_packet(pkt))
//other code to derive to DUT etc
end
endtask
endclass
1)定义数据包packet类
2)定义从sequence中接收此数据包并将其发送到DUT的driver类
3)定义一个从uvm_callback基类派生的driver callback类,并添加一个虚拟方法,该方法可用于注入错误或翻转数据包中的某个位。
4)使用`uvm_register_cb()宏注册callback类
5)在driver类的run()方法中接收数据包并将其发送到DUT
uvm_root类充当所有UVM组件的隐式顶层和phase控制器。用户不直接实例化uvm_-root,UVM自动创建一个uvm_root实例,用户可以通过全局实例变量uvm_top访问。
uvm_test类是用户可以实现的顶层类,并且没有提到显式父类。但是,UVM有一个称为uvmtop的特殊组件,它被指定为test类的父类。
class seqItem extends uvm_sequence_item;
`uvm_object_utils(seqItem)
logic [8-1:0] rdata;
logic wfull;
logic rempty;
rand logic [8-1:0] wdata;
logic winc, wrst_n;
logic rinc, rrst_n;
function new (string name="seqItem");
super. new(name);
endfunction
endclass
`uvm_do自动地创建、随机化 和 发送 新的对象
`uvm_send用于发送已经完成创建和随机化之后的对象
backdoor访问:通过RTL信号路径访问,不消耗仿真时间
frontdoor访问:通过数据总线协议访问,消耗仿真时间
set_config_*可以映射到相应的uvm_ config_db:
set_config_int(...)=>uvm_config_db#(uvm_bit_stream_t) :: set(cntxt, ...)
set_config_string(..)>uvm_config_db#(string) :: set(cntxt,...)
set_config_object (..)>uvm_config_db#(uvm_object) :: set(cntxt,...)
virtual class car;
static int counter =0; // shared by all instances
static local
function void increment counter();
counter ++
$display("creating item %od counter);
endfunction
function new();
increment counter(); // increment on construction
endfunction: new
endclass: car
class ahb_driver extends uvm_driver ;
semaphore sema ;
virtual task run_phase(uvm phasephase);
fork
drive_tx();
drive_tx();
join
endtask
virtual task drivetx();
//1. Gethold of a semaphore
//2. Gettransaction packet from sequencer
//3. Drivethe address phase
//4. Releasesemaphore
//5. Drive data phase
endtask
endclass
class test_sequence_c extends uvm_sequence;
test_sequencer_c p_sequencer;
clock_monitor_c my_clock_monitor;
task pre_body();
if(!$cast(p_sequencer, m_sequencer)) begin
`uvm_fatal ("Sequencer Type Mismatch:", "wrong sequencer");
end
my_clock_monitor = p_sequencer. clk_monitor;
endtask
endclass
class test_sequencer_c extends uvm_sequencer;
clock_monitor_c clk_monitor;
endclass