将generator
,driver
与test
的关系最终移植为sequence
,sequencer
,driver
和test
的关系,聚焦于sequence
和sequencer
的使用。
1. 验证结构框图
2. 实际代码
以chnl_pkg为例:
class chnl_driver extends uvm_driver #(chnl_trans);
//driver通过端口得到req,并通过端口返回rsp,返回rsp前用rsp.set_sequence_id(req.get_sequence_id())方法给rsp贴标签
local virtual chnl_intf intf;
`uvm_component_utils(chnl_driver)
function new (string name = "chnl_driver", uvm_component parent);
super.new(name, parent);
endfunction
function void set_interface(virtual chnl_intf intf);
if(intf = null)
$error("interface handle is NULL, please check if target interface has been intantiated");
else
this.intf = intf;
endfunction
task run_phase(uvm_phase phase);
fork
this.do_drive();
this.do_reset();
join
endtask
task do_reset();
forever begin
@(negedge intf.rstn);
intf.ch_valid <= 0;
intf.ch_data <= 0;
end
endtask
task do_drive();
chnl_trans req, rsp;
@(posedge intf.rstn);
forever begin
seq_item_port.get_next_item(req);
this.chnl_write(req);
void'($cast(rsp, req.clone()));
rsp.rsp = 1;
rsp.set_sequence_id(req.get_sequence_id());
seq_item_port.item_done(rsp);
end
endtask
task chnl_write(input chnl_trans t);
foreach(t.data[i]) begin
@(posedge intf.clk);
intf.drv_ck.ch_valid <= 1;
intf.drv_ck.ch_data <= t.data[i];
@(negedge intf.clk);
wait(intf.ch_ready === 'b1);
`uvm_info(get_type_name(), $sformatf("send data 'h%8x", t.data[i]),UVM_HIGH)
repeat(t.data_nidles) chnl_idle();
end
endtask
task chnl_idle();
@(posedge intf.clk);
intf.drv_ck.ch_valid <= 0;
intf.drv_ck.ch_data <= 0;
endtask
endclass:chnl_driver
class chnl_sequencer extends uvm_sequencer #(chnl_trans);//sequencer类的声明,uvm_sequencer已经内置了端口与方法
`uvm_component_utils(chnl_sequencer)
function new(string name = "chnl_sequencer", uvm_component parent);
super.new(name, parent);
endfunction
endclass: chnl_sequencer
class chnl_agent extends uvm_agent;//在agent中完成driver与sequencer的创建和端口的连接
chnl_driver driver;
chnl_monitor monitor;
chnl_sequencer sequencer;
local virtual chnl_intf vif;
`uvm_component_utils(chnl_agent)
function new(string name = "chnl_agent", uvm_component parent);
super.new(name parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
driver = chnl_driver::type_id::create("driver", this);
monitor = chnl_monitor::type_id::create("monitor", this);
sequencer = chnl_sequencer::type_id::create("sequencer", this);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
driver.seq_item_port.connect(sequencer.seq_item_export);
endfunction
function void set_interface(virtual chnl_intf vif);
this.vif = vif;
driver.set_interface(vif);
monitor.set_interface(vif);
endfunction
task run_phase(uvm_phase phase);//调用子类的run时省略,使用自己的方法时不能省略
//NOTE::No more needs to call run manually
//fork
// drive.run();
// monitor.run();
//join
endtask
endclass:chnl_agent
3. 代码分析与理解
driver
与sequencer
都是组件,通过TLM端口进行通信,并且为了便于item传输,UVM专门定义了匹配的TLM端口供sequencer
和driver
使用;
driver:uvm_seq_item_pull_port(类型) #(REQ, RSP) seq_item_port(句柄) 常用
uvm_analysis_port #(RSP) rsp_port
sequencer:uvm_seq_item_pull_imp(类型) #(REQ, RSP) seq_item_export(句柄) 常用
uvm_analysis_port #(RSP) rsp_port
具体步骤:
a. 由于driver
和sequencer
自身在创建时已经例化了TLM端口,所以我们只需要在agent
的build_phase阶段创建driver和sequencer,在connect_phase阶段连接driver
和sequencer
的port端口即可,不需要在组件driver
和sequencer
中声明端口并例化端口。
b. 作为target的sequencer内置的方法有:task item get_next_item(REQ)
(常用,采取blocking的方式等待从sequencer获取下一个item)和 function void item_done(RSP or null)
(常用,用来通知sequence当前的sequence item已经消化完毕,可以选择性的传递RSP参数),如果driver返回了RSP参数,则sequence需要在uvm_do发送item后用sequencer里的方法
get_response(rsp)得到rsp。
补充:rsp.set_sequence_id(req.get_sequence_id())
方法是指给返回的rsp贴上一个标签,让读者明白rsp是谁返回的,一般在用item_done()返回rsp时都要加上
1. 验证结构框图如上
2. 实际代码
class chnl_data_sequence extends uvm_sequence #(chnl_trans);
rand int pkt_id = 0;
rand int ch_id = -1;
rand int data_nidles = -1;
rand int pkt_nidles = -1;
rand int data_size = -1;
rand int ntrans = 10;
constraint cstr{
soft ch_id == -1;
soft pkt_id == 0;
soft data_size == -1;
soft data_nidles == -1;
soft pkt_nidles == -1;
soft ntrans ==10;
}
`uvm_component_utils_begin(chnl_generator)
`uvm_filed_int(pkt_id, UVM_ALL_ON)
`uvm_filed_int(ch_id, UVM_ALL_ON)
`uvm_filed_int(data_nidles, UVM_ALL_ON)
`uvm_filed_int(pkt_nidles, UVM_ALL_ON)
`uvm_filed_int(data_size, UVM_ALL_ON)
`uvm_filed_int(ntrans, UVM_ALL_ON)
`uvm_component_utils_end
`uvm_declare_p_sequencer(chnl_sequencer)
function new(string name = "chnl_data_sequence");
super.new(name, parent);
endfunction
task body();
repeat(ntrans) send_trans();
endtask
task send_trans();
chnl_trans req, rsp;
`uvm_do_with(req, {local::ch_id >= 0 -> ch_id == local::ch_id;
local::pkt_id >= 0 -> pkt_id ==local::pkt_id;
local::data_nidles >= 0 -> data_nidles ==local::data_nidles;
local::pkt_nidles >= 0 -> pkt_nidles ==local::pkt_nidles;
local::data_size >= 0 -> data.size ==local::data_size;
})
this.pkt_id++;
`uvm_info(get_type_name(), req.sprint(), UVM_HIGH)
get_response(rsp);
`uvm_info(get_type_name(), rsp.sprint(), UVM_HIGH)
assert(rsp.rsp)
else $error("[RSPERR] %0t error response received!", $time);
endtask
function string sprint();
string s;
s = {s, $sformatf("======================================\n")};
s = {s, $sformatf("chnl_generator object content is as below: \n")};
s = {s, super.sprint()};
s = {s, $sformatf("======================================\n")};
return s;
endfunction
function void post_randomize();
string s;
s = {"AFTER RANDOMIZATION \n", this.sprint()};
`uvm_info(get_type_name(), s, UVM_HIGH)
endfunction
endclass:chnl_data_sequence
3. 代码分析与理解
strat( )方法:
1.创建item
2.通过start_item()方法获得sequencer的授权许可,
3.对item进行随机化处理
4.finish_item(),在sequencer关卡前等候发送
`uvm_do宏:
包含了以上过程,创建,sequencer授权,随机化,等待发送。
具体步骤: 通过宏uvm_declare_p_sequencer(chnl_sequencer)
,chnl_data_sequence可以定位到chnl_sequencer,通过宏uvm_do_with
创建item(req),授权item(req),随机化item(req)并等待发送,task body()会自动执行。
class mcdf_virtual_sequencer extends uvm_sequencer;//定义virtual sequencer类
reg_sequencer reg_sqr;
fmt_sequencer fmt_sqr;
chnl_sequencer chnl_sqr[3];
`uvm_component_utils(mcdf_virtual_sequencer)
function new(string name = "mcdf_virtual_sequencer", uvm_component parent);
super.new(name, parent);
endfunction
endclass
class mcdf_env extends uvm_env;
//将定义的virtual sequencer类在mcdf_env中声明,例化,并且完成其与底层sequencer的句柄连接
chnl_agent chnl_agts[3];
reg_agent reg_agts;
fmt_agent fmt_agts;
mcdf_checker chker;
mcdf_coverage cvrg;
mcdf_virtual_sequencer virt_sqr;
`uvm_component_utils(mcdf_env)
function new(string name = "mcdf_env", uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
this.chker = mcdf_checker::type_id::create("chker", this);
foreach(chnl_agts[i]) begin
this.chnl_agts[i] = chnl_agent::type_id::create($sformatf("chnl_agts[%0d]", i), this);
end
this.reg_agts[i] = reg_agent::type_id::create("reg_agts", this);
this.fmt_agts[i] = fmt_agent::type_id::create("fmt_agts", this);
this.cvrg = mcdf_coverage::type_id::create("cvrg", this);
virt_sqr = mcdf_virtual_sequencer::type_id::create("virt_sqr", this);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
chnl_agts[0].monitor.mon_bp_port.connect(chker.chnl0_bp_imp);
chnl_agts[1].monitor.mon_bp_port.connect(chker.chnl1_bp_imp);
chnl_agts[2].monitor.mon_bp_port.connect(chker.chnl2_bp_imp);
reg_agt.monitor.mon_bp_port.connect(chker.reg_bp_imp);
fmt_agt.monitor.mon_bp_port.connect(chker.fmt_bp_imp);
virt_sqr.reg_sqr = reg_agt.sequencer;
virt_sqr.fmt_sqr = fmt_agt.sequencer;
foreach(virt_sqr.chnl_sqrs[i]) vir_sqr.chnl_sqrs[i] = chnl_agts[i].sequencer;
endfunction
task run_phase(uvm_phase phase);
endtask
function void report_phase(uvm_phase phase);
super.report_phase(phase);
endfunction
endclass:mcdf_env
3. 代码分析与理解
virtual sequencer
起到了桥接其他sequencer
的作用,即virtual sequencer是一个连接所有底层sequencer句柄的地方,它是一个中心化的路由器。我们只需做好virtual sequencer中各个sequencer句柄与底层sequencer实体对象的一一对接,避免句柄悬空即可。
具体步骤: 定义一个virtual sequencer
的类,在virtual sequencer
类里声明各个底层sequencer
的句柄。然后在顶层环境env
里例化virtual sequencer
类,并完成其与底层sequencer
句柄的连接。
class mcdf_base_virtual_sequence extends uvm_sequence;
//将原有test里发送激励的功能移植到新定义的mcdf_base_virtual_sequence一侧
idle_reg_sequence idle_reg_seq;
write_reg_sequence write_reg_seq;
read_reg_sequence read_reg_seq;
chnl_data_sequence chnl_data_seq;
fmt_config_sequence fmt_config_seq;
`uvm_object_utils(mcdf_base_virtual_sequence)
`uvm_declare_p_sequencer(mcdf_virtual_sequencer)
function new(string name = "mcdf_base_virtual_sequence");
super.new(name);
endfunction
virtual task body();
`uvm_info(get_type_name(), "======================STARTED====================", UVM_LOW)
this.do_reg();
this.do_formatter();
this.do_data();
`uvm_info(get_type_name(), "======================FINISHED====================", UVM_LOW)
endtask
// do register configuration
virtual task do_reg();
endtask
// do external formatter down stream slave configuration
virtual task do_formatter();
endtask
// do data transition from 3 channel slaves
virtual task do_data();
endtask
virtual function bit diff_value(int val1, int val2, string id = "value_compare");
if(val1 != val2) begin
`uvm_error("[CMPERR]", $sformatf("ERROR! %s val1 %8x != val2 %8x", id, val1, val2))
return 0;
end
else begin
`uvm_info("[CMPSUC]", $sformatf("SUCCESS! %s val1 %8x != val2 %8x", id, val1, val2), UVM_LOW)
return 1;
end
endfunction
endclass
class mcdf_base_test extends uvm_test;
//test一侧只需要在run_phase里例化sequence,并将其挂载到对应的顶层virtual sequencer即可。
mcdf_env env;
virtual chnl_intf ch0_vif;
virtual chnl_intf ch1_vif;
virtual chnl_intf ch2_vif;
virtual reg_intf reg_vif;
virtual arb_intf arb_vif;
virtual fmt_intf fmt_vif;
virtual mcdf_intf mcdf_vif;
`uvm_component_utils(mcdf_base_test)
function new(string name = "mcdf_base_test", uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase);
super.build_phase(phase);
// get virtual interface from top TB
if(!uvm_config_db#(virtual chnl_intf)::get(this, "", "ch0_vif", ch0_vif)) begin
`uvm_fatal("GETVIF", "cannot get vif handle from config DB")
end
if(!uvm_config_db#(virtual chnl_intf)::get(this, "", "ch1_vif", ch1_vif)) begin
`uvm_fatal("GETVIF", "cannot get vif handle from config DB")
end
if(!uvm_config_db#(virtual chnl_intf)::get(this, "", "ch2_vif", ch2_vif)) begin
`uvm_fatal("GETVIF", "cannot get vif handle from config DB")
end
if(!uvm_config_db#(virtual reg_intf)::get(this, "", "reg_vif", reg_vif)) begin
`uvm_fatal("GETVIF", "cannot get vif handle from config DB")
end
if(!uvm_config_db#(virtual arb_intf)::get(this, "", "arb_vif", arb_vif)) begin
`uvm_fatal("GETVIF", "cannot get vif handle from config DB")
end
if(!uvm_config_db#(virtual fmt_intf)::get(this, "", "fmt_vif", ch2_vif)) begin
`uvm_fatal("GETVIF", "cannot get vif handle from config DB")
end
if(!uvm_config_db#(virtual mcdf_intf)::get(this, "", "mcdf_vif", mcdf_vif)) begin
`uvm_fatal("GETVIF", "cannot get vif handle from config DB")
end
this.env = mcdf_env::type_id::create("env", this);
foreach(this.chnl_gen[i]) begin
this.chnl_gens[i] = chnl_generator::type_id::create($sformatf("chnl_gens[%0d]", i), this);
end
this.reg_gen = reg_generator::type_id::create("reg_gen", this);
this.fmt_gen = fmt_generator::type_id::create("fmt_gen", this);
// rpt_pkg::logname = {this.m_name, "_check.log"};
// rpt_pkg::clean_log();
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
// After get virtual interface from config_db, and then set them to
// child components
this.set_interface(ch0_vif, ch1_vif, ch2_vif, reg_vif, arb_vif, fmt_vif, mcdf_vif);
endfunction
function void end_of_elaboration_phase(uvm_phase phase);
super.end_of_elaboration_phase(phase);
uvm_root::get().set_report_verbosity_level_hier(UVM_HIGH);
uvm_root::get().set_report_max_quit_count(1);
uvm_root::get().set_timeout(10ms);
endfunction
task run_phase(uvm_phase phase);
// NOTE:: raise objection to prevent simulation stopping
phase.raise_objection(this);
this.run_top_virtual_sequence();
// Note::drop objection to request simulation stopping
phase.drop_objection(this);
endtask
virtual task run_top_virtual_sequence();
endtask
virtual function void set_interface(virtual chnl_intf ch0_vif
,virtual chnl_intf ch1_vif
,virtual chnl_intf ch2_vif
,virtual reg_intf reg_vif
,virtual arb_intf arb_vif
,virtual fmt_intf fmt_vif
,virtual mcdf_intf mcdf_vif
);
this.env.chnl_agts[0].set_interface(ch0_vif);
this.env.chnl_agts[1].set_interface(ch1_vif);
this.env.chnl_agts[2].set_interface(ch2_vif);
this. env.reg_agt.set_interface(reg_vif);
this. env.fmt_agt.set_interface(fmt_vif);
this.env.chker.set_interface(mcdf_vif, '{ch0_vif, ch1_vif, ch2_vif}, arb_vif);
this.env.cvrg.set_interface('{ch0_vif, ch1_vif, ch2_vif}, reg_vif, arb_vif, fmt_vif, mcdf_vif);
endfunction
endclass:mcdf_base_test
3. 代码分析与理解
将原先run_phase发送序列的功能移植到新定义的mcdf_base_virtual_sequence
一侧,mcdf_base_test
::run_phase( )只需要挂载对应的顶层virtual_sequence即可,即在run_phase里例化配置好的virtual sequence,然后sequence.start(env.virt_sqr)
挂载sequence。
具体步骤: 定义mcdf_base_virtual_sequence
,声明各个sequence的句柄,利用宏uvm_declare_p_sequencer(mcdf_virtual_sequencer)
,使mcdf_base_virtual_sequence
定位到mcdf_virtual_sequencer
,将原先test里的配置方法移植到mcdf_base_virtual_sequence
中。而在test里只需要例化sequence,并挂载sequence.start(env.virt_sqr)
即可。
1. 验证框图如上
2. 实际代码
class mcdf_data_consistence_basic_virtual_sequence extends mcdf_base_virtual_sequence;
//通过do_reg配置寄存器,do_formatter配置fmt,do_data配置chnl
`uvm_object_utils(mcdf_data_consistence_basic_virtual_sequence)
function new(string name = "mcdf_data_consistence_basic_virtual_sequence");
super.new(name);
endfunction
task do_reg();
bit[31:0] wr_val, rd_val;
// slv0 with len=8, prio=0, en=1
wr_val = (1<<3) + (0<<1) + 1;
`uvm_do_on_with(write_reg_seq, p_sequencer.reg_sqr, {addr == `SLV0_RW_ADDR; data == wr_val;})
`uvm_do_on_with(read_reg_seq, p_sequencer.reg_sqr, {addr == `SLV0_RW_ADDR;})
rd_val = read_reg_seq.data;
void'(this.diff_value(wr_val, rd_val, "SLV0_WR_REG"));
// slv1 with len=16, prio=1, en=1
wr_val = (2<<3) + (1<<1) + 1;
`uvm_do_on_with(write_reg_seq, p_sequencer.reg_sqr, {addr == `SLV1_RW_ADDR; data == wr_val;})
`uvm_do_on_with(read_reg_seq, p_sequencer.reg_sqr, {addr == `SLV1_RW_ADDR;})
rd_val = read_reg_seq.data;
void'(this.diff_value(wr_val, rd_val, "SLV1_WR_REG"));
// slv2 with len=32, prio=2, en=1
wr_val = (3<<3) + (2<<1) + 1;
`uvm_do_on_with(write_reg_seq, p_sequencer.reg_sqr, {addr == `SLV2_RW_ADDR; data == wr_val;})
`uvm_do_on_with(read_reg_seq, p_sequencer.reg_sqr, {addr == `SLV2_RW_ADDR;})
rd_val = read_reg_seq.data;
void'(this.diff_value(wr_val, rd_val, "SLV2_WR_REG"));
// send IDLE command
`uvm_do_on(idle_reg_seq, p_sequencer.reg_sqr)
endtask
task do_formatter();
`uvm_do_on(fmt_config_seq, p_sequencer.fmt_sqr, {fifo == LONG_FIFO; bandwidth == HIGH_WIDTH;})
endtask
task.do_data();
fork
`uvm_do_on_with(chnl_data_seq, p_sequencer.chnl_sqrs[0], {ntrans==100; ch_id==0; data_nidles ==0;pkt_nidles==1;data_size==8;})
`uvm_do_on_with(chnl_data_seq, p_sequencer.chnl_sqrs[1], {ntrans==100; ch_id==1; data_nidles ==1;pkt_nidles==4;data_size==16;})
`uvm_do_on_with(chnl_data_seq, p_sequencer.chnl_sqrs[2], {ntrans==100; ch_id==2; data_nidles ==2;pkt_nidles==8;data_size==32;})
join
#10us; // wait until all data haven been transfered through MCDF
endtask
endclass: mcdf_data_consistence_basic_virtual_sequence
class mcdf_data_consistence_basic_test extends mcdf_base_test;
`uvm_component_utils(mcdf_data_consistence_basic_test)
function new(string name = "mcdf_data_consistence_basic_test", uvm_component parent);
super.new(name, parent);
endfunction
task run_top_virtual_sequence();
mcdf_data_consistence_basic_virtual_sequence top_seq = new();
top_seq.start(env.virt_sqr);
endtask
endclass:mcdf_data_consistence_basic_test
3. 代码分析与理解
uvm_do_with(item, {constraint}): 所有item只对应一个顶层sequencer时可以使用。
例如,对于chnl_sequence和chnl_sequencer,如果没有宏uvm_declare_p_sequencer(chnl_sequencer)
,则使用宏uvm_do_with
时,所有item发送默认定位的是m_sequence即uvm_sequence。
uvm_do_on_with(item, p_sequencer.SQR, {constraint} ): 所有item可通过改变p_sequencer.SQR里的SQR让item定位到对应的SQR上,同样,在定义sequence类时,需声明宏 uvm_declare_p_sequencer(mcdf_virtual_sequencer)
,那么item定位到的sequencer就是mcdf_virtual_sequencer的句柄p_sequencer。
uvm_sequence,uvm_driver,uvm_sequencer都是参数化的类,在数据句柄传递时要注意数据类型的转换。
讲义p169
class bus_trans extends uvm_sequence_item;
rand int data;
`uvm_object_utils_begin(bus_trans)
`uvm_field_int(data, UVM_ALL_ON)
`uvm_object_utils_end
...
endclass
class flat_seq extends uvm_sequence;
`uvm_object_utils(flat_seq)
...
task body();
uvm_sequence_item tmp;
bus_trans req, rsp;
tmp = create_item(bus_trans::get_type(), m_sequencer, "req");
//使用create_item创建item时返回的是父类句柄(子类对象),所以需要声明一个uvm_sequence_item tmp去指向新创建的item。
//不能直接用子类bus_trans req去指向该item
void'($cast(req, tmp));
req.randomize with {data == 10;};
`uvm_info("SEQ", $sformatf("send a item \n %s", req.sprint()), UVM_LOW)
finish_item(req);
get_response(tmp);
void'($cast(rsp, tmp));
`uvm_info("SEQ", $sformatf("got a item \n %s", rsp.sprint()), UVM_LOW)
endtask
endclass
class driver extends uvm_driver;
`uvm_component_utils(driver)
...
task run_phase(uvm_phase phase)
REQ tmp;
bus_trans req, rsp;
seq_item_port.get_next_item(tmp);
void'($cast(req, tmp));
`uvm_info("DRV", $sformatf("got a item \n %s", req.sprint()), UVM_LOW)
void'($cast(rsp, req.clone());
rsp.set_sequence_id(req.get_sequence_id());
rsp.data += 100;
seq_item_port.item_done(rsp);
`uvm_info("DRV", $sformatf("sent a item \n %s", rsp.sprint()), UVM_LOW)
class sequencer extends uvm_sequencer;
`uvm_component_utils(sequencer)
...
endclass
driver,sequence,sequencer都是参数化的类,在不指定数据类型时均为uvm_sequence_item类型。
对于class flat_seq extends uvm_sequence
类来说,未指定数据参数,那么在使用create_item创建item时返回的是父类句柄,指向的是子类对象,所以需要声明一个uvm_sequence_item tmp去指向新创建的item。不能直接用子类bus_trans req去指向该item。
tmp = create_item(bus_trans::get_type(), m_sequencer, "req")
:
1.sequence未指定参数,默认返回的是父类句柄;
2.bus_trans::get_type()指的是创建的对象为bus_trans类型(子类);
3.m_sequencer指的是默认挂载到父类uvm_sequencer上;
4.“req”指的是创建的对象句柄字符串名字为req。
sequence同理。
class chnl_driver extends uvm_driver #(chnl_trans);
...
endclass
class chnl_sequencer extends uvm_sequencer #(chnl_trans);
...
endclass
class chnl_data_sequence extends uvm_sequence #(chnl_trans);
...
endclass
使用参数化的类,就不需要句柄的转换了。