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.
@@ -0,0 +1,375 @@
1
+ """``AMIParameter`` class definition, plus some helpers.
2
+
3
+ Original author: David Banas <capn.freako@gmail.com>
4
+
5
+ Original date: December 24, 2016
6
+
7
+ Copyright (c) 2019 David Banas; all rights reserved World wide.
8
+ """
9
+
10
+ #####
11
+ # AMI parameter
12
+ #####
13
+
14
+
15
+ class AMIParamError(Exception):
16
+ """Base Exception for all AMI Parameter Errors."""
17
+
18
+
19
+ class AMIParameter: # pylint: disable=too-many-instance-attributes,too-few-public-methods
20
+ """IBIS-AMI model parameter.
21
+
22
+ This class encapsulates the attributes and behavior of a AMI
23
+ parameter.
24
+ """
25
+
26
+ RESERVED_PARAM_NAMES = [
27
+ "AMI_Version",
28
+ "Init_Returns_Impulse",
29
+ "GetWave_Exists",
30
+ "Use_Init_Output",
31
+ "Max_Init_Aggressors",
32
+ "Ignore_Bits",
33
+ "Resolve_Exists",
34
+ "Model_Name",
35
+ "Special_Param_Names",
36
+ "Component_Name",
37
+ "Signal_Name",
38
+ "Rx_Decision_Time",
39
+ "DC_Offset",
40
+ "Rx_Use_Clock_Input",
41
+ "Supporting_Files",
42
+ "DLL_Path",
43
+ "DLL_ID",
44
+ "Tx_Jitter",
45
+ "Tx_DCD",
46
+ "Tx_Rj",
47
+ "Tx_Dj",
48
+ "Tx_Sj",
49
+ "Tx_Sj_Frequency",
50
+ "Rx_DCD",
51
+ "Rx_Rj",
52
+ "Rx_Dj",
53
+ "Rx_Sj",
54
+ "Rx_Clock_PDF",
55
+ "Rx_Clock_Recovery_Mean",
56
+ "Rx_Clock_Recovery_Rj",
57
+ "Rx_Clock_Recovery_Dj",
58
+ "Rx_Clock_Recovery_Sj",
59
+ "Rx_Clock_Recovery_DCD",
60
+ "Rx_Receiver_Sensitivity",
61
+ "Rx_Noise",
62
+ "Rx_GaussianNoise",
63
+ "Rx_UniformNoise",
64
+ "Modulation",
65
+ "PAM4_Mapping",
66
+ "PAM4_UpperThreshold",
67
+ "PAM4_CenterThreshold",
68
+ "PAM4_LowerThreshold",
69
+ "PAM4_UpperEyeOffset",
70
+ "PAM4_CenterEyeOffset",
71
+ "PAM4_LowerEyeOffset",
72
+ "Repeater_Type",
73
+ "BCI_Protocol",
74
+ "BCI_ID",
75
+ "BCI_State",
76
+ "BCI_Message_Interval_UI",
77
+ "BCI_Training_UI",
78
+ "BCI_Training_Mode",
79
+ "Ts4file",
80
+ "Tx_V",
81
+ "Tx_R",
82
+ "Rx_R",
83
+ ]
84
+
85
+ # Properties.
86
+
87
+ # Note: They are read-only, despite the presence of apparent setters.
88
+ # (The idea is that, once initialized, parameter definitions
89
+ # are immutable.)
90
+ # The only exception to this is pvalue, which has been made
91
+ # writable, for scripted non-GUI use cases.
92
+ # Be very careful w/ this; there is NO CHECKING!
93
+
94
+ # Note that the setters, below, are only intended for use by
95
+ # __init__(). They may raise an *AMIParamError* exception. This is
96
+ # to ensure that a malformed instance never be created.
97
+
98
+ def _get_name(self):
99
+ """pname."""
100
+ return self._name
101
+
102
+ pname = property(_get_name, doc="Name of AMI parameter.")
103
+
104
+ # pusage
105
+ def _set_usage(self, values):
106
+ "Process *Usage* tag."
107
+
108
+ val = values[0]
109
+ if val in ("In", "Out", "InOut", "Info"):
110
+ self._usage = val
111
+ else:
112
+ raise AMIParamError(f"Unrecognized usage value: '{val}'.")
113
+
114
+ def _get_usage(self):
115
+ return self._usage
116
+
117
+ pusage = property(_get_usage, doc="Value of AMI parameter 'Usage' tag.")
118
+
119
+ # ptype
120
+ def _set_type(self, values):
121
+ "Process *Type* tag."
122
+
123
+ val = values[0]
124
+ if val in ("Float", "Integer", "String", "Boolean", "UI", "Tap"):
125
+ self._type = val
126
+ else:
127
+ raise AMIParamError(f"Unrecognized type value: '{val}'.")
128
+
129
+ def _get_type(self):
130
+ return self._type
131
+
132
+ ptype = property(_get_type, doc="Value of AMI parameter 'Type' tag.")
133
+
134
+ # pformat
135
+ def _set_format(self, values):
136
+ "Process *Format* tag."
137
+
138
+ form = values[0]
139
+ if len(values) < 2:
140
+ raise AMIParamError(f"No values provided for: '{form}'.")
141
+ self._format = form
142
+ self._format_rem = values[1:]
143
+
144
+ def _get_format(self):
145
+ return self._format
146
+
147
+ pformat = property(_get_format, doc="Value of AMI parameter 'Format' tag.")
148
+
149
+ # pvalue
150
+ def _get_value(self):
151
+ return self._value
152
+
153
+ def _set_val(self, new_val):
154
+ self._value = new_val
155
+
156
+ pvalue = property(_get_value, _set_val, doc="Value of AMI parameter.")
157
+
158
+ # pmin
159
+ def _get_min(self):
160
+ return self._min
161
+
162
+ pmin = property(_get_min, doc="Minimum value of AMI parameter.")
163
+
164
+ # pmax
165
+ def _get_max(self):
166
+ return self._max
167
+
168
+ pmax = property(_get_max, doc="Maximum value of AMI parameter.")
169
+
170
+ # default
171
+ def _set_default(self, values):
172
+ """Process *Default* tag."""
173
+ self._default = values[0]
174
+
175
+ def _get_default(self):
176
+ return self._default
177
+
178
+ pdefault = property(_get_default, doc="Default value of AMI parameter.")
179
+
180
+ # pdescription
181
+ def _set_description(self, values):
182
+ """Process *Description* tag."""
183
+ self._description = values[0]
184
+
185
+ def _get_description(self):
186
+ return self._description
187
+
188
+ pdescription = property(_get_description, doc="Description of AMI parameter.")
189
+
190
+ # plist_tip
191
+ def _set_list_tip(self, values):
192
+ """Process *List_Tip* tag."""
193
+ self._list_tip = [x.strip('"') for x in values]
194
+
195
+ def _get_list_tip(self):
196
+ return self._list_tip
197
+
198
+ plist_tip = property(_get_list_tip, doc="List tips of AMI parameter.")
199
+
200
+ # Helpers.
201
+ # These 3 just accomodate the awkwardness caused by the optional
202
+ # nature of the 'Format' keyword, in *.AMI files. Since, a properly
203
+ # formed instance of *AMIParameter* will always have a *format*
204
+ # property, we don't need to make these properties of that class.
205
+
206
+ # value
207
+ def _set_value(self, values):
208
+ """Process *Value* tag."""
209
+
210
+ return self._set_format(["Value"] + values)
211
+
212
+ # range
213
+ def _set_range(self, values):
214
+ """Process *Range* tag."""
215
+
216
+ return self._set_format(["Range"] + values)
217
+
218
+ # usage
219
+ def _set_list(self, values):
220
+ """Process *List* tag."""
221
+
222
+ return self._set_format(["List"] + values)
223
+
224
+ # Holds any warnings encountered, during initialization.
225
+ # (Any errors encountered will prevent initialization from completing.)
226
+ _msg = ""
227
+
228
+ def _get_msg(self):
229
+ return self._msg
230
+
231
+ msg = property(_get_msg, doc="Any warning messages encountered, during parameter initialization.")
232
+
233
+ # This dictionary defines both:
234
+ #
235
+ # - the allowed parameter definition tag names, and
236
+ # - their processing functions.
237
+ #
238
+ # The idea is to allow this class to grow along with the IBIS
239
+ # standard, without having to change any of its boilerplate.
240
+
241
+ _param_def_tag_procs = {
242
+ "Usage": _set_usage,
243
+ "Type": _set_type,
244
+ "Format": _set_format,
245
+ "Value": _set_value,
246
+ "Range": _set_range,
247
+ "List": _set_list,
248
+ "Corner": _set_list,
249
+ "Default": _set_default,
250
+ "Description": _set_description,
251
+ "List_Tip": _set_list_tip,
252
+ "Label": _set_list_tip,
253
+ "Labels": _set_list_tip,
254
+ }
255
+
256
+ def __init__(self, name, tags): # pylint: disable=too-many-branches,too-many-statements
257
+ """
258
+ Args:
259
+ name (str): The name of the AMI parameter being created.
260
+ tags ([(str, [a])]): A list of pairs, each containing
261
+
262
+ - a parameter definition tag name
263
+ (Must be one of the keys from the '_param_def_tag_procs' dictionary.)
264
+
265
+ - a list of values to be associated with that tag.
266
+ """
267
+ # Initialization
268
+ self._usage = None
269
+ self._type = None
270
+ self._format = None
271
+ self._value = None
272
+ self._min = None
273
+ self._max = None
274
+ self._default = None
275
+ self._description = ""
276
+ self._list_tip = None
277
+
278
+ # Holds any warnings encountered, during initialization.
279
+ # (Any errors encountered will prevent initialization from completing.)
280
+ self._msg = ""
281
+
282
+ # Process all parameter definition tags.
283
+ for tag in tags:
284
+ tag_name = tag[0]
285
+ if tag_name in self._param_def_tag_procs:
286
+ try:
287
+ self._param_def_tag_procs[tag_name](self, tag[1])
288
+ except AMIParamError as err:
289
+ raise AMIParamError(f"Problem initializing parameter, '{name}': {err}\n") from err
290
+
291
+ # Validate and complete the instance.
292
+ # Check for required tags.
293
+ param_usage = self._usage
294
+ param_type = self._type
295
+ param_format = self._format
296
+ param_default = self._default
297
+ if param_usage is None:
298
+ raise AMIParamError("Missing 'Usage' tag!\n")
299
+ if param_type is None:
300
+ raise AMIParamError("Missing 'Type' tag!\n")
301
+ if param_format is None:
302
+ if param_default is None:
303
+ raise AMIParamError("Missing both 'Format' and 'Default' tags!\n")
304
+ self._value = param_default
305
+ param_format = "Value"
306
+ self._format_rem = [param_default]
307
+ # Check for mutual exclusivity of 'Format Value' and 'Default'.
308
+ elif (param_format == "Value") and (param_default is not None):
309
+ self._msg += "'Format Value' and 'Default' both found! (They are mutually exclusive.)\n"
310
+
311
+ # Check for 'Default' used with parameter type 'Out'.
312
+ if (param_type == "Out") and (param_default is not None):
313
+ raise AMIParamError("'Default' may not be used with parameter type 'Out'!\n")
314
+
315
+ # Complete the instance.
316
+ vals = self._format_rem
317
+ if param_format == "Value":
318
+ value_str = vals[0].strip()
319
+ if param_type in ("Float", "UI"):
320
+ try:
321
+ self._value = float(value_str)
322
+ except (ValueError, TypeError) as exc:
323
+ raise AMIParamError(f"Couldn't read float from '{value_str}'.\n") from exc
324
+ elif param_type == "Integer":
325
+ try:
326
+ self._value = int(float(value_str)) # Hack to accommodate: "1e5", for instance.
327
+ except (ValueError, TypeError) as exc:
328
+ raise AMIParamError(f"Couldn't read integer from '{value_str}'.\n") from exc
329
+ elif param_type == "Boolean":
330
+ if value_str == "True":
331
+ self._value = True
332
+ elif value_str == "False":
333
+ self._value = False
334
+ else:
335
+ raise AMIParamError(f"Couldn't read Boolean from '{value_str}'.\n")
336
+ else:
337
+ self._value = value_str.strip('"')
338
+ elif param_format == "Range":
339
+ if param_type not in ("Float", "Integer", "UI", "Tap"):
340
+ raise AMIParamError(f"Illegal type, '{param_type}', for use with Range.\n")
341
+ if len(vals) < 3:
342
+ raise AMIParamError(f"Insufficient number of values, {len(vals)}, provided for Range.\n")
343
+ if param_type in ("Float", "UI", "Tap"):
344
+ try:
345
+ temp_vals = list(map(float, vals[:3]))
346
+ except (ValueError, TypeError) as exc:
347
+ raise AMIParamError(f"Couldn't read floats from '{vals[:3]}'.\n") from exc
348
+ else:
349
+ try:
350
+ temp_vals = list(map(int, vals[:3]))
351
+ except (ValueError, TypeError) as exc:
352
+ raise AMIParamError(f"Couldn't read integers from '{vals[:3]}'.\n") from exc
353
+ self._value = temp_vals[0]
354
+ self._min = temp_vals[1]
355
+ self._max = temp_vals[2]
356
+ else: # param_format == 'List'
357
+ if param_type in ("Float", "UI"):
358
+ try:
359
+ temp_vals = list(map(float, vals))
360
+ except (ValueError, TypeError) as exc:
361
+ raise AMIParamError(f"Couldn't read floats from '{vals}'.\n") from exc
362
+ elif param_type in ("Integer", "Tap"):
363
+ try:
364
+ temp_vals = list(map(int, vals))
365
+ except (ValueError, TypeError) as exc:
366
+ raise AMIParamError(f"Couldn't read integers from '{vals}'.\n") from exc
367
+ else: # 'param_type' == 'String'
368
+ try:
369
+ temp_vals = list(map(str, vals))
370
+ temp_vals = [x.strip('"') for x in temp_vals]
371
+ except (ValueError, TypeError) as exc:
372
+ raise AMIParamError(f"Couldn't read strings from '{vals}'.\n") from exc
373
+ self._value = temp_vals
374
+
375
+ self._name = name