← Design Verification Interviews
Interview Question · Code Writing
Calling a C Function from the Testbench
- Likely at Analog Devices
The Question
The interviewer asks
A C reference model exposes int is_large(unsigned char len). Write the SystemVerilog side that calls it with an 8-bit burst length, and tell me which type you chose and why.
You have two and a half minutes. Speak or type, then press Get Feedback under your answer. No credits needed.
Speech-to-text runs in your browser. Chrome and Edge may send audio to their own speech service.
You can edit your answer before you send it. Feedback uses AI and checks your answer only against our written answer. No credits are used and your answer is not saved.
Reveal the Model Answer
Declare import "DPI-C" function int is_large(input byte unsigned len); and call it like any function. SystemVerilog byte is signed and maps to C char, so an unsigned C parameter needs byte unsigned, which maps to unsigned char. Include the simulator-generated header in the C file, so a type mismatch fails the build instead of passing wrong values.
- Declare
The import line names the function, its result type and its two
intinputs. Nothing in SystemVerilog says what it does. - Generate
Verilator reads every import and writes
Vtb__Dpi.h: one C prototype per import, with C linkage. - Define
The bridge includes that header and defines the function. The compiler compares the definition with the prototype and stops on any difference.
- Link
The generated simulation code and the bridge are compiled into one program. The linker joins the call to the C definition.
- Call
At run time the testbench calls
add_small(7, 5). C adds, returns 12, and the testbench checks the value before printing it.Follow the calls, values and waits
- call add_small(7, 5): main.sv → add_small(7, 5)
- call add_small(7, 5): add_small(7, 5) → bridge.cpp
- return 12: bridge.cpp → add_small(7, 5)
- return 12: add_small(7, 5) → main.sv
Read the diagram as text
- main.sv: import "DPI-C" function int add_small(input int a, b);
- Vtb__Dpi.h: extern "C" int add_small(int a, int b);
- bridge.cpp: int add_small(int a, int b) { return a + b; }
- Vtb: one program: the simulation and the bridge
- add_small(7, 5): the testbench calls, C returns 12
- main.sv → Vtb__Dpi.h: Verilator writes
- Vtb__Dpi.h → bridge.cpp: included and compared
- main.sv → Vtb: compiled in
- bridge.cpp → Vtb: compiled in
- main.sv → add_small(7, 5): call
- add_small(7, 5) → bridge.cpp: runs the body
Read the three files as one unit: the import declares, the generated header carries the prototype, the bridge defines.
// SystemVerilog side
import "DPI-C" function int is_large(input byte unsigned len);
...
if (is_large(burst_len)) large_bursts++;
// C side (bridge.cpp)
#include "Vtb__Dpi.h" // prototypes generated from the import
extern "C" int is_large(unsigned char len) { return len > 127; }Self-Check
Tick each key point you covered in your answer.
What Interviewers Listen For (Junior)
A good junior answer writes the import with an explicit direction and byte unsigned. It includes the generated header in the C file.
What Interviewers Listen For (Mid-Level)
A good mid-level answer uses context only when the C code calls back into SystemVerilog. It says what happens to x and z values when they cross into C.
Cross-Questions
Where the interviewer goes next. Open a node to practise that follow-up.
WhyWhat IfBreak ItCompare
- Calling a C Function from the Testbench
-
WhyWhy Include the Generated DPI HeaderL1 Junior
Why bother including
Vtb__Dpi.hin the C file when the code compiles without it?2:30You have two and a half minutes. Speak or type, then press Get Feedback under your answer. No credits needed.
Speech-to-text runs in your browser. Chrome and Edge may send audio to their own speech service.
You can edit your answer before you send it. Feedback uses AI and checks your answer only against our written answer. No credits are used and your answer is not saved.
Reveal the Model Answer
The simulator writes a C prototype for every import into that header. With it included, the C compiler compares your definition against the import and refuses a mismatch such as
charagainstunsigned char. Without it, the linker matches names only, so a wrong type or direction runs and produces wrong numbers.Self-Check
Tick each key point you covered in your answer.
Half-Truth
"The header is only needed for exported functions."
It carries a prototype for every import too, and that is what catches type mismatches at build time.
Source Lesson D1.1 review claims (Verilator writes Vtb__Dpi.h with a prototype per import)
-
What IfA C++ Definition Without Extern CL1 JuniorL2 Mid-Level
What if the C side is a C++ file and you forget
extern "C"on the definition?2:30You have two and a half minutes. Speak or type, then press Get Feedback under your answer. No credits needed.
Speech-to-text runs in your browser. Chrome and Edge may send audio to their own speech service.
You can edit your answer before you send it. Feedback uses AI and checks your answer only against our written answer. No credits are used and your answer is not saved.
Reveal the Model Answer
The C++ compiler mangles the function name. The linker then looks for the plain C name and does not find it. So the build stops with an undefined reference at link time. Add
extern "C"to the definition.Self-Check
Tick each key point you covered in your answer.
Misconception
"A missing
extern \"C\"gives wrong values at run time."It fails earlier, at link time, as an undefined reference. Wrong values come from a type mismatch, which the header catches.
Source Lesson D1.1 key points
-
ComparePure, Plain and Context ImportsL2 Mid-Level
Compare a
pureimport, a plain import and acontextimport. When do you need each?2:30You have two and a half minutes. Speak or type, then press Get Feedback under your answer. No credits needed.
Speech-to-text runs in your browser. Chrome and Edge may send audio to their own speech service.
You can edit your answer before you send it. Feedback uses AI and checks your answer only against our written answer. No credits are used and your answer is not saved.
Reveal the Model Answer
A
purefunction promises its result depends only on its arguments, with no side effects. So the simulator may skip or reuse calls. A plain import may keep state and have side effects but must not call back into SystemVerilog. Acontextimport may call exported SystemVerilog routines and ask for its calling scope. This costs some optimisation, so use it only where C calls back.Self-Check
Tick each key point you covered in your answer.
Half-Truth
"Mark every import
pure; it runs faster."A function with state, such as a model update, may then be called fewer times than written, and the model drifts.
Source IEEE 1800-2017 §35.5.2 (pure functions)
-
What IfPassing a Whole Packet to CL2 Mid-Level
Asked if they need to pass a whole packet
What if the C model needs a whole packet of bytes, whose length varies?
2:30You have two and a half minutes. Speak or type, then press Get Feedback under your answer. No credits needed.
Speech-to-text runs in your browser. Chrome and Edge may send audio to their own speech service.
You can edit your answer before you send it. Feedback uses AI and checks your answer only against our written answer. No credits are used and your answer is not saved.
Reveal the Model Answer
Declare the argument as an open array,
input byte unsigned data[], and C receives ansvOpenArrayHandle. It asks the handle for the bounds withsvSize,svLeftandsvRight, and gets element pointers withsvGetArrElemPtr1using SystemVerilog indexes. Those pointers are valid only during the call, so copy anything the model must keep.A Handle Describes the ArrayArrows show what each side passes or reads. The handle is a descriptor; the bytes stay in the simulator's storage and C reads them one at a time through the accessor. Illustrative sequenceCallValueReturnWaiting- Pass the handle
The testbench calls
sum_bytes(ascending, 3, total). C gets a handle to the array, with its bounds, and a count of valid bytes. - Ask the shape
Before reading anything, C asks the handle for its dimension count and bounds: one dimension, left 3, right 5, size 3.
Follow the calls, values and waits
- call svLeft(h, 1): sum_bytes → svOpenArrayHandle
- return left 3, right 5, size 3: svOpenArrayHandle → sum_bytes
- Walk from the left bound
Three reads through the accessor, at the array's own indexes 3, 4 and 5. Each pointer is used at once and never kept.
Follow the calls, values and waits
- data 1, 128, 255: elements → sum_bytes
- Return the sum
The sum 1 + 128 + 255 = 384 goes out through the output pointer, and status 0 says it is valid.
Follow the calls, values and waits
- data 384: sum_bytes → total
Read the diagram as text
- main.sv: byte unsigned ascending[3:5] = '{1, 128, 255};
- svOpenArrayHandle: dimensions 1 · left 3 · right 5 · size 3
- elements: [3] = 1 [4] = 128 [5] = 255
- sum_bytes: asks the handle, then reads [3], [4], [5]
- total: 384, written through the output pointer
- main.sv → svOpenArrayHandle: passes a handle
- svOpenArrayHandle → elements: describes
- sum_bytes → svOpenArrayHandle: svLeft, svRight, svSize
- sum_bytes → elements: svGetArrElemPtr1(h, i)
- sum_bytes → total: writes
The handle is not a byte pointer. Ask it for the bounds, then read each element by the index the caller used.
Self-Check
Tick each key point you covered in your answer.
Misconception
"C gets a plain
unsigned char*to element 0 and can index from 0."It gets a handle, and indexes follow the SystemVerilog declaration, so an array declared
[3:5]has no element 0.Source IEEE 1800-2017 §35.5.6.1 (open arrays)
- Pass the handle
-
Break ItA C Model That Kept a String PointerL2 Mid-Level
A C model stores the
const char*it receives for a test name and prints it at the end of the test. Sometimes it prints garbage. Why?2:30You have two and a half minutes. Speak or type, then press Get Feedback under your answer. No credits needed.
Speech-to-text runs in your browser. Chrome and Edge may send audio to their own speech service.
You can edit your answer before you send it. Feedback uses AI and checks your answer only against our written answer. No credits are used and your answer is not saved.
Reveal the Model Answer
A string argument reaches C as a pointer to memory the simulator owns, valid only while the call runs. After the call returns, the simulator may reuse or free that memory, so the stored pointer can point at anything. Copy the text into storage the C side owns during the call.
The Life of a String Across the CallArrows show where the pointer points and what C does with it. The text lives in the simulator's buffer; the copy in keptis the only thing that survives the call.Illustrative sequenceCallValueReturnWaiting- Pass
The testbench calls
protocol_code(cfg). C gets a pointer to the bytesA X Iand a terminating zero, nothing more.Follow the calls, values and waits
- data AXI: string cfg → const char* name
- Read during the call
strcmpreads the bytes and the function returns 2. The pointer has done its job.Follow the calls, values and waits
- data AXI: const char* name → protocol_code
- return 2: protocol_code → const char* name
- Copy what you must keep
remember_namecopies the bytes intokept, storage the model owns. After this the model never touchesnameagain.Follow the calls, values and waits
- data AXI: const char* name → kept[32]
- The call returns
The testbench assigns a new value to
cfg. A keptnamepointer would now read whatever the simulator put there;keptstill says AXI.
Read the diagram as text
- string cfg: "AXI" in the simulator's own buffer
- const char* name: points at that buffer while the call lasts
- protocol_code: strcmp during the call, returns 2
- kept[32]: remember_name copies the bytes; the model keeps this
- cfg = "changed…": after the call the simulator reuses its buffer
- string cfg → const char* name: passes
- const char* name → protocol_code: read now
- const char* name → kept[32]: copied now
- cfg = "changed…" → const char* name: stale pointer
Borrow during the call, copy to keep. A pointer that outlives the call points at memory the simulator may have changed, moved or freed.
Self-Check
Tick each key point you covered in your answer.
Misconception
"The string lives as long as the SystemVerilog variable does."
The pointer refers to the simulator's copy for that call, not to the variable, so a later change or reuse invalidates it.
Source IEEE 1800-2017 Annex H.7.2 (argument passing: ownership of string storage)
- Pass
What People Get Wrong
Common wrong answers and why they fail. Run It shows the wrong belief failing on a real simulator.
Misconception
"byte holds 0 to 255, like a byte in C."
byte is a signed 8-bit type, so 200 reaches C as -56. The twin's model calls it small; byte unsigned fixes it.
Run It
A burst length of 200 passed as byte to a C function that tests len > 127.
FAIL: byte is signed, so C received -56 and called it smallSee the Fix
module tb;
// The fix: byte unsigned maps to unsigned char.
import "DPI-C" function int is_large(input byte unsigned len);
byte unsigned len = 8'd200;
initial begin
if (is_large(len)) $display("PASS: C received 200 and called it large");
else $display("FAIL: byte is signed, so C received -56 and called it small");
$finish;
end
endmodule
Prints: PASS: C received 200 and called it large
Tool as Truth
"It linked and ran, so the C prototype matches the import."
DPI-C uses C linkage, which carries no types. Without the generated header, a mismatched prototype links and passes garbage, as lesson D1.1 measured.
Source Lesson D1.1 review claims (a mismatched output argument linked and returned a wrong sum on Verilator 5.052)
Missed Corner
"Pass the bus as logic [7:0] and C gets the x bits as ordinary numbers."
A logic vector crosses as pairs of words called aval and bval. The C side must read bval to see an x or a z. On a two-state simulator the x is already 0 before the call.
Source IEEE 1800-2017 Annex H.8.3 (canonical representation of logic vectors)
Spec Misquote
"The C function can wait a few cycles with a sleep."
An import function returns in zero simulation time. A sleep only stalls the simulator's wall clock; time-consuming behaviour belongs in SystemVerilog tasks.
Source IEEE 1800-2017 §35.5.1