catalyst-n1 / sdk /visualize_async.py
mrwabbit's picture
Initial upload: Catalyst N1 open source neuromorphic processor RTL
e4cdd5f verified
"""Visualize async vs sync mode β€” the key P12 feature."""
import sys
sys.path.insert(0, r"C:\Users\mrwab\neuromorphic-chip\sdk")
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import matplotlib.patches as mpatches
import numpy as np
from collections import defaultdict
import neurocore as nc
from neurocore.result import RunResult
from neurocore.constants import NEURONS_PER_CORE
BG = "#0a0a1a"
PANEL = "#0f1029"
TEXT = "#e0e0e0"
CYAN = "#00ffcc"
RED = "#ff6b6b"
GOLD = "#ffd93d"
BLUE = "#6bcfff"
PURPLE = "#c084fc"
GREEN = "#2ed573"
def run_chain(async_mode):
net = nc.Network()
pops = []
for i in range(8):
p = net.population(1, params={"threshold": 100, "leak": 0, "refrac": 1},
label=f"N{i}")
pops.append(p)
for i in range(7):
net.connect(pops[i], pops[i+1], topology="all_to_all", weight=200)
sim = nc.Simulator()
sim.deploy(net)
sim.set_learning(async_mode=async_mode)
trains = defaultdict(list)
total = 0
for t in range(12):
if t == 0:
sim.inject(pops[0], current=200)
result = sim.run(1)
total += result.total_spikes
for gid, times in result.spike_trains.items():
trains[gid].extend([t])
return trains, total, sim._compiled.placement, pops
sync_trains, sync_total, placement, pops = run_chain(False)
async_trains, async_total, _, _ = run_chain(True)
def run_ei(async_mode, timesteps=150):
net = nc.Network()
exc = net.population(64, params={"threshold": 500, "leak": 2, "refrac": 2}, label="Excitatory")
inh = net.population(16, params={"threshold": 400, "leak": 2, "refrac": 2}, label="Inhibitory")
net.connect(exc, exc, topology="random_sparse", p=0.15, weight=300, seed=42)
net.connect(exc, inh, topology="fixed_fan_out", fan_out=16, weight=250, seed=42)
net.connect(inh, exc, topology="fixed_fan_out", fan_out=32, weight=-200, seed=42)
sim = nc.Simulator()
sim.deploy(net)
sim.set_learning(async_mode=async_mode)
trains = defaultdict(list)
counts = []
total = 0
for t in range(timesteps):
sim.inject(exc[:16], current=600)
result = sim.run(1)
total += result.total_spikes
counts.append(result.total_spikes)
for gid, times in result.spike_trains.items():
trains[gid].extend([t])
return dict(trains), counts, total, sim._compiled.placement, exc, inh
sync_ei_trains, sync_ei_counts, sync_ei_total, ei_place, exc, inh = run_ei(False)
async_ei_trains, async_ei_counts, async_ei_total, _, _, _ = run_ei(True)
fig = plt.figure(figsize=(22, 18), facecolor=BG)
fig.suptitle("NEUROCORE β€” Async Event-Driven Mode (Phase 12 GALS)",
fontsize=20, color=CYAN, fontweight="bold", fontfamily="monospace", y=0.98)
fig.text(0.5, 0.955, "Togglable via set_learning(async_mode=True) | "
"Cores fire only on pending spikes | Quiescence detection ends timestep",
ha="center", fontsize=9, color="#666", fontfamily="monospace")
gs = gridspec.GridSpec(3, 2, figure=fig, hspace=0.32, wspace=0.25,
left=0.05, right=0.96, top=0.93, bottom=0.05)
ax1 = fig.add_subplot(gs[0, 0])
ax1.set_facecolor(PANEL)
ax1.set_title("SYNC Mode β€” 8-Neuron Chain", color=TEXT, fontsize=12,
fontfamily="monospace", pad=10)
for gid, times in sync_trains.items():
neuron = gid % NEURONS_PER_CORE
ax1.scatter(times, [neuron] * len(times), s=120, c=CYAN, marker="|", linewidths=2.5)
for t in times:
ax1.annotate(f"N{neuron}", (t + 0.15, neuron), fontsize=7, color="#888",
fontfamily="monospace")
ax1.set_xlabel("Timestep", color=TEXT, fontsize=9, fontfamily="monospace")
ax1.set_ylabel("Neuron", color=TEXT, fontsize=9, fontfamily="monospace")
ax1.set_xlim(-0.5, 11.5)
ax1.set_ylim(-0.5, 7.5)
ax1.set_yticks(range(8))
ax1.set_yticklabels([f"N{i}" for i in range(8)])
ax1.tick_params(colors="#666", labelsize=8)
for spine in ax1.spines.values():
spine.set_color("#222")
# Arrow showing propagation direction
ax1.annotate("", xy=(7.5, 7), xytext=(0.5, 0),
arrowprops=dict(arrowstyle="->", color=GOLD, lw=1.5, ls="--"))
ax1.text(5, 2.5, f"7 timesteps\n{sync_total} total spikes", fontsize=10,
color=GOLD, fontfamily="monospace", ha="center",
bbox=dict(boxstyle="round,pad=0.4", facecolor=PANEL, edgecolor=GOLD, alpha=0.8))
ax2 = fig.add_subplot(gs[0, 1])
ax2.set_facecolor(PANEL)
ax2.set_title("ASYNC Mode β€” 8-Neuron Chain (same network)", color=TEXT, fontsize=12,
fontfamily="monospace", pad=10)
for gid, times in async_trains.items():
neuron = gid % NEURONS_PER_CORE
ax2.scatter(times, [neuron] * len(times), s=120, c=GREEN, marker="|", linewidths=2.5)
for t in times:
ax2.annotate(f"N{neuron}", (t + 0.15, neuron), fontsize=7, color="#888",
fontfamily="monospace")
ax2.set_xlabel("Timestep", color=TEXT, fontsize=9, fontfamily="monospace")
ax2.set_ylabel("Neuron", color=TEXT, fontsize=9, fontfamily="monospace")
ax2.set_xlim(-0.5, 11.5)
ax2.set_ylim(-0.5, 7.5)
ax2.set_yticks(range(8))
ax2.set_yticklabels([f"N{i}" for i in range(8)])
ax2.tick_params(colors="#666", labelsize=8)
for spine in ax2.spines.values():
spine.set_color("#222")
# All spikes at t=0
ax2.text(0.5, 4, f"1 timestep!\n{async_total} spikes\n(micro-steps)", fontsize=10,
color=GREEN, fontfamily="monospace", ha="center",
bbox=dict(boxstyle="round,pad=0.4", facecolor=PANEL, edgecolor=GREEN, alpha=0.8))
ax3 = fig.add_subplot(gs[1, 0])
ax3.set_facecolor(PANEL)
ax3.set_title(f"SYNC E/I Network β€” {sync_ei_total:,} spikes / 150 ts",
color=TEXT, fontsize=12, fontfamily="monospace", pad=10)
for gid, times in sync_ei_trains.items():
local = gid % NEURONS_PER_CORE
color = CYAN if local < 64 else RED
ax3.scatter(times, [gid] * len(times), s=0.6, c=color, marker="|", linewidths=0.3)
ax3.set_xlabel("Timestep", color=TEXT, fontsize=9, fontfamily="monospace")
ax3.set_ylabel("Neuron ID", color=TEXT, fontsize=9, fontfamily="monospace")
ax3.tick_params(colors="#666", labelsize=7)
for spine in ax3.spines.values():
spine.set_color("#222")
exc_p = mpatches.Patch(color=CYAN, label="Exc")
inh_p = mpatches.Patch(color=RED, label="Inh")
ax3.legend(handles=[exc_p, inh_p], loc="upper right", fontsize=7,
facecolor=PANEL, edgecolor="#333", labelcolor=TEXT)
ax4 = fig.add_subplot(gs[1, 1])
ax4.set_facecolor(PANEL)
ax4.set_title(f"ASYNC E/I Network β€” {async_ei_total:,} spikes / 150 ts",
color=TEXT, fontsize=12, fontfamily="monospace", pad=10)
for gid, times in async_ei_trains.items():
local = gid % NEURONS_PER_CORE
color = GREEN if local < 64 else PURPLE
ax4.scatter(times, [gid] * len(times), s=0.6, c=color, marker="|", linewidths=0.3)
ax4.set_xlabel("Timestep", color=TEXT, fontsize=9, fontfamily="monospace")
ax4.set_ylabel("Neuron ID", color=TEXT, fontsize=9, fontfamily="monospace")
ax4.tick_params(colors="#666", labelsize=7)
for spine in ax4.spines.values():
spine.set_color("#222")
exc_p2 = mpatches.Patch(color=GREEN, label="Exc (async)")
inh_p2 = mpatches.Patch(color=PURPLE, label="Inh (async)")
ax4.legend(handles=[exc_p2, inh_p2], loc="upper right", fontsize=7,
facecolor=PANEL, edgecolor="#333", labelcolor=TEXT)
ax5 = fig.add_subplot(gs[2, 0])
ax5.set_facecolor(PANEL)
ax5.set_title("Network Activity β€” Sync vs Async", color=TEXT, fontsize=12,
fontfamily="monospace", pad=10)
window = 5
sync_ma = np.convolve(sync_ei_counts, np.ones(window)/window, mode="valid")
async_ma = np.convolve(async_ei_counts, np.ones(window)/window, mode="valid")
x = range(window - 1, 150)
ax5.fill_between(x, sync_ma, alpha=0.15, color=CYAN)
ax5.plot(x, sync_ma, color=CYAN, lw=1.5, label=f"Sync ({sync_ei_total:,} spikes)")
ax5.fill_between(x, async_ma, alpha=0.15, color=GREEN)
ax5.plot(x, async_ma, color=GREEN, lw=1.5, label=f"Async ({async_ei_total:,} spikes)")
ax5.set_xlabel("Timestep", color=TEXT, fontsize=9, fontfamily="monospace")
ax5.set_ylabel("Spikes / ts (5-pt avg)", color=TEXT, fontsize=9, fontfamily="monospace")
ax5.tick_params(colors="#666", labelsize=7)
ax5.legend(fontsize=8, facecolor=PANEL, edgecolor="#333", labelcolor=TEXT)
for spine in ax5.spines.values():
spine.set_color("#222")
ax6 = fig.add_subplot(gs[2, 1])
ax6.set_facecolor(PANEL)
ax6.set_title("P12 Async Architecture β€” GALS Event Loop", color=TEXT, fontsize=12,
fontfamily="monospace", pad=10)
ax6.set_xlim(0, 10)
ax6.set_ylim(0, 8)
ax6.axis("off")
# Draw the async FSM flow
boxes = [
(5, 7, "IDLE", "#555"),
(5, 5.5, "ASYNC_ACTIVE\n(main loop)", GREEN),
(1.5, 3.5, "INJECT\n(drain PCIF)", BLUE),
(5, 3.5, "ROUTE\n(inter-core)", GOLD),
(8.5, 3.5, "RESTART\n(deferred)", PURPLE),
(5, 1.5, "QUIESCENT\n(timestep done)", CYAN),
]
for bx, by, label, color in boxes:
rect = mpatches.FancyBboxPatch((bx - 1.1, by - 0.55), 2.2, 1.1,
boxstyle="round,pad=0.15",
facecolor=color, alpha=0.15,
edgecolor=color, linewidth=1.5)
ax6.add_patch(rect)
ax6.text(bx, by, label, ha="center", va="center", fontsize=7.5,
color=color, fontweight="bold", fontfamily="monospace")
# Arrows
arrow_style = dict(arrowstyle="->", lw=1.2)
arrows = [
((5, 6.4), (5, 6.1), "#555"), # IDLE β†’ ACTIVE
((3.8, 5.2), (2.6, 4.1), BLUE), # ACTIVE β†’ INJECT
((5, 4.9), (5, 4.1), GOLD), # ACTIVE β†’ ROUTE
((6.2, 5.2), (7.4, 4.1), PURPLE), # ACTIVE β†’ RESTART
((2.6, 3.0), (3.8, 5.0), BLUE), # INJECT β†’ ACTIVE (back)
((4.0, 3.8), (3.8, 5.0), GOLD), # ROUTE β†’ ACTIVE (back, shifted)
((7.4, 3.0), (6.2, 5.0), PURPLE), # RESTART β†’ ACTIVE (back)
((5, 4.9), (5, 2.1), CYAN), # ACTIVE β†’ QUIESCENT
]
for start, end, color in arrows:
ax6.annotate("", xy=end, xytext=start,
arrowprops=dict(arrowstyle="->", color=color, lw=1.2))
# Labels on arrows
ax6.text(2.2, 4.8, "PCIF\nnon-empty", fontsize=6, color=BLUE,
fontfamily="monospace", ha="center")
ax6.text(5.7, 4.5, "capture\nFIFO", fontsize=6, color=GOLD,
fontfamily="monospace", ha="center")
ax6.text(7.8, 4.8, "core\nspiked", fontsize=6, color=PURPLE,
fontfamily="monospace", ha="center")
ax6.text(3.8, 2.3, "all quiet", fontsize=6, color=CYAN,
fontfamily="monospace", ha="center")
# Key insight callout
ax6.text(5, 0.5,
"Key: chains collapse into micro-steps within 1 timestep\n"
"Quiescence = all cores idle + no restarts + all FIFOs empty",
ha="center", va="center", fontsize=7, color="#888",
fontfamily="monospace", style="italic",
bbox=dict(boxstyle="round,pad=0.4", facecolor="#0a0a1a",
edgecolor="#333", alpha=0.8))
# Save
output = r"C:\Users\mrwab\neuromorphic-chip\sdk\async_dashboard.png"
plt.savefig(output, dpi=180, facecolor=BG, bbox_inches="tight")
plt.close()
print(f"Saved to: {output}")