| | """Tests for the compiler: CSR placement, pool allocation, multicast routing."""
|
| |
|
| | import pytest
|
| | import sys, os
|
| | sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
| |
|
| | import neurocore as nc
|
| | from neurocore.compiler import Compiler
|
| | from neurocore.exceptions import (
|
| | PoolOverflowError, RouteOverflowError, PlacementError, NetworkTooLargeError,
|
| | )
|
| | from neurocore.constants import NEURONS_PER_CORE, POOL_DEPTH, ROUTE_FANOUT
|
| |
|
| |
|
| | class TestPlacement:
|
| | def test_single_core(self):
|
| | net = nc.Network()
|
| | net.population(100)
|
| | c = Compiler()
|
| | compiled = c.compile(net)
|
| | assert compiled.placement.num_cores_used == 1
|
| |
|
| | def test_two_cores(self):
|
| | net = nc.Network()
|
| |
|
| | net.population(1025)
|
| | c = Compiler()
|
| | compiled = c.compile(net)
|
| | assert compiled.placement.num_cores_used == 2
|
| |
|
| | def test_exact_core_boundary(self):
|
| | net = nc.Network()
|
| | net.population(NEURONS_PER_CORE)
|
| | c = Compiler()
|
| | compiled = c.compile(net)
|
| | assert compiled.placement.num_cores_used == 1
|
| |
|
| | def test_multiple_populations(self):
|
| | net = nc.Network()
|
| | net.population(800)
|
| | net.population(400)
|
| | c = Compiler()
|
| | compiled = c.compile(net)
|
| |
|
| | assert compiled.placement.num_cores_used == 2
|
| | assert compiled.placement.total_neurons == 1200
|
| |
|
| | def test_too_many_neurons(self):
|
| | net = nc.Network()
|
| | net.population(128 * NEURONS_PER_CORE + 1)
|
| | c = Compiler()
|
| | with pytest.raises(NetworkTooLargeError):
|
| | c.compile(net)
|
| |
|
| |
|
| | class TestCSRPool:
|
| | """Tests for CSR (Compressed Sparse Row) pool allocation."""
|
| |
|
| | def test_pool_entries_generated(self):
|
| | """Intra-core connections generate pool entries."""
|
| | net = nc.Network()
|
| | a = net.population(4)
|
| | b = net.population(4)
|
| | net.connect(a, b, topology="all_to_all", weight=200)
|
| | c = Compiler()
|
| | compiled = c.compile(net)
|
| |
|
| | assert len(compiled.prog_pool_cmds) == 16
|
| | assert len(compiled.prog_route_cmds) == 0
|
| |
|
| | def test_index_entries_generated(self):
|
| | """Each source neuron with connections gets an index entry."""
|
| | net = nc.Network()
|
| | a = net.population(4)
|
| | b = net.population(4)
|
| | net.connect(a, b, topology="all_to_all", weight=200)
|
| | c = Compiler()
|
| | compiled = c.compile(net)
|
| |
|
| | assert len(compiled.prog_index_cmds) == 4
|
| |
|
| | idx0 = compiled.prog_index_cmds[0]
|
| | assert idx0["count"] == 4
|
| | assert idx0["base_addr"] == 0
|
| |
|
| | def test_bump_allocator_contiguous(self):
|
| | """Pool addresses should be contiguous per core."""
|
| | net = nc.Network()
|
| | a = net.population(3)
|
| | b = net.population(6)
|
| | net.connect(a, b, topology="all_to_all", weight=100)
|
| | c = Compiler()
|
| | compiled = c.compile(net)
|
| |
|
| | assert len(compiled.prog_pool_cmds) == 18
|
| |
|
| | addrs = [cmd["pool_addr"] for cmd in compiled.prog_pool_cmds]
|
| | assert addrs == list(range(18))
|
| |
|
| | def test_variable_fanout(self):
|
| | """Different source neurons can have different connection counts."""
|
| | net = nc.Network()
|
| | src1 = net.population(1)
|
| | src2 = net.population(1)
|
| | tgt_small = net.population(5)
|
| | tgt_large = net.population(10)
|
| | net.connect(src1, tgt_small, topology="all_to_all", weight=100)
|
| | net.connect(src2, tgt_large, topology="all_to_all", weight=100)
|
| | c = Compiler()
|
| | compiled = c.compile(net)
|
| | counts = sorted([cmd["count"] for cmd in compiled.prog_index_cmds])
|
| | assert counts == [5, 10]
|
| |
|
| | def test_high_fanout_no_error(self):
|
| | """With CSR pool, >32 connections per source is now allowed."""
|
| | net = nc.Network()
|
| | src = net.population(1)
|
| | tgt = net.population(100)
|
| | net.connect(src, tgt, topology="all_to_all", weight=100)
|
| | c = Compiler()
|
| |
|
| | compiled = c.compile(net)
|
| | assert len(compiled.prog_pool_cmds) == 100
|
| |
|
| | def test_pool_overflow(self):
|
| | """Exceeding POOL_DEPTH per core should raise PoolOverflowError."""
|
| | net = nc.Network()
|
| | src = net.population(200)
|
| | net.connect(src, src, topology="all_to_all", weight=100)
|
| | c = Compiler()
|
| | with pytest.raises(PoolOverflowError):
|
| | c.compile(net)
|
| |
|
| | def test_legacy_prog_conn_alias(self):
|
| | """prog_conn_cmds property should alias prog_pool_cmds."""
|
| | net = nc.Network()
|
| | a = net.population(2)
|
| | b = net.population(2)
|
| | net.connect(a, b, topology="all_to_all", weight=200)
|
| | c = Compiler()
|
| | compiled = c.compile(net)
|
| | assert compiled.prog_conn_cmds is compiled.prog_pool_cmds
|
| |
|
| |
|
| | class TestMulticastRouting:
|
| | """Tests for P13b multicast inter-core routing."""
|
| |
|
| | def test_single_route(self):
|
| | """One inter-core route per source should work."""
|
| | net = nc.Network()
|
| | a = net.population(NEURONS_PER_CORE)
|
| | b = net.population(1)
|
| | net.connect(a, b, topology="all_to_all", weight=200)
|
| | c = Compiler()
|
| | compiled = c.compile(net)
|
| |
|
| | assert len(compiled.prog_route_cmds) == NEURONS_PER_CORE
|
| |
|
| | assert all(cmd["slot"] == 0 for cmd in compiled.prog_route_cmds)
|
| |
|
| | def test_multicast_two_destinations(self):
|
| | """One source routing to 2 targets on another core (2 route slots)."""
|
| | net = nc.Network()
|
| |
|
| | src = net.population(NEURONS_PER_CORE)
|
| | tgt1 = net.population(1)
|
| | tgt2 = net.population(1)
|
| | net.connect(src, tgt1, topology="all_to_all", weight=200)
|
| | net.connect(src, tgt2, topology="all_to_all", weight=200)
|
| | comp = Compiler()
|
| | compiled = comp.compile(net)
|
| |
|
| | src_core, src_neuron = compiled.placement.neuron_map[(src.id, 0)]
|
| | routes_for_src0 = [r for r in compiled.prog_route_cmds
|
| | if r["src_neuron"] == src_neuron and r["src_core"] == src_core]
|
| | assert len(routes_for_src0) == 2
|
| | slots = sorted(r["slot"] for r in routes_for_src0)
|
| | assert slots == [0, 1]
|
| |
|
| | def test_multicast_8_way(self):
|
| | """Max 8 multicast destinations should work."""
|
| | net = nc.Network()
|
| |
|
| | src = net.population(NEURONS_PER_CORE)
|
| | targets = []
|
| | for _ in range(8):
|
| | targets.append(net.population(1))
|
| | for t in targets:
|
| | net.connect(src, t, topology="all_to_all", weight=100)
|
| | comp = Compiler()
|
| | compiled = comp.compile(net)
|
| | src_core, src_neuron = compiled.placement.neuron_map[(src.id, 0)]
|
| | routes_for_src0 = [r for r in compiled.prog_route_cmds
|
| | if r["src_neuron"] == src_neuron and r["src_core"] == src_core]
|
| | assert len(routes_for_src0) == 8
|
| |
|
| | def test_multicast_overflow(self):
|
| | """More than ROUTE_FANOUT unique destinations should raise RouteOverflowError."""
|
| | net = nc.Network()
|
| |
|
| | src = net.population(NEURONS_PER_CORE)
|
| | targets = []
|
| | for _ in range(ROUTE_FANOUT + 1):
|
| | targets.append(net.population(1))
|
| | for t in targets:
|
| | net.connect(src, t, topology="all_to_all", weight=100)
|
| | comp = Compiler()
|
| | with pytest.raises(RouteOverflowError):
|
| | comp.compile(net)
|
| |
|
| | def test_route_deduplication(self):
|
| | """Multiple connections to same (dest_core, dest_neuron) use 1 route slot."""
|
| | net = nc.Network()
|
| | a = net.population(NEURONS_PER_CORE)
|
| | b = net.population(1)
|
| |
|
| |
|
| | net.connect(a, b, topology="all_to_all", weight=200)
|
| |
|
| | net.connect(a, b, topology="all_to_all", weight=300)
|
| | comp = Compiler()
|
| | compiled = comp.compile(net)
|
| |
|
| | routes_for_n0 = [r for r in compiled.prog_route_cmds
|
| | if r["src_neuron"] == 0 and r["src_core"] == 0]
|
| | assert len(routes_for_n0) == 1
|
| |
|
| |
|
| | class TestNeuronParams:
|
| | def test_non_default_params(self):
|
| | net = nc.Network()
|
| | net.population(4, params={"threshold": 800, "leak": 5})
|
| | c = Compiler()
|
| | compiled = c.compile(net)
|
| |
|
| | assert len(compiled.prog_neuron_cmds) == 8
|
| |
|
| | def test_default_params_no_commands(self):
|
| | net = nc.Network()
|
| | net.population(4)
|
| | c = Compiler()
|
| | compiled = c.compile(net)
|
| | assert len(compiled.prog_neuron_cmds) == 0
|
| |
|
| |
|
| | class TestCompiledSummary:
|
| | def test_summary(self, small_network):
|
| | net, _, _ = small_network
|
| | c = Compiler()
|
| | compiled = c.compile(net)
|
| | s = compiled.summary()
|
| | assert "pool entries" in s
|
| | assert "inter-core" in s
|
| |
|