Skip to content
VLSI Mentor

C Part II · Module 21 · Arrays and Open Arrays in DPI-C

Packed and Unpacked Arrays and What Each Becomes in C

The module's first question, and the one everything downstream depends on: is the array one value made of bits, or a collection of separate objects? It is answered by reading a SystemVerilog declaration, not by inspecting bytes — and the measurements show why guessing is so durable. For 8-bit elements the two models produce byte-identical memory, so a C function written for the wrong kind passes every test it is given. Widen the element's storage and the same reader takes element 1 from what is now padding and returns 0x0000 instead of 0x2222, with no diagnostic from anything.

Expert24 min readCPart IIVerificationDPI-C

Part II · Module 21 · Chapter 21.2 · Packed and Unpacked Arrays and What Each Becomes in C

1. Ask the Declaration, Not the Bytes

Chapter 21.1 established the asymmetry and left the fix as "carry the size deliberately". Before you can carry a size you have to know what it is a size of, and that is this chapter's question:

Is the array packed or unpacked?

The purpose of this chapter is explicit about where to look: "distinguish the two array kinds on the SystemVerilog side first". It is a property of the declaration, not something to be deduced from what the bytes look like in C — and §5 measures exactly why deducing it from bytes is a trap.

From the language definition, the two kinds are different in kind and not in degree:

  • Packed — a contiguous vector of bits with named slices. It is one object. Its "elements" are bit ranges of that object.
  • Unpacked — a collection of separate element objects, each with its own storage.

2. A Packed Array Is One Value

Measured, four 8-bit elements packed into a 32-bit quantity:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  storage: 1 chunk(s) = 4 bytes, contiguous
  the whole thing as one value: 0x44332211
  element 2 is a BIT SLICE: (v >> 16) & 0xFF = 0x33
  there is no address for element 2 -- it is bits 23:16 of one object

Three things follow, and all three matter downstream.

It crosses as a packed vector, which means Chapter 20.6's canonical representation — 32-bit chunks, svBitVecVal or svLogicVecVal — not as an array of anything. The layout is specified, which is good news this module does not get often.

Its elements are extracted with shifts and masks, Chapter 15.2's tools:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
uint8_t element(uint32_t v, unsigned i) { return (uint8_t)((v >> (8u * i)) & 0xFFu); }

Its elements have no addresses. You cannot take &a[2], pass one by pointer, or iterate with a pointer, because element 2 is not an object — it is a range of bits inside one.

3. An Unpacked Array Is Several Objects

Measured, the same four values as separate uint8_t objects:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  storage: 4 bytes, 1 per element
  element 2 is an OBJECT: up[2] = 0x33, and &up[2] exists
  offset of element 2 from the base: 2 bytes

And with a wider element type:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  storage: 16 bytes, 4 per element, stride 4
  4 elements of 8 bits packed = 4 bytes; 4 elements of 32 bits = 16 bytes
  same element COUNT, 4x the storage

The element count and the storage size are different numbers, and which one your interface carries is a decision — Chapter 21.7 returns to it for payloads.

An unpacked array crosses as an element array: a fixed-size one as a pointer to the first element (Chapter 21.3), one whose size is not known when the C is written as an open-array handle (Chapters 21.4 and 21.5).

So the two kinds answer different questions:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  packed   : how many BITS?  32.  address of an element? no
  unpacked : how many ELEMENTS? 4.  address of an element? yes

4. Why a Packed Array Sometimes Cannot Be an Element Array at All

Not a style preference — arithmetic. Measured, eight elements of four bits packed into one 32-bit value:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  8 elements of 4 bits packed into one 32-bit value: 0x87654321
  element 5 = (v >> 20) & 0xF = 6
  storage per element: 0.5 bytes -- no C array can have that stride
  so this kind has NO element-array representation at all

A C array's elements are objects, and from the C standard every object occupies a whole number of bytes. There is no C array type with a half-byte stride, so a packed array of 4-bit elements cannot be represented as an array of anything in C. It can only be what it is: one value, with the elements taken out by shifting.

This is the cleanest argument that the two kinds are not interchangeable, and it is also why Chapter 20.6's chunk-and-mask approach is the right tool for packed data rather than a workaround.

5. The Finding: Why the Wrong Answer Survives Testing

