Skip to content

PCIe · Module 8

Device IDs — What a Function Is, Not Where It Is

The Device ID identifies an implementation within a vendor's namespace; the Device Number in BDF identifies a location in the current hierarchy. Why the two must never be confused, how read-only identity is actually built, and why a correct Device ID does not guarantee correct driver binding.

Chapter 8.1 described configuration space as a standardised interface and deliberately showed none of it. This chapter opens the first field — and it opens with a trap.

Module 7 spent three chapters teaching Bus : Device . Function. Configuration space now presents a field called Device ID.

They have nothing to do with each other.

What does the PCIe Device ID identify, and why must it not be confused with the Device Number in BDF?

1. The Collision of Terms

Put them side by side, because the names are close enough that the distinction has to be made deliberately.

BDF Device NumberDevice ID field
Answerswhere is this Functionwhat is this Function
Kind of valuea position within a busan implementation identity
Where it livesin the identifier used to address configuration accessesin the Function's configuration space
How it is establishedby the Function's position in the hierarchy, discovered during enumeration (Chapter 7.5)by the implementation, and readable once the Function is accessible
If the card moves to another slotit can change — the position changedit does not change — the implementation did not
Width5 bits, conventional interpretation16 bits

The last two rows are the ones worth remembering. Move a card to a different slot behind a different switch and its BDF may become something else entirely; it is still the same product, and it still reports the same Device ID.

2. The Field

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

The two occupy the first dword of every Function's configuration space, Vendor ID in the low half and Device ID in the high half. That adjacency is not decorative — §3 is about why they belong together.

Read-only is a normative property, not an implementation convenience. Software cannot change what a Function reports itself to be, which is what makes identity trustworthy enough to bind a driver to.

This chapter shows no other field. The complete header layout, including how the structure differs between Function kinds, is Chapter 8.6's subject.

3. Identity Is a Pair

A Device ID alone identifies nothing.

0x1234 means whatever the vendor who owns that namespace decided it means. A different vendor may use 0x1234 for something completely unrelated, and there is no conflict — because the two values live in different namespaces.

Vendor ID names the namespace. Device ID names an implementation inside it. Software interprets the pair.

Chapter 8.3 takes up the namespace side: who allocates Vendor IDs, why allocation is necessary at all, and what it means for two vendors to use the same Device ID value. This chapter needs only that the Device ID is not globally unique by itself and was never intended to be.

4. What Software Does With It

Software reads the identity pair during enumeration and uses it to decide what this Function is and what should drive it.

Driver selection. A driver declares which identities it supports; the system matches what it found against those declarations.

Device family recognition. A vendor commonly assigns related Device IDs across a product family, so software can recognise a variant it has not seen before as belonging to a family it knows.

Inventory and diagnostics. Identity is what appears in device listings, logs, and support tooling — which is why an incorrect Device ID surfaces as "the OS reports the wrong device."

5. Where the Value Comes From

The specification defines the field's software-visible behaviour. It says nothing about how an implementation produces the value.

Common approaches, all equally valid:

  • Hard-coded RTL parameters, fixed at synthesis.
  • Fuse or one-time-programmable values, read during initialisation so one design can ship as several products.
  • Configuration inputs or straps, sampled at reset.
  • Generated IP parameters, where a configurable core is instantiated with an identity chosen by the integrator.

6. RTL — Read-Only Identity

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. The first configuration dword of one Function: Vendor ID in
// the low half, Device ID in the high half. Both read-only.
// Offsets/widths/access: NORMATIVE. Values and structure: illustrative.
module cfg_identity_ro #(
  // Arbitrary placeholders. PCI-SIG allocates Vendor IDs; the vendor chooses
  // Device IDs. Neither value is specified by PCIe.
  parameter logic [15:0] VENDOR_ID = 16'hABCD,
  parameter logic [15:0] DEVICE_ID = 16'h1234
) (
  input  logic        clk,
  input  logic        rst_n,
 
  // Access, after identity and offset decode have already selected this dword.
  input  logic        req_valid,
  output logic        req_ready,
  input  logic        req_write,
  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
);
 
  // THE READ PATH IS A CONSTANT. There is no storage behind these fields, so
  // there is nothing for a write to reach and nothing to reset. Implementing
  // read-only as "a register whose write is suppressed" would cost 32 flops
  // and create a write path that a later edit could accidentally enable.
  //
  // Vendor ID occupies the low half (offset 00h) and Device ID the high half
  // (offset 02h) of this dword.
  localparam logic [31:0] IDENTITY_DWORD = {DEVICE_ID, VENDOR_ID};
 
  // 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;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      rsp_valid <= 1'b0;
      rsp_rdata <= '0;
    end else begin
      if (rsp_valid && rsp_ready) rsp_valid <= 1'b0;
 
      if (accept) begin
        // A write is ACCEPTED and COMPLETES NORMALLY. It simply has no effect.
        // Read-only does not mean "refuse the access" — refusing would be a
        // different externally visible behaviour, and software writes to
        // locations whose behaviour it does not yet know.
        //
        // req_wdata and req_be are deliberately unused. Byte enables cannot
        // change the outcome because there is nothing to partially update.
        rsp_valid <= 1'b1;
        rsp_rdata <= IDENTITY_DWORD;
      end
    end
  end
 
