Skip to content

AER-over-UDP Communication

Open protocol for inter-FPGA spike routing. No equivalent open standard exists.

  • AERSender — Pack AER events into UDP packets. Max 180 events per 1500-byte MTU.
  • AERReceiver — Receive and decode. receive_as_vector() returns binary spike vectors.
  • AEREvent — Single spike event (timestamp, neuron_id, data).
from sc_neurocore.comm import AERSender, AERReceiver

See Tutorial 50: AER Communication and Spike Codec Library.

sc_neurocore.comm.aer_udp

AER-over-UDP: open protocol for inter-FPGA spike routing.

Pack AER (Address Event Representation) spike events into UDP packets for communication between FPGA boards or between FPGA and host.

Packet format::

Header: magic(16) | seq(16) | n_events(16) | reserved(16) = 8 bytes Events: timestamp(32) | neuron_id(16) | data(16) = 8 bytes each Max events per packet: 180 (fits in 1500-byte Ethernet MTU)

No equivalent open standard exists for multi-FPGA SNN communication. SpiNNaker uses a proprietary protocol. BrainScaleS uses wafer routing.

AEREvent dataclass

Single AER spike event.

Source code in src/sc_neurocore/comm/aer_udp.py
39
40
41
42
43
44
45
@dataclass
class AEREvent:
    """Single AER spike event."""

    timestamp: int
    neuron_id: int
    data: int = 0

AERSender

Send AER spike events over UDP.

Parameters

host : str Destination IP address. port : int Destination UDP port.

Source code in src/sc_neurocore/comm/aer_udp.py
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
class AERSender:
    """Send AER spike events over UDP.

    Parameters
    ----------
    host : str
        Destination IP address.
    port : int
        Destination UDP port.
    """

    def __init__(self, host: str = "127.0.0.1", port: int = 9000):
        self.host = host
        self.port = port
        self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self._seq = 0

    def send(self, events: list[AEREvent]) -> int:
        """Send a batch of AER events. Returns number of packets sent."""
        packets_sent = 0
        for i in range(0, len(events), MAX_EVENTS_PER_PACKET):
            batch = events[i : i + MAX_EVENTS_PER_PACKET]
            header = struct.pack(HEADER_FMT, MAGIC, self._seq & 0xFFFF, len(batch), 0)
            body = b"".join(
                struct.pack(
                    EVENT_FMT, e.timestamp & 0xFFFFFFFF, e.neuron_id & 0xFFFF, e.data & 0xFFFF
                )
                for e in batch
            )
            self._sock.sendto(header + body, (self.host, self.port))
            self._seq += 1
            packets_sent += 1
        return packets_sent

    def send_spikes(self, spike_vector: np.ndarray, timestamp: int) -> int:
        """Convert a binary spike vector to AER events and send."""
        events = [
            AEREvent(timestamp=timestamp, neuron_id=int(i)) for i in np.nonzero(spike_vector)[0]
        ]
        if events:
            return self.send(events)
        return 0

    def close(self):
        self._sock.close()

send(events)

Send a batch of AER events. Returns number of packets sent.

Source code in src/sc_neurocore/comm/aer_udp.py
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
def send(self, events: list[AEREvent]) -> int:
    """Send a batch of AER events. Returns number of packets sent."""
    packets_sent = 0
    for i in range(0, len(events), MAX_EVENTS_PER_PACKET):
        batch = events[i : i + MAX_EVENTS_PER_PACKET]
        header = struct.pack(HEADER_FMT, MAGIC, self._seq & 0xFFFF, len(batch), 0)
        body = b"".join(
            struct.pack(
                EVENT_FMT, e.timestamp & 0xFFFFFFFF, e.neuron_id & 0xFFFF, e.data & 0xFFFF
            )
            for e in batch
        )
        self._sock.sendto(header + body, (self.host, self.port))
        self._seq += 1
        packets_sent += 1
    return packets_sent

send_spikes(spike_vector, timestamp)

Convert a binary spike vector to AER events and send.

