Skip to content

PCIe · Module 24

UVM Architecture — A Network of Responsibilities, Not a Container of Components

Five chapters produced monitors, properties, a scoreboard, coverage and a VIP boundary. Composing them fails on three things: who owns an observation, who agrees about reset, and what the transaction key is when there is more than one Function.

Module 24 has produced parts. 24.2 bound properties to interfaces, 24.3 built a transaction scoreboard, 24.4 built subscribers, 24.5 drew a boundary around a vendor agent.

This chapter composes them, and composition is where verification environments actually fail — not in any single component, but in the assumptions each one made about the others.

1. Sources, Scope, and What the UVM Track Owns

2. A Network of Responsibilities

The useful definition of an architecture is negative. Not "what each component does" — that is a component list — but "what each component is forbidden to do."

ComponentOwnsMust not
sequenceintent — what traffic to generateknow how the driver pins it
driverinterface stimulusdecide what to send next
monitorobservation, published immutablycompute expectations, check anything
VIP adaptervendor→project translationperform scoreboard work (24.5 §8)
predictorexpected behaviour from inputsread DUT internal state
scoreboardcomparison and conservationdrive stimulus, own configuration
coveragewhat was exercisedinfluence expected results
environmentcomposition and configurationcontain protocol logic
virtual sequencecross-agent coordinationreach into another agent's internals

The right-hand column is where §15's five defects live. A monitor that mutates its object, a coverage subscriber that writes, a predictor that survives a reset the scoreboard honoured — each is a component doing something the table forbids, and none of them is detectable by looking at that component alone.

And one architectural consequence worth stating early: because the monitor may not check and the scoreboard may not observe, the observation object is the contract between them. §4 is about making that object project-owned and immutable, and §15 measured 96,106 immutability violations when it is not.

3. One Agent Per PCIe Boundary

A PCIe endpoint verification environment has more than one interface, and cramming them into one agent is the most common structural error.

BoundaryWhat it carriesAgent
PCIe protocol sideTLPs to and from the link partnerprotocol agent (often a VIP, 24.5)
configuration / register sideBAR writes, Command register, capability setupcontrol agent
application / DMA sidedescriptors, payload streams, doorbellsapplication agent
interrupt indicationMSI/MSI-X observation (19.3)passive monitor
reset and clock controlreset assertion, link-up indicationreset agent

Why separate agents rather than one. Each boundary has its own clock, its own protocol, its own active/passive decision, and its own reset behaviour — and merging them means one component's timing model constrains all of them.

The specific PCIe reason is stronger than the general one. A scenario like §10's — configure a BAR, enable bus mastering, post a descriptor, let the host service the reads — spans three of these boundaries in a defined order. With one agent, that ordering is buried inside a sequence; with separate agents and a virtual sequence, the ordering is the scenario and it is visible.

And correlation happens downstream, not in the agents. The predictor and scoreboard see all the streams and can relate a descriptor to the read requests it produced. A monitor that tries to correlate has taken the scoreboard's job (§2).

4. Normalized Transactions Everywhere

Every monitor in §3 publishes project-owned types, and nothing else crosses a component boundary:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
pcie_req_item     a request observed on the protocol side
pcie_cpl_item     a completion observed on the protocol side
pcie_cfg_item     a configuration or register access
pcie_app_item     an application-side descriptor or payload event
pcie_link_event   a link-state or reset observation

Three properties make composition work, and each one is a §15 finding.

They are project-owned. A vendor type flowing past the adapter locks the environment to a product (24.5 §5).

They are immutable after publication. §15 measured 96,106 immutability violations from a single subscriber that modified the object it received — and because analysis ports broadcast the same handle, the modification is visible to every other subscriber, including the scoreboard, which then compares against altered data.

And they carry the epoch. Every item is stamped with the reset generation in force when it was observed (§8), so a component can reject a stale observation without knowing anything about who produced it.

5. The Environment

A layered PCIe UVM environment. A test creates sequences on a virtual sequencer, which coordinates a control agent, an application agent and a PCIe protocol agent. All three connect to the device under test. Each agent's monitor publishes normalized observations. The control and application observations feed an independent predictor, which produces expected transactions. The protocol observations feed the scoreboard, which compares against the predictor's expectations, and separately feed coverage subscribers and an event timeline. A reset coordinator distributes a reset epoch to the predictor, scoreboard and coverage. One environment configuration object feeds every agent and the coverage model. Assertions bind directly to the device interfaces.testvirtual sequencercontrol agentapplication agentprotocol agentDUTpredictorscoreboardcoveragereset coordinatorenvironment configbound assertions12
Figure 1 — the composed environment. A test drives a virtual sequencer that coordinates three agents at three PCIe boundaries. Their monitors publish normalized, immutable, epoch-stamped observations. An independent predictor consumes configuration and application events to compute expectations; the scoreboard compares them against protocol observations; coverage subscribes to the same observations separately. Assertions bind directly to the device's interfaces, and one configuration object feeds every component.

Five things to read out of the figure.

Three agents at three boundaries (§3), each connected to the DUT and each publishing separately.

The predictor consumes control and application events, not protocol events. It computes what should appear on the protocol side from what the test asked for — which is what makes it independent (§7). An arrow from prot to pred would destroy that.

The scoreboard has exactly two inputs: expectations from the predictor and observations from the protocol monitor. That is 24.3's structure, wired.

The reset coordinator fans out to all three model components. §15 measured what happens when it reaches only some of them — the predictor retains entries the scoreboard flushed (§8).

And configuration is a single source feeding the protocol agent and the coverage model (§6), so a DUT built with 8 Tags and a coverage model expecting 16 cannot happen.

