"""``AMIParameter`` class definition, plus some helpers.
Original author: David Banas <capn.freako@gmail.com>
Original date: December 24, 2016
Copyright (c) 2019 David Banas; all rights reserved World wide.
"""
#####
# AMI parameter
#####
[docs]
class AMIParamError(Exception):
"""Base Exception for all AMI Parameter Errors."""
[docs]
class AMIParameter: # pylint: disable=too-many-instance-attributes,too-few-public-methods
"""IBIS-AMI model parameter.
This class encapsulates the attributes and behavior of a AMI
parameter.
"""
RESERVED_PARAM_NAMES = [
"AMI_Version",
"Init_Returns_Impulse",
"GetWave_Exists",
"Use_Init_Output",
"Max_Init_Aggressors",
"Ignore_Bits",
"Resolve_Exists",
"Model_Name",
"Special_Param_Names",
"Component_Name",
"Signal_Name",
"Rx_Decision_Time",
"DC_Offset",
"Rx_Use_Clock_Input",
"Supporting_Files",
"DLL_Path",
"DLL_ID",
"Tx_Jitter",
"Tx_DCD",
"Tx_Rj",
"Tx_Dj",
"Tx_Sj",
"Tx_Sj_Frequency",
"Rx_DCD",
"Rx_Rj",
"Rx_Dj",
"Rx_Sj",
"Rx_Clock_PDF",
"Rx_Clock_Recovery_Mean",
"Rx_Clock_Recovery_Rj",
"Rx_Clock_Recovery_Dj",
"Rx_Clock_Recovery_Sj",
"Rx_Clock_Recovery_DCD",
"Rx_Receiver_Sensitivity",
"Rx_Noise",
"Rx_GaussianNoise",
"Rx_UniformNoise",
"Modulation",
"PAM4_Mapping",
"PAM4_UpperThreshold",
"PAM4_CenterThreshold",
"PAM4_LowerThreshold",
"PAM4_UpperEyeOffset",
"PAM4_CenterEyeOffset",
"PAM4_LowerEyeOffset",
"Repeater_Type",
"BCI_Protocol",
"BCI_ID",
"BCI_State",
"BCI_Message_Interval_UI",
"BCI_Training_UI",
"BCI_Training_Mode",
"Ts4file",
"Tx_V",
"Tx_R",
"Rx_R",
]
# Properties.
# Note: They are read-only, despite the presence of apparent setters.
# (The idea is that, once initialized, parameter definitions
# are immutable.)
# The only exception to this is pvalue, which has been made
# writable, for scripted non-GUI use cases.
# Be very careful w/ this; there is NO CHECKING!
# Note that the setters, below, are only intended for use by
# __init__(). They may raise an *AMIParamError* exception. This is
# to ensure that a malformed instance never be created.
def _get_name(self):
"""pname."""
return self._name
pname = property(_get_name, doc="Name of AMI parameter.")
# pusage
def _set_usage(self, values):
"Process *Usage* tag."
val = values[0]
if val in ("In", "Out", "InOut", "Info"):
self._usage = val
else:
raise AMIParamError(f"Unrecognized usage value: '{val}'.")
def _get_usage(self):
return self._usage
pusage = property(_get_usage, doc="Value of AMI parameter 'Usage' tag.")
# ptype
def _set_type(self, values):
"Process *Type* tag."
val = values[0]
if val in ("Float", "Integer", "String", "Boolean", "UI", "Tap"):
self._type = val
else:
raise AMIParamError(f"Unrecognized type value: '{val}'.")
def _get_type(self):
return self._type
ptype = property(_get_type, doc="Value of AMI parameter 'Type' tag.")
# pformat
def _set_format(self, values):
"Process *Format* tag."
form = values[0]
# if(not form in ['Value', 'Range', 'List']):
# raise AMIParamError ("Unrecognized format value: '{}'.".format(form))
if len(values) < 2:
raise AMIParamError(f"No values provided for: '{form}'.")
self._format = form
self._format_rem = values[1:]
def _get_format(self):
return self._format
pformat = property(_get_format, doc="Value of AMI parameter 'Format' tag.")
# pvalue
def _get_value(self):
return self._value
def _set_val(self, new_val):
self._value = new_val
pvalue = property(_get_value, _set_val, doc="Value of AMI parameter.")
# pmin
def _get_min(self):
return self._min
pmin = property(_get_min, doc="Minimum value of AMI parameter.")
# pmax
def _get_max(self):
return self._max
pmax = property(_get_max, doc="Maximum value of AMI parameter.")
# default
def _set_default(self, values):
"""Process *Default* tag."""
self._default = values[0]
def _get_default(self):
return self._default
pdefault = property(_get_default, doc="Default value of AMI parameter.")
# pdescription
def _set_description(self, values):
"""Process *Description* tag."""
self._description = values[0]
def _get_description(self):
return self._description
pdescription = property(_get_description, doc="Description of AMI parameter.")
# plist_tip
def _set_list_tip(self, values):
"""Process *List_Tip* tag."""
self._list_tip = [x.strip('"') for x in values]
def _get_list_tip(self):
return self._list_tip
plist_tip = property(_get_list_tip, doc="List tips of AMI parameter.")
# Helpers.
# These 3 just accomodate the awkwardness caused by the optional
# nature of the 'Format' keyword, in *.AMI files. Since, a properly
# formed instance of *AMIParameter* will always have a *format*
# property, we don't need to make these properties of that class.
# value
def _set_value(self, values):
"""Process *Value* tag."""
return self._set_format(["Value"] + values)
# range
def _set_range(self, values):
"""Process *Range* tag."""
return self._set_format(["Range"] + values)
# usage
def _set_list(self, values):
"""Process *List* tag."""
return self._set_format(["List"] + values)
# Holds any warnings encountered, during initialization.
# (Any errors encountered will prevent initialization from completing.)
_msg = ""
def _get_msg(self):
return self._msg
msg = property(_get_msg, doc="Any warning messages encountered, during parameter initialization.")
# This dictionary defines both:
#
# - the allowed parameter definition tag names, and
# - their processing functions.
#
# The idea is to allow this class to grow along with the IBIS
# standard, without having to change any of its boilerplate.
_param_def_tag_procs = {
"Usage": _set_usage,
"Type": _set_type,
"Format": _set_format,
"Value": _set_value,
"Range": _set_range,
"List": _set_list,
"Corner": _set_list,
"Default": _set_default,
"Description": _set_description,
"List_Tip": _set_list_tip,
"Label": _set_list_tip,
"Labels": _set_list_tip,
}
def __init__(self, name, tags): # pylint: disable=too-many-branches,too-many-statements
"""
Args:
name (str): The name of the AMI parameter being created.
tags ([(str, [a])]): A list of pairs, each containing:
- a parameter definition tag name
(Must be one of the keys from the
'_param_def_tag_procs' dictionary.)
- a list of values to be associated with that tag.
"""
# Initialization
self._usage = None
self._type = None
self._format = None
self._value = None
self._min = None
self._max = None
self._default = None
self._description = ""
self._list_tip = None
# Holds any warnings encountered, during initialization.
# (Any errors encountered will prevent initialization from completing.)
self._msg = ""
# Process all parameter definition tags.
for tag in tags:
tag_name = tag[0]
if tag_name in self._param_def_tag_procs:
try:
self._param_def_tag_procs[tag_name](self, tag[1])
except AMIParamError as err:
raise AMIParamError(f"Problem initializing parameter, '{name}': {err}\n") from err
# Validate and complete the instance.
# Check for required tags.
param_usage = self._usage
param_type = self._type
param_format = self._format
param_default = self._default
if param_usage is None:
raise AMIParamError("Missing 'Usage' tag!\n")
if param_type is None:
raise AMIParamError("Missing 'Type' tag!\n")
if param_format is None:
if param_default is None:
raise AMIParamError("Missing both 'Format' and 'Default' tags!\n")
self._value = param_default
param_format = "Value"
self._format_rem = [param_default]
# Check for mutual exclusivity of 'Format Value' and 'Default'.
elif (param_format == "Value") and (param_default is not None):
self._msg += "'Format Value' and 'Default' both found! (They are mutually exclusive.)\n"
# Check for 'Default' used with parameter type 'Out'.
if (param_type == "Out") and (param_default is not None):
raise AMIParamError("'Default' may not be used with parameter type 'Out'!\n")
# Complete the instance.
vals = self._format_rem
if param_format == "Value":
value_str = vals[0].strip()
if param_type in ("Float", "UI"):
try:
self._value = float(value_str)
except (ValueError, TypeError) as exc:
raise AMIParamError(f"Couldn't read float from '{value_str}'.\n") from exc
elif param_type == "Integer":
try:
self._value = int(float(value_str)) # Hack to accommodate: "1e5", for instance.
except (ValueError, TypeError) as exc:
raise AMIParamError(f"Couldn't read integer from '{value_str}'.\n") from exc
elif param_type == "Boolean":
if value_str == "True":
self._value = True
elif value_str == "False":
self._value = False
else:
raise AMIParamError(f"Couldn't read Boolean from '{value_str}'.\n")
else:
self._value = value_str.strip('"')
elif param_format == "Range":
if param_type not in ("Float", "Integer", "UI", "Tap"):
raise AMIParamError(f"Illegal type, '{param_type}', for use with Range.\n")
if len(vals) < 3:
raise AMIParamError(f"Insufficient number of values, {len(vals)}, provided for Range.\n")
if param_type in ("Float", "UI"):
try:
temp_vals = list(map(float, vals[:3]))
except (ValueError, TypeError) as exc:
raise AMIParamError(f"Couldn't read floats from '{vals[:3]}'.\n") from exc
else:
try:
temp_vals = list(map(int, vals[:3]))
except (ValueError, TypeError) as exc:
raise AMIParamError(f"Couldn't read integers from '{vals[:3]}'.\n") from exc
self._value = temp_vals[0]
self._min = temp_vals[1]
self._max = temp_vals[2]
else: # param_format == 'List'
if param_type in ("Float", "UI"):
try:
temp_vals = list(map(float, vals))
except (ValueError, TypeError) as exc:
raise AMIParamError(f"Couldn't read floats from '{vals}'.\n") from exc
elif param_type in ("Integer", "Tap"):
try:
temp_vals = list(map(int, vals))
except (ValueError, TypeError) as exc:
raise AMIParamError(f"Couldn't read integers from '{vals}'.\n") from exc
else: # 'param_type' == 'String'
try:
temp_vals = list(map(str, vals))
temp_vals = [x.strip('"') for x in temp_vals]
except (ValueError, TypeError) as exc:
raise AMIParamError(f"Couldn't read strings from '{vals}'.\n") from exc
self._value = temp_vals
self._name = name