PCIe · Module 24
VIP — A Trusted Participant, Not an Oracle for Your Design
Verification IP proves your traffic is legal PCIe. It cannot prove your device did what the design intended — and across 200,000 transactions the VIP flagged nothing while the project scoreboard caught 6,061 wrong-destination writes.
A commercial PCIe VIP is a large, well-tested implementation of a protocol you would rather not re-implement. Using one is usually the right decision.
The failure mode is not using it — it is believing it. A VIP answers one question extremely well and a different question not at all, and the gap between those two questions is where a device ships broken with a green regression.
1. Sources, Scope, and a Deliberate Vendor-Neutrality
2. A Participant, Not the Testbench
A PCIe VIP typically provides some subset of:
| It can provide | It does not provide |
|---|---|
| an active protocol partner (a Root Complex side, say) | your design's intent |
| stimulus at protocol level | your system-level scenarios |
| a protocol monitor | your end-to-end scoreboard |
| conformance checking | your internal ownership contracts |
| transaction objects and callbacks | your coverage plan |
| configuration abstractions | your debug methodology |
The right-hand column is the chapter. Every item in it is a project responsibility that survives buying the best VIP available, and §7 turns the two columns into a matrix you can write down and defend.
The specific trap has a name: outsourcing the oracle. A team adopts a VIP with a strong conformance checker, sees a green run, and removes the project scoreboard as redundant. §14 shows what that costs — the wrong-destination defect becomes invisible, because the VIP was never asked whether the address was right, only whether it was legal.
3. Active and Passive
Two deployment modes, and the choice is architectural rather than a configuration detail.
| Mode | The VIP | Typical use |
|---|---|---|
| active | drives a PCIe role — it is the link partner | an Endpoint DUT with a Root-side VIP |
| passive | observes and checks only | a VIP monitor wrapped around an existing BFM, or around a real link model |
A passive VIP that is accidentally active is a specific, ugly failure. Two drivers on one interface produce contention that presents as random protocol errors — and the VIP's own checker reports them, so the investigation starts at the DUT. §13's P7 asserts the exclusivity structurally, and §12's configuration object makes the mode a required field rather than a default.
And a passive VIP still needs to be configured correctly. A monitor that believes the link is Gen4 when it is Gen3, or that the operative MPS is 512 when it is 128, will flag legal traffic — §4.
4. Configuration Is Functional Correctness
5. The Adapter Boundary
6. VIP Checks Conformance; You Check Intent
7. The Responsibility Matrix
Write this down for your project, once, and defend it at review. Every row must have exactly one owner.
| Responsibility | VIP | Project SVA | Project scoreboard | Project coverage |
|---|---|---|---|---|
| TLP header legality | ✔ | |||
| Link-layer integrity and retry | ✔ | |||
| Link state legality | ✔ | limited (§9, 24.2 §12) | ||
| Ordering rules between transactions | ✔ | |||
| Requested address is the intended one | ✔ | |||
| Byte coverage of a split read | ✔ | |||
| Nothing outstanding at end of test | ✔ | |||
| Internal stream ownership / snapshot | ✔ | |||
| Tag allocator and credit reservation | ✔ | |||
| Stimulus reach | ✔ |
Two rules make the matrix useful rather than decorative.
No row has two owners. Duplicating the VIP's header-legality checking in your own SVA costs effort and produces a second set of failures to triage — and it is the checks in the middle block, which the VIP cannot do, that go unwritten while you build it.
And no row is empty. An empty row is a responsibility nobody has. §6's 6,061 is what an empty "intended address" row looks like in production.
8. Callbacks Publish; They Do Not Check
A VIP typically exposes observations through a callback or an analysis port. Whichever it is, the rule is the same as for your own monitors (24.3 §10):
callback -> adapt -> publish an immutable observation -> [scoreboard, coverage]Doing scoreboard work inside the callback is wrong for three reasons, and the third is the one that bites.
It couples your checking to the vendor's calling context, so a version that changes when callbacks fire changes your checking.
It can block. A callback that does heavy work — an associative-array walk, a report — inside the VIP's execution path can perturb the timing of the very traffic it is observing.
And it makes the observation mutable at the worst moment. If the callback hands the vendor's object straight to the scoreboard, the vendor is free to reuse it — which is Chapter 24.3 §13's mutation 3 with someone else's code doing the reuse, and you cannot fix it by being careful in your own monitor.
So the adapter clones. §12's adapter constructs a fresh project object per observation and never retains a handle to the vendor's.
9. Version and Revision Discipline
10. Log Triage — the First Error
A VIP under a real failure produces a lot of output, and most of it is consequence.
A single protocol violation typically cascades: one malformed packet leads to a retry, which leads to a sequence mismatch, which leads to a link event, which leads to dozens of transaction-level errors. The last error is almost never the cause — Chapter 21.4 §11 measured last-write-wins error reporting naming a non-root-cause 96.8% of the time.
The triage order that works:
| Step | Read |
|---|---|
| 1 | the first VIP error, with its timestamp |
| 2 | your DUT's first-failure capture at that timestamp (23.6 §7) |
| 3 | your scoreboard's first mismatch |
| 4 | the correlated event timeline around that time (24.1 §10) |
| 5 | only then, the waveform |
Steps 1–3 are three independent observers of the same instant, and their disagreement is itself information: if the VIP flags a protocol error and your scoreboard's first mismatch is later, the protocol error came first and your mismatch is downstream of it.
And the inverse case is the §6 case. The VIP has no first error and your scoreboard has thousands — which localizes the defect to intent rather than conformance, and takes the whole protocol layer off the suspect list in one reading.
11. The Environment With a VIP
Four things to read out of the figure.
The adapter is a chokepoint on purpose. Exactly one block knows the vendor's types; everything to its right compiles without the VIP (§5).
The assertions attach to the DUT directly, not through the VIP. They check internal ownership contracts that no link-level observer can see (24.2 §14) — which is why the arrow bypasses the whole VIP path.
One configuration feeds both the VIP and coverage. That shared source is what prevents §4's silent disagreement between what the DUT was built for and what the VIP expects.
And nothing flows back from the scoreboard into the VIP. The VIP is a participant and an observer; an environment that lets checking steer the protocol partner has made the partner part of the design under test.
12. Code — Configuration, Adapter, Wrapper, Mock
// UVM / VERIFICATION-ONLY. Vendor-neutral VIP configuration.
// Every field here is ALSO a checking parameter (§4): wrong, it produces
// false accusations against a correct DUT.
typedef enum { PCIE_ROLE_ROOT, PCIE_ROLE_ENDPOINT, PCIE_ROLE_SWITCH } pcie_role_e;
typedef enum { VIP_ACTIVE, VIP_PASSIVE } vip_mode_e;
class pcie_vip_cfg extends uvm_object;
`uvm_object_utils(pcie_vip_cfg)
// --- role and mode: architectural, not cosmetic (§3) ---
rand pcie_role_e role = PCIE_ROLE_ROOT; // what the VIP PLAYS
rand vip_mode_e mode = VIP_ACTIVE;
// --- capability model: must agree with the DUT's build (§4) ---
rand int unsigned max_payload_bytes = 256; // operative MPS (11.4 §5)
rand int unsigned max_read_req_bytes = 512; // MRRS
rand int unsigned num_functions = 1;
rand int unsigned max_outstanding = 8;
// --- checking: EXPLICIT, because a silent 0 makes runs green (§4) ---
bit checking_enable = 1;
bit protocol_log_enable = 1;
// --- provenance, logged every run (§9) ---
string vip_version_str = "UNSET";
string vip_spec_mode_str = "UNSET";
function new(string name = "pcie_vip_cfg"); super.new(name); endfunction
// Derived from the SAME environment config the DUT wrapper used, so the
// two cannot disagree by editing one of them (§4).
virtual function void adopt_from_env(int unsigned mps, int unsigned mrrs,
int unsigned nfunc, int unsigned ntags);
max_payload_bytes = mps;
max_read_req_bytes = mrrs;
num_functions = nfunc;
max_outstanding = ntags;
endfunction
virtual function void log_provenance(uvm_component ctx);
// The block whose DIFF is the first thing to read when a result changes.
`uvm_info("VIP_CFG", $sformatf(
"role=%s mode=%s mps=%0d mrrs=%0d funcs=%0d tags=%0d checking=%0b vip=%s spec=%s",
role.name(), mode.name(), max_payload_bytes, max_read_req_bytes,
num_functions, max_outstanding, checking_enable,
vip_version_str, vip_spec_mode_str), UVM_LOW)
if (!checking_enable)
`uvm_warning("VIP_CFG",
"VIP checking is DISABLED -- a green run proves nothing about conformance")
endfunction
endclass// UVM / VERIFICATION-ONLY. THE ADAPTER -- the only vendor-aware code (§5).
// It maps what the project uses, never invents a value, and preserves the
// identity exactly. §14: dropping the Requester ID lost 149,607 fields
// while the VIP still reported a clean protocol run.
class pcie_vip_adapter extends uvm_component;
`uvm_component_utils(pcie_vip_adapter)
uvm_analysis_port #(pcie_req_item) req_ap;
uvm_analysis_port #(pcie_cpl_item) cpl_ap;
bit [31:0] epoch; // stamped in, checked downstream (24.3 §6)
int unsigned n_unmapped; // sticky: unrecognized kinds
function new(string name, uvm_component parent);
super.new(name, parent);
req_ap = new("req_ap", this);
cpl_ap = new("cpl_ap", this);
endfunction
// -----------------------------------------------------------------
// VENDOR INTEGRATION POINT.
// Replace `vip_obs_t` with the VIP's observation type and read its
// fields here. Nothing below this function may see the vendor type.
// -----------------------------------------------------------------
virtual function void on_vip_observation(vip_obs_t o);
case (map_kind(o.kind_raw))
TLP_MEM_RD, TLP_MEM_WR: publish_request(o);
TLP_CPL, TLP_CPL_D: publish_completion(o);
default: begin
// NEVER default to a legal kind. §14: defaulting lost 150,381
// observations' worth of type information -- and the VIP saw
// nothing wrong, because the packets themselves were legal.
n_unmapped++;
`uvm_error("VIP_ADAPT", $sformatf(
"unmapped VIP transaction kind 0x%0h -- observation DROPPED, not guessed",
o.kind_raw))
end
endcase
endfunction
protected virtual function pcie_txn_e map_kind(bit [7:0] raw);
// An explicit, total mapping. An unlisted value falls to `default`.
case (raw)
8'h00: return TLP_MEM_RD;
8'h01: return TLP_MEM_WR;
8'h0A: return TLP_CPL;
8'h4A: return TLP_CPL_D;
default: return TLP_UNKNOWN;
endcase
endfunction
protected virtual function void publish_request(vip_obs_t o);
pcie_req_item t = pcie_req_item::type_id::create("t");
t.key = pcie_txn_key::type_id::create("k");
// IDENTITY PRESERVED EXACTLY -- full width, all three fields (24.3 §3).
t.key.requester_id = o.requester_id;
t.key.tag = o.tag;
t.key.function_num = o.function_num;
t.is_read = (map_kind(o.kind_raw) == TLP_MEM_RD);
t.address = o.address;
t.byte_count = o.byte_count;
t.obs_epoch = epoch;
t.obs_time = $time;
req_ap.write(t); // a FRESH project object; the vendor's is not retained
endfunction
protected virtual function void publish_completion(vip_obs_t o);
pcie_cpl_item t = pcie_cpl_item::type_id::create("t");
t.key = pcie_txn_key::type_id::create("k");
t.key.requester_id = o.requester_id;
t.key.tag = o.tag;
t.key.function_num = o.function_num;
t.status = o.status;
t.byte_count = o.byte_count; // REMAINING count (13.3 §2)
t.lower_address = o.lower_address;
t.payload_bytes = o.payload_bytes;
t.obs_epoch = epoch;
t.obs_time = $time;
cpl_ap.write(t);
endfunction
function void check_phase(uvm_phase phase);
if (n_unmapped != 0)
`uvm_error("VIP_ADAPT", $sformatf(
"%0d observations were dropped as unmapped -- coverage and scoreboard \
results are incomplete for this run", n_unmapped))
endfunction
endclass// UVM / VERIFICATION-ONLY. The project wrapper around the VIP agent.
// Its job is to make the VIP look like any other project agent, so the
// environment (24.6) instantiates it without knowing a vendor exists.
class pcie_vip_wrapper extends uvm_env;
`uvm_component_utils(pcie_vip_wrapper)
pcie_vip_cfg vcfg;
pcie_vip_adapter adapter;
// VENDOR INTEGRATION POINT: the vendor agent instance goes here.
// vendor_pcie_agent m_agent;
uvm_analysis_port #(pcie_req_item) req_ap;
uvm_analysis_port #(pcie_cpl_item) cpl_ap;
function new(string name, uvm_component parent);
super.new(name, parent);
req_ap = new("req_ap", this);
cpl_ap = new("cpl_ap", this);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db#(pcie_vip_cfg)::get(this, "", "vcfg", vcfg))
`uvm_fatal("VIP_WRAP", "no VIP configuration provided")
// ACTIVE and PASSIVE are exclusive (§3, P7). A passive VIP that drives
// contends with the real driver and blames the DUT for the result.
if (vcfg.mode == VIP_PASSIVE && vcfg.role != PCIE_ROLE_SWITCH)
`uvm_info("VIP_WRAP", "passive mode: monitor only, no driver instantiated", UVM_LOW)
adapter = pcie_vip_adapter::type_id::create("adapter", this);
vcfg.log_provenance(this); // §9's bookkeeping, every run
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
adapter.req_ap.connect(req_ap);
adapter.cpl_ap.connect(cpl_ap);
// VENDOR INTEGRATION POINT: hook the VIP's monitor callback or analysis
// port to adapter.on_vip_observation().
endfunction
endclass// UVM / VERIFICATION-ONLY. A MOCK VIP producer.
// This is what makes §14 executable without a commercial licence. It emits
// PROTOCOL-LEGAL observations, including ones whose DESTINATION is wrong --
// which is precisely the case a conformance checker cannot flag (§6).
class pcie_mock_vip extends uvm_component;
`uvm_component_utils(pcie_mock_vip)
pcie_vip_adapter adapter;
int unsigned n_txn = 200_000;
int unsigned wrong_dest_pct = 3; // the DUT's injected intent bug
// The INTENDED address, known to the test but not to any conformance
// checker -- the VIP has never seen the descriptor.
bit [63:0] intended_q[$];
function new(string name, uvm_component parent); super.new(name, parent); endfunction
task run_phase(uvm_phase phase);
vip_obs_t o;
for (int i = 0; i < n_txn; i++) begin
bit [63:0] intended = ($urandom_range(16'h9000, 16'h1000)) & ~64'h3F;
bit wrong = ($urandom_range(99, 0) < wrong_dest_pct);
o.kind_raw = 8'h01; // MemWr -- always legal
o.requester_id = $urandom_range(3, 0);
o.tag = $urandom_range(7, 0);
o.function_num = 0;
// A LEGAL packet either way: aligned, well-formed, valid identity.
o.address = wrong ? (intended ^ 64'h1000) : intended;
o.byte_count = 64;
intended_q.push_back(intended);
adapter.on_vip_observation(o);
#1ns;
end
endtask
endclass// UVM / VERIFICATION-ONLY. The responsibility matrix of §7, as an
// executable build-time check. A row with no owner, or two owners, is a
// FATAL at elaboration rather than a discovery during a debug.
typedef enum {
RESP_TLP_LEGALITY, RESP_LINK_INTEGRITY, RESP_LINK_STATE, RESP_ORDERING,
RESP_INTENDED_ADDRESS, RESP_BYTE_COVERAGE, RESP_RESIDUE,
RESP_STREAM_OWNERSHIP, RESP_TAG_AND_CREDIT, RESP_STIMULUS_REACH
} responsibility_e;
typedef enum { OWN_NONE, OWN_VIP, OWN_SVA, OWN_SCOREBOARD, OWN_COVERAGE } owner_e;
class pcie_responsibility_matrix extends uvm_object;
`uvm_object_utils(pcie_responsibility_matrix)
owner_e owner [responsibility_e];
function new(string name = "pcie_responsibility_matrix");
super.new(name);
owner[RESP_TLP_LEGALITY] = OWN_VIP;
owner[RESP_LINK_INTEGRITY] = OWN_VIP;
owner[RESP_LINK_STATE] = OWN_VIP;
owner[RESP_ORDERING] = OWN_VIP;
owner[RESP_INTENDED_ADDRESS] = OWN_SCOREBOARD; // §6: the VIP CANNOT
owner[RESP_BYTE_COVERAGE] = OWN_SCOREBOARD;
owner[RESP_RESIDUE] = OWN_SCOREBOARD;
owner[RESP_STREAM_OWNERSHIP] = OWN_SVA;
owner[RESP_TAG_AND_CREDIT] = OWN_SVA;
owner[RESP_STIMULUS_REACH] = OWN_COVERAGE;
endfunction
// An EMPTY row is a responsibility nobody has -- §14's 6,061 is what an
// empty RESP_INTENDED_ADDRESS looks like in production.
function void check(uvm_component ctx, bit scoreboard_present,
bit coverage_present, bit vip_checking);
responsibility_e r;
foreach (owner[r]) begin
if (owner[r] == OWN_NONE)
`uvm_fatal("RESP", $sformatf("responsibility %s has NO owner", r.name()))
if (owner[r] == OWN_SCOREBOARD && !scoreboard_present)
`uvm_fatal("RESP", $sformatf(
"%s is assigned to the scoreboard, which is not instantiated", r.name()))
if (owner[r] == OWN_COVERAGE && !coverage_present)
`uvm_fatal("RESP", $sformatf(
"%s is assigned to coverage, which is not instantiated", r.name()))
if (owner[r] == OWN_VIP && !vip_checking)
`uvm_fatal("RESP", $sformatf(
"%s is assigned to the VIP, but VIP checking is disabled", r.name()))
end
endfunction
endclass// UVM / VERIFICATION-ONLY. Adapter unit test -- runs with NO VIP licence.
// This is the practical payoff of §5's boundary: the adapter is the only
// vendor-aware block, so it is also the only one that needs a mock to test,
// and everything downstream compiles and runs against project types alone.
class pcie_adapter_unit_test extends uvm_test;
`uvm_component_utils(pcie_adapter_unit_test)
pcie_vip_adapter adapter;
int unsigned n_req, n_cpl, n_dropped;
function new(string name, uvm_component parent); super.new(name, parent); endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
adapter = pcie_vip_adapter::type_id::create("adapter", this);
endfunction
task run_phase(uvm_phase phase);
vip_obs_t o;
phase.raise_objection(this);
// 1. Identity must survive at FULL width (§14: 149,607 lost when it
// does not). Deliberately uses the widest legal values.
o = '{kind_raw:8'h01, requester_id:16'hFFFF, tag:8'hFF, function_num:3'h7,
address:64'hDEAD_BEEF_0000_0040, byte_count:16'd256,
status:'0, lower_address:'0, payload_bytes:'0};
adapter.on_vip_observation(o);
// 2. An unmapped kind must DROP and report -- never default (§14:
// defaulting mistyped 150,381 observations).
o.kind_raw = 8'h7F;
adapter.on_vip_observation(o);
// 3. A completion must carry Byte Count and Lower Address through
// unchanged (13.3 §2), or 24.3's progression checks are meaningless.
o = '{kind_raw:8'h4A, requester_id:16'h1234, tag:8'h05, function_num:3'h1,
address:'0, byte_count:16'd128, status:3'b000,
lower_address:7'h40, payload_bytes:16'd64};
adapter.on_vip_observation(o);
phase.drop_objection(this);
endtask
function void check_phase(uvm_phase phase);
super.check_phase(phase);
if (adapter.n_unmapped != 1)
`uvm_error("ADAPT_UT", $sformatf(
"expected exactly 1 unmapped observation, saw %0d", adapter.n_unmapped))
`uvm_info("ADAPT_UT", "adapter fidelity checks complete", UVM_LOW)
endfunction
endclassClassification: all UVM / verification-only.
Failure — seven. Passing the vendor object downstream (§5). Defaulting an unmapped kind (150,381 lost). Dropping or truncating the identity (149,607 / 100,073). VIP configuration maintained separately from the DUT's (§4). checking_enable left at 0. Heavy work inside a callback (§8). And removing the project scoreboard because the VIP has a checker (§6 — 0 versus 6,061).
13. Checks
// ---- ADAPTER FIDELITY (§5) --------------------------------------
// P1: the Requester ID survives the adapter, at full width.
property p_adapter_preserves_rid;
@(posedge clk) disable iff (!rst_n)
adapt_fire |-> (proj_item.key.requester_id == vip_obs.requester_id);
endproperty
// P2: the Tag survives, at full width. §14: truncation lost 100,073 fields.
property p_adapter_preserves_tag;
@(posedge clk) disable iff (!rst_n)
adapt_fire |-> (proj_item.key.tag == vip_obs.tag);
endproperty
// P3: the Function survives -- required for a multi-Function key (24.3 §3).
property p_adapter_preserves_function;
@(posedge clk) disable iff (!rst_n)
adapt_fire |-> (proj_item.key.function_num == vip_obs.function_num);
endproperty
// P4: the type is mapped, never defaulted. An unmapped kind DROPS and
// reports; §14 measured defaulting losing 150,381 observations.
property p_no_default_kind;
@(posedge clk) disable iff (!rst_n)
(vip_obs.kind_raw inside {8'h00,8'h01,8'h0A,8'h4A}) or
(!adapt_fire && unmapped_reported);
endproperty
// P5: length and address survive unchanged.
property p_adapter_preserves_extent;
@(posedge clk) disable iff (!rst_n)
adapt_fire |-> ((proj_item.byte_count == vip_obs.byte_count)
&& (proj_item.address == vip_obs.address));
endproperty
// P6: the published project object is fresh -- no vendor handle retained.
property p_no_vendor_handle_retained;
@(posedge clk) disable iff (!rst_n)
adapt_fire |=> $stable(proj_item_contents);
endproperty
// ---- CONFIGURATION (§4) -----------------------------------------
// P7: active and passive are EXCLUSIVE. A passive VIP that drives
// contends with the real driver and the VIP then blames the DUT (§3).
property p_mode_exclusive;
@(posedge clk) disable iff (!rst_n)
(vip_mode == VIP_PASSIVE) |-> !vip_driving;
endproperty
// P8: the VIP's capability model matches the environment's.
property p_cfg_agrees_with_env;
@(posedge clk) disable iff (!rst_n)
cfg_resolved |-> ((vip_cfg.max_payload_bytes == env_cfg.max_payload)
&& (vip_cfg.max_read_req_bytes == env_cfg.max_read_req)
&& (vip_cfg.max_outstanding == env_cfg.max_tags));
endproperty
// P9: a role is always one of the modelled values.
property p_role_valid;
@(posedge clk) disable iff (!rst_n)
cfg_resolved |-> (vip_cfg.role inside {PCIE_ROLE_ROOT, PCIE_ROLE_ENDPOINT,
PCIE_ROLE_SWITCH});
endproperty
// P10: no feature is enabled that the environment did not declare.
property p_no_silent_feature_enable;
@(posedge clk) disable iff (!rst_n)
cfg_resolved |-> (vip_enabled_features == (vip_enabled_features & env_declared_features));
endproperty
// P11: disabled checking is REPORTED, never silent (§4).
property p_checking_disabled_reported;
@(posedge clk) disable iff (!rst_n)
(!vip_cfg.checking_enable) |-> checking_disabled_warned;
endproperty
// P12: provenance is logged before any traffic (§9).
property p_provenance_logged_first;
@(posedge clk) disable iff (!rst_n)
first_txn |-> provenance_logged;
endproperty
// ---- OBSERVATION DISCIPLINE (§8) --------------------------------
// P13: the adapter publishes once per VIP observation.
property p_publish_once;
@(posedge clk) disable iff (!rst_n)
vip_obs_valid |=> (publish_count == $past(publish_count) + 1);
endproperty
// P14: the epoch is stamped in at the adapter, so downstream can reject
// stale observations after reset (24.3 §6).
property p_epoch_stamped;
@(posedge clk) disable iff (!rst_n)
adapt_fire |-> (proj_item.obs_epoch == adapter_epoch);
endproperty
// P15: the callback does not block -- the adapter completes in the cycle
// it is invoked.
property p_callback_non_blocking;
@(posedge clk) disable iff (!rst_n)
vip_obs_valid |=> !adapter_busy;
endproperty
// P16: the VIP observation path never drives the DUT.
property p_observation_does_not_drive;
@(posedge clk) disable iff (!rst_n)
$stable(dut_if_signals) or !$stable(publish_count);
endproperty
// ---- DIVISION OF LABOUR (§6, §7) --------------------------------
// P17: a protocol-legal transaction with the wrong destination is caught
// by the PROJECT scoreboard, not the VIP. §14: 0 versus 6,061.
property p_intent_checked_by_project;
@(posedge clk) disable iff (!rst_n)
(wrong_destination && !vip_flagged) |-> sb_flagged;
endproperty
// P18: the project scoreboard is present whenever a VIP is present --
// the VIP's checker does not substitute for it.
property p_scoreboard_present;
@(posedge clk) disable iff (!rst_n)
vip_present |-> scoreboard_present;
endproperty
// P19: the VIP's verdict never feeds the project's expected results.
property p_vip_verdict_not_an_oracle;
@(posedge clk) disable iff (!rst_n)
$stable(sb_expected_state) or !$stable(vip_error_count);
endproperty
// P20: unmapped observations are counted and reported at end of test, so a
// partially-blind run is not mistaken for a clean one.
property p_unmapped_reported_at_end;
@(posedge clk) disable iff (!rst_n)
(check_phase_active && (n_unmapped != 0)) |-> unmapped_error_issued;
endpropertyTwenty checks. P1–P6 are the adapter's fidelity contract, and they are the ones §14 measured failing silently — the VIP reports a clean protocol run while your model has lost a field. P17–P19 encode the division of labour so that removing the project scoreboard is a property failure rather than a code review opinion.
14. Measured Behaviour
15. Verification — Mutations
| # | Mutation | Symptom | Caught by |
|---|---|---|---|
| 1 | Treat the VIP as the whole testbench | intent defects invisible — 6,061 missed (§14) | P18 |
| 2 | Remove the project scoreboard because the VIP checks | the same, deliberately | P18 |
| 3 | Pass the vendor transaction through the whole environment | a vendor change becomes a rewrite (§5) | review |
| 4 | Adapter drops the Requester ID | 149,607 fields lost; requesters collapse into one key | P1 |
| 5 | Adapter truncates the Tag | 100,073 lost; identities alias | P2 |
| 6 | Adapter omits the Function | multi-Function keys collide (24.3 §3) | P3 |
| 7 | Unmapped kind defaults to MemWr | 150,381 observations mistyped (§14) | P4 |
| 8 | Adapter retains the vendor's object handle | the vendor reuses it; your entry mutates | P6 |
| 9 | VIP configured with the wrong role | traffic in the wrong direction; nothing enumerates | P9 |
| 10 | VIP spec mode differs from the DUT's feature set | legal behaviour flagged (§9) | P8 |
| 11 | VIP MPS differs from the DUT's operative MPS | legal payloads reported oversized | P8 |
| 12 | VIP max_outstanding differs from the DUT's Tag count | false depth-related errors | P8 |
| 13 | Passive VIP accidentally driving | contention blamed on the DUT (§3) | P7 |
| 14 | checking_enable = 0, silently | a green run that checked nothing | P11 |
| 15 | A feature enabled by a VIP default the project did not declare | behaviour differs from the DUT's build | P10 |
| 16 | Callback performs scoreboard work | couples checking to the vendor's calling context (§8) | P15 |
| 17 | Callback drives or perturbs the DUT | the measured system is not the shipping one | P16 |
| 18 | The VIP's error count feeds the project's expected results | both wrong together | P19 |
| 19 | Investigate the last VIP error | non-root-cause 96.8% of the time (§10) | review |
| 20 | Describe a VIP limitation as a PCIe limitation | a wrong belief enters the team (§9) | review |
| 21 | Invent vendor class names in project code or docs | code that cannot compile; false authority (§1) | review |
| 22 | Coverage bins defined over the vendor's enum | the model breaks on a version change (24.4 §12) | review |
| 23 | Scoreboard keyed on the vendor object handle | a reused handle rewrites entries (24.3 §3) | P6 |
| 24 | VIP reset and DUT reset not coordinated | stale observations cross the boundary | P14 |
| 25 | VIP and project timestamps from different domains compared directly | a false ordering conclusion in triage | review |
| 26 | An "unsupported feature" warning ignored | a capability mismatch mistaken for noise (§9) | P10 |
| 27 | VIP auto-configuration overrides the test's intended setting | the run tests a different configuration than intended | P8 |
| 28 | Provenance not logged | a result change cannot be diffed (§9) | P12 |
| 29 | Duplicate observations from the VIP monitor and a custom monitor | every transaction counted twice | P13 |
| 30 | Unmapped observations dropped without an end-of-test report | a partially-blind run looks clean | P20 |
| 31 | Test passes because the VIP and the DUT share an incorrect assumption | conformance agreed; intent never checked | P17 |
| 32 | Claim the VIP proves silicon correctness | it proves conformance of the modelled traffic | review |
Two counterexamples worth stating explicitly.
Mutation 2 is the one that gets approved in a planning meeting. The VIP has a mature protocol checker; the project scoreboard is expensive to build and maintain; removing it looks like sensible reuse. §14 measures the result exactly: 0 VIP flags, 6,061 real defects, green regression. The reasoning error is treating "checks PCIe thoroughly" as "checks everything" — and the missing question is the one no VIP can answer, because it never saw the descriptor (§6).
Mutation 7 is silent on both sides, which makes it the worst in the table. An adapter that maps an unrecognized kind_raw to MemWr produces a project observation that is well-formed and wrong. The VIP reports nothing — the packet was legal. The scoreboard reports nothing — it received a plausible write. §14 measured 150,381 observations mistyped, and the only evidence is a count nobody printed. §12 therefore drops and reports rather than guessing, and P4 and P20 make the drop visible at end of test.
16. Debugging
Symptom — the VIP checker fails and the DUT's waveform looks legal. Suspect configuration before the DUT (§4). Compare the VIP's resolved capability model against the DUT's build parameters — MPS, MRRS, Function count, spec mode. A VIP configured for a different operative MPS flags legal payloads, and the provenance block §12 logs makes this a one-line diff.
Symptom — the project scoreboard fails and the VIP passes. That is the expected division of labour (§6), not a contradiction. The VIP checked legality; your scoreboard checked intent. §14's 0-versus-6,061 is exactly this shape — and it localizes the defect to the DUT's decision-making rather than its packet formatting, which removes the entire protocol layer from the suspect list.
Symptom — the test passes with one VIP version and fails with another, with no DUT change. A default changed (§9). Read the provenance block from both runs and diff it. The most damaging direction is red-to-green, which nobody investigates — which is why §12 logs the resolved configuration rather than the requested one.
Symptom — no transactions reach the scoreboard at all.
Three candidates, checked in order. Wrong role (mutation 9) — the VIP is playing the same role as the DUT and nothing is being driven. Passive mode with no driver where an active partner was needed. Or the adapter is dropping everything as unmapped — read n_unmapped, which §12 reports at end of test for exactly this.
Symptom — fields are correct in the VIP log and wrong in the scoreboard. The adapter (§5, mutations 4–7). §14 measured all three field-loss modes with the VIP reporting a clean run in every one. P1–P5 catch them at the boundary, which is much cheaper than inferring the loss from downstream mismatches.
Symptom — a passive VIP appears to be driving the interface. Mode misconfiguration (§3, mutation 13). The signature is protocol errors that only appear when the VIP is instantiated, and that the VIP itself reports. P7 asserts the exclusivity; without it, the contention looks like a DUT fault.
Symptom — a flood of errors after one initial mismatch. Read the first one (§10). One protocol violation cascades into retries, sequence errors and transaction failures — and 21.4 §11 measured last-error reporting naming a non-root-cause 96.8% of the time. Correlate the first VIP error, the DUT's first-failure capture and the scoreboard's first mismatch by timestamp before opening a waveform.
Symptom — coverage changed after a VIP configuration update. Expected, and it must be explained (§9, mutation 22). If bins were defined over the vendor's enum, a version change re-numbers them. If coverage is defined over project types (24.4 §12), a genuine change in reachable behaviour is the only thing that can move the numbers — which is the point of the adapter.
17. Misconceptions
"The VIP is the testbench." It is one participant and one observer (§2).
"The VIP has a checker, so we don't need a scoreboard." 0 flags versus 6,061 real defects (§14).
"A legal TLP means the device did the right thing." Legality and intent are different questions (§6).
"VIP configuration is setup, not checking." Wrong configuration accuses a correct DUT (§4).
"A checker warning is a DUT bug." It may be a capability or revision mismatch (§9).
"Pass the vendor's transaction around; it has all the fields." Then the environment is vendor-locked and a version change is a rewrite (§5).
"An unrecognized type can default to something sensible." There is no sensible default — 150,381 observations mistyped (§14).
"Do the checking in the callback; it's right there." It couples, it can block, and the object may be reused (§8).
"A green run after a VIP upgrade is good news." It may mean checking was disabled by a changed default (§4).
"The last error in the log is the failure." A non-root-cause 96.8% of the time (§10).
"This VIP doesn't support it, so PCIe doesn't allow it." A VIP limitation is not a protocol limitation (§9).
"A commercial VIP proves the device is silicon-correct." It proves the modelled traffic conformed (§2).
18. Understanding Check
Q1. Your DMA engine writes to the wrong host address 3% of the time. Every TLP is perfectly legal. What does the VIP report, and why? Nothing (§6). Fmt, Type, Length, alignment and identity are all valid — there is no protocol rule against writing to a legal address you were not asked to write to. §14 measured 0 VIP flags against 6,061 real defects. The check that catches it needs to know what the descriptor asked for, and the VIP has never seen a descriptor.
Q2. Why does the adapter drop an unrecognized transaction kind rather than defaulting it? Because a default produces a plausible, well-formed, wrong observation that neither checker can question (§5). §14 measured 150,381 observations mistyped with the VIP reporting a clean run throughout — the packets were legal, and the corruption was entirely in the project's model. Dropping and counting makes the blindness visible, and P20 turns it into an end-of-test error rather than a silent gap.
Q3. A regression that was failing goes green after a VIP version upgrade. What do you check first?
The provenance block (§9, §12). Compare the resolved configuration from both runs — particularly checking_enable, the spec mode and the capability model. A default that changed can disable checking silently, which produces exactly this transition and is the most dangerous direction of change because nobody investigates a run that started passing.
Q4. Name one responsibility a VIP owns outright and one it structurally cannot. Owns: TLP header legality — it implements the protocol and checks conformance far more thoroughly than a project team would (§7). Cannot: whether the requested address was the intended one — that requires knowledge of the design's intent, which lives in the descriptor the VIP never observes (§6). §7's matrix exists so both facts are written down before the schedule pressure arrives.
Q5. Your passive VIP seems to be causing protocol errors. What is the likely cause and why is it confusing? The VIP is not actually passive (§3, mutation 13) and is contending with the real driver. It is confusing because the VIP's own checker reports the resulting corruption, so the evidence points at the interface and the DUT rather than at the observer. P7 asserts that a passive-configured VIP never drives, which converts this from a multi-day debug into a build-time failure.
Q6. Why does this chapter refuse to show a real vendor's API?
Because an invented svt_pcie_transaction would be code a reader would try to compile, and a fabricated API carries false authority (§1). The architecturally correct answer is the same as the honest one: vendor types should not appear outside the adapter anyway (§5), so a vendor-neutral pattern with marked integration points is both what you can verify and what you should build.
19. What's Next
This chapter placed a VIP inside an environment and drew the line around it. Chapter 24.6 UVM Architecture builds the environment around that line — the agents, the configuration that §4 insisted be single-sourced, the reset epoch that §12's adapter stamps and 24.3 §6 consumes, and the virtual sequences that coordinate configuration with traffic.
And 24.7 Error Injection owns the knobs this chapter deliberately only mentioned. A VIP typically exposes error-generation controls — bad CRC, bad sequence numbers, credit violations, malformed packets. §1 kept those out on purpose, because injecting them systematically and proving recovery is a campaign, not a configuration field.
One idea carries forward from all five chapters of this batch. 24.2 proved local contracts, 24.3 proved end-to-end conservation, 24.4 measured reach, and this chapter divided the labour with a tool that answers a different question. None of them substitutes for another — and the responsibility matrix of §7 is where a team writes that down before discovering it the expensive way.