├── tests ├── __init__.py ├── unit_tests │ ├── __init__.py │ ├── commands │ │ ├── __init__.py │ │ ├── test_apio_upgrade.py │ │ ├── test_apio_drivers.py │ │ ├── test_apio_sim.py │ │ ├── test_apio_graph.py │ │ ├── test_apio_lint.py │ │ ├── test_apio_report.py │ │ ├── test_apio_format.py │ │ ├── test_apio_test.py │ │ ├── test_apio.py │ │ ├── test_apio_upload.py │ │ ├── test_shortcuts.py │ │ ├── test_apio_devices.py │ │ ├── test_apio_raw.py │ │ ├── test_apio_preferences.py │ │ ├── test_apio_create.py │ │ ├── test_apio_info.py │ │ ├── test_apio_examples.py │ │ ├── test_apio_fpgas.py │ │ └── test_apio_packages.py │ ├── managers │ │ ├── __init__.py │ │ └── test_scons_filters.py │ ├── scons │ │ ├── __init__.py │ │ └── test_apio_env.py │ ├── utils │ │ ├── __init__.py │ │ ├── test_jsonc.py │ │ ├── test_cmd_util.py │ │ └── test_usb_util.py │ ├── common │ │ ├── test_apio_themes.py │ │ └── test_apio_console.py │ └── test_apio_context.py ├── integration_tests │ └── __init__.py ├── README.md └── first_test.py ├── apio ├── common │ ├── __init__.py │ ├── proto │ │ ├── __init__.py │ │ └── update-protos.sh │ ├── README.md │ ├── apio_styles.py │ └── rich_lib_windows.py ├── scons │ ├── __init__.py │ ├── SConstruct │ └── README.md ├── utils │ └── __init__.py ├── commands │ ├── __init__.py │ ├── apio_drivers.py │ ├── apio_docs.py │ ├── apio_report.py │ ├── apio_build.py │ ├── apio_test.py │ ├── apio_create.py │ ├── apio_drivers_install.py │ ├── apio_drivers_uninstall.py │ ├── apio_lint.py │ └── apio_upload.py ├── managers │ ├── __init__.py │ └── downloader.py ├── resources │ ├── 80-fpga-serial.rules │ ├── platforms.jsonc │ ├── 80-fpga-ftdi.rules │ ├── config.jsonc │ └── packages.jsonc └── __init__.py ├── scripts ├── __init__.py ├── browser-test │ ├── graph.pdf │ ├── graph.png │ └── test.py ├── update_commands_txt.sh ├── check-icestudio-test-examples.sh ├── diff_jsonc.py ├── generate_commands_help.py └── genereate-repo-cleaner.sh ├── docs ├── assets │ ├── apio-logo.png │ ├── sim-gtkwave.png │ ├── apio-devices-usb.png │ ├── apio-info-themes.png │ ├── apio-illustration.png │ ├── apio-report-clock.png │ ├── apio-vscode-window.png │ ├── apio-devices-serial.png │ └── apio-report-utilization.png ├── creating-package-version.md ├── apio-examples.md ├── supported-fpgas.md ├── commands-list.md ├── supported-boards.md ├── cmd-apio-upgrade.md ├── using-system-verilog.md ├── cmd-apio-docs.md ├── cmd-apio-clean.md ├── help.md ├── video-tutorial.md ├── cmd-apio-create.md ├── cmd-apio-fpgas.md ├── cmd-apio-boards.md ├── cmd-apio-lint.md ├── creating-apio-version.md ├── cmd-apio-preferences.md ├── board-drivers.md ├── contributing-definitions.md ├── cmd-apio-test.md ├── cmd-apio-upload.md ├── cmd-apio-build.md ├── cmd-apio-raw.md ├── contributing-examples.md ├── cmd-apio-graph.md ├── cmd-apio-report.md ├── system-requirements.md ├── cmd-apio-format.md ├── cmd-apio-devices.md ├── command-line.md ├── cmd-apio-drivers.md ├── custom-boards.md ├── cmd-apio-packages.md ├── installing-apio-ide.md ├── cmd-apio-sim.md ├── updating-the-docs.md ├── cmd-apio-examples.md ├── using-examples.md ├── project-structure.md ├── migrating-from-apio-0.9.5.md ├── cmd-apio-info.md ├── terminology.md └── raw-tools.md ├── .github └── workflows │ ├── resources │ ├── linux │ │ └── control.template │ ├── windows │ │ ├── README.txt │ │ └── innosetup.template │ ├── darwin │ │ ├── postinstall │ │ ├── distribution.xml │ │ └── README.html │ └── apio-pyinstaller.spec │ ├── publish-to-pypi.yaml │ ├── monitor-apio-prod.yaml │ ├── publish-docs.yaml │ └── test.yaml ├── .vscode ├── extensions.json └── launch.json ├── remote-config ├── README.md ├── apio-0.9.7.jsonc ├── apio-1.0.0.jsonc ├── apio-1.0.2.jsonc ├── apio-1.1.x.jsonc └── apio-1.0.1.jsonc ├── .pylintrc ├── .coveragerc ├── .gitignore ├── pyproject.toml ├── mkdocs.yml └── tox.ini /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apio/common/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apio/scons/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apio/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apio/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apio/managers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit_tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apio/common/proto/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration_tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit_tests/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit_tests/managers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit_tests/scons/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit_tests/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | This directory contains the tests that do not require connected boards. 2 | -------------------------------------------------------------------------------- /docs/assets/apio-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FPGAwars/apio/HEAD/docs/assets/apio-logo.png -------------------------------------------------------------------------------- /docs/assets/sim-gtkwave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FPGAwars/apio/HEAD/docs/assets/sim-gtkwave.png -------------------------------------------------------------------------------- /docs/assets/apio-devices-usb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FPGAwars/apio/HEAD/docs/assets/apio-devices-usb.png -------------------------------------------------------------------------------- /docs/assets/apio-info-themes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FPGAwars/apio/HEAD/docs/assets/apio-info-themes.png -------------------------------------------------------------------------------- /scripts/browser-test/graph.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FPGAwars/apio/HEAD/scripts/browser-test/graph.pdf -------------------------------------------------------------------------------- /scripts/browser-test/graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FPGAwars/apio/HEAD/scripts/browser-test/graph.png -------------------------------------------------------------------------------- /docs/assets/apio-illustration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FPGAwars/apio/HEAD/docs/assets/apio-illustration.png -------------------------------------------------------------------------------- /docs/assets/apio-report-clock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FPGAwars/apio/HEAD/docs/assets/apio-report-clock.png -------------------------------------------------------------------------------- /docs/assets/apio-vscode-window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FPGAwars/apio/HEAD/docs/assets/apio-vscode-window.png -------------------------------------------------------------------------------- /docs/assets/apio-devices-serial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FPGAwars/apio/HEAD/docs/assets/apio-devices-serial.png -------------------------------------------------------------------------------- /apio/common/README.md: -------------------------------------------------------------------------------- 1 | The files in this directory are used in both the apio parent process 2 | and the scons child process. 3 | -------------------------------------------------------------------------------- /docs/assets/apio-report-utilization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FPGAwars/apio/HEAD/docs/assets/apio-report-utilization.png -------------------------------------------------------------------------------- /docs/creating-package-version.md: -------------------------------------------------------------------------------- 1 | # Creating a Package version 2 | 3 | !!! warning "Page under construction" 4 | The details of the release process is not finalized yet. 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/apio-examples.md: -------------------------------------------------------------------------------- 1 | # Apio examples 2 | 3 | This page is a placeholder. The actual content is injected automatically 4 | by the Apio docs publishing workflow. 5 | 6 | If you see this message in a published Apio doc, please file an Apio bug. 7 | -------------------------------------------------------------------------------- /docs/supported-fpgas.md: -------------------------------------------------------------------------------- 1 | # Supported FPGAs 2 | 3 | This page is a placeholder. The actual content is injected automatically 4 | by the Apio docs publishing workflow. 5 | 6 | If you see this message in a published Apio doc, please file an Apio bug. -------------------------------------------------------------------------------- /docs/commands-list.md: -------------------------------------------------------------------------------- 1 | # Apio Commands List 2 | 3 | This page is a placeholder. The actual content is injected automatically 4 | by the Apio docs publishing workflow. 5 | 6 | If you see this message in a published Apio doc, please file an Apio bug. 7 | -------------------------------------------------------------------------------- /docs/supported-boards.md: -------------------------------------------------------------------------------- 1 | # Supported FPGA Boards 2 | 3 | This page is a placeholder. The actual content is injected automatically 4 | by the Apio docs publishing workflow. 5 | 6 | If you see this message in a published Apio doc, please file an Apio bug. -------------------------------------------------------------------------------- /.github/workflows/resources/linux/control.template: -------------------------------------------------------------------------------- 1 | Package: apio 2 | Version: [APIO_VERSION] 3 | Section: electronic 4 | Priority: optional 5 | Architecture: amd64 6 | Maintainer: Jesus Arroyo jesus.jkhlg@gmail.com, https://github.com/FPGAwars/apio 7 | Description: Open source FPGA design tool. 8 | 9 | -------------------------------------------------------------------------------- /apio/scons/SConstruct: -------------------------------------------------------------------------------- 1 | """Apio's scons handler entry point.""" 2 | 3 | # -*- coding: utf-8 -*- 4 | # -- This file is part of the Apio project 5 | # -- (C) 2016-2024 FPGAwars 6 | # -- Authors Juan Gonzáles, Jesús Arroyo 7 | # -- License GPLv2 8 | 9 | from apio.scons.scons_handler import SconsHandler 10 | 11 | SconsHandler.start() 12 | -------------------------------------------------------------------------------- /docs/cmd-apio-upgrade.md: -------------------------------------------------------------------------------- 1 | # Apio upgrade 2 | 3 | --- 4 | 5 | ## apio upgrade 6 | 7 | The `apio upgrade` command checks the latest Apio release and provides upgrade instructions if needed. 8 | 9 |

Examples

10 | 11 | ``` 12 | apio upgrade 13 | ``` 14 | 15 |

Options

16 | 17 | ``` 18 | -h, --help Show help message and exit 19 | ``` 20 | -------------------------------------------------------------------------------- /.github/workflows/resources/windows/README.txt: -------------------------------------------------------------------------------- 1 | Apio is installed and is ready for use. 2 | 3 | To test it, open a new Command window and try a few 4 | of the commands below. For more information see 5 | https://fpgawars.github.io/apio/quick-start 6 | 7 | apio -h 8 | apio info system 9 | apio boards 10 | apio fpgas 11 | apio examples list 12 | apio examples fetch -h 13 | 14 | 15 | -------------------------------------------------------------------------------- /apio/resources/80-fpga-serial.rules: -------------------------------------------------------------------------------- 1 | # Disable ModemManager for BlackIce 2 | ATTRS{idVendor}=="0483", ATTRS{idProduct}=="5740", ENV{ID_MM_DEVICE_IGNORE}="1" 3 | 4 | # Disable ModemManager for TinyFPGA B2 5 | ATTRS{idVendor}=="1209", ATTRS{idProduct}=="2100", ENV{ID_MM_DEVICE_IGNORE}="1" 6 | 7 | # Disable ModemManager for TinyFPGA BX 8 | ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="6130", ENV{ID_MM_DEVICE_IGNORE}="1" 9 | -------------------------------------------------------------------------------- /docs/using-system-verilog.md: -------------------------------------------------------------------------------- 1 | # Using SystemVerilog 2 | 3 | You can use SystemVerilog files in your Apio project just like Verilog files. Simply change the file extension from `.v` to `.sv` to indicate that they should be handled as SystemVerilog files. This applies to both synthesizable modules and testbenches. 4 | 5 | > It's also okay to mix Verilog `.v` files and SystemVerilog `.sv` files within the same project. 6 | -------------------------------------------------------------------------------- /scripts/update_commands_txt.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # The python script below requires apio to be installed as a python package. 4 | invoke install-apio 5 | 6 | # Run to update COMMANDS.txt with the latest apio commands help text. 7 | 8 | output="COMMANDS.txt" 9 | 10 | rm -f $output 11 | 12 | echo "Generating $output ..." 13 | 14 | python ./scripts/generate_commands_help.py > $output 15 | 16 | echo "$output updated" 17 | -------------------------------------------------------------------------------- /tests/unit_tests/common/test_apio_themes.py: -------------------------------------------------------------------------------- 1 | """Test for the apio_themes.py.""" 2 | 3 | from apio.common.apio_themes import THEMES_TABLE, DEFAULT_THEME 4 | 5 | 6 | def test_theme_style(): 7 | """Tests that all theme have the same set of style keys.""" 8 | 9 | for theme_name, theme_obj in THEMES_TABLE.items(): 10 | print(f"Testing theme {theme_name}") 11 | assert set(theme_obj.styles.keys()) == set(DEFAULT_THEME.styles.keys()) 12 | -------------------------------------------------------------------------------- /docs/cmd-apio-docs.md: -------------------------------------------------------------------------------- 1 | # Apio docs 2 | 3 | --- 4 | 5 | ## apio docs 6 | 7 | The command `apio docs` opens the Apio documentation using the user's 8 | default browser. 9 | 10 | 11 | 12 |

Examples

13 | 14 | ``` 15 | apio docs # Open the docs. 16 | apio docs --commands # Land on the commands list page. 17 | ``` 18 | 19 |

Options

20 | 21 | ``` 22 | -c, --commands Show the commands page. 23 | -h, --help Show this message and exit. 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/cmd-apio-clean.md: -------------------------------------------------------------------------------- 1 | Apio clean 2 | 3 | --- 4 | 5 | ## apio clean 6 | 7 | The `apio clean` command removes all build output files previously generated by Apio. 8 | 9 |

Examples

10 | 11 | ``` 12 | apio clean # Clean current project directory 13 | apio clean -p /projects/blinky # Clean a specific project directory 14 | ``` 15 | 16 |

Options