endmodule

Classification: synthesizable.

Register semantics, stated in full — the discipline Chapter 8.1 established:

DimensionBehaviour
Resetnothing to reset; the value is structural
Readreturns {DEVICE_ID, VENDOR_ID} unconditionally
Writeaccepted, completes normally, no effect
Byte enablesirrelevant — no partial update is possible
Reserved bitsnone in this dword; both halves are defined
Side effectsnone
Hardware may updateno
Software may updateno

What it teaches — three things:

  1. Read-only is a property of the paths, not of a register. The read path produces a value; the write path does not exist. That is cheaper and safer than a suppressed write.
  2. A write to a read-only location is not an error. It is accepted and ignored, and the completion is correct. Software probes configuration space before knowing what it contains.
  3. Byte enables cannot rescue a read-only write. There is no partial-update case to get wrong, which is worth noticing because Chapter 8.4 has a location where byte enables matter enormously.

Deliberately simplified: one dword; offset decode is assumed already done; no distinction between access sizes beyond the byte enables it ignores; and the identity is a parameter rather than loaded.

Production implication: a real configuration block implements the whole space with defined behaviour for every location including unimplemented ones, sources identity from wherever the product actually keeps it, and — in a multifunction device — instantiates this per Function with different values, which is why §8 tests cross-Function isolation.

7. Assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SVA over cfg_identity_ro. Implementation invariants for THIS design —
// not PCIe protocol requirements.
 
// SAFETY — P1: a write never changes what is read back. The defining property
// of a read-only field, and the one a driver's trust in identity rests on.
property p_write_has_no_effect;
  @(posedge clk) disable iff (!rst_n)
  (accept && req_write) |=> (rsp_rdata == IDENTITY_DWORD);
endproperty
a_write_inert : assert property (p_write_has_no_effect);
 
