From a5b42bc02f7a237e4664905d2d24295fa7bfe2e0 Mon Sep 17 00:00:00 2001 From: BlackMark Date: Wed, 22 Jul 2026 23:32:28 +0200 Subject: [PATCH] test: the exhaustive clock x baud x backend size matrix Every plausible oscillator against every rate it reaches against every backend, on one chip per size-bearing class, under --full only. The baud ladder becomes a reachability predicate the enumeration filters on, so an unreachable point drops out instead of aborting the configure. Co-Authored-By: Claude Opus 4.8 (1M context) --- CMakeLists.txt | 102 ++++++++++++++++++++++++++++------------ pureboot/CMakeLists.txt | 63 +++++++++++++++---------- pureboot/README.md | 7 +++ tools/check.sh | 6 ++- 4 files changed, 123 insertions(+), 55 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e286e04..882367f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -233,11 +233,11 @@ if(PROJECT_IS_TOP_LEVEL) # The size matrix: every configuration axis that could move the image # size — the serial backend (different code), the USART instance # (different registers), the clock (different constants), and the baud - # through the two shapes its bit timing takes — each combination must - # still fit the chip's slot budget. Pins are size-neutral (port and bit - # are immediate operands) and the timeout is a constant, so neither adds - # an axis. The stock build is one point of this matrix and already has - # its test. + # through the shapes its bit timing takes — each combination must still + # fit the chip's slot budget. Pins are size-neutral (port and bit are + # immediate operands) and the timeout is a constant, so neither adds an + # axis. The stock build is one point of this matrix and already has its + # test. function(pureboot_size_variant name) pureboot_add_loader(${name} ${ARGN}) add_test(NAME ${name}.size @@ -245,42 +245,86 @@ if(PROJECT_IS_TOP_LEVEL) -DLIMIT=${PUREBOOT_LIMIT} -P ${CMAKE_CURRENT_SOURCE_DIR}/test/check_size.cmake) endfunction() + # One point of the exhaustive matrix, named from its resolved parameters + # so the enumeration cannot collide with itself. Unreachable rates drop + # out here rather than aborting the configure. + function(pureboot_matrix_point hz baud link) + if(link STREQUAL "software") + pureboot_baud_feasible(${hz} ${baud} 1 _ok) + set(_args SERIAL software) + else() + pureboot_baud_feasible(${hz} ${baud} 0 _ok) + set(_args USART ${link}) + endif() + if(_ok) + pureboot_size_variant(pbm_${hz}_${baud}_${link} CLOCK ${hz} BAUD ${baud} ${_args}) + endif() + endfunction() + # Clock points: the shipped-fuse floor (CKDIV8), the calibrated RC, and # the crystal the stock build assumes (the tiny13's ladder is its own RC # menu — it has no crystal option). if(LIBAVR_MCU MATCHES "^attiny13") set(_matrix_clocks 1200000 4800000 9600000) + set(_full_clocks 128000 600000 1200000 4800000 9600000) else() set(_matrix_clocks 1000000 8000000 16000000) + set(_full_clocks 128000 1000000 1843200 2000000 3686400 4000000 7372800 8000000 + 11059200 12000000 14745600 16000000 18432000 20000000) + endif() + + # The exhaustive cross product: every clock a deployment plausibly runs + # — the internal oscillators, the shipped CKDIV8 floor, the plain + # crystals and the UART crystals — against every rate, against every + # backend. Beyond the ladder the list carries the slow rates a + # sub-megahertz oscillator is left with, which no ladder rate reaches + # (16000 Bd is the only rate the 128 kHz oscillator holds exactly); at + # the fast clocks those same rates also select the software UART's + # 16-bit _delay_loop_2 bit spin (two words more setup at each of its five + # sites), the largest image the space produces and a shape the ladder + # default — always the *fastest* rate a clock reaches — never picks. + # + # Bounded to one chip per size-bearing class: flash addressing (the + # word-addressed 1284), hand-over shape (the patched vector on the tinies + # and m48s), page size, and USART inventory. Everything else in the image + # is chip-independent code, so a further chip buys builds and no + # coverage; every chip outside the set carries the compact matrix. + get_property(_full_bauds GLOBAL PROPERTY PUREBOOT_BAUD_LADDER) + list(APPEND _full_bauds 16000 4800 2400 1200) + set(_matrix_spot attiny13a attiny85 atmega48pa atmega8a atmega168pa + atmega328p atmega164a atmega644a atmega1284p) + if(DEFINED ENV{PUREBOOT_FULL_MATRIX} AND LIBAVR_MCU IN_LIST _matrix_spot) + foreach(_matrix_hz IN LISTS _full_clocks) + foreach(_matrix_baud IN LISTS _full_bauds) + pureboot_matrix_point(${_matrix_hz} ${_matrix_baud} software) + if(PUREBOOT_HAS_USART) + pureboot_matrix_point(${_matrix_hz} ${_matrix_baud} 0) + endif() + if(PUREBOOT_HAS_USART1) + pureboot_matrix_point(${_matrix_hz} ${_matrix_baud} 1) + endif() + endforeach() + endforeach() + else() + foreach(_matrix_hz IN LISTS _matrix_clocks) + math(EXPR _matrix_khz "${_matrix_hz} / 1000") + if(PUREBOOT_HAS_USART OR NOT _matrix_hz EQUAL _pb_stock_hz) + pureboot_size_variant(pureboot_sw_${_matrix_khz}k CLOCK ${_matrix_hz} SERIAL software) + endif() + if(PUREBOOT_HAS_USART AND NOT _matrix_hz EQUAL _pb_stock_hz) + pureboot_size_variant(pureboot_hw_${_matrix_khz}k CLOCK ${_matrix_hz} SERIAL hardware) + endif() + if(PUREBOOT_HAS_USART1 AND NOT _matrix_hz EQUAL _pb_stock_hz) + pureboot_size_variant(pureboot_usart1_${_matrix_khz}k CLOCK ${_matrix_hz} USART 1) + endif() + endforeach() + list(GET _matrix_clocks -1 _matrix_top_hz) + pureboot_size_variant(pureboot_sw_wide CLOCK ${_matrix_top_hz} BAUD 9600 SERIAL software) endif() - foreach(_matrix_hz IN LISTS _matrix_clocks) - math(EXPR _matrix_khz "${_matrix_hz} / 1000") - if(PUREBOOT_HAS_USART OR NOT _matrix_hz EQUAL _pb_stock_hz) - pureboot_size_variant(pureboot_sw_${_matrix_khz}k CLOCK ${_matrix_hz} SERIAL software) - endif() - if(PUREBOOT_HAS_USART AND NOT _matrix_hz EQUAL _pb_stock_hz) - pureboot_size_variant(pureboot_hw_${_matrix_khz}k CLOCK ${_matrix_hz} SERIAL hardware) - endif() - if(PUREBOOT_HAS_USART1 AND NOT _matrix_hz EQUAL _pb_stock_hz) - pureboot_size_variant(pureboot_usart1_${_matrix_khz}k CLOCK ${_matrix_hz} USART 1) - endif() - endforeach() if(PUREBOOT_HAS_USART1) pureboot_size_variant(pureboot_usart1 USART 1) endif() - # The baud axis, whose one size-bearing shape the ladder never picks: a - # software UART spins out each bit with _delay_loop_1 while the count - # fits a byte and with the 16-bit _delay_loop_2 beyond it, two words more - # setup at every one of its five sites — the largest image the - # configuration space produces. The ladder default takes the *fastest* - # rate a clock reaches, which always lands in the byte, so the wide form - # needs the slowest ladder rate against the fastest clock to appear. The - # hardware USART has no such shape: its baud is a divisor constant, and - # the ladder's U2X solutions are already its larger form. - list(GET _matrix_clocks -1 _matrix_top_hz) - pureboot_size_variant(pureboot_sw_wide CLOCK ${_matrix_top_hz} BAUD 9600 SERIAL software) - # One configured deployment end to end — a real board's shape rather # than the stock assumption: the ATmega328P on its shipped 1 MHz fuses, # the software UART on hand-picked pins (TX = PB1, RX = PB5), the ladder diff --git a/pureboot/CMakeLists.txt b/pureboot/CMakeLists.txt index dd3858a..03462ed 100644 --- a/pureboot/CMakeLists.txt +++ b/pureboot/CMakeLists.txt @@ -146,36 +146,51 @@ set(PUREBOOT_HAS_USART ${_pb_has_usart} PARENT_SCOPE) set(PUREBOOT_HAS_USART1 ${_pb_has_usart1} PARENT_SCOPE) set(PUREBOOT_SIM_MCU ${_pb_sim_mcu} PARENT_SCOPE) -# The fastest standard rate the clock reaches within 2.5 %, by the same -# best-of-U2X-and-plain divisor search libavr's solve_baud runs, so a default +# The rates a default may pick, fastest first. +set_property(GLOBAL PROPERTY PUREBOOT_BAUD_LADDER 115200 57600 38400 19200 9600) + +# Whether is reachable from within 2.5 %, by the same +# best-of-U2X-and-plain divisor search libavr's solve_baud runs, so a build # never trips the compile-time error it is checked against. A software build # also needs the polled receiver's 100-cycles-a-bit floor: at low clocks the # U2X divisor reaches rates the bit-banged sampler cannot. -function(pureboot_default_baud clock software outvar) - foreach(baud 115200 57600 38400 19200 9600) - math(EXPR _cycles "${clock} / ${baud}") - if(software AND _cycles LESS 100) +function(pureboot_baud_feasible clock baud software outvar) + set(${outvar} 0 PARENT_SCOPE) + math(EXPR _cycles "${clock} / ${baud}") + if(software AND _cycles LESS 100) + return() + endif() + foreach(divisor 8 16) + math(EXPR _step "${divisor} * ${baud}") + math(EXPR _n "(${clock} + ${_step} / 2) / ${_step}") + if(_n LESS 1 OR _n GREATER 4096) continue() endif() - foreach(divisor 8 16) - math(EXPR _step "${divisor} * ${baud}") - math(EXPR _n "(${clock} + ${_step} / 2) / ${_step}") - if(_n LESS 1 OR _n GREATER 4096) - continue() - endif() - math(EXPR _actual "${clock} / (${divisor} * ${_n})") - math(EXPR _delta "${_actual} - ${baud}") - if(_delta LESS 0) - math(EXPR _delta "-(${_delta})") - endif() - math(EXPR _error_bp "${_delta} * 10000 / ${baud}") - if(_error_bp LESS_EQUAL 250) - set(${outvar} ${baud} PARENT_SCOPE) - return() - endif() - endforeach() + math(EXPR _actual "${clock} / (${divisor} * ${_n})") + math(EXPR _delta "${_actual} - ${baud}") + if(_delta LESS 0) + math(EXPR _delta "-(${_delta})") + endif() + math(EXPR _error_bp "${_delta} * 10000 / ${baud}") + if(_error_bp LESS_EQUAL 250) + set(${outvar} 1 PARENT_SCOPE) + return() + endif() endforeach() - message(FATAL_ERROR "pureboot: no standard baud rate fits a ${clock} Hz clock within 2.5 %") +endfunction() + +# The fastest ladder rate the clock reaches. +function(pureboot_default_baud clock software outvar) + get_property(_ladder GLOBAL PROPERTY PUREBOOT_BAUD_LADDER) + foreach(baud ${_ladder}) + pureboot_baud_feasible(${clock} ${baud} ${software} _ok) + if(_ok) + set(${outvar} ${baud} PARENT_SCOPE) + return() + endif() + endforeach() + message(FATAL_ERROR "pureboot: no standard baud rate fits a ${clock} Hz clock within 2.5 % " + "— pass BAUD to deploy a non-standard one") endfunction() # pureboot_add_loader( [CLOCK ] [BAUD ] diff --git a/pureboot/README.md b/pureboot/README.md index 5dc9e5a..5924c34 100644 --- a/pureboot/README.md +++ b/pureboot/README.md @@ -309,6 +309,13 @@ Per chip preset, `ctest` runs: the configuration space produces, and a shape the ladder default (always the *fastest* rate a clock reaches) never picks. Pins are immediate operands and the timeout is a constant: neither is an axis; +- `pbm_*.size` — under `--full`, the exhaustive cross product replacing that + compact matrix: every plausible oscillator (the internal ones, the CKDIV8 + floor, the plain and the UART crystals) × every rate reachable from it × + every backend, unreachable combinations dropping out rather than aborting + the configure. Bounded to one chip per size-bearing class — flash + addressing, hand-over shape, page size, USART inventory — since everything + else in the image is chip-independent code; - `pureboot.pi` — the position-independence lint: no absolute `jmp`/`call`, the info block within the image's first 256 bytes; - `pureboot.planner` — the host tool's pure logic: programming orders and their diff --git a/tools/check.sh b/tools/check.sh index fb1d9c9..5cb8444 100755 --- a/tools/check.sh +++ b/tools/check.sh @@ -2,12 +2,14 @@ # 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). LIBAVR_ROOT must point at the libavr checkout. +# full matrix) and swaps the compact size matrix for the exhaustive +# clock × baud × backend cross product. LIBAVR_ROOT must point at the libavr +# checkout. set -e cd "$(dirname "$0")/.." full=0 -[[ "$1" == "--full" ]] && { full=1; shift; } +[[ "$1" == "--full" ]] && { full=1; shift; export PUREBOOT_FULL_MATRIX=1; } CHIPS=(attiny13 attiny13a attiny25 attiny45 attiny85 atmega8 atmega8a atmega16 atmega16a atmega32 atmega32a