Skip to content

Development Guide

bwintermann edited this page Dec 1, 2025 · 7 revisions

Installation for Development Use

Just as for normal use, you need to install all packages into the Poetry environment:

poetry install

Tip

Consider setting your Poetry cache directory somewhere else than the default if you have limited space!

We recommend sourcing the environment. Usually, its location can be found by running poetry env info. If you have issues here, please check out the Poetry documentation.

Now that the environment is sourced:

  1. In your IDE of choice (e.g., VS Code), select the interpreter from the environment for your workspace
  2. This ensures you get the correct hints and autocompletes

If you need to edit another package as part of your development, change the package to editable in the pyproject.toml. Further information is found in the Poetry documentation.

Before running FINN+ for the first time, we recommend running:

finn config create .

This creates a template config in your cloned FINN+ repository. In this configuration, set:

FINN_DEPS: ./deps

This places all dependencies local to your FINN+ repository, enabling you to work on multiple FINN versions simultaneously. If you don't plan on changing dependencies, you can leave this to the default value to maintain a single shared dependency directory.

If you want your temporary build files to stay local to your repository as well, add the absolute path to your settings.yaml:

FINN_BUILD_DIR: <path>

For more information about configuration, check the Settings wiki page.

You can now start finn using:

finn run build.py

# or

finn build cfg.yaml model.onnx

Prerequisites

Before starting to do development on FINN it is a good idea to start with understanding the basics as a user. Going through all of the Tutorials is strongly recommended if you haven’t already done so. Additionally, please review the documentation available on Internals.

Branching model

All of the FINN repositories mentioned above use a variant of the GitHub flow from https://guides.github.com/introduction/flow as further detailed below:

  • The master or main branch contains the latest released version, with a version tag.
  • The dev branch is where new feature branches get merged after testing. dev is β€œalmost ready to release” at any time, and is tested with regular Jenkins/GitLab builds – including all unit tests and end-to-end tests.
  • New features or fixes are developed in branches that split from dev and are named similar to feature/name_of_feature. Single-commit fixes may be made without feature branches.
  • New features must come with unit tests and docstrings. If applicable, it must also be tested as part of an end-to-end flow, preferably with a new standalone test. Make sure the existing test suite (including end-to-end tests) still pass. When in doubt, consult with the FINN+ maintainers.
  • When a new feature is ready, a pull request (PR) can be opened targeting the dev branch, with a brief description of what the PR changes or introduces.
  • Larger features must be broken down into several, smaller PRs. If your PRs have dependencies on each other please state in which order they should be reviewed and merged.

Overview of the FINN+ Execution Flow

When you run the finn command, the system performs several initialization steps:

  1. Entry point: src/finn/interface/run_finn.py initializes the execution
  2. Configuration: System reads settings.yaml files (local and global)
  3. Dependencies resolution:
    • Poetry dependencies (from pyproject.toml)
    • External dependencies (configured in external_dependencies.yaml, or, depending on the version used, in src/finn/interface/manage_deps.py)
  4. Environment setup:
    • FINN_DEPS: Points to dependency location
    • FINN_BUILD_DIR: Specifies where generated files are stored

After initialization, the system:

  1. Creates a DataflowBuildConfig object
  2. Starts the build flow

Warning

During testing, finn is not called directly. See the VS Code Integration section for testing-specific setup.

Linting

We use a pre-commit hook to auto-format Python code and check for issues. See https://pre-commit.com/ for installation. Once you have pre-commit, you can install the hooks into your local clone of the FINN+ repo:

pre-commit install

Every time you commit some code, the pre-commit hooks will first run, performing various checks and fixes. In some cases pre-commit won’t be able to fix the issues and you may have to fix it manually, then run git commit once again. The checks are configured in .pre-commit-config.yaml under the repo root.

The pre-commit hooks use black, isort and flake8. However we also added linting support for Ruff, which can be found and customized in pyproject.toml. This is quite helpful during development.

Typing

It is also a good idea to have a type-checker or LSP available (such as Pylance, pyright or ty). Since FINN is a complex multi-repo project, it is helpful to have type annotations available everywhere. We strongly encourage you to annotate your own added code with type annotations as well.

Documentation

To have a PR merged, it is required that everything is documented using docstrings. Use existing files as a guideline on how the docstrings should look like. (A notable exception for this is src/finn/builder/build_dataflow_config.py, which uses a different documentation style.) To ensure good formatting, Ruff is quite helpful here as well.

For custom transformations and steps it is common to also describe what the requisites for this transformation are, and what the expected result will be. This can help prevent bugs where transformation B requires transformation A to be run first, but A was not executed before B. (It is however good practice to also check this in code, as far as possible.)

Debugging FINN+

Command Line Interface

To enable debug mode for the FINN+ command line interface:

FINN_DEBUG=1 finn <your-command>

This activates some basic debugging information during FINN+'s CLI initialization stage. Feel free, when developing your own CLI extension, to include debugging statements checked by FINN_DEBUG as well (the finn.interface subpackage contains a flag DEBUG that you can check).

In FINN+

General

