Skip to content
VLSI Mentor

C Part II · Module 25 · Debugging DPI-C and Simulator Integration

How DPI Failures Actually Present Themselves

Before you know anything about a DPI failure you know one thing: what it looked like. Six symptoms, each with a measured exit code that names a family of causes before any log is read — 1 for a link error, 3 for a function nothing registered, 134 for an abort inside the simulator's own source, 139 for a crash that printed nothing at all including the lines that had already run. And then the two that exit 0, where the run is clean and the data is wrong or quietly incomplete, which is where this track has spent six modules.

Advanced22 min readCPart IIVerificationDPI-C

Part II · Module 25 · Chapter 25.1 · How DPI Failures Actually Present Themselves

1. Starting From the Symptom

Every previous module in this track started from a mechanism and derived what could go wrong. Debugging runs the other way: something has already failed, you know what it looked like, and you need to get from that to a cause.

So this chapter is a symptom vocabulary — what you actually see, and what each thing narrows the cause to. It treats none of them; each of the next nine chapters owns one row.

The module's organising claim, from its own blurb, is that every failure here is one the earlier modules make explainable rather than mysterious. The index is what connects the two.

2. The Cheapest Discriminator Is the Exit Code

Before reading a log, read the exit status. Measured across this track, five distinct values name five different families:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  compile 0, link 1  -> symbols and translation units
  3                  -> a function nothing registered, or no library
  134                -> an abort inside the simulator's own source
  139                -> a memory fault in your C
  0                  -> everything the toolchain can check is fine

That costs nothing and it is almost never the first thing anyone looks at. A run that exits 3 and a run that exits 139 have essentially nothing in common, and the log may look similarly unhelpful for both.

3. The Five Non-Zero Symptoms

It will not build. Measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  compile exit: 0
  link exit: 1
  Undefined symbols for architecture arm64:
    "_crc8_step", referenced from:
        _main in user.o

The compile succeeded, so this is not a syntax or type problem — it is about symbols and translation units, and nothing about the simulator is involved yet. Chapter 25.6, which also covers the version of this message that means your C was compiled as C++.

It will not start. Two shapes. Measured, a function nothing registered:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  tb_add.v:4: Error: System task/function $c_add() is not defined by any module.
  exit 3

and a library that could not be found:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  dlopen FAILED: dlopen(./nosuch.dylib, 0x0002): tried: './nosuch.dylib' (no such file), …

The first names your testbench line, which is excellent. The second lists every path it tried, which is the single most useful thing in a loader message. Chapters 25.2 and 25.7.

It aborts part-way. Measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  Assertion failed: (expr), function vpi_get_value, file vpi_priv.cc, line 1027.
  exit 134

An assertion in the simulator's own source file. The instinct is to conclude the simulator has a bug; the cause is almost always the call. Chapter 25.2.

It dies silently. Measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  exit: 139
  output lines: 0

Zero lines — and three $display statements had already executed. Chapter 25.8, and §5 is why the output vanished.

It runs clean and the data is wrong. Exit 0, plausible numbers, and nothing in the toolchain to narrow it. Chapters 25.3, 25.4 and 25.10 — and most of Modules 20 to 24.

4. The Sixth: Information That Is Simply Gone

The last symptom is worth separating from "wrong data" because it looks like nothing at all.

Measured, a 32-bit value of 0x000000F0 with bit 5 set to Z, read two ways in the same call:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  [C] 4-state: aval=0x000000D0 bval=0x00000020 -> HAS X or Z ; read as int: 0x000000D0

Read as a four-state value, the Z is visible in bval. Read as an integer — which is what ordinary code does — the value is 0xD0.

The original was 0xF0. Bit 5 did not change to zero; it stopped existing, and the integer access reported the result without comment. A model receiving 0xD0 has no way to know it was ever anything else. Chapter 25.9.

Six symptoms a DPI failure can present as, each with its measured exit code, what it narrows the cause to, and which chapter treats it. A build that fails at the link stage with undefined symbols exits 1 after compiling cleanly with 0, and narrows the cause to symbols and translation units. A run that refuses to start exits 3 with the message that the system function is not defined by any module, or fails at load with a list of every path tried, narrowing it to registration or library loading. A run that aborts mid-way exits 134 on an assertion inside the simulator's own source file, which points at the call rather than at the simulator and usually means an argument-count mismatch. A run that dies silently exits 139 with no output at all, not even the display lines that already ran, because the output was buffered and lost. A run that completes cleanly and returns wrong data exits 0, which is the hardest case and the one most of this track is about. And a run that completes cleanly having silently flattened four-state information also exits 0, measured here as a value of 0xF0 reading back as 0xD0 once one bit became Z.The symptomwhat you actually seeExit codemeasuredWhat it narrows thecause toWhere it is treatedIt will not buildundefined symbols at thelink stepcompile 0, link 1measuredsymbols andtranslation unitsnothing to do with thesimulator yet25.6including themangled-name caseIt will not startrefuses before anysimulation3measured: not defined byany moduleregistration, or nolibraryand it names yourtestbench line25.2 and 25.7two causes, one messageIt aborts part-wayan assertion inside thesimulator134measuredthe call, not thesimulatorusually an argument count25.2the message names thewrong fileIt dies silentlyno message of any kind139measured: and 0 lines ofoutputa memory fault inyour Cthe output you didproduce is lost too25.8three ways to localise itIt runs clean andthe data is wrongthe expensive one0a clean run, plausiblenumberstypes, widths,layout, or timingnothing in the toolchainnarrows it further25.3, 25.4, 25.10and most of modules 20 to24It runs clean and information is missingmeasured: a value of 0xF0 reads back as 0xD0 once one bit is Z, with no indication anywhere12
Figure 1 — six symptoms with their measured exit codes and what each narrows the cause to. The top four rows are the good cases, in the specific sense that the toolchain is telling you something: a non-zero exit names a family before any log is read, and two of the four even name your line. The fifth row is where most of this track's effort went — a clean exit and plausible wrong numbers, which nothing in the toolchain can narrow. The last row is the same exit code with a different character: not wrong data but absent data, measured here as a value of 0xF0 that reads back as 0xD0 once a single bit becomes Z, with the loss visible only to code that asked for the four-state form.

