UVM practice volume I learning notes 7 - TLM communication in UVM

Communication mode in UVM

*Use FIFO communication

When implementing the communication between monitor and scoreboard in the previous section, first declare two suffixes, and then write the corresponding functions. This method seems troublesome and difficult to understand. Is there a simple method? In addition, the monitor plays an active role in the communication between the two, while the scoreboard can only receive passively. Is there a way to enable the scoreboard to receive actively? The answer is yes, that is to use FIFO to realize the communication between monitor and scoreboard.

As shown in Figure b, add an UVM between agent and scoreboard_ analysis_ fifo. The essence of FIFO is one cache plus two imps. In the connection relationship between monitor and FIFO, analysis is still in monitor_ Port, UVM in FIFO_ analysis_ IMP, the direction of data flow and control flow is the same. In the connection relationship between scoreboard and FIFO, blocking is used in scoreboard_ get_ Port:

class my_scoreboard extends uvm_scoreboard;
	my_transaction expect_queue[$];
	uvm_blocking_get_port #(my_transaction) exp_port;
	uvm_blocking_get_port #(my_transaction) act_port;
	...
endclass
...
task my_scoreboard::main_phase(uvm_phase phase);
...
	fork
		while (1) begin
			exp_port.get(get_expect);
			expect_queue.push_back(get_expect);
		end
		while (1) begin
			act_port.get(get_actual);
			...
		end
	join
endtask

The FIFO uses the IMP of a get port. In this connection relationship, the control flow is from scoreboard to FIFO, and the data flow is from FIFO to scoreboard.

Connect in env as follows:

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;
	...
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

As shown in Figure b, there are two imps in FIFO, but in the above connection relationship, FIFO is export. Why? In fact, analysis in FIFO_ Export and blocking_get_export although there is export in the name, its type is imp. In order to cover up the existence of IMP, UVM adds export to their name. Such as analysis_ The prototype of export is as follows: uvm_analysis_imp #(T, uvm_tlm_analysis_fifo #(T)) analysis_export;

After using FIFO connection, the first advantage is that there is no need to write another function called write in scoreboard. Scoreboard can work at its own pace without following the rhythm of monitor. The second advantage is that the existence of FIFO hides IMP. The third advantage is that it can easily solve the problem of how to handle when reference model and monitor are connected to scoreboard at the same time. In fact, the existence of FIFO naturally solves it, which is not a problem at all.

Port and debugging on FIFO

The previous section introduced uvm_tlm_analysis_fifo and introduces its two ports: blocking_get_export and analysis_export. In fact, the ports on the FIFO are not limited to the above two. There are many ports in a FIFO, as shown in the figure.

All exports represented by circles in the figure above are IMP in essence. It contains 12 imps, which are used to connect with the corresponding PORT and EXPORT respectively. put and get series ports have been introduced earlier, and peek series ports are briefly described here: PEEK PORT is similar to get, and its data flow and control flow are similar. The only difference is that when get task is called, there will be one transaction less in FIFO internal cache, while when peek is called, FIFO will copy a transaction and send it, The number of transactions in its internal cache will not decrease.

In addition to the 12 imps, there are also put in the figure_ AP and get_ap. When blocking on FIFO_ put_export or put_export is connected to a blocking_put_port or put_port, the put task defined in the FIFO is called, and the transaction passed is placed in the cache in the FIFO. At the same time, the transaction is passed through put_ The AP is sent using the write function. The put task of FIFO is defined as follows:

virtual task put( input T t );
	m.put( t ); //m is the internal cache of FIFO, which is implemented by mailbox in SV
	put_ap.write( t );
endtask

And put_ Similar to AP, when the get task of FIFO is called, there will also be a transaction from get_ Issued on AP:

virtual task get(output T t);
	m_pending_blocked_gets++;
	m.get(t);
	m_pending_blocked_gets--;
	get_ap.write(t);
endtask

A blocking_get_port is connected to FIFO. When it calls get task to obtain transaction, it will call get task of FIFO. In addition, FIFO get_export,get_peek_export and blocking_ get_ peek_ When EXPORT is connected by the corresponding PORT or EXPORT, the get task of FIFO can also be called.

There are two types of FIFO: uvm_tlm_analysis_fifo and uvm_tlm_fifo. The difference between the two is that the former has an analysis_ The export port does not have a write function, which does not.

UVM provides functions for FIFO debugging: the used function is used to query the number of transactions in the FIFO cache; flush function is used to clear all data in FIFO cache. It is generally used for reset and other operations. is_ The empty function is used to judge whether the current FIFO cache is empty; is_ The full function is used to determine whether the current FIFO cache is full. As a cache, the transactions it can store are limited. So where is this maximum defined? The new function prototype of FIFO is as follows: function new(string name, uvm_component parent = null, int size = 1);

FIFO is essentially a component, so the first two parameters are UVM_ Two parameters in the new function of component. The third parameter is size, which is used to set the upper limit of FIFO cache. By default, it is 1. To set the cache to infinite size, set the passed in size parameter to 0. You can return this upper limit value through the size function.

