Skip to content

Latest commit

 

History

History
143 lines (130 loc) · 17.9 KB

NOTES.md

File metadata and controls

143 lines (130 loc) · 17.9 KB

Verilator DPI notes (measured, Verilator 5.052, macOS)

Append-only. One ## NN_name section per step.

01_first_call

  • .c files on the verilator command line are compiled with $(CXX) (clang warns "treating 'c' input as 'c++'" on a cache miss). Every imported function needs C linkage: extern "C" or #include "V<top>__Dpi.h" before the definitions.
  • V<top>__Dpi.h (in the -Mdir) holds one extern "C" prototype per import. Including it turns a return-type mismatch into "functions that differ only in their return type cannot be overloaded". A parameter mismatch defined outside extern "C" silently becomes a C++ overload → link error Undefined symbols "_negate".
  • Missing / mangled / static symbol → macOS ld: Undefined symbols for architecture x86_64: "_factorial", referenced from: ...Vdpiimwrap_top__DOT__factorial_TOP....
  • Each import gets a generated wrapper Vtop___024root____Vdpiimwrap_<scope>__DOT__<name>_TOP that converts types and passes a temporary for each output (copied back after the call).
  • import "DPI" (1800-2005 spelling): %Error-UNSUPPORTED: Unsupported DPI type 'DPI': Use 'DPI-C'.
  • import "DPI-C" \expect = function int fexpect(); works (c_name must be escaped when it is an SV keyword).
  • pure: no call elimination observed (unused result, f(2)+f(2), loop) — every call executes.
  • Importing libc strlen directly fails: Verilator emits unsigned long long strlen(const char*) for longint unsigned, which clashes with size_t strlen from <string.h> (included by the generated code). No SV type maps to unsigned long; use a wrapper. libm sin/pow/fabs/log10 and libc access import fine.
  • Same C name imported with two different signatures in two packages: Duplicate declaration of DPI function with different signature (followed by an internal error).
  • SV gotcha seen while writing labs: $display(c ? "PASS" : "FAIL") prints a number (the ternary yields an integral, not a string).

