Power Monitoring#

This notebook demonstrates how to monitor the power consumption of the speck2e devkit.

! pip install matplotlib
Requirement already satisfied: matplotlib in /home/vleite/.pyenv/versions/sinabs/lib/python3.12/site-packages (3.10.0)
Requirement already satisfied: contourpy>=1.0.1 in /home/vleite/.pyenv/versions/sinabs/lib/python3.12/site-packages (from matplotlib) (1.3.1)
Requirement already satisfied: cycler>=0.10 in /home/vleite/.pyenv/versions/sinabs/lib/python3.12/site-packages (from matplotlib) (0.12.1)
Requirement already satisfied: fonttools>=4.22.0 in /home/vleite/.pyenv/versions/sinabs/lib/python3.12/site-packages (from matplotlib) (4.56.0)
Requirement already satisfied: kiwisolver>=1.3.1 in /home/vleite/.pyenv/versions/sinabs/lib/python3.12/site-packages (from matplotlib) (1.4.8)
Requirement already satisfied: numpy>=1.23 in /home/vleite/.pyenv/versions/sinabs/lib/python3.12/site-packages (from matplotlib) (1.26.4)
Requirement already satisfied: packaging>=20.0 in /home/vleite/.pyenv/versions/sinabs/lib/python3.12/site-packages (from matplotlib) (24.2)
Requirement already satisfied: pillow>=8 in /home/vleite/.pyenv/versions/sinabs/lib/python3.12/site-packages (from matplotlib) (11.1.0)
Requirement already satisfied: pyparsing>=2.3.1 in /home/vleite/.pyenv/versions/sinabs/lib/python3.12/site-packages (from matplotlib) (3.2.1)
Requirement already satisfied: python-dateutil>=2.7 in /home/vleite/.pyenv/versions/sinabs/lib/python3.12/site-packages (from matplotlib) (2.9.0.post0)
Requirement already satisfied: six>=1.5 in /home/vleite/.pyenv/versions/sinabs/lib/python3.12/site-packages (from python-dateutil>=2.7->matplotlib) (1.17.0)
import samna
import samnagui
import time
import torch
import socket
import matplotlib.pyplot as plt
import matplotlib.font_manager as font_manager
import sinabs.backend.dynapcnn.io as sio

from torch import nn
from multiprocessing import Process
from sinabs.from_torch import from_model
from sinabs.backend.dynapcnn import DynapcnnNetwork
assert (
    samna.__version__ >= "0.21.8"
), f"samna version {samna.__version__} is too low for this experiment"

1. Power consumption of the devkit#

The power consumption mainly consist of the following 5 parts:

  1. IO: IO power consumption.

  2. Ram: Power consumption of read and write of ram.

  3. Logic: Logic operation power consumption.

  4. VDDD: DVS digital power consumption.

  5. VDDA: DVS analog power consumption.

On the devkit there are 5 channels of power tracks:

  • channel 0: VDD_IO

  • channel 1: VDD_RAM

  • channel 2: VDD_LOGIC

  • channel 3: VDD_PIXEL_DIGITAL

  • channel 4: VDD_PIXEL_ANALOG

we can monitor each type of the power consumption based on each relevant power track.

Samna provides a PowerMonitor for users to read the power from the power tracks. By calling the start_auto_power_measurement method of the PowerMonitor instance, we can start read the power consumption statistics.

To measure the power consumption, we need add a samna.BasicSinkNode_unifirm_modules_events_measurement node into the samna graph, combined with the PowerMonitor we can read the power statistics from this node.

2. Monitor the idle power#

Here we demonstrate the way of monitoring the idle power of the devkit.

# get device name
devices = samna.device.get_all_devices()
device_names = [each.device_type_name for each in devices]
device_names
['Speck2fDevKit']
# open devkit
devkit = samna.device.open_device(device_names[0])

# get the handle of the stop-watch of the devkit
stop_watch = devkit.get_stop_watch()

# get the handle of the power monitor of the devkit
power_monitor = devkit.get_power_monitor()

# create samna node for power reading
power_source_node = power_monitor.get_source_node()
power_buffer_node = samna.BasicSinkNode_unifirm_modules_events_measurement()

build samna graph for the power monitoring

idle_power_graph

