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.

WordBits of the vectorValue for 64'h11223344_aabbccdd
src[0]31:0aabbccdd
src[1]63:3211223344

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.

Two words in, two words outArrows carry data. The vector is split into words on the way in, C writes the words of the output in the other order, and SystemVerilog reads the output as one value again.
Two words in, two words outArrows carry data. The vector is split into words on the way in, C writes the words of the output in the other order, and SystemVerilog reads the output as one value again. SystemVerilog bit [63:0] src 64'h11223344_aabbccdd word src[0] bits 31:0 = aabbccdd word src[1] bits 63:32 = 11223344 C swap_words dst[0] = src[1]; dst[1]= src[0]; word dst[0] bits 31:0 = 11223344 word dst[1] bits 63:32 = aabbccdd low halfhigh halfsrc[0]src[1]high goes lowlow goes high
  1. 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
    1. data aabbccdd: bit [63:0] src → src[0]
    2. data 11223344: bit [63:0] src → src[1]
  2. Read both words first

    C copies src[0] and src[1] into locals before writing anything, so an output that overlaps the input would still be safe.

    Follow the calls, values and waits
    1. data aabbccdd: src[0] → swap_words
    2. data 11223344: src[1] → swap_words
  3. Write in the other order

    The old high word goes into dst[0], the old low word into dst[1]. Each word keeps its own bytes; only their significance changes.

    Follow the calls, values and waits
    1. data 11223344: swap_words → dst[0]
    2. data aabbccdd: swap_words → dst[1]
  4. Read the result

    SystemVerilog sees dst[1] above dst[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 bitaval bitSystemVerilog value
000
011
11x
10z

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.

RUNVerilator + C/C++ over DPI · simulator service
Edit the code, then run it
Expected: swapped=aabbccdd11223344
Read 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

Predict the outputword_of(v, i) returns word i of the 64-bit vector as C sees it. What line does this program print?
`timescale 1ns/1ps
module tb;
  import "DPI-C" function int word_of(input bit [63:0] v, input int i);
  bit [63:0] v = 64'h01020304_55667788;
  initial begin
    $display("%08h %08h", word_of(v, 0), word_of(v, 1));
    $finish;
  end
endmodule
Read included file: bridge.cpp
#include "Vtb__Dpi.h"
#include "svdpi.h"
#include <cstdint>
#include <climits>
#include <cstring>
extern "C" int word_of(const svBitVecVal* v, int i) { return v[i]; }
Spot the bugThe bridge offers word_of(v, i), which returns one 32-bit word of a 64-bit vector. Each option is a SystemVerilog function rebuild(v) that is meant to return v unchanged. Which one returns the wrong value?
Read included file: bridge.cpp
#include "Vtb__Dpi.h"
#include "svdpi.h"
#include <cstdint>
#include <climits>
#include <cstring>
extern "C" int word_of(const svBitVecVal* v, int i) { return v[i]; }
ConceptA bit [63:0] reaches C as const svBitVecVal* v. Which word holds bits 31:0?
ConceptIn an svLogicVecVal word, one bit has bval 1 and aval 0. What SystemVerilog value is that?
Conceptknown_low returned 1 for 32'h1234 and also for 32'hx on this Verilator run. Does the run show that x crosses correctly?

Remember

  • A packed vector crosses as svBitVecVal words: 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 logic vector crosses as svLogicVecVal pairs: bval 1 marks x or z, and aval then says which. A checker that rejects unknowns has to read bval.
  • Verilator is two-state: an x becomes 0 before the call, so a run here checks only known values.
  • Choose bit for checked binary payloads and logic or a validity mask where an unknown has to survive the crossing.