@typecad/framework-zephyr 1.0.0-alpha.16 → 1.0.0-alpha.18

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 (132) hide show
  1. package/LICENSE +202 -21
  2. package/README.md +49 -87
  3. package/dist/as-built.d.ts +2 -2
  4. package/dist/as-built.js +2 -2
  5. package/dist/boardgen.d.ts +1 -9
  6. package/dist/boardgen.js +228 -45
  7. package/dist/chips/types.d.ts +1 -1
  8. package/dist/debug-codegen.js +1 -1
  9. package/dist/display/touch-adapter.js +1 -1
  10. package/dist/display/ui-adapter.js +549 -549
  11. package/dist/doctor.js +4 -4
  12. package/dist/dt-config/custom-board.js +2 -2
  13. package/dist/dt-config/kconfig.js +18 -12
  14. package/dist/dt-config/overlay.d.ts +2 -2
  15. package/dist/dt-config/overlay.js +2 -2
  16. package/dist/framework.manifest.d.ts +3 -3
  17. package/dist/framework.manifest.js +10 -7
  18. package/dist/index.js +5 -5
  19. package/dist/licenses.d.ts +2 -2
  20. package/dist/licenses.js +8 -8
  21. package/dist/lowering/fs.js +1 -1
  22. package/dist/lowering/gpio.js +0 -32
  23. package/dist/lowering/http.js +264 -32
  24. package/dist/lowering/i2c.js +0 -8
  25. package/dist/lowering/interrupts.js +6 -0
  26. package/dist/lowering/mqtt.js +110 -8
  27. package/dist/lowering/usb.js +11 -11
  28. package/dist/lowering/wdt.js +2 -29
  29. package/dist/sdk/board-catalog-sync.d.ts +1 -3
  30. package/dist/sdk/board-catalog-sync.js +4 -10
  31. package/dist/strategy.d.ts +22 -44
  32. package/dist/strategy.js +152 -155
  33. package/dist/tmp-probe.d.ts +2 -0
  34. package/dist/tmp-probe.js +9 -0
  35. package/dist/toolchain/debug-config.d.ts +50 -90
  36. package/dist/toolchain/debug-config.js +239 -510
  37. package/dist/toolchain/env-check.d.ts +1 -3
  38. package/dist/toolchain/env-check.js +2 -7
  39. package/dist/toolchain/index.d.ts +16 -2
  40. package/dist/toolchain/index.js +383 -35
  41. package/dist/toolchain/runners.d.ts +16 -0
  42. package/dist/toolchain/runners.js +75 -0
  43. package/dist/toolchain/scaffold.d.ts +1 -1
  44. package/dist/toolchain/scaffold.js +12 -12
  45. package/dist/toolchain/west-discover.d.ts +6 -0
  46. package/dist/toolchain/west-discover.js +36 -13
  47. package/dist/toolchain/west-spawn.js +8 -2
  48. package/installer/README.md +328 -328
  49. package/installer/install.sh +2 -2
  50. package/installer/templates/project/.typecad/activate-zephyr.ps1 +1 -1
  51. package/installer/templates/project/.typecad/activate-zephyr.sh +1 -1
  52. package/installer/templates/project/.vscode/settings.json +1 -1
  53. package/installer/templates/project/README.md +2 -2
  54. package/package.json +5 -5
  55. package/src/as-built.ts +206 -206
  56. package/src/boardgen.ts +214 -48
  57. package/src/chips/types.ts +567 -567
  58. package/src/display/touch-adapter.ts +204 -204
  59. package/src/display/ui-adapter.ts +781 -781
  60. package/src/doctor.ts +4 -4
  61. package/src/dt-config/custom-board.ts +2 -2
  62. package/src/dt-config/kconfig.ts +18 -12
  63. package/src/dt-config/overlay.ts +1058 -1058
  64. package/src/framework.manifest.ts +538 -535
  65. package/src/index.ts +5 -5
  66. package/src/licenses.ts +425 -425
  67. package/src/lowering/fs.ts +135 -135
  68. package/src/lowering/gpio.ts +0 -33
  69. package/src/lowering/http.ts +264 -32
  70. package/src/lowering/i2c.ts +0 -9
  71. package/src/lowering/interrupts.ts +6 -0
  72. package/src/lowering/mqtt.ts +109 -8
  73. package/src/lowering/usb.ts +221 -221
  74. package/src/lowering/wdt.ts +2 -25
  75. package/src/sdk/board-catalog-sync.ts +4 -25
  76. package/src/strategy.ts +2265 -2309
  77. package/src/toolchain/debug-config.ts +262 -522
  78. package/src/toolchain/env-check.ts +279 -285
  79. package/src/toolchain/index.ts +1703 -1359
  80. package/src/toolchain/runners.ts +80 -0
  81. package/src/toolchain/scaffold.ts +296 -296
  82. package/src/toolchain/west-discover.ts +35 -13
  83. package/src/toolchain/west-spawn.ts +174 -168
  84. package/dist/async/timer-polyfill.d.ts +0 -10
  85. package/dist/async/timer-polyfill.js +0 -95
  86. package/dist/chips/board-overrides.d.ts +0 -7
  87. package/dist/chips/board-overrides.js +0 -11
  88. package/dist/chips/esp32.d.ts +0 -2
  89. package/dist/chips/esp32.js +0 -71
  90. package/dist/chips/esp32s3.d.ts +0 -2
  91. package/dist/chips/esp32s3.js +0 -103
  92. package/dist/chips/soc/.d.ts +0 -2
  93. package/dist/chips/soc/.js +0 -129
  94. package/dist/chips/soc/esp32.d.ts +0 -2
  95. package/dist/chips/soc/esp32.js +0 -120
  96. package/dist/chips/soc/esp32c3.d.ts +0 -2
  97. package/dist/chips/soc/esp32c3.js +0 -90
  98. package/dist/chips/soc/esp32c6.d.ts +0 -2
  99. package/dist/chips/soc/esp32c6.js +0 -109
  100. package/dist/chips/soc/esp32s3.d.ts +0 -2
  101. package/dist/chips/soc/esp32s3.js +0 -189
  102. package/dist/chips/soc/index.d.ts +0 -2
  103. package/dist/chips/soc/index.js +0 -23
  104. package/dist/chips/soc/nrf52840.d.ts +0 -2
  105. package/dist/chips/soc/nrf52840.js +0 -130
  106. package/dist/chips/soc/rp2040.d.ts +0 -2
  107. package/dist/chips/soc/rp2040.js +0 -141
  108. package/dist/chips/soc/rp2350a.d.ts +0 -2
  109. package/dist/chips/soc/rp2350a.js +0 -145
  110. package/dist/chips/soc/samd21g18a.d.ts +0 -2
  111. package/dist/chips/soc/samd21g18a.js +0 -143
  112. package/dist/chips/soc/stm32f411xe.d.ts +0 -2
  113. package/dist/chips/soc/stm32f411xe.js +0 -251
  114. package/dist/chips/xiao-ble.d.ts +0 -2
  115. package/dist/chips/xiao-ble.js +0 -100
  116. package/dist/lowering/power.d.ts +0 -9
  117. package/dist/lowering/power.js +0 -60
  118. package/dist/lowering/pulse.d.ts +0 -7
  119. package/dist/lowering/pulse.js +0 -51
  120. package/dist/lowering/tone.d.ts +0 -10
  121. package/dist/lowering/tone.js +0 -63
  122. package/dist/lowering/worker-backing.d.ts +0 -14
  123. package/dist/lowering/worker-backing.js +0 -79
  124. package/dist/lowering/worker.d.ts +0 -6
  125. package/dist/lowering/worker.js +0 -14
  126. package/dist/sdk/board-data.generated.d.ts +0 -2
  127. package/dist/sdk/board-data.generated.js +0 -4
  128. package/dist/sdk/catalog-walker.d.ts +0 -90
  129. package/dist/sdk/catalog-walker.js +0 -682
  130. package/dist/sdk/dts-reader.d.ts +0 -83
  131. package/dist/sdk/dts-reader.js +0 -596
  132. package/src/debug-codegen.ts +0 -207
