Skip to content

PCIe · Module 8

Command Register — Permission, Not Storage

Memory Space, I/O Space, and Bus Master Enable are live permission bits that change what other hardware blocks may do. Why Bus Master Enable is not bus ownership, how enables gate acceptance and launch in RTL, and what must happen when software clears one.

Chapter 8.2 and Chapter 8.3 covered fields software only reads. A Function can now state what it is and who made it.

It still cannot move a single byte of traffic.

Which fundamental classes of Function behaviour does software enable through the Command Register, and what must RTL do when those permission bits change?

1. Why Enablement Is Separate From Discovery

Module 7 established a sequence: discover a Function, establish its identity, learn what resources it needs, assign them, and only then let it operate (Chapter 7.1).

The Command Register is the last step made concrete.

Why separation is necessary. A Function that began responding to memory accesses the moment it was discovered would be responding before it had been assigned an address range — so it would be claiming addresses nobody gave it, potentially belonging to something else. A Function that began issuing outbound requests on discovery would be generating traffic before the system had prepared anywhere for it to go.

Discovery must be safe. Operation must be deliberate.

Configuration access works from the moment a Function is reachable (Chapter 7.7) precisely because it is the mechanism used to set everything else up. Operational traffic waits for permission.

2. The Register

The reset state is the load-bearing fact. All three enables come out of reset disabled. A Function that has just been discovered can be identified and configured and cannot yet respond to memory accesses or issue requests. That is the sequence of §1 enforced by hardware reset values, not by software convention.

3. Memory Space Enable

Controls whether the Function responds to memory-space accesses targeting the memory resources it has been assigned.

The dependency that matters. This bit is only useful once a memory resource has actually been assigned and programmed (Chapter 7.8). Enabling it with no valid resource permits the Function to respond to a range it does not have.

But it is not a dependency the specification enforces, and inventing one would be wrong. The two are separate steps that system software sequences: assign, program, then enable. §12's first debugging scenario is what happens when only one of them was done.

What this chapter does not teach. How a memory resource is described, sized, or programmed is Module 9's subject, beginning with Chapter 9.1. This chapter refers only to a memory resource assigned through BAR-related configuration and goes no further.

4. I/O Space Enable

Controls whether the Function responds to I/O-space accesses targeting its assigned I/O resources.

Applicability needs qualifying. I/O space is a legacy address space inherited from PCI (Chapter 1.3). Many modern PCIe Functions implement no I/O resources at all and have no use for this bit; its relevance depends on the Function and on the platform.

Teaching it as though every PCIe endpoint uses I/O space would misrepresent modern practice. Teaching it as though it does not exist would leave a gap in the register. It exists, it is architecturally defined, and whether it matters is a per-Function question.

5. Bus Master Enable

Controls whether the Function is permitted to initiate transactions as a requester — which for most devices is what makes DMA possible.

This is the bit most drivers set explicitly, and the one whose name causes the most confusion.

What it does not do. Setting Bus Master Enable does not start anything. It permits a Function to initiate requests when it has reason to. A device with no queued work generates no traffic whether the bit is set or not.

6. Microarchitecture — Permission Fans Out

The Command Register receives configuration writes and produces three permission outputs. Memory Space Enable gates the MMIO acceptance path. I/O Space Enable gates the I/O acceptance path. Bus Master Enable gates the outbound requester launch path, which is fed by a local DMA work queue.Config write pathbyte-enabled, maskedCommand Registeroffset 04h, 16 bitsMMIO accept gateMemory Space EnableI/O accept gateI/O Space EnableRequester launchgateBus Master EnableInbound accessesaccepted or notLocal work queueheld, never discarded12
Figure 1 — the Command Register's bits are consumed by other blocks. A configuration write updates the register; its outputs then gate whether inbound memory and I/O accesses are accepted and whether the requester may launch outbound transactions. The register is small; its reach is not.

Illustrative organisation — not a required partitioning.

A configuration bit is not merely a register value. It changes behaviour in hardware blocks somewhere else entirely.

That is why this chapter carries three RTL blocks rather than one. The register is the easy part; the gates it drives are where the design decisions live.