Source code in src/sc_neurocore/comm/aer_udp.py
82
83
84
85
86
87
88
89
def send_spikes(self, spike_vector: np.ndarray, timestamp: int) -> int:
    """Convert a binary spike vector to AER events and send."""
    events = [
        AEREvent(timestamp=timestamp, neuron_id=int(i)) for i in np.nonzero(spike_vector)[0]
    ]
    if events:
        return self.send(events)
    return 0

AERReceiver

Receive AER spike events over UDP.

Parameters

host : str Bind address. port : int Listen port. timeout : float Socket timeout in seconds.

Source code in src/sc_neurocore/comm/aer_udp.py
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
class AERReceiver:
    """Receive AER spike events over UDP.

    Parameters
    ----------
    host : str
        Bind address.
    port : int
        Listen port.
    timeout : float
        Socket timeout in seconds.
    """

    def __init__(self, host: str = "127.0.0.1", port: int = 9000, timeout: float = 1.0):
        self.host = host
        self.port = port
        self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self._sock.settimeout(timeout)
        self._sock.bind((host, port))

    def receive(self) -> list[AEREvent]:
        """Receive one packet of AER events. Returns empty list on timeout."""
        try:
            data, addr = self._sock.recvfrom(2048)
        except TimeoutError:
            return []

        if len(data) < HEADER_SIZE:
            return []

        magic, seq, n_events, _ = struct.unpack(HEADER_FMT, data[:HEADER_SIZE])
        if magic != MAGIC:
            return []

        events = []
        offset = HEADER_SIZE
        for _ in range(n_events):
            if offset + EVENT_SIZE > len(data):
                break
            ts, nid, d = struct.unpack(EVENT_FMT, data[offset : offset + EVENT_SIZE])
            events.append(AEREvent(timestamp=ts, neuron_id=nid, data=d))
            offset += EVENT_SIZE

        return events

    def receive_as_vector(self, n_neurons: int) -> tuple[np.ndarray, int]:
        """Receive and convert to binary spike vector.

        Returns (spike_vector, timestamp) or (zeros, -1) on timeout.
        """
        events = self.receive()
        vector = np.zeros(n_neurons, dtype=np.int8)
        ts = -1
        for e in events:
            if 0 <= e.neuron_id < n_neurons:
                vector[e.neuron_id] = 1
            ts = e.timestamp
        return vector, ts

    def close(self):
        self._sock.close()

receive()

Receive one packet of AER events. Returns empty list on timeout.

Source code in src/sc_neurocore/comm/aer_udp.py
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
def receive(self) -> list[AEREvent]:
    """Receive one packet of AER events. Returns empty list on timeout."""
    try:
        data, addr = self._sock.recvfrom(2048)
    except TimeoutError:
        return []

    if len(data) < HEADER_SIZE:
        return []

    magic, seq, n_events, _ = struct.unpack(HEADER_FMT, data[:HEADER_SIZE])
    if magic != MAGIC:
        return []

    events = []
    offset = HEADER_SIZE
    for _ in range(n_events):
        if offset + EVENT_SIZE > len(data):
            break
        ts, nid, d = struct.unpack(EVENT_FMT, data[offset : offset + EVENT_SIZE])
        events.append(AEREvent(timestamp=ts, neuron_id=nid, data=d))
        offset += EVENT_SIZE

    return events

receive_as_vector(n_neurons)

Receive and convert to binary spike vector.

Returns (spike_vector, timestamp) or (zeros, -1) on timeout.

Source code in src/sc_neurocore/comm/aer_udp.py
140
141
142
143
144
145
146
147
148
149
150
151
152
def receive_as_vector(self, n_neurons: int) -> tuple[np.ndarray, int]:
    """Receive and convert to binary spike vector.

    Returns (spike_vector, timestamp) or (zeros, -1) on timeout.
    """
    events = self.receive()
    vector = np.zeros(n_neurons, dtype=np.int8)
    ts = -1
    for e in events:
        if 0 <= e.neuron_id < n_neurons:
            vector[e.neuron_id] = 1
        ts = e.timestamp
    return vector, ts