Skip to content
VLSI Mentor

C Part II · Module 23 · Calling SystemVerilog from C

Control Flow Across the Boundary

Where is control, right now? The question has an exact answer at every point of a crossing, and a measured timeline settles it: four steps from the testbench into C, a write back into the simulator, a return, and the testbench resuming — every one of them carrying the same timestamp. A second SystemVerilog process running alongside ran before the call and after it and never once inside it. That single observation is the mechanism behind the previous chapters' most surprising result and behind every constraint in the next one.

Expert22 min readCPart IIVerificationDPI-C

Part II · Module 23 · Chapter 23.4 · Control Flow Across the Boundary

1. The Question, Asked Precisely

Chapter 23.1 measured something that surprises nearly everyone: C wrote a value three times inside one call and a SystemVerilog process watching it woke once. Chapter 23.3 measured a callback whose scope was the task rather than the module.

Both follow from one fact about control flow, and this chapter's purpose states the job exactly: follow the call path and say where control sits at every moment.

So this is a timeline with a timestamp on every line, not an argument.

2. The Measurement

A testbench calls one C function. The C function reaches back into the simulator, writes a variable, and returns. A second SystemVerilog process runs alongside, so that "nothing else ran" is something observed rather than claimed.

Measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  ----    t=1  [SV other process] I ran
  ----    t=3  [SV other process] I ran again
  step 0  t=5  [SV] about to call, from_c = 0
  step 1  t=5  [C]  entered -- control is now in C, SystemVerilog is suspended
  step 2  t=5  [C]  wrote tb.from_c -- still inside the same call
  step 3  t=5  [C]  about to return -- control goes back to SystemVerilog
  step 4  t=5  [SV] back. returned 22, from_c = 42

Read the timestamps down the right-hand side of the steps: 5, 5, 5, 5, 5.

A measured timeline of one call from SystemVerilog into C and back. Before the call, at times 1 and 3, another SystemVerilog process runs freely. At time 5 the testbench process calls into C and control crosses the boundary; this is step 1, and from here the simulator is suspended. Inside the call, at step 2, the C function reaches back into the simulator and writes the value 42 into a variable. At step 3 the C function returns the value 22 and control crosses back. Step 4 is the testbench process resuming, where it observes both the returned 22 and the written 42. Every one of the four steps carries the same timestamp of 5, and the other process ran only at times 1 and 3, never between steps 1 and 3 — which is the whole point: a call is one instant and nothing else in the simulation runs inside it.One call, four steps, all at the same simulation timeAnother SV processTestbench processThe C functiontb.from_ct=1 and t=3: thisprocess runsstep 1, t=5: controlcrosses into Cstep 2, t=5: writes42step 3, t=5: returns22step 4, t=5: reads42
Figure 1 — the measured crossing, drawn as a timeline. Time runs downward, but the simulation's clock does not: every step from 1 to 4 happened at the same timestamp of 5. The first message is the other SystemVerilog process, which ran at times 1 and 3 — before the call — and appears nowhere between steps 1 and 3, because there is nowhere for it to appear. Steps 2 and 3 are both inside the single call: the C function writes a simulator variable and then returns a value, and the testbench at step 4 sees both. The important feature of this picture is what is absent from it, namely any second thing happening while control is on the right-hand side.

3. Four Facts, All in Seven Lines

One — the crossing is a single instant. Every step carries t=5. Entering C, writing a simulator variable, returning, and resuming all happened at the same simulation time. The C side did real work; the clock did not move.

Two — nothing else ran during it. The other process ran at t=1 and t=3, and never between steps 1 and 3. It was not slowed down or deprioritised: there was no moment between those steps for it to run in.

Three — both results arrive, and arrive together. The write at step 2 and the return value at step 3 are both visible at step 4: returned 22, from_c = 42. From the testbench's point of view they happened simultaneously, because they did.

Four — control is in exactly one place at a time. There is no concurrency anywhere in the picture. A call into C is a subroutine call that happens to cross a language boundary, and the simulator is a single thread of control for its whole duration.

4. Why Chapter 23.1's Result Is Now Obvious

Chapter 23.1 §3 measured three writes producing one wake-up. With the timeline in hand that stops being surprising and becomes arithmetic.

The three writes all happened between steps 1 and 3 — inside the single instant, with the simulator suspended. A process sensitive to that variable can only run when the simulator is running processes, and the first time it got to do so was after step 3. By then the variable held its final value, so the process woke once and saw the last write.

Nothing was dropped and nothing raced. The intermediate values existed in the C program's memory and never existed in the simulation, because the simulation was not running while they were there.

The same reasoning settles two further questions without new measurements:

