OR-1 dataflow CPU sketch
at main 645 lines 20 kB view raw
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};