Packed vectors and four-state bits
Move a 64-bit packed value into C without mixing up its words, and read the two-bit encoding that carries x and z across.
- Lesson
- D1.3
- Time
- 20 min
- Runs on
- Verilator with C/C++
- Check
- 5 questions
A memory model in C hands back the 64-bit base address of each buffer, and the first transfer goes to aabbccdd_11223344 instead of 11223344_aabbccdd. The C code built the value from two 32-bit words and put the high half in word 0, which is where the low half belongs. The simulator passed exactly what it was given; the two sides disagreed on which word comes first. This lesson shows how a packed vector is laid out at the hand-off, and how a four-state value carries its x and z bits.
| Word | Bits of the vector | Value for 64'h11223344_aabbccdd |
|---|---|---|
src[0] | 31:0 | aabbccdd |
src[1] | 63:32 | 11223344 |
A bit [63:0] as C sees it: an array of two 32-bit words, least significant word first.
A vector crosses as 32-bit words
A packed vector wider than one bit reaches C as a pointer to svBitVecVal, an unsigned 32-bit word. Word 0 holds bits 31:0, word 1 holds bits 63:32, and so on upward. This is the order of significance inside one value. It says nothing about the byte order of a packet on a wire, which a protocol defines separately.
- Split into words
The simulator presents the 64-bit value as two words. Word 0 is the least significant half.
Follow the calls, values and waits
- data aabbccdd: bit [63:0] src → src[0]
- data 11223344: bit [63:0] src → src[1]
- Read both words first
C copies
src[0]andsrc[1]into locals before writing anything, so an output that overlaps the input would still be safe.Follow the calls, values and waits
- data aabbccdd: src[0] → swap_words
- data 11223344: src[1] → swap_words
- Write in the other order
The old high word goes into
dst[0], the old low word intodst[1]. Each word keeps its own bytes; only their significance changes.Follow the calls, values and waits
- data 11223344: swap_words → dst[0]
- data aabbccdd: swap_words → dst[1]
- Read the result
SystemVerilog sees
dst[1]abovedst[0]:aabbccdd_11223344. A model that wrote the high half into word 0 would produce this by accident, which is the hook.
Read the diagram as text
- bit [63:0] src: 64'h11223344_aabbccdd
- src[0]: bits 31:0 = aabbccdd
- src[1]: bits 63:32 = 11223344
- swap_words: dst[0] = src[1]; dst[1] = src[0];
- dst[0]: bits 31:0 = 11223344
- dst[1]: bits 63:32 = aabbccdd
- bit [63:0] src → src[0]: low half
- bit [63:0] src → src[1]: high half
- src[0] → swap_words: src[0]
- src[1] → swap_words: src[1]
- swap_words → dst[0]: high goes low
- swap_words → dst[1]: low goes high
Word 0 is always the low word. A C routine that assembles a value from parts has to start from that fact.
A width that is not a multiple of 32 leaves the last word partly used. A bit [39:0] is two words, and only the low 8 bits of word 1 belong to the value. When your C code builds such an output, write zeros into the unused bits. The simulator may rely on them being clean.
Four-state values carry two bits each
A logic vector cannot fit in one bit per position, because each position may also be x or z. It crosses as svLogicVecVal words, each a pair of 32-bit fields named aval and bval. For a given bit, bval is 0 when the value is a known 0 or 1, and aval then holds that value. When bval is 1 the bit is unknown, and aval tells x from z.
bval bit | aval bit | SystemVerilog value |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 1 | x |
| 1 | 0 | z |
The four-state encoding, bit by bit. A word with bval == 0 is a plain binary value.
known_low asks whether the single word of a logic [31:0] has any bval bit set, and returns 1 when every bit is known. A checker that promises to reject unknown input has to read bval. Dropping it turns an x on the bus into a plausible 0 or 1, which is the kind of silent miss a reference model exists to catch.
Choose bit or logic on purpose
Pick the SystemVerilog type by what the C side has to know. For a binary payload that a monitor has already checked, bit and byte are enough, and C gets plain integers. For a protocol where an unknown matters, keep logic so bval crosses, or send a separate validity mask next to a two-state value. The wrong choice does not fail; it quietly makes a failed bus sample look like zero data.
The same care applies to the direction of the copy. An input vector is read-only on the C side; an output vector is a buffer C fills, word by word, and every word the vector covers has to be written.
Try it
Run it, then replace the input with 64'h01020304_55667788 and predict the swapped value before you look: the words trade places, the bytes inside each word stay as they are.
Then change swap_words so it also prints src[0] in C with printf("%08x\n", src[0]), and see aabbccdd come out: word 0 is the low half.
swapped=aabbccdd11223344Read included file: bridge.cpp
#include "Vtb__Dpi.h"
#include "svdpi.h"
#include <cstdint>
#include <climits>
#include <cstring>
// A bit [63:0] crosses as two svBitVecVal words: word 0 holds bits 31:0, word 1 holds bits 63:32.
extern "C" void swap_words(const svBitVecVal* src, svBitVecVal* dst) {
const svBitVecVal low = src[0], high = src[1];
dst[0] = high;
dst[1] = low;
}
// A logic [31:0] crosses as svLogicVecVal words; bval marks the bits that are x or z.
extern "C" int known_low(const svLogicVecVal* src) {
return src[0].bval == 0;
}
Check yourself
Remember
- A packed vector crosses as
svBitVecValwords: word 0 is bits 31:0, word 1 is bits 63:32. That is significance order, unrelated to a packet's byte order. - When C builds an output vector, write every word, and zero the unused bits of a partly used last word.
- A
logicvector crosses assvLogicVecValpairs:bval1 marks x or z, andavalthen says which. A checker that rejects unknowns has to readbval. - Verilator is two-state: an x becomes 0 before the call, so a run here checks only known values.
- Choose
bitfor checked binary payloads andlogicor a validity mask where an unknown has to survive the crossing.