# init graph
samna_graph = samna.graph.EventFilterGraph()

# build graph
samna_graph.sequential([power_source_node, power_buffer_node])
time.sleep(0.5)

then we can start mearsuring the idle power of the devkit

measure_time = 5.0  # seconds
sample_rate = 100.0  # Hz

# start samna graph
samna_graph.start()

# start the stop-watch of devkit, then each output data has a proper timestamp
stop_watch.set_enable_value(True)

# clear buffer
power_buffer_node.get_events()

# start monitor, we need pass a sample rate argument to the power monitor
power_monitor.start_auto_power_measurement(sample_rate)

# sleep the procees to wait for the measurement time up
time.sleep(measure_time)

# stop monitor
power_monitor.stop_auto_power_measurement()

# stop samna graph
samna_graph.stop()

# get power-measurement data
power_events = power_buffer_node.get_events()
# time * sample rate * number of power tracks
estimated_number_of_data = measure_time * sample_rate * 5
print(f"estimated number of collect data: {estimated_number_of_data}")

print(f"number of collected data: {len(power_events)}")
estimated number of collect data: 2500.0
number of collected data: 2498

parse the collected data

num_power_tracks = 5

# init dict for storing data of each power track
power_each_track = dict()
event_count_each_track = dict()

# check whether timestamp is correct
timestamp_all_zero = True

# loop through all collected power events and get data
for evt in power_events:
    if evt.timestamp != 0:
        timestamp_all_zero = False

    p_track_id = evt.channel
    tmp_power = power_each_track.get(p_track_id, 0) + evt.value
    tmp_count = event_count_each_track.get(p_track_id, 0) + 1

    power_each_track.update({p_track_id: tmp_power})
    event_count_each_track.update({p_track_id: tmp_count})

# average power and current of each track
for p_track_id in range(num_power_tracks):
    # average power in microwatt
    avg_power = power_each_track[p_track_id] / event_count_each_track[p_track_id] * 1e6
    # calculate current
    if p_track_id == 0:
        current = avg_power / 2.5
    else:
        current = avg_power / 1.2

    print(f"track{p_track_id}: {avg_power}uW, {current}uA")
track0: 61.67912292480463uW, 24.671649169921853uA
track1: 51.723632812499645uW, 43.10302734374971uA
track2: 366.9914899553563uW, 305.8262416294636uA
track3: 78.87112114854692uW, 65.72593429045577uA
track4: 610.8792641085738uW, 509.0660534238115uA

plot the power data

if timestamp_all_zero:
    print(
        "timestamps are all zeros, can't plot power vs. time, you might need to update the firmware!"
    )

else:
    # plot the output neuron index vs. time
    plt.style.use("ggplot")
    fig, ax = plt.subplots()
    p_track_name = ["io", "ram", "logic", "pixel digital", "pixel analog"]
    for p_track_id in range(num_power_tracks):
        x = [each.timestamp for each in power_events if each.channel == p_track_id]
        y = [each.value * 1e6 for each in power_events if each.channel == p_track_id]
        plt.plot(x, y, label=p_track_name[p_track_id], alpha=0.8)

    ax.set_xlabel("time(us)")
    ax.set_ylabel("power(uW)")
    ax.set_title("Idle Power")
    ax.legend(loc="upper right", fontsize=10)
../../_images/15231d76a58ede57afabaeacfeabb56a8d9389a46c975a5482c6d125c15f6b16.png
# close devkit after experiment finished
samna.device.close_device(devkit)

3. Monitor the dynamic power#

In this section we introduce the way to monitor the dynamic power of the devkit i.e. the power after we deploy an SNN on it.

Also, we introduce a GUI module of samna which helps to monitor the real-time power plot.

real_time_power_plot

3.1 Create a simple SNN#

# create a one layer CNN
input_shape = (1, 128, 128)

cnn = nn.Sequential(
    nn.Conv2d(
        in_channels=1,
        out_channels=1,
        kernel_size=(1, 1),
        stride=(2, 2),
        padding=(0, 0),
        bias=False,
    ),
    nn.ReLU(),
)

# assign a handcraft weight to CNN
cnn[0].weight.data = torch.ones_like(cnn[0].weight.data, dtype=torch.float32) * 0.05
# cnn to snn
snn = from_model(cnn, input_shape=input_shape, batch_size=1).spiking_model

