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/planner/paths.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""Filesystem roots for the planner package -- the bottom leaf.
|
|
4
|
+
|
|
5
|
+
`REPO`, `METADATA_ROOT`, and the schema paths. A near-dependency-free leaf so
|
|
6
|
+
any sibling -- topology, the loader, the carve-out / partition resolvers -- can
|
|
7
|
+
import a path root directly instead of reaching back into the package __init__
|
|
8
|
+
(which would cycle). Extracting these is the #285 paths seam that lets
|
|
9
|
+
topology.py drop its lazy `BOARD_SCHEMA` import.
|
|
10
|
+
|
|
11
|
+
RELOCATED (was alp-sdk `scripts/alp_orchestrate/paths.py`): the roots used to be
|
|
12
|
+
derived from this file's own location -- three parents up landed on the SDK
|
|
13
|
+
checkout. Inside `tan` that walk lands on the `tan` package, so the root is now
|
|
14
|
+
an explicit binding resolved by `tan.planner_root.sdk_root()`, which raises if
|
|
15
|
+
the package was imported before a root was bound. `REPO` still means *the
|
|
16
|
+
alp-sdk checkout*, never tan's own tree: `metadata/**`, `firmware/`,
|
|
17
|
+
`zephyr/sysbuild/` and the git commit all come from the SDK (ADR-0017 -- the
|
|
18
|
+
generators relocated, the facts did not).
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from tan.planner_root import sdk_root
|
|
24
|
+
|
|
25
|
+
# Evaluated at import time, and deliberately so: siblings take
|
|
26
|
+
# `metadata_root: Path = METADATA_ROOT` as a *default argument*, which binds
|
|
27
|
+
# here too. `sdk_root()` raising on an unbound import is what keeps that from
|
|
28
|
+
# becoming a silently-wrong metadata tree.
|
|
29
|
+
REPO = sdk_root()
|
|
30
|
+
METADATA_ROOT = REPO / "metadata"
|
|
31
|
+
BOARD_SCHEMA = METADATA_ROOT / "schemas" / "board.schema.json"
|
|
32
|
+
BOARD_PRESET_SCHEMA = METADATA_ROOT / "schemas" / "board-preset.schema.json"
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""The `system-manifest.yaml` a `board.yaml`-less Zephyr build writes (tan-cli#1370).
|
|
3
|
+
|
|
4
|
+
`tan build --board <zephyr-board-target>` has no `board.yaml` for the planner to
|
|
5
|
+
project a manifest from. This builds the same document the planner would have
|
|
6
|
+
written for ONE slice -- same schema, same `flash_method`/`flash_args` (composed
|
|
7
|
+
by the planner's own `_slice_flash_recipe`, not re-spelled here) -- from the SoM
|
|
8
|
+
preset whose `topology.<core>.board` names the target.
|
|
9
|
+
|
|
10
|
+
The planner core id (`m55_he`) is NOT the Zephyr cpucluster (`rtss_he`) the board
|
|
11
|
+
qualifier ends in: it comes from the preset's topology key, so no core name is
|
|
12
|
+
spelled in this module. An unknown target (`native_sim`, a non-Alp board) still
|
|
13
|
+
gets a one-slice manifest, with no SoC facts to carry.
|
|
14
|
+
|
|
15
|
+
Imports of the planner are local: the planner freezes its SDK root at import
|
|
16
|
+
time, so callers must `bind_sdk_root` first.
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
#: A manifest slice linked below the SoC flash base runs from RAM only.
|
|
24
|
+
RAM_RUN_ONLY = "ram_run_only"
|
|
25
|
+
#: The flash_args a RAM-run-only slice keeps: the wrong-board preflight pair.
|
|
26
|
+
_RAM_RUN_KEYS = ("expect_dpidr", "jlink_device")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _find_preset(board: str, metadata_root: Path) -> tuple[dict, str] | None:
|
|
30
|
+
from .loader import _load_yaml
|
|
31
|
+
|
|
32
|
+
for path in sorted((metadata_root / "e1m_modules").glob("*.yaml")):
|
|
33
|
+
try:
|
|
34
|
+
preset = _load_yaml(path)
|
|
35
|
+
except Exception: # noqa: BLE001 -- an unreadable preset is not THIS target's
|
|
36
|
+
continue
|
|
37
|
+
for core_id, topo in sorted((preset.get("topology") or {}).items()):
|
|
38
|
+
if isinstance(topo, dict) and topo.get("board") == board:
|
|
39
|
+
return preset, str(core_id)
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def plain_core_id(board: str, metadata_root: Path) -> str | None:
|
|
44
|
+
"""The planner core id of the SoM topology entry that names `board`."""
|
|
45
|
+
found = _find_preset(board, metadata_root)
|
|
46
|
+
return found[1] if found else None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def plain_system_manifest(
|
|
50
|
+
board: str, metadata_root: Path | None, *, fallback_core_id: str, elf_path: str | None = None
|
|
51
|
+
) -> str:
|
|
52
|
+
"""The manifest YAML for one plain Zephyr slice, status `pending`.
|
|
53
|
+
|
|
54
|
+
`elf_path`, when it points at an ELF whose lowest load address is below the
|
|
55
|
+
SoC's `soc_flash_base`, marks the slice `ram_run_only`."""
|
|
56
|
+
import yaml
|
|
57
|
+
|
|
58
|
+
from tan.core.elf_load import lowest_load_address
|
|
59
|
+
|
|
60
|
+
from .loader import (
|
|
61
|
+
_enforce_flow_d_preflight_pair, _jlink_flash_device_declared, _load_json, _resolve_flow_d_preflight,
|
|
62
|
+
_resolve_jlink_flash_device, _resolve_slot0_load_address,
|
|
63
|
+
_resolve_variant_debug, _silicon_to_soc_path, _slice_from_resolved)
|
|
64
|
+
from .models import Slice
|
|
65
|
+
from .validate import _enforce_os_matches_core_class
|
|
66
|
+
from .orchestrator import _slice_flash_recipe
|
|
67
|
+
|
|
68
|
+
found = _find_preset(board, metadata_root) if metadata_root is not None else None
|
|
69
|
+
hw_info: dict[str, Any] = dict.fromkeys(
|
|
70
|
+
("sku", "som_hw_rev", "board_name", "board_hw_rev", "silicon"))
|
|
71
|
+
hw_info["sku"] = ""
|
|
72
|
+
flash_base: int | None = None
|
|
73
|
+
if found is None:
|
|
74
|
+
slice_ = Slice(core_id=fallback_core_id, os="zephyr", board=board)
|
|
75
|
+
else:
|
|
76
|
+
preset, core_id = found
|
|
77
|
+
topo = preset["topology"][core_id]
|
|
78
|
+
soc_spec = _load_json(_silicon_to_soc_path(preset["silicon"], metadata_root))
|
|
79
|
+
debug = _resolve_variant_debug(preset, soc_spec)
|
|
80
|
+
declared = _jlink_flash_device_declared(debug)
|
|
81
|
+
flash_device = _resolve_jlink_flash_device(debug)
|
|
82
|
+
expect_dpidr, jlink_device = _resolve_flow_d_preflight(debug, core_id)
|
|
83
|
+
slice_ = _slice_from_resolved(
|
|
84
|
+
core_id,
|
|
85
|
+
{"os": "zephyr", "board": board, "toolchain": topo.get("toolchain")},
|
|
86
|
+
jlink_flash_device=flash_device,
|
|
87
|
+
jlink_flash_device_declared=declared,
|
|
88
|
+
expect_dpidr=expect_dpidr,
|
|
89
|
+
jlink_device=jlink_device,
|
|
90
|
+
slot0_load_address=(
|
|
91
|
+
_resolve_slot0_load_address(preset, core_id)
|
|
92
|
+
if (declared or flash_device is not None) else None),
|
|
93
|
+
)
|
|
94
|
+
# The same refusals the planned route applies per slice (loader.py).
|
|
95
|
+
core_type = next(
|
|
96
|
+
(str(c.get("type") or "") for c in (soc_spec.get("cores") or [])
|
|
97
|
+
if c.get("id") == core_id), "")
|
|
98
|
+
_enforce_flow_d_preflight_pair(slice_, debug, preset["sku"])
|
|
99
|
+
_enforce_os_matches_core_class(slice_, core_type)
|
|
100
|
+
hw_info.update(sku=preset["sku"], silicon=preset.get("silicon"))
|
|
101
|
+
base = soc_spec.get("soc_flash_base")
|
|
102
|
+
flash_base = int(base) if isinstance(base, int) else None
|
|
103
|
+
|
|
104
|
+
entry = slice_.to_manifest_entry()
|
|
105
|
+
if found is None:
|
|
106
|
+
# No SoM preset names this target: there is no recipe to compose, and a
|
|
107
|
+
# guessed one could program the wrong thing. `tan flash` skips a slice
|
|
108
|
+
# with no flash_method, naming this reason.
|
|
109
|
+
entry["flash_method"] = "none"
|
|
110
|
+
entry["flash_args"] = {}
|
|
111
|
+
entry["reason"] = (
|
|
112
|
+
f"no SoM preset in the SDK names board target `{board}`; "
|
|
113
|
+
"no flash recipe is known for it")
|
|
114
|
+
elif flash_base is not None:
|
|
115
|
+
# The SoC has a flash window, so WHERE the image is linked decides how it
|
|
116
|
+
# may be run. An image tan cannot classify must never inherit the MRAM
|
|
117
|
+
# recipe (slot0_load_address / jlink_flash_device): refuse it as RAM-only.
|
|
118
|
+
# TODO(tan-cli#1360): also set `slice_.link_target = "itcm"` and import
|
|
119
|
+
# RAM_RUN_ONLY_METHOD from `tan.core.link_refusal` once #1360 is in this
|
|
120
|
+
# base, so `tan flash` refuses the method by name.
|
|
121
|
+
low = lowest_load_address(elf_path) if elf_path else None
|
|
122
|
+
if low is None or low < flash_base:
|
|
123
|
+
_, args = _slice_flash_recipe(slice_)
|
|
124
|
+
entry["flash_method"] = RAM_RUN_ONLY
|
|
125
|
+
entry["flash_args"] = {k: v for k, v in (args or {}).items() if k in _RAM_RUN_KEYS}
|
|
126
|
+
if low is None:
|
|
127
|
+
entry["reason"] = (
|
|
128
|
+
"the built ELF could not be classified (missing, unreadable, or "
|
|
129
|
+
"no loadable segment); not treated as a flash image")
|
|
130
|
+
doc = {
|
|
131
|
+
"schema_version": 1,
|
|
132
|
+
"generated_by": "tan build --board",
|
|
133
|
+
"hw_info": hw_info,
|
|
134
|
+
"slices": [entry],
|
|
135
|
+
"ipc": [],
|
|
136
|
+
"helper_mcus": [],
|
|
137
|
+
"boot_order": [],
|
|
138
|
+
}
|
|
139
|
+
return yaml.safe_dump(doc, sort_keys=False, default_flow_style=False)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""board.yaml emitters whose `--emit` front door is `scripts/alp_project.py`.
|
|
3
|
+
|
|
4
|
+
RELOCATED (was alp-sdk `scripts/alp_project_emit/`). The package keeps its shape
|
|
5
|
+
so the move stays reviewable as a move: `dts.py`, `native_sim.py`, `hw_info.py`,
|
|
6
|
+
`west_libs.py` and `bom_netlist.py` are the same files with their imports
|
|
7
|
+
repointed at `tan.planner`'s own leaves.
|
|
8
|
+
|
|
9
|
+
Two things alp-sdk's package root carried did NOT come across, because nothing
|
|
10
|
+
these five modules do reaches them and a second copy would be a second source of
|
|
11
|
+
truth:
|
|
12
|
+
|
|
13
|
+
* `_PERIPHERAL_KCONFIG` -- `slugs.peripheral_kconfig(metadata_root)` already
|
|
14
|
+
reads `metadata/registries/peripheral-kconfig.json` here. It takes the
|
|
15
|
+
project's own root since alp-sdk#1485; there is no module-level table to
|
|
16
|
+
copy any more.
|
|
17
|
+
* `_CHIP_SUBSYSTEMS` -- already in `slugs.py`, with its I-26 justification.
|
|
18
|
+
|
|
19
|
+
`composed-route-table` came across too: `bom_netlist.py` carries
|
|
20
|
+
`_emit_composed_route_table` alongside `_emit_carrier_netlist`, sharing the same
|
|
21
|
+
`_composed_route_rows` helper.
|
|
22
|
+
|
|
23
|
+
What DOES live here is the one fact genuinely shared by two leaves: the canonical
|
|
24
|
+
E1M / E1M-X GPIO pad order, which `dts.py` (`--emit dts-overlay`) and
|
|
25
|
+
`native_sim.py` (`--emit native-sim-overlay`) must agree on byte for byte -- the
|
|
26
|
+
"Devicetree / overlay invariant" documented in the SDK's `e1m_pinout.h` /
|
|
27
|
+
`e1m_x_pinout.h`. Kept here, not duplicated in either leaf, so the two overlays
|
|
28
|
+
cannot drift.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
__all__ = ["_e1m_gpio_canonical", "_e1m_x_gpio_canonical"]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _e1m_gpio_canonical() -> list[str]:
|
|
37
|
+
"""Return the 52 ALP_E1M_GPIO_<suffix> names in canonical index order."""
|
|
38
|
+
names: list[str] = [f"IO{n}" for n in range(26)] # 0..25
|
|
39
|
+
names += [f"PWM{n}" for n in range(8)] # 26..33
|
|
40
|
+
for e in range(4): # 34..41
|
|
41
|
+
names += [f"ENC{e}_X", f"ENC{e}_Y"]
|
|
42
|
+
names += [f"ADC{n}" for n in range(8)] # 42..49
|
|
43
|
+
names += ["DAC0", "DAC1"] # 50..51
|
|
44
|
+
return names
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _e1m_x_gpio_canonical() -> list[str]:
|
|
48
|
+
"""Return the 99 ALP_E1M_X_GPIO_<suffix> names in canonical index order."""
|
|
49
|
+
names: list[str] = [f"IO{n}" for n in range(36)] # 0..35
|
|
50
|
+
names += [f"PWM{n}" for n in range(8)] # 36..43
|
|
51
|
+
for e in range(4): # 44..51
|
|
52
|
+
names += [f"ENC{e}_X", f"ENC{e}_Y"]
|
|
53
|
+
names += [f"ADC{n}" for n in range(8)] # 52..59
|
|
54
|
+
names += ["DAC0", "DAC1"] # 60..61
|
|
55
|
+
names += ["I2C2_SDA", "I2C2_SCL", "I2C3_SDA", "I2C3_SCL"]
|
|
56
|
+
names += ["SPI2_MISO", "SPI2_MOSI", "SPI2_SCLK", "SPI2_CS0", "SPI2_CS1"]
|
|
57
|
+
names += ["CAN1_H", "CAN1_L"]
|
|
58
|
+
names += [f"LCD_B{n}" for n in range(24)]
|
|
59
|
+
names += ["LCD_HSYNC", "LCD_VSYNC"] # 97..98
|
|
60
|
+
return names
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Carrier netlist / BOM emission (`--emit carrier-netlist` /
|
|
3
|
+
`--emit composed-route-table`).
|
|
4
|
+
|
|
5
|
+
RELOCATED (was alp-sdk `scripts/alp_project_emit/bom_netlist.py`) -- see this
|
|
6
|
+
package's `__init__.py` for the move's contract.
|
|
7
|
+
|
|
8
|
+
`_emit_composed_route_table` relocated alongside `_emit_carrier_netlist`
|
|
9
|
+
(tan-cli composed-route-table front door): the two share `_composed_route_rows`
|
|
10
|
+
in full -- that is where the hw_rev pad-route override actually happens -- so a
|
|
11
|
+
second copy of that resolution could not exist to drift.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from ..loader import _load_yaml
|
|
21
|
+
from ..project_loader import (
|
|
22
|
+
_compose_route,
|
|
23
|
+
_hwrev_pad_route_overrides,
|
|
24
|
+
_resolve_pad_routes,
|
|
25
|
+
)
|
|
26
|
+
from ..som_metadata import _resolve_silicon_variant
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ---------------------------------------------------------------------
|
|
30
|
+
# Carrier route / netlist emitters
|
|
31
|
+
# ---------------------------------------------------------------------
|
|
32
|
+
#
|
|
33
|
+
# `composed-route-table` stays as the original debug surface. The
|
|
34
|
+
# route-row helper below is shared by the production `carrier-netlist`
|
|
35
|
+
# contract so the two views cannot drift on hw_rev pad-route overrides.
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _composed_route_rows(
|
|
39
|
+
project: dict[str, Any],
|
|
40
|
+
sku_preset: dict[str, Any],
|
|
41
|
+
board_preset: dict[str, Any] | None,
|
|
42
|
+
metadata_root: Path,
|
|
43
|
+
) -> tuple[list[dict[str, Any]], str | None, str | None]:
|
|
44
|
+
"""Return composed route rows plus the selected hw_rev / variant.
|
|
45
|
+
|
|
46
|
+
Rows cover every E1M pad named by the board and every SoM-only pad
|
|
47
|
+
that has a dispatch route. Board-defined rows preserve YAML order;
|
|
48
|
+
SoM-only rows are sorted by E1M ID for deterministic output.
|
|
49
|
+
"""
|
|
50
|
+
pad_routes = _resolve_pad_routes(sku_preset)
|
|
51
|
+
|
|
52
|
+
# Apply the selected board revision's pad-route overrides on top of the
|
|
53
|
+
# base (production-rev) pad_routes, so the composed table -- and thus
|
|
54
|
+
# `--emit composed-route-table` -- differs by hw_rev. The rev comes from
|
|
55
|
+
# the board's `som.hw_rev`, falling back to the SoM's `default_hw_rev`.
|
|
56
|
+
hw_rev = ((project.get("som") or {}).get("hw_rev")
|
|
57
|
+
or sku_preset.get("default_hw_rev"))
|
|
58
|
+
for ov in _hwrev_pad_route_overrides(project["som"]["sku"], hw_rev,
|
|
59
|
+
metadata_root):
|
|
60
|
+
pad_routes[ov["e1m"]] = ov
|
|
61
|
+
|
|
62
|
+
# Resolve silicon variant order_code for the top-level summary field.
|
|
63
|
+
variant = _resolve_silicon_variant(sku_preset, metadata_root)
|
|
64
|
+
silicon_variant_str = variant["order_code"] if variant else None
|
|
65
|
+
|
|
66
|
+
# Collect board-side entries, preserving the sub-category name.
|
|
67
|
+
# Build a mapping: e1m_id -> (category, entry_dict).
|
|
68
|
+
# When the same E1M pad appears multiple times (e.g. E1M_PWM1 maps to
|
|
69
|
+
# both EVK_PWM_LED_BLUE and EVK_ARD_PWM1 in the EVK YAML) we emit one
|
|
70
|
+
# row per board entry so no information is lost.
|
|
71
|
+
board_entries: list[tuple[str, dict[str, Any]]] = []
|
|
72
|
+
seen_from_board: set[str] = set()
|
|
73
|
+
if board_preset is not None:
|
|
74
|
+
e1m_routes = board_preset.get("e1m_routes") or {}
|
|
75
|
+
for category, entries in e1m_routes.items():
|
|
76
|
+
if not isinstance(entries, list):
|
|
77
|
+
continue
|
|
78
|
+
for entry in entries:
|
|
79
|
+
if not isinstance(entry, dict):
|
|
80
|
+
continue
|
|
81
|
+
e1m = entry.get("e1m")
|
|
82
|
+
if not isinstance(e1m, str):
|
|
83
|
+
continue
|
|
84
|
+
board_entries.append((category, entry))
|
|
85
|
+
seen_from_board.add(e1m)
|
|
86
|
+
|
|
87
|
+
# Also include SoM-only pads (in pad_routes but not in board).
|
|
88
|
+
som_only_pads = sorted(set(pad_routes.keys()) - seen_from_board)
|
|
89
|
+
|
|
90
|
+
routes: list[dict[str, Any]] = []
|
|
91
|
+
|
|
92
|
+
# Board-defined entries first (preserves YAML order).
|
|
93
|
+
for category, c_entry in board_entries:
|
|
94
|
+
e1m = c_entry["e1m"]
|
|
95
|
+
composed = _compose_route(e1m, c_entry, pad_routes)
|
|
96
|
+
row: dict[str, Any] = {"e1m": e1m, "board_category": category}
|
|
97
|
+
row["board_macro"] = composed.get("board_macro")
|
|
98
|
+
row["board_role"] = composed.get("board_role")
|
|
99
|
+
if "board_doc" in composed:
|
|
100
|
+
row["board_doc"] = composed["board_doc"]
|
|
101
|
+
# active_low is a board-side flag, not surfaced by _compose_route;
|
|
102
|
+
# read it directly from the board entry.
|
|
103
|
+
active_low = c_entry.get("active_low")
|
|
104
|
+
if active_low is not None:
|
|
105
|
+
row["active_low"] = bool(active_low)
|
|
106
|
+
row["dispatch"] = composed.get("dispatch", "direct")
|
|
107
|
+
if "dispatch_pin" in composed:
|
|
108
|
+
row["dispatch_pin"] = composed["dispatch_pin"]
|
|
109
|
+
if "som_doc" in composed:
|
|
110
|
+
row["som_doc"] = composed["som_doc"]
|
|
111
|
+
routes.append(row)
|
|
112
|
+
|
|
113
|
+
# SoM-only pads (not assigned a board role in this board YAML).
|
|
114
|
+
for e1m in som_only_pads:
|
|
115
|
+
composed = _compose_route(e1m, None, pad_routes)
|
|
116
|
+
row = {
|
|
117
|
+
"e1m": e1m,
|
|
118
|
+
"board_category": None,
|
|
119
|
+
"board_macro": None,
|
|
120
|
+
"board_role": None,
|
|
121
|
+
"dispatch": composed.get("dispatch", "direct"),
|
|
122
|
+
}
|
|
123
|
+
if "dispatch_pin" in composed:
|
|
124
|
+
row["dispatch_pin"] = composed["dispatch_pin"]
|
|
125
|
+
if "som_doc" in composed:
|
|
126
|
+
row["som_doc"] = composed["som_doc"]
|
|
127
|
+
routes.append(row)
|
|
128
|
+
|
|
129
|
+
return routes, hw_rev, silicon_variant_str
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _manifest_path(kind: str, item_id: str, metadata_root: Path) -> Path:
|
|
133
|
+
return metadata_root / kind / f"{item_id}.yaml"
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _load_optional_manifest(kind: str, item_id: str,
|
|
137
|
+
metadata_root: Path) -> dict[str, Any] | None:
|
|
138
|
+
path = _manifest_path(kind, item_id, metadata_root)
|
|
139
|
+
if not path.is_file():
|
|
140
|
+
return None
|
|
141
|
+
return _load_yaml(path)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _passive_rows(passives: list[dict[str, Any]] | None) -> list[dict[str, Any]]:
|
|
145
|
+
rows: list[dict[str, Any]] = []
|
|
146
|
+
for passive in passives or []:
|
|
147
|
+
if not isinstance(passive, dict):
|
|
148
|
+
continue
|
|
149
|
+
row: dict[str, Any] = {
|
|
150
|
+
"role": passive.get("role"),
|
|
151
|
+
"value": passive.get("value"),
|
|
152
|
+
"net": passive.get("net"),
|
|
153
|
+
"refdes_prefix": passive.get("refdes_prefix"),
|
|
154
|
+
}
|
|
155
|
+
rows.append({k: v for k, v in row.items() if v is not None})
|
|
156
|
+
return rows
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _chip_bom_row(item_id: str, manifest: dict[str, Any],
|
|
160
|
+
manifest_relpath: str) -> dict[str, Any]:
|
|
161
|
+
physical = manifest.get("physical") or {}
|
|
162
|
+
caveats: list[str] = []
|
|
163
|
+
if not physical:
|
|
164
|
+
caveats.append("missing_physical")
|
|
165
|
+
elif physical.get("visibility") == "internal":
|
|
166
|
+
caveats.append("physical_detail_internal")
|
|
167
|
+
if len(manifest.get("mpn_population") or []) > 1:
|
|
168
|
+
caveats.append("mpn_population_candidates")
|
|
169
|
+
|
|
170
|
+
row: dict[str, Any] = {
|
|
171
|
+
"item_id": item_id,
|
|
172
|
+
"kind": "chip",
|
|
173
|
+
"scope": "carrier",
|
|
174
|
+
"source": manifest_relpath,
|
|
175
|
+
"display_name": manifest.get("display_name"),
|
|
176
|
+
"vendor": manifest.get("vendor"),
|
|
177
|
+
"mpn_population": manifest.get("mpn_population") or [],
|
|
178
|
+
"bus": manifest.get("bus"),
|
|
179
|
+
"quantity": None,
|
|
180
|
+
"physical": {
|
|
181
|
+
"refdes_prefix": physical.get("refdes_prefix"),
|
|
182
|
+
"package": physical.get("package"),
|
|
183
|
+
"footprint": physical.get("footprint"),
|
|
184
|
+
"visibility": physical.get("visibility"),
|
|
185
|
+
"provenance": physical.get("provenance"),
|
|
186
|
+
},
|
|
187
|
+
"passives": _passive_rows(physical.get("passives")),
|
|
188
|
+
}
|
|
189
|
+
row["physical"] = {k: v for k, v in row["physical"].items()
|
|
190
|
+
if v is not None}
|
|
191
|
+
if caveats:
|
|
192
|
+
row["caveats"] = caveats
|
|
193
|
+
return row
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _block_bom_row(item_id: str, manifest: dict[str, Any],
|
|
197
|
+
manifest_relpath: str) -> dict[str, Any]:
|
|
198
|
+
realizations = [
|
|
199
|
+
r for r in manifest.get("realizations") or []
|
|
200
|
+
if isinstance(r, dict)
|
|
201
|
+
]
|
|
202
|
+
realization = realizations[0] if realizations else {}
|
|
203
|
+
caveats: list[str] = []
|
|
204
|
+
if not realizations:
|
|
205
|
+
caveats.append("missing_realization")
|
|
206
|
+
elif len(realizations) > 1:
|
|
207
|
+
caveats.append("multiple_realizations")
|
|
208
|
+
if realization.get("visibility") == "internal":
|
|
209
|
+
caveats.append("physical_detail_internal")
|
|
210
|
+
if not realization.get("parts"):
|
|
211
|
+
caveats.append("no_concrete_parts")
|
|
212
|
+
|
|
213
|
+
row: dict[str, Any] = {
|
|
214
|
+
"item_id": item_id,
|
|
215
|
+
"kind": "block",
|
|
216
|
+
"scope": "carrier",
|
|
217
|
+
"source": manifest_relpath,
|
|
218
|
+
"display_name": manifest.get("display_name"),
|
|
219
|
+
"quantity": None,
|
|
220
|
+
"realization": {
|
|
221
|
+
"id": realization.get("id"),
|
|
222
|
+
"physical_form": realization.get("physical_form"),
|
|
223
|
+
"visibility": realization.get("visibility"),
|
|
224
|
+
},
|
|
225
|
+
"parts": realization.get("parts") or [],
|
|
226
|
+
"passives": _passive_rows(realization.get("passives")),
|
|
227
|
+
}
|
|
228
|
+
row["realization"] = {k: v for k, v in row["realization"].items()
|
|
229
|
+
if v is not None}
|
|
230
|
+
if caveats:
|
|
231
|
+
row["caveats"] = caveats
|
|
232
|
+
return row
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _carrier_bom_rows(
|
|
236
|
+
board_preset: dict[str, Any] | None,
|
|
237
|
+
metadata_root: Path,
|
|
238
|
+
) -> list[dict[str, Any]]:
|
|
239
|
+
"""Build carrier BOM rows from board `populated: true`.
|
|
240
|
+
|
|
241
|
+
`populated:` is a logical population map, not a line-item BOM with
|
|
242
|
+
refdes or count, so rows deliberately leave `quantity` null unless a
|
|
243
|
+
future metadata field makes it authoritative.
|
|
244
|
+
"""
|
|
245
|
+
rows: list[dict[str, Any]] = []
|
|
246
|
+
if board_preset is None:
|
|
247
|
+
return rows
|
|
248
|
+
|
|
249
|
+
populated = board_preset.get("populated") or {}
|
|
250
|
+
for item_id in sorted(k for k, v in populated.items() if v is True):
|
|
251
|
+
chip = _load_optional_manifest("chips", item_id, metadata_root)
|
|
252
|
+
if chip is not None:
|
|
253
|
+
rows.append(_chip_bom_row(
|
|
254
|
+
item_id, chip, f"metadata/chips/{item_id}.yaml"))
|
|
255
|
+
continue
|
|
256
|
+
|
|
257
|
+
block = _load_optional_manifest("blocks", item_id, metadata_root)
|
|
258
|
+
if block is not None:
|
|
259
|
+
rows.append(_block_bom_row(
|
|
260
|
+
item_id, block, f"metadata/blocks/{item_id}.yaml"))
|
|
261
|
+
continue
|
|
262
|
+
|
|
263
|
+
rows.append({
|
|
264
|
+
"item_id": item_id,
|
|
265
|
+
"kind": "unknown",
|
|
266
|
+
"scope": "carrier",
|
|
267
|
+
"source": None,
|
|
268
|
+
"quantity": None,
|
|
269
|
+
"caveats": ["missing_manifest"],
|
|
270
|
+
})
|
|
271
|
+
return rows
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _route_to_net(row: dict[str, Any]) -> dict[str, Any]:
|
|
275
|
+
net_id = row.get("board_macro") or row["e1m"]
|
|
276
|
+
endpoints: list[dict[str, Any]] = [
|
|
277
|
+
{"kind": "e1m", "ref": row["e1m"]},
|
|
278
|
+
]
|
|
279
|
+
if row.get("board_macro"):
|
|
280
|
+
endpoints.append({"kind": "board-macro", "ref": row["board_macro"]})
|
|
281
|
+
if row.get("dispatch") and row["dispatch"] != "direct":
|
|
282
|
+
endpoint: dict[str, Any] = {
|
|
283
|
+
"kind": "som-dispatch",
|
|
284
|
+
"ref": row["dispatch"],
|
|
285
|
+
}
|
|
286
|
+
if "dispatch_pin" in row:
|
|
287
|
+
endpoint["pin"] = row["dispatch_pin"]
|
|
288
|
+
endpoints.append(endpoint)
|
|
289
|
+
|
|
290
|
+
net: dict[str, Any] = {
|
|
291
|
+
"net_id": net_id,
|
|
292
|
+
"e1m": row["e1m"],
|
|
293
|
+
"board_category": row.get("board_category"),
|
|
294
|
+
"board_macro": row.get("board_macro"),
|
|
295
|
+
"board_role": row.get("board_role"),
|
|
296
|
+
"dispatch": row.get("dispatch", "direct"),
|
|
297
|
+
"endpoints": endpoints,
|
|
298
|
+
}
|
|
299
|
+
for key in ("board_doc", "active_low", "dispatch_pin", "som_doc"):
|
|
300
|
+
if key in row:
|
|
301
|
+
net[key] = row[key]
|
|
302
|
+
caveats = []
|
|
303
|
+
if row.get("board_macro") is None:
|
|
304
|
+
caveats.append("som_only_no_carrier_role")
|
|
305
|
+
if caveats:
|
|
306
|
+
net["caveats"] = caveats
|
|
307
|
+
return net
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _emit_carrier_netlist(
|
|
311
|
+
project: dict[str, Any],
|
|
312
|
+
sku_preset: dict[str, Any],
|
|
313
|
+
board_preset: dict[str, Any] | None,
|
|
314
|
+
metadata_root: Path,
|
|
315
|
+
) -> str:
|
|
316
|
+
"""Emit the Studio-facing carrier netlist + BOM handoff contract.
|
|
317
|
+
|
|
318
|
+
This is intentionally not a KiCad, Gerber, or layout artifact. It
|
|
319
|
+
exposes only public carrier-facing facts derivable from board.yaml,
|
|
320
|
+
board presets, chip/block manifests, and SoM pad dispatch metadata.
|
|
321
|
+
"""
|
|
322
|
+
routes, hw_rev, silicon_variant_str = _composed_route_rows(
|
|
323
|
+
project, sku_preset, board_preset, metadata_root)
|
|
324
|
+
board_name = (board_preset or {}).get("name") or project.get("name")
|
|
325
|
+
result: dict[str, Any] = {
|
|
326
|
+
"schema_version": 1,
|
|
327
|
+
"kind": "alp.carrier_netlist",
|
|
328
|
+
"generated_by": "scripts/alp_project.py --emit carrier-netlist",
|
|
329
|
+
"board": board_name,
|
|
330
|
+
"som": project["som"]["sku"],
|
|
331
|
+
"hw_rev": hw_rev,
|
|
332
|
+
"silicon_variant": silicon_variant_str,
|
|
333
|
+
"nets": [_route_to_net(row) for row in routes],
|
|
334
|
+
"bom": {
|
|
335
|
+
"carrier": _carrier_bom_rows(board_preset, metadata_root),
|
|
336
|
+
},
|
|
337
|
+
"caveats": [
|
|
338
|
+
"carrier_handoff_not_pcb_layout",
|
|
339
|
+
"no_kicad_or_gerber_output",
|
|
340
|
+
"som_internals_excluded",
|
|
341
|
+
"quantity_null_when_board_populated_has_no_count",
|
|
342
|
+
],
|
|
343
|
+
}
|
|
344
|
+
return json.dumps(result, indent=2) + "\n"
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _emit_composed_route_table(
|
|
348
|
+
project: dict[str, Any],
|
|
349
|
+
sku_preset: dict[str, Any],
|
|
350
|
+
board_preset: dict[str, Any] | None,
|
|
351
|
+
metadata_root: Path,
|
|
352
|
+
) -> str:
|
|
353
|
+
"""Emit a JSON summary of the fully-composed pad route table for
|
|
354
|
+
the current (board x SoM) pair.
|
|
355
|
+
|
|
356
|
+
The table is derived by calling _resolve_pad_routes() (SoM side) and
|
|
357
|
+
_compose_route() (join with board side) for every E1M pad that
|
|
358
|
+
appears in either the board's e1m_routes: block or the SoM's
|
|
359
|
+
pad_routes: block.
|
|
360
|
+
|
|
361
|
+
Pads that only appear in the SoM's pad_routes: block (i.e. no
|
|
362
|
+
board-side role assigned) are included with null board_* fields
|
|
363
|
+
so the table is complete for the SoM-standalone scenario.
|
|
364
|
+
"""
|
|
365
|
+
routes, hw_rev, silicon_variant_str = _composed_route_rows(
|
|
366
|
+
project, sku_preset, board_preset, metadata_root)
|
|
367
|
+
board_name = (board_preset or {}).get("name") or project.get("name")
|
|
368
|
+
result: dict[str, Any] = {
|
|
369
|
+
"board": board_name,
|
|
370
|
+
"som": project["som"]["sku"],
|
|
371
|
+
"hw_rev": hw_rev,
|
|
372
|
+
"silicon_variant": silicon_variant_str,
|
|
373
|
+
"routes": routes,
|
|
374
|
+
}
|
|
375
|
+
return json.dumps(result, indent=2) + "\n"
|