Skip to content

Commit

Permalink
Added custom ERC using erc_assert().
Browse files Browse the repository at this point in the history
  • Loading branch information
xesscorp committed May 14, 2020
1 parent d2e6f81 commit ec90f46
Show file tree
Hide file tree
Showing 13 changed files with 304 additions and 129 deletions.
3 changes: 3 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ ______________________
* Interface I/O can now be accessed using brackets ([]) and via attributes (e.g., intfc.a).
* Interface I/O can now be assigned aliases.
* Added tee() function for creating T-junctions in networks.
* Custom ERCs can now be added using the `erc_assert` function.
* Aliases take precedence over default pin names.
* Substring matching in pin names can be enabled/disabled (off by default).


0.0.29 (2020-01-30)
Expand Down
13 changes: 11 additions & 2 deletions skidl/Circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,9 +360,18 @@ def ERC(self, *args, **kwargs):

if self.no_files:
erc_logger.stop_file_output()

super().ERC(*args, **kwargs)
super().ERC_asserts()

if (erc_logger.error.count, erc_logger.warning.count) == (0, 0):
sys.stderr.write("\nNo ERC errors or warnings found.\n\n")
else:
sys.stderr.write(
"\n{} warnings found during ERC.\n".format(erc_logger.warning.count)
)
sys.stderr.write(
"{} errors found during ERC.\n\n".format(erc_logger.error.count)
)

def _merge_net_names(self):
"""Select a single name for each multi-segment net."""
Expand Down
11 changes: 9 additions & 2 deletions skidl/Interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,7 @@ def __setitem__(self, key, value):
# Set the ProtoNet in the interface entry with key to its new Net or Bus value.
dict.__setitem__(self, key, v)
dict.__setattr__(self, key, v)
# The interface key and += flag in the values are no longer needed.
rmv_attr(value, "intfc_key")
# The += flag in the values are no longer needed.
rmv_attr(value, "iadd_flag")
else:
# This is for a straight assignment of value to key.
Expand Down Expand Up @@ -148,3 +147,11 @@ def get_io(self, *io_ids):

# Get I/Os from an interface using brackets, e.g. ['A, B'].
__getitem__ = get_io

def __getattribute__(self, key):
value = dict.__getattribute__(self, key)
if isinstance(value, ProtoNet):
# If the retrieved attribute is a ProtoNet, record where it came from.
value.intfc_key = key
value.intfc = self
return value
5 changes: 4 additions & 1 deletion skidl/Package.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,10 @@ def package(subcirc_func):

# By default, set parameters to a package to be ProtoNets.
for arg_name in arg_names:
pckg[arg_name] = ProtoNet(arg_name)
pn = ProtoNet(arg_name)
pn.intfc = pckg
pn.intfc_key = arg_name
pckg[arg_name] = pn

