uvm_sequence_base 类通过添加body方法扩展了 uvm_sequence_item 类。sequence用于通过执行其body来产生激励。sequence item被设计为一个瞬态动态对象,这意味着它可以在被取消引用后被创建、使用和垃圾回收。
在 UVM 中使用sequence可以实现非常灵活的激励。sequence用于控制sequence item的生成和流入driver,但它们也可以在同一驱动程序或不同驱动程序上创建和执行其他sequence。sequence还可以将 sequence_items 的生成与子sequence的执行混合在一起。由于sequence是object,灵活使用oop可以生成多变地激励。
通过sequence产生激励的过程中,sequence的层次主要分为三个:
下面实际中sequence构造flow的基本方案
sequence将顺序启动
在并行flow中,用fork-join 并行执行sequence。这意味着两个或多个sequence可能在任何时间点与driver交互。SystemVerilog join_any 和 join_none 允许sequence在执行的时候和他后面的sequence存在时间上的交叠。
sequence在结束之前必须把所有item发送完毕,否则会导致sequencer和driver的死锁。对sequence使用 fork join_any/join_nones 注意一些细节。
在sequence中使用 fork <multiple_sequences> join_any 后跟一个disable fork 将导致未完成的sequence线程被终止,会导致sequencer死锁。
//
// DO NOT USE THIS PATTERN - Supplied as an example of what NOT to do
//
// Parent sequence body method running child sub_sequences within a fork join_any
task body();
//
// Code creating and randomizing the child sequences
//
fork
seq_A.start(m_sequencer);
seq_B.start(m_sequencer);
seq_C.start(m_sequencer);
join_any
// First past the post completes the fork and disables it
disable fork;
// Assuming seq_A completes first - seq_B and seq_C will be terminated in an indeterminate
// way, locking up the sequencer
在 body 方法中使用 fork,join_none 来让sequence和而后续的sequence并行,会导致导致sequence start 方法在 body 方法内容开始与sequencer通信之前终止。通过在更高级别的控制线程中使用 fork join_none 可以避免这种情况。
//
// DO NOT USE THIS PATTERN - Supplied as an example of what NOT to do
//
// Inside sequence_As body method
//
task body();
// Initialise etc
fork
// The body code including other sequences and sequence_items
// ...
join_none
endtask body;
//
// The body task exits immediately, in the controlling thread the idea is that sequence_A executes
// in parallel with other sequences:
//
task run_phase( uvm_phase phase );
// ....
sequence_A_h.start(m_sequencer);
another_seq_h.start(m_sequencer); // This sequence executes in parallel with sequence_A_h
// ...
// The way to achieve the desired functionality is to remove the fork join_none from sequence_A
// and to fork join the two sequences in the control thread:
//
task run;
// ....
fork
sequence_A_h.start(m_sequencer);
another_seq_h.start(m_sequencer);
join
// ....
如果一个sequence在其body方法中包含一个无限循环,并且这个sequence在父sequence的 fork join中启动并且父sequence终止,那么子sequence的body方法将继续循环。如果父sequence被disable终止,则子sequence可能会在握手过程中被捕获,sequencer也有可能被死锁。
分层flow从创建并执行一个或多个子sequence的顶级sequence开始,这些子sequence又创建并执行进一步的子sequence。这种方法类似于使用自顶向下组织的分层软件,以便将高级命令转换为一系列低级事务,直到它达到可以执行总线级命令的原子级。后续章节中会详细展开。
sequence看作对象,意味着会在产生激励的过程中使用对象的特性。
与 sequence_item 一样,sequence可以包含可以标记为 rand 字段的数据字段。这意味着可以通过在开始之前随机化其变量来使sequence表现出不同的行为。sequence的内部约束和外部约束共同作用就能够产生我们所期望了随机字段。
通常,sequence通过内部约束随机化字段。例如,一个将数据从一个内存块移动到另一个内存块的sequence将包含一个随机的从地址开始、一个开始到地址和一个传输大小。传输大小可以限制在系统限制内 - 例如 1K 字节。当sequence被随机化时,起始位置将被限制在相关内存区域的范围内。
//
// This sequence shows how data members can be set to rand values
// to allow the sequence to either be randomized or set to a directed
// set of values in the controlling thread
//
// The sequence reads one block of memory (src_addr) into a buffer and then
// writes the buffer into another block of memory (dst_addr). The size
// of the buffer is determined by the transfer size
//
class mem_trans_seq extends bus_seq_base;
`uvm_object_utils(mem_trans_seq)
// Randomised variables
rand logic[31:0] src_addr;
rand logic[31:0] dst_addr;
rand int transfer_size;
// Internal buffer
logic[31:0] buffer[];
// Legal limit on the page size is 1023 transfers
//
// No point in doing a transfer of 0 transfers
//
constraint page_size {
transfer_size inside {[1:1024]};
}
// Addresses need to be aligned to 32 bit transfers
constraint address_alignment {
src_addr[1:0] == 0;
dst_addr[1:0] == 0;
}
function new(string name = "mem_trans_seq");
super.new(name);
endfunction
task body;
bus_seq_item req = bus_seq_item::type_id::create("req");
logic[31:0] dst_start_addr = dst_addr;
buffer = new[transfer_size];
`uvm_info("run:", $sformatf("Transfer block of %0d words from %0h-%0h to %0h-%0h", transfer_size, src_addr, src_addr+((transfer_size-1)*4), dst_addr, dst_addr+((transfer_size-1)*4)), UVM_LOW)
// Fill the buffer
for(int i = 0; i < transfer_size-1; i++) begin
start_item(req);
if(!req.randomize() with {addr == src_addr; read_not_write == 1; delay < 3;}) begin
`uvm_error("body", "randomization failed for req")
end
finish_item(req);
buffer[i] = req.read_data;
src_addr = src_addr + 4; // Increment to the next location
end
// Empty the buffer
for(int i = 0; i < transfer_size-1; i++) begin
start_item(req);
if(!req.randomize() with {addr == dst_addr; read_not_write == 0; write_data == buffer[i];delay < 3;}) begin
`uvm_error("body", "randomization failed for req")
end
finish_item(req);
dst_addr = dst_addr + 4; // Increment to the next location
end
dst_addr = dst_start_addr;
// Check the buffer transfer
for(int i = 0; i < transfer_size-1; i++) begin
start_item(req);
if(!req.randomize() with {addr == dst_addr; read_not_write == 1; write_data == buffer[i];delay < 3;}) begin
`uvm_error("body", "randomization failed for req")
end
finish_item(req);
if(buffer[i] != req.read_data) begin
`uvm_error("run:", $sformatf("Error in transfer @%0h : Expected %0h, Actual %0h", dst_addr, buffer[i], req.read_data))
end
dst_addr = dst_addr + 4; // Increment to the next location
end
`uvm_info("run:", $sformatf("Finished transfer end addresses SRC: %0h DST:%0h", src_addr, dst_addr), UVM_LOW)
endtask: body
endclass: mem_trans_seq
//
// This test shows how to randomize the memory_trans_seq
// to set it up for a block transfer
//
class seq_rand_test extends bus_test_base;
`uvm_component_utils(seq_rand_test)
function new(string name = "seq_rand_test", uvm_component parent = null);
super.new(name);
endfunction
task run_phase( uvm_phase phase ); phase.raise_objection( this , "start mem_trans_seq" );
mem_trans_seq seq = mem_trans_seq::type_id::create("seq");
// Using randomization and constraints to set the initial values
//
// This could also be done directly
//
assert(seq.randomize() with {src_addr == 32'h0100_0800; dst_addr inside {[32'h0101_0000:32'h0103_0000]}; transfer_size == 128;});
seq.start(m_agent.m_sequencer);
phase.drop_objection( this , "finished mem_trans_seq" );
endtask: run
endclass: seq_rand_test
systemverilog的类可以使用randomize函数进行一次随机化,也可以通过循环,实现多次随机化。
当创建一个sequence然后使用 sequence.start() 执行时,将执行sequence的body方法。当 body 方法完成时,sequence对象仍然存在于内存中。这意味着sequence及其对象层次结构中包含的任何信息仍然可以访问。可以利用此功能将一系列sequence链接在一起,使用来自一个sequence的信息为另一个sequence的执行提供种子。
以前面的内存传输sequence为例,可以在不随机化的情况下重新执行相同的sequence以进行一系列相同大小的顺序传输,然后重新随机化该sequence以从不同的起始地址进行不同大小的传输。
再举一个例子,从外围设备读取或读取块的sequence。然后下一个sequence可以使用前一个sequence数据字段的内容来指导它做什么。
//
// This class shows how to reuse the values persistent within a sequence
// It runs the mem_trans_seq once with randomized values and then repeats it
// several times without further randomization until the memory limit is
// reached. This shows how the end address values are reused on each repeat.
//
class rpt_mem_trans_seq extends bus_seq_base;
`uvm_object_utils(rpt_mem_trans_seq)
function new(string name = "rpt_mem_trans_seq");
super.new(name);
endfunction
task body();
mem_trans_seq trans_seq =
mem_trans_seq::type_id::create("trans_seq");
// First transfer:
assert(trans_seq.randomize() with {src_addr inside {[32'h0100_0000:32'h0100_FFFF]}; dst_addr inside {[32'h0103_0000:(32'h0104_0000 - (transfer_size*4))]}; transfer_size < 512; solve transfer_size before dst_addr;});
trans_seq.start(m_sequencer);
// Continue with next block whilst we can complete within range
// Each block transfer continues from where the last one left off
while ((trans_seq.dst_addr + (trans_seq.transfer_size*4)) < 32'h0104_0000) begin
trans_seq.start(m_sequencer);
end
endtask: body
endclass: rpt_mem_trans_seq
如果创建了一个sequence库,所有这些sequence都源自相同的对象类型,那么就可以创建这些sequence并将它们放入一个数组中,然后以随机顺序执行它们。通过随机生成数组的索引,或使用 <array>.shuffle() 方法改组数组的顺序,可以使该顺序随机化。
//
// This sequence executes some sub-sequences in a random order
//
class rand_order_seq extends bus_seq_base;
`uvm_object_utils(rand_order_seq)
function new(string name = "rand_order_seq");
super.new(name);
endfunction
//
// The sub-sequences are created and put into an array of
// the common base type.
//
// Then the array order is shuffled before each sequence is
// randomized and then executed
//
task body;
bus_seq_base seq_array[4];
seq_array[0] = n_m_rw interleaved_seq::type_id::create("seq_0");
seq_array[1] = rwr_seq::type_id::create("seq_1");
seq_array[2] = n_m_rw_seq::type_id::create("seq_2");
seq_array[3] = fill_memory_seq::type_id::create("seq_3");
// Shuffle the array contents into a random order:
seq_array.shuffle();
// Execute all the array items in turn
foreach(seq_array[i]) begin
if(!seq_array[i].randomize()) begin
`uvm_error("body", "randomization failed for req")
end
seq_array[i].start(m_sequencer);
end
endtask: body
endclass: rand_order_seq
sequence也可以使用 UVM 工厂用派生类型的sequence覆盖,有关更多信息,请参阅关于覆盖sequence的的章节。这种方法允许生成flow改变其特性而不必改变原始sequence代码。