Repository: nengo/nengo-loihi

Issue:
Error splitting probe across blocks
```import numpy as np
import tensorflow as tf

import nengo
import nengo_dl
import nengo_loihi

inp = tf.keras.Input(shape=(28, 28, 1))
conv0 = tf.keras.layers.Conv2D(
    filters=3,  
    strides=1,
    kernel_size=1,
    activation=tf.nn.relu,
    use_bias=False,
)(inp)

# convolutional layers
conv1 = tf.keras.layers.Conv2D(
    filters=32,
    kernel_size=3,
    activation=tf.nn.relu,
    use_bias=False,
)(conv0)

model = tf.keras.Model(inputs=inp, outputs=conv1)  
   
# convert the keras model to a nengo network
nengo_converter = nengo_dl.Converter(
    model,
    swap_activations={tf.nn.relu: nengo_loihi.neurons.LoihiSpikingRectifiedLinear()},
)

# add functionality to input node, to send in images over time
with nengo_converter.net as net:
    nengo_loihi.add_params(net)

    net.config[nengo_converter.layers[conv0].ensemble].on_chip = False
    net.config[nengo_converter.layers[conv1].ensemble].block_shape = (
        nengo_loihi.BlockShape((10, 10, 1), (28, 28, 1)))

# build Nengo Loihi Simulator and run network
with nengo_loihi.Simulator(nengo_converter.net) as sim:
    sim.run(1)
```

gives 

```probe_target: [<nengo_loihi.block.LoihiBlock object at 0x7f6b28736910>]
ids:  (784,)
old_comp:  (21632,)
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-22-9aba82875b45> in <module>
     40 
     41 # build Nengo Loihi Simulator and run network
---> 42 with nengo_loihi.Simulator(nengo_converter.net) as sim:
     43     sim.run(1)

~/Dropbox/code/nengo-loihi/nengo_loihi/simulator.py in __init__(self, network, dt, seed, model, precompute, target, progress_bar, remove_passthrough, hardware_options)
    137             precompute=precompute,
    138             remove_passthrough=remove_passthrough,
--> 139             discretize=target != "simreal",
    140         )
    141 

~/Dropbox/code/nengo-loihi/nengo_loihi/builder/builder.py in build(self, obj, *args, **kwargs)
    220             add_params(obj)
    221 
--> 222         built = model.builder.build(model, obj, *args, **kwargs)
    223         if self.build_callback is not None:
    224             self.build_callback(obj)

~/Dropbox/code/nengo/nengo/builder/builder.py in build(cls, model, obj, *args, **kwargs)
    240             raise BuildError("Cannot build object of type %r" % type(obj).__name__)
    241 
--> 242         return cls.builders[obj_cls](model, obj, *args, **kwargs)
    243 
    244     @classmethod

~/Dropbox/code/nengo-loihi/nengo_loihi/builder/network.py in build_network(model, network, precompute, remove_passthrough, discretize)
     42 
     43         # Split blocks into blocks that will fit on cores
---> 44         split_model(model)
     45 
     46         if discretize:

~/Dropbox/code/nengo-loihi/nengo_loihi/builder/split_blocks.py in split_model(model)
    129     for probe in model.probes:
    130         print(probe.target)
--> 131         split_probe(probe, block_map, synapse_map)
    132 
    133     new_blocks = [block for group in block_map.values() for block in group]

~/Dropbox/code/nengo-loihi/nengo_loihi/builder/split_blocks.py in split_probe(probe, block_map, synapse_map)
    208     print('ids: ', ids.shape)
    209     print('old_comp: ', old_comp_ids.shape)
--> 210     assert ids.shape == old_comp_ids.shape
    211     assert np.array_equal(np.unique(ids), old_comp_ids)
    212 

AssertionError: 
```

Discussion:

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index adb146f..511e4b6 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -129,6 +129,9 @@ Release history
   (`#284 <https://github.com/nengo/nengo-loihi/pull/284>`__)
 - Fixed bug when connecting to a single neuron ensemble with a single scalar
   weight. (`#287 <https://github.com/nengo/nengo-loihi/pull/287>`__)