7. RTL — The Command Register

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. The three verified Command Register enables.
// Bit positions, access types, reset values for bits 2:0: NORMATIVE.
// The writable mask's treatment of other bits: SCOPED TO THIS MODEL (see §2).
module pcie_command_register (
  input  logic        clk,
  input  logic        rst_n,
 
  // Configuration write, after identity and offset decode selected 04h.
  input  logic        cfg_wr_en,
  input  logic [15:0] cfg_wdata,
  input  logic [1:0]  cfg_be,        // byte 0 = bits 7:0, byte 1 = bits 15:8
 
  output logic [15:0] cfg_rdata,
 
  // Permission outputs, consumed by other blocks.
  output logic        io_space_en,
  output logic        mem_space_en,
  output logic        bus_master_en
);
 
  // Verified bit positions.
  localparam int BIT_IO_SPACE   = 0;
  localparam int BIT_MEM_SPACE  = 1;
  localparam int BIT_BUS_MASTER = 2;
 
  // Only the bits this chapter verified are writable in this model.
  localparam logic [15:0] WRITABLE_MASK = 16'h0007;
 
  logic [15:0] cmd_q;
 
  // Byte-enable merge. A configuration write need not cover the whole word,
  // and all three enables live in byte 0 — so a write that enables byte 1
  // only must leave them completely alone.
  function automatic logic [15:0] apply_be(input logic [15:0] old_v,
                                           input logic [15:0] new_v,
                                           input logic [1:0]  be);
    apply_be = old_v;
    if (be[0]) apply_be[7:0]  = new_v[7:0];
    if (be[1]) apply_be[15:8] = new_v[15:8];
  endfunction
 
  assign cfg_rdata     = cmd_q;
  assign io_space_en   = cmd_q[BIT_IO_SPACE];
  assign mem_space_en  = cmd_q[BIT_MEM_SPACE];
  assign bus_master_en = cmd_q[BIT_BUS_MASTER];
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      // NORMATIVE for bits 2:0: all three enables reset to disabled. This is
      // what makes discovery safe — a Function coming out of reset cannot
      // respond to memory accesses or issue requests until software permits it.
      cmd_q <= 16'h0000;
    end else if (cfg_wr_en) begin
      logic [15:0] merged;
      merged = apply_be(cmd_q, cfg_wdata, cfg_be);
 
      // Update ONLY writable bits; preserve everything else exactly. Masking
      // the whole merged value instead would clear non-writable bits that a
      // fuller implementation might legitimately hold.
      cmd_q <= (cmd_q & ~WRITABLE_MASK) | (merged & WRITABLE_MASK);
    end
  end
 
endmodule

Classification: synthesizable.

Register semantics, stated in full:

DimensionBehaviour
Reset16'h0000 — all three enables disabled (normative for bits 2:0)
Readreturns the stored value
Writeupdates only bits in WRITABLE_MASK, subject to byte enables
Byte enableshonoured; all three enables are in byte 0, so a byte-1-only write cannot touch them
Non-writable bitspreserved, never modified by a write
Side effectsthe outputs change, which changes other blocks' behaviour
Hardware may updateno, in this model
Software may updateyes, at any time, repeatedly

What it teaches — three things:

  1. Preserve-and-merge, not mask-the-result. (cmd_q & ~MASK) | (merged & MASK) keeps non-writable bits untouched. Writing merged & MASK would zero them, which is wrong for any bit that holds a value.
  2. Byte enables are not optional here. All three enables share byte 0. A design ignoring byte enables would let a write intended for byte 1 clear all three — silently disabling a working device.
  3. The reset value is normative and load-bearing. It is what makes discovery safe, and it is why a Function is never accidentally operational before software says so.

Deliberately simplified: only the three verified bits are modelled as writable; there is no hardware-update path; and the register is shown standalone rather than within a bank.

Production implication: a real implementation must implement every architecturally defined bit with its correct access type, hardwire the bits that are not applicable to PCIe to zero, integrate with the surrounding configuration bank of Chapter 8.1, and instantiate per Function.

