The reading pass over this repo found the tiers disagreeing with themselves,
and every fix here was measured.
**The turn-around guard is real code.** `tsb_asm` and `tsb_tricks` wrote
`for (std::uint8_t guard = 46; guard; --guard) ;` between taking the one-wire
line and the first UDR0 store, under a comment naming it a turn-around guard.
It has no side effect, so GCC deleted it - `sts UCSR0B` went straight to
`sts UDR0` - while the hand-written oracle spends six bytes on that wait and
libavr's own half-duplex spends them through `delay::cycles`. Two of four
tiers described a feature they did not have, which made the size gradient a
comparison between different loaders. `avr::delay::cycles<one bit time>()`
bottoms out in asm and cannot be deleted.
**The entry belongs to the library, and hand-rolling it was expensive.** Three
tiers wrote their own naked `.vectors` stub with `asm volatile("clr
__zero_reg__")` - which design.md fences to libavr and never a port, and which
`tsb_tricks` denied having in its own title line. `avr::startup::entry` also
keeps the body `noinline` for a stated reason: avr-ld must not shrink a
`.vectors` section, so a loader inlined into one forfeits call relaxation
everywhere. `tsb_pure` came out **836 -> 734** bytes for that alone.
`stack::hardware` - the reset value this part guarantees, with the write kept
where a part does not - saved another four, which is what let `tsb_asm` afford
the guard it had been four bytes short of. It fills its 512-byte section
exactly now, with the whole feature set.
**`tsb_pure` had no receive timeout.** Its `rx()` was `read_blocking()`, so a
silent host wedged the password gate and the command loop forever - the one
fix the oracle's own header lists by name, and one the other three tiers
implement. It is bounded now, and 0-on-silence falls through every compare as
theirs does.
Three gates could pass without proving anything. `sizes.py check-readme`
reported a match when every row's lookup missed; `check_size.cmake` used
`CMAKE_MATCH_1` without checking the match succeeded, which is the guard its
sibling `check_unit.cmake` has and it is the size gate; `check_pi.py` raised
IndexError instead of reporting a position-independence break that changed the
image's length. And `check.sh` spelled the 37-chip list a second time beside
make_presets.py, where a chip added to one and missed in the other is a
silently unbuilt chip - it reads the presets now, and produces the same 37 and
12.
tsbtest.py gains the scenario nothing covered: a wrong password byte must
neither activate the loader nor reach the emergency erase behind it. Red-green
on a tier with the refusal removed.
Smaller, all measured or checked: the signature is `hw::db.signature` in every
tier as the page size and EEPROM end beside it already were; `act_min` derives
from the clock; pureboot.py's `rjmp` helpers refuse a part past rjmp's
4096-word reach rather than silently folding an offset (unreachable today, the
ATtiny85 sits exactly on it); the host tool calls space 2 `data` as the wire
and the loader do; `.clangd` strips the fifth GCC-only flag the build passes;
pbrig's bitclock guard reads its own ladder; pbreloc's unexplained retry is
gone, the write being reliable on five runs without it; and the four tier
sizes live in oracle/README.md's table instead of four file headers and a
CMake comment.
`--poke` before `--peek` turned out to be right - pbtest.py round-trips a poke
through the peek behind it - so the parser order and README say so now.
Every chip green, the README size table matching every image.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
69 lines
2.4 KiB
Bash
Executable File
69 lines
2.4 KiB
Bash
Executable File
#!/bin/bash
|
|
# The port's gate: every chip's generated workflow - build, size matrix, and
|
|
# the simulator-driven protocol suites. --full adds the reflect-spot builds
|
|
# (libavr's rule: reflect compiles are bounded to its spot set, never the
|
|
# full matrix) and swaps the compact size matrix for the exhaustive
|
|
# clock x baud x backend cross product. libavr resolves from the `libavr/`
|
|
# submodule; LIBAVR_ROOT overrides it for a working tree.
|
|
set -e
|
|
cd "$(dirname "$0")/.."
|
|
|
|
full=0
|
|
[[ "$1" == "--full" ]] && { full=1; shift; export PUREBOOT_FULL_MATRIX=1; }
|
|
|
|
# The chip lists come from the presets rather than being spelled a second time
|
|
# here: a chip added to make_presets.py and missed in a copy of its list would
|
|
# be a gate that silently never builds it, which is the one failure mode a gate
|
|
# cannot report. tools/make_presets.py is the single source, CMakePresets.json
|
|
# is its output, and this reads that.
|
|
readarray -t WORKFLOWS < <(python3 -c '
|
|
import json, sys
|
|
presets = json.load(open("CMakePresets.json"))["workflowPresets"]
|
|
print("\n".join(p["name"] for p in presets))')
|
|
if ((${#WORKFLOWS[@]} == 0)); then
|
|
echo "no workflow presets in CMakePresets.json - run tools/make_presets.py" >&2
|
|
exit 1
|
|
fi
|
|
CHIPS=()
|
|
REFLECT_SPOT=()
|
|
for workflow in "${WORKFLOWS[@]}"; do
|
|
case $workflow in
|
|
*-generated) CHIPS+=("${workflow%-generated}") ;;
|
|
*-reflect) REFLECT_SPOT+=("${workflow%-reflect}") ;;
|
|
esac
|
|
done
|
|
|
|
# Every preset runs even after one goes red, and the gate fails at the end
|
|
# naming all of them: stopping at the first failure turns a red - a stale size
|
|
# canary above all - into an alibi for every chip behind it, and a loader can
|
|
# ship on a chip this gate has not compiled since.
|
|
red=()
|
|
run_preset() {
|
|
echo "==== $1 ===="
|
|
cmake --workflow --preset "$1" "${@:2}" || red+=("$1")
|
|
}
|
|
|
|
for chip in "${CHIPS[@]}"; do
|
|
run_preset "$chip-generated" "$@"
|
|
done
|
|
|
|
if ((full)); then
|
|
for chip in "${REFLECT_SPOT[@]}"; do
|
|
run_preset "$chip-reflect" "$@"
|
|
done
|
|
fi
|
|
|
|
if ((${#red[@]})); then
|
|
printf '==== red presets ====\n' >&2
|
|
printf ' %s\n' "${red[@]}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Every tree is freshly built now - the one moment the README's size table
|
|
# can be held to what the images measure (a per-preset ctest sees only its
|
|
# own chip; the table needs all of them, and ungated it drifts: a
|
|
# common-code shave moves every row at once with nothing over budget).
|
|
python3 tools/sizes.py check-readme
|
|
|
|
echo "check: every chip green"
|