Skip to content

rebase: pythoc onto upstream/main (5b9b3a3) - #25

Draft
fifield wants to merge 49 commits into
pythocfrom
gc/pythoc/rebase-mlir-aie-on-5b9b3a3
Draft

rebase: pythoc onto upstream/main (5b9b3a3)#25
fifield wants to merge 49 commits into
pythocfrom
gc/pythoc/rebase-mlir-aie-on-5b9b3a3

Conversation

@fifield

@fifield fifield commented May 30, 2026

Copy link
Copy Markdown
Owner

Rebased pythoc onto Xilinx/mlir-aie main at 5b9b3a3.

The weekly npu-dev bead recorded upstream head 91692b3, but upstream/main had advanced when fetched for this rebase. This topic uses the current upstream/main head.

Reviewer: rebase and merge or squash and merge to advance pythoc. The npu-dev gitlink bump lands as a separate draft PR gated on this one.

fifield and others added 30 commits May 30, 2026 08:30
Implement external PythoC kernel compilation for IRON framework.
PythoC kernels can now be compiled to LLVM IR and integrated into
IRON programs as Kernel objects.

New files:
- python/iron/pythoc/__init__.py - Package exports
- python/iron/pythoc/compiler.py - PythoC→LLVM IR compilation
- python/iron/pythoc/types.py - Type mapping (PythoC↔NumPy)
- python/iron/pythoc/kernel.py - PythocKernel class
- python/iron/pythoc/test_standalone.py - Compilation test

Modified:
- python/iron/__init__.py - Export PythocKernel, compile_pythoc_kernel

Key features:
- Compiles PythoC kernels to AIE2-compatible LLVM IR
- Returns .ll files (Peano toolchain handles .ll→.o)
- Type system mapping for IRON integration
- Tested with mul.py kernel (1557 bytes LLVM IR)

Phase 2 (single-source inline kernels) ready for implementation.

See: PythoC/PYTHOC_IRON_INTEGRATION_PLAN.md for details
…el decorator

- Add @aie_kernel decorator for inline kernel definitions
- Extend PythocKernel to detect and compile decorated functions
- Add vector_add_inline.py example demonstrating single-source workflow
- Add comprehensive test suite (all 3/3 tests pass)
- Update compiler.py to support inline kernel compilation

Phase 2 enables single-source development where PythoC kernels are
defined inline using the @aie_kernel decorator and compiled automatically
during PythocKernel initialization. This eliminates the need for separate
kernel files and manual compilation steps.

Test results:
- Decorator metadata capture: PASS
- Inline kernel compilation: PASS (1546 bytes LLVM IR)
- Error handling: PASS
…mple

- Follow pattern from programming_guide/mini_tutorial/single_file_end_to_end.py
- Add command-line argument parsing (device, work-dir, tensor-size, etc.)
- Include full compilation pipeline: MLIR generation, aiecc, and XRT execution
- Add proper error handling and validation
- Make executable with proper shebang

The example now demonstrates the complete workflow:
1. Define PythoC kernel inline with @aie_kernel
2. Build IRON program (kernel compiles automatically)
3. Generate MLIR and compile to xclbin
4. Run on NPU and verify results

Usage: python3 vector_add_inline.py [--device npu2] [--tensor-size 4096] [--skip-run]
out of date
- Use ml_dtypes.bfloat16 for proper float32→bf16→uint16 conversion
  (old float32.view(uint16)[::2] produced zero/half-size buffers)
- Validate results with np.allclose on float32 instead of exact uint16 match
- NPU end-to-end test now passes correctly
- my_kernels.py: reusable kernel library with add, sub, add_bf16, mul_bf16
- vector_add_library.py: imports add_kernel from my_kernels module
  instead of defining inline; NPU test passes
End-to-end example reading hardware registers (lock values) from the
processor bus using PythoC's read_tm() intrinsic. Demonstrates the
Peano equivalent of the Chess-based tile_mapped_read test.

Targets AIE2P (npu2) by default. Verified passing on hardware.
Demonstrates runtime DMA programming from the AIE core using write_tm
(processor bus writes). The kernel programs BD registers, starts DMA
channels, and uses lock-based signaling for completion — no static
tile DMA configuration from MLIR.

Flow: host sends data via shim DMA -> core programs S2MM BD0 to
receive -> core does add-one -> core programs MM2S BD1 to send ->
host verifies output.
…-coding

- Add get_register_offset() method to AIEAddressDecoder in regdb.py
- Add extra_globals parameter to compile_pythoc_source() so callers
  can inject constants (e.g. register addresses) into PythoC kernels
- Replace all hard-coded hex addresses in dynamic_dma_add_one.py with
  named constants looked up from the register database at import time
- Add extra_globals parameter to PythocKernel, forwarded to
  compile_pythoc_source for injecting constants (e.g. regdb addresses)
- Update dynamic_dma_add_one.py and tile_mapped_read_inline.py to use
  PythocKernel for inline kernel compilation instead of calling
  compile_pythoc_source directly
PythocKernel inherits Kernel.resolve() which calls external_func
internally. Use kernel.resolve() + kernel(...) directly instead of
manually declaring external_func and a separate call variable.
Compiler changes:
- Support helpers parameter in compile_pythoc_source() to compile
  helper functions alongside the main kernel in a single .o
