Skip to content

PCIe · Module 8

Configuration Header — One Structure, Two Roles

The header places common fields at predictable offsets and then diverges by Function role. Why Type 0 and Type 1 exist, how software discovers which layout it is reading, and how role-selected decode keeps endpoint and bridge state from aliasing in RTL.

Four chapters have each shown one field with just enough local context to explain it. None showed where they sit relative to each other, and every one deferred the layout to here.

How do the individual configuration fields of Module 8 fit into the standardised header structures that different PCIe Function roles expose?

1. Why One Layout Is Not Enough

An endpoint and a bridge do different jobs, and software needs different things from each.

An endpoint terminates traffic. Software needs to know what address resources it requires so they can be assigned (Chapter 7.8).

A bridge forwards traffic. Software needs to establish which hierarchy regions lie behind it and which address ranges it should forward — the primary, secondary, and subordinate relationships of Chapter 7.4.

Those are incompatible requirements for the same offsets. A bridge has no use for six endpoint resource descriptors; an endpoint has no use for bus-number registers. Reserving space for both in every Function would waste most of the header in every Function.

So the header keeps a common region where every Function is the same, and diverges after it by role.

2. The Verified Layout

3. Where the Divergence Begins

Configuration header structure. A common region from offset 00h to 17h contains Vendor ID, Device ID, Command, Status, Header Type, and Base Address Registers 0 and 1, and is identical in both layouts. From offset 18h the layout diverges: a Type 0 header continues with Base Address Registers 2 through 5, while a Type 1 header contains primary, secondary and subordinate bus numbers.00h–07h — identity+ controlVendor, Device, Command,Status0Eh — Header Typewhich layout follows10h–17h — BAR 0 andBAR 1common to both layouts18h — layoutsdivergesame offset, differentfieldType 0 — BAR 2 toBAR 5endpoint resourcedescriptorsType 1 — busnumbersprimary, secondary,subordinate12
Figure 1 — the header's common region and its two role-specific continuations. Everything up to and including the first two Base Address Registers is identical in both layouts. From offset 18h onward the same addresses carry entirely different fields depending on the Function's role.

Read the figure as a common trunk with two continuations. Identity, control, status, the header-type indication, and the first two resource descriptors are the same wherever you find them. From 18h the structure forks.

Why 10h and 14h are common is worth noticing: a bridge is still a Function with its own configuration space, and it may have its own resources to describe. What it does not need is four more of them, so the space they would have occupied carries the hierarchy fields instead.

4. How Software Knows Which Layout

The header contains the answer to its own interpretation. Header Type at 0Eh, bits 6:0, encodes which layout follows.

The sequence software follows is therefore:

  1. Read the common region, which is safe because it is layout-independent.
  2. Read the Header Type field.
  3. Interpret everything from 18h onward according to that value.

Bit 7 is a separate question sharing a byte. It indicates whether the device position presents more than one Function — Chapter 7.6's multifunction indication. It says nothing about layout, and layout says nothing about multifunction. Two independent facts, packed into one byte for space.

5. Module 8 Assembled

The header is the map that connects everything Module 8 has covered:

RegionWhat it isChapter
00h, 02hwho made it, and what it is8.3, 8.2
04hwhat software permits it to do8.4
06hwhat it has observed and what it provides8.5
0Ehwhich layout the rest of it usesthis chapter
10h onward, Type 0what address resources it needsModule 9
18h1Ah, Type 1which hierarchy regions lie behind it7.4
34hwhere optional feature descriptions beginlater chapters

And the access mechanism that reaches all of it is Chapter 7.7 — identity plus a register offset, where the offset is exactly the column on the left.

That connection is worth making explicit: the offsets in this table are the same values that appear in an ECAM address's low bits. A configuration access to 4:00.1 offset 04h reaches the Command Register of that Function, and the arithmetic that gets it there is Chapter 7.7 §3's.

6. Microarchitecture — Role-Selected Decode

A Function implements one role. Its header therefore has one common bank and one role-specific bank, and the decode must route each offset to the right place.

The structure:

Offset decode determines which region an access falls in — common, or role-specific, or outside both.

