Skip to content
VLSI Mentor

C++ Part II · Module 28 · C/C++ and SystemVerilog DPI

Open Arrays and the Open Array API

Chapter 28.8 ended on a contract neither language enforces: the size of an array, passed as a second argument and trusted. An open array removes it, by handing the C side a handle that carries its own bounds and insisting the elements be reached through accessor functions rather than by pointer arithmetic. That insistence is the point of the mechanism and the thing people work around, usually by fetching the base pointer and indexing it directly — which works until the SystemVerilog declaration uses a descending range, or one that does not start at zero, at which point the C side reads outside the array and keeps going. This is also the most constrained chapter in the module, and it says so plainly: the API lives in a header that is absent on this machine, so the access pattern is demonstrated through a mock with the standard's own signatures and the API's semantics are quoted rather than measured. What the mock does establish is the shape of the correct loop and exactly what each of the two common shortcuts costs.

Expert19 min readC++Part IIVerificationDPI

Part II · Module 28 · Chapter 28.9 · Open Arrays and the Open Array API

1. What an Open Array Is

A SystemVerilog argument declared with an unsized dimension:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Reference syntax -- not executed here, per Chapter 28.1 §5.
import "DPI-C" function int sum_all(input int values[]);

That [] is the open array. The function accepts an array of any size, and the size travels with the argument rather than beside it — which is the whole reason the mechanism exists, because Chapter 28.8 §5's size contract is the part of an array interface with no language support on either side.

On the C side the parameter is a handle, not a pointer:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
extern "C" int sum_all(const svOpenArrayHandle h);

And the handle is opaque. You do not know its layout, you may not dereference it, and the elements are reached through functions the standard provides.

2. What This Chapter Can and Cannot Establish

This needs stating before any transcript, because the constraint is tighter here than anywhere else in the module.

The API lives in svdpi.h, which is absent. Chapter 28.1 §5 measured zero matches for that header anywhere under the Homebrew prefix, /usr/local or /usr/include, and no simulator here supports DPI at all. So the C side of a real open-array binding cannot be compiled on this machine, let alone run.

So the chapter does three distinct things and labels which is which:

The API's existence, names and semantics are quoted from IEEE 1800. svLeft, svRight, svLow, svHigh, svSize, svDimensions, svGetArrElemPtr1, svGetArrayPtr, svIncrement — these are the standard's, and I am citing them rather than demonstrating them.

The access pattern is demonstrated through a mock with the standard's signatures over a known buffer, which establishes the shape of the correct loop and what the shortcuts cost. The mock is mine; it is not any simulator's implementation, and it proves nothing about how a real one behaves internally.

And no claim is made about a real implementation's layout or performance. Where this chapter says "the standard requires", that is a citation; where it shows a number, that number came from the mock.

This is the technique the Tcl track used when no EDA tool existed on the machine: build a runnable stand-in with the real interface, demonstrate the usage, and never present it as the tool's own output.

3. The Accessors, and the Correct Loop

The functions worth knowing, with the dimension argument being 1 for the first dimension:

functionreturns
svLeft(h, dim)the left bound as declared
svRight(h, dim)the right bound as declared
svLow(h, dim)the smaller of the two
svHigh(h, dim)the larger of the two
svSize(h, dim)the number of elements
svDimensions(h)how many dimensions
svGetArrElemPtr1(h, i)a pointer to element i in the declared range
svGetArrayPtr(h)the base pointer — see §5

The correct loop uses svLow and svHigh, and indexes in the declared range:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
static long sum_correctly(svOpenArrayHandle h)
{
    long s = 0;
    for (int i = svLow(h, 1); i <= svHigh(h, 1); ++i)
        s += *(const int*)svGetArrElemPtr1(h, i);
    return s;
}

Three properties of that loop are the content of this chapter.

It uses svLow/svHigh, not svLeft/svRight. Here is why, measured through the mock on the same five-element storage declared two ways:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
A. an ascending range, int a[0:4]
   left=0 right=4 low=0 high=4 size=5
   sum via the API = 150   (expected 150)

B. a DESCENDING range, int a[4:0] -- same storage
   left=4 right=0 low=0 high=4 size=5
   sum via the API = 150   (still 150 -- low/high normalise)

left and right swapped; low and high did not. So one loop written against svLow/svHigh handles both range directions, while a loop written as for (i = svLeft; i <= svRight; ++i) runs zero times for a descending declaration — a silent zero-iteration loop returning a plausible 0 rather than an error.