5. Why the Output Vanished

The crash row deserves its own paragraph because the missing output misleads people more than the crash does.

Measured, the same program two ways:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  vvp …                  exit: 139, output lines: 0
  stdbuf -o0 vvp …       exit: 139, and all three lines survive:
      [SV] initialising the table with 0 entries
      [SV] init returned 0
      [SV] about to read entry 3

Nothing changed about the program or the fault. What changed is buffering: standard output is block-buffered when it is not a terminal, so lines accumulate in memory and are written when the buffer fills or the program exits cleanly. A process killed by a signal does neither.

So the rule, which saves more time than any other single thing in this module:

The last line you see is the last line that was flushed, not the last line that ran.

Chapter 25.8 turns that into a method.

6. The Misconception: The Last Line Shows Where It Got To

"The log stops after the third transaction, so it failed on the fourth."

Measured false. Three $display statements executed and zero lines appeared. Had the crash happened on transaction four hundred, the log would have looked identical — empty.

Two consequences worth holding onto.

An empty or short log is evidence about buffering, not about progress. The only way to read progress from a log is to make the output survive, which §5 measured and Chapter 25.8 makes into a procedure.

And the converse: a log that ends mid-line is informative. If output is unbuffered and the last line is incomplete, the process really did die at that point — which is a much stronger signal than a log that simply stops.

The accurate position:

Establish that your output survives before you reason from it. One environment variable or one flush call turns a log from a misleading artefact into the cheapest localisation tool available — and it is the same change that makes every other method in this module work better.

7. What You Should Be Able to Say Now

  1. Debugging starts from the symptom, not the mechanism — this chapter is the index and treats nothing.
  2. The exit code is the cheapest discriminator and is almost never read first.
  3. Measured, five families: compile 0 / link 1 · 3 · 134 · 139 · 0.
  4. Will not build — undefined symbols, compile succeeded, so it is symbols and translation units (25.6).
  5. Will not start — exit 3, "not defined by any module", naming your line; or a loader listing every path tried (25.2, 25.7).
  6. Aborts part-way — exit 134, an assertion in the simulator's source, usually caused by the call (25.2).
  7. Dies silently — exit 139 and 0 lines of output (25.8).
  8. Runs clean, data wrong — exit 0, and nothing in the toolchain narrows it (25.3, 25.4, 25.10).
  9. Runs clean, information missing — measured, 0xF0 reads back as 0xD0 once one bit is Z; the bit stopped existing (25.9).
  10. Output vanishes because of buffering — measured, the same crash shows 3 lines under stdbuf -o0.
  11. The last line you see is the last line flushed, not the last line that ran.
  12. Record the exit code with every run — it is free and it names a family.

8. Exercises

  1. Run a failing DPI build and record the compile and link exit codes separately. Which one failed?
  2. Produce each of the five non-zero symptoms deliberately. Write down the exit code and the first line of output for each.
  3. Crash a program that has already printed several lines, with and without unbuffered output. How many lines survive in each case?
  4. From a log that stops after three transactions, list everything you can and cannot conclude.
  5. Read a four-state value as an integer and compare it with its aval/bval form. Construct a case where the integer form is a plausible value that was never there.
  6. For the last three DPI failures you saw, place each in the figure. Which row was hardest, and did its exit code tell you so?
  7. Your regression log records "pass" or "fail". What would recording the exit code add, and what would it have told you about a recent failure?
  8. Why are the non-zero symptoms described as the good cases? Answer in terms of what the toolchain has already done for you.

9. Summary

  • Start from the symptom; this chapter indexes, the next nine treat.
  • Read the exit code first — measured: link 1 · 3 · 134 · 139 · 0.
  • Will not build → symbols and translation units (25.6).
  • Will not start → exit 3 naming your line, or a loader naming every path tried (25.2, 25.7).
  • Aborts part-way → exit 134, an assertion in the simulator's file, caused by the call (25.2).
  • Dies silently → exit 139, 0 lines (25.8).
  • Clean exit, wrong data → nothing narrows it (25.3, 25.4, 25.10, and Modules 20–24).
  • Clean exit, missing information → measured 0xF0 → 0xD0; the bit stopped existing (25.9).
  • Buffering, not progress, explains a short log — measured, 3 lines recovered under stdbuf -o0.
  • Record the exit code with every run.

Two of the six symptoms were a function the simulator could not find, or could find and could not call. Both come from the same place: a declaration on one side and a definition on the other, with nothing in the toolchain able to compare them.

Where this fits

Part of the C Programming curriculum.