DPI-C from scratch · Reference
What doesn't work (or works differently) in Verilator 5.052
Every DPI-C behavior in this tutorial where Verilator 5.052 departs from the LRM, from commercial simulators, or from what the book assumes, in one place. Nothing here is from memory: each row links to the step and the lab file that measures it (check.py asserts it, or NOTES.md records the measurement).
The 10 that will bite you
Silent wrong results first: a build error costs you a minute, a silent one costs you a tape-out review.
- Outputs C never writes come back as garbage — silent wrong result.
- Missing
contextcan call the wrong instance — silent wrong result. shortrealis passed asdouble— silent wrong result.- X and Z arrive in C as 0 — silent wrong result.
--threads: DPI runs on other OS threads — silent wrong result.- Without the generated header, a parameter mismatch links silently — silent wrong result.
pureis never exploited — silent wrong result.$erroraborts at the first error, andfinalblocks don't run — aborts / crashes at run time.- Dynamic arrays and queues can't be open-array actuals — breaks the build.
- No timing controls in exported tasks — breaks the build.
Three habits remove most of this page: include Vtop__Dpi.h in every C file, write every output on every path, and run with +verilator+error+limit+N. The rest are portability notes: code that passes here can still break on Questa, VCS or Xcelium, mostly because Verilator is 2-state and more forgiving with illegal code.
How to read the tables
| Severity | Meaning | Count |
|---|---|---|
| silent wrong result | Builds and runs; a value, a call count or an instance is wrong, and nothing tells you. | 21 |
| aborts / crashes at run time | Segfault, abort, or a skipped final. | 3 |
| breaks the build | Verilator, the C++ compiler or the linker stops you. Annoying, but safe. | 16 |
| just different | Works, but not the way the LRM, the book or another simulator does. | 12 |
| Kind | Meaning | Count |
|---|---|---|
| LRM violation | Verilator accepts code that IEEE 1800-2023 forbids. It will not port. | 5 |
| LRM deviation | Legal code, but Verilator's result or generated header differs from what IEEE 1800-2023 specifies. | 3 |
| unsupported feature | Legal SV that Verilator 5.052 rejects. | 6 |
| implementation-defined | The LRM leaves it open; don't depend on either answer. | 26 |
| undefined behavior | Your code breaks an LRM rule whose effect is undefined; any outcome is "correct", including the quiet one. | 1 |
| bug | Wrong per its own spec, or a tool crash. | 3 |
| platform / toolchain | macOS, clang or CPython, not Verilator itself. | 5 |
| language gotcha | Plain SystemVerilog, met while writing the labs. | 3 |
"Proof" links go to the step guide (number) and to the lab file. Run cd NN_*/lab && python3 check.py to reproduce.
Build and link (8)
| Item | Expected (LRM / commercial) | Verilator 5.052 | Workaround | Proof |
|---|---|---|---|---|
| .c files are compiled as C++ breaks the build · implementation-defined | Commercial flows compile .c as C; a plain C definition links. | Every user file goes through $(CXX) (clang: treating 'c' input as 'c++'). Without C linkage the symbol is mangled and the link fails: Undefined symbols for architecture x86_64: "_factorial". C idioms that are invalid C++ (implicit void* from malloc) fail too. | Wrap definitions in extern "C" { }, or #include "Vtop__Dpi.h" first (its prototypes give the definitions C linkage). Write C that also compiles as C++. | 01 · noextern.c03 · nocxx.cpp |
| Without the generated header, a parameter mismatch links silently silent wrong result · implementation-defined | Nothing in the LRM checks the C side; any simulator behaves the same. | C linkage carries no parameter types, so the book's counter7(c7* inst, …) links against the SV chandle import and runs. With the header included, a return-type or parameter mismatch inside extern "C" is a compile error (conflicting types); a parameter mismatch defined outside extern "C" becomes a C++ overload and fails only at link (Undefined symbols "_negate"). | Always #include "Vtop__Dpi.h" (it is in the -Mdir, found with no extra flag) and copy prototypes from it. | 01 · mismatch_hdr2.c03 · book_sig.c |
import "DPI" is rejectedbreaks the build · implementation-defined | "DPI" selects the deprecated SV3.1a packed-argument passing (opaque handles, H.14), not just an old spelling. §35.5.4: the tool shall give a warning or an error; Verilator's error is compliant. | %Error-UNSUPPORTED: Unsupported DPI type 'DPI': Use 'DPI-C'. | Write "DPI-C". | 01 · legacy.sv |
Importing strlen (or anything returning size_t) directly failsbreaks the build · implementation-defined | longint unsigned maps to a 64-bit unsigned C type. | Verilator emits unsigned long long strlen(const char*), which clashes with size_t strlen from <string.h> (included by the generated code). No SV type maps to unsigned long. libm sin/pow/fabs/log10 and libc access import fine. | Write a one-line C wrapper with your own name. | 01 · ex/ex5_strlen.sv |
| Same C name, two signatures breaks the build · implementation-defined | Illegal everywhere (the LRM requires one signature per C name). | Duplicate declaration of DPI function with different signature, followed by an internal error. | One import per C name, in one package. | 01 · dup.sv |
A comment starting with Verilator: is a pragmabreaks the build · implementation-defined | A comment is a comment. | %Error-BADVLTPRAGMA: Unknown verilator comment (any case of the first word). | Don't start comments with the tool's name. | 07 08 |
| Warnings disappear on an up-to-date build just different · implementation-defined | — | Verilator skips verilation (and its warnings) when the -Mdir is current. C compiler warnings (-Wreturn-stack-address) appear only in the build log, replayed by ccache on hits. | Clean build in CI scripts that grep warnings; read the build log, not just the run log. | 02 · run.sh04 |
macOS: the book's <wait.h> and WEXITSTATUS(system(cmd))breaks the build · platform / toolchain | Sample 12.52 compiles on Linux. | <wait.h> does not exist; WEXITSTATUS on an rvalue does not compile as C++ (cannot take the address of an rvalue). | #include <sys/wait.h>; store system()'s result in a variable first. | 07 · ex/spawn.c |
Types and directions (5)
| Item | Expected (LRM / commercial) | Verilator 5.052 | Workaround | Proof |
|---|---|---|---|---|
shortreal is passed as doublesilent wrong result · implementation-defined | Table H.1 (H.7.4): shortreal ↔ float. | Warning SHORTREAL; Vtop__Dpi.h declares double. A C definition with float links silently and reads garbage (0.1 arrives as -1.59e-23). | Use real/double. If you must use shortreal, include the header: the mismatch becomes a compile error. | 02 · types.c |
ref arguments on imports are acceptedsilent wrong result · LRM violation (accepts illegal code) | IEEE 1800 forbids ref in imports; commercial tools reject it. | Accepted with no warning even with -Wall, mapped to int*. | Use inout (or output). Code that works here will not port. | 01 · ref.sv03 |
| Outputs C never writes come back as garbage silent wrong result · implementation-defined | The value of an unwritten output is undetermined (§35.5.1.2, H.6.3); tools differ. | The generated wrapper passes an uninitialized stack temporary (svBitVecVal v__Vcvt[2], o__Vcvt) and copies it back unconditionally: the SV variable gets stale stack data (measured: the previous call's 40-bit temp). | Write every output on every path, including error paths. | 02 · dir.c05 · outarg.c |
| Unused upper bits and sign extension just different · implementation-defined | The LRM does not guarantee zeroed unused bits on input, nor masking on output. | Inputs arrive with zero unused bits and no sign extension; output copy-back masks to the declared width (VL_SET_Q_SVBV(40, …), scalars 1U & x). Sloppy C works here. | Mask on input (SV_MASK) and write only the declared bits, so the code survives Questa/Xcelium. | 02 · vec.c |
SV_GET_SIGNED_BITS tests the wrong bitsilent wrong result · bug in Verilator / svdpi.h | Sign-extend an N-bit value from bit N-1. | The macro in vltstd/svdpi.h (Annex I text) tests bit N: SV_GET_SIGNED_BITS(0x80, 8) = 128. | Sign-extend by hand: (int32_t)(v << (32-N)) >> (32-N). | 02 · vec.c |
4-state values (3)
| Item | Expected (LRM / commercial) | Verilator 5.052 | Workaround | Proof |
|---|---|---|---|---|
| X and Z arrive in C as 0 silent wrong result · implementation-defined | 4-state simulators deliver X/Z in bval; Sample 12.12's checks fire. | Verilator is 2-state: an uninitialized reset is seen as 0 (the counter counts), 7'b000_zzxx is loaded as 0. None of the sample's error branches can fire. | Don't validate X/Z in C under Verilator; check in SV with $isunknown on a 4-state simulator, or document the limitation. | 02 · counter4.c |
A 1'bz literal as an argumentbreaks the build · unsupported feature | Legal; C sees sv_z. | %Error-UNSUPPORTED: Unsupported tristate construct: ARG. | Pass a variable (which then arrives as 0, see above). | 02 · zlit.sv |
bval written by C is droppedsilent wrong result · implementation-defined | C can return X/Z through svLogicVecVal outputs. | Output bval bits are ignored; svGetLogicArrElem*VecVal writes bval = 0. | Report errors through a return status, not by driving X. | 02 · counter4.c04 |
Arrays and open arrays (8)
| Item | Expected (LRM / commercial) | Verilator 5.052 | Workaround | Proof |
|---|---|---|---|---|
| Dynamic arrays and queues can't be open-array actuals breaks the build · unsupported feature | §35.6.1 (with §7.7): any unpacked array (fixed, dynamic, queue) may be passed to input T a[]. | %Error-UNSUPPORTED: Unsupported: DPI argument of type DYNARRAYDTYPE (or QUEUEDTYPE), sometimes followed by Internal Error: ../V3Task.cpp:759. | Copy into a fixed-size buffer and pass the count as an extra int (the contract in golden_pkg.sv). | 04 · kinds.sv06 · golden_pkg.sv08 · tb_dyn_fail.sv |
| A slice as an open-array actual breaks the build · unsupported feature | Legal. | %Error: Internal: Unexpected Call for a[1:2]. | Copy the slice into a fixed buffer. | 04 · kinds.sv |
An assignment pattern '{…} as an open-array actualbreaks the build · unsupported feature | Legal. | Unsupported: Assignment pattern applies against non-struct/union data type: 'UNSIZEDARRAYDTYPE'. | Assign the pattern to a fixed array variable first. | 08 |
| Element types must match exactly breaks the build · implementation-defined | Type-equivalent element types are interchangeable. | logic signed [15:0] c[8] passed to input shortint x[8]: %Error-UNSUPPORTED: Shape of the argument does not match the shape of the parameter. | Declare the actual with the formal's exact type. | 08 · tb_shape_fail.sv |
| Packed open-array elements are stored natively silent wrong result · LRM deviation | Packed elements of an open-array actual are in canonical form (H.7.3); if a tool stores them otherwise, svGetArrElemPtr* shall return NULL (H.12.4). | bit[7:0] = 1 byte, bit[12:0] = 2, bit[63:0] = 8, bit[99:0] = 16. Casting a svGetArrElemPtr1 result to svBitVecVal* reads neighbouring elements for widths ≤ 32. | Use svGetBitArrElem1VecVal / svPutBitArrElem1VecVal, never a raw pointer cast. | 04 · packed.c |
| Unwritten elements of output arrays silent wrong result · implementation-defined | Undetermined. | Open-array output temporaries arrive zero-filled and are copied back whole (unwritten elements become 0). Fixed-size output arrays return garbage in unwritten elements. inout keeps them. | Write every element, or use inout. | 04 · outdir.c |
| An input array pointer aliases the SV variable silent wrong result · implementation-defined | Pointers into arguments are valid only during the call. | svGetArrayPtr() of an input fixed array points at the SV variable itself: a pointer C/Python keeps sees later SV writes. Output open arrays are a fresh stack temporary on every call. | Copy what you need before returning; never keep argument pointers. | 08 |
| Out-of-range element access just different · implementation-defined | Undefined. | Run-time %Warning: DPI svOpenArrayHandle function … and NULL. | Iterate svLow … <= svHigh; check for NULL. | 04 · openarr.c |
Structs and strings (2)
| Item | Expected (LRM / commercial) | Verilator 5.052 | Workaround | Proof |
|---|---|---|---|---|
| Unpacked structs as DPI arguments breaks the build · unsupported feature | Allowed (C sees a struct with the LRM's layout rules). | Unsupported: DPI argument of type REFDTYPE '<name>'. | Pass a packed struct (as svBitVecVal*) or separate members. | 04 · structs.sv |
| Returned strings are copied immediately just different · implementation-defined | A returned const char* must stay valid until the next call; tools may read it later. | Verilator copies it into a std::string at once: one static buffer survives two calls in one $display. | Don't rely on it: return static or caller-owned storage, as H.8.10 requires. | 04 · strings.c |
chandle, threads and ordering (3)
| Item | Expected (LRM / commercial) | Verilator 5.052 | Workaround | Proof |
|---|---|---|---|---|
| chandle behaves like a 64-bit integer silent wrong result · LRM violation (accepts illegal code) | IEEE 1800-2023 §6.14 forbids arithmetic, continuous assignment and events on chandles. | Under verilator --lint-only -Wall: h + 1, assign g = h and @(h) are accepted silently; int x = h only warns (WIDTHTRUNC). Only a chandle inside a packed struct is an error. | Treat chandles as opaque: assign, compare to null, pass. Nothing else. | 03 · neg/ |
--threads: DPI runs on other OS threadssilent wrong result · implementation-defined | Single-threaded simulators call DPI from one thread. | Default --threads-dpi pure: pure imports run on several threads concurrently; non-pure calls are serialized onto one thread that is not the thread that ran initial (3200/3200 calls off the init thread with --threads 4). Embedded CPython without PyGILState_Ensure segfaults. | Don't mark stateful C pure; use --threads-dpi none, or attach per call (PyGILState_Ensure/Release). | 03 · threads.sv07 · threads.c |
Order of same-time calls and final blocksjust different · implementation-defined | Unspecified by the LRM. | Two always blocks calling the same import at the same time: the second-declared instance ran first (u2 before u1). final: top's ran before the instances' (u2, then u1). | Never depend on it; free shared C state in one place. | 03 · static_counter.sv |
Export, context, scopes and time (8)
| Item | Expected (LRM / commercial) | Verilator 5.052 | Workaround | Proof |
|---|---|---|---|---|
| No timing controls in exported tasks breaks the build · unsupported feature | Exported tasks may consume time (Sample 12.40). | Any #, @, or delay nested in a called task: %Error-UNSUPPORTED: … Unsupported: Timing controls inside DPI-exported tasks. | Pull model: SV owns the loop and the waiting; C is a zero-time iterator returning the next command. | 05 · xtime.sv05 · mem_pull.c |
Missing context can call the wrong instancesilent wrong result · undefined behavior | Calling an export from a non-context import is an error (§35.5.3); its behavior is undefined (H.9.3). | Fatal only if no scope was ever set: Testbench C called 'X' but scope wasn't set, preceded by %Warning: DPI C Function called by Verilog DPI import with missing 'context' keyword. If an earlier context call left a scope, the export of that instance runs with no error. | Every import that reaches an export, VPI or the scope API is context. No exceptions. | 05 · noctx.c |
| An imported function may call an exported task silent wrong result · LRM violation (accepts illegal code) | Illegal: functions can't call tasks, across DPI too. | Accepted. | Import as task when the C side calls an exported task. | 05 · fn2task.sv |
Exported tasks return void in the headerjust different · LRM deviation | §35.8, H.8.2: an exported task has the same C header as an import task, returning int (the disable flag). | Vtop__Dpi.h declares exported tasks void, imported tasks int; svIsDisabledState() links and returns 0. | Include the header and never use an exported task's return value. | 05 · xtask.c |
svGetScopeFromName wants the full %m namejust different · implementation-defined | Name format is tool-specific. | top.b2, top.gen[0].u work; TOP.top.b2 and relative names return NULL. | Get names from SV ($sformatf("%m")) instead of hard-coding them. | 05 · scopes.c |
context costs ~6× a plain calljust different · implementation-defined | — | Per call (5 M calls, idle machine): generic ≈ pure ≈ 1.3–1.8 ns, context ≈ 10 ns, context + 1 export ≈ 18–21 ns. | Irrelevant unless you call per cycle in a hot loop; batch if so. | 05 · bench.c |
svGetTime ignores the time type and the scopesilent wrong result · LRM deviation | H.13: vpiScaledRealTime returns real time in the scope's unit; svGetTimeUnit returns that scope's unit. | Always simulation time in precision units in high/low; t.real is never written (garbage if uninitialized); a module with timeunit 1us reports −9. | Use vpiSimTime plus svGetTimePrecision(NULL, …) and scale in C. | 05 · svtime.c |
| A dynamic-array formal on an export crashes Verilator breaks the build · bug in Verilator / svdpi.h | Erroneous (§35.5.6; exports can't have open arrays either, §35.5.6.1): a compile error. | %Error: Verilator internal fault, sorry. (no message pointing at the export). | Exports take fixed-size arrays or scalars; pass sizes explicitly. | 05 · xdyn.sv |
pure and combinational calls (3)
| Item | Expected (LRM / commercial) | Verilator 5.052 | Workaround | Proof |
|---|---|---|---|---|
pure is never exploitedsilent wrong result · implementation-defined | The LRM lets a tool drop or merge calls to a pure function. | Every call executes: discarded results, duplicates (f(2)+f(2)), loop-invariant calls, also with -O3. A pure function with hidden state ran all 13 calls. A lying pure goes unnoticed until another tool uses its license. | Mark pure only what really is (CRC, factorial). Never stateful models. | 01 · pure.sv03 · pure_lie.sv05 · pure.sv |
Call count in always_comb / assignjust different · implementation-defined | Evaluation count is implementation-defined; event-driven tools may evaluate more often. | Measured one call per input change per call site (2 sites × 10 changes → 2 + 20 calls). A DPI function in a continuous assign ran 11 times for 5 input changes in step 05's lab. | Keep stateful models out of combinational code. | 01 · comb.sv05 · comb.sv |
pure is accepted where the LRM forbids itsilent wrong result · LRM violation (accepts illegal code) | Only nonvoid functions without output/inout can be pure; a task never (§35.5.1.3, §35.5.2). | pure function void, pure task and a pure function with an output all build and run, no warning. | Use pure only on side-effect-free functions that return a value. | 01 · pure_rules.sv |
Embedded Python (4)
| Item | Expected (LRM / commercial) | Verilator 5.052 | Workaround | Proof |
|---|---|---|---|---|
python3-config --ldflags is not enoughbreaks the build · platform / toolchain | — | It lacks -lpython3.14: the link fails without --embed. | python3-config --embed --ldflags (the py_flags helper in common/lab.sh). | 07 · run.sh |
| numpy can be imported once per process aborts / crashes at run time · platform / toolchain | — | Py_FinalizeEx + re-init + import numpy: cannot load module more than once per process. A first import costs ~0.1 s. | Initialize once, finalize once (at exit). | 07 · lifecycle.c |
| Any CPython call before init segfaults aborts / crashes at run time · platform / toolchain | — | E.g. PyLong_FromNativeBytes before Py_Initialize: segfault on the first DPI call. | Route every bridge entry point through the function that initializes. | 08 · golden_py.c |
| Python and C stdout interleave wrongly just different · platform / toolchain | — | Without flushing, Python's text appears at finalize; flushing only one side puts the other side's text last. | Flush both sides (or buffered_stdio=0 + fflush). | 07 · buffering.c |
Testbench-level gotchas (8)
| Item | Expected (LRM / commercial) | Verilator 5.052 | Workaround | Proof |
|---|---|---|---|---|
$error aborts at the first error, and final blocks don't runaborts / crashes at run time · implementation-defined | $error reports and continues. | Default error limit is 1: $error becomes $stop ("Aborting…", exit 1). final blocks do not run, so a Py_FinalizeEx or a free in final is skipped. | Run with +verilator+error+limit+N; count mismatches in the scoreboard and report once. | 05 07 · errors.sv08 · tb_err.sv |
$finish doesn't stop the calling processjust different · implementation-defined | — | Statements after $finish in the same initial still execute, then final blocks run. | Put $finish last. | 07 |
A sized negative literal in dist is unsatisfiablesilent wrong result · bug in Verilator / svdpi.h | x dist {-16'sh8000 := 5} on a shortint weights -32768. | %Warning-UNSATCONSTR and randomize() returns 0. | Write -32768; always check randomize()'s result. | 06 |
randomize() calls an external SMT solverjust different · implementation-defined | — | ~5–10 ms per call (z3), CPU mostly idle (cpu 0.68 s vs walltime 12.9 s for 2500 calls); a DPI call into C costs 2–10 ns. | Use $urandom stimulus when you measure anything else. | 06 · fir_tb.sv |
Static initializer inside a loop of an initialsilent wrong result · language gotcha | — | int len = $urandom_range(…) declared in a loop body runs once at time 0 (always 0); no warning. The scoreboard tested 0 bytes and still passed. | Declare it automatic, or assign separately. | 08 · tb_crc.sv |
| Unpacked port element types are checked inconsistently silent wrong result · LRM violation (accepts illegal code) | Non-equivalent element types are illegal on unpacked array ports. | A whole shortint c[8] on a logic signed [15:0] coef[8] port fails (Array element types are not equivalent), but a sub-array .coef(c[u]) of shortint c[2][8] builds and simulates. | Declare stimulus arrays with the port's exact type. | 08 · tb_port_fail.sv |
$display(c ? "PASS" : "FAIL") prints a numbersilent wrong result · language gotcha | — | The ternary yields an integral, not a format string. | $display("%s", c ? "PASS" : "FAIL"). | 01 02 |
buf is a keywordbreaks the build · language gotcha | — | int buf[256]; is a syntax error (gate primitive). | Pick another name. | 04 |
Spear & Tumbush ch. 12: book errata
Samples that are wrong, or that don't work as printed in a 2026 flow (C compiled as C++, Verilator, macOS). Details and the corrected code are in the linked step.
| Sample | Problem | Fix | Step |
|---|---|---|---|
| 12.3 | The \expect backslash is essential (without it: syntax error, unexpected expect). The stat = function int file_exists(...) example can't work: stat takes a struct stat*. | Escape the keyword; import access(path, F_OK) instead. | 01 |
| Table 12.1 | longint outputs as long int*. | long long* (32-bit long on Windows loses the upper half). | 02 |
| 12.7 | No extern "C". | Fatal in any flow that compiles the file as C++ (Verilator). | 03 |
| 12.9 | io_printf/veriuser.h; handle declared c7* instead of void*; no null guard. | printf + fflush; void* (the header then agrees); check for NULL. | 03 |
| 12.12 | if (i->bval) tests all 32 bits (25 undetermined); every error branch returns without writing count. | Mask with SV_MASK(7); drive X explicitly: count->aval = count->bval = SV_MASK(7). | 02 |
| 12.22 | Calls fib_oa(data, SIZE) with two arguments; the import takes one. | Drop the size: it travels inside the open-array handle. | 04 |
| 12.23 | Loops i <= svSize(h,1): writes one past the end. | i < svSize(h,1), or svLow … <= svHigh. | 04 |
| 12.27 | Loops i < svHigh(h,1): skips the last element. | i <= svHigh(h,1). | 04 |
| 12.37 / 12.41 / 12.45 | while (!feof(file)), unchecked fopen. | while (fscanf(f, " %c", &cmd) == 1); check fopen. | 05 |
| 12.40 / §12.8.3 | Timed exported tasks: legal SV, but don't build on Verilator. | Pull model (SV waits, C iterates). | 05 |
| 12.41 | Exported tasks declared extern int (per LRM; footnote: VCS used void). | Verilator also uses void: include the header, ignore the return. | 05 |
| 12.43 | R1 22 44 reads a location no command wrote. | A guaranteed mismatch; the lab keeps it as a negative test. | 05 |
| §12.8.4 / 12.44 | Text says mem_build returns the index; the sample declares it void and C trusts the file. | Return the index and check it in C. | 05 |
| §12.8.6 | "…use that handle in a call to svGetScope" means svSetScope; the previous scope is never restored. | prev = svSetScope(s); …; svSetScope(prev); | 05 |
| 12.52 | <wait.h>; WEXITSTATUS(system(cmd)). | <sys/wait.h>; store the status in a variable first. | 07 |
Not an erratum: §12.1.4's "a function cannot return bit [6:0]" is correct per §35.5.5 and H.8.9; Verilator accepts packed bit results up to 32 bits as an extension (02).
Errata in the LRM itself (IEEE 1800-2023)
The standard's normative text wins over its examples. These are the places where the two disagree, or where the normative header has a bug.
| Where | Problem | What is right | Step |
|---|---|---|---|
| §35.5.4 example | import "DPI-C" function bit [15:0] getStimulus(); returns a packed vector. | §35.5.5 and H.8.9 allow only byte…string and scalar bit/logic results. Use an output or shortint unsigned. | 02 |
| H.9.4 Example 1 | Calls svGetScopeByName (no such function) and writes svScope = svGetScope();, assigning to a type name. | svScope s = svGetScopeFromName(path); / svScope s = svGetScope(); | 05 |
| H.10.2 Example 2 | o3[1].b = 0; | o3[1].bval = 0; (s_vpi_vecval has aval/bval). | 02 |
| H.10.1.2 / Annex I | SV_GET_SIGNED_BITS(VALUE, N) tests bit N, not the sign bit N−1: SV_GET_SIGNED_BITS(0x80, 8) = 128. Every tool copies this header. | Sign-extend yourself: (v & (1u << (N-1))) ? (v | ~SV_MASK(N)) : (v & SV_MASK(N)). | 02 |