This page was generated from /builds/synsense/rockpool/docs/devices/quick-xylo/xylo-audio-2-intro.ipynb. Interactive online version: Binder badge

🐝🎡 Introduction to Xylo-Audio

Xylo-Audio is a platform for audio sensory processing, combining a low-power analog audio front-end with a low-power SNN inference core. Xylo-Audio is designed for sub-mW audio processing, in always-on applications.

This notebook gives you an overview of interfacing from Rockpool to the various cores of Xylo-Audio.

See also 🐝⚑️ Quick-start with Xylo and Using the analog frontend model .

[1]:
# - Display images
from IPython.display import Image

Image('xylo-a2-block-level.png')
[1]:
../../_images/devices_quick-xylo_xylo-audio-2-intro_2_0.png

Part I: Using the Xylo-Audio v2 SNN Core

Interfacing with the Xylo SNN core is the same as for other Xylo family devices, making use of the Xylo deployment pipeline; syns61201.XyloSim; and syns61201.XyloSamna.

See also 🐝⚑️ Quick-start with Xylo and 🐝 Overview of the Xylo family.

[2]:
Image('XyloSamna.png', width=400)
[2]:
../../_images/devices_quick-xylo_xylo-audio-2-intro_5_0.png

Step 1: Build a network in rockpool and convert it to a hardware configuration

[3]:
# - Import the computational modules and combinators required for the network
from rockpool.nn.modules import LIFTorch, LinearTorch
from rockpool.nn.combinators import Sequential, Residual
import rockpool.devices.xylo.syns61201 as x
from rockpool.devices.xylo.syns61201 import xa2_devkit_utils as xu
from rockpool.transform import quantize_methods as q
from rockpool import TSEvent, TSContinuous

import numpy as np

try:
    from rich import print
except:
    pass

import matplotlib.pyplot as plt
%matplotlib inline
plt.rcParams['figure.figsize'] = [12, 4]
plt.rcParams['figure.dpi'] = 300

# - Disable warnings
import warnings
warnings.filterwarnings('ignore')

from IPython import display
[4]:
# - Define the size of the network layers
Nin = 2
Nhidden = 4
Nout = 2
dt = 1e-3
[5]:
# - Define the network architecture using combinators and modules
net = Sequential(
    LinearTorch((Nin, Nhidden), has_bias = False),
    LIFTorch(Nhidden, dt = dt),

    Residual(
        LinearTorch((Nhidden, Nhidden), has_bias = False),
        LIFTorch(Nhidden, has_rec = True, threshold = 1., dt = dt),
    ),

    LinearTorch((Nhidden, Nout), has_bias = False),
    LIFTorch(Nout, dt = dt),
)
print(net)

# - Scale down recurrent weights for stability
# net[2][1].w_rec.data = net[2][1].w_rec / 10.
TorchSequential  with shape (2, 2) {
    LinearTorch '0_LinearTorch' with shape (2, 4)
    LIFTorch '1_LIFTorch' with shape (4, 4)
    TorchResidual '2_TorchResidual' with shape (4, 4) {
        LinearTorch '0_LinearTorch' with shape (4, 4)
        LIFTorch '1_LIFTorch' with shape (4, 4)
    }
    LinearTorch '3_LinearTorch' with shape (4, 2)
    LIFTorch '4_LIFTorch' with shape (2, 2)
}
[7]:
# - Call the Xylo mapper on the extracted computational graph
spec = x.mapper(net.as_graph(),  weight_dtype='float', threshold_dtype='float', dash_dtype='float')

# - Quantize the specification
spec.update(q.global_quantize(**spec))

# # you can also try channel-wise quantization
# spec.update(q.channel_quantize(**spec))
# print(spec)

# - Use rockpool.devices.xylo.config_from_specification to convert it to a hardware configuration
config, is_valid, msg = x.config_from_specification(**spec)
if not is_valid:
    print(msg)
[8]:
# - Use rockpool.devices.xylo.find_xylo_hdks to connect to an HDK
from rockpool.devices.xylo import find_xylo_hdks
xylo_hdk_nodes, modules, versions = find_xylo_hdks()
print(versions)

if len(xylo_hdk_nodes) == 0 or versions[0] is not 'syns61201':
    assert False, 'This tutorial requires a connected Xylo A2 HDK to demonstrate.'
else:
    db = xylo_hdk_nodes[0]
