build: the pin advances over the bounded calibration
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 <noreply@anthropic.com>
This commit is contained in:
2
libavr
2
libavr
Submodule libavr updated: c21ed3171e...a9fe6bed50
@@ -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.
|
||||
# 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} $<TARGET_FILE:${name}>)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user