← Design Verification Interviews

Interview Question · Code Reading

A Static Variable That Lost Its Value

L2 Mid-LevelL3 Seniordpireentrancy

The Question

The interviewer asks

What does this print? The packet of length 10 carries an inner packet of length 4.

systemverilog
// C
static int cur_len;                       // the packet being parsed
extern "C" int c_parse(int len, int inner_len) {
  cur_len = len;
  sv_on_header(inner_len);                // export: may parse an inner packet
  return cur_len;
}

// SystemVerilog
import "DPI-C" context function int c_parse(input int len, input int inner_len);
export "DPI-C" function sv_on_header;
function void sv_on_header(input int inner_len);
  if (inner_len != 0) void'(c_parse(inner_len, 0));   // a tunnelled packet
endfunction
initial $display("%0d", c_parse(10, 4));
Reveal the Model Answer

It prints 4, not 10. The export runs inside the outer call and calls c_parse again for the inner packet. That nested call writes the one static cur_len, so the outer call returns the inner length. A static is safe only if nothing can re-enter before the call finishes. A callback can re-enter, and so can a context task that waits in an exported task. Keep in-progress state in local variables or a per-call struct.

Self-Check

Tick each key point you covered in your answer.

What Interviewers Listen For (Mid-Level)

Traces the nested call through the export and predicts 4.

Cross-Questions

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

WhyWhat IfBreak ItCompare

  1. A Static Variable That Lost Its Value
  2. WhyWhy Forked Threads Do Not Interleave in CL2 Mid-Level

    Why is a C static safe when two forked SystemVerilog threads call the same plain import?

    Reveal the Model Answer

    An imported function returns in zero time and cannot block. So the scheduler never switches threads while it runs. The second thread enters only after the first call has returned. The danger starts only when C calls back into SystemVerilog, or when an imported task suspends.

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "Each SystemVerilog thread is an OS thread, so C needs locks."

    SystemVerilog threads are simulator processes, not OS threads. The simulator runs DPI calls one at a time on its own thread.

    Source IEEE 1800-2017 §35.5.1 (imported functions complete without consuming time)

  3. CompareThree Places for C StateL2 Mid-Level

    Compare the three places C state can live: a local variable, a struct behind a handle, and a static or global.

    Reveal the Model Answer

    A local lasts for one call, so re-entry cannot harm it. A struct behind a chandle lasts as long as the model, and each instance owns its own. A static lasts for the whole simulation and is shared by every caller and every instance. Use locals for work in progress, the struct for model state, and statics almost never.

    StorageLifetimeShared byBreaks when
    Local variableOne callNobodyNever, for re-entry
    Struct behind a chandleFrom model_new to model_deleteHolders of that handleThe handle is copied by mistake
    Static or globalThe whole simulationEvery call and instanceA second instance or a nested call

    Pick the shortest lifetime that still works.

    Self-Check

    Tick each key point you covered in your answer.

    Missed Corner

    "A static is fine while the bench has one agent."

    The next project adds a second agent, and both share the static. Lesson D1.6 measured exactly that failure.

    Source Lesson D1.6 review claims (a shared global gave a=4 b=4)

  4. What IfRe-Entry Through a Suspended TaskL3 Senior

    Asked if the import is a context task

    What if the import is a context task that waits inside an exported task, and two agents call it?

    Reveal the Model Answer

    Agent A enters C and suspends inside the export. While it waits, agent B calls the same C function and runs it from the top. Both calls are now live in C at once. Any static they touch is shared, so the C code must be reentrant.

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "The simulator queues agent B until agent A's C call returns."

    Nothing queues it. A suspended task has handed control back to the scheduler, and B's call proceeds at once.

    Source IEEE 1800-2017 §35.5.1.5 (reentrancy of imported tasks)

  5. Break ItA Mutex That Hangs the SimulationL3 Senior

    Break it: someone wraps the C task in a std::mutex to fix the re-entry. What happens?

    Reveal the Model Answer

    Agent B's call comes from the same OS thread that still holds the lock for agent A. Locking a std::mutex you already own is undefined, and usually blocks forever. Agent A can never resume, because the simulator thread is now stuck. Remove the static instead.

    Self-Check

    Tick each key point you covered in your answer.

    Half-Truth

    "Use a recursive mutex, then."

    It stops the hang, but both calls still edit the same static. The lock protects nothing when the re-entry is on one thread.

What People Get Wrong

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

Misconception

"A static variable is private to the call that set it."

There is one copy for the whole program. The twin's nested parse writes 4 over the 10 that the outer call stored.

Run It

A C parser keeps the current length in a static. An export parses a tunnelled packet through the same import.

RUNVerilator + C/C++ over DPI · simulator service
Edit the code, then run it
Expected: outer length returned=4 FAIL: the nested call overwrote the static cur_len before the outer call returned
See the Fix
module tb;
  import "DPI-C" context function int c_parse(input int len, input int inner_len);
  export "DPI-C" function sv_on_header;
  // The header hook parses a tunnelled inner packet through the same import.
  function void sv_on_header(input int inner_len);
    if (inner_len != 0) void'(c_parse(inner_len, 0));
  endfunction
  int outer;
  initial begin
    outer = c_parse(10, 4);
    $display("outer length returned=%0d", outer);
    if (outer != 10) $display("FAIL: the nested call overwrote the static cur_len before the outer call returned");
    else $display("PASS: each call kept its own length in a local variable");
    $finish;
  end
endmodule

Prints: outer length returned=10 PASS: each call kept its own length in a local variable

Misconception

"Two forked threads can interleave inside a plain import function."

An imported function runs to completion on the simulator thread. The scheduler cannot switch to another SystemVerilog thread in the middle of it.

Source Spear & Tumbush, SystemVerilog for Verification (3rd ed.), §12.6 (a call to an imported routine is never interrupted by the scheduler)

Wrong Altitude

"Protect the static with a mutex."

The nested call comes from the same thread. A normal mutex locked twice by one thread is undefined behaviour, and in practice it hangs the simulation.

Source ISO C++17 [thread.mutex.requirements.mutex] (lock by the owning thread is undefined)

Learn It, Then Prove It