C Part II · Module 20 · DPI-C Type Mapping
Why Types Do Not Map One to One
A SystemVerilog type and a C type are two different descriptions of storage, and the translation between them is specified rather than obvious. Most of it is lossless and some of it cannot be: a four-state logic value carries two bits of information per bit position and C has one, so something has to be decided. This chapter is the map — what crosses cleanly, what has no counterpart at all, and the one case where the answer comes from your compiler's ABI rather than from any standard — and it establishes why the real failure is not that the mapping is unclear but that it is right for small values and wrong at the top bit.
Advanced21 min readCPart IIVerificationDPI-C
Part II · Module 20 · Chapter 20.1 · Why Types Do Not Map One to One
1. Two Descriptions of Storage
Module 19 covered the boundary's architecture and deferred one thing at every opportunity: what happens to a value as it crosses.
Chapter 19.4 declared input byte unsigned data[] and said an array arrives as a pointer. Chapter 19.1 §3 said SystemVerilog's int maps to C's int and measured sizeof(int) as 4. Both are true, and both are the easy end of a question this module's description calls "the heart of DPI and the source of most of its bugs".
The reason there is a question at all is that the two languages describe storage differently.
C describes objects. A type has a size, an alignment, a representation, and — Chapter 14.1 established — a good deal that is implementation-defined. Every bit of an object holds one of two values.
SystemVerilog describes hardware. A logic carries four states, not two. A packed vector can be any width, not just 8, 16, 32 or 64. A bit and a logic differ in what they can represent rather than in how big they are.
So the mapping is a defined translation, written in the language standard, between two systems that were designed for different purposes. Most of it is lossless. One part of it cannot be.
2. Specified, Not Obvious
The most important thing to establish before any detail: the mapping is specified. The SystemVerilog standard says what each type becomes on the C side. It is not a convention, not a per-tool arrangement, and not something to infer from sizes agreeing.
That matters because of how this goes wrong in practice. The failure is almost never "nobody knew what the mapping was". It is that somebody assumed it, the assumption was right for the values they tested, and it was wrong somewhere they did not look.
Chapter 20.4 measures exactly that shape. The same byte read as signed and as unsigned:
SV byte as int8_t as uint8_t agree?
0x00 0 0 yes
0x01 1 1 yes
0x7F 127 127 yes
0x80 -128 128 NO <- differ
0xFF -1 255 NO <- differThree of five agree. A model tested with small values passes; the first value with the top bit set is wrong, and Chapter 15.6 measured what happens next when it is widened — -128 becomes 0xFFFFFF80 and 128 becomes 0x00000080.
That is the module's characteristic bug: not a mapping nobody understood, but a mapping nobody read, which worked until the data reached the top of its range.
3. The Map
4. The Easy Majority
Most of the map is unremarkable, and it is worth seeing the numbers so that the hard cases stand out properly.
Measured on this machine:
SV byte -> char sizeof 1 range -128..127
SV shortint -> short sizeof 2 range -32768..32767
SV int -> int sizeof 4 range -2147483648..2147483647
SV longint -> long long sizeof 8 range -9223372036854775808..9223372036854775807
sizeof(double) 8 sizeof(float) 4
sizeof(void *) 8 sizeof(uintptr_t) 8
pointer round trip through uintptr_t: preservedFour integer types at four widths, a double for real, a void * for chandle. Chapters 20.3, 20.7 and 20.10 are mostly confirming those and naming the assumptions inside them.
One of those assumptions is visible already:
long is 8 bytes here -- NOT the same as long long on every targetlong and long long are both 8 bytes on this machine, and the standard maps longint to long long rather than to long. That is not arbitrary: Chapter 14.1 §4 established that C fixes minimum ranges rather than widths, and long is 4 bytes on 32-bit targets and on 64-bit Windows. long long is at least 64 bits everywhere. The mapping chose the one that is portable, and a model that writes long because it happens to be 8 bytes here has made Chapter 14.1's mistake at a language boundary.
5. The Part That Cannot Be Lossless
One row of Figure 1 is different in kind, and it is why Chapter 20.5 is rated Expert and Critical.
A SystemVerilog logic bit is not a bit. It carries four states — 0, 1, X and Z — which is two bits of information per bit position. A C uint32_t has thirty-two bits of value and no second plane.
From the LRM: a 4-state value crosses as two words — conventionally called aval and bval — with the pair encoding the four states:
bval aval meaning
0 0 0
0 1 1
1 0 Z
1 1 XMeasured, modelling that encoding in C:
the 4-state value: ZX01 aval=0x5 bval=0xC
collapsed, bval discarded: 0101 aval=0x5 bval=0x0Discarding the second word turned Z into 0 and X into 1 — and, crucially, made them indistinguishable from real data. A model that reads only aval cannot tell a genuine 1 from an X.
This is the module's one genuinely lossy crossing, and the loss is not a defect of the interface. C has nowhere to put the information. What the interface gives you is the choice, and Chapter 20.5's whole argument is that the choice must be made deliberately rather than by not looking at bval.
6. The Part That Is Not Specified At All
One row is different again, and this is the third thing the module's description promises: where the answer is tool-dependent rather than specified.
A struct crossing the boundary has a layout, and on the C side that layout is chosen by the compiler's ABI — not by the SystemVerilog standard and not by C's. Chapter 14.5 measured what that means:
struct natural sizeof 12 offsets a=0 b=4 c=8
struct reordered sizeof 8 offsets b=0 a=4 c=5
struct packed_s sizeof 6 offsets a=0 b=1 c=5Three different layouts for the same three members, chosen by declaration order and one compiler extension. Chapter 14.5 §2 made this a judgement rather than a rule, and Chapter 20.9 does the same one level up — with the extra difficulty that the thing on the other side is not another C compiler.
7. The Misconception: An int Is an int
"SystemVerilog
intis 32 bits, Cintis 32 bits. They are the same thing. Mapping is a formality."
Measured false in three directions, and the three are the module's three hard groups.
Width is not the only axis. Signedness is a separate decision — Chapter 14.2 §4 established that and Chapter 20.4 owns it here. Two types can be the same width and disagree about half their range, and §2's table measured exactly where: 0x80 is -128 or 128 depending on nothing but how it was declared.
C's widths are not fixed. Chapter 14.1 §4 established that the standard fixes minimum ranges. long is 8 bytes on this machine and 4 on others, which is precisely why longint maps to long long. A mapping inferred from "these happen to be the same size" is Chapter 16.8's observed, not guaranteed.
Some types have no counterpart at all. logic is the clearest case — §5 measured a 4-state value needing two words and losing X and Z when collapsed — and string lifetime is the other: the type crosses, the contract does not.
The accurate position, which is the chapter's purpose in its own words:
The mapping is a defined translation, not an assumption. Read it, write it down next to the declaration, and test at the top of the range — because an assumed mapping is right for small values and wrong where the data actually goes.
8. What You Should Be Able to Say Now
- The two languages describe storage differently. C describes objects with two-valued bits; SystemVerilog describes hardware with four-valued bits and arbitrary widths.
- The mapping is specified, not conventional. The failure is that people assume it rather than read it.
- The characteristic bug is right for small values and wrong at the top bit. Measured:
0x00,0x01,0x7Fagree as signed and unsigned;0x80and0xFFdo not. - Most of the map is lossless. Measured:
char1,short2,int4,long long8,double8,void *8, pointer round trip preserved. longintmaps tolong long, notlong— becauselongis 4 bytes on some targets. Measured 8 here, which is exactly the trap.- A
logichas no C counterpart. It crosses as two words; measured, collapsingZX01by discardingbvalgives0101, makingXandZindistinguishable from data. - A struct's layout is the ABI's decision. Measured: three layouts —
sizeof12, 8 and 6 — for the same three members. - A string's lifetime is not part of the interface. The type crosses; the contract has to be supplied.
svdpi.hdoes not exist on this machine, so Chapter 20.2 is LRM-derived — but every C-side representation question in this module is measurable, and is measured.- Write the mapping down next to the declaration and test at the top of the range.
9. Exercises
- Print
sizeofand the range forchar,short,int,long,long long,double,floatandvoid *on your machine. Which of them differ from the values measured here? - §4 says
longintmaps tolong longrather thanlong. Name two targets where that distinction matters and say what would break. - Reproduce §2's signed/unsigned table for a 16-bit value. At which values do the two readings diverge?
- Implement the
aval/bvalencoding from §5 and decode a value containing all four states. Then write the one-line check a model should perform before using it. - Reproduce §6's three structure layouts. Then add a fourth declaration order and predict its
sizeofbefore measuring. - For each row of Figure 1, say whether the information needed to get it right is in the SystemVerilog standard, in your C compiler's documentation, or in neither.
- Take a DPI declaration you have seen or written. For every argument, write down the C type it maps to and the range of values you have actually tested it with.
10. Summary
- Two different descriptions of storage: C's two-valued bits and fixed object types against SystemVerilog's four-valued bits and arbitrary widths.
- The mapping is a defined translation, specified in the standard — the bug is assuming it rather than reading it.
- Right for small values, wrong at the top bit — measured:
0x00/0x01/0x7Fagree,0x80/0xFFdo not. - Most of it is lossless — measured
char1,short2,int4,long long8,double8,void *8, round trip preserved. longint→long long, neverlong—longis 8 here and 4 elsewhere.logichas no counterpart: two words, and collapsingZX01gives0101— X and Z become indistinguishable from data.- A struct's layout is the ABI's — measured
sizeof12 / 8 / 6 for the same members. - A string's lifetime is not in the interface.
svdpi.his absent here, so 20.2 is LRM-derived; every C-side representation question is measured.- Write the mapping beside the declaration; test at the top of the range.
The translation is defined, and the thing that defines the C half of it is a header the standard specifies and ships with every simulator — one that supplies type names, accessor functions and a small amount of machinery that is easy to mistake for your own code.
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.
