← Design Verification Interviews

Interview Question · Debug

A Batched DPI Call That Got Slower

L3 SeniorL4 Staffdpiperformancedebug

The Question

The interviewer asks

A teammate batched a per-byte CRC into one DPI call per packet to save time. The timing log says the batch is slower. Explain the numbers and say what you would change.

log
# 100000 packets of 64 bytes. Verilator 5.052, C++ built at -O0 (a debug build).
crc_byte    one DPI call per byte      6400000 calls   114 ms
crc_packet  one DPI call per packet     100000 calls   317 ms
# The same C file rebuilt at -O2.
crc_byte                                                48 ms
crc_packet                                              49 ms
Reveal the Model Answer

The crossing is not the cost here: Verilator turns an import into a direct C++ call. The batched version still calls svGetArrElemPtr1 once per byte, and at -O0 each call does full bounds checks. At -O2 both versions take the same time, so the CRC loop itself dominates. Take one pointer with svGetArrayPtr when it is not null, and build the model optimised. Profile on the sign-off simulator before changing the interface again.

Self-Check

Tick each key point you covered in your answer.

What Interviewers Listen For (Senior)

Reads both builds before blaming DPI and finds the per-element accessor. Asks for a profile on the sign-off simulator.

Cross-Questions

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

WhyProve ItCompareScale It

  1. A Batched DPI Call That Got Slower
  2. WhyWhy a DPI Call Is Cheap in VerilatorL3 Senior

    Why is a DPI call so cheap in Verilator?

    Reveal the Model Answer

    Verilator compiles the whole testbench to C++. An import becomes a small generated wrapper that converts the arguments and calls your function in the same binary. Our backtrace shows that wrapper, Vdpiimwrap_model_predict, as one frame. There is no interpreter between the two sides.

    Self-Check

    Tick each key point you covered in your answer.

    Half-Truth

    "So DPI is cheap in every simulator."

    Other simulators may add work at the boundary, such as converting values or saving context. Only a measurement on that tool tells you.

  3. CompareWhat Argument Types Cost in a Hot LoopL3 Senior

    Compare passing logic [511:0], bit [511:0] and a string to C a million times in a loop.

    Reveal the Model Answer

    In our -O0 probe, a million calls took 46 ms with logic [511:0], 30 ms with bit [511:0] and 50 ms with a short string. A four-state vector carries two words for every 32 bits, so there is more to convert. A string may need a conversion to const char*. In a hot path, prefer two-state types and numbers.

    Self-Check

    Tick each key point you covered in your answer.

    Half-Truth

    "Use logic everywhere; it costs nothing extra."

    Each 32 bits of logic cross as an aval and a bval word. That doubles the data the call must build.

  4. Prove ItMeasuring Where DPI Time GoesL3 Senior

    Prove it: how do you show where the time goes before you change the model's interface?

    Reveal the Model Answer

    Run the simulator's profiler, or perf on a compiled binary, and read the top functions. Count the calls into C per test. Then time the same calls with an empty C body. That time is the crossing, and the rest is the model's own work.

    Self-Check

    Tick each key point you covered in your answer.

    Half-Truth

    "Time the whole test with and without the model."

    That mixes the crossing, the model's work and any stimulus changes. It cannot tell you which part to fix.

  5. Scale ItA Model Called on Every ClockL2 Mid-LevelL3 Senior

    What if the C model is called from an always @(posedge clk) block on every cycle, even when no transfer happens?

    Reveal the Model Answer

    The call count now grows with simulated cycles, not with transactions. A long idle period costs as much as busy traffic. Call the model only when the protocol says a transfer happened, such as valid && ready at the edge.

    Self-Check

    Tick each key point you covered in your answer.

    Half-Truth

    "Return early from C on idle cycles, and it is free."

    The call still happens, with its argument conversion, on every cycle. Gate the call in SystemVerilog instead.

What People Get Wrong

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

Misconception

"One DPI call per packet means one function call per packet."

The element accessor is a function call too. The twin counts 6400 accessor calls for 100 packets.

Run It

100 packets of 64 bytes through a batched CRC. A counter in C records each open-array accessor call.

RUNVerilator + C/C++ over DPI · simulator service
Edit the code, then run it
Expected: DPI calls=100, accessor calls=6400, crc=100ece8c FAIL: the batched call still made one accessor call per byte
See the Fix
module tb;
  import "DPI-C" function int unsigned crc_packet(input byte unsigned data[], input int count);
  import "DPI-C" function int accessor_calls();
  byte unsigned pkt[64];
  int unsigned crc;
  initial begin
    foreach (pkt[i]) pkt[i] = i;
    for (int p = 0; p < 100; p++) crc = crc_packet(pkt, 64);
    $display("DPI calls=100, accessor calls=%0d, crc=%h", accessor_calls(), crc);
    if (accessor_calls() > 100) $display("FAIL: the batched call still made one accessor call per byte");
    else $display("PASS: one pointer per packet, so the per-byte calls are gone");
    $finish;
  end
endmodule

Prints: DPI calls=100, accessor calls=100, crc=100ece8c PASS: one pointer per packet, so the per-byte calls are gone

Tool as Truth

"DPI calls are nearly free; Verilator just proved it."

That is one compiled-code simulator. An event-driven simulator may convert arguments and save context on each call, so measure on the tool you sign off with.

Source Measured on a local Verilator 5.052 build with g++, 2026-11-08 (6.4 million per-byte calls cost 114 ms at -O0); Spear & Tumbush, SystemVerilog for Verification (3rd ed.), §12.7

Wrong Altitude

"Rewrite the CRC in SystemVerilog before measuring anything."

The -O2 numbers already show the CRC work, not the language boundary, sets the time. A rewrite guesses at a cause the log rules out.

Learn It, Then Prove It