Skip to content

PCIe · Module 8

Vendor IDs — Who Owns the Identity Namespace

Vendor IDs are allocated so identity namespaces cannot collide, which is what lets two vendors use the same Device ID without ambiguity. Plus the hardware problem loaded identity creates: never exposing a half-initialised Vendor/Device pair.

Chapter 8.2 established that a Device ID identifies an implementation within a vendor's namespace, and left the namespace itself undescribed.

That omission is the whole reason a second field exists.

How does PCIe identify the owner of a Function's identity namespace, and how does software use Vendor ID together with Device ID?

1. The Problem Allocation Solves

Suppose identity were a single 16-bit number that each vendor chose for itself.

Two vendors independently pick 0x1234 for unrelated products. A host reads 0x1234 and cannot determine which it has. A driver declaring support for 0x1234 binds to hardware it has never seen and does not understand. There is no mechanism to detect the collision, because nothing in the value says who chose it.

Coordination is unavoidable. Either every vendor's value comes from one central assignment — which does not scale and requires vendors to disclose products before shipping — or the space is partitioned so each vendor can choose freely inside its own part.

PCI took the second approach:

Vendor IDs are allocated by PCI-SIG. Device IDs are chosen by the vendor that owns the Vendor ID.

One number is allocated centrally, once per organisation. The other is chosen locally, as often as the vendor likes. Central coordination scales because it happens once per vendor rather than once per product.

2. The Field

FieldOffsetWidthAccessValue source
Vendor ID00h16 bitsread-onlyallocated by PCI-SIG
Device ID02h16 bitsread-onlychosen by the vendor (Chapter 8.2)

Both are read-only, both sit in the first configuration dword — Vendor ID in the low half, Device ID in the high half.

Why read-only matters more for Vendor ID than for almost any other field: it is the root of the namespace. A writable Vendor ID would let software relabel a Function as belonging to a different organisation, invalidating every identity interpretation downstream of it.

3. The Namespace

Two vendor namespaces. Vendor A owns device IDs 0x1234 and 0x5678. Vendor B owns device IDs 0x1234 and 0x9ABC. The value 0x1234 appears under both vendors and identifies different implementations in each, because the vendor ID scopes the interpretation.Vendor Aallocated namespaceownerDevice ID 0x1234some implementation ofA'sDevice ID 0x5678another of A'sVendor Ba different namespaceownerDevice ID 0x1234unrelated to A's 0x1234Device ID 0x9ABCanother of B'sSame value,different thingthe pair is theidentity12
Figure 1 — two vendor namespaces. Device ID 0x1234 exists under both vendors and refers to entirely unrelated implementations. The values do not collide because each is interpreted only within the namespace its Vendor ID identifies.

Vendor A's 0x1234 and Vendor B's 0x1234 are different implementations with nothing in common. A host reading either value alone learns nothing; reading the pair resolves it completely.

A driver's matching declaration therefore has to name both. A driver claiming "Device ID 0x1234" without qualification would claim hardware from every vendor that happens to use that value.

4. How Software Uses the Pair

The identity pair is the most common driver-matching key, and Chapter 8.2 already established that it is not the only one. Worth restating here because the namespace framing sharpens why the alternatives exist.

The pair is exact. It names one implementation from one vendor. A driver matching on it binds to exactly what it was written for.

The pair is also narrow, and that is its limitation. A driver matching only on pairs must be updated for every new product, even one that behaves identically to its predecessor. Hence the other mechanisms:

  • class information, letting a generic driver serve any device of a kind it understands without knowing the product;
  • subsystem identity, distinguishing implementations built around a common base design — relevant when several vendors ship variants of one silicon;
  • capability presence, where a driver requires an optional feature rather than a specific product;
  • firmware or platform description, which can direct binding independently of configuration space;
  • operating-system policy, including overrides.

5. The Hardware Problem Loaded Identity Creates

Chapter 8.2 §5 listed where an identity value can come from: parameters, fuses, straps, or configuration inputs. It built the parameter case, where the identity is structural and always present.

The loaded case has a problem the parameter case does not. If Vendor ID and Device ID arrive from a fuse or configuration path during initialisation, there is an interval in which they are not yet valid — and Chapter 7.2 established what happens if software reads during such an interval.

Two failure modes, and the second is worse:

Reading before the load completes returns whatever the registers hold — reset values or uninitialised state — and software treats it as a real identity.

