Skip to content
VLSI Mentor

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

Packed Data: svBitVecVal and svLogicVecVal

Past 64 bits there is no C type left to be, so a wide vector crosses as an array of 32-bit chunks whose layout the standard specifies exactly. Chapter 20.2 measured what happens when that layout is guessed instead: the guess compiles perfectly. This chapter gives the real thing — three rules, the chunk arithmetic people get wrong, and both canonical layouts implemented in C and round-tripped bit by bit, including the four-state case where each chunk is a pair of words rather than one.

Expert22 min readCPart IIVerificationDPI-C

Part II · Module 20 · Chapter 20.6 · Packed Data: svBitVecVal and svLogicVecVal

1. Past the Scalar Types

Chapter 20.3 mapped four integer types at four widths, and every one of them fit a C type exactly. SystemVerilog is not restricted to those widths: logic [71:0] is an ordinary declaration, and so is bit [11:0].

Anything wider than 64 bits has no C type to be, so from the LRM it crosses as an array of 32-bit chunks, with two type names from svdpi.h:

  • svBitVecVal for a 2-state vector — one word per chunk
  • svLogicVecVal for a 4-state vector — a pair of words per chunk, which is Chapter 20.5's aval/bval encoding

Chapter 20.2 §5 measured what happens when the layout is guessed rather than read:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
typedef uint32_t svBitVecVal;    /* "it is a 32-bit chunk, surely" */
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  hand-written typedef: compiles CLEAN under the full flag set

A guess that compiles is the worst available outcome, so this chapter is the layout itself.

2. A Note on How This Is Presented

svdpi.h does not exist on this machine — Chapter 19.1 §4 verified it and Chapter 20.2 §1 restated it. So the layout below is from the LRM, and rather than asserting it, it is implemented in C and round-tripped, which is the strongest check available without the header: if the rules are stated correctly, a value written through them and read back through them survives bit for bit.

Both layouts are exercised in §3 and §5 and both round-trip exactly.

3. The 2-State Layout

From the LRM, three rules:

One — the chunk is 32 bits. Always, regardless of the host. Not int, not a word, not size_t — 32 bits.

Two — chunk 0 holds bits 31:0, chunk 1 holds bits 63:32, and so on upward.

Three — within a chunk, bit n of the vector is bit n mod 32 of that chunk.

Implemented:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
typedef uint32_t svBitVecVal;
#define CHUNKS(width) (((width) + 31) / 32)

static void put_bits(svBitVecVal *v, int width, const uint8_t *bits) {
    memset(v, 0, (size_t)CHUNKS(width) * sizeof *v);
    for (int i = 0; i < width; i++)
        if (bits[i]) v[i/32] |= (uint32_t)1u << (i % 32);
}
static unsigned get_bit(const svBitVecVal *v, int i) {
    return (v[i/32] >> (i % 32)) & 1u;
}

Measured on a 72-bit vector with bits 0, 31, 32, 63, 64 and 71 set:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  a 72-bit vector needs 3 chunks of 32 bits = 12 bytes
  chunks: [0]=0x80000001 [1]=0x80000001 [2]=0x00000081
  round trip over all 72 bits: exact
  bit 31 is in chunk 0, bit 32 in chunk 1  <- the boundary that catches people

Chunk 0 is 0x80000001 — bits 0 and 31. Chunk 1 is the same pattern for bits 32 and 63. Chunk 2 is 0x00000081 — bits 64 and 71, in positions 0 and 7 of that chunk.

4. The Arithmetic People Get Wrong

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
#define CHUNKS(width) (((width) + 31) / 32)

That is the expression, and three of its consequences are worth stating because each produces a bug. Measured across the widths where it matters, with both buffer sizes:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  width  1 -> 1 chunk(s) 2-state =  4 bytes | 4-state =  8 bytes
  width 31 -> 1 chunk(s) 2-state =  4 bytes | 4-state =  8 bytes
  width 32 -> 1 chunk(s) 2-state =  4 bytes | 4-state =  8 bytes
  width 33 -> 2 chunk(s) 2-state =  8 bytes | 4-state = 16 bytes
  width 64 -> 2 chunk(s) 2-state =  8 bytes | 4-state = 16 bytes
  width 65 -> 3 chunk(s) 2-state = 12 bytes | 4-state = 24 bytes

A 32-bit vector is one chunk; a 33-bit vector is two. The + 31 is a round-up, and getting it wrong by writing width / 32 loses the top bits of every vector whose width is not a multiple of 32.

