python: Gonk bundle
Create a Gonk tools bundle containing Python wheels that include the
firmware image and FPGA bitstream file.
write_fpga.py and flash.py now default to using binaries included in
the gonk_firmware python package.
Change-Id: I28fe6746d9319dadda889520dc4b52476533ca95
Reviewed-on: https://pigweed-review.googlesource.com/c/gonk/+/194770
Reviewed-by: Keir Mierle <keir@google.com>
Commit-Queue: Anthony DiGirolamo <tonymd@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index 90eb91d..ecd7a90 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -47,10 +47,7 @@
# This group requires the following tools for the FPGA build:
# yosys, nextpnr-ice40, icepack, icetime
group("fpga") {
- deps = [
- ":pip_install_gonk_fpga",
- "//fpga",
- ]
+ deps = [ "//fpga" ]
}
# Python Targets
@@ -75,7 +72,7 @@
}
# Bundle of all Gonk Python packages and their Pigweed dependencies
-pw_python_distribution("gonk_python_distribution") {
+pw_python_distribution("gonk_python_tools") {
packages = _gonk_python_packages + _gonk_proto_packages
generate_setup_cfg = {
name = "gonk-dist"
@@ -86,15 +83,15 @@
}
# Pip install target for Gonk Python tools and protos.
-pw_python_pip_install("pip_install_gonk_dist") {
- packages = [ ":gonk_python_distribution" ]
+pw_python_pip_install("pip_install_gonk_tools") {
+ packages = [ ":gonk_python_tools" ]
}
-# Python data package for the FPGA build.
-pw_python_distribution("gonk_fpga_distribution") {
+# Python data package for Gonk firmware build.
+pw_python_distribution("gonk_firmware") {
packages = []
generate_setup_cfg = {
- name = "gonk-fpga"
+ name = "gonk-firmware"
version = "0.0.1"
append_date_to_version = true
include_default_pyproject_file = true
@@ -102,28 +99,57 @@
auto_create_package_data_init_py_files = true
}
- public_deps = []
+ public_deps = [
+ "//applications/fpga_config:fpga_config(//targets/stm32f730r8tx_arduino:arduino_size_optimized)",
+ "//applications/fpga_config:fpga_config.elf(//targets/stm32f730r8tx_arduino:arduino_size_optimized)",
+ ]
+
+ _gonk_bin = "$root_build_dir/arduino_size_optimized/obj/applications/fpga_config/fpga_config.bin"
+ _gonk_elf = "$root_build_dir/arduino_size_optimized/obj/applications/fpga_config/bin/fpga_config.elf"
+ extra_files = [
+ "$_gonk_bin > gonk_firmware/gonk.bin",
+ "$_gonk_elf > gonk_firmware/gonk.elf",
+ ]
# The FPGA image is only built on Linux so far.
if (host_os == "linux") {
_fpga_artifacts_dir = "$root_build_dir/obj/fpga/toplevel"
public_deps += [ "//fpga:toplevel._bin($default_toolchain)" ]
- extra_files =
- [ "$_fpga_artifacts_dir/toplevel.bin > gonk_fpga/toplevel.bin" ]
+ extra_files +=
+ [ "$_fpga_artifacts_dir/toplevel.bin > gonk_firmware/fpga.bin" ]
}
}
-# Pip install target for the FPGA datapackage only. This is sparate since the
-# FPGA toolchain is an optional build component.
-pw_python_pip_install("pip_install_gonk_fpga") {
- packages = [ ":gonk_fpga_distribution" ]
+# Pip install target for the firmware datapackage only. This is separate since
+# the FPGA toolchain is an optional build component.
+pw_python_pip_install("pip_install_gonk_firmware") {
+ packages = [ ":gonk_firmware" ]
}
# Python group used durring bootstrap.
pw_python_group("python") {
python_deps = [
- ":pip_install_gonk_dist",
+ ":pip_install_gonk_tools",
"$dir_pw_env_setup:pip_install_pigweed_package",
]
}
+
+_gonk_bundle_packages =
+ _gonk_python_packages + _gonk_proto_packages + [ ":gonk_firmware" ]
+
+pw_python_venv("gonk_bundle_venv") {
+ path = "$root_build_dir/gonk-bundle-venv"
+ source_packages = _gonk_bundle_packages
+}
+
+pw_python_zip_with_setup("gonk_bundle") {
+ # Include vendored wheels for this virtualenv.
+ venv = ":gonk_bundle_venv"
+
+ # Gonk packages to include
+ packages = [
+ ":gonk_firmware",
+ ":gonk_python_tools",
+ ]
+}
diff --git a/README.md b/README.md
index af729c0..48e3532 100644
--- a/README.md
+++ b/README.md
@@ -111,6 +111,38 @@
./scripts/flash-with-blackmagic-probe.sh ./out/gn/arduino_size_optimized/obj/applications/spi_flash_test/bin/spi_flash_test.elf
```
+## Generating the Gonk Python Bundle
+
+On Linux with the FPGA toolchain available run:
+
+```sh
+pw build
+```
+
+The zip file containing all the dependencies for the gonk python tooling is located in:
+
+```sh
+out/gn/obj/gonk_bundle.zip
+```
+
+Inside are the Python wheels for `gonk_dist`, `gonk_firmware`, and all
+third_party dependencies.
+
+```
+gonk_bundle
+├── python_wheels
+│ ├── appdirs-1.4.4-py2.py3-none-any.whl
+│ ├── astroid-3.0.1-py3-none-any.whl
+│ ├── ...
+│ ├── gonk_dist-0.0.1+20240305140627-py3-none-any.whl
+│ ├── gonk_firmware-0.0.1+20240305140542-py3-none-any.whl
+│ ├── ...
+│ └── wheel-0.40.0-py3-none-any.whl
+├── requirements.txt
+├── setup.bat
+└── setup.sh
+```
+
## pw_system Example
### Run on Host
diff --git a/tools/BUILD.gn b/tools/BUILD.gn
index d7027cd..117a1fe 100644
--- a/tools/BUILD.gn
+++ b/tools/BUILD.gn
@@ -26,6 +26,7 @@
"gonk_tools/binary_handler.py",
"gonk_tools/build_project.py",
"gonk_tools/find_serial_port.py",
+ "gonk_tools/firmware_files.py",
"gonk_tools/flash.py",
"gonk_tools/gonk_log_stream.py",
"gonk_tools/presubmit_checks.py",
diff --git a/tools/gonk_tools/build_project.py b/tools/gonk_tools/build_project.py
index 1b1c874..c07ab9b 100644
--- a/tools/gonk_tools/build_project.py
+++ b/tools/gonk_tools/build_project.py
@@ -59,13 +59,15 @@
'--export-compile-commands',
]
- default_gn_args: dict[str, str] = dict()
+ default_gn_args: dict[str, str] = {}
default_gn_gen_command.append(gn_args(**default_gn_args))
default_targets = ['default']
if _ENABLE_FPGA_BUILD:
- default_targets += ['fpga']
+ # Build the FPGA image, gonk tools bundle, and pip install the
+ # gonk_firmware package into the bootstrap venv.
+ default_targets += ['fpga', 'gonk_bundle', 'pip_install_gonk_firmware']
else:
_LOG.warning(
'FPGA build disabled. The following tools were not found: '
diff --git a/tools/gonk_tools/firmware_files.py b/tools/gonk_tools/firmware_files.py
new file mode 100644
index 0000000..5048a48
--- /dev/null
+++ b/tools/gonk_tools/firmware_files.py
@@ -0,0 +1,71 @@
+# Copyright 2023 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+"""Checks for firmware files bundled into the gonk_firmware module."""
+
+import importlib.resources
+
+FIRMWARE_PY_PACKAGE = 'gonk_firmware'
+
+BUNDLED_FPGA_BINFILE_NAME = 'fpga.bin'
+BUNDLED_FPGA_BINFILE = ''
+BUNDLED_ELF_NAME = 'gonk.elf'
+BUNDLED_ELF = ''
+BUNDLED_BIN_NAME = 'gonk.bin'
+BUNDLED_BIN = ''
+
+# Check for a bundled Gonk firmware and FPGA bitstream file.
+try:
+ with importlib.resources.as_file(
+ importlib.resources.files(FIRMWARE_PY_PACKAGE) /
+ BUNDLED_FPGA_BINFILE_NAME) as bin_path:
+ if bin_path.is_file():
+ BUNDLED_FPGA_BINFILE = BUNDLED_FPGA_BINFILE_NAME
+
+ with importlib.resources.as_file(
+ importlib.resources.files(FIRMWARE_PY_PACKAGE) /
+ BUNDLED_ELF_NAME) as elf_path:
+ if elf_path.is_file():
+ BUNDLED_ELF = BUNDLED_ELF_NAME
+
+ with importlib.resources.as_file(
+ importlib.resources.files(FIRMWARE_PY_PACKAGE) /
+ BUNDLED_BIN_NAME) as bin_path:
+ if bin_path.is_file():
+ BUNDLED_BIN = BUNDLED_BIN_NAME
+except ModuleNotFoundError:
+ pass
+
+
+def bundled_bin_path():
+ try:
+ return importlib.resources.files(FIRMWARE_PY_PACKAGE).joinpath(
+ BUNDLED_BIN)
+ except ModuleNotFoundError:
+ return None
+
+
+def bundled_elf_path():
+ try:
+ return importlib.resources.files(FIRMWARE_PY_PACKAGE).joinpath(
+ BUNDLED_ELF)
+ except ModuleNotFoundError:
+ return None
+
+
+def bundled_fpga_binfile_path():
+ try:
+ return importlib.resources.files(FIRMWARE_PY_PACKAGE).joinpath(
+ BUNDLED_FPGA_BINFILE)
+ except ModuleNotFoundError:
+ return None
diff --git a/tools/gonk_tools/flash.py b/tools/gonk_tools/flash.py
index 7b7c2c1..2887116 100644
--- a/tools/gonk_tools/flash.py
+++ b/tools/gonk_tools/flash.py
@@ -19,9 +19,12 @@
import signal
import subprocess
import sys
+from typing import Optional
import pw_cli.color
+from gonk_tools.firmware_files import bundled_bin_path
+
_COLOR = pw_cli.color.colors()
DFU_SERIAL_STRING = 'STM32FxSTM32'
@@ -36,7 +39,7 @@
)
parser.add_argument(
- 'bin_file',
+ '--bin-file',
help='Binary to flash with dfu-util',
type=Path,
)
@@ -44,8 +47,8 @@
def main(
- bin_file: Path,
dfu_serial_string: str,
+ bin_file: Optional[Path],
) -> int:
"""Flash Gonk via dfu-util."""
@@ -53,8 +56,12 @@
if not dfu_util_binary or not Path(dfu_util_binary).is_file():
raise FileNotFoundError('Unable to find "dfu-util"')
- if not bin_file.is_file():
- raise FileNotFoundError(f'Unable to find the file "{bin_file}"')
+ if not bin_file:
+ bin_file = bundled_bin_path()
+
+ if not bin_file or not bin_file.is_file():
+ raise FileNotFoundError(
+ f'Unable to find the binary file to flash: "{bin_file}"')
dfu_flash_args = [
dfu_util_binary,
diff --git a/tools/gonk_tools/write_fpga.py b/tools/gonk_tools/write_fpga.py
index fd879f7..55c3653 100644
--- a/tools/gonk_tools/write_fpga.py
+++ b/tools/gonk_tools/write_fpga.py
@@ -14,7 +14,6 @@
"""Write a binary over serial to provision the Gonk FPGA."""
import argparse
-import importlib.resources
from itertools import islice
import logging
import operator
@@ -38,6 +37,12 @@
)
from gonk_tools.gonk_log_stream import GonkLogStream
+from gonk_tools.firmware_files import (
+ BUNDLED_ELF,
+ BUNDLED_FPGA_BINFILE,
+ bundled_elf_path,
+ bundled_fpga_binfile_path,
+)
_ROOT_LOG = logging.getLogger()
_LOG = logging.getLogger('host')
@@ -45,19 +50,6 @@
_COLOR = pw_cli.color.colors()
-_BUNDLED_FPGA_BINFILE = ''
-_BUNDLED_FPGA_BINFILE_NAME = 'toplevel.bin'
-
-# Check for a bundled FPGA bitstream file.
-try:
- with importlib.resources.as_file(
- importlib.resources.files('gonk_fpga') /
- _BUNDLED_FPGA_BINFILE_NAME) as bin_path:
- if bin_path.is_file():
- _BUNDLED_FPGA_BINFILE = _BUNDLED_FPGA_BINFILE_NAME
-except ModuleNotFoundError:
- pass
-
def _parse_args():
parser = argparse.ArgumentParser(
@@ -259,7 +251,7 @@
except serial.SerialException:
self.alive = False
self.console.cancel()
- raise # XXX handle instead of re-raise?
+ raise
def load_bitstream_file(bitstream_file: Path) -> bytes:
@@ -267,11 +259,13 @@
bitstream_bytes = b''
if str(bitstream_file) == 'DEFAULT':
- if not _BUNDLED_FPGA_BINFILE:
+ if not BUNDLED_FPGA_BINFILE:
raise FileNotFoundError('No default bitstream file is available.')
- bitstream_path = importlib.resources.files('gonk_fpga').joinpath(
- _BUNDLED_FPGA_BINFILE)
+ bitstream_path = bundled_fpga_binfile_path()
+ if not bitstream_path:
+ raise FileNotFoundError('No default bitstream file is available.')
+
bitstream_bytes = bitstream_path.read_bytes()
else:
if not bitstream_file.is_file():
@@ -322,6 +316,13 @@
) -> int:
"""Write a bitstream file over serial while monitoring output."""
+ if not databases and BUNDLED_ELF:
+ databases = []
+ elf_file = bundled_elf_path()
+ if elf_file:
+ databases.append(
+ pw_tokenizer_database.load_token_database(elf_file))
+
if not logfile:
# Create a temp logfile to prevent logs from appearing over stdout. This
# would corrupt the prompt toolkit UI.