目录
sequence机制在UVM验证平台中扮演什么样的角色?
UVM验证平台中,sequence相当于子弹,sequencer相当于弹夹,driver相当于手枪。主从关系而言,当driver请求发射的时候告诉弹夹准备好子弹。sequence主要负责transaction的产生,UVM平台需要对DUT施加不同的激励,而激励的不同体现在transaction,本质上是通过不同case中不同的sequence产生不同的激励。
drvier完全可以产生激励,并对DUT进行驱动,为什么还要多此一举,加入sequence机制,将激励的产生和驱动做以隔离?
类似UVM的phase机制,验证平台将运行过程隔离成了不同的phase,在特定时间完成特定工作,提高易用性,提高效率。sequence机制隔离激励的产生和驱动继承了这种设计哲学,使得驱动组件可以被重用。
举个例子来说明为什么驱动组件被重用了:
当我们决定要用driver产生并驱动激励,main_phase中包括了DUT的运行,因此我们的想法是在main_phase中产生各种激励。
task driver::main_phase(uvm_phase phase);
transaction tr;
super.main_phase(phase);
phase.raise_objection(tr);
for(int i = 0; i < 10; i++) begin
tr = new();
assert(tr.randomize);
drive(tr);
end
phase.drop_objection(tr);
endtask
这种写法使得driver产生并驱动了transaction level的激励,但是当我们需要驱动不同的激励时,我们必须对driver的main_phase任务重新写,导致重用性很差。因此自然有了把产生什么样的激励这部分隔离出去,留下可以重用的部分。
自然的想法是通过函数的方法:
task driver::main_phase(uvm_phase phase);
transaction tr;
super.main_phase(phase);
phase.raise_objection(tr);
for(int i = 0; i < 10; i++) begin
gen_pkt(tr);
drive(tr);
end
phase.drop_objection(tr);
endtask
function gen_pkt(ref transaction tr);
tr = new;
assert(tr.randomize);
endfunction
需要不同的激励时呢?改函数吗?很麻烦。我们需要在driver组件不改变的情况下能让函数调用不同的内容。因此引入了sequence的机制。sequence机制的大致过程为 在不同的case中,把不同的sequence设置为sequencer的main_phase中的defalut_sequence,当sequencer执行到main_phase,如果发现有default_phase,那么就会启动sequence。在一个case的env.agt.sqr中的build_phase,用config_db设置对应的sequence及启动的位置(main_phase)。
uvm_config_db#(uvm_object_wrapper)::set(this,"env.agt.sqr.main_phase","default_sequence",my_sequence::type_id::get)
sequence的执行与启动
有一个sequence如下:
class my_sequence extends uvm_sequence #(transaction);
`uvm_object_utils(my_sequence)
transaction tr;
extern function new(string name = "my_sequence");
virtual task body();
repeat(10) begin
`uvm_do(tr)
end
#100;
endtask
end class
function my_sequence::new(string name = "my_sequence");
super.new(name);
endfunction
启动如上的sequence:实例化并调用start任务,对于start任务传入sequencer参数,start任务中肯定执行了sequence的body任务
my_sequence my_seq;
my_seq = my_sequence::type_id::creat("my_seq");
my_seq.start(sequencer)
根据以上,sequencer中main_phase中先通过config_db得到defalut_sequence,然后实例化并调用start任务,start的参数是this。
通过sequence来控制验证平台的关闭
在激励的产生部分没有隔离出driver之前,driver通过raise_objection 和 drop_objection 控制平台的关闭,当sequence从driver中隔离出去后,driver只保留有驱动的功能,它向sequencer索要transaction,要到了就将transaction驱动到DUT,否则一直等待。因此应该将控制平台关闭的功能转移到sequence中,但 raise_objection 和 drop_objection 是 phase 的函数,而 sequence 继承于uvm_object 而不是 uvm_component。做法是在 sequence 中加入一个指向 phase 的指针,在sequencer 的 main_phase 中的 phase 赋给这个指针,这个指针的名字是 starting_phase :
task body();
if(starting_phase != null)
starting_phase.raise_objection(this);
...
if(starting_phase != null)
starting_phase.drop_objection(this);
endtask
保险起见,先判断starting_phase这个指针不为空
`uvm_do宏
一个sequence被启动后,UVM会自动执行body任务,body任务和之前的 gen_pkt function 是一样的作用,不同点在于 gen_pkt function 函数是通过 ref 引用的方式给 driver 传递 transaction ,而sequence 和 driver 之间要进行通信。
一个transaction的产生有四步:
task body();
transaction tr;
tr = transaction::type_id::creat("tr");//实例化
start_item(tr);
assert(tr.randomize);
finish_item(tr);
endtask
当driver向sequencer进行请求,sequencer才会启动sequence产生一个transaction,产生完成后等待driver把item取走,driver会显式调用seq_item_port.item_done(),此函数被调用后,finish_item才会返回,一个transaction的传输才算完成。而这个过程中只有第三步是变化的,因此引入了`uvm_do系列宏。以上步骤简化为:
task body();
transaction tr;
`uvm_do(tr)
endtask
一个`uvm_dowith的宏
task body();
transaction tr;
`uvm_do_with(tr, {tr.en == 1;})
endtask
以上会将第三步替换为
assert(tr.randomize() with {tr.en == 1;});
再考虑一种情况,如果我们有30个case,每个case中的sequence都要发10中transaction,但在30个case中仅限于这10种transaction,如果要分别写sequence的话,很麻烦而且代码很多都是重复的,为了解决这个问题,uvm_do宏的参数也可以为sequence类型。
class seq0 extends uvm_sequence #(transaction);
`uvm_object_utils(seq0)
task body();
transaction tr;
`uvm_do_with(tr, {tr.data == 16'd0;tr.en == 1;})
endtask
endclass
class seq1 extends uvm_sequence #(transaction);
`uvm_object_utils(seq1)
task body();
transaction tr;
`uvm_do_with(tr, {tr.data == 16'd2;tr.en == 1;})
endtask
endclass
class seq extends uvm_sequence #(transaction);
`uvm_object_utils(seq)
task body();
seq0 s1;
seq1 s2;
`uvm_do(s1)
`uvm_do(s2)
endtask
endclass
virtual sequence & virtual sequencer
考虑这样一种情况,验证平台中有两个driver,等待一个driver发送给DUT激励后另一个driver再发送激励,有先后顺序,自然的想法是通过全局变量定义一个事件,在两个sequence的body中进行触发和等待,但全局变量是危险的操作,不建议。因此引入了virtual sequence 和 virtual sequencer 的概念来调度有先后顺序的sequence。
要使用virtual sequence,一般有一个virtual sequencer包含指向其它sequencer的指针。而virtual sequencer不同于虚拟类或者虚拟方法,它本质上只是一个component。
class vsequencer extends uvm_sequencer;
`uvm_component_utils(vsequnecer)
cpu_sequencer cpu_sqr;
mac_sequencer mac_sqr;
endclass
在test中实例化virtual sequencer 并将 sequencer 赋给virtual sequencer 的指针。
class base_test extends uvm_test;
env env_inst;
vsequencer vsqr;
function bulid_phase(uvm_phase phase);
vsqr = vsquencer::type_id::creat("vsqr", this);
endfunction
function connect_phase(uvm_phase phase);
vsqr.cpu_sqr = env_inst.cpu_agt.cpu_sqr;
vsqr.mac_sqr = env_inst.mac_agt.mac_sqr;
endfunction
endclass
virtual sequence如何启动sequence?
class vseq extends uvm_sequence;
`uvm_object_utils(vseq)
task body();
cpu_seq cseq;
mac_seq mseq;
`uvm_do_on(cseq, p_sequencer.cpu.sqr)
`uvm_do_on(mseq, p_sequencer.mac.sqr)
endtask
`uvm_declare_p_sequencer(vsequencer)
endclass
`uvm_do_on将启动的sequence产生的transaction交给了p_sequencer指向的任意sqr,而p_sequencer是指向virtual sequencer的指针,也需要一句宏命令显示定义,整个过程中,virtual sequence 扮演了调度sequence顺序的角色,virtual sequencer 扮演了整合所有squencer的角色,他们有三个特点:
- virtual sequencer 控制其他sequencer
- virtual sequencer 不处理transaction
- virtual sequencer 不连接driver
这也是为什么取名为virtual 的原因。
在以前,启动sequence时,首先在test中将sequence通过config_db设置为对应sequencer 的 defalut_sequence,然后再sequencer的main_phase中通过config_db取到sequence,再通过start任务启动。当有多个sequence时不免要进行多次config_db的操作,而通过virtual sequence,则只需要一次操作:
`uvm_config_db#(uvm_object_wrapper)::set("this", "env.vsqr.main_phase", "default sequence", type_id_get());