目录
sequencer和sequence
- 目的:sequence挂载到sequencer上的方法和宏
class bus_trans extends uvm_sequence_item;
rand int data;
...//注册,域的自动化,例化
endclass
class child_seq extends uvm_sequence;//扁平类seq
...//注册,例化
task body();
uvm_sequence_item tmp;
bus_trans req;
tmp=create_item(bus_trans::get_type(), m_sequencer, "req");
void'($cast(req,tmp));
start_item(req);
req.randomize with{data==10;};
finish_item(req);
endtask
endclass
class env extends uvm_env;
sequencer sqr;
driver drv;
..//注册例化
function void build_phase(u p);
sqr=sequencer::type_id::create("sqr", this);
drv=driver::type_id::create("sqr", this);
endfunction
function void connect_phase(u p);
drv.seq_item_port.connect(sqr.seq_item_export);
endfunction
endclass
class top_seq extends uvm_sequence;
...//注册,例化
task body();
uvm_sequence_item tmp;
child_seq cseq;
bus_trans req;
cseq=child_req::type_id::create("cseq");//包含了子seq
req=create_item(bus_trans::get_type(), m_sequencer, "req");
cseq.start(m_sequencer, this);//发送cseq
void'($(req, tpm));
start_item(req);
endtask
endclass
class sequencer extends uvm_sequencer;
...//注册,例化
endclass
class driver extends uvm_driver;
...//注册,例化
task run_phase(u p);
REQ tmp;
bus_trans req;
forever begin
seq_itme_port.get_next_item(tmp);
void'($cast(req,tmp));
`uvm_info("", $sformatf("got a item"), )
seq_item_port.item_done();
end
endtask
endclass
class test1 extends uvm_test;
env e;
...//注册,例化
function void build_phase(u p);
e=env::type_id::create("e", this)
endfunction
task run_phase(u p);
top_seq seq;
phase.raise_objection(p);
seq=new();
seq.start(e.sqr);
phase.drop_objection(p);
endtask
endclass
- 发送sequencen/item的方法
- uvm_sequence::start(uvm sequencer_base sequencer, uvm_sequence_base parent_sequence=null, int this priotiry=-1, bit call_pre_post=1)
- 其中,第一个参数是sequencer的句柄,如果是顶部sequence,可以省略对第二个参数的指定,第三个参数-1默认使该sequence如果有parent_sequence会继承上层的优先级,如果是顶部root sequence,优先级自动设为100;第四个参数建议使用默认值1,这样uvm_sequence::pre_doby()和uvm_sequence::post_body()两个方法会在uvm_sequence::body()前后执行,其实就是预定义的body的回调函数
- 针对将item挂载到sequencer上:
- uvm_sequence::start_item(uvm_sequence_item, int set_priority=-1, uvm_sequencer_base sequencer=null);//第三个参数注意是否要将item挂载到“非当前parent sequence挂载的sequencer”
- uvm_sequence::finish_item(uvm_sequence_item item, int set_priority=-1);
- item完整传送的步骤
- 创建item
- start_item()方法等待获得sequencer的许可,然后执行parent sequence的方法pre_do()
- 随机化item
- finish_item()方法在随机化后执行parent sequence的mid_do()。以及调用uvm_sequencer::send_request()和uvm_sequencer::wait_for_item_done()将item发送到sequencer完成与driver的握手。最后执行parent_sequence的post_do()
class child_seq extends uvm_sequence;
...
task body();
bus_trans req;
`uvm_create(req)
`uvm_rand_send_with(req, {data==10;})
//查表,当然可以合成一个`uvm_do_with(req, {data==10;})
endtask
endclass
class top_seq extends uvm_sequence;
...
`uvm_do(cseq)
`uvm_do_with(req, {data==20;})
endtask
endclass
- 建议
- 无论seq处于什么层次,都应该在test结束前执行完毕,一般来说还要保留出一部分时间供dut将所有发送的激励处理完毕,进入空闲状态才可以结束测试
- 避免使用fork join_any和fork join_none来控制seq的发送顺序。用disable终止seq线程时可能会在不恰当的时间锁住sequencer,则接下来再也无法发送其他seq
sequencer的仲裁特性
- 多个seq同时挂载到sequencer上时,按照仲裁规则通过特定seq中的item
- uvm_sequence::set_arbitration(UVM_SEQ_ARB_TYPE val)设置仲裁模式,UVM_SEQ_ARB_TYPE有以下选择
- UVM_SEQ_ARB_FIFO:默认模式,按照fifo先进先出依次授权,与seq优先级无关
- UVM_SEQ_ARB_WEIGHTED:对不同seq的发送请求,按照优先级权重随机授权
- UVM_SEQ_ARB_RANDOM:不同的请求会被随机授权,无视抵达顺序和优先级
- UVM_SEQ_ARB_STRICT_FIFO:按照优先级和抵达顺序依次授权
- UVM_SEQ_ARB_STRICT_RANDOM:按照最高优先级随机授权
- UVM_SEQ_ARB_USER:用户自定义仲裁方法user_priority_arbitration()
class bus_trans extends uvm_sequence_item;
rand int data;
...endclass
class child_seq extends uvm_sequence;
rand int base;
...
task body();
bus_trans req;//1个seq发送2个item
repeat(2) `uvm_do_with(req, {data inside{[base: base+9]};})
endtask
endclass
class top_seq extends uvm_sequence;
...
task body();
child_seq seq1, seq2, seq3;
m_sequencer.set_arbitration(UVM_SEQ_ARB_STRICT_FIFO);//假设已经挂载在sequencer上
fork
`uvm_do_pri_with(seq1, 500, {base==10;})
`uvm_do_pri_with(seq2, 500, {base==20;})
`uvm_do_pri_with(seq3, 300, {base==30;})
join
endtask
endclass
class sequencer extends uvm_sequencer;
...endclass
class driver extends uvm_driver;
...
task run_phase(u p);
REQ tmp;
bus_trans req;
forever begin
seq_item_port.get_next_item(tmp);
void'($cast(req, tmp));
`uvm_info("got a item")
seq_item_port.item_done();
end
endtask
endclass
class test1 extends uvm_test;
env e;
...
task run_phase(u p);
top_seq seq;
phase.raise_objection(p);
seq=new();//不可以用`uvm_do,只能在seq的class中才可以调用
seq.start(e.sqr);
phase.drop_objection(p);
endtask
endclass
输出结果:0时刻依次完成seq1的16,seq2的22,seq1的19,seq2的23,seq3的33和32。
由于1和2的优先级相同,传送完seq1的第一个item后seq2的第一个item已经在等待发送,所以先发送seq2的第一个的item
- 如何让相同优先级的seq发送完item后再发送下一个seq的?
- uvm_sequencer提供了锁定机制:lock()和grab()方法
- lock()和unlock():对seq提供排外的访问权限,前提条件是该seq按照sequencer的仲裁机制获得授权;是一种阻塞任务,只有获得权限才会返回
- grab()和ungrab():对seq提供排外的访问权限,只需要在sequencer下一次授权周期时无条件获得授权,区别:无视同一时刻内发起传送请求的其他seq,唯一可以阻止它的只有已经预先获得授权的其他lock或者grab的seq
- 如果使用了这两种锁定方法,必须在seq结束前调用un()来释放权限,否则sequencer会进入死锁状态,不能为其他seq授权
class bus_trans extends uvm_sequence_item;... endclass
class child_seq extends uvm_sequence;... endclass
class lock_seq extends uvm_sequence;
...
task body();
bus_trans req;
#10ns;
m_sequencer.lock(this);//都在等待权限时才可以用lock争取过来
`uvm_info()
repeat(3) #10ns `uvm_do_with(req, {data inside {[100:110]};})
m_sequencer.unlock(this);
endtask
endclass
class grab_seq extends uvm_sequence;
...
task body();
bus_trans req;
#20ns;
m_sequencer.grab(this);//仲裁时拿到权限
`uvm_info()
repeat(3) #10ns `uvm_do_with(req, {data inside {[200:210]};})
class top_seq extends uvm_sequence;
...
task body();
child_seq seq1, seq2, seq3;
lock_seq locks;
grab_seq grabs;
m_sequencer.set_arbitration(UVM_SEQ_ARB_STRICT_FIFO);
fork
`uvm_do_pri_with(seq1, 500, {base==10;})
`uvm_do_pri_with(seq2, 500, {base==20;})
`uvm_do_pri_with(seq3, 300, {base==30;})
`uvm_do_pri(locks, 300)//lock优先级300
`uvm_do(grabs)//grab没有优先级
join
endtask
endclass
输出结果:10ns处,这也是零时刻,首先seq1,2,3依次拿到item16,22,33,此时lock也期望拿到权限,同时等待的有seq1-2-3和locks;由于locks的优先级300对应seq3,随后lock拿到3个item108,110,101,且每个都有10ns延迟,所以分别是20ns,30ns,40ns处;20ns时刻grab开始等待权限,此时是被lock拿着,40ns时seq1-3也在等待,所以40ns时grab拿到权限,发送3个item;此时是70ns,才轮到seq1,2,3去拿到权限
- seq默认的优先级是100
sequence的层次化
- 验证的复用
- 水平复用:在各个子模块的验证语境中,利用已有资源完成高效的激励场景创建
- 垂直复用:子系统验证中完成结构复用和激励场景复用
- 激励场景的复用很大程度上取决于如何设计seq,使底层seq实现合理的粒度
hierarchical sequence
对于寄存器模块来说,测试场景可以拆分为:
- 设置时钟和复位
- 测试ch1,2,3的控制寄存器和只读寄存器
typedef enum {CLKON, CLKOFF, RESET, WRREG, RDREG} cmd_t;
class bus_trans extends uvm_sequence_item;
rand cmd_t cmd;
rand int addr;
rand int data;
constraint cstr { soft addr =='h0;
soft data =='h0;}
...endclass
class clk_rst_seq extends uvm_sequence;//底层seq1,也叫element seq
rand int freq;
...
task body();
bus_trans req;
`uvm_do_with(req, {cmd==CLKON; data==freq;})
`uvm_do_with(req, {cmd==RESET;})
endtask
endclass
class reg_test_seq extends uvm_sequence;//底层seq2
rand int chnl;
...
task body();
bus_trans req;
`uvm_do_with(req, {cmd==WRREG; addr==chnl*'h4;})
`uvm_do_with(req, {cmd==RDREG; addr==chnl*'h4;})
`uvm_do_with(req, {cmd==RDREG; addr==chnl*'h4 + 'h10;})
endtask
endclass
class top_seq extends uvm_sequence;//顶层seq,对底层seq进行组合和随机化赋值
...
task body();//hs可以包含多个seq和item
clk_rst_seq clkseq;
reg_test_seq regseq0, regseq1, regseq2;
`uvm_do_with(clkseq, {freq==150;})
`uvm_do_with(regseq0, {chnl==0;})
`uvm_do_with(regseq1, {chnl==1;})
`uvm_do_with(regseq2, {chnl==2;})
endtask
endclass
class reg_master_sequencer extends uvm_sequencer;
...endclass
class reg_master_driver extends uvm_driver;
...
task run_phase(u p);
REQ tmp;
bus_trans req;
forever begin
seq_item_port.get_next_item(tmp);
void'($cast(req, tmp));
`uvm_info()
seq_item_port.item_done();
end
endtask
endclass
- 和virtual seq的异同
- 同:对各个seq进行协调
- 异:hs面对的对象时同一个sequencer,hs本身会挂载到sequencer上;vs内部不同的seq可以面向不同的sequencer种类
virtual sequence
- 目的:更上层env的测试序列,面向多个sequencer
- vs:各个模块环境的element seq和hs组成的容器
- vser:作为routing sequencer。组织不同结构中的ser。连接所有底层sequencer句柄,是一个中心化的路由器,本身不会传递item数据对象(vser不需要和任何driver做tlm连接)
- 需要在顶层的connect阶段,做好vser中各个sequencer句柄与底层sequencer对象的一一连接,避免句柄悬空
- vs一般只会挂载在vser上
class mcdf_virtual_sequencer extends uvm_sequencer;
cr_master_sequencer cr_sqr;
reg_master_sequencer reg_sqr;
chnl_master_sequencer chnl_sqr0;
chnl_master_sequencer chnl_sqr1;
chnl_master_sequencer chnl_sqr2;
fmt_slave_sequencer fmt_sqr;
`uvm_component_utils()
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
endclass
class mcdf_normal_seq extends uvm_sequence;
`uvm_object_utils(mcdf_normal_seq)
`uvm_declare_p_sequencer(mcdf_virtual_sequencer)
//新定义一个成员变量p_sequencer类型是p_sequencer,并$(p_seq, m_seq);
//m_sequencer的默认变量是uvm_sequencer_base类型
...
task body();
clk_rst_seq clkreq;
reg_cfg_seq cfgreq;
data_trans_seq dataseq;
fmt_slv_cfg_seq fmtseq;
//m_sequencer的类型是uvm_sequencer,p_sequencer的类型是子类mcdf_virtual_sequencer
`uvm_do_on(fmtseq, p_sequencer.fmt_sqr)//挂载各个seq到各个ser
`uvm_do_on(clkreq, p_sequencer.cr_sqr)
`uvm_do_on(cfgreq, p_sequencer.reg_sqr)
fork
`uvm_do_on(dataseq, p_sequencer.chnl_sqr0)
`uvm_do_on(dataseq, p_sequencer.chnl_sqr1)
`uvm_do_on(dataseq, p_sequencer.chnl_sqr2)
join
endtask
endclass
class mcdf_env extends uvm_env;
cr_master_agent cr_agt;
reg_master_agent reg_agt;
chnl_master_agent chnl_agt0;
chnl_master_agent chnl_agt1;
chnl_master_agent chnl_agt2;
fmt_slave_agent fmt_agt;
mcdf_virtual_sequencer virt_sqr;
`uvm_component_utils(mcdf_env)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(u p);
cr_master_agent=cr_master_agent::type_id::create("cr_agt", this);
reg_agt=reg_master_agent::type_id::create("reg_agt", this);
chnl_agt0=chnl_master_agent::type_id::create("chnl_agt0", this);
chnl_agt1=chnl_master_agent::type_id::create("chnl_agt1", this);
chnl_agt2=chnl_master_agent::type_id::create("chnl_agt2", this);
fmt_agt=fmt_slave_agent::type_id::create("fmt_agt", this);
virt_sqr=mcdf_virtual_sequencer::type_id::create("virt_sqr", this);
endfunction
endclass
class test1 extends uvm_test;
mcdf_env e;
...
task run_phase(u p);
mcdf_normal_seq seq;
phase.raise_objection(p);
seq=new();
seq.start(e.virt_sqr);//将vs挂载到vser
phase.drop_objection(p);
endtask
endclass
layering sequence
- 目的:更复杂的协议总线传输,更深层次化的数据传输
- 概念:通过层次化的seq可以分别构建transaction layer,transport layer, physical layer等从高抽象级到低抽象级的transaction转化,是一种层次化的seq构建方式
typedef enum {CLKON, CLKOFF, RESET, WRREG, RDREG} phy_cmd_t;
typedef enum {FREQ_LOW_TRANS, FREQ_MED_TRANS, FREQ_HIGH_TRANS} layer_cmd_t;
class bus_trans extends uvm_sequence_item;//底层trans
rand phy_cmd_t cmd;
rand int addr;
rand int data;
constraint cstr { soft addr =='h0;
soft data =='h0;}
...endclass
class layer_trans extends uvm_sequence_item;//抽象一点
rand layer_cmd_t layer_cmd;
rand int pkt_len;
rand int pkt_idle;
constraint cstr {soft pkt_len inside{[10:20]};
layer_cmd==FREQ_LOW_TRANS -> pkt_idle inside{[300:400]};
layer_cmd==FREQ_MED_TRANS -> pkt_idle inside{[100:200]};
layer_cmd==FREQ_HIGH_TRANS -> pkt_idle inside{[20:40]};}
...endclass
class packet_seq extends uvm_sequence;
rand int len;
rand int addr;
rand int data[];
ramd phy_cmd_t cmd;
constraint cstr {soft len inside{[30:50]};
soft addr[31:16]=='hFF00;
data.size()==len;}
...
task body();
bus_trans req;
foreach(data[i]) `uvm_do_with(req, {cmd==local::cmd; addr==local::addr; data==local::data[i];})
endtask
endclass
class adapter_seq extends uvm_sequence;//转化层
`uvm_objection_utils()
`uvm_declare_p_sequencer(phy_master_sequencer)
...
task body();
layer_trans trans;
packet_seq pkt;
forever begin
p_sequencer.up_sqr.get_next_item(req);//可以调用
void'($cast(trans, req));
repeat(trans.pkt_len) begin
`uvm_do(pkt)//高抽象级len到低的pkt
delay(trans.pkt_idle);
end
p_sequencer.up_sqr.item_done();//告诉上层,消化了item
end
endtask
class top_seq extends uvm_sequence;
...
task body();
layer_trans trans;
`uvm_do_with(trans, {layer_cmd==FREQ_LOW_TRANS;});
`uvm_do_with(trans, {layer_cmd==FREQ_HIGH_TRANS;});
endtask
endclass
class layering_sequencer extends uvm_sequencer;
...endclass
class phy_master_sequencer extends uvm_sequencer;
layering_sequencer up_sqr;
...endclass
class phy_master_driver extends uvm_driver;
...
task run_phase(u p);
REQ tmp;
bus_trans req;
forever begin
seq_item_port.get_next_item(tmp);
void'($cast(req, tmp));
`uvm_info()
seq_item_port.item_done();
end
endtask
endclass
class phy_master_agent extends uvm_agent;
phy_master_sequencer sqr;
phy_master_driver drv;
...
function void build_phase(u p);
sqr=phy_master_sequencer::type_id::create("sqr", this);
drv=phy_master_driver::type_id::create("drv", this);
endfunction
function void connect_phase(u p);
drv.seq_item_port.connect(sqr.seq_item_export);
endfunction
endclass
virtual task delay(int delay); ...endtask
endclass
class test1 extends uvm_test;
layering_sequencer layer_sqr;
phy_master_agent phy_agt;
...
function void build_phase(u p);
layer_sqr=layering_sequencer::type_id::create("layer_sqr", this);
phy_agt =phy_master_agent::type_id::create();
endfunction
function void connect_phase(u p);
phy_agt.sqr.up_sqr=layer_sqr;
endfunction
task run_phase(u p);
top_seq seq;
adapter_seq adapter;
//举手
seq=new();
adapter=new();
fork
adapter.start(phy_agt.sqr);//挂载到低层次的sqr
join_none
seq.start(layer_sqr);//顶层seq挂载到sqr上
//落手
endtask
endclass
- 抽象层次的transaction类定义都要有对应的sequencer作为trans的路由通道
- 抽象级sequencer中要有相应的转换方法,从高层次sequencer获取高层次trans转换成低层次trans,并通过低层次sequencer发送出去