The connected Xylo HDK contains a Xylo Audio v2 (SYNS61201). Importing `rockpool.devices.xylo.syns61201`
['syns61201']
[9]:
# - Use XyloSamna to deploy to the HDK
modSamna = x.XyloSamna(db, config, dt = dt)
print(modSamna)
XyloSamna  with shape (2, 8, 2)
[10]:
# - Generate some Poisson input
T = 100
f = 0.4
input_spikes = np.random.rand(T, Nin) < f
TSEvent.from_raster(input_spikes, dt, name = 'Poisson input events').plot();
../../_images/devices_quick-xylo_xylo-audio-2-intro_13_0.png
[11]:
# - Evolve the network on the Xylo HDK
modSamna.reset_state()
out, _, r_d = modSamna(input_spikes, record = True)

# - Show the internal state variables recorded
print(r_d.keys())
dict_keys(['Vmem', 'Isyn', 'Isyn2', 'Spikes', 'Vmem_out', 'Isyn_out', 'times'])
[12]:
# - Plot some internal state variables
plt.figure()
plt.imshow(r_d['Spikes'].T, aspect = 'auto', origin = 'lower')
plt.title('Hidden spikes')
plt.ylabel('Channel')

plt.figure()
TSContinuous(r_d['times'], r_d['Isyn'], name = 'Hidden synaptic currents').plot(stagger = 127)

plt.figure()
TSContinuous(r_d['times'], r_d['Vmem'], name = 'Hidden membrane potentials').plot(stagger = 127);
../../_images/devices_quick-xylo_xylo-audio-2-intro_15_0.png
../../_images/devices_quick-xylo_xylo-audio-2-intro_15_1.png
../../_images/devices_quick-xylo_xylo-audio-2-intro_15_2.png

Step 3: Simulate the HDK using the XyloSim bit-precise simulator

[13]:
# - Configure the simulator with the HW network config
modSim = x.XyloSim.from_config(config, dt=dt)
print(modSim)
XyloSim  with shape (16, 1000, 8)
[14]:
# - Evolve the input over the network, in simulation
out, _, r_d = modSim(input_spikes, record = True)

# - Show the internal state variables recorded
print(r_d.keys())
dict_keys(['Vmem', 'Isyn', 'Isyn2', 'Spikes', 'Vmem_out', 'Isyn_out'])
[15]:
# - Plot some internal state variables
plt.figure()
plt.imshow(r_d['Spikes'].T, aspect = 'auto', origin = 'lower')
plt.title('Hidden spikes')
plt.ylabel('Channel')

plt.figure()
TSContinuous.from_clocked(r_d['Isyn'], dt, name = 'Hidden synaptic currents').plot(stagger = 127);

plt.figure()
TSContinuous.from_clocked(r_d['Vmem'], dt, name = 'Hidden membrane potentials').plot(stagger = 127);
../../_images/devices_quick-xylo_xylo-audio-2-intro_19_0.png
../../_images/devices_quick-xylo_xylo-audio-2-intro_19_1.png
../../_images/devices_quick-xylo_xylo-audio-2-intro_19_2.png

Part II: Using the Xylo-Audio v2 audio front-end interface

The AFE (Audio Front-End) is used to preprocess single-channel audio signals and convert them into spikes. Here the audio signal is input to the AFE by a microphone mounted on the hardware dev kit, or an external differential analog audio signal. The AFE has 16 output channels, and you can adjust its parameters via hyperparameters in the AFESamna class.

See also the introductory notebook Using the analog frontend model .

AFESamna allows you to access the audio front-end on the dev kit, and record encoded audio either from the on-board microphone or analog audio injected to the dev kit.

AFESamna also allows a custom config input which is without auto-calibration. If you do not provide a custom config, we highly suggest you set auto_calibrate = True on instantiation, which helps to mitigate the effects of background and mechanical noise.

[16]:
Image('AFESamna.png', width=400)
[16]:
../../_images/devices_quick-xylo_xylo-audio-2-intro_22_0.png
[17]:
# - Find and connect to a Xylo A2 HDK
xylo_hdk_nodes, modules, versions = find_xylo_hdks()
print(xylo_hdk_nodes)

if len(xylo_hdk_nodes) == 0 or versions[0] is not 'syns61201':
    assert False, 'This tutorial requires a connected Xylo A2 HDK to demonstrate.'
else:
    db = xylo_hdk_nodes[0]
