1. sequence基础
1.1. sequence机制相关class的继承关系
Sequence机制是UVM最核心的机制之一,sequence机制最基本的思想是将激励的产生和驱动分开来,这样可以极大提高验证平台的复用性和产生激励的灵活性。要发送的激励即transaction在sequence中产生,通过sequencer这个中介送到driver,driver把激励驱动到DUT中去并通过sequencer将response返回给sequence。其中sequence和sequencer的相关class继承关系如下图所示:
1.2. sequence基本用法
我们可以通过如下方式来实现一个简单的sequence:
class my_sequence extends uvm_sequence #(my_transaction);
virtual task body();
my_transaction my_trans;
my_trans = my_transaction::type_id::creat("my_trans");
start_item(my_trans);
assert(my_trans.randomize());
finish_item(my_trans);
`uvm_info("my_sequence", "send one transaction ..", UVM_LOW)
endtask
endclass
在这个sequence中首先创建一个my_transaction类型的实例,其次调用start_item(),然后对这个transaction进行随机化的操作,最后调用finish_item()。UVM还提供了`uvm_do系列宏来更方便的实现transaction实例的创建和随机化:
class my_sequence extends uvm_sequence #(my_transaction);
virtual task body();
`uvm_do(my_transaction);
`uvm_info("my_sequence", "send one transaction ..", UVM_LOW)
endtask
endclass
在验证平台中可以直接在某testcase的run_phase中显式调用start()来启动这个sequence,start()会调用sequence中的body()来执行:
my_sequence seq;
seq = my_sequence::type_id::creat("seq");
seq.start(sequencer);
还可以通过uvm_config_db将此sequence设置为对应sequencer的某task phase如main_phase的"default_sequence",这样当UVM平台运行到该sequencer的main_phase时就会调用default_sequence即my_sequence的start()来启动my_sequence,达到在phase中隐式执行sequence的目的:
virtual function void my_test::build_phase(uvm_phase phase);
super.build_phase(phase);
uvm_config_db #(uvm_object_wrapper)::set(this, "env.agt.sqr.main_phase", "default_sequence", my_sequence::type_id::get());
endfunction
1.3. virtual sequence & virtual sequencer
当在同一个testcase中需要多个sequence来发送不同的transaction的时候,尤其是这些sequence要发送的激励需要满足某些时序要求的时候,UVM提供了virtual sequence来进行多个sequence间的同步和调度。由于同一个sequencer类型只能发送固定类型的transaction,对于不同sequence要发送的不同transaction类型,当然也是需要其各自对应的sequencer,因此可以通过virtual sequencer提供句柄在virtual sequence中来指向不同类型的sequencer:
class my_vsequencer extends uvm_sequencer;
`uvm_component_utils(my_vsequencer);
my_sequencer sqr;
my_sequencer1 sqr1;
endclass
在testcase中我们可以将其实例化并分别让它们指向不同的sequencer:
class my_test extends uvm_test;
my_env env;
my_vsequencer vsqr;
function build_phase(uvm_phase phase);
vsqr = my_vsequencer::type_id::creat("vsqr", this);
...
endfunction
function connect_phase(uvm_phase phase);
vsqr.sqr = env.agt.sqr;
vsqr.sqr1 = env.agt1.sqr;
...
endfunction
endclass
在virtual sequence中可以将my_vsequencer声明为p_sequencer并分别使用virtual sequencer中的sqr和sqr1来分别执行my_sequence和my_sequence1:
class my_vsequence extends uvm_sequence;
`uvm_object_utils(my_vsequence)
`uvm_declare_p_sequencer(my_vsequencer)
task body();
my_sequence seq;
my_sequence1 seq1;
`uvm_do_on(seq, p_sequencer.sqr);
`uvm_do_on(seq1, p_sequencer.sqr1);
endtask
endclass
1.4. sequence library
顾名思义,sequence library就是一系列sequence的集合,其实它本质上还是一个sequence。我们可以启动一个sequence library通过设置算法来令其执行一组sequence,例如我们可以新建一个名为my_seq_lib的sequence library:
class my_seq_lib extends uvm_sequence_library #(my_transaction);
`uvm_object_utils(my_seq_lib)
`uvm_sequence_library_utils(my_seq_lib);
function new(string name="my_seq_lib");
super.new(name);
init_sequence_library();
endfunction
endclass
在创建my_seq_lib类时需要指明其能发送的transaction类型,因此其中所有的sequence都是发送相同固定的transaction类型的,然后在new()函数中调用init_sequence_library()并且使用宏`uvm_sequence_library_utils进行注册。接下来向my_seq_lib中添加名为my_seqsequence:
class my_seq extends uvm_sequence #(my_transaction);
`uvm_object_utils(my_seq)
`uvm_add_to_seq_lib(my_seq, my_seq_lib);
virtual task body();
...
endtask
endclass
可以直接在sequence中使用宏`uvm_add_to_seq_lib把其加入到某sequence library,一个sequence可以被加入到多个sequence library中,此外还可以通过直接调用sequence library提供的函数add_typewide_sequence()/add_sequence()来向其中加入sequences。
此后我们可以通过uvm_config_db将my_seq_lib作为sequencer的default_sequence:
function void my_test::build_phase();
super.build_phase(phase);
uvm_config_db #(uvm_object_wrapper)::set(this, "env.agt_sqr.main_phase", "default_sequence", my_seq_lib::type_id::get());
endfunction
此外我们可以通过配置sequence library的selectime_mode等变量来决定选择sequence的算法以及要执行多少次(参考后面关于sequence library的源码部分)。
2. transaction的发送
2.1. `uvm_do系列宏
UVM提供了`uvm_do系列宏,我们在sequence中使用这些宏可以指定当前要执行的sequence_item或者sequence的sequencer,priority,以及一些变量在随机化时的约束条件:
//uvm_sequence_defines.svh
`define uvm_do(SEQ_OR_ITEM) \
`uvm_do_on_pri_with(SEQ_OR_ITEM, m_sequencer, -1, {})
`define uvm_do_on(SEQ_OR_ITEM, SEQR) \
`uvm_do_on_pri_with(SEQ_OR_ITEM, SEQR, -1, {})
`define uvm_do_pri(SEQ_OR_ITEM, PRIORITY) \
`uvm_do_on_pri_with(SEQ_OR_ITEM, m_sequencer, PRIORITY, {})
`define uvm_do_with(SEQ_OR_ITEM, CONSTRAINTS) \
`uvm_do_on_pri_with(SEQ_OR_ITEM, m_sequencer, -1, CONSTRAINTS)
`define uvm_do_on_pri(SEQ_OR_ITEM, SEQR, PRIORITY) \
`uvm_do_on_pri_with(SEQ_OR_ITEM, SEQR, PRIORITY, {})
`define uvm_do_on_with(SEQ_OR_ITEM, SEQR, CONSTRAINTS) \
`uvm_do_on_pri_with(SEQ_OR_ITEM, SEQR, -1, CONSTRAINTS)
`define uvm_do_pri_with(SEQ_OR_ITEM, PRIORITY, CONSTRAINTS) \
`uvm_do_on_pri_with(SEQ_OR_ITEM, m_sequencer, PRIORITY, CONSTRAINTS)
这些宏都是调用的同一个宏`uvm_do_on_pri_with(SEQ_OR_ITEM, m_sequencer, PRIORITY, CONSTRAINTS),其定义如下:
//uvm_sequence_defines.svh
`define uvm_do_on_pri_with(SEQ_OR_ITEM, SEQR, PRIORITY, CONSTRAINTS) \
begin \
uvm_sequence_base __seq; \
`uvm_create_on(SEQ_OR_ITEM, SEQR) \
if (!$cast(__seq,SEQ_OR_ITEM)) start_item(SEQ_OR_ITEM, PRIORITY);\
if ((__seq == null || !__seq.do_not_randomize) && !SEQ_OR_ITEM.randomize() with CONSTRAINTS ) begin \
`uvm_warning("RNDFLD", "Randomization failed in uvm_do_with action") \
end\
if (!$cast(__seq,SEQ_OR_ITEM)) finish_item(SEQ_OR_ITEM, PRIORITY); \
else __seq.start(SEQR, this, PRIORITY, 0); \
end
这个宏有四个参数,第一个参数SEQ_OR_ITEM表示我们可以用这个宏来处理sequence_item或者是sequence类型,第二个参数SEQR可以指定使用哪个sequencer来执行当前sequence_item/sequence,第三个参数PRIORITY可以设置优先级,最后一个参数CONSTRAINTS可以给sequence_item或者sequence中的一些参数在随机化时设置约束条件。
这个宏首先调用`uvm_create_on(SEQ_OR_ITEM, SEQR)创建一个SEQ_OR_ITEM实例,然后判断其是否是一个sequence类型,若不是,调用start_item()和finish_item()来处理这个sequence_item,否则调用start()。宏uvm_create_on定义如下:
//uvm_sequence_defines.svh
`define uvm_create_on(SEQ_OR_ITEM, SEQR) \
begin \
uvm_object_wrapper w_; \
w_ = SEQ_OR_ITEM.get_type(); \
$cast(SEQ_OR_ITEM , create_item(w_, SEQR, `"SEQ_OR_ITEM`"));\
end
在`uvm_creat_on()中使用get_type()拿到一个uvm_object_wrapper类型的句柄,这个get_type()最初是定义在uvm_object类里的一个静态函数,当我们使用`uvm_object_utils宏在factory中将SEQ_OR_ITEM(无论是sequence或者是sequence_item类型)注册时会重载这个函数,返回该类型的唯一句柄me(具体可参看UVM factory机制源码探微),然后将该句柄作为第一个参数调用create_item():
//uvm_sequence_base.svh
class uvm_sequence_base extends uvm_sequence_item;
...
protected function uvm_sequence_item create_item(uvm_object_wrapper type_var,
uvm_sequencer_base l_sequencer, string name);
uvm_coreservice_t cs = uvm_coreservice_t::get();
uvm_factory factory=cs.get_factory();
$cast(create_item, factory.create_object_by_type( type_var, this.get_full_name(), name ));
create_item.set_item_context(this, l_sequencer);
endfunction
...
endclass
这个函数定义在uvm_sequence_base类中,使用factory机制创建一个SEQ_OR_ITEM类型句柄赋值给create_item,这个create_item就是这个函数的返回值,随后调用set_item_context():
//uvm_sequence_item.svh
class uvm_sequence_item extends uvm_transaction;
protected bit m_use_sequence_info;
protected int m_depth = -1;
protected uvm_sequencer_base m_sequencer;
protected uvm_sequence_base m_parent_sequence;
...
function void set_item_context(uvm_sequence_base parent_seq,
uvm_sequencer_base sequencer = null);
set_use_sequence_info(1);
if (parent_seq != null) set_parent_sequence(parent_seq);
if (sequencer == null && m_parent_sequence != null) sequencer = m_parent_sequence.get_sequencer();
set_sequencer(sequencer);
if (m_parent_sequence != null) set_depth(m_parent_sequence.get_depth() + 1);
reseed();
endfunction
function void set_parent_sequence(uvm_sequence_base parent);
m_parent_sequence = parent;
endfunction
virtual function void set_sequencer(uvm_sequencer_base sequencer);
m_sequencer = sequencer;
m_set_p_sequencer();
endfunction
function uvm_sequencer_base get_sequencer();
return m_sequencer;
endfunction
virtual function void m_set_p_sequencer();
return;
endfunction
function void set_depth(int value);
m_depth = value;
endfunction
function int get_depth();
// If depth has been set or calculated, then use that
if (m_depth != -1) begin
return (m_depth);
end
// Calculate the depth, store it, and return the value
if (m_parent_sequence == null) begin
m_depth = 1;
end else begin
m_depth = m_parent_sequence.get_depth() + 1;
end
return (m_depth);
endfunction
...
endclass
函数set_item_context()是定义在uvm_sequence_item类中的,它有两个参数,第一个参数是uvm_sequence_base类型的,给当前sequence_item/sequence指定一个parent sequence,这里传入的是this句柄,即调用`uvm_do宏的sequence,第二个参数是指定一个sequencer。在函数内部首先调用set_use_sequence_info(),这里就是把bit类型的用于debug的变量m_use_sequence_info置为1。接下来判断若传入参数parent_seq不为null,则调用set_parent_sequence()函数把parent_seq赋值给m_parent_sequence,这是一个定义在uvm_sequence_item类中的uvm_sequence_base类型的句柄,指向当前sequence_item/sequence的一个parent sequence。若传入的sequencer为null且现在m_parent_sequence不为null,则调用get_sequencer()拿到parent sequence的m_sequencer句柄,m_sequencer是定义在uvm_sequence_item类中的uvm_sequencer_base类型的句柄,指向用来执行当前sequence_item/sequence的sequencer。随后调用set_sequencer()把parent sequence的sequencer赋值给当前的sequence_item/sequence,并且调用m_set_p_sequencer(),这个virtual函数此时为空,会直接返回,我们在后面会提到它。最后若m_parent_sequence不为null,则调用set_depth()把当前sequence_item/sequence的m_depth的m_depth在其parent sequence的基础上加1。m_depth是定义在uvm_sequence_item类中的一个int类型变量,默认初始值为-1,若当前sequence没有任何parent sequence,则其m_depth=1。他的子sequence_item/sequence在此基础上依次加1。最后调用reseed()来为sequence_item/sequence设置一个seed,这里用到了UVM seeding机制的一个函数,它基于类型和实例层次名来生成随机化种子。
总结:在宏`uvm_create_on中会在factory中拿到sequence_item/sequence类型的实例句柄SEQ_OR_ITEM然后调用函数set_item_context()分别设置其m_parent_sequence,m_sequencer和m_depth等相关信息。
实际上UVM还提供了`uvm_creat()和`uvm_send()系列宏,`uvm_creat()就是直接调用`uvm_create_on(),`uvm_send()系列宏也是在其内部调用了start()或者start_item()/finish_item(),这里不再赘述。
2.2. start()
UVM定义了一个枚举变量uvm_sequence_state,表示sequence当前不同的执行状态:
//uvm_object_globals.svh
typedef enum
{
UVM_CREATED = 1,
UVM_PRE_START = 2,
UVM_PRE_BODY = 4,
UVM_BODY = 8,
UVM_POST_BODY = 16,
UVM_POST_START= 32,
UVM_ENDED = 64,
UVM_STOPPED = 128,
UVM_FINISHED = 256
} uvm_sequence_state;
若`uvm_create_on中拿到的SEQ_OR_ITEM句柄类型是一个sequence,则接下来会调用start():
//uvm_sequence_base.svh
class uvm_sequence_base extends uvm_sequence_item;
protected uvm_sequence_state m_sequence_state;
local int m_priority = -1;
protected int m_sqr_seq_ids[int];
protected bit children_array[uvm_sequence_base];
protected uvm_sequence_item response_queue[$];
...
virtual task start (uvm_sequencer_base sequencer,
uvm_sequence_base parent_sequence = null,
int this_priority = -1,
bit call_pre_post = 1);
bit old_automatic_phase_objection;
set_item_context(parent_sequence, sequencer);
if (!(m_sequence_state inside {UVM_CREATED,UVM_STOPPED,UVM_FINISHED})) begin
uvm_report_fatal("SEQ_NOT_DONE",
{"Sequence ", get_full_name(), " already started"},UVM_NONE);
end
if (m_parent_sequence != null) begin
m_parent_sequence.children_array[this] = 1;
end
if (this_priority < -1) begin
uvm_report_fatal("SEQPRI", $sformatf("Sequence %s start has illegal priority: %0d",
get_full_name(),
this_priority), UVM_NONE);
end
if (this_priority < 0) begin
if (parent_sequence == null) this_priority = 100;
else this_priority = parent_sequence.get_priority();
end
// Check that the response queue is empty from earlier runs
clear_response_queue();
m_priority = this_priority;
`ifdef UVMKIT_TR_RECORDING
if (m_sequencer != null) begin
integer handle;
uvm_tr_stream stream;
if (m_parent_sequence == null) begin
stream = m_sequencer.get_tr_stream(get_name(), "Transactions");
handle = m_sequencer.begin_tr(this, get_name());
m_tr_recorder = uvm_recorder::get_recorder_from_handle(handle);
end else begin
stream = m_sequencer.get_tr_stream(get_root_sequence_name(), "Transactions");
handle = m_sequencer.begin_child_tr(this,
(m_parent_sequence.m_tr_recorder == null) ? 0 : m_parent_sequence.m_tr_recorder.get_handle(),
get_root_sequence_name());
m_tr_recorder = uvm_recorder::get_recorder_from_handle(handle);
end
end
`endif
// Ensure that the sequence_id is intialized in case this sequence has been stopped previously
set_sequence_id(-1);
// Remove all sqr_seq_ids
m_sqr_seq_ids.delete();
// Register the sequence with the sequencer if defined.
if (m_sequencer != null) begin
void'(m_sequencer.m_register_sequence(this));
end
// Change the state to PRE_START, do this before the fork so that
// the "if (!(m_sequence_state inside {...}" works
m_sequence_state = UVM_PRE_START;
fork
begin
m_sequence_process = process::self();
// absorb delta to ensure PRE_START was seen
#0;
// Raise the objection if enabled
// (This will lock the uvm_get_to_lock_dap)
if (get_automatic_phase_objection()) begin
m_safe_raise_starting_phase("automatic phase objection");
end
pre_start();
if (call_pre_post == 1) begin
m_sequence_state = UVM_PRE_BODY;
#0;
pre_body();
end
if (parent_sequence != null) begin
parent_sequence.pre_do(0); // task
parent_sequence.mid_do(this); // function
end
m_sequence_state = UVM_BODY;
#0;
body();
m_sequence_state = UVM_ENDED;
#0;
if (parent_sequence != null) begin
parent_sequence.post_do(this);
end
if (call_pre_post == 1) begin
m_sequence_state = UVM_POST_BODY;
#0;
post_body();
end
m_sequence_state = UVM_POST_START;
#0;
post_start();
// Drop the objection if enabled
if (get_automatic_phase_objection()) begin
m_safe_drop_starting_phase("automatic phase objection");
end
m_sequence_state = UVM_FINISHED;
#0;
end
join
`ifdef UVMKIT_TR_RECORDING
if (m_sequencer != null) begin
m_sequencer.end_tr(this);
end
`endif
// Clean up any sequencer queues after exiting; if we
// were forcibly stoped, this step has already taken place
if (m_sequence_state != UVM_STOPPED) begin
if (m_sequencer != null)
m_sequencer.m_sequence_exiting(this);
end
#0; // allow stopped and finish waiters to resume
if ((m_parent_sequence != null) && (m_parent_sequence.children_array.exists(this))) begin
m_parent_sequence.children_array.delete(this);
end
old_automatic_phase_objection = get_automatic_phase_objection();
m_init_phase_daps(1);
set_automatic_phase_objection(old_automatic_phase_objection);
endtask
function int get_priority();
return m_priority;
endfunction
virtual function void clear_response_queue();
response_queue.delete();
endfunction
...
endclass
start()有四个参数,分别可以指定sequencer,parent sequence,priority和call_pre_post。首先调用set_item_context()设置当前sequencesequencer和parent sequence等相关信息。若m_sequence_state不是UVM_CREATED/UVM_STOPPED/UVM_FINISHED这几种,则表示当前sequence正在执行,此时会报错。然后把parent sequence的children_array[this]置为1,children_array[]是定义在uvm_sequence_base类中以uvm_sequence_base类型为索引的一个联合数组,储存的是当前sequence的子sequence信息。另一个int类型变量m_priority表示当前sequence在同一sequencer进行仲裁时的优先级,数值越大优先级越高,初始默认值是-1。若当前sequence没有parent sequence,则m_priority赋值为100,否则调用get_priority()函数拿到其parent sequence的m_priority并配置给自身。接下来调用clear_response_queue()来清空队列response_queue[$],这个队列储存的是uvm_sequence_item类型的从driver返回来的response,这里目的是要清空当前sequence在前面执行过程中储存的response。
接下来被`UVMKIT_TR_RECORDING宏包裹的部分是关于在debug的时候更好的trace transaction的数据的部分代码,这里我们直接略过。然后调用set_sequence_id()给m_sequence_id赋值为-1,m_sequence_id是定义在uvm_sequence_item类中的int类型变量,表示当前sequence的一个独一无二的id号:
//uvm_sequence_item.svh
class uvm_sequence_item extends uvm_transaction;
local int m_sequence_id = -1;
...
function void set_sequence_id(int id);
m_sequence_id = id;
endfunction
...
endclass
接下来在start()中会把数组m_sqr_seq_ids[]清空,这是一个定义在uvm_sequence_base类中的以int类型为索引的联合数组,其中储存的是当前sequence在某sequencer(每一个sequencer也会有一个id,作为索引)中所分配的id号(一个sequence可能对应多个sequencer,一个sequencer也可能被用于执行多个sequence)。如my_sqr1和my_sqr2的id号分别为1和2,my_sqr1可以执行my_seq1和my_seq2,分别给他们分配id为3和4,同时my_sqr2也可以执行my_seq1,给my_seq1分配id为5,则:
my_seq1.m_sqr_seq_ids[1] = 3 //索引为my_sqr1的id,值为其给my_seq1分配的id
my_seq1.m_sqr_seq_ids[2] = 5 //索引为my_sqr2的id,值为其给my_seq1分配的id
my_seq2.m_sqr_seq_ids[1] = 4 //索引为my_sqr1的id,值为其给my_seq2分配的id
随后若m_sequencer不为null(此时这个m_sequencer指向的就是我们在最开始调用start()时传入的第一个参数sequencer),则调用其m_register_sequence()函数,传入的参数为this指针,指向当前调用start()的这个sequence实例,这个函数是定义在uvm_sequencer_base类中的:
//uvm_sequencer_base.svh
class uvm_sequencer_base extends uvm_component;
local static int g_sequence_id = 1;
protected uvm_sequence_base reg_sequences[int];
...
endclass
function int uvm_sequencer_base::m_register_sequence(uvm_sequence_base sequence_ptr);
if (sequence_ptr.m_get_sqr_sequence_id(m_sequencer_id, 1) > 0)
return sequence_ptr.get_sequence_id();
sequence_ptr.m_set_sqr_sequence_id(m_sequencer_id, g_sequence_id++);
reg_sequences[sequence_ptr.get_sequence_id()] = sequence_ptr;
return sequence_ptr.get_sequence_id();
endfunction
在该函数中会分别用到m_get_sqr_sequence_id()和m_set_sqr_sequence_id()函数,代码如下:
//uvm_sequence_base.svh
class uvm_sequence_base extends uvm_sequence_item;
local static int g_sequence_id = 1;
...
function int m_get_sqr_sequence_id(int sequencer_id, bit update_sequence_id);
if (m_sqr_seq_ids.exists(sequencer_id)) begin
if (update_sequence_id == 1) begin
set_sequence_id(m_sqr_seq_ids[sequencer_id]);
end
return m_sqr_seq_ids[sequencer_id];
end
if (update_sequence_id == 1)
set_sequence_id(-1);
return -1;
endfunction
function void m_set_sqr_sequence_id(int sequencer_id, int sequence_id);
m_sqr_seq_ids[sequencer_id] = sequence_id;
set_sequence_id(sequence_id);
endfunction
...
endclass
函数m_get_sqr_sequence_id()会在数组m_sqr_seq_ids[]中寻找以传入的sequencer_id为索引的值,若存在且第二个参数update_sequence_id为1,则返回该值,否则把该sequence的m_sequence_id设置为默认值-1并返回该值。这里由于m_sqr_seq_ids[]刚被清空,所以会返回-1。随后把m_sequencer_id和g_sequence_id加1的值分别作为参数调用m_set_sqr_sequence_id()存入数组m_sqr_seq_ids[]。int类型的静态变量g_sequence_id定义在uvm_sequencer_base类中,初始值为1,这个变量是系统中所有sequencer共享的,当任何一个sequencer有一个新的sequence要register的时候,这个值就会加1,从而分配给该sequence一个独一无二的新id。
回到m_register_sequence()中,接下来会把当前sequence的指针以sequence_id为索引存入数组reg_sequences[],这是一个定义在uvm_sequencer_base类中的以int类型为索引,值为uvm_sequence_base类型的联合数组,sequencer可以用这个数组通过sequence_id来找到登记在该sequencer名下的某sequence的实例。实际上sequencer调用函数m_register_sequence()就是把m_sqr_seq_ids[]和reg_sequences[]进行更新,储存登记在其名下的要执行的sequence相关信息。
接下来会把m_sequence_state设置为UVM_PRE_START并且在fork…join中启动一个新的进程process来执行一系列函数/任务,在执行这些函数/任务之前,会调用get_automatic_phase_objection()来做一些phase机制objection的操作:
//uvm_sequence_base.svh
class uvm_sequence_base extends uvm_sequence_item;
...
function new (string name = "uvm_sequence");
super.new(name);
m_sequence_state = UVM_CREATED;
m_wait_for_grant_semaphore = 0;
m_init_phase_daps(1);
endfunction
// Automatic Phase Objection DAP
local uvm_get_to_lock_dap#(bit) m_automatic_phase_objection_dap;
// Starting Phase DAP
local uvm_get_to_lock_dap#(uvm_phase) m_starting_phase_dap;
// Function- m_init_phase_daps
// Either creates or renames DAPS
function void m_init_phase_daps(bit create);
string apo_name = $sformatf("%s.automatic_phase_objection", get_full_name());
string sp_name = $sformatf("%s.starting_phase", get_full_name());
if (create) begin
m_automatic_phase_objection_dap = uvm_get_to_lock_dap#(bit)::type_id::create(apo_name, get_sequencer());
m_starting_phase_dap = uvm_get_to_lock_dap#(uvm_phase)::type_id::create(sp_name, get_sequencer());
end
else begin
m_automatic_phase_objection_dap.set_name(apo_name);
m_starting_phase_dap.set_name(sp_name);
end
endfunction : m_init_phase_daps
function void set_automatic_phase_objection(bit value);
m_automatic_phase_objection_dap.set(value);
endfunction : set_automatic_phase_objection
function bit get_automatic_phase_objection();
return m_automatic_phase_objection_dap.get();
endfunction : get_automatic_phase_objection
...
endclass
这个函数会调用m_automatic_phase_objection_dap的get(),这是一个uvm_get_to_lock_dap#(bit)数据类型的变量,UVM的Data Access Policy(DAP)的一个参数化类,这个独特的数据类型的特点是我们可以通过set()给其设置一个其参数所代表数据类型的值,如此处为bit类型的数值,一旦调用get()后,就返回当前值并处于lock状态,不可以再被设置为别的值。m_automatic_phase_objection_dap会在sequence调用new()的时候调用m_init_phase_daps()来创建出一个自身实例,此时由于并没有给它设置一个值,所以get()函数返回0。我们可以在sequence中调用set_automatic_phase_objection(1)把m_automatic_phase_objection_dap的值置为1。与之类似的还有一个同样数据类型的变量m_starting_phase_dap,它也会在m_init_phase_daps()被实例化,只不过其参数是一个uvm_phase类型,我们只能给其设置一个uvm_phase类型的句柄,而不是bit类型。
回到start()任务中,若我们在调用start()之前调用了m_automatic_phase_objection_dap的set()函数给其bit值置为1,则调用m_safe_raise_starting_phase()来自动为starting_phase 提起一个objection:
//uvm_sequence_base.svh
class uvm_sequence_base extends uvm_sequence_item;
...
`ifndef UVM_NO_DEPRECATED
`define UVM_DEPRECATED_STARTING_PHASE
`endif
`ifdef UVM_DEPRECATED_STARTING_PHASE
// DEPRECATED!! Use get/set_starting_phase accessors instead!
uvm_phase starting_phase;
// Value set via set_starting_phase
uvm_phase m_set_starting_phase;
// Ensures we only warn once per sequence
bit m_warn_deprecated_set;
`endif
function uvm_phase get_starting_phase();
`ifdef UVM_DEPRECATED_STARTING_PHASE
begin
bit throw_error;
if (starting_phase != m_set_starting_phase) begin
if (!m_warn_deprecated_set) begin
`uvm_warning("UVM_DEPRECATED", "'starting_phase' is deprecated and not part of the UVM standard. See documentation for uvm_sequence_base::set_starting_phase")
m_warn_deprecated_set = 1;
end
if (m_starting_phase_dap.try_set(starting_phase))
m_set_starting_phase = starting_phase;
else begin
uvm_phase dap_phase = m_starting_phase_dap.get();
`uvm_error("UVM/SEQ/LOCK_DEPR",
{"The deprecated 'starting_phase' variable has been set to '",
(starting_phase == null) ? "<null>" : starting_phase.get_full_name(),
"' after a call to get_starting_phase locked the value to '",
(dap_phase == null) ? "<null>" : dap_phase.get_full_name(),
"'. See documentation for uvm_sequence_base::set_starting_phase."})
end
end
end
`endif
return m_starting_phase_dap.get();
endfunction : get_starting_phase
function void set_starting_phase(uvm_phase phase);
`ifdef UVM_DEPRECATED_STARTING_PHASE
begin
if (starting_phase != m_set_starting_phase) begin
if (!m_warn_deprecated_set) begin
`uvm_warning("UVM_DEPRECATED",
{"The deprecated 'starting_phase' variable has been set to '",
starting_phase.get_full_name(),
"' manually. See documentation for uvm_sequence_base::set_starting_phase."})
m_warn_deprecated_set = 1;
end
starting_phase = phase;
m_set_starting_phase = phase;
end
end
`endif
m_starting_phase_dap.set(phase);
endfunction : set_starting_phase
function void m_safe_raise_starting_phase(string description = "",
int count = 1);
uvm_phase starting_phase = get_starting_phase();
if (starting_phase != null)
starting_phase.raise_objection(this, description, count);
endfunction : m_safe_raise_starting_phase
...
endclass
在函数m_safe_raise_starting_phase()中调用get_starting_phase()来拿到starting_phase的句柄,若之前已经通过调用set_starting_phase()给m_starting_phase_dap赋值,我们可以直接拿到starting_phase的句柄从而raise一个objection。若之前没有调用过set_starting_phase(),这里会调用m_starting_phase_dap的try_put()函数,由于之前从未调用过get(),所以这个DAP还没有被lock住,可以直接把starting_phase赋值给它,最后调用get()拿到starting_pahse句柄并返回。这里可以有两种方式来调用set_starting_phase(),第一种是直接在sequence中调用start()任务之前显式调用set_starting_phase(),另外一种是隐式调用,把当前sequence设置为对应sequencer的某task phase如main_phase的default_sequence,这样在执行到main_phase时会自动调用set_starting_phase(),我们后面会研究相关代码。同样的,在post_start()执行完毕后,若get_automatic_phase_objection()返回1,start()会调用m_safe_drop_starting_phase()来drop掉objection。关于在sequence中raise/drop objection,我们还可以在基类sequence的callback pre_body/start()和post_body/start()中来raise和drop objection。
在start()任务中,之后会顺序执行一系列(callback)函数/任务,并更新m_sequence_state到相应状态,我们可以在sequence中重载这些callback来实现一些特定目的,各函数/任务执行顺序如下:
v| starting_phase.raise_objection if (automatic_phase_objection ==1)
v| seq.pre_start() (task)
v| seq.pre_body() (task) if call_pre_post==1
v| parent_seq.pre_do(0) (task) if parent_sequence!=null
v| parent_seq.mid_do(this) (func) if parent_sequence!=null
v| seq.body (task) YOUR STIMULUS CODE
v| parent_seq.post_do(this) (func) if parent_sequence!=null
v| seq.post_body() (task) if call_pre_post==1
v| seq.post_start() (task)
v| starting_phase.drop_objection if (automatic_phase_objection ==1)
若sequence正常执行结束的话(m_sequence_state == UVM_FINISHED),调用m_sequencer的m_sequence_exiting()函数:
//uvm_sequencer_base.svh
function void uvm_sequencer_base::m_unregister_sequence(int sequence_id);
if (!reg_sequences.exists(sequence_id))
return;
reg_sequences.delete(sequence_id);
endfunction
function void uvm_sequencer_base::remove_sequence_from_queues(
uvm_sequence_base sequence_ptr);
int i;
int seq_id;
seq_id = sequence_ptr.m_get_sqr_sequence_id(m_sequencer_id, 0);
// Remove all queued items for this sequence and any child sequences
i = 0;
do
begin
if (arb_sequence_q.size() > i) begin
if ((arb_sequence_q[i].sequence_id == seq_id) ||
(is_child(sequence_ptr, arb_sequence_q[i].sequence_ptr))) begin
if (sequence_ptr.get_sequence_state() == UVM_FINISHED)
`uvm_error("SEQFINERR", $sformatf("Parent sequence '%s' should not finish before all items from itself and items from descendent sequences are processed. The item request from the sequence '%s' is being removed.", sequence_ptr.get_full_name(), arb_sequence_q[i].sequence_ptr.get_full_name()))
arb_sequence_q.delete(i);
m_update_lists();
end
else begin
i++;
end
end
end
while (i < arb_sequence_q.size());
// remove locks for this sequence, and any child sequences
i = 0;
do
begin
if (lock_list.size() > i) begin
if ((lock_list[i].get_inst_id() == sequence_ptr.get_inst_id()) ||
(is_child(sequence_ptr, lock_list[i]))) begin
if (sequence_ptr.get_sequence_state() == UVM_FINISHED)
`uvm_error("SEQFINERR", $sformatf("Parent sequence '%s' should not finish before locks from itself and descedent sequences are removed. The lock held by the child sequence '%s' is being removed.",sequence_ptr.get_full_name(), lock_list[i].get_full_name()))
lock_list.delete(i);
m_update_lists();
end
else begin
i++;
end
end
end
while (i < lock_list.size());
// Unregister the sequence_id, so that any returning data is dropped
m_unregister_sequence(sequence_ptr.m_get_sqr_sequence_id(m_sequencer_id, 1));
endfunction
function void uvm_sequencer_base::m_sequence_exiting(uvm_sequence_base sequence_ptr);
remove_sequence_from_queues(sequence_ptr);
endfunction
这个函数会直接调用remove_sequence_from_queues()函数,这个函数做的事情就是把当前已经执行完毕的sequence从sequencer的仲裁队列和lock队列中删除并调用m_unregister_sequence()将其从reg_sequences[]数组中删除。
在start()任务中的收尾阶段,会将该sequence从parent sequence的children_array[]数组中删除,最后拿到automatic_phase_objection的值重新调用m_init_phase_daps(1)并设置给m_automatic_phase_objection_dap。
2.3. start_item()
前面提到,在宏`uvm_do_on_pri_with()中,若要执行的SEQ_OR_ITEM的类型不是uvm_sequence_base类型(即不是一个sequence),则调用start_item()和finish_item()任务来执行这个sequence_item,我们先来看start_item():
//uvm_sequence_base.svh
class uvm_sequence_base extends uvm_sequence_item;
...
virtual task start_item (uvm_sequence_item item,
int set_priority = -1,
uvm_sequencer_base sequencer=null);
uvm_sequence_base seq;
if(item == null) begin
uvm_report_fatal("NULLITM",
{"attempting to start a null item from sequence '",
get_full_name(), "'"}, UVM_NONE);
return;
end
if($cast(seq, item)) begin
uvm_report_fatal("SEQNOTITM",
{"attempting to start a sequence using start_item() from sequence '",
get_full_name(), "'. Use seq.start() instead."}, UVM_NONE);
return;
end
if (sequencer == null)
sequencer = item.get_sequencer();
if(sequencer == null)
sequencer = get_sequencer();
if(sequencer == null) begin
uvm_report_fatal("SEQ",{"neither the item's sequencer nor dedicated sequencer has been supplied to start item in ",get_full_name()},UVM_NONE);
return;
end
item.set_item_context(this, sequencer);
if (set_priority < 0)
set_priority = get_priority();
sequencer.wait_for_grant(this, set_priority);
if (sequencer.is_auto_item_recording_enabled()) begin
void'(sequencer.begin_child_tr(item,
(m_tr_recorder == null) ? 0 : m_tr_recorder.get_handle(),
item.get_root_sequence_name(), "Transactions"));
end
pre_do(1);
endtask
...
endclass
任务start_item()通过三个参数来分别指定要执行的uvm_sequence_item类型的对象,priority和sequencer,若传入的sequence_item对象句柄为null,或者该对象句柄指向的是一个uvm_sequence_base对象(即是一个sequence)则报错返回。若传入sequencer参数为null,则调用get_sequencer()拿到sequence_item或者是当前sequence的m_sequencer,若均为null,则报错返回。接着调用set_item_context()传入this指针将当前的sequence设置为该sequence_item的parent sequence,将刚才拿到的m_sequencer句柄设置为它的m_sequencer。若没有设置priority参数或者传入的priority值小于0,则调用get_priority()拿到当前sequence的m_priority,然后作为第二个参数调用m_sequencer的wait_for_grant(),sequencer中有一套仲裁机制,来决定sequence的执行顺序,这个函数返回以后,最后调用callback pre_do(1)。
2.4. sequencer的仲裁机制
Sequence机制提供了枚举类型UVM_SEQ_ARB_TYPE来表示不同的仲裁算法:
//uvm_object_globals.svh
typedef enum
{
UVM_SEQ_ARB_FIFO,
UVM_SEQ_ARB_WEIGHTED,
UVM_SEQ_ARB_RANDOM,
UVM_SEQ_ARB_STRICT_FIFO,
UVM_SEQ_ARB_STRICT_RANDOM,
UVM_SEQ_ARB_USER
} uvm_sequencer_arb_mode;
typedef uvm_sequencer_arb_mode UVM_SEQ_ARB_TYPE;
其中UVM_SEQ_ARB_FIFO表示先到先得,UVM_SEQ_ARB_WEIGHTED根据每个请求的priority权重来随机选择优先执行哪个sequence,UVM_SEQ_ARB_RANDOM则是纯粹随机选择,UVM_SEQ_ARB_STRICT_FIFO是根据priority来把所有请求分成不同的优先级,具有相同最高优先级的请求则遵循先到先得的原则,UVM_SEQ_ARB_STRICT_RANDOM是根据priority来把所有请求分成不同的优先级,然后从具有相同最高优先级的请求中随机选择一个,UVM_SEQ_ARB_USER是用户自定义算法。UVM默认采用UVM_SEQ_ARB_FIFO,我们可以通过调用sequencer的set_arbitration()函数来设置当前sequencer采用哪种仲裁算法:
//uvm_sequencer_base.svh
class uvm_sequencer_base extends uvm_component;
typedef enum {SEQ_TYPE_REQ,
SEQ_TYPE_LOCK,
SEQ_TYPE_GRAB} seq_req_t;
...
local uvm_sequencer_arb_mode m_arbitration = UVM_SEQ_ARB_FIFO;
...
local static int g_request_id;
...
endclass
function void uvm_sequencer_base::set_arbitration(UVM_SEQ_ARB_TYPE val);
m_arbitration = val;
endfunction
class uvm_sequence_request;
bit grant;
int sequence_id;
int request_id;
int item_priority;
process process_id;
uvm_sequencer_base::seq_req_t request;
uvm_sequence_base sequence_ptr;
endclass
在uvm_sequencer_base中定义了一个枚举变量seq_req_t,表示request的请求类型如SEQ_TYPE_REQ和SEQ_TYPE_LOCK,此外UVM还提供了一个类uvm_sequence_request,这个类里面记录了当前一笔请求的sequence_id/request_id/request类型/对应sequence实例指针,request id等信息,这个request id是定义在uvm_sequencer_base类中的一个静态int类型变量,这个变量也是所有sequencer共享,每当系统中有一个新的uvm_sequence_request实例被创建,都会给它分配一个独一无二的新的request id。
现在来看任务wait_for_grant():
//uvm_sequencer_base.svh
task uvm_sequencer_base::wait_for_grant(uvm_sequence_base sequence_ptr,
int item_priority = -1,
bit lock_request = 0);
uvm_sequence_request req_s;
int my_seq_id;
if (sequence_ptr == null)
uvm_report_fatal("uvm_sequencer",
"wait_for_grant passed null sequence_ptr", UVM_NONE);
my_seq_id = m_register_sequence(sequence_ptr);
// If lock_request is asserted, then issue a lock. Don't wait for the response, since
// there is a request immediately following the lock request
if (lock_request == 1) begin
req_s = new();
req_s.grant = 0;
req_s.sequence_id = my_seq_id;
req_s.request = SEQ_TYPE_LOCK;
req_s.sequence_ptr = sequence_ptr;
req_s.request_id = g_request_id++;
req_s.process_id = process::self();
arb_sequence_q.push_back(req_s);
end
// Push the request onto the queue
req_s = new();
req_s.grant = 0;
req_s.request = SEQ_TYPE_REQ;
req_s.sequence_id = my_seq_id;
req_s.item_priority = item_priority;
req_s.sequence_ptr = sequence_ptr;
req_s.request_id = g_request_id++;
req_s.process_id = process::self();
arb_sequence_q.push_back(req_s);
m_update_lists();
// Wait until this entry is granted
// Continue to point to the element, since location in queue will change
m_wait_for_arbitration_completed(req_s.request_id);
// The wait_for_grant_semaphore is used only to check that send_request
// is only called after wait_for_grant. This is not a complete check, since
// requests might be done in parallel, but it will catch basic errors
req_s.sequence_ptr.m_wait_for_grant_semaphore++;
endtask
这个任务有三个参数,分别是等待被仲裁的sequence实例,priority和是否为lock request。首先传入的sequence实例指针不能为mull,然后调用m_register_sequence()函数拿到其sequence id,若这是一个lock request,则创建一个uvm_sequence_request类型实例req_s,把它的request设置为SEQ_TYPE_LOCK类型,并且把sequence_id,新的request_id(原request_id基础上加1)等信息储存起来,然后放入队列arb_sequence_q[]。然后创建一个新的uvm_sequence_request类型实例,把它的request设置为SEQ_TYPE_REQ类型并把sequence_id,priority,新的request_id等信息储存起来放入队列arb_sequence_q[],这个队列定义在uvm_sequencer_base中,用来存放等待被仲裁的所有uvm_sequence_request类型实例句柄,随后调用m_update_lists()来把int型变量m_lock_arb_size值加1,这个变量同样定义在uvm_sequencer_base中,它和int型变量m_arb_size配合用来判断当前仲裁队列arb_sequence_q[]中是否有任何更新。
//uvm_sequencer_base.svh
class uvm_sequencer_base extends uvm_component;
// queue of sequences waiting for arbitration
protected uvm_sequence_request arb_sequence_q[$];
protected int m_lock_arb_size; // used for waiting processes
protected int m_arb_size;
...
endclass
function void uvm_sequencer_base::m_update_lists();
m_lock_arb_size++;
endfunction
随后将当前request的sequence_id作为参数调用m_wait_for_arbitration_completed()等待当前request仲裁结束:
//uvm_sequencer_base.svh
class uvm_sequencer_base extends uvm_component;
protected bit arb_completed[int];
...
endclass
task uvm_sequencer_base::m_wait_for_arbitration_completed(int request_id);
int lock_arb_size;
// Search the list of arb_wait_q, see if this item is done
forever
begin
lock_arb_size = m_lock_arb_size;
if (arb_completed.exists(request_id)) begin
arb_completed.delete(request_id);
return;
end
wait (lock_arb_size != m_lock_arb_size);
end
endtask
这个任务内部是一个forever循环,每当仲裁队列有更新时,他就会检查数组arb_completed[]中是否有以传入的request_id为索引的内容,若找到则将其删除并返回。联合数组arb_completed[]定义在uvm_sequencer_base类中,以int类型为索引储存bit类型的值,用来记录哪些sequence_id已经仲裁完成。很明显,在当前这笔request被仲裁之前,m_wait_for_arbitration_completed()会一直等在这里,一旦仲裁完成就会返回,然后相应sequence的变量m_wait_for_grant_semaphore会被加1,这个int型变量定义在uvm_sequence_base类中,在sequence调用new()的时候会被初始化为0,仅仅是用来保证sequencer在调用send_request()之前需要先调用wait_for_grant()。
当我们在sequence中调用start_item(),其中会调用wait_for_grant()来创建一些新的request并且调用m_wait_for_arbitration_completed()来等待数组arb_completed[]被更新,那这个事情谁来做呢?我们一般在driver的run_phase中会调用get_next_item():seq_item_port.get_next_item(req);实际上这里调用的是uvm_sequencer中的get_next_item()任务:
//uvm_sequencer.svh
class uvm_sequencer #(type REQ=uvm_sequence_item, RSP=REQ)
extends uvm_sequencer_param_base #(REQ, RSP);
bit sequence_item_requested;
bit get_next_item_called;
...
endclass
task uvm_sequencer::get_next_item(output REQ t);
REQ req_item;
// If a sequence_item has already been requested, then get_next_item()
// should not be called again until item_done() has been called.
if (get_next_item_called == 1)
uvm_report_error(get_full_name(),
"Get_next_item called twice without item_done or get in between", UVM_NONE);
if (!sequence_item_requested)
m_select_sequence();
// Set flag indicating that the item has been requested to ensure that item_done or get
// is called between requests
sequence_item_requested = 1;
get_next_item_called = 1;
m_req_fifo.peek(t);
endtask
其中的bit类型变量sequence_item_requested和get_next_item_called在每次调用get_next_item()之后都会被置为1,在item_done()被调用时会把它们清零,防止在item_done()被调用之前多次调用get_next_item()引发问题。这其中最关键的就是调用m_select_sequence()任务,在这个任务中会真正进行所有request的仲裁工作。最后从m_req_fifo中试图拿到一笔sequence_item,这是一个定义在uvm_sequencer_param_base #()中的uvm_tlm_fifo #(REQ)类型接口,sequence_item就是通过这个接口来传输。来看m_select_sequence():
//uvm_sequencer_base.svh
task uvm_sequencer_base::m_select_sequence();
int selected_sequence;
// Select a sequence
do begin
wait_for_sequences();
selected_sequence = m_choose_next_request();
if (selected_sequence == -1) begin
m_wait_for_available_sequence();
end
end while (selected_sequence == -1);
// issue grant
if (selected_sequence >= 0) begin
m_set_arbitration_completed(arb_sequence_q[selected_sequence].request_id);
arb_sequence_q.delete(selected_sequence);
m_update_lists();
end
endtask
这个任务定义在uvm_sequencer_base类中,其中的while循环中会首先调用wait_for_sequences()任务,这个任务就是单纯的UVM仿真time slot延时,目的是为了消除可能存在的竞争冒险,可以略过。然后调用m_choose_next_request()函数来选择一个sequence,若返回的sequence_id为默认值-1,说明当前仲裁队列中没有合适的request,接着调用任务m_wait_for_available_sequence()来等待仲裁队列request更新。一旦拿到有效的request,则会调用m_set_arbitration_completed()把数组arb_completed[]相应记录更新,然后将这个request从队列arb_sequence_q[$]中删除并更新m_lock_arb_size的值。先来看函数m_choose_next_request():
//uvm_sequencer_base.svh
function int uvm_sequencer_base::m_choose_next_request();
int i, temp;
int avail_sequence_count;
int sum_priority_val;
integer avail_sequences[$];
integer highest_sequences[$];
int highest_pri;
string s;
avail_sequence_count = 0;
grant_queued_locks();
i = 0;
while (i < arb_sequence_q.size()) begin
if ((arb_sequence_q[i].process_id.status == process::KILLED) ||
(arb_sequence_q[i].process_id.status == process::FINISHED)) begin
`uvm_error("SEQREQZMB", $sformatf("The task responsible for requesting a wait_for_grant on sequencer '%s' for sequence '%s' has been killed, to avoid a deadlock the sequence will be removed from the arbitration queues", this.get_full_name(), arb_sequence_q[i].sequence_ptr.get_full_name()))
remove_sequence_from_queues(arb_sequence_q[i].sequence_ptr);
continue;
end
if (i < arb_sequence_q.size())
if (arb_sequence_q[i].request == SEQ_TYPE_REQ)
if (is_blocked(arb_sequence_q[i].sequence_ptr) == 0)
if (arb_sequence_q[i].sequence_ptr.is_relevant() == 1) begin
if (m_arbitration == UVM_SEQ_ARB_FIFO) begin
return i;
end
else avail_sequences.push_back(i);
end
i++;
end
// Return immediately if there are 0 or 1 available sequences
if (m_arbitration == UVM_SEQ_ARB_FIFO) begin
return -1;
end
if (avail_sequences.size() < 1) begin
return -1;
end
if (avail_sequences.size() == 1) begin
return avail_sequences[0];
end
// If any locks are in place, then the available queue must
// be checked to see if a lock prevents any sequence from proceeding
if (lock_list.size() > 0) begin
for (i = 0; i < avail_sequences.size(); i++) begin
if (is_blocked(arb_sequence_q[avail_sequences[i]].sequence_ptr) != 0) begin
avail_sequences.delete(i);
i--;
end
end
if (avail_sequences.size() < 1)
return -1;
if (avail_sequences.size() == 1)
return avail_sequences[0];
end
// Weighted Priority Distribution
// Pick an available sequence based on weighted priorities of available sequences
if (m_arbitration == UVM_SEQ_ARB_WEIGHTED) begin
sum_priority_val = 0;
for (i = 0; i < avail_sequences.size(); i++) begin
sum_priority_val += m_get_seq_item_priority(arb_sequence_q[avail_sequences[i]]);
end
temp = $urandom_range(sum_priority_val-1, 0);
sum_priority_val = 0;
for (i = 0; i < avail_sequences.size(); i++) begin
if ((m_get_seq_item_priority(arb_sequence_q[avail_sequences[i]]) +
sum_priority_val) > temp) begin
return avail_sequences[i];
end
sum_priority_val += m_get_seq_item_priority(arb_sequence_q[avail_sequences[i]]);
end
uvm_report_fatal("Sequencer", "UVM Internal error in weighted arbitration code", UVM_NONE);
end
// Random Distribution
if (m_arbitration == UVM_SEQ_ARB_RANDOM) begin
i = $urandom_range(avail_sequences.size()-1, 0);
return avail_sequences[i];
end
// Strict Fifo
if ((m_arbitration == UVM_SEQ_ARB_STRICT_FIFO) || m_arbitration == UVM_SEQ_ARB_STRICT_RANDOM) begin
highest_pri = 0;
// Build a list of sequences at the highest priority
for (i = 0; i < avail_sequences.size(); i++) begin
if (m_get_seq_item_priority(arb_sequence_q[avail_sequences[i]]) > highest_pri) begin
// New highest priority, so start new list
highest_sequences.delete();
highest_sequences.push_back(avail_sequences[i]);
highest_pri = m_get_seq_item_priority(arb_sequence_q[avail_sequences[i]]);
end
else if (m_get_seq_item_priority(arb_sequence_q[avail_sequences[i]]) == highest_pri) begin
highest_sequences.push_back(avail_sequences[i]);
end
end
// Now choose one based on arbitration type
if (m_arbitration == UVM_SEQ_ARB_STRICT_FIFO) begin
return(highest_sequences[0]);
end
i = $urandom_range(highest_sequences.size()-1, 0);
return highest_sequences[i];
end
if (m_arbitration == UVM_SEQ_ARB_USER) begin
i = user_priority_arbitration( avail_sequences);
// Check that the returned sequence is in the list of available sequences. Failure to
// use an available sequence will cause highly unpredictable results.
highest_sequences = avail_sequences.find with (item == i);
if (highest_sequences.size() == 0) begin
uvm_report_fatal("Sequencer",
$sformatf("Error in User arbitration, sequence %0d not available\n%s",
i, convert2string()), UVM_NONE);
end
return(i);
end
uvm_report_fatal("Sequencer", "Internal error: Failed to choose sequence", UVM_NONE);
endfunction
这个函数会首先调用grant_queued_locks():
//uvm_sequencer_base.svh
function void uvm_sequencer_base::grant_queued_locks();
// first remove sequences with dead lock control process
begin
uvm_sequence_request q[$];
q = arb_sequence_q.find(item) with (item.request==SEQ_TYPE_LOCK && item.process_id.status inside {process::KILLED,process::FINISHED});
foreach(q[idx]) begin
`uvm_error("SEQLCKZMB", $sformatf("The task responsible for requesting a lock on sequencer '%s' for sequence '%s' has been killed, to avoid a deadlock the sequence will be removed from the arbitration queues", this.get_full_name(), q[idx].sequence_ptr.get_full_name()))
remove_sequence_from_queues(q[idx].sequence_ptr);
end
end
// now move all is_blocked() into lock_list
begin
uvm_sequence_request leading_lock_reqs[$],blocked_seqs[$],not_blocked_seqs[$];
int q1[$];
int b=arb_sequence_q.size(); // index for first non-LOCK request
q1 = arb_sequence_q.find_first_index(item) with (item.request!=SEQ_TYPE_LOCK);
if(q1.size())
b=q1[0];
if(b!=0) begin // at least one lock
leading_lock_reqs = arb_sequence_q[0:b-1]; // set of locks; arb_sequence[b] is the first req!=SEQ_TYPE_LOCK
// split into blocked/not-blocked requests
foreach(leading_lock_reqs[i]) begin
uvm_sequence_request item = leading_lock_reqs[i];
if(is_blocked(item.sequence_ptr)!=0) begin
blocked_seqs.push_back(item);
end
else begin
not_blocked_seqs.push_back(item);
lock_list.push_back(item.sequence_ptr); // added per Mantis 5005
end
end
if(b>arb_sequence_q.size()-1)
arb_sequence_q=blocked_seqs;
else
arb_sequence_q={blocked_seqs,arb_sequence_q[b:arb_sequence_q.size()-1]};
foreach(not_blocked_seqs[idx]) begin
//lock_list.push_back(not_blocked_seqs[idx].sequence_ptr); // taken out per Mantis 5005
m_set_arbitration_completed(not_blocked_seqs[idx].request_id);
end
// trigger listeners if lock list has changed
if(not_blocked_seqs.size())
m_update_lists();
end
end
endfunction
函数grant_queued_locks()会首先在整个request仲裁队列arb_sequence_q[$]中寻找是否有request为SEQ_TYPE_LOCK类型并且其当前process_state处于KILLED或者FINISHED状态,然后调用remove_sequence_from_queues()来删除这些requests:
//uvm_sequencer_base.svh
function void uvm_sequencer_base::remove_sequence_from_queues(
uvm_sequence_base sequence_ptr);
int i;
int seq_id;
seq_id = sequence_ptr.m_get_sqr_sequence_id(m_sequencer_id, 0);
// Remove all queued items for this sequence and any child sequences
i = 0;
do
begin
if (arb_sequence_q.size() > i) begin
if ((arb_sequence_q[i].sequence_id == seq_id) ||
(is_child(sequence_ptr, arb_sequence_q[i].sequence_ptr))) begin
if (sequence_ptr.get_sequence_state() == UVM_FINISHED)
`uvm_error("SEQFINERR", $sformatf("Parent sequence '%s' should not finish before all items from itself and items from descendent sequences are processed. The item request from the sequence '%s' is being removed.", sequence_ptr.get_full_name(), arb_sequence_q[i].sequence_ptr.get_full_name()))
arb_sequence_q.delete(i);
m_update_lists();
end
else begin
i++;
end
end
end
while (i < arb_sequence_q.size());
// remove locks for this sequence, and any child sequences
i = 0;
do
begin
if (lock_list.size() > i) begin
if ((lock_list[i].get_inst_id() == sequence_ptr.get_inst_id()) ||
(is_child(sequence_ptr, lock_list[i]))) begin
if (sequence_ptr.get_sequence_state() == UVM_FINISHED)
`uvm_error("SEQFINERR", $sformatf("Parent sequence '%s' should not finish before locks from itself and descedent sequences are removed. The lock held by the child sequence '%s' is being removed.",sequence_ptr.get_full_name(), lock_list[i].get_full_name()))
lock_list.delete(i);
m_update_lists();
end
else begin
i++;
end
end
end
while (i < lock_list.size());
// Unregister the sequence_id, so that any returning data is dropped
m_unregister_sequence(sequence_ptr.m_get_sqr_sequence_id(m_sequencer_id, 1));
endfunction
函数remove_sequence_from_queues()很简单,若当前传入的sequence指针及其子sequence的m_state为UVM_FINISHED状态,表明其已经执行完毕,将它们分别从仲裁队列arb_sequence_q[$]和lock_list[$]以及数组reg_sequences[]中删除,并且更新m_lock_arb_size的值。队列lock_list[$]设定义在uvm_sequencer_base类中的uvm_sequence_base类型的队列,储存的是当前sequencer被哪些sequence锁定。
//uvm_sequencer_base.svh
class uvm_sequencer_base extends uvm_component;
protected uvm_sequence_base lock_list[$];
...
endclass
我们可以在sequence中调用lock()/grab()来获得sequencer的优先使用权并一直占有该sequencer,直到调用unlock()/ungrab()来解除独占权。
//uvm_sequencer_base.svh
task uvm_sequencer_base::m_lock_req(uvm_sequence_base sequence_ptr, bit lock);
int my_seq_id;
uvm_sequence_request new_req;
if (sequence_ptr == null)
uvm_report_fatal("uvm_sequence_controller",
"lock_req passed null sequence_ptr", UVM_NONE);
my_seq_id = m_register_sequence(sequence_ptr);
new_req = new();
new_req.grant = 0;
new_req.sequence_id = sequence_ptr.get_sequence_id();
new_req.request = SEQ_TYPE_LOCK;
new_req.sequence_ptr = sequence_ptr;
new_req.request_id = g_request_id++;
new_req.process_id = process::self();
if (lock == 1) begin
// Locks are arbitrated just like all other requests
arb_sequence_q.push_back(new_req);
end else begin
// Grabs are not arbitrated - they go to the front
// TODO:
// Missing: grabs get arbitrated behind other grabs
arb_sequence_q.push_front(new_req);
m_update_lists();
end
// If this lock can be granted immediately, then do so.
grant_queued_locks();
m_wait_for_arbitration_completed(new_req.request_id);
endtask
// m_unlock_req
// ------------
// Called by a sequence to request an unlock. This
// will remove a lock for this sequence if it exists
function void uvm_sequencer_base::m_unlock_req(uvm_sequence_base sequence_ptr);
if (sequence_ptr == null) begin
uvm_report_fatal("uvm_sequencer",
"m_unlock_req passed null sequence_ptr", UVM_NONE);
end
begin
int q[$];
int seqid=sequence_ptr.get_inst_id();
q=lock_list.find_first_index(item) with (item.get_inst_id() == seqid);
if(q.size()==1) begin
lock_list.delete(q[0]);
grant_queued_locks(); // grant lock requests
m_update_lists();
end
else
uvm_report_warning("SQRUNL",
{"Sequence '", sequence_ptr.get_full_name(),
"' called ungrab / unlock, but didn't have lock"}, UVM_NONE);
end
endfunction
// lock
// ----
task uvm_sequencer_base::lock(uvm_sequence_base sequence_ptr);
m_lock_req(sequence_ptr, 1);
endtask
// grab
// ----
task uvm_sequencer_base::grab(uvm_sequence_base sequence_ptr);
m_lock_req(sequence_ptr, 0);
endtask
// unlock
// ------
function void uvm_sequencer_base::unlock(uvm_sequence_base sequence_ptr);
m_unlock_req(sequence_ptr);
endfunction
// ungrab
// ------
function void uvm_sequencer_base::ungrab(uvm_sequence_base sequence_ptr);
m_unlock_req(sequence_ptr);
endfunction
lock()和grab()都是调用m_lock_req()任务,二者唯一的区别就是传入的第二个参数lock前者为1而后者为0,在m_lock_req()中会创建一个uvm_sequence_request实例,把request设置为SEQ_TYPE_LOCK类型,接下来若参数lock为1,则把该实例指针放到队列arb_sequence_q[$]最后,否则放在队列最前面并更新m_lock_arb_size的值。这么做的区别在于:
lock()操作需要等待当前sequence和其它先提起lock/grab请求的sequences执行完毕才轮到自己。而grab()操作只需要等待当前sequence执行完毕就会开始占有sequencer,不必等待其它先提起lock/grab请求的sequences。
在解除占用的时候函数unlock()和ungrab()是一样的,都会调用m_unlock_req(),这个函数会把参数传入的sequence句柄从lock_list[$]中删除,然后调用grant_queued_locks()并且更新m_lock_arb_size的值。
回到grant_queued_locks()中,函数检查队列arb_sequence_q[$]中是否存在request不为SEQ_TYPE_LOCK类型的请求,这里有三种情况:
- 队列arb_sequence_q[$]中全部都是SEQ_TYPE_LOCK类型的请求,此时函数会遍历这个队列调用is_blocked()来检查lock_list[$]中是否有相关记录,若lock_list[$]为空会直接返回0,若记录不是当前检查的这个sequence或其parent sequence,说明有其它sequence提交了lock请求,而当前的这个请求被block住了,is_blocked()会返回1,那么把这个request放入blocked_seqs[$],否则把这个request分别放入队列not_blocked_seqs[$]和lock_list[$](进入队列lock_list[$]的请求就可以独占当前sequencer了),然后把blocked_seqs[$]中的请求转移到arb_sequence_q[$]等待下一次调用grant_queued_locks()被仲裁。对于not_blocked_seqs[$]中的请求,则调用m_set_arbitration_completed()把他们对应的arb_completed[]记录置为1,表明仲裁完成,最后更新m_lock_arb_size的值。
- 队列arb_sequence_q[$]中第一个请求不是SEQ_TYPE_LOCK类型的,其索引为0,则lock操作需要等待该请求执行完毕,等待下次调用grant_queued_locks()被仲裁。
- 队列arb_sequence_q[$]中第一个不是SEQ_TYPE_LOCK类型的请求索引大于0,说明至少有一个SEQ_TYPE_LOCK类型请求排在队列最前面,则调用is_blocked()遍历排在前面的这些SEQ_TYPE_LOCK类型的请求,若返回1,把这个request放入blocked_seqs[$],否则把这个request分别放入队列not_blocked_seqs[$]和lock_list[$]。对于not_blocked_seqs[$]队列中的请求,把它们从仲裁队列arb_sequence_q[$]中删除,调用m_set_arbitration_completed()把它们对应的arb_completed[]记录置为1,表明仲裁完成,最后更新m_lock_arb_size的值。
//uvm_sequencer_base.svh
function bit uvm_sequencer_base::is_blocked(uvm_sequence_base sequence_ptr);
if (sequence_ptr == null)
uvm_report_fatal("uvm_sequence_controller",
"is_blocked passed null sequence_ptr", UVM_NONE);
foreach (lock_list[i]) begin
if ((lock_list[i].get_inst_id() !=
sequence_ptr.get_inst_id()) &&
(is_child(lock_list[i], sequence_ptr) == 0)) begin
return 1;
end
end
return 0;
endfunction
function void uvm_sequencer_base::m_set_arbitration_completed(int request_id);
arb_completed[request_id] = 1;
endfunction
回到函数m_choose_next_request()中,在调用grant_queued_locks()把排在仲裁队列前面的SEQ_TYPE_LOCK类型的请求放入队列lock_list[$]之后,会遍历仲裁队列arb_sequence_q[$]把当前process state为KILLED或者FINISHED的请求调用remove_sequence_from_queues()分别从arb_sequence_q[$]和lock_list[$]中删除。遍历仲裁队列arb_sequence_q[$]时,若request为SEQ_TYPE_REQ,调用is_blocked()返回0,并且is_relevant()返回值为1时,若此时仲裁算法为默认的UVM_SEQ_ARB_FIFO,则直接返回当前的request,因为这是仲裁队列中第一个符合条件的request。若采用别的仲裁算法,则把这些符合上述条件的requests放入队列avail_sequences[$]等待后续算法处理。
这里is_relevant()是定义在uvm_sequence_base类中的函数,表示当前sequence是否可以参与仲裁,默认返回值为1,我们可以在sequence中重载这个函数,决定sequence被执行的时机。若所有的sequence的is_relevant()返回值都是0,sequencer会调用wait_for_relevant()任务来等待sequence再次有效。我们在重载is_relevant()时可以同时重载wait_for_relevant()来决定sequence再次有效的时机。
class uvm_sequence_base extends uvm_sequence_item;
...
virtual function bit is_relevant();
is_rel_default = 1;
return 1;
endfunction
virtual task wait_for_relevant();
event e;
wait_rel_default = 1;
if (is_rel_default != wait_rel_default)
uvm_report_fatal("RELMSM",
"is_relevant() was implemented without defining wait_for_relevant()", UVM_NONE);
@e; // this is intended to never return
endtask
...
endclass
在函数m_choose_next_request()中,若avail_sequences[$]为空则直接返回-1,若其中只有一个request,则直接返回这个request句柄。接着遍历avail_sequences[$],再次调用is_blocked(),若返回值不为0,则从avail_sequences[$]中删除该request。之后若avail_sequences[$]为空则直接返回-1,若其中只有一个request,则直接返回这个request句柄。接着根据不同的仲裁算法在avail_sequences[$]找到相应的request并返回,关于这些算法具体实现,这里不再赘述。
接着来看m_wait_for_available_sequence():
//uvm_sequencer_base.svh
task uvm_sequencer_base::m_wait_for_available_sequence();
int i;
int is_relevant_entries[$];
// This routine will wait for a change in the request list, or for
// wait_for_relevant to return on any non-relevant, non-blocked sequence
m_arb_size = m_lock_arb_size;
for (i = 0; i < arb_sequence_q.size(); i++) begin
if (arb_sequence_q[i].request == SEQ_TYPE_REQ) begin
if (is_blocked(arb_sequence_q[i].sequence_ptr) == 0) begin
if (arb_sequence_q[i].sequence_ptr.is_relevant() == 0) begin
is_relevant_entries.push_back(i);
end
end
end
end
// Typical path - don't need fork if all queued entries are relevant
if (is_relevant_entries.size() == 0) begin
m_wait_arb_not_equal();
return;
end
fork // isolate inner fork block for disabling
begin
fork
begin
fork
begin
// One path in fork is for any wait_for_relevant to return
m_is_relevant_completed = 0;
for(i = 0; i < is_relevant_entries.size(); i++) begin
fork
automatic int k = i;
begin
arb_sequence_q[is_relevant_entries[k]].sequence_ptr.wait_for_relevant();
if ($realtime != m_last_wait_relevant_time) begin
m_last_wait_relevant_time = $realtime ;
m_wait_relevant_count = 0 ;
end
else begin
m_wait_relevant_count++ ;
if (m_wait_relevant_count > m_max_zero_time_wait_relevant_count) begin
`uvm_fatal("SEQRELEVANTLOOP",$sformatf("Zero time loop detected, passed wait_for_relevant %0d times without time advancing",m_wait_relevant_count))
end
end
m_is_relevant_completed = 1;
end
join_none
end
wait (m_is_relevant_completed > 0);
end
// The other path in the fork is for any queue entry to change
begin
m_wait_arb_not_equal();
end
join_any
end
join_any
disable fork;
end
join
endtask
在这个任务中首先遍历队列arb_sequence_q[$]把request为SEQ_TYPE_REQ类型并且调用is_blocked()返回值为0但是is_relevant()返回值为0的requests在队列arb_sequence_q[$]中的索引存入int型队列is_relevant_entries[$],若这个队列为空,则说明当前所有request都是有效的,则调用m_wait_arb_not_equal()等待m_lock_arb_size的值更新(说明仲裁队列arb_sequence_q[$]有更新)后直接返回。若队列is_relevant_entries[$]不为空,说明当前有request是无效的,通过fork…join_any启动并行线程,一方面调用m_wait_arb_not_equal()等待m_lock_arb_size的值更新,若有更新函数会杀掉fork进程立即返回。另一方面对队列is_relevant_entries[$]中所有requests使用fork…join_none在同一时间并行调用wait_for_relevant(),等该函数返回后检查仿真时间是否向前推进,若仿真时间一直没有向前推进,则每次执行到这里m_wait_relevant_count值都会加1,当执行次数超过m_max_zero_time_wait_relevant_count时会报错,m_max_zero_time_wait_relevant_count默认值是10,这段代码是为了防止仿真block在这里,最后把m_is_relevant_completed置为1,表示某个request的wait_for_relevant()已经返回,这样仲裁队列中就会又有relevant的requests,函数也会杀掉fork进程立即返回。
2.5. finish_item()
通常来说,在start_item()之后必须调用finish_item(),中间可以做些随机化的操作,来看finish_item():
//uvm_sequence_base.svh
class uvm_sequence_base extends uvm_sequence_item;
...
virtual task finish_item (uvm_sequence_item item,
int set_priority = -1);
uvm_sequencer_base sequencer;
sequencer = item.get_sequencer();
if (sequencer == null) begin
uvm_report_fatal("STRITM", "sequence_item has null sequencer", UVM_NONE);
end
mid_do(item);
sequencer.send_request(this, item);
sequencer.wait_for_item_done(this, -1);
if (sequencer.is_auto_item_recording_enabled()) begin
sequencer.end_tr(item);
end
`ifndef AMD_DISABLE_SEQ_BASE_END_EVENT_TRIGGER
else begin
item.end_event.trigger();
end
`endif
post_do(item);
endtask
...
endclass
在finish_item()任务中,首先调用get_sequencer()拿到当前sequence_item的m_sequencer句柄,然后执行callback mid_do(),接着分别调用send_request()和wait_for_item_done(),最后执行callback post_do()。来看send_request():
//uvm_sequencer_param_base.svh
function void uvm_sequencer_param_base::send_request(uvm_sequence_base sequence_ptr,
uvm_sequence_item t,
bit rerandomize = 0);
REQ param_t;
if (sequence_ptr == null) begin
uvm_report_fatal("SNDREQ", "Send request sequence_ptr is null", UVM_NONE);
end
if (sequence_ptr.m_wait_for_grant_semaphore < 1) begin
uvm_report_fatal("SNDREQ", "Send request called without wait_for_grant", UVM_NONE);
end
sequence_ptr.m_wait_for_grant_semaphore--;
if ($cast(param_t, t)) begin
if (rerandomize == 1) begin
if (!param_t.randomize()) begin
uvm_report_warning("SQRSNDREQ", "Failed to rerandomize sequence item in send_request");
end
end
if (param_t.get_transaction_id() == -1) begin
param_t.set_transaction_id(sequence_ptr.m_next_transaction_id++);
end
m_last_req_push_front(param_t);
end else begin
uvm_report_fatal(get_name(),$sformatf("send_request failed to cast sequence item"), UVM_NONE);
end
param_t.set_sequence_id(sequence_ptr.m_get_sqr_sequence_id(m_sequencer_id, 1));
t.set_sequencer(this);
if (m_req_fifo.try_put(param_t) != 1) begin
uvm_report_fatal(get_full_name(), "Concurrent calls to get_next_item() not supported. Consider using a semaphore to ensure that concurrent processes take turns in the driver", UVM_NONE);
end
m_num_reqs_sent++;
// Grant any locks as soon as possible
grant_queued_locks();
endfunction
这个函数定义在uvm_sequencer_param_base #()中,首先检查m_wait_for_grant_semaphore是否为0,这个变量就是保证在每次调用send_request()之前已经调用过wait_for_grant(),在每次执行wait_for_grant()时相应request被仲裁之后加1,之后才能调用send_request()并在其中将变量的值减1。若函数第三个参数rerandomize为1,则对sequence_item做随机化操作。这里之所以要重新随机化,是因为若直接对uvm_sequence_item类型的t随机化时,我们真正要发送的继承自uvm_sequence_item类型的REQ类型中的某些变量是不会做随机化的。接着调用get_transaction_id()拿到sequence_item的m_transaction_id,若其为默认值-1,则把当前sequence中的变量m_next_transaction_id值加1作为参数调用set_transaction_id()赋值给m_transaction_id。integer类型变量m_transaction_id定义在uvm_transaction类中,代表每一笔transaction的id。int型变量m_next_transaction_id定义在uvm_sequence_base类中,用来记录当前sequence要发送的transaction的m_transaction_id。
//uvm_transaction.svh
virtual class uvm_transaction extends uvm_object;
...
local integer m_transaction_id = -1;
...
endclass
// set_transaction_id
function void uvm_transaction::set_transaction_id(integer id);
m_transaction_id = id;
endfunction
// get_transaction_id
function integer uvm_transaction::get_transaction_id();
return (m_transaction_id);
endfunction
//uvm_sequence_base.svh
class uvm_sequence_base extends uvm_sequence_item;
...
int m_next_transaction_id = 1;
...
endclass
接下来调用m_last_req_push_front()把要发送的这个sequence_item放入队列m_last_req_buffer[$]:
//uvm_sequencer_param_base.svh
class uvm_sequencer_param_base #(type REQ = uvm_sequence_item,
type RSP = REQ) extends uvm_sequencer_base;
REQ m_last_req_buffer[$];
protected int m_num_last_reqs = 1;
...
endclass
function void uvm_sequencer_param_base::m_last_req_push_front(REQ item);
if(!m_num_last_reqs)
return;
if(m_last_req_buffer.size() == m_num_last_reqs)
void'(m_last_req_buffer.pop_back());
this.m_last_req_buffer.push_front(item);
endfunction
队列m_last_req_buffer[$]定义在uvm_sequencer_param_base #()中,存放的是通过该sequencer发送的最后的一些sequence_item句柄,能存放多少个由int型变量m_num_last_reqs决定,默认值是1,若当前队列已经存满,则把队列最后面的一个sequence_item句柄弹出,将当前要发送的sequence_item存入队列最前面位置。
回到函数send_request()中,设置好要发送sequence_item的sequence_id,调用try_put()向m_req_fifo中放入要发送的sequence_item,sequencer的get_next_item()会从中取走它交在driver。接下来int型变量m_num_reqs_sent的值加1,用来记录当前发送了多少个sequence_item,最后调用grant_queued_locks(),尽快处理仲裁队列中出现的lock请求。
回到finish_item()中来,当send_request()将sequence_item发送出去之后,接着调用wait_for_item_done():
//uvm_sequencer_base.svh
task uvm_sequencer_base::wait_for_item_done(uvm_sequence_base sequence_ptr,
int transaction_id);
int sequence_id;
sequence_id = sequence_ptr.m_get_sqr_sequence_id(m_sequencer_id, 1);
m_wait_for_item_sequence_id = -1;
m_wait_for_item_transaction_id = -1;
if (transaction_id == -1)
wait (m_wait_for_item_sequence_id == sequence_id);
else
wait ((m_wait_for_item_sequence_id == sequence_id &&
m_wait_for_item_transaction_id == transaction_id));
endtask
这个函数定义在uvm_sequencer_base中,在其中会把两个变量m_wait_for_item_sequence_id和m_wait_for_item_transaction_id分别置为-1,然后等待sequence_id和transaction_id的值被复位为-1,实际上是在等待item_done()被调用,我们后面会讨论。
因而,start_item()与finish_item()中各函数/任务执行顺序如下:
//| sequencer.wait_for_grant(prior) (task) \ start_item \
//| parent_seq.pre_do(1) (task) / \
//| `uvm_do* macros
//| parent_seq.mid_do(item) (func) \ /
//| sequencer.send_request(item) (func) \ finish_item /
//| sequencer.wait_for_item_done() (task) /
//| parent_seq.post_do(item) (func) /
3. sequencer与driver的交互
3.1. put_response()
我们在driver中调用get_next_item()拿到sequence_item并做出相应处理之后,要接着调用rsp.set_id_info(req)把request的transaction_id和sequence_id设置给response,然后调用seq_item_port.item_done(rsp),这里实际上执行的是uvm_sequencer的item_done():
//uvm_sequencer.svh
function void uvm_sequencer::item_done(RSP item = null);
REQ t;
// Set flag to allow next get_next_item or peek to get a new sequence_item
sequence_item_requested = 0;
get_next_item_called = 0;
if (m_req_fifo.try_get(t) == 0) begin
uvm_report_fatal(get_full_name(), {"Item_done() called with no outstanding requests.",
" Each call to item_done() must be paired with a previous call to get_next_item()."});
end else begin
m_wait_for_item_sequence_id = t.get_sequence_id();
m_wait_for_item_transaction_id = t.get_transaction_id();
end
if (item != null) begin
seq_item_export.put_response(item);
end
// Grant any locks as soon as possible
grant_queued_locks();
endfunction
首先从m_req_fifo试图拿到一个sequence_item,若成功则把其sequence_id和transaction_id分别赋值给m_wait_for_item_sequence_id和m_wait_for_item_transaction_id表示已经成功取到该sequence_id对应的这笔transaction,此时wait_for_item_done()中的wait()语句被触发。若参数传入的item句柄不为null,则调用 seq_item_export.put_response(),最后再次仲裁队列中的lock请求。这里实际上调用的是uvm_sequencer_param_base #()中的put_response()函数:
//uvm_sequencer_param_base.svh
function void uvm_sequencer_param_base::put_response (RSP t);
uvm_sequence_base sequence_ptr;
if (t == null) begin
uvm_report_fatal("SQRPUT", "Driver put a null response", UVM_NONE);
end
m_last_rsp_push_front(t);
m_num_rsps_received++;
// Check that set_id_info was called
if (t.get_sequence_id() == -1) begin
`ifndef CDNS_NO_SQR_CHK_SEQ_ID
uvm_report_fatal("SQRPUT", "Driver put a response with null sequence_id", UVM_NONE);
`endif
return;
end
sequence_ptr = m_find_sequence(t.get_sequence_id());
if (sequence_ptr != null) begin
// If the response_handler is enabled for this sequence, then call the response handler
if (sequence_ptr.get_use_response_handler() == 1) begin
sequence_ptr.response_handler(t);
return;
end
sequence_ptr.put_response(t);
end
else begin
uvm_report_info("Sequencer",
$sformatf("Dropping response for sequence %0d, sequence not found. Probable cause: sequence exited or has been killed",
t.get_sequence_id()));
end
endfunction
function void uvm_sequencer_param_base::m_last_rsp_push_front(RSP item);
if(!m_num_last_rsps)
return;
if(m_last_rsp_buffer.size() == m_num_last_rsps)
void'(m_last_rsp_buffer.pop_back());
this.m_last_rsp_buffer.push_front(item);
endfunction
函数开始调用m_last_rsp_push_front(),这个跟之前的m_last_req_push_front()函数类似,把最后发送的response存放在RSP类型的队列m_last_rsp_buffer[$]中。用int型变量m_num_rsps_received记录发送的response数量。接着检查参数item的sequence_id是否为-1,若是(说明在driver中调用item_done()之前没有调用set_id_info()为response设置相关信息),函数直接返回。接下来若sequence的get_use_response_handler()返回1,则直接调用response_handler()并返回,我们后面会研究这个response_handler。否则,调用sequence的put_response():
//uvm_sequence_base.svh
class uvm_sequence_base extends uvm_sequence_item;
...
protected uvm_sequence_item response_queue[$];
protected int response_queue_depth = 8;
...
virtual function void put_base_response(input uvm_sequence_item response);
if ((response_queue_depth == -1) ||
(response_queue.size() < response_queue_depth)) begin
response_queue.push_back(response);
return;
end
if (response_queue_error_report_disabled == 0) begin
uvm_report_error(get_full_name(), "Response queue overflow, response was dropped", UVM_NONE);
end
endfunction
virtual function void put_response (uvm_sequence_item response_item);
put_base_response(response_item); // no error-checking
endfunction
...
endclass
这个函数会直接调用put_base_response(),这里会首先判断负责存放response的uvm_sequence_item类型的队列response_queue[$]的大小,其大小由int型变量 response_queue_depth决定,默认值是8,若response_queue_depth的值是-1,表示该队列可以无限存放response,若当前队列未满,则向其中存入通过参数传入的response句柄。
3.2. get_response()
我们在sequence中发送完一个item之后等待driver返回一个response,通常会调用get_response():
//uvm_sequence.svh
virtual class uvm_sequence #(type REQ = uvm_sequence_item,
type RSP = REQ) extends uvm_sequence_base;
...
virtual task get_response(output RSP response, input int transaction_id = -1);
uvm_sequence_item rsp;
get_base_response( rsp, transaction_id);
$cast(response,rsp);
endtask
...
endclass
任务get_response()会直接调用get_base_response():
//uvm_sequence_base.svh
class uvm_sequence_base extends uvm_sequence_item;
...
virtual task get_base_response(output uvm_sequence_item response, input int transaction_id = -1);
int queue_size, i;
if (response_queue.size() == 0)
wait (response_queue.size() != 0);
if (transaction_id == -1) begin
response = response_queue.pop_front();
return;
end
forever begin
queue_size = response_queue.size();
for (i = 0; i < queue_size; i++) begin
if (response_queue[i].get_transaction_id() == transaction_id)
begin
$cast(response,response_queue[i]);
response_queue.delete(i);
return;
end
end
wait (response_queue.size() != queue_size);
end
endtask
...
endclass
若当前response_queue[$]队列中没有response,则先等待直到有response被放入该队列。通过第二个参数transaction_id可以拿到与之相对于的response,若参数为默认值-1,则从response_queue[$]最前面取出一个response并返回。若指定了参数transaction_id不为-1,则每次response_queue[$]有更新,就遍历该队列找到与之匹配的response并将其从队列中删除。
3.3. response_handler
通过之前的分析可以看出,在sequence中通过get_response()来取得response依赖于put_response()把response放入response_queue[$],若driver中response产生不及时,则sequence中get_response()就会一直阻塞在那里,进而导致无法放下一笔transaction,而driver也会被阻塞在这里。此时可以使用response_handler,将get_response()和put_response()的依赖性分开,在之前分析的uvm_sequencer_param_base #()::put_response()中,若sequence调用get_use_response_handler()返回值为1,会直接执行response_handler()函数并返回,而不会再去sequence的put_response()函数,从而可以避免等待response_queue[$]中的response。
//uvm_sequence_base.svh
class uvm_sequence_base extends uvm_sequence_item;
...
local bit m_use_response_handler;
...
function void use_response_handler(bit enable);
m_use_response_handler = enable;
endfunction
function bit get_use_response_handler();
return m_use_response_handler;
endfunction
virtual function void response_handler(uvm_sequence_item response);
return;
endfunction
...
endclass
函数get_use_response_handler()会返回变量m_use_response_handler的值,这是一个定义在uvm_sequence_base类中的bit型变量,默认值是0,我们可以通过调用use_response_handler(1)把变量m_use_response_handler置为1,同时可以通过重载函数response_handler()来处理每一笔response。
4. 其它
4.1. p_sequencer
由于sequence是一个object类型,在仿真过程中是动态分配内存的,我们若想从sequence中访问UVM hireachy中其它component节点中的资源非常困难,因此需要一个中介句柄,其对应的sequencer恰巧就可以作为一个component类型的句柄。刚好在sequence中有一个指向其对应sequencer的uvm_sequencer_base类型的句柄m_sequencer,我们可以在sequence中使用m_sequencer这个句柄来访问UVM平台其它component节点的资源。但是这么做还存在一个问题,由于m_sequencer是uvm_sequencer_base类型,我们平台中使用的sequencer是从uvm_sequencer_base类型继承而来的,这样m_sequencer是没有办法访问到我们扩展类sequencer中自定义的一些变量。例如我们现在定义一个sequencer:
class my_sqr extends uvm_sequencer;
int num;
...
endclass
很显然m_sequencer是没有办法访问my_sqr中的num的。为了解决这个问题,我们可以使用p_sequencer,在sequence中使用宏`uvm_declare_p_sequencer(my_sqr)来声明当前sequence的p_sequencer为my_sqr类型:
//uvm_sequence_defines.svh
`define uvm_declare_p_sequencer(SEQUENCER) \
SEQUENCER p_sequencer;\
virtual function void m_set_p_sequencer();\
super.m_set_p_sequencer(); \
if( !$cast(p_sequencer, m_sequencer)) \
`uvm_fatal("DCLPSQ", \
$sformatf("%m %s Error casting p_sequencer, please verify that this sequence/sequence item is intended to execute on this type of sequencer", get_full_name())) \
endfunction
这个宏首先声明一个通过参数传入的SEQUENCER类型的p_sequencer句柄,然后重载函数m_set_p_sequencer(),在其中把m_sequencer赋值给p_sequencer句柄,这样可以使用p_sequencer句柄来访问my_sqr的变量num。函数m_set_p_sequencer()定义在uvm_sequence_item类中,当其被重载后,在调用set_sequencer()时会调用这个函数来同时设置m_sequencer和p_sequencer,而set_sequencer()又会在set_item_context()执行时被调用。在执行start(),start_item(),create_item()等时都会调用set_item_context(),最终会调用set_sequencer()来设置m_sequencer和p_sequencer。
4.2. default_sequence
前面提到过我们可以通过uvm_config_db机制把某个sequence设置为其对应sequencer的某task phase的default_sequence,UVM phase机制主要执行m_traverse()函数(参看UVM phase机制源码探微),来看task phase的m_traverse()函数:
//uvm_task_phase.svh
virtual class uvm_task_phase extends uvm_phase;
...
function void m_traverse(uvm_component comp,
uvm_phase phase,
uvm_phase_state state);
uvm_sequencer_base seqr;
...
case (state)
UVM_PHASE_STARTED: begin
...
if ($cast(seqr, comp))
seqr.start_phase_sequence(phase);
end
...
endclass
在phase_state为UVM_PHASE_STARTED阶段,会判断当前的component是否是一个uvm_sequencer_base类型(即是否为一个sequencer),若是,则调用该sequencer的start_phase_sequence():
//uvm_sequencer_base.svh
function void uvm_sequencer_base::start_phase_sequence(uvm_phase phase);
uvm_resource_pool rp = uvm_resource_pool::get();
uvm_resource_types::rsrc_q_t rq;
uvm_sequence_base seq;
uvm_coreservice_t cs = uvm_coreservice_t::get();
uvm_factory f = cs.get_factory();
// Has a default sequence been specified?
rq = rp.lookup_name({get_full_name(), ".", phase.get_name(), "_phase"},
"default_sequence", null, 0);
uvm_resource_pool::sort_by_precedence(rq);
// Look for the first one if the appropriate type
for (int i = 0; seq == null && i < rq.size(); i++) begin
uvm_resource_base rsrc = rq.get(i);
uvm_resource#(uvm_sequence_base) sbr;
uvm_resource#(uvm_object_wrapper) owr;
// uvm_config_db#(uvm_sequence_base)?
// Priority is given to uvm_sequence_base because it is a specific sequence instance
// and thus more specific than one that is dynamically created via the
// factory and the object wrapper.
if ($cast(sbr, rsrc) && sbr != null) begin
seq = sbr.read(this);
if (seq == null) begin
`uvm_info("UVM/SQR/PH/DEF/SB/NULL", {"Default phase sequence for phase '",
phase.get_name(),"' explicitly disabled"}, UVM_FULL)
return;
end
end
// uvm_config_db#(uvm_object_wrapper)?
else if ($cast(owr, rsrc) && owr != null) begin
uvm_object_wrapper wrapper;
wrapper = owr.read(this);
if (wrapper == null) begin
`uvm_info("UVM/SQR/PH/DEF/OW/NULL", {"Default phase sequence for phase '",
phase.get_name(),"' explicitly disabled"}, UVM_FULL)
return;
end
if (!$cast(seq, f.create_object_by_type(wrapper, get_full_name(),
wrapper.get_type_name()))
|| seq == null) begin
`uvm_warning("PHASESEQ", {"Default sequence for phase '",
phase.get_name(),"' %s is not a sequence type"})
return;
end
end
end
if (seq == null) begin
`uvm_info("PHASESEQ", {"No default phase sequence for phase '",
phase.get_name(),"'"}, UVM_FULL)
return;
end
`uvm_info("PHASESEQ", {"Starting default sequence '",
seq.get_type_name(),"' for phase '", phase.get_name(),"'"}, UVM_FULL)
seq.print_sequence_info = 1;
seq.set_sequencer(this);
seq.reseed();
seq.set_starting_phase(phase);
if (!seq.do_not_randomize && !seq.randomize()) begin
`uvm_warning("STRDEFSEQ", {"Randomization failed for default sequence '",
seq.get_type_name(),"' for phase '", phase.get_name(),"'"})
return;
end
fork begin
uvm_sequence_process_wrapper w = new();
// reseed this process for random stability
w.pid = process::self();
w.seq = seq;
w.pid.srandom(uvm_create_random_seed(seq.get_type_name(), this.get_full_name()));
m_default_sequences[phase] = w;
// this will either complete naturally, or be killed later
seq.start(this);
m_default_sequences.delete(phase);
end
join_none
endfunction
在这个函数里首先判断是否有uvm_sequence_base类型通过uvm_config_db被设置为"default_sequence",然后用factory机制拿到sequence的一个实例句柄,为其设置sequencer等信息,然后调用set_starting_phase()函数为starting_phase赋值,最后用fork…join_none启动新线程,在其中启动新的进程调用start()来执行这个sequence。来看set_starting_phase()函数:
//uvm_sequence_base.svh
class uvm_sequence_base extends uvm_sequence_item;
...
`ifndef UVM_NO_DEPRECATED
`define UVM_DEPRECATED_STARTING_PHASE
`endif
`ifdef UVM_DEPRECATED_STARTING_PHASE
// DEPRECATED!! Use get/set_starting_phase accessors instead!
uvm_phase starting_phase;
// Value set via set_starting_phase
uvm_phase m_set_starting_phase;
// Ensures we only warn once per sequence
bit m_warn_deprecated_set;
`endif
...
function void set_starting_phase(uvm_phase phase);
`ifdef UVM_DEPRECATED_STARTING_PHASE
begin
if (starting_phase != m_set_starting_phase) begin
if (!m_warn_deprecated_set) begin
`uvm_warning("UVM_DEPRECATED",
{"The deprecated 'starting_phase' variable has been set to '",
starting_phase.get_full_name(),
"' manually. See documentation for uvm_sequence_base::set_starting_phase."})
m_warn_deprecated_set = 1;
end
starting_phase = phase;
m_set_starting_phase = phase;
end
end
`endif
m_starting_phase_dap.set(phase);
endfunction : set_starting_phase
...
endclass
这个函数定义在uvm_sequence_base类中,被UVM_DEPRECATED_STARTING_PHASE宏包裹,UVM目前不推荐这主要目的就是把参数传入的phase句柄赋值给同样定义在该类中的uvm_phase类型的starting_phase,这个starting_phase句柄典型用法就是我们可以在一个sequence的body()中利用它来raise/drop objection。例如:
task my_seq::body();
if (starting_phase != null)
starting_phase.raise_objection(this);
...
if (starting_phase != null)
starting_phase.drop_objection(this);
endtask
由此可见,在sequence中使用starting_phase的前提是该sequence已经被设置为某sequencer的default_sequence,或者starting_phase已经被我们手动显式赋值。
4.3. sequence library
sequence library是直接继承自uvm_sequence的类型:
//uvm_sequence_library.svh
class uvm_sequence_library #(type REQ=uvm_sequence_item,RSP=REQ) extends uvm_sequence #(REQ,RSP);
...
endclass
4.3.1. `uvm_sequence_library_utils
在创建一个新的sequence library类时,我们要使用宏`uvm_sequence_library_utils()对其进行注册:
//uvm_sequence_defines.svh
`define uvm_sequence_library_utils(TYPE) \
\
static protected uvm_object_wrapper m_typewide_sequences[$]; \
\
function void init_sequence_library(); \
foreach (TYPE::m_typewide_sequences[i]) \
sequences.push_back(TYPE::m_typewide_sequences[i]); \
endfunction \
\
static function void add_typewide_sequence(uvm_object_wrapper seq_type); \
if (m_static_check(seq_type)) \
TYPE::m_typewide_sequences.push_back(seq_type); \
endfunction \
\
static function void add_typewide_sequences(uvm_object_wrapper seq_types[$]); \
foreach (seq_types[i]) \
TYPE::add_typewide_sequence(seq_types[i]); \
endfunction \
\
static function bit m_add_typewide_sequence(uvm_object_wrapper seq_type); \
TYPE::add_typewide_sequence(seq_type); \
return 1; \
endfunction
这个宏首先声明了一个静态的uvm_object_wrapper类型队列m_typewide_sequences[$],其次声明了一个函数init_sequence_library(),这个函数会遍历m_typewide_sequences[$]把其中的元素放入队列sequences[$],这个uvm_object_wrapper类型队列里面储存了所有已被加入该sequence library的sequence的实例。
//uvm_sequence_library.svh
class uvm_sequence_library #(type REQ=uvm_sequence_item,RSP=REQ) extends uvm_sequence #(REQ,RSP);
...
protected uvm_object_wrapper sequences[$];
...
endclass
接下来定义了静态函数add_typewide_sequence(),若m_static_check()的返回值为1则将参数传入的该sequence实例句柄存入m_typewide_sequences[$]。因为参数都是uvm_object_wrapper类型的,所以我们在手动调用该函数添加sequence时需要调用sequence.get_type()或者sequence::type_id::get()拿到该sequence注册在factory中的唯一实例句柄作为参数,这个跟我们在之前分析`uvm_do系列宏的时候使用`uvm_creat_on创建sequence/sequence_item的用法类似,不再赘述。来看m_static_check():
//uvm_sequence_library.svh
function bit uvm_sequence_library::m_check(uvm_object_wrapper seq_type, this_type lib);
uvm_object obj;
uvm_sequence_base seq;
uvm_root top;
uvm_coreservice_t cs;
string name;
string typ;
obj = seq_type.create_object();
name = (lib == null) ? type_name : lib.get_full_name();
typ = (lib == null) ? type_name : lib.get_type_name();
cs = uvm_coreservice_t::get();
top = cs.get_root();
if (!$cast(seq, obj)) begin
`uvm_error_context("SEQLIB/BAD_SEQ_TYPE",
{"Object '",obj.get_type_name(),
"' is not a sequence. Cannot add to sequence library '",name,
"'"},top)
return 0;
end
return 1;
endfunction
function bit uvm_sequence_library::m_static_check(uvm_object_wrapper seq_type);
if (!m_check(seq_type,null))
return 0;
foreach (m_typewide_sequences[i])
if (m_typewide_sequences[i] == seq_type)
return 0;
return 1;
endfunction
函数m_static_check()会先调用m_check()来检查传入参数是否为一个uvm_sequence_base类型(即检查其是否为一个sequence),若不是则返回0,此时函数m_static_check()直接返回。否则遍历队列m_typewide_sequences[$]检查该sequence是否已经被添加,若是则返回0,否则函数add_typewide_sequence()将其存入m_typewide_sequences[$]。
此外`uvm_sequence_library_utils宏还分别定义了两个静态函数add_typewide_sequences()和m_add_typewide_sequence(),二者都是通过调用add_typewide_sequence()来向队列m_typewide_sequences[$]添加sequence,所不同的是这两个函数前者参数是一个队列,可以批量添加sequence,而后者会有一个返回值1。
除了手动调用函数添加sequence,我们还可以在sequence中使用宏`uvm_add_to_seq_lib来实现:
//uvm_sequence_defines.svh
`define uvm_add_to_seq_lib(TYPE,LIBTYPE) \
static bit add_``TYPE``_to_seq_lib_``LIBTYPE =\
LIBTYPE::m_add_typewide_sequence(TYPE::get_type());
这个宏很简单,就是调用sequence library的静态函数m_add_typewide_sequence()并使用一个新的静态变量来接收其返回值,这个bit类型变量是根据宏传入的名字来声明,如uvm_add_to_seq_lib(my_seq, my_seq_lib),则此变量名为add_my_seq_to_seq_lib_my_seq_lib。
4.3.2. uvm_sequence_library_cfg
UVM sequence library 提供了一个uvm_sequence_lib_mode枚举类型的变量selection_mode来决定它选择执行sequence的算法,以及int类型变量min_random_count和max_random_count决定其每次执行的sequence/sequence_item的数量,在这两个变量之间随机出的数字会作为sequence/sequence_item执行数量:
//uvm_object_globals.svh
typedef enum
{
UVM_SEQ_LIB_RAND,
UVM_SEQ_LIB_RANDC,
UVM_SEQ_LIB_ITEM,
UVM_SEQ_LIB_USER
} uvm_sequence_lib_mode;
//uvm_sequence_library.svh
class uvm_sequence_library #(type REQ=uvm_sequence_item,RSP=REQ) extends uvm_sequence #(REQ,RSP);
...
uvm_sequence_lib_mode selection_mode;
int unsigned min_random_count=10;
int unsigned max_random_count=10;
...
endclass
其中UVM_SEQ_LIB_RAND是随机选择sequence来执行,UVM_SEQ_LIB_RANDC是把相应数量的sequence随机排序执行一次,在所有sequence都被执行完毕之前不会有sequence被执行第二次,UVM_SEQ_LIB_ITEM是发送sequence library自己定义的sequence_item,而不是执行sequence,UVM_SEQ_LIB_USER是用户自定义sequence选择算法。
我们可以通过对某个sequence library实例中的这些变量直接赋值,也可以通过uvm_config_db对某sequence library类型进行设置从而影响其所有实例,但是程序中多次使用uvm_config_db代码冗长重复又容易出错,因而UVM提供了uvm_sequence_library_cfg类:
class uvm_sequence_library_cfg extends uvm_object;
`uvm_object_utils(uvm_sequence_library_cfg)
uvm_sequence_lib_mode selection_mode;
int unsigned min_random_count;
int unsigned max_random_count;
function new(string name="",
uvm_sequence_lib_mode mode=UVM_SEQ_LIB_RAND,
int unsigned min=1,
int unsigned max=10);
super.new(name);
selection_mode = mode;
min_random_count = min;
max_random_count = max;
endfunction
endclass
uvm_sequence_library_cfg类中包含了这三个变量,可以在实例化时通过new()来一次性配置,然后仅需要一次uvm_config_db的调用,例如:
function void my_test::build_phase(uvm_phase phase);
uvm_sequence__library_cfg cfg;
super.build_phase(phase);
cfg = new("", UVM_SEQ_LIB_RANDC, 10, 20);
uvm_config_db #(uvm_object_wrapper)::set(this, "env.agt.sqr.main_phase", "default_sequence", my_seq_lib::type_id::get());
uvm_config_db #(uvm_sequence_library_cfg)::set(this, "env.agt.sqr.main_phase", "default_sequence.config", cfg);
endfunction
4.3.3. body()
sequence library本质上还是一个sequence,那么关键还要看其body() task:
//uvm_sequence_library.svh
task uvm_sequence_library::body();
uvm_object_wrapper wrap;
uvm_phase starting_phase = get_starting_phase();
if (m_sequencer == null) begin
`uvm_fatal("SEQLIB/VIRT_SEQ", {"Sequence library 'm_sequencer' handle is null; ",
" no current support for running as a virtual sequence."})
return;
end
if (sequences.size() == 0) begin
`uvm_error("SEQLIB/NOSEQS", "Sequence library does not contain any sequences. Did you forget to call init_sequence_library() in the constructor?")
return;
end
if (do_not_randomize)
m_get_config();
m_safe_raise_starting_phase({"starting sequence library ",get_full_name()," (", get_type_name(),")"});
`uvm_info("SEQLIB/START",
$sformatf("Starting sequence library %s in %s phase: %0d iterations in mode %s",
get_type_name(),
(starting_phase != null ? starting_phase.get_name() : "unknown"),
sequence_count, selection_mode.name()),UVM_LOW)
`uvm_info("SEQLIB/SPRINT",{"\n",sprint(uvm_default_table_printer)},UVM_FULL)
case (selection_mode)
UVM_SEQ_LIB_RAND: begin
valid_rand_selection.constraint_mode(1);
valid_sequence_count.constraint_mode(0);
for (int i=1; i<=sequence_count; i++) begin
if (!randomize(select_rand)) begin
`uvm_error("SEQLIB/RAND_FAIL", "Random sequence selection failed")
break;
end
else begin
wrap = sequences[select_rand];
end
execute(wrap);
end
valid_rand_selection.constraint_mode(0);
valid_sequence_count.constraint_mode(1);
end
UVM_SEQ_LIB_RANDC: begin
uvm_object_wrapper q[$];
valid_randc_selection.constraint_mode(1);
valid_sequence_count.constraint_mode(0);
for (int i=1; i<=sequence_count; i++) begin
if (!randomize(select_randc)) begin
`uvm_error("SEQLIB/RANDC_FAIL", "Random sequence selection failed")
break;
end
else begin
wrap = sequences[select_randc];
end
q.push_back(wrap);
end
valid_randc_selection.constraint_mode(0);
valid_sequence_count.constraint_mode(1);
foreach(q[i])
execute(q[i]);
valid_randc_selection.constraint_mode(0);
valid_sequence_count.constraint_mode(1);
end
UVM_SEQ_LIB_ITEM: begin
for (int i=1; i<=sequence_count; i++) begin
wrap = REQ::get_type();
execute(wrap);
end
end
UVM_SEQ_LIB_USER: begin
for (int i=1; i<=sequence_count; i++) begin
int user_selection;
user_selection = select_sequence(sequences.size()-1);
if (user_selection >= sequences.size()) begin
`uvm_error("SEQLIB/USER_FAIL", "User sequence selection out of range")
wrap = REQ::get_type();
end
else begin
wrap = sequences[user_selection];
end
execute(wrap);
end
end
default: begin
`uvm_fatal("SEQLIB/RAND_MODE",
$sformatf("Unknown random sequence selection mode: %0d",selection_mode))
end
endcase
`uvm_info("SEQLIB/END",{"Ending sequence library in phase ",
(starting_phase != null ? starting_phase.get_name() : "unknown")},UVM_LOW)
`uvm_info("SEQLIB/DSTRB",$sformatf("%p",seqs_distrib),UVM_HIGH)
m_safe_drop_starting_phase({"starting sequence library ",get_full_name()," (", get_type_name(),")"});
endtask
首先通过get_starting_phase()拿到starting_phase句柄,若m_sequencer为null,或者队列sequences[$]为空(说明尚未向其中加入过任何sequence)则直接报错返回。若变量do_not_randomize为1,则调用m_get_config(),bit型变量do_not_randomize定义在uvm_sequence_base类中,若被置为1,则sequence在调用`uvm_do系列宏和`uvm_rand_send系列宏以及作为default_sequence启动时不做随机化操作。
//uvm_sequence_base.svh
class uvm_sequence_base extends uvm_sequence_item;
...
bit do_not_randomize;
...
endclass
若该变量为默认值0,则执行过程中需要做随机化操作,UVM在sequence library类中重载了SV随机化callback pre_randomize():
//uvm_sequence_library.svh
function void uvm_sequence_library::pre_randomize();
m_get_config();
endfunction
在sequence library随机化之前也会调用m_get_config()函数,这样无论做不做随机化操作,在sequence library被执行之前m_get_config()都会被调用一次 :
//uvm_sequence_library.svh
function void uvm_sequence_library::m_get_config();
uvm_sequence_library_cfg cfg;
string phase_name;
uvm_phase starting_phase = get_starting_phase();
if (starting_phase != null) begin
phase_name = {starting_phase.get_name(),"_phase"};
end
if (uvm_config_db #(uvm_sequence_library_cfg)::get(m_sequencer,
phase_name,
"default_sequence.config",
cfg) ) begin
selection_mode = cfg.selection_mode;
min_random_count = cfg.min_random_count;
max_random_count = cfg.max_random_count;
end
else begin
void'(uvm_config_db #(int unsigned)::get(m_sequencer,
phase_name,
"default_sequence.min_random_count",
min_random_count) );
void'(uvm_config_db #(int unsigned)::get(m_sequencer,
phase_name,
"default_sequence.max_random_count",
max_random_count) );
void'(uvm_config_db #(uvm_sequence_lib_mode)::get(m_sequencer,
phase_name,
"default_sequence.selection_mode",
selection_mode) );
end
if (max_random_count == 0) begin
`uvm_warning("SEQLIB/MAX_ZERO",
$sformatf("max_random_count (%0d) zero. Nothing will be done.",
max_random_count))
if (min_random_count > max_random_count)
min_random_count = max_random_count;
end
else if (min_random_count > max_random_count) begin
`uvm_error("SEQLIB/MIN_GT_MAX",
$sformatf("min_random_count (%0d) greater than max_random_count (%0d). Setting min to max.",
min_random_count,max_random_count))
min_random_count = max_random_count;
end
else begin
if (selection_mode == UVM_SEQ_LIB_ITEM) begin
uvm_sequencer #(REQ,RSP) seqr;
uvm_object_wrapper lhs = REQ::get_type();
uvm_object_wrapper rhs = uvm_sequence_item::get_type();
if (lhs == rhs) begin
`uvm_error("SEQLIB/BASE_ITEM", {"selection_mode cannot be UVM_SEQ_LIB_ITEM when ",
"the REQ type is the base uvm_sequence_item. Using UVM_SEQ_LIB_RAND mode"})
selection_mode = UVM_SEQ_LIB_RAND;
end
if (m_sequencer == null || !$cast(seqr,m_sequencer)) begin
`uvm_error("SEQLIB/VIRT_SEQ", {"selection_mode cannot be UVM_SEQ_LIB_ITEM when ",
"running as a virtual sequence. Using UVM_SEQ_LIB_RAND mode"})
selection_mode = UVM_SEQ_LIB_RAND;
end
end
end
endfunction
m_get_config()函数会首先拿先调用get_starting_phase()拿到starting_phase句柄,然后通过uvm_config_db的get()试图获取uvm_sequence_library_cfg句柄,若之前已经通过uvm_config_db的set()设置过,则拿到配置类句柄后将其分别赋值给三个变量。若之前没有通过uvm_config_db的set()设置过uvm_sequence_library_cfg,则检查其是否通过uvm_config_db分别配置并拿到相应的值。若max_random_count值被设置为0且min_random_count大于max_random_count,则把二者都置为0。若max_random_count值不为0且min_random_count大于max_random_count,则报错并把二者都设置为smax_random_count的值。接下来检测若selection_mode被设置为UVM_SEQ_LIB_ITEM且sequence library的transaction类型为uvm_sequence_item时,则报错并把selection_mode强制设置为UVM_SEQ_LIB_RAND。若selection_mode被设置为UVM_SEQ_LIB_ITEM且m_sequencer指向的是virtual sequencer,则同样报错并把selection_mode强制设置为UVM_SEQ_LIB_RAND。
回到body()中,调用m_safe_raise_starting_phase()来raise objection,接下来根据随机算法来选择sequence执行,最后调用m_safe_drop_starting_phase()来drop objection。关于sequence选择算法这里定义了几个变量和约束条件:
class uvm_sequence_library #(type REQ=uvm_sequence_item,RSP=REQ) extends uvm_sequence #(REQ,RSP);
...
rand int unsigned sequence_count = 10;
rand int unsigned select_rand;
randc bit [15:0] select_randc;
...
constraint valid_rand_selection {
select_rand inside {[0:sequences.size()-1]};
}
constraint valid_randc_selection {
select_randc inside {[0:sequences.size()-1]};
}
constraint valid_sequence_count {
sequence_count inside {[min_random_count:max_random_count]};
}
...
endclass
当sequence library在执行之前进行随机化时,这些约束条件是默认有效的,因而会首先随机化出一个介于min_random_count和max_random_count之间的sequence_count。回到body()中,当selection_mode为:
- UVM_SEQ_LIB_RAND,把valid_sequence_count约束关闭而valid_rand_selection打开,随机出sequence_count个(已经随机出数值)select_rand的值,这些select_rand就是相应sequence在队列sequences[$]中的索引,随后调用execute()执行这些sequence。
- UVM_SEQ_LIB_RANDC,把valid_sequence_count约束关闭而valid_randc_selection打开,随机出sequence_count个(已经随机出数值)randc类型的select_randc的值,然后将它们存入uvm_object_wrapper类型的一个临时队列q[$],从队列中取出这些sequence句柄并调用execute()执行。
- UVM_SEQ_LIB_ITEM,直接调用execute()执行squence_count次当前sequence library的sequence_item。
- UVM_SEQ_LIB_USER,每次调用select_sequence()选出一个队列sequences[$]索引,一共选出sequence_count个索引并调用execute()执行其对应的sequence,若索引超出sequences[$]大小,则调用execute()执行当前sequence library的sequence_item。select_sequence()函数默认定义如下,其中定义了一个静态变量counter,每次在前一次的基础上加1并返回,若其值达到参数传入的最大值max,则从0重新开始。用户可以重载这个函数来实现自己的算法。
//uvm_sequence_library.svh
function int unsigned uvm_sequence_library::select_sequence(int unsigned max);
static int unsigned counter;
select_sequence = counter;
counter++;
if (counter >= max)
counter = 0;
endfunction
最后来看下task execute():
//uvm_sequence_library.svh
task uvm_sequence_library::execute(uvm_object_wrapper wrap);
uvm_object obj;
uvm_sequence_item seq_or_item;
uvm_sequence_base seq_base;
REQ req_item;
uvm_coreservice_t cs = uvm_coreservice_t::get();
uvm_factory factory=cs.get_factory();
obj = factory.create_object_by_type(wrap,get_full_name(),
$sformatf("%s:%0d",wrap.get_type_name(),sequences_executed+1));
if (!$cast(seq_base, obj)) begin
// If we're executing an item (not a sequence)
if (!$cast(req_item, obj)) begin
// But it's not our item type (This can happen if we were parameterized with
// a pure virtual type, because we're getting get_type() from the base class)
`uvm_error("SEQLIB/WRONG_ITEM_TYPE", {"The item created by '", get_full_name(), "' when in 'UVM_SEQ_LIB_ITEM' mode doesn't match the REQ type which was passed in to the uvm_sequence_library#(REQ[,RSP]), this can happen if the REQ type which was passed in was a pure-virtual type. Either configure the factory overrides to properly generate items for this sequence library, or do not execute this sequence library in UVM_SEQ_LIB_ITEM mode."})
return;
end
end
void'($cast(seq_or_item,obj)); // already qualified,
`uvm_info("SEQLIB/EXEC",{"Executing ",(seq_or_item.is_item() ? "item " : "sequence "),seq_or_item.get_name(),
" (",seq_or_item.get_type_name(),")"},UVM_FULL)
seq_or_item.print_sequence_info = 1;
`uvm_rand_send(seq_or_item)
seqs_distrib[seq_or_item.get_type_name()] = seqs_distrib[seq_or_item.get_type_name()]+1;
sequences_executed++;
endtask
这个task首先调用factory的create_object_by_type()拿到sequence/sequence_item实例句柄,若其既不是sequence,也不是sequence library指定的sequence_item类型,则直接报错返回。接下来使用$cast将该句柄赋值给其父类uvm_sequence_item类型句柄seq_or_item并调用宏`uvm_rand_send()执行。