Reading during the load can return a mixed pair: the new Vendor ID with the old Device ID, or the reverse. That names an implementation that does not exist. Unlike a wholly-unloaded read, which may be obviously wrong, a mixed pair can look entirely plausible.

The pair must become visible atomically, or not at all.

That requirement is what makes this chapter's RTL a different problem from Chapter 8.2's.

6. RTL — Atomic Identity Load and Freeze

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Captures a Vendor/Device identity pair atomically and
// freezes it. NOT a PCIe requirement — an implementation pattern for the
// case where identity is loaded rather than fixed at synthesis.
module identity_load_freeze (
  input  logic        clk,
  input  logic        rst_n,
 
  // Initialisation source: fuse, strap, or configuration path. Both halves
  // must be presented together — see load_valid.
  input  logic        load_valid,
  input  logic [15:0] load_vendor_id,
  input  logic [15:0] load_device_id,
 
  // Identity output. NOT to be exposed to software before identity_valid.
  output logic        identity_valid,
  output logic [15:0] vendor_id,
  output logic [15:0] device_id,
 
  // Sticky: a load was attempted after the identity was frozen.
  output logic        reload_fault
);
 
  logic        valid_q;
  logic [15:0] vid_q, did_q;
  logic        fault_q;
 
  assign identity_valid = valid_q;
  assign vendor_id      = vid_q;
  assign device_id      = did_q;
  assign reload_fault   = fault_q;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      valid_q <= 1'b0;
      // Reset to a defined value. Not because software should ever see it —
      // the visibility gate prevents that — but because undefined state must
      // not exist anywhere it could reach a read path (Chapter 8.1 §9).
      vid_q   <= 16'h0000;
      did_q   <= 16'h0000;
      fault_q <= 1'b0;
    end else if (load_valid && !valid_q) begin
      // ATOMIC: both halves are captured in the SAME cycle, and valid_q is
      // set in that same cycle. There is no state in which one half has been
      // updated and the other has not, so a mixed pair is unrepresentable
      // rather than merely unlikely.
      vid_q   <= load_vendor_id;
      did_q   <= load_device_id;
      valid_q <= 1'b1;
    end else if (load_valid && valid_q) begin
      // FROZEN. A second load is refused and recorded. Silently accepting it
      // would let identity change under software that has already read it and
      // bound a driver — and the change would be invisible.
      fault_q <= 1'b1;
    end
  end
 
endmodule

Classification: synthesizable.

Register semantics, stated in full:

DimensionBehaviour
Resetidentity_valid clears; the halves take a defined value that software never observes
Read (software)gated — nothing is exposed before identity_valid; see below
Write (software)not possible; these fields are read-only
Byte enablesnot applicable; software cannot write them
Reserved bitsnone
Side effectsfirst load sets valid; a later load sets reload_fault and changes nothing
Hardware may updateonce, on the first load
Software may updatenever

What it teaches — three things:

  1. Atomicity is achieved by construction, not by timing. Both halves and the valid bit are written in one cycle, so the mixed-pair state does not exist in the design. An implementation loading the halves in separate cycles has that state, and its duration depends on the load source rather than on anything the design controls.
  2. Freezing prevents a change nobody would see. Software reads identity once, early, and binds a driver. If identity could change afterwards, the system would be operating on a stale belief with no indication. Refusing and recording is the honest behaviour.
  3. Reset values matter even for state software cannot see. Chapter 8.1 §9's rule: undefined state must not exist anywhere it might reach a read path, because a gate that is later modified or bypassed exposes whatever is there.

Deliberately simplified: one identity pair; the load source is abstract; no distinction between a benign re-load attempt and a malicious one; and the visibility gate is a separate concern shown below rather than integrated.

Production implication: a real implementation must define what happens if the load source never presents a value, integrate the visibility gate with the readiness sequencing of Chapter 7.2 so a Function does not become accessible before its identity is valid, expose reload_fault where an engineer can see it, and handle a multifunction device where each Function's identity may load independently.

The visibility gate is the same shape as Chapter 7.2 §7's: an identity read must not complete before identity_valid. Its correctness rests on the same property — identity_valid is monotonic outside reset, which the freeze guarantees — and §7's P1 asserts exactly that.

7. Assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SVA over identity_load_freeze. Implementation invariants for THIS design —
// not PCIe protocol requirements.
 
// SAFETY — P1: identity_valid is monotonic outside reset. The property a
// visibility gate built on it silently depends on (Chapter 7.2 §7).
property p_valid_monotonic;
  @(posedge clk) disable iff (!rst_n)
  identity_valid |=> identity_valid;