A common bank holds identity, control, and status, and is present regardless of role.

A role-specific bank holds either endpoint resource state or bridge hierarchy state, never both.

A response mux gathers the read value from whichever bank was selected.

7. RTL — Role-Selected Header Decode

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Offset decode for a configuration header, with a common
// MODELLED REGION and one role-specific modelled region selected at
// elaboration.
// Offsets and the role divergence point: NORMATIVE (§2).
// Bank contents, interface, and out-of-modelled-region behaviour: illustrative.
//
// SCOPE: each region is ONE abstract bank. Individual registers inside a
// region are not modelled, so `rsp_unsupported` means "outside the regions
// this model represents" — NOT "the specification defines this location as
// unsupported".
module cfg_header_decode #(
  // A Function implements one role. Fixing it here makes the two
  // role-specific banks mutually exclusive in the synthesised design.
  parameter bit IS_BRIDGE = 1'b0
) (
  input  logic        clk,
  input  logic        rst_n,
 
  // Access, after identity decode selected this Function (Chapters 7.5-7.6).
  input  logic        req_valid,
  output logic        req_ready,
  input  logic        req_write,
  input  logic [7:0]  req_offset,     // byte offset within the header
  input  logic [31:0] req_wdata,
  input  logic [3:0]  req_be,
 
  output logic        rsp_valid,
  input  logic        rsp_ready,
  output logic [31:0] rsp_rdata,
  output logic        rsp_unsupported
);
 
  // The divergence point (§2). Below this, both layouts are identical.
  localparam logic [7:0] ROLE_SPLIT   = 8'h18;
  localparam logic [7:0] COMMON_TOP   = 8'h17;
  localparam logic [7:0] T0_TOP       = 8'h27;  // through BAR 5 at 24h..27h
  localparam logic [7:0] T1_TOP       = 8'h1B;  // through 1Ah, dword-aligned
 
  // Modelled-region decode. Exactly one of these can be true, because the
  // role-specific terms are qualified by a parameter fixed at elaboration.
  // These classify an offset into a STRUCTURAL REGION. They do not claim that
  // every offset inside a region is an implemented PCIe register.
  wire in_common = (req_offset <= COMMON_TOP);
  wire in_type0  = !IS_BRIDGE && (req_offset >= ROLE_SPLIT) && (req_offset <= T0_TOP);
  wire in_type1  =  IS_BRIDGE && (req_offset >= ROLE_SPLIT) && (req_offset <= T1_TOP);
 
  wire in_range  = in_common || in_type0 || in_type1;
 
  // Ready when no response is outstanding. Depends on state, never on
  // req_valid — no combinational path from a requester's valid to its ready.
  assign req_ready = !rsp_valid || rsp_ready;
  wire   accept    = req_valid && req_ready;
 
  // ------------------------------------------------------------------
  // Banks — one per MODELLED REGION. Deliberately tiny: this module teaches
  // DECODE ORGANISATION, not the contents of the header. Each bank stands in
  // for the whole set of registers its region would really hold, which is
  // exactly why this model cannot speak about individual offsets.
  // ------------------------------------------------------------------
  logic [31:0] common_bank;   // identity / control / status / header type
  logic [31:0] t0_bank;       // endpoint resource state
  logic [31:0] t1_bank;       // bridge hierarchy state
 
  // Write enables. One per bank, and only the selected modelled region
  // asserts — so a Type 0 write can never reach t1_bank and vice versa. With
  // IS_BRIDGE fixed, one of these two is constant zero and optimises away.
  wire wr_common = accept && req_write && in_common;
  wire wr_t0     = accept && req_write && in_type0;
  wire wr_t1     = accept && req_write && in_type1;
 
  // Read mux: exactly one source, chosen by the same modelled-region decode.
  logic [31:0] rd_mux;
  always_comb begin
    rd_mux = 32'h0000_0000;          // assigned first: no latch, and an
    if (in_common)      rd_mux = common_bank;   // offset outside every
    else if (in_type0)  rd_mux = t0_bank;       // modelled region reads zero
    else if (in_type1)  rd_mux = t1_bank;       // in this model.
  end
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      common_bank     <= 32'h0000_0000;
      t0_bank         <= 32'h0000_0000;
      t1_bank         <= 32'h0000_0000;
      rsp_valid       <= 1'b0;
      rsp_rdata       <= 32'h0000_0000;
      rsp_unsupported <= 1'b0;
    end else begin
      if (rsp_valid && rsp_ready) rsp_valid <= 1'b0;
 
      if (wr_common) common_bank <= req_wdata;
      if (wr_t0)     t0_bank     <= req_wdata;
      if (wr_t1)     t1_bank     <= req_wdata;
 
      if (accept) begin
        rsp_valid       <= 1'b1;
        rsp_rdata       <= in_range ? rd_mux : 32'h0000_0000;
        rsp_unsupported <= !in_range;
      end
    end
  end
 