# Set any default values for the parameters.
if getattr(subcirc_func, "__defaults__", None):
Expand Down
17 changes: 11 additions & 6 deletions skidl/Part.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ def __init__(
self.ref_prefix = "" # Provide a member for holding the part reference prefix.
self.tool = tool # Initial type of part (SKIDL, KICAD, etc.)
self.circuit = None # Part starts off unassociated with any circuit.
self.match_pin_substring = False # Select pins only with an exact match of their name.
self.match_pin_substring = False # Only select pins with exact name matches.

# Create a Part from a library entry.
if lib:
Expand Down Expand Up @@ -587,7 +587,9 @@ def get_pins(self, *pin_ids, **criteria):
only_search_names = criteria.pop("only_search_names", False)

# Extract permission to search for substring matches in pin names/aliases.
match_substring = criteria.pop("match_substring", False) or self.match_pin_substring
match_substring = (
criteria.pop("match_substring", False) or self.match_pin_substring
)

# If no pin identifiers were given, then use a wildcard that will
# select all pins.
Expand All @@ -601,7 +603,9 @@ def get_pins(self, *pin_ids, **criteria):

# Go through the list of pin IDs one-by-one.
pins = NetPinList()
for p_id in expand_indices(self.min_pin, self.max_pin, match_substring, *pin_ids):
for p_id in expand_indices(
self.min_pin, self.max_pin, match_substring, *pin_ids
):

# If only names are being searched, the search of pin numbers is skipped.
if not only_search_names:
Expand Down Expand Up @@ -634,13 +638,14 @@ def get_pins(self, *pin_ids, **criteria):
pins.extend(tmp_pins)
continue

# If matching a substring within a pin name is enabled, then
# create wildcards to match the beginning/ending surrounding a
# substring. Remove these wildcards if substring matching is disabled.
wildcard = ".*" if match_substring else ""
# if not match_substring:
# # Skip search of pin names/aliases for a matching substring.
# continue

# OK, pin ID is not a pin number and doesn't exactly match a pin
# name or alias. Does it match a substring within a pin name?
# Or does it match as a regex?
try:
p_id_re = "".join([wildcard, p_id, wildcard])
except TypeError:
Expand Down
1 change: 1 addition & 0 deletions skidl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from .Bus import *
from .Circuit import *
from .defines import *
from .erc import *
from .Interface import *
from .logger import *
from .Net import *
Expand Down
8 changes: 4 additions & 4 deletions skidl/baseobj.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@

from .Alias import Alias
from .AttrDict import AttrDict
from .erc import eval_stmnt_list, exec_function_list
from .Note import Note
from .utilities import exec_function_list, eval_stmnt_list

standard_library.install_aliases()

Expand Down Expand Up @@ -112,10 +112,10 @@ def copy(self):
return cpy

def ERC(self, *args, **kwargs):
"""Run ERC functions on this object."""
"""Run ERC on this object."""

# Run ERC functions.
exec_function_list(self, "erc_list", *args, **kwargs)

def ERC_asserts(self, *args, **kwargs):
"""Run ERC assertions on this object."""
# Run ERC assertions.
eval_stmnt_list(self, "erc_assertion_list")
116 changes: 106 additions & 10 deletions skidl/erc.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,14 @@

from __future__ import absolute_import, division, print_function, unicode_literals

import inspect
import sys
from builtins import range, str
from collections import namedtuple

from future import standard_library

from .defines import *
from .logger import erc_logger

standard_library.install_aliases()
Expand Down Expand Up @@ -60,16 +63,6 @@ def dflt_circuit_erc(circuit):
for piece in circuit.parts + circuit.interfaces + circuit.packages:
piece.ERC()

if (erc_logger.error.count, erc_logger.warning.count) == (0, 0):
sys.stderr.write("\nNo ERC errors or warnings found.\n\n")
else:
sys.stderr.write(
"\n{} warnings found during ERC.\n".format(erc_logger.warning.count)
)
sys.stderr.write(
"{} errors found during ERC.\n\n".format(erc_logger.error.count)
)


def dflt_part_erc(part):
"""
Expand Down Expand Up @@ -149,3 +142,106 @@ def dflt_net_erc(net):
n=net.name, p=p.erc_desc()
)
)


# Tuple for storing assertion code object with its global & local dicts.
EvalTuple = namedtuple(
"EvalTuple", "stmnt fail_msg severity filename lineno function globals locals"
)


def eval_stmnt_list(inst, list_name):
"""
Evaluate class-wide and local statements on a class instance.
Args:
inst: Instance of a class.
list_name: String containing the attribute name of the list of
class-wide and local code objects.
"""

def erc_report(evtpl):
log_msg = "{evtpl.stmnt} {evtpl.fail_msg} in {evtpl.filename}:{evtpl.lineno}:{evtpl.function}.".format(
evtpl=evtpl
)
if evtpl.severity == ERROR:
erc_logger.error(log_msg)
elif evtpl.severity == WARNING:
erc_logger.warning(log_msg)

# Evaluate class-wide statements on this instance.
if list_name in inst.__class__.__dict__:
for evtpl in inst.__class__.__dict__[list_name]:
try:
assert eval(evtpl.stmnt, evtpl.globals, evtpl.locals)
except AssertionError:
erc_report(evtpl)

# Now evaluate any statements for this particular instance.
if list_name in inst.__dict__:
for evtpl in inst.__dict__[list_name]:
try:
assert eval(evtpl.stmnt, evtpl.globals, evtpl.locals)
except AssertionError:
erc_report(evtpl)


def exec_function_list(inst, list_name, *args, **kwargs):
"""
Execute class-wide and local functions on a class instance.
Args:
inst: Instance of a class.
list_name: String containing the attribute name of the list of
class-wide and local functions.
args, kwargs: Arbitary argument lists to pass to the functions
that are executed. (All functions get the same arguments.)
"""

# Execute the class-wide functions on this instance.
if list_name in inst.__class__.__dict__:
for f in inst.__class__.__dict__[list_name]:
f(inst, *args, **kwargs)

# Now execute any instance functions for this particular instance.
if list_name in inst.__dict__:
for f in inst.__dict__[list_name]:
f(inst, *args, **kwargs)


def add_to_exec_or_eval_list(class_or_inst, list_name, func):
"""Append a function to a function list of a class or class instance."""

if list_name not in class_or_inst.__dict__:
setattr(class_or_inst, list_name, [])
getattr(class_or_inst, list_name).append(func)


def add_erc_function(class_or_inst, func):
"""Add an ERC function to a class or class instance."""

add_to_exec_or_eval_list(class_or_inst, "erc_list", func)


def add_erc_assertion(assertion, fail_msg="FAILED", severity=ERROR, class_or_inst=None):
"""Add an ERC assertion to a class or class instance."""

cls_or_inst = class_or_inst or default_circuit
assertion_frame, filename, lineno, function, _, _ = inspect.stack()[1]
add_to_exec_or_eval_list(
cls_or_inst,
"erc_assertion_list",
EvalTuple(
assertion,
fail_msg,
severity,
filename,
lineno,
function,
assertion_frame.f_globals,
assertion_frame.f_locals,
),
)


erc_assert = add_erc_assertion
2 changes: 1 addition & 1 deletion skidl/scriptinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@
"""
from __future__ import absolute_import, division, print_function, unicode_literals

from builtins import str
import inspect
import os
import sys
import traceback
from builtins import str


def scriptinfo():
Expand Down
78 changes: 2 additions & 76 deletions skidl/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,16 @@
from __future__ import absolute_import, division, print_function, unicode_literals

import collections
from collections import namedtuple
import inspect
import os
import os.path
import re
import sys
import traceback
from builtins import chr, dict, int, object, open, range, str, super, zip
from collections import namedtuple
from contextlib import contextmanager

from .defines import *
from .py_2_3 import *

"""Separator for strings containing multiple indices."""
Expand Down Expand Up @@ -757,80 +757,6 @@ def expand_buses(pins_nets_buses):
return pins_nets


# Tuple for storing assertion code object with its global & local dicts.
EvalTuple = namedtuple("EvalTuple", "stmnt globals locals")


def eval_stmnt_list(inst, list_name):
"""
Evaluate class-wide and local statements on a class instance.
Args:
inst: Instance of a class.
list_name: String containing the attribute name of the list of
class-wide and local code objects.
"""

# Evaluate class-wide statements on this instance.
if list_name in inst.__class__.__dict__:
for evtpl in inst.__class__.__dict__[list_name]:
assert eval(evtpl.stmnt, evtpl.globals, evtpl.locals)

# Now evaluate any statements for this particular instance.
if list_name in inst.__dict__:
for evtpl in inst.__dict__[list_name]:
assert eval(evtpl.stmnt, evtpl.globals, evtpl.locals)


def exec_function_list(inst, list_name, *args, **kwargs):
"""
Execute class-wide and local functions on a class instance.
Args:
inst: Instance of a class.
list_name: String containing the attribute name of the list of
class-wide and local functions.
args, kwargs: Arbitary argument lists to pass to the functions
that are executed. (All functions get the same arguments.)
"""

# Execute the class-wide functions on this instance.
if list_name in inst.__class__.__dict__:
for f in inst.__class__.__dict__[list_name]:
f(inst, *args, **kwargs)

# Now execute any instance functions for this particular instance.
if list_name in inst.__dict__:
for f in inst.__dict__[list_name]:
f(inst, *args, **kwargs)


def add_to_exec_or_eval_list(class_or_inst, list_name, func):
"""Append a function to a function list of a class or class instance."""

if list_name not in class_or_inst.__dict__:
setattr(class_or_inst, list_name, [])
getattr(class_or_inst, list_name).append(func)


def add_erc_function(class_or_inst, func):
"""Add an ERC function to a class or class instance."""

add_to_exec_or_eval_list(class_or_inst, "erc_list", func)


def add_erc_assertion(class_or_inst, assertion):
"""Add an ERC assertion to a class or class instance."""

assertion_code = compile(assertion, "<stdin>", "eval")
assertion_frame = inspect.stack()[1].frame
add_to_exec_or_eval_list(
class_or_inst,
"erc_assertion_list",
EvalTuple(assertion_code, assertion_frame.f_globals, assertion_frame.f_locals),
)


def log_and_raise(logger_in, exc_class, message):
logger_in.error(message)
raise exc_class(message)
Loading

0 comments on commit ec90f46

Please sign in to comment.