cmake_minimum_required(VERSION 3.28)

project(tsb_libavr LANGUAGES CXX)

# libavr from a local checkout (LIBAVR_ROOT) or the forge; the toolchain file
# comes from the same checkout via CMakePresets.json.
include(FetchContent)
if(NOT LIBAVR_ROOT AND DEFINED ENV{LIBAVR_ROOT})
	set(LIBAVR_ROOT $ENV{LIBAVR_ROOT})
endif()
if(LIBAVR_ROOT)
	FetchContent_Declare(libavr SOURCE_DIR ${LIBAVR_ROOT})
else()
	FetchContent_Declare(libavr GIT_REPOSITORY git@git.blackmark.me:avr/libavr.git GIT_TAG main)
endif()
FetchContent_MakeAvailable(libavr)

if(PROJECT_IS_TOP_LEVEL)
	add_compile_options(-Werror) # warnings are errors for the port's own code
	enable_testing()

	# The behavioral tests drive the real wire protocols over a simavr pty
	# (as the host tools do) and actually flash the device. The runners are
	# host programs built at configure time against libsimavr; if they or
	# Python are missing, only the size tests run.
	find_program(_host_cc NAMES cc gcc)
	find_package(Python3 COMPONENTS Interpreter)
	if(_host_cc AND Python3_FOUND)
		set(PB_DEVICE ${CMAKE_BINARY_DIR}/pureboot_device)
		execute_process(
		    COMMAND ${_host_cc} -O2 -I/usr/include/simavr -I/usr/include/simavr/parts
		            -o ${PB_DEVICE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pureboot_device.c
		            -lsimavr -lsimavrparts -lelf -lutil
		    RESULT_VARIABLE _pbdev_res ERROR_VARIABLE _pbdev_err)
		if(NOT _pbdev_res EQUAL 0)
			message(STATUS "pureboot_device not built (${_pbdev_err}) — protocol tests skipped")
			unset(PB_DEVICE)
		endif()
		if(LIBAVR_MCU STREQUAL "atmega328p")
			set(TSB_DEVICE ${CMAKE_BINARY_DIR}/tsb_device)
			execute_process(
			    COMMAND ${_host_cc} -O2 -I/usr/include/simavr -I/usr/include/simavr/parts
			            -o ${TSB_DEVICE} ${CMAKE_CURRENT_SOURCE_DIR}/test/device.c
			            -lsimavr -lsimavrparts -lelf
			    RESULT_VARIABLE _dev_res ERROR_VARIABLE _dev_err)
			if(NOT _dev_res EQUAL 0)
				message(STATUS "tsb_device not built (${_dev_err}) — protocol tests skipped")
				unset(TSB_DEVICE)
			endif()
		endif()
	endif()
endif()

# The ELF is only a container (symbols, section headers) and is never flashed —
# and the host tool's load_image() dispatches on extension, so handing it one
# would silently program the header bytes. Every loader image therefore gets
# both flashable forms beside it at link time: .hex for avrdude, and .bin for
# the host tool's raw path (which is what the reloc and update tests convert to
# on the fly). .eeprom is dropped — EEPROM content is its own update.
function(add_image_outputs name)
	add_custom_command(TARGET ${name} POST_BUILD
	                   COMMAND ${CMAKE_OBJCOPY} -O ihex -R .eeprom
	                           $<TARGET_FILE:${name}> $<TARGET_FILE:${name}>.hex
	                   COMMAND ${CMAKE_OBJCOPY} -O binary -R .eeprom
	                           $<TARGET_FILE:${name}> $<TARGET_FILE:${name}>.bin)
endfunction()

# The TinySafeBoot protocol reimplemented on libavr in three variants that trade
# clarity for size. Each links into the ATmega328P boot section (BOOTSZ selects
# its size; BOOTRST vectors a reset to its base) with -nostartfiles — a polled
# loader has no use for the crt or the vector table. The naked entry sits in
# .vectors, laid first, and runs. The boot base is FLASHEND+1 minus the section
# size; the linker section-start and the source's boot_bytes agree. tsb_app is
# the application's reset vector, pinned to 0 here so the loaders jump to a
# named function; --pmem-wrap-around lets relaxation turn that absolute jump
# into the wrapped rjmp AVR's modulo-flash PC actually executes.
# All three implement the full oracle feature set (see oracle/README.md):
# watchdog bail, one-wire half-duplex, config-page activation timeout, password
# gate, emergency erase, config/flash/EEPROM read-write. They differ only in how,
# and the size gradient is the cost of that "how" — see dev/lessons.md.
#   tsb_asm   — the tricks tier's C++ with exactly two routines in asm (the
#                 bounded rx and the page-store loop — the two whose remaining
#                 cost is the C ABI itself): 510 B in the 512 B section the
#                 hand-written 500 B oracle occupies. Everything else, from
#                 bring-up to dispatch, is C++ on libavr.
#   tsb_tricks — no asm at all: the whole-loader register allocation lives in
#                 global register variables (Y walks the page pointer), every
#                 helper is a tiny noinline primitive placed by the
#                 global-register store rules, pages stream straight to
#                 SPM/EEPROM, and the bring-up is the two reset-non-default
#                 registers only. 526 B in the 1 KB section (BOOTSZ=10) — 14
#                 over the oracle's section, from 168 over at this tier's first
#                 floor.
#   tsb_pure  — pure idiomatic libavr, one function per command, TU-local
#                 (internal linkage), streaming (no SRAM page buffer): 836 B in
#                 the 1 KB section.
#
# add_tsb_variant(<name> <boot-section-bytes>)
function(add_tsb_variant name bytes)
	math(EXPR base_dec "32768 - ${bytes}")
	math(EXPR base_hex "${base_dec}" OUTPUT_FORMAT HEXADECIMAL)
	add_executable(${name} tsb/${name}.cpp)
	target_link_libraries(${name} PRIVATE libavr)
	target_link_options(${name} PRIVATE -nostartfiles -Wl,--section-start=.text=${base_hex}
	                    -Wl,--defsym=tsb_app=0 -Wl,--pmem-wrap-around=32k)
	add_custom_command(TARGET ${name} POST_BUILD COMMAND ${CMAKE_SIZE} $<TARGET_FILE:${name}>)
	add_image_outputs(${name})
	if(PROJECT_IS_TOP_LEVEL)
		add_test(NAME ${name}.size
		         COMMAND ${CMAKE_COMMAND} -DSIZE_TOOL=${CMAKE_SIZE} -DELF=$<TARGET_FILE:${name}>
		                 -DLIMIT=${bytes} -P ${CMAKE_CURRENT_SOURCE_DIR}/test/check_size.cmake)
		if(DEFINED TSB_DEVICE)
			add_test(NAME ${name}.protocol
			         COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/tsbtest.py
			                 ${TSB_DEVICE} $<TARGET_FILE:${name}> ${base_hex})
		endif()
	endif()
endfunction()

# The tsb tiers reimplement the ATmega328P-only reference protocol; the other
# chips build pureboot alone.
if(LIBAVR_MCU STREQUAL "atmega328p")
	add_tsb_variant(tsb_asm 512)
	add_tsb_variant(tsb_pure 1024)
	add_tsb_variant(tsb_tricks 1024)
endif()

# pureboot — the pure-constraint port (see pureboot/README.md): one source,
# no inline assembly, no global register variables, every libavr chip,
# fitting each chip's smallest boot sector. The geometry and the
# pureboot_add_loader() deployment function live in pureboot/CMakeLists.txt —
# the unit a downstream project consumes; everything below is this port's
# own build: the stock loaders, their tests, and the size matrix. The
# distinct binary dir keeps the `pureboot` target's output name free.
add_subdirectory(pureboot pureboot-cmake)

# The stock loader: the family-default deployment (crystal/RC clock, the
# chip's natural link, default pins). The activation window stays a cache
# variable — re-timing a deployed loader is a self-update with a re-timed
# build. pureboot9 is that re-timed build, and what the update test installs.
set(PUREBOOT_TIMEOUT 8 CACHE STRING "pureboot activation window, seconds")
pureboot_add_loader(pureboot TIMEOUT ${PUREBOOT_TIMEOUT})
if(PROJECT_IS_TOP_LEVEL)
	get_target_property(_pb_stock_hz pureboot PUREBOOT_HZ)
	get_target_property(_pb_stock_baud pureboot PUREBOOT_BAUD)

	add_test(NAME pureboot.size
	         COMMAND ${CMAKE_COMMAND} -DSIZE_TOOL=${CMAKE_SIZE} -DELF=$<TARGET_FILE:pureboot>
	                 -DLIMIT=${PUREBOOT_LIMIT} -P ${CMAKE_CURRENT_SOURCE_DIR}/test/check_size.cmake)
	if(Python3_FOUND)
		add_test(NAME pureboot.pi
		         COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/check_pi.py
		                 ${CMAKE_OBJDUMP} ${CMAKE_NM} $<TARGET_FILE:pureboot> ${PUREBOOT_BASE_HEX})
		add_test(NAME pureboot.planner
		         COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/test_planner.py
		                 ${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py)
	endif()

	# The protocol test flashes this fixture through the loader with the real
	# host tool and expects its banner after the hand-over; a normally linked
	# application whose reset vector is what the tinies' surgery re-homes.
	if(DEFINED PB_DEVICE)
		add_executable(pbapp test/pbapp.cpp)
		target_link_libraries(pbapp PRIVATE libavr)
		add_custom_command(TARGET pbapp POST_BUILD
		                   COMMAND ${CMAKE_OBJCOPY} -O binary $<TARGET_FILE:pbapp> $<TARGET_FILE:pbapp>.bin)
		add_test(NAME pureboot.protocol
		         COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbtest.py
		                 ${PB_DEVICE} $<TARGET_FILE:pureboot> ${PUREBOOT_SIM_MCU} ${_pb_stock_hz}
		                 ${PUREBOOT_BASE_HEX} ${PUREBOOT_PAGE} ${_pb_stock_baud} ${PUREBOOT_EEPROM}
		                 $<TARGET_FILE:pbapp>.bin ${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py
		                 ${CMAKE_BINARY_DIR}/pbtest-work)
		set_tests_properties(pureboot.protocol PROPERTIES TIMEOUT 180)

		# The position-independence acceptance test: the identical image,
		# installed one slot lower, must serve the full command set.
		add_test(NAME pureboot.reloc
		         COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbreloc.py
		                 ${PB_DEVICE} $<TARGET_FILE:pureboot> ${PUREBOOT_SIM_MCU} ${_pb_stock_hz}
		                 ${PUREBOOT_BASE_HEX} ${PUREBOOT_PAGE} ${_pb_stock_baud}
		                 ${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py
		                 ${CMAKE_BINARY_DIR}/pbreloc-work)
		set_tests_properties(pureboot.reloc PROPERTIES TIMEOUT 180
		                     ENVIRONMENT "PB_OBJCOPY=${CMAKE_OBJCOPY}")

		# Entering the loader from a running application with no reset
		# between, over a page buffer the application dirtied — the case the
		# loader declines to guard and the host repairs. Hardware forbids the
		# state here (SPM runs only from the boot section); simavr does not,
		# which is what makes it constructible.
		if(LIBAVR_MCU STREQUAL "atmega328p")
			add_test(NAME pureboot.dirty
			         COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbdirty.py
			                 ${PB_DEVICE} $<TARGET_FILE:pureboot> ${PUREBOOT_SIM_MCU} ${_pb_stock_hz}
			                 ${PUREBOOT_BASE_HEX} ${PUREBOOT_PAGE} ${_pb_stock_baud}
			                 $<TARGET_FILE:pbapp>.bin
			                 ${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py
			                 ${CMAKE_BINARY_DIR}/pbdirty-work)
			set_tests_properties(pureboot.dirty PROPERTIES TIMEOUT 180)
		endif()

		# Re-homing: a loader mistakenly programmed at address 0 (a raw .bin
		# handed to a programmer) or sitting in the staging slot must heal
		# into the canonical slot through the ordinary --update-loader flow.
		# Patched-vector behavior, so one representative chip carries it.
		if(LIBAVR_MCU STREQUAL "attiny85")
			add_test(NAME pureboot.rehome
			         COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbrehome.py
			                 ${PB_DEVICE} $<TARGET_FILE:pureboot> $<TARGET_FILE:pureboot9>.bin
			                 ${PUREBOOT_SIM_MCU} ${_pb_stock_hz} ${PUREBOOT_BASE_HEX} ${PUREBOOT_PAGE}
			                 ${_pb_stock_baud} $<TARGET_FILE:pbapp>.bin
			                 ${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py
			                 ${CMAKE_BINARY_DIR}/pbrehome-work)
			set_tests_properties(pureboot.rehome PROPERTIES TIMEOUT 180)
		endif()

		# The self-update end-to-end: the re-timed build (same source, only
		# the timeout differs — a byte-different image) replaces the resident
		# through --update-loader, with every power-fail phase rehearsed from
		# the runner's flash dumps.
		pureboot_add_loader(pureboot9 TIMEOUT 9)
		add_test(NAME pureboot.update
		         COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbupdate.py
		                 ${PB_DEVICE} $<TARGET_FILE:pureboot> $<TARGET_FILE:pureboot9>
		                 ${PUREBOOT_SIM_MCU} ${_pb_stock_hz} ${PUREBOOT_BASE_HEX} ${PUREBOOT_PAGE}
		                 ${_pb_stock_baud} $<TARGET_FILE:pbapp>.bin
		                 ${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py
		                 ${CMAKE_BINARY_DIR}/pbupdate-work)
		set_tests_properties(pureboot.update PROPERTIES TIMEOUT 600
		                     ENVIRONMENT "PB_OBJCOPY=${CMAKE_OBJCOPY}")
	endif()

	# 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 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
		         COMMAND ${CMAKE_COMMAND} -DSIZE_TOOL=${CMAKE_SIZE} -DELF=$<TARGET_FILE:${name}>
		                 -DLIMIT=${PUREBOOT_LIMIT} -P ${CMAKE_CURRENT_SOURCE_DIR}/test/check_size.cmake)
	endfunction()

	# The autobaud loader (pureboot/autobaud.md): one clock-agnostic image per
	# chip, no clock × baud axis, size-tested against the same per-chip budget on
	# every chip.
	#
	# Only the unified version is built. Its two predecessors —
	# pureboot_autobaud_pure.cpp at 508 B and pureboot_autobaud_reg.cpp at 512 —
	# had 4 B and 0 B of margin on the 1284P, and the fix for the activation hang
	# costs ~22, which puts them at 530 and 534. Neither can ship, so the choice
	# the branch existed to offer is settled by measurement rather than taste.
	# The sources stay for the record; autobaud.md carries the numbers.
	function(pureboot_autobaud_variant name source)
		pureboot_add_autobaud(${name} ${source})
		add_test(NAME ${name}.size
		         COMMAND ${CMAKE_COMMAND} -DSIZE_TOOL=${CMAKE_SIZE} -DELF=$<TARGET_FILE:${name}>
		                 -DLIMIT=${PUREBOOT_LIMIT} -P ${CMAKE_CURRENT_SOURCE_DIR}/test/check_size.cmake)
	endfunction()
	pureboot_autobaud_variant(pureboot_autobaud_uni pureboot_autobaud_uni.cpp)

	# 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()
	if(PUREBOOT_HAS_USART1)
		pureboot_size_variant(pureboot_usart1 USART 1)
	endif()

	# 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
	# baud (9600). The full protocol suite runs against it, fixture
	# application included, over the runner's GPIO bridge — proving the
	# configuration plumbing produces a working loader, not just one that
	# fits.
	if(LIBAVR_MCU STREQUAL "atmega328p" AND DEFINED PB_DEVICE)
		pureboot_size_variant(pureboot_custom CLOCK 1000000 SERIAL software RX pb5 TX pb1)
		get_target_property(_custom_hz pureboot_custom PUREBOOT_HZ)
		get_target_property(_custom_baud pureboot_custom PUREBOOT_BAUD)
		get_target_property(_custom_link pureboot_custom PUREBOOT_LINK)
		add_executable(pbapp_custom test/pbapp.cpp)
		target_link_libraries(pbapp_custom PRIVATE libavr)
		target_compile_definitions(pbapp_custom PRIVATE PUREBOOT_CLOCK_HZ=${_custom_hz}
		                           PUREBOOT_BAUD=${_custom_baud} PUREBOOT_SOFT_SERIAL PUREBOOT_TX=pb1)
		add_custom_command(TARGET pbapp_custom POST_BUILD
		                   COMMAND ${CMAKE_OBJCOPY} -O binary
		                           $<TARGET_FILE:pbapp_custom> $<TARGET_FILE:pbapp_custom>.bin)
		add_test(NAME pureboot.custom
		         COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbtest.py
		                 ${PB_DEVICE} $<TARGET_FILE:pureboot_custom> ${PUREBOOT_SIM_MCU} ${_custom_hz}
		                 ${PUREBOOT_BASE_HEX} ${PUREBOOT_PAGE} ${_custom_baud} ${PUREBOOT_EEPROM}
		                 $<TARGET_FILE:pbapp_custom>.bin ${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py
		                 ${CMAKE_BINARY_DIR}/pbcustom-work ${_custom_link})
		set_tests_properties(pureboot.custom PROPERTIES TIMEOUT 180)
	endif()

	# The second USART, driven for real on one chip: instance selection is
	# compile-checked everywhere, but only a live session proves the loader
	# initialized and polls the USART it claims to. The fixture application
	# banners on the same instance.
	if(LIBAVR_MCU STREQUAL "atmega644a" AND DEFINED PB_DEVICE)
		get_target_property(_usart1_hz pureboot_usart1 PUREBOOT_HZ)
		get_target_property(_usart1_baud pureboot_usart1 PUREBOOT_BAUD)
		add_executable(pbapp_usart1 test/pbapp.cpp)
		target_link_libraries(pbapp_usart1 PRIVATE libavr)
		target_compile_definitions(pbapp_usart1 PRIVATE PUREBOOT_CLOCK_HZ=${_usart1_hz}
		                           PUREBOOT_BAUD=${_usart1_baud} PUREBOOT_USART=1)
		add_custom_command(TARGET pbapp_usart1 POST_BUILD
		                   COMMAND ${CMAKE_OBJCOPY} -O binary
		                           $<TARGET_FILE:pbapp_usart1> $<TARGET_FILE:pbapp_usart1>.bin)
		add_test(NAME pureboot.usart1
		         COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbtest.py
		                 ${PB_DEVICE} $<TARGET_FILE:pureboot_usart1> ${PUREBOOT_SIM_MCU} ${_usart1_hz}
		                 ${PUREBOOT_BASE_HEX} ${PUREBOOT_PAGE} ${_usart1_baud} ${PUREBOOT_EEPROM}
		                 $<TARGET_FILE:pbapp_usart1>.bin ${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py
		                 ${CMAKE_BINARY_DIR}/pbusart1-work usart1)
		set_tests_properties(pureboot.usart1 PROPERTIES TIMEOUT 180)
	endif()

	# The autobaud variants driven end to end over the software-UART bridge (both
	# under review — pureboot/autobaud.md): the host sends the 0xC0 calibration
	# pulse, the loader times it, locks, and programs. Run on the near-flash 328P
	# and the word-addressed 1284P — the two flash-addressing classes — and each
	# at two clocks with the one binary, which is the clock-agnostic property
	# autobaud exists for (test/pbautobaud.py). The fixture application banners
	# over the same software link at the first clock's rate.
	if(LIBAVR_MCU MATCHES "^atmega(328p|1284p)$" AND DEFINED PB_DEVICE)
		add_executable(pbapp_autobaud test/pbapp.cpp)
		target_link_libraries(pbapp_autobaud PRIVATE libavr)
		target_compile_definitions(pbapp_autobaud PRIVATE PUREBOOT_CLOCK_HZ=1000000
		                           PUREBOOT_BAUD=9600 PUREBOOT_SOFT_SERIAL PUREBOOT_TX=pb1)
		add_custom_command(TARGET pbapp_autobaud POST_BUILD
		                   COMMAND ${CMAKE_OBJCOPY} -O binary
		                           $<TARGET_FILE:pbapp_autobaud> $<TARGET_FILE:pbapp_autobaud>.bin)
		foreach(_variant uni)
			add_test(NAME pureboot.autobaud_${_variant}
			         COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbautobaud.py
			                 ${PB_DEVICE} $<TARGET_FILE:pureboot_autobaud_${_variant}> ${PUREBOOT_SIM_MCU}
			                 ${PUREBOOT_BASE_HEX} ${PUREBOOT_PAGE} $<TARGET_FILE:pbapp_autobaud>.bin
			                 1000000 9600 ${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py
			                 ${CMAKE_BINARY_DIR}/pbautobaud-${_variant}-work)
			set_tests_properties(pureboot.autobaud_${_variant} PROPERTIES TIMEOUT 240)
		endforeach()
	endif()
endif()