8. RTL — Gating Inbound Acceptance

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Gates acceptance of inbound memory accesses on
// Memory Space Enable. `resource_hit` is ABSTRACT — Module 9 owns how a
// memory resource is described and decoded.
module mmio_accept_gate (
  input  logic clk,
  input  logic rst_n,
 
  input  logic mem_space_en,
  input  logic resource_hit,      // this Function's assigned range was hit
 
  // Inbound access.
  input  logic req_valid,
  output logic req_ready,
 
  // Toward the Function's operational logic.
  output logic fwd_valid,
  input  logic fwd_ready,
 
  // Observability: an access that WOULD have been ours, refused because the
  // Function is not enabled. Worth exposing — see §12.
  output logic declined_sticky
);
 
  logic decl_q;
  assign declined_sticky = decl_q;
 
  // Acceptance requires the resource to match AND the enable to be set. The
  // enable gates READY — the acceptance decision — rather than the upstream
  // valid, which belongs to the requester and is none of this block's business.
  assign req_ready = resource_hit && mem_space_en && fwd_ready;
  assign fwd_valid = resource_hit && mem_space_en && req_valid;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n)                                          decl_q <= 1'b0;
    else if (req_valid && resource_hit && !mem_space_en) decl_q <= 1'b1;
  end
 
  // WHAT HAPPENS TO A DECLINED ACCESS is deliberately NOT modelled here. The
  // local contract is only that it is not accepted and not forwarded. What
  // response the protocol requires a disabled Function to produce is not
  // stated in this chapter, because it was not verified to the standard the
  // rest of the chapter's normative claims meet.
 
endmodule

Classification: synthesizable.

What it teaches: that a permission bit becomes real at an acceptance decision. mem_space_en gates req_ready and the forwarded fwd_valid — it does not reach back and suppress the requester's valid, which the requester owns.

Why declined_sticky is worth its one flop. An access that hit this Function's assigned range and was refused because the Function was not enabled is the single most useful fact in §12's first debugging scenario. Without it, "MMIO does not work" and "MMIO is disabled" look identical from outside.

Deliberately simplified: resource_hit is abstract; there is no protocol response for a declined access; and no distinction between access types.

Production implication: a real design must produce whatever response the specification requires for an access to a disabled Function, decode the actual assigned resources (Module 9), and handle several resources per Function.

9. RTL — Gating Outbound Launch

The subtlest of the three, because Bus Master Enable can be cleared at any moment — including while a Function is midway through offering work to its outbound requester.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Gates the START of an outbound offer on Bus Master Enable.
// Local contract: while disabled, no NEW offer begins; an offer already in
// progress completes normally; and locally buffered work is never discarded.
module requester_launch_gate #(
  parameter int DESC_W = 64
) (
  input  logic              clk,
  input  logic              rst_n,
 
  input  logic              bus_master_en,
 
  // From the Function's local work queue.
  input  logic              work_valid,
  output logic              work_ready,
  input  logic [DESC_W-1:0] work_desc,
 
  // To the outbound requester.
  output logic              launch_valid,
  input  logic              launch_ready,
  output logic [DESC_W-1:0] launch_desc
);
 
  // TWO PIECES OF STATE, because "buffered" and "offered" are different facts.
  // A design with only one register cannot distinguish them, and that is
  // exactly the ambiguity this module exists to remove.
  logic              have_item_q;     // an item is buffered locally
  logic              offer_active_q;  // launch_valid is asserted for it
  logic [DESC_W-1:0] desc_q;
 
  // Local intake is NOT gated by the enable. Work may be buffered while the
  // Function is disabled — it simply will not be offered onward.
  assign work_ready = !have_item_q || (offer_active_q && launch_ready);
 
  wire accept = work_valid && work_ready;
 
  // An offer may BEGIN only while permitted, and only if one is not already
  // in progress. This is the single transition the enable governs.
  wire begin_offer = bus_master_en && have_item_q && !offer_active_q;
 
  // launch_valid is REGISTERED and never observes launch_ready.
  assign launch_valid = offer_active_q;
  assign launch_desc  = desc_q;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      have_item_q    <= 1'b0;
      offer_active_q <= 1'b0;
      desc_q         <= '0;
    end else begin
      // Completed handshake retires the offer and the item together.
      if (offer_active_q && launch_ready) begin
        offer_active_q <= 1'b0;
        have_item_q    <= 1'b0;
      end
 
      // Intake. Last assignment wins over the retire above, giving
      // back-to-back buffering when an item is taken the same cycle.
      if (accept) begin
        desc_q      <= work_desc;
        have_item_q <= 1'b1;
      end
 
      // Begin an offer. Note this cannot fire in the same cycle a handshake
      // completes, because begin_offer requires !offer_active_q — so a newly
      // buffered item waits one cycle, and no offer is ever skipped.
      if (begin_offer)
        offer_active_q <= 1'b1;
 
      // WHAT IS DELIBERATELY ABSENT: nothing clears offer_active_q when
      // bus_master_en falls. An offer already presented is owned by the
      // handshake, not by the permission bit. Withdrawing it would drop a
      // valid without acceptance — the contract violation this whole track
      // has prohibited since Chapter 6.2.
    end
  end
 