The last chunk is usually partly unused. A 72-bit vector occupies 3 chunks = 96 bits, of which 24 are not part of the value. From the LRM: those bits are not meaningful and must not be relied upon. A model reading chunk 2 of the measurement above as a full uint32_t gets 0x00000081 here and has no guarantee about bits 8 through 31 in general.

So a model must mask the last chunk before using it as a number — Chapter 15.2's mask, built from the width:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
/* the meaningful bits of the final chunk */
unsigned used = (unsigned)width % 32u;
uint32_t last_mask = used ? ((UINT32_C(1) << used) - 1u) : UINT32_MAX;

Chapter 15.2 §5 measured why the used ? … : UINT32_MAX guard is there: 1u << 32 is undefined behaviour, and a width that is an exact multiple of 32 produces used == 0 and hits precisely that case.

5. The 4-State Layout

From the LRM: a 4-state vector is an array of chunks where each chunk is a pair — an aval word and a bval word — using Chapter 20.5's encoding.

The detail most likely to be guessed wrong: it is an array of pairs, not two separate arrays. The words interleave.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
typedef struct { uint32_t aval, bval; } svLogicVecVal;

Measured on a 40-bit vector carrying the repeating pattern 01XZ:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  a 40-bit 4-state vector needs 2 chunks = 16 bytes (two planes)
  chunk0 aval=0x66666666 bval=0xCCCCCCCC
  chunk1 aval=0x00000066 bval=0x000000CC
  round trip over all 40 bit-states: exact
  bits 39..32 read: ZX10ZX10

Two chunks for 40 bits, and 16 bytes rather than 8 — each chunk carries two words. A model that sizes a 4-state buffer with the 2-state arithmetic allocates half what it needs, which is Chapter 13.6's out-of-bounds access with nothing to warn about it.

Reading a bit's state is Chapter 20.5's table applied per chunk:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
static char get_state(const svLogicVecVal *v, int i) {
    uint32_t m = (uint32_t)1u << (i % 32);
    int c = i / 32;
    unsigned a = (v[c].aval & m) != 0, b = (v[c].bval & m) != 0;
    return b ? (a ? 'X' : 'Z') : (a ? '1' : '0');
}

And Chapter 20.5 §5's first strategy generalises to a vector — check before computing:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
static int vec_has_unknown(const svLogicVecVal *v, int width) {
    for (int c = 0; c < CHUNKS(width); c++) if (v[c].bval) return 1;
    return 0;
}

6. Converting to Something a Model Can Use

A model rarely wants chunks. It wants a number, or bytes. Two conversions, both with a trap.

To a uint64_t, for a vector of 64 bits or fewer:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
static uint64_t vec_to_u64(const svBitVecVal *v, int width) {
    assert(width > 0 && width <= 64);
    uint64_t r = (uint64_t)v[0];
    if (width > 32) r |= (uint64_t)v[1] << 32;
    /* mask the bits above the width -- §4 */
    if (width < 64) r &= ((uint64_t)1u << width) - 1u;
    return r;
}

The mask matters for the reason §4 gave. Measured on a 40-bit vector whose chunks were handed over with every spare high bit set:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  vec_to_u64 of a 40-bit vector with spare high bits set: 0x000000FFFFFFFFFF

Forty bits, not sixty-four — the mask discarded the 24 bits that were never part of the value. Without it, the function would have returned 0x000001FFFFFFFFFF and the model would have been computing on bits the LRM says mean nothing.

The width < 64 guard is Chapter 15.2 §5's edge again — 1 << 64 on a 64-bit type is undefined — and the same shape as §4's final-chunk guard. Measured, the two cases side by side:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  width 72: used bits in final chunk = 8, mask = 0x000000FF
  width 64: used bits in final chunk = 0, mask = 0xFFFFFFFF (guard taken)

To bytes, for a wire format — and this is where Chapter 14.3's distinction earns its place:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
/* little-endian byte order, chosen explicitly */
for (int i = 0; i < width; i += 8) {
    out[i/8] = (uint8_t)((v[i/32] >> (i % 32)) & 0xFFu);
}

The chunk order is specified by the LRM and the byte order within your output is your choice — Chapter 14.3 §5 established that a byte order must be chosen deliberately and written with shifts rather than inherited from the host. The two are independent questions and conflating them is §7's misconception.

7. The Misconception: It Is Just a Byte Array

"A packed vector is a block of bits. I can memcpy it into a byte buffer."

Measured false twice, and the two failures are different.

The chunking is 32-bit and index-ordered. Chunk 0 holds the low bits — measured, bit 31 in chunk 0 and bit 32 in chunk 1. That is a property of the specified layout, not of the host's byte order, and Chapter 14.3 §7 drew exactly this line: byte order is where bytes of an object sit in memory, and this is a different question that happens to use the word "order". A memcpy of the chunk array to a byte buffer produces a byte string whose meaning depends on both, and conflating them is how a model ends up byte-swapping something that was never byte-ordered.