FIFO or IMP

There are different answers to this question. In the method of FIFO communication, IMP, which is unique in UVM and not in TLM, is completely hidden. Users can be completely indifferent to IMP. For users, they only need to know analysis_port,blocking_get_port. This simplifies the workload of beginners, especially when scoreboard faces multiple imps and needs to declare a suffix for IMP.

FIFO connection increases the complexity of the code in env, which seems to be full of FIFO related code. Especially when the number of ports to be connected is large, this disadvantage is more obvious.

However, for the case of using port array, FIFO is better than IMP. If there are 16 similar ports in the reference model to communicate with the corresponding ports in scoreboard, so many ports can be realized by using port array in the reference model:

class my_model extends uvm_component;
	uvm_blocking_get_port #(my_transaction) port;
	uvm_analysis_port #(my_transaction) ap[16];
	...
endclass
...
function void my_model::build_phase(uvm_phase phase);
	super.build_phase(phase);
	port = new("port", this);
	for(int i = 0; i <16; i++)
		ap[i] = new($sformatf("ap_%0d", i), this);
endfunction

If the connection relationship uses the method of IMP plus suffix, the code in scoreboard is as follows: (higher and lower judgment)

`uvm_analysis_imp_decl(_model0)
...
`uvm_analysis_imp_decl(_modelf)
`uvm_analysis_imp_decl(_monitor)
class my_scoreboard extends uvm_scoreboard;
	my_transaction expect_queue[$];
	uvm_analysis_imp_monitor#(my_transaction, my_scoreboard) monitor_imp;
	uvm_analysis_imp_model0#(my_transaction, my_scoreboard) model0_imp;
	...
	uvm_analysis_imp_modelf#(my_transaction, my_scoreboard) modelf_imp;
	`uvm_component_utils(my_scoreboard)
	extern function new(string name, uvm_component parent = null);
	extern virtual function void build_phase(uvm_phase phase);
	extern virtual task main_phase(uvm_phase phase);
	extern function void write_monitor(my_transaction tr);
	extern function void write_model0(my_transaction tr);
	...
	extern function void write_modelf(my_transaction tr);
endclass
...
function void my_scoreboard::build_phase(uvm_phase phase);
	super.build_phase(phase);
	monitor_imp = new("monitor_imp", this);
	model0_imp = new("model0_imp", this);
	...
	modelf_imp = new("modelf_imp", this);
endfunction
function void my_scoreboard::write_model0(my_transaction tr);
	expect_queue.push_back(tr);
endfunction
...
function void my_scoreboard::write_modelf(my_transaction tr);
	expect_queue.push_back(tr);
endfunction
function void my_scoreboard::write_monitor(my_transaction tr);
	...
endfunction

And in env, you need to:

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);
	o_agt.ap.connect(scb.monitor_imp);
	mdl.ap[0].connect(scb.model0_imp);
	...
	mdl.ap[15].connect(scb.modelf_imp);
endfunction

Many ellipsis are used in the code listed above, but even so, you can feel the severity of the code. All this is because ap is directly connected to imp and cannot use the for loop.

If FIFO connection is used, you can:

class my_scoreboard extends uvm_scoreboard;
	my_transaction expect_queue[$];
	uvm_blocking_get_port #(my_transaction) exp_port[16];
	uvm_blocking_get_port #(my_transaction) act_port;
	...
endclass
...
function void my_scoreboard::build_phase(uvm_phase phase);
	super.build_phase(phase);
	for(int i = 0; i < 16; i++)
		exp_port[i] = new($sformatf("exp_port_%0d", i), this);
		act_port = new("act_port", this);
endfunction
task my_scoreboard::main_phase(uvm_phase phase);
	...
	for(int i = 0; i < 16; i++)
	fork
		automatic int k = i;
	while (1) begin
		exp_port[k].get(get_expect);
		expect_queue.push_back(get_expect);
	end
	join_none
	while (1) begin
	act_port.get(get_actual);
	...
	end
endtask

You can also use the for loop in env:

class my_env extends uvm_env;
	...
	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[16];
	...
	virtual function void build_phase(uvm_phase phase);
	...
	agt_scb_fifo = new("agt_scb_fifo", this);
	agt_mdl_fifo = new("agt_mdl_fifo", this);
	for(int i = 0; i < 16; i++)
		mdl_scb_fifo[i] = new($sformatf("mdl_scb_fifo_%0d", i), this);
	endfunction
	...
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);
	for(int i = 0; i < 16; i++) begin
		mdl.ap[i].connect(mdl_scb_fifo[i].analysis_export);
		scb.exp_port[i].connect(mdl_scb_fifo[i].blocking_get_export);
	end
	o_agt.ap.connect(agt_scb_fifo.analysis_export);
	scb.act_port.connect(agt_scb_fifo.blocking_get_export);
endfunction

Both FIFO and IMP can achieve the same goal, and they have their own advantages and disadvantages. In practical application, you can choose the appropriate connection mode according to your habits.

Posted on Fri, 15 Oct 2021 00:47:14 -0400 by mevasquez