endmodule

Classification: synthesizable.

What it teaches — three things:

  1. "Buffered" and "offered" need separate state. With one register the two facts are indistinguishable, and any gating decision built on it is ambiguous. have_item_q and offer_active_q make the boundary explicit and assertable.
  2. The enable gates a transition, not a signal. It appears only in begin_offer — the queued→offered edge. It touches neither intake nor an offer in progress, which is precisely why an item can be buffered while disabled and an active offer can survive a disable.
  3. Disabling must not destroy state. Clearing the bit asks the Function to stop starting work, not to forget what it was asked to do. Buffered items remain and are offered once permission returns.

Deliberately simplified: one descriptor of holding; no quiesce or drain sequence; no completion tracking; and no interaction with other reasons a Function generates traffic.

Production implication: a real requester needs deeper buffering, a defined quiesce sequence when the enable clears, tracking of outstanding requests so software can tell when the Function is genuinely idle, and coordination with the Function's own reset and power management.

10. Assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SVA over the three modules. Implementation invariants for THESE designs —
// not PCIe protocol requirements.
 
// SAFETY — P1: reset disables all three enables. NORMATIVE reset state, and
// what makes discovery safe.
property p_reset_disables_all;
  @(posedge clk)
  !rst_n |=> (!io_space_en && !mem_space_en && !bus_master_en);
endproperty
a_reset_disabled : assert property (p_reset_disables_all);
 
// CORRECTNESS — P2: only writable bits change. Catches a write path that
// modifies bits it should not, which in a fuller implementation would corrupt
// architecturally defined state.
property p_only_writable_change;
  @(posedge clk) disable iff (!rst_n)
  cfg_wr_en |=> ((cfg_rdata & ~WRITABLE_MASK) == $past(cfg_rdata & ~WRITABLE_MASK));
endproperty
a_mask_respected : assert property (p_only_writable_change);
 
// CORRECTNESS — P3: a write with byte 0 disabled cannot change the enables.
// All three live in byte 0, so ignoring byte enables would let an unrelated
// write to byte 1 silently disable a working device.
property p_byte1_write_preserves_enables;
  @(posedge clk) disable iff (!rst_n)
  (cfg_wr_en && !cfg_be[0]) |=> ($stable(io_space_en) && $stable(mem_space_en)
                                 && $stable(bus_master_en));
endproperty
a_be_respected : assert property (p_byte1_write_preserves_enables);
 
// CORRECTNESS — P4: readback agrees with the permission outputs. Catches the
// outputs and the stored value diverging, which would make every debugging
// observation through configuration space misleading.
property p_readback_matches_outputs;
  @(posedge clk) disable iff (!rst_n)
  (cfg_rdata[0] == io_space_en) && (cfg_rdata[1] == mem_space_en)
    && (cfg_rdata[2] == bus_master_en);
endproperty
a_readback_agrees : assert property (p_readback_matches_outputs);
 
// CORRECTNESS — P5: the register changes only on a write.
property p_no_change_without_write;
  @(posedge clk) disable iff (!rst_n)
  (!cfg_wr_en) |=> $stable(cfg_rdata);
endproperty
a_one_update : assert property (p_no_change_without_write);
 
// SAFETY — P6: no permission output is ever unknown. An X on an enable would
// make acceptance and launch behaviour undefined in simulation and arbitrary
// in silicon.
property p_enables_never_unknown;
  @(posedge clk) disable iff (!rst_n)
  !$isunknown({io_space_en, mem_space_en, bus_master_en});
