Data Arrays

DataArray — fixed-capacity array

DataArray is the fixed-capacity array schema in Waveflow. It defines element type, maximum shape, runtime/static shape behavior, and C++ storage lowering.

1. Declaring DataArray

A DataArray subclass declares:

  • element_type: schema type for each element
  • max_shape: maximum dimensions
  • static: fixed-size (True) or runtime-length (False, leading dimension)
  • cpp_storage: C++ layout mode ("struct" or "raw")

Example from examples/stream_inband/poly.py:

class CoeffArray(DataArray):
    ncoeff = 4
    element_type = Float32
    static = True
    max_shape = (ncoeff,)
    cpp_storage = "raw"

2. Runtime construction with array()

For runtime values, use the array factory in waveflow/hw/arrayutils.py:

from waveflow.hw.arrayutils import array

samples = array(Float32, [1.0, -2.0, 3.5])

array(elem_type, data, static=False) specializes DataArray from the runtime shape and returns an initialized instance.

3. cpp_storage="struct" vs "raw"

DataArray supports two C++ lowering modes:

  • cpp_storage="struct" (default): generates a struct wrapper with a named member (default data) and schema methods.
  • cpp_storage="raw": lowers to a raw C++ array (T[N]) and requires static=True with 1-D shape.

Illustrative lowering shape:

// struct mode (default)
struct Float32Array {
    float data[N];
    template<int WORD_BW> void write_array(ap_uint<WORD_BW> x[]) const;
    template<int WORD_BW> void read_array(const ap_uint<WORD_BW> x[]);
};

// raw mode (used by CoeffArray)
float coeffs[N];

CoeffArray in the poly example uses cpp_storage="raw" to map coefficient storage directly to a flat C++ array.

4. Generated array utilities via ArrayUtilsStep

Array packing helpers are generated by ArrayUtilsStep in a BuildDag:

from waveflow.build.build import BuildConfig, BuildDag
from waveflow.build.streamutils import StreamUtilsStep
from waveflow.hw.arrayutils import ArrayUtilsStep

cfg = BuildConfig(root_dir=project_dir)
dag = BuildDag()
dag.add(StreamUtilsStep(output_dir="include"))
dag.add(ArrayUtilsStep(Float32, [32, 64]))
dag.run(cfg)

ArrayUtilsStep is dependency-aware and resolves StreamUtilsStep in the same DAG. It emits include/<elem>_array_utils.h and include/<elem>_array_utils_tb.h.

Pipelined stream operation note

Pipelined stream operations (get_pipelined, write_pipelined) are only valid inside @synthesizable hook bodies. They are not legal in top-level extracted bodies such as on_start, run_proc, or testbench main(). See Synthesis Extractor.


VarDataArray — variable-length array with in-band length

VarDataArray is an array schema with a fixed maximum capacity and a runtime-variable active length. The active length is serialized as an integer field at the start of the packed representation, followed by exactly that many elements.

Serialized layout

[length : nbits_len][elem0][elem1]...[elemN-1]

where N = len(obj.val) <= len_max. Everything is bit-contiguous (no padding between the length field and the first element).

Max vs active size

VarDataArray distinguishes two notions of size:

API Meaning
cls.get_bitwidth() / cls.get_bitwidth_max() Declared / max bitwidth: nbits_len + len_max * elem_bitwidth. Used for C++ struct sizing and worst-case allocation.
obj.get_bitwidth_active() Active bitwidth: nbits_len + len(val) * elem_bitwidth. Matches actual serialized bits.
cls.nwords_per_inst(word_bw) / cls.nwords_max(word_bw) Max word count (worst-case, for allocation).
obj.nwords_active(word_bw) Active word count (matches len(obj.serialize(word_bw))).

Using VarDataArray

from waveflow.hw import VarDataArray, IntField
import numpy as np

U16 = IntField.specialize(bitwidth=16, signed=False)

# Specialize: elem_type, len_max, optional nbits_len
VU16 = VarDataArray.specialize(elem_type=U16, len_max=32)

obj = VU16()
obj.val = np.array([10, 20, 30], dtype=np.uint32)
print(len(obj))              # 3
print(obj.get_bitwidth_active())    # 5 + 3*16 = 53

# Serialize (active length only)
packed = obj.serialize(word_bw=32)
print(packed.shape)          # (2,) — 53 bits fits in 2 words

# Roundtrip
obj2 = VU16()
obj2.deserialize(packed, word_bw=32)
print(obj2.val)              # [10 20 30]

The nbits_len defaults to max(1, int(len_max).bit_length()), which is just enough bits to encode values 0..len_max.

VarDataArray inside a DataList

When a DataList contains a VarDataArray member, active sizing propagates upward automatically:

from waveflow.hw import DataList, IntField, VarDataArray
import numpy as np

U8  = IntField.specialize(bitwidth=8, signed=False)
VU8 = VarDataArray.specialize(elem_type=U8, len_max=8)

class Packet(DataList):
    elements = {
        "header":  U8,
        "payload": VU8,
        "footer":  U8,
    }

pkt = Packet()
pkt.header  = 0x01
pkt.payload = np.array([0xAA, 0xBB, 0xCC], dtype=np.uint32)
pkt.footer  = 0xFF

# Max size (for allocation / C++ struct)
print(Packet.nwords_per_inst(32))    # worst-case, 8 active bytes

# Active size (matches actual serialized words)
print(pkt.nwords_active(32))         # 3 active bytes → fewer words
print(pkt.get_bitwidth_active())     # 8 + (4 + 3*8) + 8 = 44 bits

packed = pkt.serialize(word_bw=32)  # writes only active words
pkt2 = Packet()
pkt2.deserialize(packed, word_bw=32)
print(pkt2.payload)  # [170 187 204]

C++ lowering

VarDataArray generates a C++ struct with an in-band len field and fixed-size storage. For DataField element types, write_array and read_array helpers are emitted. Complex element types (DataList, DataArray) produce only the data members and constants; transport helpers for those are left for future implementation.

struct UInt8VarArray {
    ap_uint<4> len;           // nbits_len bits
    ap_uint<8> data[10];      // len_max elements

    static constexpr int len_max   = 10;
    static constexpr int nbits_len = 4;
    static constexpr int bitwidth  = 84;  // nbits_len + len_max * 8

    template<int word_bw>
    int nwords_active() const;            // runtime cursor simulation

    void write_array(ap_uint<32> x[]) const;
    void read_array(const ap_uint<32> x[]);
};

Use cls.as_buildable(word_bw_supported=[32]) to generate the header file.