endmodule

Classification: synthesizable.

Register semantics for the banks shown:

DimensionBehaviour in this model
Resetall banks cleared
Readthe selected bank, or zero if the offset is outside every modelled region
Writethe selected bank only; offsets outside every modelled region write nothing
Byte enablesnot applied — deliberately, see below
Side effectsnone; each bank stands in for a whole region's registers
Hardware may updateno, in this model
Software may updateyes, within a modelled region

Byte enables are deliberately absent here and that is a scoping decision, not an omission by accident. Chapter 8.1 §6 and Chapter 8.4 §7 both build byte-enable merging properly. Repeating it would obscure this module's subject, which is modelled-region decode and role separation. A real header decode composes both.

What it teaches — three things:

  1. The role parameter makes cross-role aliasing unrepresentable. With IS_BRIDGE fixed, in_type0 and in_type1 cannot both be true, and one of the two banks is optimised away entirely. That is stronger than checking for the condition at runtime.
  2. One offset, two meanings, one decode. 18h selects t0_bank in an endpoint and t1_bank in a bridge. The decode is the only thing that makes the same address mean different things, which is why §10's failure is a decode failure rather than a register failure.
  3. An access outside every modelled region needs defined behaviour. It reads zero, writes nothing, and reports rsp_unsupported rather than silently succeeding. In this local model that indication lets a testbench distinguish a region the teaching abstraction does not represent from a represented region that happens to read zero — two situations a bare zero cannot tell apart.

Deliberately simplified: one register per modelled region; no per-offset register presence; no byte enables; no field-level access classes; and the out-of-region response is a local interface contract, not a verified protocol behaviour.

Production implication: a real header decode implements every field at its own offset with its correct access class, composes the byte-enable and W1C machinery of Chapters 8.1 and 8.5, produces whatever response the specification requires for the locations it does not implement, handles the whole configuration space rather than the header alone, and instantiates per Function.

8. Assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SVA over cfg_header_decode. Implementation invariants for THIS design —
// not PCIe protocol requirements. Every property refers to explicit RTL state.
// "Region" throughout means MODELLED REGION, never "implemented register".
 
// EXCLUSIVITY — P1: at most one modelled region is selected. With IS_BRIDGE fixed this
// holds structurally; asserted so that a later edit making the role dynamic
// fails here rather than silently aliasing the banks.
property p_regions_exclusive;
  @(posedge clk) disable iff (!rst_n)
  $onehot0({in_common, in_type0, in_type1});
endproperty
a_one_region : assert property (p_regions_exclusive);
 
// ISOLATION — P2: a Type 0 access never modifies bridge state. The property
// that makes "same offset, different meaning" safe.
property p_t0_write_isolated;
  @(posedge clk) disable iff (!rst_n)
  wr_t0 |=> $stable(t1_bank);
endproperty
a_t0_isolated : assert property (p_t0_write_isolated);
 
// ISOLATION — P3: a Type 1 access never modifies endpoint state.
property p_t1_write_isolated;
  @(posedge clk) disable iff (!rst_n)
  wr_t1 |=> $stable(t0_bank);
endproperty
a_t1_isolated : assert property (p_t1_write_isolated);
 
