TLM1 Method

Here’s a typical TLM application using uvm_blocking_put_port and um_blocking_put_imp to connect a producer and a consumer.


class producer extends uvm_component; 
uvm_blocking_put_port#(instruction) put_port; 

function new(string name, uvm_component p = null); 
super.new(name,p); 
put_port = new("put_port", this); 
endfunction 

task run; 
for(int i = 0; i < 10; i++) 
begin 
instruction ints; 
#10; 
ints = new(); 
if(ints.randomize()) begin 
`uvm_info("producer", $sformatf("sending %s",ints.inst.name()), UVM_MEDIUM) 
put_port.put(ints); 
end 
end 
endtask 
endclass : producer 

class consumer extends uvm_component; 
uvm_blocking_put_imp#(instruction,consumer) put_export; 

function new(string name, uvm_component p = null); 
super.new(name,p); 
put_export = new("put_export", this); 
endfunction 

task put(instruction t); 
`uvm_info("consumer", $sformatf("receiving %s",t.inst.name()), UVM_MEDIUM) 
//push the transaction into queue or array 
//or drive the transaction to next level 
//or drive to interface 
endtask 
endclass : consumer 

class env extends uvm_env; 
producer p; 
consumer c; 

function new(string name = "env"); 
super.new(name); 
p = new("producer", this); 
c = new("consumer", this); 
endfunction 

function void connect(); 
p.put_port.connect(c.put_export);
endfunction 

One thing that bothers me in this application is that: Why would the put() function which belongs to class consumer get executed when we are calling the put() function which belongs to class put_port– “put_port.put(ints)” inside class producer. Is there some UVM magic happening at the connect step?

The answer can be found by looking at the class uvm_blocking_put_imp. It’s a little tricky because this OVM/UVM code was written before interface classes were added to SystemVerilog, so there are many layers of wrappers to make this work.

Basically, uvm_blocking_put_imp is parameterized by the consumer class, and is initialized with an instance of the consumer class (the **imp****lementation). It has a wrapping put method that calls the consumer::put().

The class uvm_blocking_put_port is also a wrapper. When you make the connection, you are copying the uvm_blocking_put_imp handle (stored in c.put_export) and placing it inside the uvm_blocking_put_port (p.put_port). uvm_blocking_put_port has a put method that calls eventually calls the uvm_blocking_put_imp::put(). So when you call put_port.put(), there are actually 4 wrapping layers of put() method calls to get to consumer::put().

In reply to dave_59:

Thanks, Dave!
This makes sense. The consumer-parameterized uvm_*_put_imp along with the wrapping put method can let the user customize the put() function in user-defined consumer class.