A UVM TLM nonblocking get port lets a component ask for a transaction without waiting for one. The requester calls try_get(), a function that returns 1 and hands back a transaction if one is available, or returns 0 straight away if not. It can also call can_get() to ask whether a transaction is available before trying. The provider implements both functions behind a uvm_nonblocking_get_imp.
This is the right connection when the requester has other work to do and only wants a transaction if one is ready now, for example a component that checks for pending work once per clock cycle and otherwise carries on.
7 min read | Intermediate Level
What You'll Learn
- Declare a nonblocking get port and implement its imp
- Use
try_get()safely when no transaction is available - Poll with
can_get()while letting simulation time advance - Choose between blocking and nonblocking get
Syntax
uvm_nonblocking_get_port #(T) port_name; // in the requester
uvm_nonblocking_get_imp #(T, IMP) imp_name; // in the provider
virtual function bit try_get(output T t); // return 1 and set t, or return 0
virtual function bit can_get(); // return 1 if a try_get() now would succeed
- Both are functions: the provider must answer immediately and cannot wait for data to appear.
- When
try_get()returns 0, the output argument does not hold a valid transaction, so the requester must not use it. - A nonblocking imp must implement both functions.
The Provider
componentA creates a packet whenever it is asked and reports success. Its can_get() always says yes in this first example.
class componentA extends uvm_component;
`uvm_component_utils(componentA)
uvm_nonblocking_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
virtual function bit try_get(output Packet pkt);
pkt = new();
assert(pkt.randomize());
`uvm_info("COMPA", "ComponentB has requested for a packet", UVM_LOW)
pkt.print(uvm_default_line_printer);
return 1; // a packet is always available here
endfunction
virtual function bit can_get();
return 1;
endfunction
endclass
The Requester
componentB asks for a packet with try_get() and only uses it if the call succeeded.
class componentB extends uvm_component;
`uvm_component_utils(componentB)
uvm_nonblocking_get_port #(Packet) m_get_port;
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
if (m_get_port.try_get(pkt)) begin // returns at once, in zero time
`uvm_info("COMPB", "ComponentA just gave me the packet", UVM_LOW)
pkt.print(uvm_default_line_printer);
end else
`uvm_info("COMPB", "ComponentA did not give packet", UVM_LOW)
end
phase.drop_objection(this);
endtask
endclass
The test connects them with compB.m_get_port.connect(compA.m_get_imp) in connect_phase and prints the topology at the end of elaboration.
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_nonblocking_get_imp - @1971
compB componentB - @1936
m_get_port uvm_nonblocking_get_port - @2010
-----------------------------------------------------
UVM_INFO testbench.sv(97) @ 0: uvm_test_top.compA [COMPA] ComponentB has requested for a packet
Packet: (Packet@1903) { addr: 'he8 data: 'hc5 }
UVM_INFO testbench.sv(67) @ 0: uvm_test_top.compB [COMPB] ComponentA just gave me the packet
Packet: (Packet@1903) { addr: 'he8 data: 'hc5 }
UVM_INFO testbench.sv(97) @ 0: uvm_test_top.compA [COMPA] ComponentB has requested for a packet
Packet: (Packet@2058) { addr: 'hd6 data: 'hd }
UVM_INFO testbench.sv(67) @ 0: uvm_test_top.compB [COMPB] ComponentA just gave me the packet
Packet: (Packet@2058) { addr: 'hd6 data: 'hd }
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_objection.svh(1271) @ 0: 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) @ 0: reporter [UVM/REPORT/SERVER]
--- UVM Report Summary ---
Both transfers complete at time 0, since neither side consumes time. The provider runs inside the requester's try_get() call, which is why its message always appears first.
Quick Check
Q: What would happen in the original version of this requester, which called pkt.print() after the if/else, the first time try_get() returned 0?
Show Answer
pkt would still be null, so pkt.print() would dereference a null handle and the simulation would stop with an error. Only touch the output of try_get() when it returned 1.
Polling with can_get()
In this version the provider is ready only some of the time. Its can_get() returns a random bit that is 1 about 30% of the time, and the requester checks every 10 ns until it gets a yes.
// componentA (provider): ready only some of the time
virtual function bit can_get();
bit ready;
std::randomize(ready) with { ready dist {0:/70, 1:/30}; };
return ready;
endfunction
// componentB (requester): rest of the code remains the same
repeat (m_num_tx) begin
while (!m_get_port.can_get()) begin
#10 `uvm_info("COMPB", $sformatf("See if can_get() is ready"), UVM_LOW)
end
`uvm_info("COMPB", $sformatf("COMPA ready, get packet now"), UVM_LOW)
m_get_port.try_get(pkt);
pkt.print(uvm_default_line_printer);
end
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_nonblocking_get_imp - @1972
compB componentB - @1937
m_get_port uvm_nonblocking_get_port - @2011
-----------------------------------------------------
UVM_INFO testbench.sv(60) @ 10: uvm_test_top.compB [COMPB] See if can_get() is ready
UVM_INFO testbench.sv(60) @ 20: uvm_test_top.compB [COMPB] See if can_get() is ready
UVM_INFO testbench.sv(60) @ 30: uvm_test_top.compB [COMPB] See if can_get() is ready
UVM_INFO testbench.sv(62) @ 30: uvm_test_top.compB [COMPB] COMPA ready, get packet now
UVM_INFO testbench.sv(97) @ 30: uvm_test_top.compA [COMPA] ComponentB has requested for a packet
Packet: (Packet@2065) { addr: 'h8c data: 'h99 }
Packet: (Packet@2065) { addr: 'h8c data: 'h99 }
UVM_INFO testbench.sv(62) @ 30: uvm_test_top.compB [COMPB] COMPA ready, get packet now
UVM_INFO testbench.sv(97) @ 30: uvm_test_top.compA [COMPA] ComponentB has requested for a packet
Packet: (Packet@2095) { addr: 'h97 data: 'hb8 }
Packet: (Packet@2095) { addr: 'h97 data: 'hb8 }
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_objection.svh(1271) @ 30: 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) @ 30: reporter [UVM/REPORT/SERVER]
--- UVM Report Summary ---
The first check at 0 ns says no, so the requester waits 10 ns and prints a message each time it checks again. The provider first reports ready at 30 ns. For the second packet the very first check happens to return 1, so both packets are taken at 30 ns. Each packet appears twice in the log because the provider prints it inside try_get() and the requester prints it again afterwards.
The #10 inside the loop is essential. Without a delay, the loop would keep calling can_get() at the same timestamp, and if the provider's readiness depended on anything that changes over time, the simulation would never move forward.
Pattern: Drain Everything Available Each Cycle
A very common use of try_get() is a component that wakes up on every clock edge, takes every transaction that has arrived since the last edge, and then goes back to waiting. Because try_get() returns 0 as soon as nothing is left, the inner loop ends by itself and the component never blocks.
virtual task run_phase(uvm_phase phase);
Packet pkt;
forever begin
@(posedge vif.clk); // time advances between checks
while (m_get_port.try_get(pkt)) begin // take everything that is ready
process(pkt);
end
update_statistics(); // other per-cycle work continues
end
endtask
The clock edge is what lets time advance between rounds, and the while loop handles any number of transactions per cycle, including none. A blocking get() could not be used here, because it would stop the per-cycle work whenever nothing had arrived.
Note that this loop is a forever, so the component should not hold an objection itself. Let the test or the stimulus source decide when the phase ends.
Where try_get() Really Shines
In real testbenches, the provider behind a nonblocking get is often a uvm_tlm_fifo. Its try_get() returns 1 with the oldest entry if the FIFO holds anything, and 0 if it is empty, which lets a component drain whatever has arrived without ever blocking. That pattern is covered in UVM TLM Fifo.
Blocking or Nonblocking Get?
If the requester has nothing else to do until a transaction arrives, a blocking get() is simpler and wastes no simulation effort on polling. If it must keep doing other work, give up after a while, or handle "nothing available" as a normal case, use try_get(). The plain uvm_get_port and uvm_get_imp provide get(), try_get() and can_get() together, so one provider can serve both kinds of requester. The blocking form is described in UVM TLM Blocking Get Port.
Common Mistakes
Mistake #1: Using the output of a failed try_get()
Wrong: calling pkt.print() whatever try_get() returned
Right: if (m_get_port.try_get(pkt)) pkt.print();
Why? A failed try_get() leaves the output without a valid transaction. Using it causes a null-handle error, or worse, silently reuses a stale transaction from a previous call.
Mistake #2: Polling can_get() in zero time
A while (!can_get()); loop with no delay or clock edge inside lets no simulation time pass, so the provider can never change state. Always wait for an event or a delay between checks.
Mistake #3: Leaving can_get() empty in the provider
An empty can_get() returns 0, so any requester that checks first will conclude the provider is never ready. Implement it properly, even if your own requester only calls try_get().
Mistake #4: Letting can_get() and try_get() disagree
A requester that sees can_get() return 1 expects the next try_get() to succeed. In the polling example both functions make independent decisions, which is fine for a demonstration, but in a real provider they should be driven by the same state, for example whether an internal queue is empty. Otherwise a requester can be told "ready" and then still be refused.
Summary
In this article, you learned:
- A nonblocking get uses
uvm_nonblocking_get_portanduvm_nonblocking_get_imp try_get()returns 1 with a transaction, or 0 immediately if none is available- The output of a failed
try_get()must never be used can_get()checks availability, and polling loops must let time advance- A
uvm_tlm_fifois a common provider for nonblocking gets