diff --git a/README.md b/README.md index fc1da5e51d..dde69c39ba 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ hls4ml.utils.fetch_example_list() ``` ### Building a project. -We will build the project using Xilinx Vivado HLS, which can be downloaded and installed from [here](https://www.xilinx.com/products/design-tools/vivado/integration/esl-design.html). Alongside Vivado HLS, hls4ml also supports Vitis HLS, Intel HLS, Catapult HLS and has some experimental support dor Intel oneAPI. The target back-end can be changed using the argument backend when building the model. +We will build the project using Xilinx Vivado HLS, which can be downloaded and installed from [here](https://www.xilinx.com/products/design-tools/vivado/integration/esl-design.html). Alongside Vivado HLS, hls4ml also supports Vitis HLS, Intel HLS, Catapult HLS and has experimental support for the Altera backend. The target back-end can be changed using the argument backend when building the model. ```Python # Use Vivado HLS to synthesize the model diff --git a/docs/api/configuration.rst b/docs/api/configuration.rst index ccd6cf53cf..85f4f8c2aa 100644 --- a/docs/api/configuration.rst +++ b/docs/api/configuration.rst @@ -54,7 +54,7 @@ for Keras models: model, granularity='name', default_precision='fixed<16,6>', - backend='oneAPI') + backend='Altera') or for PyTorch models: diff --git a/docs/backend/oneapi.rst b/docs/backend/altera.rst similarity index 55% rename from docs/backend/oneapi.rst rename to docs/backend/altera.rst index 585bfc27cb..90ec396684 100644 --- a/docs/backend/oneapi.rst +++ b/docs/backend/altera.rst @@ -1,24 +1,30 @@ -====== -oneAPI -====== +=============== +Altera / oneAPI +=============== -The **oneAPI** backend of hls4ml is designed for deploying NNs on Intel/Altera FPGAs. It will eventually -replace the **Quartus** backend, which targeted Intel HLS. (Quartus continues to be used with IP produced by the -**oneAPI** backend.) This section discusses details of the **oneAPI** backend. +The **Altera** backend of hls4ml is designed for deploying neural networks on Intel/Altera FPGAs. +It is based on the SYCL FPGA flow that was previously provided through **Intel oneAPI** and is now continued +by the **Altera HLS IP Gen** toolchain. The functionality and terminology used by hls4ml remain largely unchanged. +(The **oneAPI** backend remains available as a slight modification of the **Altera** backend for use with oneAPI 2025.0.) +Select ``backend='Altera'`` for Altera HLS IP Gen or ``backend='oneAPI'`` for the legacy Intel oneAPI FPGA compiler. -The **oneAPI** code uses SYCL kernels to implement the logic that is deployed on FPGAs. It naturally leads to the +The **Altera HLS/oneAPI** backend is intended to supersede the **Quartus** backend, which targets the discontinued +Intel HLS Compiler. Quartus Prime continues to be used to integrate the IP generated by the Altera HLS IP Gen flow +into FPGA designs. + +The **Altera** backend uses SYCL kernels to implement the logic that is deployed on FPGAs. It naturally leads to the accelerator style of programming. In the SYCL HLS (IP Component) flow, which is currently the only flow supported, the kernel becomes the IP, and the "host code" becomes the testbench. An accelerator flow, with easier deployment on PCIe accelerator boards, is planned to be added in the future. The produced work areas use cmake to build the projects in a style based -`oneAPI-samples `_. +`Altera HLS IP Gen hls-samples `_ (or deprecated `oneAPI-samples `_). The standard ``fpga_emu``, ``report``, ``fpga_sim``, and ``fpga`` make targets are supported. Additionally, ``make lib`` produces the library used for calling the ``predict`` function from hls4ml. The ``compile`` and ``build`` commands in hls4ml interact with the cmake system, so one does not need to manually use the build system, but it there if desired. -The **oneAPI** backend, like the **Quartus** backend, only implements the ``Resource`` strategy for the layers. There +The **Altera** backend, like the **Quartus** backend, only implements the ``Resource`` strategy for the layers. There is no ``Latency`` implementation of any of the layers. Note: currently tracing and external weights (i.e. setting BramFactor) are not supported. @@ -27,7 +33,7 @@ io_parallel and io_stream ========================= As mentioned in the :ref:`I/O Types` section, ``io_parallel`` is for small models, while ``io_stream`` is for -larger models. In ``oneAPI``, there is an additional difference: ``io_stream`` implements each layer on its +larger models. In ``Altera``, there is an additional difference: ``io_stream`` implements each layer on its own ``task_sequence``. Thus, the layers run in parallel, with pipes connecting the inputs and outputs. This is similar in style to the `dataflow` implementation on Vitis HLS, but more explicit. It is also a change relative to the Intel HLS-based ``Quartus`` backend. On the other hand, ``io_parallel`` always uses a single task, diff --git a/docs/backend/quartus.rst b/docs/backend/quartus.rst index 8cde5f97b2..56ea1eb557 100644 --- a/docs/backend/quartus.rst +++ b/docs/backend/quartus.rst @@ -3,10 +3,10 @@ Quartus ======= .. warning:: - The **Quartus** backend is deprecated and will be removed in a future version. Users should migrate to the **oneAPI** backend. + The **Quartus** backend is deprecated and will be removed in a future version. Users should migrate to the **Altera** backend. -The **Quartus** backend of hls4ml is designed for deploying NNs on Intel/Altera FPGAs. It uses the discontinued Intel HLS compiler. The **oneAPI** backend should be preferred for new projects. -The **oneAPI** backend contains the migrated the HLS code from this backend, with significantly better io_stream support, though the **oneAPI** backend does not yet support profiling, tracing, +The **Quartus** backend of hls4ml is designed for deploying NNs on Intel/Altera FPGAs. It uses the discontinued Intel HLS compiler. The **Altera** backend should be preferred for new projects. +The **Altera** backend contains the migrated the HLS code from this backend, with significantly better io_stream support, though the **Altera** backend does not yet support profiling, tracing, or the BramFactor option supported by the **Quartus** backend. Nevertheless, little or no further development is expected for this backend. The **Quartus** backend only implements the ``Resource`` strategy for the layers. There is no ``Latency`` implementation of any of the layers. diff --git a/docs/index.rst b/docs/index.rst index b0d1480bcd..435ed708ae 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -35,7 +35,7 @@ backend/vitis backend/accelerator - backend/oneapi + backend/altera backend/catapult backend/quartus backend/sr diff --git a/docs/intro/setup.rst b/docs/intro/setup.rst index 15d95401bc..431f6dcf34 100644 --- a/docs/intro/setup.rst +++ b/docs/intro/setup.rst @@ -60,7 +60,7 @@ The following Python packages are all optional and are only required if you inte * `Brevitas `_: Based on PyTorch. See `frontend/pytorch <../frontend/pytorch.html>`_ for more details. * `QONNX `_: Based on ONNX. See `frontend/onnx <../frontend/onnx.html>`_ for more details. -Running C simulation from Python requires a C++11-compatible compiler. On Linux, a GCC C++ compiler ``g++`` is required. Any version from a recent Linux should work. On MacOS, when the *clang*-based ``g++`` finds issues with ``ap_types`` headers, one may still need to install GCC, using ``brew`` for example. For the oneAPI backend, one must have `oneAPI=2025.0` (2025.1 is known **not to work**) installed, along with the FPGA compiler, to run C/SYCL simulations. +Running C simulation from Python requires a C++11-compatible compiler. On Linux, a GCC C++ compiler ``g++`` is required. Any version from a recent Linux should work. On MacOS, when the *clang*-based ``g++`` finds issues with ``ap_types`` headers, one may still need to install GCC, using ``brew`` for example. The ``Altera`` backend requires Altera HLS IP Gen, while the ``oneAPI`` backend requires Intel oneAPI 2024.1 to 2025.0 with the FPGA compiler. oneAPI 2025.1 is known **not to work**. Specific functionalities may need additional Python packages. If any needed is missing, ``hls4ml`` will raise an error and prompt you to install the missing packages. @@ -72,7 +72,8 @@ To run FPGA synthesis, installation of following tools is required: * Intel Quartus 20.1 to 21.4 for the synthesis for Intel/Altera FPGAs using the ``Quartus`` backend. -* oneAPI 2024.1 to 2025.0 with the FPGA compiler and recent Intel/Altera Quartus for Intel/Altera FPGAs using the ``oneAPI`` backend. Newer versions of ``OneAPI`` removed FPGA support and **will to work** with ``hls4ml``. +* Altera HLS IP Gen and recent Quartus for Intel/Altera FPGAs using the ``Altera`` backend. +* Intel oneAPI 2024.1 to 2025.0 with the FPGA compiler and recent Intel/Altera Quartus using the ``oneAPI`` backend. Newer versions of oneAPI removed FPGA support and **will not work** with ``hls4ml``. Catapult HLS 2024.1_1 or 2024.2 can be used to synthesize both for ASICs and FPGAs. diff --git a/docs/intro/status.rst b/docs/intro/status.rst index d3942691ca..35dd3f3ab7 100644 --- a/docs/intro/status.rst +++ b/docs/intro/status.rst @@ -44,10 +44,11 @@ Neural network architectures: HLS backends: * Vivado HLS -* Intel HLS +* Intel HLS (deprecated) * Vitis HLS * Catapult HLS -* oneAPI (experimental) +* Altera +* oneAPI (to be superseded by Altera) * XLS (experimental) A summary of the on-going status of the ``hls4ml`` tool is in the table below. @@ -81,7 +82,9 @@ A summary of the on-going status of the ``hls4ml`` tool is in the table below. +-----------------------+-----+-----+--------------+--------+--------+-----+ | Catapult HLS | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | +-----------------------+-----+-----+--------------+--------+--------+-----+ -| oneAPI (experimental) | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | +| Altera | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ++-----------------------+-----+-----+--------------+--------+--------+-----+ +| oneAPI | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | +-----------------------+-----+-----+--------------+--------+--------+-----+ | XLS (experimental) | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | +-----------------------+-----+-----+--------------+--------+--------+-----+ @@ -94,6 +97,7 @@ Other feature notes: - Intel HLS versions 20.1 to 21.4, versions > 21.4 have not been tested. - Vitis HLS versions 2022.2 to 2024.1. Versions > 2024.1 are less tested. - Catapult HLS versions 2024.1_1 to 2024.2 + - Altera HLS IP Gen (Beta) 2026.1 - oneAPI versions 2024.1 to 2025.0. Any future versions are known to not work. * ``hls4ml`` supports Linux [*]_ and requires python >=3.10. hls4ml does not require a specific Linux distribution version and we recommend following the requirements of the HLS tool you are using. diff --git a/docs/ir/attributes.rst b/docs/ir/attributes.rst index 8fecef73aa..b08c5fb1d8 100644 --- a/docs/ir/attributes.rst +++ b/docs/ir/attributes.rst @@ -87,19 +87,19 @@ Backend-specific attributes * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * table_size: int (Default: 1024) * The size of the lookup table used to approximate the function. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * table_t: NamedType (Default: fixed<18,8,TRN,WRAP,0>) * The datatype (precision) used for the values of the lookup table. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS ParametrizedActivation ====================== @@ -143,19 +143,19 @@ Backend-specific attributes * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * table_size: int (Default: 1024) * The size of the lookup table used to approximate the function. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * table_t: NamedType (Default: fixed<18,8,TRN,WRAP,0>) * The datatype (precision) used for the values of the lookup table. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS PReLU ===== @@ -203,19 +203,19 @@ Backend-specific attributes * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * table_size: int (Default: 1024) * The size of the lookup table used to approximate the function. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * table_t: NamedType (Default: fixed<18,8,TRN,WRAP,0>) * The datatype (precision) used for the values of the lookup table. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS Softmax ======= @@ -251,59 +251,59 @@ Backend-specific attributes * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * table_size: int (Default: 1024) * The size of the lookup table used to approximate the function. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * table_t: NamedType (Default: fixed<18,8,TRN,WRAP,0>) * The datatype (precision) used for the values of the lookup table. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * n_outer: int (Default: 1) - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * n_inner: int (Default: 1) - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * implementation: list [latency,stable,argmax,legacy] (Default: stable) * Choice of implementation of softmax function. "latency" provides good latency at the expense of extra resources. performs well on small number of classes. "stable" may require extra clock cycles but has better accuracy. "legacy" is the older implementation which has bad accuracy, but is fast and has low resource use. It is superseded by the "latency" implementation for most applications. "argmax" is a special implementation that can be used if only the output with the highest probability is important. Using this implementation will save resources and clock cycles. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * skip: bool (Default: False) * If enabled, skips the softmax node and returns the raw outputs. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * exp_table_t: NamedType (Default: fixed<18,8,RND,SAT,0>) * The datatype (precision) used for the values of the lookup table. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * inv_table_t: NamedType (Default: fixed<18,8,RND,SAT,0>) * The datatype (precision) used for the values of the lookup table. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * inv_inp_t: NamedType (Default: fixed<18,8,RND,SAT,0>) - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * accum_t: NamedType (Default: fixed<18,8,RND,SAT,0>) - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS TernaryTanh =========== @@ -339,19 +339,19 @@ Backend-specific attributes * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * table_size: int (Default: 1024) * The size of the lookup table used to approximate the function. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * table_t: NamedType (Default: fixed<18,8,TRN,WRAP,0>) * The datatype (precision) used for the values of the lookup table. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS HardActivation ============== @@ -399,19 +399,19 @@ Backend-specific attributes * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * table_size: int (Default: 1024) * The size of the lookup table used to approximate the function. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * table_t: NamedType (Default: fixed<18,8,TRN,WRAP,0>) * The datatype (precision) used for the values of the lookup table. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS Reshape ======= @@ -487,13 +487,13 @@ Backend-specific attributes * The datatype (precision) used to store intermediate results of the computation within the layer. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * reuse_factor: int (Default: 1) * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * strategy: list [latency,resource] (Default: latency) @@ -529,13 +529,13 @@ Backend-specific attributes * The datatype (precision) used to store intermediate results of the computation within the layer. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * reuse_factor: int (Default: 1) * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS Conv1D ====== @@ -597,19 +597,19 @@ Backend-specific attributes * The datatype (precision) used to store intermediate results of the computation within the layer. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * reuse_factor: int (Default: 1) * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * parallelization_factor: int (Default: 1) * The number of outputs computed in parallel. Essentially the number of multiplications of input window with the convolution kernel occuring in parallel. Higher number results in more parallelism (lower latency and II) at the expense of resources used.Currently only supported in io_parallel. - * Available in: Vivado, VivadoAccelerator, Vitis, Catapult, oneAPI + * Available in: Vivado, VivadoAccelerator, Vitis, Catapult, Altera * conv_implementation: list [LineBuffer,Encoded] (Default: LineBuffer) @@ -689,19 +689,19 @@ Backend-specific attributes * The datatype (precision) used to store intermediate results of the computation within the layer. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * reuse_factor: int (Default: 1) * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * parallelization_factor: int (Default: 1) * The number of outputs computed in parallel. Essentially the number of multiplications of input window with the convolution kernel occuring in parallel. Higher number results in more parallelism (lower latency and II) at the expense of resources used.Currently only supported in io_parallel. - * Available in: Vivado, VivadoAccelerator, Vitis, Catapult, oneAPI + * Available in: Vivado, VivadoAccelerator, Vitis, Catapult, Altera * conv_implementation: list [LineBuffer,Encoded] (Default: LineBuffer) @@ -781,19 +781,19 @@ Backend-specific attributes * The datatype (precision) used to store intermediate results of the computation within the layer. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * reuse_factor: int (Default: 1) * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * parallelization_factor: int (Default: 1) * The number of outputs computed in parallel. Essentially the number of multiplications of input window with the convolution kernel occuring in parallel. Higher number results in more parallelism (lower latency and II) at the expense of resources used.Currently only supported in io_parallel. - * Available in: Vivado, VivadoAccelerator, Vitis, Catapult, oneAPI + * Available in: Vivado, VivadoAccelerator, Vitis, Catapult, Altera * conv_implementation: list [LineBuffer,Encoded] (Default: LineBuffer) @@ -867,23 +867,23 @@ Backend-specific attributes --------------------------- * depthwise_accum_t: NamedType - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * pointwise_accum_t: NamedType - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * depthwise_result_t: NamedType - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * depthwise_reuse_factor: int (Default: 1) - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * pointwise_reuse_factor: int (Default: 1) - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * conv_implementation: list [LineBuffer,Encoded] (Default: LineBuffer) @@ -985,19 +985,19 @@ Backend-specific attributes * The datatype (precision) used to store intermediate results of the computation within the layer. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * reuse_factor: int (Default: 1) * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * parallelization_factor: int (Default: 1) * The number of outputs computed in parallel. Essentially the number of multiplications of input window with the convolution kernel occuring in parallel. Higher number results in more parallelism (lower latency and II) at the expense of resources used.Currently only supported in io_parallel. - * Available in: Vivado, VivadoAccelerator, Vitis, Catapult, oneAPI + * Available in: Vivado, VivadoAccelerator, Vitis, Catapult, Altera * conv_implementation: list [LineBuffer,Encoded] (Default: LineBuffer) @@ -1083,23 +1083,23 @@ Backend-specific attributes --------------------------- * depthwise_accum_t: NamedType - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * pointwise_accum_t: NamedType - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * depthwise_result_t: NamedType - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * depthwise_reuse_factor: int (Default: 1) - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * pointwise_reuse_factor: int (Default: 1) - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * conv_implementation: list [LineBuffer,Encoded] (Default: LineBuffer) @@ -1225,19 +1225,19 @@ Backend-specific attributes * The datatype (precision) used to store intermediate results of the computation within the layer. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * reuse_factor: int (Default: 1) * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * parallelization_factor: int (Default: 1) * The number of outputs computed in parallel. Essentially the number of multiplications of input window with the convolution kernel occuring in parallel. Higher number results in more parallelism (lower latency and II) at the expense of resources used.Currently only supported in io_parallel. - * Available in: Vivado, VivadoAccelerator, Vitis, Catapult, oneAPI + * Available in: Vivado, VivadoAccelerator, Vitis, Catapult, Altera * conv_implementation: list [LineBuffer,Encoded] (Default: LineBuffer) @@ -1297,7 +1297,7 @@ Backend-specific attributes * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS Pooling1D ========= @@ -1347,13 +1347,13 @@ Backend-specific attributes * The datatype (precision) used to store intermediate results of the computation within the layer. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * reuse_factor: int (Default: 1) * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * conv_implementation: list [LineBuffer,Encoded] (Default: LineBuffer) @@ -1421,13 +1421,13 @@ Backend-specific attributes * The datatype (precision) used to store intermediate results of the computation within the layer. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * reuse_factor: int (Default: 1) * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * conv_implementation: list [LineBuffer,Encoded] (Default: LineBuffer) @@ -1471,13 +1471,13 @@ Backend-specific attributes * The datatype (precision) used to store intermediate results of the computation within the layer. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * reuse_factor: int (Default: 1) * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS GlobalPooling2D =============== @@ -1517,13 +1517,13 @@ Backend-specific attributes * The datatype (precision) used to store intermediate results of the computation within the layer. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * reuse_factor: int (Default: 1) * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS ZeroPadding1D ============= @@ -1707,7 +1707,7 @@ Backend-specific attributes * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS MatMul ====== @@ -1739,13 +1739,13 @@ Backend-specific attributes * The datatype (precision) used to store intermediate results of the computation within the layer. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * reuse_factor: int (Default: 1) * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS Dot === @@ -1793,13 +1793,13 @@ Backend-specific attributes * The datatype (precision) used to store intermediate results of the computation within the layer. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * reuse_factor: int (Default: 1) * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, Vivado, VivadoAccelerator, VivadoAccelerator, Vitis, Vitis, Quartus, Quartus, Catapult, Catapult, SymbolicExpression, SymbolicExpression, oneAPI, oneAPI, Libero, Libero, XLS, XLS + * Available in: Vivado, Vivado, VivadoAccelerator, VivadoAccelerator, Vitis, Vitis, Quartus, Quartus, Catapult, Catapult, SymbolicExpression, SymbolicExpression, Altera, Altera, Libero, Libero, XLS, XLS Concatenate =========== @@ -1847,7 +1847,7 @@ Backend-specific attributes * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS Resize ====== @@ -1955,7 +1955,7 @@ Backend-specific attributes * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS SimpleRNN ========= @@ -2019,19 +2019,19 @@ Backend-specific attributes * The datatype (precision) used to store intermediate results of the computation within the layer. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * reuse_factor: int (Default: 1) * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * recurrent_reuse_factor: int (Default: 1) * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, oneAPI + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, Altera * static: bool (Default: True) @@ -2043,13 +2043,13 @@ Backend-specific attributes * The size of the lookup table used to approximate the function. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, oneAPI + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, Altera * table_t: NamedType (Default: fixed<18,8,TRN,WRAP,0>) * The datatype (precision) used for the values of the lookup table. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, oneAPI + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, Altera LSTM ==== @@ -2123,19 +2123,19 @@ Backend-specific attributes * The datatype (precision) used to store intermediate results of the computation within the layer. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * reuse_factor: int (Default: 1) * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * recurrent_reuse_factor: int (Default: 1) * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, oneAPI + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, Altera * static: bool (Default: True) @@ -2147,13 +2147,13 @@ Backend-specific attributes * The size of the lookup table used to approximate the function. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, oneAPI + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, Altera * table_t: NamedType (Default: fixed<18,8,TRN,WRAP,0>) * The datatype (precision) used for the values of the lookup table. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, oneAPI + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, Altera GRU === @@ -2229,19 +2229,19 @@ Backend-specific attributes * The datatype (precision) used to store intermediate results of the computation within the layer. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * reuse_factor: int (Default: 1) * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * recurrent_reuse_factor: int (Default: 1) * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, oneAPI + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, Altera * static: bool (Default: True) @@ -2253,13 +2253,13 @@ Backend-specific attributes * The size of the lookup table used to approximate the function. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, oneAPI + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, Altera * table_t: NamedType (Default: fixed<18,8,TRN,WRAP,0>) * The datatype (precision) used for the values of the lookup table. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, oneAPI + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, Altera Bidirectional ============= @@ -2359,13 +2359,13 @@ Backend-specific attributes * The datatype (precision) used to store intermediate results of the computation within the layer. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * reuse_factor: int (Default: 1) * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * forward_reuse_factor: int (Default: 1) @@ -2477,7 +2477,7 @@ Backend-specific attributes * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, Vivado, VivadoAccelerator, VivadoAccelerator, Vitis, Vitis, Quartus, Quartus, Catapult, Catapult, SymbolicExpression, SymbolicExpression, oneAPI, oneAPI, Libero, Libero, XLS, XLS + * Available in: Vivado, Vivado, VivadoAccelerator, VivadoAccelerator, Vitis, Vitis, Quartus, Quartus, Catapult, Catapult, SymbolicExpression, SymbolicExpression, Altera, Altera, Libero, Libero, XLS, XLS GarNetStack =========== @@ -2525,7 +2525,7 @@ Backend-specific attributes * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, Vivado, VivadoAccelerator, VivadoAccelerator, Vitis, Vitis, Quartus, Quartus, Catapult, Catapult, SymbolicExpression, SymbolicExpression, oneAPI, oneAPI, Libero, Libero, XLS, XLS + * Available in: Vivado, Vivado, VivadoAccelerator, VivadoAccelerator, Vitis, Vitis, Quartus, Quartus, Catapult, Catapult, SymbolicExpression, SymbolicExpression, Altera, Altera, Libero, Libero, XLS, XLS Quant ===== @@ -2563,7 +2563,7 @@ Backend-specific attributes * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS BipolarQuant ============ @@ -2641,7 +2641,7 @@ Backend-specific attributes * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS BatchNormOnnx ============= @@ -2673,7 +2673,7 @@ Backend-specific attributes * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS LayerGroup ========== @@ -2791,13 +2791,13 @@ Backend-specific attributes * The datatype (precision) used to store intermediate results of the computation within the layer. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * reuse_factor: int (Default: 1) * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * table_range_power2: int (Default: 0) @@ -2953,7 +2953,7 @@ Backend-specific attributes * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS DACombinational =============== @@ -3145,19 +3145,19 @@ Backend-specific attributes * The datatype (precision) used to store intermediate results of the computation within the layer. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * reuse_factor: int (Default: 1) * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * parallelization_factor: int (Default: 1) * The number of outputs computed in parallel. Essentially the number of multiplications of input window with the convolution kernel occuring in parallel. Higher number results in more parallelism (lower latency and II) at the expense of resources used.Currently only supported in io_parallel. - * Available in: Vivado, VivadoAccelerator, Vitis, Catapult, oneAPI + * Available in: Vivado, VivadoAccelerator, Vitis, Catapult, Altera * conv_implementation: list [LineBuffer,Encoded] (Default: LineBuffer) @@ -3237,19 +3237,19 @@ Backend-specific attributes * The datatype (precision) used to store intermediate results of the computation within the layer. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * reuse_factor: int (Default: 1) * The number of times each multiplier is used by controlling the amount of pipelining/unrolling. Lower number results in more parallelism and lower latency at the expense of the resources used.Reuse factor = 1 corresponds to all multiplications executed in parallel, and hence, the lowest possible latency. - * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, oneAPI, Libero, XLS + * Available in: Vivado, VivadoAccelerator, Vitis, Quartus, Catapult, SymbolicExpression, Altera, Libero, XLS * parallelization_factor: int (Default: 1) * The number of outputs computed in parallel. Essentially the number of multiplications of input window with the convolution kernel occuring in parallel. Higher number results in more parallelism (lower latency and II) at the expense of resources used.Currently only supported in io_parallel. - * Available in: Vivado, VivadoAccelerator, Vitis, Catapult, oneAPI + * Available in: Vivado, VivadoAccelerator, Vitis, Catapult, Altera * conv_implementation: list [LineBuffer,Encoded] (Default: LineBuffer) diff --git a/docs/ir/ir.rst b/docs/ir/ir.rst index 18b0a1c679..19748dbda8 100644 --- a/docs/ir/ir.rst +++ b/docs/ir/ir.rst @@ -53,7 +53,7 @@ io_parallel Parallel IO is applicable to small models that require low latency implementation. Larger models face synthesizability limits very quickly. -In Vivado/Vitis backends, parallel convolution relies on the *im2col* transformation of the input, which turns convolution into a matrix-multiplication task. This task is then implemented as a sequence of matrix-vector multiplications using the routine mentioned above. The ``Latency`` and ``Resource`` strategies refer to the function used for matrix-vector multiplication routine, with ``Resource`` allowing for a slightly larger models to be synthesized. Parallelism can be further controlled via the ``ParallelizationFactor``. Catapult backend in turn uses a direct implementation of convolution via nested loops. The ``Quartus``, ``oneAPI``, and ``Catapult`` backends also implement a ``Winograd`` algorithm choosable by setting the ``implementation`` to ``Winograd`` or ``combination``. Winograd implementation is available for only a handful of filter size configurations, and it is less concerned about bit accuracy and overflow. In certain conditions it can be faster. +In Vivado/Vitis backends, parallel convolution relies on the *im2col* transformation of the input, which turns convolution into a matrix-multiplication task. This task is then implemented as a sequence of matrix-vector multiplications using the routine mentioned above. The ``Latency`` and ``Resource`` strategies refer to the function used for matrix-vector multiplication routine, with ``Resource`` allowing for a slightly larger models to be synthesized. Parallelism can be further controlled via the ``ParallelizationFactor``. Catapult backend in turn uses a direct implementation of convolution via nested loops. The ``Quartus``, ``Altera``, and ``Catapult`` backends also implement a ``Winograd`` algorithm choosable by setting the ``implementation`` to ``Winograd`` or ``combination``. Winograd implementation is available for only a handful of filter size configurations, and it is less concerned about bit accuracy and overflow. In certain conditions it can be faster. io_stream ^^^^^^^^^ diff --git a/hls4ml/backends/__init__.py b/hls4ml/backends/__init__.py index 6febdce076..80d78293f1 100644 --- a/hls4ml/backends/__init__.py +++ b/hls4ml/backends/__init__.py @@ -1,6 +1,8 @@ from hls4ml.backends.backend import Backend, get_available_backends, get_backend, register_backend # noqa: F401 from hls4ml.backends.fpga.fpga_backend import FPGABackend # noqa: F401 from hls4ml.backends.libero.libero_backend import LiberoBackend + +from hls4ml.backends.altera.altera_backend import AlteraBackend # isort: skip from hls4ml.backends.oneapi.oneapi_backend import OneAPIBackend from hls4ml.backends.plugin_loader import load_backend_plugins from hls4ml.backends.quartus.quartus_backend import QuartusBackend @@ -23,6 +25,7 @@ def _register_builtin_backends(): register_backend('Quartus', QuartusBackend) register_backend('Catapult', CatapultBackend) register_backend('SymbolicExpression', SymbolicExpressionBackend) + register_backend('Altera', AlteraBackend) register_backend('oneAPI', OneAPIBackend) register_backend('Coyote', CoyoteBackend) register_backend('Libero', LiberoBackend) diff --git a/hls4ml/backends/oneapi/passes/__init__.py b/hls4ml/backends/altera/__init__.py similarity index 100% rename from hls4ml/backends/oneapi/passes/__init__.py rename to hls4ml/backends/altera/__init__.py diff --git a/hls4ml/backends/altera/altera_backend.py b/hls4ml/backends/altera/altera_backend.py new file mode 100644 index 0000000000..a099a6fbe9 --- /dev/null +++ b/hls4ml/backends/altera/altera_backend.py @@ -0,0 +1,533 @@ +import inspect +import os +import subprocess +from pathlib import Path +from warnings import warn + +import numpy as np + +from hls4ml.backends import FPGABackend +from hls4ml.model.attributes import ConfigurableAttribute, TypeAttribute +from hls4ml.model.flow import register_flow +from hls4ml.model.layers import ( + GRU, + LSTM, + Activation, + Conv1D, + Conv2D, + Dense, + Einsum, + EinsumDense, + Embedding, + Layer, + SimpleRNN, + Softmax, +) +from hls4ml.model.optimizer import get_backend_passes, layer_optimizer +from hls4ml.model.optimizer.optimizer import extract_optimizers_from_path +from hls4ml.model.types import FixedPrecisionType, IntegerPrecisionType, NamedType +from hls4ml.report import parse_altera_report +from hls4ml.utils import attribute_descriptions as descriptions +from hls4ml.utils.einsum_utils import parse_einsum + +# from hls4ml.report import parse_altera_report + + +class AlteraBackend(FPGABackend): + compiler_executable = 'ahls' + + def __init__(self, name='Altera'): + super().__init__(name) + self._register_layer_attributes() + self._register_flows() + + def _init_file_optimizers(self): + file_optimizers = {} + for cls in (FPGABackend, AlteraBackend): + opt_path = os.path.dirname(inspect.getfile(cls)) + '/passes' + module_path = cls.__module__[: cls.__module__.rfind('.')] + '.passes' + file_optimizers.update(extract_optimizers_from_path(opt_path, module_path, self)) + return file_optimizers + + def _register_layer_attributes(self): + # Add RNN-specific recurrent_reuse_factor attribute + rnn_layers = [ + SimpleRNN, + LSTM, + GRU, + ] + + for layer in rnn_layers: + attrs = self.attribute_map.get(layer, []) + attrs.append(ConfigurableAttribute('recurrent_reuse_factor', default=1, description=descriptions.reuse_factor)) + attrs.append(ConfigurableAttribute('table_size', default=1024, description=descriptions.table_size)) + attrs.append(TypeAttribute('table', default=FixedPrecisionType(18, 8), description=descriptions.table_type)) + self.attribute_map[layer] = attrs + + # Add ParallelizationFactor to Conv1D/2D + pf_layers = [ + Conv1D, + Conv2D, + ] + + for layer in pf_layers: + attrs = self.attribute_map.get(layer, []) + attrs.append(ConfigurableAttribute('parallelization_factor', default=1, description=descriptions.conv_pf)) + self.attribute_map[layer] = attrs + + def _register_flows(self): + backend_prefix = self.name.lower() + + def backend_pass(name): + return f'{backend_prefix}:{name}' + + initializers = self._get_layer_initializers() + init_flow = register_flow('init_layers', initializers, requires=['optimize'], backend=self.name) + + streaming_passes = [backend_pass('clone_output')] + streaming_flow = register_flow('streaming', streaming_passes, requires=[init_flow], backend=self.name) + + altera_types = [ + backend_pass('transform_types'), + backend_pass('register_bram_weights'), + backend_pass('apply_resource_strategy'), + backend_pass('apply_winograd_kernel_transformation'), + ] + altera_types_flow = register_flow('specific_types', altera_types, requires=[init_flow], backend=self.name) + + quantization_passes = [ + backend_pass('merge_batch_norm_quantized_tanh'), + backend_pass('quantize_dense_output'), + 'fuse_consecutive_batch_normalization', + backend_pass('xnor_pooling'), + ] + quantization_flow = register_flow('quantization', quantization_passes, requires=[init_flow], backend=self.name) + + optimization_passes = [ + backend_pass('remove_final_reshape'), + backend_pass('optimize_pointwise_conv'), + backend_pass('inplace_parallel_reshape'), + backend_pass('skip_softmax'), + backend_pass('fix_softmax_table_size'), + 'infer_precision_types', + backend_pass('process_fixed_point_quantizer_layer'), + backend_pass('validate_ac_types'), + ] + optimization_flow = register_flow('optimize', optimization_passes, requires=[init_flow], backend=self.name) + + templates = self._get_layer_templates() + template_flow = register_flow('apply_templates', self._get_layer_templates, requires=[init_flow], backend=self.name) + + writer_passes = ['make_stamp', backend_pass('write_hls')] + + self._writer_flow = register_flow('write', writer_passes, requires=[backend_pass('ip')], backend=self.name) + + all_passes = get_backend_passes(self.name) + + extras = [ + # Ideally this should be empty + opt_pass + for opt_pass in all_passes + if opt_pass + not in initializers + + streaming_passes + + altera_types + + quantization_passes + + templates + + optimization_passes + + writer_passes + + [backend_pass('inplace_stream_flatten'), backend_pass('reshape_stream')] # not needed + ] + + if len(extras) > 0: + for opt in extras: + warn(f'WARNING: Optimizer "{opt}" is not part of any flow and will not be executed.') + + ip_flow_requirements = [ + 'optimize', + init_flow, + streaming_flow, + quantization_flow, + optimization_flow, + altera_types_flow, + template_flow, + ] + ip_flow_requirements = list(filter(None, ip_flow_requirements)) + + self._default_flow = register_flow('ip', None, requires=ip_flow_requirements, backend=self.name) + + def get_default_flow(self): + return self._default_flow + + def get_writer_flow(self): + return self._writer_flow + + def create_initial_config( + self, part='Agilex7', clock_period=5, hyperopt_handshake=False, io_type='io_parallel', write_tar=False, **_ + ): + """Create initial configuration of the Altera backend. + + Args: + part (str, optional): The FPGA part to be used. Defaults to 'Agilex7'. + clock_period (int, optional): The clock period in ns. Defaults to 5. + hyperopt_handshake (bool, optional): Should hyper-optimized handshaking be used? Defaults to False + io_type (str, optional): Type of implementation used. One of + 'io_parallel' or 'io_stream'. Defaults to 'io_parallel'. + write_tar (bool, optional): If True, compresses the output directory into a .tar.gz file. Defaults to False. + + Returns: + dict: initial configuration. + """ + + config = {} + + config['Part'] = part if part is not None else 'Agilex7' + config['ClockPeriod'] = clock_period + config['HyperoptHandshake'] = hyperopt_handshake + config['IOType'] = io_type + config['HLSConfig'] = {} + config['WriterConfig'] = { + # TODO: add namespace + 'WriteTar': write_tar, + } + + return config + + def compile(self, model): + """Compile the generated project that can be linked into Python runtime. + + Args: + model (ModelGraph): Model to compile. + + Raises: + Exception: If the project failed to compile + + Returns: + Path: Returns the name of the compiled library. + """ + outdir = Path(Path.cwd(), model.config.get_output_dir()) + self.build(model, build_type='lib', run=False) + + return outdir / f'build/lib{model.config.get_project_name()}-{model.config.get_config_value("Stamp")}.so' + + def build(self, model, build_type='fpga_emu', run=False): + """ + Builds the project using the backend's configured compiler. + + Args: + model (ModelGraph): The model to build + build_type, optional: What to build (e.g. fpga_emu, fpga_sim, fpga, report) + run, optional: Whether to run the testbench + Errors raise exceptions + """ + + # Check software needed is present + outdir = Path(Path.cwd(), model.config.get_output_dir()) + builddir = outdir / 'build' + builddir.mkdir(exist_ok=True) + try: + subprocess.run(f'which {self.compiler_executable}', shell=True, cwd=builddir, check=True) + except subprocess.CalledProcessError: + raise RuntimeError( + f'Could not find {self.compiler_executable}. Please configure the {self.name} toolchain appropriately' + ) + subprocess.run('cmake ..', shell=True, cwd=builddir, check=True) + subprocess.run(f'make {build_type}', shell=True, cwd=builddir, check=True) + + if run: + if build_type not in ('fpga_emu', 'fpga_sim', 'fpga'): + raise ValueError('Running is only supported for fpga_emu, fpga_sim, or fpga builds') + executable = builddir / f'{model.config.get_project_name()}.{build_type}' + subprocess.run(f'{str(executable)}', shell=True, cwd=builddir, check=True) + + return parse_altera_report(model.config.get_output_dir()) + + @layer_optimizer(Layer) + def init_base_layer(self, layer): + reuse_factor = layer.model.config.get_reuse_factor(layer) + layer.set_attr('reuse_factor', reuse_factor) + + target_cycles = layer.model.config.get_target_cycles(layer) + layer.set_attr('target_cycles', target_cycles) + + @layer_optimizer(Dense) + def init_dense(self, layer): + index_t = IntegerPrecisionType(width=1, signed=False) + + layer.set_attr('rfpad', 0) + layer.set_attr('bfpad', 0) + + if layer.model.config.get_compression(layer): + layer.set_attr('strategy', 'compressed') + else: + n_in, n_out = self.get_layer_mult_size(layer) + self.set_closest_reuse_factor(layer, n_in, n_out) + layer.set_attr('strategy', 'resource') + + if layer.model.config.is_resource_strategy(layer): + if layer.model.config.get_compression(layer): + index_t = layer.get_weights('weight').type.index_precision + + layer.set_attr('index_t', NamedType(f'layer{layer.index}_index', index_t)) + + @layer_optimizer(Activation) + def init_activation(self, layer): + if layer.get_attr('activation') == 'tanh': + layer.set_attr('activation', 'dense_tanh') + if layer.get_attr('recurrent_activation') == 'tanh': + layer.set_attr('recurrent_activation', 'dense_tanh') + + @layer_optimizer(Softmax) + def init_softmax(self, layer): + if layer.model.config.get_config_value('IOType') == 'io_parallel': + assert len(layer.get_input_variable().shape) == 1, ( + 'Softmax with io_parallel strategy cannot be used on multidimensional tensors.' + ) + + @layer_optimizer(Embedding) + def init_embed(self, layer): + if layer.attributes['n_in'] is None: + raise Exception('Input length of Embedding layer must be specified.') + + @layer_optimizer(GRU) + def init_gru(self, layer): + reuse_factor = layer.model.config.get_reuse_factor(layer) + layer.set_attr('recurrent_reuse_factor', reuse_factor) + + # Dense multiplication properties + layer.set_attr('rfpad', 0) + layer.set_attr('bfpad', 0) + + index_t = IntegerPrecisionType(width=1, signed=False) + layer.set_attr('index_t', index_t) + + if 'table_t' not in layer.attributes: + layer.set_attr( + 'table_t', NamedType(name=layer.name + '_table_t', precision=FixedPrecisionType(width=18, integer=8)) + ) + if 'table_size' not in layer.attributes: + layer.set_attr('table_size', 1024) + if True: # layer.model.config.is_resource_strategy(layer): ... Altera only supports Dense resource multiplication + n_in, n_out, n_in_recr, n_out_recr = self.get_layer_mult_size(layer) + self.set_closest_reuse_factor(layer, n_in, n_out) + self.set_closest_reuse_factor(layer, n_in_recr, n_out_recr, attribute='recurrent_reuse_factor') + layer.set_attr('strategy', 'resource') + + layer.set_attr('index_t', index_t) + + @layer_optimizer(Conv1D) + def init_conv1d(self, layer): + # This can happen if we assign weights of Dense layer to 1x1 Conv1D + if len(layer.weights['weight'].data.shape) == 2: + layer.weights['weight'].data = np.expand_dims(layer.weights['weight'].data, axis=(0, 1)) + + # Dense matrix multiply properties + layer.set_attr('rfpad', 0) + layer.set_attr('bfpad', 0) + + # Reuse and parallelization factors + layer.set_attr('strategy', 'resource') + n_in, n_out = self.get_layer_mult_size(layer) + self.set_target_reuse_factor(layer) + self.set_closest_reuse_factor(layer, n_in, n_out) + layer.set_attr('parallelization', layer.model.config.get_layer_config_value(layer, 'ParallelizationFactor', 1)) + + # impl_filt_width determines the filter size post-Winograd transformation + layer.set_attr('impl_filt_width', layer.get_attr('filt_width')) + + # Implementation: + # - combination - at compile-time, the decision between Winograd and im2col is made + # - im2col - specifically use im2col + # - Winograd - use Winograd, if possible + layer.set_attr('implementation', layer.model.config.get_layer_config_value(layer, 'Implementation', 'im2col')) + + layer.set_attr( + 'n_partitions', 1 + ) # TODO Not used yet as there is no codegen implementation of CNNs for Altera backend + + @layer_optimizer(Conv2D) + def init_conv2d(self, layer): + # This can happen if we assign weights of Dense layer to 1x1 Conv2D + if len(layer.weights['weight'].data.shape) == 2: + layer.weights['weight'].data = np.expand_dims(layer.weights['weight'].data, axis=(0, 1)) + + # Dense matrix multiply properties + layer.set_attr('rfpad', 0) + layer.set_attr('bfpad', 0) + + # Reuse and parallelization factors + layer.set_attr('strategy', 'resource') + n_in, n_out = self.get_layer_mult_size(layer) + self.set_target_reuse_factor(layer) + self.set_closest_reuse_factor(layer, n_in, n_out) + layer.set_attr('parallelization', layer.model.config.get_layer_config_value(layer, 'ParallelizationFactor', 1)) + + # impl_filt_width & impl_filt_height determine the filter size post-Winograd transformation + layer.set_attr('impl_filt_height', layer.get_attr('filt_height')) + layer.set_attr('impl_filt_width', layer.get_attr('filt_width')) + + # Implementation: + # - combination - at compile-time, the decision between Winograd and im2col is made + # - im2col - specifically use im2col + # - Winograd - use Winograd, if possible + layer.set_attr('implementation', layer.model.config.get_layer_config_value(layer, 'Implementation', 'im2col')) + + layer.set_attr( + 'n_partitions', 1 + ) # TODO Not used yet as there is no codegen implementation of CNNs for Altera backend + + @layer_optimizer(LSTM) + def init_lstm(self, layer): + reuse_factor = layer.model.config.get_reuse_factor(layer) + layer.set_attr('recurrent_reuse_factor', reuse_factor) + + # We don't use RF yet + if True: # layer.model.config.is_resource_strategy(layer): ... Altera only supports Dense resource multiplication + n_in, n_out, n_in_recr, n_out_recr = self.get_layer_mult_size(layer) + self.set_closest_reuse_factor(layer, n_in, n_out) + self.set_closest_reuse_factor(layer, n_in_recr, n_out_recr, attribute='recurrent_reuse_factor') + layer.set_attr('strategy', 'resource') + + # Split weights for easier storage in on-chip memory and implementation in HLS + weights_data = layer.weights['weight'].data + rec_weights_data = layer.weights['recurrent_weight'].data + bias_data = layer.weights['bias'].data + + weight_types = ['i', 'f', 'c', 'o'] + for i in range(0, 4): + layer.add_weights_variable( + name=f'weight_{weight_types[i]}', + var_name=f'kernel_{weight_types[i]}_{{index}}', + data=weights_data[ + 0 : layer.get_attr('n_in'), i * layer.get_attr('n_out') : (i + 1) * layer.get_attr('n_out') + ], + quantizer=layer.get_attr('weight_quantizer'), + compression=None, + ) + layer.add_weights_variable( + name=f'recurrent_weight_{weight_types[i]}', + var_name=f'recurrent_kernel_{weight_types[i]}_{{index}}', + data=rec_weights_data[ + 0 : layer.get_attr('n_out'), i * layer.get_attr('n_out') : (i + 1) * layer.get_attr('n_out') + ], + quantizer=layer.get_attr('weight_quantizer'), + compression=None, + ) + layer.add_weights_variable( + name=f'bias_{weight_types[i]}', + var_name=f'bias_{weight_types[i]}_{{index}}', + data=bias_data[i * layer.get_attr('n_out') : (i + 1) * (layer.get_attr('n_out'))], + quantizer=layer.get_attr('weight_quantizer'), + compression=None, + ) + + @layer_optimizer(SimpleRNN) + def init_simple_rnn(self, layer): + reuse_factor = layer.model.config.get_reuse_factor(layer) + layer.set_attr('recurrent_reuse_factor', reuse_factor) + + # TODO - Consider setting and using RF + + @layer_optimizer(EinsumDense) + def init_einsum_dense(self, layer: EinsumDense) -> None: + kernel: np.ndarray = layer.attributes['weight_data'] + bias: np.ndarray | None = layer.attributes['bias_data'] + equation = layer.attributes['equation'] + inp_shape = layer.attributes['inp_shape'] + out_shape = layer.attributes['out_shape'] + + kernel_shape = kernel.shape + recipe = parse_einsum(equation, inp_shape, kernel_shape) + assert not any(recipe['direct_sum_axis']), ( + 'Do not put direct sum indices (e.g., only appears in one of the operands) in the equation.' + 'Use explicit addition operator before instead.' + ) + inp_tpose_idxs, ker_tpose_idxs = recipe['in_transpose_idxs'] + out_tpose_idxs = recipe['out_transpose_idxs'] + + # Pre-transpose kernel (and bias) to save a transpose in cpp. Shouldn't matter for latency strategy though. + # hls4ml dense acts like i,ij->j + # parser assumes ij,j->i, so we need to transpose the kernel to match + kernel = kernel.transpose(ker_tpose_idxs) + kernel = kernel.reshape(recipe['I'], recipe['L1'], recipe['C']).transpose(0, 2, 1) + + def to_original_kernel(tkernel: np.ndarray) -> np.ndarray: + _kernel = tkernel.transpose(0, 2, 1) + _kernel = _kernel.reshape(tuple(kernel_shape[i] for i in ker_tpose_idxs)) + return _kernel.transpose(np.argsort(ker_tpose_idxs)) + + # TODO: for weight in bram mode (resource), broadcasting bias here shall be avoided. + if bias is not None: + bias = np.broadcast_to(bias, out_shape).transpose(np.argsort(out_tpose_idxs)) + else: + # The automatically created bias is just the last dimension of the output shape + # Which is too small in general for einsum dense. + # The transpose is just to match the shape in case of have real bias, no real effect. + bias = np.zeros(out_shape).transpose(np.argsort(out_tpose_idxs)) + + layer.attributes['weight_data'] = kernel + layer.attributes['to_original_kernel'] = to_original_kernel + layer.attributes['bias_data'] = bias + layer.attributes['inp_tpose_idxs'] = inp_tpose_idxs + layer.attributes['out_tpose_idxs'] = out_tpose_idxs + layer.attributes['out_interpert_shape'] = recipe['out_interpert_shape'] + layer.attributes['n_free_data'] = recipe['L0'] + layer.attributes['n_free_kernel'] = recipe['L1'] + layer.attributes['n_inplace'] = recipe['I'] + layer.attributes['n_contract'] = recipe['C'] + pf = layer.attributes.get('parallelization_factor', recipe['L0']) + layer.attributes['parallelization_factor'] = pf + + layer.add_weights(compression=layer.model.config.get_compression(layer)) + layer.add_bias() + + strategy: str | None = layer.model.config.get_strategy(layer) + if not strategy: + layer.set_attr('strategy', 'latency') + return + if strategy in ('latency', 'resource', 'distributed_arithmetic'): + layer.set_attr('strategy', strategy) + return + warn(f'Invalid strategy "{strategy}" for EinsumDense layer "{layer.name}". Using "latency" strategy instead.') + layer.set_attr('strategy', 'latency') + + @layer_optimizer(Einsum) + def init_einsum(self, layer: Einsum) -> None: + equation = layer.attributes['equation'] + inp0_shape = layer.attributes['inp0_shape'] + inp1_shape = layer.attributes['inp1_shape'] + + recipe = parse_einsum(equation, inp0_shape, inp1_shape) + assert not any(recipe['direct_sum_axis']), ( + 'Do not put direct sum indices (e.g., only appears in one of the operands) in the equation.' + 'Use explicit addition operator before instead.' + ) + inp0_tpose_idxs, inp1_tpose_idxs = recipe['in_transpose_idxs'] + out_tpose_idxs = recipe['out_transpose_idxs'] + + layer.attributes.update(recipe) + layer.attributes['n_free0'] = recipe['L0'] + layer.attributes['n_free1'] = recipe['L1'] + layer.attributes['n_inplace'] = recipe['I'] + layer.attributes['n_contract'] = recipe['C'] + layer.attributes['out_interpert_shape'] = recipe['out_interpert_shape'] + + layer.attributes['inp0_tpose_idxs'] = inp0_tpose_idxs + layer.attributes['inp1_tpose_idxs'] = inp1_tpose_idxs + layer.attributes['out_tpose_idxs'] = out_tpose_idxs + + pf = layer.attributes.get('parallelization_factor', recipe['L0']) + layer.attributes['parallelization_factor'] = pf + + strategy: str | None = layer.model.config.get_strategy(layer) + if not strategy: + layer.set_attr('strategy', 'latency') + return + if strategy.lower() == 'resource': + layer.set_attr('strategy', 'resource') + return + if strategy.lower() in ('latency', 'distributed_arithmetic'): + layer.set_attr('strategy', 'latency') + return + warn(f'Invalid strategy "{strategy}" for Einsum layer "{layer.name}". Using "latency" strategy instead.') + layer.set_attr('strategy', 'latency') diff --git a/hls4ml/backends/oneapi/oneapi_template.py b/hls4ml/backends/altera/altera_template.py similarity index 89% rename from hls4ml/backends/oneapi/oneapi_template.py rename to hls4ml/backends/altera/altera_template.py index 469861429a..7d7f2ad849 100644 --- a/hls4ml/backends/oneapi/oneapi_template.py +++ b/hls4ml/backends/altera/altera_template.py @@ -1,12 +1,12 @@ """ -This package includes oneAPI-specific templates +This package includes Altera-specific templates """ from hls4ml.backends.template import Template class StreamFunctionCallTemplate(Template): - """Base class for the streaming function call templates in oneAPI: provides the 'stream_function_cpp' attribute. + """Base class for the streaming function call templates in Altera: provides the 'stream_function_cpp' attribute. This generally provides the async call to the task sequence that executes the streaming function. Note: the include header files are specified in the regular FunctionCallTemplate, not here. @@ -33,7 +33,7 @@ def transform(self, model, node): class TaskSequenceTemplate(Template): - """Base class for the task sequence definition in oneAPI: provides the 'task_sequence_cpp' attribute. + """Base class for the task sequence definition in Altera: provides the 'task_sequence_cpp' attribute. This defines the task sequence that is then called by the StreamFunctionCallTemplate. Args: diff --git a/hls4ml/backends/oneapi/oneapi_types.py b/hls4ml/backends/altera/altera_types.py similarity index 78% rename from hls4ml/backends/oneapi/oneapi_types.py rename to hls4ml/backends/altera/altera_types.py index 0ecff79034..15f9b1799a 100644 --- a/hls4ml/backends/oneapi/oneapi_types.py +++ b/hls4ml/backends/altera/altera_types.py @@ -1,5 +1,5 @@ """ -This package includes oneAPI-specific customizations to the variable types +This package includes Altera-specific customizations to the variable types """ import numpy as np @@ -39,7 +39,7 @@ def definition_cpp(self): return typestring -class OneAPIACTypeConverter(FPGAPrecisionConverter): +class AlteraACTypeConverter(FPGAPrecisionConverter): def __init__(self): super().__init__( type_map={ @@ -54,8 +54,8 @@ def __init__(self): ) -class OneAPICompressedTypeConverter(TypeDefinition, TypePrecisionConverter): - """Use a tuple for storing a compressed type for oneAPI since it's better supported. (Currently unused)""" +class AlteraCompressedTypeConverter(TypeDefinition, TypePrecisionConverter): + """Use a tuple for storing a compressed type for Altera since it's better supported. (Currently unused)""" def definition_cpp(self): """tuple format is row_index, col_index, weight""" @@ -67,8 +67,8 @@ def convert_precision(self, precision_converter): self.index_precision = precision_converter.convert(self.index_precision) -class OneAPIExponentTypeConverter(TypeDefinition, TypePrecisionConverter): - """Use a pair for storing a exponent type for oneAPI since it's better supported""" +class AlteraExponentTypeConverter(TypeDefinition, TypePrecisionConverter): + """Use a pair for storing a exponent type for Altera since it's better supported""" def definition_cpp(self): cpp_fmt = 'typedef std::pair<{sign}, {precision}> {name};\n' @@ -79,7 +79,7 @@ def convert_precision(self, precision_converter): self.sign = precision_converter.convert(self.sign) -class OneAPIPackedTypeConverter(TypeDefinition, TypePrecisionConverter): +class AlteraPackedTypeConverter(TypeDefinition, TypePrecisionConverter): def definition_cpp(self): n_elem_expr = '/' if self.unpack else '*' return 'typedef nnet::array<{precision}, {n_elem}> {name};\n'.format( @@ -92,21 +92,21 @@ def convert_precision(self, precision_converter): self.precision = precision_converter.convert(self.precision) -class OneAPIHLSTypeConverter(HLSTypeConverter): +class AlteraHLSTypeConverter(HLSTypeConverter): def __init__(self, precision_converter): self.precision_converter = precision_converter self.type_map = { NamedType: NamedTypeConverter, - CompressedType: OneAPICompressedTypeConverter, - ExponentType: OneAPIExponentTypeConverter, - PackedType: OneAPIPackedTypeConverter, + CompressedType: AlteraCompressedTypeConverter, + ExponentType: AlteraExponentTypeConverter, + PackedType: AlteraPackedTypeConverter, } # region ArrayVarable -class OneAPIArrayVariableDefinition(VariableDefinition): +class AlteraArrayVariableDefinition(VariableDefinition): def definition_cpp(self, name_suffix='', as_reference=False): if self.pragma and not isinstance(self.pragma, tuple): return f'[[{self.pragma}]] {self.type.name} {self.name}{name_suffix}' @@ -114,7 +114,7 @@ def definition_cpp(self, name_suffix='', as_reference=False): return f'{self.type.name} {self.name}{name_suffix}' -class OneAPIInplaceArrayVariableDefinition(VariableDefinition): +class AlteraInplaceArrayVariableDefinition(VariableDefinition): def definition_cpp(self): return f'auto& {self.name} = {self.input_var.name}' @@ -157,14 +157,14 @@ def convert(self, tensor_var, pragma='', depth=0, n_pack=1): return tensor_var -class OneAPIArrayVariableConverter(AggregratedArrayVariableConverter): +class AlteraArrayVariableConverter(AggregratedArrayVariableConverter): def __init__(self, type_converter): - super().__init__(type_converter=type_converter, prefix='OneAPI', definition_cls=OneAPIArrayVariableDefinition) + super().__init__(type_converter=type_converter, prefix='Altera', definition_cls=AlteraArrayVariableDefinition) -class OneAPIInplaceArrayVariableConverter(AggregratedArrayVariableConverter): +class AlteraInplaceArrayVariableConverter(AggregratedArrayVariableConverter): def __init__(self, type_converter): - super().__init__(type_converter=type_converter, prefix='OneAPI', definition_cls=OneAPIInplaceArrayVariableDefinition) + super().__init__(type_converter=type_converter, prefix='Altera', definition_cls=AlteraInplaceArrayVariableDefinition) # endregion @@ -172,7 +172,7 @@ def __init__(self, type_converter): # region InterfaceMemberVariable -class OneAPIInterfaceVariableDefinition(VariableDefinition): +class AlteraInterfaceVariableDefinition(VariableDefinition): def definition_cpp(self, name_suffix='', as_reference=False): if self.pragma and not isinstance(self.pragma, tuple): return f'[[{self.pragma}]] {self.type.name} {self.name}{name_suffix}' @@ -182,55 +182,55 @@ def definition_cpp(self, name_suffix='', as_reference=False): def declare_cpp(self, pipe_min_size=0, indent=''): lines = indent + f'class {self.pipe_id};\n' lines += indent + ( - f'using {self.pipe_name} = sycl::ext::intel::experimental::pipe<{self.pipe_id}, ' - + f'{self.type.name}, {pipe_min_size}, PipeProps>;\n' + f'using {self.pipe_name} = hls4ml_sycl_ext::experimental::pipe<{self.pipe_id}, ' + f'{self.type.name}, {pipe_min_size}, PipeProps>;\n' ) return lines -class OneAPIInterfaceVariableConverter(AggregratedArrayVariableConverter): +class AlteraInterfaceVariableConverter(AggregratedArrayVariableConverter): def __init__(self, type_converter): - super().__init__(type_converter=type_converter, prefix='OneAPI', definition_cls=OneAPIInterfaceVariableDefinition) + super().__init__(type_converter=type_converter, prefix='Altera', definition_cls=AlteraInterfaceVariableDefinition) # endregion # region StreamVariable -class OneAPIStreamVariableDefinition(VariableDefinition): +class AlteraStreamVariableDefinition(VariableDefinition): def definition_cpp(self, name_suffix='', as_reference=True): return f'{self.name}{name_suffix}' def declare_cpp(self, indent=''): lines = indent + f'class {self.pipe_id};\n' lines += indent + ( - f'using {self.pipe_name} = sycl::ext::intel::experimental::pipe<{self.pipe_id}, ' - + f'{self.type.name}, {self.pragma[-1]}>;\n' + f'using {self.pipe_name} = hls4ml_sycl_ext::experimental::pipe<{self.pipe_id}, ' + f'{self.type.name}, {self.pragma[-1]}>;\n' ) return lines -class OneAPIInplaceStreamVariableDefinition(VariableDefinition): +class AlteraInplaceStreamVariableDefinition(VariableDefinition): def definition_cpp(self): return f'using {self.name} = {self.input_var.name}' -class OneAPIStreamVariableConverter(AggregratedArrayVariableConverter): +class AlteraStreamVariableConverter(AggregratedArrayVariableConverter): def __init__(self, type_converter): - super().__init__(type_converter=type_converter, prefix='OneAPI', definition_cls=OneAPIStreamVariableDefinition) + super().__init__(type_converter=type_converter, prefix='Altera', definition_cls=AlteraStreamVariableDefinition) -class OneAPIInplaceStreamVariableConverter(AggregratedArrayVariableConverter): +class AlteraInplaceStreamVariableConverter(AggregratedArrayVariableConverter): def __init__(self, type_converter): super().__init__( - type_converter=type_converter, prefix='OneAPI', definition_cls=OneAPIInplaceStreamVariableDefinition + type_converter=type_converter, prefix='Altera', definition_cls=AlteraInplaceStreamVariableDefinition ) # region WeightsVariable -class OneAPIStaticWeightVariableDefinition(VariableDefinition): +class AlteraStaticWeightVariableDefinition(VariableDefinition): def definition_cpp(self, reuse_factor): """Write the appropriate weight definiiton""" # first determine whether to store in register or bram (heuristic) @@ -251,12 +251,12 @@ def definition_cpp(self, reuse_factor): return f'{attribute} {self.type.name} {self.name}' -class OneAPIStaticWeightVariableConverter: +class AlteraStaticWeightVariableConverter: def __init__(self, type_converter): self.type_converter = type_converter def convert(self, weight_var): - if isinstance(weight_var, OneAPIStaticWeightVariableDefinition): # Already converted + if isinstance(weight_var, AlteraStaticWeightVariableDefinition): # Already converted return weight_var weight_var.weight_class = weight_var.__class__.__name__ @@ -267,8 +267,8 @@ def convert(self, weight_var): weight_cls_fqn = weight_var.__class__.__module__ + '.' + weight_var.__class__.__qualname__ weight_var.__class__ = type( - 'OneAPIStaticWeightVariable', - (type(weight_var), OneAPIStaticWeightVariableDefinition), + 'AlteraStaticWeightVariable', + (type(weight_var), AlteraStaticWeightVariableDefinition), {'_wrapped': weight_cls_fqn}, ) return weight_var diff --git a/hls4ml/backends/altera/passes/__init__.py b/hls4ml/backends/altera/passes/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/hls4ml/backends/oneapi/passes/bn_quant.py b/hls4ml/backends/altera/passes/bn_quant.py similarity index 99% rename from hls4ml/backends/oneapi/passes/bn_quant.py rename to hls4ml/backends/altera/passes/bn_quant.py index 7a7055b852..e6eda4c6a2 100644 --- a/hls4ml/backends/oneapi/passes/bn_quant.py +++ b/hls4ml/backends/altera/passes/bn_quant.py @@ -1,7 +1,7 @@ import numpy as np +from hls4ml.backends.altera.altera_template import StreamFunctionCallTemplate, TaskSequenceTemplate from hls4ml.backends.fpga.fpga_layers import BatchNormalizationQuantizedTanh -from hls4ml.backends.oneapi.oneapi_template import StreamFunctionCallTemplate, TaskSequenceTemplate from hls4ml.backends.template import FunctionCallTemplate, LayerConfigTemplate from hls4ml.model.layers import BatchNormalization, register_layer from hls4ml.model.optimizer import OptimizerPass diff --git a/hls4ml/backends/oneapi/passes/clone_templates.py b/hls4ml/backends/altera/passes/clone_templates.py similarity index 85% rename from hls4ml/backends/oneapi/passes/clone_templates.py rename to hls4ml/backends/altera/passes/clone_templates.py index c49fb3e454..27f6e7a8e4 100644 --- a/hls4ml/backends/oneapi/passes/clone_templates.py +++ b/hls4ml/backends/altera/passes/clone_templates.py @@ -1,7 +1,7 @@ -"""The clone templates in the fpga backend are not enough for oneAPI, so this adds the missing parts""" +"""The clone templates in the fpga backend are not enough for Altera, so this adds the missing parts""" +from hls4ml.backends.altera.altera_template import StreamFunctionCallTemplate, TaskSequenceTemplate from hls4ml.backends.fpga.passes.clone import Clone -from hls4ml.backends.oneapi.oneapi_template import StreamFunctionCallTemplate, TaskSequenceTemplate clone_stream_function_template = '{name}.async();' diff --git a/hls4ml/backends/oneapi/passes/convolution_templates.py b/hls4ml/backends/altera/passes/convolution_templates.py similarity index 97% rename from hls4ml/backends/oneapi/passes/convolution_templates.py rename to hls4ml/backends/altera/passes/convolution_templates.py index f1e1abded5..e775343e22 100644 --- a/hls4ml/backends/oneapi/passes/convolution_templates.py +++ b/hls4ml/backends/altera/passes/convolution_templates.py @@ -1,5 +1,5 @@ +from hls4ml.backends.altera.altera_template import StreamFunctionCallTemplate, TaskSequenceTemplate from hls4ml.backends.backend import get_backend -from hls4ml.backends.oneapi.oneapi_template import StreamFunctionCallTemplate, TaskSequenceTemplate from hls4ml.backends.template import FunctionCallTemplate, LayerConfigTemplate from hls4ml.model.layers import Conv1D, Conv2D, Conv2DBatchnorm, DepthwiseConv1D, DepthwiseConv2D @@ -98,7 +98,7 @@ def format(self, node): mult_params = self._default_config_params(node) mult_params['n_in'] = node.get_attr('n_chan') * node.get_attr('filt_width') mult_params['n_out'] = node.get_attr('n_filt') - mult_params['product_type'] = get_backend('oneAPI').product_type( + mult_params['product_type'] = get_backend('Altera').product_type( node.get_input_variable().type.precision, node.get_weights('weight').type.precision ) mult_config = self.mult_template.format(**mult_params) @@ -114,7 +114,7 @@ def __init__(self): def format(self, node): params = self._default_function_params(node) if node.get_attr('data_format') == 'channels_first': - raise RuntimeError('channels_first not supported on oneAPI') + raise RuntimeError('channels_first not supported on Altera') params['data_format'] = 'cl' params['w'] = node.get_weights('weight').name params['b'] = node.get_weights('bias').name @@ -130,7 +130,7 @@ def __init__(self): def format(self, node): params = self._default_function_params(node) if node.get_attr('data_format') == 'channels_first': - raise RuntimeError('channels_first not supported on oneAPI') + raise RuntimeError('channels_first not supported on Altera') params['data_format'] = 'cl' return self.template.format(**params) @@ -215,7 +215,7 @@ def format(self, node): mult_params = self._default_config_params(node) mult_params['n_in'] = node.get_attr('n_chan') * node.get_attr('filt_height') * node.get_attr('filt_width') mult_params['n_out'] = node.get_attr('n_filt') - mult_params['product_type'] = get_backend('oneAPI').product_type( + mult_params['product_type'] = get_backend('Altera').product_type( node.get_input_variable().type.precision, node.get_weights('weight').type.precision ) mult_config = self.mult_template.format(**mult_params) @@ -231,7 +231,7 @@ def __init__(self): def format(self, node): params = self._default_function_params(node) if node.get_attr('data_format') == 'channels_first': - raise RuntimeError('channels_first not supported for oneAPI') + raise RuntimeError('channels_first not supported for Altera') params['data_format'] = 'cl' params['w'] = node.get_weights('weight').name params['b'] = node.get_weights('bias').name @@ -247,7 +247,7 @@ def __init__(self): def format(self, node): params = self._default_function_params(node) if node.get_attr('data_format') == 'channels_first': - raise RuntimeError('channels_first not supported on oneAPI') + raise RuntimeError('channels_first not supported on Altera') params['data_format'] = 'cl' return self.template.format(**params) diff --git a/hls4ml/backends/oneapi/passes/convolution_winograd.py b/hls4ml/backends/altera/passes/convolution_winograd.py similarity index 100% rename from hls4ml/backends/oneapi/passes/convolution_winograd.py rename to hls4ml/backends/altera/passes/convolution_winograd.py diff --git a/hls4ml/backends/oneapi/passes/core_templates.py b/hls4ml/backends/altera/passes/core_templates.py similarity index 98% rename from hls4ml/backends/oneapi/passes/core_templates.py rename to hls4ml/backends/altera/passes/core_templates.py index 9602b2d0fc..6cd0fe23e8 100644 --- a/hls4ml/backends/oneapi/passes/core_templates.py +++ b/hls4ml/backends/altera/passes/core_templates.py @@ -1,5 +1,5 @@ +from hls4ml.backends.altera.altera_template import StreamFunctionCallTemplate, TaskSequenceTemplate from hls4ml.backends.backend import get_backend -from hls4ml.backends.oneapi.oneapi_template import StreamFunctionCallTemplate, TaskSequenceTemplate from hls4ml.backends.template import FunctionCallTemplate, LayerConfigTemplate from hls4ml.model.layers import Activation, BatchNormalization, Dense, HardActivation, ParametrizedActivation, PReLU, Softmax @@ -49,7 +49,7 @@ def format(self, node): params = self._default_config_params(node) params['nzeros'] = node.get_weights('weight').nzeros params['nonzeros'] = node.get_weights('weight').nonzeros - params['product_type'] = get_backend('oneAPI').product_type( + params['product_type'] = get_backend('Altera').product_type( node.get_input_variable().type.precision, node.get_weights('weight').type.precision ) @@ -121,7 +121,7 @@ def __init__(self): def format(self, node): params = self._default_config_params(node) params['n_in'] = node.get_input_variable().size_cpp() - params['product_type'] = get_backend('oneAPI').product_type( + params['product_type'] = get_backend('Altera').product_type( node.get_input_variable().type.precision, node.get_weights('scale').type.precision ) diff --git a/hls4ml/backends/oneapi/passes/einsum.py b/hls4ml/backends/altera/passes/einsum.py similarity index 98% rename from hls4ml/backends/oneapi/passes/einsum.py rename to hls4ml/backends/altera/passes/einsum.py index ed9150486b..293e0b0d1d 100644 --- a/hls4ml/backends/oneapi/passes/einsum.py +++ b/hls4ml/backends/altera/passes/einsum.py @@ -63,7 +63,7 @@ def format(self, node: Einsum): params['n_inplace'] = node.attributes['n_inplace'] inp0_t = node.get_input_variable(node.inputs[0]).type.precision inp1_t = node.get_input_variable(node.inputs[1]).type.precision - params['product_type'] = get_backend('oneapi').product_type(inp0_t, inp1_t) + params['product_type'] = get_backend('altera').product_type(inp0_t, inp1_t) total_mults = params['n_free0'] * params['n_free1'] * params['n_contract'] * params['n_inplace'] params['multiplier_limit'] = ceil(total_mults / params['reuse_factor']) diff --git a/hls4ml/backends/oneapi/passes/einsum_dense.py b/hls4ml/backends/altera/passes/einsum_dense.py similarity index 99% rename from hls4ml/backends/oneapi/passes/einsum_dense.py rename to hls4ml/backends/altera/passes/einsum_dense.py index a6cac36c6f..faad2531c5 100644 --- a/hls4ml/backends/oneapi/passes/einsum_dense.py +++ b/hls4ml/backends/altera/passes/einsum_dense.py @@ -83,7 +83,7 @@ def dense_config(self, node: EinsumDense): dense_params['nzeros'] = '-1; // Not making sense when kernels are switching' dense_params['nonzeros'] = node.get_weights('weight').nonzeros - dense_params['product_type'] = get_backend('oneAPI').product_type( + dense_params['product_type'] = get_backend('Altera').product_type( node.get_input_variable().type.precision, node.get_weights('weight').type.precision, # type: ignore ) diff --git a/hls4ml/backends/oneapi/passes/embedding_templates.py b/hls4ml/backends/altera/passes/embedding_templates.py similarity index 88% rename from hls4ml/backends/oneapi/passes/embedding_templates.py rename to hls4ml/backends/altera/passes/embedding_templates.py index 6fda678f05..3016f2789f 100644 --- a/hls4ml/backends/oneapi/passes/embedding_templates.py +++ b/hls4ml/backends/altera/passes/embedding_templates.py @@ -1,8 +1,8 @@ """ -These are the stream oneAPI templates for embedding layers. The io_parallel ones are in backends/fpga/passes/embedding.py. +These are the stream Altera templates for embedding layers. The io_parallel ones are in backends/fpga/passes/embedding.py. """ -from hls4ml.backends.oneapi.oneapi_template import StreamFunctionCallTemplate, TaskSequenceTemplate +from hls4ml.backends.altera.altera_template import StreamFunctionCallTemplate, TaskSequenceTemplate from hls4ml.model.layers import Embedding embed_task_sequence_template = 'task_sequence> {name};' diff --git a/hls4ml/backends/oneapi/passes/feature_check.py b/hls4ml/backends/altera/passes/feature_check.py similarity index 92% rename from hls4ml/backends/oneapi/passes/feature_check.py rename to hls4ml/backends/altera/passes/feature_check.py index ca7d417900..7a656b292a 100644 --- a/hls4ml/backends/oneapi/passes/feature_check.py +++ b/hls4ml/backends/altera/passes/feature_check.py @@ -10,4 +10,4 @@ def transform(self, model, node): prec_types = [prec_type.precision for prec_type in node.get_layer_precision().values()] prec_types = [prec_type for prec_type in prec_types if isinstance(prec_type, FloatPrecisionType)] if len(prec_types) > 0: - raise Exception(f'Layer "{node.name}" uses ac_float types that are not supported in oneAPI') + raise Exception(f'Layer "{node.name}" uses ac_float types that are not supported in Altera') diff --git a/hls4ml/backends/oneapi/passes/merge_templates.py b/hls4ml/backends/altera/passes/merge_templates.py similarity index 97% rename from hls4ml/backends/oneapi/passes/merge_templates.py rename to hls4ml/backends/altera/passes/merge_templates.py index 9d261e1f74..60e34589b6 100644 --- a/hls4ml/backends/oneapi/passes/merge_templates.py +++ b/hls4ml/backends/altera/passes/merge_templates.py @@ -1,5 +1,5 @@ +from hls4ml.backends.altera.altera_template import StreamFunctionCallTemplate, TaskSequenceTemplate from hls4ml.backends.backend import get_backend -from hls4ml.backends.oneapi.oneapi_template import StreamFunctionCallTemplate, TaskSequenceTemplate from hls4ml.backends.template import FunctionCallTemplate, LayerConfigTemplate from hls4ml.model.layers import Concatenate, Dot, Merge @@ -101,7 +101,7 @@ def format(self, node): params = self._default_config_params(node) params['n_out'] = 1 params['n_in'] = inp1.shape[0] - params['product_type'] = get_backend('oneAPI').product_type(inp1.type.precision, inp2.type.precision) + params['product_type'] = get_backend('Altera').product_type(inp1.type.precision, inp2.type.precision) return self.template.format(**params) diff --git a/hls4ml/backends/oneapi/passes/pointwise.py b/hls4ml/backends/altera/passes/pointwise.py similarity index 97% rename from hls4ml/backends/oneapi/passes/pointwise.py rename to hls4ml/backends/altera/passes/pointwise.py index 0ec51f87b8..b6b965221b 100644 --- a/hls4ml/backends/oneapi/passes/pointwise.py +++ b/hls4ml/backends/altera/passes/pointwise.py @@ -1,12 +1,12 @@ -from hls4ml.backends.fpga.fpga_layers import PointwiseConv1D, PointwiseConv2D -from hls4ml.backends.oneapi.oneapi_template import StreamFunctionCallTemplate, TaskSequenceTemplate -from hls4ml.backends.oneapi.passes.convolution_templates import ( +from hls4ml.backends.altera.altera_template import StreamFunctionCallTemplate, TaskSequenceTemplate +from hls4ml.backends.altera.passes.convolution_templates import ( Conv1DConfigTemplate, Conv2DConfigTemplate, conv1d_config_template, conv2d_config_template, conv_mult_config_template, ) +from hls4ml.backends.fpga.fpga_layers import PointwiseConv1D, PointwiseConv2D from hls4ml.backends.template import FunctionCallTemplate from hls4ml.model.layers import register_layer from hls4ml.model.optimizer import OptimizerPass @@ -52,7 +52,7 @@ def __init__(self): def format(self, node): params = self._default_function_params(node) if node.get_attr('data_format') == 'channels_first': - raise RuntimeError('channels_first not supported on oneAPI') + raise RuntimeError('channels_first not supported on Altera') params['data_format'] = 'cl' params['w'] = node.get_weights('weight').name params['b'] = node.get_weights('bias').name @@ -68,7 +68,7 @@ def __init__(self): def format(self, node): params = self._default_function_params(node) if node.get_attr('data_format') == 'channels_first': - raise RuntimeError('channels_first not supported on oneAPI') + raise RuntimeError('channels_first not supported on Altera') params['data_format'] = 'cl' return self.template.format(**params) @@ -88,7 +88,7 @@ def __init__(self): def format(self, node): params = self._default_function_params(node) if node.get_attr('data_format') == 'channels_first': - raise RuntimeError('channels_first not supported on oneAPI') + raise RuntimeError('channels_first not supported on Altera') params['data_format'] = 'cl' params['w'] = node.get_weights('weight').name params['b'] = node.get_weights('bias').name @@ -104,7 +104,7 @@ def __init__(self): def format(self, node): params = self._default_function_params(node) if node.get_attr('data_format') == 'channels_first': - raise RuntimeError('channels_first not supported on oneAPI') + raise RuntimeError('channels_first not supported on Altera') params['data_format'] = 'cl' return self.template.format(**params) diff --git a/hls4ml/backends/oneapi/passes/pooling_templates.py b/hls4ml/backends/altera/passes/pooling_templates.py similarity index 96% rename from hls4ml/backends/oneapi/passes/pooling_templates.py rename to hls4ml/backends/altera/passes/pooling_templates.py index 97136ed847..bde4fd2455 100644 --- a/hls4ml/backends/oneapi/passes/pooling_templates.py +++ b/hls4ml/backends/altera/passes/pooling_templates.py @@ -1,4 +1,4 @@ -from hls4ml.backends.oneapi.oneapi_template import StreamFunctionCallTemplate, TaskSequenceTemplate +from hls4ml.backends.altera.altera_template import StreamFunctionCallTemplate, TaskSequenceTemplate from hls4ml.backends.template import FunctionCallTemplate, LayerConfigTemplate from hls4ml.model.layers import GlobalPooling1D, GlobalPooling2D, Pooling1D, Pooling2D @@ -120,7 +120,7 @@ def __init__(self): def format(self, node): params = self._default_function_params(node) if node.get_attr('data_format') == 'channels_first': - raise Exception('channels_first not supported for oneAPI') + raise Exception('channels_first not supported for Altera') params['data_format'] = 'cl' return self.templates[node.class_name].format(**params) @@ -138,7 +138,7 @@ def __init__(self): def format(self, node): params = self._default_function_params(node) if node.get_attr('data_format') == 'channels_first': - raise Exception('channels_first not supported for oneAPI') + raise Exception('channels_first not supported for Altera') params['data_format'] = 'cl' return self.templates[node.class_name].format(**params) diff --git a/hls4ml/backends/oneapi/passes/quantization_templates.py b/hls4ml/backends/altera/passes/quantization_templates.py similarity index 91% rename from hls4ml/backends/oneapi/passes/quantization_templates.py rename to hls4ml/backends/altera/passes/quantization_templates.py index c46e174852..857cc6059b 100644 --- a/hls4ml/backends/oneapi/passes/quantization_templates.py +++ b/hls4ml/backends/altera/passes/quantization_templates.py @@ -1,12 +1,12 @@ -from hls4ml.backends.backend import get_backend -from hls4ml.backends.oneapi.oneapi_template import StreamFunctionCallTemplate, TaskSequenceTemplate -from hls4ml.backends.oneapi.passes.core_templates import ( +from hls4ml.backends.altera.altera_template import StreamFunctionCallTemplate, TaskSequenceTemplate +from hls4ml.backends.altera.passes.core_templates import ( batchnorm_config_template, batchnorm_function_template, batchnorm_include_list, batchnorm_stream_function_template, batchnorm_task_sequence_template, ) +from hls4ml.backends.backend import get_backend from hls4ml.backends.template import FunctionCallTemplate, LayerConfigTemplate from hls4ml.model.optimizer.passes.qkeras import ApplyAlpha @@ -19,7 +19,7 @@ def __init__(self): def format(self, node): params = self._default_config_params(node) params['n_in'] = node.get_input_variable().size_cpp() - params['product_type'] = get_backend('oneAPI').product_type( + params['product_type'] = get_backend('Altera').product_type( node.get_input_variable().type.precision, node.get_weights('scale').type.precision ) diff --git a/hls4ml/backends/oneapi/passes/recurrent_templates.py b/hls4ml/backends/altera/passes/recurrent_templates.py similarity index 99% rename from hls4ml/backends/oneapi/passes/recurrent_templates.py rename to hls4ml/backends/altera/passes/recurrent_templates.py index 0799de5b61..b42ac25c5a 100644 --- a/hls4ml/backends/oneapi/passes/recurrent_templates.py +++ b/hls4ml/backends/altera/passes/recurrent_templates.py @@ -1,5 +1,5 @@ +from hls4ml.backends.altera.altera_template import StreamFunctionCallTemplate, TaskSequenceTemplate from hls4ml.backends.backend import get_backend -from hls4ml.backends.oneapi.oneapi_template import StreamFunctionCallTemplate, TaskSequenceTemplate from hls4ml.backends.template import FunctionCallTemplate, LayerConfigTemplate from hls4ml.model.layers import GRU, LSTM, SimpleRNN @@ -145,7 +145,7 @@ def format(self, node): mult_params_x = self._default_config_params(node) mult_params_x['n_in'] = node.get_attr('n_in') mult_params_x['n_out'] = str(node.get_attr('n_out')) + ' * 3' - mult_params_x['product_type'] = get_backend('oneAPI').product_type( + mult_params_x['product_type'] = get_backend('Altera').product_type( node.get_input_variable().type.precision, node.get_weights('weight').type.precision ) mult_params_x['index'] = str(node.index) + '_x' @@ -156,7 +156,7 @@ def format(self, node): mult_params_h['n_in'] = node.get_attr('n_out') mult_params_h['n_out'] = str(node.get_attr('n_out')) + ' * 3' mult_params_h['reuse_factor'] = params['recurrent_reuse_factor'] - mult_params_h['product_type'] = get_backend('oneAPI').product_type( + mult_params_h['product_type'] = get_backend('Altera').product_type( node.get_input_variable().type.precision, node.get_weights('recurrent_weight').type.precision ) mult_params_h['index'] = str(node.index) + '_h' diff --git a/hls4ml/backends/oneapi/passes/reshaping_templates.py b/hls4ml/backends/altera/passes/reshaping_templates.py similarity index 98% rename from hls4ml/backends/oneapi/passes/reshaping_templates.py rename to hls4ml/backends/altera/passes/reshaping_templates.py index 0f07584440..3d8a71b54d 100644 --- a/hls4ml/backends/oneapi/passes/reshaping_templates.py +++ b/hls4ml/backends/altera/passes/reshaping_templates.py @@ -1,6 +1,6 @@ import numpy as np -from hls4ml.backends.oneapi.oneapi_template import StreamFunctionCallTemplate, TaskSequenceTemplate +from hls4ml.backends.altera.altera_template import StreamFunctionCallTemplate, TaskSequenceTemplate from hls4ml.backends.template import FunctionCallTemplate, LayerConfigTemplate from hls4ml.model.layers import Reshape, Resize, Transpose, ZeroPadding1D, ZeroPadding2D from hls4ml.utils.transpose_utils import transpose_config_gen @@ -68,7 +68,7 @@ def __init__(self): def format(self, node): params = self._default_function_params(node) if node.get_attr('data_format') == 'channels_first': - raise Exception('oneAPI only supports channels_last data format') + raise Exception('Altera only supports channels_last data format') params['data_format'] = 'cl' return self.templates[node.class_name].format(**params) @@ -85,7 +85,7 @@ def __init__(self): def format(self, node): params = self._default_function_params(node) if node.get_attr('data_format') == 'channels_first': - raise RuntimeError('channels_first not supported on oneAPI') + raise RuntimeError('channels_first not supported on Altera') params['data_format'] = 'cl' return self.templates[node.class_name].format(**params) diff --git a/hls4ml/backends/oneapi/passes/resource_strategy.py b/hls4ml/backends/altera/passes/resource_strategy.py similarity index 98% rename from hls4ml/backends/oneapi/passes/resource_strategy.py rename to hls4ml/backends/altera/passes/resource_strategy.py index 2d45410706..96c4dd5738 100644 --- a/hls4ml/backends/oneapi/passes/resource_strategy.py +++ b/hls4ml/backends/altera/passes/resource_strategy.py @@ -10,7 +10,7 @@ class ApplyResourceStrategy(OptimizerPass): def match(self, node): node_matches = isinstance(node, (Dense, Conv1D, Conv2D, GRU, LSTM, SimpleRNN)) is_resource_strategy = ( - True # node.get_attr('strategy', '').lower() == 'resource' -> oneAPI only supportr Resource strategy + True # node.get_attr('strategy', '').lower() == 'resource' -> Altera only supportr Resource strategy ) already_transformed = node.get_attr('_weights_transposed', False) is True return node_matches and is_resource_strategy and not already_transformed diff --git a/hls4ml/backends/oneapi/passes/transform_types.py b/hls4ml/backends/altera/passes/transform_types.py similarity index 74% rename from hls4ml/backends/oneapi/passes/transform_types.py rename to hls4ml/backends/altera/passes/transform_types.py index 40e748d276..bb4e05f64a 100644 --- a/hls4ml/backends/oneapi/passes/transform_types.py +++ b/hls4ml/backends/altera/passes/transform_types.py @@ -1,12 +1,12 @@ -from hls4ml.backends.oneapi.oneapi_types import ( - OneAPIACTypeConverter, - OneAPIArrayVariableConverter, - OneAPIHLSTypeConverter, - OneAPIInplaceArrayVariableConverter, - OneAPIInplaceStreamVariableConverter, - OneAPIInterfaceVariableConverter, - OneAPIStaticWeightVariableConverter, - OneAPIStreamVariableConverter, +from hls4ml.backends.altera.altera_types import ( + AlteraACTypeConverter, + AlteraArrayVariableConverter, + AlteraHLSTypeConverter, + AlteraInplaceArrayVariableConverter, + AlteraInplaceStreamVariableConverter, + AlteraInterfaceVariableConverter, + AlteraStaticWeightVariableConverter, + AlteraStreamVariableConverter, ) from hls4ml.model.optimizer import GlobalOptimizerPass from hls4ml.model.types import InplaceTensorVariable @@ -16,13 +16,13 @@ class TransformTypes(GlobalOptimizerPass): def __init__(self): - self.type_converter = OneAPIHLSTypeConverter(precision_converter=OneAPIACTypeConverter()) - self.array_var_converter = OneAPIArrayVariableConverter(type_converter=self.type_converter) - self.inplace_array_var_converter = OneAPIInplaceArrayVariableConverter(type_converter=self.type_converter) - self.interface_var_converter = OneAPIInterfaceVariableConverter(type_converter=self.type_converter) - self.stream_var_converter = OneAPIStreamVariableConverter(type_converter=self.type_converter) - self.inplace_stream_var_converter = OneAPIInplaceStreamVariableConverter(type_converter=self.type_converter) - self.weight_var_converter = OneAPIStaticWeightVariableConverter(type_converter=self.type_converter) + self.type_converter = AlteraHLSTypeConverter(precision_converter=AlteraACTypeConverter()) + self.array_var_converter = AlteraArrayVariableConverter(type_converter=self.type_converter) + self.inplace_array_var_converter = AlteraInplaceArrayVariableConverter(type_converter=self.type_converter) + self.interface_var_converter = AlteraInterfaceVariableConverter(type_converter=self.type_converter) + self.stream_var_converter = AlteraStreamVariableConverter(type_converter=self.type_converter) + self.inplace_stream_var_converter = AlteraInplaceStreamVariableConverter(type_converter=self.type_converter) + self.weight_var_converter = AlteraStaticWeightVariableConverter(type_converter=self.type_converter) def transform(self, model, node): io_type = node.model.config.get_config_value('IOType') diff --git a/hls4ml/backends/fpga/fpga_types.py b/hls4ml/backends/fpga/fpga_types.py index 0523873903..ca24610447 100644 --- a/hls4ml/backends/fpga/fpga_types.py +++ b/hls4ml/backends/fpga/fpga_types.py @@ -97,8 +97,8 @@ def definition_cpp(self): self.saturation_bits, ] if args[0] == 1: - # Currently oneAPI ac_fixed requires at least two bits for both signed and unsigned cases - # Should be fixed in the future once oneAPI supports 1-bit unsigned ac_fixed + # Currently Altera ac_fixed requires at least two bits for both signed and unsigned cases + # Should be fixed in the future once Altera supports 1-bit unsigned ac_fixed args[0] = 2 if args[3] == 'AC_TRN' and args[4] == 'AC_WRAP': # This is the default, so we won't write the full definition for brevity diff --git a/hls4ml/backends/fpga/passes/hgq_proxy_model.py b/hls4ml/backends/fpga/passes/hgq_proxy_model.py index a8135f655a..9e7bf5d692 100644 --- a/hls4ml/backends/fpga/passes/hgq_proxy_model.py +++ b/hls4ml/backends/fpga/passes/hgq_proxy_model.py @@ -16,8 +16,8 @@ def to_apfixed(k, b, i, RND, SAT): def to_acfixed(k, b, i, RND, SAT): k = 'false' if k == 0 else 'true' if b == 1: - # Currently oneAPI ac_fixed requires at least two bits for both signed and unsigned cases - # Should be fixed in the future once oneAPI supports 1-bit unsigned ac_fixed + # Currently Altera ac_fixed requires at least two bits for both signed and unsigned cases + # Should be fixed in the future once Altera supports 1-bit unsigned ac_fixed b = 2 return f'ac_fixed<{b},{i},{k},AC_{RND},AC_{SAT}>' @@ -27,12 +27,12 @@ def generate_mask_fn( ) -> str: """Generate heterogenous quantization mask function, ONLY works for IOType=io_parallel""" assert k.shape[0] == b.shape[0] == i.shape[0] == 1 - assert backend.lower() in ('oneapi', 'quartus', 'vivado', 'vitis'), f'Backend {backend} not tested' + assert backend.lower() in ('altera', 'oneapi', 'quartus', 'vivado', 'vitis'), f'Backend {backend} not tested' Ks, Bs, Is = k[0], b[0], i[0] Ks, Bs, Is = np.broadcast_to(Ks, shape), np.broadcast_to(Bs, shape), np.broadcast_to(Is, shape) Ks, Bs, Is = Ks.ravel(), Bs.ravel(), Is.ravel() masks = [] - to_fixed = to_acfixed if backend.lower() in ['oneapi', 'quartus'] else to_apfixed + to_fixed = to_acfixed if backend.lower() in ['altera', 'oneapi', 'quartus'] else to_apfixed for idx, (k, b, i) in enumerate(zip(Ks, Bs, Is)): if b == 0: fn = f'out[{idx}] = 0;' @@ -41,12 +41,14 @@ def generate_mask_fn( masks.append(f' {fn}') body = '\n'.join(masks) arguments = ( - 'input_t *inp, output_t *out' if backend.lower() not in ['oneapi', 'quartus'] else 'input_t &inp, output_t &out' + 'input_t *inp, output_t *out' + if backend.lower() not in ['altera', 'oneapi', 'quartus'] + else 'input_t &inp, output_t &out' ) mask_fn = f""" template void {name}({arguments}) {{ - {'#pragma HLS INLINE' if backend.lower() not in ['oneapi', 'quartus'] else ''} + {'#pragma HLS INLINE' if backend.lower() not in ['altera', 'oneapi', 'quartus'] else ''} {body} }} diff --git a/hls4ml/backends/oneapi/oneapi_backend.py b/hls4ml/backends/oneapi/oneapi_backend.py index 0c11c16d09..dad02e2981 100644 --- a/hls4ml/backends/oneapi/oneapi_backend.py +++ b/hls4ml/backends/oneapi/oneapi_backend.py @@ -1,513 +1,8 @@ -import subprocess -from pathlib import Path -from warnings import warn +from hls4ml.backends.altera.altera_backend import AlteraBackend -import numpy as np -from hls4ml.backends import FPGABackend -from hls4ml.model.attributes import ConfigurableAttribute, TypeAttribute -from hls4ml.model.flow import register_flow -from hls4ml.model.layers import ( - GRU, - LSTM, - Activation, - Conv1D, - Conv2D, - Dense, - Einsum, - EinsumDense, - Embedding, - Layer, - SimpleRNN, - Softmax, -) -from hls4ml.model.optimizer import get_backend_passes, layer_optimizer -from hls4ml.model.types import FixedPrecisionType, IntegerPrecisionType, NamedType -from hls4ml.report import parse_oneapi_report -from hls4ml.utils import attribute_descriptions as descriptions -from hls4ml.utils.einsum_utils import parse_einsum +class OneAPIBackend(AlteraBackend): + compiler_executable = 'icpx' -# from hls4ml.report import parse_oneapi_report - - -class OneAPIBackend(FPGABackend): def __init__(self): super().__init__('oneAPI') - self._register_layer_attributes() - self._register_flows() - - def _register_layer_attributes(self): - # Add RNN-specific recurrent_reuse_factor attribute - rnn_layers = [ - SimpleRNN, - LSTM, - GRU, - ] - - for layer in rnn_layers: - attrs = self.attribute_map.get(layer, []) - attrs.append(ConfigurableAttribute('recurrent_reuse_factor', default=1, description=descriptions.reuse_factor)) - attrs.append(ConfigurableAttribute('table_size', default=1024, description=descriptions.table_size)) - attrs.append(TypeAttribute('table', default=FixedPrecisionType(18, 8), description=descriptions.table_type)) - self.attribute_map[layer] = attrs - - # Add ParallelizationFactor to Conv1D/2D - pf_layers = [ - Conv1D, - Conv2D, - ] - - for layer in pf_layers: - attrs = self.attribute_map.get(layer, []) - attrs.append(ConfigurableAttribute('parallelization_factor', default=1, description=descriptions.conv_pf)) - self.attribute_map[layer] = attrs - - def _register_flows(self): - initializers = self._get_layer_initializers() - init_flow = register_flow('init_layers', initializers, requires=['optimize'], backend=self.name) - - streaming_passes = ['oneapi:clone_output'] - streaming_flow = register_flow('streaming', streaming_passes, requires=[init_flow], backend=self.name) - - oneapi_types = [ - 'oneapi:transform_types', - 'oneapi:register_bram_weights', - 'oneapi:apply_resource_strategy', - 'oneapi:apply_winograd_kernel_transformation', - ] - oneapi_types_flow = register_flow('specific_types', oneapi_types, requires=[init_flow], backend=self.name) - - quantization_passes = [ - 'oneapi:merge_batch_norm_quantized_tanh', - 'oneapi:quantize_dense_output', - 'fuse_consecutive_batch_normalization', - 'oneapi:xnor_pooling', - ] - quantization_flow = register_flow('quantization', quantization_passes, requires=[init_flow], backend=self.name) - - optimization_passes = [ - 'oneapi:remove_final_reshape', - 'oneapi:optimize_pointwise_conv', - 'oneapi:inplace_parallel_reshape', - 'oneapi:skip_softmax', - 'oneapi:fix_softmax_table_size', - 'infer_precision_types', - 'oneapi:process_fixed_point_quantizer_layer', - 'oneapi:validate_ac_types', - ] - optimization_flow = register_flow('optimize', optimization_passes, requires=[init_flow], backend=self.name) - - templates = self._get_layer_templates() - template_flow = register_flow('apply_templates', self._get_layer_templates, requires=[init_flow], backend=self.name) - - writer_passes = ['make_stamp', 'oneapi:write_hls'] - - self._writer_flow = register_flow('write', writer_passes, requires=['oneapi:ip'], backend=self.name) - - all_passes = get_backend_passes(self.name) - - extras = [ - # Ideally this should be empty - opt_pass - for opt_pass in all_passes - if opt_pass - not in initializers - + streaming_passes - + oneapi_types - + quantization_passes - + templates - + optimization_passes - + writer_passes - + ['oneapi:inplace_stream_flatten', 'oneapi:reshape_stream'] # not needed - ] - - if len(extras) > 0: - for opt in extras: - warn(f'WARNING: Optimizer "{opt}" is not part of any flow and will not be executed.') - - ip_flow_requirements = [ - 'optimize', - init_flow, - streaming_flow, - quantization_flow, - optimization_flow, - oneapi_types_flow, - template_flow, - ] - ip_flow_requirements = list(filter(None, ip_flow_requirements)) - - self._default_flow = register_flow('ip', None, requires=ip_flow_requirements, backend=self.name) - - def get_default_flow(self): - return self._default_flow - - def get_writer_flow(self): - return self._writer_flow - - def create_initial_config( - self, part='Agilex7', clock_period=5, hyperopt_handshake=False, io_type='io_parallel', write_tar=False, **_ - ): - """Create initial configuration of the oneAPI backend. - - Args: - part (str, optional): The FPGA part to be used. Defaults to 'Agilex7'. - clock_period (int, optional): The clock period in ns. Defaults to 5. - hyperopt_handshake (bool, optional): Should hyper-optimized handshaking be used? Defaults to False - io_type (str, optional): Type of implementation used. One of - 'io_parallel' or 'io_stream'. Defaults to 'io_parallel'. - write_tar (bool, optional): If True, compresses the output directory into a .tar.gz file. Defaults to False. - - Returns: - dict: initial configuration. - """ - - config = {} - - config['Part'] = part if part is not None else 'Agilex7' - config['ClockPeriod'] = clock_period - config['HyperoptHandshake'] = hyperopt_handshake - config['IOType'] = io_type - config['HLSConfig'] = {} - config['WriterConfig'] = { - # TODO: add namespace - 'WriteTar': write_tar, - } - - return config - - def compile(self, model): - """Compile the generated project that can be linked into Python runtime. - - Args: - model (ModelGraph): Model to compile. - - Raises: - Exception: If the project failed to compile - - Returns: - Path: Returns the name of the compiled library. - """ - outdir = Path(Path.cwd(), model.config.get_output_dir()) - self.build(model, build_type='lib', run=False) - - return outdir / f'build/lib{model.config.get_project_name()}-{model.config.get_config_value("Stamp")}.so' - - def build(self, model, build_type='fpga_emu', run=False): - """ - Builds the project using Intel DPC++ (oneAPI) compiler. - - Args: - model (ModelGraph): The model to build - build_type, optional: What to build (e.g. fpga_emu, fpga_sim, fpga, report) - run, optional: Whether to run the testbench - Errors raise exceptions - """ - - # Check software needed is present - outdir = Path(Path.cwd(), model.config.get_output_dir()) - builddir = outdir / 'build' - builddir.mkdir(exist_ok=True) - try: - subprocess.run('which icpx', shell=True, cwd=builddir, check=True) - except subprocess.CalledProcessError: - raise RuntimeError('Could not find icpx. Please configure oneAPI appropriately') - subprocess.run('cmake ..', shell=True, cwd=builddir, check=True) - subprocess.run(f'make {build_type}', shell=True, cwd=builddir, check=True) - - if run: - if build_type not in ('fpga_emu', 'fpga_sim', 'fpga'): - raise ValueError('Running is only supported for fpga_emu, fpga_sim, or fpga builds') - executable = builddir / f'{model.config.get_project_name()}.{build_type}' - subprocess.run(f'{str(executable)}', shell=True, cwd=builddir, check=True) - - return parse_oneapi_report(model.config.get_output_dir()) - - @layer_optimizer(Layer) - def init_base_layer(self, layer): - reuse_factor = layer.model.config.get_reuse_factor(layer) - layer.set_attr('reuse_factor', reuse_factor) - - target_cycles = layer.model.config.get_target_cycles(layer) - layer.set_attr('target_cycles', target_cycles) - - @layer_optimizer(Dense) - def init_dense(self, layer): - index_t = IntegerPrecisionType(width=1, signed=False) - - layer.set_attr('rfpad', 0) - layer.set_attr('bfpad', 0) - - if layer.model.config.get_compression(layer): - layer.set_attr('strategy', 'compressed') - else: - n_in, n_out = self.get_layer_mult_size(layer) - self.set_closest_reuse_factor(layer, n_in, n_out) - layer.set_attr('strategy', 'resource') - - if layer.model.config.is_resource_strategy(layer): - if layer.model.config.get_compression(layer): - index_t = layer.get_weights('weight').type.index_precision - - layer.set_attr('index_t', NamedType(f'layer{layer.index}_index', index_t)) - - @layer_optimizer(Activation) - def init_activation(self, layer): - if layer.get_attr('activation') == 'tanh': - layer.set_attr('activation', 'dense_tanh') - if layer.get_attr('recurrent_activation') == 'tanh': - layer.set_attr('recurrent_activation', 'dense_tanh') - - @layer_optimizer(Softmax) - def init_softmax(self, layer): - if layer.model.config.get_config_value('IOType') == 'io_parallel': - assert len(layer.get_input_variable().shape) == 1, ( - 'Softmax with io_parallel strategy cannot be used on multidimensional tensors.' - ) - - @layer_optimizer(Embedding) - def init_embed(self, layer): - if layer.attributes['n_in'] is None: - raise Exception('Input length of Embedding layer must be specified.') - - @layer_optimizer(GRU) - def init_gru(self, layer): - reuse_factor = layer.model.config.get_reuse_factor(layer) - layer.set_attr('recurrent_reuse_factor', reuse_factor) - - # Dense multiplication properties - layer.set_attr('rfpad', 0) - layer.set_attr('bfpad', 0) - - index_t = IntegerPrecisionType(width=1, signed=False) - layer.set_attr('index_t', index_t) - - if 'table_t' not in layer.attributes: - layer.set_attr( - 'table_t', NamedType(name=layer.name + '_table_t', precision=FixedPrecisionType(width=18, integer=8)) - ) - if 'table_size' not in layer.attributes: - layer.set_attr('table_size', 1024) - if True: # layer.model.config.is_resource_strategy(layer): ... oneAPI only supports Dense resource multiplication - n_in, n_out, n_in_recr, n_out_recr = self.get_layer_mult_size(layer) - self.set_closest_reuse_factor(layer, n_in, n_out) - self.set_closest_reuse_factor(layer, n_in_recr, n_out_recr, attribute='recurrent_reuse_factor') - layer.set_attr('strategy', 'resource') - - layer.set_attr('index_t', index_t) - - @layer_optimizer(Conv1D) - def init_conv1d(self, layer): - # This can happen if we assign weights of Dense layer to 1x1 Conv1D - if len(layer.weights['weight'].data.shape) == 2: - layer.weights['weight'].data = np.expand_dims(layer.weights['weight'].data, axis=(0, 1)) - - # Dense matrix multiply properties - layer.set_attr('rfpad', 0) - layer.set_attr('bfpad', 0) - - # Reuse and parallelization factors - layer.set_attr('strategy', 'resource') - n_in, n_out = self.get_layer_mult_size(layer) - self.set_target_reuse_factor(layer) - self.set_closest_reuse_factor(layer, n_in, n_out) - layer.set_attr('parallelization', layer.model.config.get_layer_config_value(layer, 'ParallelizationFactor', 1)) - - # impl_filt_width determines the filter size post-Winograd transformation - layer.set_attr('impl_filt_width', layer.get_attr('filt_width')) - - # Implementation: - # - combination - at compile-time, the decision between Winograd and im2col is made - # - im2col - specifically use im2col - # - Winograd - use Winograd, if possible - layer.set_attr('implementation', layer.model.config.get_layer_config_value(layer, 'Implementation', 'im2col')) - - layer.set_attr( - 'n_partitions', 1 - ) # TODO Not used yet as there is no codegen implementation of CNNs for oneAPI backend - - @layer_optimizer(Conv2D) - def init_conv2d(self, layer): - # This can happen if we assign weights of Dense layer to 1x1 Conv2D - if len(layer.weights['weight'].data.shape) == 2: - layer.weights['weight'].data = np.expand_dims(layer.weights['weight'].data, axis=(0, 1)) - - # Dense matrix multiply properties - layer.set_attr('rfpad', 0) - layer.set_attr('bfpad', 0) - - # Reuse and parallelization factors - layer.set_attr('strategy', 'resource') - n_in, n_out = self.get_layer_mult_size(layer) - self.set_target_reuse_factor(layer) - self.set_closest_reuse_factor(layer, n_in, n_out) - layer.set_attr('parallelization', layer.model.config.get_layer_config_value(layer, 'ParallelizationFactor', 1)) - - # impl_filt_width & impl_filt_height determine the filter size post-Winograd transformation - layer.set_attr('impl_filt_height', layer.get_attr('filt_height')) - layer.set_attr('impl_filt_width', layer.get_attr('filt_width')) - - # Implementation: - # - combination - at compile-time, the decision between Winograd and im2col is made - # - im2col - specifically use im2col - # - Winograd - use Winograd, if possible - layer.set_attr('implementation', layer.model.config.get_layer_config_value(layer, 'Implementation', 'im2col')) - - layer.set_attr( - 'n_partitions', 1 - ) # TODO Not used yet as there is no codegen implementation of CNNs for oneAPI backend - - @layer_optimizer(LSTM) - def init_lstm(self, layer): - reuse_factor = layer.model.config.get_reuse_factor(layer) - layer.set_attr('recurrent_reuse_factor', reuse_factor) - - # We don't use RF yet - if True: # layer.model.config.is_resource_strategy(layer): ... oneAPI only supports Dense resource multiplication - n_in, n_out, n_in_recr, n_out_recr = self.get_layer_mult_size(layer) - self.set_closest_reuse_factor(layer, n_in, n_out) - self.set_closest_reuse_factor(layer, n_in_recr, n_out_recr, attribute='recurrent_reuse_factor') - layer.set_attr('strategy', 'resource') - - # Split weights for easier storage in on-chip memory and implementation in HLS - weights_data = layer.weights['weight'].data - rec_weights_data = layer.weights['recurrent_weight'].data - bias_data = layer.weights['bias'].data - - weight_types = ['i', 'f', 'c', 'o'] - for i in range(0, 4): - layer.add_weights_variable( - name=f'weight_{weight_types[i]}', - var_name=f'kernel_{weight_types[i]}_{{index}}', - data=weights_data[ - 0 : layer.get_attr('n_in'), i * layer.get_attr('n_out') : (i + 1) * layer.get_attr('n_out') - ], - quantizer=layer.get_attr('weight_quantizer'), - compression=None, - ) - layer.add_weights_variable( - name=f'recurrent_weight_{weight_types[i]}', - var_name=f'recurrent_kernel_{weight_types[i]}_{{index}}', - data=rec_weights_data[ - 0 : layer.get_attr('n_out'), i * layer.get_attr('n_out') : (i + 1) * layer.get_attr('n_out') - ], - quantizer=layer.get_attr('weight_quantizer'), - compression=None, - ) - layer.add_weights_variable( - name=f'bias_{weight_types[i]}', - var_name=f'bias_{weight_types[i]}_{{index}}', - data=bias_data[i * layer.get_attr('n_out') : (i + 1) * (layer.get_attr('n_out'))], - quantizer=layer.get_attr('weight_quantizer'), - compression=None, - ) - - @layer_optimizer(SimpleRNN) - def init_simple_rnn(self, layer): - reuse_factor = layer.model.config.get_reuse_factor(layer) - layer.set_attr('recurrent_reuse_factor', reuse_factor) - - # TODO - Consider setting and using RF - - @layer_optimizer(EinsumDense) - def init_einsum_dense(self, layer: EinsumDense) -> None: - kernel: np.ndarray = layer.attributes['weight_data'] - bias: np.ndarray | None = layer.attributes['bias_data'] - equation = layer.attributes['equation'] - inp_shape = layer.attributes['inp_shape'] - out_shape = layer.attributes['out_shape'] - - kernel_shape = kernel.shape - recipe = parse_einsum(equation, inp_shape, kernel_shape) - assert not any(recipe['direct_sum_axis']), ( - 'Do not put direct sum indices (e.g., only appears in one of the operands) in the equation.' - 'Use explicit addition operator before instead.' - ) - inp_tpose_idxs, ker_tpose_idxs = recipe['in_transpose_idxs'] - out_tpose_idxs = recipe['out_transpose_idxs'] - - # Pre-transpose kernel (and bias) to save a transpose in cpp. Shouldn't matter for latency strategy though. - # hls4ml dense acts like i,ij->j - # parser assumes ij,j->i, so we need to transpose the kernel to match - kernel = kernel.transpose(ker_tpose_idxs) - kernel = kernel.reshape(recipe['I'], recipe['L1'], recipe['C']).transpose(0, 2, 1) - - def to_original_kernel(tkernel: np.ndarray) -> np.ndarray: - _kernel = tkernel.transpose(0, 2, 1) - _kernel = _kernel.reshape(tuple(kernel_shape[i] for i in ker_tpose_idxs)) - return _kernel.transpose(np.argsort(ker_tpose_idxs)) - - # TODO: for weight in bram mode (resource), broadcasting bias here shall be avoided. - if bias is not None: - bias = np.broadcast_to(bias, out_shape).transpose(np.argsort(out_tpose_idxs)) - else: - # The automatically created bias is just the last dimension of the output shape - # Which is too small in general for einsum dense. - # The transpose is just to match the shape in case of have real bias, no real effect. - bias = np.zeros(out_shape).transpose(np.argsort(out_tpose_idxs)) - - layer.attributes['weight_data'] = kernel - layer.attributes['to_original_kernel'] = to_original_kernel - layer.attributes['bias_data'] = bias - layer.attributes['inp_tpose_idxs'] = inp_tpose_idxs - layer.attributes['out_tpose_idxs'] = out_tpose_idxs - layer.attributes['out_interpert_shape'] = recipe['out_interpert_shape'] - layer.attributes['n_free_data'] = recipe['L0'] - layer.attributes['n_free_kernel'] = recipe['L1'] - layer.attributes['n_inplace'] = recipe['I'] - layer.attributes['n_contract'] = recipe['C'] - pf = layer.attributes.get('parallelization_factor', recipe['L0']) - layer.attributes['parallelization_factor'] = pf - - layer.add_weights(compression=layer.model.config.get_compression(layer)) - layer.add_bias() - - strategy: str | None = layer.model.config.get_strategy(layer) - if not strategy: - layer.set_attr('strategy', 'latency') - return - if strategy in ('latency', 'resource', 'distributed_arithmetic'): - layer.set_attr('strategy', strategy) - return - warn(f'Invalid strategy "{strategy}" for EinsumDense layer "{layer.name}". Using "latency" strategy instead.') - layer.set_attr('strategy', 'latency') - - @layer_optimizer(Einsum) - def init_einsum(self, layer: Einsum) -> None: - equation = layer.attributes['equation'] - inp0_shape = layer.attributes['inp0_shape'] - inp1_shape = layer.attributes['inp1_shape'] - - recipe = parse_einsum(equation, inp0_shape, inp1_shape) - assert not any(recipe['direct_sum_axis']), ( - 'Do not put direct sum indices (e.g., only appears in one of the operands) in the equation.' - 'Use explicit addition operator before instead.' - ) - inp0_tpose_idxs, inp1_tpose_idxs = recipe['in_transpose_idxs'] - out_tpose_idxs = recipe['out_transpose_idxs'] - - layer.attributes.update(recipe) - layer.attributes['n_free0'] = recipe['L0'] - layer.attributes['n_free1'] = recipe['L1'] - layer.attributes['n_inplace'] = recipe['I'] - layer.attributes['n_contract'] = recipe['C'] - layer.attributes['out_interpert_shape'] = recipe['out_interpert_shape'] - - layer.attributes['inp0_tpose_idxs'] = inp0_tpose_idxs - layer.attributes['inp1_tpose_idxs'] = inp1_tpose_idxs - layer.attributes['out_tpose_idxs'] = out_tpose_idxs - - pf = layer.attributes.get('parallelization_factor', recipe['L0']) - layer.attributes['parallelization_factor'] = pf - - strategy: str | None = layer.model.config.get_strategy(layer) - if not strategy: - layer.set_attr('strategy', 'latency') - return - if strategy.lower() == 'resource': - layer.set_attr('strategy', 'resource') - return - if strategy.lower() in ('latency', 'distributed_arithmetic'): - layer.set_attr('strategy', 'latency') - return - warn(f'Invalid strategy "{strategy}" for Einsum layer "{layer.name}". Using "latency" strategy instead.') - layer.set_attr('strategy', 'latency') diff --git a/hls4ml/backends/vivado/passes/distributed_arithmetic.py b/hls4ml/backends/vivado/passes/distributed_arithmetic.py index d8672104de..6a3b02450f 100644 --- a/hls4ml/backends/vivado/passes/distributed_arithmetic.py +++ b/hls4ml/backends/vivado/passes/distributed_arithmetic.py @@ -438,7 +438,7 @@ def transform(self, model: 'ModelGraph', node: DACombinational): backend = model.config.get_config_value('Backend').lower() if backend in ('vitis', 'vivado'): flavor = 'vitis' - elif backend == 'oneapi': + elif backend in ('altera', 'oneapi'): flavor = 'oneapi' else: raise ValueError(f'Unsupported backend {backend} for DACombinational layer.') diff --git a/hls4ml/report/__init__.py b/hls4ml/report/__init__.py index 4d3641a5ac..05f3b2d7d6 100644 --- a/hls4ml/report/__init__.py +++ b/hls4ml/report/__init__.py @@ -1,13 +1,13 @@ +from hls4ml.report.altera_report import ( + parse_altera_report, # noqa: F401 + print_altera_report, # noqa: F401 +) from hls4ml.report.catapult_report import ( parse_catapult_report, # noqa: F401 qofr, # noqa: F401 read_catapult_report, # noqa: F401 ) from hls4ml.report.libero_report import parse_libero_report # noqa: F401 -from hls4ml.report.oneapi_report import ( - parse_oneapi_report, # noqa: F401 - print_oneapi_report, # noqa: F401 -) from hls4ml.report.quartus_report import ( parse_quartus_report, # noqa: F401 read_quartus_report, # noqa: F401 diff --git a/hls4ml/report/oneapi_report.py b/hls4ml/report/altera_report.py similarity index 97% rename from hls4ml/report/oneapi_report.py rename to hls4ml/report/altera_report.py index fc62f0f15a..fcfbff6ed4 100644 --- a/hls4ml/report/oneapi_report.py +++ b/hls4ml/report/altera_report.py @@ -4,7 +4,7 @@ import re -def _convert_to_oneapi_naming(s): +def _convert_to_altera_naming(s): s2 = s.lower() # Capitalize the first letter @@ -54,7 +54,7 @@ def _parse_single_report(prjDir): # PathSimDataJson = PathJson + 'simulation_raw.ndjson' targetName, makeType, _ = os.path.basename(prjDir).rsplit('.', 2) - simTask = _convert_to_oneapi_naming(targetName) + simTask = _convert_to_altera_naming(targetName) # if targetName not in report: # report[targetName] = {} @@ -137,9 +137,9 @@ def _parse_single_report(prjDir): return report -def parse_oneapi_report(hls_dir): +def parse_altera_report(hls_dir): """ - Parse a report from a given oneAPI project as a dictionary. + Parse a report from a given Altera project as a dictionary. Args: hls_dir (string): The directory where the project is found @@ -159,9 +159,9 @@ def parse_oneapi_report(hls_dir): return report -def print_oneapi_report(report_dict): +def print_altera_report(report_dict): """ - Prints the oneAPI report dictionary as a table. + Prints the Altera report dictionary as a table. Args: report_dict (dictionary): The report dictionary, containing latency, resource usage etc. diff --git a/hls4ml/templates/oneapi/CMakeLists.txt b/hls4ml/templates/altera/CMakeLists.txt similarity index 98% rename from hls4ml/templates/oneapi/CMakeLists.txt rename to hls4ml/templates/altera/CMakeLists.txt index 5bce2aaf84..9a4576f211 100644 --- a/hls4ml/templates/oneapi/CMakeLists.txt +++ b/hls4ml/templates/altera/CMakeLists.txt @@ -1,17 +1,11 @@ -# Direct CMake to use icpx rather than the default C++ compiler/linker on Linux -# and icx-cl on Windows -if(UNIX) - set(CMAKE_CXX_COMPILER icpx) -else() # Windows - include (CMakeForceCompiler) - CMAKE_FORCE_CXX_COMPILER (icx-cl IntelDPCPP) - include (Platform/Windows-Clang) -endif() +# hls-fpga-machine-learning insert compiler cmake_minimum_required (VERSION 3.7.2) project(myproject CXX) +# hls-fpga-machine-learning insert compiler definitions + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) diff --git a/hls4ml/templates/oneapi/exception_handler.hpp b/hls4ml/templates/altera/exception_handler.hpp similarity index 100% rename from hls4ml/templates/oneapi/exception_handler.hpp rename to hls4ml/templates/altera/exception_handler.hpp diff --git a/hls4ml/templates/oneapi/firmware/defines.h b/hls4ml/templates/altera/firmware/defines.h similarity index 69% rename from hls4ml/templates/oneapi/firmware/defines.h rename to hls4ml/templates/altera/firmware/defines.h index 05de507dcd..68da9683c9 100644 --- a/hls4ml/templates/oneapi/firmware/defines.h +++ b/hls4ml/templates/altera/firmware/defines.h @@ -1,10 +1,7 @@ #ifndef DEFINES_H_ #define DEFINES_H_ -#include -#include -#include -#include +#include "nnet_utils/hls4ml_sycl.h" // Include nnet::array - a custom array-like struct, mainly used with io_stream #include "nnet_utils/nnet_types.h" diff --git a/hls4ml/templates/oneapi/firmware/myproject.cpp b/hls4ml/templates/altera/firmware/myproject.cpp similarity index 83% rename from hls4ml/templates/oneapi/firmware/myproject.cpp rename to hls4ml/templates/altera/firmware/myproject.cpp index 06e7d3fe37..d8e383db92 100644 --- a/hls4ml/templates/oneapi/firmware/myproject.cpp +++ b/hls4ml/templates/altera/firmware/myproject.cpp @@ -1,13 +1,12 @@ #include "myproject.h" #include "parameters.h" -#include // hls-fpga-machine-learning insert weights // The inter-task pipes need to be declared in the global scope // hls-fpga-machine-learning insert inter-task pipes -using sycl::ext::intel::experimental::task_sequence; +using hls4ml_sycl_ext::experimental::task_sequence; void MyProject::operator()() const { // **************************************** diff --git a/hls4ml/templates/oneapi/firmware/myproject.h b/hls4ml/templates/altera/firmware/myproject.h similarity index 67% rename from hls4ml/templates/oneapi/firmware/myproject.h rename to hls4ml/templates/altera/firmware/myproject.h index 082ae5dc8c..e371cf91e0 100644 --- a/hls4ml/templates/oneapi/firmware/myproject.h +++ b/hls4ml/templates/altera/firmware/myproject.h @@ -6,7 +6,8 @@ // This file defines the interface to the kernel // currently this is fixed -using PipeProps = decltype(sycl::ext::oneapi::experimental::properties(sycl::ext::intel::experimental::ready_latency<0>)); + +using PipeProps = decltype(sycl::ext::oneapi::experimental::properties(hls4ml_sycl_ext::experimental::ready_latency<0>)); // Need to declare the input and output pipes @@ -19,8 +20,8 @@ struct MyProject { // kernel property method to config invocation interface auto get(sycl::ext::oneapi::experimental::properties_tag) { - return sycl::ext::oneapi::experimental::properties{sycl::ext::intel::experimental::streaming_interface<>, - sycl::ext::intel::experimental::pipelined<>}; + return sycl::ext::oneapi::experimental::properties{hls4ml_sycl_ext::experimental::streaming_interface<>, + hls4ml_sycl_ext::experimental::pipelined<>}; } SYCL_EXTERNAL void operator()() const; diff --git a/hls4ml/templates/altera/firmware/nnet_utils/hls4ml_sycl.h b/hls4ml/templates/altera/firmware/nnet_utils/hls4ml_sycl.h new file mode 100644 index 0000000000..62fa180dc6 --- /dev/null +++ b/hls4ml/templates/altera/firmware/nnet_utils/hls4ml_sycl.h @@ -0,0 +1,28 @@ +#ifndef HLS4ML_SYCL_H_ +#define HLS4ML_SYCL_H_ + +#include + +#ifdef HLS4ML_ONEAPI +#include +#include +#include +#include +#include + +#if __INTEL_CLANG_COMPILER < 20250000 +#include +#endif + +namespace hls4ml_sycl_ext = sycl::ext::intel; +#else +#include +#include +#include +#include +#include + +namespace hls4ml_sycl_ext = sycl::ext::altera; +#endif + +#endif diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_activation.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_activation.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_activation.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_activation.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_activation_stream.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_activation_stream.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_activation_stream.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_activation_stream.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_batchnorm.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_batchnorm.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_batchnorm.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_batchnorm.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_batchnorm_stream.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_batchnorm_stream.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_batchnorm_stream.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_batchnorm_stream.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_code_gen.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_code_gen.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_code_gen.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_code_gen.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_common.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_common.h similarity index 93% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_common.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_common.h index f37a61cb0c..2ab42b557e 100644 --- a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_common.h +++ b/hls4ml/templates/altera/firmware/nnet_utils/nnet_common.h @@ -1,10 +1,8 @@ #ifndef NNET_COMMON_H_ #define NNET_COMMON_H_ +#include "hls4ml_sycl.h" #include "nnet_helpers.h" -#include -#include -#include typedef ac_fixed<16, 6> table_default_t; diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_conv1d.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_conv1d.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_conv1d.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_conv1d.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_conv1d_resource.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_conv1d_resource.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_conv1d_resource.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_conv1d_resource.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_conv1d_stream.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_conv1d_stream.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_conv1d_stream.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_conv1d_stream.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_conv2d.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_conv2d.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_conv2d.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_conv2d.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_conv2d_resource.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_conv2d_resource.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_conv2d_resource.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_conv2d_resource.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_conv2d_stream.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_conv2d_stream.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_conv2d_stream.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_conv2d_stream.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_dense.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_dense.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_dense.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_dense.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_dense_stream.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_dense_stream.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_dense_stream.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_dense_stream.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_depthconv1d.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_depthconv1d.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_depthconv1d.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_depthconv1d.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_depthconv1d_resource.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_depthconv1d_resource.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_depthconv1d_resource.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_depthconv1d_resource.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_depthconv2d.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_depthconv2d.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_depthconv2d.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_depthconv2d.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_depthconv2d_resource.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_depthconv2d_resource.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_depthconv2d_resource.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_depthconv2d_resource.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_einsum.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_einsum.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_einsum.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_einsum.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_einsum_dense.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_einsum_dense.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_einsum_dense.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_einsum_dense.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_embed.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_embed.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_embed.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_embed.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_embed_stream.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_embed_stream.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_embed_stream.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_embed_stream.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_helpers.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_helpers.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_helpers.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_helpers.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_merge.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_merge.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_merge.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_merge.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_merge_stream.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_merge_stream.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_merge_stream.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_merge_stream.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_mult.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_mult.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_mult.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_mult.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_padding.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_padding.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_padding.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_padding.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_padding_stream.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_padding_stream.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_padding_stream.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_padding_stream.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_pooling.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_pooling.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_pooling.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_pooling.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_pooling_stream.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_pooling_stream.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_pooling_stream.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_pooling_stream.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_printf.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_printf.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_printf.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_printf.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_recurrent.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_recurrent.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_recurrent.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_recurrent.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_recurrent_activation.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_recurrent_activation.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_recurrent_activation.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_recurrent_activation.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_recurrent_stream.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_recurrent_stream.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_recurrent_stream.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_recurrent_stream.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_resize.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_resize.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_resize.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_resize.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_resize_stream.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_resize_stream.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_resize_stream.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_resize_stream.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_stream.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_stream.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_stream.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_stream.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_transpose.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_transpose.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_transpose.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_transpose.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_transpose_stream.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_transpose_stream.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_transpose_stream.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_transpose_stream.h diff --git a/hls4ml/templates/oneapi/firmware/nnet_utils/nnet_types.h b/hls4ml/templates/altera/firmware/nnet_utils/nnet_types.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/nnet_utils/nnet_types.h rename to hls4ml/templates/altera/firmware/nnet_utils/nnet_types.h diff --git a/hls4ml/templates/oneapi/firmware/parameters.h b/hls4ml/templates/altera/firmware/parameters.h similarity index 100% rename from hls4ml/templates/oneapi/firmware/parameters.h rename to hls4ml/templates/altera/firmware/parameters.h diff --git a/hls4ml/templates/oneapi/myproject_bridge.cpp b/hls4ml/templates/altera/myproject_bridge.cpp similarity index 94% rename from hls4ml/templates/oneapi/myproject_bridge.cpp rename to hls4ml/templates/altera/myproject_bridge.cpp index ddad1d054b..5a22748478 100644 --- a/hls4ml/templates/oneapi/myproject_bridge.cpp +++ b/hls4ml/templates/altera/myproject_bridge.cpp @@ -56,7 +56,7 @@ void collect_trace_output(struct trace_data *c_trace_outputs) { void myproject_float( // hls-fpga-machine-learning insert header #float ) { - auto selector = sycl::ext::intel::fpga_emulator_selector_v; + auto selector = hls4ml_sycl_ext::fpga_emulator_selector_v; static sycl::queue q(selector, fpga_tools::exception_handler, sycl::property::queue::enable_profiling{}); // hls-fpga-machine-learning insert wrapper #float @@ -67,7 +67,7 @@ void myproject_float( void myproject_double( // hls-fpga-machine-learning insert header #double ) { - auto selector = sycl::ext::intel::fpga_emulator_selector_v; + auto selector = hls4ml_sycl_ext::fpga_emulator_selector_v; static sycl::queue q(selector, fpga_tools::exception_handler, sycl::property::queue::enable_profiling{}); // hls-fpga-machine-learning insert wrapper #double diff --git a/hls4ml/templates/oneapi/myproject_test.cpp b/hls4ml/templates/altera/myproject_test.cpp similarity index 92% rename from hls4ml/templates/oneapi/myproject_test.cpp rename to hls4ml/templates/altera/myproject_test.cpp index 82fb60d2f8..ffdbde3a57 100644 --- a/hls4ml/templates/oneapi/myproject_test.cpp +++ b/hls4ml/templates/altera/myproject_test.cpp @@ -9,12 +9,6 @@ #include "firmware/myproject.h" #include "firmware/parameters.h" -#include - -#if (__INTEL_CLANG_COMPILER < 20250000) -#include -#endif - #include "exception_handler.hpp" // hls-fpga-machine-learning insert bram @@ -23,11 +17,11 @@ int main(int argc, char **argv) { #if FPGA_SIMULATOR - auto selector = sycl::ext::intel::fpga_simulator_selector_v; + auto selector = hls4ml_sycl_ext::fpga_simulator_selector_v; #elif FPGA_HARDWARE - auto selector = sycl::ext::intel::fpga_selector_v; + auto selector = hls4ml_sycl_ext::fpga_selector_v; #else // #if FPGA_EMULATOR - auto selector = sycl::ext::intel::fpga_emulator_selector_v; + auto selector = hls4ml_sycl_ext::fpga_emulator_selector_v; #endif sycl::queue q(selector, fpga_tools::exception_handler, sycl::property::queue::enable_profiling{}); diff --git a/hls4ml/writer/__init__.py b/hls4ml/writer/__init__.py index 49a9aceb80..53470ab51c 100644 --- a/hls4ml/writer/__init__.py +++ b/hls4ml/writer/__init__.py @@ -1,3 +1,4 @@ +from hls4ml.writer.altera_writer import AlteraWriter from hls4ml.writer.catapult_writer import CatapultWriter from hls4ml.writer.coyote_writer import CoyoteWriter from hls4ml.writer.libero_writer import LiberoWriter @@ -14,6 +15,7 @@ register_writer('VivadoAccelerator', VivadoAcceleratorWriter) register_writer('Vitis', VitisWriter) register_writer('Quartus', QuartusWriter) +register_writer('Altera', AlteraWriter) register_writer('oneAPI', OneAPIWriter) register_writer('Catapult', CatapultWriter) register_writer('Libero', LiberoWriter) diff --git a/hls4ml/writer/altera_writer.py b/hls4ml/writer/altera_writer.py new file mode 100644 index 0000000000..836d02c93c --- /dev/null +++ b/hls4ml/writer/altera_writer.py @@ -0,0 +1,1005 @@ +import glob +import os +import tarfile +from collections import OrderedDict +from shutil import copyfile + +import numpy as np +import yaml + +from hls4ml.utils.fixed_point_utils import FixedPointEmulator, ceil_log2, uint_to_binary +from hls4ml.utils.string_utils import convert_to_pascal_case +from hls4ml.writer.writers import Writer + +config_filename = 'hls4ml_config.yml' + + +class AlteraWriter(Writer): + compiler_executable = 'ahls' + compiler_definitions = () + + def __make_dat_file(self, original_path, project_path): + """ + Convert other input/output data types into a dat file, which is + a text file with the falttened matrix printed out. Note that ' ' is + assumed to be the delimiter. + """ + + # Take in data from current supported data files + if original_path[-3:] == 'npy': + data = np.load(original_path) + else: + raise Exception('Unsupported input/output data files.') + + # Faltten data, just keep first dimension + data = data.reshape(data.shape[0], -1) + + def print_data(f): + for i in range(data.shape[0]): + for j in range(data.shape[1]): + f.write(str(data[i][j]) + ' ') + f.write('\n') + + # Print out in dat file + with open(project_path, 'w') as f: + print_data(f) + + def get_max_reuse_factor(self, model): + max_rf = 0 + for layer in model.get_layers(): + rf = int(layer.get_attr('reuse_factor')) + if rf > max_rf: + max_rf = rf + return max_rf + + def print_array_to_cpp(self, var, layer, odir): + """Write a weights array to C++ header files. + + Args: + var (WeightVariable): Weight to write + layer (Layer): Instance of the layer to which the weights belong + odir (str): Output directory + """ + with open(f'{odir}/src/firmware/weights/{var.name}.h', 'w') as h_file: + # meta data + h_file.write(f'//Numpy array shape {var.shape}\n') + h_file.write(f'//Min {np.min(var.min):.12f}\n') + h_file.write(f'//Max {np.max(var.max):.12f}\n') + h_file.write(f'//Number of zeros {var.nzeros}\n') + h_file.write('\n') + + h_file.write(f'#ifndef {var.name.upper()}_H_\n') + h_file.write(f'#define {var.name.upper()}_H_\n') + h_file.write('\n') + + rf = int(layer.get_attr('reuse_factor', 1)) + + h_file.write(var.definition_cpp(rf) + ' = {{') + + # fill c++ array. + # not including internal brackets for multidimensional case + sep = '' + for x in var: + h_file.write(sep + x) + sep = ', ' + h_file.write('}};\n') + h_file.write('\n#endif\n') + + def write_project_dir(self, model): + """Write the base project directory + + Args: + model (ModelGraph): the hls4ml model. + """ + if not os.path.isdir(f'{model.config.get_output_dir()}/src/firmware/weights'): + os.makedirs(f'{model.config.get_output_dir()}/src/firmware/weights') + + def write_project_cpp(self, model): + """Write the main architecture source file (myproject.cpp) + + Args: + model (ModelGraph): the hls4ml model. + """ + project_name = model.config.get_project_name() + + filedir = os.path.dirname(os.path.abspath(__file__)) + with ( + open(os.path.join(filedir, '../templates/altera/firmware/myproject.cpp')) as f, + open(f'{model.config.get_output_dir()}/src/firmware/{project_name}.cpp', 'w') as fout, + ): + model_inputs = model.get_input_variables() + model_outputs = model.get_output_variables() + model_brams = [var for var in model.get_weight_variables() if var.storage.lower() == 'bram'] + + if len(model_brams) != 0: + raise NotImplementedError('Weights on the interface is currently not supported') + + io_type = model.config.get_config_value('IOType') + indent = ' ' + + for line in f.readlines(): + # Add headers to weights and biases + if 'myproject' in line: + newline = line.replace('myproject', project_name) + elif 'MyProject' in line: + newline = line.replace('MyProject', convert_to_pascal_case(project_name)) + + # Altera pipes need to be declared and passed as template parameters + elif '// hls-fpga-machine-learning insert inter-task pipes' in line: + newline = line + if io_type == 'io_stream': + for layer in model.get_layers(): + vars = layer.get_variables() + for var in vars: + if var not in model_inputs and var not in model_outputs: + newline += var.declare_cpp() + + # Read in inputs + elif '// hls-fpga-machine-learning read in' in line: + newline = line + if io_type == 'io_parallel': + for inp in model_inputs: + newline += indent + f'auto {inp.name} = {inp.pipe_name}::read();\n' + # for streaming we don't need to read it in + + # Insert weights + elif '// hls-fpga-machine-learning insert weights' in line: + newline = line + for layer in model.get_layers(): + for w in layer.get_weights(): + if w not in model_brams: + newline += f'#include "weights/{w.name}.h"\n' + + # Insert task sequences + elif '// hls-fpga-machine-learning declare task sequences' in line: + newline = line + if io_type == 'io_stream': # only need this for io_stream + for layer in model.get_layers(): + ts = layer.get_attr('task_sequence_cpp') + if ts: + newline += ' ' + ts + '\n' + + # Neural net instantiation + elif '// hls-fpga-machine-learning insert layers' in line: + newline = line + '\n' + for layer in model.get_layers(): + if io_type != 'io_stream': + vars = layer.get_variables() + for var in vars: + if var not in model_inputs: + def_cpp = var.definition_cpp() + if def_cpp is not None: + newline += ' ' + def_cpp + ';\n' + func = ( + layer.get_attr('function_cpp') + if io_type == 'io_parallel' + else layer.get_attr('stream_function_cpp') + ) + if func: + newline += ' ' + func + '\n' + if model.config.trace_output and layer.get_attr('trace', False): + newline += '#ifndef HLS_SYNTHESIS\n' + for var in vars: + newline += ' nnet::save_layer_output<{}>({}, "{}", {});\n'.format( + var.type.name, var.name, layer.name, var.size_cpp() + ) + newline += '#endif\n' + + # Write the output + elif '// hls-fpga-machine-learning return' in line: + newline = line + if io_type == 'io_parallel': + for out in model_outputs: + newline += indent + f'{out.pipe_name}::write({out.name});\n' + # don't need to add anything in io_stream + + # Just copy line + else: + newline = line + + fout.write(newline) + + def write_project_header(self, model): + """Write the main architecture header file (myproject.h) + + Args: + model (ModelGraph): the hls4ml model. + """ + + project_name = model.config.get_project_name() + + filedir = os.path.dirname(os.path.abspath(__file__)) + with ( + open(os.path.join(filedir, '../templates/altera/firmware/myproject.h')) as f, + open(f'{model.config.get_output_dir()}/src/firmware/{project_name}.h', 'w') as fout, + ): + model_inputs = model.get_input_variables() + model_outputs = model.get_output_variables() + # model_brams = [var for var in model.get_weight_variables() if var.storage.lower() == 'bram'] + + # io_parallel and io_stream instantiate the top-level function differently (io_stream not yet supported) + # io_type = model.config.get_config_value('IOType') + # indent = ' ' + # brams_str = ', \n'.join([indent + b.definition_cpp(as_reference=False) for b in model_brams]) + + for line in f.readlines(): + if 'MYPROJECT' in line: + newline = line.replace('MYPROJECT', format(project_name.upper())) + + elif 'myproject' in line: + newline = line.replace('myproject', project_name) + + elif 'MyProject' in line: + newline = line.replace('MyProject', convert_to_pascal_case(project_name)) + + # Declarations for the inputs. May need modification when io_stream is supported + elif '// hls-fpga-machine-learning insert inputs' in line: + newline = line + for inp in model_inputs: + newline += inp.declare_cpp() + + # and declareations for the outputs + elif '// hls-fpga-machine-learning insert outputs' in line: + newline = line + for out in model_outputs: + newline += out.declare_cpp() + + # Simply copy line, if no inserts are required + else: + newline = line + + fout.write(newline) + + def write_defines(self, model): + """Write the C++ type definitions file (defines.h) + + Args: + model (ModelGraph): the hls4ml model. + """ + filedir = os.path.dirname(os.path.abspath(__file__)) + with ( + open(os.path.join(filedir, '../templates/altera/firmware/defines.h')) as f, + open(f'{model.config.get_output_dir()}/src/firmware/defines.h', 'w') as fout, + ): + for line in f.readlines(): + if '// hls-fpga-machine-learning insert layer-precision' in line: + newline = line + all_precision = OrderedDict() + for layer in model.get_layers(): + layer_precision = layer.get_layer_precision() + for type_name, type_var in layer_precision.items(): + # Ensure that layer's types doesn't override existing types + # This can happen in case of InplaceVariable types + if type_name not in all_precision: + all_precision[type_name] = type_var + for used_type in all_precision.values(): + newline += used_type.definition_cpp() + + else: + newline = line + fout.write(newline) + + def write_parameters(self, model): + """Write the C++ layer config file (parameters.h) + + Args: + model (ModelGraph): the hls4ml model. + """ + filedir = os.path.dirname(os.path.abspath(__file__)) + with ( + open(os.path.join(filedir, '../templates/altera/firmware/parameters.h')) as f, + open(f'{model.config.get_output_dir()}/src/firmware/parameters.h', 'w') as fout, + ): + for line in f.readlines(): + if '// hls-fpga-machine-learning insert includes' in line: + newline = line + for include in sorted( + set(sum((layer.get_attr('include_header', []) for layer in model.get_layers()), [])) + ): + newline += '#include "%s"\n' % include + + elif '// hls-fpga-machine-learning insert layer-config' in line: + newline = line + for layer in model.get_layers(): + config = layer.get_attr('config_cpp', None) + if config: + newline += config + '\n' + else: + newline = line + fout.write(newline) + + def write_weights(self, model): + """Write the weights into header files + + Args: + model (ModelGraph): the hls4ml model. + """ + for layer in model.get_layers(): + for weights in layer.get_weights(): + self.print_array_to_cpp(weights, layer, model.config.get_output_dir()) + + def write_test_bench(self, model): + """Write the testbench + + Args: + model (ModelGraph): the hls4ml model. + """ + # TODO - This function only works with one model input + # (NOT one data point - it works as expected with multiple data points) + + # copy the exception handler + filedir = os.path.dirname(os.path.abspath(__file__)) + srcpath = os.path.join(filedir, '../templates/altera/exception_handler.hpp') + dstpath = f'{model.config.get_output_dir()}/src/exception_handler.hpp' + copyfile(srcpath, dstpath) + + project_name = model.config.get_project_name() + model_inputs = model.get_input_variables() + model_outputs = model.get_output_variables() + model_brams = [var for var in model.get_weight_variables() if var.storage.lower() == 'bram'] + + if len(model_brams) != 0: + raise NotImplementedError('Weights on the interface is currently not supported') + + if len(model_inputs) != 1 or len(model_outputs) != 1: + print('The testbench supports only single input arrays and single output arrays.') + print('Please modify it before using it.') + + if not os.path.exists(f'{model.config.get_output_dir()}/tb_data/'): + os.mkdir(f'{model.config.get_output_dir()}/tb_data/') + + input_data = model.config.get_config_value('InputData') + output_predictions = model.config.get_config_value('OutputPredictions') + + if input_data: + if input_data[-3:] == 'dat': + copyfile(input_data, f'{model.config.get_output_dir()}/tb_data/tb_input_features.dat') + else: + self.__make_dat_file(input_data, f'{model.config.get_output_dir()}/tb_data/tb_input_features.dat') + + if output_predictions: + if output_predictions[-3:] == 'dat': + copyfile(output_predictions, f'{model.config.get_output_dir()}/tb_data/tb_output_predictions.dat') + else: + self.__make_dat_file( + output_predictions, f'{model.config.get_output_dir()}/tb_data/tb_output_predictions.dat' + ) + + with ( + open(os.path.join(filedir, '../templates/altera/myproject_test.cpp')) as f, + open(f'{model.config.get_output_dir()}/src/{project_name}_test.cpp', 'w') as fout, + ): + for line in f.readlines(): + indent = ' ' * (len(line) - len(line.lstrip(' '))) + + if 'myproject' in line: + newline = line.replace('myproject', project_name) + elif 'MyProject' in line: + newline = line.replace('MyProject', convert_to_pascal_case(project_name)) + + elif '// hls-fpga-machine-learning insert bram' in line: + newline = line + for bram in model_brams: + newline += f'#include "firmware/weights/{bram.name}.h"\n' + elif '// hls-fpga-machine-learning insert zero' in line: + newline = line + inp = model_inputs[0] + newline += indent + f'float vals[{inp.size_cpp()}]; \n' + newline += indent + f'for (int j = 0 ; j < {inp.size_cpp()} ; j++) {{\n' + newline += indent + ' vals[j] = 0.0; \n' + newline += indent + '}\n' + newline += indent + f'nnet::convert_data(q, vals);\n' + elif '// hls-fpga-machine-learning insert data' in line: + newline = line + inp = model_inputs[0] + newline += indent + f'float vals[{inp.size_cpp()}]; \n' + newline += indent + f'for (int j = 0 ; j < {inp.size_cpp()} ; j++) {{\n' + newline += indent + ' vals[j] = in[j]; \n' + newline += indent + '}\n' + newline += indent + f'nnet::convert_data(q, vals);\n' + elif '// hls-fpga-machine-learning convert output' in line: + newline = line + out = model_outputs[0] + newline += indent + f'float outputs[{out.size_cpp()}];\n' + newline += indent + f'nnet::convert_data_back<{out.pipe_name}, float, {out.size_cpp()}>(q, outputs);\n' + else: + newline = line + + fout.write(newline) + + def write_bridge(self, model): + """Write the Python-C++ bridge (myproject_bridge.cpp) + + Args: + model (ModelGraph): the hls4ml model. + """ + project_name = model.config.get_project_name() + stamp = model.config.get_config_value('Stamp') + model_inputs = model.get_input_variables() + model_outputs = model.get_output_variables() + model_brams = [var for var in model.get_weight_variables() if var.storage.lower() == 'bram'] + # model brambs aren't actually supported yet + + # io_type = model.config.get_config_value('IOType') + indent = ' ' + + filedir = os.path.dirname(os.path.abspath(__file__)) + with ( + open(os.path.join(filedir, '../templates/altera/myproject_bridge.cpp')) as f, + open(f'{model.config.get_output_dir()}/src/{project_name}_bridge.cpp', 'w') as fout, + ): + for line in f.readlines(): + if 'MYPROJECT' in line: + newline = line.replace('MYPROJECT', format(project_name.upper())) + + elif 'myproject' in line: + newline = line.replace('myproject', format(project_name)) + + elif 'MyProject' in line: + newline = line.replace('MyProject', convert_to_pascal_case(project_name)) + + elif '// hls-fpga-machine-learning insert bram' in line: + newline = line + for bram in model_brams: + newline += f'#include "firmware/weights/{bram.name}.h"\n' + + elif '// hls-fpga-machine-learning insert class def' in line: + dtype = line.split('#', 1)[1].strip() + newline = f'class {convert_to_pascal_case(project_name)}Class{dtype.capitalize()}_{stamp};\n' + + elif '// hls-fpga-machine-learning insert header' in line: + dtype = line.split('#', 1)[1].strip() + inputs_str = ', '.join([f'{dtype} {i.name}[{i.size_cpp()}]' for i in model_inputs]) + outputs_str = ', '.join([f'{dtype} {o.name}[{o.size_cpp()}]' for o in model_outputs]) + + newline = '' + newline += indent + inputs_str + ',\n' + newline += indent + outputs_str + '\n' + + elif '// hls-fpga-machine-learning insert wrapper' in line: + dtype = line.split('#', 1)[1].strip() + newline = '' + for i in model_inputs: + newline += indent + f'nnet::convert_data<{dtype}, {i.pipe_name}, {i.size_cpp()}>(q, {i.name});\n' + + newline += ( + indent + + f'q.single_task<{convert_to_pascal_case(project_name)}Class{dtype.capitalize()}_{stamp}>' + + f'({convert_to_pascal_case(project_name)}{{}});\n' + ) + + for o in model_outputs: + newline += ( + indent + f'nnet::convert_data_back<{o.pipe_name}, {dtype}, {o.size_cpp()}>(q, {o.name});\n' + ) + newline += '\n' + newline += indent + 'q.wait();\n' + + elif '// hls-fpga-machine-learning insert trace_outputs' in line: + newline = '' + for layer in model.get_layers(): + func = layer.get_attr('function_cpp') + if func and model.config.trace_output and layer.get_attr('trace', False): + vars = layer.get_variables() + for var in vars: + newline += ( + indent + + 'nnet::trace_outputs->insert(std::pair(' + + f'"{layer.name}", (void *) malloc({var.size_cpp()} * element_size)));\n' + ) + + else: + newline = line + fout.write(newline) + + def write_build_script(self, model): + """Write the build scripts (Makefile, build_lib.sh) + + Args: + model (ModelGraph): the hls4ml model. + """ + + # Makefile + filedir = os.path.dirname(os.path.abspath(__file__)) + device = model.config.get_config_value('Part') + period = model.config.get_config_value('ClockPeriod') + hyper = model.config.get_config_value('HyperoptHandshake') + with ( + open(os.path.join(filedir, '../templates/altera/CMakeLists.txt')) as f, + open(f'{model.config.get_output_dir()}/CMakeLists.txt', 'w') as fout, + ): + for line in f.readlines(): + line = line.replace('myproject', model.config.get_project_name()) + line = line.replace('mystamp', model.config.get_config_value('Stamp')) + + if 'hls-fpga-machine-learning insert compiler definitions' in line: + for definition in self.compiler_definitions: + line += f'add_compile_definitions({definition})\n' + + elif 'hls-fpga-machine-learning insert compiler' in line: + line += f'set(CMAKE_CXX_COMPILER {self.compiler_executable})\n' + + if 'set(FPGA_DEVICE' in line: + line = f' set(FPGA_DEVICE "{device}")\n' + + if 'set(USER_FPGA_FLAGS' in line: + line += f'set(USER_FPGA_FLAGS -Xsclock={period}ns; ${{USER_FPGA_FLAGS}})\n' + if not hyper: + line += 'set(USER_FPGA_FLAGS -Xsoptimize=latency; ${USER_FPGA_FLAGS})\n' + + fout.write(line) + + def write_nnet_utils(self, model): + """Copy the nnet_utils, AP types headers and any custom source to the project output directory + + Args: + model (ModelGraph): the hls4ml model. + """ + + # nnet_utils + filedir = os.path.dirname(os.path.abspath(__file__)) + + srcpath = os.path.join(filedir, '../templates/altera/firmware/nnet_utils/') + dstpath = f'{model.config.get_output_dir()}/src/firmware/nnet_utils/' + + if not os.path.exists(dstpath): + os.mkdir(dstpath) + + headers = [os.path.basename(h) for h in glob.glob(srcpath + '*.h')] + + for h in headers: + copyfile(srcpath + h, dstpath + h) + + # custom source + filedir = os.path.dirname(os.path.abspath(__file__)) + + custom_source = model.config.backend.get_custom_source() + for dst, srcpath in custom_source.items(): + dstpath = f'{model.config.get_output_dir()}/src/firmware/{dst}' + copyfile(srcpath, dstpath) + + def __get_table_size(self, model, activation): + for layer in model.get_layers(): + if ( + layer.get_attr('activation') == activation or layer.get_attr('recurrent_activation') == activation + ) and layer.get_attr('table_size') is not None: + return int(layer.get_attr('table_size')) + return 1024 + + def __get_table_header(self, table_name, table_size): + table_header = f'static const typename CONFIG_T::table_t {table_name}[{table_size}] = {{' + return table_header + + def __write_elu_table(self, model, path): + table_name = 'elu_table' + table_size = self.__get_table_size(model, 'elu') + + h_file = open(f'{path}/{table_name}.tb', 'w') + h_file.write(self.__get_table_header(table_name, table_size)) + + sep = '' + for i in range(table_size): + in_val = -8.0 * i / float(table_size) + real_val = np.exp(in_val) - 1.0 + h_file.write(sep + str(real_val)) + sep = ', ' + + h_file.write('};\n') + h_file.close() + + def __write_sigmoid_table(self, model, path): + MAX_VALUE = 8 + MIN_VALUE = 0 + + table_name = 'sigmoid_table' + table_size = self.__get_table_size(model, 'sigmoid') + + h_file = open(f'{path}/{table_name}.tb', 'w') + h_file.write(self.__get_table_header(table_name, table_size)) + + sep = '' + for i in range(int(table_size)): + in_val = ( + i * (MAX_VALUE - MIN_VALUE) / float(table_size) + + (MAX_VALUE - MIN_VALUE) / (float(table_size) * 2) + + MIN_VALUE + ) + real_val = 1.0 / (1 + np.exp(-in_val)) + if real_val >= 0.5: + h_file.write(sep + str(real_val)) + sep = ', ' + + h_file.write('};\n') + h_file.close() + + def __write_tanh_table(self, model, path): + MAX_VALUE = 4 + MIN_VALUE = 0 + + table_name = 'tanh_table' + table_size = self.__get_table_size(model, 'tanh') + + h_file = open(f'{path}/{table_name}.tb', 'w') + h_file.write(self.__get_table_header(table_name, table_size)) + + sep = '' + for i in range(table_size): + in_val = ( + i * (MAX_VALUE - MIN_VALUE) / float(table_size) + + (MAX_VALUE - MIN_VALUE) / (float(table_size) * 2) + + MIN_VALUE + ) + real_val = np.tanh(in_val) + if real_val >= 0: + h_file.write(sep + str(real_val)) + sep = ', ' + + h_file.write('};\n') + h_file.close() + + def __write_softplus_table(self, model, path): + table_name = 'softplus_table' + table_size = self.__get_table_size(model, 'softplus') + + h_file = open(f'{path}/{table_name}.tb', 'w') + h_file.write(self.__get_table_header(table_name, table_size)) + + sep = '' + for i in range(table_size): + in_val = 2 * 8.0 * (i - float(table_size) / 2.0) / float(table_size) + real_val = np.log(np.exp(in_val) + 1.0) + h_file.write(sep + str(real_val)) + sep = ', ' + + h_file.write('};\n') + h_file.close() + + def __write_softsign_table(self, model, path): + MAX_VALUE = 8 + MIN_VALUE = 0 + table_name = 'softsign_table' + table_size = self.__get_table_size(model, 'softsign') + + h_file = open(f'{path}/{table_name}.tb', 'w') + h_file.write(self.__get_table_header(table_name, table_size)) + + sep = '' + for i in range(table_size): + in_val = ( + i * (MAX_VALUE - MIN_VALUE) / float(table_size) + + (MAX_VALUE - MIN_VALUE) / (float(table_size) * 2) + + MIN_VALUE + ) + + real_val = in_val / (np.fabs(in_val) + 1.0) + if real_val >= 0: + h_file.write(sep + str(real_val)) + sep = ', ' + + h_file.write('};\n') + h_file.close() + + def __write_selu_table(self, model, path): + table_name = 'selu_table' + table_size = self.__get_table_size(model, 'selu') + + h_file = open(f'{path}/{table_name}.tb', 'w') + h_file.write(self.__get_table_header(table_name, table_size)) + + sep = '' + for i in range(table_size): + in_val = -8.0 * i / float(table_size) + real_val = 1.0507009873554804934193349852946 * (1.6732632423543772848170429916717 * (np.exp(in_val) - 1.0)) + h_file.write(sep + str(real_val)) + sep = ', ' + + h_file.write('};\n') + h_file.close() + + def __write_exp_table(self, model, path): + table_name = 'exp_table' + table_size = self.__get_table_size(model, 'softmax') + + h_file = open(f'{path}/{table_name}.tb', 'w') + h_file.write(self.__get_table_header(table_name, table_size)) + + # Default fixed point precision + # 6 bits for integer part, 10 bits for decimal - total, 16 + fp_bits = 16 + fp_integer = 6 + fp_signed = True + + # Exp table should use the same precision as exp_table, as seen in Vivado code + # init_exp_table(exp_table); + for layer in model.get_layers(): + if layer.name == 'softmax': + ac_type = layer.get_input_variable().type + if ac_type is not None: + try: + fp_bits = ac_type.precision.integer + ac_type.precision.fractional + fp_integer = ac_type.precision.integer + fp_signed = ac_type.precision.signed + except Exception: + # FixedPrecisionType wasn't correctly stored in layer attributes, use default values + pass + if fp_signed is False: + raise Exception('Softmax types need to be signed') + + sep = '' + N = ceil_log2(table_size) + for i in range(table_size): + f = FixedPointEmulator(fp_bits, fp_integer, signed=fp_signed) + b = uint_to_binary(i, N) + if i == 0: + b.insert(0, 0) + else: + b.insert(0, 1) + f.set_msb_bits(b) + real_val = f.exp_float() + h_file.write(sep + str(real_val)) + sep = ', ' + + h_file.write('};\n') + h_file.close() + + def __write_invert_table(self, model, path): + table_name = 'invert_table' + table_size = self.__get_table_size(model, 'softmax') + + h_file = open(f'{path}/{table_name}.tb', 'w') + h_file.write(self.__get_table_header(table_name, table_size)) + + # Default fixed point precision, in case values from layer attributes cannot be extracted + # 8 bits for integer part, 10 bits for decimal - total, 18 + fp_bits = 18 + fp_integer = 8 + fp_signed = True + + # Invert table should use the same precision as exp_table, as seen in Vivado code + # init_invert_table(invert_table); + for layer in model.get_layers(): + if layer.name == 'softmax': + ac_type = layer.get_attr('exp_table_t') + if ac_type is not None: + try: + fp_bits = ac_type.precision.integer + ac_type.precision.fractional + fp_integer = ac_type.precision.integer + fp_signed = ac_type.precision.signed + except Exception: + # FixedPrecisionType wasn't correctly stored in layer attributes, use default values + pass + if fp_signed is False: + raise Exception('Softmax types need to be signed') + + sep = '' + N = ceil_log2(table_size) + for i in range(table_size): + f = FixedPointEmulator(fp_bits, fp_integer, signed=fp_signed) + b = uint_to_binary(i, N) + b.insert(0, 0) + f.set_msb_bits(b) + real_val = f.inv_float() + h_file.write(sep + str(real_val)) + sep = ', ' + + h_file.write('};\n') + h_file.close() + + def __write_exp_table_latency(self, model, path): + table_name = 'exp_table_latency' + table_size = self.__get_table_size(model, 'softmax') + + h_file = open(f'{path}/{table_name}.tb', 'w') + h_file.write(self.__get_table_header(table_name, table_size)) + + # Default fixed point precision + # 6 bits for integer part, 10 bits for decimal - total, 16 + fp_bits = 16 + fp_integer = 6 + fp_signed = True + + # Exp table should use the same precision as exp_table, as seen in Vivado code + # init_exp_table(exp_table); + for layer in model.get_layers(): + if layer.name == 'softmax': + ac_type = layer.get_input_variable().type + if ac_type is not None: + try: + fp_bits = ac_type.precision.integer + ac_type.precision.fractional + fp_integer = ac_type.precision.integer + fp_signed = ac_type.precision.signed + except Exception: + # FixedPrecisionType wasn't correctly stored in layer attributes, use default values + pass + + sep = '' + N = ceil_log2(table_size) + for i in range(table_size): + f = FixedPointEmulator(fp_bits, fp_integer, signed=fp_signed) + f.set_msb_bits(uint_to_binary(i, N)) + real_val = f.exp_float() + h_file.write(sep + str(real_val)) + sep = ', ' + + h_file.write('};\n') + h_file.close() + + def __write_invert_table_latency(self, model, path): + table_name = 'invert_table_latency' + table_size = self.__get_table_size(model, 'softmax') + + h_file = open(f'{path}/{table_name}.tb', 'w') + h_file.write(self.__get_table_header(table_name, table_size)) + + # Default fixed point precision, in case values from layer attributes cannot be extracted + # 8 bits for integer part, 10 bits for decimal - total, 18 + fp_bits = 18 + fp_integer = 8 + fp_signed = True + + # Invert table should use the same precision as exp_table, as seen in Vivado code + # init_invert_table(invert_table); + for layer in model.get_layers(): + if layer.name == 'softmax': + ac_type = layer.get_attr('exp_table_t') + if ac_type is not None: + try: + fp_bits = ac_type.precision.integer + ac_type.precision.fractional + fp_integer = ac_type.precision.integer + fp_signed = ac_type.precision.signed + except Exception: + # FixedPrecisionType wasn't correctly stored in layer attributes, use default values + pass + + sep = '' + N = ceil_log2(table_size) + for i in range(table_size): + f = FixedPointEmulator(fp_bits, fp_integer, signed=fp_signed) + f.set_msb_bits(uint_to_binary(i, N)) + real_val = f.inv_float() + h_file.write(sep + str(real_val)) + sep = ', ' + + h_file.write('};\n') + h_file.close() + + def __write_exp_table_legacy(self, model, path): + table_name = 'exp_table_legacy' + table_size = self.__get_table_size(model, 'softmax') + + h_file = open(f'{path}/{table_name}.tb', 'w') + h_file.write(self.__get_table_header(table_name, table_size)) + + sep = '' + for i in range(table_size): + in_val = 2 * 8.0 * (i - float(table_size) / 2.0) / float(table_size) + real_val = np.exp(in_val) + h_file.write(sep + str(real_val)) + sep = ', ' + + h_file.write('};\n') + h_file.close() + + def __write_invert_table_legacy(self, model, path): + table_name = 'invert_table_legacy' + table_size = self.__get_table_size(model, 'softmax') + + h_file = open(f'{path}/{table_name}.tb', 'w') + h_file.write(self.__get_table_header(table_name, table_size)) + + sep = '' + for i in range(table_size): + real_val = 0 + in_val = 64.0 * i / float(table_size) + if in_val > 0.0: + real_val = 1.0 / in_val + h_file.write(sep + str(real_val)) + sep = ', ' + + h_file.write('};\n') + h_file.close() + + def write_activation_tables(self, model): + """Write the lookup tables for activation functions + + Args: + model (ModelGraph): the hls4ml model. + """ + # Output path + dstpath = f'{model.config.get_output_dir()}/src/firmware/nnet_utils/activation_tables' + if not os.path.exists(dstpath): + os.mkdir(dstpath) + + # Tables + # TODO - Only write tables needed by model, not all of them + self.__write_elu_table(model, dstpath) + self.__write_sigmoid_table(model, dstpath) + self.__write_tanh_table(model, dstpath) + self.__write_softplus_table(model, dstpath) + self.__write_softsign_table(model, dstpath) + self.__write_selu_table(model, dstpath) + self.__write_exp_table(model, dstpath) + self.__write_invert_table(model, dstpath) + self.__write_exp_table_latency(model, dstpath) + self.__write_invert_table_latency(model, dstpath) + self.__write_exp_table_legacy(model, dstpath) + self.__write_invert_table_legacy(model, dstpath) + + def write_generated_code(self, model): + """Write the generated code (nnet_code_gen.h) + + Args: + model (ModelGraph): the hls4ml model. + """ + path = f'{model.config.get_output_dir()}/src/firmware/nnet_utils/nnet_code_gen.h' + f = open(path) + contents = f.readlines() + f.close() + f = open(path, 'w') + namespace = model.config.get_writer_config().get('Namespace', None) + + for line in contents: + if '// hls4ml insert code' in line: + newline = line + for layer in model.get_layers(): + for generated_code in layer.code.values(): + newline += str(generated_code) + else: + newline = line + if namespace is not None: + if 'namespace nnet' in newline: + newline = newline.replace('namespace nnet', f'namespace {namespace}') + f.write(newline) + f.close() + + def write_yml(self, model): + """Write the config to the YAML file + + Args: + model (ModelGraph): the hls4ml model. + """ + + def keras_model_representer(dumper, keras_model): + model_path = model.config.get_output_dir() + '/keras_model.keras' + keras_model.save(model_path) + return dumper.represent_scalar('!keras_model', model_path) + + try: + from tensorflow.keras import Model as KerasModel + + yaml.add_multi_representer(KerasModel, keras_model_representer) + except Exception: + pass + + with open(model.config.get_output_dir() + '/' + config_filename, 'w') as file: + yaml.dump(model.config.config, file) + + def write_tar(self, model): + """Write the generated project as a .tar.gz archive + + Args: + model (ModelGraph): the hls4ml model. + """ + + if model.config.get_writer_config().get('WriteTar', False): + tar_path = model.config.get_output_dir() + '.tar.gz' + if os.path.exists(tar_path): + os.remove(tar_path) + with tarfile.open(model.config.get_output_dir() + '.tar.gz', mode='w:gz') as archive: + archive.add(model.config.get_output_dir(), recursive=True) + + def write_hls(self, model): + self.write_project_dir(model) + self.write_project_cpp(model) + self.write_project_header(model) + self.write_weights(model) + self.write_defines(model) + self.write_parameters(model) + self.write_test_bench(model) + self.write_bridge(model) + self.write_build_script(model) + self.write_nnet_utils(model) + self.write_activation_tables(model) + self.write_generated_code(model) + self.write_yml(model) + self.write_tar(model) diff --git a/hls4ml/writer/oneapi_writer.py b/hls4ml/writer/oneapi_writer.py index 3c0a778c50..e08cbfd51f 100644 --- a/hls4ml/writer/oneapi_writer.py +++ b/hls4ml/writer/oneapi_writer.py @@ -1,996 +1,6 @@ -import glob -import os -import tarfile -from collections import OrderedDict -from shutil import copyfile +from hls4ml.writer.altera_writer import AlteraWriter -import numpy as np -import yaml -from hls4ml.backends import get_backend -from hls4ml.utils.fixed_point_utils import FixedPointEmulator, ceil_log2, uint_to_binary -from hls4ml.utils.string_utils import convert_to_pascal_case -from hls4ml.writer.writers import Writer - -config_filename = 'hls4ml_config.yml' - - -class OneAPIWriter(Writer): - def __make_dat_file(self, original_path, project_path): - """ - Convert other input/output data types into a dat file, which is - a text file with the falttened matrix printed out. Note that ' ' is - assumed to be the delimiter. - """ - - # Take in data from current supported data files - if original_path[-3:] == 'npy': - data = np.load(original_path) - else: - raise Exception('Unsupported input/output data files.') - - # Faltten data, just keep first dimension - data = data.reshape(data.shape[0], -1) - - def print_data(f): - for i in range(data.shape[0]): - for j in range(data.shape[1]): - f.write(str(data[i][j]) + ' ') - f.write('\n') - - # Print out in dat file - with open(project_path, 'w') as f: - print_data(f) - - def get_max_reuse_factor(self, model): - max_rf = 0 - for layer in model.get_layers(): - rf = int(layer.get_attr('reuse_factor')) - if rf > max_rf: - max_rf = rf - return max_rf - - def print_array_to_cpp(self, var, layer, odir): - """Write a weights array to C++ header files. - - Args: - var (WeightVariable): Weight to write - layer (Layer): Instance of the layer to which the weights belong - odir (str): Output directory - """ - with open(f'{odir}/src/firmware/weights/{var.name}.h', 'w') as h_file: - # meta data - h_file.write(f'//Numpy array shape {var.shape}\n') - h_file.write(f'//Min {np.min(var.min):.12f}\n') - h_file.write(f'//Max {np.max(var.max):.12f}\n') - h_file.write(f'//Number of zeros {var.nzeros}\n') - h_file.write('\n') - - h_file.write(f'#ifndef {var.name.upper()}_H_\n') - h_file.write(f'#define {var.name.upper()}_H_\n') - h_file.write('\n') - - rf = int(layer.get_attr('reuse_factor', 1)) - - h_file.write(var.definition_cpp(rf) + ' = {{') - - # fill c++ array. - # not including internal brackets for multidimensional case - sep = '' - for x in var: - h_file.write(sep + x) - sep = ', ' - h_file.write('}};\n') - h_file.write('\n#endif\n') - - def write_project_dir(self, model): - """Write the base project directory - - Args: - model (ModelGraph): the hls4ml model. - """ - if not os.path.isdir(f'{model.config.get_output_dir()}/src/firmware/weights'): - os.makedirs(f'{model.config.get_output_dir()}/src/firmware/weights') - - def write_project_cpp(self, model): - """Write the main architecture source file (myproject.cpp) - - Args: - model (ModelGraph): the hls4ml model. - """ - project_name = model.config.get_project_name() - - filedir = os.path.dirname(os.path.abspath(__file__)) - with ( - open(os.path.join(filedir, '../templates/oneapi/firmware/myproject.cpp')) as f, - open(f'{model.config.get_output_dir()}/src/firmware/{project_name}.cpp', 'w') as fout, - ): - model_inputs = model.get_input_variables() - model_outputs = model.get_output_variables() - model_brams = [var for var in model.get_weight_variables() if var.storage.lower() == 'bram'] - - if len(model_brams) != 0: - raise NotImplementedError('Weights on the interface is currently not supported') - - io_type = model.config.get_config_value('IOType') - indent = ' ' - - for line in f.readlines(): - # Add headers to weights and biases - if 'myproject' in line: - newline = line.replace('myproject', project_name) - elif 'MyProject' in line: - newline = line.replace('MyProject', convert_to_pascal_case(project_name)) - - # oneAPI pipes need to be declared and passed as template parameters - elif '// hls-fpga-machine-learning insert inter-task pipes' in line: - newline = line - if io_type == 'io_stream': - for layer in model.get_layers(): - vars = layer.get_variables() - for var in vars: - if var not in model_inputs and var not in model_outputs: - newline += var.declare_cpp() - - # Read in inputs - elif '// hls-fpga-machine-learning read in' in line: - newline = line - if io_type == 'io_parallel': - for inp in model_inputs: - newline += indent + f'auto {inp.name} = {inp.pipe_name}::read();\n' - # for streaming we don't need to read it in - - # Insert weights - elif '// hls-fpga-machine-learning insert weights' in line: - newline = line - for layer in model.get_layers(): - for w in layer.get_weights(): - if w not in model_brams: - newline += f'#include "weights/{w.name}.h"\n' - - # Insert task sequences - elif '// hls-fpga-machine-learning declare task sequences' in line: - newline = line - if io_type == 'io_stream': # only need this for io_stream - for layer in model.get_layers(): - ts = layer.get_attr('task_sequence_cpp') - if ts: - newline += ' ' + ts + '\n' - - # Neural net instantiation - elif '// hls-fpga-machine-learning insert layers' in line: - newline = line + '\n' - for layer in model.get_layers(): - if io_type != 'io_stream': - vars = layer.get_variables() - for var in vars: - if var not in model_inputs: - def_cpp = var.definition_cpp() - if def_cpp is not None: - newline += ' ' + def_cpp + ';\n' - func = ( - layer.get_attr('function_cpp') - if io_type == 'io_parallel' - else layer.get_attr('stream_function_cpp') - ) - if func: - newline += ' ' + func + '\n' - if model.config.trace_output and layer.get_attr('trace', False): - newline += '#ifndef HLS_SYNTHESIS\n' - for var in vars: - newline += ' nnet::save_layer_output<{}>({}, "{}", {});\n'.format( - var.type.name, var.name, layer.name, var.size_cpp() - ) - newline += '#endif\n' - - # Write the output - elif '// hls-fpga-machine-learning return' in line: - newline = line - if io_type == 'io_parallel': - for out in model_outputs: - newline += indent + f'{out.pipe_name}::write({out.name});\n' - # don't need to add anything in io_stream - - # Just copy line - else: - newline = line - - fout.write(newline) - - def write_project_header(self, model): - """Write the main architecture header file (myproject.h) - - Args: - model (ModelGraph): the hls4ml model. - """ - - project_name = model.config.get_project_name() - - filedir = os.path.dirname(os.path.abspath(__file__)) - with ( - open(os.path.join(filedir, '../templates/oneapi/firmware/myproject.h')) as f, - open(f'{model.config.get_output_dir()}/src/firmware/{project_name}.h', 'w') as fout, - ): - model_inputs = model.get_input_variables() - model_outputs = model.get_output_variables() - # model_brams = [var for var in model.get_weight_variables() if var.storage.lower() == 'bram'] - - # io_parallel and io_stream instantiate the top-level function differently (io_stream not yet supported) - # io_type = model.config.get_config_value('IOType') - # indent = ' ' - # brams_str = ', \n'.join([indent + b.definition_cpp(as_reference=False) for b in model_brams]) - - for line in f.readlines(): - if 'MYPROJECT' in line: - newline = line.replace('MYPROJECT', format(project_name.upper())) - - elif 'myproject' in line: - newline = line.replace('myproject', project_name) - - elif 'MyProject' in line: - newline = line.replace('MyProject', convert_to_pascal_case(project_name)) - - # Declarations for the inputs. May need modification when io_stream is supported - elif '// hls-fpga-machine-learning insert inputs' in line: - newline = line - for inp in model_inputs: - newline += inp.declare_cpp() - - # and declareations for the outputs - elif '// hls-fpga-machine-learning insert outputs' in line: - newline = line - for out in model_outputs: - newline += out.declare_cpp() - - # Simply copy line, if no inserts are required - else: - newline = line - - fout.write(newline) - - def write_defines(self, model): - """Write the C++ type definitions file (defines.h) - - Args: - model (ModelGraph): the hls4ml model. - """ - filedir = os.path.dirname(os.path.abspath(__file__)) - with ( - open(os.path.join(filedir, '../templates/oneapi/firmware/defines.h')) as f, - open(f'{model.config.get_output_dir()}/src/firmware/defines.h', 'w') as fout, - ): - for line in f.readlines(): - if '// hls-fpga-machine-learning insert layer-precision' in line: - newline = line - all_precision = OrderedDict() - for layer in model.get_layers(): - layer_precision = layer.get_layer_precision() - for type_name, type_var in layer_precision.items(): - # Ensure that layer's types doesn't override existing types - # This can happen in case of InplaceVariable types - if type_name not in all_precision: - all_precision[type_name] = type_var - for used_type in all_precision.values(): - newline += used_type.definition_cpp() - - else: - newline = line - fout.write(newline) - - def write_parameters(self, model): - """Write the C++ layer config file (parameters.h) - - Args: - model (ModelGraph): the hls4ml model. - """ - filedir = os.path.dirname(os.path.abspath(__file__)) - with ( - open(os.path.join(filedir, '../templates/oneapi/firmware/parameters.h')) as f, - open(f'{model.config.get_output_dir()}/src/firmware/parameters.h', 'w') as fout, - ): - for line in f.readlines(): - if '// hls-fpga-machine-learning insert includes' in line: - newline = line - for include in sorted( - set(sum((layer.get_attr('include_header', []) for layer in model.get_layers()), [])) - ): - newline += '#include "%s"\n' % include - - elif '// hls-fpga-machine-learning insert layer-config' in line: - newline = line - for layer in model.get_layers(): - config = layer.get_attr('config_cpp', None) - if config: - newline += config + '\n' - else: - newline = line - fout.write(newline) - - def write_weights(self, model): - """Write the weights into header files - - Args: - model (ModelGraph): the hls4ml model. - """ - for layer in model.get_layers(): - for weights in layer.get_weights(): - self.print_array_to_cpp(weights, layer, model.config.get_output_dir()) - - def write_test_bench(self, model): - """Write the testbench - - Args: - model (ModelGraph): the hls4ml model. - """ - # TODO - This function only works with one model input - # (NOT one data point - it works as expected with multiple data points) - - # copy the exception handler - filedir = os.path.dirname(os.path.abspath(__file__)) - srcpath = os.path.join(filedir, '../templates/oneapi/exception_handler.hpp') - dstpath = f'{model.config.get_output_dir()}/src/exception_handler.hpp' - copyfile(srcpath, dstpath) - - project_name = model.config.get_project_name() - model_inputs = model.get_input_variables() - model_outputs = model.get_output_variables() - model_brams = [var for var in model.get_weight_variables() if var.storage.lower() == 'bram'] - - if len(model_brams) != 0: - raise NotImplementedError('Weights on the interface is currently not supported') - - if len(model_inputs) != 1 or len(model_outputs) != 1: - print('The testbench supports only single input arrays and single output arrays.') - print('Please modify it before using it.') - - if not os.path.exists(f'{model.config.get_output_dir()}/tb_data/'): - os.mkdir(f'{model.config.get_output_dir()}/tb_data/') - - input_data = model.config.get_config_value('InputData') - output_predictions = model.config.get_config_value('OutputPredictions') - - if input_data: - if input_data[-3:] == 'dat': - copyfile(input_data, f'{model.config.get_output_dir()}/tb_data/tb_input_features.dat') - else: - self.__make_dat_file(input_data, f'{model.config.get_output_dir()}/tb_data/tb_input_features.dat') - - if output_predictions: - if output_predictions[-3:] == 'dat': - copyfile(output_predictions, f'{model.config.get_output_dir()}/tb_data/tb_output_predictions.dat') - else: - self.__make_dat_file( - output_predictions, f'{model.config.get_output_dir()}/tb_data/tb_output_predictions.dat' - ) - - with ( - open(os.path.join(filedir, '../templates/oneapi/myproject_test.cpp')) as f, - open(f'{model.config.get_output_dir()}/src/{project_name}_test.cpp', 'w') as fout, - ): - for line in f.readlines(): - indent = ' ' * (len(line) - len(line.lstrip(' '))) - - if 'myproject' in line: - newline = line.replace('myproject', project_name) - elif 'MyProject' in line: - newline = line.replace('MyProject', convert_to_pascal_case(project_name)) - - elif '// hls-fpga-machine-learning insert bram' in line: - newline = line - for bram in model_brams: - newline += f'#include "firmware/weights/{bram.name}.h"\n' - elif '// hls-fpga-machine-learning insert zero' in line: - newline = line - inp = model_inputs[0] - newline += indent + f'float vals[{inp.size_cpp()}]; \n' - newline += indent + f'for (int j = 0 ; j < {inp.size_cpp()} ; j++) {{\n' - newline += indent + ' vals[j] = 0.0; \n' - newline += indent + '}\n' - newline += indent + f'nnet::convert_data(q, vals);\n' - elif '// hls-fpga-machine-learning insert data' in line: - newline = line - inp = model_inputs[0] - newline += indent + f'float vals[{inp.size_cpp()}]; \n' - newline += indent + f'for (int j = 0 ; j < {inp.size_cpp()} ; j++) {{\n' - newline += indent + ' vals[j] = in[j]; \n' - newline += indent + '}\n' - newline += indent + f'nnet::convert_data(q, vals);\n' - elif '// hls-fpga-machine-learning convert output' in line: - newline = line - out = model_outputs[0] - newline += indent + f'float outputs[{out.size_cpp()}];\n' - newline += indent + f'nnet::convert_data_back<{out.pipe_name}, float, {out.size_cpp()}>(q, outputs);\n' - else: - newline = line - - fout.write(newline) - - def write_bridge(self, model): - """Write the Python-C++ bridge (myproject_bridge.cpp) - - Args: - model (ModelGraph): the hls4ml model. - """ - project_name = model.config.get_project_name() - stamp = model.config.get_config_value('Stamp') - model_inputs = model.get_input_variables() - model_outputs = model.get_output_variables() - model_brams = [var for var in model.get_weight_variables() if var.storage.lower() == 'bram'] - # model brambs aren't actually supported yet - - # io_type = model.config.get_config_value('IOType') - indent = ' ' - - filedir = os.path.dirname(os.path.abspath(__file__)) - with ( - open(os.path.join(filedir, '../templates/oneapi/myproject_bridge.cpp')) as f, - open(f'{model.config.get_output_dir()}/src/{project_name}_bridge.cpp', 'w') as fout, - ): - for line in f.readlines(): - if 'MYPROJECT' in line: - newline = line.replace('MYPROJECT', format(project_name.upper())) - - elif 'myproject' in line: - newline = line.replace('myproject', format(project_name)) - - elif 'MyProject' in line: - newline = line.replace('MyProject', convert_to_pascal_case(project_name)) - - elif '// hls-fpga-machine-learning insert bram' in line: - newline = line - for bram in model_brams: - newline += f'#include "firmware/weights/{bram.name}.h"\n' - - elif '// hls-fpga-machine-learning insert class def' in line: - dtype = line.split('#', 1)[1].strip() - newline = f'class {convert_to_pascal_case(project_name)}Class{dtype.capitalize()}_{stamp};\n' - - elif '// hls-fpga-machine-learning insert header' in line: - dtype = line.split('#', 1)[1].strip() - inputs_str = ', '.join([f'{dtype} {i.name}[{i.size_cpp()}]' for i in model_inputs]) - outputs_str = ', '.join([f'{dtype} {o.name}[{o.size_cpp()}]' for o in model_outputs]) - - newline = '' - newline += indent + inputs_str + ',\n' - newline += indent + outputs_str + '\n' - - elif '// hls-fpga-machine-learning insert wrapper' in line: - dtype = line.split('#', 1)[1].strip() - newline = '' - for i in model_inputs: - newline += indent + f'nnet::convert_data<{dtype}, {i.pipe_name}, {i.size_cpp()}>(q, {i.name});\n' - - newline += ( - indent - + f'q.single_task<{convert_to_pascal_case(project_name)}Class{dtype.capitalize()}_{stamp}>' - + f'({convert_to_pascal_case(project_name)}{{}});\n' - ) - - for o in model_outputs: - newline += ( - indent + f'nnet::convert_data_back<{o.pipe_name}, {dtype}, {o.size_cpp()}>(q, {o.name});\n' - ) - newline += '\n' - newline += indent + 'q.wait();\n' - - elif '// hls-fpga-machine-learning insert trace_outputs' in line: - newline = '' - for layer in model.get_layers(): - func = layer.get_attr('function_cpp') - if func and model.config.trace_output and layer.get_attr('trace', False): - vars = layer.get_variables() - for var in vars: - newline += ( - indent - + 'nnet::trace_outputs->insert(std::pair(' - + f'"{layer.name}", (void *) malloc({var.size_cpp()} * element_size)));\n' - ) - - else: - newline = line - fout.write(newline) - - def write_build_script(self, model): - """Write the build scripts (Makefile, build_lib.sh) - - Args: - model (ModelGraph): the hls4ml model. - """ - - # Makefile - filedir = os.path.dirname(os.path.abspath(__file__)) - device = model.config.get_config_value('Part') - period = model.config.get_config_value('ClockPeriod') - hyper = model.config.get_config_value('HyperoptHandshake') - with ( - open(os.path.join(filedir, '../templates/oneapi/CMakeLists.txt')) as f, - open(f'{model.config.get_output_dir()}/CMakeLists.txt', 'w') as fout, - ): - for line in f.readlines(): - line = line.replace('myproject', model.config.get_project_name()) - line = line.replace('mystamp', model.config.get_config_value('Stamp')) - - if 'set(FPGA_DEVICE' in line: - line = f' set(FPGA_DEVICE "{device}")\n' - - if 'set(USER_FPGA_FLAGS' in line: - line += f'set(USER_FPGA_FLAGS -Xsclock={period}ns; ${{USER_FPGA_FLAGS}})\n' - if not hyper: - line += 'set(USER_FPGA_FLAGS -Xsoptimize=latency; ${USER_FPGA_FLAGS})\n' - - fout.write(line) - - def write_nnet_utils(self, model): - """Copy the nnet_utils, AP types headers and any custom source to the project output directory - - Args: - model (ModelGraph): the hls4ml model. - """ - - # nnet_utils - filedir = os.path.dirname(os.path.abspath(__file__)) - - srcpath = os.path.join(filedir, '../templates/oneapi/firmware/nnet_utils/') - dstpath = f'{model.config.get_output_dir()}/src/firmware/nnet_utils/' - - if not os.path.exists(dstpath): - os.mkdir(dstpath) - - headers = [os.path.basename(h) for h in glob.glob(srcpath + '*.h')] - - for h in headers: - copyfile(srcpath + h, dstpath + h) - - # custom source - filedir = os.path.dirname(os.path.abspath(__file__)) - - custom_source = get_backend('oneAPI').get_custom_source() - for dst, srcpath in custom_source.items(): - dstpath = f'{model.config.get_output_dir()}/src/firmware/{dst}' - copyfile(srcpath, dstpath) - - def __get_table_size(self, model, activation): - for layer in model.get_layers(): - if ( - layer.get_attr('activation') == activation or layer.get_attr('recurrent_activation') == activation - ) and layer.get_attr('table_size') is not None: - return int(layer.get_attr('table_size')) - return 1024 - - def __get_table_header(self, table_name, table_size): - table_header = f'static const typename CONFIG_T::table_t {table_name}[{table_size}] = {{' - return table_header - - def __write_elu_table(self, model, path): - table_name = 'elu_table' - table_size = self.__get_table_size(model, 'elu') - - h_file = open(f'{path}/{table_name}.tb', 'w') - h_file.write(self.__get_table_header(table_name, table_size)) - - sep = '' - for i in range(table_size): - in_val = -8.0 * i / float(table_size) - real_val = np.exp(in_val) - 1.0 - h_file.write(sep + str(real_val)) - sep = ', ' - - h_file.write('};\n') - h_file.close() - - def __write_sigmoid_table(self, model, path): - MAX_VALUE = 8 - MIN_VALUE = 0 - - table_name = 'sigmoid_table' - table_size = self.__get_table_size(model, 'sigmoid') - - h_file = open(f'{path}/{table_name}.tb', 'w') - h_file.write(self.__get_table_header(table_name, table_size)) - - sep = '' - for i in range(int(table_size)): - in_val = ( - i * (MAX_VALUE - MIN_VALUE) / float(table_size) - + (MAX_VALUE - MIN_VALUE) / (float(table_size) * 2) - + MIN_VALUE - ) - real_val = 1.0 / (1 + np.exp(-in_val)) - if real_val >= 0.5: - h_file.write(sep + str(real_val)) - sep = ', ' - - h_file.write('};\n') - h_file.close() - - def __write_tanh_table(self, model, path): - MAX_VALUE = 4 - MIN_VALUE = 0 - - table_name = 'tanh_table' - table_size = self.__get_table_size(model, 'tanh') - - h_file = open(f'{path}/{table_name}.tb', 'w') - h_file.write(self.__get_table_header(table_name, table_size)) - - sep = '' - for i in range(table_size): - in_val = ( - i * (MAX_VALUE - MIN_VALUE) / float(table_size) - + (MAX_VALUE - MIN_VALUE) / (float(table_size) * 2) - + MIN_VALUE - ) - real_val = np.tanh(in_val) - if real_val >= 0: - h_file.write(sep + str(real_val)) - sep = ', ' - - h_file.write('};\n') - h_file.close() - - def __write_softplus_table(self, model, path): - table_name = 'softplus_table' - table_size = self.__get_table_size(model, 'softplus') - - h_file = open(f'{path}/{table_name}.tb', 'w') - h_file.write(self.__get_table_header(table_name, table_size)) - - sep = '' - for i in range(table_size): - in_val = 2 * 8.0 * (i - float(table_size) / 2.0) / float(table_size) - real_val = np.log(np.exp(in_val) + 1.0) - h_file.write(sep + str(real_val)) - sep = ', ' - - h_file.write('};\n') - h_file.close() - - def __write_softsign_table(self, model, path): - MAX_VALUE = 8 - MIN_VALUE = 0 - table_name = 'softsign_table' - table_size = self.__get_table_size(model, 'softsign') - - h_file = open(f'{path}/{table_name}.tb', 'w') - h_file.write(self.__get_table_header(table_name, table_size)) - - sep = '' - for i in range(table_size): - in_val = ( - i * (MAX_VALUE - MIN_VALUE) / float(table_size) - + (MAX_VALUE - MIN_VALUE) / (float(table_size) * 2) - + MIN_VALUE - ) - - real_val = in_val / (np.fabs(in_val) + 1.0) - if real_val >= 0: - h_file.write(sep + str(real_val)) - sep = ', ' - - h_file.write('};\n') - h_file.close() - - def __write_selu_table(self, model, path): - table_name = 'selu_table' - table_size = self.__get_table_size(model, 'selu') - - h_file = open(f'{path}/{table_name}.tb', 'w') - h_file.write(self.__get_table_header(table_name, table_size)) - - sep = '' - for i in range(table_size): - in_val = -8.0 * i / float(table_size) - real_val = 1.0507009873554804934193349852946 * (1.6732632423543772848170429916717 * (np.exp(in_val) - 1.0)) - h_file.write(sep + str(real_val)) - sep = ', ' - - h_file.write('};\n') - h_file.close() - - def __write_exp_table(self, model, path): - table_name = 'exp_table' - table_size = self.__get_table_size(model, 'softmax') - - h_file = open(f'{path}/{table_name}.tb', 'w') - h_file.write(self.__get_table_header(table_name, table_size)) - - # Default fixed point precision - # 6 bits for integer part, 10 bits for decimal - total, 16 - fp_bits = 16 - fp_integer = 6 - fp_signed = True - - # Exp table should use the same precision as exp_table, as seen in Vivado code - # init_exp_table(exp_table); - for layer in model.get_layers(): - if layer.name == 'softmax': - ac_type = layer.get_input_variable().type - if ac_type is not None: - try: - fp_bits = ac_type.precision.integer + ac_type.precision.fractional - fp_integer = ac_type.precision.integer - fp_signed = ac_type.precision.signed - except Exception: - # FixedPrecisionType wasn't correctly stored in layer attributes, use default values - pass - if fp_signed is False: - raise Exception('Softmax types need to be signed') - - sep = '' - N = ceil_log2(table_size) - for i in range(table_size): - f = FixedPointEmulator(fp_bits, fp_integer, signed=fp_signed) - b = uint_to_binary(i, N) - if i == 0: - b.insert(0, 0) - else: - b.insert(0, 1) - f.set_msb_bits(b) - real_val = f.exp_float() - h_file.write(sep + str(real_val)) - sep = ', ' - - h_file.write('};\n') - h_file.close() - - def __write_invert_table(self, model, path): - table_name = 'invert_table' - table_size = self.__get_table_size(model, 'softmax') - - h_file = open(f'{path}/{table_name}.tb', 'w') - h_file.write(self.__get_table_header(table_name, table_size)) - - # Default fixed point precision, in case values from layer attributes cannot be extracted - # 8 bits for integer part, 10 bits for decimal - total, 18 - fp_bits = 18 - fp_integer = 8 - fp_signed = True - - # Invert table should use the same precision as exp_table, as seen in Vivado code - # init_invert_table(invert_table); - for layer in model.get_layers(): - if layer.name == 'softmax': - ac_type = layer.get_attr('exp_table_t') - if ac_type is not None: - try: - fp_bits = ac_type.precision.integer + ac_type.precision.fractional - fp_integer = ac_type.precision.integer - fp_signed = ac_type.precision.signed - except Exception: - # FixedPrecisionType wasn't correctly stored in layer attributes, use default values - pass - if fp_signed is False: - raise Exception('Softmax types need to be signed') - - sep = '' - N = ceil_log2(table_size) - for i in range(table_size): - f = FixedPointEmulator(fp_bits, fp_integer, signed=fp_signed) - b = uint_to_binary(i, N) - b.insert(0, 0) - f.set_msb_bits(b) - real_val = f.inv_float() - h_file.write(sep + str(real_val)) - sep = ', ' - - h_file.write('};\n') - h_file.close() - - def __write_exp_table_latency(self, model, path): - table_name = 'exp_table_latency' - table_size = self.__get_table_size(model, 'softmax') - - h_file = open(f'{path}/{table_name}.tb', 'w') - h_file.write(self.__get_table_header(table_name, table_size)) - - # Default fixed point precision - # 6 bits for integer part, 10 bits for decimal - total, 16 - fp_bits = 16 - fp_integer = 6 - fp_signed = True - - # Exp table should use the same precision as exp_table, as seen in Vivado code - # init_exp_table(exp_table); - for layer in model.get_layers(): - if layer.name == 'softmax': - ac_type = layer.get_input_variable().type - if ac_type is not None: - try: - fp_bits = ac_type.precision.integer + ac_type.precision.fractional - fp_integer = ac_type.precision.integer - fp_signed = ac_type.precision.signed - except Exception: - # FixedPrecisionType wasn't correctly stored in layer attributes, use default values - pass - - sep = '' - N = ceil_log2(table_size) - for i in range(table_size): - f = FixedPointEmulator(fp_bits, fp_integer, signed=fp_signed) - f.set_msb_bits(uint_to_binary(i, N)) - real_val = f.exp_float() - h_file.write(sep + str(real_val)) - sep = ', ' - - h_file.write('};\n') - h_file.close() - - def __write_invert_table_latency(self, model, path): - table_name = 'invert_table_latency' - table_size = self.__get_table_size(model, 'softmax') - - h_file = open(f'{path}/{table_name}.tb', 'w') - h_file.write(self.__get_table_header(table_name, table_size)) - - # Default fixed point precision, in case values from layer attributes cannot be extracted - # 8 bits for integer part, 10 bits for decimal - total, 18 - fp_bits = 18 - fp_integer = 8 - fp_signed = True - - # Invert table should use the same precision as exp_table, as seen in Vivado code - # init_invert_table(invert_table); - for layer in model.get_layers(): - if layer.name == 'softmax': - ac_type = layer.get_attr('exp_table_t') - if ac_type is not None: - try: - fp_bits = ac_type.precision.integer + ac_type.precision.fractional - fp_integer = ac_type.precision.integer - fp_signed = ac_type.precision.signed - except Exception: - # FixedPrecisionType wasn't correctly stored in layer attributes, use default values - pass - - sep = '' - N = ceil_log2(table_size) - for i in range(table_size): - f = FixedPointEmulator(fp_bits, fp_integer, signed=fp_signed) - f.set_msb_bits(uint_to_binary(i, N)) - real_val = f.inv_float() - h_file.write(sep + str(real_val)) - sep = ', ' - - h_file.write('};\n') - h_file.close() - - def __write_exp_table_legacy(self, model, path): - table_name = 'exp_table_legacy' - table_size = self.__get_table_size(model, 'softmax') - - h_file = open(f'{path}/{table_name}.tb', 'w') - h_file.write(self.__get_table_header(table_name, table_size)) - - sep = '' - for i in range(table_size): - in_val = 2 * 8.0 * (i - float(table_size) / 2.0) / float(table_size) - real_val = np.exp(in_val) - h_file.write(sep + str(real_val)) - sep = ', ' - - h_file.write('};\n') - h_file.close() - - def __write_invert_table_legacy(self, model, path): - table_name = 'invert_table_legacy' - table_size = self.__get_table_size(model, 'softmax') - - h_file = open(f'{path}/{table_name}.tb', 'w') - h_file.write(self.__get_table_header(table_name, table_size)) - - sep = '' - for i in range(table_size): - real_val = 0 - in_val = 64.0 * i / float(table_size) - if in_val > 0.0: - real_val = 1.0 / in_val - h_file.write(sep + str(real_val)) - sep = ', ' - - h_file.write('};\n') - h_file.close() - - def write_activation_tables(self, model): - """Write the lookup tables for activation functions - - Args: - model (ModelGraph): the hls4ml model. - """ - # Output path - dstpath = f'{model.config.get_output_dir()}/src/firmware/nnet_utils/activation_tables' - if not os.path.exists(dstpath): - os.mkdir(dstpath) - - # Tables - # TODO - Only write tables needed by model, not all of them - self.__write_elu_table(model, dstpath) - self.__write_sigmoid_table(model, dstpath) - self.__write_tanh_table(model, dstpath) - self.__write_softplus_table(model, dstpath) - self.__write_softsign_table(model, dstpath) - self.__write_selu_table(model, dstpath) - self.__write_exp_table(model, dstpath) - self.__write_invert_table(model, dstpath) - self.__write_exp_table_latency(model, dstpath) - self.__write_invert_table_latency(model, dstpath) - self.__write_exp_table_legacy(model, dstpath) - self.__write_invert_table_legacy(model, dstpath) - - def write_generated_code(self, model): - """Write the generated code (nnet_code_gen.h) - - Args: - model (ModelGraph): the hls4ml model. - """ - path = f'{model.config.get_output_dir()}/src/firmware/nnet_utils/nnet_code_gen.h' - f = open(path) - contents = f.readlines() - f.close() - f = open(path, 'w') - namespace = model.config.get_writer_config().get('Namespace', None) - - for line in contents: - if '// hls4ml insert code' in line: - newline = line - for layer in model.get_layers(): - for generated_code in layer.code.values(): - newline += str(generated_code) - else: - newline = line - if namespace is not None: - if 'namespace nnet' in newline: - newline = newline.replace('namespace nnet', f'namespace {namespace}') - f.write(newline) - f.close() - - def write_yml(self, model): - """Write the config to the YAML file - - Args: - model (ModelGraph): the hls4ml model. - """ - - def keras_model_representer(dumper, keras_model): - model_path = model.config.get_output_dir() + '/keras_model.keras' - keras_model.save(model_path) - return dumper.represent_scalar('!keras_model', model_path) - - try: - from tensorflow.keras import Model as KerasModel - - yaml.add_multi_representer(KerasModel, keras_model_representer) - except Exception: - pass - - with open(model.config.get_output_dir() + '/' + config_filename, 'w') as file: - yaml.dump(model.config.config, file) - - def write_tar(self, model): - """Write the generated project as a .tar.gz archive - - Args: - model (ModelGraph): the hls4ml model. - """ - - if model.config.get_writer_config().get('WriteTar', False): - tar_path = model.config.get_output_dir() + '.tar.gz' - if os.path.exists(tar_path): - os.remove(tar_path) - with tarfile.open(model.config.get_output_dir() + '.tar.gz', mode='w:gz') as archive: - archive.add(model.config.get_output_dir(), recursive=True) - - def write_hls(self, model): - self.write_project_dir(model) - self.write_project_cpp(model) - self.write_project_header(model) - self.write_weights(model) - self.write_defines(model) - self.write_parameters(model) - self.write_test_bench(model) - self.write_bridge(model) - self.write_build_script(model) - self.write_nnet_utils(model) - self.write_activation_tables(model) - self.write_generated_code(model) - self.write_yml(model) - self.write_tar(model) +class OneAPIWriter(AlteraWriter): + compiler_executable = 'icpx' + compiler_definitions = ('HLS4ML_ONEAPI',) diff --git a/test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_activations_io_parallel-oneAPI-elu.json b/test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_activations_io_parallel-Altera-elu.json similarity index 100% rename from test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_activations_io_parallel-oneAPI-elu.json rename to test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_activations_io_parallel-Altera-elu.json diff --git a/test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_activations_io_parallel-oneAPI-leaky_relu.json b/test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_activations_io_parallel-Altera-leaky_relu.json similarity index 100% rename from test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_activations_io_parallel-oneAPI-leaky_relu.json rename to test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_activations_io_parallel-Altera-leaky_relu.json diff --git a/test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_activations_io_parallel-oneAPI-prelu.json b/test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_activations_io_parallel-Altera-prelu.json similarity index 100% rename from test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_activations_io_parallel-oneAPI-prelu.json rename to test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_activations_io_parallel-Altera-prelu.json diff --git a/test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_activations_io_parallel-oneAPI-relu.json b/test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_activations_io_parallel-Altera-relu.json similarity index 100% rename from test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_activations_io_parallel-oneAPI-relu.json rename to test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_activations_io_parallel-Altera-relu.json diff --git a/test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_activations_io_parallel-oneAPI-sigmoid.json b/test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_activations_io_parallel-Altera-sigmoid.json similarity index 100% rename from test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_activations_io_parallel-oneAPI-sigmoid.json rename to test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_activations_io_parallel-Altera-sigmoid.json diff --git a/test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_activations_io_stream-oneAPI-elu.json b/test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_activations_io_stream-Altera-elu.json similarity index 100% rename from test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_activations_io_stream-oneAPI-elu.json rename to test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_activations_io_stream-Altera-elu.json diff --git a/test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_activations_io_stream-oneAPI-leaky_relu.json b/test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_activations_io_stream-Altera-leaky_relu.json similarity index 100% rename from test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_activations_io_stream-oneAPI-leaky_relu.json rename to test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_activations_io_stream-Altera-leaky_relu.json diff --git a/test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_activations_io_stream-oneAPI-prelu.json b/test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_activations_io_stream-Altera-prelu.json similarity index 100% rename from test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_activations_io_stream-oneAPI-prelu.json rename to test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_activations_io_stream-Altera-prelu.json diff --git a/test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_activations_io_stream-oneAPI-relu.json b/test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_activations_io_stream-Altera-relu.json similarity index 100% rename from test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_activations_io_stream-oneAPI-relu.json rename to test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_activations_io_stream-Altera-relu.json diff --git a/test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_activations_io_stream-oneAPI-sigmoid.json b/test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_activations_io_stream-Altera-sigmoid.json similarity index 100% rename from test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_activations_io_stream-oneAPI-sigmoid.json rename to test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_activations_io_stream-Altera-sigmoid.json diff --git a/test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_conv2d_io_parallel-oneAPI-Resource-same-channels_last.json b/test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_conv2d_io_parallel-Altera-Resource-same-channels_last.json similarity index 100% rename from test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_conv2d_io_parallel-oneAPI-Resource-same-channels_last.json rename to test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_conv2d_io_parallel-Altera-Resource-same-channels_last.json diff --git a/test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_dense_io_parallel-oneAPI.json b/test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_dense_io_parallel-Altera.json similarity index 100% rename from test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_dense_io_parallel-oneAPI.json rename to test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_dense_io_parallel-Altera.json diff --git a/test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_dense_io_stream-oneAPI.json b/test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_dense_io_stream-Altera.json similarity index 100% rename from test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_dense_io_stream-oneAPI.json rename to test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_dense_io_stream-Altera.json diff --git a/test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_pooling_oneAPI-channels_last-same-AveragePooling1D.json b/test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_pooling_Altera-channels_last-same-AveragePooling1D.json similarity index 100% rename from test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_pooling_oneAPI-channels_last-same-AveragePooling1D.json rename to test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_pooling_Altera-channels_last-same-AveragePooling1D.json diff --git a/test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_pooling_oneAPI-channels_last-same-AveragePooling2D.json b/test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_pooling_Altera-channels_last-same-AveragePooling2D.json similarity index 100% rename from test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_pooling_oneAPI-channels_last-same-AveragePooling2D.json rename to test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_pooling_Altera-channels_last-same-AveragePooling2D.json diff --git a/test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_pooling_oneAPI-channels_last-same-MaxPooling1D.json b/test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_pooling_Altera-channels_last-same-MaxPooling1D.json similarity index 100% rename from test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_pooling_oneAPI-channels_last-same-MaxPooling1D.json rename to test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_pooling_Altera-channels_last-same-MaxPooling1D.json diff --git a/test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_pooling_oneAPI-channels_last-same-MaxPooling2D.json b/test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_pooling_Altera-channels_last-same-MaxPooling2D.json similarity index 100% rename from test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_pooling_oneAPI-channels_last-same-MaxPooling2D.json rename to test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_pooling_Altera-channels_last-same-MaxPooling2D.json diff --git a/test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_pooling_oneAPI-channels_last-valid-AveragePooling1D.json b/test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_pooling_Altera-channels_last-valid-AveragePooling1D.json similarity index 100% rename from test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_pooling_oneAPI-channels_last-valid-AveragePooling1D.json rename to test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_pooling_Altera-channels_last-valid-AveragePooling1D.json diff --git a/test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_pooling_oneAPI-channels_last-valid-AveragePooling2D.json b/test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_pooling_Altera-channels_last-valid-AveragePooling2D.json similarity index 100% rename from test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_pooling_oneAPI-channels_last-valid-AveragePooling2D.json rename to test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_pooling_Altera-channels_last-valid-AveragePooling2D.json diff --git a/test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_pooling_oneAPI-channels_last-valid-MaxPooling1D.json b/test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_pooling_Altera-channels_last-valid-MaxPooling1D.json similarity index 100% rename from test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_pooling_oneAPI-channels_last-valid-MaxPooling1D.json rename to test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_pooling_Altera-channels_last-valid-MaxPooling1D.json diff --git a/test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_pooling_oneAPI-channels_last-valid-MaxPooling2D.json b/test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_pooling_Altera-channels_last-valid-MaxPooling2D.json similarity index 100% rename from test/pytest/baselines/oneAPI/2025.0.1/test_keras_api_test_pooling_oneAPI-channels_last-valid-MaxPooling2D.json rename to test/pytest/baselines/Altera/2025.0.1/test_keras_api_test_pooling_Altera-channels_last-valid-MaxPooling2D.json diff --git a/test/pytest/ci-template.yml b/test/pytest/ci-template.yml index 4b58559cd9..ac68990e03 100644 --- a/test/pytest/ci-template.yml +++ b/test/pytest/ci-template.yml @@ -28,7 +28,7 @@ - chmod +x cmd_vitis_${VITIS_VERSION}/vitis-run - export PATH=$PWD/cmd_vitis_${VITIS_VERSION}:$PATH - # Load Intel oneAPI environment variables + # Load Intel oneAPI environment variables for the oneAPI backend - source /opt/intel/oneapi/setvars.sh --force script: - cd test/pytest diff --git a/test/pytest/conftest.py b/test/pytest/conftest.py index 7841a1a989..31813239e9 100644 --- a/test/pytest/conftest.py +++ b/test/pytest/conftest.py @@ -58,12 +58,14 @@ def synthesis_config(): 'Vivado': os.getenv('VIVADO_VERSION', '2020.1'), 'Vitis': os.getenv('VITIS_VERSION', '2024.1'), 'Quartus': os.getenv('QUARTUS_VERSION', 'latest'), + 'Altera': os.getenv('AHLS_VERSION', '2026.1.0'), # this doesn't actually work 'oneAPI': os.getenv('ONEAPI_VERSION', '2025.0.1'), }, 'build_args': { 'Vivado': {'csim': False, 'synth': True, 'export': False}, 'Vitis': {'csim': False, 'synth': True, 'export': False}, 'Quartus': {'synth': True, 'fpgasynth': False}, + 'Altera': {'build_type': 'report', 'run': False}, 'oneAPI': {'build_type': 'report', 'run': False}, }, } diff --git a/test/pytest/synthesis_helpers.py b/test/pytest/synthesis_helpers.py index 5d3a7989ea..2b7ef8f97f 100644 --- a/test/pytest/synthesis_helpers.py +++ b/test/pytest/synthesis_helpers.py @@ -92,9 +92,9 @@ def compare_vitis_backend(data, baseline): compare_dicts(data['CSynthesisReport'], baseline['CSynthesisReport'], tolerances) -def compare_oneapi_backend(data, baseline): +def compare_altera_backend(data, baseline): """ - Compare reports from the oneAPI backend. + Compare reports from the Altera backend. Args: data (dict): The current synthesis report. @@ -120,13 +120,15 @@ def compare_oneapi_backend(data, baseline): COMPARE_FUNCS = { 'Vivado': compare_vitis_backend, 'Vitis': compare_vitis_backend, - 'oneAPI': compare_oneapi_backend, + 'Altera': compare_altera_backend, + 'oneAPI': compare_altera_backend, } EXPECTED_REPORT_KEYS = { 'Vivado': {'CSynthesisReport'}, 'Vitis': {'CSynthesisReport'}, + 'Altera': {'report'}, 'oneAPI': {'report'}, } diff --git a/test/pytest/test_activations.py b/test/pytest/test_activations.py index 7442757ac0..bc55959cb1 100644 --- a/test/pytest/test_activations.py +++ b/test/pytest/test_activations.py @@ -13,7 +13,7 @@ # Variable 'name' is simply used as an identifier for the activation -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Catapult', 'Quartus', 'oneAPI', 'XLS']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Catapult', 'Quartus', 'Altera', 'oneAPI', 'XLS']) @pytest.mark.parametrize('shape, io_type', [((8,), 'io_parallel'), ((8,), 'io_stream'), ((8, 8, 3), 'io_stream')]) @pytest.mark.parametrize( 'activation, name', diff --git a/test/pytest/test_batchnorm.py b/test/pytest/test_batchnorm.py index 8b15e48a57..390d89022d 100644 --- a/test/pytest/test_batchnorm.py +++ b/test/pytest/test_batchnorm.py @@ -29,7 +29,7 @@ def model(request): @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Catapult', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Catapult', 'Altera', 'oneAPI']) @pytest.mark.parametrize('model', [True, False], indirect=True) def test_batchnorm(test_case_id, model, data, backend, io_type): default_precision = 'fixed<32, 1>' diff --git a/test/pytest/test_bit_exact_grouped_conv.py b/test/pytest/test_bit_exact_grouped_conv.py index e84b4ee6e8..bce400453a 100644 --- a/test/pytest/test_bit_exact_grouped_conv.py +++ b/test/pytest/test_bit_exact_grouped_conv.py @@ -48,7 +48,7 @@ def _find(hls_model, cls, name): return next(node for node in hls_model.graph.values() if isinstance(node, cls) and node.name == name) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Altera', 'oneAPI']) @pytest.mark.parametrize('n_chan, groups', [(16, 16), (16, 4), (16, 1)], ids=['depthwise', 'grouped', 'dense']) def test_bit_exact_grouped_conv1d(test_case_id, backend, n_chan, groups): with QuantizerConfigScope(f0=4, i0=4): @@ -60,7 +60,7 @@ def test_bit_exact_grouped_conv1d(test_case_id, backend, n_chan, groups): data = np.random.default_rng(0).standard_normal((1000, 16, n_chan)).astype(np.float32) r_keras = trace_minmax(model, data, return_results=True) - precision = 'ac_fixed<2,0>' if backend == 'oneAPI' else 'ap_fixed<1,0>' + precision = 'ac_fixed<2,0>' if backend in ('Altera', 'oneAPI') else 'ap_fixed<1,0>' hls_config = {'Model': {'Precision': precision, 'ReuseFactor': 1, 'Strategy': 'latency'}} output_dir = str(test_root_path / test_case_id) hls_model = convert_from_keras_model( @@ -70,12 +70,12 @@ def test_bit_exact_grouped_conv1d(test_case_id, backend, n_chan, groups): conv = _find(hls_model, Conv1D, 'cg') _assert_exactly_representable(r_keras, *_result_kif(conv)) - # Per-channel check; skipped for oneAPI, which transposes conv weights after the pass. - if backend != 'oneAPI': + # Per-channel check; skipped for Altera-family backends, which transpose conv weights after the pass. + if backend not in ('Altera', 'oneAPI'): _assert_exactly_representable(r_keras, *produce_kif(conv)) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Altera', 'oneAPI']) @pytest.mark.parametrize('n_chan, groups', [(4, 4), (4, 2), (4, 1)], ids=['depthwise', 'grouped', 'dense']) def test_bit_exact_grouped_conv2d(test_case_id, backend, n_chan, groups): with QuantizerConfigScope(f0=4, i0=4): @@ -87,7 +87,7 @@ def test_bit_exact_grouped_conv2d(test_case_id, backend, n_chan, groups): data = np.random.default_rng(1).standard_normal((500, 8, 8, n_chan)).astype(np.float32) r_keras = trace_minmax(model, data, return_results=True) - precision = 'ac_fixed<2,0>' if backend == 'oneAPI' else 'ap_fixed<1,0>' + precision = 'ac_fixed<2,0>' if backend in ('Altera', 'oneAPI') else 'ap_fixed<1,0>' hls_config = {'Model': {'Precision': precision, 'ReuseFactor': 1, 'Strategy': 'latency'}} output_dir = str(test_root_path / test_case_id) hls_model = convert_from_keras_model( @@ -96,5 +96,5 @@ def test_bit_exact_grouped_conv2d(test_case_id, backend, n_chan, groups): conv = _find(hls_model, Conv2D, 'cg') _assert_exactly_representable(r_keras, *_result_kif(conv)) - if backend != 'oneAPI': + if backend not in ('Altera', 'oneAPI'): _assert_exactly_representable(r_keras, *produce_kif(conv)) diff --git a/test/pytest/test_bit_exact_zeropadding.py b/test/pytest/test_bit_exact_zeropadding.py index bd872f399d..99ea81486a 100644 --- a/test/pytest/test_bit_exact_zeropadding.py +++ b/test/pytest/test_bit_exact_zeropadding.py @@ -26,7 +26,7 @@ test_root_path = Path(__file__).parent -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Altera', 'oneAPI']) @pytest.mark.parametrize('io_type', ['io_parallel']) def test_bit_exact_zeropadding1d(test_case_id, backend, io_type): """ZeroPadding1D between two quantized Conv1D layers must convert via the @@ -41,7 +41,7 @@ def test_bit_exact_zeropadding1d(test_case_id, backend, io_type): data = np.random.default_rng(0).standard_normal((1000, 16, 8)).astype(np.float32) r_keras = trace_minmax(model, data, return_results=True) - precision = 'ac_fixed<2,0>' if backend == 'oneAPI' else 'ap_fixed<1,0>' + precision = 'ac_fixed<2,0>' if backend in ('Altera', 'oneAPI') else 'ap_fixed<1,0>' hls_config = {'Model': {'Precision': precision, 'ReuseFactor': 1, 'Strategy': 'latency'}} output_dir = str(test_root_path / test_case_id) hls_model = convert_from_keras_model( @@ -53,7 +53,7 @@ def test_bit_exact_zeropadding1d(test_case_id, backend, io_type): np.testing.assert_array_equal(r_keras, r_hls) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Altera', 'oneAPI']) @pytest.mark.parametrize('io_type', ['io_parallel']) def test_bit_exact_zeropadding2d(test_case_id, backend, io_type): """ZeroPadding2D between two quantized Conv2D layers must convert via the @@ -68,7 +68,7 @@ def test_bit_exact_zeropadding2d(test_case_id, backend, io_type): data = np.random.default_rng(1).standard_normal((500, 8, 8, 4)).astype(np.float32) r_keras = trace_minmax(model, data, return_results=True) - precision = 'ac_fixed<2,0>' if backend == 'oneAPI' else 'ap_fixed<1,0>' + precision = 'ac_fixed<2,0>' if backend in ('Altera', 'oneAPI') else 'ap_fixed<1,0>' hls_config = {'Model': {'Precision': precision, 'ReuseFactor': 1, 'Strategy': 'latency'}} output_dir = str(test_root_path / test_case_id) hls_model = convert_from_keras_model( diff --git a/test/pytest/test_conv1d.py b/test/pytest/test_conv1d.py index 899e2e6112..a65586914f 100644 --- a/test/pytest/test_conv1d.py +++ b/test/pytest/test_conv1d.py @@ -68,6 +68,8 @@ def hls_model(keras_model, request, test_case_id): [ ('Quartus', 'io_parallel', 'resource'), ('Quartus', 'io_stream', 'resource'), + ('Altera', 'io_parallel', 'resource'), + ('Altera', 'io_stream', 'resource'), ('oneAPI', 'io_parallel', 'resource'), ('oneAPI', 'io_stream', 'resource'), ('Vivado', 'io_parallel', 'resource'), @@ -85,6 +87,8 @@ def hls_model(keras_model, request, test_case_id): ids=[ 'Quartus_io_parallel_resource', 'Quartus_io_stream_resource', + 'Altera_io_parallel_resource', + 'Altera_io_stream_resource', 'oneAPI_io_parallel_resource', 'oneAPI_io_stream_resource', 'Vivado_io_parallel_resource', diff --git a/test/pytest/test_embed.py b/test/pytest/test_embed.py index 6f733cc399..5e4e685d47 100644 --- a/test/pytest/test_embed.py +++ b/test/pytest/test_embed.py @@ -47,11 +47,13 @@ def hls_model(keras_model, request, test_case_id): ('Vitis', 'io_parallel'), ('Quartus', 'io_parallel'), ('Catapult', 'io_parallel'), + ('Altera', 'io_parallel'), ('oneAPI', 'io_parallel'), ('Vivado', 'io_stream'), ('Vitis', 'io_stream'), ('Quartus', 'io_stream'), ('Catapult', 'io_stream'), + ('Altera', 'io_stream'), ('oneAPI', 'io_stream'), ], ids=[ @@ -59,11 +61,13 @@ def hls_model(keras_model, request, test_case_id): 'vitis_parallel', 'quartus_parallel', 'catapult_parallel', + 'altera_parallel', 'oneapi_parallel', 'vivado_stream', 'vitis_stream', 'quartus_stream', 'catapult_stream', + 'altera_stream', 'oneapi_stream', ], indirect=True, diff --git a/test/pytest/test_globalpooling.py b/test/pytest/test_globalpooling.py index 8abefe7a46..c9566d6e2c 100644 --- a/test/pytest/test_globalpooling.py +++ b/test/pytest/test_globalpooling.py @@ -32,7 +32,7 @@ def keras_model_1d(request): return model, model_type, keepdims -@pytest.mark.parametrize('backend', ['Quartus', 'Vitis', 'Vivado', 'Catapult', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Quartus', 'Vitis', 'Vivado', 'Catapult', 'Altera', 'oneAPI']) @pytest.mark.parametrize( 'keras_model_1d', [ @@ -89,7 +89,7 @@ def keras_model_2d(request): return model, model_type, keepdims -@pytest.mark.parametrize('backend', ['Quartus', 'Vitis', 'Vivado', 'Catapult', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Quartus', 'Vitis', 'Vivado', 'Catapult', 'Altera', 'oneAPI']) @pytest.mark.parametrize( 'keras_model_2d', [ diff --git a/test/pytest/test_keras_api.py b/test/pytest/test_keras_api.py index 6bde581929..96a48b4b9b 100644 --- a/test/pytest/test_keras_api.py +++ b/test/pytest/test_keras_api.py @@ -26,7 +26,7 @@ test_root_path = Path(__file__).parent -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI', 'XLS']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI', 'XLS']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_dense(test_case_id, backend, io_type, synthesis_config): if backend == 'XLS' and io_type != 'io_parallel': @@ -97,7 +97,7 @@ def test_dense(test_case_id, backend, io_type, synthesis_config): ids=['relu', 'leaky_relu', 'elu', 'prelu', 'sigmoid'], ) # ThresholdedReLU(theta=1.0)]) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI', 'XLS']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI', 'XLS']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_activations(test_case_id, activation_function, backend, io_type, synthesis_config): if backend == 'XLS' and io_type != 'io_parallel': @@ -140,6 +140,7 @@ def test_activations(test_case_id, activation_function, backend, io_type, synthe ('Vitis', 'Resource'), ('Vitis', 'Latency'), ('Quartus', 'Resource'), + ('Altera', 'Resource'), ('oneAPI', 'Resource'), ('XLS', 'Latency'), ], @@ -236,6 +237,7 @@ def test_conv1d(test_case_id, padds, backend, strategy, io_type, synthesis_confi ('Vitis', 'Resource'), ('Vitis', 'Latency'), ('Quartus', 'Resource'), + ('Altera', 'Resource'), ('oneAPI', 'Resource'), ('XLS', 'Latency'), ], @@ -431,7 +433,7 @@ def test_depthwise1d(test_case_id, backend, io_type, synthesis_config): ) @pytest.mark.parametrize('padds', padds_options) @pytest.mark.parametrize('chans', chans_options) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI', 'XLS']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI', 'XLS']) def test_pooling(test_case_id, pooling, padds, chans, backend, synthesis_config): assert '1D' in pooling.__name__ or '2D' in pooling.__name__ diff --git a/test/pytest/test_keras_v3_api.py b/test/pytest/test_keras_v3_api.py index f4f661fb56..d9bc7246c4 100644 --- a/test/pytest/test_keras_v3_api.py +++ b/test/pytest/test_keras_v3_api.py @@ -29,7 +29,7 @@ test_root_path = Path(__file__).parent -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI', 'Catapult', 'XLS']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI', 'Catapult', 'XLS']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_dense(test_case_id, backend, io_type): if backend == 'XLS' and io_type != 'io_parallel': @@ -93,7 +93,7 @@ def test_dense(test_case_id, backend, io_type): Activation(activation='sigmoid', name='sigmoid'), ], ) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI', 'XLS']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI', 'XLS']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_activations(test_case_id, activation_function, backend, io_type): if backend == 'XLS' and io_type != 'io_parallel': @@ -129,7 +129,7 @@ def test_activations(test_case_id, activation_function, backend, io_type): @pytest.mark.parametrize('padds', padds_options) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI', 'Catapult', 'XLS']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI', 'Catapult', 'XLS']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) @pytest.mark.parametrize('activation', ['elu', 'relu']) def test_conv1d(test_case_id, padds, backend, io_type, activation): @@ -216,7 +216,7 @@ def test_conv1d(test_case_id, padds, backend, io_type, activation): @pytest.mark.parametrize('chans', chans_options) @pytest.mark.parametrize('padds', padds_options) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI', 'Catapult', 'XLS']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI', 'Catapult', 'XLS']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_conv2d(test_case_id, chans, padds, backend, io_type): if backend == 'XLS' and io_type != 'io_parallel': @@ -383,7 +383,7 @@ def test_depthwise1d(test_case_id, backend, io_type): @pytest.mark.parametrize('pooling', pooling_layers) @pytest.mark.parametrize('padds', padds_options) @pytest.mark.parametrize('chans', chans_options) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI', 'Catapult', 'XLS']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI', 'Catapult', 'XLS']) def test_pooling(test_case_id, pooling, padds, chans, backend): assert '1D' in pooling.__name__ or '2D' in pooling.__name__ @@ -504,7 +504,7 @@ def test_pooling(test_case_id, pooling, padds, chans, backend): # assert hls_pool.attributes['pad_right'] == 0 -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Catapult', 'oneAPI', 'XLS']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Catapult', 'Altera', 'oneAPI', 'XLS']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_reused_layer(test_case_id, backend, io_type): if backend == 'XLS' and io_type != 'io_parallel': diff --git a/test/pytest/test_merge.py b/test/pytest/test_merge.py index 9b5e1095bc..7db031ab01 100644 --- a/test/pytest/test_merge.py +++ b/test/pytest/test_merge.py @@ -12,7 +12,7 @@ @pytest.mark.parametrize('merge_layer', [Add, Average, Maximum, Minimum, Multiply, Subtract]) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI', 'XLS']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI', 'XLS']) @pytest.mark.parametrize('swap_inputs', [True, False]) def test_merge(test_case_id, merge_layer, io_type, backend, swap_inputs): if backend == 'XLS' and io_type != 'io_parallel': @@ -50,7 +50,7 @@ def test_merge(test_case_id, merge_layer, io_type, backend, swap_inputs): @pytest.mark.parametrize('axes', [1]) @pytest.mark.parametrize('io_type', ['io_parallel']) # No io_stream implementation yet -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI', 'XLS']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI', 'XLS']) def test_dot(test_case_id, axes, io_type, backend): if backend == 'XLS' and io_type != 'io_parallel': pytest.skip(f'XLS backend only supports IOType: io_parallel, but got: {io_type}') @@ -82,7 +82,7 @@ def test_dot(test_case_id, axes, io_type, backend): @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI', 'XLS']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI', 'XLS']) def test_concatenate1d(test_case_id, io_type, backend): if backend == 'XLS' and io_type != 'io_parallel': pytest.skip(f'XLS backend only supports IOType: io_parallel, but got: {io_type}') @@ -115,7 +115,7 @@ def test_concatenate1d(test_case_id, io_type, backend): @pytest.mark.parametrize('axis', [1, 2]) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI', 'XLS']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI', 'XLS']) def test_concatenate2d(test_case_id, axis, io_type, backend): if backend == 'XLS' and io_type != 'io_parallel': pytest.skip(f'XLS backend only supports IOType: io_parallel, but got: {io_type}') @@ -151,7 +151,7 @@ def test_concatenate2d(test_case_id, axis, io_type, backend): @pytest.mark.parametrize('axis', [1, 2, 3]) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI', 'XLS']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI', 'XLS']) def test_concatenate3d(test_case_id, axis, io_type, backend): if backend == 'XLS' and io_type != 'io_parallel': pytest.skip(f'XLS backend only supports IOType: io_parallel, but got: {io_type}') diff --git a/test/pytest/test_multi_dense.py b/test/pytest/test_multi_dense.py index bf67dc3448..d778ca1d64 100644 --- a/test/pytest/test_multi_dense.py +++ b/test/pytest/test_multi_dense.py @@ -18,6 +18,7 @@ ('Vitis', 'Latency'), ('Vitis', 'Resource'), ('Quartus', 'Resource'), + ('Altera', 'Resource'), ('oneAPI', 'Resource'), ('Catapult', 'Latency'), ('Catapult', 'Resource'), diff --git a/test/pytest/test_multiout_network.py b/test/pytest/test_multiout_network.py index 4d77f637db..778a49583a 100644 --- a/test/pytest/test_multiout_network.py +++ b/test/pytest/test_multiout_network.py @@ -69,7 +69,7 @@ def test_multi_output_nn(test_case_id, model, data, backend: str, io_type: str): assert np.allclose(r_hls[1], r_keras[1], atol=1e-5, rtol=0) -@pytest.mark.parametrize('backend', ['Vivado', 'Quartus', 'Vitis', 'Catapult', 'OneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Quartus', 'Vitis', 'Catapult', 'Altera', 'oneAPI']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) @pytest.mark.parametrize('strategy', ['latency', 'resource']) def test_multi_output_nn_corner_cases( diff --git a/test/pytest/test_oneapi_backend.py b/test/pytest/test_oneapi_backend.py new file mode 100644 index 0000000000..92af9065fe --- /dev/null +++ b/test/pytest/test_oneapi_backend.py @@ -0,0 +1,75 @@ +from pathlib import Path + +import pytest +import tensorflow as tf + +import hls4ml +from hls4ml.backends.altera.altera_backend import AlteraBackend +from hls4ml.backends.oneapi.oneapi_backend import OneAPIBackend +from hls4ml.model.flow import get_flow +from hls4ml.model.optimizer import get_optimizer +from hls4ml.writer.altera_writer import AlteraWriter +from hls4ml.writer.oneapi_writer import OneAPIWriter + + +def test_oneapi_backend_registration(): + altera = hls4ml.backends.get_backend('Altera') + oneapi = hls4ml.backends.get_backend('oneAPI') + + assert type(altera) is AlteraBackend + assert type(oneapi) is OneAPIBackend + assert isinstance(oneapi, AlteraBackend) + assert type(altera.writer) is AlteraWriter + assert type(oneapi.writer) is OneAPIWriter + + for backend_name in ('altera', 'oneapi'): + assert get_optimizer(f'{backend_name}:transform_types') is not None + assert get_optimizer(f'{backend_name}:write_hls') is not None + assert get_flow(f'{backend_name}:ip') is not None + assert get_flow(f'{backend_name}:write').requires == [f'{backend_name}:ip'] + + +@pytest.mark.parametrize( + 'backend,compiler,definition', + [('Altera', 'ahls', None), ('oneAPI', 'icpx', 'HLS4ML_ONEAPI')], +) +@pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) +def test_altera_family_generated_project(tmp_path, backend, compiler, definition, io_type): + keras_model = tf.keras.Sequential([tf.keras.Input(shape=(4,)), tf.keras.layers.Dense(2)]) + config = hls4ml.utils.config_from_keras_model(keras_model, backend=backend) + output_dir = tmp_path / f'{backend}-{io_type}' + + hls_model = hls4ml.converters.convert_from_keras_model( + keras_model, + hls_config=config, + output_dir=str(output_dir), + backend=backend, + io_type=io_type, + ) + hls_model.write() + + cmake = (output_dir / 'CMakeLists.txt').read_text() + assert f'set(CMAKE_CXX_COMPILER {compiler})' in cmake + if definition is None: + assert 'add_compile_definitions(HLS4ML_ONEAPI)' not in cmake + else: + assert f'add_compile_definitions({definition})' in cmake + + compatibility_header = (output_dir / 'src/firmware/nnet_utils/hls4ml_sycl.h').read_text() + assert '#ifdef HLS4ML_ONEAPI' in compatibility_header + assert 'namespace hls4ml_sycl_ext = sycl::ext::intel;' in compatibility_header + assert 'namespace hls4ml_sycl_ext = sycl::ext::altera;' in compatibility_header + + generated_sources = [ + output_dir / 'src/firmware/myproject.h', + output_dir / 'src/firmware/myproject.cpp', + output_dir / 'src/myproject_test.cpp', + output_dir / 'src/myproject_bridge.cpp', + ] + generated_text = '\n'.join(path.read_text() for path in generated_sources) + assert 'hls4ml_sycl_ext::' in generated_text + assert 'hls4ml_sycl_ext::experimental::pipe<' in generated_text + assert 'sycl::ext::altera::' not in generated_text + assert 'sycl::ext::intel::' not in generated_text + + assert Path(output_dir / 'src/firmware/nnet_utils/nnet_common.h').exists() diff --git a/test/pytest/test_pointwiseconv.py b/test/pytest/test_pointwiseconv.py index 50a2785905..9fd43ff1b1 100644 --- a/test/pytest/test_pointwiseconv.py +++ b/test/pytest/test_pointwiseconv.py @@ -23,6 +23,8 @@ [ ('Quartus', 'io_parallel', 'resource', 1), ('Quartus', 'io_stream', 'resource', 1), + ('Altera', 'io_parallel', 'resource', 1), + ('Altera', 'io_stream', 'resource', 1), ('oneAPI', 'io_parallel', 'resource', 1), ('oneAPI', 'io_stream', 'resource', 1), ('Vivado', 'io_parallel', 'resource', 1), @@ -79,7 +81,7 @@ def test_pointwiseconv1d(test_case_id, chans, padds, strides, backend, io_type, hls_model.compile() hls_prediction = hls_model.predict(X_input).reshape(keras_prediction.shape) - if backend != 'XLS' and not (backend in ['Quartus', 'oneAPI'] and io_type == 'io_stream'): + if backend != 'XLS' and not (backend in ['Quartus', 'Altera', 'oneAPI'] and io_type == 'io_stream'): # Quartus io_stream does not currently have a special pointwise implementation assert 'Pointwise' in list(hls_model.graph.values())[1].class_name np.testing.assert_allclose(hls_prediction, keras_prediction, rtol=0, atol=0.001) @@ -93,6 +95,8 @@ def test_pointwiseconv1d(test_case_id, chans, padds, strides, backend, io_type, [ ('Quartus', 'io_parallel', 'resource'), ('Quartus', 'io_stream', 'resource'), + ('Altera', 'io_parallel', 'resource'), + ('Altera', 'io_stream', 'resource'), ('oneAPI', 'io_parallel', 'resource'), ('oneAPI', 'io_stream', 'resource'), ('Vivado', 'io_parallel', 'resource'), @@ -144,7 +148,7 @@ def test_pointwiseconv2d(test_case_id, chans, padds, strides, backend, io_type, hls_model.compile() hls_prediction = hls_model.predict(X_input).reshape(keras_prediction.shape) - if backend != 'XLS' and not (backend in ['Quartus', 'oneAPI'] and io_type == 'io_stream'): + if backend != 'XLS' and not (backend in ['Quartus', 'Altera', 'oneAPI'] and io_type == 'io_stream'): # Quartus io_stream does not currently have a special pointwise implementation assert 'Pointwise' in list(hls_model.graph.values())[1].class_name np.testing.assert_allclose(hls_prediction, keras_prediction, rtol=0, atol=0.001) diff --git a/test/pytest/test_pooling.py b/test/pytest/test_pooling.py index f4822e60ef..313576969c 100644 --- a/test/pytest/test_pooling.py +++ b/test/pytest/test_pooling.py @@ -49,7 +49,7 @@ def keras_model_1d(request, backend): return model, model_type, pads, strides -@pytest.mark.parametrize('backend', ['Quartus', 'Vitis', 'Vivado', 'Catapult', 'oneAPI', 'XLS']) +@pytest.mark.parametrize('backend', ['Quartus', 'Vitis', 'Vivado', 'Catapult', 'Altera', 'oneAPI', 'XLS']) @pytest.mark.parametrize( 'keras_model_1d', [ @@ -92,7 +92,7 @@ def test_pool1d(test_case_id, backend, keras_model_1d, data_1d, io_type): np.testing.assert_allclose(y_keras, y_hls, rtol=0, atol=atol, verbose=True) -@pytest.mark.parametrize('backend', ['Quartus', 'Vitis', 'Vivado', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Quartus', 'Vitis', 'Vivado', 'Altera', 'oneAPI']) @pytest.mark.parametrize( 'keras_model_1d', [ @@ -149,7 +149,7 @@ def keras_model_2d(request, backend): return model, model_type, pads, strides -@pytest.mark.parametrize('backend', ['Quartus', 'Vitis', 'Vivado', 'Catapult', 'oneAPI', 'XLS']) +@pytest.mark.parametrize('backend', ['Quartus', 'Vitis', 'Vivado', 'Catapult', 'Altera', 'oneAPI', 'XLS']) @pytest.mark.parametrize( 'keras_model_2d', [ @@ -192,7 +192,7 @@ def test_pool2d(test_case_id, backend, keras_model_2d, data_2d, io_type): np.testing.assert_allclose(y_keras, y_hls, rtol=0, atol=atol, verbose=True) -@pytest.mark.parametrize('backend', ['Quartus', 'Vitis', 'Vivado', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Quartus', 'Vitis', 'Vivado', 'Altera', 'oneAPI']) @pytest.mark.parametrize( 'keras_model_2d', [ diff --git a/test/pytest/test_pytorch_api.py b/test/pytest/test_pytorch_api.py index cfac61fa4b..36bb7c7795 100644 --- a/test/pytest/test_pytorch_api.py +++ b/test/pytest/test_pytorch_api.py @@ -22,7 +22,7 @@ def forward(self, x): return self.linear(x) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI', 'XLS']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI', 'XLS']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_linear(test_case_id, backend, io_type): if backend == 'XLS' and io_type != 'io_parallel': @@ -76,7 +76,7 @@ def test_linear(test_case_id, backend, io_type): ], ids=['softmax', 'relu', 'tanh', 'leaky_relu', 'elu', 'prelu', 'sigmoid', 'threshold'], ) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI', 'XLS']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI', 'XLS']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_activations(test_case_id, activation_function, backend, io_type): if backend == 'XLS' and io_type != 'io_parallel': @@ -191,7 +191,7 @@ def forward(self, x): ], ids=['softmax', 'relu', 'tanh', 'leaky_relu', 'elu', 'sigmoid', 'threshold'], ) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI', 'XLS']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI', 'XLS']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_activation_functionals(test_case_id, activation_function, backend, io_type): if backend == 'XLS' and io_type != 'io_parallel': @@ -227,7 +227,7 @@ def test_activation_functionals(test_case_id, activation_function, backend, io_t @pytest.mark.parametrize('padds', padds_options) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI', 'XLS']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI', 'XLS']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_conv1d(test_case_id, padds, backend, io_type): if backend == 'XLS' and io_type != 'io_parallel': @@ -336,7 +336,7 @@ def test_conv1d(test_case_id, padds, backend, io_type): @pytest.mark.parametrize('padds', padds_options) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI', 'XLS']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI', 'XLS']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_conv2d(test_case_id, padds, backend, io_type): if backend == 'XLS' and io_type != 'io_parallel': @@ -493,7 +493,7 @@ def test_conv2d(test_case_id, padds, backend, io_type): @pytest.mark.parametrize('pooling', pooling_layers, ids=['MaxPool1d', 'MaxPool2d', 'AvgPool1d', 'AvgPool2d']) @pytest.mark.parametrize('padds', padds_options) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI', 'XLS']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI', 'XLS']) def test_pooling(test_case_id, pooling, padds, backend): assert '1d' in pooling.__name__ or '2d' in pooling.__name__ @@ -613,7 +613,7 @@ def forward(self, x): return self.bn(x) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI', 'XLS']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI', 'XLS']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_bn(test_case_id, backend, io_type): if backend == 'XLS' and io_type != 'io_parallel': @@ -657,7 +657,7 @@ def forward(self, x): # TODO: this test fails for XLS due to PyTorch weights shape mismatch. -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_squeeze(test_case_id, backend, io_type): model = SqueezeModel() @@ -679,8 +679,8 @@ def test_squeeze(test_case_id, backend, io_type): np.testing.assert_allclose(hls_prediction, pytorch_prediction, rtol=1e-2, atol=0.01) - # oneAPI doesn't use the Repack class (and for io_stream does not use inplace variables) - if io_type == 'io_parallel' or backend == 'oneAPI': + # Altera-family backends don't use the Repack class (and for io_stream do not use inplace variables) + if io_type == 'io_parallel' or backend in ('Altera', 'oneAPI'): assert list(hls_model.get_layers())[1].attributes['class_name'] == 'Reshape' assert list(hls_model.get_layers())[1].attributes['target_shape'] == [1, 5] assert list(hls_model.get_layers())[3].attributes['class_name'] == 'Reshape' @@ -692,7 +692,7 @@ def test_squeeze(test_case_id, backend, io_type): assert list(hls_model.get_layers())[3].attributes['target_shape'] == [3] -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI', 'XLS']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI', 'XLS']) def test_flatten(test_case_id, backend): input = torch.randn(1, 1, 5, 5) model = nn.Sequential(nn.Conv2d(1, 32, 5, 1, 1), nn.Flatten(), nn.ReLU()) @@ -736,7 +736,7 @@ def forward(self, x): return x -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI', 'XLS']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI', 'XLS']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_skipped_layers(test_case_id, backend, io_type): if backend == 'XLS' and io_type != 'io_parallel': @@ -771,7 +771,7 @@ def test_skipped_layers(test_case_id, backend, io_type): np.testing.assert_allclose(hls_prediction, pytorch_prediction, rtol=0, atol=5e-2) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI', 'XLS']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI', 'XLS']) @pytest.mark.parametrize('io_type', ['io_parallel']) # Only io_parallel for now @pytest.mark.parametrize('tensor_rank', [2, 3]) def test_remove_transpose(test_case_id, backend, io_type, tensor_rank): @@ -838,7 +838,7 @@ def forward(self, x): np.testing.assert_allclose(hls_prediction, pytorch_prediction, rtol=0, atol=5e-2) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI', 'XLS']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI', 'XLS']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_view(test_case_id, backend, io_type): if backend == 'XLS' and io_type != 'io_parallel': diff --git a/test/pytest/test_qeinsum.py b/test/pytest/test_qeinsum.py index b8711be234..a63a294555 100644 --- a/test/pytest/test_qeinsum.py +++ b/test/pytest/test_qeinsum.py @@ -19,7 +19,7 @@ @pytest.mark.parametrize('strategy', ['latency']) @pytest.mark.parametrize('io_type', ['io_parallel']) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Altera', 'oneAPI']) @pytest.mark.parametrize( 'operation', [ @@ -41,7 +41,10 @@ def test_einsum_dense(test_case_id, backend, io_type, strategy, operation): data = np.random.randn(1000, *inp0_shape).astype(np.float32), np.random.randn(1000, *inp1_shape).astype(np.float32) output_dir = str(test_root_path / test_case_id) hls_config = { - 'Model': {'Precision': 'ap_fixed<1,0>' if backend != 'oneAPI' else 'ac_fixed<2,0>', 'ReuseFactor': 1}, + 'Model': { + 'Precision': 'ac_fixed<2,0>' if backend in ('Altera', 'oneAPI') else 'ap_fixed<1,0>', + 'ReuseFactor': 1, + }, 'Strategy': strategy, } diff --git a/test/pytest/test_qkeras.py b/test/pytest/test_qkeras.py index 25d33080fa..0e33a0311d 100644 --- a/test/pytest/test_qkeras.py +++ b/test/pytest/test_qkeras.py @@ -135,7 +135,7 @@ def randX_100_16(): # https://github.com/fastmachinelearning/hls4ml/issues/381 # @pytest.mark.parametrize('bits', [4, 6, 8]) @pytest.mark.parametrize('bits,alpha', [(4, 1), (4, 'auto_po2')]) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI', 'XLS']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI', 'XLS']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_single_dense_activation_exact(test_case_id, randX_100_16, bits, alpha, backend, io_type): """ @@ -200,7 +200,7 @@ def randX_100_10(): @pytest.mark.parametrize( 'quantizer', [(quantized_tanh(8)), (quantized_sigmoid(5)), (quantized_sigmoid(7, use_real_sigmoid=True))] ) -@pytest.mark.parametrize('backend', ['Vivado', 'Quartus', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Quartus', 'Altera', 'oneAPI']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_quantizer_special(test_case_id, randX_1000_1, quantizer, backend, io_type): """ @@ -239,7 +239,7 @@ def test_quantizer_special(test_case_id, randX_1000_1, quantizer, backend, io_ty (7, 10, binary(), quantized_bits(5, 2), binary(), False, True), ], ) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_btnn(test_case_id, make_btnn, randX_100_10, backend, io_type): model, is_xnor, test_no = make_btnn @@ -282,7 +282,7 @@ def randX_1000_1(): (quantized_relu(10, 5)), ], ) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_quantizer(test_case_id, randX_1000_1, quantizer, backend, io_type): """ @@ -317,7 +317,7 @@ def test_quantizer(test_case_id, randX_1000_1, quantizer, backend, io_type): (quantized_relu(10, 2, negative_slope=0.25)), ], ) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_relu_negative_slope(test_case_id, randX_1000_1, quantizer, backend, io_type): """ @@ -398,7 +398,7 @@ def test_qactivation_kwarg(test_case_id, randX_100_10, activation_quantizer, wei assert sum(wrong) / len(wrong) <= 0.005 -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_quantizer_parsing(test_case_id, randX_100_10, backend, io_type): X = randX_100_10 @@ -437,7 +437,7 @@ def randX_100_8_8_1(): return np.random.rand(100, 8, 8, 1) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_qconv2dbn(test_case_id, randX_100_8_8_1, backend, io_type): """ @@ -518,7 +518,7 @@ def test_qdepthwiseconv2d(test_case_id, randX_10_32_32_3, backend, io_type): np.testing.assert_allclose(y_qkeras, y_hls4ml.reshape(y_qkeras.shape), rtol=1e-2, atol=0.01) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) @pytest.mark.parametrize('strategy', ['Latency', 'Resource']) def test_quantised_po2_bit_width(test_case_id, backend, io_type, strategy): @@ -556,7 +556,7 @@ def test_quantised_po2_bit_width(test_case_id, backend, io_type, strategy): np.testing.assert_allclose(y_hls.flatten(), y_keras.flatten(), rtol=2e-2) -@pytest.mark.parametrize('backend', ['Quartus', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Quartus', 'Altera', 'oneAPI']) def test_qsimplernn(test_case_id, backend): """ Test proper handling of QSimpleRNN. @@ -591,7 +591,7 @@ def test_qsimplernn(test_case_id, backend): np.testing.assert_allclose(y_qkeras, y_hls4ml.reshape(y_qkeras.shape), atol=0.1) -@pytest.mark.parametrize('backend', ['Vivado', 'Quartus', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Quartus', 'Altera', 'oneAPI']) def test_qlstm(test_case_id, backend): """ Test proper handling of QLSTM. @@ -627,7 +627,7 @@ def test_qlstm(test_case_id, backend): np.testing.assert_allclose(y_qkeras, y_hls4ml.reshape(y_qkeras.shape), atol=0.1) -@pytest.mark.parametrize('backend', ['Vivado', 'Quartus', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Quartus', 'Altera', 'oneAPI']) def test_qgru(test_case_id, backend): """ Test proper handling of QGRU. diff --git a/test/pytest/test_qkerasV3.py b/test/pytest/test_qkerasV3.py index cd9429108b..c48437e2e2 100644 --- a/test/pytest/test_qkerasV3.py +++ b/test/pytest/test_qkerasV3.py @@ -148,7 +148,7 @@ def randX_100_16(): # https://github.com/fastmachinelearning/hls4ml/issues/381 # @pytest.mark.parametrize('bits', [4, 6, 8]) @pytest.mark.parametrize('bits,alpha', [(4, 1), (4, 'auto_po2')]) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_single_dense_activation_exact(test_case_id, randX_100_16, bits, alpha, backend, io_type): """ @@ -211,7 +211,7 @@ def randX_100_10(): @pytest.mark.parametrize( 'quantizer', [(quantized_tanh(8)), (quantized_sigmoid(5)), (quantized_sigmoid(7, use_real_sigmoid=True))] ) -@pytest.mark.parametrize('backend', ['Vivado', 'Quartus', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Quartus', 'Altera', 'oneAPI']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_quantizer_special(test_case_id, randX_1000_1, quantizer, backend, io_type): """ @@ -250,7 +250,7 @@ def test_quantizer_special(test_case_id, randX_1000_1, quantizer, backend, io_ty (7, 10, binary(), quantized_bits(5, 2), binary(), False, True), ], ) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_btnn(test_case_id, make_btnn, randX_100_10, backend, io_type): model, is_xnor, test_no = make_btnn @@ -293,7 +293,7 @@ def randX_1000_1(): (quantized_relu(10, 5)), ], ) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_quantizer(test_case_id, randX_1000_1, quantizer, backend, io_type): """ @@ -328,7 +328,7 @@ def test_quantizer(test_case_id, randX_1000_1, quantizer, backend, io_type): (quantized_relu(10, 2, negative_slope=0.25)), ], ) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_relu_negative_slope(test_case_id, randX_1000_1, quantizer, backend, io_type): """ @@ -400,7 +400,7 @@ def test_qactivation_kwarg(test_case_id, randX_100_10, activation_quantizer, wei assert sum(wrong) / len(wrong) <= 0.005 -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Altera', 'oneAPI']) @pytest.mark.parametrize('io_type', ['io_parallel']) def test_qkeras_einsum_dense(test_case_id, randX_100_10, backend, io_type): """ @@ -440,7 +440,7 @@ def test_qkeras_einsum_dense(test_case_id, randX_100_10, backend, io_type): np.testing.assert_array_equal(y_qkeras, y_hls4ml.reshape(y_qkeras.shape)) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_quantizer_parsing(test_case_id, randX_100_10, backend, io_type): X = randX_100_10 @@ -479,7 +479,7 @@ def randX_100_8_8_1(): return np.random.rand(100, 8, 8, 1) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) @pytest.mark.parametrize( 'qconv_layer,input_shape,input_data_shape,layer_kwargs', @@ -531,7 +531,7 @@ def test_qconv_activation_kwarg(test_case_id, qconv_layer, input_shape, input_da np.testing.assert_array_equal(y_qkeras, y_hls4ml.reshape(y_qkeras.shape)) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_qconv2dbn(test_case_id, randX_100_8_8_1, backend, io_type): """ @@ -621,7 +621,7 @@ def test_qdepthwiseconv2d(test_case_id, randX_10_32_32_3, backend, io_type, quan np.testing.assert_allclose(y_qkeras, y_hls4ml.reshape(y_qkeras.shape), rtol=1e-2, atol=0.01) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) @pytest.mark.parametrize('strategy', ['Latency', 'Resource']) def test_quantised_po2_bit_width(test_case_id, backend, io_type, strategy): @@ -659,7 +659,7 @@ def test_quantised_po2_bit_width(test_case_id, backend, io_type, strategy): np.testing.assert_allclose(y_hls.flatten(), y_keras.flatten(), rtol=2e-2) -@pytest.mark.parametrize('backend', ['Quartus', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Quartus', 'Altera', 'oneAPI']) def test_qsimplernn(test_case_id, backend): """ Test proper handling of QSimpleRNN. @@ -694,7 +694,7 @@ def test_qsimplernn(test_case_id, backend): np.testing.assert_allclose(y_qkeras, y_hls4ml.reshape(y_qkeras.shape), atol=0.1) -@pytest.mark.parametrize('backend', ['Vivado', 'Quartus', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Quartus', 'Altera', 'oneAPI']) def test_qlstm(test_case_id, backend): """ Test proper handling of QLSTM. @@ -730,7 +730,7 @@ def test_qlstm(test_case_id, backend): np.testing.assert_allclose(y_qkeras, y_hls4ml.reshape(y_qkeras.shape), atol=0.1) -@pytest.mark.parametrize('backend', ['Vivado', 'Quartus', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Quartus', 'Altera', 'oneAPI']) def test_qgru(test_case_id, backend): """ Test proper handling of QGRU. diff --git a/test/pytest/test_qonnx.py b/test/pytest/test_qonnx.py index a467dd170d..dfc9042905 100644 --- a/test/pytest/test_qonnx.py +++ b/test/pytest/test_qonnx.py @@ -506,6 +506,7 @@ def test_simple_model(test_case_id, request, model_name, io_type, backend): ('Catapult', 'Latency'), ('Vitis', 'Resource'), ('Vitis', 'Latency'), + ('Altera', 'Resource'), ('oneAPI', 'Resource'), ], ) diff --git a/test/pytest/test_recurrent_pytorch.py b/test/pytest/test_recurrent_pytorch.py index b0157d7e99..61c68e878f 100644 --- a/test/pytest/test_recurrent_pytorch.py +++ b/test/pytest/test_recurrent_pytorch.py @@ -31,7 +31,7 @@ def forward(self, x): return output -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI']) @pytest.mark.parametrize('io_type', ['io_parallel']) def test_gru(test_case_id, backend, io_type): model = GRUNet() @@ -62,7 +62,7 @@ def test_gru(test_case_id, backend, io_type): np.testing.assert_allclose(hls_prediction, pytorch_prediction, rtol=0, atol=1e-1) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI']) @pytest.mark.parametrize('io_type', ['io_stream']) def test_gru_stream(test_case_id, backend, io_type): model = GRUNetStream() @@ -107,7 +107,7 @@ def forward(self, x): return output -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI']) @pytest.mark.parametrize('io_type', ['io_parallel']) def test_lstm(test_case_id, backend, io_type): model = LSTM() @@ -148,10 +148,10 @@ def test_lstm(test_case_id, backend, io_type): np.testing.assert_allclose(hls_prediction, pytorch_prediction, rtol=0, atol=1e-1) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Altera', 'oneAPI']) @pytest.mark.parametrize('io_type', ['io_stream']) def test_lstm_stream(test_case_id, backend, io_type): - if not (backend in ('Quartus', 'oneAPI') and io_type == 'io_stream'): + if not (backend in ('Quartus', 'Altera', 'oneAPI') and io_type == 'io_stream'): model = LSTMStream() model.eval() @@ -190,10 +190,10 @@ def forward(self, x, h0): return output -@pytest.mark.parametrize('backend', ['Quartus', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Quartus', 'Altera', 'oneAPI']) @pytest.mark.parametrize('io_type', ['io_parallel']) def test_rnn(test_case_id, backend, io_type): - if not (backend in ('Quartus', 'oneAPI') and io_type == 'io_stream'): + if not (backend in ('Quartus', 'Altera', 'oneAPI') and io_type == 'io_stream'): model = RNN() model.eval() diff --git a/test/pytest/test_repack_stream.py b/test/pytest/test_repack_stream.py index f24906647a..b9e32dfe3e 100644 --- a/test/pytest/test_repack_stream.py +++ b/test/pytest/test_repack_stream.py @@ -9,7 +9,7 @@ test_root_path = Path(__file__).parent -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Catapult', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Catapult', 'Altera', 'oneAPI']) def test_repack_precision(test_case_id, backend: str): inp = keras.Input(shape=(3, 3), name='inp') out = keras.layers.Reshape((3, 3), name='reshape')(inp) @@ -34,7 +34,7 @@ def test_repack_precision(test_case_id, backend: str): ) model_hls.write() # Not needed for this test, but useful for debugging - reshape_name = 'reshape' if backend == 'oneAPI' else 'repack_reshape' + reshape_name = 'reshape' if backend in ('Altera', 'oneAPI') else 'repack_reshape' assert reshape_name in model_hls.graph, f'{reshape_name} not found in graph' repack_precision = model_hls.graph[reshape_name].attributes['result_t'].precision assert repack_precision.integer == 10, 'Precision mismatch' @@ -47,6 +47,7 @@ def test_repack_precision(test_case_id, backend: str): 'backend, strategy', [ ('Quartus', 'Resource'), + ('Altera', 'Resource'), ('oneAPI', 'Resource'), ('Vivado', 'Resource'), ('Vitis', 'Resource'), diff --git a/test/pytest/test_report.py b/test/pytest/test_report.py index 5424102235..ffa4547eeb 100644 --- a/test/pytest/test_report.py +++ b/test/pytest/test_report.py @@ -26,8 +26,8 @@ def copy_vivado_report(output_dir, test_report_dir): return -def copy_oneapi_report(output_dir, test_report_dir): - # copy pregenerated oneAPI reports +def copy_altera_report(output_dir, test_report_dir): + # copy pregenerated Altera reports json_dir = f'{output_dir}/build/myproject.fpga.prj/reports/resources/json' os.makedirs(json_dir, exist_ok=True) shutil.copy(test_report_dir / 'quartus.ndjson', f'{json_dir}/quartus.ndjson') @@ -74,13 +74,13 @@ def backend_configs(): + ' LUT: 1526\n' + ' URAM: N/A\n\n', }, - 'oneAPI': { - 'backend': 'oneAPI', + 'Altera': { + 'backend': 'Altera', 'part': 'Agilex7', 'build': {'build_type': 'fpga'}, - 'copy_func': copy_oneapi_report, - 'parse_func': hls4ml.report.parse_oneapi_report, - 'print_func': hls4ml.report.print_oneapi_report, + 'copy_func': copy_altera_report, + 'parse_func': hls4ml.report.parse_altera_report, + 'print_func': hls4ml.report.print_altera_report, 'expected_outcome': '\n' + '==================================================\n' + '== FPGA Hardware Synthesis\n' @@ -100,6 +100,7 @@ def backend_configs(): + ' ALMs: 4520.0 N/A N/A \n\n', }, } + config_dict['oneAPI'] = {**config_dict['Altera'], 'backend': 'oneAPI', 'report_backend': 'Altera'} return config_dict @@ -116,7 +117,8 @@ def hls_model_setup(request, test_case_id, backend_configs, tmp_path): config = hls4ml.utils.config_from_keras_model(model, granularity='model') output_dir = str(tmp_path / test_case_id) - test_report_dir = test_root_path / f'test_report/{backend_config["backend"]}' + report_backend = backend_config.get('report_backend', backend_config['backend']) + test_report_dir = test_root_path / f'test_report/{report_backend}' hls_model = hls4ml.converters.convert_from_keras_model( model, @@ -128,7 +130,7 @@ def hls_model_setup(request, test_case_id, backend_configs, tmp_path): ) hls_model.write() - # to actually generate the reports (using Vivado 2020.1 or oneAPI 2025.0) + # to actually generate the reports (using Vivado 2020.1 or Altera 2025.0) # hls_model.build(**(backend_config['build'])) backend_config['copy_func'](output_dir, test_report_dir) @@ -136,7 +138,7 @@ def hls_model_setup(request, test_case_id, backend_configs, tmp_path): yield output_dir, backend_config -@pytest.mark.parametrize('hls_model_setup', ['Vivado', 'oneAPI'], indirect=True) +@pytest.mark.parametrize('hls_model_setup', ['Vivado', 'Altera', 'oneAPI'], indirect=True) def test_report(hls_model_setup, capsys): """Tests that the report parsing and printing functions work for different backends.""" output_dir, backend_config = hls_model_setup diff --git a/test/pytest/test_report/oneAPI/loop_attr.ndjson b/test/pytest/test_report/Altera/loop_attr.ndjson similarity index 100% rename from test/pytest/test_report/oneAPI/loop_attr.ndjson rename to test/pytest/test_report/Altera/loop_attr.ndjson diff --git a/test/pytest/test_report/oneAPI/quartus.ndjson b/test/pytest/test_report/Altera/quartus.ndjson similarity index 100% rename from test/pytest/test_report/oneAPI/quartus.ndjson rename to test/pytest/test_report/Altera/quartus.ndjson diff --git a/test/pytest/test_report/oneAPI/summary.ndjson b/test/pytest/test_report/Altera/summary.ndjson similarity index 100% rename from test/pytest/test_report/oneAPI/summary.ndjson rename to test/pytest/test_report/Altera/summary.ndjson diff --git a/test/pytest/test_reshape.py b/test/pytest/test_reshape.py index 25fd6418c9..8bb448ab92 100755 --- a/test/pytest/test_reshape.py +++ b/test/pytest/test_reshape.py @@ -20,7 +20,7 @@ def randX_20_10(): return randX(20, 10) -@pytest.mark.parametrize('backend', ['Vivado', 'Quartus', 'Catapult', 'oneAPI', 'XLS']) +@pytest.mark.parametrize('backend', ['Vivado', 'Quartus', 'Catapult', 'Altera', 'oneAPI', 'XLS']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_reshape_parallel(test_case_id, randX_20_10, backend, io_type): if backend == 'XLS' and io_type != 'io_parallel': diff --git a/test/pytest/test_rnn.py b/test/pytest/test_rnn.py index b259888653..c2092b3d94 100644 --- a/test/pytest/test_rnn.py +++ b/test/pytest/test_rnn.py @@ -159,12 +159,14 @@ def create_model_accuracy(rnn_layer, return_sequences): 'rnn_layer, backend, io_type, strategy', [ (SimpleRNN, 'Quartus', 'io_parallel', 'resource'), + (SimpleRNN, 'Altera', 'io_parallel', 'resource'), (SimpleRNN, 'oneAPI', 'io_parallel', 'resource'), (LSTM, 'Vivado', 'io_parallel', 'resource'), (LSTM, 'Vivado', 'io_parallel', 'latency'), (LSTM, 'Vitis', 'io_parallel', 'resource'), (LSTM, 'Vitis', 'io_parallel', 'latency'), (LSTM, 'Quartus', 'io_parallel', 'resource'), + (LSTM, 'Altera', 'io_parallel', 'resource'), (LSTM, 'oneAPI', 'io_parallel', 'resource'), (LSTM, 'Vivado', 'io_stream', 'resource'), (LSTM, 'Vivado', 'io_stream', 'latency'), @@ -175,12 +177,14 @@ def create_model_accuracy(rnn_layer, return_sequences): (GRU, 'Vitis', 'io_parallel', 'resource'), (GRU, 'Vitis', 'io_parallel', 'latency'), (GRU, 'Quartus', 'io_parallel', 'resource'), + (GRU, 'Altera', 'io_parallel', 'resource'), (GRU, 'oneAPI', 'io_parallel', 'resource'), (GRU, 'Vivado', 'io_stream', 'resource'), (GRU, 'Vivado', 'io_stream', 'latency'), (GRU, 'Vitis', 'io_stream', 'resource'), (GRU, 'Vitis', 'io_stream', 'latency'), (GRU, 'Quartus', 'io_stream', 'resource'), + (GRU, 'Altera', 'io_stream', 'resource'), (GRU, 'oneAPI', 'io_stream', 'resource'), (Bidirectional, 'Vivado', 'io_parallel', 'resource'), (Bidirectional, 'Vivado', 'io_parallel', 'latency'), @@ -189,12 +193,14 @@ def create_model_accuracy(rnn_layer, return_sequences): ], ids=[ 'SimpleRNN-Quartus-io_parallel-resource', + 'SimpleRNN-Altera-io_parallel-resource', 'SimpleRNN-oneAPI-io_parallel-resource', 'LSTM-Vivado-io_parallel-resource', 'LSTM-Vivado-io_parallel-latency', 'LSTM-Vitis-io_parallel-resource', 'LSTM-Vitis-io_parallel-latency', 'LSTM-Quartus-io_parallel-resource', + 'LSTM-Altera-io_parallel-resource', 'LSTM-oneAPI-io_parallel-resource', 'LSTM-Vivado-io_stream-resource', 'LSTM-Vivado-io_stream-latency', @@ -205,12 +211,14 @@ def create_model_accuracy(rnn_layer, return_sequences): 'GRU-Vitis-io_parallel-resource', 'GRU-Vitis-io_parallel-latency', 'GRU-Quartus-io_parallel-resource', + 'GRU-Altera-io_parallel-resource', 'GRU-oneAPI-io_parallel-resource', 'GRU-Vivado-io_stream-resource', 'GRU-Vivado-io_stream-latency', 'GRU-Vitis-io_stream-resource', 'GRU-Vitis-io_stream-latency', 'GRU-Quartus-io_stream-resource', + 'GRU-Altera-io_stream-resource', 'GRU-oneAPI-io_stream-resource', 'Bidirectional-Vivado-io_parallel-resource', 'Bidirectional-Vivado-io_parallel-latency', diff --git a/test/pytest/test_serialization.py b/test/pytest/test_serialization.py index eb403d74e3..7999c71fda 100644 --- a/test/pytest/test_serialization.py +++ b/test/pytest/test_serialization.py @@ -32,7 +32,7 @@ def qkeras_model(input_shape): return model -@pytest.mark.parametrize('backend', ['Vitis', 'Catapult', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vitis', 'Catapult', 'Altera', 'oneAPI']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_save_load__model(test_case_id, io_type, backend): input_shape = (8, 8, 3) @@ -155,7 +155,7 @@ def test_save_load_testbench_data(test_case_id, backend, input_configured, outpu assert hls_model_clone.config.config.get('OutputPredictions') is None -@pytest.mark.parametrize('backend', ['Vitis']) # Disabling OneAPI for now excessive run time +@pytest.mark.parametrize('backend', ['Vitis']) # Disabling Altera for now due to excessive run time def test_save_load_qonnx_model(test_case_id, backend): dl_file = str(example_model_path / 'onnx/branched_model_ch_last.onnx') @@ -194,7 +194,7 @@ def test_save_load_qonnx_model(test_case_id, backend): np.testing.assert_equal(y_original, y_clone) -@pytest.mark.parametrize('backend', ['Vitis', 'Catapult', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vitis', 'Catapult', 'Altera', 'oneAPI']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def test_linking_project(test_case_id, io_type, backend): input_shape = (8, 8, 3) diff --git a/test/pytest/test_stream_clone.py b/test/pytest/test_stream_clone.py index 0931770ce5..03ff13db35 100644 --- a/test/pytest/test_stream_clone.py +++ b/test/pytest/test_stream_clone.py @@ -70,7 +70,7 @@ def data(): return X -@pytest.mark.parametrize('backend', ['Vivado', 'Quartus', 'Vitis', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Quartus', 'Vitis', 'Altera', 'oneAPI']) def test_multi_clone(test_case_id, model_multi_clone, data, backend: str): output_dir = str(test_root_path / test_case_id) hls_config = hls4ml.utils.config_from_keras_model( @@ -90,7 +90,7 @@ def test_multi_clone(test_case_id, model_multi_clone, data, backend: str): np.testing.assert_allclose(r_hls, r_keras, atol=1e-4, rtol=0) -@pytest.mark.parametrize('backend', ['Vivado', 'Quartus', 'Vitis', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Quartus', 'Vitis', 'Altera', 'oneAPI']) def test_clone_precision_inheritance(test_case_id, model_clone_precision_inheritance, data, backend: str): output_dir = str(test_root_path / test_case_id) layer_config = { diff --git a/test/pytest/test_transpose_concat.py b/test/pytest/test_transpose_concat.py index 6b0866e797..7590f8c207 100644 --- a/test/pytest/test_transpose_concat.py +++ b/test/pytest/test_transpose_concat.py @@ -49,10 +49,12 @@ def hls_model(keras_model, request, test_case_id): ('io_stream', 'Vivado'), ('io_stream', 'Vitis'), ('io_stream', 'Quartus'), + ('io_stream', 'Altera'), ('io_stream', 'oneAPI'), ('io_parallel', 'Vivado'), ('io_parallel', 'Vitis'), ('io_parallel', 'Quartus'), + ('io_parallel', 'Altera'), ('io_parallel', 'oneAPI'), ], indirect=True, @@ -60,10 +62,12 @@ def hls_model(keras_model, request, test_case_id): 'vivado_stream', 'vitis_streamq', 'quartus_stream', + 'altera_stream', 'oneapi_stream', 'vivado_parallel', 'vitis_parallel', 'quartus_parallel', + 'altera_parallel', 'oneapi_parallel', ], ) @@ -93,7 +97,7 @@ def data_highdim(): @pytest.mark.parametrize('io_type', ['io_stream', 'io_parallel']) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Altera', 'oneAPI']) def test_highdim_permute(test_case_id, data_highdim, keras_model_highdim, io_type, backend): X = data_highdim model = keras_model_highdim diff --git a/test/pytest/test_upsampling.py b/test/pytest/test_upsampling.py index 0252b55708..e084f768d7 100644 --- a/test/pytest/test_upsampling.py +++ b/test/pytest/test_upsampling.py @@ -46,7 +46,7 @@ def keras_model_2d(): @pytest.mark.parametrize('io_type', ['io_stream', 'io_parallel']) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Catapult', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Catapult', 'Altera', 'oneAPI']) @pytest.mark.parametrize('model_type', ['1d', '2d']) def test_upsampling(test_case_id, keras_model_1d, keras_model_2d, data_1d, data_2d, model_type, io_type, backend): if model_type == '1d': diff --git a/test/pytest/test_zeropadding.py b/test/pytest/test_zeropadding.py index 29d366bdb9..a6e2a9810b 100644 --- a/test/pytest/test_zeropadding.py +++ b/test/pytest/test_zeropadding.py @@ -50,7 +50,7 @@ def keras_model_2d(): @pytest.mark.parametrize('io_type', ['io_stream', 'io_parallel']) -@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Catapult', 'oneAPI']) +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Catapult', 'Altera', 'oneAPI']) @pytest.mark.parametrize('model_type', ['1d', '2d']) def test_zeropadding(test_case_id, keras_model_1d, keras_model_2d, data_1d, data_2d, model_type, io_type, backend): if model_type == '1d':