pureboot: autobaud host support and simavr end-to-end for both variants
pureboot.py --autobaud sends the 0xC0 calibration pulse and a single knock at the host's chosen baud, reads the slimmed info block, and derives the full geometry from the signature (AUTOBAUD_GEOMETRY, a table over every pureboot chip). Everything downstream — flash, EEPROM, fuses, hand-over, verify — is the fixed-baud path unchanged; the dropped write guard is host-transparent. test/pbautobaud.py drives each variant over the GPIO⇄pty software-UART bridge through the calibration handshake and a flash + EEPROM + fuse round-trip cross-checked against the simulator's ground-truth memory, then repeats at double the F_CPU with the same binary — the clock-agnostic property autobaud exists for. Wired as pureboot.autobaud_pure/reg on the near-flash 328P and the word-addressed 1284P. A wrong measured unit fails the flash/verify, so the test also pins the codegen-coupled calibration constant against a toolchain bump. Both variants green in sim on both chips at two clocks each; the fixed-baud suite is unaffected. Only real-hardware acceptance on an RC part remains (pureboot/autobaud.md). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -388,4 +388,30 @@ if(PROJECT_IS_TOP_LEVEL)
|
|||||||
${CMAKE_BINARY_DIR}/pbusart1-work usart1)
|
${CMAKE_BINARY_DIR}/pbusart1-work usart1)
|
||||||
set_tests_properties(pureboot.usart1 PROPERTIES TIMEOUT 180)
|
set_tests_properties(pureboot.usart1 PROPERTIES TIMEOUT 180)
|
||||||
endif()
|
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 pure reg)
|
||||||
|
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()
|
endif()
|
||||||
|
|||||||
@@ -164,30 +164,49 @@ port's build.
|
|||||||
guarantees safety" (README.md). Self-update still works — it is the safety net
|
guarantees safety" (README.md). Self-update still works — it is the safety net
|
||||||
against host bugs that is lost, not a functional path.
|
against host bugs that is lost, not a functional path.
|
||||||
|
|
||||||
## What remains (either version)
|
## Host tool and simulation — done
|
||||||
|
|
||||||
The decider — does pure C++ fit 512 on every chip — is answered (yes). Not yet
|
Both are wired and green, for **both** variants.
|
||||||
done, and required before shipping:
|
|
||||||
|
|
||||||
1. **Sim-validate the lock.** Run the chosen variant over the GPIO⇄pty bridge at
|
- **Host tool.** `pureboot.py --autobaud` sends the 0xC0 calibration pulse and a
|
||||||
≥3 exact F_CPU (1/8/16 MHz, the tiny13's 9.6 MHz): confirm it measures the
|
single knock at the host's chosen baud, then reads the slimmed info block and
|
||||||
right unit and completes a flash + verify. This test is also what pins the
|
**derives the full geometry from the signature** (`AUTOBAUD_GEOMETRY`, a table
|
||||||
codegen-coupled calibration constant (0xC0 vs the 7-cycle loop) against a
|
over every chip pureboot targets). The rest of the tool — flash, EEPROM, fuses,
|
||||||
toolchain bump.
|
hand-over, verify — is unchanged: once connected, the derived `Info` presents
|
||||||
2. **Host tool + protocol.** `pureboot.py` learns the calibration handshake
|
the same geometry the fixed-baud path reads off the wire. The pure version's
|
||||||
(send the 0xC0 lead, then knock at the locked rate), the slim info format
|
dropped write guard is host-transparent (the tool already never targets the
|
||||||
(derive geometry from the signature), and — for the pure version — that the
|
running slot).
|
||||||
loader carries no write guard. README protocol section updated.
|
- **Sim test** (`test/pbautobaud.py`, `pureboot.autobaud_pure` /
|
||||||
3. **Real-hardware acceptance.** Autobaud exists for what a cycle-exact simulator
|
`pureboot.autobaud_reg`). Drives each variant over the GPIO⇄pty software-UART
|
||||||
cannot produce: a real RC oscillator at ±10 % with drift and jitter. Drive an
|
bridge through the calibration handshake, a flash + EEPROM + fuse round-trip
|
||||||
internal-oscillator ATtiny from the host at a fixed baud; confirm lock plus a
|
cross-checked against the simulator's ground-truth memory, and a hand-over to
|
||||||
full flash + verify (Windows environment, hardware present — confirm first).
|
the fixture application — then **repeats at double the F_CPU with the same
|
||||||
|
binary**, which is the clock-agnostic property autobaud exists for. Run on the
|
||||||
|
near-flash 328P and the word-addressed 1284P (both flash-addressing classes).
|
||||||
|
Because the lock fails a flash/verify if the measured unit is wrong, this test
|
||||||
|
also **pins the codegen-coupled calibration constant** (0xC0 against the
|
||||||
|
7-cycle poll loop): a toolchain bump that reshaped the loop would fail it.
|
||||||
|
|
||||||
|
## What remains
|
||||||
|
|
||||||
|
The decider is answered (pure C++ fits 512 on every chip) and both variants pass
|
||||||
|
in simulation. One item remains before shipping the chosen variant:
|
||||||
|
|
||||||
|
- **Real-hardware acceptance.** Autobaud exists for what a cycle-exact simulator
|
||||||
|
cannot produce: a real RC oscillator at ±10 % with drift and jitter. simavr
|
||||||
|
proves the arithmetic and the fit at *exact* clocks; only silicon proves the
|
||||||
|
feature does its job. Drive an internal-oscillator ATtiny from the host at a
|
||||||
|
fixed baud; confirm lock plus a full flash + verify (Windows environment,
|
||||||
|
hardware present — confirm first). Deferred until the variant is chosen.
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
|
|
||||||
- `pureboot_autobaud_pure.cpp` — the pure version (GPIOR/RAM unit, no guard).
|
- `pureboot_autobaud_pure.cpp` — the pure version (GPIOR/RAM unit, no guard).
|
||||||
- `pureboot_autobaud_reg.cpp` — the register version (GRV unit, guard kept).
|
- `pureboot_autobaud_reg.cpp` — the register version (GRV unit, guard kept).
|
||||||
- `CMakeLists.txt` — `pureboot_add_autobaud()` builds either; the port's build
|
- `pureboot.py` — `--autobaud`: the calibration handshake, slim info, and the
|
||||||
size-tests both on every chip.
|
signature→geometry table both variants rely on.
|
||||||
|
- `test/pbautobaud.py` — the end-to-end sim test; `CMakeLists.txt` runs it for
|
||||||
|
both variants on the 328P and 1284P, and `pureboot_add_autobaud()` size-tests
|
||||||
|
both on every chip.
|
||||||
- `local/scratch/autobaud/floor_1284.S` (libavr checkout) — the hand-asm floor
|
- `local/scratch/autobaud/floor_1284.S` (libavr checkout) — the hand-asm floor
|
||||||
probe, off-tree and gitignored; not sim-verified, a size reference only.
|
probe, off-tree and gitignored; not sim-verified, a size reference only.
|
||||||
|
|||||||
@@ -34,6 +34,44 @@ NEWEST_LOADER = 4
|
|||||||
SLOT = 512 # the loader slot, on every chip
|
SLOT = 512 # the loader slot, on every chip
|
||||||
RETRIES = 3 # rewrites of a page that reads back wrong, before the run stops
|
RETRIES = 3 # rewrites of a page that reads back wrong, before the run stops
|
||||||
|
|
||||||
|
# Calibration byte for the autobaud loader: 0xC0 is a start bit plus six zero
|
||||||
|
# data bits — one low pulse of seven bit-times, which the loader times into its
|
||||||
|
# per-bit unit. Sent at whatever baud the host chose; the loader locks to it.
|
||||||
|
CALIBRATE = 0xC0
|
||||||
|
|
||||||
|
# The autobaud loader slims its info block to the version and signature; the host
|
||||||
|
# derives the rest of the geometry from the signature. flash, page, eeprom,
|
||||||
|
# patch-vector per distinct signature, over every chip pureboot targets (the
|
||||||
|
# loader computes the same from its chip database at build time). Die revisions
|
||||||
|
# that share a signature share this row, as they share the silicon.
|
||||||
|
AUTOBAUD_GEOMETRY = {
|
||||||
|
# signature : (flash, page, eeprom, patch_vector)
|
||||||
|
(0x1E, 0x90, 0x07): (1024, 32, 64, True), # ATtiny13/13A
|
||||||
|
(0x1E, 0x91, 0x08): (2048, 32, 128, True), # ATtiny25
|
||||||
|
(0x1E, 0x92, 0x06): (4096, 64, 256, True), # ATtiny45
|
||||||
|
(0x1E, 0x93, 0x0B): (8192, 64, 512, True), # ATtiny85
|
||||||
|
(0x1E, 0x92, 0x05): (4096, 64, 256, True), # ATmega48/48A
|
||||||
|
(0x1E, 0x92, 0x0A): (4096, 64, 256, True), # ATmega48P/48PA
|
||||||
|
(0x1E, 0x93, 0x07): (8192, 64, 512, False), # ATmega8/8A
|
||||||
|
(0x1E, 0x93, 0x0A): (8192, 64, 512, False), # ATmega88/88A
|
||||||
|
(0x1E, 0x93, 0x0F): (8192, 64, 512, False), # ATmega88P/88PA
|
||||||
|
(0x1E, 0x94, 0x03): (16384, 128, 512, False), # ATmega16/16A
|
||||||
|
(0x1E, 0x94, 0x06): (16384, 128, 512, False), # ATmega168/168A
|
||||||
|
(0x1E, 0x94, 0x0B): (16384, 128, 512, False), # ATmega168P/168PA
|
||||||
|
(0x1E, 0x94, 0x0A): (16384, 128, 512, False), # ATmega164P/164PA
|
||||||
|
(0x1E, 0x94, 0x0F): (16384, 128, 512, False), # ATmega164A
|
||||||
|
(0x1E, 0x95, 0x02): (32768, 128, 1024, False), # ATmega32/32A
|
||||||
|
(0x1E, 0x95, 0x0F): (32768, 128, 1024, False), # ATmega328P
|
||||||
|
(0x1E, 0x95, 0x14): (32768, 128, 1024, False), # ATmega328
|
||||||
|
(0x1E, 0x95, 0x08): (32768, 128, 1024, False), # ATmega324P
|
||||||
|
(0x1E, 0x95, 0x11): (32768, 128, 1024, False), # ATmega324PA
|
||||||
|
(0x1E, 0x95, 0x15): (32768, 128, 1024, False), # ATmega324A
|
||||||
|
(0x1E, 0x96, 0x09): (65536, 256, 2048, False), # ATmega644/644A
|
||||||
|
(0x1E, 0x96, 0x0A): (65536, 256, 2048, False), # ATmega644P/644PA
|
||||||
|
(0x1E, 0x97, 0x05): (131072, 256, 4096, False),# ATmega1284P
|
||||||
|
(0x1E, 0x97, 0x06): (131072, 256, 4096, False),# ATmega1284
|
||||||
|
}
|
||||||
|
|
||||||
VERBOSE = False
|
VERBOSE = False
|
||||||
|
|
||||||
|
|
||||||
@@ -301,6 +339,28 @@ Port = WindowsPort if os.name == "nt" else PosixPort
|
|||||||
class Info:
|
class Info:
|
||||||
"""The 12-byte info block."""
|
"""The 12-byte info block."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_slim(cls, raw):
|
||||||
|
"""The autobaud loader's slimmed reply — version and signature only —
|
||||||
|
with the rest of the geometry looked up from the signature (the loader
|
||||||
|
derived it from the same chip facts at build time). Reconstructs the full
|
||||||
|
block so every derived attribute matches the fixed-baud path exactly."""
|
||||||
|
if len(raw) != 4:
|
||||||
|
raise Error(f"bad slim info block: {raw.hex()}")
|
||||||
|
version, signature = raw[0], tuple(raw[1:4])
|
||||||
|
geometry = AUTOBAUD_GEOMETRY.get(signature)
|
||||||
|
if geometry is None:
|
||||||
|
sig = " ".join(f"{b:02x}" for b in signature)
|
||||||
|
raise Error(f"unknown signature {sig} — this tool has no autobaud geometry for it")
|
||||||
|
flash, page, eeprom, patch = geometry
|
||||||
|
base = flash - SLOT
|
||||||
|
word_flash = flash > 0x10000
|
||||||
|
wire_base = base // 2 if word_flash else base
|
||||||
|
flags = (1 if patch else 0) | (2 if word_flash else 0)
|
||||||
|
raw12 = bytes((ord("P"), ord("B"), version, *signature, page & 0xFF,
|
||||||
|
wire_base & 0xFF, wire_base >> 8, eeprom & 0xFF, eeprom >> 8, flags))
|
||||||
|
return cls(raw12)
|
||||||
|
|
||||||
def __init__(self, raw):
|
def __init__(self, raw):
|
||||||
if len(raw) != 12 or raw[0:2] != b"PB":
|
if len(raw) != 12 or raw[0:2] != b"PB":
|
||||||
raise Error(f"bad info block: {raw.hex()}")
|
raise Error(f"bad info block: {raw.hex()}")
|
||||||
@@ -396,6 +456,37 @@ class Loader:
|
|||||||
if time.monotonic() > deadline:
|
if time.monotonic() > deadline:
|
||||||
raise Error("no answer — reset the device within its activation window")
|
raise Error("no answer — reset the device within its activation window")
|
||||||
|
|
||||||
|
def connect_autobaud(self, wait):
|
||||||
|
"""The autobaud handshake. Instead of the p+b knock, the host sends the
|
||||||
|
0xC0 calibration pulse — a single seven-bit-time low pulse at the host's
|
||||||
|
chosen baud — which the loader times into its per-bit unit, then a single
|
||||||
|
'p' knock the loader decodes at the rate it just measured. As with
|
||||||
|
connect(), each attempt is the whole handshake, retried until the slim
|
||||||
|
info block comes back or the window closes: a lost pulse or a knock that
|
||||||
|
lands while the loader is mid-frame simply fails to answer, and the
|
||||||
|
loader's measurement loop is back waiting for the next pulse."""
|
||||||
|
deadline = time.monotonic() + wait
|
||||||
|
knocks = 0
|
||||||
|
while True:
|
||||||
|
self.port.flush_input()
|
||||||
|
self.port.write(bytes((CALIBRATE, ord("p"))))
|
||||||
|
knocks += 1
|
||||||
|
if PROMPT in self.port.read_available(0.4):
|
||||||
|
while self.port.read_available(0.3):
|
||||||
|
pass
|
||||||
|
self.port.write(b"b")
|
||||||
|
try:
|
||||||
|
block = self.port.read_exact(4, 2.0)
|
||||||
|
except Error:
|
||||||
|
block = b""
|
||||||
|
if len(block) == 4:
|
||||||
|
self.info = Info.from_slim(block)
|
||||||
|
self._expect_prompt()
|
||||||
|
verbose(f"loader locked on knock {knocks}; slim info read")
|
||||||
|
return self.info
|
||||||
|
if time.monotonic() > deadline:
|
||||||
|
raise Error("no answer — reset the device within its activation window")
|
||||||
|
|
||||||
def _expect_prompt(self, timeout=2.0):
|
def _expect_prompt(self, timeout=2.0):
|
||||||
byte = self.port.read_exact(1, timeout)
|
byte = self.port.read_exact(1, timeout)
|
||||||
if byte != PROMPT:
|
if byte != PROMPT:
|
||||||
@@ -1049,6 +1140,9 @@ def main():
|
|||||||
parser.add_argument("--port", required=True, help="serial device: COM6, /dev/ttyUSB0, or a simavr pty")
|
parser.add_argument("--port", required=True, help="serial device: COM6, /dev/ttyUSB0, or a simavr pty")
|
||||||
parser.add_argument("--baud", type=int, default=115200, help="115200 mega, 57600 tinies")
|
parser.add_argument("--baud", type=int, default=115200, help="115200 mega, 57600 tinies")
|
||||||
parser.add_argument("--wait", type=float, default=30.0, help="seconds to keep knocking")
|
parser.add_argument("--wait", type=float, default=30.0, help="seconds to keep knocking")
|
||||||
|
parser.add_argument("--autobaud", action="store_true",
|
||||||
|
help="drive an autobaud loader: send the 0xC0 calibration pulse and a single "
|
||||||
|
"knock, and take geometry from the signature (no clock/baud baked in)")
|
||||||
parser.add_argument("--info", action="store_true", help="print the device info block")
|
parser.add_argument("--info", action="store_true", help="print the device info block")
|
||||||
parser.add_argument("--fuses", action="store_true", help="read the fuse and lock bytes")
|
parser.add_argument("--fuses", action="store_true", help="read the fuse and lock bytes")
|
||||||
parser.add_argument("--update-loader", metavar="FILE", help="replace the loader with this pureboot binary")
|
parser.add_argument("--update-loader", metavar="FILE", help="replace the loader with this pureboot binary")
|
||||||
@@ -1086,7 +1180,7 @@ def main():
|
|||||||
verbose(f"{args.port}: {args.baud} Bd 8N1, DTR/RTS asserted")
|
verbose(f"{args.port}: {args.baud} Bd 8N1, DTR/RTS asserted")
|
||||||
try:
|
try:
|
||||||
loader = Loader(port)
|
loader = Loader(port)
|
||||||
info = loader.connect(args.wait)
|
info = loader.connect_autobaud(args.wait) if args.autobaud else loader.connect(args.wait)
|
||||||
if args.info:
|
if args.info:
|
||||||
print("device:")
|
print("device:")
|
||||||
for line in info.lines():
|
for line in info.lines():
|
||||||
|
|||||||
113
test/pbautobaud.py
Normal file
113
test/pbautobaud.py
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""End-to-end autobaud test: drive an autobaud loader in simavr through the
|
||||||
|
calibration handshake and a flash + EEPROM + fuse round-trip, cross-checked
|
||||||
|
against the simulator's ground-truth memory — then repeat at a second F_CPU with
|
||||||
|
the *same* loader binary, which is the property autobaud exists for: one
|
||||||
|
clock-agnostic image that locks onto whatever rate the host sends.
|
||||||
|
|
||||||
|
Usage: pbautobaud.py <device_bin> <loader_elf> <mcu> <base_hex> <page>
|
||||||
|
<app_bin> <app_hz> <app_baud> <tool_py> <workdir>
|
||||||
|
|
||||||
|
The loader is a software-serial build on PB0/PB1 (pureboot_add_autobaud's
|
||||||
|
default), so the runner drives it over the GPIO⇄pty bridge (-l sw:B0,B1). The
|
||||||
|
app fixture is built for (app_hz, app_baud); the hand-over is checked at that
|
||||||
|
point, and a second point at half the clock proves the lock is measured, not
|
||||||
|
baked in.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message):
|
||||||
|
print(f"FAIL: {message}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
(device_bin, elf, mcu, base_hex, page, app_bin, app_hz, app_baud, tool, workdir) = sys.argv[1:]
|
||||||
|
base, page, app_hz, app_baud = int(base_hex, 0), int(page), int(app_hz), int(app_baud)
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(tool)))
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
import pbsim
|
||||||
|
import pureboot as pb
|
||||||
|
|
||||||
|
os.makedirs(workdir, exist_ok=True)
|
||||||
|
ee_image = bytes(range(0xA0, 0xB0))
|
||||||
|
ee_path = os.path.join(workdir, "ee.bin")
|
||||||
|
open(ee_path, "wb").write(ee_image)
|
||||||
|
|
||||||
|
# The geometry the surgery planner needs, from the chip class the runner is
|
||||||
|
# told — the same derivation pbtest.py makes: the boot-sectioned megas need
|
||||||
|
# no vector surgery, the tinies and the boot-section-less m48s do, and the
|
||||||
|
# large chips speak word addresses.
|
||||||
|
mega = mcu.startswith("atmega")
|
||||||
|
patch = not mega or mcu.startswith("atmega48")
|
||||||
|
word_flash = base + pb.SLOT > 0x10000
|
||||||
|
wire_base = base // 2 if word_flash else base
|
||||||
|
flags = (1 if patch else 0) | (2 if word_flash else 0)
|
||||||
|
ground_truth = pb.Info(bytes([ord("P"), ord("B"), pb.NEWEST_LOADER, 0, 0, 0, page & 0xFF,
|
||||||
|
wire_base & 0xFF, wire_base >> 8, 0, 0, flags]))
|
||||||
|
|
||||||
|
def round_trip(hz, baud, label, hand_over):
|
||||||
|
"""One clock point: reset, calibrate + knock, program, verify against the
|
||||||
|
simulator's own flash, and (at the app's point) hand over to the fixture."""
|
||||||
|
dump = os.path.join(workdir, f"flash_{label}.bin")
|
||||||
|
device = pbsim.Device(device_bin, elf, mcu, str(hz), base_hex, page, baud, dump, link="sw:B0,B1")
|
||||||
|
try:
|
||||||
|
# The host tool, in autobaud mode, sends the 0xC0 calibration pulse
|
||||||
|
# and a single knock at `baud`; the loader locks to it.
|
||||||
|
out = pbsim.run_tool(tool, device.pty, baud, "--autobaud", "--info", "--fuses",
|
||||||
|
"--flash", app_bin, "--eeprom", ee_path, "--stay")
|
||||||
|
for needed in ("version", "signature", "fuses", "verify:", "stays"):
|
||||||
|
if needed not in out:
|
||||||
|
fail(f"{label}: session output lacks {needed!r}\n{out}")
|
||||||
|
# Read both memories back over the locked link and check them.
|
||||||
|
read_flash = os.path.join(workdir, f"rf_{label}.bin")
|
||||||
|
read_eeprom = os.path.join(workdir, f"re_{label}.bin")
|
||||||
|
out = pbsim.run_tool(tool, device.pty, baud, "--autobaud", "--verify-flash", app_bin,
|
||||||
|
"--verify-eeprom", ee_path, "--read-flash", read_flash,
|
||||||
|
"--read-eeprom", read_eeprom, "--stay")
|
||||||
|
if out.count("verify:") != 2:
|
||||||
|
fail(f"{label}: did not verify both memories\n{out}")
|
||||||
|
if open(read_eeprom, "rb").read()[: len(ee_image)] != ee_image:
|
||||||
|
fail(f"{label}: EEPROM read-back mismatch")
|
||||||
|
|
||||||
|
if hand_over:
|
||||||
|
device.reset()
|
||||||
|
port = pb.Port(device.pty, baud)
|
||||||
|
try:
|
||||||
|
loader = pb.Loader(port)
|
||||||
|
live = loader.connect_autobaud(15)
|
||||||
|
if live.version != pb.NEWEST_LOADER:
|
||||||
|
fail(f"{label}: loader reports pureboot {live.version}")
|
||||||
|
loader.run_application()
|
||||||
|
banner = port.read_exact(3, 5.0)
|
||||||
|
if banner != b"APP":
|
||||||
|
fail(f"{label}: application banner was {banner!r}")
|
||||||
|
finally:
|
||||||
|
port.close()
|
||||||
|
finally:
|
||||||
|
device.stop()
|
||||||
|
|
||||||
|
# Ground truth (read after the runner exits and writes its dump): what
|
||||||
|
# the tool programmed must be what the simulator actually holds.
|
||||||
|
pages = pb.plan_flash(open(app_bin, "rb").read(), ground_truth)
|
||||||
|
flash_true = open(dump, "rb").read()
|
||||||
|
for address, data in pages.items():
|
||||||
|
if flash_true[address : address + page] != data:
|
||||||
|
fail(f"{label}: simulator flash differs from the programmed image at {address:#06x}")
|
||||||
|
print(f" {label}: locked at {hz} Hz / {baud} Bd, flash+EEPROM verified"
|
||||||
|
+ (", hand-over ok" if hand_over else ""))
|
||||||
|
|
||||||
|
# The app fixture is built for one clock; the hand-over banners there. A
|
||||||
|
# second point at double that clock, same loader binary, proves the lock is
|
||||||
|
# measured, not baked in — the whole point of autobaud. (Doubling keeps the
|
||||||
|
# bit period healthy; halving would drop it below the software UART's floor.)
|
||||||
|
round_trip(app_hz, app_baud, "clock-a", hand_over=True)
|
||||||
|
round_trip(app_hz * 2, app_baud, "clock-b", hand_over=False)
|
||||||
|
print("pbautobaud: calibration lock and flash/EEPROM/fuse round-trip pass at both clocks")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user