6. Configuration Is a Single Source of Truth

7. The Predictor Is Independent by Construction

A predictor computes what the DUT should produce. Its value depends entirely on not asking the DUT.

In this environment the independence is structural rather than a matter of discipline (§5's figure): the predictor's inputs are the control and application streams — what software configured and what work was posted — and it never subscribes to the protocol monitor. It therefore cannot read the DUT's outputs to form an expectation, because they are not connected to it.

What a PCIe predictor actually computes, and each piece comes from a live chapter:

FromIt predictsRule
a BAR writewhich addresses the DUT should claim23.2 §3
a descriptorhow many requests, of what size22.4 §3, §4
MRRS + transfer sizethe read request count22.4 §4
the Tag countthe maximum outstanding depth20.3 §3
bus-master enablewhether requests are permitted at all9.5 §8

And one thing it must not predict. Chapter 13.3 §1 established that the Completer chooses the fragmentation of a read's answer. A predictor that computes an expected Completion count is guessing, and the scoreboard's byte-coverage model exists precisely so it does not have to (24.3 §5).

8. Reset Coordination

9. Multi-Function and Multi-Port Scaling

10. The Virtual Sequence — A Real Scenario

A scenario that spans three agents in a defined order is what a virtual sequence is for. Here is one, in full, and note what it does not include.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1. control agent   : size and program a BAR                    (9.4, 9.5)
2. control agent   : enable Memory Space and Bus Mastering     (9.5 §8)
3. application     : post a DMA descriptor                     (20.2)
4. protocol agent  : host side services the resulting reads    (12.1)
5. observe         : completions return, descriptor retires    (23.3 §6)
6. control agent   : read the status record                    (20.6 §13)

Three architectural points are embedded in that ordering.

Step 2 before step 3 is a real dependency, not a formality: a descriptor posted before bus mastering is enabled produces no traffic, and a virtual sequence that races them produces an intermittent test failure with no DUT bug behind it (§13).

Step 4 is the VIP doing its job as a participant (24.5 §2) — the sequence does not need to model completions itself.

And step 6 reads the status record, not the interrupt. Chapter 20.6 §13 established that the record is authoritative and the notification is a hint; a scenario that waits on the interrupt has made an unreliable path load-bearing.

What this scenario deliberately omits: any error. No bad CRC, no credit violation, no malformed packet. 24.7 owns injection, and a scenario chapter that started injecting would consume it.

11. Code — The Environment

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// UVM. 1 -- Environment configuration. ONE source of truth (§6).
// It describes the TESTBENCH, not the device's register map.
class pcie_env_cfg extends uvm_object;
  `uvm_object_utils(pcie_env_cfg)
 
  pcie_role_e       dut_role        = PCIE_ROLE_ENDPOINT;
  int unsigned      num_functions   = 1;
  int unsigned      num_ports       = 1;          // §9 multi-port scaling
  int unsigned      max_tags        = 8;
  int unsigned      max_payload     = 256;        // operative MPS
  int unsigned      max_read_req    = 512;        // MRRS
  bit               has_credit_visibility = 0;    // 24.1 §4
  uvm_active_passive_enum ctl_active  = UVM_ACTIVE;
  uvm_active_passive_enum app_active  = UVM_ACTIVE;
  uvm_active_passive_enum prot_active = UVM_ACTIVE;
  bit               scoreboard_enable = 1;
  bit               coverage_enable   = 1;
 
  function new(string name = "pcie_env_cfg"); super.new(name); endfunction
 
  function void report(uvm_component ctx);
    `uvm_info("ENV_CFG", $sformatf(
      "role=%s funcs=%0d ports=%0d tags=%0d mps=%0d mrrs=%0d sb=%0b cvg=%0b",
      dut_role.name(), num_functions, num_ports, max_tags,
      max_payload, max_read_req, scoreboard_enable, coverage_enable), UVM_LOW)
  endfunction
endclass
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// UVM. 2 -- The transaction key, scaled (§9).
// §15: dropping the Function raised key collisions from 21,028 to 37,209.
class pcie_key extends uvm_object;
  `uvm_object_utils(pcie_key)
  bit [2:0]  port_num;        // §9 multi-port
  bit [2:0]  function_num;
  bit [15:0] requester_id;
  bit [7:0]  tag;
 
  function new(string name = "pcie_key"); super.new(name); endfunction
 
  // A VALUE key. Never an object handle (24.3 §3).
  function string str();
    return $sformatf("p%0d/f%0d/%04h/%02h", port_num, function_num, requester_id, tag);
  endfunction
endclass
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// UVM. 3 -- Normalized observations (§4). Immutable after publication,
// epoch-stamped at the source (§8). One family for the whole environment.
class pcie_obs_base extends uvm_sequence_item;
  pcie_key   key;
  bit [31:0] epoch;           // stamped by the MONITOR, not the consumer
  time       obs_time;
  `uvm_object_utils(pcie_obs_base)
  function new(string name = "pcie_obs_base"); super.new(name); endfunction
endclass
 
class pcie_cfg_item extends pcie_obs_base;      // control-side observation
  bit [11:0] reg_offset;
  bit [31:0] reg_data;
  bit        is_write;
  `uvm_object_utils(pcie_cfg_item)
  function new(string name = "pcie_cfg_item"); super.new(name); endfunction
endclass
 
class pcie_app_item extends pcie_obs_base;      // application-side observation
  bit [63:0] host_addr;
  bit [31:0] length;
  bit        is_read;
  bit [15:0] cookie;
  `uvm_object_utils(pcie_app_item)
  function new(string name = "pcie_app_item"); super.new(name); endfunction
endclass
 
class pcie_link_event extends pcie_obs_base;    // link-state observation
  bit        operational;
  bit        reset_asserted;
  `uvm_object_utils(pcie_link_event)
  function new(string name = "pcie_link_event"); super.new(name); endfunction
endclass
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// UVM. 4 -- Agent skeleton, one per PCIe boundary (§3).
// Passive means NO driver is built -- not a driver that chooses not to
// drive, which is 24.5 §3's contention failure in another form.
class pcie_agent_base #(type ITEM = pcie_obs_base) extends uvm_agent;
  `uvm_component_utils(pcie_agent_base#(ITEM))
 
  uvm_analysis_port #(ITEM) ap;
  pcie_env_cfg              cfg;
  int unsigned              port_index = 0;
 
  function new(string name, uvm_component parent);
    super.new(name, parent); ap = new("ap", this);
  endfunction
 
  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    if (!uvm_config_db#(pcie_env_cfg)::get(this, "", "cfg", cfg))
      `uvm_fatal("AGENT", "no environment configuration")
    // Sequencer and driver exist ONLY when active.
    if (get_is_active() == UVM_ACTIVE) begin
      // sequencer and driver construction here
    end
    // The monitor is always built -- observation is never optional.
  endfunction
endclass
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// UVM. 5 -- Monitor publication contract (§2, §4).
// Publishes on the HANDSHAKE, stamps the epoch, and creates a FRESH object
// every time. §15: reusing the object produced 96,106 immutability
// violations, visible to every subscriber because the port broadcasts a handle.
class pcie_app_monitor extends uvm_monitor;
  `uvm_component_utils(pcie_app_monitor)
  uvm_analysis_port #(pcie_app_item) ap;
  virtual pcie_app_if vif;
  pcie_reset_coordinator rstc;
  int unsigned port_index, function_num;
 
  function new(string name, uvm_component parent);
    super.new(name, parent); ap = new("ap", this);
  endfunction
 
  task run_phase(uvm_phase phase);
    forever begin
      @(posedge vif.clk);
      if (vif.rst_n && vif.desc_valid && vif.desc_ready) begin
        pcie_app_item t = pcie_app_item::type_id::create("t");   // FRESH
        t.key = pcie_key::type_id::create("k");
        t.key.port_num     = port_index;
        t.key.function_num = function_num;
        t.key.requester_id = vif.requester_id;
        t.key.tag          = '0;                 // assigned when the request issues
        t.host_addr = vif.desc_addr;
        t.length    = vif.desc_len;
        t.is_read   = vif.desc_is_read;
        t.cookie    = vif.desc_cookie;
        t.epoch     = rstc.current_epoch();      // STAMPED AT THE SOURCE (§8)
        t.obs_time  = $time;
        ap.write(t);
        // The monitor does NOT check, compare, or compute anything (§2).
      end
    end
  endtask
endclass
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// UVM. 6 -- THE RESET COORDINATOR (§8).
// One epoch, one opinion. §15: without the epoch check, 4,692 stale
// observations were accepted across 200,000 steps.
class pcie_reset_coordinator extends uvm_component;
  `uvm_component_utils(pcie_reset_coordinator)
 
  uvm_analysis_port #(pcie_link_event) ap;
  protected bit [31:0] m_epoch;
  uvm_event            reset_ev;
 
  function new(string name, uvm_component parent);
    super.new(name, parent);
    ap = new("ap", this);
    reset_ev = new("reset_ev");
    m_epoch = 0;
  endfunction
 
  function bit [31:0] current_epoch(); return m_epoch; endfunction
 
  // Called by the reset monitor. Every model component observes ONE event
  // and applies ITS OWN declared flush policy (§8).
  virtual function void on_reset_observed();
    m_epoch++;
    `uvm_info("RSTC", $sformatf("reset observed; epoch -> %0d", m_epoch), UVM_LOW)
    reset_ev.trigger();
  endfunction
endclass
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// UVM. 7 -- THE PREDICTOR. Independent BY CONSTRUCTION (§7): its inputs
// are configuration and application events. It is not connected to the
// protocol monitor and therefore cannot read the DUT's outputs.
class pcie_predictor extends uvm_component;
  `uvm_component_utils(pcie_predictor)
 
  uvm_analysis_imp_cfg #(pcie_cfg_item, pcie_predictor) cfg_imp;
  uvm_analysis_imp_app #(pcie_app_item, pcie_predictor) app_imp;
  uvm_analysis_port    #(pcie_expect_entry)             exp_ap;
 
  pcie_env_cfg            cfg;
  pcie_reset_coordinator  rstc;
  protected bit           m_bus_master_enabled;
  protected bit [63:0]    m_bar_base;
  protected bit [63:0]    m_bar_mask;
 
  function new(string name, uvm_component parent);
    super.new(name, parent);
    cfg_imp = new("cfg_imp", this);
    app_imp = new("app_imp", this);
    exp_ap  = new("exp_ap",  this);
  endfunction
 
  // ---- control side: track what software configured ----------------
  virtual function void write_cfg(pcie_cfg_item t);
    if (t.epoch != rstc.current_epoch()) return;          // stale (§8)
    if (t.is_write) begin
      case (t.reg_offset)
        12'h004: m_bus_master_enabled = t.reg_data[2];    // 9.5 §8
        12'h010: m_bar_base = {32'd0, t.reg_data};        // 9.4, 9.5
        default: ;
      endcase
    end
  endfunction
 
  // ---- application side: predict the protocol traffic --------------
  virtual function void write_app(pcie_app_item t);
    int unsigned limit, n_req, i, remaining, chunk;
    if (t.epoch != rstc.current_epoch()) return;
    // A descriptor posted with bus mastering disabled produces NOTHING.
    if (!m_bus_master_enabled) begin
      `uvm_info("PRED", "descriptor posted with bus mastering disabled: expect no traffic",
                UVM_MEDIUM)
      return;
    end
    // 22.4 §3/§4: the request count follows from the configured limit.
    limit     = t.is_read ? cfg.max_read_req : cfg.max_payload;
    remaining = t.length;
    i = 0;
    while (remaining > 0) begin
      pcie_expect_entry e = pcie_expect_entry::type_id::create("e");
      chunk = (remaining > limit) ? limit : remaining;
      e.key       = t.key;
      e.address   = t.host_addr + (t.length - remaining);
      e.requested = chunk;
      e.received  = 0;
      e.epoch     = t.epoch;
      exp_ap.write(e);
      remaining -= chunk;
      i++;
    end
    // NOTE: it predicts the REQUESTS, never the number of Completions --
    // the Completer chooses that (13.3 §1, §7 here).
  endfunction
 
  virtual function void on_reset(bit [31:0] new_epoch);
    // The predictor's DECLARED policy: drop expectations, keep configuration
    // state only if the DUT's reset contract preserves it. Stated, not assumed.
    m_bus_master_enabled = 0;
    `uvm_info("PRED", "reset: expectations dropped, config state cleared", UVM_LOW)
  endfunction
endclass
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// UVM. 8 -- Environment build and connect (§5's figure, wired).
class pcie_env extends uvm_env;
  `uvm_component_utils(pcie_env)
 
  pcie_env_cfg              cfg;
  pcie_reset_coordinator    rstc;
  pcie_agent_base #(pcie_cfg_item) ctl_agent;
  pcie_agent_base #(pcie_app_item) app_agent;
  pcie_vip_wrapper          prot_agent[];        // one per port (§9)
  pcie_predictor            predictor;
  pcie_txn_scoreboard       scoreboard;
  pcie_coverage             coverage;
  pcie_virtual_sequencer    vseqr;
 
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
 
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    if (!uvm_config_db#(pcie_env_cfg)::get(this, "", "cfg", cfg))
      `uvm_fatal("ENV", "no environment configuration");
    cfg.report(this);
 
    rstc      = pcie_reset_coordinator::type_id::create("rstc", this);
    ctl_agent = pcie_agent_base#(pcie_cfg_item)::type_id::create("ctl_agent", this);
    app_agent = pcie_agent_base#(pcie_app_item)::type_id::create("app_agent", this);
 
    prot_agent = new[cfg.num_ports];             // §9 multi-port scaling
    foreach (prot_agent[i])
      prot_agent[i] = pcie_vip_wrapper::type_id::create($sformatf("prot_agent_%0d", i), this);
 
    predictor = pcie_predictor::type_id::create("predictor", this);
    if (cfg.scoreboard_enable)
      scoreboard = pcie_txn_scoreboard::type_id::create("scoreboard", this);
    if (cfg.coverage_enable)
      coverage = pcie_coverage::type_id::create("coverage", this);
    vseqr = pcie_virtual_sequencer::type_id::create("vseqr", this);
  endfunction
 
  function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    // Predictor sees CONTROL and APPLICATION only -- never the protocol
    // monitor. That missing connection IS the independence (§7).
    ctl_agent.ap.connect(predictor.cfg_imp);
    app_agent.ap.connect(predictor.app_imp);
 
    foreach (prot_agent[i]) begin
      if (cfg.scoreboard_enable) begin
        prot_agent[i].req_ap.connect(scoreboard.req_imp);
        prot_agent[i].cpl_ap.connect(scoreboard.cpl_imp);
      end
      if (cfg.coverage_enable)
        prot_agent[i].req_ap.connect(coverage.analysis_export);
    end
    if (cfg.scoreboard_enable)
      predictor.exp_ap.connect(scoreboard.exp_imp);
 
    vseqr.ctl_seqr  = ctl_agent.get_sequencer();
    vseqr.app_seqr  = app_agent.get_sequencer();
    vseqr.prot_seqr = prot_agent[0].get_sequencer();
  endfunction
endclass
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// UVM. 9 -- Virtual sequencer. HANDLES ONLY, to the sequencers a scenario
// needs (§10). Not a directory of every component's internals.
class pcie_virtual_sequencer extends uvm_sequencer;
  `uvm_component_utils(pcie_virtual_sequencer)
  uvm_sequencer_base ctl_seqr;
  uvm_sequencer_base app_seqr;
  uvm_sequencer_base prot_seqr;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
endclass
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// UVM. 10 -- The virtual sequence of §10. The ORDERING is the scenario.
class pcie_dma_read_vseq extends uvm_sequence;
  `uvm_object_utils(pcie_dma_read_vseq)
  `uvm_declare_p_sequencer(pcie_virtual_sequencer)
 
  rand bit [63:0] host_addr;
  rand int unsigned length = 4096;
 
  function new(string name = "pcie_dma_read_vseq"); super.new(name); endfunction
 
  task body();
    bar_program_seq   bar_seq  = bar_program_seq::type_id::create("bar_seq");
    cmd_enable_seq    cmd_seq  = cmd_enable_seq::type_id::create("cmd_seq");
    dma_descriptor_seq desc_seq = dma_descriptor_seq::type_id::create("desc_seq");
 
    // 1-2: configuration FIRST. A descriptor posted before bus mastering is
    // enabled produces no traffic -- and racing them is §13's intermittent
    // failure with no DUT bug behind it.
    bar_seq.start(p_sequencer.ctl_seqr);
    cmd_seq.start(p_sequencer.ctl_seqr);
 
    // 3: post the work only once the configuration is OBSERVABLY in effect.
    desc_seq.host_addr = host_addr;
    desc_seq.length    = length;
    desc_seq.start(p_sequencer.app_seqr);
 
    // 4-5: the protocol agent services the resulting reads as a participant
    // (24.5 §2). The sequence does not model completions itself.
  endtask
endclass
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// UVM. 11 -- End-of-test drain and check (§14).
// The scoreboard does NOT hold an objection forever; the test decides when
// stimulus is done, and the drain gives outstanding work a bounded chance
// to complete before residue is judged.
class pcie_base_test extends uvm_test;
  `uvm_component_utils(pcie_base_test)
  pcie_env     env;
  pcie_env_cfg cfg;
 
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
 
  task run_phase(uvm_phase phase);
    pcie_dma_read_vseq vseq = pcie_dma_read_vseq::type_id::create("vseq");
    phase.raise_objection(this, "stimulus");
    phase.phase_done.set_drain_time(this, 10us);   // bounded quiesce
    vseq.start(env.vseqr);
    phase.drop_objection(this, "stimulus");
  endtask
 
  function void check_phase(uvm_phase phase);
    super.check_phase(phase);
    // The residue check is 24.3 §12's; the ENVIRONMENT owns making sure it
    // runs after the drain rather than while traffic is still in flight.
    `uvm_info("TEST", "end-of-test checks complete", UVM_LOW)
  endfunction
endclass
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// VERIFICATION-ONLY. 12 -- Environment integrity monitor.
// Checks the composition itself: exactly-once delivery, epoch agreement,
// and that no component drifted from another. §15 measured all five
// architectural defects through checks of this shape.
class pcie_env_integrity extends uvm_component;
  `uvm_component_utils(pcie_env_integrity)
 
  int unsigned n_published, n_delivered_sb, n_delivered_cvg;
  int unsigned n_stale_accepted, n_key_collisions;
 
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
 
  function void check_phase(uvm_phase phase);
    super.check_phase(phase);
    // Exactly-once delivery to each subscriber (§15: 96,106 duplicates).
    if (n_delivered_sb != n_published)
      `uvm_error("ENV_INTEG", $sformatf(
        "scoreboard received %0d of %0d published observations",
        n_delivered_sb, n_published))
    if (n_delivered_cvg != n_published)
      `uvm_error("ENV_INTEG", $sformatf(
        "coverage received %0d of %0d published observations",
        n_delivered_cvg, n_published))
    if (n_stale_accepted != 0)
      `uvm_error("ENV_INTEG", $sformatf(
        "%0d stale-epoch observations were accepted (§8)", n_stale_accepted))
  endfunction
endclass

Classification: all UVM / verification-only.

Failure — eight. A monitor that checks. A reused observation object (96,106 violations). A predictor connected to the protocol monitor. Per-component configuration copies. Components disagreeing about reset (4,692 stale). A key without the Function (+77% collisions). A virtual sequencer holding every component's internals. And a scoreboard that raises an objection and never drops it.

12. Phase and Same-Cycle Races

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---- OBSERVATION AND DELIVERY (§4) ------------------------------
// P1: a monitor publishes exactly once per observed handshake.
property p_publish_once_per_handshake;
  @(posedge clk) disable iff (!rst_n)
    (desc_valid && desc_ready) |=> (publish_count == $past(publish_count) + 1);
endproperty
 
// P2: nothing is published on an unaccepted offer.
property p_no_publish_on_offer;
  @(posedge clk) disable iff (!rst_n)
    (desc_valid && !desc_ready) |=> $stable(publish_count);
endproperty
 
// P3: the published object is IMMUTABLE afterwards. §15: 96,106 violations
// from one mutating subscriber, visible to every other subscriber.
property p_observation_immutable;
  @(posedge clk) disable iff (!rst_n)
    published |=> $stable(published_contents);
endproperty
 
// P4: every published observation reaches every connected subscriber
// exactly once. §15: a duplicate source produced 96,106 extra deliveries.
property p_exactly_once_delivery;
  @(posedge clk) disable iff (!rst_n)
    (n_delivered_sb == n_published) && (n_delivered_cvg == n_published);
endproperty
 
// P5: no observation has two sources -- a VIP monitor and a custom monitor
// both watching one interface is an integrity error, not redundancy.
property p_single_observation_source;
  @(posedge clk) disable iff (!rst_n)
    obs_event |-> $onehot(source_vec);
endproperty
 
// ---- PREDICTOR INDEPENDENCE (§7) --------------------------------
// P6: the predictor never receives a protocol-side observation.
property p_predictor_not_connected_to_protocol;
  @(posedge clk) disable iff (!rst_n)
    (predictor_input_count == cfg_obs_count + app_obs_count);
endproperty
 
// P7: the predictor never reads DUT internal state.
property p_predictor_no_dut_access;
  @(posedge clk) disable iff (!rst_n)
    $stable(dut_internal_state) or !$stable(predictor_expectation_count);
endproperty
 
// P8: an expectation is published before the matching observation is
// consumed, so the scoreboard is never comparing against nothing.
property p_expectation_before_observation;
  @(posedge clk) disable iff (!rst_n)
    sb_compare_fire |-> sb_expectation_present;
endproperty
 
// P9: a descriptor posted with bus mastering disabled predicts NO traffic.
property p_no_traffic_without_bus_master;
  @(posedge clk) disable iff (!rst_n)
    (app_obs && !predictor_bus_master) |=> $stable(predictor_expectation_count);
endproperty
 
// ---- RESET AND EPOCH (§8) ---------------------------------------
// P10: the epoch increments exactly once per observed reset.
property p_epoch_once_per_reset;
  @(posedge clk) disable iff (!rst_n)
    reset_observed |=> (epoch == $past(epoch) + 1);
endproperty
 
// P11: an observation stamped with a stale epoch is REJECTED and counted.
// §15: 4,692 accepted without this check.
property p_stale_epoch_rejected;
  @(posedge clk) disable iff (!rst_n)
    (obs_arrives && (obs_epoch != current_epoch)) |=> stale_rejected_count_incremented;
endproperty
 
// P12: the epoch is stamped at the SOURCE, not by the consumer.
property p_epoch_stamped_at_source;
  @(posedge clk) disable iff (!rst_n)
    published |-> (published_epoch == monitor_epoch_at_publish);
endproperty
 
// P13: every model component observes the same reset event -- the
// predictor and the scoreboard cannot flush at different epochs. §15:
// the predictor retained entries the scoreboard had discarded.
property p_all_models_same_epoch;
  @(posedge clk) disable iff (!rst_n)
    (predictor_epoch == scoreboard_epoch) && (scoreboard_epoch == coverage_epoch);
endproperty
 
// P14: reset does not silently discard scoreboard entries (24.3 §6).
property p_reset_records_reason;
  @(posedge clk) disable iff (!rst_n)
    (epoch_changed && $past(sb_outstanding) > 0) |-> reset_reason_logged;
endproperty
 
// ---- KEYS AND SCALING (§9) --------------------------------------
// P15: the key carries the Function. §15: dropping it raised collisions
// from 21,028 to 37,209.
property p_key_includes_function;
  @(posedge clk) disable iff (!rst_n)
    key_constructed |-> (key.function_num == observed_function);
endproperty
 
// P16: the key carries the port index for a multi-port environment.
property p_key_includes_port;
  @(posedge clk) disable iff (!rst_n)
    (cfg_num_ports > 1) |-> (key.port_num == observed_port);
endproperty
 
// P17: the Function index is within the configured range.
property p_function_in_range;
  @(posedge clk) disable iff (!rst_n)
    key_constructed |-> (key.function_num < cfg_num_functions);
endproperty
 
// P18: model population equals the sum of per-Function populations --
// per-Function conservation, never a single global total (21.4 §7).
property p_population_conservation;
  @(posedge clk) disable iff (!rst_n)
    (total_outstanding == sum_of_per_function_outstanding);
endproperty
 
// ---- PHASING AND END OF TEST (§14) ------------------------------
// P19: the residue check runs after the drain, not during stimulus.
property p_residue_after_drain;
  @(posedge clk) disable iff (!rst_n)
    residue_check_fire |-> drain_complete;
endproperty
 
// P20: no model component holds an objection indefinitely.
property p_no_permanent_objection;
  @(posedge clk) disable iff (!rst_n)
    (sb_objection_raised) |-> s_eventually (!sb_objection_raised);
endproperty
 
// P21: samples are flushed before the report phase (24.4 §15).
property p_samples_flushed;
  @(posedge clk) disable iff (!rst_n)
    report_phase_active |-> (pending_samples == 0);
endproperty
 
// ---- ACTIVE / PASSIVE AND NON-INTERFERENCE ----------------------
// P22: a passive agent has NO driver -- not a driver that abstains.
property p_passive_has_no_driver;
  @(posedge clk) disable iff (!rst_n)
    (agent_is_passive) |-> !driver_exists;
endproperty
 
// P23: exactly one component drives each DUT interface.
property p_single_driver_per_interface;
  @(posedge clk) disable iff (!rst_n)
    $onehot0(interface_drive_vec);
endproperty
 
// P24: no verification component drives or gates the DUT.
property p_verification_non_functional;
  @(posedge clk) disable iff (!rst_n)
    $stable(dut_if_signals) or !$stable({n_published, sb_compare_count});
endproperty
 
// P25: configuration is read, never written, by model components.
property p_config_read_only;
  @(posedge clk) disable iff (!rst_n)
    $stable(env_cfg_contents);
endproperty
 
// P26: EVIDENCE -- reset with work outstanding actually occurred.
property c_reset_with_outstanding;
  @(posedge clk) disable iff (!rst_n)
    (reset_observed && (sb_outstanding > 0));
endproperty

Twenty-six properties. P4, P5 and P13 are the composition contract — exactly-once delivery, single source, one epoch — and they are the three §15 measured failing. P6 and P7 make predictor independence checkable rather than a matter of reviewing a connect phase, and P26 is the cover property that proves the hardest race was actually reached.

13. Measured Behaviour

14. End-of-Test Policy

Before the final objection drops, five things must be true, and each has a chapter behind it:

CheckOwner
stimulus completethe test
outstanding transactions drained, or intentionally aborted24.3 §7
scoreboard map empty (or declared residue)24.3 §12
no pending analysis records or unflushed samples24.4 §15
no fatal protocol errors outstanding24.5 §10

Two architectural rules make this workable.

Objections belong to the test and the sequences, not to the model components. A scoreboard that raises an objection while it holds outstanding entries will keep the simulation alive forever if the DUT lost one — turning a clean residue failure into a timeout with no message. P20 asserts the objection is eventually dropped, and §11's test uses a bounded drain time instead.

And coverage closure is a campaign property, not a test property. A single test that does not fill a bin has not failed. Chapter 24.4 §16 owns closure; the environment's job is only to make sure samples are flushed before the report.

15. Verification — Mutations

#MutationSymptomCaught by
1Monitor performs checkingobservation and judgement coupled; no independent replayreview
2Monitor reuses one transaction object96,106 immutability violations, visible to all subscribers (§13)P3
3Monitor publishes on validrequests that never transferred become expectationsP1, P2
4Two sources publish the same observation96,106 duplicate deliveriesP4, P5
5An analysis connection omitted in connect_phasesilent under-checking; counts divergeP4
6Predictor subscribes to the protocol monitorit predicts from the DUT's own output (§7)P6
7Predictor reads DUT internal statea mirror, not an oracle (24.1 §5)P7
8Predictor computes an expected Completion countthe Completer chooses fragmentation (13.3 §1)review
9Predictor ignores bus-master enableexpects traffic that legally cannot occurP9
10Each component keeps its own configuration copythey drift; VIP and DUT disagree (24.5 §4)P25
11Coverage bins sized independently of the DUT's Tag countmanufactured holes (24.4 §12)P25
12Epoch stamped by the consumer, not the sourcethe stamp reflects arrival, not observationP12
13No epoch check at all4,692 stale observations accepted (§13)P11
14Reset flushes the scoreboard but not the predictorthe two models diverge permanently (§13)P13
15Reset clears the scoreboard with no recorded reasonevidence of pre-reset losses destroyedP14
16Epoch increments twice for one resetvalid observations rejected as staleP10
17Key without the Functioncollisions +77% (§13)P15
18Key without the port in a multi-port environmenttwo ports' transactions collideP16
19Function index unboundedarray indexing outside the configured rangeP17
20One global outstanding total instead of per-Functiontwo Functions wrong in opposite directions pass (21.4 §7)P18
21Virtual sequence races configuration and descriptorintermittent failure with no DUT bug (§12)P9
22Virtual sequencer holds handles to every componentany refactor breaks every scenarioreview
23Sequence reaches into another agent's driverthe agent boundary stops meaning anythingreview
24Scoreboard raises an objection and never drops ita lost transaction becomes a timeout, not a messageP20
25Objections raised inside monitorsthe test cannot end deterministicallyP20
26No drain time; residue judged during trafficfalse residue failures on in-flight workP19
27Samples not flushed before the reportthe last transactions never countedP21
28Passive agent builds a driver that abstainsone misconfiguration away from contention (24.5 §3)P22
29Two components drive one interfacecontention blamed on the DUTP23
30A checker gates a DUT signalthe measured design is not the shipping oneP24
31Coverage subscriber writes to the scoreboardboth wrong together (24.4 §13)review
32Environment contains protocol logicthe topology becomes untestable and unreusable (§2)review
33Vendor VIP types flow past the adapterthe environment is vendor-locked (24.5 §5)review
34One agent for all five boundariesscenario ordering buried inside a sequence (§3)review

Two counterexamples worth stating explicitly.

Mutation 2 is architectural even though it looks like a coding slip. A monitor that fills in one item handle and calls ap.write(t) each time is handing the same object to every subscriber. Analysis ports broadcast the handle, so when the coverage subscriber sorts a field or the scoreboard stores it, the next observation rewrites data the others already believe they own. §13 measured 96,106 violations in a single run. The architectural point is that immutability is a property of the contract between components (§2), not of any one of them — which is why P3 is asserted at the environment level rather than inside the monitor.

Mutation 14 is the reset failure nobody tests for, and it is permanent. The reset coordinator fires; the scoreboard flushes and logs; the predictor, connected to a different port or built before the coordinator, never hears about it. From that point the predictor's expectations describe a pre-reset world and the scoreboard's observations describe a post-reset one — and every subsequent comparison fails, or worse, spuriously passes. §13 measured the divergence as predictor residue outliving the scoreboard's. P13 asserts all model components share one epoch, which is the only check that sees it.

16. Debugging

Symptom — the scoreboard sees fewer observations than the monitor published. A connection is missing or a subscriber is dropping (mutations 4, 5). Read the integrity counters (§11's block 12): n_published versus n_delivered_sb. This is a two-minute check that otherwise presents as intermittent under-checking.

Symptom — every transaction appears twice. Two observation sources (mutation 4) — commonly a VIP monitor and a custom monitor both watching the same interface (24.5 §15's mutation 29). §13 measured 96,106 duplicates, and P5 asserts a single source.

Symptom — failures begin immediately after every reset and then stop. Stale observations from analysis queues (§8, mutation 13). Items published before the reset arrive after it and match post-reset entries. §13 measured 4,692 acceptances without the epoch check. The tell is that the errors stop once the queues drain — a real DUT bug would not be time-limited that way.

Symptom — after a reset the scoreboard and predictor never agree again. They flushed at different epochs (mutation 14). Compare predictor_epoch and scoreboard_epoch — P13's condition. This is permanent divergence, not intermittent, which distinguishes it from the previous symptom.

Symptom — a test fails intermittently and the DUT waveform is identical in passing and failing runs. A virtual-sequence race (§12, mutation 21). The descriptor sequence is winning against the configuration sequence some of the time. Check whether the sequences are started sequentially or forked — §11's body() orders them explicitly, and a fork there is the bug.

Symptom — the simulation never ends. An objection that is never dropped (mutations 24, 25). Most often a scoreboard holding one while it waits for a transaction the DUT lost. The correct structure is a bounded drain plus a residue check (§14) — which turns an infinite run into a clear error message naming the missing transaction.

Symptom — the environment works with one Function and fails with four. The key (§9, mutation 17). §13 measured collisions rising +77% when the Function is dropped. The signature is that failures scale with Function count, exactly as 23.3 §16's QD1-versus-QD8 signature scales with concurrency.

Symptom — coverage numbers changed and no test did. A configuration copy drifted (mutation 10, 11), or bins were sized independently of the DUT. Diff the resolved pcie_env_cfg between runs — §11's block 1 reports it at build time for exactly this.

17. Misconceptions

"The environment is a container of components." It is the set of things components may not do (§2).

"A monitor may check; it has the data." Then observation and judgement are coupled and neither can be replaced (§2).

"Reusing the transaction object saves allocations." 96,106 immutability violations, visible to every subscriber (§13).

"The predictor can look at the DUT output to stay in sync." Then it is a mirror (24.1 §5).

"The predictor should compute how many Completions to expect." The Completer chooses (13.3 §1).

"Each component can hold its own configuration." They drift, and the VIP starts accusing a correct DUT (24.5 §4).

"Reset obviously clears everything." Only components that heard about it — and 4,692 stale observations arrive afterwards (§8).

"One reset event is enough; components can handle it themselves." That is exactly mutation 14's permanent divergence.

"One Function today, so a global context is fine." +77% collisions the day a second Function appears (§9).

"One agent for everything is simpler." Scenario ordering disappears into a sequence and cannot be reviewed (§3).

"The virtual sequencer should hold every component handle." Then every refactor breaks every scenario (§11).

"The scoreboard should hold an objection until it's satisfied." A lost transaction then becomes a timeout instead of a message (§14).

"Passive means the driver doesn't drive." Passive means there is no driver (24.5 §3).

"Coverage closure is an end-of-test check." It is a campaign property (24.4 §16).

18. Understanding Check

Q1. Your predictor is connected to the protocol monitor "so it can stay in sync with what the DUT actually sent." What have you lost? The oracle. The predictor now derives expectations from the DUT's own output, so it agrees with the design by construction — including when the design is wrong (24.1 §5). Independence here is structural, not disciplinary: §5's figure connects the predictor to the control and application streams only, so it cannot see the protocol side. P6 asserts the input count matches those two sources alone.

Q2. A monitor fills in one pcie_app_item and calls ap.write(t) each time. Why is this worse than the same bug inside a scoreboard? Because analysis ports broadcast the handle to every subscriber (§4, mutation 2). The scoreboard, the coverage subscriber and the timeline all hold the same object, and the next observation rewrites data all three already believe they own. §13 measured 96,106 violations in one run — and the corruption appears in components that never touched the object.

Q3. Reset fires. The scoreboard flushes and the predictor does not. Why is this worse than either flushing alone? Because the two models now describe different worlds, permanently (§8, mutation 14). The predictor's expectations were formed before the reset; the scoreboard's observations come after it. Every subsequent comparison is against a stale expectation — and unlike a stale-observation problem, this one does not drain away. P13 asserts every model component shares one epoch.

Q4. Why is the epoch stamped by the monitor rather than checked by the consumer's clock? Because the consumer receives the observation later than it was made (§8). §13 delivered observations 1–6 cycles after publication and measured 4,692 stale acceptances without a source stamp. A consumer comparing against "the current epoch when I received this" cannot distinguish a pre-reset observation from a post-reset one — the stamp has to record when the event happened.

Q5. Your environment works with one Function and fails with four. Name the most likely cause and the check that finds it. The transaction key is missing the Function (§9, mutation 17). With one Function it is redundant; with four, two Functions' (Requester ID, Tag) pairs collide — §13 measured collisions rising from 21,028 to 37,209, +77%. P15 asserts the Function is in the key, and P18's per-Function conservation catches the downstream effect.

Q6. A test hangs forever. The scoreboard is holding an objection because a transaction never completed. What is architecturally wrong? Objections belong to the test and its sequences, not to model components (§14, mutation 24). A scoreboard that keeps the simulation alive while it waits converts a clean, informative residue failure into a timeout with no message. The correct structure is a bounded drain followed by the residue check — which names the missing transaction, its identity and how many bytes it received (24.3 §12).

Q7. Which single property from this chapter would you add to an environment you inherited? P4 — exactly-once delivery. It requires no protocol knowledge, it is three counters, and it detects the two most damaging composition defects at once: a missing connection (silent under-checking, which makes every other check weaker without failing) and a duplicate source (96,106 duplicates in §13). An environment that under-delivers is worse than one that fails, because it produces confidence rather than a bug report.

19. Module 24 So Far, and What 24.7 Owns

Five chapters, five different questions, one environment.

ChapterProves
24.1 Protocol Verificationwhat must be verified, and at which layer
24.2 Assertionslocal temporal and ownership contracts
24.3 Scoreboardsend-to-end transaction conservation and identity
24.4 Coveragethat the campaign reached what matters
24.5 VIPconformance, by a participant that owes you nothing else
24.6 (this)that all of the above compose without contradicting each other

And the recurring finding across all six is the same one Module 23 kept producing. Every defect measured here — duplicate delivery, mutated observations, disagreeing epochs, a key missing a field — is an ownership failure between components, which is Chapter 23.6's law applied to a testbench instead of to hardware.

24.7 Error Injection is the one chapter still planned, and it remains substantial. Everything in this module verifies a device that is trying to behave. 24.7 deliberately breaks the link — bad CRC, bad sequence numbers, credit violations, malformed packets, timeouts — and proves recovery.

It also completes several chapters here. 24.2 §10's credit-exhaustion cover property, 24.3 §10's terminal-status branches, 24.4 §16's error bins and 24.5 §12's injection knobs are all unreachable until something deliberately misbehaves. This module built the observers; 24.7 gives them something worth observing.