endproperty
a_no_x : assert property (p_enables_never_unknown);
 
// GATING — P7: no inbound access is accepted while Memory Space is disabled.
// Scoped to THIS gate's acceptance decision — it says nothing about what
// response the protocol requires for a declined access.
property p_no_accept_when_mem_disabled;
  @(posedge clk) disable iff (!rst_n)
  !mem_space_en |-> !(req_valid && req_ready);
endproperty
a_mmio_gated : assert property (p_no_accept_when_mem_disabled);
 
// GATING — P8: no new offer begins while Bus Master Enable is low. This is
// the ONE transition the permission bit governs (§9's callout). Deliberately
// narrow: it is the local queued-to-offered contract, NOT a claim that no
// request exists anywhere in the fabric.
property p_no_new_offer_when_disabled;
  @(posedge clk) disable iff (!rst_n)
  (!bus_master_en && !offer_active_q) |=> !offer_active_q;
endproperty
a_offer_gated : assert property (p_no_new_offer_when_disabled);
 
// SAFETY — P9: an offer already in progress survives the enable being
// cleared. The handshake owns it, not the permission bit. This property is
// deliberately unconditioned on bus_master_en — that is the point.
property p_active_offer_not_withdrawn;
  @(posedge clk) disable iff (!rst_n)
  (offer_active_q && !launch_ready) |=> offer_active_q;
endproperty
a_offer_survives : assert property (p_active_offer_not_withdrawn);
 
// STABILITY — P10: the offered payload is stable while the sink stalls, for
// the same reason. Catches a design that lets desc_q move under an offer.
property p_offer_payload_stable;
  @(posedge clk) disable iff (!rst_n)
  (launch_valid && !launch_ready) |=> (launch_valid && $stable(launch_desc));
endproperty
a_payload_stable : assert property (p_offer_payload_stable);
 
// SAFETY — P11: locally buffered work is never lost. have_item_q clears only
// on a completed handshake, so a disable — or anything else — cannot discard
// an item the queue already handed over.
property p_buffered_work_preserved;
  @(posedge clk) disable iff (!rst_n)
  (have_item_q && !(offer_active_q && launch_ready)) |=> have_item_q;
endproperty
a_work_preserved : assert property (p_buffered_work_preserved);
 
// CONSERVATION — P12: one completed handshake retires exactly one offer, so
// a single buffered item cannot be offered twice.
property p_offer_retires_on_handshake;
  @(posedge clk) disable iff (!rst_n)
  (offer_active_q && launch_ready) |=> !offer_active_q;
endproperty
a_offer_retires : assert property (p_offer_retires_on_handshake);
 
