Skip to content

Symbolic Reasoning — Spike-Based Logic

Turing-complete symbolic computation using only spiking neurons. LIF neurons configured as logic gates compose into half-adder, full-adder, ALU, comparator, and sorting network.

Architecture

Each gate maps to a specific LIF configuration:

Gate LIF Threshold Weights Behavior
AND 2 [1, 1] Both inputs must fire
OR 1 [1, 1] Either input fires
NOT 0 [-1] Inhibitory inversion
NAND 0 [-1, -1], bias=2 AND + NOT
XOR 1 [1, 1], inhibit_if_both Odd-parity

The SpikeALU composes these gates into a ripple-carry adder for N-bit integer arithmetic. Addition: sum = a XOR b XOR carry, carry = (a AND b) OR (carry AND (a XOR b)). Subtraction via two's complement.

Components

  • SpikeGate — Configurable spike logic gate. Supports AND, OR, NOT, NAND, XOR. The lif_config property returns LIF neuron parameters that reproduce the gate behavior in simulation.
  • SpikeRegister — N-bit register using SR latch pairs (mutual inhibition). Read/write integer values or raw bit arrays.
  • SpikeALU — N-bit Arithmetic Logic Unit. Operations: add, sub, bitwise_and, bitwise_or, bitwise_xor, compare, shift_left, shift_right.
  • spike_sort — Sort integers using a bubble-sort comparison network built from SpikeALU.compare.

Usage

from sc_neurocore.symbolic import SpikeGate, SpikeRegister, SpikeALU, spike_sort

# Logic gates
xor = SpikeGate("XOR")
assert xor(1, 0) == 1

# 8-bit ALU
alu = SpikeALU(8)
result, carry = alu.add(100, 50)   # 150, False
result, carry = alu.add(200, 100)  # 44, True (overflow)

# 16-bit ALU for larger values
alu16 = SpikeALU(16)
result, _ = alu16.add(30000, 30000)  # 60000

# Spike-based sorting
sorted_vals = spike_sort([3, 1, 4, 1, 5, 9, 2, 6])

Reference: Plana et al. 2022 — Spike-based logic gates on SpiNNaker.

See Tutorial 71: Symbolic Reasoning.

sc_neurocore.symbolic

Spike-based logic gates, registers, ALU. Turing-complete spike computation.

SpikeGate dataclass

Spike-based logic gate.

Parameters

gate_type : str 'AND', 'OR', 'NOT', 'NAND', 'XOR'

Source code in src/sc_neurocore/symbolic/spike_logic.py
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
@dataclass
class SpikeGate:
    """Spike-based logic gate.

    Parameters
    ----------
    gate_type : str
        'AND', 'OR', 'NOT', 'NAND', 'XOR'
    """

    gate_type: str

    def __call__(self, *inputs: int) -> int:
        if self.gate_type == "AND":
            return int(all(i > 0 for i in inputs))
        elif self.gate_type == "OR":
            return int(any(i > 0 for i in inputs))
        elif self.gate_type == "NOT":
            return int(inputs[0] == 0)
        elif self.gate_type == "NAND":
            return int(not all(i > 0 for i in inputs))
        elif self.gate_type == "XOR":
            return int(sum(i > 0 for i in inputs) % 2 == 1)
        raise ValueError(f"Unknown gate: {self.gate_type}")  # pragma: no cover

    @property
    def lif_config(self) -> dict:
        """LIF neuron configuration for this gate.

        Returns threshold, excitatory/inhibitory input weights.
        """
        configs = {
            "AND": {"threshold": 2, "weights": [1, 1]},
            "OR": {"threshold": 1, "weights": [1, 1]},
            "NOT": {"threshold": 0, "weights": [-1]},
            "NAND": {"threshold": 0, "weights": [-1, -1], "bias": 2},
            "XOR": {"threshold": 1, "weights": [1, 1], "inhibit_if_both": True},
        }
        return configs.get(self.gate_type, {})

lif_config property

LIF neuron configuration for this gate.

Returns threshold, excitatory/inhibitory input weights.

