A sequence from a ten-year-old VIP has worked in every project since. The team moves to a new simulator release that ships an IEEE 1800.2 library, and the compile stops on one line: starting_phase.raise_objection(this);. Someone suggests adding a define to bring the old name back. In the interview a week later, the question is almost the same: your sequence uses `starting_phase`. What happens on 1800.2, and what would you write instead?
This page answers that question and the dozen like it. It lists the differences between UVM 1.2 and IEEE 1800.2 that change code you write or answers you give, explains why the course writes to the subset both accept, and shows how to tell which library you are actually running.
"UVM" names two different kinds of thing, and most confusion comes from mixing them up. UVM 1.2 (2014) is an Accellera library release: a package of SystemVerilog code that was, in practice, the standard. IEEE 1800.2 (first 2017, revised 2020) is a standard document: it defines the API but ships no code. Libraries then implement it, including Accellera's reference releases (the 2020.3.x line) and the libraries simulator vendors ship.
The analogy is a language standard and its compilers. C++17 says what a conforming compiler must accept; each compiler also accepts some extras. In the same way, a 1800.2 library may keep some UVM 1.2 names for compatibility even though the standard removed them. Code that leans on those extras compiles on one library and fails on the next, which is the hook's story. Code written to the common subset, the API that both UVM 1.2 and the 1800.2 standard define, compiles everywhere.
The history of how OVM and VMM became UVM is in Why UVM exists. This page is about the last step, 1.2 to 1800.2, because that is the one that breaks code.
Topic | UVM 1.2 | IEEE 1800.2 | Portable code writes |
|---|---|---|---|
A sequence's phase |
|
|
|
Old config API |
| Removed |
|
Global end of test |
| Removed |
|
Factory handle | Global | Global variable removed |
|
The root |
| The standard documents |
|
Automatic config |
| Only if | Call |
Sequence macros |
| One |
|
Printer settings | Public |
| Keep printer tweaks in one helper |
Register predict on a bus error |
|
| Filter errored transfers yourself if the mirror matters |
The register row deserves a sentence of its own because it changes behaviour, not just names. The stock uvm_reg_predictor passes every observed transfer to the register's do_predict, and skips only the read check when the status is UVM_NOT_OK. In UVM 1.2 the mirror is then updated anyway, so a write that the slave rejected with an error still changes the model. In 1800.2 the mirror stays put. A register test that passes on one and fails on the other is often this. The register layer chapters build the predictor that makes the difference visible.
This is the UVM 1.1d idiom the hook's VIP used. A sequence raised an objection on the phase it was started in, so the test ended only when the sequence did. It compiles on UVM 1.2 with a deprecation warning, and it is not part of the 1800.2 standard.
// bad-example: UVM 1.1d style; starting_phase is not in IEEE 1800.2
task pre_body();
if (starting_phase != null) starting_phase.raise_objection(this);
endtask
task post_body();
if (starting_phase != null) starting_phase.drop_objection(this);
endtaskUVM 1.2 added two calls that 1800.2 kept. set_automatic_phase_objection(1) tells the sequence to raise an objection on its starting phase before pre_start and drop it after post_start, so the sequence no longer needs pre_body and post_body at all. set_starting_phase() tells it which phase that is, because start() from a test does not set it.
// snippet: compiles on UVM 1.2 and IEEE 1800.2
class dma_job_vseq extends uvm_sequence;
`uvm_object_utils(dma_job_vseq)
function new(string name = "dma_job_vseq");
super.new(name);
set_automatic_phase_objection(1); // object to the starting phase for the whole run
endfunction
task body();
// program a DMA job and stream its data (see the sequences chapter)
endtask
endclass
// In the test:
task run_phase(uvm_phase phase);
dma_job_vseq vseq = dma_job_vseq::type_id::create("vseq");
vseq.set_starting_phase(phase); // which phase to object to
vseq.start(null); // a virtual sequence needs no sequencer
endtaskThe config call follows the same pattern. The left form is gone from the standard; the right form is what the DMA bench's env already uses to make the output agent passive:
// snippet
// set_config_int("out_agt", "is_active", UVM_PASSIVE); // UVM 1.2 only, removed in 1800.2
uvm_config_db#(uvm_active_passive_enum)::set(this, "out_agt", "is_active", UVM_PASSIVE);Before you debug a version difference, confirm which library you are running. Every UVM library prints a release banner at time 0 unless you pass +UVM_NO_RELNOTES. This is the start of every course run of the DMA bench:
UVM_INFO .../base/uvm_root.svh(457) @ 0: reporter [UVM/RELNOTES]
*********** IMPORTANT RELEASE NOTES ************
This implementation of the UVM Library deviates from the 1800.2-2020
standard. See the DEVIATIONS.md file contained in the release
for more details.
----------------------------------------------------------------
Accellera:1800.2:UVM:2020.3.2`Accellera:1800.2:UVM:2020.3.2` is the library: Accellera's implementation of the 2020 standard, release 3.2. On EDA Playground with UVM 1.2 selected, the banner names UVM 1.2 instead.
"deviates from the 1800.2-2020 standard" is honest: even the reference library does not match the document exactly, which is one more reason to write to the common subset.
In code, Accellera 1800.2 libraries define UVM_VERSION_POST_2017, so VIP that must support both can guard the few calls that differ with an ifdef UVM_VERSION_POST_2017 block.
Interview code has to compile in the interviewer's head, whatever library they use at work. Most teams in the industry still run UVM 1.2 or a vendor 1800.2 library with compatibility switches; a few have moved fully to 1800.2. The course therefore writes every listing so that it compiles on UVM 1.2 and under the 1800.2 standard, checks the 1800.2 side with its own Verilator runs, and relies on EDA Playground with UVM 1.2 for the other side. Where the two give different behaviour, as in the predictor row, the lesson says so in a note. Compile switches exist to blur the line (UVM 1.2's UVM_NO_DEPRECATED hides deprecated API; Accellera's 2017 library had UVM_ENABLE_DEPRECATED_API to bring some back), but code that needs a switch is code that will break on the next project.
Back to the hook, and to how that kind of quick fix usually plays out. The team adds a compatibility define to the build script, which brings starting_phase back on that library. The VIP compiles, the regression goes green, and everyone moves on. Months later the same VIP is integrated by a partner whose library has no such switch. Their compile fails in every sequence that used the old idiom, close to a milestone, and they end up patching a supplier's code under pressure.
The define did not fix anything; it moved the failure to someone else's schedule. The real fix was a few lines per sequence: delete pre_body and post_body, set set_automatic_phase_objection(1) in the constructor, and have the starting code call set_starting_phase(phase). A useful habit when you own VIP is to compile it once against UVM 1.2 with UVM_NO_DEPRECATED defined: whatever fails there will fail on 1800.2 too.
use_automatic_config() to return 0 for speed. Does is_active still reach the agent from config_db?Yes, as long as the agent's build_phase calls super.build_phase(): uvm_agent::build_phase looks up is_active in config_db itself, separately from automatic configuration. What stops working are fields that relied on field macros being applied automatically; those now have to be read with explicit uvm_config_db::get calls.
mirror() check fails on UVM 1.2 but passes on a 1800.2 library. Why?With the stock predictor, the errored write still reaches do_predict. UVM 1.2's do_predict updates the mirror regardless of status, so the model now holds the rejected value and the next mirror check disagrees with the hardware. The 1800.2 do_predict skips the update on UVM_NOT_OK. To get the same behaviour on 1.2, filter errored transfers in pre_predict or in a custom predictor. Note that the field must be one whose policy accepts writes: a field modelled RO keeps its mirror on a write in both libraries, error or not, so it cannot show the difference.
uvm_top.print_topology(); the course's bench calls uvm_root::get().print_topology(). Which is right?Both work on every current library, because libraries still provide the uvm_top variable. The IEEE standard documents uvm_root::get() (and the core service's get_root()), so new code should use it, which is what the bench does. Recognise uvm_top when you read older code, and do not rewrite working code just to change it.
Next: you have the map, the design, the tools and the dialect. Start the course proper with UVM Foundations, and its first full testbench.