知识点:
uvm世界运转得必要条件:组件之间进行事务传送,sequence_item是道路上行驶卡车,sequence是道路,sequencer是目的地的关卡,driver是最终的目的地。sequence_item从sequence出发,经过sequencer,到达driver。
uvm_sequence_item:基于uvm_object类,可以在任何阶段创建;完成所需要的具体数据和控制要求,由于item本身的数据属性,为了充分利用UVM域声明的特性,可以把不要的数据成员通过`uvm_field_xxx宏来声明,以便日后uvm_object的基本数据方法自动实现;
uvm_sequence:基于uvm_object类,可以在任何阶段创建;sequence只负责生成item的相关内容,驱动激励时序的任务则是由driver完成的。
uvm_sequencer:将数据put给driver;
driver:负责进行处理数据;driver与sequencer之间的TLM通信采用get模式,由driver发起请求,从sequencer获得item,再由sequencer将其传给driver。所以driver是一个永动机,不用的索求item;
实现driver和sequencer的request获取和response返回端口:
item的传送:driver::seq_item_port和sequencer::seq_item_export
端口的连接:driver::seq_item_port::connect(sequencer::seq_item_export)
REQ和RSP的一致性:在自定义sequencer和driver时就表明传递的具体item类型,就不需要额外的转换了,否则在driver得到REQ在进行下一步数据处理的时候,需要动态转换将REQ转换为uvm_sequence_item的子类型才能有效的获取数据。目的是方便统一处理。
1.1 driver与squencer的改建
首先移除邮箱句柄以及句柄通信的方式,然后更改通信方式,这里使用uvm_driver::seq_Item_port的通信方式,通过get_next_item的方式获取句柄,然后等待driver消化了req的,通过item_done的方式告知sequence此次传输已经结束了,然后把rsp返回到fifo里面;移除generator,定义具有相同功能的reg_sequence和reg_sequencer:
1.2 底层sequence的提取
把各个generator中的transaction任务提取为对应的sequence,除了声明reg_sequencer类以外,还声明了reg_sequence类,内容完全是reg_generator类的内容基础上做的改变,移除了邮箱句柄,task start()替换为task body(),mcdf_base_test中的idle_reg、write_reg、read_reg都是通过调用generator来做randomize,现在改用`uvm_do_with 的方法完成随机化。send_trans的目的还是发送reg_trans,以前是通过邮箱握手的方式进行的,使用`uvm_do_with的方式也包含了创建、随机化和发送,调用get_response的函数拿回句柄。把 mcdf_base_test 中的task移到 reg_sequences中去,声明为idle_reg_sequence、write_reg_sequence、read_reg_sequence
class reg_base_sequence extends uvm_sequence #(reg_trans);
rand bit[7:0] addr = -1;
rand bit[1:0] cmd = -1;
rand bit[31:0] data = -1;
constraint cstr{
soft addr == -1;
soft cmd == -1;
soft data == -1;
}
`uvm_object_utils_begin(reg_base_sequence)
`uvm_field_int(addr, UVM_ALL_ON)
`uvm_field_int(cmd, UVM_ALL_ON)
`uvm_field_int(data, UVM_ALL_ON)
`uvm_object_utils_end
`uvm_declare_p_sequencer(reg_sequencer)
function new (string name = "reg_base_sequence");
super.new(name);
endfunction
task body();
send_trans();
endtask
// generate transaction and put into local mailbox
task send_trans();
reg_trans req, rsp;
`uvm_do_with(req, {local::addr >= 0 -> addr == local::addr;
local::cmd >= 0 -> cmd == local::cmd;
local::data >= 0 -> data == local::data;
})
`uvm_info(get_type_name(), req.sprint(), UVM_HIGH)
get_response(rsp);
`uvm_info(get_type_name(), rsp.sprint(), UVM_HIGH)
if(req.cmd == `READ)
this.data = rsp.data;
assert(rsp.rsp)
else $error("[RSPERR] %0t error response received!", $time);
endtask
function void post_randomize();
string s;
s = {s, "AFTER RANDOMIZATION \n"};
s = {s, "=======================================\n"};
s = {s, "reg_base_sequence object content is as below: \n"};
s = {s, super.sprint()};
s = {s, "=======================================\n"};
`uvm_info(get_type_name(), s, UVM_HIGH)
endfunction
endclass: reg_base_sequence
class idle_reg_sequence extends reg_base_sequence;
constraint cstr{
addr == 0;
cmd == `IDLE;
data == 0;
}
`uvm_object_utils(idle_reg_sequence)
function new (string name = "idle_reg_sequence");
super.new(name);
endfunction
endclass: idle_reg_sequence
class write_reg_sequence extends reg_base_sequence;
constraint cstr{
cmd == `WRITE;
}
`uvm_object_utils(write_reg_sequence)
function new (string name = "write_reg_sequence");
super.new(name);
endfunction
endclass: write_reg_sequence
class read_reg_sequence extends reg_base_sequence;
constraint cstr{
cmd == `READ;
}
`uvm_object_utils(read_reg_sequence)
function new (string name = "read_reg_sequence");
super.new(name);
endfunction
endclass: read_reg_sequence
1.3 sequencer的创建和连接
在agent里面声明、创建对应的sequencer,并和driver通过TLM port连接起来;声明reg_sequencer的句柄,build_phase中例化,connect_phase中连接。idle_reg、write_reg、read_reg已经被改造成相应的sequence
class reg_agent extends uvm_agent;
reg_driver driver;
reg_monitor monitor;
reg_sequencer sequencer;
local virtual reg_intf vif;
`uvm_component_utils(reg_agent)
function new(string name = "reg_agent", uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
driver = reg_driver::type_id::create("driver", this);
monitor = reg_monitor::type_id::create("monitor", this);
sequencer = reg_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 reg_intf vif);
this.vif = vif;
driver.set_interface(vif);
monitor.set_interface(vif);
endfunction
endclass
1.4 移除generator的踪迹
清楚generator在mcdf_base_test中的产物;以前在mcdf_base_test,generator是同一层,目前generator是被移除了 ,而sequencer已经下沉到agent里面了,所有的sequencer将组合到virtual sequencer里面
1.5 移除uvm_base_test的transaction发送方法
uvm_base_test之前除了承担着容器的作用,还承担着协调generator的作用,目前我们需要它只是承担容器的作用,内部由mcdf_env、mcdf_config配置对象、被用来挂载的顶层sequence组成。
1.6 添加顶层的virtual sequencer
添加virtual sequencer的作用是:单一的sequencer挂载很简单,通过uvm_sequence::start()来挂载root sequence,如果各个模块环境的都可以复用sequence资源,那么就需要一个可以容纳各个sequence的容器,称之为virtual sequence ,对应的有着virtual sequencer。virtual sequence可以承载不同的sequence,而且一般挂载在virtual sequencer中,virtual sequencer一般起到桥接的作用,链接所有底层sequencer句柄的。因为virtual sequencer本身不会传递item数据,所以virtual sequencer不需要和任何的driver进行TLM链接。
class mcdf_virtual_sequencer extends uvm_sequencer;
reg_sequencer reg_sqr;
fmt_sequencer fmt_sqr;
chnl_sequencer chnl_sqrs[3];
`uvm_component_utils(mcdf_virtual_sequencer)
function new (string name = "mcdf_virtual_sequencer", uvm_component parent);
super.new(name, parent);
endfunction
endclass
1.7 完成在mcdf_env中mcdf_virtual_sequencer的声明、例化和连接
1.8 重构mcdf_base_test
将mcdf_base_test的run_phase阶段的发送序列功能移植到mcdf_base_virtual_sequence中去,mcdf_base_test的run_phase只需要挂载对应的底层virtual sequence。以前的mcdf_base_test既包含了环境也包含了测试场景的构建,重构mcdf_base_test把他拆解成了两部分,一部分是mcdf_base_test,承担了容器的功能,只是例化了env,对于场景的描述都是在mcdf_base_virtual_sequence,对各个底层的sequence进行声明,在body()里面,作用相当于mcdf_base_test里面的run_phase,所以场景的调动都交给了mcdf_base_virtual_sequence。
class mcdf_base_virtual_sequence extends uvm_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
virtual task do_reg();
endtask
virtual task do_formatter();
endtask
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;
//TODO-1.4 remove the generators' handle clarification
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 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", fmt_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);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
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();
// User to implement this task in the child tests
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
1.9 重构mcdf_dala_consistence_basic_test
以前分别的实现三个task,现在交给了virtual sequence,mcdf_data_consistence_basic_virtual_sequence继承于mcdf_base_virtual_sequence ,重新定义了do_reg、do_formatter、do_data;`uvm_do_on_with(write_reg_seq, p_sequencer.reg_sqr, {addr == `SLV0_RW_ADDR; data == wr_val;})代表调用write_reg_seq,然后挂载到p_sequencer.reg_sqr中去,with是告诉地址和数据分别是多少。 在mcdf_base_virtual_sequenc中声明virtual sequence的时候就已经声明过p_sequencer.reg_sqr 。
`uvm_declare_p_sequencer(mcdf_virtual_sequencer);
run_top_virtual_sequence任务做两件事,一个是例化,一个数挂载。mcdf_data_consistence_basic_test类的代码变得十分简洁。
class mcdf_data_consistence_basic_virtual_sequence extends mcdf_base_virtual_sequence;
`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_with(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
1.10 重构mcdf_full_random_test
class mcdf_full_random_virtual_sequence extends mcdf_base_virtual_sequence;
`uvm_object_utils(mcdf_base_virtual_sequence)
function new (string name = "mcdf_base_virtual_sequence");
super.new(name);
endfunction
task do_reg();
bit[31:0] wr_val, rd_val;
// slv0 with len={4,8,16,32}, prio={[0:3]}, en={[0:1]}
wr_val = ($urandom_range(0,3)<<3)+($urandom_range(0,3)<<1)+$urandom_range(0,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"));
// slv0 with len={4,8,16,32}, prio={[0:3]}, en={[0:1]}
wr_val = ($urandom_range(0,3)<<3)+($urandom_range(0,3)<<1)+$urandom_range(0,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"));
// slv0 with len={4,8,16,32}, prio={[0:3]}, en={[0:1]}
wr_val = ($urandom_range(0,3)<<3)+($urandom_range(0,3)<<1)+$urandom_range(0,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_with(fmt_config_seq, p_sequencer.fmt_sqr, {fifo inside {SHORT_FIFO, ULTRA_FIFO}; bandwidth inside {LOW_WIDTH, ULTRA_WIDTH};})
endtask
task do_data();
fork
`uvm_do_on_with(chnl_data_seq, p_sequencer.chnl_sqrs[0],
{ntrans inside {[400:600]}; ch_id==0; data_nidles inside {[0:3]}; pkt_nidles inside {1,2,4,8}; data_size inside {8,16,32};})
`uvm_do_on_with(chnl_data_seq, p_sequencer.chnl_sqrs[1],
{ntrans inside {[400:600]}; ch_id==0; data_nidles inside {[0:3]}; pkt_nidles inside {1,2,4,8}; data_size inside {8,16,32};})
`uvm_do_on_with(chnl_data_seq, p_sequencer.chnl_sqrs[2],
{ntrans inside {[400:600]}; ch_id==0; data_nidles inside {[0:3]}; pkt_nidles inside {1,2,4,8}; data_size inside {8,16,32};})
join
#10us; // wait until all data haven been transfered through MCDF
endtask
endclass: mcdf_full_random_virtual_sequence
class mcdf_full_random_test extends mcdf_base_test;
`uvm_component_utils(mcdf_full_random_test)
function new(string name = "mcdf_full_random_test", uvm_component parent);
super.new(name, parent);
endfunction
task run_top_virtual_sequence();
mcdf_full_random_virtual_sequence top_seq = new();
top_seq.start(env.virt_sqr);
endtask
endclass: mcdf_full_random_test