pureboot: a 512-byte slot on every chip, the 1284s included

The word-addressed 1284s were the one family deploying in a 1 KiB slot,
because the far-flash machinery (ELPM reads, RAMPZ page commands, a
word-addressed wire) did not fit 512 B. It does now: 478 B stock, 494 B in
the heaviest configuration the build can produce. They take the 644s'
geometry, where the smallest boot section holds the resident slot and its
staging slot together.

Most of the saving is one restructure. The info block and a flash read are
the same act, so giving all four streamed commands one address-and-count
path leaves exactly one call site for the flash streamer: it inlines into
the never-returning command loop and its 24-bit cursor stops being saved
and restored around every transmit. Around it, the ack byte moved out of
line, the wire's byte pair is bit_cast into the word it already is, the
fuse loop ends on its count, the info block's in-slot offset is taken as
the one-byte relocation it is, and -fno-expensive-optimizations gives way
to -fno-move-loop-invariants -fno-tree-ter. Every chip shrank 14-18 B.

The size matrix grew the axes it was missing: the USART1 instance across
the whole clock ladder, and the shape a slow baud gives a software UART —
past 255 delay iterations libavr takes the 16-bit delay loop, which the
ladder default never selects and which was 4 B over the 1284's slot the
first time it was built.

The protocol fixture stopped deriving the loader entry from the flash
size; on the 1284s it had been jumping a slot low and reaching the loader
only because erased flash walked it up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 19:02:59 +02:00
parent 12ab035fd3
commit 9196ba5711
11 changed files with 191 additions and 159 deletions

View File

@@ -17,11 +17,11 @@
# software-UART cycle-floor static asserts re-check whatever is passed.
# Per-family geometry: flash/page/EEPROM sizes and the linker wrap the PC
# modulo needs, the loader slot (each chip's smallest boot sector — 1 KiB on
# the word-addressed 1284s), and the deployment defaults (crystal assumption
# on the megas, calibrated RC on the tinies). The USART flags mirror the
# hardware inventory the loader's own static asserts check (the plain 644 is
# the x4 family's one single-USART die, Atmel-2593).
# modulo needs, plus the deployment defaults (crystal assumption on the
# megas, calibrated RC on the tinies). The loader slot is 512 bytes on every
# chip. The USART flags mirror the hardware inventory the loader's own static
# asserts check (the plain 644 is the x4 family's one single-USART die,
# Atmel-2593).
set(_pb_has_usart 1)
set(_pb_has_usart1 0)
if(LIBAVR_MCU MATCHES "^attiny13a?$")
@@ -106,31 +106,25 @@ elseif(LIBAVR_MCU MATCHES "^atmega644(a|p|pa)?$")
elseif(LIBAVR_MCU MATCHES "^atmega1284p?$")
# 128 KiB: wire flash addresses are word addresses, reads go through
# ELPM, and the PC's modulo wrap exceeds what --pmem-wrap-around models.
# The slot is 1 KiB — this chip's own smallest boot sector; the far
# machinery cannot fit 512 B (see README.md).
# Its smallest boot section (512 words = 1 KiB) holds the loader and its
# staging slot together, the 644's geometry (see README.md).
set(_pb_flash 131072)
set(_pb_wrap "")
set(_pb_page 256)
set(_pb_hz 16000000)
set(_pb_eeprom 4096)
set(_pb_slot 1024)
set(_pb_limit 1024)
set(_pb_has_usart1 1)
else()
message(FATAL_ERROR "pureboot: no geometry for ${LIBAVR_MCU}")
endif()
if(NOT DEFINED _pb_slot)
set(_pb_slot 512)
endif()
set(_pb_slot 512)
math(EXPR _pb_base "${_pb_flash} - ${_pb_slot}")
math(EXPR _pb_base_hex "${_pb_base}" OUTPUT_FORMAT HEXADECIMAL)
# Patched-vector chips hand over through the trampoline word below the slot,
# which is also the slot's own last word — their budget is slot 2.
if(LIBAVR_MCU MATCHES "^atmega" AND NOT LIBAVR_MCU MATCHES "^atmega48")
set(_pb_app 0)
if(NOT DEFINED _pb_limit)
set(_pb_limit ${_pb_slot})
endif()
set(_pb_limit ${_pb_slot})
else()
math(EXPR _pb_app "${_pb_base} - 2")
math(EXPR _pb_limit "${_pb_slot} - 2")
@@ -294,15 +288,17 @@ function(pureboot_add_loader name)
add_executable(${name} ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/pureboot.cpp)
target_link_libraries(${name} PRIVATE libavr)
target_compile_definitions(${name} PRIVATE ${_defines})
# Codegen shaping for the loader TU only, worth ~40 B on every chip and
# what carries the far-flash 1284 build under 512. At -Os GCC otherwise
# rewrites the byte-stream loops' counters into end-pointer forms that
# cost registers (-fno-ivopts, -fno-split-wide-types), leaves register
# pressure on the table with the default allocator
# (-fira-algorithm=priority), and spends bytes on rewrites a
# straight-line loader gains nothing from.
# Codegen shaping for the loader TU only, worth 1436 B depending on the
# chip. At -Os GCC otherwise rewrites the byte-stream loops' counters into
# end-pointer forms that cost registers (-fno-ivopts,
# -fno-split-wide-types), leaves register pressure on the table with the
# default allocator (-fira-algorithm=priority), and keeps loop-invariant
# immediates and expression temporaries in registers
# (-fno-move-loop-invariants, -fno-tree-ter) — but every loop body here
# contains a call, so a register held across it costs more than the
# load-immediate it saves.
target_compile_options(${name} PRIVATE
-fno-ivopts -fira-algorithm=priority -fno-expensive-optimizations -fno-split-wide-types)
-fno-ivopts -fira-algorithm=priority -fno-move-loop-invariants -fno-tree-ter -fno-split-wide-types)
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}>)