tan-cli 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tan/__init__.py +0 -0
- tan/__main__.py +120 -0
- tan/cli.py +939 -0
- tan/command_table.py +265 -0
- tan/commands/__init__.py +8 -0
- tan/commands/bootstrap_cmd.py +4856 -0
- tan/commands/bootstrap_patches.py +164 -0
- tan/commands/bootstrap_toolchain_fetch.py +155 -0
- tan/commands/bootstrap_toolchain_pin.py +36 -0
- tan/commands/build/__init__.py +1 -0
- tan/commands/build/configure_inputs.py +277 -0
- tan/commands/build/execute.py +1852 -0
- tan/commands/build/host_python.py +59 -0
- tan/commands/build/link_stale.py +78 -0
- tan/commands/build/manifest.py +428 -0
- tan/commands/build/materialise.py +81 -0
- tan/commands/build/token_substitution.py +381 -0
- tan/commands/build/toolchain.py +482 -0
- tan/commands/build/user_defines_wipe.py +115 -0
- tan/commands/build/workspace_patches.py +65 -0
- tan/commands/build_cmd.py +2236 -0
- tan/commands/build_output.py +331 -0
- tan/commands/clean_cmd.py +1183 -0
- tan/commands/completion_cmd.py +786 -0
- tan/commands/debug_config_cmd.py +2425 -0
- tan/commands/diff_cmd.py +940 -0
- tan/commands/doctor_cmd.py +4767 -0
- tan/commands/examples_cmd.py +644 -0
- tan/commands/explain_cmd.py +1319 -0
- tan/commands/faultdecode_cmd.py +931 -0
- tan/commands/flash_atoc_guard.py +240 -0
- tan/commands/flash_cmd.py +5731 -0
- tan/commands/flash_mram_guard.py +150 -0
- tan/commands/flash_ram.py +578 -0
- tan/commands/flash_raw.py +583 -0
- tan/commands/generate_cmd.py +1456 -0
- tan/commands/image_cmd.py +753 -0
- tan/commands/init_cmd.py +1740 -0
- tan/commands/inspect_cmd.py +377 -0
- tan/commands/kconfig_cmd.py +734 -0
- tan/commands/model_cmd.py +1514 -0
- tan/commands/model_device_cmd.py +351 -0
- tan/commands/model_device_live.py +234 -0
- tan/commands/model_host_cmd.py +430 -0
- tan/commands/model_zoo_cmd.py +363 -0
- tan/commands/monitor_actions.py +125 -0
- tan/commands/monitor_cmd.py +626 -0
- tan/commands/monitor_session.py +505 -0
- tan/commands/new_som_cmd.py +1522 -0
- tan/commands/pinmux_cmd.py +568 -0
- tan/commands/presets_cmd.py +1123 -0
- tan/commands/probe_cmd.py +421 -0
- tan/commands/reset_cmd.py +354 -0
- tan/commands/reset_confirm.py +168 -0
- tan/commands/run_cmd.py +637 -0
- tan/commands/scaffold_cmd.py +490 -0
- tan/commands/sdk_cmd.py +1723 -0
- tan/commands/size_cmd.py +1020 -0
- tan/commands/support_bundle_cmd.py +1073 -0
- tan/commands/trace_cmd.py +341 -0
- tan/commands/validate_cmd.py +2009 -0
- tan/commands/west_forward_cmd.py +736 -0
- tan/commands/workspace_patch_check.py +296 -0
- tan/core/__init__.py +1 -0
- tan/core/alp_conf_pregen.py +100 -0
- tan/core/artifact_provenance.py +240 -0
- tan/core/atoc_guard.py +276 -0
- tan/core/atoc_guard_messages.py +232 -0
- tan/core/atoc_replacement.py +118 -0
- tan/core/atomic_write.py +178 -0
- tan/core/board_context.py +351 -0
- tan/core/board_diagnostic.py +111 -0
- tan/core/board_files.py +221 -0
- tan/core/board_validator.py +542 -0
- tan/core/board_validator_compat.py +196 -0
- tan/core/board_validator_run.py +129 -0
- tan/core/board_validator_schema.py +186 -0
- tan/core/board_validator_skew.py +46 -0
- tan/core/board_yaml_edit.py +295 -0
- tan/core/board_yaml_pos.py +118 -0
- tan/core/bootstrap.py +2572 -0
- tan/core/build_plan.py +625 -0
- tan/core/consent.py +104 -0
- tan/core/console_filter.py +245 -0
- tan/core/debug_launch.py +1803 -0
- tan/core/devicetree_lint.py +168 -0
- tan/core/dir_removal.py +229 -0
- tan/core/doctor_git.py +172 -0
- tan/core/doctor_libraries.py +253 -0
- tan/core/doctor_render.py +185 -0
- tan/core/doctor_scope.py +66 -0
- tan/core/doctor_setools.py +237 -0
- tan/core/doctor_stale.py +180 -0
- tan/core/document_guards.py +723 -0
- tan/core/dp_id.py +97 -0
- tan/core/elf_load.py +63 -0
- tan/core/error_catalog.py +171 -0
- tan/core/example_catalog.py +499 -0
- tan/core/example_facets.py +151 -0
- tan/core/faultdecode.py +521 -0
- tan/core/flash_plan.py +2501 -0
- tan/core/flow_d_report.py +381 -0
- tan/core/flow_d_uptime.py +164 -0
- tan/core/fs_confine.py +130 -0
- tan/core/global_flags.py +230 -0
- tan/core/host_python.py +210 -0
- tan/core/image_bundle.py +247 -0
- tan/core/inert.py +147 -0
- tan/core/jlink_binary.py +128 -0
- tan/core/jlink_probe.py +481 -0
- tan/core/jsonc_splice.py +404 -0
- tan/core/launch_provenance.py +257 -0
- tan/core/link_refusal.py +76 -0
- tan/core/metadata_schema.py +376 -0
- tan/core/model_check.py +227 -0
- tan/core/model_device.py +449 -0
- tan/core/model_doctor.py +198 -0
- tan/core/model_host.py +48 -0
- tan/core/model_list.py +112 -0
- tan/core/model_zoo.py +353 -0
- tan/core/module_template.py +340 -0
- tan/core/mram_link.py +87 -0
- tan/core/new_som_ospi.py +94 -0
- tan/core/os_class.py +159 -0
- tan/core/patch_paths.py +87 -0
- tan/core/pending.py +40 -0
- tan/core/plain_zephyr_plan.py +138 -0
- tan/core/plan_exec.py +373 -0
- tan/core/plan_tokens.py +600 -0
- tan/core/probe.py +155 -0
- tan/core/probe_plan.py +237 -0
- tan/core/proxy.py +168 -0
- tan/core/publish.py +31 -0
- tan/core/python_floor.py +178 -0
- tan/core/ram_run.py +694 -0
- tan/core/ram_watch.py +231 -0
- tan/core/raw_write.py +160 -0
- tan/core/reset_plan.py +56 -0
- tan/core/run.py +161 -0
- tan/core/scaffold.py +2203 -0
- tan/core/scaffold_selftest_identity.py +225 -0
- tan/core/sdk_default_registry.py +565 -0
- tan/core/sdk_discovery.py +996 -0
- tan/core/sdk_removal.py +465 -0
- tan/core/serial_actions.py +175 -0
- tan/core/serial_capture.py +185 -0
- tan/core/serial_url.py +45 -0
- tan/core/setools.py +608 -0
- tan/core/setools_scratch.py +243 -0
- tan/core/shapes.py +194 -0
- tan/core/size.py +610 -0
- tan/core/som_buildability.py +204 -0
- tan/core/som_schema_version.py +62 -0
- tan/core/subprocess_env.py +201 -0
- tan/core/sysbuild_image.py +94 -0
- tan/core/system_manifest.py +827 -0
- tan/core/text_layout.py +73 -0
- tan/core/timestamp.py +182 -0
- tan/core/tool_lookup.py +383 -0
- tan/core/toolchain_pin.py +117 -0
- tan/core/toolchain_provision.py +1224 -0
- tan/core/trusted_path.py +134 -0
- tan/core/uboot_breakin.py +154 -0
- tan/core/uri_reference.py +415 -0
- tan/core/user_defines.py +152 -0
- tan/core/venv.py +332 -0
- tan/core/west_patches.py +143 -0
- tan/core/west_workspace_refusal.py +159 -0
- tan/core/zephyr_env.py +124 -0
- tan/env.py +185 -0
- tan/envelope.py +647 -0
- tan/exit_codes.py +12 -0
- tan/lazy_typer.py +185 -0
- tan/model/__init__.py +2 -0
- tan/model/_gen_fixture.py +132 -0
- tan/model/adapters/__init__.py +116 -0
- tan/model/adapters/cpu.py +26 -0
- tan/model/adapters/deepx.py +94 -0
- tan/model/adapters/drpai.py +242 -0
- tan/model/adapters/ethos_u.py +951 -0
- tan/model/adapters/executorch.py +55 -0
- tan/model/analyze.py +694 -0
- tan/model/build.py +304 -0
- tan/model/check.py +682 -0
- tan/model/manifest.py +625 -0
- tan/model/measure.py +216 -0
- tan/model/npu_ops_validate.py +130 -0
- tan/model/package.py +125 -0
- tan/model/perf.py +635 -0
- tan/model/perf_apply.py +721 -0
- tan/model/prep.py +193 -0
- tan/model/sram_fit.py +502 -0
- tan/model/targets.py +374 -0
- tan/model/tensorio.py +287 -0
- tan/net.py +73 -0
- tan/output_format.py +213 -0
- tan/planner/__init__.py +219 -0
- tan/planner/aperture.py +202 -0
- tan/planner/buildplan.py +1092 -0
- tan/planner/camera_owner.py +192 -0
- tan/planner/cameras.py +132 -0
- tan/planner/carveout.py +755 -0
- tan/planner/cli.py +274 -0
- tan/planner/headers.py +315 -0
- tan/planner/kconfig.py +2435 -0
- tan/planner/kconfig_symbols.py +473 -0
- tan/planner/libraries.py +677 -0
- tan/planner/link_target.py +237 -0
- tan/planner/linux_ownership.py +216 -0
- tan/planner/loader.py +1525 -0
- tan/planner/manifest.py +116 -0
- tan/planner/memory.py +161 -0
- tan/planner/memregion.py +56 -0
- tan/planner/models.py +533 -0
- tan/planner/orchestrator.py +874 -0
- tan/planner/ownership.py +203 -0
- tan/planner/partition.py +1160 -0
- tan/planner/paths.py +32 -0
- tan/planner/plain_slice.py +139 -0
- tan/planner/project_emit/__init__.py +60 -0
- tan/planner/project_emit/bom_netlist.py +375 -0
- tan/planner/project_emit/dts.py +580 -0
- tan/planner/project_emit/hw_info.py +221 -0
- tan/planner/project_emit/native_sim.py +136 -0
- tan/planner/project_emit/west_libs.py +213 -0
- tan/planner/project_loader.py +264 -0
- tan/planner/sdk_capability.py +203 -0
- tan/planner/sdk_compat.py +395 -0
- tan/planner/secure.py +350 -0
- tan/planner/slugs.py +394 -0
- tan/planner/som_metadata.py +325 -0
- tan/planner/strict_loaders.py +131 -0
- tan/planner/template.py +1037 -0
- tan/planner/template_pins.py +535 -0
- tan/planner/template_rewrite.py +714 -0
- tan/planner/topology.py +208 -0
- tan/planner/validate.py +365 -0
- tan/planner/whole_device_alias.py +67 -0
- tan/planner/zephyr_board.py +3510 -0
- tan/planner_cli.py +97 -0
- tan/planner_emit.py +617 -0
- tan/planner_root.py +119 -0
- tan/soc_ref.py +58 -0
- tan/templates/__init__.py +34 -0
- tan/templates/vendored/MANIFEST.md +1862 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/README.md +112 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/board.yaml +38 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.conf +11 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.overlay +29 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/prj.conf +5 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/src/main.c +266 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/testcase.yaml +53 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/CMakeLists.txt +8 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/README.md +112 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/board.yaml +38 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.conf +11 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.overlay +29 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/prj.conf +5 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/src/main.c +266 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/testcase.yaml +53 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/CMakeLists.txt +10 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/README.md +75 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/board.yaml +38 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/prj.conf +8 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.c +371 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.h +178 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/src/main.c +373 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/testcase.yaml +62 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/CMakeLists.txt +10 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/README.md +75 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/board.yaml +37 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/prj.conf +8 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.c +371 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.h +178 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/src/main.c +373 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/testcase.yaml +62 -0
- tan/templates/vendored/iot/E1M-AEN801/CMakeLists.txt +14 -0
- tan/templates/vendored/iot/E1M-AEN801/README.md +103 -0
- tan/templates/vendored/iot/E1M-AEN801/board.yaml +69 -0
- tan/templates/vendored/iot/E1M-AEN801/prj.conf +15 -0
- tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.c +196 -0
- tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.h +173 -0
- tan/templates/vendored/iot/E1M-AEN801/src/main.c +262 -0
- tan/templates/vendored/iot/E1M-AEN801/testcase.yaml +68 -0
- tan/templates/vendored/minimal/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/minimal/E1M-AEN801/README.md +108 -0
- tan/templates/vendored/minimal/E1M-AEN801/board.yaml +32 -0
- tan/templates/vendored/minimal/E1M-AEN801/prj.conf +8 -0
- tan/templates/vendored/minimal/E1M-AEN801/src/main.c +111 -0
- tan/templates/vendored/minimal/E1M-AEN801/testcase.yaml +49 -0
- tan/templates/vendored/minimal/E1M-V2N101/CMakeLists.txt +8 -0
- tan/templates/vendored/minimal/E1M-V2N101/README.md +108 -0
- tan/templates/vendored/minimal/E1M-V2N101/board.yaml +32 -0
- tan/templates/vendored/minimal/E1M-V2N101/prj.conf +8 -0
- tan/templates/vendored/minimal/E1M-V2N101/src/main.c +111 -0
- tan/templates/vendored/minimal/E1M-V2N101/testcase.yaml +49 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/README.md +120 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/board.yaml +57 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/boards/native_sim_native_64.overlay +40 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/CMakeLists.txt +13 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/main.c +171 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/prj.conf +17 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/testcase.yaml +23 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/prj.conf +22 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/src/main.c +185 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/testcase.yaml +26 -0
- tan/templates/vendored/sensor/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/sensor/E1M-AEN801/README.md +112 -0
- tan/templates/vendored/sensor/E1M-AEN801/board.yaml +57 -0
- tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.conf +10 -0
- tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.overlay +28 -0
- tan/templates/vendored/sensor/E1M-AEN801/prj.conf +5 -0
- tan/templates/vendored/sensor/E1M-AEN801/src/main.c +186 -0
- tan/templates/vendored/sensor/E1M-AEN801/testcase.yaml +52 -0
- tan/templates/vendored/sensor/E1M-V2N101/CMakeLists.txt +8 -0
- tan/templates/vendored/sensor/E1M-V2N101/README.md +112 -0
- tan/templates/vendored/sensor/E1M-V2N101/board.yaml +57 -0
- tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.conf +10 -0
- tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.overlay +28 -0
- tan/templates/vendored/sensor/E1M-V2N101/prj.conf +5 -0
- tan/templates/vendored/sensor/E1M-V2N101/src/main.c +186 -0
- tan/templates/vendored/sensor/E1M-V2N101/testcase.yaml +52 -0
- tan/version.py +102 -0
- tan_cli-0.7.0.dist-info/METADATA +125 -0
- tan_cli-0.7.0.dist-info/RECORD +330 -0
- tan_cli-0.7.0.dist-info/WHEEL +5 -0
- tan_cli-0.7.0.dist-info/entry_points.txt +2 -0
- tan_cli-0.7.0.dist-info/top_level.txt +1 -0
tan/model/sram_fit.py
ADDED
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""SRAM0 residency + arena fit for a `Sram_Only` ethos_u compile (tan-cli#1288).
|
|
3
|
+
|
|
4
|
+
**Premise correction, recorded here since the issue that opened this file got
|
|
5
|
+
it wrong.** tan-cli#1288 claimed tan already compares the vela ARENA
|
|
6
|
+
(`req_sram_kib`) against the board's own budget "per tan-cli#1011". It does
|
|
7
|
+
not: #1011 was only the scope decision that `req_sram_kib` stays arena-only
|
|
8
|
+
(alp-sdk#2312 confirms weights are never summed into it) -- no host-side
|
|
9
|
+
comparison against `board.yaml` existed anywhere before this file.
|
|
10
|
+
`tan.model.targets.TargetSpec` carried no arena limit, and `_vela_profile`
|
|
11
|
+
never resolved a SoC-JSON `variants[]` entry, so `sram_banks_kb.SRAM0`
|
|
12
|
+
(nested under `variants[]`, `tan.core.size.sram_banks`) was unreachable from
|
|
13
|
+
model code. This module adds BOTH halves: the arena check the issue thought
|
|
14
|
+
already existed, and the SRAM0 residency check it asked for.
|
|
15
|
+
|
|
16
|
+
alp-sdk's `src/backends/inference/ethos_u_aen.cpp` pins EVERY NPU access to
|
|
17
|
+
the SRAM AXI port under `Sram_Only` -- not just the tensor arena
|
|
18
|
+
(`tan.model.adapters.ethos_u._footprint`'s own docstring has the full vela
|
|
19
|
+
bookkeeping story for why the arena figure is arena-only), but the compiled
|
|
20
|
+
BLOB too: the AEN examples copy the model's weights into
|
|
21
|
+
`section("SRAM0")` right beside the arena. Applies ONLY under that one vela
|
|
22
|
+
memory mode (`SRAM_ONLY_MODE` below) -- every other mode may place the blob
|
|
23
|
+
off SRAM0, so both checks report `"skipped"`, with a reason, everywhere else.
|
|
24
|
+
|
|
25
|
+
**Never "fits", only "fits-unverified".** Every SoC ships
|
|
26
|
+
`inference_arena_sram_kib: 0` today, so there is no VERIFIED inference-arena
|
|
27
|
+
SRAM budget in metadata to compare against -- a pass here means "the
|
|
28
|
+
declared/default budget is not exceeded", never "measured to run". A
|
|
29
|
+
`no-fit` is certain whenever it is proven against a real, sourced number OR a
|
|
30
|
+
valid LOWER BOUND on one (see `resolve_arena_budget`'s own docstring for
|
|
31
|
+
exactly which arena resolutions are sourced enough to certify one, and
|
|
32
|
+
`MIN_ARENA_KIB` below for the lower-bound case an unresolved arena still
|
|
33
|
+
allows).
|
|
34
|
+
|
|
35
|
+
Pure logic (`evaluate_sram_fit`, `resolve_arena_budget`, `FitVerdict`,
|
|
36
|
+
`SramFit`, `ArenaBudget`) is IO-free; the two resolvers that do real
|
|
37
|
+
filesystem reads (`resolve_sram0_kib`, and `read_sdk_som_and_soc`/
|
|
38
|
+
`resolve_variant`/`sram_banks` it reuses) are the only IO in this module --
|
|
39
|
+
the same split every other `tan.model` file makes (`tan.model.check`'s own
|
|
40
|
+
module doc: "IO lives in tan.model, never in tan.core")."""
|
|
41
|
+
from __future__ import annotations
|
|
42
|
+
|
|
43
|
+
import math
|
|
44
|
+
from dataclasses import dataclass
|
|
45
|
+
from pathlib import Path
|
|
46
|
+
|
|
47
|
+
from tan.commands.build_output import read_sdk_som_and_soc
|
|
48
|
+
from tan.core.size import resolve_variant, sram_banks
|
|
49
|
+
|
|
50
|
+
#: `metadata/schemas/board.schema.json` (alp-sdk, verified against the pinned
|
|
51
|
+
#: checkout at commit 79c834e6): `cores.<id>.inference.default_arena_kib` is
|
|
52
|
+
#: `{"type": "integer", "minimum": 16, "default": 128}`. `DEFAULT_ARENA_KIB`
|
|
53
|
+
#: is applied only to a core that RUNS INFERENCE (`_core_uses_inference`) and
|
|
54
|
+
#: that `board.yaml` actually declares, when that core's own `inference:`
|
|
55
|
+
#: block omits an override -- never invented for a core the board never
|
|
56
|
+
#: declares, or never runs inference on, at all (`resolve_arena_budget`
|
|
57
|
+
#: below). `MIN_ARENA_KIB` is the schema's own floor: even when NO core can
|
|
58
|
+
#: be resolved at all, the real arena -- whichever core ends up owning it --
|
|
59
|
+
#: can never be smaller than this, so `blob_kib + MIN_ARENA_KIB` is a valid
|
|
60
|
+
#: certain LOWER BOUND for a SRAM0 `NO_FIT` proof (tan-cli#1288 review round
|
|
61
|
+
#: 2, finding 2).
|
|
62
|
+
DEFAULT_ARENA_KIB = 128
|
|
63
|
+
MIN_ARENA_KIB = 16
|
|
64
|
+
|
|
65
|
+
#: The one vela memory mode that pins BOTH the tensor arena and the compiled
|
|
66
|
+
#: blob (weights) to the same SRAM AXI port
|
|
67
|
+
#: (`src/backends/inference/ethos_u_aen.cpp`) -- every other mode
|
|
68
|
+
#: (`Shared_Sram` / `Dedicated_Sram*`) may place the blob off SRAM0, so this
|
|
69
|
+
#: whole module is a no-op there.
|
|
70
|
+
SRAM_ONLY_MODE = "Sram_Only"
|
|
71
|
+
|
|
72
|
+
#: The three verdict words this module ever emits -- never a bare "fits" (see
|
|
73
|
+
#: the module docstring's "Never fits" paragraph).
|
|
74
|
+
FIT_UNVERIFIED = "fits-unverified"
|
|
75
|
+
NO_FIT = "no-fit"
|
|
76
|
+
SKIPPED = "skipped"
|
|
77
|
+
|
|
78
|
+
#: The caveat every `FIT_UNVERIFIED` arena verdict carries -- the board's OWN
|
|
79
|
+
#: declared/default budget is a real, sourced number, but nothing in
|
|
80
|
+
#: metadata publishes a firmware-verified one to compare it against (the
|
|
81
|
+
#: identical reason the word is "unverified" rather than "fits" at all).
|
|
82
|
+
_ARENA_UNVERIFIED_REASON = "board arena budget not verified against firmware"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass(frozen=True)
|
|
86
|
+
class FitVerdict:
|
|
87
|
+
"""One axis's verdict (arena, or SRAM0 residency): a KiB comparison plus
|
|
88
|
+
an optional human note. `reason` is populated for `SKIPPED` (why),
|
|
89
|
+
`FIT_UNVERIFIED` (the "not verified" caveat), and for a `NO_FIT` proven
|
|
90
|
+
only as a LOWER BOUND rather than off one exact sourced number --
|
|
91
|
+
`resolve_arena_budget`'s `"range"` kind's largest-budget bound, or an
|
|
92
|
+
`"unresolved"` budget's `MIN_ARENA_KIB` bound (both name which bound
|
|
93
|
+
proved it). A `NO_FIT` proven off one single exact figure carries no
|
|
94
|
+
`reason`: the number already says everything there is to say."""
|
|
95
|
+
|
|
96
|
+
verdict: str
|
|
97
|
+
needed_kib: int | None = None
|
|
98
|
+
limit_kib: int | None = None
|
|
99
|
+
reason: str | None = None
|
|
100
|
+
|
|
101
|
+
def as_dict(self) -> dict:
|
|
102
|
+
return {"verdict": self.verdict, "neededKib": self.needed_kib,
|
|
103
|
+
"limitKib": self.limit_kib, "reason": self.reason}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass(frozen=True)
|
|
107
|
+
class SramFit:
|
|
108
|
+
"""Both axes of a `Sram_Only` ethos_u compile's SRAM fit, plus the
|
|
109
|
+
compiled blob's own size in KiB -- the one shape `tan model build` (the
|
|
110
|
+
refusal decision only) and `tan model check --exact` (the `sramFit`
|
|
111
|
+
envelope block, `tan.core.model_check.backend_report_as_dict`) both
|
|
112
|
+
read."""
|
|
113
|
+
|
|
114
|
+
arena: FitVerdict
|
|
115
|
+
sram0: FitVerdict
|
|
116
|
+
blob_kib: int | None = None
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def no_fit(self) -> bool:
|
|
120
|
+
"""True the moment EITHER axis is a certain `NO_FIT` -- the
|
|
121
|
+
per-target skip `tan model build` records (the model is refused only
|
|
122
|
+
when no ethos_u target fits). A `SKIPPED` axis
|
|
123
|
+
never contributes: an unresolvable SRAM0 total, or an arena budget
|
|
124
|
+
this project's board.yaml never actually declared, is an unanswered
|
|
125
|
+
question, never treated as a failure (`resolve_arena_budget`)."""
|
|
126
|
+
return self.arena.verdict == NO_FIT or self.sram0.verdict == NO_FIT
|
|
127
|
+
|
|
128
|
+
def as_dict(self) -> dict:
|
|
129
|
+
return {"arena": self.arena.as_dict(), "sram0": self.sram0.as_dict(),
|
|
130
|
+
"blobKib": self.blob_kib}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _skipped_not_sram_only(memory_mode: str | None) -> SramFit:
|
|
134
|
+
reason = (f"the SRAM0 residency and arena fit checks apply only under "
|
|
135
|
+
f"the Sram_Only vela memory mode; this target resolved "
|
|
136
|
+
f"{memory_mode!r}.")
|
|
137
|
+
skip = FitVerdict(verdict=SKIPPED, reason=reason)
|
|
138
|
+
return SramFit(arena=skip, sram0=skip, blob_kib=None)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@dataclass(frozen=True)
|
|
142
|
+
class ArenaBudget:
|
|
143
|
+
"""How `resolve_arena_budget` resolved a target's arena budget -- exactly
|
|
144
|
+
one of three shapes, keyed by `kind`:
|
|
145
|
+
|
|
146
|
+
* `"single"` -- ONE board.yaml core's own arena figure is the answer,
|
|
147
|
+
CERTAIN enough for `NO_FIT` (`single_kib`, `cores` names it).
|
|
148
|
+
* `"range"` -- the target's own NPU pairs to no core
|
|
149
|
+
(`TargetSpec.paired_core is None`, a real answer for a shared NPU, e.g.
|
|
150
|
+
the E8/E6/E4's own Ethos-U85 -- not a gap), and board.yaml declares
|
|
151
|
+
MORE THAN ONE core that runs inference (`_core_uses_inference`), so
|
|
152
|
+
nothing sourced picks between them (`min_kib`/`max_kib`/`cores`, the
|
|
153
|
+
declared budgets and the core names that produced them).
|
|
154
|
+
* `"unresolved"` -- no board.yaml in hand, the target's paired core is
|
|
155
|
+
not declared in board.yaml at all, the target's paired core IS
|
|
156
|
+
declared but parked (`os: "off"`, `_core_participates`) or runs no
|
|
157
|
+
inference workload at all, or (paired_core is `None` too) board.yaml
|
|
158
|
+
has no participating core running an inference workload (`reason`
|
|
159
|
+
says which). Never a `NO_FIT` off one exact figure -- but see
|
|
160
|
+
`MIN_ARENA_KIB` for the lower-bound proof it still allows."""
|
|
161
|
+
|
|
162
|
+
kind: str
|
|
163
|
+
single_kib: int | None = None
|
|
164
|
+
min_kib: int | None = None
|
|
165
|
+
max_kib: int | None = None
|
|
166
|
+
cores: tuple[str, ...] = ()
|
|
167
|
+
reason: str | None = None
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _core_arena_kib_or_default(core_slice: dict) -> int:
|
|
171
|
+
"""`inference.default_arena_kib` off one already-declared board.yaml core
|
|
172
|
+
slice, or `DEFAULT_ARENA_KIB` when that core's own `inference:` block
|
|
173
|
+
omits it (or declares none at all) -- the core EXISTING in board.yaml is
|
|
174
|
+
what makes this a sourced fact about THIS project, not the presence of
|
|
175
|
+
the tuning key itself."""
|
|
176
|
+
inference = core_slice.get("inference")
|
|
177
|
+
if isinstance(inference, dict):
|
|
178
|
+
value = inference.get("default_arena_kib")
|
|
179
|
+
# bool is an int subclass in Python; a malformed `true`/`false` value
|
|
180
|
+
# is not a KiB count.
|
|
181
|
+
if isinstance(value, int) and not isinstance(value, bool):
|
|
182
|
+
return value
|
|
183
|
+
return DEFAULT_ARENA_KIB
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _core_participates(core_slice: dict) -> bool:
|
|
187
|
+
"""Mirrors `tan.planner.orchestrator.iter_buildable_slices`'s own filter
|
|
188
|
+
(tan-cli#1288 review round 3, finding 1) -- that function's own
|
|
189
|
+
docstring calls it "the SINGLE source of WHICH cores build ... so
|
|
190
|
+
`emit_build_plan()` ... always enumerates slices the same way": `os:
|
|
191
|
+
"off"` is the one exclusion, no other. MIRRORED, not imported/edited --
|
|
192
|
+
`tan/planner/` is a hash-audited upstream mirror, and that function reads
|
|
193
|
+
a parsed `Slice.os`, not a raw board.yaml dict.
|
|
194
|
+
|
|
195
|
+
A core the planner never builds a slice for cannot run this model's
|
|
196
|
+
inference regardless of what `libraries:`/`inference:` says about it --
|
|
197
|
+
a project-wide `libraries: [tflite-micro]` entry (no `cores:` scoping)
|
|
198
|
+
must not mark a parked `os: "off"` core as "running inference" just
|
|
199
|
+
because nothing narrows it away."""
|
|
200
|
+
return core_slice.get("os") != "off"
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _core_uses_inference(board_doc: dict, core_id: str, core_slice: dict) -> bool:
|
|
204
|
+
"""Does this PARTICIPATING (`_core_participates`) board.yaml core
|
|
205
|
+
genuinely run an inference workload? Mirrors `tan.planner.kconfig.
|
|
206
|
+
_slice_wants_inference`'s two independent signals (tan-cli#1288 review
|
|
207
|
+
round 2, finding 1) -- MIRRORED rather than imported: that function takes
|
|
208
|
+
a fully parsed `BoardProject`/`Slice` (`tan.core.system_manifest`), a
|
|
209
|
+
heavier object this module has no other reason to construct, and editing
|
|
210
|
+
`tan/planner/` itself is forbidden (it is a hash-audited upstream
|
|
211
|
+
mirror).
|
|
212
|
+
|
|
213
|
+
1. `cores.<core_id>.inference:` declared at all -- app-level tuning
|
|
214
|
+
(`default_arena_kib:`), the signal every inference example declares.
|
|
215
|
+
2. `libraries:` names `tflite-micro` -- the board.schema.json shape: a
|
|
216
|
+
TOP-LEVEL list, project-wide unless an entry's own `cores:` scopes it
|
|
217
|
+
to include this core (`core_entry`'s schema is `additionalProperties:
|
|
218
|
+
false` with no per-core `libraries:` property of its own -- tan-cli
|
|
219
|
+
#1288 review round 3, finding 2 -- so this is the ONLY shape to read).
|
|
220
|
+
The library signal exists because `inference:` is TUNING, not a
|
|
221
|
+
declaration of intent: an app that never overrides the arena default
|
|
222
|
+
has no reason to write the block at all (`_slice_wants_inference`'s
|
|
223
|
+
own docstring, alp-sdk #874). Matched on the LITERAL name only, never
|
|
224
|
+
the planner's own library-alias table (`library-aliases-v1.json`) --
|
|
225
|
+
and provably safe to: the schema's own library-name pattern
|
|
226
|
+
(`^[a-z][a-z0-9-]*$`, board.schema.json's `libraries[].name`) forbids
|
|
227
|
+
underscores, so the alias table's ONLY `tflite-micro` alias
|
|
228
|
+
(`"tflite_micro": "tflite-micro"`) can never appear as a `libraries:`
|
|
229
|
+
entry in any schema-valid board.yaml in the first place -- the
|
|
230
|
+
canonical spelling is the only one reachable here, not merely the
|
|
231
|
+
only one observed.
|
|
232
|
+
|
|
233
|
+
A core failing BOTH is exactly the AEN801-template shape a `paired_core`
|
|
234
|
+
could still name without this guard: `board.yaml` declaring the core key
|
|
235
|
+
(e.g. for an unrelated peripheral) is not the same fact as the core
|
|
236
|
+
running THIS NPU's model."""
|
|
237
|
+
if isinstance(core_slice.get("inference"), dict):
|
|
238
|
+
return True
|
|
239
|
+
raw_libraries = board_doc.get("libraries")
|
|
240
|
+
libraries = raw_libraries if isinstance(raw_libraries, list) else []
|
|
241
|
+
for entry in libraries:
|
|
242
|
+
if isinstance(entry, str):
|
|
243
|
+
name, scoped_cores = entry, None
|
|
244
|
+
elif isinstance(entry, dict):
|
|
245
|
+
name, scoped_cores = entry.get("name"), entry.get("cores")
|
|
246
|
+
else:
|
|
247
|
+
continue
|
|
248
|
+
if name != "tflite-micro":
|
|
249
|
+
continue
|
|
250
|
+
if scoped_cores is None:
|
|
251
|
+
return True # project-wide: every PARTICIPATING core (caller filters `os: "off"`)
|
|
252
|
+
if isinstance(scoped_cores, list) and core_id in scoped_cores:
|
|
253
|
+
return True
|
|
254
|
+
return False
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def resolve_arena_budget(board_doc: dict | None, core_id: str | None) -> ArenaBudget:
|
|
258
|
+
"""The arena budget for one ethos_u target, scoped to exactly what
|
|
259
|
+
board.yaml actually says (tan-cli#1288 review, rounds 1 and 2):
|
|
260
|
+
|
|
261
|
+
1. @core_id resolved (`TargetSpec.paired_core`) AND that core is declared
|
|
262
|
+
under board.yaml's `cores:`, PARTICIPATES (`_core_participates`: not
|
|
263
|
+
parked `os: "off"`), AND runs an inference workload
|
|
264
|
+
(`_core_uses_inference`) -- `"single"`, CERTAIN: THIS core is what
|
|
265
|
+
runs this NPU, full stop.
|
|
266
|
+
2. @core_id resolved but NOT declared under `cores:`, declared but parked
|
|
267
|
+
(`os: "off"`), or declared-and-participating but running no inference
|
|
268
|
+
at all -- `"unresolved"`: nothing in this project runs THIS model on
|
|
269
|
+
that core, so a board arena figure has no core to attach to. Never a
|
|
270
|
+
`DEFAULT_ARENA_KIB`-based guess.
|
|
271
|
+
3. @core_id is `None` (a shared NPU with no pairing of its own, e.g. the
|
|
272
|
+
E8/E6/E4's own Ethos-U85) -- collect every board.yaml core that
|
|
273
|
+
PARTICIPATES and runs an inference workload (the only honest way to
|
|
274
|
+
guess "which core(s) might run this", absent a real pairing): exactly
|
|
275
|
+
one -- `"single"`, CERTAIN, use it; two or more -- `"range"` (the
|
|
276
|
+
caller decides what a `NO_FIT`/`FIT_UNVERIFIED` bound can honestly
|
|
277
|
+
claim across them); none at all -- `"unresolved"` (a bare
|
|
278
|
+
`DEFAULT_ARENA_KIB` here would be a number about nothing declared,
|
|
279
|
+
never certain enough to refuse a build on off one exact figure). A
|
|
280
|
+
parked `os: "off"` core is excluded here EVEN when a project-wide
|
|
281
|
+
`libraries:` entry (no `cores:` scoping) would otherwise mark it --
|
|
282
|
+
the planner never builds it a slice at all, so it cannot be the core
|
|
283
|
+
that runs this model regardless of what the library list says
|
|
284
|
+
(tan-cli#1288 review round 3, finding 1: the AEN801 template's own
|
|
285
|
+
`a32_cluster: {os: "off"}` used to count).
|
|
286
|
+
4. @board_doc itself absent -- `"unresolved"`, same reasoning as 3's
|
|
287
|
+
empty case: nothing to inspect at all."""
|
|
288
|
+
if not isinstance(board_doc, dict):
|
|
289
|
+
return ArenaBudget(kind="unresolved",
|
|
290
|
+
reason="no board.yaml in hand; arena/SRAM0 residency not checked")
|
|
291
|
+
raw_cores = board_doc.get("cores")
|
|
292
|
+
cores = raw_cores if isinstance(raw_cores, dict) else {}
|
|
293
|
+
|
|
294
|
+
if core_id:
|
|
295
|
+
slice_ = cores.get(core_id)
|
|
296
|
+
if not isinstance(slice_, dict):
|
|
297
|
+
return ArenaBudget(kind="unresolved", reason=(
|
|
298
|
+
f"core {core_id!r} not declared in board.yaml; nothing in "
|
|
299
|
+
f"this project runs on it"))
|
|
300
|
+
if not _core_participates(slice_):
|
|
301
|
+
return ArenaBudget(kind="unresolved", reason=(
|
|
302
|
+
f"core {core_id!r} is parked (os: \"off\"); nothing in this "
|
|
303
|
+
f"project runs on it"))
|
|
304
|
+
if not _core_uses_inference(board_doc, core_id, slice_):
|
|
305
|
+
return ArenaBudget(kind="unresolved", reason=(
|
|
306
|
+
f"core {core_id!r} is declared in board.yaml but runs no "
|
|
307
|
+
f"inference workload (no inference: block, no tflite-micro "
|
|
308
|
+
f"library); nothing in this project runs THIS model on it"))
|
|
309
|
+
return ArenaBudget(kind="single", single_kib=_core_arena_kib_or_default(slice_),
|
|
310
|
+
cores=(core_id,))
|
|
311
|
+
|
|
312
|
+
inference_cores = sorted(
|
|
313
|
+
name for name, slice_ in cores.items()
|
|
314
|
+
if isinstance(slice_, dict) and _core_participates(slice_)
|
|
315
|
+
and _core_uses_inference(board_doc, name, slice_)
|
|
316
|
+
)
|
|
317
|
+
if not inference_cores:
|
|
318
|
+
return ArenaBudget(kind="unresolved", reason=(
|
|
319
|
+
"board.yaml declares no core running an inference workload; "
|
|
320
|
+
"arena/SRAM0 residency not checked"))
|
|
321
|
+
budgets = {name: _core_arena_kib_or_default(cores[name]) for name in inference_cores}
|
|
322
|
+
if len(inference_cores) == 1:
|
|
323
|
+
only = inference_cores[0]
|
|
324
|
+
return ArenaBudget(kind="single", single_kib=budgets[only], cores=(only,))
|
|
325
|
+
return ArenaBudget(kind="range", min_kib=min(budgets.values()),
|
|
326
|
+
max_kib=max(budgets.values()), cores=tuple(inference_cores))
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def resolve_sram0_kib(sku: str, metadata_root: Path) -> tuple[float | None, str | None]:
|
|
330
|
+
"""`(sram0_kib, None)` off the SoC-JSON variant @sku resolves to, or
|
|
331
|
+
`(None, reason)` when any leg of that resolution fails.
|
|
332
|
+
|
|
333
|
+
Reused, not re-walked: `tan.commands.build_output.read_sdk_som_and_soc`
|
|
334
|
+
is the SAME metadata-layout walk `tan size`/`tan debug-config` already
|
|
335
|
+
use (its own docstring: "must call ONE reader, not two that could
|
|
336
|
+
disagree"), and `tan.core.size.resolve_variant`/`sram_banks` are the SAME
|
|
337
|
+
pure variant/bank resolution `tan size`'s own budget uses -- this module
|
|
338
|
+
adds no second, potentially-drifting reader."""
|
|
339
|
+
walked = read_sdk_som_and_soc(str(metadata_root), sku)
|
|
340
|
+
if walked is None:
|
|
341
|
+
return None, f"unreadable SoM preset for {sku}; SRAM0 residency not checked"
|
|
342
|
+
_silicon, silicon_variant, variants, _soc_flash_mb, _soc_cores = walked
|
|
343
|
+
variant = resolve_variant(silicon_variant, sku, variants)
|
|
344
|
+
if variant is None:
|
|
345
|
+
return None, f"no SoC variant resolved for {sku}; SRAM0 residency not checked"
|
|
346
|
+
for name, kib in sram_banks(variant):
|
|
347
|
+
if name == "SRAM0":
|
|
348
|
+
return kib, None
|
|
349
|
+
return None, (f"the SoC variant resolved for {sku} declares no SRAM0 bank; "
|
|
350
|
+
f"SRAM0 residency not checked")
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def _evaluate_sram0_single(blob_kib: int, arena_kib: int, sram0_total: float | None,
|
|
354
|
+
sram0_reason: str | None) -> FitVerdict:
|
|
355
|
+
"""`blob_kib + arena_kib` against @sram0_total, for a `"single"` (CERTAIN)
|
|
356
|
+
arena resolution -- both sides of the sum are exact, sourced numbers."""
|
|
357
|
+
if sram0_total is None:
|
|
358
|
+
return FitVerdict(verdict=SKIPPED, reason=sram0_reason)
|
|
359
|
+
needed = blob_kib + arena_kib
|
|
360
|
+
limit_kib = int(sram0_total)
|
|
361
|
+
if needed > sram0_total:
|
|
362
|
+
return FitVerdict(verdict=NO_FIT, needed_kib=needed, limit_kib=limit_kib)
|
|
363
|
+
return FitVerdict(
|
|
364
|
+
verdict=FIT_UNVERIFIED, needed_kib=needed, limit_kib=limit_kib,
|
|
365
|
+
reason=(f"blob {blob_kib} + arena {arena_kib} = {needed} KiB of SRAM0; "
|
|
366
|
+
f"firmware's own SRAM0 use not checked"),
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def _evaluate_sram0_range(blob_kib: int, budget: ArenaBudget, sram0_total: float | None,
|
|
371
|
+
sram0_reason: str | None) -> FitVerdict:
|
|
372
|
+
"""The `"range"` shape's own SRAM0 three-way split (tan-cli#1288 review
|
|
373
|
+
round 2, finding 3) -- NOT the same two-way split `_evaluate_sram0_single`
|
|
374
|
+
uses, because neither bound alone may assert BOTH directions here:
|
|
375
|
+
|
|
376
|
+
* `NO_FIT` iff `blob_kib + min > total` -- fails even at the SMALLEST
|
|
377
|
+
declared core's budget, so every other candidate (which only needs
|
|
378
|
+
MORE SRAM0) fails too: a certain lower bound.
|
|
379
|
+
* `FIT_UNVERIFIED` iff `blob_kib + max <= total` -- fits even at the
|
|
380
|
+
LARGEST declared core's budget, so every candidate fits: as certain a
|
|
381
|
+
pass as any `FIT_UNVERIFIED` gets.
|
|
382
|
+
* Otherwise `SKIPPED`: some candidates would fit, others would not, and
|
|
383
|
+
nothing sourced picks between them -- asserting either word here would
|
|
384
|
+
overclaim (the exact defect round 2 found: the old code asserted
|
|
385
|
+
`FIT_UNVERIFIED` off the MIN alone, silently passing a blob that could
|
|
386
|
+
genuinely fail under a different, equally-plausible core)."""
|
|
387
|
+
if sram0_total is None:
|
|
388
|
+
return FitVerdict(verdict=SKIPPED, reason=sram0_reason)
|
|
389
|
+
limit_kib = int(sram0_total)
|
|
390
|
+
names = ", ".join(budget.cores)
|
|
391
|
+
needed_lo = blob_kib + budget.min_kib
|
|
392
|
+
needed_hi = blob_kib + budget.max_kib
|
|
393
|
+
if needed_lo > sram0_total:
|
|
394
|
+
return FitVerdict(
|
|
395
|
+
verdict=NO_FIT, needed_kib=needed_lo, limit_kib=limit_kib,
|
|
396
|
+
reason=(f"needs at least {needed_lo} KiB of SRAM0 even at the smallest "
|
|
397
|
+
f"declared core budget ({budget.min_kib} KiB across cores {names})"),
|
|
398
|
+
)
|
|
399
|
+
if needed_hi <= sram0_total:
|
|
400
|
+
return FitVerdict(
|
|
401
|
+
verdict=FIT_UNVERIFIED, needed_kib=needed_hi, limit_kib=limit_kib,
|
|
402
|
+
reason=(f"blob {blob_kib} + arena {budget.max_kib} KiB (the LARGEST declared "
|
|
403
|
+
f"core budget across cores {names}) = {needed_hi} KiB of SRAM0; "
|
|
404
|
+
f"firmware's own SRAM0 use not checked"),
|
|
405
|
+
)
|
|
406
|
+
return FitVerdict(verdict=SKIPPED, reason=(
|
|
407
|
+
f"ambiguous across cores {names} (declared budgets {budget.min_kib}-"
|
|
408
|
+
f"{budget.max_kib} KiB, needing {needed_lo}-{needed_hi} of {limit_kib} KiB "
|
|
409
|
+
f"SRAM0) with no paired_core to narrow which one actually runs this NPU"
|
|
410
|
+
))
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _evaluate_sram0_lower_bound(blob_kib: int, reason: str,
|
|
414
|
+
sram0_total: float | None,
|
|
415
|
+
sram0_reason: str | None) -> FitVerdict:
|
|
416
|
+
"""The `"unresolved"` shape's own SRAM0 check (tan-cli#1288 review round
|
|
417
|
+
2, finding 2): with NO board.yaml core resolved at all, the real arena is
|
|
418
|
+
still bounded below by `MIN_ARENA_KIB` (`board.schema.json`'s own
|
|
419
|
+
`minimum`) -- so `blob_kib + MIN_ARENA_KIB` is a valid certain LOWER
|
|
420
|
+
BOUND a `NO_FIT` can be proven against, even though nothing here can
|
|
421
|
+
assert a PASS: the real arena could be arbitrarily larger. Only ever
|
|
422
|
+
`NO_FIT` or `SKIPPED`, NEVER `FIT_UNVERIFIED`."""
|
|
423
|
+
if sram0_total is None:
|
|
424
|
+
return FitVerdict(verdict=SKIPPED, reason=sram0_reason)
|
|
425
|
+
needed = blob_kib + MIN_ARENA_KIB
|
|
426
|
+
limit_kib = int(sram0_total)
|
|
427
|
+
if needed > sram0_total:
|
|
428
|
+
return FitVerdict(
|
|
429
|
+
verdict=NO_FIT, needed_kib=needed, limit_kib=limit_kib,
|
|
430
|
+
reason=(f"needs at least {needed} KiB of SRAM0 even at the smallest "
|
|
431
|
+
f"possible arena ({MIN_ARENA_KIB} KiB, board.schema.json's own "
|
|
432
|
+
f"default_arena_kib minimum) -- {reason}"),
|
|
433
|
+
)
|
|
434
|
+
return FitVerdict(verdict=SKIPPED, reason=reason)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def _evaluate_arena_single(req_sram_kib: int, arena_kib: int) -> FitVerdict:
|
|
438
|
+
if req_sram_kib > arena_kib:
|
|
439
|
+
return FitVerdict(verdict=NO_FIT, needed_kib=req_sram_kib, limit_kib=arena_kib)
|
|
440
|
+
return FitVerdict(verdict=FIT_UNVERIFIED, needed_kib=req_sram_kib, limit_kib=arena_kib,
|
|
441
|
+
reason=_ARENA_UNVERIFIED_REASON)
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def _evaluate_arena_range(req_sram_kib: int, budget: ArenaBudget) -> FitVerdict:
|
|
445
|
+
lo, hi, names = budget.min_kib, budget.max_kib, ", ".join(budget.cores)
|
|
446
|
+
if req_sram_kib > hi:
|
|
447
|
+
return FitVerdict(
|
|
448
|
+
verdict=NO_FIT, needed_kib=req_sram_kib, limit_kib=hi,
|
|
449
|
+
reason=(f"exceeds even the largest declared core budget ({hi} KiB, "
|
|
450
|
+
f"across cores {names} with no paired_core to narrow which "
|
|
451
|
+
f"one actually runs this NPU)"),
|
|
452
|
+
)
|
|
453
|
+
if req_sram_kib <= lo:
|
|
454
|
+
return FitVerdict(
|
|
455
|
+
verdict=FIT_UNVERIFIED, needed_kib=req_sram_kib, limit_kib=lo,
|
|
456
|
+
reason=(f"{_ARENA_UNVERIFIED_REASON} (fits every declared core's budget, "
|
|
457
|
+
f"{lo}-{hi} KiB across cores {names})"),
|
|
458
|
+
)
|
|
459
|
+
return FitVerdict(verdict=SKIPPED, reason=(
|
|
460
|
+
f"ambiguous across cores {names} (declared budgets {lo}-{hi} KiB) with no "
|
|
461
|
+
f"paired_core to narrow which one actually runs this NPU; needs {req_sram_kib} KiB"
|
|
462
|
+
))
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def evaluate_sram_fit(*, memory_mode: str | None, req_sram_kib: int, blob_len_bytes: int,
|
|
466
|
+
board_doc: dict | None, paired_core: str | None,
|
|
467
|
+
sku: str, metadata_root: Path) -> SramFit:
|
|
468
|
+
"""The whole check for one compiled ethos_u target.
|
|
469
|
+
|
|
470
|
+
`req_sram_kib` is the arena, already ceil'd
|
|
471
|
+
(`tan.model.adapters.ethos_u._footprint`, tan-cli#1011's own arena-only
|
|
472
|
+
scope -- never re-derived here). `blob_len_bytes` is `len(Blob.payload)`,
|
|
473
|
+
the compiled weights -- the SECOND half of `Sram_Only` residency #1288
|
|
474
|
+
exists to check, which nothing read before this module.
|
|
475
|
+
|
|
476
|
+
`SramFit.no_fit` is what `tan model build` refuses on; `SramFit.as_dict()`
|
|
477
|
+
is the `sramFit` envelope block `tan model check --exact` attaches.
|
|
478
|
+
`resolve_arena_budget`'s own docstring is the source of truth for exactly
|
|
479
|
+
which of its three shapes may ever produce a `NO_FIT` here."""
|
|
480
|
+
if memory_mode != SRAM_ONLY_MODE:
|
|
481
|
+
return _skipped_not_sram_only(memory_mode)
|
|
482
|
+
|
|
483
|
+
budget = resolve_arena_budget(board_doc, paired_core)
|
|
484
|
+
blob_kib = math.ceil(blob_len_bytes / 1024) if blob_len_bytes > 0 else 0
|
|
485
|
+
sram0_total, sram0_reason = resolve_sram0_kib(sku, metadata_root)
|
|
486
|
+
|
|
487
|
+
if budget.kind == "unresolved":
|
|
488
|
+
arena = FitVerdict(verdict=SKIPPED, reason=budget.reason)
|
|
489
|
+
sram0 = _evaluate_sram0_lower_bound(blob_kib, budget.reason, sram0_total, sram0_reason)
|
|
490
|
+
return SramFit(arena=arena, sram0=sram0, blob_kib=blob_kib)
|
|
491
|
+
|
|
492
|
+
if budget.kind == "single":
|
|
493
|
+
arena = _evaluate_arena_single(req_sram_kib, budget.single_kib)
|
|
494
|
+
sram0 = _evaluate_sram0_single(blob_kib, budget.single_kib, sram0_total, sram0_reason)
|
|
495
|
+
return SramFit(arena=arena, sram0=sram0, blob_kib=blob_kib)
|
|
496
|
+
|
|
497
|
+
# budget.kind == "range": paired_core is None and MORE THAN ONE board core
|
|
498
|
+
# runs inference -- see `_evaluate_sram0_range`'s own docstring for the
|
|
499
|
+
# three-way split it uses instead of `_evaluate_sram0_single`'s two-way one.
|
|
500
|
+
arena = _evaluate_arena_range(req_sram_kib, budget)
|
|
501
|
+
sram0 = _evaluate_sram0_range(blob_kib, budget, sram0_total, sram0_reason)
|
|
502
|
+
return SramFit(arena=arena, sram0=sram0, blob_kib=blob_kib)
|