A 4-state vector is not contiguous value bits at all. Measured: chunk0 aval=0x66666666 bval=0xCCCCCCCC, interleaved as pairs. A memcpy of a svLogicVecVal array copies both planes, interleaved; the value bits are every other word. There is no contiguous run of them to copy.

And the size trap underneath both: measured, 40 bits of 4-state data occupy 16 bytes, not 8. A memcpy sized from the width in bits, divided by eight, is wrong by a factor of two before anything else goes wrong.

The accurate position:

The layout is specified, not arbitrary — so read it rather than guessing, and convert deliberately rather than copying. Use the header's accessors for individual bits; own the size arithmetic, the final-chunk mask, and any byte order yourself.

8. What You Should Be Able to Say Now

  1. Anything wider than the scalar types crosses as 32-bit chunks — svBitVecVal for 2-state, svLogicVecVal for 4-state.
  2. Guessing the layout compiles. Chapter 20.2 §5 measured a hand-written typedef building clean under the full flag set.
  3. Three rules: chunks are always 32 bits; chunk 0 holds bits 31:0 and chunk 1 bits 63:32; within a chunk, bit n is bit n mod 32.
  4. Measured, 2-state: a 72-bit vector is 3 chunks / 12 bytes, chunks 0x80000001, 0x80000001, 0x00000081, round trip exact over all 72 bits.
  5. CHUNKS(w) = (w + 31) / 32. A 32-bit vector is one chunk, a 33-bit vector is two.
  6. The final chunk is partly unused and its spare bits are not meaningful — mask it, with Chapter 15.2 §5's guard, because a width that is a multiple of 32 makes the naive mask 1u << 32.
  7. A 4-state vector is an array of pairs, not two arrays. Measured: 40 bits = 2 chunks = 16 bytes, round trip exact, aval=0x66666666 bval=0xCCCCCCCC.
  8. Sizing a 4-state buffer with 2-state arithmetic allocates half what is needed.
  9. Use the header's accessors for bits; own the size, the mask and the byte order.
  10. It is not a byte array: chunk order is specified layout rather than byte order (14.3 §7), and 4-state value bits are not contiguous.

9. Exercises

  1. Implement CHUNKS, put_bits and get_bit, and round-trip a 72-bit vector. Confirm the three chunk values.
  2. Compute the chunk count for widths 1, 31, 32, 33, 64 and 65. Which pairs differ by one chunk for a one-bit difference in width?
  3. Write the final-chunk mask for widths 72 and 64. Explain why the second needs the guard from Chapter 15.2 §5.
  4. Implement the 4-state layout and round-trip a 40-bit 01XZ pattern. Confirm aval=0x66666666 for chunk 0, and explain that value from the pattern.
  5. Write vec_has_unknown and apply it to a vector where only bit 39 is X. Which chunk does it find it in?
  6. Size a buffer for a 100-bit 2-state vector and for a 100-bit 4-state vector. The measured answers are 16 and 32 bytes; say what a model that used the first number for the second would do.
  7. §7 says chunk order is not byte order. Write a one-paragraph explanation for someone who has just byte-swapped a packed vector and made things worse.

10. Summary

  • Wider than scalar → 32-bit chunks: svBitVecVal (one word per chunk), svLogicVecVal (a pair per chunk).
  • Guessing compiles (20.2 §5) — which is why the layout is given here and round-tripped rather than asserted.
  • Three rules: always 32-bit chunks · chunk 0 holds bits 31:0 · bit n is bit n mod 32.
  • Measured 2-state: 72 bits → 3 chunks / 12 bytes, 0x80000001 0x80000001 0x00000081, exact round trip.
  • (w + 31) / 32 — 32 bits is one chunk, 33 is two.
  • Mask the final chunk; a width that is a multiple of 32 makes the naive mask 1u << 32 (15.2 §5).
  • Measured 4-state: 40 bits → 2 chunks → 16 bytes, aval=0x66666666 bval=0xCCCCCCCC, exact.
  • 2-state arithmetic on a 4-state buffer allocates half.
  • Accessors for bits; you own size, mask and byte order.
  • Not a byte array — chunk order ≠ byte order (14.3 §7), and 4-state value bits are interleaved.

Every type so far has been an integer of some width, and the questions have all been about how many bits and what they mean. One SystemVerilog type is not an integer at all, and it crosses into a C type whose problems are entirely different ones.

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.