pureboot 7: the same features in fewer words on every chip

Four cuts, none touching what the loader can do. The entry stub stops
re-doing the reset logic's own SP write where the datasheet guarantees
RAMEND (stack::hardware — the classic megas keep theirs). The autobaud
unit moves into GPIOR2:GPIOR1 wherever the chip has the pair: one-word
accesses, no RAM object, and the host's measured-clock peek follows it
by version and geometry. 'J' rides the unified decode, carrying a
selector it ignores so its address is the same two reads as every other
command — the tool sends the bare form to older residents. run_app stops
insisting on a body of its own. The fleet lands at 358–410 B stock and
438–474 B autobaud; the tightest image in the space — the 1284s'
autobaud on a USART's own pins with the OSCCAL trim — drops from 510 to
484 of its 512. Every chip's suite is green on the wire that changed,
and the README's table is machine-checked against the built images.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-28 17:02:38 +02:00
parent a3ea099105
commit 8f6319c068
9 changed files with 164 additions and 103 deletions

View File

@@ -1,7 +1,10 @@
# Asserts the autobaud loader's measured unit is the first RAM object: the
# host tool reads the bit period from ram_start (--info's measured clock), so
# the unit's address is wire contract. Run as
# cmake -DOBJDUMP=... -DELF=... -DRAM_START=<data address> -P check_unit.cmake
# Asserts the autobaud loader's measured unit sits where the host will read
# it (--info's measured clock — the address is wire contract). Two homes: on
# a chip with the GPIOR pair the unit lives there and the image must carry no
# RAM word for it at all; elsewhere it is the first RAM object at SRAM start.
# Run as
# cmake -DOBJDUMP=... -DELF=... -DRAM_START=<data address> [-DGPIOR=<data address>]
# -P check_unit.cmake
execute_process(COMMAND ${OBJDUMP} -t ${ELF} OUTPUT_VARIABLE _syms RESULT_VARIABLE _res)
if(NOT _res EQUAL 0)
@@ -9,7 +12,17 @@ if(NOT _res EQUAL 0)
endif()
# The symbol line: "00800100 l O .noinit 00000002 <mangled>unit_E".
string(REGEX MATCH "\n0*([0-9a-f]+)[^\n]+[ \t][^ \t\n]*unit_[^ \t\n]*\n" _line "${_syms}")
string(REGEX MATCH "\n0*([0-9a-f]+)[^\n]+[ \t][^ \t\n]*unit_E\n" _line "${_syms}")
if(GPIOR)
if(_line)
message(FATAL_ERROR "unit_ RAM symbol present although the unit's home is GPIOR ${GPIOR} — "
"the host peeks the pair, and a RAM copy would be dead weight")
endif()
message(STATUS "no unit_ RAM object — the unit lives in the GPIOR pair at ${GPIOR}")
return()
endif()
if(NOT _line)
message(FATAL_ERROR "no unit_ symbol in ${ELF} — is this the autobaud loader?")
endif()

View File

@@ -64,8 +64,9 @@ def main():
for needed in ("version", "signature", "fuses", "verify:", "stays"):
if needed not in out:
fail(f"{label}: session output lacks {needed!r}\n{out}")
# The measured clock, decoded from the unit at ram_start. The
# runner's clock is exact, so the figure must land inside the
# The measured clock, decoded from the unit at whichever home this
# version keeps it in. The runner's clock is exact, so the figure
# must land inside the
# encoding's own envelope: the loader floors the bit period to
# 4-cycle spin granules after an 8-cycle discount, and the edge
# poll can shave a few cycles more — one granule of slack below