// CONSERVATION — P13: an offer only begins because an item was buffered and
// permission was granted. Catches an offer fabricated from no item.
property p_offer_needs_item_and_permission;
  @(posedge clk) disable iff (!rst_n)
  (!offer_active_q ##1 offer_active_q) |-> $past(have_item_q && bus_master_en);
endproperty
a_offer_justified : assert property (p_offer_needs_item_and_permission);

P3 catches a bug whose symptom is a device that stops working for no visible reason. All three enables share byte 0. A design ignoring byte enables lets a write aimed at byte 1 — for a bit this chapter does not even model — clear Memory Space Enable and Bus Master Enable. The device goes silent, its configuration space still reads fine, and nothing indicates why.

P9 is the one an engineer writing this block for the first time would omit, and note that it is deliberately not conditioned on bus_master_en. The obvious properties are about gating; this one asserts what gating must not reach. An offer already presented is owned by the handshake, so a design that withdraws it when the permission bit clears drops a valid without acceptance — losing an item the interface had committed to, with no error anywhere.

P8's narrowness is deliberate and worth noticing. It asserts exactly what §9's interface guarantees: no new offer begins. It says nothing about intake, which is ungated; nothing about an offer already in progress, which P9 covers; and nothing about whether a request exists in the fabric, which this block cannot see and never promised. Writing the broader property would be the mistake Chapter 7.4 corrected.

11. Verification

Monitors observe: configuration writes with data and byte enables, the register readback, all three permission outputs, the inbound access handshake with resource_hit, the declined indication, and the queue and launch handshakes.

The scoreboard independently models the expected register value from its own byte-enable merge and its own writable mask, and independently predicts whether each inbound access should have been accepted and whether each launch should have occurred. It must not derive expected behaviour from the design's outputs.

Memory Space Enable

  • Resource programmed, enable clear. Verify no access is accepted (P7) and declined_sticky sets.
  • Enable set. Verify accesses are accepted and forwarded.
  • Disable while idle. Verify subsequent accesses are refused.
  • Clear coincident with an incoming access. The race: a write clearing the bit in the same cycle an access arrives. Verify the outcome is deterministic and matches the model.
  • Byte-1-only write. Verify the enable is untouched (P3).
  • Byte-0 write setting and clearing. Verify both directions take effect.
  • Reset while enabled. Verify the enable clears (P1) and accesses stop being accepted.

Bus Master Enable

  • Queue empty, enable set. No launches — the bit permits, it does not initiate.
  • Work queued while disabled. Verify the item IS buffered locally (intake is ungated), that no offer begins (P8), and that the item is preserved (P11).
  • Enable set with work already queued. Verify launches begin and every queued descriptor eventually launches.
  • Disable while idle. Verify clean cessation.
  • Clear the enable during an active offer. The P9 scenario — assert launch_valid, hold launch_ready low, then clear the bit. Verify the offer is not withdrawn, the payload stays stable (P10), and the handshake completes normally when the sink is ready. Then verify no next offer begins while the bit stays low.
  • Repeated set and clear. Toggle across many cycles with work continuously queued. Verify no descriptor is lost or duplicated across the whole run — the strongest single check on this block.
  • Reset with work held. Verify defined behaviour and no stale launch afterwards.

I/O Space Enable

Exercised as for Memory Space where the Function implements I/O resources. For a Function with none, the meaningful test is that the bit is writable and reads back correctly and that nothing else changes — which is itself worth checking, since a bit with no consumer is easy to implement wrongly and never notice.

Register semantics

  • Every byte-enable combination on a write: all four for a 16-bit access.
  • Write all-ones and all-zeros. Verify only writable bits change (P2).
  • Readback of every legal writable combination — all eight values of bits 2:0 — and verify the permission outputs agree in each (P4).
  • Read with no preceding write. Verify the reset value.
  • Back-to-back writes and write followed immediately by read.

Coverage should include: all eight enable combinations; every byte-enable pattern; each enable toggled in both directions; the clear-during-access and clear-during-held-work races; reset in each enable state; and a long randomised toggle run with continuous queued work.

12. Debugging

Symptom: enumeration succeeds, resource assignment looks correct, and MMIO reads never reach the device

What is already established. The Function was discovered, is identified, has an assigned resource, and its configuration space is readable — so all of Module 7 and the identity chapters are exonerated.

Check the enable first, before anything else. It costs one configuration read.

The ladder:

  1. Is Memory Space Enable set? If not, the Function is behaving exactly as specified: refusing accesses it has not been permitted to serve. Nothing is broken. This is the most common cause of this symptom and the cheapest to check.
  2. Was the resource actually programmed into the Function, not merely decided by software (Chapter 7.8)? An assignment made and not written leaves the Function decoding a reset value.
  3. Do the parent windows cover the range? Chapter 7.8 §5 — the access may never reach the Function at all.
  4. Does the Function's decode hit? Observe resource_hit. No hit means the access arrived and was not recognised as this Function's.
  5. Is the enable reaching the decode path? resource_hit asserting with declined_sticky set is the signature of an access that arrived, matched, and was refused for permission — which points back at rung 1 conclusively.

Why declined_sticky earns its flop. It distinguishes the access never arrived from the access arrived and we refused it, and those lead to opposite investigations. Without it both look like silence.

Symptom: the driver queues DMA work and no outbound requests appear

Do not go to the PHY. The Link is demonstrably working, because configuration access to this Function succeeded.

The ladder:

  1. Is Bus Master Enable set? The same first check, for the same reason. A driver that programmed its queue and never set the bit gets exactly this.
  2. Is the Function's own DMA engine enabled? Bus Master Enable is a permission. The Function's device-specific control — which is not in configuration space — determines whether it has work to do.
  3. Is the queue actually populated? Verify from the Function's side rather than from the driver's belief.
  4. Is the requester backpressured? Observe launch_ready. Work held and never taken is a downstream problem, not a permission one.
  5. Is the outbound path functioning? Only now, and only if the first four are clean.

The discriminator between rungs 1 and 2. If Bus Master Enable is clear, items are still buffered but offer_active_q never asserts — work accumulates and nothing is ever offered onward. If the bit is set and the engine is not enabled, there is nothing buffered at all, because nothing produced work. Work present but never offered, versus no work present, separates permission from initiation — and it is one observation.

Symptom: setting one Command Register bit changes another

Prime suspects, in order:

  • Byte enables ignored. A write intended for one byte updates the whole word. All three enables share byte 0, so this affects them together (P3).
  • The writable mask applied to the result rather than merged. merged & MASK zeroes non-writable bits; (old & ~MASK) | (merged & MASK) does not.
  • Bit indexing. An off-by-one in the bit constants, which maps a write of one enable onto its neighbour.
  • A read-modify-write in software that read a stale value, modified one bit, and wrote back — changing others to whatever it had read. The hardware may be entirely correct here, which is why this candidate belongs on the list.

The observation that separates hardware from software. Perform a single-byte write with known data and read the whole register back. If bits outside the written byte changed, the fault is in the design. If they did not, the earlier symptom came from what software wrote, not from how hardware applied it.

13. Common Misconceptions

  • "Bus Master Enable gives the device ownership of the PCIe bus." PCIe has no shared bus to own — every Link is point-to-point. The bit is a permission for the Function to initiate transactions as a requester. The name is inherited from PCI, where a shared bus genuinely existed.
  • "A device is usable as soon as it enumerates." Enumeration establishes identity and resources. Operational traffic requires the relevant enables to be set, and all three come out of reset disabled — deliberately.
  • "Memory Space Enable assigns the address range." It permits the Function to respond to accesses in a range assigned separately (Chapter 7.8, Module 9). Assignment and permission are different steps.
  • "If the resource is programmed, Memory Space must already be enabled." They are independent. A programmed resource with the enable clear is a Function that knows its range and is not permitted to serve it — which is exactly §12's first scenario.
  • "Setting Bus Master Enable starts DMA." It permits the Function to initiate requests. Whether it has anything to initiate is determined by the Function's own control, which is not in configuration space.
  • "Clearing Bus Master Enable discards queued descriptors, or aborts an offer in progress." In the design shown it does neither. Buffered work is preserved and offered once permission returns, and an offer already presented completes under the handshake contract. Clearing is a permission to start, withdrawn — not an instruction to forget or to abort.
  • "Clearing Bus Master Enable guarantees no request from this Function exists anywhere." The shown gate guarantees no new launch from it. Requests already issued may be in flight, and a Function generates protocol activity for other reasons. Over-claiming here is the assertion mistake §9 warns about.
  • "All Command Register bits matter equally to every PCIe Function." I/O Space Enable is irrelevant to a Function implementing no I/O resources, and several inherited PCI bits are not applicable to PCIe at all.
  • "Reserved and non-applicable bits are free for implementation use because software ignores them." They are architecturally defined as not available. Using them creates a Function that misbehaves against software following the specification, and breaks when the bits are later given meaning.
  • "A successful configuration write means operational traffic is now enabled." The write completing means it was accepted and applied per the location's behaviour. Whether traffic flows depends on the resource being programmed, the parent windows, the Function's own controls, and the rest of the path.
  • "The Command Register is device-specific." It is standardised configuration state at a defined offset with defined bit meanings — which is what lets generic enumeration software enable any Function without knowing what it is.

14. Understanding Check

15. What's Next

The Command Register is what software writes to grant permission. Its counterpart is what hardware writes to report condition.

Chapter 8.5 — Status Register covers that side: the flags a Function exposes describing what has happened to it, how those flags are cleared, and why status behaves differently from control in both software and RTL. Where Command is software-controlled permission, Status is software-visible condition — and the distinction shapes both their register semantics and their verification.

Chapter 8.6 — Configuration Header then assembles the whole layout: where these fields sit relative to everything else, the complete per-bit map this chapter deliberately deferred, and how the structure differs between the Function kinds Module 7 spent eight chapters distinguishing.