# snn to DynapcnnNetwork
dynapcnn_net = DynapcnnNetwork(snn=snn, input_shape=input_shape, dvs_input=True)

3.2 Create a devkit config based on the SNN#

dynapcnn_device_str = "speck2fdevkit:0"
devkit_cfg = dynapcnn_net.make_config(
    device=dynapcnn_device_str, monitor_layers=["dvs"]
)
Network is valid

3.3 Construct a samna graph for power monitoring & visualization#

dynamic_power_graph

# get device name
devices = samna.device.get_all_devices()
device_names = [each.device_type_name for each in devices]
device_names

# open devkit
devkit = samna.device.open_device(device_names[0])

# get the handle of the stop-watch of the devkit
stop_watch = devkit.get_stop_watch()

# reset the stop-watch of devkit
# stop_watch = devkit.get_stop_watch()
# stop_watch.set_enable_value(True)
# stop_watch.reset()
# time.sleep(0.01)

# get the handle of the power monitor of the devkit
power_monitor = devkit.get_power_monitor()

# create samna node for power reading
power_source_node = power_monitor.get_source_node()
power_buffer_node = samna.BasicSinkNode_unifirm_modules_events_measurement()

# init samna graph
samna_graph = samna.graph.EventFilterGraph()


"""
=====  construct branches in the graph to define how data flows  =====
"""
# branch #1: DVS data visualization on GUI
_, _, streamer = samna_graph.sequential(
    [devkit.get_model_source_node(), "Speck2fDvsToVizConverter", "VizEventStreamer"]
)

# branch #2: Collect power data
samna_graph.sequential([power_source_node, power_buffer_node])

# branch #3: Real time power data visualization on GUI
samna_graph.sequential([power_source_node, "MeasurementToVizConverter", streamer])

# define tcp port for data visualization
streamer_endpoint = "tcp://0.0.0.0:40000"
streamer.set_streamer_endpoint(streamer_endpoint)

3.4 Setup the GUI visualizer of samna#

def free_port():
    """
    Determines a free port using sockets.
    """
    free_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    free_socket.bind(("0.0.0.0", 0))
    free_socket.listen(5)
    port = free_socket.getsockname()[1]
    free_socket.close()
    return port


# Specify the tcp port of the visualizer
visualizer_port = "tcp://0.0.0.0:" + str(free_port())

# Launch visualizer
gui_process = sio.launch_visualizer(
    receiver_endpoint=visualizer_port, disjoint_process=False
)
# Visualizer configuration branch of the graph.
visualizer_config, _ = samna_graph.sequential(
    [samna.BasicSourceNode_ui_event(), streamer]  # For generating UI commands
)

# Connect to the visualizer
streamer.set_streamer_endpoint(visualizer_port)
# start the samna graph
samna_graph.start()

plot1 = samna.ui.ActivityPlotConfiguration(
    image_width=128, image_height=128, title="DVS Layer", layout=[0, 0, 0.5, 0.89]
)

plot2 = samna.ui.PowerMeasurementPlotConfiguration(
    "Power Measurement Plot",
    5,
    ["io", "ram", "logic", "vdd", "vda"],
    (0, 0.75, 1.0, 1.0),
    10,
    2,
    "",
    1.5,
    False,
    1,
    "Power consumption",
    "Time (s)",
    "Power (mW)",
)

visualizer_config.write([samna.ui.VisualizerConfiguration(plots=[plot1, plot2])])

print("Now you should see a change on the GUI window!")
Now you should see a change on the GUI window!

3.5 Apply devkit config and launch the devit#

# apply devkit config
devkit.get_model().apply_configuration(devkit_cfg)
time.sleep(0.1)

print("Now you should see the input from the dvs sensor on the GUI window!")
Now you should see the input from the dvs sensor on the GUI window!

3.6 Start to monitor dynamic power#

sample_rate = 100
power_monitor.start_auto_power_measurement(sample_rate)

print("Now you should see the real-time power plot shows on the GUI window!")
Now you should see the real-time power plot shows on the GUI window!
# stop the experiment
gui_process.terminate()
gui_process.join()

samna_graph.stop()
samna.device.close_device(devkit)