- 2to3 was used to convert the Python scripts, except where the tool choked and manual intervention was required - All references to "python" where replaced with "python3" - buffer() was replaced by memoryview() Original-commit: ca68195b5d12c5410cfac8d459a0b0902c4c72c7
758 lines
27 KiB
Python
758 lines
27 KiB
Python
#!/usr/bin/env python
|
|
#
|
|
# Copyright 2016 Ettus Research
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
|
|
import collections
|
|
import copy
|
|
import re
|
|
import math
|
|
import numpy as np
|
|
import matplotlib.pyplot as plt
|
|
import matplotlib.ticker as mticker
|
|
from graphviz import Digraph
|
|
|
|
#------------------------------------------------------------
|
|
# Simulator Core Components
|
|
#------------------------------------------------------------
|
|
class comptype():
|
|
"""
|
|
Simulation component type enumeration
|
|
"""
|
|
producer = 'Producer'
|
|
consumer = 'Consumer'
|
|
channel = 'Channel'
|
|
function = 'Function'
|
|
hardware = 'Hardware'
|
|
other = 'Other'
|
|
|
|
class SimulatorCore:
|
|
"""
|
|
Core simulation engine:
|
|
This class owns all the simulation components and
|
|
manages time and other housekeeping operations.
|
|
"""
|
|
|
|
def __init__(self, tick_rate):
|
|
self.__ticks = 0
|
|
self.__tick_rate = tick_rate
|
|
self.__tick_aware_comps = list()
|
|
self.__all_comps = dict()
|
|
self.__edge_render_db = list()
|
|
|
|
def register(self, comp, tick_aware):
|
|
if comp.name not in self.__all_comps:
|
|
self.__all_comps[comp.name] = comp
|
|
else:
|
|
raise RuntimeError('Duplicate component ' + comp.name)
|
|
if tick_aware:
|
|
self.__tick_aware_comps.append(comp)
|
|
|
|
def connect(self, src, srcport, dst, dstport, render_label=None, render_color=None):
|
|
src.connect(srcport, dst.inputs(dstport, bind=True))
|
|
if render_label:
|
|
self.__edge_render_db.append(
|
|
(src.name, dst.name, 1.0, render_label, render_color))
|
|
|
|
def connect_bidir(self, ep1, ep1port, ep2, ep2port, render_labels=None, render_colors=None):
|
|
if render_labels:
|
|
if not isinstance(render_labels, (list, tuple)):
|
|
render_labels = [render_labels, render_labels]
|
|
else:
|
|
render_labels = [None, None]
|
|
if render_colors:
|
|
if not isinstance(render_colors, (list, tuple)):
|
|
render_colors = [render_colors, render_colors]
|
|
else:
|
|
render_colors = [None, None]
|
|
self.connect(ep1, ep1port, ep2, ep2port, render_labels[0], render_colors[0])
|
|
self.connect(ep2, ep2port, ep1, ep1port, render_labels[1], render_colors[1])
|
|
|
|
def connect_multi(self, src, srcports, dst, dstports, render_label=None, render_color=None):
|
|
if len(srcports) != len(dstports):
|
|
raise RuntimeError(
|
|
'Source and destination ports should be of the same length')
|
|
for i in range(len(srcports)):
|
|
src.connect(srcports[i], dst.inputs(dstports[i], bind=True))
|
|
if render_label:
|
|
self.__edge_render_db.append((src.name, dst.name, float(len(srcports)), render_label, render_color))
|
|
|
|
def connect_multi_bidir(self, ep1, ep1port, ep2, ep2port, render_labels=None, render_colors=None):
|
|
if render_labels:
|
|
if not isinstance(render_labels, (list, tuple)):
|
|
render_labels = [render_labels, render_labels]
|
|
else:
|
|
render_labels = [None, None]
|
|
if render_colors:
|
|
if not isinstance(render_colors, (list, tuple)):
|
|
render_colors = [render_colors, render_colors]
|
|
else:
|
|
render_colors = [None, None]
|
|
self.connect_multi(ep1, ep1port, ep2, ep2port, render_labels[0], render_colors[0])
|
|
self.connect_multi(ep2, ep2port, ep1, ep1port, render_labels[1], render_colors[1])
|
|
|
|
def list_components(self, comptype='', name_filt=''):
|
|
if not comptype:
|
|
return sorted([c for c in list(self.__all_comps.keys())
|
|
if (re.match(name_filt, self.__all_comps[c].name))])
|
|
else:
|
|
return sorted([c for c in list(self.__all_comps.keys())
|
|
if (self.__all_comps[c].type == comptype and
|
|
re.match(name_filt, self.__all_comps[c].name))])
|
|
|
|
def lookup(self, comp_name):
|
|
return self.__all_comps[comp_name]
|
|
|
|
def tick(self):
|
|
self.__ticks += 1
|
|
for c in self.__tick_aware_comps:
|
|
c.tick()
|
|
|
|
def run(self, time_s):
|
|
for i in range(int(time_s * self.__tick_rate)):
|
|
self.tick()
|
|
|
|
def get_ticks(self):
|
|
return self.__ticks
|
|
|
|
def get_tick_rate(self):
|
|
return self.__tick_rate
|
|
|
|
def network_to_dot(self):
|
|
dot = Digraph(comment='RFNoC Network Topology')
|
|
node_ids = dict()
|
|
next_node_id = 1
|
|
for edgeinfo in self.__edge_render_db:
|
|
for i in range(2):
|
|
node = edgeinfo[i]
|
|
if node not in node_ids:
|
|
node_id = next_node_id
|
|
node_ids[node] = node_id
|
|
dot.node(str(node_id), node)
|
|
next_node_id += 1
|
|
for edgeinfo in self.__edge_render_db:
|
|
dot.edge(
|
|
tail_name=str(node_ids[edgeinfo[0]]),
|
|
head_name=str(node_ids[edgeinfo[1]]),
|
|
label=edgeinfo[3],
|
|
weight=str(edgeinfo[2]), penwidth=str(edgeinfo[2]/2),
|
|
color=str(edgeinfo[4] if edgeinfo[4] else 'black'))
|
|
return dot
|
|
|
|
class SimComp:
|
|
"""
|
|
Base simulation component:
|
|
All components must inherit from SimComp.
|
|
"""
|
|
|
|
def __init__(self, sim_core, name, ctype):
|
|
self.__sim_core = sim_core
|
|
self.name = name
|
|
self.type = ctype
|
|
self.__sim_core.register(self, (ctype == comptype.producer))
|
|
|
|
def get_ticks(self):
|
|
return self.__sim_core.get_ticks()
|
|
|
|
def get_tick_rate(self):
|
|
return self.__sim_core.get_tick_rate()
|
|
|
|
def SimCompError(self, msg):
|
|
raise RuntimeError(msg + ' [' + self.name + ']')
|
|
|
|
#------------------------------------------------------------
|
|
# Data stream components
|
|
#------------------------------------------------------------
|
|
class HwRsrcs():
|
|
"""
|
|
Hardware Resources Container:
|
|
This object holds physical hardware resource information
|
|
that can be used to report utilization. Resource items are
|
|
generic and can be defined by the actual simulation.
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.__rsrcs = dict()
|
|
|
|
def get(self, what):
|
|
if what in self.__rsrcs:
|
|
return self.__rsrcs[what]
|
|
else:
|
|
return 0.0
|
|
|
|
def set(self, what, value):
|
|
self.__rsrcs[what] = float(value)
|
|
|
|
def add(self, what, value):
|
|
if what in self.__rsrcs:
|
|
self.__rsrcs[what] += float(value)
|
|
else:
|
|
self.__rsrcs[what] = float(value)
|
|
|
|
def merge(self, other_rsrcs):
|
|
for attr in other_rsrcs.get_attrs():
|
|
self.add(attr, other_rsrcs.get(attr))
|
|
|
|
def get_attrs(self):
|
|
return list(self.__rsrcs.keys())
|
|
|
|
def reset(self, what = None):
|
|
if what is not None:
|
|
if what in self.__rsrcs:
|
|
self.__rsrcs[what] = 0.0
|
|
else:
|
|
self.__rsrcs = dict()
|
|
|
|
class DataStream:
|
|
"""
|
|
Data Stream Object:
|
|
Holds information about a date stream that passes through various block.
|
|
The simulator simulates event on the actual stream so each stream Object
|
|
must have a unique payload (items) to disambiguate it from the rest.
|
|
"""
|
|
HopInfo = collections.namedtuple('HopInfo', ['location', 'latency'])
|
|
|
|
class HopDb():
|
|
def __init__(self, hops):
|
|
self.__hops = hops
|
|
|
|
def get_src(self):
|
|
return self.__hops[0].location
|
|
|
|
def get_dst(self):
|
|
return self.__hops[-1].location
|
|
|
|
def get_hops(self):
|
|
hoparr = []
|
|
for h in self.__hops:
|
|
hoparr.append(h.location)
|
|
return hoparr
|
|
|
|
def get_latency(self, ticks, location = ''):
|
|
latency = ticks - self.__hops[0].latency #Hop0 always has the init timestamp
|
|
if (self.__hops[0].location != location):
|
|
for i in range(1,len(self.__hops)):
|
|
latency += self.__hops[i].latency
|
|
if (self.__hops[i].location == location):
|
|
break
|
|
return latency
|
|
|
|
def __init__(self, bpi, items, count, producer=None, parent=None):
|
|
self.bpi = bpi
|
|
self.items = []
|
|
self.items.extend(items)
|
|
self.count = count
|
|
self.__hops = list()
|
|
if producer and parent:
|
|
raise RuntimeError('Data stream cannot have both a producer and a parent stream')
|
|
elif producer:
|
|
self.__hops.append(self.HopInfo(location='Gen@'+producer.name, latency=producer.get_ticks()))
|
|
elif parent:
|
|
self.__hops.extend(parent.get_hops())
|
|
else:
|
|
raise RuntimeError('Data stream must have a producer or a parent stream')
|
|
|
|
def add_hop(self, location, latency):
|
|
self.__hops.append(self.HopInfo(location=location, latency=latency))
|
|
|
|
def get_hops(self):
|
|
return self.__hops
|
|
|
|
def get_bytes(self):
|
|
return self.bpi * len(self.items) * self.count
|
|
|
|
"""
|
|
Type specific methods
|
|
"""
|
|
@staticmethod
|
|
def submatrix_gen(matrix_id, coordinates):
|
|
coord_arr = []
|
|
for c in coordinates:
|
|
if isinstance(c, collections.Iterable):
|
|
coord_arr.append('(' + (','.join(str(x) for x in c)) + ')')
|
|
else:
|
|
coord_arr.append('(' + str(c) + ')')
|
|
return matrix_id + '[' + ';'.join(coord_arr) + ']'
|
|
|
|
@staticmethod
|
|
def submatrix_parse(stream_id):
|
|
m = re.match('(.+)\[(.*)\]', stream_id)
|
|
matrix_id = m.group(1)
|
|
coords = []
|
|
for cstr in m.group(2).split(';'):
|
|
coords.append([int(x) for x in re.match('\((.+)\)', cstr).group(1).split(',')])
|
|
return (matrix_id, coords)
|
|
|
|
#------------------------------------------------------------
|
|
# Basic Network components
|
|
#------------------------------------------------------------
|
|
|
|
# Producer object.
|
|
class Producer(SimComp):
|
|
"""
|
|
Producer Block:
|
|
Generates data at a constant rate
|
|
"""
|
|
|
|
def __init__(self, sim_core, name, bpi, items, max_samp_rate = float('inf'), latency = 0):
|
|
SimComp.__init__(self, sim_core, name, comptype.producer)
|
|
self.__bpi = bpi
|
|
self.__items = items
|
|
self.__bw = max_samp_rate * bpi
|
|
self.__latency = latency
|
|
self.__dests = list()
|
|
self.__data_count = 0
|
|
self.__byte_count = 0
|
|
self.__backpressure_ticks = 0
|
|
self.set_rate(self.get_tick_rate())
|
|
|
|
def inputs(self, i, bind=False):
|
|
raise self.SimCompError('This is a producer block. Cannot connect another block to it.')
|
|
|
|
def connect(self, i, dest):
|
|
self.__dests.append(dest)
|
|
|
|
def set_rate(self, samp_rate):
|
|
self.__data_count = samp_rate / self.get_tick_rate()
|
|
|
|
def tick(self):
|
|
if len(self.__dests) > 0:
|
|
ready = True
|
|
for dest in self.__dests:
|
|
ready = ready and dest.is_ready()
|
|
if ready:
|
|
data = DataStream(
|
|
bpi=self.__bpi, items=self.__items, count=self.__data_count, producer=self)
|
|
if self.__backpressure_ticks > 0:
|
|
data.add_hop('BP@'+self.name, self.__backpressure_ticks)
|
|
data.add_hop(self.name, self.__latency)
|
|
for dest in self.__dests:
|
|
dest.push(copy.deepcopy(data))
|
|
self.__byte_count += data.get_bytes()
|
|
self.__backpressure_ticks = 0
|
|
else:
|
|
self.__backpressure_ticks += 1
|
|
|
|
def get_bytes(self):
|
|
return self.__byte_count
|
|
|
|
def get_util_attrs(self):
|
|
return ['bandwidth']
|
|
|
|
def get_utilization(self, what):
|
|
if what in self.get_util_attrs():
|
|
return ((self.__byte_count / (self.get_ticks() / self.get_tick_rate())) /
|
|
self.__bw)
|
|
else:
|
|
return 0.0
|
|
|
|
# Consumer object.
|
|
class Consumer(SimComp):
|
|
"""
|
|
Consumes Block:
|
|
Consumes data at a constant rate
|
|
"""
|
|
|
|
def __init__(self, sim_core, name, bw = float("inf"), latency = 0):
|
|
SimComp.__init__(self, sim_core, name, comptype.consumer)
|
|
self.__byte_count = 0
|
|
self.__item_db = dict()
|
|
self.__bw = bw
|
|
self.__latency = latency
|
|
self.__bound = False
|
|
|
|
def inputs(self, i, bind=False):
|
|
if bind and self.__bound:
|
|
raise self.SimCompError('Input ' + str(i) + ' is already driven (bound).')
|
|
self.__bound = bind
|
|
return self
|
|
|
|
def connect(self, i, dest):
|
|
raise self.SimCompError('This is a consumer block. Cannot connect to another block.')
|
|
|
|
def is_ready(self):
|
|
return True #TODO: Readiness can depend on bw and byte_count
|
|
|
|
def push(self, data):
|
|
data.add_hop(self.name, self.__latency)
|
|
for item in data.items:
|
|
self.__item_db[item] = DataStream.HopDb(data.get_hops())
|
|
self.__byte_count += data.get_bytes()
|
|
|
|
def get_items(self):
|
|
return list(self.__item_db.keys())
|
|
|
|
def get_bytes(self):
|
|
return self.__byte_count
|
|
|
|
def get_hops(self, item):
|
|
return self.__item_db[item].get_hops()
|
|
|
|
def get_latency(self, item, hop=None):
|
|
if not hop:
|
|
hop = self.get_hops(item)[-1]
|
|
return self.__item_db[item].get_latency(self.get_ticks(), hop) / self.get_tick_rate()
|
|
|
|
def get_util_attrs(self):
|
|
return ['bandwidth']
|
|
|
|
def get_utilization(self, what):
|
|
if what in self.get_util_attrs():
|
|
return ((self.__byte_count / (self.get_ticks() / self.get_tick_rate())) /
|
|
self.__bw)
|
|
else:
|
|
return 0.0
|
|
|
|
# Channel
|
|
class Channel(SimComp):
|
|
"""
|
|
A resource limited IO pipe:
|
|
From the data stream perspective, this is a passthrough
|
|
"""
|
|
|
|
def __init__(self, sim_core, name, bw = float("inf"), latency = 0, lossy = True):
|
|
SimComp.__init__(self, sim_core, name, comptype.channel)
|
|
self.__bw = bw
|
|
self.__latency = latency
|
|
self.__lossy = lossy
|
|
self.__dests = list()
|
|
self.__byte_count = 0
|
|
self.__bound = False
|
|
|
|
def get_bytes(self):
|
|
return self.__byte_count
|
|
|
|
def inputs(self, i, bind=False):
|
|
if (i != 0):
|
|
raise self.SimCompError('An IO lane has only one input.')
|
|
if bind and self.__bound:
|
|
raise self.SimCompError('Input ' + str(i) + ' is already driven (bound).')
|
|
self.__bound = bind
|
|
return self
|
|
|
|
def connect(self, i, dest):
|
|
self.__dests.append(dest)
|
|
|
|
def is_connected(self):
|
|
return len(self.__dests) > 0
|
|
|
|
def is_bound(self):
|
|
return self.__bound
|
|
|
|
def is_ready(self):
|
|
# If nothing is hooked up to a lossy lane, it will drop data
|
|
if self.__lossy and not self.is_connected():
|
|
return True
|
|
ready = self.is_connected()
|
|
for dest in self.__dests:
|
|
ready = ready and dest.is_ready()
|
|
return ready
|
|
|
|
def push(self, data):
|
|
# If nothing is hooked up to a lossy lane, it will drop data
|
|
if self.__lossy and not self.is_connected():
|
|
return
|
|
data.add_hop(self.name, self.__latency)
|
|
for dest in self.__dests:
|
|
dest.push(copy.deepcopy(data))
|
|
self.__byte_count += data.get_bytes()
|
|
|
|
def get_util_attrs(self):
|
|
return ['bandwidth']
|
|
|
|
def get_utilization(self, what):
|
|
if what in self.get_util_attrs():
|
|
return ((self.__byte_count / (self.get_ticks() / self.get_tick_rate())) /
|
|
self.__bw)
|
|
else:
|
|
return 0.0
|
|
|
|
# Function
|
|
class Function(SimComp):
|
|
"""
|
|
A Function Component:
|
|
A function block is something that does anything interesting with a data stream.
|
|
A function can have multiple input and output streams.
|
|
"""
|
|
|
|
class Arg:
|
|
def __init__(self, num, base_func):
|
|
self.__num = num
|
|
self.__data = None
|
|
self.__base_func = base_func
|
|
self.__bound = False
|
|
|
|
def get_num(self):
|
|
return self.__num
|
|
|
|
def is_ready(self):
|
|
return self.__base_func.is_ready() and not self.__data
|
|
|
|
def push(self, data):
|
|
self.__data = data
|
|
self.__base_func.notify(self.__num)
|
|
|
|
def pop(self):
|
|
if self.__data:
|
|
data = self.__data
|
|
self.__data = None
|
|
return data
|
|
else:
|
|
raise RuntimeError('Nothing to pop.')
|
|
|
|
def bind(self, bind):
|
|
retval = self.__bound
|
|
self.__bound = bind
|
|
return retval
|
|
|
|
Latencies = collections.namedtuple('Latencies', ['func','inarg','outarg'])
|
|
|
|
def __init__(self, sim_core, name, num_in_args, num_out_args, ticks_per_exec = 1):
|
|
SimComp.__init__(self, sim_core, name, comptype.function)
|
|
self.__ticks_per_exec = ticks_per_exec
|
|
self.__last_exec_ticks = 0
|
|
self.__in_args = list()
|
|
for i in range(num_in_args):
|
|
self.__in_args.append(Function.Arg(i, self))
|
|
self.__dests = list()
|
|
for i in range(num_out_args):
|
|
self.__dests.append(None)
|
|
self.__in_args_pushed = dict()
|
|
# Resources required by this function to do its job in one tick
|
|
self.__rsrcs = HwRsrcs()
|
|
self.__latencies = self.Latencies(func=0, inarg=[0]*num_in_args, outarg=[0]*num_out_args)
|
|
|
|
def get_rsrcs(self):
|
|
return self.__rsrcs
|
|
|
|
def update_rsrcs(self, rsrcs):
|
|
self.__rsrcs = rsrcs
|
|
|
|
def update_latency(self, func, inarg=None, outarg=None):
|
|
self.__latencies = self.Latencies(
|
|
func=func,
|
|
inarg=inarg if inarg else [0]*len(self.__in_args),
|
|
outarg=outarg if outarg else [0]*len(self.__dests))
|
|
|
|
def inputs(self, i, bind=False):
|
|
if bind and self.__in_args[i].bind(True):
|
|
raise self.SimCompError('Input argument ' + str(i) + ' is already driven (bound).')
|
|
return self.__in_args[i]
|
|
|
|
def connect(self, i, dest):
|
|
self.__dests[i] = dest
|
|
|
|
def is_ready(self):
|
|
ready = len(self.__dests) > 0
|
|
for dest in self.__dests:
|
|
ready = ready and dest.is_ready()
|
|
exec_ready = (self.get_ticks() - self.__last_exec_ticks) >= self.__ticks_per_exec
|
|
return ready and exec_ready
|
|
|
|
def create_outdata_stream(self, bpi, items, count):
|
|
return DataStream(
|
|
bpi=bpi, items=items, count=count, parent=self.__max_latency_input)
|
|
|
|
def notify(self, arg_i):
|
|
self.__in_args_pushed[arg_i] = True
|
|
# Wait for all input args to come in
|
|
if (sorted(self.__in_args_pushed.keys()) == list(range(len(self.__in_args)))):
|
|
# Pop data out of each input arg
|
|
max_in_latency = 0
|
|
self.__max_latency_input = None
|
|
arg_data_in = list()
|
|
for arg in self.__in_args:
|
|
d = arg.pop()
|
|
arg_data_in.append(d)
|
|
lat = DataStream.HopDb(d.get_hops()).get_latency(self.get_ticks())
|
|
if lat > max_in_latency:
|
|
max_in_latency = lat
|
|
self.__max_latency_input = d
|
|
# Call the function
|
|
arg_data_out = self.do_func(arg_data_in)
|
|
if not isinstance(arg_data_out, collections.Iterable):
|
|
arg_data_out = [arg_data_out]
|
|
# Update output args
|
|
for i in range(len(arg_data_out)):
|
|
arg_data_out[i].add_hop(self.name,
|
|
max(self.__latencies.inarg) + self.__latencies.func + self.__latencies.outarg[i])
|
|
self.__dests[i].push(arg_data_out[i])
|
|
# Cleanup
|
|
self.__last_exec_ticks = self.get_ticks()
|
|
self.__in_args_pushed = dict()
|
|
|
|
def get_util_attrs(self):
|
|
return []
|
|
|
|
def get_utilization(self, what):
|
|
return 0.0
|
|
|
|
#------------------------------------------------------------
|
|
# Plotting Functions
|
|
#------------------------------------------------------------
|
|
class Visualizer():
|
|
def __init__(self, sim_core):
|
|
self.__sim_core = sim_core
|
|
self.__figure = None
|
|
self.__fig_dims = None
|
|
|
|
def show_network(self, engine='fdp'):
|
|
dot = self.__sim_core.network_to_dot()
|
|
dot.format = 'png'
|
|
dot.engine = engine
|
|
dot.render('/tmp/rfnoc_sim.dot', view=True, cleanup=True)
|
|
|
|
def dump_consumed_streams(self, consumer_filt='.*'):
|
|
comps = self.__sim_core.list_components(comptype.consumer, consumer_filt)
|
|
print('=================================================================')
|
|
print('Streams Received by Consumers matching (%s) at Tick = %04d'%(consumer_filt,self.__sim_core.get_ticks()))
|
|
print('=================================================================')
|
|
for c in sorted(comps):
|
|
comp = self.__sim_core.lookup(c)
|
|
for s in sorted(comp.get_items()):
|
|
print((' - %s: (%s) Latency = %gs' % (s, c, comp.get_latency(s))))
|
|
print('=================================================================')
|
|
|
|
def dump_debug_audit_log(self, ctype, name_filt='.*'):
|
|
if ctype != comptype.channel:
|
|
raise NotImplementedError('Component type not yet supported: ' + ctype)
|
|
|
|
comps = self.__sim_core.list_components(ctype, name_filt)
|
|
print('=================================================================')
|
|
print('Debug Audit for all %s Components matching (%s)'%(ctype,name_filt))
|
|
print('=================================================================')
|
|
for c in sorted(comps):
|
|
comp = self.__sim_core.lookup(c)
|
|
status = 'Unknown'
|
|
if comp.is_bound() and comp.is_connected():
|
|
status = 'Good'
|
|
elif comp.is_bound() and not comp.is_connected():
|
|
status = 'WARNING (Driven but Unused)'
|
|
elif not comp.is_bound() and comp.is_connected():
|
|
status = 'WARNING (Used but Undriven)'
|
|
else:
|
|
status = 'Unused'
|
|
print((' - %s: Status = %s'%(c,status)))
|
|
print('=================================================================')
|
|
|
|
def new_figure(self, grid_dims=[1,1], fignum=1, figsize=(16, 9), dpi=72):
|
|
self.__figure = plt.figure(num=fignum, figsize=figsize, dpi=dpi)
|
|
self.__fig_dims = grid_dims
|
|
|
|
def show_figure(self):
|
|
plt.show()
|
|
self.__figure = None
|
|
|
|
def plot_utilization(self, ctype, name_filt='.*', grid_pos=1):
|
|
colors = ['b','r','g','y']
|
|
comps = self.__sim_core.list_components(ctype, name_filt)
|
|
attrs = set()
|
|
for c in comps:
|
|
attrs |= set(self.__sim_core.lookup(c).get_util_attrs())
|
|
attrs = sorted(list(attrs))
|
|
|
|
if not self.__figure:
|
|
self.new_figure()
|
|
show = True
|
|
else:
|
|
show = False
|
|
self.__figure.subplots_adjust(bottom=0.25)
|
|
ax = self.__figure.add_subplot(*(self.__fig_dims + [grid_pos]))
|
|
title = 'Resource utilization for all %s\ncomponents matching \"%s\"' % \
|
|
(ctype, name_filt)
|
|
ax.set_title(title)
|
|
ax.set_ylabel('Resource Utilization (%)')
|
|
if comps:
|
|
ind = np.arange(len(comps))
|
|
width = 0.95/len(attrs)
|
|
rects = []
|
|
ymax = 100
|
|
for i in range(len(attrs)):
|
|
utilz = [self.__sim_core.lookup(c).get_utilization(attrs[i]) * 100 for c in comps]
|
|
rects.append(ax.bar(ind + width*i, utilz, width, color=colors[i%len(colors)]))
|
|
ymax = max(ymax, int(math.ceil(max(utilz) / 100.0)) * 100)
|
|
ax.set_ylim([0,ymax])
|
|
ax.set_yticks(list(range(0,ymax,10)))
|
|
ax.set_xticks(ind + 0.5)
|
|
ax.set_xticklabels(comps, rotation=90)
|
|
ax.legend(rects, attrs)
|
|
ax.grid(b=True, which='both', color='0.65',linestyle='--')
|
|
ax.plot([0, len(comps)], [100, 100], "k--", linewidth=3.0)
|
|
if show:
|
|
self.show_figure()
|
|
|
|
def plot_consumption_latency(self, stream_filt='.*', consumer_filt='.*', grid_pos=1):
|
|
streams = list()
|
|
for c in sorted(self.__sim_core.list_components(comptype.consumer, consumer_filt)):
|
|
for s in sorted(self.__sim_core.lookup(c).get_items()):
|
|
if (re.match(stream_filt, s)):
|
|
streams.append((c, s, c + '/' + s))
|
|
|
|
if not self.__figure:
|
|
self.new_figure()
|
|
show = True
|
|
else:
|
|
show = False
|
|
self.__figure.subplots_adjust(bottom=0.25)
|
|
ax = self.__figure.add_subplot(*(self.__fig_dims + [grid_pos]))
|
|
title = 'Latency of Maximal Path Terminating in\nStream(s) matching \"%s\"\n(Consumer Filter = \"%s\")' % \
|
|
(stream_filt, consumer_filt)
|
|
ax.set_title(title)
|
|
ax.set_ylabel('Maximal Source-to-Sink Latency (s)')
|
|
if streams:
|
|
ind = np.arange(len(streams))
|
|
latency = [self.__sim_core.lookup(c_s_d1[0]).get_latency(c_s_d1[1]) for c_s_d1 in streams]
|
|
rects = [ax.bar(ind, latency, 1.0, color='b')]
|
|
ax.set_xticks(ind + 0.5)
|
|
ax.set_xticklabels([c_s_d[2] for c_s_d in streams], rotation=90)
|
|
attrs = ['latency']
|
|
ax.legend(rects, attrs)
|
|
ax.yaxis.set_major_formatter(mticker.FormatStrFormatter('%.2e'))
|
|
ax.grid(b=True, which='both', color='0.65',linestyle='--')
|
|
if show:
|
|
self.show_figure()
|
|
|
|
def plot_path_latency(self, stream_id, consumer_filt = '.*', grid_pos=1):
|
|
path = []
|
|
latencies = []
|
|
for c in self.__sim_core.list_components(comptype.consumer, consumer_filt):
|
|
for s in self.__sim_core.lookup(c).get_items():
|
|
if (stream_id == s):
|
|
for h in self.__sim_core.lookup(c).get_hops(s):
|
|
path.append(h)
|
|
latencies.append(self.__sim_core.lookup(c).get_latency(s, h))
|
|
break
|
|
if not self.__figure:
|
|
self.new_figure()
|
|
show = True
|
|
else:
|
|
show = False
|
|
self.__figure.subplots_adjust(bottom=0.25)
|
|
ax = self.__figure.add_subplot(*(self.__fig_dims + [grid_pos]))
|
|
title = 'Accumulated Latency per Hop for Stream \"%s\"\n(Consumer Filter = \"%s\")' % \
|
|
(stream_id, consumer_filt)
|
|
ax.set_title(title)
|
|
ax.set_ylabel('Maximal Source-to-Sink Latency (s)')
|
|
if path:
|
|
ind = np.arange(len(path))
|
|
rects = [ax.plot(ind, latencies, '--rs')]
|
|
ax.set_xticks(ind)
|
|
ax.set_xticklabels(path, rotation=90)
|
|
ax.yaxis.set_major_formatter(mticker.FormatStrFormatter('%.2e'))
|
|
ax.grid(b=True, which='both', color='0.65',linestyle='--')
|
|
if show:
|
|
self.show_figure()
|