Skip to content

build_release_files: predict translation sizes instead of building them on PRs - #2

Open
lynt-smitka wants to merge 27 commits into
mainfrom
ci-language-predict
Open

lynt-smitka wants to merge 27 commits into
mainfrom
ci-language-predict

Conversation

@lynt-smitka

Copy link
Copy Markdown

Test run in the fork, not for upstream in this shape.

Rebased onto main now that adafruit#11314 is in: flash_usage no longer falls back to the ld
table, it just reads firmware.size.json, which after adafruit#11314 exists on ten ports
instead of six.

What to read out of this run:

  • Predict <board> for <lang>: ... -> skip|build — the prediction and what it decided
  • Predict check <board> for <lang>: predicted X, actual Y, error N — only where a
    build did happen, so the error can be checked against the real number
  • board job durations against run 34106118825, which is the same code as main

zephyr-cp is excluded: it has no make target for the translation generators and the
generated files live under circuitpython-prefix, not in the build dir.

4RH1T3CT0R7 and others added 14 commits September 5, 2026 01:05
The last buffer was padded by length_read % 4 bytes instead of
4 - length_read % 4, so a 5 or 7 byte tail still ended unaligned.
Round caller-supplied buffer halves down to a multiple of 4 so the
pad always fits, and add a unix coverage test.
C5 is the first dual-band ESP32. It differs from c6 in ways that build
cleanly but fail at run time:

- bootloader flash offset is 0x2000, not 0x0
- ADC1 channels start at GPIO1, not GPIO0
- efuse MAC registers are named like c61, not c6
- libphy.a references _rom_eco_version, so rom.version.ld must link
- USB-Serial-JTAG pads (GPIO13/14) are cleared by gpio_ll_func_sel(),
  so reset_all_pins() must skip them along with the flash/PSRAM MSPI
  pins and VDD_SPI
- boot button is GPIO28, not GPIO9 or GPIO0
- the 48MHz crystal leaves only 40/80/160/240MHz CPU valid
- TWAI is the newer TWAI-FD, so canio is off pending a backend
8MB flash, 8MB quad PSRAM, WS2812B on GPIO27, UART0 on GPIO11/12.

Flash mode is dio: qio locks the CPU during bootloader startup on this
board. PSRAM stays qio, which is the only mode C5 offers.
The comment was copied from the c6 block. The 8MB flash has a 2048K
app partition, and enabling both leaves 42 KB free.
The C5 has an I2S peripheral and the DevKitC breaks out 19 GPIOs, so
external I2S hardware works the same as on the S3 DevKitC, which ships
with these on and has no onboard audio either.

Costs 78160 bytes, leaving 42928 free in the 2048K app partition.
Seven ports never write firmware.size.json, so build_release_files.py cannot
tell whether the en_US build left room and builds all 17 languages for every
board of those ports on every pull request.

They cannot use this script as it stands: it reads the region size out of a
linker script, which needs the size to be a literal there. raspberrypi writes
LENGTH = firmware_size, a symbol the board can override; broadcom, cxd56 and
silabs link a script from a submodule or an SDK; zephyr-cp has no script of its
own at all.

The linker map has the same numbers with the symbols already resolved, and
every one of these ports either writes a map already or is one flag away from
it. So read the Memory Configuration table when the file has one, and keep the
linker script path for the ports that pass one. The region name is FLASH_FIRMWARE
or FLASH, or --region for anything else.

Wired into raspberrypi, renode, mimxrt10xx, broadcom, cxd56 and silabs, which
only needed -Wl,-Map added. A missing map or an unknown region name prints what
was found and writes nothing, so the language skip falls back to today's
behaviour instead of failing the build.

zephyr-cp still needs doing; its Makefile has none of the toolchain variables.
Its makefile has none of the toolchain variables the other ports get from
circuitpy_mkenv.mk, so there is no size(1) to pipe in. There does not need to
be: what the image occupies in flash is the size of the binary, which is how
ports/espressif has always measured it. Pass --image and the region still comes
from the map.

Measured on rpi_pico: 502256 bytes used of a 1040128 byte region, against the
502000 and 1040128 Zephyr prints itself -- the difference is the 256 byte
second stage, which the .bin includes and the FLASH region does not.
Rather than rounding each half of a caller-supplied buffer down to a
multiple of 4, reject lengths that are not a multiple of 8 up front so
the buffer the user passes is the buffer that gets used.
…padding

audiocore: fix WaveFile 8-bit sample padding at end of file
espressif: add ESP32-C5 support and the ESP32-C5-DevKitC-1-N8R8 board
Neither has a flash firmware region to measure against. cxd56 links the image
into RAM through the Spresense SDK script, so its map has one region and it is
called ram; broadcom boots kernel.img off an SD card and its regions are RAM and
READONLY. Nothing there constrains the firmware, so there is no headroom to
compute and the step only printed what it could not find.
atmel-samd, analog, litex, nordic and stm were still passing their linker
script, which left the script answering the same question two ways. Their maps
carry the same numbers -- the script computed them, ld resolved them -- so point
those five at the map as well and the linker script parser, the K and M suffix
handling and the eval that needed them all come out.

What a port supplies is now one rule: the map for the region, size(1) or --image
for what is used.
…fallback