The connected Xylo HDK contains a Xylo Audio v2 (SYNS61201). Importing `rockpool.devices.xylo.syns61201`
[<samna.xyloA2TestBoard.XyloA2TestBoard object at 0x299255bf0>]
[18]:
# - Set the time resolution and duration to record encoded audio
dt = 10e-3
timesteps = 1000
[19]:
# - Create an AFESamna module, which wraps the AFE on the Xylo A2 HDK
mod = x.AFESamna(db, None, dt=dt, auto_calibrate=True, amplify_level='low', hibernation_mode=False)
print(mod)

# - Evolve the module to record encoded real-time audio as events
spikes_ts, _, _ = mod(np.zeros([0, timesteps, 0]))
Configuring AFE...
Configured AFE
AFESamna  with shape (0, 16)
[20]:
# - Plot some encoded audio events recorded from the AFE
plt.imshow(spikes_ts.T, aspect='auto', interpolation='none')
plt.title('#Spikes in AFE output channels')
plt.xlabel('Time')
plt.ylabel('Channel')
plt.show()

../../_images/devices_quick-xylo_xylo-audio-2-intro_26_0.png

You can record encoded audio using the AFESamna class, and feed these outputs to the Xylo core for testing using XyloSamna. You can also combine the two cores for inference, as shown in the following section.

Part III: Deploying the AFE and SNN cores in free-running inference mode

Once you have a complete chip HW specification, you can deploy it to the chip in real-time infrence mode, using the class XyloMonitor. This mode uses the AFE core to pre-process audio signals in real time, then send encoded audio to the SNN core for inference. In this mode you only read the output events from the SNN core, without providing input.

[21]:
Image('XyloMonitor.png', width=400)
[21]:
../../_images/devices_quick-xylo_xylo-audio-2-intro_30_0.png
[22]:
# - Find and connect to a Xylo A2 HDK
xylo_hdk_nodes, modules, versions = find_xylo_hdks()
print(xylo_hdk_nodes)

if len(xylo_hdk_nodes) == 0 or versions[0] is not 'syns61201':
    assert False, 'This tutorial requires a connected Xylo A2 HDK to demonstrate.'
else:
    db = xylo_hdk_nodes[0]
The connected Xylo HDK contains a Xylo Audio v2 (SYNS61201). Importing `rockpool.devices.xylo.syns61201`
[<samna.xyloA2TestBoard.XyloA2TestBoard object at 0x299255bf0>]
[23]:
# - Use XyloMonitor to deploy to the HDK
# - You need to wait 45s until the AFE auto-calibration is done

output_mode = "Vmem"
amplify_level = "low"
hibernation = False
DN = False
T = 10

modMonitor = x.XyloMonitor(db, config, dt=dt, output_mode=output_mode, amplify_level=amplify_level, hibernation_mode=hibernation, divisive_norm=DN)
Configuring AFE...
Configured AFE
[24]:
# - A resultList stack to store the results

class ResultList(object):
    def __init__(self, max_len=100):
        self._list = []
        self.max_len = max_len

    def reset(self):
        self._list = []

    def append(self, num):
        if len(self._list) < self.max_len:
            self._list.append(num)
        else:
            self._list[: self.max_len - 1] = self._list[1:]
            self._list[self.max_len - 1] = num

    def is_full(self):
        if len(self._list) == self.max_len:
            return True
        else:
            return False

    def counts(self, features=[]):
        count = 0
        for _ in self._list:
            if _ in features:
                count += 1
        return count

    def __len__(self):
        return len(self._list)

    def print_result(self):
        return self._list


[25]:
# - Draw a real time image for output channels
lines = [ResultList(max_len=10) for _ in range(Nout)]
time_base = ResultList(max_len=10)
tt = 0
t_inference = 10.


from time import time

t_start = time()

while (time() - t_start) < t_inference:
    # - Perform inference on the Xylo A2 HDK
    output, _, _ = modMonitor(input_data=np.zeros((T, Nin)))
    if output is not None:
        output = np.max(output, axis=0)
        for i in range(Nout):
            lines[i].append(output[i])

        time_base.append(tt)
        tt += 0.1
        ax_time = time_base.print_result()

        for i in range(Nout):
            plt.plot(ax_time, lines[i].print_result(), label=f"class{i}")

        plt.xlabel('time')
        plt.ylabel('Vmem')
        plt.legend()
        plt.pause(0.1)
        display.clear_output(wait=True)
../../_images/devices_quick-xylo_xylo-audio-2-intro_34_0.png

Part IV: Measuring power on the Xylo HDK

XyloSamna provides an interface to real-time power measurements on the Xylo-Audio 2 HDK. Current on several power nets on the chip can be sampled asynchronously, while the device is in operation.

