Source code for pyibisami.ibis.model

"""Classes for encapsulating IBIS model constituents.

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

Original Date:   November 1, 2019

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

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

import numpy as np
from chaco.api import ArrayPlotData, Plot
from enable.component_editor import ComponentEditor
from traits.api import HasTraits, String, Trait
from traitsui.api import Group, Item, ModalButtons, View

DBG = False


[docs] class Component(HasTraits): """Encapsulation of a particular component from an IBIS model file.""" def __init__(self, subDict): """ Args: subDict(dict): Dictionary of [Component] sub-keywords/params. """ # Super-class initialization is ABSOLUTELY NECESSARY, in order # to get all the Traits/UI machinery setup correctly. super().__init__() # Stash the sub-keywords/parameters. self._subDict = subDict # Fetch available keyword/parameter definitions. def maybe(name): return subDict[name] if name in subDict else None self._mfr = maybe("manufacturer") self._pkg = maybe("package") self._pins = maybe("pin") self._diffs = maybe("diff_pin") # Check for the required keywords. if not self._mfr: raise LookupError("Missing [Manufacturer]!") if not self._pkg: print(self._mfr) raise LookupError("Missing [Package]!") if not self._pins: raise LookupError("Missing [Pin]!") # Set up the GUI. self.add_trait("manufacturer", String(self._mfr)) self.add_trait("package", String(self._pkg)) self.add_trait("_pin", Trait(list(self._pins)[0], self._pins)) self._content = [ Group( Item("manufacturer", label="Manufacturer", style="readonly"), Item("package", label="Package", style="readonly"), Item("_pin", label="Pin"), label="Component", show_border=True, ), ] def __str__(self): res = "Manufacturer:\t" + self._mfr + "\n" res += "Package: \t" + str(self._pkg) + "\n" res += "Pins:\n" for pname in self._pins: res += " " + pname + ":\t" + str(self._pins[pname]) + "\n" return res def __call__(self): self.edit_traits() def default_traits_view(self): "Default Traits/UI view definition." view = View( resizable=False, buttons=ModalButtons, title="PyBERT IBIS Component Viewer", id="pyibisami.ibis_parser.Component", ) view.set_content(self._content) return view @property def pin(self): """The pin selected most recently by the user. Returns the first pin in the list, if the user hasn't made a selection yet. """ return self._pin_ @property def pins(self): "The list of component pins." return self._pins
[docs] class Model(HasTraits): # pylint: disable=too-many-instance-attributes """Encapsulation of a particular I/O model from an IBIS model file.""" def __init__(self, subDict): # pylint: disable=too-many-locals,too-many-statements """ Args: subDict (dict): Dictionary of sub-keywords/params. """ # Super-class initialization is ABSOLUTELY NECESSARY, in order # to get all the Traits/UI machinery setup correctly. super().__init__() # Stash the sub-keywords/parameters. self._subDict = subDict # Fetch available keyword/parameter definitions. def maybe(name): return subDict[name] if name in subDict else None self._mtype = maybe("model_type") self._ccomp = maybe("c_comp") self._cref = maybe("cref") self._vref = maybe("vref") self._vmeas = maybe("vmeas") self._rref = maybe("rref") self._trange = maybe("temperature_range") self._vrange = maybe("voltage_range") self._ramp = maybe("ramp") # Check for the required keywords. if not self._mtype: raise LookupError("Missing Model_type!") if not self._vrange: raise LookupError("Missing [Voltage Range]!") def proc_iv(xs): """Process an I/V table.""" if len(xs) < 2: raise ValueError("Insufficient number of I-V data points!") try: vs, iss = zip(*(xs)) # Idiomatic Python for ``unzip``. except Exception as exc: raise ValueError(f"xs: {xs}") from exc ityps, imins, imaxs = zip(*iss) vmeas = self._vmeas def calcZ(x): (vs, ivals) = x if vmeas: ix = np.where(np.array(vs) >= vmeas)[0][0] else: ix = np.where(np.array(vs) >= max(vs) / 2)[0][0] try: return abs((vs[ix] - vs[ix - 1]) / (ivals[ix] - ivals[ix - 1])) except ZeroDivisionError: return 1e7 # Use 10 MOhms in place of infinity. zs = map(calcZ, zip([vs, vs, vs], [ityps, imins, imaxs])) return vs, ityps, imins, imaxs, zs # Infer impedance and/or rise/fall time, as per model type. mtype = self._mtype.lower() if mtype in ("output", "i/o"): if "pulldown" not in subDict or "pullup" not in subDict: raise LookupError("Missing I-V curves!") plotdata = ArrayPlotData() pd_vs, pd_ityps, pd_imins, pd_imaxs, pd_zs = proc_iv(subDict["pulldown"]) pu_vs, pu_ityps, pu_imins, pu_imaxs, pu_zs = proc_iv(subDict["pullup"]) pu_vs = self._vrange[0] - np.array(pu_vs) # Correct for Vdd-relative pull-up voltages. pu_ityps = -np.array(pu_ityps) # Correct for current sense, for nicer plot. pu_imins = -np.array(pu_imins) pu_imaxs = -np.array(pu_imaxs) self._zout = (list(pd_zs)[0] + list(pu_zs)[0]) / 2 plotdata.set_data("pd_vs", pd_vs) plotdata.set_data("pd_ityps", pd_ityps) plotdata.set_data("pd_imins", pd_imins) plotdata.set_data("pd_imaxs", pd_imaxs) plotdata.set_data("pu_vs", pu_vs) plotdata.set_data("pu_ityps", pu_ityps) plotdata.set_data("pu_imins", pu_imins) plotdata.set_data("pu_imaxs", pu_imaxs) plot_iv = Plot(plotdata) # , padding_left=75) # The 'line_style' trait of a LinePlot instance must be 'dash' or 'dot dash' or 'dot' or 'long dash' or 'solid'. plot_iv.plot(("pd_vs", "pd_ityps"), type="line", color="blue", line_style="solid", name="PD-Typ") plot_iv.plot(("pd_vs", "pd_imins"), type="line", color="blue", line_style="dot", name="PD-Min") plot_iv.plot(("pd_vs", "pd_imaxs"), type="line", color="blue", line_style="dash", name="PD-Max") plot_iv.plot(("pu_vs", "pu_ityps"), type="line", color="red", line_style="solid", name="PU-Typ") plot_iv.plot(("pu_vs", "pu_imins"), type="line", color="red", line_style="dot", name="PU-Min") plot_iv.plot(("pu_vs", "pu_imaxs"), type="line", color="red", line_style="dash", name="PU-Max") plot_iv.title = "Pull-Up/Down I-V Curves" plot_iv.index_axis.title = "Vout (V)" plot_iv.value_axis.title = "Iout (A)" plot_iv.index_range.low_setting = 0 plot_iv.index_range.high_setting = self._vrange[0] plot_iv.value_range.low_setting = 0 plot_iv.value_range.high_setting = 0.1 plot_iv.legend.visible = True plot_iv.legend.align = "ul" self.plot_iv = plot_iv if not self._ramp: raise LookupError("Missing [Ramp]!") ramp = subDict["ramp"] self._slew = (ramp["rising"][0] + ramp["falling"][0]) / 2e9 # (V/ns) elif mtype == "input": if "gnd_clamp" not in subDict or "power_clamp" not in subDict: raise LookupError("Missing clamp curves!") plotdata = ArrayPlotData() gc_vs, gc_ityps, gc_imins, gc_imaxs, gc_zs = proc_iv(subDict["gnd_clamp"]) pc_vs, pc_ityps, pc_imins, pc_imaxs, pc_zs = proc_iv(subDict["power_clamp"]) pc_vs = self._vrange[0] - np.array(pc_vs) # Correct for Vdd-relative pull-up voltages. pc_ityps = -np.array(pc_ityps) # Correct for current sense, for nicer plot. pc_imins = -np.array(pc_imins) pc_imaxs = -np.array(pc_imaxs) gc_z = list(gc_zs)[0] # Use typical value for Zin calc. pc_z = list(pc_zs)[0] self._zin = (gc_z * pc_z) / (gc_z + pc_z) # Parallel combination, as both clamps are always active. plotdata.set_data("gc_vs", gc_vs) plotdata.set_data("gc_ityps", gc_ityps) plotdata.set_data("gc_imins", gc_imins) plotdata.set_data("gc_imaxs", gc_imaxs) plotdata.set_data("pc_vs", pc_vs) plotdata.set_data("pc_ityps", pc_ityps) plotdata.set_data("pc_imins", pc_imins) plotdata.set_data("pc_imaxs", pc_imaxs) plot_iv = Plot(plotdata) # , padding_left=75) # The 'line_style' trait of a LinePlot instance must be 'dash' or 'dot dash' or 'dot' or 'long dash' or 'solid'. plot_iv.plot(("gc_vs", "gc_ityps"), type="line", color="blue", line_style="solid", name="PD-Typ") plot_iv.plot(("gc_vs", "gc_imins"), type="line", color="blue", line_style="dot", name="PD-Min") plot_iv.plot(("gc_vs", "gc_imaxs"), type="line", color="blue", line_style="dash", name="PD-Max") plot_iv.plot(("pc_vs", "pc_ityps"), type="line", color="red", line_style="solid", name="PU-Typ") plot_iv.plot(("pc_vs", "pc_imins"), type="line", color="red", line_style="dot", name="PU-Min") plot_iv.plot(("pc_vs", "pc_imaxs"), type="line", color="red", line_style="dash", name="PU-Max") plot_iv.title = "Power/GND Clamp I-V Curves" plot_iv.index_axis.title = "Vin (V)" plot_iv.value_axis.title = "Iin (A)" plot_iv.index_range.low_setting = 0 plot_iv.index_range.high_setting = self._vrange[0] plot_iv.value_range.low_setting = 0 plot_iv.value_range.high_setting = 0.1 plot_iv.legend.visible = True plot_iv.legend.align = "ul" self.plot_iv = plot_iv # Separate AMI executables by OS. def is64(x): ((_, b), _) = x return int(b) == 64 def isWin(x): ((os, _), _) = x return os.lower() == "windows" def partition(p, xs): ts, fs = [], [] for x in xs: if p(x): ts.append(x) else: fs.append(x) return ts, fs def getFiles(x): if x: ((_, _), fs) = x[0] return fs return [] def splitExecs(fs): wins, lins = partition(isWin, fs) return (getFiles(wins), getFiles(lins)) self._exec32Wins, self._exec32Lins = [], [] self._exec64Wins, self._exec64Lins = [], [] if "algorithmic_model" in subDict: execs = subDict["algorithmic_model"] exec64s, exec32s = partition(is64, execs) self._exec32Wins, self._exec32Lins = splitExecs(exec32s) self._exec64Wins, self._exec64Lins = splitExecs(exec64s) # Set up the GUI. self.add_trait("model_type", String(self._mtype)) self.add_trait("c_comp", String(self._ccomp)) self.add_trait("cref", String(self._cref)) self.add_trait("vref", String(self._vref)) self.add_trait("vmeas", String(self._vmeas)) self.add_trait("rref", String(self._rref)) self.add_trait("trange", String(self._trange)) self.add_trait("vrange", String(self._vrange)) if mtype in ("output", "i/o"): self.add_trait("zout", String(self._zout)) self.add_trait("slew", String(self._slew)) elif mtype == "input": self.add_trait("zin", String(self._zin)) self._content = [ Group( Item("model_type", label="Model type", style="readonly"), Item("c_comp", label="Ccomp", style="readonly"), Item("trange", label="Temperature Range", style="readonly"), Item("vrange", label="Voltage Range", style="readonly"), Group( Item("cref", label="Cref", style="readonly"), Item("vref", label="Vref", style="readonly"), Item("vmeas", label="Vmeas", style="readonly"), Item("rref", label="Rref", style="readonly"), orientation="horizontal", ), label="Model", show_border=True, ), ] if mtype in ("output", "i/o"): self._content.append(Item("zout", label="Impedance (Ohms)", style="readonly", format_str="%4.1f")) self._content.append(Item("slew", label="Slew Rate (V/ns)", style="readonly", format_str="%4.1f")) self._content.append(Item("plot_iv", editor=ComponentEditor(), show_label=False)) elif mtype == "input": self._content.append(Item("zin", label="Impedance (Ohms)", style="readonly", format_str="%4.1f")) self._content.append(Item("plot_iv", editor=ComponentEditor(), show_label=False)) def __str__(self): res = "Model Type:\t" + self._mtype + "\n" res += "C_comp: \t" + str(self._ccomp) + "\n" res += "Cref: \t" + str(self._cref) + "\n" res += "Vref: \t" + str(self._vref) + "\n" res += "Vmeas: \t" + str(self._vmeas) + "\n" res += "Rref: \t" + str(self._rref) + "\n" res += "Temperature Range:\t" + str(self._trange) + "\n" res += "Voltage Range: \t" + str(self._vrange) + "\n" if "algorithmic_model" in self._subDict: res += "Algorithmic Model:\n" + "\t32-bit:\n" if self._exec32Lins: res += "\t\tLinux: " + str(self._exec32Lins) + "\n" if self._exec32Wins: res += "\t\tWindows: " + str(self._exec32Wins) + "\n" res += "\t64-bit:\n" if self._exec64Lins: res += "\t\tLinux: " + str(self._exec64Lins) + "\n" if self._exec64Wins: res += "\t\tWindows: " + str(self._exec64Wins) + "\n" return res def __call__(self): self.edit_traits(kind="livemodal") def default_traits_view(self): "Default Traits/UI view definition." view = View( resizable=False, buttons=ModalButtons, title="PyBERT IBIS Model Viewer", id="pyibisami.ibis_parser.Model", ) view.set_content(self._content) return view @property def zout(self): "The driver impedance." return self._zout @property def slew(self): "The driver slew rate." return self._slew @property def zin(self): "The input impedance." return self._zin @property def ccomp(self): "The parasitic capacitance." return self._ccomp @property def mtype(self): """Model type.""" return self._mtype