本文为验证平台添加了 sequence和sequencer两个组件,整个验证平台基本成型,文中并没有选择《UVM实战》2.4.3中的default_sequence 的方式,因为我在工作中也没有采用这种方式。
https://github.com/shirainbown/UVM-Step-by-Step/tree/master
之前就说了,driver 本身不应该负责激励的产生而是负责激励的驱动,激励的产生由sequence负责,而sequencer只需要源源不断把sequence产生的transaction 送给driver。
1. 添加sequencer
一个sequencer的定义如下:
`ifndef MY_SEQUENCER__SV
`define MY_SEQUENCER__SV
class my_sequencer extends uvm_sequencer #(my_transaction);
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
`uvm_component_utils(my_sequencer)
endclass
`endif
上述需要注意几点:
sequencer 继承于uvm_sequencer,同时还有一个参数#(my_transaction),这里指定了该sequencer处理的对象是my_transaction。
需要用 uvm_component_utils对其进行注册,其余所有东西不用再定义,uvm内置的功能已经满足了所有需求。
sequencer 是 agent的一个部分,需要和drier、monitor一起在agent中声明。
2. 添加 sequence
sequence 不属于验证平台的任何一部分,同样可以理解为transaction本质上也不属于平台的一部分,它们类似一种消耗品,都属于uvm_object,而其他组件都是始终存在于仿真期间的,属于uvm_component。sequence类似手枪,而transaction就是里面的子弹,sequencer控制手枪发送子弹。
`ifndef MY_SEQUENCE__SV `define MY_SEQUENCE__SV class my_sequence extends uvm_sequence #(my_transaction); my_transaction m_trans; function new(string name= "my_sequence"); super.new(name); endfunction virtual task body(); repeat (10) begin `uvm_do(m_trans) // 发送transaction end #1000; endtask `uvm_object_utils(my_sequence) endclass `endif
my_sequence 继承于 uvm_sequence,同样需要指定发送的参数transaction的类型。
uvm_do是一个宏,可以完成:
1) 随机化创建一个transaction,实例化后给m_trans
2) 发送给sequencer
sequencer 首先一直等待driver的请求,如果driver有请求新的trasaction,则sequencer同意接收sequence发送的transaction。
3. 修改driver
那么下一步需要修改driver,来增加向sequencer请求transaction的功能。
`ifndef MY_DRIVER__SV
`define MY_DRIVER__SV
class my_driver extends uvm_driver#(my_transaction);
virtual my_if vif;
`uvm_component_utils(my_driver)
function new(string name = "my_driver", uvm_component parent = null);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
`uvm_fatal("my_driver", "virtual interface must be set for vif!!!")
endfunction
extern task main_phase(uvm_phase phase);
extern task drive_one_pkt(my_transaction tr);
endclass
task my_driver::main_phase(uvm_phase phase);
vif.data <= 8'b0;
vif.valid <= 1'b0;
while(!vif.rst_n)
@(posedge vif.clk);
while(1) begin
seq_item_port.get_next_item(req);
drive_one_pkt(req);
seq_item_port.item_done();
end
endtask
task my_driver::drive_one_pkt(my_transaction tr);
byte unsigned data_q[];
int data_size;
data_size = tr.pack_bytes(data_q) / 8;
`uvm_info("my_driver", "begin to drive one pkt", UVM_LOW);
repeat(3) @(posedge vif.clk);
for ( int i = 0; i < data_size; i++ ) begin
@(posedge vif.clk);
vif.valid <= 1'b1;
vif.data <= data_q[i];
end
@(posedge vif.clk);
vif.valid <= 1'b0;
`uvm_info("my_driver", "end drive one pkt", UVM_LOW);
endtask
`endif
同样的,我们需要给driver新增参数 #(my_transaction)。
seq_item_port 是uvm_driver中内置的成员变量,我们只需要使用get_next_item的方法就能向sequencer发送请求,请求完后,还需要使用item_done 来通知sequencer已经成功获取到了transaction。
uvm_sequence 中同样有 seq_item_export成员变量用于发送transaction。
4. 通过 agent连接seq_item_port和seq_item_export
`ifndef MY_AGENT__SV
`define MY_AGENT__SV
class my_agent extends uvm_agent ;
my_sequencer sqr; // 声明 sequencer
my_driver drv;
my_monitor mon;
uvm_analysis_port #(my_transaction) ap;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
extern virtual function void build_phase(uvm_phase phase);
extern virtual function void connect_phase(uvm_phase phase);
`uvm_component_utils(my_agent)
endclass
function void my_agent::build_phase(uvm_phase phase);
super.build_phase(phase);
if (is_active == UVM_ACTIVE) begin
sqr = my_sequencer::type_id::create("sqr", this);
drv = my_driver::type_id::create("drv", this);
end
mon = my_monitor::type_id::create("mon", this);
endfunction
function void my_agent::connect_phase(uvm_phase phase); //新增该函数,将两个port连接起来
super.connect_phase(phase);
if (is_active == UVM_ACTIVE) begin
drv.seq_item_port.connect(sqr.seq_item_export);
end
ap = mon.ap;
endfunction
`endif
connect_phase 在 build_phase之后执行,可以确保每个组件都已经实例化后再进行连接,但是实际上connect_phase的这部分代码也可以合入build_phase中。
5. 在env中启动sequence
`ifndef MY_ENV__SV
`define MY_ENV__SV
class my_env extends uvm_env;
my_agent i_agt;
my_agent o_agt;
my_model mdl;
my_scoreboard scb;
uvm_tlm_analysis_fifo #(my_transaction) agt_scb_fifo;
uvm_tlm_analysis_fifo #(my_transaction) agt_mdl_fifo;
uvm_tlm_analysis_fifo #(my_transaction) mdl_scb_fifo;
function new(string name = "my_env", uvm_component parent);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
i_agt = my_agent::type_id::create("i_agt", this);
o_agt = my_agent::type_id::create("o_agt", this);
i_agt.is_active = UVM_ACTIVE;
o_agt.is_active = UVM_PASSIVE;
mdl = my_model::type_id::create("mdl", this);
scb = my_scoreboard::type_id::create("scb", this);
agt_scb_fifo = new("agt_scb_fifo", this);
agt_mdl_fifo = new("agt_mdl_fifo", this);
mdl_scb_fifo = new("mdl_scb_fifo", this);
endfunction
extern virtual function void connect_phase(uvm_phase phase);
extern virtual task main_phase(uvm_phase phase);
`uvm_component_utils(my_env)
endclass
function void my_env::connect_phase(uvm_phase phase);
super.connect_phase(phase);
i_agt.ap.connect(agt_mdl_fifo.analysis_export);
mdl.port.connect(agt_mdl_fifo.blocking_get_export);
mdl.ap.connect(mdl_scb_fifo.analysis_export);
scb.exp_port.connect(mdl_scb_fifo.blocking_get_export);
o_agt.ap.connect(agt_scb_fifo.analysis_export);
scb.act_port.connect(agt_scb_fifo.blocking_get_export);
endfunction
// -------------------
task my_env::main_phase(uvm_phase phase); //重点看这里
my_sequence seq;
phase.raise_objection(this);
seq = my_sequence::type_id::create("seq");
seq.start(i_agt.sqr); //指定发送给哪个sequencer
phase.drop_objection(this);
endtask
// --------------------
`endif
phase.raise_objection(this)和 phase.drop_objection(this) 用于控制仿真的开始和结束,与sequence紧密相关,本质上就是在sequence发送前raise,在所有transaction发送完后结束一轮仿真。在后续我们可以看到如何重复多次启动一个sequence开启新的一轮测试。