UVM——sequence(一)

在UVM中,将激励生成和测试平台分开,使平台只编译一次,但可以运行多个激励,要实现这一点需要sequence机制、factory机制和config_db机制。

在不同的测试用例中,将不同的sequence设置成sequencer的main_phase中的default_sequence。当sequencer执行到main_phase时,发现有default_sequence,就启动sequence。

1. sequence的启动

1)方法一

1
2
3
my_sequence seq0;
seq0 = my_sequence::type_id::create("seq0");
uvm_config_db#(uvm_sequence_base)::set(this,"env.i_agt.sqr.main_phase","default_sequence",seq0);

2)方法二

1
uvm_config_db#(uvm_object_wrapper)::set(this,"env.i_agt.sqr.main_phase","default_sequence",my_sequence::type_id::get());

3)方法三

1
2
3
my_sequence seq0;
seq0 = my_sequence::type_id::create("seq0");
seq0.start(env.i_agt.sqr);

2. sequence相关的宏

1)`uvm_do系列

在sequence类中的body任务里调用`uvm_do系列的宏,用来指定在那个sequencer中发送transaction。

1
2
3
4
5
6
7
8
`uvm_do(SEQ_OR_ITEM)
`uvm_do_pri(SEQ_OR_ITEM, PRIORITY)
`uvm_do_with(SEQ_OR_ITEM, CONSTRAINTS)
`uvm_do_pri_with(SEQ_OR_ITEM, PRIORITY, CONSTRAINTS)
`uvm_do_on(SEQ_OR_ITEM, SEQR)
`uvm_do_on_pri(SEQ_OR_ITEM, SEQR, PRIORITY)
`uvm_do_on_with(SEQ_OR_ITEM, SEQR, CONSTRAINTS)
`uvm_do_on_pri_with(SEQ_OR_ITEM, SEQR, PRIORITY, CONSTRAINTS)

在UVM中uvm_do的其他七个宏都是从是从`uvm_do_on_pri_with产生的

1
2
`define uvm_do(SEQ_OR_ITME) \
`uvm_do_on_pri_with(SEQ_OR_ITEM,m_sequencer,-1,{})

下面是一个`uvm_do_with的例子:

1
2
3
4
5
6
7
8
9
10
11
12
class crc_seq extends uvm_sequence#(my_transaction);
`uvm_object_utils(crc_seq)
function new(string name= "crc_seq");
super.new(name);
endfunction

virtual task body();
my_transaction tr;
`uvm_do_with(tr, {tr.crc_err == 1;
tr.dmac == 48'h980F;})
endtask
endclass

2)`uvm_send系列

1
2
3
4
5
6
`uvm_send(SEQ_OR_ITEM)
`uvm_send_pri(SEQ_OR_ITEM, PRIORITY)
`uvm_rand_send(SEQ_OR_ITEM)
`uvm_rand_send_pri(SEQ_OR_ITEM, PRIORITY)
`uvm_rand_send_with(SEQ_OR_ITEM, CONSTRAINTS)
`uvm_rand_send_pri_with(SEQ_OR_ITEM, PRIORITY, CONSTRAINTS)

在使用`uvm_send之前,transaction必须是已经创建好了的,可以用`uvm_create宏来创建,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
virtual task body();
int num = 0;
int p_sz;
my_transaction m_trans;
if(starting_phase != null)
starting_phase.raise_objection(this);
repeat (10) begin
num++;
`uvm_create(m_trans) //创建transaction
assert(m_trans.randomize());
p_sz = m_trans.pload.size();
{m_trans.pload[p_sz - 4],
m_trans.pload[p_sz - 3],
m_trans.pload[p_sz - 2],
m_trans.pload[p_sz - 1]}
= num;
`uvm_send(m_trans) //发送transaction
end
#100;
if(starting_phase != null)
starting_phase.drop_objection(this);
endtask

3. sequence相关函数

1)start_item和finish_item

用这两个函数来代替`uvm_do系列的宏,来控制transaction的发送。但是要求transaction是已经创建好了的。

1
2
3
4
virtual task start_item (	uvm_sequence_item 	item,int 	set_priority	 = 	-1,
uvm_sequencer_base sequencer = null )
///Randomization may be done between start_item and finish_item to ensure late generation
virtual task finish_item ( uvm_sequence_item item,int set_priority = -1 )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
virtual task body();
my_transaction tr;
if(starting_phase != null)
starting_phase.raise_objection(this);
repeat (10) begin
tr = new("tr"); // 先将transaction创建好。
assert(tr.randomize() with {tr.pload.size == 200;});//随机化也可以在两个函数之间
start_item(tr); // 等待sequencer能够接受transaction,否则阻塞
finish_item(tr); // 等待driver调用itme_done(),否则阻塞。表明一个transaction发送完,可以收集运行结果。
end
#100;
if(starting_phase != null)
starting_phase.drop_objection(this);
endtask

2)pre_do、mid_do和post_do

