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 elementmax_shape: maximum dimensionsstatic: 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 (defaultdata) and schema methods.cpp_storage="raw": lowers to a raw C++ array (T[N]) and requiresstatic=Truewith 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.