Skip to content

Add an adf-free accessor for the core-to-core cascade - #13

Open
atassis wants to merge 2 commits into
Xilinx:mainfrom
atassis:feat/adf-free-cascade
Open

Add an adf-free accessor for the core-to-core cascade#13
atassis wants to merge 2 commits into
Xilinx:mainfrom
atassis:feat/adf-free-cascade

Conversation

@atassis

@atassis atassis commented Jul 19, 2026

Copy link
Copy Markdown

Summary

aie_api exposes the AIE core-to-core cascade only through the ADF stream API in aie_api/adf/, which includes <adf.h>. That header is part of the Vitis ADF framework and is absent in bare-metal, IRON, and mlir-aie builds, so a kernel compiled outside the ADF flow cannot issue a cascade put or get through aie_api, even though the hardware datapath is reachable through the stream intrinsics directly. This makes the cascade usable outside the ADF flow.

The branch is two self-standing commits; happy to split into two PRs if you prefer.

1. Only include the ADF headers when adf.h is available

aie.hpp pulls aie_adf.hpp under #ifdef __AIENGINE__, and aie_adf.hpp includes <adf.h>. <adf.h> ships with Vitis, so under a non-Vitis toolchain (bare-metal, IRON, mlir-aie) the umbrella fails to compile. Guarding the include with __has_include(<adf.h>) keeps the Vitis flow unchanged (the header is present, so it is included exactly as before) and skips it gracefully otherwise, so <aie_api/aie.hpp> can be used outside the ADF flow. When __has_include is not available the previous behavior is kept.

2. A typed adf-free cascade accessor

aie_api/cascade.hpp adds typed free-function accessors: cascade_out/cascade_in_i32 for vector<int32_t,16>, and cascade_out/cascade_in_acc32 for accum<acc32,16>. They bridge aie_api's vector and accum types onto the compiler's put_mcd/get_scd stream intrinsics, which enable the cascade with en = 1 by default. The accumulator path is the one a fused cross-core K-reduction uses, since partial sums travel as accumulators; the accum to native conversion stays inside the public accum API (accum::to_native() out, the implicit accum(storage_t) constructor in).

Availability follows the capability model

The cascade datapath exists across the AIE generations, but its intrinsics are compiler and architecture specific, so the accessor is gated on a per-architecture capability macro. __AIE_API_HAS_CASCADE__ is defined in each detail/<arch>/config.hpp and is 1 exactly where an implementation is present: today aie2p under Peano. It is 0 on the other architectures and on chess for aie2p, so the header is a no-op wherever the intrinsics are not available and it does not affect other compilers or architectures. put_mcd/get_scd are the standard stream intrinsic names, so extending to another compiler or architecture is expected to be a matter of confirming them there and setting the macro. I would welcome guidance on the preferred shape.

Testing

examples/cascade.cpp, guarded on __AIE_API_HAS_CASCADE__, issues both round-trips (int32 vector and acc32) and is added to the peano examples Makefile. A default-target build stays green, and an aie2p build exercises the accessors.

The accessor is validated on aie2p silicon: a three-core cascade chain (built with mlir-aie, connected by aie.cascade_flow) whose kernels call aie::cascade_out and aie::cascade_in_i32 propagates a seeded value across two cascade hops (14 -> 114 -> 214), and the host reads back 214, PASS, stable across repeated runs. The same put_mcd/get_scd intrinsics the accessor forwards to (with en = 1) are also exercised by mlir-aie's own test/npu-xrt/cascade_flows. The numerical behavior of the cascade is unchanged.

Not in this PR

Folding the cascade into the mmul and accum path so a multi-core K-reduction fuses natively is a larger design that I would be glad to raise separately as an RFC. The hand-written reduction this accessor enables is the use case that would inform that design.

atassis added 2 commits July 19, 2026 19:42
aie_adf.hpp brings in the ADF stream API, which includes <adf.h>. That header
is part of the Vitis ADF framework and is not present in bare-metal, IRON, or
mlir-aie builds, so including it unconditionally under __AIENGINE__ makes the
aie.hpp umbrella fail to compile in those flows. Guard the include with
__has_include(<adf.h>) so it is pulled in when it is available (the Vitis flow,
unchanged) and skipped gracefully otherwise, which lets aie_api be used outside
the ADF flow. When __has_include is not available, fall back to the previous
behavior.
aie_api exposes the AIE core-to-core cascade only through the ADF stream API in
aie_api/adf/, which includes <adf.h>. Outside the ADF flow that header is
absent, so a kernel cannot issue a cascade put or get through aie_api, even
though the hardware datapath is reachable through the stream intrinsics.

Add aie_api/cascade.hpp with typed free-function accessors (cascade_out,
cascade_in_i32, cascade_in_acc32) that bridge aie_api's vector and accum types
onto the compiler's put_mcd/get_scd stream intrinsics, which enable the cascade
with en = 1 by default. The acc32 path is the one a fused cross-core
K-reduction uses; the accum to native conversion stays inside the public accum
API through accum::to_native() and the implicit accum(storage_t) constructor.

Availability follows aie_api's capability model. __AIE_API_HAS_CASCADE__ is
defined in each detail/<arch>/config.hpp and is 1 exactly where an
implementation is present: today aie2p under Peano. It is 0 on the other
architectures and on chess for aie2p, so the header is a no-op wherever the
intrinsics are not available and it does not affect other compilers or
architectures. examples/cascade.cpp, guarded on __AIE_API_HAS_CASCADE__, shows
the int32 and acc32 round-trips.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant