Metadata-Version: 2.2
Name: aidge_export_arm_cortexm
Version: 0.10.0.post2
Summary: Aidge export for ARM CortexM systems
License: Eclipse Public License 2.0 (EPL-2.0)
Classifier: Development Status :: 2 - Pre-Alpha
Classifier: Programming Language :: Python :: 3
Project-URL: Homepage, https://www.deepgreen.ai/en/platform
Project-URL: Documentation, https://eclipse.dev/aidge/
Project-URL: Repository, https://gitlab.eclipse.org/eclipse/aidge/aidge_export_arm_cortexm
Project-URL: Issues, https://gitlab.eclipse.org/eclipse/aidge/aidge/-/issues
Project-URL: Changelog, https://gitlab.eclipse.org/eclipse/aidge/aidge/-/releases
Requires-Python: >=3.10
Requires-Dist: numpy>=1.21.6
Requires-Dist: onnx>=1.16.0
Requires-Dist: Jinja2>=3.1.2
Requires-Dist: pyocd>=0.35.0
Requires-Dist: pyserial>=3.5
Provides-Extra: test
Requires-Dist: pytest; extra == "test"
Provides-Extra: dev
Requires-Dist: ipython; extra == "dev"
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Description-Content-Type: text/markdown

<p align="center">
  <img src="https://gitlab.eclipse.org/eclipse/aidge/aidge/-/raw/main/docs/source/_static/Logotype-aidge.png" alt="Aidge logo" width="800"/>
</p>

