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

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 (114) hide show
  1. package/dist/audit.d.ts +111 -0
  2. package/dist/audit.js +416 -0
  3. package/dist/boardgen.js +63 -5
  4. package/dist/chips/resolve.js +20 -0
  5. package/dist/chips/types.d.ts +21 -0
  6. package/dist/display/bindings.d.ts +55 -0
  7. package/dist/display/bindings.js +316 -0
  8. package/dist/display/gfx.d.ts +2 -3
  9. package/dist/display/gfx.js +166 -154
  10. package/dist/display/index.js +20 -1
  11. package/dist/display/mipi-dbi-host.d.ts +9 -0
  12. package/dist/display/mipi-dbi-host.js +174 -0
  13. package/dist/display/profiles.d.ts +109 -4
  14. package/dist/display/profiles.js +270 -7
  15. package/dist/display/touch-adapter.js +118 -48
  16. package/dist/display/ui-adapter-eink.d.ts +2 -0
  17. package/dist/display/ui-adapter-eink.js +4 -0
  18. package/dist/display/ui-adapter-gray.d.ts +8 -0
  19. package/dist/display/ui-adapter-gray.js +170 -0
  20. package/dist/display/ui-adapter-mono.d.ts +13 -0
  21. package/dist/display/ui-adapter-mono.js +230 -0
  22. package/dist/display/ui-adapter-native.d.ts +10 -0
  23. package/dist/display/ui-adapter-native.js +295 -0
  24. package/dist/display/ui-adapter-shared.d.ts +11 -0
  25. package/dist/display/ui-adapter-shared.js +122 -0
  26. package/dist/display/ui-adapter.js +53 -101
  27. package/dist/dt-config/kconfig.d.ts +62 -1
  28. package/dist/dt-config/kconfig.js +123 -26
  29. package/dist/dt-config/overlay.d.ts +13 -0
  30. package/dist/dt-config/overlay.js +431 -16
  31. package/dist/framework.manifest.d.ts +9 -3
  32. package/dist/framework.manifest.js +123 -10
  33. package/dist/index.d.ts +2 -0
  34. package/dist/index.js +9 -1
  35. package/dist/licenses.js +5 -84
  36. package/dist/lowering/can.d.ts +25 -0
  37. package/dist/lowering/can.js +97 -0
  38. package/dist/lowering/clock.d.ts +17 -0
  39. package/dist/lowering/clock.js +58 -0
  40. package/dist/lowering/hid.d.ts +27 -0
  41. package/dist/lowering/hid.js +244 -0
  42. package/dist/lowering/i2c.d.ts +8 -0
  43. package/dist/lowering/i2c.js +140 -0
  44. package/dist/lowering/i2s.d.ts +27 -0
  45. package/dist/lowering/i2s.js +98 -0
  46. package/dist/lowering/index.d.ts +9 -1
  47. package/dist/lowering/index.js +25 -1
  48. package/dist/lowering/matrix.d.ts +15 -0
  49. package/dist/lowering/matrix.js +63 -0
  50. package/dist/lowering/power.d.ts +10 -0
  51. package/dist/lowering/power.js +35 -0
  52. package/dist/lowering/pwm.js +25 -0
  53. package/dist/lowering/sensor.d.ts +2 -2
  54. package/dist/lowering/sensor.js +8 -4
  55. package/dist/lowering/strip.d.ts +16 -0
  56. package/dist/lowering/strip.js +70 -0
  57. package/dist/lowering/thread.js +5 -1
  58. package/dist/lowering/trace.d.ts +44 -0
  59. package/dist/lowering/trace.js +239 -0
  60. package/dist/lowering/uart.js +6 -1
  61. package/dist/lowering/usb.d.ts +3 -1
  62. package/dist/lowering/usb.js +6 -3
  63. package/dist/sbom.d.ts +181 -0
  64. package/dist/sbom.js +901 -0
  65. package/dist/strategy.d.ts +60 -2
  66. package/dist/strategy.js +485 -25
  67. package/dist/toolchain/index.d.ts +12 -1
  68. package/dist/toolchain/index.js +115 -27
  69. package/dist/toolchain/scaffold.d.ts +4 -1
  70. package/dist/toolchain/scaffold.js +67 -3
  71. package/dist/west-inventory.d.ts +25 -0
  72. package/dist/west-inventory.js +97 -0
  73. package/package.json +6 -6
  74. package/src/audit.ts +529 -0
  75. package/src/boardgen.ts +54 -5
  76. package/src/chips/resolve.ts +21 -0
  77. package/src/chips/types.ts +576 -567
  78. package/src/display/bindings.ts +347 -0
  79. package/src/display/gfx.ts +318 -306
  80. package/src/display/index.ts +87 -70
  81. package/src/display/mipi-dbi-host.ts +183 -0
  82. package/src/display/profiles.ts +458 -139
  83. package/src/display/touch-adapter.ts +274 -204
  84. package/src/display/ui-adapter-eink.ts +13 -0
  85. package/src/display/ui-adapter-gray.ts +178 -0
  86. package/src/display/ui-adapter-mono.ts +238 -0
  87. package/src/display/ui-adapter-native.ts +304 -0
  88. package/src/display/ui-adapter-shared.ts +125 -0
  89. package/src/display/ui-adapter.ts +51 -100
  90. package/src/dt-config/kconfig.ts +647 -511
  91. package/src/dt-config/overlay.ts +433 -16
  92. package/src/framework.manifest.ts +131 -10
  93. package/src/index.ts +11 -1
  94. package/src/licenses.ts +5 -84
  95. package/src/lowering/can.ts +140 -0
  96. package/src/lowering/clock.ts +91 -0
  97. package/src/lowering/hid.ts +261 -0
  98. package/src/lowering/i2c.ts +146 -0
  99. package/src/lowering/i2s.ts +143 -0
  100. package/src/lowering/index.ts +18 -1
  101. package/src/lowering/matrix.ts +70 -0
  102. package/src/lowering/power.ts +41 -0
  103. package/src/lowering/pwm.ts +192 -167
  104. package/src/lowering/sensor.ts +159 -155
  105. package/src/lowering/strip.ts +81 -0
  106. package/src/lowering/thread.ts +5 -1
  107. package/src/lowering/trace.ts +270 -0
  108. package/src/lowering/uart.ts +6 -1
  109. package/src/lowering/usb.ts +224 -221
  110. package/src/sbom.ts +1117 -0
  111. package/src/strategy.ts +440 -25
  112. package/src/toolchain/index.ts +117 -28
  113. package/src/toolchain/scaffold.ts +62 -3
  114. package/src/west-inventory.ts +102 -0
