| """
|
| 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()
|
|
|