`uvm_do系列的宏封装了很多命令:

如果`uvm_do中的参数是uvm_sequence_item:

1
2
3
4
5
6
7
8
`uvm_create(item)
sequencer.wait_for_grant(prior) (task)
this.pre_do(1) (task) // start_item
item.randomize()
this.mid_do(item) (func) //finish_item
sequencer.send_request(item) (func)
sequencer.wait_for_item_done() (task)
this.post_do(item) (func) //finish_item

pre_do是start_item返回前执行的最后一行代码;mid_do是finish_item最开始,post_do是finish_item最后一行代码。

如果`uvm_do中的参数是uvm_sequence:

1
2
3
4
5
6
7
8
`uvm_create(sub_seq)
sub_seq.randomize()//也进行随机化,在sequence也可以定义rand变量
sub_seq.pre_start() (task)
this.pre_do(0) (task)
this.mid_do(sub_seq) (func)
sub_seq.body() (task)
this.post_do(sub_seq) (func)
sub_seq.post_start() (task)

4. sequence的仲裁机制

4.1 在同一个sequencer上同时启动多个sequence

如果在同一个sequencer上同时启动多个sequence,UVM通过优先级来仲裁先发送哪个transaction或者先启动哪个sequence。

1)transaction的优先级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
task my_case0::main_phase(uvm_phase phase);
sequence0 seq0;
sequence1 seq1;

seq0 = new("seq0");
seq0.starting_phase = phase;
seq1 = new("seq1");
seq1.starting_phase = phase;
env.i_agt.sqr.set_arbitration(SEQ_ARB_STRICT_FIFO); //仲裁算法
fork // 并行启动sequence
seq0.start(env.i_agt.sqr);
seq1.start(env.i_agt.sqr);
join
endtask

transaction的优先级在`uvm_pri_do宏中指定:

1
`uvm_do_pri(m_trans, 100)

除此之外还需要设置sequencer的仲裁算法:

1
function void set_arbitration(	SEQ_ARB_TYPE 	val	) //原型
仲裁算法 描述
SEQ_ARB_FIFO Requests are granted in FIFO order (default)
SEQ_ARB_WEIGHTED Requests are granted randomly by weight
SEQ_ARB_RANDOM Requests are granted randomly
SEQ_ARB_STRICT_FIFO Requests at highest priority granted in fifo order
SEQ_ARB_STRICT_RANDOM Requests at highest priority granted in randomly
SEQ_ARB_USER Arbitration is delegated to the user-defined function, user_priority_arbitration. That function will specify the next sequence to grant.
1
env.i_agt.sqr.set_arbitration(SEQ_ARB_STRICT_FIFO);  //仲裁算法

2)sequence的优先级

sequence也有优先级,在start任务中指定:

1
2
3
4
virtual task start (	uvm_sequencer_base 	sequencer,	  	
uvm_sequence_base parent_sequence = null,
int this_priority = -1,
bit call_pre_post = 1 )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class sequence0 extends uvm_sequence #(my_transaction);
my_transaction m_trans;

function new(string name= "sequence0");
super.new(name);
endfunction

virtual task body();
if(starting_phase != null) //sequence中控制objection
starting_phase.raise_objection(this);
repeat (5) begin
`uvm_do(m_trans)
`uvm_info("sequence0", "send one transaction", UVM_MEDIUM)
end
#100;
if(starting_phase != null)
starting_phase.drop_objection(this);
endtask

`uvm_object_utils(sequence0)
endclass
1
2
3
4
5
6
7
8
9
10
11
12
13
14
task my_case0::main_phase(uvm_phase phase);
sequence0 seq0;
sequence1 seq1;

