UVM
Analysis Exports
The analysis export — the hierarchical forwarder that promotes a sub-component's analysis interface to a parent's boundary so connections are made at the boundary, not the internals, and why an analysis chain must terminate in an imp, not an export.
TLM Communication · Module 16 · Page 16.4
The Engineering Problem
Analysis ports broadcast transactions, and a subscriber implements write() to receive them (Module 16.3). But a subscriber is often buried in the hierarchy — a scoreboard inside an environment inside a larger environment — while the producer (a monitor's analysis port) is elsewhere. Connecting them directly would mean reaching across hierarchy boundaries: the producer's parent connecting straight into env.scoreboard.write_imp, deep inside the consumer's structure. That breaks encapsulation — the producer's side now knows the consumer's internal layout (that the scoreboard is there, named that), so a change to the consumer's internals breaks the connection, and the consumer can't be reused as a sealed unit. The problem this chapter solves is connecting across hierarchy boundaries cleanly: how does a component expose a buried analysis interface at its own boundary, so others connect there — not by reaching inside?
The answer is the analysis export (uvm_analysis_export) — the hierarchical forwarder that promotes a sub-component's analysis interface to a parent's boundary. It is the third TLM analysis role, distinct from the other two: a port originates the stream (it calls write()); an imp terminates it (it implements write()); and an export forwards it (it passes a connection through a hierarchy boundary, exposing an inner interface at the outer level). A parent declares an analysis export, connects it to its child's port or imp, and exposes the export at its own boundary — so an external component connects to the parent's export, which forwards the connection inward to the actual imp, without the external component ever reaching into the child. The export preserves encapsulation: connect at the boundary, not into the internals. And a rule follows: every analysis connection chain (port → export → … ) must terminate in an imp — the implementer — because an export only forwards, it does not implement. This chapter is the analysis export: the forwarder role, hierarchical promotion, encapsulation, and the chain that must end in an imp.
What is an analysis export — the hierarchical forwarder that promotes a buried analysis interface to a component's boundary so others connect there, not into the internals — and why must an analysis chain terminate in an imp rather than an export?
Motivation — why a forwarding role exists
The export exists because connecting across hierarchy boundaries must preserve encapsulation. The reasons:
- Subscribers are buried; producers are elsewhere. A scoreboard sits deep inside an environment; a monitor's analysis port is in an agent elsewhere. Connecting them means crossing hierarchy boundaries — and that crossing must not require reaching into either side's internals.
- Reaching into internals breaks encapsulation. If the producer's side connects directly to
env.scoreboard.write_imp, it knows the consumer's internal structure — so a change there breaks the connection, and the consumer can't be reused as a sealed unit. The export lets the consumer expose a clean boundary instead. - A component should present analysis at its own boundary. Just as an agent exposes a clean boundary (Module 14.1), a component with an internal subscriber should expose that subscriber's analysis interface at its own surface — via an export — so external code connects there.
- Forwarding is a distinct role from originating and implementing. Originating (port) and implementing (imp) are the endpoints; forwarding (export) is the middle — promoting a connection through a level without being either endpoint. Three roles, three constructs.
- The chain must reach a real implementer. Exports forward, but something must actually implement
write(). The chain must terminate in an imp — or there's nothing to receive the transactions.
The motivation, in one line: connecting analysis across hierarchy boundaries must preserve encapsulation — so a component exposes a buried subscriber's interface at its own boundary via an export (the forwarder), letting external code connect at the boundary, not into the internals — and the chain of forwarding exports must terminate in an imp (the implementer), because an export forwards but does not implement.
Mental Model
Hold the analysis export as a wall plate that brings an internal connection point to the box's surface:
An analysis export is a wall plate: it brings a connection point from deep inside a sealed box out to the box's surface, so you plug in at the surface without opening the box. The port is the plug that originates the signal; the imp is the device that consumes it; the export is the pass-through that exposes an inner jack on the outer surface. Picture a sealed appliance with a device deep inside it that needs an input (a subscriber — a scoreboard — with an imp that implements
write()). You don't want users opening the box and wiring directly to the internal device — that breaks the seal and couples them to the box's internals. So the appliance has a wall plate on its surface — a jack (the analysis export) — internally wired to the device. A user plugs into the wall plate, and the signal is forwarded inward to the device — without the user ever seeing inside. The wall plate itself is not the device — it doesn't process the signal; it passes it through to the real device. And the chain must reach a real device: a wall plate wired to another wall plate wired to nothing forwards into a void — somewhere at the end, there must be an actual device (an imp) that consumes the input. So the roles are clear: the plug (port) sends; the device (imp) receives and processes; the wall plate (export) forwards — bringing the device's input jack to the surface. To connect across a boundary, you plug into the wall plate, and the export forwards through the box's internal wiring to the device — clean, encapsulated, no opening the box.
So the analysis export is a wall plate that brings an inner jack to the surface: it forwards a connection from a component's boundary to an internal subscriber, so external code connects at the boundary (the wall plate), not by reaching inside (opening the box). The port (plug) originates, the imp (device) implements/terminates, the export (wall plate) forwards. And the chain of forwarding must terminate in an imp — a wall plate forwards to a device, never to nothing. Plug into the surface; let the export forward inward.
Visual Explanation — the three roles and the connection chain
The defining picture is the three roles and the chain: a port originates, exports forward through hierarchy boundaries, and an imp terminates.
The figure shows the three roles and how they chain. A producer's analysis port originates the stream — it calls write(). The connection passes through analysis exports — each forwarding across a hierarchy boundary, promoting the inner interface to the outer level — and terminates in an analysis imp, the component that actually implements write() and processes the transaction (the subscriber logic). The crucial reading is the division of roles: the brand-colored port and success-colored imp are the endpoints — the port originates (sends), the imp terminates (implements and processes) — while the warning-colored exports are forwarders in between, each crossing a hierarchy boundary. A transaction write()-ten at the port travels port → export → export → imp, where it is finally implemented — the exports pass it through, the imp receives it. The essential distinction is that exports forward but do not implement: an export has no write() body of its own — it delegates to whatever it's connected toward, ultimately the imp. This is why the chain must terminate in an imp: the exports are all forwarders, so without an imp at the end, the write() forwards into nothing — there's no implementation to receive it. The practical meaning is encapsulation: external code connects to an export at a boundary (the outer warning node), and the connection is forwarded inward to the imp — without the external code ever naming or reaching the imp directly. The diagram is the anatomy of hierarchical analysis connection: port originates, exports forward across boundaries, imp terminates and implements — three roles, one chain, connected at boundaries, terminating in a real implementer.
RTL / Simulation Perspective — promoting an imp to the boundary
In code, a parent declares an export, connects it to its child's interface, and exposes it — so external code connects to the export, not the inner imp. The code shows the promotion.
// === SUBSCRIBER: implements write() via an analysis IMP (the terminal implementer) ===
class scoreboard extends uvm_scoreboard;
uvm_analysis_imp #(bus_txn, scoreboard) analysis_imp; // the IMP — implements write()
function void write(bus_txn t); check(t); endfunction // the actual implementation
endclass
// === INNER ENV: exposes the scoreboard's imp at ITS boundary via an analysis EXPORT ===
class inner_env extends uvm_env;
scoreboard sb;
uvm_analysis_export #(bus_txn) analysis_export; // the EXPORT — forwards (no write() body)
function void build_phase(uvm_phase phase);
sb = scoreboard::type_id::create("sb", this);
analysis_export = new("analysis_export", this);
endfunction
function void connect_phase(uvm_phase phase);
analysis_export.connect(sb.analysis_imp); // forward the export INWARD to the imp (promotion)
endfunction
endclass
// === OUTER: a producer connects to the EXPORT at the boundary — never reaching into sb ===
function void top_env::connect_phase(uvm_phase phase);
agt.monitor.ap.connect(inner.analysis_export); // port → export (boundary); export forwards to imp
// ✗ NEVER: agt.monitor.ap.connect(inner.sb.analysis_imp); ← reaches inside, breaks encapsulation
endfunctionThe code shows hierarchical promotion via an export. The subscriber (scoreboard) has an analysis imp — the terminal implementer, with the actual write() body (check(t)). The inner env exposes that imp at its own boundary: it declares an analysis_export (the forwarder, which has no write() body) and, in connect_phase, connects the export inward to the imp (analysis_export.connect(sb.analysis_imp)) — promoting the imp's interface up to the env's surface. The outer code then connects the producer to the export at the boundary (agt.monitor.ap.connect(inner.analysis_export)) — and the ✗ comment marks the encapsulation violation: never connect directly to inner.sb.analysis_imp (reaching inside the inner env, coupling to its internals). The shape to carry: an imp implements write() (the endpoint); an export forwards (declared with no write() body, connected inward toward the imp); and a parent promotes a buried imp to its boundary by connecting its export to the imp, so external code connects to the export. The direction of connect matters: the outer port connects to the export, and the export connects to the imp — port → export → imp — each toward the terminal implementer. This is the promotion pattern: expose an inner analysis interface at the boundary via an export, so connections are made at the boundary — encapsulated, reusable, not reaching inside.
Verification Perspective — encapsulation through the boundary
The export's value is encapsulation: external code connects at the boundary, so a component's internals can change (or be reused as a sealed unit) without breaking connections. Seeing the contrast is seeing why exports matter.
The figure contrasts connecting at the boundary (with an export) versus reaching in. With an export (top): external code connects to the component's analysis export at its boundary; the component forwards inward to its subscriber imp, which is hidden. The component's internals can change — the subscriber can be renamed, restructured, replaced — and the connection still works, because external code only knows the boundary export. The component is a sealed, reusable unit. Without an export (bottom): external code connects directly to the buried subscriber imp, coupling to the internal structure — a warning-colored exposed imp. Now a change inside (renaming the subscriber, restructuring) breaks the connection, and the component can't be reused as a sealed unit (every user knows its internals). The verification insight is that the export is what makes the analysis boundary clean — it's the analysis counterpart to the encapsulation principles throughout the methodology (an agent exposing a clean boundary, Module 14.1; an env owning its assembly, Module 15.1). Connecting at the boundary (the export) means the connection depends only on the boundary, not the internals — so the internals are free to change and the component is reusable. Reaching in (the bare imp) means the connection depends on the internals — fragile and non-reusable. The figure is the argument for exports: they promote a buried interface to the boundary precisely so connections are made there — decoupling the connection from the internals, which is exactly what encapsulation (and reuse) require. The export is the clean analysis boundary; reaching past it forfeits the encapsulation.
Runtime / Execution Flow — a write forwarded to the imp
At run time, a write() at the port is forwarded transparently through the export chain to the imp, where it's implemented — all in zero time. The flow shows the forwarding.
The flow shows the write() forwarded transparently to the imp. Call (step 1): the producer calls write() on its analysis port — one call, originating the transfer. Forward (step 2): the connected export receives it and forwards it — it has no write() body of its own, so it passes the call along toward what it's connected to. Reach (step 3): the call reaches the imp — through any number of forwarding exports, all transparent, all zero-time. Implement (step 4): the imp implements write() and processes the transaction — the terminal implementer does the actual work. The runtime insight is that the exports are invisible conduits: the producer's single write() call lands in the imp's write() body, no matter how many exports it passed through — the forwarding is transparent and zero-time. From the producer's view, it called write(); from the imp's view, its write() was called — the exports in between are not observable in the transaction's delivery (they add no delay, no transformation, just routing). This is why exports are purely structural: they affect the connection topology (promoting interfaces to boundaries) but not the runtime behavior of the transfer (the write() arrives at the imp exactly as sent). The imp is where the work happens; the exports just route to it. This also re-grounds the terminate-in-an-imp rule: since exports have no implementation, a chain ending in an export would have the write() forward into a conduit with no device at the end — nowhere to land. The flow is the export's operational nature: a transparent, zero-time conduit that routes a write() from a boundary to the imp that implements it — structure without runtime effect, routing without processing.
Waveform Perspective — the write arrives at the imp unchanged
The export's transparency is visible on a timeline: a write() at the port arrives at the imp in the same time-step, unchanged, however many exports it forwarded through. The waveform shows the zero-time forwarding.
A write forwarded through exports arrives at the imp in the same time-step, unchanged
12 cyclesThe waveform shows the export's transparency. The producer calls write() on its analysis port — port_write pulses (transaction value 7E). The call forwards through the export chain — exp_fwd marks the export passing it along — and arrives at the imp (imp_write) in the same cycle, with the same value (7E). The crucial visual is the vertical alignment: port_write, exp_fwd, and imp_write all pulse on the same cycle, with the same value — the export adds no delay and no transformation. The producer's write() lands in the imp's write() body in zero time. The second transaction (B2) shows the same pattern: port → export → imp, same cycle, unchanged. The picture to carry is that the exports are invisible conduits — the transaction is delivered to the imp exactly as sent, no matter how many exports it passed through. The export's role is purely to route (promote the interface to the boundary, forward the connection); it has no observable effect on the transaction's delivery — same time-step, same value. Reading a waveform across an export chain — does the imp receive what the port sent, in the same cycle, unchanged? — confirms the forwarding is transparent. The zero-time, unchanged arrival at the imp is the signature of the export's conduit nature: it connects a boundary to an imp, transparently, so the producer's write and the imp's write are the same event — the export between them adds structure, not behavior. The imp is where the transaction lands; the export just carried it there.
DebugLab — the analysis chain that terminated in an export, not an imp
Transactions vanishing because an analysis chain forwarded into an export with no imp behind it
A producer's write() calls succeeded — no error at the call — but the intended subscriber never received any transactions: its processing never ran, and (where the tool checked) a connection error appeared at build ("no implementation" / export not connected to an imp). The analysis connection had been wired through exports across hierarchy boundaries, but the transactions vanished — nothing at the end of the chain implemented write(), so the writes forwarded into nothing.
The analysis chain terminated in an export — a forwarder — instead of an imp — the implementer. Exports forward but do not implement write(), so with no imp at the end, the writes had nowhere to land:
✗ the chain TERMINATES in an EXPORT (a forwarder, no implementation):
producer.ap.connect(midA.analysis_export); // port → export
midA.analysis_export.connect(midB.analysis_export); // export → export
// ...and midB's export was never connected to an IMP
// exports FORWARD but don't IMPLEMENT write() → the chain ends at a forwarder → writes vanish
// (build-time: "export has no implementation" / connection error)
✓ the chain TERMINATES in an IMP (the implementer of write()):
producer.ap.connect(midA.analysis_export);
midA.analysis_export.connect(midB.analysis_export);
midB.analysis_export.connect(sb.analysis_imp); // ← terminate in the IMP
// now write() forwards through the exports and LANDS in sb's write() body → processedThis is the unterminated-chain bug — an analysis connection built from forwarders (exports) that never reaches an implementer (an imp), so write() calls forward into a void. The connection was wired correctly in topology — port → export → export, crossing the hierarchy boundaries properly. But the last export was never connected to an imp — so the chain ended at a forwarder. Exports forward but do not implement write() (they have no write() body); they delegate to whatever they're connected toward. With nothing (no imp) at the end, the write() forwards through the exports and reaches a forwarder with nothing beyond it — so it lands nowhere: the transactions vanish, and the subscriber (which does have an imp, but was never connected) never receives them. Most tools flag this at build ("export has no implementation"), but the conceptual error is treating an export as a terminal — it isn't; it's a conduit. The fix is to terminate the chain in an imp: connect the last export to the subscriber's analysis imp (midB.analysis_export.connect(sb.analysis_imp)), so the write() forwards through the exports and lands in the imp's write() body, where it's processed. The general lesson, and the chapter's thesis: an analysis export forwards but does not implement — so every analysis connection chain (port → export → … ) must terminate in an imp, the component that actually implements write(); a chain that ends in an export forwards into nothing (no implementation), and the transactions vanish. Exports are conduits; an imp is the destination — every chain must reach a destination, not end in a conduit.
The tell is writes that succeed but a subscriber that never receives. Diagnose unterminated chains:
- Check that the chain ends in an imp. Trace the connection from the port through the exports; the last connection must be to an analysis imp, not another export.
- Look for build-time "no implementation" errors. Most tools flag an export with no imp behind it; that message points straight at the unterminated chain.
- Confirm the imp is connected, not just present. A subscriber with an imp that was never connected to the chain still receives nothing.
- Remember exports don't implement write(). A chain of exports forwards but processes nothing until it reaches an imp.
Terminate every analysis chain in an imp:
- End the chain at an imp. The forwarding exports must ultimately connect to the subscriber's analysis imp, which implements write().
- Use exports only to forward, imps only to implement. Don't expect an export to receive; it has no write() body — only an imp does.
- Connect toward the implementer. Each connect goes from port or export toward the imp; verify the direction reaches the terminal implementer.
- Heed the no-implementation error. A build-time export-not-connected-to-imp error means a chain ends in a conduit; connect it to an imp.
The one-sentence lesson: an analysis export forwards but does not implement write(), so every analysis connection chain (port → export → …) must terminate in an imp — the component that actually implements write(); a chain ending in an export forwards into nothing (no implementation) and the transactions vanish, so connect the last export to the subscriber's imp.
Common Mistakes
- Terminating a chain in an export. Exports forward but don't implement write(); every chain must end in an imp, or transactions vanish.
- Reaching into a buried imp instead of an export. Connecting directly to
env.sb.impcouples to internals; connect at the component's analysis export to preserve encapsulation. - Confusing the three roles. A port originates (calls write), an imp terminates (implements write), an export forwards (no write body) — using one where another is needed breaks the connection.
- Connecting in the wrong direction. Each connect goes from a port or export toward the imp; reversing it doesn't reach the implementer.
- Expecting an export to process transactions. An export is a transparent conduit with no behavior; the imp is where transactions are received and processed.
- Not exposing an internal subscriber at the boundary. A component with an internal subscriber should declare an export and forward to the subscriber, so external code connects at the boundary.
Senior Design Review Notes
Interview Insights
An analysis export is the hierarchical forwarder of the TLM analysis system — it promotes a sub-component's analysis interface to a parent's boundary. It's the third of three analysis roles, and the three are distinct by what they do with write. A port originates the stream: it calls write to broadcast a transaction. An imp terminates the stream: it implements write, providing the actual body that receives and processes the transaction. An export forwards the stream: it passes a connection through a hierarchy boundary, exposing an inner interface at the outer level, and it has no write body of its own — it just delegates to whatever it's connected toward. So the port and imp are the endpoints, originate and terminate, while the export is the forwarder in between. The purpose of the export is encapsulation across hierarchy boundaries. A subscriber like a scoreboard is often buried deep in the hierarchy, with its imp inside an environment inside another environment. Rather than have an external producer reach directly into env.scoreboard.imp, which couples to the internal structure, the environment declares an analysis export, connects it inward to the scoreboard's imp, and exposes the export at its own boundary. External code connects to the boundary export, which forwards inward to the imp, without ever reaching inside. The mental model is a wall plate: the imp is a device deep inside a sealed box, the export is a wall plate on the surface internally wired to the device, and you plug into the wall plate rather than opening the box. So the export differs from the port and imp by being a forwarder, not an endpoint — it routes connections to boundaries to preserve encapsulation, while the port sends and the imp implements.
Because exports forward but don't implement write, so without an imp at the end, the write calls forward into nothing and the transactions vanish. An analysis connection can be a chain: a producer's port connects to an export, which connects to another export, and so on across hierarchy boundaries. Each export is a forwarder — it has no write body of its own; it just passes the call along toward whatever it's connected to. So the call travels through the chain of exports, but the exports don't process anything; they only route. Somewhere at the end, there must be an imp, which is the component that actually implements write — has the body that receives the transaction and does the work. If the chain ends in an export instead of an imp, the write forwards through all the exports and reaches a forwarder with nothing beyond it, so it lands nowhere. The subscriber never receives the transaction, and most tools flag this at build time with a no-implementation or export-not-connected-to-imp error. The conceptual mistake is treating an export as a terminal, when it's really a conduit. The mental model makes it clear: a wall plate wired to another wall plate wired to nothing forwards into a void; somewhere there must be an actual device that consumes the input. So the rule is that every analysis chain — port through any number of exports — must terminate in an imp. In practice, you ensure the last export connects to the subscriber's analysis imp, so the write forwards through the exports and finally lands in the imp's write body, where it's processed. The exports are the routing; the imp is the destination, and a chain must reach a destination, not end in a conduit. That's why the imp is mandatory at the end and the export alone is never sufficient to terminate a chain.
An export lets external code connect at a component's boundary rather than reaching into its internals, so the component's internal structure stays hidden and can change without breaking the connection. Consider a subscriber buried inside an environment. Without an export, an external producer would connect directly to the buried imp — something like env.scoreboard.analysis_imp. That connection now depends on the environment's internal structure: it knows the scoreboard is there and named that. So if you rename or restructure the scoreboard, or replace it, the connection breaks, and the environment can't be reused as a sealed unit because every user knows its internals. With an export, the environment declares an analysis export at its own boundary and connects it inward to the scoreboard's imp. External code connects to env.analysis_export, the boundary, and the environment forwards inward to the scoreboard. Now the connection depends only on the boundary export, not the internals. The scoreboard can be renamed, restructured, or replaced, and as long as the export still forwards to whatever implements write, the external connection keeps working. The environment is a sealed, reusable unit that exposes a clean analysis boundary. This is the same encapsulation principle that runs through the methodology — an agent exposing a clean boundary, an environment owning its own assembly — applied to analysis connections. The export is what makes the analysis boundary clean: it decouples the connection from the internals by promoting the interface to the surface. So the practical rule is to connect to the boundary export, never reach into component.sub.imp, because reaching past the boundary couples you to internals and forfeits the encapsulation that the export exists to provide. The export is the mechanism for a clean, stable analysis boundary across hierarchy.
No — an export is a transparent, zero-time conduit. It adds no delay and no transformation; the producer's write lands in the imp's write body in the same time-step, with the transaction unchanged, no matter how many exports it passed through. When the producer calls write on its port, the connected export receives it and forwards it immediately, because the export has no implementation of its own — it just passes the call along toward what it's connected to. Any further exports forward likewise. The call reaches the imp, which implements write and processes the transaction, all within the same simulation time-step as the original call. From the producer's perspective, it called write; from the imp's perspective, its write was called; and the exports in between are not observable in the transaction's delivery — they're invisible conduits. This is why exports are purely structural: they affect the connection topology, promoting interfaces to boundaries and routing the connection across hierarchy, but they don't affect the runtime behavior of the transfer. The write arrives at the imp exactly as sent — same time, same value. Reading a waveform across an export chain, you'd see the port's write, the export forwarding, and the imp's write all on the same cycle with the same value. The practical consequence is that you can insert exports freely to build clean hierarchical boundaries without worrying that they'll slow down or alter the analysis stream; the imp is where any actual work happens, and the exports just carry the transaction there. So an export's effect is on structure, not behavior — it routes without processing and forwards without delay, which is exactly what you want from a mechanism whose job is to expose an interface at a boundary rather than to do anything to the data.
You use an analysis export to promote a consumer-side interface — an imp — to a boundary, whereas assigning a port handle up promotes a producer-side interface — a port. They handle the two directions of promotion. On the producer side, a component like an agent has a monitor with an analysis port, and to expose that port at the agent's boundary, a common simple pattern is to assign the handle up — the agent's ap equals the monitor's ap — so external code connects to agent.ap. That works because the port is the originator and you're just making the same originating port accessible at the boundary. On the consumer side, a component has a subscriber with an imp, the implementer, and you want external producers to connect toward it across the boundary. There you declare an analysis export at the component's boundary and connect it inward to the imp, so external code connects to the export, which forwards to the imp. The export is the right construct here because you're promoting a destination, not an origin — you need a boundary connection point that forwards inward to where write is implemented. More generally, exports are the formal mechanism for building connection chains across multiple hierarchy levels, where at each level you forward toward the terminal imp. Assigning a port handle up is a convenient shortcut for the producer side when you simply want the same port visible higher up. The unifying idea is that both promote an interface to a boundary to preserve encapsulation; the export is specifically for forwarding toward an imp through the hierarchy, and it's what you use when the buried interface is a consumer that needs to receive. So you reach for an analysis export when you're exposing a subscriber — something that implements write — at a boundary, and the chain of exports forwards external writes inward to that imp.
Exercises
- Name the three roles. Define port, export, and imp by what each does with write(), and which are endpoints.
- Terminate the chain. Given a port connected through two exports, state what the last export must connect to and why.
- Preserve encapsulation. Explain why connecting to a component's analysis export is better than connecting to its buried imp.
- Promote an interface. Describe how a component exposes an internal subscriber's analysis interface at its boundary.
Summary
- An analysis export (
uvm_analysis_export) is the hierarchical forwarder — the third TLM analysis role — that promotes a sub-component's analysis interface to a parent's boundary. - The three roles are distinct: a port originates (calls
write()), an imp terminates (implementswrite()), and an export forwards (passes a connection through a boundary, with nowrite()body of its own). - The export preserves encapsulation: external code connects at the boundary (the export), which forwards inward to the imp — so the component's internals stay hidden and can change, and the component is a sealed, reusable unit (versus reaching into a buried imp, which couples to internals).
- Forwarding is transparent and zero-time: a
write()lands in the imp'swrite()body in the same time-step, unchanged, however many exports it passed through — exports are invisible conduits, the imp is where work happens. - The durable rule of thumb: use an analysis export to promote a buried subscriber's interface to a component's boundary so others connect there, not into the internals — and terminate every analysis chain (port → export → …) in an imp, the component that actually implements
write(); an export forwards but does not implement, so a chain ending in an export forwards into nothing and the transactions vanish.
Next — TLM FIFOs: ports, exports, and imps connect producers to consumers — but sometimes a consumer needs to receive at its own pace, decoupled in time from the producer. The next chapter covers the TLM FIFO: a buffered channel that sits between a producer and consumer, letting them run at different rates while transactions queue safely in between.