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).
Python
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
Python
40
41
42
43
44
45
46
@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
Python
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
93
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) -> None:
        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
Python
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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
Python
83
84
85
86
87
88
89
90
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
Python
 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
156
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) -> None:
        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
Python
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
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
Python
141
142
143
144
145
146
147
148
149
150
151
152
153
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