本篇将sequence的构建,一个dut有很多个driver,每个driver要配一个sequencer,而每个sequencer可能需要准备很多个事物产生器,即sequence,那该如何组织呢?
1. flat sequence关于trans的随机化,我们可以不深入sequence::body()
内部改变trans的硬约束以产生不同的测试用例,而是在sequence声明一些随机变量,通过随机化sequence实现对trans的随机化,即flat sequence。
下面结合了之前讲到的sequence机制代码给出一个例子。
如红色箭头所示,通过硬约束seq
实现了seq::body()
中产生的trans的随机化控制,这样每次改变测试用例只需在test::run_phase
中改seq.randomize()
的硬约束即可,不用深入seq
内部啦!
尽量使transaction中包含最大的数据包,例如能包含一个数据包data[]就不要只包含一个数据data,否则会使sequence的属性更复杂
既然能够通过对seq的随机化控制实现对seq中trans的随机化控制,那么也可以嵌套sequence也可以实现对多个sequence的控制
比如将上面代码中的test
类重写,并添加一个chnl_top_sequence
类
如上图所示代码,在test::run_phase()
中只有一个chnl_top_sequence
了,而在chnl_top_sequence::body()
中实现了多个其他flat sequence的控制。
注意chnl_top_sequence
根本就没有发送trans。
但有一个问题,不创建
chnl_top_sequence
类,将chnl_top_sequence::body()
中的内容原封不动地放入test::run_phase()
中
的phase.raise_objection(phase);
和phase.drop_objection(phase);
之间,是不是也能实现上述效果,而且似乎代码量也没啥变化。
答:的确如此。但是可别忘了,这里我们举的例子中只有一个agent,实际应用中一个env要管理好几个agent,那样的话将所有的sequence都放在
test::run_phase()
中运行就显得代码冗长、可读性差。
那么如果一个env有很多个agent,每个agent有一个sequencer,每个sequencer需要好几个sequence同步驱动,那又该如何组织sequence呢?
思路是:为每一个agent的sequencer创建一个top_sequence,在test::run_phase
中驱动
看起来挺好哈。那么问题又来了,每个agent的top_sequence之间的关系一定是fork…join嘛?如果我想先启动某几个agent,再全部启动呢?
所以说干脆啊,在test::run_phase()
中直接一句dut_top_sequence.start(dut_top_sequencer);
得了。其中dut_top_sequence
是对每个agent的top_sequence进行控制,dut_top_sequencer
中包含了每个agent的sequencer,这就是virtual sequence。
virtual sequence其实是包含所有sequence成员的一个总体hierarchical sequence,它要挂载的virtual sequencer也是包含全部sequence成员的总体sequencer。
如下图所示,黑线表示嵌套
这样test的作用就只是创建env、配置env,挂载sequence。至于怎么产生trans发送给各sequencer,就是sequence的任务了。
如下所示,base_test需要配一个base_virtual_sequence,继承的test也配了一个继承的virtual_sequence。
但base_test必不会作为run_test()
的测试用例,所以它无需挂载base_virtual_sequence。
class virtual_sequencer extends uvm_sequencer; chnl_sequencer chnl_sqr[3];reg_sequencer reg_sqr;fmt_sequencer fmt_sqr;//...
endclass
class env extends uvm_env;virtual_sequencer v_sqr;//...function void build_phase(uvm_phase phase);super.build_phase(phase);v_sqr = virtual_sequencer::type_id::create("v_sqr",this);//...endfunctionfunction void connect_phase(uvm_phase phase);foreach(v_sqr.chnl_sqr[i])v_sqr.chnl_sqr[i] = chnl_agts[i].sqr;v_sqr.reg_sqr = reg_agt.sqr;v_sqr.fmt_sqr = fmt_agt.sqr;//...endfunction
endclass
class base_test extends uvm_test;env e;//...task run_phase(uvm_phase phase);phase.raise_objection(this);run_virtual_sequence();phase.drop_objection(this);endtaskvirtual task run_virtual_sequence();endtask
endclass
class base_test_virtual_sequence extends uvm_sequence;//...`uvm_declare_p_sequencer(virtual_sequencer)chnl_sequence chnl_seq[3];reg_sequence reg_seq;fmt_sequence fmt_seq;task body();do_reg();do_fmt();do_data();endtaskvirtual task do_reg();endtaskvirtual task do_fmt();endtaskvirtual task do_data();endtask
endclass
class my_test extends base_test;//...task run_virtual_sequence();base_virtual_sequence seq;seq = base_virtual_sequence::type_id::create("seq");seq.start(env.v_sqr);endtask
endclass
class my_virtual_sequence extends base_virtual_sequence;//...task do_reg();`uvm_do_on_with(reg_seq,this.p_sequencer.reg_sqr,{cmd == `WRITE;addr == 'h000AC;})//...endtask
endclass
在实际调试中,可能存在多个flat sequence、virtual sequence和test,它们相互之间有着多重继承or重载的关系,调试起来可能比较麻烦,所以个人认为属于一条继承分支的联合在一起看就好
例如上述代码中的base_test
和data_consistence_base_test
就可以一起看,base_virtual_sequence
和data_consistence_base_virtual_sequence
一起看
如果一个sequencer有好几个sequence可用于驱动呢?
例如,对于reg_sequencer,有uvm_reg_access_seq、uvm_reg_bit_bash_seq等等一堆sequence,把这些sequence跟其他的fmt_sequence之类的全都堆在virtual_sequence里,就会显得非常冗杂。
所以在virtual_sequence内再放一个reg_virtual_sequence,不是用于驱动sequencer,就是为了对多个reg sequence进行控制
下面这个例子说明了几个点:
● 每一个版本的test,都对应一个版本的全部virtual sequence
● 对于同一个版本的virtual sequence,低层级的virtual sequence要在高层级的virtual sequence的body()内例化、挂载
class base_test extends uvm_test;env e;//...task run_phase(uvm_phase phase);phase.raise_objection(this);run_virtual_sequence();phase.drop_objection(this);endtaskvirtual task run_virtual_sequence();endtask
endclassclass base_virtual_sequence extends uvm_sequence;//...fmt_sequence fmt_seq;reg_base_virtual_seuqence reg_base_seq;`uvm_declare_p_sequencer(virtual_sequencer)chnl_sequence chnl_seq[3];reg_virtual_sequence reg_vseq;fmt_sequence fmt_seq;task body();do_reg();do_fmt();do_data();endtaskvirtual task do_reg();endtaskvirtual task do_fmt();endtaskvirtual task do_data();endtask
endclassclass reg_base_virtual_sequence extends uvm_sequence;reg_block rgm;reg_adapter adapter;uvm_reg_hw_reset_seq reg_rst_seq; uvm_reg_bit_bash_seq reg_bit_bash_seq;uvm_reg_access_seq reg_acc_seq;virtual task body();endtask
endclass
class my_test extends base_test;//...task run_virtual_sequence();my_virtual_sequence seq;seq = my_virtual_sequence::type_id::create("seq");seq.start(env.v_sqr);endtask
endclassclass my_virtual_sequence extends base_virtual_sequence;//...reg_my_virtual_sequence seq;task do_reg();seq = reg_my_virtual_sequence::type_id::create("seq");seq.start_item(this.p_sequencer.reg_sqr,this);endtasktask do_fmt();`uvm_do_on_with(fmt_seq,this.p_sequencer.fmt_sqr)endtask//...
endclassclass reg_my_virtual_sequence extends reg_base_virtual_sequence;task body();//...endtask
endclass
class full_test extends base_test;//...task run_virtual_sequence();full_virtual_sequence seq;seq = full_virtual_sequence::type_id::create("seq");seq.start(env.v_sqr);endtask
endclassclass full_virtual_sequence extends base_virtual_sequence;//...reg_full_virtual_sequence seq;task do_reg();seq = reg_full_virtual_sequence::type_id::create("seq");seq.start_item(this.p_sequencer.reg_sqr,this);endtask//...
endclassclass reg_full_virtual_sequence extends reg_base_virtual_sequence;task body();//...endtask
endclass
4. layer sequence
driver向dut驱动时,如果要驱动的数据项过多,要加的约束过于庞大时,只通过一个trans或flat sequence罗列一大堆随机变量和constraint,就非常冗杂。
例如串行RapidIO(Serial RapidIO,SRIO)的请求数据包就长这个样子的
可以看到内容很多,而且分成三级规范体系结构:逻辑层、传输层和物理层,层层嵌套,最终driver要驱动的就是Physical layer的全部内容。
如果这些内容都罗列在一个trans类中,每一项的constraint也都堆在这个trans类中,那就太庞杂了。
class transaction extends uvm_sequence_item;rand bit S;rand bit [2:0] AckID;rand bit Crf;rand bit [1:0] Prio;//...rand bit [15:0] CRC;constraint cstr{...}//...
endclass
不仅如此,在OSI(Open System Interconnect)模型中,网络结构分为7层:应用层、表示层、会话层、传输层、
网络层、链路层、物理层,每层都有着相应的协议与数据结构。
常见的PCIe、USB、UFS等标准都是分层传输协议,相应的验证IP也常采用分层验证环境,方便扩展和复用。
那怎么办呢?我们可以分成好几个transaction类嘛
class phy_trans extends uvm_sequence_item;rand bit S;rand bit [2:0] AckID;rand bit Crf;rand bit [1:0] Prio;rand bit [182:0] log_trp_payload; //逻辑层和传输层的位置//...
endclassclass trp_trans extends uvm_sequence_item;rand bit [1:0] TT;//...rand bit [143:0] log_payload; //逻辑层的位置//...
endclassclass log_trans extends uvm_sequence_item;rand bit [3:0] Ttype;rand bit [3:0] Wrsize;//...
endclass
而对于sequence,也是每个trans对应一个sequence,那么问题来了,如何将这些sequence组织在一起呢?
思路是这样的:首先我们必须有一个sequencer与driver建立TLM连接,然后phy_seq产生一个合适的phy_trans再`uvm_do_with。那么我们就需要填补phy_trans中log_trp_payload的部分,而这一部分由trp_trans组成。而trp_trans是由trp_seq产生,那么问题来了,如何将trp_trans放入到phy_trans中呢?
方法是,学着driver与sequence的样子,为trp_seq设立一个trp_sequencer,为phy_seq设立一个phy_sequencer,将两个sequencer实现TLM连接。然后在phy_seq的body()内通过p_sequencer和uvm_seq_item_pull_port获取trp_trans。trp_trans获取log_trans的方法同上。
对于monitor来说也可按照层级划分monitor
见下图
代码实现起来就是个迭代的流程
class phy_sequencer extends uvm_sequencer#(phy_trans);uvm_seq_item_pull_port#(trp_trans) seq_trp_trans_port;//uvm_seq_item_pull_imp #(phy_trans,phy_sequencer) seq_item_export; //uvm_sequencer already has//...
endclassclass trp_sequencer extends uvm_sequencer#(trp_trans);uvm_seq_item_pull_port#(log_trans) seq_log_trans_port;//uvm_seq_item_pull_imp #(trp_trans,trp_sequencer) seq_item_export; //uvm_sequencer already has//...
endclassclass log_sequencer extends uvm_sequencer#(log_trans);//uvm_seq_item_pull_imp #(log_trans,log_sequencer) seq_item_export; //uvm_sequencer already has//...
endclassclass agent extends uvm_agent;//...function void connect_phase(uvm_phase phase);driver.seq_item_port.connect(phy_sqr.seq_item_export);phy_sqr.seq_trp_trans_port.connect(trp_sqr.seq_item_export);trp_sqr.seq_log_trans_port.connect(log_sqr.seq_item_export);phy_mon.phy_collected_port.connect(trp_mon.phy_collected_export);trp_mon.trp_collected_port.connect(log_mon.trp_collected_export);//...endfunction
endfunction
class log_sequence extends uvm_sequence#(log_trans);task body();//...endtask
endclassclass trp_sequence extends uvm_sequence#(trp_trans);`uvm_declare_p_sequencer(trp_sequencer)//...task body();trp_trans trp_req;log_trans log_req;forever begin //forever`uvm_rand(trp_req)p_sequencer.seq_log_trans_port.get_next_item(log_req);trp_req = this.put_into_req(log_req);`uvm_send(trp_req)p_sequencer.seq_log_trans_port.item_done();endendtask
endclassclass phy_sequence extends uvm_sequence#(phy_trans);`uvm_declare_p_sequencer(phy_sequencer)//...task body();phy_trans phy_req;trp_trans trp_req;forever begin //forever`uvm_rand(phy_req)p_sequencer.seq_trp_trans_port.get_next_item(trp_req);phy_req = this.put_into_req(trp_req);`uvm_send(phy_req)p_sequencer.seq_trp_trans_port.item_done();endendtask
endclass
class phy_monitor extends uvm_monitor;uvm_analysis_port#(phy_trans) phy_collected_port;//...
endclassclass trp_monitor extends uvm_monitor;uvm_analysis_imp#(phy_trans,trp_monitor) phy_collected_export;uvm_analysis_port#(trp_trans) trp_collected_port;//...no run phasefunction void write(phy_trans phy_t);trp_trans trp_t;trp_t = this.extract_trp_trans(phy_t);trp_collected_port.write(trp_t);endfunction
endclassclass log_monitor extends uvm_monitor;uvm_analysis_imp#(trp_trans,log_monitor) trp_collected_export;uvm_analysis_port#(log_trans) log_collected_port;//...no run phasefunction void write(trp_trans trp_t);log_trans log_t;log_t = this.extract_log_trans(trp_t);log_collected_port.write(log_t);endfunction
endclass
class virtual_sequencer extends uvm_sequencer;log_sequencer log_sqr;trp_sequencer trp_sqr;phy_sequencer phy_sqr;//...
endclassclass virtual_sequence extends uvm_sequence;//...log_sequence log_seq;trp_sequence trp_seq;phy_sequence phy_seq;`uvm_declare_p_sequencer(virtual_sequencer)task body();fork`uvm_do_on(log_seq,p_sequencer.log_sqr);`uvm_do_on(trp_seq,p_sequencer.trp_sqr);`uvm_do_on(phy_seq,p_sequencer.phy_sqr);join_none //这里是fork...join_none
endclassclass test extends uvm_test;//...task run_phase(uvm_phase phase);phase.raise_objection(this);virtual_sequence seq;seq = virtual_sequence::type_id::create("seq");seq.start(env.v_sqr);phase.drop_objection(this);endtask
endclass
由于每产生一个log_seq,都要将它填补到trp_seq,进而填补到log_seq,所以trp_seq::body()
和phy_seq::body()
都是forever。
所以在virtual_sequence::body()
中三者必须是fork…join_none的关系,否则就会一直运行下去。
无论是virtual sequence还是layer sequence,之所以能玩的这么花,其核心在于p_sequencer。
有了p_sequencer,sequence就可以访问它所挂载的sequencer。
例如 virtual sequence中的sequence就可以通过p_sequencer访问virtual sequencer中的其他sequencer
layer sequence中的p_sequencer就可以访问挂载的sequencer的端口。
其实是因为sequencer作为component,啥都可以有,跟谁都能打交道,sequence借助p_sequencer访问其他的component
先看下面这个代码
class sequence extends uvm_sequence;//...`uvm_declare_p_sequencer(sequencer)local reg_model rgm = p_sequencer.rgm;//...
endclass
task test::run_phase(uvm_phase phase);//...sequence seq = new(); //这一步例化seq,会对seq.rgm产生什么操作?seq.start(env.sqr);
endtask
会报错,因为在执行sequence seq = new();
的时候,会执行local reg_model rgm = p_sequencer.rgm;
,那么问题来了这个时候有没有p_sequencer?
没有,因为只有在seq.start(env.sqr);
才挂载sqr,挂载之前去哪找p_sequencer啊?
class heartbeat_sequence extends uvm_sequence#(trans);`uvm_declare_p_sequencer(sequencer)//...task body();trans t;forever beginrepeat(10) @(posedge p_sequencer.vif.clk);`uvm_do(t)endendtask
endclassclass sequencer extends uvm_sequencer;local chnl_intf vif;//...
endclassclass virtual_sequence extends uvm_sequence;//...`uvm_declare_p_sequencer(virtual_sequencer)heartbeat_sequence hb_seq;normal_sequence nor_seq;task body();fork`uvm_do_on(hb_seq,p_sequencer.sequencer)`uvm_do_on(nor_seq,p_sequencer.sequencer)join_noneendtask
endclass