Auto-generating Vitis HLS Files
A key feature of PySilicon’s data schemas is that each schema class can auto-generate Vitis HLS C++ headers that mirror the Python definition exactly. For a full walkthrough of the build system, see the Build System guide.
What gets generated
For each schema class, two files are produced:
<schema_name>.h— synthesizable header used in Vitis HLS kernel code. Defines the C++ struct and templated serialization/deserialization methods.<schema_name>_tb.h— testbench companion header with file I/O and JSON helpers for non-synthesizable code.
For example, PolyCmdHdr generates:
// poly_cmd_hdr.h
struct PolyCmdHdr {
ap_uint<16> tx_id;
CoeffArray coeffs;
ap_uint<16> nsamp;
template<int word_bw>
void write_axi4_stream(hls::stream<streamutils::axi4s_word<word_bw>>& s,
bool tlast = true) const;
template<int word_bw>
void read_axi4_stream(hls::stream<streamutils::axi4s_word<word_bw>>& s,
streamutils::tlast_status& tl);
// ... write_array, read_array, write_stream, read_stream ...
};
The C++ struct fields match the Python elements dict exactly, and the serialization methods are templated over word width so a single header works for 32-bit and 64-bit AXI streams.
Serialization methods
Each generated header includes methods for all configured word widths and interface types:
| Method | Interface | Direction |
|---|---|---|
write_array / read_array |
ap_uint<W>[] array |
kernel ↔ array |
write_stream / read_stream |
hls::stream<ap_uint<W>> |
kernel ↔ plain HLS stream |
write_axi4_stream / read_axi4_stream |
hls::stream<streamutils::axi4s_word<W>> |
kernel ↔ AXI4-Stream |
All methods are templated on word_bw so the same code works regardless of bus width. Changing WORD_BW from 32 to 64 requires no changes to the kernel source.
Using the generated headers in Vitis HLS
Once the headers are generated, use them directly in your HLS kernel. The stream word type must match what the generated headers expect — streamutils::axi4s_word<W>:
#include "include/poly_cmd_hdr.h"
#include "include/streamutils_hls.h"
static const int WORD_BW = 32;
using axis_word_t = streamutils::axi4s_word<WORD_BW>;
void poly(hls::stream<axis_word_t>& in_stream,
hls::stream<axis_word_t>& out_stream) {
#pragma HLS INTERFACE axis port=in_stream
#pragma HLS INTERFACE axis port=out_stream
#pragma HLS INTERFACE ap_ctrl_none port=return
PolyCmdHdr cmd_hdr;
streamutils::tlast_status cmd_hdr_tlast;
cmd_hdr.read_axi4_stream<WORD_BW>(in_stream, cmd_hdr_tlast);
// ... computation ...
PolyRespHdr resp_hdr;
resp_hdr.tx_id = cmd_hdr.tx_id;
resp_hdr.write_axi4_stream<WORD_BW>(out_stream, true);
}
Serialization is one line per struct. If the bus width changes, only WORD_BW changes — no manual bit-packing is needed.
Generating headers with the build system
Headers are generated through the Build System. The recommended approach uses a BuildDag:
from pysilicon.build.build import BuildConfig, BuildDag
from pysilicon.build.streamutils import StreamUtilsStep
from pysilicon.hw.dataschema import DataSchemaStep
cfg = BuildConfig(root_dir=example_dir)
dag = BuildDag()
dag.add(StreamUtilsStep(output_dir="include"))
dag.add(DataSchemaStep(PolyCmdHdr, word_bw_supported=[32, 64], include_dir="include"))
dag.run(cfg)
See Schema and Array Steps for the full reference.