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,578 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """`tan flash --ram` -- AEN Flow C: J-Link ITCM RAM-run + RAM-console read
3
+ (tan-cli#1313).
4
+
5
+ **Why `flash --ram` and not `run --ram`.** `tan run` is a thin orchestrator that
6
+ builds and then delegates to this command's engine; everything Flow C needs --
7
+ the probe-selection guard (tan-cli#1312), the DPIDR preflight, the trusted J-Link
8
+ binary (tan-cli#1336), `--dry-run`, `--jlink`, the per-run envelope -- already
9
+ lives behind `tan flash`. A `run --ram` would re-expose a third copy of it.
10
+
11
+ **What it does** (the proven raw recipe is alp-sdk `scripts/bench/aen/ram-run.sh`):
12
+
13
+ 1. read the slice's ELF and its sibling `zephyr.bin`; the load address is DERIVED
14
+ from the ELF's LOAD segments and the image is refused unless it is linked for
15
+ ITCM/SRAM (`tan.core.ram_run.plan_ram_image`) -- an MRAM-linked image would just
16
+ re-enter the resident MRAM image;
17
+ 2. verify the probe (the same `_ProbeGuard` as a Flow D write: ShowEmuList, TOCTOU
18
+ re-check, shared-serial handshake, `exec DisableAutoUpdateFW`), run the read-only
19
+ DPIDR preflight when `expect_dpidr`/`jlink_device` arm it, then ONE J-Link session:
20
+ `connect; halt; loadbin zephyr.bin <base>; setpc <reset handler>; go`. `loadbin`
21
+ resets the core, which loads SP from the vector table; the PC is set from the
22
+ vector table's reset handler (refused if it disagrees with the ELF entry).
23
+ `WReg` is not accepted by J-Link Commander here (bench notes), so SP is not
24
+ written by hand -- the vector-table SP is reported, not applied;
25
+ 3. with `--ram-console`, wait `--wait` seconds and read `ram_console_buf`
26
+ (address and size from the ELF symbol, never hardcoded) in a second session
27
+ through `mem8`, decode it and report it.
28
+
29
+ **Nothing writes MRAM**: the only addresses in the script are the ITCM/SRAM load
30
+ base and the console buffer, and an MRAM-linked image is refused.
31
+ """
32
+ from __future__ import annotations
33
+
34
+ import contextlib
35
+ import os
36
+ import re
37
+ import shutil
38
+ import tempfile
39
+ import time
40
+ from typing import Any
41
+
42
+ from tan.commands import flash_cmd as fc
43
+ from tan.core.flash_plan import (
44
+ FLOW_D_METHOD,
45
+ FlashInputs,
46
+ FlashPlan,
47
+ FlashPlanError,
48
+ FlashTarget,
49
+ _DEFAULT_JLINK_SPEED,
50
+ commander_path,
51
+ confirm_gate_note,
52
+ dpidr_preflight_unarmed,
53
+ fa_bool_checked,
54
+ fa_int_checked,
55
+ fa_str,
56
+ fa_str_checked,
57
+ validate_commander_path,
58
+ validate_identifier,
59
+ )
60
+ from tan.core.jlink_binary import resolve_jlink
61
+ from tan.core import ram_watch
62
+ from tan.core.ram_run import (
63
+ CODE_CONSOLE_SYMBOL_MISSING,
64
+ CODE_CORE_MISMATCH,
65
+ CODE_CORE_UNCONFIRMED,
66
+ OVERRIDABLE_VERDICTS,
67
+ trouble_markers,
68
+ ap_verdict,
69
+ core_check,
70
+ core_check_script,
71
+ apertures_for,
72
+ attached_core,
73
+ CODE_FAILED,
74
+ CONSOLE_SYMBOL,
75
+ RamRunError,
76
+ check_session,
77
+ decode_console,
78
+ load_script,
79
+ parse_elf,
80
+ plan_ram_image,
81
+ preamble,
82
+ read_back,
83
+ read_script,
84
+ )
85
+
86
+ #: The `method` this entry reports.
87
+ METHOD = "ram_run"
88
+ #: Default `--wait` (seconds the app runs before the console is read) -- the raw
89
+ #: script's `sleep_ms` default of 1500.
90
+ DEFAULT_WAIT_S = 1.5
91
+ #: Experimental (tan-cli#1372 ask 2, pending bench A/B): keep the load session open for
92
+ #: the `--wait` window after `go` and skip the separate pre-read wait.
93
+ HOLD_ENV = "TAN_FLASH_RAM_HOLD"
94
+ #: J-Link's own attach profile for a live core (the script's `JLINK_DEVICE_READ`).
95
+ _DEFAULT_ATTACH_DEVICE = "Cortex-M55"
96
+
97
+
98
+ def _artefact_pair(path: str) -> tuple[str, str]:
99
+ """`(elf, bin)` for a manifest artefact: `zephyr.elf` + `zephyr.bin`."""
100
+ stem, ext = os.path.splitext(path)
101
+ elf = path if ext.lower() == ".elf" else stem + ".elf"
102
+ return elf, os.path.splitext(elf)[0] + ".bin"
103
+
104
+
105
+ def _read(path: str) -> bytes:
106
+ with open(path, "rb") as fh:
107
+ return fh.read()
108
+
109
+
110
+ def run_ram_entry(target: FlashTarget, ctx: Any) -> tuple[int, Any, list[str]]:
111
+ """RAM-run one slice. Returns `(rc, entry, text-lines)` like `_flash_entry`."""
112
+ with contextlib.ExitStack() as stack:
113
+ return _run_ram_entry(target, ctx, stack)
114
+
115
+
116
+ def _stage_image(stack: contextlib.ExitStack, data: bytes) -> str:
117
+ """Write the image to a TAN-OWNED temp file with a fixed safe name and return its
118
+ path -- the ONLY path that is ever interpolated into the Commander `loadbin`
119
+ line, so a project-controlled artefact path (newline, quote, `;`) can never reach
120
+ the script. Removed when the entry ends."""
121
+ directory = tempfile.mkdtemp(prefix="tan-ram-")
122
+ stack.callback(shutil.rmtree, directory, True)
123
+ path = os.path.join(directory, "image.bin")
124
+ with open(path, "wb") as fh:
125
+ fh.write(data)
126
+ validate_commander_path(path, "the staged RAM image path")
127
+ return path
128
+
129
+
130
+ def _run_ram_entry(
131
+ target: FlashTarget, ctx: Any, stack: contextlib.ExitStack
132
+ ) -> tuple[int, Any, list[str]]:
133
+ kind, entry_id = target.kind, target.id
134
+ lines: list[str] = [f"flash: {kind} '{entry_id}' -> {METHOD}"]
135
+ report: dict[str, Any] = {}
136
+ probe_echo: dict[str, Any] | None = None
137
+
138
+ def entry(status: str, rc: int, message: str, **kw: Any) -> Any:
139
+ return fc._Entry(
140
+ kind=kind, id=entry_id, method=METHOD, status=status, rc=rc, message=message,
141
+ probe=probe_echo, extra=report, **kw,
142
+ )
143
+
144
+ def fail(message: str, code: str | None = CODE_FAILED, **kw: Any) -> tuple[int, Any, list[str]]:
145
+ text = f"{METHOD}[{entry_id}]: {message}"
146
+ lines.append(f" FAIL: {text}")
147
+ return 1, entry("failed", 1, text, issue_code=code, **kw), lines
148
+
149
+ if kind != "slice":
150
+ return fail("only a slice can be RAM-run, not a helper MCU")
151
+ if not isinstance(target.flash_args, dict) and target.flash_args is not None:
152
+ return fail("flash_args is not a mapping")
153
+ # `--watch` is refused before anything is read or spawned, under --dry-run too.
154
+ watch_ms = int(round(max(ctx.ram_wait, 0.0) * 1000))
155
+ try:
156
+ watches = ram_watch.parse_watches(ctx.ram_watch)
157
+ watch_script = ram_watch.watch_lines(watches, watch_ms) if watches else []
158
+ except ram_watch.WatchError as err:
159
+ return fail(str(err), err.code)
160
+ flash_args: Any = dict(target.flash_args or {})
161
+
162
+ # ── the image ──
163
+ artefact = target.output_artefact
164
+ if not artefact or fc.is_pending(artefact):
165
+ return fail("the slice has no built output_artefact; run `tan build` first")
166
+ elf_path, bin_path = _artefact_pair(
167
+ fc.resolve_artefact_path(artefact, ctx.build_root, ctx.sdk_root, fc._is_file)
168
+ )
169
+ try:
170
+ # Refused before anything is read or spawned: these strings appear in
171
+ # messages and the transcript header, and a control character in a path is
172
+ # never legitimate. (The script itself only ever sees the staged copy.)
173
+ validate_commander_path(elf_path, "the ELF path")
174
+ validate_commander_path(bin_path, "the binary path")
175
+ validate_identifier(entry_id, "the flash target id")
176
+ except FlashPlanError as err:
177
+ return fail(str(err))
178
+ try:
179
+ elf_bytes, bin_bytes = _read(elf_path), _read(bin_path)
180
+ except OSError as err:
181
+ return fail(f"cannot read the built image ({err}); expected {elf_path} and {bin_path}")
182
+ try:
183
+ apertures = _load_apertures(ctx, entry_id)
184
+ image = plan_ram_image(
185
+ parse_elf(elf_bytes), bin_bytes, core_id=entry_id, apertures=apertures
186
+ )
187
+ except RamRunError as err:
188
+ return fail(str(err), err.code)
189
+
190
+ # ── J-Link: trusted binary, probe selection, parameters ──
191
+ found = resolve_jlink(ctx.jlink_path, project_dir=ctx.project_dir)
192
+ exe = found.path if found is not None else None
193
+ report["jlink"] = {"binary": exe, "binarySource": found.source if found else None}
194
+ if exe is None and not ctx.dry_run:
195
+ return fail(fc._NO_TRUSTED_JLINK)
196
+ try:
197
+ flash_args, selection, snapshot = fc._flow_d_probe_selection(flash_args, ctx)
198
+ probe_echo = fc._probe_echo(selection, ctx)
199
+ if selection is not None and selection.refusal_code is not None:
200
+ lines.append(f" FAIL: {selection.refusal}")
201
+ return (
202
+ 1,
203
+ entry("failed", 1, selection.refusal or "", probe_refusal=selection.refusal_code),
204
+ lines,
205
+ )
206
+ guard = None
207
+ if selection is not None and (selection.serial is not None or selection.visible is None):
208
+ guard = fc._ProbeGuard(selection, snapshot, ctx.enumerate_probes, probe_echo)
209
+ serial = fa_str_checked(flash_args, "jlink_serial", False)
210
+ device = fa_str(flash_args, "jlink_device") or _DEFAULT_ATTACH_DEVICE
211
+ validate_identifier(device, "jlink_device", destination="a J-Link Commander script line")
212
+ if not device.startswith("Cortex-"):
213
+ # The part-number FLASH profile (`jlink_flash_device`) unlocks the MRAM
214
+ # loader and will not re-halt a running core; the bench's JLINK_DEVICE_READ
215
+ # contract is the generic core profile for every RAM-run attach.
216
+ raise FlashPlanError(
217
+ f"jlink_device '{device}' is a part-number profile, not a generic core "
218
+ f"profile; a RAM-run attaches with '{_DEFAULT_ATTACH_DEVICE}' (the part "
219
+ "profile unlocks the MRAM loader and cannot re-halt a live core)"
220
+ )
221
+ speed = fa_int_checked(flash_args, "jlink_speed") or _DEFAULT_JLINK_SPEED
222
+ if serial is not None:
223
+ validate_identifier(serial, "jlink_serial", destination=fc._JLINK_SERIAL_DESTINATION)
224
+ fc.validate_flow_d_preflight_args(flash_args)
225
+ except FlashPlanError as err:
226
+ return fail(str(err))
227
+
228
+ try:
229
+ staged = _stage_image(stack, bin_bytes)
230
+ pre = preamble(serial, speed, device)
231
+ except (OSError, FlashPlanError, RamRunError) as err:
232
+ return fail(f"cannot prepare the RAM-run ({err})")
233
+ # `image.base`/`entry` and the console address/size are ints rendered with
234
+ # `0x%X` by `ram_run`; the symbol NAME never reaches a script at all.
235
+ console = image.console
236
+ # A watch session already spans the whole --wait window, so it needs no extra Sleep.
237
+ hold = (
238
+ os.environ.get(HOLD_ENV) == "1" and bool(ctx.ram_console) and console is not None
239
+ )
240
+ hold_ms = watch_ms if hold and not watches else 0
241
+ load = load_script(pre, commander_path(staged), image, watch_script, hold_ms)
242
+ argv = (
243
+ "JLinkExe", "-device", device, "-if", "SWD", "-speed", str(speed),
244
+ "-ExitOnError", "1", "-NoGui", "1", "-CommanderScript",
245
+ )
246
+ selected = "ram" if console is not None else "uart"
247
+ ram_console: dict[str, Any] = {
248
+ "selected": selected,
249
+ "symbol": CONSOLE_SYMBOL if console else None,
250
+ "address": f"0x{console[0]:08X}" if console else None,
251
+ "size": console[1] if console else None,
252
+ "requested": bool(ctx.ram_console),
253
+ }
254
+ report["ramConsole"] = ram_console
255
+ report["ram"] = {
256
+ "elf": elf_path, "binary": bin_path, "stagedImage": staged, "loadAddress": f"0x{image.base:08X}",
257
+ "size": image.size, "entry": f"0x{image.entry:08X}",
258
+ "initialSp": f"0x{image.initial_sp:08X}",
259
+ "spNote": "from the vector table; applied by loadbin's reset, not written by tan",
260
+ "wait": ctx.ram_wait, "writesMram": False,
261
+ "holdSession": hold,
262
+ }
263
+ if watches:
264
+ report["ram"]["watch"] = {
265
+ "specs": [
266
+ {"address": f"0x{w.address:08X}", "words": w.words, "periodMs": w.period_ms}
267
+ for w in watches
268
+ ],
269
+ "durationMs": watch_ms,
270
+ "timeBasis": "scheduled",
271
+ "access": "mem32 (read-only)",
272
+ }
273
+ report["plan"] = {
274
+ "argv": list(argv),
275
+ "jlinkScript": (fc._DISABLE_FW_UPDATE + load).splitlines(),
276
+ "coreCheckScript": (fc._DISABLE_FW_UPDATE + core_check_script(pre)).splitlines(),
277
+ "consoleReadScript": (
278
+ (fc._DISABLE_FW_UPDATE + read_script(pre, *console)).splitlines()
279
+ if console and ctx.ram_console else None
280
+ ),
281
+ }
282
+ missing_symbol = bool(ctx.ram_console and console is None)
283
+ warn_missing = {"ram_console_missing": True} if missing_symbol else {}
284
+ summary = (
285
+ f"loaded {image.size} B at 0x{image.base:X}, PC=0x{image.entry:X}, "
286
+ f"SP=0x{image.initial_sp:X} (vector table)"
287
+ )
288
+
289
+ if ctx.dry_run:
290
+ msg = f"{METHOD}[{entry_id}]: would run -- {summary}; no MRAM write; nothing spawned"
291
+ lines.append(f" {msg}")
292
+ return 0, entry("ok", 0, msg, trcena_note=True, trcena_hold=hold, **warn_missing), lines
293
+
294
+ # ── the confirm gate (bench round 7) ──
295
+ # `loadbin` resets the core through AIRCR.SYSRESETREQ, a full-device reset that also
296
+ # resets the Secure Enclave, and the run REPLACES the running image: it is gated like
297
+ # every other write. Without --confirm (or ALP_FLASH_FORCE=1 / flash_args.confirm) it
298
+ # previews and exits non-zero, exactly as an unconfirmed flash does.
299
+ try:
300
+ confirmed = ctx.force_confirm or bool(fa_bool_checked(flash_args, "confirm"))
301
+ except FlashPlanError as err:
302
+ return fail(str(err))
303
+ if not confirmed:
304
+ msg = (
305
+ f"{METHOD}[{entry_id}]: would run -- {summary} -- NOT run: "
306
+ f"{confirm_gate_note('--confirm was not given')}. --ram resets the whole device "
307
+ "(AIRCR.SYSRESETREQ, which resets the Secure Enclave) and replaces the running image."
308
+ )
309
+ lines.append(f" {msg}")
310
+ return 0, entry("planned", 0, msg, **warn_missing), lines
311
+
312
+ # ── the wrong-board guard, exactly as for a Flow D write ──
313
+ armed = not dpidr_preflight_unarmed(FLOW_D_METHOD, flash_args, None)
314
+ if ctx.require_dpidr and not armed:
315
+ return fail(
316
+ "ALP_FLASH_REQUIRE_DPIDR=1 is set and flash_args.expect_dpidr / jlink_device are "
317
+ "not both set -- refusing to run with no wrong-board guard"
318
+ )
319
+ if guard is not None and armed:
320
+ # Verified once up front; the armed DPIDR preflight reuses it (as in Flow D) and
321
+ # the load session re-verifies immediately before ITS spawn.
322
+ refusal = fc._probe_guard_refusal(guard, exe, None, ctx.workspace)
323
+ if refusal is not None:
324
+ return fail(refusal, None, probe_refusal=guard.tripped_code)
325
+ guard.fresh = True
326
+ facts: dict[str, Any] = {}
327
+ refusal = fc._flow_d_preflight(
328
+ FlashInputs(
329
+ artefact=elf_path, flash_args=flash_args, core_id=entry_id, sku=ctx.sku,
330
+ dry_run=False, force_confirm=ctx.force_confirm,
331
+ ),
332
+ ctx.venv_bin, ctx.workspace, probe_guard=guard, facts=facts, jlink_exe=exe,
333
+ )
334
+ if refusal is not None:
335
+ tripped = guard is not None and guard.tripped == refusal
336
+ return fail(refusal, None, probe_refusal=guard.tripped_code if tripped else None)
337
+ report["jlink"].update({"dpidr": facts.get("dpidr"), "dpidrSource": "preflight" if facts else "none"})
338
+ report["jlink"]["attachedCore"] = None
339
+
340
+ # ── which core did the generic attach land on? (tan-cli#1354) ──
341
+ # DECISIVE: the AP that reports `Core found` (HE APAddr 0x00300000). CORROBORATION: the
342
+ # local ITCM at 0x0 against the HE window 0x58000000 -- two read-only `mem32`s, no halt,
343
+ # no write, and the HP window is never read. Only an HE access port proceeds; HP
344
+ # evidence refuses and --assume-he can never override it.
345
+ check = fc._execute(
346
+ FlashPlan(argv=argv, ok_message="", jlink_script=core_check_script(pre)),
347
+ True, ctx.venv_bin, ctx.workspace, guard, jlink_exe=exe,
348
+ )
349
+ if guard is not None and guard.tripped:
350
+ return fail(guard.tripped, None, probe_refusal=guard.tripped_code)
351
+ check_text = f"{check.stdout}\n{check.stderr}"
352
+ bad = None if check.success else (fc._capture_tail(check) or "J-Link failed")
353
+ bad = bad or check_session(check_text, loadbin=False)
354
+ if bad:
355
+ verdict, evidence = "unreadable", {}
356
+ else:
357
+ verdict, evidence = core_check(check_text)
358
+ report["ram"]["coreCheck"] = {
359
+ "verdict": verdict, **evidence, "assumeHe": bool(ctx.assume_he),
360
+ "basis": "DECISIVE: the AP that reports `Core found` (HE APAddr 0x00300000, HP "
361
+ "0x00200000); CORROBORATION: local ITCM 0x0 compared with the HE global window "
362
+ "0x58000000 (the HP window is never read)",
363
+ **({"sessionError": bad} if bad else {}),
364
+ }
365
+ report["jlink"]["attachedCore"] = evidence.get("ap")
366
+ # Halt/reset trouble the CHECK session itself reported (it halts nothing, but a
367
+ # core that is already unhaltable says so on connect): reported against the check.
368
+ check_trouble = trouble_markers(check_text)
369
+ report["ram"]["coreCheck"]["resetFailures"] = list(check_trouble)
370
+ if verdict in ("hp", "conflict-hp"):
371
+ return fail(
372
+ "the probe is attached to the M55-HP core (J-Link's Core-found access port is "
373
+ "the HP's 0x00200000"
374
+ + (", and the local ITCM equals the HE window, which is contradictory" if verdict == "conflict-hp" else "")
375
+ + ") -- refusing to load the HE image into it. Select the HE core's debug port. "
376
+ "--assume-he never overrides HP evidence.", CODE_CORE_MISMATCH,
377
+ )
378
+ if verdict != "he":
379
+ if not (ctx.assume_he and verdict in OVERRIDABLE_VERDICTS):
380
+ return fail(
381
+ f"cannot confirm the attached core is the M55-HE ({verdict}"
382
+ + (f": {bad}" if bad else "")
383
+ + "): "
384
+ + (
385
+ "the access port says HE but the local ITCM does not equal the HE window "
386
+ "-- contradictory, and not overridable. "
387
+ if verdict == "conflict"
388
+ else "J-Link named no Core-found access port tan can place "
389
+ "(HE 0x00300000), or the check session could not be read. "
390
+ )
391
+ + "Refusing to load."
392
+ + (
393
+ " --assume-he overrides this at your own risk (the bench has seen the "
394
+ "generic attach land on HE 6 of 6 times, which is not proof); it never "
395
+ "overrides HP evidence or a contradiction."
396
+ if verdict in OVERRIDABLE_VERDICTS
397
+ else ""
398
+ ),
399
+ CODE_CORE_UNCONFIRMED,
400
+ )
401
+ report["ram"]["coreCheck"]["assumed"] = True
402
+
403
+ # ── load + go ──
404
+ plan = FlashPlan(argv=argv, ok_message="", jlink_script=load)
405
+ started = time.monotonic()
406
+ outcome = fc._execute(
407
+ plan, ctx.capture, ctx.venv_bin, ctx.workspace, guard, jlink_exe=exe,
408
+ timeout_s=(
409
+ max(fc._FLASH_TIMEOUT_S, ram_watch.session_timeout_s(watches, watch_ms))
410
+ if watches
411
+ else max(fc._FLASH_TIMEOUT_S, hold_ms / 1000 + ram_watch.TIMEOUT_MARGIN_S)
412
+ if hold_ms
413
+ else None
414
+ ),
415
+ )
416
+ spawn_ms = int(round((time.monotonic() - started) * 1000))
417
+ transcript = f"{outcome.stdout}\n{outcome.stderr}"
418
+ _save_transcript(ctx, entry_id, report, load, outcome)
419
+ if guard is not None and guard.tripped:
420
+ return fail(guard.tripped, None, probe_refusal=guard.tripped_code)
421
+ problem = None if outcome.success else (fc._capture_tail(outcome) or "J-Link failed")
422
+ problem = problem or check_session(transcript, loadbin=True)
423
+ if problem:
424
+ return fail(f"the load session failed: {problem}")
425
+ # tan-cli#1497: from here the image IS on the board and the device was reset,
426
+ # whatever fails next; `model run --device` reads this to keep its provenance.
427
+ report["ram"]["loaded"] = True
428
+ loaded_on = attached_core(transcript)
429
+ checked_on = report["jlink"].get("attachedCore")
430
+ if not (loaded_on and loaded_on.get("apAddr")):
431
+ # Not silently skipped: say the load banner named no Core-found AP.
432
+ report["jlink"]["attachedCoreAtLoad"] = None
433
+ report["jlink"]["attachedCoreAtLoadNote"] = (
434
+ "the load transcript named no Core-found access port, so the load session's "
435
+ "core was not compared with the core check's"
436
+ )
437
+ if loaded_on and loaded_on.get("apAddr"):
438
+ # The load is a SEPARATE J-Link session: it can attach to a different AP than the
439
+ # check did (a probe that re-enumerated). After the fact, but loud -- the image may
440
+ # now be in the wrong core. Both attaches are reported.
441
+ report["jlink"]["attachedCoreAtLoad"] = loaded_on
442
+ differs = bool(checked_on) and checked_on.get("apAddr") != loaded_on.get("apAddr")
443
+ if differs or ap_verdict(loaded_on) == "hp":
444
+ return fail(
445
+ f"the load session attached to AP {loaded_on.get('apAddr')} "
446
+ f"({ap_verdict(loaded_on)}) but the core check attached to AP "
447
+ f"{(checked_on or {}).get('apAddr')}: the image may have been loaded into the "
448
+ "WRONG core. Power-cycle the board and check both cores before running "
449
+ "anything else.", CODE_CORE_MISMATCH,
450
+ )
451
+ elif checked_on is None and loaded_on:
452
+ report["jlink"]["attachedCore"] = loaded_on
453
+ if not facts:
454
+ from tan.core.flow_d_report import dpidr_in
455
+
456
+ report["jlink"].update({"dpidr": dpidr_in(transcript), "dpidrSource": "write-transcript"})
457
+ unarmed = not armed
458
+ # Halt/reset trouble is surfaced like Flow D's reset failures (bench round 8): a load
459
+ # that only worked through J-Link's fallback chain is reported, never reported clean.
460
+ trouble = trouble_markers(transcript)
461
+ report["jlink"]["resetFailures"] = list(dict.fromkeys([*check_trouble, *trouble]))
462
+
463
+ # ── the watch samples (tan-cli#1436) ──
464
+ watch_note = None
465
+ if watches:
466
+ samples = ram_watch.parse_samples(transcript, watches, watch_ms)
467
+ report["watch"] = samples
468
+ report["ram"]["watch"]["spawnMs"] = spawn_ms
469
+ missing = sum(1 for s in samples if s["values"] is None)
470
+ if missing == len(samples):
471
+ watch_note = (
472
+ f"{entry_id}: --watch returned NO data: none of the {len(samples)} samples has a "
473
+ "mem32 dump in the J-Link transcript (all values: null); the watched addresses "
474
+ "were not read -- see the transcript"
475
+ )
476
+ elif missing:
477
+ watch_note = (
478
+ f"{entry_id}: {missing} of {len(samples)} --watch samples have no mem32 dump in "
479
+ "the J-Link transcript (values: null); the rest are in data.watch[]"
480
+ )
481
+
482
+ # ── the console ──
483
+ message = f"{METHOD}[{entry_id}]: {summary}; running"
484
+ if check_trouble and not trouble:
485
+ message += (
486
+ "; the core check reported halt/reset trouble (" + ", ".join(check_trouble) + ")"
487
+ )
488
+ if trouble:
489
+ message += (
490
+ "; the load only worked through a J-Link fallback (" + ", ".join(trouble) + ") -- "
491
+ "the core did not halt/reset cleanly, so power-cycle before trusting this run"
492
+ )
493
+ if ctx.ram_console:
494
+ if console is None:
495
+ message += (
496
+ f"; no {CONSOLE_SYMBOL} symbol -- this build selected the UART console "
497
+ "(read it on the console, e.g. `tan monitor`); nothing to read over SWD"
498
+ )
499
+ else:
500
+ if not watches and not hold: # a watch/hold session already ran the whole --wait window
501
+ time.sleep(max(ctx.ram_wait, 0.0))
502
+ read = fc._execute(
503
+ FlashPlan(argv=argv, ok_message="", jlink_script=read_script(pre, *console)),
504
+ True, ctx.venv_bin, ctx.workspace, guard, jlink_exe=exe,
505
+ )
506
+ if guard is not None and guard.tripped:
507
+ return fail(guard.tripped, None, probe_refusal=guard.tripped_code)
508
+ bad = None if read.success else (fc._capture_tail(read) or "J-Link failed")
509
+ bad = bad or check_session(f"{read.stdout}\n{read.stderr}", loadbin=False)
510
+ if bad:
511
+ return fail(f"the image is running but the RAM console read failed: {bad}")
512
+ try:
513
+ data = read_back(f"{read.stdout}\n{read.stderr}", console[0], console[1])
514
+ except RamRunError as err:
515
+ return fail(f"the image is running but {err}")
516
+ text = decode_console(data)
517
+ ram_console.update(
518
+ {"bytesRead": len(data), "textBytes": len(text.encode()), "text": text}
519
+ )
520
+ message += f"; read {len(data)} B of {CONSOLE_SYMBOL} ({len(text)} chars)"
521
+ lines.append(f" ok: {message}")
522
+ return (
523
+ 0,
524
+ entry("ok", 0, message, preflight_unarmed=unarmed,
525
+ reset_unconfirmed=bool(trouble or check_trouble), trcena_note=True, trcena_hold=hold,
526
+ ram_watch_incomplete=watch_note, **warn_missing),
527
+ lines,
528
+ )
529
+
530
+
531
+ def _load_apertures(ctx: Any, core_id: str) -> Any:
532
+ """The core's code/data apertures from the SoC metadata of the SDK in use
533
+ (`sram_banks_kb` of the manifest SKU's silicon variant). Refuses -- never guesses a
534
+ size -- when the SDK metadata, the SoM preset or the variant's banks cannot be
535
+ read."""
536
+ from tan.commands.build_output import read_sdk_som_and_soc
537
+ from tan.core.size import resolve_variant, sram_banks
538
+
539
+ metadata_root = os.path.join(ctx.sdk_root, "metadata") if ctx.sdk_root else None
540
+ why: list[str] = []
541
+ walked = (
542
+ read_sdk_som_and_soc(metadata_root, ctx.sku, skipped=why, explain_unsupported=True)
543
+ if metadata_root and ctx.sku and os.path.isdir(metadata_root)
544
+ else None
545
+ )
546
+ if walked is None:
547
+ raise RamRunError(
548
+ f"cannot bound the image: no readable SoM preset / SoC metadata for "
549
+ f"'{ctx.sku}' under {metadata_root}"
550
+ + (f" ({'; '.join(why)})" if why else "")
551
+ + " -- refusing to guess the TCM sizes"
552
+ )
553
+ _silicon, silicon_variant, variants, _flash_mb, _cores = walked
554
+ variant = resolve_variant(silicon_variant, ctx.sku, variants)
555
+ apertures = apertures_for(core_id, sram_banks(variant)) if variant else None
556
+ if apertures is None:
557
+ raise RamRunError(
558
+ f"cannot bound the image: the SoC variant for '{ctx.sku}' lists no "
559
+ f"ITCM/DTCM bank for core '{core_id}' -- refusing to guess the TCM sizes"
560
+ )
561
+ return apertures
562
+
563
+
564
+ def _save_transcript(ctx: Any, entry_id: str, report: dict[str, Any], script: str, outcome: Any) -> None:
565
+ safe = re.sub(r"[^A-Za-z0-9._-]", "_", entry_id) or "entry"
566
+ stamp = time.strftime("%Y%m%dT%H%M%SZ", time.gmtime())
567
+ path = os.path.join(ctx.build_root, "flash-logs", f"{METHOD}-{safe}-{stamp}.log")
568
+ try:
569
+ report["jlink"]["transcriptPath"] = fc._write_transcript(
570
+ path,
571
+ f"# tan flash --ram [{entry_id}] rc={outcome.returncode}\n## script\n"
572
+ f"{fc._DISABLE_FW_UPDATE}{script}\n## stdout\n{outcome.stdout}\n## stderr\n"
573
+ f"{outcome.stderr}\n",
574
+ )
575
+ except OSError as err:
576
+ report["jlink"]["transcriptPath"] = None
577
+ report["jlink"]["transcriptError"] = str(err)
578
+ report["jlink"]["transcriptTail"] = fc.transcript_tail(f"{outcome.stdout}\n{outcome.stderr}")