目录
sequencer
sequencer控制着sequence与driver之间的sequence item流向,一个sequence必须挂载在一个sequencer上运行 ;uvm_sequencer派生自uvm_sequencer_param_base,其定义如下所示:
class uvm_sequencer #(
type REQ = uvm_sequence_item,
RSP = REQ//定义了传递的参数类
) extends uvm_sequencer_param_base #(REQ, RSP)
除了构造方法new之外,uvm_sequencer还提供了stop_sequences()方法用来终止挂载在该sequencer上的sequence,使sequencer回到复位状态,该方法的定义如下:
virtual function void stop_sequences()
uvm_sequencer内建了两个TLM端口seq_item_export和rsp_export来进行与driver之间的通信:
uvm_seq_item_pull_imp #(REQ,RSP,this_type) seq_item_export;//imp端口
uvm_analysis_export #(RSP) rsp_port;//分析端口
通常用来与driver通信的是第一个imp端口;上面的端口带有下面这些方法,可以通过driver的端口来调用下面的一系列方法进行通信:
Requests://REQ
virtual task get_next_item (output REQ request);
virtual task try_next_item (output REQ request);
virtual task get (output REQ request);
virtual task peek (output REQ request);
Responses://RSP
virtual function void item_done (input RSP response=null);
virtual task put (input RSP response);
Sync Control:
virtual task wait_for_sequences ();
virtual function bit has_do_available ();
(1)get_next_item
virtual task get_next_item (
output REQ t
)//采用blocking的方式从sequence获取下一个sequence item
(2)try_next_item
virtual task try_next_item (
output REQ t
);采用non_blocking的方式从sequence获取下一个sequence_item,如果返回的结果为null表明sequence没有准备好
它与get_next_item()的区别就是,它是非阻塞的,而后者是阻塞的;get_next_item只有获取到新的item后才会继续往下执行,否则就一直等待;
(3) item_done
virtual function void item_done (
RSP item = null
)//用来通知sequence表明当前sequence item已经由driver处理完成,可选择性的传递RSP
(4) wait_for_sequences ()
virtual task wait_for_sequences ();
//等待当前的sequence直至产生下一个sequence_item;
(5)has_do_available
virtual function bit has_do_available ();
//如果sequence已经准备好,并且可以获取下一个sequence_item则返回1,否则返回0
(6)get
task get (
output REQ t
)//采用get方法获取下一个sequence item
(7)peek
task peek (
output REQ t
);//采用peek的方法获取下一个sequence_item
(8)put
virtual task put (
RSP t
)//以blocking的方式返回RSP给sequence
(9)put_response
function void put_response (
RSP t
)//以non_blocking的方式返回RSP给sequence,如果成功返回1,否则返回0
Driver Sequencer Handshake
driver中有一个叫做uvm_seq_item_pull_port的TLM端口,该端口与sequencer的TLM端口:uvm_seq_item_pull_imp对应。driver可以利用sequencer中imp端口定义的方法从sequencer获取下一个sequence item。也就是说driver和sequencer之间的通信是利用TLM端口来完成的。
在uvm_driver和uvm_sequencer这两个基类中有内建的TLM端口,所以当用户自定义driver和sequencer继承这两个基类时,不需要再实例化TLM端口,只需要调用即可。TLM端口在uvm_driver和uvm_sequencer中的定义情况如下:
// Definition of uvm_driver
class uvm_driver #(type REQ=uvm_sequence_item,
type RSP=REQ) extends uvm_component;//REQ的类默认为是uvm_sequence_item
// Port: seq_item_port
// Derived driver classes should use this port to request items from the
// sequencer. They may also use it to send responses back.
uvm_seq_item_pull_port #(REQ, RSP) seq_item_port;//创建TLM端口
// Port: rsp_port
// This port provides an alternate way of sending responses back to the
// originating sequencer. Which port to use depends on which export the
// sequencer provides for connection.
uvm_analysis_port #(RSP) rsp_port;//TLM分析端口,分析端口一般用于广播
REQ req;
RSP rsp;
// Rest of the code follows ...
endclass
rsp_port只有在driver使用该端口向sequencer写入回复(write response)时才需要与sequencer中的rsp_export分析端口连接;
// Definition of uvm_sequencer
class uvm_sequencer #(type REQ=uvm_sequence_item, RSP=REQ)
extends uvm_sequencer_param_base #(REQ, RSP);
// Variable: seq_item_export
// This export provides access to this sequencer's implementation of the
// sequencer interface.
uvm_seq_item_pull_imp #(REQ, RSP, this_type) seq_item_export;//创建TLM端口
uvm_analysis_export #(RSP) rsp_export;//TLM分析端口
// Rest of the class contents follow ...
endclass
两个TLM端口要在agent中的connect_phase中连接,如下:
class my_agent extends uvm_agent;
`uvm_component_utils (my_agent)
my_driver #(my_sequence_item) m_drv;
uvm_sequencer #(my_sequence_item) m_seqr;
virtual function void connect_phase (uvm_phase phase);
// Always the port is connected to an export
m_drv.seq_item_port.connect(m_seqr.seq_item_export);//注意,发起端都是driver,所以driver的端口调用connect()方法,sequencer的端口为connect()方法的参数
endfunction
endclass
用户在自定义sequencer或者driver的时候,它们可以使用缺省类型type REQ=uvm_sequence_item,以及RSP与REQ类型一致。同时这会带来一个潜在的类型转换($cast(destination,source))要求,即driver得到REQ对象(uvm_sequence_item)在进行下一步处理时,需要进行动态的类型转换,将REQ转换为uvm_sequence_item的子类型才可以从中获取有效的成员数据;而另外一种可行的方式是在自定义sequencer和driver时就标明了其传递的具体item类型,这样就不用再进行二次的类型转换了。通常情况下,RSP类型与REQ类型保持一致,这么做的好处是为了便于统一处理,方便item对象的拷贝、修改等操作 ;
driver在消化完当前的REQ后,可以通过item_done(input RSP) 方法来告知sequence当前sequence_item已经处理完毕,返回的RSP可选。driver也可以利用put_response()或put()方法来单独发送RSP;除此之外,还可以利用driver与sequencer之间的分析端口来完成RSP的发送!!
Using get_next_item()
driver从squencer获得要求的sequence item(request sequence item REQ),同时可以选择的返回一个反馈sequence item(response sequence item RSP) ,通常REQ和RSP是同一类的,也可以设置为不同类。driver发送完RSP后,接着再获取下一个REQ。获取REQ的方法有两种,我们来讨论第一种get_next_item().
可以通过TLM端口句柄seq_item_port调用该方法,例子如下:
class my_driver extends uvm_driver #(my_data);
`uvm_component_utils (my_driver)
virtual task run_phase(uvm_phase phase);
super.run_phase(phase);
// 1.调用get_next_item()获取下一个REQ
`uvm_info ("DRIVER", $sformatf ("Waiting for data from sequencer"), UVM_MEDIUM)
seq_item_port.get_next_item(req);
// 2. For simplicity, lets just assume the driver drives the received packet
// during this time and consumes 20ns to complete driving the transaction
//driver将REQ发送给DUT
`uvm_info ("DRIVER", $sformatf ("Start driving tx addr=0x%0h data=0x%0h", req.addr, req.data), UVM_MEDIUM)
#20;
// 3. After driver has finished the transaction, it has to let the sequencer know
// by calling item_done()
//调用item_done()告诉sequencer,driver已经发送完了这个REQ
`uvm_info ("DRIVER", $sformatf ("Finish driving tx addr=0x%0h data=0x%0h", req.addr, req.data), UVM_MEDIUM)
seq_item_port.item_done();
endtask
一旦driver获得了REQ,它就可以通过一个虚接口将REQ发送到DUT中,发送完毕后调用item_done() 告诉sequencer发送完成.
在driver的run_phase()中的主要操作包括:
-
通过seq_item_port.get_next_item(REQ)从sequencer获取有效的request item;
-
从request item中获取数据,进而产生数据激励;
-
对request item进行克隆生成新的对象response item;
-
修改response item中的数据成员,最终通过seq_item_port.item_done(RSP)将response item对象返回给sequence;
REQ也就是sequence item是通过sequence发送到sequencer的REQ FIFO中去的,sequence在sequencer上执行,而sequence item 在sequence中实例化并随机化.如下:
class my_sequence extends uvm_sequence(# my_data);
`uvm_object_utils (my_sequence)
virtual task body();
// 1. 产生一个sequence item 对象
my_data tx = my_data::type_id::create("tx");
`uvm_info ("SEQ", $sformatf("About to call start_item"), UVM_MEDIUM)
// 2. Call the start_item() task which will send this object to the driver
start_item(tx);
`uvm_info ("SEQ", $sformatf("start_item() fn call done"), UVM_MEDIUM)
// 3. Because the class handle passed to the driver points to the same object, we
// can do late randomization
tx.randomize();
`uvm_info ("SEQ", $sformatf("tx randomized with addr=0x%0h data=0x%0h", tx.addr, tx.data), UVM_MEDIUM)
// 4. Call finish_item method so that the sequence waits until the driver lets the
// sequencer know that this item has finished
finish_item(tx);
`uvm_info ("SEQ", $sformatf("finish_item() fn call done"), UVM_MEDIUM)
endtask
endclass
来看下面一个完整的过程:
(1)定义sequence item
// Note that this class is dervide from "uvm_sequence_item"
class my_data extends uvm_sequence_item;
rand bit [7:0] data;
rand bit [7:0] addr;
// Rest of the class contents come here ...
endclass
(2)定义sequence
class my_sequence extends uvm_sequence #(my_data);
// Rest of the sequence code
virtual task body();
// 1. Create a sequence item of the given type
my_data tx = my_data::type_id::create("tx");
`uvm_info ("SEQ", $sformatf("About to call start_item"), UVM_MEDIUM)
// 2. Start the item on the sequencer which will send this to the driver
start_item(tx);
`uvm_info ("SEQ", $sformatf("start_item() fn call done"), UVM_MEDIUM)
// 3. Do some late randomization to create a different content in this transaction object
tx.randomize();
`uvm_info ("SEQ", $sformatf("tx randomized with addr=0x%0h data=0x%0h", tx.addr, tx.data), UVM_MEDIUM)
// 4. Call finish_item to let driver continue driving the transaction object or sequence item
finish_item(tx);
`uvm_info ("SEQ", $sformatf("finish_item() fn call done"), UVM_MEDIUM)
endtask
endclass
没有指明sequence在哪个sequencer上执行,就使用默认的sequencer!
(3)定义driver
class my_driver extends uvm_driver #(my_data);
`uvm_component_utils (my_driver)
// Other parts of the driver code if they exist
virtual task run_phase(uvm_phase phase);
super.run_phase(phase);
// 1. Get the next available item from the sequencer. If none exists, then wait until
// next item is available -> this is blocking in nature
`uvm_info ("DRIVER", $sformatf ("Waiting for data from sequencer"), UVM_MEDIUM)
seq_item_port.get_next_item(req);
// 2. Let us assume that the driver actively does the pin wiggling of the DUT during this time and
// consider it takes 20ns
`uvm_info ("DRIVER", $sformatf ("Start driving tx addr=0x%0h data=0x%0h", req.addr, req.data), UVM_MEDIUM)
#20;
// 3. After the driver has driven all data to the DUT, it should let the sequencer know that it finished
// driving the transaction by calling "item_done". Optionally the response packet can be passed along with
// the item_done method call and it will be placed in the sequencer's response FIFO
`uvm_info ("DRIVER", $sformatf ("Finish driving tx addr=0x%0h data=0x%0h", req.addr, req.data), UVM_MEDIUM)
seq_item_port.item_done();
endtask
endclass
(4)定义test
class base_test extends uvm_test;
// Rest of the test code is here
// The sequencer is parameterized to accept objects of type "my_data" only
my_driver m_drv0;
uvm_sequencer #(my_data) m_seqr0;//使用默认的sequencer,即uvm_sequencer
my_sequence m_seq;
// Build the sequencer and driver components
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
m_drv0 = my_driver::type_id::create ("m_drv0", this);//创建sequencer和driver对象
m_seqr0 = uvm_sequencer#(my_data)::type_id::create ("m_seqr0", this);
endfunction
// Connect the sequencer "export" to the driver's "port"
virtual function void connect_phase (uvm_phase phase);
super.connect_phase (phase);
m_drv0.seq_item_port.connect (m_seqr0.seq_item_export);//连接sequencer和driver的TLM端口
endfunction
// Start the sequence on the given sequencer
virtual task run_phase(uvm_phase phase);
m_seq = my_sequence::type_id::create("m_seq");//创建sequence对象
phase.raise_objection(this);
m_seq.start(m_seqr0);//启动sequence
phase.drop_objection(this);
endtask
endclass
将上面的步骤整合如下:
`timescale 1ns/1ns
`include "uvm_macros.svh"
import uvm_pkg::*;
// This is the main sequence item class that will be used to create
// transactions such that it is sent to the driver from the sequencer
class my_data extends uvm_sequence_item;
rand bit [7:0] data;
rand bit [7:0] addr;
`uvm_object_utils_begin (my_data)
`uvm_field_int (data, UVM_ALL_ON)
`uvm_field_int (addr, UVM_ALL_ON)
`uvm_object_utils_end
function new (string name="my_data");
super.new(name);
endfunction
endclass
// This driver class will call "get_next_item" and "item_done" methods to communicate
// with the sequencer. Note that this driver is parameterized with "my_data"
// and hence it can accept only sequence items of type "my_data" and anything
class my_driver extends uvm_driver #(my_data);
`uvm_component_utils (my_driver)
function new (string name, uvm_component parent);
super.new (name, parent);
endfunction
virtual task run_phase(uvm_phase phase);
super.run_phase(phase);
`uvm_info ("DRIVER", $sformatf ("Waiting for data from sequencer"), UVM_MEDIUM)
seq_item_port.get_next_item(req);
`uvm_info ("DRIVER", $sformatf ("Start driving tx addr=0x%0h data=0x%0h", req.addr, req.data), UVM_MEDIUM)
#20;
`uvm_info ("DRIVER", $sformatf ("Finish driving tx addr=0x%0h data=0x%0h", req.addr, req.data), UVM_MEDIUM)
seq_item_port.item_done();
endtask
endclass
// This is the main sequence that will be executed by the sequencer in
// this environment. The sequence constructs sequence items and the sequencer
// FIFO is filled with these sequence items. When the driver asks for the next
// sequence item, the sequencer will pop the item and provided to the driver
class my_sequence extends uvm_sequence;
`uvm_object_utils (my_sequence)
function new(string name = "my_sequence");
super.new(name);
endfunction
virtual task body();
my_data tx = my_data::type_id::create("tx");
`uvm_info ("SEQ", $sformatf("About to call start_item"), UVM_MEDIUM)
start_item(tx);
`uvm_info ("SEQ", $sformatf("start_item() fn call done"), UVM_MEDIUM)
tx.randomize();
`uvm_info ("SEQ", $sformatf("tx randomized with addr=0x%0h data=0x%0h", tx.addr, tx.data), UVM_MEDIUM)
finish_item(tx);
`uvm_info ("SEQ", $sformatf("finish_item() fn call done"), UVM_MEDIUM)
endtask
endclass
// This is the base test class in which we will start the main sequence
// on the given sequencer
class base_test extends uvm_test;
`uvm_component_utils (base_test)
function new (string name, uvm_component parent = null);
super.new (name, parent);
endfunction : new
my_driver m_drv0;
uvm_sequencer #(my_data) m_seqr0;
my_sequence m_seq;
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
m_drv0 = my_driver::type_id::create ("m_drv0", this);
m_seqr0 = uvm_sequencer#(my_data)::type_id::create ("m_seqr0", this);
endfunction
// Connect the sequencer "export" to the driver's "port"
virtual function void connect_phase (uvm_phase phase);
super.connect_phase(phase);
m_drv0.seq_item_port.connect (m_seqr0.seq_item_export);
endfunction
virtual task run_phase(uvm_phase phase);
m_seq = my_sequence::type_id::create("m_seq");
phase.raise_objection(this);
m_seq.start(m_seqr0);
phase.drop_objection(this);
endtask
endclass
module tb_top;
initial run_test ("base_test");
endmodule
结果如下:
Using get() and put()
前面我们介绍了driver用get_next_item()方法来获取下一个REQ,也可以采用get()方法获取下一个REQ,用put()返回一个RSP。
来看在driver中如何调用put():
class my_driver extends uvm_driver #(my_data);
`uvm_component_utils (my_driver)
virtual task run_phase(uvm_phase phase);
super.run_phase(phase);
// 1. Get an item from the sequencer using "get" method
seq_item_port.get(req);
// 2. For simplicity, lets assume the driver drives the item and consumes 20ns of simulation time
#20;
// 3. After the driver is done, assume it gets back a read data called 8'hAA from the DUT
// Assign the read data into the "request" sequence_item object
req.data = 8'hAA;//该变REQ,作为RSP返回
// 4. Call the "put" method to send the request item back to the sequencer
seq_item_port.put(req);
endtask
endclass
来看相应的sequence中是如何发送sequence item和停止发送的:
class my_sequence extends uvm_sequence #(my_data);
`uvm_object_utils (my_sequence)
// Create a sequence item object handle to store the sequence_item contents
my_data tx;
virtual task body();
// 1. Create the sequence item using standard factory calls
tx = my_data::type_id::create("tx");
// 2. Start this item on the current sequencer
start_item(tx);
// 3. Do late randomization since the class handle pointers are the same
tx.randomize();
// 4. Finish executing the item from the sequence perspective
// The driver could still be actively driving and waiting for response
finish_item(tx);//从sequence的视角看,当前sequence item发送完毕,但是driver可以能还是驱动
// 5. Because "finish_item" does not indicate that the driver has finished driving the item,
// the sequence has to wait until the driver explicitly tells the sequencer that the item is over
// So, wait unitl the sequence gets a response back from the sequencer.
get_response(tx);//获取driver返回的RSP,这里才是当前sequence item的最终完成
`uvm_info ("SEQ", $sformatf("get_response() fn call done rsp addr=0x%0h data=0x%0h, exit seq", tx.addr, tx.data), UVM_MEDIUM)
endtask
endclass
来看下面一个完整的过程:
(1)定义sequence item
// Note that this class is dervide from "uvm_sequence_item"
class my_data extends uvm_sequence_item;
rand bit [7:0] data;
rand bit [7:0] addr;
// Rest of the class contents come here ...
endclass
(2)定义sequence
class my_sequence extends uvm_sequence #(my_data);
// Rest of the sequence code
virtual task body();
// 1. Create a sequence item of the given type. Note that this sequence has an internal
// variable called "req" which can be directly used as well instead of "tx"
my_data tx = my_data::type_id::create("tx");
`uvm_info ("SEQ", $sformatf("About to call start_item"), UVM_MEDIUM)
// 2. Start the item on the sequencer which will send this to the driver
start_item(tx);
`uvm_info ("SEQ", $sformatf("start_item() fn call done"), UVM_MEDIUM)
// 3. Do some late randomization to create a different content in this transaction object
tx.randomize();
`uvm_info ("SEQ", $sformatf("tx randomized with addr=0x%0h data=0x%0h", tx.addr, tx.data), UVM_MEDIUM)
// 4. Call finish_item to let driver continue driving the transaction object or sequence item
finish_item(tx);
`uvm_info ("SEQ", $sformatf("finish_item() fn call done, wait for rsp"), UVM_MEDIUM)
// 5. Wait for the driver to finish driving the object, and respond back with a response object
// The transaction is said to be complete at this point. This task is blocking in nature and hence
// the sequence will end only after the driver returns a response item.
get_response(tx);
`uvm_info ("SEQ", $sformatf("get_response() fn call done rsp addr=0x%0h data=0x%0h, exit seq", tx.addr, tx.data), UVM_MEDIUM)
endtask
endclass
(3)定义driver
class my_driver extends uvm_driver #(my_data);
`uvm_component_utils (my_driver)
virtual task run_phase(uvm_phase phase);
super.run_phase(phase);
// 1. Get sequence item from the sequencer using "get" method.
// "req" is a pre-defined variable that comes with the class "uvm_driver"
`uvm_info ("DRIVER", $sformatf ("Waiting for data from sequencer"), UVM_MEDIUM)
seq_item_port.get(req);
// 2. Assume that the driver drives this data during this time. For our purpose let's consider that the driver
// consumes 20ns of simulation time
`uvm_info ("DRIVER", $sformatf ("Start driving tx addr=0x%0h data=0x%0h", req.addr, req.data), UVM_MEDIUM)
#20;
// 3. Lets also assume that the DUT returned some read data back to the driver, which needs to be sent back
// to the sequence. Note that read data is put into the same "request" class object handle
`uvm_info ("DRIVER", $sformatf ("#20 delay over, curr data=0x%0h", req.data), UVM_MEDIUM)
req.data = 8'hAA;
// 4. The driver calls the "put" method and sends back the response data to the sequencer
`uvm_info ("DRIVER", $sformatf ("About to call put() with new data=0x%0h", req.data), UVM_MEDIUM)
seq_item_port.put(req);
`uvm_info ("DRIVER", $sformatf ("Finish driving tx addr=0x%0h data=0x%0h", req.addr, req.data), UVM_MEDIUM)
endtask
endclass
(4)定义test
class base_test extends uvm_test;
// Rest of the test code is here
// The sequencer is parameterized to accept objects of type "my_data" only
my_driver m_drv0;
uvm_sequencer #(my_data) m_seqr0;//sequence在默认的sequencer上执行
my_sequence m_seq;
// Build the sequencer and driver components
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
m_drv0 = my_driver::type_id::create ("m_drv0", this);
m_seqr0 = uvm_sequencer#(my_data)::type_id::create ("m_seqr0", this);
endfunction
// Connect the sequencer "export" to the driver's "port"
virtual function void connect_phase (uvm_phase phase);
super.connect_phase (phase);
m_drv0.seq_item_port.connect (m_seqr0.seq_item_export);
endfunction
// Start the sequence on the given sequencer
virtual task run_phase(uvm_phase phase);
m_seq = my_sequence::type_id::create("m_seq");//创建sequence对象
phase.raise_objection(this);
m_seq.start(m_seqr0);//启动sequence
phase.drop_objection(this);
endtask
endclass
将上面的步骤整合如下:
`timescale 1ns/1ns
`include "uvm_macros.svh"
import uvm_pkg::*;
// This is the main sequence item class that will be used to create
// transactions such that it is sent to the driver from the sequencer
class my_data extends uvm_sequence_item;
rand bit [7:0] data;
rand bit [7:0] addr;
`uvm_object_utils_begin (my_data)
`uvm_field_int (data, UVM_ALL_ON)
`uvm_field_int (addr, UVM_ALL_ON)
`uvm_object_utils_end
function new (string name="my_data");
super.new(name);
endfunction
endclass
// This driver class will call "get" and "put" methods to communicate
// with the sequencer. Note that this driver is parameterized with "my_data"
// and hence it can accept only sequence items of type "my_data" and anything
// else will give a compilation error.
class my_driver extends uvm_driver #(my_data);
`uvm_component_utils (my_driver)
function new (string name, uvm_component parent);
super.new (name, parent);
endfunction
virtual task run_phase(uvm_phase phase);
super.run_phase(phase);
`uvm_info ("DRIVER", $sformatf ("Waiting for data from sequencer"), UVM_MEDIUM)
seq_item_port.get(req);
`uvm_info ("DRIVER", $sformatf ("Start driving tx addr=0x%0h data=0x%0h", req.addr, req.data), UVM_MEDIUM)
#20;
`uvm_info ("DRIVER", $sformatf ("#20 delay over, curr data=0x%0h", req.data), UVM_MEDIUM)
req.data = 8'hAA;
`uvm_info ("DRIVER", $sformatf ("About to call put() with new data=0x%0h", req.data), UVM_MEDIUM)
seq_item_port.put(req);
`uvm_info ("DRIVER", $sformatf ("Finish driving tx addr=0x%0h data=0x%0h", req.addr, req.data), UVM_MEDIUM)
endtask
endclass
// This is the main sequence that will be executed by the sequencer in
// this environment. The sequence constructs sequence items and the sequencer
// FIFO is filled with these sequence items. When the driver asks for the next
// sequence item, the sequencer will pop the item and provided to the driver
class my_sequence extends uvm_sequence #(my_data);
`uvm_object_utils (my_sequence)
function new(string name = "my_sequence");
super.new(name);
endfunction
my_data tx;
virtual task body();
tx = my_data::type_id::create("tx");
`uvm_info ("SEQ", $sformatf("About to call start_item"), UVM_MEDIUM)
start_item(tx);
`uvm_info ("SEQ", $sformatf("start_item() fn call done"), UVM_MEDIUM)
tx.randomize();
`uvm_info ("SEQ", $sformatf("tx randomized with addr=0x%0h data=0x%0h", tx.addr, tx.data), UVM_MEDIUM)
finish_item(tx);
`uvm_info ("SEQ", $sformatf("finish_item() fn call done, wait for rsp"), UVM_MEDIUM)
get_response(tx);
`uvm_info ("SEQ", $sformatf("get_response() fn call done rsp addr=0x%0h data=0x%0h, exit seq", tx.addr, tx.data), UVM_MEDIUM)
endtask
endclass
// This is the base test class in which we will start the main sequence
// on the given sequencer
class base_test extends uvm_test;
`uvm_component_utils (base_test)
function new (string name, uvm_component parent = null);
super.new (name, parent);
endfunction : new
my_driver m_drv0;
uvm_sequencer #(my_data) m_seqr0;
my_sequence m_seq;
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
m_drv0 = my_driver::type_id::create ("m_drv0", this);
m_seqr0 = uvm_sequencer#(my_data)::type_id::create ("m_seqr0", this);
endfunction
// Connect the sequencer "export" to the driver's "port"
virtual function void connect_phase (uvm_phase phase);
super.connect_phase (phase);
m_drv0.seq_item_port.connect (m_seqr0.seq_item_export);
endfunction
virtual task run_phase(uvm_phase phase);
m_seq = my_sequence::type_id::create("m_seq");
phase.raise_objection(this);
m_seq.start(m_seqr0);
phase.drop_objection(this);
endtask
endclass
module tb_top;
initial run_test ("base_test");
endmodule
结果:
总结 :
- 对于uvm_sequence::get_response(RSP)和uvm_driver::item_done(RSP)这种成对的操作,是可选的而不是必须的,即用户可以选择uvm_driver不返回response item,同时sequence也无需获取response item;
-
在多个sequence同时向sequencer发送item时,就需要有ID信息表明该item从哪个sequence来,这个ID信息在sequence创建item时就赋值了,而在到达driver以后,这个ID也能用来跟踪它的sequence信息,这就跟食品加工从源头就标记二维码一样,使得运输和使用更加安全。这个ID信息在稍后driver假如要返回response item时,需要给定正确的信息,可以在driver中通过函数get_sequence_id()来获取标记,这也就使得sequencer可以根据ID信息来分发这些response item返回至正确的sequence源头;
-
我们建议用户在driver中,通过clone()的方式单独创建response item,保证request item和response item两个对象的独立性。也许有的用户为了“简便”,而在使用了request item之后,就直接修改了它的数据作为要返回给sequence的response item。这么做看来,似乎节能环保,但实际上殊不知可能埋下隐患,一方面它延长了本来应该丢进垃圾桶的request item寿命,同时也无法再对request item原始生成数据做出有效记录。所以,谈到这里,请读者们记住一点,clone()和copy()是个好习惯,虽然多了那么几行代码,但你的代码稳健性无形中却提高了,这种代码方式可以帮助减少一些可能的隐患;
-
为了统一起见,用户可以不在定义sequence或者driver时指定sequence item类型,使用默认类型REQ = uvm_sequence_item,但是用户需要注意在driver一侧的类型转换,例如get_next_item(REQ)的输出REQ句柄应当做出二次转换,得到正确类型之后再进行接下来的操作;