sequence进阶应用
嵌套的sequence
一个新的sequence可以交替产生两种包:
class case0_sequence extends uvm_sequence #(my_transaction);
virtual task body();
my_transaction tr;
repeat (10) begin
`uvm_do_with(tr, {tr.crc_err == 1; tr.dmac == 48'h980F;}) //产生CRC错误包
`uvm_do_with(tr, {tr.crc_err == 0; tr.pload.size() == 1500; //产生长包
tr.dmac == 48'hF675;})
end
endtask
endclass
似乎这样写起来显得特别麻烦。产生的两种不同的包中,第一个约束条件有两个,第二个约束条件有三个。假如约束条件有十个呢?如果整个验证平台中有30个测试用例都用到这样的两种包,那就要在这些测试用例的sequence中加入这些代码,这是一件相当恐怖的事情且特别容易出错。既然已经定义好crc_seq和long_seq,更简单的方法是在一个sequence的body中,除了可以使用uvm_do宏产生transaction外,其实还可以启动其他的sequence,即一个sequence内启动另外一个sequence,这就是嵌套的sequence:
class case0_sequence extends uvm_sequence #(my_transaction);
…
virtual task body();
crc_seq cseq;
long_seq lseq;
…
repeat (10) begin
cseq = new("cseq");
cseq.start(m_sequencer);
lseq = new("lseq");
lseq.start(m_sequencer);
end
…
endtask
…
endclass
直接在新的sequence的body中调用定义好的sequence,从而实现sequence的重用。这个功能是非常强大的。上面代码中m_sequencer是case0_sequence在启动后所使用的sequencer的指针。但通常来说并不用这么麻烦,可以使用uvm_do宏来完成这些事情:
class case0_sequence extends uvm_sequence #(my_transaction);
…
virtual task body();
crc_seq cseq;
long_seq lseq;
…
repeat (10) begin
`uvm_do(cseq)
`uvm_do(lseq)
end
…
endtask
…
endclass
uvm_do系列宏中的第一个参数可以是transaction的指针,也可以是某个sequence的指针。当第一个参数是sequence时,它调用此sequence的start任务。
除了uvm_do宏外,前面介绍的uvm_send宏、uvm_rand_send宏、uvm_create宏,其第一个参数都可以是sequence的指针。唯一例外的是start_item与finish_item,这两个任务的参数必须是transaction的指针。
*在sequence中使用rand类型变量
在transaction的定义中通常使用rand对变量进行修饰,说明在调用randomize时要对此字段进行随机化。其实在sequence中也可以使用rand修饰符。下面sequence有成员变量ldmac:
class long_seq extends uvm_sequence#(my_transaction);
rand bit[47:0] ldmac;
…
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
这个sequence可以作为底层的sequence被顶层的sequence调用:
class case0_sequence extends uvm_sequence #(my_transaction);
…
virtual task body();
long_seq lseq;
…
repeat (10) begin
`uvm_do_with(lseq, {lseq.dmac == 48'hFFFF;})
end
…
endtask
…
endclass
sequence里可以添加任意的rand修饰符规范它产生的transaction。sequence与transaction都可以调用randomize进行随机化,都可以有rand修饰符的成员变量,从某种程度上来说,二者的界限比较模糊。这也就是为什么uvm_do系列宏可以接受sequence作为其参数的原因。
在sequence中定义rand类型变量时要注意变量的命名。很多人习惯于变量的名字和transaction中相应字段的名字一致:
class long_seq extends uvm_sequence#(my_transaction);
rand bit[47:0] dmac;
…
virtual task body();
my_transaction tr;
#15 `uvm_do_with(tr, {tr.crc_err == 0; tr.pload.size() == 1500; tr.dmac == dmac;})
tr.print();
endtask
endclass
在case0_sequence中启动上述sequence并将dmac地址约束为48’hFFFF,此时将会发现产生的transaction的dmac并不是48‘hFFFF而是一个随机值!这是因为当运行到上述代码的#15行时,编译器首先去my_transaction寻找dmac,如果找到了就不再寻找。即#15行代码最后几行等价于:
`uvm_do_with(tr, {tr.crc_err == 0; tr.pload.size() == 1500; tr.dmac == tr.dmac;})
long_seq中的dmac并没有起到作用。所以在sequence中定义rand类型变量以向产生的transaction传递约束时,变量的名字一定要与transaction中相应字段的名字不同。
*transaction类型的匹配
一个sequencer只能产生一种类型的transaction,一个sequence如果要想在此sequencer上启动,那么其所产生的transaction的类型必须是这种transaction或者派生自这种transaction。
如果一个sequence中产生的transaction的类型不是此种transaction,那么将会报错:
class case0_sequence extends uvm_sequence #(my_transaction);
your_transaction y_trans;
virtual task body();
repeat (10) begin
`uvm_do(y_trans)
end
endtask
endclass
嵌套sequence的前提是:在套里面的所有sequence产生的transaction都可以被同一个sequencer所接受。
那么有没有办法将两个截然不同的transaction交给同一个sequencer呢?可以,只是需要将sequencer和driver能够接受的数据类型设置为uvm_sequence_item:
class my_sequencer extends uvm_sequencer #(uvm_sequence_item);
class my_driver extends uvm_driver#(uvm_sequence_item);
在sequence中可以交替发送my_transaction和your_transaction:
class case0_sequence extends uvm_sequence;
my_transaction m_trans;
your_transaction y_trans;
…
virtual task body();
…
repeat (10) begin
`uvm_do(m_trans)
`uvm_do(y_trans)
end
…
endtask
`uvm_object_utils(case0_sequence)
endclass
这样的问题是由于driver中接收的数据类型是uvm_sequence_item,如果要使用my_transaction或者your_transaction中的成员变量,必须使用cast转换:
task my_driver::main_phase(uvm_phase phase);
my_transaction m_tr;
your_transaction y_tr;
…
while(1) begin
seq_item_port.get_next_item(req);
if($cast(m_tr, req)) begin
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
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
*p_sequencer的使用
考虑一种情况,在sequencer中存在如下成员变量:
class my_sequencer extends uvm_sequencer #(my_transaction);
bit[47:0] dmac;
bit[47:0] smac;
…
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
void'(uvm_config_db#(bit[47:0])::get(this, "", "dmac", dmac));
void'(uvm_config_db#(bit[47:0])::get(this, "", "smac", smac));
endfunction
`uvm_component_utils(my_sequencer)
endclass
在build_phase中使用config_db::get得到这两个成员变量的值。之后sequence在发送transaction时,必须将目的地址设置为dmac,源地址设置为smac。问题是如何在sequence的body中得到这两个变量的值呢?
前面介绍嵌套的sequence时引入了m_sequencer这个属于每个sequence的成员变量,但如果直接使用m_sequencer得到这两个变量的值:
virtual task body();
…
repeat (10) begin
`uvm_do_with(m_trans, {m_trans.dmac == m_sequencer.dmac;
m_trans.smac == m_sequencer.smac;})
end
…
endtask
如上写法会引起编译错误。其根源在于m_sequencer是uvm_sequencer_base(uvm_sequencer的基类)类型,而不是my_sequencer类型的。m_sequencer的原型为:
protected uvm_sequencer_base m_sequencer;
但由于case0_sequence在my_sequencer上启动,其中的m_sequencer本质上是my_sequencer类型的,所以可以在my_sequence中通过cast转换将m_sequencer转换成my_sequencer类型,并引用其中的dmac和smac:
virtual task body();
my_sequencer x_sequencer;
…
$cast(x_sequencer, m_sequencer);
repeat (10) begin
`uvm_do_with(m_trans, {m_trans.dmac == x_sequencer.dmac;
m_trans.smac == x_sequencer.smac;})
end
…
endtask
上述过程稍显麻烦。在实际的验证平台中用到sequencer中成员变量的情况非常多。UVM考虑到这种情况,内建了一个宏:uvm_declare_p_sequencer(SEQUENCER)。这个宏的本质是声明了一个SEQUENCER类型的成员变量,如在定义sequence时使用此宏声明sequencer的类型:
class case0_sequence extends uvm_sequence #(my_transaction);
my_transaction m_trans;
`uvm_object_utils(case0_sequence)
`uvm_declare_p_sequencer(my_sequencer)
…
endclass
则相当于声明了如下的成员变量:
class case0_sequence extends uvm_sequence #(my_transaction);
my_sequencer p_sequencer;
…
endclass
UVM之后会自动将m_sequencer通过cast转换成p_sequencer。这个过程在pre_body()之前就完成了。因此在sequence中可以直接使用成员变量p_sequencer来引用dmac和smac:
class case0_sequence extends uvm_sequence #(my_transaction);
…
virtual task body();
…
repeat (10) begin
`uvm_do_with(m_trans, {m_trans.dmac == p_sequencer.dmac;
m_trans.smac == p_sequencer.smac;})
end
…
endtask
endclass
*sequence的派生与继承
sequence作为一个类,是可以从其中派生其他sequence的:
class base_sequence extends uvm_sequence #(my_transaction);
`uvm_object_utils(base_sequence)
`uvm_declare_p_sequencer(my_sequencer)
function new(string name= "base_sequence");
super.new(name);
endfunction
//define some common function and task
endclass
class case0_sequence extends base_sequence;
…
endclass
由于在同一个项目中各sequence都是类似的,所以可以将很多公用的函数或者任务写在base sequence中,其他sequence都从此sequence派生。
普通的sequence这样使用没有任何问题,但对于那些使用了uvm_declare_p_sequence声明p_sequencer的base sequence,在派生的sequence中是否也要调用此宏声明p_sequencer?
这个问题的答案是否定的,因为uvm_declare_p_sequence的实质是在base sequence中声明了一个成员变量p_sequencer。当其他sequence从其派生时, p_sequencer依然是新的sequence的成员变量,所以无须再声明一次。如果再声明一次,系统也并不会报错:
class base_sequence extends uvm_sequence #(my_transaction);
`uvm_object_utils(base_sequence)
`uvm_declare_p_sequencer(my_sequencer)
…
endclass
class case0_sequence extends base_sequence;
`uvm_object_utils(case0_sequence)
`uvm_declare_p_sequencer(my_sequencer)
…
endclass
虽然这相当于连续声明了两个成员变量p_sequencer,但是由于这两个成员变量一个是属于父类的,一个是属于子类的,所以并不会出错。