// ISOLATION — P4: a role-specific write never modifies the common bank, and
// a common write never modifies either role bank. Catches a decode that
// overlaps the divergence point.
property p_common_isolated;
  @(posedge clk) disable iff (!rst_n)
  (wr_t0 || wr_t1) |=> $stable(common_bank);
endproperty
a_common_isolated : assert property (p_common_isolated);
 
property p_role_banks_isolated_from_common;
  @(posedge clk) disable iff (!rst_n)
  wr_common |=> ($stable(t0_bank) && $stable(t1_bank));
endproperty
a_role_isolated : assert property (p_role_banks_isolated_from_common);
 
// LEGALITY — P5: the role parameter governs which role bank can ever be
// written. Catches a decode term that forgot its IS_BRIDGE qualifier — which
// would let an endpoint expose bridge registers.
property p_role_respected;
  @(posedge clk) disable iff (!rst_n)
  IS_BRIDGE ? !wr_t0 : !wr_t1;
endproperty
a_role_respected : assert property (p_role_respected);
 
// SAFETY — P6 (LOCAL TEACHING-MODEL INVARIANT): an offset outside every
// modelled region has no write side effect anywhere. Software probes offsets
// whose contents it does not know, so those probes must be inert. This is a
// property of THIS model's decode, not a published PCIe rule.
property p_out_of_range_inert;
  @(posedge clk) disable iff (!rst_n)
  (accept && !in_range) |=> ($stable(common_bank) && $stable(t0_bank)
                             && $stable(t1_bank));
endproperty
a_oor_inert : assert property (p_out_of_range_inert);
 
// CORRECTNESS — P7 (LOCAL TEACHING-MODEL INVARIANT): an offset outside every
// modelled region is reported as unsupported rather than answered as though
// this model represented it. An ILLUSTRATIVE interface contract — PCIe's own
// response requirement for locations a Function does not implement is not
// published in this chapter.
property p_out_of_range_reported;
  @(posedge clk) disable iff (!rst_n)
  (accept && !in_range) |=> (rsp_valid && rsp_unsupported);
endproperty
a_oor_reported : assert property (p_out_of_range_reported);
 
// SAFETY — P8: a read never writes. Catches an enable derived from `accept`
// without qualifying on req_write — the failure Chapter 8.1 P7 describes,
// which corrupts state during enumeration's read-heavy discovery phase.
property p_read_has_no_write;
  @(posedge clk) disable iff (!rst_n)
  (accept && !req_write) |-> (!wr_common && !wr_t0 && !wr_t1);
endproperty
a_read_no_write : assert property (p_read_has_no_write);
 
// CORRECTNESS — P9: the response comes from the selected modelled region's bank.
property p_response_from_selected;
  @(posedge clk) disable iff (!rst_n)
  (accept && !req_write && in_range) |=> (rsp_rdata == $past(rd_mux));
endproperty
a_read_correct : assert property (p_response_from_selected);
 
// CONSERVATION — P10: one accepted access produces one response, held stable
// until taken.
property p_one_response_held;
  @(posedge clk) disable iff (!rst_n)
  (rsp_valid && !rsp_ready) |=> (rsp_valid && $stable(rsp_rdata)
                                 && $stable(rsp_unsupported));
endproperty
a_response_held : assert property (p_one_response_held);

P5 is the property that would be omitted by someone who trusted the parameter. With IS_BRIDGE fixed, wr_t0 in a bridge is structurally impossible — so the property looks redundant. It is worth writing precisely because the redundancy is conditional on the qualifier being present. A decode term written as (req_offset >= ROLE_SPLIT) && (req_offset <= T0_TOP) — with the !IS_BRIDGE accidentally dropped — compiles, synthesises, and exposes endpoint resource registers on a bridge at the offsets where its bus numbers should be. P5 fires immediately; nothing else in the design would.

P2 and P3 are the pair that make the shared offset safe. 18h addresses different state in the two roles, so the isolation must hold in both directions. Asserting only one leaves the other role's aliasing undetected, and the two roles are usually verified in separate testbench configurations where a single-direction property looks sufficient.