@@ -0,0 +1,63 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Matrix lowering — gpio-kbd-matrix node + input-event trampoline
3
+ //
4
+ // The gpio-kbd-matrix driver (Zephyr input subsystem) scans the grid and
5
+ // reports each key as ABS_X (column) + ABS_Y (row) + BTN_TOUCH (press).
6
+ // The shim decodes the triple and calls the user's lowered callback as
7
+ // (row, col, pressed). The overlay generator synthesizes the DT node with
8
+ // row-gpios/col-gpios phandle arrays from the construction pad lists —
9
+ // the lowering's comment marker carries rows/cols to the toolchain scan
10
+ // (the pwm user-facts marker precedent).
11
+ // ----------------------------------------------------------------------------
12
+ /**
13
+ * Emit the matrix trampoline state. Called from shimLines when the program
14
+ * uses matrix.* — the listener targets the synthesized tc_matrix node, the
15
+ * handler pointer is assigned by the on_key lowering.
16
+ */
17
+ export function matrixInitLines() {
18
+ return [
19
+ '// CUTTLEFISH_MATRIX_BEGIN',
20
+ 'static void (*__tc_matrix_on_key)(double, double, bool) = NULL;',
21
+ 'static int32_t __tc_matrix_row = -1;',
22
+ 'static int32_t __tc_matrix_col = -1;',
23
+ 'static void __tc_matrix_cb(struct input_event* evt, void* user_data) {',
24
+ ' (void)user_data;',
25
+ ' switch (evt->code) {',
26
+ ' case INPUT_ABS_X:',
27
+ ' __tc_matrix_col = evt->value;',
28
+ ' break;',
29
+ ' case INPUT_ABS_Y:',
30
+ ' __tc_matrix_row = evt->value;',
31
+ ' break;',
32
+ ' case INPUT_BTN_TOUCH:',
33
+ ' if (__tc_matrix_on_key != NULL && __tc_matrix_row >= 0 && __tc_matrix_col >= 0) {',
34
+ ' __tc_matrix_on_key(static_cast<double>(__tc_matrix_row), static_cast<double>(__tc_matrix_col), evt->value != 0);',
35
+ ' }',
36
+ ' break;',
37
+ ' default:',
38
+ ' break;',
39
+ ' }',
40
+ '}',
41
+ 'INPUT_CALLBACK_DEFINE(DEVICE_DT_GET(DT_NODELABEL(tc_matrix)), __tc_matrix_cb, NULL);',
42
+ '// CUTTLEFISH_MATRIX_END',
43
+ ];
44
+ }
45
+ /**
46
+ * Resolve a HAL matrix.* op to Zephyr C++.
47
+ * Returns `{ code }` for statement ops.
48
+ */
49
+ export function lowerMatrix(op) {
50
+ const o = op;
51
+ switch (op.operation) {
52
+ case 'matrix.on_key': {
53
+ // The comment marker carries the construction pad lists to the
54
+ // toolchain's overlay scan (the pwm user-facts precedent).
55
+ return {
56
+ code: `/* cuttlefish-matrix: rows=${o.rows} cols=${o.cols} */ __tc_matrix_on_key = ${o.handler};`,
57
+ };
58
+ }
59
+ default:
60
+ throw new Error(`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
61
+ `Open an issue or use rawCpp() to emit it manually.`);
62
+ }
63
+ }
@@ -0,0 +1,10 @@
1
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
2
+ import type { ZephyrChipDescriptor } from '../chips/types.js';
3
+ /**
4
+ * Resolve a HAL power.* op to Zephyr C++.
5
+ * Returns `{ code }` for statement ops.
6
+ */
7
+ export declare function lowerPower(op: HALOpIR, chip?: ZephyrChipDescriptor): {
8
+ code?: string;
9
+ expression?: string;
10
+ };
@@ -0,0 +1,35 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Power lowering — sys_poweroff() for explicit soft-off entry
3
+ //
4
+ // Both SoC families implement the kernel's sys_poweroff(): ESP32 parks the
5
+ // RTC domain and calls esp_deep_sleep_start(); STM32 enters standby-class
6
+ // deep sleep. The call never returns — wake is a reset or a wake source
7
+ // (board-specific: the Black Pill's WKUP pin is PA0; ESP32 GPIOs armed as
8
+ // wake triggers). Statement emission continues after the call site, but
9
+ // nothing there runs — the transpiler treats it as any other void call.
10
+ // ----------------------------------------------------------------------------
11
+ /**
12
+ * Resolve a HAL power.* op to Zephyr C++.
13
+ * Returns `{ code }` for statement ops.
14
+ */
15
+ export function lowerPower(op, chip) {
16
+ const o = op;
17
+ switch (op.operation) {
18
+ case 'power.off':
19
+ return { code: 'sys_poweroff();' };
20
+ case 'power.off_for': {
21
+ // DAC discipline: without the RTC wake-timer fact, a timed off would
22
+ // sleep with no wake source — lower to a comment naming it.
23
+ if (!chip?.powerWakeTimer) {
24
+ return { code: `/* Power.offFor(${o.ms} ms): this board's SoC declares no RTC wake timer — soft-off here has no armed wake; use Power.off() */` };
25
+ }
26
+ // Arm the RTC timer (µs), then enter soft-off; wake is a reboot.
27
+ return {
28
+ code: `{ esp_sleep_enable_timer_wakeup(static_cast<uint64_t>(${o.ms}) * 1000ULL); sys_poweroff(); }`,
29
+ };
30
+ }
31
+ default:
32
+ throw new Error(`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
33
+ `Open an issue or use rawCpp() to emit it manually.`);
34
+ }
35
+ }
@@ -140,6 +140,31 @@ export function lowerPwm(op, chip) {
140
140
  // pulse to idle — follow with setPulse/setDuty to drive the line.
141
141
  return { code: `${marker}(void)pwm_set_dt(&${v}, ${o.periodNs}, 0);` };
142
142
  }
