Firedrake wraps Xilinx Vitis kernels in SST components. A generator reads a
kernel's metadata (kernel.xml, .xo or .xclbin) and produces an SST
element library holding one SubComponent for that kernel:
| Kernel interface | SST view |
|---|---|
m_axi buffer argument |
an SST port named after the argument, carrying SST::Firedrake::DataEvent (WRITE/READ, answered with ACK/RESP/NACK) |
s_axilite scalars, raw registers, start/done |
the C++ API SST::Firedrake::FiredrakeBase plus generated typed accessors (set_<arg>(), get_<arg>()) |
| run completion | the C++ onDone() callback and a done port carrying SST::Firedrake::DoneEvent |
axis stream argument |
no host port (XRT has no general host streaming on Alveo); keep stream connectivity inside the xclbin |
The host side runs on the Xilinx Runtime (XRT) native C++ API (xrt::device,
xrt::kernel/xrt::run, xrt::bo, xrt::ip) for the sw_emu, hw_emu and
hw targets. A csim backend calls the HLS C++ kernel directly, so a model
builds and runs without XRT or a device.
Firedrake follows the structure of VerilatorSST: a fixed core, plus a generator that turns one design into one element library.
SDL ──► controller component (yours, or FiredrakeComponent)
│ slot "model": loadUserSubComponent<FiredrakeBase>
▼
Firedrake<Kernel> generated: ELI, ports, typed accessors, csim trampoline
│ derives from
FiredrakeKernel libfiredrakecore: ports, timing, control logic
│
KernelBackend ── CsimBackend (native HLS C++)
└─ XrtBackend (XRT: sw_emu | hw_emu | hw)
-
Control is C++. The component that loads the kernel into one of its slots controls it directly:
setScalar(),writeReg()/readReg(),start(),isRunning(),isDone()andonDone(). WritingAP_STARTto control register0x00is the same as callingstart(). -
Data moves over ports. Connect each buffer port to any component that sends
DataEvents. In SDL the ports belong to the model SubComponent. -
Timing is a deterministic model, the same on every backend. Backends run immediately. Responses and completion are delivered at modeled times:
- Kernel run:
startLatency + kernelCycles / kernelClock. - Buffer transfer: all transfers share one host link. Each transfer
occupies it for
bytes / hostBWand completeshostLatencyafter it leaves the link.
kernelCyclesis a sum of products of constants and input scalar names, for example"70 + 1*size". - Kernel run:
- SST 16.0.0 or newer (
sst,sst-configonPATH) - CMake 3.19 or newer, a C++17 compiler, and Python 3.8 or newer
- For csim: the Vitis HLS headers (
$XILINX_HLS/include), found automatically fromXILINX_HLSorXILINX_VITIS - For sw_emu, hw_emu or hw: XRT (
source /opt/xilinx/xrt/setup.sh) and an Alveo platform on an x86 host. For hw you also need the card.
This is currently tested on:
- Ubuntu 24.04LTS
- Xilinx/AMD Alveo U50
- Vitis/Vivado 2026.1
mkdir build && cd build
cmake .. # XRT backends are enabled automatically when XRT is found
make -j
ctest # csim tests; run anywhere
make install # installs into ./install and runs sst-register
To also build the test xclbins with v++ and run the XRT tests:
cmake -DFIREDRAKE_PLATFORM=xilinx_u250_gen3x16_xdma_4_1_202210_1 -DFIREDRAKE_TARGETS="sw_emu;hw_emu" ..
make -j && ctest -L sw_emu
Each test runs tests/sdl/firedrake_test.py and checks the results and the
modeled timing. The tests are labelled csim, sw_emu, hw_emu, hw and u50.
The u50 tests build the vadd kernel into a U50 bitstream and run the vadd
scenarios on the card. Requirements:
- XRT, and the U50 deployment and development platform packages
(
xilinx_u50_gen3x16_xdma_5_202210_1by default) - A card running the matching base shell (
xilinx_u50_gen3x16_xdma_base_5) - A Vitis release that supports the platform (2026.1 works). Select a different
v++with-DVPP=/path/to/v++. PLATFORM_REPO_PATHS=/opt/xilinx/platformsin the environment when you run ctest, sov++finds the platform
source /opt/xilinx/xrt/setup.sh
export PLATFORM_REPO_PATHS=/opt/xilinx/platforms
cmake -DFIREDRAKE_U50_TEST=ON [-DFIREDRAKE_U50_PLATFORM=<platform>] ..
make -j
ctest -L u50 --output-on-failure
make does not build the bitstream. ctest runs it as fixtures, in this order:
vadd_u50_devicechecks for the card withxrt-smi examine.vadd_u50_xclbinrunsv++to buildtests/vadd.hw.u50/vadd.xclbin. This takes about two hours, and the timeout is 4 h.vadd_direct_u50,vadd_direct_runs_u50andvadd_links_u50run serially on the card.
The buffers are mapped to HBM by tests/kernels/vadd/vadd.u50.cfg.
Option 1: configure Firedrake against your kernel. This builds the element library firedrake<kernel>:
cmake -DFIREDRAKE_KERNEL_NAME=mykernel \
-DFIREDRAKE_KERNEL_METADATA=/path/to/mykernel.xo \
-DFIREDRAKE_HLS_SOURCES="/path/to/mykernel.cpp" \
-DFIREDRAKE_KERNEL_OVERRIDES=/path/to/mykernel.firedrake.json ..
FIREDRAKE_KERNEL_METADATAcan be akernel.xml, an.xoor an.xclbin(read withxclbinutil), or a Firedrake JSON kernel model (the<kernel>.jsonthe generator writes).FIREDRAKE_HLS_SOURCESis optional. Without it, only the XRT backends are available.
Option 2: call the CMake function from inside this tree, the same way tests/CMakeLists.txt does:
firedrake_generate_component(NAME mykernel
METADATA ${CMAKE_CURRENT_SOURCE_DIR}/mykernel/kernel.xml
OVERRIDES ${CMAKE_CURRENT_SOURCE_DIR}/mykernel/mykernel.firedrake.json
HLS_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/mykernel/mykernel.cpp)
firedrake_add_vitis_kernel(NAME mykernel SOURCES mykernel/mykernel.cpp
TARGET hw_emu PLATFORM ${FIREDRAKE_PLATFORM}) # sets mykernel_XCLBIN, mykernel_EMCONFIG_DIROption 3: run the generator directly.
scripts/firedrake-gen.py --metadata mykernel.xo --out gen/ [--csim] [--overrides o.json] [--kernel name]
The overrides file is optional:
{
"kernelCycles": "70 + 1*size",
"bufferSizes": { "in1": 4096 },
"argTypes": { "in1": "const unsigned int*", "size": "int" },
"csimInclude": "mykernel.h",
"csimLinkage": "c"
}kernelCyclesis the default cycle model.bufferSizesgives default buffer allocations.argTypesrestores source-level argument types. Vitis writes every m_axi pointer and output register intokernel.xmlasvoid*, and some signed scalars asunsigned int. These types drive the typed accessors and the csim declaration, and the test kernels show the pattern.csimIncludeis needed when arguments useap_uint<>,hls::vector<>or other C++ types. The csim trampoline then includes that header instead of declaring the kernel from itskernel.xmltypes.csimLinkageisc(the default, forextern "C"kernels) orc++.
The generator writes these files:
Firedrake<Kernel>.handFiredrake<Kernel>.cc: the SubComponent.<kernel>.json: the normalized kernel model.
A controller that wants the typed accessors includes the generated header
and dynamic_casts the FiredrakeBase* it loaded.
Direct control from your own component (see tests/elements/FiredrakeHost.cc):
model = loadUserSubComponent<SST::Firedrake::FiredrakeBase>("model");
model->onDone([this](uint64_t run, uint64_t cycles) { /* read results over the out port */ });
auto* vadd = dynamic_cast<SST::Firedrake::FiredrakeVadd*>(model);
vadd->set_size(1024); // s_axilite scalar
// ... send DataEvent::makeWrite() on the links to in1/in2, then:
model->start();host = sst.Component("host", "mylib.MyHost")
model = host.setSubComponent("model", "firedrakevadd.FiredrakeVadd")
model.addParams({"backend": "hw_emu", "xclbin": "vadd.xclbin", "emconfigDir": ".",
"bufferSize.in1": 4096, "bufferSize.in2": 4096, "bufferSize.out": 4096})
sst.Link("l_in1").connect((host, "port0", "1ns"), (model, "in1", "1ns"))If you don't need a C++ controller, use firedrake.FiredrakeComponent. It
takes scalars from the model's arg.<name> params and starts the kernel once
every buffer listed in startAfter has been written.
| Parameter | Default | Meaning |
|---|---|---|
backend |
csim |
csim, sw_emu, hw_emu or hw |
xclbin, emconfigDir, deviceIndex, instance |
Where the XRT backends find the kernel. instance selects a compute unit, for example vadd:{vadd_1}. |
|
kernelClock |
300MHz |
Kernel clock |
kernelCycles |
from overrides | Cycle model |
startLatency |
0ns |
Delay between start() and the first kernel cycle |
hostBW, hostLatency |
12GB/s, 1us |
Host link model |
bufferSize.<arg> |
from overrides | Bytes to allocate for buffer <arg>. A buffer must be allocated before it is used; accesses outside the allocation get a NACK. |
arg.<arg> |
0 |
Initial value of input scalar <arg> |
Statistics: buffer_writes, buffer_reads, bytes_written, bytes_read,
reads_while_running, kernel_runs and kernel_cycles.
- Emulation mode.
XCL_EMULATION_MODEis process wide, so every Firedrake kernel in one SST process (or MPI rank) must use the same XRT target. - Device sharing. Kernels that share a device and an xclbin share one
xrt::deviceand one xclbin load (DeviceRegistry). All XRT calls are serialized through one process-wide lock. - Control protocols.
ap_ctrl_hsandap_ctrl_chainkernels usexrt::kernelandxrt::run, opened with exclusive CU access so that raw register access works.ap_ctrl_noneand user-managed kernels usexrt::ip. In csim, a free-running kernel is re-evaluated after every input write.
- Overlapping access. Buffer reads that arrive while the kernel is running
return the backend's current contents and are counted in
reads_while_running. - Checkpointing. Device state cannot be checkpointed, so the elements are not checkpointable.
- XRT backend coverage. The XRT backend has been run on hardware: the
u50tests (XRT 2.19, Vitis 2026.1, an Alveo U50). Thesw_emuandhw_emutargets have not yet been run.
This work was supported by the U.S. Department of Energy, Office of Science, Advanced Scientific Computing Research program under project 84245 — Democratization of Co-design for Energy-Efficient Heterogeneous Computing (DeCoDe) at Pacific Northwest National Laboratory (PNNL). PNNL is a multi-program national laboratory operated for the U.S. Department of Energy (DOE) by Battelle Memorial Institute under Contract No. DE-AC05-76RL01830.
