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

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 +3 -3
  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,244 @@
1
+ // ---------------------------------------------------------------------------
2
+ // HID lowering — USB keyboard/mouse over the Zephyr "next" device stack
3
+ //
4
+ // One HID interface per program (Zephyr's zephyr,hid-device node — a v1
5
+ // ceiling like the nRF PWM matrix: Keyboard OR Mouse, not both). The shim
6
+ // block declares the interface device + the boot report buffer + the report
7
+ // descriptor macro; the overlay generator synthesizes the DT node with the
8
+ // protocol the program uses (key verbs → keyboard, mouse verbs → mouse).
9
+ // Key/button arguments arrive as KEY.*/MOUSE.* token text (or runtime
10
+ // expressions) — the token map below translates name-for-name onto Zephyr's
11
+ // HID_KEY_* / button macros, with a build error naming the valid spellings.
12
+ // ----------------------------------------------------------------------------
13
+ /** KEY.* token → Zephyr usage macro. Digits and the four modifiers are the
14
+ * spellings that differ; everything else maps name-for-name. */
15
+ const KEY_TOKENS = {
16
+ A: 'HID_KEY_A', B: 'HID_KEY_B', C: 'HID_KEY_C', D: 'HID_KEY_D', E: 'HID_KEY_E',
17
+ F: 'HID_KEY_F', G: 'HID_KEY_G', H: 'HID_KEY_H', I: 'HID_KEY_I', J: 'HID_KEY_J',
18
+ K: 'HID_KEY_K', L: 'HID_KEY_L', M: 'HID_KEY_M', N: 'HID_KEY_N', O: 'HID_KEY_O',
19
+ P: 'HID_KEY_P', Q: 'HID_KEY_Q', R: 'HID_KEY_R', S: 'HID_KEY_S', T: 'HID_KEY_T',
20
+ U: 'HID_KEY_U', V: 'HID_KEY_V', W: 'HID_KEY_W', X: 'HID_KEY_X', Y: 'HID_KEY_Y',
21
+ Z: 'HID_KEY_Z',
22
+ N1: 'HID_KEY_1', N2: 'HID_KEY_2', N3: 'HID_KEY_3', N4: 'HID_KEY_4', N5: 'HID_KEY_5',
23
+ N6: 'HID_KEY_6', N7: 'HID_KEY_7', N8: 'HID_KEY_8', N9: 'HID_KEY_9', N0: 'HID_KEY_0',
24
+ ENTER: 'HID_KEY_ENTER', ESC: 'HID_KEY_ESC', BACKSPACE: 'HID_KEY_BACKSPACE',
25
+ TAB: 'HID_KEY_TAB', SPACE: 'HID_KEY_SPACE',
26
+ MINUS: 'HID_KEY_MINUS', EQUAL: 'HID_KEY_EQUAL',
27
+ LEFTBRACE: 'HID_KEY_LEFTBRACE', RIGHTBRACE: 'HID_KEY_RIGHTBRACE',
28
+ BACKSLASH: 'HID_KEY_BACKSLASH', SEMICOLON: 'HID_KEY_SEMICOLON',
29
+ APOSTROPHE: 'HID_KEY_APOSTROPHE', GRAVE: 'HID_KEY_GRAVE',
30
+ COMMA: 'HID_KEY_COMMA', DOT: 'HID_KEY_DOT', SLASH: 'HID_KEY_SLASH',
31
+ CAPSLOCK: 'HID_KEY_CAPSLOCK',
32
+ F1: 'HID_KEY_F1', F2: 'HID_KEY_F2', F3: 'HID_KEY_F3', F4: 'HID_KEY_F4',
33
+ F5: 'HID_KEY_F5', F6: 'HID_KEY_F6', F7: 'HID_KEY_F7', F8: 'HID_KEY_F8',
34
+ F9: 'HID_KEY_F9', F10: 'HID_KEY_F10', F11: 'HID_KEY_F11', F12: 'HID_KEY_F12',
35
+ // Zephyr names usage 70 HID_KEY_SYSRQ (the /* PRINTSCREEN */ comment is
36
+ // its only mention of the name) — the HAL token keeps the datasheet word.
37
+ PRINTSCREEN: 'HID_KEY_SYSRQ', SCROLLLOCK: 'HID_KEY_SCROLLLOCK',
38
+ PAUSE: 'HID_KEY_PAUSE', INSERT: 'HID_KEY_INSERT', HOME: 'HID_KEY_HOME',
39
+ PAGEUP: 'HID_KEY_PAGEUP', DEL: 'HID_KEY_DELETE', END: 'HID_KEY_END',
40
+ PAGEDOWN: 'HID_KEY_PAGEDOWN', RIGHT: 'HID_KEY_RIGHT', LEFT: 'HID_KEY_LEFT',
41
+ DOWN: 'HID_KEY_DOWN', UP: 'HID_KEY_UP',
42
+ };
43
+ /** Modifier tokens — a separate bitmask enum in Zephyr (usage codes start
44
+ * at 4, which collides with modifier bits, so the two kinds CANNOT share
45
+ * one runtime branch: the lowering emits different code per kind).
46
+ * Exported for the hid.h drift-guard test (every mapped macro must exist
47
+ * in Zephyr's header). */
48
+ export const KEY_MODIFIER_TOKENS = {
49
+ CTRL: 'HID_KBD_MODIFIER_LEFT_CTRL',
50
+ SHIFT: 'HID_KBD_MODIFIER_LEFT_SHIFT',
51
+ ALT: 'HID_KBD_MODIFIER_LEFT_ALT',
52
+ // HID usage tables call the Windows/Apple/GUI key "UI" — Zephyr follows.
53
+ GUI: 'HID_KBD_MODIFIER_LEFT_UI',
54
+ };
55
+ /** Is this KEY.* token a modifier (bitmask) rather than a usage code? */
56
+ export function isHidModifierToken(token) {
57
+ return token.startsWith('KEY.') && KEY_MODIFIER_TOKENS[token.slice(4)] !== undefined;
58
+ }
59
+ /** Map a modifier token to its Zephyr bitmask macro. */
60
+ function hidModifierMacro(token) {
61
+ return KEY_MODIFIER_TOKENS[token.slice(4)];
62
+ }
63
+ const MOUSE_TOKENS = {
64
+ LEFT: 'BIT(0)', RIGHT: 'BIT(1)', MIDDLE: 'BIT(2)',
65
+ };
66
+ /** Map a KEY or MOUSE token (or pass a runtime expression through) to C. */
67
+ export function hidTokenToMacro(token, kind) {
68
+ const prefix = kind === 'key' ? 'KEY.' : 'MOUSE.';
69
+ if (!token.startsWith(prefix))
70
+ return token; // runtime expression
71
+ const name = token.slice(prefix.length);
72
+ const table = kind === 'key' ? KEY_TOKENS : MOUSE_TOKENS;
73
+ const mapped = table[name];
74
+ if (!mapped) {
75
+ const valid = Object.keys(table).map((k) => prefix + k).join(', ');
76
+ throw new Error(`Unknown ${kind} token '${token}'. Valid spellings: ${valid} (or a runtime number expression).`);
77
+ }
78
+ return mapped;
79
+ }
80
+ /**
81
+ * Emit the HID interface state for one protocol. Called from shimLines when
82
+ * the program uses hid.* — one device handle, one report buffer, the boot
83
+ * report descriptor, and the guarded register helper that also starts the
84
+ * shared usbd context (the same one CDC rides).
85
+ */
86
+ export function hidInitLines(protocol) {
87
+ // The boot descriptors: keyboard takes no args; mouse takes its button
88
+ // count (3 — the MOUSE.LEFT/RIGHT/MIDDLE surface).
89
+ const desc = protocol === 'keyboard' ? 'HID_KEYBOARD_REPORT_DESC()' : 'HID_MOUSE_REPORT_DESC(3)';
90
+ // Driver-contract buffers: USB_STATIC_BUF_DEFINE gives pointer alignment
91
+ // (the DWC2 driver rejects unaligned IN buffers with "Buffer not aligned"
92
+ // and the completion still fires — every submit silently failed before
93
+ // this) and the nocache section on builds that require it.
94
+ const buf = protocol === 'keyboard'
95
+ ? 'USB_STATIC_BUF_DEFINE(__tc_kb_report, 8);'
96
+ : 'USB_STATIC_BUF_DEFINE(__tc_mouse_report, 4);';
97
+ const pragmaWrap = protocol === 'mouse'
98
+ ? [
99
+ '#pragma GCC diagnostic push',
100
+ '#pragma GCC diagnostic ignored "-Wnarrowing"',
101
+ ]
102
+ : [];
103
+ const pragmaUnwrap = protocol === 'mouse' ? ['#pragma GCC diagnostic pop'] : [];
104
+ return [
105
+ '// CUTTLEFISH_HID_BEGIN',
106
+ 'static const struct device* __tc_hid_dev = DEVICE_DT_GET_ONE(zephyr_hid_device);',
107
+ ...pragmaWrap,
108
+ `static const uint8_t __tc_hid_desc[] = ${desc};`,
109
+ ...pragmaUnwrap,
110
+ buf,
111
+ 'static bool __tc_hid_registered = false;',
112
+ // The API contract: get_report is required for every HID device type
113
+ // (hid_dev_register rejects NULL ops with -EINVAL), set_protocol is
114
+ // required for boot-subclass devices (both descriptors here), and
115
+ // set_report when an output report is declared. Minimal stubs — the
116
+ // verbs never read host->device state in v1.
117
+ 'static int __tc_hid_get_report(const struct device* dev, const uint8_t type, const uint8_t id, const uint16_t len, uint8_t* const buf) {',
118
+ ' (void)dev; (void)type; (void)id; (void)len; (void)buf;',
119
+ ' return -ENOTSUP;',
120
+ '}',
121
+ 'static int __tc_hid_set_report(const struct device* dev, const uint8_t type, const uint8_t id, const uint16_t len, const uint8_t* const buf) {',
122
+ ' (void)dev; (void)type; (void)id; (void)len; (void)buf;',
123
+ ' return 0;',
124
+ '}',
125
+ 'static void __tc_hid_set_protocol(const struct device* dev, const uint8_t protocol) {',
126
+ ' (void)dev; (void)protocol;',
127
+ '}',
128
+ 'static bool __tc_hid_ready = false;',
129
+ 'static void __tc_hid_iface_ready(const struct device* dev, const bool ready) {',
130
+ ' (void)dev;',
131
+ ' __tc_hid_ready = ready;',
132
+ '}',
133
+ 'static uint32_t __tc_hid_idle = 0;',
134
+ 'static void __tc_hid_set_idle(const struct device* dev, const uint8_t id, const uint32_t duration) {',
135
+ ' (void)dev; (void)id;',
136
+ ' __tc_hid_idle = duration;',
137
+ '}',
138
+ 'static uint32_t __tc_hid_get_idle(const struct device* dev, const uint8_t id) {',
139
+ ' (void)dev; (void)id;',
140
+ ' return __tc_hid_idle;',
141
+ '}',
142
+ 'static void __tc_hid_output_report(const struct device* dev, const uint16_t len, const uint8_t* const buf) {',
143
+ ' (void)dev; (void)len; (void)buf;',
144
+ '}',
145
+ 'static const struct hid_device_ops __tc_hid_ops = {',
146
+ ' .iface_ready = __tc_hid_iface_ready,',
147
+ ' .get_report = __tc_hid_get_report,',
148
+ ' .set_report = __tc_hid_set_report,',
149
+ ' .set_idle = __tc_hid_set_idle,',
150
+ ' .get_idle = __tc_hid_get_idle,',
151
+ ' .set_protocol = __tc_hid_set_protocol,',
152
+ ' .output_report = __tc_hid_output_report,',
153
+ '};',
154
+ 'static void __tc_hid_register(void) {',
155
+ ' if (__tc_hid_registered) { return; }',
156
+ ' __tc_hid_registered = true;',
157
+ ' int err = hid_device_register(__tc_hid_dev, __tc_hid_desc, sizeof(__tc_hid_desc), &__tc_hid_ops);',
158
+ ' if (err != 0) { printk("typecad-hal hid: register failed: %d\\n", err); return; }',
159
+ ' __tc_usbd_start();',
160
+ '}',
161
+ 'static void __tc_hid_submit(const uint8_t* rep, uint16_t len) {',
162
+ ' int err = hid_device_submit_report(__tc_hid_dev, len, rep);',
163
+ ' // -EACCES is the normal pre-enumeration race (submit before the',
164
+ ' // host configures the interface) — not worth a printk; every other',
165
+ ' // errno (e.g. the alignment error that silently ate reports once)',
166
+ ' // deserves to be loud.',
167
+ ' if (err != 0 && err != -EACCES) { printk("typecad-hal hid: submit failed: %d\\n", err); }',
168
+ '}',
169
+ '// CUTTLEFISH_HID_END',
170
+ ];
171
+ }
172
+ /**
173
+ * Resolve a HAL hid.* op to Zephyr C++.
174
+ * Returns `{ code }` for statement ops.
175
+ */
176
+ export function lowerHid(op, chip) {
177
+ if (!chip.usb) {
178
+ throw new Error(`framework-zephyr: \`${op.operation}\` needs a USB device stack, but this board's chip data declares no \`zephyr.usb\` ` +
179
+ `(controller + cdcInstances). Boards whose Zephyr DTS has no enabled UDC controller node ` +
180
+ `(zephyr_udc0) cannot provide USB HID.`);
181
+ }
182
+ const o = op;
183
+ switch (op.operation) {
184
+ case 'hid.kb_begin':
185
+ return { code: '__tc_hid_register();' };
186
+ case 'hid.kb_press': {
187
+ // Modifier tokens are compile-time bitmasks — a different emit than
188
+ // the usage-code slot logic (runtime expressions take the key path).
189
+ if (isHidModifierToken(String(o.key))) {
190
+ const mod = hidModifierMacro(String(o.key));
191
+ return {
192
+ code: `{ __tc_hid_register(); __tc_kb_report[0] = static_cast<uint8_t>(__tc_kb_report[0] | ${mod}); __tc_hid_submit(__tc_kb_report, 8U); }`,
193
+ };
194
+ }
195
+ const key = hidTokenToMacro(String(o.key), 'key');
196
+ return {
197
+ code: `{ __tc_hid_register(); uint8_t __tc_k = static_cast<uint8_t>(${key}); for (uint32_t __tc_slot = 2U; __tc_slot < 8U; ++__tc_slot) { if (__tc_kb_report[__tc_slot] == 0U) { __tc_kb_report[__tc_slot] = __tc_k; break; } } __tc_hid_submit(__tc_kb_report, 8U); }`,
198
+ };
199
+ }
200
+ case 'hid.kb_release': {
201
+ if (isHidModifierToken(String(o.key))) {
202
+ const mod = hidModifierMacro(String(o.key));
203
+ return {
204
+ code: `{ __tc_hid_register(); __tc_kb_report[0] = static_cast<uint8_t>(__tc_kb_report[0] & static_cast<uint8_t>(~${mod})); __tc_hid_submit(__tc_kb_report, 8U); }`,
205
+ };
206
+ }
207
+ const key = hidTokenToMacro(String(o.key), 'key');
208
+ return {
209
+ code: `{ __tc_hid_register(); uint8_t __tc_k = static_cast<uint8_t>(${key}); for (uint32_t __tc_slot = 2U; __tc_slot < 8U; ++__tc_slot) { if (__tc_kb_report[__tc_slot] == __tc_k) { __tc_kb_report[__tc_slot] = 0U; } } __tc_hid_submit(__tc_kb_report, 8U); }`,
210
+ };
211
+ }
212
+ case 'hid.kb_release_all':
213
+ return {
214
+ code: `{ __tc_hid_register(); for (uint32_t __tc_slot = 0U; __tc_slot < 8U; ++__tc_slot) { __tc_kb_report[__tc_slot] = 0U; } __tc_hid_submit(__tc_kb_report, 8U); }`,
215
+ };
216
+ case 'hid.mouse_begin':
217
+ return { code: '__tc_hid_register();' };
218
+ case 'hid.mouse_move': {
219
+ return {
220
+ code: `{ __tc_hid_register(); int32_t __tc_mx = static_cast<int32_t>(${o.dx}); int32_t __tc_my = static_cast<int32_t>(${o.dy}); int32_t __tc_mw = static_cast<int32_t>(${o.wheel}); if (__tc_mx > 127) { __tc_mx = 127; } if (__tc_mx < -127) { __tc_mx = -127; } if (__tc_my > 127) { __tc_my = 127; } if (__tc_my < -127) { __tc_my = -127; } if (__tc_mw > 127) { __tc_mw = 127; } if (__tc_mw < -127) { __tc_mw = -127; } __tc_mouse_report[1] = static_cast<uint8_t>(__tc_mx); __tc_mouse_report[2] = static_cast<uint8_t>(__tc_my); __tc_mouse_report[3] = static_cast<uint8_t>(__tc_mw); __tc_hid_submit(__tc_mouse_report, 4U); }`,
221
+ };
222
+ }
223
+ case 'hid.mouse_press':
224
+ case 'hid.mouse_release': {
225
+ const btn = hidTokenToMacro(String(o.button), 'button');
226
+ const opText = op.operation === 'hid.mouse_press' ? '|' : '&';
227
+ const mask = op.operation === 'hid.mouse_press'
228
+ ? btn
229
+ : `static_cast<uint8_t>(~${btn})`;
230
+ return {
231
+ code: `{ __tc_hid_register(); __tc_mouse_report[0] = static_cast<uint8_t>(__tc_mouse_report[0] ${opText} ${mask}); __tc_hid_submit(__tc_mouse_report, 4U); }`,
232
+ };
233
+ }
234
+ case 'hid.mouse_click': {
235
+ const btn = hidTokenToMacro(String(o.button), 'button');
236
+ return {
237
+ code: `{ __tc_hid_register(); __tc_mouse_report[0] = static_cast<uint8_t>(__tc_mouse_report[0] | ${btn}); __tc_hid_submit(__tc_mouse_report, 4U); __tc_mouse_report[0] = static_cast<uint8_t>(__tc_mouse_report[0] & static_cast<uint8_t>(~${btn})); __tc_hid_submit(__tc_mouse_report, 4U); }`,
238
+ };
239
+ }
240
+ default:
241
+ throw new Error(`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
242
+ `Open an issue or use rawCpp() to emit it manually.`);
243
+ }
244
+ }
@@ -5,6 +5,14 @@ import type { ZephyrChipDescriptor } from '../chips/types.js';
5
5
  * controller the program uses (gated on ctx.analysis.usesI2C).
6
6
  */
7
7
  export declare function i2cInitLines(chip: ZephyrChipDescriptor, controllerIndex: number): string[];
8
+ /**
9
+ * Emit one responder's state: receive ring (UART discipline — free-running
10
+ * head/tail, overflow-drop), response buffer with its served position, the
11
+ * two handler slots, the five i2c_target callbacks, and the config the ops'
12
+ * registration guard passes to i2c_target_register. Called from shimLines
13
+ * for each constructed responder (collectI2cResponders).
14
+ */
15
+ export declare function i2cResponderStateLines(chip: ZephyrChipDescriptor, controllerIndex: number, address: number, rx: number, tx: number): string[];
8
16
  /**
9
17
  * Resolve a HAL i2c.* op to Zephyr C++.
10
18
  * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
@@ -40,6 +40,106 @@ function speedForHz(hz) {
40
40
  return 'I2C_SPEED_FAST_PLUS';
41
41
  return 'I2C_SPEED_HIGH';
42
42
  }
43
+ // ── I2C responder (hal/i2c-responder.ts) — this board as an I2C target ────
44
+ //
45
+ // One state block per constructed responder (bus + address), the i2c_target
46
+ // vocabulary verbatim: the five driver callbacks behind two user-facing ones
47
+ // (onReceive fires at STOP with the receive ring's new byte count; onRequest
48
+ // fires at read start — the write() refill point). Registration
49
+ // (i2c_target_register) is once-guarded at the first op, against the
50
+ // controller handle the master-side init block emitted (__tc_i2c<N>_dev).
51
+ /** The C symbol prefix for one responder's state. */
52
+ function respPrefix(idx, address) {
53
+ return `__tc_i2cresp${idx}_a${address.toString(16)}`;
54
+ }
55
+ /** The once-guarded registration every responder op carries — the UART arm
56
+ * discipline: the first emitted call registers the address, so the
57
+ * callbacks installed early in setup make it answer from boot. An
58
+ * unsupported controller prints loudly (the CAN policy: -ENOSYS from a
59
+ * driver with no target mode must not be a silent empty responder). */
60
+ function respRegisterGuard(p, idx) {
61
+ return `{ if (!${p}_registered) { ${p}_registered = true; int __tc_err = i2c_target_register(__tc_i2c${idx}_dev, &${p}_cfg); if (__tc_err != 0) { printk("typecad-hal i2c responder: target_register failed: %d\\n", __tc_err); } } } `;
62
+ }
63
+ /**
64
+ * Emit one responder's state: receive ring (UART discipline — free-running
65
+ * head/tail, overflow-drop), response buffer with its served position, the
66
+ * two handler slots, the five i2c_target callbacks, and the config the ops'
67
+ * registration guard passes to i2c_target_register. Called from shimLines
68
+ * for each constructed responder (collectI2cResponders).
69
+ */
70
+ export function i2cResponderStateLines(chip, controllerIndex, address, rx, tx) {
71
+ const ctrl = chip.i2c?.controllers[controllerIndex];
72
+ if (!ctrl)
73
+ return [];
74
+ const p = respPrefix(controllerIndex, address);
75
+ return [
76
+ '// CUTTLEFISH_I2CRESP_BEGIN',
77
+ `static uint8_t ${p}_rx[${rx}];`,
78
+ `static volatile uint32_t ${p}_head = 0;`,
79
+ `static volatile uint32_t ${p}_tail = 0;`,
80
+ `static uint8_t ${p}_tx[${tx}];`,
81
+ `static volatile uint32_t ${p}_txlen = 0;`,
82
+ `static volatile uint32_t ${p}_txpos = 0;`,
83
+ `static void (*${p}_on_rx)(double) = NULL;`,
84
+ `static void (*${p}_on_rq)(void) = NULL;`,
85
+ // A controller write transaction: reset the ring, then take each byte.
86
+ // A byte arriving with a full ring is dropped — the embedded-honest
87
+ // answer (no unbounded buffering), same as the UART RX ring.
88
+ `static int ${p}_write_requested(struct i2c_target_config* cfg) {`,
89
+ ' (void)cfg;',
90
+ ` ${p}_head = 0;`,
91
+ ` ${p}_tail = 0;`,
92
+ ' return 0;',
93
+ '}',
94
+ `static int ${p}_write_received(struct i2c_target_config* cfg, uint8_t val) {`,
95
+ ' (void)cfg;',
96
+ ` if ((${p}_head - ${p}_tail) < ${rx}U) {`,
97
+ ` ${p}_rx[${p}_head % ${rx}U] = val;`,
98
+ ` ${p}_head++;`,
99
+ ' }',
100
+ ' return 0;',
101
+ '}',
102
+ // A controller read transaction: the user's refill point first, then the
103
+ // first byte. Past the buffer's end the controller reads 0xFF (the bus
104
+ // has no NACK for a too-long read; the value is the honest filler).
105
+ `static int ${p}_read_requested(struct i2c_target_config* cfg, uint8_t* val) {`,
106
+ ' (void)cfg;',
107
+ ` if (${p}_on_rq != NULL) { ${p}_on_rq(); }`,
108
+ ` ${p}_txpos = 1U;`,
109
+ ` *val = (${p}_txlen > 0U) ? ${p}_tx[0] : 0xFFU;`,
110
+ ' return 0;',
111
+ '}',
112
+ `static int ${p}_read_processed(struct i2c_target_config* cfg, uint8_t* val) {`,
113
+ ' (void)cfg;',
114
+ ` *val = (${p}_txpos < ${p}_txlen) ? ${p}_tx[${p}_txpos] : 0xFFU;`,
115
+ ` ${p}_txpos++;`,
116
+ ' return 0;',
117
+ '}',
118
+ // End of transaction: announce a completed controller write with the
119
+ // count now waiting in the ring (the Wire onReceive(len) semantic).
120
+ `static int ${p}_stop(struct i2c_target_config* cfg) {`,
121
+ ' (void)cfg;',
122
+ ` if ((${p}_on_rx != NULL) && (${p}_head > ${p}_tail)) {`,
123
+ ` ${p}_on_rx(static_cast<double>(${p}_head - ${p}_tail));`,
124
+ ' }',
125
+ ' return 0;',
126
+ '}',
127
+ `static const struct i2c_target_callbacks ${p}_cbs = {`,
128
+ ` .write_requested = ${p}_write_requested,`,
129
+ ` .read_requested = ${p}_read_requested,`,
130
+ ` .write_received = ${p}_write_received,`,
131
+ ` .read_processed = ${p}_read_processed,`,
132
+ ` .stop = ${p}_stop,`,
133
+ '};',
134
+ `static struct i2c_target_config ${p}_cfg = {`,
135
+ ' .flags = 0U,',
136
+ ` .address = ${address}U,`,
137
+ ` .callbacks = &${p}_cbs,`,
138
+ '};',
139
+ `static bool ${p}_registered = false;`,
140
+ '// CUTTLEFISH_I2CRESP_END',
141
+ ];
142
+ }
43
143
  /** The thin-device bus-speed preamble: apply the construction hz once
44
144
  * (i2c_configure with Zephyr's I2C_SPEED_SET tier mapping), or '' when the
45
145
  * target constructed without hz. Guarded per controller, so the cost after
@@ -91,6 +191,46 @@ export function lowerI2c(op, chip) {
91
191
  const arr = `static const uint8_t __tc_i2cw[] = { ${bytes.join(', ')} };`;
92
192
  return { code: `${pre} ${arr} (void)i2c_write(${p}_dev, __tc_i2cw, sizeof(__tc_i2cw), static_cast<uint16_t>(${o.address}));` };
93
193
  }
194
+ // ── I2C responder (hal/i2c-responder.ts) — target-mode ops ───────────
195
+ // Every op carries the once-guarded registration; the state it touches
196
+ // is the per-responder block i2cResponderStateLines emitted.
197
+ case 'i2c.resp_on_receive': {
198
+ const rp = respPrefix(idx, o.address);
199
+ return { code: `${respRegisterGuard(rp, idx)} ${rp}_on_rx = ${o.handler};` };
200
+ }
201
+ case 'i2c.resp_on_request': {
202
+ const rp = respPrefix(idx, o.address);
203
+ return { code: `${respRegisterGuard(rp, idx)} ${rp}_on_rq = ${o.handler};` };
204
+ }
205
+ case 'i2c.resp_available': {
206
+ const rp = respPrefix(idx, o.address);
207
+ return { expression: `({ ${respRegisterGuard(rp, idx)} (${rp}_head - ${rp}_tail); })` };
208
+ }
209
+ case 'i2c.resp_read': {
210
+ const rp = respPrefix(idx, o.address);
211
+ return { expression: `({ ${respRegisterGuard(rp, idx)} (${rp}_tail < ${rp}_head ? ${rp}_rx[(${rp}_tail)++ % ${o.rx}U] : -1); })` };
212
+ }
213
+ case 'i2c.resp_write': {
214
+ const rp = respPrefix(idx, o.address);
215
+ const bytes = o.bytes ?? [];
216
+ const isBuffer = bytes.length === 1 && typeof bytes[0] === 'string' && /^[A-Za-z_][A-Za-z0-9_]*$/.test(bytes[0]);
217
+ if (isBuffer) {
218
+ const name = bytes[0];
219
+ // A named user buffer: copy its storage into the response buffer,
220
+ // clamped to the constructed size; the length lands last so an
221
+ // in-flight read never sees a torn buffer.
222
+ return {
223
+ code: `${respRegisterGuard(rp, idx)} { ${rp}_txlen = 0U; for (uint32_t __tc_i = 0U; (__tc_i < sizeof(${name})) && (__tc_i < ${o.tx}U); ++__tc_i) { ${rp}_tx[__tc_i] = ${name}[__tc_i]; } ${rp}_txlen = (sizeof(${name}) < ${o.tx}U) ? static_cast<uint32_t>(sizeof(${name})) : ${o.tx}U; }`,
224
+ };
225
+ }
226
+ if (bytes.length > Number(o.tx)) {
227
+ throw new Error(`I2C responder write: the response buffer holds ${o.tx} bytes — got ${bytes.length}. Construct with a larger txBufferBytes.`);
228
+ }
229
+ const assigns = bytes
230
+ .map((b, i) => `${rp}_tx[${i}] = static_cast<uint8_t>(${b});`)
231
+ .join(' ');
232
+ return { code: `${respRegisterGuard(rp, idx)} { ${rp}_txlen = 0U; ${assigns} ${rp}_txlen = ${bytes.length}U; }` };
233
+ }
94
234
  default:
95
235
  throw new Error(`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
96
236
  `Open an issue or use rawCpp() to emit it manually.`);
@@ -0,0 +1,27 @@
1
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
2
+ import type { ZephyrChipDescriptor } from '../chips/types.js';
3
+ /** Which i2s.* verbs the program actually uses on a controller — the init
4
+ * block emits only the state those verbs reference (a write-only program
5
+ * must not carry an unused rx buffer: -Werror fails the build). */
6
+ export interface I2sUsage {
7
+ write: boolean;
8
+ read: boolean;
9
+ readAt: boolean;
10
+ }
11
+ /**
12
+ * Emit the per-controller I2S state: device handle, mem slab, TX/RX block
13
+ * buffers, and the lazy per-direction init helpers. Called from shimLines
14
+ * when the program uses i2s.* on this controller and the chip declares it.
15
+ * The once-flags live HERE (shim scope), not in the lowered call sites — a
16
+ * second write() statement must not reconfigure/re-start a running stream
17
+ * (i2s_configure is legal only before the first trigger).
18
+ */
19
+ export declare function i2sInitLines(chip: ZephyrChipDescriptor, controllerIndex: number, blockBytes: number, usage: I2sUsage): string[];
20
+ /**
21
+ * Resolve a HAL i2s.* op to Zephyr C++.
22
+ * Returns `{ code }` for write, `{ expression }` for read/readAt.
23
+ */
24
+ export declare function lowerI2s(op: HALOpIR, chip: ZephyrChipDescriptor): {
25
+ code?: string;
26
+ expression?: string;
27
+ };
@@ -0,0 +1,98 @@
1
+ // ---------------------------------------------------------------------------
2
+ // I2S lowering — Zephyr's i2s API over the harvested controller node
3
+ //
4
+ // The controller is addressed by its DT nodelabel (chip.i2s, the harvested
5
+ // i2s@ node — ESP32 I2S0/1). Each direction lazy-inits on first use (the
6
+ // PWM discipline): configure + trigger START once, then every write/read is
7
+ // one block. The shim owns the mem slab (2 blocks), the TX packing buffer,
8
+ // and the RX receive buffer; blockAt/readAt index the last received block.
9
+ // ---------------------------------------------------------------------------
10
+ /** The C variable names for an I2S controller's state. */
11
+ function prefix(idx) {
12
+ return `__tc_i2s${idx}`;
13
+ }
14
+ /**
15
+ * Emit the per-controller I2S state: device handle, mem slab, TX/RX block
16
+ * buffers, and the lazy per-direction init helpers. Called from shimLines
17
+ * when the program uses i2s.* on this controller and the chip declares it.
18
+ * The once-flags live HERE (shim scope), not in the lowered call sites — a
19
+ * second write() statement must not reconfigure/re-start a running stream
20
+ * (i2s_configure is legal only before the first trigger).
21
+ */
22
+ export function i2sInitLines(chip, controllerIndex, blockBytes, usage) {
23
+ const ctrl = chip.i2s?.controllers[controllerIndex];
24
+ if (!ctrl)
25
+ return [];
26
+ const p = prefix(controllerIndex);
27
+ // readAt rides the RX engine too (its lowering lazily starts RX like read
28
+ // does) — a readAt-only program still references the whole engine state.
29
+ const needsEngine = usage.write || usage.read || usage.readAt;
30
+ const lines = [
31
+ '// CUTTLEFISH_I2S_BEGIN',
32
+ `static const struct device* ${p}_dev = DEVICE_DT_GET(DT_NODELABEL(${ctrl.nodeLabel}));`,
33
+ ];
34
+ if (needsEngine) {
35
+ lines.push(`K_MEM_SLAB_DEFINE_STATIC(${p}_slab, ${blockBytes}, 2, 4);`, `K_MEM_SLAB_DEFINE_STATIC(${p}_rxslab, ${blockBytes}, 2, 4);`, `static void ${p}_init(uint32_t hz, uint8_t channels, uint8_t bits, size_t block_bytes) {`, ' struct i2s_config cfg = { 0 };', ' cfg.word_size = bits;', ' cfg.channels = channels;', ' cfg.format = I2S_FMT_DATA_FORMAT_I2S;', ' cfg.options = 0;', ' cfg.frame_clk_freq = hz;', ` cfg.mem_slab = &${p}_slab;`, ' cfg.block_size = block_bytes;', ' cfg.timeout = 200;', ` int err = i2s_configure(${p}_dev, I2S_DIR_TX, &cfg);`, ' if (err != 0) { printk("typecad-hal i2s: tx configure failed: %d\\n", err); }', ` cfg.mem_slab = &${p}_rxslab;`, ` err = i2s_configure(${p}_dev, I2S_DIR_RX, &cfg);`, ' if (err != 0) { printk("typecad-hal i2s: rx configure failed: %d\\n", err); }', '}', `static bool ${p}_init_done = false;`);
36
+ }
37
+ if (usage.write) {
38
+ lines.push(`static int16_t ${p}_txbuf[${blockBytes / 2}];`, `static bool ${p}_tx_started = false;`);
39
+ }
40
+ if (usage.read || usage.readAt) {
41
+ lines.push(`static int16_t ${p}_rxbuf[${blockBytes / 2}];`);
42
+ lines.push(`static bool ${p}_rx_started = false;`);
43
+ }
44
+ lines.push('// CUTTLEFISH_I2S_END');
45
+ return lines;
46
+ }
47
+ /**
48
+ * Resolve a HAL i2s.* op to Zephyr C++.
49
+ * Returns `{ code }` for write, `{ expression }` for read/readAt.
50
+ */
51
+ export function lowerI2s(op, chip) {
52
+ const o = op;
53
+ // DAC discipline: no harvested i2s@ node lowers to the honest comment.
54
+ if (!chip.i2s || chip.i2s.controllers.length === 0) {
55
+ return { code: `/* ${op.operation}: this board's chip data declares no I2S controller (no harvested i2s@ node) */` };
56
+ }
57
+ const idx = Math.min(Number(o.instance ?? 0), chip.i2s.controllers.length - 1);
58
+ const p = prefix(idx);
59
+ const hz = Number(o.hz ?? 16000);
60
+ const channels = Number(o.channels ?? 2);
61
+ const bits = Number(o.bits ?? 16);
62
+ const blockFrames = Number(o.blockFrames ?? 64);
63
+ const blockBytes = blockFrames * channels * (bits / 8);
64
+ switch (op.operation) {
65
+ case 'i2s.write': {
66
+ const samples = (o.samples ?? []);
67
+ const cap = blockFrames * channels;
68
+ if (samples.length > cap) {
69
+ throw new Error(`I2S write: one block carries at most ${cap} samples (${blockFrames} frames × ${channels} channels) — got ${samples.length}.`);
70
+ }
71
+ // Zero-pad to the full block; samples are 16-bit.
72
+ const writes = [];
73
+ for (let i = 0; i < cap; i++) {
74
+ const v = i < samples.length ? samples[i] : 0;
75
+ writes.push(`${p}_txbuf[${i}] = static_cast<int16_t>(${v});`);
76
+ }
77
+ return {
78
+ code: `{ if (!${p}_init_done) { ${p}_init_done = true; ${p}_init(${hz}U, ${channels}U, ${bits}U, ${blockBytes}U); } ${writes.join(' ')} int err = i2s_buf_write(${p}_dev, ${p}_txbuf, ${blockBytes}U); if (err != 0) { printk("typecad-hal i2s: write failed: %d\\n", err); } else { if (!${p}_tx_started) { ${p}_tx_started = true; err = i2s_trigger(${p}_dev, I2S_DIR_TX, I2S_TRIGGER_START); if (err != 0) { printk("typecad-hal i2s: tx start failed: %d\\n", err); } } } }`,
79
+ };
80
+ }
81
+ case 'i2s.read': {
82
+ return {
83
+ expression: `({ if (!${p}_init_done) { ${p}_init_done = true; ${p}_init(${hz}U, ${channels}U, ${bits}U, ${blockBytes}U); } if (!${p}_rx_started) { ${p}_rx_started = true; int err = i2s_trigger(${p}_dev, I2S_DIR_RX, I2S_TRIGGER_START); if (err != 0) { printk("typecad-hal i2s: rx start failed: %d\\n", err); } } size_t got = 0U; int err = i2s_buf_read(${p}_dev, ${p}_rxbuf, &got); if (err != 0) { printk("typecad-hal i2s: read failed: %d\\n", err); } static_cast<double>(${p}_rxbuf[0]); })`,
84
+ };
85
+ }
86
+ case 'i2s.read_at': {
87
+ // Same lazy RX start as read(): readAt indexes the last RECEIVED
88
+ // block, so the stream must be running — and a readAt-only program
89
+ // references (and therefore compiles in) the whole engine state.
90
+ return {
91
+ expression: `({ if (!${p}_init_done) { ${p}_init_done = true; ${p}_init(${hz}U, ${channels}U, ${bits}U, ${blockBytes}U); } if (!${p}_rx_started) { ${p}_rx_started = true; int err = i2s_trigger(${p}_dev, I2S_DIR_RX, I2S_TRIGGER_START); if (err != 0) { printk("typecad-hal i2s: rx start failed: %d\\n", err); } } int32_t at = static_cast<int32_t>(${o.index}); double v = 0.0; if (at >= 0 && static_cast<uint32_t>(at) < (sizeof(${p}_rxbuf) / sizeof(${p}_rxbuf[0]))) { v = static_cast<double>(${p}_rxbuf[at]); } v; })`,
92
+ };
93
+ }
94
+ default:
95
+ throw new Error(`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
96
+ `Open an issue or use rawCpp() to emit it manually.`);
97
+ }
98
+ }
@@ -21,7 +21,15 @@ import { lowerFs } from './fs.js';
21
21
  import { lowerSensor } from './sensor.js';
22
22
  import { lowerHwtimer, lowerCounter } from './hwtimer.js';
23
23
  import { lowerThread } from './thread.js';
24
- export { lowerGpio, lowerTiming, lowerAdc, lowerPwm, lowerI2c, lowerSpi, lowerUart, lowerUsb, lowerInterrupt, lowerWdt, lowerBle, lowerWifi, lowerHttp, lowerMqtt, lowerPreferences, lowerBoard, lowerRandom, lowerDac, lowerFs, lowerHwtimer, lowerCounter, lowerSensor, lowerThread, };
24
+ import { lowerTrace } from './trace.js';
25
+ import { lowerStrip } from './strip.js';
26
+ import { lowerHid } from './hid.js';
27
+ import { lowerMatrix } from './matrix.js';
28
+ import { lowerPower } from './power.js';
29
+ import { lowerClock } from './clock.js';
30
+ import { lowerCan } from './can.js';
31
+ import { lowerI2s } from './i2s.js';
32
+ export { lowerGpio, lowerTiming, lowerAdc, lowerPwm, lowerI2c, lowerSpi, lowerUart, lowerUsb, lowerInterrupt, lowerWdt, lowerBle, lowerWifi, lowerHttp, lowerMqtt, lowerPreferences, lowerBoard, lowerRandom, lowerDac, lowerFs, lowerHwtimer, lowerCounter, lowerSensor, lowerThread, lowerTrace, lowerStrip, lowerHid, lowerMatrix, lowerPower, lowerClock, lowerCan, lowerI2s, };
25
33
  /**
26
34
  * Lower a HAL op to Zephyr C++. Returns undefined for unsupported categories
27
35
  * (mirrors lowerHalOp in framework-esp32/src/lowering/index.ts).
@@ -38,7 +38,15 @@ import { lowerFs } from './fs.js';
38
38
  import { lowerSensor } from './sensor.js';
39
39
  import { lowerHwtimer, lowerCounter } from './hwtimer.js';
40
40
  import { lowerThread } from './thread.js';
41
- export { lowerGpio, lowerTiming, lowerAdc, lowerPwm, lowerI2c, lowerSpi, lowerUart, lowerUsb, lowerInterrupt, lowerWdt, lowerBle, lowerWifi, lowerHttp, lowerMqtt, lowerPreferences, lowerBoard, lowerRandom, lowerDac, lowerFs, lowerHwtimer, lowerCounter, lowerSensor, lowerThread, };
41
+ import { lowerTrace } from './trace.js';
42
+ import { lowerStrip } from './strip.js';
43
+ import { lowerHid } from './hid.js';
44
+ import { lowerMatrix } from './matrix.js';
45
+ import { lowerPower } from './power.js';
46
+ import { lowerClock } from './clock.js';
47
+ import { lowerCan } from './can.js';
48
+ import { lowerI2s } from './i2s.js';
49
+ export { lowerGpio, lowerTiming, lowerAdc, lowerPwm, lowerI2c, lowerSpi, lowerUart, lowerUsb, lowerInterrupt, lowerWdt, lowerBle, lowerWifi, lowerHttp, lowerMqtt, lowerPreferences, lowerBoard, lowerRandom, lowerDac, lowerFs, lowerHwtimer, lowerCounter, lowerSensor, lowerThread, lowerTrace, lowerStrip, lowerHid, lowerMatrix, lowerPower, lowerClock, lowerCan, lowerI2s, };
42
50
  /**
43
51
  * Lower a HAL op to Zephyr C++. Returns undefined for unsupported categories
44
52
  * (mirrors lowerHalOp in framework-esp32/src/lowering/index.ts).
@@ -94,6 +102,22 @@ export function lowerHalOp(op) {
94
102
  return lowerSensor(op);
95
103
  if (op.operation.startsWith('thread.'))
96
104
  return lowerThread(op);
105
+ if (op.operation.startsWith('trace.'))
106
+ return lowerTrace(op);
107
+ if (op.operation.startsWith('strip.'))
108
+ return lowerStrip(op, chip);
109
+ if (op.operation.startsWith('hid.'))
110
+ return lowerHid(op, chip);
111
+ if (op.operation.startsWith('matrix.'))
112
+ return lowerMatrix(op);
113
+ if (op.operation.startsWith('power.'))
114
+ return lowerPower(op, chip);
115
+ if (op.operation.startsWith('clock.'))
116
+ return lowerClock(op);
117
+ if (op.operation.startsWith('can.'))
118
+ return lowerCan(op, chip);
119
+ if (op.operation.startsWith('i2s.'))
120
+ return lowerI2s(op, chip);
97
121
  // raw / snprintf.emit / display.* / ... — not lowered by this
98
122
  // framework. Return undefined so the transpiler falls back and the manifest
99
123
  // validator confirms the unsupported declaration.
@@ -0,0 +1,15 @@
1
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
2
+ /**
3
+ * Emit the matrix trampoline state. Called from shimLines when the program
4
+ * uses matrix.* — the listener targets the synthesized tc_matrix node, the
5
+ * handler pointer is assigned by the on_key lowering.
6
+ */
7
+ export declare function matrixInitLines(): string[];
8
+ /**
9
+ * Resolve a HAL matrix.* op to Zephyr C++.
10
+ * Returns `{ code }` for statement ops.
11
+ */
12
+ export declare function lowerMatrix(op: HALOpIR): {
13
+ code?: string;
14
+ expression?: string;
15
+ };