P6 and P7 are local teaching-model invariants, and it matters that they are read that way. They are true of this decode by construction. Neither is a PCIe protocol requirement, and neither says anything about which configuration-space locations the specification defines.

What P7 buys inside the model. The explicit unsupported indication lets the testbench distinguish a region this teaching abstraction does not represent from a represented region that happens to read zero. A bare zero conflates the two, and a scoreboard that cannot tell them apart cannot check the region map at all. That is an illustrative interface contract — the value is in what it makes observable to verification, not in any claim about protocol-level response behaviour.

9. Verification

Monitors observe: the access handshake with offset, direction, and data; the modelled-region decode signals; all three banks; and the response with its unsupported indication.

The scoreboard independently models the modelled-region map from its own copy of §2's offsets and its own knowledge of the configured role — not by reading the design's decode terms. A scoreboard that reuses in_type0 agrees with the design about a missing IS_BRIDGE qualifier, which is exactly P5's bug.

The environment must instantiate both roles. Every scenario below runs twice, once with IS_BRIDGE = 0 and once with IS_BRIDGE = 1, because the failures are asymmetric and a single-role testbench cannot see them.

Type 0 configuration

  • Common-region access. Each common offset, read and write. Verify the common bank responds in both roles.
  • Type 0 role-specific access. Offsets 18h through 27h. Verify t0_bank responds and t1_bank is untouched (P3's counterpart).
  • Access at exactly 18h. The divergence point. Verify it selects the endpoint bank — the single most important offset in the chapter.
  • Access to a Type 1-only offset. With IS_BRIDGE = 0, an access at 18h must reach t0_bank, never t1_bank (P5).

Type 1 configuration

  • Common-region access. Verify identical behaviour to the Type 0 case — the common region is genuinely common.
  • Type 1 role-specific access. Offsets 18h1Bh. Verify t1_bank responds.
  • Access at 18h. Verify it selects the bridge bank. Comparing this against the Type 0 result is the test that proves the role decode works, and it is impossible to run without both configurations.
  • Access above 1Bh but below 27h. In a bridge these are outside the modelled Type 1 region. Verify they are reported unsupported (P7) and write nothing (P6) — a Type 0-shaped access aimed at a bridge. Read the check correctly: it confirms the model's own region boundary, not that PCIe leaves those offsets undefined in a Type 1 header.

Boundaries and common cases

  • Offset 17h and 18h. The last common offset and the first role-specific one. An off-by-one in COMMON_TOP shows here and nowhere else.
  • The top of each modelled role region. 27h for Type 0, 1Bh for Type 1, and one beyond each.
  • Offset outside every modelled region. Verify unsupported reported, no bank changed.
  • Read with plausible data on the write bus. Verify nothing is written (P8).
  • Reset with all banks written. Verify all clear.
  • Response backpressure. Verify stability (P10) and no new access accepted.

Coverage should include: both role configurations; every modelled-region boundary and boundary ± 1; read and write in each region; offsets outside every modelled region in each role; and reset from each distinct bank state.

10. Debugging

Symptom: identity and Command read correctly, but resource fields are missing or wrong

What the correct common fields establish — and it is substantial. Configuration access works end to end (Chapter 7.7), identity decode selected the right Function (Chapters 7.5–7.6), and the common region of the header decodes and responds correctly.

What that leaves. Only the role-specific region — which is to say, only the decode from 18h onward.

Candidates:

  1. The Function's role is not what software thinks. Read Header Type at 0Eh and compare against expectation. If it says Type 1 and software expected an endpoint, everything from 18h is being interpreted with the wrong map and the mismatch is explained completely.
  2. The role parameter is wrong in the design. The Function implements the wrong layout. Header Type will say so, which makes rung 1 the same observation.
  3. The decode is routing to the wrong bank. Header Type is correct, and the region terms select the wrong region — P5's failure, most often a dropped IS_BRIDGE qualifier.
  4. The region boundaries are off by one. COMMON_TOP or ROLE_SPLIT wrong, so an offset lands in the neighbouring region.
  5. The fields exist and are unprogrammed. Not a header problem at all — Chapter 7.8 territory.

The observation that resolves nearly all of this: read Header Type first. It is one access, it is in the common region so it is reachable regardless of the role, and it tells you which map software should be using. If it disagrees with expectation, the investigation is over before it starts.

Symptom: bus-number registers appear where endpoint resource fields were expected

This is the diagnostic case for the whole chapter, and it is nearly self-diagnosing once the layout is understood.

18h is Base Address Register 2 in a Type 0 header and the Primary Bus Number in a Type 1 header. Reading plausible-looking bus numbers at an offset where a resource descriptor was expected means software and hardware disagree about the role.

Which direction the disagreement runs:

  • Hardware is a bridge, software expected an endpoint. Header Type reads 1. Software's interpretation is wrong — or it never read Header Type and assumed.
  • Hardware is an endpoint with a bridge decode. Header Type reads 0 while the decode routes 18h to bridge state. That is P5's failure and it is a design bug.

The one observation that separates them. Compare Header Type against the decode's actual behaviour. If Header Type says Type 0 and 18h behaves like a bus-number register, hardware is inconsistent with its own advertisement — and that is the fault, regardless of what software expected.

Why this symptom is worth recognising on sight. The values look wrong in a specific, recognisable way: small integers where an address range should be. An engineer who knows the layout diverges at 18h reads that immediately as a role mismatch rather than as corrupted resource data.

11. Common Misconceptions

  • "Every PCIe Function has the same configuration header layout." The common region is identical; from 18h the layout depends on the Function's role. That divergence is the reason the header type field exists.
  • "Type 0 means PCIe generation zero." It identifies a header layout, used by endpoint-like Functions. It has nothing to do with generations, link speeds, or lane widths.
  • "Type 1 means the second Function." It identifies the bridge-like header layout. Function numbering is a separate coordinate entirely (Chapter 7.6).
  • "The configuration header and configuration space are the same thing." The header is the standardised front portion. Configuration space extends beyond it, including the optional feature descriptions the capability pointer at 34h leads to.
  • "Type 0 and Type 1 determine link speed or width." They determine the meaning of header offsets. Generation and width are physical-layer properties (Chapter 6.1) with no relationship to header layout.
  • "Bridge and endpoint role-specific fields can be read with the same interpretation." 18h is a resource descriptor in one layout and a bus number in the other. Reading either with the wrong map produces plausible values that mean nothing.
  • "A header is best implemented as one large case statement over every offset." Region decode with separate banks keeps the roles structurally isolated and lets the unused bank optimise away. A flat case merges everything into one namespace, which is where the aliasing P2–P5 catch comes from.
  • "Header Type only matters to software." RTL must decode 18h onward according to the role it implements. A design whose decode disagrees with the Header Type it advertises is inconsistent with its own configuration space — §10's second scenario.

12. Understanding Check

13. Module 8 Complete

Six chapters, from a mechanism to a structure.

8.1 Configuration Mechanism — what configuration space is, why it is not MMIO, why every Function has its own context, and how register-access behaviour is built.

8.2 Device IDs — implementation identity, and why it is not the Device Number in BDF.

8.3 Vendor IDs — namespace ownership, and never exposing a half-initialised identity pair.

8.4 Command Register — permission that fans out into other blocks, and what may and may not be withdrawn when it is revoked.

8.5 Status Register — condition rather than control, sticky by necessity, with an explicit rule for the same-cycle conflict.

8.6 Configuration Header — the structure all of it sits in, and the role divergence that makes the structure self-describing.

The through-line: configuration space is a standardised software interface implemented by ordinary hardware, and almost every field behaves differently from its neighbours. Module 8's real contribution is the discipline of asking, for every location, who owns this value, what does a write do, and what happens when both sides act at once.

14. What's Next

The header reserves room for resource descriptors and this chapter said nothing about what they contain — only where they sit and which role has how many.

Chapter 9.1 — What BARs Are opens Module 9 with the mental-model correction that the rest of the module depends on: a Base Address Register is not the device's memory. It is a configuration-space control point associated with an addressable window, and the difference between describing where a window lives and containing what the window holds is the distinction most misunderstandings of PCIe resources come from.