package/src/boardgen.ts CHANGED
@@ -6,9 +6,10 @@
6
6
  // CONTRACT (custom PCB spec) — and emits the two artifacts a project
7
7
  // carries instead of a board package:
8
8
  //
9
- // .cuttlefish/board.ts — typed pin/bus/LED/BUTTON exports (the virtual
10
- // @typecad/board module points here)
11
- // .cuttlefish/board.json — the BoardConstants flat map (pins.all.*,
9
+ // .typecad-hal/board.ts — typed pin/bus/LED/BUTTON exports (the virtual
10
+ // '@typecad/hal' module resolves here via the
11
+ // project tsconfig — the single import surface)
12
+ // .typecad-hal/board.json — the BoardConstants flat map (pins.all.*,
12
13
  // peripherals.*, zephyr.*) the transpiler's
13
14
  // resolveChipFromBoard reconstructs its chip
14
15
  // view from
@@ -22,7 +23,7 @@
22
23
  // matrices) are absent for every board alike.
23
24
  //
24
25
  // Pure: returns the file contents; the caller (cuttlefish config-loader via
25
- // the strategy hook, or `cuttlefish board regen`) writes them.
26
+ // the strategy hook, or `typecad-hal board regen`) writes them.
26
27
  // ----------------------------------------------------------------------------
27
28
 
28
29
  import type { BoardDataEntry } from '@typecad/cuttlefish/board-catalog';
@@ -36,6 +37,7 @@ import {
36
37
  socBusLabelsFromTree,
37
38
  } from '@typecad/cuttlefish/board-catalog';
38
39
  import type { ZephyrGpioController, ZephyrProbeMethod } from './chips/types.js';