endproperty
a_valid_monotonic : assert property (p_valid_monotonic);
 
// ATOMICITY — P2: the two halves never change independently. THE property of
// this chapter. A design updating them in separate cycles fails this, and the
// window it fails in is exactly when a mixed pair is readable.
property p_halves_change_together;
  @(posedge clk) disable iff (!rst_n)
  ($changed(vendor_id) || $changed(device_id))
    |-> ($changed(vendor_id) && $changed(device_id)) || $past(!identity_valid);
endproperty
a_atomic_pair : assert property (p_halves_change_together);
 
// SAFETY — P3: identity is immutable once valid. Catches a freeze that does
// not hold, which would let identity change under software that already read
// it and bound a driver.
property p_frozen_after_valid;
  @(posedge clk) disable iff (!rst_n)
  identity_valid |=> ($stable(vendor_id) && $stable(device_id));
endproperty
a_frozen : assert property (p_frozen_after_valid);
 
// CORRECTNESS — P4: the captured pair is the pair presented. Catches the two
// halves being swapped on capture — which produces a well-formed identity
// naming a device that does not exist (Chapter 8.2 P3's failure, one layer up).
property p_capture_correct;
  @(posedge clk) disable iff (!rst_n)
  (load_valid && !identity_valid)
    |=> (vendor_id == $past(load_vendor_id) && device_id == $past(load_device_id));
endproperty
a_capture_correct : assert property (p_capture_correct);
 
// SAFETY — P5: a second load changes nothing and is recorded.
property p_reload_refused;
  @(posedge clk) disable iff (!rst_n)
  (load_valid && identity_valid)
    |=> ($stable(vendor_id) && $stable(device_id) && reload_fault);
endproperty
a_reload_refused : assert property (p_reload_refused);
 
// SAFETY — P6: a recorded reload fault is sticky until reset. An attempted
// identity change is a system or integration bug and must stay visible.
property p_fault_sticky;
  @(posedge clk) disable iff (!rst_n)
  reload_fault |=> reload_fault;
endproperty
a_fault_sticky : assert property (p_fault_sticky);
 
// SAFETY — P7: reset clears validity. A design coming out of reset believing
// its identity is valid exposes whatever the halves happen to hold.
property p_reset_clears_valid;
  @(posedge clk)
  !rst_n |=> !identity_valid;
endproperty
a_reset_clears : assert property (p_reset_clears_valid);
 
// SAFETY — P8: the identity never holds unknown state, even before it is
// valid. A gate that is later bypassed or modified would expose it.
property p_never_unknown;
  @(posedge clk) disable iff (!rst_n)
  !$isunknown({vendor_id, device_id, identity_valid});
endproperty
a_no_x : assert property (p_never_unknown);

P2 is the chapter's property, and it is the one that would be omitted by an engineer who had not thought about the mixed-pair case. The obvious properties are "identity is stable" and "identity is correct" — both true and both satisfied by an implementation that loads the halves one cycle apart. P2 is what rules that implementation out, and the failure it catches is a plausible-looking identity that names nothing.

P4 catches the swap. Chapter 8.2 asserted the halves are concatenated in the right order in the read path; P4 asserts they are captured in the right order on the way in. Both are needed — a design can be correct at one boundary and wrong at the other, and the observable symptom is identical.

P1's value is entirely for another module. Nothing in this module needs identity to be monotonic; the visibility gate that consumes it does. Asserting a property for a consumer's benefit is the discipline Chapter 7.2 §8 introduced, and it exists so that a later edit adding a de-validation path fails here rather than silently breaking the gate.

8. Verification

Monitors observe: the load interface, identity_valid, both identity halves, the fault bit, and — where the visibility gate is present — whether any identity read completed and what it returned.

The scoreboard independently models the expected identity from the load stimulus it drove, and independently tracks whether a read should have been permitted. It must not derive "should be valid" from the design's identity_valid.