03_state_and_handles

  • .c files passed to verilator --binary are compiled with the C++ compiler (clang: treating 'c' input as 'c++' when in C++ mode, this behavior is deprecated). DPI functions therefore need extern "C", and C idioms invalid in C++ (implicit void* casts from malloc) fail.
  • The generated V<top>__Dpi.h is in the Mdir and the C files compile with -I. from there, so #include "Vtop__Dpi.h" works with no extra flag. It declares chandle as void* and names parameters after the SV formals. Including it turns the book's counter7(c7* inst, ...) (Sample 12.9) into error: conflicting types for 'counter7'; without it the mismatch links silently (C linkage has no parameter types).
  • Missing extern "C" on C++ wrappers: link fails with "_counter7_new", referenced from: ... ld: symbol(s) not found (macOS adds the leading _).
  • Internally Verilator passes chandle as a 64-bit QData (unsigned long long) in its Vdpiimwrap_* wrappers.
  • Two always blocks that call the same import in the same time step: Verilator 5.052 called the second-declared instance first (u2 before u1). final blocks: top's ran before the instances' (u2, then u1). Both orders are unspecified by the LRM.
  • A pure import with hidden state (a call counter): Verilator 5.052 still executed all 13 calls (a merged duplicate, a discarded void'() result, and a loop whose results are overwritten). The LRM allows eliminating them; do not rely on either behavior.
  • --threads 2: with the default --threads-dpi pure, pure imports were called from 2 OS threads and non-pure imports from 1. --threads-dpi none serializes them to 1 thread. --threads-dpi all gave the same counts as pure in this design (non-pure calls stayed on 1 thread). Results were identical in every mode.
  • chandle rules (IEEE 1800-2023 6.14) under verilator --lint-only -Wall: int x = h only warns (WIDTHTRUNC, 64→32 bits); h + 1, assign g = h and @(h) are accepted silently; only chandle inside a packed struct is an error. Verilator models chandle as a 64-bit integer, so code that relies on this will not port to commercial simulators.
  • import "DPI-C" function void bump(ref int x); is accepted silently (no warning even with -Wall) and mapped to void bump(int* x). IEEE 1800 forbids ref in imports; commercial tools reject it.
  • Imports in always_comb/assign: exactly one call per input change per call site (2 sites, 10 changes → 2 + 20 calls). Event-driven simulators may evaluate more often; keep stateful models out of combinational code.
  • Including Vtop__Dpi.h rescues C++ wrappers written without extern "C": the header's earlier extern "C" declarations give the later definitions C linkage (lab nocxxh build links and runs correctly).

02_data_types

  • shortreal in a DPI signature: Verilator warns SHORTREAL and generates double in Vtop__Dpi.h (LRM: float). A C definition with float links silently and reads garbage (0.1 → -1.59e-23). Including Vtop__Dpi.h turns it into a compile error.
  • ref in an import is accepted silently (generates int*); the LRM forbids it.
  • A 1'bz literal as a DPI argument: %Error-UNSUPPORTED: Unsupported tristate construct: ARG. X/Z held in variables arrive as 0 (2-state); C-written bval bits are dropped on output.
  • Output arguments are uninitialized stack locals in the generated wrapper (svBitVecVal v__Vcvt[2];) and are copied back unconditionally: an output C never writes overwrites the SV variable with stale stack data (measured: the previous call's 40-bit temp).
  • Copy-back masks to the declared width (VL_SET_Q_SVBV(40, ...), scalars 1U & x); input chunks arrive with zero unused bits and no sign extension. The LRM guarantees neither.
  • SV_GET_SIGNED_BITS(v, N) in vltstd/svdpi.h (Annex I text) tests bit N instead of N-1: SV_GET_SIGNED_BITS(0x80, 8) = 128.
  • Result types: bit [31:0] → svBitVecVal; bit [32:0] and any 4-state vector (logic [6:0], integer, time) are errors citing IEEE 1800-2023 35.5.5.
  • Verilator skips verilation (and its warnings) when the -Mdir is up to date: a check that greps warnings needs a clean build.
  • SV gotcha seen in testbenches: $display(c ? "PASS" : "FAIL") prints a number (the string literal is integral in an expression).

08_python_golden_model

  • Open-array actuals must be fixed-size unpacked arrays: a dynamic array (or queue) is rejected (Unsupported: DPI argument of type DYNARRAYDTYPE, or an internal error Passing dynamic array or queue as actual argument to DPI open array is not yet supported depending on where it is declared).
  • An assignment pattern '{...} as an open-array actual: Unsupported: Assignment pattern applies against non-struct/union data type: 'UNSIZEDARRAYDTYPE'. Assign it to a fixed array variable first.
  • svGetArrayPtr() of an input fixed array points at the SV variable itself (writes in SV after the call are visible through a pointer kept by C/Python). An output open array is a temporary on the C stack (different address on every call), copied back after the call returns.
  • Unpacked-array argument shape must match exactly: logic signed [15:0] c[8] -> input shortint x[8] is %Error-UNSUPPORTED: Shape of the argument does not match the shape of the parameter. Same family: a shortint array on a logic signed [15:0] array port is Array element types are not equivalent.
  • $error stops the simulation (Verilog $stop, exit code 1) unless +verilator+error+limit+N.
  • $display(cond ? "PASS" : "FAIL") prints a number, not the string (the argument is not a format literal).
  • A comment that starts with // Verilator: is parsed as a pragma (Unknown verilator comment).
  • Variable with initializer declared inside a loop body of an initial block is static: the initializer runs once at time 0 (int len = $urandom_range(...) was always 0). Use automatic. No warning.
  • Verilator compiles .c files as C++ (clang warns treating 'c' input as 'c++'); #include "Vtop__Dpi.h" works because --top-module top makes the prefix Vtop.

06_golden_model_c

  • Open arrays only take fixed-size actuals. Passing a dynamic array (or queue) to an input T a[] DPI formal fails at elaboration: %Error-UNSUPPORTED: Unsupported: DPI argument of type DYNARRAYDTYPE (pointing at the import), followed by Internal Error: ../V3Task.cpp:759. Fixed-size unpacked arrays work (svSize, svLow, svGetArrayPtr, svGetArrElemPtr1 all fine, also for output). Workaround: copy into a fixed buffer and pass the length as a separate int (see golden_pkg.sv).
  • randomize() runs an external SMT solver (z3): ~5-10 ms per call on this machine, CPU mostly idle (sim cpu 0.68 s vs walltime 12.9 s for 2500 calls). Parallel sims contend for it. It dwarfs DPI cost (a DPI call into C is ~2-10 ns); use $urandom stimulus when measuring anything else.
  • Sized negative literal in dist: x dist {-16'sh8000 := 5} on a shortint is unsatisfiable (%Warning-UNSATCONSTR, randomize() returns 0). Plain -32768 works.
  • User .c files are compiled with the C++ compiler (clang: warning: treating 'c' input as 'c++') from inside the -Mdir, so #include "Vtop__Dpi.h" works and gives C linkage + prototype checking for free.

04_arrays_structs_strings

  • Open-array actuals: only fixed-size arrays. Dynamic array → %Error-UNSUPPORTED: Unsupported: DPI argument of type DYNARRAYDTYPE; queue → ... QUEUEDTYPE; slice a[1:2] → %Error: Internal: Unexpected Call. Workaround: copy through a fixed buffer + explicit count.
  • Unpacked struct as DPI argument → Unsupported: DPI argument of type REFDTYPE '<name>'. Use packed structs (svBitVecVal) or separate members.
  • Packed open-array elements are stored natively: bit[7:0] = 1 byte, bit[12:0] = 2, bit[63:0] = 8, bit[99:0] = 16 (32-bit words). A svBitVecVal* cast on a svGetArrElemPtr1 result is wrong for widths ≤ 32 (reads neighbors); use svGetBitArrElem1VecVal.
  • Flat storage of unpacked arrays: low index first, row-major (int v[10:1] has v[1] at offset 0).
  • output arrays: open-array temporary arrives zero-filled and is copied back whole (unwritten elements become 0); fixed-size output arrays return garbage in unwritten elements. inout keeps them.
  • svGetArrElemPtr* out of range or with the wrong number of indices: run-time %Warning: DPI svOpenArrayHandle function ... and NULL.
  • Returned strings are copied into std::string immediately (one static buffer survives two calls in one $display).
  • 4-state: bval written by C is ignored; svGetLogicArrElem*VecVal writes bval = 0.
  • C compiler warnings (e.g. -Wreturn-stack-address) only appear in the Verilator build log; with ccache they are replayed on cache hits.
  • buf is a Verilog keyword (gate primitive): int buf[256]; is a syntax error.

05_export_and_context

  • Timing controls anywhere below an exported task (#, @, or nested in a called task) are a compile error: %Error-UNSUPPORTED: ... Unsupported: Timing controls inside DPI-exported tasks. Book Sample 12.40 does not build. Portable alternative: pull model (SV loops and waits, C is a zero-time iterator), see 05_export_and_context/lab/mem_pull.*.
  • Generated header declares exported tasks as void (LRM Annex H: int disable flag); imported tasks as int. svIsDisabledState() links and returns 0.
  • Imported function calling an exported zero-time task: accepted (LRM: illegal).
  • Non-context import calling an export: fatal only if no scope was ever set (Testbench C called 'X' but scope wasn't set ... See IEEE 1800-2023 35.5.3., preceded by a %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 silently.
  • svGetScopeFromName takes the %m name (top.b2, top.gen[0].u); TOP.top.b2 and relative names return NULL.
  • svPutUserData/svGetUserData, svGetCallerInfo (path as on the command line) work.
  • pure is not exploited: discarded/duplicated/loop-invariant pure calls all execute (also with -O3). A DPI function in a continuous assign ran 11 times for 5 input changes.
  • Cost per call (5 M calls, idle machine): generic ≈ pure ≈ 1.3–1.8 ns, context ≈ 10 ns, context + 1 export ≈ 18–21 ns.
  • Unwritten output args come back as garbage: the import wrapper passes an uninitialized temp (o__Vcvt).
  • $error defaults to $stop (abort) at the first error; +verilator+error+limit+N keeps going.

07_embedding_python

  • .c files passed to verilator --binary are compiled with the C++ compiler (__cplusplus=201703; clang warns "treating 'c' input as 'c++'"). Include Vtop__Dpi.h so definitions get extern "C" from the prototypes.
  • Open-array formal (input byte unsigned d[]) with a dynamic array actual: %Error-UNSUPPORTED: Unsupported: DPI argument of type DYNARRAYDTYPE (+ an internal error in V3Task). A fixed-size actual works.
  • A // comment whose first word is Verilator: (any case) is parsed as a pragma: %Error-BADVLTPRAGMA: Unknown verilator comment. Don't start comments with the tool's name.
  • $finish does not stop the calling process: statements after it in the same initial still execute, then final blocks run.
  • $error with the default error limit becomes $stop: "Aborting...", exit status 1, final blocks do not run. +verilator+error+limit+N keeps going ("$stop, ignored due to +verilator+error+limit").
  • --threads 4 with the default --threads-dpi pure: non-pure DPI calls are serialized onto ONE thread that is NOT the thread that ran initial (3200/3200 calls off the init thread). pure imports or --threads-dpi all spread calls over 4 threads. Any thread-affine C library (CPython) needs per-call thread attach (PyGILState_Ensure).
  • Embedded CPython 3.14: python3-config --ldflags lacks -lpython3.14 (use --embed); numpy imports fine in the Verilator binary (~0.1 s) but only once per process (re-init + import → "cannot load module more than once per process").
  • macOS: WEXITSTATUS(system(cmd)) does not compile as C++ ("cannot take the address of an rvalue"); <wait.h> (Spear Sample 12.52) is Linux-only, use <sys/wait.h>.
  • Unpacked array port connections with non-equivalent element types are checked inconsistently: 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 shortint c[2][8] -> .coef(c[u]) builds and simulates. Both are illegal per the LRM.
  • Calling any CPython API (e.g. PyLong_FromNativeBytes) before the interpreter is initialized segfaults the simulator on the first DPI call: make every bridge entry point go through the function that initializes.

08_python_golden_model (contract unification)

  • Step 08 now links step 06's golden_pkg.sv by relative path; Python-only extras live in py_extra_pkg.sv. One build line per backend, no SV change.
  • 06's golden.c and wall.c rely on #include "Vtop__Dpi.h" for C linkage (no extern "C" of their own). A testbench that links wall.c without importing wall_s still builds: the definition just gets C++ linkage and is unused.
  • A contract with no shutdown call: the bridge registers atexit(Py_FinalizeEx wrapper) on first init. Verilator's main() returns after final blocks, so Python's buffered stdout is flushed and Fir8.__del__ runs.
  • Bug selection without SV or rebuild: GOLDEN_BUG=fir_trunc vlr ... (bash passes a prefix assignment to a function's child processes); the C twin is -CFLAGS -DMUTANT=1 (rebuild).

LRM review corrections

Checked against IEEE 1800-2023 clause 35 and Annexes H, I, J (2026-10-15). Older lines above stay as written; these supersede them.

  • Return types: bit [6:0]/bit [31:0] results are NOT legal per §35.5.5 and H.8.9 (only byte…string and scalar bit/logic). Verilator accepting packed bit results ≤ 32 bits is an extension, so Spear §12.1.4 is not an erratum. The LRM's own §35.5.4 example function bit [15:0] getStimulus() contradicts its normative list.
  • import "DPI" (02 notes above: "1800-2005 spelling"): it selects the deprecated SV3.1a packed-argument passing (opaque handles, H.14). §35.5.4 requires a warning or an error, so Verilator's error is compliant.
  • Unpacked array element order is fixed by H.7.3 and H.7.6(c): the lower index goes first. Verilator follows it; it is not implementation-defined.
  • Packed open-array elements: H.7.3 requires canonical form; H.12.4 requires svGetArrElemPtr* to return NULL if the storage differs. Verilator's native storage with non-NULL pointers is an LRM deviation.
  • Exported tasks declared void in Vtop__Dpi.h: deviation from §35.8 / H.8.2 (same header as an import task, int).
  • Export from a non-context import: an error per §35.5.3, behavior undefined per H.9.3 (user error, not implementation-defined).
  • svDimensions: H.12.2 defers to §20.7 ($dimensions counts packed + unpacked). Verilator returns unpacked dims only; the LRM is ambiguous.
  • pure misuse (new, 01 pure_rules.sv): pure function void, pure task, and pure with an output all build and run with no diagnostic (illegal per §35.5.1.3/§35.5.2).
  • Shared C names (new, 01 cid.sv, 05 xshared.sv): one C name in two scopes with an identical signature works. Changing only the context qualifier gives Duplicate declaration of DPI function with different signature (then an internal error). Two modules exporting hello + svSetScope works.
  • C++ exception through an import (new, 03 exc.cpp): libc++ terminating due to uncaught exception, exit 134, no final blocks (§35.5.1.6: undefined). try/catch in the wrapper fixes it.
  • Time API (new, 05 svtime.c): svGetTime ignores type and scope: always sim time in precision units (25 ns → 25000 at 1 ps), real never written. svGetTimeUnit reports −9 for a module with timeunit 1us (the scope table stores the global unit). svGetTimePrecision ignores the scope.
  • svDpiVersion() returns "1800-2005" (02 ver.c).
  • Dynamic-array formal on an export (new, 05 xdyn.sv): %Error: Verilator internal fault, sorry. instead of a diagnostic (erroneous per §35.5.6).