40
+ import { getBoardGateData } from '@typecad/cuttlefish/board-gate';
39
41
 
40
42
  /** A generated pin: schematic name, JS-safe identifier, HAL number. */
41
43
  interface GenPin {
@@ -56,7 +58,7 @@ export interface GeneratedBoard {
56
58
 
57
59
  /**
58
60
  * The catalog lookups resolve against the local overlay — the user's own
59
- * Zephyr tree (`cuttlefish board sync`, auto-refreshed when the tree
61
+ * Zephyr tree (`typecad-hal board sync`, auto-refreshed when the tree
60
62
  * moves). There is no compiled-in database: a machine with no tree and no
61
63
  * overlay has no boards, and the error below says exactly that.
62
64
  */
@@ -65,7 +67,7 @@ function activeBoardData(): Record<string, BoardDataEntry> {
65
67
  if (!data) {
66
68
  throw new Error(
67
69
  `No board catalog on this machine. The catalog is generated from your Zephyr tree —\n` +
68
- `run 'cuttlefish board sync' (or point CUTTLEFISH_BOARD_CATALOG at a catalog file).`,
70
+ `run 'typecad-hal board sync' (or point TYPECAD_HAL_BOARD_CATALOG at a catalog file).`,
69
71
  );
70
72
  }
71
73
  return data;
@@ -108,7 +110,7 @@ export function nrfSaadcAinPads(soc: string, identifier: string): readonly [numb
108
110
  * S3/C3/C6/H2 dropped it. Sourced from the HAL's dac_periph.c
109
111
  * (`dac_channel_io_num[]`), like the nRF SAADC table is from the PS.
110
112
  */
111
- export function esp32DacPins(soc: string): readonly [number, number][] | undefined {
113
+ function esp32DacPins(soc: string): readonly [number, number][] | undefined {
112
114
  const table: Record<string, readonly [number, number][]> = {
113
115
  esp32: [[0, 25], [1, 26]],
114
116
  esp32s2: [[0, 17], [1, 18]],
@@ -439,7 +441,7 @@ export function parsePinName(soc: string, name: string): { controller: string; p
439
441
  * generated off the installed SDK's tree) and contract records (custom PCB
440
442
  * specs) both route through here.
441
443
  */
442
- // ── User facts (cuttlefish.facts.json) ──────────────────────────────────────
444
+ // ── User facts (typecad-hal.facts.json) ──────────────────────────────────────
443
445
  // The project-local escape hatch: facts the pipeline has not (or cannot)
444
446
  // harvest, declared per board and merged into the manifest BEFORE anything
445
447
  // else runs — board module exports, capability flags, validation, lowering
@@ -464,13 +466,13 @@ export interface UserBoardFacts {
464
466
  };
465
467
  }
466
468
 
467
- /** The whole cuttlefish.facts.json shape. */
469
+ /** The whole typecad-hal.facts.json shape. */
468
470
  export interface UserFactsFile {
469
471
  boards: Record<string, UserBoardFacts>;
470
472
  }
471
473
 
472
474
  /** Parse + shape-validate the facts file; a clear error names the file. */
473
- export function parseUserFactsJson(text: string, source = 'cuttlefish.facts.json'): UserFactsFile {
475
+ export function parseUserFactsJson(text: string, source = 'typecad-hal.facts.json'): UserFactsFile {
474
476
  let parsed: unknown;
475
477
  try {
476
478
  parsed = JSON.parse(text);
@@ -511,7 +513,11 @@ export function buildModule(
511
513
  userFacts?: UserBoardFacts,
512
514
  factsSuffix = '',
513
515
  seedWarnings: readonly string[] = [],
514
- ): GeneratedBoard { const soc = socOfTarget(entry.identifier);
516
+ ): GeneratedBoard {
517
+ // The ungated surface is derived from the project's own hal copy — same
518
+ // resolution (project → cwd → monorepo sibling) as the engine's HAL parser.
519
+ const { ungated: BOARD_UNGATED_EXPORTS, ungatedTypes: BOARD_UNGATED_TYPE_EXPORTS } = getBoardGateData();
520
+ const soc = socOfTarget(entry.identifier);
515
521
  const conv = namingConvFor(soc);
516
522
 
517
523
  // ── Controller table + datasheet sweep (the same for every board) ──────
@@ -667,7 +673,7 @@ export function buildModule(
667
673
  if (!adcSources.includes(r.source)) adcSources.push(r.source);
668
674
  }
669
675
  }
670
- // User facts (cuttlefish.facts.json) — the escape hatch. User routes win
676
+ // User facts (typecad-hal.facts.json) — the escape hatch. User routes win
671
677
  // PER PIN over every harvested source above, and each takeover is warned.
672
678
  // Seeded with the pinctrl harvest's own lint results (name↔value
673
679
  // disagreements — routes dropped as untrustworthy upstream of here).
@@ -735,7 +741,7 @@ export function buildModule(
735
741
  }
736
742
  if (siliconDac.length > 0 && !dacSources.includes('dac')) dacSources.push('dac');
737
743
  }
738
- // User facts (cuttlefish.facts.json): dac channels win per pin, and the
744
+ // User facts (typecad-hal.facts.json): dac channels win per pin, and the
739
745
  // declared device joins the sources (no analogDevices cross-check — the
740
746
  // user vouches for it).
741
747
  if (userFacts?.dac) {
@@ -858,7 +864,7 @@ export function buildModule(
858
864
  ...pins.map((p) => p.ident),
859
865
  'LED',
860
866
  'BUTTON',
861
- // '@typecad/hal' imports — a connector silkscreen label can literally be
867
+ // '@typecad/hal/core' imports — a connector silkscreen label can literally be
862
868
  // 'Pin' (phyBOARD-Atlas), and `export const Pin` merges with the import.
863
869
  'Pin',
864
870
  'I2CBus',
@@ -866,6 +872,9 @@ export function buildModule(
866
872
  'UART',
867
873
  'USBConsole',
868
874
  'PWM',
875
+ // The ungated re-export block: a label colliding with any re-exported
876
+ // hal name would be a duplicate module export.
877
+ ...BOARD_UNGATED_EXPORTS,
869
878
  ]);
870
879
  buses.i2c.forEach((_, i) => reservedNames.add(`I2C${i}`));
871
880
  buses.spi.forEach((_, i) => reservedNames.add(`SPI${i}`));
@@ -879,40 +888,126 @@ export function buildModule(
879
888
  connectorLabelOwner.set(label, pin);
880
889
  }
881
890
 
891
+ // ── Per-pin editor annotations ──────────────────────────────────────────
892
+ // The same harvested facts board.json carries, stamped as JSDoc on the
893
+ // datasheet pin exports so plain tsserver surfaces them in hover and
894
+ // completion detail — board intelligence with zero editor extension. Only
895
+ // pins WITH a fact (silicon route, any-pad matrix membership, bus role, or
896
+ // an alias) get a doc line: bare pads stay bare, and the absence of an
897
+ // annotation says "plain GPIO" without stamping it on every line. Facts and
898
+ // module derive from the same route sets below, so they cannot disagree.
899
+ const pwmRouteByPin = new Map(siliconPwm.map((s) => [s.pin, s]));
900
+ const adcRouteByPin = new Map(siliconAdc.map((s) => [s.pin, s]));
901
+ const dacRouteByPin = new Map(siliconDac.map((s) => [s.pin, s]));
902
+ // Any-pad PWM matrices (ESP32 LEDC, nRF psel, RP2 slices) ride global pad
903
+ // numbers — same base arithmetic the manifest's matrix pins use.
904
+ const matrixBase = controllers.find((c) => c.nodelabel === 'gpio0')?.minPin ?? 0;
905
+ const matrixPwmPins = pwmMatrix
906
+ ? new Set(pwmMatrix.pads.map((pad) => matrixBase + pad))
907
+ : undefined;
908
+ // Aliases (connector silkscreen labels, LED/BUTTON) keyed by the aliased
909
+ // pin's HAL number — connectorLabelOwner's values ARE the sweep's GenPin
910
+ // objects, so identity holds through the LED/BUTTON facts too.
911
+ const aliasesByPin = new Map<number, string[]>();
912
+ const noteAlias = (pin: GenPin | undefined, label: string): void => {
913
+ if (!pin) return;
914
+ const list = aliasesByPin.get(pin.halPin);
915
+ if (list) list.push(label);
916
+ else aliasesByPin.set(pin.halPin, [label]);
917
+ };
918
+ for (const [label, pin] of connectorLabelOwner) noteAlias(pin, label);
919
+ if (ledPinFinal) noteAlias(ledPinFinal, 'LED');
920
+ if (buttonPin) noteAlias(buttonPin, 'BUTTON');
921
+ extraLedPins.forEach((p, i) => noteAlias(p, `LED${i + 1}`));
922
+ extraButtonPins.forEach((p, i) => noteAlias(p, `BUTTON${i + 1}`));
923
+ // Bus roles (SDA/SCL/MOSI/…) keyed by the route's pad NAME — best-effort
924
+ // uppercase match; pads whose spelling the pin sweep can't place simply
925
+ // carry no role line. (busPads is defined further down; this walks
926
+ // entry.busPins directly to stay above it.)
927
+ const rolesByPinName = new Map<string, string[]>();
928
+ for (const b of entry.busPins ?? []) {
929
+ const index = buses[b.bus]?.indexOf(b.nodelabel) ?? -1;
930
+ if (index < 0) continue;
931
+ const instance = `${b.bus.toUpperCase()}${index}`;
932
+ for (const route of b.routes) {
933
+ if (!route.pad || !route.role) continue;
934
+ const name = route.pad.toUpperCase();
935
+ const role = `${instance} ${route.role.toUpperCase()}`;
936
+ const list = rolesByPinName.get(name);
937
+ if (list) {
938
+ if (!list.includes(role)) list.push(role);
939
+ } else {
940
+ rolesByPinName.set(name, [role]);
941
+ }
942
+ }
943
+ }
944
+ const pinDoc = (p: GenPin): string | undefined => {
945
+ const parts: string[] = [];
946
+ const pwm = pwmRouteByPin.get(p.halPin);
947
+ if (pwm) parts.push(`PWM ${pwm.controller} ch${pwm.channel}`);
948
+ else if (pwmMatrix && matrixPwmPins?.has(p.halPin)) {
949
+ parts.push(`PWM ${pwmMatrix.controller} ch0-${pwmMatrix.channelCount - 1} (any pad)`);
950
+ }
951
+ const adc = adcRouteByPin.get(p.halPin);
952
+ // Primary-unit routes (pinctrl and ESP SARADC alike) omit the controller
953
+ // field — fall back to the pinctrl sources, then the ESP primary unit.
954
+ if (adc) parts.push(`ADC ${adc.controller ?? adcSources[0] ?? espAdcUnit ?? 'adc'} ch${adc.channel}`);
955
+ const dac = dacRouteByPin.get(p.halPin);
956
+ if (dac) parts.push(`DAC ch${dac.channel}`);
957
+ parts.push(...(rolesByPinName.get(p.name.toUpperCase()) ?? []));
958
+ const aliases = aliasesByPin.get(p.halPin);
959
+ if (aliases) parts.push(`aliases: ${aliases.join(', ')}`);
960
+ return parts.length > 0 ? parts.join(' · ') : undefined;
961
+ };
962
+
882
963
  const ts: string[] = [];
883
- ts.push(`// GENERATED by cuttlefish boardgen from the Zephyr board catalog —`);
964
+ ts.push(`// GENERATED by typecad-hal boardgen from the Zephyr board catalog —`);
884
965
  ts.push(`// ${entry.identifier} (${entry.name}, ${entry.vendor}).`);
885
- ts.push(`// Regenerate with: npx cuttlefish board regen`);
966
+ ts.push(`// Regenerate with: npx typecad-hal board regen`);
886
967
  ts.push('');
887
- ts.push(`import { Pin, I2CBus, SPIBus, UART${hasUsb ? ', USBConsole' : ''}${pwmLedSpecs.length > 0 ? ', PWM' : ''} } from '@typecad/hal';`);
968
+ // The generated module IS the user's '@typecad/hal' (the project tsconfig
969
+ // maps that specifier here), so it reaches the implementation package via
970
+ // the './core' subpath — the one specifier the paths mapping does not
971
+ // capture, avoiding a circular self-reference.
972
+ ts.push(`import { Pin, I2CBus, SPIBus, UART${hasUsb ? ', USBConsole' : ''}${pwmLedSpecs.length > 0 ? ', PWM' : ''} } from '@typecad/hal/core';`);
888
973
  ts.push('');
889
974
  // ── Hardware-class gateway ─────────────────────────────────────────────
890
- // The board module is the NARROWED surface: every hardware class re-export
891
- // here exists only when this board's facts support it, so importing
892
- // unavailable hardware fails at module resolution (editor + transpile)
893
- // instead of at a deep diagnostic. '@typecad/hal' stays the implementation
894
- // package; user code imports hardware from '@typecad/board'.
975
+ // This module is the NARROWED surface behind the user's '@typecad/hal'
976
+ // import: every hardware class gated on board facts is re-exported below
977
+ // only when this board's facts support it, so importing unavailable
978
+ // hardware fails at module resolution (editor + transpile) instead of at
979
+ // a deep diagnostic. Everything not gated (the lists hal ships in
980
+ // gate.ts) is re-exported verbatim so the full authoring surface stays
981
+ // importable from the same specifier ('@typecad/hal' is the one specifier —
982
+ // the old '@typecad/board' alias was removed with the rename).
983
+ ts.push('// Always-available HAL surface (not gated on board facts).');
984
+ ts.push(`export { ${BOARD_UNGATED_EXPORTS.join(', ')} } from '@typecad/hal/core';`);
985
+ ts.push(`export type { ${BOARD_UNGATED_TYPE_EXPORTS.join(', ')} } from '@typecad/hal/core';`);
986
+ ts.push('');
895
987
  ts.push('// Hardware this board actually has — unavailable hardware is not importable.');
896
- ts.push(`export { GPIO, Thread, Time, Sensor } from '@typecad/hal';`);
897
- if (wdtNodeLabel) ts.push(`export { Watchdog } from '@typecad/hal';`);
898
- if (siliconPwm.length > 0 || pwmLedSpecs.length > 0 || pwmMatrix) ts.push(`export { PWM } from '@typecad/hal';`);
899
- if (siliconAdc.length > 0) ts.push(`export { ADC } from '@typecad/hal';`);
900
- if (siliconDac.length > 0) ts.push(`export { DAC } from '@typecad/hal';`);
901
- if (buses.i2c.length > 0) ts.push(`export { I2CTarget } from '@typecad/hal';`);
902
- if (buses.spi.length > 0) ts.push(`export { SPITarget } from '@typecad/hal';`);
903
- if (buses.uart.length > 0) ts.push(`export { UART } from '@typecad/hal';`);
904
- if (hwtimerControllers.length > 0) ts.push(`export { Counter } from '@typecad/hal';`);
905
- if (hasUsb) ts.push(`export { USBConsole } from '@typecad/hal';`);
988
+ if (wdtNodeLabel) ts.push(`export { Watchdog } from '@typecad/hal/core';`);
989
+ if (siliconPwm.length > 0 || pwmLedSpecs.length > 0 || pwmMatrix) ts.push(`export { PWM } from '@typecad/hal/core';`);
990
+ if (siliconAdc.length > 0) ts.push(`export { ADC } from '@typecad/hal/core';`);
991
+ if (siliconDac.length > 0) ts.push(`export { DAC } from '@typecad/hal/core';`);
992
+ if (buses.i2c.length > 0) ts.push(`export { I2CTarget } from '@typecad/hal/core';`);
993
+ if (buses.spi.length > 0) ts.push(`export { SPITarget } from '@typecad/hal/core';`);
994
+ if (buses.uart.length > 0) ts.push(`export { UART } from '@typecad/hal/core';`);
995
+ if (hwtimerControllers.length > 0) ts.push(`export { Counter } from '@typecad/hal/core';`);
996
+ if (hasUsb) ts.push(`export { USBConsole } from '@typecad/hal/core';`);
906
997
  // Store/File: a persisted backend needs a storage region — either the
907
998
  // board's own storage_partition (harvested reg) or a synthesizable one
908
999
  // (flash size known, no existing partition to collide with).
909
1000
  if (entry.storageReg || (entry.flashKb && !entry.hasStoragePartition)) {
910
- ts.push(`export { Store, File } from '@typecad/hal';`);
1001
+ ts.push(`export { Store, File } from '@typecad/hal/core';`);
911
1002
  }
912
1003
  ts.push('');
913
1004
  if (pins.length > 0) {
914
1005
  ts.push(`// Datasheet-named pins (derived from the board's devicetree controllers)`);
1006
+ ts.push(`// Pins with harvested facts carry them as JSDoc (hover shows routes/roles/aliases);`);
1007
+ ts.push(`// a pin without a doc line is plain GPIO — no PWM/ADC/DAC route on this board.`);
915
1008
  for (const p of pins) {
1009
+ const doc = pinDoc(p);
1010
+ if (doc) ts.push(`/** ${doc} */`);
916
1011
  ts.push(`export const ${p.ident} = Pin.fromPort('${p.name}');`);
917
1012
  }
918
1013
  ts.push('');
@@ -932,27 +1027,53 @@ export function buildModule(
932
1027
  if (!pins.some((x) => x.ident === p.ident) && !extraPinDecls.some((x) => x.ident === p.ident)) extraPinDecls.push(p);
933
1028
  }
934
1029
  for (const p of extraPinDecls) {
935
- ts.push(`/** ${p.name} (derived from the devicetree controller label). */`);
936
1030
  ts.push(`export const ${p.ident} = Pin.fromPort('${p.name}');`);
937
1031
  }
938
1032
  if (ledPinFinal) {
939
- ts.push(`/** On-board LED (${entry.led ? `devicetree ${entry.led.dtSpec}` : 'addressable strip — no gpio-leds node'}). */`);
1033
+ ts.push(`/** On-board LED${entry.led ? '' : ' — addressable strip, this is its data pin'}. */`);
940
1034
  ts.push(`export const LED = ${ledPinFinal.ident};`);
941
1035
  }
942
1036
  if (buttonPin) {
943
- ts.push(`/** User button (devicetree ${entry.button?.dtSpec}). */`);
1037
+ ts.push(`/** User button. */`);
944
1038
  ts.push(`export const BUTTON = ${buttonPin.ident};`);
945
1039
  }
946
1040
  if (ledPinFinal || buttonPin || extraPinDecls.length > 0) ts.push('');
947
1041
 
948
- // Bus instance exports from the board's own DTS-wired controllers.
1042
+ // Bus instance exports from the board's own DTS-wired controllers. When the
1043
+ // harvest parsed a controller's pinctrl overrides, the JSDoc carries its pad
1044
+ // routes ("SDA=PB7, SCL=PB8") — the same facts the pins.<bus> conflict-map
1045
+ // constants hold, surfaced for hover.
1046
+ const busPadPairs = (kind: 'i2c' | 'spi' | 'uart', nodelabel: string): string[] =>
1047
+ (entry.busPins ?? [])
1048
+ .filter((b) => b.bus === kind && b.nodelabel === nodelabel)
1049
+ .flatMap((b) => b.routes)
1050
+ .filter((r) => r.pad && r.role)
1051
+ .map((r) => `${r.role!.toUpperCase()}=${r.pad}`);
1052
+ const busDoc = (kind: 'i2c' | 'spi' | 'uart', index: number, nodelabel: string): string => {
1053
+ const label = kind === 'i2c' ? `I2C bus ${index}` : kind === 'spi' ? `SPI bus ${index}` : `UART ${index}`;
1054
+ const pads = busPadPairs(kind, nodelabel);
1055
+ return `Board-wired ${label}${pads.length > 0 ? ` (${pads.join(', ')})` : ''}.`;
1056
+ };
949
1057
  const busLines: string[] = [];
950
- buses.i2c.forEach((_, i) => busLines.push(`export const I2C${i} = new I2CBus('I2C${i}');`));
951
- buses.spi.forEach((_, i) => busLines.push(`export const SPI${i} = new SPIBus('SPI${i}');`));
952
- buses.uart.forEach((_, i) => busLines.push(`export const UART${i} = new UART('UART${i}');`));
1058
+ buses.i2c.forEach((nodelabel, i) => {
1059
+ busLines.push(`/** ${busDoc('i2c', i, nodelabel)} */`);
1060
+ busLines.push(`export const I2C${i} = new I2CBus('I2C${i}');`);
1061
+ });
1062
+ buses.spi.forEach((nodelabel, i) => {
1063
+ busLines.push(`/** ${busDoc('spi', i, nodelabel)} */`);
1064
+ busLines.push(`export const SPI${i} = new SPIBus('SPI${i}');`);
1065
+ });
1066
+ buses.uart.forEach((nodelabel, i) => {
1067
+ busLines.push(`/** ${busDoc('uart', i, nodelabel)} */`);
1068
+ busLines.push(`export const UART${i} = new UART('UART${i}');`);
1069
+ });
953
1070
  // USB CDC instance (the thin HAL's USBConsole): the board's DTS turns the
954
- // device controller on (zephyr_udc0 status okay).
955
- if (hasUsb) busLines.push(`export const USB0 = new USBConsole('USB0');`);
1071
+ // device controller on (zephyr_udc0 status okay). CDC has no pinctrl pad
1072
+ // routes — the console rides the controller's silicon-fixed DP/DM pads.
1073
+ if (hasUsb) {
1074
+ busLines.push(`/** USB serial console — the board's USB connector (no pin to configure). */`);
1075
+ busLines.push(`export const USB0 = new USBConsole('USB0');`);
1076
+ }
956
1077
 
957
1078
  // PWM-driven LEDs (pwm-leds): addressed by the board's own devicetree
958
1079
  // alias (DT_ALIAS(pwm_led0) in the lowering) — no overlay needed. The
@@ -962,7 +1083,7 @@ export function buildModule(
962
1083
  pwmLedSpecs.forEach((l, i) => {
963
1084
  const period = l.periodNs ?? 1_000_000;
964
1085
  const name = i === 0 ? 'PWMLED' : `PWMLED${i}`;
965
- busLines.push(`/** Board PWM LED (devicetree ${l.alias}${l.flags?.length ? ', ' + l.flags.join(' ') : ''}). */`);
1086
+ busLines.push(`/** Board PWM-driven LED (already constructed — call setDuty() to dim it). */`);
966
1087
  busLines.push(`export const ${name} = new PWM(${8192 + i}, { periodNs: ${period} });`);
967
1088
  });
968
1089
  if (busLines.length > 0) {
@@ -1161,16 +1282,61 @@ export function buildModule(
1161
1282
 
1162
1283
  // Bus controllers the board's own DTS wires up — resolveChipFromBoard
1163
1284
  // reconstructs the bus lists from these, and the overlays enable the
1164
- // nodes the program uses.
1285
+ // nodes the program uses. Each controller also carries its board-DTS
1286
+ // pinctrl routes when the harvest parsed them (`pinctrl` = the route
1287
+ // names, `pads` = `role=PAD` pairs) — the diagnostics report's
1288
+ // peripheral-usage table and the pins.<bus> conflict-map read them.
1289
+ const busPads = (kind: 'i2c' | 'spi' | 'uart', nodelabel: string) =>
1290
+ (entry.busPins ?? []).filter((b) => b.bus === kind && b.nodelabel === nodelabel).flatMap((b) => b.routes);
1291
+ const busPinMap = (kind: 'i2c' | 'spi' | 'uart'): string => {
1292
+ const parts: string[] = [];
1293
+ buses[kind].forEach((nodelabel, i) => {
1294
+ const pairs = busPads(kind, nodelabel)
1295
+ .filter((r) => r.pad && r.role)
1296
+ .map((r) => `${r.role}=${r.pad}`);
1297
+ if (pairs.length > 0) parts.push(`${i}:${pairs.join(',')}`);
1298
+ });
1299
+ return parts.join(';');
1300
+ };
1165
1301
  buses.i2c.forEach((nodelabel, i) => {
1166
1302
  constants[`zephyr.i2c.controllers.${i}.nodeLabel`] = nodelabel;
1303
+ const routes = busPads('i2c', nodelabel);
1304
+ if (routes.length > 0) {
1305
+ constants[`zephyr.i2c.controllers.${i}.pinctrl`] = routes.map((r) => r.name).join(',');
1306
+ constants[`zephyr.i2c.controllers.${i}.pads`] = routes.filter((r) => r.pad).map((r) => `${r.role ?? ''}=${r.pad}`).join(',');
1307
+ }
1167
1308
  });
1168
1309
  buses.spi.forEach((nodelabel, i) => {
1169
1310
  constants[`zephyr.spi.controllers.${i}.nodeLabel`] = nodelabel;
1311
+ const routes = busPads('spi', nodelabel);
1312
+ if (routes.length > 0) {
1313
+ constants[`zephyr.spi.controllers.${i}.pinctrl`] = routes.map((r) => r.name).join(',');
1314
+ constants[`zephyr.spi.controllers.${i}.pads`] = routes.filter((r) => r.pad).map((r) => `${r.role ?? ''}=${r.pad}`).join(',');
1315
+ }
1170
1316
  });
1171
1317
  buses.uart.forEach((nodelabel, i) => {
1172
1318
  constants[`zephyr.uart.controllers.${i}.nodeLabel`] = nodelabel;
1319
+ const routes = busPads('uart', nodelabel);
1320
+ if (routes.length > 0) {
1321
+ constants[`zephyr.uart.controllers.${i}.pinctrl`] = routes.map((r) => r.name).join(',');
1322
+ constants[`zephyr.uart.controllers.${i}.pads`] = routes.filter((r) => r.pad).map((r) => `${r.role ?? ''}=${r.pad}`).join(',');
1323
+ }
1173
1324
  });
1325
+ // The board's console controller (chosen zephyr,console) — the
1326
+ // diagnostics annotate the UART instance the bootloader logs on.
1327
+ if (entry.console) constants['zephyr.console'] = entry.console;
1328
+ // Bus pad conflict maps (analyzeResources): "<inst>:<role>=<PAD>,...;..."
1329
+ const i2cPinMap = busPinMap('i2c');
1330
+ const spiPinMap = busPinMap('spi');
1331
+ const uartPinMap = busPinMap('uart');
1332
+ if (i2cPinMap) constants['pins.i2c'] = i2cPinMap;
1333
+ if (spiPinMap) constants['pins.spi'] = spiPinMap;
1334
+ if (uartPinMap) constants['pins.uart'] = uartPinMap;
1335
+ // Board silicon identity for the diagnostics metadata (the report's
1336
+ // SRAM/flash context) — flash is a devicetree fact, sram/clock are not
1337
+ // harvested and stay absent.
1338
+ if (soc) constants['mcu'] = soc;
1339
+ if (entry.flashKb) constants['memory.flash'] = entry.flashKb * 1024;
1174
1340
 
1175
1341
  // USB device (CDC-ACM): the board's DTS turned the controller on.
1176
1342
  if (hasUsb) {
@@ -1234,7 +1400,7 @@ export function buildModule(
1234
1400
  constants,
1235
1401
  // Source fingerprint: covers the record content, the extraction
1236
1402
  // revision, and the tree provenance (when the record came from a
1237
- // catalog). `cuttlefish build` recomputes it cheaply and regenerates
1403
+ // catalog). `typecad-hal build` recomputes it cheaply and regenerates
1238
1404
  // this module when it moves — a board change in the config, the catalog
1239
1405
  // overlay, or the Zephyr tree itself recreates the module.
1240
1406
  source: {
@@ -1264,8 +1430,8 @@ export function generateBoard(
1264
1430
  if (!found) {
1265
1431
  const hint = loadBoardCatalogOverlay()
1266
1432
  ? `'${target}' is not a board target in the current catalog. ` +
1267
- `It may be new in your Zephyr tree — run 'cuttlefish board sync' and retry.`
1268
- : `No board catalog on this machine. Run 'cuttlefish board sync' first.`;
1433
+ `It may be new in your Zephyr tree — run 'typecad-hal board sync' and retry.`
1434
+ : `No board catalog on this machine. Run 'typecad-hal board sync' first.`;
1269
1435
  throw new Error(hint);
1270
1436
  }
1271
1437
  // The as-built snapshot (this project's last successful build's resolved
@@ -1286,7 +1452,7 @@ export function generateBoard(
1286
1452
  asBuiltWarnings.push(`ignoring as-built snapshot: ${(err as Error).message}`);
1287
1453
  }
1288
1454
  }
1289
- // The project's cuttlefish.facts.json, when present: the section for THIS
1455
+ // The project's typecad-hal.facts.json, when present: the section for THIS
1290
1456
  // board merges into the manifest (user routes win per pin), and the raw
1291
1457
  // text hashes into the module's source fingerprint so edits regenerate.
1292
1458
  let facts: UserBoardFacts | undefined;