Here is why this chapter is rated Critical rather than merely important.

Measured, the same four values in both models with 8-bit elements:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  packed as one 32-bit value  11 22 33 44
  unpacked as 4 byte objects  11 22 33 44
  identical bytes: YES   <- a wrong model passes every test

Byte-identical. A C function written for the wrong kind reads the right values, returns the right answers, and passes every test it is given. The mistake is not merely undetected — it is actively confirmed by the evidence available.

It is not a fluke either. The two models coincide exactly when the element's value width equals its storage width and the host is little-endian, and measured, 16-bit values in 16-bit storage coincide too:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  packed                      11 11 22 22 33 33 44 44
  unpacked                    11 11 22 22 33 33 44 44
  identical bytes here too: YES

Now widen the unpacked element's storage without changing the values — a shortint array held in 32-bit C elements, which is an ordinary thing to write:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  unpacked as 4 x uint32_t: storage 16 bytes (vs 8 packed)
  unpacked, 32-bit elements   11 11 00 00 22 22 00 00 33 33 00 00 44 44 00 00
  a reader expecting the packed form takes element 1 from bytes 2-3
  and finds 0x0000 instead of 0x2222

Element 0 is still right, because both layouts start at byte 0. Element 1 comes out 0x0000 — and so does everything after it, shifted.

The same four values in the two array kinds, drawn to byte scale. Packed, the four 16-bit elements are one contiguous 64-bit value occupying 8 bytes, with element 0 in bytes 0 and 1, element 1 in bytes 2 and 3, and so on. Unpacked with a 32-bit storage element, the same four values occupy 16 bytes: each value sits in the low two bytes of its element and the upper two bytes are padding. A C reader written for the packed form therefore gets element 0 right, because both forms start at byte 0, and then takes element 1 from bytes 2 and 3, which in the unpacked layout are padding: measured, it reads 0x0000 instead of 0x2222, and every element after that lands in the wrong place. Nothing reports any of this, because every byte read belongs to the buffer.Packedone value made of bits — 4 elements of 16 bits = 64 bits = 8 bytes, contiguous00x111110x222220x333330x4444not usedthe packed form ends at byte 8Unpackedfour separate objects — measured: 4 elements of 32-bit storage = 16 bytes00x1111pad0x000010x2222pad0x000020x3333pad0x000030x4444pad0x0000Reading the unpacked bytes with the packed modelmeasured: element 1 comes out 0x0000 instead of 0x22220correct10x0000and every element after it lands in the wrong placenothing reports a thing12
Figure 1 — the same four values in both kinds, drawn to byte scale, and what a reader with the wrong model gets. The top strip is the packed form: four 16-bit elements as one contiguous 64-bit value in 8 bytes. The middle strip is the unpacked form with 32-bit storage elements, where each value occupies the low half of its element and the upper half is padding, so the same four values now span 16 bytes. The bottom strip is the measurement: a reader written for the packed layout gets element 0 right, because both forms begin at byte 0, and then takes element 1 from bytes 2 and 3 — which are padding — and returns 0x0000 instead of 0x2222. Every byte it reads belongs to the buffer, so no sanitizer and no compiler has anything to report.

Note what is not happening in that bottom row. Every byte read belongs to the buffer, so there is no overflow, no undefined behaviour, and nothing for a sanitizer to report — Chapter 21.1 §6's boundary again. The bug is entirely inside the allocation.

6. Answering the Question

Three reliable ways, in order of preference.

Read the declaration. The dimensions written before the array name are packed; those written after it are unpacked. That is the whole rule, and it is why the purpose says to look on the SystemVerilog side first.

Ask what an element is. If taking the address of one element is meaningful, it is unpacked. If an element is a bit range of a larger value, it is packed. §2 and §3 measured the difference: &up[2] exists at offset 2; element 2 of the packed value is bits 23:16 and has no address.

Ask what the size is a size of. A packed array has a width in bits; an unpacked array has a count of elements, and its storage depends on the element type — measured, 4 bytes against 16 for the same four values.

What does not work is inspecting the bytes, for the reason §5 measured: for the common case they are identical.

7. The Misconception: It Is a Style or Performance Choice

