Source code for pyibisami.ibis.file

"""A class for encapsulating IBIS model files.

Original Author: David Banas <capn.freako@gmail.com>

Original Date:   November 1, 2019

For information regarding the IBIS modeling standard, visit:
https://ibis.org/

**Note:** The ``IBISModel`` class, defined here, needs to be kept separate from the
other IBIS-related classes, defined in the ``model`` module, in order to
avoid circular imports.

Copyright (c) 2019 by David Banas; All rights reserved World wide.
"""

import platform
from datetime import datetime

from traits.api import (
    Any,
    Dict,
    Enum,
    Float,
    HasTraits,
    List,
    Property,
    String,
    Trait,
    cached_property,
)
from traitsui.api import HGroup, Item, ModalButtons, VGroup, View, spring
from traitsui.message import message

from pyibisami.ibis.parser import parse_ibis_file


[docs] class IBISModel(HasTraits): # pylint: disable=too-many-instance-attributes """HasTraits subclass for wrapping and interacting with an IBIS model. This class can be configured to present a customized GUI to the user for interacting with a particular IBIS model (i.e. - selecting components, pins, and models). The intended use model is as follows: 1. Instantiate this class only once per IBIS model file. When instantiating, provide the unprocessed contents of the IBIS file, as a single string. This class will take care of getting that string parsed properly, and report any errors or warnings it encounters, in its ``ibis_parsing_errors`` property. 2. When you want to let the user select a particular component/pin/model, call the newly created instance, as if it were a function, passing no arguments. The instance will then present a GUI to the user, allowing him to select a particular component/pin/model, which may then be retrieved, via the ``model`` property. The latest user selections will be remembered, as long as the instance remains in scope. Any errors or warnings encountered while parsing are available, in the ``ibis_parsing_errors`` property. The complete dictionary containing all parsed models may be retrieved, via the ``model_dict`` property. """ _log = "" pin_ = Property(Any, depends_on=["pin"]) pin_rlcs = Property(Dict, depends_on=["pin"]) model = Property(Any, depends_on=["mod"]) pins = List # Always holds the list of valid pin selections, given a component selection. models = List # Always holds the list of valid model selections, given a pin selection.
[docs] def get_models(self, mname): """Return the list of models associated with a particular name.""" model_dict = self._model_dict if "model_selectors" in model_dict and mname in model_dict["model_selectors"]: return list(map(lambda pr: pr[0], model_dict["model_selectors"][mname])) return [mname]
[docs] def get_pins(self): """Get the list of appropriate pins, given our type (i.e. - Tx or Rx).""" pins = self.comp_.pins def pin_ok(pname): (mname, _) = pins[pname] mods = self.get_models(mname) mod = self._models[mods[0]] mod_type = mod.mtype.lower() tx_ok = mod_type in ("output", "i/o") if self._is_tx: return tx_ok return not tx_ok return list(filter(pin_ok, list(pins)))
def __init__(self, ibis_file_name, is_tx, debug=False, gui=True): """ Args: ibis_file_name (str): The name of the IBIS file. is_tx (bool): True if this is a Tx model. Keyword Args: debug (bool): Output debugging info to console when true. Default = False gui (bool): Set to `False` for command line and/or script usage. Default = True. """ # Super-class initialization is ABSOLUTELY NECESSARY, in order # to get all the Traits/UI machinery setup correctly. super().__init__() self.debug = debug self.GUI = gui if debug: self.log("pyibisami.ibis_file.IBISModel initializing in debug mode...") else: self.log("pyibisami.ibis_file.IBISModel initializing in non-debug mode...") # Parse the IBIS file contents, storing any errors or warnings, and validate it. with open(ibis_file_name, "r", encoding="utf-8") as file: ibis_file_contents_str = file.read() err_str, model_dict = parse_ibis_file(ibis_file_contents_str, debug=debug) self.log("IBIS parsing errors/warnings:\n" + err_str) if "components" not in model_dict or not model_dict["components"]: raise ValueError("This IBIS model has no components!") components = model_dict["components"] if "models" not in model_dict or not model_dict["models"]: raise ValueError("This IBIS model has no models!") models = model_dict["models"] self._model_dict = model_dict self._models = models self._is_tx = is_tx # Add Traits for various attributes found in the IBIS file. self.add_trait("comp", Trait(list(components)[0], components)) # Doesn't need a custom mapper, because self.pins = self.get_pins() # the thing above it (file) can't change. self.add_trait("pin", Enum(self.pins[0], values="pins")) (mname, _) = self.pin_ self.models = self.get_models(mname) self.add_trait("mod", Enum(self.models[0], values="models")) self.add_trait("ibis_ver", Float(model_dict["ibis_ver"])) self.add_trait("file_name", String(model_dict["file_name"])) self.add_trait("file_rev", String(model_dict["file_rev"])) if "date" in model_dict: self.add_trait("date", String(model_dict["date"])) else: self.add_trait("date", String("(n/a)")) self._ibis_parsing_errors = err_str self._os_type = platform.system() # These 2 are used, to choose self._os_bits = platform.architecture()[0] # the correct AMI executable. self._comp_changed(list(components)[0]) # Wasn't being called automatically. self._pin_changed(self.pins[0]) # Wasn't being called automatically. self.log("Done.") def __str__(self): return f"IBIS Model '{self._model_dict['file_name']}'"
[docs] def info(self): """Basic information about the IBIS model.""" res = "" try: for k in ["ibis_ver", "file_name", "file_rev"]: res += k + ":\t" + str(self._model_dict[k]) + "\n" except Exception as err: print(f"{err}") print(self._model_dict) raise res += "date" + ":\t\t" + str(self._model_dict["date"]) + "\n" res += "\nComponents:" res += "\n==========" for c in list(self._model_dict["components"]): res += "\n" + c + ":\n" + "---\n" + str(self._model_dict["components"][c]) + "\n" res += "\nModel Selectors:" res += "\n===============\n" for s in list(self._model_dict["model_selectors"]): res += f"{s}\n" res += "\nModels:" res += "\n======" for m in list(self._model_dict["models"]): res += "\n" + m + ":\n" + "---\n" + str(self._model_dict["models"][m]) return res
def __call__(self): """Present a customized GUI to the user, for model selection, etc.""" self.edit_traits(kind="livemodal") # Logger & Pop-up
[docs] def log(self, msg, alert=False): """Log a message to the console and, optionally, to terminal and/or pop-up dialog.""" _msg = msg.strip() txt = f"\n[{datetime.now()}]: IBISModel: {_msg}\n" self._log += txt if self.debug: print(txt, flush=True) if alert and self.GUI: message(_msg, "PyAMI Alert")
def default_traits_view(self): "Default Traits/UI view definition." view = View( VGroup( HGroup( Item("file_name", label="File name", style="readonly"), spring, Item("file_rev", label="rev", style="readonly"), ), HGroup( Item("ibis_ver", label="IBIS ver", style="readonly"), spring, Item("date", label="Date", style="readonly"), ), HGroup( Item("comp", label="Component"), Item("pin", label="Pin"), Item("mod", label="Model"), ), ), resizable=False, buttons=ModalButtons, title="PyBERT IBIS Model Selector", id="pybert.pybert_ami.model_selector", ) return view @cached_property def _get_pin_(self): return self.comp_.pins[self.pin] @cached_property def _get_pin_rlcs(self): (_, pin_rlcs) = self.pin_ return pin_rlcs @cached_property def _get_model(self): return self._models[self.mod] @property def ibis_parsing_errors(self): """Any errors or warnings encountered, while parsing the IBIS file contents.""" return self._ibis_parsing_errors @property def log_txt(self): """The complete log since instantiation.""" return self._log @property def model_dict(self): "Dictionary of all model keywords." return self._model_dict @property def dll_file(self): "Shared object file." return self._dll_file @property def ami_file(self): "AMI file." return self._ami_file def _comp_changed(self, new_value): del new_value self.pins = self.get_pins() self.pin = self.pins[0] def _pin_changed(self, new_value): # (mname, rlc_dict) = self.pin_ # Doesn't work. Because ``pin_`` is a cached property and hasn't yet been marked "dirty"? (mname, _) = self.comp_.pins[new_value] self.models = self.get_models(mname) self.mod = self.models[0] def _mod_changed(self, new_value): model = self._models[new_value] os_type = self._os_type os_bits = self._os_bits fnames = [] dll_file = "" ami_file = "" if os_type.lower() == "windows": if os_bits == "64bit": fnames = model._exec64Wins # pylint: disable=protected-access else: fnames = model._exec32Wins # pylint: disable=protected-access else: if os_bits == "64bit": fnames = model._exec64Lins # pylint: disable=protected-access else: fnames = model._exec32Lins # pylint: disable=protected-access if fnames: dll_file = fnames[0] ami_file = fnames[1] self.log( "There was an [Algorithmic Model] keyword in this model.\n \ If you wish to use the AMI model associated with this IBIS model,\n \ please, go the 'Equalization' tab and enable it now.", alert=True, ) elif "algorithmic_model" in model._subDict: # pylint: disable=protected-access self.log( f"There was an [Algorithmic Model] keyword for this model,\n \ but no executable for your platform: {os_type}-{os_bits};\n \ PyBERT native equalization modeling being used instead.", alert=True, ) else: self.log( "There was no [Algorithmic Model] keyword for this model;\n \ PyBERT native equalization modeling being used instead.", alert=True, ) self._dll_file = dll_file # pylint: disable=attribute-defined-outside-init self._ami_file = ami_file # pylint: disable=attribute-defined-outside-init