UVM Driver多Phase Pipeline功能的实现
Pipeline简介
在很多总线协议中是支持pipeline操作的,比如AMBA总线的AHB总线和AXI总线,在实现Data总线和Address总线以及Response总线的并行传输过程中,会遇到很多问题,比如对于AXI总线来说需要6个通道独立的去实现Write Data,Write Control,Write Response,Read Data,Read Control,Read Response,如何实现其中信息的关联?Driver如何实现六个通道的pipeline的驱动等问题,如何保证Control命令发出以后,Data和Response可以接受完整,仿真才结束。。。。。。等问题。这里不介绍这些内容,从一个简单的DUT的Driver Pipeline驱动的实现开始介绍,麻雀虽小五脏俱全,对实现复杂的pipeline功能,有很多的借鉴意义。
UVM的driver实现pipeline的功能,在Mentor的cook-book中有相关介绍,其中运用了比较复杂的代理机制,通过Sequence和interface的BFM代理,以及Driver的相互控制来实现pipeline的功能。控制起来比较复杂,不容易debug其中的问题。这里不做介绍。
介绍一种简单通用的pipeline的实现方法。
DUT简介
DUT是一个乘除运算单元可以pipeline的接受数据,做乘除运算,并可以pipeline的发出数据,下图是port介绍和Timing图:
Portlist | Type | Description |
---|---|---|
data_a | input [7:0] | Operation data A |
data_b | input [7:0] | Operation data B |
valid_i | input | Operation data valid,high valid |
data_c | output [7:0] | Result data C |
valid_o | output | Result data valid,high valid |
clk_i | input | Work clock |
rst_n | input | Reset signal, low valid |
Pipe line driver的实现
以下是driver的实现方式:
class op_driver extends uvm_driver #(gf_op_item);
gf_op_item pipeline[$];
virtual gf_op_interface vif;
gf_op_config cfg;
`uvm_component_utils_begin(gf_op_driver)
`uvm_component_utils_end
function new(string name,uvm_component parent);
super.new(name,parent);
endfunction
function build_phase(uvm_phase phase);
super.build_phase(phase);
endfuntion
function connect_phase(uvm_phase phase);
this.vif = cfg.vif;
endfunction
task run_phase(uvm_phase phase)
vif.data_a <= 0;
vif.data_b <= 0;
vif.valid_i <=0;
@(posedge vif.clk_i iff vif.rst_n_i !==0);
fork
forever begin
gf_op_item trans;
`uvm_info(get_type_name(),"waiting for data from sequencer",UVM_LOW)
seq_item_port.get_next_item(req);
if($cast(trans,req))
`uvm_fatal("CASTFL","Failed to cast req t this trans in get_and drive")
drive_item(trans);
seq_item_port.item_done();
end
forever begin
wait_for_result();
end
join
endtask
task gf_op_driver::drive_item(gf_op_item trans);
repeat(trans.valid_delay) begin
vif.data_a <= 0;
vif.data_b <= 0;
vif.valid_i <= 0;
@(posedge vif.clk_i);
end
vif.data_a <= trans.data_a;
vif.data_b <= trans.data_b;
vif.valid_i <= 1'b1;
pipeline.push_back(trans);
@(posedge vif.clk_i);
vif.data_a <= 0;
vif.data_b <= 0;
vif.valid_i <= 0;
endtask
task wait_for_result();
gf_op_item trans_done;
trans_done = new();
wait(pipeline.size>0);
trans_done = pipeline.pop_front();
while(vif.valid_o !=1) begin
@(posedge vif.clk_i);
end
trans_done.data_c = vif.data_c;
seq_item_port.put_response(trans_done);
//Very important for the pipeline
endtask
endclass
通过pipeline queue可以实现两个独立通道的信息互通,当然你也可以采用其他方式实现。
Pipeline Sequence的实现
Driver实现以后还是不够的,我们的sequence也要进行相应的改变。下面对两种不同的sequence的写法进行解释:
class gf_op_sequence extends uvm_sequence #(gf_op_item);
virtual gf_op_interface vif;
ivt_gf_op_config cfg;
rand int num_data;
`uvm_object_utils_begin(gf_op_sequence)
`uvm_object_utils_end
`uvm_declare_p_sequencer(gf_op_sequencer)
function new(string name='gf_op_sequence');
super.new();
endfunction
virtual task pre_start();
cfg=p_sequencer.cfg;
vif=cfg.vif;
endtask
virtual task body();
gf_op_item req;
if(!uvm_config_db #(gf_op_config)::get(null,get_full_name,"cfg",cfg)) begin
`uvm_fatal("pre_randomize","Cannot get GF_OP configuration in transaction")
end
repeat(num_data) begin
req=gf_op_item::type_id::create("req");
req.cfg = cfg;
assert(req.randomize());
start_item(req);
finish_item(req);
end
endtask
endclass
通过运行simulator发现在valid data_a 和data_b可以pipeline的发出,但是最后一笔的result data_c get不到数据,在发完最后一笔的valid数据之后,仿真就结束了。究其原因,是sequence已经执行完成,虽然driver在等result。修改sequence如下:
class gf_op_sequence extends uvm_sequence #(gf_op_item);
virtual gf_op_interface vif;
ivt_gf_op_config cfg;
rand int num_data;
`uvm_object_utils_begin(gf_op_sequence)
`uvm_object_utils_end
`uvm_declare_p_sequencer(gf_op_sequencer)
function new(string name='gf_op_sequence');
super.new();
endfunction
virtual task pre_start();
cfg=p_sequencer.cfg;
vif=cfg.vif;
endtask
virtual task body();
gf_op_item req;
if(!uvm_config_db #(gf_op_config)::get(null,get_full_name,"cfg",cfg)) begin
`uvm_fatal("pre_randomize","Cannot get GF_OP configuration in transaction")
fork
repeat(num_data) begin
req=gf_op_item::type_id::create("req");
req.cfg = cfg;
assert(req.randomize());
start_item(req);
finish_item(req);
end
join_none
repeat(num_data) begin
get_response(rsp);
end
endtask
endclass
在这里虽然sequence不用等到result的结果,在monitor中会monitor result的结果上报给referenc model和scoreboard,所以必须保证数据的完整性。这样可以在最后一笔的valid数据发给dut后,等待所有的结果出来之后仿真才会结束。
但是这样做还不够,如果pipeline的结果需要等待,排队的valid数据超过8个以后,sequence在仿真中会被挂死,仿真结束不了,这是什么原因呢?经过查找uvm_sequence的源码会发现 response的queue的深度默认是8,如果超过8个,就会丢失get_response(rsp)的次数,导致sequence不能正常结束。
set_response_queue_depth(8);
通过修改sequence,仿真正常结束。
class gf_op_sequence extends uvm_sequence #(gf_op_item);
virtual gf_op_interface vif;
ivt_gf_op_config cfg;
rand int num_data;
`uvm_object_utils_begin(gf_op_sequence)
`uvm_object_utils_end
`uvm_declare_p_sequencer(gf_op_sequencer)
function new(string name='gf_op_sequence');
super.new();
endfunction
virtual task pre_start();
cfg=p_sequencer.cfg;
vif=cfg.vif;
endtask
virtual task body();
gf_op_item req;
set_response_queue_depth(512);
if(!uvm_config_db #(gf_op_config)::get(null,get_full_name,"cfg",cfg)) begin
`uvm_fatal("pre_randomize","Cannot get GF_OP configuration in transaction")
fork
repeat(num_data) begin
req=gf_op_item::type_id::create("req");
req.cfg = cfg;
assert(req.randomize());
start_item(req);
finish_item(req);
end
join_none
repeat(num_data) begin
get_response(rsp);
end
endtask
endclass
至此UVM 简单DUT pipeline可以正常工作,如果需要改为多通道的pipeline,可以参考以上代码进行实现。