- Register helpers in PythoC's unified registry so call resolution works
- Add struct type to compilation globals

PythocKernel changes:
- Accept helpers parameter (list of Python functions)
- Prepend helper source to main kernel source before compilation

New example:
- dynamic_dma_struct.py: struct-based DMA BD/lock programming with
  helper functions compiled alongside the main kernel

Minor:
- Add clarifying comments to kernel.resolve() calls in low-level examples
Kernel.__init__ renamed bin_name -> object_file_name. Core() no
longer accepts link_with; it must be set on external_func() instead
(handled automatically by Kernel.resolve()). Update PythocKernel
super().__init__() calls and programming examples accordingly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
BFP16 matrix multiplication kernel for AIE2P using PythoC + IRON.
Implements the same algorithm as mlir-air's bf16 matmul example:
bf16 → accfloat → bfp16ebs8 → BFP576.MAC hardware path.

32×32 single-core with 2×2 output blocking (8×8 tiles).
Passes random-data validation with <1.5% relative error vs f32 reference.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replicates mlir-air/test.cpp methodology: 10 warmup + 20 measurement
iterations, wall-clock timing, reports avg/min/max latency and GFLOPS.

32×32×32 single core: ~80 us / 0.82 GFLOPS peak.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The AIE2p llc backend selects vlda/vldb only when the vector load/store
carries no explicit alignment annotation (or one >= the vector width).
PythoC emits element-size alignment (e.g. align 2 for bfloat) on all
vector loads, which tells llc the pointer is only 2-byte aligned and
forces a scalar fallback: 128x lda.s16 + 448x vpush.hi.32 per 64-element
bfloat vector instead of a single vlda.

Add _strip_vector_alignment() which removes 'align N' from
load/store <N x T> instructions before writing the .ll file passed to llc.
Scalar loads/stores are left unchanged.  This matches the 'peanohack'
pre-processing step already performed by aiecc on mlir-air kernels.

Measured on bf16_gemm_tile_kernel:
  Before: vlda/vldb=4,  vpush.hi.32=448, lda=456
  After:  vlda/vldb=49, vpush.hi.32=0,   lda=63

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds hardware event tracing via IRON rt.enable_trace() to
bf16_gemm_single_core.py:
- Worker(trace=1) marks the compute tile for tracing
- rt.enable_trace(trace_size, workers=[worker]) inside the sequence
  context inserts the trace packet_flow and DDR capture BD
- TraceConfig / DefaultNPURuntime handles the 4th XRT buffer,
  hex dump, and post-processing
- --trace-size BYTES CLI flag (e.g. 0x20000); 0 = disabled
- After PASS, prints event0→event1 cycle summary per tile

Single-core topology (one tile, one shim column) avoids the stream
switch congestion that prevents tracing the multi-core design.

Note: single-core correctness regression is pre-existing and
unrelated to trace (fails identically without --trace-size).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Re-apply the _strip_vector_alignment performance fix, narrowed to only
strip alignment annotations from <N x bfloat> and <N x half> vector
load/store operations. The previous version stripped all vector types
including float/i32, which caused data corruption in accumulators.

The AIE2p llc backend selects vlda/vldb (vector load/store) only when
alignment is absent or >= vector width. PythoC emits element-size
alignment (align 2 for bfloat) which forces scalar fallback. Stripping
alignment for bfloat/half enables the efficient vector instructions
without affecting float/i32 types that require correct alignment.

Verified on hardware:
- bf16 passthrough: 0 mismatches
- f32 passthrough: 0 mismatches
- bf16 GEMM single-core: 1.4% relative error (PASS)
- bf16 GEMM multi-core 4x4: 0.95% relative error (PASS)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Compiler pipeline (python/iron/pythoc/compiler.py):
- Replace llvmlite in-process optimize+emit with Peano opt+llc subprocesses
- Strip ALL vector alignment pre-opt (peanohack) and post-opt
- Add _strip_unsupported_flags() for samesign and initializes(...) attributes
- Pipeline: raw IR → strip align+flags → Peano opt -O2 → strip align+flags → llc

Kernel changes (bf16_gemm_single_core.py, bf16_gemm_multi_core.py):
- Interleave loads between MACs to reduce peak BFP register pressure
  (load B1 after MAC(A0,B0), load A1 after MAC(A0,B1))
- Use incrementing phi-carried pointer offsets instead of recomputing k*STRIDE
- Add separate C buffer zero loop + accumulator preload from zeroed C buffer

Results: single-core PASS (1.4% relative error), multi-core 1.87 TFLOPS
peak at 2048x2048x2048 with 16-instruction VLIW inner loop.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The bf16_zero_kernel loop was being folded by Peano opt -O2 into
llvm.memset, which lowered to a scalar byte-at-a-time st.s8 loop
(16384 iterations for a 32KB buffer). Fix by using i32 vectors with
vshuffle as an optimization barrier — vshuffle is an opaque AIE
intrinsic that prevents the memset pattern match while still producing
a zero vector. The result is a proper 512-bit vst loop (512 iterations).

Also adds trace infrastructure (enable_trace, TraceConfig) for
profiling support.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- program.py: create shim/mem/compute tile stubs for trace shim_col
  override so pathfinder has valid routing paths
- runtime.py: trace configuration and shim tile handling improvements
- bf16_gemm_single_core.py: move event0() after zero-fill loop

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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