diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index dbec8e46e..e339c9501 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -43,7 +43,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -54,7 +54,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v2 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -68,4 +68,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 diff --git a/.gitignore b/.gitignore index d60b61d06..2a8bc5f90 100644 --- a/.gitignore +++ b/.gitignore @@ -112,6 +112,25 @@ examples/c-examples/ngspice-shared/sharedspice.h resources +rsync-vps.sh + +spice-examples/eg6.dat +spice-examples/gnucap.cir +spice-examples/mosfet-characterization.cir +spice-examples/ring-modulator.cir +spice-examples/xyce-error.cir +spice-examples/xyce-error.cir.FD.prn +spice-examples/xyce-raw.data +spice-examples/xyce-test.cir +spice-examples/xyce-test2.cir +spice-examples/xyce-test2.csv + +tools/run-tox +tools/upload-www + +trash/ +.idea/ + unit-test/test.py anaconda-recipe/conda-bld @@ -127,4 +146,10 @@ examples/c-examples/ngspice_cb/ng_shared_test_sl/ng_shared_test_sl.layout examples/c-examples/ngspice_cb/ng_shared_test_sl_v/ examples/c-examples/ngspice_cb/ng_shared_test_v/ -Spice64_dll/ \ No newline at end of file +Spice64_dll/ +unit-test/Spice/input.cir +unit-test/Spice/input.cir.ic +unit-test/Spice/output.raw +input.cir +input.cir.ic +output.raw diff --git a/.idea/PySpice.iml b/.idea/PySpice.iml new file mode 100644 index 000000000..df194f43d --- /dev/null +++ b/.idea/PySpice.iml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/dataSources.local.xml b/.idea/dataSources.local.xml new file mode 100644 index 000000000..d85b9629d --- /dev/null +++ b/.idea/dataSources.local.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 000000000..105ce2da2 --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 000000000..d9f89f9e4 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 000000000..42c5abcc5 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/other.xml b/.idea/other.xml new file mode 100644 index 000000000..a708ec781 --- /dev/null +++ b/.idea/other.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..94a25f7f4 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 000000000..4ae60f64e --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,864 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1585652882548 + + + 1621316590920 + + + 1621316929764 + + + 1621317272814 + + + 1621317474494 + + + 1621529024027 + + + 1621601315502 + + + 1621854642253 + + + 1621922186970 + + + 1621923696258 + + + 1622109826837 + + + 1622124559026 + + + 1622361004647 + + + 1622365436460 + + + 1622367433876 + + + 1622385838407 + + + 1622397395406 + + + 1622402434932 + + + 1622466526664 + + + 1622487755158 + + + 1622489707241 + + + 1622489751289 + + + 1622490875363 + + + 1622539484037 + + + 1622545223544 + + + 1622560988342 + + + 1622562746484 + + + 1622565898932 + + + 1622566122148 + + + 1622568968775 + + + 1622642980193 + + + 1622986018906 + + + 1623048518199 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + file://$PROJECT_DIR$/PySpice/Unit/Unit.py + 1335 + + + file://$PROJECT_DIR$/PySpice/Spice/Netlist.py + 451 + + + file://$PROJECT_DIR$/PySpice/Spice/BasicElement.py + 923 + + + file://$PROJECT_DIR$/unit-test/Unit/test_Units.py + 22 + + + file://$PROJECT_DIR$/PySpice/Spice/Library.py + 106 + + + file://$PROJECT_DIR$/examples/run-examples + 35 + + + file://$PROJECT_DIR$/setup.py + 24 + + + file://$PROJECT_DIR$/venv/lib/python3.10/site-packages/PySpice/Spice/Netlist.py + 382 + + + file://$PROJECT_DIR$/PySpice/Spice/RawFile.py + 242 + + + file://$PROJECT_DIR$/PySpice/Spice/BasicElement.py + 892 + + + file://$PROJECT_DIR$/PySpice/Spice/ElementParameter.py + 401 + + + file://$PROJECT_DIR$/PySpice/Spice/BasicElement.py + 178 + + + file://$PROJECT_DIR$/PySpice/Spice/Simulation.py + 672 + + + file://$PROJECT_DIR$/PySpice/Spice/EBNFSpiceParser.py + 1322 + + + file://$PROJECT_DIR$/PySpice/Spice/EBNFSpiceParser.py + 2036 + + + file://$PROJECT_DIR$/PySpice/Spice/EBNFSpiceParser.py + 1781 + + + file://$PROJECT_DIR$/unit-test-todo/test_netlist.py + 5 + + + file://$PROJECT_DIR$/unit-test/Spice/test_BasicElement.py + 59 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PySpice/Math/__init__.py b/PySpice/Math/__init__.py index 45367217f..945cc91d0 100644 --- a/PySpice/Math/__init__.py +++ b/PySpice/Math/__init__.py @@ -38,9 +38,9 @@ def even(x): #################################################################################################### def rms_to_amplitude(x): - """Return :math:`x \sqrt{2}`""" + """Return :math:`x \\sqrt{2}`""" return x * math.sqrt(2) def amplitude_to_rms(x): - """Return :math:`x / \sqrt{2}`""" + """Return :math:`x / \\sqrt{2}`""" return x / math.sqrt(2) diff --git a/PySpice/Probe/WaveForm.py b/PySpice/Probe/WaveForm.py index ca92d15f2..9b88baf16 100644 --- a/PySpice/Probe/WaveForm.py +++ b/PySpice/Probe/WaveForm.py @@ -31,7 +31,7 @@ import logging import os -# import numpy as np +import numpy as np #################################################################################################### @@ -39,7 +39,7 @@ #################################################################################################### -from PySpice.Unit.Unit import UnitValues +from ..Unit.Unit import UnitValues, UnitValue #################################################################################################### @@ -64,9 +64,11 @@ class WaveForm(UnitValues): ############################################## - @classmethod - def from_unit_values(cls, name, array, title=None, abscissa=None): - obj = cls( + @staticmethod + def from_unit_values(name, array, title=None, abscissa=None): + + shape = array.shape + obj = WaveForm( name, array.prefixed_unit, array.shape, @@ -82,7 +84,7 @@ def from_unit_values(cls, name, array, title=None, abscissa=None): @classmethod def from_array(cls, name, array, title=None, abscissa=None): # Fixme: ok ??? - obj = cls(name, None, array.shape, title=title, abscissa=abscissa) + obj = WaveForm(name, None, array.shape, title=title, abscissa=abscissa) obj[...] = array[...] return obj @@ -137,7 +139,10 @@ def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): result = super().__array_ufunc__(ufunc, method, *inputs, **kwargs) # self._logger.info("result\n{}".format(result)) if isinstance(result, UnitValues): - return self.from_unit_values(name='', array=result, title='', abscissa=self._abscissa) + if len(result.shape) == 0: + return UnitValue(result.prefixed_unit, result) + else: + return self.from_unit_values(name='', array=result, title='', abscissa=self._abscissa) else: return result # e.g. foo <= 0 @@ -250,10 +255,10 @@ def __init__(self, simulation, nodes=(), branches=(), elements=(), internal_para self._simulation = simulation # Fixme: to func? - self._nodes = {waveform.name:waveform for waveform in nodes} - self._branches = {waveform.name:waveform for waveform in branches} - self._elements = {waveform.name:waveform for waveform in elements} - self._internal_parameters = {waveform.name:waveform for waveform in internal_parameters} + self._nodes = {waveform.name.lower():waveform for waveform in nodes} + self._branches = {waveform.name.lower():waveform for waveform in branches} + self._elements = {waveform.name.lower():waveform for waveform in elements} + self._internal_parameters = {waveform.name.lower():waveform for waveform in internal_parameters} ############################################## @@ -297,10 +302,7 @@ def _get_item(self, name): ############################################## def __getitem__(self, name): - try: - return self._get_item(name) - except IndexError: - return self._get_item(name.lower()) + return self._get_item(name.lower()) ############################################## diff --git a/PySpice/Spice/BasicElement.py b/PySpice/Spice/BasicElement.py index f4f943a60..04a27fde4 100644 --- a/PySpice/Spice/BasicElement.py +++ b/PySpice/Spice/BasicElement.py @@ -100,8 +100,9 @@ import logging from ..Tools.StringTools import str_spice, join_list, join_dict -from ..Unit import U_m, U_s, U_A, U_V, U_Degree, U_Ω, U_F, U_H, U_Hz -from .Netlist import (Element, AnyPinElement, FixedPinElement, NPinElement, OptionalPin) +from ..Unit import U_m, U_s, U_a, U_v, U_c, U_Ω, U_F, U_h, U_Hz +from .Netlist import (Element, AnyPinElement, FixedPinElement, NPinElement, + OptionalPin, Pin, PinDefinition) from .ElementParameter import ( # KeyValueParameter, BoolKeyParameter, @@ -117,8 +118,7 @@ IntKeyParameter, ModelPositionalParameter, ) - -#################################################################################################### +from .Expressions import Expression _module_logger = logging.getLogger(__name__) @@ -159,12 +159,11 @@ class SubCircuitElement(NPinElement): ############################################## - def __init__(self, netlist, name, subcircuit_name, *nodes, **parameters): - - super().__init__(netlist, name, nodes, subcircuit_name) + def __init__(self, netlist, name, subcircuit_name, *nodes, **kwargs): # Fixme: match parameters to subcircuit - self.parameters = parameters + self.parameters = kwargs + self.parent = netlist # Fixme: investigate # for key, value in parameters.items(): @@ -173,6 +172,24 @@ def __init__(self, netlist, name, subcircuit_name, *nodes, **parameters): # self.optional_parameters[key] = parameter # setattr(self, key, parameter) + subcircuit_name = subcircuit_name.lower() + if netlist is None: + raise ValueError("Unexpected None value") + subcircuit = netlist._find_subcircuit(subcircuit_name) + + if subcircuit is None: + raise ValueError("Non existing subcircuit: {}".format(subcircuit_name)) + if len(nodes) != len(subcircuit.PINS): + raise ValueError("Incorrect number of nodes for subcircuit {}".format(subcircuit_name)) + + if not hasattr(self, '_pins'): + self._pins = [] + if len(nodes) > 0: + self._pins = [Pin(self, pin_definition, netlist.get_node(node, True)) + for pin_definition, node in zip(subcircuit.PINS, nodes)] + + super().__init__(netlist, name, subcircuit_name) + ############################################## def copy_to(self, netlist): @@ -188,7 +205,9 @@ def format_spice_parameters(self): spice_parameters = super().format_spice_parameters() if self.parameters: - spice_parameters += ' ' + join_dict(self.parameters) + parameters = {key: ('{%s}' % str_spice(value)) if isinstance(value, Expression) else str_spice(value) + for key, value in self.parameters.items()} + spice_parameters += ' params: ' + join_dict(parameters) return spice_parameters @@ -246,13 +265,17 @@ class Resistor(DipoleElement): ALIAS = 'R' PREFIX = 'R' - resistance = FloatPositionalParameter(position=0, key_parameter=False, unit=U_Ω) + resistance = FloatPositionalParameter(position=-1, key_parameter=False, unit=U_Ω) + model = ModelPositionalParameter(position=0, key_parameter=True) ac = FloatKeyParameter('ac', unit=U_Ω) multiplier = IntKeyParameter('m') scale = FloatKeyParameter('scale') - temperature = FloatKeyParameter('temp', unit=U_Degree) - device_temperature = FloatKeyParameter('dtemp', unit=U_Degree) + temperature = FloatKeyParameter('temp', unit=U_c) + device_temperature = FloatKeyParameter('dtemp', unit=U_c) noisy = BoolKeyParameter('noisy') + tc = FloatPairKeyParameter('tc') + tc1 = FloatPairKeyParameter('tc1') + tc2 = FloatKeyParameter('tc2') #################################################################################################### @@ -318,12 +341,12 @@ class SemiconductorResistor(DipoleElement): ALIAS = 'SemiconductorResistor' PREFIX = 'R' - resistance = FloatPositionalParameter(position=0, key_parameter=False, unit=U_Ω) - model = ModelPositionalParameter(position=1, key_parameter=True) + resistance = FloatPositionalParameter(position=-1, key_parameter=False, unit=U_Ω) + model = ModelPositionalParameter(position=0, key_parameter=True) length = FloatKeyParameter('l', unit=U_m) width = FloatKeyParameter('w', unit=U_m) - temperature = FloatKeyParameter('temp', unit=U_Degree) - device_temperature = FloatKeyParameter('dtemp', unit=U_Degree) + temperature = FloatKeyParameter('temp', unit=U_c) + device_temperature = FloatKeyParameter('dtemp', unit=U_c) multiplier = IntKeyParameter('m') ac = FloatKeyParameter('ac', unit=U_Ω) scale = FloatKeyParameter('scale') @@ -364,6 +387,7 @@ class BehavioralResistor(DipoleElement): PREFIX = 'R' resistance_expression = ExpressionPositionalParameter(position=0, key_parameter=False) + tc = FloatPairKeyParameter('tc') tc1 = FloatKeyParameter('tc1') tc2 = FloatKeyParameter('tc2') @@ -418,13 +442,16 @@ class Capacitor(DipoleElement): ALIAS = 'C' PREFIX = 'C' - capacitance = FloatPositionalParameter(position=0, key_parameter=False, unit=U_F) - model = ModelPositionalParameter(position=1, key_parameter=True) + capacitance = FloatPositionalParameter(position=-1, key_parameter=False, unit=U_F) + model = ModelPositionalParameter(position=0, key_parameter=True) multiplier = IntKeyParameter('m') scale = FloatKeyParameter('scale') - temperature = FloatKeyParameter('temp', unit=U_Degree) - device_temperature = FloatKeyParameter('dtemp', unit=U_Degree) + temperature = FloatKeyParameter('temp', unit=U_c) + device_temperature = FloatKeyParameter('dtemp', unit=U_c) initial_condition = FloatKeyParameter('ic') + tc = FloatPairKeyParameter('tc') + tc1 = FloatPairKeyParameter('tc1') + tc2 = FloatKeyParameter('tc2') #################################################################################################### @@ -487,14 +514,14 @@ class SemiconductorCapacitor(DipoleElement): ALIAS = 'SemiconductorCapacitor' PREFIX = 'C' - capacitance = FloatPositionalParameter(position=0, key_parameter=False, unit=U_F) - model = ModelPositionalParameter(position=1, key_parameter=True) + capacitance = FloatPositionalParameter(position=-1, key_parameter=False, unit=U_F) + model = ModelPositionalParameter(position=0, key_parameter=True) length = FloatKeyParameter('l', unit=U_m) width = FloatKeyParameter('w', unit=U_m) multiplier = IntKeyParameter('m') scale = FloatKeyParameter('scale') - temperature = FloatKeyParameter('temp', unit=U_Degree) - device_temperature = FloatKeyParameter('dtemp', unit=U_Degree) + temperature = FloatKeyParameter('temp', unit=U_c) + device_temperature = FloatKeyParameter('dtemp', unit=U_c) initial_condition = FloatKeyParameter('ic') #################################################################################################### @@ -530,6 +557,7 @@ class BehavioralCapacitor(DipoleElement): PREFIX = 'C' capacitance_expression = ExpressionPositionalParameter(position=0, key_parameter=False) + tc = FloatPairKeyParameter('tc') tc1 = FloatKeyParameter('tc1') tc2 = FloatKeyParameter('tc2') @@ -586,14 +614,17 @@ class Inductor(DipoleElement): ALIAS = 'L' PREFIX = 'L' - inductance = FloatPositionalParameter(position=0, key_parameter=False, unit=U_H) - model = ModelPositionalParameter(position=1, key_parameter=True) + inductance = FloatPositionalParameter(position=-1, key_parameter=False, unit=U_h) + model = ModelPositionalParameter(position=0, key_parameter=True) nt = FloatKeyParameter('nt') multiplier = IntKeyParameter('m') scale = FloatKeyParameter('scale') - temperature = FloatKeyParameter('temp', unit=U_Degree) - device_temperature = FloatKeyParameter('dtemp', unit=U_Degree) + temperature = FloatKeyParameter('temp', unit=U_c) + device_temperature = FloatKeyParameter('dtemp', unit=U_c) initial_condition = FloatKeyParameter('ic') + tc = FloatPairKeyParameter('tc') + tc1 = FloatPairKeyParameter('tc1') + tc2 = FloatKeyParameter('tc2') #################################################################################################### @@ -628,7 +659,8 @@ class BehavioralInductor(DipoleElement): PREFIX = 'L' inductance_expression = ExpressionPositionalParameter(position=0, key_parameter=False) - tc1 = FloatKeyParameter('tc1') + tc = FloatPairKeyParameter('tc') + tc1 = FloatPairKeyParameter('tc1') tc2 = FloatKeyParameter('tc2') #################################################################################################### @@ -658,6 +690,10 @@ class CoupledInductor(AnyPinElement): ALIAS = 'K' PREFIX = 'K' + # Adding the variable as it is not used by the coupling inductor + + _pins = tuple() + inductor1 = ElementNamePositionalParameter(position=0, key_parameter=False) inductor2 = ElementNamePositionalParameter(position=1, key_parameter=False) coupling_factor = FloatPositionalParameter(position=2, key_parameter=False) @@ -670,20 +706,7 @@ def __init__(self, name, *args, **kwargs): super().__init__(name, *args, **kwargs) - self._inductors = [] - for inductor in (self.inductor1, self.inductor2): - try: - self.netlist.element(inductor) - except KeyError: - try: - inductor = 'L' + inductor - self.netlist.element(inductor) - self._logger.info('Prefixed element {}'.format(inductor)) - except KeyError: - raise ValueError('Element with name {} not found'.format(inductor)) - # Fixme: str or Element instance ? - self._inductors.append(inductor) - self.inductor1, self.inductor2 = self._inductors + self._inductors = (self.inductor1, self.inductor2) #################################################################################################### @@ -784,7 +807,36 @@ class VoltageSource(DipoleElement): PREFIX = 'V' # Fixme: ngspice manual doesn't describe well the syntax - dc_value = FloatPositionalParameter(position=0, key_parameter=False, unit=U_V) + dc_value = FloatPositionalParameter(position=0, key_parameter=False, unit=U_v) + ac_magnitude = FloatPositionalParameter(position=1, key_parameter=False, unit=U_v) + ac_phase = ExpressionPositionalParameter(position=2, key_parameter=False) + transient = ExpressionPositionalParameter(position=3, key_parameter=False) + + def format_spice_parameters(self): + parameters = [] + if self.dc_value is not None: + dc_value = str_spice(self.dc_value) + if isinstance(self.dc_value, Expression): + dc_value = "{%s}" % dc_value + parameters.append('dc {}'.format(dc_value)) + if self.ac_magnitude is not None: + ac_magnitude = str_spice(self.ac_magnitude) + if isinstance(self.ac_magnitude, Expression): + ac_magnitude = "{%s}" % ac_magnitude + parameters.append('ac {}'.format(ac_magnitude)) + if self.ac_phase is not None: + ac_phase = str_spice(self.ac_phase) + if self.ac_magnitude is None: + parameters.append('ac 0') + if isinstance(self.ac_phase, Expression): + ac_phase = "{%s}" % ac_phase + parameters.append(ac_phase) + if self.transient is not None: + transient = str_spice(self.transient) + if isinstance(self.transient, Expression): + transient = "{%s}" % transient + parameters.append(transient) + return join_list(parameters) #################################################################################################### @@ -810,7 +862,36 @@ class CurrentSource(DipoleElement): PREFIX = 'I' # Fixme: ngspice manual doesn't describe well the syntax - dc_value = FloatPositionalParameter(position=0, key_parameter=False, unit=U_A) + dc_value = FloatPositionalParameter(position=0, key_parameter=False, unit=U_a) + ac_magnitude = FloatPositionalParameter(position=1, key_parameter=False, unit=U_a) + ac_phase = ExpressionPositionalParameter(position=2, key_parameter=False) + transient = ExpressionPositionalParameter(position=3, key_parameter=False) + + def format_spice_parameters(self): + parameters = [] + if self.dc_value is not None: + dc_value = str_spice(self.dc_value) + if isinstance(self.dc_value, Expression): + dc_value = "{%s}" % dc_value + parameters.append('dc {}'.format(dc_value)) + if self.ac_magnitude is not None: + ac_magnitude = str_spice(self.ac_magnitude) + if isinstance(self.ac_magnitude, Expression): + ac_magnitude = "{%s}" % ac_magnitude + parameters.append('ac {}'.format(ac_magnitude)) + if self.ac_phase is not None: + ac_phase = str_spice(self.ac_phase) + if self.ac_magnitude is None: + parameters.append('ac 0') + if isinstance(self.ac_phase, Expression): + ac_phase = "{%s}" % ac_phase + parameters.append(ac_phase) + if self.transient is not None: + transient = str_spice(self.transient) + if isinstance(self.transient, Expression): + transient = "{%s}" % transient + parameters.append(transient) + return join_list(parameters) #################################################################################################### @@ -983,10 +1064,76 @@ class BehavioralSource(DipoleElement): current_expression = ExpressionKeyParameter('i') voltage_expression = ExpressionKeyParameter('v') + tc = FloatPairKeyParameter('tc') tc1 = FloatKeyParameter('tc1') tc2 = FloatKeyParameter('tc2') - temperature = FloatKeyParameter('temp', unit=U_Degree) - device_temperature = FloatKeyParameter('dtemp', unit=U_Degree) + temperature = FloatKeyParameter('temp', unit=U_c) + device_temperature = FloatKeyParameter('dtemp', unit=U_c) + smoothbsrc = IntKeyParameter('smoothbsrc') + + ############################################## + + def __str__(self): + self._logger = _module_logger.getChild('BehaviouralSource') + + from .Expressions import Symbol + spice_element = self.format_node_names() + # Fixme: expression + temp_expression = None + if self.netlist.spice_sim == 'xyce': + temp = 'temp' + if self.temperature is not None: + temp = self.temperature + elif self.device_temperature is not None: + temp = self.device_temperature + if self.tc is not None: + temp_expression = (1+Symbol(self.tc[0]) * (Symbol(temp) - Symbol(27)) + + Symbol(self.tc[1]) * (Symbol(temp) - Symbol(27))**2) + else: + if self.tc1 is not None: + temp_expression = (1 + Symbol(self.tc1) * (Symbol(temp) - Symbol(27))) + if self.tc2 is not None: + temp_expression = (1 + Symbol(self.tc2) * (Symbol(temp) - Symbol(27))**2) + expression = '' + if self.current_expression is not None: + if temp_expression is not None: + if isinstance(self.current_expression, Expression): + expression = ' i={%s}' % (self.current_expression * temp_expression) + else: + self._logger.warning('Unable to include associated temperature behaviour') + else: + if isinstance(self.current_expression, Expression) or type(self.current_expression) in (int, float): + expression = ' i={%s}' % self.current_expression + else: + expression = ' i=%s' % self.current_expression + elif self.voltage_expression is not None: + if temp_expression is not None: + if isinstance(self.voltage_expression, Expression) or type(self.voltage_expression) in (int, float): + expression = ' v={%s}' % (self.voltage_expression * temp_expression) + else: + self._logger.warning('Unable to include associated temperature behaviour') + else: + if isinstance(self.voltage_expression, Expression): + expression = ' v={%s}' % self.voltage_expression + else: + expression = ' v=%s' % self.voltage_expression + spice_element += expression + if self.netlist.spice_sim != 'xyce': + if self.tc is not None: + spice_element += ' tc1=%f,%f' % self.tc + else: + if self.tc1 is not None: + spice_element += ' tc1=%f' % self.tc1 + if self.tc2 is not None: + spice_element += ' tc2=%f' % self.tc2 + if self.temperature is not None: + spice_element += ' temp=%f' % self.temperature + if self.device_temperature is not None: + spice_element += ' dtemp=%f' % self.device_temperature + else: + if self.smoothbsrc is not None: + spice_element += ' smoothbsrc=%s' % self.smoothbsrc + return spice_element #################################################################################################### @@ -1015,8 +1162,9 @@ class NonLinearVoltageSource(DipoleElement): ALIAS = 'NonLinearVoltageSource' PREFIX = 'E' - # Fixme: - VALID_KWARGS = ('expression', 'table') + value = ExpressionKeyParameter('value') + table = ExpressionKeyParameter('table') + smoothbsrc = ExpressionKeyParameter('smoothbsrc') ############################################## @@ -1136,8 +1284,8 @@ class Diode(FixedPinElement): pj = FloatKeyParameter('pj') off = FlagParameter('off') ic = FloatPairKeyParameter('ic') - temperature = FloatKeyParameter('temp', unit=U_Degree) - device_temperature = FloatKeyParameter('dtemp', unit=U_Degree) + temperature = FloatKeyParameter('temp', unit=U_c) + device_temperature = FloatKeyParameter('dtemp', unit=U_c) #################################################################################################### # @@ -1205,7 +1353,7 @@ class BipolarJunctionTransistor(FixedPinElement): ALIAS = 'Q' LONG_ALIAS = 'BJT' PREFIX = 'Q' - PINS = ('collector', 'base', 'emitter', OptionalPin('substrate')) + PINS = ('collector', 'base', 'emitter', OptionalPin('substrate'), OptionalPin('thermal')) model = ModelPositionalParameter(position=0, key_parameter=True) area = FloatKeyParameter('area') @@ -1214,8 +1362,20 @@ class BipolarJunctionTransistor(FixedPinElement): multiplier = IntKeyParameter('m') off = FlagParameter('off') ic = FloatPairKeyParameter('ic') - temperature = FloatKeyParameter('temp', unit=U_Degree) - device_temperature = FloatKeyParameter('dtemp', unit=U_Degree) + temperature = FloatKeyParameter('temp', unit=U_c) + device_temperature = FloatKeyParameter('dtemp', unit=U_c) + + def format_node_names(self): + fixed_pins = len(self.PINS) - self._number_of_optional_pins_ + nodes = [pin.node for pin in self._pins[:fixed_pins]] + for pin in self._pins[fixed_pins:]: + if str(pin.node).lower() != 'dt': + nodes.append('[{}]'.format(pin.node)) + else: + nodes.append(pin.node) + + """ Return the formatted list of nodes. """ + return join_list((self.name, join_list(nodes))) #################################################################################################### # @@ -1274,7 +1434,7 @@ class JunctionFieldEffectTransistor(JfetElement): multiplier = IntKeyParameter('m') off = FlagParameter('off') ic = FloatPairKeyParameter('ic') - temperature = FloatKeyParameter('temp', unit=U_Degree) + temperature = FloatKeyParameter('temp', unit=U_c) #################################################################################################### # @@ -1437,7 +1597,7 @@ class Mosfet(FixedPinElement): source_number_square = FloatKeyParameter('nrs') off = FlagParameter('off') ic = FloatTripletKeyParameter('ic') - temperature = FloatKeyParameter('temp', unit=U_Degree) + temperature = FloatKeyParameter('temp', unit=U_c) # only for Xyce nfin = IntKeyParameter('nfin') @@ -1509,12 +1669,13 @@ class LosslessTransmissionLine(TwoPortElement): def __init__(self, name, *args, **kwargs): - super().__init__(name, *args, **kwargs) - - if not (self.has_parameter('time_delay') or - (self.has_parameter('frequency') and self.has_parameter('normalized_length'))): + # check: ^ xor, & bitwise and + if not (('time_delay' in kwargs) ^ + (('frequency' in kwargs) & ('normalized_length' in kwargs))): raise NameError('Either TD or F, NL must be specified') + super().__init__(name, *args, **kwargs) + #################################################################################################### class LossyTransmission(TwoPortElement): @@ -1690,7 +1851,7 @@ def __init__(self, netlist, name, *nodes, **parameters): # Fixme: ok ??? - super().__init__(netlist, name, nodes, **parameters) + super().__init__(netlist, name, *nodes, **parameters) #################################################################################################### # diff --git a/PySpice/Spice/EBNFExpressionParser.py b/PySpice/Spice/EBNFExpressionParser.py new file mode 100644 index 000000000..b3f923dba --- /dev/null +++ b/PySpice/Spice/EBNFExpressionParser.py @@ -0,0 +1,470 @@ +import logging +import os +import csv + +from unicodedata import normalize +from PySpice.Unit.Unit import UnitValue, ZeroPower, PrefixedUnit, UnitMetaclass, UnitPrefixMetaclass +from PySpice.Unit.SiUnits import Tera, Giga, Mega, Kilo, Milli, Micro, Nano, Pico, Femto +from PySpice.Tools.StringTools import join_lines +from .Expressions import * + +from .ExpressionGrammar import ExpressionParser as parser +from .ExpressionModel import ExpressionModelBuilderSemantics +from tatsu import to_python_sourcecode, to_python_model, compile +from tatsu.model import NodeWalker + +_module_logger = logging.getLogger(__name__) + + +class ParseError(NameError): + pass + + +class Statement: + """ This class implements a statement, in fact a line in a Spice netlist. """ + + @staticmethod + def arg_to_python(x): + + if x: + if str(x)[0].isdigit(): + return str(x) + else: + return "'{}'".format(x) + else: + return '' + + @staticmethod + def args_to_python(*args): + + return [Statement.arg_to_python(x) for x in args] + + @staticmethod + def kwargs_to_python(self, **kwargs): + return Statement.join_args(*['{}={}'.format(key, self.value_to_python(value)) + for key, value in kwargs.items()]) + + @staticmethod + def join_args(self, *args): + return ', '.join(args) + + +class ExpressionModelWalker(NodeWalker): + + def __init__(self): + self._scales = (Tera(), Giga(), Mega(), Kilo(), Milli(), Micro(), Nano(), Pico(), Femto()) + self._suffix = dict([(normalize("NFKD", unit.prefix).lower(), PrefixedUnit(power=unit)) + for unit in self._scales] + + [(normalize("NFKD", unit.spice_prefix).lower(), PrefixedUnit(power=unit)) + for unit in self._scales + if unit.spice_prefix is not None] + ) + self._functions = {"abs": Abs, + "agauss": AGauss, + "acos": ACos, + "acosh": ACosh, + "arctan": ATan, + "asin": ASin, + "asinh": ASinh, + "atan": ATan, + "atan2": ATan2, + "atanh": ATanh, + "aunif": AUnif, + "ceil": Ceil, + "cos": Cos, + "cosh": Cosh, + "db": Db, + "ddt": Ddt, + "ddx": Ddx, + "exp": Exp, + "ln": Ln, + "log": Ln, + "log10": Log10, + "floor": Floor, + "gauss": Gauss, + "i": I, + "if": If, + "img": Img, + "int": Int, + "limit": Limit, + "m": M, + "max": Max, + "min": Min, + "nint": NInt, + "ph": Ph, + "pow": Pow, + "pwr": Pow, + "pwrs": Pwrs, + "r": Re, + "rand": Rand, + "re": Re, + "sdt": Sdt, + "sgn": Sgn, + "sign": Sign, + "sin": Sin, + "sinh": Sinh, + "sqrt": Sqrt, + "stp": Stp, + "tan": Tan, + "tanh": Tanh, + "unif": Unif, + "uramp": URamp, + "v": V + } + self._relational = { + "<": LT, + "<=": LE, + "==": EQ, + "!=": NE, + ">=": GE, + ">": GT + } + + def walk_SpiceExpression(self, node, data): + return self.walk(node.ast, data) + + def walk_GenericExpression(self, node, data): + if node.value is None: + return self.walk(node.braced, data) + else: + return self.walk(node.value, data) + + def walk_BracedExpression(self, node, data): + return self.walk(node.ast, data) + + def walk_Ternary(self, node, data): + t = self.walk(node.t, data) + x = self.walk(node.x, data) + y = self.walk(node.y, data) + return self._functions["if"](t, x, y) + + def walk_Conditional(self, node, data): + return self.walk(node.expr, data) + + def walk_And(self, node, data): + left = self.walk(node.left, data) + if node.right is None: + return left + else: + right = self.walk(node.right, data) + return And(left, right) + + def walk_Not(self, node, data): + operator = self.walk(node.operator, data) + if node.op is None: + return operator + else: + return Not(operator) + + def walk_Or(self, node, data): + left = self.walk(node.left, data) + if node.right is None: + return left + else: + right = self.walk(node.right, data) + return Or(left, right) + + def walk_Xor(self, node, data): + left = self.walk(node.left, data) + if node.right is None: + return left + else: + right = self.walk(node.right, data) + return Xor(left, right) + + def walk_Relational(self, node, data): + if node.factor is None: + left = self.walk(node.left, data) + right = self.walk(node.right, data) + return self._relational[node.op](left, right) + else: + return self.walk(node.factor, data) + + def walk_ConditionalFactor(self, node, data): + if node.boolean is None: + return self.walk(node.expr, data) + else: + return node.boolean.lower() == "true" + + def walk_Expression(self, node, data): + if node.term is None: + return self.walk(node.ternary, data) + else: + return self.walk(node.term, data) + + def walk_Functional(self, node, data): + return self.walk(node.ast, data) + + def walk_Functions(self, node, data): + l_func = node.func.lower() + function = self._functions[l_func] + if function.nargs == 0: + return function() + elif l_func == 'v': + nodes = self.walk(node.node, data) + if isinstance(nodes, list): + return function(*nodes) + else: + return function(nodes) + elif l_func == 'i': + device = self.walk(node.device, data) + return function(device) + elif function.nargs == 1: + x = self.walk(node.x, data) + return function(x) + elif l_func == 'limit': + x = self.walk(node.x, data) + y = self.walk(node.y, data) + z = self.walk(node.z, data) + return function(x, y, z) + elif l_func == 'atan2': + x = self.walk(node.x, data) + y = self.walk(node.y, data) + return function(y, x) + elif l_func in ('aunif', 'unif'): + mu = self.walk(node.mu, data) + alpha = self.walk(node.alpha, data) + return function(mu, alpha) + elif l_func == "ddx": + f = node.f + x = self.walk(node.x, data) + return function(Symbol(f), x) + elif function.nargs == 2: + x = self.walk(node.x, data) + y = self.walk(node.y, data) + return function(x, y) + elif l_func == "if": + t = self.walk(node.t, data) + x = self.walk(node.x, data) + y = self.walk(node.y, data) + return function(t, x, y) + elif l_func == "limit": + x = self.walk(node.x, data) + y = self.walk(node.y, data) + z = self.walk(node.z, data) + return function(x, y, z) + elif l_func in ('agauss', 'gauss'): + mu = self.walk(node.mu, data) + alpha = self.walk(node.alpha, data) + n = self.walk(node.n, data) + return function(mu, alpha, n) + else: + raise NotImplementedError("Function: {}".format(node.func)); + + def walk_Term(self, node, data): + return self.walk(node.ast, data) + + def walk_AddSub(self, node, data): + lhs = self.walk(node.left, data) + if node.right is not None: + rhs = self.walk(node.right, data) + if node.op == "+": + return Add(lhs, rhs) + else: + return Sub(lhs, rhs) + else: + return lhs + + def walk_ProdDivMod(self, node, data): + lhs = self.walk(node.left, data) + if node.right is not None: + rhs = self.walk(node.right, data) + if node.op == "*": + return Mul(lhs, rhs) + elif node.op == "/": + return Div(lhs, rhs) + else: + return Mod(lhs, rhs) + else: + return lhs + + def walk_Sign(self, node, data): + operator = self.walk(node.operator, data) + if node.op is not None: + if node.op == "-": + if isinstance(operator, (int, float)): + return -operator + else: + return Neg(operator) + else: + if not isinstance(operator, (int, float)): + return Pos(operator) + return operator + + def walk_Exponential(self, node, data): + lhs = self.walk(node.left, data) + if node.right is not None: + rhs = self.walk(node.right, data) + return Power(lhs, rhs) + else: + return lhs + + def walk_Factor(self, node, data): + return self.walk(node.ast, data) + + def walk_Variable(self, node, data): + if node.variable is None: + return self.walk(node.factor, data) + else: + return Symbol(node.variable) + + def walk_Value(self, node, data): + real = 0.0 + if node.real is not None: + real = self.walk(node.real, data) + imag = None + if node.imag is not None: + imag = self.walk(node.imag, data) + value = 0.0 + if imag is None: + value = real + else: + value = complex(float(real), float(imag)) + if node.unit is not None: + unit = self.walk(node.unit, data) + if type(unit) is not str: + if not isinstance(value, UnitValue): + value = PrefixedUnit.from_si_unit(unit.si_unit).new_value(value) + else: + value = PrefixedUnit.from_prefixed_unit(unit, value.power).new_value(value.value) + return value + + def walk_ImagValue(self, node, data): + return self.walk(node.value, data) + + def walk_RealValue(self, node, data): + return self.walk(node.value, data) + + def walk_NumberScale(self, node, data): + value = ExpressionModelWalker._to_number(self.walk(node.value, data)) + scale = node.scale + if scale is not None: + scale = normalize("NFKD", scale).lower() + value = PrefixedUnit(power=UnitPrefixMetaclass.get(scale)).new_value(value) + return value + + def walk_Float(self, node, data): + value = ExpressionModelWalker._to_number(node.ast) + return value + + def walk_Int(self, node, data): + value = int(node.ast) + return value + + def walk_Unit(self, node, data): + unit = UnitMetaclass.from_prefix(node.ast.lower()) + if unit is None: + unit = node.ast + return unit + + def walk_Hz(self, node, data): + return UnitMetaclass.from_prefix(node.ast.lower()) + + def walk_Comment(self, node, data): + # TODO implement comments on devices + return node.ast + + def walk_Separator(self, node, data): + if node.comment is not None: + return self.walk(node.comment, data) + + def walk_NetNode(self, node, data): + return node.node + + def walk_Filename(self, node, data): + return node.ast + + def walk_BinaryPattern(self, node, data): + return ''.join(node.pattern) + + def walk_closure(self, node, data): + return ''.join(node) + + def walk_list(self, node, data): + return [self.walk(e, data) for e in iter(node)] + + def walk_object(self, node, data): + raise ParseError("No walker defined for the node: {}".format(node)) + + @staticmethod + def _to_number(value): + if type(value) is tuple: + value = value[0] + try: + int_value = int(value) + float_value = float(value) + if int_value == float_value: + return int_value + else: + return float_value + except ValueError: + return float(value) + + +class ParsingData: + def __init__(self): + self._root = None + self._present = None + self._context = [] + + +class ExpressionParser: + """ This class parse a Spice netlist file and build a syntax tree. + + Public Attributes: + + :attr:`circuit` + + :attr:`models` + + :attr:`subcircuits` + + """ + + _logger = _module_logger.getChild('ExpressionParser') + + ############################################## + + _parser = parser(whitespace='', semantics=ExpressionModelBuilderSemantics()) + _walker = ExpressionModelWalker() + + def __init__(self): + pass + + @staticmethod + def parse(source=None): + # Fixme: empty source + + if source is not None: + raw_code = source + else: + raise ValueError("No path or source") + + try: + model = ExpressionParser._parser.parse(raw_code) + except Exception as e: + raise ParseError(str(e)) from e + + data = ParsingData() + expr = ExpressionParser._walker.walk(model, data) + return expr + + @staticmethod + def _regenerate(): + from PySpice.Spice import __file__ as spice_file + location = os.path.realpath( + os.path.join(os.getcwd(), os.path.dirname(spice_file))) + grammar_file = os.path.join(location, "expressiongrammar.ebnf") + with open(grammar_file, "r") as grammar_ifile: + grammar = grammar_ifile.read(); + with open(grammar_file, "w") as grammar_ofile: + model = compile(str(grammar)) + grammar_ofile.write(str(model)) + python_file = os.path.join(location, "ExpressionGrammar.py") + python_grammar = to_python_sourcecode(grammar) + with open(python_file, 'w') as grammar_ofile: + grammar_ofile.write(python_grammar) + python_model = to_python_model(grammar) + model_file = os.path.join(location, "ExpressionModel.py") + with open(model_file, 'w') as model_ofile: + model_ofile.write(python_model) diff --git a/PySpice/Spice/EBNFSpiceParser.py b/PySpice/Spice/EBNFSpiceParser.py new file mode 100644 index 000000000..d312e3a79 --- /dev/null +++ b/PySpice/Spice/EBNFSpiceParser.py @@ -0,0 +1,2016 @@ +import logging +import os +import csv +import sys + +from unicodedata import normalize +from collections import OrderedDict +from .EBNFExpressionParser import ExpressionModelWalker +from ..Unit.Unit import UnitValue, ZeroPower, PrefixedUnit +from ..Tools.StringTools import join_lines +from .Expressions import * +from .Netlist import (Circuit, + SubCircuit) +from .BasicElement import (BehavioralSource, + BipolarJunctionTransistor, + Capacitor, + CoupledInductor, + CurrentSource, + Diode, + Inductor, + JunctionFieldEffectTransistor, + Mosfet, + Resistor, + SubCircuitElement, + VoltageControlledSwitch, + VoltageSource) +from .HighLevelElement import (ExponentialCurrentSource, + ExponentialMixin, + ExponentialVoltageSource, + PatternCurrentSource, + PatternMixin, + PatternVoltageSource, + PieceWiseLinearCurrentSource, + PieceWiseLinearMixin, + PieceWiseLinearVoltageSource, + PulseCurrentSource, + PulseMixin, + PulseVoltageSource, + SinusoidalCurrentSource, + SinusoidalMixin, + SinusoidalVoltageSource, + SingleFrequencyFMCurrentSource, + SingleFrequencyFMMixin, + SingleFrequencyFMVoltageSource) + +from .SpiceGrammar import SpiceParser as parser +from .SpiceModel import SpiceModelBuilderSemantics +from tatsu import to_python_sourcecode, to_python_model, compile +from tatsu.model import NodeWalker + +_module_logger = logging.getLogger(__name__) + + +class ParseError(NameError): + pass + + +class Statement: + """ This class implements a statement, in fact a line in a Spice netlist. """ + + @staticmethod + def arg_to_python(x): + + if x: + if str(x)[0].isdigit(): + return str(x) + else: + return "'{}'".format(x) + else: + return '' + + @staticmethod + def args_to_python(*args): + + return [Statement.arg_to_python(x) for x in args] + + @staticmethod + def kwargs_to_python(self, **kwargs): + return Statement.join_args(*['{}={}'.format(key, self.value_to_python(value)) + for key, value in kwargs.items()]) + + @staticmethod + def join_args(self, *args): + return ', '.join(args) + + +class DataStatement(Statement): + """ This class implements a data definition. + + Spice syntax:: + + .data name, name, ..., value, value, value, value... + + """ + + ############################################## + + def __init__(self, table, **parameters): + self._table = table + self._parameters = parameters + + @property + def table(self): + """ Name of the model """ + return self._table + + ############################################## + + @property + def names(self): + """ Name of the model """ + return self._parameters.keys() + + ############################################## + + def __repr__(self): + return 'Data {}'.format(Statement.kwargs_to_python(**self._parameters)) + + ############################################## + + def to_python(self, netlist_name): + kwargs = "{{{}}}".format(", ".join(["{} = ({})".format(param, ", ".join(values)) + for param, values in self._parameters.items])) + return '{}.data({}, {})'.format(netlist_name, self._table, kwargs) + os.linesep + + ############################################## + + def build(self, circuit): + circuit.data(self._table, self._parameters) + + +class IncludeStatement(Statement): + """ This class implements a include definition. """ + + ############################################## + + def __init__(self, parent, filename): + self._include = filename + root, _ = os.path.split(parent.path) + file_name = os.path.abspath(os.path.join(root, + self._include.replace('"', ''))) + if not (os.path.exists(file_name) and os.path.isfile(file_name)): + raise ParseError("{}: File not found: {}".format(parent.path, file_name)) + try: + self._contents = SpiceParser.parse(path=file_name) + except Exception as e: + raise ParseError("{}: {:s}".format(parent.path, e)) + + ############################################## + + def __str__(self): + return self._include + + ############################################## + + def __repr__(self): + return 'Include {}'.format(self._include) + + ############################################## + + def to_python(self, netlist_name): + return '{}.include({})'.format(netlist_name, self._include) + os.linesep + + def contents(self): + return self._contents + + +class ModelStatement(Statement): + """ This class implements a model definition. + + Spice syntax:: + + .model mname type (pname1=pval1 pname2=pval2) + + """ + + def __eq__(self, other): + return isinstance(other, ModelStatement) and self._name == other._name + + def __hash__(self): + return hash(self._name) + + ############################################## + + def __init__(self, name, device, **parameters): + self._name = str(name).lower() + self._model_type = device + self._parameters = parameters + + ############################################## + + @property + def name(self): + """ Name of the model """ + return self._name + + ############################################## + + def __repr__(self): + return 'Model {} {} {}'.format(self._name, self._model_type, self._parameters) + + ############################################## + + def to_python(self, netlist_name): + args = self.values_to_python((self._name, self._model_type)) + kwargs = self.kwargs_to_python(self._parameters) + return '{}.model({})'.format(netlist_name, self.join_args(args + kwargs)) + os.linesep + + ############################################## + + def build(self, circuit): + return circuit.model(self._name, self._model_type, **self._parameters) + + +#################################################################################################### + +class ParamStatement(Statement): + """ This class implements a param definition. + + Spice syntax:: + + .param name=expr + + """ + + ############################################## + + def __init__(self, **parameters): + self._parameters = parameters + + ############################################## + + @property + def names(self): + """ Name of the model """ + return self._parameters.keys() + + ############################################## + + def __repr__(self): + return 'Param {}'.format(Statement.kwargs_to_python(**self._parameters)) + + ############################################## + + def to_python(self, netlist_name): + args = self.values_to_python((self._name, self._value)) + return '{}.param({})'.format(netlist_name, self.join_args(args)) + os.linesep + + ############################################## + + def build(self, circuit): + for key, value in self._parameters.items(): + circuit.parameter(key, value) + + +class ElementStatement(Statement): + """ This class implements an element definition. + + "{ expression }" are allowed in device line. + + """ + + _logger = _module_logger.getChild('Element') + + ############################################## + + def __init__(self, statement, name, *nodes, **params): + self._statement = statement + self._prefix = name[0] + self._name = name[1:] + self._nodes = nodes + self._params = params + + ############################################## + + @property + def name(self): + """ Name of the element """ + return self._name + + ############################################## + + def __repr__(self): + return 'Element {0._prefix} {0._name} {0._nodes} {0._params}'.format(self) + + ############################################## + + def translate_ground_node(self, ground): + + nodes = [] + for idx, node in enumerate(self._nodes): + if str(node) == str(ground): + self._node[idx] = 0 + + return nodes + + ############################################## + + def to_python(self, netlist_name, ground=0): + + args = self.translate_ground_node(ground) + args = self.values_to_python(args) + kwargs = self.kwargs_to_python(self._dict_parameters) + return '{}.{}({})'.format(netlist_name, + self._prefix, self.join_args(args + kwargs)) + os.linesep + + def build(self, circuit, ground=0): + return self._statement(circuit, self._name, *self._nodes, **self._params) + + +class LibraryStatement(Statement): + """ This class implements a library definition. + + Spice syntax:: + + .LIB entry + .ENDL [entry] + + """ + + ############################################## + + def __init__(self, entry): + self._entry = entry + + self._statements = [] + self._subcircuits = [] + self._models = [] + self._params = [] + + ############################################## + + @property + def entry(self): + """ Name of the sub-circuit. """ + return self._entry + + @property + def models(self): + """ Models of the sub-circuit. """ + return self._models + + @property + def params(self): + """ Params of the sub-circuit. """ + return self._params + + @property + def subcircuits(self): + """ Subcircuits of the sub-circuit. """ + return self._subcircuits + + ############################################## + + def __repr__(self): + text = 'LIB {}'.format(self._entry) + os.linesep + text += os.linesep.join([repr(model) for model in self._models]) + os.linesep + text += os.linesep.join([repr(subcircuit) for subcircuit in self._subcircuits]) + os.linesep + text += os.linesep.join([' ' + repr(statement) for statement in self._statements]) + return text + + ############################################## + + def __iter__(self): + """ Return an iterator on the statements. """ + return iter(self._models + self._subcircuits + self._statements) + + ############################################## + + def append(self, statement): + """ Append a statement to the statement's list. """ + self._statements.append(statement) + + def appendModel(self, statement): + + """ Append a model to the statement's list. """ + + self._models.append(statement) + + def appendParam(self, statement): + + """ Append a param to the statement's list. """ + + self._params.append(statement) + + def appendSubCircuit(self, statement): + + """ Append a model to the statement's list. """ + + self._subcircuits.append(statement) + + ############################################## + + def to_python(self, ground=0): + + lib_name = 'lib_' + self._entry + source_code = '' + source_code += '{} = Lib({})'.format(lib_name, self._entry) + os.linesep + source_code += SpiceParser.netlist_to_python(lib_name, self, ground) + return source_code + + +class LibCallStatement(Statement): + """ This class implements a library call statement. + + Spice syntax:: + + .lib library entry + + """ + + ############################################## + + def __init__(self, library, entry): + self._library = library + self._entry = entry + + ############################################## + + @property + def name(self): + """ Name of the library """ + return self._library + + ############################################## + + @property + def entry(self): + """ Entry in the library """ + return self._entry + + ############################################## + + def __repr__(self): + return 'Library {} {}'.format(self._library, self._entry) + + ############################################## + + def to_python(self, netlist_name): + args = self.values_to_python((self._name, self._model_type)) + kwargs = self.kwargs_to_python(self._parameters) + return '{}.include({}, {})'.format(netlist_name, self._library, self._entry) + os.linesep + + ############################################## + + def build(self, circuit, libraries): + library = libraries[self._entry] + for statement in library._params: + statement.build(circuit) + for statement in library._models: + statement.build(circuit) + for statement in library._subcircuits: + statement.build(circuit, parent=circuit) + return circuit + + +class SubCircuitStatement(Statement): + """ This class implements a sub-circuit definition. + + Spice syntax:: + + .SUBCKT name node1 ... param1=value1 ... + + """ + + ############################################## + def __eq__(self, other): + return isinstance(other, SubCircuitStatement) and self._name == other._name + + def __hash__(self): + return hash(self._name) + + def __init__(self, name, *nodes, **params): + + self._name = str(name).lower() + self._nodes = nodes + self._params = params + + self._statements = [] + self._subcircuits = OrderedDict() + self._models = OrderedDict() + self._required_subcircuits = OrderedDict() + self._required_models = set() + self._parameters = [] + self._parent = None + + ############################################## + + @property + def name(self): + """ Name of the sub-circuit. """ + return self._name + + @property + def nodes(self): + """ Nodes of the sub-circuit. """ + return self._nodes + + @property + def models(self): + """ Models of the sub-circuit. """ + return self._models + + @property + def params(self): + """ Params of the sub-circuit. """ + return self._params + + @property + def subcircuits(self): + """ Subcircuits of the sub-circuit. """ + return self._subcircuits + + ############################################## + + def __repr__(self): + if self._params: + text = 'SubCircuit {} {} Params: {}'.format(self._name, self._nodes, self._params) + os.linesep + else: + text = 'SubCircuit {} {}'.format(self._name, self._nodes) + os.linesep + text += os.linesep.join([repr(model) for model in self._models]) + os.linesep + text += os.linesep.join([repr(subcircuit) for subcircuit in self._subcircuits]) + os.linesep + text += os.linesep.join([' ' + repr(statement) for statement in self._statements]) + return text + + ############################################## + + def __iter__(self): + """ Return an iterator on the statements. """ + return iter(self._parameters + self._models + self._subcircuits + self._statements) + + ############################################## + + def append(self, statement): + """ Append a statement to the statement's list. """ + self._statements.append(statement) + if len(statement.name) > 0 and statement.name[0] in "xX": + self._required_subcircuits[statement.model] = None + + def appendModel(self, statement): + + """ Append a model to the models list. """ + + self._models[statement._name] = statement + + def appendParam(self, statement): + + """ Append a param to the parameters list. """ + + self._parameters.append(statement) + + def appendSubCircuit(self, statement): + + """ Append a subcircuit to the subcircuits list. """ + statement._parent = self + self._subcircuits[statement._name] = statement + + ############################################## + + def to_python(self, ground=0): + + subcircuit_name = 'subcircuit_' + self._name + args = self.values_to_python([subcircuit_name] + self._nodes) + source_code = '' + source_code += '{} = SubCircuit({})'.format(subcircuit_name, self.join_args(args)) + os.linesep + source_code += SpiceParser.netlist_to_python(subcircuit_name, self, ground) + return source_code + + ############################################## + + def _build(self, ground, netlist): + for statement in self._parameters: + statement.build(netlist) + for name, statement in self._models.items(): + statement.build(netlist) + # Check subcircuits + names = list(self._required_subcircuits.keys()) + for name in names: + req_subcircuit = self._required_subcircuits[name] + if req_subcircuit is None: + # Search for the subcircuit + valid_subcircuit = self._search_build_subcircuit(name, ground, netlist) + if valid_subcircuit is not None: + self._required_subcircuits[name] = id(valid_subcircuit) + else: + self._required_subcircuits[name] = None + for name in names: + if self._required_subcircuits[name] is None: + raise ValueError("Subcircuit not found: {}", name) + for statement in self._statements: + if isinstance(statement, ElementStatement): + statement.build(netlist, ground) + return netlist + + def build(self, ground=0, parent=None): + subcircuit = SubCircuit(str(self._name).lower(), *self._nodes, **self._params) + subcircuit.parent = parent + return self._build(ground, subcircuit) + + def _search_build_subcircuit(self, subcircuit_name, ground=0, netlist=None): + # If the subcircuit is in the netlist, return the subcircuit + subcircuit = None + if netlist is not None: + subcircuit = netlist._search_subcircuit(subcircuit_name) + if subcircuit is not None: + return subcircuit + # If it is not, search for it in subcircuits + if subcircuit_name in self._subcircuits: + subcircuit = self._subcircuits[subcircuit_name] + # Or in a possible library + elif hasattr(self, '_library'): + if subcircuit_name in self._library._subcircuits: + subcircuit = self._library._subcircuits[subcircuit_name] + self._subcircuits[subcircuit_name] = subcircuit + # If a netlist has been received and the subcircuit exists + if netlist is not None and subcircuit is not None: + # Build the subcircuit + result = self._subcircuits[subcircuit_name].build(ground, netlist) + # Add the subcircuit to the netlist + netlist.subcircuit(result) + # Return the subcircuit + return result + # If no subcircuit has been fount, try the parent + if self._parent is None or netlist.parent is None: + return None + else: + return self._parent._search_build_subcircuit(subcircuit_name, ground, netlist.parent) + + +class CircuitStatement(SubCircuitStatement): + """ This class implements a circuit definition. + + Spice syntax:: + + Title ... + + """ + + ############################################## + + def __init__(self, title, path): + super(CircuitStatement, self).__init__("") + + if path is not None: + self._path = str(path) + else: + self._path = os.getcwd() + + self._title = str(title) + + self._library_calls = [] + self._libraries = {} + self._data = {} + self._library = None + + ############################################## + + @property + def path(self): + """ Path of the circuit. """ + return self._path + + @property + def title(self): + """ Title of the circuit. """ + return self._title + + @property + def name(self): + """ Name of the circuit. """ + return self._title + + @property + def libraries(self): + """ Libraries. """ + return self._libraries + + @property + def models(self): + """ Models of the circuit. """ + return self._models + + @property + def subcircuits(self): + """ Subcircuits of the circuit. """ + return self._subcircuits + + @property + def parameters(self): + """ Parameters of the circuit. """ + return self._parameters + + ############################################## + + def __repr__(self): + + text = 'Circuit {}'.format(self._title) + os.linesep + text += os.linesep.join([repr(library) for library in self._libraries]) + os.linesep + text += os.linesep.join([repr(parameter) for parameter in self._parameters]) + os.linesep + text += os.linesep.join([repr(model) for model in self._models]) + os.linesep + text += os.linesep.join([repr(subcircuit) for subcircuit in self._subcircuits]) + os.linesep + text += os.linesep.join([' ' + repr(statement) for statement in self._statements]) + return text + + ############################################## + + def __iter__(self): + + """ Return an iterator on the statements. """ + + return iter(self._libraries, self._parameters + self._models + self._subcircuits + self._statements) + + ############################################## + + def appendData(self, statement): + + """ Append a model to the statement's list. """ + + self._data[statement.table] = statement + + def appendLibrary(self, statement): + + """ Append a library to the statement's list. """ + + self._libraries[statement.entry] = statement + + def appendLibraryCall(self, statement): + + """ Append a library to the statement's list. """ + + self._library_calls.append(statement) + + ############################################## + + def to_python(self, ground=0): + + circuit_title = self._title + source_code = '' + source_code += '{} = Circuit({})'.format(circuit_title) + os.linesep + source_code += SpiceParser.netlist_to_python(circuit_title, self, ground) + return source_code + + ############################################## + + def build(self, ground=0, library=None): + circuit = Circuit(self._title) + if library is not None: + if self._library is None: + self._library = library + else: + raise ValueError("Library already assigned.") + if self._library is not None: + circuit.include(self._library) + return self._build(ground, circuit) + + +#################################################################################################### + +class SpiceModelWalker(ExpressionModelWalker): + + def __init__(self): + self._functions = {"abs": Abs, + "agauss": AGauss, + "acos": ACos, + "acosh": ACosh, + "arctan": ATan, + "asin": ASin, + "asinh": ASinh, + "atan": ATan, + "atan2": ATan2, + "atanh": ATanh, + "aunif": AUnif, + "ceil": Ceil, + "cos": Cos, + "cosh": Cosh, + "db": Db, + "ddt": Ddt, + "ddx": Ddx, + "exp": Exp, + "ln": Ln, + "log": Ln, + "log10": Log10, + "floor": Floor, + "gauss": Gauss, + "i": I, + "if": If, + "img": Img, + "int": Int, + "limit": Limit, + "m": M, + "max": Max, + "min": Min, + "nint": NInt, + "ph": Ph, + "pow": Pow, + "pwr": Pow, + "pwrs": Pwrs, + "r": Re, + "rand": Rand, + "re": Re, + "sdt": Sdt, + "sgn": Sgn, + "sign": Sign, + "sin": Sin, + "sinh": Sinh, + "sqrt": Sqrt, + "stp": Stp, + "tan": Tan, + "tanh": Tanh, + "unif": Unif, + "uramp": URamp, + "v": V + } + self._relational = { + "<": LT, + "<=": LE, + "==": EQ, + "!=": NE, + ">=": GE, + ">": GT + } + + def walk_Circuit(self, node, data): + if data._root is None: + title = self.walk(node.title, data) + title = join_lines(title) + data._root = CircuitStatement( + title, + data._path + ) + data._present = data._root + else: + raise ValueError('Circuit already created: {}'.format(data._path)) + + self.walk(node.lines, data) + if len(data._context) != 0: + raise ParseError("Not closed hierarchy: {}".format(data._path)) + return data._root + + def walk_BJT(self, node, data): + device = self.walk(node.dev, data) + kwargs = {} + collector = self.walk(node.collector, data) + base = self.walk(node.base, data) + emitter = self.walk(node.emitter, data) + nodes = [ + collector, + base, + emitter + ] + if node.substrate is not None: + substrate = self.walk(node.substrate, data) + nodes.append(substrate) + if node.thermal is not None: + thermal = node.thermal + nodes.append(thermal) + if node.area is not None: + area = self.walk(node.area, data) + kwargs["area"] = SpiceModelWalker._to_number(area) + + model_name = self.walk(node.model, data) + kwargs["model"] = model_name + data._present._required_models.add(model_name.lower()) + + if node.parameters is not None: + parameters = self.walk(node.parameters, data) + kwargs.update(parameters) + + data._present.append( + ElementStatement( + BipolarJunctionTransistor, + device, + *nodes, + **kwargs + ) + ) + + def walk_SubstrateNode(self, node, data): + return node.text[1:-1] + + def walk_Capacitor(self, node, data): + device = self.walk(node.dev, data) + kwargs = {} + if node.model is not None: + model_name = self.walk(node.model, data) + kwargs['model'] = model_name + data._present._required_models.add(model_name.lower()) + value = None + if node.value is not None: + value = self.walk(node.value, data) + kwargs['capacitance'] = value + if node.parameters is not None: + parameters = self.walk(node.parameters, data) + kwargs.update(parameters) + if value is None: + if 'c' in kwargs: + value = kwargs.pop('c') + kwargs['capacitance'] = value + elif 'C' in kwargs: + value = kwargs.pop('C') + kwargs['capacitance'] = value + + + positive = self.walk(node.positive, data) + negative = self.walk(node.negative, data) + nodes = ( + positive, + negative + ) + data._present.append( + ElementStatement( + Capacitor, + device, + *nodes, + **kwargs + ) + ) + + def walk_CurrentControlledCurrentSource(self, node, data): + device = self.walk(node.dev, data) + if (node.controller is None and node.dev is None and + node.gain is None): + raise ValueError("Device {} not properly defined".format(node.dev)) + if node.controller is not None: + controller = self.walk(node.controller, data) + kwargs = {"I": controller} + else: + value = self.walk(node.gain, data) + kwargs = {"I": I(self.walk(node.device, data).lower())*value} + + positive = self.walk(node.positive, data) + negative = self.walk(node.negative, data) + nodes = ( + positive, + negative + ) + data._present.append( + ElementStatement( + BehavioralSource, + device, + *nodes, + **kwargs + ) + ) + + def walk_CurrentControlledVoltageSource(self, node, data): + device = self.walk(node.dev, data) + if (node.controller is None and node.dev is None and + node.gain is None): + raise ValueError("Device {} not properly defined".format(node.dev)) + if node.controller is not None: + controller = self.walk(node.controller, data) + kwargs = {"V": controller} + else: + value = self.walk(node.transresistance, data) + kwargs = {"V": I(self.walk(node.device, data).lower())*value} + + positive = self.walk(node.positive, data) + negative = self.walk(node.negative, data) + nodes = ( + positive, + negative + ) + data._present.append( + ElementStatement( + BehavioralSource, + device, + *nodes, + **kwargs + ) + ) + + def walk_CurrentSource(self, node, data): + device = self.walk(node.dev, data) + kwargs = {} + element = CurrentSource + if node.dc_value is not None: + kwargs['dc_value'] = self.walk(node.dc_value, data) + if node.ac_magnitude is not None: + kwargs['ac_magnitude'] = self.walk(node.ac_magnitude, data) + if node.ac_phase is not None: + kwargs['ac_phase'] = self.walk(node.ac_phase, data) + if node.transient is not None: + transient = self.walk(node.transient, data) + if transient[0] == ExponentialMixin: + element = ExponentialCurrentSource + elif transient[0] == PatternMixin: + element = PatternCurrentSource + elif transient[0] == PieceWiseLinearMixin: + element = PieceWiseLinearCurrentSource + elif transient[0] == PulseMixin: + element = PulseCurrentSource + elif transient[0] == SingleFrequencyFMMixin: + element = SingleFrequencyFMCurrentSource + elif transient[0] == SinusoidalMixin: + element = SinusoidalCurrentSource + else: + raise ParseError("Unknown transient: {}".format(transient[0])) + kwargs.update(transient[1]) + + positive = self.walk(node.positive, data) + negative = self.walk(node.negative, data) + nodes = ( + positive, + negative + ) + data._present.append( + ElementStatement( + element, + device, + *nodes, + **kwargs + ) + ) + + def walk_Diode(self, node, data): + device = self.walk(node.dev, data) + kwargs = {} + if node.model is None: + raise ValueError("The device {} has no model".format(node.dev)) + else: + model_name = self.walk(node.model, data) + kwargs['model'] = model_name + data._present._required_models.add(model_name.lower()) + if node.area is not None: + area = self.walk(node.area, data) + kwargs['area'] = area + + positive = self.walk(node.positive, data) + negative = self.walk(node.negative, data) + nodes = ( + positive, + negative + ) + data._present.append( + ElementStatement( + Diode, + device, + *nodes, + **kwargs + ) + ) + + def walk_Inductor(self, node, data): + device = self.walk(node.dev, data) + kwargs = {} + if node.model is not None: + model_name = self.walk(node.model, data) + kwargs['model'] = model_name + data._present._required_models.add(model_name.lower()) + value = None + if node.value is not None: + value = self.walk(node.value, data) + kwargs['inductance'] = value + if node.parameters is not None: + parameters = self.walk(node.parameters, data) + kwargs.update(parameters) + if value is None: + if 'l' in kwargs: + value = kwargs.pop('l') + kwargs['inductance'] = value + elif 'L' in kwargs: + value = kwargs.pop('L') + kwargs['inductance'] = value + + positive = self.walk(node.positive, data) + negative = self.walk(node.negative, data) + nodes = ( + positive, + negative + ) + data._present.append( + ElementStatement( + Inductor, + device, + *nodes, + **kwargs + ) + ) + + def walk_JFET(self, node, data): + device = self.walk(node.dev, data) + kwargs = {} + if node.model is None: + raise ValueError("The device {} has no model".format(node.dev)) + else: + model_name = self.walk(node.model, data) + kwargs["model"] = model_name + data._present._required_models.add(model_name.lower()) + if node.area is not None: + area = self.walk(node.area, data) + kwargs["area"] = area + if node.parameters is not None: + parameters = self.walk(node.parameters, data) + kwargs.update(parameters) + + drain = self.walk(node.drain, data) + gate = self.walk(node.gate, data) + source = self.walk(node.source, data) + nodes = [ + drain, + gate, + source + ] + data._present.append( + ElementStatement( + JunctionFieldEffectTransistor, + device, + *nodes, + **kwargs + ) + ) + + def walk_MOSFET(self, node, data): + device = self.walk(node.dev, data) + kwargs = {} + if node.model is None: + raise ValueError("The device {} has no model".format(node.dev)) + else: + model_name = self.walk(node.model, data) + kwargs["model"] = model_name + data._present._required_models.add(model_name.lower()) + if node.param is not None: + if isinstance(node.param, list): + # The separators are not taken into account + for parameter in node.param: + if isinstance(parameter, list): + kwargs[parameter[0]] = self.walk(parameter[2][::2], data) + else: + kwargs.update(self.walk(parameter, data)) + else: + kwargs.update(self.walk(node.param, data)) + drain = self.walk(node.drain, data) + gate = self.walk(node.gate, data) + source = self.walk(node.source, data) + bulk = self.walk(node.bulk, data) + nodes = [ + drain, + gate, + source, + bulk + ] + data._present.append( + ElementStatement( + Mosfet, + device, + *nodes, + **kwargs + ) + ) + + def walk_MutualInductor(self, node, data): + device = self.walk(node.dev, data) + kwargs = {} + if node.model is not None: + model_name = self.walk(node.model, data) + kwargs['model'] = model_name + data._present._required_models.add(model_name.lower()) + inductors = self.walk(node.inductor, data) + if len(inductors) != 2: + raise ParseError("Presently, only two inductors are allowed.") + inductor1 = inductors[0] + inductor2 = inductors[1] + coupling_factor = self.walk(node.value, data) + kwargs["inductor1"] = inductor1 + kwargs["inductor2"] = inductor2 + kwargs["coupling_factor"] = coupling_factor + + data._present.append( + ElementStatement( + CoupledInductor, + device, + **kwargs + ) + ) + + def walk_NonLinearDependentSource(self, node, data): + device = self.walk(node.dev, data) + positive = self.walk(node.positive, data) + negative = self.walk(node.negative, data) + nodes = ( + positive, + negative + ) + expr = self.walk(node.expr, data) + kwargs = {} + if node.parameters is not None: + parameters = self.walk(node.parameters, data) + kwargs.update(parameters) + if node.magnitude == "V": + kwargs["voltage_expression"] = expr + else: + kwargs["current_expression"] = expr + + data._present.append( + ElementStatement( + BehavioralSource, + device, + *nodes, + **kwargs + ) + ) + + def walk_Resistor(self, node, data): + device = self.walk(node.dev, data) + kwargs = {} + if node.model is not None: + model_name = self.walk(node.model, data) + kwargs['model'] = model_name + data._present._required_models.add(model_name.lower()) + value = None + if node.value is not None: + value = self.walk(node.value, data) + kwargs['resistance'] = value + if node.parameters is not None: + parameters = self.walk(node.parameters, data) + kwargs.update(parameters) + if value is None: + if 'r' in kwargs: + value = kwargs.pop('r') + kwargs['resistance'] = value + elif 'R' in kwargs: + value = kwargs.pop('R') + kwargs['resistance'] = value + + positive = self.walk(node.positive, data) + negative = self.walk(node.negative, data) + nodes = ( + positive, + negative + ) + data._present.append( + ElementStatement( + Resistor, + device, + *nodes, + **kwargs + ) + ) + + def walk_Subcircuit(self, node, data): + device = self.walk(node.dev, data) + node_node = self.walk(node.node, data) + if node.params is not None: + subcircuit_name = node_node[-2] + nodes = node_node[:-2] + else: + subcircuit_name = node_node[-1] + nodes = node_node[:-1] + kwargs = {} + if node.parameters is not None: + parameters = self.walk(node.parameters, data) + kwargs.update(parameters) + data._present._required_subcircuits[subcircuit_name.lower()] = None + data._present.append( + ElementStatement( + SubCircuitElement, + device, + subcircuit_name, + *nodes, + **kwargs + ) + ) + + def walk_Switch(self, node, data): + device = self.walk(node.dev, data) + kwargs = {} + if node.model is not None: + model_name = self.walk(node.model, data) + kwargs['model'] = model_name + data._present._required_models.add(model_name.lower()) + if node.initial_state is not None: + kwargs['initial_state'] = node.initial_state + + positive = self.walk(node.positive, data) + negative = self.walk(node.negative, data) + if node.control_p is not None: + if node.control_n is not None: + control_p = self.walk(node.control_p, data) + control_n = self.walk(node.control_n, data) + nodes = ( + positive, + negative, + control_p, + control_n + ) + else: + raise ValueError("Only one control node defined") + else: + if node.control_n is None: + nodes = ( + positive, + negative + ) + data._present.append( + ElementStatement( + VoltageControlledSwitch, + device, + *nodes, + **kwargs + ) + ) + + def walk_VoltageControlledCurrentSource(self, node, data): + device = self.walk(node.dev, data) + if node.controller is None and node.nodes is None: + raise ValueError("Device {} not properly defined".format(node.dev)) + if node.controller is not None: + controller = self.walk(node.controller, data) + kwargs = {"I": controller} + else: + value = self.walk(node.transconductance, data) + nodes = self.walk(node.nodes, data) + if len(nodes) != 2: + raise ValueError("Device {} not properly defined".format(node.dev)) + ctrl_p = nodes[0] + ctrl_n = nodes[1] + kwargs = {"I": V(ctrl_p, ctrl_n) * value} + + positive = self.walk(node.positive, data) + negative = self.walk(node.negative, data) + nodes = ( + positive, + negative + ) + data._present.append( + ElementStatement( + BehavioralSource, + device, + *nodes, + **kwargs + ) + ) + + def walk_VoltageControlledVoltageSource(self, node, data): + device = self.walk(node.dev, data) + if node.controller is not None: + controller = self.walk(node.controller, data) + kwargs = {"V": controller} + else: + value = self.walk(node.transconductance, data) + nodes = self.walk(node.nodes, data) + if len(nodes) != 2: + raise ValueError("Device {} not properly defined".format(node.dev)) + ctrl_p = nodes[0] + ctrl_n = nodes[1] + kwargs = {"V": V(ctrl_p, ctrl_n) * value} + + positive = self.walk(node.positive, data) + negative = self.walk(node.negative, data) + nodes = ( + positive, + negative + ) + data._present.append( + ElementStatement( + BehavioralSource, + device, + *nodes, + **kwargs + ) + ) + + def walk_VoltageSource(self, node, data): + device = self.walk(node.dev, data) + kwargs = {} + element = VoltageSource + if node.dc_value is not None: + kwargs['dc_value'] = self.walk(node.dc_value, data) + if node.ac_magnitude is not None: + kwargs['ac_magnitude'] = self.walk(node.ac_magnitude, data) + if node.ac_phase is not None: + kwargs['ac_phase'] = self.walk(node.ac_phase, data) + if node.transient is not None: + transient = self.walk(node.transient, data) + if transient[0] == ExponentialMixin: + element = ExponentialVoltageSource + elif transient[0] == PatternMixin: + element = PatternVoltageSource + elif transient[0] == PieceWiseLinearMixin: + element = PieceWiseLinearVoltageSource + elif transient[0] == PulseMixin: + element = PulseVoltageSource + elif transient[0] == SingleFrequencyFMMixin: + element = SingleFrequencyFMVoltageSource + elif transient[0] == SinusoidalMixin: + element = SinusoidalVoltageSource + else: + raise ParseError("Unknown transient: {}".format(transient[0])) + kwargs.update(transient[1]) + + positive = self.walk(node.positive, data) + negative = self.walk(node.negative, data) + nodes = ( + positive, + negative + ) + data._present.append( + ElementStatement( + element, + device, + *nodes, + **kwargs + ) + ) + + + def walk_ControlVoltagePoly(self, node, data): + controllers = self.walk(node.value, data) + positive = self.walk(node.positive, data) + negative = self.walk(node.negative, data) + if len(positive) < controllers or len(negative) < controllers: + raise ValueError( + "The number of control nodes is smaller than the expected controllers: {}".format(controllers)) + + ctrl_pos = positive[:controllers] + ctrl_neg = negative[:controllers] + + values = [] + if len(positive) > controllers: + if isinstance(positive, list): + values_pos = positive[controllers:] + else: + values_pos = [positive] + if isinstance(negative, list): + values_neg = negative[controllers:] + else: + values_neg = [negative] + values += [SpiceModelWalker._to_number(val) + for pair in zip(values_pos, values_neg) + for val in pair] + if node.coefficient: + coefficients = self.walk(node.coefficient, data) + if isinstance(coefficients, list): + values.extend(coefficients) + else: + values.append(coefficients) + controllers = [V(*nodes) + for nodes in zip(ctrl_pos, + ctrl_neg)] + return Poly(controllers, values) + + def walk_ControlCurrentPoly(self, node, data): + controllers = self.walk(node.value, data) + if len(node.device) < controllers: + raise ValueError( + "The number of control nodes is smaller than the expected controllers: {}".format(controllers)) + + ctrl_dev = [self.walk(dev, data) + for dev in node.device[:controllers]] + + values = [] + if controllers > len(node.device): + values = [SpiceModelWalker._to_number(self.walk(value, data)) + for value in node.device[controllers:]] + if node.coefficient: + coefficients = self.walk(node.coefficient, data) + if isinstance(coefficients, list): + values.extend(coefficients) + else: + values.append(coefficients) + controllers = [I(dev) for dev in ctrl_dev] + return Poly(controllers, values) + + def walk_ControlTable(self, node, data): + return Table(self.walk(node.expr, data), + list(zip(self.walk(node.input, data), + self.walk(node.output, data)))) + + def walk_ControlValue(self, node, data): + return self.walk(node.expression, data) + + def walk_TransientSpecification(self, node, data): + return self.walk(node.ast, data) + + def walk_TransientPulse(self, node, data): + parameters = dict([(key, value) + for value, key in zip(self.walk(node.ast, data), + ("initial_value", + "pulse_value", + "delay_time", + "rise_time", + "fall_time", + "pulse_width", + "period", + "phase"))]) + return PulseMixin, parameters + + def walk_PulseArguments(self, node, data): + v1 = self.walk(node.v1, data) + value = [] + if node.value is not None: + value = self.walk(node.value, data) + if isinstance(value, list): + return [v1] + value + else: + return [v1, value] + + def walk_TransientPWL(self, node, data): + data, parameters = self.walk(node.ast, data) + keys = list(parameters.keys()) + low_keys = [key.lower() for key in keys] + if 'r' in low_keys: + idx = low_keys.index('r') + key = keys[idx] + repeat_time = parameters.pop(key) + parameters['repeat_time'] = repeat_time + if 'td' in low_keys: + idx = low_keys.index('td') + key = keys[idx] + time_delay = parameters.pop(key) + parameters['time_delay'] = time_delay + if isinstance(data, list): + parameters.update({"values": data}) + else: + curdir = os.path.abspath(os.curdir) + datapath, filename = os.path.split(data) + if curdir.endswith(datapath): + curdir += os.sep + filename + else: + curdir = os.path.abspath(data) + + with open(curdir) as ifile: + ext = os.path.splitext(curdir)[1] + reader = csv.reader(ifile, delimiter=',' if ext.lower() == ".csv" else ' ') + data = [(SpiceModelWalker._to_number(t), + SpiceModelWalker._to_number(value)) + for t, value in reader] + parameters.update({"values": data}) + return PieceWiseLinearMixin, parameters + + def walk_PWLArguments(self, node, data): + t = self.walk(node.t, data) + value = self.walk(node.value, data) + parameters = {} + if node.parameters is not None: + parameters = self.walk(node.parameters, data) + return list(zip(t, value)), parameters + + def walk_PWLFileArguments(self, node, data): + filename = self.walk(node.filename, data) + parameters = {} + if node.parameters is not None: + parameters = self.walk(node.parameters, data) + return filename, parameters + + def walk_TransientSin(self, node, data): + parameters = dict([(key, value) + for value, key in zip(self.walk(node.ast, data), + ("offset", + "amplitude", + "frequency", + "delay", + "damping_factor"))]) + return SinusoidalMixin, parameters + + def walk_SinArguments(self, node, data): + v0 = self.walk(node.v0, data) + va = self.walk(node.va, data) + freq = self.walk(node.freq, data) + value = [] + if node.value is not None: + value = self.walk(node.value, data) + if isinstance(value, list): + return [v0, va, freq] + value + else: + return [v0, va, freq, value] + + def walk_TransientPat(self, node, data): + parameters = dict([(key, value) + for value, key in zip(self.walk(node.ast, data), + ("high_value", + "low_value", + "delay_time", + "rise_time", + "fall_time", + "bit_period", + "bit_pattern", + "repeat"))]) + return PatternMixin, parameters + + def walk_PatArguments(self, node, data): + vhi = self.walk(node.vhi, data) + vlo = self.walk(node.vlo, data) + td = self.walk(node.td, data) + tr = self.walk(node.tr, data) + tf = self.walk(node.tf, data) + tsample = self.walk(node.tsample, data) + data = self.walk(node.data, data) + repeat = False + if node.repeat is not None: + repeat = (node.repeat == '1') + return [vhi, vlo, td, tr, tf, tsample, data, repeat] + + def walk_TransientExp(self, node, data): + parameters = dict([(key, value) + for value, key in zip(self.walk(node.ast, data), + ("initial_amplitude", + "amplitude", + "rise_delay_time", + "rise_time_constant", + "delay_fall_time", + "fall_time_constant"))]) + return ExponentialMixin, parameters + + def walk_ExpArguments(self, node, data): + v1 = self.walk(node.v1, data) + v2 = self.walk(node.v2, data) + if node.value is not None: + value = self.walk(node.value, data) + if isinstance(value, list): + return [v1, v2] + value + else: + return [v1, v2, value] + + def walk_TransientSFFM(self, node, data): + parameters = dict([(key, value) + for value, key in zip(self.walk(node.ast, data), + ("offset", + "amplitude", + "carrier_frequency", + "modulation_index", + "signal_frequency"))]) + return SingleFrequencyFMMixin, parameters + + def walk_SFFMArguments(self, node, data): + v0 = self.walk(node.v0, data) + va = self.walk(node.va, data) + if node.value is not None: + value = self.walk(node.value, data) + if isinstance(value, list): + return [v0, va] + value + else: + return [v0, va, value] + + def walk_ACCmd(self, node, data): + return node.text + + def walk_DataCmd(self, node, data): + table = node.table + names = node.name + values = self.walk(node.value, data) + if len(values) % len(names) != 0: + raise ValueError("The number of elements per parameter do not match (line: {})".format(node.line)) + parameters = dict([(name, [value for value in values[idx::len(names)]]) + for idx, name in enumerate(names)]) + data._root.appendData( + DataStatement( + table, + **parameters + ) + ) + + def walk_DCCmd(self, node, data): + return node.text + + def walk_IncludeCmd(self, node, data): + filename = self.walk(node.filename, data) + include = IncludeStatement( + data._root, + filename + ) + # The include statement makes available all the parameters, models and + # subcircuits in the file. + for inc_parameters in include._contents._parameters: + for parameters in data._present._parameters: + for name in inc_parameters.names: + if name in parameters.names: + raise ValueError("Duplicated parameter name {} in include file: {}".format(name, filename)) + data._present._parameters.append(inc_parameters) + + for model in include._contents._models: + if model not in data._present._models: + data._present._models[model] = include._contents._models[model] + else: + raise ValueError("Duplicated model name {} in include file: {}".format(model.name, filename)) + for name, subcircuit in include._contents._subcircuits.items(): + if name not in data._present._subcircuits: + data._present._subcircuits[name] = subcircuit + else: + raise ValueError("Duplicated subcircuit name {} in include file: {}".format(subcircuit.name, filename)) + + def walk_ICCmd(self, node, data): + return node.text + + def walk_ModelCmd(self, node, data): + name = self.walk(node.name, data) + device = node.type + if node.parameters is not None: + parameters = self.walk(node.parameters, data) + else: + parameters = {} + + data._present.appendModel( + ModelStatement( + name, + device, + **parameters + ) + ) + + def walk_ModelName(self, node, data): + return node.name.lower() + + def walk_ParamCmd(self, node, data): + if node.parameters is not None: + parameters = self.walk(node.parameters, data) + else: + parameters = {} + + data._present.appendParam( + ParamStatement(**parameters) + ) + + def walk_LibCmd(self, node, data): + if node.block is not None: + self.walk(node.block, data) + else: + self.walk(node.call, data) + + def walk_LibBlock(self, node, data): + entries = node.entry + if len(entries) == 2: + if entries[0] != entries[1]: + raise NameError( + 'Begin and end library entries differ: {} != {}'.format(*entries)) + entries = entries[0] + library = LibraryStatement(entries) + data._context.append(data._present) + data._present = library + self.walk(node.lines, data) + tmp = data._context.pop() + tmp.appendLibrary(data._present) + data._present = tmp + + def walk_LibCall(self, node, data): + entries = node.entry + if len(entries) == 2: + if entries[0] != entries[1]: + raise NameError( + 'Begin and end library entries differ: {} != {}'.format(*entries)) + entries = entries[0] + filename = self.walk(node.filename, data) + data._present.appendLibraryCall( + LibCallStatement(filename, entries) + ) + + def walk_SimulatorCmd(self, node, data): + return node.simulator + + def walk_SubcktCmd(self, node, data): + name = self.walk(node.name, data) + if isinstance(name, list) and len(name) == 2: + if name[0] != name[1]: + raise NameError( + 'Begin and end library entries differ (file:{}, line:{}): {} != {}'.format(self._path, node.parseinfo.line, + *name)) + name = name[0] + nodes = self.walk(node.node, data) + parameters = None + if node.parameters is not None: + parameters = self.walk(node.parameters, data) + + if nodes is None: + if parameters is None: + subckt = SubCircuitStatement(name) + else: + subckt = SubCircuitStatement(name, **parameters) + else: + if parameters is None: + subckt = SubCircuitStatement(name, *nodes) + else: + subckt = SubCircuitStatement(name, *nodes, **parameters) + data._context.append(data._present) + data._present = subckt + self.walk(node.lines, data) + tmp = data._context.pop() + tmp.appendSubCircuit(data._present) + data._present = tmp + + def walk_TitleCmd(self, node, data): + if id(data._root) == id(data._present): + data._root._title = self.walk(node.title) + else: + raise SyntaxError(".Title command can only be used in the root circuit.") + return data._root + + def walk_Lines(self, node, data): + return self.walk(node.ast, data) + + def walk_CircuitLine(self, node, data): + return self.walk(node.ast, data) + + def walk_NetlistLines(self, node, data): + return self.walk(node.ast, data) + + def walk_NetlistLine(self, node, data): + return self.walk(node.ast, data) + + def walk_Parameters(self, node, data): + result = {} + # The separators are not taken into account + for parameter in self.walk(node.ast, data): + result.update(parameter) + return result + + def walk_Parameter(self, node, data): + value = self.walk(node.value, data) + return {node.name.lower(): value} + + def walk_ParenthesisNodes(self, node, data): + return self.walk(node.ast, data) + + def walk_CircuitNodes(self, node, data): + return self.walk_list(node.ast, data) + def walk_Comment(self, node, data): + # TODO implement comments on devices + return node.ast + + def walk_Separator(self, node, data): + if node.comment is not None: + return self.walk(node.comment, data) + + def walk_Device(self, node, data): + # Conversion of controlled devices to the B device names + if node.ast[0] in ("E", "F", "G", "H"): + return "B" + node.ast + else: + return node.ast + + def walk_Command(self, node, data): + return self.walk(node.ast, data) + + def walk_NetlistCmds(self, node, data): + return self.walk(node.ast, data) + + def walk_TableFile(self, node, data): + filename = self.walk(node.filename, data) + return TableFile(filename) + + + +class ParsingData: + def __init__(self, filename): + self._path = filename + self._root = None + self._present = None + self._context = [] + + +class SpiceParser: + """ This class parse a Spice netlist file and build a syntax tree. + + Public Attributes: + + :attr:`circuit` + + :attr:`models` + + :attr:`subcircuits` + + """ + + _logger = _module_logger.getChild('SpiceParser') + + ############################################## + + _parser = parser(whitespace='', semantics=SpiceModelBuilderSemantics()) + _walker = SpiceModelWalker() + + def __init__(self): + pass + + @staticmethod + def parse(path=None, source=None, library=None): + # Fixme: empty source + + if path is not None: + with open(str(path), 'rb') as f: + raw_code = f.read().decode('utf-8') + elif source is not None: + raw_code = source + else: + raise ValueError("No path or source") + + try: + model = SpiceParser._parser.parse(raw_code) + except Exception as e: + if path is not None: + raise ParseError("{}: ".format(path) + str(e)) from e + else: + raise ParseError(str(e)) from e + + if path is None: + path = os.getcwd() + data = ParsingData(path) + circuit = SpiceParser._walker.walk(model, data) + if library is not None: + circuit._library = library + + return circuit + + @staticmethod + def _regenerate(): + from PySpice.Spice import __file__ as spice_file + location = os.path.realpath( + os.path.join(os.getcwd(), os.path.dirname(spice_file))) + grammar_file = os.path.join(location, "spicegrammar.ebnf") + with open(grammar_file, "r") as grammar_ifile: + grammar = grammar_ifile.read() + with open(grammar_file, "w") as grammar_ofile: + model = compile(str(grammar)) + grammar_ofile.write(str(model)) + python_file = os.path.join(location, "SpiceGrammar.py") + python_grammar = to_python_sourcecode(grammar) + with open(python_file, 'w') as grammar_ofile: + grammar_ofile.write(python_grammar) + python_model = to_python_model(grammar) + model_file = os.path.join(location, "SpiceModel.py") + with open(model_file, 'w') as model_ofile: + model_ofile.write(python_model) + + @property + def circuit(self): + """ Circuit statements. """ + return self._circuit + + @property + def models(self): + """ Models of the sub-circuit. """ + return self._circuit.models + + @property + def subcircuits(self): + """ Subcircuits of the sub-circuit. """ + return self._circuit.subcircuits + + @property + def parameters(self): + """ Subcircuits of the sub-circuit. """ + return self._circuit.params + + ############################################## + + def is_only_subcircuit(self): + return bool(not self._circuit and self.subcircuits) + + ############################################## + + def is_only_model(self): + return bool(not self.circuit and not self.subcircuits and self.models) + + ############################################## + + @staticmethod + def _build_circuit(circuit, statements, ground): + + for statement in statements: + if isinstance(statement, IncludeStatement): + circuit.include(str(statement)) + + for statement in statements: + if isinstance(statement, ElementStatement): + statement.build(circuit, ground) + elif isinstance(statement, ModelStatement): + statement.build(circuit) + elif isinstance(statement, SubCircuitStatement): + subcircuit = statement.build(ground) # Fixme: ok ??? + circuit.subcircuit(subcircuit) + + ############################################## + + def build_circuit(self, ground=0): + + """Build a :class:`Circuit` instance. + + Use the *ground* parameter to specify the node which must be translated to 0 (SPICE ground node). + + """ + + # circuit = Circuit(str(self._title)) + circuit = self._circuit.build(str(ground)) + return circuit + + ############################################## + + @staticmethod + def netlist_to_python(netlist_name, statements, ground=0): + + source_code = '' + for statement in statements: + if isinstance(statement, ElementStatement): + source_code += statement.to_python(netlist_name, ground) + elif isinstance(statement, LibraryStatement): + source_code += statement.to_python(netlist_name) + elif isinstance(statement, ModelStatement): + source_code += statement.to_python(netlist_name) + elif isinstance(statement, SubCircuitStatement): + source_code += statement.to_python(netlist_name) + elif isinstance(statement, IncludeStatement): + source_code += statement.to_python(netlist_name) + return source_code + + ############################################## + + def to_python_code(self, ground=0): + + ground = str(ground) + + source_code = '' + + if self.circuit: + source_code += "circuit = Circuit('{}')".format(self._title) + os.linesep + source_code += self.netlist_to_python('circuit', self._statements, ground) + + return source_code diff --git a/PySpice/Spice/ElementParameter.py b/PySpice/Spice/ElementParameter.py index 19e70d578..7be06aa26 100644 --- a/PySpice/Spice/ElementParameter.py +++ b/PySpice/Spice/ElementParameter.py @@ -24,8 +24,10 @@ #################################################################################################### -from ..Unit import Unit +from ..Unit.Unit import UnitValue, PrefixedUnit from ..Tools.StringTools import str_spice +from .Expressions import Expression, Symbol +from .EBNFExpressionParser import ExpressionParser #################################################################################################### @@ -49,6 +51,7 @@ def __init__(self, default=None): self._default_value = default self._attribute_name = None + self._unit = None ############################################## @@ -76,7 +79,7 @@ def __get__(self, instance, owner=None): ############################################## def __set__(self, instance, value): - setattr(instance, '_' + self._attribute_name, value) + setattr(instance, '_' + self._attribute_name, self.validate(value)) ############################################## @@ -109,6 +112,27 @@ def to_str(self, instance): def __lt__(self, other): return self._attribute_name < other.attribute_name + def _validate_float(self, value): + if self._unit is None: + return float(value) + if isinstance(value, type(self._unit)): + return value + if type(value) is str: + value = ExpressionParser.parse(value) + if isinstance(value, Symbol): + if type(value.value) is str: + return value + value = value.value + if isinstance(value, Expression): + return value + if isinstance(value, UnitValue): + if value.prefixed_unit.is_unit_less: + unit = PrefixedUnit(self._unit.unit, value.prefixed_unit.power) + return unit.new_value(value.value) + elif value.prefixed_unit.unit == self._unit.unit: + return value + return self._unit.new_value(value) + #################################################################################################### class PositionalElementParameter(ParameterDescriptor): @@ -147,7 +171,14 @@ def key_parameter(self): ############################################## def to_str(self, instance): - return str_spice(self.__get__(instance)) + + if bool(self): + value = self.__get__(instance) + if isinstance(value, Expression): + return '{%s}' % value + return str_spice(value) + else: + return '' ############################################## @@ -174,7 +205,9 @@ class ExpressionPositionalParameter(PositionalElementParameter): ############################################## def validate(self, value): - return str(value) + if isinstance(value, Expression): + return value + return ExpressionParser.parse(value) #################################################################################################### @@ -192,11 +225,7 @@ def __init__(self, position, unit=None, **kwargs): ############################################## def validate(self, value): - - if isinstance(value, Unit): - return value - else: - return Unit(value) + return self._validate_float(value) #################################################################################################### @@ -295,7 +324,10 @@ def str_value(self, instance): def to_str(self, instance): if bool(self): - return '{}={}'.format(self.spice_name, self.str_value(instance)) + value = self.str_value(instance) + if isinstance(value, Expression): + value = '{%s}' % value + return '{}={}'.format(self.spice_name, value) else: return '' @@ -328,7 +360,9 @@ class ExpressionKeyParameter(KeyValueParameter): ############################################## def validate(self, value): - return str(value) + if isinstance(value, Expression): + return value + return ExpressionParser.parse(value) #################################################################################################### @@ -346,7 +380,7 @@ def __init__(self, spice_name, unit=None, **kwargs): ############################################## def validate(self, value): - return float(value) + return self._validate_float(value) #################################################################################################### @@ -359,7 +393,7 @@ class FloatPairKeyParameter(KeyValueParameter): def validate(self, pair): if len(pair) == 2: - return (float(pair[0]), float(pair[1])) + return (self._validate_float(pair[0]), self._validate_float(pair[1])) else: raise ValueError() @@ -379,7 +413,7 @@ class FloatTripletKeyParameter(FloatPairKeyParameter): def validate(self, uplet): if len(uplet) == 3: - return (float(uplet[0]), float(uplet[1]), float(uplet[2])) + return (self._validate_float(uplet[0]), self._validate_float(uplet[1]), self._validate_float(uplet[2])) else: raise ValueError() diff --git a/PySpice/Spice/ExpressionGrammar.py b/PySpice/Spice/ExpressionGrammar.py new file mode 100644 index 000000000..8c3fd3f73 --- /dev/null +++ b/PySpice/Spice/ExpressionGrammar.py @@ -0,0 +1,1760 @@ +#!/usr/bin/env python + +# CAVEAT UTILITOR +# +# This file was automatically generated by TatSu. +# +# https://pypi.python.org/pypi/tatsu/ +# +# Any changes you make to it will be overwritten the next time +# the file is generated. + +from __future__ import annotations + +import sys + +from tatsu.buffering import Buffer +from tatsu.parsing import Parser +from tatsu.parsing import tatsumasu +from tatsu.parsing import leftrec, nomemo, isname # noqa +from tatsu.infos import ParserConfig +from tatsu.util import re, generic_main # noqa + + +KEYWORDS = {} # type: ignore + + +class ExpressionBuffer(Buffer): + def __init__(self, text, /, config: ParserConfig = None, **settings): + config = ParserConfig.new( + config, + owner=self, + whitespace=None, + nameguard=None, + comments_re=None, + eol_comments_re=None, + ignorecase=True, + namechars='', + parseinfo=True, + ) + config = config.replace(**settings) + super().__init__(text, config=config) + + +class ExpressionParser(Parser): + def __init__(self, /, config: ParserConfig = None, **settings): + config = ParserConfig.new( + config, + owner=self, + whitespace=None, + nameguard=None, + comments_re=None, + eol_comments_re=None, + ignorecase=True, + namechars='', + parseinfo=True, + keywords=KEYWORDS, + start='start', + ) + config = config.replace(**settings) + super().__init__(config=config) + + @tatsumasu('SpiceExpression') + @nomemo + def _start_(self): # noqa + self._gen_expr_() + + @tatsumasu('GenericExpression') + @nomemo + def _gen_expr_(self): # noqa + with self._choice(): + with self._option(): + self._braced_expression_() + self.name_last_node('braced') + with self._option(): + self._value_() + self.name_last_node('value') + self._error( + 'expecting one of: ' + ' ' + ' ' + ) + + @tatsumasu('BracedExpression') + @nomemo + def _braced_expression_(self): # noqa + with self._choice(): + with self._option(): + self._lc_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('@') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rc_() + + self._define( + ['sep'], + [] + ) + with self._option(): + self._expression_() + self.name_last_node('@') + self._error( + 'expecting one of: ' + "'{' " + ) + + @tatsumasu('Expression') + @leftrec + def _expression_(self): # noqa + with self._choice(): + with self._option(): + self._ternary_() + self.name_last_node('ternary') + with self._option(): + self._term_() + self.name_last_node('term') + self._error( + 'expecting one of: ' + ' ' + ' ' + ) + + @tatsumasu('Ternary') + @nomemo + def _ternary_(self): # noqa + self._conditional_expression_() + self.name_last_node('t') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._token('?') + self.name_last_node('op') + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('x') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._token(':') + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('y') + + self._define( + ['op', 'sep', 't', 'x', 'y'], + [] + ) + + @tatsumasu('Conditional') + @nomemo + def _conditional_expression_(self): # noqa + self._boolean_or_() + self.name_last_node('expr') + + @tatsumasu('Or') + @nomemo + def _boolean_or_(self): # noqa + self._boolean_xor_() + self.name_last_node('left') + with self._optional(): + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._token('|') + self.name_last_node('op') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._boolean_or_() + self.name_last_node('right') + + self._define( + ['op', 'right', 'sep'], + [] + ) + + self._define( + ['left', 'op', 'right', 'sep'], + [] + ) + + @tatsumasu('Xor') + @nomemo + def _boolean_xor_(self): # noqa + self._boolean_and_() + self.name_last_node('left') + with self._optional(): + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._token('^') + self.name_last_node('op') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._boolean_xor_() + self.name_last_node('right') + + self._define( + ['op', 'right', 'sep'], + [] + ) + + self._define( + ['left', 'op', 'right', 'sep'], + [] + ) + + @tatsumasu('And') + @nomemo + def _boolean_and_(self): # noqa + self._boolean_not_() + self.name_last_node('left') + with self._optional(): + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._token('&') + self.name_last_node('op') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._boolean_and_() + self.name_last_node('right') + + self._define( + ['op', 'right', 'sep'], + [] + ) + + self._define( + ['left', 'op', 'right', 'sep'], + [] + ) + + @tatsumasu('Not') + @nomemo + def _boolean_not_(self): # noqa + with self._optional(): + self._token('~') + self.name_last_node('op') + self._relational_() + self.name_last_node('operator') + + self._define( + ['op', 'operator'], + [] + ) + + @tatsumasu('Relational') + @nomemo + def _relational_(self): # noqa + with self._choice(): + with self._option(): + self._expression_() + self.name_last_node('left') + with self._optional(): + self._sep_() + self.name_last_node('sep') + with self._group(): + with self._choice(): + with self._option(): + self._token('==') + with self._option(): + self._token('!=') + with self._option(): + self._token('>=') + with self._option(): + self._token('<=') + with self._option(): + self._token('>') + with self._option(): + self._token('<') + self._error( + 'expecting one of: ' + "'!=' '<' '<=' '==' '>' '>='" + ) + self.name_last_node('op') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('right') + + self._define( + ['left', 'op', 'right', 'sep'], + [] + ) + with self._option(): + self._conditional_factor_() + self.name_last_node('factor') + self._error( + 'expecting one of: ' + ' ' + '' + ' ' + ' ' + ) + + @tatsumasu('ConditionalFactor') + def _conditional_factor_(self): # noqa + with self._choice(): + with self._option(): + self._lp_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._conditional_expression_() + self.name_last_node('expr') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['expr', 'sep'], + [] + ) + with self._option(): + self._boolean_() + self.name_last_node('boolean') + self._error( + 'expecting one of: ' + "'(' 'FALSE' 'TRUE' " + ) + + @tatsumasu('Term') + def _term_(self): # noqa + self._add_sub_() + self.name_last_node('@') + + @tatsumasu('AddSub') + def _add_sub_(self): # noqa + self._prod_() + self.name_last_node('left') + with self._optional(): + with self._optional(): + self._sep_() + self.name_last_node('sep') + with self._group(): + with self._choice(): + with self._option(): + self._token('+') + with self._option(): + self._token('-') + self._error( + 'expecting one of: ' + "'+' '-'" + ) + self.name_last_node('op') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._add_sub_() + self.name_last_node('right') + + self._define( + ['op', 'right', 'sep'], + [] + ) + + self._define( + ['left', 'op', 'right', 'sep'], + [] + ) + + @tatsumasu('ProdDivMod') + def _prod_(self): # noqa + self._unary_() + self.name_last_node('left') + with self._optional(): + with self._optional(): + self._sep_() + self.name_last_node('sep') + with self._group(): + with self._choice(): + with self._option(): + self._token('*') + with self._option(): + self._token('/') + with self._option(): + self._token('%') + self._error( + 'expecting one of: ' + "'%' '*' '/'" + ) + self.name_last_node('op') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._prod_() + self.name_last_node('right') + + self._define( + ['op', 'right', 'sep'], + [] + ) + + self._define( + ['left', 'op', 'right', 'sep'], + [] + ) + + @tatsumasu('Sign') + def _unary_(self): # noqa + with self._optional(): + with self._group(): + with self._choice(): + with self._option(): + self._token('+') + with self._option(): + self._token('-') + self._error( + 'expecting one of: ' + "'+' '-'" + ) + self.name_last_node('op') + self._exp_() + self.name_last_node('operator') + + self._define( + ['op', 'operator'], + [] + ) + + @tatsumasu('Exponential') + def _exp_(self): # noqa + self._functional_() + self.name_last_node('left') + with self._optional(): + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._token('**') + self.name_last_node('op') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._exp_() + self.name_last_node('right') + + self._define( + ['op', 'right', 'sep'], + [] + ) + + self._define( + ['left', 'op', 'right', 'sep'], + [] + ) + + @tatsumasu('Functional') + def _functional_(self): # noqa + with self._choice(): + with self._option(): + self._functions_() + self.name_last_node('@') + with self._option(): + self._variable_() + self.name_last_node('@') + self._error( + 'expecting one of: ' + ' ' + ' ' + ' ' + ' ' + ) + + @tatsumasu('Variable') + def _variable_(self): # noqa + with self._choice(): + with self._option(): + self._lc_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._var_id_() + self.name_last_node('variable') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rc_() + + self._define( + ['sep', 'variable'], + [] + ) + with self._option(): + self._var_id_() + self.name_last_node('variable') + with self._option(): + self._factor_() + self.name_last_node('factor') + self._error( + 'expecting one of: ' + "'{' " + '[a-zA-Z] [a-zA-Z_`@#\\$][a-zA-Z0-' + '9_:`@#\\.\\$]*[a-zA-Z0-9_`@#\\.\\$]' + ) + + @tatsumasu('Factor') + def _factor_(self): # noqa + with self._choice(): + with self._option(): + self._lp_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('@') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['sep'], + [] + ) + with self._option(): + self._value_() + self.name_last_node('@') + self._error( + 'expecting one of: ' + "'(' " + '' + ) + + @tatsumasu('Functions') + def _functions_(self): # noqa + with self._choice(): + with self._option(): + self._functions_1_() + with self._option(): + self._atan2_() + with self._option(): + self._ddx_() + with self._option(): + self._gauss_() + with self._option(): + self._if_func_() + with self._option(): + self._limit_() + with self._option(): + self._functions_2_() + with self._option(): + self._rand_() + with self._option(): + self._unif_() + with self._option(): + self._i_func_() + with self._option(): + self._v_func_() + self._error( + 'expecting one of: ' + "'Img' 'Ph' 'R' 'Re' 'abs' 'acos' 'acosh'" + "'agauss' 'arctan' 'asin' 'asinh' 'atan'" + "'atan2' 'atanh' 'aunif' 'ceil' 'cos'" + "'cosh' 'ddt' 'ddx' 'exp' 'floor' 'gauss'" + "'i' 'if' 'int' 'limit' 'ln' 'log'" + "'log10' 'm' 'max' 'min' 'nint' 'pow'" + "'pwr' 'pwrs' 'rand' 'sdt' 'sgn' 'sign'" + "'sin' 'sinh' 'sqrt' 'stp' 'tan' 'tanh'" + "'unif' 'uramp' 'v' " + ' ' + '' + ) + + @tatsumasu() + def _functions_1_(self): # noqa + with self._group(): + with self._choice(): + with self._option(): + self._token('abs') + with self._option(): + self._token('ceil') + with self._option(): + self._token('ddt') + with self._option(): + self._token('floor') + with self._option(): + self._token('int') + with self._option(): + self._token('m') + with self._option(): + self._token('nint') + with self._option(): + self._token('sdt') + with self._option(): + self._token('sgn') + with self._option(): + self._token('stp') + with self._option(): + self._token('sqrt') + with self._option(): + self._token('uramp') + with self._option(): + self._token('Ph') + with self._option(): + self._token('Re') + with self._option(): + self._token('R') + with self._option(): + self._token('Img') + with self._option(): + self._token('acosh') + with self._option(): + self._token('acos') + with self._option(): + self._token('asinh') + with self._option(): + self._token('asin') + with self._option(): + self._token('arctan') + with self._option(): + self._token('atanh') + with self._option(): + self._token('atan') + with self._option(): + self._token('cosh') + with self._option(): + self._token('cos') + with self._option(): + self._token('exp') + with self._option(): + self._token('ln') + with self._option(): + self._token('log') + with self._option(): + self._token('log10') + with self._option(): + self._token('sinh') + with self._option(): + self._token('sin') + with self._option(): + self._token('tanh') + with self._option(): + self._token('tan') + self._error( + 'expecting one of: ' + "'Img' 'Ph' 'R' 'Re' 'abs' 'acos' 'acosh'" + "'arctan' 'asin' 'asinh' 'atan' 'atanh'" + "'ceil' 'cos' 'cosh' 'ddt' 'exp' 'floor'" + "'int' 'ln' 'log' 'log10' 'm' 'nint'" + "'sdt' 'sgn' 'sin' 'sinh' 'sqrt' 'stp'" + "'tan' 'tanh' 'uramp'" + ) + self.name_last_node('func') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._lp_() + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('x') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['func', 'sep', 'x'], + [] + ) + + @tatsumasu() + def _atan2_(self): # noqa + self._token('atan2') + self.name_last_node('func') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._lp_() + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('y') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._comma_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('x') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['func', 'sep', 'x', 'y'], + [] + ) + + @tatsumasu() + def _ddx_(self): # noqa + self._token('ddx') + self.name_last_node('func') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._lp_() + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._id_() + self.name_last_node('f') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._comma_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('x') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['f', 'func', 'sep', 'x'], + [] + ) + + @tatsumasu() + def _gauss_(self): # noqa + with self._group(): + with self._choice(): + with self._option(): + self._token('agauss') + with self._option(): + self._token('gauss') + self._error( + 'expecting one of: ' + "'agauss' 'gauss'" + ) + self.name_last_node('func') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._lp_() + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('mu') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._comma_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('alpha') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._comma_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('n') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['alpha', 'func', 'mu', 'n', 'sep'], + [] + ) + + @tatsumasu() + def _i_func_(self): # noqa + self._token('i') + self.name_last_node('func') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._lp_() + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + with self._if(): + self._token('V') + self._dev_() + self.name_last_node('device') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['device', 'func', 'sep'], + [] + ) + + @tatsumasu() + def _if_func_(self): # noqa + self._token('if') + self.name_last_node('func') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._lp_() + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._conditional_expression_() + self.name_last_node('t') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._comma_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('x') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._comma_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('y') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['func', 'sep', 't', 'x', 'y'], + [] + ) + + @tatsumasu() + def _limit_(self): # noqa + self._token('limit') + self.name_last_node('func') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._lp_() + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('x') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._comma_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('y') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._comma_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('z') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['func', 'sep', 'x', 'y', 'z'], + [] + ) + + @tatsumasu() + def _functions_2_(self): # noqa + with self._group(): + with self._choice(): + with self._option(): + self._token('min') + with self._option(): + self._token('max') + with self._option(): + self._token('pwrs') + with self._option(): + self._token('pow') + with self._option(): + self._token('pwr') + with self._option(): + self._token('sign') + self._error( + 'expecting one of: ' + "'max' 'min' 'pow' 'pwr' 'pwrs' 'sign'" + ) + self.name_last_node('func') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._lp_() + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('x') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._comma_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('y') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['func', 'sep', 'x', 'y'], + [] + ) + + @tatsumasu() + def _rand_(self): # noqa + self._token('rand') + self.name_last_node('func') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._lp_() + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['func', 'sep'], + [] + ) + + @tatsumasu() + def _unif_(self): # noqa + with self._group(): + with self._choice(): + with self._option(): + self._token('aunif') + with self._option(): + self._token('unif') + self._error( + 'expecting one of: ' + "'aunif' 'unif'" + ) + self.name_last_node('func') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._lp_() + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('mu') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._comma_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('alpha') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['alpha', 'func', 'mu', 'sep'], + [] + ) + + @tatsumasu() + def _v_func_(self): # noqa + self._token('v') + self.name_last_node('func') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._lp_() + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('node') + with self._optional(): + self._sep_() + self.name_last_node('sep') + with self._optional(): + self._comma_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('node') + with self._optional(): + self._sep_() + self.name_last_node('sep') + + self._define( + ['node', 'sep'], + [] + ) + self._rp_() + + self._define( + ['func', 'node', 'sep'], + [] + ) + + @tatsumasu() + def _special_variables_(self): # noqa + with self._choice(): + with self._option(): + self._token('time') + with self._option(): + self._token('temper') + with self._option(): + self._token('temp') + with self._option(): + self._token('freq') + with self._option(): + self._token('vt') + with self._option(): + self._token('pi') + self._error( + 'expecting one of: ' + "'freq' 'pi' 'temp' 'temper' 'time' 'vt'" + ) + + @tatsumasu('Value') + def _value_(self): # noqa + with self._group(): + with self._choice(): + with self._option(): + with self._group(): + self._real_value_() + self.name_last_node('real') + self._token('+') + self._imag_value_() + self.name_last_node('imag') + + self._define( + ['imag', 'real'], + [] + ) + with self._option(): + self._imag_value_() + self.name_last_node('imag') + with self._option(): + self._real_value_() + self.name_last_node('real') + self._error( + 'expecting one of: ' + ' ' + ) + with self._optional(): + with self._choice(): + with self._option(): + self._hz_() + with self._option(): + self._unit_() + self._error( + 'expecting one of: ' + ' ' + ) + self.name_last_node('unit') + + self._define( + ['imag', 'real', 'unit'], + [] + ) + + @tatsumasu('ImagValue') + def _imag_value_(self): # noqa + self._number_scale_() + self.name_last_node('value') + self._token('J') + + self._define( + ['value'], + [] + ) + + @tatsumasu('RealValue') + def _real_value_(self): # noqa + self._number_scale_() + self.name_last_node('value') + + @tatsumasu() + def _freq_value_(self): # noqa + self._number_scale_() + self.name_last_node('value') + with self._optional(): + self._hz_() + self.name_last_node('unit') + + self._define( + ['unit', 'value'], + [] + ) + + @tatsumasu('NumberScale') + def _number_scale_(self): # noqa + with self._choice(): + with self._option(): + self._floating_point_() + self.name_last_node('value') + with self._group(): + with self._choice(): + with self._option(): + self._meg_() + with self._option(): + with self._optional(): + self._suffix_() + self._error( + 'expecting one of: ' + ' ' + ) + self.name_last_node('scale') + + self._define( + ['scale', 'value'], + [] + ) + with self._option(): + self._integer_() + self.name_last_node('value') + with self._group(): + with self._choice(): + with self._option(): + self._meg_() + with self._option(): + with self._optional(): + self._suffix_() + self._error( + 'expecting one of: ' + ' ' + ) + self.name_last_node('scale') + + self._define( + ['scale', 'value'], + [] + ) + self._error( + 'expecting one of: ' + '([\\+\\-]?(([0-9]+(\\.[0-9]*)?)|(\\.[0-' + '9]+))([eE][\\-\\+]?[0-9]{1,3})?)' + '([\\+\\-]?[0-9]+) ' + '' + ) + + @tatsumasu() + def _suffix_(self): # noqa + self._pattern('[tTgGkKmMxXuUnNpPfFµ]') + + @tatsumasu() + def _meg_(self): # noqa + self._pattern('[mM][eE][gG]') + + @tatsumasu('Unit') + def _unit_(self): # noqa + self._pattern('[a-zA-Z%]+') + + @tatsumasu('Hz') + def _hz_(self): # noqa + self._pattern('[Hh][Zz]') + + @tatsumasu() + def _lead_name_(self): # noqa + self._pattern('I[SDGBEC1-9]') + + @tatsumasu('Float') + def _floating_point_(self): # noqa + self._pattern('([\\+\\-]?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))([eE][\\-\\+]?[0-9]{1,3})?)') + + @tatsumasu('Int') + def _integer_(self): # noqa + self._pattern('([\\+\\-]?[0-9]+)') + + @tatsumasu() + def _digit_(self): # noqa + self._pattern('[0-9]') + + @tatsumasu() + def _boolean_(self): # noqa + with self._choice(): + with self._option(): + self._token('TRUE') + with self._option(): + self._token('FALSE') + self._error( + 'expecting one of: ' + "'FALSE' 'TRUE'" + ) + + @tatsumasu('BinaryPattern') + def _binary_pattern_(self): # noqa + self._pattern('[Bb]') + + def block1(): + self._binary_() + self._positive_closure(block1) + self.name_last_node('pattern') + + self._define( + ['pattern'], + [] + ) + + @tatsumasu() + def _binary_(self): # noqa + self._pattern('[01]') + + @tatsumasu('Device') + def _dev_(self): # noqa + self._pattern('[a-zA-Z\\$][a-zA-Z0-9_:!`@#\\.\\+\\-\\$]*') + + @tatsumasu('NetNode') + def _node_(self): # noqa + with self._group(): + with self._choice(): + with self._option(): + self._pattern('[a-zA-Z0-9_\\[\\$\\/\\+\\-][a-zA-Z0-9_:\\$\\-`~!@#%&_\\+|<>\\?\\.\\\\|\\^\\*\\/]*[a-zA-Z0-9_\\$\\-`~!@#%&_\\+|<>\\?\\.\\\\|\\^\\*\\]\\/]') + with self._option(): + self._pattern('[a-zA-Z0-9_]') + self._error( + 'expecting one of: ' + '[a-zA-Z0-9_\\[\\$\\/\\+\\-][a-zA-Z0-9_:\\$\\-' + '`~!@#%&_\\+|<>\\?\\.\\|\\^\\*\\/]*[a-zA-Z0-' + '9_\\$\\-`~!@#%&_\\+|<>\\?\\.\\|\\^\\*\\]\\/]' + '[a-zA-Z0-9_]' + ) + self.name_last_node('node') + with self._ifnot(): + with self._group(): + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._token('=') + + self._define( + ['sep'], + [] + ) + + self._define( + ['node', 'sep'], + [] + ) + + @tatsumasu() + def _id_(self): # noqa + with self._choice(): + with self._option(): + self._pattern('[a-zA-Z_`@#\\$][a-zA-Z0-9_:`@#\\.\\$\\/]*[a-zA-Z0-9_`@#\\.\\$]') + with self._option(): + self._pattern('[a-zA-Z_`@#\\$]') + self._error( + 'expecting one of: ' + '[a-zA-Z_`@#\\$] [a-zA-Z_`@#\\$][a-zA-Z0-' + '9_:`@#\\.\\$\\/]*[a-zA-Z0-9_`@#\\.\\$]' + ) + + @tatsumasu() + def _var_id_(self): # noqa + with self._choice(): + with self._option(): + self._pattern('[a-zA-Z_`@#\\$][a-zA-Z0-9_:`@#\\.\\$]*[a-zA-Z0-9_`@#\\.\\$]') + with self._option(): + self._pattern('[a-zA-Z]') + self._error( + 'expecting one of: ' + '[a-zA-Z] [a-zA-Z_`@#\\$][a-zA-Z0-' + '9_:`@#\\.\\$]*[a-zA-Z0-9_`@#\\.\\$]' + ) + + @tatsumasu() + def _end_sep_(self): # noqa + with self._choice(): + with self._option(): + self._cmd_net_sep_() + self.name_last_node('@') + + def block2(): + self._st_() + self._closure(block2) + with self._option(): + + def block3(): + self._st_() + self._positive_closure(block3) + self._error( + 'expecting one of: ' + ' ' + ' [ \\t]' + ) + + @tatsumasu() + def _sep_(self): # noqa + with self._choice(): + with self._option(): + + def block1(): + self._cmd_net_sep_() + self.name_last_node('@') + + def block3(): + self._st_() + self._closure(block3) + self._token('+') + + def block4(): + self._st_() + self._closure(block4) + self._positive_closure(block1) + with self._option(): + + def block5(): + self._st_() + self._positive_closure(block5) + self._error( + 'expecting one of: ' + ' ' + ' [ \\t]' + ) + + @tatsumasu('Separator') + def _cmd_net_sep_(self): # noqa + + def block0(): + self._st_() + self._closure(block0) + with self._optional(): + self._inline_comment_() + self.name_last_node('@') + self.name_last_node('comment') + self._newline_() + + def block3(): + + def block4(): + self._st_() + self._closure(block4) + with self._optional(): + with self._choice(): + with self._option(): + self._line_comment_() + self.name_last_node('@') + with self._option(): + self._inline_comment_() + self.name_last_node('@') + self._error( + 'expecting one of: ' + ' ' + ) + self.name_last_node('comment') + self._newline_() + + self._define( + ['comment'], + [] + ) + self._closure(block3) + + self._define( + ['comment'], + [] + ) + + @tatsumasu() + def _inline_comment_(self): # noqa + self._semicolon_() + + def block0(): + self._st_() + self._closure(block0) + self._text_() + self.name_last_node('@') + + @tatsumasu() + def _line_comment_(self): # noqa + self._asterisk_() + + def block0(): + self._st_() + self._closure(block0) + self._text_() + self.name_last_node('@') + + @tatsumasu('Comment') + def _text_(self): # noqa + self._pattern('[^\\r\\n]*') + + @tatsumasu() + def _asterisk_(self): # noqa + self._token('*') + + @tatsumasu() + def _question_mark_(self): # noqa + self._token('?') + + @tatsumasu() + def _colon_(self): # noqa + self._token(':') + + @tatsumasu() + def _semicolon_(self): # noqa + self._token(';') + + @tatsumasu() + def _comma_(self): # noqa + self._token(',') + + @tatsumasu() + def _dot_(self): # noqa + self._token('.') + + @tatsumasu() + def _dollar_(self): # noqa + self._token('\\$') + + @tatsumasu() + def _double_bar_(self): # noqa + self._token('//') + + @tatsumasu() + def _single_quote_(self): # noqa + self._token("'") + + @tatsumasu() + def _double_quote_(self): # noqa + self._token('"') + + @tatsumasu() + def _lc_(self): # noqa + self._token('{') + + @tatsumasu() + def _rc_(self): # noqa + self._token('}') + + @tatsumasu() + def _lp_(self): # noqa + self._token('(') + + @tatsumasu() + def _rp_(self): # noqa + self._token(')') + + @tatsumasu() + def _newline_(self): # noqa + self._pattern('[\\r\\n]') + + @tatsumasu() + def _st_(self): # noqa + self._pattern('[ \\t]') + + @tatsumasu() + def _ws_(self): # noqa + self._pattern('[^\\S\\r\\n]*') + + +class ExpressionSemantics: + def start(self, ast): # noqa + return ast + + def gen_expr(self, ast): # noqa + return ast + + def braced_expression(self, ast): # noqa + return ast + + def expression(self, ast): # noqa + return ast + + def ternary(self, ast): # noqa + return ast + + def conditional_expression(self, ast): # noqa + return ast + + def boolean_or(self, ast): # noqa + return ast + + def boolean_xor(self, ast): # noqa + return ast + + def boolean_and(self, ast): # noqa + return ast + + def boolean_not(self, ast): # noqa + return ast + + def relational(self, ast): # noqa + return ast + + def conditional_factor(self, ast): # noqa + return ast + + def term(self, ast): # noqa + return ast + + def add_sub(self, ast): # noqa + return ast + + def prod(self, ast): # noqa + return ast + + def unary(self, ast): # noqa + return ast + + def exp(self, ast): # noqa + return ast + + def functional(self, ast): # noqa + return ast + + def variable(self, ast): # noqa + return ast + + def factor(self, ast): # noqa + return ast + + def functions(self, ast): # noqa + return ast + + def functions_1(self, ast): # noqa + return ast + + def atan2(self, ast): # noqa + return ast + + def ddx(self, ast): # noqa + return ast + + def gauss(self, ast): # noqa + return ast + + def i_func(self, ast): # noqa + return ast + + def if_func(self, ast): # noqa + return ast + + def limit(self, ast): # noqa + return ast + + def functions_2(self, ast): # noqa + return ast + + def rand(self, ast): # noqa + return ast + + def unif(self, ast): # noqa + return ast + + def v_func(self, ast): # noqa + return ast + + def special_variables(self, ast): # noqa + return ast + + def value(self, ast): # noqa + return ast + + def imag_value(self, ast): # noqa + return ast + + def real_value(self, ast): # noqa + return ast + + def freq_value(self, ast): # noqa + return ast + + def number_scale(self, ast): # noqa + return ast + + def suffix(self, ast): # noqa + return ast + + def meg(self, ast): # noqa + return ast + + def unit(self, ast): # noqa + return ast + + def hz(self, ast): # noqa + return ast + + def lead_name(self, ast): # noqa + return ast + + def floating_point(self, ast): # noqa + return ast + + def integer(self, ast): # noqa + return ast + + def digit(self, ast): # noqa + return ast + + def boolean(self, ast): # noqa + return ast + + def binary_pattern(self, ast): # noqa + return ast + + def binary(self, ast): # noqa + return ast + + def dev(self, ast): # noqa + return ast + + def node(self, ast): # noqa + return ast + + def id(self, ast): # noqa + return ast + + def var_id(self, ast): # noqa + return ast + + def end_sep(self, ast): # noqa + return ast + + def sep(self, ast): # noqa + return ast + + def cmd_net_sep(self, ast): # noqa + return ast + + def inline_comment(self, ast): # noqa + return ast + + def line_comment(self, ast): # noqa + return ast + + def text(self, ast): # noqa + return ast + + def asterisk(self, ast): # noqa + return ast + + def question_mark(self, ast): # noqa + return ast + + def colon(self, ast): # noqa + return ast + + def semicolon(self, ast): # noqa + return ast + + def comma(self, ast): # noqa + return ast + + def dot(self, ast): # noqa + return ast + + def dollar(self, ast): # noqa + return ast + + def double_bar(self, ast): # noqa + return ast + + def single_quote(self, ast): # noqa + return ast + + def double_quote(self, ast): # noqa + return ast + + def lc(self, ast): # noqa + return ast + + def rc(self, ast): # noqa + return ast + + def lp(self, ast): # noqa + return ast + + def rp(self, ast): # noqa + return ast + + def newline(self, ast): # noqa + return ast + + def st(self, ast): # noqa + return ast + + def ws(self, ast): # noqa + return ast + + +def main(filename, **kwargs): + if not filename or filename == '-': + text = sys.stdin.read() + else: + with open(filename) as f: + text = f.read() + parser = ExpressionParser() + return parser.parse( + text, + filename=filename, + **kwargs + ) + + +if __name__ == '__main__': + import json + from tatsu.util import asjson + + ast = generic_main(main, ExpressionParser, name='Expression') + data = asjson(ast) + print(json.dumps(data, indent=2)) diff --git a/PySpice/Spice/ExpressionModel.py b/PySpice/Spice/ExpressionModel.py new file mode 100644 index 000000000..9afd94599 --- /dev/null +++ b/PySpice/Spice/ExpressionModel.py @@ -0,0 +1,240 @@ +#!/usr/bin/env python + +# CAVEAT UTILITOR +# +# This file was automatically generated by TatSu. +# +# https://pypi.python.org/pypi/tatsu/ +# +# Any changes you make to it will be overwritten the next time +# the file is generated. + +from __future__ import annotations + +from typing import Any +from dataclasses import dataclass + +from tatsu.objectmodel import Node +from tatsu.semantics import ModelBuilderSemantics + + +@dataclass(eq=False) +class ModelBase(Node): + pass + + +class ExpressionModelBuilderSemantics(ModelBuilderSemantics): + def __init__(self, context=None, types=None): + types = [ + t for t in globals().values() + if type(t) is type and issubclass(t, ModelBase) + ] + (types or []) + super().__init__(context=context, types=types) + + +@dataclass(eq=False) +class SpiceExpression(ModelBase): + pass + + +@dataclass(eq=False) +class GenericExpression(ModelBase): + braced: Any = None + value: Any = None + + +@dataclass(eq=False) +class BracedExpression(ModelBase): + sep: Any = None + + +@dataclass(eq=False) +class Expression(ModelBase): + term: Any = None + ternary: Any = None + + +@dataclass(eq=False) +class Ternary(ModelBase): + op: Any = None + sep: Any = None + t: Any = None + x: Any = None + y: Any = None + + +@dataclass(eq=False) +class Conditional(ModelBase): + expr: Any = None + + +@dataclass(eq=False) +class Or(ModelBase): + left: Any = None + op: Any = None + right: Any = None + sep: Any = None + + +@dataclass(eq=False) +class Xor(ModelBase): + left: Any = None + op: Any = None + right: Any = None + sep: Any = None + + +@dataclass(eq=False) +class And(ModelBase): + left: Any = None + op: Any = None + right: Any = None + sep: Any = None + + +@dataclass(eq=False) +class Not(ModelBase): + op: Any = None + operator: Any = None + + +@dataclass(eq=False) +class Relational(ModelBase): + factor: Any = None + left: Any = None + op: Any = None + right: Any = None + sep: Any = None + + +@dataclass(eq=False) +class ConditionalFactor(ModelBase): + boolean: Any = None + expr: Any = None + sep: Any = None + + +@dataclass(eq=False) +class Term(ModelBase): + pass + + +@dataclass(eq=False) +class AddSub(ModelBase): + left: Any = None + op: Any = None + right: Any = None + sep: Any = None + + +@dataclass(eq=False) +class ProdDivMod(ModelBase): + left: Any = None + op: Any = None + right: Any = None + sep: Any = None + + +@dataclass(eq=False) +class Sign(ModelBase): + op: Any = None + operator: Any = None + + +@dataclass(eq=False) +class Exponential(ModelBase): + left: Any = None + op: Any = None + right: Any = None + sep: Any = None + + +@dataclass(eq=False) +class Functional(ModelBase): + pass + + +@dataclass(eq=False) +class Variable(ModelBase): + factor: Any = None + sep: Any = None + variable: Any = None + + +@dataclass(eq=False) +class Factor(ModelBase): + sep: Any = None + + +@dataclass(eq=False) +class Functions(ModelBase): + pass + + +@dataclass(eq=False) +class Value(ModelBase): + imag: Any = None + real: Any = None + unit: Any = None + + +@dataclass(eq=False) +class ImagValue(ModelBase): + value: Any = None + + +@dataclass(eq=False) +class RealValue(ModelBase): + value: Any = None + + +@dataclass(eq=False) +class NumberScale(ModelBase): + scale: Any = None + value: Any = None + + +@dataclass(eq=False) +class Unit(ModelBase): + pass + + +@dataclass(eq=False) +class Hz(ModelBase): + pass + + +@dataclass(eq=False) +class Float(ModelBase): + pass + + +@dataclass(eq=False) +class Int(ModelBase): + pass + + +@dataclass(eq=False) +class BinaryPattern(ModelBase): + pattern: Any = None + + +@dataclass(eq=False) +class Device(ModelBase): + pass + + +@dataclass(eq=False) +class NetNode(ModelBase): + node: Any = None + sep: Any = None + + +@dataclass(eq=False) +class Separator(ModelBase): + comment: Any = None + + +@dataclass(eq=False) +class Comment(ModelBase): + pass diff --git a/PySpice/Spice/Expressions.py b/PySpice/Spice/Expressions.py new file mode 100644 index 000000000..f2b4b1dfd --- /dev/null +++ b/PySpice/Spice/Expressions.py @@ -0,0 +1,721 @@ +import operator + +import numpy as np +import operator as op +from ..Tools.StringTools import str_spice + +class Expression: + """Base class for all expressions. + """ + def __call__(self, **kwargs): + raise NotImplementedError("The call function is not implemented in class: {}".format(type(self))) + + def __str__(self): + raise NotImplementedError("The str function is not implemented in class: {}".format(type(self))) + + def __abs__(self, symbol): + return Abs(symbol) + + def __add__(self, other): + return Add(self, other) + + def __radd__(self, other): + return Add(other, self) + + def __sub__(self, other): + return Sub(self, other) + + def __rsub__(self, other): + return Sub(other, self) + + def __mul__(self, other): + return Mul(self, other) + + def __rmul__(self, other): + return Mul(other, self) + + def __truediv__(self, other): + return Div(self, other) + + def __rtruediv__(self, other): + return Div(other, self) + + def __mod__(self, other): + return Mod(self, other) + + def __rmod__(self, other): + return Mod(other, self) + + def __pow__(self, other): + return Power(self, other) + + def __rpow__(self, other): + return Power(other, self) + + def __neg__(self): + return Neg(self) + + def __pos__(self): + return Pos(self) + + def __lt__(self, other): + return LT(self, other) + + def __le__(self, other): + return LE(self, other) + + def __eq__(self, other): + return EQ(self, other) + + def __ne__(self, other): + return NE(self, other) + + def __ge__(self, other): + return GE(self, other) + + def __gt__(self, other): + return GT(self, other) + + +class Function(Expression): + """Base class for all functions. + + Args: + Expression (arguments): The arguments of the different functions. + + Raises: + ValueError: If the number of arguments is not one of the expected. + + Returns: + Expression or basic type: The result of the calculation of the expression + if all the arguments are basic types. + """ + nargs = 0 + + def __init__(self, func, *symbols): + self._func = func + self._symbols = symbols + if isinstance(self.__class__.nargs, tuple): + if len(symbols) not in self.__class__.nargs: + raise ValueError("The number of arguments is not correct: {} != {}".format( + len(self._symbols), + self.__class__.nargs + )) + else: + if len(symbols) != self.__class__.nargs: + raise ValueError("The number of arguments is not correct: {} != {}".format( + len(self._symbols), + self.__class__.nargs + )) + + def __str__(self): + arguments = ", ".join([str_spice(symbol) for symbol in self._symbols]) + return "{:s}({:s})".format(self.__class__.__name__.lower(), arguments) + + def __call__(self, **kwargs): + result = [symbol if isinstance(symbol, (bool, int, float, complex)) else symbol.subs(**kwargs) + for symbol in self._symbols] + expr = [True for element in result + if isinstance(element, Expression)] + if len(expr) > 0: + return self.__class__(*result) + else: + return self._func(*result) + + +class BinaryOperator(Expression): + def __init__(self, op, string, lhs, rhs): + self._lhs = lhs + self._op = op + self._rhs = rhs + self._string = string + + def __str__(self): + lhs = str_spice(self._lhs) + if isinstance(self._lhs, BinaryOperator): + lhs = "({:s})".format(lhs) + rhs = str_spice(self._rhs) + if isinstance(self._rhs, BinaryOperator): + rhs = "({:s})".format(rhs) + return "{:s} {:s} {:s}".format(lhs, str(self._string), rhs) + + def __call__(self, **kwargs): + lhs = self._lhs + rhs = self._rhs + if kwargs: + lhs = lhs.subs(**kwargs) + rhs = rhs.subs(**kwargs) + if isinstance(lhs, Expression) or isinstance(rhs, Expression): + return self.__class__(lhs, rhs) + else: + return self._op(lhs, rhs) + else: + try: + return self._op(lhs, rhs) + except: + return self.__class__(lhs, rhs) + +class UnaryOperator(Expression): + def __init__(self, op, operator, operand): + self._op = op + self._operator = operator + self._operand = operand + + def __str__(self): + operand = str_spice(self._operand) + if isinstance(self._operand, BinaryOperator): + operand = "({:s})".format(operand) + return "{:s}{:s}".format(str(self._operator), operand) + + def __call__(self, **kwargs): + operator = self._operator + if kwargs: + operator.subs(**kwargs) + if isinstance(operator, Expression): + return self.__class__(operator) + else: + return self._op(operator) + else: + try: + return self._op(operator) + except: + return self.__class__(operator) + + +class Add(BinaryOperator): + def __init__(self, lhs, rhs): + super(Add, self).__init__(op.add, "+", lhs, rhs) + + +class Sub(BinaryOperator): + def __init__(self, lhs, rhs): + super(Sub, self).__init__(op.sub, "-", lhs, rhs) + + +class Mul(BinaryOperator): + def __init__(self, lhs, rhs): + super(Mul, self).__init__(op.mul, "*", lhs, rhs) + + +class Div(BinaryOperator): + def __init__(self, lhs, rhs): + super(Div, self).__init__(op.truediv, "/", lhs, rhs) + + +class Mod(BinaryOperator): + def __init__(self, lhs, rhs): + super(Mod, self).__init__(op.mod, "%", lhs, rhs) + + +class Power(BinaryOperator): + def __init__(self, lhs, rhs): + super(Power, self).__init__(op.pow, "**", lhs, rhs) + + +class Neg(UnaryOperator): + def __init__(self, operator): + super(Neg, self).__init__(op.neg, "-", operator) + + +class Pos(UnaryOperator): + def __init__(self, operator): + super(Pos, self).__init__(op.pos, "+", operator) + + +class LT(BinaryOperator): + def __init__(self, lhs, rhs): + super(LT, self).__init__(op.lt, "<", lhs, rhs) + + +class LE(BinaryOperator): + def __init__(self, lhs, rhs): + super(LE, self).__init__(op.le, "<=", lhs, rhs) + + +class EQ(BinaryOperator): + def __init__(self, lhs, rhs): + super(EQ, self).__init__(op.eq, "==", lhs, rhs) + + +class NE(BinaryOperator): + def __init__(self, lhs, rhs): + super(NE, self).__init__(op.ne, "!=", lhs, rhs) + + +class GE(BinaryOperator): + def __init__(self, lhs, rhs): + super(GE, self).__init__(op.ge, ">=", lhs, rhs) + + +class GT(BinaryOperator): + def __init__(self, lhs, rhs): + super(GT, self).__init__(op.gt, ">", lhs, rhs) + + +class Not(UnaryOperator): + def __init__(self, value): + super(Not, self).__init__(operator.not_, "~", value) + + +class And(BinaryOperator): + def __init__(self, lhs, rhs): + super(And, self).__init__(operator.and_, "&", lhs, rhs) + + +class Or(BinaryOperator): + def __init__(self, lhs, rhs): + super(Or, self).__init__(operator.or_, "|", lhs, rhs) + + +class Xor(BinaryOperator): + @staticmethod + def _xor(lhs, rhs): + return (lhs and not rhs) or (rhs and not lhs) + + def __init__(self, lhs, rhs): + super(Xor, self).__init__(Xor._xor, "^", lhs, rhs) + + +class Abs(Function): + nargs = 1 + + def __init__(self, *symbol): + super(Abs, self).__init__(np.abs, *symbol) + + +class ACos(Function): + nargs = 1 + + def __init__(self, *symbol): + super(ACos, self).__init__(np.arccos, *symbol) + + +class ACosh(Function): + nargs = 1 + + def __init__(self, *symbol): + super(ACosh, self).__init__(np.arccosh, *symbol) + + +class AGauss(Function): + nargs = 3 + + @staticmethod + def _agauss(mu, alpha, n): + return np.normal(mu, + alpha / n, + 1) + + def __init__(self, *symbol): + super(AGauss, self).__init__(AGauss._agauss, *symbol) + + +class ASin(Function): + nargs = 1 + + def __init__(self, *symbol): + super(ASin, self).__init__(np.arcsin, *symbol) + + +class ASinh(Function): + nargs = 1 + + def __init__(self, *symbol): + super(ASinh, self).__init__(np.arcsinh, *symbol) + + +class ATan(Function): + nargs = 1 + + def __init__(self, *symbol): + super(ATan, self).__init__(np.arctan, *symbol) + + +class ATan2(Function): + nargs = 2 + + def __init__(self, *symbol): + super(ATan2, self).__init__(np.arctan2, *symbol) + + +class ATanh(Function): + nargs = 1 + + def __init__(self, *symbol): + super(ATanh, self).__init__(np.arctanh, *symbol) + + +class AUnif(Function): + nargs = 2 + + @staticmethod + def _aunif(mu, alpha): + return np.uniform(mu - alpha, + mu + alpha, + 1) + + def __init__(self, *symbol): + super(AUnif, self).__init__(AUnif._aunif, *symbol) + + +class Ceil(Function): + nargs = 1 + + def __init__(self, *symbol): + super(Ceil, self).__init__(np.ceil, *symbol) + + +class Cos(Function): + nargs = 1 + + def __init__(self, *symbol): + super(Cos, self).__init__(np.cos, *symbol) + + +class Cosh(Function): + nargs = 1 + + def __init__(self, *symbol): + super(Cosh, self).__init__(np.cosh, *symbol) + + +class Db(Function): + nargs = 1 + + def __init__(self, *symbol): + super(Db, self).__init__(10*np.log10, *symbol) + + +class Ddt(Function): + nargs = 1 + + def __init__(self, *symbol): + super(Ddt, self).__init__(np.diff, *symbol) + + +class Ddx(Function): + nargs = 2 + + def __init__(self, *symbol): + super(Ddx, self).__init__(np.diff, *symbol) + + +class Exp(Function): + nargs = 1 + + def __init__(self, *symbol): + super(Exp, self).__init__(np.exp, *symbol) + + +class Floor(Function): + nargs = 1 + + def __init__(self, *symbol): + super(Floor, self).__init__(np.floor, *symbol) + + +class Gauss(Function): + nargs = 3 + + @staticmethod + def _gauss(mu, alpha, n): + return np.normal(mu, + alpha * mu / n, + 1) + + def __init__(self, *symbol): + def __init__(self, *symbol): + super(Gauss, self).__init__(Gauss._gauss, *symbol) + + +class If(Function): + nargs = 3 + + @staticmethod + def _if(t, x, y): + return x if t else y + + def __init__(self, *symbol): + super(If, self).__init__(If._if, *symbol) + + +class Img(Function): + nargs = 1 + + def __init__(self, *symbol): + super(Img, self).__init__(np.imag, *symbol) + + +class Int(Function): + nargs = 1 + + def __init__(self, *symbol): + super(Int, self).__init__(int, *symbol) + + +class Limit(Function): + nargs = 3 + + @staticmethod + def _limit(x, y, z): + return y if x < y else z if x > z else x + + def __init__(self, *symbol): + super(Limit, self).__init__(Limit._limit, *symbol) + + +class Ln(Function): + nargs = 1 + + def __init__(self, *symbol): + super(Ln, self).__init__(np.log, *symbol) + + +class Log10(Function): + nargs = 1 + + def __init__(self, *symbol): + super(Log10, self).__init__(np.log10, *symbol) + + +class M(Function): + nargs = 1 + + def __init__(self, *symbol): + super(M, self).__init__(np.abs, *symbol) + + +class Max(Function): + nargs = 2 + + def __init__(self, *symbol): + super(Max, self).__init__(np.max, *symbol) + + +class Min(Function): + nargs = 2 + + def __init__(self, *symbol): + super(Min, self).__init__(np.min, *symbol) + + +class NInt(Function): + nargs = 1 + + def __init__(self, *symbol): + super(NInt, self).__init__(np.round, *symbol) + + +class Ph(Function): + nargs = 1 + + def __init__(self, *symbol): + super(Ph, self).__init__(np.angle, *symbol) + + +class Pow(Function): + nargs = 2 + + def __init__(self, *symbol): + super(Pow, self).__init__(np.power, *symbol) + + +class Pwr(Function): + nargs = 2 + + def __init__(self, *symbol): + super(Pwr, self).__init__(np.power, *symbol) + + +class Pwrs(Function): + nargs = 2 + + @staticmethod + def _pwrs(x, y): + return np.copysign(np.power(np.abs(x), y), x) + + def __init__(self, *symbol): + super(Pwrs, self).__init__(Pwrs._pwrs, *symbol) + + +class Rand(Function): + nargs = 0 + + @staticmethod + def _rand(): + return np.random.rand(1) + + def __init__(self, *symbol): + super(Rand, self).__init__(Rand._rand, *symbol) + + +class Re(Function): + nargs = 1 + + def __init__(self, *symbol): + super(Re, self).__init__(np.real, *symbol) + + +class Sdt(Function): + nargs = 1 + + def __init__(self, *symbol): + super(Sdt, self).__init__(np.trapz, *symbol) + + +class Sgn(Function): + nargs = 1 + + def __init__(self, *symbol): + super(Sgn, self).__init__(np.sign, *symbol) + + +class Sign(Function): + nargs = 2 + + def __init__(self, *symbol): + super(Sign, self).__init__(np.copysign, *symbol) + + +class Sin(Function): + nargs = 1 + + def __init__(self, *symbol): + super(Sin, self).__init__(np.sin, *symbol) + + +class Sinh(Function): + nargs = 1 + + def __init__(self, *symbol): + super(Sinh, self).__init__(np.sinh, *symbol) + + +class Sqrt(Function): + nargs = 1 + + def __init__(self, *symbol): + super(Sqrt, self).__init__(np.sqrt, *symbol) + + +class Stp(Function): + nargs = 1 + + @staticmethod + def _stp(x): + return x * (x > 0) + + def __init__(self, *symbol): + super(Stp, self).__init__(Stp._stp, *symbol) + + +class Tan(Function): + nargs = 1 + + def __init__(self, *symbol): + super(Tan, self).__init__(np.arctan, *symbol) + + +class Tanh(Function): + nargs = 1 + + def __init__(self, *symbol): + super(Tanh, self).__init__(np.cosh, *symbol) + + +class Unif(Function): + nargs = 2 + + @staticmethod + def _unif(mu, alpha): + return np.uniform(mu * (1. - alpha), + mu * (1. + alpha), + 1) + + def __init__(self, *symbol): + super(Unif, self).__init__(Unif._unif, *symbol) + + +class URamp(Function): + nargs = 1 + + @staticmethod + def _uramp(x): + return x * (x > 0) + + def __init__(self, *symbol): + super(URamp, self).__init__(URamp._uramp, *symbol) + + +class Symbol(Expression): + def __init__(self, value): + self._value = value + + @property + def value(self): + return self._value + + def __str__(self): + return str_spice(self._value) + + def subs(self, **kwargs): + name = str(self._value) + if name in kwargs: + return kwargs[name] + else: + return self + + def __int__(self): + return int(self._value) + + def __float__(self): + return float(self._value) + + +class I(Symbol): + nargs = 1 + + def __init__(self, device): + super(I, self).__init__("i({:s})".format(device)) + + +class V(Symbol): + nargs = (1, 2) + + def __init__(self, *nodes): + if len(nodes) not in self.__class__.nargs: + ValueError("Only 1 or two nodes allowed.") + string = "" + if len(nodes) == 1: + string = "v({:s})".format(str(nodes[0])) + else: + string = "v({:s}, {:s})".format(str(nodes[0]), + str(nodes[1])) + super(V, self).__init__(string) + +class Poly(Symbol): + def __init__(self, controllers, coefficient): + self._controllers = controllers + self._coefficient = coefficient + string = "poly({}) {} {}".format(len(self._controllers), + " ".join([str(controller) + for controller in self._controllers]), + " ".join([str(coeff) + for coeff in self._coefficient])) + super(Poly, self).__init__(string) + +class Table(Symbol): + def __init__(self, expression, points): + self._expression = expression + self._points = points + string = "table {{{}}} = {}".format(self._expression, + " ".join(["({}, {})".format(*point) + for point in self._points])) + super(Table, self).__init__(string) + + +class TableFile(Symbol): + def __init__(self, filename): + self._filename = filename + string = "tablefile({})".format(self._filename) + super(TableFile, self).__init__(string) diff --git a/PySpice/Spice/HighLevelElement.py b/PySpice/Spice/HighLevelElement.py index dbce6238a..4810f773f 100644 --- a/PySpice/Spice/HighLevelElement.py +++ b/PySpice/Spice/HighLevelElement.py @@ -28,7 +28,7 @@ from ..Math import rms_to_amplitude, amplitude_to_rms from ..Tools.StringTools import join_list, join_dict, str_spice, str_spice_list -from ..Unit import as_s, as_V, as_A, as_Hz +from ..Unit import as_s, as_v, as_a, as_Hz from .BasicElement import VoltageSource, CurrentSource #################################################################################################### @@ -39,12 +39,12 @@ class SourceMixinAbc: #################################################################################################### class VoltageSourceMixinAbc: - AS_UNIT = as_V + AS_UNIT = as_v #################################################################################################### class CurrentSourceMixinAbc: - AS_UNIT = as_A + AS_UNIT = as_a #################################################################################################### @@ -81,8 +81,6 @@ class SinusoidalMixin(SourceMixinAbc): Public Attributes: - :attr:`ac_magnitude` - :attr:`amplitude` :attr:`damping_factor` @@ -148,7 +146,7 @@ class PulseMixin(SourceMixinAbc): +--------+---------------+---------------+-------+ | V1 + initial value + + V, A | +--------+---------------+---------------+-------+ - | V2 + pulsed value + + V, A | + | V2 + pulse value + + V, A | +--------+---------------+---------------+-------+ | Td + delay time + 0.0 + sec | +--------+---------------+---------------+-------+ @@ -204,7 +202,7 @@ class PulseMixin(SourceMixinAbc): :attr:`pulse_width` - :attr:`pulsed_value` + :attr:`pulse_value` :attr:`rise_time` @@ -213,30 +211,25 @@ class PulseMixin(SourceMixinAbc): ############################################## def __init__(self, - initial_value, pulsed_value, - pulse_width, period, + initial_value, pulse_value=0, + pulse_width=None, period=None, delay_time=0, rise_time=0, fall_time=0, - phase=None, - dc_offset=0): + phase=None): # Fixme: default # rise_time, fall_time = Tstep # pulse_width, period = Tstop - self.dc_offset = self.AS_UNIT(dc_offset) # Fixme: -> SourceMixinAbc self.initial_value = self.AS_UNIT(initial_value) - self.pulsed_value = self.AS_UNIT(pulsed_value) + self.pulse_value = self.AS_UNIT(pulse_value) self.delay_time = as_s(delay_time) self.rise_time = as_s(rise_time) self.fall_time = as_s(fall_time) - self.pulse_width = as_s(pulse_width) - self.period = as_s(period) # Fixme: protect by setter? + self.pulse_width = as_s(pulse_width, none=True) + self.period = as_s(period, none=True) # Fixme: protect by setter? # XSPICE - if phase is not None: - self.phase = as_s(phase) - else: - self.phase = None + self.phase = as_s(phase, none=True) # # Fixme: to func? # # Check parameters @@ -258,18 +251,25 @@ def frequency(self): ############################################## def format_spice_parameters(self): - - # if DC is not provided, ngspice complains - # Warning: vpulse: no DC value, transient time 0 value used - + values = [self.initial_value] + if self.pulse_value is not None: + values.append(self.pulse_value) + if self.delay_time is not None: + values.append(self.delay_time) + if self.rise_time is not None: + values.append(self.rise_time) + if self.fall_time is not None: + values.append(self.fall_time) + if self.pulse_width is not None: + values.append(self.pulse_width) + if self.period is not None: + values.append(self.period) + if self.phase is not None: + values.append(self.phase) # Fixme: to func? - return join_list(( - 'DC {}'.format(str_spice(self.dc_offset)), - 'PULSE(' + - join_list((self.initial_value, self.pulsed_value, self.delay_time, - self.rise_time, self.fall_time, self.pulse_width, self.period, - self.phase)) + - ')')) + return ('PULSE(' + + join_list(values) + + ')') #################################################################################################### @@ -282,15 +282,15 @@ class ExponentialMixin(SourceMixinAbc): +------+--------------------+---------------+-------+ | Name + Parameter + Default Value + Units | +------+--------------------+---------------+-------+ - | V1 + Initial value + + V, A | + | V1 + Initial amplitude + + V, A | +------+--------------------+---------------+-------+ - | V2 + pulsed value + + V, A | + | V2 + amplitude + + V, A | +------+--------------------+---------------+-------+ | Td1 + rise delay time + 0.0 + sec | +------+--------------------+---------------+-------+ | tau1 + rise time constant + Tstep + sec | +------+--------------------+---------------+-------+ - | Td2 + fall delay time + Td1+Tstep + sec | + | Td2 + delay fall time + Td1+Tstep + sec | +------+--------------------+---------------+-------+ | tau2 + fall time constant + Tstep + sec | +------+--------------------+---------------+-------+ @@ -318,28 +318,34 @@ class ExponentialMixin(SourceMixinAbc): ############################################## def __init__(self, - initial_value, pulsed_value, + initial_amplitude, amplitude, rise_delay_time=.0, rise_time_constant=None, - fall_delay_time=None, fall_time_constant=None): + delay_fall_time=None, fall_time_constant=None): # Fixme: default - self.initial_value = self.AS_UNIT(initial_value) - self.pulsed_value = self.AS_UNIT(pulsed_value) + self.initial_amplitude = self.AS_UNIT(initial_amplitude) + self.amplitude = self.AS_UNIT(amplitude) self.rise_delay_time = as_s(rise_delay_time) - self.rise_time_constant = as_s(rise_time_constant) - self.fall_delay_time = as_s(fall_delay_time) - self.fall_time_constant = as_s(fall_time_constant) + self.rise_time_constant = as_s(rise_time_constant, none=True) + self.delay_fall_time = as_s(delay_fall_time, none=True) + self.fall_time_constant = as_s(fall_time_constant, none=True) ############################################## def format_spice_parameters(self): # Fixme: to func? + values = [self.initial_amplitude, self.amplitude, + self.rise_delay_time] + if self.rise_time_constant is not None: + values.append(self.rise_time_constant) + if self.delay_fall_time is not None: + values.append(self.delay_fall_time) + if self.fall_time_constant is not None: + values.append(self.fall_time_constant) + return ('EXP(' + - join_list((self.initial_value, self.pulsed_value, - self.rise_delay_time, self.rise_time_constant, - self.fall_delay_time, self.fall_time_constant, - )) + + join_list(values) + ')') #################################################################################################### @@ -374,10 +380,13 @@ class PieceWiseLinearMixin(SourceMixinAbc): ############################################## - def __init__(self, values, repeat_time=None, delay_time=None, dc=None): + def __init__(self, values, repeat_time=0, time_delay=.0, dc=None): + + # Fixme: default + self.values = sum(([as_s(t), self.AS_UNIT(x)] for (t, x) in values), []) - self.repeat_time = as_s(repeat_time, none=True) - self.delay_time = as_s(delay_time, none=True) + self.repeat_time = as_s(repeat_time) + self.time_delay = as_s(time_delay) self.dc = self.AS_UNIT(dc, none=True) ############################################## @@ -385,22 +394,85 @@ def __init__(self, values, repeat_time=None, delay_time=None, dc=None): def format_spice_parameters(self): # Fixme: to func? + result = '' + if self.dc is not None: + result = 'dc {} '.format(str_spice(self.dc)) + return (result + 'pwl(' + + join_list(self.values) + + ' ' + + join_dict({'r':self.repeat_time, 'td':self.time_delay}) + # OrderedDict( + ')') - d = {} - if self.repeat_time is not None: - d["r"] = self.repeat_time - if self.delay_time is not None: - d["td"] = self.delay_time +#################################################################################################### - _ = "" - if self.dc is not None: - _ += "DC {} ".format(str_spice(self.dc)) - _ += "PWL(" + join_list(self.values) - if d: - _ += " " + join_dict(d) # OrderedDict( - _ += ")" +class PatternMixin(SourceMixinAbc): + + r"""This class implements a Piece-Wise Linear waveform. + + Spice Syntax:: + + PAT( VHI VLO TD TR RF TSAMPLE DATA ) + + Generates a pattern based on the bit pattern indicated in the DATA field. + + `values` should be given as a list of (`Time`, `Value`)-tuples, e.g.:: + + PatternVoltageSource( + circuit, + 'pat1', '1', '0', + high_value, + low_value, + delay_time, + rise_time, + fall_time, + bit_period, + bit_pattern, + repeat + ) + + """ + + ############################################## + + def __init__(self, + high_value, + low_value, + delay_time, + rise_time, + fall_time, + bit_period, + bit_pattern, + repeat=False): + + # Fixme: default + + self.high_value = self.AS_UNIT(high_value) + self.low_value = self.AS_UNIT(low_value) + self.delay_time = as_s(delay_time) + self.rise_time = as_s(rise_time) + self.fall_time = as_s(fall_time) + self.bit_period = as_s(bit_period) + self.bit_pattern = bit_pattern + self.repeat = repeat - return _ + ############################################## + + def format_spice_parameters(self): + + # Fixme: to func? + return ('PAT(' + + join_list((self.high_value, + self.low_value, + self.delay_time, + self.rise_time, + self.fall_time, + self.bit_period, + "b" + self.bit_pattern, + 1 if self.repeat else 0 + ) + ) + + ")" + ) #################################################################################################### @@ -436,20 +508,27 @@ class SingleFrequencyFMMixin(SourceMixinAbc): ############################################## - def __init__(self, offset, amplitude, carrier_frequency, modulation_index, signal_frequency): + def __init__(self, offset, amplitude, carrier_frequency=None, modulation_index=None, signal_frequency=None): self.offset = self.AS_UNIT(offset) self.amplitude = self.AS_UNIT(amplitude) - self.carrier_frequency = as_Hz(carrier_frequency) + self.carrier_frequency = as_Hz(carrier_frequency, none=True) self.modulation_index = modulation_index - self.signal_frequency = as_Hz(signal_frequency) + self.signal_frequency = as_Hz(signal_frequency, none=True) ############################################## def format_spice_parameters(self): + values = [self.offset, self.amplitude] + if self.carrier_frequency is not None: + values.append(self.carrier_frequency) + if self.modulation_index is not None: + values.append(self.modulation_index) + if self.signal_frequency is not None: + values.append(self.signal_frequency) + # Fixme: to func? return ('SFFM(' + - join_list((self.offset, self.amplitude, self.carrier_frequency, - self.modulation_index, self.signal_frequency)) + + join_list(values) + ')') #################################################################################################### @@ -745,6 +824,48 @@ def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs): #################################################################################################### +class PatternVoltageSource(VoltageSource, VoltageSourceMixinAbc, PatternMixin): + + r"""This class implements a pattern voltage source. + + See :class:`PatternMixin` for documentation. + + """ + + ############################################## + + def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs): + + VoltageSource.__init__(self, netlist, name, node_plus, node_minus) + PatternMixin.__init__(self, *args, **kwargs) + + ############################################## + + format_spice_parameters = PatternMixin.format_spice_parameters + +#################################################################################################### + +class PatternCurrentSource(CurrentSource, CurrentSourceMixinAbc, PatternMixin): + + r"""This class implements a pattern current source. + + See :class:`PatternMixin` for documentation. + + """ + + ############################################## + + def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs): + + CurrentSource.__init__(self, netlist, name, node_plus, node_minus) + PatternMixin.__init__(self, *args, **kwargs) + + ############################################## + + format_spice_parameters = PatternMixin.format_spice_parameters + +#################################################################################################### + class SingleFrequencyFMVoltageSource(VoltageSource, VoltageSourceMixinAbc, SingleFrequencyFMMixin): r"""This class implements a single frequency FM waveform voltage source. diff --git a/PySpice/Spice/Library.py b/PySpice/Spice/Library.py index 4518d30b4..fbe78d984 100644 --- a/PySpice/Spice/Library.py +++ b/PySpice/Spice/Library.py @@ -22,11 +22,13 @@ import logging import re +import sys +from collections import OrderedDict #################################################################################################### from ..Tools.File import Directory -from .Parser import SpiceParser +from .EBNFSpiceParser import SpiceParser #################################################################################################### @@ -62,36 +64,36 @@ class SpiceLibrary: '.mod@xyce', ) + def _add_parsed(self, parsed): + for name, subcircuit in parsed.subcircuits.items(): + self._subcircuits[name] = subcircuit + for name, model in parsed.models.items(): + self._models[name] = model + ############################################## - def __init__(self, root_path, recurse=False, section=None): + def __init__(self, root_path=None, recurse=False, section=None): self._directory = Directory(root_path).expand_vars_and_user() - self._subcircuits = {} - self._models = {} + self._subcircuits = OrderedDict() + self._models = OrderedDict() + + if root_path is None: + self._directory=None + return for path in self._directory.iter_file(): extension = path.extension.lower() if extension in self.EXTENSIONS: self._logger.debug("Parse {}".format(path)) try: - spice_parser = SpiceParser(path=path, recurse=recurse, section=section) - for lib in spice_parser.incl_libs: - self._subcircuits.update(lib._subcircuits) - self._models.update(lib._models) + parsed = SpiceParser.parse(path=path) + self._add_parsed(parsed) except Exception as e: + tb = sys.exc_info()[2] # Parse problem with this file, so skip it and keep going. - self._logger.warn("Problem parsing {path} - {e}".format(**locals())) - continue - if spice_parser.is_only_subcircuit(): - for subcircuit in spice_parser.subcircuits: - name = self._suffix_name(subcircuit.name, extension) - self._subcircuits[name] = path - elif spice_parser.is_only_model(): - for model in spice_parser.models: - name = self._suffix_name(model.name, extension) - self._models[name] = path + raise RuntimeError("Problem parsing {}".format(e)).with_traceback(tb) ############################################## @@ -148,3 +150,7 @@ def search(self, s): if re.search(s, name): matches[name] = mdl_subckt return matches + + def insert(self, raw): + parsed = SpiceParser.parse(source=raw) + self._add_parsed(parsed) diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index 96fbc7334..ae0f45eb1 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -87,13 +87,14 @@ def __init__(self, **kwargs): #################################################################################################### -from ..Tools.StringTools import join_lines, join_list, join_dict +from ..Tools.StringTools import join_lines, join_list, join_dict, str_spice from .ElementParameter import ( ParameterDescriptor, PositionalElementParameter, FlagParameter, KeyValueParameter, ) from .Simulation import CircuitSimulator +from .Expressions import Expression #################################################################################################### @@ -102,7 +103,6 @@ def __init__(self, **kwargs): #################################################################################################### class DeviceModel: - """This class implements a device model. Ngspice model types: @@ -147,13 +147,14 @@ class DeviceModel: ############################################## - def __init__(self, name, modele_type, **parameters): + def __init__(self, name, model_type, **parameters): - self._name = str(name) - self._model_type = str(modele_type) + self._name = str(name).lower() + self._model_type = str(model_type) self._parameters = {} for key, value in parameters.items(): + # For parameters like is that are also python keywords if key.endswith('_'): key = key[:-1] self._parameters[key] = value @@ -178,20 +179,31 @@ def model_type(self): def parameters(self): return self._parameters.keys() - ############################################## + @property + def include(self): + """Include file""" + return self._include - def __getitem__(self, name): - return self._parameters[name] + @property + def is_included(self): + """is_included""" + return self._include is None ############################################## + def __getitem__(self, name): + if name in self._parameters: + return self._parameters[name] + elif name.endswith('_'): + return self._parameters[name[:-1]] + else: + raise IndexError(name) + def __getattr__(self, name): try: - return self._parameters[name] - except KeyError: - if name.endswith('_'): - return self._parameters[name[:-1]] - # Fixme: else + return self.__getitem__(name) + except IndexError: + raise AttributeError(name) ############################################## @@ -206,7 +218,6 @@ def __str__(self): #################################################################################################### class PinDefinition: - """This class defines a pin of an element.""" ############################################## @@ -255,7 +266,6 @@ def name(self): #################################################################################################### class Pin(PinDefinition): - """This class implements a pin of an element. It stores a reference to the element, the name of the pin and the node. @@ -266,7 +276,6 @@ class Pin(PinDefinition): ############################################## def __init__(self, element, pin_definition, node): - super().__init__(pin_definition.position, pin_definition.name, pin_definition.alias) self._element = element @@ -298,7 +307,6 @@ def disconnect(self): ############################################## def add_current_probe(self, circuit): - """Add a current probe between the node and the pin. The ammeter is named *ElementName_PinName*. @@ -315,7 +323,6 @@ def add_current_probe(self, circuit): #################################################################################################### class ElementParameterMetaClass(type): - # Metaclass to implements the element node and parameter machinery. """Metaclass to customise the element classes when they are created and to register SPICE prefix. @@ -380,7 +387,7 @@ def __new__(meta_cls, class_name, base_classes, namespace): for parameter in namespace['_spice_to_parameters'].values(): if (parameter.spice_name in namespace and parameter.spice_name != parameter.attribute_name): - _module_logger.error("Spice parameter '{}' clash with namespace".format(parameter.spice_name)) + _module_logger.error("Spice parameter '{}' clash with namespace, attribute name: '{}'".format(parameter.spice_name, parameter.attribute_name)) # Initialise pins @@ -419,7 +426,7 @@ def getter(self): pin = PinDefinition(position, *pin_definition, optional=optional) pins.append(pin) namespace['PINS'] = pins - namespace['__number_of_optional_pins__'] = number_of_optional_pins + namespace['_number_of_optional_pins_'] = number_of_optional_pins else: _module_logger.debug("{} don't define a PINS attribute".format(class_name)) @@ -452,8 +459,8 @@ def __init__(meta_cls, class_name, base_classes, namespace): def number_of_pins(cls): #! Fixme: many pins ??? number_of_pins = len(cls.PINS) - if cls.__number_of_optional_pins__: - return slice(number_of_pins - cls.__number_of_optional_pins__, number_of_pins +1) + if cls._number_of_optional_pins_: + return slice(number_of_pins - cls._number_of_optional_pins_, number_of_pins +1) else: return number_of_pins @@ -480,7 +487,6 @@ def spice_to_parameters(cls): #################################################################################################### class Element(metaclass=ElementParameterMetaClass): - """This class implements a base class for an element. It use a metaclass machinery for the declaration of the parameters. @@ -502,30 +508,54 @@ class Element(metaclass=ElementParameterMetaClass): def __init__(self, netlist, name, *args, **kwargs): self._netlist = netlist - self._name = str(name) + self._name = str(name).lower() self.raw_spice = '' self.enabled = True + parent = netlist + self._parameters = kwargs + # self._pins = kwargs.pop('pins',()) # Process remaining args - if len(self._parameters_from_args) < len(args): - raise NameError("Number of args mismatch") - for parameter, value in zip(self._parameters_from_args, args): - setattr(self, parameter.attribute_name, value) - + if len(self._parameters_from_args) + self._number_of_optional_pins_ + len(self._positional_parameters) < len(args): + raise NameError("Number of args mismatch for device: {}".format(self.name)) + # TODO: Modify the selection of arguments to take into account the RLC model cases. + if len(args) > 0: + optional_pins = 0 + if len(self._positional_parameters) < len(args): + optional_pins = len(args) - len(self._positional_parameters) + for parameter in self._positional_parameters.values(): + if parameter.attribute_name in kwargs: + optional_pins += 1 + continue + if optional_pins <= self._number_of_optional_pins_ and len(args) >= optional_pins: + self._pins += [Pin(self, pin_definition, netlist.get_node(node, True)) + for pin_definition, node in zip(self.PINS[len(self._pins):], args[:optional_pins])] + args = args[optional_pins:] + else: + raise IndexError("Incongruent number of optional pins on device: {}{}".format(self.PREFIX, self._name)) + if len(args) > 0: + read = [False] * len(args) + for parameter in self._positional_parameters.values(): + if parameter.position < len(read) and not read[parameter.position]: + setattr(self, parameter.attribute_name, args[parameter.position]) + read[parameter.position] = True # Process kwargs for key, value in kwargs.items(): if key == 'raw_spice': self.raw_spice = value - elif (key in self._positional_parameters or - key in self._optional_parameters or - key in self._spice_to_parameters): - setattr(self, key, value) - elif hasattr(self, 'VALID_KWARGS') and key in self.VALID_KWARGS: - pass # cf. NonLinearVoltageSource else: - raise ValueError('Unknown argument {}={}'.format(key, value)) + if (key in self._positional_parameters or + key in self._optional_parameters or + key in self._spice_to_parameters): + setattr(self, key, value) + else: + for parameter in self._optional_parameters: + if key.lower() == self._optional_parameters[parameter].spice_name.lower(): + setattr(self, parameter, value) + break + else: + raise ValueError('Unknown argument for {}: {}={}'.format(self.name, key, value)) - self._pins = () netlist._add_element(self) ############################################## @@ -614,8 +644,19 @@ def format_node_names(self): def parameter_iterator(self): """ This iterator returns the parameter in the right order. """ + positional_parameters = OrderedDict() # Fixme: .parameters ??? - for parameter_dict in self._positional_parameters, self._optional_parameters: + if len(self._positional_parameters) > 0: + read = [False] * len(self._positional_parameters) + for parameter in self._positional_parameters.values(): + if parameter.position < len(read) and read[parameter.position] is False: + if parameter.nonzero(self): + read[parameter.position] = parameter + for parameter in read: + if not (parameter is False): + positional_parameters[parameter.attribute_name] = parameter + + for parameter_dict in positional_parameters, self._optional_parameters: for parameter in parameter_dict.values(): if parameter.nonzero(self): yield parameter @@ -688,11 +729,11 @@ def __init__(self, netlist, name, *args, **kwargs): raise NameError("Node '{}' is missing for element {}".format(pin_definition.name, self.name)) pin_definition_nodes.append((pin_definition, node)) - super().__init__(netlist, name, *args, **kwargs) - self._pins = [Pin(self, pin_definition, netlist.get_node(node, True)) for pin_definition, node in pin_definition_nodes] + super().__init__(netlist, name, *args, **kwargs) + ############################################## def copy_to(self, netlist): @@ -708,10 +749,25 @@ class NPinElement(Element): ############################################## - def __init__(self, netlist, name, nodes, *args, **kwargs): + def __init__(self, netlist, name, *args, **kwargs): + nodes = [] + positional = len(self._positional_parameters) + for key, parameter in self._positional_parameters.items(): + if parameter.key_parameter: + if key in kwargs: + positional -= 1 + if positional > 0: + if positional < len(args): + nodes = args[:-positional] + args = args[-positional:] + else: + nodes = args + args = [] + + if len(nodes) > 0: + self._pins = [Pin(self, self.PINS[0], netlist.get_node(node, True)) + for node in nodes] super().__init__(netlist, name, *args, **kwargs) - self._pins = [Pin(self, PinDefinition(position), netlist.get_node(node, True)) - for position, node in enumerate(nodes)] ############################################## @@ -724,7 +780,6 @@ def copy_to(self, netlist): #################################################################################################### class Node: - """This class implements a node in the circuit. It stores a reference to the pins connected to the node. @@ -740,7 +795,7 @@ def __init__(self, netlist, name): self._logger.warning("Node name '{}' is a Python keyword".format(name)) self._netlist = netlist - self._name = str(name) + self._name = str(name).lower() self._pins = set() @@ -803,7 +858,6 @@ def disconnect(self, pin): #################################################################################################### class Netlist: - """This class implements a base class for a netlist. .. note:: This class is completed with element shortcuts when the module is loaded. @@ -820,13 +874,19 @@ def __init__(self): self._nodes = {} self._ground_node = self._add_node(self._ground_name) - self._subcircuits = OrderedDict() # to keep the declaration order - self._elements = OrderedDict() # to keep the declaration order - self._models = {} + self._subcircuits = OrderedDict() # to keep the declaration order + self._elements = OrderedDict() # to keep the declaration order + self._models = OrderedDict() + self._includes = [] # .include + self._used_models = {} + self._used_subcircuits = {} + self._parameters = OrderedDict() self.raw_spice = '' - # self._graph = networkx.Graph() + self._spice_sim = '' + + self._parent = None ############################################## @@ -894,22 +954,30 @@ def model(self, name): def node(self, name): return self._nodes[name] + def _get_spice_sim(self): + return self._spice_sim + + def _set_spice_sim(self, spice_sim): + for subcircuit in self._subcircuits: + self._subcircuits[subcircuit].spice_sim = spice_sim + self._spice_sim = spice_sim + + spice_sim = property(_get_spice_sim, _set_spice_sim) + ############################################## def __getitem__(self, attribute_name): - - if attribute_name in self._elements: - return self.element(attribute_name) - elif attribute_name in self._models: - return self.model(attribute_name) + attr = str(attribute_name).lower() + if attr in self._elements: + return self.element(attr) + elif attr in self._models: + return self.model(attr) # Fixme: subcircuits - elif attribute_name in self._nodes: - return self.node(attribute_name) + elif attr in self._nodes: + return self.node(attr) else: raise IndexError(attribute_name) # KeyError - ############################################## - def __getattr__(self, attribute_name): try: return self.__getitem__(attribute_name) @@ -918,8 +986,18 @@ def __getattr__(self, attribute_name): ############################################## + def _find_subcircuit(self, name): + name_low = name.lower() + if name_low not in self._subcircuits: + if hasattr(self, 'parent') and self.parent is not None: + return self.parent._find_subcircuit(name_low) + else: + return None + else: + return self._subcircuits[name_low] + def _add_node(self, node_name): - node_name = str(node_name) + node_name = str(node_name).lower() if node_name not in self._nodes: node = Node(self, node_name) self._nodes[node_name] = node @@ -942,7 +1020,7 @@ def get_node(self, node, create=False): if isinstance(node, Node): return node else: - str_node = str(node) + str_node = str(node).lower() if str_node in self._nodes: return self._nodes[str_node] elif create: @@ -957,26 +1035,111 @@ def has_ground_node(self): ############################################## + # def _update_used_models_subcircuits(self, subcircuit): + # for mod in subcircuit._used_models: + # if mod not in subcircuit._models: + # self._used_models.add(mod) + # for sub in subcircuit._used_subcircuits: + # if sub in subcircuit._subcircuits: + # if sub in self._subcircuits: + # raise ValueError("Repeated subcircuit label: {}".format(sub)) + # self._update_used_models_subcircuits(subcircuit._subcircuits[sub]) + # if sub not in self._subcircuits: + # self._subcircuits[sub] = subcircuit._subcircuits[sub] + # self._used_subcircuits.add(subcircuit.name) + + def _revise_required_models_subcircuits(self, subcircuits): + # Check the subcircuits required in the present object + # and confirm there are no duplicated subcircuit object with the same name + for subcircuit_name, subid in self._used_subcircuits.items(): + if subcircuit_name in self._subcircuits: + subcktid = id(self._subcircuits[subcircuit_name]) + if subid is None: + self._used_subcircuits[subcircuit_name] = subcktid + elif subid != subcktid: + raise ValueError("Differing subcircuits: {}".format(subcircuit_name)) + + # Check the subcircuits required in the present object with respect to parent + # and confirm there are no duplicated subcircuit object with the same name + for subcircuit_name in self._used_subcircuits: + if subcircuit_name in subcircuits: + subcktid = id(subcircuits[subcircuit_name]) + if self._used_subcircuits[subcircuit_name] is not None and self._used_subcircuits[subcircuit_name] != subcktid: + raise ValueError("Differing subcircuits: {}".format(subcircuit_name)) + else: + self._used_subcircuits[subcircuit_name] = subcktid + + # Check the subcircuits in the present object with respect to parent + # and confirm there are no duplicated subcircuit object with the same name + for subcircuit_name in self._subcircuits: + if subcircuit_name in subcircuits: + if id(self._subcircuits[subcircuit_name]) != id(subcircuits[subcircuit_name]): + raise ValueError("Differing subcircuits: {}".format(subcircuit_name)) + else: + _, subcircuits[subcircuit_name] = self._subcircuits.popitem(subcircuit_name) + + # Check the subcircuits in the child objects + for subcircuit_name in self._subcircuits: + required_subs, required_mods = self._subcircuits[subcircuit_name]._revise_required_models_subcircuits(subcircuits) + for subckt, subid in required_subs.items(): + if subckt in self._used_subcircuits: + if self._used_subcircuits[subckt] is None: + self._used_subcircuits[subckt] = subid + elif subid is not None and subid != self._used_subcircuits[subckt]: + raise ValueError("Differing subcircuits: {}".format(subckt)) + else: + self._used_subcircuits[subckt] = subid + for mod_name, modid in required_mods.items(): + if mod_name in self._used_models: + if self._used_models[mod_name] is None: + self._used_models[mod_name] = modid + elif modid is not None and modid != self._used_models[mod_name]: + raise ValueError("Differing subcircuits: {}".format(mod_name)) + else: + self._used_models[mod_name] = modid + return self._used_subcircuits, self._used_models + def _add_element(self, element): + from .BasicElement import SubCircuitElement """Add an element.""" - if element.name not in self._elements: - self._elements[element.name] = element + element_name = str(element.name).lower() + if element_name not in self._elements: + self._elements[element_name] = element + if hasattr(element, 'model'): + model = str(element.model).lower() + if model is not None: + self._used_models[model] = None + + if isinstance(element, SubCircuitElement): + subcircuit_name = str(element.subcircuit_name).lower() + if subcircuit_name is not None: + self._used_subcircuits[subcircuit_name] = None + else: + ValueError("Subcircuit not provided for element: {}".format(element_name)) else: - raise NameError("Element name {} is already defined".format(element.name)) + raise NameError("Element name {} is already defined".format(element_name)) ############################################## def _remove_element(self, element): try: - del self._elements[element.name] + del self._elements[str(element.name).lower()] except KeyError: raise NameError("Cannot remove undefined element {}".format(element)) ############################################## - def model(self, name, modele_type, **parameters): + def parameter(self, name, expression): + """Set a parameter.""" + self._parameters[str(name).lower()] = expression + + ############################################## + + def model(self, name, model_type, **parameters): + """Add a model.""" - model = DeviceModel(name, modele_type, **parameters) + + model = DeviceModel(str(name).lower(), model_type, **parameters) if model.name not in self._models: self._models[model.name] = model else: @@ -989,7 +1152,10 @@ def model(self, name, modele_type, **parameters): def subcircuit(self, subcircuit): """Add a sub-circuit.""" # Fixme: subcircuit is a class - self._subcircuits[str(subcircuit.name)] = subcircuit + assert isinstance(subcircuit, SubCircuit) + self._subcircuits[str(subcircuit.name).lower()] = subcircuit + subcircuit._parent = self + self._revise_required_models_subcircuits(self._subcircuits) ############################################## @@ -997,30 +1163,53 @@ def __str__(self): """ Return the formatted list of element and model definitions. """ # Fixme: order ??? netlist = self._str_raw_spice() - netlist += self._str_subcircuits() # before elements - netlist += self._str_elements() - netlist += self._str_models() + if self._parameters: + parameters = self._str_parameters() + netlist += parameters + netlist += os.linesep + if self._models: + models = self._str_models() + netlist += models + netlist += os.linesep + if self._subcircuits: + subcircuits = self._str_subcircuits() + netlist += subcircuits # before elements + netlist += os.linesep + netlist += self._str_elements() + os.linesep return netlist ############################################## + def _str_parameters(self): + parameters = [".param {}={}".format(key, ('{%s}' % str_spice(value)) if isinstance(value, Expression) else str_spice(value)) + for key, value in self._parameters.items()] + return join_lines(parameters) + + ############################################## + def _str_elements(self): elements = [element for element in self.elements if element.enabled] - return join_lines(elements) + os.linesep + return join_lines(elements) ############################################## def _str_models(self): - if self._models: - return join_lines(self.models) + os.linesep + if self._used_models: + models = [self._models[model] + for model in self._models + if model in self._used_models] + return join_lines(models) else: return '' ############################################## def _str_subcircuits(self): - if self._subcircuits: - return join_lines(self.subcircuits) + if self._used_subcircuits: + subcircuits = [self._subcircuits[subcircuit] + for subcircuit in self._subcircuits + if subcircuit in self._used_subcircuits] + return join_lines(subcircuits) else: return '' @@ -1032,6 +1221,34 @@ def _str_raw_spice(self): netlist += os.linesep return netlist + def include(self, library): + from .Library import SpiceLibrary + + """Include a file.""" + if isinstance(library, SpiceLibrary): + spice_library = library + else: + spice_library = SpiceLibrary(library) + models = spice_library.models + for model_name in models: + self.model(model_name, + spice_library[model_name]._model_type, + **spice_library[model_name]._parameters) + subcircuits = spice_library.subcircuits + for subcircuit_name in subcircuits: + if self._search_subcircuit(subcircuit_name) is not None: + continue + subcircuit = spice_library[subcircuit_name] + assert subcircuit is not None + subcircuit_def = subcircuit.build(parent=self) + self.subcircuit(subcircuit_def) + + def _search_subcircuit(self, name): + if name in self._subcircuits: + return self._subcircuits[name] + if self._parent is not None: + return self._parent._search_subcircuit(name) + return None #################################################################################################### class SubCircuit(Netlist): @@ -1041,21 +1258,25 @@ class SubCircuit(Netlist): ############################################## def __init__(self, name, *nodes, **kwargs): + self._included = None - if len(set(nodes)) != len(nodes): + nodes_set = set(nodes) + if len(nodes_set) != len(nodes): raise ValueError("Duplicated nodes in {}".format(nodes)) super().__init__() - self._name = str(name) - self._external_nodes = nodes - + self._name = str(name).lower() + self._external_nodes = tuple([Node(self, str(node)) for node in nodes]) + self.PINS = [PinDefinition(position, pin_definition) + for position, pin_definition in enumerate(nodes)] # Fixme: ok ? - self._ground = kwargs.get('ground', 0) - if 'ground' in kwargs: - del kwargs['ground'] + ground = 'ground' + self._ground = kwargs.get(ground, 0) + if ground in kwargs: + kwargs.pop(ground) - self._parameters = kwargs + self._params = kwargs ############################################## @@ -1073,6 +1294,14 @@ def clone(self, name=None): ############################################## + def parameter(self, name, expression): + + """Set a parameter.""" + + self._parameters[str(name)] = expression + + ############################################## + @property def name(self): return self._name @@ -1086,6 +1315,16 @@ def parameters(self): """Parameters""" return self._parameters + @property + def included(self): + """Include file""" + return self._included + + @property + def is_included(self): + """is_included""" + return self._included is None + ############################################## def check_nodes(self): @@ -1093,31 +1332,93 @@ def check_nodes(self): """Check for dangling nodes in the subcircuit.""" nodes = self._external_nodes - connected_nodes = set() + connected_nodes = dict() + connected_nodes.update([(node.name, False) for node in nodes]) for element in self.elements: - connected_nodes.add(nodes & element.nodes) - not_connected_nodes = nodes - connected_nodes + for node in element.nodes: + node_name = node.name + if node_name in connected_nodes: + connected_nodes[node_name] = True + else: + connected_nodes[node_name] = False + not_connected_nodes = [node for node in connected_nodes + if not connected_nodes[node]] if not_connected_nodes: raise NameError("SubCircuit Nodes {} are not connected".format(not_connected_nodes)) ############################################## def __str__(self): + netlist = self._str_raw_spice() + """Return the formatted subcircuit definition.""" nodes = join_list(self._external_nodes) - parameters = join_list(['{}={}'.format(key, value) - for key, value in self._parameters.items()]) - netlist = '.subckt ' + join_list((self._name, nodes, parameters)) + os.linesep - netlist += super().__str__() + + netlist += '.subckt ' + join_list((self._name, nodes)) + if self._params: + parameters = {key: ('{%s}' % str_spice(value)) if isinstance(value, Expression) else str_spice(value) + for key, value in self._params.items()} + netlist += ' params: ' + join_dict(parameters) + netlist += os.linesep + if self._parameters: + parameters = self._str_parameters() + netlist += parameters + netlist += os.linesep + if self._models: + models = self._str_models() + netlist += models + netlist += os.linesep + if self._subcircuits: + subcircuits = self._str_subcircuits() + netlist += subcircuits # before elements + netlist += os.linesep + netlist += self._str_elements() + os.linesep netlist += '.ends ' + self._name + os.linesep return netlist #################################################################################################### +class Library(Netlist): + """This class implements a library netlist.""" + + ############################################## + + def __init__(self, entry): + self._entry = entry + + ############################################## + + def clone(self, entry=None): + if entry is None: + entry = self._entry + + library = self.__class__(entry) + self.copy_to(library) + + ############################################## + + @property + def entry(self): + return self._entry + + ############################################## + + def __str__(self): + """Return the formatted library definition.""" + + netlist = '.lib ' + self._entry + os.linesep + netlist += super().__str__() + netlist += '.endl ' + self._entry + os.linesep + return netlist + + +#################################################################################################### + class SubCircuitFactory(SubCircuit): NAME = None NODES = None + PINS = None ############################################## @@ -1149,17 +1450,24 @@ def __init__(self, title, super().__init__() + if title is None: + title = "" self.title = str(title) self._ground = ground - self._global_nodes = set(global_nodes) # .global - self._includes = [] # .include - self._libs = [] # .lib, contains a (name, section) tuple - self._parameters = {} # .param + self._global_nodes = set(global_nodes) # .global + self._data = {} # .data # Fixme: not implemented # .csparam # .func # .if + # .lib + + ############################################## + + @property + def name(self): + return self.title ############################################## @@ -1180,47 +1488,42 @@ def clone(self, title=None): ############################################## - def include(self, path): - """Include a file.""" - if path not in self._includes: - self._includes.append(path) - else: - self._logger.warn("Duplicated include") - - ############################################## - - def lib(self, name, section=None): - """Load a library.""" - v = (name, section) - if v not in self._libs: - self._libs.append(v) - else: - self._logger.warn(f"Duplicated lib {v}") - ############################################## - def parameter(self, name, expression): - """Set a parameter.""" - self._parameters[str(name)] = str(expression) + def data(self, table, **kwargs): + self._data.update[table] = kwargs ############################################## - def str(self, simulator=None): + def str(self, spice_sim=None): + if spice_sim is not None: + self.spice_sim = spice_sim """Return the formatted desk.""" # if not self.has_ground_node(): # raise NameError("Circuit don't have ground node") netlist = self._str_title() - netlist += self._str_includes(simulator) - netlist += self._str_libs(simulator) - netlist += self._str_globals() - netlist += self._str_parameters() + netlist += os.linesep + # netlist += self._str_includes(simulator) + for sub_circuit in self._subcircuits: + self._subcircuits[sub_circuit].spice_sim = self.spice_sim + + if self._global_nodes: + netlist += self._str_globals() + os.linesep netlist += super().__str__() return netlist ############################################## def _str_title(self): - return '.title {}'.format(self.title) + os.linesep + if self.title: + lines = self.title.splitlines() + title = '.title {}'.format(lines[0]) + os.linesep + if len(lines) > 1: + for line in lines[1:]: + title += '* {}'.format(line) + os.linesep + return title + else: + return '.title' + os.linesep ############################################## @@ -1242,44 +1545,18 @@ def _str_includes(self, simulator=None): ############################################## - def _str_libs(self, simulator=None): - if self._libs: - libs = [] - for lib, section in self._libs: - lib = Path(str(lib)).resolve() - if simulator: - lib_flavour = Path(f"{lib}@{simulator}") - if lib_flavour.exists(): - lib = lib_flavour - s = f".lib {lib}" - if section: - s += f" {section}" - libs.append(s) - return os.linesep.join(libs) + os.linesep - else: - return '' - - ############################################## - def _str_globals(self): - if self._global_nodes: - return '.global ' + join_list(self._global_nodes) + os.linesep - else: - return '' - - ############################################## - def _str_parameters(self): - if self._parameters: - return ''.join([f'.param {key}={value}' + os.linesep - for key, value in self._parameters.items()]) + if self._global_nodes: + return join_lines(['.global {}'.format(str_spice(node)) + for node in self._global_nodes]) else: return '' ############################################## def __str__(self): - return self.str(simulator=None) + return self.str(spice_sim=None) ############################################## @@ -1290,3 +1567,17 @@ def str_end(self): def simulator(self, *args, **kwargs): return CircuitSimulator.factory(self, *args, **kwargs) + + +#################################################################################################### + +class Comment: + + def __init__(self, txt=''): + self._txt = txt + + def __str__(self): + return self._txt + + def __repr__(self): + return "Comment({})".format(repr(self._txt)) diff --git a/PySpice/Spice/NgSpice/Shared.py b/PySpice/Spice/NgSpice/Shared.py index 183c08a4b..dd2777e77 100644 --- a/PySpice/Spice/NgSpice/Shared.py +++ b/PySpice/Spice/NgSpice/Shared.py @@ -101,7 +101,7 @@ WaveForm, ) from PySpice.Tools.EnumFactory import EnumFactory -from PySpice.Unit import u_V, u_A, u_s, u_Hz, u_F, u_Degree +from PySpice.Unit import u_V, u_A, u_s, u_Hz, u_F, u_celsius from .SimulationType import SIMULATION_TYPE diff --git a/PySpice/Spice/NgSpice/Simulation.py b/PySpice/Spice/NgSpice/Simulation.py index 92b62ec24..b7ee69362 100644 --- a/PySpice/Spice/NgSpice/Simulation.py +++ b/PySpice/Spice/NgSpice/Simulation.py @@ -24,12 +24,14 @@ #################################################################################################### import logging +import os #################################################################################################### from ..Simulation import CircuitSimulator from .Server import SpiceServer from .Shared import NgSpiceShared +from PySpice.Tools.StringTools import join_list #################################################################################################### @@ -69,6 +71,24 @@ def __init__(self, circuit, **kwargs): ############################################## + def save_str(self): + result = "" + if self._saved_nodes: + # Place 'all' first + saved_nodes = set(self._saved_nodes) + if 'all' in saved_nodes: + all_str = ' all' + saved_nodes.remove('all') + else: + all_str = '' + result += '.save' + all_str + if saved_nodes: + result += ' ' + join_list(saved_nodes) + result += os.linesep + return result + + ############################################## + def _run(self, analysis_method, *args, **kwargs): super()._run(analysis_method, *args, **kwargs) diff --git a/PySpice/Spice/Parser_jmgc.py b/PySpice/Spice/Parser_jmgc.py deleted file mode 100644 index c28e04d72..000000000 --- a/PySpice/Spice/Parser_jmgc.py +++ /dev/null @@ -1,1486 +0,0 @@ -#################################################################################################### -# -# PySpice - A Spice Package for Python -# Copyright (C) 2020 jmgc / Fabrice Salvaire -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -#################################################################################################### - -#################################################################################################### - -"""This module implements a partial SPICE netlist parser. - -See the :command:`cir2py` tool for an example of usage of the parser. - -It would be difficult to implement a full parser for Ngspice since the syntax is mainly contextual. - -SPICE is case insensitive. - -""" - -#################################################################################################### - -from collections import OrderedDict -import logging -import os -import regex - -#################################################################################################### - -from .ElementParameter import FlagParameter -from .Netlist import ElementParameterMetaClass, Circuit, SubCircuit - -#################################################################################################### - -_module_logger = logging.getLogger(__name__) - -#################################################################################################### - -class ParseError(NameError): - pass - -#################################################################################################### - -class PrefixData: - - """This class represents a device prefix.""" - - ############################################## - - def __init__(self, prefix, classes): - - self.prefix = prefix - self.classes = classes - - number_of_positionals_min = 1000 - number_of_positionals_max = 0 - has_optionals = False - for element_class in classes: - number_of_positionals = element_class.number_of_positional_parameters - number_of_positionals_min = min(number_of_positionals_min, number_of_positionals) - number_of_positionals_max = max(number_of_positionals_max, number_of_positionals) - has_optionals = max(has_optionals, bool(element_class.optional_parameters)) - - self.number_of_positionals_min = number_of_positionals_min - self.number_of_positionals_max = number_of_positionals_max - self.has_optionals = has_optionals - - self.multi_devices = len(classes) > 1 - self.has_variable_number_of_pins = prefix in ('Q', 'X') # NPinElement, Q has 3 to 4 pins - if self.has_variable_number_of_pins: - self.number_of_pins = None - else: - # Q and X are single - self.number_of_pins = classes[0].number_of_pins - - self.has_flag = False - for element_class in classes: - for parameter in element_class.optional_parameters.values(): - if isinstance(parameter, FlagParameter): - self.has_flag = True - - ############################################## - - def __len__(self): - return len(self.classes) - - ############################################## - - def __iter__(self): - return iter(self.classes) - - ############################################## - - @property - def single(self): - if not self.multi_devices: - return self.classes[0] - else: - raise NameError() - -#################################################################################################### - -_prefix_cache = {} -for prefix, classes in ElementParameterMetaClass._classes.items(): - prefix_data = PrefixData(prefix, classes) - _prefix_cache[prefix] = prefix_data - _prefix_cache[prefix.lower()] = prefix_data - -# for prefix_data in sorted(_prefix_cache.values(), key=lambda x: len(x)): -# print(prefix_data.prefix, -# len(prefix_data), -# prefix_data.number_of_positionals_min, prefix_data.number_of_positionals_max, -# prefix_data.has_optionals) - -# Single: -# B 0 True -# D 1 True -# F 2 False -# G 1 False -# H 2 False -# I 1 False -# J 1 True -# K 3 False -# M 1 True -# S 2 False -# V 1 False -# W 3 False -# Z 1 True - -# Two: -# E 0 1 False -# L 1 2 True - -# Three: -# C 1 2 True -# R 1 2 True - -# NPinElement: -# Q 1 1 True -# X 1 1 False - -#################################################################################################### - -class Statement: - - """This base class implements a statement, in fact a line in a Spice netlist.""" - - ############################################## - - def __init__(self, line, statement=None): - self._line = line - if statement is not None: - self._line.lower_case_statement(statement) - - ############################################## - - def __repr__(self): - return '{} {}'.format(self.__class__.__name__, repr(self._line)) - - ############################################## - - def value_to_python(self, x): - if x: - if str(x)[0].isdigit(): - return str(x) - else: - return "'{}'".format(x) - else: - return '' - - ############################################## - - def values_to_python(self, values): - return [self.value_to_python(x) for x in values] - - ############################################## - - def kwargs_to_python(self, kwargs): - return ['{}={}'.format(key, self.value_to_python(value)) - for key, value in kwargs.items()] - - ############################################## - - def join_args(self, args): - return ', '.join(args) - -#################################################################################################### - -class Comment(Statement): - pass - -#################################################################################################### - -class Title(Statement): - - """This class implements a title definition.""" - - ############################################## - - def __init__(self, line): - super().__init__(line, statement='title') - self._title = self._line.right_of('.title') - - ############################################## - - def __str__(self): - return self._title - - ############################################## - - def __repr__(self): - return 'Title {}'.format(self._title) - -#################################################################################################### - -class Include(Statement): - - """This class implements a include definition.""" - - ############################################## - - def __init__(self, line): - super().__init__(line, statement='include') - self._include = self._line.right_of('.include') - - ############################################## - - def __str__(self): - return self._include - - ############################################## - - def __repr__(self): - return 'Include {}'.format(self._include) - - ############################################## - - def to_python(self, netlist_name): - return '{}.include({})'.format(netlist_name, self._include) + os.linesep - -#################################################################################################### - -class Model(Statement): - - """This class implements a model definition. - - Spice syntax:: - - .model mname type(pname1=pval1 pname2=pval2 ... ) - - """ - - ############################################## - - def __init__(self, line): - super().__init__(line, statement='model') - - base, self._parameters = line.split_keyword('.model') - self._name, self._model_type = base - self._name = self._name.lower() - - ############################################## - - @property - def name(self): - """Name of the model""" - return self._name - - ############################################## - - def __repr__(self): - return 'Model {} {} {}'.format(self._name, self._model_type, self._parameters) - - ############################################## - - def to_python(self, netlist_name): - args = self.values_to_python((self._name, self._model_type)) - kwargs = self.kwargs_to_python(self._parameters) - return '{}.model({})'.format(netlist_name, self.join_args(args + kwargs)) + os.linesep - - ############################################## - - def build(self, circuit): - return circuit.model(self._name, self._model_type, **self._parameters) - -#################################################################################################### - -class Parameter(Statement): - - """This class implements a parameter definition. - - Spice syntax:: - - .param name=expr - - """ - - ############################################## - - def __init__(self, line): - super().__init__(line, statement='param') - - text = line.right_of('.param').strip().lower() # Fixme: lower ??? - idx = text.find('=') - self._name = text[:idx].strip() - self._value = text[idx + 1:].strip() - - ############################################## - - @property - def name(self): - """Name of the model""" - return self._name - - ############################################## - - def __repr__(self): - return 'Param {}={}'.format(self._name, self._value) - - ############################################## - - def to_python(self, netlist_name): - args = self.values_to_python((self._name, self._value)) - # Fixme: linesep here ??? - return '{}.param({})'.format(netlist_name, self.join_args(args)) + os.linesep - - ############################################## - - def build(self, circuit): - circuit.parameter(self._name, self._value) - -#################################################################################################### - -# Review: HERE - -class CircuitStatement(Statement): - - # Review: jmgc - - """This class implements a circuit definition. - - Spice syntax:: - - Title ... - - """ - - ############################################## - - def __init__(self, title): - - super().__init__(title, statement='title') - - # Review: Title - title_statement = '.title ' - self._title = str(title) - if self._title.startswith(title_statement): - self._title = self._title[len(title_statement):] - - self._statements = [] - self._subcircuits = [] - self._models = [] - self._required_subcircuits = set() - self._required_models = set() - self._params = [] - - ############################################## - - @property - def title(self): - """Title of the circuit.""" - return self._title - - @property - def name(self): - """Name of the circuit.""" - return self._title - - @property - def models(self): - """Models of the circuit.""" - return self._models - - @property - def subcircuits(self): - """Subcircuits of the circuit.""" - return self._subcircuits - - @property - def params(self): - """Parameters of the circuit.""" - return self._params - - ############################################## - - def __repr__(self): - text = 'Circuit {}'.format(self._title) + os.linesep - text += os.linesep.join([repr(model) for model in self._models]) + os.linesep - text += os.linesep.join([repr(subcircuit) for subcircuit in self._subcircuits]) + os.linesep - text += os.linesep.join([' ' + repr(statement) for statement in self._statements]) - return text - - ############################################## - - def __iter__(self): - """Return an iterator on the statements.""" - return iter(self._models + self._subcircuits + self._statements) - - ############################################## - - def append(self, statement): - """Append a statement to the statement's list.""" - self._statements.append(statement) - - ############################################## - - def append_model(self, statement): - """Append a model to the statement's list.""" - self._models.append(statement) - - ############################################## - - def append_param(self, statement): - """Append a param to the statement's list.""" - self._params.append(statement) - - ############################################## - - def append_subcircuit(self, statement): - """Append a subcircuit to the statement's list.""" - self._subcircuits.append(statement) - - ############################################## - - def to_python(self, ground=0): - subcircuit_name = 'subcircuit_' + self._name - args = self.values_to_python([subcircuit_name] + self._nodes) - source_code = '' - source_code += '{} = SubCircuit({})'.format(subcircuit_name, self.join_args(args)) + os.linesep - source_code += SpiceParser.netlist_to_python(subcircuit_name, self, ground) - return source_code - - ############################################## - - def build(self, ground=0): - circuit = Circuit(self._title) - for statement in self._params: - statement.build(circuit) - for statement in self._models: - model = statement.build(circuit) - for statement in self._subcircuits: - subckt = statement.build(ground) # Fixme: ok ??? - circuit.subcircuit(subckt) - for statement in self._statements: - if isinstance(statement, Element): - statement.build(circuit, ground) - return circuit - -#################################################################################################### - -class SubCircuitStatement(Statement): - - """This class implements a sub-circuit definition. - - Spice syntax:: - - .SUBCKT name node1 ... param1=value1 ... - - """ - - ############################################## - - def __init__(self, line): - - super().__init__(line, statement='subckt') - - # Fixme - parameters, dict_parameters = self._line.split_keyword('.subckt') - # Review: syntax ??? - if parameters[-1].lower() == 'params:': - parameters = parameters[:-1] - self._name, self._nodes = parameters[0], parameters[1:] - self._name = self._name.lower() - self._parameters = dict_parameters - - self._statements = [] - self._subcircuits = [] - self._models = [] - self._required_subcircuits = set() - self._required_models = set() - self._params = [] - - ############################################## - - @property - def name(self): - """Name of the sub-circuit.""" - return self._name - - @property - def nodes(self): - """Nodes of the sub-circuit.""" - return self._nodes - - @property - def models(self): - """Models of the sub-circuit.""" - return self._models - - @property - def params(self): - """Params of the sub-circuit.""" - return self._params - - @property - def subcircuits(self): - """Subcircuits of the sub-circuit.""" - return self._subcircuits - - ############################################## - - def __repr__(self): - if self._parameters: - text = 'SubCircuit {} {} Parameters: {}'.format(self._name, self._nodes, self._parameters) + os.linesep - else: - text = 'SubCircuit {} {}'.format(self._name, self._nodes) + os.linesep - text += os.linesep.join([repr(model) for model in self._models]) + os.linesep - text += os.linesep.join([repr(subcircuit) for subcircuit in self._subcircuits]) + os.linesep - text += os.linesep.join([' ' + repr(statement) for statement in self._statements]) - return text - - ############################################## - - def __iter__(self): - """Return an iterator on the statements.""" - return iter(self._models + self._subcircuits + self._statements) - - ############################################## - - def append(self, statement): - """Append a statement to the statement's list.""" - self._statements.append(statement) - - ############################################## - - def append_model(self, statement): - """Append a model to the statement's list.""" - self._models.append(statement) - - ############################################## - - def append_param(self, statement): - """Append a param to the statement's list.""" - self._params.append(statement) - - ############################################## - - def append_subcircuit(self, statement): - """Append a model to the statement's list.""" - self._subcircuits.append(statement) - - ############################################## - - def to_python(self, ground=0): - subcircuit_name = 'subcircuit_' + self._name - args = self.values_to_python([subcircuit_name] + self._nodes) - source_code = '' - source_code += '{} = SubCircuit({})'.format(subcircuit_name, self.join_args(args)) + os.linesep - source_code += SpiceParser.netlist_to_python(subcircuit_name, self, ground) - return source_code - - ############################################## - - def build(self, ground=0, parent=None): - subcircuit = SubCircuit(self._name, *self._nodes, **self._parameters) - subcircuit.parent = parent - for statement in self._params: - statement.build(subcircuit) - for statement in self._models: - model = statement.build(subcircuit) - for statement in self._subcircuits: - subckt = statement.build(ground, parent=subcircuit) # Fixme: ok ??? - subcircuit.subcircuit(subckt) - for statement in self._statements: - if isinstance(statement, Element): - statement.build(subcircuit, ground) - return subcircuit - -#################################################################################################### - -class Element(Statement): - - """This class implements an element definition. - - "{ expression }" are allowed in device line. - - """ - - _logger = _module_logger.getChild('Element') - - ############################################## - - def __init__(self, line): - - super().__init__(line) - - line_str = str(line) - # self._logger.debug(os.linesep + line_str) - - # Retrieve device prefix - prefix = line_str[0] - if prefix.isalpha(): - self._prefix = prefix - else: - raise ParseError("Not an element prefix: " + prefix) - prefix_data = _prefix_cache[self._prefix] - - # Retrieve device name - args, kwargs = line.split_element(prefix) - self._name = args.pop(0) - - self._nodes = [] - self._parameters = [] - self._dict_parameters = {} - - # Read nodes - if not prefix_data.has_variable_number_of_pins: - number_of_pins = prefix_data.number_of_pins - if number_of_pins: - self._nodes = args[:number_of_pins] - args = args[number_of_pins:] - else: # Q or X - if prefix_data.prefix == 'Q': - self._nodes = args[:3] - args = args[3:] - # Fixme: optional node - else: # X - if args[-1].lower() == 'params:': - args.pop() - self._parameters.append(args.pop()) - self._nodes = args - args = [] - - # Read positionals - number_of_positionals = prefix_data.number_of_positionals_min - if number_of_positionals and (len(args) > 0) and (prefix_data.prefix != 'X'): # model is optional - self._parameters = args[:number_of_positionals] - args = args[number_of_positionals:] - if prefix_data.multi_devices and (len(args) > 0): - remaining = args - args = [] - self._parameters.extend(remaining) - - if prefix_data.prefix in ('V', 'I') and (len(args) > 0): - # merge remaining - self._parameters[-1] += " " + " ".join(args) - self._dict_parameters = kwargs - - # Read optionals - if (prefix_data.has_optionals or (prefix_data.prefix == 'X')) and (len(kwargs) > 0): - for key in kwargs: - self._dict_parameters[key] = kwargs[key] - - if prefix_data.multi_devices: - for element_class in prefix_data: - if len(self._parameters) == element_class.number_of_positional_parameters: - break - else: - element_class = prefix_data.single - self.factory = element_class - - # Move positionals passed as kwarg - to_delete = [] - for parameter in element_class.positional_parameters.values(): - if parameter.key_parameter: - idx = parameter.position - if idx < len(self._parameters): - self._dict_parameters[parameter.attribute_name] = self._parameters[idx] - to_delete.append(idx - len(to_delete)) - for idx in to_delete: - self._parameters.pop(idx) - - # self._logger.debug(os.linesep + self.__repr__()) - - ############################################## - - @property - def name(self): - """Name of the element""" - return self._name - - ############################################## - - def __repr__(self): - return 'Element {0._prefix} {0._name} {0._nodes} {0._parameters} {0._dict_parameters}'.format(self) - - ############################################## - - def translate_ground_node(self, ground): - nodes = [] - for node in self._nodes: - if str(node) == str(ground): - node = 0 - nodes.append(node) - return nodes - - ############################################## - - def to_python(self, netlist_name, ground=0): - - nodes = self.translate_ground_node(ground) - args = [self._name] - if self._prefix != 'X': - args += nodes + self._parameters - else: # != Spice - args += self._parameters + nodes - args = self.values_to_python(args) - kwargs = self.kwargs_to_python(self._dict_parameters) - return '{}.{}({})'.format(netlist_name, self._prefix, self.join_args(args + kwargs)) + os.linesep - - ############################################## - - def _check_params(self, elements=1): - params = [] - for param in self._parameters: - values = param.replace(',', ' ') - if values[0] == '(' and values[-1] == ')': - values = values[1: -1].split() - if len(values) > elements: - raise IndexError('Incorrect number of elements for (%r): %s' % (self, param)) - params.extend(values) - else: - params.extend(values.split()) - self._parameters = params - - ############################################## - - def _voltage_controlled_nodes(self, poly_arg): - result = ['v(%s,%s)' % nodes - for nodes in zip(self._parameters[:(2 * poly_arg):2], - self._parameters[1:(2 * poly_arg):2])] - result += self._parameters[2 * poly_arg:] - return ' '.join(result) - - ############################################## - - def _current_controlled_nodes(self, poly_arg): - result = ['i(%s)' % node - for node in self._parameters[:poly_arg]] - result += self._parameters[poly_arg:] - return ' '.join(result) - - ############################################## - - def _manage_controlled_sources(self, nodes): - try: - idx = self._nodes.index('POLY') - if idx == 2: - poly_arg = self._nodes[3] - if poly_arg[0] == '(' and poly_arg[-1] == ')': - poly_arg = poly_arg[1:-1] - try: - poly_arg = int(poly_arg) - except TypeError as te: - raise TypeError('Not valid poly argument: %s' % poly_arg, te) - self._nodes = self._nodes[:2] - nodes = nodes[:2] - if self._prefix in 'EG': - self._check_params(2) - values = self._voltage_controlled_nodes(poly_arg) - if self._prefix == 'E': - key = 'v' - else: - key = 'i' - else: - self._check_params(1) - values = self._current_controlled_nodes(poly_arg) - if self._prefix == 'F': - key = 'v' - else: - key = 'i' - poly_str = '{ POLY (%d) %s }' % (poly_arg, values) - - self._dict_parameters[key] = poly_str - self._parameters.clear() - self._name = self._prefix + self._name - self._prefix = 'B' - prefix_data = _prefix_cache[self._prefix] - self.factory = prefix_data.single - return nodes - raise IndexError('Incorrect position of POLY: %r' % self) - except ValueError: - pass - _correction = [] - correction = [] - for _node, node in zip(self._nodes, nodes): - _values = _node.replace(',', ' ') - try: - values = node.replace(',', ' ') - except AttributeError: - values = str(node) - if _values[0] == '(' and _values[-1] == ')': - _values = _values[1: -1] - if values[0] == '(' and values[-1] == ')': - values = values[1: -1] - _correction.extend(_values.split()) - correction.extend(values.split()) - self._parameters = correction[len(self._nodes):] + self._parameters - self._nodes = _correction[:len(self._nodes)] - parameters = self._parameters - correction = correction[:len(self._nodes)] - if self._prefix in 'EG': - if len(correction) + len(parameters) == 5: - parameters = correction[2:] + parameters - self._nodes = _correction[:2] - value = '{v(%s, %s) * %s}' % tuple(parameters) - if self._prefix == 'E': - key = 'v' - else: - key = 'i' - self._dict_parameters[key] = value - self._parameters.clear() - self._name = self._prefix + self._name - self._prefix = 'B' - prefix_data = _prefix_cache[self._prefix] - self.factory = prefix_data.single - else: - if len(correction) + len(parameters) == 4: - parameters = correction[2:] + parameters - self._nodes = _correction[:2] - value = '{i(%s) * %s}' % tuple(parameters) - if self._prefix == 'F': - key = 'v' - else: - key = 'i' - self._dict_parameters[key] = value - self._parameters.clear() - self._name = self._prefix + self._name - self._prefix = 'B' - prefix_data = _prefix_cache[self._prefix] - self.factory = prefix_data.single - return correction[:len(self._nodes)] - - ############################################## - - def build(self, circuit, ground=0): - - nodes = self.translate_ground_node(ground) - if self._prefix != 'X': - if self._prefix in ('EFGH'): - nodes = self._manage_controlled_sources(nodes) - args = nodes + self._parameters - else: # != Spice - args = self._parameters + nodes - factory = getattr(circuit, self.factory.__alias__) - kwargs = self._dict_parameters - message = ' '.join([str(x) for x in (self._prefix, self._name, args, - self._dict_parameters)]) - self._logger.debug(message) - return factory(self._name, *args, **kwargs) - - -#################################################################################################### - -class Line: - - """This class implements a line in the netlist.""" - - _logger = _module_logger.getChild('Line') - - ############################################## - - def __init__(self, line, line_range, end_of_line_comment): - - self._end_of_line_comment = end_of_line_comment - - text, comment, self._is_comment = self._split_comment(line) - - self._text = text - self._comment = comment - self._line_range = line_range - - ############################################## - - def __repr__(self): - return '{0._line_range}: {0._text} // {0._comment}'.format(self) - - ############################################## - - def __str__(self): - return self._text - - ############################################## - - @property - def comment(self): - return self._comment - - @property - def is_comment(self): - return self._is_comment - - ############################################## - - def _split_comment(self, line): - - line = str(line) - - if line.startswith('*'): - is_comment = True - text = '' - comment = line[1:].strip() - else: - is_comment = False - # remove end of line comment - location = -1 - for marker in self._end_of_line_comment: - _location = line.find(marker) - if _location != -1: - if location == -1: - location = _location - else: - location = min(_location, location) - if location != -1: - text = line[:location].strip() - comment = line[location:].strip() - else: - text = line - comment = '' - - return text, comment, is_comment - - ############################################## - - def append(self, line): - - text, comment, is_comment = self._split_comment(line) - - if text: - if not self._text.endswith(' ') or text.startswith(' '): - self._text += ' ' - self._text += text - if comment: - self._comment += ' // ' + comment - - _slice = self._line_range - self._line_range = slice(_slice.start, _slice.stop + 1) - - ############################################## - - def lower_case_statement(self, statement): - - """Lower case the statement""" - - # statement without . prefix - - if self._text: - lower_statement = statement.lower() - _slice = slice(1, len(statement) + 1) - _statement = self._text[_slice] - if _statement.lower() == lower_statement: - self._text = '.' + lower_statement + self._text[_slice.stop:] - - ############################################## - - def right_of(self, text): - return self._text[len(text):].strip() - - ############################################## - - def read_words(self, start_location, number_of_words): - - """Read a fixed number of words separated by space.""" - - words = [] - stop_location = None - - line_str = self._text - number_of_words_read = 0 - while number_of_words_read < number_of_words: # and start_location < len(line_str) - if line_str[start_location] == '{': - stop_location = line_str.find('}', start_location) - if stop_location > start_location: - stop_location += 1 - else: - stop_location = line_str.find(' ', start_location) - if stop_location == -1: - stop_location = None # read until end - word = line_str[start_location:stop_location].strip() - if word: - number_of_words_read += 1 - words.append(word) - if stop_location is None: # we should stop - if number_of_words_read != number_of_words: - template = 'Bad element line, looking for word {}/{}:' + os.linesep - message = (template.format(number_of_words_read, number_of_words) + - line_str + os.linesep + - ' ' * start_location + '^') - self._logger.warning(message) - raise ParseError(message) - else: - if start_location < stop_location: - start_location = stop_location - else: # we have read a space - start_location += 1 - - return words, stop_location - - ############################################## - - def split_words(self, start_location, until=None): - - stop_location = None - - line_str = self._text - if until is not None: - location = line_str.find(until, start_location) - if location != -1: - stop_location = location - location = line_str.rfind(' ', start_location, stop_location) - if location != -1: - stop_location = location - else: - raise NameError('Bad element line, missing key? ' + line_str) - - line_str = line_str[start_location:stop_location] - words = [x for x in line_str.split(' ') if x] - result = [] - expression = 0 - begin_idx = 0 - for idx, word in enumerate(words): - if expression == 0: - begin_idx = idx - expression += word.count('{') - word.count('}') - if expression == 0: - if begin_idx < idx: - result.append(' '.join(words[begin_idx:idx + 1])) - else: - result.append(word) - return result, stop_location - - ############################################## - - @staticmethod - def get_kwarg(text): - - dict_parameters = {} - - parts = [] - for part in text.split(): - if '=' in part and part != '=': - left, right = [x for x in part.split('=')] - parts.append(left) - parts.append('=') - if right: - parts.append(right) - else: - parts.append(part) - - i = 0 - i_stop = len(parts) - while i < i_stop: - if i + 1 < i_stop and parts[i + 1] == '=': - key, value = parts[i], parts[i + 2] - dict_parameters[key] = value - i += 3 - else: - raise ParseError("Bad kwarg: {}".format(text)) - - return dict_parameters - - ############################################## - - @staticmethod - def _partition(text): - parts = [] - values = text.replace(',', ' ') - for part in values.split(): - if '=' in part and part != '=': - left, right = [x for x in part.split('=')] - parts.append(left) - parts.append('=') - if right: - parts.append(right) - else: - parts.append(part) - return parts - - ############################################## - - @staticmethod - def _partition_parentheses(text): - p = regex.compile(r'\(([^\(\)]|(?R))*?\)') - parts = [] - previous_start = 0 - for m in regex.finditer(p, text): - parts.extend(Line._partition(text[previous_start:m.start()])) - parts.append(m.group()) - previous_start = m.end() - parts.extend(Line._partition(text[previous_start:])) - return parts - - ############################################## - - @staticmethod - def _partition_braces(text): - p = regex.compile(r'\{([^\{\}]|(?R))*?\}') - parts = [] - previous_start = 0 - for m in regex.finditer(p, text): - parts.extend(Line._partition_parentheses(text[previous_start:m.start()])) - parts.append(m.group()) - previous_start = m.end() - parts.extend(Line._partition_parentheses(text[previous_start:])) - return parts - - ############################################## - - @staticmethod - def _check_parameters(parts): - parameters = [] - dict_parameters = {} - - i = 0 - i_stop = len(parts) - while i < i_stop: - if i + 1 < i_stop and parts[i + 1] == '=': - key, value = parts[i], parts[i + 2] - dict_parameters[key] = value - i += 3 - else: - parameters.append(parts[i]) - i += 1 - - return parameters, dict_parameters - - ############################################## - - def split_keyword(self, keyword): - - """Split the line according to the following pattern:: - - keyword parameter1 parameter2 ( key1=value1 key2=value2 ) - - Return the list of parameters and the dictionary. - The parenthesis can be omitted. - - """ - - text = self.right_of(keyword) - - p = regex.compile(r'\(([^\(\)]|(?R))*?\)') - b = regex.compile(r'\{([^\{\}]|(?R))*?\}') - parts = [] - - mp = regex.search(p, text) - mb = regex.search(b, text) - if mb is not None: - if mp is not None: - if (mb.start() > mp.start()) and (mb.end() < mp.end()): - parts.extend(Line._partition(text[:mp.start()])) - parts.extend(Line._partition_braces(mp.group()[1:-1])) - elif (mb.start() < mp.start()) and (mb.end() > mp.end()): - parts.extend(Line._partition_braces(text)) - else: - raise ValueError("Incorrect format {}".format(text)) - else: - parts.extend(Line._partition_braces(text)) - else: - if mp is not None: - parts.extend(Line._partition(text[:mp.start()])) - parts.extend(Line._partition(mp.group()[1:-1])) - else: - parts.extend(Line._partition(text)) - return Line._check_parameters(parts) - - ############################################## - - def split_element(self, prefix): - - """Split the line according to the following pattern:: - - keyword parameter1 parameter2 ... key1=value1 key2=value2 ... - - Return the list of parameters and the dictionary. - - """ - - # Fixme: cf. get_kwarg - - parameters = [] - dict_parameters = {} - - text = self.right_of(prefix) - - parts = Line._partition_braces(text) - - return Line._check_parameters(parts) - -#################################################################################################### - -class SpiceParser: - - """This class parse a Spice netlist file and build a syntax tree. - - Public Attributes: - - :attr:`circuit` - - :attr:`models` - - :attr:`subcircuits` - - """ - - _logger = _module_logger.getChild('SpiceParser') - - ############################################## - - def __init__(self, path=None, source=None, end_of_line_comment=('$', '//', ';')): - - # Fixme: empty source - - if path is not None: - with open(str(path), 'r') as fh: - raw_lines = fh.readlines() # Fixme: cf. jmgc - elif source is not None: - raw_lines = source.split(os.linesep) - else: - raise ValueError - - self._end_of_line_comment = end_of_line_comment - - lines = self._merge_lines(raw_lines) - self._title = None - self._statements = self._parse(lines) - - ############################################## - - def _merge_lines(self, raw_lines): - - """Merge broken lines and return a new list of lines. - - A line starting with "+" continues the preceding line. - - """ - - lines = [] - current_line = None - for line_index, line_string in enumerate(raw_lines): - if line_string.startswith('+'): - current_line.append(line_string[1:].strip('\r\n')) - else: - line_string = line_string.strip(' \t\r\n') - if line_string: - _slice = slice(line_index, line_index + 1) - line = Line(line_string, _slice, self._end_of_line_comment) - lines.append(line) - # handle case with comment before line continuation - if not line_string.startswith('*'): - current_line = line - - return lines - - ############################################## - - @staticmethod - def _check_models(circuit, available_models=set()): - p_available_models = available_models.copy() - p_available_models.update([model.name for model in circuit._models]) - for subcircuit in circuit._subcircuits: - SpiceParser._check_models(subcircuit, p_available_models) - for model in circuit._required_models: - if model not in p_available_models: - raise ValueError("model (%s) not available in (%s)" % (model, circuit.name)) - - ############################################## - - @staticmethod - def _sort_subcircuits(circuit, available_subcircuits=set()): - p_available_subcircuits = available_subcircuits.copy() - names = [subcircuit.name for subcircuit in circuit._subcircuits] - p_available_subcircuits.update(names) - dependencies = dict() - for subcircuit in circuit._subcircuits: - required = SpiceParser._sort_subcircuits(subcircuit, p_available_subcircuits) - dependencies[subcircuit] = required - for subcircuit in circuit._required_subcircuits: - if subcircuit not in p_available_subcircuits: - raise ValueError("subcircuit (%s) not available in (%s)" % (subcircuit, circuit.name)) - items = sorted(dependencies.items(), key=lambda item: len(item[1])) - result = list() - result_names = list() - previous = len(items) + 1 - while 0 < len(items) < previous: - previous = len(items) - remove = list() - for item in items: - subckt, depends = item - for name in depends: - if name not in result_names: - break - else: - result.append(subckt) - result_names.append(subckt.name) - remove.append(item) - for item in remove: - items.remove(item) - if len(items) > 0: - raise ValueError("Crossed dependencies (%s)" % [(key.name, value) for key, value in items]) - circuit._subcircuits = result - return circuit._required_subcircuits - set(names) - - ############################################## - - def _parse(self, lines): - - """Parse the lines and return a list of statements.""" - - # The first line in the input file must be the title, which is the only comment line that does - # not need any special character in the first place. - # - # The last line must be .end - - if len(lines) <= 1: - raise NameError('Netlist is empty') - # if lines[-1] != '.end': - # raise NameError('".end" is expected at the end of the netlist') - - circuit = CircuitStatement(lines[0]) - stack = [] - scope = circuit - for line in lines[1:]: - # print('>', repr(line)) - text = str(line) - lower_case_text = text.lower() # ! - if line.is_comment: - scope.append(Comment(line)) - elif lower_case_text.startswith('.'): - lower_case_text = lower_case_text[1:] - if lower_case_text.startswith('subckt'): - stack.append(scope) - scope = SubCircuitStatement(line) - elif lower_case_text.startswith('ends'): - parent = stack.pop() - parent.append_subcircuit(scope) - scope = parent - elif lower_case_text.startswith('title'): - # override fist line - self._title = Title(line) - scope.append(self._title) - elif lower_case_text.startswith('end'): - pass - elif lower_case_text.startswith('model'): - model = Model(line) - scope.append_model(model) - elif lower_case_text.startswith('include'): - include = Include(line) - scope.append(include) - elif lower_case_text.startswith('param'): - param = Parameter(line) - scope.append_param(param) - else: - # options param ... - # .global - # .lib filename libname - # .func .csparam .temp .if - # { expr } are allowed in .model lines and in device lines. - self._logger.warn('Parser ignored: {}'.format(line)) - else: - try: - element = Element(line) - scope.append(element) - if hasattr(element, '_prefix') and (element._prefix == "X"): - name = element._parameters[0].lower() - scope._required_subcircuits.add(name) - elif hasattr(element, '_dict_parameters') and 'model' in element._dict_parameters: - name = element._dict_parameters['model'].lower() - scope._required_models.add(name) - except ParseError: - pass - SpiceParser._check_models(circuit) - SpiceParser._sort_subcircuits(circuit) - return circuit - - ############################################## - - @property - def circuit(self): - """Circuit statements.""" - return self._statements - - @property - def models(self): - """Models of the sub-circuit.""" - return self._statements.models - - @property - def subcircuits(self): - """Subcircuits of the sub-circuit.""" - return self._statements.subcircuits - - ############################################## - - def is_only_subcircuit(self): - return bool(not self.circuit and self.subcircuits) - - ############################################## - - def is_only_model(self): - return bool(not self.circuit and not self.subcircuits and self.models) - - ############################################## - - @staticmethod - def _build_circuit(circuit, statements, ground): - - for statement in statements: - if isinstance(statement, Include): - circuit.include(str(statement)) - - for statement in statements: - if isinstance(statement, Element): - statement.build(circuit, ground) - elif isinstance(statement, Model): - statement.build(circuit) - elif isinstance(statement, SubCircuit): - subcircuit = statement.build(ground) # Fixme: ok ??? - circuit.subcircuit(subcircuit) - - ############################################## - - def build_circuit(self, ground=0): - """Build a :class:`Circuit` instance. - - Use the *ground* parameter to specify the node which must be translated to 0 (SPICE ground node). - - """ - # circuit = Circuit(str(self._title)) - circuit = self.circuit.build(str(ground)) - return circuit - - ############################################## - - @staticmethod - def netlist_to_python(netlist_name, statements, ground=0): - - source_code = '' - for statement in statements: - if isinstance(statement, Element): - source_code += statement.to_python(netlist_name, ground) - elif isinstance(statement, Include): - pass - elif isinstance(statement, Model): - source_code += statement.to_python(netlist_name) - elif isinstance(statement, SubCircuitStatement): - source_code += statement.to_python(netlist_name) - elif isinstance(statement, Include): - source_code += statement.to_python(netlist_name) - return source_code - - ############################################## - - def to_python_code(self, ground=0): - - ground = str(ground) - - source_code = '' - - if self.circuit: - source_code += "circuit = Circuit('{}')".format(self._title) + os.linesep - source_code += self.netlist_to_python('circuit', self._statements, ground) - - return source_code diff --git a/PySpice/Spice/RawFile.py b/PySpice/Spice/RawFile.py index eb7d04c6e..afcd7759f 100644 --- a/PySpice/Spice/RawFile.py +++ b/PySpice/Spice/RawFile.py @@ -29,7 +29,7 @@ #################################################################################################### -from PySpice.Unit import u_Degree, u_V, u_A, u_s, u_Hz +from PySpice.Unit import u_c, u_v, u_a, u_s, u_hz #################################################################################################### @@ -191,9 +191,9 @@ def circuit(self): _name_to_unit = { 'time': u_s, - 'voltage': u_V, - 'current': u_A, - 'frequency': u_Hz, + 'voltage': u_v, + 'current': u_a, + 'frequency': u_hz, } ############################################## @@ -232,6 +232,8 @@ def _read_header_field_line(self, header_line_iterator, expected_label, has_valu """ line = self._read_line(header_line_iterator) + while not line.startswith(expected_label): + line = self._read_line(header_line_iterator) self._logger.debug(line) if has_value: # a title can have ': ' after 'title: ' @@ -258,8 +260,8 @@ def _read_temperature_line(self, header_line_iterator): if pos1 != -1 and pos2 != -1: part1 = line[pos1+len(pattern1):pos2] part2 = line[pos2+len(pattern2):].strip() - temperature = u_Degree(float(part1)) - nominal_temperature = u_Degree(float(part2)) + temperature = u_c(float(part1)) + nominal_temperature = u_c(float(part2)) else: temperature = None nominal_temperature = None @@ -294,7 +296,10 @@ def _read_variable_data(self, raw_data): else: raise NotImplementedError - input_data = np.fromstring(raw_data, count=number_of_columns*self.number_of_points, dtype='f8') + input_data = np.frombuffer(raw_data, count=number_of_columns*self.number_of_points, dtype='f8') + if len(input_data) != number_of_columns*self.number_of_points: + self.number_of_points = len(input_data)//number_of_columns + input_data = input_data[:number_of_columns*self.number_of_points] input_data = input_data.reshape((self.number_of_points, number_of_columns)) input_data = input_data.transpose() # np.savetxt('raw.txt', input_data) diff --git a/PySpice/Spice/Simulation.py b/PySpice/Spice/Simulation.py index 54488bc5e..97432f0e9 100644 --- a/PySpice/Spice/Simulation.py +++ b/PySpice/Spice/Simulation.py @@ -30,7 +30,7 @@ from ..Config import ConfigInstall from ..Tools.StringTools import join_list, join_dict, str_spice -from ..Unit import Unit, as_V, as_A, as_s, as_Hz, as_Degree, u_Degree +from ..Unit import Unit, as_v, as_a, as_s, as_Hz, as_c, u_c #################################################################################################### @@ -159,10 +159,16 @@ class DCAnalysisParameters(AnalysisParameters): def __init__(self, **kwargs): self._parameters = [] - for variable, value_slice in kwargs.items(): + for variable, value in kwargs.items(): variable_lower = variable.lower() + if variable_lower[0] in ('v', 'i', 'r') or variable_lower == 'temp': - self._parameters += [variable, value_slice.start, value_slice.stop, value_slice.step] + try: + iter(value) + self._parameters += [variable, 'LIST'] + [val for val in value] + except TypeError: + self._parameters += [variable, value.start, value.stop, value.step] + else: raise NameError('Sweep variable must be a voltage/current source, ' 'a resistor or the circuit temperature') @@ -238,13 +244,19 @@ class TransientAnalysisParameters(AnalysisParameters): ############################################## - def __init__(self, step_time, end_time, start_time=0, max_time=None, use_initial_condition=False): + def __init__(self, step_time, end_time, start_time=0, max_time=None, + use_initial_condition=False): + + if use_initial_condition: + uic = 'uic' + else: + uic = None self._step_time = as_s(step_time) self._end_time = as_s(end_time) self._start_time = as_s(start_time) self._max_time = as_s(max_time, none=True) - self._use_initial_condition = use_initial_condition + self._use_initial_condition = uic ############################################## @@ -276,7 +288,7 @@ def to_list(self): self._end_time, self._start_time, self._max_time, - 'uic' if self._use_initial_condition else None, + self._use_initial_condition, ) #################################################################################################### @@ -536,8 +548,8 @@ def __init__(self, circuit, **kwargs): self._saved_nodes = set() self._analyses = {} - self.temperature = kwargs.get('temperature', u_Degree(27)) - self.nominal_temperature = kwargs.get('nominal_temperature', u_Degree(27)) + self.temperature = kwargs.get('temperature', u_c(27)) + self.nominal_temperature = kwargs.get('nominal_temperature', u_c(27)) ############################################## @@ -561,7 +573,7 @@ def temperature(self): @temperature.setter def temperature(self, value): - self._options['TEMP'] = as_Degree(value) + self._options['TEMP'] = as_c(value) ############################################## @@ -571,7 +583,7 @@ def nominal_temperature(self): @nominal_temperature.setter def nominal_temperature(self, value): - self._options['TNOM'] = as_Degree(value) + self._options['TNOM'] = as_c(value) ############################################## @@ -664,7 +676,7 @@ def save(self, *args): """ - self._saved_nodes |= set(*args) + self._saved_nodes.update(args) ############################################## @@ -1085,24 +1097,35 @@ def str_options(self, unit=True): ############################################## + def save_str(self): + result = "" + if self._saved_nodes: + # Place 'all' first + saved_nodes = set(self._saved_nodes) + if 'all' in saved_nodes: + all_str = ' all' + saved_nodes.remove('all') + else: + all_str = '' + result += '.save' + all_str + if saved_nodes: + result += ' ' + join_list(saved_nodes) + result += os.linesep + return result + + ############################################## + def __str__(self): - netlist = self._circuit.str(simulator=self.SIMULATOR) + netlist = self._circuit.str(spice_sim=self.SIMULATOR) netlist += self.str_options() if self._initial_condition: netlist += '.ic ' + join_dict(self._initial_condition) + os.linesep if self._node_set: netlist += '.nodeset ' + join_dict(self._node_set) + os.linesep - if self._saved_nodes: - # Place 'all' first - saved_nodes = self._saved_nodes - if 'all' in saved_nodes: - all_str = 'all ' - saved_nodes.remove('all') - else: - all_str = '' - netlist += '.save ' + all_str + join_list(saved_nodes) + os.linesep + netlist += self.save_str() + for measure_parameters in self._measures: netlist += str(measure_parameters) + os.linesep for analysis_parameters in self._analyses.values(): diff --git a/PySpice/Spice/SpiceGrammar.py b/PySpice/Spice/SpiceGrammar.py new file mode 100644 index 000000000..da9f82bee --- /dev/null +++ b/PySpice/Spice/SpiceGrammar.py @@ -0,0 +1,5343 @@ +#!/usr/bin/env python + +# CAVEAT UTILITOR +# +# This file was automatically generated by TatSu. +# +# https://pypi.python.org/pypi/tatsu/ +# +# Any changes you make to it will be overwritten the next time +# the file is generated. + +from __future__ import annotations + +import sys + +from tatsu.buffering import Buffer +from tatsu.parsing import Parser +from tatsu.parsing import tatsumasu +from tatsu.parsing import leftrec, nomemo, isname # noqa +from tatsu.infos import ParserConfig +from tatsu.util import re, generic_main # noqa + + +KEYWORDS = {} # type: ignore + + +class SpiceBuffer(Buffer): + def __init__(self, text, /, config: ParserConfig = None, **settings): + config = ParserConfig.new( + config, + owner=self, + whitespace=None, + nameguard=None, + comments_re=None, + eol_comments_re=None, + ignorecase=True, + namechars='', + parseinfo=True, + ) + config = config.replace(**settings) + super().__init__(text, config=config) + + +class SpiceParser(Parser): + def __init__(self, /, config: ParserConfig = None, **settings): + config = ParserConfig.new( + config, + owner=self, + whitespace=None, + nameguard=None, + comments_re=None, + eol_comments_re=None, + ignorecase=True, + namechars='', + parseinfo=True, + keywords=KEYWORDS, + start='start', + ) + config = config.replace(**settings) + super().__init__(config=config) + + @tatsumasu('Circuit') + def _start_(self): # noqa + with self._optional(): + self._token('.TITLE') + + def block0(): + self._st_() + self._closure(block0) + with self._optional(): + self._asterisk_() + + def block1(): + self._st_() + self._closure(block1) + self._text_() + self.add_last_node_to_name('title') + + def block3(): + self._newline_() + self._asterisk_() + + def block4(): + self._st_() + self._closure(block4) + self._text_() + self.add_last_node_to_name('title') + + self._define( + [], + ['title'] + ) + self._closure(block3) + + def block6(): + + def block7(): + self._st_() + self._closure(block7) + with self._optional(): + with self._choice(): + with self._option(): + self._line_comment_() + with self._option(): + self._inline_comment_() + self._error( + 'expecting one of: ' + ' ' + ) + self._newline_() + self._closure(block6) + + def block9(): + self._st_() + self._closure(block9) + with self._optional(): + self._lines_() + self.name_last_node('lines') + with self._optional(): + self._token('.END') + self._end_sep_() + self._check_eof() + + self._define( + ['lines'], + ['title'] + ) + + @tatsumasu('Lines') + def _lines_(self): # noqa + + def block0(): + self._circuit_line_() + self.name_last_node('@') + self._closure(block0) + + @tatsumasu('CircuitLine') + def _circuit_line_(self): # noqa + with self._group(): + with self._choice(): + with self._option(): + self._device_() + self.name_last_node('@') + with self._option(): + self._command_() + self.name_last_node('@') + with self._option(): + self._encrypted_() + self._error( + 'expecting one of: ' + ' ' + ) + self._end_sep_() + self.name_last_node('@') + + @tatsumasu('NetlistLines') + def _netlist_lines_(self): # noqa + + def block0(): + self._netlist_line_() + self.name_last_node('@') + self._closure(block0) + + @tatsumasu('NetlistLine') + def _netlist_line_(self): # noqa + with self._group(): + with self._choice(): + with self._option(): + self._device_() + self.name_last_node('@') + with self._option(): + self._netlist_cmds_() + self.name_last_node('@') + with self._option(): + self._encrypted_() + self._error( + 'expecting one of: ' + ' ' + ) + self._end_sep_() + self.name_last_node('@') + + @tatsumasu() + def _encrypted_(self): # noqa + self._token('$CDNENCSTART') + self._cut() + with self._optional(): + self._id_() + + def block0(): + self._newline_() + self._positive_closure(block0) + + def block1(): + + def block2(): + self._pattern('[0-9a-f]') + self._closure(block2) + + def block3(): + self._newline_() + self._positive_closure(block3) + self._closure(block1) + self._token('$CDNENCFINISH') + self._cut() + with self._optional(): + self._id_() + + @tatsumasu() + def _device_(self): # noqa + with self._choice(): + with self._option(): + self._nonlinear_dependent_source_() + self.name_last_node('@') + with self._option(): + self._capacitor_() + self.name_last_node('@') + with self._option(): + self._diode_() + self.name_last_node('@') + with self._option(): + self._voltage_controlled_voltage_source_() + self.name_last_node('@') + with self._option(): + self._current_controlled_current_source_() + self.name_last_node('@') + with self._option(): + self._voltage_controlled_current_source_() + self.name_last_node('@') + with self._option(): + self._current_controlled_voltage_source_() + self.name_last_node('@') + with self._option(): + self._current_source_() + self.name_last_node('@') + with self._option(): + self._jfet_() + self.name_last_node('@') + with self._option(): + self._mutual_inductor_() + self.name_last_node('@') + with self._option(): + self._inductor_() + self.name_last_node('@') + with self._option(): + self._mosfet_() + self.name_last_node('@') + with self._option(): + self._bjt_() + self.name_last_node('@') + with self._option(): + self._resistor_() + self.name_last_node('@') + with self._option(): + self._subcircuit_() + self.name_last_node('@') + with self._option(): + self._switch_() + self.name_last_node('@') + with self._option(): + self._voltage_source_() + self.name_last_node('@') + self._error( + 'expecting one of: ' + "'B' 'C' 'D' 'E' 'F' 'G' 'H' 'I' 'J' 'K'" + "'L' 'M' 'Q' 'R' 'S' 'V' 'X' " + '' + '' + '' + ' ' + ' ' + ' ' + ' ' + '' + '' + '' + ) + + @tatsumasu('NonLinearDependentSource') + def _nonlinear_dependent_source_(self): # noqa + with self._if(): + self._token('B') + self._cut() + self._dev_() + self.name_last_node('dev') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('positive') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('negative') + self._cut() + self._sep_() + self.name_last_node('sep') + with self._group(): + with self._choice(): + with self._option(): + self._token('V') + with self._option(): + self._token('I') + self._error( + 'expecting one of: ' + "'I' 'V'" + ) + self.name_last_node('magnitude') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._token('=') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._abm_expression_() + self.name_last_node('expr') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._parameters_() + self.name_last_node('parameters') + + self._define( + ['parameters', 'sep'], + [] + ) + + self._define( + ['dev', 'expr', 'magnitude', 'negative', 'parameters', 'positive', 'sep'], + [] + ) + + @tatsumasu() + def _abm_expression_(self): # noqa + with self._choice(): + with self._option(): + self._lc_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._control_table_() + self.name_last_node('@') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rc_() + + self._define( + ['sep'], + [] + ) + with self._option(): + self._braced_expression_() + self.name_last_node('@') + with self._option(): + self._tablefile_() + self.name_last_node('@') + self._error( + 'expecting one of: ' + "'tablefile' '{' " + ) + + @tatsumasu('Capacitor') + def _capacitor_(self): # noqa + with self._if(): + self._token('C') + self._cut() + self._dev_() + self.name_last_node('dev') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('positive') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('negative') + self._cut() + with self._optional(): + with self._choice(): + with self._option(): + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('value') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._model_name_() + self.name_last_node('model') + + self._define( + ['model', 'sep'], + [] + ) + + self._define( + ['model', 'sep', 'value'], + [] + ) + with self._option(): + self._sep_() + self.name_last_node('sep') + self._model_name_() + self.name_last_node('model') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('value') + + self._define( + ['sep', 'value'], + [] + ) + + self._define( + ['model', 'sep', 'value'], + [] + ) + self._error( + 'expecting one of: ' + '' + ) + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._parameters_() + self.name_last_node('parameters') + + self._define( + ['parameters', 'sep'], + [] + ) + + self._define( + ['dev', 'model', 'negative', 'parameters', 'positive', 'sep', 'value'], + [] + ) + + @tatsumasu('Diode') + def _diode_(self): # noqa + with self._if(): + self._token('D') + self._cut() + self._dev_() + self.name_last_node('dev') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('positive') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('negative') + self._cut() + self._sep_() + self.name_last_node('sep') + self._model_name_() + self.name_last_node('model') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('area') + + self._define( + ['area', 'sep'], + [] + ) + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._parameters_() + self.name_last_node('parameters') + + self._define( + ['parameters', 'sep'], + [] + ) + + self._define( + ['area', 'dev', 'model', 'negative', 'parameters', 'positive', 'sep'], + [] + ) + + @tatsumasu('VoltageControlledVoltageSource') + def _voltage_controlled_voltage_source_(self): # noqa + with self._if(): + self._token('E') + self._cut() + self._dev_() + self.name_last_node('dev') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('positive') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('negative') + self._cut() + self._sep_() + self.name_last_node('sep') + with self._group(): + with self._choice(): + with self._option(): + with self._group(): + with self._group(): + with self._choice(): + with self._option(): + self._parenthesis_nodes_() + with self._option(): + self._circuit_nodes_() + self._error( + 'expecting one of: ' + ' ' + ) + self.name_last_node('nodes') + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('transconductance') + + self._define( + ['nodes', 'sep', 'transconductance'], + [] + ) + with self._option(): + with self._group(): + with self._group(): + with self._choice(): + with self._option(): + self._control_value_() + with self._option(): + self._control_table_() + with self._option(): + self._control_voltage_poly_() + self._error( + 'expecting one of: ' + ' ' + '' + ) + self.name_last_node('controller') + self._error( + 'expecting one of: ' + ' ' + ' ' + '' + ) + + self._define( + ['controller', 'dev', 'negative', 'nodes', 'positive', 'sep', 'transconductance'], + [] + ) + + @tatsumasu('CurrentControlledCurrentSource') + def _current_controlled_current_source_(self): # noqa + with self._if(): + self._token('F') + self._cut() + self._dev_() + self.name_last_node('dev') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('positive') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('negative') + self._cut() + self._sep_() + self.name_last_node('sep') + with self._group(): + with self._choice(): + with self._option(): + with self._group(): + self._control_current_poly_() + self.name_last_node('controller') + with self._option(): + with self._group(): + self._dev_() + self.name_last_node('device') + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('gain') + + self._define( + ['device', 'gain', 'sep'], + [] + ) + self._error( + 'expecting one of: ' + ' ' + ) + + self._define( + ['controller', 'dev', 'device', 'gain', 'negative', 'positive', 'sep'], + [] + ) + + @tatsumasu('VoltageControlledCurrentSource') + def _voltage_controlled_current_source_(self): # noqa + with self._if(): + self._token('G') + self._cut() + self._dev_() + self.name_last_node('dev') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('positive') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('negative') + self._cut() + self._sep_() + self.name_last_node('sep') + with self._group(): + with self._choice(): + with self._option(): + with self._group(): + with self._group(): + with self._choice(): + with self._option(): + self._parenthesis_nodes_() + with self._option(): + self._circuit_nodes_() + self._error( + 'expecting one of: ' + ' ' + ) + self.name_last_node('nodes') + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('transconductance') + + self._define( + ['nodes', 'sep', 'transconductance'], + [] + ) + with self._option(): + with self._group(): + with self._group(): + with self._choice(): + with self._option(): + self._control_value_() + with self._option(): + self._control_table_() + with self._option(): + self._control_voltage_poly_() + self._error( + 'expecting one of: ' + ' ' + '' + ) + self.name_last_node('controller') + self._error( + 'expecting one of: ' + ' ' + ' ' + '' + ) + + self._define( + ['controller', 'dev', 'negative', 'nodes', 'positive', 'sep', 'transconductance'], + [] + ) + + @tatsumasu('CurrentControlledVoltageSource') + def _current_controlled_voltage_source_(self): # noqa + with self._if(): + self._token('H') + self._cut() + self._dev_() + self.name_last_node('dev') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('positive') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('negative') + self._cut() + self._sep_() + self.name_last_node('sep') + with self._group(): + with self._choice(): + with self._option(): + with self._group(): + self._control_current_poly_() + self.name_last_node('controller') + with self._option(): + with self._group(): + self._dev_() + self.name_last_node('device') + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('transresistance') + + self._define( + ['device', 'sep', 'transresistance'], + [] + ) + self._error( + 'expecting one of: ' + ' ' + ) + + self._define( + ['controller', 'dev', 'device', 'negative', 'positive', 'sep', 'transresistance'], + [] + ) + + @tatsumasu('ControlValue') + def _control_value_(self): # noqa + self._token('VALUE') + self.name_last_node('type') + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._token('=') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._braced_expression_() + self.name_last_node('expression') + + self._define( + ['expression', 'sep', 'type'], + [] + ) + + @tatsumasu('ControlTable') + def _control_table_(self): # noqa + self._token('TABLE') + self.name_last_node('type') + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._braced_expression_() + self.name_last_node('expr') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._token('=') + with self._optional(): + self._sep_() + self.name_last_node('sep') + with self._group(): + with self._choice(): + with self._option(): + with self._group(): + self._lp_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._value_() + self.add_last_node_to_name('input') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._comma_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._value_() + self.add_last_node_to_name('output') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + def block12(): + + def block13(): + self._sep_() + self.name_last_node('sep') + self._positive_closure(block13) + self._lp_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._value_() + self.add_last_node_to_name('input') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._comma_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._value_() + self.add_last_node_to_name('output') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['sep'], + ['input', 'output'] + ) + self._closure(block12) + + self._define( + ['sep'], + ['input', 'output'] + ) + with self._option(): + with self._group(): + self._value_() + self.add_last_node_to_name('input') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._comma_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._value_() + self.add_last_node_to_name('output') + + def block25(): + + def block26(): + self._sep_() + self.name_last_node('sep') + self._positive_closure(block26) + self._value_() + self.add_last_node_to_name('input') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._comma_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._value_() + self.add_last_node_to_name('output') + + self._define( + ['sep'], + ['input', 'output'] + ) + self._closure(block25) + + self._define( + ['sep'], + ['input', 'output'] + ) + self._error( + 'expecting one of: ' + ' ' + ) + + self._define( + ['expr', 'sep', 'type'], + ['input', 'output'] + ) + + @tatsumasu('ControlVoltagePoly') + def _control_voltage_poly_(self): # noqa + self._token('POLY') + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._lp_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._integer_() + self.name_last_node('value') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + + def sep5(): + with self._group(): + self._sep_() + self.name_last_node('sep') + + def block5(): + with self._choice(): + with self._option(): + self._lp_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._node_() + self.add_last_node_to_name('positive') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._comma_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._node_() + self.add_last_node_to_name('negative') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['sep'], + ['negative', 'positive'] + ) + with self._option(): + self._node_() + self.add_last_node_to_name('positive') + self._sep_() + self.name_last_node('sep') + self._node_() + self.add_last_node_to_name('negative') + + self._define( + ['sep'], + ['negative', 'positive'] + ) + with self._option(): + self._value_() + self.name_last_node('coefficient') + self._error( + 'expecting one of: ' + ' ' + ) + self._join(block5, sep5) + + self._define( + ['coefficient', 'sep', 'value'], + ['negative', 'positive'] + ) + + @tatsumasu('ControlCurrentPoly') + def _control_current_poly_(self): # noqa + self._token('POLY') + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._lp_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._integer_() + self.name_last_node('value') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + + def sep5(): + with self._group(): + self._sep_() + self.name_last_node('sep') + + def block5(): + with self._choice(): + with self._option(): + self._dev_() + self.add_last_node_to_name('device') + with self._option(): + self._value_() + self.add_last_node_to_name('coefficient') + self._error( + 'expecting one of: ' + ' ' + ) + self._join(block5, sep5) + + self._define( + ['sep', 'value'], + ['coefficient', 'device'] + ) + + @tatsumasu('CurrentSource') + def _current_source_(self): # noqa + with self._if(): + self._token('I') + self._cut() + self._dev_() + self.name_last_node('dev') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('positive') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('negative') + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + with self._optional(): + self._dc_() + self._cut() + self._sep_() + self.name_last_node('sep') + + self._define( + ['sep'], + [] + ) + self._gen_expr_() + self.name_last_node('dc_value') + + self._define( + ['dc_value', 'sep'], + [] + ) + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._ac_() + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('ac_magnitude') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('ac_phase') + + self._define( + ['ac_phase', 'sep'], + [] + ) + + self._define( + ['ac_magnitude', 'ac_phase', 'sep'], + [] + ) + + self._define( + ['ac_magnitude', 'ac_phase', 'sep'], + [] + ) + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._transient_specification_() + self.name_last_node('transient') + + self._define( + ['sep', 'transient'], + [] + ) + + self._define( + ['ac_magnitude', 'ac_phase', 'dc_value', 'dev', 'negative', 'positive', 'sep', 'transient'], + [] + ) + + @tatsumasu('JFET') + def _jfet_(self): # noqa + with self._if(): + self._token('J') + self._cut() + self._dev_() + self.name_last_node('dev') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('drain') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('gate') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('source') + self._cut() + self._sep_() + self.name_last_node('sep') + self._model_name_() + self.name_last_node('model') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('area') + + self._define( + ['area', 'sep'], + [] + ) + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._parameters_() + self.name_last_node('parameters') + + self._define( + ['parameters', 'sep'], + [] + ) + + self._define( + ['area', 'dev', 'drain', 'gate', 'model', 'parameters', 'sep', 'source'], + [] + ) + + @tatsumasu('MutualInductor') + def _mutual_inductor_(self): # noqa + with self._if(): + self._token('K') + self._cut() + self._dev_() + self.name_last_node('dev') + + def block1(): + self._sep_() + self.name_last_node('sep') + with self._if(): + self._token('L') + self._dev_() + self.add_last_node_to_name('inductor') + + self._define( + ['sep'], + ['inductor'] + ) + self._positive_closure(block1) + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('value') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._model_name_() + self.name_last_node('model') + + self._define( + ['model', 'sep'], + [] + ) + + self._define( + ['dev', 'model', 'sep', 'value'], + ['inductor'] + ) + + @tatsumasu('Inductor') + def _inductor_(self): # noqa + with self._if(): + self._token('L') + self._cut() + self._dev_() + self.name_last_node('dev') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('positive') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('negative') + self._cut() + with self._optional(): + with self._choice(): + with self._option(): + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('value') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._model_name_() + self.name_last_node('model') + + self._define( + ['model', 'sep'], + [] + ) + + self._define( + ['model', 'sep', 'value'], + [] + ) + with self._option(): + self._sep_() + self.name_last_node('sep') + self._model_name_() + self.name_last_node('model') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('value') + + self._define( + ['sep', 'value'], + [] + ) + + self._define( + ['model', 'sep', 'value'], + [] + ) + self._error( + 'expecting one of: ' + '' + ) + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._parameters_() + self.name_last_node('parameters') + + self._define( + ['parameters', 'sep'], + [] + ) + + self._define( + ['dev', 'model', 'negative', 'parameters', 'positive', 'sep', 'value'], + [] + ) + + @tatsumasu('MOSFET') + def _mosfet_(self): # noqa + with self._if(): + self._token('M') + self._cut() + self._dev_() + self.name_last_node('dev') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('drain') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('gate') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('source') + self._cut() + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('bulk') + self._sep_() + self.name_last_node('sep') + self._model_name_() + self.name_last_node('model') + with self._optional(): + self._sep_() + self.name_last_node('sep') + + def sep12(): + with self._group(): + self._sep_() + self.name_last_node('sep') + + def block12(): + with self._group(): + with self._choice(): + with self._option(): + self._token('IC') + self.name_last_node('name') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._token('=') + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + + def sep19(): + with self._group(): + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._comma_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + + self._define( + ['sep'], + [] + ) + + def block19(): + self._value_() + self.name_last_node('value') + self._join(block19, sep19) + + self._define( + ['name', 'sep', 'value'], + [] + ) + with self._option(): + self._parameter_() + self.name_last_node('parameter') + self._error( + 'expecting one of: ' + "'IC' " + ) + self.name_last_node('param') + self._join(block12, sep12) + + self._define( + ['name', 'param', 'parameter', 'sep', 'value'], + [] + ) + + self._define( + ['bulk', 'dev', 'drain', 'gate', 'model', 'name', 'param', 'parameter', 'sep', 'source', 'value'], + [] + ) + + @tatsumasu('BJT') + def _bjt_(self): # noqa + with self._if(): + self._token('Q') + self._cut() + self._dev_() + self.name_last_node('dev') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('collector') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('base') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('emitter') + self._cut() + self._sep_() + self.name_last_node('sep') + with self._optional(): + self._substrate_node_() + self.name_last_node('substrate') + self._sep_() + self.name_last_node('sep') + + self._define( + ['sep', 'substrate'], + [] + ) + with self._optional(): + self._token('DT') + self.name_last_node('thermal') + self._sep_() + self.name_last_node('sep') + + self._define( + ['sep', 'thermal'], + [] + ) + self._model_name_() + self.name_last_node('model') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('area') + + self._define( + ['area', 'sep'], + [] + ) + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._parameters_() + self.name_last_node('parameters') + + self._define( + ['parameters', 'sep'], + [] + ) + + self._define( + ['area', 'base', 'collector', 'dev', 'emitter', 'model', 'parameters', 'sep', 'substrate', 'thermal'], + [] + ) + + @tatsumasu('SubstrateNode') + def _substrate_node_(self): # noqa + with self._group(): + with self._choice(): + with self._option(): + self._pattern('[0-9]+') + with self._option(): + with self._if(): + self._token('[') + self._cut() + self._node_() + self._error( + 'expecting one of: ' + "'[' [0-9]+" + ) + self.name_last_node('substrate') + + @tatsumasu('Resistor') + def _resistor_(self): # noqa + with self._if(): + self._token('R') + self._cut() + self._dev_() + self.name_last_node('dev') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('positive') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('negative') + self._cut() + with self._optional(): + with self._choice(): + with self._option(): + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('value') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._model_name_() + self.name_last_node('model') + + self._define( + ['model', 'sep'], + [] + ) + + self._define( + ['model', 'sep', 'value'], + [] + ) + with self._option(): + self._sep_() + self.name_last_node('sep') + self._model_name_() + self.name_last_node('model') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('value') + + self._define( + ['sep', 'value'], + [] + ) + + self._define( + ['model', 'sep', 'value'], + [] + ) + self._error( + 'expecting one of: ' + '' + ) + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._parameters_() + self.name_last_node('parameters') + + self._define( + ['parameters', 'sep'], + [] + ) + + self._define( + ['dev', 'model', 'negative', 'parameters', 'positive', 'sep', 'value'], + [] + ) + + @tatsumasu('Switch') + def _switch_(self): # noqa + with self._if(): + self._token('S') + self._cut() + self._dev_() + self.name_last_node('dev') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('positive') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('negative') + self._cut() + self._sep_() + self.name_last_node('sep') + with self._group(): + with self._choice(): + with self._option(): + self._model_name_() + self.name_last_node('model') + with self._optional(): + self._sep_() + self.name_last_node('sep') + with self._group(): + with self._choice(): + with self._option(): + self._token('ON') + with self._option(): + self._token('OFF') + self._error( + 'expecting one of: ' + "'OFF' 'ON'" + ) + self.name_last_node('initial_state') + + self._define( + ['initial_state', 'sep'], + [] + ) + self._sep_() + self.name_last_node('sep') + self._token('control') + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._token('=') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._braced_expression_() + + self._define( + ['initial_state', 'model', 'sep'], + [] + ) + with self._option(): + self._node_() + self.name_last_node('control_p') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('control_n') + self._sep_() + self.name_last_node('sep') + self._model_name_() + self.name_last_node('model') + with self._optional(): + self._sep_() + self.name_last_node('sep') + with self._group(): + with self._choice(): + with self._option(): + self._token('ON') + with self._option(): + self._token('OFF') + self._error( + 'expecting one of: ' + "'OFF' 'ON'" + ) + self.name_last_node('initial_state') + + self._define( + ['initial_state', 'sep'], + [] + ) + + self._define( + ['control_n', 'control_p', 'initial_state', 'model', 'sep'], + [] + ) + self._error( + 'expecting one of: ' + ' ' + ) + + self._define( + ['control_n', 'control_p', 'dev', 'initial_state', 'model', 'negative', 'positive', 'sep'], + [] + ) + + @tatsumasu('Subcircuit') + def _subcircuit_(self): # noqa + with self._if(): + self._token('X') + self._cut() + self._dev_() + self.name_last_node('dev') + + def block1(): + self._sep_() + self.name_last_node('sep') + self._node_() + self.add_last_node_to_name('node') + + self._define( + ['sep'], + ['node'] + ) + self._closure(block1) + with self._optional(): + self._token(':') + self.name_last_node('params') + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._parameters_() + self.name_last_node('parameters') + + self._define( + ['parameters', 'params', 'sep'], + [] + ) + + self._define( + ['dev', 'parameters', 'params', 'sep'], + ['node'] + ) + + @tatsumasu('VoltageSource') + def _voltage_source_(self): # noqa + with self._if(): + self._token('V') + self._cut() + self._dev_() + self.name_last_node('dev') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('positive') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('negative') + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + with self._optional(): + self._dc_() + self._cut() + self._sep_() + self.name_last_node('sep') + + self._define( + ['sep'], + [] + ) + self._gen_expr_() + self.name_last_node('dc_value') + + self._define( + ['dc_value', 'sep'], + [] + ) + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._ac_() + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('ac_magnitude') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('ac_phase') + + self._define( + ['ac_phase', 'sep'], + [] + ) + + self._define( + ['ac_magnitude', 'ac_phase', 'sep'], + [] + ) + + self._define( + ['ac_magnitude', 'ac_phase', 'sep'], + [] + ) + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._transient_specification_() + self.name_last_node('transient') + + self._define( + ['sep', 'transient'], + [] + ) + + self._define( + ['ac_magnitude', 'ac_phase', 'dc_value', 'dev', 'negative', 'positive', 'sep', 'transient'], + [] + ) + + @tatsumasu() + def _dc_(self): # noqa + self._token('DC') + + @tatsumasu() + def _ac_(self): # noqa + self._token('AC') + + @tatsumasu('TransientSpecification') + def _transient_specification_(self): # noqa + with self._choice(): + with self._option(): + self._transient_pulse_() + self.name_last_node('@') + with self._option(): + self._transient_sin_() + self.name_last_node('@') + with self._option(): + self._transient_exp_() + self.name_last_node('@') + with self._option(): + self._transient_pat_() + self.name_last_node('@') + with self._option(): + self._transient_pwl_() + self.name_last_node('@') + with self._option(): + self._transient_sffm_() + self.name_last_node('@') + self._error( + 'expecting one of: ' + "'EXP' 'PAT' 'PULSE' 'PWL' 'SFFM' 'SIN'" + ' ' + ' ' + ' ' + ) + + @tatsumasu('TransientPulse') + def _transient_pulse_(self): # noqa + self._token('PULSE') + self.name_last_node('type') + with self._group(): + with self._choice(): + with self._option(): + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._lp_() + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._pulse_arguments_() + self.name_last_node('@') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['sep'], + [] + ) + with self._option(): + self._sep_() + self.name_last_node('sep') + self._pulse_arguments_() + self.name_last_node('@') + + self._define( + ['sep'], + [] + ) + self._error( + 'expecting one of: ' + ' ' + ) + + self._define( + ['sep', 'type'], + [] + ) + + @tatsumasu('PulseArguments') + def _pulse_arguments_(self): # noqa + self._gen_expr_() + self.name_last_node('v1') + self._sep_() + self.name_last_node('sep') + + def sep2(): + with self._group(): + self._sep_() + self.name_last_node('sep') + + def block2(): + self._gen_expr_() + self.name_last_node('value') + self._join(block2, sep2) + + self._define( + ['sep', 'v1', 'value'], + [] + ) + + @tatsumasu('TransientSin') + def _transient_sin_(self): # noqa + self._token('SIN') + self.name_last_node('type') + with self._group(): + with self._choice(): + with self._option(): + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._lp_() + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._sin_arguments_() + self.name_last_node('@') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['sep'], + [] + ) + with self._option(): + self._sep_() + self.name_last_node('sep') + self._sin_arguments_() + self.name_last_node('@') + + self._define( + ['sep'], + [] + ) + self._error( + 'expecting one of: ' + ' ' + ) + + self._define( + ['sep', 'type'], + [] + ) + + @tatsumasu('SinArguments') + def _sin_arguments_(self): # noqa + self._gen_expr_() + self.name_last_node('v0') + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('va') + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('freq') + self._sep_() + self.name_last_node('sep') + + def sep6(): + with self._group(): + self._sep_() + self.name_last_node('sep') + + def block6(): + self._gen_expr_() + self.name_last_node('value') + self._join(block6, sep6) + + self._define( + ['freq', 'sep', 'v0', 'va', 'value'], + [] + ) + + @tatsumasu('TransientExp') + def _transient_exp_(self): # noqa + self._token('EXP') + self.name_last_node('type') + with self._group(): + with self._choice(): + with self._option(): + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._lp_() + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._exp_arguments_() + self.name_last_node('@') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['sep'], + [] + ) + with self._option(): + self._sep_() + self.name_last_node('sep') + self._exp_arguments_() + self.name_last_node('@') + + self._define( + ['sep'], + [] + ) + self._error( + 'expecting one of: ' + ' ' + ) + + self._define( + ['sep', 'type'], + [] + ) + + @tatsumasu('ExpArguments') + def _exp_arguments_(self): # noqa + self._gen_expr_() + self.name_last_node('v1') + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('v2') + self._sep_() + self.name_last_node('sep') + + def sep4(): + with self._group(): + self._sep_() + self.name_last_node('sep') + + def block4(): + self._gen_expr_() + self.name_last_node('value') + self._join(block4, sep4) + + self._define( + ['sep', 'v1', 'v2', 'value'], + [] + ) + + @tatsumasu('TransientPat') + def _transient_pat_(self): # noqa + self._token('PAT') + self.name_last_node('type') + with self._group(): + with self._choice(): + with self._option(): + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._lp_() + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._pat_arguments_() + self.name_last_node('@') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['sep'], + [] + ) + with self._option(): + self._sep_() + self.name_last_node('sep') + self._pat_arguments_() + self.name_last_node('@') + + self._define( + ['sep'], + [] + ) + self._error( + 'expecting one of: ' + ' ' + ) + + self._define( + ['sep', 'type'], + [] + ) + + @tatsumasu('PatArguments') + def _pat_arguments_(self): # noqa + self._gen_expr_() + self.name_last_node('vhi') + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('vlo') + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('td') + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('tr') + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('tf') + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('tsample') + self._sep_() + self.name_last_node('sep') + self._binary_pattern_() + self.name_last_node('data') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._binary_() + self.name_last_node('repeat') + + self._define( + ['repeat', 'sep'], + [] + ) + + self._define( + ['data', 'repeat', 'sep', 'td', 'tf', 'tr', 'tsample', 'vhi', 'vlo'], + [] + ) + + @tatsumasu('TransientPWL') + def _transient_pwl_(self): # noqa + self._token('PWL') + self.name_last_node('type') + self._cut() + with self._group(): + with self._choice(): + with self._option(): + self._pwl_file_arguments_() + self.name_last_node('@') + with self._option(): + self._pwl_arguments_() + self.name_last_node('@') + self._error( + 'expecting one of: ' + ' ' + ) + + self._define( + ['type'], + [] + ) + + @tatsumasu('PWLFileArguments') + def _pwl_file_arguments_(self): # noqa + self._sep_() + self.name_last_node('sep') + self._token('FILE') + self._cut() + self._sep_() + self.name_last_node('sep') + with self._group(): + with self._choice(): + with self._option(): + self._double_quote_() + self._filename_() + self.name_last_node('filename') + self._double_quote_() + + self._define( + ['filename'], + [] + ) + with self._option(): + self._filename_() + self.name_last_node('filename') + self._error( + 'expecting one of: ' + ' ' + ) + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._parameters_() + self.name_last_node('parameters') + + self._define( + ['parameters', 'sep'], + [] + ) + + self._define( + ['filename', 'parameters', 'sep'], + [] + ) + + @tatsumasu('PWLArguments') + def _pwl_arguments_(self): # noqa + with self._group(): + with self._choice(): + with self._option(): + + def block1(): + self._sep_() + self.name_last_node('sep') + self._closure(block1) + self._lp_() + self._cut() + + def block3(): + self._sep_() + self.name_last_node('sep') + self._closure(block3) + self._value_() + self.name_last_node('t') + self._sep_() + self.name_last_node('sep') + self._value_() + self.name_last_node('value') + + def block8(): + self._sep_() + self.name_last_node('sep') + self._value_() + self.name_last_node('t') + self._sep_() + self.name_last_node('sep') + self._value_() + self.name_last_node('value') + + self._define( + ['sep', 't', 'value'], + [] + ) + self._closure(block8) + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._parameters_() + self.name_last_node('parameters') + + self._define( + ['parameters', 'sep'], + [] + ) + + def block15(): + self._sep_() + self.name_last_node('sep') + self._closure(block15) + self._rp_() + + self._define( + ['parameters', 'sep', 't', 'value'], + [] + ) + with self._option(): + + def block17(): + self._sep_() + self.name_last_node('sep') + self._value_() + self.name_last_node('t') + self._sep_() + self.name_last_node('sep') + self._value_() + self.name_last_node('value') + + self._define( + ['sep', 't', 'value'], + [] + ) + self._positive_closure(block17) + self._error( + 'expecting one of: ' + ' ' + ) + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._parameters_() + self.name_last_node('parameters') + + self._define( + ['parameters', 'sep'], + [] + ) + + self._define( + ['parameters', 'sep', 't', 'value'], + [] + ) + + @tatsumasu('TransientSFFM') + def _transient_sffm_(self): # noqa + self._token('SFFM') + self.name_last_node('type') + with self._group(): + with self._choice(): + with self._option(): + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._lp_() + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._sffm_arguments_() + self.name_last_node('@') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['sep'], + [] + ) + with self._option(): + self._sep_() + self.name_last_node('sep') + self._sffm_arguments_() + self.name_last_node('@') + + self._define( + ['sep'], + [] + ) + self._error( + 'expecting one of: ' + ' ' + ) + + self._define( + ['sep', 'type'], + [] + ) + + @tatsumasu('SFFMArguments') + def _sffm_arguments_(self): # noqa + self._gen_expr_() + self.name_last_node('v0') + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('va') + self._sep_() + self.name_last_node('sep') + + def sep4(): + with self._group(): + self._sep_() + self.name_last_node('sep') + + def block4(): + self._gen_expr_() + self.name_last_node('value') + self._join(block4, sep4) + + self._define( + ['sep', 'v0', 'va', 'value'], + [] + ) + + @tatsumasu('Command') + def _command_(self): # noqa + with self._choice(): + with self._option(): + self._embedded_sampling_cmd_() + self.name_last_node('@') + with self._option(): + self._include_cmd_() + self.name_last_node('@') + with self._option(): + self._lib_cmd_() + self.name_last_node('@') + with self._option(): + self._netlist_cmds_() + self.name_last_node('@') + with self._option(): + self._subckt_cmd_() + self.name_last_node('@') + with self._option(): + self._simulator_cmd_() + self.name_last_node('@') + with self._option(): + self._title_cmd_() + self.name_last_node('@') + with self._option(): + self._ac_cmd_() + self.name_last_node('@') + with self._option(): + self._dc_cmd_() + self.name_last_node('@') + self._error( + 'expecting one of: ' + "'.AC' '.DC' '.EMBEDDEDSAMPLING' '.INC'" + "'.INCL' '.INCLUDE' '.LIB' '.SIMULATOR'" + "'.SUBCKT' '.TITLE' " + ' ' + ' ' + ' ' + ' ' + ) + + @tatsumasu('NetlistCmds') + def _netlist_cmds_(self): # noqa + with self._choice(): + with self._option(): + self._data_cmd_() + self.name_last_node('@') + with self._option(): + self._ic_cmd_() + self.name_last_node('@') + with self._option(): + self._model_cmd_() + self.name_last_node('@') + with self._option(): + self._param_cmd_() + self.name_last_node('@') + with self._option(): + self._subckt_cmd_() + self.name_last_node('@') + self._error( + 'expecting one of: ' + "'.DATA' '.DCVOLT' '.IC' '.MODEL'" + "'.PARAM' '.SUBCKT' " + ' ' + ) + + @tatsumasu('ACCmd') + def _ac_cmd_(self): # noqa + self._token('.AC') + self.name_last_node('cmd') + self._cut() + self._sep_() + self.name_last_node('sep') + with self._group(): + with self._choice(): + with self._option(): + with self._group(): + self._ac_sweep_type_() + self.name_last_node('sweep') + self._sep_() + self.name_last_node('sep') + self._integer_() + self.name_last_node('points') + self._sep_() + self.name_last_node('sep') + self._value_() + self.name_last_node('start') + self._sep_() + self.name_last_node('sep') + self._value_() + self.name_last_node('end') + + self._define( + ['end', 'points', 'sep', 'start', 'sweep'], + [] + ) + with self._option(): + with self._group(): + self._token('DATA') + self.name_last_node('sweep') + + def block11(): + self._st_() + self._closure(block11) + self._token('=') + + def block12(): + self._st_() + self._closure(block12) + self._id_() + self.name_last_node('table') + + self._define( + ['sweep', 'table'], + [] + ) + self._error( + 'expecting one of: ' + "'DATA' " + ) + + self._define( + ['cmd', 'end', 'points', 'sep', 'start', 'sweep', 'table'], + [] + ) + + @tatsumasu() + def _ac_sweep_type_(self): # noqa + with self._choice(): + with self._option(): + self._token('LIN') + with self._option(): + self._token('OCT') + with self._option(): + self._token('DEC') + self._error( + 'expecting one of: ' + "'DEC' 'LIN' 'OCT'" + ) + + @tatsumasu('DataCmd') + def _data_cmd_(self): # noqa + self._token('.DATA') + self.name_last_node('cmd') + self._cut() + self._sep_() + self.name_last_node('sep') + self._id_() + self.name_last_node('table') + + def block3(): + self._sep_() + self.name_last_node('sep') + self._id_() + self.name_last_node('name') + + self._define( + ['name', 'sep'], + [] + ) + self._positive_closure(block3) + + def block6(): + self._sep_() + self.name_last_node('sep') + self._value_() + self.name_last_node('value') + + self._define( + ['sep', 'value'], + [] + ) + self._positive_closure(block6) + self._end_sep_() + self.name_last_node('sep') + self._token('.ENDDATA') + + self._define( + ['cmd', 'name', 'sep', 'table', 'value'], + [] + ) + + @tatsumasu('DCCmd') + def _dc_cmd_(self): # noqa + self._token('.DC') + self.name_last_node('cmd') + with self._group(): + with self._choice(): + with self._option(): + with self._group(): + self._sep_() + self.name_last_node('sep') + self._token('DATA') + self.name_last_node('sweep') + + def block4(): + self._st_() + self._closure(block4) + self._token('=') + + def block5(): + self._st_() + self._closure(block5) + self._id_() + self.name_last_node('table') + + self._define( + ['sep', 'sweep', 'table'], + [] + ) + with self._option(): + with self._group(): + + def block7(): + with self._choice(): + with self._option(): + with self._group(): + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._token('LIN') + self.name_last_node('sweep') + + self._define( + ['sep', 'sweep'], + [] + ) + self._sep_() + self.name_last_node('sep') + self._id_() + self.name_last_node('name') + self._sep_() + self.name_last_node('sep') + self._value_() + self.name_last_node('start') + self._sep_() + self.name_last_node('sep') + self._value_() + self.name_last_node('stop') + self._sep_() + self.name_last_node('sep') + self._value_() + self.name_last_node('step') + + self._define( + ['name', 'sep', 'start', 'step', 'stop', 'sweep'], + [] + ) + with self._option(): + with self._group(): + self._sep_() + self.name_last_node('sep') + with self._group(): + with self._choice(): + with self._option(): + self._token('DEC') + with self._option(): + self._token('OCT') + self._error( + 'expecting one of: ' + "'DEC' 'OCT'" + ) + self.name_last_node('sweep') + self._sep_() + self.name_last_node('sep') + self._id_() + self.name_last_node('name') + self._sep_() + self.name_last_node('sep') + self._value_() + self.name_last_node('start') + self._sep_() + self.name_last_node('sep') + self._value_() + self.name_last_node('stop') + self._sep_() + self.name_last_node('sep') + self._integer_() + self.name_last_node('points') + + self._define( + ['name', 'points', 'sep', 'start', 'stop', 'sweep'], + [] + ) + with self._option(): + with self._group(): + self._sep_() + self.name_last_node('sep') + self._id_() + self.name_last_node('name') + self._sep_() + self.name_last_node('sep') + self._token('LIST') + self.name_last_node('sweep') + self._sep_() + self.name_last_node('sep') + + def sep35(): + with self._group(): + self._sep_() + self.name_last_node('sep') + + def block35(): + self._value_() + self.name_last_node('point') + self._positive_join(block35, sep35) + + self._define( + ['name', 'point', 'sep', 'sweep'], + [] + ) + self._error( + 'expecting one of: ' + '' + ) + self._positive_closure(block7) + self._error( + 'expecting one of: ' + '' + ) + + self._define( + ['cmd', 'name', 'point', 'points', 'sep', 'start', 'step', 'stop', 'sweep', 'table'], + [] + ) + + @tatsumasu('EmbeddedSamplingCmd') + def _embedded_sampling_cmd_(self): # noqa + self._token('.EMBEDDEDSAMPLING') + self.name_last_node('cmd') + self._cut() + with self._group(): + with self._choice(): + with self._option(): + with self._group(): + self._sep_() + self.name_last_node('sep') + self._token('param') + self.name_last_node('parameter') + + def block4(): + self._st_() + self._closure(block4) + self._token('=') + + def block5(): + self._st_() + self._closure(block5) + + def sep6(): + with self._group(): + self._es_sep_() + + def block6(): + self._id_() + self.name_last_node('name') + self._positive_join(block6, sep6) + self._sep_() + self.name_last_node('sep') + self._token('type') + self.name_last_node('parameter') + + def block10(): + self._st_() + self._closure(block10) + self._token('=') + + def block11(): + self._st_() + self._closure(block11) + + def sep12(): + with self._group(): + self._es_sep_() + + def block12(): + self._es_parameter_type_() + self.name_last_node('type') + self._positive_join(block12, sep12) + + def block14(): + self._sep_() + self.name_last_node('sep') + self._es_parameter_name_() + self.name_last_node('parameter') + + def block17(): + self._st_() + self._closure(block17) + self._token('=') + + def block18(): + self._st_() + self._closure(block18) + + def sep19(): + with self._group(): + self._es_sep_() + + def block19(): + self._gen_expr_() + self.name_last_node('value') + self._positive_join(block19, sep19) + + self._define( + ['parameter', 'sep', 'value'], + [] + ) + self._closure(block14) + + self._define( + ['name', 'parameter', 'sep', 'type', 'value'], + [] + ) + with self._option(): + with self._group(): + self._sep_() + self.name_last_node('sep') + self._token('useExpr') + self.name_last_node('parameter') + + def block23(): + self._st_() + self._closure(block23) + self._token('=') + self._cut() + + def block24(): + self._st_() + self._closure(block24) + self._boolean_() + self.name_last_node('value') + + self._define( + ['parameter', 'sep', 'value'], + [] + ) + self._error( + 'expecting one of: ' + '' + ) + + self._define( + ['cmd', 'name', 'parameter', 'sep', 'type', 'value'], + [] + ) + + @tatsumasu() + def _es_parameter_type_(self): # noqa + with self._choice(): + with self._option(): + self._token('UNIFORM') + with self._option(): + self._token('NORMAL') + with self._option(): + self._token('GAMMA') + self._error( + 'expecting one of: ' + "'GAMMA' 'NORMAL' 'UNIFORM'" + ) + + @tatsumasu() + def _es_parameter_name_(self): # noqa + with self._choice(): + with self._option(): + self._token('alpha') + with self._option(): + self._token('beta') + with self._option(): + self._token('means') + with self._option(): + self._token('std_deviations') + with self._option(): + self._token('lower_bounds') + with self._option(): + self._token('upper_bounds') + self._error( + 'expecting one of: ' + "'alpha' 'beta' 'lower_bounds' 'means'" + "'std_deviations' 'upper_bounds'" + ) + + @tatsumasu() + def _es_sep_(self): # noqa + self._comma_() + self._cut() + + def block0(): + self._st_() + self._closure(block0) + + @tatsumasu('ICCmd') + def _ic_cmd_(self): # noqa + with self._group(): + with self._choice(): + with self._option(): + self._token('.IC') + with self._option(): + self._token('.DCVOLT') + self._error( + 'expecting one of: ' + "'.DCVOLT' '.IC'" + ) + self.name_last_node('cmd') + self._cut() + with self._group(): + with self._choice(): + with self._option(): + + def block3(): + self._sep_() + self.name_last_node('sep') + self._token('V') + self._lp_() + self._cut() + self._node_() + self.name_last_node('node') + self._rp_() + + def block6(): + self._st_() + self._closure(block6) + self._token('=') + + def block7(): + self._st_() + self._closure(block7) + self._gen_expr_() + self.name_last_node('value') + + self._define( + ['node', 'sep', 'value'], + [] + ) + self._positive_closure(block3) + with self._option(): + + def block9(): + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('node') + + def block12(): + self._st_() + self._positive_closure(block12) + self._gen_expr_() + self.name_last_node('value') + + self._define( + ['node', 'sep', 'value'], + [] + ) + self._positive_closure(block9) + self._error( + 'expecting one of: ' + '' + ) + + self._define( + ['cmd', 'node', 'sep', 'value'], + [] + ) + + @tatsumasu('IncludeCmd') + def _include_cmd_(self): # noqa + with self._group(): + with self._choice(): + with self._option(): + self._token('.INCLUDE') + with self._option(): + self._token('.INCL') + with self._option(): + self._token('.INC') + self._error( + 'expecting one of: ' + "'.INC' '.INCL' '.INCLUDE'" + ) + self.name_last_node('cmd') + self._cut() + self._sep_() + self.name_last_node('sep') + with self._group(): + with self._choice(): + with self._option(): + self._double_quote_() + self._cut() + self._filename_() + self.name_last_node('filename') + self._double_quote_() + + self._define( + ['filename'], + [] + ) + with self._option(): + self._single_quote_() + self._cut() + self._filename_() + self.name_last_node('filename') + self._single_quote_() + + self._define( + ['filename'], + [] + ) + with self._option(): + self._filename_() + self.name_last_node('filename') + self._error( + 'expecting one of: ' + ' ' + ) + + self._define( + ['cmd', 'filename', 'sep'], + [] + ) + + @tatsumasu('LibCmd') + def _lib_cmd_(self): # noqa + self._token('.LIB') + self.name_last_node('cmd') + self._cut() + self._sep_() + self.name_last_node('sep') + with self._group(): + with self._choice(): + with self._option(): + self._lib_call_() + self.name_last_node('call') + with self._option(): + self._lib_block_() + self.name_last_node('block') + self._error( + 'expecting one of: ' + ' ' + ) + + self._define( + ['block', 'call', 'cmd', 'sep'], + [] + ) + + @tatsumasu('LibCall') + def _lib_call_(self): # noqa + with self._group(): + with self._choice(): + with self._option(): + self._double_quote_() + self._cut() + self._filename_() + self.name_last_node('filename') + self._double_quote_() + + self._define( + ['filename'], + [] + ) + with self._option(): + self._single_quote_() + self._cut() + self._filename_() + self.name_last_node('filename') + self._single_quote_() + + self._define( + ['filename'], + [] + ) + with self._option(): + self._filename_() + self.name_last_node('filename') + self._error( + 'expecting one of: ' + ' ' + ) + self._sep_() + self.name_last_node('sep') + self._id_() + self.name_last_node('entry') + + self._define( + ['entry', 'filename', 'sep'], + [] + ) + + @tatsumasu('ModelCmd') + def _model_cmd_(self): # noqa + self._token('.MODEL') + self.name_last_node('cmd') + self._cut() + self._sep_() + self.name_last_node('sep') + self._model_name_() + self.name_last_node('name') + self._sep_() + self.name_last_node('sep') + self._model_type_() + self.name_last_node('type') + with self._optional(): + with self._choice(): + with self._option(): + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._lp_() + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._parameters_() + self.name_last_node('parameters') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['parameters', 'sep'], + [] + ) + with self._option(): + self._sep_() + self.name_last_node('sep') + self._parameters_() + self.name_last_node('parameters') + + self._define( + ['parameters', 'sep'], + [] + ) + self._error( + 'expecting one of: ' + ' ' + ) + + self._define( + ['cmd', 'name', 'parameters', 'sep', 'type'], + [] + ) + + @tatsumasu() + def _model_type_(self): # noqa + with self._choice(): + with self._option(): + self._token('CAP') + with self._option(): + self._token('CORE') + with self._option(): + self._token('C') + with self._option(): + self._token('DIG') + with self._option(): + self._token('D') + with self._option(): + self._token('IND') + with self._option(): + self._token('ISWITCH') + with self._option(): + self._token('LIN') + with self._option(): + self._token('LTRA') + with self._option(): + self._token('L') + with self._option(): + self._token('NJF') + with self._option(): + self._token('NMF') + with self._option(): + self._token('NMOS') + with self._option(): + self._token('NPN') + with self._option(): + self._token('PJF') + with self._option(): + self._token('PMF') + with self._option(): + self._token('PMOS') + with self._option(): + self._token('PNP') + with self._option(): + self._token('RES') + with self._option(): + self._token('R') + with self._option(): + self._token('SWITCH') + with self._option(): + self._token('TRANSLINE') + with self._option(): + self._token('VSWITCH') + with self._option(): + self._token('MEMRISTOR') + with self._option(): + self._token('ZOD') + self._error( + 'expecting one of: ' + "'C' 'CAP' 'CORE' 'D' 'DIG' 'IND'" + "'ISWITCH' 'L' 'LIN' 'LTRA' 'MEMRISTOR'" + "'NJF' 'NMF' 'NMOS' 'NPN' 'PJF' 'PMF'" + "'PMOS' 'PNP' 'R' 'RES' 'SWITCH'" + "'TRANSLINE' 'VSWITCH' 'ZOD'" + ) + + @tatsumasu('ParamCmd') + def _param_cmd_(self): # noqa + self._token('.PARAM') + self.name_last_node('cmd') + self._cut() + self._sep_() + self.name_last_node('sep') + self._parameters_() + self.name_last_node('parameters') + + self._define( + ['cmd', 'parameters', 'sep'], + [] + ) + + @tatsumasu('SimulatorCmd') + def _simulator_cmd_(self): # noqa + self._token('.SIMULATOR') + self.name_last_node('cmd') + self._cut() + self._sep_() + self.name_last_node('sep') + self._id_() + self.name_last_node('simulator') + + self._define( + ['cmd', 'sep', 'simulator'], + [] + ) + + @tatsumasu('SubcktCmd') + def _subckt_cmd_(self): # noqa + self._token('.SUBCKT') + self.name_last_node('cmd') + self._cut() + self._sep_() + self.name_last_node('sep') + self._model_name_() + self.name_last_node('name') + + def block3(): + self._sep_() + self.name_last_node('sep') + with self._group(): + self._node_() + with self._ifnot(): + self._token(':') + self.name_last_node('node') + + self._define( + ['node', 'sep'], + [] + ) + self._closure(block3) + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._token('params:') + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._parameters_() + self.name_last_node('parameters') + + self._define( + ['parameters', 'sep'], + [] + ) + self._cmd_net_sep_() + self.name_last_node('sep') + + def block10(): + self._st_() + self._closure(block10) + self._netlist_lines_() + self.name_last_node('lines') + + def block12(): + self._st_() + self._closure(block12) + self._token('.ENDS') + self._cut() + with self._optional(): + + def block13(): + self._st_() + self._positive_closure(block13) + self._model_name_() + self.name_last_node('name') + + self._define( + ['name'], + [] + ) + + self._define( + ['cmd', 'lines', 'name', 'node', 'parameters', 'sep'], + [] + ) + + @tatsumasu('LibBlock') + def _lib_block_(self): # noqa + self._id_() + self.name_last_node('entry') + self._cmd_net_sep_() + self.name_last_node('sep') + self._cut() + self._netlist_lines_() + self.name_last_node('lines') + + def block3(): + self._st_() + self._closure(block3) + self._token('.ENDL') + self._cut() + with self._optional(): + + def block4(): + self._st_() + self._positive_closure(block4) + self._id_() + self.name_last_node('entry') + + self._define( + ['entry'], + [] + ) + + self._define( + ['entry', 'lines', 'sep'], + [] + ) + + @tatsumasu('TitleCmd') + def _title_cmd_(self): # noqa + self._token('.TITLE') + self.name_last_node('cmd') + self._cut() + self._text_() + self.name_last_node('title') + + self._define( + ['cmd', 'title'], + [] + ) + + @tatsumasu('Parameters') + def _parameters_(self): # noqa + self._parameter_() + self.add_last_node_to_name('@') + + def block1(): + with self._group(): + with self._choice(): + with self._option(): + with self._group(): + with self._optional(): + self._sep_() + self._comma_() + with self._optional(): + self._sep_() + with self._option(): + self._sep_() + self._error( + 'expecting one of: ' + ' ' + ) + self._parameter_() + self.add_last_node_to_name('@') + self._closure(block1) + + @tatsumasu('Parameter') + def _parameter_(self): # noqa + self._id_() + self.name_last_node('name') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._token('=') + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('value') + + def block4(): + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._comma_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('value') + + self._define( + ['sep', 'value'], + [] + ) + self._closure(block4) + + self._define( + ['name', 'sep', 'value'], + [] + ) + + @tatsumasu('GenericExpression') + def _gen_expr_(self): # noqa + with self._choice(): + with self._option(): + self._braced_expression_() + self.name_last_node('braced') + with self._option(): + self._value_() + self.name_last_node('value') + self._error( + 'expecting one of: ' + ' ' + ' ' + ) + + @tatsumasu('TableFile') + def _tablefile_(self): # noqa + self._token('tablefile') + self.name_last_node('func') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._lp_() + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + with self._group(): + with self._choice(): + with self._option(): + self._double_quote_() + self._cut() + self._filename_() + self.name_last_node('filename') + self._double_quote_() + + self._define( + ['filename'], + [] + ) + with self._option(): + self._filename_() + self.name_last_node('filename') + self._error( + 'expecting one of: ' + ' ' + ) + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['filename', 'func', 'sep'], + [] + ) + + @tatsumasu('BracedExpression') + def _braced_expression_(self): # noqa + self._lc_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('@') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rc_() + + self._define( + ['sep'], + [] + ) + + @tatsumasu('ParenthesisNodes') + def _parenthesis_nodes_(self): # noqa + self._lp_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._circuit_nodes_() + self.name_last_node('@') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['sep'], + [] + ) + + @tatsumasu('CircuitNodes') + def _circuit_nodes_(self): # noqa + self._node_() + self.add_last_node_to_name('@') + with self._group(): + with self._choice(): + with self._option(): + self._sep_() + self.name_last_node('sep') + with self._option(): + with self._group(): + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._comma_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + + self._define( + ['sep'], + [] + ) + self._error( + 'expecting one of: ' + ' ' + ) + self._node_() + self.add_last_node_to_name('@') + + self._define( + ['sep'], + [] + ) + + @tatsumasu('Expression') + @leftrec + def _expression_(self): # noqa + with self._choice(): + with self._option(): + self._ternary_() + self.name_last_node('ternary') + with self._option(): + self._term_() + self.name_last_node('term') + self._error( + 'expecting one of: ' + ' ' + ' ' + ) + + @tatsumasu('Ternary') + @nomemo + def _ternary_(self): # noqa + self._conditional_expression_() + self.name_last_node('t') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._token('?') + self.name_last_node('op') + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('x') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._token(':') + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('y') + + self._define( + ['op', 'sep', 't', 'x', 'y'], + [] + ) + + @tatsumasu('Conditional') + @nomemo + def _conditional_expression_(self): # noqa + self._boolean_or_() + self.name_last_node('expr') + + @tatsumasu('Or') + @nomemo + def _boolean_or_(self): # noqa + self._boolean_xor_() + self.name_last_node('left') + with self._optional(): + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._token('|') + self.name_last_node('op') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._boolean_or_() + self.name_last_node('right') + + self._define( + ['op', 'right', 'sep'], + [] + ) + + self._define( + ['left', 'op', 'right', 'sep'], + [] + ) + + @tatsumasu('Xor') + @nomemo + def _boolean_xor_(self): # noqa + self._boolean_and_() + self.name_last_node('left') + with self._optional(): + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._token('^') + self.name_last_node('op') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._boolean_xor_() + self.name_last_node('right') + + self._define( + ['op', 'right', 'sep'], + [] + ) + + self._define( + ['left', 'op', 'right', 'sep'], + [] + ) + + @tatsumasu('And') + @nomemo + def _boolean_and_(self): # noqa + self._boolean_not_() + self.name_last_node('left') + with self._optional(): + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._token('&') + self.name_last_node('op') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._boolean_and_() + self.name_last_node('right') + + self._define( + ['op', 'right', 'sep'], + [] + ) + + self._define( + ['left', 'op', 'right', 'sep'], + [] + ) + + @tatsumasu('Not') + @nomemo + def _boolean_not_(self): # noqa + with self._optional(): + self._token('~') + self.name_last_node('op') + self._relational_() + self.name_last_node('operator') + + self._define( + ['op', 'operator'], + [] + ) + + @tatsumasu('Relational') + @nomemo + def _relational_(self): # noqa + with self._choice(): + with self._option(): + self._expression_() + self.name_last_node('left') + with self._optional(): + self._sep_() + self.name_last_node('sep') + with self._group(): + with self._choice(): + with self._option(): + self._token('==') + with self._option(): + self._token('!=') + with self._option(): + self._token('>=') + with self._option(): + self._token('<=') + with self._option(): + self._token('>') + with self._option(): + self._token('<') + self._error( + 'expecting one of: ' + "'!=' '<' '<=' '==' '>' '>='" + ) + self.name_last_node('op') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('right') + + self._define( + ['left', 'op', 'right', 'sep'], + [] + ) + with self._option(): + self._conditional_factor_() + self.name_last_node('factor') + self._error( + 'expecting one of: ' + ' ' + '' + ' ' + ' ' + ) + + @tatsumasu('ConditionalFactor') + def _conditional_factor_(self): # noqa + with self._choice(): + with self._option(): + self._lp_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._conditional_expression_() + self.name_last_node('expr') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['expr', 'sep'], + [] + ) + with self._option(): + self._boolean_() + self.name_last_node('boolean') + self._error( + 'expecting one of: ' + "'(' 'FALSE' 'TRUE' " + ) + + @tatsumasu('Term') + def _term_(self): # noqa + self._add_sub_() + self.name_last_node('@') + + @tatsumasu('AddSub') + def _add_sub_(self): # noqa + self._prod_() + self.name_last_node('left') + with self._optional(): + with self._optional(): + self._sep_() + self.name_last_node('sep') + with self._group(): + with self._choice(): + with self._option(): + self._token('+') + with self._option(): + self._token('-') + self._error( + 'expecting one of: ' + "'+' '-'" + ) + self.name_last_node('op') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._add_sub_() + self.name_last_node('right') + + self._define( + ['op', 'right', 'sep'], + [] + ) + + self._define( + ['left', 'op', 'right', 'sep'], + [] + ) + + @tatsumasu('ProdDivMod') + def _prod_(self): # noqa + self._unary_() + self.name_last_node('left') + with self._optional(): + with self._optional(): + self._sep_() + self.name_last_node('sep') + with self._group(): + with self._choice(): + with self._option(): + self._token('*') + with self._option(): + self._token('/') + with self._option(): + self._token('%') + self._error( + 'expecting one of: ' + "'%' '*' '/'" + ) + self.name_last_node('op') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._prod_() + self.name_last_node('right') + + self._define( + ['op', 'right', 'sep'], + [] + ) + + self._define( + ['left', 'op', 'right', 'sep'], + [] + ) + + @tatsumasu('Sign') + def _unary_(self): # noqa + with self._optional(): + with self._group(): + with self._choice(): + with self._option(): + self._token('+') + with self._option(): + self._token('-') + self._error( + 'expecting one of: ' + "'+' '-'" + ) + self.name_last_node('op') + self._exp_() + self.name_last_node('operator') + + self._define( + ['op', 'operator'], + [] + ) + + @tatsumasu('Exponential') + def _exp_(self): # noqa + self._functional_() + self.name_last_node('left') + with self._optional(): + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._token('**') + self.name_last_node('op') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._exp_() + self.name_last_node('right') + + self._define( + ['op', 'right', 'sep'], + [] + ) + + self._define( + ['left', 'op', 'right', 'sep'], + [] + ) + + @tatsumasu('Functional') + def _functional_(self): # noqa + with self._choice(): + with self._option(): + self._functions_() + self.name_last_node('@') + with self._option(): + self._variable_() + self.name_last_node('@') + self._error( + 'expecting one of: ' + ' ' + ' ' + ' ' + ' ' + ) + + @tatsumasu('Variable') + def _variable_(self): # noqa + with self._choice(): + with self._option(): + self._lc_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._var_id_() + self.name_last_node('variable') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rc_() + + self._define( + ['sep', 'variable'], + [] + ) + with self._option(): + self._var_id_() + self.name_last_node('variable') + with self._option(): + self._factor_() + self.name_last_node('factor') + self._error( + 'expecting one of: ' + "'{' " + '[a-zA-Z] [a-zA-Z_`@#\\$][a-zA-Z0-' + '9_:`@#\\.\\$]*[a-zA-Z0-9_`@#\\.\\$]' + ) + + @tatsumasu('Factor') + def _factor_(self): # noqa + with self._choice(): + with self._option(): + self._lp_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('@') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['sep'], + [] + ) + with self._option(): + self._value_() + self.name_last_node('@') + self._error( + 'expecting one of: ' + "'(' " + '' + ) + + @tatsumasu('Functions') + def _functions_(self): # noqa + with self._choice(): + with self._option(): + self._functions_1_() + with self._option(): + self._atan2_() + with self._option(): + self._ddx_() + with self._option(): + self._gauss_() + with self._option(): + self._if_func_() + with self._option(): + self._limit_() + with self._option(): + self._functions_2_() + with self._option(): + self._rand_() + with self._option(): + self._unif_() + with self._option(): + self._i_func_() + with self._option(): + self._v_func_() + self._error( + 'expecting one of: ' + "'Img' 'Ph' 'R' 'Re' 'abs' 'acos' 'acosh'" + "'agauss' 'arctan' 'asin' 'asinh' 'atan'" + "'atan2' 'atanh' 'aunif' 'ceil' 'cos'" + "'cosh' 'ddt' 'ddx' 'exp' 'floor' 'gauss'" + "'i' 'if' 'int' 'limit' 'ln' 'log'" + "'log10' 'm' 'max' 'min' 'nint' 'pow'" + "'pwr' 'pwrs' 'rand' 'sdt' 'sgn' 'sign'" + "'sin' 'sinh' 'sqrt' 'stp' 'tan' 'tanh'" + "'unif' 'uramp' 'v' " + ' ' + '' + ) + + @tatsumasu() + def _functions_1_(self): # noqa + with self._group(): + with self._choice(): + with self._option(): + self._token('abs') + with self._option(): + self._token('ceil') + with self._option(): + self._token('ddt') + with self._option(): + self._token('floor') + with self._option(): + self._token('int') + with self._option(): + self._token('m') + with self._option(): + self._token('nint') + with self._option(): + self._token('sdt') + with self._option(): + self._token('sgn') + with self._option(): + self._token('stp') + with self._option(): + self._token('sqrt') + with self._option(): + self._token('uramp') + with self._option(): + self._token('Ph') + with self._option(): + self._token('Re') + with self._option(): + self._token('R') + with self._option(): + self._token('Img') + with self._option(): + self._token('acosh') + with self._option(): + self._token('acos') + with self._option(): + self._token('asinh') + with self._option(): + self._token('asin') + with self._option(): + self._token('arctan') + with self._option(): + self._token('atanh') + with self._option(): + self._token('atan') + with self._option(): + self._token('cosh') + with self._option(): + self._token('cos') + with self._option(): + self._token('exp') + with self._option(): + self._token('ln') + with self._option(): + self._token('log') + with self._option(): + self._token('log10') + with self._option(): + self._token('sinh') + with self._option(): + self._token('sin') + with self._option(): + self._token('tanh') + with self._option(): + self._token('tan') + self._error( + 'expecting one of: ' + "'Img' 'Ph' 'R' 'Re' 'abs' 'acos' 'acosh'" + "'arctan' 'asin' 'asinh' 'atan' 'atanh'" + "'ceil' 'cos' 'cosh' 'ddt' 'exp' 'floor'" + "'int' 'ln' 'log' 'log10' 'm' 'nint'" + "'sdt' 'sgn' 'sin' 'sinh' 'sqrt' 'stp'" + "'tan' 'tanh' 'uramp'" + ) + self.name_last_node('func') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._lp_() + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('x') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['func', 'sep', 'x'], + [] + ) + + @tatsumasu() + def _atan2_(self): # noqa + self._token('atan2') + self.name_last_node('func') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._lp_() + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('y') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._comma_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('x') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['func', 'sep', 'x', 'y'], + [] + ) + + @tatsumasu() + def _ddx_(self): # noqa + self._token('ddx') + self.name_last_node('func') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._lp_() + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._id_() + self.name_last_node('f') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._comma_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('x') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['f', 'func', 'sep', 'x'], + [] + ) + + @tatsumasu() + def _gauss_(self): # noqa + with self._group(): + with self._choice(): + with self._option(): + self._token('agauss') + with self._option(): + self._token('gauss') + self._error( + 'expecting one of: ' + "'agauss' 'gauss'" + ) + self.name_last_node('func') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._lp_() + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('mu') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._comma_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('alpha') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._comma_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('n') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['alpha', 'func', 'mu', 'n', 'sep'], + [] + ) + + @tatsumasu() + def _i_func_(self): # noqa + self._token('i') + self.name_last_node('func') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._lp_() + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + with self._if(): + self._token('V') + self._dev_() + self.name_last_node('device') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['device', 'func', 'sep'], + [] + ) + + @tatsumasu() + def _if_func_(self): # noqa + self._token('if') + self.name_last_node('func') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._lp_() + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._conditional_expression_() + self.name_last_node('t') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._comma_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('x') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._comma_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('y') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['func', 'sep', 't', 'x', 'y'], + [] + ) + + @tatsumasu() + def _limit_(self): # noqa + self._token('limit') + self.name_last_node('func') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._lp_() + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('x') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._comma_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('y') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._comma_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('z') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['func', 'sep', 'x', 'y', 'z'], + [] + ) + + @tatsumasu() + def _functions_2_(self): # noqa + with self._group(): + with self._choice(): + with self._option(): + self._token('min') + with self._option(): + self._token('max') + with self._option(): + self._token('pwrs') + with self._option(): + self._token('pow') + with self._option(): + self._token('pwr') + with self._option(): + self._token('sign') + self._error( + 'expecting one of: ' + "'max' 'min' 'pow' 'pwr' 'pwrs' 'sign'" + ) + self.name_last_node('func') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._lp_() + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('x') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._comma_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('y') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['func', 'sep', 'x', 'y'], + [] + ) + + @tatsumasu() + def _rand_(self): # noqa + self._token('rand') + self.name_last_node('func') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._lp_() + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['func', 'sep'], + [] + ) + + @tatsumasu() + def _unif_(self): # noqa + with self._group(): + with self._choice(): + with self._option(): + self._token('aunif') + with self._option(): + self._token('unif') + self._error( + 'expecting one of: ' + "'aunif' 'unif'" + ) + self.name_last_node('func') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._lp_() + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('mu') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._comma_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._expression_() + self.name_last_node('alpha') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._rp_() + + self._define( + ['alpha', 'func', 'mu', 'sep'], + [] + ) + + @tatsumasu() + def _v_func_(self): # noqa + self._token('v') + self.name_last_node('func') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._lp_() + self._cut() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('node') + with self._optional(): + self._sep_() + self.name_last_node('sep') + with self._optional(): + self._comma_() + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('node') + with self._optional(): + self._sep_() + self.name_last_node('sep') + + self._define( + ['node', 'sep'], + [] + ) + self._rp_() + + self._define( + ['func', 'node', 'sep'], + [] + ) + + @tatsumasu() + def _special_variables_(self): # noqa + with self._choice(): + with self._option(): + self._token('time') + with self._option(): + self._token('temper') + with self._option(): + self._token('temp') + with self._option(): + self._token('freq') + with self._option(): + self._token('vt') + with self._option(): + self._token('pi') + self._error( + 'expecting one of: ' + "'freq' 'pi' 'temp' 'temper' 'time' 'vt'" + ) + + @tatsumasu('Value') + def _value_(self): # noqa + with self._group(): + with self._choice(): + with self._option(): + with self._group(): + self._real_value_() + self.name_last_node('real') + self._token('+') + self._imag_value_() + self.name_last_node('imag') + + self._define( + ['imag', 'real'], + [] + ) + with self._option(): + self._imag_value_() + self.name_last_node('imag') + with self._option(): + self._real_value_() + self.name_last_node('real') + self._error( + 'expecting one of: ' + ' ' + ) + with self._optional(): + with self._choice(): + with self._option(): + self._hz_() + with self._option(): + self._unit_() + self._error( + 'expecting one of: ' + ' ' + ) + self.name_last_node('unit') + + self._define( + ['imag', 'real', 'unit'], + [] + ) + + @tatsumasu('ImagValue') + def _imag_value_(self): # noqa + self._number_scale_() + self.name_last_node('value') + self._token('J') + + self._define( + ['value'], + [] + ) + + @tatsumasu('RealValue') + def _real_value_(self): # noqa + self._number_scale_() + self.name_last_node('value') + + @tatsumasu() + def _freq_value_(self): # noqa + self._number_scale_() + self.name_last_node('value') + with self._optional(): + self._hz_() + self.name_last_node('unit') + + self._define( + ['unit', 'value'], + [] + ) + + @tatsumasu('NumberScale') + def _number_scale_(self): # noqa + with self._choice(): + with self._option(): + self._floating_point_() + self.name_last_node('value') + with self._group(): + with self._choice(): + with self._option(): + self._meg_() + with self._option(): + with self._optional(): + self._suffix_() + self._error( + 'expecting one of: ' + ' ' + ) + self.name_last_node('scale') + + self._define( + ['scale', 'value'], + [] + ) + with self._option(): + self._integer_() + self.name_last_node('value') + with self._group(): + with self._choice(): + with self._option(): + self._meg_() + with self._option(): + with self._optional(): + self._suffix_() + self._error( + 'expecting one of: ' + ' ' + ) + self.name_last_node('scale') + + self._define( + ['scale', 'value'], + [] + ) + self._error( + 'expecting one of: ' + '([\\+\\-]?(([0-9]+(\\.[0-9]*)?)|(\\.[0-' + '9]+))([eE][\\-\\+]?[0-9]{1,3})?)' + '([\\+\\-]?[0-9]+) ' + '' + ) + + @tatsumasu() + def _suffix_(self): # noqa + self._pattern('[tTgGkKmMxXuUnNpPfFµ]') + + @tatsumasu() + def _meg_(self): # noqa + self._pattern('[mM][eE][gG]') + + @tatsumasu('Unit') + def _unit_(self): # noqa + self._pattern('[a-zA-Z%]+') + + @tatsumasu('Hz') + def _hz_(self): # noqa + self._pattern('[Hh][Zz]') + + @tatsumasu() + def _lead_name_(self): # noqa + self._pattern('I[SDGBEC1-9]') + + @tatsumasu('Float') + def _floating_point_(self): # noqa + self._pattern('([\\+\\-]?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))([eE][\\-\\+]?[0-9]{1,3})?)') + + @tatsumasu('Int') + def _integer_(self): # noqa + self._pattern('([\\+\\-]?[0-9]+)') + + @tatsumasu() + def _digit_(self): # noqa + self._pattern('[0-9]') + + @tatsumasu('Filename') + def _filename_(self): # noqa + self._pattern('[a-zA-Z0-9_:@#\\.\\$\\/][a-zA-Z0-9_:@#\\.\\$\\/\\+\\-]*') + + @tatsumasu() + def _boolean_(self): # noqa + with self._choice(): + with self._option(): + self._token('TRUE') + with self._option(): + self._token('FALSE') + self._error( + 'expecting one of: ' + "'FALSE' 'TRUE'" + ) + + @tatsumasu('ModelName') + def _model_name_(self): # noqa + self._pattern('[a-zA-Z0-9_][a-zA-Z0-9_\\-\\+\\.]*') + self.name_last_node('name') + with self._ifnot(): + with self._group(): + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._token('=') + + self._define( + ['sep'], + [] + ) + + self._define( + ['name', 'sep'], + [] + ) + + @tatsumasu('BinaryPattern') + def _binary_pattern_(self): # noqa + self._pattern('[Bb]') + + def block1(): + self._binary_() + self._positive_closure(block1) + self.name_last_node('pattern') + + self._define( + ['pattern'], + [] + ) + + @tatsumasu() + def _binary_(self): # noqa + self._pattern('[01]') + + @tatsumasu('Device') + def _dev_(self): # noqa + self._pattern('[a-zA-Z\\$][a-zA-Z0-9_:!`@#\\.\\+\\-\\$]*') + + @tatsumasu('NetNode') + def _node_(self): # noqa + with self._group(): + with self._choice(): + with self._option(): + self._pattern('[a-zA-Z0-9_\\[\\$\\/\\+\\-][a-zA-Z0-9_:\\$\\-`~!@#%&_\\+|<>\\?\\.\\\\|\\^\\*\\/]*[a-zA-Z0-9_\\$\\-`~!@#%&_\\+|<>\\?\\.\\\\|\\^\\*\\]\\/]') + with self._option(): + self._pattern('[a-zA-Z0-9_]') + self._error( + 'expecting one of: ' + '[a-zA-Z0-9_\\[\\$\\/\\+\\-][a-zA-Z0-9_:\\$\\-' + '`~!@#%&_\\+|<>\\?\\.\\|\\^\\*\\/]*[a-zA-Z0-' + '9_\\$\\-`~!@#%&_\\+|<>\\?\\.\\|\\^\\*\\]\\/]' + '[a-zA-Z0-9_]' + ) + self.name_last_node('node') + with self._ifnot(): + with self._group(): + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._token('=') + + self._define( + ['sep'], + [] + ) + + self._define( + ['node', 'sep'], + [] + ) + + @tatsumasu() + def _id_(self): # noqa + with self._choice(): + with self._option(): + self._pattern('[a-zA-Z_`@#\\$][a-zA-Z0-9_:`@#\\.\\$\\/]*[a-zA-Z0-9_`@#\\.\\$]') + with self._option(): + self._pattern('[a-zA-Z_`@#\\$]') + self._error( + 'expecting one of: ' + '[a-zA-Z_`@#\\$] [a-zA-Z_`@#\\$][a-zA-Z0-' + '9_:`@#\\.\\$\\/]*[a-zA-Z0-9_`@#\\.\\$]' + ) + + @tatsumasu() + def _var_id_(self): # noqa + with self._choice(): + with self._option(): + self._pattern('[a-zA-Z_`@#\\$][a-zA-Z0-9_:`@#\\.\\$]*[a-zA-Z0-9_`@#\\.\\$]') + with self._option(): + self._pattern('[a-zA-Z]') + self._error( + 'expecting one of: ' + '[a-zA-Z] [a-zA-Z_`@#\\$][a-zA-Z0-' + '9_:`@#\\.\\$]*[a-zA-Z0-9_`@#\\.\\$]' + ) + + @tatsumasu() + def _end_sep_(self): # noqa + with self._choice(): + with self._option(): + self._cmd_net_sep_() + self.name_last_node('@') + + def block2(): + self._st_() + self._closure(block2) + with self._option(): + + def block3(): + self._st_() + self._positive_closure(block3) + self._error( + 'expecting one of: ' + ' ' + ' [ \\t]' + ) + + @tatsumasu() + def _sep_(self): # noqa + with self._choice(): + with self._option(): + + def block1(): + self._cmd_net_sep_() + self.name_last_node('@') + + def block3(): + self._st_() + self._closure(block3) + self._token('+') + + def block4(): + self._st_() + self._closure(block4) + self._positive_closure(block1) + with self._option(): + + def block5(): + self._st_() + self._positive_closure(block5) + self._error( + 'expecting one of: ' + ' ' + ' [ \\t]' + ) + + @tatsumasu('Separator') + def _cmd_net_sep_(self): # noqa + + def block0(): + self._st_() + self._closure(block0) + with self._optional(): + self._inline_comment_() + self.name_last_node('@') + self.name_last_node('comment') + self._newline_() + + def block3(): + + def block4(): + self._st_() + self._closure(block4) + with self._optional(): + with self._choice(): + with self._option(): + self._line_comment_() + self.name_last_node('@') + with self._option(): + self._inline_comment_() + self.name_last_node('@') + self._error( + 'expecting one of: ' + ' ' + ) + self.name_last_node('comment') + self._newline_() + + self._define( + ['comment'], + [] + ) + self._closure(block3) + + self._define( + ['comment'], + [] + ) + + @tatsumasu() + def _inline_comment_(self): # noqa + self._semicolon_() + + def block0(): + self._st_() + self._closure(block0) + self._text_() + self.name_last_node('@') + + @tatsumasu() + def _line_comment_(self): # noqa + self._asterisk_() + + def block0(): + self._st_() + self._closure(block0) + self._text_() + self.name_last_node('@') + + @tatsumasu('Comment') + def _text_(self): # noqa + self._pattern('[^\\r\\n]*') + + @tatsumasu() + def _asterisk_(self): # noqa + self._token('*') + + @tatsumasu() + def _question_mark_(self): # noqa + self._token('?') + + @tatsumasu() + def _colon_(self): # noqa + self._token(':') + + @tatsumasu() + def _semicolon_(self): # noqa + self._token(';') + + @tatsumasu() + def _comma_(self): # noqa + self._token(',') + + @tatsumasu() + def _dot_(self): # noqa + self._token('.') + + @tatsumasu() + def _dollar_(self): # noqa + self._token('\\$') + + @tatsumasu() + def _double_bar_(self): # noqa + self._token('//') + + @tatsumasu() + def _single_quote_(self): # noqa + self._token("'") + + @tatsumasu() + def _double_quote_(self): # noqa + self._token('"') + + @tatsumasu() + def _lc_(self): # noqa + self._token('{') + + @tatsumasu() + def _rc_(self): # noqa + self._token('}') + + @tatsumasu() + def _lp_(self): # noqa + self._token('(') + + @tatsumasu() + def _rp_(self): # noqa + self._token(')') + + @tatsumasu() + def _newline_(self): # noqa + self._pattern('[\\r\\n]') + + @tatsumasu() + def _st_(self): # noqa + self._pattern('[ \\t]') + + @tatsumasu() + def _ws_(self): # noqa + self._pattern('[^\\S\\r\\n]*') + + +class SpiceSemantics: + def start(self, ast): # noqa + return ast + + def lines(self, ast): # noqa + return ast + + def circuit_line(self, ast): # noqa + return ast + + def netlist_lines(self, ast): # noqa + return ast + + def netlist_line(self, ast): # noqa + return ast + + def encrypted(self, ast): # noqa + return ast + + def device(self, ast): # noqa + return ast + + def nonlinear_dependent_source(self, ast): # noqa + return ast + + def abm_expression(self, ast): # noqa + return ast + + def capacitor(self, ast): # noqa + return ast + + def diode(self, ast): # noqa + return ast + + def voltage_controlled_voltage_source(self, ast): # noqa + return ast + + def current_controlled_current_source(self, ast): # noqa + return ast + + def voltage_controlled_current_source(self, ast): # noqa + return ast + + def current_controlled_voltage_source(self, ast): # noqa + return ast + + def control_value(self, ast): # noqa + return ast + + def control_table(self, ast): # noqa + return ast + + def control_voltage_poly(self, ast): # noqa + return ast + + def control_current_poly(self, ast): # noqa + return ast + + def current_source(self, ast): # noqa + return ast + + def jfet(self, ast): # noqa + return ast + + def mutual_inductor(self, ast): # noqa + return ast + + def inductor(self, ast): # noqa + return ast + + def mosfet(self, ast): # noqa + return ast + + def bjt(self, ast): # noqa + return ast + + def substrate_node(self, ast): # noqa + return ast + + def resistor(self, ast): # noqa + return ast + + def switch(self, ast): # noqa + return ast + + def subcircuit(self, ast): # noqa + return ast + + def voltage_source(self, ast): # noqa + return ast + + def dc(self, ast): # noqa + return ast + + def ac(self, ast): # noqa + return ast + + def transient_specification(self, ast): # noqa + return ast + + def transient_pulse(self, ast): # noqa + return ast + + def pulse_arguments(self, ast): # noqa + return ast + + def transient_sin(self, ast): # noqa + return ast + + def sin_arguments(self, ast): # noqa + return ast + + def transient_exp(self, ast): # noqa + return ast + + def exp_arguments(self, ast): # noqa + return ast + + def transient_pat(self, ast): # noqa + return ast + + def pat_arguments(self, ast): # noqa + return ast + + def transient_pwl(self, ast): # noqa + return ast + + def pwl_file_arguments(self, ast): # noqa + return ast + + def pwl_arguments(self, ast): # noqa + return ast + + def transient_sffm(self, ast): # noqa + return ast + + def sffm_arguments(self, ast): # noqa + return ast + + def command(self, ast): # noqa + return ast + + def netlist_cmds(self, ast): # noqa + return ast + + def ac_cmd(self, ast): # noqa + return ast + + def ac_sweep_type(self, ast): # noqa + return ast + + def data_cmd(self, ast): # noqa + return ast + + def dc_cmd(self, ast): # noqa + return ast + + def embedded_sampling_cmd(self, ast): # noqa + return ast + + def es_parameter_type(self, ast): # noqa + return ast + + def es_parameter_name(self, ast): # noqa + return ast + + def es_sep(self, ast): # noqa + return ast + + def ic_cmd(self, ast): # noqa + return ast + + def include_cmd(self, ast): # noqa + return ast + + def lib_cmd(self, ast): # noqa + return ast + + def lib_call(self, ast): # noqa + return ast + + def model_cmd(self, ast): # noqa + return ast + + def model_type(self, ast): # noqa + return ast + + def param_cmd(self, ast): # noqa + return ast + + def simulator_cmd(self, ast): # noqa + return ast + + def subckt_cmd(self, ast): # noqa + return ast + + def lib_block(self, ast): # noqa + return ast + + def title_cmd(self, ast): # noqa + return ast + + def parameters(self, ast): # noqa + return ast + + def parameter(self, ast): # noqa + return ast + + def gen_expr(self, ast): # noqa + return ast + + def tablefile(self, ast): # noqa + return ast + + def braced_expression(self, ast): # noqa + return ast + + def parenthesis_nodes(self, ast): # noqa + return ast + + def circuit_nodes(self, ast): # noqa + return ast + + def expression(self, ast): # noqa + return ast + + def ternary(self, ast): # noqa + return ast + + def conditional_expression(self, ast): # noqa + return ast + + def boolean_or(self, ast): # noqa + return ast + + def boolean_xor(self, ast): # noqa + return ast + + def boolean_and(self, ast): # noqa + return ast + + def boolean_not(self, ast): # noqa + return ast + + def relational(self, ast): # noqa + return ast + + def conditional_factor(self, ast): # noqa + return ast + + def term(self, ast): # noqa + return ast + + def add_sub(self, ast): # noqa + return ast + + def prod(self, ast): # noqa + return ast + + def unary(self, ast): # noqa + return ast + + def exp(self, ast): # noqa + return ast + + def functional(self, ast): # noqa + return ast + + def variable(self, ast): # noqa + return ast + + def factor(self, ast): # noqa + return ast + + def functions(self, ast): # noqa + return ast + + def functions_1(self, ast): # noqa + return ast + + def atan2(self, ast): # noqa + return ast + + def ddx(self, ast): # noqa + return ast + + def gauss(self, ast): # noqa + return ast + + def i_func(self, ast): # noqa + return ast + + def if_func(self, ast): # noqa + return ast + + def limit(self, ast): # noqa + return ast + + def functions_2(self, ast): # noqa + return ast + + def rand(self, ast): # noqa + return ast + + def unif(self, ast): # noqa + return ast + + def v_func(self, ast): # noqa + return ast + + def special_variables(self, ast): # noqa + return ast + + def value(self, ast): # noqa + return ast + + def imag_value(self, ast): # noqa + return ast + + def real_value(self, ast): # noqa + return ast + + def freq_value(self, ast): # noqa + return ast + + def number_scale(self, ast): # noqa + return ast + + def suffix(self, ast): # noqa + return ast + + def meg(self, ast): # noqa + return ast + + def unit(self, ast): # noqa + return ast + + def hz(self, ast): # noqa + return ast + + def lead_name(self, ast): # noqa + return ast + + def floating_point(self, ast): # noqa + return ast + + def integer(self, ast): # noqa + return ast + + def digit(self, ast): # noqa + return ast + + def filename(self, ast): # noqa + return ast + + def boolean(self, ast): # noqa + return ast + + def model_name(self, ast): # noqa + return ast + + def binary_pattern(self, ast): # noqa + return ast + + def binary(self, ast): # noqa + return ast + + def dev(self, ast): # noqa + return ast + + def node(self, ast): # noqa + return ast + + def id(self, ast): # noqa + return ast + + def var_id(self, ast): # noqa + return ast + + def end_sep(self, ast): # noqa + return ast + + def sep(self, ast): # noqa + return ast + + def cmd_net_sep(self, ast): # noqa + return ast + + def inline_comment(self, ast): # noqa + return ast + + def line_comment(self, ast): # noqa + return ast + + def text(self, ast): # noqa + return ast + + def asterisk(self, ast): # noqa + return ast + + def question_mark(self, ast): # noqa + return ast + + def colon(self, ast): # noqa + return ast + + def semicolon(self, ast): # noqa + return ast + + def comma(self, ast): # noqa + return ast + + def dot(self, ast): # noqa + return ast + + def dollar(self, ast): # noqa + return ast + + def double_bar(self, ast): # noqa + return ast + + def single_quote(self, ast): # noqa + return ast + + def double_quote(self, ast): # noqa + return ast + + def lc(self, ast): # noqa + return ast + + def rc(self, ast): # noqa + return ast + + def lp(self, ast): # noqa + return ast + + def rp(self, ast): # noqa + return ast + + def newline(self, ast): # noqa + return ast + + def st(self, ast): # noqa + return ast + + def ws(self, ast): # noqa + return ast + + +def main(filename, **kwargs): + if not filename or filename == '-': + text = sys.stdin.read() + else: + with open(filename) as f: + text = f.read() + parser = SpiceParser() + return parser.parse( + text, + filename=filename, + **kwargs + ) + + +if __name__ == '__main__': + import json + from tatsu.util import asjson + + ast = generic_main(main, SpiceParser, name='Spice') + data = asjson(ast) + print(json.dumps(data, indent=2)) diff --git a/PySpice/Spice/SpiceModel.py b/PySpice/Spice/SpiceModel.py new file mode 100644 index 000000000..e2b9daafe --- /dev/null +++ b/PySpice/Spice/SpiceModel.py @@ -0,0 +1,763 @@ +#!/usr/bin/env python + +# CAVEAT UTILITOR +# +# This file was automatically generated by TatSu. +# +# https://pypi.python.org/pypi/tatsu/ +# +# Any changes you make to it will be overwritten the next time +# the file is generated. + +from __future__ import annotations + +from typing import Any +from dataclasses import dataclass + +from tatsu.objectmodel import Node +from tatsu.semantics import ModelBuilderSemantics + + +@dataclass(eq=False) +class ModelBase(Node): + pass + + +class SpiceModelBuilderSemantics(ModelBuilderSemantics): + def __init__(self, context=None, types=None): + types = [ + t for t in globals().values() + if type(t) is type and issubclass(t, ModelBase) + ] + (types or []) + super().__init__(context=context, types=types) + + +@dataclass(eq=False) +class Circuit(ModelBase): + lines: Any = None + title: Any = None + + +@dataclass(eq=False) +class Lines(ModelBase): + pass + + +@dataclass(eq=False) +class CircuitLine(ModelBase): + pass + + +@dataclass(eq=False) +class NetlistLines(ModelBase): + pass + + +@dataclass(eq=False) +class NetlistLine(ModelBase): + pass + + +@dataclass(eq=False) +class NonLinearDependentSource(ModelBase): + dev: Any = None + expr: Any = None + magnitude: Any = None + negative: Any = None + parameters: Any = None + positive: Any = None + sep: Any = None + + +@dataclass(eq=False) +class Capacitor(ModelBase): + dev: Any = None + model: Any = None + negative: Any = None + parameters: Any = None + positive: Any = None + sep: Any = None + value: Any = None + + +@dataclass(eq=False) +class Diode(ModelBase): + area: Any = None + dev: Any = None + model: Any = None + negative: Any = None + parameters: Any = None + positive: Any = None + sep: Any = None + + +@dataclass(eq=False) +class VoltageControlledVoltageSource(ModelBase): + controller: Any = None + dev: Any = None + negative: Any = None + nodes: Any = None + positive: Any = None + sep: Any = None + transconductance: Any = None + + +@dataclass(eq=False) +class CurrentControlledCurrentSource(ModelBase): + controller: Any = None + dev: Any = None + device: Any = None + gain: Any = None + negative: Any = None + positive: Any = None + sep: Any = None + + +@dataclass(eq=False) +class VoltageControlledCurrentSource(ModelBase): + controller: Any = None + dev: Any = None + negative: Any = None + nodes: Any = None + positive: Any = None + sep: Any = None + transconductance: Any = None + + +@dataclass(eq=False) +class CurrentControlledVoltageSource(ModelBase): + controller: Any = None + dev: Any = None + device: Any = None + negative: Any = None + positive: Any = None + sep: Any = None + transresistance: Any = None + + +@dataclass(eq=False) +class ControlValue(ModelBase): + expression: Any = None + sep: Any = None + type: Any = None + + +@dataclass(eq=False) +class ControlTable(ModelBase): + expr: Any = None + input: Any = None + output: Any = None + sep: Any = None + type: Any = None + + +@dataclass(eq=False) +class ControlVoltagePoly(ModelBase): + coefficient: Any = None + negative: Any = None + positive: Any = None + sep: Any = None + value: Any = None + + +@dataclass(eq=False) +class ControlCurrentPoly(ModelBase): + coefficient: Any = None + device: Any = None + sep: Any = None + value: Any = None + + +@dataclass(eq=False) +class CurrentSource(ModelBase): + ac_magnitude: Any = None + ac_phase: Any = None + dc_value: Any = None + dev: Any = None + negative: Any = None + positive: Any = None + sep: Any = None + transient: Any = None + + +@dataclass(eq=False) +class JFET(ModelBase): + area: Any = None + dev: Any = None + drain: Any = None + gate: Any = None + model: Any = None + parameters: Any = None + sep: Any = None + source: Any = None + + +@dataclass(eq=False) +class MutualInductor(ModelBase): + dev: Any = None + inductor: Any = None + model: Any = None + sep: Any = None + value: Any = None + + +@dataclass(eq=False) +class Inductor(ModelBase): + dev: Any = None + model: Any = None + negative: Any = None + parameters: Any = None + positive: Any = None + sep: Any = None + value: Any = None + + +@dataclass(eq=False) +class MOSFET(ModelBase): + bulk: Any = None + dev: Any = None + drain: Any = None + gate: Any = None + model: Any = None + name: Any = None + param: Any = None + parameter: Any = None + sep: Any = None + source: Any = None + value: Any = None + + +@dataclass(eq=False) +class BJT(ModelBase): + area: Any = None + base: Any = None + collector: Any = None + dev: Any = None + emitter: Any = None + model: Any = None + parameters: Any = None + sep: Any = None + substrate: Any = None + thermal: Any = None + + +@dataclass(eq=False) +class SubstrateNode(ModelBase): + substrate: Any = None + + +@dataclass(eq=False) +class Resistor(ModelBase): + dev: Any = None + model: Any = None + negative: Any = None + parameters: Any = None + positive: Any = None + sep: Any = None + value: Any = None + + +@dataclass(eq=False) +class Switch(ModelBase): + control_n: Any = None + control_p: Any = None + dev: Any = None + initial_state: Any = None + model: Any = None + negative: Any = None + positive: Any = None + sep: Any = None + + +@dataclass(eq=False) +class Subcircuit(ModelBase): + dev: Any = None + node: Any = None + parameters: Any = None + params: Any = None + sep: Any = None + + +@dataclass(eq=False) +class VoltageSource(ModelBase): + ac_magnitude: Any = None + ac_phase: Any = None + dc_value: Any = None + dev: Any = None + negative: Any = None + positive: Any = None + sep: Any = None + transient: Any = None + + +@dataclass(eq=False) +class TransientSpecification(ModelBase): + pass + + +@dataclass(eq=False) +class TransientPulse(ModelBase): + sep: Any = None + type: Any = None + + +@dataclass(eq=False) +class PulseArguments(ModelBase): + sep: Any = None + v1: Any = None + value: Any = None + + +@dataclass(eq=False) +class TransientSin(ModelBase): + sep: Any = None + type: Any = None + + +@dataclass(eq=False) +class SinArguments(ModelBase): + freq: Any = None + sep: Any = None + v0: Any = None + va: Any = None + value: Any = None + + +@dataclass(eq=False) +class TransientExp(ModelBase): + sep: Any = None + type: Any = None + + +@dataclass(eq=False) +class ExpArguments(ModelBase): + sep: Any = None + v1: Any = None + v2: Any = None + value: Any = None + + +@dataclass(eq=False) +class TransientPat(ModelBase): + sep: Any = None + type: Any = None + + +@dataclass(eq=False) +class PatArguments(ModelBase): + data: Any = None + repeat: Any = None + sep: Any = None + td: Any = None + tf: Any = None + tr: Any = None + tsample: Any = None + vhi: Any = None + vlo: Any = None + + +@dataclass(eq=False) +class TransientPWL(ModelBase): + type: Any = None + + +@dataclass(eq=False) +class PWLFileArguments(ModelBase): + filename: Any = None + parameters: Any = None + sep: Any = None + + +@dataclass(eq=False) +class PWLArguments(ModelBase): + parameters: Any = None + sep: Any = None + t: Any = None + value: Any = None + + +@dataclass(eq=False) +class TransientSFFM(ModelBase): + sep: Any = None + type: Any = None + + +@dataclass(eq=False) +class SFFMArguments(ModelBase): + sep: Any = None + v0: Any = None + va: Any = None + value: Any = None + + +@dataclass(eq=False) +class Command(ModelBase): + pass + + +@dataclass(eq=False) +class NetlistCmds(ModelBase): + pass + + +@dataclass(eq=False) +class ACCmd(ModelBase): + cmd: Any = None + end: Any = None + points: Any = None + sep: Any = None + start: Any = None + sweep: Any = None + table: Any = None + + +@dataclass(eq=False) +class DataCmd(ModelBase): + cmd: Any = None + name: Any = None + sep: Any = None + table: Any = None + value: Any = None + + +@dataclass(eq=False) +class DCCmd(ModelBase): + cmd: Any = None + name: Any = None + point: Any = None + points: Any = None + sep: Any = None + start: Any = None + step: Any = None + stop: Any = None + sweep: Any = None + table: Any = None + + +@dataclass(eq=False) +class EmbeddedSamplingCmd(ModelBase): + cmd: Any = None + name: Any = None + parameter: Any = None + sep: Any = None + type: Any = None + value: Any = None + + +@dataclass(eq=False) +class ICCmd(ModelBase): + cmd: Any = None + node: Any = None + sep: Any = None + value: Any = None + + +@dataclass(eq=False) +class IncludeCmd(ModelBase): + cmd: Any = None + filename: Any = None + sep: Any = None + + +@dataclass(eq=False) +class LibCmd(ModelBase): + block: Any = None + call: Any = None + cmd: Any = None + sep: Any = None + + +@dataclass(eq=False) +class LibCall(ModelBase): + entry: Any = None + filename: Any = None + sep: Any = None + + +@dataclass(eq=False) +class ModelCmd(ModelBase): + cmd: Any = None + name: Any = None + parameters: Any = None + sep: Any = None + type: Any = None + + +@dataclass(eq=False) +class ParamCmd(ModelBase): + cmd: Any = None + parameters: Any = None + sep: Any = None + + +@dataclass(eq=False) +class SimulatorCmd(ModelBase): + cmd: Any = None + sep: Any = None + simulator: Any = None + + +@dataclass(eq=False) +class SubcktCmd(ModelBase): + cmd: Any = None + lines: Any = None + name: Any = None + node: Any = None + parameters: Any = None + sep: Any = None + + +@dataclass(eq=False) +class LibBlock(ModelBase): + entry: Any = None + lines: Any = None + sep: Any = None + + +@dataclass(eq=False) +class TitleCmd(ModelBase): + cmd: Any = None + title: Any = None + + +@dataclass(eq=False) +class Parameters(ModelBase): + pass + + +@dataclass(eq=False) +class Parameter(ModelBase): + name: Any = None + sep: Any = None + value: Any = None + + +@dataclass(eq=False) +class GenericExpression(ModelBase): + braced: Any = None + value: Any = None + + +@dataclass(eq=False) +class TableFile(ModelBase): + filename: Any = None + func: Any = None + sep: Any = None + + +@dataclass(eq=False) +class BracedExpression(ModelBase): + sep: Any = None + + +@dataclass(eq=False) +class ParenthesisNodes(ModelBase): + sep: Any = None + + +@dataclass(eq=False) +class CircuitNodes(ModelBase): + sep: Any = None + + +@dataclass(eq=False) +class Expression(ModelBase): + term: Any = None + ternary: Any = None + + +@dataclass(eq=False) +class Ternary(ModelBase): + op: Any = None + sep: Any = None + t: Any = None + x: Any = None + y: Any = None + + +@dataclass(eq=False) +class Conditional(ModelBase): + expr: Any = None + + +@dataclass(eq=False) +class Or(ModelBase): + left: Any = None + op: Any = None + right: Any = None + sep: Any = None + + +@dataclass(eq=False) +class Xor(ModelBase): + left: Any = None + op: Any = None + right: Any = None + sep: Any = None + + +@dataclass(eq=False) +class And(ModelBase): + left: Any = None + op: Any = None + right: Any = None + sep: Any = None + + +@dataclass(eq=False) +class Not(ModelBase): + op: Any = None + operator: Any = None + + +@dataclass(eq=False) +class Relational(ModelBase): + factor: Any = None + left: Any = None + op: Any = None + right: Any = None + sep: Any = None + + +@dataclass(eq=False) +class ConditionalFactor(ModelBase): + boolean: Any = None + expr: Any = None + sep: Any = None + + +@dataclass(eq=False) +class Term(ModelBase): + pass + + +@dataclass(eq=False) +class AddSub(ModelBase): + left: Any = None + op: Any = None + right: Any = None + sep: Any = None + + +@dataclass(eq=False) +class ProdDivMod(ModelBase): + left: Any = None + op: Any = None + right: Any = None + sep: Any = None + + +@dataclass(eq=False) +class Sign(ModelBase): + op: Any = None + operator: Any = None + + +@dataclass(eq=False) +class Exponential(ModelBase): + left: Any = None + op: Any = None + right: Any = None + sep: Any = None + + +@dataclass(eq=False) +class Functional(ModelBase): + pass + + +@dataclass(eq=False) +class Variable(ModelBase): + factor: Any = None + sep: Any = None + variable: Any = None + + +@dataclass(eq=False) +class Factor(ModelBase): + sep: Any = None + + +@dataclass(eq=False) +class Functions(ModelBase): + pass + + +@dataclass(eq=False) +class Value(ModelBase): + imag: Any = None + real: Any = None + unit: Any = None + + +@dataclass(eq=False) +class ImagValue(ModelBase): + value: Any = None + + +@dataclass(eq=False) +class RealValue(ModelBase): + value: Any = None + + +@dataclass(eq=False) +class NumberScale(ModelBase): + scale: Any = None + value: Any = None + + +@dataclass(eq=False) +class Unit(ModelBase): + pass + + +@dataclass(eq=False) +class Hz(ModelBase): + pass + + +@dataclass(eq=False) +class Float(ModelBase): + pass + + +@dataclass(eq=False) +class Int(ModelBase): + pass + + +@dataclass(eq=False) +class Filename(ModelBase): + pass + + +@dataclass(eq=False) +class ModelName(ModelBase): + name: Any = None + sep: Any = None + + +@dataclass(eq=False) +class BinaryPattern(ModelBase): + pattern: Any = None + + +@dataclass(eq=False) +class Device(ModelBase): + pass + + +@dataclass(eq=False) +class NetNode(ModelBase): + node: Any = None + sep: Any = None + + +@dataclass(eq=False) +class Separator(ModelBase): + comment: Any = None + + +@dataclass(eq=False) +class Comment(ModelBase): + pass diff --git a/PySpice/Spice/Xyce/RawFile.py b/PySpice/Spice/Xyce/RawFile.py index 8abf5ffb0..16151e7db 100644 --- a/PySpice/Spice/Xyce/RawFile.py +++ b/PySpice/Spice/Xyce/RawFile.py @@ -21,6 +21,7 @@ #################################################################################################### import os +import numpy as np from ..RawFile import VariableAbc, RawFileAbc @@ -40,6 +41,7 @@ _module_logger = logging.getLogger(__name__) + #################################################################################################### class Variable(VariableAbc): @@ -78,10 +80,10 @@ def simplified_name(self): else: return self.name + #################################################################################################### class RawFile(RawFileAbc): - """ This class parse the stdout of ngspice and the raw data output. Public Attributes: @@ -112,15 +114,25 @@ class RawFile(RawFileAbc): ############################################## - def __init__(self, output): - - raw_data = self._read_header(output) - self._read_variable_data(raw_data) + def __init__(self, output=None, filename=None): + if filename: + binary_line = b'Binary:\n' + header = b"" + with open(filename, 'rb') as ifile: + for line in ifile: + header += line + if line == binary_line: + break + idx = ifile.tell() + self._read_header(header) + self._read_file_variable_data(filename, idx) + else: + raw_data = self._read_header(output) + self._read_variable_data(raw_data) # self._to_analysis() self._simulation = None - ############################################## def _read_header(self, output): @@ -145,12 +157,41 @@ def _read_header(self, output): self.plot_name = self._read_header_field_line(header_line_iterator, 'Plotname') self.flags = self._read_header_field_line(header_line_iterator, 'Flags') self.number_of_variables = int(self._read_header_field_line(header_line_iterator, 'No. Variables')) - self.number_of_points = int(self._read_header_field_line(header_line_iterator, 'No. Points')) + try: + self.number_of_points = int(self._read_header_field_line(header_line_iterator, 'No. Points')) + except ValueError: + self.number_of_points = -1 # Simulation not finished self._read_header_field_line(header_line_iterator, 'Variables') self._read_header_variables(header_line_iterator) return raw_data + def _read_file_variable_data(self, filename, idx): + """ Read the raw data and set the variable values. """ + + if self.flags == 'real': + number_of_columns = self.number_of_variables + elif self.flags == 'complex': + number_of_columns = 2 * self.number_of_variables + else: + raise NotImplementedError + + input_data = np.fromfile(filename, + count=self.number_of_points*self.number_of_variables, + dtype='f8', + offset=idx) + + number_of_rows = input_data.shape[0] // number_of_columns + input_data = input_data[:number_of_rows * number_of_columns] + input_data = input_data.reshape((-1, number_of_columns)).transpose() + if self.flags == 'complex': + raw_data = input_data + input_data = np.array(raw_data[0::2], dtype='complex64') + input_data.imag = raw_data[1::2] + for variable in self.variables.values(): + variable.data = input_data[variable.index] + return input_data + ############################################## def fix_case(self): @@ -158,8 +199,8 @@ def fix_case(self): """ Ngspice return lower case names. This method fixes the case of the variable names. """ circuit = self.circuit - element_translation = {element.upper():element for element in circuit.element_names} - node_translation = {node.upper():node for node in circuit.node_names} + element_translation = {element.upper(): element for element in circuit.element_names} + node_translation = {node.upper(): node for node in circuit.node_names} for variable in self.variables.values(): variable.fix_case(element_translation, node_translation) diff --git a/PySpice/Spice/Xyce/Server.py b/PySpice/Spice/Xyce/Server.py index c05325c4c..3a51eb4f1 100644 --- a/PySpice/Spice/Xyce/Server.py +++ b/PySpice/Spice/Xyce/Server.py @@ -71,10 +71,13 @@ class XyceServer: def __init__(self, **kwargs): self._xyce_command = kwargs.get('xyce_command') or self.XYCE_COMMAND + self._working_directory = kwargs.get('working_directory') + if self._working_directory is not None: + self._working_directory = os.path.abspath(self._working_directory) ############################################## - def _parse_stdout(self, stdout): + def _parse_stdout(self, stdout, spice_netlist): """Parse stdout for errors.""" @@ -97,9 +100,11 @@ def _parse_stdout(self, stdout): simulation_failed = True self._logger.error(os.linesep + line.decode('utf-8')) if error_found: - raise NameError("Errors was found by Xyce") + raise NameError("Errors was found by Xyce\n{}\n{}".format( + stdout.decode('utf-8'), spice_netlist)) elif simulation_failed: - raise NameError("Xyce simulation failed") + raise NameError("Xyce simulation failed\n{}\n{}".format( + stdout.decode('utf-8'), spice_netlist)) ############################################## @@ -112,11 +117,17 @@ def __call__(self, spice_input): self._logger.debug('Start the xyce subprocess') - tmp_dir = tempfile.mkdtemp() - input_filename = os.path.join(tmp_dir, 'input.cir') - output_filename = os.path.join(tmp_dir, 'output.raw') + wd = self._working_directory + if wd is None: + wd = tempfile.mkdtemp() + else: + os.makedirs(wd, exist_ok=True) + input_filename = os.path.join(wd, 'input.cir') + output_filename = os.path.join(wd, 'output.raw') + + spice_netlist = str(spice_input) with open(input_filename, 'w') as f: - f.write(str(spice_input)) + f.write(spice_netlist) command = (self._xyce_command, '-r', output_filename, input_filename) self._logger.info('Run {}'.format(' '.join(command))) @@ -128,13 +139,14 @@ def __call__(self, spice_input): ) stdout, stderr = process.communicate() - self._parse_stdout(stdout) + self._parse_stdout(stdout, spice_netlist) with open(output_filename, 'rb') as f: output = f.read() # self._logger.debug(output) raw_file = RawFile(output) - shutil.rmtree(tmp_dir) + if self._working_directory is None: + shutil.rmtree(wd) return raw_file diff --git a/PySpice/Spice/Xyce/Simulation.py b/PySpice/Spice/Xyce/Simulation.py index 70058ce78..13559e9da 100644 --- a/PySpice/Spice/Xyce/Simulation.py +++ b/PySpice/Spice/Xyce/Simulation.py @@ -26,6 +26,7 @@ import logging #################################################################################################### +import os from ..Simulation import CircuitSimulator from .Server import XyceServer @@ -48,8 +49,10 @@ def __init__(self, circuit, **kwargs): super().__init__(circuit, **kwargs) - xyce_command = kwargs.get('xyce_command', None) - self._xyce_server = XyceServer(xyce_command=xyce_command) + self._xyce_server = XyceServer(**kwargs) + + def save_str(self): + return ".save" + os.linesep ############################################## diff --git a/PySpice/Spice/expressiongrammar.ebnf b/PySpice/Spice/expressiongrammar.ebnf new file mode 100644 index 000000000..2a3ee0983 --- /dev/null +++ b/PySpice/Spice/expressiongrammar.ebnf @@ -0,0 +1,649 @@ +@@grammar :: Expression +@@whitespace :: // +@@ignorecase :: True +@@parseinfo :: True +@@left_recursion :: True + +start::SpiceExpression + = + gen_expr + ; + + +gen_expr::GenericExpression + = + braced:braced_expression | value:value + ; + + +braced_expression::BracedExpression + = + lc [sep:sep] @:expression [sep:sep] rc | @:expression + ; + + +expression::Expression + = + ternary:ternary | term:term + ; + + +ternary::Ternary + = + t:conditional_expression + [sep:sep] + op:'?' + ~ + [sep:sep] + x:expression + [sep:sep] + ':' + ~ + [sep:sep] + y:expression + ; + + +conditional_expression::Conditional + = + expr:boolean_or + ; + + +boolean_or::Or + = + left:boolean_xor [[sep:sep] op:'|' [sep:sep] right:boolean_or] + ; + + +boolean_xor::Xor + = + left:boolean_and [[sep:sep] op:'^' [sep:sep] right:boolean_xor] + ; + + +boolean_and::And + = + left:boolean_not [[sep:sep] op:'&' [sep:sep] right:boolean_and] + ; + + +boolean_not::Not + = + [op:'~'] operator:relational + ; + + +relational::Relational + = + left:expression + [sep:sep] + op:('==' | '!=' | '>=' | '<=' | '>' | '<') + [sep:sep] + right:expression + | + factor:conditional_factor + ; + + +conditional_factor::ConditionalFactor + = + lp [sep:sep] expr:conditional_expression [sep:sep] rp | boolean:boolean + ; + + +term::Term + = + @:add_sub + ; + + +add_sub::AddSub + = + left:prod [[sep:sep] op:('+' | '-') [sep:sep] right:add_sub] + ; + + +prod::ProdDivMod + = + left:unary [[sep:sep] op:('*' | '/' | '%') [sep:sep] right:prod] + ; + + +unary::Sign + = + [op:('+' | '-')] operator:exp + ; + + +exp::Exponential + = + left:functional [[sep:sep] op:'**' [sep:sep] right:exp] + ; + + +functional::Functional + = + @:functions | @:variable + ; + + +variable::Variable + = + | lc [sep:sep] variable:var_id [sep:sep] rc + | variable:var_id + | factor:factor + ; + + +factor::Factor + = + lp [sep:sep] @:expression [sep:sep] rp | @:value + ; + + +functions::Functions + = + | functions_1 + | atan2 + | ddx + | gauss + | if_func + | limit + | functions_2 + | rand + | unif + | i_func + | v_func + ; + + +functions_1 + = + func:( + | 'abs' + | 'ceil' + | 'ddt' + | 'floor' + | 'int' + | 'm' + | 'nint' + | 'sdt' + | 'sgn' + | 'stp' + | 'sqrt' + | 'uramp' + | 'Ph' + | 'Re' + | 'R' + | 'Img' + | 'acosh' + | 'acos' + | 'asinh' + | 'asin' + | 'arctan' + | 'atanh' + | 'atan' + | 'cosh' + | 'cos' + | 'exp' + | 'ln' + | 'log' + | 'log10' + | 'sinh' + | 'sin' + | 'tanh' + | 'tan' + ) + [sep:sep] + lp + ~ + [sep:sep] + x:expression + [sep:sep] + rp + ; + + +atan2 + = + func:'atan2' + [sep:sep] + lp + ~ + [sep:sep] + y:expression + [sep:sep] + comma + [sep:sep] + x:expression + [sep:sep] + rp + ; + + +ddx + = + func:'ddx' + [sep:sep] + lp + ~ + [sep:sep] + f:id + [sep:sep] + comma + [sep:sep] + x:expression + [sep:sep] + rp + ; + + +gauss + = + func:('agauss' | 'gauss') + [sep:sep] + lp + ~ + [sep:sep] + mu:expression + [sep:sep] + comma + [sep:sep] + alpha:expression + [sep:sep] + comma + [sep:sep] + n:expression + [sep:sep] + rp + ; + + +i_func + = + func:'i' [sep:sep] lp ~ [sep:sep] &'V' device:dev [sep:sep] rp + ; + + +if_func + = + func:'if' + [sep:sep] + lp + ~ + [sep:sep] + t:conditional_expression + [sep:sep] + comma + [sep:sep] + x:expression + [sep:sep] + comma + [sep:sep] + y:expression + [sep:sep] + rp + ; + + +limit + = + func:'limit' + [sep:sep] + lp + ~ + [sep:sep] + x:expression + [sep:sep] + comma + [sep:sep] + y:expression + [sep:sep] + comma + [sep:sep] + z:expression + [sep:sep] + rp + ; + + +functions_2 + = + func:('min' | 'max' | 'pwrs' | 'pow' | 'pwr' | 'sign') + [sep:sep] + lp + ~ + [sep:sep] + x:expression + [sep:sep] + comma + [sep:sep] + y:expression + [sep:sep] + rp + ; + + +rand + = + func:'rand' [sep:sep] lp ~ [sep:sep] rp + ; + + +unif + = + func:('aunif' | 'unif') + [sep:sep] + lp + ~ + [sep:sep] + mu:expression + [sep:sep] + comma + [sep:sep] + alpha:expression + [sep:sep] + rp + ; + + +v_func + = + func:'v' + [sep:sep] + lp + ~ + [sep:sep] + node:node + [sep:sep] + [comma [sep:sep] node:node [sep:sep]] + rp + ; + + +special_variables + = + 'time' | 'temper' | 'temp' | 'freq' | 'vt' | 'pi' + ; + + +value::Value + = + ( + | (real:real_value '+' imag:imag_value) + | imag:imag_value + | real:real_value + ) + unit:[ + hz | unit + ] + ; + + +imag_value::ImagValue + = + value:number_scale 'J' + ; + + +real_value::RealValue + = + value:number_scale + ; + + +freq_value + = + value:number_scale unit:[hz] + ; + + +number_scale::NumberScale + = + | value:floating_point scale:(meg | [suffix]) + | value:integer scale:(meg | [suffix]) + ; + + +suffix + = + /[tTgGkKmMxXuUnNpPfFµ]/ + ; + + +meg + = + /[mM][eE][gG]/ + ; + + +unit::Unit + = + /[a-zA-Z%]+/ + ; + + +hz::Hz + = + /[Hh][Zz]/ + ; + + +lead_name + = + /I[SDGBEC1-9]/ + ; + + +floating_point::Float + = + /([\+\-]?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))([eE][\-\+]?[0-9]{1,3})?)/ + ; + + +integer::Int + = + /([\+\-]?[0-9]+)/ + ; + + +digit + = + /[0-9]/ + ; + + +boolean + = + 'TRUE' | 'FALSE' + ; + + +binary_pattern::BinaryPattern + = + /[Bb]/ pattern:{binary}+ + ; + + +binary + = + /[01]/ + ; + + +dev::Device + = + /[a-zA-Z\$][a-zA-Z0-9_:!`@#\.\+\-\$]*/ + ; + + +node::NetNode + = + node:( + | ?"[a-zA-Z0-9_\[\$\/\+\-][a-zA-Z0-9_:\$\-`~!@#%&_\+|<>\?\.\\|\^\*\/]*[a-zA-Z0-9_\$\-`~!@#%&_\+|<>\?\.\\|\^\*\]\/]" + | /[a-zA-Z0-9_]/ + ) + !([sep:sep] '=') + ; + + +id + = + | ?"[a-zA-Z_`@#\$][a-zA-Z0-9_:`@#\.\$\/]*[a-zA-Z0-9_`@#\.\$]" + | /[a-zA-Z_`@#\$]/ + ; + + +var_id + = + /[a-zA-Z_`@#\$][a-zA-Z0-9_:`@#\.\$]*[a-zA-Z0-9_`@#\.\$]/ | /[a-zA-Z]/ + ; + + +end_sep + = + @:cmd_net_sep {st} | {st}+ + ; + + +sep + = + {@:cmd_net_sep {st} '+' {st}}+ | {st}+ + ; + + +cmd_net_sep::Separator + = + {st} + comment:[@:inline_comment] + newline + { + {st} + comment:[ + @:line_comment | @:inline_comment + ] + + newline + } + ; + + +inline_comment + = + semicolon {st} @:text + ; + + +line_comment + = + asterisk {st} @:text + ; + + +text::Comment + = + /[^\r\n]*/ + ; + + +asterisk + = + '*' + ; + + +question_mark + = + '?' + ; + + +colon + = + ':' + ; + + +semicolon + = + ';' + ; + + +comma + = + ',' + ; + + +dot + = + '.' + ; + + +dollar + = + '\\$' + ; + + +double_bar + = + '//' + ; + + +single_quote + = + "'" + ; + + +double_quote + = + '"' + ; + + +lc + = + '{' + ; + + +rc + = + '}' + ; + + +lp + = + '(' + ; + + +rp + = + ')' + ; + + +newline + = + /[\r\n]/ + ; + + +st + = + /[ \t]/ + ; + + +ws + = + /[^\S\r\n]*/ + ; diff --git a/PySpice/Spice/spicegrammar.ebnf b/PySpice/Spice/spicegrammar.ebnf new file mode 100644 index 000000000..2b4206b4e --- /dev/null +++ b/PySpice/Spice/spicegrammar.ebnf @@ -0,0 +1,1673 @@ +@@grammar :: Spice +@@whitespace :: // +@@ignorecase :: True +@@parseinfo :: True +@@left_recursion :: True + +start::Circuit + = + ['.TITLE'] + {st} + [asterisk] + {st} + title+:text + {newline asterisk {st} title+:text} + { + {st} + [ + line_comment | inline_comment + ] + + newline + } + {st} + [lines:lines] + ['.END' end_sep] + $ + ; + + +lines::Lines + = + {@:circuit_line} + ; + + +circuit_line::CircuitLine + = + (@:device | @:command | encrypted) @:end_sep + ; + + +netlist_lines::NetlistLines + = + {@:netlist_line} + ; + + +netlist_line::NetlistLine + = + (@:device | @:netlist_cmds | encrypted) @:end_sep + ; + + +encrypted + = + '$CDNENCSTART' + ~ + [id] + {newline}+ + {{/[0-9a-f]/} {newline}+} + '$CDNENCFINISH' + ~ + [id] + ; + + +device + = + | @:nonlinear_dependent_source + | @:capacitor + | @:diode + | @:voltage_controlled_voltage_source + | @:current_controlled_current_source + | @:voltage_controlled_current_source + | @:current_controlled_voltage_source + | @:current_source + | @:jfet + | @:mutual_inductor + | @:inductor + | @:mosfet + | @:bjt + | @:resistor + | @:subcircuit + | @:switch + | @:voltage_source + ; + + +nonlinear_dependent_source::NonLinearDependentSource + = + &'B' + ~ + dev:dev + sep:sep + positive:node + sep:sep + negative:node + ~ + sep:sep + magnitude:('V' | 'I') + [sep:sep] + '=' + [sep:sep] + expr:abm_expression + [sep:sep parameters:parameters] + ; + + +abm_expression + = + | lc [sep:sep] @:control_table [sep:sep] rc + | @:braced_expression + | @:tablefile + ; + + +capacitor::Capacitor + = + &'C' + ~ + dev:dev + sep:sep + positive:node + sep:sep + negative:node + ~ + [ + | sep:sep value:gen_expr [sep:sep model:model_name] + | sep:sep model:model_name [sep:sep value:gen_expr] + ] + + [sep:sep parameters:parameters] + ; + + +diode::Diode + = + &'D' + ~ + dev:dev + sep:sep + positive:node + sep:sep + negative:node + ~ + sep:sep + model:model_name + [sep:sep area:gen_expr] + [sep:sep parameters:parameters] + ; + + +voltage_controlled_voltage_source::VoltageControlledVoltageSource + = + &'E' + ~ + dev:dev + sep:sep + positive:node + sep:sep + negative:node + ~ + sep:sep + ( + ( + nodes:(parenthesis_nodes | circuit_nodes) + sep:sep + transconductance:gen_expr + ) + | + (controller:(control_value | control_table | control_voltage_poly)) + ) + ; + + +current_controlled_current_source::CurrentControlledCurrentSource + = + &'F' + ~ + dev:dev + sep:sep + positive:node + sep:sep + negative:node + ~ + sep:sep + ((controller:control_current_poly) | (device:dev sep:sep gain:gen_expr)) + ; + + +voltage_controlled_current_source::VoltageControlledCurrentSource + = + &'G' + ~ + dev:dev + sep:sep + positive:node + sep:sep + negative:node + ~ + sep:sep + ( + ( + nodes:(parenthesis_nodes | circuit_nodes) + sep:sep + transconductance:gen_expr + ) + | + (controller:(control_value | control_table | control_voltage_poly)) + ) + ; + + +current_controlled_voltage_source::CurrentControlledVoltageSource + = + &'H' + ~ + dev:dev + sep:sep + positive:node + sep:sep + negative:node + ~ + sep:sep + ( + | (controller:control_current_poly) + | (device:dev sep:sep transresistance:gen_expr) + ) + ; + + +control_value::ControlValue + = + type:'VALUE' ~ [sep:sep] '=' [sep:sep] expression:braced_expression + ; + + +control_table::ControlTable + = + type:'TABLE' + ~ + [sep:sep] + expr:braced_expression + [sep:sep] + '=' + [sep:sep] + ( + ( + lp + [sep:sep] + input+:value + [sep:sep] + comma + [sep:sep] + output+:value + [sep:sep] + rp + { + {sep:sep}+ + lp + [sep:sep] + input+:value + [sep:sep] + comma + [sep:sep] + output+:value + [sep:sep] + rp + } + ) + | + ( + input+:value + [sep:sep] + comma + [sep:sep] + output+:value + {{sep:sep}+ input+:value [sep:sep] comma [sep:sep] output+:value} + ) + ) + ; + + +control_voltage_poly::ControlVoltagePoly + = + 'POLY' + ~ + [sep:sep] + lp + [sep:sep] + value:integer + [sep:sep] + rp + [sep:sep] + (sep:sep)%{ + lp + [sep:sep] + positive+:node + [sep:sep] + comma + [sep:sep] + negative+:node + [sep:sep] + rp + | + positive+:node sep:sep negative+:node + | + coefficient:value + } + ; + + +control_current_poly::ControlCurrentPoly + = + 'POLY' + ~ + [sep:sep] + lp + [sep:sep] + value:integer + [sep:sep] + rp + [sep:sep] + (sep:sep)%{device+:dev | coefficient+:value} + ; + + +current_source::CurrentSource + = + &'I' + ~ + dev:dev + sep:sep + positive:node + sep:sep + negative:node + ~ + [sep:sep [dc ~ sep:sep] dc_value:gen_expr] + [sep:sep ac ~ [sep:sep ac_magnitude:gen_expr [sep:sep ac_phase:gen_expr]]] + [sep:sep transient:transient_specification] + ; + + +jfet::JFET + = + &'J' + ~ + dev:dev + sep:sep + drain:node + sep:sep + gate:node + sep:sep + source:node + ~ + sep:sep + model:model_name + [sep:sep area:gen_expr] + [sep:sep parameters:parameters] + ; + + +mutual_inductor::MutualInductor + = + &'K' + ~ + dev:dev + {sep:sep &'L' inductor+:dev}+ + sep:sep + value:gen_expr + [sep:sep model:model_name] + ; + + +inductor::Inductor + = + &'L' + ~ + dev:dev + sep:sep + positive:node + sep:sep + negative:node + ~ + [ + | sep:sep value:gen_expr [sep:sep model:model_name] + | sep:sep model:model_name [sep:sep value:gen_expr] + ] + + [sep:sep parameters:parameters] + ; + + +mosfet::MOSFET + = + &'M' + ~ + dev:dev + sep:sep + drain:node + sep:sep + gate:node + sep:sep + source:node + ~ + sep:sep + bulk:node + sep:sep + model:model_name + [sep:sep + (sep:sep)%{ + param:( + name:'IC' + [sep:sep] + '=' + ~ + [sep:sep] + ([sep:sep] comma [sep:sep])%{value:value} + | + parameter:parameter + ) + }] + ; + + +bjt::BJT + = + &'Q' + ~ + dev:dev + sep:sep + collector:node + sep:sep + base:node + sep:sep + emitter:node + ~ + sep:sep + [substrate:substrate_node sep:sep] + [thermal:'DT' sep:sep] + model:model_name + [sep:sep area:gen_expr] + [sep:sep parameters:parameters] + ; + + +substrate_node::SubstrateNode + = + substrate:(/[0-9]+/ | &'[' ~ node) + ; + + +resistor::Resistor + = + &'R' + ~ + dev:dev + sep:sep + positive:node + sep:sep + negative:node + ~ + [ + | sep:sep value:gen_expr [sep:sep model:model_name] + | sep:sep model:model_name [sep:sep value:gen_expr] + ] + + [sep:sep parameters:parameters] + ; + + +switch::Switch + = + &'S' + ~ + dev:dev + sep:sep + positive:node + sep:sep + negative:node + ~ + sep:sep + ( + model:model_name + [sep:sep initial_state:('ON' | 'OFF')] + sep:sep + 'control' + ~ + [sep:sep] + '=' + [sep:sep] + braced_expression + | + control_p:node + sep:sep + control_n:node + sep:sep + model:model_name + [sep:sep initial_state:('ON' | 'OFF')] + ) + ; + + +subcircuit::Subcircuit + = + &'X' + ~ + dev:dev + {sep:sep node+:node} + [params:':' ~ [sep:sep] parameters:parameters] + ; + + +voltage_source::VoltageSource + = + &'V' + ~ + dev:dev + sep:sep + positive:node + sep:sep + negative:node + ~ + [sep:sep [dc ~ sep:sep] dc_value:gen_expr] + [sep:sep ac ~ [sep:sep ac_magnitude:gen_expr [sep:sep ac_phase:gen_expr]]] + [sep:sep transient:transient_specification] + ; + + +dc + = + 'DC' + ; + + +ac + = + 'AC' + ; + + +transient_specification::TransientSpecification + = + | @:transient_pulse + | @:transient_sin + | @:transient_exp + | @:transient_pat + | @:transient_pwl + | @:transient_sffm + ; + + +transient_pulse::TransientPulse + = + type:'PULSE' + ( + | [sep:sep] lp ~ [sep:sep] @:pulse_arguments [sep:sep] rp + | sep:sep @:pulse_arguments + ) + ; + + +pulse_arguments::PulseArguments + = + v1:gen_expr sep:sep (sep:sep)%{value:gen_expr} + ; + + +transient_sin::TransientSin + = + type:'SIN' + ( + | [sep:sep] lp ~ [sep:sep] @:sin_arguments [sep:sep] rp + | sep:sep @:sin_arguments + ) + ; + + +sin_arguments::SinArguments + = + v0:gen_expr + sep:sep + va:gen_expr + sep:sep + freq:gen_expr + sep:sep + (sep:sep)%{value:gen_expr} + ; + + +transient_exp::TransientExp + = + type:'EXP' + ( + | [sep:sep] lp ~ [sep:sep] @:exp_arguments [sep:sep] rp + | sep:sep @:exp_arguments + ) + ; + + +exp_arguments::ExpArguments + = + v1:gen_expr sep:sep v2:gen_expr sep:sep (sep:sep)%{value:gen_expr} + ; + + +transient_pat::TransientPat + = + type:'PAT' + ( + | [sep:sep] lp ~ [sep:sep] @:pat_arguments [sep:sep] rp + | sep:sep @:pat_arguments + ) + ; + + +pat_arguments::PatArguments + = + vhi:gen_expr + sep:sep + vlo:gen_expr + sep:sep + td:gen_expr + sep:sep + tr:gen_expr + sep:sep + tf:gen_expr + sep:sep + tsample:gen_expr + sep:sep + data:binary_pattern + [sep:sep repeat:binary] + ; + + +transient_pwl::TransientPWL + = + type:'PWL' ~ (@:pwl_file_arguments | @:pwl_arguments) + ; + + +pwl_file_arguments::PWLFileArguments + = + sep:sep + 'FILE' + ~ + sep:sep + (double_quote filename:filename double_quote | filename:filename) + [sep:sep parameters:parameters] + ; + + +pwl_arguments::PWLArguments + = + ( + {sep:sep} + lp + ~ + {sep:sep} + t:value + sep:sep + value:value + {sep:sep t:value sep:sep value:value} + [sep:sep parameters:parameters] + {sep:sep} + rp + | + {sep:sep t:value sep:sep value:value}+ + ) + [sep:sep parameters:parameters] + ; + + +transient_sffm::TransientSFFM + = + type:'SFFM' + ( + | [sep:sep] lp ~ [sep:sep] @:sffm_arguments [sep:sep] rp + | sep:sep @:sffm_arguments + ) + ; + + +sffm_arguments::SFFMArguments + = + v0:gen_expr sep:sep va:gen_expr sep:sep (sep:sep)%{value:gen_expr} + ; + + +command::Command + = + | @:embedded_sampling_cmd + | @:include_cmd + | @:lib_cmd + | @:netlist_cmds + | @:subckt_cmd + | @:simulator_cmd + | @:title_cmd + | @:ac_cmd + | @:dc_cmd + ; + + +netlist_cmds::NetlistCmds + = + @:data_cmd | @:ic_cmd | @:model_cmd | @:param_cmd | @:subckt_cmd + ; + + +ac_cmd::ACCmd + = + cmd:'.AC' + ~ + sep:sep + ( + ( + sweep:ac_sweep_type + sep:sep + points:integer + sep:sep + start:value + sep:sep + end:value + ) + | + (sweep:'DATA' {st} '=' {st} table:id) + ) + ; + + +ac_sweep_type + = + 'LIN' | 'OCT' | 'DEC' + ; + + +data_cmd::DataCmd + = + cmd:'.DATA' + ~ + sep:sep + table:id + {sep:sep name:id}+ + {sep:sep value:value}+ + sep:end_sep + '.ENDDATA' + ; + + +dc_cmd::DCCmd + = + cmd:'.DC' + ( + (sep:sep sweep:'DATA' {st} '=' {st} table:id) + | + ( + { + ( + [sep:sep sweep:'LIN'] + sep:sep + name:id + sep:sep + start:value + sep:sep + stop:value + sep:sep + step:value + ) + | + ( + sep:sep + sweep:('DEC' | 'OCT') + sep:sep + name:id + sep:sep + start:value + sep:sep + stop:value + sep:sep + points:integer + ) + | + (sep:sep name:id sep:sep sweep:'LIST' sep:sep (sep:sep)%{point:value}+) + }+ + ) + ) + ; + + +embedded_sampling_cmd::EmbeddedSamplingCmd + = + cmd:'.EMBEDDEDSAMPLING' + ~ + ( + ( + sep:sep + parameter:'param' + {st} + '=' + {st} + (es_sep)%{name:id}+ + sep:sep + parameter:'type' + {st} + '=' + {st} + (es_sep)%{type:es_parameter_type}+ + { + sep:sep + parameter:es_parameter_name + {st} + '=' + {st} + (es_sep)%{value:gen_expr}+ + } + ) + | + (sep:sep parameter:'useExpr' {st} '=' ~ {st} value:boolean) + ) + ; + + +es_parameter_type + = + 'UNIFORM' | 'NORMAL' | 'GAMMA' + ; + + +es_parameter_name + = + | 'alpha' + | 'beta' + | 'means' + | 'std_deviations' + | 'lower_bounds' + | 'upper_bounds' + ; + + +es_sep + = + comma ~ {st} + ; + + +ic_cmd::ICCmd + = + cmd:('.IC' | '.DCVOLT') + ~ + ( + | {sep:sep 'V' lp ~ node:node rp {st} '=' {st} value:gen_expr}+ + | {sep:sep node:node {st}+ value:gen_expr}+ + ) + ; + + +include_cmd::IncludeCmd + = + cmd:('.INCLUDE' | '.INCL' | '.INC') + ~ + sep:sep + ( + | double_quote ~ filename:filename double_quote + | single_quote ~ filename:filename single_quote + | filename:filename + ) + ; + + +lib_cmd::LibCmd + = + cmd:'.LIB' ~ sep:sep (call:lib_call | block:lib_block) + ; + + +lib_call::LibCall + = + ( + | double_quote ~ filename:filename double_quote + | single_quote ~ filename:filename single_quote + | filename:filename + ) + sep:sep + entry:id + ; + + +model_cmd::ModelCmd + = + cmd:'.MODEL' + ~ + sep:sep + name:model_name + sep:sep + type:model_type + [ + | [sep:sep] lp ~ [sep:sep] parameters:parameters [sep:sep] rp + | sep:sep parameters:parameters + ] + ; + + +model_type + = + | 'CAP' + | 'CORE' + | 'C' + | 'DIG' + | 'D' + | 'IND' + | 'ISWITCH' + | 'LIN' + | 'LTRA' + | 'L' + | 'NJF' + | 'NMF' + | 'NMOS' + | 'NPN' + | 'PJF' + | 'PMF' + | 'PMOS' + | 'PNP' + | 'RES' + | 'R' + | 'SWITCH' + | 'TRANSLINE' + | 'VSWITCH' + | 'MEMRISTOR' + | 'ZOD' + ; + + +param_cmd::ParamCmd + = + cmd:'.PARAM' ~ sep:sep parameters:parameters + ; + + +simulator_cmd::SimulatorCmd + = + cmd:'.SIMULATOR' ~ sep:sep simulator:id + ; + + +subckt_cmd::SubcktCmd + = + cmd:'.SUBCKT' + ~ + sep:sep + name:model_name + {sep:sep node:(node !':')} + [sep:sep 'params:' ~ [sep:sep] parameters:parameters] + sep:cmd_net_sep + {st} + lines:netlist_lines + {st} + '.ENDS' + ~ + [{st}+ name:model_name] + ; + + +lib_block::LibBlock + = + entry:id + sep:cmd_net_sep + ~ + lines:netlist_lines + {st} + '.ENDL' + ~ + [{st}+ entry:id] + ; + + +title_cmd::TitleCmd + = + cmd:'.TITLE' ~ title:text + ; + + +parameters::Parameters + = + @+:parameter {(([sep] comma [sep]) | sep) @+:parameter} + ; + + +parameter::Parameter + = + name:id + [sep:sep] + '=' + ~ + [sep:sep] + value:gen_expr + {[sep:sep] comma [sep:sep] value:gen_expr} + ; + + +gen_expr::GenericExpression + = + braced:braced_expression | value:value + ; + + +tablefile::TableFile + = + func:'tablefile' + [sep:sep] + lp + ~ + [sep:sep] + (double_quote ~ filename:filename double_quote | filename:filename) + [sep:sep] + rp + ; + + +braced_expression::BracedExpression + = + lc [sep:sep] @:expression [sep:sep] rc + ; + + +parenthesis_nodes::ParenthesisNodes + = + lp [sep:sep] @:circuit_nodes [sep:sep] rp + ; + + +circuit_nodes::CircuitNodes + = + @+:node (sep:sep | ([sep:sep] comma [sep:sep])) @+:node + ; + + +expression::Expression + = + ternary:ternary | term:term + ; + + +ternary::Ternary + = + t:conditional_expression + [sep:sep] + op:'?' + ~ + [sep:sep] + x:expression + [sep:sep] + ':' + ~ + [sep:sep] + y:expression + ; + + +conditional_expression::Conditional + = + expr:boolean_or + ; + + +boolean_or::Or + = + left:boolean_xor [[sep:sep] op:'|' [sep:sep] right:boolean_or] + ; + + +boolean_xor::Xor + = + left:boolean_and [[sep:sep] op:'^' [sep:sep] right:boolean_xor] + ; + + +boolean_and::And + = + left:boolean_not [[sep:sep] op:'&' [sep:sep] right:boolean_and] + ; + + +boolean_not::Not + = + [op:'~'] operator:relational + ; + + +relational::Relational + = + left:expression + [sep:sep] + op:('==' | '!=' | '>=' | '<=' | '>' | '<') + [sep:sep] + right:expression + | + factor:conditional_factor + ; + + +conditional_factor::ConditionalFactor + = + lp [sep:sep] expr:conditional_expression [sep:sep] rp | boolean:boolean + ; + + +term::Term + = + @:add_sub + ; + + +add_sub::AddSub + = + left:prod [[sep:sep] op:('+' | '-') [sep:sep] right:add_sub] + ; + + +prod::ProdDivMod + = + left:unary [[sep:sep] op:('*' | '/' | '%') [sep:sep] right:prod] + ; + + +unary::Sign + = + [op:('+' | '-')] operator:exp + ; + + +exp::Exponential + = + left:functional [[sep:sep] op:'**' [sep:sep] right:exp] + ; + + +functional::Functional + = + @:functions | @:variable + ; + + +variable::Variable + = + | lc [sep:sep] variable:var_id [sep:sep] rc + | variable:var_id + | factor:factor + ; + + +factor::Factor + = + lp [sep:sep] @:expression [sep:sep] rp | @:value + ; + + +functions::Functions + = + | functions_1 + | atan2 + | ddx + | gauss + | if_func + | limit + | functions_2 + | rand + | unif + | i_func + | v_func + ; + + +functions_1 + = + func:( + | 'abs' + | 'ceil' + | 'ddt' + | 'floor' + | 'int' + | 'm' + | 'nint' + | 'sdt' + | 'sgn' + | 'stp' + | 'sqrt' + | 'uramp' + | 'Ph' + | 'Re' + | 'R' + | 'Img' + | 'acosh' + | 'acos' + | 'asinh' + | 'asin' + | 'arctan' + | 'atanh' + | 'atan' + | 'cosh' + | 'cos' + | 'exp' + | 'ln' + | 'log' + | 'log10' + | 'sinh' + | 'sin' + | 'tanh' + | 'tan' + ) + [sep:sep] + lp + ~ + [sep:sep] + x:expression + [sep:sep] + rp + ; + + +atan2 + = + func:'atan2' + [sep:sep] + lp + ~ + [sep:sep] + y:expression + [sep:sep] + comma + [sep:sep] + x:expression + [sep:sep] + rp + ; + + +ddx + = + func:'ddx' + [sep:sep] + lp + ~ + [sep:sep] + f:id + [sep:sep] + comma + [sep:sep] + x:expression + [sep:sep] + rp + ; + + +gauss + = + func:('agauss' | 'gauss') + [sep:sep] + lp + ~ + [sep:sep] + mu:expression + [sep:sep] + comma + [sep:sep] + alpha:expression + [sep:sep] + comma + [sep:sep] + n:expression + [sep:sep] + rp + ; + + +i_func + = + func:'i' [sep:sep] lp ~ [sep:sep] &'V' device:dev [sep:sep] rp + ; + + +if_func + = + func:'if' + [sep:sep] + lp + ~ + [sep:sep] + t:conditional_expression + [sep:sep] + comma + [sep:sep] + x:expression + [sep:sep] + comma + [sep:sep] + y:expression + [sep:sep] + rp + ; + + +limit + = + func:'limit' + [sep:sep] + lp + ~ + [sep:sep] + x:expression + [sep:sep] + comma + [sep:sep] + y:expression + [sep:sep] + comma + [sep:sep] + z:expression + [sep:sep] + rp + ; + + +functions_2 + = + func:('min' | 'max' | 'pwrs' | 'pow' | 'pwr' | 'sign') + [sep:sep] + lp + ~ + [sep:sep] + x:expression + [sep:sep] + comma + [sep:sep] + y:expression + [sep:sep] + rp + ; + + +rand + = + func:'rand' [sep:sep] lp ~ [sep:sep] rp + ; + + +unif + = + func:('aunif' | 'unif') + [sep:sep] + lp + ~ + [sep:sep] + mu:expression + [sep:sep] + comma + [sep:sep] + alpha:expression + [sep:sep] + rp + ; + + +v_func + = + func:'v' + [sep:sep] + lp + ~ + [sep:sep] + node:node + [sep:sep] + [comma [sep:sep] node:node [sep:sep]] + rp + ; + + +special_variables + = + 'time' | 'temper' | 'temp' | 'freq' | 'vt' | 'pi' + ; + + +value::Value + = + ( + | (real:real_value '+' imag:imag_value) + | imag:imag_value + | real:real_value + ) + unit:[ + hz | unit + ] + ; + + +imag_value::ImagValue + = + value:number_scale 'J' + ; + + +real_value::RealValue + = + value:number_scale + ; + + +freq_value + = + value:number_scale unit:[hz] + ; + + +number_scale::NumberScale + = + | value:floating_point scale:(meg | [suffix]) + | value:integer scale:(meg | [suffix]) + ; + + +suffix + = + /[tTgGkKmMxXuUnNpPfFµ]/ + ; + + +meg + = + /[mM][eE][gG]/ + ; + + +unit::Unit + = + /[a-zA-Z%]+/ + ; + + +hz::Hz + = + /[Hh][Zz]/ + ; + + +lead_name + = + /I[SDGBEC1-9]/ + ; + + +floating_point::Float + = + /([\+\-]?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))([eE][\-\+]?[0-9]{1,3})?)/ + ; + + +integer::Int + = + /([\+\-]?[0-9]+)/ + ; + + +digit + = + /[0-9]/ + ; + + +filename::Filename + = + ?"[a-zA-Z0-9_:@#\.\$\/][a-zA-Z0-9_:@#\.\$\/\+\-]*" + ; + + +boolean + = + 'TRUE' | 'FALSE' + ; + + +model_name::ModelName + = + name:/[a-zA-Z0-9_][a-zA-Z0-9_\-\+\.]*/ !([sep:sep] '=') + ; + + +binary_pattern::BinaryPattern + = + /[Bb]/ pattern:{binary}+ + ; + + +binary + = + /[01]/ + ; + + +dev::Device + = + /[a-zA-Z\$][a-zA-Z0-9_:!`@#\.\+\-\$]*/ + ; + + +node::NetNode + = + node:( + | ?"[a-zA-Z0-9_\[\$\/\+\-][a-zA-Z0-9_:\$\-`~!@#%&_\+|<>\?\.\\|\^\*\/]*[a-zA-Z0-9_\$\-`~!@#%&_\+|<>\?\.\\|\^\*\]\/]" + | /[a-zA-Z0-9_]/ + ) + !([sep:sep] '=') + ; + + +id + = + | ?"[a-zA-Z_`@#\$][a-zA-Z0-9_:`@#\.\$\/]*[a-zA-Z0-9_`@#\.\$]" + | /[a-zA-Z_`@#\$]/ + ; + + +var_id + = + /[a-zA-Z_`@#\$][a-zA-Z0-9_:`@#\.\$]*[a-zA-Z0-9_`@#\.\$]/ | /[a-zA-Z]/ + ; + + +end_sep + = + @:cmd_net_sep {st} | {st}+ + ; + + +sep + = + {@:cmd_net_sep {st} '+' {st}}+ | {st}+ + ; + + +cmd_net_sep::Separator + = + {st} + comment:[@:inline_comment] + newline + { + {st} + comment:[ + @:line_comment | @:inline_comment + ] + + newline + } + ; + + +inline_comment + = + semicolon {st} @:text + ; + + +line_comment + = + asterisk {st} @:text + ; + + +text::Comment + = + /[^\r\n]*/ + ; + + +asterisk + = + '*' + ; + + +question_mark + = + '?' + ; + + +colon + = + ':' + ; + + +semicolon + = + ';' + ; + + +comma + = + ',' + ; + + +dot + = + '.' + ; + + +dollar + = + '\\$' + ; + + +double_bar + = + '//' + ; + + +single_quote + = + "'" + ; + + +double_quote + = + '"' + ; + + +lc + = + '{' + ; + + +rc + = + '}' + ; + + +lp + = + '(' + ; + + +rp + = + ')' + ; + + +newline + = + /[\r\n]/ + ; + + +st + = + /[ \t]/ + ; + + +ws + = + /[^\S\r\n]*/ + ; diff --git a/PySpice/Tools/StringTools.py b/PySpice/Tools/StringTools.py index 05a5c9926..82715c2e0 100644 --- a/PySpice/Tools/StringTools.py +++ b/PySpice/Tools/StringTools.py @@ -32,23 +32,18 @@ #################################################################################################### -from PySpice.Unit.Unit import UnitValue - -#################################################################################################### - def str_spice(obj, unit=True): + from ..Unit.Unit import UnitValue + from ..Spice.Expressions import Expression, Symbol # Fixme: right place ??? """Convert an object to a Spice compatible string.""" - if isinstance(obj, UnitValue): - if unit: - return obj.str_spice() - else: # Fixme: ok ??? - return obj.str(spice=False, space=False, unit=False) + if hasattr(obj, 'str_spice'): + return obj.str_spice(unit) else: - return str(obj) + return str(obj).lower() #################################################################################################### @@ -79,6 +74,7 @@ def join_list(items): #################################################################################################### def join_dict(d): - return ' '.join(["{}={}".format(key, str_spice(value)) - for key, value in sorted(d.items()) + return ' '.join(["{}={}".format(key[:-1] if key.endswith('_') else key, str_spice(value)) + for key, value in d.items() if value is not None]) + diff --git a/PySpice/Unit/SiUnits.py b/PySpice/Unit/SiUnits.py index 34296151a..defe8ceb2 100644 --- a/PySpice/Unit/SiUnits.py +++ b/PySpice/Unit/SiUnits.py @@ -62,7 +62,7 @@ class Giga(UnitPrefix): class Mega(UnitPrefix): POWER = 6 PREFIX = 'M' - SPICE_PREFIX = 'Meg' + SPICE_PREFIX = 'meg' class Kilo(UnitPrefix): POWER = 3 @@ -98,7 +98,6 @@ class Pico(UnitPrefix): class Femto(UnitPrefix): POWER = -15 PREFIX = 'f' - SPICE_PREFIX = None class Atto(UnitPrefix): POWER = -18 @@ -121,14 +120,16 @@ class Yocto(UnitPrefix): # Define SI units -class Metre(SiBaseUnit): - UNIT_NAME = 'metre' +class Meter(SiBaseUnit): + UNIT_NAME = 'meter' UNIT_SUFFIX = 'm' + SPICE_SUFFIX = '' QUANTITY = 'length' class Kilogram(SiBaseUnit): UNIT_NAME = 'kilogram' UNIT_SUFFIX = 'kg' + SPICE_SUFFIX = '' QUANTITY = 'mass' class Second(SiBaseUnit): @@ -140,16 +141,19 @@ class Second(SiBaseUnit): class Ampere(SiBaseUnit): UNIT_NAME = 'ampere' UNIT_SUFFIX = 'A' + SPICE_SUFFIX = 'a' QUANTITY = 'electric current' class Kelvin(SiBaseUnit): UNIT_NAME = 'kelvin' UNIT_SUFFIX = 'K' + SPICE_SUFFIX = '' QUANTITY = 'thermodynamic temperature' class Mole(SiBaseUnit): - UNIT_NAME = 'mole' + UNIT_NAME = 'mol' UNIT_SUFFIX = 'mol' + SPICE_SUFFIX = '' QUANTITY = 'amount of substance' class Candela(SiBaseUnit): @@ -171,6 +175,7 @@ class Radian(Unit): class Steradian(Unit): UNIT_NAME = 'steradian' UNIT_SUFFIX = 'sr' + SPICE_SUFFIX = '' QUANTITY = 'solid angle' SI_UNIT = 'm^2*m^-2' DEFAULT_UNIT = True @@ -178,6 +183,7 @@ class Steradian(Unit): class Hertz(Unit): UNIT_NAME = 'frequency' UNIT_SUFFIX = 'Hz' + SPICE_SUFFIX = 'hz' QUANTITY = 'frequency' SI_UNIT = 's^-1' DEFAULT_UNIT = True @@ -185,6 +191,7 @@ class Hertz(Unit): class Newton(Unit): UNIT_NAME = 'newton' UNIT_SUFFIX = 'N' + SPICE_SUFFIX = '' QUANTITY = 'force' SI_UNIT = 'kg*m*s^-2' DEFAULT_UNIT = True @@ -192,6 +199,7 @@ class Newton(Unit): class Pascal(Unit): UNIT_NAME = 'pascal' UNIT_SUFFIX = 'Pa' + SPICE_SUFFIX = '' QUANTITY = 'pressure' SI_UNIT = 'kg*m^-1*s^-2' DEFAULT_UNIT = True @@ -200,6 +208,7 @@ class Pascal(Unit): class Joule(Unit): UNIT_NAME = 'joule' UNIT_SUFFIX = 'J' + SPICE_SUFFIX = '' QUANTITY = 'energy' SI_UNIT = 'kg*m^2*s^-2' DEFAULT_UNIT = True @@ -208,6 +217,7 @@ class Joule(Unit): class Watt(Unit): UNIT_NAME = 'watt' UNIT_SUFFIX = 'W' + SPICE_SUFFIX = 'w' QUANTITY = 'power' SI_UNIT = 'kg*m^2*s^-3' DEFAULT_UNIT = True @@ -215,7 +225,8 @@ class Watt(Unit): class Coulomb(Unit): UNIT_NAME = 'coulomb' - UNIT_SUFFIX = 'C' + UNIT_SUFFIX = 'c' + SPICE_SUFFIX = '' QUANTITY = 'electric charge' SI_UNIT = 's*A' DEFAULT_UNIT = True @@ -223,6 +234,7 @@ class Coulomb(Unit): class Volt(Unit): UNIT_NAME = 'volt' UNIT_SUFFIX = 'V' + SPICE_SUFFIX = 'v' QUANTITY = 'voltage' SI_UNIT = 'kg*m^2*s^-3*A^-1' DEFAULT_UNIT = True @@ -231,6 +243,7 @@ class Volt(Unit): class Farad(Unit): UNIT_NAME = 'farad' UNIT_SUFFIX = 'F' + SPICE_SUFFIX = '' QUANTITY = 'capacitance' SI_UNIT = 'kg^-1*m^-2*s^4*A^2' DEFAULT_UNIT = True @@ -239,6 +252,7 @@ class Farad(Unit): class Ohm(Unit): UNIT_NAME = 'ohm' UNIT_SUFFIX = 'Ω' + SPICE_SUFFIX = 'ohm' QUANTITY = 'electric resistance, impedance, reactance' SI_UNIT = 'kg*m^2*s^-3*A^-2' DEFAULT_UNIT = True @@ -247,6 +261,7 @@ class Ohm(Unit): class Siemens(Unit): UNIT_NAME = 'siemens' UNIT_SUFFIX = 'S' + SPICE_SUFFIX = '' QUANTITY = 'electrical conductance' SI_UNIT = 'kg^-1*m^-2*s^3*A^2' DEFAULT_UNIT = True @@ -254,7 +269,7 @@ class Siemens(Unit): class Weber(Unit): UNIT_NAME = 'weber' - UNIT_SUFFIX = 'Wb' + UNIT_SUFFIX = 'wb' QUANTITY = 'magnetic flux' SI_UNIT = 'kg*m^2*s^-2*A^-1' DEFAULT_UNIT = True @@ -262,7 +277,8 @@ class Weber(Unit): class Tesla(Unit): UNIT_NAME = 'tesla' - UNIT_SUFFIX = '' + UNIT_SUFFIX = 'T' + SPICE_SUFFIX = '' QUANTITY = 'T' SI_UNIT = 'kg*s^-2*A^-1' DEFAULT_UNIT = True @@ -270,15 +286,16 @@ class Tesla(Unit): class Henry(Unit): UNIT_NAME = 'henry' - UNIT_SUFFIX = 'H' + UNIT_SUFFIX = 'h' QUANTITY = 'inductance' SI_UNIT = 'kg*m^2*s^-2*A^-2' DEFAULT_UNIT = True # Wb/A -class DegreeCelcius(Unit): - UNIT_NAME = 'degree celcuis' +class DegreeCelsius(Unit): + UNIT_NAME = 'degree celsius' UNIT_SUFFIX = '°C' + SPICE_SUFFIX = 'celsius' QUANTITY = 'temperature relative to 273.15 K' SI_UNIT = 'K' @@ -306,19 +323,22 @@ class Becquerel(Unit): class Gray(Unit): UNIT_NAME = 'gray' UNIT_SUFFIX = 'Gy' + SPICE_SUFFIX = '' QUANTITY = 'absorbed dose (of ionizing radiation)' SI_UNIT = 'm^2*s^-2' # J/kg class Sievert(Unit): UNIT_NAME = 'sievert' - UNIT_SUFFIX = 'Sv' + UNIT_SUFFIX = 'sv' + SPICE_SUFFIX = '' QUANTITY = ' equivalent dose (of ionizing radiation)' SI_UNIT = 'm^2*s^-2' class Katal(Unit): UNIT_NAME = 'katal' UNIT_SUFFIX = 'kat' + SPICE_SUFFIX = '' QUANTITY = 'catalytic activity' SI_UNIT = 'mol*s^-1' DEFAULT_UNIT = True diff --git a/PySpice/Unit/Unit.py b/PySpice/Unit/Unit.py index 8eae51751..d45d1f741 100644 --- a/PySpice/Unit/Unit.py +++ b/PySpice/Unit/Unit.py @@ -41,7 +41,8 @@ #################################################################################################### -from PySpice.Tools.EnumFactory import EnumFactory +from ..Tools.EnumFactory import EnumFactory +from ..Tools.StringTools import str_spice #################################################################################################### @@ -70,7 +71,13 @@ def register_prefix(meta, cls): power = cls.POWER if power is None: raise ValueError('Power is None for {}'.format(cls.__name__)) - meta._prefixes[power] = cls() + value = cls() + meta._prefixes[power] = value + if value.spice_prefix: + meta._prefixes[value.spice_prefix.lower()] = value + if value.spice_prefix.lower() != value.prefix.lower(): + meta._prefixes[value.prefix] = value + ############################################## @@ -122,6 +129,10 @@ def prefix(self): def is_unit(self): return self.POWER == 0 + @property + def is_unit_less(self): + return True + @property def scale(self): return 10**self.POWER @@ -139,7 +150,10 @@ def spice_prefix(self): @property def is_defined_in_spice(self): - return self.spice_prefix is not None + if hasattr(self, 'SPICE_PREFIX'): + return self.SPICE_PREFIX is not None + else: + return True ############################################## @@ -410,6 +424,8 @@ def register_unit(meta, cls): obj = cls() meta._units[obj.unit_suffix] = obj + if obj.spice_suffix != obj.unit_suffix: + meta._units[obj.spice_suffix] = obj if obj.si_unit: hash_ = obj.si_unit.hash @@ -428,7 +444,7 @@ def unit_iter(meta): @classmethod def from_prefix(meta, prefix): - return meta._units__.get(prefix, None) + return meta._units.get(prefix, None) ############################################## @@ -477,7 +493,6 @@ class Unit(metaclass=UnitMetaclass): QUANTITY = '' SI_UNIT = SiDerivedUnit() DEFAULT_UNIT = False - # SPICE_SUFFIX = '' _logger = _module_logger.getChild('Unit') @@ -485,14 +500,8 @@ class Unit(metaclass=UnitMetaclass): def __init__(self, si_unit=None): - self._unit_name = self.UNIT_NAME - self._unit_suffix = self.UNIT_SUFFIX - self._quantity = self.QUANTITY - - if si_unit is None: - self._si_unit = self.SI_UNIT - else: - self._si_unit = si_unit + if si_unit is not None: + self.SI_UNIT = si_unit ############################################## @@ -503,25 +512,32 @@ def __repr__(self): @property def unit_name(self): - return self._unit_name + return self.UNIT_NAME @property def unit_suffix(self): - return self._unit_suffix + return self.UNIT_SUFFIX + + @property + def spice_suffix(self): + if hasattr(self, 'SPICE_SUFFIX'): + return self.SPICE_SUFFIX + else: + return self.UNIT_SUFFIX @property def quantity(self): - return self._quantity + return self.QUANTITY @property def si_unit(self): - return self._si_unit + return self.SI_UNIT ############################################## @property def is_unit_less(self): - return self._si_unit.is_unit_less() + return self.SI_UNIT.is_unit_less() ############################################## @@ -537,7 +553,7 @@ def is_base_unit(cls): def __eq__(self, other): """self == other""" - return self._si_unit == other.si_unit + return self.SI_UNIT == other.si_unit ############################################## @@ -577,52 +593,52 @@ def _equivalent_unit_or_power(self, si_unit, prefixed_unit): ############################################## def multiply(self, other, prefixed_unit=False): - si_unit = self._si_unit * other.si_unit + si_unit = self.SI_UNIT * other.si_unit return self._equivalent_unit_or_power(si_unit, prefixed_unit) ############################################## def divide(self, other, prefixed_unit=False): - si_unit = self._si_unit / other.si_unit + si_unit = self.SI_UNIT / other.si_unit return self._equivalent_unit_or_power(si_unit, prefixed_unit) ############################################## def power(self, exponent, prefixed_unit=False): - si_unit = self._si_unit.power(exponent) + si_unit = self.SI_UNIT.power(exponent) return self._equivalent_unit_or_power(si_unit, prefixed_unit) ############################################## def reciprocal(self, prefixed_unit=False): - si_unit = self._si_unit.reciprocal() + si_unit = self.SI_UNIT.reciprocal() return self._equivalent_unit_or_power(si_unit, prefixed_unit) ############################################## def sqrt(self, prefixed_unit=False): - si_unit = self._si_unit.sqrt() + si_unit = self.SI_UNIT.sqrt() return self._equivalent_unit_or_power(si_unit, prefixed_unit) ############################################## def square(self, prefixed_unit=False): - si_unit = self._si_unit.square() + si_unit = self.SI_UNIT.square() return self._equivalent_unit_or_power(si_unit, prefixed_unit) ############################################## def cbrt(self, prefixed_unit=False): - si_unit = self._si_unit.cbrt() + si_unit = self.SI_UNIT.cbrt() return self._equivalent_unit_or_power(si_unit, prefixed_unit) ############################################## def __str__(self): - if self._unit_suffix: - return self._unit_suffix + if self.UNIT_SUFFIX: + return self.UNIT_SUFFIX else: - return str(self._si_unit) + return str(self.SI_UNIT) ############################################## @@ -636,13 +652,12 @@ def validate(self, value, none=False): if none and value is None: return None if isinstance(value, UnitValue): - if self.is_same_unit(value): + if self.is_same_unit(value): return value - else: + elif not value.unit.is_unit_less: raise UnitError - else: - prefixed_unit = PrefixedUnit.from_prefixed_unit(self) - return prefixed_unit.new_value(value) + prefixed_unit = PrefixedUnit.from_prefixed_unit(self) + return prefixed_unit.new_value(value) #################################################################################################### @@ -705,7 +720,8 @@ def from_si_unit(cls, si_unit): @classmethod def from_prefixed_unit(cls, unit, power=0): - + if isinstance(power, UnitPrefix): + power = power.power if unit.unit_suffix: unit_key = str(unit) else: @@ -818,27 +834,17 @@ def str(self, spice=False, unit=True): string = self._power.str(spice) if unit: - string += str(self._unit) - - if spice: - # F is interpreted as f = femto - if string == 'F': - string = '' + if spice: + string += self._unit.spice_suffix else: - # Ngspice don't support utf-8 - # degree symbole can be encoded str(176) in Extended ASCII - string = string.replace('°', '') # U+00B0 - string = string.replace('℃', '') # U+2103 - # U+2109 ℉ - string = string.replace('Ω', 'Ohm') # U+CEA0 - string = string.replace('μ', 'u') # U+CEBC + string += self._unit.unit_suffix return string ############################################## - def str_spice(self): - return self.str(spice=True, unit=True) + def str_spice(self, unit=True): + return self.str(spice=True, unit=unit) ############################################## @@ -850,7 +856,7 @@ def __str__(self): def new_value(self, value): if isinstance(value, np.ndarray): return self._values_ctor.from_ndarray(value, self) - elif isinstance(value, collections.Iterable): + elif (not isinstance(value, str)) and isinstance(value, collections.Iterable): return [self._value_ctor(self, x) for x in value] else: return self._value_ctor(self, value) @@ -875,19 +881,32 @@ def simple_value(cls, value): ############################################## def __init__(self, prefixed_unit, value): + from ..Spice.EBNFExpressionParser import ExpressionParser + from ..Spice.Expressions import Expression self._prefixed_unit = prefixed_unit if isinstance(value, UnitValue): # Fixme: anonymous ??? - if not self.is_same_unit(value): + if not value.prefixed_unit.is_unit_less and not self.prefixed_unit.is_unit_less and not self.is_same_unit( + value): raise UnitError + if not value.prefixed_unit.is_unit_less and self.prefixed_unit.is_unit_less: + self._prefixed_unit = value._prefixed_unit if self.is_same_power(value): self._value = value.value else: self._value = self._convert_scalar_value(value) elif isinstance(value, int): self._value = value # to keep as int + elif isinstance(value, str): + try: + expr = ExpressionParser.parse(value) + self._value = expr + except: + self._value = float(value) + elif isinstance(value, Expression): + self._value = value else: self._value = float(value) @@ -998,10 +1017,17 @@ def __float__(self): ############################################## def str(self, spice=False, space=False, unit=True): - string = str(self._value) - if space: - string += ' ' - string += self._prefixed_unit.str(spice, unit) + from ..Spice.Expressions import Expression + + if isinstance(self._value, Expression): + string = '{{{}}}'.format(str_spice(self._value, unit=False)) + else: + string = str_spice(self._value, unit=False) + if space and not self.unit.is_unit_less: + string += ' ' + if self.prefixed_unit: + string += self.prefixed_unit.str(spice, unit) + return string ############################################## @@ -1011,8 +1037,8 @@ def str_space(self): ############################################## - def str_spice(self): - return self.str(spice=True, space=False, unit=True) + def str_spice(self, unit=True): + return self.str(spice=True, space=False, unit=unit) ############################################## @@ -1374,6 +1400,13 @@ def canonise(self): self._logger.warning(e) return self + def as_ndarray(self, scale=False): + array = np.array(self._value) + if scale: + return array * self.scale + else: + return array + #################################################################################################### class UnitValues(np.ndarray): diff --git a/PySpice/Unit/__init__.py b/PySpice/Unit/__init__.py index b714c0c98..1a5a99912 100644 --- a/PySpice/Unit/__init__.py +++ b/PySpice/Unit/__init__.py @@ -66,13 +66,13 @@ resistances = range(1, 11)@u_kΩ # using Python 3.5 syntax capacitance = u_uF(200) - inductance = u_mH(1) - temperature = u_Degree(25) + inductance = u_mh(1) + temperature = u_degree(25) - voltage = resistance1 * u_mA(1) # compute unit + voltage = resistance1 * u_ma(1) # compute unit frequency = u_ms(20).frequency - period = u_Hz(50).period + period = u_hz(50).period pulsation = frequency.pulsation pulsation = period.pulsation @@ -138,25 +138,12 @@ def __rmatmul__(self, other): #################################################################################################### -def _to_ascii(name): - ascii_name = name - for args in ( - ('μ', 'u'), - ('Ω', 'Ohm'), - ('°C', 'Degree'), - ): - ascii_name = ascii_name.replace(*args) - return ascii_name - def define_shortcut(name, shortcut) : # ° is illegal in Python 3.5 # see https://docs.python.org/3/reference/lexical_analysis.html#identifiers # https://www.python.org/dev/peps/pep-3131/ if '°' not in name: globals()[name] = shortcut - ascii_name = _to_ascii(name) - if ascii_name != name: - globals()[ascii_name] = shortcut #################################################################################################### @@ -198,17 +185,23 @@ class PeriodValues(_Unit.UnitValues): # , _Unit.PeriodMixin def _build_unit_type_shortcut(unit): name = 'U_' + unit.unit_suffix define_shortcut(name, unit) + if unit.spice_suffix and unit.spice_suffix != unit.unit_suffix: + name = 'U_' + unit.spice_suffix + define_shortcut(name, unit) def _build_as_unit_shortcut(unit): name = 'as_' + unit.unit_suffix shortcut = unit.validate define_shortcut(name, shortcut) + if unit.spice_suffix and unit.spice_suffix != unit.unit_suffix: + name = 'as_' + unit.spice_suffix + define_shortcut(name, shortcut) + def _exec_body(ns, unit_prefix): ns['POWER'] = unit_prefix def _build_unit_prefix_shortcut(unit, unit_prefix): - name = 'u_' + str(unit_prefix) + unit.unit_suffix if unit.__class__ == _SiUnits.Hertz: value_ctor = FrequencyValue values_ctor = FrequencyValues @@ -220,9 +213,18 @@ def _build_unit_prefix_shortcut(unit, unit_prefix): values_ctor = _Unit.UnitValues prefixed_unit = _Unit.PrefixedUnit(unit, unit_prefix, value_ctor, values_ctor) _Unit.PrefixedUnit.register(prefixed_unit) - define_shortcut('U' + name[1:], prefixed_unit) + suffixes = [unit.unit_suffix] + if unit.spice_suffix and unit.spice_suffix != unit.unit_suffix: + suffixes.append(unit.spice_suffix) + prefixes = [unit_prefix.prefix] shortcut = UnitValueShorcut(prefixed_unit) - define_shortcut(name, shortcut) + if unit_prefix.spice_prefix and unit_prefix.spice_prefix != unit_prefix.prefix: + prefixes.append(unit_prefix.spice_prefix) + for suffix in suffixes: + for prefix in prefixes: + name = 'u_' + prefix + suffix + define_shortcut('U' + name[1:], prefixed_unit) + define_shortcut(name, shortcut) def _build_unit_shortcut(unit): _build_as_unit_shortcut(unit) @@ -233,7 +235,7 @@ def _build_unit_shortcut(unit): for unit in _Unit.UnitMetaclass.unit_iter(): if unit.unit_suffix and unit.__class__ not in (_SiUnits.Kilogram,): - # Fixme: kilogram + # Fixme: kilogram and Meter (it can be mixed with mili) _build_unit_shortcut(unit) #################################################################################################### diff --git a/examples/resistor/voltage-divider.py b/examples/resistor/voltage-divider.py index 2ae801310..226e2d648 100755 --- a/examples/resistor/voltage-divider.py +++ b/examples/resistor/voltage-divider.py @@ -22,7 +22,8 @@ #################################################################################################### -simulator = circuit.simulator(temperature=25, nominal_temperature=25) +simulator = circuit.simulator(simulator='ngspice-subprocess', working_directory='.', + temperature=25, nominal_temperature=25) analysis = simulator.operating_point() for node in (analysis['in'], analysis.out): # .in is invalid ! @@ -30,7 +31,7 @@ #o# # Fixme: Xyce sensitivity analysis -analysis = simulator.dc_sensitivity('v(out)') +analysis = simulator.dc_sensitivity('v(out)', 'R1') for element in analysis.elements.values(): print(element, float(element)) #o# diff --git a/requirements.txt b/requirements.txt index 61d7af492..2f2b8fcd0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ matplotlib>=3.2 numpy>=1.18 ply>=3.11 scipy>=1.4 +tatsu>=5.8.3 diff --git a/setup.cfg b/setup.cfg index 39ac6f8e0..f35bf1102 100644 --- a/setup.cfg +++ b/setup.cfg @@ -36,7 +36,7 @@ platforms = any [options] packages = find: -python_requires = >=3.6 +python_requires = >=3.10 setup_require = setuptools # install_requires should declare the loosest possible dependency versions that are still workable # https://packaging.python.org/discussions/install-requires-vs-requirements/ @@ -48,6 +48,7 @@ install_requires = ply>=3.11 scipy>=1.4 requests>=2.23 + tatsu>=5.8.3 zip_safe = False # Look in MANIFEST.in include_package_data = True diff --git a/tox.ini b/tox.ini index 860ae6359..6cc893196 100644 --- a/tox.ini +++ b/tox.ini @@ -4,8 +4,9 @@ # and then run "tox" from this directory. [tox] -envlist = py38 +envlist = py311 [testenv] commands = pytest unit-test deps = pytest + tatsu diff --git a/unit-test-todo/test_netlist.py b/unit-test-todo/test_netlist.py index 5e89def23..c70e7b31d 100644 --- a/unit-test-todo/test_netlist.py +++ b/unit-test-todo/test_netlist.py @@ -1,5 +1,5 @@ -from PySpice.Netlist import SubCircuit, Circuit -from PySpice.Units import * +from PySpice.Spice.Netlist import SubCircuit, Circuit +from PySpice.Unit import * subcircuit_1N4148 = SubCircuit('1N4148', 1, 2) subcircuit_1N4148.R('1', 1, 2, 5.827E+9) @@ -23,9 +23,9 @@ circuit.C('load', 'out', circuit.gnd, micro(100)) circuit.R('load', 'out', circuit.gnd, kilo(1), ac='1k') -circuit.Cload.plus.add_current_probe(circuit) +circuit['Cload'].plus.add_current_probe(circuit) -simulation = circuit.simulation(temperature=25, nominal_temperature=25, pipe=True) +simulation = circuit.simulator(temperature=25, nominal_temperature=25, pipe=True) simulation.options(filetype='binary') simulation.save('V(in)', 'V(out)') simulation.tran(step_time, end_time) @@ -33,7 +33,7 @@ print(circuit.nodes) for node in circuit.nodes: print(repr(node), ':', ' '.join(element.name for element in node.elements)) -print(circuit.Cload.plus) +print(circuit['Cload'].plus) # print repr(circuit.Cload) # # print circuit.1N4148 # print subcircuit_1N4148['1N4148'] diff --git a/unit-test/Math/test_Calculus.py b/unit-test/Math/test_Calculus.py index dca5f5adc..2b9a7e15b 100644 --- a/unit-test/Math/test_Calculus.py +++ b/unit-test/Math/test_Calculus.py @@ -36,6 +36,8 @@ # From Generation of Finite Difference Formulas on Arbitrary Space Grids # Bengt Fornberg, Mathematics of computation, volume 51, number 184, october 1988 +from PySpice.Probe.WaveForm import WaveForm +from PySpice.Unit import u_s centred_coefficients = { # on a grid -4,...,4 @@ -173,6 +175,32 @@ def test_derivative(self): #################################################################################################### +class TestUFunc(unittest.TestCase): + + ############################################## + + def test_ufunc(self): + waveform_a = WaveForm("A", u_s(1).prefixed_unit, (1, 1)) + waveform_a[0] = 2 + waveform_b = WaveForm("B", u_s(1).prefixed_unit, (1, 1)) + waveform_b[0] = 3 + waveform_result = waveform_a*waveform_b + self.assertEqual(waveform_result[0, 0], 6) + self.assertEqual(waveform_result.prefixed_unit.unit, (u_s(1)*u_s(1)).unit) + self.assertEqual(waveform_result.prefixed_unit.power, (u_s(1)*u_s(1)).power) + + def test_mean(self): + waveform_a = WaveForm("A", u_s(1).prefixed_unit, (1, 1)) + waveform_a[0] = 2 + waveform_b = WaveForm("B", u_s(1).prefixed_unit, (1, 1)) + waveform_b[0] = 3 + waveform_result = waveform_a*waveform_b + waveform_mean = np.mean(waveform_result) + self.assertEqual(waveform_mean.value, 6) + self.assertEqual(waveform_mean.unit, waveform_result.unit) + self.assertEqual(waveform_mean.power, waveform_result.power) + + if __name__ == '__main__': unittest.main() diff --git a/unit-test/Probe/test_WaveForm.py b/unit-test/Probe/test_WaveForm.py index e8b57cd21..c12410e88 100644 --- a/unit-test/Probe/test_WaveForm.py +++ b/unit-test/Probe/test_WaveForm.py @@ -58,11 +58,11 @@ def _test_unit_values(values, true_array): def test(self): np_array1 = np.arange(10) - array1 = u_mV(np_array1) + array1 = u_mv(np_array1) np_raw_array1 = np_array1 / 1000 np_array2 = np.arange(10, 20) - array2 = u_mV(np_array2) + array2 = u_mv(np_array2) np_raw_array2 = np_array2 / 1000 print_rule() @@ -104,7 +104,7 @@ def test(self): self.assertEqual(waveform3.name, '') print_rule() - waveform4 = waveform3.convert(U_uV) + waveform4 = waveform3.convert(U_uv) logger.info('{} {}'.format(waveform4, type(waveform4))) print_rule() @@ -114,7 +114,7 @@ def test(self): # result = super().__array_ufunc__(ufunc, method, *inputs, **kwargs) # File "PySpice/Unit/Unit.py", line 1635, in __array_ufunc__ # raise ValueError - ndarray5 = waveform1 >= 5@u_V + ndarray5 = waveform1 >= 5@u_v np_test.assert_array_equal(ndarray5, np_raw_array1 >= 5) print_rule() @@ -124,7 +124,7 @@ def test(self): self._test_unit_values(waveform4, np_raw_array2 * np_raw_array1) # Fixme: TypeError: unsupported operand type(s) for *: 'PrefixedUnit' and 'PrefixedUnit' # self.assertEqual(waveform4.prefixed_unit, array1.prefixed_unit * array2.prefixed_unit) - mV2 = u_mV(1)*u_mV(1) + mV2 = u_mv(1)*u_mv(1) self.assertEqual(waveform4.prefixed_unit.unit, mV2.unit) self.assertEqual(waveform4.prefixed_unit.power, mV2.power) @@ -133,7 +133,7 @@ def test(self): waveform_mean = np.mean(_) logger.info(repr(waveform_mean)) self._test_unit_values(waveform_mean, np.mean(np_raw_array1 + np_raw_array2)) - _ = u_mV(1) + _ = u_mv(1) self.assertEqual(waveform_mean.unit, _.unit) self.assertEqual(waveform_mean.power, _.power) diff --git a/unit-test/Spice/mosdriver.lib b/unit-test/Spice/mosdriver.lib new file mode 100644 index 000000000..e8bba2042 --- /dev/null +++ b/unit-test/Spice/mosdriver.lib @@ -0,0 +1,16 @@ +* Ideal mos driver + +.include diode.lib +.include source.lib + +.subckt mosdriver hb hi ho hs li lo vdd vss + +xhigh hoi hs hi vss source +rhoi hoi ho 1 +choi ho hs 1e-9 +xlow loi vss li vss source +rloi loi lo 1 +cloi lo vss 1e-9 +dhb vdd hb diode + +.ENDS mosdriver diff --git a/unit-test/Spice/test_BasicElement.py b/unit-test/Spice/test_BasicElement.py index 9b99b80e5..4e7ab2815 100644 --- a/unit-test/Spice/test_BasicElement.py +++ b/unit-test/Spice/test_BasicElement.py @@ -26,6 +26,7 @@ from PySpice.Spice.BasicElement import * from PySpice.Spice.Netlist import Circuit +from PySpice.Spice.Expressions import Symbol from PySpice.Unit import * #################################################################################################### @@ -43,22 +44,29 @@ def _test_spice_declaration(self, element, spice_declaration): def test(self): self._test_spice_declaration(Resistor(Circuit(''), '1', 'n1', 'n2', 100), - 'R1 n1 n2 100') + 'R1 n1 n2 100ohm'.lower()) self._test_spice_declaration(Resistor(Circuit(''), '1', 'n1', 'n2', kilo(1)), - 'R1 n1 n2 1k') + 'R1 n1 n2 1kohm'.lower()) self._test_spice_declaration(Resistor(Circuit(''), '1', 'n1', 'n2', kilo(1), ac=kilo(2), multiplier=2, scale=1.5, temperature=25, device_temperature=26, noisy=True), - 'R1 n1 n2 1k ac=2k dtemp=26 m=2 noisy=1 scale=1.5 temp=25') + 'R1 n1 n2 1kohm ac=2kohm dtemp=26 m=2 noisy=1 scale=1.5 temp=25'.lower()) self._test_spice_declaration(Resistor(Circuit(''), '1', 'n1', 'n2', kilo(1), noisy=False), - 'R1 n1 n2 1k') + 'R1 n1 n2 1kohm'.lower()) + self._test_spice_declaration(Resistor(Circuit(''), '1', 'n1', 'n2', Symbol('r_1')), + 'R1 n1 n2 {r_1}'.lower()) + + self._test_spice_declaration(Diode(Circuit(''), '1', 1, 2, '1N4148'), 'd1 1 2 1n4148') + + self._test_spice_declaration(BipolarJunctionTransistor(Circuit(''), '1', 1, 2, 3, 'bulk', 'DT', 'NPN'), + 'q1 1 2 3 [bulk] dt npn') self._test_spice_declaration(XSpiceElement(Circuit(''), '1', 1, 0, model='cap'), - 'A1 1 0 cap') + 'A1 1 0 cap'.lower()) #################################################################################################### diff --git a/unit-test/Spice/test_Expression.py b/unit-test/Spice/test_Expression.py deleted file mode 100644 index 6215ad905..000000000 --- a/unit-test/Spice/test_Expression.py +++ /dev/null @@ -1,86 +0,0 @@ -#################################################################################################### -# -# PySpice - A Spice Package for Python -# Copyright (C) 2014 Fabrice Salvaire -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -#################################################################################################### - -#################################################################################################### - -import unittest - -#################################################################################################### - -from PySpice.Spice.Expression.Parser import Parser - -#################################################################################################### - -class TestParser(unittest.TestCase): - - ############################################## - - def test_parser(self): - - parser = Parser() - - parser.parse('1') - - parser.parse('.1') - parser.parse('.123') - parser.parse('1.') - parser.parse('1.1') - parser.parse('1.123') - parser.parse('1.e2') - parser.parse('1.e-2') - parser.parse('1.123e2') - parser.parse('1.123e-2') - parser.parse('1.123e23') - parser.parse('1.123e-23') - - parser.parse('-1') - parser.parse('-1.1') - - parser.parse('! rised') - - parser.parse('1 ** 2') - - parser.parse('1 * 2') - parser.parse('1 / 2') - parser.parse('1 % 2') - # parser.parse('1 \\ 2') - parser.parse('1 + 2') - - parser.parse('1 == 2') - parser.parse('1 != 2') - parser.parse('1 >= 2') - parser.parse('1 >= 2') - parser.parse('1 < 2') - parser.parse('1 > 2') - - parser.parse('x && y') - parser.parse('x || y') - - parser.parse('c ? x : y') - - parser.parse('1 * -2') - - parser.parse('x * -y + z') - -#################################################################################################### - -if __name__ == '__main__': - - unittest.main() diff --git a/unit-test/Spice/test_ExpressionParser.py b/unit-test/Spice/test_ExpressionParser.py new file mode 100644 index 000000000..88934b0a6 --- /dev/null +++ b/unit-test/Spice/test_ExpressionParser.py @@ -0,0 +1,86 @@ +import unittest +from PySpice.Spice.EBNFExpressionParser import ExpressionParser +from PySpice.Tools.StringTools import str_spice +from PySpice.Spice.Expressions import Expression +import os + +data = { + "1": "1", + "+1": "1", + "-1": "-1", + "1.": "1", + "+1.": "1", + "-1.": "-1", + ".1": "0.1", + "-.1": "-0.1", + "+.1": "0.1", + ".1E0": "0.1", + "-.1E1": "-1", + "+.1E1": "1", + "1.E0": "1", + "-1.E-1": "-0.1", + "+1.E-1": "0.1", + "1.23": "1.23", + "-1.23": "-1.23", + "+1.23": "1.23", + "1.23E1": "12.3", + "1.23E-1": "0.123", + "1Hz": "1hz", + "2.0kohm": "2kohm", + "1.0Meg": "1meg", + "1.0MegOhm": "1megohm", + "1.0µOhm": "1uohm", + "True?1:0": "if(true, 1, 0)", + "False?1:0": "if(false, 1, 0)", + "a + b": "a + b", + "a - b": "a - b", + "(a + b)": "a + b", + "(a - b)": "a - b", + "a * b": "a * b", + "a / b": "a / b", + "{(a * b)}": "a * b", + "{(a / b)}": "a / b", + "{(a / b) * c}": "(a / b) * c", + "{if(1<2,1,0)}": "if(1 < 2, 1, 0)", + "{if((1<2),(1),(0))}": "if(1 < 2, 1, 0)", + "{2<=1?1:0}": "if(2 <= 1, 1, 0)", + "{a + (b + c)}": "a + (b + c)", + "{(a + b) + c}": "(a + b) + c", + "{1}": "1", + "{1+2}": "1 + 2", + "{(1+2)}": "1 + 2", + "{(1+2) + 3}": "(1 + 2) + 3", + "{(1+2) * 3}": "(1 + 2) * 3", + "{(1+2) * (3 + 7)}": "(1 + 2) * (3 + 7)", + "{(1+2) * -(3 + 7)}": "(1 + 2) * -(3 + 7)", + "{(1+a) * -(b + 7)}": "(1 + a) * -(b + 7)", + "{(1+sin(3.14)) * -(3 + 7)}": "(1 + sin(3.14)) * -(3 + 7)", + "{(1+v(a)) * -(3 + 7)}": "(1 + v(a)) * -(3 + 7)", + "{atan2(asin(b), ln(c))}": "atan2(asin(b), ln(c))", + "{atan2(asin(b) - 7, ln(c) + 5)}": "atan2(asin(b) - 7, ln(c) + 5)", + "{ddx(asin, ln(c) + 5)}": "ddx(asin, ln(c) + 5)", + "{if(True, 1, 2)}": "if(true, 1, 2)", + "{if(2 < 3, 1, 2)}": "if(2 < 3, 1, 2)", + "{if((2 < 3) | False , 1, 2)}": "if((2 < 3) | false, 1, 2)", + "{(2 < 3) | False ? True ? 3: 4: 2}": "if((2 < 3) | false, if(true, 3, 4), 2)", + "{(2 < 3) | False ? True ? 3: sin(4): 2}": "if((2 < 3) | false, if(true, 3, sin(4)), 2)", + "{(2 < 3) | False ? (True ? 3: sin(4)): 2}": "if((2 < 3) | false, if(true, 3, sin(4)), 2)", + "{(2 < 3) & ( False | True) ? (True ? 3: sin(4)): 2}": "if((2 < 3) & (false | true), if(true, 3, sin(4)), 2)", + "{~(2 < 3) & ~( False | True) ? (True ? 3: sin(4)): 2}": "if(~(2 < 3) & ~(false | true), if(true, 3, sin(4)), 2)", + "{limit(3, 2, a)}": "limit(3, 2, a)", + "{limit(3, 2, a)}": "limit(3, 2, a)" +} + +class TestExpressionParser(unittest.TestCase): + def test_parser(self): + # ExpressionParser._regenerate() + for case, expr in data.items(): + expr_i = ExpressionParser.parse(source=case) + case_i = str_spice(expr_i) + if expr != case_i: + expr_i = ExpressionParser.parse(source=case) + case_i = str_spice(expr_i) + self.assertEqual(expr, case_i) + +if __name__ == '__main__': + unittest.main() diff --git a/unit-test/Spice/test_Expressions.py b/unit-test/Spice/test_Expressions.py new file mode 100644 index 000000000..0f297cada --- /dev/null +++ b/unit-test/Spice/test_Expressions.py @@ -0,0 +1,71 @@ +#################################################################################################### +# +# PySpice - A Spice Package for Python +# Copyright (C) 2014 Fabrice Salvaire +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +#################################################################################################### + +#################################################################################################### + +import unittest + +#################################################################################################### + +from PySpice.Spice.Netlist import Circuit +from PySpice.Spice.Expressions import * +import math as m + +#################################################################################################### + + +class TestExpression(unittest.TestCase): + + def test_symbol(self): + x = Symbol('x') + V_3 = V(Symbol("3")) + cos_V_3 = Cos(V_3) + values = {str(V_3): 25} + self.assertAlmostEqual(m.cos(25), cos_V_3(**values)) + y = Symbol('y') + add = Add(x, y) + add_operator = x + y + self.assertEqual("(x + y)", add) + self.assertEqual("(x + y)", add_operator) + add_float = x + 5. + self.assertEqual("(x + 5.0)", add_float) + add_float = 5. + x + self.assertEqual("(5.0 + x)", add_float) + V_5 = V("5") + self.assertEqual("v(5)",V_5) + self.assertEqual("cos(27)", Cos(27)) + self.assertAlmostEqual(m.cos(27), Cos(27)()) + self.assertTrue(Xor(True, False)()) + self.assertFalse(Xor(True, True)()) + self.assertTrue(Xor(False, True)()) + self.assertFalse(Xor(False, False)()) + + def test_expression(self): + print(If(V("v_sw", "gnd") < 2. * Symbol("v_3v") / 3., 1, 1e-6)) + circuit = Circuit("Simulation") + circuit.BehavioralResistor('b', + 'na', + 'nb', + resistance_expression=If(V("v_sw", "gnd") < 2. * Symbol("v_3v") / 3., 1, 1e-6)) + print(circuit) + +if __name__ == '__main__': + + unittest.main() diff --git a/unit-test/Spice/test_HighLevelElement.py b/unit-test/Spice/test_HighLevelElement.py index e61769ef6..b2b5a163c 100644 --- a/unit-test/Spice/test_HighLevelElement.py +++ b/unit-test/Spice/test_HighLevelElement.py @@ -45,30 +45,46 @@ def test(self): PieceWiseLinearVoltageSource( Circuit(''), 'pwl1', '1', '0', - values=[(0, 0), (10@u_ms, 0), (11@u_ms, 5@u_V), (20@u_ms, 5@u_V)], + values=[(0, 0), (10@u_ms, 0), (11@u_ms, 5@u_v), (20@u_ms, 5@u_v)], ), - 'Vpwl1 1 0 PWL(0s 0V 10ms 0V 11ms 5V 20ms 5V)', + 'vpwl1 1 0 pwl(0s 0v 10ms 0v 11ms 5v 20ms 5v r=0s td=0.0s)', ) self._test_spice_declaration( PieceWiseLinearVoltageSource( Circuit(''), 'pwl1', '1', '0', - values=[(0, 0), (10@u_ms, 0), (11@u_ms, 5@u_V), (20@u_ms, 5@u_V)], - repeat_time=12@u_ms, delay_time=34@u_ms, + values=[(0, 0), (10@u_ms, 0), (11@u_ms, 5@u_v), (20@u_ms, 5@u_v)], + repeat_time=12@u_ms, time_delay=34@u_ms, ), - 'Vpwl1 1 0 PWL(0s 0V 10ms 0V 11ms 5V 20ms 5V r=12ms td=34ms)', + 'vpwl1 1 0 pwl(0s 0v 10ms 0v 11ms 5v 20ms 5v r=12ms td=34ms)', ) self._test_spice_declaration( PieceWiseLinearVoltageSource( Circuit(''), 'pwl1', '1', '0', - values=[(0, 0), (10@u_ms, 0), (11@u_ms, 5@u_V), (20@u_ms, 5@u_V)], - repeat_time=12@u_ms, delay_time=34@u_ms, - dc=50@u_V, + values=[(0, 0), (10@u_ms, 0), (11@u_ms, 5@u_v), (20@u_ms, 5@u_v)], + repeat_time=12@u_ms, time_delay=34@u_ms, + dc=50@u_v, ), - 'Vpwl1 1 0 DC 50V PWL(0s 0V 10ms 0V 11ms 5V 20ms 5V r=12ms td=34ms)', + 'vpwl1 1 0 dc 50v pwl(0s 0v 10ms 0v 11ms 5v 20ms 5v r=12ms td=34ms)', + ) + + self._test_spice_declaration( + PulseVoltageSource( + Circuit(''), + 'pulsed1', '1', '0', + initial_value=0, + pulse_value='{high}', + pulse_width=5, + period=10, + delay_time=1, + rise_time=0.1, + fall_time=0.1, + phase=None + ), + 'vpulsed1 1 0 pulse(0v {high} 1s 0.1s 0.1s 5s 10s)', ) #################################################################################################### diff --git a/unit-test/Spice/test_Netlist.py b/unit-test/Spice/test_Netlist.py index 48ac90f95..fc114d5ea 100644 --- a/unit-test/Spice/test_Netlist.py +++ b/unit-test/Spice/test_Netlist.py @@ -21,6 +21,7 @@ #################################################################################################### import unittest +import shutil #################################################################################################### @@ -78,10 +79,10 @@ class TestSubCircuit(TestNetlist): def test(self): spice_declaration = """ -.subckt VoltageDivider input output_plus output_minus -R1 input output_plus 9kOhm -R2 output_plus output_minus 1kOhm -.ends VoltageDivider +.subckt voltagedivider input output_plus output_minus +r1 input output_plus 9kohm +r2 output_plus output_minus 1kohm +.ends voltagedivider """ self._test_spice_declaration(VoltageDivider(), spice_declaration) @@ -95,16 +96,16 @@ def test_basic(self): spice_declaration = """ .title Voltage Divider -Vinput in 0 10V -R1 in out 9kOhm -R2 out 0 1kOhm +vinput in 0 dc 10v +r1 in out 9kohm +r2 out 0 1kohm """ # .end circuit = Circuit('Voltage Divider') circuit.V('input', 'in', circuit.gnd, '10V') circuit.R(1, 'in', 'out', 9@u_kΩ) - circuit.R(2, circuit.out, circuit.gnd, 1@u_kΩ) # out node is defined + circuit.R(2, 'out', circuit.gnd, 1@u_kΩ) # out node is defined self._test_spice_declaration(circuit, spice_declaration) circuit = VoltageDividerCircuit() @@ -159,8 +160,8 @@ def test_raw_spice(self): spice_declaration = """ .title Voltage Divider R2 out 0 1kOhm -Vinput in 0 10V -R1 in out 9kOhm +vinput in 0 dc 10v +r1 in out 9kohm """ # .end @@ -178,7 +179,7 @@ def test_keyword_clash(self): model = circuit.model('Diode', 'D', is_=1, rs=2) self.assertEqual(model.is_, 1) self.assertEqual(model['is'], 1) - self.assertEqual(str(model), '.model Diode D (is=1 rs=2)') + self.assertEqual(str(model), '.model diode D (is=1 rs=2)') ############################################## @@ -189,7 +190,7 @@ def test_param(self): .param pippo=5 .param po=6 .param pp=7.8 -.param pap={AGAUSS(pippo, 1 , 1.67)} +.param pap={agauss(pippo, 1 , 1.67)} .param pippp={pippo + pp} .param p={pp} """ @@ -206,6 +207,28 @@ def test_param(self): # circuit.parameter('pop', 'pp + p') self._test_spice_declaration(circuit, spice_declaration) + ############################################## + + def test_dc_list(self): + from PySpice.Spice.Xyce.RawFile import RawFile + circuit = Circuit('DC list') + circuit.V('input', 'in', circuit.gnd, '10V') + circuit.R('load', 'in', circuit.gnd, 9@u_kΩ) + simulator = circuit.simulator(simulator='xyce-serial', working_directory='.') + simulator.save('all') + if shutil.which('xyce'): + result = simulator.dc(rload=slice(1, 3, 1), vinput=(1, 2, 3, 4)) + self.assertTrue(os.path.exists('input.cir') and os.path.isfile('input.cir')) + self.assertTrue(os.path.exists('output.raw') and os.path.isfile('output.raw')) + data = RawFile(filename="output.raw") + print(data.nodes()) + result = simulator.dc_sensitivity('v(in)') + self.assertTrue(os.path.exists('input.cir') and os.path.isfile('input.cir')) + self.assertTrue(os.path.exists('output.raw') and os.path.isfile('output.raw')) + data = RawFile(filename="output.raw") + print(data.nodes()) + + #################################################################################################### if __name__ == '__main__': diff --git a/unit-test/SpiceParser/diode.lib b/unit-test/SpiceParser/diode.lib new file mode 100644 index 000000000..44caa818e --- /dev/null +++ b/unit-test/SpiceParser/diode.lib @@ -0,0 +1,3 @@ +* Diode lib + +.model diode D (is=1.038e-15 n=1 tt=20e-9 cjo=5e-12 rs=0.50 bv=130) diff --git a/unit-test/SpiceParser/ipwl.txt b/unit-test/SpiceParser/ipwl.txt new file mode 100644 index 000000000..657139adf --- /dev/null +++ b/unit-test/SpiceParser/ipwl.txt @@ -0,0 +1,5 @@ +0.00 0.00 +2.00 3.00 +3.00 2.00 +4.00 2.00 +4.01 5.00 diff --git a/unit-test/SpiceParser/mosdriver.lib b/unit-test/SpiceParser/mosdriver.lib index c3332304c..e8bba2042 100644 --- a/unit-test/SpiceParser/mosdriver.lib +++ b/unit-test/SpiceParser/mosdriver.lib @@ -1,14 +1,16 @@ * Ideal mos driver -.subckt mosdriver hb hi ho hs li lo vdd vss -.model diode D (is=1.038e-15 n=1 tt=20e-9 cjo=5e-12 rs=0.50 bv=130) +.include diode.lib +.include source.lib + +.subckt mosdriver hb hi ho hs li lo vdd vss -bhigh hoi hs v={if(v(hi, vss) > 0.5, 5, 0)} smoothbsrc=1 +xhigh hoi hs hi vss source rhoi hoi ho 1 choi ho hs 1e-9 -blow loi vss v={if(v(li, vss) > 0.5, 5, 0)} smoothbsrc=1 +xlow loi vss li vss source rloi loi lo 1 cloi lo vss 1e-9 dhb vdd hb diode -.ENDS mosdriver \ No newline at end of file +.ENDS mosdriver diff --git a/unit-test/SpiceParser/source.lib b/unit-test/SpiceParser/source.lib new file mode 100644 index 000000000..bb5224192 --- /dev/null +++ b/unit-test/SpiceParser/source.lib @@ -0,0 +1,5 @@ +* Behavioural source subcircuit + +.subckt source vh vl hi lo +bhigh vh vl v={if(v(hi, lo) > 0.5, 5, 0)} smoothbsrc=1 +.ends diff --git a/unit-test/SpiceParser/test_SpiceParser.py b/unit-test/SpiceParser/test_SpiceParser.py index fb498a41f..263ddbfd4 100644 --- a/unit-test/SpiceParser/test_SpiceParser.py +++ b/unit-test/SpiceParser/test_SpiceParser.py @@ -21,15 +21,397 @@ #################################################################################################### from pathlib import Path -import os +import shutil import unittest #################################################################################################### from PySpice.Spice.Netlist import Circuit -from PySpice.Spice.Parser import SpiceParser +from PySpice.Spice.EBNFSpiceParser import SpiceParser +import os -#################################################################################################### +data = """* Data test +*More notes + +.MODEL 2N2222 NPN +Q2N2222 Nc Nb Ne 2N2222 + +BG1 IOUTp IOUTm I={TABLE {V(VCp,VCm)} = ++(0, 12.09e-6) ++(26.6667, 0.0002474) ++(53.3333, 0.00029078) ++(71.1111, 0.0003197) ++(72, 0.00032115) ++(73.7778, 0.00032404) ++(75.5556, 0.00032693) ++(77.3333, 0.00032982) ++(79.1111, 0.00033272) ++(80, 0.00275)} + +G1N 21 98 (6,15) 26E-6 + +E23 21 98 (6,15) 26E-6 + +BG2 IOUT- IOUT+ I={IF( (V(VC+,VC-)<=0),0,GAIN*V(VC+,VC-) )} + +FB 7 99 POLY(5) VB VC VE VLP VLN 0 147.3E6 -100E6 100E6 100E6 -100E6 + +R 1 2 600000 MDR +.MODEL MDR R TC1=0 TC2=0 + +.SUBCKT AND2 A B Y +BEINT YINT 0 V = {IF(V(A) > 0.5 & V(B) > 0.5, 1, 0)} +RINT YINT Y 1 +CINT Y 0 1n +.ENDS AND2 + +E1 3 0 5 0 10 + +G2N 21 3 POLY(1) 1 3 0 1.57E-6 -0.97e-7 + +.MODEL DI_HBS410 D ( IS=130.2n RS=6.366m BV=1.229k IBV=1m ++ CJO=69.01p M=0.295 N=2.075 TT=4.32µ) + +.MODEL npnct NPN + +.PARAM t3 = {True?1:0} +.PARAM f3 = {False?1:0} +.PARAM a = {a + b} +.PARAM b = {a - b} +.PARAM c = {(a + b)} +.PARAM d = {(a - b)} +.PARAM a = {a * b} +.PARAM b = {a / b} +.PARAM c = {(a * b)} +.PARAM d = {(a / b)} +.PARAM d = {(a / b) * c} +.PARAM t3 = {if(1<2,1,0)} +.PARAM t3 = {if((1<2),(1),(0))} +.PARAM f3 = {2<=1?1:0} +.PARAM c = {a + (b + c)} +.PARAM d = {(a + b) + c} +.PARAM d = 1 +.PARAM d = {1} +.PARAM d = {1+2} +.PARAM d = {(1+2)} +.PARAM d = {(1+2) + 3} +.PARAM d = {(1+2) * 3} +.PARAM d = {(1+2) * (3 + 7)} +.PARAM d = {(1+2) * -(3 + 7)} +.PARAM d = {(1+a) * -(b + 7)} +.PARAM d = {(1+sin(3.14)) * -(3 + 7)} +.PARAM d = {(1+v(a)) * -(3 + 7)} +.PARAM d = {atan2(asin(b), ln(c))} +.PARAM d = {atan2(asin(b) - 7, ln(c) + 5)} +.PARAM d = {ddx(asin, ln(c) + 5)} +.PARAM d = {if(True, 1, 2)} +.PARAM d = {if(2 < 3, 1, 2)} +.PARAM d = {if((2 < 3) | False , 1, 2)} +.PARAM d = {(2 < 3) | False ? True ? 3: 4: 2} +.PARAM d = {(2 < 3) | False ? True ? 3: sin(4): 2} +.PARAM d = {(2 < 3) | False ? (True ? 3: sin(4)): 2} +.PARAM d = {(2 < 3) & ( False | True) ? (True ? 3: sin(4)): 2} +.PARAM d = {~(2 < 3) & ~( False | True) ? (True ? 3: sin(4)): 2} +.PARAM d = {limit(3, 2, a)} +.PARAM d = {limit(3, 2, a)} +E_ABM12 N145529 0 VALUE={ if((V(CTRL_LIMIT)<0.3) ,1,0) } + +Q1 col base eb QPWR 0.1 + +.MODEL QPWR NPN + +*Another note +Q2 10 2 9 PNP1 + +Q8 Coll Base Emit VBIC13MODEL3 temp=0 +Q9 Coll Base Emit [Subst] DT VBIC13MODEL4 +Q10 Coll Base Emit [Subst] DT HICUMMMODEL1 + +.MODEL NPN2 NPN +.MODEL VBIC13MODEL2 NPN +.MODEL LAXPNP PNP +.MODEL PNP1 PNP + +Q12 14 2 0 1 NPN2 2.0 +Q6 VC 4 11 [SUB] LAXPNP +Q7 Coll Base Emit DT VBIC13MODEL2 + + +M1 42 43 17 17 NOUT L=3U W=700U + +.MODEL NOUT NMOS + +BG3 IOUT- IOUT+ I={IF( (V(VC+,VC-) <= 0) , 0 , 1 )} +BG55 IOUT- IOUT+ I={IF( (V(VC+,VC-)<=0),0,GAIN*V(VC+,VC-) )} + +.SUBCKT VCCS_LIM_CLAW+_0_OPA350 VCp VCm IOUTp IOUTm +BG4 IOUTp IOUTm I={TABLE {V(VCp,VCm)} = ++(0, 12.09e-6) ++(26.6667, 0.0002474) ++(53.3333, 0.00029078) ++(71.1111, 0.0003197) ++(72, 0.00032115) ++(73.7778, 0.00032404) ++(75.5556, 0.00032693) ++(77.3333, 0.00032982) ++(79.1111, 0.00033272) ++(80, 0.00275)} +.ENDS + + +.PARAM GAIN = 1E-3 +Sw 14 2 12 11 SMOD + +.MODEL SMOD SWITCH +.MODEL JFAST PJF +.MODEL JNOM NJF + +JIN 100 1 0 JFAST +J13 22 14 23 ++ JNOM 2.0 ; check + +.lib models.lib nom + +J1 1 2 0 2N5114 + +.MODEL 2n5114 NJF + +* Library file res.lib +.lib low +.param rval=2 +r3 2 0 9 +.endl low + +* Library file res.lib +.lib high +.param rval=2 +r3 2 0 9 +.Model ZXTN619MA NPN ; ## Description ## ## Effect ## + ; ## DC Forward Parameters ## ++ IS = 5.8032E-13 ; transport saturation current + +.SUBCKT PLRD IN1 IN2 IN3 OUT1 ++ PARAMS: MNTYMXDELY=0 IO_LEVEL=1 +* +.ENDS + +.endl + +.LIB 'models.lib' low +.LIB "models.lib" low +.LIB "path/models.lib" high + +.lib nom +.param rval=3 +r3 2 0 8 +.endl nom + +.PARAM KG ={1e-4/(2-VTH1)**2} ; rnom/(VON-VTH1)^2 + +.SUBCKT FILTER1 INPUT OUTPUT PARAMS: CENTER=200kHz, ++ BANDWIDTH=20kHz +* +.ENDS + +XFELT 1 2 FILTER1 PARAMS: CENTER=200kHz + +.MODEL Rmod1 RES (TC1=6e-3 ) + + +.SUBCKT MYGND 25 28 7 MYPWR +.ENDS + +.SUBCKT UNITAMP 1 2 +.ENDS + + +X12 100 101 200 201 DIFFAMP +XBUFF 13 15 UNITAMP +XFOLLOW IN OUT VCC VEE OUT OPAMP +XNANDI 25 28 7 MYPWR MYGND PARAMS: IO_LEVEL=2 + +.SUBCKT OPAMP 10 12 111 112 13 +* +.ENDS + +.SUBCKT diffamp 1 2 3 4 +.ENDS + + +B4 5 0 V={Table {V(5)}=(0,0) (1.0,2.0) (2.0,3.0) (3.0,10.0)} + +B1 2 0 V={sqrt(V(1))} +B2 4 0 V={V(1)*TIME} +B3 4 2 I={I(V1) + V(4,2)/100} +B5 6 0 V=tablefile("file.dat") +B6 7 0 I=tablefile("file.dat") +Bcomplicated 1 0 V={TABLE {V(5)-V(3)/4+I(V6)*Res1} = (0, 0) (1, 2) (2, 4) (3, 6)} + +.data check r3 r4 5 6 9 23 .enddata + +.SUBCKT FILTER1 INPUT OUTPUT PARAMS: CENTER=200kHz, ++ BANDWIDTH=20kHz +XFOLLOW IN OUT VCC VEE OUT OPAMP + +.SUBCKT OPAMP 10 12 111 112 13 +* +.ENDS +* +.ENDS + +.SUBCKT 74LS01 A B Y ++ PARAMS: MNTYMXDELY=0 IO_LEVEL=1 +* +.ENDS + +IPWL1 1 0 PWL 0S 0A 2S 3A 3S 2A 4S 2A 4.01S 5A r=2s td=1 +IPWL2 2 0 PWL FILE "unit-test/SpiceParser/ipwl.txt" +VPWL3 3 0 PWL file "unit-test/SpiceParser/vpwl.csv" +VPWL4 4 0 PWL FILE unit-test/SpiceParser/vpwl.csv + +ISLOW 1 22 SIN(0.5 1.0ma 1KHz 1ms) +IPULSE 1 3 PULSE(-1 1 2ns 2ns 2ns 50ns 100ns) +IPAT 2 4 PAT(5 0 0 1n 2n 5n b0101 1) +IPAT2 2 4 PAT(5 0 0 1n 2n 5n b0101) + +M5 4 12 3 0 PNOM L=20u W=10u +M3 5 13 10 0 PSTRONG +M6 7 13 10 0 PSTRONG M=2 IC=1, 3 , 2 +M8 10 12 100 100 NWEAK L=30u W=20u ++ AD=288p AS=288p PD=60u PS=60u NRD=14 NRS=24 + +.MODEL PNOM PMOS +.MODEL NWEAK NMOS +.MODEL PSTRONG PMOS + +L1 1 5 3.718e-08 +LM 7 8 L=5e-3 M=2 +LLOAD 3 6 4.540mH IC=2mA +Lmodded 3 6 indmod 4.540mH +.model indmod L (L=.5 TC1=0.010 TC2=0.0094) + + +.MODEL VBIC13MODEL3 NPN +.MODEL VBIC13MODEL4 PNP +.MODEL HICUMMMODEL1 NPN + + +.MODEL sw Switch +*Pending adding the nonlinear element +*S3 1 2 SW OFF CONTROL={if(time>0.001,1,0)} + +*.MODEL swi Switch +*.MODEL swv Switch + +*S1 1 2 SWI OFF CONTROL={I(VMON)} +*SW2 1 2 SWV OFF CONTROL={V(3)-V(4)} + +RM 4 5 R=4e3 M=2 + +r1 2 1 {r1 * rand( ; ++ )} TEMP=27 +r2 2 1 {r2} ; check +r3 2 1 {r3} +r4 0 1 {r4} + +vin 1 0 10 +Vcntrl1 cathode 0 .23mV +Vcintrl2 anode cathode 5 +Vsense 4 0 7mil +V5 3 0 0. + +EBUFFER 1 2 10 11 5.0 +ESQROOT 5 0 VALUE = {5V*SQRT(V(3,2))} +ET2 2 0 TABLE {V(ANODE,CATHODE)} = (0,0) (30,1) +EP1 5 1 POLY(2) 3 0 4 0 0 .5 .5 + +r13 13 0 1 + +r15 100 101 23k ; f load + +r16 100 0 2 + +RLOAD 3 6 RTCMOD 4.540 TEMP=85 + +.MODEL RTCMOD R (TC1=.01 TC2=-.001) +*Pending managing this model +*RSEMICOND 2 0 RMOD L=1000u W=1u +*.MODEL RMOD R (RSH=1) + +.model switch d + +CM12 2 4 5.288e-13 +CLOAD 1 0 4.540pF IC=1.5V +CFEEDBACK 2 0 CMOD 1.0pF +*Pending managing this model. +*CAGED 2 3 4.0uF D=0.0233 AGE=86200 +CSOLDEP 3 0 C={ca*(c0+c1*tanh((V(3,0)-v0)/v1))} +*Pending managing this model. +*CSOLDEPQ 3 0 Q={ca*(c1*v1*ln(cosh((v(3,0)-v0)/v1))+c0*v(3,0))} + +.MODEL CMOD CAP + +FSENSE 1 2 VSENSE 10.0 +FAMP 13 0 POLY(1) VIN 0 500 +FNONLIN 100 101 POLY(2) VCNTRL1 VCINTRL2 0.0 13.6 0.2 0.005 + +.model dmod d + +DCLAMP 1 0 DMOD + +D2 13 100 SWITCH 1.5 + +GBUFFER 1 2 10 11 5.0 +GPSK 11 6 VALUE = {5MA*SIN(6.28*10kHz*TIME+V(3))} +GA2 2 0 TABLE {V(5)} = (0,0) (1,5) (10,5) (11,0) + +.param r1 = 1 r2= 2 r3 =3 r4 = {4mil} + +HSENSE 1 2 VSENSE 10.0 +HAMP 13 0 POLY(1) VIN 0 500 +HNONLIN 100 101 POLY(2) VCNTRL1 VCINTRL2 0.0 13.6 0.2 0.005 + +.data check r3 r4 5 6 .enddata +.data recheck r3 r4 5 6 ;recheck +.enddata + +.ac LIN 1 3 5 +.ac DEC 4 3 7 +.ac data=check +.data test ++ r1 r2 ++ 8 4mil ++ 9 4.000J ++ 0.5 0+3.0J ++ .6+.7J 4.3e6 +*For test purposes +.enddata + +.dc LIN VCNTRL1 0 10 1 +.dc VCINTRL2 0 10 2 ++ r1 3 10 1 +.dc r1 LIST 9 10 2 +.dc DEC r3 1 3 9 +.dc LIN r1 3. 4 5 ++ DEC r2 7 8 9 +.IC V(1) = 1 +.DCVOLT V(1) = {2 * 5 ++ > 3 ? abs ; ++ ( ;another ++ sin(12) ; change of line ++ + 18) : atan2((45 - 3), 43)} +.IC 1 { ++ r1 ++ } 2 3 + +*.EMBEDDEDSAMPLING +*+ param=R1 +*+ type=NORMAL +*+ means=3k +*+ std_deviations = 1k + +.end +""" path = Path(__file__).parent @@ -42,39 +424,523 @@ #################################################################################################### def circuit_gft(prb): - circuit_file = SpiceParser(source=prb[0]) - circuit = circuit_file.build_circuit() + parser = SpiceParser.parse(source=prb[0]) + circuit = parser.build() circuit.parameter('prb', str(prb[1])) # Fixme: simulate with Xyce, CI !!! simulator = circuit.simulator(simulator='xyce-serial') - simulator.save(['all']) + simulator.save('all') return simulator #################################################################################################### class TestSpiceParser(unittest.TestCase): + @staticmethod + def _getdir(pathname): + import os + curdir = os.path.abspath(os.curdir) + if curdir.endswith(pathname): + return curdir + else: + return os.path.abspath('unit-test/SpiceParser') ############################################## - @unittest.skip('') + #@unittest.skip('') def test_parser(self): - for source in (hsop77, hsada4077): - results = list(map(circuit_gft, [(source, -1), (source, 1)])) - self.assertEqual(len(results), 2) - values = str(results[0]) - self.assertNotRegex(values, r'(\.ic)') + # SpiceParser._regenerate() + results = list(map(circuit_gft, [(data, -1), (data, 1)])) + self.assertEqual(len(results), 2) + values = str(results[0]) + self.assertNotRegex(values, r'(\.ic)') + circuit_gft(values) + self.assertNotRegex(values, r'([Nn][Oo][Nn][Ee])') - ############################################## + def test_libraryRaw(self): + from PySpice.Spice.Library import SpiceLibrary + spice_library = SpiceLibrary() + spice_library.insert(""" +.subckt test1 1 2 3 +xt 1 2 3 check +.ends - @unittest.skip('') - def test_subcircuit(self): - circuit = Circuit('') - circuit.include('.../mosdriver.lib') - circuit.X('test', 'mosdriver', '0', '1', '2', '3', '4', '5') - circuit.BehavioralSource('test', '1', '0', voltage_expression='if(0, 0, 1)', smoothbsrc=1) +.subckt check 5 6 7 +.ends +""") + circuit_source = """.title Lib test +Xtest 1 2 3 test1 +.end +""" + circuit = SpiceParser.parse(source=circuit_source, library=spice_library) + expected = """.title Lib test + +.subckt check 5 6 7 + +.ends check + +.subckt test1 1 2 3 +xt 1 2 3 check +.ends test1 + +xtest 1 2 3 test1 +""" + result = str(circuit.build()) + self.assertEqual(expected, result) + + def test_library(self): + from PySpice.Spice.Library import SpiceLibrary + libraries_path = self._getdir('unit-test/SpiceParser') + spice_library = SpiceLibrary(libraries_path) + circuit = Circuit('MOS Driver') + circuit.include(spice_library) + x_mos = circuit.X('driver', + 'mosdriver', + 'hb', + 'hi', + 'ho', + 'hs', + 'li', + 'lo', + 'vdd', + 'vss') + circuit.R('hb', 'hb', 0, 1e12) + circuit.R('hi', 'hi', 0, 1e12) + circuit.R('ho', 'ho', 0, 1e12) + circuit.R('hs', 'hs', 0, 1e12) + circuit.R('li', 'li', 0, 1e12) + circuit.R('lo', 'lo', 0, 1e12) + circuit.R('test_temp', 'vss', 0, 10, tc=(4, 5)) + circuit.B('test_tc', 'vdd', 0, v=5, tc=(7, 8)) + simulator = circuit.simulator(simulator='xyce-serial', + temperature=25, + nominal_temperature=25, + working_directory='.') + simulator.options('device smoothbsrc=1') + if shutil.which('xyce'): + simulator.transient(1e-9, 1e-3) + + def test_library_direct(self): + from PySpice.Spice.Library import SpiceLibrary + + library = SpiceLibrary() + library.insert(""" +* ADA4077 SPICE DMod model Typical values +* Description: Amplifier +* Generic Desc: 30V, BIP, OP, Low Noise, Low THD, 2X +* Developed by: RM ADSJ +* Revision History: 1.0 03/31/2015 - Updated to new header style +* 0.0 (11/2012) +* Copyright 2008, 2012,2015 by Analog Devices +* +* Refer to "README.DOC" file for License Statement. Use of this +* model indicates your acceptance of the terms and provisions in +* the License Statement. +* +* Node Assignments +* noninverting input +* | inverting input +* | | positive supply +* | | | negative supply +* | | | | output +* | | | | | +* | | | | | +.SUBCKT ADA4077 1 2 99 50 45 +* +*INPUT STAGE +**4input sacled poly +Q1 15 7 60 NIX +Q2 6 2 61 NIX +IOS 1 2 1.75E-10 +I1 5 50 77e-6 +EOS 7 1 POLY(4) (14,98) (73,98) (81,98) (70,98) 10E-6 1 1 1 1 +RC1 11 15 2.6E4 +RC2 11 6 2.6E4 +RE1 60 5 0.896E2 +RE2 61 5 0.896E2 +C1 15 6 4.25E-13 +D1 50 9 DX +V1 5 9 DC 1.8 +D10 99 10 DX +V6 10 11 1.3 +* +* CMRR +* +ECM 13 98 POLY(2) (1,98) (2,98) 0 7.192E-4 7.192E-4 +RCM1 13 14 2.15E2 +RCM2 14 98 5.31E-3 +CCM1 13 14 1E-6 +* +* PSRR +* +EPSY 72 98 POLY(1) (99,50) -1.683 0.056 +CPS3 72 73 1E-6 +RPS3 72 73 7.9577E+1 +RPS4 73 98 6.5915E-4 +* +* EXTRA POLE AND ZERO +* +G1 21 98 (6,15) 26E-6 +R1 21 98 9.8E4 +R2 21 22 9E6 +C2 22 98 1.7614E-12 +D3 21 99 DX +D4 50 21 DX +* +* VOLTAGE NOISE +* +VN1 80 98 0 +RN1 80 98 16.45E-3 +HN 81 98 VN1 6 +RN2 81 98 1 +* +* FLICKER NOISE +* +D5 69 98 DNOISE +VSN 69 98 DC .60551 +H1 70 98 VSN 30.85 +RN 70 98 1 +* +* INTERNAL VOLTAGE REFERENCE +* +EREF 98 0 POLY(2) (99,0) (50,0) 0 .5 .5 +GSY 99 50 POLY(1) (99,50) 130E-6 1.7495E-10 +* +* GAIN STAGE +* +G2 98 25 (21,98) 1E-6 +R5 25 98 9.9E7 +CF 45 25 2.69E-12 +V4 25 33 5.3 +D7 51 33 DX +EVN 51 98 (50,99) 0.5 +V3 32 25 5.3 +D6 32 97 DX +EVP 97 98 (99,50) 0.5 +* +* OUTPUT STAGE +* +Q3 45 41 99 POUT +Q4 45 43 50 NOUT +RB1 40 41 9.25E-4 +RB2 42 43 9.25E-4 +EB1 99 40 POLY(1) (98,25) 0.7153 1 +EB2 42 50 POLY(1) (25,98) 0.7153 1 +* +* MODELS +* +.MODEL NIX NPN (BF=71429,IS=1E-16) +.MODEL POUT PNP (BF=200,VAF=50,BR=70,IS=1E-15,RC=71.25) +.MODEL NOUT NPN (BF=200,VAF=50,BR=22,IS=1E-15,RC=29.2) +.MODEL DX D(IS=1E-16, RS=5, KF=1E-15) +.MODEL DNOISE D(IS=1E-16,RS=0,KF=1.095E-14) +.ENDS ADA4077 +*$ + +.MODEL DI_1N5819 D ( IS=390n RS=0.115 BV=40.0 IBV=1.00m ++ CJO=203p M=0.333 N=1.70 TT=4.32u ) +""") + self.assertEqual(len(list(library.subcircuits)), 1) + self.assertEqual(len(list(library.models)), 1) + + netlist = SpiceParser.parse(source=""" +XU5A np nn ncc nss nsee ADA4077 +Dup no ncc DI_1N5819 +Ddown 0 no DI_1N5819 +""", library=library) + circuit = netlist.build() + + def test_library_str(self): + from PySpice.Spice.Library import SpiceLibrary + libraries_path = self._getdir('unit-test/SpiceParser') + spice_library = SpiceLibrary() + spice_library.insert(""" +.subckt mosdriver hb hi ho hs li lo vdd vss + +.subckt source vh vl hi lo +vhl vh vl 1 +vhilo hi lo 2 +.ends + +xhigh hb vss hi vss source +xlow ho vss hs vss source +rloi li lo 1 + +.model diode d + +.ENDS mosdriver +""") + circuit = Circuit('MOS Driver') + circuit.include(spice_library) + circuit.X('driver', + 'mosdriver', + 'hb', + 'hi', + 'ho', + 'hs', + 'li', + 'lo', + 'vdd', + 'vss') + circuit.R('hb', 'hb', 0, 1e12) + circuit.R('hi', 'hi', 0, 1e12) + circuit.R('ho', 'ho', 0, 1e12) + circuit.R('hs', 'hs', 0, 1e12) + circuit.R('li', 'li', 0, 1e12) + circuit.R('lo', 'lo', 0, 1e12) + circuit.R('test_temp', 'vss', 0, 10, tc=(4, 5)) + circuit.B('test_tc', 'vdd', 0, v=5, tc=(7, 8)) + simulator = circuit.simulator(simulator='xyce-serial', + temperature=25, + nominal_temperature=25, + working_directory='.') + simulator.options('device smoothbsrc=1') + if shutil.which('xyce'): + simulator.transient(1e-25, 1e-20) + + def test_working_dir(self): + from PySpice.Spice.Xyce.RawFile import RawFile + circuit = Circuit('Test working directory') + circuit.PulseVoltageSource('hlp', + 1, + 0, + initial_value=0, + pulse_value=1, + delay_time=0, + rise_time=0.1, + fall_time=0.1, + pulse_width=1, + period=2) + circuit.Resistor('load', 1, 0, 1e3) + simulator = circuit.simulator(simulator='xyce-serial', + temperature=25, + nominal_temperature=25) + simulator.options('device smoothbsrc=1') + if shutil.which('xyce'): + result = simulator.transient(1e-2, 10) + simulator = circuit.simulator(simulator='xyce-serial', + temperature=25, + nominal_temperature=25, + working_directory='.') + simulator.options('device smoothbsrc=1') + if shutil.which('xyce'): + result = simulator.transient(1e-2, 10) + self.assertTrue(os.path.exists('input.cir') and os.path.isfile('input.cir')) + self.assertTrue(os.path.exists('output.raw') and os.path.isfile('output.raw')) + data = RawFile(filename="output.raw") + print(data.nodes()) + + def test_transient(self): + transient = SpiceParser.parse(source=""" +VEXP 2 0 EXP(1 2 3) +VPAT 3 4 PAT(3 0 2 1 2 3 b0101 1) +IPULSE 2 3 PULSE(1 4) +IPWL1 1 0 PWL(0S 0A 2S 3A 3S 2A 4S 2A 4.01S 5A r=2s td=1) +VSFFM 1 0 SFFM (0 1 2) +ISIN 4 3 AC 1 SIN 0 5 3 1 +""") + circuit = transient.build() + + expected = """.title + +vexp 2 0 exp(1v 2v 3s) +vpat 3 4 pat(3v 0v 2s 1s 2s 3s b0101 1) +ipulse 2 3 pulse(1a 4a 0s 0s 0s) +ipwl1 1 0 pwl(0s 0a 2s 3a 3s 2a 4s 2a 4.01s 5a r=2s td=1s) +vsffm 1 0 sffm(0v 1v 2hz) +isin 4 3 dc 0a ac 1a sin(0a 5a 3hz 1s 0hz) +""" + result = str(circuit) + self.assertEqual(expected, result) + + def test_subcircuits(self): + subckt = SpiceParser.parse(source=""" + +.param a = 23 +.param b = 24 + +.subckt test1 1 2 3 +.ends + +Xtest1 4 5 6 test1 + +.subckt test2 1 3 4 params: t=3 +.ends + +Xtest21 8 7 3 test2 +Xtest22 9 5 6 test2 params: t = 5 + +.subckt test3 2 3 +.param h = 25 +.ends + +Xtest3 9 10 test3 + +.subckt test4 5 6 params: j = {a+b} +.param d = {j + 32} +.ends + +Xtest41 10 12 test4 +Xtest42 12 10 test4 params: j = 23 +""") + circuit = subckt.build() print(circuit) + expected = """.title -#################################################################################################### +.param a=23 +.param b=24 +.subckt test1 1 2 3 + +.ends test1 + +.subckt test2 1 3 4 params: t=3 + +.ends test2 + +.subckt test3 2 3 +.param h=25 + +.ends test3 + +.subckt test4 5 6 params: j={a + b} +.param d={j + 32} + +.ends test4 + +xtest1 4 5 6 test1 +xtest21 8 7 3 test2 +xtest22 9 5 6 test2 params: t=5 +xtest3 9 10 test3 +xtest41 10 12 test4 +xtest42 12 10 test4 params: j=23 +""" + result = str(circuit) + self.assertEqual(expected, result) + + def test_sources(self): + sources = SpiceParser.parse(source=""" +Iinj 0 probe 0 AC {0.5*prb*(prb+1)} +Vinj probe Ninplp 0 AC {0.5*prb*(prb-1)} +Vprobe probe Noutlp 0 + +""") + circuit = sources.build() + expected = """.title + +iinj 0 probe dc 0a ac {0.5 * (prb * (prb + 1))} +vinj probe ninplp dc 0v ac {0.5 * (prb * (prb - 1))} +vprobe probe noutlp dc 0v +""" + result = str(circuit) + self.assertEqual(expected, result) + def test_boolean(self): + and2 = SpiceParser.parse(source = """ +BEAND YINT 0 V = {IF(V(A) > 0.5 & V(B) > 0.5, 1, 0)} +BEOR YINT 0 V = {IF(V(A) > 0.5 | V(B) > 0.5, 1, 0)} +BEXOR YINT 0 V = {IF(V(A) > 0.5 ^ V(B) > 0.5, 1, 0)} +""") + circuit = and2.build() + expected = """.title + +beand yint 0 v={if((v(a) > 0.5) & (v(b) > 0.5), 1, 0)} +beor yint 0 v={if((v(a) > 0.5) | (v(b) > 0.5), 1, 0)} +bexor yint 0 v={if((v(a) > 0.5) ^ (v(b) > 0.5), 1, 0)} +""" + result = str(circuit) + self.assertEqual(expected, result) + + def test_subcircuit(self): + print(os.getcwd()) + circuit = Circuit('MOS Driver\nSimple check') + circuit.spice_sim = 'xyce' + circuit.include(self._getdir('unit-test/SpiceParser') + '/mosdriver.lib') + circuit.X('test', 'mosdriver', '0', '1', '2', '3', '4', '5', '6', '7') + circuit.BehavioralSource('test', '1', '0', voltage_expression='{if(True, 0, 1)}', smoothbsrc=1) + expected = """.title MOS Driver +* Simple check + +.model diode D (is=1.038e-15 n=1 tt=2e-08 cjo=5e-12 rs=0.5 bv=130) +.subckt source vh vl hi lo +bhigh vh vl v={if(v(hi, lo) > 0.5, 5, 0)} smoothbsrc=1 +.ends source + +.subckt mosdriver hb hi ho hs li lo vdd vss +xhigh hoi hs hi vss source +rhoi hoi ho 1ohm +choi ho hs 1e-09 +xlow loi vss li vss source +rloi loi lo 1ohm +cloi lo vss 1e-09 +dhb vdd hb diode +.ends mosdriver + +xtest 0 1 2 3 4 5 6 7 mosdriver +btest 1 0 v={if(true, 0, 1)} smoothbsrc=1 +""" + result = str(circuit) + self.assertEqual(expected, result) + + def test_title(self): + title = """.title Howland Current Source +""" + sources = SpiceParser.parse(source=title) + circuit = sources.build() + result = str(circuit) + self.assertEqual(title + os.linesep + os.linesep, result) + + def test_model(self): + models = """ +.MODEL NOUT NPN (BF=200,VAF=50,BR=22,IS=1E-15,RC=29.2) +.MODEL DX D(IS=1E-16, RS=5, KF=1E-15) +Q1 1 2 3 NOUT +D2 1 2 DX +""" + + expected = """.title + +.model nout NPN (bf=200 vaf=50 br=22 is=1e-15 rc=29.2) +.model dx D (is=1e-16 rs=5 kf=1e-15) +q1 1 2 3 nout +d2 1 2 dx +""" + + model = SpiceParser.parse(source=models) + circuit = model.build() + result = str(circuit) + self.assertEqual(expected, result) + + def test_source(self): + source = """ +HN 81 98 VN1 6 +Vp5 Np5 0 5 +Vm5 Nm5 0 -5 +""" + + expected = """.title + +bhn 81 98 v={i(vn1) * 6} +vp5 np5 0 dc 5v +vm5 nm5 0 dc -5v +""" + + model = SpiceParser.parse(source=source) + circuit = model.build() + result = str(circuit) + self.assertEqual(expected, result) + + def test_pulsed_source(self): + source = """ +vpulsed1 1 0 pulse(0v {high} 1s 0.1s 0.1s 5s 10s) +""" + + expected = """.title + +vpulsed1 1 0 pulse(0v {high} 1s 0.1s 0.1s 5s 10s) +""" + + model = SpiceParser.parse(source=source) + circuit = model.build() + result = str(circuit) + self.assertEqual(expected, result) if __name__ == '__main__': unittest.main() diff --git a/unit-test/SpiceParser/vpwl.csv b/unit-test/SpiceParser/vpwl.csv new file mode 100644 index 000000000..4c3762cf8 --- /dev/null +++ b/unit-test/SpiceParser/vpwl.csv @@ -0,0 +1,5 @@ +0.00, 0.00 +2.00, 3.00 +3.00, 2.00 +4.00, 2.00 +4.01, 5.00 diff --git a/unit-test/Unit/test_Units.py b/unit-test/Unit/test_Units.py index b32368d3f..38345325e 100644 --- a/unit-test/Unit/test_Units.py +++ b/unit-test/Unit/test_Units.py @@ -155,7 +155,7 @@ def test_unit_str(self): self.assertEqual(str(u_MHz(123.4)), '123.4 MHz') self.assertEqual(kilo(1).str_spice(), '1k') - self.assertEqual(u_MHz(123.4).str_spice(), '123.4MegHz') + self.assertEqual(u_MHz(123.4).str_spice(), '123.4meghz') ############################################## @@ -180,7 +180,7 @@ def test_canonisation(self): self._test_canonise(kilo(.100), '100.0') self._test_canonise(kilo(.999), '999.0') self._test_canonise(kilo(1), '1k') # Fixme: .0 - self._test_canonise(kilo(1000), '1.0Meg') + self._test_canonise(kilo(1000), '1.0meg') ##############################################