The evolve() method provides a power measurement interface while the device is in inference mode, and the XyloSamna module can be used also to measure idle power.

[27]:
# - Find and connect to a Xylo A2 HDK
xylo_hdk_nodes, modules, versions = find_xylo_hdks()
print(xylo_hdk_nodes)

if len(xylo_hdk_nodes) == 0 or versions[0] is not 'syns61201':
    assert False, 'This tutorial requires a connected Xylo A2 HDK to demonstrate.'
else:
    db = xylo_hdk_nodes[0]
The connected Xylo HDK contains a Xylo Audio v2 (SYNS61201). Importing `rockpool.devices.xylo.syns61201`
[<samna.xyloA2TestBoard.XyloA2TestBoard object at 0x299255bf0>]

On instantiation, XyloSamna can specify a power sampling frequency. By default, power is measured at 5 Hz.

[35]:
# - Set a low clock frequency for the Xylo device
x.xa2_devkit_utils.set_xylo_core_clock_freq(db, 6.25)

# - Use XyloSamna to deploy to the HDK
modSamna = x.XyloSamna(db, config, dt = 10e-3, power_frequency=20.)
print(modSamna)
XyloSamna  with shape (2, 8, 2)
[38]:
# - Generate some Poisson input
T = 1000
f = 0.4
input_spikes = np.random.rand(T, Nin) < f

# - Evolve some input on the SNN core, and record power during inference
out, _, record_dict = modSamna(input_spikes, record_power = True)

print(record_dict)
{
    'io_power': array([0.00010319, 0.00010395, 0.000103  , 0.00010395, 0.00010529,
       0.00010319]),
    'logic_afe_power': array([1.07421875e-05, 1.22528076e-05, 9.56726074e-06, 1.09100342e-05,
       1.07421875e-05, 9.90295410e-06]),
    'io_afe_power': array([8.96453857e-06, 6.29425049e-06, 8.20159912e-06, 6.48498535e-06,
       9.72747803e-06, 7.43865967e-06]),
    'logic_power': array([0.00027166, 0.00027309, 0.00027233, 0.00026931, 0.00027107,
       0.00026998])
}

Power is sampled on four nets, and is returned in Watts.

β€˜io_power’ is the total I/O power of the device. β€˜logic_power’ is the power consumption of the digital SNN core and control logic. β€˜logic_afe_power’ is the power of the analog audio front-end core. β€˜io_afe_power’ is the power consumption of the internal low-drop-out voltage supply used by the AFE.

[44]:
# - Measure idle power (no evolution)
from time import sleep

modSamna._power_buf.get_events()
sleep(5.)
power = modSamna._power_buf.get_events()

power_idle = ([], [], [], [])

for p in power:
    power_idle[p.channel].append(p.value)

idle_power_per_channel = np.mean(np.stack(power_idle), axis = 1)

print(f'All IO:\t\t{idle_power_per_channel[0] * 1e6}Β΅W\nAFE analog:\t{idle_power_per_channel[1] * 1e6}Β΅W\nInternal LDO:\t{idle_power_per_channel[2]*1e6}Β΅W\nSNN core logic:\t{idle_power_per_channel[3]*1e6}Β΅W')
All IO:         99.84207153320312Β΅W
AFE analog:     10.385513305664062Β΅W
Internal LDO:   7.274627685546874Β΅W
SNN core logic: 250.0865173339844Β΅W

Hints on reducing power consumption

Several points may be useful in reducing active power on Xylo Audio.

  • Reducing the core clock frequency. The dev-kit utility function devices.xylo.syns61201.xa2_devkit_utils.set_xylo_core_clock_freq() can be used to set a lower core clock frequency than the default of 50 Mhz, lowering power consumption proportionally. Depending on your network size and complexity you can most likely reduce the clock frequency and still guarantee real-time operation.

  • Reducing network activity and network size. The SNN core within Xylo only works as hard as it needs to. Once all neuron states have been updated, the core performs no operations until the next network time-step. This conserves energy.

[ ]:
help(x.xa2_devkit_utils.set_xylo_core_clock_freq)
Help on function set_xylo_core_clock_freq in module rockpool.devices.xylo.syns61201.xa2_devkit_utils:

set_xylo_core_clock_freq(device: Any, desired_freq_MHz: float) -> float
    Set the inference core clock frequency used by Xylo

    Args:
        device (XyloA2HDK): A Xylo2 device to configure
        desired_freq_MHz (float): The desired Xylo2 core clock frequency in MHz

    Returns:
        float: The obtained Xylo2 core clock frequency in MHz