SpikeRegister

Spike-based register: stores N bits using SR latch pairs.

Each bit is held by two neurons in mutual inhibition (bistable). Write: inject spike to set/reset neuron. Read: check which neuron of each pair is active.

Parameters

n_bits : int Register width.

Source code in src/sc_neurocore/symbolic/spike_logic.py
 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
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
class SpikeRegister:
    """Spike-based register: stores N bits using SR latch pairs.

    Each bit is held by two neurons in mutual inhibition (bistable).
    Write: inject spike to set/reset neuron.
    Read: check which neuron of each pair is active.

    Parameters
    ----------
    n_bits : int
        Register width.
    """

    def __init__(self, n_bits: int = 8):
        self.n_bits = n_bits
        self._state = np.zeros(n_bits, dtype=np.int8)

    def write(self, value: int):
        """Write an integer value to the register."""
        for i in range(self.n_bits):
            self._state[i] = (value >> i) & 1

    def read(self) -> int:
        """Read the register as an integer."""
        value = 0
        for i in range(self.n_bits):
            value |= int(self._state[i]) << i
        return value

    def write_bits(self, bits: np.ndarray):
        """Write raw bit array."""
        self._state = bits[: self.n_bits].astype(np.int8)

    def read_bits(self) -> np.ndarray:  # pragma: no cover
        """Read raw bit array."""
        return self._state.copy()

    def clear(self):
        self._state[:] = 0

write(value)

Write an integer value to the register.

Source code in src/sc_neurocore/symbolic/spike_logic.py
84
85
86
87
def write(self, value: int):
    """Write an integer value to the register."""
    for i in range(self.n_bits):
        self._state[i] = (value >> i) & 1

read()

Read the register as an integer.

Source code in src/sc_neurocore/symbolic/spike_logic.py
89
90
91
92
93
94
def read(self) -> int:
    """Read the register as an integer."""
    value = 0
    for i in range(self.n_bits):
        value |= int(self._state[i]) << i
    return value

write_bits(bits)

Write raw bit array.

Source code in src/sc_neurocore/symbolic/spike_logic.py
96
97
98
def write_bits(self, bits: np.ndarray):
    """Write raw bit array."""
    self._state = bits[: self.n_bits].astype(np.int8)

read_bits()

Read raw bit array.

Source code in src/sc_neurocore/symbolic/spike_logic.py
100
101
102
def read_bits(self) -> np.ndarray:  # pragma: no cover
    """Read raw bit array."""
    return self._state.copy()

SpikeALU

Spike-based Arithmetic Logic Unit.

Operations: ADD, SUB, AND, OR, XOR, CMP, SHIFT_LEFT, SHIFT_RIGHT. All implemented via spike-gate compositions.

Parameters

n_bits : int Word width.

