A VIP moves from UVM 1.2 to a 1800.2 library. It compiles on the first try, the regression is green, and the team relaxes. Three months later a register test that used to fail on a slave error passes, and a sequence that used to report a response-queue overflow is silent. Nothing broke the compile; the library changed what the same code does. Which differences stop the compile, which change behaviour without a word, and how do you write code that cannot be caught by either?
UVM 1.2 (2014) is an Accellera library: code. IEEE 1800.2 (2017, revised 2020) is a standard: a document that defines the API and ships no code. Reference libraries implement the standard: Accellera's 2020.3 line, which this course runs (Accellera:1800.2:UVM:2020.3.2), and the libraries simulator vendors ship. The overview, with the history, is in UVM 1.2 vs IEEE 1800.2 at a glance; this lesson is the detailed table.
Think of a rulebook's new edition and a publisher's printing of it. The edition dropped some old rules. The publisher kept many of them in an appendix so old games still work, and marked them "for compatibility". If you learn from that printing, old habits keep working, until you play with someone who has a different printing. The 2020.3 library's appendix is large: its source carries 155 @uvm-compat marks, API kept for 1.2 or 1.1d code that the standard does not define.
Every "2020.3 library" entry was checked in the library source, and the compile column by probe files compiled against it with Verilator.
API | UVM 1.2 | 2020.3 library (IEEE 1800.2) | Portable code |
|---|---|---|---|
| Deprecated | Not in the standard; kept as |
|
| Documented | Kept as |
|
Global | Deprecated | Gone: |
|
| Deprecated | Not in the standard; kept as |
|
| Deprecated | Gone (only a compat | Phase objections; |
| Concrete classes | Abstract: | Extend them; |
Abstract registration | None |
| Only in 1800.2-only code |
|
| Standard: one |
|
Automatic config |
| Only if | Call |
Response queue overflow |
| Report starts disabled: overflowing responses are dropped silently |
|
Bus error and the register mirror |
|
| Filter errored transfers in |
Big-endian register maps | Endianness orders bus beats only | Also byte-reverses each access ( | Avoid big-endian maps, or test the byte order on your library |
Register door names |
|
| The old names compile on both today |
Event callbacks |
|
|
|
Packer stream format | 1.2 layout | Reworked; round trips hold but streams differ |
|
Field automation | Field macros generate | Field macros generate | Hand-written |
Most portable code is simply code that avoids the left column. Where a call really differs, guard it with the version macros. Accellera 1800.2 libraries define UVM_VERSION_POST_2017 (and the 2020.3 library UVM_VERSION_POST_2020_2_0 and later); UVM 1.2 defines neither.
// snippet: compiles on UVM 1.2 and IEEE 1800.2
class dma_cfg_seq extends uvm_sequence #(apb_item);
`uvm_object_utils(dma_cfg_seq)
function new(string name = "dma_cfg_seq");
super.new(name);
set_automatic_phase_objection(1); // instead of starting_phase in pre_body
set_response_queue_error_report_disabled(0); // 1.2 name; the 2020 library keeps it, so both report overflows
endfunction
task body();
apb_item it = apb_item::type_id::create("it");
start_item(it); // instead of the uvm_do_with family
if (!it.randomize() with { addr == LEN; write == 1; })
`uvm_fatal("RAND", "apb_item randomize failed")
finish_item(it);
endtask
endclass
// In a component: the root and the factory without globals
// uvm_root::get().print_topology();
// uvm_factory::get().print();
`ifdef UVM_VERSION_POST_2017
// 1800.2-only code goes here, for example a component that returns 0 from use_automatic_config()
`endifEach line below is one small probe file compiled against the course's 2020.3.2 library. The probes that compile are the dangerous ones: they hide 1.2 habits that a stricter library would reject.
compiles uvm_top.print_topology();
compiles set_config_int("*", "x", 1); (in a component)
compiles if (starting_phase != null) starting_phase.raise_objection(this);
compiles `uvm_do_with(i, {a == 1;})
compiles uvm_path_e p = UVM_DEFAULT_PATH;
compiles e.add_callback(cb);
fails factory.print(); Can't find definition of scope/variable: 'factory'
fails uvm_test_done.raise_objection(null); Can't find definition of scope/variable: 'uvm_test_done'
fails global_stop_request(); Can't find definition of task/function: 'global_stop_request'
fails uvm_sequence_base sb = new("sb"); Illegal to call 'new' using an abstract virtual class 'uvm_sequence_base'UVM 1.2: +define+UVM_NO_DEPRECATED compiles the deprecated API out. Code that builds clean with it is on the path to 1800.2. This is the cheapest portability check you can run.
Earlier 1800.2 reference releases (2017-1.x): +define+UVM_ENABLE_DEPRECATED_API brought some old API back. In the 2020.3 kit that define appears only in a readme (src/deprecated/readme.important): the compatibility API is compiled unconditionally and marked @uvm-compat. On 2020.3, a clean compile proves nothing about portability.
The compatibility package, uvm_compat_pkg (shipped beside the 2020.3 library in compat/) supplies the 1.2 packer behaviour and a concrete proxy sequence for changes that could not coexist in uvm_pkg.
A migration plan that holds up: compile against UVM 1.2 with UVM_NO_DEPRECATED and against the target 1800.2 library; grep for the left column of the table; then run the same seeds on both libraries and diff the logs. The diff finds the behaviour rows, which no compiler will.
Take the hook's response-queue symptom. A driver calls item_done(rsp) for every write, but the write sequence never calls get_response(). On UVM 1.2, the ninth write overflows the default queue of 8 and the library reports UVM_ERROR, which a team usually notices and fixes. On the 2020 reference library the same overflow is dropped silently, because the report flag starts disabled. Migrating did not fix the bug; it removed the only message that pointed at it. If a later sequence does call get_response() for its read data, it can receive a stale write response and read the wrong value.
The register row is the same kind of trap in reverse: a write that the slave answers with PSLVERR leaves the mirror changed on 1.2 and unchanged on 1800.2, so a mirror(UVM_CHECK) that failed on one library passes on the other. Both are on the fact sheet for a reason. The defence is the dual-library regression diff above, plus code that does not depend on either default: item_done() without an argument when the data travels in the request, and an explicit pre_predict filter when a bus error must not reach the mirror.
The 2020.3 library compiles a large compatibility layer unconditionally (155 @uvm-compat marks), including starting_phase, set_config_*, uvm_top and the old uvm_do macros. A clean compile there says nothing about a stricter library. Compile on UVM 1.2 with UVM_NO_DEPRECATED defined, and grep for the API the standard does not define.
mirror(UVM_CHECK) reports a mismatch; on the 2020 library it does not. Which library is right?Both follow their own code. The stock predictor passes the transfer to do_predict in both; 1.2's do_predict updates the mirror regardless of status, so the model now holds the rejected value, while 1800.2's skips the update on UVM_NOT_OK and warns PREDICT_NOK. Decide what the mirror should do and make it explicit with a pre_predict filter, so the result does not depend on the library.
uvm_sequence_base seq = new("seq") to make a placeholder sequence for a start_item call. Why does it fail on the 2020 library, and what is the portable replacement?1800.2 made uvm_sequence_base and uvm_sequence abstract (virtual class), so new on them is illegal. Declare a small concrete subclass registered with uvm_object_utils, or use uvm_compat_pkg::uvm_compat_proxy_sequence on libraries that ship the compatibility package.
Next: the library is settled; now make it fast. Performance at scale measures where a UVM bench spends its time.