就我个人而言,我觉得编写sequence是在验证任何IP时最具挑战性的部分。首先需要仔细构想场景,然后coding。如果没有任何程度的复用,我们需要从头为每个场景编写一个sequence,这使得sequence难以维护和调试。
sequence的编写和调试是非常体现验证工程师编码能力的地方之一,如果每一个sequnce都有着完全不同的工作模式,那么维护起来非常痛苦。
网络上有一个段子,程序员最讨厌4件事情:
1、写文档
2、别人不写文档
3、写注释
4、别人不写注释
想象一下,如果你验证同事离职,交接给你上百个定向且诡异的测试用例或者sequence?你会不会立马想去重构。
sequences 由多个事务激励组成,在UVM中其继承自参数化类uvm_sequence。通过这些事务触发一些验证工程师希望触及的场景,而sequence的分层会创建一些更加复杂的场景激励。验证空间随着设计规模指数级上升,验证激励自然也会越来越复杂。
class usb_simple_sequence extends uvm_sequence #(usb_transfer);
rand int unsigned sequence_length;
constraint reasonable_seq_len { sequence_length < 10 };
//Constructor
function new(string name=”usb_simple_bulk_sequence”);
super.new(name);
endfunction
//Register with factory
`uvm_object_utils(usb_simple_bulk_sequence)
//the body() task is the actual logic of the sequence
virtual task body();
repeat(sequence_length)
`uvm_do_with(req, {
//Setting the device_id to 2
req.device_id == 8’d2;
//Setting transfer type to BULK
req.type == usb_transfer::BULK_TRANSFER;
})
endtask : body
endclass
在上面的sequence 中,我们试图将发送多次id为2的事务,在uvm_test中将该sequence指定为default sequence即可。
class usb_simple_bulk_test extends uvm_test;
…
virtual function void build_phase(uvm_phase phase );
…
uvm_config_db#(uvm_object_wrapper)::set(this, "sequencer_obj.
main_phase","default_sequence", usb_simple_sequence::type_id::get());
…
endfunction : build_phase
endclass
到目前为止,sequence看起来既简单又直接。但是直接的代码往往意味着麻烦的堆叠。为了确保sequence在更复杂的场景中重用,我们必须遵循一些准则或者说代码规范。
1、只在base sequence 类中的pre_start和post_start任务中raising objections和 dropping objections来管理测试用例的开始和结束。通过这种方式,能够减少每一个sequence子类中的相关phase控制代码。
task pre_start()
if(starting_phase != null)
starting_phase.raise_objection(this);
endtask : pre_start
task post_start()
if(starting_phase != null)
starting_phase.drop_objection(this);
endtask : post_start
需要注意的是,只有被定义为default sequence才会自动执行starting_phase,否则就需要手动调用了。
class usb_simple_bulk_test extends uvm_test;
usb_simple_sequence seq;
…
virtual function void main_phase(uvm_phase phase );
…
//User need to set the starting_phase as sequence start method
is explicitly called to invoke the sequence
seq.starting_phase = phase;
seq.start();
…
endfunction : main_phase
endclass
2、使用UVM configurations 机制从测试用例中获取值。在上面的示例中,没有给出控制sequence的按钮,一些都靠sequence自身的随机,这对于扩展用例非常不友好。我们可以对sequence做如下的修改,以提供更加精确的激励控制。
class usb_simple_sequence extends uvm_sequence #(usb_transfer);
rand int unsigned sequence_length;
constraint reasonable_seq_len { sequence_length < 10 };
…
virtual task body();
usb_transfer::type_enum local_type;
bit[7:0] local_device_id;
//Get the values for the variables in case toplevel
//test/sequence sets it.
uvm_config_db#(int unsigned)::get(null, get_full_name(),
“sequence_length”, sequence_length);
uvm_config_db#(usb_transfer::type_enum)::get(null,
get_full_name(), “local_type”, local_type);
uvm_config_db#(bit[7:0])::get(null, get_full_name(),?
“local_device_id”, local_device_id);
repeat(sequence_length)
`uvm_do_with(req, {
req.device_id == local_device_id;
req.type == local_type;
})
endtask : body
endclass
通过上述修改,我们对测试用例进行了控制,以配置device_id、sequence_length和type。
这里需要注意的是:
uvm_config_db#()::set
中使用的参数类型和字符串(第三个参数)应该与
uvm_config_db#()::get
中使用的类型相匹配,否则将无法正确配置。这个地方如果出错会非常痛苦,但好在不需要经常修改,痛苦一次就好。另外,这几个配置项是随机类型,相应的配置值也需要满足约束范围。
3、在创建复杂sequence的时候尽量去复用简单的sequence。例如,在下面的sequence 中顺序发送不同的sequnce(层次化和模块化,永远是编码规范之一):
class usb_complex_sequence extends uvm_sequence #(usb_transfer);
//Object of simple sequence used for sending bulk transfer
usb_simple_sequence simp_seq_bulk;
//Object of simple sequence used for sending interrupt transfer
usb_simple_sequence simp_seq_int;
…
virtual task body();
//Variable for getting device_id for bulk transfer
bit[7:0] local_device_id_bulk;
//Variable for getting device_id for interrupt transfer
bit[7:0] local_device_id_int;
//Variable for getting sequence length for bulk
int unsigned local_seq_len_bulk;
//Variable for getting sequence length for interrupt
int unsigned local_seq_len_int;
//Get the values for the variables in case top level
//test/sequence sets it.
uvm_config_db#(int unsigned)::get(null, get_full_name(),
“local_seq_len_bulk”,local_seq_len_bulk);
uvm_config_db#(int unsigned)::get(null, get_full_name(),
“local_seq_len_int”,local_seq_len_int);
uvm_config_db#(bit[7:0])::get(null, get_full_name(),
“local_device_id_bulk”,local_device_id_bulk);
uvm_config_db#(bit[7:0])::get(null, get_full_name(),
“local_device_id_int”,local_device_id_int);
//Set the values for the variables to the lowerlevel
//sequence/sequence item, which we got from
//above uvm_config_db::get.
//Setting the values for bulk sequence
uvm_config_db#(int unsigned)::set(null, {get_full_name(),”.”,
”simp_seq_bulk”}, “sequence_length”,local_seq_len_bulk);
uvm_config_db#(usb_transfer::type_enum)::set(null, {get_full_name(),
“.”,“simp_seq_bulk”} , “local_type”,usb_transfer::BULK_TRANSFER);
uvm_config_db#(bit[7:0])::set(null, {get_full_name(), “.”,
”simp_seq_bulk”}, “local_device_id”,local_device_id_bulk);
//Setting the values for interrupt sequence
uvm_config_db#(int unsigned)::set(null, {get_full_name(),”.”,
”simp_seq_int”}, “sequence_length”,local_ seq_len_int);
uvm_config_db#(usb_transfer::type_enum)::set(null, {get_full_name(),
“.”,“simp_seq_int”} , “local_type”,usb_transfer::INT_TRANSFER);
uvm_config_db#(bit[7:0])::set(null,{get_full_name(),“.”,
”simp_seq_bulk”},“local_device_id”,local_device_id_int);
`uvm_do(simp_seq_bulk)
simp_seq_bulk.get_response();
`uvm_send(simp_seq_int)
simp_seq_int.get_response();
endtask : body
endclass
END
作者:验证哥布林
原文链接:https://mp.weixin.qq.com/s/Nw6Xk6u8Rnu324IpyzYoSA
微信公众号:
推荐阅读
更多IC设计技术干货请关注IC设计技术专栏。