Your team has bought a commercial APB VIP. Its monitor publishes vnd_apb_txn, not the bench's apb_item, so dma_scoreboard cannot listen to it. Its configuration class has forty fields with names unlike yours. It prints a warning on every transfer because your APB has no PPROT signal, and the next release lands next quarter. Wire it in naively and the vendor's names spread into every test, the warning count becomes meaningless, and the upgrade touches fifty files. How do you integrate it so that none of that leaks?
Treat third-party VIP as a sealed component and put three small pieces of your own around it. A transaction adapter converts its transactions into yours, so your scoreboards never see the vendor's types. A config map translates your env config into its configuration, so tests never touch vendor fields. A message policy, built from report catchers or severity overrides, decides which of its messages count. Around all of it, compile isolation and a pinned version keep upgrades a one-file change.
A travel power adapter is the picture. You do not rewire the hotel, and you do not open your laptop's charger. A small adapter between the two converts one plug shape into the other, and when you travel to a different country you change only the adapter.
To make this concrete without a licence, the examples treat the bench's APB monitor as if a vendor had shipped it under the names vnd_apb_agent, vnd_apb_monitor, vnd_apb_txn and vnd_apb_cfg. Those names, and their fields, are invented; a real VIP's documentation gives you the real ones. The vendor transaction has its own field names and enums: kind (VND_READ, VND_WRITE), a 32-bit address, wdata, rdata and resp (VND_OKAY, VND_SLVERR).
A uvm_subscriber is a component with one analysis export and a write method you implement. Connect the vendor monitor's analysis port to it, convert each transaction into a fresh apb_item, and publish that on your own port. Your scoreboard connects to the adapter exactly as it would to your own monitor.
// snippet
class apb_vip_adapter extends uvm_subscriber #(vnd_apb_txn);
`uvm_component_utils(apb_vip_adapter)
uvm_analysis_port #(apb_item) ap;
function new(string name, uvm_component parent);
super.new(name, parent);
ap = new("ap", this);
endfunction
// Called by the vendor monitor. Convert at once into a new object; never keep t.
function void write(vnd_apb_txn t);
apb_item it = apb_item::type_id::create("it");
it.addr = t.address[7:0];
it.write = (t.kind == VND_WRITE);
it.data = t.wdata;
it.rdata = t.rdata;
it.slverr = (t.resp == VND_SLVERR);
ap.write(it);
endfunction
endclass
// In the env's connect_phase:
// apb_vip.monitor.item_collected_port.connect(adapt.analysis_export);
// adapt.ap.connect(sb.apb_export);The adapter is also where you handle mismatches between the two models: a 32-bit vendor address cut to the bench's 8 bits, an error response mapped to slverr, or a vendor burst split into several of your items. Keep the conversion lossless for everything your checkers read, and document what is dropped.
One function in the env translates your env config into the vendor's configuration object, sets it where the vendor's documentation says the VIP will look for it, and is the only code in the bench that names vendor fields.
// snippet
// In the env's build_phase, before creating the VIP agent.
function void map_vip_cfg();
vnd_apb_cfg vcfg = vnd_apb_cfg::type_id::create("vcfg");
vcfg.is_master = (cfg.apb_active == UVM_ACTIVE); // our knob, their field
vcfg.addr_width = 8;
vcfg.has_pprot = 0; // our APB has no PPROT
uvm_config_db#(vnd_apb_cfg)::set(this, "apb_vip.*", "cfg", vcfg);
endfunctionTests set cfg.apb_active, a field of your env config, and never see vnd_apb_cfg. If the vendor is replaced, or renames a field in a new release, this function and the adapter are the whole change.
The first tool is the built-in one: set_report_severity_id_override(UVM_WARNING, "VND_APB_PPROT", UVM_INFO) on the vendor component turns one message ID into an info for that component. Use a report catcher when the decision depends on the message itself (only for one address, only before reset ends) or must span many components. A catcher is an object whose catch() method sees every message from the objects it is attached to, and may change it or swallow it.
// snippet
class vnd_apb_demoter extends uvm_report_catcher;
`uvm_object_utils(vnd_apb_demoter)
int unsigned n_demoted;
function new(string name = "vnd_apb_demoter");
super.new(name);
endfunction
function action_e catch();
if (get_severity() == UVM_WARNING && get_id() == "VND_APB_PPROT") begin
set_severity(UVM_INFO); // still in the log, no longer counted as a warning
n_demoted++;
end
return THROW; // pass every message on; CAUGHT would drop it
endfunction
endclass
// In the env's connect_phase, scoped to the vendor's monitor only:
// demoter = vnd_apb_demoter::type_id::create("demoter");
// uvm_report_cb::add(apb_vip.monitor, demoter);Scope it. uvm_report_cb::add(obj, catcher) attaches to one object; add(null, catcher) attaches to every report object, and runs for every message in the simulation. A global catcher matching a common ID can silence your own components.
Demote, do not drop. Changing severity keeps the message in the log for the day it matters. Never demote errors from a VIP's protocol checker without a written waiver.
Count what you demote and print the count in report_phase, so a sudden jump is visible.
Never edit vendor source. Workarounds go in the wrapper; bugs go to the vendor as tickets. An edited VIP cannot be upgraded.
Pin the version in one place, the path or manifest your compile scripts read, and record it in every log. If the vendor exports a version constant, check it in the env's build and fatal on a mismatch.
Compile it as its own package (often its own library), and prefix your own package and macro names. A vendor apb_pkg and yours cannot both exist; neither can two ` define APB_DATA_W ``.
Match the UVM version. A VIP delivered for UVM 1.2 and a bench on an IEEE 1800.2 library, or the reverse, is a compile matrix to check before the purchase, not after. Encrypted VIP also ties you to the simulator versions the vendor supports.
Ask | Why it matters |
|---|---|
Which UVM versions and simulators, and which releases of each? | Your bench's library and tools must be on the list. |
Does the monitor publish a new object per transaction? | If it reuses one, every subscriber must copy on |
Does anything in the VIP raise objections? | Monitors should not; a VIP that objects can hold your test open. |
What are the message IDs, and which are protocol errors? | You need the list to write a message policy and waivers. |
Are protocol checks SVA or class code, and can each be disabled? | Errors you inject on purpose must not fail the test. |
Active and passive modes, reset in mid-transfer, coverage model? | Vertical reuse needs passive mode; reset tests need clean recovery. |
Source or encrypted, release cadence, compatibility policy? | Decides how painful each upgrade will be. |
This is a real run of the smoke test with a scratch variant of dma_env that adds the stand-in VIP monitor, the adapter, the demoter and a second scoreboard fed only through the adapter. The vendor monitor warns on every transfer because has_pprot is 0:
adapt apb_vip_adapter - @338
apb_vip vnd_apb_agent - @330
monitor vnd_apb_monitor - @580
vip_sb dma_scoreboard - @364
...
UVM_INFO dma_vip_demo_tb.sv(56) @ 75000: uvm_test_top.env.apb_vip.monitor [VND_APB_PPROT] PPROT not connected: assuming 3'b000
...
UVM_INFO dma_tb.sv(411) @ 655000: uvm_test_top.env.sb [SB] 8 words matched, 0 mismatched
UVM_INFO dma_tb.sv(411) @ 655000: uvm_test_top.env.vip_sb [SB] 8 words matched, 0 mismatched
UVM_INFO dma_vip_demo_tb.sv(174) @ 655000: uvm_test_top.env [VIP] demoted 6 vendor warnings
--- UVM Report catcher Summary ---
Number of demoted UVM_WARNING reports: 6
--- UVM Report Summary ---
UVM_WARNING : 2
UVM_ERROR : 0vip_sb matched the same 8 words as the bench's own scoreboard, but learned KEY only through the vendor monitor and the adapter. The adapter is lossless for what the scoreboard reads.
The six VND_APB_PPROT messages appear as UVM_INFO. The library prints its own catcher summary, a useful audit line. Without the catcher the same run reports UVM_WARNING : 8; the 2 that remain come from the UVM library in this Verilator build.
Some VIP monitors fill and publish one transaction object, over and over, which the stand-in monitor above also does. An engineer writes a quick coverage collector that stores the vendor's handles in a queue and samples them at the end of the test. In the same run, it reports this:
UVM_INFO dma_vip_demo_tb.sv(119) @ 655000: uvm_test_top.env.store [STORE] 6 transfers stored, addresses: 04 04 04 04 04 04The bus really carried writes to 0C, 08 and 00, then accesses to 04. Every stored handle points to the same object, which holds the last transfer. The adapter is immune because it converts each transaction into a new apb_item inside write, before the monitor overwrites it. The fix for the collector is the same: copy or convert in write, never store the handle. And it goes on the vendor question list: a monitor that publishes a fresh object per transaction is the convention for a reason.
In the adapter: map rdata only when kind == VND_READ, and set it to 0 otherwise. The adapter is the one place that knows both models, so the bench's checkers keep seeing clean apb_items, and the quirk is documented next to the conversion instead of being rediscovered by every subscriber.
uvm_report_cb::add(null, demoter) to save a line. What is the risk?A null object attaches the catcher to every report object in the simulation, so it runs on every message and demotes any VND_APB_PPROT warning from anywhere, including a future component that reuses the ID for something real. Scoping it to the vendor monitor makes it cheap and makes its effect predictable.
Next: the env now holds your agents and a vendor's. Something has to coordinate them, and Where the Virtual Sequencer Lives decides who owns the handles to their sequencers.