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.
Files changed (330) hide show
  1. tan/__init__.py +0 -0
  2. tan/__main__.py +120 -0
  3. tan/cli.py +939 -0
  4. tan/command_table.py +265 -0
  5. tan/commands/__init__.py +8 -0
  6. tan/commands/bootstrap_cmd.py +4856 -0
  7. tan/commands/bootstrap_patches.py +164 -0
  8. tan/commands/bootstrap_toolchain_fetch.py +155 -0
  9. tan/commands/bootstrap_toolchain_pin.py +36 -0
  10. tan/commands/build/__init__.py +1 -0
  11. tan/commands/build/configure_inputs.py +277 -0
  12. tan/commands/build/execute.py +1852 -0
  13. tan/commands/build/host_python.py +59 -0
  14. tan/commands/build/link_stale.py +78 -0
  15. tan/commands/build/manifest.py +428 -0
  16. tan/commands/build/materialise.py +81 -0
  17. tan/commands/build/token_substitution.py +381 -0
  18. tan/commands/build/toolchain.py +482 -0
  19. tan/commands/build/user_defines_wipe.py +115 -0
  20. tan/commands/build/workspace_patches.py +65 -0
  21. tan/commands/build_cmd.py +2236 -0
  22. tan/commands/build_output.py +331 -0
  23. tan/commands/clean_cmd.py +1183 -0
  24. tan/commands/completion_cmd.py +786 -0
  25. tan/commands/debug_config_cmd.py +2425 -0
  26. tan/commands/diff_cmd.py +940 -0
  27. tan/commands/doctor_cmd.py +4767 -0
  28. tan/commands/examples_cmd.py +644 -0
  29. tan/commands/explain_cmd.py +1319 -0
  30. tan/commands/faultdecode_cmd.py +931 -0
  31. tan/commands/flash_atoc_guard.py +240 -0
  32. tan/commands/flash_cmd.py +5731 -0
  33. tan/commands/flash_mram_guard.py +150 -0
  34. tan/commands/flash_ram.py +578 -0
  35. tan/commands/flash_raw.py +583 -0
  36. tan/commands/generate_cmd.py +1456 -0
  37. tan/commands/image_cmd.py +753 -0
  38. tan/commands/init_cmd.py +1740 -0
  39. tan/commands/inspect_cmd.py +377 -0
  40. tan/commands/kconfig_cmd.py +734 -0
  41. tan/commands/model_cmd.py +1514 -0
  42. tan/commands/model_device_cmd.py +351 -0
  43. tan/commands/model_device_live.py +234 -0
  44. tan/commands/model_host_cmd.py +430 -0
  45. tan/commands/model_zoo_cmd.py +363 -0
  46. tan/commands/monitor_actions.py +125 -0
  47. tan/commands/monitor_cmd.py +626 -0
  48. tan/commands/monitor_session.py +505 -0
  49. tan/commands/new_som_cmd.py +1522 -0
  50. tan/commands/pinmux_cmd.py +568 -0
  51. tan/commands/presets_cmd.py +1123 -0
  52. tan/commands/probe_cmd.py +421 -0
  53. tan/commands/reset_cmd.py +354 -0
  54. tan/commands/reset_confirm.py +168 -0
  55. tan/commands/run_cmd.py +637 -0
  56. tan/commands/scaffold_cmd.py +490 -0
  57. tan/commands/sdk_cmd.py +1723 -0
  58. tan/commands/size_cmd.py +1020 -0
  59. tan/commands/support_bundle_cmd.py +1073 -0
  60. tan/commands/trace_cmd.py +341 -0
  61. tan/commands/validate_cmd.py +2009 -0
  62. tan/commands/west_forward_cmd.py +736 -0
  63. tan/commands/workspace_patch_check.py +296 -0
  64. tan/core/__init__.py +1 -0
  65. tan/core/alp_conf_pregen.py +100 -0
  66. tan/core/artifact_provenance.py +240 -0
  67. tan/core/atoc_guard.py +276 -0
  68. tan/core/atoc_guard_messages.py +232 -0
  69. tan/core/atoc_replacement.py +118 -0
  70. tan/core/atomic_write.py +178 -0
  71. tan/core/board_context.py +351 -0
  72. tan/core/board_diagnostic.py +111 -0
  73. tan/core/board_files.py +221 -0
  74. tan/core/board_validator.py +542 -0
  75. tan/core/board_validator_compat.py +196 -0
  76. tan/core/board_validator_run.py +129 -0
  77. tan/core/board_validator_schema.py +186 -0
  78. tan/core/board_validator_skew.py +46 -0
  79. tan/core/board_yaml_edit.py +295 -0
  80. tan/core/board_yaml_pos.py +118 -0
  81. tan/core/bootstrap.py +2572 -0
  82. tan/core/build_plan.py +625 -0
  83. tan/core/consent.py +104 -0
  84. tan/core/console_filter.py +245 -0
  85. tan/core/debug_launch.py +1803 -0
  86. tan/core/devicetree_lint.py +168 -0
  87. tan/core/dir_removal.py +229 -0
  88. tan/core/doctor_git.py +172 -0
  89. tan/core/doctor_libraries.py +253 -0
  90. tan/core/doctor_render.py +185 -0
  91. tan/core/doctor_scope.py +66 -0
  92. tan/core/doctor_setools.py +237 -0
  93. tan/core/doctor_stale.py +180 -0
  94. tan/core/document_guards.py +723 -0
  95. tan/core/dp_id.py +97 -0
  96. tan/core/elf_load.py +63 -0
  97. tan/core/error_catalog.py +171 -0
  98. tan/core/example_catalog.py +499 -0
  99. tan/core/example_facets.py +151 -0
  100. tan/core/faultdecode.py +521 -0
  101. tan/core/flash_plan.py +2501 -0
  102. tan/core/flow_d_report.py +381 -0
  103. tan/core/flow_d_uptime.py +164 -0
  104. tan/core/fs_confine.py +130 -0
  105. tan/core/global_flags.py +230 -0
  106. tan/core/host_python.py +210 -0
  107. tan/core/image_bundle.py +247 -0
  108. tan/core/inert.py +147 -0
  109. tan/core/jlink_binary.py +128 -0
  110. tan/core/jlink_probe.py +481 -0
  111. tan/core/jsonc_splice.py +404 -0
  112. tan/core/launch_provenance.py +257 -0
  113. tan/core/link_refusal.py +76 -0
  114. tan/core/metadata_schema.py +376 -0
  115. tan/core/model_check.py +227 -0
  116. tan/core/model_device.py +449 -0
  117. tan/core/model_doctor.py +198 -0
  118. tan/core/model_host.py +48 -0
  119. tan/core/model_list.py +112 -0
  120. tan/core/model_zoo.py +353 -0
  121. tan/core/module_template.py +340 -0
  122. tan/core/mram_link.py +87 -0
  123. tan/core/new_som_ospi.py +94 -0
  124. tan/core/os_class.py +159 -0
  125. tan/core/patch_paths.py +87 -0
  126. tan/core/pending.py +40 -0
  127. tan/core/plain_zephyr_plan.py +138 -0
  128. tan/core/plan_exec.py +373 -0
  129. tan/core/plan_tokens.py +600 -0
  130. tan/core/probe.py +155 -0
  131. tan/core/probe_plan.py +237 -0
  132. tan/core/proxy.py +168 -0
  133. tan/core/publish.py +31 -0
  134. tan/core/python_floor.py +178 -0
  135. tan/core/ram_run.py +694 -0
  136. tan/core/ram_watch.py +231 -0
  137. tan/core/raw_write.py +160 -0
  138. tan/core/reset_plan.py +56 -0
  139. tan/core/run.py +161 -0
  140. tan/core/scaffold.py +2203 -0
  141. tan/core/scaffold_selftest_identity.py +225 -0
  142. tan/core/sdk_default_registry.py +565 -0
  143. tan/core/sdk_discovery.py +996 -0
  144. tan/core/sdk_removal.py +465 -0
  145. tan/core/serial_actions.py +175 -0
  146. tan/core/serial_capture.py +185 -0
  147. tan/core/serial_url.py +45 -0
  148. tan/core/setools.py +608 -0
  149. tan/core/setools_scratch.py +243 -0
  150. tan/core/shapes.py +194 -0
  151. tan/core/size.py +610 -0
  152. tan/core/som_buildability.py +204 -0
  153. tan/core/som_schema_version.py +62 -0
  154. tan/core/subprocess_env.py +201 -0
  155. tan/core/sysbuild_image.py +94 -0
  156. tan/core/system_manifest.py +827 -0
  157. tan/core/text_layout.py +73 -0
  158. tan/core/timestamp.py +182 -0
  159. tan/core/tool_lookup.py +383 -0
  160. tan/core/toolchain_pin.py +117 -0
  161. tan/core/toolchain_provision.py +1224 -0
  162. tan/core/trusted_path.py +134 -0
  163. tan/core/uboot_breakin.py +154 -0
  164. tan/core/uri_reference.py +415 -0
  165. tan/core/user_defines.py +152 -0
  166. tan/core/venv.py +332 -0
  167. tan/core/west_patches.py +143 -0
  168. tan/core/west_workspace_refusal.py +159 -0
  169. tan/core/zephyr_env.py +124 -0
  170. tan/env.py +185 -0
  171. tan/envelope.py +647 -0
  172. tan/exit_codes.py +12 -0
  173. tan/lazy_typer.py +185 -0
  174. tan/model/__init__.py +2 -0
  175. tan/model/_gen_fixture.py +132 -0
  176. tan/model/adapters/__init__.py +116 -0
  177. tan/model/adapters/cpu.py +26 -0
  178. tan/model/adapters/deepx.py +94 -0
  179. tan/model/adapters/drpai.py +242 -0
  180. tan/model/adapters/ethos_u.py +951 -0
  181. tan/model/adapters/executorch.py +55 -0
  182. tan/model/analyze.py +694 -0
  183. tan/model/build.py +304 -0
  184. tan/model/check.py +682 -0
  185. tan/model/manifest.py +625 -0
  186. tan/model/measure.py +216 -0
  187. tan/model/npu_ops_validate.py +130 -0
  188. tan/model/package.py +125 -0
  189. tan/model/perf.py +635 -0
  190. tan/model/perf_apply.py +721 -0
  191. tan/model/prep.py +193 -0
  192. tan/model/sram_fit.py +502 -0
  193. tan/model/targets.py +374 -0
  194. tan/model/tensorio.py +287 -0
  195. tan/net.py +73 -0
  196. tan/output_format.py +213 -0
  197. tan/planner/__init__.py +219 -0
  198. tan/planner/aperture.py +202 -0
  199. tan/planner/buildplan.py +1092 -0
  200. tan/planner/camera_owner.py +192 -0
  201. tan/planner/cameras.py +132 -0
  202. tan/planner/carveout.py +755 -0
  203. tan/planner/cli.py +274 -0
  204. tan/planner/headers.py +315 -0
  205. tan/planner/kconfig.py +2435 -0
  206. tan/planner/kconfig_symbols.py +473 -0
  207. tan/planner/libraries.py +677 -0
  208. tan/planner/link_target.py +237 -0
  209. tan/planner/linux_ownership.py +216 -0
  210. tan/planner/loader.py +1525 -0
  211. tan/planner/manifest.py +116 -0
  212. tan/planner/memory.py +161 -0
  213. tan/planner/memregion.py +56 -0
  214. tan/planner/models.py +533 -0
  215. tan/planner/orchestrator.py +874 -0
  216. tan/planner/ownership.py +203 -0
  217. tan/planner/partition.py +1160 -0
  218. tan/planner/paths.py +32 -0
  219. tan/planner/plain_slice.py +139 -0
  220. tan/planner/project_emit/__init__.py +60 -0
  221. tan/planner/project_emit/bom_netlist.py +375 -0
  222. tan/planner/project_emit/dts.py +580 -0
  223. tan/planner/project_emit/hw_info.py +221 -0
  224. tan/planner/project_emit/native_sim.py +136 -0
  225. tan/planner/project_emit/west_libs.py +213 -0
  226. tan/planner/project_loader.py +264 -0
  227. tan/planner/sdk_capability.py +203 -0
  228. tan/planner/sdk_compat.py +395 -0
  229. tan/planner/secure.py +350 -0
  230. tan/planner/slugs.py +394 -0
  231. tan/planner/som_metadata.py +325 -0
  232. tan/planner/strict_loaders.py +131 -0
  233. tan/planner/template.py +1037 -0
  234. tan/planner/template_pins.py +535 -0
  235. tan/planner/template_rewrite.py +714 -0
  236. tan/planner/topology.py +208 -0
  237. tan/planner/validate.py +365 -0
  238. tan/planner/whole_device_alias.py +67 -0
  239. tan/planner/zephyr_board.py +3510 -0
  240. tan/planner_cli.py +97 -0
  241. tan/planner_emit.py +617 -0
  242. tan/planner_root.py +119 -0
  243. tan/soc_ref.py +58 -0
  244. tan/templates/__init__.py +34 -0
  245. tan/templates/vendored/MANIFEST.md +1862 -0
  246. tan/templates/vendored/diagnostics/E1M-AEN801/CMakeLists.txt +8 -0
  247. tan/templates/vendored/diagnostics/E1M-AEN801/README.md +112 -0
  248. tan/templates/vendored/diagnostics/E1M-AEN801/board.yaml +38 -0
  249. tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.conf +11 -0
  250. tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.overlay +29 -0
  251. tan/templates/vendored/diagnostics/E1M-AEN801/prj.conf +5 -0
  252. tan/templates/vendored/diagnostics/E1M-AEN801/src/main.c +266 -0
  253. tan/templates/vendored/diagnostics/E1M-AEN801/testcase.yaml +53 -0
  254. tan/templates/vendored/diagnostics/E1M-V2N101/CMakeLists.txt +8 -0
  255. tan/templates/vendored/diagnostics/E1M-V2N101/README.md +112 -0
  256. tan/templates/vendored/diagnostics/E1M-V2N101/board.yaml +38 -0
  257. tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.conf +11 -0
  258. tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.overlay +29 -0
  259. tan/templates/vendored/diagnostics/E1M-V2N101/prj.conf +5 -0
  260. tan/templates/vendored/diagnostics/E1M-V2N101/src/main.c +266 -0
  261. tan/templates/vendored/diagnostics/E1M-V2N101/testcase.yaml +53 -0
  262. tan/templates/vendored/edge-ai/E1M-AEN801/CMakeLists.txt +10 -0
  263. tan/templates/vendored/edge-ai/E1M-AEN801/README.md +75 -0
  264. tan/templates/vendored/edge-ai/E1M-AEN801/board.yaml +38 -0
  265. tan/templates/vendored/edge-ai/E1M-AEN801/prj.conf +8 -0
  266. tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.c +371 -0
  267. tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.h +178 -0
  268. tan/templates/vendored/edge-ai/E1M-AEN801/src/main.c +373 -0
  269. tan/templates/vendored/edge-ai/E1M-AEN801/testcase.yaml +62 -0
  270. tan/templates/vendored/edge-ai/E1M-V2N101/CMakeLists.txt +10 -0
  271. tan/templates/vendored/edge-ai/E1M-V2N101/README.md +75 -0
  272. tan/templates/vendored/edge-ai/E1M-V2N101/board.yaml +37 -0
  273. tan/templates/vendored/edge-ai/E1M-V2N101/prj.conf +8 -0
  274. tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.c +371 -0
  275. tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.h +178 -0
  276. tan/templates/vendored/edge-ai/E1M-V2N101/src/main.c +373 -0
  277. tan/templates/vendored/edge-ai/E1M-V2N101/testcase.yaml +62 -0
  278. tan/templates/vendored/iot/E1M-AEN801/CMakeLists.txt +14 -0
  279. tan/templates/vendored/iot/E1M-AEN801/README.md +103 -0
  280. tan/templates/vendored/iot/E1M-AEN801/board.yaml +69 -0
  281. tan/templates/vendored/iot/E1M-AEN801/prj.conf +15 -0
  282. tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.c +196 -0
  283. tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.h +173 -0
  284. tan/templates/vendored/iot/E1M-AEN801/src/main.c +262 -0
  285. tan/templates/vendored/iot/E1M-AEN801/testcase.yaml +68 -0
  286. tan/templates/vendored/minimal/E1M-AEN801/CMakeLists.txt +8 -0
  287. tan/templates/vendored/minimal/E1M-AEN801/README.md +108 -0
  288. tan/templates/vendored/minimal/E1M-AEN801/board.yaml +32 -0
  289. tan/templates/vendored/minimal/E1M-AEN801/prj.conf +8 -0
  290. tan/templates/vendored/minimal/E1M-AEN801/src/main.c +111 -0
  291. tan/templates/vendored/minimal/E1M-AEN801/testcase.yaml +49 -0
  292. tan/templates/vendored/minimal/E1M-V2N101/CMakeLists.txt +8 -0
  293. tan/templates/vendored/minimal/E1M-V2N101/README.md +108 -0
  294. tan/templates/vendored/minimal/E1M-V2N101/board.yaml +32 -0
  295. tan/templates/vendored/minimal/E1M-V2N101/prj.conf +8 -0
  296. tan/templates/vendored/minimal/E1M-V2N101/src/main.c +111 -0
  297. tan/templates/vendored/minimal/E1M-V2N101/testcase.yaml +49 -0
  298. tan/templates/vendored/multicore-mailbox/E1M-AEN801/CMakeLists.txt +8 -0
  299. tan/templates/vendored/multicore-mailbox/E1M-AEN801/README.md +120 -0
  300. tan/templates/vendored/multicore-mailbox/E1M-AEN801/board.yaml +57 -0
  301. tan/templates/vendored/multicore-mailbox/E1M-AEN801/boards/native_sim_native_64.overlay +40 -0
  302. tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/CMakeLists.txt +13 -0
  303. tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/main.c +171 -0
  304. tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/prj.conf +17 -0
  305. tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/testcase.yaml +23 -0
  306. tan/templates/vendored/multicore-mailbox/E1M-AEN801/prj.conf +22 -0
  307. tan/templates/vendored/multicore-mailbox/E1M-AEN801/src/main.c +185 -0
  308. tan/templates/vendored/multicore-mailbox/E1M-AEN801/testcase.yaml +26 -0
  309. tan/templates/vendored/sensor/E1M-AEN801/CMakeLists.txt +8 -0
  310. tan/templates/vendored/sensor/E1M-AEN801/README.md +112 -0
  311. tan/templates/vendored/sensor/E1M-AEN801/board.yaml +57 -0
  312. tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.conf +10 -0
  313. tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.overlay +28 -0
  314. tan/templates/vendored/sensor/E1M-AEN801/prj.conf +5 -0
  315. tan/templates/vendored/sensor/E1M-AEN801/src/main.c +186 -0
  316. tan/templates/vendored/sensor/E1M-AEN801/testcase.yaml +52 -0
  317. tan/templates/vendored/sensor/E1M-V2N101/CMakeLists.txt +8 -0
  318. tan/templates/vendored/sensor/E1M-V2N101/README.md +112 -0
  319. tan/templates/vendored/sensor/E1M-V2N101/board.yaml +57 -0
  320. tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.conf +10 -0
  321. tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.overlay +28 -0
  322. tan/templates/vendored/sensor/E1M-V2N101/prj.conf +5 -0
  323. tan/templates/vendored/sensor/E1M-V2N101/src/main.c +186 -0
  324. tan/templates/vendored/sensor/E1M-V2N101/testcase.yaml +52 -0
  325. tan/version.py +102 -0
  326. tan_cli-0.7.0.dist-info/METADATA +125 -0
  327. tan_cli-0.7.0.dist-info/RECORD +330 -0
  328. tan_cli-0.7.0.dist-info/WHEEL +5 -0
  329. tan_cli-0.7.0.dist-info/entry_points.txt +2 -0
  330. tan_cli-0.7.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,325 @@
