【IC每日一题:IC验证面试--UVM验证-2】
IC每日一题:IC验证面试--UVM验证-2
- 2.9 get_next_iterm()和try_next_item()的区别?
- 2.10 一个典型的UVM验证平台,谈一下UVM验证环境结构,各个组件之间的关系?
- 2.11 uvm组件之间通信的方式? analysis_port和其他的区别?analysis_port是否可以不连或者连接多个import?
- 2.12 uvm_tlm_anlysis_fifo类是什么,有什么端口?
- 2.13 UVM组件中常用的方法,各种phase关系,phase机制?
- 2.14 phase中的domain概念
- 2.15 run_phase和main_phase之间的关系?
- 2.16 UVM组件的通信方式TLM的接口分类和用法,peek和get的差异;
- 2.17 sequence和item(uvm_sequence, uvm_sequence_item)的分类;
- 2.18 为什么会有sequence,sequencer以及driver,为什么我们分开实现,这样做的好处是什么?
- 2.19 如何在driver中使用interface?
- 2.20 config_db的作用以及传递其使用时的参数含义
【博客首发与微信公众号《漫谈芯片与编程》,欢迎专注一下】
本篇博客开始介绍IC验证基本功–UVM-2的相关问题;
2.9 get_next_iterm()和try_next_item()的区别?
get_next_item() 和 try_next_item() 是 uvm_sequencer 类提供的两个方法,用于从 sequencer 中获取下一个transaction。
get_next_item()–阻塞调用:
这个方法会阻塞当前的执行线程,直到 sequencer 中有可用的 item。如果 sequencer 中没有 item,调用 get_next_item() 的线程会一直等待,直到有 item 被放入 sequencer。一旦获取到 item,get_next_item() 会返回这个 item,并且调用者可以继续执行。
task run_phase(uvm_phase phase);
forever begin
seq_item_port.get_next_item(req); // 阻塞调用,直到有新的事务可用
drive(req); // 处理事务
seq_item_port.item_done(); // 通知序列器事务已完成
end
endtask
try_next_item()–非阻塞调用
当驱动器调用 try_next_item() 时,如果当前没有可用的事务,它会立即返回,不会阻塞。try_next_item() 返回一个布尔值,表示是否成功获取到事务。
task run_phase(uvm_phase phase);
forever begin
if (seq_item_port.try_next_item(req)) { // 非阻塞调用
drive(req); // 处理事务
seq_item_port.item_done(); // 通知序列器事务已完成
} else {
// 没有事务时执行其他任务
do_something_else();
}
end
endtask
典型应用场景:
- get_next_item():当你希望驱动器总是等待事务并按顺序处理时,使用 get_next_item()。这是最常见的用法,特别是在简单的驱动器实现中。
- try_next_item():当你希望驱动器在没有事务时能够执行其他任务,或者在多事务流场景下需要灵活处理时,使用 try_next_item()。这在需要更高的响应性和灵活性的场景中非常有用
2.10 一个典型的UVM验证平台,谈一下UVM验证环境结构,各个组件之间的关系?
UVM验证环境的基本组成:
环境(uvm_env):包含所有验证组件。
代理(uvm_agent):包含驱动器、监视器和序列器,与DUT进行交互。
驱动器(uvm_driver):将事务转换为信号,驱动到DUT。
监视器(uvm_monitor):监控DUT的输入和输出,并将信号转换为事务。
序列器(uvm_sequencer):从序列中获取事务并传递给驱动器。
序列(uvm_sequence):生成事务。
参考模型 (reference_model): 参考模型是一个功能性的模型,它独立于DUT实现同样的功能,参考模型的输出可以用来与DUT的实际输出进行比较,从而判断DUT的功能是否正确。
记分板(uvm_scoreboard):检查DUT的输出是否符合预期。
虚拟接口(virtual interface):连接UVM组件与DUT。
寄存器模型(uvm_reg_block):描述DUT中的寄存器和寄存器块。
在这里重点介绍一下参考模型的Demo:
class my_reference_model extends uvm_component;
`uvm_component_utils(my_reference_model)
uvm_analysis_imp #(my_transaction, my_reference_model) imp;
function new(string name, uvm_component parent);
super.new(name, parent);
imp = new("imp", this);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
endfunction
function void write(my_transaction tr);
// 执行与DUT相同的操作
// 例如:计算期望的结果
my_transaction exp_tr = tr.clone();
exp_tr.data = perform_function(tr.data); // 假设有一个函数 perform_function
// 将期望的结果发送到记分板
`uvm_info("REF_MODEL", $sformatf("Expected data: %h", exp_tr.data), UVM_HIGH)
// 这里假设记分板已经连接到了参考模型的分析端口
endfunction
// 假设这是一个简单的函数,模拟DUT的功能
function bit [7:0] perform_function(bit [7:0] input_data);
// 实现一些功能,例如简单的加法
return input_data + 1;
endfunction
endclass
class my_scoreboard extends uvm_scoreboard;
`uvm_component_utils(my_scoreboard)
uvm_analysis_imp #(my_transaction, my_scoreboard) imp;
uvm_tlm_analysis_fifo #(my_transaction) exp_fifo;
function new(string name, uvm_component parent);
super.new(name, parent);
imp = new("imp", this);
exp_fifo = new("exp_fifo", this);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
endfunction
function void write(my_transaction tr);
// 将DUT的实际输出放入FIFO
imp.write(tr);
endfunction
function void check_phase(uvm_phase phase);
my_transaction act_tr, exp_tr;
while (exp_fifo.try_get(exp_tr)) begin
if (!imp.get(act_tr)) begin
`uvm_error("SB", "No actual transaction to compare with expected")
continue;
end
if (act_tr.data !== exp_tr.data) begin
`uvm_error("SB", $sformatf("Data mismatch: Expected %h, Actual %h", exp_tr.data, act_tr.data))
end else begin
`uvm_info("SB", $sformatf("Data match: %h", act_tr.data), UVM_HIGH)
end
end
endfunction
endclass
class my_env extends uvm_env;
`uvm_component_utils(my_env)
my_agent agent;
my_scoreboard scoreboard;
my_reference_model ref_model;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
agent = my_agent::type_id::create("agent", this);
scoreboard = my_scoreboard::type_id::create("scoreboard", this);
ref_model = my_reference_model::type_id::create("ref_model", this);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
agent.monitor.ap.connect(scoreboard.imp);
agent.monitor.ap.connect(ref_model.imp);
endfunction
endclass
2.11 uvm组件之间通信的方式? analysis_port和其他的区别?analysis_port是否可以不连或者连接多个import?
UVM组件之间的通信方式:
1.TLM ports and exports:
uvm_port 和 uvm_export: 用于请求-响应式的通信。例如,uvm_blocking_put_port 和 uvm_blocking_get_port。
uvm_analysis_port 和 uvm_analysis_imp: 用于单向数据流的通信,主要用于监控和收集数据。
2.Phases and Callbacks
通过 UVM 的 phase 机制,可以在特定的 phase 中进行组件间的通信。回调(callbacks)也可以用于在特定事件发生时进行通信。
3.uvm_config_db
通过 uvm_config_db 机制,可以在组件之间传递配置信息;
uvm_analysis_port可以不连接任何uvm_analysis_imp–这种情况下,发送的数据将被忽略
uvm_analysis_port可以连接多个uvm_analysis_imp,允许多个组件同时接收相同的数据;是通过uvm_analysis_port::connect方法来实现的
uvm_analysis_port和uvm_analysis_imp的实现示例:
// 定义一个简单的事务类
class my_transaction extends uvm_sequence_item;
rand int data;
`uvm_object_utils(my_transaction)
function new(string name = "my_transaction");
super.new(name);
endfunction
endclass
// 定义一个产生事务的组件
class producer extends uvm_component;
`uvm_component_utils(producer)
uvm_analysis_port #(my_transaction) ap;
function new(string name, uvm_component parent);
super.new(name, parent);
ap = new("ap", this);
endfunction
task run_phase(uvm_phase phase);
my_transaction tr;
tr = my_transaction::type_id::create("tr");
tr.randomize();
ap.write(tr); // 将事务发送到分析端口
endtask
endclass
// 定义一个接收事务的组件
class consumer extends uvm_component;
`uvm_component_utils(consumer)
uvm_analysis_imp #(my_transaction, consumer) imp;
function new(string name, uvm_component parent);
super.new(name, parent);
imp = new("imp", this);
endfunction
function void write(my_transaction t);
`uvm_info("CONSUMER", $sformatf("Received data: %0d", t.data), UVM_HIGH)
endfunction
endclass
// 定义顶层环境
class env extends uvm_env;
`uvm_component_utils(env)
producer prod;
consumer cons1, cons2;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
prod = producer::type_id::create("prod", this);
cons1 = consumer::type_id::create("cons1", this);
cons2 = consumer::type_id::create("cons2", this);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
prod.ap.connect(cons1.imp); // 连接到第一个消费者
prod.ap.connect(cons2.imp); // 连接到第二个消费者
endfunction
endclass
// 测试用例
module top;
initial begin
run_test();
end
endmodule
2.12 uvm_tlm_anlysis_fifo类是什么,有什么端口?
uvm_tlm_analysis_fifo 是 UVM(Universal Verification Methodology)提供的一个类,它结合了 TLM(Transaction Level Modeling)和 FIFO(First-In-First-Out)的功能,用于在验证环境中存储和转发事务。这个类特别适用于需要缓存事务数据的场景,例如在监控器和记分板之间传递数据。
uvm_tlm_analysis_fifo的主要端口:
- uvm_analysis_imp:
用于接收事务数据。这是一个单向的数据流,通常用于监控器将数据发送到 FIFO。 - uvm_get_port:
用于从 FIFO 中取出事务数据。这是一个请求-响应式的端口,通常用于记分板或其他组件从 FIFO 中获取数据
使用uvm_tlm_analysis_fifo的端口:
//==transaction
class my_transaction extends uvm_sequence_item;
rand int data;
`uvm_object_utils(my_transaction)
function new(string name = "my_transaction");
super.new(name);
endfunction
endclass
//==monitor
class my_monitor extends uvm_monitor;
`uvm_component_utils(my_monitor)
uvm_analysis_port #(my_transaction) ap;
virtual my_if vif;
function new(string name, uvm_component parent);
super.new(name, parent);
ap = new("ap", this);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
`uvm_fatal("NOVIF", "Failed to get vif")
endfunction
task run_phase(uvm_phase phase);
my_transaction tr;
forever begin
@(posedge vif.clk);
if (vif.valid && vif.ready) begin
tr = my_transaction::type_id::create("tr");
tr.data = vif.data;
ap.write(tr); // 将事务发送到分析端口
end
end
endtask
endclass
//==scoreboard
class my_scoreboard extends uvm_scoreboard;
`uvm_component_utils(my_scoreboard)
uvm_tlm_analysis_fifo #(my_transaction) fifo;
function new(string name, uvm_component parent);
super.new(name, parent);
fifo = new("fifo", this);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
endfunction
task run_phase(uvm_phase phase);
my_transaction tr;
forever begin
fifo.get(tr); // 从 FIFO 中取出事务
`uvm_info("SCOREBOARD", $sformatf("Received data: %0d", tr.data), UVM_HIGH)
end
endtask
endclass
//==env
class my_env extends uvm_env;
`uvm_component_utils(my_env)
my_monitor monitor;
my_scoreboard scoreboard;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
monitor = my_monitor::type_id::create("monitor", this);
scoreboard = my_scoreboard::type_id::create("scoreboard", this);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
monitor.ap.connect(scoreboard.fifo.analysis_export); // 连接监控器和 FIFO
endfunction
endclass
//==test
class my_test extends uvm_test;
`uvm_component_utils(my_test)
my_env env;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
env = my_env::type_id::create("env", this);
endfunction
endclass
//==module
module top;
initial begin
run_test("my_test");
end
endmodule
uvm_tlm_analysis_fifo 可以有效地在监控器和记分板之间传递和缓存事务数据。
2.13 UVM组件中常用的方法,各种phase关系,phase机制?
组件的生命周期是通过一系列的phase来管理的。这些phase包括构建阶段(Build Phase)、连接阶段(Connect Phase)、运行阶段(Run Phase)等。
new():构造函数,用于创建组件实例。
build_phase():构建阶段,用于创建和配置组件的子组件。
connect_phase():连接阶段,用于连接组件之间的端口和接口。
pre_run_phase():在仿真运行之前的一些配置准备阶段;
run_phase():运行阶段,执行组件的主要功能,如发送事务、接收事务等。
report_phase():仿真结束后,报告阶段,用于生成验证结果的报告。
Phase机制:UVM的phase机制是通过一个全局的phase控制器来管理的。这个控制器负责调度和执行各个phase。在每个phase开始时,控制器会调用所有组件的相应phase方法。组件可以通过重载这些方法来实现自定义的行为。
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// 创建子组件
my_child = my_child::type_id::create("my_child", this);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
// 连接端口
my_child.my_port.connect(my_other_child.my_export);
endfunction
function void start_of_simulation_phase(uvm_phase phase);
super.start_of_simulation_phase(phase);
// 初始化某些全局变量
global_var = 0;
endfunction
task run_phase(uvm_phase phase);
super.run_phase(phase);
// 生成激励
repeat (10) begin
my_transaction tr = my_transaction::type_id::create("tr");
tr.randomize();
my_sequencer.starting_sequence.put(tr);
end
endtask
function void report_phase(uvm_phase phase);
super.report_phase(phase);
// 生成报告
uvm_report_server::get_server().write_report_messages();
endfunction
2.14 phase中的domain概念
domain 是一个用于管理和同步不同时钟域之间的信号和事件的概念。在不同的模块可能运行在不同的时钟频率上,这就需要一种机制来确保跨时钟域的信号传输和事件处理是正确和可靠的;
domain 通常与 clocking block 一起使用,clocking block 是一种特殊的代码块,用于描述信号在时钟边沿上的行为。通过将信号声明在 clocking block 中,可以确保信号的采样和驱动是同步的,并且符合特定的时钟约束。
// 定义一个时钟域
clocking cb @(posedge clk);
input data;
output ack;
endclocking
// 在组件中使用时钟域
class MyComponent extends uvm_component;
virtual clocking cb;
function new(string name, uvm_component parent);
super.new(name, parent);
// 初始化时钟域
cb = new("cb", this);
endfunction
task run_phase(uvm_phase phase);
forever begin
// 在时钟上升沿采样输入数据
@(cb);
data = cb.data;
// 处理数据
//...
// 在时钟上升沿驱动输出信号
cb.ack <= 1'b1;
@(cb);
cb.ack <= 1'b0;
end
endtask
endclass
在这个示例中,clocking block cb 定义了一个时钟域,其中包含一个输入信号 data 和一个输出信号 ack。在 MyComponent 类中,run_phase 任务使用 @(cb) 来等待时钟上升沿,然后采样输入数据并驱动输出信号。
2.15 run_phase和main_phase之间的关系?
run_phase和main phase(动态运行)都是task phase,且是并行运行的,后者称为动态运行(run-time)的phase。
如果想执行一些耗费时间的代码,那么要在此phase下任意一个component中至少提起一次objection,这个结论只适用于12个run-time的phase。对于run_phase则不适用,由于run_phase与动态运行的phase是并行运行的,如果12个动态运行的phase有objection被提起,那么run_phase根本不需要raise_objection就可以自动执行。
2.16 UVM组件的通信方式TLM的接口分类和用法,peek和get的差异;
事务级建模(Transaction Level Modeling,TLM)是一种用于在验证环境中进行组件间通信的方法。TLM 接口和方法提供了一种标准化的方式来实现组件之间的通信。
UVM提供的TLM接口分类:put/get 接口和 analysis 接口;
put/get 接口:
uvm_put_port/uvm_put_imp:
用于将数据从一个组件传递到另一个组件,发送方不需要接收方的响应。
uvm_get_port/uvm_get_imp:
用于从另一个组件请求数据,发送方需要接收方的响应。
uvm_peek_port/uvm_peek_imp:
用于查看另一个组件中的数据,但不移除数据。类似于 get,但不会消耗数据。
uvm_transport_port/uvm_transport_imp:
用于双向通信,发送方发送数据并接收响应。
analysis 接口:
uvm_analysis_port/uvm_analysis_imp:
用于单向数据流,通常用于监控和收集数据。发送方不需要接收方的响应。
class my_transaction extends uvm_sequence_item;
rand int data;
`uvm_object_utils(my_transaction)
function new(string name = "my_transaction");
super.new(name);
endfunction
endclass
class producer extends uvm_component;
`uvm_component_utils(producer)
uvm_put_port #(my_transaction) put_port;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
put_port = new("put_port", this);
endfunction
task run_phase(uvm_phase phase);
my_transaction tr;
tr = my_transaction::type_id::create("tr");
tr.randomize();
put_port.put(tr); // 将事务发送到 put_port
endtask
endclass
class consumer extends uvm_component;
`uvm_component_utils(consumer)
uvm_get_imp #(my_transaction, consumer) get_imp;
function new(string name, uvm_component parent);
super.new(name, parent);
get_imp = new("get_imp", this);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
endfunction
function void get(my_transaction tr);
`uvm_info("CONSUMER", $sformatf("Received data: %0d", tr.data), UVM_HIGH)
endfunction
endclass
class my_env extends uvm_env;
`uvm_component_utils(my_env)
producer prod;
consumer cons;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
prod = producer::type_id::create("prod", this);
cons = consumer::type_id::create("cons", this);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
prod.put_port.connect(cons.get_imp); // 连接 put_port 和 get_imp
endfunction
endclass
class my_test extends uvm_test;
`uvm_component_utils(my_test)
my_env env;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
env = my_env::type_id::create("env", this);
endfunction
endclass
module top;
initial begin
run_test("my_test");
end
endmodule
【总结】
put/get 接口:用于组件之间的数据传递。put 用于发送数据,get 用于接收数据并消耗数据。peek 用于查看数据但不消耗数据。
analysis 接口:用于单向数据流,通常用于监控和收集数据。uvm_analysis_port 用于发送数据,uvm_analysis_imp 用于接收数据。
2.17 sequence和item(uvm_sequence, uvm_sequence_item)的分类;
uvm_sequence_item(transaction)
uvm_sequence_item 是 UVM 中表示单个事务的基本类。uvm_sequence_item 类通常包含事务的数据字段,如地址、数据、命令类型等。事务类可以包含随机化约束,用于生成符合特定条件的事务。
class my_transaction extends uvm_sequence_item;
rand bit [31:0] address;
rand bit [⅛:0] data;
rand bit [2:0] command;
constraint c_address {
address inside { [0:1023] };
}
constraint c_data {
data dist { [0:255] := 7, [256:511] := ˜3 };
}
constraint c_command {
command inside { READ, WRITE, NOP };
}
`uvm_object_utils_begin(my_transaction)
`uvm_field_int(address, UVM_DEFAULT)
`uvm_field_int(data, UVM_DEFAULT)
`uvm_field_int(command, UVM_DEFAULT)
`uvm_object_utils_end
function new(string name = "my_transaction");
super.new(name);
endfunction
endclass
uvm_sequence
uvm_sequence 是 UVM 中用于控制事务生成和发送的类。它负责创建事务并将其发送到 uvm_sequencer,后者再将事务传递给 uvm_driver。
class read_sequence extends uvm_sequence #(read_transaction);
`uvm_object_utils(read_sequence)
function new(string name = "read_sequence");
super.new(name);
endfunction
virtual task body();
read_transaction tr;
repeat (10) begin
`uvm_do(tr) // 生成并发送读事务
end
endtask
endclass
class write_sequence extends uvm_sequence #(write_transaction);
`uvm_object_utils(write_sequence)
function new(string name = "write_sequence");
super.new(name);
endfunction
virtual task body();
write_transaction tr;
repeat (10) begin
`uvm_do(tr) // 生成并发送写事务
end
endtask
endclass
class mixed_sequence extends uvm_sequence #(read_transaction, write_transaction);
`uvm_object_utils(mixed_sequence)
function new(string name = "mixed_sequence");
super.new(name);
endfunction
virtual task body();
read_transaction read_tr;
write_transaction write_tr;
repeat (5) begin
`uvm_do(read_tr) // 生成并发送读事务
`uvm_do(write_tr) // 生成并发送写事务
end
endtask
endclass
【总结】
item 是事务的基本单元,包含了验证环境中事务的最小信息。
sequence 是一系列 item 的集合,用于构建和管理验证环境中的测试场景。
通过合理地分类和设计 uvm_sequence 和 uvm_sequence_item,从而可以构建灵活且高效的激励生成机制,提高验证平台的可重用性和可维护性;
2.18 为什么会有sequence,sequencer以及driver,为什么我们分开实现,这样做的好处是什么?
首先介绍一下各自的职责:
sequence:负责生成事务(transaction),并将其发送到sequencer。sequence可以包含复杂的逻辑,如随机化、条件分支等,以生成多样化的测试场景。
sequencer:作为sequence和driver之间的桥梁,负责接收sequence发送的事务,并将其转发给driver。sequencer还可以对事务进行仲裁和调度,确保事务按照正确的顺序和时间发送给driver。
driver:负责将接收到的事务转换为实际的信号电平,并驱动到DUT(Device Under Test)的接口上。driver通常与DUT的接口协议紧密相关,需要实现具体的信号驱动逻辑。
好处:
1.职责单一:
事务生成与硬件驱动分离:sequence 专注于事务生成,而 driver 专注于硬件驱动。这种分离使得事务生成逻辑和硬件驱动逻辑可以独立开发和调试。
仲裁与调度:sequencer 负责事务的仲裁和调度,使得 sequence 和 driver 不必关心这些细节。这种分离使得 sequence 和 driver 可以专注于各自的核心功能。
2.灵活性和可拓展性:
可以轻松地添加新的 sequence 来生成不同的激励模式。
可以通过配置 sequencer 来改变事务的调度策略。
可以通过嵌套 sequence 来构建复杂的激励模式。
可以通过添加新的 driver 来支持不同的 DUT 接口。
3.易于调试和维护:
隔离问题:
如果某个组件出现问题,可以单独调试该组件,而不影响其他组件。
清晰的责任划分:
每个组件都有明确的职责,使得问题定位更加容易。
代码简洁:
每个组件的代码量相对较少,使得代码更加简洁和易于阅读。
//====transaction
class my_transaction extends uvm_sequence_item;
rand bit [31:0] address;
rand bit [⅛:0] data;
rand bit [2:0] command;
`uvm_object_utils_begin(my_transaction)
`uvm_field_int(address, UVM_DEFAULT)
`uvm_field_int(data, UVM_DEFAULT)
`uvm_field_int(command, UVM_DEFAULT)
`uvm_object_utils_end
function new(string name = "my_transaction");
super.new(name);
endfunction
endclass
//==sequence
class my_sequence extends uvm_sequence #(my_transaction);
`uvm_object_utils(my_sequence)
function new(string name = "my_sequence");
super.new(name);
endfunction
virtual task body();
my_transaction tr;
repeat (10) begin
tr = my_transaction::type_id::create("tr");
start_item(tr);
assert(tr.randomize() with { address inside {[0:1023]}; });
finish_item(tr);
end
endtask
endclass
//==driver
class my_driver extends uvm_driver #(my_transaction);
`uvm_component_utils(my_driver)
virtual my_if vif;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
`uvm_fatal("NOVIF", "Failed to get vif")
endfunction
task run_phase(uvm_phase phase);
my_transaction tr;
forever begin
seq_item_port.get_next_item(tr);
drive_transaction(tr);
seq_item_port.item_done();
end
endtask
task drive_transaction(my_transaction tr);
vif.cb.address <= tr.address;
vif.cb.data <= tr.data;
vif.cb.command <= tr.command;
@(vif.cb);
endtask
endclass
//==sequencer
class my_sequencer extends uvm_sequencer #(my_transaction);
`uvm_component_utils(my_sequencer)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
endclass
//==env
class my_env extends uvm_env;
`uvm_component_utils(my_env)
my_sequencer seqr;
my_driver drv;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
seqr = my_sequencer::type_id::create("seqr", this);
drv = my_driver::type_id::create("drv", this);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
drv.seq_item_port.connect(seqr.seq_item_export);
endfunction
endclass
通过这种分离的设计,UVM验证环境可以更加灵活、高效地进行事务级验证,同时提高代码的可维护性和可重用性。
2.19 如何在driver中使用interface?
在driver中进行virtual interface声明,并接收来自顶层通过config_db传递的interface。
// 定义一个简单的interface
interface my_interface (input clk, input rst_n);
logic [7:0] data;
logic valid;
logic ready;
endinterface
// driver的实现
class my_driver extends uvm_driver #(my_transaction);
// 实例化interface
virtual my_interface vif;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
// 在build_phase中获取interface的句柄
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db#(virtual my_interface)::get(this, "", "vif", vif)) begin
`uvm_error("my_driver", "Failed to get vif handle")
end
endfunction
// 在run_phase中使用interface与DUT通信
task run_phase(uvm_phase phase);
my_transaction tr;
forever begin
seq_item_port.get_next_item(tr);
// 使用interface中的信号驱动DUT
vif.data <= tr.data;
vif.valid <= 1'b1;
@(posedge vif.clk);
while (!vif.ready) @(posedge vif.clk);
vif.valid <= 1'b0;
seq_item_port.item_done();
end
endtask
endclass
2.20 config_db的作用以及传递其使用时的参数含义
config_db 在 UVM(Universal Verification Methodology)是一个配置数据库,用于在验证环境中的不同组件之间传递配置信息–来配置和共享参数;
uvm_config_db 提供了两种主要的方法来设置和获取配置信息:uvm_config_db#(T)::set uvm_config_db#(T)::get;
// 在测试用例中设置配置信息
class my_test extends uvm_test;
my_agent my_agent_inst;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
my_agent_inst = my_agent::type_id::create("my_agent_inst", this);
// 设置配置信息
uvm_config_db#(int)::set(this, "my_agent_inst", "num_transactions", 100);
endfunction
endclass
// 在代理(agent)中获取配置信息
class my_agent extends uvm_agent;
int num_transactions;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// 获取配置信息
if (!uvm_config_db#(int)::get(this, "", "num_transactions", num_transactions)) begin
`uvm_error("my_agent", "Failed to get num_transactions config")
end
// 使用配置信息
`uvm_info("my_agent", $sformatf("Number of transactions: %0d", num_transactions), UVM_LOW)
endfunction
endclass
在build_phase中的执行顺序是从上到下进行执行的;