Scenarios:

  • Load then read. The baseline: identity becomes valid, both halves match what was loaded, in the right places.
  • Read before load. Verify no identity read completes, or that the visibility gate holds it — whichever contract the surrounding design defines. This is Chapter 7.2's early-access scenario applied to identity.
  • Attempted reload after valid. Verify the identity is unchanged and reload_fault sets and stays set (P5, P6).
  • Repeated reload attempts. Verify the fault does not clear and the identity remains stable.
  • Reset then reload. Verify identity_valid clears, a fresh load is accepted, and the fault clears with reset.
  • Reset during a load. Verify no partially captured state survives.
  • Distinct vendor/device combinations. Sweep several pairs, including ones where the two halves are equal and ones where they differ only in the high or low byte — the values most likely to make a swap invisible.
  • The same Device ID under two Vendor IDs. Load (VendorA, 0x1234) into one instance and (VendorB, 0x1234) into another. Verify each reports its own pair. This is §3's namespace argument made executable, and it is the scenario that catches a shared identity source feeding both.
  • Several Functions with independent loads. Verify each Function's identity is independent and that one Function's load does not affect another's — the cross-Function isolation of Chapter 7.6, applied to initialisation rather than to writes.

Coverage should include: load before and after reset; reload attempted in each state; identity values where the halves are equal, and where they differ in only one byte; read attempted before and after valid; and at least two instances with distinct identities.

9. Debugging

Symptom: every Function from one IP instance reports the wrong Vendor ID, while Device IDs are correct

What the correct Device IDs establish — and it is most of the system. The configuration path works, the identity registers are readable, per-Function decode is selecting correctly (or the Device IDs would be wrong too), and the read path returns what it holds.

The asymmetry is the whole clue. One half of the pair is wrong across every Function and the other half is right across every Function. That rules out anything per-Function and anything affecting the pair as a unit.

Candidates:

  • A shared identity source for the Vendor ID. Correct by design — every Function of one vendor's IP should share a Vendor ID — but sourced from the wrong place: a wrong parameter at instantiation, a wrong fuse field, or a default that was never overridden.
  • Fuse or strap field mapping. The Vendor ID is being taken from the wrong bits of the initialisation source. The Device ID mapping is right, so only one field's extraction is wrong.
  • A global default not overridden at integration. The IP shipped with a placeholder that the integrator was supposed to set.

What is not implicated: per-Function decode, the read path, the freeze logic, and the atomicity — all of which would produce a different symptom shape.

The observation that confirms it. Read the identity source directly, before the capture. If the wrong value is already there, the fault is upstream of this module entirely and no RTL change here will fix it.

Symptom: Vendor ID and Device ID appear swapped

Two very different faults produce this, and they are fixed in different places.

A capture-order error — the halves are stored in the wrong registers on load (P4's failure).

A read-path concatenation error — the halves are stored correctly and assembled in the wrong order when read (Chapter 8.2 P3's failure).

A software interpretation error — the hardware is entirely correct and the tool reading it is decomposing the dword wrongly.

10. Common Misconceptions

  • "The Vendor ID uniquely identifies a product." It identifies an organisation. One vendor ships many products, all sharing a Vendor ID and distinguished by Device ID.
  • "The Device ID alone is globally meaningful." It is meaningful only within its Vendor ID's namespace. Two vendors may use the same value for unrelated products with no conflict — §3's point.
  • "Vendor IDs are assigned during enumeration." They are allocated by PCI-SIG to organisations, long before any system boots. Enumeration reads them.
  • "Any company can choose any Vendor ID." Free choice is precisely what allocation prevents. If vendors chose independently, collisions would be undetectable and the namespace would be worthless.
  • "Every Function must have a different Vendor ID." Functions of one vendor's device share a Vendor ID — that is the normal case. What distinguishes them is the Device ID and their Function number (Chapter 7.6).
  • "Driver binding always uses only the Vendor ID." Matching commonly uses the pair, and may also use class information, subsystem identity, capability presence, firmware description, or OS policy. Vendor ID alone would match everything that vendor ships.
  • "The Vendor ID changes when the hierarchy location changes." Identity is a property of the implementation. Location is BDF (Chapter 8.2 §1). Moving hardware changes the second and not the first.
  • "Vendor ID and subsystem vendor identity are the same concept." They are different fields serving different purposes — one identifies the namespace owner, the other distinguishes implementations built around a common base design. This chapter does not teach the second.
  • "A Function's identity is stable from the instant it powers on." For a parameter-driven implementation, effectively yes. For a loaded implementation, there is an initialisation interval during which the identity is not yet valid — which is why §5 and §6 exist.

11. Understanding Check

12. What's Next

Two chapters of identity. A Function can now say what it is and who made it — and it still cannot do anything.

Chapter 8.4 — Command Register takes up the first field software writes: the enable bits that determine which fundamental classes of Function behaviour are permitted. That is where configuration space stops being a description and becomes live control — a register whose value changes what other hardware blocks are allowed to do, and whose bits software may clear at any time.