It indexes in the declared range, not from zero. The measurement that makes this concrete:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
C. a range that does not start at zero, int a[3:7]
   low=3 high=7 size=5
   element [3] = 10 (the FIRST element), element [7] = 50
   indexing it 0-based would read 12 bytes before the array

For int a[3:7], element [3] is the first element. svGetArrElemPtr1(h, 3) returns the base; svGetArrElemPtr1(h, 0) would be three elements before the array — twelve bytes of memory that are not yours. A C programmer's reflex is to loop i from 0 to size-1, and for any range not starting at zero that reflex is an out-of-bounds read.

And it takes the element type from the declaration. The (const int*) cast is still the programmer's responsibility; the handle does not carry the element type in any way the compiler can check, so Chapter 28.8 §3's wrong-element-type failure is available here too.

4. Why Ranges Are Not Always 0-Based

Worth a paragraph, because C programmers meeting this for the first time assume [3:7] is exotic. It is not, and three common cases produce it.

Bit and word ranges that mirror hardware. logic [31:0] is universal, and an unpacked array indexed to match a register map — word [8:15] for a block at that offset — is ordinary.

Descending ranges are the SystemVerilog default style for anything vector-like, because [MSB:LSB] reads naturally. So a descending unpacked range arrives more often than an ascending one in some codebases.

And a parameterised range. logic data [START:START+N-1] with START a parameter means the C side cannot know the base at compile time even in principle.

Which is the real argument for the API: the bounds are not a detail the C side can assume, so they have to be asked for. A fixed-size array lets you write the bound in the C declaration and be wrong when the SystemVerilog changes; an open array makes that impossible.

5. The Two Shortcuts, and What They Cost

Shortcut one: treat the handle as a pointer. The handle is an opaque type, and dereferencing it reads the handle's own storage:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
D. treating the handle as a raw pointer
   *(int*)handle = …   <-- a field of the handle struct, not data
   the first real element is 10

The value is elided because it is a pointer's low bits and changes every run — which is itself the diagnostic: the number varies between runs while the array's contents do not. In the mock the handle is a small struct, so the read yields one of its fields; in a real implementation it may be anything at all, including a value that looks plausible. There is no diagnostic either way, because (int*)h is a well-formed cast and the read is within the handle's own allocation.

Shortcut two: fetch the base pointer and index it directly. svGetArrayPtr(h) exists and returns a pointer to the array's storage, and using it is legitimate — with two conditions that are easy to forget.

You must still get the base index right. The pointer is to the first element, so element [3] of a[3:7] is at offset 0 — which means an index computed in the declared range must have svLow subtracted from it before it is used as an offset. Getting that backwards is §3's twelve-bytes-before-the-array error with extra steps.

And the array must actually be contiguous, which the standard guarantees only for arrays whose layout permits it. For a packed-element open array, or a multi-dimensional one, the base pointer plus a stride you guessed is not the layout.

So the ranking: use svGetArrElemPtr1 by default, and reach for svGetArrayPtr only for a one-dimensional array of a scalar element type when you have measured that the per-element call is actually costing you something — which, given Chapter 27.10 §3's figure for a simulation step, it very rarely will.

6. Output Open Arrays

An open array can be an output or inout too, and the C side writes through the same accessors:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
extern "C" void fill(svOpenArrayHandle h, int base)
{
    for (int i = svLow(h, 1); i <= svHigh(h, 1); ++i)
        *(int*)svGetArrElemPtr1(h, i) = base + (i - svLow(h, 1));
}

Two points specific to the output direction.

You must write every element the testbench will read. There is no partial-write indication, so an element you skip holds whatever it held — Chapter 28.3 §4's rule for scalars, and more exposed here because the count is not known at compile time.

And the cast drops const, which means a mistake in the direction declaration is now a write into the simulator's storage rather than a harmless read. That is the case for keeping the const on the C declaration whenever the SystemVerilog says input, so that a stray write does not compile.

7. When to Use One

situationuse
the size is fixed and known to both sidesa plain array and a length — Chapter 28.8 §2
the size varies per callan open array
the size is a parameter the C side cannot knowan open array
the range may be descending or not 0-basedan open array
the array is multi-dimensional with varying boundsan open array, with svDimensions
maximum throughput over a huge arraya plain array; measure before deciding