seq0 = new("seq0");
seq0.starting_phase = phase;//将phase传给seq0的starting_phase
seq1 = new("seq1");
seq1.starting_phase = phase;//手动启动sequence时需要给starting_phase赋值
env.i_agt.sqr.set_arbitration(SEQ_ARB_STRICT_FIFO);
fork
seq0.start(env.i_agt.sqr, null, 100); //指定优先级
seq1.start(env.i_agt.sqr, null, 200);
join
endtask

在start中指定了sequence优先级,就不需要在`uvm_do宏中指定transaction的优先级了。上面的设定中,seq1的优先级高,seq1中的transaction都发送完才会发送seq0中的transaction。

4.2 sequencer的lock

4.3 sequencer的grab

4.4 sequence的有效性

sequencer在仲裁sequence时会查看sequence的有效性。查看Is_relevant函数的返回结果,如果为1,有效,为0,无效。所以可以通过重载is_relevant函数来控制sequence,使它无效。

1
2
3
4
5
6
7
8
9
10
class sequence0 extends uvm_sequence #(my_transaction);
my_transaction m_trans;
int num;
bit has_delayed;

virtual function bit is_relevant(); //重载is_relevant
if((num >= 3)&&(!has_delayed)) return 0; //如果条件成立则sequence无效,停止产生transaction。
else return 1;
endfunction
endclass

当sequencer发现其上启动的所有的sequence都无效的时候会调用wait_for_relevant并等待sequence(查看is_relevant的返回结果)有效。可以重载wait_for_relevant使无效的sequence有效。

如果sequence无效了,那么它需要等待其他有效的sequence都执行完,sequencer才会调用wait_for_relevant,所以sequence的无效是自己控制的,重新有效却受其他sequence控制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class sequence0 extends uvm_sequence #(my_transaction);
my_transaction m_trans;
int num;
bit has_delayed;

virtual function bit is_relevant();
if((num >= 3)&&(!has_delayed)) return 0;
else return 1;
endfunction

virtual task wait_for_relevant();
#10000; // 10000ns后,is_relevant中的判断条件无效,is_relevant返回1,sequence重新有效
has_delayed = 1;
endtask
endclass

is_relevant、wait_for_relevant一般成对重载。

5. sequence进阶

5.1 嵌套sequence

sequence中不但可以发送transaction,也可以启动不同的sequence。可以启动不同sequence的sequence就是嵌套sequence,被嵌套的sequence和嵌套sequence在同一个sequencer中启动。

嵌套sequence不产生transaction,它只是调度sequence。

被嵌套的sequence中产生的transaction可以被同一个sequencer发送。这是嵌套的前提条件。这些transaction需要虚sequencer支持的transaction类型相同或者派生自这个类型。

1
2
3
4
5
6
7
8
9
10
11
12
class crc_seq extends uvm_sequence#(my_transaction);
`uvm_object_utils(crc_seq)
function new(string name= "crc_seq");
super.new(name);
endfunction

virtual task body(); //不控制objection
my_transaction tr;
`uvm_do_with(tr, {tr.crc_err == 1;
tr.dmac == 48'h980F;})
endtask
endclass
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class case0_sequence extends uvm_sequence #(my_transaction);
my_transaction m_trans;
...
virtual task body();
crc_seq cseq;
long_seq lseq;
if(starting_phase != null) //在嵌套sequence中控制objection
starting_phase.raise_objection(this);
repeat (10) begin
`uvm_do(cseq)//先启动cseq,再启动lseq
`uvm_do(lseq)
end
#100;
if(starting_phase != null)
starting_phase.drop_objection(this);
endtask

`uvm_object_utils(case0_sequence)
endclass

