Part 4 · TLM & Analysis · Intermediate
Initiator and Target Roles: Who Calls, Who Implements
Role clarity for TLM interfaces - initiators invoke methods through ports, targets implement behavior via imps, and exports relay hierarchy.
Role model first, syntax second
Teams often memorize class names first and role semantics second. Reverse that. Start from call direction: the component that needs service is the initiator; the component that provides service is the target.
In practical UVM environments, this means driver -> sequencer for pull-style item access, producer -> consumer for push-style item transfer, and monitor -> subscribers for analysis broadcast.
[UVM][TLM] caller/provider contract
caller (initiator):
has method invocation intent
owns port-like endpoint
provider (target):
owns method implementation
owns imp-like endpoint
bridge:
export forwards one hierarchy level[TLM] quick role questions
Q1: Which component invokes put/get/write?
-> initiator side
Q2: Which component defines what happens when called?
-> target side (imp implementation)
Q3: Which component simply relays interface upward/downward?
-> export ownerRole ownership is architectural; syntax is just one representation of it.
Keep call direction visible in diagrams and code reviews.
If roles are unclear, expect wrong endpoint declarations and connect errors.
Concrete example: push path with put
In a push model, initiator calls a blocking or non-blocking put method. The target implements that method on an imp. This section uses blocking put for clarity.
class packet_producer extends uvm_component;
`uvm_component_utils(packet_producer)
uvm_blocking_put_port #(pkt_t) out_port;
function new(string name, uvm_component parent);
super.new(name, parent);
out_port = new("out_port", this);
endfunction
task run_phase(uvm_phase phase);
pkt_t p;
forever begin
p = pkt_t::type_id::create("p");
assert(p.randomize());
out_port.put(p); // initiator call
end
endtask
endclassclass packet_consumer extends uvm_component;
`uvm_component_utils(packet_consumer)
uvm_blocking_put_imp #(pkt_t, packet_consumer) in_imp;
function new(string name, uvm_component parent);
super.new(name, parent);
in_imp = new("in_imp", this);
endfunction
virtual task put(pkt_t p);
`uvm_info("CONS", $sformatf("received pkt id=%0d", p.id), UVM_MEDIUM)
endtask
endclass[UVM][TLM] push flow
producer.out_port.put(pkt)
│
▼
connect chain resolves provider
│
▼
consumer.in_imp.put(pkt) implementation runsPort is where call originates; imp is where behavior executes.
Imp class type parameter binds method implementation to owning component type.
Connect chain is mandatory between initiator and provider before run-phase calls.
Concrete example: pull path with get
Pull interfaces invert data direction but not role semantics. Initiator still calls; provider still implements. The value simply flows opposite relative to call request.
class packet_requester extends uvm_component;
`uvm_component_utils(packet_requester)
uvm_blocking_get_port #(pkt_t) in_port;
function new(string name, uvm_component parent);
super.new(name, parent);
in_port = new("in_port", this);
endfunction
task run_phase(uvm_phase phase);
pkt_t p;
forever begin
in_port.get(p); // initiator requests item
`uvm_info("REQ", $sformatf("got pkt id=%0d", p.id), UVM_MEDIUM)
end
endtask
endclassclass packet_source extends uvm_component;
`uvm_component_utils(packet_source)
uvm_blocking_get_imp #(pkt_t, packet_source) out_imp;
function new(string name, uvm_component parent);
super.new(name, parent);
out_imp = new("out_imp", this);
endfunction
virtual task get(output pkt_t p);
p = pkt_t::type_id::create("p");
assert(p.randomize());
endtask
endclass[TLM] pull-role sanity check
requester has get_port and calls get()
source has get_imp and implements get()
Role rule unchanged:
caller = initiator
implementer = targetDo not confuse data direction with caller direction.
Pull interfaces still obey initiator-calls/provider-implements contract.
Output arguments carry data but role ownership remains identical.
Role-debug checklist and anti-confusion tactics
When connect or runtime errors appear, first verify role assignment rather than chasing sequence logic. Most early-stage TLM issues are endpoint misuse.
[UVM][TLM] triage sequence
1) locate caller in run_phase task
2) verify caller owns *_port endpoint
3) verify provider owns *_imp endpoint
4) verify method exists on provider class with correct signature
5) verify connect chain from caller to provider is complete[TLM] common role inversion bugs
bug: consumer declares put_port and expects to receive data
fix: consumer should declare put_imp and implement put()
bug: producer declares put_imp but never implements put()
fix: producer should declare put_port and call put()
bug: export used as terminal provider
fix: terminal endpoint must be impKey takeaways
Caller/provider role clarity prevents most port/export/imp confusion.
Ports initiate calls, imps implement behavior, exports relay hierarchy.
Push and pull interfaces differ in data flow but keep same role contract.
Role-first debugging is faster than waveform-first debugging for TLM bind issues.
Common pitfalls
Designing around class names instead of call ownership semantics.
Using export as final endpoint with no implementation behind it.
Interpreting pull data return as provider-side initiation.
Leaving method signatures implicit, causing mismatch at compile/connect time.