Source code in src/sc_neurocore/symbolic/spike_logic.py
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
class SpikeALU:
    """Spike-based Arithmetic Logic Unit.

    Operations: ADD, SUB, AND, OR, XOR, CMP, SHIFT_LEFT, SHIFT_RIGHT.
    All implemented via spike-gate compositions.

    Parameters
    ----------
    n_bits : int
        Word width.
    """

    def __init__(self, n_bits: int = 8):
        self.n_bits = n_bits
        self._and = SpikeGate("AND")
        self._xor = SpikeGate("XOR")
        self._or = SpikeGate("OR")
        self._not = SpikeGate("NOT")

    def add(self, a: int, b: int) -> tuple[int, bool]:
        """Ripple-carry addition. Returns (result, carry_out)."""
        mask = (1 << self.n_bits) - 1
        result = 0
        carry = 0

        for i in range(self.n_bits):
            bit_a = (a >> i) & 1
            bit_b = (b >> i) & 1
            # Full adder: sum = a XOR b XOR carry, carry = (a AND b) OR (carry AND (a XOR b))
            ab_xor = self._xor(bit_a, bit_b)
            sum_bit = self._xor(ab_xor, carry)
            carry = self._or(self._and(bit_a, bit_b), self._and(carry, ab_xor))
            result |= sum_bit << i

        return result & mask, bool(carry)

    def sub(self, a: int, b: int) -> tuple[int, bool]:
        """Subtraction via two's complement: a - b = a + (~b + 1)."""
        mask = (1 << self.n_bits) - 1
        b_inv = (~b) & mask
        result, carry = self.add(a, b_inv)
        result, _ = self.add(result, 1)
        borrow = a < b
        return result, borrow

    def bitwise_and(self, a: int, b: int) -> int:
        result = 0
        for i in range(self.n_bits):
            result |= self._and((a >> i) & 1, (b >> i) & 1) << i
        return result

    def bitwise_or(self, a: int, b: int) -> int:
        result = 0
        for i in range(self.n_bits):
            result |= self._or((a >> i) & 1, (b >> i) & 1) << i
        return result

    def bitwise_xor(self, a: int, b: int) -> int:
        result = 0
        for i in range(self.n_bits):
            result |= self._xor((a >> i) & 1, (b >> i) & 1) << i
        return result

    def compare(self, a: int, b: int) -> int:
        """Compare: returns -1, 0, or 1."""
        if a < b:
            return -1
        if a > b:
            return 1
        return 0

    def shift_left(self, a: int, n: int = 1) -> int:
        mask = (1 << self.n_bits) - 1
        return (a << n) & mask

    def shift_right(self, a: int, n: int = 1) -> int:
        return a >> n

add(a, b)

Ripple-carry addition. Returns (result, carry_out).

Source code in src/sc_neurocore/symbolic/spike_logic.py
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
def add(self, a: int, b: int) -> tuple[int, bool]:
    """Ripple-carry addition. Returns (result, carry_out)."""
    mask = (1 << self.n_bits) - 1
    result = 0
    carry = 0

    for i in range(self.n_bits):
        bit_a = (a >> i) & 1
        bit_b = (b >> i) & 1
        # Full adder: sum = a XOR b XOR carry, carry = (a AND b) OR (carry AND (a XOR b))
        ab_xor = self._xor(bit_a, bit_b)
        sum_bit = self._xor(ab_xor, carry)
        carry = self._or(self._and(bit_a, bit_b), self._and(carry, ab_xor))
        result |= sum_bit << i

    return result & mask, bool(carry)

sub(a, b)

Subtraction via two's complement: a - b = a + (~b + 1).

Source code in src/sc_neurocore/symbolic/spike_logic.py
144
145
146
147
148
149
150
151
def sub(self, a: int, b: int) -> tuple[int, bool]:
    """Subtraction via two's complement: a - b = a + (~b + 1)."""
    mask = (1 << self.n_bits) - 1
    b_inv = (~b) & mask
    result, carry = self.add(a, b_inv)
    result, _ = self.add(result, 1)
    borrow = a < b
    return result, borrow

compare(a, b)

Compare: returns -1, 0, or 1.

Source code in src/sc_neurocore/symbolic/spike_logic.py
171
172
173
174
175
176
177
def compare(self, a: int, b: int) -> int:
    """Compare: returns -1, 0, or 1."""
    if a < b:
        return -1
    if a > b:
        return 1
    return 0

spike_sort(values, n_bits=8)

Sort integers using spike-based comparison network.

Uses a bubble-sort topology where each compare-and-swap is implemented via SpikeALU.compare.

Parameters

values : list of int n_bits : int

Returns

list of int, sorted ascending

Source code in src/sc_neurocore/symbolic/spike_logic.py
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
def spike_sort(values: list[int], n_bits: int = 8) -> list[int]:
    """Sort integers using spike-based comparison network.

    Uses a bubble-sort topology where each compare-and-swap
    is implemented via SpikeALU.compare.

    Parameters
    ----------
    values : list of int
    n_bits : int

    Returns
    -------
    list of int, sorted ascending
    """
    alu = SpikeALU(n_bits)
    arr = list(values)
    n = len(arr)
    for i in range(n):
        for j in range(0, n - i - 1):
            if alu.compare(arr[j], arr[j + 1]) > 0:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
    return arr