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/core/mram_link.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Pure logic for Flow D's link-address guard (tan-cli#1371).
|
|
3
|
+
|
|
4
|
+
An image destined for the app's MRAM slot must be LOADed at or above that slot. An
|
|
5
|
+
ITCM-linked image (`p_paddr` 0x0) written there is the mistake this catches, and the
|
|
6
|
+
manifest's `flash_method: ram_run_only` only stops it when the manifest is current --
|
|
7
|
+
a hand-edited manifest, a build whose manifest write failed, or a direct backend call
|
|
8
|
+
bypass it. It is the mirror of `flash.ram-image-not-ram-linked` (`tan.core.ram_run`).
|
|
9
|
+
|
|
10
|
+
Only the program headers are read, with no IO: the symbol table (and its limits) is
|
|
11
|
+
irrelevant to a load address and must neither skip nor refuse the check. `p_paddr` is
|
|
12
|
+
the LOAD address; `p_vaddr` is where the image runs, and an image loaded into MRAM may
|
|
13
|
+
legitimately run from ITCM.
|
|
14
|
+
|
|
15
|
+
**Lower bound only.** An image is refused when it is loaded BELOW its slot, never for
|
|
16
|
+
being above it. An image linked above its own slot -- say an M55-HP image at 0x802B0000
|
|
17
|
+
written to the M55-HE slot 0x80010000 -- is NOT caught: that needs per-core slot windows
|
|
18
|
+
this guard does not have. Equality with the slot is not required either, because an image
|
|
19
|
+
header may offset the first segment."""
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import struct
|
|
23
|
+
|
|
24
|
+
#: Registered issue code (`contract/issue-codes.json`).
|
|
25
|
+
CODE_NOT_MRAM_LINKED = "flash.mram-image-not-mram-linked"
|
|
26
|
+
|
|
27
|
+
_ELF_HEADER_SIZE = 52
|
|
28
|
+
_PHDR_SIZE = 32
|
|
29
|
+
_PT_LOAD = 1
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class UnreadableElf(Exception):
|
|
33
|
+
"""The program headers cannot be read; the message is the reason."""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def lowest_load_paddr(data: bytes) -> int:
|
|
37
|
+
"""The lowest `p_paddr` among LOAD segments with a nonzero `p_filesz` (never "the
|
|
38
|
+
first LOAD": a zero-FileSiz `.bss` in DTCM is often listed first). Raises
|
|
39
|
+
[`UnreadableElf`] for a 64-bit or big-endian file, a truncated header, a program
|
|
40
|
+
header table past the end of the file, or no such segment."""
|
|
41
|
+
if len(data) < _ELF_HEADER_SIZE:
|
|
42
|
+
raise UnreadableElf(f"truncated: {len(data)} B is shorter than an ELF32 header")
|
|
43
|
+
if data[4] != 1:
|
|
44
|
+
raise UnreadableElf("it is not an ELF32 file (only ELF32 images are supported)")
|
|
45
|
+
if data[5] != 1:
|
|
46
|
+
raise UnreadableElf("it is not little-endian (only little-endian images are supported)")
|
|
47
|
+
phoff = struct.unpack_from("<I", data, 28)[0]
|
|
48
|
+
phentsize, phnum = struct.unpack_from("<HH", data, 42)
|
|
49
|
+
if phnum and phentsize < _PHDR_SIZE:
|
|
50
|
+
raise UnreadableElf(f"its program header entry size is {phentsize} B (an Elf32_Phdr is 32)")
|
|
51
|
+
if phoff + phnum * phentsize > len(data):
|
|
52
|
+
raise UnreadableElf(
|
|
53
|
+
f"its {phnum} program headers at offset {phoff} run past the end of the file "
|
|
54
|
+
f"({len(data)} B)"
|
|
55
|
+
)
|
|
56
|
+
loadable = []
|
|
57
|
+
for i in range(phnum):
|
|
58
|
+
p_type, _off, _vaddr, p_paddr, p_filesz, _memsz = struct.unpack_from(
|
|
59
|
+
"<IIIIII", data, phoff + i * phentsize
|
|
60
|
+
)
|
|
61
|
+
if p_type == _PT_LOAD and p_filesz:
|
|
62
|
+
loadable.append(p_paddr)
|
|
63
|
+
if not loadable:
|
|
64
|
+
raise UnreadableElf("it has no LOAD segment with file content")
|
|
65
|
+
return min(loadable)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def mram_link_refusal(data: bytes, floor: int, floor_name: str) -> str | None:
|
|
69
|
+
"""A refusal message when the ELF in `data` is not loaded at or above `floor`, else
|
|
70
|
+
`None`. `floor` is the entry's `slot0_load_address`;
|
|
71
|
+
`floor_name` names it for the message. An ELF whose program headers cannot be
|
|
72
|
+
read is REFUSED, never waved through: the guard exists for the image nothing else
|
|
73
|
+
caught. Equality is not required -- an image header may offset the first segment."""
|
|
74
|
+
try:
|
|
75
|
+
lowest = lowest_load_paddr(data)
|
|
76
|
+
except UnreadableElf as err:
|
|
77
|
+
return (
|
|
78
|
+
f"the artefact's ELF could not be parsed ({err}), so its load address cannot be "
|
|
79
|
+
"verified against the MRAM slot -- refusing to write an unchecked image."
|
|
80
|
+
)
|
|
81
|
+
if lowest >= floor:
|
|
82
|
+
return None
|
|
83
|
+
return (
|
|
84
|
+
f"the image's lowest LOAD segment (p_paddr) is at 0x{lowest:X}, below {floor_name} "
|
|
85
|
+
f"0x{floor:X}: it is not linked for that MRAM slot (an ITCM/RAM-linked build). "
|
|
86
|
+
"Rebuild it MRAM-linked, or RAM-run it with `tan flash --ram` (Flow C)."
|
|
87
|
+
)
|
tan/core/new_som_ospi.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""`ospi_memories:`/`hyperram:` `on_module:` scaffold lines for `tan new-som`
|
|
3
|
+
(tan-cli#1220).
|
|
4
|
+
|
|
5
|
+
Split into its own module rather than grown inside `new_som_cmd.py`: that
|
|
6
|
+
file already sits at its `tests/gates/module_size_budget.d/` ceiling
|
|
7
|
+
(`MODULE_CAP`), and PR #1222 set the precedent for this exact situation --
|
|
8
|
+
`model_cmd.py` at its own ceiling got a new `tan/core/model_list.py` for the
|
|
9
|
+
actual engine, keeping the command file's own growth to dispatch/glue. This
|
|
10
|
+
module is the `_render_preset` sibling: pure string rendering, no IO, no
|
|
11
|
+
click/typer.
|
|
12
|
+
|
|
13
|
+
Scoped to Alif Ensemble parts only. The caller (`_render_preset`) already
|
|
14
|
+
splits every `soc_ref` into `vendor_slug, family_slug, part_slug` for its own
|
|
15
|
+
`metadata/socs/...` path -- `is_alif_ensemble` reads that SAME triple rather
|
|
16
|
+
than adding a second vendor/family discrimination mechanism (tan-cli#1220's
|
|
17
|
+
own instruction: reuse how `new_som_cmd.py` already tells vendors/families
|
|
18
|
+
apart, don't invent a new one). The scope itself is not arbitrary: the
|
|
19
|
+
schema's own `on_module.ospi_memories` description reads "(AEN family)"
|
|
20
|
+
(`metadata/schemas/som-preset-v2.schema.json`), and every shipped
|
|
21
|
+
`E1M-AEN*.yaml` preset that populates these fields (301/401/501/601/701/801)
|
|
22
|
+
uses the identical CS0=NOR/CS1=HyperRAM split on one shared octal OSPI0
|
|
23
|
+
controller.
|
|
24
|
+
|
|
25
|
+
Every value below is either that shared Ensemble-silicon convention
|
|
26
|
+
(`chip_select`, `interface`) or an explicit placeholder -- never an invented
|
|
27
|
+
MPN or capacity (ADR-0017, invariant I-26). Two of the schema's fields have
|
|
28
|
+
no `"TBD"` string escape hatch the way `chip`/`capacity_mbit` do:
|
|
29
|
+
`chip_select` is a bare `"type": "integer"` (no `oneOf` alternative), and
|
|
30
|
+
`role`/`interface` must match a lowercase-slug pattern (`^[a-z][a-z0-9_]*$`,
|
|
31
|
+
which an uppercase `TBD` fails). The convention value fills `chip_select`/
|
|
32
|
+
`interface`; the schema-legal lowercase literal `tbd` fills `role`, matching
|
|
33
|
+
`inference.auto_order`'s own `[tbd]` placeholder convention next door in
|
|
34
|
+
`_render_preset`.
|
|
35
|
+
|
|
36
|
+
`chip_select`'s only bound in `metadata/schemas/som-preset-v2.schema.json` is
|
|
37
|
+
`"minimum": 0` -- there is no schema maximum, and no `scripts/check_*.py`
|
|
38
|
+
gate in the pinned alp-sdk checkout mentions `chip_select` at all (measured:
|
|
39
|
+
`grep -rn chip_select scripts/` finds nothing). "Bounded to 0 or 1" is a
|
|
40
|
+
description of the real OSPI0 controller's two physical chip-selects, not an
|
|
41
|
+
enforced ceiling; nothing stops a preset from declaring `chip_select: 2` and
|
|
42
|
+
still passing every automated check in this checkout.
|
|
43
|
+
|
|
44
|
+
`assembled:` (optional on both `$defs`, and also has no `"TBD"` variant) is
|
|
45
|
+
left out of the rendered block entirely -- the schema's own default when
|
|
46
|
+
omitted is `true` (populated), which this scaffold has no basis to assert OR
|
|
47
|
+
deny for a module whose BOM does not exist yet. Matches how `_render_preset`
|
|
48
|
+
already treats every other schema-optional block it cannot safely guess
|
|
49
|
+
(`capabilities:`, `pad_routes:`, `helper_firmware:`): a comment, not a
|
|
50
|
+
guessed value.
|
|
51
|
+
"""
|
|
52
|
+
from __future__ import annotations
|
|
53
|
+
|
|
54
|
+
#: The one (vendor, family) pair `ospi_memories:`/`hyperram:` are scoped to
|
|
55
|
+
#: -- see the module docstring for why.
|
|
56
|
+
_ALIF_ENSEMBLE = ("alif", "ensemble")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def is_alif_ensemble(vendor_slug: str, family_slug: str) -> bool:
|
|
60
|
+
"""True when a `soc_ref`'s parsed `vendor_slug`/`family_slug` name an
|
|
61
|
+
Alif Ensemble part -- the one scope `render_ospi_memories_and_hyperram`
|
|
62
|
+
is meant to be called under."""
|
|
63
|
+
return (vendor_slug, family_slug) == _ALIF_ENSEMBLE
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def render_ospi_memories_and_hyperram() -> list[str]:
|
|
67
|
+
"""The `ospi_memories:`/`hyperram:` `on_module:` sub-block, as 2-space-
|
|
68
|
+
indented lines ready to append inside `_render_preset`'s `on_module:`
|
|
69
|
+
section (append after `eeprom:`, before the `i2c_devices:` comment
|
|
70
|
+
block -- the same relative order every shipped `E1M-AEN*.yaml` preset
|
|
71
|
+
that carries both keys uses). See the module docstring for what each
|
|
72
|
+
field's placeholder is and why.
|
|
73
|
+
"""
|
|
74
|
+
lines: list[str] = []
|
|
75
|
+
a = lines.append
|
|
76
|
+
a(" # OSPI0 dual-chip-select external memory (Ensemble-family")
|
|
77
|
+
a(" # convention): NOR flash on CS0, HyperRAM on CS1, sharing one")
|
|
78
|
+
a(" # shared octal bus. Delete the whole ospi_memories:/hyperram:")
|
|
79
|
+
a(" # pair if this module populates neither; add an ospi1: row")
|
|
80
|
+
a(" # alongside ospi0: for a second, independent OSPI controller.")
|
|
81
|
+
a(" ospi_memories:")
|
|
82
|
+
a(" ospi0:")
|
|
83
|
+
a(" chip: TBD # storage-part MPN (datasheet/BOM), or TBD")
|
|
84
|
+
a(" capacity_mbit: TBD # Mbit, from the storage-part datasheet")
|
|
85
|
+
a(" chip_select: 0 # OSPI0 CS0 (Ensemble-family convention)")
|
|
86
|
+
a(" role: tbd # what it's for, e.g. app_storage")
|
|
87
|
+
a(" # HyperRAM sharing the OSPI0 octal bus with the flash above,")
|
|
88
|
+
a(" # separated only by chip-select -- delete if not populated.")
|
|
89
|
+
a(" hyperram:")
|
|
90
|
+
a(" chip: TBD # HyperRAM MPN (datasheet/BOM), or TBD")
|
|
91
|
+
a(" capacity_mbit: TBD # Mbit, from the HyperRAM datasheet")
|
|
92
|
+
a(" interface: ospi0 # controller instance (Ensemble convention)")
|
|
93
|
+
a(" chip_select: 1 # OSPI0 CS1 (Ensemble-family convention)")
|
|
94
|
+
return lines
|
tan/core/os_class.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""The Cortex-A/Cortex-M -> OS convention (issue #95), free of `tan.planner`'s
|
|
3
|
+
binding requirement.
|
|
4
|
+
|
|
5
|
+
`tan.planner.topology` owns the AUTHORITATIVE version of this rule --
|
|
6
|
+
`_default_os_from_core_type` there is what `validate._enforce_os_matches_core_class`
|
|
7
|
+
gates a real build on, and it re-exports the same names this module defines so
|
|
8
|
+
every existing planner caller/test is unaffected by this file's existence
|
|
9
|
+
(`tan.planner.topology._default_os_from_core_type` and `.CLASS_RUNTIMES` are
|
|
10
|
+
now this module's `default_os_from_core_type`/`CLASS_RUNTIMES`, imported not
|
|
11
|
+
redefined).
|
|
12
|
+
|
|
13
|
+
**Why this lives OUTSIDE `tan.planner` (tan-cli#870).** `tan.planner` is a
|
|
14
|
+
process-global-bound package: importing ANY of its submodules requires
|
|
15
|
+
`tan.planner_root.bind_sdk_root(<checkout>)` to have already run, because
|
|
16
|
+
`tan/planner/__init__.py` -> `tan/planner/paths.py` reads `sdk_root()` at
|
|
17
|
+
IMPORT time and the binding is refuse-to-rebind for the rest of the process
|
|
18
|
+
(`tan/planner_root.py`'s own docstring). That is the right contract for `tan
|
|
19
|
+
build`'s single big planning pass per process. It is the WRONG contract for
|
|
20
|
+
`tan presets`, which is exercised by dozens of small, independent unit tests
|
|
21
|
+
in the SAME pytest process, each against its own disposable synthetic SDK
|
|
22
|
+
root: the first such test to reach a `tan.planner` import permanently binds
|
|
23
|
+
the whole worker process to ITS throwaway root, and every later test in that
|
|
24
|
+
worker that needs a DIFFERENT (often the real, `ALP_SDK_ROOT`-derived) root
|
|
25
|
+
then fails outright -- measured while building tan-cli#870: routing
|
|
26
|
+
`presets_cmd`'s SoC-type/`allowed_os` lookups through `tan.planner.topology`
|
|
27
|
+
turned a clean local bound suite run (5 failed, 0 errors, the 5 pre-existing
|
|
28
|
+
terminal-width/breadth-layer artefacts) into 292 `PlannerRootError` cascades
|
|
29
|
+
across `tests/parity/test_planner_emit_parity.py`, in a completely unrelated
|
|
30
|
+
file, purely from `tests/commands/test_presets_command.py`'s own throwaway
|
|
31
|
+
fixtures winning the race to bind first in their xdist worker.
|
|
32
|
+
|
|
33
|
+
So the RULE (this file) lives somewhere binding-free, and both consumers
|
|
34
|
+
import it: `tan.planner.topology` for the planner's own enforcement (still
|
|
35
|
+
combined there with `_core_os_choices`'s bound-tree schema fallback, which a
|
|
36
|
+
real build-time metadata root may legitimately need), and
|
|
37
|
+
`tan.commands.presets_cmd` for the wizard's `allowedOs` (combined there with a
|
|
38
|
+
plain, no-fallback read of the GIVEN checkout's own `board.schema.json` --
|
|
39
|
+
`presets` never has a "different bound tree" to fall back to; it is reading
|
|
40
|
+
exactly one checkout throughout). Neither consumer re-derives the convention;
|
|
41
|
+
both call this module.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
from __future__ import annotations
|
|
45
|
+
|
|
46
|
+
from collections.abc import Iterable
|
|
47
|
+
|
|
48
|
+
#: The two class-determined OS runtimes (issue #95): Cortex-A -> Yocto
|
|
49
|
+
#: (Linux), Cortex-M -> Zephyr (RTOS). These follow the core class and are NOT
|
|
50
|
+
#: user-selectable (see `default_os_from_core_type`); `baremetal` (no-OS) and
|
|
51
|
+
#: `off` (disabled) are the only values a board.yaml may set explicitly.
|
|
52
|
+
CLASS_RUNTIMES = ("yocto", "zephyr")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def default_os_from_core_type(core_type: str) -> str:
|
|
56
|
+
"""Infer default OS from a SoC's `cores[].type`.
|
|
57
|
+
|
|
58
|
+
Convention (codified across the SoM presets pre-2026-05-18):
|
|
59
|
+
cortex-a* -> yocto
|
|
60
|
+
cortex-m* -> zephyr
|
|
61
|
+
anything else -> off
|
|
62
|
+
|
|
63
|
+
Used as the fallback when a SoM preset's `topology.<core>.os` is
|
|
64
|
+
omitted (the field is optional in som-preset-v2.schema.json -- M-class
|
|
65
|
+
cores default to Zephyr, A-class to Yocto).
|
|
66
|
+
|
|
67
|
+
*core_type* is typed `str` but this is the SHARED entry point both
|
|
68
|
+
binding-free callers (`presets_cmd.allowed_os_lookup`) and the planner
|
|
69
|
+
(`topology._default_os_from_core_type`) reach with a value read off a
|
|
70
|
+
SoC JSON that a schema-invalid `--sdk-root`/`--metadata-root` tree makes
|
|
71
|
+
no promise about (tan-cli#957) -- `isinstance` here, not `str`-only trust
|
|
72
|
+
in the annotation, is the backstop for every caller, present or future,
|
|
73
|
+
that forwards a `cores[].type` value without its OWN guard. A non-string
|
|
74
|
+
normalises to the same `""` unresolved sentinel a genuinely absent
|
|
75
|
+
`type` already produces, rather than raising `AttributeError` on
|
|
76
|
+
`.lower()`.
|
|
77
|
+
"""
|
|
78
|
+
t = core_type.lower() if isinstance(core_type, str) else ""
|
|
79
|
+
if t.startswith("cortex-a"):
|
|
80
|
+
return "yocto"
|
|
81
|
+
if t.startswith("cortex-m"):
|
|
82
|
+
return "zephyr"
|
|
83
|
+
return "off"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def cross_class_os(core_type: str) -> set[str]:
|
|
87
|
+
"""The class runtime a core may NOT be set to -- the OS of the *other*
|
|
88
|
+
class. A Cortex-A can't run Zephyr; a Cortex-M can't run Yocto."""
|
|
89
|
+
return set(CLASS_RUNTIMES) - {default_os_from_core_type(core_type)}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def allowed_os_for_core(core_type: str, choices: Iterable[str]) -> list[str]:
|
|
93
|
+
"""The `os:` values valid for a core: *choices* (the checkout's own `os:`
|
|
94
|
+
enum) minus the cross-class OS -- e.g. Cortex-A -> `[yocto, baremetal,
|
|
95
|
+
off]`, Cortex-M -> `[zephyr, baremetal, off]`.
|
|
96
|
+
|
|
97
|
+
`core_type == ""` is the shared UNRESOLVED sentinel both callers of this
|
|
98
|
+
function use when a core's `type` could not be determined (no matching
|
|
99
|
+
SoC JSON on disk, no entry for this core id in it, a synthetic/partial
|
|
100
|
+
checkout, ...) -- `tan.planner.topology.core_os_topology`'s
|
|
101
|
+
`soc_types.get(core_id, "")` and `tan.commands.presets_cmd.read_soms`'s
|
|
102
|
+
`core_types.get(c.id, "")` both feed it here unchanged. Degrades to `[]`
|
|
103
|
+
rather than the plausible-but-wrong cross-class subtraction `cross_class_os("")`
|
|
104
|
+
would otherwise produce (tan-cli#914): `default_os_from_core_type("")`
|
|
105
|
+
falls through to `"off"`, so an UNRESOLVED type and a genuinely
|
|
106
|
+
off-class core type were indistinguishable at this call, and the
|
|
107
|
+
unresolved case silently offered Bare-metal for what may be a Cortex-M
|
|
108
|
+
core -- the exact defect #870 exists to close, reproduced by the very
|
|
109
|
+
field meant to prevent it. An unresolved type gets an unresolved (empty)
|
|
110
|
+
answer instead, for both the wizard's `presets_cmd.allowed_os_lookup` and
|
|
111
|
+
the build-time `topology._allowed_os_for_core` -- the one call this
|
|
112
|
+
module exists to share, so the two can never drift back apart on this.
|
|
113
|
+
|
|
114
|
+
A non-string *core_type* is ALSO treated as unresolved (tan-cli#957),
|
|
115
|
+
not just falsy ones: `default_os_from_core_type`'s own `isinstance`
|
|
116
|
+
guard already stops a truthy non-string (`7`, a populated list, `True`,
|
|
117
|
+
...) from raising here, but without this function's OWN `isinstance`
|
|
118
|
+
check that value would still reach `cross_class_os` truthy, fall through
|
|
119
|
+
`default_os_from_core_type`'s `"off"` default the same way `""` does,
|
|
120
|
+
and hand back the identical `["baremetal", "off"]` plausible-but-wrong
|
|
121
|
+
guess this docstring already rejects for the empty-string case --
|
|
122
|
+
the crash-vs-leak choice tan-cli#957 found in `core_type_lookup` shows
|
|
123
|
+
up here too, one level in, if this guard is skipped: a caller's own
|
|
124
|
+
`isinstance` at its read site is not something this shared function may
|
|
125
|
+
assume every present or future caller remembered to add.
|
|
126
|
+
"""
|
|
127
|
+
if not isinstance(core_type, str) or not core_type:
|
|
128
|
+
return []
|
|
129
|
+
cross = cross_class_os(core_type)
|
|
130
|
+
return [o for o in choices if o not in cross]
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def infer_runtime_for_core_id(core_id: str) -> str:
|
|
134
|
+
"""Best-effort runtime for a core id with no OS given: an `a` followed by
|
|
135
|
+
a digit at a WORD start (`a55_cluster`, `cluster_a32`, `A55`) runs
|
|
136
|
+
`yocto`; everything else defaults to `zephyr`.
|
|
137
|
+
|
|
138
|
+
Word starts are the string start and any position after `_`/`-`, so
|
|
139
|
+
`data55` is NOT Cortex-A. Mirrors
|
|
140
|
+
`tan_core::wizard::infer_runtime_for_core_id`, and KEEP IN SYNC with
|
|
141
|
+
alp-sdk-vscode's ConfiguratorView `coreSiliconClass` (same word-start
|
|
142
|
+
test; the one intentional difference is the fallback, since a CLI must
|
|
143
|
+
pick a concrete runtime where the IDE can offer "unknown").
|
|
144
|
+
|
|
145
|
+
A last resort: a topology that declares `board:`/`machine:` never reaches
|
|
146
|
+
it. Lives here rather than beside either caller because it had TWO
|
|
147
|
+
definitions -- `tan/core/scaffold.py` and `tan/commands/presets_cmd.py` --
|
|
148
|
+
and `tan/core/bootstrap.py` reached OUTWARD past the core one to import
|
|
149
|
+
the command's copy (tan-cli#408). One owner, in the leaf module that
|
|
150
|
+
already answers "what OS class is this core", is what the duplicated
|
|
151
|
+
docstrings each said they wanted.
|
|
152
|
+
"""
|
|
153
|
+
lowered = core_id.lower()
|
|
154
|
+
word_start = True
|
|
155
|
+
for i, ch in enumerate(lowered):
|
|
156
|
+
if word_start and ch == "a" and i + 1 < len(lowered) and lowered[i + 1].isdigit():
|
|
157
|
+
return "yocto"
|
|
158
|
+
word_start = ch in "_-"
|
|
159
|
+
return "zephyr"
|
tan/core/patch_paths.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Repo-relative paths a unified diff / `git format-patch` file touches.
|
|
3
|
+
|
|
4
|
+
Pure text in, paths out (tan-cli#1384). Used to fingerprint the working-tree
|
|
5
|
+
files alp-sdk's `zephyr/patches/` write, so the set must include every path a
|
|
6
|
+
patch can change on disk, not only the ones with a `+++ b/` line:
|
|
7
|
+
|
|
8
|
+
* `diff --git a/<old> b/<new>` names BOTH sides, which is the only place a pure
|
|
9
|
+
rename, a mode-only change or a binary patch is visible (none of them carry
|
|
10
|
+
`---`/`+++` lines);
|
|
11
|
+
* `--- a/<path>` names a file the patch deletes (`+++ /dev/null`);
|
|
12
|
+
* `+++ b/<path>` names a file it writes (`--- /dev/null` when it creates it).
|
|
13
|
+
|
|
14
|
+
`---`/`+++` count only as headers, never as hunk content: after a
|
|
15
|
+
`diff --git` line and before the first `@@`, or (for a plain `diff -u` with no
|
|
16
|
+
`diff --git` line) as a `---`, `+++`, `@@` triple.
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import re
|
|
21
|
+
|
|
22
|
+
_TOKEN = re.compile(r'"(?:[^"\\]|\\.)*"|\S+')
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _unquote(raw: str) -> str:
|
|
26
|
+
"""Undo git's C-style quoting (`"a/caf\\303\\251"`); plain paths pass through."""
|
|
27
|
+
raw = raw.strip()
|
|
28
|
+
if len(raw) >= 2 and raw[0] == raw[-1] == '"':
|
|
29
|
+
try:
|
|
30
|
+
return (
|
|
31
|
+
raw[1:-1].encode("utf-8").decode("unicode_escape")
|
|
32
|
+
.encode("latin-1").decode("utf-8", "replace")
|
|
33
|
+
)
|
|
34
|
+
except (UnicodeError, ValueError):
|
|
35
|
+
return raw[1:-1]
|
|
36
|
+
return raw
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _side(raw: str, prefix: str) -> str | None:
|
|
40
|
+
"""The path on one side of a header, or `None` (`/dev/null`, no prefix)."""
|
|
41
|
+
path = _unquote(raw.split("\t", 1)[0]).strip()
|
|
42
|
+
if path == "/dev/null" or not path.startswith(prefix):
|
|
43
|
+
return None
|
|
44
|
+
return path[len(prefix):] or None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _git_header(rest: str) -> list[str]:
|
|
48
|
+
"""Both paths of `diff --git a/<old> b/<new>` (the text after `diff --git `)."""
|
|
49
|
+
if '"' in rest:
|
|
50
|
+
tokens = _TOKEN.findall(rest)
|
|
51
|
+
if len(tokens) == 2:
|
|
52
|
+
return [p for p in (_side(tokens[0], "a/"), _side(tokens[1], "b/")) if p]
|
|
53
|
+
half = (len(rest) - 1) // 2
|
|
54
|
+
if len(rest) % 2 == 1 and rest[:half].startswith("a/") and rest[half] == " " \
|
|
55
|
+
and rest[half + 1:] == "b/" + rest[:half][2:]:
|
|
56
|
+
return [rest[:half][2:]] # unrenamed: both sides are the same path
|
|
57
|
+
old, sep, new = rest.partition(" b/")
|
|
58
|
+
return [p for p in (_side(old, "a/"), new if sep else None) if p]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def patch_paths(text: str) -> set[str]:
|
|
62
|
+
"""Every repo-relative path `text` (one patch file) touches."""
|
|
63
|
+
lines = text.splitlines()
|
|
64
|
+
paths: set[str] = set()
|
|
65
|
+
in_header = False
|
|
66
|
+
for i, ln in enumerate(lines):
|
|
67
|
+
if ln.startswith("diff --git "):
|
|
68
|
+
in_header = True
|
|
69
|
+
paths.update(_git_header(ln[len("diff --git "):]))
|
|
70
|
+
elif ln.startswith("@@"):
|
|
71
|
+
in_header = False
|
|
72
|
+
elif ln.startswith(("--- ", "+++ ")):
|
|
73
|
+
plain_triple = (
|
|
74
|
+
ln.startswith("--- ")
|
|
75
|
+
and i + 2 < len(lines)
|
|
76
|
+
and lines[i + 1].startswith("+++ ")
|
|
77
|
+
and lines[i + 2].startswith("@@")
|
|
78
|
+
)
|
|
79
|
+
if in_header or plain_triple:
|
|
80
|
+
side = _side(ln[4:], "a/" if ln[0] == "-" else "b/")
|
|
81
|
+
if side:
|
|
82
|
+
paths.add(side)
|
|
83
|
+
if plain_triple:
|
|
84
|
+
plus = _side(lines[i + 1][4:], "b/")
|
|
85
|
+
if plus:
|
|
86
|
+
paths.add(plus)
|
|
87
|
+
return paths
|
tan/core/pending.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""The one definition of alp-sdk's `TBD` pending-placeholder sentinel (#276).
|
|
3
|
+
|
|
4
|
+
alp-sdk writes the literal string `TBD` into a manifest/preset field it has
|
|
5
|
+
not filled in yet -- `flash_args: TBD`, `silicon_variant: TBD`, an unfilled
|
|
6
|
+
`e1m_pad` -- a deliberate "don't invent a value" convention, not an error.
|
|
7
|
+
Every reader of that convention has to agree on what "unfilled" means, so the
|
|
8
|
+
rule lives here ONCE rather than being re-typed at each read site.
|
|
9
|
+
|
|
10
|
+
This module is deliberately the neutral one: it has no flash-planning
|
|
11
|
+
machinery (`tan.core.flash_plan`) and no image-bundle machinery
|
|
12
|
+
(`tan.core.image_bundle`) behind it, so a consumer that is neither -- `tan.core.size`,
|
|
13
|
+
and `pinmux` once it is ported -- can test for the sentinel without pulling
|
|
14
|
+
either domain in. `flash_plan` still owns the flash-specific decisions built
|
|
15
|
+
on top (`flash_args_has_tbd`'s mapping/sequence recursion, the skip-vs-fail
|
|
16
|
+
split between `flash_args` and an artefact path); only the base comparison
|
|
17
|
+
moved here.
|
|
18
|
+
|
|
19
|
+
Trimmed before comparing -- a YAML `device: " TBD "` is the same unfilled
|
|
20
|
+
field -- but deliberately NOT case-folded and NOT a substring test:
|
|
21
|
+
`TBD-1234-XYZ` is a plausible part number and `flash_args.build_dir:
|
|
22
|
+
/opt/TBDtool/x` a plausible path, and refusing either would block a
|
|
23
|
+
legitimate value. `tbd` lowercase is not the sentinel alp-sdk emits; widening
|
|
24
|
+
to it means widening the SDK's convention first, in one place, not here.
|
|
25
|
+
"""
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
#: alp-sdk's unfilled-field sentinel.
|
|
31
|
+
PENDING_PLACEHOLDER = "TBD"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def is_pending_placeholder(value: Any) -> bool:
|
|
35
|
+
"""Whether a manifest/preset SCALAR is the SDK's unfilled-field sentinel.
|
|
36
|
+
|
|
37
|
+
`None` and every non-string value are never pending; a string is pending
|
|
38
|
+
only when it is exactly `PENDING_PLACEHOLDER` after trimming surrounding
|
|
39
|
+
whitespace."""
|
|
40
|
+
return isinstance(value, str) and value.strip() == PENDING_PLACEHOLDER
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""A single-slice build plan for a `board.yaml`-less Zephyr example (tan-cli#1359).
|
|
3
|
+
|
|
4
|
+
alp-sdk's bench-style examples (`examples/aen/aen-inference-latency`, ...) are
|
|
5
|
+
plain Zephyr apps built per board target and ship no `board.yaml`, so the
|
|
6
|
+
planner has nothing to read. `tan build --project <dir> --board <target>`
|
|
7
|
+
synthesises the plan the planner WOULD have emitted for one Zephyr slice and
|
|
8
|
+
feeds it through the ordinary `tan build` pipeline -- the same token
|
|
9
|
+
substitution, host-Python resolver (#1326), toolchain / west-workspace
|
|
10
|
+
resolution, dispatch, pristine policy and envelope as a planned build. Nothing
|
|
11
|
+
here spawns or resolves anything; it only shapes data.
|
|
12
|
+
|
|
13
|
+
The slice mirrors a planner Zephyr slice: `west build -b <board> <app> --`
|
|
14
|
+
run with cwd `build/<core>-zephyr` (so west's own `build/` lands under it and
|
|
15
|
+
`--pristine`'s wipe guards still apply -- an explicit `-d` would disable them).
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import re
|
|
21
|
+
|
|
22
|
+
from tan.core.plan_tokens import TOKEN_PYTHON, TOKEN_SDK_ROOT
|
|
23
|
+
|
|
24
|
+
#: A Zephyr board target: `<board>[@rev][/<soc>[/<cpucluster>[/<variant>]]]`.
|
|
25
|
+
_BOARD_TARGET = re.compile(r"[A-Za-z0-9_][A-Za-z0-9_.@-]*(/[A-Za-z0-9_.-]+){0,3}")
|
|
26
|
+
#: `-DNAME=value` / `-DNAME:TYPE=value` / `-DNAME`.
|
|
27
|
+
_DEFINE = re.compile(r"-D[A-Za-z_][A-Za-z0-9_]*(:[A-Za-z]+)?(=.*)?", re.DOTALL)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def board_target_problem(board: str) -> str | None:
|
|
31
|
+
if not _BOARD_TARGET.fullmatch(board):
|
|
32
|
+
return (
|
|
33
|
+
f"`--board {board}` is not a Zephyr board target "
|
|
34
|
+
"(e.g. `alp_e1m_aen803_m55_he/ae822fa0e5597ls0/rtss_he`)"
|
|
35
|
+
)
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def normalise_defines(defines: list[str]) -> tuple[list[str], str | None]:
|
|
40
|
+
"""`-D` values as CMake args: `FOO=1` and `-DFOO=1` both become `-DFOO=1`."""
|
|
41
|
+
out: list[str] = []
|
|
42
|
+
for raw in defines:
|
|
43
|
+
item = raw if raw.startswith("-D") else f"-D{raw}"
|
|
44
|
+
if "${" in item:
|
|
45
|
+
# Plan token substitution would expand (or choke on) it.
|
|
46
|
+
return [], f"`-D {raw}` contains `${{`, which tan reserves for build-plan tokens"
|
|
47
|
+
if not _DEFINE.fullmatch(item):
|
|
48
|
+
return [], f"`-D {raw}` is not a CMake definition (`NAME=VALUE`)"
|
|
49
|
+
out.append(item)
|
|
50
|
+
return out, None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def core_id_for(board: str) -> str:
|
|
54
|
+
"""The slice's id: the board target's core part, else its board name."""
|
|
55
|
+
parts = board.split("/")
|
|
56
|
+
name = parts[2] if len(parts) >= 3 else parts[0]
|
|
57
|
+
return re.sub(r"[^A-Za-z0-9_]+", "_", name).strip("_").lower() or "app"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
#: `generatedBy` of a synthesised plan: how the post-build manifest write
|
|
61
|
+
#: recognises the plain route (tan-cli#1370).
|
|
62
|
+
PLAIN_GENERATED_BY = "tan build --board"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def plain_route_target(plan) -> tuple[str, str] | None:
|
|
66
|
+
"""`(board target, core id)` of a plan [`plain_zephyr_plan`] synthesised."""
|
|
67
|
+
if plan.generated_by != PLAIN_GENERATED_BY or len(plan.slices) != 1:
|
|
68
|
+
return None
|
|
69
|
+
(sl,) = plan.slices
|
|
70
|
+
args = sl.command.args if sl.command else []
|
|
71
|
+
if "-b" not in args or args.index("-b") + 1 >= len(args):
|
|
72
|
+
return None
|
|
73
|
+
return args[args.index("-b") + 1], sl.core_id
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def plain_zephyr_plan(
|
|
77
|
+
app_dir: str, board: str, defines: list[str], core_id: str | None = None
|
|
78
|
+
) -> str:
|
|
79
|
+
"""The build-plan JSON text for one Zephyr slice building `app_dir`.
|
|
80
|
+
|
|
81
|
+
`core_id` is the planner core id the board target maps to (`m55_he` for
|
|
82
|
+
`.../rtss_he`); without it the slice is named for the board qualifier."""
|
|
83
|
+
core = core_id or core_id_for(board)
|
|
84
|
+
slice_dir = f"build/{core}-zephyr"
|
|
85
|
+
out = f"{slice_dir}/build"
|
|
86
|
+
plan = {
|
|
87
|
+
"schemaVersion": 1,
|
|
88
|
+
"planPathMode": "tokened",
|
|
89
|
+
"generatedBy": PLAIN_GENERATED_BY,
|
|
90
|
+
"boardYaml": "",
|
|
91
|
+
"sku": "",
|
|
92
|
+
"buildRoot": "build",
|
|
93
|
+
"executionPolicy": {"unknownBackend": "fail", "missingTool": "skip", "nullCommand": "skip"},
|
|
94
|
+
"slices": [
|
|
95
|
+
{
|
|
96
|
+
"coreId": core,
|
|
97
|
+
"backend": "zephyr",
|
|
98
|
+
"buildDir": slice_dir,
|
|
99
|
+
"appDir": app_dir,
|
|
100
|
+
"configArtefacts": [],
|
|
101
|
+
# `toolchain` / `debug` below are required plan fields; no consumer
|
|
102
|
+
# of this synthesised plan reads their values.
|
|
103
|
+
"toolchain": {
|
|
104
|
+
"targetTriple": "arm-zephyr-eabi",
|
|
105
|
+
"compiler": "arm-zephyr-eabi-gcc",
|
|
106
|
+
"sysroot": None,
|
|
107
|
+
"id": "arm-zephyr-eabi",
|
|
108
|
+
},
|
|
109
|
+
"artifacts": {
|
|
110
|
+
"elf": f"{out}/zephyr/zephyr.elf",
|
|
111
|
+
"map": f"{out}/zephyr/zephyr.map",
|
|
112
|
+
"bin": f"{out}/zephyr/zephyr.bin",
|
|
113
|
+
"sizeReport": f"{out}/zephyr/zephyr.stat",
|
|
114
|
+
"symbols": f"{out}/zephyr/zephyr.symbols",
|
|
115
|
+
"compileCommands": f"{out}/compile_commands.json",
|
|
116
|
+
"outputDir": None,
|
|
117
|
+
},
|
|
118
|
+
"debug": {"console": "uart", "probe": None},
|
|
119
|
+
"command": {
|
|
120
|
+
"tool": "west",
|
|
121
|
+
"args": [
|
|
122
|
+
"build", "-b", board, app_dir, "--",
|
|
123
|
+
f"-DPython3_EXECUTABLE={TOKEN_PYTHON}", *defines,
|
|
124
|
+
],
|
|
125
|
+
"cwd": slice_dir,
|
|
126
|
+
},
|
|
127
|
+
"postCommands": [],
|
|
128
|
+
"env": {"ALP_SDK_ROOT": TOKEN_SDK_ROOT},
|
|
129
|
+
"envAppendPath": {
|
|
130
|
+
"EXTRA_ZEPHYR_MODULES": [TOKEN_SDK_ROOT],
|
|
131
|
+
"PYTHONPATH": [f"{TOKEN_SDK_ROOT}/scripts"],
|
|
132
|
+
},
|
|
133
|
+
}
|
|
134
|
+
],
|
|
135
|
+
"sharedArtefacts": [],
|
|
136
|
+
"warnings": [],
|
|
137
|
+
}
|
|
138
|
+
return json.dumps(plan, indent=2)
|