build_release_files: read the linker memory table when size.json is missing
A display test with scheduled captures holds its last frame in
`while True: time.sleep(1)`, so native_sim never exits on its own and
wait_until_done() waits out the whole wall-clock timeout: every capture
test takes exactly its duration, 346 s for the 26 display tests.

Pass the duration as -stop_at so the simulator ends the run itself. In
-no-rt the idle loop is fast-forwarded: gifio 60 s -> 2 s, the display
suite 346 s -> 19 s, goldens unchanged. Realtime tests are left alone.
lynt-smitka and others added 12 commits September 7, 2026 14:49
GitHub allows 256 configurations per matrix and build-boards.yml runs
one matrix per port. espressif reached 257 boards, so every full build
fails with "Strategy for job 'board' produced 257 configurations".

ci_set_matrix.py now splits such a port into equal parts listed like
ports (espressif-1, espressif-2) and a "split_ports" map lets build.yml
hand the real port name to build-boards.yml, so the toolchain setup is
unchanged. Ports below the limit and the per-board job names are not
affected.

Fixes adafruit#11324
Each part covered the whole alphabet, so finding a board meant looking
at every part. Slice the sorted list into consecutive runs instead.
The iLabs Challenger RP2040 NFC is an RP2040 board in the Adafruit Feather
form factor with 8MB of flash and an on-board NXP PN7150 NFC controller. It
is the only Challenger RP2040 variant that has no CircuitPython board
definition, and the vendor currently ships no CircuitPython support for it.

Pin mapping is taken from the official pinout sheet (98-00241-1 V0.1) and
cross-checked against iLabs' Arduino variant (arduino-pico,
variants/challenger_2040_nfc/pins_arduino.h). USB VID/PID 0x2e8a/0x1036 is
the pair iLabs registered for this board in that same core.

The PN7150 is wired to a second I2C bus on GPIO10/11, and that bus has no
external pull-up resistors: iLabs' Arduino core enables the RP2040's internal
pull-ups in TwoWire::begin() instead. This board therefore defines
CIRCUITPY_I2C_ALLOW_INTERNAL_PULL_UP, following the existing espressif idiom,
and this adds support for that option to the rp2 port.

Two details are specific to rp2. The pull-ups are enabled only after the pins
are handed to the I2C peripheral, because shared_module_bitbangio_i2c_construct()
runs first and leaves both pads as open-drain outputs with no pull (see
scl_release() and sda_read()); gpio_set_function() deliberately does not touch
pad pull settings, so anything set earlier would already be gone. And the
CIRCUITPY_REQUIRE_I2C_PULLUPS timing check is compiled out when the internal
pull-ups are in use: RP2xxx pull-ups are weak, and that check waits a fixed 3us
regardless of frequency, so it passes even at bus speeds the internal pull-ups
cannot sustain -- measured on this board, it passes 300/300 at both 100kHz and
400kHz although only 100kHz actually works.

Tested on hardware: enumerates as CIRCUITPY, NeoPixel status LED on GPIO14
and LED on GPIO24 confirmed, and the on-board PN7150 answers NCI at 100kHz
(CORE_RESET_RSP 40:00:03:00:11:01). NDEF was read from NTAG/Ultralight,
MIFARE Classic and ISO15693 tags, and FeliCa cards were identified, through
that bus.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Dan Halbert <halbert@halwitz.org>
ci: split a port with more than 256 boards into several matrix jobs
zephyr-cp tests: let a capture test stop the simulator at its duration
Add a comment regarding pull-up checks for I2C.
…em on PRs

On pull requests the translations other than en_US are built only to
prove that they still fit in flash. The compiled code is identical for
every translation; only three generated data files differ: the
compressed strings, the compression dictionary and the terminal font.

When the first language leaves less than 10 KB of headroom, run only
the generators (the make targets for translations-<lang>.c and
autogen_display_resources-<lang>.c, about 4 s, no compiler), count the
bytes of the generated tables and predict the flash usage. Only
translations predicted within LANGUAGE_MARGIN (1 KB) of the limit, or
whose build configuration differs (clean build), are really built.
LANGUAGE_PREDICT=dryrun builds everything and prints the prediction
error; LANGUAGE_PREDICT=off restores the old behaviour.

Measured on feather_m4_can, pybadge and metro_m4_express with every
language (41 predictions): error -5..+174 bytes, almost always an
over-estimate. On metro_m4_express the prediction flagged fr as not
fitting; the real build then overflowed by 56 bytes. feather_m4_can
board job: 443 s -> 228 s; the remainder is the two clean builds.
Push and release builds are unchanged.
prediction, predict_flash and usage were three near-identical names for
three different things, and two of them were tuples read by index. Name
them by their role instead: baseline_flash and flash_region for what the
first language's build establishes, baseline_translation_bytes and
translation_growth for the translation data, predicted_flash and
actual_flash for the two sides of the check. flash_usage() returns two
values so every caller unpacks them.

Say flash size in the messages too:

    Predicted flash size for itsybitsy_m0_express es: 253527 of 253696
    bytes (169 free, +1479 vs en_US) -> build
    Flash size check itsybitsy_m0_express es: predicted 253527,
    actual 253504, error +23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants