← Design Verification Interviews

Interview Question · Concept

Running a Python Model from the Testbench

L2 Mid-LevelL3 Seniordpipythonreference models

The Question

The interviewer asks

The architecture team gives you a Python model. How do you call it from a SystemVerilog bench, and what goes wrong most often?

Reveal the Model Answer

DPI reaches only C, so a C bridge embeds CPython in the simulation. Start the interpreter once, import the module once, and keep the function object. Copy each transaction into Python objects, and check every conversion back. Turn every Python exception into a status code. Give the model an explicit reset, because importing a module again returns the cached copy with its old state.

Self-Check

Tick each key point you covered in your answer.

What Interviewers Listen For (Mid-Level)

Describes the C bridge, one interpreter for the run, and status codes for exceptions.

Cross-Questions

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

WhyWhat IfBreak ItCompareScale It

  1. Running a Python Model from the Testbench
  2. WhyWhy a Second Import Changes NothingL2 Mid-Level

    Why does a second PyImport_ImportModule("model") not reset the model?

    Reveal the Model Answer

    Python runs a module's top-level code only on the first import. It then stores the module in sys.modules, and every later import returns that object. So total keeps its value. A reset must be a function the model provides.

    Self-Check

    Tick each key point you covered in your answer.

    Half-Truth

    "Use importlib.reload, and every reference is fresh."

    Reload runs the code again in the same module object. Function objects the bridge already holds still point at the old code.

    Source Python documentation, importlib.reload

  3. What IfA Python Model Behind DPIL2 Mid-LevelL3 Senior

    Asked if they skip error handling

    What if the reference model is written in Python? How does it reach the testbench?

    Reveal the Model Answer

    The standard defines DPI only between SystemVerilog and C. So a small C bridge starts the Python interpreter, calls the model and converts arguments and results. The bridge must catch every Python exception and turn it into a status code, because nothing may unwind into the simulator. Pass a whole packet per call, not one byte, because each call into Python is much slower than a C call.

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "Let the Python exception propagate; the simulator will report it."

    The standard gives imported routines no way to raise into SystemVerilog, so an exception crossing the boundary has undefined behaviour. The bridge must handle it.

    Source IEEE 1800-2017 §35.5.1 (semantic constraints on imported routines)

  4. ComparePython Calls Compared with C CallsL2 Mid-Level

    Compare the cost of calling a Python model per byte with calling it per packet.

    Reveal the Model Answer

    In our probe, 640000 per-byte calls into Python took 32 ms. 10000 per-packet calls took 6 ms for the same bytes. Each call crosses into the interpreter and builds Python objects, so fewer, larger calls win. The real cost is usually the Python work inside the model, so measure before redesigning.

    Self-Check

    Tick each key point you covered in your answer.

    Buzzword Without Mechanism

    "Python is too slow for a reference model."

    Called once per transaction, a small model adds little. Slowness comes from per-cycle or per-byte calls and from heavy work inside Python.

  5. Scale ItA Python Bridge on the Compute FarmL3 Senior

    How do you make the Python bridge behave the same on every farm machine and in every regression?

    Reveal the Model Answer

    Start CPython with an isolated configuration, so environment variables and user site packages are ignored. Put only the model's own directory on sys.path. Pin the interpreter version the bridge was built against, and print the model version at start of test. Then a failure on one machine can be reproduced on another.

    Self-Check

    Tick each key point you covered in your answer.

    What Interviewers Listen For (Senior)

    Treats the interpreter as part of the build, pinned and logged like the simulator version.

    Half-Truth

    "Set PYTHONPATH in everyone's shell."

    Shell settings differ between users and farm jobs. The isolated configuration ignores them on purpose, so the run cannot depend on them.

  6. Break ItPython Signal Handlers in a SimulatorL3 Senior

    Break it: the bridge starts Python with default settings. A user presses Ctrl-C to stop a hung simulation, and nothing happens. Why?

    Reveal the Model Answer

    By default, Python installs its own handler for SIGINT. The handler only sets a flag that Python checks while it runs Python code. While the simulator runs outside Python, nobody checks that flag. Set install_signal_handlers = 0, so Ctrl-C stays with the simulator.

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "Signals always go to the simulator, whatever Python does."

    A process has one handler per signal. Whoever installs last owns it, and embedded Python installs at start-up unless told not to.

    Source CPython C API documentation, PyConfig.install_signal_handlers

What People Get Wrong

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

Misconception

"Importing the module again resets its state."

Python keeps every imported module in sys.modules. A second import returns that same module, globals included.

Run It

A Python accumulator. The bridge "resets" it by importing the module again.

RUNVerilator + C/C++ over DPI · simulator service
Edit the code, then run it
Expected: after reset, add(1) returned 13 FAIL: the second import returned the cached module, so the old total of 12 survived the reset
See the Fix
module tb;
  import "DPI-C" function int py_start();
  import "DPI-C" function int py_add(input int v);
  import "DPI-C" function int py_reset();
  int r;
  initial begin
    if (py_start() != 0) $fatal(1, "Python did not start");
    void'(py_add(5));
    void'(py_add(7));
    void'(py_reset());               // a DUT reset: the model must start again from 0
    r = py_add(1);
    $display("after reset, add(1) returned %0d", r);
    if (r != 1) $display("FAIL: the second import returned the cached module, so the old total of 12 survived the reset");
    else $display("PASS: the model's own reset function cleared its state");
    $finish;
  end
endmodule

Prints: after reset, add(1) returned 1 PASS: the model's own reset function cleared its state

Half-Truth

"Finalize Python after each test and start it again."

Some extension modules do not survive a second start in one process. Keep one interpreter and reset the model instead.

Source CPython C API documentation, Py_FinalizeEx (bugs and caveats)

Missed Corner

Releases every object the C API returns with Py_DECREF.

Some calls return borrowed references, which the bridge must not release. Releasing one frees an object Python still uses.

Source CPython C API documentation, reference counts: new and borrowed references

Tool as Truth

"It runs with my Python, so it will run on the farm."

The farm may have another version, other packages or a different path. An isolated configuration and a pinned interpreter remove that drift.

Source CPython C API documentation, Python Initialization Configuration (isolated configuration)

Learn It, Then Prove It