A UVM TLM blocking get port lets a component request a transaction from another component by calling get(), and makes the requester wait until one is available. The requester owns a uvm_blocking_get_port, the provider owns a uvm_blocking_get_imp and implements a get() task, and a parent connects them. It is the mirror image of a put connection: the port is still on the side that makes the call, but the data travels back towards it.
Get connections suit pull-style designs, where the consumer decides when it is ready for the next item. The driver-to-sequencer connection at the heart of every UVM agent is a pull connection of this kind.
7 min read | Intermediate Level
What You'll Learn
- Declare a blocking get port in the component that requests data
- Implement the
get()task in the component that provides data - Connect the port to the imp and trace the direction of control and data
- Explain how a slow provider stalls the requester
Syntax
uvm_blocking_get_port #(T) port_name; // in the requester
uvm_blocking_get_imp #(T, IMP) imp_name; // in the provider; IMP is the provider's class
virtual task get(output T t); // the provider must define this task
- The argument of
get()is anoutput. The provider assigns a transaction handle to it, and the requester receives that handle when the task returns. - Each
get()consumes one transaction. To look at the next transaction without consuming it, the interface to use ispeek(), through a peek port and imp. - As with every port and imp, both must be created with
new(name, parent)inbuild_phase.
The Transaction
class Packet extends uvm_object;
rand bit[7:0] addr;
rand bit[7:0] data;
`uvm_object_utils_begin(Packet)
`uvm_field_int(addr, UVM_ALL_ON)
`uvm_field_int(data, UVM_ALL_ON)
`uvm_object_utils_end
function new(string name = "Packet");
super.new(name);
endfunction
endclass
Step 1: The Provider
componentA provides packets. It owns the imp and implements get(), which builds and randomizes a new packet every time it is called.
class componentA extends uvm_component;
`uvm_component_utils(componentA)
// Implementation port: routes get() calls to this component
uvm_blocking_get_imp #(Packet, componentA) m_get_imp;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
m_get_imp = new("m_get_imp", this);
endfunction
// Called whenever componentB asks for a packet
virtual task get(output Packet pkt);
pkt = new(); // default name "Packet", as seen in the log
assert(pkt.randomize());
`uvm_info("COMPA", "ComponentB has requested for a packet, give the following packet to componentB", UVM_LOW)
pkt.print(uvm_default_line_printer);
endtask
endclass
componentA never decides when to send. It only reacts when componentB calls get(). Using Packet::type_id::create() instead of new() would let a test override the packet type through the factory; new() is used here only to keep the default object name.
Step 2: The Requester
componentB owns the get port and pulls a fixed number of packets in its run_phase.
class componentB extends uvm_component;
`uvm_component_utils(componentB)
uvm_blocking_get_port #(Packet) m_get_port; // requests Packet objects
int m_num_tx = 2;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
m_get_port = new("m_get_port", this);
endfunction
virtual task run_phase(uvm_phase phase);
Packet pkt;
phase.raise_objection(this);
repeat (m_num_tx) begin
m_get_port.get(pkt); // blocks until componentA returns a packet
`uvm_info("COMPB", "ComponentA just gave me the packet", UVM_LOW)
pkt.print(uvm_default_line_printer);
end
phase.drop_objection(this);
endtask
endclass
Step 3: Connect in the Parent
The test connects the requester's port to the provider's imp. The call is made on the port, because the port is on the initiating side even though data flows the other way.
class my_test extends uvm_test;
`uvm_component_utils(my_test)
componentA compA;
componentB compB;
function new(string name = "my_test", uvm_component parent = null);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
compA = componentA::type_id::create("compA", this);
compB = componentB::type_id::create("compB", this);
endfunction
virtual function void connect_phase(uvm_phase phase);
compB.m_get_port.connect(compA.m_get_imp); // port (requester) to imp (provider)
endfunction
virtual function void end_of_elaboration_phase(uvm_phase phase);
super.end_of_elaboration_phase(phase);
uvm_top.print_topology();
endfunction
endclass
UVM_INFO @ 0: reporter [RNTST] Running test my_test...
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_root.svh(579) @ 0: reporter [UVMTOP] UVM testbench topology:
--------------------------------------------------
Name Type Size Value
--------------------------------------------------
uvm_test_top my_test - @1836
compA componentA - @1905
m_get_imp uvm_blocking_get_imp - @1971
compB componentB - @1936
put_export uvm_blocking_get_port - @2010
--------------------------------------------------
UVM_INFO testbench.sv(86) @ 0: uvm_test_top.compA [COMPA] ComponentB has requested for a packet, give the following packet to componentB
Packet: (Packet@1877) { addr: 'hdd data: 'hce }
UVM_INFO testbench.sv(54) @ 0: uvm_test_top.compB [COMPB] ComponentA just gave me the packet
Packet: (Packet@1877) { addr: 'hdd data: 'hce }
UVM_INFO testbench.sv(86) @ 0: uvm_test_top.compA [COMPA] ComponentB has requested for a packet, give the following packet to componentB
Packet: (Packet@2058) { addr: 'h4b data: 'hf5 }
UVM_INFO testbench.sv(54) @ 0: uvm_test_top.compB [COMPB] ComponentA just gave me the packet
Packet: (Packet@2058) { addr: 'h4b data: 'hf5 }
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 0: reporter [UVM/REPORT/SERVER]
--- UVM Report Summary ---
Each request produces a message from componentA first, then componentB reports receiving the same object. In this run the requester's port was still named put_export, which is why the topology lists that name with the type uvm_blocking_get_port; it plays the role of m_get_port in the code above.
Quick Check
Q: In a get connection, which component calls connect() on its connector, and why?
Show Answer
Neither component does; their parent does, in connect_phase. It calls connect() on the requester's port and passes the provider's imp, because the port is the initiating side of the method call. The direction the data travels does not change this rule.
Seeing the Requester Block
Here the provider takes 20 ns to prepare each packet, so every get() call takes 20 ns to return.
virtual task get(output Packet pkt);
pkt = new();
assert(pkt.randomize());
// componentA takes some time to prepare the packet
`uvm_info("COMPA", "Preparing packet ...", UVM_LOW)
#20;
`uvm_info("COMPA", "Preparing packet over ...", UVM_LOW)
pkt.print(uvm_default_line_printer);
endtask
UVM_INFO @ 0: reporter [RNTST] Running test my_test...
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_root.svh(579) @ 0: reporter [UVMTOP] UVM testbench topology:
--------------------------------------------------
Name Type Size Value
--------------------------------------------------
uvm_test_top my_test - @1837
compA componentA - @1906
m_get_imp uvm_blocking_get_imp - @1972
compB componentB - @1937
m_get_port uvm_blocking_get_port - @2011
--------------------------------------------------
UVM_INFO testbench.sv(89) @ 0: uvm_test_top.compA [COMPA] Preparing packet ...
UVM_INFO testbench.sv(91) @ 20: uvm_test_top.compA [COMPA] Preparing packet over ...
Packet: (Packet@1904) { addr: 'he8 data: 'hc5 }
UVM_INFO testbench.sv(55) @ 20: uvm_test_top.compB [COMPB] ComponentA just gave me the packet
Packet: (Packet@1904) { addr: 'he8 data: 'hc5 }
UVM_INFO testbench.sv(89) @ 20: uvm_test_top.compA [COMPA] Preparing packet ...
UVM_INFO testbench.sv(91) @ 40: uvm_test_top.compA [COMPA] Preparing packet over ...
Packet: (Packet@2071) { addr: 'hd6 data: 'hd }
UVM_INFO testbench.sv(55) @ 40: uvm_test_top.compB [COMPB] ComponentA just gave me the packet
Packet: (Packet@2071) { addr: 'hd6 data: 'hd }
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_objection.svh(1271) @ 40: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 40: reporter [UVM/REPORT/SERVER]
--- UVM Report Summary ---
componentB is stuck inside get() while componentA prepares each packet. The first packet reaches componentB at 20 ns and the second at 40 ns, and only then can componentB drop its objection and let the run phase end.
Get vs Put: Who Controls the Pace?
In a put connection, the sender owns the port and pushes data whenever it likes, and the receiver can only slow it down. In a get connection, the receiver owns the port and pulls data when it is ready, and the provider can only delay the answer. Choose based on which side naturally knows when the next transaction should move. A driver, for example, knows exactly when the bus is free, so it pulls the next item from its sequencer rather than having items pushed at it.
Get vs Peek
Sometimes a requester needs to look at the next transaction before deciding whether to take it, for example to check its address or its type. That is what the peek interface is for. peek() returns the same next transaction every time it is called, until a get() finally consumes it. A provider that supports both implements both tasks behind a single get_peek imp.
// Provider side: one imp offers both get() and peek()
uvm_blocking_get_peek_imp #(Packet, componentA) m_get_peek_imp;
virtual task get (output Packet pkt); /* hand over and remove the next packet */ endtask
virtual task peek(output Packet pkt); /* hand over the next packet, keep it */ endtask
// Requester side
uvm_blocking_get_peek_port #(Packet) m_get_peek_port;
The requester's code then calls peek() to inspect and get() to consume, through the same port.
In a get connection the requester owns the loop, so it is also the natural place for the objection. The provider only runs when it is asked, and it never needs to keep the phase alive on its own.
Common Mistakes
Mistake #1: Declaring the get argument as input
Wrong: virtual task get(Packet pkt);
Right: virtual task get(output Packet pkt);
Why? Without output, the handle the provider assigns never reaches the requester, and the imp does not match the interface it is supposed to implement.
Mistake #2: Returning without assigning the output
If a code path in get() returns without assigning pkt, the requester receives a null handle and fails on its next access to the packet. Every path through the task must hand back a valid object.
Mistake #3: Connecting from the imp side
Writing compA.m_get_imp.connect(compB.m_get_port) follows the data direction instead of the call direction. An imp cannot be connected to anything; the parent must call connect() on the port.
Mistake #4: Expecting get() to give up if no data comes
A blocking get() has no timeout. If the provider waits forever for something that never happens, the requester waits with it, and because the requester usually holds the objection, the simulation hangs. When the requester must keep doing other work, use the nonblocking try_get() instead, described in UVM TLM Non-blocking Get Port.
Summary
In this article, you learned:
- The requester declares
uvm_blocking_get_port #(T)and callsget()on it - The provider declares
uvm_blocking_get_imp #(T, IMP)and implementsget(output T t)as a task - The parent connects the port to the imp, because the port is the initiating side
- A blocking
get()returns only when the provider hands back a transaction, so the provider can delay the requester - Get suits pull-style flows where the consumer decides when it is ready, like a driver and its sequencer