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.

Masks decide what may connectArrows are 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.
port (calls out)imp (implements)
Masks decide what may connectArrows are `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. port bsrc.put_port uvm_blocking_put_port:mask 001 (put) port sampler.put_port uvm_nonblocking_put_port:mask 010 (try_put,can_put) imp m.put_imp uvm_put_imp: mask 011 (allthree) imp k.put_imp uvm_blocking_put_imp:mask 001 (put only) 011 & 001 = 001:legal011 & 010 = 010:legal001 & 010 = 000:error
  1. Blocking caller

    The blocking port needs bit 0, blocking put. The combined imp has bits 0 and 4, so the test (011 & 001) == 001 passes.

    More context

    Inside connect, the caller reads the provider's m_if_mask and compares it with its own. This happens in connect_phase; no method call crosses this arrow at run time.

    Follow the calls, values and waits
    1. data provides 011: m.put_imp → bsrc.put_port
  2. 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
    1. data provides 011: m.put_imp → sampler.put_port
  3. Missing promise

    A blocking-only imp has no bit 4. connect reports does not provide the complete interface required of this port and the build stops at end of elaboration.

    More context

    The comparison fails in connect itself; the run never starts, because the error is counted before end of elaboration.

    Follow the calls, values and waits
    1. 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.

InterfaceBlocking (tasks)Non-blocking (functions)Data direction for the caller
putputtry_put, can_putOut, with the call
getgettry_get, can_getIn, on return; removes
peekpeektry_peek, can_peekIn, on return; keeps
get_peekget, peekthe four of get and peekIn
transporttransport(req, rsp)nb_transport(req, rsp)Request out, response in, one call
masterput of REQ; get, peek of RSPtheir try_ and can_ formsRequests out, responses in, separate calls
slaveput of RSP; get, peek of REQtheir try_ and can_ formsRequests in, responses out
analysisnonewrite (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
InterfacePortExportImp
putuvm_blocking_put_port, uvm_nonblocking_put_port, uvm_put_portuvm_blocking_put_export, uvm_nonblocking_put_export, uvm_put_exportuvm_blocking_put_imp, uvm_nonblocking_put_imp, uvm_put_imp
getuvm_blocking_get_port, uvm_nonblocking_get_port, uvm_get_portthe same three with _exportthe same three with _imp
peekuvm_blocking_peek_port, uvm_nonblocking_peek_port, uvm_peek_portwith _exportwith _imp
get_peekuvm_blocking_get_peek_port, uvm_nonblocking_get_peek_port, uvm_get_peek_portwith _exportwith _imp
transportuvm_blocking_transport_port, uvm_nonblocking_transport_port, uvm_transport_portwith _exportwith _imp
masteruvm_blocking_master_port, uvm_nonblocking_master_port, uvm_master_portwith _exportwith _imp
slaveuvm_blocking_slave_port, uvm_nonblocking_slave_port, uvm_slave_portwith _exportwith _imp
analysisuvm_analysis_portuvm_analysis_exportuvm_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.

Two initiators, one channel, two FIFOsArrows labelled call leave the caller; arrows labelled returns carry an object back to the component whose 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.
imp (implements)call: who calls whomdata: where the transaction goes
Two initiators, one channel, two FIFOsArrows labelled *call* leave the caller; arrows labelled *returns* carry an object back to the component whose `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. component master uvm_blocking_master_port#(pkt, pkt) imp chan.master_export uvm_master_imp: put torequests, get fromresponses queue request FIFO size 2 in the example queue response FIFO size 2 in the example imp chan.slave_export uvm_slave_imp: get fromrequests, put toresponses component slave uvm_blocking_slave_port#(pkt, pkt) put(req) / get(rsp)callsreq storedget(req) / put(rsp)callsoldest reqreq returnsrsp storedoldest rsprsp returns
  1. Two requests at 30 ns

    The master calls port.put(#11) and port.put(#12). Both return at once into the size-2 request FIFO.

    More context

    Trace lines 8 and 9. Each request rides in its put call and the master's uvm_master_imp routes it to the request FIFO, which has room for both.

    Follow the calls, values and waits
    1. call req #11: master → chan.master_export
    2. data req #11: chan.master_export → request FIFO
    3. call req #12: master → chan.master_export
    4. data req #12: chan.master_export → request FIFO
    5. Waiting: slave — blocked in get(q): no request yet
  2. Serve30 ns (illustrative)

    The master calls port.get(r) and waits. The slave was already waiting in port.get(q); it receives #11 at 30 ns and works for 5 ns.

    More context

    Trace lines 10 and 11. The same put and get calls now block in different FIFOs: the master on the empty response FIFO, while the slave's pending get returns the oldest request.

    Follow the calls, values and waits
    1. call get(r): master → chan.master_export
    2. data req #11: request FIFO → chan.slave_export
    3. return req #11: chan.slave_export → slave
    4. Waiting: master — blocked in get until a response exists
    5. Waiting: slave — working on #11 for 5 ns
    6. Released: slave
  3. 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 next get(q) finds #12 waiting.

    Follow the calls, values and waits
    1. call rsp #111: slave → chan.slave_export
    2. data rsp #111: chan.slave_export → response FIFO
    3. call get(q): slave → chan.slave_export
    4. data req #12: request FIFO → chan.slave_export
    5. return req #12: chan.slave_export → slave
    6. Waiting: slave — working on #12 for 5 ns
    7. Released: slave
  4. Collect #11135 ns (illustrative)

    The master's get returns #111 at 35 ns, and it calls port.get(r) again.

    More context

    Trace lines 14 and 15. The response FIFO is empty again, so the second get waits for the slave.

    Follow the calls, values and waits
    1. data rsp #111: response FIFO → chan.master_export
    2. return rsp #111: chan.master_export → master
    3. call get(r): master → chan.master_export
    4. Waiting: master — blocked in get until #112 exists
    5. Released: master
  5. Collect #112 at 40 ns

    The slave puts #112 at 40 ns, and the master's waiting get returns 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
    1. call rsp #112: slave → chan.slave_export
    2. data rsp #112: chan.slave_export → response FIFO
    3. data rsp #112: response FIFO → chan.master_export
    4. return rsp #112: chan.master_export → master
    5. Released: slave
    6. 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 exchangeOneTwo from the master, two from the slave
Requests in flightOne per calling threadQueued requests + one in service + uncollected responses
Who does the workThe target, inside the caller's callThe slave, in its own thread
Typical useA register read model that answers in the callA 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 isSizes 1 and 1, four puts before any getSame, with puts and gets running together
Accepted by the channel (sent)3, then the fourth put waits forever4
Taken by the slave24
Answered by the slave1; the second answer waits for space4
Collected by the master (got)04, 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.

RUNVerilator + UVM 2020.3.1 · simulator service
Edit the code, then run it
Expected among the UVM messages: 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 : 0

Check yourself

Predict the outputA master puts requests #1, #2 and #3 into a uvm_tlm_req_rsp_channel with the default FIFO sizes, then gets three responses. The slave takes 5 ns per request and answers with id + 100. What does the master print?
`timescale 1ns/1ns
`include "uvm_macros.svh"
import uvm_pkg::*;

class pkt extends uvm_sequence_item;
  `uvm_object_utils(pkt)
  int id;
  function new(string name = "pkt"); super.new(name); endfunction
endclass

class master extends uvm_component;
  `uvm_component_utils(master)
  uvm_blocking_master_port #(pkt, pkt) port;
  function new(string name, uvm_component parent); super.new(name, parent); port = new("port", this); endfunction
  task run_phase(uvm_phase phase);
    pkt q, r;
    phase.raise_objection(this);
    for (int i = 1; i <= 3; i++) begin
      q = new;
      q.id = i;
      port.put(q);
    end
    repeat (3) port.get(r);
    $display("%0d ns: last response #%0d", $time, r.id);
    phase.drop_objection(this);
  endtask
endclass

class slave extends uvm_component;
  `uvm_component_utils(slave)
  uvm_blocking_slave_port #(pkt, pkt) port;
  function new(string name, uvm_component parent); super.new(name, parent); port = new("port", this); endfunction
  task run_phase(uvm_phase phase);
    pkt q, r;
    forever begin
      port.get(q);
      #5;
      r = new;
      r.id = q.id + 100;
      port.put(r);
    end
  endtask
endclass

class ms_test extends uvm_test;
  `uvm_component_utils(ms_test)
  master ma;
  slave  sl;
  uvm_tlm_req_rsp_channel #(pkt) chan;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase);
    ma   = master::type_id::create("ma", this);
    sl   = slave::type_id::create("sl", this);
    chan = new("chan", this);                 // default FIFO sizes: 1 and 1
  endfunction
  function void connect_phase(uvm_phase phase);
    ma.port.connect(chan.master_export);
    sl.port.connect(chan.slave_export);
  endfunction
endclass

module tb;
  initial run_test("ms_test");
endmodule
Spot the bugA sampler holds a uvm_nonblocking_put_port and calls try_put three times. Each option is the test's connect_phase line. m has a uvm_put_imp, k a uvm_blocking_put_imp, f is an unbounded uvm_tlm_fifo, and w forwards a uvm_put_export to a model inside it. Which line fails?
Spot the bugA master sends four requests through a uvm_tlm_req_rsp_channel with the default FIFO sizes of 1; the slave takes 5 ns per request. Each option is the master's run_phase body. The run passes when all four responses are collected within 100 ns. Which body never finishes?
ConceptWhich declaration lets one component accept both put calls from a blocking port and try_put calls from a non-blocking port?
ConceptA bus model must send several requests before the first response arrives. Why is a master port a better fit than a blocking transport port?

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; connect requires 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_transport answers at once or returns 0.