← Design Verification Interviews

Interview Question · Debug

A Simulation That Died in the C Model

L2 Mid-Leveldpidebug

The Question

The interviewer asks

A test dies with this output. The log file is empty, so even the scoreboard message printed before the call is missing. Read the backtrace: what is the cause, and what do you do next?

log
$ ./obj/Vtb > sim.log
Segmentation fault
$ wc -l sim.log
0 sim.log
$ gdb -batch -ex run -ex bt ./obj/Vtb
Program received signal SIGSEGV, Segmentation fault.
model_predict (h=0x0, data=7) at ../model.cpp:6
6         m->count++;
#0  model_predict (h=0x0, data=7) at ../model.cpp:6
#1  Vtb_tb____Vdpiimwrap_model_predict_TOP__tb (h=<optimized out>, data=7, ...) at Vtb_tb__0.cpp:17
#2  Vtb_tb__03a__03ascoreboard::__VnoInFunc_write (this=0x555555593d30, ..., data=7)
    at Vtb_tb__03a__03ascoreboard__Vclpkg__0.cpp:16
#3  Vtb_tb___eval_initial__TOP__tb__Vtiming__0 (frame_ptr=0x555555593d70) ...
#4  VlCoroutineHandle::resume (...) at verilated_timing.cpp:45
#5  VlDelayScheduler::resume (...) at verilated_timing.cpp:75
    ...
#12 main (...) at Vtb__main.cpp:22
Reveal the Model Answer

Frame #0 is the C model, model_predict, with h=0x0. Frame #2 shows the scoreboard's write made the call through the DPI wrapper. So the scoreboard's chandle was never set: nothing called model_new before the first prediction. The empty log is stdout buffering, because the process died before it flushed. Fix the build order, and make the C return a status for a null handle.

Self-Check

Tick each key point you covered in your answer.

What Interviewers Listen For (Mid-Level)

Reads frame #0 first, spots h=0x0, and follows the frames down to the scoreboard call.

Cross-Questions

Where the interviewer goes next. Open a node to practise that follow-up.

WhyProve ItCompareScale It

  1. A Simulation That Died in the C Model
  2. WhyWhy the Log Lost Its Last LinesL2 Mid-Level

    Why did the $display that ran just before the crash never reach the log?

    Reveal the Model Answer

    When output goes to a file or a pipe, the C library collects it in a buffer. It writes the buffer in large blocks. A segmentation fault kills the process before that buffer is flushed. Our probe showed the line once $fflush() ran before the risky call. Running under gdb also avoids guessing, because it stops at the crash.

    Self-Check

    Tick each key point you covered in your answer.

    Half-Truth

    "Add more $display lines to find where it dies."

    Those lines sit in the same buffer and are lost the same way. More prints can even move the point where the buffer happens to flush.

  3. Prove ItGetting a Backtrace from a Crashed RunL2 Mid-Level

    Prove it: how do you get that backtrace from a failing regression run?

    Reveal the Model Answer

    Build the C model with -g, so frames show file and line. Rerun the failing seed under gdb with run then bt. Or allow core files with ulimit -c unlimited, and open the core with gdb ./obj/Vtb core. Read frame #0 first, then walk down to the first frame in your own code.

    Self-Check

    Tick each key point you covered in your answer.

    Half-Truth

    "Without debug symbols, the backtrace is useless."

    It still names the function in frame #0, which often finds the bug. Symbols add the file, the line and the argument values.

  4. Scale ItA DPI Crash That Only Happens on the FarmL3 Senior

    What if the crash happens on the compute farm but never on your machine?

    Reveal the Model Answer

    Suspect undefined behaviour in the C: an uninitialised variable, a stale pointer or a read past an array. It shows up only with certain compilers, flags or memory layouts. Reproduce with the farm's exact build flags and seed, then run an AddressSanitizer build. It reports the bad access where it happens, not where it later crashes.

    Self-Check

    Tick each key point you covered in your answer.

    What Interviewers Listen For (Senior)

    Looks for undefined behaviour first and copies the farm build exactly before debugging.

    Tool as Truth

    "It works on my machine, so the farm is broken."

    Undefined behaviour can look correct on one build and crash on another. Your machine passing is not evidence that the C is right.

    Source Measured on a local Verilator 5.052 build with g++, 2026-11-08 (AddressSanitizer build of the crashing model reported the faulting line and caller chain)

  5. CompareStatus Codes Compared with Aborting in CL2 Mid-Level

    Compare a C model that returns a status code on bad input with one that calls abort() or exit().

    Reveal the Model Answer

    A status lets SystemVerilog report the error through the normal error path, with the time, the transaction and the test name. The run then ends cleanly, and its final blocks and end-of-test reports still run. An abort kills the process at once and skips all of that. So C returns a status, and SystemVerilog decides how bad it is.

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "Throw a C++ exception; the simulator will report it."

    An exception must not cross the DPI boundary. The behaviour is undefined, and in practice the process usually ends with no report.

    Source IEEE 1800-2017 §35.5.1 (semantic constraints on imported routines); lesson D1.2 review claims

What People Get Wrong

Common wrong answers and why they fail. Run It shows the wrong belief failing on a real simulator.

Misconception

"SystemVerilog stops a null chandle before the call."

A null chandle is a legal value to pass. The simulator hands it to C as a null pointer, and C must check it.

Run It

The scoreboard's build() lost its model_new() call. The C checks for null and reports a status.

RUNVerilator + C/C++ over DPI · simulator service
Edit the code, then run it
Expected: FAIL: the null chandle went straight to C; only the C null check prevented a crash
See the Fix
module tb;
  import "DPI-C" function chandle model_new();
  import "DPI-C" function int model_predict(input chandle h, input int data, output int status);
  class scoreboard;
    chandle model;
    function void build();
      model = model_new();                 // the fix: create the model before any prediction
    endfunction
    function int write(int data);
      int st, e;
      e = model_predict(model, data, st);
      return st;
    endfunction
  endclass
  scoreboard sb = new;
  initial begin
    sb.build();
    // The belief: SystemVerilog stops a null chandle before it reaches C.
    if (sb.write(7) != 0) $display("FAIL: the null chandle went straight to C; only the C null check prevented a crash");
    else $display("PASS: the model was created in build, so C received a real handle");
    $finish;
  end
endmodule

Prints: PASS: the model was created in build, so C received a real handle

Missed Corner

"The last line in the log shows how far the test got."

Output to a file or pipe sits in a buffer. A crash throws the buffer away, so the last lines before it are missing.

Source Measured on a local Verilator 5.052 build with g++, 2026-11-08 (a $display just before the crash was lost when output went to a file; $fflush() before the call kept it)

Half-Truth

"Most frames are inside Verilator, so it is a simulator bug."

Frame #0 is where the crash happened, and it is model.cpp. The Verilator frames below it are only the chain of callers.

Learn It, Then Prove It