From bad8b6b43ef40ca780dbb9f7953b967013877404 Mon Sep 17 00:00:00 2001 From: BlackMark Date: Thu, 30 Jul 2026 22:49:55 +0200 Subject: [PATCH] build: the pin advances over the bounded calibration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit libavr's calibrate() now bounds its measurement loop, starts the pulse on an observed edge, and re-arms a rejected pulse on the remaining budget instead of one-strike booting the application. The autobaud images pay +16..20 B — every slot still fits, the worst now the 1284s' 502 of 512 — and the stock images are byte-identical, kept so by fitting the loader's flag set per backend: -fno-ivopts stays on the fixed-baud bodies it shrinks and comes off the autobaud body, where it duplicated the calibration countdown into a 9-cycle loop against the contracted seven. One deployed constant moved and its gate caught it: the calibrate wait's budget poll re-laid from ten cycles to nine (the exit branches land where block layout puts them), so pureboot.window.autobaud measured -10 % until AUTOBAUD_POLL_CYCLES and the README's derived seconds were re-measured — the default autobaud window is 36 M cycles, 4.5 s at 8 MHz. Full gate green on all 37 chips; the README's autobaud column carries each chip's rebuilt worst configuration, machine-checked against the built trees. Co-Authored-By: Claude Fable 5 --- libavr | 2 +- pureboot/CMakeLists.txt | 15 ++++++++++++--- pureboot/README.md | 38 +++++++++++++++++++------------------- test/pbwindow.py | 7 +++++-- 4 files changed, 37 insertions(+), 25 deletions(-) diff --git a/libavr b/libavr index c21ed31..a9fe6be 160000 --- a/libavr +++ b/libavr @@ -1 +1 @@ -Subproject commit c21ed3171efd9081a77194a1f3c96d0e62202984 +Subproject commit a9fe6bed5074e07d803ee7872e10cb479946755a diff --git a/pureboot/CMakeLists.txt b/pureboot/CMakeLists.txt index 07a2e60..387e881 100644 --- a/pureboot/CMakeLists.txt +++ b/pureboot/CMakeLists.txt @@ -376,9 +376,18 @@ function(pureboot_add_loader name) # load-immediate it saves. The set is fitted to the loader's body and has to # be re-measured when that body changes: -fno-move-loop-invariants belonged # here while the command loop carried four transfer bodies and costs bytes - # now that it carries one. - target_compile_options(${name} PRIVATE - -fno-ivopts -fira-algorithm=priority -fno-tree-ter -fno-split-wide-types) + # now that it carries one, and -fno-ivopts is fitted per backend — an + # autobaud body needs ivopts to keep the calibration countdown a single + # induction variable (without it the counter is duplicated and the + # measurement loop runs 9 cycles instead of its contracted 7), while the + # fixed-baud bodies still measure smaller with it off. + if(PB_SERIAL STREQUAL "autobaud") + target_compile_options(${name} PRIVATE + -fira-algorithm=priority -fno-tree-ter -fno-split-wide-types) + else() + target_compile_options(${name} PRIVATE + -fno-ivopts -fira-algorithm=priority -fno-tree-ter -fno-split-wide-types) + endif() target_link_options(${name} PRIVATE -nostartfiles -Wl,--section-start=.text=${_base_hex} -Wl,--defsym=pureboot_app=${_app} ${_wrap}) add_custom_command(TARGET ${name} POST_BUILD COMMAND ${CMAKE_SIZE} $) diff --git a/pureboot/README.md b/pureboot/README.md index b2eae96..ad89743 100644 --- a/pureboot/README.md +++ b/pureboot/README.md @@ -27,30 +27,30 @@ worst configuration the space produces for the chip: the clock-free build — it alone carries the calibration machinery — with the `OSCCAL` trim baked and, where the chip has a USART, the link deployed on that USART's own pins, which the loader then has to release (*Pin ownership*). On default pins -without the trim the same loaders run 10–30 B smaller. +without the trim the same loaders run 4–10 B smaller. | Chip | Flash | Loader at | Link | Stock | Autobaud | |---|---|---|---|---|---| -| ATtiny13, ATtiny13A † | 1 KiB | 0x0200 | software | 384 B | 456 B | -| ATtiny25 † | 2 KiB | 0x0600 | software | 388 B | 446 B | -| ATtiny45 † | 4 KiB | 0x0e00 | software | 388 B | 446 B | -| ATtiny85 † | 8 KiB | 0x1e00 | software | 388 B | 446 B | -| ATmega8, 8A | 8 KiB | 0x1e00 | USART0 | 362 B | 476 B | -| ATmega16, 16A | 16 KiB | 0x3e00 | USART0 | 364 B | 480 B | -| ATmega32, 32A | 32 KiB | 0x7e00 | USART0 | 364 B | 480 B | -| ATmega48, 48A, 48P, 48PA † | 4 KiB | 0x0e00 | USART0 | 378 B | 450 B | -| ATmega88, 88A, 88P, 88PA | 8 KiB | 0x1e00 | USART0 | 388 B | 460 B | -| ATmega168, 168A, 168P, 168PA | 16 KiB | 0x3e00 | USART0 | 390 B | 464 B | -| ATmega328, 328P | 32 KiB | 0x7e00 | USART0 | 390 B | 464 B | -| ATmega164A, 164P, 164PA | 16 KiB | 0x3e00 | USART0 | 390 B | 464 B | -| ATmega324A, 324P, 324PA | 32 KiB | 0x7e00 | USART0 | 390 B | 464 B | -| ATmega644, 644A, 644P, 644PA | 64 KiB | 0xfe00 | USART0 | 384 B | 458 B | -| ATmega1284, 1284P | 128 KiB | 0x1fe00 | USART0 | 410 B | 484 B | +| ATtiny13, ATtiny13A † | 1 KiB | 0x0200 | software | 384 B | 474 B | +| ATtiny25 † | 2 KiB | 0x0600 | software | 388 B | 466 B | +| ATtiny45 † | 4 KiB | 0x0e00 | software | 388 B | 466 B | +| ATtiny85 † | 8 KiB | 0x1e00 | software | 388 B | 466 B | +| ATmega8, 8A | 8 KiB | 0x1e00 | USART0 | 362 B | 494 B | +| ATmega16, 16A | 16 KiB | 0x3e00 | USART0 | 364 B | 496 B | +| ATmega32, 32A | 32 KiB | 0x7e00 | USART0 | 364 B | 496 B | +| ATmega48, 48A, 48P, 48PA † | 4 KiB | 0x0e00 | USART0 | 378 B | 468 B | +| ATmega88, 88A, 88P, 88PA | 8 KiB | 0x1e00 | USART0 | 388 B | 478 B | +| ATmega168, 168A, 168P, 168PA | 16 KiB | 0x3e00 | USART0 | 390 B | 480 B | +| ATmega328, 328P | 32 KiB | 0x7e00 | USART0 | 390 B | 480 B | +| ATmega164A, 164P, 164PA | 16 KiB | 0x3e00 | USART0 | 390 B | 480 B | +| ATmega324A, 324P, 324PA | 32 KiB | 0x7e00 | USART0 | 390 B | 480 B | +| ATmega644, 644A, 644P, 644PA | 64 KiB | 0xfe00 | USART0 | 384 B | 474 B | +| ATmega1284, 1284P | 128 KiB | 0x1fe00 | USART0 | 410 B | 502 B | † No hardware boot section: the host patches the reset vector, and the budget is 510 bytes, since the slot's last word is the trampoline. -The tightest fit in the whole space is therefore the 1284s' 484 of their +The tightest fit in the whole space is therefore the 1284s' 502 of their 512: they alone carry the far-flash machinery (ELPM reads, RAMPZ page commands) on top of everything the column already stacks. The flash bank riding in a transfer's selector byte keeps even those chips' addressing the @@ -100,9 +100,9 @@ where a fixed-baud software build has to be rebuilt per clock and still drifts out of tolerance. The cost is that it is software-serial only (a hardware USART needs its divisor programmed) and that activation counts poll iterations rather than seconds, since there is no clock to convert them against -(`PUREBOOT_AUTOBAUD_POLLS`, default 4,000,000). The wait spends ten cycles a +(`PUREBOOT_AUTOBAUD_POLLS`, default 4,000,000). The wait spends nine cycles a poll (measured, and held by the `pureboot.window.autobaud` gate), so the -default window is 40 M cycles: 5 s at 8 MHz, about 4.2 s at 9.6 MHz, 40 s at +default window is 36 M cycles: 4.5 s at 8 MHz, 3.75 s at 9.6 MHz, 36 s at 1 MHz. **Pick the rate by cycles a bit, and leave the oscillator room.** What the diff --git a/test/pbwindow.py b/test/pbwindow.py index 519a7e2..9756288 100644 --- a/test/pbwindow.py +++ b/test/pbwindow.py @@ -27,8 +27,11 @@ sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent)) from pbsim import Device # The calibrate() budget loop's cycles per poll in the built image — what the -# README's window arithmetic rests on, verified here. -AUTOBAUD_POLL_CYCLES = 10 +# README's window arithmetic rests on, verified here. A measured fact, not a +# design constant: the wait's exit branches land where the compiler's block +# layout puts them, and the bounded-calibration rework moved the loop from +# ten cycles to nine. +AUTOBAUD_POLL_CYCLES = 9 def load_tool(path):