The honest summary: an open array is the right default for anything whose shape is not a compile-time constant on both sides, and a fixed array with an explicit length is simpler when the shape genuinely is fixed. The cost of the open array is verbosity and a per-element call; the cost of the fixed array is a contract that can drift silently.

8. The Table

the questionthe answermeasured
what is an open arrayan argument with an unsized dimension []§1
what does C receivean opaque handle, not a pointer§1
can this chapter run a real oneno — svdpi.h is absent system-wide§2
what is quoted and what is shownthe API is quoted; the pattern is mocked§2
which bounds should the loop usesvLow/svHigh, which normalise§3
what did [0:4] and [4:0] giveleft/right swapped, low/high identical, both summed 150§3
what happens with svLeft on a descending rangea zero-iteration loop returning 0§3
what is element [3] of a[3:7]the first element§3
what does 0-based indexing read12 bytes before the array§3
what does dereferencing the handle givea field of the handle — and it varies between runs§5
is svGetArrayPtr legitimateyes, with the base index and contiguity understood§5
why does the API refuse to show the layoutso the C side cannot depend on it§5 callout
what must an output open array dowrite every element the testbench reads§6

9. What to Actually Do

  • Use an open array whenever the shape is not a compile-time constant on both sides.
  • Loop with svLow and svHigh, never svLeft and svRight — a descending range makes a left/right loop run zero times.
  • Index in the declared range, and remember element [low] is the first one.
  • Reach elements with svGetArrElemPtr1 by default.
  • Use svGetArrayPtr only for a one-dimensional scalar array, subtracting svLow from any declared-range index, and only after measuring that it matters.
  • Never dereference the handle. It is opaque, and the result varies between runs.
  • Cast to the element type from the declaration — the handle does not carry it.
  • Keep const on the C declaration for an input, so a stray write does not compile.
  • Write every element of an output open array.
  • Query svDimensions before assuming one dimension.
  • Say which parts of your own documentation are quoted and which are measured when your environment cannot run the thing you are describing.

10. Mistakes Worth Making Once

Looping from svLeft to svRight. Zero iterations for a descending range, returning a plausible 0.

Indexing from 0. For a[3:7] that reads twelve bytes before the array.

Dereferencing the handle. A field of the handle struct, and the number changes every run.

Using svGetArrayPtr and forgetting to subtract svLow. The same out-of-bounds read, by a different route.

Assuming contiguity for a multi-dimensional or packed-element array. The stride you guessed is not the layout.

Casting to the wrong element type. Chapter 28.8 §3's failure, available here too.

Skipping an element of an output open array. It holds whatever it held.

Dropping const on an input handle. A stray write now modifies the simulator's storage.

Assuming one dimension. svDimensions exists for a reason.

11. Interview Relevance

The frozen curriculum marks this Critical. The question is usually how do you pass a variable-size array through DPI?, and the answer is the open array — but the content is in what you do with the handle.

Say first that the parameter is an opaque handle rather than a pointer, and that the bounds and elements are reached through the standard's accessors: svLow, svHigh, svSize, svGetArrElemPtr1.

Then the two traps, which are what the question is really probing. Loop with svLow/svHigh, not svLeft/svRight, because SystemVerilog ranges are commonly descending and a left-to-right loop runs zero times on [4:0] — returning a plausible 0 rather than an error. And index in the declared range, because for a[3:7] element [3] is the first element, so a C programmer's 0-based loop reads twelve bytes before the array.

Then the reason the API is shaped that way, which is the senior point: every fact the C side would otherwise hard-code — the base, the direction, the count, the stride — lives in a SystemVerilog declaration that can change without the C file being recompiled. The accessors convert a compile-time assumption into a run-time query, which is the only way a cross-language assumption survives one side changing. So the shortcuts are not "faster but riskier"; they re-introduce exactly the dependency the mechanism removes.

And if you are asked about svGetArrayPtr, the honest answer is that it is legitimate for a one-dimensional scalar array, requires subtracting svLow from any declared-range index, and should follow a measurement rather than an assumption that the per-element call is costing you something.

