In verification using UVM, most of the
people create sequences from sequence item and start the same sequence from the
testcase. In this blog, we will see a different method to drive transactions
without creating any sequence. UVM provides “uvm_random_stimulus” class which
generates random transactions and put it on to “blocking_put_port”. So by using
this port, we can get the random transaction and drive them on to the
interface. “uvm_random_stimulus” class contains “generate_stimulus” method
whose arguments are the transaction class and number of transaction to be
generated. User can override this method to have his own implementation. It
also contains “stop_stimulus_generation” function which stops generation of
stimulus.
As
we know, the integration of any VIP in
the testbench is not completed until a sequence is driven from the
driver and
monitor samples interface properly. When we integrate any VIP, it may
consume
time in creating sequences and testcase which could be done later. Here
if any error is generated, then user has to find whether there is any
problem with component
integration or it is related to the sequence or testcase. As this random
stimulus generator method doesn’t include any sequences, one can easily
stabilize the integration of VIP in the testbench. Error can be
generated only because
of integration problem (Note that, simulation time error will be printed
for
erroneous transactions. So constraints should be proper.). So, this
method of
transaction generation is very useful for the AE(application engineer)
and also
for a new user of the VIP.
There can be various ways of integrating
this random stimulus generator:
One way is, you can create an extra port
(put implementation) in the driver and connect it with the stimulus generator
put port (“blocking_put_port”). In run phase, get the transaction either from
put implementation or seq_item_port based on configuration. In that case,
driver contains two ports for getting transactions and it may break the
existing functionality.
Another way is, you can override the
“get_next_item” method in the sequencer. So without updating the driver code,
you can easily integrate the stimulus generator in the testbench. You just need
to configure the agent whether to get transaction from sequences or from
stimulus generator. Below is the demo example which demonstrates usage of
“uvm_random_stimulus” component:
Random Stimulus Generator |
// Enum to select transaction generation from sequence or random stimulus generator.
typedef enum
{
RANDOM_GENERATOR,
SEQUENCE_GENERATOR
} txn_generator;
//********************
// Transaction class
class a_item extends uvm_sequence_item;
...
endclass : a_item
//********************
// Driver class
class a_driver extends uvm_driver#(a_item);
...
task run_phase(uvm_phase phase);
super.run_phase(phase);
forever begin
seq_item_port.get_next_item(req);
...
seq_item_port.item_done();
end
endtask : run_phase
endclass : a_driver
//********************
// Sequencer class
`uvm_blocking_put_imp_decl(_rand)
class a_seqr extends uvm_sequencer#(a_item);
txn_generator gen=SEQUENCE_GENERATOR;
uvm_blocking_put_imp_my #(a_item, a_seqr) put_imp;
...
function void build_phase(uvm_phase phase);
super.build_phase(phase);
void'(uvm_config_db#(txn_generator)::get(this,"","gen",gen));
// Creating implementation port when RANDOM_GENERATOR is selected
if(gen == RANDOM_GENERATOR)
put_imp = new("put_imp", this);
endfunction : build_phase
// Put implementation
virtual task put_rand(a_item seq);
if(seq == null) $display("ERROR: TXN NULL");
else m_req_fifo.put(seq);
endtask
task get_next_item(output a_item t);
a_item req_item;
if (get_next_item_called == 1)
uvm_report_error(get_full_name(),
"Get_next_item called twice without item_done or get in between", UVM_NONE);
// Calling m_select_sequence method only when transaction from sequences
// are required. This is the only modification.
// Other code remains same as base implementation.
if ((!sequence_item_requested) && (gen == SEQUENCE_GENERATOR))
m_select_sequence();
// Set flag indicating that the item has been requested to ensure that item_done
// or get is called between requests
sequence_item_requested = 1;
get_next_item_called = 1;
m_req_fifo.peek(t);
endtask
endclass : a_seqr
//********************
// Agent class
class a_agent extends uvm_agent;
a_driver drv;
a_seqr seqr;
a_item item;
txn_generator gen=SEQUENCE_GENERATOR;
uvm_random_stimulus #(a_item) rand_txn_generator;
int num_rand_txn=20;
...
function void build_phase(uvm_phase phase);
...
// Getting transaction generator
void'(uvm_config_db#(txn_generator)::get(this,"","gen",gen));
// Setting transaction generator to sequencer
uvm_config_db#(txn_generator)::set(this,"seqr","gen",gen);
// Getting how many transaction should be generated from random stimulus
// generator.
uvm_config_db#(int)::get(this,"","num_rand_txn",num_rand_txn);
`uvm_info("build_phase", $sformatf("Transaction generator is:%0s",gen.name()), UVM_LOW)
drv = a_driver::type_id::create("drv", this);
seqr = a_seqr::type_id::create("seqr", this);
// Creating random stimulus generator
if(gen == RANDOM_GENERATOR)
begin
rand_txn_generator = uvm_random_stimulus#(a_item)::type_id::create ("rand_txn_generator", this);
item = a_item::type_id::create("item");
end
endfunction : build_phase
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
drv.seq_item_port.connect(seqr.seq_item_export);
if(gen == RANDOM_GENERATOR)
rand_txn_generator.blocking_put_port.connect(seqr.put_imp);
endfunction : connect_phase
task run_phase(uvm_phase phase);
super.run_phase(phase);
if(gen == RANDOM_GENERATOR)
begin
phase.raise_objection(this);
rand_txn_generator.generate_stimulus(item, num_rand_txn);
phase.drop_objection(this);
end
endtask : run_phase
endclass : a_agent
//********************
// Environment class
class m_env extends uvm_env;
a_agent a_ag;
...
function void build_phase(uvm_phase phase);
super.build_phase(phase);
a_ag = a_agent::type_id::create("a_ag", this);
...
endfunction : build_phase
...
endclass :m_env
//********************
// Testcase
class test extends uvm_test;
m_env env;
...
function void build_phase(uvm_phase phase);
super.build_phase(phase);
uvm_config_db#(txn_generator)::set(this,"env.a_ag","gen",RANDOM_GENERATOR);
uvm_config_db#(int)::set(this,"env.a_ag","num_rand_txn",50);
...
env = m_env::type_id::create("env", this);
endfunction : build_phase
endclass : test
As shown in the example, txn_generator is an enum which is used to select transaction generation from random stimulus generator or from sequences. Here, a_item is a sequence item class and a_driver is a driver class. Sequence item class and driver class implementation doesn't require any extra modification.
a_seqr is a sequencer class which contains "put_imp" implementation port. When txn_generator is set to RANDOM_GENERATOR, transaction will be put into m_req_fifo from random stimulus generator otherwise it will be put from sequences. Note that, get_next_item method is overridden in the sequencer class. "m_select_sequence" method is called only when txn_generator is set to SEQUENCE_GENERATOR.
In a_agent class, rand_txn_generator is created which is a random stimulus generator component and it's "blocking_put_port" is connected with "put_imp" of sequencer class. In run phase, "generate_stimulus" method is called which generates "num_rand_txn" number of random transactions.
In the testcase, we are just setting "txn_generator" to "RANDOM_GENERATOR" and "num_rand_txn" (number of random transaction to be generated) to 50. So, 50 random transaction will be generated.
This
way, without using any sequence user can generate random transactions
and drive them on to interface. So, VIP integration task will become
much simple.
Note: You may get fatal ("Driver put a response with null sequence_id") if driver is calling "put_response" task for putting response. To avoid it, you can compile with"+define+CDNS_NO_SQR_CHK_SEQ_ID".
Note: You may get fatal ("Driver put a response with null sequence_id") if driver is calling "put_response" task for putting response. To avoid it, you can compile with"+define+CDNS_NO_SQR_CHK_SEQ_ID".