也可以用start函数启动sequence:

其中m_sequencer是启动sequence的sequencer。它是uvm_sequencer_base类型。它是uvm_sequence_item中的私有变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
virtual task body();
crc_seq cseq;
long_seq lseq;
if(starting_phase != null)
starting_phase.raise_objection(this);
repeat (10) begin
cseq = new("cseq");
cseq.start(m_sequencer);//启动sequence的sequencer
lseq = new("lseq");
lseq.start(m_sequencer);
end
#100;
if(starting_phase != null)
starting_phase.drop_objection(this);
endtask

5.2 sequence中也可以使用rand变量

`uvm_do宏在启动sequence时也对他进行随机化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class long_seq extends uvm_sequence#(my_transaction);
rand bit[47:0] ldmac; //随机变量,它的名字不能和transaction类中的变量名重复。
`uvm_object_utils(long_seq)
...
virtual task body();
my_transaction tr;
`uvm_do_with(tr, {tr.crc_err == 0;
tr.pload.size() == 1500;
tr.dmac == ldmac;})
tr.print();
endtask
endclass
class case0_sequence extends uvm_sequence #(my_transaction);
my_transaction m_trans;
...
virtual task body();
long_seq lseq;
if(starting_phase != null)
starting_phase.raise_objection(this);
repeat (10) begin
`uvm_do_with(lseq, {lseq.ldmac == 48'hFFFF;})//在启动lseq时对其随机化。
end
#100;
if(starting_phase != null)
starting_phase.drop_objection(this);
endtask

`uvm_object_utils(case0_sequence)
endclass

5.3 transaction的类型匹配

一个sequencer只能产生一种transaction,如果sequence在此sequencer上启动,那么sequence中产生的transaction必须是sequencer支持的类型或者派生自这个类型。

如果想要在sequencer产生完全不同的transaction,可以将sequencer的类型参数定义为uvm_sequence_item。

1
2
class my_sequencer extends uvm_sequencer#(uvm_sequence_item)
class my_driver extends uvm_driver#(uvm_sequence_item)

需要在my_driver中用$cast函数将uvm_sequence_item类型转换成my_transaction 类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class my_driver extends uvm_driver#(uvm_sequence_item); //uvm_sequence_item
task main_phase(uvm_phase phase);
my_transaction m_tr;//两种不同的transaction,在driver中判断传过来哪种transaction
your_transaction y_tr;
vif.data <= 8'b0;
vif.valid <= 1'b0;
while(!vif.rst_n)
@(posedge vif.clk);
while(1) begin
seq_item_port.get_next_item(req);
if($cast(m_tr, req)) begin//如果req是my_transaction类型那么转化到m_tr
drive_my_transaction(m_tr);
`uvm_info("driver", "receive a transaction whose type is my_transaction", UVM_MEDIUM)
end
else if($cast(y_tr, req)) begin//如果req是your_transaction类型那么转化到y_tr
drive_your_transaction(y_tr);
`uvm_info("driver", "receive a transaction whose type is your_transaction", UVM_MEDIUM)
end
else begin
`uvm_error("driver", "receive a transaction whose type is unknown")
end
seq_item_port.item_done();
end
endtask
endclass

5.4p_sequencer变量

之前的m_sequencer是uvm_sequencer_base类型,p_sequencer是真正发送sequence的sequencer类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class case0_sequence extends uvm_sequence #(my_transaction);
my_transaction m_trans;
`uvm_object_utils(case0_sequence)
`uvm_declare_p_sequencer(my_sequencer) //声明sequencer的类型
...
virtual task body();
if(starting_phase != null)
starting_phase.raise_objection(this);
repeat (10) begin
// 使用p_sequencer中的参数。
`uvm_do_with(m_trans, {m_trans.dmac == p_sequencer.dmac;
m_trans.smac == p_sequencer.smac;})
end
#100;
if(starting_phase != null)
starting_phase.drop_objection(this);
endtask

endclass

p_sequencer主要用来指定virtual sequencer中的真正的sequencer。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!