@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/dist/strategy.js CHANGED
@@ -25,6 +25,22 @@ import { syncBoardCatalog, ensureFreshBoardCatalog, } from './sdk/board-catalog-
25
25
  import { setActiveChip, NO_BOARD_CHIP } from './chips/index.js';
26
26
  import { resolveChipFromBoard } from './chips/resolve.js';
27
27
  import { emitGpioDevDispatcher } from './chips/controllers.js';
28
+ /**
29
+ * Visit the program's transpile-resolved HAL ops with the same `visit` the IR
30
+ * walk uses. A HAL call inlined into another HAL call's template literal
31
+ * (`USB0.writeLine(\`n: ${gps.available()}\`)`) is lowered to C++ text while
32
+ * the IR is built — it never becomes a hal-op/hal-expr IR node, so the walk
33
+ * alone cannot see it. Such ops ride on ProgramIR.resolvedHalOps; wrapped as
34
+ * `{ operation: op }` they match the hal-op node shape every collector below
35
+ * tests for. Without this fold, shims keyed on these scans (UART RX rings,
36
+ * sensor device handles, per-controller bus state, ADC channels/overrides,
37
+ * dt specs) go undeclared while the baked text still references them — the
38
+ * `'__tc_dt_led0' was not declared` failure class.
39
+ */
40
+ function visitResolvedHalOps(program, visit) {
41
+ for (const op of program?.resolvedHalOps ?? [])
42
+ visit({ operation: op });
43
+ }
28
44
  /**
29
45
  * Deep-walk the program IR and collect the HAL pin numbers the program
30
46
  * actually touches for a peripheral family ('adc' | 'pwm') — the same walk
@@ -64,6 +80,7 @@ function collectUsedPins(program, kind, chip) {
64
80
  }
65
81
  };
66
82
  visit(program);
83
+ visitResolvedHalOps(program, visit);
67
84
  return pins;
68
85
  }
69
86
  /**
@@ -99,6 +116,7 @@ function collectAdcOverrideDevices(program) {
99
116
  }
100
117
  };
101
118
  visit(program);
119
+ visitResolvedHalOps(program, visit);
102
120
  return devices;
103
121
  }
104
122
  /**
@@ -136,6 +154,7 @@ function collectPwmOverrideSpecs(program) {
136
154
  }
137
155
  };
138
156
  visit(program);
157
+ visitResolvedHalOps(program, visit);
139
158
  return specs;
140
159
  }
141
160
  /**
@@ -185,6 +204,7 @@ function collectUsedBusIndices(program) {
185
204
  }
186
205
  };
187
206
  visit(program);
207
+ visitResolvedHalOps(program, visit);
188
208
  return indices;
189
209
  }
190
210
  /**
@@ -228,6 +248,7 @@ export function collectSensors(program) {
228
248
  }
229
249
  };
230
250
  visit(program);
251
+ visitResolvedHalOps(program, visit);
231
252
  return sensors;
232
253
  }
233
254
  /**
@@ -266,6 +287,7 @@ export function collectSpiTargets(program) {
266
287
  }
267
288
  };
268
289
  visit(program);
290
+ visitResolvedHalOps(program, visit);
269
291
  return targets;
270
292
  }
271
293
  /**
@@ -305,6 +327,7 @@ export function collectUartRings(program) {
305
327
  }
306
328
  };
307
329
  visit(program);
330
+ visitResolvedHalOps(program, visit);
308
331
  return rings;
309
332
  }
310
333
  /**
@@ -339,6 +362,7 @@ export function collectThreads(program) {
339
362
  }
340
363
  };
341
364
  visit(program);
365
+ visitResolvedHalOps(program, visit);
342
366
  return threads;
343
367
  }
344
368
  import { lowerHalOp } from './lowering/index.js';
@@ -385,13 +409,15 @@ import { httpInitLines } from './lowering/http.js';
385
409
  import { mqttInitLines } from './lowering/mqtt.js';
386
410
  import { preferencesInitLines } from './lowering/preferences.js';
387
411
  import { randomInitLines } from './lowering/random.js';
388
- import { generateZephyrInitCode, generateZephyrBreakpointCode, generateZephyrLogpointCode } from './debug-codegen.js';
389
412
  import { generateStaticAsyncRuntime } from '@typecad/cuttlefish/api/shared';
390
413
  import { resolveZephyrDisplayOp, newDisplayState } from './display/index.js';
391
414
  import { buildDisplayRuntime } from './display/gfx.js';
392
415
  import { ZEPHYR_DISPLAY_PROFILES, BUILT_IN_PROFILES } from './display/profiles.js';
393
416
  import { zephyrDisplayAdapterGenerator } from './display/ui-adapter.js';
394
417
  import { zephyrTouchAdapter } from './display/touch-adapter.js';
418
+ /** debugMode() memo — the boardgen facts lookup is not free and the method is
419
+ * called several times per build (transpile gate + artifact writers). */
420
+ const debugModeMemo = new Map();
395
421
  export class ZephyrStrategy {
396
422
  constructor() {
397
423
  this.id = 'zephyr';
@@ -437,22 +463,7 @@ export class ZephyrStrategy {
437
463
  setActiveChip(fromBoard ?? NO_BOARD_CHIP);
438
464
  return fromBoard ?? NO_BOARD_CHIP;
439
465
  }
440
- /**
441
- * Resolve the debug mode for the active target from the platform context.
442
- * Mirrors resolveChip's target extraction so shimLines/forcedIncludes can
443
- * gate the printf halt shim + console UART include to printf builds only
444
- * (gdb builds use VS Code native breakpoints + #line markers, so the
445
- * __tc_debug_wait_for_continue shim and its <zephyr/drivers/uart.h> include
446
- * are dead code there).
447
- */
448
- resolveDebugMode(ctx) {
449
- const fd = ctx?.frameworkData;
450
- const target = fd?.target ??
451
- fd?.buildTarget;
452
- return this.debugMode(target);
453
- }
454
466
  forcedIncludes(_program, ctx) {
455
- const isPrintf = this.resolveDebugMode(ctx) === 'printf';
456
467
  // <zephyr/kernel.h> for k_msleep / k_uptime_get_32 / k_busy_wait / printk.
457
468
  // <zephyr/drivers/gpio.h> for the gpio_pin_*_dt / gpio_dt_spec API.
458
469
  // <cstdint> because DIRECT_CPP_TYPE_MAP passes int32_t/uint8_t through
@@ -473,7 +484,7 @@ export class ZephyrStrategy {
473
484
  // passes int32_t/uint8_t through verbatim.
474
485
  const inc = ['<zephyr/kernel.h>', '<zephyr/drivers/gpio.h>', '<cstdint>'];
475
486
  // <cstdio> backs the printf family only: __tc_print/__tc_println (emitted
476
- // solely when @typecad/expect's preprocessor injected them — tracked via
487
+ // solely when the test-runner preprocessor injected them — tracked via
477
488
  // usedPolyfillHelpers), raw printf/snprintf in user code (usesCstdio), and
478
489
  // the fs/preferences/uart shims (their lowerings snprintf into buffers).
479
490
  // A program touching none of those needs no <cstdio>.
@@ -491,12 +502,6 @@ export class ZephyrStrategy {
491
502
  inc.push('<zephyr/drivers/spi.h>');
492
503
  if (uses('usesUart'))
493
504
  inc.push('<zephyr/drivers/uart.h>');
494
- // uart.h is also needed by the printf-mode debug halt shim
495
- // (__tc_debug_wait_for_continue polls the console UART) even when the
496
- // program itself does not use the UART HAL. In gdb mode the shim is not
497
- // emitted, so skip the include there to avoid pulling in an unused header.
498
- if (isPrintf && !inc.includes('<zephyr/drivers/uart.h>'))
499
- inc.push('<zephyr/drivers/uart.h>');
500
505
  // USB CDC serial: the class instance is a UART device (uart.h); the
501
506
  // device context macros + usbd_* API live in the next-stack header.
502
507
  if (uses('usesUsb')) {
@@ -550,10 +555,11 @@ export class ZephyrStrategy {
550
555
  // HTTP/S client: Zephyr's http_client_req runs over a pre-connected socket,
551
556
  // so the shim pulls in the BSD socket + POSIX DNS surfaces alongside the
552
557
  // http client/parser headers. TLS sec tags need tls_credentials; <cstring>
553
- // /<cstdlib> back the shim's memcpy/strlen/new-nothrow usage (the core
554
- // shim only includes <cstdio>/<cstdint>).
558
+ // /<cstdlib>/<new> back the shim's memcpy/strlen/new-nothrow usage (the
559
+ // core shim only includes <cstdio>/<cstdint> — std::nothrow is not
560
+ // guaranteed to arrive transitively).
555
561
  if (uses('usesHttp'))
556
- inc.push('<zephyr/net/socket.h>', '<zephyr/net/http/client.h>', '<zephyr/net/http/parser.h>', '<zephyr/net/tls_credentials.h>', '<zephyr/posix/sys/socket.h>', '<cstring>', '<cstdlib>');
562
+ inc.push('<zephyr/net/socket.h>', '<zephyr/net/http/client.h>', '<zephyr/net/http/parser.h>', '<zephyr/net/tls_credentials.h>', '<zephyr/posix/sys/socket.h>', '<cstring>', '<cstdlib>', '<new>');
557
563
  // MQTT client: <zephyr/net/mqtt.h> for mqtt_connect/publish/subscribe, plus
558
564
  // <zephyr/net/socket.h> for the zsock_* poll/getaddrinfo API the shim's poll
559
565
  // thread uses, and <zephyr/net/tls_credentials.h> for the mqtts:// path
@@ -608,6 +614,10 @@ export class ZephyrStrategy {
608
614
  return;
609
615
  }
610
616
  for (const v of Object.values(node)) {
617
+ if (typeof v === 'string' && v.includes(TOKEN)) {
618
+ found = true;
619
+ return;
620
+ }
611
621
  if (Array.isArray(v)) {
612
622
  for (const item of v)
613
623
  visit(item);
@@ -620,9 +630,17 @@ export class ZephyrStrategy {
620
630
  return found;
621
631
  }
622
632
  /** Pins referenced by gpio.* hal-ops in the program IR. lowerGpio routes a
623
- * pin to its devicetree spec by pin NUMBER, so the structured hal-op pins
624
- * are the authoritative signal for which __tc_dt_* specs are needed —
625
- * regardless of when the final call text is rendered. */
633
+ * pin to its devicetree spec by pin NUMBER, so the structured hal-op pins
634
+ * are the authoritative signal for which __tc_dt_* specs are needed —
635
+ * regardless of when the final call text is rendered.
636
+ *
637
+ * A gpio op interpolated into another HAL call's template literal
638
+ * (`USB0.writeLine(\`led: ${led.get()}\`)`) never becomes an IR node —
639
+ * resolveHALExprToText lowers it to C++ text while building the IR and bakes
640
+ * it into an __EMIT__ snprintf prelude, so the walk cannot see its pin. The
641
+ * spec NAME it references is still in that text; collectRawMatches finds it
642
+ * there, or the spec goes undeclared and the build fails on `__tc_dt_led0
643
+ * was not declared in this scope`. */
626
644
  collectGpioPinUsage(program) {
627
645
  const pins = new Set();
628
646
  if (!program)
@@ -646,13 +664,18 @@ export class ZephyrStrategy {
646
664
  }
647
665
  };
648
666
  visit(program);
667
+ visitResolvedHalOps(program, visit);
649
668
  return pins;
650
669
  }
651
670
  /** Run `re` (global) against every raw string in the IR — raw expression
652
- * values plus raw hal-op codes — returning capture group 1 of each match
653
- * (the full match when the regex has no group). This is how references the
654
- * text scanners must see but that never appear as IR call nodes (e.g. a
655
- * rawCpp() escape hatch naming `__tc_dt_sw0` directly) are discovered. */
671
+ * values plus raw hal-op codes — returning capture group 1 of each match
672
+ * (the full match when the regex has no group). This is how references the
673
+ * text scanners must see but that never appear as IR call nodes are
674
+ * discovered: a rawCpp() escape hatch naming `__tc_dt_sw0` directly, and —
675
+ * the reason every string LEAF is scanned, not just raw nodes — the
676
+ * pre-lowered C++ text of a HAL call inlined into another HAL call's
677
+ * template literal, which the IR builder bakes into plain `string` payloads
678
+ * (__EMIT__ snprintf preludes, op data fields) rather than raw nodes. */
656
679
  collectRawMatches(program, re) {
657
680
  const found = new Set();
658
681
  if (!program)
@@ -664,14 +687,10 @@ export class ZephyrStrategy {
664
687
  const visit = (node) => {
665
688
  if (!node || typeof node !== 'object')
666
689
  return;
667
- if (node.kind === 'raw' && typeof node.value === 'string')
668
- scan(node.value);
669
- if (node.operation && typeof node.operation === 'object'
670
- && node.operation.operation === 'raw' && typeof node.operation.code === 'string') {
671
- scan(node.operation.code);
672
- }
673
690
  for (const v of Object.values(node)) {
674
- if (Array.isArray(v)) {
691
+ if (typeof v === 'string')
692
+ scan(v);
693
+ else if (Array.isArray(v)) {
675
694
  for (const item of v)
676
695
  visit(item);
677
696
  }
@@ -700,7 +719,6 @@ export class ZephyrStrategy {
700
719
  }
701
720
  shimLines(program, ctx) {
702
721
  const chip = this.resolveChip(ctx, program);
703
- const isPrintf = this.resolveDebugMode(ctx) === 'printf';
704
722
  const a = ctx?.analysis;
705
723
  const uses = (f) => (a ? !!a[f] : true);
706
724
  const helpers = a?.usedPolyfillHelpers;
@@ -764,7 +782,7 @@ export class ZephyrStrategy {
764
782
  if (entryHasUI()) {
765
783
  guardBody.push('#ifndef PROGMEM', '#define PROGMEM', '#endif');
766
784
  }
767
- // Test-runner console helpers: @typecad/expect's Zephyr shim calls these
785
+ // Test-runner console helpers: the test-runner's Zephyr shim calls these
768
786
  // for protocol output. Overloaded for string (const char*) and numeric
769
787
  // (double) so the same call site works for markers and test values.
770
788
  // The fs lowering bakes __tc_println into its error paths too, so the
@@ -953,13 +971,15 @@ export class ZephyrStrategy {
953
971
  lines.push('// cuttlefish runtime shim. Wrapped in a single include guard so the', '// block is safe to emit into multiple headers and .cpp files within', '// one translation unit (a .cpp may #include several headers that each', '// carry the shim). The guard ensures the definitions are seen exactly', '// once per TU.', '#ifndef CUTTLEFISH_SHIM_DEFINED', '#define CUTTLEFISH_SHIM_DEFINED', ...guardBody, '#endif // CUTTLEFISH_SHIM_DEFINED');
954
972
  }
955
973
  // Devicetree specs — one per board-defined GPIO pin, but ONLY for pins the
956
- // program actually addresses (lowerGpio routes by pin number, and the
957
- // structured gpio.* hal-op pins are visible here) plus aliases named
958
- // verbatim in raw code (rawCpp escape hatches). Emitted OUTSIDE the single
959
- // CUTTLEFISH_SHIM_DEFINED guard with a per-symbol guard: per-file pin sets
960
- // differ, and in a multi-header TU the first header's TU-wide guard would
961
- // otherwise hide the second header's specs. Without a program (capability
962
- // query), emit them all.
974
+ // program actually addresses (lowerGpio routes by pin number; the
975
+ // structured gpio.* hal-op pins plus the spec names appearing in any IR
976
+ // string — including the pre-lowered text of a HAL call inlined into
977
+ // another HAL call's template literal — are visible here) and aliases
978
+ // named verbatim in raw code (rawCpp escape hatches). Emitted OUTSIDE the
979
+ // single CUTTLEFISH_SHIM_DEFINED guard with a per-symbol guard: per-file
980
+ // pin sets differ, and in a multi-header TU the first header's TU-wide
981
+ // guard would otherwise hide the second header's specs. Without a program
982
+ // (capability query), emit them all.
963
983
  const usedPins = this.collectGpioPinUsage(program);
964
984
  const dtTextRefs = this.collectRawMatches(program, /__tc_dt_([A-Za-z0-9_]+)/g);
965
985
  for (const spec of chip.gpio.dtSpecs) {
@@ -968,32 +988,6 @@ export class ZephyrStrategy {
968
988
  const guard = `__TC_DT_${spec.dtSpec.replace(/[^A-Za-z0-9_]/g, '_').toUpperCase()}_SPEC`;
969
989
  lines.push(`#ifndef ${guard}`, `#define ${guard}`, `static const struct gpio_dt_spec __tc_dt_${spec.dtSpec} = GPIO_DT_SPEC_GET(DT_ALIAS(${spec.dtSpec}), gpios);`, `#endif // ${guard}`);
970
990
  }
971
- // --- Debug-mode halt + per-breakpoint disable registry ---
972
- //
973
- // Printf mode only. In gdb mode the cuttlefish debug preprocessor is
974
- // skipped (core emits #line markers + VS Code native breakpoints instead),
975
- // so __tc_debug_wait_for_continue is never called — skip the shim and its
976
- // <zephyr/drivers/uart.h> dependency entirely (forcedIncludes mirrors this).
977
- //
978
- // The cuttlefish debug preprocessor injects __tc_debug_wait_for_continue(id)
979
- // calls at each breakpoint; without these definitions the emitted code
980
- // would not link.
981
- //
982
- // Zephyr's minimal libc has no getchar()/EOF, so the halt polls the console
983
- // UART directly via uart_poll_in on the system console device, yielding to
984
- // the scheduler with k_msleep between polls so an unattended breakpoint
985
- // does not starve the system. ENTER (or any non-'s' byte) = continue;
986
- // 's'/'S' = skip this breakpoint for the rest of the run (records the id).
987
- if (isPrintf) {
988
- lines.push('#ifndef __TC_BP_DISABLED_DEFINED', '#define __TC_BP_DISABLED_DEFINED', 'static bool __tc_bp_disabled[256] = {0};', 'static inline bool __tc_bp_is_disabled(int id) { return id >= 0 && id < 256 && __tc_bp_disabled[id]; }',
989
- // Console input: poll the UART console device. DEVICE_DT_GET(DT_CHOSEN(zephyr_console))
990
- // resolves to the board's console (UART0 USB-CDC on the XIAO nRF52840).
991
- // Not every board DTS declares a zephyr,console chosen (STM32MP1 M-side,
992
- // display/carrier boards): there is nothing to print a prompt on and
993
- // nothing to read a key from, so breakpoints auto-continue instead of
994
- // hanging an unattended run.
995
- '#if DT_HAS_CHOSEN(zephyr_console)', 'static inline char __tc_debug_wait_for_continue(int id) {', ' const struct device* __con = DEVICE_DT_GET(DT_CHOSEN(zephyr_console));', ' unsigned char __c = 0;', " while (uart_poll_in(__con, &__c) != 0) {", ' k_msleep(10);', ' }', " // Drain the rest of the typed line so the next breakpoint waits fresh.", " unsigned char __peek = 0;", " while (uart_poll_in(__con, &__peek) == 0 && __peek != '\\n') { (void)0; }", " if ((__c == 's') || (__c == 'S')) { if (id >= 0 && id < 256) __tc_bp_disabled[id] = true; }", ' return static_cast<char>(__c);', '}', '#else', 'static inline char __tc_debug_wait_for_continue(int id) { (void)id; return static_cast<char>(0); }', '#endif // DT_HAS_CHOSEN(zephyr_console)', '#endif // __TC_BP_DISABLED_DEFINED', '');
996
- }
997
991
  // --- Zephyr entrypoint ---------------------------------------------------
998
992
  // No bridge here: the synthesizer emits main() itself (it keys off
999
993
  // entrypointFunctionName()="main" and requiresLoopFunction()=false), with
@@ -1556,10 +1550,13 @@ export class ZephyrStrategy {
1556
1550
  // ── Interrupt safety ─────────────────────────────────────────────────────
1557
1551
  // Zephyr ISRs run above thread context: anything that sleeps (k_msleep),
1558
1552
  // pends, or takes a driver lock is illegal there (asserted by the kernel in
1559
- // debug builds; corrupts scheduler state otherwise). The names below are the
1560
- // IR-level callees cuttlefish's interrupt-analysis pass matches (the same
1561
- // keys ArduinoStrategy uses; timing.delay/delay_microseconds hal-ops are
1562
- // mapped back to the bare names by the analyzer itself).
1553
+ // debug builds; corrupts scheduler state otherwise). The keys below are
1554
+ // matched by cuttlefish's interrupt-analysis pass in two shapes: bare/qualified
1555
+ // callee names (call statements) and resolved hal-op payloads — the analyzer
1556
+ // maps timing.delay/delay_microseconds/sleep/busy_wait_us ops back to the
1557
+ // bare timing keys, and matches the op payload's port/bus field (UART0,
1558
+ // I2C0, SPI0, …) against these keys directly. The same keys ArduinoStrategy
1559
+ // uses.
1563
1560
  isrUnsafeOperations() {
1564
1561
  return new Map([
1565
1562
  ['delay', {
@@ -1698,14 +1695,14 @@ export class ZephyrStrategy {
1698
1695
  catch (err) {
1699
1696
  // A malformed facts/as-built file is the USER's error — surface it
1700
1697
  // verbatim instead of the generic "cannot generate" below.
1701
- if (err instanceof Error && (err.message.includes('cuttlefish.facts.json') || err.message.includes('as-built.json')))
1698
+ if (err instanceof Error && (err.message.includes('typecad-hal.facts.json') || err.message.includes('as-built.json')))
1702
1699
  throw err;
1703
1700
  return undefined;
1704
1701
  }
1705
1702
  }
1706
1703
  /**
1707
1704
  * Regenerate the board catalog overlay from the user's own Zephyr tree —
1708
- * `cuttlefish board sync`. After a `west update`, this is how new/changed/
1705
+ * `typecad-hal board sync`. After a `west update`, this is how new/changed/
1709
1706
  * removed boards reach projects without a cuttlefish release. See
1710
1707
  * src/sdk/board-catalog-sync.ts.
1711
1708
  */
@@ -1714,7 +1711,7 @@ export class ZephyrStrategy {
1714
1711
  }
1715
1712
  /**
1716
1713
  * Refresh the overlay only when it is stale (provenance no longer matches
1717
- * the tree) — the pre-step `cuttlefish board regen` runs so a regen after
1714
+ * the tree) — the pre-step `typecad-hal board regen` runs so a regen after
1718
1715
  * `west update` picks up the tree's boards automatically. Tree walk only
1719
1716
  * happens when there is actual refreshing to do.
1720
1717
  */
@@ -1899,23 +1896,23 @@ export class ZephyrStrategy {
1899
1896
  requiredIncludes: ['<cstring>'],
1900
1897
  forwardDeclarations: [],
1901
1898
  helperStructs: [],
1902
- helperFunctions: [`
1903
- // TypeCAD string method polyfills (Zephyr, minimal-libc).
1904
- #ifndef CUTTLEFISH_STR_BUF_SIZE
1905
- #define CUTTLEFISH_STR_BUF_SIZE 64
1906
- #endif
1907
- bool __tc_endsWith(const char* s, const char* suffix) { int sl = strlen(s), tl = strlen(suffix); return sl >= tl && strcmp(s + sl - tl, suffix) == 0; }
1908
- const char* __tc_toUpperCase(const char* s) { static char buf[2][CUTTLEFISH_STR_BUF_SIZE]; static uint8_t slot = 0; slot ^= 1; char* b = buf[slot]; strncpy(b, s, CUTTLEFISH_STR_BUF_SIZE - 1); b[CUTTLEFISH_STR_BUF_SIZE - 1] = '\\0'; for (char* p = b; *p; p++) { if (*p >= 'a' && *p <= 'z') { *p = static_cast<char>(*p - 32); } } return b; }
1909
- const char* __tc_toLowerCase(const char* s) { static char buf[2][CUTTLEFISH_STR_BUF_SIZE]; static uint8_t slot = 0; slot ^= 1; char* b = buf[slot]; strncpy(b, s, CUTTLEFISH_STR_BUF_SIZE - 1); b[CUTTLEFISH_STR_BUF_SIZE - 1] = '\\0'; for (char* p = b; *p; p++) { if (*p >= 'A' && *p <= 'Z') { *p = static_cast<char>(*p + 32); } } return b; }
1910
- const char* __tc_trim(const char* s) { static char buf[2][CUTTLEFISH_STR_BUF_SIZE]; static uint8_t slot = 0; slot ^= 1; char* b = buf[slot]; while (*s == ' ' || *s == '\\t' || *s == '\\n' || *s == '\\r') s++; int len = strlen(s); while (len > 0 && (s[len-1] == ' ' || s[len-1] == '\\t' || s[len-1] == '\\n' || s[len-1] == '\\r')) len--; int cplen = len < CUTTLEFISH_STR_BUF_SIZE - 1 ? len : CUTTLEFISH_STR_BUF_SIZE - 1; strncpy(b, s, cplen); b[cplen] = '\\0'; return b; }
1911
- const char* __tc_substring2(const char* s, int start, int end) { static char buf[2][CUTTLEFISH_STR_BUF_SIZE]; static uint8_t slot = 0; slot ^= 1; char* b = buf[slot]; int slen = strlen(s); if (start < 0) start = 0; if (end > slen) end = slen; if (end < start) end = start; int len = end - start; if (len >= CUTTLEFISH_STR_BUF_SIZE) len = CUTTLEFISH_STR_BUF_SIZE - 1; strncpy(b, s + start, len); b[len] = '\\0'; return b; }
1912
- const char* __tc_substring1(const char* s, int start) { return __tc_substring2(s, start, strlen(s)); }
1913
- const char* __tc_slice2(const char* s, int start, int end) { return __tc_substring2(s, start, end); }
1914
- const char* __tc_slice1(const char* s, int start) { return __tc_substring2(s, start, strlen(s)); }
1915
- const char* __tc_replace(const char* s, const char* old, const char* repl) { static char buf[2][CUTTLEFISH_STR_BUF_SIZE]; static uint8_t slot = 0; slot ^= 1; char* b = buf[slot]; const char* pos = strstr(s, old); if (!pos) { strncpy(b, s, CUTTLEFISH_STR_BUF_SIZE - 1); b[CUTTLEFISH_STR_BUF_SIZE - 1] = '\\0'; return b; } int beforeLen = static_cast<int>(pos - s); int oldLen = static_cast<int>(strlen(old)); int replLen = static_cast<int>(strlen(repl)); if (beforeLen + replLen + static_cast<int>(strlen(pos + oldLen)) >= CUTTLEFISH_STR_BUF_SIZE) { strncpy(b, s, CUTTLEFISH_STR_BUF_SIZE - 1); b[CUTTLEFISH_STR_BUF_SIZE - 1] = '\\0'; return b; } memcpy(b, s, beforeLen); memcpy(b + beforeLen, repl, replLen); strcpy(b + beforeLen + replLen, pos + oldLen); return b; }
1916
- const char* __tc_charAt(const char* s, int idx) { static char buf[2][2]; static uint8_t slot = 0; slot ^= 1; buf[slot][0] = s[idx]; buf[slot][1] = '\\0'; return buf[slot]; }
1917
- int __tc_charCodeAt(const char* s, int idx) { return static_cast<int>(static_cast<unsigned char>(s[idx])); }
1918
- int __tc_indexOf(const char* s, const char* needle) { const char* p = strstr(s, needle); return p ? static_cast<int>(p - s) : -1; }
1899
+ helperFunctions: [`
1900
+ // TypeCAD string method polyfills (Zephyr, minimal-libc).
1901
+ #ifndef CUTTLEFISH_STR_BUF_SIZE
1902
+ #define CUTTLEFISH_STR_BUF_SIZE 64
1903
+ #endif
1904
+ bool __tc_endsWith(const char* s, const char* suffix) { int sl = strlen(s), tl = strlen(suffix); return sl >= tl && strcmp(s + sl - tl, suffix) == 0; }
1905
+ const char* __tc_toUpperCase(const char* s) { static char buf[2][CUTTLEFISH_STR_BUF_SIZE]; static uint8_t slot = 0; slot ^= 1; char* b = buf[slot]; strncpy(b, s, CUTTLEFISH_STR_BUF_SIZE - 1); b[CUTTLEFISH_STR_BUF_SIZE - 1] = '\\0'; for (char* p = b; *p; p++) { if (*p >= 'a' && *p <= 'z') { *p = static_cast<char>(*p - 32); } } return b; }
1906
+ const char* __tc_toLowerCase(const char* s) { static char buf[2][CUTTLEFISH_STR_BUF_SIZE]; static uint8_t slot = 0; slot ^= 1; char* b = buf[slot]; strncpy(b, s, CUTTLEFISH_STR_BUF_SIZE - 1); b[CUTTLEFISH_STR_BUF_SIZE - 1] = '\\0'; for (char* p = b; *p; p++) { if (*p >= 'A' && *p <= 'Z') { *p = static_cast<char>(*p + 32); } } return b; }
1907
+ const char* __tc_trim(const char* s) { static char buf[2][CUTTLEFISH_STR_BUF_SIZE]; static uint8_t slot = 0; slot ^= 1; char* b = buf[slot]; while (*s == ' ' || *s == '\\t' || *s == '\\n' || *s == '\\r') s++; int len = strlen(s); while (len > 0 && (s[len-1] == ' ' || s[len-1] == '\\t' || s[len-1] == '\\n' || s[len-1] == '\\r')) len--; int cplen = len < CUTTLEFISH_STR_BUF_SIZE - 1 ? len : CUTTLEFISH_STR_BUF_SIZE - 1; strncpy(b, s, cplen); b[cplen] = '\\0'; return b; }
1908
+ const char* __tc_substring2(const char* s, int start, int end) { static char buf[2][CUTTLEFISH_STR_BUF_SIZE]; static uint8_t slot = 0; slot ^= 1; char* b = buf[slot]; int slen = strlen(s); if (start < 0) start = 0; if (end > slen) end = slen; if (end < start) end = start; int len = end - start; if (len >= CUTTLEFISH_STR_BUF_SIZE) len = CUTTLEFISH_STR_BUF_SIZE - 1; strncpy(b, s + start, len); b[len] = '\\0'; return b; }
1909
+ const char* __tc_substring1(const char* s, int start) { return __tc_substring2(s, start, strlen(s)); }
1910
+ const char* __tc_slice2(const char* s, int start, int end) { return __tc_substring2(s, start, end); }
1911
+ const char* __tc_slice1(const char* s, int start) { return __tc_substring2(s, start, strlen(s)); }
1912
+ const char* __tc_replace(const char* s, const char* old, const char* repl) { static char buf[2][CUTTLEFISH_STR_BUF_SIZE]; static uint8_t slot = 0; slot ^= 1; char* b = buf[slot]; const char* pos = strstr(s, old); if (!pos) { strncpy(b, s, CUTTLEFISH_STR_BUF_SIZE - 1); b[CUTTLEFISH_STR_BUF_SIZE - 1] = '\\0'; return b; } int beforeLen = static_cast<int>(pos - s); int oldLen = static_cast<int>(strlen(old)); int replLen = static_cast<int>(strlen(repl)); if (beforeLen + replLen + static_cast<int>(strlen(pos + oldLen)) >= CUTTLEFISH_STR_BUF_SIZE) { strncpy(b, s, CUTTLEFISH_STR_BUF_SIZE - 1); b[CUTTLEFISH_STR_BUF_SIZE - 1] = '\\0'; return b; } memcpy(b, s, beforeLen); memcpy(b + beforeLen, repl, replLen); strcpy(b + beforeLen + replLen, pos + oldLen); return b; }
1913
+ const char* __tc_charAt(const char* s, int idx) { static char buf[2][2]; static uint8_t slot = 0; slot ^= 1; buf[slot][0] = s[idx]; buf[slot][1] = '\\0'; return buf[slot]; }
1914
+ int __tc_charCodeAt(const char* s, int idx) { return static_cast<int>(static_cast<unsigned char>(s[idx])); }
1915
+ int __tc_indexOf(const char* s, const char* needle) { const char* p = strstr(s, needle); return p ? static_cast<int>(p - s) : -1; }
1919
1916
  `],
1920
1917
  shimMacros: [],
1921
1918
  dependencies: [],
@@ -1931,27 +1928,27 @@ int __tc_indexOf(const char* s, const char* needle) { const char* p = strstr(s,
1931
1928
  requiredIncludes: [],
1932
1929
  forwardDeclarations: [],
1933
1930
  helperStructs: [],
1934
- helperFunctions: [`
1935
- #ifndef __TC_STATIC_ARRAY_DEFINED
1936
- #define __TC_STATIC_ARRAY_DEFINED
1937
- template<typename T, int N>
1938
- struct __tc_StaticArray {
1939
- T data[N];
1940
- int _size;
1941
- __tc_StaticArray() : _size(0) {}
1942
- int length() const { return _size; }
1943
- int size() const { return _size; }
1944
- void push(T val) { if (_size < N) data[_size++] = val; }
1945
- T pop() { return (_size > 0) ? data[--_size] : T(); }
1946
- int indexOf(T val) const { for (int i = 0; i < _size; i++) if (data[i] == val) return i; return -1; }
1947
- T& operator[](int i) { return data[i]; }
1948
- const T& operator[](int i) const { return data[i]; }
1949
- T* begin() { return &data[0]; }
1950
- T* end() { return &data[_size]; }
1951
- const T* begin() const { return &data[0]; }
1952
- const T* end() const { return &data[_size]; }
1953
- };
1954
- #endif
1931
+ helperFunctions: [`
1932
+ #ifndef __TC_STATIC_ARRAY_DEFINED
1933
+ #define __TC_STATIC_ARRAY_DEFINED
1934
+ template<typename T, int N>
1935
+ struct __tc_StaticArray {
1936
+ T data[N];
1937
+ int _size;
1938
+ __tc_StaticArray() : _size(0) {}
1939
+ int length() const { return _size; }
1940
+ int size() const { return _size; }
1941
+ void push(T val) { if (_size < N) data[_size++] = val; }
1942
+ T pop() { return (_size > 0) ? data[--_size] : T(); }
1943
+ int indexOf(T val) const { for (int i = 0; i < _size; i++) if (data[i] == val) return i; return -1; }
1944
+ T& operator[](int i) { return data[i]; }
1945
+ const T& operator[](int i) const { return data[i]; }
1946
+ T* begin() { return &data[0]; }
1947
+ T* end() { return &data[_size]; }
1948
+ const T* begin() const { return &data[0]; }
1949
+ const T* end() const { return &data[_size]; }
1950
+ };
1951
+ #endif
1955
1952
  `],
1956
1953
  shimMacros: [],
1957
1954
  dependencies: [],
@@ -2085,43 +2082,43 @@ struct __tc_StaticArray {
2085
2082
  };
2086
2083
  }
2087
2084
  // ── Debug ─────────────────────────────────────────────────────────────────
2088
- // Zephyr's minimal C++ config has no <iostream>, so the GenericStrategy
2089
- // std::cout fallback the debug preprocessor uses by default would NOT
2090
- // compile. Override the debug surface to route through printk (always
2091
- // available, no CONFIG_CONSOLE dependency) and the __tc_debug_wait_for_continue
2092
- // halt emitted in shimLines. See src/debug-codegen.ts.
2093
- //
2094
- // Target-selective: targets with a debug probe get native GDB source-level
2095
- // debugging (core emits #line markers + skips printf instrumentation); the
2096
- // rest fall back to the printk instrumentation path. The ESP32-S3 has a
2097
- // built-in USB-JTAG (single-cable GDB via OpenOCD) so it selects 'gdb'.
2098
- // The XIAO nRF52840 needs its J-Link wired up; its GDB path is a follow-on,
2099
- // so it stays on printf for now.
2085
+ // Source-level debugging is the GDB flow only (the west-owned gdb server
2086
+ // serves F5 via cortex-debug's external-server mode — see
2087
+ // toolchain/debug-config.ts). The printf/Serial breakpoint instrumentation
2088
+ // pipeline was removed from the product.
2100
2089
  debugMode(target) {
2101
- // `target` is the Zephyr board id (optionally with a /qualifier suffix,
2102
- // e.g. 'esp32s3_devkitc/esp32s3/procpu'). Match on the bare board id.
2103
- const boardId = (target ?? '').split('/')[0];
2104
- if (boardId === 'esp32s3_devkitc' || boardId.startsWith('esp32s3')) {
2105
- return 'gdb';
2090
+ // `target` is the Zephyr board id (optionally with a /qualifier suffix).
2091
+ // gdb capability is a FACT of the board: its probe-method table carries a
2092
+ // debug-capable entry (bootloaders mark debug:false; SWD/JTAG probes and
2093
+ // built-in USB-JTAG carry debug:true) — the same table west's runner
2094
+ // selection uses. Boards without a table (or unresolvable targets, e.g.
2095
+ // generated custom boards) report 'none', and --debug fails with an
2096
+ // explicit diagnostic (transpile.ts) rather than building silently.
2097
+ const t = (target ?? '').trim();
2098
+ if (!t)
2099
+ return 'none';
2100
+ const memo = debugModeMemo.get(t);
2101
+ if (memo)
2102
+ return memo;
2103
+ let mode = 'none';
2104
+ try {
2105
+ const g = generateBoard(t);
2106
+ const constants = JSON.parse(g.boardJson).constants;
2107
+ for (let i = 0;; i++) {
2108
+ const id = constants[`zephyr.probeMethods.${i}.id`];
2109
+ if (typeof id !== 'string')
2110
+ break;
2111
+ if (constants[`zephyr.probeMethods.${i}.debug`] !== false) {
2112
+ mode = 'gdb';
2113
+ break;
2114
+ }
2115
+ }
2106
2116
  }
2107
- // The STM32 Black Pill ships a verified ST-Link probe method in its board
2108
- // package (openocd runner over SWD, with the reset_config quirk for the
2109
- // unwired SRST line), so F5 attaches natively out of the box.
2110
- if (boardId.startsWith('blackpill_')) {
2111
- return 'gdb';
2117
+ catch {
2118
+ // Board not resolvable from facts — 'none' (never crash codegen over
2119
+ // the debug-capability gate).
2112
2120
  }
2113
- // The plain ESP32 (esp32_devkitc) intentionally stays on 'printf': unlike
2114
- // the S3 it has NO built-in USB-JTAG, so gdb needs an external ESP-PROG
2115
- // probe + a different OpenOCD cfg/toolchain dir (deferred). Falls through.
2116
- return 'printf';
2117
- }
2118
- generateDebugInitCode() {
2119
- return generateZephyrInitCode();
2120
- }
2121
- generateDebugBreakpointCode(params) {
2122
- return generateZephyrBreakpointCode(params.fileName, params.lineNum, params.originalLine, params.variables, params.normalizedCondition, params.breakpointId);
2123
- }
2124
- generateDebugLogpointCode(params) {
2125
- return generateZephyrLogpointCode(params.fileName, params.lineNum, params.parts, params.variables);
2121
+ debugModeMemo.set(t, mode);
2122
+ return mode;
2126
2123
  }
2127
2124
  }
@@ -0,0 +1,2 @@
1
+ import type { ToolchainOptions } from '@typecad/cuttlefish/api/shared';
2
+ export declare function probe(opts: ToolchainOptions): void;
@@ -0,0 +1,9 @@
1
+ import { spawn } from 'node:child_process';
2
+ export function probe(opts) {
3
+ void opts;
4
+ const child = spawn('west', ['build'], {
5
+ stdio: ['ignore', 'pipe', 'pipe'],
6
+ ...(process.platform !== 'win32' ? { detached: true } : {}),
7
+ });
8
+ child.on('exit', (code) => { void code; });
9
+ }