#!/usr/bin/env python
# Copyright (c) 2013 Potential Ventures Ltd
# Copyright (c) 2013 SolarFlare Communications Inc
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of Potential Ventures Ltd,
# SolarFlare Communications Inc nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL POTENTIAL VENTURES LTD BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Common bus related functionality.
A bus is simply defined as a collection of signals.
"""
from cocotb.handle import _AssignmentResult
def _build_sig_attr_dict(signals):
if isinstance(signals, dict):
return signals
else:
return {sig: sig for sig in signals}
[docs]class Bus(object):
"""Wraps up a collection of signals.
Assumes we have a set of signals/nets named ``entity.<bus_name><separator><signal>``.
For example a bus ``stream_in`` with signals ``valid`` and ``data`` is assumed
to be named ``dut.stream_in_valid`` and ``dut.stream_in_data`` (with
the default separator '_').
TODO:
Support for ``struct``/``record`` ports where signals are member names.
"""
def __init__(self, entity, name, signals, optional_signals=[], bus_separator="_", array_idx=None):
"""
Args:
entity (SimHandle): :any:`SimHandle` instance to the entity containing the bus.
name (str): Name of the bus. ``None`` for a nameless bus, e.g. bus-signals
in an interface or a ``modport`` (untested on ``struct``/``record``,
but could work here as well).
signals (list or dict): In the case of an object (passed to :func:`drive`/:func:`capture`)
that has the same attribute names as the signal names of the bus,
the *signals* argument can be a list of those names.
When the object has different attribute names, the *signals* argument should be
a dict that maps bus attribute names to object signal names.
optional_signals (list or dict, optional): Signals that don't have to be present
on the interface.
See the *signals* argument above for details.
bus_separator (str, optional): Character(s) to use as separator between bus
name and signal name. Defaults to '_'.
array_idx (int or None, optional): Optional index when signal is an array.
"""
self._entity = entity
self._name = name
self._signals = {}
for attr_name, sig_name in _build_sig_attr_dict(signals).items():
if name:
signame = name + bus_separator + sig_name
else:
signame = sig_name
if array_idx is not None:
signame += "[{:d}]".format(array_idx)
self._add_signal(attr_name, signame)
# Also support a set of optional signals that don't have to be present
for attr_name, sig_name in _build_sig_attr_dict(optional_signals).items():
if name:
signame = name + bus_separator + sig_name
else:
signame = sig_name
if array_idx is not None:
signame += "[{:d}]".format(array_idx)
self._entity._log.debug("Signal name {}".format(signame))
# Attempts to access a signal that doesn't exist will print a
# backtrace so we 'peek' first, slightly un-pythonic
if entity.__hasattr__(signame):
self._add_signal(attr_name, signame)
else:
self._entity._log.debug("Ignoring optional missing signal "
"%s on bus %s" % (sig_name, name))
def _add_signal(self, attr_name, signame):
self._entity._log.debug("Signal name {}".format(signame))
setattr(self, attr_name, getattr(self._entity, signame))
self._signals[attr_name] = getattr(self, attr_name)
[docs] def drive(self, obj, strict=False):
"""Drives values onto the bus.
Args:
obj: Object with attribute names that match the bus signals.
strict (bool, optional): Check that all signals are being assigned.
Raises:
AttributeError: If not all signals have been assigned when ``strict=True``.
"""
for attr_name, hdl in self._signals.items():
if not hasattr(obj, attr_name):
if strict:
msg = ("Unable to drive onto {0}.{1} because {2} is missing "
"attribute {3}".format(self._entity._name,
self._name,
obj.__class__.__name__,
attr_name))
raise AttributeError(msg)
else:
continue
val = getattr(obj, attr_name)
hdl <= val
[docs] def capture(self):
"""Capture the values from the bus, returning an object representing the capture.
Returns:
dict: A dictionary that supports access by attribute,
where each attribute corresponds to each signal's value.
Raises:
RuntimeError: If signal not present in bus,
or attempt to modify a bus capture.
"""
class _Capture(dict):
def __getattr__(self, name):
if name in self:
return self[name]
else:
raise RuntimeError('Signal {} not present in bus'.format(name))
def __setattr__(self, name, value):
raise RuntimeError('Modifying a bus capture is not supported')
def __delattr__(self, name):
raise RuntimeError('Modifying a bus capture is not supported')
_capture = _Capture()
for attr_name, hdl in self._signals.items():
_capture[attr_name] = hdl.value
return _capture
[docs] def sample(self, obj, strict=False):
"""Sample the values from the bus, assigning them to *obj*.
Args:
obj: Object with attribute names that match the bus signals.
strict (bool, optional): Check that all signals being sampled
are present in *obj*.
Raises:
AttributeError: If attribute is missing in *obj* when ``strict=True``.
"""
for attr_name, hdl in self._signals.items():
if not hasattr(obj, attr_name):
if strict:
msg = ("Unable to sample from {0}.{1} because {2} is missing "
"attribute {3}".format(self._entity._name,
self._name,
obj.__class__.__name__,
attr_name))
raise AttributeError(msg)
else:
continue
# Try to use the get/set_binstr methods because they will not clobber the properties
# of obj.attr_name on assignment. Otherwise use setattr() to crush whatever type of
# object was in obj.attr_name with hdl.value:
try:
getattr(obj, attr_name).set_binstr(hdl.value.get_binstr())
except AttributeError:
setattr(obj, attr_name, hdl.value)
def __le__(self, value):
"""Overload the less than or equal to operator for value assignment"""
self.drive(value)
return _AssignmentResult(self, value)