[![EPL 2.0](https://img.shields.io/badge/license-EPL%202.0-blue.svg?style=for-the-badge)](https://www.eclipse.org/legal/epl-2.0/)
[![Examples](https://img.shields.io/badge/Gitlab-orange?&logo=gitlab&logoColor=white&style=for-the-badge)](https://gitlab.eclipse.org/eclipse/aidge/aidge)
[![PyPi](https://img.shields.io/badge/Pypi-blue?&logo=python&logoColor=white&style=for-the-badge)](https://pypi.org/project/aidge_export_arm_cortexm/)
[![Examples](https://img.shields.io/badge/Tutorials-darkviolet?&logo=googlescholar&logoColor=white&style=for-the-badge)](https://eclipse.dev/aidge/source/Tutorial/index.html)
[![Documentation Status](https://readthedocs.org/projects/eclipse-aidge/badge/?version=latest&style=for-the-badge)](https://eclipse-aidge.readthedocs.io/en/latest/?badge=latest)
![GitLab Contributors](https://img.shields.io/gitlab/contributors/eclipse%2Faidge%2Faidge?gitlab_url=https%3A%2F%2Fgitlab.eclipse.org&style=for-the-badge)
![Open GitLab Issues](https://img.shields.io/gitlab/issues/open-raw/eclipse%2Faidge%2Faidge?gitlab_url=https%3A%2F%2Fgitlab.eclipse.org%2F&style=for-the-badge)
![Closed GitLab Issues](https://img.shields.io/gitlab/issues/closed-raw/eclipse%2Faidge%2Faidge?gitlab_url=https%3A%2F%2Fgitlab.eclipse.org%2F&style=for-the-badge&color=1591df)


# Aidge Export for ARM CortexM systems

This plugin allow `Aidge` to create exports for ARM CortexM systems.
It generates standalone C/C++ code intended to run on STM32 targets.

```bash
pip install aidge_export_arm_cortexm
```

## Examples

If you want to generate exports and/or better understand how it works in Aidge, you may take a look at the following resources :  

- [Example Scripts](https://gitlab.eclipse.org/eclipse/aidge/aidge/-/tree/main/examples?ref_type=heads) : Python scripts examples to export common models;
- [Export Tutorials](https://gitlab.eclipse.org/eclipse/aidge/aidge/-/tree/main/examples?ref_type=heads) : Notebooks to better understand the export process (you may find these tutorials on the [Aidge documentation page](https://eclipse.dev/aidge/source/Tutorial/index.html#export-tutorials) as well);
- [User Guide](https://eclipse.dev/aidge/source/UserGuide/aidge_export_structure.html) : Theoretical guides detailing the export structure. 

### Supported targets

- STM32F746
- STM32H743
- STM32L4R5
- STM32F413

Additional targets can be added upon request.
Normally, any recent STM32 board should be compatible with the generated code. You must only ensure that the corresponding pack is installed in `pyOCD` (e.g., `stm32h743zitx` for NUCLEO-H743ZI), the `board_config.json` file is properly configured for your board, along with the respective hardware configuration (RAM/Flash size, UART settings, etc.) along with a template project for your board in `aidge/aidge_core/aidge_core/hw_model/targets`.

### Backends

This export provides two backends:

- **`arm_cortexm`**:
  Provides specific optimizations for Convolution and Fully Connected (FC) layers, and is largely based on the `aidge_export_cpp` backend.

- **`CMSIS-NN`**:
  Relies on the optimized kernels provided by [CMSIS-NN](https://github.com/ARM-software/CMSIS-NN).


### Supported layers

#### arm_cortexm backend

| Layer | Supported |
|------|-----------|
| Convolution | ✔️ |
| Depthwise Convolution | ✔️ |
| Convolution Transpose | ✔️ |
| Fully Connected | ✔️ |



#### CMSIS-NN backend

| Layer | Supported |
|------|-----------|
| Add | ✔️ |
| Mul | ❌ |
| Convolution | ✔️ |
| Depthwise Convolution | ✔️ |
| Fully Connected | ✔️ |
| Average Pooling | ✔️ |
| Max Pooling | ✔️ |
| Global Average Pooling | ✔️ |
| Concat | ❌ |
| Reshape | ❌ |
| Softmax | ❌ |

> [!NOTE]:
> - If your model contains unsupported operators, the export will still work. In that case, the implementation from the `aidge_export_cpp` module will be used.  
> - The CMSIS-NN backend only supports quantized models (int8).



### Examples

Several example scripts are available in `aidge/examples`.


| Model | arm_cortexm | CMSIS-NN | Notes |
|------|-------------|----------|-------|
| LeNet | ✔️ | ✔️ | |
| Deep Autoencoder | ✔️ | 🔶 | Generation works, but slight output differences are observed on the last FC layer |
| DS-CNN | ✔️ | ✔️ | |
| MobileNetV1 VWW | ✔️ | ✔️ | |
| ResNet8 | ✔️ | 🔶 | Generation works, but slight output differences are observed on the third convolution |



### Running an example

First of all, please make sure to execute the `setup.sh` script inside your target folder (eg. `aidge/aidge_core/aidge_core/hw_model/targets/stm32/stm32h7/board_files/setup.sh`) before trying to build for the first time. This will download the Drivers/HAL files directly from ST repository.

Then, navigate to the root of the repository and run the following command to execute the LeNet example:

```bash
cd aidge/examples/benchmark
```

Run the Python script:

```bash
python benchmark_model.py --profiling --board stm32h7 --model_path lenet_mnist
```

Run `python <model>.py --help` to see all available options.
The script will generate the export, compile it, flash it on the board, and capture the UART output to measure inference time and perform some benchmarking. Some chats will be generated with the results of the benchmark, and the raw output will be stored in `export_folder/uart_output.txt`.
By default, the compilation will be done using a docker container, but you can also choose to compile it locally by installing the required toolchain (e.g., `arm-none-eabi-gcc` for STM32 targets) and running the command with the `--no_docker` flag.

### Running an tinyML benchmark

This module also provides a template for running the [tinyMLPerf](https://github.com/mlcommons/tiny) benchmark.
The benchmark can be executed for the following models:

| Model | Dataset | Notes |
|------|-------------|-------|
| Deep AutoEncoder | ToyADMOS | |
| DS-CNN | Speech Commands | |
| MobileNetV1 VWW | Visual Wake Words | | 
| ResNet8 | Cifar10 | | 

Each dataset can be evaluated in terms of accuracy and inference time. For the moment, the evaluation of power consumption is not supported, since it relies on external hardware to do the measurement. In order to run the benchmark, you can use the following command :

```bash
python benchmark_model.py --board stm32h7 --model_path resnet8_cifar10 --tinymlbenchmark
```

This will create a different main file that communicates with the tinyML framework to perform the evaluation. Once you have flashed the board, you need to cancel the execution of the script (CTRL+C), and follow the instructions provided by tinyML to run the benchmark. 

```bash
git clone https://github.com/mlcommons/tiny.git
cd tiny

# Install dependencies into virtual environment
python -m venv .venv
source .venv/bin/activate  # On Windows, use `.venv\Scripts\activate`
pip install -r benchmark/training/streaming_wakeword/requirements.txt pyusb libusb
```

To execute the benchmark itself, you'll first need to generate the dataset samples. Each folder inside `tiny/benchmark/training/` contains the instructions to download and generate the samples for each model. For example, for the MobileNetV1 VWW model, you can run:

```bash
cd benchmark/training/visual_wake_words
./download_and_train_vww.sh
python generate_y_labels.py
```

> Note: This procedure may change in the future, so please refer to the official tinyML repository for the most up-to-date instructions on dataset generation and benchmark execution.

Once you have generated the dataset samples (and placed them in the correct directory, which is `tiny/evaluation/datasets/`), you can run the benchmark using the command provided above. 

```bash
cd benchmark/runner
python main.py --dataset_path=../evaluation/datasets/ --test_script=tests_accuracy.yaml --device_list=devices_kws_ic_vww.yaml --mode=a # for accuracy evaluation
python main.py --dataset_path=../evaluation/datasets/ --test_script=tests_performance.yaml --device_list=devices_kws_ic_vww.yaml --mode=p # for performance evaluation
```

This will generate a report with the results of the benchmark in `tiny/runner/sessions/`.

### Flashing through an API

It is also possible to use the flashing and UART capture functionalities of this module through an API. Basically, instead flashing the board locally, the export will generate a `.elf` file that will then be flashed by the API using `pyOCD`. The API is available in the (repository)[https://gitlab.eclipse.org/antoniopereira/api_board_farms], along will all the necessary instructions. Once everything is set up, you can run the same example script as before, but with the `--backend api` flag:

```bash
cd aidge/examples/benchmark
python benchmark_model.py \
  --board stm32h7 \
  --backend api \
  --api-url http://127.0.0.1:8000 \
  --api-username admin \
  --api-password changeme \
  --api-board-name stm32h7_lab_01
  --model_path lenet_mnist
```

### Benchmarks with ST Edge AI Developer Cloud (STZooAPI)

You can also compare your model's performance against the ST Edge AI Developer Cloud using the `stzooapi` backend. This backend uploads your model to ST's cloud services and benchmarks it on their remote board farms.

#### Installation

To use this feature, you must install the `stm32ai-dc` Python package:

```bash
pip install pycurl seaborn numpy matplotlib ipywidgets gitdir shutils marshmallow tqdm
gitdir https://github.com/STMicroelectronics/stm32ai-modelzoo-services/tree/main/common/stm32ai_dc
```

#### Usage

You can use the `benchmark_interface.py` script (found in `examples/`) to compare `onnxruntime`, `aidge_backend_cpu`, and `aidge_export_arm_cortexm` with the ST Cloud results:

```bash
python examples/benchmark_interface.py \
  --stmai-username your_email@st.com \
  --stmai-password your_password \
  --stmai-board B-U585I-IOT02A
```

Note: You need an active account on [ST Edge AI Developer Cloud](https://stedgeai-dc.st.com/). You can also check the boards available on their website.

### Installation

**Prerequisite**:
- `pyocd >= 0.35.0`
- `pyserial >= 3.5`

```bash
pip install aidge_export_arm_cortexm
```


#### 🛠 Build from Source

**Prerequisite** (in addition to previous one):
* Please review the [global installation instructions](https://gitlab.eclipse.org/eclipse/aidge/aidge/-/blob/main/README.md) before proceeding.
* If using a virtual environment, **use the same one** for all Aidge modules.


##### 1. Python installation using setup scripts


| Environment | Python Development |
| --- | --- | 
| **Windows** | `.\setup.ps1 -Modules backend_cpu -Tests` |
| **Unix**    |  `./setup.sh -m backend_cpu --tests` |


> [!TIP]
> Use `Get-Help setup.ps1` (Win) or `./setup.sh -h` (Unix) for full documentation.


##### 2. Python Installation using pip

Run these commands from the `aidge_export_arm_cortexm/` directory:

```bash
#fStandard install
pip install . -v

# Install with testing dependencies
pip install .[test] -v && pytest

```

#### Manual download of HAL/Drivers files 

Recently, we have changed the way we handle the HAL/Drivers files for STM32 boards. Now, these files are **not included in the repository** and must be downloaded manually. For each board in th hardware model, there is a `setup.sh` script that will download the necessary files from the ST repository. You must run this script **before the first compilation**. For example, for the STM32H7 board, you can run:

```bash
cd aidge/aidge_core/aidge_core/hw_model/targets/stm32/stm32h7/board_files
./setup.sh
```

Since every board has its own setup script, please make sure to run the script corresponding to your board before trying to compile the code.

#### Manual update of the STM32 pack is required 

By default, **pyOCD does not include all STM32 packs**. The pack corresponding to **NUCLEO-H743ZI** (`stm32h743zitx`) must be installed manually:

`pyocd pack install stm32h743zitx`

This operation can take several minutes.


#### Verify that the board is correctly detected

If you are on Windows, make sure you installed the **ST-LINK USB Driver** that you can find on [ST website](https://os.mbed.com/teams/ST/wiki/ST-Link-Driver).  

Then connect your board via USB and run:

`pyocd list`

Expected output example:

```
  #   Probe/Board     Unique ID                  Target 
------------------------------------------------------------------
  0   STM32 STLink    066DFF343339415043185830   ✔︎ stm32h743zitx 
      NUCLEO-H743ZI
```

If you see a green check `✔︎`, the board is properly detected. 
If you see a red cross `x`, manually install the pack as described above.


#### A permissions issue with PyOCD: "No available debug probes are connected"

If running the following command results in an error:

`pyocd list`

`No available debug probes are connected`

but your STM32 device is visible via `lsusb`, this may be due to **missing USB permissions**.


#### Follow these steps to fix the issue : 

- Create a new udev rule:

  `sudo nano /etc/udev/rules.d/50-st-link.rules`

- Paste this content:

  `SUBSYSTEM=="usb", ATTR{idVendor}=="0483", ATTR{idProduct}=="374b", MODE="0666"`

- Reload udev and trigger:

  `sudo udevadm control --reload-rules`

  `sudo udevadm trigger`

- Unplug and replug your STM32 device.

- Try again:

  `pyocd list`

  You should now see your board listed.

---

### Important notes
#### Serial Port: "Permission denied: '/dev/ttyACM0'"

If you see an error like this when trying to flash :

`Error connecting to serial port: [Errno 13] could not open port /dev/ttyACM0: [Errno 13] Permission denied: '/dev/ttyACM0'`

This usually means your user doesn't have the right permissions for serial access.

For fix that you have to add your user to the `dialout` group : 

`sudo usermod -a -G dialout $USER`

Then Restart your terminal for the change to take effect


#### Capture timeout and longer UART output

- When measuring inference time using multiple forward calls, capture times may increase. To avoid premature interruption of the capture process, it is important to increase `uart_capture_duration` in the `board_config.json` accordingly (e.g., from 30s to 60 or more),


#### Retrying flash in case of UART failure

- The flashing process now includes a retry mechanism:  
  if the UART output file is missing or empty, the firmware is reflashed up to **5 times by default** (this can be changed via the `MAX_RETRIES` constant in the code).
  
- This improves robustness against rare flashing issues caused by the `pyOCD` library, where firmware may not start correctly despite successful flashing.
- A special **end keyword** (default: `"DEMO END"`) is now expected in the UART output to determine when inference is complete and to stop UART capture.

- The file `uart_output.txt` is automatically generated during execution and placed in the `export_folder`.
- An `export_log.log` file is generated at compilation to store the build logs.
- The `board_config.json` file is essential for configuring board flashing. When testing dimensions like `[16]`, you must increase `uart_capture_duration` to at least `60` or more.

---


### Limitations

#### 1. **Memory limitations (RAM / Flash)**

- From dimensions like `[32, 32, 32, 32]` (e.g., for ReLU), compilation errors or RAM/Flash overflows may occur.
- It is recommended to stay within maximum dimensions of `16`, such as `[1,1,1,1]`, `[4,4,4,4]`, or `[16,16,16,16]`.


### Recommendations

- **Separate different tensor dimensions into different JSON config files.**
- Running benchmarks **individually** helps reduce flashing failures.
- Increase `uart_capture_duration` when working with large output tensors.

## License

Aidge has a Eclipse Public License 2.0, as found in the [LICENSE](LICENSE).

