C Part II · Module 20 · DPI-C Type Mapping
Signedness Across the Boundary
A width mismatch is caught by sizeof, by a warning, or by a value that is obviously nonsense. A signedness mismatch produces a value of exactly the right size that is correct for every input below the halfway point and wrong for every input above it — and the inputs people test with are almost all below. This chapter measures that divergence at 8 and 16 bits, measures what happens when the wrong reading is then widened, and makes the point that gives the bug its staying power: both sides are following good local practice, and the two practices disagree about what a byte is.
Advanced21 min readCPart IIVerificationDPI-C
Part II · Module 20 · Chapter 20.4 · Signedness Across the Boundary
1. The Other Half of Every Row
Chapter 20.3 settled four widths and said each time that signedness was being held fixed. This is that axis, and it is rated Critical where the width chapter was High for one reason: a width mismatch has something to check it against and a signedness mismatch does not.
If the C side declares 16 bits where SystemVerilog declared 32, something notices. sizeof disagrees, a truncation warning fires, or the value is obviously wrong — Chapter 15.5 §5 measured 0x12345678 becoming 0x5678, which nobody mistakes for correct data.
If the C side declares signed where SystemVerilog declared unsigned, the object is the same size, the bits are identical, and the value is correct for half the range.
2. The Divergence, Measured
The same byte, read both ways:
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 <- differAnd the same shape at 16 bits, to show it is structural rather than a property of bytes:
pattern as int16_t as uint16_t agree?
0x0000 0 0 yes
0x0001 1 1 yes
0x7FFF 32767 32767 yes
0x8000 -32768 32768 NO
0x8001 -32767 32769 NO
0xFFFF -1 65535 NOThe divergence begins exactly at the top bit and covers exactly half the range. Below it the two readings are identical; at and above it they disagree about everything.
That is the signature the chapter's purpose names, and it explains the bug's durability. A model tested with small counts, short lengths, low addresses and single-digit identifiers passes every case. The first value with its top bit set — a length over 127, a negative offset, an address in the upper half, 0xFF as a sentinel — is wrong.
3. What Happens Next
The wrong reading rarely stays in its own byte. Chapter 15.6 established that conversion to a wider type preserves the value, not the bit pattern, and that is where a one-byte disagreement becomes a four-byte one.
Measured:
signed 0x80 -> int32_t = -128 (0xFFFFFF80)
unsigned 0x80 -> uint32_t = 128 (0x00000080)Twenty-four bits of difference, from one bit of disagreement about what the byte meant.
From there it spreads into every operation:
Comparison. Chapter 13.5 is the whole subject — a signed value compared against an unsigned one is converted, and -1 becomes a very large number. A length check that reads if (len <= MAX) passes for a len of -1 read as unsigned.
Division and shifting. Chapter 15.6 §6 measured -7 >> 2 as -2 and -7 / 4 as -1, while the unsigned readings of the same bits give neither.
Array indexing. A negative index from a sign-extended byte is Chapter 13.6's out-of-bounds access, with no fault and no diagnostic.
So the measured table in §2 is not the end of it. It is the point at which two programs stop agreeing about what a value is, and everything downstream inherits the disagreement.
4. Why It Is Worse Here Than Inside One Language
Signed/unsigned confusion exists in ordinary C — Chapter 13.5 is about it — and three things make it harder at a language boundary.
Your compiler cannot see the other declaration. Chapter 20.2 §6 established this: the contract is in the SystemVerilog file, and your C compiler is not compiling that file. -Wsign-conversion checks your C against itself and will say nothing about a mismatch with a declaration it has never read.
There is no shared header to put the truth in. Within a C program, a prototype in a header forces both sides to agree — Chapter 11.3's mechanism. Across this boundary there is no such file.
And the two sides' defaults actively disagree. This is the one worth having.
SystemVerilog's byte, shortint, int and longint are signed by default; byte unsigned is the one that is written out.
A C programmer writing the model reaches for uint8_t when the thing is "a byte", and Chapter 14.2 §4 told them to: declare signed if and only if the specification says the field can be negative. For a tag, a count, an opcode or a flag byte, unsigned is the right instinct and the advice is good.
So both sides follow sound local practice and arrive at opposite answers. Nobody was careless, and the mismatch is the default outcome rather than an unusual one.
5. Keeping Them Consistent
Three habits, in order of how much they catch.
Write the SystemVerilog declaration in a comment beside the C. Chapter 20.3 §7's habit, and here it is the main defence, because it turns a cross-file agreement into a one-line check:
/* DPI: import "DPI-C" function int c_decode(input byte unsigned tag,
input shortint offset); */
int32_t c_decode(uint8_t tag, int16_t offset);A reviewer compares byte unsigned with uint8_t and shortint with int16_t without leaving the file. Nothing else available gives that.
Use the exact-width types, with signedness explicit. uint8_t says unsigned in its name; char says nothing and -funsigned-char can change it.
Test at the top of each range. The measured table in §2 is a test plan. For every integer argument, include the value with only the top bit set and the all-ones value — 0x80 and 0xFF for a byte, 0x8000 and 0xFFFF for a short. Those are the values that distinguish the two readings, and Chapter 19.4 §4's standalone vector suite is where they belong, because they can be run with no simulator.
A fourth, where your tools support it: -Wsign-conversion on the C side catches mismatches within your C — Chapter 14.2 §3 measured its exact boundary — which does not catch the cross-boundary case but does catch a model that converts inconsistently once the value is inside.
6. When a Mismatch Is Deliberate
There is a legitimate case, and recognising it stops the rule becoming superstition.
A SystemVerilog byte is signed, and a model that treats it as a bit pattern rather than a number does not care. A tag, an opcode, a flag byte, a raw data octet — none of these is a quantity, and Chapter 14.1 §5 made the point that the datasheet's question is "is bit 7 set?" rather than "is this greater than 127?".
For those, uint8_t on the C side is right even against a signed byte, because the C is doing masking and shifting rather than arithmetic — and Chapter 15.5 §8's habit of doing bit work in unsigned types applies.
What makes it legitimate rather than sloppy is saying so:
/* DPI: input byte tag -- SV byte is signed, but this is a bit pattern,
not a number, so the C side treats it as unsigned. No arithmetic is
performed on it; see decode_tag() below. */
int32_t c_decode(uint8_t tag);The distinction to apply: if the model does arithmetic on the value, the signedness must match. If it does only masking, shifting and comparison against bit patterns, unsigned is correct and the comment explains why.
7. The Misconception: The Bits Are the Same, So It Does Not Matter
"Signed or unsigned, it is the same eight bits. Nothing is lost."
The first sentence is true and the conclusion does not follow.
Measured: 0x80 is 0x80 on both sides. The bits cross intact; nothing in the transfer corrupts anything.
What differs is every operation performed on them afterwards:
- widening — measured,
0xFFFFFF80against0x00000080 - comparison — Chapter 13.5's entire subject
- division and shifting — Chapter 15.6 §6 measured
-7 >> 2= −2 against-7 / 4= −1 - indexing, where a negative value is Chapter 13.6's out-of-bounds access
A type is not a description of bits. It is a description of what the bits mean and what operations do to them, which is Chapter 14.1 §6's correction of "C maps onto the hardware" arriving at a different boundary.
The accurate statement:
The bits cross unchanged; the interpretation does not cross at all. Each side applies its own declaration, and if those disagree the two programs are computing different things from identical data.
And the practical form, which is this chapter in one line: the value that proves they agree is the one with the top bit set.
8. What You Should Be Able to Say Now
- Signedness has no size to check it against. A width mismatch is caught by
sizeof, a warning, or an obviously wrong value; a signedness mismatch produces the right size and half the right values. - Measured, 8-bit:
0x00,0x01,0x7Fagree;0x80gives-128or128;0xFFgives-1or255. - Measured, 16-bit: the same shape, diverging at
0x8000. The split is exactly at the top bit, covering exactly half the range. - Widening spreads it. Measured:
0xFFFFFF80against0x00000080— twenty-four bits of difference from one bit of disagreement. - Then comparison (13.5), shifting and division (15.6 §6), and indexing (13.6) all inherit it.
- Three reasons it is worse at a boundary: your compiler cannot see the other declaration (20.2 §6), there is no shared header, and the two sides' defaults disagree — SystemVerilog's integer types are signed, and C programmers correctly reach for
uint8_tfor a byte (14.2 §4). - A plain
charmakes it a build input. Measured:-128by default,128with-funsigned-char, same source. - Three habits: the declaration in a comment beside the C · exact-width types with explicit signedness · test at
0x80and0xFF. - A deliberate mismatch is legitimate for bit patterns — tags, opcodes, flag bytes — where the model does masking rather than arithmetic. Say so in the comment.
- The bits cross; the interpretation does not. The value that proves agreement is the one with the top bit set.
9. Exercises
- Reproduce §2's 8-bit and 16-bit tables. Then produce the 32-bit one and name the first value at which the readings diverge.
- Write a C model taking a
bytethat computesvalue * 2and one that computesvalue & 0xF0. For each, say whether a signedness mismatch would matter, and why. - Reproduce the
-funsigned-charmeasurement. Then rewrite the function withint8_tand confirm the flag no longer changes anything. - For a DPI import you have seen or written, write the comment from §5 pairing each SystemVerilog type with its C type. Did any pair surprise you?
- §4 says the two sides' defaults disagree. Write the SystemVerilog declaration and the C declaration a careful person on each side would produce for "a one-byte message tag", and show that they conflict.
- Add
0x80and0xFFto Chapter 19.4's standalone vector suite for a byte-taking function. What does it now catch that it did not? - §6 permits a deliberate mismatch. Write the comment for one, and state the condition under which it would stop being legitimate.
10. Summary
- Signedness has nothing to check it against — right size, half the right values.
- Measured:
0x00/0x01/0x7Fagree;0x80→-128or128,0xFF→-1or255. Same shape at 16 bits, diverging at0x8000. - The split is exactly at the top bit — exactly half the range, and the half nobody tests.
- Widening spreads it:
0xFFFFFF80against0x00000080. - Then comparison, shifting, division and indexing inherit it (13.5, 15.6, 13.6).
- Worse at a boundary: the compiler cannot see the other declaration · no shared header · the defaults actively disagree.
- A plain
charmakes signedness a build input — measured,-128or128by flag. - Habits: declaration in a comment · exact-width types · test
0x80and0xFF. - Deliberate mismatch is fine for bit patterns, with a comment and no arithmetic.
- The bits cross; the interpretation does not.
Every disagreement so far has been about how to read bits that both sides possess. The next one is different: a SystemVerilog value can be in a state that C has no bits for at all, and no choice of signedness or width will recover it.
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.
