From 2989425df1a9c4590f8e7ca1a781d276adee982d Mon Sep 17 00:00:00 2001 From: makslevental Date: Tue, 7 May 2024 17:54:45 -0500 Subject: [PATCH] Build libxaie and generate ctype bindings --- .github/actions/setup_base/action.yml | 58 ++++ .github/workflows/wheels.yml | 150 +++++++++ .gitignore | 5 +- .gitmodules | 6 + CMakeLists.txt | 83 +++++ pyproject.toml | 27 ++ requirements-dev.txt | 7 + setup.py | 434 ++++++++++++++++++++++++++ tests/test_type_ctypes_enum.py | 36 +++ tests/test_xaie_ctypes.py | 69 ++++ third_party/aie-rt | 1 + third_party/bootgen | 1 + xaiepy/__init__.py | 1 + xaiepy/typed_ctypes_enum.py | 42 +++ 14 files changed, 919 insertions(+), 1 deletion(-) create mode 100644 .github/actions/setup_base/action.yml create mode 100644 .github/workflows/wheels.yml create mode 100644 .gitmodules create mode 100644 CMakeLists.txt create mode 100644 pyproject.toml create mode 100644 requirements-dev.txt create mode 100644 setup.py create mode 100644 tests/test_type_ctypes_enum.py create mode 100644 tests/test_xaie_ctypes.py create mode 160000 third_party/aie-rt create mode 160000 third_party/bootgen create mode 100644 xaiepy/__init__.py create mode 100644 xaiepy/typed_ctypes_enum.py diff --git a/.github/actions/setup_base/action.yml b/.github/actions/setup_base/action.yml new file mode 100644 index 0000000..665fede --- /dev/null +++ b/.github/actions/setup_base/action.yml @@ -0,0 +1,58 @@ +name: "Setup base" + +inputs: + MATRIX_OS: + description: 'matrix.os' + required: true + MATRIX_ARCH: + description: 'matrix.arch' + required: true + +description: '' + +runs: + using: "composite" + steps: + - uses: ilammy/msvc-dev-cmd@v1.4.1 + if: ${{ inputs.MATRIX_OS == 'windows-2019' }} + + - name: Set up Visual Studio shell + if: ${{ inputs.MATRIX_OS == 'windows-2019' }} + uses: egor-tensin/vs-shell@v2 + with: + arch: x64 + + - name: MS Build + if: ${{ inputs.MATRIX_OS == 'windows-2019' }} + uses: microsoft/setup-msbuild@v1.1 + +# - name: Free disk space +# if: contains(inputs.MATRIX_OS, 'ubuntu') +# uses: descriptinc/free-disk-space@main +# with: +# tool-cache: true +# android: true +# dotnet: true +# haskell: true +# large-packages: true +# swap-storage: false # This frees space on the wrong partition. + + - uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install Ninja + uses: llvm/actions/install-ninja@6a57890d0e3f9f35dfc72e7e48bc5e1e527cdd6c # Jan 17 + + - name: Install cross-compilation toolchain + shell: bash + if: ${{ inputs.MATRIX_OS == 'ubuntu-20.04' && inputs.MATRIX_ARCH == 'aarch64' }} + run: | + + sudo apt-get update + sudo apt-get install -y binutils-aarch64-linux-gnu \ + g++-aarch64-linux-gnu gcc-aarch64-linux-gnu + + - name: pip install standard tools + shell: bash + run: pip install cibuildwheel wheel auditwheel \ No newline at end of file diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml new file mode 100644 index 0000000..0c1a32d --- /dev/null +++ b/.github/workflows/wheels.yml @@ -0,0 +1,150 @@ +name: Wheels + +on: + push: + branches: + - main + pull_request: + types: [assigned, opened, synchronize, reopened] + workflow_dispatch: + +jobs: + + build: + + continue-on-error: true + + runs-on: ${{ matrix.OS }} + strategy: + fail-fast: false + matrix: + include: + - OS: ubuntu-20.04 + ARCH: x86_64 + + - OS: windows-2019 + ARCH: AMD64 + + defaults: + run: + shell: bash + + steps: + + - name: Checkout actions + uses: actions/checkout@v3 + with: + submodules: true + + - uses: ./.github/actions/setup_base + id: setup_base + with: + MATRIX_OS: ${{ matrix.OS }} + MATRIX_ARCH: ${{ matrix.ARCH }} + + # build + + - name: cibuildwheel python bindings + run: | + + cibuildwheel --output-dir wheelhouse + + # done + + - name: Upload wheels + uses: actions/upload-artifact@v3 + with: + path: wheelhouse/*.whl + name: build_artifact_python_bindings + + build-linux-aarch64: + + continue-on-error: true + + runs-on: ${{ matrix.OS }} + strategy: + fail-fast: false + matrix: + include: + - OS: ubuntu-20.04 + ARCH: aarch64 + PY_VERSION: "cp38" + + - OS: ubuntu-20.04 + ARCH: aarch64 + PY_VERSION: "cp39" + + - OS: ubuntu-20.04 + ARCH: aarch64 + PY_VERSION: "cp310" + + - OS: ubuntu-20.04 + ARCH: aarch64 + PY_VERSION: "cp311" + + - OS: ubuntu-20.04 + ARCH: aarch64 + PY_VERSION: "cp312" + + steps: + - name: Checkout actions + uses: actions/checkout@v3 + with: + submodules: true + + - uses: ./.github/actions/setup_base + id: setup_base + with: + MATRIX_OS: ${{ matrix.OS }} + MATRIX_ARCH: ${{ matrix.ARCH }} + + - name: Set up QEMU + if: ${{ matrix.OS == 'ubuntu-20.04' && matrix.ARCH == 'aarch64' }} + uses: docker/setup-qemu-action@v2 + with: + platforms: ${{ matrix.ARCH }} + + # build + + - name: cibuildwheel python bindings aarch64 + run: | + + cibuildwheel --output-dir wheelhouse + + # done + + - name: Upload wheels + uses: actions/upload-artifact@v3 + with: + path: wheelhouse/*.whl + name: build_artifact_python_bindings + + upload_bindings_wheels: + + needs: [build, build-linux-aarch64] + + runs-on: ubuntu-latest + + permissions: + id-token: write + contents: write + + steps: + - uses: actions/download-artifact@v3 + with: + # unpacks default artifact into dist/ + # if `name: artifact` is omitted, the action will create extra parent dir + name: build_artifact_python_bindings + path: dist + + - name: Release current commit + uses: ncipollo/release-action@v1.12.0 + with: + artifacts: "dist/*.whl" + token: "${{ secrets.GITHUB_TOKEN }}" + tag: "latest" + name: "latest" + removeArtifacts: false + allowUpdates: true + replacesArtifacts: true + makeLatest: true \ No newline at end of file diff --git a/.gitignore b/.gitignore index 68bc17f..df58d7d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ __pycache__/ # C extensions *.so +*.a # Distribution / packaging .Python @@ -157,4 +158,6 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +.idea/ +cmake-build-debug +cmake-build-release diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..18fb4c5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "third_party/bootgen"] + path = third_party/bootgen + url = https://github.com/Xilinx/bootgen.git +[submodule "third_party/aie-rt"] + path = third_party/aie-rt + url = git@github.com:stephenneuendorffer/aie-rt.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..affa0cd --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,83 @@ +cmake_minimum_required(VERSION 3.15) +project(xaiepy) + +include(collect) + +#find_package(OpenSSL REQUIRED) +#if(OPENSSL_FOUND) +# message(STATUS "OpenSSL found") +# message(STATUS "OpenSSL include directories:" ${OPENSSL_INCLUDE_DIR}) +#else() +# message(FATAL_ERROR "OpenSSL Not found.") +#endif() + +set(BOOTGEN_SRC_DIR ${PROJECT_SOURCE_DIR}/third_party/bootgen) +# malloc.h is deprecated and should not be used +# https://stackoverflow.com/a/56463133 If you want to use malloc, then include +# stdlib.h +file(READ ${BOOTGEN_SRC_DIR}/cdo-npi.c FILE_CONTENTS) +string(REPLACE "#include " "" FILE_CONTENTS "${FILE_CONTENTS}") +file(WRITE ${BOOTGEN_SRC_DIR}/cdo-npi.c "${FILE_CONTENTS}") + +file(READ ${BOOTGEN_SRC_DIR}/cdo-alloc.c FILE_CONTENTS) +string(REPLACE "#include " "" FILE_CONTENTS "${FILE_CONTENTS}") +file(WRITE ${BOOTGEN_SRC_DIR}/cdo-alloc.c "${FILE_CONTENTS}") + +# since we explicitly link OpenSSL::applink +file(READ ${BOOTGEN_SRC_DIR}/main.cpp FILE_CONTENTS) +string(REPLACE "#include \"openssl/ms/applink.c\"" "" FILE_CONTENTS + "${FILE_CONTENTS}") +file(WRITE ${BOOTGEN_SRC_DIR}/main.cpp "${FILE_CONTENTS}") + +# objlib +file(GLOB BOOTGEN_SOURCE_SRCS ${BOOTGEN_SRC_DIR}/*.c*) +list(REMOVE_ITEM BOOTGEN_SOURCE_SRCS ${BOOTGEN_SRC_DIR}/main.cpp) +add_library(bootgen_objlib OBJECT ${BOOTGEN_SOURCE_SRCS}) +set_property(TARGET bootgen_objlib PROPERTY POSITION_INDEPENDENT_CODE 1) +if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + target_compile_definitions(bootgen_objlib PRIVATE YY_NO_UNISTD_H) +endif() +target_include_directories(bootgen_objlib PRIVATE ${BOOTGEN_SRC_DIR} + ${OPENSSL_INCLUDE_DIR}) +target_link_libraries(bootgen_objlib PUBLIC) + +add_library(bootgen_shared SHARED $) +add_library(bootgen_static STATIC $) + +#add_executable(bootgen ${BOOTGEN_SRC_DIR}/main.cpp) +#target_include_directories( +# bootgen PUBLIC ${BOOTGEN_SRC_DIR} ${OPENSSL_INCLUDE_DIR} +# ${CMAKE_CURRENT_BINARY_DIR}/include) +#target_compile_definitions(bootgen PRIVATE OPENSSL_USE_APPLINK) +#target_link_libraries(bootgen PRIVATE bootgen_static OpenSSL::SSL +# OpenSSL::applink) + +add_library(cdo_driver STATIC ${BOOTGEN_SRC_DIR}/cdo-driver/cdo_driver.c) +# because primarily this will be linked into libxaie.so... if not you get +# /usr/bin/ld: libcdo_driver.a(cdo_driver.c.o): warning: relocation against +# `hdr' in read-only section `.text' /usr/bin/ld: +# libcdo_driver.a(cdo_driver.c.o): relocation R_X86_64_PC32 against symbol +# `LEfwrite' can not be used when making a shared object; recompile with -fPIC +set_property(TARGET cdo_driver PROPERTY POSITION_INDEPENDENT_CODE 1) + +target_include_directories( + cdo_driver PUBLIC $ + $) +if(NOT CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + target_compile_options(cdo_driver PRIVATE -Wno-cast-qual -Wno-sign-compare) +endif() + +set(AIERT_SRC_DIR ${PROJECT_SOURCE_DIR}/third_party/aie-rt/driver/src) +# gotta add the subdirectory so the copies to build/include/xaiengine occur... +add_subdirectory(${AIERT_SRC_DIR}) + +target_compile_options(aienginev2 PRIVATE -Wall -Wextra -D__AIECDO__) +get_target_property(AIERT_SRCS aienginev2 SOURCES) +list(TRANSFORM AIERT_SRCS PREPEND ${AIERT_SRC_DIR}/) +get_target_property(AIERT_INCLUDE_DIRECTORIES aienginev2 INCLUDE_DIRECTORIES) + +add_library(xaie SHARED ${AIERT_SRCS}) +set_target_properties(xaie PROPERTIES LINKER_LANGUAGE C) +target_compile_options(xaie PRIVATE -D__AIECDO__) +target_link_libraries(xaie cdo_driver) +target_include_directories(xaie PUBLIC ${AIERT_INCLUDE_DIRECTORIES}) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..19f2c43 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,27 @@ +[build-system] +requires = [ + "setuptools>=42", + "wheel", + "ninja", + "cmake>=3.12", + "ctypesgen" +] +build-backend = "setuptools.build_meta" + +[tool.cibuildwheel] +build-verbosity = 3 +build = "cp38-* cp39-* cp310-* cp311-* cp312-*" +skip = ["*-manylinux_i686", "*-musllinux*"] +manylinux-aarch64-image = "manylinux_2_28" +manylinux-x86_64-image = "manylinux_2_28" + +[tool.cibuildwheel.linux] +before-build = [ + "pip install -r requirements-dev.txt", +] + +[tool.cibuildwheel.windows] +build = "cp38-* cp39-* cp310-* cp311-* cp312-*" +before-build = [ + "pip install -r requirements-dev.txt", +] diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..2fa4d11 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,7 @@ +setuptools>=42 +wheel +ninja +cmake>=3.28 +ctypesgen +cmake-format +black \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..fd30e7b --- /dev/null +++ b/setup.py @@ -0,0 +1,434 @@ +import argparse +import os +import platform +import re +import subprocess +import sys +from glob import glob +from pathlib import Path +from pprint import pprint +from textwrap import dedent + +from ctypesgen import parser as core_parser, processor +from ctypesgen.ctypedescs import CtypesStruct, CtypesBitfield, CtypesEnum +from ctypesgen.expressions import ConstantExpressionNode, ExpressionNode +from ctypesgen.printer_python import WrapperPrinter +from ctypesgen.printer_python.printer import LIBRARYLOADER_PATH +from setuptools import Extension, setup +from setuptools.command.build_ext import build_ext + + +def find_names_in_modules(modules): + names = set() + for module in modules: + try: + mod = __import__(module) + except Exception: + pass + else: + names.update(dir(mod)) + return names + + +class _WrapperPrinter(WrapperPrinter): + def __init__(self, outpath, options, data): + self.file = open(outpath, "w") if outpath else sys.stdout + self.options = options + + if ( + self.options.strip_build_path + and self.options.strip_build_path[-1] != os.path.sep + ): + self.options.strip_build_path += os.path.sep + + if not self.options.embed_preamble and outpath: + self._copy_preamble_loader_files(outpath) + + self.print_header() + self.file.write("\n") + + self.print_preamble() + self.file.write("\n") + + self.print_loader() + self.file.write("\n") + + self.print_group(self.options.libraries, "libraries", self.print_library) + self.print_group(self.options.modules, "modules", self.print_module) + self.file.write("from .typed_ctypes_enum import *\n") + + method_table = { + "function": self.print_function, + "macro": self.print_macro, + "struct": self.print_struct, + "struct-body": self.print_struct_members, + "typedef": self.print_typedef, + "variable": self.print_variable, + "enum": self.print_enum, + "constant": self.print_constant, + "undef": self.print_undef, + } + + for kind, desc in data.output_order: + if desc.included: + r = method_table[kind](desc) + if kind == {"constant", "variable"} and not r: + continue + self.file.write("\n") + + self.print_group( + self.options.inserted_files, "inserted files", self.insert_file + ) + self.strip_prefixes() + + def srcinfo(self, src): + return + + def print_fixed_function(self, function): + CC = "stdcall" if function.attrib.get("stdcall", False) else "cdecl" + if function.source_library: + self.file.write( + '{PN} = _libs["{L}"].get("{CN}", "{CC}")\n'.format( + L=function.source_library, + CN=function.c_name(), + PN=function.py_name(), + CC=CC, + ) + ) + else: + self.file.write( + '{PN} = _lib.get("{CN}", "{CC}")\n'.format( + CN=function.c_name(), PN=function.py_name(), CC=CC + ) + ) + + # Argument types + self.file.write( + "%s.argtypes = [%s]\n" + % ( + function.py_name(), + ", ".join([a.py_string() for a in function.argtypes]), + ) + ) + + # Return value + if function.restype.py_string() == "String": + self.file.write( + "if sizeof(c_int) == sizeof(c_void_p):\n" + " {PN}.restype = ReturnString\n" + "else:\n" + " {PN}.restype = {RT}\n" + " {PN}.errcheck = ReturnString\n".format( + PN=function.py_name(), RT=function.restype.py_string() + ) + ) + else: + self.file.write( + "%s.restype = %s\n" % (function.py_name(), function.restype.py_string()) + ) + + def print_variable(self, variable): + return + + def print_library(self, library): + self.file.write( + '_lib = _libs["%s"] = load_library("%s")\n' % (library, library) + ) + + seen_enum_variants = set() + + def print_constant(self, constant): + if constant.name in self.seen_enum_variants: + return False + self.file.write("%s = %s" % (constant.name, constant.value.py_string(False))) + return True + + def print_enum(self, enum): + enum_variant, node = enum.members[0] + assert isinstance(node, ConstantExpressionNode) + self.file.write( + dedent( + f"""\ + class {enum.ctype.tag}(CEnumeration): + {enum_variant} = {node.py_string(False)} + """ + ) + ) + self.seen_enum_variants.add(enum_variant) + for i, (enum_variant, node) in enumerate(enum.members[1:], start=1): + if i + 1 < len(enum.members): + nl = "\n" + else: + nl = "" + self.file.write(f" {enum_variant} = {node.py_string(False)}{nl}") + self.seen_enum_variants.add(enum_variant) + + def print_struct_members(self, struct): + if struct.opaque: + return + + # is this supposed to be packed? + if struct.attrib.get("packed", False): + aligned = struct.attrib.get("aligned", [1]) + assert ( + len(aligned) == 1 + ), "cgrammar gave more than one arg for aligned attribute" + aligned = aligned[0] + if isinstance(aligned, ExpressionNode): + # TODO: for non-constant expression nodes, this will fail: + aligned = aligned.evaluate(None) + self.file.write( + "{}_{}._pack_ = {}\n".format(struct.variety, struct.tag, aligned) + ) + + # handle unnamed fields. + unnamed_fields = [] + names = set([x[0] for x in struct.members]) + anon_prefix = "unnamed_" + n = 1 + for mi in range(len(struct.members)): + mem = list(struct.members[mi]) + if mem[0] is None: + while True: + name = "%s%i" % (anon_prefix, n) + n += 1 + if name not in names: + break + mem[0] = name + names.add(name) + if type(mem[1]) is CtypesStruct: + unnamed_fields.append(name) + struct.members[mi] = mem + + self.file.write("%s_%s.__slots__ = [\n" % (struct.variety, struct.tag)) + for name, ctype in struct.members: + self.file.write(" '%s',\n" % name) + self.file.write("]\n") + + if len(unnamed_fields) > 0: + self.file.write("%s_%s._anonymous_ = [\n" % (struct.variety, struct.tag)) + for name in unnamed_fields: + self.file.write(" '%s',\n" % name) + self.file.write("]\n") + + self.file.write("%s_%s._fields_ = [\n" % (struct.variety, struct.tag)) + for name, ctype in struct.members: + if isinstance(ctype, CtypesBitfield): + self.file.write( + " ('%s', %s, %s),\n" + % (name, ctype.py_string(), ctype.bitfield.py_string(False)) + ) + else: + ct = ctype.py_string() + if isinstance(ctype, CtypesEnum): + ct = ctype.tag + self.file.write(" ('%s', %s),\n" % (name, ct)) + self.file.write("]\n") + + def print_typedef(self, typedef): + ct = typedef.ctype.py_string() + if isinstance(typedef.ctype, CtypesEnum): + ct = typedef.ctype.tag + self.file.write("%s = %s" % (typedef.name, ct)) + + def print_loader(self): + self.file.write("_libs = {}\n") + self.file.write("_libdirs = %s\n\n" % self.options.compile_libdirs) + self.file.write("# Begin loader\n\n") + if self.options.embed_preamble: + with open(LIBRARYLOADER_PATH, "r") as loader_file: + self.file.write(loader_file.read()) + else: + self.file.write("from .ctypes_loader import *\n") + self.file.write("\n# End loader\n\n") + self.file.write("from pathlib import Path\n") + self.file.write( + "add_library_search_dirs([%s])" + % ", ".join([d for d in self.options.runtime_libdirs]) + ) + self.file.write("\n") + + +EXCLUDED_HEADERS = {"xaie_interrupt.h"} + + +def generate(xaie_build_include_dir: Path, output: Path): + headers = list( + filter( + lambda f: Path(f).name not in EXCLUDED_HEADERS, + glob(f'{xaie_build_include_dir / "xaiengine" / "*.h"}'), + ) + ) + + args = argparse.Namespace( + headers=headers, + all_headers=False, + allow_gnu_c=False, + builtin_symbols=False, + compile_libdirs=[str(Path(__file__).parent)], + cpp="gcc -E", + cpp_defines=[], + cpp_undefines=[], + debug_level=0, + embed_preamble=True, + exclude_symbols=["XAie_MapIrqIdToCols"], + header_template=None, + # blows up file + include_macros=False, + include_search_paths=[], + include_symbols=[], + include_undefs=True, + inserted_files=[], + libraries=["xaie"], + modules=[], + no_gnu_types=False, + no_load_library=False, + no_python_types=False, + no_stddef_types=False, + optimize_lexer=False, + other_headers=[], + other_known_names=[], + output=None, + output_language="py", + runtime_libdirs=["str(Path(__file__).parent)"], + save_preprocessed_headers=None, + show_all_errors=False, + show_long_errors=False, + show_macro_warnings=True, + strip_build_path=None, + strip_prefixes=[], + universal_libdirs=[], + ) + + args.compile_libdirs = args.compile_libdirs + args.universal_libdirs + args.runtime_libdirs = args.runtime_libdirs + args.universal_libdirs + args.other_known_names = find_names_in_modules(args.modules) + + descriptions = core_parser.parse(args.headers, args) + processor.process(descriptions, args) + + _WrapperPrinter(str(output.absolute()), args, descriptions) + + +class CMakeExtension(Extension): + def __init__(self, name: str, sourcedir: str = "") -> None: + super().__init__(name, sources=[]) + self.sourcedir = os.fspath(Path(sourcedir).resolve()) + + +def check_env(build): + return os.environ.get(build, 0) in {"1", "true", "True", "ON", "YES"} + + +class CMakeBuild(build_ext): + def build_extension(self, ext: CMakeExtension) -> None: + ext_fullpath = Path.cwd() / self.get_ext_fullpath(ext.name) + extdir = ext_fullpath.parent.resolve() + cfg = "Release" + + cmake_generator = os.environ.get("CMAKE_GENERATOR", "Ninja") + + RUN_TESTS = 1 if check_env("RUN_TESTS") else 0 + # make windows happy + PYTHON_EXECUTABLE = str(Path(sys.executable)) + if platform.system() == "Windows": + PYTHON_EXECUTABLE = PYTHON_EXECUTABLE.replace("\\", "\\\\") + + cmake_args = [ + f"-B{build_temp}", + f"-G {cmake_generator}", + f'-DCMAKE_MODULE_PATH={Path(__file__).parent / "third_party" / "aie-rt" / "fal" / "cmake"}', + "-DCMAKE_PLATFORM_NO_VERSIONED_SONAME=ON", + f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={extdir / PACKAGE_NAME}", + f"-DPython3_EXECUTABLE={PYTHON_EXECUTABLE}", + f"-DCMAKE_BUILD_TYPE={cfg}", # not used on MSVC, but no harm + ] + if platform.system() == "Windows": + cmake_args += [ + "-DCMAKE_C_COMPILER=cl", + "-DCMAKE_CXX_COMPILER=cl", + "-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded", + "-DCMAKE_C_FLAGS=/MT", + "-DCMAKE_CXX_FLAGS=/MT", + "-DLLVM_USE_CRT_MINSIZEREL=MT", + "-DLLVM_USE_CRT_RELEASE=MT", + ] + + if "CMAKE_ARGS" in os.environ: + cmake_args += [item for item in os.environ["CMAKE_ARGS"].split(" ") if item] + + build_args = [] + if self.compiler.compiler_type != "msvc": + if not cmake_generator or cmake_generator == "Ninja": + try: + import ninja + + ninja_executable_path = Path(ninja.BIN_DIR) / "ninja" + cmake_args += [ + "-GNinja", + f"-DCMAKE_MAKE_PROGRAM:FILEPATH={ninja_executable_path}", + ] + except ImportError: + pass + + else: + single_config = any(x in cmake_generator for x in {"NMake", "Ninja"}) + contains_arch = any(x in cmake_generator for x in {"ARM", "Win64"}) + if not single_config and not contains_arch: + PLAT_TO_CMAKE = { + "win32": "Win32", + "win-amd64": "x64", + "win-arm32": "ARM", + "win-arm64": "ARM64", + } + cmake_args += ["-A", PLAT_TO_CMAKE[self.plat_name]] + if not single_config: + cmake_args += [ + f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{cfg.upper()}={extdir / PACKAGE_NAME}" + ] + build_args += ["--config", cfg] + + if sys.platform.startswith("darwin"): + osx_version = os.getenv("OSX_VERSION", "11.6") + cmake_args += [f"-DCMAKE_OSX_DEPLOYMENT_TARGET={osx_version}"] + # Cross-compile support for macOS - respect ARCHFLAGS if set + archs = re.findall(r"-arch (\S+)", os.environ.get("ARCHFLAGS", "")) + if archs: + cmake_args += ["-DCMAKE_OSX_ARCHITECTURES={}".format(";".join(archs))] + + if "PARALLEL_LEVEL" not in os.environ: + build_args += [f"-j{str(2 * os.cpu_count())}"] + else: + build_args += [f"-j{os.environ.get('PARALLEL_LEVEL')}"] + + print("ENV", pprint(os.environ), file=sys.stderr) + print("CMAKE_ARGS", cmake_args, file=sys.stderr) + + subprocess.run( + ["cmake", ext.sourcedir, *cmake_args], cwd=build_temp, check=True + ) + subprocess.run( + ["cmake", "--build", ".", "--target", "xaie", *build_args], + cwd=build_temp, + check=True, + ) + generate(build_temp / "include", extdir / PACKAGE_NAME / "_xaie.py") + + +build_temp = Path.cwd() / "build" / "temp" +if not build_temp.exists(): + build_temp.mkdir(parents=True) + +PACKAGE_NAME = "xaiepy" + +setup( + name=PACKAGE_NAME, + author="Maksim Levental", + author_email="maksim.levental@gmail.com", + long_description_content_type="text/markdown", + ext_modules=[CMakeExtension(PACKAGE_NAME, sourcedir=".")], + cmdclass={"build_ext": CMakeBuild}, + zip_safe=False, + packages=[PACKAGE_NAME], + include_package_data=True, +) diff --git a/tests/test_type_ctypes_enum.py b/tests/test_type_ctypes_enum.py new file mode 100644 index 0000000..2918ac9 --- /dev/null +++ b/tests/test_type_ctypes_enum.py @@ -0,0 +1,36 @@ +from ctypes import c_int, Structure, sizeof + +from xaiepy.typed_ctypes_enum import CEnumeration + + +class Foo(CEnumeration): + A = 42 + B = 1337 + + +class Bar(CEnumeration): + A = 5 + B = 23 + + +assert isinstance(Foo(Foo.A), c_int) +assert isinstance(Bar(Bar.A), c_int) + +assert type(Foo.A) == int +assert Foo.A == 42 +assert str(Foo(Foo.A)) == "" +assert str(Bar(Bar.B)) == "" + + +class FooBar(Structure): + _pack_ = 1 + _fields_ = [("foo", Foo), ("bar", Bar)] + + +foobar = FooBar(Foo.A, Bar.B) + +assert sizeof(foobar) == 8 +assert foobar.foo.value == 42 +assert foobar.bar.value == 23 + +assert [int(x) for x in bytes(foobar)] == [42, 0, 0, 0, 23, 0, 0, 0] diff --git a/tests/test_xaie_ctypes.py b/tests/test_xaie_ctypes.py new file mode 100644 index 0000000..08bb010 --- /dev/null +++ b/tests/test_xaie_ctypes.py @@ -0,0 +1,69 @@ +from xaiepy import ( + XAie_Config, + XAie_BackendType, + XAie_PartitionProp, + XAie_DevInst, + XAie_CfgInitialize, + XAie_StartTransaction, + XAie_ExportTransactionInstance, + XAie_TxnInst, + XAie_LocType, + XAie_DmaUpdateBdAddr, + XAie_TxnOpcode, +) + +XAIE_DEV_GEN_AIEML = 2 +XAIE_BASE_ADDR = 0x40000000 +XAIE_COL_SHIFT = 25 +XAIE_ROW_SHIFT = 20 +XAIE_SHIM_ROW = 0 +XAIE_MEM_TILE_ROW_START = 1 +XAIE_PARTITION_BASE_ADDR = 0x0 +XAIE_TRANSACTION_DISABLE_AUTO_FLUSH = 0b0 +DDR_AIE_ADDR_OFFSET = 0x80000000 + +config = XAie_Config( + XAIE_DEV_GEN_AIEML, + XAIE_BASE_ADDR, + XAIE_COL_SHIFT, + XAIE_ROW_SHIFT, + 6, + 5, + XAIE_SHIM_ROW, + XAIE_MEM_TILE_ROW_START, + 1, + (XAIE_MEM_TILE_ROW_START + 1), + (6 - 1 - 1), + XAie_PartitionProp(), + XAie_BackendType.XAIE_IO_BACKEND_CDO, +) + +dev_inst = XAie_DevInst() +XAie_CfgInitialize(dev_inst, config) + +XAie_StartTransaction(dev_inst, XAIE_TRANSACTION_DISABLE_AUTO_FLUSH) + +col, row = 0, 2 +tile_loc = XAie_LocType(row, col) +XAie_DmaUpdateBdAddr(dev_inst, tile_loc, 0, 0) +XAie_DmaUpdateBdAddr(dev_inst, tile_loc, 0, 1) +XAie_DmaUpdateBdAddr(dev_inst, tile_loc, 0, 2) + +txn_inst: XAie_TxnInst = XAie_ExportTransactionInstance(dev_inst) + +offsets = [0x21D000, 0x21D020, 0x21D040] +masks = [0xFFFC000, 0xFFFC000, 0xFFFC000] + +for i in range(txn_inst.contents.NumCmds): + opcode = XAie_TxnOpcode._reverse_map_[txn_inst.contents.CmdBuf[i].Opcode.value] + reg_off = txn_inst.contents.CmdBuf[i].RegOff + mask = txn_inst.contents.CmdBuf[i].Mask + val = txn_inst.contents.CmdBuf[i].Value + + print(f"{opcode=}") + print(f"{reg_off=:016x}") + print(f"{val=}") + print(f"{mask=:016x}") + print() + + assert opcode == "XAIE_IO_WRITE" and reg_off == offsets[i] and mask == masks[i] and val == 0 diff --git a/third_party/aie-rt b/third_party/aie-rt new file mode 160000 index 0000000..8a43823 --- /dev/null +++ b/third_party/aie-rt @@ -0,0 +1 @@ +Subproject commit 8a438235e5c9146f62a109890294a14535870691 diff --git a/third_party/bootgen b/third_party/bootgen new file mode 160000 index 0000000..92e09bf --- /dev/null +++ b/third_party/bootgen @@ -0,0 +1 @@ +Subproject commit 92e09bf37ea17d7b1f0e102a2548f27fb768651c diff --git a/xaiepy/__init__.py b/xaiepy/__init__.py new file mode 100644 index 0000000..333becc --- /dev/null +++ b/xaiepy/__init__.py @@ -0,0 +1 @@ +from ._xaie import * \ No newline at end of file diff --git a/xaiepy/typed_ctypes_enum.py b/xaiepy/typed_ctypes_enum.py new file mode 100644 index 0000000..62b2b91 --- /dev/null +++ b/xaiepy/typed_ctypes_enum.py @@ -0,0 +1,42 @@ +# https://stackoverflow.com/a/70877075/9045206 +from ctypes import c_int, Structure, sizeof + +T = c_int + + +class EnumerationType(type(T)): # type: ignore + def __new__(metacls, name, bases, dict): + if not "_members_" in dict: + _members_ = {} + for key, value in dict.items(): + if not key.startswith("_"): + _members_[key] = value + + dict["_members_"] = _members_ + else: + _members_ = dict["_members_"] + + dict["_reverse_map_"] = {v: k for k, v in _members_.items()} + cls = type(T).__new__(metacls, name, bases, dict) + for key, value in cls._members_.items(): + globals()[key] = value + return cls + + def __repr__(self): + return "" % self.__name__ + + +class CEnumeration(T, metaclass=EnumerationType): + _members_ = {} + + def __repr__(self): + value = self.value + return f"<{self.__class__.__name__}.{self._reverse_map_.get(value, '(unknown)')}: {value}>" + + def __eq__(self, other): + if isinstance(other, int): + return self.value == other + + return type(self) == type(other) and self.value == other.value + +