"Packed and unpacked are two ways of storing the same data. One is tighter, one is more flexible, and either way the values are the same — so the C side can treat them the same."

Measured false three times over.

The storage differs. Four values: 4 bytes packed, 16 bytes unpacked with 32-bit elements. Any C code that sizes a buffer from the wrong kind is wrong by a factor, and Chapter 20.6 §5 measured the matching failure for four-state data.

The addressability differs. &up[2] exists at offset 2; the packed element 2 is bits 23:16 of one object and has no address. That is not a performance difference — it decides whether a pointer loop is even expressible.

Some packed arrays have no element-array form at all. §4: 4-bit elements, half a byte of stride, no C array can do it.

And the reason the belief is so durable is §5: for 8-bit elements the two are byte-identical, so the most common case produces the most confirming evidence.

The corrected position:

They are different types with different representations, different storage and different capabilities, and the C side cannot tell them apart by looking. Which one you have is read off the SystemVerilog declaration, and it decides the C representation, the argument form, and every rule after it.

8. What You Should Be Able to Say Now

  1. The question is answered in the SystemVerilog declaration — dimensions before the name are packed, after it unpacked — not by inspecting bytes.
  2. Packed is one object: a contiguous bit vector whose elements are bit ranges. Measured: 4 bytes, whole value 0x44332211, element 2 = (v >> 16) & 0xFF = 0x33, no address.
  3. Unpacked is several objects: measured, &up[2] exists at offset 2, and the same four values take 16 bytes with 32-bit elements against 4 packed.
  4. Packed crosses as Chapter 20.6's canonical packed representation; unpacked as an element array (21.3) or an open-array handle (21.4).
  5. Packed asks "how many bits"; unpacked asks "how many elements" — and an unpacked array's storage depends on the element type as well as the count.
  6. Some packed arrays cannot be element arrays at all — measured, 8 elements of 4 bits, a half-byte stride that no C array type has.
  7. The finding: for 8-bit elements the two models are byte-identical (11 22 33 44), so a C function written for the wrong kind passes every test.
  8. Widen the storage and it breaks silently — measured, the packed reader takes element 1 from padding and returns 0x0000 instead of 0x2222.
  9. Nothing reports it: every byte read belongs to the buffer, so there is no memory error to find.
  10. Write the kind in a comment beside the C function, and say explicitly what it is not — nothing else records it.

9. Exercises

  1. Pack four 8-bit values into a 32-bit quantity and extract element 2 with a shift and a mask. Then build the same four values as a uint8_t array and take &up[2]. Which operation is impossible in the other model?
  2. Print the bytes of both forms. Are they identical? What does that mean for a test that only checks values?
  3. Build the four values as uint32_t elements and dump the 16 bytes. Read element 1 at the packed offset. What do you get, and why is element 0 still right?
  4. Pack eight 4-bit values into 32 bits and read element 5. Write the C array declaration that would hold the same eight elements — what stride would it need?
  5. For an unpacked array of 100 shortint elements, write down the element count and the storage size in bytes. Which number does your interface carry, and which does the other side need?
  6. Take a DPI import you have seen. From the SystemVerilog declaration alone, say whether each array argument is packed or unpacked, and what that makes the C parameter.
  7. Write the comment from §6's callout for one of your own DPI functions, including the line that rules out the wrong reading.

10. Summary

  • Read the declaration: dimensions before the name are packed, after it unpacked.
  • Packed = one object — measured 4 bytes, 0x44332211, element 2 is (v>>16)&0xFF, no address.
  • Unpacked = several objects — measured &up[2] at offset 2; 4 vs 16 bytes for the same four values.
  • Packed → Chapter 20.6's specified chunk representation; unpacked → an element array or a handle.
  • Bits versus elements: the two kinds answer different size questions.
  • A 4-bit-element packed array has no C array form — measured, half a byte of stride.
  • The finding: 8-bit elements make the two byte-identical, so the wrong model passes every test.
  • Widen the storage and element 1 reads 0x0000 instead of 0x2222 — measured, with nothing to report.
  • Only a comment records the kind. Write it, and say what the data is not.

The kind is settled. For an unpacked array the next question is the one Chapter 21.1 left open — how the size travels — and the simplest case is the one where both sides already agree on a number and the only question is whether anything is holding them to 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.