在这篇博客中,我描述了在编写序列时必须采取的必要步骤,以确保它可以重用。就我个人而言,我觉得编写序列是验证任何IP最具挑战性的部分。编写序列需要仔细规划,否则我们最终会从头开始为每个场景编写一个序列。这使得序列难以维护和调试。
众所周知,序列由几个数据项组成,它们共同构成了一个有趣的场景。序列可以是分层的,从而创建更复杂的方案。在最简单的形式中,序列应该是 uvm_sequence 基类的派生,方法是指定请求和响应项类型参数,并使用要执行的特定方案实现 body 任务。
类 usb_simple_sequence 扩展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
在上面的顺序中,我们尝试将 USB 批量传输发送到 id 为 2 的设备。测试编写者可以通过将此序列分配给顶级测试中序列器的默认序列来调用此序列。
类usb_simple_bulk_test扩展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
到目前为止,事情看起来简单明了。为了确保序列可重用于更复杂的场景,我们必须遵循更多准则。
首先,通过在序列类中pre_start和post_start任务中提出和放弃异议来管理测试结束非常重要。这样,我们只在最上面的序列中提出和放弃异议,而不是对所有子序列都这样做。
t问 pre_start()
if(starting_phase != null)
starting_phase.raise_objection(this);
endtask : pre_start
t问 post_start()
if(starting_phase != null)
starting_phase.drop_objection(this);
endtask : post_start
请注意,starting_phase仅为作为特定阶段的默认序列启动的序列定义。如果已通过调用序列的 start 方法显式启动它,则用户负责设置starting_phase。
类usb_simple_bulk_test扩展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
结束类
使用 UVM 配置从顶级测试中获取值。在上面的示例中,没有为测试编写者提供可控性,因为序列不使用配置从顶级测试或序列中获取值(将使用此序列来构建复杂场景)。修改序列以更好地控制使用此简单序列的顶级测试或序列。
类 usb_simple_sequence 扩展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
结束类
通过上述修改,我们已经控制了顶级测试或序列来修改device_id、sequence_length和类型。这里需要注意的几点:uvm_config_db#()::set 中使用的参数类型和字符串(第三个参数)应该与 uvm_config_db#()::get 中使用的类型匹配。确保使用确切的数据类型“设置”和“获取”。否则,值将无法正确设置,调试将成为一场噩梦。
上述序列的一个问题是:如果 usb_transfer 类中对device_id或类型有任何约束,那么这将限制顶级测试或序列以确保它在约束范围内。
例如,如果usb_transfer类中的device_id存在约束,将其约束为低于 10,则顶级测试或序列应在此范围内约束它。如果顶级测试或序列将其设置为类似 15 的值(超过 10),则在运行时将看到约束失败。
有时,顶级测试或序列可能需要完全控制,并且可能不希望启用在较低级别的序列或数据项中定义的约束。需要这样做的一个示例是阴性测试:- 主机希望确保设备不会响应device_id大于 10 的传输,因此希望发送device_id 15 的传输。因此,为了完全控制顶级测试或序列,我们可以修改正文任务,如下所示:
虚拟任务正文();
usb_transfer::type_enum local_type;
bit[7:0] local_device_id;
int status_seq_len = 0;
int status_type = 0;
int status_device_id = 0;
status_seq_len = uvm_config_db#(int unsigned)::get(null,
get_full_name(), “sequence_length”, sequence_length);
status_type = uvm_config_db#(usb_transfer::type_enum)::get(null,
get_full_name(),“local_type”,local_type);
status_device_id = uvm_config_db#(bit[7:0])::get(null,
get_full_name(), “local_device_id”,local_device_id);
//If status of uvm_config_db::get is true then try to use the values
// set by toplevel test or sequence instead of the random value.
if(status_device_id || status_type)
begin
`uvm_create(req)
req.randomize();
if(status_type)
begin
//Using the value set by top level test or sequence
//instead of the random value.
req.type = local_type;
end
if(status_device_id)
begin
//Using the value set by top level test or sequence
//instead of the random value.
req.device_id = local_device_id;
end
end
repeat(sequence_length)
`uvm_send(req)
结束任务:正文
使用'uvm_do_with时总是要小心的,因为它会在较低级别的序列或序列项中的任何现有约束之上添加约束。
另请注意,如果您有更多变量要“设置”和“获取”,那么我建议您创建对象并在创建的对象中设置值,然后使用顶级测试/序列中的uvm_config_db设置此对象(而不是显式设置此对象中的每个变量)。这样,我们可以通过不搜索每个变量(当我们执行 uvm_config_db::get 时)来提高运行时性能,而是使用该对象一次性获取所有变量。
虚拟任务正文();
usb_transfer::type_enum local_type;
bit[7:0] local_device_id;
int status_seq_len = 0;
int status_type = 0;
int status_device_id = 0;
status_seq_len = uvm_config_db#(int unsigned)::get(null,
get_full_name(), “sequence_length”, sequence_length);
status_type = uvm_config_db#(usb_transfer::type_enum)::get(null,
get_full_name(),“local_type”,local_type);
status_device_id = uvm_config_db#(bit[7:0])::get(null,
get_full_name(), “local_device_id”,local_device_id);
//If status of uvm_config_db::get is true then try to use the values
// set by toplevel test or sequence instead of the random value.
if(status_device_id || status_type)
begin
`uvm_create(req)
req.randomize();
if(status_type)
begin
//Using the value set by top level test or sequence
//instead of the random value.
req.type = local_type;
end
if(status_device_id)
begin
//Using the value set by top level test or sequence
//instead of the random value.
req.device_id = local_device_id;
end
end
repeat(sequence_length)
`uvm_send(req)
结束任务:正文
始终尝试通过为复杂方案创建顶级序列来重用简单序列。例如,在下面的顺序中,我尝试发送批量传输,然后向 2 个不同的设备发送中断传输。对于此场景,我将使用我们的usb_simple_sequence如下所示:
类 usb_complex_sequence 扩展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
结束类
请注意,在上面的序列中,我们使用 uvm_config_db::get 从顶级测试或序列中获取值,然后使用 uvm_config_db::set 再次将其设置为较低级别的序列。如果我们尝试使用 'uvm_do_with 并将值传递到约束块内,那么这将作为附加约束应用而不是设置这些值,这很重要。
审核编辑:郭婷
全部0条评论
快来发表一下你的评论吧 !