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.

Output
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
Output
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_port and uvm_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_fifo is a common provider for nonblocking gets
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5