Why a callback's scope is the task and not the module (23.3 §3): the call has an exact position in the SystemVerilog source, and the innermost scope containing that position is the task. Control is at a point, not in a region.

Why a sequence of events must be a sequence of calls (23.1 §4): each call is one instant, so n instants require n crossings. That is why the fetch loop — which looked clumsy — was the only design that let the testbench act on each event.

5. The Misconception: The Simulator Carries On Meanwhile

"A simulation is concurrent. While my C function is working, the simulator is getting on with other processes — that is the whole point of an event-driven simulator."

Measured false. The other process ran at t=1 and t=3 — before the call — and produced nothing at all between steps 1 and 3.

The premise is right about simulation in general and wrong about this. A simulator is concurrent in the sense that many processes make progress over time, and it does that by interleaving them: at each instant it runs ready processes one at a time until none can continue. That interleaving happens between statements, not inside them.

A call into C is, from the simulator's point of view, part of a statement. The process executing it has not yielded, so no other process can be scheduled, and there is no mechanism by which one could be. The concurrency people have in mind requires the running process to reach a point where it can be suspended, and a C call is not such a point — which is exactly what Chapter 23.5 formalises for simulation time.

Three things follow that are worth stating as design rules:

  • Work done in C is free in simulation time and not free in wall-clock time. A model that takes a second returns at the same timestamp and the run still takes a second longer.
  • "Meanwhile" does not exist. Any design sentence containing it, at this boundary, is describing something that cannot happen.
  • To let the simulation advance, you must return. That is the only mechanism, and Chapter 23.5 measures what it costs and what it enables.

6. What You Should Be Able to Say Now

  1. Control has an exact location at every point of a crossing, and the timeline in §2 gives it with timestamps.
  2. Measured: steps 0 through 4 all at t=5 — the whole crossing is one instant.
  3. Measured: a second SystemVerilog process ran at t=1 and t=3 and never between steps 1 and 3.
  4. The write and the return arrive together — returned 22, from_c = 42 at step 4.
  5. Control is in one place at a time: a call into C is a subroutine call that crosses a language boundary, not a concurrent activity.
  6. This explains 23.1 §3: three writes inside one instant, so the first chance any process had to look was after the call, when only the last value remained.
  7. It also explains 23.3's scope — control is at a point, and the innermost scope containing that point is the task.
  8. And why n events require n calls — each call is one instant.
  9. One thread, two stacks: a crash in your C takes the simulator with it, and anything that blocks blocks everything.
  10. Output ordering is control-flow ordering, which makes cross-boundary markers unusually informative — if both sides' output goes through one mechanism.
  11. "Meanwhile" does not exist here; to let the simulation advance, return.

7. Exercises

  1. Instrument a call with a marker on each side and print the simulation time with every marker. How many distinct timestamps appear?
  2. Add a second process that prints periodically. Does any of its output land between your entry and exit markers? Explain why not.
  3. Have the C write a simulator variable and also return a value. At what point does the testbench see each? Why is the answer the same point?
  4. Using §3's four facts only, re-derive Chapter 23.1's three-writes-one-wake-up result without re-running it.
  5. Using them again, explain why the scope reported in Chapter 23.3 was the task rather than the module.
  6. A colleague proposes that the C model poll a socket until data arrives. Describe precisely what the simulation does during that poll.
  7. Measure the wall-clock time of a run with and without a slow C function. Then compare the simulated end times. Why do they differ in one dimension and not the other?
  8. Your C printf output and your $display output appear interleaved in an order that makes no sense. What is the most likely cause, and what does it destroy?

8. Summary

  • Where is control? Exactly one place, at every moment — and §2's timeline gives it with timestamps.
  • Measured: every step at t=5; the crossing is one instant.
  • Measured: the other process ran at t=1 and t=3, never inside the call.
  • Write and return arrive together: returned 22, from_c = 42.
  • A call is a subroutine call across a language boundary, not a concurrent activity.
  • This explains 23.1's three-writes-one-wake-up, 23.3's task-level scope, and why n events need n calls.
  • One thread, two stacks — a crash takes the simulator down; anything blocking blocks everything.
  • Output order is control-flow order — valuable, and destroyed by two unsynchronised output streams.
  • "Meanwhile" does not exist. To let the simulation advance, return.

Returning is the only way to let the simulation move, and that sentence is a constraint rather than an observation. The next chapter measures exactly how hard a constraint it is — including what happens to two hundred million iterations of work done inside a single instant, and what remains possible once waiting is off the table.

Standards & specifications

Governing standard
IEEE Std 1800 (SystemVerilog)(opens IEEE in a new tab)

Defines SystemVerilog language semantics — syntax, data types, scheduling and the behaviour a conforming simulator must produce. It does not define tool-specific synthesis support or vendor methodology.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the C Programming curriculum.