When building custom steps and transformations:

  • Use the debug environment variable for basic output
  • For more detailed logging, enable the verbose flag in the DataflowBuildConfig
  • Make use of the logging system. At any point simply run from finn.util.logging import log
    • As a rough guideline use log.debug for information the user will never see
    • log.info for detailed information about what FINN is currently doing
    • log.warning for warnings that the user should always see (this can be anything from simply informing the user about a certain decision that was made by the compiler, to important changes that the user might want to object to)
    • log.error for when something goes wrong within the context of the compilation (no solution found for something, incorrect graph, etc.)
    • log.fatal for unexpected or external errors, or when something goes completely wrong (Something could not be parsed, a dependency is missing, etc.)

Note that these are only guidelines. Feel free to adjust them to your situation.

Exceptions

Furthermore, proper error handling should be done as well. As a developer, if you can expect an error somewhere, you should always catch it, and re-emit a fitting subclass of FINNError (a complete list of FINN errors can be found in src/finn/util/exception.py).

If your error is not expected to appear during normal usage, use a subclass of FINNInternalError. This prints out a formatted stack trace and some of the surrounding lines of code for debugging.

If the error is expected to appear during normal usage, use a subclass of FINNUserError instead. These are errors that directly concern the user and might point to errors in the model or the configuration and can be fixed without knowledge of FINNs internals. These errors will, when emitted, only produce a one-line summary of the error and exit FINN, instead of printing a whole stack trace.

Important

When possible, avoid using assert. Replace assert with a check and the matching FINNError type. For example replace:

assert len(model.graph.node) != 0

with

if len(model.graph.node) == 0:
    raise FINNUserError("The model does not contain any nodes!")

Pull Request Merge Checklist

This should provide a short and hopefully helpful overview of things to think about before finalizing a PR:

  • Is the new code integrated properly into the codebase? (Reuse of existing files, functions, types, etc.)
  • Is the new functionality documented? (For Transformations this would be the class docstring for example)
  • Is the code itself documented?
    • Classes
    • Methods
    • Functions
    • Modules
    • (Variables, if complex)
  • Is the code type-hinted? (Function/Method signatures, variables if necessary)
  • Is the code correctly linted / formatted? (pre-commit will take care of formatting otherwise)
  • Is the new functionality tested (both unit-tested and end-to-end)?
    • Do all tests pass?
    • Do all existing tests pass?
  • (If the new code requires configuration in build_dataflow_config.py)
    • Is the new configuration variable documented similar to the others?
  • Did you remove all open TODOs from your code?

VS Code Integration

Environment Setup

Create an .env file with the following variables:

FINN_ROOT             # Your repository path
FINN_BUILD_DIR        # Temporary files location (e.g., /tmp/FINN_TEST_BUILD_DIR)
FINN_DEPS             # Dependency location
NUM_DEFAULT_WORKERS   # Number of parallel transformation workers
VIVADO_PATH           # Path to Vivado (e.g., .../Vivado/2023.1)
VITIS_PATH            # Path to Vitis
HLS_PATH              # Path to Vitis HLS

Tip

See the Settings wiki page for detailed information about these variables.

Important

Run finn deps update at least once manually before testing, as the test setup doesn't automatically fetch dependencies.

Important

FINN_BUILD_DIR determines where test-generated files are placed. Consider the resolution order.

Debugging Configuration

Configure VS Code's debugger by adding this to .vscode/launch.json:

{
    "name": "Python Debugger: Run FINN",
    "type": "debugpy",
    "request": "launch",
    "program": "${workspaceFolder}/src/finn/interface/run_finn.py",
    "console": "integratedTerminal",
    "args": "${command:pickArgs}",
    "subProcess": true
}

Testing Configuration

Tests are vital to keep FINN+ running. All the FINN+ tests can be found in the test folder. These tests can be roughly grouped into three categories:

  • Unit tests: targeting unit functionality, e.g. a single transformation. Example: tests/transformation/streamline/test_sign_to_thres.py tests the expected behavior of the ConvertSignToThres transformation pass.
  • Small-scale integration tests: targeting a group of related classes or functions that to test how they behave together. Example: tests/fpgadataflow/test_convert_to_hls_conv_layer.py sets up variants of ONNX Conv nodes that are first lowered and then converted to FINN HLS layers.
  • End-to-end tests: testing a typical β€˜end-to-end’ compilation flow in FINN, where one end is a trained QNN and the other end is a hardware implementation. These tests can be quite large and are typically broken into several steps that depend on prior ones. Examples: tests/end2end

Additionally, qonnx, brevitas and finn-hlslib also include their own test suites.

Configure VS Code to use pytest by adding this to .vscode/settings.json:

"python.testing.pytestArgs": [
    "tests",
    "src/finn/transformation/fpgadataflow",
    "-n",
    "10",
    "--doctest-modules"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,

Important

Settings in .vscode/settings.json override your pyproject.toml testing settings. Update both when changing test configurations.

Test Environment Variables

Control test execution with these environment variables:

  • FINN_TESTS_ISOLATE_BUILD_DIRS (default: 1)

    • When set to 1, creates a separate directory for each test
    • Example: /tmp/FINN_TESTS/test_operator_a, /tmp/FINN_TESTS/test_operator_b, etc.
    • During the test, FINN_BUILD_DIR points to the test-specific directory
  • FINN_TESTS_CLEANUP_BUILD_DIRS (default: 0)

    • When set to 1, removes the test build directory after execution
    • Requires FINN_TESTS_ISOLATE_BUILD_DIRS=1
    • Set to 0 by default to allow post-test debugging

Clone this wiki locally