pyibis-ami 7.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyibis_ami-7.1.0.dist-info/LICENSE +8 -0
- pyibis_ami-7.1.0.dist-info/METADATA +134 -0
- pyibis_ami-7.1.0.dist-info/RECORD +26 -0
- pyibis_ami-7.1.0.dist-info/WHEEL +5 -0
- pyibis_ami-7.1.0.dist-info/entry_points.txt +4 -0
- pyibis_ami-7.1.0.dist-info/top_level.txt +1 -0
- pyibisami/IBIS_AMI_Checker.ipynb +1693 -0
- pyibisami/IBIS_AMI_Tester.ipynb +1457 -0
- pyibisami/__init__.py +22 -0
- pyibisami/__main__.py +7 -0
- pyibisami/ami/__init__.py +0 -0
- pyibisami/ami/config.py +297 -0
- pyibisami/ami/generic.ami.em +20 -0
- pyibisami/ami/generic.ibs.em +139 -0
- pyibisami/ami/model.py +596 -0
- pyibisami/ami/parameter.py +375 -0
- pyibisami/ami/parser.py +635 -0
- pyibisami/common.py +40 -0
- pyibisami/ibis/__init__.py +0 -0
- pyibisami/ibis/file.py +325 -0
- pyibisami/ibis/model.py +399 -0
- pyibisami/ibis/parser.py +525 -0
- pyibisami/tools/__init__.py +0 -0
- pyibisami/tools/run_notebook.py +144 -0
- pyibisami/tools/run_tests.py +273 -0
- pyibisami/tools/test_results.xsl +42 -0
pyibisami/ibis/model.py
ADDED
@@ -0,0 +1,399 @@
|
|
1
|
+
"""Classes for encapsulating IBIS model constituents.
|
2
|
+
|
3
|
+
Original Author: David Banas <capn.freako@gmail.com>
|
4
|
+
|
5
|
+
Original Date: November 1, 2019
|
6
|
+
|
7
|
+
For information regarding the IBIS modeling standard, visit:
|
8
|
+
https://ibis.org/
|
9
|
+
|
10
|
+
Copyright (c) 2019 by David Banas; All rights reserved World wide.
|
11
|
+
"""
|
12
|
+
|
13
|
+
import numpy as np
|
14
|
+
from chaco.api import ArrayPlotData, Plot
|
15
|
+
from enable.component_editor import ComponentEditor
|
16
|
+
from traits.api import HasTraits, String, Trait
|
17
|
+
from traitsui.api import Group, Item, ModalButtons, View
|
18
|
+
|
19
|
+
|
20
|
+
class Component(HasTraits):
|
21
|
+
"""Encapsulation of a particular component from an IBIS model file."""
|
22
|
+
|
23
|
+
def __init__(self, subDict):
|
24
|
+
"""
|
25
|
+
Args:
|
26
|
+
subDict(dict): Dictionary of [Component] sub-keywords/params.
|
27
|
+
"""
|
28
|
+
|
29
|
+
# Super-class initialization is ABSOLUTELY NECESSARY, in order
|
30
|
+
# to get all the Traits/UI machinery setup correctly.
|
31
|
+
super().__init__()
|
32
|
+
|
33
|
+
# Stash the sub-keywords/parameters.
|
34
|
+
self._subDict = subDict
|
35
|
+
|
36
|
+
# Fetch available keyword/parameter definitions.
|
37
|
+
def maybe(name):
|
38
|
+
return subDict[name] if name in subDict else None
|
39
|
+
|
40
|
+
self._mfr = maybe("manufacturer")
|
41
|
+
self._pkg = maybe("package")
|
42
|
+
self._pins = maybe("pin")
|
43
|
+
self._diffs = maybe("diff_pin")
|
44
|
+
|
45
|
+
# Check for the required keywords.
|
46
|
+
if not self._mfr:
|
47
|
+
raise LookupError("Missing [Manufacturer]!")
|
48
|
+
if not self._pkg:
|
49
|
+
print(self._mfr)
|
50
|
+
raise LookupError("Missing [Package]!")
|
51
|
+
if not self._pins:
|
52
|
+
raise LookupError("Missing [Pin]!")
|
53
|
+
|
54
|
+
# Set up the GUI.
|
55
|
+
self.add_trait("manufacturer", String(self._mfr))
|
56
|
+
self.add_trait("package", String(self._pkg))
|
57
|
+
self.add_trait("_pin", Trait(list(self._pins)[0], self._pins))
|
58
|
+
self._content = [
|
59
|
+
Group(
|
60
|
+
Item("manufacturer", label="Manufacturer", style="readonly"),
|
61
|
+
Item("package", label="Package", style="readonly"),
|
62
|
+
Item("_pin", label="Pin"),
|
63
|
+
label="Component",
|
64
|
+
show_border=True,
|
65
|
+
),
|
66
|
+
]
|
67
|
+
|
68
|
+
def __str__(self):
|
69
|
+
res = "Manufacturer:\t" + self._mfr + "\n"
|
70
|
+
res += "Package: \t" + str(self._pkg) + "\n"
|
71
|
+
res += "Pins:\n"
|
72
|
+
for pname in self._pins:
|
73
|
+
res += " " + pname + ":\t" + str(self._pins[pname]) + "\n"
|
74
|
+
return res
|
75
|
+
|
76
|
+
def __call__(self):
|
77
|
+
self.edit_traits()
|
78
|
+
|
79
|
+
def default_traits_view(self):
|
80
|
+
"Default Traits/UI view definition."
|
81
|
+
view = View(
|
82
|
+
resizable=False,
|
83
|
+
buttons=ModalButtons,
|
84
|
+
title="PyBERT IBIS Component Viewer",
|
85
|
+
id="pyibisami.ibis_parser.Component",
|
86
|
+
)
|
87
|
+
view.set_content(self._content)
|
88
|
+
return view
|
89
|
+
|
90
|
+
@property
|
91
|
+
def pin(self):
|
92
|
+
"""The pin selected most recently by the user.
|
93
|
+
|
94
|
+
Returns the first pin in the list, if the user hasn't made a
|
95
|
+
selection yet.
|
96
|
+
"""
|
97
|
+
return self._pin_
|
98
|
+
|
99
|
+
@property
|
100
|
+
def pins(self):
|
101
|
+
"The list of component pins."
|
102
|
+
return self._pins
|
103
|
+
|
104
|
+
|
105
|
+
class Model(HasTraits): # pylint: disable=too-many-instance-attributes
|
106
|
+
"""Encapsulation of a particular I/O model from an IBIS model file."""
|
107
|
+
|
108
|
+
def __init__(self, subDict): # pylint: disable=too-many-locals,too-many-statements,too-many-branches
|
109
|
+
"""
|
110
|
+
Args:
|
111
|
+
subDict (dict): Dictionary of sub-keywords/params.
|
112
|
+
"""
|
113
|
+
|
114
|
+
# Super-class initialization is ABSOLUTELY NECESSARY, in order
|
115
|
+
# to get all the Traits/UI machinery setup correctly.
|
116
|
+
super().__init__()
|
117
|
+
|
118
|
+
# Stash the sub-keywords/parameters.
|
119
|
+
self._subDict = subDict
|
120
|
+
|
121
|
+
# Fetch available keyword/parameter definitions.
|
122
|
+
def maybe(name):
|
123
|
+
return subDict[name] if name in subDict else None
|
124
|
+
|
125
|
+
self._mtype = maybe("model_type")
|
126
|
+
self._ccomp = maybe("c_comp")
|
127
|
+
self._cref = maybe("cref")
|
128
|
+
self._vref = maybe("vref")
|
129
|
+
self._vmeas = maybe("vmeas")
|
130
|
+
self._rref = maybe("rref")
|
131
|
+
self._trange = maybe("temperature_range")
|
132
|
+
self._vrange = maybe("voltage_range")
|
133
|
+
self._ramp = maybe("ramp")
|
134
|
+
|
135
|
+
# Check for the required keywords.
|
136
|
+
if not self._mtype:
|
137
|
+
raise LookupError("Missing Model_type!")
|
138
|
+
if not self._vrange:
|
139
|
+
raise LookupError("Missing [Voltage Range]!")
|
140
|
+
|
141
|
+
def proc_iv(xs):
|
142
|
+
"""Process an I/V table."""
|
143
|
+
if len(xs) < 2:
|
144
|
+
raise ValueError("Insufficient number of I-V data points!")
|
145
|
+
try:
|
146
|
+
vs, iss = zip(*(xs)) # Idiomatic Python for ``unzip``.
|
147
|
+
except Exception as exc:
|
148
|
+
raise ValueError(f"xs: {xs}") from exc
|
149
|
+
ityps, imins, imaxs = zip(*iss)
|
150
|
+
vmeas = self._vmeas
|
151
|
+
|
152
|
+
def calcZ(x):
|
153
|
+
(vs, ivals) = x
|
154
|
+
if vmeas:
|
155
|
+
ix = np.where(np.array(vs) >= vmeas)[0][0]
|
156
|
+
else:
|
157
|
+
ix = np.where(np.array(vs) >= max(vs) / 2)[0][0]
|
158
|
+
try:
|
159
|
+
return abs((vs[ix] - vs[ix - 1]) / (ivals[ix] - ivals[ix - 1]))
|
160
|
+
except ZeroDivisionError:
|
161
|
+
return 1e7 # Use 10 MOhms in place of infinity.
|
162
|
+
|
163
|
+
zs = map(calcZ, zip([vs, vs, vs], [ityps, imins, imaxs]))
|
164
|
+
return vs, ityps, imins, imaxs, zs
|
165
|
+
|
166
|
+
# Infer impedance and/or rise/fall time, as per model type.
|
167
|
+
mtype = self._mtype.lower()
|
168
|
+
if mtype in ("output", "i/o"):
|
169
|
+
if "pulldown" not in subDict or "pullup" not in subDict:
|
170
|
+
raise LookupError("Missing I-V curves!")
|
171
|
+
plotdata = ArrayPlotData()
|
172
|
+
pd_vs, pd_ityps, pd_imins, pd_imaxs, pd_zs = proc_iv(subDict["pulldown"])
|
173
|
+
pu_vs, pu_ityps, pu_imins, pu_imaxs, pu_zs = proc_iv(subDict["pullup"])
|
174
|
+
pu_vs = self._vrange[0] - np.array(pu_vs) # Correct for Vdd-relative pull-up voltages.
|
175
|
+
pu_ityps = -np.array(pu_ityps) # Correct for current sense, for nicer plot.
|
176
|
+
pu_imins = -np.array(pu_imins)
|
177
|
+
pu_imaxs = -np.array(pu_imaxs)
|
178
|
+
self._zout = (list(pd_zs)[0] + list(pu_zs)[0]) / 2
|
179
|
+
plotdata.set_data("pd_vs", pd_vs)
|
180
|
+
plotdata.set_data("pd_ityps", pd_ityps)
|
181
|
+
plotdata.set_data("pd_imins", pd_imins)
|
182
|
+
plotdata.set_data("pd_imaxs", pd_imaxs)
|
183
|
+
plotdata.set_data("pu_vs", pu_vs)
|
184
|
+
plotdata.set_data("pu_ityps", pu_ityps)
|
185
|
+
plotdata.set_data("pu_imins", pu_imins)
|
186
|
+
plotdata.set_data("pu_imaxs", pu_imaxs)
|
187
|
+
plot_iv = Plot(plotdata) # , padding_left=75)
|
188
|
+
# The 'line_style' trait of a LinePlot instance must be 'dash' or 'dot dash' or 'dot' or 'long dash' or 'solid'.
|
189
|
+
plot_iv.plot(("pd_vs", "pd_ityps"), type="line", color="blue", line_style="solid", name="PD-Typ")
|
190
|
+
plot_iv.plot(("pd_vs", "pd_imins"), type="line", color="blue", line_style="dot", name="PD-Min")
|
191
|
+
plot_iv.plot(("pd_vs", "pd_imaxs"), type="line", color="blue", line_style="dash", name="PD-Max")
|
192
|
+
plot_iv.plot(("pu_vs", "pu_ityps"), type="line", color="red", line_style="solid", name="PU-Typ")
|
193
|
+
plot_iv.plot(("pu_vs", "pu_imins"), type="line", color="red", line_style="dot", name="PU-Min")
|
194
|
+
plot_iv.plot(("pu_vs", "pu_imaxs"), type="line", color="red", line_style="dash", name="PU-Max")
|
195
|
+
plot_iv.title = "Pull-Up/Down I-V Curves"
|
196
|
+
plot_iv.index_axis.title = "Vout (V)"
|
197
|
+
plot_iv.value_axis.title = "Iout (A)"
|
198
|
+
plot_iv.index_range.low_setting = 0
|
199
|
+
plot_iv.index_range.high_setting = self._vrange[0]
|
200
|
+
plot_iv.value_range.low_setting = 0
|
201
|
+
plot_iv.value_range.high_setting = 0.1
|
202
|
+
plot_iv.legend.visible = True
|
203
|
+
plot_iv.legend.align = "ul"
|
204
|
+
self.plot_iv = plot_iv
|
205
|
+
|
206
|
+
if not self._ramp:
|
207
|
+
raise LookupError("Missing [Ramp]!")
|
208
|
+
ramp = subDict["ramp"]
|
209
|
+
self._slew = (ramp["rising"][0] + ramp["falling"][0]) / 2e9 # (V/ns)
|
210
|
+
elif mtype == "input":
|
211
|
+
if "gnd_clamp" not in subDict and "power_clamp" not in subDict:
|
212
|
+
raise LookupError("Missing clamp curves!")
|
213
|
+
|
214
|
+
plotdata = ArrayPlotData()
|
215
|
+
|
216
|
+
if "gnd_clamp" in subDict:
|
217
|
+
gc_vs, gc_ityps, gc_imins, gc_imaxs, gc_zs = proc_iv(subDict["gnd_clamp"])
|
218
|
+
gc_z = list(gc_zs)[0] # Use typical value for Zin calc.
|
219
|
+
plotdata.set_data("gc_vs", gc_vs)
|
220
|
+
plotdata.set_data("gc_ityps", gc_ityps)
|
221
|
+
plotdata.set_data("gc_imins", gc_imins)
|
222
|
+
plotdata.set_data("gc_imaxs", gc_imaxs)
|
223
|
+
|
224
|
+
if "power_clamp" in subDict:
|
225
|
+
pc_vs, pc_ityps, pc_imins, pc_imaxs, pc_zs = proc_iv(subDict["power_clamp"])
|
226
|
+
pc_z = list(pc_zs)[0]
|
227
|
+
pc_vs = self._vrange[0] - np.array(pc_vs) # Correct for Vdd-relative pull-up voltages.
|
228
|
+
pc_ityps = -np.array(pc_ityps) # Correct for current sense, for nicer plot.
|
229
|
+
pc_imins = -np.array(pc_imins)
|
230
|
+
pc_imaxs = -np.array(pc_imaxs)
|
231
|
+
plotdata.set_data("pc_vs", pc_vs)
|
232
|
+
plotdata.set_data("pc_ityps", pc_ityps)
|
233
|
+
plotdata.set_data("pc_imins", pc_imins)
|
234
|
+
plotdata.set_data("pc_imaxs", pc_imaxs)
|
235
|
+
|
236
|
+
plot_iv = Plot(plotdata) # , padding_left=75)
|
237
|
+
# The 'line_style' trait of a LinePlot instance must be 'dash' or 'dot dash' or 'dot' or 'long dash' or 'solid'.
|
238
|
+
if "gnd_clamp" in subDict:
|
239
|
+
plot_iv.plot(("gc_vs", "gc_ityps"), type="line", color="blue", line_style="solid", name="PD-Typ")
|
240
|
+
plot_iv.plot(("gc_vs", "gc_imins"), type="line", color="blue", line_style="dot", name="PD-Min")
|
241
|
+
plot_iv.plot(("gc_vs", "gc_imaxs"), type="line", color="blue", line_style="dash", name="PD-Max")
|
242
|
+
if "power_clamp" in subDict:
|
243
|
+
plot_iv.plot(("pc_vs", "pc_ityps"), type="line", color="red", line_style="solid", name="PU-Typ")
|
244
|
+
plot_iv.plot(("pc_vs", "pc_imins"), type="line", color="red", line_style="dot", name="PU-Min")
|
245
|
+
plot_iv.plot(("pc_vs", "pc_imaxs"), type="line", color="red", line_style="dash", name="PU-Max")
|
246
|
+
plot_iv.title = "Power/GND Clamp I-V Curves"
|
247
|
+
plot_iv.index_axis.title = "Vin (V)"
|
248
|
+
plot_iv.value_axis.title = "Iin (A)"
|
249
|
+
plot_iv.index_range.low_setting = 0
|
250
|
+
plot_iv.index_range.high_setting = self._vrange[0]
|
251
|
+
plot_iv.value_range.low_setting = 0
|
252
|
+
plot_iv.value_range.high_setting = 0.1
|
253
|
+
plot_iv.legend.visible = True
|
254
|
+
plot_iv.legend.align = "ul"
|
255
|
+
self.plot_iv = plot_iv
|
256
|
+
|
257
|
+
if "gnd_clamp" in subDict and "power_clamp" in subDict:
|
258
|
+
# Parallel combination, as both clamps are always active.
|
259
|
+
self._zin = (gc_z * pc_z) / (gc_z + pc_z) # pylint: disable=possibly-used-before-assignment
|
260
|
+
elif "gnd_clamp" in subDict:
|
261
|
+
self._zin = gc_z
|
262
|
+
else:
|
263
|
+
self._zin = pc_z
|
264
|
+
|
265
|
+
# Separate AMI executables by OS.
|
266
|
+
def is64(x):
|
267
|
+
((_, b), _) = x
|
268
|
+
return int(b) == 64
|
269
|
+
|
270
|
+
def isWin(x):
|
271
|
+
((os, _), _) = x
|
272
|
+
return os.lower() == "windows"
|
273
|
+
|
274
|
+
def partition(p, xs):
|
275
|
+
ts, fs = [], []
|
276
|
+
for x in xs:
|
277
|
+
if p(x):
|
278
|
+
ts.append(x)
|
279
|
+
else:
|
280
|
+
fs.append(x)
|
281
|
+
return ts, fs
|
282
|
+
|
283
|
+
def getFiles(x):
|
284
|
+
if x:
|
285
|
+
((_, _), fs) = x[0]
|
286
|
+
return fs
|
287
|
+
return []
|
288
|
+
|
289
|
+
def splitExecs(fs):
|
290
|
+
wins, lins = partition(isWin, fs)
|
291
|
+
return (getFiles(wins), getFiles(lins))
|
292
|
+
|
293
|
+
self._exec32Wins, self._exec32Lins = [], []
|
294
|
+
self._exec64Wins, self._exec64Lins = [], []
|
295
|
+
if "algorithmic_model" in subDict:
|
296
|
+
execs = subDict["algorithmic_model"]
|
297
|
+
exec64s, exec32s = partition(is64, execs)
|
298
|
+
self._exec32Wins, self._exec32Lins = splitExecs(exec32s)
|
299
|
+
self._exec64Wins, self._exec64Lins = splitExecs(exec64s)
|
300
|
+
|
301
|
+
# Set up the GUI.
|
302
|
+
self.add_trait("model_type", String(self._mtype))
|
303
|
+
self.add_trait("c_comp", String(self._ccomp))
|
304
|
+
self.add_trait("cref", String(self._cref))
|
305
|
+
self.add_trait("vref", String(self._vref))
|
306
|
+
self.add_trait("vmeas", String(self._vmeas))
|
307
|
+
self.add_trait("rref", String(self._rref))
|
308
|
+
self.add_trait("trange", String(self._trange))
|
309
|
+
self.add_trait("vrange", String(self._vrange))
|
310
|
+
if mtype in ("output", "i/o"):
|
311
|
+
self.add_trait("zout", String(self._zout))
|
312
|
+
self.add_trait("slew", String(self._slew))
|
313
|
+
elif mtype == "input":
|
314
|
+
self.add_trait("zin", String(self._zin))
|
315
|
+
self._content = [
|
316
|
+
Group(
|
317
|
+
Item("model_type", label="Model type", style="readonly"),
|
318
|
+
Item("c_comp", label="Ccomp", style="readonly"),
|
319
|
+
Item("trange", label="Temperature Range", style="readonly"),
|
320
|
+
Item("vrange", label="Voltage Range", style="readonly"),
|
321
|
+
Group(
|
322
|
+
Item("cref", label="Cref", style="readonly"),
|
323
|
+
Item("vref", label="Vref", style="readonly"),
|
324
|
+
Item("vmeas", label="Vmeas", style="readonly"),
|
325
|
+
Item("rref", label="Rref", style="readonly"),
|
326
|
+
orientation="horizontal",
|
327
|
+
),
|
328
|
+
label="Model",
|
329
|
+
show_border=True,
|
330
|
+
),
|
331
|
+
]
|
332
|
+
if mtype in ("output", "i/o"):
|
333
|
+
self._content.append(Item("zout", label="Impedance (Ohms)", style="readonly", format_str="%4.1f"))
|
334
|
+
self._content.append(Item("slew", label="Slew Rate (V/ns)", style="readonly", format_str="%4.1f"))
|
335
|
+
self._content.append(Item("plot_iv", editor=ComponentEditor(), show_label=False))
|
336
|
+
elif mtype == "input":
|
337
|
+
self._content.append(Item("zin", label="Impedance (Ohms)", style="readonly", format_str="%4.1f"))
|
338
|
+
self._content.append(Item("plot_iv", editor=ComponentEditor(), show_label=False))
|
339
|
+
|
340
|
+
def __str__(self):
|
341
|
+
res = "Model Type:\t" + self._mtype + "\n"
|
342
|
+
res += "C_comp: \t" + str(self._ccomp) + "\n"
|
343
|
+
res += "Cref: \t" + str(self._cref) + "\n"
|
344
|
+
res += "Vref: \t" + str(self._vref) + "\n"
|
345
|
+
res += "Vmeas: \t" + str(self._vmeas) + "\n"
|
346
|
+
res += "Rref: \t" + str(self._rref) + "\n"
|
347
|
+
res += "Temperature Range:\t" + str(self._trange) + "\n"
|
348
|
+
res += "Voltage Range: \t" + str(self._vrange) + "\n"
|
349
|
+
if "algorithmic_model" in self._subDict:
|
350
|
+
res += "Algorithmic Model:\n" + "\t32-bit:\n"
|
351
|
+
if self._exec32Lins:
|
352
|
+
res += "\t\tLinux: " + str(self._exec32Lins) + "\n"
|
353
|
+
if self._exec32Wins:
|
354
|
+
res += "\t\tWindows: " + str(self._exec32Wins) + "\n"
|
355
|
+
res += "\t64-bit:\n"
|
356
|
+
if self._exec64Lins:
|
357
|
+
res += "\t\tLinux: " + str(self._exec64Lins) + "\n"
|
358
|
+
if self._exec64Wins:
|
359
|
+
res += "\t\tWindows: " + str(self._exec64Wins) + "\n"
|
360
|
+
return res
|
361
|
+
|
362
|
+
def __call__(self):
|
363
|
+
self.edit_traits(kind="livemodal")
|
364
|
+
|
365
|
+
def default_traits_view(self):
|
366
|
+
"Default Traits/UI view definition."
|
367
|
+
view = View(
|
368
|
+
resizable=False,
|
369
|
+
buttons=ModalButtons,
|
370
|
+
title="PyBERT IBIS Model Viewer",
|
371
|
+
id="pyibisami.ibis_parser.Model",
|
372
|
+
)
|
373
|
+
view.set_content(self._content)
|
374
|
+
return view
|
375
|
+
|
376
|
+
@property
|
377
|
+
def zout(self):
|
378
|
+
"The driver impedance."
|
379
|
+
return self._zout
|
380
|
+
|
381
|
+
@property
|
382
|
+
def slew(self):
|
383
|
+
"The driver slew rate."
|
384
|
+
return self._slew
|
385
|
+
|
386
|
+
@property
|
387
|
+
def zin(self):
|
388
|
+
"The input impedance."
|
389
|
+
return self._zin
|
390
|
+
|
391
|
+
@property
|
392
|
+
def ccomp(self):
|
393
|
+
"The parasitic capacitance."
|
394
|
+
return self._ccomp
|
395
|
+
|
396
|
+
@property
|
397
|
+
def mtype(self):
|
398
|
+
"""Model type."""
|
399
|
+
return self._mtype
|