1
+ #!/usr/bin/env python3
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ """SoM/SoC metadata readers -- the memory map, the capability set, the SoC ref.
4
+
5
+ RELOCATED (was alp-sdk `scripts/alp_project_loader.py`). These are the last
6
+ functions `tan/planner/*` reached for across the repo boundary: `carveout.py`,
7
+ `partition.py` and `loader.py` want `resolve_memory_map`, `libraries.py` and
8
+ `kconfig.py` want `resolve_capabilities`, and `kconfig.py` wants
9
+ `silicon_to_kconfig` + `som_unpopulated_capabilities`. While they lived in
10
+ alp-sdk every one of those module-scope imports dragged `alp_project` ->
11
+ `alp_project_loader` -> `alp_orchestrate` back into the process, so the planner
12
+ emitted from `tan` but still LOADED the SDK's Python.
13
+
14
+ **They read metadata; they do not carry it.** Every path below resolves under
15
+ `.paths.METADATA_ROOT`, i.e. the bound alp-sdk checkout -- the SoC JSON, the
16
+ SoM preset, the silicon-Kconfig registry. `metadata/**` stays in alp-sdk
17
+ (ADR-0017 / I-26): what moved is the derivation, never the fact. A copy of any
18
+ of these tables inside `tan` would be a second source of truth.
19
+
20
+ Implementations are the SDK's, moved substantially as-is: their precedence
21
+ rules (a SoM `memory_map:` override beating the SoC variant, SoC-level
22
+ `memory_regions` beating the per-bank derivation, SoM capabilities beating SoC
23
+ defaults, then the per-SKU `unpopulated` restriction forcing 0/False) are
24
+ accumulated behaviour that `tests/parity/test_planner_emit_parity.py` pins byte
25
+ for byte -- not something to re-derive.
26
+
27
+ `resolve_soc_path` itself is defined in the leaf module `tan.soc_ref` (ADR-0028
28
+ Task 2 follow-up) and re-exported here, so `tan.model.targets` can use it
29
+ without pulling in this package's own import-time metadata reads.
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ import functools
35
+ import json
36
+ import re
37
+ from pathlib import Path
38
+ from typing import Any
39
+
40
+ from tan.soc_ref import resolve_soc_path # noqa: F401 (re-export: see below)
41
+
42
+ # ---------------------------------------------------------------------
43
+ # SKU-family mapping
44
+ # ---------------------------------------------------------------------
45
+ #
46
+ # The SKU-prefix -> family-dir map is a small, pure derivation with no
47
+ # second on-disk source, so we keep it inline. When a new SoM family
48
+ # lands, add the entry here + update the schema's `som.sku` pattern.
49
+ # (The silicon -> Kconfig mapping, by contrast, lives in the versioned
50
+ # registry below -- see silicon_to_kconfig().)
51
+
52
+ _SKU_FAMILY = re.compile(r"^E1M-(AEN|V2N|V2M)")
53
+
54
+
55
+ def _sku_family(sku: str) -> str:
56
+ """Return the SoM family directory name for a SKU string."""
57
+ m = _SKU_FAMILY.match(sku)
58
+ if m is None:
59
+ raise ValueError(f"unrecognised SoM SKU pattern: {sku}")
60
+ return {"AEN": "aen", "V2N": "v2n", "V2M": "v2n-m1"}[m.group(1)]
61
+
62
+
63
+ # Silicon ref -> Zephyr SoC-select Kconfig symbol.
64
+ #
65
+ # This is NOT a hand-maintained table: the symbol is computed from the
66
+ # ref, and the single source of the allowlist is the versioned registry
67
+ # at metadata/registries/silicon-kconfig.json.
68
+
69
+
70
+ @functools.lru_cache(maxsize=None)
71
+ def _load_silicon_kconfig(metadata_root: Path) -> tuple[str, frozenset[str]]:
72
+ """Load (socSymbolPrefix, knownSilicon) from *metadata_root*'s versioned
73
+ registry. Cache is keyed on *metadata_root* so a second root in the
74
+ same process doesn't silently reuse the first root's table (#1485)."""
75
+ registry = Path(metadata_root) / "registries" / "silicon-kconfig.json"
76
+ data = json.loads(registry.read_text(encoding="utf-8"))
77
+ return data["socSymbolPrefix"], frozenset(data["knownSilicon"])
78
+
79
+
80
+ def silicon_to_kconfig(silicon: str | None, metadata_root: Path) -> str | None:
81
+ """Return the Zephyr Kconfig symbol that selects *silicon*, or ``None``.
82
+
83
+ The symbol is computed as ``socSymbolPrefix + ref.upper().replace(':','_')``;
84
+ e.g. ``alif:ensemble:e7`` -> ``ALP_SOC_ALIF_ENSEMBLE_E7``. ``None`` is
85
+ returned for any ref not in the registry allowlist (so an accelerator
86
+ such as ``deepx:dx:m1`` -- or an unknown ref -- emits no CONFIG line).
87
+
88
+ *metadata_root* is REQUIRED -- callers must pass the project's own
89
+ ``project.effective_metadata_root()``, never a module default: a
90
+ silent fall-through to the SDK's own in-tree registry regardless of a
91
+ project's `--metadata-root` override was alp-sdk#1485's exact defect,
92
+ and the module-level `SILICON_KCONFIG_REGISTRY` constant this replaces
93
+ was the last site in tan's planner still shaped that way (tan-cli#868).
94
+ """
95
+ if silicon is None:
96
+ return None
97
+ prefix, known = _load_silicon_kconfig(metadata_root)
98
+ if silicon not in known:
99
+ return None
100
+ return prefix + silicon.upper().replace(":", "_")
101
+
102
+
103
+ # resolve_soc_path() moved to the leaf module `tan.soc_ref` (imported above)
104
+ # so that `tan.model.targets` can use it without pulling in `tan.planner`'s
105
+ # import-time metadata reads. Re-exported here (rather than inlined) so this
106
+ # module's own four call sites, and every external `from
107
+ # tan.planner.som_metadata import resolve_soc_path`, keep working unchanged.
108
+
109
+
110
+ def _resolve_silicon_variant(
111
+ sku_preset: dict[str, Any],
112
+ metadata_root: Path,
113
+ ) -> dict[str, Any] | None:
114
+ """Resolve a SoM preset to its matching SoC-JSON variant entry.
115
+
116
+ Two paths, in order of preference:
117
+
118
+ 1. Forward: the SoM preset's top-level `silicon_variant:` field
119
+ names the exact `variants[].order_code` to pick out.
120
+ 2. Reverse fallback: scan the SoC JSON's `variants[]` for one
121
+ whose `alp_module_skus` array contains the SoM SKU.
122
+
123
+ Returns the matched variant dict (with keys order_code, package,
124
+ mram_mb, sram_kb, optional_features, ...) or None when neither
125
+ path resolves (the SoC JSON has no variant for this SKU AND the
126
+ preset declares no silicon_variant, or declares `silicon_variant:
127
+ TBD` per the no-inventing-values rule).
128
+ """
129
+ soc_path = resolve_soc_path(sku_preset.get("silicon"), metadata_root)
130
+ if soc_path is None or not soc_path.is_file():
131
+ return None
132
+ soc_spec = json.loads(soc_path.read_text(encoding="utf-8"))
133
+ variants = soc_spec.get("variants") or []
134
+
135
+ declared = sku_preset.get("silicon_variant")
136
+ # alp-sdk #1048: case/whitespace-insensitive TBD match ("tbd", "Tbd",
137
+ # " TBD " all match) -- a bare `!= "TBD"` let an all-lowercase
138
+ # `silicon_variant: tbd` read as a real order_code instead of being
139
+ # dropped as the placeholder it was hand-typed to mean.
140
+ declared_is_tbd = isinstance(declared, str) and declared.strip().upper() == "TBD"
141
+ if declared and not declared_is_tbd:
142
+ for v in variants:
143
+ if v.get("order_code") == declared:
144
+ return v
145
+ # Forward declared but not found -- noisy fall-through to reverse lookup.
146
+
147
+ sku = sku_preset.get("sku")
148
+ if sku:
149
+ for v in variants:
150
+ if sku in (v.get("alp_module_skus") or []):
151
+ return v
152
+ return None
153
+
154
+
155
+ def resolve_memory_map(
156
+ sku_preset: dict[str, Any],
157
+ metadata_root: Path,
158
+ ) -> list[dict[str, Any]]:
159
+ """Derive the effective memory-region table for a SoM.
160
+
161
+ Precedence:
162
+ 1. If the SoM preset declares `memory_map:` explicitly (used
163
+ only for non-stock partitioning -- e.g. reserving SRAM for
164
+ a hardware secure enclave outside the default layout), it
165
+ wins verbatim.
166
+ 2. Otherwise the loader derives the region list from the SoC
167
+ JSON variant resolved via `silicon_variant:`. Each named
168
+ SRAM bank becomes one region; the MRAM becomes one region.
169
+ Per-core TCM banks (SRAM names ending `_<core>_(ITCM|DTCM)`)
170
+ carry `accessible_from: [<core>]`; un-suffixed banks are
171
+ shared across every core declared in the variant's parent
172
+ SoC `cores[]` list.
173
+
174
+ The returned dicts have the keys defined by the memory_region
175
+ schema: `name`, `size_kib`, `accessible_from`, `cacheable`,
176
+ `write_authority` (always set explicitly on derived rows) (plus
177
+ optional `base` only when the SoM preset's override declares one
178
+ -- silicon-default bases stay unset, so downstream emitters know
179
+ to use the silicon's defaults).
180
+
181
+ Returns an empty list when the silicon_variant cannot be resolved
182
+ (e.g. a preset with `silicon_variant: TBD`) -- callers should treat
183
+ that as "memory layout pending the HW-config writeup".
184
+ """
185
+ declared = sku_preset.get("memory_map")
186
+ if declared:
187
+ # SoM-side override wins; the loader trusts the maintainer.
188
+ return list(declared)
189
+
190
+ variant = _resolve_silicon_variant(sku_preset, metadata_root)
191
+ if not variant:
192
+ return []
193
+
194
+ # Re-load the SoC JSON to grab the cores[] topology (the variant
195
+ # dict alone doesn't carry per-core ids).
196
+ soc_path = resolve_soc_path(sku_preset.get("silicon"), metadata_root)
197
+ if soc_path is None or not soc_path.is_file():
198
+ return []
199
+ soc_spec = json.loads(soc_path.read_text(encoding="utf-8"))
200
+ soc_cores = [c.get("id") for c in soc_spec.get("cores", []) if c.get("id")]
201
+
202
+ regions: list[dict[str, Any]] = []
203
+
204
+ # Prefer SoC-level fixed memory_regions when present — these carry
205
+ # authoritative base addresses (e.g. RZ/V2N OCRAM at 0x00010000).
206
+ soc_memory_regions = soc_spec.get("memory_regions")
207
+ if soc_memory_regions:
208
+ # Every SoC-level region is RAM (no SoC declares a flash window
209
+ # here), so each is runtime-writable by the application.
210
+ return [{**r, "write_authority": "customer_runtime"}
211
+ for r in soc_memory_regions]
212
+
213
+ # MRAM as one region (size in KiB; mram_mb -> *1024).
214
+ mram_mb = variant.get("mram_mb")
215
+ if isinstance(mram_mb, (int, float)) and mram_mb > 0:
216
+ regions.append({
217
+ "name": "mram_main",
218
+ "size_kib": int(mram_mb * 1024),
219
+ "accessible_from": list(soc_cores),
220
+ "cacheable": True,
221
+ # The whole-device alias spans MCUboot/ATOC/slot0/storage
222
+ # rows of differing authority, so no single value is true;
223
+ # `composite` is the schema's explicit word for that
224
+ # (ADR-0034 clause 4: stated, never defaulted).
225
+ "write_authority": "composite",
226
+ })
227
+
228
+ # SRAM banks. Per-core TCM banks get `accessible_from: [<core>]`;
229
+ # the rest are shared across every core in the SoC.
230
+ sram_banks = variant.get("sram_banks_kb") or {}
231
+ for bank_name, size_kib in sram_banks.items():
232
+ if not isinstance(size_kib, int) or size_kib <= 0:
233
+ continue
234
+ # Detect per-core TCM by suffix.
235
+ accessible: list[str] = list(soc_cores)
236
+ for core_id in soc_cores:
237
+ suffix_token = f"_{core_id.upper()}_"
238
+ if suffix_token in bank_name.upper():
239
+ accessible = [core_id]
240
+ break
241
+ # TCMs are typically non-cacheable; bulk SRAM is cacheable.
242
+ is_tcm = "ITCM" in bank_name.upper() or "DTCM" in bank_name.upper()
243
+ regions.append({
244
+ "name": bank_name.lower(),
245
+ "size_kib": int(size_kib),
246
+ "accessible_from": accessible,
247
+ "cacheable": not is_tcm,
248
+ # SRAM/TCM is RAM: runtime-writable by the application.
249
+ "write_authority": "customer_runtime",
250
+ })
251
+
252
+ return regions
253
+
254
+
255
+ def resolve_capabilities(
256
+ sku_preset: dict[str, Any],
257
+ metadata_root: Path,
258
+ ) -> dict[str, Any]:
259
+ """Compose silicon capabilities from the SoC JSON with SoM-side overrides/extensions.
260
+
261
+ Returns a dict where SoM-declared keys override SoC-declared defaults for the same key
262
+ (e.g., V2N's `cau: true` via the GD32 bridge overrides the RZ/V2N silicon's `cau: false`).
263
+ Keys that exist on only one side are passed through unchanged.
264
+
265
+ Precedence:
266
+ 1. Resolve the SoC ref via the ``silicon:`` field (same path used by
267
+ ``_resolve_silicon_variant`` and ``resolve_memory_map``).
268
+ 2. Read ``soc["capabilities"]`` (defaults to ``{}`` when absent --
269
+ older SoC JSONs that pre-date this field continue to work).
270
+ 3. Read ``sku_preset.get("capabilities", {})``.
271
+ 4. Merge; SoM-side wins on key collision so that SoM add-on chips /
272
+ GD32 bridge capabilities can override the host silicon's defaults.
273
+ 5. Apply the SKU's ``silicon_capabilities.unpopulated`` RESTRICTION
274
+ list: each listed silicon capability is forced to 0 (count) /
275
+ ``False`` (flag). A SKU can only remove what the silicon offers
276
+ (enforced by scripts/validate_metadata.py); presets without the
277
+ field keep the full silicon capability set.
278
+ """
279
+ soc_path = resolve_soc_path(sku_preset.get("silicon"), metadata_root)
280
+ soc_caps: dict[str, Any] = {}
281
+ if soc_path is not None and soc_path.is_file():
282
+ soc_spec = json.loads(soc_path.read_text(encoding="utf-8"))
283
+ soc_caps = soc_spec.get("capabilities") or {}
284
+
285
+ som_caps: dict[str, Any] = sku_preset.get("capabilities") or {}
286
+
287
+ # SoM side wins on collision (bridge / add-on overrides silicon default).
288
+ merged: dict[str, Any] = {**soc_caps, **som_caps}
289
+
290
+ # GPU: a per-die fact (variants[].optional_features.gpu_mali_g31, the one
291
+ # source) -- the SoC-level capabilities block cannot carry it because
292
+ # four of the eight RZ/V2N dies are fused without the Mali-G31.
293
+ variant = _resolve_silicon_variant(sku_preset, metadata_root)
294
+ mali = ((variant or {}).get("optional_features") or {}).get("gpu_mali_g31")
295
+ if mali is not None:
296
+ merged["gpu2d"] = bool(mali)
297
+
298
+ # SKU-level restriction: capabilities the silicon offers but this SKU
299
+ # leaves unpopulated (per-SKU granularity -- one family, many SKUs).
300
+ # Preserve the value class so count-style caps (ethos_u55_count, ...)
301
+ # restrict to 0 and flag-style caps restrict to False.
302
+ for name in som_unpopulated_capabilities(sku_preset):
303
+ base = soc_caps.get(name)
304
+ if isinstance(base, int) and not isinstance(base, bool):
305
+ merged[name] = 0
306
+ else:
307
+ merged[name] = False
308
+ return merged
309
+
310
+
311
+ def som_unpopulated_capabilities(sku_preset: dict[str, Any]) -> list[str]:
312
+ """Return the SKU's `silicon_capabilities.unpopulated` list (or []).
313
+
314
+ Single accessor for the per-SKU capability RESTRICTION block so this
315
+ module (`resolve_capabilities`), the emitters (`kconfig.py`, which passes
316
+ `-DALP_SOM_<TOKEN>` only for restricted SKUs) and alp-sdk's header
317
+ generator (`gen_soc_caps.py`) agree on where the field lives.
318
+ """
319
+ block = sku_preset.get("silicon_capabilities") or {}
320
+ if not isinstance(block, dict):
321
+ return []
322
+ names = block.get("unpopulated") or []
323
+ if not isinstance(names, list):
324
+ return []
325
+ return [str(n) for n in names]
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env python3
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ """Duplicate-key-rejecting YAML/JSON loaders (alp-sdk issue #1127).
4
+
5
+ RELOCATED (was alp-sdk `scripts/strict_loaders.py`, a top-level module outside
6
+ `scripts/alp_orchestrate/`, hand-ported the same way `zephyr_board.py` /
7
+ `project_loader.py` were -- named in `HAND_PORT_SOURCES` (not `PINNED_HASHES`,
8
+ since it has no `scripts/alp_orchestrate/` counterpart to pin by filename), but
9
+ its staleness hash lives in its OWN `STRICT_LOADERS_HASH` /
10
+ `STRICT_LOADERS_PINNED_SDK_COMMIT` pair, not `HAND_PORT_HASHES` -- see
11
+ `test_planner_relocation_freshness.py`'s STRICT_LOADERS_* comment for why).
12
+
13
+ `yaml.safe_load` and `json.loads` silently keep only the LAST value of a
14
+ duplicated mapping key -- `yaml.safe_load("som: a\\nsom: b\\n")` returns
15
+ `{'som': 'b'}` with no error or warning. In a hand-edited board.yaml,
16
+ SoM preset, or chip/SoC manifest, a duplicated key is invisible in a
17
+ diff and silently drops hardware configuration. `strict_yaml_load()`
18
+ and `strict_json_loads()` are the one place `tan/planner/loader.py`'s
19
+ `_load_yaml` / `_load_json` route through instead of the raw stdlib
20
+ loaders, so a board.yaml `tan build` accepts is the same board.yaml
21
+ `tan validate` (which shells the SDK validator, itself routed through
22
+ alp-sdk's own copy of this module) accepts.
23
+
24
+ `fast_safe_load()` is the lenient sibling: exactly `yaml.safe_load`
25
+ semantics (duplicate keys keep the last value) for hot readers that do not
26
+ opt into rejection (`sdk_compat.load_family_table`). Both YAML paths parse
27
+ with libyaml's C parser whenever PyYAML was built with it, and fall back to
28
+ the pure-Python one otherwise (alp-sdk #2328).
29
+
30
+ Zero dependencies beyond PyYAML on purpose, same rationale as alp-sdk's
31
+ copy: this is a leaf every metadata-ingestion boundary can import without
32
+ creating a cycle.
33
+ """
34
+
35
+ from __future__ import annotations
36
+
37
+ import json
38
+ from pathlib import Path
39
+ from typing import Any
40
+
41
+ import yaml
42
+
43
+
44
+ class DuplicateKeyError(ValueError):
45
+ """Raised when a YAML/JSON mapping repeats a key."""
46
+
47
+
48
+ def _no_duplicates_mapping_constructor(
49
+ loader: yaml.constructor.SafeConstructor, node: yaml.MappingNode
50
+ ) -> dict[str, Any]:
51
+ # Duplicate-key detection must run on the node's OWN explicit pairs
52
+ # BEFORE `flatten_mapping()` splices in `<<: *anchor` merge-key
53
+ # pairs -- otherwise a spec-legal merge override (the anchor
54
+ # supplies a default, a sibling explicit key overrides it) reads as
55
+ # the same key twice and gets rejected as a duplicate, when the
56
+ # explicit value should just win, exactly like the stdlib loader
57
+ # does. Scanning only the node's own pairs also leaves two merge
58
+ # SOURCES quietly disagreeing on a key to the normal YAML
59
+ # first-wins merge semantics, not a hard error -- this loader's job
60
+ # is to catch a literal duplicate key typed twice in one mapping.
61
+ seen: set[Any] = set()
62
+ for key_node, _value_node in node.value:
63
+ if key_node.tag == "tag:yaml.org,2002:merge":
64
+ continue
65
+ key = loader.construct_object(key_node, deep=True)
66
+ if key in seen:
67
+ mark = key_node.start_mark
68
+ raise DuplicateKeyError(
69
+ f"duplicate key {key!r} at line {mark.line + 1}, "
70
+ f"column {mark.column + 1}"
71
+ )
72
+ seen.add(key)
73
+
74
+ loader.flatten_mapping(node)
75
+ mapping: dict[str, Any] = {}
76
+ for key_node, value_node in node.value:
77
+ key = loader.construct_object(key_node, deep=True)
78
+ mapping[key] = loader.construct_object(value_node, deep=True)
79
+ return mapping
80
+
81
+
82
+ # libyaml's C parser when PyYAML was built with it (its wheels are), the
83
+ # pure-Python one otherwise. Same SafeConstructor either way, so the same
84
+ # values and the same duplicate-key rejection -- only ~10x faster to parse
85
+ # (alp-sdk #2328).
86
+ _SAFE_LOADER = getattr(yaml, "CSafeLoader", yaml.SafeLoader)
87
+
88
+
89
+ class _StrictLoader(_SAFE_LOADER):
90
+ pass
91
+
92
+
93
+ _StrictLoader.add_constructor(
94
+ yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, _no_duplicates_mapping_constructor
95
+ )
96
+
97
+
98
+ def strict_yaml_load(text: str, source: str | Path = "<string>") -> Any:
99
+ """`yaml.safe_load`, but raise `DuplicateKeyError` on a repeated mapping key."""
100
+ try:
101
+ return yaml.load(text, Loader=_StrictLoader)
102
+ except DuplicateKeyError as e:
103
+ raise DuplicateKeyError(f"{source}: {e}") from e
104
+
105
+
106
+ def fast_safe_load(text: str) -> Any:
107
+ """Exactly `yaml.safe_load(text)`, parsed by libyaml when available.
108
+
109
+ For the hot metadata readers that want stdlib semantics (duplicate keys
110
+ keep the last value) rather than `strict_yaml_load`'s rejection.
111
+ """
112
+ return yaml.load(text, Loader=_SAFE_LOADER)
113
+
114
+
115
+ def _no_duplicates_object_pairs_hook(
116
+ pairs: list[tuple[str, Any]],
117
+ ) -> dict[str, Any]:
118
+ mapping: dict[str, Any] = {}
119
+ for key, value in pairs:
120
+ if key in mapping:
121
+ raise DuplicateKeyError(f"duplicate key {key!r}")
122
+ mapping[key] = value
123
+ return mapping
124
+
125
+
126
+ def strict_json_loads(text: str, source: str | Path = "<string>") -> Any:
127
+ """`json.loads`, but raise `DuplicateKeyError` on a repeated object key."""
128
+ try:
129
+ return json.loads(text, object_pairs_hook=_no_duplicates_object_pairs_hook)
130
+ except DuplicateKeyError as e:
131
+ raise DuplicateKeyError(f"{source}: {e}") from e