12. What You Should Be Able to Say Now

  • That an open array is an argument with an unsized [] dimension, and that the C parameter is an opaque handle, not a pointer.
  • That the bounds and elements are reached through svLow, svHigh, svSize, svDimensions, svGetArrElemPtr1 and svGetArrayPtr.
  • That the correct loop uses svLow/svHigh, because [0:4] and [4:0] swap left and right while low and high stay the same — both summing 150 through the API.
  • That a svLeft-to-svRight loop on a descending range runs zero times and returns a plausible 0.
  • That for a[3:7] element [3] is the first element, so 0-based indexing reads twelve bytes before the array.
  • That dereferencing the handle yields a field of the handle, and that the value varies between runs while the data does not.
  • That svGetArrayPtr is legitimate for a one-dimensional scalar array, needs svLow subtracted, and assumes a contiguity the standard does not always give.
  • That the handle does not carry the element type, so the cast is still yours to get right.
  • That an output open array must have every element written, and that const on an input handle is what stops a stray write compiling.
  • That the API refuses to expose the layout so the C side cannot depend on it, since the layout is declared in the other language and can change without a recompile.
  • That this chapter's API behaviour is quoted from IEEE 1800 and its access pattern demonstrated through a mock, because svdpi.h is absent on this machine.

13. Exercises

Exercise 1 — the mock. Build a handle struct and the six accessors with the standard's signatures over a known buffer, and write the correct summing loop.

Exercise 2 — the direction trap. Declare the same storage [0:4] and [4:0] and confirm left/right swap while low/high do not. Then write a svLeft-based loop and watch it return 0.

Exercise 3 — the non-zero base. Declare [3:7] and confirm element [3] is the first. Compute how far before the array a 0-based index would read.

Exercise 4 — the handle. Dereference the handle, print the value, and run it twice. Note that the number changes and the data does not.

Exercise 5 — the base pointer. Use svGetArrayPtr correctly for a [3:7] array, subtracting svLow. Then forget to subtract and compare.

Exercise 6 — the output direction. Write a fill function through the accessors and deliberately skip one element. Decide how a testbench would notice.

Exercise 7 — on a real simulator. If you have one, compile the same C against its svdpi.h and confirm which of this chapter's quoted claims hold.

14. Summary

  • An open array is an argument with an unsized dimension, and it exists to remove Chapter 28.8 §5's size contract by making the bounds travel with the argument.
  • The C parameter is an opaque handle, not a pointer, and the elements are reached through the standard's accessors.
  • This chapter's API semantics are quoted from IEEE 1800 and its access pattern demonstrated through a mock, because svdpi.h is absent system-wide and no simulator here supports DPI — the constraint is stated rather than worked around.
  • Loop with svLow and svHigh. Measured through the mock: [0:4] and [4:0] swap left and right while low and high are identical, and both sum correctly to 150 — whereas a svLeft-to-svRight loop on the descending range would run zero times and return a plausible 0.
  • Index in the declared range. For a[3:7] element [3] is the first, so a 0-based loop reads twelve bytes before the array.
  • Never dereference the handle: it yields a field of the handle itself, and the value changes between runs while the data does not — which is the diagnostic.
  • svGetArrayPtr is legitimate for a one-dimensional scalar array, with svLow subtracted from declared-range indices and contiguity confirmed, and only after a measurement.
  • The handle does not carry the element type, so the wrong cast is still available as a failure.
  • An output open array must have every element written, and const on an input handle is what prevents a stray write.
  • The API's refusal to expose the layout is the feature: every fact the C side would hard-code lives in a declaration in the other language, so asking at run time is the only way the assumption survives that declaration changing.

Chapter 28.10 takes the same ownership question to text, where the storage belongs to somebody and only briefly.

Standards & specifications

Governing standard
ISO/IEC 14882 (the C++ language standard)(opens ISO/IEC (JTC 1/SC 22/WG 21) in a new tab)

Defines the C++ language and its standard library — the type system and templates, the object model and object lifetime, initialisation and overload resolution, the memory model and the containers, algorithms and utilities the library provides. Like C's standard it is a FAMILY of editions (C++98/03, C++11, C++14, C++17, C++20, C++23) and the editions are NOT interchangeable: a feature this curriculum relies on may be unavailable, differently specified or merely proposed in another edition, so each lesson names the edition its claim depends on. It does not define any foreign-function interface to a hardware description language, nor what a compiler actually emits, nor platform behaviour on real silicon — those are the implementation's and the device's contracts. Working drafts published by WG21 are freely available and are what most engineers read day to day; a draft is not the published standard.

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.