PCIe · Module 4
Root Complex Topology — Where the Hierarchy Is Anchored
How the host side of a PCIe system is actually organised: root ports as topology anchors, downstream fanout, integrated functions, why one root complex can anchor several independent hierarchies, and why placement decisions determine performance.
Chapter 2.2 defined the Root Complex as a role — the host-side boundary joining the processor and memory domain to one or more PCIe hierarchy paths. That was the right level for Module 2, which was building the cast.
Module 4 asks a different question of the same component:
How is the host side of a real PCIe system actually organised, and what does that organisation determine?
The distinction matters. Knowing what a Root Complex is does not tell you how many paths it anchors, what hangs off each, why a device placed on one path behaves differently from an identical device on another, or why a testbench that models "the host" as a single undifferentiated thing will produce misleading results.
1. Root Ports Are the Anchors
Chapter 2.2 introduced the Root Port as the interface from the Root Complex into a hierarchy — the host-side end of a Link. Topologically, that makes it something more specific:
A Root Port is the anchor of one hierarchy path. Everything reachable through it forms a subtree, and everything in that subtree shares that Root Port's connection to the host side.
Three properties follow, and together they are most of what this chapter is about.
A system typically has several. A host side offering multiple independent device connections does so through multiple Root Ports, not by multiplexing one.
Each anchors an independent subtree. What hangs off Root Port A has no path to what hangs off Root Port B except through the Root Complex itself. They are separate branches of the same tree, meeting only at the root.
The Root Port's connection is a convergence point for its whole subtree. Everything below it traverses it. This is Chapter 2.7's convergence reasoning applied at the topmost level: a Root Port is the upstream segment for everything beneath it, and the deeper that subtree goes, the more traffic converges there.
2. What a Real Host Side Looks Like
Four features of Figure 1 are worth drawing out, because each has consequences.
Subtrees have different depths. Endpoint A is one hop from the root; B and C are two. Both arrangements are ordinary, and the difference is not cosmetic — B and C share the Switch's upstream Link with each other before reaching Root Port 1, while A shares nothing until the Root Complex.
Some Root Ports may be unpopulated. A host side can expose more anchors than a given system uses. An unpopulated Root Port is a real part of the design that happens to have nothing attached in this configuration.
An integrated function sits inside the Root Complex. It is a device-side participant — an Endpoint in the architectural sense from Chapter 2.3 — but it is not reached by traversing a Root Port and a Link the way A, B, and C are. Its relationship to the host side is closer, and §4 develops what that means.
Everything converges at the Root Complex. Traffic from all three subtrees targeting system memory arrives here and continues into a memory subsystem shared with the processor. Whatever separation the topology achieved below, it ends at the root.
3. Placement Determines Behaviour
This is the practically consequential part, and it follows directly from Chapter 2.7's path reasoning.
Two identical devices, one on Root Port 0 and one below the Switch on Root Port 1, are not equivalent:
- The direct-attached device traverses one Link to reach the root. It shares that Link with nothing.
- The switched device traverses two Links, and shares the Switch's upstream Link with every other device below that Switch.
Under light load the difference may be invisible. Under load it is not: the switched device's available throughput depends on what its neighbours are doing, and the direct-attached device's does not.
That gives topology decisions real content:
Which Root Port a device is placed on, and whether it sits behind a Switch, is a performance decision — not merely a wiring convenience.
Practical consequences worth carrying:
- A device with sustained high demand generally benefits from an anchor it does not share, or from sharing with devices whose demand is uncorrelated with its own.
- Devices whose peak demands coincide are poor neighbours behind one Switch, because their convergence point sees the sum.
- Adding a device below an existing Switch adds a claimant to that Switch's upstream Link. It does not add capacity there — the point Chapter 2.9 made about fan-out and capacity being independent variables.
Chapter 4.2 develops endpoint attachment properly and 4.3 the switch side; 4.4 takes multi-level structures. What matters here is that the root-side anchor is where a subtree's relationship to the host is established.
4. Integrated Functions
A device-side function does not have to be reached by traversing a Root Port. Some are integrated into the Root Complex itself.
Architecturally such a function is still a device-side participant — it terminates a path, exposes resources for software to discover and configure, and can originate traffic, exactly as Chapter 2.3 described. It is not a Switch and not a host-side boundary.
What differs is its position, and the honest statement of the difference is narrower than it first appears:
- It is not reached by traversing a Root Port and an external Link, so the path characteristics of an external device do not apply to it in the same way.
- Its relationship to the host side is internal to the Root Complex rather than mediated by an external connection.
5. One Root Complex, Several Independent Hierarchies
Chapter 2.6 introduced the idea that a system may host more than one independently identified hierarchy namespace. Topologically that lands here.
The Root Ports of a host side need not all belong to one identification namespace. A system can be organised so that separate groups of anchors form separate hierarchies, each with its own identifier space — meaning the same position identifier can occur in two of them and refer to different functions.
Two consequences worth holding:
- An identifier unambiguous within a hierarchy is not automatically unambiguous within a system. Naming a device may require naming its hierarchy too.
- How many such namespaces a system has, and how they are organised, is platform-specific. This chapter deliberately makes no claim about a typical arrangement, because there is no useful universal answer.
The topology point is only that the host side is where such divisions are anchored — the Root Ports are what get grouped. Modules 7 and 8 cover how hierarchies are actually discovered and how positions within them are identified.
6. Modelling Role in RTL
Topology matters to hardware because a component's role in it determines what behaviour is legal — and a controller implementation frequently supports more than one role.
// Illustrative internal design classification — NOT a PCIe encoding.
// How a controller might organise role-dependent behaviour internally.
typedef enum logic [1:0] {
ROLE_ENDPOINT = 2'd0, // terminates paths; never forwards
ROLE_ROOT_PORT = 2'd1, // host-side anchor of a hierarchy path
ROLE_SWITCH_PORT = 2'd2 // intermediate; participates in forwarding
} pcie_role_e;// Illustrative synthesizable RTL — internal role gating.
// NOT a PCIe controller. Shows how role constrains which internal
// capabilities may be enabled, and locks the role after initialisation.
module role_gate (
input logic clk,
input logic rst_n,
input logic cfg_valid, // role being programmed during init
input pcie_role_e cfg_role,
input logic init_done, // initialisation complete; role locks
output pcie_role_e role,
output logic role_locked,
// Capability enables derived from role
output logic en_forwarding, // may forward transactions onward
output logic en_terminate, // may terminate paths as a device
output logic en_host_side // may originate host-side configuration
);
pcie_role_e role_q;
logic locked_q;
assign role = role_q;
assign role_locked = locked_q;
// Role determines which capabilities are legal for this instance. A design
// that enabled forwarding on an endpoint-role instance would be able to
// exhibit behaviour its architectural role does not permit.
assign en_forwarding = (role_q == ROLE_SWITCH_PORT);
assign en_terminate = (role_q == ROLE_ENDPOINT);
assign en_host_side = (role_q == ROLE_ROOT_PORT);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
role_q <= ROLE_ENDPOINT; // defined power-on default
locked_q <= 1'b0;
end else begin
// Role is programmable only before initialisation completes; afterwards
// it is fixed for this session. A changing role would invalidate every
// assumption made by logic that consumed it.
if (cfg_valid && !locked_q) begin
role_q <= cfg_role;
end
if (init_done) begin
locked_q <= 1'b1;
end
end
end
endmoduleWhat this models: how an implementation supporting several roles keeps role-dependent capabilities consistent, and why role must stop being mutable once anything has depended on it.
What is deliberately simplified: three roles rather than the full set an implementation may support; capability enables reduced to three booleans; no bridge role; and no representation of how the role is discovered by software, which is Module 8's subject.
What to notice:
- Capabilities are derived from role, not set independently. If forwarding enable and role were separate inputs, an inconsistent combination would be expressible — and a device configured as an endpoint but with forwarding enabled could exhibit behaviour its role does not permit.
- Role locks after initialisation. Logic that consumed the role has already made decisions based on it; a later change would silently invalidate them.
- The reset default is defined, not undefined. An unspecified role at power-on is a real bug class.
What production RTL would additionally require: the normative means of exposing role to software, any additional roles the design supports, and the interaction between role and the capabilities a function advertises.
7. Role Invariants Worth Asserting
// SVA over the illustrative role gating. These are IMPLEMENTATION
// invariants for this design, not normative PCIe requirements.
// P1 — role is stable once locked. Logic downstream has already committed
// to it; a late change silently invalidates those decisions.
property p_role_stable_after_lock;
@(posedge clk) disable iff (!rst_n)
role_locked |=> $stable(role);
endproperty
a_role_stable : assert property (p_role_stable_after_lock);
// P2 — forwarding is only enabled for a forwarding-capable role. Catches
// the inconsistent-configuration bug directly.
property p_forwarding_role_only;
@(posedge clk) disable iff (!rst_n)
en_forwarding |-> (role == ROLE_SWITCH_PORT);
endproperty
a_forwarding_role : assert property (p_forwarding_role_only);
// P3 — capabilities are mutually exclusive in this design. If two were ever
// active together, the instance would be claiming contradictory roles.
property p_capabilities_exclusive;
@(posedge clk) disable iff (!rst_n)
$onehot0({en_forwarding, en_terminate, en_host_side});
endproperty
a_caps_exclusive : assert property (p_capabilities_exclusive);
// P4 — role never changes after lock even across a configuration attempt.
property p_no_late_reconfigure;
@(posedge clk) disable iff (!rst_n)
(role_locked && cfg_valid) |=> $stable(role);
endproperty
a_no_late_reconfig : assert property (p_no_late_reconfigure);P1 and P4 catch the same bug from two directions: a role changing after something depended on it. Simulation can miss this easily, because a directed test typically programs the role once at the start and never attempts a change — the bug requires a late configuration write that a naive testbench never generates.
P2 catches inconsistent configuration, which is exactly the fault that makes a component behave as something it is not — the debugging signature §9 describes.
P3 is a structural check. A design in which two role capabilities can be simultaneously active has an internal contradiction, and $onehot0 states the intent compactly while permitting the all-zero case during reset.
8. Role-Aware Verification
This is where topology reasoning pays off most directly for a DV engineer, and the central point is easy to state and easy to get wrong:
The same stimulus is not valid for every role. A testbench that applies one component's expectations to another will produce failures that are testbench bugs, not design bugs.
For an endpoint-role instance. Verify that it terminates paths: transactions directed at it are consumed, and it never forwards anything onward. Verify it can originate its own traffic toward the host. Model the counterpart as a host-side participant — the environment must behave as something anchoring a hierarchy, not as a peer device.
For a root-port-role instance. Verify host-side origination: it produces the accesses by which software reaches devices, and it terminates traffic arriving from below on behalf of the host side. Model the counterpart as a device-side participant. Note that the traffic directions are the mirror image of the endpoint case, which is why the two environments are not interchangeable.
For a switch-port-role instance. Verify forwarding, which neither of the others does. Stimulus must exercise both directions and must include traffic that is not destined for this port, since deciding what to forward is the responsibility being tested. Path-dependent scenarios matter here in a way they do not for a terminating role.
Topology-aware scenarios worth generating, which is the specific contribution of this chapter:
- Devices on different Root Ports active simultaneously, confirming their subtrees are genuinely independent — an interference between them indicates convergence at the Root Complex or beyond, not in the fabric.
- Devices below one Switch active simultaneously, confirming the shared upstream segment behaves as expected under combined load.
- A device on a direct-attached Root Port versus an identical device behind a Switch, under matched load. The difference is the topology effect, and quantifying it validates that your model of the fabric is right.
- Unpopulated Root Ports, confirming the design behaves sensibly with nothing attached.
Coverage worth defining: each role exercised; role programmed then locked; each subtree active alone and concurrently; devices at differing depths; convergence points driven to saturation.
9. Debugging: When the Topology Model Is Wrong
The failures this chapter uniquely helps with come from a mismatch between assumed and actual topology or role, and they are treacherous because they present as design bugs.
A component behaves as a forwarding element when it should terminate. Transactions arrive and are passed onward instead of consumed. Check role configuration first: an endpoint-role instance with forwarding enabled is inconsistent configuration, not broken logic. P2 catches it directly.
Logic consumes transactions it should have forwarded. The mirror case. A switch-port-role instance behaving as a terminator — again check role before the datapath.
The testbench applies endpoint expectations to a host-side design. Traffic directions are mirrored between the two roles, so a testbench built for one produces systematically wrong stimulus for the other and reports failures everywhere. The signature is many failures rather than a specific one — a design bug rarely breaks everything at once, and a wrong environment usually does.
Two devices interfere that the topology says should not. If they are on different Root Ports, their subtrees are independent, so interference indicates convergence at the Root Complex or in the memory subsystem beyond it — not a fabric problem. If they are behind the same Switch, the interference is expected and the model was wrong.
A device underperforms relative to an identical one elsewhere. Almost always a topology effect rather than a device fault: different depth, different neighbours, different shared segments. Compare paths before investigating the device — Chapter 2.7's reasoning applies directly.
The configuration model does not match the architectural role. A function configured inconsistently with what it structurally is produces confusing behaviour precisely because it is not a logic fault. Verifying role and capability consistency early is cheap and eliminates a large class of misleading symptoms.
10. Common Misconceptions
11. Understanding Check
12. Summary
The Root Complex is not one connection point but a set of anchors. Each Root Port begins an independent hierarchy path, everything reachable through it forms a subtree, and that Root Port's connection is the convergence point for the whole subtree.
Subtrees anchored on different Root Ports are independent — they meet only at the Root Complex. Subtrees may differ in depth, some Root Ports may be unpopulated, and integrated functions sit inside the Root Complex rather than beyond a Root Port: same architectural role as any device-side participant, different position, with the specifics of integration varying by implementation.
Because path determines what a device shares, placement is a performance decision. Identical devices on a direct-attached Root Port and behind a Switch behave differently under load, and adding a device below a Switch adds a claimant to that Switch's upstream Link rather than capacity.
For implementation and verification, role determines what behaviour is legal. A controller supporting several roles should derive capabilities from role rather than allowing inconsistent combinations, and should lock role once anything has depended on it. For DV, the same stimulus is not valid across roles — traffic directions are mirrored between host-side and device-side, and a forwarding role must handle traffic not destined for it. Applying one role's environment to another produces failures everywhere, which is itself the diagnostic signature.
Hold the model: each Root Port anchors an independent path, and where a device sits relative to those anchors determines what it shares and what it contends with.
13. What Comes Next
This chapter took the host side. The rest of Module 4 works outward and downward.
Chapter 4.2 — Endpoints develops how device-side participants attach, to Root Ports and to Switches, and what attachment implies. Chapter 4.3 — Switches takes the switch side of topology, including how a switch's ports are oriented. Chapter 4.4 — Multi-Level Fabrics builds cascaded structures and the reasoning that goes with real depth. Chapter 4.5 — Real System Examples walks concrete desktop and server topologies with all of the above in hand.
Revisit Root Complex for the role this chapter gave topology to, or The PCIe Fabric for the path reasoning that makes placement consequential. Browse the full path on the PCIe tutorials index.