Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 18 additions & 11 deletions scripts/ckpt/bench/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,19 +138,26 @@ def optional_saleae(compile_only_warning: str) -> Iterator[Manager | None]:

Logs *compile_only_warning* and yields None when no device is detected.
The manager is closed on exit.

The Otii switchboard relays are closed first: with the intermittent-power
rig wired up they carry the ez-FET's SBW and 3V3 lines, and probing for
the device before closing them would find nothing.
"""
manager: Manager | None = None
if check_device_available():
from ..device.saleae import discover_saleae
from ..device.otii import debugger_connection

manager = discover_saleae()
else:
logger.warning(compile_only_warning)
try:
yield manager
finally:
if manager is not None:
manager.close()
with debugger_connection():
manager: Manager | None = None
if check_device_available():
from ..device.saleae import discover_saleae

manager = discover_saleae()
else:
logger.warning(compile_only_warning)
try:
yield manager
finally:
if manager is not None:
manager.close()


def measure_execution_time(
Expand Down
59 changes: 48 additions & 11 deletions scripts/ckpt/device/otii.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
from the debugger while it runs on replayed power. Benchmark completion and
timing are measured by the Saleae (see device/saleae.py), not by the Otii.

Continuous-power runs (``ckpt bench``, ``ckpt verify``) use the same wiring
but need the opposite relay state; see :func:`debugger_connection`.

Requires the ``otii-tcp-client`` package (``uv sync --extra otii``) and the
``otii_server`` binary (path via the ``OTII_SERVER_BIN`` environment
variable).
Expand All @@ -20,7 +23,7 @@
import threading
import time
from collections.abc import Iterator
from contextlib import contextmanager
from contextlib import ExitStack, contextmanager
from dataclasses import dataclass
from typing import Any

Expand Down Expand Up @@ -153,10 +156,11 @@ def _single_arc(otii: Any, arc_cls: Any) -> Any:


@contextmanager
def otii_session() -> Iterator[OtiiSession]:
"""Start otii_server, connect, and configure the device for replay runs.
def _otii_device() -> Iterator[OtiiSession]:
"""Start otii_server, connect, and configure the device.

On exit: main power off, switchboard relays opened, server stopped.
On exit: main power off, client and server shut down. The switchboard
relays are left as the caller set them.
"""
otii_client, arc_cls = _import_otii()
server = _start_server()
Expand Down Expand Up @@ -189,20 +193,53 @@ def otii_session() -> Iterator[OtiiSession]:
arc.set_main(False)
except Exception:
logger.exception("Error switching off Otii main power")
if otii is not None:
try:
otii.shutdown()
except Exception:
logger.exception("Error shutting down Otii client")
_stop_server(server)


@contextmanager
def otii_session() -> Iterator[OtiiSession]:
"""An Otii device session for replay runs; the relays are opened on exit."""
with _otii_device() as session:
try:
yield session
finally:
try:
arc.set_gpo(_RELAY_GPO, False)
session.arc.set_gpo(_RELAY_GPO, False)
logger.info(
"Switchboard relays left open — the ez-FET is disconnected "
"until the next intermittent run closes them"
)
except Exception:
logger.exception("Error opening switchboard relays")
if otii is not None:
try:
otii.shutdown()
except Exception:
logger.exception("Error shutting down Otii client")
_stop_server(server)


@contextmanager
def debugger_connection() -> Iterator[None]:
"""Hold the switchboard relays closed for a continuous-power run.

``ckpt bench`` and ``ckpt verify`` power the target from the ez-FET's 3V3
rail, which reaches the board through the same relays an intermittent run
opens. Closing them here means neither command needs the board rewired.

A no-op when no Otii is reachable: the board is then wired directly to the
ez-FET. The relays stay closed on exit — only an intermittent run opens
them.
"""
with ExitStack() as stack:
try:
connect_debugger(stack.enter_context(_otii_device()))
except DeviceError as exc:
logger.info(
"No Otii switchboard in the loop (%s); assuming a direct "
"ez-FET connection",
exc,
)
yield


def connect_debugger(session: OtiiSession) -> None:
Expand Down
10 changes: 6 additions & 4 deletions scripts/ckpt/verify/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import logging
import time
from collections.abc import Callable
from contextlib import closing
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
Expand All @@ -29,6 +30,7 @@
compile_uninstrumented,
)
from ..device.flash import check_region_violation, raise_if_region_violation, read_nvm
from ..device.otii import debugger_connection
from ..device.saleae import discover_saleae, saleae_run
from ..env import ProjectEnv
from ..errors import CompilationError, ConfigError, DeviceError, RegionViolationError
Expand Down Expand Up @@ -123,8 +125,10 @@ def verify_algorithms(
if not bench_files:
raise ConfigError("No benchmarks to verify")

saleae_manager = discover_saleae()
try:
# The relays must close before the Saleae/device probing below: with the
# intermittent-power rig wired up they carry the ez-FET's SBW and 3V3
# lines to the target.
with debugger_connection(), closing(discover_saleae()) as saleae_manager:
capacitors = discover_capacitors(env, algorithms[0].name, caps)

results: dict[str, list[BenchResult]] = {spec.name: [] for spec in algorithms}
Expand Down Expand Up @@ -191,8 +195,6 @@ def verify_algorithms(
_print_summary(results[spec.name], spec.name, halt_mode)

return results
finally:
saleae_manager.close()


def all_ok(results: list[BenchResult]) -> bool:
Expand Down
Loading