The TLM-1 family: interfaces, flavours and masks
Read any TLM-1 class name as interface, flavour and role, know which methods it lets a caller use, predict whether two objects may connect from their interface masks, and use master, slave and non-blocking transport connections.
- Lesson
- U3.3
- Time
- 30 min
- Runs on
- Verilator with UVM
- Check
- 5 questions
A monitor's write function must hand each packet to a reference model, so its author picks uvm_nonblocking_put_port and calls try_put. The model offers uvm_blocking_put_imp, and the build stops with does not provide the complete interface required of this port. Every TLM class promises a precise set of methods, and connect compares the promises before anything runs.
connect calls from caller to provider, labelled with the mask test. Each object carries a bit mask of the methods it requires or provides; a connection is legal when the provider has every bit the caller needs.- Blocking caller
The blocking port needs bit 0, blocking put. The combined imp has bits 0 and 4, so the test
(011 & 001) == 001passes.More context
Inside
connect, the caller reads the provider'sm_if_maskand compares it with its own. This happens inconnect_phase; no method call crosses this arrow at run time.Follow the calls, values and waits
- data provides 011: m.put_imp → bsrc.put_port
- Non-blocking caller
The non-blocking port needs bit 4. The same combined imp provides it, so both callers share one imp.
More context
A provider may offer more than a caller needs. Only the caller's own bits have to be present.
Follow the calls, values and waits
- data provides 011: m.put_imp → sampler.put_port
- Missing promise
A blocking-only imp has no bit 4.
connectreportsdoes not provide the complete interface required of this portand the build stops at end of elaboration.More context
The comparison fails in
connectitself; the run never starts, because the error is counted before end of elaboration.Follow the calls, values and waits
- data provides 001: k.put_imp → sampler.put_port
Read the diagram as text
- bsrc.put_port: uvm_blocking_put_port: mask 001 (put)
- sampler.put_port: uvm_nonblocking_put_port: mask 010 (try_put, can_put)
- m.put_imp: uvm_put_imp: mask 011 (all three)
- k.put_imp: uvm_blocking_put_imp: mask 001 (put only)
- bsrc.put_port → m.put_imp: 011 & 001 = 001: legal
- sampler.put_port → m.put_imp: 011 & 010 = 010: legal
- sampler.put_port → k.put_imp: 001 & 010 = 000: error
Masks print here as three hex digits: the non-blocking put bit is 1 << 4, so it shows as 010.
Eight interfaces, three flavours
A TLM-1 class name has three parts: uvm_ + flavour + interface + role. The interface names the methods, the flavour says blocking, non-blocking or both, and the role is port, export or imp from U3.2.
| Interface | Blocking (tasks) | Non-blocking (functions) | Data direction for the caller |
|---|---|---|---|
| put | put | try_put, can_put | Out, with the call |
| get | get | try_get, can_get | In, on return; removes |
| peek | peek | try_peek, can_peek | In, on return; keeps |
| get_peek | get, peek | the four of get and peek | In |
| transport | transport(req, rsp) | nb_transport(req, rsp) | Request out, response in, one call |
| master | put of REQ; get, peek of RSP | their try_ and can_ forms | Requests out, responses in, separate calls |
| slave | put of RSP; get, peek of REQ | their try_ and can_ forms | Requests in, responses out |
| analysis | none | write (one flavour only) | Out, to every listener |
The combined flavour has no prefix and offers both columns: uvm_put_port, uvm_get_peek_imp, uvm_master_export.
Read a name left to right. uvm_nonblocking_get_port is a caller that may use try_get and can_get only. uvm_put_imp is a target that must implement put, try_put and can_put. uvm_slave_imp is a target that hands out requests and takes responses.
Every class name, by interface and role
| Interface | Port | Export | Imp |
|---|---|---|---|
| put | uvm_blocking_put_port, uvm_nonblocking_put_port, uvm_put_port | uvm_blocking_put_export, uvm_nonblocking_put_export, uvm_put_export | uvm_blocking_put_imp, uvm_nonblocking_put_imp, uvm_put_imp |
| get | uvm_blocking_get_port, uvm_nonblocking_get_port, uvm_get_port | the same three with _export | the same three with _imp |
| peek | uvm_blocking_peek_port, uvm_nonblocking_peek_port, uvm_peek_port | with _export | with _imp |
| get_peek | uvm_blocking_get_peek_port, uvm_nonblocking_get_peek_port, uvm_get_peek_port | with _export | with _imp |
| transport | uvm_blocking_transport_port, uvm_nonblocking_transport_port, uvm_transport_port | with _export | with _imp |
| master | uvm_blocking_master_port, uvm_nonblocking_master_port, uvm_master_port | with _export | with _imp |
| slave | uvm_blocking_slave_port, uvm_nonblocking_slave_port, uvm_slave_port | with _export | with _imp |
| analysis | uvm_analysis_port | uvm_analysis_export | uvm_analysis_imp |
Parameters: one type #(T) for put, get, peek and get_peek; #(REQ, RSP) for transport, master and slave; imps add the owner type, #(T, IMP) or #(REQ, RSP, IMP).
Master and slave imps take two optional extra types, REQ_IMP and RSP_IMP, so the request half and the response half can be served by different objects. The req/rsp channel uses this to send its master_export puts to the request FIFO and its gets to the response FIFO.
How a port knows which methods it may call
Each class sets a bit mask, m_if_mask, from constants such as UVM_TLM_BLOCKING_PUT_MASK (bit 0) and UVM_TLM_NONBLOCKING_PUT_MASK (bit 4). A combined class ORs them. The example prints them at end of elaboration: blocking put port 001, non-blocking put port 010, combined put imp 011.
connect checks one rule: the provider's mask ANDed with the caller's mask must equal the caller's mask. A provider may offer more than the caller uses, never less. Exports are checked the same way, so a uvm_put_export in front of a uvm_put_imp serves both kinds of caller.
The mask only advertises; it does not create methods. Each class body expands the macros for its interface, so a uvm_blocking_put_port gets a put task and nothing else. Its try_put is still callable, because every class extends uvm_tlm_if_base, but that base body only reports a UVM_ERROR, UVM TLM interface function not implemented, and returns 0. Choose the flavour you will call, and let connect check the rest.
Master and slave: requests and responses as separate calls
A master port bundles two directions for an initiator: it puts requests and gets responses. A slave port is the mirror for the component that serves them: it gets requests and puts responses. Both are initiators, so they meet through a component that implements both sides, usually uvm_tlm_req_rsp_channel with its master_export and slave_export.
get asked for it. The master and the slave each own one port; the channel's two imps route puts and gets to its FIFOs.- Two requests at 30 ns
The master calls
port.put(#11)andport.put(#12). Both return at once into the size-2 request FIFO.More context
Trace lines 8 and 9. Each request rides in its
putcall and the master'suvm_master_improutes it to the request FIFO, which has room for both.Follow the calls, values and waits
- call req #11: master → chan.master_export
- data req #11: chan.master_export → request FIFO
- call req #12: master → chan.master_export
- data req #12: chan.master_export → request FIFO
- Waiting: slave — blocked in get(q): no request yet
- Serve30 ns (illustrative)
The master calls
port.get(r)and waits. The slave was already waiting inport.get(q); it receives #11 at 30 ns and works for 5 ns.More context
Trace lines 10 and 11. The same
putandgetcalls now block in different FIFOs: the master on the empty response FIFO, while the slave's pendinggetreturns the oldest request.Follow the calls, values and waits
- call get(r): master → chan.master_export
- data req #11: request FIFO → chan.slave_export
- return req #11: chan.slave_export → slave
- Waiting: master — blocked in get until a response exists
- Waiting: slave — working on #11 for 5 ns
- Released: slave
- Answer at 35 ns
The slave puts #111 and at once gets #12, which was already queued.
More context
Trace lines 12 and 13. The slave calls twice through one port:
put(#111)goes to the response FIFO, and the nextget(q)finds #12 waiting.Follow the calls, values and waits
- call rsp #111: slave → chan.slave_export
- data rsp #111: chan.slave_export → response FIFO
- call get(q): slave → chan.slave_export
- data req #12: request FIFO → chan.slave_export
- return req #12: chan.slave_export → slave
- Waiting: slave — working on #12 for 5 ns
- Released: slave
- Collect #11135 ns (illustrative)
The master's
getreturns #111 at 35 ns, and it callsport.get(r)again.More context
Trace lines 14 and 15. The response FIFO is empty again, so the second
getwaits for the slave.Follow the calls, values and waits
- data rsp #111: response FIFO → chan.master_export
- return rsp #111: chan.master_export → master
- call get(r): master → chan.master_export
- Waiting: master — blocked in get until #112 exists
- Released: master
- Collect #112 at 40 ns
The slave puts #112 at 40 ns, and the master's waiting
getreturns it.More context
Trace lines 16 and 17. Both requests were in the channel together; the responses come back in request order.
Follow the calls, values and waits
- call rsp #112: slave → chan.slave_export
- data rsp #112: chan.slave_export → response FIFO
- data rsp #112: response FIFO → chan.master_export
- return rsp #112: chan.master_export → master
- Released: slave
- Released: master
Read the diagram as text
- master: uvm_blocking_master_port #(pkt, pkt)
- chan.master_export: uvm_master_imp: put to requests, get from responses
- request FIFO: size 2 in the example
- response FIFO: size 2 in the example
- chan.slave_export: uvm_slave_imp: get from requests, put to responses
- slave: uvm_blocking_slave_port #(pkt, pkt)
- master → chan.master_export: put(req) / get(rsp) calls
- chan.master_export → request FIFO: req stored
- slave → chan.slave_export: get(req) / put(rsp) calls
- request FIFO → chan.slave_export: oldest req
- chan.slave_export → slave: req returns
- chan.slave_export → response FIFO: rsp stored
- response FIFO → chan.master_export: oldest rsp
- chan.master_export → master: rsp returns
The master sends its next request without waiting for a response. How far ahead it can get depends on everything still in flight, as the next table shows.
transport(req, rsp) | Master and slave | |
|---|---|---|
| Calls per exchange | One | Two from the master, two from the slave |
| Requests in flight | One per calling thread | Queued requests + one in service + uncollected responses |
| Who does the work | The target, inside the caller's call | The slave, in its own thread |
| Typical use | A register read model that answers in the call | A pipelined bus model with a separate response stream |
Transport is the simplest request and answer. Master and slave separate them, so requests can overlap.
A master that puts a batch before collecting anything can stop for good. With the default sizes of 1, the channel accepts three requests without a single get. One waits in the request FIFO, one is with the slave, and one has become a response in the response FIFO. A fourth put waits for space, the slave waits to return its second response, and neither can move.
| Where a request is | Sizes 1 and 1, four puts before any get | Same, with puts and gets running together |
|---|---|---|
| Accepted by the channel (sent) | 3, then the fourth put waits forever | 4 |
| Taken by the slave | 2 | 4 |
| Answered by the slave | 1; the second answer waits for space | 4 |
| Collected by the master (got) | 0 | 4, the last at 20 ns |
Request FIFO occupancy is not the limit; the limit is the sum of everything not yet collected.
The remedy is to collect while sending. Run the put loop and the get loop as two forked threads, or cap the requests in flight at what the channel can hold, as the spot question shows. Three requests, as in the predict question, happen to fit; that does not make a larger batch safe.
nb_transport(req, rsp) is the non-blocking transport of TLM-1: a function that returns 1 with the response filled in when the target can answer now, and 0 otherwise. Use it for zero-time models called from a function, such as a lookup inside write. It is unrelated to TLM-2.0's nb_transport_fw and nb_transport_bw, which carry phases through sockets (U9.4).
Try it
The two mask lines show the bits behind each name: put is bit 0 and its non-blocking form bit 4, get and peek add bits 1, 2, 5 and 6, and bits 9 and 10 mark master and slave. Lines 3 to 7 are two callers of different flavours sharing one combined imp; lines 8 to 17 are the master and slave exchange.
Try it: change put_model's imp to uvm_blocking_put_imp #(pkt, put_model) and rebuild. The non-blocking connection fails with the interface error, while the blocking one stays legal.
masks: blocking_put_port=001 nonblocking_put_port=010 put_imp=011 masks: blocking_master_port=207 blocking_slave_port=407 chan.master_export=277 1 0 ns test: nb_transport(#4) returned 1 with #204 2 0 ns test: nb_transport(#5) returned 0, rsp is null: 1 3 10 ns bsrc: put_port.put(#1) 4 12 ns model.put: stored #1 after 2 ns 5 12 ns bsrc: put(#1) returned 6 20 ns model.try_put: stored #2 at once 7 20 ns sampler: try_put(#2) returned 1 8 30 ns master: port.put(#11) 9 30 ns master: port.put(#12) 10 30 ns master: port.get(r) 11 30 ns slave: port.get -> #11 12 35 ns slave: port.put(#111) 13 35 ns slave: port.get -> #12 14 35 ns master: got #111 15 35 ns master: port.get(r) 16 40 ns slave: port.put(#112) 17 40 ns master: got #112 UVM_ERROR : 0Check yourself
Remember
- A TLM-1 class name is flavour (blocking, nonblocking, or none for both) + interface (put, get, peek, get_peek, transport, master, slave, analysis) + role (port, export, imp).
- Each class carries an interface mask;
connectrequires the provider to have every bit the caller has, so a provider may offer more, never less. - The mask advertises what a class provides; the class's macros define the methods, and any other TLM method falls through to a base body that reports it is not implemented.
- Master and slave ports split one exchange into a request put and a response get, so requests overlap; everything uncollected counts against the channel, so collect while sending. Transport does both in one call, and
nb_transportanswers at once or returns 0.