A VIP drop adds a few ports to a component, and the next regression run stops every test at time zero:
UVM_ERROR @ 0: uvm_test_top.demo.dangling_export [Connection Error] connection count of 0 does not meet required minimum of 1
UVM_ERROR @ 0: uvm_test_top.demo.greedy_port [Connection Error] connection count of 2 exceeds maximum of 1
UVM_ERROR @ 0: uvm_test_top.demo.lonely_port [Connection Error] connection count of 0 does not meet required minimum of 1
UVM_FATAL @ 0: reporter [BUILDERR] stopping due to build errorsThe component has five ports. Two of them were also left unconnected, an analysis port and the kind of pull port a driver uses, and neither complained. Why do some ports fail the run and others not, and what are the library's ready-made channels for?
Each port, export and imp is built with a minimum and a maximum connection count. As end_of_elaboration starts, the root calls resolve_bindings() on every one of them. That walks each connector's network down to the imps it finally reaches, counts them, and reports a Connection Error if the count is below the minimum or above the maximum. Think of seats at a table: some reservations are for exactly one guest, some say any number, including none.
This is the rule, stated once for the course: an unconnected port (non-analysis) is an error at end of elaboration (resolve_bindings, min size 1). An analysis port with no subscribers is legal. A monitor with no listeners is normal; a put port with no one to accept the put can never work, so the library refuses to start.
Connector | Min | Max | Left unconnected |
|---|---|---|---|
| 1 | 1 | Error |
| 1 | 1 | Error |
| 0 | unbounded | Legal; writes reach nobody |
| 1 | unbounded | Error, unlike the port |
Any imp | 1 | 1 | Legal: an imp counts itself |
| 0 | 1 | Legal |
TLM 2.0 initiator sockets | 1 | 1 | Error (a target socket counts itself, like an imp) |
Two rows deserve a second look. The maximum counts imps reached, not ports connected in: any number of ports may connect to one imp (fan-in), but a put port that reaches two imps exceeds its maximum of 1. And the analysis export has a minimum of 1, so converting a subscriber's imp into an export during a refactor, and forgetting to connect the export down, fails the run even though the same mistake with a port is silent. Ports and exports accept explicit counts in their constructor, new(name, parent, min_size, max_size), with UVM_UNBOUNDED_CONNECTIONS for no maximum; analysis ports and imps fix their own.
|
| |
|---|---|---|
Depth | Constructor argument, default 1; 0 means unbounded | Always unbounded; |
Input |
|
|
Output |
| Same, inherited |
Backpressure | Yes: | None: the publisher never waits |
Introspection |
| Same; watch |
Taps |
| Same |
flush() empties the FIFO, but reports an error and does nothing if a reader is blocked in get while items are queued. The analysis FIFO is the bridge between a monitor's zero-time write() and a consumer that wants to pull at its own pace; imp_decl vs FIFOs builds a scoreboard on three of them.
A master port bundles the requester's half of a request/response protocol: put(REQ) to send a request, get(RSP) and peek(RSP) to collect a response. A slave port bundles the responder's half: get(REQ) and peek(REQ) to take a request, put(RSP) to answer. Each comes in blocking, non-blocking and combined forms (uvm_blocking_master_port, uvm_master_port, and so on), with matching exports and imps.
The request/response channel is two FIFOs, requests one way and responses the other, each with a default depth of 1 (new(name, parent, request_fifo_size, response_fifo_size)). It offers master_export and slave_export for master and slave ports, separate put_request_export, get_peek_response_export, get_peek_request_export and put_response_export if you prefer single-direction ports, and request_ap and response_ap taps. The transport channel extends it with depths fixed at 1 and adds transport_export: its transport(req, rsp) puts the request and then blocks in get for the response, so a caller gets one-call semantics while the responder still runs its own get/put loop.
// snippet: from tlm_tb.sv in the course bench
// responder: one loop serves any slave port
task serve(uvm_slave_port #(apb_item, apb_item) p);
forever begin
apb_item req, rsp;
p.get(req); // take the next request
#10ns;
$cast(rsp, req.clone());
rsp.rdata = 32'hC0DE_0000 | req.addr;
p.put(rsp); // answer it
end
endtask
// test connect_phase
req.mport.connect(rr_ch.master_export); // put REQ, get RSP
rsp.sport.connect(rr_ch.slave_export); // get REQ, put RSP
req.tport.connect(tr_ch.transport_export); // transport(req, rsp) in one call
rsp.tsport.connect(tr_ch.slave_export);tlm_channel_test connects one requester and one responder through both channels. The requester sends one request each way:
UVM_INFO tlm_tb.sv(141) @ 10000: uvm_test_top.req [MASTER] response for 10: rdata=c0de0010
UVM_INFO tlm_tb.sv(145) @ 20000: uvm_test_top.req [TRANSPORT] response for 04: rdata=c0de0004The master port's put returned at once (the request FIFO had room); its get waited the responder's 10 ns. COUNT is at offset 0x10, so the response carries c0de0010.
The transport call took the same 10 ns, but as one blocking call. STATUS is at 0x04.
The opening errors came from tlm_cardinality_test in the same file: a component with an unconnected put port, a put port connected to two imps, an analysis export connected to nothing, an analysis port with no subscribers and an unconnected uvm_seq_item_pull_port. Only the first three are errors, exactly as the table predicts.
A protocol VIP release added a uvm_blocking_get_port to its monitor, for an optional feed of configuration updates. Nobody in the integrating team used the feature, so nobody connected the port. Every test in the regression now stopped at time 0 with connection count of 0 does not meet required minimum of 1 and BUILDERR. The VIP's own test bench had always connected the port, so its authors never saw it.
There are three clean fixes, in order of preference. The VIP can build the port only when its configuration enables the feature. It can construct the port with a minimum of 0, new("cfg_port", this, 0, 1), and check cfg_port.size() before calling it. Or the integrator connects it to a harmless provider such as an empty uvm_tlm_fifo. What the old course used to claim, that an unconnected port drops transactions silently, is the one outcome that cannot happen for a non-analysis port.
uvm_analysis_imp is changed to a uvm_analysis_export so a child can implement it later, but the child is not written yet. The monitor still connects to it. What happens?The export reaches no imp, and an analysis export has a minimum of 1, so resolve_bindings reports connection count of 0 does not meet required minimum of 1 and the run stops with BUILDERR. The same gap with an analysis port would be silent.
uvm_tlm_req_rsp_channel at its default depths, a requester puts four requests before it reads any response. The responder loops get-request, put-response. What happens?Deadlock on the fourth put. Each FIFO holds 1. The first response fills the response FIFO, so the responder blocks putting the second one, while the requester is blocked putting request 4, which needs the responder to take request 3 first. Each waits for the other. Read responses in a separate thread, or size the FIFOs for the number of outstanding requests.
uvm_tlm_transport_channel offer transport() to the caller while the responder uses ordinary slave-port get and put?Inside the channel, transport puts the request into the request FIFO and then blocks in get on the response FIFO. The responder sees a normal request queue and answers into the response queue, so the channel adapts a one-call interface on one side to a loop on the other.
Next: you have seen an analysis FIFO as a component. In imp_decl vs FIFOs the DMA scoreboard is rebuilt on three of them and run beside the original, and the two designs part ways at end of test.