17 | 18 | ``` 19 | -p, --project-dir path Specify the root directory of the project 20 | -h, --help Show help message and exit 21 | ``` 22 | -------------------------------------------------------------------------------- /tests/unit_tests/commands/test_apio_upgrade.py: -------------------------------------------------------------------------------- 1 | """Test for the "apio upgrade" command.""" 2 | 3 | from tests.conftest import ApioRunner 4 | from apio.commands.apio import apio_top_cli as apio 5 | 6 | 7 | def test_upgrade(apio_runner: ApioRunner): 8 | """Test "apio upgrade" """ 9 | 10 | with apio_runner.in_sandbox() as sb: 11 | 12 | # -- Execute "apio upgrade" 13 | result = sb.invoke_apio_cmd(apio, ["upgrade"]) 14 | sb.assert_result_ok(result) 15 | assert "Latest Apio stable version" in result.output 16 | -------------------------------------------------------------------------------- /docs/help.md: -------------------------------------------------------------------------------- 1 | # Getting help 2 | 3 | ## Apio Project Home 4 | 5 | The Apio project is hosted on a public GitHub repository at . 6 | 7 | ## Asking Questions 8 | 9 | You can ask the Apio team and other users in the Apio discussion forum at . 10 | 11 | ## Bug Reports 12 | 13 | Bug reports should be submitted in the main Apio repository at , even if they are related to other Apio repositories such as the package repositories. 14 | -------------------------------------------------------------------------------- /apio/resources/platforms.jsonc: -------------------------------------------------------------------------------- 1 | // List of computer platforms that are supported by apio. 2 | { 3 | "darwin-arm64": { 4 | "type": "Mac OSX", 5 | "variant": "ARM 64 bit (Apple Silicon)" 6 | }, 7 | "darwin-x86-64": { 8 | "type": "Mac OSX", 9 | "variant": "x86 64 bit (Intel)" 10 | }, 11 | "linux-x86-64": { 12 | "type": "Linux", 13 | "variant": "X86 64 bit" 14 | }, 15 | "linux-aarch64": { 16 | "type": "Linux", 17 | "variant": "ARM 64 bit" 18 | }, 19 | "windows-amd64": { 20 | "type": "Windows", 21 | "variant": "x86 64 bit" 22 | } 23 | } -------------------------------------------------------------------------------- /apio/scons/README.md: -------------------------------------------------------------------------------- 1 | The files in this directory are executed by the scons child process and 2 | not by the apio process itself, with stdout and stderr pipes that allows 3 | the apio process to read the output. 4 | 5 | As a result: 6 | * A breakpoint in this code will not stop when running the apio process. 7 | To debug code here, set the system env var below, run the apio command 8 | that invoks scons and connect to it via the Visual Studio Code python 9 | debugger. 10 | 11 | ``` 12 | # Linux and macOS. 13 | export APIO_SCONS_DEBUGGER= 14 | 15 | # Windows 16 | set APIO_SCONS_DEBUGGER= 17 | ``` 18 | 19 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "streetsidesoftware.code-spell-checker", 4 | "ms-python.black-formatter", 5 | "alefragnani.bookmarks", 6 | "pbkit.vscode-pbkit", 7 | "ms-python.vscode-pylance", 8 | "ms-python.python", 9 | "ms-python.debugpy", 10 | "kukdh1.verible-formatter", 11 | "mshr-h.veriloghdl", 12 | "shakram02.bash-beautify", 13 | "grapecity.gc-excelviewer", 14 | "ybaumes.highlight-trailing-white-spaces", 15 | "yzhang.markdown-all-in-one", 16 | "shd101wyy.markdown-preview-enhanced" 17 | ] 18 | } -------------------------------------------------------------------------------- /remote-config/README.md: -------------------------------------------------------------------------------- 1 | ## Apio Remote Configurations 2 | 3 | This directory contains json files that are used to configure remotely apio clients. 4 | As of Jan 2024, we use it only for controlling the versions of apio packages the apio 5 | clients should load. 6 | 7 | File files are keyed by the apio version, as shown by the command ``apio system info`` 8 | and the structure of each file should match the expectations of the respective apio 9 | release. 10 | 11 | This mechanism was introduced in apio 0.9.5. Prior versions of apio use VERSION 12 | files in the respective package repositories are are not affected by the files 13 | in this directory. 14 | -------------------------------------------------------------------------------- /tests/first_test.py: -------------------------------------------------------------------------------- 1 | """A pseudo test that runs first and fill in the packages cache if needed. 2 | It is not required but provide a better indication to the user if the 3 | package loading pauses the tests for a few seconds. 4 | """ 5 | 6 | from tests.conftest import ApioRunner 7 | from apio.commands.apio import apio_top_cli as apio 8 | 9 | 10 | def test_fill_packages_cache(apio_runner: ApioRunner): 11 | """Fill the packages cache.""" 12 | 13 | with apio_runner.in_sandbox() as sb: 14 | 15 | # -- Execute "apio packages" 16 | result = sb.invoke_apio_cmd(apio, ["packages", "update"]) 17 | sb.assert_result_ok(result) 18 | -------------------------------------------------------------------------------- /tests/unit_tests/commands/test_apio_drivers.py: -------------------------------------------------------------------------------- 1 | """Test for the "apio drivers" command.""" 2 | 3 | from tests.conftest import ApioRunner 4 | from apio.common.apio_console import cunstyle 5 | from apio.commands.apio import apio_top_cli as apio 6 | 7 | 8 | def test_drivers(apio_runner: ApioRunner): 9 | """Test "apio drivers" """ 10 | 11 | with apio_runner.in_sandbox() as sb: 12 | 13 | # -- Execute "apio drivers" 14 | result = sb.invoke_apio_cmd(apio, ["drivers"]) 15 | sb.assert_result_ok(result) 16 | assert "apio drivers install" in cunstyle(result.output) 17 | assert "apio drivers uninstall" in cunstyle(result.output) 18 | -------------------------------------------------------------------------------- /.github/workflows/resources/darwin/postinstall: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script is executied at the end of the package installation to 4 | # set the path to the apio binary. 5 | 6 | # Try to use the destination package var, or use default if not specified. 7 | if [ -n "$DSTROOT" ]; then 8 | dst_root="$DSTROOT" 9 | else 10 | dst_root="/Applications" 11 | fi 12 | 13 | apio_path="${dst_root}/Apio" 14 | apio_path_file="/etc/paths.d/Apio" 15 | 16 | # date > /Users/user/apio.installer.postinstall.log 17 | # env >> /Users/user/apio.installer.postinstall.log 18 | 19 | # Write the path. New shells will have it in the PATH. 20 | echo "${apio_path}" > "${apio_path_file}" 21 | 22 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | # Configuration file for pylint. 2 | 3 | [MESSAGES CONTROL] 4 | # Do not warn about TODOs. 5 | # Do not warn about too few public methods in a class. 6 | # Do not complain about string.format(...) 7 | disable=fixme,too-few-public-methods,consider-using-f-string 8 | 9 | # Warn about unnecessary pylint disable=x directives. 10 | enable=useless-suppression 11 | 12 | 13 | [SIMILARITIES] 14 | # Reducing the sensitivity of duplicate code (default is 4) 15 | min-similarity-lines=30 16 | 17 | 18 | [TYPECHECK] 19 | # This prevents false proto buffers related warnings. 20 | # It tells pylint to use the definitions in the .pyi stubs instead of the 21 | # cryptic protocol buffers .py files. 22 | prefer-stubs=yes 23 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | ; Config file for pytest coverage. Run using 2 | ; 'invoke test-coverage' or in short 'invoke tc'. 3 | 4 | [run] 5 | parallel = true 6 | patch = subprocess 7 | branch = true 8 | ; A reference to this file. 9 | data_file = .coverage 10 | ; Limit the coverage to these directories. 11 | source = 12 | apio/ 13 | tests/ 14 | 15 | 16 | [paths] 17 | ; Merge files these two aliases. Coverage of modules that are used by 18 | ; the scons subprocess may appear in the site-packages/apio instead of 19 | ; apio directly. 20 | apio = 21 | apio 22 | .tox/*/lib/python*/site-packages/apio 23 | 24 | 25 | [report] 26 | ; Do not report coverage of the proto generated stubs. 27 | omit = 28 | */apio/common/proto/* 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.asc 3 | *.bin 4 | *.rpt 5 | *.blif 6 | *.eggs 7 | *.egg-info 8 | *.swp 9 | *~ 10 | .sconsign.dblite 11 | .coverage 12 | .coverage.* 13 | .tox/ 14 | .cache/ 15 | .pytest_cache/ 16 | .venv/ 17 | build/ 18 | dist/ 19 | _dist/ 20 | ice-build 21 | ice-build/ 22 | ice-build/* 23 | _build/ 24 | _dist/ 25 | _site/ 26 | htmlcov/ 27 | _pytest-coverage/ 28 | __pycache__/ 29 | env/ 30 | venv/ 31 | examples/ 32 | ice-build/ 33 | work/ 34 | _work/ 35 | test-colorlight-v61.ice 36 | _build 37 | hardware.json 38 | hardware.out 39 | hardware.vlt 40 | hardware.bit 41 | hardware.config 42 | hardware.pnr 43 | graph.dot 44 | graph.svg 45 | *.out 46 | *.vcd 47 | .DS_Store 48 | abc.history 49 | temp-* 50 | .run.sh 51 | ,coverage.* 52 | -------------------------------------------------------------------------------- /docs/video-tutorial.md: -------------------------------------------------------------------------------- 1 | # Apio Video Tutorial 2 | 3 | [Shawn Hymel](https://shawnhymel.com/) created an excellent video series introducing FPGA design using Apio 0.6.5. To view the series on YouTube, click the video thumbnail below. 4 | 5 |
6 | 7 | > The Apio 0.6.5 commands used in the video series differ slightly from the enhanced set of Apio 1.x.x commands. For details, see the [Apio 1.x.x command differences](migrating-from-apio-0.9.5.md#know-the-new-commands) between Apio 0.x and 1.x. 8 | 9 |
10 | 11 | [![Introduction to FPGA YouTube Series](https://raw.githubusercontent.com/ShawnHymel/introduction-to-fpga/main/images/Intro%20to%20FPGA%20Part%201_Thumbnail.png)](https://www.youtube.com/watch?v=lLg1AgA2Xoo&list=PLEBQazB0HUyT1WmMONxRZn9NmQ_9CIKhb) 12 | -------------------------------------------------------------------------------- /apio/common/apio_styles.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # -- This file is part of the Apio project 3 | # -- (C) 2016-2018 FPGAwars 4 | # -- Author Jesús Arroyo 5 | # -- License GPLv2 6 | # -- Derived from: 7 | # ---- Platformio project 8 | # ---- (C) 2014-2016 Ivan Kravets 9 | # ---- License Apache v2 10 | """A file that defines and export the apio style names.""" 11 | 12 | # -- Overriding highlights. 13 | STRING = "repr.str" 14 | CODE = "code" 15 | URL = "repr.url" 16 | 17 | # -- Apio's own abstract styles, 18 | CMD_NAME = "apio.cmd_name" 19 | TITLE = "apio.title" 20 | BORDER = "apio.border" 21 | EMPH1 = "apio.emph1" 22 | EMPH2 = "apio.emph2" 23 | EMPH3 = "apio.emph3" 24 | SUCCESS = "apio.success" 25 | INFO = "apio.info" 26 | WARNING = "apio.warning" 27 | ERROR = "apio.error" 28 | -------------------------------------------------------------------------------- /docs/cmd-apio-create.md: -------------------------------------------------------------------------------- 1 | # Apio create 2 | 3 | --- 4 | 5 | ## apio create 6 | 7 | The `apio create` command initializes a new `apio.ini` file. Use it to 8 | start a new Apio project. 9 | 10 | This command only generates a new `apio.ini` file. To create a full, 11 | buildable project, use `apio examples` to fetch a template for your board. 12 | 13 |

Examples

14 | 15 | ``` 16 | apio create --board alhambra-ii 17 | apio create --board alhambra-ii --top-module MyModule 18 | ``` 19 | 20 |

Options

21 | 22 | ``` 23 | -b, --board BOARD Specify the target board. [required] 24 | -t, --top-module name Set the top-level module name. 25 | -p, --project-dir path Specify the project root directory. 26 | -h, --help Show help message and exit. 27 | ``` 28 | 29 | 30 | -------------------------------------------------------------------------------- /apio/__init__.py: -------------------------------------------------------------------------------- 1 | """Open source ecosystem for open FPGA boards""" 2 | 3 | # -*- coding: utf-8 -*- 4 | # -- This file is part of the Apio project 5 | # -- (C) 2016-2019 FPGAwars 6 | # -- Author Jesús Arroyo 7 | # -- License GPLv2 8 | 9 | # -------------------------------------------- 10 | # - Information for the Distribution package 11 | # -------------------------------------------- 12 | 13 | # -- Developer: Change this number when releasing a new version 14 | VERSION = (1, 1, 0) 15 | 16 | # -- Get the version as a string. Ex: "0.10.1" 17 | __version__ = ".".join([str(s) for s in VERSION]) 18 | 19 | __title__ = "apio" 20 | __description__ = "Open source ecosystem for open FPGA boards" 21 | __url__ = "https://github.com/FPGAwars/apio" 22 | 23 | __author__ = "Jesús Arroyo Torrens" 24 | __email__ = "jesus.arroyo.torrens@gmail.com" 25 | 26 | __license__ = "GPLv2" 27 | -------------------------------------------------------------------------------- /tests/unit_tests/utils/test_jsonc.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests of jsonc.py 3 | """ 4 | 5 | from apio.utils import jsonc 6 | 7 | # -- The converstion input and expected output strings. Notice the '//' within 8 | # -- the string, it should not be classified as a comment. The '_' characters 9 | # -- are place holders for trailing white space. 10 | BEFORE = """ 11 | // Comment 1. 12 | "image": {__ 13 | // Comment 2. 14 | "src": "Images//Sun.png", // Comment 3 15 | "name": "aaa\n", 16 | "hOffset": 250 17 | } 18 | """ 19 | 20 | AFTER = """ 21 | ____ 22 | "image": {__ 23 | ________ 24 | "src": "Images//Sun.png",_ 25 | "name": "aaa\n", 26 | "hOffset": 250 27 | } 28 | """ 29 | 30 | 31 | def test_to_json(): 32 | """Test the comments removal.""" 33 | assert jsonc.to_json(BEFORE.replace("_", " ")) == AFTER.replace("_", " ") 34 | -------------------------------------------------------------------------------- /.github/workflows/resources/darwin/distribution.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Apio 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | apio-component.pkg 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /.github/workflows/resources/darwin/README.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | Apio is installed and is ready for use. 12 |

13 | To test it, open a new shell window and try a few 14 | of the commands below. For more information see 15 |
https://fpgawars.github.io/apio/quick-start 16 |

17 | 18 |   apio -h
19 |   apio info system
20 |   apio boards
21 |   apio fpgas
22 |   apio examples list
23 |   apio examples fetch -h
24 |
25 |

26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /docs/cmd-apio-fpgas.md: -------------------------------------------------------------------------------- 1 | # Apio fpgas 2 | 3 | --- 4 | 5 | ## apio fpgas 6 | 7 | The `apio fpgas` command lists FPGAs supported by Apio. 8 | 9 | You can define custom FPGAs by placing a `fpgas.jsonc` file in the project directory, 10 | which overrides the default configuration, provided they are supported by the 11 | underlying tools. 12 | 13 | > The apio board definitions are included in the apio 'definition' package 14 | > that is updated periodically. 15 | 16 |

Examples

17 | 18 | ``` 19 | apio fpgas # List all FPGAs. 20 | apio fpgas -v # List with extra columns. 21 | apio fpgas | grep gowin # Filter FPGA results. 22 | apio boards --docs # Generate a report for Apio docs 23 | ``` 24 | 25 |

Options

26 | 27 | ``` 28 | -v, --verbose Show detailed output 29 | -d, --docs Format for Apio Docs. 30 | -p, --project-dir path Specify the project root directory 31 | -h, --help Show help message and exit 32 | ``` 33 | -------------------------------------------------------------------------------- /tests/unit_tests/managers/test_scons_filters.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests of scons_filters.py 3 | """ 4 | 5 | from apio.managers.scons_filter import PnrRangeDetector, PipeId 6 | 7 | # TODO: Add more tests. 8 | 9 | 10 | def test_pnr_range_detector(): 11 | """Tests the pnr range class.""" 12 | 13 | # -- Create a PNR range detector. 14 | rd = PnrRangeDetector() 15 | 16 | # -- Starting out of range 17 | assert not rd.update(PipeId.STDOUT, "hello world") 18 | assert not rd.update(PipeId.STDOUT, "hello world") 19 | 20 | # -- Start of range trigger (from next line) 21 | assert not rd.update(PipeId.STDOUT, "nextpnr-ice40 bla bla") 22 | 23 | # -- In range. 24 | assert rd.update(PipeId.STDOUT, "bla bla") 25 | assert rd.update(PipeId.STDOUT, "info: bla bla") 26 | 27 | # -- End of range trigger. (from next line) 28 | assert rd.update(PipeId.STDERR, "Program finished normally.") 29 | 30 | # -- out of range. 31 | assert not rd.update(PipeId.STDOUT, "bla bla") 32 | -------------------------------------------------------------------------------- /docs/cmd-apio-boards.md: -------------------------------------------------------------------------------- 1 | # Apio boards 2 | 3 | --- 4 | 5 | ## apio boards 6 | 7 | The `apio boards` command lists the FPGA boards supported by Apio. 8 | 9 | You can define custom boards by placing a `boards.jsonc` file with your 10 | board definition in your project directory, which overrides Apio’s default `boards.jsonc`. 11 | 12 | > The apio board definitions are included in the apio 'definition' package 13 | > that is updated periodically. 14 | 15 |

Examples

16 | 17 | ``` 18 | apio boards # List all boards. 19 | apio boards -v # List with extra columns. 20 | apio boards | grep ecp5 # Filter boards results. 21 | apio boards --docs # Generate a report for Apio docs 22 | ``` 23 | 24 |

Options

25 | 26 | ``` 27 | -v, --verbose Show detailed output. 28 | -d, --docs Format for Apio Docs. 29 | -p, --project-dir path Set the root directory for the project. 30 | -h, --help Show this message and exit. 31 | ``` 32 | -------------------------------------------------------------------------------- /scripts/browser-test/test.py: -------------------------------------------------------------------------------- 1 | import webbrowser 2 | from pathlib import Path 3 | 4 | 5 | def open_in_browser(file_path): 6 | # Convert file path to a proper file:// URI 7 | file_uri = Path(file_path).resolve().as_uri() 8 | 9 | # Try Chrome first (Windows, macOS, Linux), then fall back 10 | # to default browser 11 | 12 | # browser_options = [ 13 | # "chrome", # Windows 14 | # "open -a 'Google Chrome'",# macOS 15 | # "google-chrome", # Linux 16 | # "chromium-browser" # Linux alternative 17 | # ] 18 | 19 | # for option in browser_options: 20 | # try: 21 | # webbrowser.get(option).open(file_uri) 22 | # return 23 | # except webbrowser.Error: 24 | # continue 25 | 26 | # Fallback — use system default browser 27 | webbrowser.open(file_uri) 28 | 29 | 30 | if __name__ == "__main__": 31 | open_in_browser("test.svg") 32 | # open_in_browser("test.pdf") 33 | # open_in_browser("test.png") 34 | -------------------------------------------------------------------------------- /docs/cmd-apio-lint.md: -------------------------------------------------------------------------------- 1 | # Apio lint 2 | 3 | --- 4 | 5 | ## apio lint 6 | 7 | The `apio lint` command checks the project's source files for errors, inconsistencies, and style violations using the `Verilator` tool included with Apio. 8 | 9 |

Examples

10 | 11 | ``` 12 | apio lint # Lint the entire design 13 | apio lint -t my_module # Lint only 'my_module' and its dependencies 14 | apio lint --all # Enable all warnings, including style warnings 15 | ``` 16 | 17 |

Options

18 | 19 | ``` 20 | --nostyle Disable all style warnings 21 | --nowarn nowarn Disable specific warning(s) 22 | --warn warn Enable specific warning(s) 23 | -a, --all Enable all warnings, including code style warnings 24 | -t, --top-module name Restrict linting to this module and its dependencies 25 | -e, --env name Use a named environment from apio.ini 26 | -p, --project-dir path Specify the project root directory 27 | -h, --help Show help message and exit 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/creating-apio-version.md: -------------------------------------------------------------------------------- 1 | # Creating an Apio version 2 | 3 | !!! warning "Page under construction" 4 | The details of the release process is not finalized yet. 5 | 6 | ## Cutting a release candidate 7 | 8 | TBD 9 | 10 | ## Testing a release candidate 11 | 12 | TBD 13 | 14 | ## Releasing a release candidate as an official release 15 | 16 | TBD 17 | 18 |
19 | 20 | Things to pay attention to: 21 | 22 | - Determine the increment level, patch, minor, or major. 23 | - Setting the new apio version in `apio/__init__`. 24 | - Creating a `.jsonc` remote config file at https://github.com/FPGAwars/apio/tree/develop/remote-config with the desired package versions. 25 | - Waiting for the next daily apio build and make sure the build is green (use its apio commit as the cutoff commit) 26 | - Making sure that the apio test workflows are green for the cutoff commit. 27 | - Creating a new release in the apio repo and adding to it the build files from the build repo. 28 | - Somewhere along these steps, test the release candidate. 29 | 30 |
31 | -------------------------------------------------------------------------------- /scripts/check-icestudio-test-examples.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit on first error. 4 | set -e 5 | 6 | # Change to repo's dir. 7 | cd .. 8 | 9 | # Find all apio.ini in icestudio projects 10 | projects=$(find test-examples | grep icestudio | grep apio.ini) 11 | 12 | for proj in $projects; do 13 | # Get the parent directory of apio.ini 14 | proj="$(dirname "$proj")" 15 | 16 | echo 17 | echo "--- PROJECT $proj" 18 | echo 19 | 20 | # -- Go to the project's dir. 21 | pushd $proj 22 | 23 | # -- Test if the project has testbenches. 24 | set +e 25 | ls *_tb.v 26 | TB_STATUS=$? 27 | set -e 28 | 29 | 30 | # -- Exceute apio commands in the project. They should succeeed. 31 | set -x 32 | apio clean 33 | apio build 34 | apio lint 35 | if [ $TB_STATUS -eq 0 ]; then 36 | apio test 37 | fi 38 | apio graph 39 | apio report 40 | apio clean 41 | set +x 42 | 43 | # -- Go back to the repo's root. 44 | popd 45 | 46 | done 47 | 48 | 49 | -------------------------------------------------------------------------------- /docs/cmd-apio-preferences.md: -------------------------------------------------------------------------------- 1 | # Apio preferences 2 | 3 | --- 4 | 5 | ## apio preferences 6 | 7 | The `apio preferences` command lets you view and manage user preferences. These settings are stored in the `profile.json` file in the Apio home directory (e.g. `~/.apio`) and apply to all Apio projects. 8 | 9 | > To review the available themes on your screen type `apio info themes`. 10 | 11 |

Examples

12 | 13 | ``` 14 | apio preferences -t light # Set theme for light backgrounds 15 | apio preferences -t dark # Set theme for dark backgrounds 16 | apio preferences -t no-colors # Disable color output 17 | apio preferences --list # Show current preferences 18 | apio pref -t dark # Using command shortcut 19 | ``` 20 | 21 |

Options

22 | 23 | ``` 24 | -t, --theme [light|dark|no-colors] Set color theme 25 | -c, --colors List available theme colors 26 | -l, --list Show current preferences 27 | -h, --help Show help message and exit 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/board-drivers.md: -------------------------------------------------------------------------------- 1 | # FPGA Board Drivers 2 | 3 | Some FPGA boards require a system driver before the programmer can access them. When this is the case, install the driver with the `apio drivers install` command. 4 | 5 | Apio provides two types of driver: `ftdi` and `serial`. The table below compares them. 6 | 7 | > - Driver installation is not required on macOS. 8 | 9 | > - If the FPGA board appears in the device list with `--unavail--` manufacturer or product strings, the appropriate driver probably needs to be installed. 10 | 11 | | | FTDI driver | Serial driver | 12 | | ---------------- | :---------------------------- | :------------------------------ | 13 | | Platforms | Linux, Windows | Linux, Windows | 14 | | Driver install | `apio drivers install ftdi` | `apio drivers install serial` | 15 | | Driver uninstall | `apio drivers uninstall ftdi` | `apio drivers uninstall serial` | 16 | | List devices | `apio devices usb` | `apio devices serial` | 17 | -------------------------------------------------------------------------------- /docs/contributing-definitions.md: -------------------------------------------------------------------------------- 1 | # Contributing boards definitions 2 | 3 | Once you tested your custom boards, FPGAs, and programmers definitions 4 | as describe in the [Custom Boards](custom-boards.md) page, you can contribute 5 | them to the Apio project so they can benefit other users as well. 6 | 7 | > When contributing a new board definition, it's is highly recommended to 8 | > contribute also and example for that board. Please wait for the new board 9 | > definitions to be released before contributing examples for that board. 10 | 11 | Apio definitions are stored in the Apio `definitions` repository at [https://github.com/FPGAwars/apio-definitions ](https://github.com/FPGAwars/apio-definitions). Simply submit a pull request with the modified files. 12 | 13 | > Note that your contribution will be available to users only after Apio 14 | > will be configured remotely to use the new version of the `definitions` 15 | > package. The Apio remote configuration are served from [https://github.com/FPGAwars/apio/tree/develop/remote-config](https://github.com/FPGAwars/apio/tree/develop/remote-config) 16 | -------------------------------------------------------------------------------- /tests/unit_tests/common/test_apio_console.py: -------------------------------------------------------------------------------- 1 | """Test for the apio_console.py.""" 2 | 3 | from apio.common import apio_console 4 | from apio.common.apio_console import ( 5 | FORCE_TERMINAL, 6 | cstyle, 7 | cunstyle, 8 | ) 9 | 10 | 11 | def test_style_unstyle(): 12 | """Test the styling and unstyling functions""" 13 | 14 | apio_console.configure(terminal_mode=FORCE_TERMINAL, theme_name="light") 15 | 16 | # -- Test cstyle() 17 | assert cstyle("") == "" 18 | assert cstyle("", style="red") == "" 19 | assert cstyle("abc xyz", style="red") == "\x1b[31mabc xyz\x1b[0m" 20 | assert cstyle("abc xyz", style="cyan bold") == "\x1b[1;36mabc xyz\x1b[0m" 21 | assert cstyle("ab \n xy", style="cyan bold") == "\x1b[1;36mab \n xy\x1b[0m" 22 | 23 | # -- Test cunstyle() with plain text. 24 | assert cunstyle("") == "" 25 | assert cunstyle("abc xyz") == "abc xyz" 26 | 27 | # -- Test cunstyle() with colored text. 28 | assert cunstyle(cstyle("")) == "" 29 | assert cunstyle(cstyle("abc xyz")) == "abc xyz" 30 | assert cunstyle(cstyle("ab \n xy")) == "ab \n xy" 31 | -------------------------------------------------------------------------------- /.github/workflows/publish-to-pypi.yaml: -------------------------------------------------------------------------------- 1 | name: publish-to-pypi 2 | 3 | # Manual activation 4 | on: [workflow_dispatch] 5 | 6 | jobs: 7 | # -- Publish a new Apio release 8 | publish: 9 | runs-on: ubuntu-22.04 10 | steps: 11 | # -- Checkout the main branch 12 | - name: Checkout sources 13 | uses: actions/checkout@v4 14 | with: 15 | ref: master 16 | 17 | # -- Install and and configure python 18 | - name: Set up Python 19 | uses: actions/setup-python@v5 20 | with: 21 | python-version: "3.13" 22 | 23 | # -- Install all the dependencies needed 24 | # - name: Install dependencies 25 | # run: | 26 | # make deps 27 | 28 | # -- Publish to Pypi!! 29 | - name: Publish to PyPi 30 | env: 31 | FLIT_USERNAME: ${{ secrets.PYPI_USERNAME }} 32 | FLIT_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 33 | run: | 34 | invoke publish 35 | -------------------------------------------------------------------------------- /apio/common/proto/update-protos.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Run this script each time apio.proto is modified. 4 | 5 | # Input: 6 | # apio.proto - proto messages definitions. 7 | # 8 | # Outputs: 9 | # apio_pb2.py - python binding. 10 | # apio_pb2.pyi - symbols for visual studio code. 11 | 12 | 13 | 14 | # Exit on any error. 15 | set -e 16 | 17 | # This is the proto compiler 18 | echo "Installing the proto compiler" 19 | pip install --quiet grpcio-tools==1.76.0 20 | 21 | patch=" 22 | # pylint: disable=all 23 | " 24 | 25 | tmp_file="_tmp" 26 | 27 | patch_proto () { 28 | f=$1 29 | echo "Patching $f" 30 | mv $1 $tmp_file 31 | echo "$patch" > $1 32 | cat $tmp_file >> $1 33 | rm $tmp_file 34 | } 35 | 36 | # Clean old output files. 37 | rm -f apio_pb2.py 38 | rm -f apio_pb2.pyi 39 | rm -f $tmp_file 40 | 41 | # Generate new 42 | echo "Compiling apio.proto" 43 | python -m grpc_tools.protoc \ 44 | -I. \ 45 | --python_out=. \ 46 | --pyi_out=. apio.proto 47 | 48 | # Inject the pylint directive to supress warnings. 49 | patch_proto apio_pb2.py 50 | patch_proto apio_pb2.pyi 51 | 52 | # All done OK 53 | echo "All done" 54 | -------------------------------------------------------------------------------- /tests/unit_tests/commands/test_apio_sim.py: -------------------------------------------------------------------------------- 1 | """Test for the "apio sim" command""" 2 | 3 | from tests.conftest import ApioRunner 4 | from apio.commands.apio import apio_top_cli as apio 5 | 6 | 7 | def test_sim(apio_runner: ApioRunner): 8 | """Test: apio sim 9 | when no apio.ini file is given 10 | No additional parameters are given 11 | """ 12 | 13 | with apio_runner.in_sandbox() as sb: 14 | 15 | # -- apio sim 16 | result = sb.invoke_apio_cmd(apio, ["sim"]) 17 | assert result.exit_code != 0, result.output 18 | # -- TODO 19 | 20 | 21 | def test_sim_with_env_arg_error(apio_runner: ApioRunner): 22 | """Tests the command with an invalid --env value. This error message 23 | confirms that the --env arg was propagated to the apio.ini loading 24 | logic.""" 25 | 26 | with apio_runner.in_sandbox() as sb: 27 | 28 | # -- Run "apio sim --env no-such-env" 29 | sb.write_apio_ini({"[env:default]": {"top-module": "main"}}) 30 | result = sb.invoke_apio_cmd(apio, ["sim", "--env", "no-such-env"]) 31 | assert result.exit_code == 1, result.output 32 | assert ( 33 | "Error: Env 'no-such-env' not found in apio.ini" in result.output 34 | ) 35 | -------------------------------------------------------------------------------- /docs/cmd-apio-test.md: -------------------------------------------------------------------------------- 1 | # Apio test 2 | 3 | --- 4 | 5 | ## apio test 6 | 7 | The `apio test` command simulates one or more testbenches in the project. 8 | It is intended for automated testing of your design. Testbenches should 9 | have filenames ending in `_tb` (e.g., `my_module_tb.v`) and should use 10 | the `$fatal` directive to indicate errors. 11 | 12 |

Examples

13 | 14 | ``` 15 | apio test # Run all *_tb.v testbenches 16 | apio test my_module_tb.v # Run a single testbench 17 | ``` 18 | 19 |

Options

20 | 21 | ``` 22 | -e, --env name Use a named environment from apio.ini 23 | -p, --project-dir path Specify the project root directory 24 | -h, --help Show help message and exit 25 | ``` 26 | 27 |

Notes

28 | 29 | - Avoid using the Verilog `$dumpfile()` function, as it may override 30 | the default name and location Apio assigns for the generated `.vcd` file. 31 | 32 | - Testbench paths must be relative to the project directory, 33 | even when using the `--project-dir` option. 34 | 35 | - See the Apio example `alhambra-ii/getting-started` for a testbench 36 | that demonstrates recommended practices. 37 | 38 | - For graphical signal visualization, use the `apio sim` command instead. 39 | -------------------------------------------------------------------------------- /tests/unit_tests/commands/test_apio_graph.py: -------------------------------------------------------------------------------- 1 | """Test for the "apio graph" command.""" 2 | 3 | from tests.conftest import ApioRunner 4 | from apio.commands.apio import apio_top_cli as apio 5 | 6 | 7 | def test_graph_no_apio_ini(apio_runner: ApioRunner): 8 | """Test: apio graph with no apio.ini""" 9 | 10 | with apio_runner.in_sandbox() as sb: 11 | 12 | # -- Execute "apio graph" 13 | result = sb.invoke_apio_cmd(apio, ["graph"]) 14 | assert result.exit_code == 1, result.output 15 | assert "Error: Missing project file apio.ini" in result.output 16 | 17 | 18 | def test_graph_with_env_arg_error(apio_runner: ApioRunner): 19 | """Tests the command with an invalid --env value. This error message 20 | confirms that the --env arg was propagated to the apio.ini loading 21 | logic.""" 22 | 23 | with apio_runner.in_sandbox() as sb: 24 | 25 | # -- Run "apio graph --env no-such-env" 26 | sb.write_apio_ini({"[env:default]": {"top-module": "main"}}) 27 | result = sb.invoke_apio_cmd(apio, ["graph", "--env", "no-such-env"]) 28 | assert result.exit_code == 1, result.output 29 | assert ( 30 | "Error: Env 'no-such-env' not found in apio.ini" in result.output 31 | ) 32 | -------------------------------------------------------------------------------- /tests/unit_tests/commands/test_apio_lint.py: -------------------------------------------------------------------------------- 1 | """Test for the "apio lint" command.""" 2 | 3 | from tests.conftest import ApioRunner 4 | from apio.commands.apio import apio_top_cli as apio 5 | 6 | 7 | def test_lint_apio_init(apio_runner: ApioRunner): 8 | """Test: apio lint without an apio.ini project file.""" 9 | 10 | with apio_runner.in_sandbox() as sb: 11 | 12 | # -- Execute "apio lint" 13 | result = sb.invoke_apio_cmd(apio, ["lint"]) 14 | assert result.exit_code == 1, result.output 15 | assert "Error: Missing project file apio.ini" in result.output 16 | 17 | 18 | def test_lint_with_env_arg_error(apio_runner: ApioRunner): 19 | """Tests the command with an invalid --env value. This error message 20 | confirms that the --env arg was propagated to the apio.ini loading 21 | logic.""" 22 | 23 | with apio_runner.in_sandbox() as sb: 24 | 25 | # -- Run "apio lint --env no-such-env" 26 | sb.write_apio_ini({"[env:default]": {"top-module": "main"}}) 27 | result = sb.invoke_apio_cmd(apio, ["lint", "--env", "no-such-env"]) 28 | assert result.exit_code == 1, result.output 29 | assert ( 30 | "Error: Env 'no-such-env' not found in apio.ini" in result.output 31 | ) 32 | -------------------------------------------------------------------------------- /docs/cmd-apio-upload.md: -------------------------------------------------------------------------------- 1 | # Apio upload 2 | 3 | --- 4 | 5 | ## apio upload 6 | 7 | The `apio upload` command builds the bitstream (like `apio build`) and uploads it to the FPGA board. 8 | 9 |

Examples

10 | 11 | ``` 12 | apio upload # Typical usage 13 | apio upload -s /dev/cu.usbserial-1300 # Specify serial port 14 | apio upload -n FTXYA34Z # Specify USB serial number 15 | ``` 16 | 17 |

Options

18 | 19 | ``` 20 | -s, --serial-port serial-port Specify the serial port 21 | -n, --serial-num serial-num Specify the device's USB serial number 22 | -e, --env name Use a named environment from apio.ini 23 | -p, --project-dir path Specify the project root directory 24 | -h, --help Show this help message and exit 25 | ``` 26 | 27 |

Notes

28 | 29 | - In most cases, `apio upload` is enough to locate and program the FPGA board. Use the `--serial-port` or `--serial-num` options to select a specific board if multiple matching devices are connected. 30 | 31 | - Use `apio devices` to list connected USB and serial devices, and `apio drivers` to install or uninstall device drivers. 32 | 33 | - You can override the board's default programmer using the `programmer-cmd` option in `apio.ini`. 34 | -------------------------------------------------------------------------------- /docs/cmd-apio-build.md: -------------------------------------------------------------------------------- 1 | # Apio build 2 | 3 | --- 4 | 5 | ## apio build 6 | 7 | The `apio build` command compiles the project's source files and 8 | generates a bitstream ready for upload to the FPGA. 9 | 10 |

Examples

11 | 12 | ``` 13 | apio build # Typical Usage 14 | apio build -e debug # Use a specific environment from apio.ini 15 | apio build -v # Show all verbose output 16 | apio build --verbose-synth # Verbose synthesis info 17 | apio build --verbose-pnr # Verbose place and route info 18 | ``` 19 | 20 |

Options

21 | 22 | ``` 23 | -e, --env name Use a named environment from apio.ini 24 | -p, --project-dir path Set the project's root directory 25 | -v, --verbose Show all verbose output 26 | --verbose-synth Show verbose synthesis stage output 27 | --verbose-pnr Show verbose place-and-route stage output 28 | -h, --help Show help message and exit 29 | ``` 30 | 31 |

Notes

32 | 33 | - Specify the top module using the `top-module` option in `apio.ini`. 34 | - Testbench files (`*_tb.v` and `*_tb.sv`) are ignored during build. 35 | - Running `apio build` before `apio upload` is usually unnecessary. 36 | - Run `apio clean` before building to force a full rebuild. 37 | -------------------------------------------------------------------------------- /tests/unit_tests/commands/test_apio_report.py: -------------------------------------------------------------------------------- 1 | """Test for the "apio report" command.""" 2 | 3 | from tests.conftest import ApioRunner 4 | from apio.commands.apio import apio_top_cli as apio 5 | 6 | 7 | def test_report_no_apio(apio_runner: ApioRunner): 8 | """Tests the apio report command without an apio.ini file.""" 9 | 10 | with apio_runner.in_sandbox() as sb: 11 | 12 | # -- Run "apio report" without apio.ini 13 | result = sb.invoke_apio_cmd(apio, ["report"]) 14 | assert result.exit_code != 0, result.output 15 | assert "Error: Missing project file apio.ini" in result.output 16 | 17 | 18 | def test_report_with_env_arg_error(apio_runner: ApioRunner): 19 | """Tests the command with an invalid --env value. This error message 20 | confirms that the --env arg was propagated to the apio.ini loading 21 | logic.""" 22 | 23 | with apio_runner.in_sandbox() as sb: 24 | 25 | # -- Run "apio report --env no-such-env" 26 | sb.write_apio_ini({"[env:default]": {"top-module": "main"}}) 27 | result = sb.invoke_apio_cmd(apio, ["report", "--env", "no-such-env"]) 28 | assert result.exit_code == 1, result.output 29 | assert ( 30 | "Error: Env 'no-such-env' not found in apio.ini" in result.output 31 | ) 32 | -------------------------------------------------------------------------------- /docs/cmd-apio-raw.md: -------------------------------------------------------------------------------- 1 | # Apio raw 2 | 3 | --- 4 | 5 | ## apio raw 6 | 7 | The `apio raw` command bypasses Apio's usual workflow to run tools directly. It is intended for advanced users familiar with those tools. 8 | 9 | > Before execution, Apio temporarily modifies environment variables like `$PATH` 10 | > to make its packages accessible. Use the option `--verbose` to view these changes. 11 | 12 |

Examples

13 | 14 | ``` 15 | apio raw -- yosys --version # Show Yosys version 16 | apio raw -v -- yosys --version # Verbose output 17 | apio raw -- yosys # Start Yosys in interactive mode 18 | apio raw -- icepll -i 12 -o 30 # Calculate ICE PLL parameters 19 | apio raw -- which yosys # Locate yosys in the path 20 | apio raw -- bash # Open a shell with Apio's env. 21 | apio raw -- zadig # Run Zadig (on Windows). 22 | apio raw -v # Show Apio environment settings 23 | apio raw -h # Show help message 24 | ``` 25 | 26 | > If a command is specified, it must be prefixed with the `--` marker to 27 | > distinguish it from the apio command args. 28 | 29 |

Options

30 | 31 | ``` 32 | -v, --verbose Show detailed output 33 | -h, --help Show help message and exit 34 | ``` 35 | -------------------------------------------------------------------------------- /apio/commands/apio_drivers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # -- This file is part of the Apio project 3 | # -- (C) 2016-2024 FPGAwars 4 | # -- Authors 5 | # -- * Jesús Arroyo (2016-2019) 6 | # -- * Juan Gonzalez (obijuan) (2019-2024) 7 | # -- License GPLv2 8 | """Implementation of 'apio drivers' command group.""" 9 | 10 | import click 11 | from apio.utils.cmd_util import ApioGroup, ApioSubgroup 12 | from apio.commands import ( 13 | apio_drivers_install, 14 | apio_drivers_uninstall, 15 | ) 16 | 17 | 18 | # --- apio drivers 19 | 20 | # -- Text in the rich-text format of the python rich library. 21 | APIO_DRIVERS_HELP = """ 22 | The command group 'apio drivers' contains subcommands to manage the \ 23 | drivers on your system. 24 | """ 25 | 26 | # -- We have only a single group with the title 'Subcommands'. 27 | SUBGROUPS = [ 28 | ApioSubgroup( 29 | "Subcommands", 30 | [ 31 | apio_drivers_install.cli, 32 | apio_drivers_uninstall.cli, 33 | ], 34 | ) 35 | ] 36 | 37 | 38 | @click.command( 39 | name="drivers", 40 | cls=ApioGroup, 41 | subgroups=SUBGROUPS, 42 | short_help="Manage the operating system drivers.", 43 | help=APIO_DRIVERS_HELP, 44 | ) 45 | def cli(): 46 | """Implements the apio drivers command.""" 47 | 48 | # pass 49 | -------------------------------------------------------------------------------- /tests/unit_tests/commands/test_apio_format.py: -------------------------------------------------------------------------------- 1 | """Test for the "apio format" command.""" 2 | 3 | from tests.conftest import ApioRunner 4 | from apio.commands.apio import apio_top_cli as apio 5 | 6 | 7 | def test_format_without_apio_ini(apio_runner: ApioRunner): 8 | """Tests the apio format command with a missing apio.ini file.""" 9 | 10 | with apio_runner.in_sandbox() as sb: 11 | 12 | # -- Run "apio format" with no apio.ini 13 | result = sb.invoke_apio_cmd(apio, ["format"]) 14 | assert result.exit_code != 0, result.output 15 | assert "Error: Missing project file apio.ini" in result.output 16 | 17 | 18 | def test_format_with_env_arg_error(apio_runner: ApioRunner): 19 | """Tests the command with an invalid --env value. This error message 20 | confirms that the --env arg was propagated to the apio.ini loading 21 | logic.""" 22 | 23 | with apio_runner.in_sandbox() as sb: 24 | 25 | # -- Run "apio format --env no-such-env" 26 | sb.write_apio_ini({"[env:default]": {"top-module": "main"}}) 27 | result = sb.invoke_apio_cmd(apio, ["format", "--env", "no-such-env"]) 28 | assert result.exit_code == 1, result.output 29 | assert ( 30 | "Error: Env 'no-such-env' not found in apio.ini" in result.output 31 | ) 32 | -------------------------------------------------------------------------------- /tests/unit_tests/commands/test_apio_test.py: -------------------------------------------------------------------------------- 1 | """Test for the "apio test" command.""" 2 | 3 | from tests.conftest import ApioRunner 4 | from apio.commands.apio import apio_top_cli as apio 5 | 6 | 7 | def test_test(apio_runner: ApioRunner): 8 | """Test: apio test 9 | when no apio.ini file is given 10 | No additional parameters are given 11 | """ 12 | 13 | with apio_runner.in_sandbox() as sb: 14 | 15 | # -- Execute "apio test" 16 | result = sb.invoke_apio_cmd(apio, ["test"]) 17 | assert result.exit_code != 0, result.output 18 | assert "Error: Missing project file apio.ini" in result.output 19 | 20 | 21 | def test_with_env_arg_error(apio_runner: ApioRunner): 22 | """Tests the command with an invalid --env value. This error message 23 | confirms that the --env arg was propagated to the apio.ini loading 24 | logic.""" 25 | 26 | with apio_runner.in_sandbox() as sb: 27 | 28 | # -- Run "apio test --env no-such-env" 29 | sb.write_apio_ini({"[env:default]": {"top-module": "main"}}) 30 | result = sb.invoke_apio_cmd(apio, ["test", "--env", "no-such-env"]) 31 | assert result.exit_code == 1, result.output 32 | assert ( 33 | "Error: Env 'no-such-env' not found in apio.ini" in result.output 34 | ) 35 | -------------------------------------------------------------------------------- /apio/resources/80-fpga-ftdi.rules: -------------------------------------------------------------------------------- 1 | ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6010", MODE="0660", GROUP="plugdev", TAG+="uaccess" 2 | ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6014", MODE="0660", GROUP="plugdev", TAG+="uaccess" 3 | ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6015", MODE="0660", GROUP="plugdev", TAG+="uaccess" 4 | ATTRS{idVendor}=="1209", ATTRS{idProduct}=="5af0", MODE="0660", GROUP="plugdev", TAG+="uaccess" 5 | ATTRS{idVendor}=="1209", ATTRS{idProduct}=="5bf0", MODE="0660", GROUP="plugdev", TAG+="uaccess" 6 | 7 | # Ulx3s boards 8 | # -- fujprog libusb access 9 | ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6015", MODE="666", GROUP="dialout" 10 | # -- For usb-serial tty device 11 | SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6015", MODE="664", GROUP="dialout" 12 | 13 | # -- for iCESugar board 14 | ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="602b", MODE="0660", GROUP="plugdev", TAG+="uaccess" 15 | 16 | # -- for Fomu board 17 | SUBSYSTEM=="usb", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="5bf0", MODE="0664", GROUP="plugdev" 18 | 19 | # -- for USB-Blaster JTAG Programmer 20 | ATTRS{idVendor}=="09FB", ATTRS{idProduct}=="6001", MODE="0660", GROUP="plugdev", TAG+="uaccess" 21 | 22 | # -- for pico-ice board 23 | ATTRS{idVendor}=="1209", ATTRS{idProduct}=="b1c0", MODE="0660", GROUP="plugdev", TAG+="uaccess" -------------------------------------------------------------------------------- /tests/unit_tests/commands/test_apio.py: -------------------------------------------------------------------------------- 1 | """Test for the "apio" (root) command.""" 2 | 3 | from tests.conftest import ApioRunner 4 | from apio.commands.apio import apio_top_cli as apio 5 | 6 | 7 | def test_apio_cmd(apio_runner: ApioRunner): 8 | """Test the apio root command.""" 9 | 10 | with apio_runner.in_sandbox() as sb: 11 | 12 | # -- Run 'apio' 13 | result = sb.invoke_apio_cmd(apio, []) 14 | sb.assert_result_ok(result) 15 | assert "WORK WITH FPGAs WITH EASE." in result.output 16 | assert "Build commands:" in result.output 17 | assert "Upload the bitstream to the FPGA" in result.output 18 | 19 | # -- Run 'apio --help' 20 | result = sb.invoke_apio_cmd(apio, ["--help"]) 21 | sb.assert_result_ok(result) 22 | assert "WORK WITH FPGAs WITH EASE." in result.output 23 | assert "Build commands:" in result.output 24 | assert "Upload the bitstream to the FPGA" in result.output 25 | 26 | # -- Run 'apio --version' 27 | result = sb.invoke_apio_cmd(apio, ["--version"]) 28 | sb.assert_result_ok(result) 29 | assert "apio, version" in result.output 30 | 31 | # -- Run 'apio badcommand' 32 | result = sb.invoke_apio_cmd(apio, ["badcommand"]) 33 | assert result.exit_code != 0 34 | assert "Error: No such command 'badcommand'" in result.output 35 | -------------------------------------------------------------------------------- /tests/unit_tests/commands/test_apio_upload.py: -------------------------------------------------------------------------------- 1 | """Test for the "apio upload" command.""" 2 | 3 | from tests.conftest import ApioRunner 4 | from apio.commands.apio import apio_top_cli as apio 5 | 6 | 7 | def test_upload_without_apio_ini(apio_runner: ApioRunner): 8 | """Test: apio upload 9 | when no apio.ini file is given 10 | No additional parameters are given 11 | """ 12 | 13 | with apio_runner.in_sandbox() as sb: 14 | 15 | # -- Execute "apio upload" 16 | result = sb.invoke_apio_cmd(apio, ["upload"]) 17 | 18 | # -- Check the result 19 | assert result.exit_code == 1, result.output 20 | assert "Error: Missing project file apio.ini" in result.output 21 | 22 | 23 | def test_upload_with_env_arg_error(apio_runner: ApioRunner): 24 | """Tests the command with an invalid --env value. This error message 25 | confirms that the --env arg was propagated to the apio.ini loading 26 | logic.""" 27 | 28 | with apio_runner.in_sandbox() as sb: 29 | 30 | # -- Run "apio upload --env no-such-env" 31 | sb.write_apio_ini({"[env:default]": {"top-module": "main"}}) 32 | result = sb.invoke_apio_cmd(apio, ["upload", "--env", "no-such-env"]) 33 | assert result.exit_code == 1, result.output 34 | assert ( 35 | "Error: Env 'no-such-env' not found in apio.ini" in result.output 36 | ) 37 | -------------------------------------------------------------------------------- /docs/contributing-examples.md: -------------------------------------------------------------------------------- 1 | # Contributing Apio examples 2 | 3 | Apio examples are stored in the `examples` directory of the [FPGAwars/apio-examples](https://github.com/FPGAwars/apio-examples) repository and are distributed in the Apio `examples` package (type `apio examples list` to check its version). 4 | 5 | To contribute a new example, submit a pull request to the [FPGAwars/apio-examples](https://github.com/FPGAwars/apio-examples) repository after confirming it meets the following requirements: 6 | 7 | - The example includes an `info` file with a single-line description (max 50 characters). 8 | - The example passes linting with no warnings and can be built and uploaded. 9 | - The example path is `examples//`, where `` matches the value in `apio.ini` and `` includes only lowercase letters (a–z), digits (0–9), and dashes (-), and begins with a letter. 10 | - The example includes at least one testbench that passes `apio test` and `apio sim` without errors. 11 | - Each testbench has a corresponding `.gtkw` file with a saved GTKWave state. 12 | 13 | ## NOTES 14 | 15 | - Files may be placed in a single directory or organized into subdirectories. 16 | - Verilog `.v` and SystemVerilog `.sv` files may be used in any combination. 17 | - If `apio.ini` defines multiple environments, the rules above apply to each environment selected with the `--env` flag. 18 | -------------------------------------------------------------------------------- /.github/workflows/resources/windows/innosetup.template: -------------------------------------------------------------------------------- 1 | ; Config file for building Apio's InnoSetup installer. 2 | ; [APIO_VERSION] is a placeholder. 3 | 4 | [Setup] 5 | ChangesEnvironment=yes 6 | AppName=Apio 7 | AppVersion=[APIO_VERSION] 8 | DefaultDirName={commonpf}\Apio 9 | ArchitecturesInstallIn64BitMode=x64 10 | DefaultGroupName=Apio 11 | OutputDir=. 12 | OutputBaseFilename=innosetup-installer 13 | 14 | [Files] 15 | Source: "_dist\apio\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs 16 | Source: ".github\workflows\resources\windows\README.txt"; DestDir: "{app}"; Flags: isreadme 17 | 18 | ; Code from here is based on the answers at 19 | ; https://stackoverflow.com/questions/3304463/how-do-i-modify-the-path-environment-variable-when-running-an-inno-setup-install 20 | 21 | [Registry] 22 | Root: HKLM; Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{app}" 23 | 24 | [CustomMessages] 25 | AppAddPath=Add application directory to your environmental path (required) 26 | 27 | [Tasks] 28 | Name: modifypath; Description:{cm:AppAddPath}; 29 | 30 | [Code] 31 | const 32 | ModPathName = 'modifypath'; 33 | ModPathType = 'system'; 34 | 35 | function ModPathDir(): TArrayOfString; 36 | begin 37 | setArrayLength(Result, 1) 38 | Result[0] := ExpandConstant('{app}'); 39 | end; 40 | 41 | #include ".github/workflows/resources/windows/modpath.iss" 42 | 43 | 44 | -------------------------------------------------------------------------------- /docs/cmd-apio-graph.md: -------------------------------------------------------------------------------- 1 | # Apio graph 2 | 3 | --- 4 | 5 | ## apio graph 6 | 7 | The `apio graph` command generates a graphical representation of the hardware 8 | design and then opens it in a graphical viewer. 9 | 10 |

Examples

11 | 12 | ``` 13 | apio graph # Generate an SVG file (default) 14 | apio graph --no-viewer # Suppress the graphical viewer. 15 | apio graph --svg # Generate an SVG file 16 | apio graph --pdf # Generate a PDF file 17 | apio graph --png # Generate a PNG file 18 | apio graph -t my_module # Graph the 'my_module' module 19 | ``` 20 | 21 |

Options

22 | 23 | ``` 24 | --svg Generate an SVG file (default) 25 | --png Generate a PNG file 26 | --pdf Generate a PDF file 27 | -e, --env name Use a named environment from apio.ini 28 | -p, --project-dir path Specify the project root directory 29 | -t, --top-module name Set the top-level module to graph 30 | -n, --no-viewer Do not open graph viewer 31 | -v, --verbose Show detailed output 32 | -h, --help Show help message and exit 33 | ``` 34 | 35 |

Notes

36 | 37 | - On Windows, run `explorer _build/default/graph.svg` to view the graph. 38 | If your environment name is different from `default`, adjust the path accordingly. 39 | - On macOS, use `open _build/default/graph.svg`. 40 | 41 |

Example output

42 | 43 | ![](assets/apio-graph.svg) 44 | -------------------------------------------------------------------------------- /tests/unit_tests/scons/test_apio_env.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests of the scons ApioEnv. 3 | """ 4 | 5 | from tests.unit_tests.scons.testing import make_test_apio_env 6 | from tests.conftest import ApioRunner 7 | from apio.scons.apio_env import ApioEnv 8 | 9 | 10 | def test_env_is_debug(apio_runner: ApioRunner): 11 | """Tests the env handling of the in_debug var.""" 12 | 13 | with apio_runner.in_sandbox(): 14 | 15 | env: ApioEnv = make_test_apio_env(debug_level=2) 16 | assert env.is_debug(1) 17 | assert env.is_debug(2) 18 | assert not env.is_debug(3) 19 | 20 | env: ApioEnv = make_test_apio_env(debug_level=0) 21 | assert not env.is_debug(1) 22 | assert not env.is_debug(2) 23 | assert not env.is_debug(3) 24 | 25 | 26 | def test_env_platform_id(): 27 | """Tests the env handling of the platform_id param.""" 28 | 29 | # -- Test with a non windows platform id. 30 | env = make_test_apio_env(platform_id="darwin-arm64", is_windows=False) 31 | assert not env.is_windows 32 | 33 | # -- Test with a windows platform id. 34 | env = make_test_apio_env(platform_id="windows-amd64", is_windows=True) 35 | assert env.is_windows 36 | 37 | 38 | def test_targeting(): 39 | """Test the targeting() method.""" 40 | 41 | # -- The test env targets 'build'. 42 | apio_env = make_test_apio_env() 43 | 44 | assert apio_env.targeting("build") 45 | assert apio_env.targeting("upload", "build") 46 | assert not apio_env.targeting("upload") 47 | -------------------------------------------------------------------------------- /docs/cmd-apio-report.md: -------------------------------------------------------------------------------- 1 | # Apio report 2 | 3 | --- 4 | 5 | ## apio report 6 | 7 | The `apio report` command provides resource utilization and timing 8 | information for the design. It helps identify bottlenecks and verify 9 | whether the design meets its target clock speed. 10 | 11 |

Examples

12 | 13 | ``` 14 | apio report # Show report 15 | apio report --verbose # Show detailed report 16 | ``` 17 | 18 |

Options

19 | 20 | ``` 21 | -e, --env name Use a named environment from apio.ini 22 | -p, --project-dir path Specify the project root directory 23 | -v, --verbose Show detailed output 24 | -h, --help Show help message and exit 25 | ``` 26 | 27 |

Example report

28 | 29 | ``` 30 | FPGA Resource Utilization 31 | ┌────────────────┬────────┬──────────┬─────────┐ 32 | │ RESOURCE │ USED │ TOTAL │ UTIL. │ 33 | ├────────────────┼────────┼──────────┼─────────┤ 34 | │ ICESTORM_LC │ 90 │ 7680 │ 1% │ 35 | │ ICESTORM_PLL │ │ 2 │ │ 36 | │ ICESTORM_RAM │ │ 32 │ │ 37 | │ SB_GB │ 2 │ 8 │ 25% │ 38 | │ SB_IO │ 9 │ 256 │ 3% │ 39 | │ SB_WARMBOOT │ │ 1 │ │ 40 | └────────────────┴────────┴──────────┴─────────┘ 41 | 42 | Clock Information 43 | ┌─────────┬───────────────────┐ 44 | │ CLOCK │ MAX SPEED [Mhz] │ 45 | ├─────────┼───────────────────┤ 46 | │ CLK │ 119.15 │ 47 | └─────────┴───────────────────┘ 48 | ``` 49 | -------------------------------------------------------------------------------- /scripts/diff_jsonc.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python 2 | 3 | """Diff two JSONC files, ignoring comments and key order.""" 4 | 5 | # pylint: disable=import-error 6 | 7 | # Requires: 8 | # pip install deepdiff 9 | 10 | import json 11 | import sys 12 | import re 13 | from pathlib import Path 14 | from deepdiff import DeepDiff 15 | 16 | 17 | def remove_jsonc_comments(text): 18 | """Remove // and /* */ comments.""" 19 | text = re.sub(r"//.*", "", text) 20 | text = re.sub(r"/\*.*?\*/", "", text, flags=re.DOTALL) 21 | return text 22 | 23 | 24 | def load_and_normalize_jsonc(path): 25 | """Load the jsonc file as a tree of dics.""" 26 | raw = Path(path).read_text(encoding="utf-8") 27 | cleaned = remove_jsonc_comments(raw) 28 | data = json.loads(cleaned) 29 | return data 30 | 31 | 32 | if __name__ == "__main__": 33 | if len(sys.argv) != 3: 34 | print("Usage: python diff_jsonc.py file1.jsonc file2.jsonc") 35 | sys.exit(1) 36 | 37 | file1 = load_and_normalize_jsonc(sys.argv[1]) 38 | file2 = load_and_normalize_jsonc(sys.argv[2]) 39 | 40 | # print("---File 1 keys begin") 41 | # keys_list = file1.keys() 42 | # keys_list = sorted(keys_list) 43 | # for key in keys_list: 44 | # print(key) 45 | # print("---File 1 keys end") 46 | 47 | diff = DeepDiff(file1, file2, ignore_order=True) 48 | if diff: 49 | print("Differences found:") 50 | print(diff.pretty()) 51 | else: 52 | print("Files are equal (ignoring comments and key order).") 53 | -------------------------------------------------------------------------------- /docs/system-requirements.md: -------------------------------------------------------------------------------- 1 | # Apio System Requirements 2 | 3 | > The information on this page was last updated in October 2025. 4 | 5 | ## Operating System 6 | 7 | Apio is tested on the following platforms (as of Nov 2025): 8 | 9 | | Apio Platform Code | Description | Tested Github Versions | Apio IDE | Apio CLI | 10 | | :----------------- | :-------------------- | :--------------------- | -------- | -------- | 11 | | darwin_arm64 | macOS (Apple Silicon) | macos-latest | YES | YES | 12 | | darwin_x86_64 | macOS (Intel) | macos-15-intel | | YES | 13 | | linux_x86_64 | Linux x86 64-bit | ubuntu-22.04 | YES | YES | 14 | | linux_aarch64 | Linux ARM 64-bit | (not tested) | | YES | 15 | | windows_amd64 | Windows x86 64-bit | windows-latest | YES | YES | 16 | 17 | > The tested version are set in the Apio github test workflow. 18 | 19 | ## Python 20 | 21 | These requirements apply only when installing Apio as a Pip package (Python-based installation). 22 | 23 | > Python is not required when installing Apio using an installer, Debian package, or a file bundle. 24 | 25 | > To test the Python version, run `python --version`. To download Python, visit [python.org](https://www.python.org/downloads/). 26 | 27 | | Python Version | Status | 28 | | :------------- | :---------- | 29 | | 3.14.x | Recommended | 30 | | 3.13.x | Supported | 31 | | 3.12.x | Supported | 32 | | 3.11.x | Supported | 33 | -------------------------------------------------------------------------------- /tests/unit_tests/commands/test_shortcuts.py: -------------------------------------------------------------------------------- 1 | """Test command shortcuts.""" 2 | 3 | from tests.conftest import ApioRunner 4 | from apio.commands.apio import apio_top_cli as apio 5 | 6 | 7 | def test_command_shortcuts(apio_runner: ApioRunner): 8 | """Test the apio root command.""" 9 | 10 | with apio_runner.in_sandbox() as sb: 11 | 12 | # -- Run 'apio preferences --list' 13 | # -- Exact match. 14 | result = sb.invoke_apio_cmd(apio, ["preferences", "--list"]) 15 | sb.assert_result_ok(result) 16 | assert "Theme" in result.output 17 | assert "light" in result.output 18 | 19 | # -- Run 'apio pr -xyz' 20 | # -- Unique match. 21 | result = sb.invoke_apio_cmd(apio, ["pr", "-xyz"]) 22 | assert result.exit_code != 0 23 | assert "Usage: apio preferences" in result.output 24 | 25 | # -- Run 'apio pr -h' 26 | # -- Help text should contain full commands.. 27 | result = sb.invoke_apio_cmd(apio, ["pr", "-h"]) 28 | sb.assert_result_ok(result) 29 | assert "Usage: apio preferences" in result.output 30 | 31 | # -- Run 'apio p' 32 | # -- Ambiguous match 33 | result = sb.invoke_apio_cmd(apio, ["p", "--list"]) 34 | assert result.exit_code != 0 35 | assert "'p' is ambagious: ['packages', 'preferences']" in result.output 36 | 37 | # -- Run 'apio xyz --list' 38 | # -- No match 39 | result = sb.invoke_apio_cmd(apio, ["xyz", "--list"]) 40 | assert result.exit_code != 0 41 | assert "No such command 'xyz'" in result.output 42 | -------------------------------------------------------------------------------- /docs/cmd-apio-format.md: -------------------------------------------------------------------------------- 1 | # Apio format 2 | 3 | --- 4 | 5 | ## apio format 6 | 7 | The `apio format` command formats project source files to ensure consistent 8 | style without changing their behavior. You can format specific files or let 9 | it format all project files by default. 10 | 11 | > File paths are always relative to the project directory, even when using `--project-dir`. 12 | 13 |

Examples

14 | 15 | ``` 16 | apio format # Format all source files. 17 | apio format -v # Format all files with verbose output. 18 | apio format main.v main_tb.v # Format the two files. 19 | ``` 20 | 21 |

Options

22 | 23 | ``` 24 | -e, --env name Use a named environment from apio.ini 25 | -p, --project-dir path Specify the project root directory 26 | -v, --verbose Show detailed output 27 | -h, --help Show help message and exit 28 | ``` 29 | 30 |

Customization

31 | 32 | The format command utilizes the format tool from the Verible project, 33 | which can be configured using the `format-verible-options` setting in `apio.ini`. For example: 34 | 35 | ``` 36 | format-verible-options = 37 | --column_limit=80 38 | --indentation_spaces=4 39 | ``` 40 | 41 | For a full list of Verible formatter flags, refer to the documentation 42 | page online or use the command `apio raw -- verible-verilog-format --helpfull`. 43 | 44 |

Protecting code

45 | 46 | Sections of source code can be protected from formatting using the Verible formatter directives: 47 | 48 | ``` 49 | // verilog_format: off 50 | ... untouched code ... 51 | // verilog_format: on 52 | ``` 53 | -------------------------------------------------------------------------------- /tests/unit_tests/commands/test_apio_devices.py: -------------------------------------------------------------------------------- 1 | """Test for the "apio devices" command.""" 2 | 3 | from tests.conftest import ApioRunner 4 | from apio.common.apio_console import cunstyle 5 | from apio.commands.apio import apio_top_cli as apio 6 | 7 | 8 | def test_devices(apio_runner: ApioRunner): 9 | """Test "apio devices" """ 10 | 11 | with apio_runner.in_sandbox() as sb: 12 | 13 | # -- Execute "apio devices" 14 | result = sb.invoke_apio_cmd(apio, ["devices"]) 15 | sb.assert_result_ok(result) 16 | assert "apio devices usb" in cunstyle(result.output) 17 | assert "apio devices serial" in cunstyle(result.output) 18 | 19 | 20 | def test_apio_devices(apio_runner: ApioRunner): 21 | """Test "apio devices usb|serial" """ 22 | 23 | with apio_runner.in_sandbox() as sb: 24 | 25 | # -- Execute "apio devices usb". We run it in a 26 | # -- subprocess such that it releases the libusb1 file it uses. 27 | # -- This also means that it's not included in the pytest test 28 | # -- coverage report. 29 | result = sb.invoke_apio_cmd( 30 | apio, ["devices", "usb"], in_subprocess=True 31 | ) 32 | sb.assert_result_ok(result) 33 | print(result.output) 34 | 35 | # -- Execute "apio devices serial". We run it in a 36 | # -- subprocess such that it releases the libusb1 file it uses. 37 | # -- This also means that it's not included in the pytest test 38 | # -- coverage report. 39 | result = sb.invoke_apio_cmd( 40 | apio, ["devices", "serial"], in_subprocess=True 41 | ) 42 | sb.assert_result_ok(result) 43 | print(result.output) 44 | -------------------------------------------------------------------------------- /.github/workflows/resources/apio-pyinstaller.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python ; coding: utf-8 -*- 2 | 3 | # Pyinstaller spec file for apio package generation. 4 | 5 | from PyInstaller.utils.hooks import collect_submodules 6 | import apio 7 | import sys 8 | from pathlib import Path 9 | 10 | # -- Get the path to the 'apio' dir of the apio package. 11 | apio_dir = Path(sys.modules["apio"].__file__).parent 12 | print(f"{apio_dir=}") 13 | 14 | added_files = [ 15 | ( apio_dir / 'resources/*', 'apio/resources' ), 16 | ( apio_dir / 'scons/SConstruct', 'apio/scons' ), 17 | ] 18 | 19 | hiddenimports = ( 20 | collect_submodules('SCons') + 21 | collect_submodules('apio') + 22 | collect_submodules('usb') 23 | ) 24 | 25 | # Per https://github.com/orgs/pyinstaller/discussions/9148 26 | excludes = [ 'pkg_resources' ] 27 | 28 | a = Analysis( 29 | [apio_dir / '__main__.py'], 30 | pathex=[], 31 | binaries=[], 32 | datas=added_files, 33 | hiddenimports=hiddenimports, 34 | hookspath=[], 35 | hooksconfig={}, 36 | runtime_hooks=[], 37 | excludes=excludes, 38 | noarchive=False, 39 | optimize=0, 40 | ) 41 | pyz = PYZ(a.pure) 42 | 43 | exe = EXE( 44 | pyz, 45 | a.scripts, 46 | [], 47 | exclude_binaries=True, 48 | name='apio', 49 | debug=False, 50 | bootloader_ignore_signals=False, 51 | strip=False, 52 | upx=True, 53 | console=True, 54 | disable_windowed_traceback=False, 55 | argv_emulation=False, 56 | target_arch=None, 57 | codesign_identity=None, 58 | entitlements_file=None, 59 | ) 60 | coll = COLLECT( 61 | exe, 62 | a.binaries, 63 | a.datas, 64 | strip=False, 65 | upx=True, 66 | upx_exclude=[], 67 | name='apio', 68 | ) 69 | -------------------------------------------------------------------------------- /docs/cmd-apio-devices.md: -------------------------------------------------------------------------------- 1 | # Apio devices 2 | 3 | --- 4 | 5 | ## apio devices 6 | 7 | The `apio devices` command group lists devices connected to your computer. 8 | It is mainly used for diagnosing connectivity or driver issues. 9 | 10 |

Options

11 | 12 | ``` 13 | -h, --help Show this message and exit. 14 | ``` 15 | 16 |

Subcommands

17 | 18 | ``` 19 | apio devices usb 20 | apio devices serial 21 | ``` 22 | 23 | --- 24 | 25 | ## apio devices usb 26 | 27 | The command `apio devices usb` displays the USB devices currently 28 | connected to your computer. It is useful for diagnosing FPGA board 29 | connectivity issues. 30 | 31 |

Examples

32 | 33 | ``` 34 | apio devices usb # List USB devices. 35 | ``` 36 | 37 |

Options

38 | 39 | ``` 40 | -h, --help Show this message and exit. 41 | ``` 42 | 43 | Example output 44 | 45 | ![](assets/apio-devices-usb.png) 46 | 47 | --- 48 | 49 | ## apio devices serial 50 | 51 | The command `apio devices serial` displays the serial devices 52 | currently connected to your computer. It is useful for diagnosing FPGA 53 | board connectivity issues. 54 | 55 |

Examples

56 | 57 | ``` 58 | apio devices serial # List serial devices. 59 | ``` 60 | 61 |

Options

62 | 63 | ``` 64 | -h, --help Show this message and exit. 65 | ``` 66 | 67 |

Notes

68 | 69 | - Devices like the FTDI FT2232 with multiple channels may appear as 70 | multiple entries—one per serial port. 71 | 72 | - On Windows, manufacturer and product strings of FTDI based devices may 73 | show their FTDI generic values rather than the custom values such as 'Alhambra II' set by the device manufacturer. 74 | 75 | Example output 76 | 77 | ![](assets/apio-devices-serial.png) 78 | -------------------------------------------------------------------------------- /apio/resources/config.jsonc: -------------------------------------------------------------------------------- 1 | // General config parameters. 2 | // 3 | { 4 | // How many days we keep a cached remote config before we fetch a new onw. 5 | // Value of 1 means fetching once a day. 7 once every seven days, and so on. 6 | "remote-config-ttl-days" : 1, 7 | 8 | // The time period in minutes after a config fetch failure until the next 9 | // try. This applies only for optional remote config fetch where Apio can 10 | // fall back to the cached config, and does not apply to cases where a 11 | // fresh config is required, e.g. by 'apio packages update' or if a cached 12 | // config is not available. 13 | // 14 | // To force an immediate config fetch, user can run 'apio packages update'. 15 | "remote-config-retry-minutes": 60, 16 | 17 | // URL of the apio remote config file. The placeholder {major} and {minor} 18 | // are replaced with Apio's major and minor version number with no zero 19 | // padding. For example, for apio 1.12.3, {major} is "1" and {minor} is "12". 20 | // 21 | // 22 | // The value of the remote config field here can be overridden for testing 23 | // using the env var APIO_REMOTE_CONFIG_URL, including for reading from 24 | // a local file using the "file://" protocol spec. 25 | // 26 | // For testing using the remote config at zapta: 27 | // export APIO_REMOTE_CONFIG_URL="https://github.com/zapta/apio/raw/develop/remote-config/apio-{major}.{minor}.x.jsonc" 28 | // 29 | // For testing using the local remote config 30 | // export APIO_REMOTE_CONFIG_URL="file:///projects/apio-dev/repo/remote-config/apio-{major}.{minor}.x.jsonc" 31 | 32 | "remote-config-url": "https://github.com/fpgawars/apio/raw/develop/remote-config/apio-{major}.{minor}.x.jsonc" 33 | } 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /apio/resources/packages.jsonc: -------------------------------------------------------------------------------- 1 | // Definition of apio software packages. These are packages with various 2 | // data and utilities that are installed by apio. 3 | // 4 | // Additional packages information is fetched dynamically from the remote 5 | // config and is saved in .apio/profile.json. 6 | // 7 | // TODO: Delete "file_name" and "extension". They move to the remote config. 8 | { 9 | // "definitions" package 10 | "definitions": { 11 | "description": "Apio definitions", 12 | "env": {} 13 | }, 14 | // "examples" package 15 | "examples": { 16 | "description": "Apio project examples", 17 | "env": {} 18 | }, 19 | // "oss-cad-suite" package 20 | "oss-cad-suite": { 21 | "description": "Yosys HQ oss-cad-suite", 22 | "env": { 23 | "path": [ 24 | "%p/bin", 25 | "%p/lib" 26 | ], 27 | "vars": { 28 | "VERILATOR_ROOT": "%p/share/verilator", 29 | "ICEBOX": "%p/share/icebox", 30 | "TRELLIS": "%p/share/trellis", 31 | "YOSYS_LIB": "%p/share/yosys" 32 | } 33 | } 34 | }, 35 | // "graphviz" package 36 | "graphviz": { 37 | "restricted-to-platforms": [ 38 | "windows-amd64" 39 | ], 40 | "description": "Graphviz tool for Windows", 41 | "env": { 42 | "path": [ 43 | "%p/bin" 44 | ] 45 | } 46 | }, 47 | // "verible" package 48 | "verible": { 49 | "description": "Chips Aliance Verible toolset", 50 | "env": { 51 | "path": [ 52 | "%p/bin" 53 | ] 54 | } 55 | }, 56 | // "drivers" package 57 | "drivers": { 58 | "restricted-to-platforms": [ 59 | "windows-amd64" 60 | ], 61 | "description": "Drivers tools for Windows", 62 | "env": { 63 | "path": [ 64 | "%p/bin" 65 | ] 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /docs/command-line.md: -------------------------------------------------------------------------------- 1 | # Apio command line 2 | 3 | This page explains the principles and general options of using the Apio command line. For 4 | specific commands, see the respective command description. 5 | 6 | --- 7 | 8 | ## Apio command tree 9 | 10 | Apio commands are organized as a command tree rooted at `apio`. Some commands, such as `apio build`, have only two levels, while others, like `apio drivers install ftdi`, have three or more. To explore available commands at each level, use the `-h` option (short for `--help`) with any command. 11 | 12 | For example 13 | 14 | ``` 15 | apio -h 16 | apio info -h 17 | apio info cli -h 18 | ``` 19 | 20 | --- 21 | 22 | ## Apio command options 23 | 24 | Most Apio commands have options that let you control their behavior. For example, the command 'apio build' has options to control the verbosity of its output: 25 | 26 | ``` 27 | apio build --verbose 28 | apio build --verbose-synth 29 | ``` 30 | 31 | To list the options for a command, run it with the `-h` option. For example: 32 | 33 | ``` 34 | apio build -h 35 | ``` 36 | 37 | --- 38 | 39 | ## Apio command shortcuts 40 | 41 | When typing apio commands, it's sufficient to type enough of each command to make the selection unambiguous. For example, these commands below are equivalent. 42 | 43 | ``` 44 | apio preferences 45 | apio pref 46 | apio pr 47 | ``` 48 | 49 | However, `apio p` is ambiguous because it matches both `apio preferences` and `apio packages`. 50 | 51 | --- 52 | 53 | ## Apio shell auto completion 54 | 55 | Apio's command line processor is based on the Python Click package which supports auto completion with some shells. Although it worked as a proof of concept, this feature is experimental and not guaranteed to function reliably. More information is available in the Click documentation: 56 | -------------------------------------------------------------------------------- /docs/cmd-apio-drivers.md: -------------------------------------------------------------------------------- 1 | # Apio drivers 2 | 3 | --- 4 | 5 | ## apio drivers 6 | 7 | The `apio drivers` command group installs and uninstalls drivers needed by some FPGA boards. 8 | 9 |

Options

10 | 11 | ``` 12 | -h, --help Show this message and exit. 13 | ``` 14 | 15 |

Subcommands

16 | 17 | ``` 18 | apio drivers install ftdi 19 | apio drivers uninstall ftdi 20 | apio drivers install serial 21 | apio drivers uninstall serial 22 | ``` 23 | 24 | --- 25 | 26 | ## apio drivers install ftdi 27 | 28 | The `apio drivers install ftdi` command installs the FTDI drivers required by some FPGA boards. 29 | 30 |

Examples

31 | 32 | ``` 33 | apio drivers install ftdi 34 | ``` 35 | 36 |

Options

37 | 38 | ``` 39 | -h, --help Show this message and exit. 40 | ``` 41 | 42 | --- 43 | 44 | ## apio drivers uninstall ftdi 45 | 46 | The `apio drivers uninstall ftdi` command removes previously installed FTDI drivers. 47 | 48 |

Examples

49 | 50 | ``` 51 | apio drivers uninstall ftdi # Uninstall FTDI drivers. 52 | ``` 53 | 54 |

Options

55 | 56 | ``` 57 | -h, --help Show this message and exit. 58 | ``` 59 | 60 | --- 61 | 62 | ## apio drivers install serial 63 | 64 | The `apio drivers install serial` command installs the serial drivers needed by certain FPGA boards. 65 | 66 |

Examples

67 | 68 | ``` 69 | apio drivers install serial # Install serial drivers. 70 | ``` 71 | 72 |

Options

73 | 74 | ``` 75 | -h, --help Show this message and exit. 76 | ``` 77 | 78 | --- 79 | 80 | ## apio drivers uninstall serial 81 | 82 | The `apio drivers uninstall serial` command removes previously installed serial drivers. 83 | 84 |

Examples

85 | 86 | ``` 87 | apio drivers uninstall serial # Uninstall serial drivers. 88 | ``` 89 | 90 |

Options

91 | 92 | ``` 93 | -h, --help Show this message and exit. 94 | ``` 95 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Python Debugger: Current File", 6 | "type": "debugpy", 7 | "request": "launch", 8 | "program": "${file}", 9 | "console": "integratedTerminal" 10 | }, 11 | { 12 | "name": "Python Debugger: Current File", 13 | "type": "debugpy", 14 | "request": "launch", 15 | "program": "${file}", 16 | "console": "integratedTerminal" 17 | }, 18 | { 19 | "name": "Apio run command", 20 | "type": "debugpy", 21 | "request": "launch", 22 | "program": "${workspaceFolder}/apio/__main__.py", 23 | "args": [ 24 | "sim", 25 | "--detach" 26 | ], 27 | "console": "integratedTerminal", 28 | "justMyCode": false, 29 | "cwd": "/Users/user/work", 30 | "subProcess": false 31 | }, 32 | { 33 | "name": "Apio run test", 34 | "type": "debugpy", 35 | "request": "launch", 36 | "module": "pytest", 37 | "args": [ 38 | "-s", 39 | "-vv", 40 | "test/unit_tests/utils/test_usb_util.py::test_device_summaries" 41 | ], 42 | "console": "integratedTerminal", 43 | "justMyCode": false, 44 | "cwd": "${workspaceFolder}", 45 | "subProcess": false 46 | }, 47 | { 48 | "name": "Apio remote debugger", 49 | "type": "debugpy", 50 | "request": "attach", 51 | "connect": { 52 | "host": "localhost", 53 | "port": 5678 54 | }, 55 | "justMyCode": false, 56 | "subProcess": false 57 | }, 58 | ] 59 | } -------------------------------------------------------------------------------- /docs/custom-boards.md: -------------------------------------------------------------------------------- 1 | # Using Custom Boards 2 | 3 | Apio uses definitions of FPGA devices, FPGA programmers, and 4 | FPGA boards to simplify the setup of new projects. The diagram below 5 | illustrates the relationship between these definitions and Apio's operation, 6 | as well as the names of their respective definition files in the apio 7 | `definitions` packages 8 | 9 |
10 | 11 | ![](assets/custom-boards.svg) 12 | 13 |
14 | 15 | If any of these default definitions do not match the FPGA board you are using, 16 | you can override them by placing custom definition files in the top-level 17 | directory of your project. These replacement files should: 18 | 19 | - Use the same file names and formats as the originals (`fpgas.jsonc`, 20 | `programmers.jsonc`, or `boards.jsonc`). 21 | - Include only the specific FPGA, programmer, or board definitions you need. 22 | 23 | ## Definition Files 24 | 25 | | Definition File | Contains | Keyed By | 26 | | :------------------ | :--------------------- | :-------------- | 27 | | `fpgas.jsonc` | FPGA definitions | `fpga-id` | 28 | | `programmers.jsonc` | Programmer definitions | `programmer-id` | 29 | | `boards.jsonc` | Board definitions | `board-id` | 30 | 31 | > If you believe your custom definition may be useful to others, 32 | > consider submitting a pull request to the [apio-definitions](https://github.com/FPGAwars/apio-definitions/tree/main/definitions) repository. 33 | 34 | ## Example: Custom `boards.jsonc` 35 | 36 | The following is an example of a custom `boards.jsonc` file that defines 37 | a variant of the `upduino31` board: 38 | 39 | ```json 40 | { 41 | "upduino31c": { 42 | "name": "UPduino v3.1c", 43 | "fpga-id": "ice40up5k-sg48", 44 | "programmer": { 45 | "id": "iceprog" 46 | }, 47 | "usb": { 48 | "vid": "0403", 49 | "pid": "6010", 50 | "product-regex": "UPduino v3\\.1c" 51 | } 52 | } 53 | } 54 | ``` 55 | -------------------------------------------------------------------------------- /docs/cmd-apio-packages.md: -------------------------------------------------------------------------------- 1 | # Apio packages 2 | 3 | --- 4 | 5 | ## apio packages 6 | 7 | The command group `apio packages` provides commands to manage the 8 | installation of Apio packages. These are not Python packages, but collections of tools and data required by Apio. 9 | 10 | > The list of available packages depends on your operating system and may vary across platforms. 11 | 12 |

Options

13 | 14 | ``` 15 | -h, --help Show this message and exit. 16 | ``` 17 | 18 |

Subcommands

19 | 20 | ``` 21 | apio packages update 22 | apio packages list 23 | ``` 24 | 25 | --- 26 | 27 | ## apio packages update 28 | 29 | The `apio packages update` command updates installed packages to their latest versions. 30 | 31 |

Examples

32 | 33 | ``` 34 | apio packages update # Update packages 35 | apio pack upd # Same, with shortcuts 36 | apio packages update --force # Force reinstallation from scratch 37 | apio packages update --verbose # Provide additional info 38 | ``` 39 | 40 |

Options

41 | 42 | ``` 43 | -f, --force Force reinstallation. 44 | -v, --verbose Show detailed output. 45 | -h, --help Show this message and exit. 46 | ``` 47 | 48 |

Notes

49 | 50 | - Adding the `--force` option forces the reinstallation of existing 51 | packages; otherwise, packages that are already installed correctly 52 | remain unchanged. 53 | 54 | - It is recommended to run the 'apio packages update' once in a 55 | while because it checks the Apio remote server for updated packages with potential fixes and new examples. 56 | 57 | --- 58 | 59 | ## apio packages list 60 | 61 | The `apio packages list` command displays the available and installed Apio packages. The list may vary depending on your operating system. 62 | 63 |

Examples

64 | 65 | ``` 66 | apio packages list 67 | ``` 68 | 69 |

Options

70 | 71 | ``` 72 | -v, --verbose Show detailed output. 73 | -h, --help Show this message and exit. 74 | ``` 75 | -------------------------------------------------------------------------------- /apio/common/rich_lib_windows.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # -- This file is part of the Apio project 3 | # -- (C) 2016-2018 FPGAwars 4 | # -- Author Jesús Arroyo 5 | # -- License GPLv2 6 | # -- Derived from: 7 | # ---- Platformio project 8 | # ---- (C) 2014-2016 Ivan Kravets 9 | # ---- License Apache v2 10 | """Functions to workaround the rich library bugs when stdout is piped out 11 | on windows.""" 12 | 13 | import sys 14 | import platform 15 | import rich.console 16 | 17 | 18 | def fix_windows_stdout_encoding() -> bool: 19 | """Called on the apio process (parent) to fix its output encoding. 20 | Safe to call on non windows platforms. Returns True if fixed.""" 21 | # -- This takes care of the table graphic box. 22 | # -- See https://github.com/Textualize/rich/issues/3625 23 | if ( 24 | platform.system().lower() == "windows" 25 | and sys.stdout.encoding != "utf-8" 26 | ): 27 | sys.stdout.reconfigure(encoding="utf-8") 28 | return True 29 | # -- Else. 30 | return False 31 | 32 | 33 | def apply_workaround(): 34 | """Called on the scons (child) process side, when running on windows, 35 | to apply the the workaround for the rich library.""" 36 | 37 | # For accessing rich.console._windows_console_features 38 | # pylint: disable=protected-access 39 | 40 | # -- This takes care of the table graphic box. 41 | # -- See https://github.com/Textualize/rich/issues/3625 42 | # sys.stdout.reconfigure(encoding=params.stdout_encoding) 43 | 44 | # This enables the colors. 45 | # See https://github.com/Textualize/rich/issues/3082 46 | 47 | # -- Make sure that _windows_console_features is initialized with cached 48 | # -- values. 49 | rich.console.get_windows_console_features() 50 | assert rich.console._windows_console_features is not None 51 | 52 | # -- Apply the patch. 53 | rich.console._windows_console_features.vt = True 54 | rich.console._windows_console_features.truecolor = True 55 | -------------------------------------------------------------------------------- /docs/installing-apio-ide.md: -------------------------------------------------------------------------------- 1 | # Installing Apio IDE 2 | 3 | To install Apio IDE, follow these steps 4 | 5 | 1. If you don't have already Visual Studio Code install, download and install it from its [official site](https://code.visualstudio.com/download) 6 | 1. Start Visual Studio Code. 7 | 1. In the Extension tab on the left, search and install the extension `fpgawars.apio1` (**Apio FPGA**). 8 | 1. This concludes the installation of the Apio IDE. The screenshot below shows the components of the Apio IDE. 9 | 10 | > HINT: Once you start using Apio IDE, it will automatically install the Apio CLI in the `.apio/bin` directory under your home 11 | > directory. You can add that directory to your `PATH` if you want to use the Apio CLI `apio ...` commands also outside of VS Code. 12 | 13 |
14 | ![](assets/apio-vscode-window.png) 15 | 16 | | Part | Condition | Function | 17 | | ------------------------ | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | 18 | | [1] Apio project buttons | Appears when an Apio project is open. | Provides quick access to common project functions. | 19 | | [2] Apio extension tab | Appears when the Apio extensions is installed. | Opens the Apio side bar with the available commands. | 20 | | [3] Apio side bar | Appears when the apio extension tab is selected. | Provides access to all Apio functions. | 21 | | [4] Apio terminal | Appears when an Apio CLI command is executed or when **TOOLS → misc → apio shell** is selected. | Shows the output of Apio CLI commands and provides the interactive Apio shell. | 22 | -------------------------------------------------------------------------------- /scripts/generate_commands_help.py: -------------------------------------------------------------------------------- 1 | """A python script to generate COMMANDS.md.""" 2 | 3 | import sys 4 | import json 5 | from typing import List 6 | import subprocess 7 | 8 | 9 | def extract_command_list(node: dict, node_path: List[str]) -> list[List[str]]: 10 | """Recursively extract the commands from the json commands tree.""" 11 | 12 | # -- Add the node to the list. 13 | result = [node_path] 14 | 15 | # -- Simple command, no children 16 | if "commands" not in node: 17 | return result 18 | 19 | for command, command_dict in node["commands"].items(): 20 | command_path = node_path + [command] 21 | commands = extract_command_list(command_dict, command_path) 22 | result.extend(commands) 23 | 24 | # result = sorted(result) 25 | return result 26 | 27 | 28 | def get_commands_list() -> List[str]: 29 | """Run 'apio apio get-commands' and extract the commands list.""" 30 | 31 | # -- Get the command hierarchy as a JSON doc. 32 | result = subprocess.run( 33 | ["apio", "api", "get-commands"], 34 | capture_output=True, 35 | text=True, 36 | check=True, 37 | encoding="utf-8", 38 | ) 39 | 40 | data = json.loads(result.stdout) 41 | commands = extract_command_list(data["commands"]["apio"], ["apio"]) 42 | 43 | return commands 44 | 45 | 46 | def main(): 47 | """Get the apio command list and dump their --help text.""" 48 | commands = get_commands_list() 49 | 50 | commands = sorted(commands) 51 | 52 | for command in commands: 53 | command_str = " ".join(command) 54 | print(command_str, file=sys.stderr) 55 | 56 | result = subprocess.run( 57 | command + ["-h"], 58 | capture_output=True, 59 | text=True, 60 | check=True, 61 | encoding="utf-8", 62 | ) 63 | 64 | print("\n--------------------------------------------\n") 65 | print(command_str) 66 | print() 67 | print(result.stdout) 68 | 69 | 70 | if __name__ == "__main__": 71 | main() 72 | -------------------------------------------------------------------------------- /docs/cmd-apio-sim.md: -------------------------------------------------------------------------------- 1 | # Apio sim 2 | 3 | --- 4 | 5 | ## apio sim 6 | 7 | The `apio sim` command runs a simulation for the default or specified 8 | testbench and displays the results in a GTKWave window. Testbench files 9 | should end with `_tb`, such as `main_tb.v` or `main_tb.sv`. You can set 10 | the default testbench using the `default-testbench` option in `apio.ini`. 11 | If this option is not set and there's only one testbench in the project, 12 | that file will be used. 13 | 14 | The `apio sim` command defines the macro `APIO_SIM=1`, which allows failed 15 | assertions to skip the `$fatal` call. This lets the simulation continue and 16 | display faulty signals in the GTKWave viewer. 17 | 18 | ``` 19 | # Instead of this 20 | $fatal; 21 | 22 | # Use this 23 | if (!`APIO_SIM) $fatal; 24 | ``` 25 | 26 |

Examples

27 | 28 | ``` 29 | apio sim # Simulate the default testbench. 30 | apio sim my_module_tb.v # Simulate the specified testbench. 31 | apio sim my_module_tb.sv # Simulate the specified testbench. 32 | apio sim --no-gtkwave # Simulate but skip GTKWave. 33 | apio sim --detach # Launch and forget gtkwave. 34 | ``` 35 | 36 |

Options

37 | 38 | ``` 39 | -f, --force Force simulation 40 | -e, --env name Use a named environment from apio.ini 41 | -n, --no-gtkwave Skip GTKWave 42 | -d, --detach Launch and forget GTKWave. 43 | -p, --project-dir path Specify the project root directory 44 | -h, --help Show help message and exit 45 | ``` 46 | 47 |

Notes

48 | 49 | - Avoid using the Verilog `$dumpfile()` function, as it can override the default name and location Apio assigns for the `.vcd` file. 50 | 51 | - Testbench paths must always be relative to the project directory, even when using `--project-dir`. 52 | 53 | - For a sample testbench that utilizes this macro, see the apio example `alhambra-ii/getting-started`. 54 | 55 | - When configuring signals in GTKWave, save your setup so you don’t have to repeat it each time. 56 | 57 |

Example simulation results

58 | 59 | ![](assets/sim-gtkwave.png) 60 | -------------------------------------------------------------------------------- /apio/commands/apio_docs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # -- This file is part of the Apio project 3 | # -- (C) 2016-2024 FPGAwars 4 | # -- Authors 5 | # -- * Jesús Arroyo (2016-2019) 6 | # -- * Juan Gonzalez (obijuan) (2019-2024) 7 | # -- License GPLv2 8 | """Implementation of 'apio format' command""" 9 | 10 | import sys 11 | import webbrowser 12 | import click 13 | from apio.common.apio_console import cout 14 | from apio.apio_context import ( 15 | ApioContext, 16 | PackagesPolicy, 17 | ProjectPolicy, 18 | RemoteConfigPolicy, 19 | ) 20 | from apio.utils import cmd_util 21 | 22 | 23 | # -------------- apio format 24 | 25 | # -- Text in the rich-text format of the python rich library. 26 | APIO_DOCS_HELP = """ 27 | The command 'apio docs' opens the Apio documentation using the user's \ 28 | default browser. 29 | 30 | Examples:[code] 31 | apio docs # Open the apio docs. 32 | apio docs --commands # Land on the commands list page. 33 | """ 34 | 35 | commands_option = click.option( 36 | "commands", # Var name. 37 | "-c", 38 | "--commands", 39 | is_flag=True, 40 | help="Show the commands page.", 41 | cls=cmd_util.ApioOption, 42 | ) 43 | 44 | 45 | @click.command( 46 | name="docs", 47 | cls=cmd_util.ApioCommand, 48 | short_help="Show Apio documentation.", 49 | help=APIO_DOCS_HELP, 50 | ) 51 | @commands_option 52 | def cli( 53 | # Options 54 | commands, 55 | ): 56 | """Implements the docs command which opens the apio documentation in 57 | the default browser. 58 | """ 59 | 60 | # -- Create an apio context with a project object. 61 | _ = ApioContext( 62 | project_policy=ProjectPolicy.NO_PROJECT, 63 | remote_config_policy=RemoteConfigPolicy.CACHED_OK, 64 | packages_policy=PackagesPolicy.IGNORE_PACKAGES, 65 | ) 66 | 67 | url = "https://fpgawars.github.io/apio/docs" 68 | 69 | if commands: 70 | url += "/commands-list" 71 | 72 | cout(f"URL: {url}") 73 | cout("Opening default browser") 74 | 75 | default_browser = webbrowser.get() 76 | default_browser.open(url) 77 | 78 | sys.exit(0) 79 | -------------------------------------------------------------------------------- /docs/updating-the-docs.md: -------------------------------------------------------------------------------- 1 | # Updating Apio Docs 2 | 3 | Apio documentation is written in Markdown and published using the `mkdocs` 4 | tool at . The rest of this page explains 5 | how to update and preview the documentation. 6 | 7 | > NOTE: The Apio github workflow that publishes the Apio docs, also publishes the 8 | > test coverage report at 9 | 10 | ## Installing MkDocs 11 | 12 | Install MkDocs and the Material theme with: 13 | 14 | ``` 15 | pip install mkdocs-material 16 | ``` 17 | 18 | ## Navigation 19 | 20 | The structure and navigation of the docs are defined in `mkdocs.yml`, 21 | including the site layout and page mappings. 22 | 23 | ## Pages 24 | 25 | Markdown page files (`*.md`) are stored in the `docs` directory. 26 | 27 | ## Graphics 28 | 29 | Pictures, diagrams, and other graphics are stored in the `docs/assets` 30 | directory. 31 | 32 | ## Stylesheets 33 | 34 | Apio's custom styles are defined in `docs/stylesheets/extra.css`, which is 35 | referenced from `mkdocs.yml`. 36 | 37 | ## Previewing Local Changes 38 | 39 | To start a local web server and preview changes as you edit: 40 | 41 | ``` 42 | invoke docs-viewer 43 | ``` 44 | 45 | This enables live reloading in your browser. 46 | 47 | ## Sending a Pull Request 48 | 49 | Before sending a pull request to the Apio repository, check the following on your forked repository: 50 | 51 | 1. The following workflows in the **Actions** tab of your fork repo completed successfully: 52 | 53 | - **publish-mkdocs-docs** 54 | - **pages-build-deployment** 55 | - **monitor-apio-latest** 56 | - **test** 57 | 58 | 2. The docs at `https://${user}.github.io/apio/docs` are live and include 59 | your changes (replace _${user}_ with the username of your fork repo). 60 | 61 | ## Publishing 62 | 63 | Documentation is automatically published when changes are pushed 64 | to `mkdocs.yml` or the `docs` directory. This triggers the GitHub Actions 65 | workflow: 66 | 67 | ``` 68 | .github/workflows/publish-mkdocs-docs.yaml 69 | ``` 70 | 71 | The workflow updates the site on GitHub Pages via the `gh-pages` 72 | branch. You can monitor workflow runs in the repository's **Actions** tab. 73 | -------------------------------------------------------------------------------- /tests/unit_tests/utils/test_cmd_util.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests of cmd_util.py 3 | """ 4 | 5 | import sys 6 | import pytest 7 | import click 8 | from apio.utils.cmd_util import ( 9 | ApioOption, 10 | ApioCmdContext, 11 | check_at_most_one_param, 12 | ) 13 | 14 | # TODO: Add more tests. 15 | 16 | 17 | # -- A fake command for testing. 18 | @click.command(name="fake_cmd") 19 | @click.option("_opt1", "--opt1", is_flag=True, cls=ApioOption) 20 | @click.option("_opt2", "--opt2", is_flag=True, cls=ApioOption) 21 | @click.option("_opt3", "--opt3", is_flag=True, cls=ApioOption) 22 | @click.option("_opt4", "--opt4", is_flag=True, cls=ApioOption) 23 | def fake_cmd(_opt1, _opt2, _opt3, _opt4): 24 | """Fake click command for testing.""" 25 | sys.exit(0) 26 | 27 | 28 | def test_check_at_most_one_param(capsys): 29 | """Tests the check_at_most_one_param() function.""" 30 | 31 | # -- No option is specified. Should be ok. 32 | cmd_ctx = ApioCmdContext(fake_cmd) 33 | fake_cmd.parse_args(cmd_ctx, []) 34 | check_at_most_one_param(cmd_ctx, ["_opt1", "_opt2", "_opt3"]) 35 | 36 | # -- One option is specified. Should be ok. 37 | cmd_ctx = ApioCmdContext(fake_cmd) 38 | fake_cmd.parse_args(cmd_ctx, ["--opt1"]) 39 | check_at_most_one_param(cmd_ctx, ["_opt1", "_opt2", "_opt3"]) 40 | 41 | # -- Another option is specified. Should be ok. 42 | cmd_ctx = ApioCmdContext(fake_cmd) 43 | fake_cmd.parse_args(cmd_ctx, ["--opt2"]) 44 | check_at_most_one_param(cmd_ctx, ["_opt1", "_opt2", "_opt3"]) 45 | 46 | # -- Two options but one is non related to the check. Should be ok. 47 | cmd_ctx = ApioCmdContext(fake_cmd) 48 | fake_cmd.parse_args(cmd_ctx, ["--opt1", "--opt4"]) 49 | check_at_most_one_param(cmd_ctx, ["_opt1", "_opt2", "_opt3"]) 50 | 51 | # -- Two options are specifies. Should fail. 52 | cmd_ctx = ApioCmdContext(fake_cmd) 53 | fake_cmd.parse_args(cmd_ctx, ["--opt1", "--opt2"]) 54 | capsys.readouterr() # Reset capture. 55 | with pytest.raises(SystemExit) as e: 56 | check_at_most_one_param(cmd_ctx, ["_opt1", "_opt2", "_opt3"]) 57 | captured = capsys.readouterr() 58 | assert e.value.code == 1 59 | assert "--opt1 and --opt2 cannot be combined together" in captured.out 60 | -------------------------------------------------------------------------------- /docs/cmd-apio-examples.md: -------------------------------------------------------------------------------- 1 | # Apio examples 2 | 3 | --- 4 | 5 | ## apio examples 6 | 7 | The `apio examples` command group includes subcommands for listing and 8 | fetching example projects provided by Apio. Each example is a 9 | self-contained project that can be built and uploaded to its respective FPGA board. 10 | 11 | > The apio project examples are included in the apio 'examples' package 12 | > which is updated periodically. 13 | 14 |

Examples

15 | 16 | ``` 17 | -h, --help Show this message and exit. 18 | ``` 19 | 20 |

Subcommands

21 | 22 | ``` 23 | apio examples list 24 | apio examples fetch 25 | ``` 26 | 27 | --- 28 | 29 | ## apio examples list 30 | 31 | The `apio examples list` command shows available Apio project examples. 32 | 33 | > The apio project examples are included in the apio 'examples' package 34 | > which is updated periodically. 35 | 36 |

Examples

37 | 38 | ``` 39 | apio examples list # List all examples 40 | apio examples list -v # Verbose output 41 | apio examples list | grep alhambra-ii # Filter for alhambra-ii examples 42 | apio examples list | grep -i blink # Filter for blinking examples 43 | apio examples list --docs # Use Apio docs format. 44 | ``` 45 | 46 |

Options

47 | 48 | ``` 49 | -v, --verbose Show detailed output. 50 | -d, --docs Format for Apio Docs. 51 | -h, --help Show this message and exit. 52 | ``` 53 | 54 | --- 55 | 56 | ## apio examples fetch 57 | 58 | The `apio examples fetch` command retrieves a single example or all examples 59 | for a specific board. The default destination directory is the current directory and it can be overriden using the `--dst` flag. If the 60 | destination directory already exists, it must be empty. 61 | 62 | > The apio project examples are included in the apio 'examples' package 63 | > which is updated periodically. 64 | 65 |

Examples

66 | 67 | ``` 68 | apio examples fetch alhambra-ii/ledon # Single example 69 | apio examples fetch alhambra-ii # All examples for the board 70 | apio examples fetch alhambra-ii -d work # Explicit destination 71 | ``` 72 | 73 |

Options

74 | 75 | ``` 76 | -d, --dst path Set a different destination directory. 77 | -h, --help Show this message and exit. 78 | ``` 79 | -------------------------------------------------------------------------------- /tests/unit_tests/commands/test_apio_raw.py: -------------------------------------------------------------------------------- 1 | """Test for the "apio raw" command.""" 2 | 3 | from tests.conftest import ApioRunner 4 | from apio.commands.apio import apio_top_cli as apio 5 | 6 | 7 | def test_raw(apio_runner: ApioRunner): 8 | """Test "apio raw" with different parameters""" 9 | 10 | with apio_runner.in_sandbox() as sb: 11 | 12 | # -- NOTE: We run the apio raw command in a sub process to have a 13 | # -- proper sys.argv for it for the '--' separator validation. 14 | 15 | # -- Execute "apio raw" 16 | result = sb.invoke_apio_cmd(apio, ["raw"], in_subprocess=True) 17 | assert result.exit_code != 0, result.output 18 | assert ( 19 | "at least one of --verbose or COMMAND must be specified" 20 | in result.output 21 | ) 22 | 23 | # -- Execute "apio raw -v" 24 | result = sb.invoke_apio_cmd(apio, ["raw", "-v"], in_subprocess=True) 25 | sb.assert_result_ok(result) 26 | assert "Environment settings:" in result.output 27 | assert "PATH" in result.output 28 | assert "YOSYS_LIB" in result.output 29 | 30 | # -- Run 'apio raw "nextpnr-ice40 --help"'. 31 | result = sb.invoke_apio_cmd( 32 | apio, ["raw", "--", "nextpnr-ice40", "--help"], in_subprocess=True 33 | ) 34 | sb.assert_result_ok(result, bad_words=[]) 35 | 36 | # -- Run a command without the required '--' 37 | result = sb.invoke_apio_cmd( 38 | apio, ["raw", "nextpnr-ice40"], in_subprocess=True 39 | ) 40 | assert result.exit_code != 0, result.output 41 | assert "command separator '--' was not found" in result.output 42 | 43 | # -- Run a command with a token before the '--' separator. 44 | result = sb.invoke_apio_cmd( 45 | apio, ["raw", "nextpnr-ice40", "--", "--help"], in_subprocess=True 46 | ) 47 | assert result.exit_code != 0, result.output 48 | assert "Invalid arguments: ['nextpnr-ice40']" in result.output 49 | 50 | # -- Run 'apio raw -v' 51 | result = sb.invoke_apio_cmd(apio, ["raw", "-v"], in_subprocess=True) 52 | sb.assert_result_ok(result) 53 | assert "Environment settings:" in result.output 54 | assert "YOSYS_LIB" in result.output 55 | -------------------------------------------------------------------------------- /remote-config/apio-0.9.7.jsonc: -------------------------------------------------------------------------------- 1 | // Remote config file for Apio 0.9.7 2 | // 3 | // Supported vars (see installer.py for details): 4 | // 5 | // ${PLATFORM} - platform tag (from platforms.jsonc) 6 | // ${YYYY-MM-DD} - version as YYYY-MM-DD 7 | // ${YYYYMMDD} - version as YYYYMMDD 8 | // 9 | // NOTE: Github has a cache propagation of about 1 min between the 10 | // time this file is submitted and until it's available for download. 11 | { 12 | "packages": { 13 | // -- Examples 14 | "examples": { 15 | "repository": { 16 | "organization": "fpgawars", 17 | "name": "apio-examples" 18 | }, 19 | "release": { 20 | "version": "2025.07.11", 21 | "release-tag": "${YYYY-MM-DD}", 22 | "package-file": "apio-examples-${YYYYMMDD}.tgz" 23 | } 24 | }, 25 | // -- OSS cad suite 26 | "oss-cad-suite": { 27 | "repository": { 28 | "organization": "fpgawars", 29 | "name": "tools-oss-cad-suite" 30 | }, 31 | "release": { 32 | "version": "2025.07.07", 33 | "release-tag": "${YYYY-MM-DD}", 34 | "package-file": "apio-oss-cad-suite-${PLATFORM}-${YYYYMMDD}.tgz" 35 | } 36 | }, 37 | // -- Graphviz 38 | "graphviz": { 39 | "repository": { 40 | "organization": "fpgawars", 41 | "name": "tools-graphviz" 42 | }, 43 | "release": { 44 | "version": "2025.06.13", 45 | "release-tag": "${YYYY-MM-DD}", 46 | "package-file": "apio-graphviz-${PLATFORM}-${YYYYMMDD}.tgz" 47 | } 48 | }, 49 | // -- Verible 50 | "verible": { 51 | "repository": { 52 | "organization": "fpgawars", 53 | "name": "tools-verible" 54 | }, 55 | "release": { 56 | "version": "2025.06.13", 57 | "release-tag": "${YYYY-MM-DD}", 58 | "package-file": "apio-verible-${PLATFORM}-${YYYYMMDD}.tgz" 59 | } 60 | }, 61 | // -- Drivers 62 | "drivers": { 63 | "repository": { 64 | "organization": "fpgawars", 65 | "name": "tools-drivers" 66 | }, 67 | "release": { 68 | "version": "2025.06.13", 69 | "release-tag": "${YYYY-MM-DD}", 70 | "package-file": "apio-drivers-${PLATFORM}-${YYYYMMDD}.tgz" 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /remote-config/apio-1.0.0.jsonc: -------------------------------------------------------------------------------- 1 | // Remote config file for Apio 1.0.0 2 | // 3 | // Supported vars (see installer.py for details): 4 | // 5 | // ${PLATFORM} - platform tag (from platforms.jsonc) 6 | // ${YYYY-MM-DD} - version as YYYY-MM-DD 7 | // ${YYYYMMDD} - version as YYYYMMDD 8 | // 9 | // NOTE: Github has a cache propagation of about 1 min between the 10 | // time this file is submitted and until it's available for download. 11 | { 12 | "packages": { 13 | // -- Examples 14 | "examples": { 15 | "repository": { 16 | "organization": "fpgawars", 17 | "name": "apio-examples" 18 | }, 19 | "release": { 20 | "version": "2025.09.23", 21 | "release-tag": "${YYYY-MM-DD}", 22 | "package-file": "apio-examples-${YYYYMMDD}.tgz" 23 | } 24 | }, 25 | // -- OSS cad suite 26 | "oss-cad-suite": { 27 | "repository": { 28 | "organization": "fpgawars", 29 | "name": "tools-oss-cad-suite" 30 | }, 31 | "release": { 32 | "version": "2025.09.24", 33 | "release-tag": "${YYYY-MM-DD}", 34 | "package-file": "apio-oss-cad-suite-${PLATFORM}-${YYYYMMDD}.tgz" 35 | } 36 | }, 37 | // -- Graphviz 38 | "graphviz": { 39 | "repository": { 40 | "organization": "fpgawars", 41 | "name": "tools-graphviz" 42 | }, 43 | "release": { 44 | "version": "2025.06.13", 45 | "release-tag": "${YYYY-MM-DD}", 46 | "package-file": "apio-graphviz-${PLATFORM}-${YYYYMMDD}.tgz" 47 | } 48 | }, 49 | // -- Verible 50 | "verible": { 51 | "repository": { 52 | "organization": "fpgawars", 53 | "name": "tools-verible" 54 | }, 55 | "release": { 56 | "version": "2025.06.13", 57 | "release-tag": "${YYYY-MM-DD}", 58 | "package-file": "apio-verible-${PLATFORM}-${YYYYMMDD}.tgz" 59 | } 60 | }, 61 | // -- Drivers 62 | "drivers": { 63 | "repository": { 64 | "organization": "fpgawars", 65 | "name": "tools-drivers" 66 | }, 67 | "release": { 68 | "version": "2025.06.13", 69 | "release-tag": "${YYYY-MM-DD}", 70 | "package-file": "apio-drivers-${PLATFORM}-${YYYYMMDD}.tgz" 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # Apio's project file. 2 | # Package version is set in apio/__init.py 3 | 4 | [build-system] 5 | requires = ["flit_core >=2,<4"] 6 | build-backend = "flit_core.buildapi" 7 | 8 | [tool.flit.metadata.urls] 9 | "Homepage" = "https://github.com/fpgawars/apio" 10 | "Documentation" = "https://fpgawars.github.io/apio/docs" 11 | "Bugs Tracker" = "https://github.com/fpgawars/apio/issues" 12 | "Wiki" = "https://github.com/fpgawars/apio/wiki" 13 | "Discussions" = "https://github.com/fpgawars/apio/discussions" 14 | 15 | [tool.flit.metadata] 16 | module = "apio" 17 | author = "Jesus Arroyo" 18 | author-email = "jesus.jkhlg@gmail.com " 19 | home-page = "https://github.com/fpgawars/apio" 20 | description-file = "README.md" 21 | classifiers=[ 22 | 'Development Status :: 5 - Production/Stable', 23 | 'Environment :: Console', 24 | 'Intended Audience :: Developers', 25 | 'License :: OSI Approved :: GNU General Public License v2 (GPLv2)', 26 | 'Programming Language :: Python', 27 | 'Natural Language :: English', 28 | 'Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)', 29 | ] 30 | requires-python = ">=3.11" 31 | requires = [ 32 | 'click==8.2.1', 33 | 'colorama==0.4.6', 34 | 'configobj==5.0.9', 35 | 'debugpy==1.8.19', 36 | 'packaging==25.0', 37 | 'pyserial==3.5', 38 | 'requests==2.32.4', 39 | 'scons==4.8.1', 40 | 'semantic_version==2.10.0', 41 | 'wheel==0.45.1', 42 | 'blackiceprog==2.0.0', 43 | 'tinyfpgab==1.1.0', 44 | 'tinyprog==1.0.21', 45 | 'icefunprog==2.0.3', 46 | 'apycula==0.15', 47 | 'apollo_fpga==1.1.1', 48 | 'protobuf==6.33.0', 49 | 'rich==14.0.0', 50 | 'invoke==2.2.1' 51 | ] 52 | 53 | [tool.flit.sdist] 54 | # TODO: Revisit 'exclude`. What else we don't need in the package? 55 | exclude = ["tests/"] 56 | 57 | [tool.flit.metadata.requires-extra] 58 | blackiceprog = ['blackiceprog==2.0.0'] 59 | litterbox = ['litterbox==0.2.2'] 60 | tinyfpgab = ['tinyfpgab==1.1.0'] 61 | icefunprog = ['icefunprog==2.0.3'] 62 | 63 | [tool.flit.scripts] 64 | apio = "apio.__main__:main" 65 | 66 | [tool.black] 67 | line-length = 79 68 | target-version = ['py311'] 69 | 70 | # NOTE: For pylint control see .pylintrc in this directory. 71 | # It allows controls that pyproject.toml doesn't support. 72 | -------------------------------------------------------------------------------- /apio/commands/apio_report.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # -- This file is part of the Apio project 3 | # -- (C) 2016-2024 FPGAwars 4 | # -- Authors 5 | # -- * Jesús Arroyo (2016-2019) 6 | # -- * Juan Gonzalez (obijuan) (2019-2024) 7 | # -- License GPLv2 8 | """Implementation of 'apio' report' command""" 9 | 10 | import sys 11 | from typing import Optional 12 | from pathlib import Path 13 | import click 14 | from apio.managers.scons_manager import SConsManager 15 | from apio.commands import options 16 | from apio.apio_context import ( 17 | ApioContext, 18 | PackagesPolicy, 19 | ProjectPolicy, 20 | RemoteConfigPolicy, 21 | ) 22 | from apio.common.proto.apio_pb2 import Verbosity 23 | from apio.utils import cmd_util 24 | 25 | 26 | # ---------- apio report 27 | 28 | # -- Text in the rich-text format of the python rich library. 29 | APIO_REPORT_HELP = """ 30 | The command 'apio report' provides information on the utilization and timing \ 31 | of the design. It is useful for analyzing utilization bottlenecks and \ 32 | verifying that the design can operate at the desired clock speed. 33 | 34 | Examples:[code] 35 | apio report # Print report. 36 | apio report --verbose # Print extra information.[/code] 37 | """ 38 | 39 | 40 | @click.command( 41 | name="report", 42 | cls=cmd_util.ApioCommand, 43 | short_help="Report design utilization and timing.", 44 | help=APIO_REPORT_HELP, 45 | ) 46 | @click.pass_context 47 | @options.env_option_gen() 48 | @options.project_dir_option 49 | @options.verbose_option 50 | def cli( 51 | _: click.Context, 52 | # Options 53 | env: Optional[str], 54 | project_dir: Optional[Path], 55 | verbose: bool, 56 | ): 57 | """Analyze the design and report timing.""" 58 | 59 | # -- Create the apio context. 60 | apio_ctx = ApioContext( 61 | project_policy=ProjectPolicy.PROJECT_REQUIRED, 62 | remote_config_policy=RemoteConfigPolicy.CACHED_OK, 63 | packages_policy=PackagesPolicy.ENSURE_PACKAGES, 64 | project_dir_arg=project_dir, 65 | env_arg=env, 66 | ) 67 | 68 | # -- Create the scons manager. 69 | scons = SConsManager(apio_ctx) 70 | 71 | # -- Create the verbosity params. 72 | verbosity = Verbosity(pnr=verbose) 73 | 74 | # Run scons 75 | exit_code = scons.report(verbosity) 76 | 77 | # -- Done! 78 | sys.exit(exit_code) 79 | -------------------------------------------------------------------------------- /remote-config/apio-1.0.2.jsonc: -------------------------------------------------------------------------------- 1 | // Remote config file for Apio 1.0.2 2 | // 3 | // Supported vars (see installer.py for details): 4 | // 5 | // ${PLATFORM} - platform id (from platforms.jsonc) 6 | // ${YYYYMMDD} - the YYYY-MM_DD tag converted to YYYYMMDD 7 | // 8 | // NOTE: Github has a cache propagation of about 1 min between the 9 | // time this file is submitted and until it's available for download. 10 | { 11 | "packages": { 12 | // -- Definitions package 13 | "definitions": { 14 | "repository": { 15 | "organization": "fpgawars", 16 | "name": "apio-definitions" 17 | }, 18 | "release": { 19 | "tag": "2025-10-20", 20 | "package": "apio-definitions-${YYYYMMDD}.tgz" 21 | } 22 | }, 23 | // -- Examples package 24 | "examples": { 25 | "repository": { 26 | "organization": "fpgawars", 27 | "name": "apio-examples" 28 | }, 29 | "release": { 30 | "tag": "2025-12-01", 31 | "package": "apio-examples-${YYYYMMDD}.tgz" 32 | } 33 | }, 34 | // -- OSS Cad Suite package (Yosys) 35 | "oss-cad-suite": { 36 | "repository": { 37 | "organization": "fpgawars", 38 | "name": "tools-oss-cad-suite" 39 | }, 40 | "release": { 41 | "tag": "2025-09-24", 42 | "package": "apio-oss-cad-suite-${PLATFORM}-${YYYYMMDD}.tgz" 43 | } 44 | }, 45 | // -- Graphviz package 46 | "graphviz": { 47 | "repository": { 48 | "organization": "fpgawars", 49 | "name": "tools-graphviz" 50 | }, 51 | "release": { 52 | "tag": "2025-06-13", 53 | "package": "apio-graphviz-${PLATFORM}-${YYYYMMDD}.tgz" 54 | } 55 | }, 56 | // -- Verible package 57 | "verible": { 58 | "repository": { 59 | "organization": "fpgawars", 60 | "name": "tools-verible" 61 | }, 62 | "release": { 63 | "tag": "2025-06-13", 64 | "package": "apio-verible-${PLATFORM}-${YYYYMMDD}.tgz" 65 | } 66 | }, 67 | // -- Drivers package 68 | "drivers": { 69 | "repository": { 70 | "organization": "fpgawars", 71 | "name": "tools-drivers" 72 | }, 73 | "release": { 74 | "tag": "2025-06-13", 75 | "package": "apio-drivers-${PLATFORM}-${YYYYMMDD}.tgz" 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /remote-config/apio-1.1.x.jsonc: -------------------------------------------------------------------------------- 1 | // Remote config file for Apio versions 1.1.x. 2 | // 3 | // Supported vars (see installer.py for details): 4 | // 5 | // ${PLATFORM} - platform id (from platforms.jsonc) 6 | // ${YYYYMMDD} - the YYYY-MM_DD tag converted to YYYYMMDD 7 | // 8 | // NOTE: Github has a cache propagation of about 1 min between the 9 | // time this file is submitted and until it's available for download. 10 | { 11 | "packages": { 12 | // -- Definitions package 13 | "definitions": { 14 | "repository": { 15 | "organization": "fpgawars", 16 | "name": "apio-definitions" 17 | }, 18 | "release": { 19 | "tag": "2025-10-20", 20 | "package": "apio-definitions-${YYYYMMDD}.tgz" 21 | } 22 | }, 23 | // -- Examples package 24 | "examples": { 25 | "repository": { 26 | "organization": "fpgawars", 27 | "name": "apio-examples" 28 | }, 29 | "release": { 30 | "tag": "2025-12-23", 31 | "package": "apio-examples-${YYYYMMDD}.tgz" 32 | } 33 | }, 34 | // -- OSS Cad Suite package (Yosys) 35 | "oss-cad-suite": { 36 | "repository": { 37 | "organization": "fpgawars", 38 | "name": "tools-oss-cad-suite" 39 | }, 40 | "release": { 41 | "tag": "2025-09-24", 42 | "package": "apio-oss-cad-suite-${PLATFORM}-${YYYYMMDD}.tgz" 43 | } 44 | }, 45 | // -- Graphviz package 46 | "graphviz": { 47 | "repository": { 48 | "organization": "fpgawars", 49 | "name": "tools-graphviz" 50 | }, 51 | "release": { 52 | "tag": "2025-06-13", 53 | "package": "apio-graphviz-${PLATFORM}-${YYYYMMDD}.tgz" 54 | } 55 | }, 56 | // -- Verible package 57 | "verible": { 58 | "repository": { 59 | "organization": "fpgawars", 60 | "name": "tools-verible" 61 | }, 62 | "release": { 63 | "tag": "2025-06-13", 64 | "package": "apio-verible-${PLATFORM}-${YYYYMMDD}.tgz" 65 | } 66 | }, 67 | // -- Drivers package 68 | "drivers": { 69 | "repository": { 70 | "organization": "fpgawars", 71 | "name": "tools-drivers" 72 | }, 73 | "release": { 74 | "tag": "2025-06-13", 75 | "package": "apio-drivers-${PLATFORM}-${YYYYMMDD}.tgz" 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /docs/using-examples.md: -------------------------------------------------------------------------------- 1 | # Using Apio's Examples 2 | 3 | Apio comes with a set of sample projects that demonstrate its features and can be used as starting points for your own projects. To list the available examples, type: 4 | 5 | ``` 6 | $ apio examples list 7 | ``` 8 | 9 | ``` 10 | ┌────────────────────────────────┬───────┬─────────────────────────────────────────────────────────────┐ 11 | │ BOARD/EXAMPLE │ ARCH │ DESCRIPTION │ 12 | ├────────────────────────────────┼───────┼─────────────────────────────────────────────────────────────┤ 13 | │ alchitry-cu/blinky │ ice40 │ Blinking all LEDs │ 14 | │ alhambra-ii/bcd-counter │ ice40 │ Verilog example with testbenches and subdirectories. │ 15 | │ alhambra-ii/bcd-counter-sv │ ice40 │ SystemVerilog example with testbenches and subdirectories. │ 16 | │ alhambra-ii/blinky │ ice40 │ Blinking LED │ 17 | │ alhambra-ii/getting-started │ ice40 │ Example for Apio Getting Started docs. │ 18 | │ alhambra-ii/ledon │ ice40 │ Turning on an LED │ 19 | │ sipeed-tang-nano-9k/blinky-sv │ gowin │ Blinking LED (SystemVerilog) │ 20 | │ sipeed-tang-nano-9k/pll │ gowin │ PLL clock multiplier │ 21 | └────────────────────────────────┴───────┴─────────────────────────────────────────────────────────────┘ 22 | ``` 23 | 24 | To fetch an example we create a new empty directory and fetch the example files int it. 25 | 26 | ``` 27 | # Create an empty project directory 28 | $ apio mkdir work 29 | $ cd work 30 | 31 | # Fetch the example 32 | $ apio example fetch alhambra-ii/getting-started 33 | ``` 34 | 35 | Now lets look at the project file structure 36 | 37 | ``` 38 | $ tree . 39 | . 40 | ├── apio.ini 41 | ├── main_tb.gtkw 42 | ├── main_tb.v 43 | ├── main.v 44 | └── pinout.pcf 45 | ``` 46 | 47 | And the project file `apio.ini`. 48 | ``` 49 | $ cat -n apio.ini 50 | 1 ; Apio project file. 51 | 2 52 | 3 [env:default] 53 | 4 54 | 5 ; Board ID 55 | 6 board = alhambra-ii 56 | 7 57 | 8 ; Top module name (in main.v) 58 | 9 top-module = Main 59 | ``` 60 | 61 | The fetched example is now an Apio project that can be built and uploaded to a matching FPGA board. 62 | -------------------------------------------------------------------------------- /.github/workflows/monitor-apio-prod.yaml: -------------------------------------------------------------------------------- 1 | # A periodic workflow to test that the latest dev version is functional. 2 | # It's not a through test by any means, more like a sanity test. 3 | 4 | name: monitor-apio-prod 5 | 6 | on: 7 | # Run on commit. 8 | # push: 9 | 10 | # Run every 6 hours 11 | schedule: 12 | - cron: '0 */6 * * *' 13 | 14 | # Allow manual launch 15 | workflow_dispatch: 16 | 17 | jobs: 18 | monitor: 19 | runs-on: ${{ matrix.os }} 20 | 21 | defaults: 22 | run: 23 | shell: bash 24 | 25 | strategy: 26 | matrix: 27 | # 'macos-latest' -> darwin apple silicon 28 | # 'macos-15-intel' -> darwin intel x86 29 | os: [ubuntu-22.04, macos-latest, macos-15-intel, windows-latest] 30 | python-version: ['3.11', '3.12', '3.13', "3.14"] 31 | 32 | steps: 33 | - name: Show architecture 34 | run: uname -a 35 | 36 | - name: Set up Python 37 | uses: actions/setup-python@v4 38 | with: 39 | python-version: ${{matrix.python-version}} 40 | 41 | - name: Show python version 42 | run: | 43 | python --version 44 | 45 | - name: Install apio from pypi 46 | run: | 47 | pip install apio 48 | 49 | - name: Load apio packages (slow) 50 | run: | 51 | apio install --all 52 | 53 | - name: Show apio info 54 | run: | 55 | apio system --info 56 | 57 | - name: Test ICE40 project 58 | run: | 59 | # Show commands 60 | set -x 61 | 62 | # Create a project 63 | mkdir ice40_project 64 | cd ice40_project 65 | apio examples --files Alhambra-II/Blinky 66 | ls -al 67 | 68 | # Run a few command 69 | # apio lint 70 | apio verify 71 | apio build 72 | apio time 73 | apio system --lsftdi 74 | apio system --lsusb 75 | apio system --lsserial 76 | 77 | find . 78 | 79 | # Check that a few files exists (fails if not) 80 | ls -al hardware.bin 81 | ls -al hardware.rpt 82 | ls -al hardware.out 83 | -------------------------------------------------------------------------------- /scripts/genereate-repo-cleaner.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Skeleton script to for one time cleanup of repo's releases and tag. 4 | # It generates a bash script with commented out commands to delete each 5 | # release and each tag. Uncomment the ones you want to delete and run 6 | # the generated shell script. 7 | 8 | set -euo pipefail 9 | 10 | # ------------------------------------------------------------ 11 | # Usage: ./repo-cleaner-gen.sh 12 | # Example: ./repo-cleaner-gen.sh zapta/apio-vscode 13 | # ------------------------------------------------------------ 14 | 15 | if [[ $# -ne 1 ]]; then 16 | echo "Error: Please provide exactly one argument: owner/repo" 17 | echo "Example: $0 zapta/apio-vscode" 18 | exit 1 19 | fi 20 | 21 | REPO="$1" 22 | 23 | cat </dev/null || echo " → Release already gone or not accessible" 33 | } 34 | 35 | delete_tag() { 36 | local tag="\$1" 37 | echo "Deleting tag: \$tag" 38 | gh api --method DELETE "/repos/\$REPO/git/refs/tags/\$tag" --silent 2>/dev/null || echo " → Tag already gone or not accessible" 39 | } 40 | 41 | delete_release_and_tag() { 42 | local tag="\$1" 43 | delete_release "\$tag" 44 | delete_tag "\$tag" 45 | } 46 | 47 | echo "DRY RUN — No deletions will happen until you uncomment the lines below" 48 | echo "Repo: $REPO" 49 | echo "========================================================================" 50 | echo 51 | echo "# 1. Releases + their tags (release first, then tag)" 52 | echo 53 | 54 | EOF 55 | 56 | # Step 1: For every release → delete release + tag (in that order) 57 | gh release list --repo "$REPO" --limit 1000 --json tagName -q '.[].tagName' | 58 | sort -V | 59 | while read -r tag; do 60 | [[ -n "$tag" ]] || continue 61 | echo "# delete_release_and_tag \"$tag\"" 62 | done 63 | 64 | # Step 2: All tags that are NOT attached to any release 65 | cat <<'EOF' 66 | 67 | echo 68 | echo "# 2. Orphaned / unused tags (no associated release)" 69 | echo 70 | 71 | EOF 72 | 73 | # Get all tags without a release 74 | comm -23 \ 75 | <(gh api "repos/$REPO/tags?per_page=100" --paginate --jq '.[].name' | sort -u) \ 76 | <(gh release list --repo "$REPO" --limit 1000 --json tagName -q '.[].tagName' | sort -u) | 77 | while read -r tag; do 78 | [[ -n "$tag" ]] && echo "# delete_tag \"$tag\"" 79 | done 80 | 81 | cat <<'EOF' 82 | 83 | echo 84 | EOF -------------------------------------------------------------------------------- /remote-config/apio-1.0.1.jsonc: -------------------------------------------------------------------------------- 1 | // Remote config file for Apio 1.0.1 2 | // 3 | // Supported vars (see installer.py for details): 4 | // 5 | // ${PLATFORM} - platform tag (from platforms.jsonc) 6 | // ${YYYY-MM-DD} - version as YYYY-MM-DD 7 | // ${YYYYMMDD} - version as YYYYMMDD 8 | // 9 | // NOTE: Github has a cache propagation of about 1 min between the 10 | // time this file is submitted and until it's available for download. 11 | { 12 | "packages": { 13 | // -- Definitions 14 | "definitions": { 15 | "repository": { 16 | "organization": "fpgawars", 17 | "name": "apio-definitions" 18 | }, 19 | "release": { 20 | "version": "2025.10.20", 21 | "release-tag": "${YYYY-MM-DD}", 22 | "package-file": "apio-definitions-${YYYYMMDD}.tgz" 23 | } 24 | }, 25 | // -- Examples 26 | "examples": { 27 | "repository": { 28 | "organization": "fpgawars", 29 | "name": "apio-examples" 30 | }, 31 | "release": { 32 | "version": "2025.12.01", 33 | "release-tag": "${YYYY-MM-DD}", 34 | "package-file": "apio-examples-${YYYYMMDD}.tgz" 35 | } 36 | }, 37 | // -- OSS cad suite 38 | "oss-cad-suite": { 39 | "repository": { 40 | "organization": "fpgawars", 41 | "name": "tools-oss-cad-suite" 42 | }, 43 | "release": { 44 | "version": "2025.09.24", 45 | "release-tag": "${YYYY-MM-DD}", 46 | "package-file": "apio-oss-cad-suite-${PLATFORM}-${YYYYMMDD}.tgz" 47 | } 48 | }, 49 | // -- Graphviz 50 | "graphviz": { 51 | "repository": { 52 | "organization": "fpgawars", 53 | "name": "tools-graphviz" 54 | }, 55 | "release": { 56 | "version": "2025.06.13", 57 | "release-tag": "${YYYY-MM-DD}", 58 | "package-file": "apio-graphviz-${PLATFORM}-${YYYYMMDD}.tgz" 59 | } 60 | }, 61 | // -- Verible 62 | "verible": { 63 | "repository": { 64 | "organization": "fpgawars", 65 | "name": "tools-verible" 66 | }, 67 | "release": { 68 | "version": "2025.06.13", 69 | "release-tag": "${YYYY-MM-DD}", 70 | "package-file": "apio-verible-${PLATFORM}-${YYYYMMDD}.tgz" 71 | } 72 | }, 73 | // -- Drivers 74 | "drivers": { 75 | "repository": { 76 | "organization": "fpgawars", 77 | "name": "tools-drivers" 78 | }, 79 | "release": { 80 | "version": "2025.06.13", 81 | "release-tag": "${YYYY-MM-DD}", 82 | "package-file": "apio-drivers-${PLATFORM}-${YYYYMMDD}.tgz" 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /docs/project-structure.md: -------------------------------------------------------------------------------- 1 | # Apio Project Structure 2 | 3 | ## Directory structure 4 | 5 | An Apio project consists of a directory that contains the required project file `apio.ini` and the project files. Below is an example of a minimal ICE40 project (`alhambra-ii/blinky` example) which contains the project file `apio.ini`, a Verilog source file `blinky.v`, and the pinout constraints file `pinout.pcf`, which maps symbolic pin names to pin numbers. 6 | 7 | ``` 8 | my-project/ 9 | ├── apio.ini 10 | ├── blinky.v 11 | └── pinout.pcf 12 | ``` 13 | 14 | The next example (alhambra-ii/bcd-output) is more complex, with Verilog `*.v` source files, their `_tb.*` testbenches, and their `*.gtkw` GTKWave state files organized in a directory tree. 15 | 16 | ``` 17 | my-project/ 18 | ├── apio.ini 19 | ├── bcd 20 | │   ├── bcd_digit_tb.gtkw 21 | │   ├── bcd_digit_tb.v 22 | │   └── bcd_digit.v 23 | ├── main_tb.gtkw 24 | ├── main_tb.v 25 | ├── main.v 26 | ├── pinout.pcf 27 | ├── testing 28 | │   └── apio_testing.vh 29 | └── util 30 | ├── reset_gen.v 31 | ├── ticker_tb.gtkw 32 | ├── ticker_tb.v 33 | └── ticker.v 34 | ``` 35 | 36 | **Directory structure rules** 37 | 38 | - The project file `apio.ini` and the pinout constraints file should reside in the top-level directory. 39 | - Source files and testbenches can reside in the root directory or in any subdirectory. 40 | - Testbenches' GTKWave state files (`.gtkw`) should reside in the same directory as their respective testbenches. 41 | 42 | ## Custom definitions 43 | 44 | The apio definitions files `boards.jsonc`, `fpga.jsonc`, and `programmers.jsonc` can be overridden 45 | by placing files with same names at the project's top directory. This allows debugging 46 | and testing custom boards that are not included yet in the Apio's `definitions` package. 47 | 48 | ## Output files 49 | 50 | Apio commands write their output to the directory `_build/` under the project root directory, where `` is the active environment name from `apio.ini`. For example, when building for an environment called `default`, the output directory is `_build/default`. The example below shows the results of the `apio build` command, including the ICE40 bitstream file `hardware.bin` and intermediate files created during the build. 51 | 52 | ``` 53 | my-project/ 54 | ├── _build 55 | │   └── default 56 | │   ├── hardware.asc 57 | │   ├── hardware.bin 58 | │   ├── hardware.json 59 | │   ├── hardware.pnr 60 | │   └── scons.params 61 | ├── apio.ini 62 | ├── blinky.v 63 | └── pinout.pcf 64 | ``` 65 | 66 | > The command `apio clean` can be used to delete all Apio-generated files and force a build from scratch. 67 | 68 | ## Using Git 69 | 70 | When working with Git, we recommend including the following in your `.gitignore` file to avoid committing build artifacts and system files: 71 | 72 | **.gitignore** 73 | 74 | ``` 75 | _build 76 | .DS_Store 77 | ``` 78 | -------------------------------------------------------------------------------- /tests/unit_tests/commands/test_apio_preferences.py: -------------------------------------------------------------------------------- 1 | """Test for the "apio preferences" command.""" 2 | 3 | import re 4 | from tests.conftest import ApioRunner 5 | from apio.common.apio_console import cunstyle 6 | from apio.commands.apio import apio_top_cli as apio 7 | 8 | 9 | def test_colors_on_off(apio_runner: ApioRunner): 10 | """Test "apio preferences" with different parameters""" 11 | 12 | with apio_runner.in_sandbox() as sb: 13 | 14 | # -- Execute "apio preferences" 15 | result = sb.invoke_apio_cmd(apio, ["preferences"]) 16 | sb.assert_result_ok(result) 17 | assert "'apio preferences' allows" in cunstyle(result.output) 18 | assert "-t, --theme [light|dark|no-colors]" in result.output 19 | assert result.output != cunstyle(result.output) # Colored. 20 | 21 | # -- Execute "apio preferences --theme dark" 22 | result = sb.invoke_apio_cmd(apio, ["preferences", "--theme", "dark"]) 23 | sb.assert_result_ok(result) 24 | assert "Theme set to [dark]" in result.output 25 | assert result.output != cunstyle(result.output) # Colored. 26 | 27 | # -- Execute "apio preferences --list". It should reports the dark 28 | # -- theme. 29 | result = sb.invoke_apio_cmd(apio, ["preferences", "--list"]) 30 | sb.assert_result_ok(result) 31 | assert result.output != cunstyle(result.output) # Colored. 32 | assert "dark" in result.output 33 | 34 | # -- Execute "apio system info". It should emit colors. 35 | result = sb.invoke_apio_cmd(apio, ["info", "system"]) 36 | sb.assert_result_ok(result) 37 | assert result.output != cunstyle(result.output) # Colored. 38 | 39 | # -- Execute "apio preferences --theme no-colors" 40 | result = sb.invoke_apio_cmd( 41 | apio, ["preferences", "--theme", "no-colors"] 42 | ) 43 | sb.assert_result_ok(result) 44 | assert "Theme set to [no-colors]" in result.output 45 | 46 | # -- Execute "apio preferences --list". It should reports the 47 | # -- no-colors theme. 48 | result = sb.invoke_apio_cmd(apio, ["preferences", "--list"]) 49 | sb.assert_result_ok(result) 50 | assert re.search(r"Theme.*no-colors", result.output), result.output 51 | assert result.output == cunstyle(result.output) # Non colored.. 52 | 53 | # -- Execute "apio preferences --colors". 54 | result = sb.invoke_apio_cmd(apio, ["preferences", "--colors"]) 55 | # -- It's normal to have 'error' in the output test. 56 | sb.assert_result_ok(result, bad_words=[]) 57 | assert result.output != cunstyle(result.output) # Colored.. 58 | # -- Ideally we would like to have this assertion enabled but 59 | # -- when running on github workflows we sometimes get different 60 | # -- colors. 61 | # assert "\x1b[38;5;237mbar.back " in result.output 62 | -------------------------------------------------------------------------------- /docs/migrating-from-apio-0.9.5.md: -------------------------------------------------------------------------------- 1 | # Migrating from Apio 0.9.5 2 | 3 | Apio 1.x.x introduces many improvements compared to Apio 0.9.5. Many of the changes were done in a backward compatible way, but some do require user attention. On this page, we outline the main changes from a compatibility point of view to help users migrate their projects successfully to Apio 1.x.x. 4 | 5 | ## Uninstall Apio 0.9.5 6 | 7 | It is recommended to first delete Apio 0.9.5 before installing Apio 1.x.x. The steps to do so are: 8 | 9 | 1. Delete the Apio Python package `pip uninstall apio` 10 | 11 | 2. Delete the directory `.apio` under the user home directory. That directory contains packages and other transient files used by Apio. 12 | 13 | ## Create project file `apio.ini` 14 | 15 | Apio 1.x.x requires a project file called `apio.ini` in the directory of each Apio project. Make sure your project has a text file called `apio.ini` with the content below, replace __ with the id of your board (e.g. `alhambra-ii`) and replace __ with the name of the top Verilog module of your project (e.g. `Blinky`). 16 | 17 | ``` 18 | [env:default] 19 | board = 20 | main-module = 21 | ``` 22 | 23 | ## Delete calls to the verilog function `$dumpfile()`. 24 | 25 | Remove from your testbenches all calls to the Verilog function `$dumpfile()`. The location of the generated simulation files is now automatically controlled by Apio. 26 | 27 | ## Know the new commands 28 | 29 | The hierarchy and names of some Apio commands were changed in Apio 1.x.x, and the table below will help you migrate from the old to the new commands. You can also use the `-h` option for detailed information on any command level, for example `apio -h`, `apio devices -h`, and `apio devices usb -h`. 30 | 31 | | Apio 0.9.5 | Apio 1.x.x | Comments | 32 | | :--------------------------- | :-------------------------- | :--------------------------- | 33 | | `apio boards --fpga` | `apio fpgas` | List supported FPGAs | 34 | | `apio boards --list` | `apio boards` | List supported boards | 35 | | `apio drivers --ftdi-enable` | `apio drivers install ftdi` | Install FTDI driver | 36 | | `apio examples --files` | `apio examples fetch` | Fetch an example | 37 | | `apio examples --list` | `apio examples list` | List examples | 38 | | `apio init` | `apio create` | Create an apio.ini file | 39 | | `apio install --all` | `apio packages update` | Install Apio packages | 40 | | `apio install --list` | `apio packages list` | List installed apio packages | 41 | | `apio system --lsftdi` | `apio devices usb` | List FTDI and USB devices | 42 | | `apio system --lsserial` | `apio devices serial` | List serial ports | 43 | | `apio time` | `apio report` | Report design timing. | 44 | | `apio verify` | `apio lint` | Verify the source code. | 45 | -------------------------------------------------------------------------------- /tests/unit_tests/commands/test_apio_create.py: -------------------------------------------------------------------------------- 1 | """Test for the "apio create" command.""" 2 | 3 | from pathlib import Path 4 | from os.path import isfile, exists 5 | from typing import Dict 6 | from configobj import ConfigObj 7 | from tests.conftest import ApioRunner 8 | from apio.commands.apio import apio_top_cli as apio 9 | 10 | 11 | def _check_ini_file(apio_ini: Path, expected_vars: Dict[str, str]) -> None: 12 | """Assert that apio.ini contains exactly the given vars.""" 13 | # Read the ini file. 14 | assert isfile(apio_ini) 15 | conf = ConfigObj(str(apio_ini)) 16 | # Check the expected comment at the top. 17 | assert "# APIO project configuration file" in conf.initial_comment[0] 18 | # Check the expected vars. 19 | assert conf.dict() == {"env:default": expected_vars} 20 | 21 | 22 | def test_create(apio_runner: ApioRunner): 23 | """Test "apio create" with different parameters""" 24 | 25 | with apio_runner.in_sandbox() as sb: 26 | 27 | apio_ini = Path("apio.ini") 28 | assert not exists(apio_ini) 29 | 30 | # -- Execute "apio create" 31 | result = sb.invoke_apio_cmd(apio, ["create"]) 32 | assert result.exit_code != 0, result.output 33 | assert "Error: Missing option" in result.output 34 | assert not exists(apio_ini) 35 | 36 | # -- Execute "apio create --board no-such-board" 37 | result = sb.invoke_apio_cmd( 38 | apio, ["create", "--board", "no-such-board"] 39 | ) 40 | assert result.exit_code == 1, result.output 41 | assert "Error: Unknown board id 'no-such-board'" in result.output 42 | assert not exists(apio_ini) 43 | 44 | # -- Execute "apio create --board alhambra-ii" 45 | result = sb.invoke_apio_cmd(apio, ["create", "--board", "alhambra-ii"]) 46 | sb.assert_result_ok(result) 47 | assert "was created successfully." in result.output 48 | _check_ini_file( 49 | apio_ini, {"board": "alhambra-ii", "top-module": "main"} 50 | ) 51 | 52 | # -- Execute "apio create --board alhambra-ii 53 | # -- --top-module my_module" with 'y' input" 54 | result = sb.invoke_apio_cmd( 55 | apio, 56 | [ 57 | "create", 58 | "--board", 59 | "alhambra-ii", 60 | "--top-module", 61 | "my_module", 62 | ], 63 | ) 64 | assert result.exit_code != 0 65 | assert "Error: The file apio.ini already exists." in result.output 66 | _check_ini_file( 67 | apio_ini, {"board": "alhambra-ii", "top-module": "main"} 68 | ) 69 | 70 | # -- Execute "apio create --board alhambra-ii -p aa/bb" 71 | result = sb.invoke_apio_cmd( 72 | apio, ["create", "--board", "alhambra-ii", "-p", "aa/bb"] 73 | ) 74 | sb.assert_result_ok(result) 75 | assert "was created successfully." in result.output 76 | _check_ini_file( 77 | Path("aa/bb") / apio_ini, 78 | {"board": "alhambra-ii", "top-module": "main"}, 79 | ) 80 | -------------------------------------------------------------------------------- /apio/commands/apio_build.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # -- This file is part of the Apio project 3 | # -- (C) 2016-2024 FPGAwars 4 | # -- Authors 5 | # -- * Jesús Arroyo (2016-2019) 6 | # -- * Juan Gonzalez (obijuan) (2019-2024) 7 | # -- License GPLv2 8 | """Implementation of 'apio build' command""" 9 | 10 | import sys 11 | from typing import Optional 12 | from pathlib import Path 13 | import click 14 | from apio.utils import cmd_util 15 | from apio.managers.scons_manager import SConsManager 16 | from apio.commands import options 17 | from apio.common.proto.apio_pb2 import Verbosity 18 | from apio.apio_context import ( 19 | ApioContext, 20 | PackagesPolicy, 21 | ProjectPolicy, 22 | RemoteConfigPolicy, 23 | ) 24 | 25 | # ------------ apio build 26 | 27 | # -- Text in the rich-text format of the python rich library. 28 | APIO_BUILD_HELP = """ 29 | The command 'apio build' processes the project’s synthesis source files and \ 30 | generates a bitstream file, which can then be uploaded to your FPGA. 31 | 32 | Examples:[code] 33 | apio build # Typical usage 34 | apio build -e debug # Set the apio.ini env. 35 | apio build -v # Verbose info (all) 36 | apio build --verbose-synth # Verbose synthesis info 37 | apio build --verbose-pnr # Verbose place and route info[/code] 38 | 39 | NOTES: 40 | * The files are sorted in a deterministic lexicographic order. 41 | * You can specify the name of the top module in apio.ini. 42 | * The build command ignores testbench files (*_tb.v, and *_tb.sv). 43 | * It is unnecessary to run 'apio build' before 'apio upload'. 44 | * To force a rebuild from scratch use the command 'apio clean' first. 45 | """ 46 | 47 | 48 | @click.command( 49 | name="build", 50 | cls=cmd_util.ApioCommand, 51 | short_help="Synthesize the bitstream.", 52 | help=APIO_BUILD_HELP, 53 | ) 54 | @click.pass_context 55 | @options.env_option_gen() 56 | @options.project_dir_option 57 | @options.verbose_option 58 | @options.verbose_synth_option 59 | @options.verbose_pnr_option 60 | def cli( 61 | _: click.Context, 62 | # Options 63 | env: Optional[str], 64 | project_dir: Optional[Path], 65 | verbose: bool, 66 | verbose_synth: bool, 67 | verbose_pnr: bool, 68 | ): 69 | """Implements the apio build command. It invokes the toolchain 70 | to synthesize the source files into a bitstream file. 71 | """ 72 | 73 | # -- Create the apio context. 74 | apio_ctx = ApioContext( 75 | project_policy=ProjectPolicy.PROJECT_REQUIRED, 76 | remote_config_policy=RemoteConfigPolicy.CACHED_OK, 77 | packages_policy=PackagesPolicy.ENSURE_PACKAGES, 78 | project_dir_arg=project_dir, 79 | env_arg=env, 80 | ) 81 | 82 | # -- Create the scons manager. 83 | scons = SConsManager(apio_ctx) 84 | 85 | # -- Build the project with the given parameters 86 | exit_code = scons.build( 87 | Verbosity(all=verbose, synth=verbose_synth, pnr=verbose_pnr) 88 | ) 89 | 90 | # -- Done! 91 | sys.exit(exit_code) 92 | 93 | 94 | # Advanced notes: https://github.com/FPGAwars/apio/wiki/Commands#apio-build 95 | -------------------------------------------------------------------------------- /tests/unit_tests/utils/test_usb_util.py: -------------------------------------------------------------------------------- 1 | """Tests of usb_util.py""" 2 | 3 | from typing import List 4 | from apio.utils.usb_util import ( 5 | UsbDevice, 6 | UsbDeviceFilter, 7 | ) 8 | 9 | 10 | def test_device_summaries(): 11 | """Test usb device summary() string.""" 12 | device = UsbDevice("0403", "6010", 0, 1, "m0", "p0", "sn0", "t0") 13 | assert device.summary() == "[0403:6010] [0:1] [m0] [p0] [sn0]" 14 | 15 | 16 | def test_filtering(): 17 | """Test the filtering function.""" 18 | devs: List[UsbDevice] = [ 19 | UsbDevice("0403", "6010", 0, 1, "m0", "p0", "sn0", "t0"), # devs[0] 20 | UsbDevice("0403", "6020", 3, 1, "m1", "p1", "sn1", "t1"), # devs[1] 21 | UsbDevice("0405", "6020", 3, 1, "m2", "p2", "sn2", "t2"), # devs[2] 22 | UsbDevice("0403", "6020", 2, 1, "m3", "p3", "sn3", "t3"), # devs[3] 23 | UsbDevice("0403", "6010", 1, 1, "m4", "p4", "sn4", "t4"), # devs[4] 24 | UsbDevice("0405", "6010", 1, 1, "m5", "p5", "sn5", "t5"), # devs[5] 25 | ] 26 | 27 | # -- All filtering disabled. 28 | filt = UsbDeviceFilter() 29 | assert filt.summary() == "[all]" 30 | assert filt.filter(devs) == devs 31 | 32 | # -- Filter by VID 33 | filt = UsbDeviceFilter().set_vendor_id("9999") 34 | assert filt.summary() == "[VID=9999]" 35 | assert filt.filter(devs) == [] 36 | 37 | filt = UsbDeviceFilter().set_vendor_id("0405") 38 | assert filt.summary() == "[VID=0405]" 39 | assert filt.filter(devs) == [devs[2], devs[5]] 40 | 41 | # -- Filter by PID 42 | filt = UsbDeviceFilter().set_product_id("9999") 43 | assert filt.summary() == "[PID=9999]" 44 | assert filt.filter(devs) == [] 45 | 46 | filt = UsbDeviceFilter().set_product_id("6020") 47 | assert filt.summary() == "[PID=6020]" 48 | assert filt.filter(devs) == [devs[1], devs[2], devs[3]] 49 | 50 | # -- Filter by description regex 51 | filt = UsbDeviceFilter().set_product_regex("no-such-device") 52 | assert filt.summary() == '[REGEX="no-such-device"]' 53 | assert filt.filter(devs) == [] 54 | 55 | filt = UsbDeviceFilter().set_product_regex("^p2$") 56 | assert filt.summary() == '[REGEX="^p2$"]' 57 | assert filt.filter(devs) == [devs[2]] 58 | 59 | filt = UsbDeviceFilter().set_product_regex("p2") 60 | assert filt.summary() == '[REGEX="p2"]' 61 | assert filt.filter(devs) == [devs[2]] 62 | 63 | filt = UsbDeviceFilter().set_product_regex("(p3)|(p2)") 64 | assert filt.summary() == '[REGEX="(p3)|(p2)"]' 65 | assert filt.filter(devs) == [devs[2], devs[3]] 66 | 67 | # -- Filter by serial number 68 | filt = UsbDeviceFilter().set_serial_num("no-such-device") 69 | assert filt.summary() == '[S/N="no-such-device"]' 70 | assert filt.filter(devs) == [] 71 | 72 | filt = UsbDeviceFilter().set_serial_num("sn2") 73 | assert filt.summary() == '[S/N="sn2"]' 74 | assert filt.filter(devs) == [devs[2]] 75 | 76 | # -- Filter by VID, PID 77 | filt = UsbDeviceFilter().set_vendor_id("0403").set_product_id("6010") 78 | assert filt.summary() == "[VID=0403, PID=6010]" 79 | assert filt.filter(devs) == [devs[0], devs[4]] 80 | -------------------------------------------------------------------------------- /apio/commands/apio_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # -- This file is part of the Apio project 3 | # -- (C) 2016-2024 FPGAwars 4 | # -- Authors 5 | # -- * Jesús Arroyo (2016-2019) 6 | # -- * Juan Gonzalez (obijuan) (2019-2024) 7 | # -- License GPLv2 8 | """Implementation of 'apio test' command""" 9 | 10 | import sys 11 | from typing import Optional 12 | from pathlib import Path 13 | import click 14 | from apio.managers.scons_manager import SConsManager 15 | from apio.commands import options 16 | from apio.apio_context import ( 17 | ApioContext, 18 | PackagesPolicy, 19 | ProjectPolicy, 20 | RemoteConfigPolicy, 21 | ) 22 | from apio.common.proto.apio_pb2 import ApioTestParams 23 | from apio.utils import cmd_util 24 | 25 | 26 | # --------- apio test 27 | 28 | 29 | # -- Text in the rich-text format of the python rich library. 30 | APIO_TEST_HELP = """ 31 | The command 'apio test' simulates one or all the testbenches in the project \ 32 | and is useful for automated testing of your design. Testbenches are expected \ 33 | to have names ending with _tb (e.g., my_module_tb.v) and should exit with the \ 34 | '$fatal' directive if an error is detected. 35 | 36 | Examples:[code] 37 | apio test # Run all *_tb.v testbenches. 38 | apio test my_module_tb.v # Run a single testbench.[/code] 39 | 40 | [NOTE] Testbench specification is always the testbench file path relative to \ 41 | the project directory, even if using the '--project-dir' option. 42 | 43 | [IMPORTANT] Avoid using the Verilog '$dumpfile()' function in your \ 44 | testbenches, as this may override the default name and location Apio sets \ 45 | for the generated .vcd file. 46 | 47 | For a sample testbench compatible with Apio features, see: \ 48 | https://github.com/FPGAwars/apio-examples/tree/master/upduino31/testbench 49 | 50 | [b][Hint][/b] To simulate a testbench with a graphical visualization \ 51 | of the signals, refer to the 'apio sim' command. 52 | """ 53 | 54 | 55 | @click.command( 56 | name="test", 57 | cls=cmd_util.ApioCommand, 58 | short_help="Test all or a single verilog testbench module.", 59 | help=APIO_TEST_HELP, 60 | ) 61 | @click.pass_context 62 | @click.argument("testbench_file", nargs=1, required=False) 63 | @options.env_option_gen() 64 | @options.project_dir_option 65 | # @options.testbench 66 | def cli( 67 | _: click.Context, 68 | # Arguments 69 | testbench_file: str, 70 | # Options 71 | env: Optional[str], 72 | project_dir: Optional[Path], 73 | ): 74 | """Implements the test command.""" 75 | 76 | # -- Create the apio context. 77 | apio_ctx = ApioContext( 78 | project_policy=ProjectPolicy.PROJECT_REQUIRED, 79 | remote_config_policy=RemoteConfigPolicy.CACHED_OK, 80 | packages_policy=PackagesPolicy.ENSURE_PACKAGES, 81 | project_dir_arg=project_dir, 82 | env_arg=env, 83 | ) 84 | 85 | # -- Create the scons manager. 86 | scons = SConsManager(apio_ctx) 87 | 88 | # -- Construct the test params 89 | test_params = ApioTestParams( 90 | testbench=testbench_file if testbench_file else None 91 | ) 92 | 93 | exit_code = scons.test(test_params) 94 | sys.exit(exit_code) 95 | -------------------------------------------------------------------------------- /apio/commands/apio_create.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # -- This file is part of the Apio project 3 | # -- (C) 2016-2024 FPGAwars 4 | # -- Authors 5 | # -- * Jesús Arroyo (2016-2019) 6 | # -- * Juan Gonzalez (obijuan) (2019-2024) 7 | # -- License GPLv2 8 | """Implementation of 'apio create' command""" 9 | 10 | import sys 11 | from typing import Optional 12 | from pathlib import Path 13 | import click 14 | from apio.common.apio_console import cerror 15 | from apio.utils import util, cmd_util 16 | from apio.commands import options 17 | from apio.apio_context import ( 18 | ApioContext, 19 | PackagesPolicy, 20 | ProjectPolicy, 21 | RemoteConfigPolicy, 22 | ) 23 | from apio.managers.project import ( 24 | DEFAULT_TOP_MODULE, 25 | create_project_file, 26 | ) 27 | 28 | 29 | board_option = click.option( 30 | "board", # Var name. 31 | "-b", 32 | "--board", 33 | type=str, 34 | required=True, 35 | metavar="BOARD", 36 | help="Set the board.", 37 | cls=cmd_util.ApioOption, 38 | ) 39 | 40 | # -------------- apio create 41 | 42 | # -- Text in the rich-text format of the python rich library. 43 | APIO_CREATE_HELP = """ 44 | The command 'apio create' creates a new 'apio.ini' project file and is \ 45 | typically used when setting up a new Apio project. 46 | 47 | Examples:[code] 48 | apio create --board alhambra-ii 49 | apio create --board alhambra-ii --top-module MyModule[/code] 50 | 51 | [b][NOTE][/b] This command only creates a new 'apio.ini' file, rather than a \ 52 | complete and buildable project. To create complete projects, refer to the \ 53 | 'apio examples' command. 54 | """ 55 | 56 | 57 | @click.command( 58 | name="create", 59 | cls=cmd_util.ApioCommand, 60 | short_help="Create an apio.ini project file.", 61 | help=APIO_CREATE_HELP, 62 | ) 63 | @click.pass_context 64 | @board_option 65 | @options.top_module_option_gen(short_help="Set the top level module name.") 66 | @options.project_dir_option 67 | def cli( 68 | _: click.Context, 69 | # Options 70 | board: str, 71 | top_module: str, 72 | project_dir: Optional[Path], 73 | ): 74 | """Create a project file.""" 75 | 76 | # Board is annotated above as required so must exist. 77 | assert board is not None 78 | 79 | if not top_module: 80 | top_module = DEFAULT_TOP_MODULE 81 | 82 | # -- Create the apio context. 83 | apio_ctx = ApioContext( 84 | project_policy=ProjectPolicy.NO_PROJECT, 85 | remote_config_policy=RemoteConfigPolicy.CACHED_OK, 86 | packages_policy=PackagesPolicy.ENSURE_PACKAGES, 87 | ) 88 | 89 | # -- Make sure the board exist. 90 | if board not in apio_ctx.boards: 91 | cerror(f"Unknown board id '{board}'.") 92 | sys.exit(1) 93 | 94 | # -- Determine the new project directory. Create if needed. 95 | project_dir: Path = util.user_directory_or_cwd( 96 | project_dir, description="Project", create_if_missing=True 97 | ) 98 | 99 | # Create the apio.ini file. It exists with an error status if any error. 100 | create_project_file( 101 | project_dir, 102 | board, 103 | top_module, 104 | ) 105 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | # The project name. 2 | site_name: Apio 1.x.x Documentation 3 | 4 | # The temporary directory with the generated site. 5 | site_dir: _site 6 | 7 | plugins: 8 | - search 9 | 10 | extra_css: 11 | - stylesheets/extra.css 12 | 13 | theme: 14 | name: material 15 | 16 | # This controls the behavior of the navigation. 17 | features: 18 | - navigation.sections # Enables second-level grouping 19 | - navigation.expand # Expands all sections and subsections by default 20 | - navigation.tabs 21 | - content.code.copy # 'copy' button in code blocks 22 | 23 | markdown_extensions: 24 | - admonition 25 | - pymdownx.extra 26 | - pymdownx.tabbed: 27 | alternate_style: true 28 | - pymdownx.superfences 29 | - toc: 30 | permalink: true 31 | 32 | # Navigation tree 33 | nav: 34 | - Home: index.md 35 | - Getting Started: 36 | - Quick start: quick-start.md 37 | - System requirements: system-requirements.md 38 | - Installing Apio IDE: installing-apio-ide.md 39 | - Installing Apio CLI: installing-apio-cli.md 40 | - Video tutorial: video-tutorial.md 41 | - Migrating from Apio 0.9.5: migrating-from-apio-0.9.5.md 42 | - Apio Commands: 43 | - Commands list: commands-list.md 44 | - api: cmd-apio-api.md 45 | - boards: cmd-apio-boards.md 46 | - build: cmd-apio-build.md 47 | - clean: cmd-apio-clean.md 48 | - create: cmd-apio-create.md 49 | - devices: cmd-apio-devices.md 50 | - docs: cmd-apio-docs.md 51 | - drivers: cmd-apio-drivers.md 52 | - examples: cmd-apio-examples.md 53 | - format: cmd-apio-format.md 54 | - fpgas: cmd-apio-fpgas.md 55 | - graph: cmd-apio-graph.md 56 | - info: cmd-apio-info.md 57 | - lint: cmd-apio-lint.md 58 | - packages: cmd-apio-packages.md 59 | - preferences: cmd-apio-preferences.md 60 | - raw: cmd-apio-raw.md 61 | - report: cmd-apio-report.md 62 | - sim: cmd-apio-sim.md 63 | - test: cmd-apio-test.md 64 | - upgrade: cmd-apio-upgrade.md 65 | - upload: cmd-apio-upload.md 66 | - Apio Projects: 67 | - Project structure: project-structure.md 68 | - Project file apio.ini: project-file.md 69 | - FPGA Boards: 70 | - Supported boards: supported-boards.md 71 | - Supported FPGAs: supported-fpgas.md 72 | - Custom boards: custom-boards.md 73 | - Contributing definitions: contributing-definitions.md 74 | - Board drivers: board-drivers.md 75 | - Apio Examples: 76 | - Apio examples: apio-examples.md 77 | - Using examples: using-examples.md 78 | - Contributing examples: contributing-examples.md 79 | - Apio Tips: 80 | - Apio command line: command-line.md 81 | - Using System Verilog: using-system-verilog.md 82 | - Using testbenches: using-testbenches.md 83 | - Raw tools: raw-tools.md 84 | - Apio Development: 85 | - Apio repositories: apio-repositories.md 86 | - Development environment: development-environment.md 87 | - Apio's terminology: terminology.md 88 | - Command execution: command-execution.md 89 | - Updating the docs: updating-the-docs.md 90 | - Creating an Apio version: creating-apio-version.md 91 | - Creating a package version: creating-package-version.md 92 | - Help: help.md 93 | -------------------------------------------------------------------------------- /tests/unit_tests/commands/test_apio_info.py: -------------------------------------------------------------------------------- 1 | """Test for the "apio system" command.""" 2 | 3 | import re 4 | from tests.conftest import ApioRunner 5 | from apio.commands.apio import apio_top_cli as apio 6 | from apio.common.apio_console import cunstyle, cwidth 7 | 8 | 9 | def test_apio_info(apio_runner: ApioRunner): 10 | """Test "apio info" with different parameters""" 11 | 12 | with apio_runner.in_sandbox() as sb: 13 | # -- For debugging table column truncation in github testing. 14 | print(f"Apio console width = {cwidth()}") 15 | 16 | # -- Execute "apio info" 17 | result = sb.invoke_apio_cmd(apio, ["info"]) 18 | sb.assert_result_ok(result) 19 | assert "platforms" in result.output 20 | 21 | # -- Execute "apio info system" 22 | result = sb.invoke_apio_cmd(apio, ["info", "system"]) 23 | sb.assert_result_ok(result) 24 | assert "Platform id" in result.output 25 | # -- The these env options are set by the apio text fixture. We 26 | # -- relax the expression to allow additional env vars that are 27 | # -- injected to the test such as APIO_REMOTE_URL_CONFIG 28 | assert re.search( 29 | r"Active env options \[[^]]*APIO_HOME[^]]*\]", result.output 30 | ) 31 | 32 | # -- Execute "apio info platforms" 33 | result = sb.invoke_apio_cmd(apio, ["info", "platforms"]) 34 | sb.assert_result_ok(result) 35 | assert "darwin-arm64" in result.output 36 | assert "Mac OSX" in result.output 37 | assert "ARM 64 bit (Apple Silicon)" in result.output 38 | 39 | # -- Execute "apio info commands" 40 | result = sb.invoke_apio_cmd(apio, ["info", "commands"]) 41 | sb.assert_result_ok(result) 42 | assert " build " in cunstyle(result.output) 43 | assert "Synthesize the bitstream." in result.output 44 | assert "[build](cmd-apio-build.md)" not in result.output 45 | 46 | # -- Execute "apio info commands --docs" 47 | result = sb.invoke_apio_cmd(apio, ["info", "commands", "--docs"]) 48 | sb.assert_result_ok(result) 49 | assert "[build](cmd-apio-build.md)" in result.output 50 | assert "Synthesize the bitstream." in result.output 51 | assert " build " not in cunstyle(result.output) 52 | 53 | # -- Execute "apio info colors" 54 | result = sb.invoke_apio_cmd(apio, ["info", "colors"]) 55 | sb.assert_result_ok(result) 56 | assert result.output != cunstyle(result.output) # Colored 57 | assert "ANSI Colors" in result.output 58 | assert "\x1b[31m 1 red \x1b[0m" in result.output 59 | 60 | # -- Execute "apio info themes" 61 | result = sb.invoke_apio_cmd(apio, ["info", "themes"]) 62 | # -- It's normal to have 'error' in the output text. 63 | sb.assert_result_ok(result, bad_words=[]) 64 | assert result.output != cunstyle(result.output) # Colored 65 | assert "NO-COLORS" in result.output 66 | assert "apio.cmd_name\x1b[0m" in result.output 67 | 68 | # -- Execute "apio info system". It should not emit colors. 69 | result = sb.invoke_apio_cmd(apio, ["info", "system"]) 70 | sb.assert_result_ok(result) 71 | assert result.output != cunstyle(result.output) # Colored 72 | -------------------------------------------------------------------------------- /tests/unit_tests/commands/test_apio_examples.py: -------------------------------------------------------------------------------- 1 | """Test for the "apio examples" command.""" 2 | 3 | import os 4 | from os.path import getsize 5 | from tests.conftest import ApioRunner 6 | from apio.commands.apio import apio_top_cli as apio 7 | from apio.common.apio_console import cunstyle 8 | 9 | 10 | def test_examples(apio_runner: ApioRunner): 11 | """Tests the listing and fetching apio examples.""" 12 | 13 | with apio_runner.in_sandbox() as sb: 14 | 15 | # -- Execute "apio examples" 16 | result = sb.invoke_apio_cmd(apio, ["examples"]) 17 | sb.assert_result_ok(result) 18 | assert "Subcommands:" in cunstyle(result.output) 19 | assert "examples list" in cunstyle(result.output) 20 | 21 | # -- 'apio examples list' 22 | result = sb.invoke_apio_cmd(apio, ["examples", "list"]) 23 | sb.assert_result_ok(result) 24 | assert "alhambra-ii/ledon" in result.output 25 | assert "Turning on a led" in result.output 26 | 27 | # -- 'apio examples list --docs' 28 | result = sb.invoke_apio_cmd(apio, ["examples", "list", "--docs"]) 29 | sb.assert_result_ok(result) 30 | assert "alhambra-ii/ledon" in result.output 31 | assert "Turning on a led" in result.output 32 | 33 | # -- 'apio examples fetch alhambra-ii/ledon' (colors off) 34 | result = sb.invoke_apio_cmd( 35 | apio, 36 | ["examples", "fetch", "alhambra-ii/ledon"], 37 | terminal_mode=False, 38 | ) 39 | sb.assert_result_ok(result) 40 | assert "Copying alhambra-ii/ledon example files" in result.output 41 | assert ( 42 | "Example 'alhambra-ii/ledon' fetched successfully" in result.output 43 | ) 44 | assert getsize("apio.ini") 45 | assert getsize("ledon.v") 46 | 47 | # -- 'apio examples fetch alhambra-ii' (colors off) 48 | os.makedirs("temp", exist_ok=False) 49 | os.chdir("temp") 50 | result = sb.invoke_apio_cmd( 51 | apio, 52 | ["examples", "fetch", "alhambra-ii"], 53 | terminal_mode=False, 54 | ) 55 | sb.assert_result_ok(result) 56 | assert "Fetching alhambra-ii/blinky" in result.output 57 | assert "Fetching alhambra-ii/ledon" in result.output 58 | assert "Examples fetched successfully" in result.output 59 | assert getsize("ledon/ledon.v") 60 | os.chdir("..") 61 | 62 | # -- 'apio examples fetch alhambra-ii/ledon -d dir1' (colors off) 63 | result = sb.invoke_apio_cmd( 64 | apio, 65 | ["examples", "fetch", "alhambra-ii/ledon", "-d", "dir1"], 66 | terminal_mode=False, 67 | ) 68 | sb.assert_result_ok(result) 69 | assert "Copying alhambra-ii/ledon example files" in result.output 70 | assert ( 71 | "Example 'alhambra-ii/ledon' fetched successfully" in result.output 72 | ) 73 | assert getsize("dir1/ledon.v") 74 | 75 | # -- 'apio examples fetch alhambra -d dir2 (colors off) 76 | result = sb.invoke_apio_cmd( 77 | apio, 78 | ["examples", "fetch", "alhambra-ii", "-d", "dir2"], 79 | terminal_mode=False, 80 | ) 81 | sb.assert_result_ok(result) 82 | assert "Examples fetched successfully" in result.output 83 | assert getsize("dir2/ledon/ledon.v") 84 | -------------------------------------------------------------------------------- /tests/unit_tests/commands/test_apio_fpgas.py: -------------------------------------------------------------------------------- 1 | """Test for the "apio boards" command.""" 2 | 3 | from tests.conftest import ApioRunner 4 | from apio.commands.apio import apio_top_cli as apio 5 | 6 | CUSTOM_FPGAS = """ 7 | { 8 | "custom-ice40hx4k-bg121": { 9 | "part-num": "CUSTOM-ICE40HX4K-BG121", 10 | "arch": "ice40", 11 | "size": "4k", 12 | "type": "hx4k", 13 | "pack": "bg121" 14 | }, 15 | "ice40hx4k-tq144-8k": { 16 | "part-num": "MODIFIED-ICE40HX4K-TQ144", 17 | "arch": "ice40", 18 | "size": "8k", 19 | "type": "hx8k", 20 | "pack": "tq144:4k" 21 | } 22 | } 23 | """ 24 | 25 | 26 | def test_fpgas_ok(apio_runner: ApioRunner): 27 | """Test "apio fpgas" command with standard fpgas.jsonc.""" 28 | 29 | with apio_runner.in_sandbox() as sb: 30 | 31 | # -- Execute "apio fpgas" 32 | result = sb.invoke_apio_cmd(apio, ["fpgas"]) 33 | sb.assert_result_ok(result) 34 | # -- Note: pytest sees the piped version of the command's output. 35 | # -- Run 'apio fpgas' | cat' to reproduce it. 36 | assert "Loading custom 'fpgas.jsonc'" not in result.output 37 | assert "ice40hx4k-tq144-8k" in result.output 38 | assert "my_custom_fpga" not in result.output 39 | assert "─────┐" in result.output # Graphic table border 40 | assert ":---" not in result.output # Graphic table border 41 | 42 | # -- Execute "apio fpgas --docs" 43 | result = sb.invoke_apio_cmd(apio, ["fpgas", "--docs"]) 44 | sb.assert_result_ok(result) 45 | assert "Loading custom 'fpgas.jsonc'" not in result.output 46 | assert "ice40hx4k-tq144-8k" in result.output 47 | assert "my_custom_fpga" not in result.output 48 | assert "─────┐" not in result.output # Graphic table border 49 | assert ":---" in result.output # Graphic table border 50 | 51 | 52 | def test_custom_fpga(apio_runner: ApioRunner): 53 | """Test "apio fpgas" command with a custom fpgas.jsonc.""" 54 | 55 | with apio_runner.in_sandbox() as sb: 56 | 57 | # -- Write apio.ini for apio to pick the project's default 58 | # -- fpgas.jsonc. 59 | sb.write_default_apio_ini() 60 | 61 | # -- Write a custom boards.jsonc file in the project's directory. 62 | sb.write_file("fpgas.jsonc", CUSTOM_FPGAS) 63 | 64 | # -- Execute "apio fpgas". It should include the customization. 65 | result = sb.invoke_apio_cmd(apio, ["fpgas"]) 66 | sb.assert_result_ok(result) 67 | # -- Note: pytest sees the piped version of the command's output. 68 | # -- Run 'apio build' | cat' to reproduce it. 69 | assert "Loading custom 'fpgas.jsonc'" in result.output 70 | assert "gw1nz-lv1qn48c6-i5" in result.output 71 | assert "custom-ice40hx4k-bg121" in result.output 72 | assert "ice40hx4k-tq144-8k" in result.output 73 | assert "CUSTOM-ICE40HX4K-BG121" in result.output 74 | 75 | # -- Execute "apio fpgas --docs". It should not include the 76 | # -- customization. 77 | result = sb.invoke_apio_cmd(apio, ["fpgas", "--docs"]) 78 | sb.assert_result_ok(result) 79 | # -- Note: pytest sees the piped version of the command's output. 80 | # -- Run 'apio build' | cat' to reproduce it. 81 | assert "Loading custom 'fpgas.jsonc'" not in result.output 82 | assert "gw1nz-lv1qn48c6-i5" in result.output 83 | assert "custom-ice40hx4k-bg121" not in result.output 84 | assert "ice40hx4k-tq144-8k" in result.output 85 | assert "CUSTOM-ICE40HX4K-BG121" not in result.output 86 | -------------------------------------------------------------------------------- /docs/cmd-apio-info.md: -------------------------------------------------------------------------------- 1 | # Apio info 2 | 3 | --- 4 | 5 | ## apio info 6 | 7 | The `apio info` command group displays additional information about Apio and your system. 8 | 9 |

Options

10 | 11 | ` -h, --help Show this message and exit.` 12 | 13 |

Subcommands

14 | 15 | ``` 16 | apio info platforms 17 | apio info system 18 | apio info colors 19 | apio info themes 20 | apio info commands 21 | ``` 22 | 23 | --- 24 | 25 | ## apio info platforms 26 | 27 | The command `apio info platforms` lists the platform IDs supported by Apio and highlights your system's effective ID. 28 | 29 | > [ADVANCED] The automatic platform ID detection of Apio can be overridden by defining a different platform ID using the `APIO_PLATFORM` environment variable, though this is generally not recommended. 30 | 31 |

Examples

32 | 33 | ``` 34 | apio info platforms # List supported platform IDs 35 | ``` 36 | 37 |

Options

38 | 39 | ``` 40 | -h, --help Show this message and exit 41 | ``` 42 | 43 | --- 44 | 45 | ## apio info system 46 | 47 | The `apio info system` command displays general information about your system and Apio installation. Useful for diagnosing setup or environment issues. 48 | 49 | 50 | > [NOTE] For programmatic access to this information use `apio api get-system`. 51 | 52 | 53 | > [ADVANCED] The default location of the Apio home directory, where it saves preferences and packages, is `.apio` under your home directory. This can be changed using the `APIO_HOME` environment variable. The location of the packages directory can be set using the `APIO_PACKAGES` environment variable. 54 | 55 |

Examples

56 | 57 | ``` 58 | apio info system # Show system information 59 | ``` 60 | 61 |

Options

62 | 63 | ``` 64 | -h, --help Show this message and exit 65 | ``` 66 | 67 | --- 68 | 69 | ## apio info colors 70 | 71 | The `apio info colors` command shows how ANSI colors are rendered on your 72 | system, which helps diagnose color-related issues. 73 | 74 | > The command shows colors even if the current theme is `no-colors`. 75 | 76 |

Examples

77 | 78 | ``` 79 | apio info colors # Rich library output (default) 80 | apio sys col -p # Using shortcut 81 | ``` 82 | 83 |

Options

84 | 85 | ``` 86 | -h, --help Show this message and exit 87 | ``` 88 | 89 | --- 90 | 91 | ## apio info themes 92 | 93 | The command `apio info themes` shows the colors of the Apio themes. It 94 | can be used to select the theme that works the best for you. Type 95 | `apio preferences -h` for information on our to select a theme. 96 | 97 | > The command shows the themes colors even if the current theme is `no-colors`. 98 | 99 |

Examples

100 | ``` 101 | apio info themes # Show themes colors 102 | apio inf col -p # Using shortcuts. 103 | ``` 104 | 105 |

Options

106 | ``` 107 | -h, --help Show this message and exit. 108 | ``` 109 | 110 |

Example output

111 | 112 | ![](assets/apio-info-themes.png) 113 | 114 | ## apio info commands 115 | 116 | The command `apio info commands` lists the the available apio commands 117 | in a table format. If the option `--docs` is specified, the command 118 | outputs the list as a markdown document that is used to automatically 119 | update the Apio documentation. 120 | 121 |

Examples

122 | ``` 123 | apio info commands 124 | apio info commands --docs > docs/commands-list.md 125 | ``` 126 | 127 |

Options

128 | ``` 129 | -d, --docs Format for Apio Docs. 130 | -h, --help Show this message and exit. 131 | ``` 132 | -------------------------------------------------------------------------------- /tests/unit_tests/test_apio_context.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests of apio_context.py 3 | """ 4 | 5 | import os 6 | from pathlib import Path 7 | from pytest import LogCaptureFixture, raises 8 | from tests.conftest import ApioRunner 9 | from apio.apio_context import ( 10 | ApioContext, 11 | PackagesPolicy, 12 | ProjectPolicy, 13 | RemoteConfigPolicy, 14 | ) 15 | from apio.common.common_util import PROJECT_BUILD_PATH 16 | 17 | 18 | def test_init(apio_runner: ApioRunner): 19 | """Tests the initialization of the apio context.""" 20 | 21 | with apio_runner.in_sandbox() as sb: 22 | 23 | # -- Create an apio.ini file. 24 | sb.write_default_apio_ini() 25 | 26 | # -- Default init. 27 | apio_ctx = ApioContext( 28 | project_policy=ProjectPolicy.PROJECT_REQUIRED, 29 | remote_config_policy=RemoteConfigPolicy.CACHED_OK, 30 | packages_policy=PackagesPolicy.ENSURE_PACKAGES, 31 | ) 32 | 33 | assert apio_ctx.has_project 34 | 35 | # -- Verify context's project dir. 36 | assert str(apio_ctx.project_dir) == "." 37 | assert apio_ctx.project_dir.samefile(Path.cwd()) 38 | assert apio_ctx.project_dir.samefile(sb.proj_dir) 39 | 40 | # -- Verify context's home and packages dirs. 41 | assert apio_ctx.apio_home_dir == sb.home_dir 42 | assert apio_ctx.apio_packages_dir == sb.packages_dir 43 | 44 | # -- Verify build dir 45 | assert PROJECT_BUILD_PATH == Path("_build") 46 | assert apio_ctx.env_build_path == Path("_build/default") 47 | 48 | 49 | def test_home_dir_with_a_bad_character( 50 | apio_runner: ApioRunner, capsys: LogCaptureFixture 51 | ): 52 | """Tests the initialization of the apio context with home dirs that 53 | contain invalid chars.""" 54 | 55 | for invalid_char in ["ó", "ñ", " ", "😼"]: 56 | with apio_runner.in_sandbox() as sb: 57 | 58 | # -- Make up a home dir path with the invalid char. 59 | invalid_home_dir = sb.sandbox_dir / f"apio-{invalid_char}-home" 60 | os.environ["APIO_HOME"] = str(invalid_home_dir) 61 | 62 | # -- Initialize an apio context. It should exit with an error. 63 | with raises(SystemExit) as e: 64 | ApioContext( 65 | project_policy=ProjectPolicy.NO_PROJECT, 66 | remote_config_policy=RemoteConfigPolicy.CACHED_OK, 67 | packages_policy=PackagesPolicy.ENSURE_PACKAGES, 68 | ) 69 | assert e.value.code == 1 70 | assert ( 71 | f"Unsupported character [{invalid_char}]" 72 | in capsys.readouterr().out 73 | ) 74 | 75 | 76 | def test_home_dir_with_relative_path( 77 | apio_runner: ApioRunner, capsys: LogCaptureFixture 78 | ): 79 | """Apio context should fail if the apio home dir is a relative path""" 80 | 81 | with apio_runner.in_sandbox(): 82 | 83 | # -- Make up a home dir path with the invalid char. 84 | invalid_home_dir = Path("./aa/bb") 85 | os.environ["APIO_HOME"] = str(invalid_home_dir) 86 | 87 | # -- Initialize an apio context. It should exit with an error. 88 | with raises(SystemExit) as e: 89 | ApioContext( 90 | project_policy=ProjectPolicy.NO_PROJECT, 91 | remote_config_policy=RemoteConfigPolicy.CACHED_OK, 92 | packages_policy=PackagesPolicy.ENSURE_PACKAGES, 93 | ) 94 | assert e.value.code == 1 95 | assert ( 96 | "Error: Apio home dir should be an absolute path" 97 | in capsys.readouterr().out 98 | ) 99 | -------------------------------------------------------------------------------- /.github/workflows/publish-docs.yaml: -------------------------------------------------------------------------------- 1 | # A periodic workflow to publish the apio mkDocs at /docs dir to the github 2 | # Pages of this repo. 3 | 4 | name: publish-docs 5 | 6 | on: 7 | push: 8 | # Run on push if doc related files change. 9 | paths: 10 | - "docs/**" 11 | - "mkdocs.yml" 12 | - ".github/workflows/publish-docs.yaml" 13 | 14 | # Run daily at UTC midnight. 15 | schedule: 16 | - cron: "0 0 * * *" 17 | 18 | # Enables manual trigger via GitHub UI 19 | workflow_dispatch: 20 | 21 | jobs: 22 | publish-mkdocs-docs: 23 | runs-on: ubuntu-latest 24 | # NOTE: Make sure the github-pages environment ifn the github repo 25 | # dashboard lists 'develop' as an allowable branch. Otherwise 26 | # this workflow will fail. 27 | environment: github-pages 28 | 29 | permissions: 30 | contents: write 31 | pages: write 32 | id-token: write 33 | 34 | steps: 35 | - name: Checkout apio repository 36 | uses: actions/checkout@v4 37 | 38 | - name: Set up Python 39 | uses: actions/setup-python@v5 40 | with: 41 | python-version: "3.14" 42 | 43 | - name: Install dev tools 44 | run: | 45 | pip install invoke 46 | invoke install-deps 47 | 48 | - name: Install apio 49 | run: | 50 | invoke install-apio 51 | 52 | - name: Generate boards page 53 | run: | 54 | ls docs/supported-boards.md 55 | apio boards --docs > docs/supported-boards.md 56 | cat docs/supported-boards.md 57 | 58 | - name: Generate fpgas page 59 | run: | 60 | ls docs/supported-fpgas.md 61 | apio fpgas --docs > docs/supported-fpgas.md 62 | cat docs/supported-fpgas.md 63 | 64 | - name: Generate commands list page 65 | run: | 66 | ls docs/commands-list.md 67 | apio info commands --docs > docs/commands-list.md 68 | cat docs/commands-list.md 69 | 70 | - name: Generate examples list page 71 | run: | 72 | ls docs/apio-examples.md 73 | apio examples list --docs > docs/apio-examples.md 74 | cat docs/apio-examples.md 75 | 76 | - name: Build mkDocs pages 77 | run: | 78 | mkdocs build --strict --site-dir site/docs 79 | 80 | - name: Add redirector from root dir to docs 81 | run: | 82 | cat > site/index.html <<'EOF' 83 | 84 | 85 | 86 | 87 | Redirecting… 88 | 92 | 93 | 94 |

📚 Docs loading… click here if not automatic

95 | 96 | EOF 97 | 98 | - name: Run test coverage 99 | run: | 100 | invoke test-coverage --no-viewer 101 | 102 | - name: Move test coverage to site 103 | run: | 104 | ls -al 105 | mv _pytest-coverage site/coverage 106 | 107 | - name: Upload new site 108 | uses: actions/upload-pages-artifact@v3 109 | with: 110 | path: site 111 | 112 | - name: Deploy to GitHub Pages 113 | uses: actions/deploy-pages@v4 114 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # Automated testing. 2 | # https://tox.wiki/en/latest/config.html 3 | 4 | # NOTE: Since we don't use clean and --cov-append, the tests coverage is from the 5 | # last python env that is run. Shouldn't be a problem though since we expect similar 6 | # coverage from all python env (we don't branch much on python version). 7 | 8 | # NOTE: The coverage report doesn't include the files in the scons/ directory 9 | # which are run in a subprocess. This includes python files such as 10 | # scons_util.py and the SConstruct files. 11 | 12 | # Useful commands 13 | # 14 | # Run everything: 15 | # tox 16 | # 17 | # Lint only: 18 | # tox -e lint 19 | # 20 | # Test only (in decreasing scope size) 21 | # tox --skip-env lint 22 | # tox -e py313 23 | # tox -e py313 -- test/unit_tests 24 | # tox -e py313 -- test/unit_tests/commands 25 | # tox -e py313 -- test/unit_tests/commands/test_examples.py 26 | # tox -e py313 -- test/unit_tests/commands/test_examples.py::test_examples 27 | # 28 | 29 | # Installing python interpreters 30 | # Mac: brew install python@3.12 31 | # Win: ??? 32 | # Linux: ??? 33 | 34 | # ---------------------------------------------------- 35 | 36 | [tox] 37 | isolated_build = True 38 | 39 | # Runs testenv:x for each env x here. Listing in increasing order 40 | # since more compatibility errors happens with the older version. 41 | envlist = 42 | lint 43 | py311 44 | py312 45 | py313 46 | py314 47 | 48 | # ---------------------------------------------------- 49 | 50 | # Lints the apio code and tests. 51 | [testenv:lint] 52 | deps = 53 | # For validating the mkdocs docs at ./docs 54 | mkdocs-material==9.6.14 55 | 56 | # For linting the python code. 57 | black==25.1.0 58 | flake8==7.2.0 59 | pylint==3.3.7 60 | pytest==8.4.2 61 | 62 | setenv= 63 | LINT_ITEMS = apio tests scripts tasks.py 64 | 65 | 66 | # When we generate the proto files at apio/proto, we also patch at the top 67 | # directives to suppress pylint warnings. 68 | # 69 | # See .pylintrc for pylint's configuration. 70 | commands = 71 | # Check the docs for errors. 72 | mkdocs build --strict 73 | 74 | # Check python for lint errors. 75 | black {env:LINT_ITEMS} --exclude apio/common/proto 76 | flake8 {env:LINT_ITEMS} --exclude apio/common/proto 77 | pylint {env:LINT_ITEMS} 78 | 79 | # ---------------------------------------------------- 80 | 81 | # Runs the test that don't require connected boards. 82 | # This is a template for the pyxx envs listed above.. 83 | [testenv] 84 | deps = 85 | pytest==8.4.2 86 | pytest-cov==7.0.0 87 | 88 | # Pass these env vars, if defined, to the test environment. 89 | # See env_options.py for the list of env variable Apio recognizes. 90 | passenv = 91 | APIO_DEBUG 92 | APIO_REMOTE_CONFIG_URL 93 | 94 | setenv = 95 | COVERAGE_PROCESS_START = {toxinidir}/.coveragerc 96 | COVERAGE_FILE = {toxworkdir}/.coverage 97 | 98 | # Testing while treating warnings as errors. Also, generating coverage report. 99 | # -vv provides more details error messages. 100 | # --duration=N prints the N slowest steps. 101 | # 102 | # For test coverage report add these flags or use 'invoke test-coverage' which 103 | # adds the pytest flags --cov --cov-report=html:_pytest-coverage. Generating 104 | # the test coverage report makes the tests slower. 105 | commands = 106 | python -B -m pytest \ 107 | -W error \ 108 | -vv \ 109 | apio tests/first_test.py tests/unit_tests tests/integration_tests \ 110 | {posargs} 111 | 112 | 113 | # ---------------------------------------------------- 114 | -------------------------------------------------------------------------------- /docs/terminology.md: -------------------------------------------------------------------------------- 1 | - **APIO_DEBUG** - An environment variable that enables debug output during command execution. 2 | Accepts values from 1 to 10, where 10 is the most verbose. 3 | 4 | - **Apio home** - The directory where Apio stores its profile file. Defaults to 5 | `~/.apio`, but can be changed using the `APIO_HOME` environment variable as done 6 | during the automated tests. 7 | 8 | - **Apio packages dir** - The directory where Apio stores its installed packages. Defaults to 9 | `~/.apio/packages`, but can be changed using the `APIO_PACKAGES` environment variable as done 10 | during the automated tests. 11 | 12 | - **ApioContext** - A key Apio class instantiated at the start of each command. It provides 13 | access to Apio's resources and project and profile information. 14 | 15 | - **Board** - Defines an FPGA board, either in the Apio definitions or 16 | or a project-local `boards.jsonc` file. 17 | 18 | - **Click** - A third-party Python library for building command-line applications with 19 | subcommands. It handles Apio's command tree, argument parsing, and help text. 20 | Click commands are functions named `cli` and are configured using decorators. 21 | 22 | - **Drivers** - OS drivers that may be required for programming some FPGA boards. 23 | Missing drivers can prevent board detection by `apio upload` or 24 | `apio devices list`. Install them with `apio drivers install`. 25 | 26 | - **FPGA** - Defines an FPGA device, either in 27 | the Apio standard definition or a project-local `fpgas.jsonc` file. 28 | 29 | - **Invoke** - A third-party Python tool used to run development tasks defined in 30 | `tasks.py`. For example, `invoke check` runs comprehensive pre-submit 31 | tests. 32 | 33 | - **MkDocs** - An open-source tool for publishing Apio's documentation to GitHub Pages. 34 | Content is stored in `mkdocs.yml` and the `docs` directory. It is published 35 | automatically via a GitHub workflow. 36 | 37 | - **Packages** - Tools such as Yosys that Apio manages. Packages are downloaded and stored 38 | under `~/.apio/packages` and are managed using the `apio packages` command. 39 | Some packages are cross-platform, while others are platform-specific. 40 | 41 | - **Platforms** - The operating systems that Apio supports. To list them, run 42 | `apio info platforms`. 43 | 44 | - **Profile** - An Apio class that abstracts the Apio profile file, typically located at 45 | `~/.apio/profile.json`. 46 | 47 | - **Programmer** - Defines an FPGA programming tool, either in 48 | the Apio standard definitions or a project-local `programmers.jsonc` file. 49 | 50 | - **Project** - An Apio class that abstracts the project configuration defined in the 51 | `apio.ini` file. 52 | 53 | - **Protocol Buffers** - A Google-developed open-source language/tool for serializing structured data. 54 | Apio uses it to communicate with the SCons subprocess. Definitions are in 55 | `apio/common/proto/apio.proto` and should be recompiled via 56 | `update-protos.sh` when modified. 57 | 58 | - **Remote config** - A `.jsonc` configuration file stored in the Apio GitHub repository under 59 | `remote-config`. Apio occasionally fetches this file to check for updated 60 | package versions. 61 | 62 | - **Rich** - A third-party Python library for managing Apio's terminal output, including 63 | colored text and data tables. 64 | 65 | - **SCons** - A third-party Python 'make' like build tool used to run Apio project operations 66 | incrementally. SCons prevents redundant operations such as repeating Yosys 67 | synthesis or Nextpnr place-and-route. Apio invokes SCons as a subprocess 68 | only for tasks requiring incremental builds. 69 | 70 | - **Workflows** - GitHub Actions workflows stored in the `.github/workflows` directory of Apio 71 | repositories. 72 | -------------------------------------------------------------------------------- /apio/commands/apio_drivers_install.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # -- This file is part of the Apio project 3 | # -- (C) 2016-2024 FPGAwars 4 | # -- Authors 5 | # -- * Jesús Arroyo (2016-2019) 6 | # -- * Juan Gonzalez (obijuan) (2019-2024) 7 | # -- License GPLv2 8 | """Implementation of 'apio drivers install' command""" 9 | 10 | import sys 11 | import click 12 | from apio.managers.drivers import Drivers 13 | from apio.apio_context import ( 14 | ApioContext, 15 | PackagesPolicy, 16 | ProjectPolicy, 17 | RemoteConfigPolicy, 18 | ) 19 | from apio.utils.cmd_util import ApioGroup, ApioSubgroup, ApioCommand 20 | 21 | 22 | # -- apio drivers install ftdi 23 | 24 | # -- Text in the rich-text format of the python rich library. 25 | APIO_DRIVERS_INSTALL_FTDI_HELP = """ 26 | The command 'apio drivers install ftdi' installs on your system the FTDI \ 27 | drivers required by some FPGA boards. 28 | 29 | Examples:[code] 30 | apio drivers install ftdi # Install the ftdi drivers.[/code] 31 | """ 32 | 33 | 34 | @click.command( 35 | name="ftdi", 36 | cls=ApioCommand, 37 | short_help="Install the ftdi drivers.", 38 | help=APIO_DRIVERS_INSTALL_FTDI_HELP, 39 | ) 40 | def _ftdi_cli(): 41 | """Implements the 'apio drivers install ftdi' command.""" 42 | 43 | # -- Create the apio context. 44 | apio_ctx = ApioContext( 45 | project_policy=ProjectPolicy.NO_PROJECT, 46 | remote_config_policy=RemoteConfigPolicy.CACHED_OK, 47 | packages_policy=PackagesPolicy.ENSURE_PACKAGES, 48 | ) 49 | 50 | # # -- Create the drivers manager. 51 | drivers = Drivers(apio_ctx) 52 | 53 | # -- Install. 54 | exit_code = drivers.ftdi_install() 55 | sys.exit(exit_code) 56 | 57 | 58 | # -- apio driver install serial 59 | 60 | # -- Text in the rich-text format of the python rich library. 61 | APIO_DRIVERS_INSTALL_SERIAL_HELP = """ 62 | The command 'apio drivers install serial' installs the necessary serial \ 63 | drivers on your system, as required by certain FPGA boards. 64 | 65 | Examples:[code] 66 | apio drivers install serial # Install the serial drivers.[/code] 67 | """ 68 | 69 | 70 | @click.command( 71 | name="serial", 72 | cls=ApioCommand, 73 | short_help="Install the serial drivers.", 74 | help=APIO_DRIVERS_INSTALL_SERIAL_HELP, 75 | ) 76 | def _serial_cli(): 77 | """Implements the 'apio drivers install serial' command.""" 78 | 79 | # -- Create the apio context. 80 | apio_ctx = ApioContext( 81 | project_policy=ProjectPolicy.NO_PROJECT, 82 | remote_config_policy=RemoteConfigPolicy.CACHED_OK, 83 | packages_policy=PackagesPolicy.ENSURE_PACKAGES, 84 | ) 85 | 86 | # -- Create the drivers manager. 87 | drivers = Drivers(apio_ctx) 88 | 89 | # Install 90 | exit_code = drivers.serial_install() 91 | sys.exit(exit_code) 92 | 93 | 94 | # --- apio drivers install 95 | 96 | # -- Text in the rich-text format of the python rich library. 97 | APIO_DRIVERS_INSTALL_HELP = """ 98 | The command group 'apio drivers install' includes subcommands that that \ 99 | install system drivers that are used to upload designs to FPGA boards. 100 | """ 101 | 102 | # -- We have only a single group with the title 'Subcommands'. 103 | SUBGROUPS = [ 104 | ApioSubgroup( 105 | "Subcommands", 106 | [ 107 | _ftdi_cli, 108 | _serial_cli, 109 | ], 110 | ) 111 | ] 112 | 113 | 114 | @click.command( 115 | name="install", 116 | cls=ApioGroup, 117 | subgroups=SUBGROUPS, 118 | short_help="Install drivers.", 119 | help=APIO_DRIVERS_INSTALL_HELP, 120 | ) 121 | def cli(): 122 | """Implements the 'apio drivers install' command.""" 123 | 124 | # pass 125 | -------------------------------------------------------------------------------- /apio/commands/apio_drivers_uninstall.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # -- This file is part of the Apio project 3 | # -- (C) 2016-2024 FPGAwars 4 | # -- Authors 5 | # -- * Jesús Arroyo (2016-2019) 6 | # -- * Juan Gonzalez (obijuan) (2019-2024) 7 | # -- License GPLv2 8 | """Implementation of 'apio drivers uninstall' command""" 9 | 10 | import sys 11 | import click 12 | from apio.managers.drivers import Drivers 13 | from apio.apio_context import ( 14 | ApioContext, 15 | PackagesPolicy, 16 | ProjectPolicy, 17 | RemoteConfigPolicy, 18 | ) 19 | from apio.utils.cmd_util import ApioGroup, ApioSubgroup, ApioCommand 20 | 21 | 22 | # -- apio driver uninstall ftdi 23 | 24 | # -- Text in the rich-text format of the python rich library. 25 | APIO_DRIVERS_UNINSTALL_FTDI_HELP = """ 26 | The command 'apio drivers uninstall ftdi' removes the FTDI drivers that may \ 27 | have been installed earlier. 28 | 29 | Examples:[code] 30 | apio drivers uninstall ftdi # Uninstall the ftdi drivers.[/code] 31 | """ 32 | 33 | 34 | @click.command( 35 | name="ftdi", 36 | cls=ApioCommand, 37 | short_help="Uninstall the ftdi drivers.", 38 | help=APIO_DRIVERS_UNINSTALL_FTDI_HELP, 39 | ) 40 | def _ftdi_cli(): 41 | """Implements the 'apio drivers uninstall ftdi' command.""" 42 | 43 | # -- Create the apio context. 44 | apio_ctx = ApioContext( 45 | project_policy=ProjectPolicy.NO_PROJECT, 46 | remote_config_policy=RemoteConfigPolicy.CACHED_OK, 47 | packages_policy=PackagesPolicy.ENSURE_PACKAGES, 48 | ) 49 | 50 | # -- Create the drivers manager. 51 | drivers = Drivers(apio_ctx) 52 | 53 | # -- Uninstall 54 | exit_code = drivers.ftdi_uninstall() 55 | sys.exit(exit_code) 56 | 57 | 58 | # -- apio drivers uninstall serial 59 | 60 | # -- Text in the rich-text format of the python rich library. 61 | APIO_DRIVERS_UNINSTALL_SERIAL_HELP = """ 62 | The command 'apio drivers uninstall serial' removes the serial drivers that \ 63 | you may have installed earlier. 64 | 65 | Examples:[code] 66 | apio drivers uninstall serial # Uninstall the serial drivers.[/code] 67 | """ 68 | 69 | 70 | @click.command( 71 | name="serial", 72 | cls=ApioCommand, 73 | short_help="Uninstall the serial drivers.", 74 | help=APIO_DRIVERS_UNINSTALL_SERIAL_HELP, 75 | ) 76 | def _serial_cli(): 77 | """Implements the 'apio drivers uninstall serial' command.""" 78 | 79 | # -- Create the apio context. 80 | apio_ctx = ApioContext( 81 | project_policy=ProjectPolicy.NO_PROJECT, 82 | remote_config_policy=RemoteConfigPolicy.CACHED_OK, 83 | packages_policy=PackagesPolicy.ENSURE_PACKAGES, 84 | ) 85 | 86 | # -- Create the drivers manager. 87 | drivers = Drivers(apio_ctx) 88 | 89 | # -- Uninstall 90 | exit_code = drivers.serial_uninstall() 91 | sys.exit(exit_code) 92 | 93 | 94 | # --- apio drivers uninstall 95 | 96 | # -- Text in the rich-text format of the python rich library. 97 | APIO_DRIVERS_UNINSTALL_HELP = """ 98 | The command group 'apio drivers uninstall' includes subcommands that that \ 99 | uninstall system drivers that are used to upload designs to FPGA boards. 100 | """ 101 | 102 | # -- We have only a single group with the title 'Subcommands'. 103 | SUBGROUPS = [ 104 | ApioSubgroup( 105 | "Subcommands", 106 | [ 107 | _ftdi_cli, 108 | _serial_cli, 109 | ], 110 | ) 111 | ] 112 | 113 | 114 | @click.command( 115 | name="uninstall", 116 | cls=ApioGroup, 117 | subgroups=SUBGROUPS, 118 | short_help="Uninstall drivers.", 119 | help=APIO_DRIVERS_UNINSTALL_HELP, 120 | ) 121 | def cli(): 122 | """Implements the 'apio drivers uninstall' command.""" 123 | 124 | # pass 125 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | # Run on each commit. 5 | push: 6 | 7 | # Run on pull requests. 8 | pull_request: 9 | branches: 10 | - develop 11 | 12 | # Run daily at UTC midnight. 13 | schedule: 14 | - cron: "0 0 * * *" 15 | 16 | # Can be launched manually in github actions tab. 17 | workflow_dispatch: # Allows manual trigger 18 | inputs: 19 | commit_sha: 20 | description: "[Optional] commit SHA to test" 21 | required: false 22 | default: "" 23 | verbose: 24 | description: "Verbose [false|true]" 25 | required: false 26 | default: "false" 27 | 28 | jobs: 29 | test: 30 | runs-on: ${{ matrix.os }} 31 | 32 | defaults: 33 | run: 34 | shell: bash 35 | 36 | strategy: 37 | matrix: 38 | # 'macos-latest' -> darwin apple silicon 39 | # 'macos-15-intel' -> darwin intel x86 40 | os: [ubuntu-22.04, macos-latest, macos-15-intel, windows-latest] 41 | python-version: ['3.11', "3.12", "3.13", "3.14"] 42 | 43 | steps: 44 | - name: Show architecture 45 | run: uname -a 46 | 47 | - name: Determine commit to use 48 | run: | 49 | # Strip leading and trailing spaces 50 | sha="$(echo "${{ github.event.inputs.commit_sha }}" | xargs)" 51 | 52 | # User specified commit SHA. 53 | if [ -n "$sha" ]; then 54 | echo "COMMIT_SHA=$sha" >> $GITHUB_ENV 55 | echo "COMMIT_NOTE=Using manual commit" >> $GITHUB_ENV 56 | 57 | # Default behavior, use latest commit. 58 | else 59 | echo "COMMIT_SHA=${{ github.sha }}" >> $GITHUB_ENV 60 | echo "COMMIT_NOTE=Using latest commit" >> $GITHUB_ENV 61 | fi 62 | 63 | - name: Show selected commit 64 | run: | 65 | echo "$COMMIT_NOTE" 66 | echo "Selected: $COMMIT_SHA" 67 | echo "Latest: ${{github.sha}}" 68 | 69 | - name: Checkout apio 70 | uses: actions/checkout@v3 71 | with: 72 | ref: ${{env.COMMIT_SHA}} 73 | 74 | - name: Install python 75 | uses: actions/setup-python@v4 76 | with: 77 | python-version: ${{ matrix.python-version }} 78 | 79 | - name: Show python version 80 | run: python --version 81 | 82 | - name: Install dev tools 83 | run: | 84 | pip install invoke 85 | invoke install-deps 86 | 87 | # TODO: Move the logic here to an Invoke task and use it instead. 88 | - name: Run Tests 89 | run: | 90 | # Convert py version such as '3.13' to 'py313' as required by tox.ini. 91 | pyver="py${{matrix.python-version}}" 92 | echo "pyver: [$pyver]" 93 | pyver=${pyver//./} 94 | echo "pyver: [$pyver]" 95 | 96 | # Determine if we run in verbose mode. This can be enabled 97 | # in the github dashboard when running the workflow manuall.y. 98 | echo "Original verbose: [${{ github.event.inputs.verbose }}]" 99 | verbose="${{ github.event.inputs.verbose || 'false' }}" 100 | echo "Effective verbose: [${verbose}]" 101 | if [[ "$verbose" != "true" && "$verbose" != "false" ]]; then 102 | echo "Error: 'verbose' must be 'true' or 'false'." 103 | exit 1 104 | fi 105 | 106 | # Determine the posargs to pass to pytest. 107 | if [ "${verbose}" = "true" ]; then 108 | echo "Verbose mode ON" 109 | posargs="-s" 110 | else 111 | echo "Verbose mode OFF" 112 | posargs="" 113 | fi 114 | 115 | # Run the tests 116 | python -m tox --skip-missing-interpreters false -e lint,$pyver -- ${posargs} 117 | -------------------------------------------------------------------------------- /apio/managers/downloader.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # -- This file is part of the Apio project 3 | # -- (C) 2016-2019 FPGAwars 4 | # -- Author Jesús Arroyo 5 | # -- License GPLv2 6 | # -- Derived from: 7 | # ---- Platformio project 8 | # ---- (C) 2014-2016 Ivan Kravets 9 | # ---- License Apache v2 10 | """Implement a remote file downloader. Used to fetch packages from github 11 | packages release repositories. 12 | """ 13 | 14 | # TODO: capture all the exceptions and return them as method return status. 15 | # Motivation is simplifying the usage. 16 | 17 | from math import ceil 18 | import requests 19 | from rich.progress import track 20 | from apio.utils import util 21 | from apio.common.apio_console import cout, console 22 | from apio.common.apio_styles import ERROR 23 | 24 | # -- Timeout for getting a response from the server when downloading 25 | # -- a file (in seconds). We had github tests failing with timeout=10 26 | TIMEOUT_SECS = 30 27 | 28 | 29 | class FileDownloader: 30 | """Class for downloading files""" 31 | 32 | CHUNK_SIZE = 1024 33 | 34 | def __init__(self, url: str, dest_dir=None): 35 | """Initialize a FileDownloader object 36 | * INPUTs: 37 | * url: File to download (full url) 38 | (Ex. 'https://github.com/FPGAwars/apio-examples/ 39 | releases/download/0.0.35/apio-examples-0.0.35.zip') 40 | * dest_dir: Destination folder (where to download the file) 41 | """ 42 | 43 | # -- Store the url 44 | self._url = url 45 | 46 | # -- Get the file from the url 47 | # -- Ex: 'apio-examples-0.0.35.zip' 48 | self.fname = url.split("/")[-1] 49 | 50 | # -- Build the destination path 51 | self.destination = self.fname 52 | if dest_dir: 53 | 54 | # -- Add the path 55 | self.destination = dest_dir / self.fname 56 | 57 | # -- Request the file 58 | self._request = requests.get(url, stream=True, timeout=TIMEOUT_SECS) 59 | 60 | # -- Raise an exception in case of download error... 61 | if self._request.status_code != 200: 62 | cout( 63 | "Got an unexpected HTTP status code: " 64 | f"{self._request.status_code}", 65 | f"When downloading {url}", 66 | style=ERROR, 67 | ) 68 | raise util.ApioException() 69 | 70 | def get_size(self) -> int: 71 | """Return the size (in bytes) of the latest bytes block received""" 72 | 73 | return int(self._request.headers["content-length"]) 74 | 75 | def start(self): 76 | """Start the downloading of the file""" 77 | 78 | # -- Download iterator 79 | itercontent = self._request.iter_content(chunk_size=self.CHUNK_SIZE) 80 | 81 | # -- Open destination file, for writing bytes 82 | with open(self.destination, "wb") as file: 83 | 84 | # -- Get the file length in Kbytes 85 | num_chunks = int(ceil(self.get_size() / float(self.CHUNK_SIZE))) 86 | 87 | # -- Download and write the chunks, while displaying the progress. 88 | for _ in track( 89 | range(num_chunks), 90 | description="Downloading", 91 | console=console(), 92 | ): 93 | 94 | file.write(next(itercontent)) 95 | 96 | # -- Check that the iterator reached its end. When the end is 97 | # -- reached, next() returns the default value None. 98 | assert next(itercontent, None) is None 99 | 100 | # -- Download done! 101 | self._request.close() 102 | 103 | def __del__(self): 104 | """Close any pending request""" 105 | 106 | if self._request: 107 | self._request.close() 108 | -------------------------------------------------------------------------------- /apio/commands/apio_lint.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # -- This file is part of the Apio project 3 | # -- (C) 2016-2024 FPGAwars 4 | # -- Authors 5 | # -- * Jesús Arroyo (2016-2019) 6 | # -- * Juan Gonzalez (obijuan) (2019-2024) 7 | # -- License GPLv2 8 | """Implementation of 'apio lint' command""" 9 | import sys 10 | from typing import Optional 11 | from pathlib import Path 12 | import click 13 | from apio.managers.scons_manager import SConsManager 14 | from apio.utils import util 15 | from apio.utils import cmd_util 16 | from apio.commands import options 17 | from apio.apio_context import ( 18 | ApioContext, 19 | PackagesPolicy, 20 | ProjectPolicy, 21 | RemoteConfigPolicy, 22 | ) 23 | from apio.common.proto.apio_pb2 import LintParams 24 | 25 | 26 | # ------- apio lint 27 | 28 | 29 | nostyle_option = click.option( 30 | "nostyle", # Var name 31 | "--nostyle", 32 | is_flag=True, 33 | help="Disable all style warnings.", 34 | cls=cmd_util.ApioOption, 35 | ) 36 | 37 | 38 | nowarn_option = click.option( 39 | "nowarn", # Var name 40 | "--nowarn", 41 | type=str, 42 | metavar="nowarn", 43 | help="Disable specific warning(s).", 44 | cls=cmd_util.ApioOption, 45 | ) 46 | 47 | warn_option = click.option( 48 | "warn", # Var name 49 | "--warn", 50 | type=str, 51 | metavar="warn", 52 | help="Enable specific warning(s).", 53 | cls=cmd_util.ApioOption, 54 | ) 55 | 56 | 57 | # -- Text in the rich-text format of the python rich library. 58 | APIO_LINT_HELP = """ 59 | The command 'apio lint' scans the project's source files and reports errors, \ 60 | inconsistencies, and style violations. The command uses the Verilator tool, \ 61 | which is included with the standard Apio installation. 62 | 63 | Examples:[code] 64 | apio lint 65 | apio lint -t my_module 66 | apio lint --all[/code] 67 | """ 68 | 69 | 70 | @click.command( 71 | name="lint", 72 | cls=cmd_util.ApioCommand, 73 | short_help="Lint the source code.", 74 | help=APIO_LINT_HELP, 75 | ) 76 | @click.pass_context 77 | @nostyle_option 78 | @nowarn_option 79 | @warn_option 80 | @options.all_option_gen( 81 | short_help="Enable all warnings, including code style warnings." 82 | ) 83 | @options.top_module_option_gen( 84 | short_help="Restrict linting to this module and its dependencies." 85 | ) 86 | @options.env_option_gen() 87 | @options.project_dir_option 88 | def cli( 89 | _: click.Context, 90 | # Options 91 | nostyle: bool, 92 | nowarn: str, 93 | warn: str, 94 | all_: bool, 95 | top_module: str, 96 | env: Optional[str], 97 | project_dir: Optional[Path], 98 | ): 99 | """Lint the source code.""" 100 | 101 | # pylint: disable=too-many-arguments 102 | # pylint: disable=too-many-positional-arguments 103 | 104 | # -- Create the apio context. 105 | apio_ctx = ApioContext( 106 | project_policy=ProjectPolicy.PROJECT_REQUIRED, 107 | remote_config_policy=RemoteConfigPolicy.CACHED_OK, 108 | packages_policy=PackagesPolicy.ENSURE_PACKAGES, 109 | project_dir_arg=project_dir, 110 | env_arg=env, 111 | ) 112 | 113 | # -- Create the scons manager. 114 | scons = SConsManager(apio_ctx) 115 | 116 | # -- Convert the comma separated args values to python lists 117 | no_warns_list = util.split(nowarn, ",", strip=True, keep_empty=False) 118 | warns_list = util.split(warn, ",", strip=True, keep_empty=False) 119 | 120 | # -- Create the lint params 121 | lint_params = LintParams( 122 | top_module=top_module if top_module else None, 123 | verilator_all=all_, 124 | verilator_no_style=nostyle, 125 | verilator_no_warns=no_warns_list, 126 | verilator_warns=warns_list, 127 | ) 128 | 129 | assert lint_params.IsInitialized(), lint_params 130 | 131 | # -- Lint the project with the given parameters 132 | exit_code = scons.lint(lint_params) 133 | sys.exit(exit_code) 134 | -------------------------------------------------------------------------------- /docs/raw-tools.md: -------------------------------------------------------------------------------- 1 | # Raw tools 2 | 3 | The `apio raw` command provides access to additional tools that are included 4 | in the apio packages, for example, in the `oss-cad-suite` package from the 5 | YosysHQ project. 6 | 7 | This page describes several such raw tools that are available in Apio. This list is not exhaustive. 8 | 9 | > If you encounter a useful tool in the apio packages that is not listed 10 | > here please please file an issue in the 11 | > [Apio repository](https://github.com/fpgawars/apio/issues) to add it. 12 | 13 | --- 14 | 15 | ## ICE40 PLL generator 16 | 17 | The ICE40 PLL Generator is a command-line tool that creates an ICE40 PLL module from specified flags. It is used to run the design at a different, typically higher, frequency than that of the external clock. 18 | 19 | Get help text. 20 | 21 | ``` 22 | apio raw -- icepll -h 23 | ``` 24 | 25 | Generate a PLL module that converts a 12 MHz input to a 48 MHz output clock. 26 | 27 | ``` 28 | apio raw -- icepll -i 12 -o 48 -q -m -f pll.v 29 | apio format pll.v 30 | ``` 31 | 32 | The Apio example `alhambra/pll` demonstrates a `pll.v` module that 33 | was generated with this command. 34 | 35 | > Per [Apio issue 669](https://github.com/FPGAwars/apio/issues/669), 36 | > the generated module does not pass `apio lint` because it 37 | > doesn't specify the unused PLL signals. As a workaround, manually add the following 38 | > signals to the PLL instantiation in `pll.v`: 39 | 40 | ``` 41 | .PLLOUTGLOBAL(), 42 | .EXTFEEDBACK(), 43 | .LATCHINPUTVALUE(), 44 | .SDO(), 45 | .SDI(), 46 | .SCLK(), 47 | .DYNAMICDELAY() 48 | ``` 49 | 50 | --- 51 | 52 | ## ECP5 PLL generator 53 | 54 | The ECP5 PLL Generator is a command-line tool that creates an ECP5 PLL module from specified flags. It is used to run the design at a different, typically higher, frequency than that of the external clock. 55 | 56 | Get help text. 57 | 58 | ``` 59 | apio raw -- ecppll -h 60 | ``` 61 | 62 | Generate a PLL module that converts a 25 MHz input to a 120 MHz output clock. 63 | 64 | ``` 65 | apio raw -- ecppll -i 25 -o 120 -f pll.v 66 | apio format pll.v 67 | ``` 68 | 69 | The Apio example `colorlight-5a-75b-v8/pll` demonstrates a `pll.v` module that 70 | was generated with this command. 71 | 72 | > Per [Apio issue 670](https://github.com/FPGAwars/apio/issues/669), 73 | > the generated module does not pass `apio lint` because it 74 | > doesn't specify the unused PLL signals. As a workaround, manually add the following 75 | > signals to the PLL instantiation in `pll.v`: 76 | 77 | ``` 78 | .ENCLKOS(), 79 | .ENCLKOS2(), 80 | .ENCLKOS3(), 81 | .CLKOS(), 82 | .CLKOS2(), 83 | .CLKOS3(), 84 | .INTLOCK(), 85 | .REFCLK() 86 | ``` 87 | 88 | --- 89 | 90 | ## Gowin PLL generator 91 | 92 | The GOWIN PLL Generator is a command-line tool that creates a GOWIN PLL module from specified flags. It is used to run the design at a different, typically higher, frequency than that of the external clock. 93 | 94 | Get help text. 95 | 96 | ``` 97 | apio raw -- gowin_pll -h 98 | ``` 99 | 100 | Generate a PLL module for the Sipeed Nano 9K that converts a 27 MHz input to a 75 MHz output clock. 101 | 102 | ``` 103 | apio raw -- gowin_pll -d "GW1NR-9 C6/I5" -i 27 -o 75 -f pll.v 104 | apio format pll.v 105 | ``` 106 | 107 | The Apio example `sipeed-tang-nano-9k/pll` demonstrates a `pll.v` module that 108 | was generated with this command. 109 | 110 | --- 111 | 112 | ## Zadig (Windows only) 113 | 114 | Zadig is a third party Windows tool that allow to manage and replace USB 115 | device drivers. Zadig is used by Apio to install and uninstall FPGA boards 116 | drivers on windows but can also be used independently using the command 117 | 118 | ``` 119 | apio raw -- zadig 120 | ``` 121 | 122 | --- 123 | 124 | ## Verible verilog diff 125 | 126 | verible verilog diff is a command line tool that finds the semantic differences 127 | between verilog files. 128 | 129 | Get help text. 130 | 131 | ``` 132 | apio raw -- verible-verilog-diff --helpfull 133 | ``` 134 | -------------------------------------------------------------------------------- /apio/commands/apio_upload.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # -- This file is part of the Apio project 3 | # -- (C) 2016-2024 FPGAwars 4 | # -- Authors 5 | # -- * Jesús Arroyo (2016-2019) 6 | # -- * Juan Gonzalez (obijuan) (2019-2024) 7 | # -- License GPLv2 8 | """Implementation of 'apio upload' command""" 9 | 10 | import sys 11 | from typing import Optional 12 | from pathlib import Path 13 | import click 14 | from apio.managers.scons_manager import SConsManager 15 | from apio.utils import cmd_util 16 | from apio.commands import options 17 | from apio.apio_context import ( 18 | ApioContext, 19 | PackagesPolicy, 20 | ProjectPolicy, 21 | RemoteConfigPolicy, 22 | ) 23 | from apio.managers.programmers import construct_programmer_cmd 24 | from apio.common.proto.apio_pb2 import UploadParams 25 | 26 | 27 | # --------- apio upload 28 | 29 | serial_port_option = click.option( 30 | "serial_port", # Var name. 31 | "-s", 32 | "--serial-port", 33 | type=str, 34 | metavar="serial-port", 35 | help="Set the serial port.", 36 | cls=cmd_util.ApioOption, 37 | ) 38 | 39 | serial_num_option = click.option( 40 | "serial_num", # Var name. 41 | "-n", 42 | "--serial-num", 43 | type=str, 44 | metavar="serial-num", 45 | help="Select the device's USB serial number.", 46 | cls=cmd_util.ApioOption, 47 | ) 48 | 49 | 50 | # -- Text in the rich-text format of the python rich library. 51 | APIO_UPLOAD_HELP = """ 52 | The command 'apio upload' builds the bitstream file (similar to the \ 53 | 'apio build' command) and uploads it to the FPGA board. 54 | 55 | Examples:[code] 56 | apio upload # Typical invocation 57 | apio upload -s /dev/cu.usbserial-1300 # Select serial port 58 | apio upload -n FTXYA34Z # Select serial number[/code] 59 | 60 | Typically the simple form 'apio upload' is sufficient to locate and program \ 61 | the FPGA board. The optional flags '--serial-port' and '--serial-num' allows \ 62 | to select the desired board if more than one matching board is detected. 63 | 64 | [HINT] You can use the command 'apio devices' to list the connected USB and \ 65 | serial devices and the command 'apio drivers' to install and uninstall device \ 66 | drivers. 67 | 68 | [HINT] The default programmer command of your board can be overridden using \ 69 | the apio.ini option 'programmer-cmd'. 70 | """ 71 | 72 | 73 | @click.command( 74 | name="upload", 75 | cls=cmd_util.ApioCommand, 76 | short_help="Upload the bitstream to the FPGA.", 77 | help=APIO_UPLOAD_HELP, 78 | ) 79 | @click.pass_context 80 | @serial_port_option 81 | @serial_num_option 82 | @options.env_option_gen() 83 | @options.project_dir_option 84 | def cli( 85 | _: click.Context, 86 | # Options 87 | serial_port: str, 88 | serial_num: str, 89 | env: Optional[str], 90 | project_dir: Optional[Path], 91 | ): 92 | """Implements the upload command.""" 93 | 94 | # -- Create a apio context. 95 | apio_ctx = ApioContext( 96 | project_policy=ProjectPolicy.PROJECT_REQUIRED, 97 | remote_config_policy=RemoteConfigPolicy.CACHED_OK, 98 | packages_policy=PackagesPolicy.ENSURE_PACKAGES, 99 | project_dir_arg=project_dir, 100 | env_arg=env, 101 | ) 102 | 103 | # -- Set the shell env. 104 | apio_ctx.set_env_for_packages() 105 | 106 | # -- Get the programmer command. 107 | programmer_cmd = construct_programmer_cmd( 108 | apio_ctx, serial_port_flag=serial_port, serial_num_flag=serial_num 109 | ) 110 | 111 | # Construct the scons upload params. 112 | upload_params = UploadParams(programmer_cmd=programmer_cmd) 113 | 114 | # -- Create the scons manager 115 | scons = SConsManager(apio_ctx) 116 | 117 | # Run scons: upload command 118 | exit_code = scons.upload(upload_params) 119 | 120 | # -- Done! 121 | sys.exit(exit_code) 122 | 123 | 124 | # Advanced notes: https://github.com/FPGAwars/apio/wiki/Commands#apio-upload 125 | -------------------------------------------------------------------------------- /tests/unit_tests/commands/test_apio_packages.py: -------------------------------------------------------------------------------- 1 | """Test for the "apio packages" command.""" 2 | 3 | from os import listdir, rename 4 | from tests.conftest import ApioRunner 5 | from apio.commands.apio import apio_top_cli as apio 6 | from apio.common.apio_console import cunstyle 7 | 8 | 9 | def test_packages(apio_runner: ApioRunner): 10 | """Test "apio packages" with different parameters""" 11 | 12 | with apio_runner.in_sandbox() as sb: 13 | 14 | # -- Execute "apio packages" 15 | result = sb.invoke_apio_cmd(apio, ["packages"]) 16 | sb.assert_result_ok(result) 17 | assert "Subcommands:" in result.output 18 | assert "apio packages update" in cunstyle(result.output) 19 | assert "apio packages list" in cunstyle(result.output) 20 | assert result.output != cunstyle(result.output) # Colored. 21 | 22 | # -- Execute "apio packages list" 23 | result = sb.invoke_apio_cmd(apio, ["packages", "list"]) 24 | sb.assert_result_ok(result) 25 | 26 | 27 | def test_packages_slow(apio_runner: ApioRunner): 28 | """Tests listing, installation and uninstallation of packages.""" 29 | 30 | with apio_runner.in_sandbox() as sb: 31 | 32 | # -- Run 'apio packages list' 33 | result = sb.invoke_apio_cmd(apio, ["packages", "list"]) 34 | sb.assert_result_ok(result) 35 | assert "definitions" in result.output 36 | assert "examples" in result.output 37 | assert "oss-cad-suite" in result.output 38 | 39 | # -- Run 'apio packages update'. 40 | # -- Both 'examples' and 'oss-cad-suite' should exist, and 41 | # -- possibly others, depending on the platform. 42 | result = sb.invoke_apio_cmd(apio, ["packages", "update"]) 43 | sb.assert_result_ok(result) 44 | assert "All Apio packages are installed OK" in result.output 45 | assert listdir(sb.packages_dir / "definitions") 46 | assert listdir(sb.packages_dir / "examples/alhambra-ii") 47 | assert listdir(sb.packages_dir / "oss-cad-suite/bin") 48 | 49 | # -- Delete a file from the examples package, we will use it as an 50 | # -- indicator for the reinstallation of the package. 51 | marker_file = sb.packages_dir / "examples/alhambra-ii/ledon/ledon.v" 52 | assert marker_file.is_file() 53 | marker_file.unlink() 54 | assert not marker_file.exists() 55 | 56 | # -- Run 'apio packages update'. 57 | # -- This should not do anything since it's considered to be installed. 58 | result = sb.invoke_apio_cmd(apio, ["packages", "update"]) 59 | sb.assert_result_ok(result) 60 | assert "Package 'examples' installed" not in result.output 61 | assert not marker_file.exists() 62 | 63 | # -- Run 'apio packages update --force' 64 | # -- This should recover the file. 65 | result = sb.invoke_apio_cmd(apio, ["packages", "update", "--force"]) 66 | sb.assert_result_ok(result) 67 | assert "Package 'examples' installed" in result.output 68 | assert marker_file.is_file() 69 | 70 | # -- Break the examples package by renaming it. This also creates an 71 | # -- orphan dir. 72 | example_package_dir = sb.packages_dir / "examples" 73 | bad_package_dir = sb.packages_dir / "unknown-package" 74 | rename(example_package_dir, bad_package_dir) 75 | assert not example_package_dir.exists() 76 | assert bad_package_dir.is_dir() 77 | 78 | # -- Run 'apio packages update'. This should fix everything. 79 | result = sb.invoke_apio_cmd(apio, ["packages", "update"]) 80 | sb.assert_result_ok(result) 81 | assert "Uninstalling broken package 'examples'" in result.output 82 | assert ( 83 | "Deleting unknown package dir 'unknown-package'" in result.output 84 | ) 85 | assert "Package 'examples' installed successfully" in result.output 86 | assert example_package_dir.is_dir() 87 | assert marker_file.exists() 88 | assert not bad_package_dir.exists() 89 | --------------------------------------------------------------------------------