OR-1 dataflow CPU sketch
1import type {
2 SimEventJSON, SystemState, PEState, SMState, SMCellState,
3 MatchingEntry,
4} from "./types";
5import { rp } from "./palette";
6
7type EventFilter = {
8 readonly component: string | null;
9 readonly eventType: string | null;
10};
11
12type EventLogOptions = {
13 readonly container: HTMLElement;
14 readonly maxEvents: number;
15};
16
17type StateInspectorOptions = {
18 readonly container: HTMLElement;
19};
20
21/**
22 * Module-level state for the event log panel.
23 *
24 * `allEvents` accumulates all events; `currentFilter` controls visible subset.
25 * setEventFilter() updates the filter and re-renders with stored events.
26 */
27let currentFilter: EventFilter = { component: null, eventType: null };
28let allEvents: ReadonlyArray<SimEventJSON> = [];
29
30const eventTypeColors: Record<string, string> = {
31 Matched: rp.foam,
32 Executed: rp.pine,
33 TokenReceived: rp.iris,
34 CellWritten: rp.gold,
35 DeferredRead: rp.rose,
36 Emitted: rp.love,
37 TokenStored: rp.gold,
38};
39
40function formatTime(time: number): string {
41 return `[${time.toFixed(3)}]`;
42}
43
44function renderEventEntry(event: SimEventJSON): HTMLElement {
45 const entry = document.createElement("div");
46 entry.className = "event-entry";
47
48 entry.addEventListener("mouseenter", () => {
49 entry.style.backgroundColor = rp.overlay;
50 });
51 entry.addEventListener("mouseleave", () => {
52 entry.style.backgroundColor = "transparent";
53 });
54
55 const timeSpan = document.createElement("span");
56 timeSpan.textContent = formatTime(event.time);
57 timeSpan.style.color = rp.foam;
58 entry.appendChild(timeSpan);
59
60 entry.appendChild(document.createTextNode(" "));
61
62 const componentSpan = document.createElement("span");
63 componentSpan.textContent = event.component;
64 componentSpan.style.color = rp.gold;
65 entry.appendChild(componentSpan);
66
67 entry.appendChild(document.createTextNode(": "));
68
69 const eventTypeSpan = document.createElement("span");
70 eventTypeSpan.textContent = event.type;
71 eventTypeSpan.style.color = eventTypeColors[event.type] || rp.subtle;
72 entry.appendChild(eventTypeSpan);
73
74 if (Object.keys(event.details).length > 0) {
75 entry.appendChild(document.createTextNode(" — "));
76 const detailsSpan = document.createElement("span");
77 detailsSpan.textContent = JSON.stringify(event.details);
78 detailsSpan.style.color = rp.muted;
79 entry.appendChild(detailsSpan);
80 }
81
82 entry.addEventListener("click", () => {
83 const evt = new CustomEvent("event-selected", {
84 detail: event,
85 bubbles: true,
86 });
87 entry.dispatchEvent(evt);
88 });
89
90 (entry as any)._eventData = event;
91
92 return entry;
93}
94
95function eventMatchesFilter(event: SimEventJSON, filter: EventFilter): boolean {
96 if (filter.component !== null && event.component !== filter.component) {
97 return false;
98 }
99 if (filter.eventType !== null && event.type !== filter.eventType) {
100 return false;
101 }
102 return true;
103}
104
105function clearEventLog(): void {
106 allEvents = [];
107}
108
109function updateEventLog(
110 events: ReadonlyArray<SimEventJSON>,
111 options: EventLogOptions
112): void {
113 if (events.length > 0) {
114 allEvents = [...allEvents, ...events];
115 }
116
117 options.container.innerHTML = "";
118
119 const visibleEvents = Array.from(allEvents).slice(-options.maxEvents);
120
121 for (const event of visibleEvents) {
122 if (eventMatchesFilter(event, currentFilter)) {
123 const entry = renderEventEntry(event);
124 options.container.appendChild(entry);
125 }
126 }
127
128 options.container.scrollTop = options.container.scrollHeight;
129}
130
131function setEventFilter(
132 filter: EventFilter,
133 logContainer: HTMLElement | null
134): void {
135 currentFilter = filter;
136 if (logContainer) {
137 const maxEvents = 1000;
138 updateEventLog(allEvents, { container: logContainer, maxEvents });
139 }
140}
141
142function renderPEState(peId: string, state: PEState): HTMLElement {
143 const section = document.createElement("details");
144 section.style.marginBottom = "12px";
145
146 const summary = document.createElement("summary");
147 summary.textContent = `PE ${peId}`;
148 summary.style.cursor = "pointer";
149 summary.style.fontWeight = "bold";
150 summary.style.color = rp.foam;
151 section.appendChild(summary);
152
153 const content = document.createElement("div");
154 content.style.paddingLeft = "16px";
155 content.style.fontFamily = "monospace";
156 content.style.fontSize = "11px";
157
158 // IRAM entries
159 const iramDiv = document.createElement("div");
160 iramDiv.style.marginBottom = "8px";
161 const iramLabel = document.createElement("strong");
162 iramLabel.textContent = "IRAM:";
163 iramLabel.style.color = rp.pine;
164 iramDiv.appendChild(iramLabel);
165 iramDiv.appendChild(document.createElement("br"));
166
167 const iramEntries = Object.entries(state.iram || {});
168 if (iramEntries.length === 0) {
169 const empty = document.createElement("span");
170 empty.textContent = "(empty)";
171 empty.style.color = rp.muted;
172 iramDiv.appendChild(empty);
173 } else {
174 for (const [offset, instr] of iramEntries) {
175 const line = document.createElement("div");
176 line.textContent = ` [${offset}]: ${JSON.stringify(instr)}`;
177 line.style.color = rp.subtle;
178 iramDiv.appendChild(line);
179 }
180 }
181 content.appendChild(iramDiv);
182
183 // Matching store
184 const msDiv = document.createElement("div");
185 msDiv.style.marginBottom = "8px";
186 const msLabel = document.createElement("strong");
187 msLabel.textContent = "Matching Store:";
188 msLabel.style.color = rp.pine;
189 msDiv.appendChild(msLabel);
190 msDiv.appendChild(document.createElement("br"));
191
192 const matchingStore = state.matching_store || [];
193 if (matchingStore.length === 0) {
194 const empty = document.createElement("span");
195 empty.textContent = "(empty)";
196 empty.style.color = rp.muted;
197 msDiv.appendChild(empty);
198 } else {
199 for (let ctx = 0; ctx < matchingStore.length; ctx++) {
200 const ctx_slots = matchingStore[ctx];
201 if (!ctx_slots || ctx_slots.length === 0) continue;
202
203 const ctxDiv = document.createElement("div");
204 ctxDiv.textContent = ` ctx[${ctx}]:`;
205 ctxDiv.style.color = rp.gold;
206 msDiv.appendChild(ctxDiv);
207
208 for (let offset = 0; offset < ctx_slots.length; offset++) {
209 const entry = ctx_slots[offset];
210 if (!entry.occupied) continue;
211
212 const entryDiv = document.createElement("div");
213 entryDiv.style.paddingLeft = "16px";
214 const portStr = entry.port || "—";
215 const dataStr = entry.data !== null ? entry.data.toString() : "—";
216 entryDiv.textContent = `[${offset}]: data=${dataStr}, port=${portStr}`;
217 entryDiv.style.color = rp.subtle;
218 msDiv.appendChild(entryDiv);
219 }
220 }
221 }
222 content.appendChild(msDiv);
223
224 // Gen counters
225 const genDiv = document.createElement("div");
226 genDiv.style.marginBottom = "8px";
227 const genLabel = document.createElement("strong");
228 genLabel.textContent = "Gen Counters:";
229 genLabel.style.color = rp.pine;
230 genDiv.appendChild(genLabel);
231 genDiv.appendChild(document.createElement("br"));
232
233 const genCounters = state.gen_counters || [];
234 if (genCounters.length === 0) {
235 const empty = document.createElement("span");
236 empty.textContent = "(none)";
237 empty.style.color = rp.muted;
238 genDiv.appendChild(empty);
239 } else {
240 for (let i = 0; i < genCounters.length; i++) {
241 const line = document.createElement("div");
242 line.textContent = ` ctx[${i}]: ${genCounters[i]}`;
243 line.style.color = rp.subtle;
244 genDiv.appendChild(line);
245 }
246 }
247 content.appendChild(genDiv);
248
249 // Input queue and output log
250 const statsDiv = document.createElement("div");
251 const queueSpan = document.createElement("span");
252 queueSpan.textContent = `Input Queue: ${state.input_queue_depth}`;
253 queueSpan.style.color = rp.subtle;
254 statsDiv.appendChild(queueSpan);
255 statsDiv.appendChild(document.createElement("br"));
256
257 const outputSpan = document.createElement("span");
258 outputSpan.textContent = `Output Count: ${state.output_count}`;
259 outputSpan.style.color = rp.subtle;
260 statsDiv.appendChild(outputSpan);
261 content.appendChild(statsDiv);
262
263 section.appendChild(content);
264 return section;
265}
266
267function renderSMState(smId: string, state: SMState): HTMLElement {
268 const section = document.createElement("details");
269 section.style.marginBottom = "12px";
270
271 const summary = document.createElement("summary");
272 summary.textContent = `SM ${smId}`;
273 summary.style.cursor = "pointer";
274 summary.style.fontWeight = "bold";
275 summary.style.color = rp.gold;
276 section.appendChild(summary);
277
278 const content = document.createElement("div");
279 content.style.paddingLeft = "16px";
280 content.style.fontFamily = "monospace";
281 content.style.fontSize = "11px";
282
283 // Cells
284 const cellsDiv = document.createElement("div");
285 cellsDiv.style.marginBottom = "8px";
286 const cellsLabel = document.createElement("strong");
287 cellsLabel.textContent = "Cells:";
288 cellsLabel.style.color = rp.pine;
289 cellsDiv.appendChild(cellsLabel);
290 cellsDiv.appendChild(document.createElement("br"));
291
292 const cells = state.cells || {};
293 const cellAddrs = Object.keys(cells).filter(addr => {
294 const cell = (cells as any)[addr];
295 return cell && cell.presence !== "EMPTY";
296 });
297
298 if (cellAddrs.length === 0) {
299 const empty = document.createElement("span");
300 empty.textContent = "(all empty)";
301 empty.style.color = rp.muted;
302 cellsDiv.appendChild(empty);
303 } else {
304 for (const addr of cellAddrs) {
305 const cell = (cells as any)[addr];
306 if (!cell) continue;
307
308 const line = document.createElement("div");
309 const dataStr = cell.data_l !== null ? cell.data_l : (cell.data_r !== null ? cell.data_r : "—");
310 line.textContent = ` [${addr}]: ${cell.presence} = ${dataStr}`;
311 line.style.color = rp.subtle;
312 cellsDiv.appendChild(line);
313 }
314 }
315 content.appendChild(cellsDiv);
316
317 // Deferred read
318 if (state.deferred_read) {
319 const deferredDiv = document.createElement("div");
320 deferredDiv.style.marginBottom = "8px";
321 const deferredLabel = document.createElement("strong");
322 deferredLabel.textContent = "Deferred Read:";
323 deferredLabel.style.color = rp.iris;
324 deferredDiv.appendChild(deferredLabel);
325 deferredDiv.appendChild(document.createElement("br"));
326
327 const drLine = document.createElement("div");
328 drLine.textContent = ` cell_addr: ${state.deferred_read.cell_addr}`;
329 drLine.style.color = rp.subtle;
330 deferredDiv.appendChild(drLine);
331
332 content.appendChild(deferredDiv);
333 }
334
335 // T0 store and input queue
336 const statsDiv = document.createElement("div");
337 const t0Span = document.createElement("span");
338 t0Span.textContent = `T0 Store Size: ${state.t0_store_size}`;
339 t0Span.style.color = rp.subtle;
340 statsDiv.appendChild(t0Span);
341 statsDiv.appendChild(document.createElement("br"));
342
343 const queueSpan = document.createElement("span");
344 queueSpan.textContent = `Input Queue: ${state.input_queue_depth}`;
345 queueSpan.style.color = rp.subtle;
346 statsDiv.appendChild(queueSpan);
347 content.appendChild(statsDiv);
348
349 section.appendChild(content);
350 return section;
351}
352
353function updateStateInspector(
354 state: SystemState,
355 options: StateInspectorOptions
356): void {
357 options.container.innerHTML = "";
358
359 const scrollContainer = document.createElement("div");
360 scrollContainer.style.overflowY = "auto";
361 scrollContainer.style.height = "100%";
362 scrollContainer.style.paddingRight = "8px";
363
364 // PE section
365 const peSection = document.createElement("div");
366 peSection.style.marginBottom = "16px";
367
368 const peTitle = document.createElement("h3");
369 peTitle.textContent = "Processing Elements";
370 peTitle.style.color = rp.foam;
371 peTitle.style.marginTop = "0";
372 peTitle.style.marginBottom = "8px";
373 peTitle.style.fontSize = "14px";
374 peSection.appendChild(peTitle);
375
376 const pes = state.pes || {};
377 if (Object.keys(pes).length === 0) {
378 const empty = document.createElement("span");
379 empty.textContent = "(none)";
380 empty.style.color = rp.muted;
381 empty.style.fontSize = "11px";
382 peSection.appendChild(empty);
383 } else {
384 for (const [peId, peState] of Object.entries(pes)) {
385 peSection.appendChild(renderPEState(peId, peState));
386 }
387 }
388 scrollContainer.appendChild(peSection);
389
390 // SM section
391 const smSection = document.createElement("div");
392
393 const smTitle = document.createElement("h3");
394 smTitle.textContent = "Structure Memories";
395 smTitle.style.color = rp.gold;
396 smTitle.style.marginTop = "0";
397 smTitle.style.marginBottom = "8px";
398 smTitle.style.fontSize = "14px";
399 smSection.appendChild(smTitle);
400
401 const sms = state.sms || {};
402 if (Object.keys(sms).length === 0) {
403 const empty = document.createElement("span");
404 empty.textContent = "(none)";
405 empty.style.color = rp.muted;
406 empty.style.fontSize = "11px";
407 smSection.appendChild(empty);
408 } else {
409 for (const [smId, smState] of Object.entries(sms)) {
410 smSection.appendChild(renderSMState(smId, smState));
411 }
412 }
413 scrollContainer.appendChild(smSection);
414
415 options.container.appendChild(scrollContainer);
416}
417
418function displayNodeMatchingStore(
419 peId: number,
420 ctx: number,
421 offset: number,
422 entry: MatchingEntry,
423 container: HTMLElement
424): void {
425 const detailSection = document.createElement("div");
426 detailSection.id = "node-detail";
427 detailSection.style.marginTop = "16px";
428 detailSection.style.padding = "12px";
429 detailSection.style.backgroundColor = rp.overlay;
430 detailSection.style.border = `2px solid ${rp.foam}`;
431 detailSection.style.borderRadius = "4px";
432
433 const title = document.createElement("h4");
434 title.textContent = `Selected Node: PE${peId} IRAM[${offset}]`;
435 title.style.color = rp.foam;
436 title.style.margin = "0 0 8px 0";
437 title.style.fontSize = "13px";
438 detailSection.appendChild(title);
439
440 const contentDiv = document.createElement("div");
441 contentDiv.style.fontFamily = "monospace";
442 contentDiv.style.fontSize = "11px";
443
444 const statusDiv = document.createElement("div");
445 statusDiv.style.marginBottom = "8px";
446
447 const statusLabel = document.createElement("span");
448 statusLabel.textContent = "Status: ";
449 statusLabel.style.color = rp.pine;
450 statusDiv.appendChild(statusLabel);
451
452 const statusValue = document.createElement("span");
453 statusValue.textContent = entry.occupied ? "OCCUPIED" : "EMPTY";
454 statusValue.style.color = entry.occupied ? rp.gold : rp.muted;
455 statusDiv.appendChild(statusValue);
456
457 contentDiv.appendChild(statusDiv);
458
459 if (entry.occupied) {
460 const dataDiv = document.createElement("div");
461 dataDiv.style.marginBottom = "8px";
462
463 const dataLabel = document.createElement("span");
464 dataLabel.textContent = "Data: ";
465 dataLabel.style.color = rp.pine;
466 dataDiv.appendChild(dataLabel);
467
468 const dataValue = document.createElement("span");
469 dataValue.textContent = entry.data !== null ? entry.data.toString() : "(null)";
470 dataValue.style.color = rp.gold;
471 dataDiv.appendChild(dataValue);
472
473 contentDiv.appendChild(dataDiv);
474
475 const portDiv = document.createElement("div");
476
477 const portLabel = document.createElement("span");
478 portLabel.textContent = "Waiting Port: ";
479 portLabel.style.color = rp.pine;
480 portDiv.appendChild(portLabel);
481
482 const portValue = document.createElement("span");
483 portValue.textContent = entry.port || "(none)";
484 portValue.style.color = entry.port ? rp.foam : rp.muted;
485 portDiv.appendChild(portValue);
486
487 contentDiv.appendChild(portDiv);
488 }
489
490 detailSection.appendChild(contentDiv);
491
492 const existing = container.querySelector("#node-detail");
493 if (existing) {
494 existing.remove();
495 }
496
497 const scrollContainer = container.querySelector("div");
498 if (scrollContainer) {
499 scrollContainer.insertBefore(detailSection, scrollContainer.firstChild);
500 } else {
501 container.appendChild(detailSection);
502 }
503}
504
505function displaySMNodeState(
506 smId: number,
507 state: SMState,
508 container: HTMLElement
509): void {
510 const detailSection = document.createElement("div");
511 detailSection.id = "node-detail";
512 detailSection.style.marginTop = "16px";
513 detailSection.style.padding = "12px";
514 detailSection.style.backgroundColor = rp.overlay;
515 detailSection.style.border = `2px solid ${rp.gold}`;
516 detailSection.style.borderRadius = "4px";
517
518 const title = document.createElement("h4");
519 title.textContent = `Selected: SM ${smId}`;
520 title.style.color = rp.gold;
521 title.style.margin = "0 0 8px 0";
522 title.style.fontSize = "13px";
523 detailSection.appendChild(title);
524
525 const contentDiv = document.createElement("div");
526 contentDiv.style.fontFamily = "monospace";
527 contentDiv.style.fontSize = "11px";
528
529 // Cells
530 const cells = state.cells || {};
531 const cellAddrs = Object.keys(cells).sort((a, b) => parseInt(a) - parseInt(b));
532 const nonEmptyCells = cellAddrs.filter(addr => {
533 const cell = (cells as any)[addr];
534 return cell && cell.presence !== "EMPTY";
535 });
536
537 if (nonEmptyCells.length === 0) {
538 const emptyDiv = document.createElement("div");
539 emptyDiv.style.marginBottom = "8px";
540 const label = document.createElement("strong");
541 label.textContent = "Cells: ";
542 label.style.color = rp.pine;
543 emptyDiv.appendChild(label);
544 const value = document.createElement("span");
545 value.textContent = "(all empty)";
546 value.style.color = rp.muted;
547 emptyDiv.appendChild(value);
548 contentDiv.appendChild(emptyDiv);
549 } else {
550 const cellsHeader = document.createElement("strong");
551 cellsHeader.textContent = "Cells:";
552 cellsHeader.style.color = rp.pine;
553 contentDiv.appendChild(cellsHeader);
554 contentDiv.appendChild(document.createElement("br"));
555
556 for (const addr of nonEmptyCells) {
557 const cell = (cells as any)[addr] as SMCellState;
558 const line = document.createElement("div");
559 line.style.paddingLeft = "8px";
560 line.style.marginBottom = "2px";
561
562 const addrSpan = document.createElement("span");
563 addrSpan.textContent = `[${addr}] `;
564 addrSpan.style.color = rp.gold;
565 line.appendChild(addrSpan);
566
567 const presSpan = document.createElement("span");
568 presSpan.textContent = cell.presence;
569 presSpan.style.color = cell.presence === "FULL" ? rp.pine
570 : cell.presence === "WAITING" ? rp.iris
571 : cell.presence === "RESERVED" ? rp.gold
572 : rp.muted;
573 line.appendChild(presSpan);
574
575 if (cell.data_l !== null) {
576 const dataSpan = document.createElement("span");
577 dataSpan.textContent = ` L=${cell.data_l}`;
578 dataSpan.style.color = rp.subtle;
579 line.appendChild(dataSpan);
580 }
581 if (cell.data_r !== null) {
582 const dataSpan = document.createElement("span");
583 dataSpan.textContent = ` R=${cell.data_r}`;
584 dataSpan.style.color = rp.subtle;
585 line.appendChild(dataSpan);
586 }
587
588 contentDiv.appendChild(line);
589 }
590 }
591
592 // Deferred read
593 if (state.deferred_read) {
594 const drDiv = document.createElement("div");
595 drDiv.style.marginTop = "8px";
596 const drLabel = document.createElement("strong");
597 drLabel.textContent = "Deferred Read: ";
598 drLabel.style.color = rp.iris;
599 drDiv.appendChild(drLabel);
600 const drValue = document.createElement("span");
601 drValue.textContent = `cell[${state.deferred_read.cell_addr}]`;
602 drValue.style.color = rp.subtle;
603 drDiv.appendChild(drValue);
604 contentDiv.appendChild(drDiv);
605 }
606
607 // T0 store
608 const t0Div = document.createElement("div");
609 t0Div.style.marginTop = "8px";
610 const t0Label = document.createElement("span");
611 t0Label.textContent = `T0 Store: ${state.t0_store_size} entries`;
612 t0Label.style.color = rp.muted;
613 t0Div.appendChild(t0Label);
614 contentDiv.appendChild(t0Div);
615
616 // Input queue
617 const queueDiv = document.createElement("div");
618 const queueLabel = document.createElement("span");
619 queueLabel.textContent = `Input Queue: ${state.input_queue_depth}`;
620 queueLabel.style.color = rp.muted;
621 queueDiv.appendChild(queueLabel);
622 contentDiv.appendChild(queueDiv);
623
624 detailSection.appendChild(contentDiv);
625
626 const existing = container.querySelector("#node-detail");
627 if (existing) {
628 existing.remove();
629 }
630
631 const scrollContainer = container.querySelector("div");
632 if (scrollContainer) {
633 scrollContainer.insertBefore(detailSection, scrollContainer.firstChild);
634 } else {
635 container.appendChild(detailSection);
636 }
637}
638
639export type {
640 EventFilter, EventLogOptions, StateInspectorOptions,
641};
642
643export {
644 clearEventLog, updateEventLog, setEventFilter, updateStateInspector, displayNodeMatchingStore, displaySMNodeState,
645};