+- Added an error if more than 32 "populations" (e.g. convolutional filters) are used
+  with ``pop_type=16`` axons, since this is not yet supported by NxSDK.
+  (`#286 <https://github.com/nengo/nengo-loihi/pull/286>`__)
 
 0.10.0 (November 25, 2019)
 ==========================
diff --git a/nengo_loihi/builder/split_blocks.py b/nengo_loihi/builder/split_blocks.py
index fb527b8..57ed57d 100644
--- a/nengo_loihi/builder/split_blocks.py
+++ b/nengo_loihi/builder/split_blocks.py
@@ -344,6 +344,7 @@ def split_block(old_block, block_shapes):
             (ceil_div(n_compartments, n_split),), (n_compartments,)
         )
     old_block_shape = block_shapes[old_block]
+    assert old_block_shape.ensemble_size == old_block.n_neurons
 
     # find compartment indices for each new block
     new_block_inds = []
diff --git a/nengo_loihi/config.py b/nengo_loihi/config.py
index 4bae6ff..57cca5b 100644
--- a/nengo_loihi/config.py
+++ b/nengo_loihi/config.py
@@ -1,4 +1,6 @@
 import nengo
+from nengo import Ensemble
+from nengo.config import InstanceParams
 from nengo.exceptions import ValidationError
 from nengo.params import Parameter
 import numpy as np
@@ -8,7 +10,25 @@ from nengo_loihi.compat import is_integer, nengo_transforms
 
 class BlockShapeParam(Parameter):
     def coerce(self, instance, block_shape):
+        if isinstance(instance, InstanceParams):
+            instance = instance._configures
+
+        assert isinstance(instance, Ensemble), "Not implemented for non-Ensembles"
         self.check_type(instance, block_shape, BlockShape)
+
+        if instance.n_neurons != block_shape.ensemble_size:
+            raise ValidationError(
+                "Block shape ensemble size (`prod(%s) = %d`) must match "
+                "number of ensemble neurons (%d)"
+                % (
+                    list(block_shape.ensemble_shape),
+                    block_shape.ensemble_size,
+                    instance.n_neurons,
+                ),
+                attr=self.name,
+                obj=instance,
+            )
+
         return super().coerce(instance, block_shape)
 
 
diff --git a/nengo_loihi/hardware/builder.py b/nengo_loihi/hardware/builder.py
index 5669b53..7b3cf99 100644
--- a/nengo_loihi/hardware/builder.py
+++ b/nengo_loihi/hardware/builder.py
@@ -436,6 +436,11 @@ def build_synapse(nxsdk_core, core, block, synapse, compartment_idxs):  # noqa C
     atom_bits = synapse.atom_bits()
     axon_bits = synapse.axon_bits()
     atom_bits_extra = synapse.atom_bits_extra()
+    if atom_bits_extra > 0:
+        raise NotImplementedError(
+            "Using more than 32 'populations' (e.g. convolutional filters) with "
+            "`pop_type=16` axons has not yet been implemented in NxSDK"
+        )
 
     target_compartments = set()
     synapse_map = {}  # map weight_idx to (ptr, pop_size, len)


Test patch:
diff --git a/nengo_loihi/tests/test_config.py b/nengo_loihi/tests/test_config.py
index abc2745..7fa6231 100644
--- a/nengo_loihi/tests/test_config.py
+++ b/nengo_loihi/tests/test_config.py
@@ -1,7 +1,8 @@
+import nengo
 from nengo.exceptions import ValidationError
 import pytest
 
-from nengo_loihi.config import BlockShape
+from nengo_loihi.config import add_params, BlockShape
 
 
 def test_block_shape_errors():
@@ -14,6 +15,13 @@ def test_block_shape_errors():
     with pytest.raises(ValidationError, match="[Mm]ust be the same length"):
         BlockShape((2, 2), (8,))
 
+    with nengo.Network() as net:
+        add_params(net)
+        a = nengo.Ensemble(10, 1)
+
+        with pytest.raises(ValidationError, match="Block shape ensemble size"):
+            net.config[a].block_shape = BlockShape((3, 2), (6, 2))
+
 
 def test_block_shape_1d():
     block_shape = BlockShape((5,), (15,))