Skip to content
VLSI Mentor

C Part II · Module 20 · DPI-C Type Mapping

Mapping SystemVerilog Integer Types to C

Four types, four obvious-looking C counterparts, and three of the four hide an assumption worth knowing about. The most useful one concerns the simplest row: SystemVerilog's byte maps to C's char, and plain char's signedness is implementation-defined — measured signed here, and measured reversing on this same machine with one compiler flag. This chapter is the four rows and what each one assumes, and it ends with the spelling to actually write in your C, which is not the one the mapping table names.

Advanced20 min readCPart IIVerificationDPI-C

Part II · Module 20 · Chapter 20.3 · Mapping SystemVerilog Integer Types to C

1. The Four Rows

From the LRM, the integer mapping:

SystemVerilogCWidth
bytechar8
shortintshort int16
intint32
longintlong long int64

Measured on this machine:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  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

Everything lines up, every width is what the table says, and Chapter 20.2 §4's line puts all four on the no-header side.

So the table is correct and this chapter is not about the table. It is about the four assumptions underneath it, three of which are real and one of which will change what you write.

2. The First Row Is the Dangerous One

byte → char looks like the simplest row and it is the one to be careful about, for a reason Chapter 14.1 established in Part II's very first chapter.

Plain char's signedness is implementation-defined. Not signed char, not unsigned char — plain char is a third type, and whether it behaves as signed or unsigned is the implementation's choice.

Measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  plain char is SIGNED here
  CHAR_MIN -128  CHAR_MAX 127
  the byte 0x80 through a plain char: -128
  through signed char: -128   through unsigned char: 128

And Chapter 14.1 §2 measured the part that makes this more than trivia: the same source on the same machine with -funsigned-char gives the opposite answer. It is a build input, not a property of the chip.

So a C function written as

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int c_handle(char value);          /* the mapping table's spelling */

has an argument whose range is -128..127 on this build and 0..255 on another, from the same source. SystemVerilog's byte is signed and byte unsigned is not; the C side should say which it is rather than inherit a choice from a compiler flag.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int c_handle(int8_t  value);       /* SV byte          */
int c_handle(uint8_t value);       /* SV byte unsigned */

Chapter 14.1 §5 gave the rule — if a representation is specified outside your program, declare it explicitly rather than inheriting the compiler's default — and a DPI declaration is exactly such a specification.

3. The Second and Third Rows

Both assume something C does not promise, and in practice both are fine.

shortint → short. C guarantees short is at least 16 bits, not exactly 16. Measured 2 bytes here. A target with a 32-bit short is permitted by C and is essentially unknown in practice, but the assumption is there.

int → int. C guarantees int is at least 16 bits; SystemVerilog's int is exactly 32. Measured 4 bytes here, so they coincide. On a 16-bit embedded target C's int would be 16 bits and the mapping would not hold — which is why the DPI mapping is specified against the simulator's host rather than against any target.

Chapter 14.1 §4 established the underlying fact and it is the same one in both rows: C fixes minimum ranges, not widths. Two types agreeing in size on your machine is Chapter 16.8's observed, not guaranteed.

4. The Fourth Row, and Why It Is Not long

longint maps to long long int, and the obvious-looking alternative is wrong in a way that is invisible here.

Measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  long 8 bytes, long long 8 bytes -- equal HERE
  LONG_MAX  9223372036854775807
  LLONG_MAX 9223372036854775807

Identical. Both 8 bytes, both with the same maximum. A function written with long works perfectly on this machine and passes every test.

From the C standard: long is guaranteed at least 32 bits; long long is guaranteed at least 64. On 32-bit targets and on 64-bit Windows, long is 4 bytes. A long parameter there carries half of a longint.

That is why the mapping names long long — it is the type that is 64 bits everywhere — and it is a good illustration of the chapter's theme. The row looks like a free choice between two synonyms, the two are synonyms on the machine in front of you, and one of them is a portability bug waiting for a different host.

5. What to Write

The mapping tells you what the SystemVerilog type is. It does not tell you the best way to spell the C side, and the two answers differ in one row.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
#include <stdint.h>

int8_t   /* SV byte     */
int16_t  /* SV shortint */
int32_t  /* SV int      */
int64_t  /* SV longint  */

Three reasons, all of them Chapter 14.2's:

The width is stated rather than assumed. int32_t is 32 bits by definition; int is 32 bits on this host.

The signedness is stated. This is the one that matters, and §2 is why: int8_t is signed and uint8_t is unsigned, and neither depends on a compiler flag the way char does.

It reads as a transcription. A reviewer comparing the C against the DPI declaration sees int32_t against int and checks one fact. Seeing long against longint requires knowing which targets make them differ.

And the one honest caveat, from the callout: the exact-width types are optional in principle. On any host that runs a simulator they exist, so this is a recommendation you can follow without reservation — but it is worth knowing why the standard did not make it for you.