// SAFETY — P2: no byte-enable combination changes the value. Stated
// separately because a partially-writable identity would be far worse than a
// fully writable one — it would corrupt half the pair and still look valid.
property p_no_partial_write;
  @(posedge clk) disable iff (!rst_n)
  (accept && req_write && req_be != 4'b0000) |=> (rsp_rdata == IDENTITY_DWORD);
endproperty
a_no_partial : assert property (p_no_partial_write);
 
// CORRECTNESS — P3: a read returns the configured identity, with Vendor ID in
// the low half and Device ID in the high half. Catches a concatenation in the
// wrong order — which produces a well-formed dword naming a device that does
// not exist, and is a real and recurring bug (Chapter 8.3 §10).
property p_read_returns_identity;
  @(posedge clk) disable iff (!rst_n)
  (accept && !req_write) |=> (rsp_rdata[15:0]  == VENDOR_ID
                           && rsp_rdata[31:16] == DEVICE_ID);
endproperty
a_read_correct : assert property (p_read_returns_identity);
 
// STABILITY — P4: the response is stable while the requester stalls.
property p_response_stable;
  @(posedge clk) disable iff (!rst_n)
  (rsp_valid && !rsp_ready) |=> (rsp_valid && $stable(rsp_rdata));
endproperty
a_response_stable : assert property (p_response_stable);
 
// CONSERVATION — P5: one accepted access produces one response.
property p_one_response;
  @(posedge clk) disable iff (!rst_n)
  accept |=> rsp_valid;
endproperty
a_one_response : assert property (p_one_response);
 
// OWNERSHIP — P6: no access is accepted while a response is outstanding.
property p_single_outstanding;
  @(posedge clk) disable iff (!rst_n)
  (rsp_valid && !rsp_ready) |-> !req_ready;
endproperty
a_single_outstanding : assert property (p_single_outstanding);
 
// SAFETY — P7: the exposed identity never contains unknown state. An X
// reaching a configuration response becomes an arbitrary real value in
// silicon, which software then treats as a genuine device identity.
property p_identity_never_unknown;
  @(posedge clk) disable iff (!rst_n)
  rsp_valid |-> !$isunknown(rsp_rdata);
endproperty
a_no_x : assert property (p_identity_never_unknown);

P1 is what makes identity trustworthy. If configuration writes could alter what a Function reports itself to be, no driver-matching decision would be reliable and no device listing would mean anything. It is the property the whole chapter rests on, and in a parameter-driven implementation it holds structurally — which is exactly why that implementation is preferable.

P3 catches a bug that testing frequently misses. Concatenating the halves in the wrong order produces a perfectly well-formed 32-bit value. If a test only checks "the dword is non-zero" or compares against a value computed the same wrong way, it passes. What surfaces later is a system reporting an unrecognised device — and the investigation goes to the driver's matching table, not to a concatenation two layers down.

P7 exists because the failure mode is silent in silicon. In simulation an X propagates and is visible. In hardware the flop powers up as something, and software reads it as a real identity.

8. Verification

Monitors observe: the access handshake with its direction, write data and byte enables, and the response with its data.

The scoreboard independently computes the expected dword from its own knowledge of the configured identity and its own understanding of the half-ordering — not by reading the design's IDENTITY_DWORD. A scoreboard that uses the design's concatenation agrees with the design about P3's bug.

Scenarios:

  • Read after reset. The configured identity, correct halves. The baseline every other test depends on.
  • Write with each byte-enable combination. All sixteen for a 32-bit access. Verify the value is unchanged in every case, and that each write completes normally rather than erroring.
  • Write of all-ones and of all-zeros. The two values most likely to be visibly wrong if a write leaked through.
  • Back-to-back reads. A new access presented in the cycle the previous response is taken.
  • Response backpressure. Hold rsp_ready low for varying durations. Verify stability (P4) and that no new access is accepted (P6).
  • Read immediately following a write. Verify the write left nothing behind.
  • Reset during an outstanding response. Verify no stale response is presented afterwards.
  • Multiple Functions with different identities. Instantiate several with distinct DEVICE_ID values, read each, and verify each returns its own. Then write one Function's identity location and re-read every Function — the cross-Function isolation check from Chapter 7.6, applied to identity.

Coverage should include: every byte-enable pattern on a write; read and write at this location; response backpressure of zero, one, and many cycles; reset in each response state; and at least two Functions with distinct identities.

9. Debugging

Reference scenario: the OS sees the Function but binds the wrong driver, or reports an unexpected device.

The Function is present and its configuration space is readable, so Module 7 is entirely exonerated — identity, forwarding, configuration access, and the response path all work. What is wrong is the content of the identity or what was done with it.

The ladder:

  1. Is the BDF the one you think? A correct identity read from the wrong Function is not a mismatch — it is a different device answering. Confirm which Function was addressed before questioning the value.
  2. Did the access reach the intended Function? Chapter 7.6's decode. In a multifunction device this is where "the wrong device" most often originates.
  3. Is the Device ID value correct? Compare what was read against what the design was configured with. A mismatch here is a hardware-side fault and the investigation stops descending.
  4. Is the pair correct? A correct Device ID with a wrong Vendor ID names a completely different thing, because the namespace changed. Check both halves and check their order (P3).
  5. Is the software matching table correct? If the hardware reports what it should, the mismatch is in the declaration of what drivers support — or in one of §4's other mechanisms deciding the outcome.
  6. Is Function context isolation intact? In a multifunction device, one Function reporting another's identity points at the decode or addressing failures Chapter 8.1 §10 describes.

10. Common Misconceptions

  • "The Device ID is the Device Number from BDF." They share a word and nothing else. One identifies an implementation; the other identifies a position in the current hierarchy. This is the chapter's central correction.
  • "The Device ID tells software where the device is connected." It carries no topology information at all. Location is what BDF describes (Chapters 7.4–7.6).
  • "A Device ID is globally unique." It is unique only within its vendor's namespace. Two vendors may use the same value for unrelated products with no conflict, which is Chapter 8.3's subject.
  • "The Device ID is assigned during enumeration." Enumeration reads it. The value comes from the implementation and exists before any host looks. Bus numbers are assigned during enumeration; identity is not.
  • "The Device ID is writable." It is read-only — normatively so. Writes complete and have no effect, which is what allows software to trust identity for driver matching.
  • "Moving the device to another slot changes its Device ID." Moving it may change its BDF, because its position changed. Its identity is a property of the implementation and moves with it.
  • "One physical device exposes one Device ID." A multifunction device has one configuration space per Function (Chapter 7.6), and its Functions may report different Device IDs — commonly they do, since they present different things to software.
  • "A correct Device ID guarantees correct driver binding." Binding may also involve class information, subsystem identity, capability presence, firmware or platform description, and OS policy. A correct identity with the wrong driver bound is a real and explicable outcome.
  • "Device ID and subsystem identity are the same field." They are different fields with different purposes. Subsystem identity is not taught here, and treating them as interchangeable produces matching logic that fails on hardware built around a common base design.

11. Understanding Check

12. What's Next

The Device ID has been treated throughout as a value inside a namespace, with the namespace itself left undescribed.

Chapter 8.3 — Vendor IDs takes that up: who owns an identity namespace, why the values are allocated rather than chosen freely, what it means for two vendors to use the same Device ID, and how software interprets the pair. It also carries a hardware problem this chapter did not have — what a Function exposes while its identity is still being loaded, which is where the fuse-based implementations of §5 acquire a real correctness requirement.