143
+ // ── Servo (hal/servo.ts) — calibrated 50 Hz sugar on the same channel ──
144
+ // The commanded value is runtime text; the calibrated range and travel
145
+ // are construction literals carried on the op. Clamping and the angle→
146
+ // pulse mapping run in the emitted C++ — they cannot fold at transpile
147
+ // time. Pulse widths convert µs→ns here (×1000).
148
+ case 'pwm.servo_us': {
149
+ const minNs = Math.round(Number(o.minUs ?? 1000) * 1000);
150
+ const maxNs = Math.round(Number(o.maxUs ?? 2000) * 1000);
151
+ return {
152
+ code: `{ ${marker}static bool __tc_pwm_p${o.pin}_prd = false; if (!__tc_pwm_p${o.pin}_prd) { (void)pwm_set_dt(&${v}, ${o.periodNs}, 0); __tc_pwm_p${o.pin}_prd = true; } uint32_t __tc_sv_p${o.pin} = static_cast<uint32_t>(${o.us}) * 1000U; if (__tc_sv_p${o.pin} < ${minNs}U) { __tc_sv_p${o.pin} = ${minNs}U; } if (__tc_sv_p${o.pin} > ${maxNs}U) { __tc_sv_p${o.pin} = ${maxNs}U; } (void)pwm_set_pulse_dt(&${v}, __tc_sv_p${o.pin}); }`,
153
+ };
154
+ }
155
+ case 'pwm.servo_angle': {
156
+ const minNs = Math.round(Number(o.minUs ?? 1000) * 1000);
157
+ const maxNs = Math.round(Number(o.maxUs ?? 2000) * 1000);
158
+ const maxAngle = Number(o.maxAngle ?? 180) || 180;
159
+ return {
160
+ code: `{ ${marker}static bool __tc_pwm_p${o.pin}_prd = false; if (!__tc_pwm_p${o.pin}_prd) { (void)pwm_set_dt(&${v}, ${o.periodNs}, 0); __tc_pwm_p${o.pin}_prd = true; } double __tc_sv_a${o.pin} = static_cast<double>(${o.angle}); if (__tc_sv_a${o.pin} < 0.0) { __tc_sv_a${o.pin} = 0.0; } if (__tc_sv_a${o.pin} > ${maxAngle}.0) { __tc_sv_a${o.pin} = ${maxAngle}.0; } (void)pwm_set_pulse_dt(&${v}, static_cast<uint32_t>(static_cast<double>(${minNs}) + (__tc_sv_a${o.pin} * ((static_cast<double>(${maxNs}) - static_cast<double>(${minNs})) / static_cast<double>(${maxAngle}.0))))); }`,
161
+ };
162
+ }
163
+ case 'pwm.servo_idle': {
164
+ return {
165
+ code: `{ ${marker}static bool __tc_pwm_p${o.pin}_prd = false; if (!__tc_pwm_p${o.pin}_prd) { (void)pwm_set_dt(&${v}, ${o.periodNs}, 0); __tc_pwm_p${o.pin}_prd = true; } (void)pwm_set_pulse_dt(&${v}, 0U); }`,
166
+ };
167
+ }
143
168
  default:
144
169
  throw new Error(`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
145
170
  `Open an issue or use rawCpp() to emit it manually.`);
@@ -17,7 +17,7 @@ export interface SensorNames {
17
17
  /** The bus port: I2C address or SPI CS pin. */
18
18
  port: number;
19
19
  /** 'i2c' | 'spi'. */
20
- busKind: 'i2c' | 'spi';
20
+ busKind: 'i2c' | 'spi' | 'w1';
21
21
  }
22
22
  /** Derive a sensor's DT/C++ names. The scanner regexes the emitted __tc_
23
23
  * prefix form, so the part group must stay [a-z0-9_]+ and greedy. */
@@ -26,7 +26,7 @@ export declare function sensorNames(part: string, bus: string, port: number | st
26
26
  * Emit the per-sensor state block (device handle + sensor_value scratch).
27
27
  * Called from shimLines for each distinct sensor the program's ops reference.
28
28
  */
29
- export declare function sensorStateLines(part: string, bus: string, port: number | string, busKind?: string, spiHz?: number | string, spiMode?: number | string, alertPin?: number | string): string[];
29
+ export declare function sensorStateLines(part: string, bus: string, port: number | string, busKind?: string, spiHz?: number | string, spiMode?: number | string, alertPin?: number | string, resolution?: number | string): string[];
30
30
  /**
31
31
  * Resolve a HAL sensor.* op to Zephyr C++.
32
32
  * Returns `{ code }` for fetch (statement), `{ expression }` for get (value).
@@ -31,14 +31,16 @@ export function sensorNames(part, bus, port, busKind = 'i2c') {
31
31
  const portNum = typeof port === 'number' ? port : parseInt(String(port));
32
32
  const stem = busKind === 'spi'
33
33
  ? `${partKey}_spi${busIndex}_cs${portNum}`
34
- : `${partKey}_i2c${busIndex}_0x${portNum.toString(16)}`;
34
+ : busKind === 'w1'
35
+ ? `${partKey}_w1_p${portNum}`
36
+ : `${partKey}_i2c${busIndex}_0x${portNum.toString(16)}`;
35
37
  return {
36
38
  dtLabel: `tc_${stem}`,
37
39
  devVar: `__tc_sensor_${stem}_dev`,
38
40
  valVar: `__tc_sensor_${stem}_val`,
39
41
  busIndex,
40
42
  port: portNum,
41
- busKind: busKind === 'spi' ? 'spi' : 'i2c',
43
+ busKind: busKind === 'spi' ? 'spi' : busKind === 'w1' ? 'w1' : 'i2c',
42
44
  };
43
45
  }
44
46
  /**
@@ -67,13 +69,15 @@ function requireBusKind(partKey, info, busKind) {
67
69
  * Emit the per-sensor state block (device handle + sensor_value scratch).
68
70
  * Called from shimLines for each distinct sensor the program's ops reference.
69
71
  */
70
- export function sensorStateLines(part, bus, port, busKind = 'i2c', spiHz = 0, spiMode = 0, alertPin = -1) {
72
+ export function sensorStateLines(part, bus, port, busKind = 'i2c', spiHz = 0, spiMode = 0, alertPin = -1, resolution = 12) {
71
73
  const partKey = sensorPartKey(String(part));
72
74
  requireBusKind(partKey, requirePartInfo(partKey), busKind);
73
75
  const n = sensorNames(part, bus, port, busKind);
74
76
  // The config comment carries construction facts (SPI clock, mode, alert
75
77
  // GPIO) to the overlay scanner — they shape the DT node, not the C++.
76
- const cfg = `// tc-sensor-cfg: ${n.dtLabel} hz=${Number(spiHz)} mode=${Number(spiMode)} alert=${Number(alertPin)}`;
78
+ const cfg = busKind === 'w1'
79
+ ? `// tc-sensor-cfg: ${n.dtLabel} res=${Number(resolution)}`
80
+ : `// tc-sensor-cfg: ${n.dtLabel} hz=${Number(spiHz)} mode=${Number(spiMode)} alert=${Number(alertPin)}`;
77
81
  return [
78
82
  '// CUTTLEFISH_SENSOR_BEGIN',
79
83
  `static const struct device* ${n.devVar} = DEVICE_DT_GET(DT_NODELABEL(${n.dtLabel}));`,
@@ -0,0 +1,16 @@
1
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
2
+ import type { ZephyrChipDescriptor } from '../chips/types.js';
3
+ /**
4
+ * Emit the per-strip state. Called from shimLines for each SPI controller
5
+ * the program drives a strip on: one `struct led_rgb` buffer sized by the
6
+ * construction chain length + the DT device handle.
7
+ */
8
+ export declare function stripInitLines(chip: ZephyrChipDescriptor, busIndex: number, count: number): string[];
9
+ /**
10
+ * Resolve a HAL strip.* op to Zephyr C++.
11
+ * Returns `{ code }` for statement ops.
12
+ */
13
+ export declare function lowerStrip(op: HALOpIR, chip: ZephyrChipDescriptor): {
14
+ code?: string;
15
+ expression?: string;
16
+ };
@@ -0,0 +1,70 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Strip lowering — ws2812-spi child node + led_strip_update_rgb
3
+ //
4
+ // The strip is an spi-device child of one of the board's wired SPI
5
+ // controllers (Zephyr's ws2812-spi driver synthesizes the WS2812 waveform
6
+ // on the bus's MOSI line — the board-equal form: the strip uses whatever
7
+ // pads the board's devicetree routes that SPI to). The shim block declares
8
+ // the pixel buffer + device handle; the overlay generator synthesizes the
9
+ // DT child node (chain length + tuned frames — see dt-config/overlay.ts).
10
+ // set/fill edit the buffer; show() is one led_strip_update_rgb.
11
+ // ----------------------------------------------------------------------------
12
+ import { parseControllerIndex } from './util.js';
13
+ /** The C variable names for a strip on controller index `idx`. */
14
+ function stripVars(idx) {
15
+ return { buf: `__tc_strip${idx}_buf`, dev: `__tc_strip${idx}_dev` };
16
+ }
17
+ /**
18
+ * Emit the per-strip state. Called from shimLines for each SPI controller
19
+ * the program drives a strip on: one `struct led_rgb` buffer sized by the
20
+ * construction chain length + the DT device handle.
21
+ */
22
+ export function stripInitLines(chip, busIndex, count) {
23
+ const ctrl = chip.spi?.controllers[busIndex];
24
+ if (!ctrl)
25
+ return [];
26
+ const { buf, dev } = stripVars(busIndex);
27
+ return [
28
+ '// CUTTLEFISH_STRIP_BEGIN',
29
+ `static struct led_rgb ${buf}[${count}];`,
30
+ `static const struct device* ${dev} = DEVICE_DT_GET(DT_NODELABEL(tc_strip${busIndex}));`,
31
+ '// CUTTLEFISH_STRIP_END',
32
+ ];
33
+ }
34
+ /**
35
+ * Resolve a HAL strip.* op to Zephyr C++.
36
+ * Returns `{ code }` for statement ops.
37
+ */
38
+ export function lowerStrip(op, chip) {
39
+ const o = op;
40
+ // DAC discipline: a board with no wired SPI (or an out-of-range bus)
41
+ // lowers to a comment naming the constraint — the honest per-board answer
42
+ // (the manifest probe runs boardless; profileDiagnostics flags the usage).
43
+ const idx = parseControllerIndex(typeof o.bus === 'string' ? o.bus : String(o.bus));
44
+ if (!chip.spi || idx >= chip.spi.controllers.length) {
45
+ const wired = chip.spi ? `this board wires ${chip.spi.controllers.length} SPI controller(s)` : 'this board wires no SPI controller';
46
+ return { code: `/* strip on bus ${o.bus}: no such SPI controller — ${wired}; a Strip rides one of the board's SPI buses */` };
47
+ }
48
+ const { buf, dev } = stripVars(idx);
49
+ const count = Number(o.count ?? 1) || 1;
50
+ switch (op.operation) {
51
+ case 'strip.set_pixel': {
52
+ // Buffer assignment — runtime index and channel values splice as C++.
53
+ // The driver applies the strip's color order (GRB mapping) at show.
54
+ return {
55
+ code: `{ uint32_t __tc_strip_i = static_cast<uint32_t>(${o.index}); if (__tc_strip_i < ${count}U) { ${buf}[__tc_strip_i].r = static_cast<uint8_t>(${o.r}); ${buf}[__tc_strip_i].g = static_cast<uint8_t>(${o.g}); ${buf}[__tc_strip_i].b = static_cast<uint8_t>(${o.b}); } }`,
56
+ };
57
+ }
58
+ case 'strip.fill': {
59
+ return {
60
+ code: `{ for (uint32_t __tc_strip_j = 0U; __tc_strip_j < ${count}U; ++__tc_strip_j) { ${buf}[__tc_strip_j].r = static_cast<uint8_t>(${o.r}); ${buf}[__tc_strip_j].g = static_cast<uint8_t>(${o.g}); ${buf}[__tc_strip_j].b = static_cast<uint8_t>(${o.b}); } }`,
61
+ };
62
+ }
63
+ case 'strip.show': {
64
+ return { code: `(void)led_strip_update_rgb(${dev}, ${buf}, ${count}U);` };
65
+ }
66
+ default:
67
+ throw new Error(`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
68
+ `Open an issue or use rawCpp() to emit it manually.`);
69
+ }
70
+ }
@@ -42,8 +42,12 @@ export function lowerThread(op) {
42
42
  case 'thread.start': {
43
43
  // Store the entry, create + schedule immediately. The stack size comes
44
44
  // from the state block (the construction fact), via K_THREAD_STACK_SIZEOF.
45
+ // The thread gets a name so trace heartbeats report it as something
46
+ // other than "unnamed" — k_thread_name_set returns -ENOSYS without
47
+ // CONFIG_THREAD_NAME (which the scaffold turns on for traced builds),
48
+ // so the call is emitted unconditionally.
45
49
  return {
46
- code: `${p}_fn = (${o.handler}); (void)k_thread_create(&${p}_thread, ${p}_stack, K_THREAD_STACK_SIZEOF(${p}_stack), ${p}_tramp, NULL, NULL, NULL, ${o.priority ?? 5}, 0, K_NO_WAIT);`,
50
+ code: `${p}_fn = (${o.handler}); (void)k_thread_create(&${p}_thread, ${p}_stack, K_THREAD_STACK_SIZEOF(${p}_stack), ${p}_tramp, NULL, NULL, NULL, ${o.priority ?? 5}, 0, K_NO_WAIT); (void)k_thread_name_set(&${p}_thread, "tc_thread_${instance}");`,
47
51
  };
48
52
  }
49
53
  case 'thread.join':
@@ -0,0 +1,44 @@
1
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
2
+ /**
3
+ * Resolve a HAL trace.* op to Zephyr C++. The device helpers
4
+ * (__tc_trace_mark / __tc_trace_event) are emitted unconditionally by
5
+ * shimLines — they are two printf calls, and a Trace.mark() call site must
6
+ * never fail to link because tracing was disabled (the lines simply have no
7
+ * capture listening).
8
+ */
9
+ export declare function lowerTrace(op: HALOpIR): {
10
+ code?: string;
11
+ expression?: string;
12
+ };
13
+ /** Default heartbeat interval (ms) when zephyr.trace.intervalMs is absent. */
14
+ export declare const TRACE_HB_DEFAULT_INTERVAL_MS = 1000;
15
+ /** Clamp for zephyr.trace.intervalMs — faster than 50 ms floods the console
16
+ * and skews the measured CPU load; slower than a minute adds nothing. */
17
+ export declare function clampTraceIntervalMs(intervalMs: number | undefined): number;
18
+ /**
19
+ * UI frame-time trace block — emitted UNCONDITIONALLY for UI-mounted
20
+ * programs (the call site the emitter stamps after ui_tick must never fail
21
+ * to link), but every body compiles away unless the heartbeat block defined
22
+ * CUTTLEFISH_TRACE_UI (zephyr.trace enabled). Wall-time stats only: frame
23
+ * count / total / max ms per heartbeat interval — the per-phase breakdown
24
+ * inside ui_tick stays a ui-engine change for a later stage.
25
+ */
26
+ /**
27
+ * On-device alarm thresholds (zephyr.trace.alarms) — the sampler prints
28
+ * [TR:ALARM:<seq>:<code>:<detail>] the moment one is breached, so an
29
+ * untethered device still DETECTS the degradation; reading it only needs a
30
+ * capture (or a serial console) after the fact.
31
+ */
32
+ export interface TraceAlarmThresholds {
33
+ /** Alarm when a thread's unused stack falls below this (bytes). */
34
+ stackMinBytes?: number;
35
+ /** Alarm when an interval's worst UI frame exceeds this (ms). */
36
+ frameMaxMs?: number;
37
+ }
38
+ export declare function uiFrameTraceLines(frameAlarmMs?: number): string[];
39
+ /**
40
+ * The heartbeat shim lines (definitions + SYS_INIT self-start). Emitted by
41
+ * shimLines when the platform context carries zephyr.trace.enabled.
42
+ * `withUi` adds the [TR:UI: frame-stats line (UI-mounted programs).
43
+ */
44
+ export declare function traceHeartbeatLines(intervalMs: number, withUi: boolean, alarms?: TraceAlarmThresholds): string[];
@@ -0,0 +1,239 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Trace heartbeat — the runtime-stats sampler behind `typecad-hal trace`
3
+ //
4
+ // Emits a self-starting sampler into the ENTRY translation unit only (the
5
+ // block compiles under CUTTLEFISH_ENTRY_TU, which the emitter defines once
6
+ // per program — a copy in a split-file TU would register the SYS_INIT hook
7
+ // twice and double every heartbeat). A k_work_delayable on the system work
8
+ // queue samples, every intervalMs:
9
+ //
10
+ // - per-thread execution cycles (CONFIG_THREAD_RUNTIME_STATS)
11
+ // - per-thread stack unused/size (CONFIG_INIT_STACKS / THREAD_STACK_INFO)
12
+ // - the system-wide cycle counter (k_thread_runtime_stats_all_get — the
13
+ // CPU-time denominator; per-thread CPU% is computed HOST-side as
14
+ // delta_thread_exec / delta_sys_exec, so no cycle/Hz units leak onto
15
+ // the wire and idle shows up as its own thread)
16
+ //
17
+ // and prints `[TR:` lines on the console (printf → STDOUT_CONSOLE, the same
18
+ // channel the test-runner protocol uses). Sampling, not event tracing: the
19
+ // device cost is O(live threads) arithmetic per interval — no context-switch
20
+ // hook, no ring buffer, no per-frame work — so the probe effect on the
21
+ // measured system is one short work-queue visit per interval.
22
+ //
23
+ // Heap usage is deliberately NOT sampled: engine-emitted C++ is no-malloc by
24
+ // construction (AUTOSAR rule set), so a heap series would be a constant line.
25
+ //
26
+ // The whole block also gates on CONFIG_THREAD_MONITOR + CONFIG_THREAD_RUNTIME
27
+ // _STATS: the scaffold enables both when zephyr.trace.enabled is set, but a
28
+ // user override in zephyr.kconfig compiles the sampler out instead of
29
+ // failing the link (k_thread_foreach lives in kernel/thread_monitor.c and
30
+ // only builds under THREAD_MONITOR).
31
+ // ---------------------------------------------------------------------------
32
+ /**
33
+ * Resolve a HAL trace.* op to Zephyr C++. The device helpers
34
+ * (__tc_trace_mark / __tc_trace_event) are emitted unconditionally by
35
+ * shimLines — they are two printf calls, and a Trace.mark() call site must
36
+ * never fail to link because tracing was disabled (the lines simply have no
37
+ * capture listening).
38
+ */
39
+ export function lowerTrace(op) {
40
+ const o = op;
41
+ switch (op.operation) {
42
+ case 'trace.mark':
43
+ return { code: `__tc_trace_mark(${o.name ?? '""'});` };
44
+ case 'trace.event':
45
+ // The value may be a runtime expression (variable, ternary) — the
46
+ // plugin resolved it as text; interpolate inside static_cast so any
47
+ // numeric C++ expression is valid here.
48
+ return { code: `__tc_trace_event(${o.name ?? '""'}, static_cast<double>(${o.value ?? 0}));` };
49
+ default:
50
+ throw new Error(`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
51
+ `Open an issue or use rawCpp() to emit it manually.`);
52
+ }
53
+ }
54
+ /** Default heartbeat interval (ms) when zephyr.trace.intervalMs is absent. */
55
+ export const TRACE_HB_DEFAULT_INTERVAL_MS = 1000;
56
+ /** Clamp for zephyr.trace.intervalMs — faster than 50 ms floods the console
57
+ * and skews the measured CPU load; slower than a minute adds nothing. */
58
+ export function clampTraceIntervalMs(intervalMs) {
59
+ if (typeof intervalMs !== 'number' || !Number.isFinite(intervalMs)) {
60
+ return TRACE_HB_DEFAULT_INTERVAL_MS;
61
+ }
62
+ return Math.min(60_000, Math.max(50, Math.round(intervalMs)));
63
+ }
64
+ export function uiFrameTraceLines(frameAlarmMs) {
65
+ const alarmDefine = frameAlarmMs !== undefined ? [`#define __TC_TRACE_ALARM_FRAME ${frameAlarmMs}U`] : [];
66
+ const frameAlarm = frameAlarmMs !== undefined
67
+ ? [
68
+ ' // Continual monitoring: the worst frame breaching the ceiling is an',
69
+ ' // alarm, not a statistic — printed the moment it happens.',
70
+ ' if (__tc_trace_ui_max_ms > __TC_TRACE_ALARM_FRAME) {',
71
+ ' printf("[TR:ALARM:%u:frame:%u\\n", static_cast<unsigned int>(seq),',
72
+ ' static_cast<unsigned int>(__tc_trace_ui_max_ms));',
73
+ ' }',
74
+ ]
75
+ : [];
76
+ return [
77
+ '// CUTTLEFISH_TRACE_UI_BEGIN',
78
+ '#if defined(CUTTLEFISH_TRACE_UI)',
79
+ ...alarmDefine,
80
+ 'static uint32_t __tc_trace_ui_frames = 0U;',
81
+ 'static uint32_t __tc_trace_ui_total_ms = 0U;',
82
+ 'static uint32_t __tc_trace_ui_max_ms = 0U;',
83
+ 'static uint64_t __tc_trace_ui_phase[5];',
84
+ '#endif',
85
+ 'static inline void __tc_trace_ui_frame(uint32_t frame_delta_ms) {',
86
+ '#if defined(CUTTLEFISH_TRACE_UI)',
87
+ ' __tc_trace_ui_frames += 1U;',
88
+ ' __tc_trace_ui_total_ms += frame_delta_ms;',
89
+ ' if (frame_delta_ms > __tc_trace_ui_max_ms) { __tc_trace_ui_max_ms = frame_delta_ms; }',
90
+ '#else',
91
+ ' (void)frame_delta_ms;',
92
+ '#endif',
93
+ '}',
94
+ // Called from the seams the ui-engine's emitTick interleaves between its
95
+ // five phase slices (bindings / transitions / draw / scroll / flush).
96
+ // Cycles are accumulated raw; the report converts to µs once per
97
+ // heartbeat via sys_clock_hw_cycles_per_sec().
98
+ 'static inline void __tc_trace_ui_phase_add(uint8_t idx, uint64_t cycles) {',
99
+ '#if defined(CUTTLEFISH_TRACE_UI)',
100
+ ' if (idx < 5U) { __tc_trace_ui_phase[idx] += cycles; }',
101
+ '#else',
102
+ ' (void)idx; (void)cycles;',
103
+ '#endif',
104
+ '}',
105
+ 'static inline void __tc_trace_ui_report(uint32_t seq) {',
106
+ '#if defined(CUTTLEFISH_TRACE_UI)',
107
+ ' if (__tc_trace_ui_frames > 0U) {',
108
+ ' const uint32_t avg_x10 = (__tc_trace_ui_total_ms * 10U) / __tc_trace_ui_frames;',
109
+ ' printf("[TR:UI:%u:%u:%u:%u\\n", static_cast<unsigned int>(seq),',
110
+ ' static_cast<unsigned int>(__tc_trace_ui_frames),',
111
+ ' static_cast<unsigned int>(avg_x10),',
112
+ ' static_cast<unsigned int>(__tc_trace_ui_max_ms));',
113
+ ' printf("[TR:UP:%u:%llu:%llu:%llu:%llu:%llu\\n", static_cast<unsigned int>(seq),',
114
+ ' static_cast<unsigned long long>((__tc_trace_ui_phase[0] * 1000000ULL) / sys_clock_hw_cycles_per_sec()),',
115
+ ' static_cast<unsigned long long>((__tc_trace_ui_phase[1] * 1000000ULL) / sys_clock_hw_cycles_per_sec()),',
116
+ ' static_cast<unsigned long long>((__tc_trace_ui_phase[2] * 1000000ULL) / sys_clock_hw_cycles_per_sec()),',
117
+ ' static_cast<unsigned long long>((__tc_trace_ui_phase[3] * 1000000ULL) / sys_clock_hw_cycles_per_sec()),',
118
+ ' static_cast<unsigned long long>((__tc_trace_ui_phase[4] * 1000000ULL) / sys_clock_hw_cycles_per_sec()));',
119
+ ...frameAlarm,
120
+ ' }',
121
+ '#else',
122
+ ' (void)seq;',
123
+ '#endif',
124
+ '}',
125
+ 'static inline void __tc_trace_ui_reset(void) {',
126
+ '#if defined(CUTTLEFISH_TRACE_UI)',
127
+ ' __tc_trace_ui_frames = 0U;',
128
+ ' __tc_trace_ui_total_ms = 0U;',
129
+ ' __tc_trace_ui_max_ms = 0U;',
130
+ ' for (uint8_t i = 0U; i < 5U; i++) { __tc_trace_ui_phase[i] = 0ULL; }',
131
+ '#endif',
132
+ '}',
133
+ '// CUTTLEFISH_TRACE_UI_END',
134
+ ];
135
+ }
136
+ /**
137
+ * The heartbeat shim lines (definitions + SYS_INIT self-start). Emitted by
138
+ * shimLines when the platform context carries zephyr.trace.enabled.
139
+ * `withUi` adds the [TR:UI: frame-stats line (UI-mounted programs).
140
+ */
141
+ export function traceHeartbeatLines(intervalMs, withUi, alarms) {
142
+ const ms = clampTraceIntervalMs(intervalMs);
143
+ const stackAlarmDefine = alarms?.stackMinBytes !== undefined
144
+ ? [`#define __TC_TRACE_ALARM_STACK ${alarms.stackMinBytes}U`]
145
+ : [];
146
+ const stackAlarm = alarms?.stackMinBytes !== undefined
147
+ ? [
148
+ ' // Continual monitoring: headroom under the floor is an alarm, printed',
149
+ ' // the moment the sampler sees it (once per interval while held).',
150
+ '#ifdef __TC_TRACE_ALARM_STACK',
151
+ ' if (stack_ok && unused < __TC_TRACE_ALARM_STACK) {',
152
+ ' printf("[TR:ALARM:%u:stack:%s:%u\\n", static_cast<unsigned int>(seq), name,',
153
+ ' static_cast<unsigned int>(unused));',
154
+ ' }',
155
+ '#endif',
156
+ ]
157
+ : [];
158
+ return [
159
+ '// CUTTLEFISH_TRACE_BEGIN',
160
+ '#if defined(CUTTLEFISH_ENTRY_TU) && defined(CONFIG_THREAD_MONITOR) && defined(CONFIG_THREAD_RUNTIME_STATS)',
161
+ `#define __TC_TRACE_HB_MS ${ms}U`,
162
+ ...stackAlarmDefine,
163
+ 'static uint32_t __tc_trace_seq = 0U;',
164
+ // One [TR:TH:<seq>:<name>:<exec>:<unused>:<size>] line per live thread.
165
+ // Stack fields print -1 when the platform cannot inspect that thread
166
+ // (ARC-style NO_UNUSED_STACK_INSPECTION faults) — the host reads the
167
+ // sentinel as "unknown", not zero.
168
+ 'static void __tc_trace_thread_line(const struct k_thread* cthread, void* user_data) {',
169
+ ' (void)user_data;',
170
+ ' // The foreach callback hands out a const thread; the stats/name',
171
+ ' // syscalls take k_tid_t (non-const) — the same const_cast Zephyr\'s',
172
+ ' // own thread_analyzer performs at this exact boundary.',
173
+ ' struct k_thread* thread = const_cast<struct k_thread*>(cthread);',
174
+ ' const uint32_t seq = __tc_trace_seq;',
175
+ ' k_thread_runtime_stats_t stats;',
176
+ ' uint64_t exec = 0U;',
177
+ ' if (k_thread_runtime_stats_get(thread, &stats) == 0) {',
178
+ ' exec = stats.execution_cycles;',
179
+ ' }',
180
+ ' size_t unused = 0U;',
181
+ ' const bool stack_ok = k_thread_stack_space_get(thread, &unused) == 0;',
182
+ '#ifdef CONFIG_THREAD_STACK_INFO',
183
+ ' const size_t size = thread->stack_info.size;',
184
+ '#else',
185
+ ' const size_t size = 0U;',
186
+ '#endif',
187
+ ' char name[24];',
188
+ ' const char* thread_name = k_thread_name_get(thread);',
189
+ ' uint32_t n = 0U;',
190
+ ' if (thread_name != nullptr) {',
191
+ ' for (; thread_name[n] != \'\\0\' && n < (sizeof(name) - 1U); n++) {',
192
+ ' const char c = thread_name[n];',
193
+ ' const bool plain = (c >= \'a\' && c <= \'z\') || (c >= \'A\' && c <= \'Z\')',
194
+ ' || (c >= \'0\' && c <= \'9\') || c == \'_\' || c == \'-\' || c == \'.\';',
195
+ ' name[n] = plain ? c : \'_\';',
196
+ ' }',
197
+ ' }',
198
+ ' name[n] = \'\\0\';',
199
+ ' if (n == 0U) { name[0] = \'u\'; name[1] = \'n\'; name[2] = \'\\0\'; }',
200
+ ' printf("[TR:TH:%u:%s:%llu:%lld:%u\\n",',
201
+ ' static_cast<unsigned int>(seq), name,',
202
+ ' static_cast<unsigned long long>(exec),',
203
+ ' stack_ok ? static_cast<long long>(unused) : -1LL,',
204
+ ' static_cast<unsigned int>(size));',
205
+ ...stackAlarm,
206
+ '}',
207
+ 'static void __tc_trace_hb_work(struct k_work* work);',
208
+ 'static K_WORK_DELAYABLE_DEFINE(__tc_trace_hb_dwork, __tc_trace_hb_work);',
209
+ 'static void __tc_trace_hb_work(struct k_work* work) {',
210
+ ' (void)work;',
211
+ ' k_thread_runtime_stats_t all;',
212
+ ' uint64_t sys_exec = 0U;',
213
+ ' if (k_thread_runtime_stats_all_get(&all) == 0) {',
214
+ ' sys_exec = all.execution_cycles;',
215
+ ' }',
216
+ ' __tc_trace_seq += 1U;',
217
+ ' printf("[TR:HB:%u:%u:%llu\\n",',
218
+ ' static_cast<unsigned int>(__tc_trace_seq),',
219
+ ' static_cast<unsigned int>(k_uptime_get_32()),',
220
+ ' static_cast<unsigned long long>(sys_exec));',
221
+ ...(withUi
222
+ ? [
223
+ ' __tc_trace_ui_report(__tc_trace_seq);',
224
+ ' __tc_trace_ui_reset();',
225
+ ]
226
+ : []),
227
+ ' k_thread_foreach(__tc_trace_thread_line, nullptr);',
228
+ ' k_work_reschedule(&__tc_trace_hb_dwork, K_MSEC(__TC_TRACE_HB_MS));',
229
+ '}',
230
+ 'static int __tc_trace_hb_boot(void) {',
231
+ ` printf("[TR:CFG:1:${ms}]\\n");`,
232
+ ' k_work_schedule(&__tc_trace_hb_dwork, K_MSEC(__TC_TRACE_HB_MS));',
233
+ ' return 0;',
234
+ '}',
235
+ 'SYS_INIT(__tc_trace_hb_boot, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);',
236
+ '#endif // CUTTLEFISH_ENTRY_TU && THREAD_MONITOR && THREAD_RUNTIME_STATS',
237
+ '// CUTTLEFISH_TRACE_END',
238
+ ];
239
+ }
@@ -26,7 +26,12 @@ export function uartInitLines(chip, controllerIndex) {
26
26
  '// CUTTLEFISH_UART_BEGIN',
27
27
  `static const struct device* ${p}_dev = DEVICE_DT_GET(DT_NODELABEL(${ctrl.nodeLabel}));`,
28
28
  `static void ${p}_init(uint32_t baud) {`,
29
- ` const struct uart_config cfg = { .baudrate = (baud ? baud : 115200), .parity = UART_CFG_PARITY_NONE, .stop_bits = UART_CFG_STOP_BITS_1, .data_bits = UART_CFG_DATA_BITS_8, .flow_ctrl = UART_CFG_FLOW_CTRL_NONE };`,
29
+ // 115200 8N1 is the console default — reconfiguring an already-matching
30
+ // port RESETS the ESP32 UART's TX path and silently eats the FIRST write
31
+ // that follows (poll_out completes into a reconfiguring FIFO). Only a
32
+ // genuinely different rate reconfigures.
33
+ ` if (baud == 0U || baud == 115200U) { return; }`,
34
+ ` const struct uart_config cfg = { .baudrate = baud, .parity = UART_CFG_PARITY_NONE, .stop_bits = UART_CFG_STOP_BITS_1, .data_bits = UART_CFG_DATA_BITS_8, .flow_ctrl = UART_CFG_FLOW_CTRL_NONE };`,
30
35
  ` uart_configure(${p}_dev, &cfg);`,
31
36
  `}`,
32
37
  '// CUTTLEFISH_UART_END',
@@ -16,7 +16,9 @@ import type { ZephyrChipDescriptor } from '../chips/types.js';
16
16
  * subsys/usb/device_next/Kconfig, include/zephyr/usb/usbd.h, and
17
17
  * samples/subsys/usb/common/sample_usbd_init.c.
18
18
  */
19
- export declare function usbdDeviceLines(chip: ZephyrChipDescriptor): string[];
19
+ export declare function usbdDeviceLines(chip: ZephyrChipDescriptor, opts?: {
20
+ hid?: boolean;
21
+ }): string[];
20
22
  /**
21
23
  * Emit the per-instance CDC device + init helper. Called from shimLines when
22
24
  * the program uses USB and the chip declares `usb`; must be paired with one
@@ -44,11 +44,11 @@ function nodeLabel(idx) {
44
44
  * subsys/usb/device_next/Kconfig, include/zephyr/usb/usbd.h, and
45
45
  * samples/subsys/usb/common/sample_usbd_init.c.
46
46
  */
47
- export function usbdDeviceLines(chip) {
47
+ export function usbdDeviceLines(chip, opts) {
48
48
  if (!chip.usb)
49
49
  return [];
50
50
  const vid = chip.usb.vid ?? '0x2fe3';
51
- const pid = chip.usb.pid ?? '0x0001';
51
+ const pid = chip.usb.pid ?? (opts?.hid ? '0x0008' : '0x0001');
52
52
  // 1200-baud touch-to-reset (BOSSA-bootloader boards): a usbd message
53
53
  // callback that reboots into the bootloader when the host sets the CDC
54
54
  // baud rate to 1200. Zephyr's CDC-ACM class publishes
@@ -126,7 +126,10 @@ export function usbdDeviceLines(chip) {
126
126
  // for boards without detection (STM32 OTG_FS).
127
127
  ' err = usbd_enable(&__tc_usbd);',
128
128
  ' if (err != 0) { printk("typecad-hal usb: enable failed: %d\\n", err); return; }',
129
- ' printk("typecad-hal usb: device enabled\\n");',
129
+ // No success printk: the message interleaves with the hardware-test
130
+ // protocol on the shared uart console (a [TC:EXPECT] line corrupted
131
+ // mid-suite when the HID tests first brought the device up). Failures
132
+ // still print — a broken USB is worth the noise.
130
133
  '}',
131
134
  '// CUTTLEFISH_USBD_END',
132
135
  ];