6. Where the Width Is Not the Question

One thing this chapter has held fixed deliberately: every row above is about width, and signedness has been treated as settled.

It is not. Chapter 20.1 §2 measured the shape of the problem:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  0x7F       127            127            yes
  0x80       -128           128            NO  <- differ

A byte and a byte unsigned are the same width and carry different meanings, and a C side that picks the wrong one is correct for every value below the halfway point. That is a separate axis from everything in this chapter, it is rated Critical where this one is High, and it is Chapter 20.4.

7. The Misconception: The Table Tells Me What to Write

"The mapping says byte is char, so I write char."

The table says what the SystemVerilog type corresponds to. It is a statement about the language mapping, not advice about your declaration, and following it literally on the first row gives you an argument whose signedness is a build input.

Measured: plain char is signed here, -funsigned-char reverses it (14.1 §2), and the byte 0x80 is therefore -128 or 128 depending on how the project is built.

The same reasoning applies more weakly to the other rows — long long is the right spelling and int64_t is a clearer one — but the first row is the one where the table's spelling is actually a poor choice.

The corrected reading:

Read the table to learn what the SystemVerilog type is. Write the exact-width C type that says the same thing, with the signedness explicit.

And the general habit, which is Chapter 16.8 §7's and Chapter 20.1 §7's: put the SystemVerilog type in a comment beside the C, so the next reader can check one line against the declaration without consulting a table.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
/* DPI import: function int c_decode(input byte unsigned tag, input int len); */
int32_t c_decode(uint8_t tag, int32_t len);

8. What You Should Be Able to Say Now

  1. Four rows: byte→char, shortint→short, int→int, longint→long long int. Measured 1, 2, 4 and 8 bytes here, all matching.
  2. All four are scalar types, so none needs svdpi.h (20.2 §4).
  3. The first row hides the real assumption: plain char's signedness is implementation-defined. Measured signed here, and 14.1 §2 measured -funsigned-char reversing it on the same machine.
  4. So 0x80 is -128 or 128 depending on the build — measured both ways.
  5. Rows two and three assume minimums are exact. C guarantees short ≥ 16 and int ≥ 16; SystemVerilog's int is exactly 32. They coincide here.
  6. Row four is not long. Measured: long and long long are both 8 bytes here with identical maxima — and long is 4 bytes on 32-bit targets and 64-bit Windows.
  7. The mapping names traditional types because they always exist; the exact-width types are optional in principle (14.2 §2) and present on every simulator host.
  8. Write int8_t/int16_t/int32_t/int64_t — width stated, signedness stated, and it reads as a transcription.
  9. This chapter held signedness fixed, and that axis is separate, Critical, and Chapter 20.4's.
  10. Put the SystemVerilog declaration in a comment beside the C, so one line can be checked against one line.

9. Exercises

  1. Print sizeof and the range for char, signed char, unsigned char, short, int, long and long long. Which pairs are indistinguishable on your machine?
  2. Compile a function taking a plain char and print what it receives for the byte 0x80. Then rebuild with -funsigned-char and repeat.
  3. Write the C side of import "DPI-C" function void c_f(input byte a, input byte unsigned b, input longint c); using the exact-width types, with the declaration in a comment.
  4. §4 says long would be a portability bug. Name two targets where it breaks and say what a longint argument would arrive as.
  5. The callout says int8_t is optional. Find the condition in the C standard under which an implementation may omit it, and say whether a simulator host could meet it.
  6. Take a DPI import you have seen with a byte argument. Was the C side char, signed char, unsigned char, or an exact-width type? What range of values has it been tested with?
  7. §6 says signedness is a separate axis. Construct a function that is correct for all values of a byte and wrong for half the values of a byte unsigned, with the same C declaration.

10. Summary

  • Four rows: byte→char, shortint→short, int→int, longint→long long. Measured 1 / 2 / 4 / 8 here.
  • All scalar — no svdpi.h needed (20.2).
  • The first row is the dangerous one: plain char's signedness is implementation-defined — measured signed, reversed by -funsigned-char (14.1 §2). 0x80 is -128 or 128 by build flag.
  • Rows two and three assume minimums are exact — C fixes ranges, not widths (14.1 §4).
  • Row four is not long — measured identical here, and 4 bytes on 32-bit targets and 64-bit Windows.
  • The mapping names traditional types because they always exist; exact-width types are optional in principle, present in practice.
  • Write int8_t … int64_t — width and signedness stated, reads as a transcription.
  • Signedness is a separate, Critical axis — Chapter 20.4.
  • Put the declaration in a comment beside the C.

Width has been settled four times and each time the chapter said "assuming the signedness is right". It is the other half of every one of those rows, it is the half with no size to check it against, and the mistake it produces passes every test that uses ordinary values.

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.