| | """
|
| | Neuromorphic Chip - Spike Visualizer
|
| | Parses the VCD waveform file and generates visual plots of neuron activity.
|
| | """
|
| |
|
| | import re
|
| | import os
|
| |
|
| | def parse_vcd_spikes(vcd_path):
|
| | """Parse VCD file to extract spike timing for each neuron."""
|
| | spikes = {0: [], 1: [], 2: [], 3: []}
|
| | membrane = {0: [], 1: [], 2: [], 3: []}
|
| |
|
| | current_time = 0
|
| |
|
| | id_map = {}
|
| |
|
| | with open(vcd_path, 'r') as f:
|
| | in_header = True
|
| | for line in f:
|
| | line = line.strip()
|
| |
|
| |
|
| | if line.startswith('$var'):
|
| | parts = line.split()
|
| | if len(parts) >= 5:
|
| | var_id = parts[3]
|
| | var_name = parts[4]
|
| |
|
| | id_map[var_id] = var_name
|
| |
|
| | if line == '$enddefinitions $end':
|
| | in_header = False
|
| | continue
|
| |
|
| | if in_header:
|
| | continue
|
| |
|
| |
|
| | if line.startswith('#'):
|
| | current_time = int(line[1:])
|
| | continue
|
| |
|
| |
|
| |
|
| | if len(line) >= 2 and line[0] in ('0', '1'):
|
| | val = int(line[0])
|
| | var_id = line[1:]
|
| | if var_id in id_map:
|
| | name = id_map[var_id]
|
| | for i in range(4):
|
| | if name == f'spikes[{i}]' or (name == 'spikes' and var_id.endswith(f'[{i}]')):
|
| | if val == 1:
|
| | spikes[i].append(current_time)
|
| |
|
| | return spikes, current_time
|
| |
|
| | def parse_simulation_output(sim_output=None):
|
| | """Parse spike times from simulation console output."""
|
| | spikes = {0: [], 1: [], 2: [], 3: []}
|
| |
|
| |
|
| | raw = """[185000] SPIKE! Neuron 0
|
| | [335000] SPIKE! Neuron 0
|
| | [485000] SPIKE! Neuron 0
|
| | [505000] SPIKE! Neuron 1
|
| | [635000] SPIKE! Neuron 0
|
| | [655000] SPIKE! Neuron 2
|
| | [785000] SPIKE! Neuron 0
|
| | [935000] SPIKE! Neuron 0
|
| | [955000] SPIKE! Neuron 1
|
| | [1085000] SPIKE! Neuron 0
|
| | [1235000] SPIKE! Neuron 0
|
| | [1255000] SPIKE! Neuron 2
|
| | [1385000] SPIKE! Neuron 0
|
| | [1405000] SPIKE! Neuron 1
|
| | [1535000] SPIKE! Neuron 0
|
| | [1685000] SPIKE! Neuron 0
|
| | [1835000] SPIKE! Neuron 0
|
| | [1855000] SPIKE! Neuron 1
|
| | [1855000] SPIKE! Neuron 2
|
| | [1875000] SPIKE! Neuron 3
|
| | [1895000] SPIKE! Neuron 0
|
| | [2045000] SPIKE! Neuron 0
|
| | [2145000] SPIKE! Neuron 0
|
| | [2165000] SPIKE! Neuron 1
|
| | [2245000] SPIKE! Neuron 0
|
| | [2265000] SPIKE! Neuron 2
|
| | [2345000] SPIKE! Neuron 0
|
| | [2445000] SPIKE! Neuron 0
|
| | [2465000] SPIKE! Neuron 1
|
| | [2545000] SPIKE! Neuron 0
|
| | [2645000] SPIKE! Neuron 0
|
| | [2665000] SPIKE! Neuron 2
|
| | [2745000] SPIKE! Neuron 0
|
| | [2765000] SPIKE! Neuron 1
|
| | [2845000] SPIKE! Neuron 0
|
| | [2945000] SPIKE! Neuron 0
|
| | [3045000] SPIKE! Neuron 0
|
| | [3065000] SPIKE! Neuron 1
|
| | [3065000] SPIKE! Neuron 2
|
| | [3085000] SPIKE! Neuron 3
|
| | [3105000] SPIKE! Neuron 0
|
| | [3205000] SPIKE! Neuron 0
|
| | [3305000] SPIKE! Neuron 0
|
| | [3325000] SPIKE! Neuron 1
|
| | [3405000] SPIKE! Neuron 0
|
| | [3425000] SPIKE! Neuron 2
|
| | [3505000] SPIKE! Neuron 0
|
| | [3605000] SPIKE! Neuron 0
|
| | [3625000] SPIKE! Neuron 1
|
| | [3705000] SPIKE! Neuron 0
|
| | [3805000] SPIKE! Neuron 0
|
| | [3825000] SPIKE! Neuron 2
|
| | [3905000] SPIKE! Neuron 0
|
| | [3925000] SPIKE! Neuron 1
|
| | [4005000] SPIKE! Neuron 0
|
| | [4105000] SPIKE! Neuron 0
|
| | [4105000] SPIKE! Neuron 2
|
| | [4125000] SPIKE! Neuron 3
|
| | [4205000] SPIKE! Neuron 0
|
| | [4215000] SPIKE! Neuron 2
|
| | [4225000] SPIKE! Neuron 1
|
| | [4305000] SPIKE! Neuron 0
|
| | [4325000] SPIKE! Neuron 2
|
| | [4405000] SPIKE! Neuron 0
|
| | [4425000] SPIKE! Neuron 2
|
| | [4445000] SPIKE! Neuron 3
|
| | [4465000] SPIKE! Neuron 0
|
| | [4485000] SPIKE! Neuron 1
|
| | [4515000] SPIKE! Neuron 2
|
| | [4565000] SPIKE! Neuron 0
|
| | [4605000] SPIKE! Neuron 2
|
| | [4665000] SPIKE! Neuron 0
|
| | [4695000] SPIKE! Neuron 2
|
| | [4715000] SPIKE! Neuron 3
|
| | [4785000] SPIKE! Neuron 0
|
| | [4805000] SPIKE! Neuron 1
|
| | [4805000] SPIKE! Neuron 2
|
| | [4885000] SPIKE! Neuron 0
|
| | [4905000] SPIKE! Neuron 2
|
| | [4985000] SPIKE! Neuron 0
|
| | [5005000] SPIKE! Neuron 2
|
| | [5025000] SPIKE! Neuron 3
|
| | [5045000] SPIKE! Neuron 0
|
| | [5065000] SPIKE! Neuron 1
|
| | [5095000] SPIKE! Neuron 2
|
| | [5145000] SPIKE! Neuron 0
|
| | [5185000] SPIKE! Neuron 2
|
| | [5245000] SPIKE! Neuron 0
|
| | [5275000] SPIKE! Neuron 2
|
| | [5295000] SPIKE! Neuron 3
|
| | [5365000] SPIKE! Neuron 0
|
| | [5385000] SPIKE! Neuron 1
|
| | [5385000] SPIKE! Neuron 2
|
| | [5465000] SPIKE! Neuron 0
|
| | [5485000] SPIKE! Neuron 2
|
| | [5565000] SPIKE! Neuron 0
|
| | [5585000] SPIKE! Neuron 2
|
| | [5605000] SPIKE! Neuron 3
|
| | [5625000] SPIKE! Neuron 0
|
| | [5645000] SPIKE! Neuron 1
|
| | [5675000] SPIKE! Neuron 2
|
| | [5725000] SPIKE! Neuron 0
|
| | [5765000] SPIKE! Neuron 2
|
| | [5825000] SPIKE! Neuron 0
|
| | [5855000] SPIKE! Neuron 2
|
| | [5875000] SPIKE! Neuron 3
|
| | [5945000] SPIKE! Neuron 0
|
| | [5965000] SPIKE! Neuron 1
|
| | [5965000] SPIKE! Neuron 2
|
| | [6045000] SPIKE! Neuron 0
|
| | [6065000] SPIKE! Neuron 2"""
|
| |
|
| | for line in raw.strip().split('\n'):
|
| | m = re.match(r'\[(\d+)\] SPIKE! Neuron (\d)', line)
|
| | if m:
|
| | time_ps = int(m.group(1))
|
| | neuron = int(m.group(2))
|
| | spikes[neuron].append(time_ps)
|
| |
|
| | return spikes
|
| |
|
| | def draw_raster_plot(spikes, total_time=7070000):
|
| | """Draw a text-based spike raster plot."""
|
| | width = 100
|
| |
|
| | neuron_names = ['Neuron 0 (Input) ', 'Neuron 1 (Excit) ', 'Neuron 2 (Chain) ', 'Neuron 3 (Inhibit) ']
|
| | neuron_chars = ['#', '+', '*', 'o']
|
| |
|
| |
|
| | phases = [
|
| | (70000, 'Phase 1: Low stimulus'),
|
| | (2070000, 'Phase 2: High stimulus'),
|
| | (4070000, 'Phase 3: Dual stimulus'),
|
| | (6070000, 'Phase 4: No stimulus'),
|
| | ]
|
| |
|
| | print()
|
| | print('=' * (width + 25))
|
| | print(' NEUROMORPHIC CHIP - SPIKE RASTER PLOT')
|
| | print(' Each mark = one spike from that neuron')
|
| | print('=' * (width + 25))
|
| | print()
|
| |
|
| |
|
| | header = ' '
|
| | for i in range(0, width + 1, 20):
|
| | time_us = (i / width) * (total_time / 1000)
|
| | header += f'{time_us:>6.0f}us' + ' ' * 12
|
| | print(header)
|
| | print(' ' + '-' * width)
|
| |
|
| |
|
| | phase_line = ' '
|
| | for t, name in phases:
|
| | pos = int((t / total_time) * width)
|
| | phase_line = phase_line[:20+pos] + '|' + phase_line[21+pos:]
|
| | print(phase_line)
|
| |
|
| |
|
| | for n in range(4):
|
| | line = neuron_names[n]
|
| | row = [' '] * width
|
| |
|
| | for spike_time in spikes[n]:
|
| | pos = int((spike_time / total_time) * width)
|
| | if 0 <= pos < width:
|
| | row[pos] = neuron_chars[n]
|
| |
|
| | line += ''.join(row) + f' ({len(spikes[n])} spikes)'
|
| | print(line)
|
| |
|
| | print(' ' + '-' * width)
|
| |
|
| |
|
| | print()
|
| | print(' Phases:')
|
| | for t, name in phases:
|
| | print(f' | {name} (t={t/1000:.0f}us)')
|
| |
|
| | print()
|
| | print(' Circuit:')
|
| | print(' External Input --> [N0] --excit--> [N1]')
|
| | print(' |')
|
| | print(' +---excit--> [N2] --excit--> [N3]')
|
| | print(' | |')
|
| | print(' +<--------inhibit--------------+')
|
| | print()
|
| |
|
| |
|
| | print(' Firing Rate Analysis:')
|
| | for phase_idx in range(len(phases)):
|
| | t_start = phases[phase_idx][0]
|
| | t_end = phases[phase_idx + 1][0] if phase_idx + 1 < len(phases) else total_time
|
| | duration_us = (t_end - t_start) / 1000
|
| |
|
| | print(f' {phases[phase_idx][1]}:')
|
| | for n in range(4):
|
| | count = sum(1 for s in spikes[n] if t_start <= s < t_end)
|
| | rate = (count / duration_us) * 1000 if duration_us > 0 else 0
|
| | bar = '#' * int(rate * 2)
|
| | print(f' N{n}: {count:>3} spikes ({rate:>5.1f} spikes/ms) {bar}')
|
| | print()
|
| |
|
| | def draw_membrane_ascii(spikes, total_time=7070000):
|
| | """Draw a simplified membrane potential visualization."""
|
| | width = 100
|
| | height = 10
|
| |
|
| | print('=' * (width + 25))
|
| | print(' MEMBRANE POTENTIAL APPROXIMATION (Neuron 0)')
|
| | print(' Threshold = 1000 (top line)')
|
| | print('=' * (width + 25))
|
| | print()
|
| |
|
| |
|
| | threshold = 1000
|
| | leak = 2
|
| | input_current = 0
|
| | potential = 0
|
| |
|
| | potentials = []
|
| | for t in range(0, total_time, total_time // width):
|
| |
|
| | if t < 70000:
|
| | input_current = 0
|
| | elif t < 2070000:
|
| | input_current = 100
|
| | elif t < 4070000:
|
| | input_current = 200
|
| | elif t < 6070000:
|
| | input_current = 200
|
| | else:
|
| | input_current = 0
|
| |
|
| |
|
| | spiked = any(abs(s - t) < (total_time // width) for s in spikes[0])
|
| |
|
| | if spiked:
|
| | potentials.append(threshold)
|
| | potential = 0
|
| | else:
|
| | potential = min(potential + input_current - leak, threshold)
|
| | potential = max(potential, 0)
|
| | potentials.append(potential)
|
| |
|
| |
|
| | for row in range(height, -1, -1):
|
| | level = (row / height) * threshold
|
| | line = f' {level:>6.0f} |'
|
| | for col in range(min(width, len(potentials))):
|
| | if potentials[col] >= level and (row == 0 or potentials[col] < ((row + 1) / height) * threshold):
|
| | line += '#'
|
| | elif potentials[col] >= level:
|
| | line += '|'
|
| | elif row == height and potentials[col] >= threshold * 0.95:
|
| | line += '^'
|
| | else:
|
| | line += ' '
|
| | print(line)
|
| |
|
| | print(f' +' + '-' * width)
|
| | print(f' 0us' + ' ' * (width - 20) + f'{total_time/1000:.0f}us')
|
| | print()
|
| |
|
| |
|
| | if __name__ == '__main__':
|
| | print('\n' * 2)
|
| |
|
| |
|
| | spikes = parse_simulation_output()
|
| |
|
| |
|
| | draw_raster_plot(spikes)
|
| | draw_membrane_ascii(spikes)
|
| |
|
| | print('To view full waveforms with GTKWave:')
|
| | print(' wsl gtkwave /mnt/c/Users/mrwab/neuromorphic-chip/sim/neuron_core.vcd')
|
| | print()
|
| |
|