From 1119659395ae09ebb5268a17bb11f09a40cba2fd Mon Sep 17 00:00:00 2001 From: Chaffra Affouda Date: Sun, 25 Nov 2018 23:18:01 -0500 Subject: [PATCH 001/134] added circuit plotting with SchemDraw --- PySpice/Spice/BasicElement.py | 17 +- PySpice/Spice/Netlist.py | 109 +- PySpice/Spice/NgSpice/SimulationType.py | 1 + .../diode/diode-characteristic-curve.ipynb | 2292 +++++++++++++++++ setup.py | 6 +- 5 files changed, 2389 insertions(+), 36 deletions(-) create mode 100644 examples/diode/diode-characteristic-curve.ipynb diff --git a/PySpice/Spice/BasicElement.py b/PySpice/Spice/BasicElement.py index ddc01b201..5a5ebabca 100644 --- a/PySpice/Spice/BasicElement.py +++ b/PySpice/Spice/BasicElement.py @@ -17,6 +17,7 @@ # along with this program. If not, see . # #################################################################################################### +from PySpice.Spice.ElementParameter import KeyValueParameter """This module implements SPICE circuit elements. @@ -116,6 +117,8 @@ ModelPositionalParameter, ) +import SchemDraw as schem + #################################################################################################### class DipoleElement(FixedPinElement): @@ -153,12 +156,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): + schematic_kwargs = kwargs.pop('schematic_kwargs', {}) # Fixme: match parameters to subcircuit - self.parameters = parameters + self.parameters = kwargs # Fixme: investigate # for key, value in parameters.items(): @@ -166,6 +168,9 @@ def __init__(self, netlist, name, subcircuit_name, *nodes, **parameters): # parameter.__set__(self, value) # self.optional_parameters[key] = parameter # setattr(self, key, parameter) + + super().__init__(netlist, name, nodes, subcircuit_name, + schematic_kwargs=schematic_kwargs) ############################################## @@ -239,6 +244,8 @@ class Resistor(DipoleElement): __alias__ = 'R' __prefix__ = 'R' + + schematic = schem.elements.RES resistance = FloatPositionalParameter(position=0, key_parameter=False, unit=U_Ω) ac = FloatKeyParameter('ac', unit=U_Ω) @@ -761,6 +768,8 @@ class VoltageSource(DipoleElement): __alias__ = 'V' __prefix__ = 'V' + + schematic = schem.elements.SOURCE_V # Fixme: ngspice manual doesn't describe well the syntax dc_value = FloatPositionalParameter(position=0, key_parameter=False, unit=U_V) diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index 462db633a..0622265a8 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -82,7 +82,8 @@ def __init__(self, **kwargs): import logging import os -# import networkx +import networkx +import SchemDraw #################################################################################################### @@ -337,14 +338,14 @@ class Resistor(metaclass=ElementParameterMetaClass): """ - #: Dictionary for SPICE prefix -> [cls,] + #: Dictionary for SPICE prefix -> [cls,] __classes__ = {} _logger = _module_logger.getChild('ElementParameterMetaClass') ############################################## - def __new__(meta_cls, class_name, base_classes, namespace): + def __new__(cls, class_name, base_classes, namespace): # __new__ is called for the creation of a class depending of this metaclass, i.e. at module loading # It customises the namespace of the new class @@ -428,15 +429,15 @@ def getter(self): else: _module_logger.debug("{} don't define a __pins__ attribute".format(class_name)) - return type.__new__(meta_cls, class_name, base_classes, namespace) + return type.__new__(cls, class_name, base_classes, namespace) ############################################## - def __init__(meta_cls, class_name, base_classes, namespace): + def __init__(cls, class_name, base_classes, namespace): # __init__ is called after the class is created (__new__) - type.__init__(meta_cls, class_name, base_classes, namespace) + type.__init__(cls, class_name, base_classes, namespace) # Collect basic element classes if '__prefix__' in namespace: @@ -444,9 +445,9 @@ def __init__(meta_cls, class_name, base_classes, namespace): if prefix is not None: classes = ElementParameterMetaClass.__classes__ if prefix in classes: - classes[prefix].append(meta_cls) + classes[prefix].append(cls) else: - classes[prefix] = [meta_cls] + classes[prefix] = [cls] ############################################## @@ -454,33 +455,37 @@ def __init__(meta_cls, class_name, base_classes, namespace): # e.g. Resistor.number_of_pins or Resistor.__class__.number_of_pins @property - def number_of_pins(cls): + def number_of_pins(self): #! 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) + number_of_pins = len(self.__pins__) + if self.__number_of_optional_pins__: + return slice(number_of_pins - self.__number_of_optional_pins__, number_of_pins +1) else: return number_of_pins @property - def number_of_positional_parameters(cls): - return len(cls.__positional_parameters__) + def number_of_positional_parameters(self): + return len(self.__positional_parameters__) @property - def positional_parameters(cls): - return cls.__positional_parameters__ + def positional_parameters(self): + return self.__positional_parameters__ @property - def optional_parameters(cls): - return cls.__optional_parameters__ + def optional_parameters(self): + return self.__optional_parameters__ @property - def parameters_from_args(cls): - return cls.__parameters_from_args__ + def parameters_from_args(self): + return self.__parameters_from_args__ @property - def spice_to_parameters(cls): - return cls.__spice_to_parameters__ + def spice_to_parameters(self): + return self.__spice_to_parameters__ + +# @property +# def schematic(self): +# return self.__schematic__ #################################################################################################### @@ -501,6 +506,8 @@ class Element(metaclass=ElementParameterMetaClass): #: SPICE element prefix __prefix__ = None + + schematic = None ############################################## @@ -510,6 +517,7 @@ def __init__(self, netlist, name, *args, **kwargs): self._name = str(name) self.raw_spice = '' self.enabled = True + #self._pins = kwargs.pop('pins',()) # Process remaining args if len(self.__parameters_from_args__) < len(args): @@ -524,8 +532,10 @@ def __init__(self, netlist, name, *args, **kwargs): elif key in self.__positional_parameters__ or key in self.__optional_parameters__: setattr(self, key, value) - self._pins = () - netlist._add_element(self) + schematic_kwargs = kwargs.pop('schematic_kwargs', {}) + self.schematic_kwargs = schematic_kwargs + + netlist._add_element(self, **schematic_kwargs) ############################################## @@ -690,10 +700,13 @@ 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) ############################################## @@ -713,10 +726,10 @@ class NPinElement(Element): def __init__(self, netlist, name, nodes, *args, **kwargs): - super().__init__(netlist, name, *args, **kwargs) - self._pins = [Pin(self, PinDefinition(position), netlist.get_node(node, True)) for position, node in enumerate(nodes)] + + super().__init__(netlist, name, *args, **kwargs) ############################################## @@ -826,6 +839,8 @@ def __init__(self): self._ground_name = 0 self._nodes = {} + self._graph = networkx.Graph() + self._schematic = SchemDraw.Drawing() self._ground_node = self._add_node(self._ground_name) self._subcircuits = OrderedDict() # to keep the declaration order @@ -834,7 +849,6 @@ def __init__(self): self.raw_spice = '' - # self._graph = networkx.Graph() ############################################## @@ -890,6 +904,14 @@ def subcircuits(self): @property def subcircuit_names(self): return self._subcircuits.keys() + + @property + def graph(self): + return self._graph + + @property + def schematic(self): + return self._schematic ############################################## @@ -922,6 +944,10 @@ def _add_node(self, node_name): if node_name not in self._nodes: node = Node(self, node_name) self._nodes[node_name] = node + self._graph.add_node(node, name=node_name) + + #if(node_name == str(self._ground_name)): + # self.schematic.add(SchemDraw.elements.GND) return node else: raise ValueError("Node {} is already defined".format(node_name)) @@ -960,12 +986,35 @@ def has_ground_node(self): ############################################## - def _add_element(self, element): + def _add_element(self, element, **schematic_kwargs): """Add an element.""" - if element.name not in self._elements: self._elements[element.name] = element + + if len(element.nodes) == 2: + self.graph.add_edge(element.nodes[0], element.nodes[1], + x=element, name=element.name) + + #print(schematic_kwargs) + schematic = schematic_kwargs.pop('schematic', element.schematic) + if(schematic): + element.schematic = schematic + label = schematic_kwargs.pop('label', element.name) + schematic_element = self.schematic.add(schematic, label=label, + **schematic_kwargs) + element.schematic_element = schematic_element + + show_plus = schematic_kwargs.pop('show_plus', False) + show_minus = schematic_kwargs.pop('show_minus', False) + if(show_plus): + self.schematic.add(SchemDraw.elements.DOT, label=element.plus.node.name) + + if(show_minus): + self.schematic.add(SchemDraw.elements.DOT, label=element.minus.node.name) + #if(element.pins[1].node == self.get_node(0, False)): + # self.schematic.add(SchemDraw.elements.GND) + else: raise NameError("Element name {} is already defined".format(element.name)) diff --git a/PySpice/Spice/NgSpice/SimulationType.py b/PySpice/Spice/NgSpice/SimulationType.py index e5df5da74..b896aebb7 100644 --- a/PySpice/Spice/NgSpice/SimulationType.py +++ b/PySpice/Spice/NgSpice/SimulationType.py @@ -81,3 +81,4 @@ ) SIMULATION_TYPE[28] = SIMULATION_TYPE[27] +SIMULATION_TYPE[29] = SIMULATION_TYPE[28] diff --git a/examples/diode/diode-characteristic-curve.ipynb b/examples/diode/diode-characteristic-curve.ipynb new file mode 100644 index 000000000..9c0758bc5 --- /dev/null +++ b/examples/diode/diode-characteristic-curve.ipynb @@ -0,0 +1,2292 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "%config InlineBackend.figure_format = 'svg'\n", + "####################################################################################################\n", + "\n", + "import os\n", + "\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import matplotlib.ticker as ticker\n", + "import networkx as nx\n", + "import SchemDraw as schem\n", + "\n", + "####################################################################################################\n", + "\n", + "import PySpice.Logging.Logging as Logging\n", + "logger = Logging.setup_logging()\n", + "\n", + "####################################################################################################\n", + "\n", + "from PySpice.Doc.ExampleTools import find_libraries\n", + "from PySpice.Spice.Netlist import Circuit\n", + "from PySpice.Spice.Library import SpiceLibrary\n", + "from PySpice.Unit import *\n", + "from PySpice.Physics.SemiConductor import ShockleyDiode\n", + "\n", + "os.environ['PySpiceLibraryPath'] = '~/Projects/ROIC/src/PySpice/examples/libraries'\n", + "\n", + "####################################################################################################\n", + "\n", + "libraries_path = find_libraries()\n", + "spice_library = SpiceLibrary(libraries_path)\n", + "####################################################################################################" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "#r# For this purpose, we use the common high-speed diode 1N4148. The diode is driven by a variable\n", + "#r# voltage source through a limiting current resistance.\n", + "\n", + "#f# circuit_macros('diode-characteristic-curve-circuit.m4')\n", + "\n", + "circuit = Circuit('Diode Characteristic Curve')\n", + "\n", + "circuit.include(spice_library['1N4148'])\n", + "\n", + "circuit.schematic.add(schem.elements.GND)\n", + "V =circuit.V('input', 'Vin', circuit.gnd, 10@u_V,\n", + " schematic_kwargs={'show_plus': True}\n", + " )\n", + "R = circuit.R('1', 'Vin', 'Vout', 1@u_Ω,\n", + " schematic_kwargs={'d':'right', 'show_minus': True}\n", + " ) # not required for simulation\n", + "X = circuit.X('D1', '1N4148', 'Vout', circuit.gnd,\n", + " schematic_kwargs={'schematic': schem.elements.DIODE, 'd':'down'},\n", + " )\n", + "circuit.schematic.add(schem.elements.GND)\n", + "circuit.schematic.draw()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "#pos = nx.spectral_layout(circuit.graph)\n", + "#nx.draw(circuit.graph, pos=pos, with_labels=True, node_size=1200, node_shape='s',\n", + "# width=2.0)\n", + "#edge_labels = nx.get_edge_attributes(circuit.graph,'name')\n", + "#nx.draw_networkx_edge_labels(circuit.graph, pos, edge_labels=edge_labels)\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [], + "source": [ + "#r# We simulate the circuit at these temperatures: 0, 25 and 100 °C.\n", + "\n", + "# Fixme: Xyce ???\n", + "temperatures = [0, 25, 100]@u_Degree\n", + "analyses = {}\n", + "for temperature in temperatures:\n", + " simulator = circuit.simulator(temperature=temperature, nominal_temperature=temperature)\n", + " analysis = simulator.dc(Vinput=slice(-2, 5, .01))\n", + " analyses[float(temperature)] = analysis\n", + "\n", + "####################################################################################################" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(1,-100,'Forward Biased Region')" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "#r# We plot the characteristic curve and compare it to the Shockley diode model:\n", + "#r#\n", + "#r# .. math::\n", + "#r#\n", + "#r# I_d = I_s \\left( e^{\\frac{V_d}{n V_T}} - 1 \\right)\n", + "#r#\n", + "#r# where :math:`V_T = \\frac{k T}{q}`\n", + "#r#\n", + "#r# In order to scale the reverse biased region, we have to do some hack with Matplotlib.\n", + "#r#\n", + "\n", + "silicon_forward_voltage_threshold = .7\n", + "\n", + "shockley_diode = ShockleyDiode(Is=4.0@u_nA, degree=25)\n", + "\n", + "def two_scales_tick_formatter(value, position):\n", + " if value >= 0:\n", + " return '{} mA'.format(value)\n", + " else:\n", + " return '{} nA'.format(value/100)\n", + "formatter = ticker.FuncFormatter(two_scales_tick_formatter)\n", + "\n", + "figure = plt.figure(1, (10, 5))\n", + "\n", + "axe = plt.subplot(121)\n", + "axe.set_title('1N4148 Characteristic Curve ')\n", + "axe.set_xlabel('Voltage [V]')\n", + "axe.set_ylabel('Current')\n", + "axe.grid()\n", + "axe.set_xlim(-2, 2)\n", + "axe.axvspan(-2, 0, facecolor='green', alpha=.2)\n", + "axe.axvspan(0, silicon_forward_voltage_threshold, facecolor='blue', alpha=.1)\n", + "axe.axvspan(silicon_forward_voltage_threshold, 2, facecolor='blue', alpha=.2)\n", + "#axe.set_ylim(-500, 750) # Fixme: round\n", + "#axe.yaxis.set_major_formatter(formatter)\n", + "Vd = analyses[25].Vout\n", + "# compute scale for reverse and forward region\n", + "forward_region = Vd >= 0@u_V\n", + "reverse_region = np.invert(forward_region)\n", + "scale = reverse_region*1e11 + forward_region*1e3\n", + "#?# check temperature\n", + "for temperature in temperatures:\n", + " analysis = analyses[float(temperature)]\n", + " axe.plot(Vd, np.abs(- analysis.Vinput))\n", + "axe.plot(Vd, np.abs(shockley_diode.I(Vd)), 'black')\n", + "axe.set_ylim(1e-9, 1e3)\n", + "axe.set_yscale('log')\n", + "axe.legend(['@ {}'.format(temperature)\n", + " for temperature in temperatures] + ['Shockley Diode Model Is = 4 nA'],\n", + " loc=2, fontsize=10)\n", + "axe.axvline(x=0, color='black')\n", + "axe.axhline(y=0, color='black')\n", + "axe.axvline(x=silicon_forward_voltage_threshold, color='red')\n", + "axe.text(-1, -100, 'Reverse Biased Region', ha='center', va='center')\n", + "axe.text( 1, -100, 'Forward Biased Region', ha='center', va='center')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.7" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/setup.py b/setup.py index 7c518ba0b..80ea122f7 100755 --- a/setup.py +++ b/setup.py @@ -48,8 +48,8 @@ #################################################################################################### -exec(compile(open('setup_data.py').read(), 'setup_data.py', 'exec')) - +#exec(compile(open('setup_data.py').read(), 'setup_data.py', 'exec')) +from setup_data import setup_dict #################################################################################################### setup_dict.update(dict( @@ -83,6 +83,8 @@ 'numpy', 'ply', 'scipy', + 'networkx', + 'SchemDraw' ], )) From 9afd20d5636a9ea09c02084d99185832ac24a5ec Mon Sep 17 00:00:00 2001 From: Chaffra Affouda Date: Mon, 26 Nov 2018 00:03:37 -0500 Subject: [PATCH 002/134] iadding pin positionning for SchemDraw --- PySpice/Spice/Netlist.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index 0622265a8..267b0cf33 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -1007,11 +1007,16 @@ def _add_element(self, element, **schematic_kwargs): show_plus = schematic_kwargs.pop('show_plus', False) show_minus = schematic_kwargs.pop('show_minus', False) + + minus_xy = schematic_kwargs.pop('minus_xy', []) + plus_xy = schematic_kwargs.pop('minus_xy', []) if(show_plus): - self.schematic.add(SchemDraw.elements.DOT, label=element.plus.node.name) + self.schematic.add(SchemDraw.elements.DOT, label=element.plus.node.name, + xy=plus_xy) if(show_minus): - self.schematic.add(SchemDraw.elements.DOT, label=element.minus.node.name) + self.schematic.add(SchemDraw.elements.DOT, label=element.minus.node.name, + xy=minus_xy) #if(element.pins[1].node == self.get_node(0, False)): # self.schematic.add(SchemDraw.elements.GND) From 827603c939598d65bc2f292a0a4954fe8abc5afb Mon Sep 17 00:00:00 2001 From: Chaffra Affouda Date: Mon, 26 Nov 2018 01:56:50 -0500 Subject: [PATCH 003/134] bug fixes --- PySpice/Spice/BasicElement.py | 9 +++++++-- PySpice/Spice/Netlist.py | 20 +++++++++++--------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/PySpice/Spice/BasicElement.py b/PySpice/Spice/BasicElement.py index 5a5ebabca..e4470d3d6 100644 --- a/PySpice/Spice/BasicElement.py +++ b/PySpice/Spice/BasicElement.py @@ -17,7 +17,6 @@ # along with this program. If not, see . # #################################################################################################### -from PySpice.Spice.ElementParameter import KeyValueParameter """This module implements SPICE circuit elements. @@ -100,7 +99,8 @@ 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 .Netlist import (Element, AnyPinElement, FixedPinElement, NPinElement, + OptionalPin, Pin, PinDefinition) from .ElementParameter import ( # KeyValueParameter, BoolKeyParameter, @@ -169,6 +169,11 @@ def __init__(self, netlist, name, subcircuit_name, *nodes, **kwargs): # self.optional_parameters[key] = parameter # setattr(self, key, parameter) + subcircuit = netlist._subcircuits.get(subcircuit_name) + + self._pins = [Pin(self, PinDefinition(position, name=subcircuit.__pins__[position]), netlist.get_node(node, True)) + for position, node in enumerate(nodes)] + super().__init__(netlist, name, nodes, subcircuit_name, schematic_kwargs=schematic_kwargs) diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index 267b0cf33..5231f518b 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -84,6 +84,7 @@ def __init__(self, **kwargs): import networkx import SchemDraw +import numpy as np #################################################################################################### @@ -726,9 +727,6 @@ class NPinElement(Element): def __init__(self, netlist, name, nodes, *args, **kwargs): - self._pins = [Pin(self, PinDefinition(position), netlist.get_node(node, True)) - for position, node in enumerate(nodes)] - super().__init__(netlist, name, *args, **kwargs) ############################################## @@ -1008,15 +1006,18 @@ def _add_element(self, element, **schematic_kwargs): show_plus = schematic_kwargs.pop('show_plus', False) show_minus = schematic_kwargs.pop('show_minus', False) - minus_xy = schematic_kwargs.pop('minus_xy', []) - plus_xy = schematic_kwargs.pop('minus_xy', []) if(show_plus): - self.schematic.add(SchemDraw.elements.DOT, label=element.plus.node.name, - xy=plus_xy) + plus_label = schematic_kwargs.pop('plus_label',{}) + + self.schematic.add(SchemDraw.elements.DOT, + xy=schematic_element.start, + **plus_label) if(show_minus): - self.schematic.add(SchemDraw.elements.DOT, label=element.minus.node.name, - xy=minus_xy) + minus_label = schematic_kwargs.pop('minus_label',{}) + self.schematic.add(SchemDraw.elements.DOT, + xy=schematic_element.end, + **minus_label) #if(element.pins[1].node == self.get_node(0, False)): # self.schematic.add(SchemDraw.elements.GND) @@ -1192,6 +1193,7 @@ class SubCircuitFactory(SubCircuit): __name__ = None __nodes__ = None + __pins__ = None ############################################## From a896fc714c387bdc08a6a7569e229df24a9e2208 Mon Sep 17 00:00:00 2001 From: Chaffra Affouda Date: Mon, 26 Nov 2018 07:59:52 -0500 Subject: [PATCH 004/134] commit --- examples/ctia_readout/ctia_readout.ipynb | 683 +++++++++++++++++++++++ 1 file changed, 683 insertions(+) create mode 100644 examples/ctia_readout/ctia_readout.ipynb diff --git a/examples/ctia_readout/ctia_readout.ipynb b/examples/ctia_readout/ctia_readout.ipynb new file mode 100644 index 000000000..5506cad69 --- /dev/null +++ b/examples/ctia_readout/ctia_readout.ipynb @@ -0,0 +1,683 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1;32m2018-11-26 01:51:27,970\u001b[0m - \u001b[1;34mPySpice.Doc.ExampleTools.find_libraries\u001b[0m - \u001b[1;31mINFO\u001b[0m - SPICE library path is ~/Projects/ROIC/src/PySpice/examples/libraries\n" + ] + } + ], + "source": [ + "%matplotlib inline\n", + "%config InlineBackend.figure_format = 'svg'\n", + "####################################################################################################\n", + "\n", + "import os\n", + "\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import matplotlib.ticker as ticker\n", + "import networkx as nx\n", + "import SchemDraw as schem\n", + "\n", + "####################################################################################################\n", + "\n", + "import PySpice.Logging.Logging as Logging\n", + "logger = Logging.setup_logging()\n", + "\n", + "####################################################################################################\n", + "\n", + "from PySpice.Doc.ExampleTools import find_libraries\n", + "from PySpice.Spice.Netlist import Circuit\n", + "from PySpice.Spice.Library import SpiceLibrary\n", + "from PySpice.Unit import *\n", + "from PySpice.Physics.SemiConductor import ShockleyDiode\n", + "from PySpice.Spice.Netlist import SubCircuitFactory\n", + "\n", + "os.environ['PySpiceLibraryPath'] = '~/Projects/ROIC/src/PySpice/examples/libraries'\n", + "\n", + "####################################################################################################\n", + "\n", + "libraries_path = find_libraries()\n", + "spice_library = SpiceLibrary(libraries_path)\n", + "####################################################################################################" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "class BasicOperationalAmplifier(SubCircuitFactory):\n", + "\n", + " __name__ = 'BasicOperationalAmplifier'\n", + " __nodes__ = ('non_inverting_input', 'inverting_input', 'output')\n", + " __pins__ = ('plus', 'minus', 'out')\n", + "\n", + "\n", + " def __init__(self):\n", + "\n", + " super().__init__()\n", + "\n", + " # Input impedance\n", + " self.R('input', 'non_inverting_input', 'inverting_input', 10@u_MΩ)\n", + "\n", + " # dc gain=100k and pole1=100hz\n", + " # unity gain = dcgain x pole1 = 10MHZ\n", + " self.VCVS('gain', 1, self.gnd, 'non_inverting_input', 'inverting_input', voltage_gain=kilo(100))\n", + " self.R('P1', 1, 2, 1@u_kΩ)\n", + " self.C('P1', 2, self.gnd, 1.5915@u_uF)\n", + "\n", + " # Output buffer and resistance\n", + " self.VCVS('buffer', 3, self.gnd, 2, self.gnd, 1)\n", + " self.R('out', 3, 'output', 10@u_Ω)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "#r# For this purpose, we use the common high-speed diode 1N4148. The diode is driven by a variable\n", + "#r# voltage source through a limiting current resistance.\n", + "\n", + "#f# circuit_macros('diode-characteristic-curve-circuit.m4')\n", + "\n", + "circuit = Circuit('Capacitive Transimpedance Amplifier (CTIA) readout')\n", + "\n", + "#circuit.include(spice_library['1N4148'])\n", + "\n", + "\n", + "PD = circuit.D('IR', 'anode', 'Vd',\n", + " schematic_kwargs={'schematic': schem.elements.PHOTODIODE, \n", + " 'd': 'down', 'show_minus': True, 'minus_label': {'lftlabel': 'Vd'},\n", + " 'flip': True,\n", + " }\n", + " )\n", + "circuit.schematic.add(schem.elements.LINE, xy=PD.schematic_element.start, d ='right')\n", + "CD = circuit.C('d', 'anode', 'Vd', 1@u_uF,\n", + " schematic_kwargs={'schematic': schem.elements.CAP, \n", + " 'd': 'down', 'to': PD.schematic_element.start }\n", + " )\n", + "\n", + "circuit.subcircuit(BasicOperationalAmplifier())\n", + "circuit.schematic.add(schem.elements.LINE, xy=CD.schematic_element.start, d ='right')\n", + "XA = circuit.X('A', 'BasicOperationalAmplifier', 'Vcom', 'anode', 'Aout',\n", + " schematic_kwargs={'schematic': schem.elements.OPAMP, 'anchor': 'in1', \n", + " 'd': 'right', 'flip':False})\n", + "\n", + "circuit.schematic.draw()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "#pos = nx.spectral_layout(circuit.graph)\n", + "#nx.draw(circuit.graph, pos=pos, with_labels=True, node_size=1200, node_shape='s',\n", + "# width=2.0)\n", + "#edge_labels = nx.get_edge_attributes(circuit.graph,'name')\n", + "#nx.draw_networkx_edge_labels(circuit.graph, pos, edge_labels=edge_labels)\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Pin plus of XA on node Vcom,\n", + " Pin minus of XA on node anode,\n", + " Pin out of XA on node Aout]" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "XA.pins" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#r# We simulate the circuit at these temperatures: 0, 25 and 100 °C.\n", + "\n", + "# Fixme: Xyce ???\n", + "temperatures = [0, 25, 100]@u_Degree\n", + "analyses = {}\n", + "for temperature in temperatures:\n", + " simulator = circuit.simulator(temperature=temperature, nominal_temperature=temperature)\n", + " analysis = simulator.dc(Vinput=slice(-2, 5, .01))\n", + " analyses[float(temperature)] = analysis\n", + "\n", + "####################################################################################################" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "#r# We plot the characteristic curve and compare it to the Shockley diode model:\n", + "#r#\n", + "#r# .. math::\n", + "#r#\n", + "#r# I_d = I_s \\left( e^{\\frac{V_d}{n V_T}} - 1 \\right)\n", + "#r#\n", + "#r# where :math:`V_T = \\frac{k T}{q}`\n", + "#r#\n", + "#r# In order to scale the reverse biased region, we have to do some hack with Matplotlib.\n", + "#r#\n", + "\n", + "silicon_forward_voltage_threshold = .7\n", + "\n", + "shockley_diode = ShockleyDiode(Is=4.0@u_nA, degree=25)\n", + "\n", + "def two_scales_tick_formatter(value, position):\n", + " if value >= 0:\n", + " return '{} mA'.format(value)\n", + " else:\n", + " return '{} nA'.format(value/100)\n", + "formatter = ticker.FuncFormatter(two_scales_tick_formatter)\n", + "\n", + "figure = plt.figure(1, (10, 5))\n", + "\n", + "axe = plt.subplot(121)\n", + "axe.set_title('1N4148 Characteristic Curve ')\n", + "axe.set_xlabel('Voltage [V]')\n", + "axe.set_ylabel('Current')\n", + "axe.grid()\n", + "axe.set_xlim(-2, 2)\n", + "axe.axvspan(-2, 0, facecolor='green', alpha=.2)\n", + "axe.axvspan(0, silicon_forward_voltage_threshold, facecolor='blue', alpha=.1)\n", + "axe.axvspan(silicon_forward_voltage_threshold, 2, facecolor='blue', alpha=.2)\n", + "#axe.set_ylim(-500, 750) # Fixme: round\n", + "#axe.yaxis.set_major_formatter(formatter)\n", + "Vd = analyses[25].Vout\n", + "# compute scale for reverse and forward region\n", + "forward_region = Vd >= 0@u_V\n", + "reverse_region = np.invert(forward_region)\n", + "scale = reverse_region*1e11 + forward_region*1e3\n", + "#?# check temperature\n", + "for temperature in temperatures:\n", + " analysis = analyses[float(temperature)]\n", + " axe.plot(Vd, np.abs(- analysis.Vinput))\n", + "axe.plot(Vd, np.abs(shockley_diode.I(Vd)), 'black')\n", + "axe.set_ylim(1e-9, 1e3)\n", + "axe.set_yscale('log')\n", + "axe.legend(['@ {}'.format(temperature)\n", + " for temperature in temperatures] + ['Shockley Diode Model Is = 4 nA'],\n", + " loc=2, fontsize=10)\n", + "axe.axvline(x=0, color='black')\n", + "axe.axhline(y=0, color='black')\n", + "axe.axvline(x=silicon_forward_voltage_threshold, color='red')\n", + "axe.text(-1, -100, 'Reverse Biased Region', ha='center', va='center')\n", + "axe.text( 1, -100, 'Forward Biased Region', ha='center', va='center')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.7" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 59d9b04739a63f211ad83842994890d0c82a8f37 Mon Sep 17 00:00:00 2001 From: Chaffra Affouda Date: Mon, 26 Nov 2018 18:48:56 -0500 Subject: [PATCH 005/134] some syntax improvements --- PySpice/Spice/Netlist.py | 24 +- examples/ctia_readout/ctia_readout.ipynb | 3165 +++++++++++++++++++--- 2 files changed, 2763 insertions(+), 426 deletions(-) diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index 5231f518b..ba6bff4b2 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -998,26 +998,26 @@ def _add_element(self, element, **schematic_kwargs): schematic = schematic_kwargs.pop('schematic', element.schematic) if(schematic): element.schematic = schematic - label = schematic_kwargs.pop('label', element.name) - schematic_element = self.schematic.add(schematic, label=label, + #label = schematic_kwargs.pop('label', element.name) + schematic_element = self.schematic.add(schematic, **schematic_kwargs) element.schematic_element = schematic_element - show_plus = schematic_kwargs.pop('show_plus', False) - show_minus = schematic_kwargs.pop('show_minus', False) + show_start = schematic_kwargs.pop('show_start', False) + show_end = schematic_kwargs.pop('show_end', False) - if(show_plus): - plus_label = schematic_kwargs.pop('plus_label',{}) + if(show_start): + start_label = schematic_kwargs.pop('start_label',{}) - self.schematic.add(SchemDraw.elements.DOT, + self.schematic.add(SchemDraw.elements.DOT_OPEN, xy=schematic_element.start, - **plus_label) + **start_label) - if(show_minus): - minus_label = schematic_kwargs.pop('minus_label',{}) - self.schematic.add(SchemDraw.elements.DOT, + if(show_end): + end_label = schematic_kwargs.pop('end_label',{}) + self.schematic.add(SchemDraw.elements.DOT_OPEN, xy=schematic_element.end, - **minus_label) + **end_label) #if(element.pins[1].node == self.get_node(0, False)): # self.schematic.add(SchemDraw.elements.GND) diff --git a/examples/ctia_readout/ctia_readout.ipynb b/examples/ctia_readout/ctia_readout.ipynb index 5506cad69..c77ed304c 100644 --- a/examples/ctia_readout/ctia_readout.ipynb +++ b/examples/ctia_readout/ctia_readout.ipynb @@ -5,11 +5,21 @@ "execution_count": 1, "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/usr/local/lib/python3.6/dist-packages/matplotlib/__init__.py:908: MatplotlibDeprecationWarning: The backend.qt4 rcParam was deprecated in version 2.2. In order to force the use of a specific Qt binding, either import that binding first, or set the QT_API environment variable.\n", + " mplDeprecation)\n", + "/usr/local/lib/python3.6/dist-packages/matplotlib/__init__.py:908: MatplotlibDeprecationWarning: The backend.qt4 rcParam was deprecated in version 2.2. In order to force the use of a specific Qt binding, either import that binding first, or set the QT_API environment variable.\n", + " mplDeprecation)\n" + ] + }, { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[1;32m2018-11-26 01:51:27,970\u001b[0m - \u001b[1;34mPySpice.Doc.ExampleTools.find_libraries\u001b[0m - \u001b[1;31mINFO\u001b[0m - SPICE library path is ~/Projects/ROIC/src/PySpice/examples/libraries\n" + "\u001b[1;32m2018-11-26 18:08:39,871\u001b[0m - \u001b[1;34mPySpice.Doc.ExampleTools.find_libraries\u001b[0m - \u001b[1;31mINFO\u001b[0m - SPICE library path is ~/Projects/ROIC/src/PySpice/examples/libraries\n" ] } ], @@ -21,18 +31,20 @@ "import os\n", "\n", "import numpy as np\n", + "import pylab as pb\n", "import matplotlib.pyplot as plt\n", "import matplotlib.ticker as ticker\n", "import networkx as nx\n", "import SchemDraw as schem\n", "\n", "####################################################################################################\n", - "\n", + "import PySpice\n", "import PySpice.Logging.Logging as Logging\n", "logger = Logging.setup_logging()\n", "\n", "####################################################################################################\n", "\n", + "\n", "from PySpice.Doc.ExampleTools import find_libraries\n", "from PySpice.Spice.Netlist import Circuit\n", "from PySpice.Spice.Library import SpiceLibrary\n", @@ -55,35 +67,69 @@ "metadata": {}, "outputs": [], "source": [ - "class BasicOperationalAmplifier(SubCircuitFactory):\n", + "from math import pi\n", + "#cgs units\n", + "cm = 1.0\n", + "m = 100.0\n", + "mum = 1e-6*m\n", + "nm = 1e-9*m\n", + "g = 1.0\n", + "kg = 1000.0*g\n", "\n", - " __name__ = 'BasicOperationalAmplifier'\n", - " __nodes__ = ('non_inverting_input', 'inverting_input', 'output')\n", - " __pins__ = ('plus', 'minus', 'out')\n", + "s = 1.0\n", + "ms = 1e-3*s\n", + "ns = 1e-9*s\n", + "mus = 1e-6*s\n", "\n", + "Hertz = Hz = 1.0/s\n", "\n", - " def __init__(self):\n", + "erg = g*cm**2/s**2\n", + "Joule = kg*m**2/s**2\n", "\n", - " super().__init__()\n", + "Watt = Joule/s\n", + "mW = 1e-3*Watt\n", "\n", - " # Input impedance\n", - " self.R('input', 'non_inverting_input', 'inverting_input', 10@u_MΩ)\n", + "Coulomb = 1.0\n", + "Ampere = Coulomb/s\n", + "mA = 1e-3*Ampere\n", + "nA = 1e-9*Ampere\n", + "Kelvin = 1.0\n", "\n", - " # dc gain=100k and pole1=100hz\n", - " # unity gain = dcgain x pole1 = 10MHZ\n", - " self.VCVS('gain', 1, self.gnd, 'non_inverting_input', 'inverting_input', voltage_gain=kilo(100))\n", - " self.R('P1', 1, 2, 1@u_kΩ)\n", - " self.C('P1', 2, self.gnd, 1.5915@u_uF)\n", + "eps0 = 8.854187817620e-12*Ampere**2*s**4/kg/m**3\n", + "h_planck = 6.62606885e-27*erg*s\n", + "hbar_planck = h_planck/2.0/pi\n", + "q_e = 1.6021766208e-19*Coulomb\n", + "k_b = 1.38064852e-16*erg/Kelvin\n", "\n", - " # Output buffer and resistance\n", - " self.VCVS('buffer', 3, self.gnd, 2, self.gnd, 1)\n", - " self.R('out', 3, 'output', 10@u_Ω)" + "eV =q_e*Joule/Coulomb\n", + "Volt = Joule/Coulomb\n", + "mV = 1e-3*Volt\n", + "\n", + "Ohm = Volt/Ampere\n", + "kOhm = 1e3*Ohm\n", + "MOhm = 1e6*Ohm \n", + "\n", + "Farad = Coulomb/Volt\n", + "uF = 1e-6*Farad\n", + "nF = 1e-9*Farad\n", + "pF = 1e-12*Farad" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": {}, + "outputs": [], + "source": [ + "PySpice.__file__" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "scrolled": true + }, "outputs": [ { "data": { @@ -92,7 +138,7 @@ "\n", "\n", - "\n", + "\n", " \n", " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", " \n", - " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", + "\" style=\"fill:#ffffff;stroke:#000000;stroke-linejoin:miter;stroke-width:1.5;\"/>\n", " \n", " \n", - " \n", + "\" style=\"fill:#ffffff;stroke:#000000;stroke-linejoin:miter;stroke-width:1.5;\"/>\n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", + " \n", + " \n", + " \n", + "\" style=\"fill:#ffffff;stroke:#000000;stroke-linejoin:miter;stroke-width:1.5;\"/>\n", + " \n", + " \n", + " \n", + " \n", " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "M 10.59375 25.484375 \n", + "L 73.1875 25.484375 \n", + "L 73.1875 17.1875 \n", + "L 10.59375 17.1875 \n", + "z\n", + "\" id=\"DejaVuSans-3d\"/>\n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\" id=\"DejaVuSans-33\"/>\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", "\n" ], "text/plain": [ - "
" + "
" ] }, "metadata": {}, @@ -505,157 +894,2105 @@ } ], "source": [ - "#r# For this purpose, we use the common high-speed diode 1N4148. The diode is driven by a variable\n", - "#r# voltage source through a limiting current resistance.\n", + "class BasicOperationalAmplifier(SubCircuitFactory):\n", "\n", - "#f# circuit_macros('diode-characteristic-curve-circuit.m4')\n", + " __name__ = 'BasicOperationalAmplifier'\n", + " __nodes__ = ('non_inverting_input', 'inverting_input', 'output')\n", + " __pins__ = ('plus', 'minus', 'out')\n", "\n", - "circuit = Circuit('Capacitive Transimpedance Amplifier (CTIA) readout')\n", "\n", - "#circuit.include(spice_library['1N4148'])\n", + " def __init__(self):\n", "\n", + " super().__init__()\n", "\n", - "PD = circuit.D('IR', 'anode', 'Vd',\n", - " schematic_kwargs={'schematic': schem.elements.PHOTODIODE, \n", - " 'd': 'down', 'show_minus': True, 'minus_label': {'lftlabel': 'Vd'},\n", - " 'flip': True,\n", + " # Input impedance\n", + " RIN = self.R('input', 'non_inverting_input', 'inverting_input', 10@u_MΩ,\n", + " schematic_kwargs={'schematic': schem.elements.RES,\n", + " 'label': r'$R_{in}$', 'd': 'up',\n", + " 'show_start': True, 'start_label': {'lftlabel': r'$v_+$'},\n", + " 'show_end': True, 'end_label': {'lftlabel': r'$v_-$'},\n", " }\n", - " )\n", - "circuit.schematic.add(schem.elements.LINE, xy=PD.schematic_element.start, d ='right')\n", - "CD = circuit.C('d', 'anode', 'Vd', 1@u_uF,\n", - " schematic_kwargs={'schematic': schem.elements.CAP, \n", - " 'd': 'down', 'to': PD.schematic_element.start }\n", " )\n", + " self.RIN = RIN\n", + " #self._schematic.add(schem.elements.RES,label=r'$R_in$')\n", "\n", - "circuit.subcircuit(BasicOperationalAmplifier())\n", - "circuit.schematic.add(schem.elements.LINE, xy=CD.schematic_element.start, d ='right')\n", - "XA = circuit.X('A', 'BasicOperationalAmplifier', 'Vcom', 'anode', 'Aout',\n", - " schematic_kwargs={'schematic': schem.elements.OPAMP, 'anchor': 'in1', \n", - " 'd': 'right', 'flip':False})\n", + " # dc gain=100k and pole1=100hz\n", + " # unity gain = dcgain x pole1 = 10MHZ\n", + " self.schematic.add(schem.elements.LINE, d ='right', l=1.0)\n", + " vcvs_in = self.VCVS('gain', 1, self.gnd, 'non_inverting_input', 'inverting_input', voltage_gain=kilo(100),\n", + " schematic_kwargs={'schematic': schem.elements.SOURCE_CONT_V,\n", + " 'anchor':'in1',#'d':'right',\n", + " 'show_end': True, 'end_label': {'toplabel': r'$v_1 = A(v_+ - v_-)$'},\n", + " #'rgtlabel': r'$A(v_+ - v_-)$',\n", + " #'l': RIN.schematic_element.dy,\n", + " })\n", + " self.vcvs_in = vcvs_in\n", + " self.schematic.add(schem.elements.GND, xy=vcvs_in.schematic_element.start)\n", + " self.schematic.add(schem.elements.DOT_OPEN, xy=vcvs_in.schematic_element.in2, rgtlabel=r'$v_+$')\n", + " \n", + " #self.schematic.add(schem.elements.LINE, xy=vcvs_in.schematic_element.end, d='right', l=1.5)\n", + " RP1 = self.R('P1', 1, 2, 1@u_kΩ,\n", + " schematic_kwargs={'schematic': schem.elements.RES,\n", + " 'd':'right',\n", + " 'xy': vcvs_in.schematic_element.end,\n", + " 'botlabel': r'$RP1$',\n", + " 'l':5.5\n", + " })\n", + " CP1 = self.C('P1', 2, self.gnd, 1.5915@u_uF, \n", + " schematic_kwargs={'schematic': schem.elements.CAP,\n", + " 'd':'down',\n", + " 'botlabel': r'$CP1$',\n", + " 'show_start': True, 'start_label': {'toplabel': r'$v_2$'},\n", + " })\n", + " self.schematic.add(schem.elements.GND, xy=CP1.schematic_element.end)\n", "\n", - "circuit.schematic.draw()" + " # Output buffer and resistance\n", + " self.schematic.add(schem.elements.LINE, xy=CP1.schematic_element.start, d='right', l=2.0)\n", + " vcvs_out = self.VCVS('buffer', 3, self.gnd, 2, self.gnd, 1,\n", + " schematic_kwargs={'schematic': schem.elements.SOURCE_CONT_V,\n", + " 'anchor':'in1',#'d':'right',\n", + " 'show_end': True, 'end_label': {'toplabel': r'$v_3 = v_2$'}}\n", + " )\n", + " self.vcvs_out = vcvs_out\n", + " self.schematic.add(schem.elements.GND, xy=vcvs_out.schematic_element.start)\n", + " self.schematic.add(schem.elements.GND, xy=vcvs_out.schematic_element.in2)\n", + " \n", + " \n", + " ROUT = self.R('out', 3, 'output', 10@u_Ω,\n", + " schematic_kwargs={'schematic': schem.elements.RES,\n", + " 'label': r'$R_{out}$', 'd': 'right',\n", + " 'show_end': True, 'end_label': {'rgtlabel': r'$v_{o}$'},\n", + " 'l': 3.5\n", + " }\n", + " )\n", + " self.ROUT = ROUT\n", + " \n", + "opamp = BasicOperationalAmplifier()\n", + "opamp.schematic.draw()" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "scrolled": false - }, + "metadata": {}, "outputs": [], - "source": [ - "#pos = nx.spectral_layout(circuit.graph)\n", - "#nx.draw(circuit.graph, pos=pos, with_labels=True, node_size=1200, node_shape='s',\n", - "# width=2.0)\n", - "#edge_labels = nx.get_edge_attributes(circuit.graph,'name')\n", - "#nx.draw_networkx_edge_labels(circuit.graph, pos, edge_labels=edge_labels)\n", - "\n" - ] + "source": [] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 50, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "[Pin plus of XA on node Vcom,\n", - " Pin minus of XA on node anode,\n", - " Pin out of XA on node Aout]" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "XA.pins" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#r# We simulate the circuit at these temperatures: 0, 25 and 100 °C.\n", + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "#r# For this purpose, we use the common high-speed diode 1N4148. The diode is driven by a variable\n", + "#r# voltage source through a limiting current resistance.\n", "\n", - "# Fixme: Xyce ???\n", - "temperatures = [0, 25, 100]@u_Degree\n", - "analyses = {}\n", - "for temperature in temperatures:\n", - " simulator = circuit.simulator(temperature=temperature, nominal_temperature=temperature)\n", - " analysis = simulator.dc(Vinput=slice(-2, 5, .01))\n", - " analyses[float(temperature)] = analysis\n", + "#f# circuit_macros('diode-characteristic-curve-circuit.m4')\n", "\n", - "####################################################################################################" + "circuit = Circuit('Capacitive Transimpedance Amplifier (CTIA) readout')\n", + "\n", + "#circuit.include(spice_library['1N4148'])\n", + "\n", + "\n", + "DD = circuit.D('sensor', 'anode', 'Vd',\n", + " schematic_kwargs={'schematic': schem.elements.PHOTODIODE, \n", + " 'd': 'left', 'show_end': True, 'end_label': {'lftlabel': r'$V_d$'},\n", + " 'flip': True,\n", + " 'botlabel': r'$D_d$'\n", + " }\n", + " )\n", + "#circuit.schematic.add(schem.elements.LINE, xy=DD.schematic_element.start, d ='down', l=1.5)\n", + "#CD = circuit.C('d', 'anode', 'Vd', 1@u_uF,\n", + "# schematic_kwargs={'schematic': schem.elements.CAP, 'botlabel': r'$C_d$',\n", + "# 'd': 'left', 'l': DD.schematic_element.dx}\n", + "# )\n", + "#circuit.schematic.add(schem.elements.LINE, xy=CD.schematic_element.end, to=DD.schematic_element.end)\n", + "\n", + "circuit.subcircuit(opamp)\n", + "seg = circuit.schematic.add(schem.elements.LINE, xy=DD.schematic_element.start, d ='right', l=2.0)\n", + "circuit.schematic.labelI(seg, arrowlen=1.0, reverse=False, arrowofst=0.5,\n", + " label=r'$i_S(t)$', top=True)\n", + "\n", + "XA = circuit.X('A', 'BasicOperationalAmplifier', 'Vcom', 'anode', 'Aout',\n", + " schematic_kwargs={'schematic': schem.elements.OPAMP, 'anchor': 'in1', \n", + " 'd': 'right', 'flip':False,\n", + " #'xy': CD.schematic_element.start\n", + " })\n", + "circuit.schematic.add(schem.elements.DOT, lftlabel =r'$V_{com}$', xy=XA.schematic_element.in2)\n", + "\n", + "circuit.schematic.add(schem.elements.LINE, xy=XA.schematic_element.in1, d ='up', l=2.0)\n", + "CINT = circuit.C('int', 'anode', 'Aout', 1@u_uF,\n", + " schematic_kwargs={'schematic': schem.elements.CAP, 'toplabel': r'$C_{int}$',\n", + " 'd': 'right', 'l': XA.schematic_element.dx}\n", + " )\n", + "circuit.schematic.add(schem.elements.LINE, xy=XA.schematic_element.out, d ='up', \n", + " to=CINT.schematic_element.end)\n", + "\n", + "\n", + "circuit.schematic.add(schem.elements.LINE, xy=CINT.schematic_element.start, d ='up', l=2.0)\n", + "RF = circuit.R('F', 'anode', 'Aout', 1@u_MOhm,\n", + " schematic_kwargs={'schematic': schem.elements.RES, 'toplabel': r'$R_F$',\n", + " 'd': 'right', 'l': XA.schematic_element.dx}\n", + " )\n", + "circuit.schematic.add(schem.elements.LINE, xy=CINT.schematic_element.end, d ='up', l=2.0)\n", + "\n", + "circuit.schematic.add(schem.elements.LINE, xy=XA.schematic_element.out, d ='right', l=2.0)\n", + "circuit.schematic.add(schem.elements.DOT_OPEN, label =r'$v_S(t)$')\n", + "\n", + "circuit.schematic.draw()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\\begin{gather}\n", + "v_C(t) \\equiv V_{com}-v_S(t) = V_C + \\frac{1}{C_{int}}\\int_0^{\\tau_{int}}{i_C(t)dt} \\\\\n", + "i_C = i_S - (V_{com}-v_S)/R_F\n", + "\\end{gather}\n", + "\n", + "\\begin{align}\n", + "v_S &= V_{com}-V_C-\\frac{1}{C_{int}}\\int_0^{\\tau_{int}}{i_C(t)dt} \\\\\n", + "&= V_{com}-V_C-\\frac{1}{C_{int}}\\int_0^{\\tau_{int}}{i_S(t)dt} + \\frac{1}{C_{int}}\\int_0^{\\tau_{int}}{\\frac{V_{com}-v_S}{R_F}dt} \\\\\n", + "&= V_{com}-V_C-\\frac{1}{C_{int}}\\int_0^{\\tau_{int}}{i_S(t)dt} + \\frac{V_{com}\\tau_{int}}{R_FC_{int}}\n", + "-\\frac{1}{R_FC_{int}}\\int_0^{\\tau_{int}}{v_S(t)dt}\n", + "\\end{align}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Thus the signal voltage $v_S$ is defined by the differential equation,\n", + "\n", + "\\begin{equation}\n", + "\\frac{dv_S}{dt} + \\frac{v_S}{R_FC_{int}} + \\frac{i_S}{C_{int}} = 0\n", + "\\end{equation}\n", + "\n", + "which can be solved as,\n", + "\n", + "\\begin{gather}\n", + "\\mu = e^{\\frac{t}{R_FC_{int}}} \\\\\n", + "\\mu\\left[v_S^\\prime + \\frac{v_S}{R_FC_{int}}\\right] + \\frac{\\mu i_S}{C_{int}} \\\\\n", + "(\\mu v_S)^\\prime = \\mu v_S^\\prime + \\mu^\\prime v_S \\\\\n", + "\\mu^\\prime = \\frac{\\mu}{R_FC_{int}} \\\\\n", + "(\\mu v_S)^\\prime + \\frac{\\mu i_S}{C_{int}} = 0 \\\\\n", + "v_S(\\tau_{int}) = v_S(0) -\\frac{e^{\\frac{-\\tau_{int}}{R_FC_{int}}}}{R_FC_{int}} \\int_0^{\\tau_{int}}{e^{\\frac{t}{R_FC_{int}}} R_F i_S dt}\\\\\n", + "v_S(\\tau_{int}) = v_S(0) -\\frac{1}{R_FC_{int}} \\int_0^{\\tau_{int}}{e^{\\frac{t-\\tau_{int}}{R_FC_{int}}} R_F i_S dt}\\\\\n", + "v_S(\\infty) \\equiv R_F i_S(\\infty) \\\\\n", + "\\lim_{\\tau_{int}\\to\\infty}\\int_0^{\\tau_{int}}{e^{\\frac{t-\\tau_{int}}{R_FC_{int}}} R_F i_S dt} = R_F C_{int} \\left[ v_S(0)-v_S(\\infty) \\right]\n", + "\\end{gather}" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "def i_S(t):\n", + " t_off = np.argwhere(np.logical_or(t>i_S.t1, t<=i_S.t0))\n", + " t_on = np.argwhere(np.logical_and(t<=i_S.t1, t>i_S.t0))\n", + " i = np.zeros_like(t)\n", + " i[t_on] = i_S.val #i_S.tramp*t[t_pos]\n", + " \n", + " return i\n", + "\n", + "\n", + "R_F = 1.0*MOhm\n", + "C_INT = 0.1*nF\n", + "rc_delay = R_F*C_INT\n", + "\n", + "i_S.val = -100.0*nA\n", + "i_S.t0 = 0.0\n", + "i_S.t1 = 0.5*rc_delay\n", + "i_S.tramp = 0.1*mA/mus\n", + "\n", + "def v_S(tau_int, RF=R_F, CINT=C_INT, v0=0.0*Volt):\n", + " \n", + " v = np.zeros_like(tau_int)\n", + " for j, tau in enumerate(tau_int):\n", + " t = np.linspace(0,tau, 500)\n", + " i = i_S(t)\n", + " f = np.exp((t-tau)/RF/CINT)*RF*i\n", + " I = np.trapz(f,x=t)\n", + " v[j] = v0 - I/RF/CINT\n", + " \n", + " return v\n", + "\n", + "\n", + "t = np.linspace(0, 4.0*rc_delay,100)\n", + "\n", + "pb.plot(t/rc_delay, i_S(t)/nA, ls='--', c='b')\n", + "ax1 = pb.gca()\n", + "ax2 = pb.twinx(ax1)\n", + "tau_int = np.linspace(0.0, 4.0*rc_delay, 100)\n", + "pb.plot(tau_int/rc_delay, v_S(tau_int)/mV, c='b')\n", + "i_S.val = -200.0*nA\n", + "i_S.tramp = 0.2*mA/mus\n", + "ax1.plot(t/rc_delay, i_S(t)/nA, ls='--', c='r')\n", + "ax2.plot(tau_int/rc_delay, v_S(tau_int)/mV, c='r')\n", + "ax2.axvline(rc_delay/rc_delay, c='k', ls='--')\n", + "\n", + "ax1.set_xlabel('Time ($RC$)')\n", + "ax1.set_ylabel('$i_S$ ($nA$)')\n", + "ax2.set_ylabel('$v_S$ ($mV$)')\n", + "ax2.set_yscale('linear')" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "scrolled": false - }, + "metadata": {}, "outputs": [], "source": [ - "#r# We plot the characteristic curve and compare it to the Shockley diode model:\n", - "#r#\n", - "#r# .. math::\n", - "#r#\n", - "#r# I_d = I_s \\left( e^{\\frac{V_d}{n V_T}} - 1 \\right)\n", - "#r#\n", - "#r# where :math:`V_T = \\frac{k T}{q}`\n", - "#r#\n", - "#r# In order to scale the reverse biased region, we have to do some hack with Matplotlib.\n", - "#r#\n", - "\n", - "silicon_forward_voltage_threshold = .7\n", - "\n", - "shockley_diode = ShockleyDiode(Is=4.0@u_nA, degree=25)\n", - "\n", - "def two_scales_tick_formatter(value, position):\n", - " if value >= 0:\n", - " return '{} mA'.format(value)\n", - " else:\n", - " return '{} nA'.format(value/100)\n", - "formatter = ticker.FuncFormatter(two_scales_tick_formatter)\n", - "\n", - "figure = plt.figure(1, (10, 5))\n", - "\n", - "axe = plt.subplot(121)\n", - "axe.set_title('1N4148 Characteristic Curve ')\n", - "axe.set_xlabel('Voltage [V]')\n", - "axe.set_ylabel('Current')\n", - "axe.grid()\n", - "axe.set_xlim(-2, 2)\n", - "axe.axvspan(-2, 0, facecolor='green', alpha=.2)\n", - "axe.axvspan(0, silicon_forward_voltage_threshold, facecolor='blue', alpha=.1)\n", - "axe.axvspan(silicon_forward_voltage_threshold, 2, facecolor='blue', alpha=.2)\n", - "#axe.set_ylim(-500, 750) # Fixme: round\n", - "#axe.yaxis.set_major_formatter(formatter)\n", - "Vd = analyses[25].Vout\n", - "# compute scale for reverse and forward region\n", - "forward_region = Vd >= 0@u_V\n", - "reverse_region = np.invert(forward_region)\n", - "scale = reverse_region*1e11 + forward_region*1e3\n", - "#?# check temperature\n", - "for temperature in temperatures:\n", - " analysis = analyses[float(temperature)]\n", - " axe.plot(Vd, np.abs(- analysis.Vinput))\n", - "axe.plot(Vd, np.abs(shockley_diode.I(Vd)), 'black')\n", - "axe.set_ylim(1e-9, 1e3)\n", - "axe.set_yscale('log')\n", - "axe.legend(['@ {}'.format(temperature)\n", - " for temperature in temperatures] + ['Shockley Diode Model Is = 4 nA'],\n", - " loc=2, fontsize=10)\n", - "axe.axvline(x=0, color='black')\n", - "axe.axhline(y=0, color='black')\n", - "axe.axvline(x=silicon_forward_voltage_threshold, color='red')\n", - "axe.text(-1, -100, 'Reverse Biased Region', ha='center', va='center')\n", - "axe.text( 1, -100, 'Forward Biased Region', ha='center', va='center')" + "np.trapz?" ] } ], From 3892217b21699a30ea57ef102eb69ed292d2d6a9 Mon Sep 17 00:00:00 2001 From: "Jose M. Gomez" Date: Wed, 28 Nov 2018 14:31:42 +0100 Subject: [PATCH 006/134] Correction of the method SubCircuit.check_nodes() Change of the definition of Node to ensure the node name is always stored in lower case. Converted the _external_nodes in SubCircuit into a tuple of Nodes. Modified SubCircuit.check_nodes to use the Node name property, and a dictionary to verify if a node has more than one element connected. --- PySpice/Spice/Netlist.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index 462db633a..dedca88bb 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -746,7 +746,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() @@ -1067,6 +1067,7 @@ def __init__(self, name, *nodes, **kwargs): self._name = str(name) self._external_nodes = nodes + self._external_nodes = tuple([Node(self, str(node)) for node in nodes]) # Fixme: ok ? self._ground = kwargs.get('ground', 0) @@ -1111,13 +1112,21 @@ 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): From 53b2acd4279737661afc764b7e148f03ab3e0c8e Mon Sep 17 00:00:00 2001 From: "Jose M. Gomez" Date: Wed, 28 Nov 2018 14:33:11 +0100 Subject: [PATCH 007/134] Update Netlist.py Remove unused line. --- PySpice/Spice/Netlist.py | 1 - 1 file changed, 1 deletion(-) diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index dedca88bb..477763c73 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -1066,7 +1066,6 @@ def __init__(self, name, *nodes, **kwargs): super().__init__() self._name = str(name) - self._external_nodes = nodes self._external_nodes = tuple([Node(self, str(node)) for node in nodes]) # Fixme: ok ? From cfd75310997178031cd462a1ed49a8fb8be47955 Mon Sep 17 00:00:00 2001 From: "Jose M. Gomez" Date: Sat, 1 Dec 2018 15:30:08 +0100 Subject: [PATCH 008/134] Improved the subcircuit management. --- PySpice/Spice/Netlist.py | 39 ++++++++++++++++++++++++++++++++++++--- PySpice/Spice/Parser.py | 3 +-- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index 19ef2846d..3ab7cfe7e 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -84,7 +84,6 @@ def __init__(self, **kwargs): import networkx import SchemDraw -import numpy as np #################################################################################################### @@ -147,6 +146,7 @@ class DeviceModel: ############################################## def __init__(self, name, modele_type, **parameters): + self._include = None self._name = str(name) self._model_type = str(modele_type) @@ -178,6 +178,16 @@ def model_type(self): def parameters(self): return self._parameters.keys() + @property + def include(self): + """Include file""" + return self._include + + @property + def is_included(self): + """is_included""" + return self._include is None + ############################################## def __getitem__(self, name): @@ -1114,15 +1124,17 @@ class SubCircuit(Netlist): ############################################## def __init__(self, name, *nodes, **kwargs): + self._include = 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 = tuple([Node(self, str(node)) for node in nodes]) - + self.__pins__ = nodes # Fixme: ok ? self._ground = kwargs.get('ground', 0) if 'ground' in kwargs: @@ -1159,6 +1171,16 @@ def parameters(self): """Parameters""" return self._parameters + @property + def include(self): + """Include file""" + return self._include + + @property + def is_included(self): + """is_included""" + return self._include is None + ############################################## def check_nodes(self): @@ -1266,11 +1288,22 @@ def clone(self, title=None): ############################################## def include(self, path): + from .Parser import SpiceParser """Include a file.""" if path not in self._includes: self._includes.append(path) + parser = SpiceParser(path=path) + subcircuits = parser.subcircuits + for subcircuit in subcircuits: + subcircuit_def = subcircuit.build() + self.subcircuit(subcircuit_def) + self._subcircuits[subcircuit._name]._include = path + models = parser.models + for model in models: + self.model(model._name, model._model_type, **model._parameters) + self._models[model._name]._include = path else: self._logger.warn("Duplicated include") diff --git a/PySpice/Spice/Parser.py b/PySpice/Spice/Parser.py index 3955713eb..45fccf0d5 100644 --- a/PySpice/Spice/Parser.py +++ b/PySpice/Spice/Parser.py @@ -35,7 +35,6 @@ #################################################################################################### -from .BasicElement import SubCircuitElement, BipolarJunctionTransistor from .ElementParameter import FlagParameter from .Netlist import ElementParameterMetaClass, NPinElement, Circuit, SubCircuit @@ -388,7 +387,7 @@ def to_python(self, ground=0): def build(self, ground=0): - subcircuit = SubCircuit(self._name, self._nodes) + subcircuit = SubCircuit(self._name, *self._nodes) SpiceParser._build_circuit(subcircuit, self._statements, ground) return subcircuit From 10ef59bbd2310520b344e437b8d81159c8b9fc38 Mon Sep 17 00:00:00 2001 From: "Jose M. Gomez" Date: Sat, 1 Dec 2018 21:11:58 +0100 Subject: [PATCH 009/134] Modified the variable names to avoid the __var_name__ format. --- PySpice/Spice/BasicElement.py | 82 ++++---- PySpice/Spice/Netlist.py | 89 ++++----- PySpice/Spice/Parser.py | 2 +- PySpice/Spice/__init__.py | 2 +- PySpice/Unit/SiUnits.py | 348 +++++++++++++++++----------------- PySpice/Unit/Unit.py | 120 ++++++------ PySpice/Unit/__init__.py | 2 +- unit-test/Unit/test_Units.py | 2 +- 8 files changed, 324 insertions(+), 323 deletions(-) diff --git a/PySpice/Spice/BasicElement.py b/PySpice/Spice/BasicElement.py index e4470d3d6..f1204562d 100644 --- a/PySpice/Spice/BasicElement.py +++ b/PySpice/Spice/BasicElement.py @@ -123,11 +123,11 @@ class DipoleElement(FixedPinElement): """This class implements a base class for dipole element.""" - __pins__ = ('plus', 'minus') + _pins_ = ('plus', 'minus') class TwoPortElement(FixedPinElement): """This class implements a base class for two-port element.""" - __pins__ = ('output_plus', 'output_minus', 'input_plus', 'input_minus') + _pins_ = ('output_plus', 'output_minus', 'input_plus', 'input_minus') #################################################################################################### @@ -150,7 +150,7 @@ class SubCircuitElement(NPinElement): """ __alias__ = 'X' - __prefix__ = 'X' + _prefix_ = 'X' subcircuit_name = ElementNamePositionalParameter(position=0, key_parameter=False) @@ -248,7 +248,7 @@ class Resistor(DipoleElement): """ __alias__ = 'R' - __prefix__ = 'R' + _prefix_ = 'R' schematic = schem.elements.RES @@ -322,7 +322,7 @@ class SemiconductorResistor(DipoleElement): """ __alias__ = 'SemiconductorResistor' - __prefix__ = 'R' + _prefix_ = 'R' resistance = FloatPositionalParameter(position=0, key_parameter=False, unit=U_Ω) model = ModelPositionalParameter(position=1, key_parameter=True) @@ -367,7 +367,7 @@ class BehavioralResistor(DipoleElement): """ __alias__ = 'BehavioralResistor' - __prefix__ = 'R' + _prefix_ = 'R' resistance_expression = ExpressionPositionalParameter(position=0, key_parameter=False) tc1 = FloatKeyParameter('tc1') @@ -422,7 +422,7 @@ class Capacitor(DipoleElement): """ __alias__ = 'C' - __prefix__ = 'C' + _prefix_ = 'C' capacitance = FloatPositionalParameter(position=0, key_parameter=False, unit=U_F) model = ModelPositionalParameter(position=1, key_parameter=True) @@ -491,7 +491,7 @@ class SemiconductorCapacitor(DipoleElement): """ __alias__ = 'SemiconductorCapacitor' - __prefix__ = 'C' + _prefix_ = 'C' capacitance = FloatPositionalParameter(position=0, key_parameter=False, unit=U_F) model = ModelPositionalParameter(position=1, key_parameter=True) @@ -533,7 +533,7 @@ class BehavioralCapacitor(DipoleElement): """ __alias__ = 'BehavioralCapacitor' - __prefix__ = 'C' + _prefix_ = 'C' capacitance_expression = ExpressionPositionalParameter(position=0, key_parameter=False) tc1 = FloatKeyParameter('tc1') @@ -590,7 +590,7 @@ class Inductor(DipoleElement): """ __alias__ = 'L' - __prefix__ = 'L' + _prefix_ = 'L' inductance = FloatPositionalParameter(position=0, key_parameter=False, unit=U_H) model = ModelPositionalParameter(position=1, key_parameter=True) @@ -631,7 +631,7 @@ class BehavioralInductor(DipoleElement): """ __alias__ = 'BehavioralInductor' - __prefix__ = 'L' + _prefix_ = 'L' inductance_expression = ExpressionPositionalParameter(position=0, key_parameter=False) tc1 = FloatKeyParameter('tc1') @@ -662,7 +662,7 @@ class CoupledInductor(AnyPinElement): """ __alias__ = 'K' - __prefix__ = 'K' + _prefix_ = 'K' inductor1 = ElementNamePositionalParameter(position=0, key_parameter=False) inductor2 = ElementNamePositionalParameter(position=1, key_parameter=False) @@ -704,7 +704,7 @@ class VoltageControlledSwitch(TwoPortElement): __alias__ = 'S' __long_alias__ = 'VCS' - __prefix__ = 'S' + _prefix_ = 'S' model = ModelPositionalParameter(position=0, key_parameter=True) initial_state = InitialStatePositionalParameter(position=1, key_parameter=True) @@ -741,7 +741,7 @@ class CurrentControlledSwitch(DipoleElement): __alias__ = 'W' __long_alias__ = 'CCS' - __prefix__ = 'W' + _prefix_ = 'W' source = ElementNamePositionalParameter(position=0, key_parameter=True) model = ModelPositionalParameter(position=1, key_parameter=True) @@ -772,7 +772,7 @@ class VoltageSource(DipoleElement): """ __alias__ = 'V' - __prefix__ = 'V' + _prefix_ = 'V' schematic = schem.elements.SOURCE_V @@ -800,7 +800,7 @@ class CurrentSource(DipoleElement): """ __alias__ = 'I' - __prefix__ = 'I' + _prefix_ = 'I' # Fixme: ngspice manual doesn't describe well the syntax dc_value = FloatPositionalParameter(position=0, key_parameter=False, unit=U_A) @@ -829,7 +829,7 @@ class VoltageControlledCurrentSource(TwoPortElement): """ __alias__ = 'VCCS' - __prefix__ = 'G' + _prefix_ = 'G' transconductance = ExpressionPositionalParameter(position=0, key_parameter=False) multiplier = IntKeyParameter('m') @@ -855,7 +855,7 @@ class VoltageControlledVoltageSource(TwoPortElement): """ __alias__ = 'VCVS' - __prefix__ = 'E' + _prefix_ = 'E' voltage_gain = ExpressionPositionalParameter(position=0, key_parameter=False) @@ -886,7 +886,7 @@ class CurrentControlledCurrentSource(DipoleElement): __alias__ = 'F' __long_alias__ = 'CCCS' - __prefix__ = 'F' + _prefix_ = 'F' source = ElementNamePositionalParameter(position=0, key_parameter=False) current_gain = ExpressionPositionalParameter(position=1, key_parameter=False) @@ -916,7 +916,7 @@ class CurrentControlledVoltageSource(DipoleElement): __alias__ = 'H' __long_alias__ = 'CCVS' - __prefix__ = 'H' + _prefix_ = 'H' source = ElementNamePositionalParameter(position=0, key_parameter=False) transresistance = ExpressionPositionalParameter(position=1, key_parameter=False) @@ -972,7 +972,7 @@ class BehavioralSource(DipoleElement): """ __alias__ = 'B' - __prefix__ = 'B' + _prefix_ = 'B' current_expression = ExpressionKeyParameter('i') voltage_expression = ExpressionKeyParameter('v') @@ -1006,7 +1006,7 @@ class NonLinearVoltageSource(DipoleElement): """ __alias__ = 'NonLinearVoltageSource' - __prefix__ = 'E' + _prefix_ = 'E' ############################################## @@ -1055,7 +1055,7 @@ class NonLinearCurrentSource(DipoleElement): """ __alias__ = 'NonLinearCurrentSource' - __prefix__ = 'G' + _prefix_ = 'G' transconductance = ExpressionPositionalParameter(position=0, key_parameter=False) @@ -1117,8 +1117,8 @@ class Diode(FixedPinElement): """ __alias__ = 'D' - __prefix__ = 'D' - __pins__ = (('cathode', 'plus'), ('anode', 'minus')) + _prefix_ = 'D' + _pins_ = (('cathode', 'plus'), ('anode', 'minus')) model = ModelPositionalParameter(position=0, key_parameter=True) area = FloatKeyParameter('area') @@ -1194,8 +1194,8 @@ class BipolarJunctionTransistor(FixedPinElement): __alias__ = 'Q' __long_alias__ = 'BJT' - __prefix__ = 'Q' - __pins__ = ('collector', 'base', 'emitter', OptionalPin('substrate')) + _prefix_ = 'Q' + _pins_ = ('collector', 'base', 'emitter', OptionalPin('substrate')) model = ModelPositionalParameter(position=0, key_parameter=True) area = FloatKeyParameter('area') @@ -1214,7 +1214,7 @@ class BipolarJunctionTransistor(FixedPinElement): #################################################################################################### class JfetElement(FixedPinElement): - __pins__ = ('drain', 'gate', 'source') + _pins_ = ('drain', 'gate', 'source') class JunctionFieldEffectTransistor(JfetElement): @@ -1257,7 +1257,7 @@ class JunctionFieldEffectTransistor(JfetElement): __alias__ = 'J' __long_alias__ = 'JFET' - __prefix__ = 'J' + _prefix_ = 'J' model = ModelPositionalParameter(position=0, key_parameter=True) area = FloatKeyParameter('area') @@ -1308,7 +1308,7 @@ class Mesfet(JfetElement): __alias__ = 'Z' __long_alias__ = 'MESFET' - __prefix__ = 'Z' + _prefix_ = 'Z' model = ModelPositionalParameter(position=0, key_parameter=True) area = FloatKeyParameter('area') @@ -1406,8 +1406,8 @@ class Mosfet(FixedPinElement): __alias__ = 'M' __long_alias__ = 'MOSFET' - __prefix__ = 'M' - __pins__ = ('drain', 'gate', 'source', ('bulk', 'substrate')) + _prefix_ = 'M' + _pins_ = ('drain', 'gate', 'source', ('bulk', 'substrate')) model = ModelPositionalParameter(position=0, key_parameter=True) multiplier = IntKeyParameter('m') @@ -1470,7 +1470,7 @@ class LosslessTransmissionLine(TwoPortElement): """ __alias__ = 'TransmissionLine' - __prefix__ = 'T' + _prefix_ = 'T' impedance = FloatKeyParameter('Z0', default=50, unit=U_Ω) time_delay = FloatKeyParameter('TD', unit=U_s) @@ -1509,7 +1509,7 @@ class LossyTransmission(TwoPortElement): """ __alias__ = 'O' - __prefix__ = 'O' + _prefix_ = 'O' model = ModelPositionalParameter(position=0, key_parameter=True) @@ -1538,7 +1538,7 @@ class CoupledMulticonductorLine(NPinElement): """ __alias__ = 'P' - __prefix__ = 'P' + _prefix_ = 'P' model = ModelPositionalParameter(position=0, key_parameter=True) length = FloatKeyParameter('len', unit=U_m) @@ -1577,8 +1577,8 @@ class UniformDistributedRCLine(FixedPinElement): """ __alias__ = 'U' - __prefix__ = 'U' - __pins__ = ('output', 'input', 'capacitance_node') + _prefix_ = 'U' + _pins_ = ('output', 'input', 'capacitance_node') model = ModelPositionalParameter(position=0, key_parameter=True) length = FloatKeyParameter('l', unit=U_m) @@ -1611,7 +1611,7 @@ class SingleLossyTransmissionLine(TwoPortElement): """ __alias__ = 'Y' - __prefix__ = 'Y' + _prefix_ = 'Y' model = ModelPositionalParameter(position=0, key_parameter=True) length = FloatKeyParameter('len', unit=U_m) @@ -1653,7 +1653,7 @@ class XSpiceElement(NPinElement): """ __alias__ = 'A' - __prefix__ = 'A' + _prefix_ = 'A' model = ModelPositionalParameter(position=0, key_parameter=True) @@ -1663,7 +1663,7 @@ def __init__(self, netlist, name, *nodes, **parameters): # Fixme: ok ??? - super().__init__(netlist, name, nodes, **parameters) + super().__init__(netlist, name, *nodes, **parameters) #################################################################################################### # @@ -1679,7 +1679,7 @@ class GSSElement(NPinElement): """ __alias__ = 'N' - __prefix__ = 'N' + _prefix_ = 'N' ############################################## diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index 3ab7cfe7e..15da4c3c1 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -331,6 +331,7 @@ def add_current_probe(self, circuit): #################################################################################################### + class ElementParameterMetaClass(type): # Metaclass to implements the element node and parameter machinery. @@ -350,7 +351,7 @@ class Resistor(metaclass=ElementParameterMetaClass): """ #: Dictionary for SPICE prefix -> [cls,] - __classes__ = {} + _classes_ = {} _logger = _module_logger.getChild('ElementParameterMetaClass') @@ -376,25 +377,25 @@ def __new__(cls, class_name, base_classes, namespace): d[attribute_name] = obj # Dictionary for positional parameters : attribute_name -> parameter - namespace['__positional_parameters__'] = OrderedDict( + namespace['_positional_parameters_'] = OrderedDict( sorted(list(positional_parameters.items()), key=lambda t: t[1])) # Dictionary for optional parameters # order is not required for SPICE, but for unit test - namespace['__optional_parameters__'] = OrderedDict( + namespace['_optional_parameters_'] = OrderedDict( sorted(list(parameters.items()), key=lambda t: t[0])) # Positional parameter array - namespace['__parameters_from_args__'] = [ + namespace['_parameters_from_args_'] = [ parameter for parameter in sorted(positional_parameters.values()) if not parameter.key_parameter] # Implement alias for parameters: spice name -> parameter - namespace['__spice_to_parameters__'] = { + namespace['_spice_to_parameters_'] = { parameter.spice_name:parameter - for parameter in namespace['__optional_parameters__'].values()} - for parameter in namespace['__spice_to_parameters__'].values(): + for parameter in namespace['_optional_parameters_'].values()} + 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)) @@ -411,10 +412,10 @@ def getter(self): return self._pins[position] if position < len(self._pins) else None return getter - if '__pins__' in namespace and namespace['__pins__'] is not None: + if '_pins_' in namespace and namespace['_pins_'] is not None: number_of_optional_pins = 0 pins = [] - for position, pin_definition in enumerate(namespace['__pins__']): + for position, pin_definition in enumerate(namespace['_pins_']): # ensure pin_definition is a tuple if isinstance(pin_definition, OptionalPin): optional = True @@ -435,10 +436,10 @@ def getter(self): # Add pin pin = PinDefinition(position, *pin_definition, optional=optional) pins.append(pin) - namespace['__pins__'] = pins - namespace['__number_of_optional_pins__'] = number_of_optional_pins + namespace['_pins_'] = pins + namespace['_number_of_optional_pins_'] = number_of_optional_pins else: - _module_logger.debug("{} don't define a __pins__ attribute".format(class_name)) + _module_logger.debug("{} don't define a _pins_ attribute".format(class_name)) return type.__new__(cls, class_name, base_classes, namespace) @@ -451,10 +452,10 @@ def __init__(cls, class_name, base_classes, namespace): type.__init__(cls, class_name, base_classes, namespace) # Collect basic element classes - if '__prefix__' in namespace: - prefix = namespace['__prefix__'] + if '_prefix_' in namespace: + prefix = namespace['_prefix_'] if prefix is not None: - classes = ElementParameterMetaClass.__classes__ + classes = ElementParameterMetaClass._classes_ if prefix in classes: classes[prefix].append(cls) else: @@ -468,31 +469,31 @@ def __init__(cls, class_name, base_classes, namespace): @property def number_of_pins(self): #! Fixme: many pins ??? - number_of_pins = len(self.__pins__) - if self.__number_of_optional_pins__: - return slice(number_of_pins - self.__number_of_optional_pins__, number_of_pins +1) + number_of_pins = len(self._pins_) + if self._number_of_optional_pins_: + return slice(number_of_pins - self._number_of_optional_pins_, number_of_pins + 1) else: return number_of_pins @property def number_of_positional_parameters(self): - return len(self.__positional_parameters__) + return len(self._positional_parameters_) @property def positional_parameters(self): - return self.__positional_parameters__ + return self._positional_parameters_ @property def optional_parameters(self): - return self.__optional_parameters__ + return self._optional_parameters_ @property def parameters_from_args(self): - return self.__parameters_from_args__ + return self._parameters_from_args_ @property def spice_to_parameters(self): - return self.__spice_to_parameters__ + return self._spice_to_parameters_ # @property # def schematic(self): @@ -509,14 +510,14 @@ class Element(metaclass=ElementParameterMetaClass): """ # These class attributes are defined in subclasses or via the metaclass. - __pins__ = None - __positional_parameters__ = None - __optional_parameters__ = None - __parameters_from_args__ = None - __spice_to_parameters__ = None + _pins_ = None + _positional_parameters_ = None + _optional_parameters_ = None + _parameters_from_args_ = None + _spice_to_parameters_ = None #: SPICE element prefix - __prefix__ = None + _prefix_ = None schematic = None @@ -531,16 +532,16 @@ def __init__(self, netlist, name, *args, **kwargs): #self._pins = kwargs.pop('pins',()) # Process remaining args - if len(self.__parameters_from_args__) < len(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): + for parameter, value in zip(self._parameters_from_args_, args): setattr(self, parameter.attribute_name, value) # 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__: + elif key in self._positional_parameters_ or key in self._optional_parameters_: setattr(self, key, value) schematic_kwargs = kwargs.pop('schematic_kwargs', {}) @@ -552,7 +553,7 @@ def __init__(self, netlist, name, *args, **kwargs): def copy_to(self, element): - for parameter_dict in self.__positional_parameters__, self.__optional_parameters__: + for parameter_dict in self._positional_parameters_, self._optional_parameters_: for parameter in parameter_dict.values(): if hasattr(self, parameter.attribute_name): value = getattr(self, parameter.attribute_name) @@ -569,7 +570,7 @@ def netlist(self): @property def name(self): - return self.__prefix__ + self._name + return self._prefix_ + self._name @property def pins(self): @@ -606,8 +607,8 @@ def __repr__(self): def __setattr__(self, name, value): # Implement alias for parameters - if name in self.__spice_to_parameters__: - parameter = self.__spice_to_parameters__[name] + if name in self._spice_to_parameters_: + parameter = self._spice_to_parameters_[name] object.__setattr__(self, parameter.attribute_name, value) else: object.__setattr__(self, name, value) @@ -617,8 +618,8 @@ def __setattr__(self, name, value): def __getattr__(self, name): # Implement alias for parameters - if name in self.__spice_to_parameters__: - parameter = self.__spice_to_parameters__[name] + if name in self._spice_to_parameters_: + parameter = self._spice_to_parameters_[name] return object.__getattribute__(self, parameter.attribute_name) else: raise AttributeError(name) @@ -637,7 +638,7 @@ def parameter_iterator(self): # Fixme: .parameters ??? - for parameter_dict in self.__positional_parameters__, self.__optional_parameters__: + for parameter_dict in self._positional_parameters_, self._optional_parameters_: for parameter in parameter_dict.values(): if parameter.nonzero(self): yield parameter @@ -664,7 +665,7 @@ def __str__(self): class AnyPinElement(Element): - __pins__ = () + _pins_ = () ############################################## @@ -696,9 +697,9 @@ def __init__(self, netlist, name, *args, **kwargs): else: nodes = args[:expected_number_of_pins] args = args[expected_number_of_pins:] - pin_definition_nodes = zip(self.__pins__, nodes) + pin_definition_nodes = zip(self._pins_, nodes) else: - for pin_definition in self.__pins__: + for pin_definition in self._pins_: if pin_definition.name in kwargs: node = kwargs[pin_definition.name] del kwargs[pin_definition.name] @@ -731,11 +732,11 @@ def copy_to(self, netlist): class NPinElement(Element): - __pins__ = '*' + _pins_ = '*' ############################################## - def __init__(self, netlist, name, nodes, *args, **kwargs): + def __init__(self, netlist, name, *args, **kwargs): super().__init__(netlist, name, *args, **kwargs) diff --git a/PySpice/Spice/Parser.py b/PySpice/Spice/Parser.py index 45fccf0d5..70920a25d 100644 --- a/PySpice/Spice/Parser.py +++ b/PySpice/Spice/Parser.py @@ -109,7 +109,7 @@ def single(self): #################################################################################################### _prefix_cache = {} -for prefix, classes in ElementParameterMetaClass.__classes__.items(): +for prefix, classes in ElementParameterMetaClass._classes_.items(): prefix_data = PrefixData(prefix, classes) _prefix_cache[prefix] = prefix_data _prefix_cache[prefix.lower()] = prefix_data diff --git a/PySpice/Spice/__init__.py b/PySpice/Spice/__init__.py index 1dfd19f47..e55c52aa7 100644 --- a/PySpice/Spice/__init__.py +++ b/PySpice/Spice/__init__.py @@ -36,7 +36,7 @@ def _get_elements(module): element_classes = [] for item in module.__dict__.values(): if (type(item) is ElementParameterMetaClass - and item.__prefix__ is not None + and item._prefix_ is not None ): element_classes.append(item) return element_classes diff --git a/PySpice/Unit/SiUnits.py b/PySpice/Unit/SiUnits.py index b580ebec8..e632a08ac 100644 --- a/PySpice/Unit/SiUnits.py +++ b/PySpice/Unit/SiUnits.py @@ -32,88 +32,88 @@ # Define SI unit prefixes class Yotta(UnitPrefix): - __power__ = 24 - __prefix__ = 'Y' - __spice_prefix__ = None + _power_ = 24 + _prefix_ = 'Y' + _spice_prefix_ = None class Zetta(UnitPrefix): - __power__ = 21 - __prefix__ = 'Z' - __spice_prefix__ = None + _power_ = 21 + _prefix_ = 'Z' + _spice_prefix_ = None class Exa(UnitPrefix): - __power__ = 18 - __prefix__ = 'E' - __spice_prefix__ = None + _power_ = 18 + _prefix_ = 'E' + _spice_prefix_ = None class Peta(UnitPrefix): - __power__ = 15 - __prefix__ = 'P' - __spice_prefix__ = None + _power_ = 15 + _prefix_ = 'P' + _spice_prefix_ = None class Tera(UnitPrefix): - __power__ = 12 - __prefix__ = 'T' + _power_ = 12 + _prefix_ = 'T' class Giga(UnitPrefix): - __power__ = 9 - __prefix__ = 'G' + _power_ = 9 + _prefix_ = 'G' class Mega(UnitPrefix): - __power__ = 6 - __prefix__ = 'M' - __spice_prefix__ = 'Meg' + _power_ = 6 + _prefix_ = 'M' + _spice_prefix_ = 'Meg' class Kilo(UnitPrefix): - __power__ = 3 - __prefix__ = 'k' + _power_ = 3 + _prefix_ = 'k' class Hecto(UnitPrefix): - __power__ = 2 - __prefix__ = 'h' - __spice_prefix__ = None + _power_ = 2 + _prefix_ = 'h' + _spice_prefix_ = None class Deca(UnitPrefix): - __power__ = 1 - __prefix__ = 'da' - __spice_prefix__ = None + _power_ = 1 + _prefix_ = 'da' + _spice_prefix_ = None class Milli(UnitPrefix): - __power__ = -3 - __prefix__ = 'm' + _power_ = -3 + _prefix_ = 'm' class Micro(UnitPrefix): - __power__ = -6 - __prefix__ = 'μ' - __spice_prefix__ = 'u' + _power_ = -6 + _prefix_ = 'μ' + _spice_prefix_ = 'u' class Nano(UnitPrefix): - __power__ = -9 - __prefix__ = 'n' + _power_ = -9 + _prefix_ = 'n' class Pico(UnitPrefix): - __power__ = -12 - __prefix__ = 'p' + _power_ = -12 + _prefix_ = 'p' class Femto(UnitPrefix): - __power__ = -15 - __prefix__ = 'f' - __spice_prefix__ = None + _power_ = -15 + _prefix_ = 'f' + _spice_prefix_ = None class Atto(UnitPrefix): - __power__ = -18 - __prefix__ = 'a' - __spice_prefix__ = None + _power_ = -18 + _prefix_ = 'a' + _spice_prefix_ = None class Zepto(UnitPrefix): - __power__ = -21 - __prefix__ = 'z' - __spice_prefix__ = None + _power_ = -21 + _prefix_ = 'z' + _spice_prefix_ = None class Yocto(UnitPrefix): - __power__ = -24 - __prefix__ = 'y' - __spice_prefix__ = None + _power_ = -24 + _prefix_ = 'y' + _spice_prefix_ = None # Fixme: ngspice defines mil @@ -122,206 +122,206 @@ class Yocto(UnitPrefix): # Define SI units class Metre(SiBaseUnit): - __unit_name__ = 'metre' - __unit_suffix__ = 'm' - __quantity__ = 'length' + _unit_name_ = 'metre' + _unit_suffix_ = 'm' + _quantity_ = 'length' class Kilogram(SiBaseUnit): - __unit_name__ = 'kilogram' - __unit_suffix__ = 'kg' - __quantity__ = 'mass' + _unit_name_ = 'kilogram' + _unit_suffix_ = 'kg' + _quantity_ = 'mass' class Second(SiBaseUnit): - __unit_name__ = 'second' - __unit_suffix__ = 's' - __quantity__ = 'time' + _unit_name_ = 'second' + _unit_suffix_ = 's' + _quantity_ = 'time' __is_si__ = True class Ampere(SiBaseUnit): - __unit_name__ = 'ampere' - __unit_suffix__ = 'A' - __quantity__ = 'electric current' + _unit_name_ = 'ampere' + _unit_suffix_ = 'A' + _quantity_ = 'electric current' class Kelvin(SiBaseUnit): - __unit_name__ = 'kelvin' - __unit_suffix__ = 'K' - __quantity__ = 'thermodynamic temperature' + _unit_name_ = 'kelvin' + _unit_suffix_ = 'K' + _quantity_ = 'thermodynamic temperature' class Mole(SiBaseUnit): - __unit_name__ = 'mole' - __unit_suffix__ = 'mol' - __quantity__ = 'amount of substance' + _unit_name_ = 'mole' + _unit_suffix_ = 'mol' + _quantity_ = 'amount of substance' class Candela(SiBaseUnit): - __unit_name__ = 'candela' - __unit_suffix__ = 'cd' - __quantity__ = 'luminosity intensity' + _unit_name_ = 'candela' + _unit_suffix_ = 'cd' + _quantity_ = 'luminosity intensity' #################################################################################################### # Define Derived units class Radian(Unit): - __unit_name__ = 'radian' - __unit_suffix__ = 'rad' - __quantity__ = 'angle' - __si_unit__ = 'm*m^-1' - __default_unit__ = True + _unit_name_ = 'radian' + _unit_suffix_ = 'rad' + _quantity_ = 'angle' + _si_unit_ = 'm*m^-1' + _default_unit_ = True class Steradian(Unit): - __unit_name__ = 'steradian' - __unit_suffix__ = 'sr' - __quantity__ = 'solid angle' - __si_unit__ = 'm^2*m^-2' - __default_unit__ = True + _unit_name_ = 'steradian' + _unit_suffix_ = 'sr' + _quantity_ = 'solid angle' + _si_unit_ = 'm^2*m^-2' + _default_unit_ = True class Hertz(Unit): - __unit_name__ = 'frequency' - __unit_suffix__ = 'Hz' - __quantity__ = 'frequency' - __si_unit__ = 's^-1' - __default_unit__ = True + _unit_name_ = 'frequency' + _unit_suffix_ = 'Hz' + _quantity_ = 'frequency' + _si_unit_ = 's^-1' + _default_unit_ = True class Newton(Unit): - __unit_name__ = 'newton' - __unit_suffix__ = 'N' - __quantity__ = 'force' - __si_unit__ = 'kg*m*s^-2' - __default_unit__ = True + _unit_name_ = 'newton' + _unit_suffix_ = 'N' + _quantity_ = 'force' + _si_unit_ = 'kg*m*s^-2' + _default_unit_ = True class Pascal(Unit): - __unit_name__ = 'pascal' - __unit_suffix__ = 'Pa' - __quantity__ = 'pressure' - __si_unit__ = 'kg*m^-1*s^-2' - __default_unit__ = True + _unit_name_ = 'pascal' + _unit_suffix_ = 'Pa' + _quantity_ = 'pressure' + _si_unit_ = 'kg*m^-1*s^-2' + _default_unit_ = True # N/m^2 class Joule(Unit): - __unit_name__ = 'joule' - __unit_suffix__ = 'J' - __quantity__ = 'energy' - __si_unit__ = 'kg*m^2*s^-2' - __default_unit__ = True + _unit_name_ = 'joule' + _unit_suffix_ = 'J' + _quantity_ = 'energy' + _si_unit_ = 'kg*m^2*s^-2' + _default_unit_ = True # N*m class Watt(Unit): - __unit_name__ = 'watt' - __unit_suffix__ = 'W' - __quantity__ = 'power' - __si_unit__ = 'kg*m^2*s^-3' - __default_unit__ = True + _unit_name_ = 'watt' + _unit_suffix_ = 'W' + _quantity_ = 'power' + _si_unit_ = 'kg*m^2*s^-3' + _default_unit_ = True # J/s class Coulomb(Unit): - __unit_name__ = 'coulomb' - __unit_suffix__ = 'C' - __quantity__ = 'electric charge' - __si_unit__ = 's*A' - __default_unit__ = True + _unit_name_ = 'coulomb' + _unit_suffix_ = 'C' + _quantity_ = 'electric charge' + _si_unit_ = 's*A' + _default_unit_ = True class Volt(Unit): - __unit_name__ = 'volt' - __unit_suffix__ = 'V' - __quantity__ = 'voltage' - __si_unit__ = 'kg*m^2*s^-3*A^-1' - __default_unit__ = True + _unit_name_ = 'volt' + _unit_suffix_ = 'V' + _quantity_ = 'voltage' + _si_unit_ = 'kg*m^2*s^-3*A^-1' + _default_unit_ = True # W/A class Farad(Unit): - __unit_name__ = 'farad' - __unit_suffix__ = 'F' - __quantity__ = 'capacitance' - __si_unit__ = 'kg^-1*m^-2*s^4*A^2' - __default_unit__ = True + _unit_name_ = 'farad' + _unit_suffix_ = 'F' + _quantity_ = 'capacitance' + _si_unit_ = 'kg^-1*m^-2*s^4*A^2' + _default_unit_ = True # C/V class Ohm(Unit): - __unit_name__ = 'ohm' - __unit_suffix__ = 'Ω' - __quantity__ = 'electric resistance, impedance, reactance' - __si_unit__ = 'kg*m^2*s^-3*A^-2' - __default_unit__ = True + _unit_name_ = 'ohm' + _unit_suffix_ = 'Ω' + _quantity_ = 'electric resistance, impedance, reactance' + _si_unit_ = 'kg*m^2*s^-3*A^-2' + _default_unit_ = True # V/A class Siemens(Unit): - __unit_name__ = 'siemens' - __unit_suffix__ = 'S' - __quantity__ = 'electrical conductance' - __si_unit__ = 'kg^-1*m^-2*s^3*A^2' - __default_unit__ = True + _unit_name_ = 'siemens' + _unit_suffix_ = 'S' + _quantity_ = 'electrical conductance' + _si_unit_ = 'kg^-1*m^-2*s^3*A^2' + _default_unit_ = True # A/V class Weber(Unit): - __unit_name__ = 'weber' - __unit_suffix__ = 'Wb' - __quantity__ = 'magnetic flux' - __si_unit__ = 'kg*m^2*s^-2*A^-1' - __default_unit__ = True + _unit_name_ = 'weber' + _unit_suffix_ = 'Wb' + _quantity_ = 'magnetic flux' + _si_unit_ = 'kg*m^2*s^-2*A^-1' + _default_unit_ = True # V*s class Tesla(Unit): - __unit_name__ = 'tesla' - __unit_suffix__ = '' - __quantity__ = 'T' - __si_unit__ = 'kg*s^-2*A^-1' - __default_unit__ = True + _unit_name_ = 'tesla' + _unit_suffix_ = '' + _quantity_ = 'T' + _si_unit_ = 'kg*s^-2*A^-1' + _default_unit_ = True # Wb/m2 class Henry(Unit): - __unit_name__ = 'henry' - __unit_suffix__ = 'H' - __quantity__ = 'inductance' - __si_unit__ = 'kg*m^2*s^-2*A^-2' - __default_unit__ = True + _unit_name_ = 'henry' + _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' - __unit_suffix__ = '°C' - __quantity__ = 'temperature relative to 273.15 K' - __si_unit__ = 'K' + _unit_name_ = 'degree celcuis' + _unit_suffix_ = '°C' + _quantity_ = 'temperature relative to 273.15 K' + _si_unit_ = 'K' class Lumen(Unit): - __unit_name__ = 'lumen' - __unit_suffix__ = 'lm' - __quantity__ = 'luminous flux' - __si_unit__ = 'cd' + _unit_name_ = 'lumen' + _unit_suffix_ = 'lm' + _quantity_ = 'luminous flux' + _si_unit_ = 'cd' # cd*sr class Lux(Unit): - __unit_name__ = 'lux' - __unit_suffix__ = 'lx' - __quantity__ = 'illuminance' - __si_unit__ = 'm^-2*cd' - __default_unit__ = True + _unit_name_ = 'lux' + _unit_suffix_ = 'lx' + _quantity_ = 'illuminance' + _si_unit_ = 'm^-2*cd' + _default_unit_ = True # lm/m2 class Becquerel(Unit): - __unit_name__ = 'becquerel' - __unit_suffix__ = 'Bq' - __quantity__ = 'radioactivity (decays per unit time)' - __si_unit__ = 's^-1' # same as Hertz + _unit_name_ = 'becquerel' + _unit_suffix_ = 'Bq' + _quantity_ = 'radioactivity (decays per unit time)' + _si_unit_ = 's^-1' # same as Hertz class Gray(Unit): - __unit_name__ = 'gray' - __unit_suffix__ = 'Gy' - __quantity__ = 'absorbed dose (of ionizing radiation)' - __si_unit__ = 'm^2*s^-2' + _unit_name_ = 'gray' + _unit_suffix_ = 'Gy' + _quantity_ = 'absorbed dose (of ionizing radiation)' + _si_unit_ = 'm^2*s^-2' # J/kg class Sievert(Unit): - __unit_name__ = 'sievert' - __unit_suffix__ = 'Sv' - __quantity__ = ' equivalent dose (of ionizing radiation)' - __si_unit__ = 'm^2*s^-2' + _unit_name_ = 'sievert' + _unit_suffix_ = 'Sv' + _quantity_ = ' equivalent dose (of ionizing radiation)' + _si_unit_ = 'm^2*s^-2' class Katal(Unit): - __unit_name__ = 'katal' - __unit_suffix__ = 'kat' - __quantity__ = 'catalytic activity' - __si_unit__ = 'mol*s^-1' - __default_unit__ = True + _unit_name_ = 'katal' + _unit_suffix_ = 'kat' + _quantity_ = 'catalytic activity' + _si_unit_ = 'mol*s^-1' + _default_unit_ = True #################################################################################################### diff --git a/PySpice/Unit/Unit.py b/PySpice/Unit/Unit.py index 3faf912d3..58633a626 100644 --- a/PySpice/Unit/Unit.py +++ b/PySpice/Unit/Unit.py @@ -51,7 +51,7 @@ class UnitPrefixMetaclass(type): """Metaclass to register unit prefixes""" - __prefixes__ = {} # singletons + _prefixes_ = {} # singletons ############################################## @@ -67,22 +67,22 @@ def __new__(meta, class_name, base_classes, attributes): @classmethod def register_prefix(meta, cls): - power = cls.__power__ + power = cls._power_ if power is None: raise ValueError('Power is None for {}'.format(cls.__name__)) - meta.__prefixes__[power] = cls() + meta._prefixes_[power] = cls() ############################################## @classmethod def prefix_iter(cls): - return cls.__prefixes__.values() + return cls._prefixes_.values() ############################################## @classmethod def get(cls, power): - return cls.__prefixes__[power] + return cls._prefixes_[power] #################################################################################################### @@ -90,51 +90,51 @@ class UnitPrefix(metaclass=UnitPrefixMetaclass): """This class implements a unit prefix like kilo""" - __power__ = None - __prefix__ = '' + _power_ = None + _prefix_ = '' ############################################## def __repr__(self): - return '{}({}, {})'.format(self.__class__.__name__, self.__power__, self.__prefix__) + return '{}({}, {})'.format(self.__class__.__name__, self._power_, self._prefix_) ############################################## def __int__(self): - return self.__power__ + return self._power_ ############################################## def __str__(self): - return self.__prefix__ + return self._prefix_ ############################################## @property def power(self): - return self.__power__ + return self._power_ @property def prefix(self): - return self.__prefix__ + return self._prefix_ @property def is_unit(self): - return self.__power__ == 0 + return self._power_ == 0 @property def scale(self): - return 10**self.__power__ + return 10**self._power_ ############################################## @property def spice_prefix(self): - if hasattr(self, '__spice_prefix__'): - return self.__spice_prefix__ + if hasattr(self, '_spice_prefix_'): + return self._spice_prefix_ else: - return self.__prefix__ + return self._prefix_ ############################################## @@ -147,25 +147,25 @@ def is_defined_in_spice(self): def __eq__(self, other): - return self.__power__ == other.__power__ + return self._power_ == other._power_ ############################################## def __ne__(self, other): - return self.__power__ != other.__power__ + return self._power_ != other._power_ ############################################## def __lt__(self, other): - return self.__power__ < other.__power__ + return self._power_ < other._power_ ############################################## def __gt__(self, other): - return self.__power__ > other.__power__ + return self._power_ > other._power_ ############################################## @@ -174,14 +174,14 @@ def str(self, spice=False): if spice: return self.spice_prefix else: - return self.__prefix__ + return self._prefix_ #################################################################################################### class ZeroPower(UnitPrefix): - __power__ = 0 - __prefix__ = '' - __spice_prefix__ = '' + _power_ = 0 + _prefix_ = '' + _spice_prefix_ = '' _zero_power = UnitPrefixMetaclass.get(0) @@ -405,8 +405,8 @@ class UnitMetaclass(type): """Metaclass to register units""" - __units__ = {} - __hash_map__ = {} + _units_ = {} + _hash_map_ = {} ############################################## @@ -422,14 +422,14 @@ def __new__(meta, class_name, base_classes, attributes): @classmethod def init_unit(meta, cls): - si_unit = cls.__si_unit__ + si_unit = cls._si_unit_ if not (isinstance(si_unit, SiDerivedUnit) and si_unit): # si_unit is not defined if cls.is_base_unit(): - si_unit = SiDerivedUnit(cls.__unit_suffix__) + si_unit = SiDerivedUnit(cls._unit_suffix_) else: # str si_unit = SiDerivedUnit(si_unit) - cls.__si_unit__ = si_unit + cls._si_unit_ = si_unit ############################################## @@ -437,20 +437,20 @@ def init_unit(meta, cls): def register_unit(meta, cls): obj = cls() - meta.__units__[obj.unit_suffix] = obj + meta._units_[obj.unit_suffix] = obj if obj.si_unit: hash_ = obj.si_unit.hash - if hash_ in meta.__hash_map__: - meta.__hash_map__[hash_].append(obj) + if hash_ in meta._hash_map_: + meta._hash_map_[hash_].append(obj) else: - meta.__hash_map__[hash_] = [obj] + meta._hash_map_[hash_] = [obj] ############################################## @classmethod def unit_iter(meta): - return meta.__units__.values() + return meta._units_.values() ############################################## @@ -462,7 +462,7 @@ def from_prefix(meta, prefix): @classmethod def from_hash(meta, hash_): - return meta.__hash_map__.get(hash_, None) + return meta._hash_map_.get(hash_, None) ############################################## @@ -475,7 +475,7 @@ def from_si_unit(meta, si_unit, unique=True): # define unit, format as V^2 # - complex unit - units = meta.__hash_map__.get(si_unit.hash, None) + units = meta._hash_map_.get(si_unit.hash, None) if unique and units is not None: if len(units) > 1: units = [unit for unit in units if unit.is_default_unit()] @@ -500,11 +500,11 @@ class Unit(metaclass=UnitMetaclass): """This class implements a unit. """ - __unit_name__ = '' - __unit_suffix__ = '' - __quantity__ = '' - __si_unit__ = SiDerivedUnit() - __default_unit__ = False + _unit_name_ = '' + _unit_suffix_ = '' + _quantity_ = '' + _si_unit_ = SiDerivedUnit() + _default_unit_ = False # __spice_suffix__ = '' _logger = _module_logger.getChild('Unit') @@ -513,12 +513,12 @@ 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__ + 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__ + self._si_unit = self._si_unit_ else: self._si_unit = si_unit @@ -556,7 +556,7 @@ def is_unit_less(self): @classmethod def is_default_unit(cls): - return cls.__default_unit__ + return cls._default_unit_ @classmethod def is_base_unit(cls): @@ -713,11 +713,11 @@ class PrefixedUnit: """This class implements a prefixed unit. """ - __unit_map__ = {} # Prefixed unit singletons - __prefixed_unit_map__ = {} + _unit_map_ = {} # Prefixed unit singletons + _prefixed_unit_map_ = {} - __value_ctor__ = None - __values_ctor__ = None + _value_ctor_ = None + _values_ctor_ = None ############################################## @@ -728,22 +728,22 @@ def register(cls, prefixed_unit): if unit_prefix.is_unit and unit.is_default_unit(): key = unit.si_unit.hash # print('Register', key, prefixed_unit) - cls.__unit_map__[key] = prefixed_unit + cls._unit_map_[key] = prefixed_unit if unit.unit_suffix: unit_key = str(unit) else: unit_key = '_' power_key = unit_prefix.power # print('Register', unit_key, power_key, prefixed_unit) - if unit_key not in cls.__prefixed_unit_map__: - cls.__prefixed_unit_map__[unit_key] = {} - cls.__prefixed_unit_map__[unit_key][power_key] = prefixed_unit + if unit_key not in cls._prefixed_unit_map_: + cls._prefixed_unit_map_[unit_key] = {} + cls._prefixed_unit_map_[unit_key][power_key] = prefixed_unit ############################################## @classmethod def from_si_unit(cls, si_unit): - return cls.__unit_map__.get(si_unit.hash, None) + return cls._unit_map_.get(si_unit.hash, None) ############################################## @@ -757,7 +757,7 @@ def from_prefixed_unit(cls, unit, power=0): return _simple_prefixed_unit unit_key = '_' try: - return cls.__prefixed_unit_map__[unit_key][power] + return cls._prefixed_unit_map_[unit_key][power] except KeyError: return None @@ -775,12 +775,12 @@ def __init__(self, unit=None, power=None, value_ctor=None, values_ctor=None): self._power = power if value_ctor is None: - self._value_ctor = self.__value_ctor__ + self._value_ctor = self._value_ctor_ else: self._value_ctor = value_ctor if values_ctor is None: - self._values_ctor = self.__values_ctor__ + self._values_ctor = self._values_ctor_ else: self._values_ctor = values_ctor @@ -2004,7 +2004,7 @@ def convert_to_power(self, power=0): #################################################################################################### # Reset -PrefixedUnit.__value_ctor__ = UnitValue +PrefixedUnit._value_ctor_ = UnitValue _simple_prefixed_unit = PrefixedUnit() diff --git a/PySpice/Unit/__init__.py b/PySpice/Unit/__init__.py index 48596d7c7..615d44d15 100644 --- a/PySpice/Unit/__init__.py +++ b/PySpice/Unit/__init__.py @@ -205,7 +205,7 @@ def _build_as_unit_shortcut(unit): define_shortcut(name, shortcut) def _exec_body(ns, unit_prefix): - ns['__power__'] = unit_prefix + ns['_power_'] = unit_prefix def _build_unit_prefix_shortcut(unit, unit_prefix): name = 'u_' + str(unit_prefix) + unit.unit_suffix diff --git a/unit-test/Unit/test_Units.py b/unit-test/Unit/test_Units.py index 5cf1c5897..ceb6c804a 100644 --- a/unit-test/Unit/test_Units.py +++ b/unit-test/Unit/test_Units.py @@ -187,7 +187,7 @@ def test_canonisation(self): # @unittest.skip('') def test_unit_conversion(self): - # for units in UnitMetaclass.__hash_map__.values(): + # for units in UnitMetaclass._hash_map_.values(): # print(units, [x for x in units if x.is_default_unit()]) self.assertEqual(u_V(10) / u_A(2), u_Ω(5)) From ba39b09c421968c3e48c71cfe9697f10dcf98a7f Mon Sep 17 00:00:00 2001 From: "Jose M. Gomez" Date: Sun, 2 Dec 2018 18:06:55 +0100 Subject: [PATCH 010/134] Update of the todo tests. --- unit-test-todo/test_file.py | 8 ++++---- unit-test-todo/test_netlist.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/unit-test-todo/test_file.py b/unit-test-todo/test_file.py index e301e0278..fcdd4a178 100644 --- a/unit-test-todo/test_file.py +++ b/unit-test-todo/test_file.py @@ -1,7 +1,7 @@ -from PySpice.SpiceLibrary import SpiceLibrary +from PySpice.Spice.Library import SpiceLibrary -spice_library = SpiceLibrary('/home/gv/sys/fc14/fabrice/electronic-design-pattern/spice/libraries') +spice_library = SpiceLibrary('../examples/libraries') # print list(spice_library.iter_on_subcircuits()) # print list(spice_library.iter_on_models()) -print(list(spice_library.subcircuits.keys())) -print(list(spice_library.models.keys())) +print(list(spice_library.subcircuits)) +print(list(spice_library.models)) diff --git a/unit-test-todo/test_netlist.py b/unit-test-todo/test_netlist.py index 5e89def23..0619a11d0 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) From 7cd2821491dc5912bcee37c5c7b5eb5c8d5d3632 Mon Sep 17 00:00:00 2001 From: "Jose M. Gomez" Date: Sun, 2 Dec 2018 18:07:25 +0100 Subject: [PATCH 011/134] Renamed _nodes_ to avoid using standard Python names. --- unit-test/Spice/test_Netlist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit-test/Spice/test_Netlist.py b/unit-test/Spice/test_Netlist.py index f2f2ad376..84e8a99af 100644 --- a/unit-test/Spice/test_Netlist.py +++ b/unit-test/Spice/test_Netlist.py @@ -34,7 +34,7 @@ class VoltageDivider(SubCircuitFactory): __name__ = 'VoltageDivider' - __nodes__ = ('input', 'output_plus', 'output_minus') + _nodes_ = ('input', 'output_plus', 'output_minus') def __init__(self): super().__init__() self.R(1, 'input', 'output_plus', 9@u_kΩ) From 00f724d341e864a14a3d854ab764b2e72cbc7484 Mon Sep 17 00:00:00 2001 From: "Jose M. Gomez" Date: Sun, 2 Dec 2018 18:08:45 +0100 Subject: [PATCH 012/134] Renamed _nodes_, _pins_ and schematic --- PySpice/Spice/BasicElement.py | 8 ++++---- PySpice/Spice/Netlist.py | 32 +++++++++++++++++++------------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/PySpice/Spice/BasicElement.py b/PySpice/Spice/BasicElement.py index f1204562d..1c58c514a 100644 --- a/PySpice/Spice/BasicElement.py +++ b/PySpice/Spice/BasicElement.py @@ -158,7 +158,7 @@ class SubCircuitElement(NPinElement): def __init__(self, netlist, name, subcircuit_name, *nodes, **kwargs): - schematic_kwargs = kwargs.pop('schematic_kwargs', {}) + schematic_kwargs = kwargs.pop('schematic', {}) # Fixme: match parameters to subcircuit self.parameters = kwargs @@ -171,11 +171,11 @@ def __init__(self, netlist, name, subcircuit_name, *nodes, **kwargs): subcircuit = netlist._subcircuits.get(subcircuit_name) - self._pins = [Pin(self, PinDefinition(position, name=subcircuit.__pins__[position]), netlist.get_node(node, True)) + self._pins = [Pin(self, PinDefinition(position, name=subcircuit._pins_[position]), netlist.get_node(node, True)) for position, node in enumerate(nodes)] - super().__init__(netlist, name, nodes, subcircuit_name, - schematic_kwargs=schematic_kwargs) + super().__init__(netlist, name, subcircuit_name, + schematic=schematic_kwargs) ############################################## diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index 15da4c3c1..0b5d4a34a 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -269,6 +269,11 @@ def name(self): #################################################################################################### + +def schematic(**kwargs): + return kwargs + + class Pin(PinDefinition): """This class implements a pin of an element. It stores a reference to the element, the name of the @@ -501,6 +506,7 @@ def spice_to_parameters(self): #################################################################################################### + class Element(metaclass=ElementParameterMetaClass): """This class implements a base class for an element. @@ -544,10 +550,10 @@ def __init__(self, netlist, name, *args, **kwargs): elif key in self._positional_parameters_ or key in self._optional_parameters_: setattr(self, key, value) - schematic_kwargs = kwargs.pop('schematic_kwargs', {}) - self.schematic_kwargs = schematic_kwargs + schematic = kwargs.pop('schematic', {}) + self._schematic = schematic - netlist._add_element(self, **schematic_kwargs) + netlist._add_element(self, **schematic) ############################################## @@ -1125,7 +1131,7 @@ class SubCircuit(Netlist): ############################################## def __init__(self, name, *nodes, **kwargs): - self._include = None + self._included = None nodes_set = set(nodes) if len(nodes_set) != len(nodes): @@ -1135,7 +1141,7 @@ def __init__(self, name, *nodes, **kwargs): self._name = str(name) self._external_nodes = tuple([Node(self, str(node)) for node in nodes]) - self.__pins__ = nodes + self._pins_ = nodes # Fixme: ok ? self._ground = kwargs.get('ground', 0) if 'ground' in kwargs: @@ -1173,14 +1179,14 @@ def parameters(self): return self._parameters @property - def include(self): + def included(self): """Include file""" - return self._include + return self._included @property def is_included(self): """is_included""" - return self._include is None + return self._included is None ############################################## @@ -1223,14 +1229,14 @@ def __str__(self): class SubCircuitFactory(SubCircuit): __name__ = None - __nodes__ = None - __pins__ = None + _nodes_ = None + _pins_ = None ############################################## def __init__(self, **kwargs): - super().__init__(self.__name__, *self.__nodes__, **kwargs) + super().__init__(self.__name__, *self._nodes_, **kwargs) #################################################################################################### @@ -1300,11 +1306,11 @@ def include(self, path): for subcircuit in subcircuits: subcircuit_def = subcircuit.build() self.subcircuit(subcircuit_def) - self._subcircuits[subcircuit._name]._include = path + self._subcircuits[subcircuit._name]._included = path models = parser.models for model in models: self.model(model._name, model._model_type, **model._parameters) - self._models[model._name]._include = path + self._models[model._name]._included = path else: self._logger.warn("Duplicated include") From 6029992573212096a782b93a33ded4baae0474b3 Mon Sep 17 00:00:00 2001 From: "Jose M. Gomez" Date: Tue, 4 Dec 2018 06:33:27 +0100 Subject: [PATCH 013/134] Adding the schematic elements and convert to lower the model names. --- PySpice/Spice/BasicElement.py | 60 +++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/PySpice/Spice/BasicElement.py b/PySpice/Spice/BasicElement.py index 1c58c514a..dfc335091 100644 --- a/PySpice/Spice/BasicElement.py +++ b/PySpice/Spice/BasicElement.py @@ -324,8 +324,10 @@ class SemiconductorResistor(DipoleElement): __alias__ = 'SemiconductorResistor' _prefix_ = 'R' + schematic = schem.elements.RES + resistance = FloatPositionalParameter(position=0, key_parameter=False, unit=U_Ω) - model = ModelPositionalParameter(position=1, key_parameter=True) + model = ModelPositionalParameter(position=1, key_parameter=True).lower() length = FloatKeyParameter('l', unit=U_m) width = FloatKeyParameter('w', unit=U_m) temperature = FloatKeyParameter('temp', unit=U_Degree) @@ -369,6 +371,8 @@ class BehavioralResistor(DipoleElement): __alias__ = 'BehavioralResistor' _prefix_ = 'R' + schematic = schem.elements.RES + resistance_expression = ExpressionPositionalParameter(position=0, key_parameter=False) tc1 = FloatKeyParameter('tc1') tc2 = FloatKeyParameter('tc2') @@ -424,8 +428,10 @@ class Capacitor(DipoleElement): __alias__ = 'C' _prefix_ = 'C' + schematic = schem.elements.CAP + capacitance = FloatPositionalParameter(position=0, key_parameter=False, unit=U_F) - model = ModelPositionalParameter(position=1, key_parameter=True) + model = ModelPositionalParameter(position=1, key_parameter=True).lower() multiplier = IntKeyParameter('m') scale = FloatKeyParameter('scale') temperature = FloatKeyParameter('temp', unit=U_Degree) @@ -493,8 +499,10 @@ class SemiconductorCapacitor(DipoleElement): __alias__ = 'SemiconductorCapacitor' _prefix_ = 'C' + schematic = schem.elements.CAP + capacitance = FloatPositionalParameter(position=0, key_parameter=False, unit=U_F) - model = ModelPositionalParameter(position=1, key_parameter=True) + model = ModelPositionalParameter(position=1, key_parameter=True).lower() length = FloatKeyParameter('l', unit=U_m) width = FloatKeyParameter('w', unit=U_m) multiplier = IntKeyParameter('m') @@ -535,6 +543,8 @@ class BehavioralCapacitor(DipoleElement): __alias__ = 'BehavioralCapacitor' _prefix_ = 'C' + schematic = schem.elements.CAP + capacitance_expression = ExpressionPositionalParameter(position=0, key_parameter=False) tc1 = FloatKeyParameter('tc1') tc2 = FloatKeyParameter('tc2') @@ -592,8 +602,10 @@ class Inductor(DipoleElement): __alias__ = 'L' _prefix_ = 'L' + schematic = schem.elements.INDUCTOR2 + inductance = FloatPositionalParameter(position=0, key_parameter=False, unit=U_H) - model = ModelPositionalParameter(position=1, key_parameter=True) + model = ModelPositionalParameter(position=1, key_parameter=True).lower() nt = FloatKeyParameter('nt') multiplier = IntKeyParameter('m') scale = FloatKeyParameter('scale') @@ -633,6 +645,8 @@ class BehavioralInductor(DipoleElement): __alias__ = 'BehavioralInductor' _prefix_ = 'L' + schematic = schem.elements.INDUCTOR2 + inductance_expression = ExpressionPositionalParameter(position=0, key_parameter=False) tc1 = FloatKeyParameter('tc1') tc2 = FloatKeyParameter('tc2') @@ -664,6 +678,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) @@ -706,7 +724,7 @@ class VoltageControlledSwitch(TwoPortElement): __long_alias__ = 'VCS' _prefix_ = 'S' - model = ModelPositionalParameter(position=0, key_parameter=True) + model = ModelPositionalParameter(position=0, key_parameter=True).lower() initial_state = InitialStatePositionalParameter(position=1, key_parameter=True) #################################################################################################### @@ -744,7 +762,7 @@ class CurrentControlledSwitch(DipoleElement): _prefix_ = 'W' source = ElementNamePositionalParameter(position=0, key_parameter=True) - model = ModelPositionalParameter(position=1, key_parameter=True) + model = ModelPositionalParameter(position=1, key_parameter=True).lower() initial_state = InitialStatePositionalParameter(position=2, key_parameter=True) #################################################################################################### @@ -802,6 +820,8 @@ class CurrentSource(DipoleElement): __alias__ = 'I' _prefix_ = 'I' + schematic = schem.elements.SOURCE_I + # Fixme: ngspice manual doesn't describe well the syntax dc_value = FloatPositionalParameter(position=0, key_parameter=False, unit=U_A) @@ -974,6 +994,8 @@ class BehavioralSource(DipoleElement): __alias__ = 'B' _prefix_ = 'B' + schematic = schem.elements.SOURCE + current_expression = ExpressionKeyParameter('i') voltage_expression = ExpressionKeyParameter('v') tc1 = FloatKeyParameter('tc1') @@ -1008,6 +1030,8 @@ class NonLinearVoltageSource(DipoleElement): __alias__ = 'NonLinearVoltageSource' _prefix_ = 'E' + schematic = schem.elements.SOURCE_V + ############################################## def __init__(self, name, *args, **kwargs): @@ -1057,6 +1081,8 @@ class NonLinearCurrentSource(DipoleElement): __alias__ = 'NonLinearCurrentSource' _prefix_ = 'G' + schematic = schem.elements.SOURCE_I + transconductance = ExpressionPositionalParameter(position=0, key_parameter=False) #################################################################################################### @@ -1120,7 +1146,9 @@ class Diode(FixedPinElement): _prefix_ = 'D' _pins_ = (('cathode', 'plus'), ('anode', 'minus')) - model = ModelPositionalParameter(position=0, key_parameter=True) + schematic = schem.elements.DIODE_F + + model = ModelPositionalParameter(position=0, key_parameter=True).lower() area = FloatKeyParameter('area') multiplier = IntKeyParameter('m') pj = FloatKeyParameter('pj') @@ -1197,7 +1225,7 @@ class BipolarJunctionTransistor(FixedPinElement): _prefix_ = 'Q' _pins_ = ('collector', 'base', 'emitter', OptionalPin('substrate')) - model = ModelPositionalParameter(position=0, key_parameter=True) + model = ModelPositionalParameter(position=0, key_parameter=True).lower() area = FloatKeyParameter('area') areac = FloatKeyParameter('areac') areab = FloatKeyParameter('areab') @@ -1259,7 +1287,7 @@ class JunctionFieldEffectTransistor(JfetElement): __long_alias__ = 'JFET' _prefix_ = 'J' - model = ModelPositionalParameter(position=0, key_parameter=True) + model = ModelPositionalParameter(position=0, key_parameter=True).lower() area = FloatKeyParameter('area') multiplier = IntKeyParameter('m') off = FlagParameter('off') @@ -1310,7 +1338,7 @@ class Mesfet(JfetElement): __long_alias__ = 'MESFET' _prefix_ = 'Z' - model = ModelPositionalParameter(position=0, key_parameter=True) + model = ModelPositionalParameter(position=0, key_parameter=True).lower() area = FloatKeyParameter('area') multiplier = IntKeyParameter('m') off = FlagParameter('off') @@ -1409,7 +1437,7 @@ class Mosfet(FixedPinElement): _prefix_ = 'M' _pins_ = ('drain', 'gate', 'source', ('bulk', 'substrate')) - model = ModelPositionalParameter(position=0, key_parameter=True) + model = ModelPositionalParameter(position=0, key_parameter=True).lower() multiplier = IntKeyParameter('m') length = FloatKeyParameter('l', unit=U_m) width = FloatKeyParameter('w', unit=U_m) @@ -1511,7 +1539,7 @@ class LossyTransmission(TwoPortElement): __alias__ = 'O' _prefix_ = 'O' - model = ModelPositionalParameter(position=0, key_parameter=True) + model = ModelPositionalParameter(position=0, key_parameter=True).lower() #################################################################################################### @@ -1540,7 +1568,7 @@ class CoupledMulticonductorLine(NPinElement): __alias__ = 'P' _prefix_ = 'P' - model = ModelPositionalParameter(position=0, key_parameter=True) + model = ModelPositionalParameter(position=0, key_parameter=True).lower() length = FloatKeyParameter('len', unit=U_m) ############################################## @@ -1580,7 +1608,7 @@ class UniformDistributedRCLine(FixedPinElement): _prefix_ = 'U' _pins_ = ('output', 'input', 'capacitance_node') - model = ModelPositionalParameter(position=0, key_parameter=True) + model = ModelPositionalParameter(position=0, key_parameter=True).lower() length = FloatKeyParameter('l', unit=U_m) number_of_lumps = IntKeyParameter('n') @@ -1613,7 +1641,7 @@ class SingleLossyTransmissionLine(TwoPortElement): __alias__ = 'Y' _prefix_ = 'Y' - model = ModelPositionalParameter(position=0, key_parameter=True) + model = ModelPositionalParameter(position=0, key_parameter=True).lower() length = FloatKeyParameter('len', unit=U_m) #################################################################################################### @@ -1655,7 +1683,7 @@ class XSpiceElement(NPinElement): __alias__ = 'A' _prefix_ = 'A' - model = ModelPositionalParameter(position=0, key_parameter=True) + model = ModelPositionalParameter(position=0, key_parameter=True).lower() ############################################## From e3982e90695cfed72e276cd3fd3dcec48ab02fd1 Mon Sep 17 00:00:00 2001 From: "Jose M. Gomez" Date: Tue, 4 Dec 2018 06:33:57 +0100 Subject: [PATCH 014/134] Use lower case names for models and subcircuits. --- PySpice/Spice/Library.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/PySpice/Spice/Library.py b/PySpice/Spice/Library.py index 82bca051e..b71e280d3 100644 --- a/PySpice/Spice/Library.py +++ b/PySpice/Spice/Library.py @@ -74,14 +74,12 @@ def __init__(self, root_path): if extension in self.EXTENSIONS: self._logger.debug("Parse {}".format(path)) spice_parser = SpiceParser(path) - 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 + for subcircuit in spice_parser.subcircuits: + name = self._suffix_name(subcircuit.name, extension) + self._subcircuits[name.lower()] = path + for model in spice_parser.models: + name = self._suffix_name(model.name, extension) + self._models[name.lower()] = path ############################################## @@ -96,7 +94,7 @@ def _suffix_name(name, extension): ############################################## def __getitem__(self, name): - + name = name.lower() if name in self._subcircuits: return self._subcircuits[name] elif name in self._models: From 631b22f0dbc40d927c8ca677e9a6b401723cf5c5 Mon Sep 17 00:00:00 2001 From: "Jose M. Gomez" Date: Tue, 4 Dec 2018 06:34:39 +0100 Subject: [PATCH 015/134] Modify include to take into account all the subcircuits and models inside a file. --- PySpice/Spice/Netlist.py | 42 ++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index 0b5d4a34a..62e837fa1 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -861,6 +861,7 @@ def __init__(self): self._subcircuits = OrderedDict() # to keep the declaration order self._elements = OrderedDict() # to keep the declaration order self._models = {} + self._includes = [] # .include self.raw_spice = '' @@ -1122,6 +1123,26 @@ def _str_raw_spice(self): netlist += os.linesep return netlist + def include(self, path): + from .Parser import SpiceParser + + """Include a file.""" + + if path not in self._includes: + self._includes.append(path) + parser = SpiceParser(path=path) + subcircuits = parser.subcircuits + for subcircuit in subcircuits: + subcircuit_def = subcircuit.build() + self.subcircuit(subcircuit_def) + self._subcircuits[subcircuit._name]._included = path + models = parser.models + for model in models: + self.model(model._name, model._model_type, **model._parameters) + self._models[model._name]._included = path + else: + self._logger.warn("Duplicated include") + #################################################################################################### class SubCircuit(Netlist): @@ -1266,7 +1287,6 @@ def __init__(self, title, self.title = str(title) self._ground = ground self._global_nodes = set(global_nodes) # .global - self._includes = [] # .include self._parameters = {} # .param # Fixme: not implemented @@ -1294,26 +1314,6 @@ def clone(self, title=None): ############################################## - def include(self, path): - from .Parser import SpiceParser - - """Include a file.""" - - if path not in self._includes: - self._includes.append(path) - parser = SpiceParser(path=path) - subcircuits = parser.subcircuits - for subcircuit in subcircuits: - subcircuit_def = subcircuit.build() - self.subcircuit(subcircuit_def) - self._subcircuits[subcircuit._name]._included = path - models = parser.models - for model in models: - self.model(model._name, model._model_type, **model._parameters) - self._models[model._name]._included = path - else: - self._logger.warn("Duplicated include") - ############################################## def parameter(self, name, expression): From 1e2806a473b187415a0226d5c4fecd875ed65380 Mon Sep 17 00:00:00 2001 From: "Jose M. Gomez" Date: Tue, 4 Dec 2018 06:35:00 +0100 Subject: [PATCH 016/134] Added a test for a transformer. --- unit-test/Spice/test_Netlist.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/unit-test/Spice/test_Netlist.py b/unit-test/Spice/test_Netlist.py index 84e8a99af..27f7daeaf 100644 --- a/unit-test/Spice/test_Netlist.py +++ b/unit-test/Spice/test_Netlist.py @@ -178,6 +178,17 @@ def test_keyword_clash(self): self.assertEqual(model['is'], 1) self.assertEqual(str(model), '.model Diode D (is=1 rs=2)') + def test_transformer(self): + import os + from PySpice.Spice.Netlist import Circuit + circuit = Circuit('Diode Characteristic Curve') + circuit.L('primary', 'Vlp', 'Vdrain', '{l_trf}') + circuit.C('resonance', 'Vlv', 'Vdrain', '{cap_r}') + circuit.L('secondary', 'Vls', 'ghv', '{Ls}') + circuit.R('secondary', 'Vls', 1, 5.15) + circuit.K('flyback', 'Lprimary', 'Lsecondary', 1) + + #################################################################################################### if __name__ == '__main__': From 51d5a39288c519bf8a644b61d8b461d502b7b197 Mon Sep 17 00:00:00 2001 From: "Jose M. Gomez" Date: Tue, 4 Dec 2018 06:37:00 +0100 Subject: [PATCH 017/134] Convert subcircuit_name to lower. --- PySpice/Spice/BasicElement.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PySpice/Spice/BasicElement.py b/PySpice/Spice/BasicElement.py index dfc335091..cd877e4c6 100644 --- a/PySpice/Spice/BasicElement.py +++ b/PySpice/Spice/BasicElement.py @@ -152,7 +152,7 @@ class SubCircuitElement(NPinElement): __alias__ = 'X' _prefix_ = 'X' - subcircuit_name = ElementNamePositionalParameter(position=0, key_parameter=False) + subcircuit_name = ElementNamePositionalParameter(position=0, key_parameter=False).lower() ############################################## From 216be84b9575ea8739443b9654f664a1f9dd8a27 Mon Sep 17 00:00:00 2001 From: "Jose M. Gomez" Date: Tue, 4 Dec 2018 07:26:12 +0100 Subject: [PATCH 018/134] Corrected the lower position. --- PySpice/Spice/BasicElement.py | 34 +++++++++++++++++----------------- PySpice/Tools/StringTools.py | 2 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/PySpice/Spice/BasicElement.py b/PySpice/Spice/BasicElement.py index cd877e4c6..e94d8ae8c 100644 --- a/PySpice/Spice/BasicElement.py +++ b/PySpice/Spice/BasicElement.py @@ -152,7 +152,7 @@ class SubCircuitElement(NPinElement): __alias__ = 'X' _prefix_ = 'X' - subcircuit_name = ElementNamePositionalParameter(position=0, key_parameter=False).lower() + subcircuit_name = ElementNamePositionalParameter(position=0, key_parameter=False) ############################################## @@ -327,7 +327,7 @@ class SemiconductorResistor(DipoleElement): schematic = schem.elements.RES resistance = FloatPositionalParameter(position=0, key_parameter=False, unit=U_Ω) - model = ModelPositionalParameter(position=1, key_parameter=True).lower() + model = ModelPositionalParameter(position=1, key_parameter=True) length = FloatKeyParameter('l', unit=U_m) width = FloatKeyParameter('w', unit=U_m) temperature = FloatKeyParameter('temp', unit=U_Degree) @@ -431,7 +431,7 @@ class Capacitor(DipoleElement): schematic = schem.elements.CAP capacitance = FloatPositionalParameter(position=0, key_parameter=False, unit=U_F) - model = ModelPositionalParameter(position=1, key_parameter=True).lower() + model = ModelPositionalParameter(position=1, key_parameter=True) multiplier = IntKeyParameter('m') scale = FloatKeyParameter('scale') temperature = FloatKeyParameter('temp', unit=U_Degree) @@ -502,7 +502,7 @@ class SemiconductorCapacitor(DipoleElement): schematic = schem.elements.CAP capacitance = FloatPositionalParameter(position=0, key_parameter=False, unit=U_F) - model = ModelPositionalParameter(position=1, key_parameter=True).lower() + model = ModelPositionalParameter(position=1, key_parameter=True) length = FloatKeyParameter('l', unit=U_m) width = FloatKeyParameter('w', unit=U_m) multiplier = IntKeyParameter('m') @@ -605,7 +605,7 @@ class Inductor(DipoleElement): schematic = schem.elements.INDUCTOR2 inductance = FloatPositionalParameter(position=0, key_parameter=False, unit=U_H) - model = ModelPositionalParameter(position=1, key_parameter=True).lower() + model = ModelPositionalParameter(position=1, key_parameter=True) nt = FloatKeyParameter('nt') multiplier = IntKeyParameter('m') scale = FloatKeyParameter('scale') @@ -724,7 +724,7 @@ class VoltageControlledSwitch(TwoPortElement): __long_alias__ = 'VCS' _prefix_ = 'S' - model = ModelPositionalParameter(position=0, key_parameter=True).lower() + model = ModelPositionalParameter(position=0, key_parameter=True) initial_state = InitialStatePositionalParameter(position=1, key_parameter=True) #################################################################################################### @@ -762,7 +762,7 @@ class CurrentControlledSwitch(DipoleElement): _prefix_ = 'W' source = ElementNamePositionalParameter(position=0, key_parameter=True) - model = ModelPositionalParameter(position=1, key_parameter=True).lower() + model = ModelPositionalParameter(position=1, key_parameter=True) initial_state = InitialStatePositionalParameter(position=2, key_parameter=True) #################################################################################################### @@ -1148,7 +1148,7 @@ class Diode(FixedPinElement): schematic = schem.elements.DIODE_F - model = ModelPositionalParameter(position=0, key_parameter=True).lower() + model = ModelPositionalParameter(position=0, key_parameter=True) area = FloatKeyParameter('area') multiplier = IntKeyParameter('m') pj = FloatKeyParameter('pj') @@ -1225,7 +1225,7 @@ class BipolarJunctionTransistor(FixedPinElement): _prefix_ = 'Q' _pins_ = ('collector', 'base', 'emitter', OptionalPin('substrate')) - model = ModelPositionalParameter(position=0, key_parameter=True).lower() + model = ModelPositionalParameter(position=0, key_parameter=True) area = FloatKeyParameter('area') areac = FloatKeyParameter('areac') areab = FloatKeyParameter('areab') @@ -1287,7 +1287,7 @@ class JunctionFieldEffectTransistor(JfetElement): __long_alias__ = 'JFET' _prefix_ = 'J' - model = ModelPositionalParameter(position=0, key_parameter=True).lower() + model = ModelPositionalParameter(position=0, key_parameter=True) area = FloatKeyParameter('area') multiplier = IntKeyParameter('m') off = FlagParameter('off') @@ -1338,7 +1338,7 @@ class Mesfet(JfetElement): __long_alias__ = 'MESFET' _prefix_ = 'Z' - model = ModelPositionalParameter(position=0, key_parameter=True).lower() + model = ModelPositionalParameter(position=0, key_parameter=True) area = FloatKeyParameter('area') multiplier = IntKeyParameter('m') off = FlagParameter('off') @@ -1437,7 +1437,7 @@ class Mosfet(FixedPinElement): _prefix_ = 'M' _pins_ = ('drain', 'gate', 'source', ('bulk', 'substrate')) - model = ModelPositionalParameter(position=0, key_parameter=True).lower() + model = ModelPositionalParameter(position=0, key_parameter=True) multiplier = IntKeyParameter('m') length = FloatKeyParameter('l', unit=U_m) width = FloatKeyParameter('w', unit=U_m) @@ -1539,7 +1539,7 @@ class LossyTransmission(TwoPortElement): __alias__ = 'O' _prefix_ = 'O' - model = ModelPositionalParameter(position=0, key_parameter=True).lower() + model = ModelPositionalParameter(position=0, key_parameter=True) #################################################################################################### @@ -1568,7 +1568,7 @@ class CoupledMulticonductorLine(NPinElement): __alias__ = 'P' _prefix_ = 'P' - model = ModelPositionalParameter(position=0, key_parameter=True).lower() + model = ModelPositionalParameter(position=0, key_parameter=True) length = FloatKeyParameter('len', unit=U_m) ############################################## @@ -1608,7 +1608,7 @@ class UniformDistributedRCLine(FixedPinElement): _prefix_ = 'U' _pins_ = ('output', 'input', 'capacitance_node') - model = ModelPositionalParameter(position=0, key_parameter=True).lower() + model = ModelPositionalParameter(position=0, key_parameter=True) length = FloatKeyParameter('l', unit=U_m) number_of_lumps = IntKeyParameter('n') @@ -1641,7 +1641,7 @@ class SingleLossyTransmissionLine(TwoPortElement): __alias__ = 'Y' _prefix_ = 'Y' - model = ModelPositionalParameter(position=0, key_parameter=True).lower() + model = ModelPositionalParameter(position=0, key_parameter=True) length = FloatKeyParameter('len', unit=U_m) #################################################################################################### @@ -1683,7 +1683,7 @@ class XSpiceElement(NPinElement): __alias__ = 'A' _prefix_ = 'A' - model = ModelPositionalParameter(position=0, key_parameter=True).lower() + model = ModelPositionalParameter(position=0, key_parameter=True) ############################################## diff --git a/PySpice/Tools/StringTools.py b/PySpice/Tools/StringTools.py index ac11fea63..eeb76b989 100644 --- a/PySpice/Tools/StringTools.py +++ b/PySpice/Tools/StringTools.py @@ -38,7 +38,7 @@ def str_spice(obj, unit=True): else: # Fixme: ok ??? return obj.str(spice=False, space=False, unit=False) else: - return str(obj) + return str(obj).lower() #################################################################################################### From e0af0a3f94319a19fc2917b1109996eefe08fcbd Mon Sep 17 00:00:00 2001 From: "Jose M. Gomez" Date: Tue, 4 Dec 2018 07:26:37 +0100 Subject: [PATCH 019/134] Added the used elements to Netlist. --- PySpice/Spice/Netlist.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index 62e837fa1..3222fbb5c 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -862,6 +862,8 @@ def __init__(self): self._elements = OrderedDict() # to keep the declaration order self._models = {} self._includes = [] # .include + self._used_models = set() + self._used_subcircuits = set() self.raw_spice = '' @@ -1007,7 +1009,14 @@ def _add_element(self, element, **schematic_kwargs): """Add an element.""" if element.name not in self._elements: self._elements[element.name] = element - + if hasattr(element, 'model'): + model = element.model + self._used_models.add(model) + + if hasattr(element, 'subcircuit_name'): + subcircuit_name = element.subcircuit_name + self._used_subcircuits.add(subcircuit_name) + if len(element.nodes) == 2: self.graph.add_edge(element.nodes[0], element.nodes[1], x=element, name=element.name) From 49231aa8a1c4a08ea898abaa1c687ce526bfd65a Mon Sep 17 00:00:00 2001 From: "Jose M. Gomez" Date: Wed, 5 Dec 2018 07:07:37 +0100 Subject: [PATCH 020/134] Change all the names to lower. --- PySpice/Spice/BasicElement.py | 3 ++- PySpice/Spice/Netlist.py | 31 ++++++++++++++++++------------- PySpice/Spice/Parser.py | 2 ++ 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/PySpice/Spice/BasicElement.py b/PySpice/Spice/BasicElement.py index e94d8ae8c..830a965df 100644 --- a/PySpice/Spice/BasicElement.py +++ b/PySpice/Spice/BasicElement.py @@ -168,7 +168,8 @@ def __init__(self, netlist, name, subcircuit_name, *nodes, **kwargs): # parameter.__set__(self, value) # self.optional_parameters[key] = parameter # setattr(self, key, parameter) - + + subcircuit_name = subcircuit_name.lower() subcircuit = netlist._subcircuits.get(subcircuit_name) self._pins = [Pin(self, PinDefinition(position, name=subcircuit._pins_[position]), netlist.get_node(node, True)) diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index 3222fbb5c..904eca7b0 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -148,7 +148,7 @@ class DeviceModel: def __init__(self, name, modele_type, **parameters): self._include = None - self._name = str(name) + self._name = str(name).lower() self._model_type = str(modele_type) self._parameters = {} @@ -1011,11 +1011,13 @@ def _add_element(self, element, **schematic_kwargs): self._elements[element.name] = element if hasattr(element, 'model'): model = element.model - self._used_models.add(model) + if model is not None: + self._used_models.add(str(model).lower()) if hasattr(element, 'subcircuit_name'): subcircuit_name = element.subcircuit_name - self._used_subcircuits.add(subcircuit_name) + if subcircuit_name is not None: + self._used_subcircuits.add(str(subcircuit_name).lower()) if len(element.nodes) == 2: self.graph.add_edge(element.nodes[0], element.nodes[1], @@ -1066,7 +1068,7 @@ def model(self, name, modele_type, **parameters): """Add a model.""" - model = DeviceModel(name, modele_type, **parameters) + model = DeviceModel(str(name).lower(), modele_type, **parameters) if model.name not in self._models: self._models[model.name] = model else: @@ -1093,8 +1095,10 @@ def __str__(self): # Fixme: order ??? netlist = self._str_raw_spice() netlist += self._str_subcircuits() # before elements - netlist += self._str_elements() + netlist += "\n" netlist += self._str_models() + netlist += "\n" + netlist += self._str_elements() return netlist @@ -1108,18 +1112,18 @@ def _str_elements(self): ############################################## 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._used_models] + return join_lines(models) + os.linesep 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._used_subcircuits] + return join_lines(subcircuits) else: return '' @@ -1169,7 +1173,7 @@ def __init__(self, name, *nodes, **kwargs): super().__init__() - self._name = str(name) + self._name = str(name).lower() self._external_nodes = tuple([Node(self, str(node)) for node in nodes]) self._pins_ = nodes # Fixme: ok ? @@ -1341,7 +1345,8 @@ def str(self, simulator=None): # raise NameError("Circuit don't have ground node") netlist = self._str_title() - netlist += self._str_includes(simulator) + netlist = "\n" + # netlist += self._str_includes(simulator) netlist += self._str_globals() netlist += self._str_parameters() netlist += super().__str__() diff --git a/PySpice/Spice/Parser.py b/PySpice/Spice/Parser.py index 70920a25d..4e43e4450 100644 --- a/PySpice/Spice/Parser.py +++ b/PySpice/Spice/Parser.py @@ -284,6 +284,7 @@ def __init__(self, line): else: self._name, self._model_type = text[:kwarg_start].split() self._parameters = Line.get_kwarg(text[kwarg_start+1:kwarg_stop]) + self._name = self._name.lower() ############################################## @@ -333,6 +334,7 @@ def __init__(self, line): # Fixme parameters, dict_parameters = self._line.split_line('.subckt') self._name, self._nodes = parameters[0], parameters[1:] + self._name = self._name.lower() self._statements = [] From fb5a95116cf6f7a81b1a661a24c9b9389e4602a7 Mon Sep 17 00:00:00 2001 From: "Jose M. Gomez" Date: Fri, 7 Dec 2018 22:18:13 +0100 Subject: [PATCH 021/134] Multiple changes and improvements, including the tests. --- PySpice/Probe/WaveForm.py | 23 +++----- PySpice/Spice/Netlist.py | 40 ++++++-------- PySpice/Tools/StringTools.py | 2 +- PySpice/Unit/Unit.py | 78 +++++++++++++++++++--------- unit-test/Math/test_Calculus.py | 28 ++++++++++ unit-test/Spice/test_BasicElement.py | 10 ++-- unit-test/Spice/test_Netlist.py | 41 ++++++++------- unit-test/Spice/test_Pickle.py | 28 ++++++++++ 8 files changed, 161 insertions(+), 89 deletions(-) create mode 100644 unit-test/Spice/test_Pickle.py diff --git a/PySpice/Probe/WaveForm.py b/PySpice/Probe/WaveForm.py index b476915ad..0291ae1df 100644 --- a/PySpice/Probe/WaveForm.py +++ b/PySpice/Probe/WaveForm.py @@ -37,7 +37,7 @@ #################################################################################################### -from PySpice.Unit.Unit import UnitValues +from PySpice.Unit.Unit import UnitValues, UnitValue #################################################################################################### @@ -65,6 +65,7 @@ class WaveForm(UnitValues): @classmethod def from_unit_values(cls, name, array, title=None, abscissa=None): + shape = array.shape obj = cls( name, array.prefixed_unit, @@ -133,7 +134,10 @@ def __array_ufunc__(self, 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 @@ -298,7 +302,7 @@ def __getitem__(self, name): try: return self._get_item(name) except IndexError: - return self._get_item(name.lower()) + return self._get_item(str(name).lower()) ############################################## @@ -307,19 +311,6 @@ def _format_dict(d): return os.linesep.join([' '*2 + str(x) for x in d]) - ############################################## - - def __getattr__(self, name): - - try: - return self.__getitem__(name) - except IndexError: - raise AttributeError(name + os.linesep + - 'Nodes :' + os.linesep + self._format_dict(self._nodes) + os.linesep + - 'Branches :' + os.linesep + self._format_dict(self._branches) + os.linesep + - 'Elements :' + os.linesep + self._format_dict(self._elements) + os.linesep + - 'Internal Parameters :' + os.linesep + self._format_dict(self._internal_parameters) - ) #################################################################################################### diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index 904eca7b0..0e1518572 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -151,11 +151,7 @@ def __init__(self, name, modele_type, **parameters): self._name = str(name).lower() self._model_type = str(modele_type) - self._parameters = {} - for key, value in parameters.items(): - if key.endswith('_'): - key = key[:-1] - self._parameters[key] = value + self._parameters = parameters.copy() ############################################## @@ -191,18 +187,12 @@ def is_included(self): ############################################## def __getitem__(self, name): - return self._parameters[name] ############################################## - def __getattr__(self, name): - - try: - return self._parameters[name] - except KeyError: - if name.endswith('_'): - return self._parameters[name[:-1]] + #def __getattr__(self, name): + # return super(DeviceModel, self).__getattr__('_parameter')[name] ############################################## @@ -947,12 +937,12 @@ def __getitem__(self, attribute_name): ############################################## - def __getattr__(self, attribute_name): + #def __getattr__(self, attribute_name): - try: - return self.__getitem__(attribute_name) - except IndexError: - raise AttributeError(attribute_name) + # try: + # return self.__getitem__(attribute_name) + # except IndexError: + # raise AttributeError(attribute_name) ############################################## @@ -1094,10 +1084,14 @@ def __str__(self): # Fixme: order ??? netlist = self._str_raw_spice() - netlist += self._str_subcircuits() # before elements - netlist += "\n" - netlist += self._str_models() - netlist += "\n" + subcircuits = self._str_subcircuits() + if subcircuits: + netlist += subcircuits# before elements + netlist += os.linesep + models = self._str_models() + if models: + netlist += models + netlist += os.linesep netlist += self._str_elements() return netlist @@ -1345,7 +1339,7 @@ def str(self, simulator=None): # raise NameError("Circuit don't have ground node") netlist = self._str_title() - netlist = "\n" + netlist += os.linesep # netlist += self._str_includes(simulator) netlist += self._str_globals() netlist += self._str_parameters() diff --git a/PySpice/Tools/StringTools.py b/PySpice/Tools/StringTools.py index eeb76b989..7920e08bb 100644 --- a/PySpice/Tools/StringTools.py +++ b/PySpice/Tools/StringTools.py @@ -64,6 +64,6 @@ def join_list(items): #################################################################################################### def join_dict(d): - return ' '.join(["{}={}".format(key, str_spice(value)) + 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/Unit.py b/PySpice/Unit/Unit.py index 58633a626..53853821a 100644 --- a/PySpice/Unit/Unit.py +++ b/PySpice/Unit/Unit.py @@ -840,7 +840,7 @@ def __eq__(self, other): """self == other""" - return self.is_same_unit(other) and self.is_same_power(other) + return (self._unit == other.unit) and (self._power == other.power) ############################################## @@ -1602,6 +1602,7 @@ class UnitValues(np.ndarray): np.logical_xor: CONVERSION.UNIT_MATCH, np.logical_not: CONVERSION.UNIT_MATCH, + np.mean: CONVERSION.UNIT_MATCH, np.maximum: CONVERSION.UNIT_MATCH, np.minimum: CONVERSION.UNIT_MATCH, np.fmax: CONVERSION.UNIT_MATCH, @@ -1612,7 +1613,7 @@ class UnitValues(np.ndarray): np.isfinite: CONVERSION.NOT_IMPLEMENTED, # ! _T np.isinf: CONVERSION.NOT_IMPLEMENTED, # ! _T np.isnan: CONVERSION.NOT_IMPLEMENTED, # ! _T - np.fabs: CONVERSION.NOT_IMPLEMENTED, # ! _ + # np.fabs: CONVERSION.NOT_IMPLEMENTED, # ! _ np.signbit: CONVERSION.NOT_IMPLEMENTED, # ! _T np.copysign: CONVERSION.NOT_IMPLEMENTED, # ! np.nextafter: CONVERSION.NOT_IMPLEMENTED, # ! @@ -1620,7 +1621,7 @@ class UnitValues(np.ndarray): np.modf: CONVERSION.NOT_IMPLEMENTED, # ! np.ldexp: CONVERSION.NOT_IMPLEMENTED, # ! np.frexp: CONVERSION.NOT_IMPLEMENTED, # ! - np.fmod: CONVERSION.NOT_IMPLEMENTED, # ! + # np.fmod: CONVERSION.NOT_IMPLEMENTED, # ! np.floor: CONVERSION.NOT_IMPLEMENTED, # ! np.ceil: CONVERSION.NO_CONVERSION, np.trunc: CONVERSION.NO_CONVERSION, @@ -1745,19 +1746,24 @@ def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): for input_ in inputs] # elif conversion in (self.CONVERSION.UNIT_MATCH, self.CONVERSION.UNIT_MATCH_NO_OUT_CAST): - # len(inputs) == 2 - other = inputs[1] - if isinstance(other, (UnitValues, UnitValue)): - self._check_unit(other) - args.append(self.as_ndarray()) - nd_other = self._convert_value(other) - if isinstance(other, UnitValues): - nd_other = nd_other.as_ndarray() - elif isinstance(other, UnitValue): - nd_other = float(nd_other) - args.append(nd_other) + if len(inputs) == 1: + prefixed_unit = self._prefixed_unit + args.append(self.as_ndarray(True)) + elif len(inputs) == 2: + other = inputs[1] + if isinstance(other, (UnitValues, UnitValue)): + self._check_unit(other) + args.append(self.as_ndarray()) + nd_other = self._convert_value(other) + if isinstance(other, UnitValues): + nd_other = nd_other.as_ndarray() + elif isinstance(other, UnitValue): + nd_other = float(nd_other) + args.append(nd_other) + else: + raise ValueError else: - raise ValueError + raise NotImplementedError # elif conversion == self.CONVERSION.NEW_UNIT: if len(inputs) == 1: @@ -1827,11 +1833,16 @@ def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): # Cast results if conversion in (self.CONVERSION.FLOAT, self.CONVERSION.UNIT_MATCH_NO_OUT_CAST): # Fixme: ok ??? - results = tuple(( result if output is None else output ) + results = tuple((result if output is None else output) for result, output in zip(results, outputs)) else: - results = tuple(( UnitValues.from_ndarray(np.asarray(result), prefixed_unit) if output is None else output ) - for result, output in zip(results, outputs)) + value = np.asarray(results[0]) + if len(value.shape) == 1 and value.shape[0] == 1: + results = (UnitValue(value[0], prefixed_unit),) + else: + results = tuple((UnitValues.from_ndarray(np.asarray(result), prefixed_unit) + if output is None else output) + for result, output in zip(results, outputs)) # list or scalar return results[0] if len(results) == 1 else results @@ -1856,9 +1867,9 @@ def as_ndarray(self, scale=False): ############################################## - def __getitem__(self, _slice): + def __getitem__(self, slice_): - value = super(UnitValues, self).__getitem__(_slice) + value = super(UnitValues, self).__getitem__(slice_) if isinstance(value, UnitValue): # slice return value @@ -1867,7 +1878,7 @@ def __getitem__(self, _slice): ############################################## - def __setitem__(self, _slice, value): + def __setitem__(self, slice_, value): if isinstance(value, UnitValue): self._check_unit(value) @@ -1876,10 +1887,29 @@ def __setitem__(self, _slice, value): self._check_unit(value) value = self._convert_value(value) - super(UnitValues, self).__setitem__(_slice, value) + super(UnitValues, self).__setitem__(slice_, value) ############################################## + def __getstate__(self): + return {'data': super(UnitValues, self).__getstate__(), + 'prefixed_unit': self._prefixed_unit + } + + ############################################## + + def __reduce__(self): + np_ret = super(UnitValues, self).__reduce__() + obj_state = np_ret[2] + unit_state = ((self._prefixed_unit,) + obj_state[:],) + new_ret = np_ret[:2] + unit_state + np_ret[3:] + return new_ret + + def __setstate__(self, state): + super(UnitValues, self).__setstate__(state[1:]) + self._prefixed_unit = state[0] + + ############################################## def __contains__(self, value): raise NotImplementedError @@ -1912,8 +1942,7 @@ def scale(self): ############################################## def is_same_unit(self, other): - - return self._prefixed_unit.is_same_unit(other.prefixed_unit) + return self._prefixed_unit == other.prefixed_unit ############################################## @@ -2005,6 +2034,7 @@ def convert_to_power(self, power=0): # Reset PrefixedUnit._value_ctor_ = UnitValue +PrefixedUnit._values_ctor_ = UnitValues _simple_prefixed_unit = PrefixedUnit() 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/Spice/test_BasicElement.py b/unit-test/Spice/test_BasicElement.py index 9b99b80e5..5b3d867ee 100644 --- a/unit-test/Spice/test_BasicElement.py +++ b/unit-test/Spice/test_BasicElement.py @@ -43,22 +43,22 @@ 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 100'.lower()) self._test_spice_declaration(Resistor(Circuit(''), '1', 'n1', 'n2', kilo(1)), - 'R1 n1 n2 1k') + 'R1 n1 n2 1k'.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 1k ac=2k 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 1k'.lower()) 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_Netlist.py b/unit-test/Spice/test_Netlist.py index 27f7daeaf..da2a9c64a 100644 --- a/unit-test/Spice/test_Netlist.py +++ b/unit-test/Spice/test_Netlist.py @@ -76,10 +76,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) @@ -93,16 +93,17 @@ def test_basic(self): spice_declaration = """ .title Voltage Divider -Vinput in 0 10V -R1 in out 9kOhm -R2 out 0 1kOhm + +vinput in 0 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, circuit['out'], circuit.gnd, 1@u_kΩ) # out node is defined self._test_spice_declaration(circuit, spice_declaration) circuit = VoltageDividerCircuit() @@ -110,24 +111,24 @@ def test_basic(self): self._test_nodes(circuit, (0, 'in', 'out')) - self.assertTrue(circuit.R1.minus.node is circuit.out) + self.assertTrue(circuit['R1'].minus.node is circuit['out']) - self.assertEqual(str(circuit.R1.plus.node), 'in') - self.assertEqual(str(circuit.R1.minus.node), 'out') + self.assertEqual(str(circuit['R1'].plus.node), 'in') + self.assertEqual(str(circuit['R1'].minus.node), 'out') self.assertEqual(str(circuit['in']), 'in') self.assertEqual(str(circuit['out']), 'out') - self.assertEqual(str(circuit.out), 'out') + #self.assertEqual(str(circuit.out), 'out') # for pin in circuit.out: # print(pin) - self.assertEqual(circuit.out.pins, set((circuit.R1.minus, circuit.R2.plus))) + self.assertEqual(circuit['out'].pins, set((circuit['R1'].minus, circuit['R2'].plus))) - self.assertEqual(circuit.R1.resistance, 9@u_kΩ) + self.assertEqual(circuit['R1'].resistance, 9@u_kΩ) self.assertEqual(circuit['R2'].resistance, 1@u_kΩ) - circuit.R1.resistance = 10@u_kΩ + circuit['R1'].resistance = 10@u_kΩ self._test_spice_declaration(circuit, spice_declaration.replace('9k', '10k')) # .global .param .include .model @@ -156,9 +157,10 @@ 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 10v +r1 in out 9kohm """ # .end @@ -174,9 +176,8 @@ def test_keyword_clash(self): circuit = Circuit('') 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(model['is_'], 1) + self.assertEqual(str(model), '.model diode D (is=1 rs=2)') def test_transformer(self): import os diff --git a/unit-test/Spice/test_Pickle.py b/unit-test/Spice/test_Pickle.py new file mode 100644 index 000000000..44957101a --- /dev/null +++ b/unit-test/Spice/test_Pickle.py @@ -0,0 +1,28 @@ +import unittest +from PySpice.Probe.WaveForm import WaveForm +from PySpice.Unit.SiUnits import Second +from PySpice.Unit.Unit import UnitValues, PrefixedUnit +from PySpice.Unit import u_kHz +import pickle +import os +import tempfile +import numpy as np + +class TestPickle(unittest.TestCase): + def test_ndarray(self): + array = np.ndarray((1, 1)) + with tempfile.TemporaryFile() as fp: + pickle.dump(array, fp) + fp.seek(0) + new_array = pickle.load(fp) + self.assertEqual(array, new_array) + + def test_unit_values(self): + unit_values = UnitValues(u_kHz(100).prefixed_unit, (1, 1)) + new_unit_values = pickle.loads(pickle.dumps(unit_values)) + self.assertEqual(unit_values, new_unit_values) + + def test_waveforms(self): + waveform = WaveForm("Test", u_kHz(100).prefixed_unit, (1, 1)) + new_waveform = pickle.loads(pickle.dumps(waveform)) + self.assertEqual(waveform, new_waveform) From cdce6c424a7e17b182d6a0b3c258fdb93b293f4e Mon Sep 17 00:00:00 2001 From: "Jose M. Gomez" Date: Fri, 15 Feb 2019 16:41:51 +0100 Subject: [PATCH 022/134] Adding test for Nonlinear sources. --- PySpice/Spice/BasicElement.py | 18 ++++ unit-test/Unit/test_SpiceParser.py | 15 +++ unit-test/Unit/ucc27211.lib | 161 +++++++++++++++++++++++++++++ 3 files changed, 194 insertions(+) create mode 100644 unit-test/Unit/test_SpiceParser.py create mode 100644 unit-test/Unit/ucc27211.lib diff --git a/PySpice/Spice/BasicElement.py b/PySpice/Spice/BasicElement.py index 830a965df..487fe45db 100644 --- a/PySpice/Spice/BasicElement.py +++ b/PySpice/Spice/BasicElement.py @@ -119,6 +119,19 @@ import SchemDraw as schem +def _non_linear_source_parser_(args): + value = 'value' + table = 'table' + expression = None + table = None + value_idx = 2 + if value in args[value_idx]: + if args[value_idx+1] == '=': + value_idx += 1 + expression = ''.join(args[value_idx:]) + table = None + return expression, table + #################################################################################################### class DipoleElement(FixedPinElement): @@ -1031,6 +1044,9 @@ class NonLinearVoltageSource(DipoleElement): __alias__ = 'NonLinearVoltageSource' _prefix_ = 'E' + value = ExpressionKeyParameter('value') + table = ExpressionKeyParameter('table') + schematic = schem.elements.SOURCE_V ############################################## @@ -1052,6 +1068,8 @@ def __str__(self): # TABLE {expression} = (x0, y0) (x1, y1) ... table = ['({}, {})'.format(str_spice(x), str_spice(y)) for x, y in self.table] spice_element += ' TABLE {%s} = %s' % (self.expression, join_list(table)) + else: + spice_element += ' VALUE={%s}' % (self.expression) return spice_element #################################################################################################### diff --git a/unit-test/Unit/test_SpiceParser.py b/unit-test/Unit/test_SpiceParser.py new file mode 100644 index 000000000..4988e568c --- /dev/null +++ b/unit-test/Unit/test_SpiceParser.py @@ -0,0 +1,15 @@ +import unittest +from PySpice.Spice.Netlist import Circuit +import os + + +class TestSpiceParser(unittest.TestCase): + def test_subcircuit(self): + print(os.getcwd()) + circuit = Circuit('Diode Characteristic Curve') + circuit.include(os.path.join(os.getcwd(), 'ucc27211.lib')) + self.assertEqual(True, False) + + +if __name__ == '__main__': + unittest.main() diff --git a/unit-test/Unit/ucc27211.lib b/unit-test/Unit/ucc27211.lib new file mode 100644 index 000000000..2044b48c4 --- /dev/null +++ b/unit-test/Unit/ucc27211.lib @@ -0,0 +1,161 @@ +*** +*$ +* UCC27211 Model +***************************************************************************** +* (C) Copyright 2011 Texas Instruments Incorporated. All rights reserved. +***************************************************************************** +** This model is designed as an aid for customers of Texas Instruments. +** TI and its licensors and suppliers make no warrenties, either expressed +** or implied, with respect to this model, including the warranties of +** merchantability or fitness for a particular purpose. The model is +** provided solely on an "as is" basis. The entire risk as to its quality +** and performance is with the customer +***************************************************************************** +* +** Released by: Analog eLab Design Center, Texas Instruments Inc. +* Part: UCC27211 +* Date: 11/16/2011 +* Model Type: TRANSIENT +* Simulator: PSPICE +* Simulator Version: 16.0.0.p001 +* EVM Order Number: None +* EVM Users Guide: None +* Datasheet: SLUSAT7 - November 2011 +* +* Model Version: Final 1.00 +* +***************************************************************************** +* +* Updates: +* +* Final 1.00 +* Release to Web. +* +***************************************************************************** +* source UCC27211 +.SUBCKT UCC27211 ++ HB ++ HI ++ HO ++ HS ++ LI ++ LO ++ VDD ++ VSS +C_U7_C4 HS HO 1pF +E_U7_E1 U7_N208620 HO ++ VALUE { IF(V(U7_N208706, 0) > 0.5, 5, -5) } +C_U7_C3 HO U7_N208506 20p +C_U7_C5 HO HB 1pF +R_U7_R4 HB U7_N208424 .2 +M_U7_M1 U7_N208424 U7_N208506 HO HO NMOS01 ++ L=10u ++ W=10u +M_U7_M2 U7_N208826 U7_N208506 HO HO PMOS01 +R_U7_R5 U7_N208826 HS .2 +R_U7_R3 U7_N208506 U7_N208620 100 +C_U7_C1 U7_N208506 U7_N208424 7p +C_U7_C2 U7_N208826 U7_N208506 7p +X_U7_U1 H_DRV U7_N208706 DELAY PARAMS: RINP=1K DELAY=16n +C_U8_C4 VSS LO 1pF +E_U8_E1 U8_N208620 LO ++ VALUE { IF(V(U8_N208706, 0) > 0.5, 5, -5) } +C_U8_C3 LO U8_N208506 20p +C_U8_C5 LO VDD 1pF +R_U8_R4 VDD U8_N208424 .2 +M_U8_M1 U8_N208424 U8_N208506 LO LO NMOS01 ++ L=10u ++ W=10u +M_U8_M2 U8_N208826 U8_N208506 LO LO PMOS01 +R_U8_R5 U8_N208826 VSS .2 +R_U8_R3 U8_N208506 U8_N208620 100 +C_U8_C1 U8_N208506 U8_N208424 7p +C_U8_C2 U8_N208826 U8_N208506 7p +X_U8_U1 L_DRV U8_N208706 DELAY PARAMS: RINP=1K DELAY=16n +G_U3_G1 HB HS H_ENB 0 65u +V_U3_V1 U3_N00585 HS 6.7Vdc +V_U3_V2 U3_N00613 0 1.1Vdc +X_U3_U1 H_ENB HB U3_N00585 U3_N00613 COMP_BOB +R_U1_R1 VSS HI 70k +C_U1_C1 VSS HI 2p +X_U1_U1 H_INP HI U1_N02053 U1_N02141 COMP_BOB +R_U1_R2 VSS VDD 1G +V_U1_V1 U1_N02053 VSS 2.3Vdc +V_U1_V2 U1_N02141 0 0.7Vdc +G_U4_G1 VDD VSS L_ENB 0 80u +V_U4_V1 U4_N00415 VSS 7.0Vdc +V_U4_V2 U4_N00435 0 0.5Vdc +X_U4_U1 L_ENB VDD U4_N00415 U4_N00435 COMP_BOB +R_U2_R1 VSS LI 70k +C_U2_C1 VSS LI 2p +X_U2_U1 L_INP LI U2_N02053 U2_N02141 COMP_BOB +R_U2_R2 VSS VDD 1G +V_U2_V1 U2_N02053 VSS 2.3Vdc +V_U2_V2 U2_N02141 0 0.7Vdc +X_U6 L_INP L_ENB L_DRV AND2 +D_D1 VDD HB DIODE01 +X_U5 H_ENB H_INP L_ENB H_DRV AND3 +.ENDS UCC27211 +*$ +.model PMOS01 PMOS ++ VTO = -1 ++ KP = 0.46 ++ LAMBDA = 0.001 ++ RS = 1m +*$ +.model NMOS01 NMOS ++ VTO = 3 ++ KP = 2.25 ++ LAMBDA = 0.001 ++ RS = 1m +*$ +.model DIODE01 D ++ IS = 1.038e-15 ++ N = 1 ++ TT = 20e-9 ++ CJO = 5e-12 ++ RS = 0.50 ++ BV = 130 +*$ +.SUBCKT DELAY INP OUT PARAMS: RINP = 1k DELAY = 10n +R1 INP 101 {RINP} +C1 101 102 { 1.4427 * DELAY / RINP } +E1 102 0 OUT 0 0.5 +E2 103 0 VALUE {IF(V(101) > 0.5, 1, 0)} +R2 103 OUT 1 +C2 OUT 0 1n +.ENDS DELAY +*$ +.SUBCKT AND2 A B Y +EINT YINT 0 VALUE {IF(V(A) > 0.5 & V(B) > 0.5, 1, 0)} +RINT YINT Y 1 +CINT Y 0 1n +.ENDS AND2 +*$ +.SUBCKT AND3 A B C Y +EINT YINT 0 ++ VALUE {IF(V(A) > 0.5 & V(B) > 0.5 & V(C) > 0.5, 1, 0)} +RINT YINT Y 1 +CINT Y 0 1n +.ENDS AND3 +*$ +.SUBCKT BUF_BOB A Y VDD VSS PARAMS: VTHRESH = 0.5 +EINT YINT 0 VALUE {IF(V(A) > { VTHRESH }, V(VDD), V(VSS))} +RINT YINT Y 1 +CINT Y VSS 1n +.ENDS BUF_BOB +*$ +.SUBCKT INV_BOB A Y VDD VSS PARAMS: VTHRESH = 0.5 +EINT YINT 0 VALUE {IF(V(A) < { VTHRESH }, V(VDD), V(VSS))} +RINT YINT Y 1 +CINT Y VSS 1n +.ENDS INV_BOB +*$ +.SUBCKT COMP_BOB Y VINP VINN VHYS +EINT YINT 0 ++ VALUE { MAX(0, MIN(1, 1000*(5e-4 + V(Y)*V(VHYS) + V(VINP) - V(VINN)))) } +RINT YINT Y 1 +CINT Y 0 1n +.ENDS COMP_BOB +*$ + From 1dd15407d84c577bea0cd4b12e05ee7f2c6a0f75 Mon Sep 17 00:00:00 2001 From: "Jose M. Gomez" Date: Sat, 16 Feb 2019 19:40:56 +0100 Subject: [PATCH 023/134] spice name Adding the possibility to use parameters with the spice name in an element. --- PySpice/Spice/Netlist.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index 0e1518572..fdd235da2 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -537,9 +537,14 @@ def __init__(self, netlist, name, *args, **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_: - setattr(self, key, value) - + else: + if key in self._positional_parameters_ or key in self._optional_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 schematic = kwargs.pop('schematic', {}) self._schematic = schematic From b850770f9a29a3e23754513e014dd60e14d875d8 Mon Sep 17 00:00:00 2001 From: "Jose M. Gomez" Date: Sat, 16 Feb 2019 19:41:47 +0100 Subject: [PATCH 024/134] expressions Add the management of expressions inside brackets. --- PySpice/Spice/Parser.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/PySpice/Spice/Parser.py b/PySpice/Spice/Parser.py index 4e43e4450..332cb0371 100644 --- a/PySpice/Spice/Parser.py +++ b/PySpice/Spice/Parser.py @@ -465,7 +465,7 @@ def __init__(self, line): if kwarg in ('off',) and prefix_data.has_flag: self._dict_parameters['off'] = True else: - self._logger.warn(line_str) + self._logger.warning(line_str) # raise NameError('Bad element line:', line_str) if prefix_data.multi_devices: @@ -663,7 +663,12 @@ def read_words(self, start_location, number_of_words): line_str = self._text number_of_words_read = 0 while number_of_words_read < number_of_words: # and start_location < len(line_str) - stop_location = line_str.find(' ', start_location) + 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() @@ -705,8 +710,19 @@ def split_words(self, start_location, until=None): line_str = line_str[start_location:stop_location] words = [x for x in line_str.split(' ') if x] - - return words, stop_location + 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 ############################################## From a8fa62fe50fdefd23f92130b0d6274583e917275 Mon Sep 17 00:00:00 2001 From: "Jose M. Gomez" Date: Sat, 16 Feb 2019 19:42:07 +0100 Subject: [PATCH 025/134] Modifications to make the library compatible with pyspice. --- unit-test/Unit/ucc27211.lib | 125 ++++++++++++++++++------------------ 1 file changed, 63 insertions(+), 62 deletions(-) diff --git a/unit-test/Unit/ucc27211.lib b/unit-test/Unit/ucc27211.lib index 2044b48c4..854c7b6ba 100644 --- a/unit-test/Unit/ucc27211.lib +++ b/unit-test/Unit/ucc27211.lib @@ -42,60 +42,7 @@ + LO + VDD + VSS -C_U7_C4 HS HO 1pF -E_U7_E1 U7_N208620 HO -+ VALUE { IF(V(U7_N208706, 0) > 0.5, 5, -5) } -C_U7_C3 HO U7_N208506 20p -C_U7_C5 HO HB 1pF -R_U7_R4 HB U7_N208424 .2 -M_U7_M1 U7_N208424 U7_N208506 HO HO NMOS01 -+ L=10u -+ W=10u -M_U7_M2 U7_N208826 U7_N208506 HO HO PMOS01 -R_U7_R5 U7_N208826 HS .2 -R_U7_R3 U7_N208506 U7_N208620 100 -C_U7_C1 U7_N208506 U7_N208424 7p -C_U7_C2 U7_N208826 U7_N208506 7p -X_U7_U1 H_DRV U7_N208706 DELAY PARAMS: RINP=1K DELAY=16n -C_U8_C4 VSS LO 1pF -E_U8_E1 U8_N208620 LO -+ VALUE { IF(V(U8_N208706, 0) > 0.5, 5, -5) } -C_U8_C3 LO U8_N208506 20p -C_U8_C5 LO VDD 1pF -R_U8_R4 VDD U8_N208424 .2 -M_U8_M1 U8_N208424 U8_N208506 LO LO NMOS01 -+ L=10u -+ W=10u -M_U8_M2 U8_N208826 U8_N208506 LO LO PMOS01 -R_U8_R5 U8_N208826 VSS .2 -R_U8_R3 U8_N208506 U8_N208620 100 -C_U8_C1 U8_N208506 U8_N208424 7p -C_U8_C2 U8_N208826 U8_N208506 7p -X_U8_U1 L_DRV U8_N208706 DELAY PARAMS: RINP=1K DELAY=16n -G_U3_G1 HB HS H_ENB 0 65u -V_U3_V1 U3_N00585 HS 6.7Vdc -V_U3_V2 U3_N00613 0 1.1Vdc -X_U3_U1 H_ENB HB U3_N00585 U3_N00613 COMP_BOB -R_U1_R1 VSS HI 70k -C_U1_C1 VSS HI 2p -X_U1_U1 H_INP HI U1_N02053 U1_N02141 COMP_BOB -R_U1_R2 VSS VDD 1G -V_U1_V1 U1_N02053 VSS 2.3Vdc -V_U1_V2 U1_N02141 0 0.7Vdc -G_U4_G1 VDD VSS L_ENB 0 80u -V_U4_V1 U4_N00415 VSS 7.0Vdc -V_U4_V2 U4_N00435 0 0.5Vdc -X_U4_U1 L_ENB VDD U4_N00415 U4_N00435 COMP_BOB -R_U2_R1 VSS LI 70k -C_U2_C1 VSS LI 2p -X_U2_U1 L_INP LI U2_N02053 U2_N02141 COMP_BOB -R_U2_R2 VSS VDD 1G -V_U2_V1 U2_N02053 VSS 2.3Vdc -V_U2_V2 U2_N02141 0 0.7Vdc -X_U6 L_INP L_ENB L_DRV AND2 -D_D1 VDD HB DIODE01 -X_U5 H_ENB H_INP L_ENB H_DRV AND3 -.ENDS UCC27211 + *$ .model PMOS01 PMOS + VTO = -1 @@ -121,41 +68,95 @@ X_U5 H_ENB H_INP L_ENB H_DRV AND3 R1 INP 101 {RINP} C1 101 102 { 1.4427 * DELAY / RINP } E1 102 0 OUT 0 0.5 -E2 103 0 VALUE {IF(V(101) > 0.5, 1, 0)} +B2 103 0 V={IF(V(101) > 0.5, 1, 0)} R2 103 OUT 1 C2 OUT 0 1n .ENDS DELAY *$ .SUBCKT AND2 A B Y -EINT YINT 0 VALUE {IF(V(A) > 0.5 & V(B) > 0.5, 1, 0)} +BINT YINT 0 V={IF(V(A) > 0.5 & V(B) > 0.5, 1, 0)} RINT YINT Y 1 CINT Y 0 1n .ENDS AND2 *$ .SUBCKT AND3 A B C Y -EINT YINT 0 -+ VALUE {IF(V(A) > 0.5 & V(B) > 0.5 & V(C) > 0.5, 1, 0)} +BINT YINT 0 ++ V={IF(V(A) > 0.5 & V(B) > 0.5 & V(C) > 0.5, 1, 0)} RINT YINT Y 1 CINT Y 0 1n .ENDS AND3 *$ .SUBCKT BUF_BOB A Y VDD VSS PARAMS: VTHRESH = 0.5 -EINT YINT 0 VALUE {IF(V(A) > { VTHRESH }, V(VDD), V(VSS))} +BINT YINT 0 V={IF(V(A) > { VTHRESH }, V(VDD), V(VSS))} RINT YINT Y 1 CINT Y VSS 1n .ENDS BUF_BOB *$ .SUBCKT INV_BOB A Y VDD VSS PARAMS: VTHRESH = 0.5 -EINT YINT 0 VALUE {IF(V(A) < { VTHRESH }, V(VDD), V(VSS))} +BINT YINT 0 V={IF(V(A) < { VTHRESH }, V(VDD), V(VSS))} RINT YINT Y 1 CINT Y VSS 1n .ENDS INV_BOB *$ .SUBCKT COMP_BOB Y VINP VINN VHYS -EINT YINT 0 -+ VALUE { MAX(0, MIN(1, 1000*(5e-4 + V(Y)*V(VHYS) + V(VINP) - V(VINN)))) } +BINT YINT 0 ++ V={ MAX(0, MIN(1, 1000*(5e-4 + V(Y)*V(VHYS) + V(VINP) - V(VINN)))) } RINT YINT Y 1 CINT Y 0 1n .ENDS COMP_BOB *$ +C_U7_C4 HS HO 1pF +B_U7_E1 U7_N208620 HO ++ V={ IF(V(U7_N208706, 0) > 0.5, 5, -5) } +C_U7_C3 HO U7_N208506 20p +C_U7_C5 HO HB 1pF +R_U7_R4 HB U7_N208424 .2 +M_U7_M1 U7_N208424 U7_N208506 HO HO NMOS01 ++ L=10u ++ W=10u +M_U7_M2 U7_N208826 U7_N208506 HO HO PMOS01 +R_U7_R5 U7_N208826 HS .2 +R_U7_R3 U7_N208506 U7_N208620 100 +C_U7_C1 U7_N208506 U7_N208424 7p +C_U7_C2 U7_N208826 U7_N208506 7p +X_U7_U1 H_DRV U7_N208706 DELAY PARAMS: RINP=1K DELAY=16n +C_U8_C4 VSS LO 1pF +B_U8_E1 U8_N208620 LO ++ V={ IF(V(U8_N208706, 0) > 0.5, 5, -5) } +C_U8_C3 LO U8_N208506 20p +C_U8_C5 LO VDD 1pF +R_U8_R4 VDD U8_N208424 .2 +M_U8_M1 U8_N208424 U8_N208506 LO LO NMOS01 ++ L=10u ++ W=10u +M_U8_M2 U8_N208826 U8_N208506 LO LO PMOS01 +R_U8_R5 U8_N208826 VSS .2 +R_U8_R3 U8_N208506 U8_N208620 100 +C_U8_C1 U8_N208506 U8_N208424 7p +C_U8_C2 U8_N208826 U8_N208506 7p +X_U8_U1 L_DRV U8_N208706 DELAY PARAMS: RINP=1K DELAY=16n +G_U3_G1 HB HS H_ENB 0 65u +V_U3_V1 U3_N00585 HS 6.7Vdc +V_U3_V2 U3_N00613 0 1.1Vdc +X_U3_U1 H_ENB HB U3_N00585 U3_N00613 COMP_BOB +R_U1_R1 VSS HI 70k +C_U1_C1 VSS HI 2p +X_U1_U1 H_INP HI U1_N02053 U1_N02141 COMP_BOB +R_U1_R2 VSS VDD 1G +V_U1_V1 U1_N02053 VSS 2.3Vdc +V_U1_V2 U1_N02141 0 0.7Vdc +G_U4_G1 VDD VSS L_ENB 0 80u +V_U4_V1 U4_N00415 VSS 7.0Vdc +V_U4_V2 U4_N00435 0 0.5Vdc +X_U4_U1 L_ENB VDD U4_N00415 U4_N00435 COMP_BOB +R_U2_R1 VSS LI 70k +C_U2_C1 VSS LI 2p +X_U2_U1 L_INP LI U2_N02053 U2_N02141 COMP_BOB +R_U2_R2 VSS VDD 1G +V_U2_V1 U2_N02053 VSS 2.3Vdc +V_U2_V2 U2_N02141 0 0.7Vdc +X_U6 L_INP L_ENB L_DRV AND2 +D_D1 VDD HB DIODE01 +X_U5 H_ENB H_INP L_ENB H_DRV AND3 +.ENDS UCC27211 From 3eb9b621e2c9d1ff6121872a9c30a2fc0da0598c Mon Sep 17 00:00:00 2001 From: "Jose M. Gomez" Date: Sun, 17 Feb 2019 15:20:00 +0100 Subject: [PATCH 026/134] Update .gitignore Avoding .idea. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 78aeda6a1..bc93ee08f 100644 --- a/.gitignore +++ b/.gitignore @@ -106,3 +106,4 @@ issues/multi-shared.py issues/nazir/ make-graph.sh trash/ +.idea/ From f2ab07c9585411c480139ee0ff2afc755292a7af Mon Sep 17 00:00:00 2001 From: "Jose M. Gomez" Date: Sun, 17 Feb 2019 16:06:45 +0100 Subject: [PATCH 027/134] Changing the order of models. --- PySpice/Spice/Netlist.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index fdd235da2..db878968b 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -1143,15 +1143,15 @@ def include(self, path): if path not in self._includes: self._includes.append(path) parser = SpiceParser(path=path) + models = parser.models + for model in models: + self.model(model._name, model._model_type, **model._parameters) + self._models[model._name]._included = path subcircuits = parser.subcircuits for subcircuit in subcircuits: subcircuit_def = subcircuit.build() self.subcircuit(subcircuit_def) self._subcircuits[subcircuit._name]._included = path - models = parser.models - for model in models: - self.model(model._name, model._model_type, **model._parameters) - self._models[model._name]._included = path else: self._logger.warn("Duplicated include") From 81c1ea2735c2d242f70886aece1bcf16779c6bb5 Mon Sep 17 00:00:00 2001 From: "Jose M. Gomez" Date: Sun, 17 Feb 2019 16:08:58 +0100 Subject: [PATCH 028/134] Managing the parser. --- PySpice/Spice/Parser.py | 218 +++++++++++++++++++++++++++++++++------- 1 file changed, 179 insertions(+), 39 deletions(-) diff --git a/PySpice/Spice/Parser.py b/PySpice/Spice/Parser.py index 332cb0371..f985d05ce 100644 --- a/PySpice/Spice/Parser.py +++ b/PySpice/Spice/Parser.py @@ -315,6 +315,114 @@ def build(self, circuit): #################################################################################################### +class CircuitStatement(Statement): + + """ This class implements a circuit definition. + + Spice syntax:: + + Title ... + + """ + + ############################################## + + def __init__(self, title): + + super().__init__(title, statement='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 = [] + + ############################################## + + @property + def title(self): + """ Title 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 + + ############################################## + + 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 appendModel(self, statement): + + """ Append a model to the statement's list. """ + + self._models.append(statement) + + def appendSubCircuit(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): + circuit = Circuit(self._title) + for statement in self._models: + 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. @@ -333,10 +441,15 @@ def __init__(self, line): # Fixme parameters, dict_parameters = self._line.split_line('.subckt') + 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 = [] ############################################## @@ -350,11 +463,26 @@ 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 subcircuits(self): + """ Subcircuits of the sub-circuit. """ + return self._subcircuits + ############################################## def __repr__(self): - text = 'SubCircuit {} {}'.format(self._name, self._nodes) + os.linesep + if self._parameters: + text = 'SubCircuit {} {} Params: {}'.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 @@ -364,7 +492,7 @@ def __iter__(self): """ Return an iterator on the statements. """ - return iter(self._statements) + return iter(self._models + self._subcircuits + self._statements) ############################################## @@ -374,6 +502,18 @@ def append(self, statement): self._statements.append(statement) + def appendModel(self, statement): + + """ Append a model to the statement's list. """ + + self._models.append(statement) + + def appendSubCircuit(self, statement): + + """ Append a model to the statement's list. """ + + self._subcircuits.append(statement) + ############################################## def to_python(self, ground=0): @@ -389,8 +529,15 @@ def to_python(self, ground=0): def build(self, ground=0): - subcircuit = SubCircuit(self._name, *self._nodes) - SpiceParser._build_circuit(subcircuit, self._statements, ground) + subcircuit = SubCircuit(self._name, *self._nodes, **self._parameters) + for statement in self._models: + statement.build(subcircuit) + for statement in self._subcircuits: + subckt = statement.build(ground) # Fixme: ok ??? + subcircuit.subcircuit(subckt) + for statement in self._statements: + if isinstance(statement, Element): + statement.build(subcircuit, ground) return subcircuit #################################################################################################### @@ -439,12 +586,14 @@ def __init__(self, line): # Fixme: optional node else: # X args, stop_location = self._line.split_words(stop_location, until='=') + if args[-1].lower() == 'params:': + args = args[:-1] self._nodes = args[:-1] self._parameters.append(args[-1]) # model name # Read positionals number_of_positionals = prefix_data.number_of_positionals_min - if number_of_positionals and stop_location is not None: # model is optional + if number_of_positionals and (stop_location is not None) and (prefix_data.prefix != 'X'): # model is optional self._parameters, stop_location = self._line.read_words(stop_location, number_of_positionals) if prefix_data.multi_devices and stop_location is not None: remaining, stop_location = self._line.split_words(stop_location, until='=') @@ -455,7 +604,7 @@ def __init__(self, line): self._parameters[-1] += line_str[stop_location:] # Read optionals - if prefix_data.has_optionals and stop_location is not None: + if (prefix_data.has_optionals or (prefix_data.prefix == 'X')) and stop_location is not None: kwargs, stop_location = self._line.split_words(stop_location) for kwarg in kwargs: try: @@ -834,7 +983,6 @@ def __init__(self, path=None, source=None, end_of_line_comment=('$', '//', ';')) lines = self._merge_lines(raw_lines) self._title = None self._statements = self._parse(lines) - self._find_sections() ############################################## @@ -878,14 +1026,9 @@ def _parse(self, lines): # if lines[-1] != '.end': # raise NameError('".end" is expected at the end of the netlist') - title_statement = '.title ' - self._title = str(lines[0]) - if self._title.startswith(title_statement): - self._title = self._title[len(title_statement):] - - statements = [] - sub_circuit = None - scope = statements + circuit = CircuitStatement(lines[0]) + stack = [] + scope = circuit for line in lines[1:]: # print('>', repr(line)) text = str(line) @@ -895,12 +1038,12 @@ def _parse(self, lines): elif lower_case_text.startswith('.'): lower_case_text = lower_case_text[1:] if lower_case_text.startswith('subckt'): - sub_circuit = SubCircuitStatement(line) - statements.append(sub_circuit) - scope = sub_circuit + stack.append(scope) + scope = SubCircuitStatement(line) elif lower_case_text.startswith('ends'): - sub_circuit = None - scope = statements + parent = stack.pop() + parent.appendSubCircuit(scope) + scope = parent elif lower_case_text.startswith('title'): # override fist line self._title = Title(line) @@ -909,7 +1052,7 @@ def _parse(self, lines): pass elif lower_case_text.startswith('model'): model = Model(line) - scope.append(model) + scope.appendModel(model) elif lower_case_text.startswith('include'): scope.append(Include(line)) else: @@ -927,27 +1070,24 @@ def _parse(self, lines): except ParseError: pass - return statements + return circuit ############################################## - def _find_sections(self): + @property + def circuit(self): + """ Circuit statements. """ + return self._statements - """ Look for model, sub-circuit and circuit definitions in the statement list. """ + @property + def models(self): + """ Models of the sub-circuit. """ + return self._statements.models - self.circuit = None - self.subcircuits = [] - self.models = [] - for statement in self._statements: - if isinstance(statement, Title): - if self.circuit is None: - self.circuit = statement - else: - raise NameError('More than one title') - elif isinstance(statement, SubCircuitStatement): - self.subcircuits.append(statement) - elif isinstance(statement, Model): - self.models.append(statement) + @property + def subcircuits(self): + """ Subcircuits of the sub-circuit. """ + return self._statements.subcircuits ############################################## @@ -989,8 +1129,8 @@ def build_circuit(self, ground=0): """ - circuit = Circuit(str(self._title)) - self._build_circuit(circuit, self._statements, ground) + #circuit = Circuit(str(self._title)) + circuit = self.circuit.build(ground) return circuit ############################################## From 51053e530db0fb03e161ff3d45e49135906c86e5 Mon Sep 17 00:00:00 2001 From: "Jose M. Gomez" Date: Mon, 18 Feb 2019 06:50:33 +0100 Subject: [PATCH 029/134] Changing the node 0 for vss --- unit-test/Unit/ucc27211.lib | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/unit-test/Unit/ucc27211.lib b/unit-test/Unit/ucc27211.lib index 854c7b6ba..b11f1f541 100644 --- a/unit-test/Unit/ucc27211.lib +++ b/unit-test/Unit/ucc27211.lib @@ -136,7 +136,7 @@ R_U8_R3 U8_N208506 U8_N208620 100 C_U8_C1 U8_N208506 U8_N208424 7p C_U8_C2 U8_N208826 U8_N208506 7p X_U8_U1 L_DRV U8_N208706 DELAY PARAMS: RINP=1K DELAY=16n -G_U3_G1 HB HS H_ENB 0 65u +G_U3_G1 HB HS H_ENB VSS 65u V_U3_V1 U3_N00585 HS 6.7Vdc V_U3_V2 U3_N00613 0 1.1Vdc X_U3_U1 H_ENB HB U3_N00585 U3_N00613 COMP_BOB @@ -145,17 +145,17 @@ C_U1_C1 VSS HI 2p X_U1_U1 H_INP HI U1_N02053 U1_N02141 COMP_BOB R_U1_R2 VSS VDD 1G V_U1_V1 U1_N02053 VSS 2.3Vdc -V_U1_V2 U1_N02141 0 0.7Vdc +V_U1_V2 U1_N02141 VSS 0.7Vdc G_U4_G1 VDD VSS L_ENB 0 80u V_U4_V1 U4_N00415 VSS 7.0Vdc -V_U4_V2 U4_N00435 0 0.5Vdc +V_U4_V2 U4_N00435 VSS 0.5Vdc X_U4_U1 L_ENB VDD U4_N00415 U4_N00435 COMP_BOB R_U2_R1 VSS LI 70k C_U2_C1 VSS LI 2p X_U2_U1 L_INP LI U2_N02053 U2_N02141 COMP_BOB R_U2_R2 VSS VDD 1G V_U2_V1 U2_N02053 VSS 2.3Vdc -V_U2_V2 U2_N02141 0 0.7Vdc +V_U2_V2 U2_N02141 VSS 0.7Vdc X_U6 L_INP L_ENB L_DRV AND2 D_D1 VDD HB DIODE01 X_U5 H_ENB H_INP L_ENB H_DRV AND3 From a49aaa7741a0a78b6e4daf037af9fac4de708c98 Mon Sep 17 00:00:00 2001 From: "Jose M. Gomez" Date: Mon, 18 Feb 2019 08:41:41 +0100 Subject: [PATCH 030/134] Revert "Changing the node 0 for vss" This reverts commit 51053e530db0fb03e161ff3d45e49135906c86e5. --- unit-test/Unit/ucc27211.lib | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/unit-test/Unit/ucc27211.lib b/unit-test/Unit/ucc27211.lib index b11f1f541..854c7b6ba 100644 --- a/unit-test/Unit/ucc27211.lib +++ b/unit-test/Unit/ucc27211.lib @@ -136,7 +136,7 @@ R_U8_R3 U8_N208506 U8_N208620 100 C_U8_C1 U8_N208506 U8_N208424 7p C_U8_C2 U8_N208826 U8_N208506 7p X_U8_U1 L_DRV U8_N208706 DELAY PARAMS: RINP=1K DELAY=16n -G_U3_G1 HB HS H_ENB VSS 65u +G_U3_G1 HB HS H_ENB 0 65u V_U3_V1 U3_N00585 HS 6.7Vdc V_U3_V2 U3_N00613 0 1.1Vdc X_U3_U1 H_ENB HB U3_N00585 U3_N00613 COMP_BOB @@ -145,17 +145,17 @@ C_U1_C1 VSS HI 2p X_U1_U1 H_INP HI U1_N02053 U1_N02141 COMP_BOB R_U1_R2 VSS VDD 1G V_U1_V1 U1_N02053 VSS 2.3Vdc -V_U1_V2 U1_N02141 VSS 0.7Vdc +V_U1_V2 U1_N02141 0 0.7Vdc G_U4_G1 VDD VSS L_ENB 0 80u V_U4_V1 U4_N00415 VSS 7.0Vdc -V_U4_V2 U4_N00435 VSS 0.5Vdc +V_U4_V2 U4_N00435 0 0.5Vdc X_U4_U1 L_ENB VDD U4_N00415 U4_N00435 COMP_BOB R_U2_R1 VSS LI 70k C_U2_C1 VSS LI 2p X_U2_U1 L_INP LI U2_N02053 U2_N02141 COMP_BOB R_U2_R2 VSS VDD 1G V_U2_V1 U2_N02053 VSS 2.3Vdc -V_U2_V2 U2_N02141 VSS 0.7Vdc +V_U2_V2 U2_N02141 0 0.7Vdc X_U6 L_INP L_ENB L_DRV AND2 D_D1 VDD HB DIODE01 X_U5 H_ENB H_INP L_ENB H_DRV AND3 From 563a4c05c32e58069d934c27cd8eebc2c457933c Mon Sep 17 00:00:00 2001 From: "Jose M. Gomez" Date: Mon, 18 Feb 2019 10:25:49 +0100 Subject: [PATCH 031/134] Create mosdriver.lib --- unit-test/Unit/mosdriver.lib | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 unit-test/Unit/mosdriver.lib diff --git a/unit-test/Unit/mosdriver.lib b/unit-test/Unit/mosdriver.lib new file mode 100644 index 000000000..8611c3750 --- /dev/null +++ b/unit-test/Unit/mosdriver.lib @@ -0,0 +1,23 @@ +.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 + +bhigh ho hs v={if(v(hi, vss) > 0.5, v(hb, vss), 0)} +blow lo vss v={if(v(li, vss) > 0.5, v(vdd, vss), 0)} +dhb vdd hb diode + +.ENDS mosdriver From 456132ce1050e027d7c62f14e8f8b71363bfe609 Mon Sep 17 00:00:00 2001 From: "Jose M. Gomez" Date: Mon, 18 Feb 2019 10:28:06 +0100 Subject: [PATCH 032/134] Remove Texas Instruments ucc27211 model. --- unit-test/Unit/mosdriver.lib | 1 + unit-test/Unit/ucc27211.lib | 162 ----------------------------------- 2 files changed, 1 insertion(+), 162 deletions(-) delete mode 100644 unit-test/Unit/ucc27211.lib diff --git a/unit-test/Unit/mosdriver.lib b/unit-test/Unit/mosdriver.lib index 8611c3750..7e73d164e 100644 --- a/unit-test/Unit/mosdriver.lib +++ b/unit-test/Unit/mosdriver.lib @@ -1,3 +1,4 @@ +* Ideal mos driver .SUBCKT mosdriver + HB + HI diff --git a/unit-test/Unit/ucc27211.lib b/unit-test/Unit/ucc27211.lib deleted file mode 100644 index 854c7b6ba..000000000 --- a/unit-test/Unit/ucc27211.lib +++ /dev/null @@ -1,162 +0,0 @@ -*** -*$ -* UCC27211 Model -***************************************************************************** -* (C) Copyright 2011 Texas Instruments Incorporated. All rights reserved. -***************************************************************************** -** This model is designed as an aid for customers of Texas Instruments. -** TI and its licensors and suppliers make no warrenties, either expressed -** or implied, with respect to this model, including the warranties of -** merchantability or fitness for a particular purpose. The model is -** provided solely on an "as is" basis. The entire risk as to its quality -** and performance is with the customer -***************************************************************************** -* -** Released by: Analog eLab Design Center, Texas Instruments Inc. -* Part: UCC27211 -* Date: 11/16/2011 -* Model Type: TRANSIENT -* Simulator: PSPICE -* Simulator Version: 16.0.0.p001 -* EVM Order Number: None -* EVM Users Guide: None -* Datasheet: SLUSAT7 - November 2011 -* -* Model Version: Final 1.00 -* -***************************************************************************** -* -* Updates: -* -* Final 1.00 -* Release to Web. -* -***************************************************************************** -* source UCC27211 -.SUBCKT UCC27211 -+ HB -+ HI -+ HO -+ HS -+ LI -+ LO -+ VDD -+ VSS - -*$ -.model PMOS01 PMOS -+ VTO = -1 -+ KP = 0.46 -+ LAMBDA = 0.001 -+ RS = 1m -*$ -.model NMOS01 NMOS -+ VTO = 3 -+ KP = 2.25 -+ LAMBDA = 0.001 -+ RS = 1m -*$ -.model DIODE01 D -+ IS = 1.038e-15 -+ N = 1 -+ TT = 20e-9 -+ CJO = 5e-12 -+ RS = 0.50 -+ BV = 130 -*$ -.SUBCKT DELAY INP OUT PARAMS: RINP = 1k DELAY = 10n -R1 INP 101 {RINP} -C1 101 102 { 1.4427 * DELAY / RINP } -E1 102 0 OUT 0 0.5 -B2 103 0 V={IF(V(101) > 0.5, 1, 0)} -R2 103 OUT 1 -C2 OUT 0 1n -.ENDS DELAY -*$ -.SUBCKT AND2 A B Y -BINT YINT 0 V={IF(V(A) > 0.5 & V(B) > 0.5, 1, 0)} -RINT YINT Y 1 -CINT Y 0 1n -.ENDS AND2 -*$ -.SUBCKT AND3 A B C Y -BINT YINT 0 -+ V={IF(V(A) > 0.5 & V(B) > 0.5 & V(C) > 0.5, 1, 0)} -RINT YINT Y 1 -CINT Y 0 1n -.ENDS AND3 -*$ -.SUBCKT BUF_BOB A Y VDD VSS PARAMS: VTHRESH = 0.5 -BINT YINT 0 V={IF(V(A) > { VTHRESH }, V(VDD), V(VSS))} -RINT YINT Y 1 -CINT Y VSS 1n -.ENDS BUF_BOB -*$ -.SUBCKT INV_BOB A Y VDD VSS PARAMS: VTHRESH = 0.5 -BINT YINT 0 V={IF(V(A) < { VTHRESH }, V(VDD), V(VSS))} -RINT YINT Y 1 -CINT Y VSS 1n -.ENDS INV_BOB -*$ -.SUBCKT COMP_BOB Y VINP VINN VHYS -BINT YINT 0 -+ V={ MAX(0, MIN(1, 1000*(5e-4 + V(Y)*V(VHYS) + V(VINP) - V(VINN)))) } -RINT YINT Y 1 -CINT Y 0 1n -.ENDS COMP_BOB -*$ - -C_U7_C4 HS HO 1pF -B_U7_E1 U7_N208620 HO -+ V={ IF(V(U7_N208706, 0) > 0.5, 5, -5) } -C_U7_C3 HO U7_N208506 20p -C_U7_C5 HO HB 1pF -R_U7_R4 HB U7_N208424 .2 -M_U7_M1 U7_N208424 U7_N208506 HO HO NMOS01 -+ L=10u -+ W=10u -M_U7_M2 U7_N208826 U7_N208506 HO HO PMOS01 -R_U7_R5 U7_N208826 HS .2 -R_U7_R3 U7_N208506 U7_N208620 100 -C_U7_C1 U7_N208506 U7_N208424 7p -C_U7_C2 U7_N208826 U7_N208506 7p -X_U7_U1 H_DRV U7_N208706 DELAY PARAMS: RINP=1K DELAY=16n -C_U8_C4 VSS LO 1pF -B_U8_E1 U8_N208620 LO -+ V={ IF(V(U8_N208706, 0) > 0.5, 5, -5) } -C_U8_C3 LO U8_N208506 20p -C_U8_C5 LO VDD 1pF -R_U8_R4 VDD U8_N208424 .2 -M_U8_M1 U8_N208424 U8_N208506 LO LO NMOS01 -+ L=10u -+ W=10u -M_U8_M2 U8_N208826 U8_N208506 LO LO PMOS01 -R_U8_R5 U8_N208826 VSS .2 -R_U8_R3 U8_N208506 U8_N208620 100 -C_U8_C1 U8_N208506 U8_N208424 7p -C_U8_C2 U8_N208826 U8_N208506 7p -X_U8_U1 L_DRV U8_N208706 DELAY PARAMS: RINP=1K DELAY=16n -G_U3_G1 HB HS H_ENB 0 65u -V_U3_V1 U3_N00585 HS 6.7Vdc -V_U3_V2 U3_N00613 0 1.1Vdc -X_U3_U1 H_ENB HB U3_N00585 U3_N00613 COMP_BOB -R_U1_R1 VSS HI 70k -C_U1_C1 VSS HI 2p -X_U1_U1 H_INP HI U1_N02053 U1_N02141 COMP_BOB -R_U1_R2 VSS VDD 1G -V_U1_V1 U1_N02053 VSS 2.3Vdc -V_U1_V2 U1_N02141 0 0.7Vdc -G_U4_G1 VDD VSS L_ENB 0 80u -V_U4_V1 U4_N00415 VSS 7.0Vdc -V_U4_V2 U4_N00435 0 0.5Vdc -X_U4_U1 L_ENB VDD U4_N00415 U4_N00435 COMP_BOB -R_U2_R1 VSS LI 70k -C_U2_C1 VSS LI 2p -X_U2_U1 L_INP LI U2_N02053 U2_N02141 COMP_BOB -R_U2_R2 VSS VDD 1G -V_U2_V1 U2_N02053 VSS 2.3Vdc -V_U2_V2 U2_N02141 0 0.7Vdc -X_U6 L_INP L_ENB L_DRV AND2 -D_D1 VDD HB DIODE01 -X_U5 H_ENB H_INP L_ENB H_DRV AND3 -.ENDS UCC27211 From 6a63efadf3a73ede25721098507e17e1c63f8ab9 Mon Sep 17 00:00:00 2001 From: "Jose M. Gomez" Date: Mon, 18 Feb 2019 11:55:51 +0100 Subject: [PATCH 033/134] Changed mosdriver and BasicElement to use smoothbsrc parameter. --- PySpice/Spice/BasicElement.py | 15 ++------------- unit-test/Unit/mosdriver.lib | 26 ++++++++------------------ 2 files changed, 10 insertions(+), 31 deletions(-) diff --git a/PySpice/Spice/BasicElement.py b/PySpice/Spice/BasicElement.py index 487fe45db..2554c2d90 100644 --- a/PySpice/Spice/BasicElement.py +++ b/PySpice/Spice/BasicElement.py @@ -119,19 +119,6 @@ import SchemDraw as schem -def _non_linear_source_parser_(args): - value = 'value' - table = 'table' - expression = None - table = None - value_idx = 2 - if value in args[value_idx]: - if args[value_idx+1] == '=': - value_idx += 1 - expression = ''.join(args[value_idx:]) - table = None - return expression, table - #################################################################################################### class DipoleElement(FixedPinElement): @@ -1016,6 +1003,7 @@ class BehavioralSource(DipoleElement): tc2 = FloatKeyParameter('tc2') temperature = FloatKeyParameter('temp', unit=U_Degree) device_temperature = FloatKeyParameter('dtemp', unit=U_Degree) + smoothbsrc = IntKeyParameter('smoothbsrc') #################################################################################################### @@ -1046,6 +1034,7 @@ class NonLinearVoltageSource(DipoleElement): value = ExpressionKeyParameter('value') table = ExpressionKeyParameter('table') + smoothbsrc = ExpressionKeyParameter('smoothbsrc') schematic = schem.elements.SOURCE_V diff --git a/unit-test/Unit/mosdriver.lib b/unit-test/Unit/mosdriver.lib index 7e73d164e..f0bb9f0ec 100644 --- a/unit-test/Unit/mosdriver.lib +++ b/unit-test/Unit/mosdriver.lib @@ -1,24 +1,14 @@ * Ideal mos driver -.SUBCKT mosdriver -+ HB -+ HI -+ HO -+ HS -+ LI -+ LO -+ VDD -+ VSS +.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 +.model diode D (is=1.038e-15 n=1 tt=20e-9 cjo=5e-12 rs=0.50 bv=130) -bhigh ho hs v={if(v(hi, vss) > 0.5, v(hb, vss), 0)} -blow lo vss v={if(v(li, vss) > 0.5, v(vdd, vss), 0)} +bhigh hoi hs v={if(v(hi, vss) > 0.5, 5, 0)} smoothbsrc=1 +rhoi hoi ho 1 +choi ho hs 1e-9 +blow loi vss v={if(v(li, vss) > 0.5, 5, 0)} smoothbsrc=1 +rloi loi lo 1 +cloi lo vss 1e-9 dhb vdd hb diode .ENDS mosdriver From 4f3719e7d56bb14a3ea8968515c4399bb2a09efa Mon Sep 17 00:00:00 2001 From: "Jose M. Gomez" Date: Mon, 18 Feb 2019 12:25:23 +0100 Subject: [PATCH 034/134] Update BasicElement.py Organizing the parameters of BehavioralSource. --- PySpice/Spice/BasicElement.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/PySpice/Spice/BasicElement.py b/PySpice/Spice/BasicElement.py index 2554c2d90..a09e6c49a 100644 --- a/PySpice/Spice/BasicElement.py +++ b/PySpice/Spice/BasicElement.py @@ -1005,6 +1005,31 @@ class BehavioralSource(DipoleElement): device_temperature = FloatKeyParameter('dtemp', unit=U_Degree) smoothbsrc = IntKeyParameter('smoothbsrc') + ############################################## + + def __str__(self): + + spice_element = self.format_node_names() + # Fixme: expression + if self.current_expression is not None: + expression = ' i=%s' % self.current_expression + elif self.voltage_expression is not None: + expression = ' v=%s' % self.voltage_expression + else: + expression = '' + spice_element += expression + 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 + if self.smoothbsrc is not None: + spice_element += ' smoothbsrc=%s' % self.smoothbsrc + return spice_element + #################################################################################################### class NonLinearVoltageSource(DipoleElement): From cf8b90bb78fa495f98cd148a390415fb1e181c7f Mon Sep 17 00:00:00 2001 From: "Jose M. Gomez" Date: Tue, 9 Jul 2019 23:07:21 +0200 Subject: [PATCH 035/134] Modified the parser and include methods to properly check and organize the models and subcircuits. --- PySpice/Spice/BasicElement.py | 42 ++-- PySpice/Spice/Netlist.py | 73 +++++-- PySpice/Spice/Parser.py | 358 +++++++++++++++++++++++++++------- 3 files changed, 366 insertions(+), 107 deletions(-) diff --git a/PySpice/Spice/BasicElement.py b/PySpice/Spice/BasicElement.py index a09e6c49a..98f399968 100644 --- a/PySpice/Spice/BasicElement.py +++ b/PySpice/Spice/BasicElement.py @@ -161,6 +161,7 @@ def __init__(self, netlist, name, subcircuit_name, *nodes, **kwargs): schematic_kwargs = kwargs.pop('schematic', {}) # Fixme: match parameters to subcircuit self.parameters = kwargs + self.parent = netlist # Fixme: investigate # for key, value in parameters.items(): @@ -170,11 +171,14 @@ def __init__(self, netlist, name, subcircuit_name, *nodes, **kwargs): # setattr(self, key, parameter) subcircuit_name = subcircuit_name.lower() - subcircuit = netlist._subcircuits.get(subcircuit_name) - - self._pins = [Pin(self, PinDefinition(position, name=subcircuit._pins_[position]), netlist.get_node(node, True)) - for position, node in enumerate(nodes)] - + subcircuit = netlist._find_subcircuit(subcircuit_name) + + try: + self._pins = [Pin(self, PinDefinition(position, name=subcircuit._pins_[position]), netlist.get_node(node, True)) + for position, node in enumerate(nodes)] + except: + raise ValueError() + super().__init__(netlist, name, subcircuit_name, schematic=schematic_kwargs) @@ -253,7 +257,8 @@ class Resistor(DipoleElement): schematic = schem.elements.RES - 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') @@ -327,8 +332,8 @@ class SemiconductorResistor(DipoleElement): schematic = schem.elements.RES - 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) @@ -431,8 +436,8 @@ class Capacitor(DipoleElement): schematic = schem.elements.CAP - 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) @@ -502,8 +507,8 @@ class SemiconductorCapacitor(DipoleElement): schematic = schem.elements.CAP - 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') @@ -605,8 +610,8 @@ class Inductor(DipoleElement): schematic = schem.elements.INDUCTOR2 - 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') @@ -798,6 +803,15 @@ class VoltageSource(DipoleElement): # Fixme: ngspice manual doesn't describe well the syntax dc_value = FloatPositionalParameter(position=0, key_parameter=False, unit=U_V) + def __init__(self, netlist, name, *args, **kwargs): + number_of_pins = len(self._pins_) + arguments = args + if len(args) > number_of_pins: + arguments = list(args[:number_of_pins]) + arguments.append(join_list(args[number_of_pins:])) + + super().__init__(netlist, name, *arguments, **kwargs) + #################################################################################################### class CurrentSource(DipoleElement): diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index db878968b..0db0b549f 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -525,14 +525,20 @@ def __init__(self, netlist, name, *args, **kwargs): self._name = str(name) 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): + if len(self._positional_parameters_) < len(args): raise NameError("Number of args mismatch") - for parameter, value in zip(self._parameters_from_args_, args): - setattr(self, parameter.attribute_name, value) - + # TODO: Modify the selection of arguments to take into account the RLC model cases. + 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': @@ -636,10 +642,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 @@ -940,6 +955,15 @@ def __getitem__(self, attribute_name): else: raise IndexError(attribute_name) # KeyError + def _find_subcircuit(self, name): + if name not in self._subcircuits: + if hasattr(self, 'parent'): + return self.parent._find_subcircuit(name) + else: + return None + else: + return self._subcircuits[name] + ############################################## #def __getattr__(self, attribute_name): @@ -1080,6 +1104,7 @@ def subcircuit(self, subcircuit): # Fixme: subcircuit is a class self._subcircuits[str(subcircuit.name)] = subcircuit + subcircuit.parent=self ############################################## @@ -1089,14 +1114,14 @@ def __str__(self): # Fixme: order ??? netlist = self._str_raw_spice() - subcircuits = self._str_subcircuits() - if subcircuits: - netlist += subcircuits# before elements - netlist += os.linesep models = self._str_models() if models: netlist += models netlist += os.linesep + subcircuits = self._str_subcircuits() + if subcircuits: + netlist += subcircuits# before elements + netlist += os.linesep netlist += self._str_elements() return netlist @@ -1112,7 +1137,9 @@ def _str_elements(self): def _str_models(self): if self._used_models: - models = [self._models[model] for model in self._used_models] + models = [self._models[model] + for model in self._used_models + if model in self._models] return join_lines(models) + os.linesep else: return '' @@ -1121,7 +1148,9 @@ def _str_models(self): def _str_subcircuits(self): if self._used_subcircuits: - subcircuits = [self._subcircuits[subcircuit] for subcircuit in self._used_subcircuits] + subcircuits = [self._subcircuits[subcircuit] + for subcircuit in self._used_subcircuits + if subcircuit in self._subcircuits] return join_lines(subcircuits) else: return '' @@ -1149,7 +1178,7 @@ def include(self, path): self._models[model._name]._included = path subcircuits = parser.subcircuits for subcircuit in subcircuits: - subcircuit_def = subcircuit.build() + subcircuit_def = subcircuit.build(parent=self) self.subcircuit(subcircuit_def) self._subcircuits[subcircuit._name]._included = path else: @@ -1176,10 +1205,10 @@ def __init__(self, name, *nodes, **kwargs): self._external_nodes = tuple([Node(self, str(node)) for node in nodes]) self._pins_ = 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 ############################################## @@ -1198,6 +1227,14 @@ def clone(self, name=None): ############################################## + def parameter(self, name, expression): + + """Set a parameter.""" + + self._parameters[str(name)] = str(expression) + + ############################################## + @property def name(self): return self._name diff --git a/PySpice/Spice/Parser.py b/PySpice/Spice/Parser.py index f985d05ce..65417d9ba 100644 --- a/PySpice/Spice/Parser.py +++ b/PySpice/Spice/Parser.py @@ -30,8 +30,10 @@ #################################################################################################### +from collections import OrderedDict import logging import os +import regex #################################################################################################### @@ -274,16 +276,8 @@ def __init__(self, line): super().__init__(line, statement='model') - text = line.right_of('.model') - kwarg_start = text.find('(') - kwarg_stop = text.find(')') - if kwarg_start == -1 or kwarg_stop == -1: - # raise ParseError("Bad model: {}".format(line)) - parts, self._parameters = line.split_line('.model') - self._name, self._model_type = parts - else: - self._name, self._model_type = text[:kwarg_start].split() - self._parameters = Line.get_kwarg(text[kwarg_start+1:kwarg_stop]) + base, self._parameters = line.split_keyword('.model') + self._name, self._model_type = base self._name = self._name.lower() ############################################## @@ -311,7 +305,56 @@ def to_python(self, netlist_name): def build(self, circuit): - circuit.model(self._name, self._model_type, **self._parameters) + return circuit.model(self._name, self._model_type, **self._parameters) + +#################################################################################################### + +class Param(Statement): + + """ This class implements a model definition. + + Spice syntax:: + + .param name=expr + + """ + + ############################################## + + def __init__(self, line): + + super().__init__(line, statement='param') + + text = line.right_of('.param').strip().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)) + return '{}.param({})'.format(netlist_name, self.join_args(args)) + os.linesep + + ############################################## + + def build(self, circuit): + + circuit.parameter(self._name, self._value) #################################################################################################### @@ -339,6 +382,9 @@ def __init__(self, title): self._statements = [] self._subcircuits = [] self._models = [] + self._required_subcircuits = set() + self._required_models = set() + self._params = [] ############################################## @@ -357,6 +403,11 @@ def subcircuits(self): """ Subcircuits of the circuit. """ return self._subcircuits + @property + def params(self): + """ Parameters of the circuit. """ + return self._params + ############################################## def __repr__(self): @@ -389,6 +440,12 @@ def appendModel(self, statement): 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. """ @@ -410,8 +467,10 @@ def to_python(self, ground=0): def build(self, ground=0): circuit = Circuit(self._title) - for statement in self._models: + 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) @@ -440,7 +499,7 @@ def __init__(self, line): super().__init__(line, statement='subckt') # Fixme - parameters, dict_parameters = self._line.split_line('.subckt') + parameters, dict_parameters = self._line.split_keyword('.subckt') if parameters[-1].lower() == 'params:': parameters = parameters[:-1] self._name, self._nodes = parameters[0], parameters[1:] @@ -450,6 +509,9 @@ def __init__(self, line): self._statements = [] self._subcircuits = [] self._models = [] + self._required_subcircuits = set() + self._required_models = set() + self._params = [] ############################################## @@ -468,6 +530,11 @@ 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. """ @@ -508,6 +575,12 @@ def appendModel(self, statement): 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. """ @@ -527,13 +600,15 @@ def to_python(self, ground=0): ############################################## - def build(self, ground=0): - + def build(self, ground=0, parent = None): subcircuit = SubCircuit(self._name, *self._nodes, **self._parameters) - for statement in self._models: + 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) # Fixme: ok ??? + subckt = statement.build(ground, parent=subcircuit) # Fixme: ok ??? subcircuit.subcircuit(subckt) for statement in self._statements: if isinstance(statement, Element): @@ -562,14 +637,16 @@ def __init__(self, line): # self._logger.debug(os.linesep + line_str) # Retrieve device prefix - self._prefix = line_str[0] + 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 - start_location = 1 - stop_location = line_str.find(' ') - # Fixme: if stop_location == -1: - self._name = line_str[start_location:stop_location] + args, kwargs = line.split_element(prefix) + self._name = args.pop(0) self._nodes = [] self._parameters = [] @@ -579,43 +656,44 @@ def __init__(self, line): if not prefix_data.npins: number_of_pins = prefix_data.number_of_pins if number_of_pins: - self._nodes, stop_location = self._line.read_words(stop_location, 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, stop_location = self._line.read_words(stop_location, 3) + self._nodes = args[:3] + args = args[3:] # Fixme: optional node else: # X - args, stop_location = self._line.split_words(stop_location, until='=') if args[-1].lower() == 'params:': - args = args[:-1] - self._nodes = args[:-1] - self._parameters.append(args[-1]) # model name + 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 (stop_location is not None) and (prefix_data.prefix != 'X'): # model is optional - self._parameters, stop_location = self._line.read_words(stop_location, number_of_positionals) - if prefix_data.multi_devices and stop_location is not None: - remaining, stop_location = self._line.split_words(stop_location, until='=') + 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 stop_location is not None: + if prefix_data.prefix in ('V', 'I') and (len(args) > 0): # merge remaining - self._parameters[-1] += line_str[stop_location:] + self._parameters[-1] += " " + " ".join(args) + self._dict_parameters = kwargs # Read optionals - if (prefix_data.has_optionals or (prefix_data.prefix == 'X')) and stop_location is not None: - kwargs, stop_location = self._line.split_words(stop_location) - for kwarg in kwargs: - try: - key, value = kwarg.split('=') - self._dict_parameters[key] = value - except ValueError: - if kwarg in ('off',) and prefix_data.has_flag: - self._dict_parameters['off'] = True - else: - self._logger.warning(line_str) - # raise NameError('Bad element line:', line_str) + 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 kwarg in ('off',) and prefix_data.has_flag: + # self._dict_parameters['off'] = True + #else: + # self._logger.warning(line_str) + # # raise NameError('Bad element line:', line_str) if prefix_data.multi_devices: for element_class in prefix_data: @@ -629,11 +707,12 @@ def __init__(self, line): to_delete = [] for parameter in element_class.positional_parameters.values(): if parameter.key_parameter: - i = parameter.position - self._dict_parameters[parameter.attribute_name] = self._parameters[i] - to_delete.append(i) - for i in to_delete: - del self._parameters[i] + 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__()) @@ -690,7 +769,7 @@ def build(self, circuit, ground=0): message = ' '.join([str(x) for x in (self._prefix, self._name, nodes, self._parameters, self._dict_parameters)]) self._logger.debug(message) - factory(self._name, *args, **kwargs) + return factory(self._name, *args, **kwargs) #################################################################################################### @@ -905,23 +984,8 @@ def get_kwarg(text): ############################################## - def split_line(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. - - """ - - # Fixme: cf. get_kwarg - - parameters = [] - dict_parameters = {} - - text = self.right_of(keyword) - + @staticmethod + def _partition(text): parts = [] for part in text.split(): if '=' in part and part != '=': @@ -932,6 +996,36 @@ def split_line(self, keyword): 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) @@ -946,6 +1040,65 @@ def split_line(self, keyword): 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: @@ -971,8 +1124,8 @@ 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 f: - raw_lines = f.readlines() + with open(str(path), 'rb') as f: + raw_lines = [line.decode('utf-8') for line in f] elif source is not None: raw_lines = source.split(os.linesep) else: @@ -999,7 +1152,7 @@ def _merge_lines(self, raw_lines): if line_string.startswith('+'): current_line.append(line_string[1:].strip('\r\n')) else: - line_string = line_string.strip('\r\n') + 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) @@ -1012,6 +1165,51 @@ def _merge_lines(self, raw_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. """ @@ -1054,12 +1252,15 @@ def _parse(self, lines): model = Model(line) scope.appendModel(model) elif lower_case_text.startswith('include'): - scope.append(Include(line)) + include = Include(line) + scope.append(include) + elif lower_case_text.startswith('param'): + param = Param(line) + scope.appendParam(param) else: # options param ... # .global # .lib filename libname - # .param # .func .csparam .temp .if # { expr } are allowed in .model lines and in device lines. self._logger.warn(line) @@ -1067,9 +1268,16 @@ def _parse(self, lines): 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 ############################################## From 989969be89ffcd2ccf5892906f4a1187b494f44f Mon Sep 17 00:00:00 2001 From: "Jose M. Gomez" Date: Wed, 10 Jul 2019 10:18:21 +0200 Subject: [PATCH 036/134] Update test_SpiceParser.py --- unit-test/Unit/test_SpiceParser.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/unit-test/Unit/test_SpiceParser.py b/unit-test/Unit/test_SpiceParser.py index 4988e568c..d58742348 100644 --- a/unit-test/Unit/test_SpiceParser.py +++ b/unit-test/Unit/test_SpiceParser.py @@ -7,7 +7,10 @@ class TestSpiceParser(unittest.TestCase): def test_subcircuit(self): print(os.getcwd()) circuit = Circuit('Diode Characteristic Curve') - circuit.include(os.path.join(os.getcwd(), 'ucc27211.lib')) + circuit.include(os.path.join(os.getcwd(), '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) + print(circuit) self.assertEqual(True, False) From 12a2ea2ad88ff42541e61d0dfe9c0d421e94494d Mon Sep 17 00:00:00 2001 From: jmgc Date: Thu, 2 Apr 2020 21:35:15 +0200 Subject: [PATCH 037/134] Removed SchemDraw dependency. --- PySpice/Spice/BasicElement.py | 38 +----- PySpice/Spice/Netlist.py | 71 +++-------- unit-test/Unit/HSOP77case.net | 191 +++++++++++++++++++++++++++++ unit-test/Unit/test_SpiceParser.py | 29 +++++ 4 files changed, 240 insertions(+), 89 deletions(-) create mode 100644 unit-test/Unit/HSOP77case.net diff --git a/PySpice/Spice/BasicElement.py b/PySpice/Spice/BasicElement.py index 98f399968..eca087edd 100644 --- a/PySpice/Spice/BasicElement.py +++ b/PySpice/Spice/BasicElement.py @@ -99,7 +99,7 @@ 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, +from .Netlist import (Element, AnyPinElement, FixedPinElement, NPinElement, OptionalPin, Pin, PinDefinition) from .ElementParameter import ( # KeyValueParameter, @@ -117,8 +117,6 @@ ModelPositionalParameter, ) -import SchemDraw as schem - #################################################################################################### class DipoleElement(FixedPinElement): @@ -158,7 +156,6 @@ class SubCircuitElement(NPinElement): def __init__(self, netlist, name, subcircuit_name, *nodes, **kwargs): - schematic_kwargs = kwargs.pop('schematic', {}) # Fixme: match parameters to subcircuit self.parameters = kwargs self.parent = netlist @@ -179,8 +176,7 @@ def __init__(self, netlist, name, subcircuit_name, *nodes, **kwargs): except: raise ValueError() - super().__init__(netlist, name, subcircuit_name, - schematic=schematic_kwargs) + super().__init__(netlist, name, subcircuit_name) ############################################## @@ -197,7 +193,7 @@ def format_spice_parameters(self): spice_parameters = super().format_spice_parameters() if self.parameters: - spice_parameters += ' ' + join_dict(self.parameters) + spice_parameters += ' params: ' + join_dict(self.parameters) return spice_parameters @@ -254,8 +250,6 @@ class Resistor(DipoleElement): __alias__ = 'R' _prefix_ = 'R' - - schematic = schem.elements.RES resistance = FloatPositionalParameter(position=-1, key_parameter=False, unit=U_Ω) model = ModelPositionalParameter(position=0, key_parameter=True) @@ -330,8 +324,6 @@ class SemiconductorResistor(DipoleElement): __alias__ = 'SemiconductorResistor' _prefix_ = 'R' - schematic = schem.elements.RES - resistance = FloatPositionalParameter(position=-1, key_parameter=False, unit=U_Ω) model = ModelPositionalParameter(position=0, key_parameter=True) length = FloatKeyParameter('l', unit=U_m) @@ -377,8 +369,6 @@ class BehavioralResistor(DipoleElement): __alias__ = 'BehavioralResistor' _prefix_ = 'R' - schematic = schem.elements.RES - resistance_expression = ExpressionPositionalParameter(position=0, key_parameter=False) tc1 = FloatKeyParameter('tc1') tc2 = FloatKeyParameter('tc2') @@ -434,8 +424,6 @@ class Capacitor(DipoleElement): __alias__ = 'C' _prefix_ = 'C' - schematic = schem.elements.CAP - capacitance = FloatPositionalParameter(position=-1, key_parameter=False, unit=U_F) model = ModelPositionalParameter(position=0, key_parameter=True) multiplier = IntKeyParameter('m') @@ -505,8 +493,6 @@ class SemiconductorCapacitor(DipoleElement): __alias__ = 'SemiconductorCapacitor' _prefix_ = 'C' - schematic = schem.elements.CAP - capacitance = FloatPositionalParameter(position=-1, key_parameter=False, unit=U_F) model = ModelPositionalParameter(position=0, key_parameter=True) length = FloatKeyParameter('l', unit=U_m) @@ -549,8 +535,6 @@ class BehavioralCapacitor(DipoleElement): __alias__ = 'BehavioralCapacitor' _prefix_ = 'C' - schematic = schem.elements.CAP - capacitance_expression = ExpressionPositionalParameter(position=0, key_parameter=False) tc1 = FloatKeyParameter('tc1') tc2 = FloatKeyParameter('tc2') @@ -608,8 +592,6 @@ class Inductor(DipoleElement): __alias__ = 'L' _prefix_ = 'L' - schematic = schem.elements.INDUCTOR2 - inductance = FloatPositionalParameter(position=-1, key_parameter=False, unit=U_H) model = ModelPositionalParameter(position=0, key_parameter=True) nt = FloatKeyParameter('nt') @@ -651,8 +633,6 @@ class BehavioralInductor(DipoleElement): __alias__ = 'BehavioralInductor' _prefix_ = 'L' - schematic = schem.elements.INDUCTOR2 - inductance_expression = ExpressionPositionalParameter(position=0, key_parameter=False) tc1 = FloatKeyParameter('tc1') tc2 = FloatKeyParameter('tc2') @@ -797,8 +777,6 @@ class VoltageSource(DipoleElement): __alias__ = 'V' _prefix_ = 'V' - - schematic = schem.elements.SOURCE_V # Fixme: ngspice manual doesn't describe well the syntax dc_value = FloatPositionalParameter(position=0, key_parameter=False, unit=U_V) @@ -835,8 +813,6 @@ class CurrentSource(DipoleElement): __alias__ = 'I' _prefix_ = 'I' - schematic = schem.elements.SOURCE_I - # Fixme: ngspice manual doesn't describe well the syntax dc_value = FloatPositionalParameter(position=0, key_parameter=False, unit=U_A) @@ -1009,8 +985,6 @@ class BehavioralSource(DipoleElement): __alias__ = 'B' _prefix_ = 'B' - schematic = schem.elements.SOURCE - current_expression = ExpressionKeyParameter('i') voltage_expression = ExpressionKeyParameter('v') tc1 = FloatKeyParameter('tc1') @@ -1075,8 +1049,6 @@ class NonLinearVoltageSource(DipoleElement): table = ExpressionKeyParameter('table') smoothbsrc = ExpressionKeyParameter('smoothbsrc') - schematic = schem.elements.SOURCE_V - ############################################## def __init__(self, name, *args, **kwargs): @@ -1128,8 +1100,6 @@ class NonLinearCurrentSource(DipoleElement): __alias__ = 'NonLinearCurrentSource' _prefix_ = 'G' - schematic = schem.elements.SOURCE_I - transconductance = ExpressionPositionalParameter(position=0, key_parameter=False) #################################################################################################### @@ -1193,8 +1163,6 @@ class Diode(FixedPinElement): _prefix_ = 'D' _pins_ = (('cathode', 'plus'), ('anode', 'minus')) - schematic = schem.elements.DIODE_F - model = ModelPositionalParameter(position=0, key_parameter=True) area = FloatKeyParameter('area') multiplier = IntKeyParameter('m') diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index 0db0b549f..360a74afb 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -83,7 +83,6 @@ def __init__(self, **kwargs): import os import networkx -import SchemDraw #################################################################################################### @@ -489,10 +488,6 @@ def parameters_from_args(self): @property def spice_to_parameters(self): return self._spice_to_parameters_ - -# @property -# def schematic(self): -# return self.__schematic__ #################################################################################################### @@ -514,8 +509,6 @@ class Element(metaclass=ElementParameterMetaClass): #: SPICE element prefix _prefix_ = None - - schematic = None ############################################## @@ -551,10 +544,8 @@ def __init__(self, netlist, name, *args, **kwargs): if key.lower() == self._optional_parameters_[parameter].spice_name.lower(): setattr(self, parameter, value) break - schematic = kwargs.pop('schematic', {}) - self._schematic = schematic - netlist._add_element(self, **schematic) + netlist._add_element(self) ############################################## @@ -622,7 +613,7 @@ def __setattr__(self, name, value): ############################################## - def __getattr__(self, name): + def __getattr__(self, name): # Implement alias for parameters if name in self._spice_to_parameters_: @@ -728,13 +719,13 @@ 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)) - + 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) + + + super().__init__(netlist, name, *args, **kwargs) ############################################## @@ -865,7 +856,6 @@ def __init__(self): self._ground_name = 0 self._nodes = {} self._graph = networkx.Graph() - self._schematic = SchemDraw.Drawing() self._ground_node = self._add_node(self._ground_name) self._subcircuits = OrderedDict() # to keep the declaration order @@ -932,14 +922,10 @@ def subcircuits(self): @property def subcircuit_names(self): return self._subcircuits.keys() - + @property def graph(self): return self._graph - - @property - def schematic(self): - return self._schematic ############################################## @@ -982,9 +968,7 @@ def _add_node(self, node_name): node = Node(self, node_name) self._nodes[node_name] = node self._graph.add_node(node, name=node_name) - - #if(node_name == str(self._ground_name)): - # self.schematic.add(SchemDraw.elements.GND) + return node else: raise ValueError("Node {} is already defined".format(node_name)) @@ -1023,7 +1007,7 @@ def has_ground_node(self): ############################################## - def _add_element(self, element, **schematic_kwargs): + def _add_element(self, element): """Add an element.""" if element.name not in self._elements: @@ -1041,34 +1025,6 @@ def _add_element(self, element, **schematic_kwargs): if len(element.nodes) == 2: self.graph.add_edge(element.nodes[0], element.nodes[1], x=element, name=element.name) - - #print(schematic_kwargs) - schematic = schematic_kwargs.pop('schematic', element.schematic) - if(schematic): - element.schematic = schematic - #label = schematic_kwargs.pop('label', element.name) - schematic_element = self.schematic.add(schematic, - **schematic_kwargs) - element.schematic_element = schematic_element - - show_start = schematic_kwargs.pop('show_start', False) - show_end = schematic_kwargs.pop('show_end', False) - - if(show_start): - start_label = schematic_kwargs.pop('start_label',{}) - - self.schematic.add(SchemDraw.elements.DOT_OPEN, - xy=schematic_element.start, - **start_label) - - if(show_end): - end_label = schematic_kwargs.pop('end_label',{}) - self.schematic.add(SchemDraw.elements.DOT_OPEN, - xy=schematic_element.end, - **end_label) - #if(element.pins[1].node == self.get_node(0, False)): - # self.schematic.add(SchemDraw.elements.GND) - else: raise NameError("Element name {} is already defined".format(element.name)) @@ -1346,6 +1302,12 @@ def __init__(self, title, ############################################## + @property + def name(self): + return self.title + + ############################################## + def clone(self, title=None): if title is None: @@ -1427,7 +1389,8 @@ def _str_globals(self): def _str_parameters(self): if self._parameters: - return join_lines(self._parameters, prefix='.param ') + os.linesep + return join_lines([key + ("" if value is None else " = " + str(value)) + for key, value in self._parameters.items()], prefix='.param ') + os.linesep else: return '' diff --git a/unit-test/Unit/HSOP77case.net b/unit-test/Unit/HSOP77case.net new file mode 100644 index 000000000..ab2bebd14 --- /dev/null +++ b/unit-test/Unit/HSOP77case.net @@ -0,0 +1,191 @@ +.title HSOP77case + +* OP77 SPICE Macro-model +* Description: Amplifier +* Generic Desc: 6/30V, BIP, OP, Low Vos, Precision, 1X +* Developed by: JCB / PMI +* Revision History: 08/10/2012 - Updated to new header style +* 2.0 (12/1990) - Re-ordered subcircuit call out nodes to put the output node last. +* - Changed Ios from 0.3E-9 to 0.15E-9 +* - Added F1 and F2 to fix short circuit current limit. +* Copyright 1990, 2012 by Analog Devices, Inc. +* +* Refer to http://www.analog.com/Analog_Root/static/techSupport/designTools/spiceModels/license/spice_general.html for License Statement. +* Use of this model +* indicates your acceptance with the terms and provisions in the License Statement. +* +* BEGIN Notes: +* +* Not Modeled: +* +* Parameters modeled include: +* +* END Notes +* +* Node assignments +* non-inverting input +* | inverting input +* | | positive supply +* | | | negative supply +* | | | | output +* | | | | | +.SUBCKT OP77 1 2 99 50 39 +*#ASSOC Category="Op-amps" symbol=opamp +* +* MODELS USED +* +.MODEL QX NPN(BF=417E6) +.MODEL DX D(IS=1E-15) +.MODEL DY D(IS=1E-15 BV=50) +.MODEL DEN D(IS=1E-12 RS=12.08K KF=1E-17 AF=1) +.MODEL DIN D(IS=1E-12 RS=7.55E-6 KF=1.55E-15 AF=1) +* +* INPUT STAGE & POLE AT 6 MHZ +* +R1 2 3 5E11 +R2 1 3 5E11 +R3 5 97 0.0606 +R4 6 97 0.0606 +CIN 1 2 4E-12 +C2 5 6 218.9E-9 +I1 4 51 1 +IOS 1 2 0.15E-9 +BOS 9 10 V={10E-6 + V(30, 33)} +Q1 5 2 7 QX +Q2 6 9 8 QX +R5 7 4 0.009 +R6 8 4 0.009 +D1 2 1 DX +D2 1 2 DX +EN 10 1 12 0 1 +GN1 0 2 15 0 1 +GN2 0 1 18 0 1 +* +EREF 98 0 33 0 1 +EPLUS 97 0 99 0 1 +ENEG 51 0 50 0 1 +* +* VOLTAGE NOISE SOURCE WITH FLICKER NOISE +* +DN1 11 12 DEN +DN2 12 13 DEN +VN1 11 0 DC 2 +VN2 0 13 DC 2 +* +* CURRENT NOISE SOURCE WITH FLICKER NOISE +* +DN3 14 15 DIN +DN4 15 16 DIN +VN3 14 0 DC 2 +VN4 0 16 DC 2 +* +* SECOND CURRENT NOISE SOURCE +* +DN5 17 18 DIN +DN6 18 19 DIN +VN5 17 0 DC 2 +VN6 0 19 DC 2 +* +* FIRST GAIN STAGE +* +R7 20 98 1 +G1 98 20 5 6 59.91 +D3 20 21 DX +D4 22 20 DX +B1 97 21 V={-2.4 + V(97, 33)} +B2 22 51 V={-2.4 + V(33, 51)} +* +* GAIN STAGE & DOMINANT POLE AT 0.053 HZ +* +R8 23 98 6.01E9 +C3 23 98 500E-12 +G2 98 23 20 33 33.3E-6 +V1 97 24 1.3 +V2 25 51 1.3 +D5 23 24 DX +D6 25 23 DX +* +* NEGATIVE ZERO AT -4MHZ +* +R9 26 27 1 +C4 26 27 -39.75E-9 +R10 27 98 1E-6 +E3 26 98 23 33 1E6 +* +* COMMON-MODE GAIN NETWORK WITH ZERO AT 20 HZ +* +R13 30 31 1 +L2 31 98 7.96E-3 +G4 98 30 3 33 1.0E-7 +D7 30 97 DX +D8 51 30 DX +* +* POLE AT 2 MHZ +* +R14 32 98 1 +C5 32 98 79.5E-9 +G5 98 32 27 33 1 +* +* OUTPUT STAGE +* +R15 33 97 1 +R16 33 51 1 +BSY 99 50 I={0.325E-3 + 0.0425E-3*V(99, 50)} +F1 34 0 V3 1 +F2 0 34 V4 1 +R17 34 99 400 +R18 34 50 400 +L3 34 39 2E-7 +G6 37 50 32 34 2.5E-3 +G7 38 50 34 32 2.5E-3 +G8 34 99 99 32 2.5E-3 +G9 50 34 32 50 2.5E-3 +V3 35 34 6.8 +V4 34 36 4.4 +D9 32 35 DX +D10 36 32 DX +D11 99 37 DX +D12 99 38 DX +D13 50 37 DY +D14 50 38 DY +.ENDS OP77 + +.SUBCKT loop_probe inp out params: lpvar=0 +Iinj 0 probe AC {0.5*lpvar*(lpvar-1)} +Vinj probe inp AC {0.5*lpvar*(lpvar+1)} +Vprobe probe out 0 +.ENDS loop_probe + +.model 2N2222 NPN(IS=1E-14 VAF=100 ++ BF=200 IKF=0.3 XTB=1.5 BR=3 ++ CJC=8E-12 CJE=25E-12 TR=100E-9 TF=400E-12 ++ ITF=1 VTF=2 XTF=3 RB=10 RC=.3 RE=.2) + +XU1 Ninp Ninn Np15 Nm15 Ninplp OP77 +XU2 No Nrep Np15 Nm15 Nrep OP77 +R1 Ninn 0 10k +R2 Ne Ninn 10k +C2 Ne Ninn 100n +R3 Nin Ninp 10k +R4 Nrep Ninp 10k +C4 Nrep Ninp 100n +R5 Ne No 200 +Vi Nin 0 {vin} AC {1-prb*prb} +V2p15 Np15 0 15 +Vm15 Nm15 0 -15 +Q1 Nc Nb Ne 2N2222 +R6 Np15 Nc 50 +R7 Noutlp Nb 10 +D1 Ngl No YELLOW +Vl Ngl 0 0 +XXlp Ninlp Noutlp loop_probe params: lpvar=prb +.model YELLOW D(IS=93.1P RS=42M N=4.61 BV=2 IBV=10U ++ CJO=2.97P VJ=.75 M=.333 TT=4.32U) + +*.lib /Users/chema/Library/Application Support/LTspice/lib/cmp/standard.dio +.model NPN NPN +.model PNP PNP +.param prb=0 +.param vin=2.5 + +.end diff --git a/unit-test/Unit/test_SpiceParser.py b/unit-test/Unit/test_SpiceParser.py index d58742348..7e6ce9626 100644 --- a/unit-test/Unit/test_SpiceParser.py +++ b/unit-test/Unit/test_SpiceParser.py @@ -1,9 +1,38 @@ import unittest from PySpice.Spice.Netlist import Circuit +from PySpice.Spice.Parser import SpiceParser +from multiprocessing import Pool, cpu_count import os +def multiple_sim(simulate, values): + cpus = cpu_count() - 1 + if cpus == 0: + cpus = 1 + pools = min(cpus, len(values)) + with Pool(pools) as p: + analysis = p.map(simulate, values) + return list(analysis) + +def circuit_gft(prb): + circuit_file = SpiceParser('HSOP77case.net') + circuit = circuit_file.build_circuit() + circuit.parameter('prb', str(prb)) + simulator = circuit.simulator(simulator='xyce-serial') + simulator.save(['all']) + return simulator.ac(start_frequency=10 - 2, + stop_frequency=1e9, + number_of_points=10, + variation='dec') + + class TestSpiceParser(unittest.TestCase): + def test_parser(self): + results = multiple_sim(circuit_gft, [-1, 1]) + result = results[0] + values = result.nodes['x'] + print(repr(values)) + def test_subcircuit(self): print(os.getcwd()) circuit = Circuit('Diode Characteristic Curve') From 5a4db84c3efe126e9e0c73b13f3cb56ecbdb71f0 Mon Sep 17 00:00:00 2001 From: jmgc Date: Thu, 2 Apr 2020 21:36:04 +0200 Subject: [PATCH 038/134] Added the name property to CircuitStatement. --- PySpice/Spice/Parser.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/PySpice/Spice/Parser.py b/PySpice/Spice/Parser.py index 65417d9ba..915a4d609 100644 --- a/PySpice/Spice/Parser.py +++ b/PySpice/Spice/Parser.py @@ -393,6 +393,11 @@ 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. """ From c449fdaaa50a89f40722ea7ac7193a5e4ac9b106 Mon Sep 17 00:00:00 2001 From: jmgc Date: Thu, 2 Apr 2020 21:44:39 +0200 Subject: [PATCH 039/134] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ce04b90be..5a41f15bc 100644 --- a/.gitignore +++ b/.gitignore @@ -98,3 +98,4 @@ tools/upload-www trash/ unit-test/test.py +.idea From 2719e2acb580b5d2dbf3ae4ad57d87752e165485 Mon Sep 17 00:00:00 2001 From: jmgc Date: Thu, 2 Apr 2020 22:58:39 +0200 Subject: [PATCH 040/134] Modifications to correct minor issues with the update of versions. --- PySpice/Probe/WaveForm.py | 38 ++++++++++++++++++------------ PySpice/Spice/Netlist.py | 4 ++-- unit-test/Unit/test_SpiceParser.py | 6 ++++- 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/PySpice/Probe/WaveForm.py b/PySpice/Probe/WaveForm.py index 0291ae1df..1f944dba0 100644 --- a/PySpice/Probe/WaveForm.py +++ b/PySpice/Probe/WaveForm.py @@ -62,11 +62,11 @@ class WaveForm(UnitValues): ############################################## - @classmethod - def from_unit_values(cls, name, array, title=None, abscissa=None): + @staticmethod + def from_unit_values(name, array, title=None, abscissa=None): shape = array.shape - obj = cls( + obj = WaveForm( name, array.prefixed_unit, array.shape, @@ -80,12 +80,12 @@ def from_unit_values(cls, name, array, title=None, abscissa=None): ############################################## - @classmethod - def from_array(cls, name, array, title=None, abscissa=None): + @staticmethod + def from_array(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 @@ -101,14 +101,19 @@ def __new__(cls, name, prefixed_unit, obj = super(WaveForm, cls).__new__(cls, prefixed_unit, shape, dtype, buffer, offset, strides, order) # obj = np.asarray(data).view(cls) - obj._name = str(name) - obj._title = title # str(title) - obj._abscissa = abscissa - return obj ############################################## + def __init__(self, name, prefixed_unit, + shape, dtype=float, buffer=None, offset=0, strides=None, order=None, + title=None, abscissa=None): + self._name = str(name) + self._title = title # str(title) + self._abscissa = abscissa + + ############################################## + def __array_finalize__(self, obj): # self._logger.info('\n {}'.format(obj)) @@ -137,7 +142,7 @@ def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): if len(result.shape) == 0: return UnitValue(result.prefixed_unit, result) else: - return self.from_unit_values(name='', array=result, title='', abscissa=self._abscissa) + return self.__class__.from_unit_values(name='', array=result, title='', abscissa=self._abscissa) else: return result # e.g. foo <= 0 @@ -145,7 +150,10 @@ def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): @property def name(self): - return self._name + if hasattr(self, '_name'): + return self._name + else: + return '' @property def abscissa(self): @@ -163,16 +171,16 @@ def title(self, value): def __repr__(self): - return '{0.__class__.__name__} {0._name} {1}'.format(self, super().__str__()) + return '{0.__class__.__name__} {0.name} {1}'.format(self, super().__str__()) ############################################## def __str__(self): - if self._title is not None: + if hasattr(self, '_title') and self._title is not None: return self._title else: - return self._name + return self.name ############################################## diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index a7566a64e..e73b3d858 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -535,9 +535,9 @@ def __init__(self, netlist, name, *args, **kwargs): if key == 'raw_spice': self.raw_spice = value else: - if key in self._positional_parameters_ or + if (key in self._positional_parameters_ or key in self._optional_parameters_ or - key in self._spice_to_parameters_: + key in self._spice_to_parameters_): setattr(self, key, value) else: for parameter in self._optional_parameters_: diff --git a/unit-test/Unit/test_SpiceParser.py b/unit-test/Unit/test_SpiceParser.py index 7e6ce9626..f95513544 100644 --- a/unit-test/Unit/test_SpiceParser.py +++ b/unit-test/Unit/test_SpiceParser.py @@ -28,9 +28,13 @@ def circuit_gft(prb): class TestSpiceParser(unittest.TestCase): def test_parser(self): + results = list(map(circuit_gft, [-1, 1])) + result = results[0] + values = result.nodes['Ninp'] + print(repr(values)) results = multiple_sim(circuit_gft, [-1, 1]) result = results[0] - values = result.nodes['x'] + values = result.nodes['Ninp'] print(repr(values)) def test_subcircuit(self): From 5162ec5e89f695eea9afe7bc66edb60dd914becd Mon Sep 17 00:00:00 2001 From: jmgc Date: Sat, 4 Apr 2020 19:30:38 +0200 Subject: [PATCH 041/134] Added the condition to avoid adding a .ic if there is no initial condition. Check improved to take that into account. --- PySpice/Spice/Simulation.py | 2 +- unit-test/Unit/test_SpiceParser.py | 23 ++++++----------------- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/PySpice/Spice/Simulation.py b/PySpice/Spice/Simulation.py index 15ab2d0e7..513c0499c 100644 --- a/PySpice/Spice/Simulation.py +++ b/PySpice/Spice/Simulation.py @@ -635,7 +635,7 @@ def __str__(self): netlist = self._circuit.str(simulator=self.SIMULATOR) netlist += self.str_options() - if self.initial_condition: + if self.initial_condition and len(self._initial_condition) > 0: netlist += '.ic ' + join_dict(self._initial_condition) + os.linesep if self._saved_nodes: # Place 'all' first diff --git a/unit-test/Unit/test_SpiceParser.py b/unit-test/Unit/test_SpiceParser.py index f95513544..321b50c23 100644 --- a/unit-test/Unit/test_SpiceParser.py +++ b/unit-test/Unit/test_SpiceParser.py @@ -5,15 +5,6 @@ import os -def multiple_sim(simulate, values): - cpus = cpu_count() - 1 - if cpus == 0: - cpus = 1 - pools = min(cpus, len(values)) - with Pool(pools) as p: - analysis = p.map(simulate, values) - return list(analysis) - def circuit_gft(prb): circuit_file = SpiceParser('HSOP77case.net') circuit = circuit_file.build_circuit() @@ -23,19 +14,17 @@ def circuit_gft(prb): return simulator.ac(start_frequency=10 - 2, stop_frequency=1e9, number_of_points=10, - variation='dec') + variation='dec'), simulator class TestSpiceParser(unittest.TestCase): def test_parser(self): results = list(map(circuit_gft, [-1, 1])) - result = results[0] - values = result.nodes['Ninp'] - print(repr(values)) - results = multiple_sim(circuit_gft, [-1, 1]) - result = results[0] - values = result.nodes['Ninp'] - print(repr(values)) + self.assertEqual(len(results), 2) + self.assertIn('Ninp', results[0][0].nodes) + circuit = results[0][1] + values = str(circuit) + self.assertNotRegex(values, r'(\.ic)') def test_subcircuit(self): print(os.getcwd()) From 528d9057debf189561369774aef1fab94d54024b Mon Sep 17 00:00:00 2001 From: jmgc Date: Fri, 24 Apr 2020 12:29:55 +0200 Subject: [PATCH 042/134] Improved parser to solve issues with the EFGH sources. --- PySpice/Spice/Parser.py | 199 +++++++++++---- PySpice/Tools/StringTools.py | 1 + unit-test/Unit/HSOP77case.net | 191 -------------- unit-test/Unit/test_SpiceParser.py | 384 ++++++++++++++++++++++++++++- 4 files changed, 529 insertions(+), 246 deletions(-) delete mode 100644 unit-test/Unit/HSOP77case.net diff --git a/PySpice/Spice/Parser.py b/PySpice/Spice/Parser.py index 332ab065a..b4b24ee76 100644 --- a/PySpice/Spice/Parser.py +++ b/PySpice/Spice/Parser.py @@ -44,15 +44,16 @@ _module_logger = logging.getLogger(__name__) + #################################################################################################### class ParseError(NameError): pass + #################################################################################################### class PrefixData: - """This class represents a device prefix.""" ############################################## @@ -76,7 +77,7 @@ def __init__(self, prefix, classes): 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 + 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: @@ -108,6 +109,7 @@ def single(self): else: raise NameError() + #################################################################################################### _prefix_cache = {} @@ -116,6 +118,7 @@ def single(self): _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), @@ -152,7 +155,6 @@ def single(self): #################################################################################################### class Statement: - """ This class implements a statement, in fact a line in a Spice netlist. """ ############################################## @@ -198,21 +200,21 @@ def kwargs_to_python(self, kwargs): 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') @@ -226,16 +228,15 @@ def __str__(self): 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') @@ -252,13 +253,12 @@ def __repr__(self): ############################################## 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:: @@ -270,7 +270,6 @@ class Model(Statement): ############################################## def __init__(self, line): - super().__init__(line, statement='model') base, self._parameters = line.split_keyword('.model') @@ -301,10 +300,10 @@ def to_python(self, netlist_name): def build(self, circuit): return circuit.model(self._name, self._model_type, **self._parameters) + #################################################################################################### class Param(Statement): - """ This class implements a model definition. Spice syntax:: @@ -316,13 +315,12 @@ class Param(Statement): ############################################## def __init__(self, line): - super().__init__(line, statement='param') text = line.right_of('.param').strip().lower() idx = text.find('=') self._name = text[:idx].strip() - self._value = text[idx+1:].strip() + self._value = text[idx + 1:].strip() ############################################## @@ -334,26 +332,23 @@ def name(self): ############################################## 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)) return '{}.param({})'.format(netlist_name, self.join_args(args)) + os.linesep ############################################## def build(self, circuit): - circuit.parameter(self._name, self._value) + #################################################################################################### class CircuitStatement(Statement): - """ This class implements a circuit definition. Spice syntax:: @@ -478,10 +473,10 @@ def build(self, ground=0): statement.build(circuit, ground) return circuit + #################################################################################################### class SubCircuitStatement(Statement): - """ This class implements a sub-circuit definition. Spice syntax:: @@ -593,7 +588,7 @@ def to_python(self, ground=0): ############################################## - def build(self, ground=0, parent = None): + def build(self, ground=0, parent=None): subcircuit = SubCircuit(self._name, *self._nodes, **self._parameters) subcircuit.parent = parent for statement in self._params: @@ -608,10 +603,10 @@ def build(self, ground=0, parent = None): statement.build(subcircuit, ground) return subcircuit + #################################################################################################### class Element(Statement): - """ This class implements an element definition. "{ expression }" are allowed in device line. @@ -651,12 +646,12 @@ def __init__(self, line): if number_of_pins: self._nodes = args[:number_of_pins] args = args[number_of_pins:] - else: # Q or X + else: # Q or X if prefix_data.prefix == 'Q': self._nodes = args[:3] args = args[3:] # Fixme: optional node - else: # X + else: # X if args[-1].lower() == 'params:': args.pop() self._parameters.append(args.pop()) @@ -665,7 +660,7 @@ def __init__(self, line): # 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 + 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): @@ -736,7 +731,7 @@ def to_python(self, netlist_name, ground=0): args = [self._name] if self._prefix != 'X': args += nodes + self._parameters - else: # != Spice + else: # != Spice args += self._parameters + nodes args = self.values_to_python(args) kwargs = self.kwargs_to_python(self._dict_parameters) @@ -744,24 +739,143 @@ def to_python(self, netlist_name, ground=0): ############################################## + 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): - factory = getattr(circuit, self.factory.__alias__) 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 + 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, nodes, - self._parameters, 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('Element') @@ -875,7 +989,7 @@ def read_words(self, start_location, number_of_words): line_str = self._text number_of_words_read = 0 - while number_of_words_read < number_of_words: # and start_location < len(line_str) + 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: @@ -883,23 +997,23 @@ def read_words(self, start_location, number_of_words): else: stop_location = line_str.find(' ', start_location) if stop_location == -1: - stop_location = None # read until end + 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 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 + '^') + ' ' * 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 + else: # we have read a space start_location += 1 return words, stop_location @@ -932,7 +1046,7 @@ def split_words(self, start_location, until=None): expression += word.count('{') - word.count('}') if expression == 0: if begin_idx < idx: - result.append(' '.join(words[begin_idx:idx+1])) + result.append(' '.join(words[begin_idx:idx + 1])) else: result.append(word) return result, stop_location @@ -972,7 +1086,8 @@ def get_kwarg(text): @staticmethod def _partition(text): parts = [] - for part in text.split(): + values = text.replace(',', ' ') + for part in values.split(): if '=' in part and part != '=': left, right = [x for x in part.split('=')] parts.append(left) @@ -1084,10 +1199,10 @@ def split_element(self, prefix): return Line._check_parameters(parts) + #################################################################################################### class SpiceParser: - """ This class parse a Spice netlist file and build a syntax tree. Public Attributes: @@ -1139,7 +1254,7 @@ def _merge_lines(self, raw_lines): else: line_string = line_string.strip(' \t\r\n') if line_string: - _slice = slice(line_index, line_index +1) + _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 @@ -1215,7 +1330,7 @@ def _parse(self, lines): for line in lines[1:]: # print('>', repr(line)) text = str(line) - lower_case_text = text.lower() # ! + lower_case_text = text.lower() # ! if line.is_comment: scope.append(Comment(line)) elif lower_case_text.startswith('.'): @@ -1307,7 +1422,7 @@ def _build_circuit(circuit, statements, ground): elif isinstance(statement, Model): statement.build(circuit) elif isinstance(statement, SubCircuit): - subcircuit = statement.build(ground) # Fixme: ok ??? + subcircuit = statement.build(ground) # Fixme: ok ??? circuit.subcircuit(subcircuit) ############################################## @@ -1320,8 +1435,8 @@ def build_circuit(self, ground=0): """ - #circuit = Circuit(str(self._title)) - circuit = self.circuit.build(ground) + # circuit = Circuit(str(self._title)) + circuit = self.circuit.build(str(ground)) return circuit ############################################## diff --git a/PySpice/Tools/StringTools.py b/PySpice/Tools/StringTools.py index 7920e08bb..e4569b6a6 100644 --- a/PySpice/Tools/StringTools.py +++ b/PySpice/Tools/StringTools.py @@ -67,3 +67,4 @@ def join_dict(d): 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/unit-test/Unit/HSOP77case.net b/unit-test/Unit/HSOP77case.net deleted file mode 100644 index ab2bebd14..000000000 --- a/unit-test/Unit/HSOP77case.net +++ /dev/null @@ -1,191 +0,0 @@ -.title HSOP77case - -* OP77 SPICE Macro-model -* Description: Amplifier -* Generic Desc: 6/30V, BIP, OP, Low Vos, Precision, 1X -* Developed by: JCB / PMI -* Revision History: 08/10/2012 - Updated to new header style -* 2.0 (12/1990) - Re-ordered subcircuit call out nodes to put the output node last. -* - Changed Ios from 0.3E-9 to 0.15E-9 -* - Added F1 and F2 to fix short circuit current limit. -* Copyright 1990, 2012 by Analog Devices, Inc. -* -* Refer to http://www.analog.com/Analog_Root/static/techSupport/designTools/spiceModels/license/spice_general.html for License Statement. -* Use of this model -* indicates your acceptance with the terms and provisions in the License Statement. -* -* BEGIN Notes: -* -* Not Modeled: -* -* Parameters modeled include: -* -* END Notes -* -* Node assignments -* non-inverting input -* | inverting input -* | | positive supply -* | | | negative supply -* | | | | output -* | | | | | -.SUBCKT OP77 1 2 99 50 39 -*#ASSOC Category="Op-amps" symbol=opamp -* -* MODELS USED -* -.MODEL QX NPN(BF=417E6) -.MODEL DX D(IS=1E-15) -.MODEL DY D(IS=1E-15 BV=50) -.MODEL DEN D(IS=1E-12 RS=12.08K KF=1E-17 AF=1) -.MODEL DIN D(IS=1E-12 RS=7.55E-6 KF=1.55E-15 AF=1) -* -* INPUT STAGE & POLE AT 6 MHZ -* -R1 2 3 5E11 -R2 1 3 5E11 -R3 5 97 0.0606 -R4 6 97 0.0606 -CIN 1 2 4E-12 -C2 5 6 218.9E-9 -I1 4 51 1 -IOS 1 2 0.15E-9 -BOS 9 10 V={10E-6 + V(30, 33)} -Q1 5 2 7 QX -Q2 6 9 8 QX -R5 7 4 0.009 -R6 8 4 0.009 -D1 2 1 DX -D2 1 2 DX -EN 10 1 12 0 1 -GN1 0 2 15 0 1 -GN2 0 1 18 0 1 -* -EREF 98 0 33 0 1 -EPLUS 97 0 99 0 1 -ENEG 51 0 50 0 1 -* -* VOLTAGE NOISE SOURCE WITH FLICKER NOISE -* -DN1 11 12 DEN -DN2 12 13 DEN -VN1 11 0 DC 2 -VN2 0 13 DC 2 -* -* CURRENT NOISE SOURCE WITH FLICKER NOISE -* -DN3 14 15 DIN -DN4 15 16 DIN -VN3 14 0 DC 2 -VN4 0 16 DC 2 -* -* SECOND CURRENT NOISE SOURCE -* -DN5 17 18 DIN -DN6 18 19 DIN -VN5 17 0 DC 2 -VN6 0 19 DC 2 -* -* FIRST GAIN STAGE -* -R7 20 98 1 -G1 98 20 5 6 59.91 -D3 20 21 DX -D4 22 20 DX -B1 97 21 V={-2.4 + V(97, 33)} -B2 22 51 V={-2.4 + V(33, 51)} -* -* GAIN STAGE & DOMINANT POLE AT 0.053 HZ -* -R8 23 98 6.01E9 -C3 23 98 500E-12 -G2 98 23 20 33 33.3E-6 -V1 97 24 1.3 -V2 25 51 1.3 -D5 23 24 DX -D6 25 23 DX -* -* NEGATIVE ZERO AT -4MHZ -* -R9 26 27 1 -C4 26 27 -39.75E-9 -R10 27 98 1E-6 -E3 26 98 23 33 1E6 -* -* COMMON-MODE GAIN NETWORK WITH ZERO AT 20 HZ -* -R13 30 31 1 -L2 31 98 7.96E-3 -G4 98 30 3 33 1.0E-7 -D7 30 97 DX -D8 51 30 DX -* -* POLE AT 2 MHZ -* -R14 32 98 1 -C5 32 98 79.5E-9 -G5 98 32 27 33 1 -* -* OUTPUT STAGE -* -R15 33 97 1 -R16 33 51 1 -BSY 99 50 I={0.325E-3 + 0.0425E-3*V(99, 50)} -F1 34 0 V3 1 -F2 0 34 V4 1 -R17 34 99 400 -R18 34 50 400 -L3 34 39 2E-7 -G6 37 50 32 34 2.5E-3 -G7 38 50 34 32 2.5E-3 -G8 34 99 99 32 2.5E-3 -G9 50 34 32 50 2.5E-3 -V3 35 34 6.8 -V4 34 36 4.4 -D9 32 35 DX -D10 36 32 DX -D11 99 37 DX -D12 99 38 DX -D13 50 37 DY -D14 50 38 DY -.ENDS OP77 - -.SUBCKT loop_probe inp out params: lpvar=0 -Iinj 0 probe AC {0.5*lpvar*(lpvar-1)} -Vinj probe inp AC {0.5*lpvar*(lpvar+1)} -Vprobe probe out 0 -.ENDS loop_probe - -.model 2N2222 NPN(IS=1E-14 VAF=100 -+ BF=200 IKF=0.3 XTB=1.5 BR=3 -+ CJC=8E-12 CJE=25E-12 TR=100E-9 TF=400E-12 -+ ITF=1 VTF=2 XTF=3 RB=10 RC=.3 RE=.2) - -XU1 Ninp Ninn Np15 Nm15 Ninplp OP77 -XU2 No Nrep Np15 Nm15 Nrep OP77 -R1 Ninn 0 10k -R2 Ne Ninn 10k -C2 Ne Ninn 100n -R3 Nin Ninp 10k -R4 Nrep Ninp 10k -C4 Nrep Ninp 100n -R5 Ne No 200 -Vi Nin 0 {vin} AC {1-prb*prb} -V2p15 Np15 0 15 -Vm15 Nm15 0 -15 -Q1 Nc Nb Ne 2N2222 -R6 Np15 Nc 50 -R7 Noutlp Nb 10 -D1 Ngl No YELLOW -Vl Ngl 0 0 -XXlp Ninlp Noutlp loop_probe params: lpvar=prb -.model YELLOW D(IS=93.1P RS=42M N=4.61 BV=2 IBV=10U -+ CJO=2.97P VJ=.75 M=.333 TT=4.32U) - -*.lib /Users/chema/Library/Application Support/LTspice/lib/cmp/standard.dio -.model NPN NPN -.model PNP PNP -.param prb=0 -.param vin=2.5 - -.end diff --git a/unit-test/Unit/test_SpiceParser.py b/unit-test/Unit/test_SpiceParser.py index 321b50c23..d59d803d8 100644 --- a/unit-test/Unit/test_SpiceParser.py +++ b/unit-test/Unit/test_SpiceParser.py @@ -4,27 +4,385 @@ from multiprocessing import Pool, cpu_count import os +hsop77 = """ +.title HSOP77case + +* OP77 SPICE Macro-model +* Description: Amplifier +* Generic Desc: 6/30V, BIP, OP, Low Vos, Precision, 1X +* Developed by: JCB / PMI +* Revision History: 08/10/2012 - Updated to new header style +* 2.0 (12/1990) - Re-ordered subcircuit call out nodes to put the output node last. +* - Changed Ios from 0.3E-9 to 0.15E-9 +* - Added F1 and F2 to fix short circuit current limit. +* Copyright 1990, 2012 by Analog Devices, Inc. +* +* Refer to http://www.analog.com/Analog_Root/static/techSupport/designTools/spiceModels/license/spice_general.html for License Statement. Use of this model +* indicates your acceptance with the terms and provisions in the License Statement. +* +* BEGIN Notes: +* +* Not Modeled: +* +* Parameters modeled include: +* +* END Notes +* +* Node assignments +* non-inverting input +* | inverting input +* | | positive supply +* | | | negative supply +* | | | | output +* | | | | | +.SUBCKT OP77 1 2 99 50 39 +*#ASSOC Category="Op-amps" symbol=opamp +* +* INPUT STAGE & POLE AT 6 MHZ +* +R1 2 3 5E11 +R2 1 3 5E11 +R3 5 97 0.0606 +R4 6 97 0.0606 +CIN 1 2 4E-12 +C2 5 6 218.9E-9 +I1 4 51 1 +IOS 1 2 0.15E-9 +EOS 9 10 POLY(1) 30 33 10E-6 1 +Q1 5 2 7 QX +Q2 6 9 8 QX +R5 7 4 0.009 +R6 8 4 0.009 +D1 2 1 DX +D2 1 2 DX +EN 10 1 12 0 1 +GN1 0 2 15 0 1 +GN2 0 1 18 0 1 +* +EREF 98 0 33 0 1 +EPLUS 97 0 99 0 1 +ENEG 51 0 50 0 1 +* +* VOLTAGE NOISE SOURCE WITH FLICKER NOISE +* +DN1 11 12 DEN +DN2 12 13 DEN +VN1 11 0 DC 2 +VN2 0 13 DC 2 +* +* CURRENT NOISE SOURCE WITH FLICKER NOISE +* +DN3 14 15 DIN +DN4 15 16 DIN +VN3 14 0 DC 2 +VN4 0 16 DC 2 +* +* SECOND CURRENT NOISE SOURCE +* +DN5 17 18 DIN +DN6 18 19 DIN +VN5 17 0 DC 2 +VN6 0 19 DC 2 +* +* FIRST GAIN STAGE +* +R7 20 98 1 +G1 98 20 5 6 59.91 +D3 20 21 DX +D4 22 20 DX +E1 97 21 POLY(1) 97 33 -2.4 1 +E2 22 51 POLY(1) 33 51 -2.4 1 +* +* GAIN STAGE & DOMINANT POLE AT 0.053 HZ +* +R8 23 98 6.01E9 +C3 23 98 500E-12 +G2 98 23 20 33 33.3E-6 +V1 97 24 1.3 +V2 25 51 1.3 +D5 23 24 DX +D6 25 23 DX +* +* NEGATIVE ZERO AT -4MHZ +* +R9 26 27 1 +C4 26 27 -39.75E-9 +R10 27 98 1E-6 +E3 26 98 23 33 1E6 +* +* COMMON-MODE GAIN NETWORK WITH ZERO AT 20 HZ +* +R13 30 31 1 +L2 31 98 7.96E-3 +G4 98 30 3 33 1.0E-7 +D7 30 97 DX +D8 51 30 DX +* +* POLE AT 2 MHZ +* +R14 32 98 1 +C5 32 98 79.5E-9 +G5 98 32 27 33 1 +* +* OUTPUT STAGE +* +R15 33 97 1 +R16 33 51 1 +GSY 99 50 POLY(1) 99 50 0.325E-3 0.0425E-3 +F1 34 0 V3 1 +F2 0 34 V4 1 +R17 34 99 400 +R18 34 50 400 +L3 34 39 2E-7 +G6 37 50 32 34 2.5E-3 +G7 38 50 34 32 2.5E-3 +G8 34 99 99 32 2.5E-3 +G9 50 34 32 50 2.5E-3 +V3 35 34 6.8 +V4 34 36 4.4 +D9 32 35 DX +D10 36 32 DX +D11 99 37 DX +D12 99 38 DX +D13 50 37 DY +D14 50 38 DY +* +* MODELS USED +* +.MODEL QX NPN(BF=417E6) +.MODEL DX D(IS=1E-15) +.MODEL DY D(IS=1E-15 BV=50) +.MODEL DEN D(IS=1E-12, RS=12.08K, KF=1E-17, AF=1) +.MODEL DIN D(IS=1E-12, RS=7.55E-6, KF=1.55E-15, AF=1) +.ENDS OP77 + +Iinj 0 probe 0 AC {0.5*prb*(prb+1)} +Vinj probe Ninplp 0 AC {0.5*prb*(prb-1)} +Vprobe probe Noutlp 0 + +.model 2N2222 NPN(IS=1E-14 VAF=100 ++ BF=200 IKF=0.3 XTB=1.5 BR=3 ++ CJC=8E-12 CJE=25E-12 TR=100E-9 TF=400E-12 ++ ITF=1 VTF=2 XTF=3 RB=10 RC=.3 RE=.2) + +XU1 Ninp Ninn Np15 Nm15 Ninplp OP77 +XU2 No Nre Np15 Nm15 Nrep OP77 +R1 Ninnm 0 {r_1} +R2 Ne Ninnm {r_2} +C2 Ne Ninnm {c_2} +VU1offset Ninn Ninnm {v_1offset} +R3 Nin Ninp {r_3} +VU2offset Nre Nrep {v_2offset} +R4 Nrep Ninp {r_4} +C4 Nrep Ninp {c_4} +R5 Ne No {r_o} +Vi Nin 0 {vin} AC {1-prb*prb} +V2p15 Np15 0 15 +Vm15 Nm15 0 -15 +Q1 Nc Nb Ne 2N2222 +R6 Np15 Nc 50 +R7 Noutlp Nb 10 +D1 Ngl No YELLOW +Vl Ngl 0 0 +.model YELLOW D(IS=93.1P RS=42M N=4.61 BV=2 IBV=10U ++ CJO=2.97P VJ=.75 M=.333 TT=4.32U) + +.model NPN NPN +.model PNP PNP +.param prb=0 +.param vin=2.5 +.param r_1=1k +.param r_2=1k +.param r_3=1k +.param r_4=1k +.param r_o=200 +.param c_2=20p +.param c_4=20p +.param v_1offset=0 +.param v_2offset=0 + +.end +""" + +hsada4077 = """ +.title ADA4077-2case + +* ADA4077-2 SPICE DMod model Typical values +* Description: Amplifier +* Generic Desc: 30V, BIP, OP, Low Noise, Low THD, 2X +* Developed by: RM ADSJ +* Revision History: 02/11/2012 - Updated to new header style +* 0.0 (11/2012) +* Copyright 2008, 2012 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-2 1 2 99 50 45 +*#ASSOC Category="Op-amps" symbol=opamp +* +*INPUT STAGE +* +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.25E4 +RB2 42 43 9.25E4 +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-2 +*$ + +Iinj 0 probe 0 AC {0.5*prb*(prb+1)} +Vinj probe Ninplp 0 AC {0.5*prb*(prb-1)} +Vprobe probe Noutlp 0 + +.model 2N2222 NPN(IS=1E-14 VAF=100 ++ BF=200 IKF=0.3 XTB=1.5 BR=3 ++ CJC=8E-12 CJE=25E-12 TR=100E-9 TF=400E-12 ++ ITF=1 VTF=2 XTF=3 RB=10 RC=.3 RE=.2) + +XU1 Ninp Ninn Np15 Nm15 Ninplp ADA4077-2 +XU2 No Nre Np15 Nm15 Nrep ADA4077-2 +R1 Ninnm 0 {r_1} +R2 Ne Ninnm {r_2} +C2 Ne Ninnm {c_2} +VU1offset Ninn Ninnm {v_1offset} +R3 Nin Ninp {r_3} +VU2offset Nre Nrep {v_2offset} +R4 Nrep Ninp {r_4} +C4 Nrep Ninp {c_4} +R5 Ne No {r_o} +Vi Nin 0 {vin} AC {1-prb*prb} +V2p15 Np15 0 15 +Vm15 Nm15 0 -15 +Q1 Nc Nb Ne 2N2222 +R6 Np15 Nc 50 +R7 Noutlp Nb 10 +D1 Ngl No YELLOW +Vl Ngl 0 0 +.model YELLOW D(IS=93.1P RS=42M N=4.61 BV=2 IBV=10U ++ CJO=2.97P VJ=.75 M=.333 TT=4.32U) + +.model NPN NPN +.model PNP PNP +.param prb=0 +.param vin=2.5 +.param r_1=1k +.param r_2=1k +.param r_3=1k +.param r_4=1k +.param r_o=200 +.param c_2=20p +.param c_4=20p +.param v_1offset=0 +.param v_2offset=0 + +.end +""" def circuit_gft(prb): - circuit_file = SpiceParser('HSOP77case.net') + circuit_file = SpiceParser(source=prb[0]) circuit = circuit_file.build_circuit() - circuit.parameter('prb', str(prb)) + circuit.parameter('prb', str(prb[1])) simulator = circuit.simulator(simulator='xyce-serial') simulator.save(['all']) - return simulator.ac(start_frequency=10 - 2, - stop_frequency=1e9, - number_of_points=10, - variation='dec'), simulator - + return simulator class TestSpiceParser(unittest.TestCase): def test_parser(self): - results = list(map(circuit_gft, [-1, 1])) - self.assertEqual(len(results), 2) - self.assertIn('Ninp', results[0][0].nodes) - circuit = results[0][1] - values = str(circuit) - self.assertNotRegex(values, r'(\.ic)') + 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)') def test_subcircuit(self): print(os.getcwd()) From 6877d36d0a3846349e9b54abf26b867c73200848 Mon Sep 17 00:00:00 2001 From: jmgc Date: Sat, 25 Apr 2020 07:33:54 +0200 Subject: [PATCH 043/134] Remove SchemDraw. --- examples/ctia_readout/ctia_readout.ipynb | 3020 ----------------- .../diode/diode-characteristic-curve.ipynb | 2292 ------------- setup.py | 1 - 3 files changed, 5313 deletions(-) delete mode 100644 examples/ctia_readout/ctia_readout.ipynb delete mode 100644 examples/diode/diode-characteristic-curve.ipynb diff --git a/examples/ctia_readout/ctia_readout.ipynb b/examples/ctia_readout/ctia_readout.ipynb deleted file mode 100644 index c77ed304c..000000000 --- a/examples/ctia_readout/ctia_readout.ipynb +++ /dev/null @@ -1,3020 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/lib/python3.6/dist-packages/matplotlib/__init__.py:908: MatplotlibDeprecationWarning: The backend.qt4 rcParam was deprecated in version 2.2. In order to force the use of a specific Qt binding, either import that binding first, or set the QT_API environment variable.\n", - " mplDeprecation)\n", - "/usr/local/lib/python3.6/dist-packages/matplotlib/__init__.py:908: MatplotlibDeprecationWarning: The backend.qt4 rcParam was deprecated in version 2.2. In order to force the use of a specific Qt binding, either import that binding first, or set the QT_API environment variable.\n", - " mplDeprecation)\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[1;32m2018-11-26 18:08:39,871\u001b[0m - \u001b[1;34mPySpice.Doc.ExampleTools.find_libraries\u001b[0m - \u001b[1;31mINFO\u001b[0m - SPICE library path is ~/Projects/ROIC/src/PySpice/examples/libraries\n" - ] - } - ], - "source": [ - "%matplotlib inline\n", - "%config InlineBackend.figure_format = 'svg'\n", - "####################################################################################################\n", - "\n", - "import os\n", - "\n", - "import numpy as np\n", - "import pylab as pb\n", - "import matplotlib.pyplot as plt\n", - "import matplotlib.ticker as ticker\n", - "import networkx as nx\n", - "import SchemDraw as schem\n", - "\n", - "####################################################################################################\n", - "import PySpice\n", - "import PySpice.Logging.Logging as Logging\n", - "logger = Logging.setup_logging()\n", - "\n", - "####################################################################################################\n", - "\n", - "\n", - "from PySpice.Doc.ExampleTools import find_libraries\n", - "from PySpice.Spice.Netlist import Circuit\n", - "from PySpice.Spice.Library import SpiceLibrary\n", - "from PySpice.Unit import *\n", - "from PySpice.Physics.SemiConductor import ShockleyDiode\n", - "from PySpice.Spice.Netlist import SubCircuitFactory\n", - "\n", - "os.environ['PySpiceLibraryPath'] = '~/Projects/ROIC/src/PySpice/examples/libraries'\n", - "\n", - "####################################################################################################\n", - "\n", - "libraries_path = find_libraries()\n", - "spice_library = SpiceLibrary(libraries_path)\n", - "####################################################################################################" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "from math import pi\n", - "#cgs units\n", - "cm = 1.0\n", - "m = 100.0\n", - "mum = 1e-6*m\n", - "nm = 1e-9*m\n", - "g = 1.0\n", - "kg = 1000.0*g\n", - "\n", - "s = 1.0\n", - "ms = 1e-3*s\n", - "ns = 1e-9*s\n", - "mus = 1e-6*s\n", - "\n", - "Hertz = Hz = 1.0/s\n", - "\n", - "erg = g*cm**2/s**2\n", - "Joule = kg*m**2/s**2\n", - "\n", - "Watt = Joule/s\n", - "mW = 1e-3*Watt\n", - "\n", - "Coulomb = 1.0\n", - "Ampere = Coulomb/s\n", - "mA = 1e-3*Ampere\n", - "nA = 1e-9*Ampere\n", - "Kelvin = 1.0\n", - "\n", - "eps0 = 8.854187817620e-12*Ampere**2*s**4/kg/m**3\n", - "h_planck = 6.62606885e-27*erg*s\n", - "hbar_planck = h_planck/2.0/pi\n", - "q_e = 1.6021766208e-19*Coulomb\n", - "k_b = 1.38064852e-16*erg/Kelvin\n", - "\n", - "eV =q_e*Joule/Coulomb\n", - "Volt = Joule/Coulomb\n", - "mV = 1e-3*Volt\n", - "\n", - "Ohm = Volt/Ampere\n", - "kOhm = 1e3*Ohm\n", - "MOhm = 1e6*Ohm \n", - "\n", - "Farad = Coulomb/Volt\n", - "uF = 1e-6*Farad\n", - "nF = 1e-9*Farad\n", - "pF = 1e-12*Farad" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "PySpice.__file__" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n" - ], - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "class BasicOperationalAmplifier(SubCircuitFactory):\n", - "\n", - " __name__ = 'BasicOperationalAmplifier'\n", - " __nodes__ = ('non_inverting_input', 'inverting_input', 'output')\n", - " __pins__ = ('plus', 'minus', 'out')\n", - "\n", - "\n", - " def __init__(self):\n", - "\n", - " super().__init__()\n", - "\n", - " # Input impedance\n", - " RIN = self.R('input', 'non_inverting_input', 'inverting_input', 10@u_MΩ,\n", - " schematic_kwargs={'schematic': schem.elements.RES,\n", - " 'label': r'$R_{in}$', 'd': 'up',\n", - " 'show_start': True, 'start_label': {'lftlabel': r'$v_+$'},\n", - " 'show_end': True, 'end_label': {'lftlabel': r'$v_-$'},\n", - " }\n", - " )\n", - " self.RIN = RIN\n", - " #self._schematic.add(schem.elements.RES,label=r'$R_in$')\n", - "\n", - " # dc gain=100k and pole1=100hz\n", - " # unity gain = dcgain x pole1 = 10MHZ\n", - " self.schematic.add(schem.elements.LINE, d ='right', l=1.0)\n", - " vcvs_in = self.VCVS('gain', 1, self.gnd, 'non_inverting_input', 'inverting_input', voltage_gain=kilo(100),\n", - " schematic_kwargs={'schematic': schem.elements.SOURCE_CONT_V,\n", - " 'anchor':'in1',#'d':'right',\n", - " 'show_end': True, 'end_label': {'toplabel': r'$v_1 = A(v_+ - v_-)$'},\n", - " #'rgtlabel': r'$A(v_+ - v_-)$',\n", - " #'l': RIN.schematic_element.dy,\n", - " })\n", - " self.vcvs_in = vcvs_in\n", - " self.schematic.add(schem.elements.GND, xy=vcvs_in.schematic_element.start)\n", - " self.schematic.add(schem.elements.DOT_OPEN, xy=vcvs_in.schematic_element.in2, rgtlabel=r'$v_+$')\n", - " \n", - " #self.schematic.add(schem.elements.LINE, xy=vcvs_in.schematic_element.end, d='right', l=1.5)\n", - " RP1 = self.R('P1', 1, 2, 1@u_kΩ,\n", - " schematic_kwargs={'schematic': schem.elements.RES,\n", - " 'd':'right',\n", - " 'xy': vcvs_in.schematic_element.end,\n", - " 'botlabel': r'$RP1$',\n", - " 'l':5.5\n", - " })\n", - " CP1 = self.C('P1', 2, self.gnd, 1.5915@u_uF, \n", - " schematic_kwargs={'schematic': schem.elements.CAP,\n", - " 'd':'down',\n", - " 'botlabel': r'$CP1$',\n", - " 'show_start': True, 'start_label': {'toplabel': r'$v_2$'},\n", - " })\n", - " self.schematic.add(schem.elements.GND, xy=CP1.schematic_element.end)\n", - "\n", - " # Output buffer and resistance\n", - " self.schematic.add(schem.elements.LINE, xy=CP1.schematic_element.start, d='right', l=2.0)\n", - " vcvs_out = self.VCVS('buffer', 3, self.gnd, 2, self.gnd, 1,\n", - " schematic_kwargs={'schematic': schem.elements.SOURCE_CONT_V,\n", - " 'anchor':'in1',#'d':'right',\n", - " 'show_end': True, 'end_label': {'toplabel': r'$v_3 = v_2$'}}\n", - " )\n", - " self.vcvs_out = vcvs_out\n", - " self.schematic.add(schem.elements.GND, xy=vcvs_out.schematic_element.start)\n", - " self.schematic.add(schem.elements.GND, xy=vcvs_out.schematic_element.in2)\n", - " \n", - " \n", - " ROUT = self.R('out', 3, 'output', 10@u_Ω,\n", - " schematic_kwargs={'schematic': schem.elements.RES,\n", - " 'label': r'$R_{out}$', 'd': 'right',\n", - " 'show_end': True, 'end_label': {'rgtlabel': r'$v_{o}$'},\n", - " 'l': 3.5\n", - " }\n", - " )\n", - " self.ROUT = ROUT\n", - " \n", - "opamp = BasicOperationalAmplifier()\n", - "opamp.schematic.draw()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 50, - "metadata": {}, - "outputs": [ - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n" - ], - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "#r# For this purpose, we use the common high-speed diode 1N4148. The diode is driven by a variable\n", - "#r# voltage source through a limiting current resistance.\n", - "\n", - "#f# circuit_macros('diode-characteristic-curve-circuit.m4')\n", - "\n", - "circuit = Circuit('Capacitive Transimpedance Amplifier (CTIA) readout')\n", - "\n", - "#circuit.include(spice_library['1N4148'])\n", - "\n", - "\n", - "DD = circuit.D('sensor', 'anode', 'Vd',\n", - " schematic_kwargs={'schematic': schem.elements.PHOTODIODE, \n", - " 'd': 'left', 'show_end': True, 'end_label': {'lftlabel': r'$V_d$'},\n", - " 'flip': True,\n", - " 'botlabel': r'$D_d$'\n", - " }\n", - " )\n", - "#circuit.schematic.add(schem.elements.LINE, xy=DD.schematic_element.start, d ='down', l=1.5)\n", - "#CD = circuit.C('d', 'anode', 'Vd', 1@u_uF,\n", - "# schematic_kwargs={'schematic': schem.elements.CAP, 'botlabel': r'$C_d$',\n", - "# 'd': 'left', 'l': DD.schematic_element.dx}\n", - "# )\n", - "#circuit.schematic.add(schem.elements.LINE, xy=CD.schematic_element.end, to=DD.schematic_element.end)\n", - "\n", - "circuit.subcircuit(opamp)\n", - "seg = circuit.schematic.add(schem.elements.LINE, xy=DD.schematic_element.start, d ='right', l=2.0)\n", - "circuit.schematic.labelI(seg, arrowlen=1.0, reverse=False, arrowofst=0.5,\n", - " label=r'$i_S(t)$', top=True)\n", - "\n", - "XA = circuit.X('A', 'BasicOperationalAmplifier', 'Vcom', 'anode', 'Aout',\n", - " schematic_kwargs={'schematic': schem.elements.OPAMP, 'anchor': 'in1', \n", - " 'd': 'right', 'flip':False,\n", - " #'xy': CD.schematic_element.start\n", - " })\n", - "circuit.schematic.add(schem.elements.DOT, lftlabel =r'$V_{com}$', xy=XA.schematic_element.in2)\n", - "\n", - "circuit.schematic.add(schem.elements.LINE, xy=XA.schematic_element.in1, d ='up', l=2.0)\n", - "CINT = circuit.C('int', 'anode', 'Aout', 1@u_uF,\n", - " schematic_kwargs={'schematic': schem.elements.CAP, 'toplabel': r'$C_{int}$',\n", - " 'd': 'right', 'l': XA.schematic_element.dx}\n", - " )\n", - "circuit.schematic.add(schem.elements.LINE, xy=XA.schematic_element.out, d ='up', \n", - " to=CINT.schematic_element.end)\n", - "\n", - "\n", - "circuit.schematic.add(schem.elements.LINE, xy=CINT.schematic_element.start, d ='up', l=2.0)\n", - "RF = circuit.R('F', 'anode', 'Aout', 1@u_MOhm,\n", - " schematic_kwargs={'schematic': schem.elements.RES, 'toplabel': r'$R_F$',\n", - " 'd': 'right', 'l': XA.schematic_element.dx}\n", - " )\n", - "circuit.schematic.add(schem.elements.LINE, xy=CINT.schematic_element.end, d ='up', l=2.0)\n", - "\n", - "circuit.schematic.add(schem.elements.LINE, xy=XA.schematic_element.out, d ='right', l=2.0)\n", - "circuit.schematic.add(schem.elements.DOT_OPEN, label =r'$v_S(t)$')\n", - "\n", - "circuit.schematic.draw()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\\begin{gather}\n", - "v_C(t) \\equiv V_{com}-v_S(t) = V_C + \\frac{1}{C_{int}}\\int_0^{\\tau_{int}}{i_C(t)dt} \\\\\n", - "i_C = i_S - (V_{com}-v_S)/R_F\n", - "\\end{gather}\n", - "\n", - "\\begin{align}\n", - "v_S &= V_{com}-V_C-\\frac{1}{C_{int}}\\int_0^{\\tau_{int}}{i_C(t)dt} \\\\\n", - "&= V_{com}-V_C-\\frac{1}{C_{int}}\\int_0^{\\tau_{int}}{i_S(t)dt} + \\frac{1}{C_{int}}\\int_0^{\\tau_{int}}{\\frac{V_{com}-v_S}{R_F}dt} \\\\\n", - "&= V_{com}-V_C-\\frac{1}{C_{int}}\\int_0^{\\tau_{int}}{i_S(t)dt} + \\frac{V_{com}\\tau_{int}}{R_FC_{int}}\n", - "-\\frac{1}{R_FC_{int}}\\int_0^{\\tau_{int}}{v_S(t)dt}\n", - "\\end{align}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Thus the signal voltage $v_S$ is defined by the differential equation,\n", - "\n", - "\\begin{equation}\n", - "\\frac{dv_S}{dt} + \\frac{v_S}{R_FC_{int}} + \\frac{i_S}{C_{int}} = 0\n", - "\\end{equation}\n", - "\n", - "which can be solved as,\n", - "\n", - "\\begin{gather}\n", - "\\mu = e^{\\frac{t}{R_FC_{int}}} \\\\\n", - "\\mu\\left[v_S^\\prime + \\frac{v_S}{R_FC_{int}}\\right] + \\frac{\\mu i_S}{C_{int}} \\\\\n", - "(\\mu v_S)^\\prime = \\mu v_S^\\prime + \\mu^\\prime v_S \\\\\n", - "\\mu^\\prime = \\frac{\\mu}{R_FC_{int}} \\\\\n", - "(\\mu v_S)^\\prime + \\frac{\\mu i_S}{C_{int}} = 0 \\\\\n", - "v_S(\\tau_{int}) = v_S(0) -\\frac{e^{\\frac{-\\tau_{int}}{R_FC_{int}}}}{R_FC_{int}} \\int_0^{\\tau_{int}}{e^{\\frac{t}{R_FC_{int}}} R_F i_S dt}\\\\\n", - "v_S(\\tau_{int}) = v_S(0) -\\frac{1}{R_FC_{int}} \\int_0^{\\tau_{int}}{e^{\\frac{t-\\tau_{int}}{R_FC_{int}}} R_F i_S dt}\\\\\n", - "v_S(\\infty) \\equiv R_F i_S(\\infty) \\\\\n", - "\\lim_{\\tau_{int}\\to\\infty}\\int_0^{\\tau_{int}}{e^{\\frac{t-\\tau_{int}}{R_FC_{int}}} R_F i_S dt} = R_F C_{int} \\left[ v_S(0)-v_S(\\infty) \\right]\n", - "\\end{gather}" - ] - }, - { - "cell_type": "code", - "execution_count": 54, - "metadata": {}, - "outputs": [ - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n" - ], - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "def i_S(t):\n", - " t_off = np.argwhere(np.logical_or(t>i_S.t1, t<=i_S.t0))\n", - " t_on = np.argwhere(np.logical_and(t<=i_S.t1, t>i_S.t0))\n", - " i = np.zeros_like(t)\n", - " i[t_on] = i_S.val #i_S.tramp*t[t_pos]\n", - " \n", - " return i\n", - "\n", - "\n", - "R_F = 1.0*MOhm\n", - "C_INT = 0.1*nF\n", - "rc_delay = R_F*C_INT\n", - "\n", - "i_S.val = -100.0*nA\n", - "i_S.t0 = 0.0\n", - "i_S.t1 = 0.5*rc_delay\n", - "i_S.tramp = 0.1*mA/mus\n", - "\n", - "def v_S(tau_int, RF=R_F, CINT=C_INT, v0=0.0*Volt):\n", - " \n", - " v = np.zeros_like(tau_int)\n", - " for j, tau in enumerate(tau_int):\n", - " t = np.linspace(0,tau, 500)\n", - " i = i_S(t)\n", - " f = np.exp((t-tau)/RF/CINT)*RF*i\n", - " I = np.trapz(f,x=t)\n", - " v[j] = v0 - I/RF/CINT\n", - " \n", - " return v\n", - "\n", - "\n", - "t = np.linspace(0, 4.0*rc_delay,100)\n", - "\n", - "pb.plot(t/rc_delay, i_S(t)/nA, ls='--', c='b')\n", - "ax1 = pb.gca()\n", - "ax2 = pb.twinx(ax1)\n", - "tau_int = np.linspace(0.0, 4.0*rc_delay, 100)\n", - "pb.plot(tau_int/rc_delay, v_S(tau_int)/mV, c='b')\n", - "i_S.val = -200.0*nA\n", - "i_S.tramp = 0.2*mA/mus\n", - "ax1.plot(t/rc_delay, i_S(t)/nA, ls='--', c='r')\n", - "ax2.plot(tau_int/rc_delay, v_S(tau_int)/mV, c='r')\n", - "ax2.axvline(rc_delay/rc_delay, c='k', ls='--')\n", - "\n", - "ax1.set_xlabel('Time ($RC$)')\n", - "ax1.set_ylabel('$i_S$ ($nA$)')\n", - "ax2.set_ylabel('$v_S$ ($mV$)')\n", - "ax2.set_yscale('linear')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "np.trapz?" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.7" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/examples/diode/diode-characteristic-curve.ipynb b/examples/diode/diode-characteristic-curve.ipynb deleted file mode 100644 index 9c0758bc5..000000000 --- a/examples/diode/diode-characteristic-curve.ipynb +++ /dev/null @@ -1,2292 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "%config InlineBackend.figure_format = 'svg'\n", - "####################################################################################################\n", - "\n", - "import os\n", - "\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "import matplotlib.ticker as ticker\n", - "import networkx as nx\n", - "import SchemDraw as schem\n", - "\n", - "####################################################################################################\n", - "\n", - "import PySpice.Logging.Logging as Logging\n", - "logger = Logging.setup_logging()\n", - "\n", - "####################################################################################################\n", - "\n", - "from PySpice.Doc.ExampleTools import find_libraries\n", - "from PySpice.Spice.Netlist import Circuit\n", - "from PySpice.Spice.Library import SpiceLibrary\n", - "from PySpice.Unit import *\n", - "from PySpice.Physics.SemiConductor import ShockleyDiode\n", - "\n", - "os.environ['PySpiceLibraryPath'] = '~/Projects/ROIC/src/PySpice/examples/libraries'\n", - "\n", - "####################################################################################################\n", - "\n", - "libraries_path = find_libraries()\n", - "spice_library = SpiceLibrary(libraries_path)\n", - "####################################################################################################" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": {}, - "outputs": [ - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n" - ], - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "#r# For this purpose, we use the common high-speed diode 1N4148. The diode is driven by a variable\n", - "#r# voltage source through a limiting current resistance.\n", - "\n", - "#f# circuit_macros('diode-characteristic-curve-circuit.m4')\n", - "\n", - "circuit = Circuit('Diode Characteristic Curve')\n", - "\n", - "circuit.include(spice_library['1N4148'])\n", - "\n", - "circuit.schematic.add(schem.elements.GND)\n", - "V =circuit.V('input', 'Vin', circuit.gnd, 10@u_V,\n", - " schematic_kwargs={'show_plus': True}\n", - " )\n", - "R = circuit.R('1', 'Vin', 'Vout', 1@u_Ω,\n", - " schematic_kwargs={'d':'right', 'show_minus': True}\n", - " ) # not required for simulation\n", - "X = circuit.X('D1', '1N4148', 'Vout', circuit.gnd,\n", - " schematic_kwargs={'schematic': schem.elements.DIODE, 'd':'down'},\n", - " )\n", - "circuit.schematic.add(schem.elements.GND)\n", - "circuit.schematic.draw()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": false - }, - "outputs": [], - "source": [ - "#pos = nx.spectral_layout(circuit.graph)\n", - "#nx.draw(circuit.graph, pos=pos, with_labels=True, node_size=1200, node_shape='s',\n", - "# width=2.0)\n", - "#edge_labels = nx.get_edge_attributes(circuit.graph,'name')\n", - "#nx.draw_networkx_edge_labels(circuit.graph, pos, edge_labels=edge_labels)\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": {}, - "outputs": [], - "source": [ - "#r# We simulate the circuit at these temperatures: 0, 25 and 100 °C.\n", - "\n", - "# Fixme: Xyce ???\n", - "temperatures = [0, 25, 100]@u_Degree\n", - "analyses = {}\n", - "for temperature in temperatures:\n", - " simulator = circuit.simulator(temperature=temperature, nominal_temperature=temperature)\n", - " analysis = simulator.dc(Vinput=slice(-2, 5, .01))\n", - " analyses[float(temperature)] = analysis\n", - "\n", - "####################################################################################################" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": { - "scrolled": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "Text(1,-100,'Forward Biased Region')" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n" - ], - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "#r# We plot the characteristic curve and compare it to the Shockley diode model:\n", - "#r#\n", - "#r# .. math::\n", - "#r#\n", - "#r# I_d = I_s \\left( e^{\\frac{V_d}{n V_T}} - 1 \\right)\n", - "#r#\n", - "#r# where :math:`V_T = \\frac{k T}{q}`\n", - "#r#\n", - "#r# In order to scale the reverse biased region, we have to do some hack with Matplotlib.\n", - "#r#\n", - "\n", - "silicon_forward_voltage_threshold = .7\n", - "\n", - "shockley_diode = ShockleyDiode(Is=4.0@u_nA, degree=25)\n", - "\n", - "def two_scales_tick_formatter(value, position):\n", - " if value >= 0:\n", - " return '{} mA'.format(value)\n", - " else:\n", - " return '{} nA'.format(value/100)\n", - "formatter = ticker.FuncFormatter(two_scales_tick_formatter)\n", - "\n", - "figure = plt.figure(1, (10, 5))\n", - "\n", - "axe = plt.subplot(121)\n", - "axe.set_title('1N4148 Characteristic Curve ')\n", - "axe.set_xlabel('Voltage [V]')\n", - "axe.set_ylabel('Current')\n", - "axe.grid()\n", - "axe.set_xlim(-2, 2)\n", - "axe.axvspan(-2, 0, facecolor='green', alpha=.2)\n", - "axe.axvspan(0, silicon_forward_voltage_threshold, facecolor='blue', alpha=.1)\n", - "axe.axvspan(silicon_forward_voltage_threshold, 2, facecolor='blue', alpha=.2)\n", - "#axe.set_ylim(-500, 750) # Fixme: round\n", - "#axe.yaxis.set_major_formatter(formatter)\n", - "Vd = analyses[25].Vout\n", - "# compute scale for reverse and forward region\n", - "forward_region = Vd >= 0@u_V\n", - "reverse_region = np.invert(forward_region)\n", - "scale = reverse_region*1e11 + forward_region*1e3\n", - "#?# check temperature\n", - "for temperature in temperatures:\n", - " analysis = analyses[float(temperature)]\n", - " axe.plot(Vd, np.abs(- analysis.Vinput))\n", - "axe.plot(Vd, np.abs(shockley_diode.I(Vd)), 'black')\n", - "axe.set_ylim(1e-9, 1e3)\n", - "axe.set_yscale('log')\n", - "axe.legend(['@ {}'.format(temperature)\n", - " for temperature in temperatures] + ['Shockley Diode Model Is = 4 nA'],\n", - " loc=2, fontsize=10)\n", - "axe.axvline(x=0, color='black')\n", - "axe.axhline(y=0, color='black')\n", - "axe.axvline(x=silicon_forward_voltage_threshold, color='red')\n", - "axe.text(-1, -100, 'Reverse Biased Region', ha='center', va='center')\n", - "axe.text( 1, -100, 'Forward Biased Region', ha='center', va='center')" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.7" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/setup.py b/setup.py index 11cdb8b7b..f4989a5c9 100755 --- a/setup.py +++ b/setup.py @@ -83,7 +83,6 @@ 'ply', 'scipy', 'networkx', - 'SchemDraw' ], )) From adb2c0b8552ea13ad4e057534c49d37d25c3f76c Mon Sep 17 00:00:00 2001 From: jmgc Date: Tue, 28 Apr 2020 13:46:24 +0200 Subject: [PATCH 044/134] Update and rename __init__.py.in to __init__.py. --- PySpice/{__init__.py.in => __init__.py} | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) rename PySpice/{__init__.py.in => __init__.py} (93%) diff --git a/PySpice/__init__.py.in b/PySpice/__init__.py similarity index 93% rename from PySpice/__init__.py.in rename to PySpice/__init__.py index 78175bd4e..614f33b34 100644 --- a/PySpice/__init__.py.in +++ b/PySpice/__init__.py @@ -20,9 +20,7 @@ #################################################################################################### -__version__ = '@VERSION@' -__git_tag__ = '@GIT_TAG@' -__git_sha__ = '@GIT_SHA@' +__version__ = '1.3.dev0' def show_version(): print('PySpice Version {}'.format(__version__)) From d04ccd462bc4707b9ee4ab022bd6da8673569000 Mon Sep 17 00:00:00 2001 From: jmgc Date: Wed, 21 Apr 2021 15:26:33 +0200 Subject: [PATCH 045/134] Updated to accept changes of order between value and model of some components. --- PySpice/Spice/BasicElement.py | 1 - PySpice/Spice/ElementParameter.py | 4 ++-- PySpice/Spice/Netlist.py | 28 +++++++++++++----------- PySpice/Spice/Parser.py | 24 ++++++++++++++++---- PySpice/Unit/Unit.py | 2 ++ unit-test-todo/test_netlist.py | 6 ++--- unit-test/Spice/test_HighLevelElement.py | 2 +- unit-test/Unit/test_SpiceParser.py | 2 +- 8 files changed, 44 insertions(+), 25 deletions(-) diff --git a/PySpice/Spice/BasicElement.py b/PySpice/Spice/BasicElement.py index eca087edd..a4ff40d80 100644 --- a/PySpice/Spice/BasicElement.py +++ b/PySpice/Spice/BasicElement.py @@ -1703,7 +1703,6 @@ class XSpiceElement(NPinElement): ############################################## def __init__(self, netlist, name, *nodes, **parameters): - # Fixme: ok ??? super().__init__(netlist, name, *nodes, **parameters) diff --git a/PySpice/Spice/ElementParameter.py b/PySpice/Spice/ElementParameter.py index d6eca7a0c..fd871e024 100644 --- a/PySpice/Spice/ElementParameter.py +++ b/PySpice/Spice/ElementParameter.py @@ -193,10 +193,10 @@ def __init__(self, position, unit=None, **kwargs): def validate(self, value): - if isinstance(value, Unit): + if isinstance(value, type(self._unit)): return value else: - return Unit(value) + return self._unit.new_value(value) #################################################################################################### diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index e73b3d858..dc4c28297 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -720,8 +720,6 @@ 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)) - - self._pins = [Pin(self, pin_definition, netlist.get_node(node, True)) for pin_definition, node in pin_definition_nodes] @@ -744,7 +742,22 @@ class NPinElement(Element): ############################################## 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 = [] + self._pins = [Pin(self, self._pins_[0], netlist.get_node(node, True)) + for node in nodes] super().__init__(netlist, name, *args, **kwargs) ############################################## @@ -948,17 +961,6 @@ def _find_subcircuit(self, name): else: return self._subcircuits[name] - ############################################## - - #def __getattr__(self, attribute_name): - - # try: - # return self.__getitem__(attribute_name) - # except IndexError: - # raise AttributeError(attribute_name) - - ############################################## - def _add_node(self, node_name): node_name = str(node_name) diff --git a/PySpice/Spice/Parser.py b/PySpice/Spice/Parser.py index b4b24ee76..f03a2aa60 100644 --- a/PySpice/Spice/Parser.py +++ b/PySpice/Spice/Parser.py @@ -660,6 +660,8 @@ def __init__(self, line): # Read positionals number_of_positionals = prefix_data.number_of_positionals_min + if number_of_positionals and len(args) > number_of_positionals and len(args) <= prefix_data.number_of_positionals_max: + number_of_positionals = len(args) 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:] @@ -688,14 +690,28 @@ def __init__(self, line): # Move positionals passed as kwarg to_delete = [] - for parameter in element_class.positional_parameters.values(): + values = self._parameters[:] + update = [] + for parameter in sorted(element_class.positional_parameters.values(), + key=lambda parameter: parameter.position): + if not parameter.key_parameter: + parameters_map = {} + for idx, value in enumerate(values): + try: + data = parameter.validate(value) + parameters_map[idx] = data + except Exception as e: + pass + if len(parameters_map) == 1: + update.append(values.pop(next(iter(parameters_map)))) if parameter.key_parameter: idx = parameter.position - if idx < len(self._parameters): - self._dict_parameters[parameter.attribute_name] = self._parameters[idx] + if idx < len(values): + self._dict_parameters[parameter.attribute_name] = parameter.validate(values[idx]) to_delete.append(idx - len(to_delete)) for idx in to_delete: - self._parameters.pop(idx) + values.pop(idx) + self._parameters = update + values # self._logger.debug(os.linesep + self.__repr__()) diff --git a/PySpice/Unit/Unit.py b/PySpice/Unit/Unit.py index c8f43b565..ba8874543 100644 --- a/PySpice/Unit/Unit.py +++ b/PySpice/Unit/Unit.py @@ -897,6 +897,8 @@ def new_value(self, value): if isinstance(value, np.ndarray): return self._values_ctor.from_ndarray(value, self) + elif isinstance(value, str): + return self._value_ctor(self, value) elif isinstance(value, collections.Iterable): return [self._value_ctor(self, x) for x in value] else: diff --git a/unit-test-todo/test_netlist.py b/unit-test-todo/test_netlist.py index 0619a11d0..c70e7b31d 100644 --- a/unit-test-todo/test_netlist.py +++ b/unit-test-todo/test_netlist.py @@ -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/Spice/test_HighLevelElement.py b/unit-test/Spice/test_HighLevelElement.py index 7bc753616..9fa8024e0 100644 --- a/unit-test/Spice/test_HighLevelElement.py +++ b/unit-test/Spice/test_HighLevelElement.py @@ -47,7 +47,7 @@ def test(self): 'pwl1', '1', '0', 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 r=0s td=0.0s)', + 'Vpwl1 1 0 PWL(0s 0V 10ms 0V 11ms 5V 20ms 5V r=0s td=0.0s)'.lower(), ) #################################################################################################### diff --git a/unit-test/Unit/test_SpiceParser.py b/unit-test/Unit/test_SpiceParser.py index d59d803d8..71a400e1e 100644 --- a/unit-test/Unit/test_SpiceParser.py +++ b/unit-test/Unit/test_SpiceParser.py @@ -391,7 +391,7 @@ def test_subcircuit(self): circuit.X('test', 'mosdriver', '0', '1', '2', '3', '4', '5') circuit.BehavioralSource('test', '1', '0', voltage_expression='if(0, 0, 1)', smoothbsrc=1) print(circuit) - self.assertEqual(True, False) + #self.assertEqual(True, False) if __name__ == '__main__': From 65e2604e7ae9e58c625715cbdf95a7e1c215ea04 Mon Sep 17 00:00:00 2001 From: jmgc Date: Wed, 21 Apr 2021 16:26:21 +0200 Subject: [PATCH 046/134] Update Parser.py --- PySpice/Spice/Parser.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/PySpice/Spice/Parser.py b/PySpice/Spice/Parser.py index f03a2aa60..30c5a4d6f 100644 --- a/PySpice/Spice/Parser.py +++ b/PySpice/Spice/Parser.py @@ -693,7 +693,7 @@ def __init__(self, line): values = self._parameters[:] update = [] for parameter in sorted(element_class.positional_parameters.values(), - key=lambda parameter: parameter.position): + key=lambda parameter: parameter.position): if not parameter.key_parameter: parameters_map = {} for idx, value in enumerate(values): @@ -1101,6 +1101,20 @@ def get_kwarg(text): @staticmethod def _partition(text): + 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) + return parts + + @staticmethod + def _partition_in_parentheses(text): parts = [] values = text.replace(',', ' ') for part in values.split(): @@ -1120,7 +1134,7 @@ def _partition_parentheses(text): parts = [] previous_start = 0 for m in regex.finditer(p, text): - parts.extend(Line._partition(text[previous_start:m.start()])) + parts.extend(Line._partition_in_parentheses(text[previous_start:m.start()])) parts.append(m.group()) previous_start = m.end() parts.extend(Line._partition(text[previous_start:])) @@ -1188,10 +1202,10 @@ def split_keyword(self, keyword): 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])) + parts.extend(Line._partition_in_parentheses(text[:mp.start()])) + parts.extend(Line._partition_in_parentheses(mp.group()[1:-1])) else: - parts.extend(Line._partition(text)) + parts.extend(Line._partition_in_parentheses(text)) return Line._check_parameters(parts) def split_element(self, prefix): From 5c67a28e123a2c9490454865b69acbe3eddaa55e Mon Sep 17 00:00:00 2001 From: jmgc Date: Wed, 21 Apr 2021 19:34:26 +0200 Subject: [PATCH 047/134] Update Netlist.py Correction on NPinElement. --- PySpice/Spice/Netlist.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index dc4c28297..593dff350 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -756,8 +756,9 @@ def __init__(self, netlist, name, *args, **kwargs): nodes = args args = [] - self._pins = [Pin(self, self._pins_[0], netlist.get_node(node, True)) - for node in nodes] + 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) ############################################## From 1cab0bffe18322ac98c53c96f8a44637dcada26a Mon Sep 17 00:00:00 2001 From: jmgc Date: Wed, 21 Apr 2021 19:38:10 +0200 Subject: [PATCH 048/134] Update test_SpiceParser.py --- unit-test/Unit/test_SpiceParser.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/unit-test/Unit/test_SpiceParser.py b/unit-test/Unit/test_SpiceParser.py index 71a400e1e..9e180224e 100644 --- a/unit-test/Unit/test_SpiceParser.py +++ b/unit-test/Unit/test_SpiceParser.py @@ -368,6 +368,7 @@ .end """ + def circuit_gft(prb): circuit_file = SpiceParser(source=prb[0]) circuit = circuit_file.build_circuit() @@ -390,8 +391,24 @@ def test_subcircuit(self): circuit.include(os.path.join(os.getcwd(), '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) - print(circuit) - #self.assertEqual(True, False) + result = """.title Diode Characteristic Curve + +.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) + +bhigh hoi hs v={if(v(hi, vss) > 0.5, 5, 0)} smoothbsrc=1 +rhoi hoi ho 1 +choi ho hs 1e-9 +blow loi vss v={if(v(li, vss) > 0.5, 5, 0)} smoothbsrc=1 +rloi loi lo 1 +cloi lo vss 1e-9 +dhb vdd hb diode +.ends mosdriver + +xtest 0 1 2 3 4 5 mosdriver +btest 1 0 v=if(0, 0, 1) smoothbsrc=1 +""" + self.assertEqual(str(circuit), result) if __name__ == '__main__': From 902d04da56c4a5dd8e873f14a6d2ef94efca77a7 Mon Sep 17 00:00:00 2001 From: jmgc Date: Tue, 18 May 2021 07:43:10 +0200 Subject: [PATCH 049/134] EBNF Spice Grammar definition for tatsu. Still being improved. --- PySpice/Spice/spicegrammar.ebnf | 541 ++++++++++++++++++++++++++++++++ 1 file changed, 541 insertions(+) create mode 100644 PySpice/Spice/spicegrammar.ebnf diff --git a/PySpice/Spice/spicegrammar.ebnf b/PySpice/Spice/spicegrammar.ebnf new file mode 100644 index 000000000..f36a52e7d --- /dev/null +++ b/PySpice/Spice/spicegrammar.ebnf @@ -0,0 +1,541 @@ +@@grammar :: Spice +@@whitespace :: // +@@ignorecase :: True +@@parseinfo :: True + +start::Circuit = [ ".TITLE" ] {st} title:text [ {newline}+ lines:lines ] [ newline ".END" ] {newline} ; + +# Lines + +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" ~ { newline }+ { { /[0-9a-f]/ } { newline }+ } "$CDNENCFINISH" ; + +device = | @:nonlinear_dependent_source # B + | @:capacitor # C + | @:diode # D + | @:voltage_controlled_voltage_source # E + | @:current_controlled_current_source # F + | @:voltage_controlled_current_source # G + | @:current_controlled_voltage_source # H + | @:current_source # I + | @:jfet # J + | @:inductor # L + | @:mosfet # M + | @:bjt # Q + | @:resistor # R + | @:switch # S + | @:voltage_source # V + | @:subcircuit ; # X + +nonlinear_dependent_source::NonLinearDependentSource = &"B" ~ dev:dev + sep:sep positive:node sep:sep negative:node ~ + sep:sep ("V" | "I") [sep:sep] "=" [sep:sep] expr:abm_expression [ sep:sep paramters: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 model:model_name sep:sep value:gen_expr | + sep:sep value:gen_expr | + sep:sep model:model_name ] [ 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 ] ; + +voltage_controlled_voltage_source::VoltageControlledVoltageSource = + &"E" ~ dev:dev sep:sep positive:node sep:sep negative:node ~ + sep:sep ( ( controller: ( control_value | + control_table | + control_voltage_poly) ) | + ( control_positive:node sep:sep control_negative:node sep:sep gain:gen_expr ) ) ; + +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 ( ( controller: ( control_value | + control_table | + control_voltage_poly) ) | + ( control_positive:node sep:sep control_negative:node sep:sep transconductance:gen_expr ) ) ; + +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] braced_expression [sep:sep] "=" + [sep:sep] (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 ) }+ ; + +control_voltage_poly::ControlVoltagePoly = + "POLY" ~ [sep:sep] lp [sep:sep] value:integer [sep:sep] rp + [sep:sep] (sep:sep)%{ 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 ] ; + +inductor::Inductor = &"L" ~ dev:dev sep:sep positive:node sep:sep negative:node ~ + [ sep:sep model:model_name sep:sep value:gen_expr | + sep:sep value:gen_expr | + sep:sep model:model_name ] [ 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: ("IC" [sep:sep] "=" ~ [sep:sep] ( [sep:sep] "," [sep:sep] )%{value} | parameter ) } ]; + + +bjt::BJT = &"Q" ~ dev:dev sep:sep collector:node sep:sep base:node sep:sep emitter:node ~ + sep:sep ( substrate:node sep:sep thermal:node sep:sep model:model_name | + ( substrate:{digit}+ | &"[" substrate:node ) sep:sep model:model_name | + thermal:node sep:sep model:model_name | + model:model_name ) [ sep:sep area:gen_expr ] [ sep:sep parameters:parameters ]; + +resistor::Resistor = &"R" ~ dev:dev sep:sep positive:node sep:sep negative:node ~ + [ sep:sep model:model_name sep:sep value:gen_expr | + sep:sep value:gen_expr | + sep:sep model:model_name ] [ sep:sep parameters:parameters ] ; + +switch::Switch = &"S" ~ dev:dev sep:sep positive:node sep:sep negative:node ~ + sep:sep ( model:model_name [ sep:sep ( "ON" | "OFF" ) ] sep:sep "control" ~ [sep:sep] "=" [sep:sep] braced_expression | + control_p:node sep:sep control_n:node sep:sep model:model_name ); + +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 ] ; + +subcircuit::Subcircuit = &"X" ~ dev:dev { sep:sep node:node }+ # We assume the model will be detected as a node + [ params:":" ~ [sep:sep] parameters:parameters ] ; # We also assume the params will be detected as a node + +dc = "DC" ; +ac = "AC" ; + +transient_specification = | @:transient_pulse + | @:transient_sin + | @:transient_exp + | @:transient_pat + | @:transient_pwl + | @:transient_sffm ; + +# The parenthesis are not mandatory + +transient_pulse = type:"PULSE" ( [sep:sep] lp ~ [sep:sep] @:pulse_arguments [sep:sep] rp + | sep:sep @:pulse_arguments ) ; + +pulse_arguments = v1:gen_expr sep:sep (sep:sep)%{gen_expr} ; + +transient_sin = type:"SIN" ( [sep:sep] lp ~ [sep:sep] @:sin_arguments [sep:sep] rp + | sep:sep @:sin_arguments ); + +sin_arguments = v0:gen_expr sep:sep va:gen_expr sep:sep freq:gen_expr + sep:sep (sep:sep)%{value:gen_expr} ; + +transient_exp = type:"EXP" ( [sep:sep] lp ~ [sep:sep] @:exp_arguments [sep:sep] rp + | sep:sep @:exp_arguments ) ; + +exp_arguments = v1:gen_expr sep:sep v2:gen_expr + sep:sep (sep:sep)%{value:gen_expr} ; + +transient_pat = type:"PAT" ( [sep:sep] lp ~ [sep:sep] @:pat_arguments [sep:sep] rp + | sep:sep @:pat_arguments ) ; + +pat_arguments = 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 = type:"PWL" ~ sep:sep ( "FILE" ~ sep:sep ( double_quote filename double_quote | + filename ) [sep:sep parameters:parameters] | + (sep:sep)%{t:value ~ sep:sep v:value}+ ) ; + +transient_sffm = type:"SFFM" ( [sep:sep] lp ~ [sep:sep] @:sffm_arguments [sep:sep] rp + | sep:sep @:sffm_arguments ) ; + +sffm_arguments = v0:gen_expr sep:sep va:gen_expr + sep:sep (sep:sep)%{value:gen_expr} ; + +command = | @:ac_cmd + | @:ic_cmd # To avoid issues with .DCVOLT and .DC + | @:dc_cmd + | @:embedded_sampling_cmd + | @:include_cmd + | @:lib_cmd + | @:netlist_cmds + | @:subckt_cmd + | @:simulator_cmd + | @:title_cmd ; + +netlist_cmds = | @:data_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:( ".INC" | ".INCLUDE" | ".INCL" ) ~ sep:sep ( filename | + single_quote ~ filename single_quote | + double_quote ~ filename double_quote ) ; + +lib_cmd::LibCmd = @:lib_block | @:lib_call ; + +lib_call = cmd:".LIB" sep:sep ( 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 ; + +#Ignore simulator command by Simetrix +simulator_cmd::SimulatorCmd = cmd:".SIMULATOR" ~ sep:sep simulator:id ; + +# The param in the subckt is read as a node. The colon is used to detect it and check if +# the previous node is PARAM in the grammar. + +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 + lines:netlist_lines + ".ENDS" ~ [ {st}+ end_name:model_name ]; + +lib_block = cmd:".LIB" sep:sep entry:id sep:cmd_net_sep ~ + lines:netlist_lines + ".ENDL" ~ [ {st}+ entry:id ] ; + +title_cmd::TitleCmd = cmd:".TITLE" ~ title:text ; + +# Parameters + +parameters = ( [@:sep] comma [@:sep] | @:sep )%{ @:parameter } ; + +parameter = name:id [sep:sep] "=" ~ [sep:sep] value:gen_expr ~ { [sep:sep] comma [sep:sep] value:gen_expr } ; + +# Expressions + +gen_expr = | @:braced_expression + | @:value ; + +tablefile = func:"tablefile" [sep:sep] lp ~ [sep:sep] ( double_quote ~ filename:filename double_quote + | filename:filename ) [sep:sep] rp ; + +braced_expression = lc ~ [sep:sep] expression [sep:sep] rc ; + +#functions + +functions = | functions_1 + | atan2 + | ddx + | gauss + | i_func + | if_func + | limit + | functions_2 + | rand + | unif + | v_func ; + +functions_1 = func:( "abs" + | "ceil" + | "ddt" + | "floor" + | "int" + | "m" + | "nint" + | "sdt" + | "sgn" + | "stp" + | "sqrt" + | "uramp" + | "Ph" + | "Re" + | "R" + | "Img" + | "Db" + | "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] "," + [sep:sep] x:expression [sep:sep] rp ; + +ddx = func:"ddx" [sep:sep] lp ~ [sep:sep] f:id [sep:sep] "," + [sep:sep] x:expression [sep:sep] rp ; + +gauss = func:( "agauss" + | "gauss" )[sep:sep] lp ~ [sep:sep] mu:expression [sep:sep] "," + [sep:sep] alpha:expression [sep:sep] "," + [sep:sep] n:expression [sep:sep] rp ; + +i_func = func:"i" [sep:sep] lp ~ [sep:sep] &"V" positive:dev [sep:sep] rp ; + +if_func = func:"if" [sep:sep] lp ~ [sep:sep] t:conditional_expression [sep:sep] "," + [sep:sep] x:expression [sep:sep] "," + [sep:sep] y:expression [sep:sep] rp ; + +limit = func:"limit" [sep:sep] lp ~ [sep:sep] x:expression [sep:sep] "," + [sep:sep] y:expression [sep:sep] "," + [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] "," + [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] "," + [sep:sep] alpha:expression [sep:sep] rp ; + +v_func = func:"v" [sep:sep] lp ~ [sep:sep] node:node [sep:sep] + [ "," [sep:sep] negative:node [sep:sep] ] rp ; + +# Relational and conditional expressions + +expression = | t:conditional_expression [sep:sep] op:"?" ~ [sep:sep] x:expression [sep:sep] ":" [sep:sep] y:expression + | term ; + +conditional_expression = | boolean_or + | boolean ; + +boolean_or = | left:boolean_or [sep:sep] op:"|" ~ [sep:sep] right:boolean_xor + | boolean_xor ; + +boolean_xor = | left:boolean_xor [sep:sep] op:"^" ~ [sep:sep] right:boolean_and + | boolean_and ; + +boolean_and = | left:boolean_and [sep:sep] op:"&" ~ [sep:sep] right:boolean_not + | boolean_not ; + +boolean_not = | op:"~" ~ operator:relational + | relational ; + +relational = left:term [sep:sep] op:( "==" | "!=" | ">=" | "<=" | ">" | "<" ) ~ [sep:sep] right:term ; + +# Arithmetic term + +term = | left:term [sep:sep] op:( "+" | "-" ) ~ [sep:sep] right:prod + | prod ; + +prod = | left:prod [sep:sep] op:( "*" | "/" | "%" ) ~ [sep:sep] right:unary + | unary ; + +unary = | op:( "+" | "-" ) ~ operator:exp + | exp ; + +exp = | left:exp [sep:sep] op:'**' ~ [sep:sep] right:factor + | factor ; + +factor = | '(' ~ [sep:sep] @:expression [sep:sep] ')' + | @:functions + | var:id + | @:value ; + +special_variables = | "time" + | "temper" + | "temp" + | "freq" + | "vt" + | "pi" ; + +value::Value = | ( real:real_value "+" imag:imag_value ) + | imag:imag_value + | real:real_value ; + +imag_value::ImagValue = @:real_value "J" ; +real_value::RealValue = value:number_scale unit:[hz | unit] ; + +freq_value = value:number_scale unit:[hz] ; + +number_scale::NumberScale = | value:floating_point scale:(meg | mil | [suffix]) + | value:integer scale:(meg | mil | [suffix]) ; + +suffix = /[tTgGkKmMxXuUnNpPfF]/ ; + +meg = /[mM][eE][gG]/ ; +mil = /[mM][iI][lL]/ ; + +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 = /[a-zA-Z0-9_:@#\.\$\/]+/ ; + +boolean = | "TRUE" + | "FALSE" ; + +# Patterns + +model_name = name:/[a-zA-Z0-9_]+/ !([sep:sep] "=") ; + +binary_pattern = /[Bb]/ pattern:{binary}+ ; + +binary = /[01]/ ; + +# to support bug 1034, the DEV needs to recognize some weird characters, including '+' and '-' + +dev = /[a-zA-Z][a-zA-Z0-9_:!`@#\.\+\-]*/ ; + +# to support bug 1034, the NODE needs to recognize a lot of weird characters: +# from bug 1034: +# ` ~ ! @ # $ % ^ & - _ + [ ] | \ < > . ? | +# unfortunately, some of these are special regex characters, so they need to be +# handled with a preceding backslash. Probably more elegant regex, such as a +# range of ASCII characters could be used for NODE, but this seems to work for now + +node = | /[a-zA-Z0-9_\[\$][a-zA-Z0-9_:\$\-`~!@#%&_\+|<>\?\.\\|\^\*]*[a-zA-Z0-9_\$\-`~!@#%&_\+|<>\?\.\\|\^\*\]]/ + | /[a-zA-Z0-9_]/ ; + +id = | /[a-zA-Z_`@#\$][a-zA-Z0-9_:`@#\.\$\/]*[a-zA-Z0-9_`@#\.\$]/ + | /[a-zA-Z_`@#\$]/ ; + +# Separators + +end_sep = | @:cmd_net_sep + | {st}+; + +sep = | @:cmd_net_sep '+' {st} + | {st}+ ; + +cmd_net_sep::Separator = {st} comment:[ @:inline_comment ] newline + { {st} comment:[ ( @:line_comment | @:inline_comment ) ~ ] newline } + {st} ; + +#comments +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]*/ ; From caf410e9d004833a5d14cbe77d3ef01f33e862f1 Mon Sep 17 00:00:00 2001 From: jmgc Date: Tue, 18 May 2021 07:48:49 +0200 Subject: [PATCH 050/134] EBNF Spice Grammar definition for tatsu. Still being improved. --- PySpice/Spice/spicegrammar.ebnf | 541 -------------------------------- 1 file changed, 541 deletions(-) diff --git a/PySpice/Spice/spicegrammar.ebnf b/PySpice/Spice/spicegrammar.ebnf index f36a52e7d..e69de29bb 100644 --- a/PySpice/Spice/spicegrammar.ebnf +++ b/PySpice/Spice/spicegrammar.ebnf @@ -1,541 +0,0 @@ -@@grammar :: Spice -@@whitespace :: // -@@ignorecase :: True -@@parseinfo :: True - -start::Circuit = [ ".TITLE" ] {st} title:text [ {newline}+ lines:lines ] [ newline ".END" ] {newline} ; - -# Lines - -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" ~ { newline }+ { { /[0-9a-f]/ } { newline }+ } "$CDNENCFINISH" ; - -device = | @:nonlinear_dependent_source # B - | @:capacitor # C - | @:diode # D - | @:voltage_controlled_voltage_source # E - | @:current_controlled_current_source # F - | @:voltage_controlled_current_source # G - | @:current_controlled_voltage_source # H - | @:current_source # I - | @:jfet # J - | @:inductor # L - | @:mosfet # M - | @:bjt # Q - | @:resistor # R - | @:switch # S - | @:voltage_source # V - | @:subcircuit ; # X - -nonlinear_dependent_source::NonLinearDependentSource = &"B" ~ dev:dev - sep:sep positive:node sep:sep negative:node ~ - sep:sep ("V" | "I") [sep:sep] "=" [sep:sep] expr:abm_expression [ sep:sep paramters: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 model:model_name sep:sep value:gen_expr | - sep:sep value:gen_expr | - sep:sep model:model_name ] [ 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 ] ; - -voltage_controlled_voltage_source::VoltageControlledVoltageSource = - &"E" ~ dev:dev sep:sep positive:node sep:sep negative:node ~ - sep:sep ( ( controller: ( control_value | - control_table | - control_voltage_poly) ) | - ( control_positive:node sep:sep control_negative:node sep:sep gain:gen_expr ) ) ; - -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 ( ( controller: ( control_value | - control_table | - control_voltage_poly) ) | - ( control_positive:node sep:sep control_negative:node sep:sep transconductance:gen_expr ) ) ; - -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] braced_expression [sep:sep] "=" - [sep:sep] (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 ) }+ ; - -control_voltage_poly::ControlVoltagePoly = - "POLY" ~ [sep:sep] lp [sep:sep] value:integer [sep:sep] rp - [sep:sep] (sep:sep)%{ 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 ] ; - -inductor::Inductor = &"L" ~ dev:dev sep:sep positive:node sep:sep negative:node ~ - [ sep:sep model:model_name sep:sep value:gen_expr | - sep:sep value:gen_expr | - sep:sep model:model_name ] [ 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: ("IC" [sep:sep] "=" ~ [sep:sep] ( [sep:sep] "," [sep:sep] )%{value} | parameter ) } ]; - - -bjt::BJT = &"Q" ~ dev:dev sep:sep collector:node sep:sep base:node sep:sep emitter:node ~ - sep:sep ( substrate:node sep:sep thermal:node sep:sep model:model_name | - ( substrate:{digit}+ | &"[" substrate:node ) sep:sep model:model_name | - thermal:node sep:sep model:model_name | - model:model_name ) [ sep:sep area:gen_expr ] [ sep:sep parameters:parameters ]; - -resistor::Resistor = &"R" ~ dev:dev sep:sep positive:node sep:sep negative:node ~ - [ sep:sep model:model_name sep:sep value:gen_expr | - sep:sep value:gen_expr | - sep:sep model:model_name ] [ sep:sep parameters:parameters ] ; - -switch::Switch = &"S" ~ dev:dev sep:sep positive:node sep:sep negative:node ~ - sep:sep ( model:model_name [ sep:sep ( "ON" | "OFF" ) ] sep:sep "control" ~ [sep:sep] "=" [sep:sep] braced_expression | - control_p:node sep:sep control_n:node sep:sep model:model_name ); - -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 ] ; - -subcircuit::Subcircuit = &"X" ~ dev:dev { sep:sep node:node }+ # We assume the model will be detected as a node - [ params:":" ~ [sep:sep] parameters:parameters ] ; # We also assume the params will be detected as a node - -dc = "DC" ; -ac = "AC" ; - -transient_specification = | @:transient_pulse - | @:transient_sin - | @:transient_exp - | @:transient_pat - | @:transient_pwl - | @:transient_sffm ; - -# The parenthesis are not mandatory - -transient_pulse = type:"PULSE" ( [sep:sep] lp ~ [sep:sep] @:pulse_arguments [sep:sep] rp - | sep:sep @:pulse_arguments ) ; - -pulse_arguments = v1:gen_expr sep:sep (sep:sep)%{gen_expr} ; - -transient_sin = type:"SIN" ( [sep:sep] lp ~ [sep:sep] @:sin_arguments [sep:sep] rp - | sep:sep @:sin_arguments ); - -sin_arguments = v0:gen_expr sep:sep va:gen_expr sep:sep freq:gen_expr - sep:sep (sep:sep)%{value:gen_expr} ; - -transient_exp = type:"EXP" ( [sep:sep] lp ~ [sep:sep] @:exp_arguments [sep:sep] rp - | sep:sep @:exp_arguments ) ; - -exp_arguments = v1:gen_expr sep:sep v2:gen_expr - sep:sep (sep:sep)%{value:gen_expr} ; - -transient_pat = type:"PAT" ( [sep:sep] lp ~ [sep:sep] @:pat_arguments [sep:sep] rp - | sep:sep @:pat_arguments ) ; - -pat_arguments = 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 = type:"PWL" ~ sep:sep ( "FILE" ~ sep:sep ( double_quote filename double_quote | - filename ) [sep:sep parameters:parameters] | - (sep:sep)%{t:value ~ sep:sep v:value}+ ) ; - -transient_sffm = type:"SFFM" ( [sep:sep] lp ~ [sep:sep] @:sffm_arguments [sep:sep] rp - | sep:sep @:sffm_arguments ) ; - -sffm_arguments = v0:gen_expr sep:sep va:gen_expr - sep:sep (sep:sep)%{value:gen_expr} ; - -command = | @:ac_cmd - | @:ic_cmd # To avoid issues with .DCVOLT and .DC - | @:dc_cmd - | @:embedded_sampling_cmd - | @:include_cmd - | @:lib_cmd - | @:netlist_cmds - | @:subckt_cmd - | @:simulator_cmd - | @:title_cmd ; - -netlist_cmds = | @:data_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:( ".INC" | ".INCLUDE" | ".INCL" ) ~ sep:sep ( filename | - single_quote ~ filename single_quote | - double_quote ~ filename double_quote ) ; - -lib_cmd::LibCmd = @:lib_block | @:lib_call ; - -lib_call = cmd:".LIB" sep:sep ( 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 ; - -#Ignore simulator command by Simetrix -simulator_cmd::SimulatorCmd = cmd:".SIMULATOR" ~ sep:sep simulator:id ; - -# The param in the subckt is read as a node. The colon is used to detect it and check if -# the previous node is PARAM in the grammar. - -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 - lines:netlist_lines - ".ENDS" ~ [ {st}+ end_name:model_name ]; - -lib_block = cmd:".LIB" sep:sep entry:id sep:cmd_net_sep ~ - lines:netlist_lines - ".ENDL" ~ [ {st}+ entry:id ] ; - -title_cmd::TitleCmd = cmd:".TITLE" ~ title:text ; - -# Parameters - -parameters = ( [@:sep] comma [@:sep] | @:sep )%{ @:parameter } ; - -parameter = name:id [sep:sep] "=" ~ [sep:sep] value:gen_expr ~ { [sep:sep] comma [sep:sep] value:gen_expr } ; - -# Expressions - -gen_expr = | @:braced_expression - | @:value ; - -tablefile = func:"tablefile" [sep:sep] lp ~ [sep:sep] ( double_quote ~ filename:filename double_quote - | filename:filename ) [sep:sep] rp ; - -braced_expression = lc ~ [sep:sep] expression [sep:sep] rc ; - -#functions - -functions = | functions_1 - | atan2 - | ddx - | gauss - | i_func - | if_func - | limit - | functions_2 - | rand - | unif - | v_func ; - -functions_1 = func:( "abs" - | "ceil" - | "ddt" - | "floor" - | "int" - | "m" - | "nint" - | "sdt" - | "sgn" - | "stp" - | "sqrt" - | "uramp" - | "Ph" - | "Re" - | "R" - | "Img" - | "Db" - | "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] "," - [sep:sep] x:expression [sep:sep] rp ; - -ddx = func:"ddx" [sep:sep] lp ~ [sep:sep] f:id [sep:sep] "," - [sep:sep] x:expression [sep:sep] rp ; - -gauss = func:( "agauss" - | "gauss" )[sep:sep] lp ~ [sep:sep] mu:expression [sep:sep] "," - [sep:sep] alpha:expression [sep:sep] "," - [sep:sep] n:expression [sep:sep] rp ; - -i_func = func:"i" [sep:sep] lp ~ [sep:sep] &"V" positive:dev [sep:sep] rp ; - -if_func = func:"if" [sep:sep] lp ~ [sep:sep] t:conditional_expression [sep:sep] "," - [sep:sep] x:expression [sep:sep] "," - [sep:sep] y:expression [sep:sep] rp ; - -limit = func:"limit" [sep:sep] lp ~ [sep:sep] x:expression [sep:sep] "," - [sep:sep] y:expression [sep:sep] "," - [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] "," - [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] "," - [sep:sep] alpha:expression [sep:sep] rp ; - -v_func = func:"v" [sep:sep] lp ~ [sep:sep] node:node [sep:sep] - [ "," [sep:sep] negative:node [sep:sep] ] rp ; - -# Relational and conditional expressions - -expression = | t:conditional_expression [sep:sep] op:"?" ~ [sep:sep] x:expression [sep:sep] ":" [sep:sep] y:expression - | term ; - -conditional_expression = | boolean_or - | boolean ; - -boolean_or = | left:boolean_or [sep:sep] op:"|" ~ [sep:sep] right:boolean_xor - | boolean_xor ; - -boolean_xor = | left:boolean_xor [sep:sep] op:"^" ~ [sep:sep] right:boolean_and - | boolean_and ; - -boolean_and = | left:boolean_and [sep:sep] op:"&" ~ [sep:sep] right:boolean_not - | boolean_not ; - -boolean_not = | op:"~" ~ operator:relational - | relational ; - -relational = left:term [sep:sep] op:( "==" | "!=" | ">=" | "<=" | ">" | "<" ) ~ [sep:sep] right:term ; - -# Arithmetic term - -term = | left:term [sep:sep] op:( "+" | "-" ) ~ [sep:sep] right:prod - | prod ; - -prod = | left:prod [sep:sep] op:( "*" | "/" | "%" ) ~ [sep:sep] right:unary - | unary ; - -unary = | op:( "+" | "-" ) ~ operator:exp - | exp ; - -exp = | left:exp [sep:sep] op:'**' ~ [sep:sep] right:factor - | factor ; - -factor = | '(' ~ [sep:sep] @:expression [sep:sep] ')' - | @:functions - | var:id - | @:value ; - -special_variables = | "time" - | "temper" - | "temp" - | "freq" - | "vt" - | "pi" ; - -value::Value = | ( real:real_value "+" imag:imag_value ) - | imag:imag_value - | real:real_value ; - -imag_value::ImagValue = @:real_value "J" ; -real_value::RealValue = value:number_scale unit:[hz | unit] ; - -freq_value = value:number_scale unit:[hz] ; - -number_scale::NumberScale = | value:floating_point scale:(meg | mil | [suffix]) - | value:integer scale:(meg | mil | [suffix]) ; - -suffix = /[tTgGkKmMxXuUnNpPfF]/ ; - -meg = /[mM][eE][gG]/ ; -mil = /[mM][iI][lL]/ ; - -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 = /[a-zA-Z0-9_:@#\.\$\/]+/ ; - -boolean = | "TRUE" - | "FALSE" ; - -# Patterns - -model_name = name:/[a-zA-Z0-9_]+/ !([sep:sep] "=") ; - -binary_pattern = /[Bb]/ pattern:{binary}+ ; - -binary = /[01]/ ; - -# to support bug 1034, the DEV needs to recognize some weird characters, including '+' and '-' - -dev = /[a-zA-Z][a-zA-Z0-9_:!`@#\.\+\-]*/ ; - -# to support bug 1034, the NODE needs to recognize a lot of weird characters: -# from bug 1034: -# ` ~ ! @ # $ % ^ & - _ + [ ] | \ < > . ? | -# unfortunately, some of these are special regex characters, so they need to be -# handled with a preceding backslash. Probably more elegant regex, such as a -# range of ASCII characters could be used for NODE, but this seems to work for now - -node = | /[a-zA-Z0-9_\[\$][a-zA-Z0-9_:\$\-`~!@#%&_\+|<>\?\.\\|\^\*]*[a-zA-Z0-9_\$\-`~!@#%&_\+|<>\?\.\\|\^\*\]]/ - | /[a-zA-Z0-9_]/ ; - -id = | /[a-zA-Z_`@#\$][a-zA-Z0-9_:`@#\.\$\/]*[a-zA-Z0-9_`@#\.\$]/ - | /[a-zA-Z_`@#\$]/ ; - -# Separators - -end_sep = | @:cmd_net_sep - | {st}+; - -sep = | @:cmd_net_sep '+' {st} - | {st}+ ; - -cmd_net_sep::Separator = {st} comment:[ @:inline_comment ] newline - { {st} comment:[ ( @:line_comment | @:inline_comment ) ~ ] newline } - {st} ; - -#comments -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]*/ ; From 5c573d680975f29e4fc2177684974f33c6522982 Mon Sep 17 00:00:00 2001 From: jmgc Date: Tue, 18 May 2021 07:54:32 +0200 Subject: [PATCH 051/134] EBNF Spice Grammar definition for tatsu. Still being improved. --- PySpice/Spice/spicegrammar.ebnf | 541 ++++++++++++++++++++++++++++++++ 1 file changed, 541 insertions(+) diff --git a/PySpice/Spice/spicegrammar.ebnf b/PySpice/Spice/spicegrammar.ebnf index e69de29bb..f36a52e7d 100644 --- a/PySpice/Spice/spicegrammar.ebnf +++ b/PySpice/Spice/spicegrammar.ebnf @@ -0,0 +1,541 @@ +@@grammar :: Spice +@@whitespace :: // +@@ignorecase :: True +@@parseinfo :: True + +start::Circuit = [ ".TITLE" ] {st} title:text [ {newline}+ lines:lines ] [ newline ".END" ] {newline} ; + +# Lines + +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" ~ { newline }+ { { /[0-9a-f]/ } { newline }+ } "$CDNENCFINISH" ; + +device = | @:nonlinear_dependent_source # B + | @:capacitor # C + | @:diode # D + | @:voltage_controlled_voltage_source # E + | @:current_controlled_current_source # F + | @:voltage_controlled_current_source # G + | @:current_controlled_voltage_source # H + | @:current_source # I + | @:jfet # J + | @:inductor # L + | @:mosfet # M + | @:bjt # Q + | @:resistor # R + | @:switch # S + | @:voltage_source # V + | @:subcircuit ; # X + +nonlinear_dependent_source::NonLinearDependentSource = &"B" ~ dev:dev + sep:sep positive:node sep:sep negative:node ~ + sep:sep ("V" | "I") [sep:sep] "=" [sep:sep] expr:abm_expression [ sep:sep paramters: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 model:model_name sep:sep value:gen_expr | + sep:sep value:gen_expr | + sep:sep model:model_name ] [ 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 ] ; + +voltage_controlled_voltage_source::VoltageControlledVoltageSource = + &"E" ~ dev:dev sep:sep positive:node sep:sep negative:node ~ + sep:sep ( ( controller: ( control_value | + control_table | + control_voltage_poly) ) | + ( control_positive:node sep:sep control_negative:node sep:sep gain:gen_expr ) ) ; + +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 ( ( controller: ( control_value | + control_table | + control_voltage_poly) ) | + ( control_positive:node sep:sep control_negative:node sep:sep transconductance:gen_expr ) ) ; + +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] braced_expression [sep:sep] "=" + [sep:sep] (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 ) }+ ; + +control_voltage_poly::ControlVoltagePoly = + "POLY" ~ [sep:sep] lp [sep:sep] value:integer [sep:sep] rp + [sep:sep] (sep:sep)%{ 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 ] ; + +inductor::Inductor = &"L" ~ dev:dev sep:sep positive:node sep:sep negative:node ~ + [ sep:sep model:model_name sep:sep value:gen_expr | + sep:sep value:gen_expr | + sep:sep model:model_name ] [ 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: ("IC" [sep:sep] "=" ~ [sep:sep] ( [sep:sep] "," [sep:sep] )%{value} | parameter ) } ]; + + +bjt::BJT = &"Q" ~ dev:dev sep:sep collector:node sep:sep base:node sep:sep emitter:node ~ + sep:sep ( substrate:node sep:sep thermal:node sep:sep model:model_name | + ( substrate:{digit}+ | &"[" substrate:node ) sep:sep model:model_name | + thermal:node sep:sep model:model_name | + model:model_name ) [ sep:sep area:gen_expr ] [ sep:sep parameters:parameters ]; + +resistor::Resistor = &"R" ~ dev:dev sep:sep positive:node sep:sep negative:node ~ + [ sep:sep model:model_name sep:sep value:gen_expr | + sep:sep value:gen_expr | + sep:sep model:model_name ] [ sep:sep parameters:parameters ] ; + +switch::Switch = &"S" ~ dev:dev sep:sep positive:node sep:sep negative:node ~ + sep:sep ( model:model_name [ sep:sep ( "ON" | "OFF" ) ] sep:sep "control" ~ [sep:sep] "=" [sep:sep] braced_expression | + control_p:node sep:sep control_n:node sep:sep model:model_name ); + +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 ] ; + +subcircuit::Subcircuit = &"X" ~ dev:dev { sep:sep node:node }+ # We assume the model will be detected as a node + [ params:":" ~ [sep:sep] parameters:parameters ] ; # We also assume the params will be detected as a node + +dc = "DC" ; +ac = "AC" ; + +transient_specification = | @:transient_pulse + | @:transient_sin + | @:transient_exp + | @:transient_pat + | @:transient_pwl + | @:transient_sffm ; + +# The parenthesis are not mandatory + +transient_pulse = type:"PULSE" ( [sep:sep] lp ~ [sep:sep] @:pulse_arguments [sep:sep] rp + | sep:sep @:pulse_arguments ) ; + +pulse_arguments = v1:gen_expr sep:sep (sep:sep)%{gen_expr} ; + +transient_sin = type:"SIN" ( [sep:sep] lp ~ [sep:sep] @:sin_arguments [sep:sep] rp + | sep:sep @:sin_arguments ); + +sin_arguments = v0:gen_expr sep:sep va:gen_expr sep:sep freq:gen_expr + sep:sep (sep:sep)%{value:gen_expr} ; + +transient_exp = type:"EXP" ( [sep:sep] lp ~ [sep:sep] @:exp_arguments [sep:sep] rp + | sep:sep @:exp_arguments ) ; + +exp_arguments = v1:gen_expr sep:sep v2:gen_expr + sep:sep (sep:sep)%{value:gen_expr} ; + +transient_pat = type:"PAT" ( [sep:sep] lp ~ [sep:sep] @:pat_arguments [sep:sep] rp + | sep:sep @:pat_arguments ) ; + +pat_arguments = 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 = type:"PWL" ~ sep:sep ( "FILE" ~ sep:sep ( double_quote filename double_quote | + filename ) [sep:sep parameters:parameters] | + (sep:sep)%{t:value ~ sep:sep v:value}+ ) ; + +transient_sffm = type:"SFFM" ( [sep:sep] lp ~ [sep:sep] @:sffm_arguments [sep:sep] rp + | sep:sep @:sffm_arguments ) ; + +sffm_arguments = v0:gen_expr sep:sep va:gen_expr + sep:sep (sep:sep)%{value:gen_expr} ; + +command = | @:ac_cmd + | @:ic_cmd # To avoid issues with .DCVOLT and .DC + | @:dc_cmd + | @:embedded_sampling_cmd + | @:include_cmd + | @:lib_cmd + | @:netlist_cmds + | @:subckt_cmd + | @:simulator_cmd + | @:title_cmd ; + +netlist_cmds = | @:data_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:( ".INC" | ".INCLUDE" | ".INCL" ) ~ sep:sep ( filename | + single_quote ~ filename single_quote | + double_quote ~ filename double_quote ) ; + +lib_cmd::LibCmd = @:lib_block | @:lib_call ; + +lib_call = cmd:".LIB" sep:sep ( 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 ; + +#Ignore simulator command by Simetrix +simulator_cmd::SimulatorCmd = cmd:".SIMULATOR" ~ sep:sep simulator:id ; + +# The param in the subckt is read as a node. The colon is used to detect it and check if +# the previous node is PARAM in the grammar. + +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 + lines:netlist_lines + ".ENDS" ~ [ {st}+ end_name:model_name ]; + +lib_block = cmd:".LIB" sep:sep entry:id sep:cmd_net_sep ~ + lines:netlist_lines + ".ENDL" ~ [ {st}+ entry:id ] ; + +title_cmd::TitleCmd = cmd:".TITLE" ~ title:text ; + +# Parameters + +parameters = ( [@:sep] comma [@:sep] | @:sep )%{ @:parameter } ; + +parameter = name:id [sep:sep] "=" ~ [sep:sep] value:gen_expr ~ { [sep:sep] comma [sep:sep] value:gen_expr } ; + +# Expressions + +gen_expr = | @:braced_expression + | @:value ; + +tablefile = func:"tablefile" [sep:sep] lp ~ [sep:sep] ( double_quote ~ filename:filename double_quote + | filename:filename ) [sep:sep] rp ; + +braced_expression = lc ~ [sep:sep] expression [sep:sep] rc ; + +#functions + +functions = | functions_1 + | atan2 + | ddx + | gauss + | i_func + | if_func + | limit + | functions_2 + | rand + | unif + | v_func ; + +functions_1 = func:( "abs" + | "ceil" + | "ddt" + | "floor" + | "int" + | "m" + | "nint" + | "sdt" + | "sgn" + | "stp" + | "sqrt" + | "uramp" + | "Ph" + | "Re" + | "R" + | "Img" + | "Db" + | "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] "," + [sep:sep] x:expression [sep:sep] rp ; + +ddx = func:"ddx" [sep:sep] lp ~ [sep:sep] f:id [sep:sep] "," + [sep:sep] x:expression [sep:sep] rp ; + +gauss = func:( "agauss" + | "gauss" )[sep:sep] lp ~ [sep:sep] mu:expression [sep:sep] "," + [sep:sep] alpha:expression [sep:sep] "," + [sep:sep] n:expression [sep:sep] rp ; + +i_func = func:"i" [sep:sep] lp ~ [sep:sep] &"V" positive:dev [sep:sep] rp ; + +if_func = func:"if" [sep:sep] lp ~ [sep:sep] t:conditional_expression [sep:sep] "," + [sep:sep] x:expression [sep:sep] "," + [sep:sep] y:expression [sep:sep] rp ; + +limit = func:"limit" [sep:sep] lp ~ [sep:sep] x:expression [sep:sep] "," + [sep:sep] y:expression [sep:sep] "," + [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] "," + [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] "," + [sep:sep] alpha:expression [sep:sep] rp ; + +v_func = func:"v" [sep:sep] lp ~ [sep:sep] node:node [sep:sep] + [ "," [sep:sep] negative:node [sep:sep] ] rp ; + +# Relational and conditional expressions + +expression = | t:conditional_expression [sep:sep] op:"?" ~ [sep:sep] x:expression [sep:sep] ":" [sep:sep] y:expression + | term ; + +conditional_expression = | boolean_or + | boolean ; + +boolean_or = | left:boolean_or [sep:sep] op:"|" ~ [sep:sep] right:boolean_xor + | boolean_xor ; + +boolean_xor = | left:boolean_xor [sep:sep] op:"^" ~ [sep:sep] right:boolean_and + | boolean_and ; + +boolean_and = | left:boolean_and [sep:sep] op:"&" ~ [sep:sep] right:boolean_not + | boolean_not ; + +boolean_not = | op:"~" ~ operator:relational + | relational ; + +relational = left:term [sep:sep] op:( "==" | "!=" | ">=" | "<=" | ">" | "<" ) ~ [sep:sep] right:term ; + +# Arithmetic term + +term = | left:term [sep:sep] op:( "+" | "-" ) ~ [sep:sep] right:prod + | prod ; + +prod = | left:prod [sep:sep] op:( "*" | "/" | "%" ) ~ [sep:sep] right:unary + | unary ; + +unary = | op:( "+" | "-" ) ~ operator:exp + | exp ; + +exp = | left:exp [sep:sep] op:'**' ~ [sep:sep] right:factor + | factor ; + +factor = | '(' ~ [sep:sep] @:expression [sep:sep] ')' + | @:functions + | var:id + | @:value ; + +special_variables = | "time" + | "temper" + | "temp" + | "freq" + | "vt" + | "pi" ; + +value::Value = | ( real:real_value "+" imag:imag_value ) + | imag:imag_value + | real:real_value ; + +imag_value::ImagValue = @:real_value "J" ; +real_value::RealValue = value:number_scale unit:[hz | unit] ; + +freq_value = value:number_scale unit:[hz] ; + +number_scale::NumberScale = | value:floating_point scale:(meg | mil | [suffix]) + | value:integer scale:(meg | mil | [suffix]) ; + +suffix = /[tTgGkKmMxXuUnNpPfF]/ ; + +meg = /[mM][eE][gG]/ ; +mil = /[mM][iI][lL]/ ; + +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 = /[a-zA-Z0-9_:@#\.\$\/]+/ ; + +boolean = | "TRUE" + | "FALSE" ; + +# Patterns + +model_name = name:/[a-zA-Z0-9_]+/ !([sep:sep] "=") ; + +binary_pattern = /[Bb]/ pattern:{binary}+ ; + +binary = /[01]/ ; + +# to support bug 1034, the DEV needs to recognize some weird characters, including '+' and '-' + +dev = /[a-zA-Z][a-zA-Z0-9_:!`@#\.\+\-]*/ ; + +# to support bug 1034, the NODE needs to recognize a lot of weird characters: +# from bug 1034: +# ` ~ ! @ # $ % ^ & - _ + [ ] | \ < > . ? | +# unfortunately, some of these are special regex characters, so they need to be +# handled with a preceding backslash. Probably more elegant regex, such as a +# range of ASCII characters could be used for NODE, but this seems to work for now + +node = | /[a-zA-Z0-9_\[\$][a-zA-Z0-9_:\$\-`~!@#%&_\+|<>\?\.\\|\^\*]*[a-zA-Z0-9_\$\-`~!@#%&_\+|<>\?\.\\|\^\*\]]/ + | /[a-zA-Z0-9_]/ ; + +id = | /[a-zA-Z_`@#\$][a-zA-Z0-9_:`@#\.\$\/]*[a-zA-Z0-9_`@#\.\$]/ + | /[a-zA-Z_`@#\$]/ ; + +# Separators + +end_sep = | @:cmd_net_sep + | {st}+; + +sep = | @:cmd_net_sep '+' {st} + | {st}+ ; + +cmd_net_sep::Separator = {st} comment:[ @:inline_comment ] newline + { {st} comment:[ ( @:line_comment | @:inline_comment ) ~ ] newline } + {st} ; + +#comments +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]*/ ; From 3b815b9d5cbe91694d7ae2ad8481e2032b85726e Mon Sep 17 00:00:00 2001 From: jmgc Date: Tue, 18 May 2021 07:57:54 +0200 Subject: [PATCH 052/134] EBNF Spice Grammar tatsu formatted. --- PySpice/Spice/spicegrammar.ebnf | 1947 +++++++++++++++++++++++-------- 1 file changed, 1469 insertions(+), 478 deletions(-) diff --git a/PySpice/Spice/spicegrammar.ebnf b/PySpice/Spice/spicegrammar.ebnf index f36a52e7d..5fe9c948c 100644 --- a/PySpice/Spice/spicegrammar.ebnf +++ b/PySpice/Spice/spicegrammar.ebnf @@ -3,539 +3,1530 @@ @@ignorecase :: True @@parseinfo :: True -start::Circuit = [ ".TITLE" ] {st} title:text [ {newline}+ lines:lines ] [ newline ".END" ] {newline} ; - -# Lines - -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" ~ { newline }+ { { /[0-9a-f]/ } { newline }+ } "$CDNENCFINISH" ; - -device = | @:nonlinear_dependent_source # B - | @:capacitor # C - | @:diode # D - | @:voltage_controlled_voltage_source # E - | @:current_controlled_current_source # F - | @:voltage_controlled_current_source # G - | @:current_controlled_voltage_source # H - | @:current_source # I - | @:jfet # J - | @:inductor # L - | @:mosfet # M - | @:bjt # Q - | @:resistor # R - | @:switch # S - | @:voltage_source # V - | @:subcircuit ; # X - -nonlinear_dependent_source::NonLinearDependentSource = &"B" ~ dev:dev - sep:sep positive:node sep:sep negative:node ~ - sep:sep ("V" | "I") [sep:sep] "=" [sep:sep] expr:abm_expression [ sep:sep paramters: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 model:model_name sep:sep value:gen_expr | - sep:sep value:gen_expr | - sep:sep model:model_name ] [ 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 ] ; - -voltage_controlled_voltage_source::VoltageControlledVoltageSource = - &"E" ~ dev:dev sep:sep positive:node sep:sep negative:node ~ - sep:sep ( ( controller: ( control_value | - control_table | - control_voltage_poly) ) | - ( control_positive:node sep:sep control_negative:node sep:sep gain:gen_expr ) ) ; - -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 ( ( controller: ( control_value | - control_table | - control_voltage_poly) ) | - ( control_positive:node sep:sep control_negative:node sep:sep transconductance:gen_expr ) ) ; - -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] braced_expression [sep:sep] "=" - [sep:sep] (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 ) }+ ; - -control_voltage_poly::ControlVoltagePoly = - "POLY" ~ [sep:sep] lp [sep:sep] value:integer [sep:sep] rp - [sep:sep] (sep:sep)%{ 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 ] ; - -inductor::Inductor = &"L" ~ dev:dev sep:sep positive:node sep:sep negative:node ~ - [ sep:sep model:model_name sep:sep value:gen_expr | - sep:sep value:gen_expr | - sep:sep model:model_name ] [ 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: ("IC" [sep:sep] "=" ~ [sep:sep] ( [sep:sep] "," [sep:sep] )%{value} | parameter ) } ]; - - -bjt::BJT = &"Q" ~ dev:dev sep:sep collector:node sep:sep base:node sep:sep emitter:node ~ - sep:sep ( substrate:node sep:sep thermal:node sep:sep model:model_name | - ( substrate:{digit}+ | &"[" substrate:node ) sep:sep model:model_name | - thermal:node sep:sep model:model_name | - model:model_name ) [ sep:sep area:gen_expr ] [ sep:sep parameters:parameters ]; - -resistor::Resistor = &"R" ~ dev:dev sep:sep positive:node sep:sep negative:node ~ - [ sep:sep model:model_name sep:sep value:gen_expr | - sep:sep value:gen_expr | - sep:sep model:model_name ] [ sep:sep parameters:parameters ] ; - -switch::Switch = &"S" ~ dev:dev sep:sep positive:node sep:sep negative:node ~ - sep:sep ( model:model_name [ sep:sep ( "ON" | "OFF" ) ] sep:sep "control" ~ [sep:sep] "=" [sep:sep] braced_expression | - control_p:node sep:sep control_n:node sep:sep model:model_name ); - -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 ] ; - -subcircuit::Subcircuit = &"X" ~ dev:dev { sep:sep node:node }+ # We assume the model will be detected as a node - [ params:":" ~ [sep:sep] parameters:parameters ] ; # We also assume the params will be detected as a node - -dc = "DC" ; -ac = "AC" ; - -transient_specification = | @:transient_pulse - | @:transient_sin - | @:transient_exp - | @:transient_pat - | @:transient_pwl - | @:transient_sffm ; - -# The parenthesis are not mandatory - -transient_pulse = type:"PULSE" ( [sep:sep] lp ~ [sep:sep] @:pulse_arguments [sep:sep] rp - | sep:sep @:pulse_arguments ) ; - -pulse_arguments = v1:gen_expr sep:sep (sep:sep)%{gen_expr} ; - -transient_sin = type:"SIN" ( [sep:sep] lp ~ [sep:sep] @:sin_arguments [sep:sep] rp - | sep:sep @:sin_arguments ); - -sin_arguments = v0:gen_expr sep:sep va:gen_expr sep:sep freq:gen_expr - sep:sep (sep:sep)%{value:gen_expr} ; - -transient_exp = type:"EXP" ( [sep:sep] lp ~ [sep:sep] @:exp_arguments [sep:sep] rp - | sep:sep @:exp_arguments ) ; - -exp_arguments = v1:gen_expr sep:sep v2:gen_expr - sep:sep (sep:sep)%{value:gen_expr} ; - -transient_pat = type:"PAT" ( [sep:sep] lp ~ [sep:sep] @:pat_arguments [sep:sep] rp - | sep:sep @:pat_arguments ) ; - -pat_arguments = 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 = type:"PWL" ~ sep:sep ( "FILE" ~ sep:sep ( double_quote filename double_quote | - filename ) [sep:sep parameters:parameters] | - (sep:sep)%{t:value ~ sep:sep v:value}+ ) ; - -transient_sffm = type:"SFFM" ( [sep:sep] lp ~ [sep:sep] @:sffm_arguments [sep:sep] rp - | sep:sep @:sffm_arguments ) ; - -sffm_arguments = v0:gen_expr sep:sep va:gen_expr - sep:sep (sep:sep)%{value:gen_expr} ; - -command = | @:ac_cmd - | @:ic_cmd # To avoid issues with .DCVOLT and .DC - | @:dc_cmd - | @:embedded_sampling_cmd - | @:include_cmd - | @:lib_cmd - | @:netlist_cmds - | @:subckt_cmd - | @:simulator_cmd - | @:title_cmd ; - -netlist_cmds = | @:data_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:( ".INC" | ".INCLUDE" | ".INCL" ) ~ sep:sep ( filename | - single_quote ~ filename single_quote | - double_quote ~ filename double_quote ) ; - -lib_cmd::LibCmd = @:lib_block | @:lib_call ; - -lib_call = cmd:".LIB" sep:sep ( 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 ; - -#Ignore simulator command by Simetrix -simulator_cmd::SimulatorCmd = cmd:".SIMULATOR" ~ sep:sep simulator:id ; - -# The param in the subckt is read as a node. The colon is used to detect it and check if -# the previous node is PARAM in the grammar. - -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 +start::Circuit + = + ['.TITLE'] + {st} + title:text + [{newline}+ lines:lines] + [newline '.END'] + {newline} + ; + + +lines::Lines + = + description:{@:circuit_line} + ; + + +circuit_line::CircuitLine + = + description:(@:device | @:command | encrypted) @:end_sep + ; + + +netlist_lines::NetlistLines + = + description:{@:netlist_line} + ; + + +netlist_line::NetlistLine + = + description:(@:device | @:netlist_cmds | encrypted) @:end_sep + ; + + +encrypted + = + '$CDNENCSTART' ~ {newline}+ {{/[0-9a-f]/} {newline}+} '$CDNENCFINISH' + ; + + +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 + | @:inductor + | @:mosfet + | @:bjt + | @:resistor + | @:switch + | @:voltage_source + | @:subcircuit + ; + + +nonlinear_dependent_source::NonLinearDependentSource + = + &'B' + ~ + dev:dev + sep:sep + positive:node + sep:sep + negative:node + ~ + sep:sep + ('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 model:model_name sep:sep value:gen_expr + | sep:sep value:gen_expr + | sep:sep model:model_name + ] + + [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] + ; + + +voltage_controlled_voltage_source::VoltageControlledVoltageSource + = + &'E' + ~ + dev:dev + sep:sep + positive:node + sep:sep + negative:node + ~ + sep:sep + ( + (controller:(control_value | control_table | control_voltage_poly)) + | + ( + control_positive:node + sep:sep + control_negative:node + sep:sep + gain:gen_expr + ) + ) + ; + + +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 + ( + (controller:(control_value | control_table | control_voltage_poly)) + | + ( + control_positive:node + sep:sep + control_negative:node + sep:sep + transconductance:gen_expr + ) + ) + ; + + +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] + braced_expression + [sep:sep] + '=' + [sep:sep] + (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) + }+ + ; + + +control_voltage_poly::ControlVoltagePoly + = + 'POLY' + ~ + [sep:sep] + lp + [sep:sep] + value:integer + [sep:sep] + rp + [sep:sep] + (sep:sep)%{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] + ; + + +inductor::Inductor + = + &'L' + ~ + dev:dev + sep:sep + positive:node + sep:sep + negative:node + ~ + [ + | sep:sep model:model_name sep:sep value:gen_expr + | sep:sep value:gen_expr + | sep:sep model:model_name + ] + + [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:( + | 'IC' [sep:sep] '=' ~ [sep:sep] ([sep:sep] ',' [sep:sep])%{value} + | parameter + ) + }] + ; + + +bjt::BJT + = + &'Q' + ~ + dev:dev + sep:sep + collector:node + sep:sep + base:node + sep:sep + emitter:node + ~ + sep:sep + ( + | substrate:node sep:sep thermal:node sep:sep model:model_name + | (substrate:{digit}+ | &'[' substrate:node) sep:sep model:model_name + | thermal:node sep:sep model:model_name + | model:model_name + ) + [sep:sep area:gen_expr] + [sep:sep parameters:parameters] + ; + + +resistor::Resistor + = + &'R' + ~ + dev:dev + sep:sep + positive:node + sep:sep + negative:node + ~ + [ + | sep:sep model:model_name sep:sep value:gen_expr + | sep:sep value:gen_expr + | sep:sep model:model_name + ] + + [sep:sep parameters:parameters] + ; + + +switch::Switch + = + &'S' + ~ + dev:dev + sep:sep + positive:node + sep:sep + negative:node + ~ + sep:sep + ( + model:model_name + [sep:sep ('ON' | 'OFF')] + sep:sep + 'control' + ~ + [sep:sep] + '=' + [sep:sep] + braced_expression + | + control_p:node sep:sep control_n:node sep:sep model:model_name + ) + ; + + +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] + ; + + +subcircuit::Subcircuit + = + &'X' + ~ + dev:dev + {sep:sep node:node}+ + [params:':' ~ [sep:sep] parameters:parameters] + ; + + +dc + = + 'DC' + ; + + +ac + = + 'AC' + ; + + +transient_specification + = + | @:transient_pulse + | @:transient_sin + | @:transient_exp + | @:transient_pat + | @:transient_pwl + | @:transient_sffm + ; + + +transient_pulse + = + type:'PULSE' + ( + | [sep:sep] lp ~ [sep:sep] @:pulse_arguments [sep:sep] rp + | sep:sep @:pulse_arguments + ) + ; + + +pulse_arguments + = + v1:gen_expr sep:sep (sep:sep)%{gen_expr} + ; + + +transient_sin + = + type:'SIN' + ( + | [sep:sep] lp ~ [sep:sep] @:sin_arguments [sep:sep] rp + | sep:sep @:sin_arguments + ) + ; + + +sin_arguments + = + v0:gen_expr + sep:sep + va:gen_expr + sep:sep + freq:gen_expr + sep:sep + (sep:sep)%{value:gen_expr} + ; + + +transient_exp + = + type:'EXP' + ( + | [sep:sep] lp ~ [sep:sep] @:exp_arguments [sep:sep] rp + | sep:sep @:exp_arguments + ) + ; + + +exp_arguments + = + v1:gen_expr sep:sep v2:gen_expr sep:sep (sep:sep)%{value:gen_expr} + ; + + +transient_pat + = + type:'PAT' + ( + | [sep:sep] lp ~ [sep:sep] @:pat_arguments [sep:sep] rp + | sep:sep @:pat_arguments + ) + ; + + +pat_arguments + = + 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 + = + type:'PWL' + ~ + sep:sep + ( + 'FILE' + ~ + sep:sep + (double_quote filename double_quote | filename) + [sep:sep parameters:parameters] + | + (sep:sep)%{t:value ~ sep:sep v:value}+ + ) + ; + + +transient_sffm + = + type:'SFFM' + ( + | [sep:sep] lp ~ [sep:sep] @:sffm_arguments [sep:sep] rp + | sep:sep @:sffm_arguments + ) + ; + + +sffm_arguments + = + v0:gen_expr sep:sep va:gen_expr sep:sep (sep:sep)%{value:gen_expr} + ; + + +command + = + | @:ac_cmd + | @:ic_cmd + | @:dc_cmd + | @:embedded_sampling_cmd + | @:include_cmd + | @:lib_cmd + | @:netlist_cmds + | @:subckt_cmd + | @:simulator_cmd + | @:title_cmd + ; + + +netlist_cmds + = + @:data_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:('.INC' | '.INCLUDE' | '.INCL') + ~ + sep:sep + ( + | filename + | single_quote ~ filename single_quote + | double_quote ~ filename double_quote + ) + ; + + +lib_cmd::LibCmd + = + @:lib_block | @:lib_call + ; + + +lib_call + = + cmd:'.LIB' + sep:sep + ( + | 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 lines:netlist_lines - ".ENDS" ~ [ {st}+ end_name:model_name ]; - -lib_block = cmd:".LIB" sep:sep entry:id sep:cmd_net_sep ~ + '.ENDS' + ~ + [{st}+ end_name:model_name] + ; + + +lib_block + = + cmd:'.LIB' + sep:sep + entry:id + sep:cmd_net_sep + ~ lines:netlist_lines - ".ENDL" ~ [ {st}+ entry:id ] ; - -title_cmd::TitleCmd = cmd:".TITLE" ~ title:text ; - -# Parameters - -parameters = ( [@:sep] comma [@:sep] | @:sep )%{ @:parameter } ; - -parameter = name:id [sep:sep] "=" ~ [sep:sep] value:gen_expr ~ { [sep:sep] comma [sep:sep] value:gen_expr } ; - -# Expressions - -gen_expr = | @:braced_expression - | @:value ; - -tablefile = func:"tablefile" [sep:sep] lp ~ [sep:sep] ( double_quote ~ filename:filename double_quote - | filename:filename ) [sep:sep] rp ; - -braced_expression = lc ~ [sep:sep] expression [sep:sep] rc ; - -#functions - -functions = | functions_1 - | atan2 - | ddx - | gauss - | i_func - | if_func - | limit - | functions_2 - | rand - | unif - | v_func ; - -functions_1 = func:( "abs" - | "ceil" - | "ddt" - | "floor" - | "int" - | "m" - | "nint" - | "sdt" - | "sgn" - | "stp" - | "sqrt" - | "uramp" - | "Ph" - | "Re" - | "R" - | "Img" - | "Db" - | "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 ; + '.ENDL' + ~ + [{st}+ entry:id] + ; + + +title_cmd::TitleCmd + = + cmd:'.TITLE' ~ title:text + ; + + +parameters + = + ([@:sep] comma [@:sep] | @:sep)%{@:parameter} + ; + + +parameter + = + name:id + [sep:sep] + '=' + ~ + [sep:sep] + value:gen_expr + ~ + {[sep:sep] comma [sep:sep] value:gen_expr} + ; + + +gen_expr + = + @:braced_expression | @:value + ; + + +tablefile + = + func:'tablefile' + [sep:sep] + lp + ~ + [sep:sep] + (double_quote ~ filename:filename double_quote | filename:filename) + [sep:sep] + rp + ; + + +braced_expression + = + lc ~ [sep:sep] expression [sep:sep] rc + ; + + +functions + = + | functions_1 + | atan2 + | ddx + | gauss + | i_func + | if_func + | limit + | functions_2 + | rand + | unif + | v_func + ; + + +functions_1 + = + func:( + | 'abs' + | 'ceil' + | 'ddt' + | 'floor' + | 'int' + | 'm' + | 'nint' + | 'sdt' + | 'sgn' + | 'stp' + | 'sqrt' + | 'uramp' + | 'Ph' + | 'Re' + | 'R' + | 'Img' + | 'Db' + | '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] + ',' + [sep:sep] + x:expression + [sep:sep] + rp + ; + + +ddx + = + func:'ddx' + [sep:sep] + lp + ~ + [sep:sep] + f:id + [sep:sep] + ',' + [sep:sep] + x:expression + [sep:sep] + rp + ; + + +gauss + = + func:('agauss' | 'gauss') + [sep:sep] + lp + ~ + [sep:sep] + mu:expression + [sep:sep] + ',' + [sep:sep] + alpha:expression + [sep:sep] + ',' + [sep:sep] + n:expression + [sep:sep] + rp + ; + + +i_func + = + func:'i' [sep:sep] lp ~ [sep:sep] &'V' positive:dev [sep:sep] rp + ; + + +if_func + = + func:'if' + [sep:sep] + lp + ~ + [sep:sep] + t:conditional_expression + [sep:sep] + ',' + [sep:sep] + x:expression + [sep:sep] + ',' + [sep:sep] + y:expression + [sep:sep] + rp + ; + + +limit + = + func:'limit' + [sep:sep] + lp + ~ + [sep:sep] + x:expression + [sep:sep] + ',' + [sep:sep] + y:expression + [sep:sep] + ',' + [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] + ',' + [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] + ',' + [sep:sep] + alpha:expression + [sep:sep] + rp + ; + + +v_func + = + func:'v' + [sep:sep] + lp + ~ + [sep:sep] + node:node + [sep:sep] + [',' [sep:sep] negative:node [sep:sep]] + rp + ; + + +expression + = + t:conditional_expression + [sep:sep] + op:'?' + ~ + [sep:sep] + x:expression + [sep:sep] + ':' + [sep:sep] + y:expression + | + term + ; + + +conditional_expression + = + boolean_or | boolean + ; + + +boolean_or + = + | left:boolean_or [sep:sep] op:'|' ~ [sep:sep] right:boolean_xor + | boolean_xor + ; + + +boolean_xor + = + | left:boolean_xor [sep:sep] op:'^' ~ [sep:sep] right:boolean_and + | boolean_and + ; + + +boolean_and + = + | left:boolean_and [sep:sep] op:'&' ~ [sep:sep] right:boolean_not + | boolean_not + ; + + +boolean_not + = + op:'~' ~ operator:relational | relational + ; + + +relational + = + left:term + [sep:sep] + op:('==' | '!=' | '>=' | '<=' | '>' | '<') + ~ + [sep:sep] + right:term + ; + + +term + = + left:term [sep:sep] op:('+' | '-') ~ [sep:sep] right:prod | prod + ; + + +prod + = + left:prod [sep:sep] op:('*' | '/' | '%') ~ [sep:sep] right:unary | unary + ; + + +unary + = + op:('+' | '-') ~ operator:exp | exp + ; + + +exp + = + left:exp [sep:sep] op:'**' ~ [sep:sep] right:factor | factor + ; + + +factor + = + | '(' ~ [sep:sep] @:expression [sep:sep] ')' + | @:functions + | var:id + | @:value + ; + + +special_variables + = + 'time' | 'temper' | 'temp' | 'freq' | 'vt' | 'pi' + ; + + +value::Value + = + | (real:real_value '+' imag:imag_value) + | imag:imag_value + | real:real_value + ; + + +imag_value::ImagValue + = + @:real_value 'J' + ; + + +real_value::RealValue + = + value:number_scale + unit:[ + hz | unit + ] + ; + + +freq_value + = + value:number_scale unit:[hz] + ; + + +number_scale::NumberScale + = + | value:floating_point scale:(meg | mil | [suffix]) + | value:integer scale:(meg | mil | [suffix]) + ; + + +suffix + = + /[tTgGkKmMxXuUnNpPfF]/ + ; + + +meg + = + /[mM][eE][gG]/ + ; + + +mil + = + /[mM][iI][lL]/ + ; + + +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 + = + ?"[a-zA-Z0-9_:@#\.\$\/]+" + ; + + +boolean + = + 'TRUE' | 'FALSE' + ; + + +model_name + = + name:/[a-zA-Z0-9_]+/ !([sep:sep] '=') + ; -atan2 = func:"atan2" [sep:sep] lp ~ [sep:sep] y:expression [sep:sep] "," - [sep:sep] x:expression [sep:sep] rp ; -ddx = func:"ddx" [sep:sep] lp ~ [sep:sep] f:id [sep:sep] "," - [sep:sep] x:expression [sep:sep] rp ; +binary_pattern + = + /[Bb]/ pattern:{binary}+ + ; -gauss = func:( "agauss" - | "gauss" )[sep:sep] lp ~ [sep:sep] mu:expression [sep:sep] "," - [sep:sep] alpha:expression [sep:sep] "," - [sep:sep] n:expression [sep:sep] rp ; -i_func = func:"i" [sep:sep] lp ~ [sep:sep] &"V" positive:dev [sep:sep] rp ; +binary + = + /[01]/ + ; -if_func = func:"if" [sep:sep] lp ~ [sep:sep] t:conditional_expression [sep:sep] "," - [sep:sep] x:expression [sep:sep] "," - [sep:sep] y:expression [sep:sep] rp ; -limit = func:"limit" [sep:sep] lp ~ [sep:sep] x:expression [sep:sep] "," - [sep:sep] y:expression [sep:sep] "," - [sep:sep] z:expression [sep:sep] rp ; +dev + = + /[a-zA-Z][a-zA-Z0-9_:!`@#\.\+\-]*/ + ; -functions_2 = func:( "min" - | "max" - | "pwrs" - | "pow" - | "pwr" - | "sign" ) [sep:sep] lp ~ [sep:sep] x:expression [sep:sep] "," - [sep:sep] y:expression [sep:sep] rp ; -rand = func:"rand" [sep:sep] lp ~ [sep:sep] rp ; +node + = + | /[a-zA-Z0-9_\[\$][a-zA-Z0-9_:\$\-`~!@#%&_\+|<>\?\.\\|\^\*]*[a-zA-Z0-9_\$\-`~!@#%&_\+|<>\?\.\\|\^\*\]]/ + | /[a-zA-Z0-9_]/ + ; -unif = func:( "aunif" - | "unif" ) [sep:sep] lp ~ [sep:sep] mu:expression [sep:sep] "," - [sep:sep] alpha:expression [sep:sep] rp ; -v_func = func:"v" [sep:sep] lp ~ [sep:sep] node:node [sep:sep] - [ "," [sep:sep] negative:node [sep:sep] ] rp ; +id + = + | ?"[a-zA-Z_`@#\$][a-zA-Z0-9_:`@#\.\$\/]*[a-zA-Z0-9_`@#\.\$]" + | /[a-zA-Z_`@#\$]/ + ; -# Relational and conditional expressions -expression = | t:conditional_expression [sep:sep] op:"?" ~ [sep:sep] x:expression [sep:sep] ":" [sep:sep] y:expression - | term ; +end_sep + = + @:cmd_net_sep | {st}+ + ; -conditional_expression = | boolean_or - | boolean ; -boolean_or = | left:boolean_or [sep:sep] op:"|" ~ [sep:sep] right:boolean_xor - | boolean_xor ; +sep + = + @:cmd_net_sep '+' {st} | {st}+ + ; -boolean_xor = | left:boolean_xor [sep:sep] op:"^" ~ [sep:sep] right:boolean_and - | boolean_and ; -boolean_and = | left:boolean_and [sep:sep] op:"&" ~ [sep:sep] right:boolean_not - | boolean_not ; +cmd_net_sep::Separator + = + {st} + comment:[@:inline_comment] + newline + {{st} comment:[(@:line_comment | @:inline_comment) ~] newline} + {st} + ; -boolean_not = | op:"~" ~ operator:relational - | relational ; -relational = left:term [sep:sep] op:( "==" | "!=" | ">=" | "<=" | ">" | "<" ) ~ [sep:sep] right:term ; +inline_comment + = + semicolon {st} @:text + ; -# Arithmetic term -term = | left:term [sep:sep] op:( "+" | "-" ) ~ [sep:sep] right:prod - | prod ; +line_comment + = + asterisk {st} @:text + ; -prod = | left:prod [sep:sep] op:( "*" | "/" | "%" ) ~ [sep:sep] right:unary - | unary ; -unary = | op:( "+" | "-" ) ~ operator:exp - | exp ; +text::Comment + = + /[^\r\n]*/ + ; -exp = | left:exp [sep:sep] op:'**' ~ [sep:sep] right:factor - | factor ; -factor = | '(' ~ [sep:sep] @:expression [sep:sep] ')' - | @:functions - | var:id - | @:value ; +asterisk + = + '*' + ; -special_variables = | "time" - | "temper" - | "temp" - | "freq" - | "vt" - | "pi" ; -value::Value = | ( real:real_value "+" imag:imag_value ) - | imag:imag_value - | real:real_value ; +question_mark + = + '?' + ; -imag_value::ImagValue = @:real_value "J" ; -real_value::RealValue = value:number_scale unit:[hz | unit] ; -freq_value = value:number_scale unit:[hz] ; +colon + = + ':' + ; -number_scale::NumberScale = | value:floating_point scale:(meg | mil | [suffix]) - | value:integer scale:(meg | mil | [suffix]) ; -suffix = /[tTgGkKmMxXuUnNpPfF]/ ; +semicolon + = + ';' + ; -meg = /[mM][eE][gG]/ ; -mil = /[mM][iI][lL]/ ; -unit::Unit = /[a-zA-Z%]+/ ; +comma + = + ',' + ; -hz::Hz = /[Hh][Zz]/ ; -lead_name = /I[SDGBEC1-9]/ ; +dot + = + '.' + ; -floating_point::Float = /[\+\-]?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))([eE][\-\+]?[0-9]{1,3})?/ ; -integer::Int = /[\+\-]?[0-9]+/ ; +dollar + = + '\\$' + ; -digit = /[0-9]/ ; -filename = /[a-zA-Z0-9_:@#\.\$\/]+/ ; +double_bar + = + '//' + ; -boolean = | "TRUE" - | "FALSE" ; -# Patterns +single_quote + = + "'" + ; -model_name = name:/[a-zA-Z0-9_]+/ !([sep:sep] "=") ; -binary_pattern = /[Bb]/ pattern:{binary}+ ; +double_quote + = + '"' + ; -binary = /[01]/ ; -# to support bug 1034, the DEV needs to recognize some weird characters, including '+' and '-' +lc + = + '{' + ; -dev = /[a-zA-Z][a-zA-Z0-9_:!`@#\.\+\-]*/ ; -# to support bug 1034, the NODE needs to recognize a lot of weird characters: -# from bug 1034: -# ` ~ ! @ # $ % ^ & - _ + [ ] | \ < > . ? | -# unfortunately, some of these are special regex characters, so they need to be -# handled with a preceding backslash. Probably more elegant regex, such as a -# range of ASCII characters could be used for NODE, but this seems to work for now +rc + = + '}' + ; -node = | /[a-zA-Z0-9_\[\$][a-zA-Z0-9_:\$\-`~!@#%&_\+|<>\?\.\\|\^\*]*[a-zA-Z0-9_\$\-`~!@#%&_\+|<>\?\.\\|\^\*\]]/ - | /[a-zA-Z0-9_]/ ; -id = | /[a-zA-Z_`@#\$][a-zA-Z0-9_:`@#\.\$\/]*[a-zA-Z0-9_`@#\.\$]/ - | /[a-zA-Z_`@#\$]/ ; +lp + = + '(' + ; -# Separators -end_sep = | @:cmd_net_sep - | {st}+; +rp + = + ')' + ; -sep = | @:cmd_net_sep '+' {st} - | {st}+ ; -cmd_net_sep::Separator = {st} comment:[ @:inline_comment ] newline - { {st} comment:[ ( @:line_comment | @:inline_comment ) ~ ] newline } - {st} ; +newline + = + /[\r\n]/ + ; -#comments -inline_comment = semicolon {st} @:text ; -line_comment = asterisk {st} @:text ; -text::Comment = /[^\r\n]*/ ; -asterisk = "*" ; -question_mark = "?" ; -colon = ":" ; -semicolon = ";" ; -comma = "," ; -dot = "." ; -dollar = "\$" ; -double_bar = "//" ; +st + = + /[ \t]/ + ; -single_quote = "'" ; -double_quote = '"' ; -lc = "{" ; -rc = "}" ; -lp = "(" ; -rp = ")" ; -newline = /[\r\n]/ ; -st = /[ \t]/ ; -ws = /[^\S\r\n]*/ ; +ws + = + /[^\S\r\n]*/ + ; From 2ab8169391579d931167d5ad0892d026056077f1 Mon Sep 17 00:00:00 2001 From: jmgc Date: Thu, 20 May 2021 18:43:43 +0200 Subject: [PATCH 053/134] EBNF Spice Grammar tatsu formatted. --- PySpice/Spice/spicegrammar.ebnf | 176 ++++++++++++++++---------------- 1 file changed, 87 insertions(+), 89 deletions(-) diff --git a/PySpice/Spice/spicegrammar.ebnf b/PySpice/Spice/spicegrammar.ebnf index 5fe9c948c..8ed9c8f54 100644 --- a/PySpice/Spice/spicegrammar.ebnf +++ b/PySpice/Spice/spicegrammar.ebnf @@ -2,6 +2,7 @@ @@whitespace :: // @@ignorecase :: True @@parseinfo :: True +@@left_recursion :: True start::Circuit = @@ -16,25 +17,25 @@ start::Circuit lines::Lines = - description:{@:circuit_line} + {@:circuit_line} ; circuit_line::CircuitLine = - description:(@:device | @:command | encrypted) @:end_sep + (@:device | @:command | encrypted) @:end_sep ; netlist_lines::NetlistLines = - description:{@:netlist_line} + {@:netlist_line} ; netlist_line::NetlistLine = - description:(@:device | @:netlist_cmds | encrypted) @:end_sep + (@:device | @:netlist_cmds | encrypted) @:end_sep ; @@ -81,7 +82,7 @@ nonlinear_dependent_source::NonLinearDependentSource '=' [sep:sep] expr:abm_expression - [sep:sep parameters:parameters] + [sep:sep parameters+:parameters] ; @@ -109,7 +110,7 @@ capacitor::Capacitor | sep:sep model:model_name ] - [sep:sep parameters:parameters] + [sep:sep parameters+:parameters] ; @@ -231,16 +232,16 @@ control_table::ControlTable ( lp [sep:sep] - input:value + input+:value [sep:sep] comma [sep:sep] - output:value + output+:value [sep:sep] rp ) | - (input:value [sep:sep] comma [sep:sep] output:value) + (input+:value [sep:sep] comma [sep:sep] output+:value) }+ ; @@ -256,7 +257,7 @@ control_voltage_poly::ControlVoltagePoly [sep:sep] rp [sep:sep] - (sep:sep)%{positive:node sep:sep negative:node | coefficient:value} + (sep:sep)%{positive+:node sep:sep negative+:node | coefficient:value} ; @@ -271,7 +272,7 @@ control_current_poly::ControlCurrentPoly [sep:sep] rp [sep:sep] - (sep:sep)%{device:dev | coefficient:value} + (sep:sep)%{device+:dev | coefficient+:value} ; @@ -306,7 +307,7 @@ jfet::JFET sep:sep model:model_name [sep:sep area:gen_expr] - [sep:sep parameters:parameters] + [sep:sep parameters+:parameters] ; @@ -326,7 +327,7 @@ inductor::Inductor | sep:sep model:model_name ] - [sep:sep parameters:parameters] + [sep:sep parameters+:parameters] ; @@ -348,8 +349,8 @@ mosfet::MOSFET model:model_name [sep:sep (sep:sep)%{ - param:( - | 'IC' [sep:sep] '=' ~ [sep:sep] ([sep:sep] ',' [sep:sep])%{value} + param+:( + | name:'IC' [sep:sep] '=' ~ [sep:sep] ([sep:sep] ',' [sep:sep])%{value} | parameter ) }] @@ -376,7 +377,7 @@ bjt::BJT | model:model_name ) [sep:sep area:gen_expr] - [sep:sep parameters:parameters] + [sep:sep parameters+:parameters] ; @@ -396,7 +397,7 @@ resistor::Resistor | sep:sep model:model_name ] - [sep:sep parameters:parameters] + [sep:sep parameters+:parameters] ; @@ -427,6 +428,16 @@ switch::Switch ; +subcircuit::Subcircuit + = + &'X' + ~ + dev:dev + {sep:sep node+:node} + [params:':' ~ [sep:sep] parameters+:parameters] + ; + + voltage_source::VoltageSource = &'V' @@ -443,16 +454,6 @@ voltage_source::VoltageSource ; -subcircuit::Subcircuit - = - &'X' - ~ - dev:dev - {sep:sep node:node}+ - [params:':' ~ [sep:sep] parameters:parameters] - ; - - dc = 'DC' @@ -776,17 +777,18 @@ include_cmd::IncludeCmd lib_cmd::LibCmd = - @:lib_block | @:lib_call + cmd:'.LIB' + sep:sep + ~ + block::LibBlock:lib_block | call::LibCall:lib_call ; lib_call = - cmd:'.LIB' - sep:sep ( - | double_quote ~ filename:filename double_quote - | single_quote ~ filename:filename single_quote + | double_quote filename:filename double_quote + | single_quote filename:filename single_quote | filename:filename ) sep:sep @@ -802,8 +804,10 @@ model_cmd::ModelCmd name:model_name sep:sep type:model_type - [| [sep:sep] lp ~ [sep:sep] parameters:parameters [sep:sep] rp - | sep:sep parameters:parameters] + [ + | [sep:sep] lp ~ [sep:sep] parameters:parameters [sep:sep] rp + | sep:sep parameters:parameters + ] ; @@ -867,11 +871,8 @@ subckt_cmd::SubcktCmd lib_block = - cmd:'.LIB' - sep:sep entry:id sep:cmd_net_sep - ~ lines:netlist_lines '.ENDL' ~ @@ -885,13 +886,13 @@ title_cmd::TitleCmd ; -parameters +parameters::Parameters = ([@:sep] comma [@:sep] | @:sep)%{@:parameter} ; -parameter +parameter::Parameter = name:id [sep:sep] @@ -904,7 +905,7 @@ parameter ; -gen_expr +gen_expr::Expression = @:braced_expression | @:value ; @@ -923,13 +924,13 @@ tablefile ; -braced_expression +braced_expression::BracedExpression = - lc ~ [sep:sep] expression [sep:sep] rc + lc ~ [sep:sep] @:ternary [sep:sep] rc ; -functions +functions::Functions = | functions_1 | atan2 @@ -964,7 +965,6 @@ functions_1 | 'Re' | 'R' | 'Img' - | 'Db' | 'acosh' | 'acos' | 'asinh' @@ -987,7 +987,7 @@ functions_1 lp ~ [sep:sep] - x:expression + x:ternary [sep:sep] rp ; @@ -1000,11 +1000,11 @@ atan2 lp ~ [sep:sep] - y:expression + y:ternary [sep:sep] ',' [sep:sep] - x:expression + x:ternary [sep:sep] rp ; @@ -1021,7 +1021,7 @@ ddx [sep:sep] ',' [sep:sep] - x:expression + x:ternary [sep:sep] rp ; @@ -1034,15 +1034,15 @@ gauss lp ~ [sep:sep] - mu:expression + mu:ternary [sep:sep] ',' [sep:sep] - alpha:expression + alpha:ternary [sep:sep] ',' [sep:sep] - n:expression + n:ternary [sep:sep] rp ; @@ -1065,11 +1065,11 @@ if_func [sep:sep] ',' [sep:sep] - x:expression + x:ternary [sep:sep] ',' [sep:sep] - y:expression + y:ternary [sep:sep] rp ; @@ -1082,15 +1082,15 @@ limit lp ~ [sep:sep] - x:expression + x:ternary [sep:sep] ',' [sep:sep] - y:expression + y:ternary [sep:sep] ',' [sep:sep] - z:expression + z:ternary [sep:sep] rp ; @@ -1103,11 +1103,11 @@ functions_2 lp ~ [sep:sep] - x:expression + x:ternary [sep:sep] ',' [sep:sep] - y:expression + y:ternary [sep:sep] rp ; @@ -1126,11 +1126,11 @@ unif lp ~ [sep:sep] - mu:expression + mu:ternary [sep:sep] ',' [sep:sep] - alpha:expression + alpha:ternary [sep:sep] rp ; @@ -1150,57 +1150,57 @@ v_func ; -expression +ternary::Ternary = t:conditional_expression [sep:sep] op:'?' ~ [sep:sep] - x:expression + x:ternary [sep:sep] ':' [sep:sep] - y:expression + y:ternary | - term + value:term ; -conditional_expression +conditional_expression::Conditional = boolean_or | boolean ; -boolean_or +boolean_or::Or = | left:boolean_or [sep:sep] op:'|' ~ [sep:sep] right:boolean_xor - | boolean_xor + | value:boolean_xor ; -boolean_xor +boolean_xor::Xor = | left:boolean_xor [sep:sep] op:'^' ~ [sep:sep] right:boolean_and - | boolean_and + | value:boolean_and ; -boolean_and +boolean_and::And = | left:boolean_and [sep:sep] op:'&' ~ [sep:sep] right:boolean_not - | boolean_not + | value:boolean_not ; -boolean_not +boolean_not::Not = - op:'~' ~ operator:relational | relational + op:'~' ~ operator:relational | value:relational ; -relational +relational::Relational = left:term [sep:sep] @@ -1211,36 +1211,34 @@ relational ; -term +term::AddSub = - left:term [sep:sep] op:('+' | '-') ~ [sep:sep] right:prod | prod + left:term [sep:sep] op:('+' | '-') ~ [sep:sep] right:prod | value:prod ; -prod +prod::ProdDivMod = - left:prod [sep:sep] op:('*' | '/' | '%') ~ [sep:sep] right:unary | unary + | left:prod [sep:sep] op:('*' | '/' | '%') ~ [sep:sep] right:unary + | value:unary ; -unary +unary::Sign = - op:('+' | '-') ~ operator:exp | exp + op:('+' | '-') ~ operator:exp | value:exp ; -exp +exp::Exponential = - left:exp [sep:sep] op:'**' ~ [sep:sep] right:factor | factor + left:exp [sep:sep] op:'**' ~ [sep:sep] right:factor | value:factor ; -factor +factor::Factor = - | '(' ~ [sep:sep] @:expression [sep:sep] ')' - | @:functions - | var:id - | @:value + '(' ~ [sep:sep] @:ternary [sep:sep] ')' | @:functions | var:id | @:value ; @@ -1352,7 +1350,7 @@ boolean ; -model_name +model_name::ModelName = name:/[a-zA-Z0-9_]+/ !([sep:sep] '=') ; @@ -1370,7 +1368,7 @@ binary ; -dev +dev::Device = /[a-zA-Z][a-zA-Z0-9_:!`@#\.\+\-]*/ ; From 38dbdb3d6d0abc7232531e1d1824dc6848eb0391 Mon Sep 17 00:00:00 2001 From: jmgc Date: Fri, 21 May 2021 14:48:35 +0200 Subject: [PATCH 054/134] EBNF Spice Grammar tatsu formatted. --- PySpice/Spice/spicegrammar.ebnf | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/PySpice/Spice/spicegrammar.ebnf b/PySpice/Spice/spicegrammar.ebnf index 8ed9c8f54..86c546270 100644 --- a/PySpice/Spice/spicegrammar.ebnf +++ b/PySpice/Spice/spicegrammar.ebnf @@ -777,18 +777,15 @@ include_cmd::IncludeCmd lib_cmd::LibCmd = - cmd:'.LIB' - sep:sep - ~ - block::LibBlock:lib_block | call::LibCall:lib_call + cmd:'.LIB' ~ sep:sep (call:lib_call | block:lib_block) ; -lib_call +lib_call::LibCall = ( - | double_quote filename:filename double_quote - | single_quote filename:filename single_quote + | double_quote ~ filename:filename double_quote + | single_quote ~ filename:filename single_quote | filename:filename ) sep:sep @@ -865,14 +862,15 @@ subckt_cmd::SubcktCmd lines:netlist_lines '.ENDS' ~ - [{st}+ end_name:model_name] + [{st}+ name:model_name] ; -lib_block +lib_block::LibBlock = entry:id sep:cmd_net_sep + ~ lines:netlist_lines '.ENDL' ~ @@ -888,7 +886,7 @@ title_cmd::TitleCmd parameters::Parameters = - ([@:sep] comma [@:sep] | @:sep)%{@:parameter} + @:parameter {(([@:sep] comma [@:sep]) | @:sep) @:parameter} ; @@ -900,7 +898,6 @@ parameter::Parameter ~ [sep:sep] value:gen_expr - ~ {[sep:sep] comma [sep:sep] value:gen_expr} ; @@ -1396,7 +1393,7 @@ end_sep sep = - @:cmd_net_sep '+' {st} | {st}+ + (@:cmd_net_sep '+' {st}) | {st}+ ; @@ -1405,7 +1402,14 @@ cmd_net_sep::Separator {st} comment:[@:inline_comment] newline - {{st} comment:[(@:line_comment | @:inline_comment) ~] newline} + { + {st} + comment:[ + @:line_comment | @:inline_comment + ] + + newline + } {st} ; From 928b4add7c52629a3f4688ad55ec9bea85386fbc Mon Sep 17 00:00:00 2001 From: jmgc Date: Mon, 24 May 2021 13:10:42 +0200 Subject: [PATCH 055/134] EBNF Spice Grammar optimised. --- PySpice/Spice/spicegrammar.ebnf | 110 ++++++++++++++++++-------------- 1 file changed, 63 insertions(+), 47 deletions(-) diff --git a/PySpice/Spice/spicegrammar.ebnf b/PySpice/Spice/spicegrammar.ebnf index 86c546270..232da6531 100644 --- a/PySpice/Spice/spicegrammar.ebnf +++ b/PySpice/Spice/spicegrammar.ebnf @@ -60,9 +60,9 @@ device | @:mosfet | @:bjt | @:resistor + | @:subcircuit | @:switch | @:voltage_source - | @:subcircuit ; @@ -77,12 +77,12 @@ nonlinear_dependent_source::NonLinearDependentSource negative:node ~ sep:sep - ('V' | 'I') + magnitude:('V' | 'I') [sep:sep] '=' [sep:sep] expr:abm_expression - [sep:sep parameters+:parameters] + [sep:sep parameters:parameters] ; @@ -110,7 +110,7 @@ capacitor::Capacitor | sep:sep model:model_name ] - [sep:sep parameters+:parameters] + [sep:sep parameters:parameters] ; @@ -224,7 +224,7 @@ control_table::ControlTable type:'TABLE' ~ [sep:sep] - braced_expression + expr:braced_expression [sep:sep] '=' [sep:sep] @@ -307,7 +307,7 @@ jfet::JFET sep:sep model:model_name [sep:sep area:gen_expr] - [sep:sep parameters+:parameters] + [sep:sep parameters:parameters] ; @@ -327,7 +327,7 @@ inductor::Inductor | sep:sep model:model_name ] - [sep:sep parameters+:parameters] + [sep:sep parameters:parameters] ; @@ -349,9 +349,15 @@ mosfet::MOSFET model:model_name [sep:sep (sep:sep)%{ - param+:( - | name:'IC' [sep:sep] '=' ~ [sep:sep] ([sep:sep] ',' [sep:sep])%{value} - | parameter + param:( + name:'IC' + [sep:sep] + '=' + ~ + [sep:sep] + ([sep:sep] ',' [sep:sep])%{value:value} + | + parameter:parameter ) }] ; @@ -377,7 +383,7 @@ bjt::BJT | model:model_name ) [sep:sep area:gen_expr] - [sep:sep parameters+:parameters] + [sep:sep parameters:parameters] ; @@ -397,7 +403,7 @@ resistor::Resistor | sep:sep model:model_name ] - [sep:sep parameters+:parameters] + [sep:sep parameters:parameters] ; @@ -434,7 +440,7 @@ subcircuit::Subcircuit ~ dev:dev {sep:sep node+:node} - [params:':' ~ [sep:sep] parameters+:parameters] + [params:':' ~ [sep:sep] parameters:parameters] ; @@ -466,7 +472,7 @@ ac ; -transient_specification +transient_specification::TransientSpecification = | @:transient_pulse | @:transient_sin @@ -477,7 +483,7 @@ transient_specification ; -transient_pulse +transient_pulse::TransientPulse = type:'PULSE' ( @@ -487,13 +493,13 @@ transient_pulse ; -pulse_arguments +pulse_arguments::PulseArguments = - v1:gen_expr sep:sep (sep:sep)%{gen_expr} + v1:gen_expr sep:sep (sep:sep)%{value:gen_expr} ; -transient_sin +transient_sin::TransientSin = type:'SIN' ( @@ -503,7 +509,7 @@ transient_sin ; -sin_arguments +sin_arguments::SinArguments = v0:gen_expr sep:sep @@ -515,7 +521,7 @@ sin_arguments ; -transient_exp +transient_exp::TransientExp = type:'EXP' ( @@ -525,13 +531,13 @@ transient_exp ; -exp_arguments +exp_arguments::ExpArguments = v1:gen_expr sep:sep v2:gen_expr sep:sep (sep:sep)%{value:gen_expr} ; -transient_pat +transient_pat::TransientPat = type:'PAT' ( @@ -541,7 +547,7 @@ transient_pat ; -pat_arguments +pat_arguments::PatArguments = vhi:gen_expr sep:sep @@ -560,24 +566,34 @@ pat_arguments ; -transient_pwl +transient_pwl::TransientPWL + = + type:'PWL' ~ (@:pwl_file_arguments | @:pwl_arguments) + ; + + +pwl_file_arguments::PWLFileArguments = - type:'PWL' + sep:sep + 'FILE' ~ sep:sep + (double_quote filename:filename double_quote | filename:filename) + [sep:sep parameters:parameters] + ; + + +pwl_arguments::PWLArguments + = ( - 'FILE' - ~ - sep:sep - (double_quote filename double_quote | filename) - [sep:sep parameters:parameters] - | - (sep:sep)%{t:value ~ sep:sep v:value}+ + | {sep:sep} lp ~ {sep:sep t:value sep:sep value:value}+ sep:sep rp + | {sep:sep t:value sep:sep value:value}+ ) + [sep:sep parameters:parameters] ; -transient_sffm +transient_sffm::TransientSFFM = type:'SFFM' ( @@ -587,13 +603,13 @@ transient_sffm ; -sffm_arguments +sffm_arguments::SFFMArguments = v0:gen_expr sep:sep va:gen_expr sep:sep (sep:sep)%{value:gen_expr} ; -command +command::Command = | @:ac_cmd | @:ic_cmd @@ -608,7 +624,7 @@ command ; -netlist_cmds +netlist_cmds::NetlistCmds = @:data_cmd | @:model_cmd | @:param_cmd | @:subckt_cmd ; @@ -764,13 +780,13 @@ ic_cmd::ICCmd include_cmd::IncludeCmd = - cmd:('.INC' | '.INCLUDE' | '.INCL') + cmd:('.INCLUDE' | '.INCL' | '.INC') ~ sep:sep ( - | filename - | single_quote ~ filename single_quote - | double_quote ~ filename double_quote + | double_quote ~ filename:filename double_quote + | single_quote ~ filename:filename single_quote + | filename:filename ) ; @@ -908,7 +924,7 @@ gen_expr::Expression ; -tablefile +tablefile::TableFile = func:'tablefile' [sep:sep] @@ -1047,7 +1063,7 @@ gauss i_func = - func:'i' [sep:sep] lp ~ [sep:sep] &'V' positive:dev [sep:sep] rp + func:'i' [sep:sep] lp ~ [sep:sep] &'V' device:dev [sep:sep] rp ; @@ -1142,7 +1158,7 @@ v_func [sep:sep] node:node [sep:sep] - [',' [sep:sep] negative:node [sep:sep]] + [',' [sep:sep] node:node [sep:sep]] rp ; @@ -1335,9 +1351,9 @@ digit ; -filename +filename::Filename = - ?"[a-zA-Z0-9_:@#\.\$\/]+" + ?"[a-zA-Z0-9_:@#\.\$\/][a-zA-Z0-9_:@#\.\$\/\+\-]*" ; @@ -1353,7 +1369,7 @@ model_name::ModelName ; -binary_pattern +binary_pattern::BinaryPattern = /[Bb]/ pattern:{binary}+ ; @@ -1373,7 +1389,7 @@ dev::Device node = - | /[a-zA-Z0-9_\[\$][a-zA-Z0-9_:\$\-`~!@#%&_\+|<>\?\.\\|\^\*]*[a-zA-Z0-9_\$\-`~!@#%&_\+|<>\?\.\\|\^\*\]]/ + | ?"[a-zA-Z0-9_\[\$\/\+\-][a-zA-Z0-9_:\$\-`~!@#%&_\+|<>\?\.\\|\^\*\/]*[a-zA-Z0-9_\$\-`~!@#%&_\+|<>\?\.\\|\^\*\]\/]" | /[a-zA-Z0-9_]/ ; From 5d9fd98fcc2fb772dbfd760ea01b9af52b5d8a3a Mon Sep 17 00:00:00 2001 From: jmgc Date: Tue, 25 May 2021 07:56:26 +0200 Subject: [PATCH 056/134] EBNF Spice Grammar with some corrections and improvements. --- PySpice/Spice/spicegrammar.ebnf | 88 ++++++++++++++++++++++++--------- 1 file changed, 64 insertions(+), 24 deletions(-) diff --git a/PySpice/Spice/spicegrammar.ebnf b/PySpice/Spice/spicegrammar.ebnf index 232da6531..263e1e1e3 100644 --- a/PySpice/Spice/spicegrammar.ebnf +++ b/PySpice/Spice/spicegrammar.ebnf @@ -8,8 +8,20 @@ start::Circuit = ['.TITLE'] {st} + [asterisk] + {st} title:text - [{newline}+ lines:lines] + {newline asterisk {st} title:text} + { + {st} + [ + line_comment | inline_comment + ] + + newline + } + {st} + [lines:lines] [newline '.END'] {newline} ; @@ -257,7 +269,21 @@ control_voltage_poly::ControlVoltagePoly [sep:sep] rp [sep:sep] - (sep:sep)%{positive+:node sep:sep negative+:node | coefficient:value} + (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 + } ; @@ -355,7 +381,7 @@ mosfet::MOSFET '=' ~ [sep:sep] - ([sep:sep] ',' [sep:sep])%{value:value} + ([sep:sep] comma [sep:sep])%{value:value} | parameter:parameter ) @@ -377,12 +403,20 @@ bjt::BJT ~ sep:sep ( - | substrate:node sep:sep thermal:node sep:sep model:model_name - | (substrate:{digit}+ | &'[' substrate:node) sep:sep model:model_name - | thermal:node sep:sep model:model_name - | model:model_name + (substrate:{digit}+ | &'[' substrate:node) + [sep:sep thermal:node] + sep:sep + model:model_name + | + [ + substrate:{digit}+ | &'[' substrate:node + ] + + model:model_name + [sep:sep area:gen_expr] + | + thermal:node sep:sep model:model_name ) - [sep:sep area:gen_expr] [sep:sep parameters:parameters] ; @@ -420,7 +454,7 @@ switch::Switch sep:sep ( model:model_name - [sep:sep ('ON' | 'OFF')] + [sep:sep initial_state:('ON' | 'OFF')] sep:sep 'control' ~ @@ -429,7 +463,12 @@ switch::Switch [sep:sep] braced_expression | - control_p:node sep:sep control_n:node sep:sep model:model_name + control_p:node + sep:sep + control_n:node + sep:sep + model:model_name + [sep:sep initial_state:('ON' | 'OFF')] ) ; @@ -1015,7 +1054,7 @@ atan2 [sep:sep] y:ternary [sep:sep] - ',' + comma [sep:sep] x:ternary [sep:sep] @@ -1032,7 +1071,7 @@ ddx [sep:sep] f:id [sep:sep] - ',' + comma [sep:sep] x:ternary [sep:sep] @@ -1049,11 +1088,11 @@ gauss [sep:sep] mu:ternary [sep:sep] - ',' + comma [sep:sep] alpha:ternary [sep:sep] - ',' + comma [sep:sep] n:ternary [sep:sep] @@ -1076,11 +1115,11 @@ if_func [sep:sep] t:conditional_expression [sep:sep] - ',' + comma [sep:sep] x:ternary [sep:sep] - ',' + comma [sep:sep] y:ternary [sep:sep] @@ -1097,11 +1136,11 @@ limit [sep:sep] x:ternary [sep:sep] - ',' + comma [sep:sep] y:ternary [sep:sep] - ',' + comma [sep:sep] z:ternary [sep:sep] @@ -1118,7 +1157,7 @@ functions_2 [sep:sep] x:ternary [sep:sep] - ',' + comma [sep:sep] y:ternary [sep:sep] @@ -1141,7 +1180,7 @@ unif [sep:sep] mu:ternary [sep:sep] - ',' + comma [sep:sep] alpha:ternary [sep:sep] @@ -1158,7 +1197,7 @@ v_func [sep:sep] node:node [sep:sep] - [',' [sep:sep] node:node [sep:sep]] + [comma [sep:sep] node:node [sep:sep]] rp ; @@ -1182,7 +1221,8 @@ ternary::Ternary conditional_expression::Conditional = - boolean_or | boolean + | lp [sep:sep] (boolean_or | boolean) [sep:sep] rp + | (boolean_or | boolean) ; @@ -1251,7 +1291,7 @@ exp::Exponential factor::Factor = - '(' ~ [sep:sep] @:ternary [sep:sep] ')' | @:functions | var:id | @:value + lp ~ [sep:sep] @:ternary [sep:sep] rp | @:functions | var:id | @:value ; @@ -1365,7 +1405,7 @@ boolean model_name::ModelName = - name:/[a-zA-Z0-9_]+/ !([sep:sep] '=') + name:/[a-zA-Z0-9_][a-zA-Z0-9_\-\+]*/ !([sep:sep] '=') ; From 0142c41f84577f36492e7d7641518d1c1d43f21b Mon Sep 17 00:00:00 2001 From: jmgc Date: Tue, 25 May 2021 08:21:36 +0200 Subject: [PATCH 057/134] Added the mutual inductor to the EBNF Spice Grammar. --- PySpice/Spice/spicegrammar.ebnf | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/PySpice/Spice/spicegrammar.ebnf b/PySpice/Spice/spicegrammar.ebnf index 263e1e1e3..6890b5488 100644 --- a/PySpice/Spice/spicegrammar.ebnf +++ b/PySpice/Spice/spicegrammar.ebnf @@ -68,6 +68,7 @@ device | @:current_controlled_voltage_source | @:current_source | @:jfet + | @:mutual_inductor | @:inductor | @:mosfet | @:bjt @@ -139,6 +140,7 @@ diode::Diode sep:sep model:model_name [sep:sep area:gen_expr] + [sep:sep parameters:parameters] ; @@ -337,6 +339,18 @@ jfet::JFET ; +mutual_inductor::MutualInductor + = + &'K' + ~ + dev:dev + {sep:sep &'L' inductor:dev}+ + sep:sep + value:gen_expr + [sep:sep model:model_name] + ; + + inductor::Inductor = &'L' @@ -1291,7 +1305,11 @@ exp::Exponential factor::Factor = - lp ~ [sep:sep] @:ternary [sep:sep] rp | @:functions | var:id | @:value + | lp ~ [sep:sep] @:ternary [sep:sep] rp + | @:functions + | lc [sep:sep] var:id [sep:sep] rc + | var:id + | @:value ; From 0ec310cabc2395965006fe76519a60dc30e08762 Mon Sep 17 00:00:00 2001 From: jmgc Date: Thu, 27 May 2021 12:03:46 +0200 Subject: [PATCH 058/134] Solving issues with expressions. --- PySpice/Spice/spicegrammar.ebnf | 136 +++++++++++++++++--------------- 1 file changed, 72 insertions(+), 64 deletions(-) diff --git a/PySpice/Spice/spicegrammar.ebnf b/PySpice/Spice/spicegrammar.ebnf index 6890b5488..595c9b4c0 100644 --- a/PySpice/Spice/spicegrammar.ebnf +++ b/PySpice/Spice/spicegrammar.ebnf @@ -22,8 +22,8 @@ start::Circuit } {st} [lines:lines] - [newline '.END'] - {newline} + ['.END' end_sep] + $ ; @@ -53,7 +53,14 @@ netlist_line::NetlistLine encrypted = - '$CDNENCSTART' ~ {newline}+ {{/[0-9a-f]/} {newline}+} '$CDNENCFINISH' + '$CDNENCSTART' + ~ + [id] + {newline}+ + {{/[0-9a-f]/} {newline}+} + '$CDNENCFINISH' + ~ + [id] ; @@ -344,7 +351,7 @@ mutual_inductor::MutualInductor &'K' ~ dev:dev - {sep:sep &'L' inductor:dev}+ + {sep:sep &'L' inductor+:dev}+ sep:sep value:gen_expr [sep:sep model:model_name] @@ -414,27 +421,19 @@ bjt::BJT base:node sep:sep emitter:node - ~ - sep:sep - ( - (substrate:{digit}+ | &'[' substrate:node) - [sep:sep thermal:node] - sep:sep - model:model_name - | - [ - substrate:{digit}+ | &'[' substrate:node - ] - - model:model_name - [sep:sep area:gen_expr] - | - thermal:node sep:sep model:model_name - ) + [sep:sep substrate:substrate_node] + {sep:sep args+:node}+ + [sep:sep area:gen_expr] [sep:sep parameters:parameters] ; +substrate_node::SubstrateNode + = + substrate:(/[0-9]+/ | &'[' ~ node) + ; + + resistor::Resistor = &'R' @@ -664,9 +663,6 @@ sffm_arguments::SFFMArguments command::Command = - | @:ac_cmd - | @:ic_cmd - | @:dc_cmd | @:embedded_sampling_cmd | @:include_cmd | @:lib_cmd @@ -674,12 +670,14 @@ command::Command | @:subckt_cmd | @:simulator_cmd | @:title_cmd + | @:ac_cmd + | @:dc_cmd ; netlist_cmds::NetlistCmds = - @:data_cmd | @:model_cmd | @:param_cmd | @:subckt_cmd + @:data_cmd | @:ic_cmd | @:model_cmd | @:param_cmd | @:subckt_cmd ; @@ -726,7 +724,6 @@ data_cmd::DataCmd dc_cmd::DCCmd = cmd:'.DC' - ~ ( (sep:sep sweep:'DATA' {st} '=' {st} table:id) | @@ -926,7 +923,7 @@ subckt_cmd::SubcktCmd sep:sep name:model_name {sep:sep node:(node !':')} - [sep:sep 'params:' ~ sep:sep parameters:parameters] + [sep:sep 'params:' ~ [sep:sep] parameters:parameters] sep:cmd_net_sep lines:netlist_lines '.ENDS' @@ -1235,29 +1232,25 @@ ternary::Ternary conditional_expression::Conditional = - | lp [sep:sep] (boolean_or | boolean) [sep:sep] rp - | (boolean_or | boolean) + lp [sep:sep] @:boolean_or [sep:sep] rp | @:boolean_or ; boolean_or::Or = - | left:boolean_or [sep:sep] op:'|' ~ [sep:sep] right:boolean_xor - | value:boolean_xor + left:boolean_xor [[sep:sep] op:'|' ~ [sep:sep] right:boolean_or] ; boolean_xor::Xor = - | left:boolean_xor [sep:sep] op:'^' ~ [sep:sep] right:boolean_and - | value:boolean_and + left:boolean_and [[sep:sep] op:'^' ~ [sep:sep] right:boolean_xor] ; boolean_and::And = - | left:boolean_and [sep:sep] op:'&' ~ [sep:sep] right:boolean_not - | value:boolean_not + left:boolean_not [[sep:sep] op:'&' ~ [sep:sep] right:boolean_and] ; @@ -1269,16 +1262,26 @@ boolean_not::Not relational::Relational = - left:term - [sep:sep] - op:('==' | '!=' | '>=' | '<=' | '>' | '<') - ~ - [sep:sep] - right:term + left:term + [sep:sep] + op:('==' | '!=' | '>=' | '<=' | '>' | '<') + ~ + [sep:sep] + right:term + | + value:term + | + value:boolean ; -term::AddSub +term::Term + = + lp [sep] @:add_sub [sep] rp | @:add_sub + ; + + +add_sub::AddSub = left:term [sep:sep] op:('+' | '-') ~ [sep:sep] right:prod | value:prod ; @@ -1307,8 +1310,8 @@ factor::Factor = | lp ~ [sep:sep] @:ternary [sep:sep] rp | @:functions - | lc [sep:sep] var:id [sep:sep] rc - | var:id + | lc [sep:sep] var:var_id [sep:sep] rc + | var:var_id | @:value ; @@ -1321,24 +1324,26 @@ special_variables value::Value = - | (real:real_value '+' imag:imag_value) - | imag:imag_value - | real:real_value + ( + | (real:real_value '+' imag:imag_value) + | imag:imag_value + | real:real_value + ) + unit:[ + hz | unit + ] ; imag_value::ImagValue = - @:real_value 'J' + value:number_scale 'J' ; real_value::RealValue = value:number_scale - unit:[ - hz | unit - ] ; @@ -1350,8 +1355,8 @@ freq_value number_scale::NumberScale = - | value:floating_point scale:(meg | mil | [suffix]) - | value:integer scale:(meg | mil | [suffix]) + | value:floating_point scale:(meg | [suffix]) + | value:integer scale:(meg | [suffix]) ; @@ -1367,12 +1372,6 @@ meg ; -mil - = - /[mM][iI][lL]/ - ; - - unit::Unit = /[a-zA-Z%]+/ @@ -1441,14 +1440,17 @@ binary dev::Device = - /[a-zA-Z][a-zA-Z0-9_:!`@#\.\+\-]*/ + /[a-zA-Z\$][a-zA-Z0-9_:!`@#\.\+\-\$]*/ ; -node +node::NetNode = - | ?"[a-zA-Z0-9_\[\$\/\+\-][a-zA-Z0-9_:\$\-`~!@#%&_\+|<>\?\.\\|\^\*\/]*[a-zA-Z0-9_\$\-`~!@#%&_\+|<>\?\.\\|\^\*\]\/]" - | /[a-zA-Z0-9_]/ + node:( + | ?"[a-zA-Z0-9_\[\$\/\+\-][a-zA-Z0-9_:\$\-`~!@#%&_\+|<>\?\.\\|\^\*\/]*[a-zA-Z0-9_\$\-`~!@#%&_\+|<>\?\.\\|\^\*\]\/]" + | /[a-zA-Z0-9_]/ + ) + !([sep:sep] '=') ; @@ -1459,6 +1461,12 @@ id ; +var_id + = + /[a-zA-Z_`@#\$][a-zA-Z0-9_:`@#\.\$]*[a-zA-Z0-9_`@#\.\$]/ | /[a-zA-Z]/ + ; + + end_sep = @:cmd_net_sep | {st}+ @@ -1467,7 +1475,7 @@ end_sep sep = - (@:cmd_net_sep '+' {st}) | {st}+ + {@:cmd_net_sep '+' {st}}+ | {st}+ ; From d090d1334f3da1ec90a7833aca21ad81f81fb828 Mon Sep 17 00:00:00 2001 From: jmgc Date: Thu, 27 May 2021 16:09:18 +0200 Subject: [PATCH 059/134] Revisiting expressions recognition, as it does not work. --- PySpice/Spice/spicegrammar.ebnf | 93 ++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 43 deletions(-) diff --git a/PySpice/Spice/spicegrammar.ebnf b/PySpice/Spice/spicegrammar.ebnf index 595c9b4c0..d3657979a 100644 --- a/PySpice/Spice/spicegrammar.ebnf +++ b/PySpice/Spice/spicegrammar.ebnf @@ -925,7 +925,9 @@ subckt_cmd::SubcktCmd {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] @@ -938,6 +940,7 @@ lib_block::LibBlock sep:cmd_net_sep ~ lines:netlist_lines + {st} '.ENDL' ~ [{st}+ entry:id] @@ -989,7 +992,13 @@ tablefile::TableFile braced_expression::BracedExpression = - lc ~ [sep:sep] @:ternary [sep:sep] rc + lc [sep:sep] @:parentheses [sep:sep] rc + ; + + +parentheses + = + lp [sep:sep] @:ternary [sep:sep] rp | @:ternary ; @@ -1050,7 +1059,7 @@ functions_1 lp ~ [sep:sep] - x:ternary + x:parentheses [sep:sep] rp ; @@ -1063,11 +1072,11 @@ atan2 lp ~ [sep:sep] - y:ternary + y:parentheses [sep:sep] comma [sep:sep] - x:ternary + x:parentheses [sep:sep] rp ; @@ -1084,7 +1093,7 @@ ddx [sep:sep] comma [sep:sep] - x:ternary + x:parentheses [sep:sep] rp ; @@ -1097,15 +1106,15 @@ gauss lp ~ [sep:sep] - mu:ternary + mu:parentheses [sep:sep] comma [sep:sep] - alpha:ternary + alpha:parentheses [sep:sep] comma [sep:sep] - n:ternary + n:parentheses [sep:sep] rp ; @@ -1128,11 +1137,11 @@ if_func [sep:sep] comma [sep:sep] - x:ternary + x:parentheses [sep:sep] comma [sep:sep] - y:ternary + y:parentheses [sep:sep] rp ; @@ -1145,15 +1154,15 @@ limit lp ~ [sep:sep] - x:ternary + x:parentheses [sep:sep] comma [sep:sep] - y:ternary + y:parentheses [sep:sep] comma [sep:sep] - z:ternary + z:parentheses [sep:sep] rp ; @@ -1166,11 +1175,11 @@ functions_2 lp ~ [sep:sep] - x:ternary + x:parentheses [sep:sep] comma [sep:sep] - y:ternary + y:parentheses [sep:sep] rp ; @@ -1189,11 +1198,11 @@ unif lp ~ [sep:sep] - mu:ternary + mu:parentheses [sep:sep] comma [sep:sep] - alpha:ternary + alpha:parentheses [sep:sep] rp ; @@ -1220,11 +1229,12 @@ ternary::Ternary op:'?' ~ [sep:sep] - x:ternary + x:parentheses [sep:sep] ':' + ~ [sep:sep] - y:ternary + y:parentheses | value:term ; @@ -1238,38 +1248,35 @@ conditional_expression::Conditional boolean_or::Or = - left:boolean_xor [[sep:sep] op:'|' ~ [sep:sep] right:boolean_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] + 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] + left:boolean_not [[sep:sep] op:'&' [sep:sep] right:boolean_and] ; boolean_not::Not = - op:'~' ~ operator:relational | value:relational + [op:'~'] operator:relational ; relational::Relational = - left:term - [sep:sep] + left:parentheses + [[sep:sep] op:('==' | '!=' | '>=' | '<=' | '>' | '<') - ~ [sep:sep] - right:term - | - value:term + right:parentheses] | value:boolean ; @@ -1277,42 +1284,43 @@ relational::Relational term::Term = - lp [sep] @:add_sub [sep] rp | @:add_sub + @:add_sub ; add_sub::AddSub = - left:term [sep:sep] op:('+' | '-') ~ [sep:sep] right:prod | value:prod + left:prod [[sep:sep] op:('+' | '-') [sep:sep] right:add_sub] ; prod::ProdDivMod = - | left:prod [sep:sep] op:('*' | '/' | '%') ~ [sep:sep] right:unary - | value:unary + left:unary [[sep:sep] op:('*' | '/' | '%') [sep:sep] right:prod] ; unary::Sign = - op:('+' | '-') ~ operator:exp | value:exp + [op:('+' | '-')] operator:exp ; exp::Exponential = - left:exp [sep:sep] op:'**' ~ [sep:sep] right:factor | value:factor + left:functional [[sep:sep] op:'**' [sep:sep] right:exp] + ; + + +functional::Functional + = + @:functions | @:factor ; factor::Factor = - | lp ~ [sep:sep] @:ternary [sep:sep] rp - | @:functions - | lc [sep:sep] var:var_id [sep:sep] rc - | var:var_id - | @:value + @:parentheses | lc [sep:sep] @:var_id [sep:sep] rc | @:var_id | @:value ; @@ -1469,13 +1477,13 @@ var_id end_sep = - @:cmd_net_sep | {st}+ + @:cmd_net_sep {st} | {st}+ ; sep = - {@:cmd_net_sep '+' {st}}+ | {st}+ + {@:cmd_net_sep {st} '+' {st}}+ | {st}+ ; @@ -1492,7 +1500,6 @@ cmd_net_sep::Separator newline } - {st} ; From 961ffad313c6a31395a129a6c3cc7852ca652906 Mon Sep 17 00:00:00 2001 From: jmgc Date: Sun, 30 May 2021 09:50:04 +0200 Subject: [PATCH 060/134] Corrected the expressions recognition. --- PySpice/Spice/spicegrammar.ebnf | 262 +++++++++++++++++--------------- 1 file changed, 137 insertions(+), 125 deletions(-) diff --git a/PySpice/Spice/spicegrammar.ebnf b/PySpice/Spice/spicegrammar.ebnf index d3657979a..3eafb0971 100644 --- a/PySpice/Spice/spicegrammar.ebnf +++ b/PySpice/Spice/spicegrammar.ebnf @@ -971,9 +971,9 @@ parameter::Parameter ; -gen_expr::Expression +gen_expr::GenericExpression = - @:braced_expression | @:value + braced:braced_expression | value:value ; @@ -992,13 +992,127 @@ tablefile::TableFile braced_expression::BracedExpression = - lc [sep:sep] @:parentheses [sep:sep] rc + lc [sep:sep] @:expression [sep:sep] rc ; -parentheses +expression::Expression = - lp [sep:sep] @:ternary [sep:sep] rp | @:ternary + 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 ; @@ -1008,12 +1122,12 @@ functions::Functions | atan2 | ddx | gauss - | i_func | if_func | limit | functions_2 | rand | unif + | i_func | v_func ; @@ -1059,7 +1173,7 @@ functions_1 lp ~ [sep:sep] - x:parentheses + x:expression [sep:sep] rp ; @@ -1072,11 +1186,11 @@ atan2 lp ~ [sep:sep] - y:parentheses + y:expression [sep:sep] comma [sep:sep] - x:parentheses + x:expression [sep:sep] rp ; @@ -1093,7 +1207,7 @@ ddx [sep:sep] comma [sep:sep] - x:parentheses + x:expression [sep:sep] rp ; @@ -1106,15 +1220,15 @@ gauss lp ~ [sep:sep] - mu:parentheses + mu:expression [sep:sep] comma [sep:sep] - alpha:parentheses + alpha:expression [sep:sep] comma [sep:sep] - n:parentheses + n:expression [sep:sep] rp ; @@ -1137,11 +1251,11 @@ if_func [sep:sep] comma [sep:sep] - x:parentheses + x:expression [sep:sep] comma [sep:sep] - y:parentheses + y:expression [sep:sep] rp ; @@ -1154,15 +1268,15 @@ limit lp ~ [sep:sep] - x:parentheses + x:expression [sep:sep] comma [sep:sep] - y:parentheses + y:expression [sep:sep] comma [sep:sep] - z:parentheses + z:expression [sep:sep] rp ; @@ -1175,11 +1289,11 @@ functions_2 lp ~ [sep:sep] - x:parentheses + x:expression [sep:sep] comma [sep:sep] - y:parentheses + y:expression [sep:sep] rp ; @@ -1198,11 +1312,11 @@ unif lp ~ [sep:sep] - mu:parentheses + mu:expression [sep:sep] comma [sep:sep] - alpha:parentheses + alpha:expression [sep:sep] rp ; @@ -1222,108 +1336,6 @@ v_func ; -ternary::Ternary - = - t:conditional_expression - [sep:sep] - op:'?' - ~ - [sep:sep] - x:parentheses - [sep:sep] - ':' - ~ - [sep:sep] - y:parentheses - | - value:term - ; - - -conditional_expression::Conditional - = - lp [sep:sep] @:boolean_or [sep:sep] rp | @: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:parentheses - [[sep:sep] - op:('==' | '!=' | '>=' | '<=' | '>' | '<') - [sep:sep] - right:parentheses] - | - value: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 | @:factor - ; - - -factor::Factor - = - @:parentheses | lc [sep:sep] @:var_id [sep:sep] rc | @:var_id | @:value - ; - - special_variables = 'time' | 'temper' | 'temp' | 'freq' | 'vt' | 'pi' @@ -1370,7 +1382,7 @@ number_scale::NumberScale suffix = - /[tTgGkKmMxXuUnNpPfF]/ + /[tTgGkKmMxXuUnNpPfFµ]/ ; From 4ac2b94bb31de7f3fb04a86e4ef4826699a3fb65 Mon Sep 17 00:00:00 2001 From: jmgc Date: Sun, 30 May 2021 11:03:56 +0200 Subject: [PATCH 061/134] Updates to use the tatsu based parser. --- PySpice/Spice/BasicElement.py | 30 +- PySpice/Spice/EBNFParser.py | 2180 ++++++++ PySpice/Spice/Expressions.py | 602 +++ PySpice/Spice/HighLevelElement.py | 113 + PySpice/Spice/Library.py | 10 +- PySpice/Spice/Netlist.py | 116 +- PySpice/Spice/SpiceGrammar.py | 4595 +++++++++++++++++ PySpice/Spice/SpiceModel.py | 655 +++ PySpice/Unit/Unit.py | 6 +- requirements.txt | 8 + unit-test/{Unit => Spice}/mosdriver.lib | 0 ...test_Expression.py => test_Expressions.py} | 65 +- unit-test/Spice/test_SpiceParser.py | 427 ++ unit-test/Unit/test_SpiceParser.py | 415 -- 14 files changed, 8716 insertions(+), 506 deletions(-) create mode 100644 PySpice/Spice/EBNFParser.py create mode 100644 PySpice/Spice/Expressions.py create mode 100644 PySpice/Spice/SpiceGrammar.py create mode 100644 PySpice/Spice/SpiceModel.py rename unit-test/{Unit => Spice}/mosdriver.lib (100%) rename unit-test/Spice/{test_Expression.py => test_Expressions.py} (50%) create mode 100644 unit-test/Spice/test_SpiceParser.py delete mode 100644 unit-test/Unit/test_SpiceParser.py diff --git a/PySpice/Spice/BasicElement.py b/PySpice/Spice/BasicElement.py index a4ff40d80..7cb41101f 100644 --- a/PySpice/Spice/BasicElement.py +++ b/PySpice/Spice/BasicElement.py @@ -116,6 +116,7 @@ IntKeyParameter, ModelPositionalParameter, ) +from .Expressions import Expression #################################################################################################### @@ -173,8 +174,8 @@ def __init__(self, netlist, name, subcircuit_name, *nodes, **kwargs): try: self._pins = [Pin(self, PinDefinition(position, name=subcircuit._pins_[position]), netlist.get_node(node, True)) for position, node in enumerate(nodes)] - except: - raise ValueError() + except : + raise ValueError("Incorrect number of nodes for subcircuit {}".format(subcircuit_name)) super().__init__(netlist, name, subcircuit_name) @@ -259,6 +260,9 @@ class Resistor(DipoleElement): temperature = FloatKeyParameter('temp', unit=U_Degree) device_temperature = FloatKeyParameter('dtemp', unit=U_Degree) noisy = BoolKeyParameter('noisy') + tc = FloatPairKeyParameter('tc') + tc1 = FloatPairKeyParameter('tc1') + tc2 = FloatKeyParameter('tc1') #################################################################################################### @@ -779,7 +783,10 @@ 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 = ExpressionPositionalParameter(position=0, key_parameter=False) + ac_magnitude = ExpressionPositionalParameter(position=1, key_parameter=False) + ac_phase = ExpressionPositionalParameter(position=2, key_parameter=False) + transient = ExpressionPositionalParameter(position=3, key_parameter=False) def __init__(self, netlist, name, *args, **kwargs): number_of_pins = len(self._pins_) @@ -814,7 +821,10 @@ 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 = ExpressionPositionalParameter(position=0, key_parameter=False) + ac_magnitude = ExpressionPositionalParameter(position=1, key_parameter=False) + ac_phase = ExpressionPositionalParameter(position=2, key_parameter=False) + transient = ExpressionPositionalParameter(position=3, key_parameter=False) #################################################################################################### @@ -1000,9 +1010,15 @@ def __str__(self): spice_element = self.format_node_names() # Fixme: expression if self.current_expression is not None: - expression = ' i=%s' % self.current_expression + if isinstance(self.current_expression, Expression): + expression = ' i={%s}' % self.current_expression + else: + expression = ' i=%s' % self.current_expression elif self.voltage_expression is not None: - expression = ' v=%s' % self.voltage_expression + if isinstance(self.voltage_expression, Expression): + expression = ' v={%s}' % self.voltage_expression + else: + expression = ' v=%s' % self.voltage_expression else: expression = '' spice_element += expression @@ -1238,7 +1254,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') diff --git a/PySpice/Spice/EBNFParser.py b/PySpice/Spice/EBNFParser.py new file mode 100644 index 000000000..7d6ae11ae --- /dev/null +++ b/PySpice/Spice/EBNFParser.py @@ -0,0 +1,2180 @@ +import logging +import os + +from unicodedata import normalize +from PySpice.Unit.Unit import UnitValue, ZeroPower, PrefixedUnit +from PySpice.Unit.SiUnits import Tera, Giga, Mega, Kilo, Milli, Micro, Nano, Pico, Femto +from PySpice.Spice.Expressions import * +from .ElementParameter import FlagParameter +from .Netlist import (Circuit, + DeviceModel, + Library, + SubCircuit, + Comment) +from .BasicElement import (BehavioralSource, + BipolarJunctionTransistor, + Capacitor, + CoupledInductor, + CurrentSource, + Diode, + Inductor, + JunctionFieldEffectTransistor, + Mosfet, + Resistor, + SubCircuitElement, + VoltageControlledSwitch, + VoltageSource) +from .HighLevelElement import (PulseMixin, + SinusoidalMixin, + SingleFrequencyFMMixin, + ExponentialMixin, + AmplitudeModulatedMixin, + PatternMixin, + PieceWiseLinearMixin) +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('"', ''))) + try: + self._contents = SpiceParser(path=file_name) + except Exception as e: + raise FileNotFoundError("{}: ".format(parent.path) + str(e)) from 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 __init__(self, name, device, **parameters): + self._name = name + 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 __init__(self, name, *nodes, **params): + + self._name = name + self._nodes = nodes + self._parameters = params + + 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 {} {} Params: {}'.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 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): + + 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, ElementStatement): + statement.build(subcircuit, ground) + return subcircuit + + +class CircuitStatement(Statement): + """ This class implements a circuit definition. + + Spice syntax:: + + Title ... + + """ + + ############################################## + + def __init__(self, title, path): + if path is not None: + self._path = str(path) + else: + self._path = os.getcwd() + + self._title = str(title) + + self._library_calls = [] + self._statements = [] + self._libraries = {} + self._subcircuits = [] + self._models = [] + self._required_subcircuits = set() + self._required_models = set() + self._params = [] + self._data = {} + + ############################################## + + @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 params(self): + """ Parameters of the circuit. """ + return self._params + + ############################################## + + def __repr__(self): + + text = 'Library {}'.format(self._title) + os.linesep + text += os.linesep.join([repr(library) for library in self._libraries]) + 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._models + self._subcircuits + self._statements) + + ############################################## + + def append(self, statement): + + """ Append a statement to the statement's list. """ + + self._statements.append(statement) + + 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 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): + + 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): + circuit = Circuit(self._title) + for statement in self._library_calls: + statement.build(circuit, self._libraries) + for statement in self._params: + statement.build(circuit) + for statement in self._models: + statement.build(circuit) + for statement in self._subcircuits: + subckt = statement.build(ground, parent=circuit) # Fixme: ok ??? + circuit.subcircuit(subckt) + for statement in self._statements: + if isinstance(statement, ElementStatement): + statement.build(circuit, ground) + return circuit + + +#################################################################################################### + +class SpiceModelWalker(NodeWalker): + + def __init__(self, filename): + self._path = filename + self._root = None + self._present = None + self._context = [] + 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_Circuit(self, node): + if self._root is None: + self._root = CircuitStatement( + self.walk(node.title), + self._path + ) + self._present = self._root + else: + raise ValueError('Circuit already created: {}'.format(self._path)) + + self.walk(node.lines) + if len(self._context) != 0: + raise ParseError("Not closed hierarchy: {}".format(self._path)) + return self._root + + def walk_BJT(self, node): + device = self.walk(node.dev) + args = self.walk(node.args) + l_args = len(args) + kwargs = {} + collector = self.walk(node.collector) + base = self.walk(node.base) + emitter = self.walk(node.emitter) + nodes = [ + collector, + base, + emitter + ] + substrate = None + if node.substrate is not None: + substrate = node.substrate + nodes.append(substrate) + area = None + if node.area is not None: + area = self.walk(node.area) + if l_args == 0: + raise ValueError("The device {} has no model".format(node.dev)) + elif l_args == 1: + model_name = args[0] + elif l_args == 2: + if area is None: + try: + area = SpiceModelWalker._to_number(args[1]) + kwargs["area"] = area + model_name = args[0] + except ValueError: + pass + if area is None: + thermal = args[0] + nodes.append(thermal) + model_name = args[1] + elif l_args == 3: + if area is None: + try: + area = SpiceModelWalker._to_number(args[2]) + kwargs["area"] = area + model_name = args[1] + thermal = args[0] + nodes.append(thermal) + except ValueError: + pass + if area is None and substrate is None: + substrate = args[0] + nodes.append(substrate) + thermal = args[1] + nodes.append(thermal) + model_name = args[2] + else: + raise ValueError("Present device not compatible with BJT definition: {}".format(node.dev)) + else: + raise ValueError("Present device not compatible with BJT definition: {}".format(node.dev)) + + if node.parameters is not None: + parameters = self.walk(node.parameters) + kwargs.update(parameters) + + kwargs["model"] = model_name + self._present._required_models.add(model_name.lower()) + + self._present.append( + ElementStatement( + BipolarJunctionTransistor, + device, + *nodes, + **kwargs + ) + ) + + def walk_SubstrateNode(self, node): + return node.substrate + + def walk_Capacitor(self, node): + device = self.walk(node.dev) + kwargs = {} + if node.model is not None: + model_name = self.walk(node.model) + kwargs['model'] = model_name + self._present._required_models.add(model_name.lower()) + value = None + if node.value is not None: + value = self.walk(node.value) + kwargs['capacitance'] = value + if node.parameters is not None: + parameters = self.walk(node.parameters) + 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) + negative = self.walk(node.negative) + nodes = ( + positive, + negative + ) + self._present.append( + ElementStatement( + Capacitor, + device, + *nodes, + **kwargs + ) + ) + + def walk_CurrentControlledCurrentSource(self, node): + device = self.walk(node.dev) + 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) + kwargs = {"I": controller} + else: + value = self.walk(node.gain) + kwargs = {"I": I(device)*value} + + positive = self.walk(node.positive) + negative = self.walk(node.negative) + nodes = ( + positive, + negative + ) + self._present.append( + ElementStatement( + BehavioralSource, + device, + *nodes, + **kwargs + ) + ) + + def walk_CurrentControlledVoltageSource(self, node): + device = self.walk(node.dev) + 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) + kwargs = {"V": controller} + else: + value = self.walk(node.transresistance) + kwargs = {"V": I(device)*value} + + positive = self.walk(node.positive) + negative = self.walk(node.negative) + nodes = ( + positive, + negative + ) + self._present.append( + ElementStatement( + BehavioralSource, + device, + *nodes, + **kwargs + ) + ) + + def walk_CurrentSource(self, node): + device = self.walk(node.dev) + kwargs = {} + if node.dc_value is not None: + kwargs['dc_value'] = self.walk(node.dc_value) + if node.ac_magnitude is not None: + kwargs['ac_magnitude'] = self.walk(node.ac_magnitude) + if node.ac_phase is not None: + kwargs['ac_phase'] = self.walk(node.ac_phase) + if node.transient is not None: + kwargs['transient'] = self.walk(node.transient) + + positive = self.walk(node.positive) + negative = self.walk(node.negative) + nodes = ( + positive, + negative + ) + self._present.append( + ElementStatement( + CurrentSource, + device, + *nodes, + **kwargs + ) + ) + + def walk_Diode(self, node): + device = self.walk(node.dev) + kwargs = {} + if node.model is None: + raise ValueError("The device {} has no model".format(node.dev)) + else: + model_name = self.walk(node.model) + kwargs['model'] = model_name + self._present._required_models.add(model_name.lower()) + if node.area is not None: + area = self.walk(node.area) + kwargs['area'] = area + + positive = self.walk(node.positive) + negative = self.walk(node.negative) + nodes = ( + positive, + negative + ) + self._present.append( + ElementStatement( + Diode, + device, + *nodes, + **kwargs + ) + ) + + def walk_Inductor(self, node): + device = self.walk(node.dev) + kwargs = {} + if node.model is not None: + model_name = self.walk(node.model) + kwargs['model'] = model_name + self._present._required_models.add(model_name.lower()) + value = None + if node.value is not None: + value = self.walk(node.value) + kwargs['inductance'] = value + if node.parameters is not None: + parameters = self.walk(node.parameters) + 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) + negative = self.walk(node.negative) + nodes = ( + positive, + negative + ) + self._present.append( + ElementStatement( + Inductor, + device, + *nodes, + **kwargs + ) + ) + + def walk_JFET(self, node): + device = self.walk(node.dev) + kwargs = {} + if node.model is None: + raise ValueError("The device {} has no model".format(node.dev)) + else: + model_name = self.walk(node.model) + kwargs["model"] = model_name + self._present._required_models.add(model_name.lower()) + if node.area is not None: + area = self.walk(node.area) + kwargs["area"] = area + if node.parameters is not None: + parameters = self.walk(node.parameters) + kwargs.update(parameters) + + drain = self.walk(node.drain) + gate = self.walk(node.gate) + source = self.walk(node.source) + nodes = [ + drain, + gate, + source + ] + self._present.append( + ElementStatement( + JunctionFieldEffectTransistor, + device, + *nodes, + **kwargs + ) + ) + + def walk_MOSFET(self, node): + device = self.walk(node.dev) + kwargs = {} + if node.model is None: + raise ValueError("The device {} has no model".format(node.dev)) + else: + model_name = self.walk(node.model) + kwargs["model"] = model_name + self._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]) + else: + kwargs.update(self.walk(parameter)) + else: + kwargs.update(self.walk(node.param)) + drain = self.walk(node.drain) + gate = self.walk(node.gate) + source = self.walk(node.source) + bulk = self.walk(node.bulk) + nodes = [ + drain, + gate, + source, + bulk + ] + self._present.append( + ElementStatement( + Mosfet, + device, + *nodes, + **kwargs + ) + ) + + def walk_MutualInductor(self, node): + device = self.walk(node.dev) + kwargs = {} + if node.model is not None: + model_name = self.walk(node.model) + kwargs['model'] = model_name + self._present._required_models.add(model_name.lower()) + inductors = self.walk(node.inductor) + if len(inductors) != 2: + raise ParseError("Presently, only two inductors are allowed.") + inductor1 = inductors[0] + inductor2 = inductors[1] + coupling_factor = self.walk(node.value) + kwargs["inductor1"] = inductor1 + kwargs["inductor2"] = inductor2 + kwargs["coupling_factor"] = coupling_factor + + self._present.append( + ElementStatement( + CoupledInductor, + device, + **kwargs + ) + ) + + def walk_NonLinearDependentSource(self, node): + device = self.walk(node.dev) + positive = self.walk(node.positive) + negative = self.walk(node.negative) + nodes = ( + positive, + negative + ) + expr = self.walk(node.expr) + kwargs = {} + if node.parameters is not None: + parameters = self.walk(node.parameters) + kwargs.update(parameters) + if node.magnitude == "V": + kwargs["voltage_expression"] = expr + else: + kwargs["current_expression"] = expr + + self._present.append( + ElementStatement( + BehavioralSource, + device, + *nodes, + **kwargs + ) + ) + + def walk_Resistor(self, node): + device = self.walk(node.dev) + kwargs = {} + if node.model is not None: + model_name = self.walk(node.model) + kwargs['model'] = model_name + self._present._required_models.add(model_name.lower()) + value = None + if node.value is not None: + value = self.walk(node.value) + kwargs['resistance'] = value + if node.parameters is not None: + parameters = self.walk(node.parameters) + 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) + negative = self.walk(node.negative) + nodes = ( + positive, + negative + ) + self._present.append( + ElementStatement( + Resistor, + device, + *nodes, + **kwargs + ) + ) + + def walk_Subcircuit(self, node): + device = self.walk(node.dev) + node_node = self.walk(node.node) + 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) + kwargs.update(parameters) + self._present._required_subcircuits.add(subcircuit_name.lower()) + self._present.append( + ElementStatement( + SubCircuitElement, + device, + subcircuit_name, + *nodes, + **kwargs + ) + ) + + def walk_Switch(self, node): + device = self.walk(node.dev) + kwargs = {} + if node.model is not None: + model_name = self.walk(node.model) + kwargs['model'] = model_name + self._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) + negative = self.walk(node.negative) + if node.control_p is not None: + if node.control_n is not None: + control_p = self.walk(node.control_p) + control_n = self.walk(node.control_n) + 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 + ) + self._present.append( + ElementStatement( + VoltageControlledSwitch, + device, + *nodes, + **kwargs + ) + ) + + def walk_VoltageControlledCurrentSource(self, node): + device = self.walk(node.dev) + if (node.controller is None and node.control_positive is None and + node.control_negative is None and node.transconductance is None): + raise ValueError("Device {} not properly defined".format(node.dev)) + if node.controller is not None: + controller = self.walk(node.controller) + kwargs = {"I": controller} + else: + value = self.walk(node.transconductance) + kwargs = {"I": V(node.control_positive, node.control_negative) * value} + + positive = self.walk(node.positive) + negative = self.walk(node.negative) + nodes = ( + positive, + negative + ) + self._present.append( + ElementStatement( + BehavioralSource, + device, + *nodes, + **kwargs + ) + ) + + def walk_VoltageControlledVoltageSource(self, node): + device = self.walk(node.dev) + if (node.controller is None and node.control_positive is None and + node.control_negative 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) + kwargs = {"V": controller} + else: + value = self.walk(node.gain) + kwargs = {"V": V(node.control_positive, node.control_negative) * value} + + positive = self.walk(node.positive) + negative = self.walk(node.negative) + nodes = ( + positive, + negative + ) + self._present.append( + ElementStatement( + BehavioralSource, + device, + *nodes, + **kwargs + ) + ) + + def walk_VoltageSource(self, node): + device = self.walk(node.dev) + kwargs = {} + if node.dc_value is not None: + kwargs['dc_value'] = self.walk(node.dc_value) + if node.ac_magnitude is not None: + kwargs['ac_magnitude'] = self.walk(node.ac_magnitude) + if node.ac_phase is not None: + kwargs['ac_phase'] = self.walk(node.ac_phase) + if node.transient is not None: + kwargs['transient'] = self.walk(node.transient) + + positive = self.walk(node.positive) + negative = self.walk(node.negative) + nodes = ( + positive, + negative + ) + self._present.append( + ElementStatement( + VoltageSource, + device, + *nodes, + **kwargs + ) + ) + + + def walk_ControlVoltagePoly(self, node): + controllers = self.walk(node.value) + positive = self.walk(node.positive) + negative = self.walk(node.negative) + 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) + if isinstance(coefficients, list): + values.extend(coefficients) + else: + values.append(coefficients) + result = ['v(%s,%s)' % nodes + for nodes in zip(ctrl_pos, + ctrl_neg)] + result += [str(value) for value in values] + parameters = ' '.join(result) + return '{ POLY (%d) %s }' % (controllers, parameters) + + def walk_ControlCurrentPoly(self, node): + controllers = self.walk(node.value) + if len(node.device) < controllers: + raise ValueError( + "The number of control nodes is smaller than the expected controllers: {}".format(controllers)) + + ctrl_dev = node.device[:controllers] + + values = [] + if node.coefficient: + coefficients = self.walk(node.coefficient) + if isinstance(coefficients, list): + values.extend(coefficients) + else: + values.append(coefficients) + data = [self.walk(dev) for dev in ctrl_dev] + [str(value) for value in values] + parameters = ' '.join(data) + return '{ POLY (%d) %s }' % (controllers, parameters) + + def walk_ControlTable(self, node): + return Table(self.walk(node.expr), + list(zip(self.walk(node.input), + self.walk(node.output)))) + + def walk_ControlValue(self, node): + return self.walk(node.expression) + + def walk_TransientSpecification(self, node): + return self.walk(node.ast) + + def walk_TransientPulse(self, node): + parameters = dict([(key, value) + for value, key in zip(self.walk(node.ast), + ("initial_value", + "pulsed_value", + "delay_time", + "rise_time", + "fall_time", + "pulse_width", + "period", + "phase"))]) + return PulseMixin, parameters + + def walk_PulseArguments(self, node): + v1 = self.walk(node.v1) + value = [] + if node.value is not None: + value = self.walk(node.value) + return [v1] + value + + def walk_TransientPWL(self, node): + values = self.walk(node.ast) + + return PieceWiseLinearMixin, values + + def walk_PWLArguments(self, node): + t = self.walk(node.t) + value = self.walk(node.value) + if node.parameters is not None: + parameters = self.walk(node.parameters) + return (t, value), parameters + + def walk_PWLFileArguments(self, node): + filename = self.walk(node.filename) + parameters = {} + if node.parameters is not None: + parameters = self.walk(node.parameters) + return filename, parameters + + def walk_TransientSin(self, node): + parameters = dict([(key, value) + for value, key in zip(self.walk(node.ast), + ("offset", + "amplitude", + "frequency", + "delay", + "damping_factor"))]) + return SinusoidalMixin, parameters + + def walk_SinArguments(self, node): + v0 = self.walk(node.v0) + va = self.walk(node.va) + freq = self.walk(node.freq) + value = [] + if node.value is not None: + value = self.walk(node.value) + if isinstance(value, list): + return [v0, va, freq] + value + else: + return [v0, va, freq, value] + + def walk_TransientPat(self, node): + parameters = dict([(key, value) + for value, key in zip(self.walk(node.ast), + ("high_value", + "low_value", + "delay_time", + "rise_time", + "fall_time", + "bit_period", + "bit_pattern", + "repeat"))]) + return PatternMixin, parameters + + def walk_PatArguments(self, node): + vhi = self.walk(node.vhi) + vlo = self.walk(node.vlo) + td = self.walk(node.td) + tr = self.walk(node.tr) + tf = self.walk(node.tf) + tsample = self.walk(node.tsample) + data = self.walk(node.data) + repeat = False + if node.repeat is not None: + repeat = (node.repeat == '1') + return [vhi, vlo, td, tr, tf, tsample, data, repeat] + + def walk_ACCmd(self, node): + return node.text + + def walk_DataCmd(self, node): + table = node.table + names = node.name + values = self.walk(node.value) + 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)]) + self._root.appendData( + DataStatement( + table, + **parameters + ) + ) + + def walk_DCCmd(self, node): + return node.text + + def walk_IncludeCmd(self, node): + filename = self.walk(node.filename) + self._present.append( + IncludeStatement( + self._root, + filename + ) + ) + + def walk_ICCmd(self, node): + return node.text + + def walk_ModelCmd(self, node): + name = self.walk(node.name) + device = node.type + if node.parameters is not None: + parameters = self.walk(node.parameters) + else: + parameters = {} + + self._present.appendModel( + ModelStatement( + name, + device, + **parameters + ) + ) + + def walk_ModelName(self, node): + return node.name + + def walk_ParamCmd(self, node): + if node.parameters is not None: + parameters = self.walk(node.parameters) + else: + parameters = {} + + self._present.appendParam( + ParamStatement(**parameters) + ) + + def walk_LibCmd(self, node): + if node.block is not None: + self.walk(node.block) + else: + self.walk(node.call) + + def walk_LibBlock(self, node): + 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) + self._context.append(self._present) + self._present = library + self.walk(node.lines) + tmp = self._context.pop() + tmp.appendLibrary(self._present) + self._present = tmp + + def walk_LibCall(self, node): + 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) + self._present.appendLibraryCall( + LibCallStatement(filename, entries) + ) + + def walk_SimulatorCmd(self, node): + return node.simulator + + def walk_SubcktCmd(self, node): + name = self.walk(node.name) + 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) + parameters = None + if node.parameters is not None: + parameters = self.walk(node.parameters) + + 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) + self._context.append(self._present) + self._present = subckt + self.walk(node.lines) + tmp = self._context.pop() + tmp.appendSubCircuit(self._present) + self._present = tmp + + def walk_TitleCmd(self, node): + if id(self._root) == id(self._present): + self._root._title = self.walk(node.title) + else: + raise SyntaxError(".Title command can only be used in the root circuit.") + return self._root + + def walk_Lines(self, node): + self.walk(node.ast) + + def walk_CircuitLine(self, node): + self.walk(node.ast) + + def walk_NetlistLines(self, node): + self.walk(node.ast) + + def walk_NetlistLine(self, node): + self.walk(node.ast) + + def walk_Parameters(self, node): + if isinstance(node.ast, list): + result = {} + # The separators are not taken into account + for parameter in self.walk(node.ast[::2]): + result.update(parameter) + else: + result = self.walk(node.ast) + return result + + def walk_Parameter(self, node): + value = self.walk(node.value) + return {node.name: value} + + def walk_GenericExpression(self, node): + if node.value is None: + return self.walk(node.braced) + else: + return self.walk(node.value) + + def walk_BracedExpression(self, node): + return self.walk(node.ast) + + def walk_Ternary(self, node): + t = self.walk(node.t) + x = self.walk(node.x) + y = self.walk(node.y) + return self._functions["if"](t, x, y) + + def walk_Conditional(self, node): + return self.walk(node.expr) + + def walk_And(self, node): + left = self.walk(node.left) + if node.right is None: + return left + else: + right = node.right + return And(left, right) + + def walk_Not(self, node): + operator = self.walk(node.operator) + if node.op is None: + return operator + else: + return Not(operator) + + def walk_Or(self, node): + left = self.walk(node.left) + if node.right is None: + return left + else: + right = node.right + return Or(left, right) + + def walk_Xor(self, node): + left = self.walk(node.left) + if node.right is None: + return left + else: + right = node.right + return Xor(left, right) + + def walk_Relational(self, node): + if node.factor is None: + left = self.walk(node.left) + right = self.walk(node.right) + return self._relational[node.op](left, right) + else: + return self.walk(node.factor) + + def walk_ConditionalFactor(self, node): + if node.boolean is None: + self.walk(node.expr) + else: + return node.boolean.lower == "true" + + def walk_Expression(self, node): + if node.term is None: + return self.walk(node.ternary) + else: + return self.walk(node.term) + + def walk_Functional(self, node): + return self.walk(node.ast) + + def walk_Functions(self, node): + 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) + if isinstance(nodes, list): + return function(*nodes) + else: + return function(nodes) + elif l_func == 'i': + device = self.walk(node.device) + return function(device) + elif function.nargs == 1: + x = self.walk(node.x) + return function(x) + elif l_func == 'limit': + x = self.walk(node.x) + y = self.walk(node.y) + z = self.walk(node.z) + return function(x, y, z) + elif l_func == 'atan2': + x = self.walk(node.x) + y = self.walk(node.y) + return function(y, x) + elif l_func in ('aunif', 'unif'): + mu = self.walk(node.mu) + alpha = self.walk(node.alpha) + return function(mu, alpha) + elif l_func == "ddx": + f = node.f + x = self.walk(node.x) + return function(Symbol(f), x) + elif function.nargs == 2: + x = self.walk(node.x) + y = self.walk(node.y) + return function(x, y) + elif l_func == "if": + t = self.walk(node.t) + x = self.walk(node.x) + y = self.walk(node.y) + return function(t, x, y) + elif l_func == "limit": + x = self.walk(node.x) + y = self.walk(node.y) + z = self.walk(node.z) + return function(x, y, z) + elif l_func in ('agauss', 'gauss'): + mu = self.walk(node.mu) + alpha = self.walk(node.alpha) + n = self.walk(node.n) + return function(mu, alpha, n) + else: + raise NotImplementedError("Function: {}".format(node.func)); + + def walk_Term(self, node): + return self.walk(node.ast) + + def walk_AddSub(self, node): + lhs = self.walk(node.left) + if node.right is not None: + rhs = self.walk(node.right) + if node.op == "+": + return Add(lhs, rhs) + else: + return Sub(lhs, rhs) + else: + return lhs + + def walk_ProdDivMod(self, node): + lhs = self.walk(node.left) + if node.right is not None: + rhs = self.walk(node.right) + 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): + operator = self.walk(node.operator) + if node.op is not None: + if node.op == "-": + return Neg(operator) + else: + return Pos(operator) + else: + return operator + + def walk_Exponential(self, node): + lhs = self.walk(node.left) + if node.right is not None: + rhs = self.walk(node.right) + return Power(lhs, rhs) + else: + return lhs + + def walk_Factor(self, node): + return self.walk(node.ast) + + def walk_Variable(self, node): + if node.variable is None: + return self.walk(node.factor) + else: + return Symbol(node.variable) + + def walk_Value(self, node): + real = 0.0 + if node.real is not None: + real = self.walk(node.real) + imag = None + if node.imag is not None: + imag = self.walk(node.imag) + if imag is None: + return SpiceModelWalker._to_number(real) + else: + return complex(float(real), float(imag)) + + def walk_ImagValue(self, node): + return self.walk(node.value) + + def walk_RealValue(self, node): + return self.walk(node.value) + + def walk_NumberScale(self, node): + value = self.walk(node.value) + scale = node.scale + if scale is not None: + scale = normalize("NFKD", scale).lower() + result = UnitValue(self._suffix[scale], value) + else: + result = UnitValue(PrefixedUnit(ZeroPower()), value) + return result + + def walk_Float(self, node): + value = SpiceModelWalker._to_number(node.ast) + return value + + def walk_Int(self, node): + value = int(node.ast) + return value + + def walk_Comment(self, node): + # TODO implement comments on devices + return + + def walk_Separator(self, node): + if node.comment is not None: + return self.walk(node.comment) + + def walk_Device(self, node): + # 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): + return self.walk(node.ast) + + def walk_NetlistCmds(self, node): + return self.walk(node.ast) + + def walk_TableFile(self, node): + filename = self.walk(node.filename) + return TableFile(filename) + + def walk_NetNode(self, node): + return node.node + + def walk_Filename(self, node): + return node.ast + + def walk_BinaryPattern(self, node): + return ''.join(node.pattern) + + def walk_list(self, node): + return [self.walk(element) for element in node] + + def walk_closure(self, node): + return ''.join(node) + + def walk_object(self, node): + raise ParseError("No walker defined for the node: {}".format(node)) + + @staticmethod + def _to_number(value): + if isinstance(value, UnitValue): + newValue = int(value) + if value == newValue: + return value + else: + return float(value) + try: + return int(value) + except ValueError: + return float(value) + +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, library=False): + # Fixme: empty source + + self._path = path + self._root = None + self._present = None + self._context = [] + + 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") + + self._parser = parser(whitespace='', semantics=SpiceModelBuilderSemantics()) + + try: + self._model = self._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: + self._path = os.getcwd() + self._walker = SpiceModelWalker(self._path) + self._circuit = self._walker.walk(self._model) + if library: + self._circuit._required_models = {model.name.lower() + for model in self._circuit._models} + self._circuit._required_subcircuits = {subckt.name.lower() + for subckt in self._circuit._subcircuits} + try: + SpiceParser._check_models(self._circuit) + SpiceParser._sort_subcircuits(self._circuit) + except Exception as e: + raise ParseError("{}: ".format(self._path) + str(e)) from e + + @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) + + @staticmethod + def _check_models(circuit, available_models=set()): + p_available_models = {model.lower() for model in available_models} + p_available_models.update([model.name.lower() 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 = {subckt.lower() for subckt in available_subcircuits} + names = [subcircuit.name.lower() 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.lower()) + 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) + + + @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/Expressions.py b/PySpice/Spice/Expressions.py new file mode 100644 index 000000000..6f72d6fa7 --- /dev/null +++ b/PySpice/Spice/Expressions.py @@ -0,0 +1,602 @@ +import numpy as np +import operator as op + + +class Expression: + 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 __sub__(self, other): + return Sub(self, other) + + def __mul__(self, other): + return Mul(self, other) + + def __truediv__(self, other): + return Div(self, other) + + def __mod__(self, other): + return Mod(self, other) + + def __pow__(self, other): + return Power(self, other) + + 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) + + def __not__(self): + return Not(self) + + +class Function(Expression): + 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(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): + return "({:s} {:s} {:s})".format(str(self._lhs), str(self._string), str(self._rhs)) + + def __call__(self, **kwargs): + lhs = self._lhs.subs(**kwargs) + rhs = self._rhs.subs(**kwargs) + if isinstance(lhs, Expression) or isinstance(rhs, Expression): + return self.__class__(lhs, rhs) + else: + return self._op(lhs, rhs) + + +class UnaryOperator(Expression): + def __init__(self, op, string, operator): + self._op = op + self._operator = operator + self._string = string + + def __str__(self): + return "({:s}({:s}))".format(str(self._string), str(self._operator)) + + def __call__(self, **kwargs): + operator = self._operator.subs(**kwargs) + if isinstance(operator, Expression): + return self.__class__(operator) + else: + return self._op(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, operator): + super(Not, self).__init__(lambda operator: not operator, "not", operator) + + +class And(BinaryOperator): + def __init__(self, lhs, rhs): + super(And, self).__init__(lambda lhs, rhs: lhs and rhs, "and", lhs, rhs) + + +class Or(BinaryOperator): + def __init__(self, lhs, rhs): + super(Or, self).__init__(lambda lhs, rhs: lhs or rhs, "or", lhs, rhs) + + +class Xor(BinaryOperator): + def __init__(self, lhs, rhs): + super(Xor, self).__init__(lambda lhs, rhs: lhs != rhs, "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 + + def __init__(self, *symbol): + super(AGauss, self).__init__(lambda mu, alpha, n: np.normal(mu, + alpha / n, + 1), *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 + + def __init__(self, *symbol): + super(AUnif, self).__init__(lambda mu, alpha: np.uniform(mu - alpha, + mu + alpha, + 1), *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 + + def __init__(self, *symbol): + def __init__(self, *symbol): + super(Gauss, self).__init__(lambda mu, alpha, n: np.normal(mu, + alpha * mu / n, + 1), *symbol) + + +class If(Function): + nargs = 3 + + def __init__(self, *symbol): + super(If, self).__init__(lambda t, x, y: x if t else y, *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 + + def __init__(self, *symbol): + super(Limit, self).__init__(lambda x, y, z: y if x < y else z if x > z else x, *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__(lambda x, y: np.power(x, y), *symbol) + + +class Pwr(Function): + nargs = 2 + + def __init__(self, *symbol): + super(Pwr, self).__init__(lambda x, y: np.power(x, y), *symbol) + + +class Pwrs(Function): + nargs = 2 + + def __init__(self, *symbol): + super(Pwrs, self).__init__(lambda x, y: np.copysign(np.power(np.abs(x), y), x), *symbol) + + +class Rand(Function): + nargs = 0 + + def __init__(self, *symbol): + super(Rand, self).__init__(np.random.rand(1), *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 + + def __init__(self, *symbol): + super(Stp, self).__init__(lambda x: x * (x > 0), *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 + + def __init__(self, *symbol): + super(Unif, self).__init__(lambda mu, alpha: np.uniform(mu * (1. - alpha), + mu * (1. + alpha), + 1), *symbol) + + +class URamp(Function): + nargs = 1 + + def __init__(self, *symbol): + super(URamp, self).__init__(lambda x: x * (x > 0), *symbol) + + +class Symbol(Expression): + def __init__(self, name): + self._name = str(name) + + def __str__(self): + return self._name + + def subs(self, **kwargs): + if self._name in kwargs: + return kwargs[self._name] + else: + return self + + +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 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 f3b1c32ca..0500eb0f5 100644 --- a/PySpice/Spice/HighLevelElement.py +++ b/PySpice/Spice/HighLevelElement.py @@ -386,6 +386,77 @@ def format_spice_parameters(self): #################################################################################################### +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 + + ############################################## + + 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 + ) + ) + + ")" + ) + +#################################################################################################### + class SingleFrequencyFMMixin(SourceMixinAbc): r"""This class implements a Single-Frequency FM waveform. @@ -741,6 +812,48 @@ def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs): #################################################################################################### +class PattermVoltageSource(VoltageSource, VoltageSourceMixinAbc, PatternMixin): + + r"""This class implements a patter 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 b71e280d3..ec4255e33 100644 --- a/PySpice/Spice/Library.py +++ b/PySpice/Spice/Library.py @@ -25,7 +25,7 @@ #################################################################################################### from ..Tools.File import Directory -from .Parser import SpiceParser +from .EBNFParser import SpiceParser #################################################################################################### @@ -73,13 +73,13 @@ def __init__(self, root_path): extension = path.extension.lower() if extension in self.EXTENSIONS: self._logger.debug("Parse {}".format(path)) - spice_parser = SpiceParser(path) - for subcircuit in spice_parser.subcircuits: - name = self._suffix_name(subcircuit.name, extension) - self._subcircuits[name.lower()] = path + spice_parser = SpiceParser(path=path, library=True) for model in spice_parser.models: name = self._suffix_name(model.name, extension) self._models[name.lower()] = path + for subcircuit in spice_parser.subcircuits: + name = self._suffix_name(subcircuit.name, extension) + self._subcircuits[name.lower()] = path ############################################## diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index 593dff350..0a4970926 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -521,8 +521,8 @@ def __init__(self, netlist, name, *args, **kwargs): #self._pins = kwargs.pop('pins',()) # Process remaining args - if len(self._positional_parameters_) < len(args): - raise NameError("Number of args mismatch") + if len(self._positional_parameters_) + self._number_of_optional_pins_ < 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: read = [False]*len(args) @@ -545,7 +545,7 @@ def __init__(self, netlist, name, *args, **kwargs): setattr(self, parameter, value) break else: - raise ValueError('Unknown argument {}={}'.format(key, value)) + raise ValueError('Unknown argument for {}: {}={}'.format(self.name, key, value)) netlist._add_element(self) @@ -942,25 +942,27 @@ def graph(self): ############################################## def __getitem__(self, attribute_name): + attr = str(attribute_name).lower() - if attribute_name in self._elements: - return self._elements[attribute_name] - elif attribute_name in self._models: - return self._models[attribute_name] + if attr in self._elements: + return self._elements[attr] + elif attr in self._models: + return self._models[attr] # Fixme: subcircuits - elif attribute_name in self._nodes: - return self._nodes[attribute_name] + elif attr in self._nodes: + return self._nodes[attr] else: - raise IndexError(attribute_name) # KeyError + raise IndexError(attr) # KeyError def _find_subcircuit(self, name): - if name not in self._subcircuits: - if hasattr(self, 'parent'): - return self.parent._find_subcircuit(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] + return self._subcircuits[name_low] def _add_node(self, node_name): @@ -1011,13 +1013,13 @@ def _add_element(self, element): """Add an element.""" if element.name not in self._elements: - self._elements[element.name] = element + self._elements[str(element.name).lower()] = element if hasattr(element, 'model'): model = element.model if model is not None: self._used_models.add(str(model).lower()) - if hasattr(element, 'subcircuit_name'): + if element.name[0] in "xX": subcircuit_name = element.subcircuit_name if subcircuit_name is not None: self._used_subcircuits.add(str(subcircuit_name).lower()) @@ -1032,7 +1034,7 @@ def _add_element(self, element): 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)) @@ -1044,7 +1046,7 @@ def model(self, name, modele_type, **parameters): model = DeviceModel(str(name).lower(), modele_type, **parameters) if model.name not in self._models: - self._models[model.name] = model + self._models[str(model.name).lower()] = model else: raise NameError("Model name {} is already defined".format(name)) @@ -1058,7 +1060,7 @@ def subcircuit(self, subcircuit): # Fixme: subcircuit is a class - self._subcircuits[str(subcircuit.name)] = subcircuit + self._subcircuits[str(subcircuit.name).lower()] = subcircuit subcircuit.parent=self ############################################## @@ -1119,23 +1121,28 @@ def _str_raw_spice(self): netlist += os.linesep return netlist - def include(self, path): - from .Parser import SpiceParser + def include(self, path, entry=None): + from .EBNFParser import SpiceParser """Include a file.""" if path not in self._includes: self._includes.append(path) - parser = SpiceParser(path=path) - models = parser.models + library = SpiceParser(path=path) + if entry is not None: + library = library[entry] + models = library.models for model in models: self.model(model._name, model._model_type, **model._parameters) self._models[model._name]._included = path - subcircuits = parser.subcircuits + subcircuits = library.subcircuits for subcircuit in subcircuits: subcircuit_def = subcircuit.build(parent=self) self.subcircuit(subcircuit_def) - self._subcircuits[subcircuit._name]._included = path + self._subcircuits[subcircuit._name.lower()]._included = path + parameters = library.parameters + for param in parameters: + self.param(param) else: self._logger.warn("Duplicated include") @@ -1251,6 +1258,44 @@ def __str__(self): #################################################################################################### +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 @@ -1291,6 +1336,7 @@ def __init__(self, title, self._ground = ground self._global_nodes = set(global_nodes) # .global self._parameters = {} # .param + self._data = {} # .data # Fixme: not implemented # .csparam @@ -1327,7 +1373,12 @@ def clone(self, title=None): def parameter(self, name, expression): """Set a parameter.""" - self._parameters[str(name)] = str(expression) + self._parameters[str(name)] = expression + + ############################################## + + def data(self, table, **kwargs): + self._data.update[table] = kwargs ############################################## @@ -1402,3 +1453,16 @@ 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/SpiceGrammar.py b/PySpice/Spice/SpiceGrammar.py new file mode 100644 index 000000000..6031f5018 --- /dev/null +++ b/PySpice/Spice/SpiceGrammar.py @@ -0,0 +1,4595 @@ +#!/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.util import re, generic_main # noqa + + +KEYWORDS = {} # type: ignore + + +class SpiceBuffer(Buffer): + def __init__( + self, + text, + whitespace=None, + nameguard=None, + comments_re=None, + eol_comments_re=None, + ignorecase=True, + namechars='', + **kwargs + ): + super().__init__( + text, + whitespace=whitespace, + nameguard=nameguard, + comments_re=comments_re, + eol_comments_re=eol_comments_re, + ignorecase=ignorecase, + namechars=namechars, + **kwargs + ) + + +class SpiceParser(Parser): + def __init__( + self, + whitespace=None, + nameguard=None, + comments_re=None, + eol_comments_re=None, + ignorecase=True, + left_recursion=True, + parseinfo=True, + keywords=None, + namechars='', + tokenizercls=SpiceBuffer, + **kwargs + ): + if keywords is None: + keywords = KEYWORDS + super().__init__( + whitespace=whitespace, + nameguard=nameguard, + comments_re=comments_re, + eol_comments_re=eol_comments_re, + ignorecase=ignorecase, + left_recursion=left_recursion, + parseinfo=parseinfo, + keywords=keywords, + namechars=namechars, + tokenizercls=tokenizercls, + **kwargs + ) + + @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.name_last_node('title') + + def block3(): + self._newline_() + self._asterisk_() + + def block4(): + self._st_() + self._closure(block4) + self._text_() + self.name_last_node('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: ' + "'~' " + ' ' + '' + '' + '' + '' + ' ' + ' ' + ' ' + '' + ) + + @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: ' + "'V' 'I'" + ) + 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( + ['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_() + 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'" + ) + self._define( + ['sep'], + [] + ) + + @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._model_name_() + self.name_last_node('model') + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('value') + with self._option(): + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('value') + with self._option(): + self._sep_() + self.name_last_node('sep') + self._model_name_() + self.name_last_node('model') + self._error( + 'expecting one of: ' + '' + ) + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._parameters_() + self.name_last_node('parameters') + 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') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._parameters_() + self.name_last_node('parameters') + 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._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') + with self._option(): + with self._group(): + self._node_() + self.name_last_node('control_positive') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('control_negative') + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('gain') + self._error( + 'expecting one of: ' + ' ' + ' ' + ) + self._define( + ['control_negative', 'control_positive', 'controller', 'dev', 'gain', 'negative', 'positive', 'sep'], + [] + ) + + @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._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._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') + with self._option(): + with self._group(): + self._node_() + self.name_last_node('control_positive') + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('control_negative') + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('transconductance') + self._error( + 'expecting one of: ' + ' ' + ' ' + ) + self._define( + ['control_negative', 'control_positive', 'controller', 'dev', 'negative', '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._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') + + def sep5(): + with self._group(): + self._sep_() + self.name_last_node('sep') + + def block5(): + 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_() + 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') + self._error( + 'expecting one of: ' + ' ' + ) + self._positive_join(block5, sep5) + 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_() + 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') + 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._gen_expr_() + self.name_last_node('dc_value') + 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') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._transient_specification_() + self.name_last_node('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') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._parameters_() + self.name_last_node('parameters') + 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._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( + ['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._model_name_() + self.name_last_node('model') + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('value') + with self._option(): + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('value') + with self._option(): + self._sep_() + self.name_last_node('sep') + self._model_name_() + self.name_last_node('model') + self._error( + 'expecting one of: ' + '' + ) + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._parameters_() + self.name_last_node('parameters') + 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 sep18(): + 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') + + def block18(): + self._value_() + self.name_last_node('value') + self._join(block18, sep18) + 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( + ['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') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._substrate_node_() + self.name_last_node('substrate') + + def block9(): + self._sep_() + self.name_last_node('sep') + self._node_() + self.add_last_node_to_name('args') + self._positive_closure(block9) + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('area') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._parameters_() + self.name_last_node('parameters') + self._define( + ['area', 'base', 'collector', 'dev', 'emitter', 'parameters', 'sep', 'substrate'], + ['args'] + ) + + @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') + self._define( + ['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._model_name_() + self.name_last_node('model') + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('value') + with self._option(): + self._sep_() + self.name_last_node('sep') + self._gen_expr_() + self.name_last_node('value') + with self._option(): + self._sep_() + self.name_last_node('sep') + self._model_name_() + self.name_last_node('model') + self._error( + 'expecting one of: ' + '' + ) + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._parameters_() + self.name_last_node('parameters') + 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: ' + "'ON' 'OFF'" + ) + self.name_last_node('initial_state') + 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_() + 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: ' + "'ON' 'OFF'" + ) + self.name_last_node('initial_state') + 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._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( + ['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._gen_expr_() + self.name_last_node('dc_value') + 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') + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._transient_specification_() + self.name_last_node('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: ' + "'PULSE' 'SIN'" + " 'EXP' " + "'PAT' 'PWL'" + " 'SFFM' " + ) + + @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_() + with self._option(): + self._sep_() + self.name_last_node('sep') + self._pulse_arguments_() + self.name_last_node('@') + 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_() + with self._option(): + self._sep_() + self.name_last_node('sep') + self._sin_arguments_() + self.name_last_node('@') + 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_() + with self._option(): + self._sep_() + self.name_last_node('sep') + self._exp_arguments_() + self.name_last_node('@') + 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_() + with self._option(): + self._sep_() + self.name_last_node('sep') + self._pat_arguments_() + self.name_last_node('@') + 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( + ['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_() + 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( + ['filename', 'parameters', 'sep'], + [] + ) + + @tatsumasu('PWLArguments') + def _pwl_arguments_(self): # noqa + with self._group(): + with self._choice(): + with self._option(): + + def block0(): + self._sep_() + self.name_last_node('sep') + self._closure(block0) + self._lp_() + self._cut() + + def block2(): + 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._positive_closure(block2) + self._sep_() + self.name_last_node('sep') + self._rp_() + with self._option(): + + 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._positive_closure(block8) + 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', '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_() + with self._option(): + self._sep_() + self.name_last_node('sep') + self._sffm_arguments_() + self.name_last_node('@') + 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: ' + "'.EMBEDDEDSAMPLING'" + " '.INCLUDE'" + "'.INCL' '.INC' '.LIB'" + ' ' + ' ' + " '.SUBCKT' '.SIMULATOR'" + " '.TITLE' " + "'.AC' '.DC' " + ) + + @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' '.IC' '.DCVOLT'" + " '.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') + with self._option(): + with self._group(): + self._token('DATA') + self.name_last_node('sweep') + + def block10(): + self._st_() + self._closure(block10) + self._token('=') + + def block11(): + self._st_() + self._closure(block11) + self._id_() + self.name_last_node('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: ' + "'LIN' 'OCT' 'DEC'" + ) + + @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._positive_closure(block3) + + def block6(): + self._sep_() + self.name_last_node('sep') + self._value_() + self.name_last_node('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 block3(): + self._st_() + self._closure(block3) + self._token('=') + + def block4(): + self._st_() + self._closure(block4) + self._id_() + self.name_last_node('table') + with self._option(): + with self._group(): + + def block6(): + 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._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') + 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') + 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 sep33(): + with self._group(): + self._sep_() + self.name_last_node('sep') + + def block33(): + self._value_() + self.name_last_node('point') + self._positive_join(block33, sep33) + self._error( + 'expecting one of: ' + '' + ) + self._positive_closure(block6) + 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 block3(): + self._st_() + self._closure(block3) + self._token('=') + + def block4(): + self._st_() + self._closure(block4) + + def sep5(): + with self._group(): + self._es_sep_() + + def block5(): + self._id_() + self.name_last_node('name') + self._positive_join(block5, sep5) + self._sep_() + self.name_last_node('sep') + self._token('type') + self.name_last_node('parameter') + + def block9(): + self._st_() + self._closure(block9) + self._token('=') + + def block10(): + self._st_() + self._closure(block10) + + def sep11(): + with self._group(): + self._es_sep_() + + def block11(): + self._es_parameter_type_() + self.name_last_node('type') + self._positive_join(block11, sep11) + + def block13(): + self._sep_() + self.name_last_node('sep') + self._es_parameter_name_() + self.name_last_node('parameter') + + def block16(): + self._st_() + self._closure(block16) + self._token('=') + + def block17(): + self._st_() + self._closure(block17) + + def sep18(): + with self._group(): + self._es_sep_() + + def block18(): + self._gen_expr_() + self.name_last_node('value') + self._positive_join(block18, sep18) + self._closure(block13) + with self._option(): + with self._group(): + self._sep_() + self.name_last_node('sep') + self._token('useExpr') + self.name_last_node('parameter') + + def block22(): + self._st_() + self._closure(block22) + self._token('=') + self._cut() + + def block23(): + self._st_() + self._closure(block23) + self._boolean_() + self.name_last_node('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: ' + "'UNIFORM' 'NORMAL' 'GAMMA'" + ) + + @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' 'means' 'std_deviations'" + "'lower_bounds' '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: ' + "'.IC' '.DCVOLT'" + ) + self.name_last_node('cmd') + self._cut() + with self._group(): + with self._choice(): + with self._option(): + + def block2(): + self._sep_() + self.name_last_node('sep') + self._token('V') + self._lp_() + self._cut() + self._node_() + self.name_last_node('node') + self._rp_() + + def block5(): + self._st_() + self._closure(block5) + self._token('=') + + def block6(): + self._st_() + self._closure(block6) + self._gen_expr_() + self.name_last_node('value') + self._positive_closure(block2) + with self._option(): + + def block8(): + self._sep_() + self.name_last_node('sep') + self._node_() + self.name_last_node('node') + + def block11(): + self._st_() + self._positive_closure(block11) + self._gen_expr_() + self.name_last_node('value') + self._positive_closure(block8) + 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: ' + "'.INCLUDE' '.INCL' '.INC'" + ) + 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_() + with self._option(): + self._single_quote_() + self._cut() + self._filename_() + self.name_last_node('filename') + self._single_quote_() + 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_() + with self._option(): + self._single_quote_() + self._cut() + self._filename_() + self.name_last_node('filename') + self._single_quote_() + 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_() + with self._option(): + self._sep_() + self.name_last_node('sep') + self._parameters_() + self.name_last_node('parameters') + 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: ' + "'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'" + ) + + @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._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._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( + ['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', '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.name_last_node('@') + + def block1(): + with self._group(): + with self._choice(): + with self._option(): + with self._group(): + with self._optional(): + self._sep_() + self.name_last_node('@') + self._comma_() + with self._optional(): + self._sep_() + self.name_last_node('@') + with self._option(): + self._sep_() + self.name_last_node('@') + self._error( + 'expecting one of: ' + ' ' + ) + self._parameter_() + self.name_last_node('@') + 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._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: ' + ' ' + ' ' + ) + self._define( + ['braced', 'value'], + [] + ) + + @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_() + 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('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: ' + ' ' + ' ' + ) + self._define( + ['term', 'ternary'], + [] + ) + + @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') + self._define( + ['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( + ['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( + ['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( + ['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') + with self._option(): + self._conditional_factor_() + self.name_last_node('factor') + self._error( + 'expecting one of: ' + ' ' + ' ' + ' ' + '' + ) + self._define( + ['factor', 'left', 'op', 'right', 'sep'], + [] + ) + + @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_() + with self._option(): + self._boolean_() + self.name_last_node('boolean') + self._error( + 'expecting one of: ' + "'(' 'TRUE' 'FALSE' " + ) + self._define( + ['boolean', 'expr', 'sep'], + [] + ) + + @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( + ['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( + ['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( + ['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_() + 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-Z0-9_:`@#\\." + '\\$]*[a-zA-Z0-9_`@#\\.\\$] [a-zA-Z]' + ' ' + ) + self._define( + ['factor', 'sep', 'variable'], + [] + ) + + @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_() + with self._option(): + self._value_() + self.name_last_node('@') + self._error( + 'expecting one of: ' + "'(' " + '' + ) + self._define( + ['sep'], + [] + ) + + @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: ' + "'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' " + "'atan2' 'ddx' 'agauss' 'gauss' 'if'" + " 'limit' 'min' 'max' 'pwrs'" + "'pow' 'pwr' 'sign' 'rand'" + "'aunif' 'unif' 'i' '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: ' + "'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'" + ) + 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: ' + "'min' 'max' 'pwrs' 'pow' 'pwr' '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._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: ' + "'time' 'temper' 'temp' 'freq' 'vt' 'pi'" + ) + + @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') + 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') + self._define( + ['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') + 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._error( + 'expecting one of: ' + '[\\+\\-]?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(' + '[eE][\\-\\+]?[0-9]{1,3})? ' + '[\\+\\-]?[0-9]+ ' + ) + self._define( + ['scale', 'value'], + [] + ) + + @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: ' + "'TRUE' 'FALSE'" + ) + + @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( + ['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( + ['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-Z0-9_:`@#\\.\\$\\/]*[a-' + 'zA-Z0-9_`@#\\.\\$] [a-zA-Z_`@#\\$]' + ) + + @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-Z0-9_:`@#\\.\\$]*[a-zA' + '-Z0-9_`@#\\.\\$] [a-zA-Z]' + ) + + @tatsumasu() + def _end_sep_(self): # noqa + with self._choice(): + with self._option(): + self._cmd_net_sep_() + self.name_last_node('@') + + def block1(): + self._st_() + self._closure(block1) + with self._option(): + + def block2(): + self._st_() + self._positive_closure(block2) + self._error( + 'expecting one of: ' + ' ' + ' [ \\t]' + ) + + @tatsumasu() + def _sep_(self): # noqa + with self._choice(): + with self._option(): + + def block0(): + self._cmd_net_sep_() + self.name_last_node('@') + + def block2(): + self._st_() + self._closure(block2) + self._token('+') + + def block3(): + self._st_() + self._closure(block3) + self._positive_closure(block0) + with self._option(): + + def block4(): + self._st_() + self._positive_closure(block4) + 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._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(object): + 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 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, start=None, **kwargs): + if start is None: + start = 'start' + if not filename or filename == '-': + text = sys.stdin.read() + else: + with open(filename) as f: + text = f.read() + parser = SpiceParser() + return parser.parse( + text, + rule_name=start, + 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..6235bf856 --- /dev/null +++ b/PySpice/Spice/SpiceModel.py @@ -0,0 +1,655 @@ +#!/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 tatsu.objectmodel import Node +from tatsu.semantics import ModelBuilderSemantics + + +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(SpiceModelBuilderSemantics, self).__init__(context=context, types=types) + + +class Circuit(ModelBase): + lines = None + title = None + + +class Lines(ModelBase): + pass + + +class CircuitLine(ModelBase): + pass + + +class NetlistLines(ModelBase): + pass + + +class NetlistLine(ModelBase): + pass + + +class NonLinearDependentSource(ModelBase): + dev = None + expr = None + magnitude = None + negative = None + parameters = None + positive = None + sep = None + + +class Capacitor(ModelBase): + dev = None + model = None + negative = None + parameters = None + positive = None + sep = None + value = None + + +class Diode(ModelBase): + area = None + dev = None + model = None + negative = None + parameters = None + positive = None + sep = None + + +class VoltageControlledVoltageSource(ModelBase): + control_negative = None + control_positive = None + controller = None + dev = None + gain = None + negative = None + positive = None + sep = None + + +class CurrentControlledCurrentSource(ModelBase): + controller = None + dev = None + device = None + gain = None + negative = None + positive = None + sep = None + + +class VoltageControlledCurrentSource(ModelBase): + control_negative = None + control_positive = None + controller = None + dev = None + negative = None + positive = None + sep = None + transconductance = None + + +class CurrentControlledVoltageSource(ModelBase): + controller = None + dev = None + device = None + negative = None + positive = None + sep = None + transresistance = None + + +class ControlValue(ModelBase): + expression = None + sep = None + type = None + + +class ControlTable(ModelBase): + expr = None + input = None + output = None + sep = None + type = None + + +class ControlVoltagePoly(ModelBase): + coefficient = None + negative = None + positive = None + sep = None + value = None + + +class ControlCurrentPoly(ModelBase): + coefficient = None + device = None + sep = None + value = None + + +class CurrentSource(ModelBase): + ac_magnitude = None + ac_phase = None + dc_value = None + dev = None + negative = None + positive = None + sep = None + transient = None + + +class JFET(ModelBase): + area = None + dev = None + drain = None + gate = None + model = None + parameters = None + sep = None + source = None + + +class MutualInductor(ModelBase): + dev = None + inductor = None + model = None + sep = None + value = None + + +class Inductor(ModelBase): + dev = None + model = None + negative = None + parameters = None + positive = None + sep = None + value = None + + +class MOSFET(ModelBase): + bulk = None + dev = None + drain = None + gate = None + model = None + name = None + param = None + parameter = None + sep = None + source = None + value = None + + +class BJT(ModelBase): + area = None + args = None + base = None + collector = None + dev = None + emitter = None + parameters = None + sep = None + substrate = None + + +class SubstrateNode(ModelBase): + substrate = None + + +class Resistor(ModelBase): + dev = None + model = None + negative = None + parameters = None + positive = None + sep = None + value = None + + +class Switch(ModelBase): + control_n = None + control_p = None + dev = None + initial_state = None + model = None + negative = None + positive = None + sep = None + + +class Subcircuit(ModelBase): + dev = None + node = None + parameters = None + params = None + sep = None + + +class VoltageSource(ModelBase): + ac_magnitude = None + ac_phase = None + dc_value = None + dev = None + negative = None + positive = None + sep = None + transient = None + + +class TransientSpecification(ModelBase): + pass + + +class TransientPulse(ModelBase): + sep = None + type = None + + +class PulseArguments(ModelBase): + sep = None + v1 = None + value = None + + +class TransientSin(ModelBase): + sep = None + type = None + + +class SinArguments(ModelBase): + freq = None + sep = None + v0 = None + va = None + value = None + + +class TransientExp(ModelBase): + sep = None + type = None + + +class ExpArguments(ModelBase): + sep = None + v1 = None + v2 = None + value = None + + +class TransientPat(ModelBase): + sep = None + type = None + + +class PatArguments(ModelBase): + data = None + repeat = None + sep = None + td = None + tf = None + tr = None + tsample = None + vhi = None + vlo = None + + +class TransientPWL(ModelBase): + type = None + + +class PWLFileArguments(ModelBase): + filename = None + parameters = None + sep = None + + +class PWLArguments(ModelBase): + parameters = None + sep = None + t = None + value = None + + +class TransientSFFM(ModelBase): + sep = None + type = None + + +class SFFMArguments(ModelBase): + sep = None + v0 = None + va = None + value = None + + +class Command(ModelBase): + pass + + +class NetlistCmds(ModelBase): + pass + + +class ACCmd(ModelBase): + cmd = None + end = None + points = None + sep = None + start = None + sweep = None + table = None + + +class DataCmd(ModelBase): + cmd = None + name = None + sep = None + table = None + value = None + + +class DCCmd(ModelBase): + cmd = None + name = None + point = None + points = None + sep = None + start = None + step = None + stop = None + sweep = None + table = None + + +class EmbeddedSamplingCmd(ModelBase): + cmd = None + name = None + parameter = None + sep = None + type = None + value = None + + +class ICCmd(ModelBase): + cmd = None + node = None + sep = None + value = None + + +class IncludeCmd(ModelBase): + cmd = None + filename = None + sep = None + + +class LibCmd(ModelBase): + block = None + call = None + cmd = None + sep = None + + +class LibCall(ModelBase): + entry = None + filename = None + sep = None + + +class ModelCmd(ModelBase): + cmd = None + name = None + parameters = None + sep = None + type = None + + +class ParamCmd(ModelBase): + cmd = None + parameters = None + sep = None + + +class SimulatorCmd(ModelBase): + cmd = None + sep = None + simulator = None + + +class SubcktCmd(ModelBase): + cmd = None + lines = None + name = None + node = None + parameters = None + sep = None + + +class LibBlock(ModelBase): + entry = None + lines = None + sep = None + + +class TitleCmd(ModelBase): + cmd = None + title = None + + +class Parameters(ModelBase): + pass + + +class Parameter(ModelBase): + name = None + sep = None + value = None + + +class GenericExpression(ModelBase): + braced = None + value = None + + +class TableFile(ModelBase): + filename = None + func = None + sep = None + + +class BracedExpression(ModelBase): + sep = None + + +class Expression(ModelBase): + term = None + ternary = None + + +class Ternary(ModelBase): + op = None + sep = None + t = None + x = None + y = None + + +class Conditional(ModelBase): + expr = None + + +class Or(ModelBase): + left = None + op = None + right = None + sep = None + + +class Xor(ModelBase): + left = None + op = None + right = None + sep = None + + +class And(ModelBase): + left = None + op = None + right = None + sep = None + + +class Not(ModelBase): + op = None + operator = None + + +class Relational(ModelBase): + factor = None + left = None + op = None + right = None + sep = None + + +class ConditionalFactor(ModelBase): + boolean = None + expr = None + sep = None + + +class Term(ModelBase): + pass + + +class AddSub(ModelBase): + left = None + op = None + right = None + sep = None + + +class ProdDivMod(ModelBase): + left = None + op = None + right = None + sep = None + + +class Sign(ModelBase): + op = None + operator = None + + +class Exponential(ModelBase): + left = None + op = None + right = None + sep = None + + +class Functional(ModelBase): + pass + + +class Variable(ModelBase): + factor = None + sep = None + variable = None + + +class Factor(ModelBase): + sep = None + + +class Functions(ModelBase): + pass + + +class Value(ModelBase): + imag = None + real = None + unit = None + + +class ImagValue(ModelBase): + value = None + + +class RealValue(ModelBase): + value = None + + +class NumberScale(ModelBase): + scale = None + value = None + + +class Unit(ModelBase): + pass + + +class Hz(ModelBase): + pass + + +class Float(ModelBase): + pass + + +class Int(ModelBase): + pass + + +class Filename(ModelBase): + pass + + +class ModelName(ModelBase): + name = None + sep = None + + +class BinaryPattern(ModelBase): + pattern = None + + +class Device(ModelBase): + pass + + +class NetNode(ModelBase): + node = None + sep = None + + +class Separator(ModelBase): + comment = None + + +class Comment(ModelBase): + pass diff --git a/PySpice/Unit/Unit.py b/PySpice/Unit/Unit.py index ba8874543..5170b8901 100644 --- a/PySpice/Unit/Unit.py +++ b/PySpice/Unit/Unit.py @@ -1064,9 +1064,9 @@ 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) + prefix_string = self._prefixed_unit.str(spice, unit) + if space and (prefix_string != ""): + string += ' ' + prefix_string return string ############################################## diff --git a/requirements.txt b/requirements.txt index b06bc68d4..4c5cf42c4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,11 @@ PyYAML>=3.10 cffi>=0.8.6 matplotlib>=1.3 numpy>=1.7 + +PySpice~=1.3.dev0 +regex~=2021.4.4 +ply~=3.11 +networkx~=2.5.1 +TatSu~=5.6.1 +scipy~=1.6.3 +setuptools~=56.2.0 diff --git a/unit-test/Unit/mosdriver.lib b/unit-test/Spice/mosdriver.lib similarity index 100% rename from unit-test/Unit/mosdriver.lib rename to unit-test/Spice/mosdriver.lib diff --git a/unit-test/Spice/test_Expression.py b/unit-test/Spice/test_Expressions.py similarity index 50% rename from unit-test/Spice/test_Expression.py rename to unit-test/Spice/test_Expressions.py index 6215ad905..c12cb5e52 100644 --- a/unit-test/Spice/test_Expression.py +++ b/unit-test/Spice/test_Expressions.py @@ -24,62 +24,27 @@ #################################################################################################### -from PySpice.Spice.Expression.Parser import Parser +from PySpice.Spice.Expressions import * #################################################################################################### -class TestParser(unittest.TestCase): - ############################################## +class TestExpression: - def test_parser(self): + def test_symbol(self): + x = Symbol('x') + V_3 = V(Symbol("3")) + cos_V_3 = Cos(V_3) + values = {str(V_3): 25} + print(cos_V_3(**values)) + y = Symbol('y') + add = Add(x, y) + print(add) + V_5 = V("5") + print(V_5) + print(Cos(27)) + print(Cos(27)()) - 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__': diff --git a/unit-test/Spice/test_SpiceParser.py b/unit-test/Spice/test_SpiceParser.py new file mode 100644 index 000000000..7368279af --- /dev/null +++ b/unit-test/Spice/test_SpiceParser.py @@ -0,0 +1,427 @@ +import unittest +from PySpice.Spice.Netlist import Circuit +from PySpice.Spice.EBNFParser import SpiceParser +from multiprocessing import Pool, cpu_count +import os +import tatsu + +data = """* Data test +*More notes + +G1 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 .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 + +BG1 IOUT- IOUT+ I={IF( (V(VC+,VC-) <= 0) , 0 , 1 )} +BG2 IOUT- IOUT+ I={IF( (V(VC+,VC-)<=0),0,GAIN*V(VC+,VC-) )} + +.SUBCKT VCCS_LIM_CLAW+_0_OPA350 VCp VCm IOUTp IOUTm +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)} +.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 + +V99 99 26 DC 0 AC 0 PULSE 0 0 0 100n 100n 500n 1u + +.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 "ipwl.txt" +IPWL3 3 0 PWL file "ipwl.csv" +IPWL4 4 0 PWL FILE ipwl.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,4 +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 +""" + +def circuit_gft(prb): + parser = SpiceParser(source=prb[0]) + circuit = parser.build_circuit() + circuit.parameter('prb', str(prb[1])) + simulator = circuit.simulator(simulator='xyce-serial') + simulator.save(['all']) + return simulator + + +class TestSpiceParser(unittest.TestCase): + def test_parser(self): + # 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)') + + def test_library(self): + from PySpice.Spice.Library import SpiceLibrary + import os + libraries_path = os.path.abspath('.') + spice_library = SpiceLibrary(libraries_path) + circuit = Circuit('MOS Driver') + circuit.include(spice_library['mosdriver']) + x_mos = circuit.X('driver', + 'mosdriver', + 'hb', + 'hi', + 'ho', + 'hs', + 'li', + 'lo', + 'vdd', + 'vss') + simulator = circuit.simulator(simulator='xyce-serial', + temperature=25, + nominal_temperature=25) + simulator.options('device smoothbsrc=1') + print(simulator) + + def test_subcircuit(self): + print(os.getcwd()) + circuit = Circuit('MOS Driver') + circuit.include(os.path.join(os.getcwd(), '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) + expected = """.title MOS Driver + +.subckt mosdriver hb hi ho hs li lo vdd vss +.model diode D (is=1.038e-15 n=1 tt=2e-08 cjo=5e-12 rs=0.5 bv=130) + +bhigh hoi hs v={if((v(hi, vss) > 0.5), 5, 0)} smoothbsrc=1 +rhoi hoi ho 1 +choi ho hs 1e-09 +blow loi vss v={if((v(li, vss) > 0.5), 5, 0)} smoothbsrc=1 +rloi loi lo 1 +cloi lo vss 1e-09 +dhb vdd hb diode +.ends mosdriver + +xtest 0 1 2 3 4 5 mosdriver +btest 1 0 v=if(0, 0, 1) smoothbsrc=1 +""" + result = str(circuit) + self.assertEqual(expected, result) + + +if __name__ == '__main__': + unittest.main() diff --git a/unit-test/Unit/test_SpiceParser.py b/unit-test/Unit/test_SpiceParser.py deleted file mode 100644 index 9e180224e..000000000 --- a/unit-test/Unit/test_SpiceParser.py +++ /dev/null @@ -1,415 +0,0 @@ -import unittest -from PySpice.Spice.Netlist import Circuit -from PySpice.Spice.Parser import SpiceParser -from multiprocessing import Pool, cpu_count -import os - -hsop77 = """ -.title HSOP77case - -* OP77 SPICE Macro-model -* Description: Amplifier -* Generic Desc: 6/30V, BIP, OP, Low Vos, Precision, 1X -* Developed by: JCB / PMI -* Revision History: 08/10/2012 - Updated to new header style -* 2.0 (12/1990) - Re-ordered subcircuit call out nodes to put the output node last. -* - Changed Ios from 0.3E-9 to 0.15E-9 -* - Added F1 and F2 to fix short circuit current limit. -* Copyright 1990, 2012 by Analog Devices, Inc. -* -* Refer to http://www.analog.com/Analog_Root/static/techSupport/designTools/spiceModels/license/spice_general.html for License Statement. Use of this model -* indicates your acceptance with the terms and provisions in the License Statement. -* -* BEGIN Notes: -* -* Not Modeled: -* -* Parameters modeled include: -* -* END Notes -* -* Node assignments -* non-inverting input -* | inverting input -* | | positive supply -* | | | negative supply -* | | | | output -* | | | | | -.SUBCKT OP77 1 2 99 50 39 -*#ASSOC Category="Op-amps" symbol=opamp -* -* INPUT STAGE & POLE AT 6 MHZ -* -R1 2 3 5E11 -R2 1 3 5E11 -R3 5 97 0.0606 -R4 6 97 0.0606 -CIN 1 2 4E-12 -C2 5 6 218.9E-9 -I1 4 51 1 -IOS 1 2 0.15E-9 -EOS 9 10 POLY(1) 30 33 10E-6 1 -Q1 5 2 7 QX -Q2 6 9 8 QX -R5 7 4 0.009 -R6 8 4 0.009 -D1 2 1 DX -D2 1 2 DX -EN 10 1 12 0 1 -GN1 0 2 15 0 1 -GN2 0 1 18 0 1 -* -EREF 98 0 33 0 1 -EPLUS 97 0 99 0 1 -ENEG 51 0 50 0 1 -* -* VOLTAGE NOISE SOURCE WITH FLICKER NOISE -* -DN1 11 12 DEN -DN2 12 13 DEN -VN1 11 0 DC 2 -VN2 0 13 DC 2 -* -* CURRENT NOISE SOURCE WITH FLICKER NOISE -* -DN3 14 15 DIN -DN4 15 16 DIN -VN3 14 0 DC 2 -VN4 0 16 DC 2 -* -* SECOND CURRENT NOISE SOURCE -* -DN5 17 18 DIN -DN6 18 19 DIN -VN5 17 0 DC 2 -VN6 0 19 DC 2 -* -* FIRST GAIN STAGE -* -R7 20 98 1 -G1 98 20 5 6 59.91 -D3 20 21 DX -D4 22 20 DX -E1 97 21 POLY(1) 97 33 -2.4 1 -E2 22 51 POLY(1) 33 51 -2.4 1 -* -* GAIN STAGE & DOMINANT POLE AT 0.053 HZ -* -R8 23 98 6.01E9 -C3 23 98 500E-12 -G2 98 23 20 33 33.3E-6 -V1 97 24 1.3 -V2 25 51 1.3 -D5 23 24 DX -D6 25 23 DX -* -* NEGATIVE ZERO AT -4MHZ -* -R9 26 27 1 -C4 26 27 -39.75E-9 -R10 27 98 1E-6 -E3 26 98 23 33 1E6 -* -* COMMON-MODE GAIN NETWORK WITH ZERO AT 20 HZ -* -R13 30 31 1 -L2 31 98 7.96E-3 -G4 98 30 3 33 1.0E-7 -D7 30 97 DX -D8 51 30 DX -* -* POLE AT 2 MHZ -* -R14 32 98 1 -C5 32 98 79.5E-9 -G5 98 32 27 33 1 -* -* OUTPUT STAGE -* -R15 33 97 1 -R16 33 51 1 -GSY 99 50 POLY(1) 99 50 0.325E-3 0.0425E-3 -F1 34 0 V3 1 -F2 0 34 V4 1 -R17 34 99 400 -R18 34 50 400 -L3 34 39 2E-7 -G6 37 50 32 34 2.5E-3 -G7 38 50 34 32 2.5E-3 -G8 34 99 99 32 2.5E-3 -G9 50 34 32 50 2.5E-3 -V3 35 34 6.8 -V4 34 36 4.4 -D9 32 35 DX -D10 36 32 DX -D11 99 37 DX -D12 99 38 DX -D13 50 37 DY -D14 50 38 DY -* -* MODELS USED -* -.MODEL QX NPN(BF=417E6) -.MODEL DX D(IS=1E-15) -.MODEL DY D(IS=1E-15 BV=50) -.MODEL DEN D(IS=1E-12, RS=12.08K, KF=1E-17, AF=1) -.MODEL DIN D(IS=1E-12, RS=7.55E-6, KF=1.55E-15, AF=1) -.ENDS OP77 - -Iinj 0 probe 0 AC {0.5*prb*(prb+1)} -Vinj probe Ninplp 0 AC {0.5*prb*(prb-1)} -Vprobe probe Noutlp 0 - -.model 2N2222 NPN(IS=1E-14 VAF=100 -+ BF=200 IKF=0.3 XTB=1.5 BR=3 -+ CJC=8E-12 CJE=25E-12 TR=100E-9 TF=400E-12 -+ ITF=1 VTF=2 XTF=3 RB=10 RC=.3 RE=.2) - -XU1 Ninp Ninn Np15 Nm15 Ninplp OP77 -XU2 No Nre Np15 Nm15 Nrep OP77 -R1 Ninnm 0 {r_1} -R2 Ne Ninnm {r_2} -C2 Ne Ninnm {c_2} -VU1offset Ninn Ninnm {v_1offset} -R3 Nin Ninp {r_3} -VU2offset Nre Nrep {v_2offset} -R4 Nrep Ninp {r_4} -C4 Nrep Ninp {c_4} -R5 Ne No {r_o} -Vi Nin 0 {vin} AC {1-prb*prb} -V2p15 Np15 0 15 -Vm15 Nm15 0 -15 -Q1 Nc Nb Ne 2N2222 -R6 Np15 Nc 50 -R7 Noutlp Nb 10 -D1 Ngl No YELLOW -Vl Ngl 0 0 -.model YELLOW D(IS=93.1P RS=42M N=4.61 BV=2 IBV=10U -+ CJO=2.97P VJ=.75 M=.333 TT=4.32U) - -.model NPN NPN -.model PNP PNP -.param prb=0 -.param vin=2.5 -.param r_1=1k -.param r_2=1k -.param r_3=1k -.param r_4=1k -.param r_o=200 -.param c_2=20p -.param c_4=20p -.param v_1offset=0 -.param v_2offset=0 - -.end -""" - -hsada4077 = """ -.title ADA4077-2case - -* ADA4077-2 SPICE DMod model Typical values -* Description: Amplifier -* Generic Desc: 30V, BIP, OP, Low Noise, Low THD, 2X -* Developed by: RM ADSJ -* Revision History: 02/11/2012 - Updated to new header style -* 0.0 (11/2012) -* Copyright 2008, 2012 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-2 1 2 99 50 45 -*#ASSOC Category="Op-amps" symbol=opamp -* -*INPUT STAGE -* -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.25E4 -RB2 42 43 9.25E4 -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-2 -*$ - -Iinj 0 probe 0 AC {0.5*prb*(prb+1)} -Vinj probe Ninplp 0 AC {0.5*prb*(prb-1)} -Vprobe probe Noutlp 0 - -.model 2N2222 NPN(IS=1E-14 VAF=100 -+ BF=200 IKF=0.3 XTB=1.5 BR=3 -+ CJC=8E-12 CJE=25E-12 TR=100E-9 TF=400E-12 -+ ITF=1 VTF=2 XTF=3 RB=10 RC=.3 RE=.2) - -XU1 Ninp Ninn Np15 Nm15 Ninplp ADA4077-2 -XU2 No Nre Np15 Nm15 Nrep ADA4077-2 -R1 Ninnm 0 {r_1} -R2 Ne Ninnm {r_2} -C2 Ne Ninnm {c_2} -VU1offset Ninn Ninnm {v_1offset} -R3 Nin Ninp {r_3} -VU2offset Nre Nrep {v_2offset} -R4 Nrep Ninp {r_4} -C4 Nrep Ninp {c_4} -R5 Ne No {r_o} -Vi Nin 0 {vin} AC {1-prb*prb} -V2p15 Np15 0 15 -Vm15 Nm15 0 -15 -Q1 Nc Nb Ne 2N2222 -R6 Np15 Nc 50 -R7 Noutlp Nb 10 -D1 Ngl No YELLOW -Vl Ngl 0 0 -.model YELLOW D(IS=93.1P RS=42M N=4.61 BV=2 IBV=10U -+ CJO=2.97P VJ=.75 M=.333 TT=4.32U) - -.model NPN NPN -.model PNP PNP -.param prb=0 -.param vin=2.5 -.param r_1=1k -.param r_2=1k -.param r_3=1k -.param r_4=1k -.param r_o=200 -.param c_2=20p -.param c_4=20p -.param v_1offset=0 -.param v_2offset=0 - -.end -""" - - -def circuit_gft(prb): - circuit_file = SpiceParser(source=prb[0]) - circuit = circuit_file.build_circuit() - circuit.parameter('prb', str(prb[1])) - simulator = circuit.simulator(simulator='xyce-serial') - simulator.save(['all']) - return simulator - -class TestSpiceParser(unittest.TestCase): - 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)') - - def test_subcircuit(self): - print(os.getcwd()) - circuit = Circuit('Diode Characteristic Curve') - circuit.include(os.path.join(os.getcwd(), '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) - result = """.title Diode Characteristic Curve - -.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) - -bhigh hoi hs v={if(v(hi, vss) > 0.5, 5, 0)} smoothbsrc=1 -rhoi hoi ho 1 -choi ho hs 1e-9 -blow loi vss v={if(v(li, vss) > 0.5, 5, 0)} smoothbsrc=1 -rloi loi lo 1 -cloi lo vss 1e-9 -dhb vdd hb diode -.ends mosdriver - -xtest 0 1 2 3 4 5 mosdriver -btest 1 0 v=if(0, 0, 1) smoothbsrc=1 -""" - self.assertEqual(str(circuit), result) - - -if __name__ == '__main__': - unittest.main() From 791f99159be5d225e01f4761c1fd74c6460cf10c Mon Sep 17 00:00:00 2001 From: jmgc Date: Sun, 30 May 2021 11:37:13 +0200 Subject: [PATCH 062/134] Added the possibility to use point as a character in model_name. --- PySpice/Spice/SpiceGrammar.py | 2 +- PySpice/Spice/spicegrammar.ebnf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PySpice/Spice/SpiceGrammar.py b/PySpice/Spice/SpiceGrammar.py index 6031f5018..f459a2f71 100644 --- a/PySpice/Spice/SpiceGrammar.py +++ b/PySpice/Spice/SpiceGrammar.py @@ -3859,7 +3859,7 @@ def _boolean_(self): # noqa @tatsumasu('ModelName') def _model_name_(self): # noqa - self._pattern('[a-zA-Z0-9_][a-zA-Z0-9_\\-\\+]*') + self._pattern('[a-zA-Z0-9_][a-zA-Z0-9_\\-\\+\\.]*') self.name_last_node('name') with self._ifnot(): with self._group(): diff --git a/PySpice/Spice/spicegrammar.ebnf b/PySpice/Spice/spicegrammar.ebnf index 3eafb0971..a8183b58f 100644 --- a/PySpice/Spice/spicegrammar.ebnf +++ b/PySpice/Spice/spicegrammar.ebnf @@ -1442,7 +1442,7 @@ boolean model_name::ModelName = - name:/[a-zA-Z0-9_][a-zA-Z0-9_\-\+]*/ !([sep:sep] '=') + name:/[a-zA-Z0-9_][a-zA-Z0-9_\-\+\.]*/ !([sep:sep] '=') ; From ac6781b22a7343dee6b587698cc22d8aa81752cf Mon Sep 17 00:00:00 2001 From: jmgc Date: Sun, 30 May 2021 16:43:58 +0200 Subject: [PATCH 063/134] Correction on the SpiceParser and ModelWalker to avoid issues when being used with different files. --- PySpice/Spice/EBNFParser.py | 794 ++++++++++++++-------------- PySpice/Spice/Library.py | 2 +- PySpice/Spice/Netlist.py | 4 +- unit-test/Spice/test_SpiceParser.py | 4 +- 4 files changed, 408 insertions(+), 396 deletions(-) diff --git a/PySpice/Spice/EBNFParser.py b/PySpice/Spice/EBNFParser.py index 7d6ae11ae..be0372974 100644 --- a/PySpice/Spice/EBNFParser.py +++ b/PySpice/Spice/EBNFParser.py @@ -127,10 +127,12 @@ def __init__(self, parent, 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(path=file_name) + self._contents = SpiceParser.parse(path=file_name, library=True) except Exception as e: - raise FileNotFoundError("{}: ".format(parent.path) + str(e)) from e + raise ParseError("{}: {:s}".format(parent.path, e)) ############################################## @@ -718,11 +720,7 @@ def build(self, ground=0): class SpiceModelWalker(NodeWalker): - def __init__(self, filename): - self._path = filename - self._root = None - self._present = None - self._context = [] + 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] + @@ -791,29 +789,29 @@ def __init__(self, filename): ">": GT } - def walk_Circuit(self, node): - if self._root is None: - self._root = CircuitStatement( - self.walk(node.title), - self._path + def walk_Circuit(self, node, data): + if data._root is None: + data._root = CircuitStatement( + self.walk(node.title, data), + data._path ) - self._present = self._root + data._present = data._root else: - raise ValueError('Circuit already created: {}'.format(self._path)) + raise ValueError('Circuit already created: {}'.format(data._path)) - self.walk(node.lines) - if len(self._context) != 0: - raise ParseError("Not closed hierarchy: {}".format(self._path)) - return self._root + 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): - device = self.walk(node.dev) - args = self.walk(node.args) + def walk_BJT(self, node, data): + device = self.walk(node.dev, data) + args = self.walk(node.args, data) l_args = len(args) kwargs = {} - collector = self.walk(node.collector) - base = self.walk(node.base) - emitter = self.walk(node.emitter) + collector = self.walk(node.collector, data) + base = self.walk(node.base, data) + emitter = self.walk(node.emitter, data) nodes = [ collector, base, @@ -825,7 +823,7 @@ def walk_BJT(self, node): nodes.append(substrate) area = None if node.area is not None: - area = self.walk(node.area) + area = self.walk(node.area, data) if l_args == 0: raise ValueError("The device {} has no model".format(node.dev)) elif l_args == 1: @@ -864,13 +862,13 @@ def walk_BJT(self, node): raise ValueError("Present device not compatible with BJT definition: {}".format(node.dev)) if node.parameters is not None: - parameters = self.walk(node.parameters) + parameters = self.walk(node.parameters, data) kwargs.update(parameters) kwargs["model"] = model_name - self._present._required_models.add(model_name.lower()) + data._present._required_models.add(model_name.lower()) - self._present.append( + data._present.append( ElementStatement( BipolarJunctionTransistor, device, @@ -879,22 +877,22 @@ def walk_BJT(self, node): ) ) - def walk_SubstrateNode(self, node): + def walk_SubstrateNode(self, node, data): return node.substrate - def walk_Capacitor(self, node): - device = self.walk(node.dev) + 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) + model_name = self.walk(node.model, data) kwargs['model'] = model_name - self._present._required_models.add(model_name.lower()) + data._present._required_models.add(model_name.lower()) value = None if node.value is not None: - value = self.walk(node.value) + value = self.walk(node.value, data) kwargs['capacitance'] = value if node.parameters is not None: - parameters = self.walk(node.parameters) + parameters = self.walk(node.parameters, data) kwargs.update(parameters) if value is None: if 'c' in kwargs: @@ -905,13 +903,13 @@ def walk_Capacitor(self, node): kwargs['capacitance'] = value - positive = self.walk(node.positive) - negative = self.walk(node.negative) + positive = self.walk(node.positive, data) + negative = self.walk(node.negative, data) nodes = ( positive, negative ) - self._present.append( + data._present.append( ElementStatement( Capacitor, device, @@ -920,25 +918,25 @@ def walk_Capacitor(self, node): ) ) - def walk_CurrentControlledCurrentSource(self, node): - device = self.walk(node.dev) + 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) + controller = self.walk(node.controller, data) kwargs = {"I": controller} else: - value = self.walk(node.gain) + value = self.walk(node.gain, data) kwargs = {"I": I(device)*value} - positive = self.walk(node.positive) - negative = self.walk(node.negative) + positive = self.walk(node.positive, data) + negative = self.walk(node.negative, data) nodes = ( positive, negative ) - self._present.append( + data._present.append( ElementStatement( BehavioralSource, device, @@ -947,25 +945,25 @@ def walk_CurrentControlledCurrentSource(self, node): ) ) - def walk_CurrentControlledVoltageSource(self, node): - device = self.walk(node.dev) + 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) + controller = self.walk(node.controller, data) kwargs = {"V": controller} else: - value = self.walk(node.transresistance) + value = self.walk(node.transresistance, data) kwargs = {"V": I(device)*value} - positive = self.walk(node.positive) - negative = self.walk(node.negative) + positive = self.walk(node.positive, data) + negative = self.walk(node.negative, data) nodes = ( positive, negative ) - self._present.append( + data._present.append( ElementStatement( BehavioralSource, device, @@ -974,25 +972,25 @@ def walk_CurrentControlledVoltageSource(self, node): ) ) - def walk_CurrentSource(self, node): - device = self.walk(node.dev) + def walk_CurrentSource(self, node, data): + device = self.walk(node.dev, data) kwargs = {} if node.dc_value is not None: - kwargs['dc_value'] = self.walk(node.dc_value) + kwargs['dc_value'] = self.walk(node.dc_value, data) if node.ac_magnitude is not None: - kwargs['ac_magnitude'] = self.walk(node.ac_magnitude) + kwargs['ac_magnitude'] = self.walk(node.ac_magnitude, data) if node.ac_phase is not None: - kwargs['ac_phase'] = self.walk(node.ac_phase) + kwargs['ac_phase'] = self.walk(node.ac_phase, data) if node.transient is not None: - kwargs['transient'] = self.walk(node.transient) + kwargs['transient'] = self.walk(node.transient, data) - positive = self.walk(node.positive) - negative = self.walk(node.negative) + positive = self.walk(node.positive, data) + negative = self.walk(node.negative, data) nodes = ( positive, negative ) - self._present.append( + data._present.append( ElementStatement( CurrentSource, device, @@ -1001,26 +999,26 @@ def walk_CurrentSource(self, node): ) ) - def walk_Diode(self, node): - device = self.walk(node.dev) + 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) + model_name = self.walk(node.model, data) kwargs['model'] = model_name - self._present._required_models.add(model_name.lower()) + data._present._required_models.add(model_name.lower()) if node.area is not None: - area = self.walk(node.area) + area = self.walk(node.area, data) kwargs['area'] = area - positive = self.walk(node.positive) - negative = self.walk(node.negative) + positive = self.walk(node.positive, data) + negative = self.walk(node.negative, data) nodes = ( positive, negative ) - self._present.append( + data._present.append( ElementStatement( Diode, device, @@ -1029,19 +1027,19 @@ def walk_Diode(self, node): ) ) - def walk_Inductor(self, node): - device = self.walk(node.dev) + 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) + model_name = self.walk(node.model, data) kwargs['model'] = model_name - self._present._required_models.add(model_name.lower()) + data._present._required_models.add(model_name.lower()) value = None if node.value is not None: - value = self.walk(node.value) + value = self.walk(node.value, data) kwargs['inductance'] = value if node.parameters is not None: - parameters = self.walk(node.parameters) + parameters = self.walk(node.parameters, data) kwargs.update(parameters) if value is None: if 'l' in kwargs: @@ -1051,13 +1049,13 @@ def walk_Inductor(self, node): value = kwargs.pop('L') kwargs['inductance'] = value - positive = self.walk(node.positive) - negative = self.walk(node.negative) + positive = self.walk(node.positive, data) + negative = self.walk(node.negative, data) nodes = ( positive, negative ) - self._present.append( + data._present.append( ElementStatement( Inductor, device, @@ -1066,31 +1064,31 @@ def walk_Inductor(self, node): ) ) - def walk_JFET(self, node): - device = self.walk(node.dev) + 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) + model_name = self.walk(node.model, data) kwargs["model"] = model_name - self._present._required_models.add(model_name.lower()) + data._present._required_models.add(model_name.lower()) if node.area is not None: - area = self.walk(node.area) + area = self.walk(node.area, data) kwargs["area"] = area if node.parameters is not None: - parameters = self.walk(node.parameters) + parameters = self.walk(node.parameters, data) kwargs.update(parameters) - drain = self.walk(node.drain) - gate = self.walk(node.gate) - source = self.walk(node.source) + drain = self.walk(node.drain, data) + gate = self.walk(node.gate, data) + source = self.walk(node.source, data) nodes = [ drain, gate, source ] - self._present.append( + data._present.append( ElementStatement( JunctionFieldEffectTransistor, device, @@ -1099,36 +1097,36 @@ def walk_JFET(self, node): ) ) - def walk_MOSFET(self, node): - device = self.walk(node.dev) + 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) + model_name = self.walk(node.model, data) kwargs["model"] = model_name - self._present._required_models.add(model_name.lower()) + 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]) + kwargs[parameter[0]] = self.walk(parameter[2][::2], data) else: - kwargs.update(self.walk(parameter)) + kwargs.update(self.walk(parameter, data)) else: - kwargs.update(self.walk(node.param)) - drain = self.walk(node.drain) - gate = self.walk(node.gate) - source = self.walk(node.source) - bulk = self.walk(node.bulk) + 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 ] - self._present.append( + data._present.append( ElementStatement( Mosfet, device, @@ -1137,24 +1135,24 @@ def walk_MOSFET(self, node): ) ) - def walk_MutualInductor(self, node): - device = self.walk(node.dev) + 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) + model_name = self.walk(node.model, data) kwargs['model'] = model_name - self._present._required_models.add(model_name.lower()) - inductors = self.walk(node.inductor) + 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) + coupling_factor = self.walk(node.value, data) kwargs["inductor1"] = inductor1 kwargs["inductor2"] = inductor2 kwargs["coupling_factor"] = coupling_factor - self._present.append( + data._present.append( ElementStatement( CoupledInductor, device, @@ -1162,25 +1160,25 @@ def walk_MutualInductor(self, node): ) ) - def walk_NonLinearDependentSource(self, node): - device = self.walk(node.dev) - positive = self.walk(node.positive) - negative = self.walk(node.negative) + 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) + expr = self.walk(node.expr, data) kwargs = {} if node.parameters is not None: - parameters = self.walk(node.parameters) + parameters = self.walk(node.parameters, data) kwargs.update(parameters) if node.magnitude == "V": kwargs["voltage_expression"] = expr else: kwargs["current_expression"] = expr - self._present.append( + data._present.append( ElementStatement( BehavioralSource, device, @@ -1189,19 +1187,19 @@ def walk_NonLinearDependentSource(self, node): ) ) - def walk_Resistor(self, node): - device = self.walk(node.dev) + 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) + model_name = self.walk(node.model, data) kwargs['model'] = model_name - self._present._required_models.add(model_name.lower()) + data._present._required_models.add(model_name.lower()) value = None if node.value is not None: - value = self.walk(node.value) + value = self.walk(node.value, data) kwargs['resistance'] = value if node.parameters is not None: - parameters = self.walk(node.parameters) + parameters = self.walk(node.parameters, data) kwargs.update(parameters) if value is None: if 'r' in kwargs: @@ -1211,13 +1209,13 @@ def walk_Resistor(self, node): value = kwargs.pop('R') kwargs['resistance'] = value - positive = self.walk(node.positive) - negative = self.walk(node.negative) + positive = self.walk(node.positive, data) + negative = self.walk(node.negative, data) nodes = ( positive, negative ) - self._present.append( + data._present.append( ElementStatement( Resistor, device, @@ -1226,9 +1224,9 @@ def walk_Resistor(self, node): ) ) - def walk_Subcircuit(self, node): - device = self.walk(node.dev) - node_node = self.walk(node.node) + 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] @@ -1237,10 +1235,10 @@ def walk_Subcircuit(self, node): nodes = node_node[:-1] kwargs = {} if node.parameters is not None: - parameters = self.walk(node.parameters) + parameters = self.walk(node.parameters, data) kwargs.update(parameters) - self._present._required_subcircuits.add(subcircuit_name.lower()) - self._present.append( + data._present._required_subcircuits.add(subcircuit_name.lower()) + data._present.append( ElementStatement( SubCircuitElement, device, @@ -1250,22 +1248,22 @@ def walk_Subcircuit(self, node): ) ) - def walk_Switch(self, node): - device = self.walk(node.dev) + 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) + model_name = self.walk(node.model, data) kwargs['model'] = model_name - self._present._required_models.add(model_name.lower()) + 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) - negative = self.walk(node.negative) + 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) - control_n = self.walk(node.control_n) + control_p = self.walk(node.control_p, data) + control_n = self.walk(node.control_n, data) nodes = ( positive, negative, @@ -1280,7 +1278,7 @@ def walk_Switch(self, node): positive, negative ) - self._present.append( + data._present.append( ElementStatement( VoltageControlledSwitch, device, @@ -1289,25 +1287,25 @@ def walk_Switch(self, node): ) ) - def walk_VoltageControlledCurrentSource(self, node): - device = self.walk(node.dev) + def walk_VoltageControlledCurrentSource(self, node, data): + device = self.walk(node.dev, data) if (node.controller is None and node.control_positive is None and node.control_negative is None and node.transconductance is None): raise ValueError("Device {} not properly defined".format(node.dev)) if node.controller is not None: - controller = self.walk(node.controller) + controller = self.walk(node.controller, data) kwargs = {"I": controller} else: - value = self.walk(node.transconductance) + value = self.walk(node.transconductance, data) kwargs = {"I": V(node.control_positive, node.control_negative) * value} - positive = self.walk(node.positive) - negative = self.walk(node.negative) + positive = self.walk(node.positive, data) + negative = self.walk(node.negative, data) nodes = ( positive, negative ) - self._present.append( + data._present.append( ElementStatement( BehavioralSource, device, @@ -1316,25 +1314,25 @@ def walk_VoltageControlledCurrentSource(self, node): ) ) - def walk_VoltageControlledVoltageSource(self, node): - device = self.walk(node.dev) + def walk_VoltageControlledVoltageSource(self, node, data): + device = self.walk(node.dev, data) if (node.controller is None and node.control_positive is None and node.control_negative 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) + controller = self.walk(node.controller, data) kwargs = {"V": controller} else: - value = self.walk(node.gain) + value = self.walk(node.gain, data) kwargs = {"V": V(node.control_positive, node.control_negative) * value} - positive = self.walk(node.positive) - negative = self.walk(node.negative) + positive = self.walk(node.positive, data) + negative = self.walk(node.negative, data) nodes = ( positive, negative ) - self._present.append( + data._present.append( ElementStatement( BehavioralSource, device, @@ -1343,25 +1341,25 @@ def walk_VoltageControlledVoltageSource(self, node): ) ) - def walk_VoltageSource(self, node): - device = self.walk(node.dev) + def walk_VoltageSource(self, node, data): + device = self.walk(node.dev, data) kwargs = {} if node.dc_value is not None: - kwargs['dc_value'] = self.walk(node.dc_value) + kwargs['dc_value'] = self.walk(node.dc_value, data) if node.ac_magnitude is not None: - kwargs['ac_magnitude'] = self.walk(node.ac_magnitude) + kwargs['ac_magnitude'] = self.walk(node.ac_magnitude, data) if node.ac_phase is not None: - kwargs['ac_phase'] = self.walk(node.ac_phase) + kwargs['ac_phase'] = self.walk(node.ac_phase, data) if node.transient is not None: - kwargs['transient'] = self.walk(node.transient) + kwargs['transient'] = self.walk(node.transient, data) - positive = self.walk(node.positive) - negative = self.walk(node.negative) + positive = self.walk(node.positive, data) + negative = self.walk(node.negative, data) nodes = ( positive, negative ) - self._present.append( + data._present.append( ElementStatement( VoltageSource, device, @@ -1371,10 +1369,10 @@ def walk_VoltageSource(self, node): ) - def walk_ControlVoltagePoly(self, node): - controllers = self.walk(node.value) - positive = self.walk(node.positive) - negative = self.walk(node.negative) + 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)) @@ -1396,7 +1394,7 @@ def walk_ControlVoltagePoly(self, node): for pair in zip(values_pos, values_neg) for val in pair] if node.coefficient: - coefficients = self.walk(node.coefficient) + coefficients = self.walk(node.coefficient, data) if isinstance(coefficients, list): values.extend(coefficients) else: @@ -1408,8 +1406,8 @@ def walk_ControlVoltagePoly(self, node): parameters = ' '.join(result) return '{ POLY (%d) %s }' % (controllers, parameters) - def walk_ControlCurrentPoly(self, node): - controllers = self.walk(node.value) + 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)) @@ -1418,29 +1416,29 @@ def walk_ControlCurrentPoly(self, node): values = [] if node.coefficient: - coefficients = self.walk(node.coefficient) + coefficients = self.walk(node.coefficient, data) if isinstance(coefficients, list): values.extend(coefficients) else: values.append(coefficients) - data = [self.walk(dev) for dev in ctrl_dev] + [str(value) for value in values] + data = [self.walk(dev, data) for dev in ctrl_dev] + [str(value) for value in values] parameters = ' '.join(data) return '{ POLY (%d) %s }' % (controllers, parameters) - def walk_ControlTable(self, node): - return Table(self.walk(node.expr), - list(zip(self.walk(node.input), - self.walk(node.output)))) + 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): - return self.walk(node.expression) + def walk_ControlValue(self, node, data): + return self.walk(node.expression, data) - def walk_TransientSpecification(self, node): - return self.walk(node.ast) + def walk_TransientSpecification(self, node, data): + return self.walk(node.ast, data) - def walk_TransientPulse(self, node): + def walk_TransientPulse(self, node, data): parameters = dict([(key, value) - for value, key in zip(self.walk(node.ast), + for value, key in zip(self.walk(node.ast, data), ("initial_value", "pulsed_value", "delay_time", @@ -1451,35 +1449,35 @@ def walk_TransientPulse(self, node): "phase"))]) return PulseMixin, parameters - def walk_PulseArguments(self, node): - v1 = self.walk(node.v1) + def walk_PulseArguments(self, node, data): + v1 = self.walk(node.v1, data) value = [] if node.value is not None: - value = self.walk(node.value) + value = self.walk(node.value, data) return [v1] + value - def walk_TransientPWL(self, node): - values = self.walk(node.ast) + def walk_TransientPWL(self, node, data): + values = self.walk(node.ast, data) return PieceWiseLinearMixin, values - def walk_PWLArguments(self, node): - t = self.walk(node.t) - value = self.walk(node.value) + def walk_PWLArguments(self, node, data): + t = self.walk(node.t, data) + value = self.walk(node.value, data) if node.parameters is not None: - parameters = self.walk(node.parameters) + parameters = self.walk(node.parameters, data) return (t, value), parameters - def walk_PWLFileArguments(self, node): - filename = self.walk(node.filename) + def walk_PWLFileArguments(self, node, data): + filename = self.walk(node.filename, data) parameters = {} if node.parameters is not None: - parameters = self.walk(node.parameters) + parameters = self.walk(node.parameters, data) return filename, parameters - def walk_TransientSin(self, node): + def walk_TransientSin(self, node, data): parameters = dict([(key, value) - for value, key in zip(self.walk(node.ast), + for value, key in zip(self.walk(node.ast, data), ("offset", "amplitude", "frequency", @@ -1487,21 +1485,21 @@ def walk_TransientSin(self, node): "damping_factor"))]) return SinusoidalMixin, parameters - def walk_SinArguments(self, node): - v0 = self.walk(node.v0) - va = self.walk(node.va) - freq = self.walk(node.freq) + 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) + 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): + def walk_TransientPat(self, node, data): parameters = dict([(key, value) - for value, key in zip(self.walk(node.ast), + for value, key in zip(self.walk(node.ast, data), ("high_value", "low_value", "delay_time", @@ -1512,61 +1510,64 @@ def walk_TransientPat(self, node): "repeat"))]) return PatternMixin, parameters - def walk_PatArguments(self, node): - vhi = self.walk(node.vhi) - vlo = self.walk(node.vlo) - td = self.walk(node.td) - tr = self.walk(node.tr) - tf = self.walk(node.tf) - tsample = self.walk(node.tsample) - data = self.walk(node.data) + 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_ACCmd(self, node): + def walk_ACCmd(self, node, data): return node.text - def walk_DataCmd(self, node): + def walk_DataCmd(self, node, data): table = node.table names = node.name - values = self.walk(node.value) + 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)]) - self._root.appendData( + data._root.appendData( DataStatement( table, **parameters ) ) - def walk_DCCmd(self, node): + def walk_DCCmd(self, node, data): return node.text - def walk_IncludeCmd(self, node): - filename = self.walk(node.filename) - self._present.append( - IncludeStatement( - self._root, - filename - ) + 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. + data._present._params.extend(include._contents._params) + data._present._models.extend(include._contents._models) + data._present._subcircuits.extend(include._contents._subcircuits) - def walk_ICCmd(self, node): + def walk_ICCmd(self, node, data): return node.text - def walk_ModelCmd(self, node): - name = self.walk(node.name) + 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) + parameters = self.walk(node.parameters, data) else: parameters = {} - self._present.appendModel( + data._present.appendModel( ModelStatement( name, device, @@ -1574,26 +1575,26 @@ def walk_ModelCmd(self, node): ) ) - def walk_ModelName(self, node): + def walk_ModelName(self, node, data): return node.name - def walk_ParamCmd(self, node): + def walk_ParamCmd(self, node, data): if node.parameters is not None: - parameters = self.walk(node.parameters) + parameters = self.walk(node.parameters, data) else: parameters = {} - self._present.appendParam( + data._present.appendParam( ParamStatement(**parameters) ) - def walk_LibCmd(self, node): + def walk_LibCmd(self, node, data): if node.block is not None: - self.walk(node.block) + self.walk(node.block, data) else: - self.walk(node.call) + self.walk(node.call, data) - def walk_LibBlock(self, node): + def walk_LibBlock(self, node, data): entries = node.entry if len(entries) == 2: if entries[0] != entries[1]: @@ -1601,40 +1602,40 @@ def walk_LibBlock(self, node): 'Begin and end library entries differ: {} != {}'.format(*entries)) entries = entries[0] library = LibraryStatement(entries) - self._context.append(self._present) - self._present = library - self.walk(node.lines) - tmp = self._context.pop() - tmp.appendLibrary(self._present) - self._present = tmp - - def walk_LibCall(self, node): + 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) - self._present.appendLibraryCall( + filename = self.walk(node.filename, data) + data._present.appendLibraryCall( LibCallStatement(filename, entries) ) - def walk_SimulatorCmd(self, node): + def walk_SimulatorCmd(self, node, data): return node.simulator - def walk_SubcktCmd(self, node): - name = self.walk(node.name) + 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) + nodes = self.walk(node.node, data) parameters = None if node.parameters is not None: - parameters = self.walk(node.parameters) + parameters = self.walk(node.parameters, data) if nodes is None: if parameters is None: @@ -1646,181 +1647,181 @@ def walk_SubcktCmd(self, node): subckt = SubCircuitStatement(name, *nodes) else: subckt = SubCircuitStatement(name, *nodes, **parameters) - self._context.append(self._present) - self._present = subckt - self.walk(node.lines) - tmp = self._context.pop() - tmp.appendSubCircuit(self._present) - self._present = tmp - - def walk_TitleCmd(self, node): - if id(self._root) == id(self._present): - self._root._title = self.walk(node.title) + 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 self._root + return data._root - def walk_Lines(self, node): - self.walk(node.ast) + def walk_Lines(self, node, data): + return self.walk(node.ast, data) - def walk_CircuitLine(self, node): - self.walk(node.ast) + def walk_CircuitLine(self, node, data): + return self.walk(node.ast, data) - def walk_NetlistLines(self, node): - self.walk(node.ast) + def walk_NetlistLines(self, node, data): + return self.walk(node.ast, data) - def walk_NetlistLine(self, node): - self.walk(node.ast) + def walk_NetlistLine(self, node, data): + return self.walk(node.ast, data) - def walk_Parameters(self, node): + def walk_Parameters(self, node, data): if isinstance(node.ast, list): result = {} # The separators are not taken into account - for parameter in self.walk(node.ast[::2]): + for parameter in self.walk(node.ast[::2], data): result.update(parameter) else: - result = self.walk(node.ast) + result = self.walk(node.ast, data) return result - def walk_Parameter(self, node): - value = self.walk(node.value) + def walk_Parameter(self, node, data): + value = self.walk(node.value, data) return {node.name: value} - def walk_GenericExpression(self, node): + def walk_GenericExpression(self, node, data): if node.value is None: - return self.walk(node.braced) + return self.walk(node.braced, data) else: - return self.walk(node.value) + return self.walk(node.value, data) - def walk_BracedExpression(self, node): - return self.walk(node.ast) + def walk_BracedExpression(self, node, data): + return self.walk(node.ast, data) - def walk_Ternary(self, node): - t = self.walk(node.t) - x = self.walk(node.x) - y = self.walk(node.y) + 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): - return self.walk(node.expr) + def walk_Conditional(self, node, data): + return self.walk(node.expr, data) - def walk_And(self, node): - left = self.walk(node.left) + def walk_And(self, node, data): + left = self.walk(node.left, data) if node.right is None: return left else: right = node.right return And(left, right) - def walk_Not(self, node): - operator = self.walk(node.operator) + 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): - left = self.walk(node.left) + def walk_Or(self, node, data): + left = self.walk(node.left, data) if node.right is None: return left else: right = node.right return Or(left, right) - def walk_Xor(self, node): - left = self.walk(node.left) + def walk_Xor(self, node, data): + left = self.walk(node.left, data) if node.right is None: return left else: right = node.right return Xor(left, right) - def walk_Relational(self, node): + def walk_Relational(self, node, data): if node.factor is None: - left = self.walk(node.left) - right = self.walk(node.right) + 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) + return self.walk(node.factor, data) - def walk_ConditionalFactor(self, node): + def walk_ConditionalFactor(self, node, data): if node.boolean is None: - self.walk(node.expr) + self.walk(node.expr, data) else: return node.boolean.lower == "true" - def walk_Expression(self, node): + def walk_Expression(self, node, data): if node.term is None: - return self.walk(node.ternary) + return self.walk(node.ternary, data) else: - return self.walk(node.term) + return self.walk(node.term, data) - def walk_Functional(self, node): - return self.walk(node.ast) + def walk_Functional(self, node, data): + return self.walk(node.ast, data) - def walk_Functions(self, node): + 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) + 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) + device = self.walk(node.device, data) return function(device) elif function.nargs == 1: - x = self.walk(node.x) + x = self.walk(node.x, data) return function(x) elif l_func == 'limit': - x = self.walk(node.x) - y = self.walk(node.y) - z = self.walk(node.z) + 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) - y = self.walk(node.y) + 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) - alpha = self.walk(node.alpha) + 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) + x = self.walk(node.x, data) return function(Symbol(f), x) elif function.nargs == 2: - x = self.walk(node.x) - y = self.walk(node.y) + 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) - x = self.walk(node.x) - y = self.walk(node.y) + 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) - y = self.walk(node.y) - z = self.walk(node.z) + 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) - alpha = self.walk(node.alpha) - n = self.walk(node.n) + 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): - return self.walk(node.ast) + def walk_Term(self, node, data): + return self.walk(node.ast, data) - def walk_AddSub(self, node): - lhs = self.walk(node.left) + def walk_AddSub(self, node, data): + lhs = self.walk(node.left, data) if node.right is not None: - rhs = self.walk(node.right) + rhs = self.walk(node.right, data) if node.op == "+": return Add(lhs, rhs) else: @@ -1828,10 +1829,10 @@ def walk_AddSub(self, node): else: return lhs - def walk_ProdDivMod(self, node): - lhs = self.walk(node.left) + def walk_ProdDivMod(self, node, data): + lhs = self.walk(node.left, data) if node.right is not None: - rhs = self.walk(node.right) + rhs = self.walk(node.right, data) if node.op == "*": return Mul(lhs, rhs) elif node.op == "/": @@ -1841,8 +1842,8 @@ def walk_ProdDivMod(self, node): else: return lhs - def walk_Sign(self, node): - operator = self.walk(node.operator) + def walk_Sign(self, node, data): + operator = self.walk(node.operator, data) if node.op is not None: if node.op == "-": return Neg(operator) @@ -1851,43 +1852,43 @@ def walk_Sign(self, node): else: return operator - def walk_Exponential(self, node): - lhs = self.walk(node.left) + def walk_Exponential(self, node, data): + lhs = self.walk(node.left, data) if node.right is not None: - rhs = self.walk(node.right) + rhs = self.walk(node.right, data) return Power(lhs, rhs) else: return lhs - def walk_Factor(self, node): - return self.walk(node.ast) + def walk_Factor(self, node, data): + return self.walk(node.ast, data) - def walk_Variable(self, node): + def walk_Variable(self, node, data): if node.variable is None: - return self.walk(node.factor) + return self.walk(node.factor, data) else: return Symbol(node.variable) - def walk_Value(self, node): + def walk_Value(self, node, data): real = 0.0 if node.real is not None: - real = self.walk(node.real) + real = self.walk(node.real, data) imag = None if node.imag is not None: - imag = self.walk(node.imag) + imag = self.walk(node.imag, data) if imag is None: return SpiceModelWalker._to_number(real) else: return complex(float(real), float(imag)) - def walk_ImagValue(self, node): - return self.walk(node.value) + def walk_ImagValue(self, node, data): + return self.walk(node.value, data) - def walk_RealValue(self, node): - return self.walk(node.value) + def walk_RealValue(self, node, data): + return self.walk(node.value, data) - def walk_NumberScale(self, node): - value = self.walk(node.value) + def walk_NumberScale(self, node, data): + value = self.walk(node.value, data) scale = node.scale if scale is not None: scale = normalize("NFKD", scale).lower() @@ -1896,55 +1897,55 @@ def walk_NumberScale(self, node): result = UnitValue(PrefixedUnit(ZeroPower()), value) return result - def walk_Float(self, node): + def walk_Float(self, node, data): value = SpiceModelWalker._to_number(node.ast) return value - def walk_Int(self, node): + def walk_Int(self, node, data): value = int(node.ast) return value - def walk_Comment(self, node): + def walk_Comment(self, node, data): # TODO implement comments on devices return - def walk_Separator(self, node): + def walk_Separator(self, node, data): if node.comment is not None: - return self.walk(node.comment) + return self.walk(node.comment, data) - def walk_Device(self, node): + 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): - return self.walk(node.ast) + def walk_Command(self, node, data): + return self.walk(node.ast, data) - def walk_NetlistCmds(self, node): - return self.walk(node.ast) + def walk_NetlistCmds(self, node, data): + return self.walk(node.ast, data) - def walk_TableFile(self, node): - filename = self.walk(node.filename) + def walk_TableFile(self, node, data): + filename = self.walk(node.filename, data) return TableFile(filename) - def walk_NetNode(self, node): + def walk_NetNode(self, node, data): return node.node - def walk_Filename(self, node): + def walk_Filename(self, node, data): return node.ast - def walk_BinaryPattern(self, node): + def walk_BinaryPattern(self, node, data): return ''.join(node.pattern) - def walk_list(self, node): - return [self.walk(element) for element in node] - - def walk_closure(self, node): + def walk_closure(self, node, data): return ''.join(node) - def walk_object(self, 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 @@ -1960,6 +1961,15 @@ def _to_number(value): except ValueError: return float(value) + +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. @@ -1977,13 +1987,15 @@ class SpiceParser: ############################################## - def __init__(self, path=None, source=None, library=False): - # Fixme: empty source + _parser = parser(whitespace='', semantics=SpiceModelBuilderSemantics()) + _walker = SpiceModelWalker() - self._path = path - self._root = None - self._present = None - self._context = [] + def __init__(self): + pass + + @staticmethod + def parse(path=None, source=None, library=False): + # Fixme: empty source if path is not None: with open(str(path), 'rb') as f: @@ -1993,10 +2005,8 @@ def __init__(self, path=None, source=None, library=False): else: raise ValueError("No path or source") - self._parser = parser(whitespace='', semantics=SpiceModelBuilderSemantics()) - try: - self._model = self._parser.parse(raw_code) + model = SpiceParser._parser.parse(raw_code) except Exception as e: if path is not None: raise ParseError("{}: ".format(path) + str(e)) from e @@ -2004,19 +2014,21 @@ def __init__(self, path=None, source=None, library=False): raise ParseError(str(e)) from e if path is None: - self._path = os.getcwd() - self._walker = SpiceModelWalker(self._path) - self._circuit = self._walker.walk(self._model) + path = os.getcwd() + data = ParsingData(path) + circuit = SpiceParser._walker.walk(model, data) if library: - self._circuit._required_models = {model.name.lower() - for model in self._circuit._models} - self._circuit._required_subcircuits = {subckt.name.lower() - for subckt in self._circuit._subcircuits} + circuit._required_models = {model.name.lower() + for model in circuit._models} + circuit._required_subcircuits = {subckt.name.lower() + for subckt in circuit._subcircuits} try: - SpiceParser._check_models(self._circuit) - SpiceParser._sort_subcircuits(self._circuit) + SpiceParser._check_models(circuit) + SpiceParser._sort_subcircuits(circuit) except Exception as e: - raise ParseError("{}: ".format(self._path) + str(e)) from e + raise ParseError("{}: ".format(path) + str(e)) from e + + return circuit @staticmethod def _regenerate(): diff --git a/PySpice/Spice/Library.py b/PySpice/Spice/Library.py index ec4255e33..b4a884814 100644 --- a/PySpice/Spice/Library.py +++ b/PySpice/Spice/Library.py @@ -73,7 +73,7 @@ def __init__(self, root_path): extension = path.extension.lower() if extension in self.EXTENSIONS: self._logger.debug("Parse {}".format(path)) - spice_parser = SpiceParser(path=path, library=True) + spice_parser = SpiceParser.parse(path=path, library=True) for model in spice_parser.models: name = self._suffix_name(model.name, extension) self._models[name.lower()] = path diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index 0a4970926..e6e103d86 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -1128,7 +1128,7 @@ def include(self, path, entry=None): if path not in self._includes: self._includes.append(path) - library = SpiceParser(path=path) + library = SpiceParser.parse(path=path) if entry is not None: library = library[entry] models = library.models @@ -1140,7 +1140,7 @@ def include(self, path, entry=None): subcircuit_def = subcircuit.build(parent=self) self.subcircuit(subcircuit_def) self._subcircuits[subcircuit._name.lower()]._included = path - parameters = library.parameters + parameters = library.params for param in parameters: self.param(param) else: diff --git a/unit-test/Spice/test_SpiceParser.py b/unit-test/Spice/test_SpiceParser.py index 7368279af..f3af4adaa 100644 --- a/unit-test/Spice/test_SpiceParser.py +++ b/unit-test/Spice/test_SpiceParser.py @@ -357,8 +357,8 @@ """ def circuit_gft(prb): - parser = SpiceParser(source=prb[0]) - circuit = parser.build_circuit() + parser = SpiceParser.parse(source=prb[0]) + circuit = parser.build() circuit.parameter('prb', str(prb[1])) simulator = circuit.simulator(simulator='xyce-serial') simulator.save(['all']) From b98323a8bbe5331dc20ae81980037a1540118824 Mon Sep 17 00:00:00 2001 From: jmgc Date: Sun, 30 May 2021 19:56:35 +0200 Subject: [PATCH 064/134] Correction on the management of the model names and the tc parameter. --- PySpice/Spice/BasicElement.py | 25 +++++++++++++++++++------ PySpice/Spice/Netlist.py | 2 +- unit-test/Spice/test_SpiceParser.py | 2 ++ 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/PySpice/Spice/BasicElement.py b/PySpice/Spice/BasicElement.py index 7cb41101f..c7d6a93c8 100644 --- a/PySpice/Spice/BasicElement.py +++ b/PySpice/Spice/BasicElement.py @@ -374,6 +374,7 @@ class BehavioralResistor(DipoleElement): _prefix_ = 'R' resistance_expression = ExpressionPositionalParameter(position=0, key_parameter=False) + tc = FloatPairKeyParameter('tc') tc1 = FloatKeyParameter('tc1') tc2 = FloatKeyParameter('tc2') @@ -435,6 +436,9 @@ class Capacitor(DipoleElement): temperature = FloatKeyParameter('temp', unit=U_Degree) device_temperature = FloatKeyParameter('dtemp', unit=U_Degree) initial_condition = FloatKeyParameter('ic') + tc = FloatPairKeyParameter('tc') + tc1 = FloatPairKeyParameter('tc1') + tc2 = FloatKeyParameter('tc1') #################################################################################################### @@ -540,6 +544,7 @@ class BehavioralCapacitor(DipoleElement): _prefix_ = 'C' capacitance_expression = ExpressionPositionalParameter(position=0, key_parameter=False) + tc = FloatPairKeyParameter('tc') tc1 = FloatKeyParameter('tc1') tc2 = FloatKeyParameter('tc2') @@ -604,6 +609,9 @@ class Inductor(DipoleElement): temperature = FloatKeyParameter('temp', unit=U_Degree) device_temperature = FloatKeyParameter('dtemp', unit=U_Degree) initial_condition = FloatKeyParameter('ic') + tc = FloatPairKeyParameter('tc') + tc1 = FloatPairKeyParameter('tc1') + tc2 = FloatKeyParameter('tc1') #################################################################################################### @@ -638,8 +646,9 @@ class BehavioralInductor(DipoleElement): _prefix_ = 'L' inductance_expression = ExpressionPositionalParameter(position=0, key_parameter=False) - tc1 = FloatKeyParameter('tc1') - tc2 = FloatKeyParameter('tc2') + tc = FloatPairKeyParameter('tc') + tc1 = FloatPairKeyParameter('tc1') + tc2 = FloatKeyParameter('tc1') #################################################################################################### @@ -997,6 +1006,7 @@ 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) @@ -1022,10 +1032,13 @@ def __str__(self): else: expression = '' spice_element += expression - 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.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: diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index e6e103d86..7935b6ea0 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -1134,7 +1134,7 @@ def include(self, path, entry=None): models = library.models for model in models: self.model(model._name, model._model_type, **model._parameters) - self._models[model._name]._included = path + self._models[model._name.lower()]._included = path subcircuits = library.subcircuits for subcircuit in subcircuits: subcircuit_def = subcircuit.build(parent=self) diff --git a/unit-test/Spice/test_SpiceParser.py b/unit-test/Spice/test_SpiceParser.py index f3af4adaa..4209fcd59 100644 --- a/unit-test/Spice/test_SpiceParser.py +++ b/unit-test/Spice/test_SpiceParser.py @@ -390,6 +390,8 @@ def test_library(self): 'lo', 'vdd', 'vss') + circuit.R('test_temp', 1, 2, tc=(4, 5)) + circuit.B('test_tc', 1, 2, v={5}, tc=(7, 8)) simulator = circuit.simulator(simulator='xyce-serial', temperature=25, nominal_temperature=25) From 421127aa2e012bb7cde995b92a60de944fa1cfde Mon Sep 17 00:00:00 2001 From: jmgc Date: Sun, 30 May 2021 21:20:34 +0200 Subject: [PATCH 065/134] Lambda expressions converted to static functions to allow their use with pickle. --- PySpice/Spice/Expressions.py | 130 ++++++++++++++++++++-------- unit-test/Spice/test_Expressions.py | 17 ++-- 2 files changed, 106 insertions(+), 41 deletions(-) diff --git a/PySpice/Spice/Expressions.py b/PySpice/Spice/Expressions.py index 6f72d6fa7..ec982a5ed 100644 --- a/PySpice/Spice/Expressions.py +++ b/PySpice/Spice/Expressions.py @@ -1,3 +1,5 @@ +import operator + import numpy as np import operator as op @@ -103,13 +105,20 @@ def __str__(self): return "({:s} {:s} {:s})".format(str(self._lhs), str(self._string), str(self._rhs)) def __call__(self, **kwargs): - lhs = self._lhs.subs(**kwargs) - rhs = self._rhs.subs(**kwargs) - if isinstance(lhs, Expression) or isinstance(rhs, Expression): - return self.__class__(lhs, rhs) + 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: - return self._op(lhs, rhs) - + try: + return self._op(lhs, rhs) + except: + return self.__class__(lhs, rhs) class UnaryOperator(Expression): def __init__(self, op, string, operator): @@ -121,11 +130,18 @@ def __str__(self): return "({:s}({:s}))".format(str(self._string), str(self._operator)) def __call__(self, **kwargs): - operator = self._operator.subs(**kwargs) - if isinstance(operator, Expression): - return self.__class__(operator) + operator = self._operator + if kwargs: + operator.subs(**kwargs) + if isinstance(operator, Expression): + return self.__class__(operator) + else: + return self._op(operator) else: - return self._op(operator) + try: + return self._op(operator) + except: + return self.__class__(operator) class Add(BinaryOperator): @@ -199,23 +215,27 @@ def __init__(self, lhs, rhs): class Not(UnaryOperator): - def __init__(self, operator): - super(Not, self).__init__(lambda operator: not operator, "not", operator) + def __init__(self, value): + super(Not, self).__init__(operator.not_, "not", value) class And(BinaryOperator): def __init__(self, lhs, rhs): - super(And, self).__init__(lambda lhs, rhs: lhs and rhs, "and", lhs, rhs) + super(And, self).__init__(operator.and_, "and", lhs, rhs) class Or(BinaryOperator): def __init__(self, lhs, rhs): - super(Or, self).__init__(lambda lhs, rhs: lhs or rhs, "or", lhs, rhs) + super(Or, self).__init__(operator.or_, "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__(lambda lhs, rhs: lhs != rhs, "xor", lhs, rhs) + super(Xor, self).__init__(Xor._xor, "xor", lhs, rhs) class Abs(Function): @@ -242,10 +262,14 @@ def __init__(self, *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__(lambda mu, alpha, n: np.normal(mu, - alpha / n, - 1), *symbol) + super(AGauss, self).__init__(AGauss._agauss, *symbol) class ASin(Function): @@ -286,10 +310,14 @@ def __init__(self, *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__(lambda mu, alpha: np.uniform(mu - alpha, - mu + alpha, - 1), *symbol) + super(AUnif, self).__init__(AUnif._aunif, *symbol) class Ceil(Function): @@ -351,18 +379,26 @@ def __init__(self, *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__(lambda mu, alpha, n: np.normal(mu, - alpha * mu / n, - 1), *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__(lambda t, x, y: x if t else y, *symbol) + super(If, self).__init__(If._if, *symbol) class Img(Function): @@ -382,8 +418,12 @@ def __init__(self, *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__(lambda x, y, z: y if x < y else z if x > z else x, *symbol) + super(Limit, self).__init__(Limit._limit, *symbol) class Ln(Function): @@ -439,28 +479,36 @@ class Pow(Function): nargs = 2 def __init__(self, *symbol): - super(Pow, self).__init__(lambda x, y: np.power(x, y), *symbol) + super(Pow, self).__init__(np.power, *symbol) class Pwr(Function): nargs = 2 def __init__(self, *symbol): - super(Pwr, self).__init__(lambda x, y: np.power(x, y), *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__(lambda x, y: np.copysign(np.power(np.abs(x), y), x), *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__(np.random.rand(1), *symbol) + super(Rand, self).__init__(Rand._rand, *symbol) class Re(Function): @@ -515,8 +563,12 @@ def __init__(self, *symbol): class Stp(Function): nargs = 1 + @staticmethod + def _stp(x): + return x * (x > 0) + def __init__(self, *symbol): - super(Stp, self).__init__(lambda x: x * (x > 0), *symbol) + super(Stp, self).__init__(Stp._stp, *symbol) class Tan(Function): @@ -536,17 +588,25 @@ def __init__(self, *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__(lambda mu, alpha: np.uniform(mu * (1. - alpha), - mu * (1. + alpha), - 1), *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__(lambda x: x * (x > 0), *symbol) + super(URamp, self).__init__(URamp._uramp, *symbol) class Symbol(Expression): diff --git a/unit-test/Spice/test_Expressions.py b/unit-test/Spice/test_Expressions.py index c12cb5e52..9338958b5 100644 --- a/unit-test/Spice/test_Expressions.py +++ b/unit-test/Spice/test_Expressions.py @@ -25,25 +25,30 @@ #################################################################################################### from PySpice.Spice.Expressions import * +import math as m #################################################################################################### -class TestExpression: +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} - print(cos_V_3(**values)) + self.assertEqual(m.cos(25), cos_V_3(**values)) y = Symbol('y') add = Add(x, y) - print(add) + self.assertEqual("(x + y)", add) V_5 = V("5") - print(V_5) - print(Cos(27)) - print(Cos(27)()) + self.assertEqual("v(5)",V_5) + self.assertEqual("cos(27)", Cos(27)) + self.assertEqual(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)()) if __name__ == '__main__': From f6d1d9fd95c5ef524bb0e2c105745f3d6ef1c67f Mon Sep 17 00:00:00 2001 From: jmgc Date: Mon, 31 May 2021 15:08:46 +0200 Subject: [PATCH 066/134] Added the curly braces to an expression. --- PySpice/Spice/Expressions.py | 18 ++++++++++++++++++ PySpice/Tools/StringTools.py | 4 +++- unit-test/Spice/test_Expressions.py | 15 +++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/PySpice/Spice/Expressions.py b/PySpice/Spice/Expressions.py index ec982a5ed..3c36a2ef8 100644 --- a/PySpice/Spice/Expressions.py +++ b/PySpice/Spice/Expressions.py @@ -17,21 +17,39 @@ def __abs__(self, 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) diff --git a/PySpice/Tools/StringTools.py b/PySpice/Tools/StringTools.py index e4569b6a6..a6af4d144 100644 --- a/PySpice/Tools/StringTools.py +++ b/PySpice/Tools/StringTools.py @@ -23,7 +23,7 @@ #################################################################################################### from PySpice.Unit.Unit import UnitValue - +from PySpice.Spice.Expressions import Expression #################################################################################################### def str_spice(obj, unit=True): @@ -37,6 +37,8 @@ def str_spice(obj, unit=True): return obj.str_spice() else: # Fixme: ok ??? return obj.str(spice=False, space=False, unit=False) + elif isinstance(obj, Expression): + return "{{{}}}".format(obj) else: return str(obj).lower() diff --git a/unit-test/Spice/test_Expressions.py b/unit-test/Spice/test_Expressions.py index 9338958b5..65727078f 100644 --- a/unit-test/Spice/test_Expressions.py +++ b/unit-test/Spice/test_Expressions.py @@ -24,6 +24,7 @@ #################################################################################################### +from PySpice.Spice.Netlist import Circuit from PySpice.Spice.Expressions import * import math as m @@ -40,7 +41,13 @@ def test_symbol(self): self.assertEqual(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)) @@ -50,6 +57,14 @@ def test_symbol(self): 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__': From 96889e9db8616f8a8f6315635486781aaa0c13a4 Mon Sep 17 00:00:00 2001 From: jmgc Date: Mon, 31 May 2021 21:02:35 +0200 Subject: [PATCH 067/134] Correction on the management of included models and subcircuits. --- PySpice/Spice/Netlist.py | 6 ++++++ unit-test/Spice/diode.lib | 3 +++ unit-test/Spice/mosdriver.lib | 10 ++++++---- unit-test/Spice/source.lib | 5 +++++ unit-test/Spice/test_SpiceParser.py | 12 +++++++++--- 5 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 unit-test/Spice/diode.lib create mode 100644 unit-test/Spice/source.lib diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index 7935b6ea0..a447573f8 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -1062,6 +1062,12 @@ def subcircuit(self, subcircuit): self._subcircuits[str(subcircuit.name).lower()] = subcircuit subcircuit.parent=self + for model in subcircuit._used_models: + if model not in subcircuit._models: + self._used_models.add(model) + for subckt in subcircuit._used_subcircuits: + if subckt not in subcircuit._subcircuits: + self._used_subcircuits.add(subckt) ############################################## diff --git a/unit-test/Spice/diode.lib b/unit-test/Spice/diode.lib new file mode 100644 index 000000000..44caa818e --- /dev/null +++ b/unit-test/Spice/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/Spice/mosdriver.lib b/unit-test/Spice/mosdriver.lib index f0bb9f0ec..e8bba2042 100644 --- a/unit-test/Spice/mosdriver.lib +++ b/unit-test/Spice/mosdriver.lib @@ -1,12 +1,14 @@ * 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 diff --git a/unit-test/Spice/source.lib b/unit-test/Spice/source.lib new file mode 100644 index 000000000..bb5224192 --- /dev/null +++ b/unit-test/Spice/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/Spice/test_SpiceParser.py b/unit-test/Spice/test_SpiceParser.py index 4209fcd59..0affbca85 100644 --- a/unit-test/Spice/test_SpiceParser.py +++ b/unit-test/Spice/test_SpiceParser.py @@ -406,18 +406,24 @@ def test_subcircuit(self): circuit.BehavioralSource('test', '1', '0', voltage_expression='if(0, 0, 1)', smoothbsrc=1) expected = """.title MOS Driver -.subckt mosdriver hb hi ho hs li lo vdd vss .model diode D (is=1.038e-15 n=1 tt=2e-08 cjo=5e-12 rs=0.5 bv=130) -bhigh hoi hs v={if((v(hi, vss) > 0.5), 5, 0)} smoothbsrc=1 +.subckt mosdriver hb hi ho hs li lo vdd vss + + +xhigh hoi hs hi vss source rhoi hoi ho 1 choi ho hs 1e-09 -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-09 dhb vdd hb diode .ends mosdriver +.subckt source vh vl hi lo +bhigh vh vl v={if((v(hi, lo) > 0.5), 5, 0)} smoothbsrc=1 +.ends source + xtest 0 1 2 3 4 5 mosdriver btest 1 0 v=if(0, 0, 1) smoothbsrc=1 """ From 9646b5d56b9dc2d862a3ede3deb463d7b90a0086 Mon Sep 17 00:00:00 2001 From: jmgc Date: Mon, 31 May 2021 21:35:07 +0200 Subject: [PATCH 068/134] Correction on voltage controlled source nodes detection. --- PySpice/Spice/EBNFParser.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/PySpice/Spice/EBNFParser.py b/PySpice/Spice/EBNFParser.py index be0372974..548284dd3 100644 --- a/PySpice/Spice/EBNFParser.py +++ b/PySpice/Spice/EBNFParser.py @@ -1297,7 +1297,9 @@ def walk_VoltageControlledCurrentSource(self, node, data): kwargs = {"I": controller} else: value = self.walk(node.transconductance, data) - kwargs = {"I": V(node.control_positive, node.control_negative) * value} + ctrl_p = self.walk(node.control_positive, data) + ctrl_n = self.walk(node.control_negative, data) + kwargs = {"I": V(ctrl_p, ctrl_n) * value} positive = self.walk(node.positive, data) negative = self.walk(node.negative, data) From 40f740db5ab1cd695370870a5b9498b29f4418b6 Mon Sep 17 00:00:00 2001 From: jmgc Date: Mon, 31 May 2021 21:35:51 +0200 Subject: [PATCH 069/134] Added a sort of the models and subcircuits to make the output more consistent. --- PySpice/Spice/Netlist.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index a447573f8..f2e01a5f4 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -1101,7 +1101,7 @@ def _str_elements(self): def _str_models(self): if self._used_models: models = [self._models[model] - for model in self._used_models + for model in sorted(self._used_models) if model in self._models] return join_lines(models) + os.linesep else: @@ -1112,7 +1112,7 @@ def _str_models(self): def _str_subcircuits(self): if self._used_subcircuits: subcircuits = [self._subcircuits[subcircuit] - for subcircuit in self._used_subcircuits + for subcircuit in sorted(self._used_subcircuits) if subcircuit in self._subcircuits] return join_lines(subcircuits) else: From 4c6dd39486c28b3ed9b4ad944e2287655f889e45 Mon Sep 17 00:00:00 2001 From: jmgc Date: Mon, 31 May 2021 21:54:35 +0200 Subject: [PATCH 070/134] Correction on the nodes of the e sources. --- PySpice/Spice/EBNFParser.py | 4 +++- unit-test/Spice/test_SpiceParser.py | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/PySpice/Spice/EBNFParser.py b/PySpice/Spice/EBNFParser.py index 548284dd3..5b9678a36 100644 --- a/PySpice/Spice/EBNFParser.py +++ b/PySpice/Spice/EBNFParser.py @@ -1326,7 +1326,9 @@ def walk_VoltageControlledVoltageSource(self, node, data): kwargs = {"V": controller} else: value = self.walk(node.gain, data) - kwargs = {"V": V(node.control_positive, node.control_negative) * value} + ctrl_p = self.walk(node.control_positive, data) + ctrl_n = self.walk(node.control_negative, data) + kwargs = {"V": V(ctrl_p, ctrl_n) * value} positive = self.walk(node.positive, data) negative = self.walk(node.negative, data) diff --git a/unit-test/Spice/test_SpiceParser.py b/unit-test/Spice/test_SpiceParser.py index 0affbca85..8c6333edf 100644 --- a/unit-test/Spice/test_SpiceParser.py +++ b/unit-test/Spice/test_SpiceParser.py @@ -8,6 +8,8 @@ data = """* Data test *More notes +E1 3 0 5 0 10 + G1 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 From d8984c98b867e2e4122db58028564651000a363a Mon Sep 17 00:00:00 2001 From: jmgc Date: Tue, 1 Jun 2021 11:24:43 +0200 Subject: [PATCH 071/134] Updates and corrections for the voltage and current transient sources. --- PySpice/Spice/EBNFParser.py | 162 +++++++++++++++--- PySpice/Spice/Expressions.py | 11 +- PySpice/Spice/HighLevelElement.py | 101 ++++++----- PySpice/Spice/Parser.py | 272 ++++++++++++++++++++---------- PySpice/Spice/SpiceGrammar.py | 29 +++- PySpice/Spice/spicegrammar.ebnf | 15 +- 6 files changed, 425 insertions(+), 165 deletions(-) diff --git a/PySpice/Spice/EBNFParser.py b/PySpice/Spice/EBNFParser.py index 5b9678a36..e4e97b1fd 100644 --- a/PySpice/Spice/EBNFParser.py +++ b/PySpice/Spice/EBNFParser.py @@ -1,5 +1,6 @@ import logging import os +import csv from unicodedata import normalize from PySpice.Unit.Unit import UnitValue, ZeroPower, PrefixedUnit @@ -24,13 +25,25 @@ SubCircuitElement, VoltageControlledSwitch, VoltageSource) -from .HighLevelElement import (PulseMixin, - SinusoidalMixin, - SingleFrequencyFMMixin, +from .HighLevelElement import (ExponentialCurrentSource, ExponentialMixin, - AmplitudeModulatedMixin, + ExponentialVoltageSource, + PatternCurrentSource, PatternMixin, - PieceWiseLinearMixin) + 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 @@ -975,6 +988,7 @@ def walk_CurrentControlledVoltageSource(self, node, data): 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: @@ -982,7 +996,22 @@ def walk_CurrentSource(self, node, data): if node.ac_phase is not None: kwargs['ac_phase'] = self.walk(node.ac_phase, data) if node.transient is not None: - kwargs['transient'] = self.walk(node.transient, data) + 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) @@ -992,7 +1021,7 @@ def walk_CurrentSource(self, node, data): ) data._present.append( ElementStatement( - CurrentSource, + element, device, *nodes, **kwargs @@ -1348,6 +1377,7 @@ def walk_VoltageControlledVoltageSource(self, node, data): 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: @@ -1355,7 +1385,22 @@ def walk_VoltageSource(self, node, data): if node.ac_phase is not None: kwargs['ac_phase'] = self.walk(node.ac_phase, data) if node.transient is not None: - kwargs['transient'] = self.walk(node.transient, data) + 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) @@ -1365,7 +1410,7 @@ def walk_VoltageSource(self, node, data): ) data._present.append( ElementStatement( - VoltageSource, + element, device, *nodes, **kwargs @@ -1444,7 +1489,7 @@ def walk_TransientPulse(self, node, data): parameters = dict([(key, value) for value, key in zip(self.walk(node.ast, data), ("initial_value", - "pulsed_value", + "pulse_value", "delay_time", "rise_time", "fall_time", @@ -1458,19 +1503,44 @@ def walk_PulseArguments(self, node, data): value = [] if node.value is not None: value = self.walk(node.value, data) - return [v1] + value + if isinstance(value, list): + return [v1] + value + else: + return [v1, value] def walk_TransientPWL(self, node, data): - values = self.walk(node.ast, data) - - return PieceWiseLinearMixin, values + 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: + with open(data) as ifile: + ext = os.path.splitext(data)[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 (t, value), parameters + return list(zip(t, value)), parameters def walk_PWLFileArguments(self, node, data): filename = self.walk(node.filename, data) @@ -1527,6 +1597,47 @@ def walk_PatArguments(self, node, data): 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 @@ -1714,7 +1825,7 @@ def walk_And(self, node, data): if node.right is None: return left else: - right = node.right + right = self.walk(node.right, data) return And(left, right) def walk_Not(self, node, data): @@ -1729,7 +1840,7 @@ def walk_Or(self, node, data): if node.right is None: return left else: - right = node.right + right = self.walk(node.right, data) return Or(left, right) def walk_Xor(self, node, data): @@ -1737,7 +1848,7 @@ def walk_Xor(self, node, data): if node.right is None: return left else: - right = node.right + right = self.walk(node.right, data) return Xor(left, right) def walk_Relational(self, node, data): @@ -1899,7 +2010,7 @@ def walk_NumberScale(self, node, data): result = UnitValue(self._suffix[scale], value) else: result = UnitValue(PrefixedUnit(ZeroPower()), value) - return result + return SpiceModelWalker._to_number(result) def walk_Float(self, node, data): value = SpiceModelWalker._to_number(node.ast) @@ -1954,14 +2065,13 @@ def walk_object(self, node, data): @staticmethod def _to_number(value): - if isinstance(value, UnitValue): - newValue = int(value) - if value == newValue: - return value - else: - return float(value) try: - return int(value) + 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) diff --git a/PySpice/Spice/Expressions.py b/PySpice/Spice/Expressions.py index 3c36a2ef8..95dc9b9d6 100644 --- a/PySpice/Spice/Expressions.py +++ b/PySpice/Spice/Expressions.py @@ -74,9 +74,6 @@ def __ge__(self, other): def __gt__(self, other): return GT(self, other) - def __not__(self): - return Not(self) - class Function(Expression): nargs = 0 @@ -234,17 +231,17 @@ def __init__(self, lhs, rhs): class Not(UnaryOperator): def __init__(self, value): - super(Not, self).__init__(operator.not_, "not", value) + super(Not, self).__init__(operator.not_, "~", value) class And(BinaryOperator): def __init__(self, lhs, rhs): - super(And, self).__init__(operator.and_, "and", lhs, rhs) + super(And, self).__init__(operator.and_, "&", lhs, rhs) class Or(BinaryOperator): def __init__(self, lhs, rhs): - super(Or, self).__init__(operator.or_, "or", lhs, rhs) + super(Or, self).__init__(operator.or_, "|", lhs, rhs) class Xor(BinaryOperator): @@ -253,7 +250,7 @@ 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, "xor", lhs, rhs) + super(Xor, self).__init__(Xor._xor, "^", lhs, rhs) class Abs(Function): diff --git a/PySpice/Spice/HighLevelElement.py b/PySpice/Spice/HighLevelElement.py index 0500eb0f5..0ee8ace71 100644 --- a/PySpice/Spice/HighLevelElement.py +++ b/PySpice/Spice/HighLevelElement.py @@ -145,7 +145,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 | +--------+---------------+---------------+-------+ @@ -201,7 +201,7 @@ class PulseMixin(SourceMixinAbc): :attr:`pulse_width` - :attr:`pulsed_value` + :attr:`pulse_value` :attr:`rise_time` @@ -210,8 +210,8 @@ 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): @@ -220,18 +220,15 @@ def __init__(self, # pulse_width, period = Tstop 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 @@ -253,12 +250,24 @@ def frequency(self): ############################################## def format_spice_parameters(self): - + 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 ('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)) + + join_list(values) + ')') #################################################################################################### @@ -272,15 +281,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 | +------+--------------------+---------------+-------+ @@ -308,29 +317,35 @@ 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) + ')') #################################################################################################### @@ -365,13 +380,13 @@ class PieceWiseLinearMixin(SourceMixinAbc): ############################################## - def __init__(self, values, repeate_time=0, delay_time=.0): + def __init__(self, values, repeat_time=0, time_delay=.0): # Fixme: default self.values = sum(([as_s(t), self.__as_unit__(x)] for (t, x) in values), []) - self.repeate_time = as_s(repeate_time) - self.delay_time = as_s(delay_time) + self.repeat_time = as_s(repeat_time) + self.time_delay = as_s(time_delay) ############################################## @@ -381,7 +396,7 @@ def format_spice_parameters(self): return ('PWL(' + join_list(self.values) + ' ' + - join_dict({'r':self.repeate_time, 'td':self.delay_time}) + # OrderedDict( + join_dict({'r':self.repeat_time, 'td':self.time_delay}) + # OrderedDict( ')') #################################################################################################### @@ -489,22 +504,28 @@ 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) + ')') #################################################################################################### @@ -812,9 +833,9 @@ def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs): #################################################################################################### -class PattermVoltageSource(VoltageSource, VoltageSourceMixinAbc, PatternMixin): +class PatternVoltageSource(VoltageSource, VoltageSourceMixinAbc, PatternMixin): - r"""This class implements a patter voltage source. + r"""This class implements a pattern voltage source. See :class:`PatternMixin` for documentation. diff --git a/PySpice/Spice/Parser.py b/PySpice/Spice/Parser.py index 30c5a4d6f..07cba7cd0 100644 --- a/PySpice/Spice/Parser.py +++ b/PySpice/Spice/Parser.py @@ -236,9 +236,14 @@ class Include(Statement): ############################################## - def __init__(self, line): + def __init__(self, line, parent): + import os + root, _ = os.path.split(parent.path) super().__init__(line, statement='include') self._include = self._line.right_of('.include') + file_name = os.path.abspath(os.path.join(root, + self._include.replace('"', ''))) + self._contents = SpiceParser(path=file_name) ############################################## @@ -255,6 +260,8 @@ def __repr__(self): def to_python(self, netlist_name): return '{}.include({})'.format(netlist_name, self._include) + os.linesep + def contents(self): + return self._contents #################################################################################################### @@ -272,9 +279,13 @@ class Model(Statement): def __init__(self, line): super().__init__(line, statement='model') - base, self._parameters = line.split_keyword('.model') - self._name, self._model_type = base + elements, parameters = line.split_keyword('.model') + if len(elements) != 2: + line.split_keyword('.model') + self._name, self._model_type = elements + self._parameters = parameters self._name = self._name.lower() + self._model_type = self._model_type.lower() ############################################## @@ -304,7 +315,7 @@ def build(self, circuit): #################################################################################################### class Param(Statement): - """ This class implements a model definition. + """ This class implements a param definition. Spice syntax:: @@ -493,11 +504,17 @@ def __init__(self, line): # Fixme parameters, dict_parameters = self._line.split_keyword('.subckt') - if parameters[-1].lower() == 'params:': + if len(parameters) < 1: + ParseError('Incorrect subcircuit declaration: {}'.format(line)) + if parameters[-1] == 'params:': parameters = parameters[:-1] + self._parameters = dict_parameters + else: + self._parameters = {} + if len(dict_parameters) != 0: + ParseError('Incorrect subcircuit sintax: {}'.format(line)) self._name, self._nodes = parameters[0], parameters[1:] self._name = self._name.lower() - self._parameters = dict_parameters self._statements = [] self._subcircuits = [] @@ -621,9 +638,12 @@ def __init__(self, line): super().__init__(line) - line_str = str(line) + line_str = str(line).lower() # self._logger.debug(os.linesep + line_str) + if len(line_str) < 1: + ParseError(line_str) + # Retrieve device prefix prefix = line_str[0] if prefix.isalpha(): @@ -634,6 +654,8 @@ def __init__(self, line): # Retrieve device name args, kwargs = line.split_element(prefix) + if len(args) < 1: + ParseError('Name not found: {}'.format(line)) self._name = args.pop(0) self._nodes = [] @@ -952,6 +974,8 @@ def _split_comment(self, line): if location != -1: text = line[:location].strip() comment = line[location:].strip() + if location == 0: + is_comment = True else: text = line comment = '' @@ -1101,74 +1125,113 @@ def get_kwarg(text): @staticmethod def _partition(text): - 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) - return parts - - @staticmethod - def _partition_in_parentheses(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) + # Physical suffix + # T E+12 G E+09 XorMEG E+06 K E+03 M E-03 U E-06 N E-09 P E-12 F E-15 + # Both upper and lower case letters are + # T 1,000,000,000,000 Tera + # G 1,000,000,000 Giga + # X or MEG 1,000,000 Mega + # K 1,000 Kilo + # M 0.001 Milli + # U 0.000001 Micro + # N 0.000000001 Nano + # P 0.000000000001 Pico + # F 0.000000000000001 Femto + # The letters after the suffix are ignored + + left = regex.compile(r'\+?\s*([\w\/][\w\/\.\+\-]*\:?|\{([^\{\}]|(?R))*?\}|([\+\-]?([0-9]+\.?[0-9]*|\.[0-9]+)(e[\+\-]?[0-9]*)?(meg|mil|[tgxkmunpfµ\%])?[a-z]*))(\s*|$)') + right = regex.compile(r'\s*(\{([^\{\}]|(?R))*?\}|([\+\-]?([0-9]+\.?[0-9]*|\.[0-9]+)(e[\+\-]?[0-9]*)?(meg|mil|[tgxkmunpfµ\%])?[a-z]*))(\s*$?)') + parentheses = regex.compile(r'\s*\(([^\(\)\{\}]|(?R))*?\)\s*') + elements = [] + parameters = {} + + values = str(text).lower() + + rhs_comma_error = False + + while len(values) != 0: + lhs = regex.match(left, values) + if lhs is None: + lhs = regex.match(parentheses, values) + if lhs is None: + if rhs_comma_error: + raise ParseError('No data after ,') + else: + raise ParseError('Unable to parse token: {}'.format(text)) + else: + rhs_comma_error = False + if len(values) > lhs.end() and values[lhs.end()] == "=": + values = values[lhs.end() + 1:] + rhs = regex.match(right, values) + if rhs is not None: + data = rhs.group(1) + while len(values) > rhs.end() and values[rhs.end()] == ",": + values = values[rhs.end() + 1:] + rhs = regex.match(right, values) + if rhs is None: + rhs_comma_error = True + break + data += "," + rhs.group(1) + else: + if len(values) > rhs.end(): + values = values[rhs.end():] + else: + values = '' + parameters[lhs.group(1)] = data + if rhs_comma_error: + continue + else: + raise ParseError('Parameter without rhs: {}'.format(text)) else: - parts.append(part) - return parts + elements.append(lhs.group(1)) + values = values[lhs.end():] + return elements, parameters @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_in_parentheses(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 = [] + elements = [] + parameters = {} + previous_start = 0 for m in regex.finditer(p, text): - parts.extend(Line._partition_parentheses(text[previous_start:m.start()])) - parts.append(m.group()) + parts = Line._partition(text[previous_start:m.start()]) + elements.extend(parts[0]) + elements.append(m.group()) + parameters.update(parts[1]) previous_start = m.end() - parts.extend(Line._partition_parentheses(text[previous_start:])) - return parts + parts = Line._partition(text[previous_start:]) + elements.extend(parts[0]) + parameters.update(parts[1]) + return elements, parameters @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 + def _partition_braces(text): + equals = regex.compile(r'\+?\s*([\w\/][\w\/\.]*[\+\-]?)\s*=\s*\{([^\{\}]|(?R))*?\}\s*') + braces = regex.compile(r'\{([^\{\}]|(?R))*?\}') + + elements = [] + parameters = {} + + values = text + + while len(values) != 0: + equalities = regex.match(equals, values) + if equalities is None: + parts = regex.match(braces, values) + if parts is not None: + part = parts.group(1) + values = values[parts.end():] + elements.append(part) + else: + raise ParseError(text) else: - parameters.append(parts[i]) - i += 1 - - return parameters, dict_parameters + left = equalities.group(1) + right = equalities.group(2) + values = values[equalities.end():] + parameters[left] = right + return elements, parameters def split_keyword(self, keyword): @@ -1181,32 +1244,48 @@ def split_keyword(self, keyword): """ - text = self.right_of(keyword) + text = self.right_of(keyword).lower() p = regex.compile(r'\(([^\(\)]|(?R))*?\)') b = regex.compile(r'\{([^\{\}]|(?R))*?\}') - parts = [] + + elements = [] + parameters = {} 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])) + parts = Line._partition(text[:mp.start()]) + elements.extend(parts[0]) + parameters.update(parts[1]) + parts = Line._partition(mp.group()[1:-1]) + elements.extend(parts[0]) + parameters.update(parts[1]) elif (mb.start() < mp.start()) and (mb.end() > mp.end()): - parts.extend(Line._partition_braces(text)) + parts = Line._partition(text) + elements.extend(parts[0]) + parameters.update(parts[1]) else: raise ValueError("Incorrect format {}".format(text)) else: - parts.extend(Line._partition_braces(text)) + parts = Line._partition(text) + elements.extend(parts[0]) + parameters.update(parts[1]) else: if mp is not None: - parts.extend(Line._partition_in_parentheses(text[:mp.start()])) - parts.extend(Line._partition_in_parentheses(mp.group()[1:-1])) + parts = Line._partition(text[:mp.start()]) + elements.extend(parts[0]) + parameters.update(parts[1]) + parts = Line._partition(mp.group()[1:-1]) + elements.extend(parts[0]) + parameters.update(parts[1]) else: - parts.extend(Line._partition_in_parentheses(text)) - return Line._check_parameters(parts) + parts = Line._partition(text) + elements.extend(parts[0]) + parameters.update(parts[1]) + return elements, parameters def split_element(self, prefix): @@ -1220,14 +1299,11 @@ def split_element(self, prefix): # Fixme: cf. get_kwarg - parameters = [] - dict_parameters = {} - - text = self.right_of(prefix) + text = self.right_of(prefix).lower() - parts = Line._partition_braces(text) + elements, parameters = Line._partition(text) - return Line._check_parameters(parts) + return elements, parameters #################################################################################################### @@ -1253,6 +1329,8 @@ def __init__(self, path=None, source=None, end_of_line_comment=('$', '//', ';')) # Fixme: empty source + self._path = path + if path is not None: with open(str(path), 'rb') as f: raw_lines = [line.decode('utf-8') for line in f] @@ -1276,11 +1354,13 @@ def _merge_lines(self, raw_lines): A line starting with "+" continues the preceding line. """ + follows = regex.compile(r'\s*(\+)') 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')) + result = regex.match(follows, line_string) + if result is not None: + current_line.append(line_string[result.end():].strip('\r\n')) else: line_string = line_string.strip(' \t\r\n') if line_string: @@ -1357,11 +1437,15 @@ def _parse(self, lines): circuit = CircuitStatement(lines[0]) stack = [] scope = circuit - for line in lines[1:]: + encripted = False + for line_idx, line in enumerate(lines[1:]): # print('>', repr(line)) text = str(line) lower_case_text = text.lower() # ! - if line.is_comment: + if encripted: + if line.comment.startswith('$CDNENCFINISH'): + encripted = False + elif line.is_comment: scope.append(Comment(line)) elif lower_case_text.startswith('.'): lower_case_text = lower_case_text[1:] @@ -1369,6 +1453,9 @@ def _parse(self, lines): stack.append(scope) scope = SubCircuitStatement(line) elif lower_case_text.startswith('ends'): + if len(stack) < 1: + ParseError('Unbalanced subcircuit definition: {}, {}'.format(line, + line_idx + 2)) parent = stack.pop() parent.appendSubCircuit(scope) scope = parent @@ -1379,11 +1466,18 @@ def _parse(self, lines): elif lower_case_text.startswith('end'): pass elif lower_case_text.startswith('model'): - model = Model(line) + try: + model = Model(line) + except Exception as e: + raise ParseError(line, e) scope.appendModel(model) elif lower_case_text.startswith('include'): - include = Include(line) + include = Include(line, self._path) scope.append(include) + for model in include.contents().models: + scope.appendModel(model) + for subcircuit in include.contents().subcircuits: + scope.appendSubCircuit(subcircuit) elif lower_case_text.startswith('param'): param = Param(line) scope.appendParam(param) @@ -1394,6 +1488,8 @@ def _parse(self, lines): # .func .csparam .temp .if # { expr } are allowed in .model lines and in device lines. self._logger.warn('Parser ignored: {}'.format(line)) + elif line.comment.startswith('$CDNENCSTART'): + encripted = True else: try: element = Element(line) @@ -1406,6 +1502,12 @@ def _parse(self, lines): scope._required_models.add(name) except ParseError: pass + n = len(stack) + if n != 0: + if n == 1: + ParseError('One unbalanced subcircuit definition') + else: + ParseError('{} unbalanced subcircuits definitions') SpiceParser._check_models(circuit) SpiceParser._sort_subcircuits(circuit) return circuit diff --git a/PySpice/Spice/SpiceGrammar.py b/PySpice/Spice/SpiceGrammar.py index f459a2f71..e9bc7415c 100644 --- a/PySpice/Spice/SpiceGrammar.py +++ b/PySpice/Spice/SpiceGrammar.py @@ -1739,19 +1739,38 @@ def block0(): def block2(): self._sep_() self.name_last_node('sep') + self._closure(block2) + self._value_() + self.name_last_node('t') + self._sep_() + self.name_last_node('sep') + self._value_() + self.name_last_node('value') + + def block7(): + 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._positive_closure(block2) - self._sep_() - self.name_last_node('sep') + self._closure(block7) + with self._optional(): + self._sep_() + self.name_last_node('sep') + self._parameters_() + self.name_last_node('parameters') + + def block14(): + self._sep_() + self.name_last_node('sep') + self._closure(block14) self._rp_() with self._option(): - def block8(): + def block16(): self._sep_() self.name_last_node('sep') self._value_() @@ -1760,7 +1779,7 @@ def block8(): self.name_last_node('sep') self._value_() self.name_last_node('value') - self._positive_closure(block8) + self._positive_closure(block16) self._error( 'expecting one of: ' ' ' diff --git a/PySpice/Spice/spicegrammar.ebnf b/PySpice/Spice/spicegrammar.ebnf index a8183b58f..67d408b49 100644 --- a/PySpice/Spice/spicegrammar.ebnf +++ b/PySpice/Spice/spicegrammar.ebnf @@ -638,8 +638,19 @@ pwl_file_arguments::PWLFileArguments pwl_arguments::PWLArguments = ( - | {sep:sep} lp ~ {sep:sep t:value sep:sep value:value}+ sep:sep rp - | {sep:sep t:value sep:sep value:value}+ + {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] ; From c90afb7448a7685432e42faa133b57908ec6a8b4 Mon Sep 17 00:00:00 2001 From: jmgc Date: Tue, 1 Jun 2021 13:00:23 +0200 Subject: [PATCH 072/134] Updated the subcircuit and circuit statements to better manage the parameters. --- PySpice/Spice/EBNFParser.py | 29 +++++----- PySpice/Spice/Netlist.py | 63 +++++++++++---------- unit-test/Spice/ipwl.txt | 5 ++ unit-test/Spice/test_SpiceParser.py | 88 +++++++++++++++++++++++++++-- unit-test/Spice/vpwl.csv | 5 ++ 5 files changed, 143 insertions(+), 47 deletions(-) create mode 100644 unit-test/Spice/ipwl.txt create mode 100644 unit-test/Spice/vpwl.csv diff --git a/PySpice/Spice/EBNFParser.py b/PySpice/Spice/EBNFParser.py index e4e97b1fd..ae16ee8f5 100644 --- a/PySpice/Spice/EBNFParser.py +++ b/PySpice/Spice/EBNFParser.py @@ -464,14 +464,14 @@ def __init__(self, name, *nodes, **params): self._name = name self._nodes = nodes - self._parameters = params + self._params = params self._statements = [] self._subcircuits = [] self._models = [] self._required_subcircuits = set() self._required_models = set() - self._params = [] + self._parameters = [] ############################################## @@ -503,8 +503,8 @@ def subcircuits(self): ############################################## def __repr__(self): - if self._parameters: - text = 'SubCircuit {} {} Params: {}'.format(self._name, self._nodes, self._parameters) + os.linesep + 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 @@ -516,7 +516,7 @@ def __repr__(self): def __iter__(self): """ Return an iterator on the statements. """ - return iter(self._models + self._subcircuits + self._statements) + return iter(self._parameters + self._models + self._subcircuits + self._statements) ############################################## @@ -534,7 +534,7 @@ def appendParam(self, statement): """ Append a param to the statement's list. """ - self._params.append(statement) + self._parameters.append(statement) def appendSubCircuit(self, statement): @@ -556,9 +556,9 @@ def to_python(self, ground=0): ############################################## def build(self, ground=0, parent=None): - subcircuit = SubCircuit(self._name, *self._nodes, **self._parameters) + subcircuit = SubCircuit(self._name, *self._nodes, **self._params) subcircuit.parent = parent - for statement in self._params: + for statement in self._parameters: statement.build(subcircuit) for statement in self._models: model = statement.build(subcircuit) @@ -597,7 +597,7 @@ def __init__(self, title, path): self._models = [] self._required_subcircuits = set() self._required_models = set() - self._params = [] + self._parameters = [] self._data = {} ############################################## @@ -633,9 +633,9 @@ def subcircuits(self): return self._subcircuits @property - def params(self): + def parameters(self): """ Parameters of the circuit. """ - return self._params + return self._parameters ############################################## @@ -643,6 +643,7 @@ def __repr__(self): text = 'Library {}'.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]) @@ -654,7 +655,7 @@ def __iter__(self): """ Return an iterator on the statements. """ - return iter(self._libraries, self._models + self._subcircuits + self._statements) + return iter(self._libraries, self._parameters + self._models + self._subcircuits + self._statements) ############################################## @@ -692,7 +693,7 @@ def appendParam(self, statement): """ Append a param to the statement's list. """ - self._params.append(statement) + self._parameters.append(statement) def appendSubCircuit(self, statement): @@ -716,7 +717,7 @@ def build(self, ground=0): circuit = Circuit(self._title) for statement in self._library_calls: statement.build(circuit, self._libraries) - for statement in self._params: + for statement in self._parameters: statement.build(circuit) for statement in self._models: statement.build(circuit) diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index f2e01a5f4..3ab23fb34 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -87,7 +87,7 @@ 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, @@ -872,10 +872,11 @@ def __init__(self): self._subcircuits = OrderedDict() # to keep the declaration order self._elements = OrderedDict() # to keep the declaration order - self._models = {} + self._models = OrderedDict() self._includes = [] # .include self._used_models = set() self._used_subcircuits = set() + self._parameters = OrderedDict() self.raw_spice = '' @@ -1077,33 +1078,44 @@ def __str__(self): # Fixme: order ??? netlist = self._str_raw_spice() - models = self._str_models() - if 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 - subcircuits = self._str_subcircuits() - if subcircuits: + if self._subcircuits: + subcircuits = self._str_subcircuits() netlist += subcircuits# before elements netlist += os.linesep - netlist += self._str_elements() + netlist += self._str_elements() + os.linesep return netlist ############################################## + def _str_parameters(self): + parameters = [".param {}={}".format(key, 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._used_models: models = [self._models[model] - for model in sorted(self._used_models) - if model in self._models] - return join_lines(models) + os.linesep + for model in self._models + if model in self._used_models] + return join_lines(models) else: return '' @@ -1112,8 +1124,8 @@ def _str_models(self): def _str_subcircuits(self): if self._used_subcircuits: subcircuits = [self._subcircuits[subcircuit] - for subcircuit in sorted(self._used_subcircuits) - if subcircuit in self._subcircuits] + for subcircuit in self._subcircuits + if subcircuit in self._used_subcircuits] return join_lines(subcircuits) else: return '' @@ -1177,7 +1189,7 @@ def __init__(self, name, *nodes, **kwargs): self._ground = kwargs.get(ground, 0) if ground in kwargs: kwargs.pop(ground) - self._parameters = kwargs + self._params = kwargs ############################################## @@ -1199,7 +1211,7 @@ def parameter(self, name, expression): """Set a parameter.""" - self._parameters[str(name)] = str(expression) + self._parameters[str(name)] = expression ############################################## @@ -1255,9 +1267,13 @@ def __str__(self): """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 = '.subckt ' + join_list((self._name, nodes)) + if self._params: + parameters = join_list(['{}={}'.format(key, str_spice(value)) + for key, value in self._params.items()]) + netlist += ' params: ' + parameters + netlist += os.linesep netlist += super().__str__() netlist += '.ends ' + self._name + os.linesep return netlist @@ -1341,7 +1357,6 @@ def __init__(self, title, self.title = str(title) self._ground = ground self._global_nodes = set(global_nodes) # .global - self._parameters = {} # .param self._data = {} # .data # Fixme: not implemented @@ -1399,7 +1414,6 @@ def str(self, simulator=None): netlist += os.linesep # netlist += self._str_includes(simulator) netlist += self._str_globals() - netlist += self._str_parameters() netlist += super().__str__() return netlist @@ -1438,15 +1452,6 @@ def _str_globals(self): ############################################## - def _str_parameters(self): - if self._parameters: - return join_lines([key + ("" if value is None else " = " + str(value)) - for key, value in self._parameters.items()], prefix='.param ') + os.linesep - else: - return '' - - ############################################## - def __str__(self): return self.str(simulator=None) diff --git a/unit-test/Spice/ipwl.txt b/unit-test/Spice/ipwl.txt new file mode 100644 index 000000000..657139adf --- /dev/null +++ b/unit-test/Spice/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/Spice/test_SpiceParser.py b/unit-test/Spice/test_SpiceParser.py index 8c6333edf..1a7bae9fd 100644 --- a/unit-test/Spice/test_SpiceParser.py +++ b/unit-test/Spice/test_SpiceParser.py @@ -8,6 +8,12 @@ data = """* Data test *More notes +.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 G1 21 3 POLY(1) 1 3 0 1.57E-6 -0.97e-7 @@ -164,8 +170,6 @@ .SUBCKT MYGND 25 28 7 MYPWR .ENDS -V99 99 26 DC 0 AC 0 PULSE 0 0 0 100n 100n 500n 1u - .SUBCKT UNITAMP 1 2 .ENDS @@ -211,8 +215,8 @@ IPWL1 1 0 PWL 0S 0A 2S 3A 3S 2A 4S 2A 4.01S 5A r=2s td=1 IPWL2 2 0 PWL FILE "ipwl.txt" -IPWL3 3 0 PWL file "ipwl.csv" -IPWL4 4 0 PWL FILE ipwl.csv +VPWL3 3 0 PWL file "vpwl.csv" +VPWL4 4 0 PWL FILE vpwl.csv ISLOW 1 22 SIN(0.5 1.0ma 1KHz 1ms) IPULSE 1 3 PULSE(-1 1 2ns 2ns 2ns 50ns 100ns) @@ -400,6 +404,82 @@ def test_library(self): simulator.options('device smoothbsrc=1') print(simulator) + 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) +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 SIN 0 5 3 1 +""") + circuit = transient.build() + + expected = """.title None + +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 0 0 0) +ipwl1 1 0 pwl(0 0 2 3 3 2 4 2 4.01 5 r=2 td=1) +vsffm 1 0 sffm(0 1 2) +isin 4 3 dc 0 ac sin(0 5 3 1 0) +""" + 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 = """""" + 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 None + +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') diff --git a/unit-test/Spice/vpwl.csv b/unit-test/Spice/vpwl.csv new file mode 100644 index 000000000..4c3762cf8 --- /dev/null +++ b/unit-test/Spice/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 From 52ca14e6a93430fb390a1e80111424b11aaecc6a Mon Sep 17 00:00:00 2001 From: jmgc Date: Tue, 1 Jun 2021 17:23:08 +0200 Subject: [PATCH 073/134] Updated the subcircuit and circuit statements to better manage the parameters. --- PySpice/Spice/EBNFParser.py | 2 +- PySpice/Spice/Netlist.py | 21 +- PySpice/Spice/Parser.py | 1605 --------------------------- PySpice/Spice/SpiceGrammar.py | 57 +- PySpice/Spice/spicegrammar.ebnf | 15 +- PySpice/Unit/Unit.py | 2 + unit-test/Spice/test_SpiceParser.py | 58 +- 7 files changed, 94 insertions(+), 1666 deletions(-) delete mode 100644 PySpice/Spice/Parser.py diff --git a/PySpice/Spice/EBNFParser.py b/PySpice/Spice/EBNFParser.py index ae16ee8f5..b90f2ad71 100644 --- a/PySpice/Spice/EBNFParser.py +++ b/PySpice/Spice/EBNFParser.py @@ -1668,7 +1668,7 @@ def walk_IncludeCmd(self, node, data): ) # The include statement makes available all the parameters, models and # subcircuits in the file. - data._present._params.extend(include._contents._params) + data._present._parameters.extend(include._contents._parameters) data._present._models.extend(include._contents._models) data._present._subcircuits.extend(include._contents._subcircuits) diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index 3ab23fb34..3d8e50337 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -1041,6 +1041,12 @@ def _remove_element(self, element): ############################################## + def parameter(self, name, expression): + """Set a parameter.""" + self._parameters[str(name)] = expression + + ############################################## + def model(self, name, modele_type, **parameters): """Add a model.""" @@ -1158,9 +1164,9 @@ def include(self, path, entry=None): subcircuit_def = subcircuit.build(parent=self) self.subcircuit(subcircuit_def) self._subcircuits[subcircuit._name.lower()]._included = path - parameters = library.params + parameters = library.parameters for param in parameters: - self.param(param) + self.parameters(*param) else: self._logger.warn("Duplicated include") @@ -1392,11 +1398,6 @@ def clone(self, title=None): ############################################## - def parameter(self, name, expression): - """Set a parameter.""" - self._parameters[str(name)] = expression - - ############################################## def data(self, table, **kwargs): self._data.update[table] = kwargs @@ -1413,7 +1414,8 @@ def str(self, simulator=None): netlist = self._str_title() netlist += os.linesep # netlist += self._str_includes(simulator) - netlist += self._str_globals() + if self._global_nodes: + netlist += self._str_globals() + os.linesep netlist += super().__str__() return netlist @@ -1446,7 +1448,8 @@ def _str_includes(self, simulator=None): def _str_globals(self): if self._global_nodes: - return '.global ' + join_list(self._global_nodes) + os.linesep + return join_lines(['.global {}'.format(str_spice(node)) + for node in self._global_nodes]) else: return '' diff --git a/PySpice/Spice/Parser.py b/PySpice/Spice/Parser.py deleted file mode 100644 index 07cba7cd0..000000000 --- a/PySpice/Spice/Parser.py +++ /dev/null @@ -1,1605 +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 . -# -#################################################################################################### - -#################################################################################################### - -"""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. - -""" - -#################################################################################################### - -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 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, parent): - import os - root, _ = os.path.split(parent.path) - super().__init__(line, statement='include') - self._include = self._line.right_of('.include') - file_name = os.path.abspath(os.path.join(root, - self._include.replace('"', ''))) - self._contents = SpiceParser(path=file_name) - - ############################################## - - 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 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') - - elements, parameters = line.split_keyword('.model') - if len(elements) != 2: - line.split_keyword('.model') - self._name, self._model_type = elements - self._parameters = parameters - self._name = self._name.lower() - self._model_type = self._model_type.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 Param(Statement): - """ This class implements a param definition. - - Spice syntax:: - - .param name=expr - - """ - - ############################################## - - def __init__(self, line): - super().__init__(line, statement='param') - - text = line.right_of('.param').strip().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)) - return '{}.param({})'.format(netlist_name, self.join_args(args)) + os.linesep - - ############################################## - - def build(self, circuit): - circuit.parameter(self._name, self._value) - - -#################################################################################################### - -class CircuitStatement(Statement): - """ This class implements a circuit definition. - - Spice syntax:: - - Title ... - - """ - - ############################################## - - def __init__(self, title): - - super().__init__(title, statement='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 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): - - 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') - if len(parameters) < 1: - ParseError('Incorrect subcircuit declaration: {}'.format(line)) - if parameters[-1] == 'params:': - parameters = parameters[:-1] - self._parameters = dict_parameters - else: - self._parameters = {} - if len(dict_parameters) != 0: - ParseError('Incorrect subcircuit sintax: {}'.format(line)) - self._name, self._nodes = parameters[0], parameters[1:] - self._name = self._name.lower() - - 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 {} {} Params: {}'.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 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): - - 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).lower() - # self._logger.debug(os.linesep + line_str) - - if len(line_str) < 1: - ParseError(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) - if len(args) < 1: - ParseError('Name not found: {}'.format(line)) - 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) > number_of_positionals and len(args) <= prefix_data.number_of_positionals_max: - number_of_positionals = len(args) - 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 = [] - values = self._parameters[:] - update = [] - for parameter in sorted(element_class.positional_parameters.values(), - key=lambda parameter: parameter.position): - if not parameter.key_parameter: - parameters_map = {} - for idx, value in enumerate(values): - try: - data = parameter.validate(value) - parameters_map[idx] = data - except Exception as e: - pass - if len(parameters_map) == 1: - update.append(values.pop(next(iter(parameters_map)))) - if parameter.key_parameter: - idx = parameter.position - if idx < len(values): - self._dict_parameters[parameter.attribute_name] = parameter.validate(values[idx]) - to_delete.append(idx - len(to_delete)) - for idx in to_delete: - values.pop(idx) - self._parameters = update + values - - # 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('Element') - - ############################################## - - 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() - if location == 0: - is_comment = True - 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): - # Physical suffix - # T E+12 G E+09 XorMEG E+06 K E+03 M E-03 U E-06 N E-09 P E-12 F E-15 - # Both upper and lower case letters are - # T 1,000,000,000,000 Tera - # G 1,000,000,000 Giga - # X or MEG 1,000,000 Mega - # K 1,000 Kilo - # M 0.001 Milli - # U 0.000001 Micro - # N 0.000000001 Nano - # P 0.000000000001 Pico - # F 0.000000000000001 Femto - # The letters after the suffix are ignored - - left = regex.compile(r'\+?\s*([\w\/][\w\/\.\+\-]*\:?|\{([^\{\}]|(?R))*?\}|([\+\-]?([0-9]+\.?[0-9]*|\.[0-9]+)(e[\+\-]?[0-9]*)?(meg|mil|[tgxkmunpfµ\%])?[a-z]*))(\s*|$)') - right = regex.compile(r'\s*(\{([^\{\}]|(?R))*?\}|([\+\-]?([0-9]+\.?[0-9]*|\.[0-9]+)(e[\+\-]?[0-9]*)?(meg|mil|[tgxkmunpfµ\%])?[a-z]*))(\s*$?)') - parentheses = regex.compile(r'\s*\(([^\(\)\{\}]|(?R))*?\)\s*') - elements = [] - parameters = {} - - values = str(text).lower() - - rhs_comma_error = False - - while len(values) != 0: - lhs = regex.match(left, values) - if lhs is None: - lhs = regex.match(parentheses, values) - if lhs is None: - if rhs_comma_error: - raise ParseError('No data after ,') - else: - raise ParseError('Unable to parse token: {}'.format(text)) - else: - rhs_comma_error = False - if len(values) > lhs.end() and values[lhs.end()] == "=": - values = values[lhs.end() + 1:] - rhs = regex.match(right, values) - if rhs is not None: - data = rhs.group(1) - while len(values) > rhs.end() and values[rhs.end()] == ",": - values = values[rhs.end() + 1:] - rhs = regex.match(right, values) - if rhs is None: - rhs_comma_error = True - break - data += "," + rhs.group(1) - else: - if len(values) > rhs.end(): - values = values[rhs.end():] - else: - values = '' - parameters[lhs.group(1)] = data - if rhs_comma_error: - continue - else: - raise ParseError('Parameter without rhs: {}'.format(text)) - else: - elements.append(lhs.group(1)) - values = values[lhs.end():] - return elements, parameters - - @staticmethod - def _partition_parentheses(text): - p = regex.compile(r'\(([^\(\)]|(?R))*?\)') - - elements = [] - parameters = {} - - previous_start = 0 - for m in regex.finditer(p, text): - parts = Line._partition(text[previous_start:m.start()]) - elements.extend(parts[0]) - elements.append(m.group()) - parameters.update(parts[1]) - previous_start = m.end() - parts = Line._partition(text[previous_start:]) - elements.extend(parts[0]) - parameters.update(parts[1]) - return elements, parameters - - @staticmethod - def _partition_braces(text): - equals = regex.compile(r'\+?\s*([\w\/][\w\/\.]*[\+\-]?)\s*=\s*\{([^\{\}]|(?R))*?\}\s*') - braces = regex.compile(r'\{([^\{\}]|(?R))*?\}') - - elements = [] - parameters = {} - - values = text - - while len(values) != 0: - equalities = regex.match(equals, values) - if equalities is None: - parts = regex.match(braces, values) - if parts is not None: - part = parts.group(1) - values = values[parts.end():] - elements.append(part) - else: - raise ParseError(text) - else: - left = equalities.group(1) - right = equalities.group(2) - values = values[equalities.end():] - parameters[left] = right - return elements, 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).lower() - - p = regex.compile(r'\(([^\(\)]|(?R))*?\)') - b = regex.compile(r'\{([^\{\}]|(?R))*?\}') - - elements = [] - parameters = {} - - 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 = Line._partition(text[:mp.start()]) - elements.extend(parts[0]) - parameters.update(parts[1]) - parts = Line._partition(mp.group()[1:-1]) - elements.extend(parts[0]) - parameters.update(parts[1]) - elif (mb.start() < mp.start()) and (mb.end() > mp.end()): - parts = Line._partition(text) - elements.extend(parts[0]) - parameters.update(parts[1]) - else: - raise ValueError("Incorrect format {}".format(text)) - else: - parts = Line._partition(text) - elements.extend(parts[0]) - parameters.update(parts[1]) - else: - if mp is not None: - parts = Line._partition(text[:mp.start()]) - elements.extend(parts[0]) - parameters.update(parts[1]) - parts = Line._partition(mp.group()[1:-1]) - elements.extend(parts[0]) - parameters.update(parts[1]) - else: - parts = Line._partition(text) - elements.extend(parts[0]) - parameters.update(parts[1]) - return elements, parameters - - 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 - - text = self.right_of(prefix).lower() - - elements, parameters = Line._partition(text) - - return elements, parameters - - -#################################################################################################### - -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 - - self._path = path - - if path is not None: - with open(str(path), 'rb') as f: - raw_lines = [line.decode('utf-8') for line in f] - 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. - """ - - follows = regex.compile(r'\s*(\+)') - lines = [] - current_line = None - for line_index, line_string in enumerate(raw_lines): - result = regex.match(follows, line_string) - if result is not None: - current_line.append(line_string[result.end():].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 - encripted = False - for line_idx, line in enumerate(lines[1:]): - # print('>', repr(line)) - text = str(line) - lower_case_text = text.lower() # ! - if encripted: - if line.comment.startswith('$CDNENCFINISH'): - encripted = False - elif 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'): - if len(stack) < 1: - ParseError('Unbalanced subcircuit definition: {}, {}'.format(line, - line_idx + 2)) - parent = stack.pop() - parent.appendSubCircuit(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'): - try: - model = Model(line) - except Exception as e: - raise ParseError(line, e) - scope.appendModel(model) - elif lower_case_text.startswith('include'): - include = Include(line, self._path) - scope.append(include) - for model in include.contents().models: - scope.appendModel(model) - for subcircuit in include.contents().subcircuits: - scope.appendSubCircuit(subcircuit) - elif lower_case_text.startswith('param'): - param = Param(line) - scope.appendParam(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)) - elif line.comment.startswith('$CDNENCSTART'): - encripted = True - 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 - n = len(stack) - if n != 0: - if n == 1: - ParseError('One unbalanced subcircuit definition') - else: - ParseError('{} unbalanced subcircuits definitions') - 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/SpiceGrammar.py b/PySpice/Spice/SpiceGrammar.py index e9bc7415c..10b9715a1 100644 --- a/PySpice/Spice/SpiceGrammar.py +++ b/PySpice/Spice/SpiceGrammar.py @@ -382,25 +382,26 @@ def _capacitor_(self): # noqa self._cut() with self._optional(): with self._choice(): - with self._option(): - self._sep_() - self.name_last_node('sep') - self._model_name_() - self.name_last_node('model') - self._sep_() - self.name_last_node('sep') - self._gen_expr_() - self.name_last_node('value') 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') 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._error( 'expecting one of: ' '' @@ -986,25 +987,26 @@ def _inductor_(self): # noqa self._cut() with self._optional(): with self._choice(): - with self._option(): - self._sep_() - self.name_last_node('sep') - self._model_name_() - self.name_last_node('model') - self._sep_() - self.name_last_node('sep') - self._gen_expr_() - self.name_last_node('value') 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') 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._error( 'expecting one of: ' '' @@ -1184,25 +1186,26 @@ def _resistor_(self): # noqa self._cut() with self._optional(): with self._choice(): - with self._option(): - self._sep_() - self.name_last_node('sep') - self._model_name_() - self.name_last_node('model') - self._sep_() - self.name_last_node('sep') - self._gen_expr_() - self.name_last_node('value') 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') 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._error( 'expecting one of: ' '' diff --git a/PySpice/Spice/spicegrammar.ebnf b/PySpice/Spice/spicegrammar.ebnf index 67d408b49..0deaed046 100644 --- a/PySpice/Spice/spicegrammar.ebnf +++ b/PySpice/Spice/spicegrammar.ebnf @@ -125,9 +125,8 @@ capacitor::Capacitor negative:node ~ [ - | sep:sep model:model_name sep:sep value:gen_expr - | sep:sep value:gen_expr - | sep:sep model:model_name + | sep:sep value:gen_expr [sep:sep model:model_name] + | sep:sep model:model_name [sep:sep value:gen_expr] ] [sep:sep parameters:parameters] @@ -369,9 +368,8 @@ inductor::Inductor negative:node ~ [ - | sep:sep model:model_name sep:sep value:gen_expr - | sep:sep value:gen_expr - | sep:sep model:model_name + | sep:sep value:gen_expr [sep:sep model:model_name] + | sep:sep model:model_name [sep:sep value:gen_expr] ] [sep:sep parameters:parameters] @@ -445,9 +443,8 @@ resistor::Resistor negative:node ~ [ - | sep:sep model:model_name sep:sep value:gen_expr - | sep:sep value:gen_expr - | sep:sep model:model_name + | sep:sep value:gen_expr [sep:sep model:model_name] + | sep:sep model:model_name [sep:sep value:gen_expr] ] [sep:sep parameters:parameters] diff --git a/PySpice/Unit/Unit.py b/PySpice/Unit/Unit.py index 5170b8901..9e7f128f6 100644 --- a/PySpice/Unit/Unit.py +++ b/PySpice/Unit/Unit.py @@ -1067,6 +1067,8 @@ def str(self, spice=False, space=False, unit=True): prefix_string = self._prefixed_unit.str(spice, unit) if space and (prefix_string != ""): string += ' ' + prefix_string + else: + string += prefix_string return string ############################################## diff --git a/unit-test/Spice/test_SpiceParser.py b/unit-test/Spice/test_SpiceParser.py index 1a7bae9fd..7a4efd0ff 100644 --- a/unit-test/Spice/test_SpiceParser.py +++ b/unit-test/Spice/test_SpiceParser.py @@ -8,6 +8,9 @@ data = """* Data test *More notes +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 @@ -367,13 +370,13 @@ def circuit_gft(prb): circuit = parser.build() circuit.parameter('prb', str(prb[1])) simulator = circuit.simulator(simulator='xyce-serial') - simulator.save(['all']) + simulator.save('all') return simulator class TestSpiceParser(unittest.TestCase): def test_parser(self): - # SpiceParser._regenerate() + #SpiceParser._regenerate() results = list(map(circuit_gft, [(data, -1), (data, 1)])) self.assertEqual(len(results), 2) values = str(results[0]) @@ -418,12 +421,12 @@ def test_transient(self): expected = """.title None -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 0 0 0) -ipwl1 1 0 pwl(0 0 2 3 3 2 4 2 4.01 5 r=2 td=1) -vsffm 1 0 sffm(0 1 2) -isin 4 3 dc 0 ac sin(0 5 3 1 0) +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 sin(0a 5a 3hz 1s 0hz) """ result = str(circuit) self.assertEqual(expected, result) @@ -460,7 +463,35 @@ def test_subcircuits(self): """) circuit = subckt.build() print(circuit) - expected = """""" + expected = """.title None + +.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) @@ -489,10 +520,11 @@ def test_subcircuit(self): expected = """.title MOS Driver .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 1 choi ho hs 1e-09 @@ -502,10 +534,6 @@ def test_subcircuit(self): dhb vdd hb diode .ends mosdriver -.subckt source vh vl hi lo -bhigh vh vl v={if((v(hi, lo) > 0.5), 5, 0)} smoothbsrc=1 -.ends source - xtest 0 1 2 3 4 5 mosdriver btest 1 0 v=if(0, 0, 1) smoothbsrc=1 """ From 42fd988c083c9803000cc4412064a94fdc3abf96 Mon Sep 17 00:00:00 2001 From: jmgc Date: Tue, 1 Jun 2021 17:52:26 +0200 Subject: [PATCH 074/134] Correction on the poly for current management. --- PySpice/Spice/EBNFParser.py | 3 ++- unit-test/Spice/test_SpiceParser.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/PySpice/Spice/EBNFParser.py b/PySpice/Spice/EBNFParser.py index b90f2ad71..5ac34a4dd 100644 --- a/PySpice/Spice/EBNFParser.py +++ b/PySpice/Spice/EBNFParser.py @@ -1471,7 +1471,8 @@ def walk_ControlCurrentPoly(self, node, data): values.extend(coefficients) else: values.append(coefficients) - data = [self.walk(dev, data) for dev in ctrl_dev] + [str(value) for value in values] + data = ["i({})".format(self.walk(dev, data)) + for dev in ctrl_dev] + [str(value) for value in values] parameters = ' '.join(data) return '{ POLY (%d) %s }' % (controllers, parameters) diff --git a/unit-test/Spice/test_SpiceParser.py b/unit-test/Spice/test_SpiceParser.py index 7a4efd0ff..a15fd7818 100644 --- a/unit-test/Spice/test_SpiceParser.py +++ b/unit-test/Spice/test_SpiceParser.py @@ -8,6 +8,8 @@ data = """* Data test *More notes +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 From 4ad962bb2f108b090294d64d221145973e6327c2 Mon Sep 17 00:00:00 2001 From: jmgc Date: Tue, 1 Jun 2021 18:44:58 +0200 Subject: [PATCH 075/134] Correction on the walker as a value return was missing. --- PySpice/Spice/EBNFParser.py | 10 ++++++---- PySpice/Spice/Netlist.py | 2 ++ unit-test/Spice/test_SpiceParser.py | 4 ++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/PySpice/Spice/EBNFParser.py b/PySpice/Spice/EBNFParser.py index 5ac34a4dd..c05f82f7c 100644 --- a/PySpice/Spice/EBNFParser.py +++ b/PySpice/Spice/EBNFParser.py @@ -5,7 +5,8 @@ from unicodedata import normalize from PySpice.Unit.Unit import UnitValue, ZeroPower, PrefixedUnit from PySpice.Unit.SiUnits import Tera, Giga, Mega, Kilo, Milli, Micro, Nano, Pico, Femto -from PySpice.Spice.Expressions import * +from PySpice.Tools.StringTools import join_lines +from .Expressions import * from .ElementParameter import FlagParameter from .Netlist import (Circuit, DeviceModel, @@ -805,8 +806,9 @@ def __init__(self): def walk_Circuit(self, node, data): if data._root is None: + title = join_lines(self.walk(node.title, data)) data._root = CircuitStatement( - self.walk(node.title, data), + title, data._path ) data._present = data._root @@ -1863,7 +1865,7 @@ def walk_Relational(self, node, data): def walk_ConditionalFactor(self, node, data): if node.boolean is None: - self.walk(node.expr, data) + return self.walk(node.expr, data) else: return node.boolean.lower == "true" @@ -2024,7 +2026,7 @@ def walk_Int(self, node, data): def walk_Comment(self, node, data): # TODO implement comments on devices - return + return node.ast def walk_Separator(self, node, data): if node.comment is not None: diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index 3d8e50337..1576abcb9 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -1360,6 +1360,8 @@ 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 diff --git a/unit-test/Spice/test_SpiceParser.py b/unit-test/Spice/test_SpiceParser.py index a15fd7818..e485f1de4 100644 --- a/unit-test/Spice/test_SpiceParser.py +++ b/unit-test/Spice/test_SpiceParser.py @@ -8,6 +8,8 @@ data = """* Data test *More notes +BG1 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 @@ -383,6 +385,8 @@ def test_parser(self): 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_library(self): from PySpice.Spice.Library import SpiceLibrary From 92d23cc245b45c630d211e73aa753035842f1305 Mon Sep 17 00:00:00 2001 From: jmgc Date: Tue, 1 Jun 2021 18:48:42 +0200 Subject: [PATCH 076/134] Removing the space from .title line when the title string is empty. --- PySpice/Spice/Netlist.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index 1576abcb9..8392c8dbc 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -1424,7 +1424,10 @@ def str(self, simulator=None): ############################################## def _str_title(self): - return '.title {}'.format(self.title) + os.linesep + if self.title: + return '.title {}'.format(self.title) + os.linesep + else: + return '.title' + os.linesep ############################################## From 9e6eb6196dd15f102c68d2f49936300586b71a8f Mon Sep 17 00:00:00 2001 From: jmgc Date: Tue, 1 Jun 2021 19:36:08 +0200 Subject: [PATCH 077/134] Correction in the table expression string and improvements in the poly management. --- PySpice/Spice/EBNFParser.py | 22 ++++++------ PySpice/Spice/Expressions.py | 13 +++++-- PySpice/Spice/SpiceGrammar.py | 55 ++++++++++++++++++++++++----- PySpice/Spice/spicegrammar.ebnf | 49 +++++++++++++++++-------- unit-test/Spice/test_SpiceParser.py | 21 ++++++++--- 5 files changed, 119 insertions(+), 41 deletions(-) diff --git a/PySpice/Spice/EBNFParser.py b/PySpice/Spice/EBNFParser.py index c05f82f7c..a254a7de1 100644 --- a/PySpice/Spice/EBNFParser.py +++ b/PySpice/Spice/EBNFParser.py @@ -1451,12 +1451,10 @@ def walk_ControlVoltagePoly(self, node, data): values.extend(coefficients) else: values.append(coefficients) - result = ['v(%s,%s)' % nodes - for nodes in zip(ctrl_pos, - ctrl_neg)] - result += [str(value) for value in values] - parameters = ' '.join(result) - return '{ POLY (%d) %s }' % (controllers, parameters) + 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) @@ -1464,19 +1462,21 @@ def walk_ControlCurrentPoly(self, node, data): raise ValueError( "The number of control nodes is smaller than the expected controllers: {}".format(controllers)) - ctrl_dev = node.device[: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) - data = ["i({})".format(self.walk(dev, data)) - for dev in ctrl_dev] + [str(value) for value in values] - parameters = ' '.join(data) - return '{ POLY (%d) %s }' % (controllers, parameters) + 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), diff --git a/PySpice/Spice/Expressions.py b/PySpice/Spice/Expressions.py index 95dc9b9d6..dafa4678e 100644 --- a/PySpice/Spice/Expressions.py +++ b/PySpice/Spice/Expressions.py @@ -3,7 +3,6 @@ import numpy as np import operator as op - class Expression: def __call__(self, **kwargs): raise NotImplementedError("The call function is not implemented in class: {}".format(type(self))) @@ -659,12 +658,22 @@ def __init__(self, *nodes): 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, + string = "table {{{}}} = {}".format(self._expression, " ".join(["({}, {})".format(*point) for point in self._points])) super(Table, self).__init__(string) diff --git a/PySpice/Spice/SpiceGrammar.py b/PySpice/Spice/SpiceGrammar.py index 10b9715a1..f891b3f68 100644 --- a/PySpice/Spice/SpiceGrammar.py +++ b/PySpice/Spice/SpiceGrammar.py @@ -685,13 +685,7 @@ def _control_table_(self): # noqa 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._group(): with self._choice(): with self._option(): with self._group(): @@ -714,6 +708,33 @@ def block5(): self._sep_() self.name_last_node('sep') self._rp_() + + def block11(): + + def block12(): + self._sep_() + self.name_last_node('sep') + self._positive_closure(block12) + 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._closure(block11) with self._option(): with self._group(): self._value_() @@ -727,11 +748,29 @@ def block5(): self.name_last_node('sep') self._value_() self.add_last_node_to_name('output') + + def block24(): + + def block25(): + self._sep_() + self.name_last_node('sep') + self._positive_closure(block25) + 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._closure(block24) self._error( 'expecting one of: ' ' ' ) - self._positive_join(block5, sep5) self._define( ['expr', 'sep', 'type'], ['input', 'output'] diff --git a/PySpice/Spice/spicegrammar.ebnf b/PySpice/Spice/spicegrammar.ebnf index 0deaed046..221281918 100644 --- a/PySpice/Spice/spicegrammar.ebnf +++ b/PySpice/Spice/spicegrammar.ebnf @@ -248,21 +248,40 @@ control_table::ControlTable [sep:sep] '=' [sep:sep] - (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) - }+ + ( + ( + 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} + ) + ) ; diff --git a/unit-test/Spice/test_SpiceParser.py b/unit-test/Spice/test_SpiceParser.py index e485f1de4..eb9cf00c9 100644 --- a/unit-test/Spice/test_SpiceParser.py +++ b/unit-test/Spice/test_SpiceParser.py @@ -3,11 +3,22 @@ from PySpice.Spice.EBNFParser import SpiceParser from multiprocessing import Pool, cpu_count import os -import tatsu data = """* Data test *More notes +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)} + BG1 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 @@ -380,7 +391,7 @@ def circuit_gft(prb): class TestSpiceParser(unittest.TestCase): def test_parser(self): - #SpiceParser._regenerate() + # SpiceParser._regenerate() results = list(map(circuit_gft, [(data, -1), (data, 1)])) self.assertEqual(len(results), 2) values = str(results[0]) @@ -425,7 +436,7 @@ def test_transient(self): """) circuit = transient.build() - expected = """.title None + expected = """.title vexp 2 0 exp(1v 2v 3s) vpat 3 4 pat(3v 0v 2s 1s 2s 3s b0101 1) @@ -469,7 +480,7 @@ def test_subcircuits(self): """) circuit = subckt.build() print(circuit) - expected = """.title None + expected = """.title .param a=23 .param b=24 @@ -508,7 +519,7 @@ def test_boolean(self): BEXOR YINT 0 V = {IF(V(A) > 0.5 ^ V(B) > 0.5, 1, 0)} """) circuit = and2.build() - expected = """.title None + 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)} From 46cb3a2f948acef1f2bd84f88523519a77ca19d6 Mon Sep 17 00:00:00 2001 From: jmgc Date: Wed, 2 Jun 2021 16:09:40 +0200 Subject: [PATCH 078/134] Modifications to avoid compatibility issues with the ".save" command between xyce and ngspice. --- PySpice/Spice/NgSpice/Simulation.py | 20 ++++++++++++++++++++ PySpice/Spice/Simulation.py | 12 ++---------- PySpice/Spice/Xyce/Simulation.py | 4 ++++ unit-test/Spice/test_Netlist.py | 16 +++++++++++++++- 4 files changed, 41 insertions(+), 11 deletions(-) diff --git a/PySpice/Spice/NgSpice/Simulation.py b/PySpice/Spice/NgSpice/Simulation.py index c465728bf..31423f0b7 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/Simulation.py b/PySpice/Spice/Simulation.py index 513c0499c..5119afcca 100644 --- a/PySpice/Spice/Simulation.py +++ b/PySpice/Spice/Simulation.py @@ -395,7 +395,7 @@ def save(self, *args): """ - self._saved_nodes |= set(*args) + self._saved_nodes.update(args) ############################################## @@ -637,15 +637,7 @@ def __str__(self): netlist += self.str_options() if self.initial_condition and len(self._initial_condition) > 0: netlist += '.ic ' + join_dict(self._initial_condition) + 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 analysis_parameters in self._analyses.values(): netlist += str(analysis_parameters) + os.linesep netlist += '.end' + os.linesep diff --git a/PySpice/Spice/Xyce/Simulation.py b/PySpice/Spice/Xyce/Simulation.py index 70058ce78..7d906c2c7 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 @@ -51,6 +52,9 @@ def __init__(self, circuit, **kwargs): xyce_command = kwargs.get('xyce_command', None) self._xyce_server = XyceServer(xyce_command=xyce_command) + def save_str(self): + return ".save" + os.linesep + ############################################## def str_options(self): diff --git a/unit-test/Spice/test_Netlist.py b/unit-test/Spice/test_Netlist.py index da2a9c64a..142c1cecb 100644 --- a/unit-test/Spice/test_Netlist.py +++ b/unit-test/Spice/test_Netlist.py @@ -169,6 +169,20 @@ def test_raw_spice(self): circuit.R(1, 'in', 'out', raw_spice='9kOhm') circuit.raw_spice += 'R2 out 0 1kOhm' self._test_spice_declaration(circuit, spice_declaration) + simulator = circuit.simulator(simulator='xyce-serial') + simulator.save('all') + result = simulator.transient(1e-9, 1e-6) + simulate_txt = str(simulator) + self.assertRegex(simulate_txt, "\.save" + os.linesep) + self.assertTrue(result is not None) + simulator = circuit.simulator(simulator='ngspice-subprocess') + simulator.save('all') + result = simulator.transient(1e-9, 1e-6) + simulate_txt = str(simulator) + self.assertRegex(simulate_txt, "\.save all" + os.linesep) + self.assertTrue(result is not None) + + ############################################## @@ -182,7 +196,7 @@ def test_keyword_clash(self): def test_transformer(self): import os from PySpice.Spice.Netlist import Circuit - circuit = Circuit('Diode Characteristic Curve') + circuit = Circuit('Transformer') circuit.L('primary', 'Vlp', 'Vdrain', '{l_trf}') circuit.C('resonance', 'Vlv', 'Vdrain', '{cap_r}') circuit.L('secondary', 'Vls', 'ghv', '{Ls}') From 363b4616a4c760a423568b302c7b639da369ee63 Mon Sep 17 00:00:00 2001 From: jmgc Date: Sun, 6 Jun 2021 15:26:58 +0200 Subject: [PATCH 079/134] Allow the Xyce server to run in a specified directory to allow the recovery of the data. --- PySpice/Spice/Xyce/RawFile.py | 13 ++++++++----- PySpice/Spice/Xyce/Server.py | 16 +++++++++++---- PySpice/Spice/Xyce/Simulation.py | 3 +-- unit-test/Spice/test_SpiceParser.py | 30 +++++++++++++++++++++++++++-- 4 files changed, 49 insertions(+), 13 deletions(-) diff --git a/PySpice/Spice/Xyce/RawFile.py b/PySpice/Spice/Xyce/RawFile.py index 1029017dd..6d71c92cc 100644 --- a/PySpice/Spice/Xyce/RawFile.py +++ b/PySpice/Spice/Xyce/RawFile.py @@ -40,6 +40,7 @@ _module_logger = logging.getLogger(__name__) + #################################################################################################### class Variable(VariableAbc): @@ -78,10 +79,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: @@ -120,7 +121,6 @@ def __init__(self, output): self._simulation = None - ############################################## def _read_header(self, output): @@ -145,7 +145,10 @@ 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) @@ -158,8 +161,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..2c02e5d65 100644 --- a/PySpice/Spice/Xyce/Server.py +++ b/PySpice/Spice/Xyce/Server.py @@ -71,6 +71,9 @@ 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) ############################################## @@ -112,9 +115,13 @@ 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') with open(input_filename, 'w') as f: f.write(str(spice_input)) @@ -135,6 +142,7 @@ def __call__(self, spice_input): # 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 7d906c2c7..13559e9da 100644 --- a/PySpice/Spice/Xyce/Simulation.py +++ b/PySpice/Spice/Xyce/Simulation.py @@ -49,8 +49,7 @@ 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/unit-test/Spice/test_SpiceParser.py b/unit-test/Spice/test_SpiceParser.py index eb9cf00c9..40b59c8a0 100644 --- a/unit-test/Spice/test_SpiceParser.py +++ b/unit-test/Spice/test_SpiceParser.py @@ -420,9 +420,35 @@ def test_library(self): circuit.B('test_tc', 1, 2, v={5}, tc=(7, 8)) simulator = circuit.simulator(simulator='xyce-serial', temperature=25, - nominal_temperature=25) + nominal_temperature=25, + working_directory='.') simulator.options('device smoothbsrc=1') - print(simulator) + simulator.transient(1e-9, 1e-3) + + def test_working_dir(self): + 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) + result = simulator.transient(1e-2, 10) + simulator = circuit.simulator(simulator='xyce-serial', + temperature=25, + nominal_temperature=25, + working_directory='.') + 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')) def test_transient(self): transient = SpiceParser.parse(source=""" From aa4989a6c3ef8dc6162cd9d4413a3d134ed9f278 Mon Sep 17 00:00:00 2001 From: jmgc Date: Mon, 7 Jun 2021 08:48:38 +0200 Subject: [PATCH 080/134] Modified RawFile reader to access directly with numpy to the file to read the raw data, and also accept unfinished simulations. --- PySpice/Spice/Xyce/RawFile.py | 46 ++++++++++++++++++++++++++--- unit-test/Spice/test_SpiceParser.py | 3 ++ 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/PySpice/Spice/Xyce/RawFile.py b/PySpice/Spice/Xyce/RawFile.py index 6d71c92cc..32b241b8e 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 @@ -113,10 +114,21 @@ 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 @@ -154,6 +166,32 @@ def _read_header(self, output): 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): diff --git a/unit-test/Spice/test_SpiceParser.py b/unit-test/Spice/test_SpiceParser.py index 40b59c8a0..83aa3acd2 100644 --- a/unit-test/Spice/test_SpiceParser.py +++ b/unit-test/Spice/test_SpiceParser.py @@ -426,6 +426,7 @@ def test_library(self): simulator.transient(1e-9, 1e-3) def test_working_dir(self): + from PySpice.Spice.Xyce.RawFile import RawFile circuit = Circuit('Test working directory') circuit.PulseVoltageSource('hlp', 1, @@ -449,6 +450,8 @@ def test_working_dir(self): 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=""" From ccbcb2e1168a9f2a1445eff696a23b47dfea7495 Mon Sep 17 00:00:00 2001 From: jmgc Date: Fri, 3 Mar 2023 16:59:40 +0100 Subject: [PATCH 081/134] Corrected different parser issues Mainly management of differences between xyce and ngspice formats. --- PySpice/Logging/Logging.py | 2 +- PySpice/Spice/BasicElement.py | 76 +++++++++++++++------- PySpice/Spice/Netlist.py | 98 ++++++++++++++--------------- PySpice/Spice/RawFile.py | 2 + PySpice/Spice/Simulation.py | 2 +- requirements.txt | 2 +- unit-test/Spice/test_SpiceParser.py | 5 ++ 7 files changed, 112 insertions(+), 75 deletions(-) diff --git a/PySpice/Logging/Logging.py b/PySpice/Logging/Logging.py index eab4a4f1e..d5919bd59 100644 --- a/PySpice/Logging/Logging.py +++ b/PySpice/Logging/Logging.py @@ -44,7 +44,7 @@ def setup_logging(application_name='PySpice', """ logging_config_file_name = ConfigInstall.Logging.find(config_file) - logging_config = yaml.load(open(logging_config_file_name, 'r')) + logging_config = yaml.safe_load(open(logging_config_file_name, 'r')) if ConfigInstall.OS.on_linux: # Fixme: \033 is not interpreted in YAML diff --git a/PySpice/Spice/BasicElement.py b/PySpice/Spice/BasicElement.py index c7d6a93c8..b14405f04 100644 --- a/PySpice/Spice/BasicElement.py +++ b/PySpice/Spice/BasicElement.py @@ -94,6 +94,7 @@ +--------------+------------------------------------------------------+ """ +import warnings #################################################################################################### @@ -262,7 +263,7 @@ class Resistor(DipoleElement): noisy = BoolKeyParameter('noisy') tc = FloatPairKeyParameter('tc') tc1 = FloatPairKeyParameter('tc1') - tc2 = FloatKeyParameter('tc1') + tc2 = FloatKeyParameter('tc2') #################################################################################################### @@ -438,7 +439,7 @@ class Capacitor(DipoleElement): initial_condition = FloatKeyParameter('ic') tc = FloatPairKeyParameter('tc') tc1 = FloatPairKeyParameter('tc1') - tc2 = FloatKeyParameter('tc1') + tc2 = FloatKeyParameter('tc2') #################################################################################################### @@ -611,7 +612,7 @@ class Inductor(DipoleElement): initial_condition = FloatKeyParameter('ic') tc = FloatPairKeyParameter('tc') tc1 = FloatPairKeyParameter('tc1') - tc2 = FloatKeyParameter('tc1') + tc2 = FloatKeyParameter('tc2') #################################################################################################### @@ -648,7 +649,7 @@ class BehavioralInductor(DipoleElement): inductance_expression = ExpressionPositionalParameter(position=0, key_parameter=False) tc = FloatPairKeyParameter('tc') tc1 = FloatPairKeyParameter('tc1') - tc2 = FloatKeyParameter('tc1') + tc2 = FloatKeyParameter('tc2') #################################################################################################### @@ -1016,35 +1017,64 @@ class BehavioralSource(DipoleElement): ############################################## def __str__(self): - + 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('tnom')) + + Symbol(self.tc[1]) * (Symbol(temp) - Symbol('tnom'))**2) + else: + if self.tc1 is not None: + temp_expression = (1 + Symbol(self.tc1) * (Symbol(temp) - Symbol('tnom'))) + if self.tc2 is not None: + temp_expression = (1 + Symbol(self.tc2) * (Symbol(temp) - Symbol('tnom'))**2) if self.current_expression is not None: - if isinstance(self.current_expression, Expression): - expression = ' i={%s}' % self.current_expression + if temp_expression is not None: + if isinstance(self.current_expression, Expression): + expression = ' i={%s}' % (self.current_expression * temp_expression) + else: + warnings.WarningMessage('Unable to include associated temperature behaviour') else: - expression = ' i=%s' % self.current_expression + if isinstance(self.current_expression, Expression): + expression = ' i={%s}' % self.current_expression + else: + expression = ' i=%s' % self.current_expression elif self.voltage_expression is not None: - if isinstance(self.voltage_expression, Expression): - expression = ' v={%s}' % self.voltage_expression + if temp_expression is not None: + if isinstance(self.voltage_expression, Expression): + expression = ' v={%s}' % (self.voltage_expression * temp_expression) + else: + warnings.WarningMessage('Unable to include associated temperature behaviour') else: - expression = ' v=%s' % self.voltage_expression + if isinstance(self.voltage_expression, Expression): + expression = ' v={%s}' % self.voltage_expression + else: + expression = ' v=%s' % self.voltage_expression else: expression = '' spice_element += expression - if self.tc is not None: - spice_element += ' tc1=%f,%f' % self.tc + 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.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 - if self.smoothbsrc is not None: - spice_element += ' smoothbsrc=%s' % self.smoothbsrc + if self.smoothbsrc is not None: + spice_element += ' smoothbsrc=%s' % self.smoothbsrc return spice_element #################################################################################################### diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index 8392c8dbc..286c2daa3 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -99,10 +99,10 @@ def __init__(self, **kwargs): _module_logger = logging.getLogger(__name__) + #################################################################################################### class DeviceModel: - """This class implements a device model. Ngspice model types: @@ -158,7 +158,6 @@ def __init__(self, name, modele_type, **parameters): ############################################## def clone(self): - # Fixme: clone parameters ??? return self.__class__(self._name, self._model_type, self._parameters) @@ -193,7 +192,7 @@ def __getitem__(self, name): ############################################## - #def __getattr__(self, name): + # def __getattr__(self, name): # return super(DeviceModel, self).__getattr__('_parameter')[name] ############################################## @@ -206,16 +205,15 @@ def __repr__(self): def __str__(self): return ".model {0._name} {0._model_type} ({1})".format(self, join_dict(self._parameters)) + #################################################################################################### class PinDefinition: - """This class defines a pin of an element.""" ############################################## def __init__(self, position, name=None, alias=None, optional=False): - self._position = position self._name = name self._alias = alias @@ -245,6 +243,7 @@ def alias(self): def optional(self): return self._optional + #################################################################################################### class OptionalPin: @@ -256,15 +255,15 @@ def __init__(self, name): def name(self): return self._name + #################################################################################################### def schematic(**kwargs): - return kwargs + return kwargs 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. @@ -275,7 +274,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 @@ -307,7 +305,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*. @@ -321,11 +318,11 @@ def add_current_probe(self, circuit): self._node = '_'.join((self._element.name, self._name)) circuit.V(self._node, node, self._node, '0') + #################################################################################################### 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. @@ -385,23 +382,24 @@ def __new__(cls, class_name, base_classes, namespace): # Implement alias for parameters: spice name -> parameter namespace['_spice_to_parameters_'] = { - parameter.spice_name:parameter + parameter.spice_name: parameter for parameter in namespace['_optional_parameters_'].values()} 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)) + if parameter.spice_name in namespace and parameter.spice_name != parameter.attribute_name: + _module_logger.error("Spice parameter '{}' clash with namespace, attribute name: '{}'".format(parameter.spice_name, parameter.attribute_name)) # Initialise pins def make_pin_getter(position): def getter(self): return self._pins[position] + return getter def make_optional_pin_getter(position): def getter(self): return self._pins[position] if position < len(self._pins) else None + return getter if '_pins_' in namespace and namespace['_pins_'] is not None: @@ -460,7 +458,7 @@ def __init__(cls, class_name, base_classes, namespace): @property def number_of_pins(self): - #! Fixme: many pins ??? + # ! Fixme: many pins ??? number_of_pins = len(self._pins_) if self._number_of_optional_pins_: return slice(number_of_pins - self._number_of_optional_pins_, number_of_pins + 1) @@ -487,11 +485,11 @@ def parameters_from_args(self): def spice_to_parameters(self): return self._spice_to_parameters_ + #################################################################################################### class Element(metaclass=ElementParameterMetaClass): - """This class implements a base class for an element. It use a metaclass machinery for the declaration of the parameters. @@ -518,14 +516,14 @@ def __init__(self, netlist, name, *args, **kwargs): self.enabled = True parent = netlist self._parameters = kwargs - #self._pins = kwargs.pop('pins',()) + # self._pins = kwargs.pop('pins',()) # Process remaining args if len(self._positional_parameters_) + self._number_of_optional_pins_ < 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: - read = [False]*len(args) + 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]) @@ -536,8 +534,8 @@ def __init__(self, netlist, name, *args, **kwargs): self.raw_spice = value else: if (key in self._positional_parameters_ or - key in self._optional_parameters_ or - key in self._spice_to_parameters_): + key in self._optional_parameters_ or + key in self._spice_to_parameters_): setattr(self, key, value) else: for parameter in self._optional_parameters_: @@ -638,7 +636,7 @@ def parameter_iterator(self): positional_parameters = OrderedDict() # Fixme: .parameters ??? if len(self._positional_parameters_) > 0: - read = [False]*len(self._positional_parameters_) + 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): @@ -670,10 +668,10 @@ def __str__(self): """ Return the SPICE element definition. """ return join_list((self.format_node_names(), self.format_spice_parameters(), self.raw_spice)) + #################################################################################################### class AnyPinElement(Element): - _pins_ = () ############################################## @@ -683,6 +681,7 @@ def copy_to(self, netlist): super().copy_to(element) return element + #################################################################################################### class FixedPinElement(Element): @@ -697,7 +696,7 @@ def __init__(self, netlist, name, *args, **kwargs): pin_definition_nodes = [] number_of_args = len(args) if number_of_args: - expected_number_of_pins = self.__class__.number_of_pins # Fixme: + expected_number_of_pins = self.__class__.number_of_pins # Fixme: if isinstance(expected_number_of_pins, slice): expected_number_of_pins = expected_number_of_pins.start if number_of_args < expected_number_of_pins: @@ -723,7 +722,6 @@ def __init__(self, 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) ############################################## @@ -733,10 +731,10 @@ def copy_to(self, netlist): super().copy_to(element) return element + #################################################################################################### class NPinElement(Element): - _pins_ = '*' ############################################## @@ -770,10 +768,10 @@ def copy_to(self, netlist): super().copy_to(element) return element + #################################################################################################### class Node: - """This class implements a node in the circuit. It stores a reference to the pins connected to the node. @@ -813,7 +811,7 @@ def name(self): @name.setter def name(self, value): - self._netlist._update_node_name(self, value) # update nodes dict + self._netlist._update_node_name(self, value) # update nodes dict self._name = value @property @@ -849,10 +847,10 @@ def connect(self, pin): def disconnect(self, pin): self._pins.remove(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. @@ -870,16 +868,17 @@ def __init__(self): self._graph = networkx.Graph() 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._subcircuits = OrderedDict() # to keep the declaration order + self._elements = OrderedDict() # to keep the declaration order self._models = OrderedDict() - self._includes = [] # .include + self._includes = [] # .include self._used_models = set() self._used_subcircuits = set() self._parameters = OrderedDict() self.raw_spice = '' + self.spice_sim = '' ############################################## @@ -953,7 +952,7 @@ def __getitem__(self, attribute_name): elif attr in self._nodes: return self._nodes[attr] else: - raise IndexError(attr) # KeyError + raise IndexError(attr) # KeyError def _find_subcircuit(self, name): name_low = name.lower() @@ -1068,7 +1067,7 @@ def subcircuit(self, subcircuit): # Fixme: subcircuit is a class self._subcircuits[str(subcircuit.name).lower()] = subcircuit - subcircuit.parent=self + subcircuit.parent = self for model in subcircuit._used_models: if model not in subcircuit._models: self._used_models.add(model) @@ -1094,7 +1093,7 @@ def __str__(self): netlist += os.linesep if self._subcircuits: subcircuits = self._str_subcircuits() - netlist += subcircuits# before elements + netlist += subcircuits # before elements netlist += os.linesep netlist += self._str_elements() + os.linesep @@ -1170,10 +1169,10 @@ def include(self, path, entry=None): else: self._logger.warn("Duplicated include") + #################################################################################################### class SubCircuit(Netlist): - """This class implements a sub-cicuit netlist.""" ############################################## @@ -1265,7 +1264,6 @@ def check_nodes(self): if not_connected_nodes: raise NameError("SubCircuit Nodes {} are not connected".format(not_connected_nodes)) - ############################################## def __str__(self): @@ -1284,10 +1282,10 @@ def __str__(self): netlist += '.ends ' + self._name + os.linesep return netlist + #################################################################################################### class Library(Netlist): - """This class implements a library netlist.""" ############################################## @@ -1298,7 +1296,6 @@ def __init__(self, entry): ############################################## def clone(self, entry=None): - if entry is None: entry = self._entry @@ -1314,7 +1311,6 @@ def entry(self): ############################################## def __str__(self): - """Return the formatted library definition.""" netlist = '.lib ' + self._entry + os.linesep @@ -1322,10 +1318,10 @@ def __str__(self): netlist += '.endl ' + self._entry + os.linesep return netlist + #################################################################################################### class SubCircuitFactory(SubCircuit): - __name__ = None _nodes_ = None _pins_ = None @@ -1335,10 +1331,10 @@ class SubCircuitFactory(SubCircuit): def __init__(self, **kwargs): super().__init__(self.__name__, *self._nodes_, **kwargs) + #################################################################################################### class Circuit(Netlist): - """This class implements a cicuit netlist. To get the corresponding Spice netlist use:: @@ -1354,9 +1350,9 @@ class Circuit(Netlist): ############################################## def __init__(self, title, - ground=0, # Fixme: gnd = 0 + ground=0, # Fixme: gnd = 0 global_nodes=(), - ): + ): super().__init__() @@ -1364,8 +1360,8 @@ def __init__(self, title, title = "" self.title = str(title) self._ground = ground - self._global_nodes = set(global_nodes) # .global - self._data = {} # .data + self._global_nodes = set(global_nodes) # .global + self._data = {} # .data # Fixme: not implemented # .csparam @@ -1400,14 +1396,14 @@ def clone(self, title=None): ############################################## - 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(): @@ -1416,6 +1412,9 @@ def str(self, simulator=None): netlist = self._str_title() netlist += os.linesep # netlist += self._str_includes(simulator) + for sub_circuit in self.subcircuits: + sub_circuit.spice_sim = self.spice_sim + if self._global_nodes: netlist += self._str_globals() + os.linesep netlist += super().__str__() @@ -1461,7 +1460,7 @@ def _str_globals(self): ############################################## def __str__(self): - return self.str(simulator=None) + return self.str(spice_sim=None) ############################################## @@ -1473,6 +1472,7 @@ def str_end(self): def simulator(self, *args, **kwargs): return CircuitSimulator.factory(self, *args, **kwargs) + #################################################################################################### class Comment: diff --git a/PySpice/Spice/RawFile.py b/PySpice/Spice/RawFile.py index d2e78fbdb..40d75940e 100644 --- a/PySpice/Spice/RawFile.py +++ b/PySpice/Spice/RawFile.py @@ -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: ' diff --git a/PySpice/Spice/Simulation.py b/PySpice/Spice/Simulation.py index 5119afcca..4f23c4a02 100644 --- a/PySpice/Spice/Simulation.py +++ b/PySpice/Spice/Simulation.py @@ -633,7 +633,7 @@ def str_options(self, unit=True): 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 and len(self._initial_condition) > 0: netlist += '.ic ' + join_dict(self._initial_condition) + os.linesep diff --git a/requirements.txt b/requirements.txt index 4c5cf42c4..2edf30b17 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,5 +9,5 @@ regex~=2021.4.4 ply~=3.11 networkx~=2.5.1 TatSu~=5.6.1 -scipy~=1.6.3 +scipy~=1.10.0 setuptools~=56.2.0 diff --git a/unit-test/Spice/test_SpiceParser.py b/unit-test/Spice/test_SpiceParser.py index 83aa3acd2..cb02d7e47 100644 --- a/unit-test/Spice/test_SpiceParser.py +++ b/unit-test/Spice/test_SpiceParser.py @@ -87,9 +87,13 @@ .MODEL QPWR NPN +.MODEL Q2N2222 NPN + *Another note Q2 10 2 9 PNP1 +Q2N2222 Nc Nb Ne Q2N2222 + Q8 Coll Base Emit VBIC13MODEL3 temp=0 Q9 Coll Base Emit Subst DT VBIC13MODEL4 Q10 Coll Base Emit Subst DT HICUMMMODEL1 @@ -560,6 +564,7 @@ def test_boolean(self): def test_subcircuit(self): print(os.getcwd()) circuit = Circuit('MOS Driver') + circuit.spice_sim = 'xyce' circuit.include(os.path.join(os.getcwd(), '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) From 2effc3325393e907dc5acadaa085051b11b31d39 Mon Sep 17 00:00:00 2001 From: jmgc Date: Fri, 3 Mar 2023 17:02:57 +0100 Subject: [PATCH 082/134] Update project --- .idea/PySpice.iml | 17 + .idea/dataSources.local.xml | 4 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 10 + .idea/modules.xml | 8 + .idea/other.xml | 6 + .idea/vcs.xml | 6 + .idea/workspace.xml | 700 ++++++++++++++++++ 8 files changed, 757 insertions(+) create mode 100644 .idea/PySpice.iml create mode 100644 .idea/dataSources.local.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/other.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml 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..0bae31380 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,700 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { + "keyToString": { + "WebServerToolWindowFactoryState": "false", + "last_opened_file_path": "/Users/chema/Workspace/PySpice", + "node.js.detected.package.eslint": "true", + "node.js.detected.package.tslint": "true", + "node.js.selected.package.eslint": "(autodetect)", + "node.js.selected.package.tslint": "(autodetect)", + "settings.editor.selected.configurable": "com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable", + "vue.rearranger.settings.migration": "true" + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1585652882548 + + + 1585652882548 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1621316590920 + + + + 1621316590920 + + + 1621316929764 + + + + 1621316929764 + + + 1621317272814 + + + + 1621317272814 + + + 1621317474494 + + + + 1621317474494 + + + 1621529024027 + + + + 1621529024027 + + + 1621601315502 + + + + 1621601315502 + + + 1621854642253 + + + + 1621854642253 + + + 1621922186970 + + + + 1621922186970 + + + 1621923696258 + + + + 1621923696258 + + + 1622109826837 + + + + 1622109826837 + + + 1622124559026 + + + + 1622124559026 + + + 1622361004647 + + + + 1622361004647 + + + 1622365436460 + + + + 1622365436460 + + + 1622367433876 + + + + 1622367433876 + + + 1622385838407 + + + + 1622385838407 + + + 1622397395406 + + + + 1622397395406 + + + 1622402434932 + + + + 1622402434932 + + + 1622466526664 + + + + 1622466526664 + + + 1622487755158 + + + + 1622487755158 + + + 1622489707241 + + + + 1622489707241 + + + 1622489751289 + + + + 1622489751289 + + + 1622490875363 + + + + 1622490875363 + + + 1622539484037 + + + + 1622539484037 + + + 1622545223544 + + + + 1622545223544 + + + 1622560988342 + + + + 1622560988342 + + + 1622562746484 + + + + 1622562746484 + + + 1622565898932 + + + + 1622565898932 + + + 1622566122148 + + + + 1622566122148 + + + 1622568968775 + + + + 1622568968775 + + + 1622642980193 + + + + 1622642980193 + + + 1622986018906 + + + + 1622986018906 + + + 1623048518199 + + + + 1623048518199 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + file://$PROJECT_DIR$/PySpice/Unit/Unit.py + 1532 + + + + file://$PROJECT_DIR$/PySpice/Probe/WaveForm.py + 148 + + + + file://$PROJECT_DIR$/PySpice/Spice/Netlist.py + 510 + + + + file://$PROJECT_DIR$/PySpice/Spice/BasicElement.py + 1026 + + + + file://$PROJECT_DIR$/PySpice/Unit/Unit.py + 98 + + + + file://$PROJECT_DIR$/unit-test/Unit/test_Units.py + 22 + + + + file://$PROJECT_DIR$/PySpice/Spice/Netlist.py + 671 + + + + file://$PROJECT_DIR$/PySpice/Spice/BasicElement.py + 175 + + + + file://$PROJECT_DIR$/PySpice/Spice/Library.py + 116 + + + + 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 + 990 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From b91458a26d21bb1c31ea91694196b50921e9e16d Mon Sep 17 00:00:00 2001 From: jmgc Date: Sun, 5 Mar 2023 14:25:24 +0100 Subject: [PATCH 083/134] Solved the issues associated with the parser The different issues that came from the parser, like bjt, diode and voltage controlled sources have been solved. The codes has also been updated to the latest from the original repository. --- .gitignore | 3 + .idea/workspace.xml | 220 ++- PySpice/Probe/WaveForm.py | 19 +- PySpice/Spice/BasicElement.py | 192 +- PySpice/Spice/EBNFExpressionParser.py | 459 +++++ .../{EBNFParser.py => EBNFSpiceParser.py} | 93 +- PySpice/Spice/ElementParameter.py | 42 +- PySpice/Spice/ExpressionGrammar.py | 1709 +++++++++++++++++ PySpice/Spice/ExpressionModel.py | 202 ++ PySpice/Spice/HighLevelElement.py | 245 ++- PySpice/Spice/Netlist.py | 485 +++-- PySpice/Spice/Simulation.py | 45 +- PySpice/Spice/SpiceGrammar.py | 161 +- PySpice/Spice/SpiceModel.py | 19 +- PySpice/Spice/expressiongrammar.ebnf | 649 +++++++ PySpice/Spice/spicegrammar.ebnf | 37 +- PySpice/Tools/StringTools.py | 4 +- PySpice/Unit/Unit.py | 14 +- unit-test/Spice/test_BasicElement.py | 10 +- unit-test/Spice/test_ExpressionParser.py | 58 + unit-test/Spice/test_HighLevelElement.py | 10 +- unit-test/Spice/test_Netlist.py | 24 +- unit-test/Spice/test_SpiceParser.py | 48 +- unit-test/SpiceParser/mosdriver.lib | 2 +- unit-test/SpiceParser/test_SpiceParser.py | 18 +- 25 files changed, 4271 insertions(+), 497 deletions(-) create mode 100644 PySpice/Spice/EBNFExpressionParser.py rename PySpice/Spice/{EBNFParser.py => EBNFSpiceParser.py} (96%) create mode 100644 PySpice/Spice/ExpressionGrammar.py create mode 100644 PySpice/Spice/ExpressionModel.py create mode 100644 PySpice/Spice/expressiongrammar.ebnf create mode 100644 unit-test/Spice/test_ExpressionParser.py diff --git a/.gitignore b/.gitignore index 2ad721779..f5e31e848 100644 --- a/.gitignore +++ b/.gitignore @@ -147,3 +147,6 @@ examples/c-examples/ngspice_cb/ng_shared_test_sl_v/ examples/c-examples/ngspice_cb/ng_shared_test_v/ Spice64_dll/ +unit-test/Spice/input.cir +unit-test/Spice/input.cir.ic +unit-test/Spice/output.raw diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 0bae31380..9c5f63987 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,7 +4,28 @@ - + + + + + + + + + + + + + + + + + + + + + + @@ -40,17 +61,22 @@ { "keyToString": { "WebServerToolWindowFactoryState": "false", - "last_opened_file_path": "/Users/chema/Workspace/PySpice", + "last_opened_file_path": "/Users/chema/Workspace/PySpice/unit-test/Spice", "node.js.detected.package.eslint": "true", + "node.js.detected.package.standard": "true", "node.js.detected.package.tslint": "true", "node.js.selected.package.eslint": "(autodetect)", + "node.js.selected.package.standard": "", "node.js.selected.package.tslint": "(autodetect)", - "settings.editor.selected.configurable": "com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable", + "nodejs_package_manager_path": "npm", + "settings.editor.selected.configurable": "Errors", "vue.rearranger.settings.migration": "true" } } + + @@ -58,7 +84,7 @@ - + @@ -235,6 +261,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -251,13 +309,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -333,6 +461,8 @@ + + 1621316590920 @@ -597,47 +727,27 @@ file://$PROJECT_DIR$/PySpice/Unit/Unit.py - 1532 + 1335 - - file://$PROJECT_DIR$/PySpice/Probe/WaveForm.py - 148 - - file://$PROJECT_DIR$/PySpice/Spice/Netlist.py - 510 + 451 file://$PROJECT_DIR$/PySpice/Spice/BasicElement.py - 1026 + 923 - - file://$PROJECT_DIR$/PySpice/Unit/Unit.py - 98 - - file://$PROJECT_DIR$/unit-test/Unit/test_Units.py 22 - - file://$PROJECT_DIR$/PySpice/Spice/Netlist.py - 671 - - - - file://$PROJECT_DIR$/PySpice/Spice/BasicElement.py - 175 - - file://$PROJECT_DIR$/PySpice/Spice/Library.py - 116 + 106 @@ -662,9 +772,49 @@ file://$PROJECT_DIR$/PySpice/Spice/BasicElement.py - 990 + 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 + + @@ -672,26 +822,32 @@ - + + + + + + - + + - + diff --git a/PySpice/Probe/WaveForm.py b/PySpice/Probe/WaveForm.py index ca92d15f2..d0ea9e6dd 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 diff --git a/PySpice/Spice/BasicElement.py b/PySpice/Spice/BasicElement.py index f4f943a60..08377c5bc 100644 --- a/PySpice/Spice/BasicElement.py +++ b/PySpice/Spice/BasicElement.py @@ -101,7 +101,8 @@ 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 .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,20 @@ def __init__(self, netlist, name, subcircuit_name, *nodes, **parameters): # self.optional_parameters[key] = parameter # setattr(self, key, parameter) + subcircuit_name = subcircuit_name.lower() + subcircuit = netlist._find_subcircuit(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 +201,7 @@ def format_spice_parameters(self): spice_parameters = super().format_spice_parameters() if self.parameters: - spice_parameters += ' ' + join_dict(self.parameters) + spice_parameters += ' params: ' + join_dict(self.parameters) return spice_parameters @@ -246,13 +259,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) noisy = BoolKeyParameter('noisy') + tc = FloatPairKeyParameter('tc') + tc1 = FloatPairKeyParameter('tc1') + tc2 = FloatKeyParameter('tc2') #################################################################################################### @@ -318,8 +335,8 @@ 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) @@ -364,6 +381,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 +436,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) initial_condition = FloatKeyParameter('ic') + tc = FloatPairKeyParameter('tc') + tc1 = FloatPairKeyParameter('tc1') + tc2 = FloatKeyParameter('tc2') #################################################################################################### @@ -487,8 +508,8 @@ 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') @@ -530,6 +551,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 +608,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) initial_condition = FloatKeyParameter('ic') + tc = FloatPairKeyParameter('tc') + tc1 = FloatPairKeyParameter('tc1') + tc2 = FloatKeyParameter('tc2') #################################################################################################### @@ -628,7 +653,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 +684,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 +700,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) #################################################################################################### @@ -785,6 +802,18 @@ class VoltageSource(DipoleElement): # Fixme: ngspice manual doesn't describe well the syntax 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 __init__(self, netlist, name, *args, **kwargs): + number_of_pins = len(self.PINS) + arguments = args + if len(args) > number_of_pins: + arguments = list(args[:number_of_pins]) + arguments.append(join_list(args[number_of_pins:])) + + super().__init__(netlist, name, *arguments, **kwargs) #################################################################################################### @@ -811,6 +840,9 @@ class CurrentSource(DipoleElement): # Fixme: ngspice manual doesn't describe well the syntax 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) #################################################################################################### @@ -983,10 +1015,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) + 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 +1113,9 @@ class NonLinearVoltageSource(DipoleElement): ALIAS = 'NonLinearVoltageSource' PREFIX = 'E' - # Fixme: - VALID_KWARGS = ('expression', 'table') + value = ExpressionKeyParameter('value') + table = ExpressionKeyParameter('table') + smoothbsrc = ExpressionKeyParameter('smoothbsrc') ############################################## @@ -1205,7 +1304,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') @@ -1217,6 +1316,18 @@ class BipolarJunctionTransistor(FixedPinElement): temperature = FloatKeyParameter('temp', unit=U_Degree) device_temperature = FloatKeyParameter('dtemp', unit=U_Degree) + def format_node_names(self): + fixed_pins = len(self.PINS) - self._number_of_optional_pins_ + nodes = self._pins[:fixed_pins] + for node in self._pins[fixed_pins:]: + if str(node).lower() != 'dt': + nodes.append('[{}]'.format(node)) + else: + nodes.append(node) + + """ Return the formatted list of nodes. """ + return join_list((self.name, join_list(nodes))) + #################################################################################################### # # JFETs @@ -1509,12 +1620,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 +1802,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..e5a96c4f2 --- /dev/null +++ b/PySpice/Spice/EBNFExpressionParser.py @@ -0,0 +1,459 @@ +import logging +import os +import csv + +from unicodedata import normalize +from PySpice.Unit.Unit import UnitValue, ZeroPower, PrefixedUnit, UnitMetaclass +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 == "-": + return Neg(operator) + else: + return Pos(operator) + else: + 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 = ExpressionModelWalker._to_number(real) + else: + value = complex(float(real), float(imag)) + if node.unit is not None: + unit = self.walk(node.unit, data) + 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 = self.walk(node.value, data) + scale = node.scale + if scale is not None: + scale = normalize("NFKD", scale).lower() + result = UnitValue(self._suffix[scale], value) + else: + result = UnitValue(PrefixedUnit(ZeroPower()), value) + return ExpressionModelWalker._to_number(result) + + 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): + return node.ast + + 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_Command(self, node, data): + return self.walk(node.ast, data) + + def walk_NetlistCmds(self, node, data): + return self.walk(node.ast, data) + + def walk_NetNode(self, node, data): + return node.node + + 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): + 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/EBNFParser.py b/PySpice/Spice/EBNFSpiceParser.py similarity index 96% rename from PySpice/Spice/EBNFParser.py rename to PySpice/Spice/EBNFSpiceParser.py index a254a7de1..fc77ddd07 100644 --- a/PySpice/Spice/EBNFParser.py +++ b/PySpice/Spice/EBNFSpiceParser.py @@ -7,12 +7,8 @@ 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 .ElementParameter import FlagParameter from .Netlist import (Circuit, - DeviceModel, - Library, - SubCircuit, - Comment) + SubCircuit) from .BasicElement import (BehavioralSource, BipolarJunctionTransistor, Capacitor, @@ -822,8 +818,6 @@ def walk_Circuit(self, node, data): def walk_BJT(self, node, data): device = self.walk(node.dev, data) - args = self.walk(node.args, data) - l_args = len(args) kwargs = {} collector = self.walk(node.collector, data) base = self.walk(node.base, data) @@ -833,57 +827,24 @@ def walk_BJT(self, node, data): base, emitter ] - substrate = None if node.substrate is not None: - substrate = node.substrate + substrate = self.walk(node.substrate, data) nodes.append(substrate) - area = None + if node.thermal is not None: + thermal = node.thermal + nodes.append(thermal) if node.area is not None: area = self.walk(node.area, data) - if l_args == 0: - raise ValueError("The device {} has no model".format(node.dev)) - elif l_args == 1: - model_name = args[0] - elif l_args == 2: - if area is None: - try: - area = SpiceModelWalker._to_number(args[1]) - kwargs["area"] = area - model_name = args[0] - except ValueError: - pass - if area is None: - thermal = args[0] - nodes.append(thermal) - model_name = args[1] - elif l_args == 3: - if area is None: - try: - area = SpiceModelWalker._to_number(args[2]) - kwargs["area"] = area - model_name = args[1] - thermal = args[0] - nodes.append(thermal) - except ValueError: - pass - if area is None and substrate is None: - substrate = args[0] - nodes.append(substrate) - thermal = args[1] - nodes.append(thermal) - model_name = args[2] - else: - raise ValueError("Present device not compatible with BJT definition: {}".format(node.dev)) - else: - raise ValueError("Present device not compatible with BJT definition: {}".format(node.dev)) + 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) - kwargs["model"] = model_name - data._present._required_models.add(model_name.lower()) - data._present.append( ElementStatement( BipolarJunctionTransistor, @@ -894,7 +855,7 @@ def walk_BJT(self, node, data): ) def walk_SubstrateNode(self, node, data): - return node.substrate + return node.text[1:-1] def walk_Capacitor(self, node, data): device = self.walk(node.dev, data) @@ -1321,16 +1282,18 @@ def walk_Switch(self, node, data): def walk_VoltageControlledCurrentSource(self, node, data): device = self.walk(node.dev, data) - if (node.controller is None and node.control_positive is None and - node.control_negative is None and node.transconductance is None): + 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) - ctrl_p = self.walk(node.control_positive, data) - ctrl_n = self.walk(node.control_negative, 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) @@ -1350,16 +1313,16 @@ def walk_VoltageControlledCurrentSource(self, node, data): def walk_VoltageControlledVoltageSource(self, node, data): device = self.walk(node.dev, data) - if (node.controller is None and node.control_positive is None and - node.control_negative 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.gain, data) - ctrl_p = self.walk(node.control_positive, data) - ctrl_n = self.walk(node.control_negative, data) + 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) @@ -1812,6 +1775,12 @@ def walk_GenericExpression(self, node, data): else: return self.walk(node.value, data) + 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_BracedExpression(self, node, data): return self.walk(node.ast, data) @@ -1867,7 +1836,7 @@ def walk_ConditionalFactor(self, node, data): if node.boolean is None: return self.walk(node.expr, data) else: - return node.boolean.lower == "true" + return node.boolean.lower() == "true" def walk_Expression(self, node, data): if node.term is None: @@ -2155,7 +2124,7 @@ def _regenerate(): 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(); + grammar = grammar_ifile.read() with open(grammar_file, "w") as grammar_ofile: model = compile(str(grammar)) grammar_ofile.write(str(model)) diff --git a/PySpice/Spice/ElementParameter.py b/PySpice/Spice/ElementParameter.py index 640c00b1c..2b6518639 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 +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,21 @@ 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 + elif 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 + elif type(value) is str: + value = ExpressionParser.parse(value) + return self._unit.new_value(value) + #################################################################################################### class PositionalElementParameter(ParameterDescriptor): @@ -174,7 +192,9 @@ class ExpressionPositionalParameter(PositionalElementParameter): ############################################## def validate(self, value): - return str(value) + if isinstance(value, Expression): + return value + return ExpressionParser.parse(value) #################################################################################################### @@ -192,11 +212,7 @@ def __init__(self, position, unit=None, **kwargs): ############################################## def validate(self, value): - - if isinstance(value, type(self._unit)): - return value - else: - return self._unit.new_value(value) + return self._validate_float(value) #################################################################################################### @@ -328,7 +344,9 @@ class ExpressionKeyParameter(KeyValueParameter): ############################################## def validate(self, value): - return str(value) + if isinstance(value, Expression): + return value + return ExpressionParser.parse(value) #################################################################################################### @@ -346,7 +364,7 @@ def __init__(self, spice_name, unit=None, **kwargs): ############################################## def validate(self, value): - return float(value) + return self._validate_float(value) #################################################################################################### @@ -359,7 +377,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 +397,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..7a3e1f9d6 --- /dev/null +++ b/PySpice/Spice/ExpressionGrammar.py @@ -0,0 +1,1709 @@ +#!/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.util import re, generic_main # noqa + + +KEYWORDS = {} # type: ignore + + +class ExpressionBuffer(Buffer): + def __init__( + self, + text, + whitespace=None, + nameguard=None, + comments_re=None, + eol_comments_re=None, + ignorecase=True, + namechars='', + **kwargs + ): + super().__init__( + text, + whitespace=whitespace, + nameguard=nameguard, + comments_re=comments_re, + eol_comments_re=eol_comments_re, + ignorecase=ignorecase, + namechars=namechars, + **kwargs + ) + + +class ExpressionParser(Parser): + def __init__( + self, + whitespace=None, + nameguard=None, + comments_re=None, + eol_comments_re=None, + ignorecase=True, + left_recursion=True, + parseinfo=True, + keywords=None, + namechars='', + tokenizercls=ExpressionBuffer, + **kwargs + ): + if keywords is None: + keywords = KEYWORDS + super().__init__( + whitespace=whitespace, + nameguard=nameguard, + comments_re=comments_re, + eol_comments_re=eol_comments_re, + ignorecase=ignorecase, + left_recursion=left_recursion, + parseinfo=parseinfo, + keywords=keywords, + namechars=namechars, + tokenizercls=tokenizercls, + **kwargs + ) + + @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: ' + ' ' + ' ' + ) + self._define( + ['braced', 'value'], + [] + ) + + @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_() + with self._option(): + self._expression_() + self.name_last_node('@') + self._error( + 'expecting one of: ' + "'{' " + ) + 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: ' + ' ' + ' ' + ) + self._define( + ['term', 'ternary'], + [] + ) + + @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') + self._define( + ['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( + ['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( + ['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( + ['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') + with self._option(): + self._conditional_factor_() + self.name_last_node('factor') + self._error( + 'expecting one of: ' + ' ' + ' ' + ' ' + '' + ) + self._define( + ['factor', 'left', 'op', 'right', 'sep'], + [] + ) + + @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_() + with self._option(): + self._boolean_() + self.name_last_node('boolean') + self._error( + 'expecting one of: ' + "'(' 'TRUE' 'FALSE' " + ) + self._define( + ['boolean', 'expr', 'sep'], + [] + ) + + @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( + ['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( + ['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( + ['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_() + 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-Z0-" + '9_:`@#\\.\\$]*[a-zA-Z0-9_`@#\\.\\$] [a-zA-Z]' + ' ' + ) + self._define( + ['factor', 'sep', 'variable'], + [] + ) + + @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_() + with self._option(): + self._value_() + self.name_last_node('@') + self._error( + 'expecting one of: ' + "'(' " + '' + ) + self._define( + ['sep'], + [] + ) + + @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: ' + "'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' " + "'atan2' 'ddx' 'agauss' 'gauss' 'if'" + " 'limit' 'min' 'max' 'pwrs'" + "'pow' 'pwr' 'sign' 'rand'" + "'aunif' 'unif' 'i' '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: ' + "'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'" + ) + 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: ' + "'min' 'max' 'pwrs' 'pow' 'pwr' '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._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: ' + "'time' 'temper' 'temp' 'freq' 'vt' 'pi'" + ) + + @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') + 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') + self._define( + ['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') + 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._error( + 'expecting one of: ' + '[\\+\\-]?(([0-9]+(\\.[0-9]*)?)|(\\.[0-' + '9]+))([eE][\\-\\+]?[0-9]{1,3})?' + ' [\\+\\-]?[0-9]+ ' + ) + self._define( + ['scale', 'value'], + [] + ) + + @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: ' + "'TRUE' 'FALSE'" + ) + + @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( + ['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-Z0-9_:`@#\\.\\$\\/]*[a-' + 'zA-Z0-9_`@#\\.\\$] [a-zA-Z_`@#\\$]' + ) + + @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-Z0-9_:`@#\\.\\$]*[a-' + 'zA-Z0-9_`@#\\.\\$] [a-zA-Z]' + ) + + @tatsumasu() + def _end_sep_(self): # noqa + with self._choice(): + with self._option(): + self._cmd_net_sep_() + self.name_last_node('@') + + def block1(): + self._st_() + self._closure(block1) + with self._option(): + + def block2(): + self._st_() + self._positive_closure(block2) + self._error( + 'expecting one of: ' + ' ' + ' [ \\t]' + ) + + @tatsumasu() + def _sep_(self): # noqa + with self._choice(): + with self._option(): + + def block0(): + self._cmd_net_sep_() + self.name_last_node('@') + + def block2(): + self._st_() + self._closure(block2) + self._token('+') + + def block3(): + self._st_() + self._closure(block3) + self._positive_closure(block0) + with self._option(): + + def block4(): + self._st_() + self._positive_closure(block4) + 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._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(object): + 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, start=None, **kwargs): + if start is None: + start = 'start' + if not filename or filename == '-': + text = sys.stdin.read() + else: + with open(filename) as f: + text = f.read() + parser = ExpressionParser() + return parser.parse( + text, + rule_name=start, + 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..103b74e83 --- /dev/null +++ b/PySpice/Spice/ExpressionModel.py @@ -0,0 +1,202 @@ +#!/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 tatsu.objectmodel import Node +from tatsu.semantics import ModelBuilderSemantics + + +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(ExpressionModelBuilderSemantics, self).__init__(context=context, types=types) + + +class SpiceExpression(ModelBase): + pass + + +class GenericExpression(ModelBase): + braced = None + value = None + + +class BracedExpression(ModelBase): + sep = None + + +class Expression(ModelBase): + term = None + ternary = None + + +class Ternary(ModelBase): + op = None + sep = None + t = None + x = None + y = None + + +class Conditional(ModelBase): + expr = None + + +class Or(ModelBase): + left = None + op = None + right = None + sep = None + + +class Xor(ModelBase): + left = None + op = None + right = None + sep = None + + +class And(ModelBase): + left = None + op = None + right = None + sep = None + + +class Not(ModelBase): + op = None + operator = None + + +class Relational(ModelBase): + factor = None + left = None + op = None + right = None + sep = None + + +class ConditionalFactor(ModelBase): + boolean = None + expr = None + sep = None + + +class Term(ModelBase): + pass + + +class AddSub(ModelBase): + left = None + op = None + right = None + sep = None + + +class ProdDivMod(ModelBase): + left = None + op = None + right = None + sep = None + + +class Sign(ModelBase): + op = None + operator = None + + +class Exponential(ModelBase): + left = None + op = None + right = None + sep = None + + +class Functional(ModelBase): + pass + + +class Variable(ModelBase): + factor = None + sep = None + variable = None + + +class Factor(ModelBase): + sep = None + + +class Functions(ModelBase): + pass + + +class Value(ModelBase): + imag = None + real = None + unit = None + + +class ImagValue(ModelBase): + value = None + + +class RealValue(ModelBase): + value = None + + +class NumberScale(ModelBase): + scale = None + value = None + + +class Unit(ModelBase): + pass + + +class Hz(ModelBase): + pass + + +class Float(ModelBase): + pass + + +class Int(ModelBase): + pass + + +class BinaryPattern(ModelBase): + pattern = None + + +class Device(ModelBase): + pass + + +class NetNode(ModelBase): + node = None + sep = None + + +class Separator(ModelBase): + comment = None + + +class Comment(ModelBase): + pass diff --git a/PySpice/Spice/HighLevelElement.py b/PySpice/Spice/HighLevelElement.py index dbce6238a..6132619de 100644 --- a/PySpice/Spice/HighLevelElement.py +++ b/PySpice/Spice/HighLevelElement.py @@ -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/Netlist.py b/PySpice/Spice/Netlist.py index 96fbc7334..13c18702e 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -87,7 +87,7 @@ 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, @@ -102,7 +102,6 @@ def __init__(self, **kwargs): #################################################################################################### class DeviceModel: - """This class implements a device model. Ngspice model types: @@ -147,13 +146,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 +178,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 +217,6 @@ def __str__(self): #################################################################################################### class PinDefinition: - """This class defines a pin of an element.""" ############################################## @@ -255,7 +265,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 +275,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 +306,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 +322,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 +386,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 +425,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 +458,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 +486,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. @@ -505,27 +510,51 @@ def __init__(self, netlist, name, *args, **kwargs): self._name = str(name) 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 0 < optional_pins <= self._number_of_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: + IndexError("Incongruent number of args") + 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 +643,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 +728,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 +748,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 +779,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 +794,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 +857,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 +873,17 @@ 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 = set() + self._used_subcircuits = set() + self._parameters = OrderedDict() self.raw_spice = '' - # self._graph = networkx.Graph() + self.spice_sim = '' ############################################## @@ -897,19 +954,17 @@ def node(self, name): ############################################## 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,6 +973,16 @@ 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) if node_name not in self._nodes: @@ -960,7 +1025,16 @@ def has_ground_node(self): def _add_element(self, element): """Add an element.""" if element.name not in self._elements: - self._elements[element.name] = element + self._elements[str(element.name).lower()] = element + if hasattr(element, 'model'): + model = element.model + if model is not None: + self._used_models.add(str(model).lower()) + + if element.name[0] in "xX": + subcircuit_name = element.subcircuit_name + if subcircuit_name is not None: + self._used_subcircuits.add(str(subcircuit_name).lower()) else: raise NameError("Element name {} is already defined".format(element.name)) @@ -968,17 +1042,25 @@ def _add_element(self, element): 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)] = 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 + self._models[str(model.name).lower()] = model else: raise NameError("Model name {} is already defined".format(name)) @@ -989,7 +1071,14 @@ 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 + self._subcircuits[str(subcircuit.name).lower()] = subcircuit + subcircuit.parent = self + for model in subcircuit._used_models: + if model not in subcircuit._models: + self._used_models.add(model) + for subckt in subcircuit._used_subcircuits: + if subckt not in subcircuit._subcircuits: + self._used_subcircuits.add(subckt) ############################################## @@ -997,30 +1086,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, 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 +1144,32 @@ def _str_raw_spice(self): netlist += os.linesep return netlist + def include(self, path, entry=None): + from .EBNFSpiceParser import SpiceParser + + """Include a file.""" + + if path not in self._includes: + self._includes.append(path) + library = SpiceParser.parse(path=path) + if entry is not None: + library = library[entry] + models = library.models + for model in models: + self.model(model._name, model._model_type, **model._parameters) + self._models[model._name.lower()]._included = path + subcircuits = library.subcircuits + for subcircuit in subcircuits: + subcircuit_def = subcircuit.build(parent=self) + self.subcircuit(subcircuit_def) + self._subcircuits[subcircuit._name.lower()]._included = path + parameters = library.parameters + for param in parameters: + self.parameters(*param) + else: + self._logger.warn("Duplicated include") + + #################################################################################################### class SubCircuit(Netlist): @@ -1041,21 +1179,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 +1215,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 +1236,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,10 +1253,17 @@ 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)) @@ -1105,19 +1272,60 @@ def check_nodes(self): def __str__(self): """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 = '.subckt ' + join_list((self._name, nodes)) + if self._params: + parameters = join_list(['{}={}'.format(key, str_spice(value)) + for key, value in self._params.items()]) + netlist += ' params: ' + parameters + netlist += os.linesep netlist += super().__str__() 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 +1357,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 +1395,37 @@ 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 data(self, table, **kwargs): + self._data.update[table] = kwargs ############################################## - def parameter(self, name, expression): - """Set a parameter.""" - self._parameters[str(name)] = str(expression) - - ############################################## - - 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: + 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: + return '.title {}'.format(self.title) + os.linesep + else: + return '.title' + os.linesep ############################################## @@ -1242,44 +1447,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 +1469,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/Simulation.py b/PySpice/Spice/Simulation.py index 54488bc5e..7ad796ad7 100644 --- a/PySpice/Spice/Simulation.py +++ b/PySpice/Spice/Simulation.py @@ -238,13 +238,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 +282,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, ) #################################################################################################### @@ -664,7 +670,7 @@ def save(self, *args): """ - self._saved_nodes |= set(*args) + self._saved_nodes.update(args) ############################################## @@ -1085,24 +1091,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 index f891b3f68..abffa0c95 100644 --- a/PySpice/Spice/SpiceGrammar.py +++ b/PySpice/Spice/SpiceGrammar.py @@ -471,6 +471,23 @@ def _voltage_controlled_voltage_source_(self): # noqa 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') with self._option(): with self._group(): with self._group(): @@ -487,25 +504,14 @@ def _voltage_controlled_voltage_source_(self): # noqa '' ) self.name_last_node('controller') - with self._option(): - with self._group(): - self._node_() - self.name_last_node('control_positive') - self._sep_() - self.name_last_node('sep') - self._node_() - self.name_last_node('control_negative') - self._sep_() - self.name_last_node('sep') - self._gen_expr_() - self.name_last_node('gain') self._error( 'expecting one of: ' + ' ' ' ' - ' ' + '' ) self._define( - ['control_negative', 'control_positive', 'controller', 'dev', 'gain', 'negative', 'positive', 'sep'], + ['controller', 'dev', 'negative', 'nodes', 'positive', 'sep', 'transconductance'], [] ) @@ -570,6 +576,23 @@ def _voltage_controlled_current_source_(self): # noqa 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') with self._option(): with self._group(): with self._group(): @@ -586,25 +609,14 @@ def _voltage_controlled_current_source_(self): # noqa '' ) self.name_last_node('controller') - with self._option(): - with self._group(): - self._node_() - self.name_last_node('control_positive') - self._sep_() - self.name_last_node('sep') - self._node_() - self.name_last_node('control_negative') - self._sep_() - self.name_last_node('sep') - self._gen_expr_() - self.name_last_node('transconductance') self._error( 'expecting one of: ' + ' ' ' ' - ' ' + '' ) self._define( - ['control_negative', 'control_positive', 'controller', 'dev', 'negative', 'positive', 'sep', 'transconductance'], + ['controller', 'dev', 'negative', 'nodes', 'positive', 'sep', 'transconductance'], [] ) @@ -1159,18 +1171,21 @@ def _bjt_(self): # noqa 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._sep_() - self.name_last_node('sep') self._substrate_node_() self.name_last_node('substrate') - - def block9(): self._sep_() self.name_last_node('sep') - self._node_() - self.add_last_node_to_name('args') - self._positive_closure(block9) + with self._optional(): + self._token('DT') + self.name_last_node('thermal') + 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') @@ -1182,8 +1197,8 @@ def block9(): self._parameters_() self.name_last_node('parameters') self._define( - ['area', 'base', 'collector', 'dev', 'emitter', 'parameters', 'sep', 'substrate'], - ['args'] + ['area', 'base', 'collector', 'dev', 'emitter', 'model', 'parameters', 'sep', 'substrate', 'thermal'], + [] ) @tatsumasu('SubstrateNode') @@ -2845,6 +2860,52 @@ def _braced_expression_(self): # noqa [] ) + @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._error( + 'expecting one of: ' + ' ' + ) + self._node_() + self.add_last_node_to_name('@') + self._define( + ['sep'], + [] + ) + @tatsumasu('Expression') @leftrec def _expression_(self): # noqa @@ -3202,8 +3263,8 @@ def _variable_(self): # noqa self.name_last_node('factor') self._error( 'expecting one of: ' - "'{' [a-zA-Z_`@#\\$][a-zA-Z0-9_:`@#\\." - '\\$]*[a-zA-Z0-9_`@#\\.\\$] [a-zA-Z]' + "'{' [a-zA-Z_`@#\\$][a-zA-Z0-" + '9_:`@#\\.\\$]*[a-zA-Z0-9_`@#\\.\\$] [a-zA-Z]' ' ' ) self._define( @@ -3861,9 +3922,9 @@ def _number_scale_(self): # noqa self.name_last_node('scale') self._error( 'expecting one of: ' - '[\\+\\-]?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(' - '[eE][\\-\\+]?[0-9]{1,3})? ' - '[\\+\\-]?[0-9]+ ' + '[\\+\\-]?(([0-9]+(\\.[0-9]*)?)|(\\.[0-' + '9]+))([eE][\\-\\+]?[0-9]{1,3})?' + ' [\\+\\-]?[0-9]+ ' ) self._define( ['scale', 'value'], @@ -3964,9 +4025,9 @@ def _node_(self): # noqa 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_\\[\\$\\/\\+\\-][a-zA-Z0-9_:\\$\\-' + '`~!@#%&_\\+|<>\\?\\.\\|\\^\\*\\/]*[a-zA-Z0-' + '9_\\$\\-`~!@#%&_\\+|<>\\?\\.\\|\\^\\*\\]\\/]' '[a-zA-Z0-9_]' ) self.name_last_node('node') @@ -4003,8 +4064,8 @@ def _var_id_(self): # noqa self._pattern('[a-zA-Z]') self._error( 'expecting one of: ' - '[a-zA-Z_`@#\\$][a-zA-Z0-9_:`@#\\.\\$]*[a-zA' - '-Z0-9_`@#\\.\\$] [a-zA-Z]' + '[a-zA-Z_`@#\\$][a-zA-Z0-9_:`@#\\.\\$]*[a-' + 'zA-Z0-9_`@#\\.\\$] [a-zA-Z]' ) @tatsumasu() @@ -4404,6 +4465,12 @@ def tablefile(self, ast): # noqa 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 diff --git a/PySpice/Spice/SpiceModel.py b/PySpice/Spice/SpiceModel.py index 6235bf856..035e3594a 100644 --- a/PySpice/Spice/SpiceModel.py +++ b/PySpice/Spice/SpiceModel.py @@ -80,14 +80,13 @@ class Diode(ModelBase): class VoltageControlledVoltageSource(ModelBase): - control_negative = None - control_positive = None controller = None dev = None - gain = None negative = None + nodes = None positive = None sep = None + transconductance = None class CurrentControlledCurrentSource(ModelBase): @@ -101,11 +100,10 @@ class CurrentControlledCurrentSource(ModelBase): class VoltageControlledCurrentSource(ModelBase): - control_negative = None - control_positive = None controller = None dev = None negative = None + nodes = None positive = None sep = None transconductance = None @@ -206,14 +204,15 @@ class MOSFET(ModelBase): class BJT(ModelBase): area = None - args = None base = None collector = None dev = None emitter = None + model = None parameters = None sep = None substrate = None + thermal = None class SubstrateNode(ModelBase): @@ -485,6 +484,14 @@ class BracedExpression(ModelBase): sep = None +class ParenthesisNodes(ModelBase): + sep = None + + +class CircuitNodes(ModelBase): + sep = None + + class Expression(ModelBase): term = None ternary = None diff --git a/PySpice/Spice/expressiongrammar.ebnf b/PySpice/Spice/expressiongrammar.ebnf new file mode 100644 index 000000000..c023c6bfe --- /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 index 221281918..18e486ce9 100644 --- a/PySpice/Spice/spicegrammar.ebnf +++ b/PySpice/Spice/spicegrammar.ebnf @@ -162,15 +162,13 @@ voltage_controlled_voltage_source::VoltageControlledVoltageSource ~ sep:sep ( - (controller:(control_value | control_table | control_voltage_poly)) - | ( - control_positive:node - sep:sep - control_negative:node + nodes:(parenthesis_nodes | circuit_nodes) sep:sep - gain:gen_expr + transconductance:gen_expr ) + | + (controller:(control_value | control_table | control_voltage_poly)) ) ; @@ -202,15 +200,13 @@ voltage_controlled_current_source::VoltageControlledCurrentSource ~ sep:sep ( - (controller:(control_value | control_table | control_voltage_poly)) - | ( - control_positive:node - sep:sep - control_negative:node + nodes:(parenthesis_nodes | circuit_nodes) sep:sep transconductance:gen_expr ) + | + (controller:(control_value | control_table | control_voltage_poly)) ) ; @@ -438,8 +434,11 @@ bjt::BJT base:node sep:sep emitter:node - [sep:sep substrate:substrate_node] - {sep:sep args+:node}+ + ~ + sep:sep + [substrate:substrate_node sep:sep] + [thermal:'DT' sep:sep] + model:model_name [sep:sep area:gen_expr] [sep:sep parameters:parameters] ; @@ -1023,6 +1022,18 @@ braced_expression::BracedExpression ; +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 diff --git a/PySpice/Tools/StringTools.py b/PySpice/Tools/StringTools.py index c48293f8f..2df200b9e 100644 --- a/PySpice/Tools/StringTools.py +++ b/PySpice/Tools/StringTools.py @@ -32,11 +32,11 @@ #################################################################################################### -from PySpice.Unit.Unit import UnitValue -from PySpice.Spice.Expressions import Expression +from ..Spice.Expressions import Expression #################################################################################################### def str_spice(obj, unit=True): + from ..Unit.Unit import UnitValue # Fixme: right place ??? diff --git a/PySpice/Unit/Unit.py b/PySpice/Unit/Unit.py index 8eae51751..561a5a223 100644 --- a/PySpice/Unit/Unit.py +++ b/PySpice/Unit/Unit.py @@ -826,7 +826,7 @@ def str(self, spice=False, unit=True): string = '' else: # Ngspice don't support utf-8 - # degree symbole can be encoded str(176) in Extended ASCII + # degree symbol can be encoded str(176) in Extended ASCII string = string.replace('°', '') # U+00B0 string = string.replace('℃', '') # U+2103 # U+2109 ℉ @@ -861,7 +861,7 @@ class UnitValue: # numbers.Real """This class implements a value with a unit and a power (prefix). - The value is not converted to float if the value is an int. + The value is not converted to float if the value is an int or expression. """ _logger = _module_logger.getChild('UnitValue') @@ -875,6 +875,7 @@ def simple_value(cls, value): ############################################## def __init__(self, prefixed_unit, value): + from ..Spice.Expressions import Expression self._prefixed_unit = prefixed_unit @@ -886,7 +887,7 @@ def __init__(self, prefixed_unit, value): self._value = value.value else: self._value = self._convert_scalar_value(value) - elif isinstance(value, int): + elif isinstance(value, int) or isinstance(value, Expression): self._value = value # to keep as int else: self._value = float(value) @@ -1374,6 +1375,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/unit-test/Spice/test_BasicElement.py b/unit-test/Spice/test_BasicElement.py index 5b3d867ee..a4cab3d7f 100644 --- a/unit-test/Spice/test_BasicElement.py +++ b/unit-test/Spice/test_BasicElement.py @@ -43,19 +43,21 @@ 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'.lower()) + 'R1 n1 n2 100ohm'.lower()) self._test_spice_declaration(Resistor(Circuit(''), '1', 'n1', 'n2', kilo(1)), - 'R1 n1 n2 1k'.lower()) + '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'.lower()) + 'R1 n1 n2 1kohm ac=2kohm dtemp=26c m=2 noisy=1 scale=1.5 temp=25c'.lower()) self._test_spice_declaration(Resistor(Circuit(''), '1', 'n1', 'n2', kilo(1), noisy=False), - 'R1 n1 n2 1k'.lower()) + 'R1 n1 n2 1kohm'.lower()) + + self._test_spice_declaration(Diode(Circuit(''), '1', 1, 2, '1N4148'), 'd1 1 2 1n4148') self._test_spice_declaration(XSpiceElement(Circuit(''), '1', 1, 0, model='cap'), 'A1 1 0 cap'.lower()) diff --git a/unit-test/Spice/test_ExpressionParser.py b/unit-test/Spice/test_ExpressionParser.py new file mode 100644 index 000000000..adef53fdd --- /dev/null +++ b/unit-test/Spice/test_ExpressionParser.py @@ -0,0 +1,58 @@ +import unittest +from PySpice.Spice.EBNFExpressionParser import ExpressionParser +import os + +data = [ + "{True?1:0}", + "{False?1:0}", + "{a + b}", + "{a - b}", + "{(a + b)}", + "{(a - b)}", + "{a * b}", + "{a / b}", + "{(a * b)}", + "{(a / b)}", + "{(a / b) * c}", + "{if(1<2,1,0)}", + "{if((1<2),(1),(0))}", + "{2<=1?1:0}", + "{a + (b + c)}", + "{(a + b) + c}", + "1", + "{1}", + "{1+2}", + "{(1+2)}", + "{(1+2) + 3}", + "{(1+2) * 3}", + "{(1+2) * (3 + 7)}", + "{(1+2) * -(3 + 7)}", + "{(1+a) * -(b + 7)}", + "{(1+sin(3.14)) * -(3 + 7)}", + "{(1+v(a)) * -(3 + 7)}", + "{atan2(asin(b), ln(c))}", + "{atan2(asin(b) - 7, ln(c) + 5)}", + "{ddx(asin, ln(c) + 5)}", + "{if(True, 1, 2)}", + "{if(2 < 3, 1, 2)}", + "{if((2 < 3) | False , 1, 2)}", + "{(2 < 3) | False ? True ? 3: 4: 2}", + "{(2 < 3) | False ? True ? 3: sin(4): 2}", + "{(2 < 3) | False ? (True ? 3: sin(4)): 2}", + "{(2 < 3) & ( False | True) ? (True ? 3: sin(4)): 2}", + "{~(2 < 3) & ~( False | True) ? (True ? 3: sin(4)): 2}", + "{limit(3, 2, a)}", + "{limit(3, 2, a)}" +] + +class TestExpressionParser(unittest.TestCase): + def test_parser(self): + #ExpressionParser._regenerate() + for case in data: + expr_i = ExpressionParser.parse(source=case) + case_i = "{%s}" % expr_i + expr_f = ExpressionParser.parse(source=case_i) + self.assertEqual("{%s}" % expr_f, case_i) + +if __name__ == '__main__': + unittest.main() diff --git a/unit-test/Spice/test_HighLevelElement.py b/unit-test/Spice/test_HighLevelElement.py index e61769ef6..1e32715a5 100644 --- a/unit-test/Spice/test_HighLevelElement.py +++ b/unit-test/Spice/test_HighLevelElement.py @@ -47,7 +47,7 @@ def test(self): 'pwl1', '1', '0', 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( @@ -55,9 +55,9 @@ def test(self): 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, + 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( @@ -65,10 +65,10 @@ def test(self): 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, + 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)', ) #################################################################################################### diff --git a/unit-test/Spice/test_Netlist.py b/unit-test/Spice/test_Netlist.py index 48ac90f95..df17e7a2a 100644 --- a/unit-test/Spice/test_Netlist.py +++ b/unit-test/Spice/test_Netlist.py @@ -78,10 +78,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 +95,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 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 +159,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 10v +r1 in out 9kohm """ # .end @@ -178,7 +178,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 +189,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} """ diff --git a/unit-test/Spice/test_SpiceParser.py b/unit-test/Spice/test_SpiceParser.py index cb02d7e47..18b0b9203 100644 --- a/unit-test/Spice/test_SpiceParser.py +++ b/unit-test/Spice/test_SpiceParser.py @@ -1,12 +1,14 @@ import unittest from PySpice.Spice.Netlist import Circuit -from PySpice.Spice.EBNFParser import SpiceParser -from multiprocessing import Pool, cpu_count +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) @@ -19,6 +21,10 @@ +(79.1111, 0.00033272) +(80, 0.00275)} +G1 21 98 (6,15) 26E-6 + +E23 21 98 (6,15) 26E-6 + BG1 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 @@ -83,20 +89,16 @@ .PARAM d = {limit(3, 2, a)} E_ABM12 N145529 0 VALUE={ if((V(CTRL_LIMIT)<0.3) ,1,0) } -Q1 col base eb QPWR .1 +Q1 col base eb QPWR 0.1 .MODEL QPWR NPN -.MODEL Q2N2222 NPN - *Another note Q2 10 2 9 PNP1 -Q2N2222 Nc Nb Ne Q2N2222 - Q8 Coll Base Emit VBIC13MODEL3 temp=0 -Q9 Coll Base Emit Subst DT VBIC13MODEL4 -Q10 Coll Base Emit Subst DT HICUMMMODEL1 +Q9 Coll Base Emit [Subst] DT VBIC13MODEL4 +Q10 Coll Base Emit [Subst] DT HICUMMMODEL1 .MODEL NPN2 NPN .MODEL VBIC13MODEL2 NPN @@ -247,7 +249,7 @@ 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,4 +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 @@ -420,8 +422,14 @@ def test_library(self): 'lo', 'vdd', 'vss') - circuit.R('test_temp', 1, 2, tc=(4, 5)) - circuit.B('test_tc', 1, 2, v={5}, tc=(7, 8)) + 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, @@ -465,7 +473,7 @@ def test_transient(self): IPWL1 1 0 PWL( 0S 0A 2S 3A 3S 2A 4S 2A 4.01S 5A r=2s td=1) 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 SIN 0 5 3 1 +ISIN 4 3 AC 1 SIN 0 5 3 1 """) circuit = transient.build() @@ -476,7 +484,7 @@ def test_transient(self): 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 sin(0a 5a 3hz 1s 0hz) +isin 4 3 dc 0a ac 1a sin(0a 5a 3hz 1s 0hz) """ result = str(circuit) self.assertEqual(expected, result) @@ -566,8 +574,8 @@ def test_subcircuit(self): circuit = Circuit('MOS Driver') circuit.spice_sim = 'xyce' circuit.include(os.path.join(os.getcwd(), '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) + 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 .model diode D (is=1.038e-15 n=1 tt=2e-08 cjo=5e-12 rs=0.5 bv=130) @@ -577,16 +585,16 @@ def test_subcircuit(self): .subckt mosdriver hb hi ho hs li lo vdd vss xhigh hoi hs hi vss source -rhoi hoi ho 1 +rhoi hoi ho 1ohm choi ho hs 1e-09 xlow loi vss li vss source -rloi loi lo 1 +rloi loi lo 1ohm cloi lo vss 1e-09 dhb vdd hb diode .ends mosdriver -xtest 0 1 2 3 4 5 mosdriver -btest 1 0 v=if(0, 0, 1) smoothbsrc=1 +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) diff --git a/unit-test/SpiceParser/mosdriver.lib b/unit-test/SpiceParser/mosdriver.lib index c3332304c..f0bb9f0ec 100644 --- a/unit-test/SpiceParser/mosdriver.lib +++ b/unit-test/SpiceParser/mosdriver.lib @@ -11,4 +11,4 @@ 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/test_SpiceParser.py b/unit-test/SpiceParser/test_SpiceParser.py index fb498a41f..c934c3193 100644 --- a/unit-test/SpiceParser/test_SpiceParser.py +++ b/unit-test/SpiceParser/test_SpiceParser.py @@ -27,7 +27,7 @@ #################################################################################################### from PySpice.Spice.Netlist import Circuit -from PySpice.Spice.Parser import SpiceParser +from PySpice.Spice.EBNFSpiceParser import SpiceParser #################################################################################################### @@ -42,12 +42,12 @@ #################################################################################################### def circuit_gft(prb): - circuit_file = SpiceParser(source=prb[0]) - circuit = circuit_file.build_circuit() + circuit_file = SpiceParser.parse(source=prb[0]) + circuit = circuit_file.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 #################################################################################################### @@ -56,7 +56,7 @@ class TestSpiceParser(unittest.TestCase): ############################################## - @unittest.skip('') + #@unittest.skip('') def test_parser(self): for source in (hsop77, hsada4077): results = list(map(circuit_gft, [(source, -1), (source, 1)])) @@ -66,12 +66,12 @@ def test_parser(self): ############################################## - @unittest.skip('') + #@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) + circuit.include('./mosdriver.lib') + circuit.X('test', 'mosdriver', '0', '1', '2', '3', '4', '5', '6', '7') + circuit.BehavioralSource('test', '1', '0', voltage_expression='if(False, 0, 1)', smoothbsrc=1) print(circuit) #################################################################################################### From 974094169a72d18db060be3a27368dca565421e7 Mon Sep 17 00:00:00 2001 From: jmgc Date: Sun, 5 Mar 2023 14:54:30 +0100 Subject: [PATCH 084/134] Update codeql-analysis.yml --- .github/workflows/codeql-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index dbec8e46e..79fa3f982 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. From 8791cadcdaa76b1373ea91aab69f3859928875e9 Mon Sep 17 00:00:00 2001 From: jmgc Date: Sun, 5 Mar 2023 14:56:20 +0100 Subject: [PATCH 085/134] Update codeql-analysis.yml --- .github/workflows/codeql-analysis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 79fa3f982..e339c9501 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -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 From b1ffd795b3084f29b823a1419499084181905cb9 Mon Sep 17 00:00:00 2001 From: jmgc Date: Sun, 5 Mar 2023 15:36:44 +0100 Subject: [PATCH 086/134] Update requirements.txt Include TatSu --- requirements.txt | 1 + 1 file changed, 1 insertion(+) 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 From 48e8bd97bf5d39403cb58ac0fd5fa6a79f2ade71 Mon Sep 17 00:00:00 2001 From: jmgc Date: Sun, 5 Mar 2023 15:41:21 +0100 Subject: [PATCH 087/134] Delete test_Pickle.py Already in another folder. --- unit-test/Spice/test_Pickle.py | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 unit-test/Spice/test_Pickle.py diff --git a/unit-test/Spice/test_Pickle.py b/unit-test/Spice/test_Pickle.py deleted file mode 100644 index 44957101a..000000000 --- a/unit-test/Spice/test_Pickle.py +++ /dev/null @@ -1,28 +0,0 @@ -import unittest -from PySpice.Probe.WaveForm import WaveForm -from PySpice.Unit.SiUnits import Second -from PySpice.Unit.Unit import UnitValues, PrefixedUnit -from PySpice.Unit import u_kHz -import pickle -import os -import tempfile -import numpy as np - -class TestPickle(unittest.TestCase): - def test_ndarray(self): - array = np.ndarray((1, 1)) - with tempfile.TemporaryFile() as fp: - pickle.dump(array, fp) - fp.seek(0) - new_array = pickle.load(fp) - self.assertEqual(array, new_array) - - def test_unit_values(self): - unit_values = UnitValues(u_kHz(100).prefixed_unit, (1, 1)) - new_unit_values = pickle.loads(pickle.dumps(unit_values)) - self.assertEqual(unit_values, new_unit_values) - - def test_waveforms(self): - waveform = WaveForm("Test", u_kHz(100).prefixed_unit, (1, 1)) - new_waveform = pickle.loads(pickle.dumps(waveform)) - self.assertEqual(waveform, new_waveform) From 5349ac9924da3435001f44356a23ceb7dc8516e8 Mon Sep 17 00:00:00 2001 From: jmgc Date: Sun, 5 Mar 2023 15:51:20 +0100 Subject: [PATCH 088/134] Updated test_SpiceParser in its new dir --- unit-test/Spice/test_SpiceParser.py | 604 ---------------------- unit-test/SpiceParser/test_SpiceParser.py | 597 ++++++++++++++++++++- 2 files changed, 582 insertions(+), 619 deletions(-) delete mode 100644 unit-test/Spice/test_SpiceParser.py diff --git a/unit-test/Spice/test_SpiceParser.py b/unit-test/Spice/test_SpiceParser.py deleted file mode 100644 index 18b0b9203..000000000 --- a/unit-test/Spice/test_SpiceParser.py +++ /dev/null @@ -1,604 +0,0 @@ -import unittest -from PySpice.Spice.Netlist import Circuit -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)} - -G1 21 98 (6,15) 26E-6 - -E23 21 98 (6,15) 26E-6 - -BG1 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 - -G1 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 - -BG1 IOUT- IOUT+ I={IF( (V(VC+,VC-) <= 0) , 0 , 1 )} -BG2 IOUT- IOUT+ I={IF( (V(VC+,VC-)<=0),0,GAIN*V(VC+,VC-) )} - -.SUBCKT VCCS_LIM_CLAW+_0_OPA350 VCp VCm IOUTp IOUTm -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)} -.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 "ipwl.txt" -VPWL3 3 0 PWL file "vpwl.csv" -VPWL4 4 0 PWL FILE 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 -""" - -def circuit_gft(prb): - parser = SpiceParser.parse(source=prb[0]) - circuit = parser.build() - circuit.parameter('prb', str(prb[1])) - simulator = circuit.simulator(simulator='xyce-serial') - simulator.save('all') - return simulator - - -class TestSpiceParser(unittest.TestCase): - def test_parser(self): - # 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_library(self): - from PySpice.Spice.Library import SpiceLibrary - import os - libraries_path = os.path.abspath('.') - spice_library = SpiceLibrary(libraries_path) - circuit = Circuit('MOS Driver') - circuit.include(spice_library['mosdriver']) - 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') - simulator.transient(1e-9, 1e-3) - - 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) - result = simulator.transient(1e-2, 10) - simulator = circuit.simulator(simulator='xyce-serial', - temperature=25, - nominal_temperature=25, - working_directory='.') - 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) -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_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') - circuit.spice_sim = 'xyce' - circuit.include(os.path.join(os.getcwd(), '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 - -.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) - - -if __name__ == '__main__': - unittest.main() diff --git a/unit-test/SpiceParser/test_SpiceParser.py b/unit-test/SpiceParser/test_SpiceParser.py index c934c3193..9f290df4d 100644 --- a/unit-test/SpiceParser/test_SpiceParser.py +++ b/unit-test/SpiceParser/test_SpiceParser.py @@ -28,8 +28,390 @@ from PySpice.Spice.Netlist import Circuit 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)} + +G1 21 98 (6,15) 26E-6 + +E23 21 98 (6,15) 26E-6 + +BG1 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 + +G1 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 + +BG1 IOUT- IOUT+ I={IF( (V(VC+,VC-) <= 0) , 0 , 1 )} +BG2 IOUT- IOUT+ I={IF( (V(VC+,VC-)<=0),0,GAIN*V(VC+,VC-) )} + +.SUBCKT VCCS_LIM_CLAW+_0_OPA350 VCp VCm IOUTp IOUTm +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)} +.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 "ipwl.txt" +VPWL3 3 0 PWL file "vpwl.csv" +VPWL4 4 0 PWL FILE 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,8 +424,8 @@ #################################################################################################### def circuit_gft(prb): - circuit_file = SpiceParser.parse(source=prb[0]) - circuit = circuit_file.build() + 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') @@ -58,23 +440,208 @@ class TestSpiceParser(unittest.TestCase): #@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_library(self): + from PySpice.Spice.Library import SpiceLibrary + import os + libraries_path = os.path.abspath('.') + spice_library = SpiceLibrary(libraries_path) + circuit = Circuit('MOS Driver') + circuit.include(spice_library['mosdriver']) + 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') + simulator.transient(1e-9, 1e-3) + + 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) + result = simulator.transient(1e-2, 10) + simulator = circuit.simulator(simulator='xyce-serial', + temperature=25, + nominal_temperature=25, + working_directory='.') + 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) +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_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) - #@unittest.skip('') def test_subcircuit(self): - circuit = Circuit('') - circuit.include('./mosdriver.lib') + print(os.getcwd()) + circuit = Circuit('MOS Driver') + circuit.spice_sim = 'xyce' + circuit.include(os.path.join(os.getcwd(), 'mosdriver.lib')) circuit.X('test', 'mosdriver', '0', '1', '2', '3', '4', '5', '6', '7') - circuit.BehavioralSource('test', '1', '0', voltage_expression='if(False, 0, 1)', smoothbsrc=1) - print(circuit) + circuit.BehavioralSource('test', '1', '0', voltage_expression='{if(True, 0, 1)}', smoothbsrc=1) + expected = """.title MOS Driver + +.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) -#################################################################################################### if __name__ == '__main__': unittest.main() From 700a0eaa7d94adbbc939e0f30fa95b8d1996ebdf Mon Sep 17 00:00:00 2001 From: jmgc Date: Sun, 5 Mar 2023 15:54:26 +0100 Subject: [PATCH 089/134] Update EBNFSpiceParser.py Trial to solve an issue in some cases with the numbers that are converted into tuples. --- PySpice/Spice/EBNFSpiceParser.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/PySpice/Spice/EBNFSpiceParser.py b/PySpice/Spice/EBNFSpiceParser.py index fc77ddd07..3fd921d5f 100644 --- a/PySpice/Spice/EBNFSpiceParser.py +++ b/PySpice/Spice/EBNFSpiceParser.py @@ -2038,6 +2038,8 @@ def walk_object(self, node, data): @staticmethod def _to_number(value): + if type(value) is tuple: + value = value[0] + value[-1] try: int_value = int(value) float_value = float(value) From 9e0ec9462fde79f4494646128d51977dcfac4aa9 Mon Sep 17 00:00:00 2001 From: jmgc Date: Sun, 5 Mar 2023 16:06:49 +0100 Subject: [PATCH 090/134] Update EBNFExpressionParser.py --- PySpice/Spice/EBNFExpressionParser.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/PySpice/Spice/EBNFExpressionParser.py b/PySpice/Spice/EBNFExpressionParser.py index e5a96c4f2..7b95473af 100644 --- a/PySpice/Spice/EBNFExpressionParser.py +++ b/PySpice/Spice/EBNFExpressionParser.py @@ -379,6 +379,8 @@ def walk_object(self, node, data): @staticmethod def _to_number(value): + if type(value) is tuple: + value = value[0] + value[-1] try: int_value = int(value) float_value = float(value) From 21f36b44c1309fc0880e5fc0f77d0065b7d04316 Mon Sep 17 00:00:00 2001 From: jmgc Date: Sun, 5 Mar 2023 22:29:14 +0100 Subject: [PATCH 091/134] Revised the tests --- tox.ini | 3 ++- unit-test/{Spice => SpiceParser}/diode.lib | 0 unit-test/{Spice => SpiceParser}/ipwl.txt | 0 unit-test/SpiceParser/mosdriver.lib | 10 ++++++---- unit-test/{Spice => SpiceParser}/source.lib | 0 unit-test/SpiceParser/test_SpiceParser.py | 10 +++++----- 6 files changed, 13 insertions(+), 10 deletions(-) rename unit-test/{Spice => SpiceParser}/diode.lib (100%) rename unit-test/{Spice => SpiceParser}/ipwl.txt (100%) rename unit-test/{Spice => SpiceParser}/source.lib (100%) diff --git a/tox.ini b/tox.ini index 860ae6359..ac9aefb53 100644 --- a/tox.ini +++ b/tox.ini @@ -4,8 +4,9 @@ # and then run "tox" from this directory. [tox] -envlist = py38 +envlist = py310 [testenv] commands = pytest unit-test deps = pytest + tatsu diff --git a/unit-test/Spice/diode.lib b/unit-test/SpiceParser/diode.lib similarity index 100% rename from unit-test/Spice/diode.lib rename to unit-test/SpiceParser/diode.lib diff --git a/unit-test/Spice/ipwl.txt b/unit-test/SpiceParser/ipwl.txt similarity index 100% rename from unit-test/Spice/ipwl.txt rename to unit-test/SpiceParser/ipwl.txt diff --git a/unit-test/SpiceParser/mosdriver.lib b/unit-test/SpiceParser/mosdriver.lib index f0bb9f0ec..e8bba2042 100644 --- a/unit-test/SpiceParser/mosdriver.lib +++ b/unit-test/SpiceParser/mosdriver.lib @@ -1,12 +1,14 @@ * 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 diff --git a/unit-test/Spice/source.lib b/unit-test/SpiceParser/source.lib similarity index 100% rename from unit-test/Spice/source.lib rename to unit-test/SpiceParser/source.lib diff --git a/unit-test/SpiceParser/test_SpiceParser.py b/unit-test/SpiceParser/test_SpiceParser.py index 9f290df4d..141e95469 100644 --- a/unit-test/SpiceParser/test_SpiceParser.py +++ b/unit-test/SpiceParser/test_SpiceParser.py @@ -265,9 +265,9 @@ .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 "ipwl.txt" -VPWL3 3 0 PWL file "vpwl.csv" -VPWL4 4 0 PWL FILE vpwl.csv +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) @@ -451,7 +451,7 @@ def test_parser(self): def test_library(self): from PySpice.Spice.Library import SpiceLibrary import os - libraries_path = os.path.abspath('.') + libraries_path = os.path.abspath('unit-test/SpiceParser') spice_library = SpiceLibrary(libraries_path) circuit = Circuit('MOS Driver') circuit.include(spice_library['mosdriver']) @@ -616,7 +616,7 @@ def test_subcircuit(self): print(os.getcwd()) circuit = Circuit('MOS Driver') circuit.spice_sim = 'xyce' - circuit.include(os.path.join(os.getcwd(), 'mosdriver.lib')) + circuit.include(os.path.join(os.getcwd(), '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 From 5168b3c6c206af2a4f01c5fe809e57fd4e9eafd6 Mon Sep 17 00:00:00 2001 From: jmgc Date: Sun, 5 Mar 2023 22:29:33 +0100 Subject: [PATCH 092/134] Moved the file --- unit-test/{Spice => SpiceParser}/vpwl.csv | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename unit-test/{Spice => SpiceParser}/vpwl.csv (100%) diff --git a/unit-test/Spice/vpwl.csv b/unit-test/SpiceParser/vpwl.csv similarity index 100% rename from unit-test/Spice/vpwl.csv rename to unit-test/SpiceParser/vpwl.csv From a64a9de2be2866c99edd1c2976d4142242267cfa Mon Sep 17 00:00:00 2001 From: jmgc Date: Sun, 5 Mar 2023 22:29:48 +0100 Subject: [PATCH 093/134] Update .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index f5e31e848..2a8bc5f90 100644 --- a/.gitignore +++ b/.gitignore @@ -150,3 +150,6 @@ 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 From 9675baafe39270cad84b5661a29c1384eb7d3ebf Mon Sep 17 00:00:00 2001 From: jmgc Date: Sun, 5 Mar 2023 22:34:27 +0100 Subject: [PATCH 094/134] Update workspace.xml --- .idea/workspace.xml | 60 +++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 9c5f63987..4ae60f64e 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -5,26 +5,7 @@ - - - - - - - - - - - - - - - - - - - @@ -70,6 +51,7 @@ "node.js.selected.package.tslint": "(autodetect)", "nodejs_package_manager_path": "npm", "settings.editor.selected.configurable": "Errors", + "two.files.diff.last.used.file": "/Users/chema/Workspace/PySpice/unit-test/Spice/mosdriver.lib", "vue.rearranger.settings.migration": "true" } }]]> @@ -80,11 +62,33 @@ + - + + + + + + + + + + + + + + + + + + + + + + @@ -341,7 +345,7 @@ - + @@ -353,11 +357,11 @@ - + - + @@ -369,7 +373,7 @@ - + @@ -382,7 +386,7 @@ - + @@ -392,6 +396,7 @@ + @@ -463,6 +468,8 @@ + + 1621316590920 @@ -833,7 +840,7 @@ - + @@ -851,6 +858,7 @@ + \ No newline at end of file From 4e614ffc220c12bfbae7fd4e702855681db79741 Mon Sep 17 00:00:00 2001 From: jmgc Date: Mon, 6 Mar 2023 07:54:22 +0100 Subject: [PATCH 095/134] Checking the availability of xyce --- unit-test/Spice/test_Expressions.py | 2 +- unit-test/SpiceParser/test_SpiceParser.py | 21 +++++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/unit-test/Spice/test_Expressions.py b/unit-test/Spice/test_Expressions.py index 65727078f..4c7a3d3e7 100644 --- a/unit-test/Spice/test_Expressions.py +++ b/unit-test/Spice/test_Expressions.py @@ -38,7 +38,7 @@ def test_symbol(self): V_3 = V(Symbol("3")) cos_V_3 = Cos(V_3) values = {str(V_3): 25} - self.assertEqual(m.cos(25), cos_V_3(**values)) + self.assertAlmostEqual(m.cos(25), cos_V_3(**values)) y = Symbol('y') add = Add(x, y) add_operator = x + y diff --git a/unit-test/SpiceParser/test_SpiceParser.py b/unit-test/SpiceParser/test_SpiceParser.py index 141e95469..3dc9e56ff 100644 --- a/unit-test/SpiceParser/test_SpiceParser.py +++ b/unit-test/SpiceParser/test_SpiceParser.py @@ -21,7 +21,7 @@ #################################################################################################### from pathlib import Path -import os +import shutil import unittest #################################################################################################### @@ -478,7 +478,8 @@ def test_library(self): nominal_temperature=25, working_directory='.') simulator.options('device smoothbsrc=1') - simulator.transient(1e-9, 1e-3) + if shutil.which('xyce'): + simulator.transient(1e-9, 1e-3) def test_working_dir(self): from PySpice.Spice.Xyce.RawFile import RawFile @@ -497,16 +498,20 @@ def test_working_dir(self): simulator = circuit.simulator(simulator='xyce-serial', temperature=25, nominal_temperature=25) - result = simulator.transient(1e-2, 10) + 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='.') - 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()) + 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=""" From 60b0c7e9eaae8179d73c7ce88d4315807a2df282 Mon Sep 17 00:00:00 2001 From: jmgc Date: Mon, 6 Mar 2023 08:11:08 +0100 Subject: [PATCH 096/134] Update test_ExpressionParser.py Added more tests for the number expressions. --- unit-test/Spice/test_ExpressionParser.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/unit-test/Spice/test_ExpressionParser.py b/unit-test/Spice/test_ExpressionParser.py index adef53fdd..0d60b7e4c 100644 --- a/unit-test/Spice/test_ExpressionParser.py +++ b/unit-test/Spice/test_ExpressionParser.py @@ -3,6 +3,23 @@ import os data = [ + "1.", + "+1.", + "-1.", + ".1", + "-.1", + "+.1", + ".1E0", + "-.1E1", + "+.1E1", + "1.E0", + "-1.E-1", + "+1.E-1", + "1.23", + "-1.23", + "+1.23", + "1.23E1", + "1.23E-1", "{True?1:0}", "{False?1:0}", "{a + b}", From 56bf042a9cc6b17ca231a33721b657a06d73b0f1 Mon Sep 17 00:00:00 2001 From: jmgc Date: Mon, 6 Mar 2023 08:12:58 +0100 Subject: [PATCH 097/134] Update test_Expressions.py Changed to almostEqual an assert. --- unit-test/Spice/test_Expressions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit-test/Spice/test_Expressions.py b/unit-test/Spice/test_Expressions.py index 4c7a3d3e7..0f297cada 100644 --- a/unit-test/Spice/test_Expressions.py +++ b/unit-test/Spice/test_Expressions.py @@ -51,7 +51,7 @@ def test_symbol(self): V_5 = V("5") self.assertEqual("v(5)",V_5) self.assertEqual("cos(27)", Cos(27)) - self.assertEqual(m.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)()) From 3fdd4751ffb2ef0f440a1fb93645b04e291779b2 Mon Sep 17 00:00:00 2001 From: jmgc Date: Mon, 6 Mar 2023 10:29:57 +0100 Subject: [PATCH 098/134] Update Netlist.py Solve an issue with multiline titles --- PySpice/Spice/Netlist.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index 13c18702e..52b20120a 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -1423,7 +1423,12 @@ def str(self, spice_sim=None): def _str_title(self): if self.title: - return '.title {}'.format(self.title) + os.linesep + 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 From 37c4add45b4f8c2b28a3fb14f85cf3a4b80e489a Mon Sep 17 00:00:00 2001 From: jmgc Date: Mon, 6 Mar 2023 10:32:08 +0100 Subject: [PATCH 099/134] Update Unit.py Correction of Units with expressions to avoid adding the units. --- PySpice/Unit/Unit.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/PySpice/Unit/Unit.py b/PySpice/Unit/Unit.py index 561a5a223..9794a7fbd 100644 --- a/PySpice/Unit/Unit.py +++ b/PySpice/Unit/Unit.py @@ -999,6 +999,9 @@ def __float__(self): ############################################## def str(self, spice=False, space=False, unit=True): + from ..Spice.Expressions import Expression + if isinstance(self._value, Expression): + return "{%s}" % self._value string = str(self._value) if space: string += ' ' From 7557172b66d44094c65d3217ac8c9db3aab461d9 Mon Sep 17 00:00:00 2001 From: jmgc Date: Mon, 6 Mar 2023 10:32:21 +0100 Subject: [PATCH 100/134] Update test_SpiceParser.py Check for the title issue. --- unit-test/SpiceParser/test_SpiceParser.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/unit-test/SpiceParser/test_SpiceParser.py b/unit-test/SpiceParser/test_SpiceParser.py index 3dc9e56ff..70c5f4133 100644 --- a/unit-test/SpiceParser/test_SpiceParser.py +++ b/unit-test/SpiceParser/test_SpiceParser.py @@ -619,12 +619,13 @@ def test_boolean(self): def test_subcircuit(self): print(os.getcwd()) - circuit = Circuit('MOS Driver') + circuit = Circuit('MOS Driver\nSimple check') circuit.spice_sim = 'xyce' circuit.include(os.path.join(os.getcwd(), '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 From 30dda1e6e1b1b21a3e89b61af2c7058ee2352586 Mon Sep 17 00:00:00 2001 From: jmgc Date: Mon, 6 Mar 2023 10:34:58 +0100 Subject: [PATCH 101/134] Solve an issue with BJT pin naming Included a test --- PySpice/Spice/BasicElement.py | 10 +++++----- unit-test/Spice/test_BasicElement.py | 3 +++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/PySpice/Spice/BasicElement.py b/PySpice/Spice/BasicElement.py index 08377c5bc..5c279be2a 100644 --- a/PySpice/Spice/BasicElement.py +++ b/PySpice/Spice/BasicElement.py @@ -1318,12 +1318,12 @@ class BipolarJunctionTransistor(FixedPinElement): def format_node_names(self): fixed_pins = len(self.PINS) - self._number_of_optional_pins_ - nodes = self._pins[:fixed_pins] - for node in self._pins[fixed_pins:]: - if str(node).lower() != 'dt': - nodes.append('[{}]'.format(node)) + 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(node) + nodes.append(pin.node) """ Return the formatted list of nodes. """ return join_list((self.name, join_list(nodes))) diff --git a/unit-test/Spice/test_BasicElement.py b/unit-test/Spice/test_BasicElement.py index a4cab3d7f..52def75e0 100644 --- a/unit-test/Spice/test_BasicElement.py +++ b/unit-test/Spice/test_BasicElement.py @@ -59,6 +59,9 @@ def test(self): 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'.lower()) From 300fa6c0b2a481dda0be2d990744847becad9d7c Mon Sep 17 00:00:00 2001 From: jmgc Date: Mon, 6 Mar 2023 15:28:41 +0100 Subject: [PATCH 102/134] Update EBNFSpiceParser.py Solve an issue with multiple lines in the title. --- PySpice/Spice/EBNFSpiceParser.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/PySpice/Spice/EBNFSpiceParser.py b/PySpice/Spice/EBNFSpiceParser.py index 3fd921d5f..cc938334d 100644 --- a/PySpice/Spice/EBNFSpiceParser.py +++ b/PySpice/Spice/EBNFSpiceParser.py @@ -802,7 +802,9 @@ def __init__(self): def walk_Circuit(self, node, data): if data._root is None: - title = join_lines(self.walk(node.title, data)) + title = self.walk(node.title, data) + if type(title) is list: + title = join_lines(title) data._root = CircuitStatement( title, data._path From e70e213fb1b5f7171b0e548c879dae8f2e76ce23 Mon Sep 17 00:00:00 2001 From: jmgc Date: Mon, 6 Mar 2023 15:30:57 +0100 Subject: [PATCH 103/134] Update BasicElement.py Manage the voltage source to take into account the different parameters. --- PySpice/Spice/BasicElement.py | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/PySpice/Spice/BasicElement.py b/PySpice/Spice/BasicElement.py index 5c279be2a..54d21f1bd 100644 --- a/PySpice/Spice/BasicElement.py +++ b/PySpice/Spice/BasicElement.py @@ -806,14 +806,19 @@ class VoltageSource(DipoleElement): ac_phase = ExpressionPositionalParameter(position=2, key_parameter=False) transient = ExpressionPositionalParameter(position=3, key_parameter=False) - def __init__(self, netlist, name, *args, **kwargs): - number_of_pins = len(self.PINS) - arguments = args - if len(args) > number_of_pins: - arguments = list(args[:number_of_pins]) - arguments.append(join_list(args[number_of_pins:])) - - super().__init__(netlist, name, *arguments, **kwargs) + def format_spice_parameters(self): + parameters = [] + if self.dc_value is not None: + parameters.append('dc {}'.format(str_spice(self.dc_value))) + if self.ac_magnitude is not None: + parameters.append('ac {}'.format(str_spice(self.ac_magnitude))) + if self.ac_phase is not None: + if self.ac_magnitude is None: + parameters.append('ac 0') + parameters.append(str_spice(self.ac_phase)) + if self.transient is not None: + parameters.append(str_spice(self.transient)) + return join_list(parameters) #################################################################################################### @@ -844,6 +849,20 @@ class CurrentSource(DipoleElement): 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: + parameters.append('dc {}'.format(str_spice(self.dc_value))) + if self.ac_magnitude is not None: + parameters.append('ac {}'.format(str_spice(self.ac_magnitude))) + if self.ac_phase is not None: + if self.ac_magnitude is None: + parameters.append('ac 0') + parameters.append(str_spice(self.ac_phase)) + if self.transient is not None: + parameters.append(str_spice(self.transient)) + return join_list(parameters) + #################################################################################################### class VoltageControlledCurrentSource(TwoPortElement): From 9e47314bfbd357df2c7c69f83b428caeacf823f0 Mon Sep 17 00:00:00 2001 From: jmgc Date: Mon, 6 Mar 2023 15:31:40 +0100 Subject: [PATCH 104/134] Update the tests to check the latest updates --- unit-test/Spice/test_Netlist.py | 4 ++-- unit-test/SpiceParser/test_SpiceParser.py | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/unit-test/Spice/test_Netlist.py b/unit-test/Spice/test_Netlist.py index df17e7a2a..b06b6d55a 100644 --- a/unit-test/Spice/test_Netlist.py +++ b/unit-test/Spice/test_Netlist.py @@ -95,7 +95,7 @@ def test_basic(self): spice_declaration = """ .title Voltage Divider -vinput in 0 10v +vinput in 0 dc 10v r1 in out 9kohm r2 out 0 1kohm """ @@ -159,7 +159,7 @@ def test_raw_spice(self): spice_declaration = """ .title Voltage Divider R2 out 0 1kOhm -vinput in 0 10v +vinput in 0 dc 10v r1 in out 9kohm """ # .end diff --git a/unit-test/SpiceParser/test_SpiceParser.py b/unit-test/SpiceParser/test_SpiceParser.py index 70c5f4133..d5336f712 100644 --- a/unit-test/SpiceParser/test_SpiceParser.py +++ b/unit-test/SpiceParser/test_SpiceParser.py @@ -601,6 +601,22 @@ def test_subcircuits(self): 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)} @@ -648,6 +664,13 @@ def test_subcircuit(self): 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) if __name__ == '__main__': unittest.main() From c9869d905c850a12ae50f96bb249c50e685943a7 Mon Sep 17 00:00:00 2001 From: jmgc Date: Mon, 6 Mar 2023 16:14:34 +0100 Subject: [PATCH 105/134] Update the spice grammar Detected an issue with the parameters determination. The grammar has been modified to be more robust and tests have been added. --- PySpice/Spice/EBNFSpiceParser.py | 16 +- PySpice/Spice/SpiceGrammar.py | 1213 ++++++++++++++++----- PySpice/Spice/SpiceModel.py | 769 +++++++------ PySpice/Spice/spicegrammar.ebnf | 6 +- unit-test/SpiceParser/test_SpiceParser.py | 21 + 5 files changed, 1381 insertions(+), 644 deletions(-) diff --git a/PySpice/Spice/EBNFSpiceParser.py b/PySpice/Spice/EBNFSpiceParser.py index cc938334d..07cdb410c 100644 --- a/PySpice/Spice/EBNFSpiceParser.py +++ b/PySpice/Spice/EBNFSpiceParser.py @@ -803,8 +803,7 @@ def __init__(self): def walk_Circuit(self, node, data): if data._root is None: title = self.walk(node.title, data) - if type(title) is list: - title = join_lines(title) + title = join_lines(title) data._root = CircuitStatement( title, data._path @@ -1758,18 +1757,15 @@ def walk_NetlistLine(self, node, data): return self.walk(node.ast, data) def walk_Parameters(self, node, data): - if isinstance(node.ast, list): - result = {} - # The separators are not taken into account - for parameter in self.walk(node.ast[::2], data): - result.update(parameter) - else: - result = self.walk(node.ast, 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: value} + return {node.name.lower(): value} def walk_GenericExpression(self, node, data): if node.value is None: diff --git a/PySpice/Spice/SpiceGrammar.py b/PySpice/Spice/SpiceGrammar.py index abffa0c95..55e7f6145 100644 --- a/PySpice/Spice/SpiceGrammar.py +++ b/PySpice/Spice/SpiceGrammar.py @@ -17,6 +17,7 @@ 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 @@ -24,59 +25,39 @@ class SpiceBuffer(Buffer): - def __init__( - self, - text, - whitespace=None, - nameguard=None, - comments_re=None, - eol_comments_re=None, - ignorecase=True, - namechars='', - **kwargs - ): - super().__init__( - text, - whitespace=whitespace, - nameguard=nameguard, - comments_re=comments_re, - eol_comments_re=eol_comments_re, - ignorecase=ignorecase, - namechars=namechars, - **kwargs + 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, - whitespace=None, - nameguard=None, - comments_re=None, - eol_comments_re=None, - ignorecase=True, - left_recursion=True, - parseinfo=True, - keywords=None, - namechars='', - tokenizercls=SpiceBuffer, - **kwargs - ): - if keywords is None: - keywords = KEYWORDS - super().__init__( - whitespace=whitespace, - nameguard=nameguard, - comments_re=comments_re, - eol_comments_re=eol_comments_re, - ignorecase=ignorecase, - left_recursion=left_recursion, - parseinfo=parseinfo, - keywords=keywords, - namechars=namechars, - tokenizercls=tokenizercls, - **kwargs + 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 @@ -93,7 +74,7 @@ def block1(): self._st_() self._closure(block1) self._text_() - self.name_last_node('title') + self.add_last_node_to_name('title') def block3(): self._newline_() @@ -103,7 +84,12 @@ def block4(): self._st_() self._closure(block4) self._text_() - self.name_last_node('title') + self.add_last_node_to_name('title') + + self._define( + [], + ['title'] + ) self._closure(block3) def block6(): @@ -119,7 +105,7 @@ def block7(): self._inline_comment_() self._error( 'expecting one of: ' - ' ' + ' ' ) self._newline_() self._closure(block6) @@ -134,9 +120,10 @@ def block9(): self._token('.END') self._end_sep_() self._check_eof() + self._define( - ['lines', 'title'], - [] + ['lines'], + ['title'] ) @tatsumasu('Lines') @@ -161,7 +148,7 @@ def _circuit_line_(self): # noqa self._encrypted_() self._error( 'expecting one of: ' - ' ' + ' ' ) self._end_sep_() self.name_last_node('@') @@ -188,7 +175,7 @@ def _netlist_line_(self): # noqa self._encrypted_() self._error( 'expecting one of: ' - ' ' + ' ' ) self._end_sep_() self.name_last_node('@') @@ -275,15 +262,17 @@ def _device_(self): # noqa 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' " + '' '' - '' '' - ' ' - ' ' - ' ' + ' ' + ' ' + ' ' + ' ' + '' + '' '' ) @@ -313,7 +302,7 @@ def _nonlinear_dependent_source_(self): # noqa self._token('I') self._error( 'expecting one of: ' - "'V' 'I'" + "'I' 'V'" ) self.name_last_node('magnitude') with self._optional(): @@ -330,6 +319,12 @@ def _nonlinear_dependent_source_(self): # noqa 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'], [] @@ -349,6 +344,11 @@ def _abm_expression_(self): # noqa self._sep_() self.name_last_node('sep') self._rc_() + + self._define( + ['sep'], + [] + ) with self._option(): self._braced_expression_() self.name_last_node('@') @@ -357,12 +357,8 @@ def _abm_expression_(self): # noqa self.name_last_node('@') self._error( 'expecting one of: ' - "'{' 'tablefile'" + "'tablefile' '{' " ) - self._define( - ['sep'], - [] - ) @tatsumasu('Capacitor') def _capacitor_(self): # noqa @@ -392,6 +388,16 @@ def _capacitor_(self): # noqa 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') @@ -402,6 +408,16 @@ def _capacitor_(self): # noqa 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: ' '' @@ -411,6 +427,12 @@ def _capacitor_(self): # noqa 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'], [] @@ -441,11 +463,22 @@ def _diode_(self): # noqa 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'], [] @@ -481,13 +514,18 @@ def _voltage_controlled_voltage_source_(self): # noqa 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(): @@ -500,16 +538,17 @@ def _voltage_controlled_voltage_source_(self): # noqa 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'], [] @@ -547,10 +586,16 @@ def _current_controlled_current_source_(self): # noqa 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'], [] @@ -586,13 +631,18 @@ def _voltage_controlled_current_source_(self): # noqa 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(): @@ -605,16 +655,17 @@ def _voltage_controlled_current_source_(self): # noqa 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'], [] @@ -652,10 +703,16 @@ def _current_controlled_voltage_source_(self): # noqa 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'], [] @@ -675,6 +732,7 @@ def _control_value_(self): # noqa self.name_last_node('sep') self._braced_expression_() self.name_last_node('expression') + self._define( ['expression', 'sep', 'type'], [] @@ -721,12 +779,12 @@ def _control_table_(self): # noqa self.name_last_node('sep') self._rp_() - def block11(): + def block12(): - def block12(): + def block13(): self._sep_() self.name_last_node('sep') - self._positive_closure(block12) + self._positive_closure(block13) self._lp_() with self._optional(): self._sep_() @@ -746,7 +804,17 @@ def block12(): self._sep_() self.name_last_node('sep') self._rp_() - self._closure(block11) + + self._define( + ['sep'], + ['input', 'output'] + ) + self._closure(block12) + + self._define( + ['sep'], + ['input', 'output'] + ) with self._option(): with self._group(): self._value_() @@ -761,12 +829,12 @@ def block12(): self._value_() self.add_last_node_to_name('output') - def block24(): + def block25(): - def block25(): + def block26(): self._sep_() self.name_last_node('sep') - self._positive_closure(block25) + self._positive_closure(block26) self._value_() self.add_last_node_to_name('input') with self._optional(): @@ -778,11 +846,22 @@ def block25(): self.name_last_node('sep') self._value_() self.add_last_node_to_name('output') - self._closure(block24) + + 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'] @@ -836,6 +915,11 @@ def block5(): 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') @@ -843,6 +927,11 @@ def block5(): 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') @@ -851,6 +940,7 @@ def block5(): ' ' ) self._join(block5, sep5) + self._define( ['coefficient', 'sep', 'value'], ['negative', 'positive'] @@ -895,6 +985,7 @@ def block5(): ' ' ) self._join(block5, sep5) + self._define( ['sep', 'value'], ['coefficient', 'device'] @@ -924,8 +1015,18 @@ def _current_source_(self): # noqa 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') @@ -941,11 +1042,32 @@ def _current_source_(self): # noqa 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'], [] @@ -980,11 +1102,22 @@ def _jfet_(self): # noqa 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'], [] @@ -1005,6 +1138,11 @@ def block1(): 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') @@ -1015,6 +1153,12 @@ def block1(): self.name_last_node('sep') self._model_name_() self.name_last_node('model') + + self._define( + ['model', 'sep'], + [] + ) + self._define( ['dev', 'model', 'sep', 'value'], ['inductor'] @@ -1048,6 +1192,16 @@ def _inductor_(self): # noqa 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') @@ -1058,6 +1212,16 @@ def _inductor_(self): # noqa 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: ' '' @@ -1067,6 +1231,12 @@ def _inductor_(self): # noqa 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'], [] @@ -1124,7 +1294,7 @@ def block12(): self._sep_() self.name_last_node('sep') - def sep18(): + def sep19(): with self._group(): with self._optional(): self._sep_() @@ -1134,10 +1304,20 @@ def sep18(): self._sep_() self.name_last_node('sep') - def block18(): + self._define( + ['sep'], + [] + ) + + def block19(): self._value_() self.name_last_node('value') - self._join(block18, sep18) + self._join(block19, sep19) + + self._define( + ['name', 'sep', 'value'], + [] + ) with self._option(): self._parameter_() self.name_last_node('parameter') @@ -1147,6 +1327,12 @@ def block18(): ) 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'], [] @@ -1179,11 +1365,21 @@ def _bjt_(self): # noqa 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(): @@ -1191,11 +1387,22 @@ def _bjt_(self): # noqa 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'], [] @@ -1214,13 +1421,9 @@ def _substrate_node_(self): # noqa self._node_() self._error( 'expecting one of: ' - "[0-9]+ '~'" + "'[' [0-9]+" ) self.name_last_node('substrate') - self._define( - ['substrate'], - [] - ) @tatsumasu('Resistor') def _resistor_(self): # noqa @@ -1250,6 +1453,16 @@ def _resistor_(self): # noqa 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') @@ -1260,6 +1473,16 @@ def _resistor_(self): # noqa 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: ' '' @@ -1269,6 +1492,12 @@ def _resistor_(self): # noqa 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'], [] @@ -1308,9 +1537,14 @@ def _switch_(self): # noqa self._token('OFF') self._error( 'expecting one of: ' - "'ON' 'OFF'" + "'OFF' 'ON'" ) self.name_last_node('initial_state') + + self._define( + ['initial_state', 'sep'], + [] + ) self._sep_() self.name_last_node('sep') self._token('control') @@ -1323,6 +1557,11 @@ def _switch_(self): # noqa 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') @@ -1345,13 +1584,24 @@ def _switch_(self): # noqa self._token('OFF') self._error( 'expecting one of: ' - "'ON' 'OFF'" + "'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'], [] @@ -1370,6 +1620,11 @@ def block1(): 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(':') @@ -1380,6 +1635,12 @@ def block1(): self.name_last_node('sep') self._parameters_() self.name_last_node('parameters') + + self._define( + ['parameters', 'params', 'sep'], + [] + ) + self._define( ['dev', 'parameters', 'params', 'sep'], ['node'] @@ -1409,8 +1670,18 @@ def _voltage_source_(self): # noqa 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') @@ -1426,11 +1697,32 @@ def _voltage_source_(self): # noqa 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'], [] @@ -1467,10 +1759,10 @@ def _transient_specification_(self): # noqa self.name_last_node('@') self._error( 'expecting one of: ' - "'PULSE' 'SIN'" - " 'EXP' " - "'PAT' 'PWL'" - " 'SFFM' " + "'EXP' 'PAT' 'PULSE' 'PWL' 'SFFM' 'SIN'" + ' ' + ' ' + ' ' ) @tatsumasu('TransientPulse') @@ -1494,15 +1786,26 @@ def _transient_pulse_(self): # noqa 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'], [] @@ -1524,6 +1827,7 @@ def block2(): self._gen_expr_() self.name_last_node('value') self._join(block2, sep2) + self._define( ['sep', 'v1', 'value'], [] @@ -1550,15 +1854,26 @@ def _transient_sin_(self): # noqa 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'], [] @@ -1588,6 +1903,7 @@ def block6(): self._gen_expr_() self.name_last_node('value') self._join(block6, sep6) + self._define( ['freq', 'sep', 'v0', 'va', 'value'], [] @@ -1614,15 +1930,26 @@ def _transient_exp_(self): # noqa 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'], [] @@ -1648,6 +1975,7 @@ def block4(): self._gen_expr_() self.name_last_node('value') self._join(block4, sep4) + self._define( ['sep', 'v1', 'v2', 'value'], [] @@ -1674,15 +2002,26 @@ def _transient_pat_(self): # noqa 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'], [] @@ -1721,6 +2060,12 @@ def _pat_arguments_(self): # noqa 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'], [] @@ -1741,8 +2086,9 @@ def _transient_pwl_(self): # noqa self.name_last_node('@') self._error( 'expecting one of: ' - ' ' + ' ' ) + self._define( ['type'], [] @@ -1763,6 +2109,11 @@ def _pwl_file_arguments_(self): # noqa self._filename_() self.name_last_node('filename') self._double_quote_() + + self._define( + ['filename'], + [] + ) with self._option(): self._filename_() self.name_last_node('filename') @@ -1775,6 +2126,12 @@ def _pwl_file_arguments_(self): # noqa self.name_last_node('sep') self._parameters_() self.name_last_node('parameters') + + self._define( + ['parameters', 'sep'], + [] + ) + self._define( ['filename', 'parameters', 'sep'], [] @@ -1786,17 +2143,17 @@ def _pwl_arguments_(self): # noqa with self._choice(): with self._option(): - def block0(): + def block1(): self._sep_() self.name_last_node('sep') - self._closure(block0) + self._closure(block1) self._lp_() self._cut() - def block2(): + def block3(): self._sep_() self.name_last_node('sep') - self._closure(block2) + self._closure(block3) self._value_() self.name_last_node('t') self._sep_() @@ -1804,7 +2161,7 @@ def block2(): self._value_() self.name_last_node('value') - def block7(): + def block8(): self._sep_() self.name_last_node('sep') self._value_() @@ -1813,21 +2170,36 @@ def block7(): self.name_last_node('sep') self._value_() self.name_last_node('value') - self._closure(block7) + + 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') - def block14(): + self._define( + ['parameters', 'sep'], + [] + ) + + def block15(): self._sep_() self.name_last_node('sep') - self._closure(block14) + self._closure(block15) self._rp_() + + self._define( + ['parameters', 'sep', 't', 'value'], + [] + ) with self._option(): - def block16(): + def block17(): self._sep_() self.name_last_node('sep') self._value_() @@ -1836,7 +2208,12 @@ def block16(): self.name_last_node('sep') self._value_() self.name_last_node('value') - self._positive_closure(block16) + + self._define( + ['sep', 't', 'value'], + [] + ) + self._positive_closure(block17) self._error( 'expecting one of: ' ' ' @@ -1846,6 +2223,12 @@ def block16(): self.name_last_node('sep') self._parameters_() self.name_last_node('parameters') + + self._define( + ['parameters', 'sep'], + [] + ) + self._define( ['parameters', 'sep', 't', 'value'], [] @@ -1872,15 +2255,26 @@ def _transient_sffm_(self): # noqa 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'], [] @@ -1906,6 +2300,7 @@ def block4(): self._gen_expr_() self.name_last_node('value') self._join(block4, sep4) + self._define( ['sep', 'v0', 'va', 'value'], [] @@ -1943,14 +2338,13 @@ def _command_(self): # noqa self.name_last_node('@') self._error( 'expecting one of: ' - "'.EMBEDDEDSAMPLING'" - " '.INCLUDE'" - "'.INCL' '.INC' '.LIB'" - ' ' - ' ' - " '.SUBCKT' '.SIMULATOR'" - " '.TITLE' " - "'.AC' '.DC' " + "'.AC' '.DC' '.EMBEDDEDSAMPLING' '.INC'" + "'.INCL' '.INCLUDE' '.LIB' '.SIMULATOR'" + "'.SUBCKT' '.TITLE' " + ' ' + ' ' + ' ' + ' ' ) @tatsumasu('NetlistCmds') @@ -1973,9 +2367,9 @@ def _netlist_cmds_(self): # noqa self.name_last_node('@') self._error( 'expecting one of: ' - "'.DATA' '.IC' '.DCVOLT'" - " '.MODEL' '.PARAM'" - " '.SUBCKT' " + "'.DATA' '.DCVOLT' '.IC' '.MODEL'" + "'.PARAM' '.SUBCKT' " + ' ' ) @tatsumasu('ACCmd') @@ -2003,25 +2397,36 @@ def _ac_cmd_(self): # noqa 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 block10(): + def block11(): self._st_() - self._closure(block10) + self._closure(block11) self._token('=') - def block11(): + def block12(): self._st_() - self._closure(block11) + self._closure(block12) self._id_() self.name_last_node('table') + + self._define( + ['sweep', 'table'], + [] + ) self._error( 'expecting one of: ' - " 'DATA'" + "'DATA' " ) + self._define( ['cmd', 'end', 'points', 'sep', 'start', 'sweep', 'table'], [] @@ -2038,7 +2443,7 @@ def _ac_sweep_type_(self): # noqa self._token('DEC') self._error( 'expecting one of: ' - "'LIN' 'OCT' 'DEC'" + "'DEC' 'LIN' 'OCT'" ) @tatsumasu('DataCmd') @@ -2056,6 +2461,11 @@ def block3(): self.name_last_node('sep') self._id_() self.name_last_node('name') + + self._define( + ['name', 'sep'], + [] + ) self._positive_closure(block3) def block6(): @@ -2063,10 +2473,16 @@ def block6(): 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'], [] @@ -2085,20 +2501,25 @@ def _dc_cmd_(self): # noqa self._token('DATA') self.name_last_node('sweep') - def block3(): + def block4(): self._st_() - self._closure(block3) + self._closure(block4) self._token('=') - def block4(): + def block5(): self._st_() - self._closure(block4) + self._closure(block5) self._id_() self.name_last_node('table') + + self._define( + ['sep', 'sweep', 'table'], + [] + ) with self._option(): with self._group(): - def block6(): + def block7(): with self._choice(): with self._option(): with self._group(): @@ -2107,6 +2528,11 @@ def block6(): 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_() @@ -2123,6 +2549,11 @@ def block6(): 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_() @@ -2154,6 +2585,11 @@ def block6(): 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_() @@ -2167,24 +2603,30 @@ def block6(): self._sep_() self.name_last_node('sep') - def sep33(): + def sep35(): with self._group(): self._sep_() self.name_last_node('sep') - def block33(): + def block35(): self._value_() self.name_last_node('point') - self._positive_join(block33, sep33) + self._positive_join(block35, sep35) + + self._define( + ['name', 'point', 'sep', 'sweep'], + [] + ) self._error( 'expecting one of: ' '' ) - self._positive_closure(block6) + self._positive_closure(block7) self._error( 'expecting one of: ' '' ) + self._define( ['cmd', 'name', 'point', 'points', 'sep', 'start', 'step', 'stop', 'sweep', 'table'], [] @@ -2204,70 +2646,80 @@ def _embedded_sampling_cmd_(self): # noqa self._token('param') self.name_last_node('parameter') - def block3(): + def block4(): self._st_() - self._closure(block3) + self._closure(block4) self._token('=') - def block4(): + def block5(): self._st_() - self._closure(block4) + self._closure(block5) - def sep5(): + def sep6(): with self._group(): self._es_sep_() - def block5(): + def block6(): self._id_() self.name_last_node('name') - self._positive_join(block5, sep5) + self._positive_join(block6, sep6) self._sep_() self.name_last_node('sep') self._token('type') self.name_last_node('parameter') - def block9(): + def block10(): self._st_() - self._closure(block9) + self._closure(block10) self._token('=') - def block10(): + def block11(): self._st_() - self._closure(block10) + self._closure(block11) - def sep11(): + def sep12(): with self._group(): self._es_sep_() - def block11(): + def block12(): self._es_parameter_type_() self.name_last_node('type') - self._positive_join(block11, sep11) + self._positive_join(block12, sep12) - def block13(): + def block14(): self._sep_() self.name_last_node('sep') self._es_parameter_name_() self.name_last_node('parameter') - def block16(): + def block17(): self._st_() - self._closure(block16) + self._closure(block17) self._token('=') - def block17(): + def block18(): self._st_() - self._closure(block17) + self._closure(block18) - def sep18(): + def sep19(): with self._group(): self._es_sep_() - def block18(): + def block19(): self._gen_expr_() self.name_last_node('value') - self._positive_join(block18, sep18) - self._closure(block13) + 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_() @@ -2275,21 +2727,27 @@ def block18(): self._token('useExpr') self.name_last_node('parameter') - def block22(): + def block23(): self._st_() - self._closure(block22) + self._closure(block23) self._token('=') self._cut() - def block23(): + def block24(): self._st_() - self._closure(block23) + 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'], [] @@ -2306,7 +2764,7 @@ def _es_parameter_type_(self): # noqa self._token('GAMMA') self._error( 'expecting one of: ' - "'UNIFORM' 'NORMAL' 'GAMMA'" + "'GAMMA' 'NORMAL' 'UNIFORM'" ) @tatsumasu() @@ -2326,8 +2784,8 @@ def _es_parameter_name_(self): # noqa self._token('upper_bounds') self._error( 'expecting one of: ' - "'alpha' 'beta' 'means' 'std_deviations'" - "'lower_bounds' 'upper_bounds'" + "'alpha' 'beta' 'lower_bounds' 'means'" + "'std_deviations' 'upper_bounds'" ) @tatsumasu() @@ -2349,7 +2807,7 @@ def _ic_cmd_(self): # noqa self._token('.DCVOLT') self._error( 'expecting one of: ' - "'.IC' '.DCVOLT'" + "'.DCVOLT' '.IC'" ) self.name_last_node('cmd') self._cut() @@ -2357,7 +2815,7 @@ def _ic_cmd_(self): # noqa with self._choice(): with self._option(): - def block2(): + def block3(): self._sep_() self.name_last_node('sep') self._token('V') @@ -2367,35 +2825,46 @@ def block2(): self.name_last_node('node') self._rp_() - def block5(): + def block6(): self._st_() - self._closure(block5) + self._closure(block6) self._token('=') - def block6(): + def block7(): self._st_() - self._closure(block6) + self._closure(block7) self._gen_expr_() self.name_last_node('value') - self._positive_closure(block2) + + self._define( + ['node', 'sep', 'value'], + [] + ) + self._positive_closure(block3) with self._option(): - def block8(): + def block9(): self._sep_() self.name_last_node('sep') self._node_() self.name_last_node('node') - def block11(): + def block12(): self._st_() - self._positive_closure(block11) + self._positive_closure(block12) self._gen_expr_() self.name_last_node('value') - self._positive_closure(block8) + + self._define( + ['node', 'sep', 'value'], + [] + ) + self._positive_closure(block9) self._error( 'expecting one of: ' '' ) + self._define( ['cmd', 'node', 'sep', 'value'], [] @@ -2413,7 +2882,7 @@ def _include_cmd_(self): # noqa self._token('.INC') self._error( 'expecting one of: ' - "'.INCLUDE' '.INCL' '.INC'" + "'.INC' '.INCL' '.INCLUDE'" ) self.name_last_node('cmd') self._cut() @@ -2427,19 +2896,30 @@ def _include_cmd_(self): # noqa 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'], [] @@ -2462,8 +2942,9 @@ def _lib_cmd_(self): # noqa self.name_last_node('block') self._error( 'expecting one of: ' - ' ' + ' ' ) + self._define( ['block', 'call', 'cmd', 'sep'], [] @@ -2479,23 +2960,34 @@ def _lib_call_(self): # noqa 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'], [] @@ -2531,15 +3023,26 @@ def _model_cmd_(self): # noqa 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'], [] @@ -2600,11 +3103,11 @@ def _model_type_(self): # noqa self._token('ZOD') self._error( 'expecting one of: ' - "'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'" + "'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') @@ -2616,6 +3119,7 @@ def _param_cmd_(self): # noqa self.name_last_node('sep') self._parameters_() self.name_last_node('parameters') + self._define( ['cmd', 'parameters', 'sep'], [] @@ -2630,6 +3134,7 @@ def _simulator_cmd_(self): # noqa self.name_last_node('sep') self._id_() self.name_last_node('simulator') + self._define( ['cmd', 'sep', 'simulator'], [] @@ -2653,6 +3158,11 @@ def block3(): with self._ifnot(): self._token(':') self.name_last_node('node') + + self._define( + ['node', 'sep'], + [] + ) self._closure(block3) with self._optional(): self._sep_() @@ -2664,6 +3174,11 @@ def block3(): 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') @@ -2685,6 +3200,12 @@ def block13(): self._positive_closure(block13) self._model_name_() self.name_last_node('name') + + self._define( + ['name'], + [] + ) + self._define( ['cmd', 'lines', 'name', 'node', 'parameters', 'sep'], [] @@ -2712,6 +3233,12 @@ def block4(): self._positive_closure(block4) self._id_() self.name_last_node('entry') + + self._define( + ['entry'], + [] + ) + self._define( ['entry', 'lines', 'sep'], [] @@ -2724,6 +3251,7 @@ def _title_cmd_(self): # noqa self._cut() self._text_() self.name_last_node('title') + self._define( ['cmd', 'title'], [] @@ -2732,7 +3260,7 @@ def _title_cmd_(self): # noqa @tatsumasu('Parameters') def _parameters_(self): # noqa self._parameter_() - self.name_last_node('@') + self.add_last_node_to_name('@') def block1(): with self._group(): @@ -2741,20 +3269,17 @@ def block1(): with self._group(): with self._optional(): self._sep_() - self.name_last_node('@') self._comma_() with self._optional(): self._sep_() - self.name_last_node('@') with self._option(): self._sep_() - self.name_last_node('@') self._error( 'expecting one of: ' ' ' ) self._parameter_() - self.name_last_node('@') + self.add_last_node_to_name('@') self._closure(block1) @tatsumasu('Parameter') @@ -2782,7 +3307,13 @@ def block4(): 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'], [] @@ -2799,13 +3330,9 @@ def _gen_expr_(self): # noqa self.name_last_node('value') self._error( 'expecting one of: ' - ' ' - ' ' + ' ' + ' ' ) - self._define( - ['braced', 'value'], - [] - ) @tatsumasu('TableFile') def _tablefile_(self): # noqa @@ -2827,6 +3354,11 @@ def _tablefile_(self): # noqa self._filename_() self.name_last_node('filename') self._double_quote_() + + self._define( + ['filename'], + [] + ) with self._option(): self._filename_() self.name_last_node('filename') @@ -2838,6 +3370,7 @@ def _tablefile_(self): # noqa self._sep_() self.name_last_node('sep') self._rp_() + self._define( ['filename', 'func', 'sep'], [] @@ -2855,6 +3388,7 @@ def _braced_expression_(self): # noqa self._sep_() self.name_last_node('sep') self._rc_() + self._define( ['sep'], [] @@ -2872,6 +3406,7 @@ def _parenthesis_nodes_(self): # noqa self._sep_() self.name_last_node('sep') self._rp_() + self._define( ['sep'], [] @@ -2895,12 +3430,18 @@ def _circuit_nodes_(self): # noqa 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'], [] @@ -2918,13 +3459,9 @@ def _expression_(self): # noqa self.name_last_node('term') self._error( 'expecting one of: ' - ' ' - ' ' + ' ' + ' ' ) - self._define( - ['term', 'ternary'], - [] - ) @tatsumasu('Ternary') @nomemo @@ -2952,6 +3489,7 @@ def _ternary_(self): # noqa self.name_last_node('sep') self._expression_() self.name_last_node('y') + self._define( ['op', 'sep', 't', 'x', 'y'], [] @@ -2962,10 +3500,6 @@ def _ternary_(self): # noqa def _conditional_expression_(self): # noqa self._boolean_or_() self.name_last_node('expr') - self._define( - ['expr'], - [] - ) @tatsumasu('Or') @nomemo @@ -2983,6 +3517,12 @@ def _boolean_or_(self): # noqa self.name_last_node('sep') self._boolean_or_() self.name_last_node('right') + + self._define( + ['op', 'right', 'sep'], + [] + ) + self._define( ['left', 'op', 'right', 'sep'], [] @@ -3004,6 +3544,12 @@ def _boolean_xor_(self): # noqa self.name_last_node('sep') self._boolean_xor_() self.name_last_node('right') + + self._define( + ['op', 'right', 'sep'], + [] + ) + self._define( ['left', 'op', 'right', 'sep'], [] @@ -3025,6 +3571,12 @@ def _boolean_and_(self): # noqa self.name_last_node('sep') self._boolean_and_() self.name_last_node('right') + + self._define( + ['op', 'right', 'sep'], + [] + ) + self._define( ['left', 'op', 'right', 'sep'], [] @@ -3038,6 +3590,7 @@ def _boolean_not_(self): # noqa self.name_last_node('op') self._relational_() self.name_last_node('operator') + self._define( ['op', 'operator'], [] @@ -3069,7 +3622,7 @@ def _relational_(self): # noqa self._token('<') self._error( 'expecting one of: ' - "'==' '!=' '>=' '<=' '>' '<'" + "'!=' '<' '<=' '==' '>' '>='" ) self.name_last_node('op') with self._optional(): @@ -3077,20 +3630,21 @@ def _relational_(self): # noqa 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: ' - ' ' - ' ' - ' ' - '' + ' ' + '' + ' ' + ' ' ) - self._define( - ['factor', 'left', 'op', 'right', 'sep'], - [] - ) @tatsumasu('ConditionalFactor') def _conditional_factor_(self): # noqa @@ -3106,17 +3660,18 @@ def _conditional_factor_(self): # noqa 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: ' - "'(' 'TRUE' 'FALSE' " + "'(' 'FALSE' 'TRUE' " ) - self._define( - ['boolean', 'expr', 'sep'], - [] - ) @tatsumasu('Term') def _term_(self): # noqa @@ -3147,6 +3702,12 @@ def _add_sub_(self): # noqa self.name_last_node('sep') self._add_sub_() self.name_last_node('right') + + self._define( + ['op', 'right', 'sep'], + [] + ) + self._define( ['left', 'op', 'right', 'sep'], [] @@ -3170,7 +3731,7 @@ def _prod_(self): # noqa self._token('%') self._error( 'expecting one of: ' - "'*' '/' '%'" + "'%' '*' '/'" ) self.name_last_node('op') with self._optional(): @@ -3178,6 +3739,12 @@ def _prod_(self): # noqa self.name_last_node('sep') self._prod_() self.name_last_node('right') + + self._define( + ['op', 'right', 'sep'], + [] + ) + self._define( ['left', 'op', 'right', 'sep'], [] @@ -3199,6 +3766,7 @@ def _unary_(self): # noqa self.name_last_node('op') self._exp_() self.name_last_node('operator') + self._define( ['op', 'operator'], [] @@ -3219,6 +3787,12 @@ def _exp_(self): # noqa self.name_last_node('sep') self._exp_() self.name_last_node('right') + + self._define( + ['op', 'right', 'sep'], + [] + ) + self._define( ['left', 'op', 'right', 'sep'], [] @@ -3235,10 +3809,10 @@ def _functional_(self): # noqa self.name_last_node('@') self._error( 'expecting one of: ' - ' ' - ' ' - ' ' - ' ' + ' ' + ' ' + ' ' + ' ' ) @tatsumasu('Variable') @@ -3255,6 +3829,11 @@ def _variable_(self): # noqa self._sep_() self.name_last_node('sep') self._rc_() + + self._define( + ['sep', 'variable'], + [] + ) with self._option(): self._var_id_() self.name_last_node('variable') @@ -3263,14 +3842,10 @@ def _variable_(self): # noqa self.name_last_node('factor') self._error( 'expecting one of: ' - "'{' [a-zA-Z_`@#\\$][a-zA-Z0-" - '9_:`@#\\.\\$]*[a-zA-Z0-9_`@#\\.\\$] [a-zA-Z]' - ' ' + "'{' " + '[a-zA-Z] [a-zA-Z_`@#\\$][a-zA-Z0-' + '9_:`@#\\.\\$]*[a-zA-Z0-9_`@#\\.\\$]' ) - self._define( - ['factor', 'sep', 'variable'], - [] - ) @tatsumasu('Factor') def _factor_(self): # noqa @@ -3286,18 +3861,19 @@ def _factor_(self): # noqa 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: ' - "'(' " + "'(' " '' ) - self._define( - ['sep'], - [] - ) @tatsumasu('Functions') def _functions_(self): # noqa @@ -3326,16 +3902,17 @@ def _functions_(self): # noqa self._v_func_() self._error( 'expecting one of: ' - "'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' " - "'atan2' 'ddx' 'agauss' 'gauss' 'if'" - " 'limit' 'min' 'max' 'pwrs'" - "'pow' 'pwr' 'sign' 'rand'" - "'aunif' 'unif' 'i' 'v' " + "'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() @@ -3410,12 +3987,12 @@ def _functions_1_(self): # noqa self._token('tan') self._error( 'expecting one of: ' - "'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'" + "'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(): @@ -3432,6 +4009,7 @@ def _functions_1_(self): # noqa self._sep_() self.name_last_node('sep') self._rp_() + self._define( ['func', 'sep', 'x'], [] @@ -3464,6 +4042,7 @@ def _atan2_(self): # noqa self._sep_() self.name_last_node('sep') self._rp_() + self._define( ['func', 'sep', 'x', 'y'], [] @@ -3496,6 +4075,7 @@ def _ddx_(self): # noqa self._sep_() self.name_last_node('sep') self._rp_() + self._define( ['f', 'func', 'sep', 'x'], [] @@ -3546,6 +4126,7 @@ def _gauss_(self): # noqa self._sep_() self.name_last_node('sep') self._rp_() + self._define( ['alpha', 'func', 'mu', 'n', 'sep'], [] @@ -3571,6 +4152,7 @@ def _i_func_(self): # noqa self._sep_() self.name_last_node('sep') self._rp_() + self._define( ['device', 'func', 'sep'], [] @@ -3612,6 +4194,7 @@ def _if_func_(self): # noqa self._sep_() self.name_last_node('sep') self._rp_() + self._define( ['func', 'sep', 't', 'x', 'y'], [] @@ -3653,6 +4236,7 @@ def _limit_(self): # noqa self._sep_() self.name_last_node('sep') self._rp_() + self._define( ['func', 'sep', 'x', 'y', 'z'], [] @@ -3676,7 +4260,7 @@ def _functions_2_(self): # noqa self._token('sign') self._error( 'expecting one of: ' - "'min' 'max' 'pwrs' 'pow' 'pwr' 'sign'" + "'max' 'min' 'pow' 'pwr' 'pwrs' 'sign'" ) self.name_last_node('func') with self._optional(): @@ -3702,6 +4286,7 @@ def _functions_2_(self): # noqa self._sep_() self.name_last_node('sep') self._rp_() + self._define( ['func', 'sep', 'x', 'y'], [] @@ -3720,6 +4305,7 @@ def _rand_(self): # noqa self._sep_() self.name_last_node('sep') self._rp_() + self._define( ['func', 'sep'], [] @@ -3761,6 +4347,7 @@ def _unif_(self): # noqa self._sep_() self.name_last_node('sep') self._rp_() + self._define( ['alpha', 'func', 'mu', 'sep'], [] @@ -3793,7 +4380,13 @@ def _v_func_(self): # noqa with self._optional(): self._sep_() self.name_last_node('sep') + + self._define( + ['node', 'sep'], + [] + ) self._rp_() + self._define( ['func', 'node', 'sep'], [] @@ -3816,7 +4409,7 @@ def _special_variables_(self): # noqa self._token('pi') self._error( 'expecting one of: ' - "'time' 'temper' 'temp' 'freq' 'vt' 'pi'" + "'freq' 'pi' 'temp' 'temper' 'time' 'vt'" ) @tatsumasu('Value') @@ -3830,6 +4423,11 @@ def _value_(self): # noqa 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') @@ -3838,7 +4436,7 @@ def _value_(self): # noqa self.name_last_node('real') self._error( 'expecting one of: ' - ' ' + ' ' ) with self._optional(): with self._choice(): @@ -3851,6 +4449,7 @@ def _value_(self): # noqa ' ' ) self.name_last_node('unit') + self._define( ['imag', 'real', 'unit'], [] @@ -3861,6 +4460,7 @@ def _imag_value_(self): # noqa self._number_scale_() self.name_last_node('value') self._token('J') + self._define( ['value'], [] @@ -3870,10 +4470,6 @@ def _imag_value_(self): # noqa def _real_value_(self): # noqa self._number_scale_() self.name_last_node('value') - self._define( - ['value'], - [] - ) @tatsumasu() def _freq_value_(self): # noqa @@ -3882,6 +4478,7 @@ def _freq_value_(self): # noqa with self._optional(): self._hz_() self.name_last_node('unit') + self._define( ['unit', 'value'], [] @@ -3905,6 +4502,11 @@ def _number_scale_(self): # noqa ' ' ) self.name_last_node('scale') + + self._define( + ['scale', 'value'], + [] + ) with self._option(): self._integer_() self.name_last_node('value') @@ -3920,16 +4522,17 @@ def _number_scale_(self): # noqa ' ' ) 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]+ ' + ' [\\+\\-]?(([0-' + '9]+(\\.[0-9]*)?)|(\\.[0-9]+))([eE][\\-' + '\\+]?[0-9]{1,3})? [\\+\\-]?[0-9]+' ) - self._define( - ['scale', 'value'], - [] - ) @tatsumasu() def _suffix_(self): # noqa @@ -3976,7 +4579,7 @@ def _boolean_(self): # noqa self._token('FALSE') self._error( 'expecting one of: ' - "'TRUE' 'FALSE'" + "'FALSE' 'TRUE'" ) @tatsumasu('ModelName') @@ -3989,6 +4592,12 @@ def _model_name_(self): # noqa self._sep_() self.name_last_node('sep') self._token('=') + + self._define( + ['sep'], + [] + ) + self._define( ['name', 'sep'], [] @@ -4002,6 +4611,7 @@ def block1(): self._binary_() self._positive_closure(block1) self.name_last_node('pattern') + self._define( ['pattern'], [] @@ -4037,6 +4647,12 @@ def _node_(self): # noqa self._sep_() self.name_last_node('sep') self._token('=') + + self._define( + ['sep'], + [] + ) + self._define( ['node', 'sep'], [] @@ -4051,8 +4667,8 @@ def _id_(self): # noqa self._pattern('[a-zA-Z_`@#\\$]') self._error( 'expecting one of: ' - '[a-zA-Z_`@#\\$][a-zA-Z0-9_:`@#\\.\\$\\/]*[a-' - 'zA-Z0-9_`@#\\.\\$] [a-zA-Z_`@#\\$]' + '[a-zA-Z_`@#\\$] [a-zA-Z_`@#\\$][a-zA-Z0-' + '9_:`@#\\.\\$\\/]*[a-zA-Z0-9_`@#\\.\\$]' ) @tatsumasu() @@ -4064,8 +4680,8 @@ def _var_id_(self): # noqa self._pattern('[a-zA-Z]') self._error( 'expecting one of: ' - '[a-zA-Z_`@#\\$][a-zA-Z0-9_:`@#\\.\\$]*[a-' - 'zA-Z0-9_`@#\\.\\$] [a-zA-Z]' + '[a-zA-Z] [a-zA-Z_`@#\\$][a-zA-Z0-' + '9_:`@#\\.\\$]*[a-zA-Z0-9_`@#\\.\\$]' ) @tatsumasu() @@ -4075,18 +4691,18 @@ def _end_sep_(self): # noqa self._cmd_net_sep_() self.name_last_node('@') - def block1(): + def block2(): self._st_() - self._closure(block1) + self._closure(block2) with self._option(): - def block2(): + def block3(): self._st_() - self._positive_closure(block2) + self._positive_closure(block3) self._error( 'expecting one of: ' - ' ' - ' [ \\t]' + ' ' + ' [ \\t]' ) @tatsumasu() @@ -4094,28 +4710,28 @@ def _sep_(self): # noqa with self._choice(): with self._option(): - def block0(): + def block1(): self._cmd_net_sep_() self.name_last_node('@') - def block2(): + def block3(): self._st_() - self._closure(block2) + self._closure(block3) self._token('+') - def block3(): + def block4(): self._st_() - self._closure(block3) - self._positive_closure(block0) + self._closure(block4) + self._positive_closure(block1) with self._option(): - def block4(): + def block5(): self._st_() - self._positive_closure(block4) + self._positive_closure(block5) self._error( 'expecting one of: ' - ' ' - ' [ \\t]' + ' ' + ' [ \\t]' ) @tatsumasu('Separator') @@ -4145,11 +4761,17 @@ def block4(): 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'], [] @@ -4248,7 +4870,7 @@ def _ws_(self): # noqa self._pattern('[^\\S\\r\\n]*') -class SpiceSemantics(object): +class SpiceSemantics: def start(self, ast): # noqa return ast @@ -4697,9 +5319,7 @@ def ws(self, ast): # noqa return ast -def main(filename, start=None, **kwargs): - if start is None: - start = 'start' +def main(filename, **kwargs): if not filename or filename == '-': text = sys.stdin.read() else: @@ -4708,7 +5328,6 @@ def main(filename, start=None, **kwargs): parser = SpiceParser() return parser.parse( text, - rule_name=start, filename=filename, **kwargs ) diff --git a/PySpice/Spice/SpiceModel.py b/PySpice/Spice/SpiceModel.py index 035e3594a..e2b9daafe 100644 --- a/PySpice/Spice/SpiceModel.py +++ b/PySpice/Spice/SpiceModel.py @@ -11,10 +11,14 @@ 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 @@ -25,638 +29,735 @@ def __init__(self, context=None, types=None): t for t in globals().values() if type(t) is type and issubclass(t, ModelBase) ] + (types or []) - super(SpiceModelBuilderSemantics, self).__init__(context=context, types=types) + super().__init__(context=context, types=types) +@dataclass(eq=False) class Circuit(ModelBase): - lines = None - title = None + 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 = None - expr = None - magnitude = None - negative = None - parameters = None - positive = None - sep = None + 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 = None - model = None - negative = None - parameters = None - positive = None - sep = None - value = None + 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 = None - dev = None - model = None - negative = None - parameters = None - positive = None - sep = None + 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 = None - dev = None - negative = None - nodes = None - positive = None - sep = None - transconductance = None + 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 = None - dev = None - device = None - gain = None - negative = None - positive = None - sep = None + 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 = None - dev = None - negative = None - nodes = None - positive = None - sep = None - transconductance = None + 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 = None - dev = None - device = None - negative = None - positive = None - sep = None - transresistance = None + 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 = None - sep = None - type = None + expression: Any = None + sep: Any = None + type: Any = None +@dataclass(eq=False) class ControlTable(ModelBase): - expr = None - input = None - output = None - sep = None - type = None + expr: Any = None + input: Any = None + output: Any = None + sep: Any = None + type: Any = None +@dataclass(eq=False) class ControlVoltagePoly(ModelBase): - coefficient = None - negative = None - positive = None - sep = None - value = None + coefficient: Any = None + negative: Any = None + positive: Any = None + sep: Any = None + value: Any = None +@dataclass(eq=False) class ControlCurrentPoly(ModelBase): - coefficient = None - device = None - sep = None - value = None + coefficient: Any = None + device: Any = None + sep: Any = None + value: Any = None +@dataclass(eq=False) class CurrentSource(ModelBase): - ac_magnitude = None - ac_phase = None - dc_value = None - dev = None - negative = None - positive = None - sep = None - transient = None + 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 = None - dev = None - drain = None - gate = None - model = None - parameters = None - sep = None - source = None + 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 = None - inductor = None - model = None - sep = None - value = None + dev: Any = None + inductor: Any = None + model: Any = None + sep: Any = None + value: Any = None +@dataclass(eq=False) class Inductor(ModelBase): - dev = None - model = None - negative = None - parameters = None - positive = None - sep = None - value = None + 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 = None - dev = None - drain = None - gate = None - model = None - name = None - param = None - parameter = None - sep = None - source = None - value = None - - + 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 = None - base = None - collector = None - dev = None - emitter = None - model = None - parameters = None - sep = None - substrate = None - thermal = None - - + 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 = None + substrate: Any = None +@dataclass(eq=False) class Resistor(ModelBase): - dev = None - model = None - negative = None - parameters = None - positive = None - sep = None - value = None + 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 = None - control_p = None - dev = None - initial_state = None - model = None - negative = None - positive = None - sep = None + 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 = None - node = None - parameters = None - params = None - sep = None + dev: Any = None + node: Any = None + parameters: Any = None + params: Any = None + sep: Any = None +@dataclass(eq=False) class VoltageSource(ModelBase): - ac_magnitude = None - ac_phase = None - dc_value = None - dev = None - negative = None - positive = None - sep = None - transient = None + 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 = None - type = None + sep: Any = None + type: Any = None +@dataclass(eq=False) class PulseArguments(ModelBase): - sep = None - v1 = None - value = None + sep: Any = None + v1: Any = None + value: Any = None +@dataclass(eq=False) class TransientSin(ModelBase): - sep = None - type = None + sep: Any = None + type: Any = None +@dataclass(eq=False) class SinArguments(ModelBase): - freq = None - sep = None - v0 = None - va = None - value = None + freq: Any = None + sep: Any = None + v0: Any = None + va: Any = None + value: Any = None +@dataclass(eq=False) class TransientExp(ModelBase): - sep = None - type = None + sep: Any = None + type: Any = None +@dataclass(eq=False) class ExpArguments(ModelBase): - sep = None - v1 = None - v2 = None - value = None + sep: Any = None + v1: Any = None + v2: Any = None + value: Any = None +@dataclass(eq=False) class TransientPat(ModelBase): - sep = None - type = None + sep: Any = None + type: Any = None +@dataclass(eq=False) class PatArguments(ModelBase): - data = None - repeat = None - sep = None - td = None - tf = None - tr = None - tsample = None - vhi = None - vlo = None - - + 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 = None + type: Any = None +@dataclass(eq=False) class PWLFileArguments(ModelBase): - filename = None - parameters = None - sep = None + filename: Any = None + parameters: Any = None + sep: Any = None +@dataclass(eq=False) class PWLArguments(ModelBase): - parameters = None - sep = None - t = None - value = None + parameters: Any = None + sep: Any = None + t: Any = None + value: Any = None +@dataclass(eq=False) class TransientSFFM(ModelBase): - sep = None - type = None + sep: Any = None + type: Any = None +@dataclass(eq=False) class SFFMArguments(ModelBase): - sep = None - v0 = None - va = None - value = None + 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 = None - end = None - points = None - sep = None - start = None - sweep = None - table = None + 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 = None - name = None - sep = None - table = None - value = None + cmd: Any = None + name: Any = None + sep: Any = None + table: Any = None + value: Any = None +@dataclass(eq=False) class DCCmd(ModelBase): - cmd = None - name = None - point = None - points = None - sep = None - start = None - step = None - stop = None - sweep = None - table = None - - + 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 = None - name = None - parameter = None - sep = None - type = None - value = None + 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 = None - node = None - sep = None - value = None + cmd: Any = None + node: Any = None + sep: Any = None + value: Any = None +@dataclass(eq=False) class IncludeCmd(ModelBase): - cmd = None - filename = None - sep = None + cmd: Any = None + filename: Any = None + sep: Any = None +@dataclass(eq=False) class LibCmd(ModelBase): - block = None - call = None - cmd = None - sep = None + block: Any = None + call: Any = None + cmd: Any = None + sep: Any = None +@dataclass(eq=False) class LibCall(ModelBase): - entry = None - filename = None - sep = None + entry: Any = None + filename: Any = None + sep: Any = None +@dataclass(eq=False) class ModelCmd(ModelBase): - cmd = None - name = None - parameters = None - sep = None - type = None + cmd: Any = None + name: Any = None + parameters: Any = None + sep: Any = None + type: Any = None +@dataclass(eq=False) class ParamCmd(ModelBase): - cmd = None - parameters = None - sep = None + cmd: Any = None + parameters: Any = None + sep: Any = None +@dataclass(eq=False) class SimulatorCmd(ModelBase): - cmd = None - sep = None - simulator = None + cmd: Any = None + sep: Any = None + simulator: Any = None +@dataclass(eq=False) class SubcktCmd(ModelBase): - cmd = None - lines = None - name = None - node = None - parameters = None - sep = None + 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 = None - lines = None - sep = None + entry: Any = None + lines: Any = None + sep: Any = None +@dataclass(eq=False) class TitleCmd(ModelBase): - cmd = None - title = None + cmd: Any = None + title: Any = None +@dataclass(eq=False) class Parameters(ModelBase): pass +@dataclass(eq=False) class Parameter(ModelBase): - name = None - sep = None - value = None + name: Any = None + sep: Any = None + value: Any = None +@dataclass(eq=False) class GenericExpression(ModelBase): - braced = None - value = None + braced: Any = None + value: Any = None +@dataclass(eq=False) class TableFile(ModelBase): - filename = None - func = None - sep = None + filename: Any = None + func: Any = None + sep: Any = None +@dataclass(eq=False) class BracedExpression(ModelBase): - sep = None + sep: Any = None +@dataclass(eq=False) class ParenthesisNodes(ModelBase): - sep = None + sep: Any = None +@dataclass(eq=False) class CircuitNodes(ModelBase): - sep = None + sep: Any = None +@dataclass(eq=False) class Expression(ModelBase): - term = None - ternary = None + term: Any = None + ternary: Any = None +@dataclass(eq=False) class Ternary(ModelBase): - op = None - sep = None - t = None - x = None - y = None + op: Any = None + sep: Any = None + t: Any = None + x: Any = None + y: Any = None +@dataclass(eq=False) class Conditional(ModelBase): - expr = None + expr: Any = None +@dataclass(eq=False) class Or(ModelBase): - left = None - op = None - right = None - sep = None + left: Any = None + op: Any = None + right: Any = None + sep: Any = None +@dataclass(eq=False) class Xor(ModelBase): - left = None - op = None - right = None - sep = None + left: Any = None + op: Any = None + right: Any = None + sep: Any = None +@dataclass(eq=False) class And(ModelBase): - left = None - op = None - right = None - sep = None + left: Any = None + op: Any = None + right: Any = None + sep: Any = None +@dataclass(eq=False) class Not(ModelBase): - op = None - operator = None + op: Any = None + operator: Any = None +@dataclass(eq=False) class Relational(ModelBase): - factor = None - left = None - op = None - right = None - sep = None + factor: Any = None + left: Any = None + op: Any = None + right: Any = None + sep: Any = None +@dataclass(eq=False) class ConditionalFactor(ModelBase): - boolean = None - expr = None - sep = None + boolean: Any = None + expr: Any = None + sep: Any = None +@dataclass(eq=False) class Term(ModelBase): pass +@dataclass(eq=False) class AddSub(ModelBase): - left = None - op = None - right = None - sep = None + left: Any = None + op: Any = None + right: Any = None + sep: Any = None +@dataclass(eq=False) class ProdDivMod(ModelBase): - left = None - op = None - right = None - sep = None + left: Any = None + op: Any = None + right: Any = None + sep: Any = None +@dataclass(eq=False) class Sign(ModelBase): - op = None - operator = None + op: Any = None + operator: Any = None +@dataclass(eq=False) class Exponential(ModelBase): - left = None - op = None - right = None - sep = None + 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 = None - sep = None - variable = None + factor: Any = None + sep: Any = None + variable: Any = None +@dataclass(eq=False) class Factor(ModelBase): - sep = None + sep: Any = None +@dataclass(eq=False) class Functions(ModelBase): pass +@dataclass(eq=False) class Value(ModelBase): - imag = None - real = None - unit = None + imag: Any = None + real: Any = None + unit: Any = None +@dataclass(eq=False) class ImagValue(ModelBase): - value = None + value: Any = None +@dataclass(eq=False) class RealValue(ModelBase): - value = None + value: Any = None +@dataclass(eq=False) class NumberScale(ModelBase): - scale = None - value = None + 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 = None - sep = None + name: Any = None + sep: Any = None +@dataclass(eq=False) class BinaryPattern(ModelBase): - pattern = None + pattern: Any = None +@dataclass(eq=False) class Device(ModelBase): pass +@dataclass(eq=False) class NetNode(ModelBase): - node = None - sep = None + node: Any = None + sep: Any = None +@dataclass(eq=False) class Separator(ModelBase): - comment = None + comment: Any = None +@dataclass(eq=False) class Comment(ModelBase): pass diff --git a/PySpice/Spice/spicegrammar.ebnf b/PySpice/Spice/spicegrammar.ebnf index 18e486ce9..cf4722582 100644 --- a/PySpice/Spice/spicegrammar.ebnf +++ b/PySpice/Spice/spicegrammar.ebnf @@ -10,8 +10,8 @@ start::Circuit {st} [asterisk] {st} - title:text - {newline asterisk {st} title:text} + title+:text + {newline asterisk {st} title+:text} { {st} [ @@ -981,7 +981,7 @@ title_cmd::TitleCmd parameters::Parameters = - @:parameter {(([@:sep] comma [@:sep]) | @:sep) @:parameter} + @+:parameter {(([sep] comma [sep]) | sep) @+:parameter} ; diff --git a/unit-test/SpiceParser/test_SpiceParser.py b/unit-test/SpiceParser/test_SpiceParser.py index d5336f712..5e120cce3 100644 --- a/unit-test/SpiceParser/test_SpiceParser.py +++ b/unit-test/SpiceParser/test_SpiceParser.py @@ -672,5 +672,26 @@ def test_title(self): 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) + if __name__ == '__main__': unittest.main() From 3bbe3c84b3c178b11107ce22d53f43d31ac5e39a Mon Sep 17 00:00:00 2001 From: jmgc Date: Mon, 6 Mar 2023 16:52:26 +0100 Subject: [PATCH 106/134] Tests on the behavioural sources. --- PySpice/Spice/EBNFSpiceParser.py | 4 ++-- unit-test/SpiceParser/test_SpiceParser.py | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/PySpice/Spice/EBNFSpiceParser.py b/PySpice/Spice/EBNFSpiceParser.py index 07cdb410c..7d7f6fe87 100644 --- a/PySpice/Spice/EBNFSpiceParser.py +++ b/PySpice/Spice/EBNFSpiceParser.py @@ -906,7 +906,7 @@ def walk_CurrentControlledCurrentSource(self, node, data): kwargs = {"I": controller} else: value = self.walk(node.gain, data) - kwargs = {"I": I(device)*value} + kwargs = {"I": I(self.walk(node.device, data).lower())*value} positive = self.walk(node.positive, data) negative = self.walk(node.negative, data) @@ -933,7 +933,7 @@ def walk_CurrentControlledVoltageSource(self, node, data): kwargs = {"V": controller} else: value = self.walk(node.transresistance, data) - kwargs = {"V": I(device)*value} + kwargs = {"V": I(self.walk(node.device, data).lower())*value} positive = self.walk(node.positive, data) negative = self.walk(node.negative, data) diff --git a/unit-test/SpiceParser/test_SpiceParser.py b/unit-test/SpiceParser/test_SpiceParser.py index 5e120cce3..40e6fff57 100644 --- a/unit-test/SpiceParser/test_SpiceParser.py +++ b/unit-test/SpiceParser/test_SpiceParser.py @@ -693,5 +693,20 @@ def test_model(self): result = str(circuit) self.assertEqual(expected, result) + def test_model(self): + source = """ +HN 81 98 VN1 6 +""" + + expected = """.title + +bhn 81 98 v={(i(vn1) * 6)} +""" + + model = SpiceParser.parse(source=source) + circuit = model.build() + result = str(circuit) + self.assertEqual(expected, result) + if __name__ == '__main__': unittest.main() From 78ef879ffea3293895ad846f98e14bbda9512824 Mon Sep 17 00:00:00 2001 From: jmgc Date: Mon, 6 Mar 2023 18:43:46 +0100 Subject: [PATCH 107/134] Solved an issue with the numbers sign --- PySpice/Spice/EBNFExpressionParser.py | 2 +- PySpice/Spice/EBNFSpiceParser.py | 2 +- PySpice/Spice/ExpressionGrammar.py | 369 ++++++++++++---------- PySpice/Spice/ExpressionModel.py | 164 ++++++---- PySpice/Spice/SpiceGrammar.py | 11 +- PySpice/Spice/expressiongrammar.ebnf | 4 +- PySpice/Spice/spicegrammar.ebnf | 4 +- unit-test/Spice/test_ExpressionParser.py | 5 +- unit-test/SpiceParser/test_SpiceParser.py | 6 +- 9 files changed, 332 insertions(+), 235 deletions(-) diff --git a/PySpice/Spice/EBNFExpressionParser.py b/PySpice/Spice/EBNFExpressionParser.py index 7b95473af..8cd48f870 100644 --- a/PySpice/Spice/EBNFExpressionParser.py +++ b/PySpice/Spice/EBNFExpressionParser.py @@ -380,7 +380,7 @@ def walk_object(self, node, data): @staticmethod def _to_number(value): if type(value) is tuple: - value = value[0] + value[-1] + value = value[0] try: int_value = int(value) float_value = float(value) diff --git a/PySpice/Spice/EBNFSpiceParser.py b/PySpice/Spice/EBNFSpiceParser.py index 7d7f6fe87..e3966f6b1 100644 --- a/PySpice/Spice/EBNFSpiceParser.py +++ b/PySpice/Spice/EBNFSpiceParser.py @@ -2037,7 +2037,7 @@ def walk_object(self, node, data): @staticmethod def _to_number(value): if type(value) is tuple: - value = value[0] + value[-1] + value = value[0] try: int_value = int(value) float_value = float(value) diff --git a/PySpice/Spice/ExpressionGrammar.py b/PySpice/Spice/ExpressionGrammar.py index 7a3e1f9d6..8c3fd3f73 100644 --- a/PySpice/Spice/ExpressionGrammar.py +++ b/PySpice/Spice/ExpressionGrammar.py @@ -17,6 +17,7 @@ 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 @@ -24,59 +25,39 @@ class ExpressionBuffer(Buffer): - def __init__( - self, - text, - whitespace=None, - nameguard=None, - comments_re=None, - eol_comments_re=None, - ignorecase=True, - namechars='', - **kwargs - ): - super().__init__( - text, - whitespace=whitespace, - nameguard=nameguard, - comments_re=comments_re, - eol_comments_re=eol_comments_re, - ignorecase=ignorecase, - namechars=namechars, - **kwargs + 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, - whitespace=None, - nameguard=None, - comments_re=None, - eol_comments_re=None, - ignorecase=True, - left_recursion=True, - parseinfo=True, - keywords=None, - namechars='', - tokenizercls=ExpressionBuffer, - **kwargs - ): - if keywords is None: - keywords = KEYWORDS - super().__init__( - whitespace=whitespace, - nameguard=nameguard, - comments_re=comments_re, - eol_comments_re=eol_comments_re, - ignorecase=ignorecase, - left_recursion=left_recursion, - parseinfo=parseinfo, - keywords=keywords, - namechars=namechars, - tokenizercls=tokenizercls, - **kwargs + 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 @@ -95,13 +76,9 @@ def _gen_expr_(self): # noqa self.name_last_node('value') self._error( 'expecting one of: ' - ' ' - ' ' + ' ' + ' ' ) - self._define( - ['braced', 'value'], - [] - ) @tatsumasu('BracedExpression') @nomemo @@ -118,17 +95,18 @@ def _braced_expression_(self): # noqa 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: ' - "'{' " + "'{' " ) - self._define( - ['sep'], - [] - ) @tatsumasu('Expression') @leftrec @@ -142,13 +120,9 @@ def _expression_(self): # noqa self.name_last_node('term') self._error( 'expecting one of: ' - ' ' - ' ' + ' ' + ' ' ) - self._define( - ['term', 'ternary'], - [] - ) @tatsumasu('Ternary') @nomemo @@ -176,6 +150,7 @@ def _ternary_(self): # noqa self.name_last_node('sep') self._expression_() self.name_last_node('y') + self._define( ['op', 'sep', 't', 'x', 'y'], [] @@ -186,10 +161,6 @@ def _ternary_(self): # noqa def _conditional_expression_(self): # noqa self._boolean_or_() self.name_last_node('expr') - self._define( - ['expr'], - [] - ) @tatsumasu('Or') @nomemo @@ -207,6 +178,12 @@ def _boolean_or_(self): # noqa self.name_last_node('sep') self._boolean_or_() self.name_last_node('right') + + self._define( + ['op', 'right', 'sep'], + [] + ) + self._define( ['left', 'op', 'right', 'sep'], [] @@ -228,6 +205,12 @@ def _boolean_xor_(self): # noqa self.name_last_node('sep') self._boolean_xor_() self.name_last_node('right') + + self._define( + ['op', 'right', 'sep'], + [] + ) + self._define( ['left', 'op', 'right', 'sep'], [] @@ -249,6 +232,12 @@ def _boolean_and_(self): # noqa self.name_last_node('sep') self._boolean_and_() self.name_last_node('right') + + self._define( + ['op', 'right', 'sep'], + [] + ) + self._define( ['left', 'op', 'right', 'sep'], [] @@ -262,6 +251,7 @@ def _boolean_not_(self): # noqa self.name_last_node('op') self._relational_() self.name_last_node('operator') + self._define( ['op', 'operator'], [] @@ -293,7 +283,7 @@ def _relational_(self): # noqa self._token('<') self._error( 'expecting one of: ' - "'==' '!=' '>=' '<=' '>' '<'" + "'!=' '<' '<=' '==' '>' '>='" ) self.name_last_node('op') with self._optional(): @@ -301,20 +291,21 @@ def _relational_(self): # noqa 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: ' - ' ' - ' ' - ' ' - '' + ' ' + '' + ' ' + ' ' ) - self._define( - ['factor', 'left', 'op', 'right', 'sep'], - [] - ) @tatsumasu('ConditionalFactor') def _conditional_factor_(self): # noqa @@ -330,17 +321,18 @@ def _conditional_factor_(self): # noqa 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: ' - "'(' 'TRUE' 'FALSE' " + "'(' 'FALSE' 'TRUE' " ) - self._define( - ['boolean', 'expr', 'sep'], - [] - ) @tatsumasu('Term') def _term_(self): # noqa @@ -371,6 +363,12 @@ def _add_sub_(self): # noqa self.name_last_node('sep') self._add_sub_() self.name_last_node('right') + + self._define( + ['op', 'right', 'sep'], + [] + ) + self._define( ['left', 'op', 'right', 'sep'], [] @@ -394,7 +392,7 @@ def _prod_(self): # noqa self._token('%') self._error( 'expecting one of: ' - "'*' '/' '%'" + "'%' '*' '/'" ) self.name_last_node('op') with self._optional(): @@ -402,6 +400,12 @@ def _prod_(self): # noqa self.name_last_node('sep') self._prod_() self.name_last_node('right') + + self._define( + ['op', 'right', 'sep'], + [] + ) + self._define( ['left', 'op', 'right', 'sep'], [] @@ -423,6 +427,7 @@ def _unary_(self): # noqa self.name_last_node('op') self._exp_() self.name_last_node('operator') + self._define( ['op', 'operator'], [] @@ -443,6 +448,12 @@ def _exp_(self): # noqa self.name_last_node('sep') self._exp_() self.name_last_node('right') + + self._define( + ['op', 'right', 'sep'], + [] + ) + self._define( ['left', 'op', 'right', 'sep'], [] @@ -459,10 +470,10 @@ def _functional_(self): # noqa self.name_last_node('@') self._error( 'expecting one of: ' - ' ' - ' ' - ' ' - ' ' + ' ' + ' ' + ' ' + ' ' ) @tatsumasu('Variable') @@ -479,6 +490,11 @@ def _variable_(self): # noqa self._sep_() self.name_last_node('sep') self._rc_() + + self._define( + ['sep', 'variable'], + [] + ) with self._option(): self._var_id_() self.name_last_node('variable') @@ -487,14 +503,10 @@ def _variable_(self): # noqa self.name_last_node('factor') self._error( 'expecting one of: ' - "'{' [a-zA-Z_`@#\\$][a-zA-Z0-" - '9_:`@#\\.\\$]*[a-zA-Z0-9_`@#\\.\\$] [a-zA-Z]' - ' ' + "'{' " + '[a-zA-Z] [a-zA-Z_`@#\\$][a-zA-Z0-' + '9_:`@#\\.\\$]*[a-zA-Z0-9_`@#\\.\\$]' ) - self._define( - ['factor', 'sep', 'variable'], - [] - ) @tatsumasu('Factor') def _factor_(self): # noqa @@ -510,18 +522,19 @@ def _factor_(self): # noqa 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: ' - "'(' " + "'(' " '' ) - self._define( - ['sep'], - [] - ) @tatsumasu('Functions') def _functions_(self): # noqa @@ -550,16 +563,17 @@ def _functions_(self): # noqa self._v_func_() self._error( 'expecting one of: ' - "'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' " - "'atan2' 'ddx' 'agauss' 'gauss' 'if'" - " 'limit' 'min' 'max' 'pwrs'" - "'pow' 'pwr' 'sign' 'rand'" - "'aunif' 'unif' 'i' 'v' " + "'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() @@ -634,12 +648,12 @@ def _functions_1_(self): # noqa self._token('tan') self._error( 'expecting one of: ' - "'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'" + "'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(): @@ -656,6 +670,7 @@ def _functions_1_(self): # noqa self._sep_() self.name_last_node('sep') self._rp_() + self._define( ['func', 'sep', 'x'], [] @@ -688,6 +703,7 @@ def _atan2_(self): # noqa self._sep_() self.name_last_node('sep') self._rp_() + self._define( ['func', 'sep', 'x', 'y'], [] @@ -720,6 +736,7 @@ def _ddx_(self): # noqa self._sep_() self.name_last_node('sep') self._rp_() + self._define( ['f', 'func', 'sep', 'x'], [] @@ -770,6 +787,7 @@ def _gauss_(self): # noqa self._sep_() self.name_last_node('sep') self._rp_() + self._define( ['alpha', 'func', 'mu', 'n', 'sep'], [] @@ -795,6 +813,7 @@ def _i_func_(self): # noqa self._sep_() self.name_last_node('sep') self._rp_() + self._define( ['device', 'func', 'sep'], [] @@ -836,6 +855,7 @@ def _if_func_(self): # noqa self._sep_() self.name_last_node('sep') self._rp_() + self._define( ['func', 'sep', 't', 'x', 'y'], [] @@ -877,6 +897,7 @@ def _limit_(self): # noqa self._sep_() self.name_last_node('sep') self._rp_() + self._define( ['func', 'sep', 'x', 'y', 'z'], [] @@ -900,7 +921,7 @@ def _functions_2_(self): # noqa self._token('sign') self._error( 'expecting one of: ' - "'min' 'max' 'pwrs' 'pow' 'pwr' 'sign'" + "'max' 'min' 'pow' 'pwr' 'pwrs' 'sign'" ) self.name_last_node('func') with self._optional(): @@ -926,6 +947,7 @@ def _functions_2_(self): # noqa self._sep_() self.name_last_node('sep') self._rp_() + self._define( ['func', 'sep', 'x', 'y'], [] @@ -944,6 +966,7 @@ def _rand_(self): # noqa self._sep_() self.name_last_node('sep') self._rp_() + self._define( ['func', 'sep'], [] @@ -985,6 +1008,7 @@ def _unif_(self): # noqa self._sep_() self.name_last_node('sep') self._rp_() + self._define( ['alpha', 'func', 'mu', 'sep'], [] @@ -1017,7 +1041,13 @@ def _v_func_(self): # noqa with self._optional(): self._sep_() self.name_last_node('sep') + + self._define( + ['node', 'sep'], + [] + ) self._rp_() + self._define( ['func', 'node', 'sep'], [] @@ -1040,7 +1070,7 @@ def _special_variables_(self): # noqa self._token('pi') self._error( 'expecting one of: ' - "'time' 'temper' 'temp' 'freq' 'vt' 'pi'" + "'freq' 'pi' 'temp' 'temper' 'time' 'vt'" ) @tatsumasu('Value') @@ -1054,6 +1084,11 @@ def _value_(self): # noqa 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') @@ -1062,7 +1097,7 @@ def _value_(self): # noqa self.name_last_node('real') self._error( 'expecting one of: ' - ' ' + ' ' ) with self._optional(): with self._choice(): @@ -1075,6 +1110,7 @@ def _value_(self): # noqa ' ' ) self.name_last_node('unit') + self._define( ['imag', 'real', 'unit'], [] @@ -1085,6 +1121,7 @@ def _imag_value_(self): # noqa self._number_scale_() self.name_last_node('value') self._token('J') + self._define( ['value'], [] @@ -1094,10 +1131,6 @@ def _imag_value_(self): # noqa def _real_value_(self): # noqa self._number_scale_() self.name_last_node('value') - self._define( - ['value'], - [] - ) @tatsumasu() def _freq_value_(self): # noqa @@ -1106,6 +1139,7 @@ def _freq_value_(self): # noqa with self._optional(): self._hz_() self.name_last_node('unit') + self._define( ['unit', 'value'], [] @@ -1129,6 +1163,11 @@ def _number_scale_(self): # noqa ' ' ) self.name_last_node('scale') + + self._define( + ['scale', 'value'], + [] + ) with self._option(): self._integer_() self.name_last_node('value') @@ -1144,16 +1183,18 @@ def _number_scale_(self): # noqa ' ' ) 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]+ ' + '([\\+\\-]?(([0-9]+(\\.[0-9]*)?)|(\\.[0-' + '9]+))([eE][\\-\\+]?[0-9]{1,3})?)' + '([\\+\\-]?[0-9]+) ' + '' ) - self._define( - ['scale', 'value'], - [] - ) @tatsumasu() def _suffix_(self): # noqa @@ -1177,11 +1218,11 @@ def _lead_name_(self): # noqa @tatsumasu('Float') def _floating_point_(self): # noqa - self._pattern('[\\+\\-]?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))([eE][\\-\\+]?[0-9]{1,3})?') + self._pattern('([\\+\\-]?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))([eE][\\-\\+]?[0-9]{1,3})?)') @tatsumasu('Int') def _integer_(self): # noqa - self._pattern('[\\+\\-]?[0-9]+') + self._pattern('([\\+\\-]?[0-9]+)') @tatsumasu() def _digit_(self): # noqa @@ -1196,7 +1237,7 @@ def _boolean_(self): # noqa self._token('FALSE') self._error( 'expecting one of: ' - "'TRUE' 'FALSE'" + "'FALSE' 'TRUE'" ) @tatsumasu('BinaryPattern') @@ -1207,6 +1248,7 @@ def block1(): self._binary_() self._positive_closure(block1) self.name_last_node('pattern') + self._define( ['pattern'], [] @@ -1242,6 +1284,12 @@ def _node_(self): # noqa self._sep_() self.name_last_node('sep') self._token('=') + + self._define( + ['sep'], + [] + ) + self._define( ['node', 'sep'], [] @@ -1256,8 +1304,8 @@ def _id_(self): # noqa self._pattern('[a-zA-Z_`@#\\$]') self._error( 'expecting one of: ' - '[a-zA-Z_`@#\\$][a-zA-Z0-9_:`@#\\.\\$\\/]*[a-' - 'zA-Z0-9_`@#\\.\\$] [a-zA-Z_`@#\\$]' + '[a-zA-Z_`@#\\$] [a-zA-Z_`@#\\$][a-zA-Z0-' + '9_:`@#\\.\\$\\/]*[a-zA-Z0-9_`@#\\.\\$]' ) @tatsumasu() @@ -1269,8 +1317,8 @@ def _var_id_(self): # noqa self._pattern('[a-zA-Z]') self._error( 'expecting one of: ' - '[a-zA-Z_`@#\\$][a-zA-Z0-9_:`@#\\.\\$]*[a-' - 'zA-Z0-9_`@#\\.\\$] [a-zA-Z]' + '[a-zA-Z] [a-zA-Z_`@#\\$][a-zA-Z0-' + '9_:`@#\\.\\$]*[a-zA-Z0-9_`@#\\.\\$]' ) @tatsumasu() @@ -1280,18 +1328,18 @@ def _end_sep_(self): # noqa self._cmd_net_sep_() self.name_last_node('@') - def block1(): + def block2(): self._st_() - self._closure(block1) + self._closure(block2) with self._option(): - def block2(): + def block3(): self._st_() - self._positive_closure(block2) + self._positive_closure(block3) self._error( 'expecting one of: ' - ' ' - ' [ \\t]' + ' ' + ' [ \\t]' ) @tatsumasu() @@ -1299,28 +1347,28 @@ def _sep_(self): # noqa with self._choice(): with self._option(): - def block0(): + def block1(): self._cmd_net_sep_() self.name_last_node('@') - def block2(): + def block3(): self._st_() - self._closure(block2) + self._closure(block3) self._token('+') - def block3(): + def block4(): self._st_() - self._closure(block3) - self._positive_closure(block0) + self._closure(block4) + self._positive_closure(block1) with self._option(): - def block4(): + def block5(): self._st_() - self._positive_closure(block4) + self._positive_closure(block5) self._error( 'expecting one of: ' - ' ' - ' [ \\t]' + ' ' + ' [ \\t]' ) @tatsumasu('Separator') @@ -1350,11 +1398,17 @@ def block4(): 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'], [] @@ -1453,7 +1507,7 @@ def _ws_(self): # noqa self._pattern('[^\\S\\r\\n]*') -class ExpressionSemantics(object): +class ExpressionSemantics: def start(self, ast): # noqa return ast @@ -1683,9 +1737,7 @@ def ws(self, ast): # noqa return ast -def main(filename, start=None, **kwargs): - if start is None: - start = 'start' +def main(filename, **kwargs): if not filename or filename == '-': text = sys.stdin.read() else: @@ -1694,7 +1746,6 @@ def main(filename, start=None, **kwargs): parser = ExpressionParser() return parser.parse( text, - rule_name=start, filename=filename, **kwargs ) diff --git a/PySpice/Spice/ExpressionModel.py b/PySpice/Spice/ExpressionModel.py index 103b74e83..9afd94599 100644 --- a/PySpice/Spice/ExpressionModel.py +++ b/PySpice/Spice/ExpressionModel.py @@ -11,10 +11,14 @@ 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 @@ -25,178 +29,212 @@ def __init__(self, context=None, types=None): t for t in globals().values() if type(t) is type and issubclass(t, ModelBase) ] + (types or []) - super(ExpressionModelBuilderSemantics, self).__init__(context=context, types=types) + super().__init__(context=context, types=types) +@dataclass(eq=False) class SpiceExpression(ModelBase): pass +@dataclass(eq=False) class GenericExpression(ModelBase): - braced = None - value = None + braced: Any = None + value: Any = None +@dataclass(eq=False) class BracedExpression(ModelBase): - sep = None + sep: Any = None +@dataclass(eq=False) class Expression(ModelBase): - term = None - ternary = None + term: Any = None + ternary: Any = None +@dataclass(eq=False) class Ternary(ModelBase): - op = None - sep = None - t = None - x = None - y = None + op: Any = None + sep: Any = None + t: Any = None + x: Any = None + y: Any = None +@dataclass(eq=False) class Conditional(ModelBase): - expr = None + expr: Any = None +@dataclass(eq=False) class Or(ModelBase): - left = None - op = None - right = None - sep = None + left: Any = None + op: Any = None + right: Any = None + sep: Any = None +@dataclass(eq=False) class Xor(ModelBase): - left = None - op = None - right = None - sep = None + left: Any = None + op: Any = None + right: Any = None + sep: Any = None +@dataclass(eq=False) class And(ModelBase): - left = None - op = None - right = None - sep = None + left: Any = None + op: Any = None + right: Any = None + sep: Any = None +@dataclass(eq=False) class Not(ModelBase): - op = None - operator = None + op: Any = None + operator: Any = None +@dataclass(eq=False) class Relational(ModelBase): - factor = None - left = None - op = None - right = None - sep = None + factor: Any = None + left: Any = None + op: Any = None + right: Any = None + sep: Any = None +@dataclass(eq=False) class ConditionalFactor(ModelBase): - boolean = None - expr = None - sep = None + boolean: Any = None + expr: Any = None + sep: Any = None +@dataclass(eq=False) class Term(ModelBase): pass +@dataclass(eq=False) class AddSub(ModelBase): - left = None - op = None - right = None - sep = None + left: Any = None + op: Any = None + right: Any = None + sep: Any = None +@dataclass(eq=False) class ProdDivMod(ModelBase): - left = None - op = None - right = None - sep = None + left: Any = None + op: Any = None + right: Any = None + sep: Any = None +@dataclass(eq=False) class Sign(ModelBase): - op = None - operator = None + op: Any = None + operator: Any = None +@dataclass(eq=False) class Exponential(ModelBase): - left = None - op = None - right = None - sep = None + 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 = None - sep = None - variable = None + factor: Any = None + sep: Any = None + variable: Any = None +@dataclass(eq=False) class Factor(ModelBase): - sep = None + sep: Any = None +@dataclass(eq=False) class Functions(ModelBase): pass +@dataclass(eq=False) class Value(ModelBase): - imag = None - real = None - unit = None + imag: Any = None + real: Any = None + unit: Any = None +@dataclass(eq=False) class ImagValue(ModelBase): - value = None + value: Any = None +@dataclass(eq=False) class RealValue(ModelBase): - value = None + value: Any = None +@dataclass(eq=False) class NumberScale(ModelBase): - scale = None - value = None + 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 = None + pattern: Any = None +@dataclass(eq=False) class Device(ModelBase): pass +@dataclass(eq=False) class NetNode(ModelBase): - node = None - sep = None + node: Any = None + sep: Any = None +@dataclass(eq=False) class Separator(ModelBase): - comment = None + comment: Any = None +@dataclass(eq=False) class Comment(ModelBase): pass diff --git a/PySpice/Spice/SpiceGrammar.py b/PySpice/Spice/SpiceGrammar.py index 55e7f6145..da9f82bee 100644 --- a/PySpice/Spice/SpiceGrammar.py +++ b/PySpice/Spice/SpiceGrammar.py @@ -4529,9 +4529,10 @@ def _number_scale_(self): # noqa ) self._error( 'expecting one of: ' - ' [\\+\\-]?(([0-' - '9]+(\\.[0-9]*)?)|(\\.[0-9]+))([eE][\\-' - '\\+]?[0-9]{1,3})? [\\+\\-]?[0-9]+' + '([\\+\\-]?(([0-9]+(\\.[0-9]*)?)|(\\.[0-' + '9]+))([eE][\\-\\+]?[0-9]{1,3})?)' + '([\\+\\-]?[0-9]+) ' + '' ) @tatsumasu() @@ -4556,11 +4557,11 @@ def _lead_name_(self): # noqa @tatsumasu('Float') def _floating_point_(self): # noqa - self._pattern('[\\+\\-]?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))([eE][\\-\\+]?[0-9]{1,3})?') + self._pattern('([\\+\\-]?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))([eE][\\-\\+]?[0-9]{1,3})?)') @tatsumasu('Int') def _integer_(self): # noqa - self._pattern('[\\+\\-]?[0-9]+') + self._pattern('([\\+\\-]?[0-9]+)') @tatsumasu() def _digit_(self): # noqa diff --git a/PySpice/Spice/expressiongrammar.ebnf b/PySpice/Spice/expressiongrammar.ebnf index c023c6bfe..2a3ee0983 100644 --- a/PySpice/Spice/expressiongrammar.ebnf +++ b/PySpice/Spice/expressiongrammar.ebnf @@ -438,13 +438,13 @@ lead_name floating_point::Float = - /[\+\-]?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))([eE][\-\+]?[0-9]{1,3})?/ + /([\+\-]?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))([eE][\-\+]?[0-9]{1,3})?)/ ; integer::Int = - /[\+\-]?[0-9]+/ + /([\+\-]?[0-9]+)/ ; diff --git a/PySpice/Spice/spicegrammar.ebnf b/PySpice/Spice/spicegrammar.ebnf index cf4722582..2b4206b4e 100644 --- a/PySpice/Spice/spicegrammar.ebnf +++ b/PySpice/Spice/spicegrammar.ebnf @@ -1450,13 +1450,13 @@ lead_name floating_point::Float = - /[\+\-]?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))([eE][\-\+]?[0-9]{1,3})?/ + /([\+\-]?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))([eE][\-\+]?[0-9]{1,3})?)/ ; integer::Int = - /[\+\-]?[0-9]+/ + /([\+\-]?[0-9]+)/ ; diff --git a/unit-test/Spice/test_ExpressionParser.py b/unit-test/Spice/test_ExpressionParser.py index 0d60b7e4c..aa0324d79 100644 --- a/unit-test/Spice/test_ExpressionParser.py +++ b/unit-test/Spice/test_ExpressionParser.py @@ -3,6 +3,9 @@ import os data = [ + "1", + "+1", + "-1", "1.", "+1.", "-1.", @@ -64,7 +67,7 @@ class TestExpressionParser(unittest.TestCase): def test_parser(self): - #ExpressionParser._regenerate() + # ExpressionParser._regenerate() for case in data: expr_i = ExpressionParser.parse(source=case) case_i = "{%s}" % expr_i diff --git a/unit-test/SpiceParser/test_SpiceParser.py b/unit-test/SpiceParser/test_SpiceParser.py index 40e6fff57..e38e775ab 100644 --- a/unit-test/SpiceParser/test_SpiceParser.py +++ b/unit-test/SpiceParser/test_SpiceParser.py @@ -693,14 +693,18 @@ def test_model(self): result = str(circuit) self.assertEqual(expected, result) - def test_model(self): + 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) From b4ec4f613c879b570cb3ba0c09dd4911868a6609 Mon Sep 17 00:00:00 2001 From: jmgc Date: Tue, 7 Mar 2023 21:25:05 +0100 Subject: [PATCH 108/134] Update SiUnits.py Changed the unit suffixes to lower case, and commented those that can be misinterpreted when the units in the spice file are converted to lower case. Meter left, though it can be misinterpreted with mili. --- PySpice/Unit/SiUnits.py | 110 ++++++++++++++++++++-------------------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/PySpice/Unit/SiUnits.py b/PySpice/Unit/SiUnits.py index 34296151a..a78fd389c 100644 --- a/PySpice/Unit/SiUnits.py +++ b/PySpice/Unit/SiUnits.py @@ -121,8 +121,8 @@ class Yocto(UnitPrefix): # Define SI units -class Metre(SiBaseUnit): - UNIT_NAME = 'metre' +class Meter(SiBaseUnit): + UNIT_NAME = 'meter' UNIT_SUFFIX = 'm' QUANTITY = 'length' @@ -139,7 +139,7 @@ class Second(SiBaseUnit): class Ampere(SiBaseUnit): UNIT_NAME = 'ampere' - UNIT_SUFFIX = 'A' + UNIT_SUFFIX = 'a' QUANTITY = 'electric current' class Kelvin(SiBaseUnit): @@ -177,37 +177,37 @@ class Steradian(Unit): class Hertz(Unit): UNIT_NAME = 'frequency' - UNIT_SUFFIX = 'Hz' + UNIT_SUFFIX = 'hz' QUANTITY = 'frequency' SI_UNIT = 's^-1' DEFAULT_UNIT = True -class Newton(Unit): - UNIT_NAME = 'newton' - UNIT_SUFFIX = 'N' - QUANTITY = 'force' - SI_UNIT = 'kg*m*s^-2' - DEFAULT_UNIT = True - -class Pascal(Unit): - UNIT_NAME = 'pascal' - UNIT_SUFFIX = 'Pa' - QUANTITY = 'pressure' - SI_UNIT = 'kg*m^-1*s^-2' - DEFAULT_UNIT = True +#class Newton(Unit): +# UNIT_NAME = 'newton' +# UNIT_SUFFIX = 'N' +# QUANTITY = 'force' +# SI_UNIT = 'kg*m*s^-2' +# DEFAULT_UNIT = True + +#class Pascal(Unit): +# UNIT_NAME = 'pascal' +# UNIT_SUFFIX = 'Pa' +# QUANTITY = 'pressure' +# SI_UNIT = 'kg*m^-1*s^-2' +# DEFAULT_UNIT = True # N/m^2 -class Joule(Unit): - UNIT_NAME = 'joule' - UNIT_SUFFIX = 'J' - QUANTITY = 'energy' - SI_UNIT = 'kg*m^2*s^-2' - DEFAULT_UNIT = True +#class Joule(Unit): +# UNIT_NAME = 'joule' +# UNIT_SUFFIX = 'J' +# QUANTITY = 'energy' +# SI_UNIT = 'kg*m^2*s^-2' +# DEFAULT_UNIT = True # N*m class Watt(Unit): UNIT_NAME = 'watt' - UNIT_SUFFIX = 'W' + UNIT_SUFFIX = 'w' QUANTITY = 'power' SI_UNIT = 'kg*m^2*s^-3' DEFAULT_UNIT = True @@ -215,25 +215,25 @@ class Watt(Unit): class Coulomb(Unit): UNIT_NAME = 'coulomb' - UNIT_SUFFIX = 'C' + UNIT_SUFFIX = 'c' QUANTITY = 'electric charge' SI_UNIT = 's*A' DEFAULT_UNIT = True class Volt(Unit): UNIT_NAME = 'volt' - UNIT_SUFFIX = 'V' + UNIT_SUFFIX = 'v' QUANTITY = 'voltage' SI_UNIT = 'kg*m^2*s^-3*A^-1' DEFAULT_UNIT = True # W/A -class Farad(Unit): - UNIT_NAME = 'farad' - UNIT_SUFFIX = 'F' - QUANTITY = 'capacitance' - SI_UNIT = 'kg^-1*m^-2*s^4*A^2' - DEFAULT_UNIT = True +#class Farad(Unit): +# UNIT_NAME = 'farad' +# UNIT_SUFFIX = 'F' +# QUANTITY = 'capacitance' +# SI_UNIT = 'kg^-1*m^-2*s^4*A^2' +# DEFAULT_UNIT = True # C/V class Ohm(Unit): @@ -244,17 +244,17 @@ class Ohm(Unit): DEFAULT_UNIT = True # V/A -class Siemens(Unit): - UNIT_NAME = 'siemens' - UNIT_SUFFIX = 'S' - QUANTITY = 'electrical conductance' - SI_UNIT = 'kg^-1*m^-2*s^3*A^2' - DEFAULT_UNIT = True +#class Siemens(Unit): +# UNIT_NAME = 'siemens' +# UNIT_SUFFIX = 'S' +# QUANTITY = 'electrical conductance' +# SI_UNIT = 'kg^-1*m^-2*s^3*A^2' +# DEFAULT_UNIT = True # A/V 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 @@ -270,14 +270,14 @@ 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' + 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' QUANTITY = 'temperature relative to 273.15 K' SI_UNIT = 'K' @@ -303,25 +303,25 @@ class Becquerel(Unit): QUANTITY = 'radioactivity (decays per unit time)' SI_UNIT = 's^-1' # same as Hertz -class Gray(Unit): - UNIT_NAME = 'gray' - UNIT_SUFFIX = 'Gy' - QUANTITY = 'absorbed dose (of ionizing radiation)' - SI_UNIT = 'm^2*s^-2' +#class Gray(Unit): +# UNIT_NAME = 'gray' +# UNIT_SUFFIX = 'Gy' +# 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' QUANTITY = ' equivalent dose (of ionizing radiation)' SI_UNIT = 'm^2*s^-2' -class Katal(Unit): - UNIT_NAME = 'katal' - UNIT_SUFFIX = 'kat' - QUANTITY = 'catalytic activity' - SI_UNIT = 'mol*s^-1' - DEFAULT_UNIT = True +#class Katal(Unit): +# UNIT_NAME = 'katal' +# UNIT_SUFFIX = 'kat' +# QUANTITY = 'catalytic activity' +# SI_UNIT = 'mol*s^-1' +# DEFAULT_UNIT = True #################################################################################################### From 01af29d0d57f37c511fea19b8b862cd1cd04285c Mon Sep 17 00:00:00 2001 From: jmgc Date: Wed, 8 Mar 2023 21:18:46 +0100 Subject: [PATCH 109/134] Update Units and revise the letter casing To avoid issues with Spice, the full spice code is lower cased. Also revised the expression parsing to avoid issues between the expressions, the units and the recovering of the Spice code. --- PySpice/Spice/BasicElement.py | 92 +++++--- PySpice/Spice/EBNFExpressionParser.py | 45 ++-- PySpice/Spice/EBNFSpiceParser.py | 271 +--------------------- PySpice/Spice/ElementParameter.py | 21 +- PySpice/Spice/Expressions.py | 41 +++- PySpice/Spice/HighLevelElement.py | 6 +- PySpice/Spice/Netlist.py | 41 ++-- PySpice/Spice/RawFile.py | 12 +- PySpice/Spice/Simulation.py | 10 +- PySpice/Tools/StringTools.py | 13 +- PySpice/Unit/SiUnits.py | 122 ++++++---- PySpice/Unit/Unit.py | 133 ++++++----- PySpice/Unit/__init__.py | 44 ++-- unit-test/Probe/test_WaveForm.py | 12 +- unit-test/Spice/test_ExpressionParser.py | 140 +++++------ unit-test/Spice/test_HighLevelElement.py | 8 +- unit-test/SpiceParser/test_SpiceParser.py | 35 ++- unit-test/Unit/test_Units.py | 4 +- 18 files changed, 445 insertions(+), 605 deletions(-) diff --git a/PySpice/Spice/BasicElement.py b/PySpice/Spice/BasicElement.py index 54d21f1bd..4e5efb7d5 100644 --- a/PySpice/Spice/BasicElement.py +++ b/PySpice/Spice/BasicElement.py @@ -100,7 +100,7 @@ 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 ..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 ( @@ -201,7 +201,9 @@ def format_spice_parameters(self): spice_parameters = super().format_spice_parameters() if self.parameters: - spice_parameters += ' params: ' + 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 @@ -264,8 +266,8 @@ class Resistor(DipoleElement): 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') @@ -339,8 +341,8 @@ class SemiconductorResistor(DipoleElement): 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') @@ -440,8 +442,8 @@ class Capacitor(DipoleElement): 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') @@ -514,8 +516,8 @@ class SemiconductorCapacitor(DipoleElement): 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') #################################################################################################### @@ -608,13 +610,13 @@ class Inductor(DipoleElement): ALIAS = 'L' PREFIX = 'L' - inductance = FloatPositionalParameter(position=-1, key_parameter=False, unit=U_H) + 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') @@ -801,23 +803,35 @@ 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) - ac_magnitude = FloatPositionalParameter(position=1, 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: - parameters.append('dc {}'.format(str_spice(self.dc_value))) + 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: - parameters.append('ac {}'.format(str_spice(self.ac_magnitude))) + 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') - parameters.append(str_spice(self.ac_phase)) + if isinstance(self.ac_phase, Expression): + ac_phase = "{%s}" % ac_phase + parameters.append(ac_phase) if self.transient is not None: - parameters.append(str_spice(self.transient)) + transient = str_spice(self.transient) + if isinstance(self.transient, Expression): + transient = "{%s}" % transient + parameters.append(transient) return join_list(parameters) #################################################################################################### @@ -844,23 +858,35 @@ 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) - ac_magnitude = FloatPositionalParameter(position=1, 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: - parameters.append('dc {}'.format(str_spice(self.dc_value))) + 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: - parameters.append('ac {}'.format(str_spice(self.ac_magnitude))) + 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') - parameters.append(str_spice(self.ac_phase)) + if isinstance(self.ac_phase, Expression): + ac_phase = "{%s}" % ac_phase + parameters.append(ac_phase) if self.transient is not None: - parameters.append(str_spice(self.transient)) + transient = str_spice(self.transient) + if isinstance(self.transient, Expression): + transient = "{%s}" % transient + parameters.append(transient) return join_list(parameters) #################################################################################################### @@ -1037,8 +1063,8 @@ class BehavioralSource(DipoleElement): 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') ############################################## @@ -1254,8 +1280,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) #################################################################################################### # @@ -1332,8 +1358,8 @@ 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_ @@ -1404,7 +1430,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) #################################################################################################### # @@ -1567,7 +1593,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') diff --git a/PySpice/Spice/EBNFExpressionParser.py b/PySpice/Spice/EBNFExpressionParser.py index 8cd48f870..b3f923dba 100644 --- a/PySpice/Spice/EBNFExpressionParser.py +++ b/PySpice/Spice/EBNFExpressionParser.py @@ -3,7 +3,7 @@ import csv from unicodedata import normalize -from PySpice.Unit.Unit import UnitValue, ZeroPower, PrefixedUnit, UnitMetaclass +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 * @@ -282,11 +282,14 @@ def walk_Sign(self, node, data): operator = self.walk(node.operator, data) if node.op is not None: if node.op == "-": - return Neg(operator) + if isinstance(operator, (int, float)): + return -operator + else: + return Neg(operator) else: - return Pos(operator) - else: - return operator + if not isinstance(operator, (int, float)): + return Pos(operator) + return operator def walk_Exponential(self, node, data): lhs = self.walk(node.left, data) @@ -314,11 +317,16 @@ def walk_Value(self, node, data): imag = self.walk(node.imag, data) value = 0.0 if imag is None: - value = ExpressionModelWalker._to_number(real) + 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): @@ -328,14 +336,12 @@ def walk_RealValue(self, node, data): return self.walk(node.value, data) def walk_NumberScale(self, node, data): - value = self.walk(node.value, data) + value = ExpressionModelWalker._to_number(self.walk(node.value, data)) scale = node.scale if scale is not None: scale = normalize("NFKD", scale).lower() - result = UnitValue(self._suffix[scale], value) - else: - result = UnitValue(PrefixedUnit(ZeroPower()), value) - return ExpressionModelWalker._to_number(result) + value = PrefixedUnit(power=UnitPrefixMetaclass.get(scale)).new_value(value) + return value def walk_Float(self, node, data): value = ExpressionModelWalker._to_number(node.ast) @@ -346,7 +352,13 @@ def walk_Int(self, node, data): return value def walk_Unit(self, node, data): - return node.ast + 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 @@ -356,15 +368,12 @@ def walk_Separator(self, node, data): if node.comment is not None: return self.walk(node.comment, data) - 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_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) diff --git a/PySpice/Spice/EBNFSpiceParser.py b/PySpice/Spice/EBNFSpiceParser.py index e3966f6b1..801844174 100644 --- a/PySpice/Spice/EBNFSpiceParser.py +++ b/PySpice/Spice/EBNFSpiceParser.py @@ -3,9 +3,9 @@ import csv from unicodedata import normalize -from PySpice.Unit.Unit import UnitValue, ZeroPower, PrefixedUnit -from PySpice.Unit.SiUnits import Tera, Giga, Mega, Kilo, Milli, Micro, Nano, Pico, Femto -from PySpice.Tools.StringTools import join_lines +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) @@ -553,12 +553,12 @@ def to_python(self, ground=0): ############################################## def build(self, ground=0, parent=None): - subcircuit = SubCircuit(self._name, *self._nodes, **self._params) + subcircuit = SubCircuit(str(self._name).lower(), *self._nodes, **self._params) subcircuit.parent = parent for statement in self._parameters: statement.build(subcircuit) for statement in self._models: - model = statement.build(subcircuit) + statement.build(subcircuit) for statement in self._subcircuits: subckt = statement.build(ground, parent=subcircuit) # Fixme: ok ??? subcircuit.subcircuit(subckt) @@ -729,16 +729,9 @@ def build(self, ground=0): #################################################################################################### -class SpiceModelWalker(NodeWalker): +class SpiceModelWalker(ExpressionModelWalker): 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, @@ -1659,7 +1652,7 @@ def walk_ModelCmd(self, node, data): ) def walk_ModelName(self, node, data): - return node.name + return node.name.lower() def walk_ParamCmd(self, node, data): if node.parameters is not None: @@ -1767,230 +1760,11 @@ def walk_Parameter(self, node, data): value = self.walk(node.value, data) return {node.name.lower(): value} - 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_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_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 == "-": - return Neg(operator) - else: - return Pos(operator) - else: - 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) - if imag is None: - return SpiceModelWalker._to_number(real) - else: - return complex(float(real), float(imag)) - - 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 = self.walk(node.value, data) - scale = node.scale - if scale is not None: - scale = normalize("NFKD", scale).lower() - result = UnitValue(self._suffix[scale], value) - else: - result = UnitValue(PrefixedUnit(ZeroPower()), value) - return SpiceModelWalker._to_number(result) - - def walk_Float(self, node, data): - value = SpiceModelWalker._to_number(node.ast) - return value - - def walk_Int(self, node, data): - value = int(node.ast) - return value - def walk_Comment(self, node, data): # TODO implement comments on devices return node.ast @@ -2016,37 +1790,6 @@ def walk_TableFile(self, node, data): filename = self.walk(node.filename, data) return TableFile(filename) - 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: diff --git a/PySpice/Spice/ElementParameter.py b/PySpice/Spice/ElementParameter.py index 2b6518639..5081d7b1f 100644 --- a/PySpice/Spice/ElementParameter.py +++ b/PySpice/Spice/ElementParameter.py @@ -26,7 +26,7 @@ from ..Unit.Unit import UnitValue, PrefixedUnit from ..Tools.StringTools import str_spice -from .Expressions import Expression +from .Expressions import Expression, Symbol from .EBNFExpressionParser import ExpressionParser #################################################################################################### @@ -113,18 +113,24 @@ def __lt__(self, other): return self._attribute_name < other.attribute_name def _validate_float(self, value): - if (self._unit is None): + if self._unit is None: return float(value) if isinstance(value, type(self._unit)): return value - elif isinstance(value, UnitValue): + 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 - elif type(value) is str: - value = ExpressionParser.parse(value) return self._unit.new_value(value) #################################################################################################### @@ -311,7 +317,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 '' diff --git a/PySpice/Spice/Expressions.py b/PySpice/Spice/Expressions.py index dafa4678e..802a57c56 100644 --- a/PySpice/Spice/Expressions.py +++ b/PySpice/Spice/Expressions.py @@ -2,6 +2,7 @@ import numpy as np import operator as op +from ..Tools.StringTools import str_spice class Expression: def __call__(self, **kwargs): @@ -94,7 +95,7 @@ def __init__(self, func, *symbols): )) def __str__(self): - arguments = ", ".join([str(symbol) for symbol in self._symbols]) + arguments = ", ".join([str_spice(symbol) for symbol in self._symbols]) return "{:s}({:s})".format(self.__class__.__name__.lower(), arguments) def __call__(self, **kwargs): @@ -116,7 +117,13 @@ def __init__(self, op, string, lhs, rhs): self._string = string def __str__(self): - return "({:s} {:s} {:s})".format(str(self._lhs), str(self._string), str(self._rhs)) + 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 @@ -135,13 +142,16 @@ def __call__(self, **kwargs): return self.__class__(lhs, rhs) class UnaryOperator(Expression): - def __init__(self, op, string, operator): + def __init__(self, op, operator, operand): self._op = op self._operator = operator - self._string = string + self._operand = operand def __str__(self): - return "({:s}({:s}))".format(str(self._string), str(self._operator)) + 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 @@ -624,18 +634,29 @@ def __init__(self, *symbol): class Symbol(Expression): - def __init__(self, name): - self._name = str(name) + def __init__(self, value): + self._value = value + + @property + def value(self): + return self._value def __str__(self): - return self._name + return str_spice(self._value) def subs(self, **kwargs): - if self._name in kwargs: - return kwargs[self._name] + 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 diff --git a/PySpice/Spice/HighLevelElement.py b/PySpice/Spice/HighLevelElement.py index 6132619de..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 #################################################################################################### diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index 52b20120a..1fb09f7fb 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -94,6 +94,7 @@ def __init__(self, **kwargs): FlagParameter, KeyValueParameter, ) from .Simulation import CircuitSimulator +from .Expressions import Expression #################################################################################################### @@ -507,7 +508,7 @@ 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 @@ -526,12 +527,12 @@ def __init__(self, netlist, name, *args, **kwargs): if parameter.attribute_name in kwargs: optional_pins += 1 continue - if 0 < optional_pins <= self._number_of_optional_pins_: + 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: - IndexError("Incongruent number of args") + 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(): @@ -984,7 +985,7 @@ def _find_subcircuit(self, name): 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 @@ -1007,7 +1008,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: @@ -1024,19 +1025,20 @@ def has_ground_node(self): def _add_element(self, element): """Add an element.""" - if element.name not in self._elements: - self._elements[str(element.name).lower()] = element + element_name = str(element.name).lower() + if element_name not in self._elements: + self._elements[element_name] = element if hasattr(element, 'model'): model = element.model if model is not None: - self._used_models.add(str(model).lower()) + self._used_models.add(model) if element.name[0] in "xX": - subcircuit_name = element.subcircuit_name + subcircuit_name = str(element.subcircuit_name).lower() if subcircuit_name is not None: - self._used_subcircuits.add(str(subcircuit_name).lower()) + self._used_subcircuits.add(subcircuit_name) else: - raise NameError("Element name {} is already defined".format(element.name)) + raise NameError("Element name {} is already defined".format(element_name)) ############################################## @@ -1050,7 +1052,7 @@ def _remove_element(self, element): def parameter(self, name, expression): """Set a parameter.""" - self._parameters[str(name)] = expression + self._parameters[str(name).lower()] = expression ############################################## @@ -1060,7 +1062,7 @@ def model(self, name, model_type, **parameters): model = DeviceModel(str(name).lower(), model_type, **parameters) if model.name not in self._models: - self._models[str(model.name).lower()] = model + self._models[model.name] = model else: raise NameError("Model name {} is already defined".format(name)) @@ -1104,7 +1106,7 @@ def __str__(self): ############################################## def _str_parameters(self): - parameters = [".param {}={}".format(key, str_spice(value)) + 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) @@ -1156,8 +1158,9 @@ def include(self, path, entry=None): library = library[entry] models = library.models for model in models: - self.model(model._name, model._model_type, **model._parameters) - self._models[model._name.lower()]._included = path + model_name = str(model._name).lower() + self.model(model_name, model._model_type, **model._parameters) + self._models[model_name]._included = path subcircuits = library.subcircuits for subcircuit in subcircuits: subcircuit_def = subcircuit.build(parent=self) @@ -1275,9 +1278,9 @@ def __str__(self): netlist = '.subckt ' + join_list((self._name, nodes)) if self._params: - parameters = join_list(['{}={}'.format(key, str_spice(value)) - for key, value in self._params.items()]) - netlist += ' params: ' + parameters + 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 netlist += super().__str__() netlist += '.ends ' + self._name + os.linesep diff --git a/PySpice/Spice/RawFile.py b/PySpice/Spice/RawFile.py index bb8b1b4fb..10475a078 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, } ############################################## @@ -260,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 diff --git a/PySpice/Spice/Simulation.py b/PySpice/Spice/Simulation.py index 7ad796ad7..15a64a736 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 #################################################################################################### @@ -542,8 +542,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)) ############################################## @@ -567,7 +567,7 @@ def temperature(self): @temperature.setter def temperature(self, value): - self._options['TEMP'] = as_Degree(value) + self._options['TEMP'] = as_c(value) ############################################## @@ -577,7 +577,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) ############################################## diff --git a/PySpice/Tools/StringTools.py b/PySpice/Tools/StringTools.py index 2df200b9e..82715c2e0 100644 --- a/PySpice/Tools/StringTools.py +++ b/PySpice/Tools/StringTools.py @@ -32,23 +32,16 @@ #################################################################################################### -from ..Spice.Expressions import Expression -#################################################################################################### - 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) - elif isinstance(obj, Expression): - return "{{{}}}".format(obj) + if hasattr(obj, 'str_spice'): + return obj.str_spice(unit) else: return str(obj).lower() diff --git a/PySpice/Unit/SiUnits.py b/PySpice/Unit/SiUnits.py index a78fd389c..8bed9b9e0 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 @@ -124,11 +123,13 @@ class Yocto(UnitPrefix): 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): @@ -139,17 +140,20 @@ class Second(SiBaseUnit): class Ampere(SiBaseUnit): UNIT_NAME = 'ampere' - UNIT_SUFFIX = 'a' + 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,43 +175,49 @@ 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 class Hertz(Unit): UNIT_NAME = 'frequency' - UNIT_SUFFIX = 'hz' + UNIT_SUFFIX = 'Hz' + SPICE_SUFFIX = 'hz' QUANTITY = 'frequency' SI_UNIT = 's^-1' DEFAULT_UNIT = True -#class Newton(Unit): -# UNIT_NAME = 'newton' -# UNIT_SUFFIX = 'N' -# QUANTITY = 'force' -# SI_UNIT = 'kg*m*s^-2' -# DEFAULT_UNIT = True - -#class Pascal(Unit): -# UNIT_NAME = 'pascal' -# UNIT_SUFFIX = 'Pa' -# QUANTITY = 'pressure' -# SI_UNIT = 'kg*m^-1*s^-2' -# DEFAULT_UNIT = True +class Newton(Unit): + UNIT_NAME = 'newton' + UNIT_SUFFIX = 'N' + SPICE_SUFFIX = '' + QUANTITY = 'force' + SI_UNIT = 'kg*m*s^-2' + DEFAULT_UNIT = True + +class Pascal(Unit): + UNIT_NAME = 'pascal' + UNIT_SUFFIX = 'Pa' + SPICE_SUFFIX = '' + QUANTITY = 'pressure' + SI_UNIT = 'kg*m^-1*s^-2' + DEFAULT_UNIT = True # N/m^2 -#class Joule(Unit): -# UNIT_NAME = 'joule' -# UNIT_SUFFIX = 'J' -# QUANTITY = 'energy' -# SI_UNIT = 'kg*m^2*s^-2' -# DEFAULT_UNIT = True +class Joule(Unit): + UNIT_NAME = 'joule' + UNIT_SUFFIX = 'J' + SPICE_SUFFIX = '' + QUANTITY = 'energy' + SI_UNIT = 'kg*m^2*s^-2' + DEFAULT_UNIT = True # N*m class Watt(Unit): UNIT_NAME = 'watt' - UNIT_SUFFIX = 'w' + UNIT_SUFFIX = 'W' + SPICE_SUFFIX = 'w' QUANTITY = 'power' SI_UNIT = 'kg*m^2*s^-3' DEFAULT_UNIT = True @@ -216,40 +226,45 @@ class Watt(Unit): class Coulomb(Unit): UNIT_NAME = 'coulomb' UNIT_SUFFIX = 'c' + SPICE_SUFFIX = '' QUANTITY = 'electric charge' SI_UNIT = 's*A' DEFAULT_UNIT = True class Volt(Unit): UNIT_NAME = 'volt' - UNIT_SUFFIX = 'v' + UNIT_SUFFIX = 'V' + SPICE_SUFFIX = 'v' QUANTITY = 'voltage' SI_UNIT = 'kg*m^2*s^-3*A^-1' DEFAULT_UNIT = True # W/A -#class Farad(Unit): -# UNIT_NAME = 'farad' -# UNIT_SUFFIX = 'F' -# QUANTITY = 'capacitance' -# SI_UNIT = 'kg^-1*m^-2*s^4*A^2' -# DEFAULT_UNIT = True +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 # C/V 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 # V/A -#class Siemens(Unit): -# UNIT_NAME = 'siemens' -# UNIT_SUFFIX = 'S' -# QUANTITY = 'electrical conductance' -# SI_UNIT = 'kg^-1*m^-2*s^3*A^2' -# DEFAULT_UNIT = True +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 # A/V class Weber(Unit): @@ -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 @@ -272,13 +288,14 @@ class Henry(Unit): UNIT_NAME = 'henry' UNIT_SUFFIX = 'h' QUANTITY = 'inductance' - SI_UNIT = 'kg*m^2*s^-2*a^-2' + SI_UNIT = 'kg*m^2*s^-2*A^-2' DEFAULT_UNIT = True # Wb/A class DegreeCelsius(Unit): UNIT_NAME = 'degree celsius' UNIT_SUFFIX = '°C' + SPICE_SUFFIX = 'c' QUANTITY = 'temperature relative to 273.15 K' SI_UNIT = 'K' @@ -303,25 +320,28 @@ class Becquerel(Unit): QUANTITY = 'radioactivity (decays per unit time)' SI_UNIT = 's^-1' # same as Hertz -#class Gray(Unit): -# UNIT_NAME = 'gray' -# UNIT_SUFFIX = 'Gy' -# QUANTITY = 'absorbed dose (of ionizing radiation)' -# SI_UNIT = 'm^2*s^-2' +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' + SPICE_SUFFIX = '' QUANTITY = ' equivalent dose (of ionizing radiation)' SI_UNIT = 'm^2*s^-2' -#class Katal(Unit): -# UNIT_NAME = 'katal' -# UNIT_SUFFIX = 'kat' -# QUANTITY = 'catalytic activity' -# SI_UNIT = 'mol*s^-1' -# DEFAULT_UNIT = True +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 9794a7fbd..a1bc30c29 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 symbol 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) ############################################## @@ -861,7 +867,7 @@ class UnitValue: # numbers.Real """This class implements a value with a unit and a power (prefix). - The value is not converted to float if the value is an int or expression. + The value is not converted to float if the value is an int. """ _logger = _module_logger.getChild('UnitValue') @@ -875,20 +881,23 @@ def simple_value(cls, value): ############################################## def __init__(self, prefixed_unit, value): - 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) or isinstance(value, Expression): + elif isinstance(value, int): self._value = value # to keep as int + elif isinstance(value, str): + ValueError(value) else: self._value = float(value) @@ -999,13 +1008,11 @@ def __float__(self): ############################################## def str(self, spice=False, space=False, unit=True): - from ..Spice.Expressions import Expression - if isinstance(self._value, Expression): - return "{%s}" % self._value - string = str(self._value) - if space: + string = str_spice(self._value, unit=False) + if space and not self.unit.is_unit_less: string += ' ' - string += self._prefixed_unit.str(spice, unit) + if self.prefixed_unit: + string += self.prefixed_unit.str(spice, unit) return string ############################################## @@ -1015,8 +1022,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) ############################################## 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/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/test_ExpressionParser.py b/unit-test/Spice/test_ExpressionParser.py index aa0324d79..88934b0a6 100644 --- a/unit-test/Spice/test_ExpressionParser.py +++ b/unit-test/Spice/test_ExpressionParser.py @@ -1,78 +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", - ".1E0", - "-.1E1", - "+.1E1", - "1.E0", - "-1.E-1", - "+1.E-1", - "1.23", - "-1.23", - "+1.23", - "1.23E1", - "1.23E-1", - "{True?1:0}", - "{False?1:0}", - "{a + b}", - "{a - b}", - "{(a + b)}", - "{(a - b)}", - "{a * b}", - "{a / b}", - "{(a * b)}", - "{(a / b)}", - "{(a / b) * c}", - "{if(1<2,1,0)}", - "{if((1<2),(1),(0))}", - "{2<=1?1:0}", - "{a + (b + c)}", - "{(a + b) + c}", - "1", - "{1}", - "{1+2}", - "{(1+2)}", - "{(1+2) + 3}", - "{(1+2) * 3}", - "{(1+2) * (3 + 7)}", - "{(1+2) * -(3 + 7)}", - "{(1+a) * -(b + 7)}", - "{(1+sin(3.14)) * -(3 + 7)}", - "{(1+v(a)) * -(3 + 7)}", - "{atan2(asin(b), ln(c))}", - "{atan2(asin(b) - 7, ln(c) + 5)}", - "{ddx(asin, ln(c) + 5)}", - "{if(True, 1, 2)}", - "{if(2 < 3, 1, 2)}", - "{if((2 < 3) | False , 1, 2)}", - "{(2 < 3) | False ? True ? 3: 4: 2}", - "{(2 < 3) | False ? True ? 3: sin(4): 2}", - "{(2 < 3) | False ? (True ? 3: sin(4)): 2}", - "{(2 < 3) & ( False | True) ? (True ? 3: sin(4)): 2}", - "{~(2 < 3) & ~( False | True) ? (True ? 3: sin(4)): 2}", - "{limit(3, 2, a)}", - "{limit(3, 2, a)}" -] +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 in data: + for case, expr in data.items(): expr_i = ExpressionParser.parse(source=case) - case_i = "{%s}" % expr_i - expr_f = ExpressionParser.parse(source=case_i) - self.assertEqual("{%s}" % expr_f, case_i) + 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_HighLevelElement.py b/unit-test/Spice/test_HighLevelElement.py index 1e32715a5..4831c306a 100644 --- a/unit-test/Spice/test_HighLevelElement.py +++ b/unit-test/Spice/test_HighLevelElement.py @@ -45,7 +45,7 @@ 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 r=0s td=0.0s)', ) @@ -54,7 +54,7 @@ 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)], 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)', @@ -64,9 +64,9 @@ 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)], repeat_time=12@u_ms, time_delay=34@u_ms, - dc=50@u_V, + dc=50@u_v, ), 'vpwl1 1 0 dc 50v pwl(0s 0v 10ms 0v 11ms 5v 20ms 5v r=12ms td=34ms)', ) diff --git a/unit-test/SpiceParser/test_SpiceParser.py b/unit-test/SpiceParser/test_SpiceParser.py index e38e775ab..0d838a57a 100644 --- a/unit-test/SpiceParser/test_SpiceParser.py +++ b/unit-test/SpiceParser/test_SpiceParser.py @@ -48,11 +48,11 @@ +(79.1111, 0.00033272) +(80, 0.00275)} -G1 21 98 (6,15) 26E-6 +G1N 21 98 (6,15) 26E-6 E23 21 98 (6,15) 26E-6 -BG1 IOUT- IOUT+ I={IF( (V(VC+,VC-)<=0),0,GAIN*V(VC+,VC-) )} +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 @@ -67,7 +67,7 @@ E1 3 0 5 0 10 -G1 21 3 POLY(1) 1 3 0 1.57E-6 -0.97e-7 +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µ) @@ -141,11 +141,11 @@ .MODEL NOUT NMOS -BG1 IOUT- IOUT+ I={IF( (V(VC+,VC-) <= 0) , 0 , 1 )} -BG2 IOUT- IOUT+ I={IF( (V(VC+,VC-)<=0),0,GAIN*V(VC+,VC-) )} +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 -BG1 IOUTp IOUTm I={TABLE {V(VCp,VCm)} = +BG4 IOUTp IOUTm I={TABLE {V(VCp,VCm)} = +(0, 12.09e-6) +(26.6667, 0.0002474) +(53.3333, 0.00029078) @@ -518,8 +518,7 @@ def test_transient(self): 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) -IPWL1 1 0 PWL(0S 0A 2S 3A 3S 2A 4S 2A 4.01S 5A r=2s td=1 ) +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 """) @@ -586,8 +585,8 @@ def test_subcircuits(self): .ends test3 -.subckt test4 5 6 params: j={(a + b)} -.param d={(j + 32)} +.subckt test4 5 6 params: j={a + b} +.param d={j + 32} .ends test4 @@ -611,8 +610,8 @@ def test_sources(self): 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)))} +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) @@ -626,9 +625,9 @@ def test_boolean(self): 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)} +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) @@ -645,7 +644,7 @@ def test_subcircuit(self): .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 +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 @@ -659,7 +658,7 @@ def test_subcircuit(self): .ends mosdriver xtest 0 1 2 3 4 5 6 7 mosdriver -btest 1 0 v={if(True, 0, 1)} smoothbsrc=1 +btest 1 0 v={if(true, 0, 1)} smoothbsrc=1 """ result = str(circuit) self.assertEqual(expected, result) @@ -702,7 +701,7 @@ def test_source(self): expected = """.title -bhn 81 98 v={(i(vn1) * 6)} +bhn 81 98 v={i(vn1) * 6} vp5 np5 0 dc 5v vm5 nm5 0 dc -5v """ 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') ############################################## From 7c9fe99f8187998110dd42c5c466fa5cb6b5c0bb Mon Sep 17 00:00:00 2001 From: jmgc Date: Thu, 9 Mar 2023 02:09:00 +0100 Subject: [PATCH 110/134] Update WaveForm.py Work with lower case in all the cases, to be consistent with SPICE's case-insensitive behaviour. --- PySpice/Probe/WaveForm.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/PySpice/Probe/WaveForm.py b/PySpice/Probe/WaveForm.py index d0ea9e6dd..9b88baf16 100644 --- a/PySpice/Probe/WaveForm.py +++ b/PySpice/Probe/WaveForm.py @@ -255,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} ############################################## @@ -302,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()) ############################################## From ef84b908fc8fa7b506086b15f37b8fc8ee9a3887 Mon Sep 17 00:00:00 2001 From: jmgc Date: Tue, 14 Mar 2023 09:03:21 +0000 Subject: [PATCH 111/134] Update SiUnits.py Rename celsius SPICE suffix. --- PySpice/Unit/SiUnits.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PySpice/Unit/SiUnits.py b/PySpice/Unit/SiUnits.py index 8bed9b9e0..defe8ceb2 100644 --- a/PySpice/Unit/SiUnits.py +++ b/PySpice/Unit/SiUnits.py @@ -295,7 +295,7 @@ class Henry(Unit): class DegreeCelsius(Unit): UNIT_NAME = 'degree celsius' UNIT_SUFFIX = '°C' - SPICE_SUFFIX = 'c' + SPICE_SUFFIX = 'celsius' QUANTITY = 'temperature relative to 273.15 K' SI_UNIT = 'K' From f70539163e261d29bd59add1070bfc7bcfcb8df1 Mon Sep 17 00:00:00 2001 From: jmgc Date: Mon, 27 Mar 2023 14:50:15 +0200 Subject: [PATCH 112/134] Solve an issue with expressions Manage an expression as positional parameter. --- PySpice/Spice/ElementParameter.py | 9 ++++++++- unit-test/Spice/test_BasicElement.py | 5 ++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/PySpice/Spice/ElementParameter.py b/PySpice/Spice/ElementParameter.py index 5081d7b1f..7be06aa26 100644 --- a/PySpice/Spice/ElementParameter.py +++ b/PySpice/Spice/ElementParameter.py @@ -171,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 '' ############################################## diff --git a/unit-test/Spice/test_BasicElement.py b/unit-test/Spice/test_BasicElement.py index 52def75e0..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 * #################################################################################################### @@ -52,10 +53,12 @@ def test(self): scale=1.5, temperature=25, device_temperature=26, noisy=True), - 'R1 n1 n2 1kohm ac=2kohm dtemp=26c m=2 noisy=1 scale=1.5 temp=25c'.lower()) + '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 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') From 7e92501544a260418c443840ea4dd495e0331041 Mon Sep 17 00:00:00 2001 From: jmgc Date: Thu, 30 Mar 2023 10:55:39 +0200 Subject: [PATCH 113/134] Added the option to use LIST in dc simulation --- PySpice/Spice/Simulation.py | 10 ++++++++-- unit-test/Spice/test_Netlist.py | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/PySpice/Spice/Simulation.py b/PySpice/Spice/Simulation.py index 15a64a736..97432f0e9 100644 --- a/PySpice/Spice/Simulation.py +++ b/PySpice/Spice/Simulation.py @@ -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') diff --git a/unit-test/Spice/test_Netlist.py b/unit-test/Spice/test_Netlist.py index b06b6d55a..506c267e2 100644 --- a/unit-test/Spice/test_Netlist.py +++ b/unit-test/Spice/test_Netlist.py @@ -21,6 +21,7 @@ #################################################################################################### import unittest +import shutil #################################################################################################### @@ -206,6 +207,23 @@ 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', 'out', 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()) + + #################################################################################################### if __name__ == '__main__': From 9dacc67fb87fbb771f6809d6993b45ccd26199c0 Mon Sep 17 00:00:00 2001 From: jmgc Date: Fri, 26 May 2023 09:26:28 +0200 Subject: [PATCH 114/134] Update Shared.py Correction of unit name. --- PySpice/Spice/NgSpice/Shared.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From b0c4c8c70de2c744a2030f26f88df7e1c2d137b8 Mon Sep 17 00:00:00 2001 From: jmgc Date: Fri, 26 May 2023 09:27:33 +0200 Subject: [PATCH 115/134] Update Unit to use Expressions Update PrefixedUnit and UnitValue to manage expressions. --- PySpice/Unit/Unit.py | 25 +++++++++++++++++------- unit-test/Spice/test_HighLevelElement.py | 16 +++++++++++++++ 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/PySpice/Unit/Unit.py b/PySpice/Unit/Unit.py index a1bc30c29..1eb17be56 100644 --- a/PySpice/Unit/Unit.py +++ b/PySpice/Unit/Unit.py @@ -856,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) @@ -881,6 +881,8 @@ def simple_value(cls, value): ############################################## def __init__(self, prefixed_unit, value): + from ..Spice.EBNFExpressionParser import ExpressionParser + self._prefixed_unit = prefixed_unit if isinstance(value, UnitValue): @@ -897,7 +899,11 @@ def __init__(self, prefixed_unit, value): elif isinstance(value, int): self._value = value # to keep as int elif isinstance(value, str): - ValueError(value) + try: + expr = ExpressionParser.parse(value) + self._value = expr + except: + self._value = float(value) else: self._value = float(value) @@ -1008,11 +1014,16 @@ def __float__(self): ############################################## def str(self, spice=False, space=False, unit=True): - 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) + 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 ############################################## diff --git a/unit-test/Spice/test_HighLevelElement.py b/unit-test/Spice/test_HighLevelElement.py index 4831c306a..b2b5a163c 100644 --- a/unit-test/Spice/test_HighLevelElement.py +++ b/unit-test/Spice/test_HighLevelElement.py @@ -71,6 +71,22 @@ def test(self): '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)', + ) + #################################################################################################### if __name__ == '__main__': From 83064967d10b9b3dcbe34219ba211a7da294560d Mon Sep 17 00:00:00 2001 From: jmgc Date: Fri, 26 May 2023 09:40:30 +0200 Subject: [PATCH 116/134] Improvement on Unit with Expression Take into account the case when the Unit value is already an Expression. And the associated test. --- PySpice/Unit/Unit.py | 4 +++- unit-test/SpiceParser/test_SpiceParser.py | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/PySpice/Unit/Unit.py b/PySpice/Unit/Unit.py index 1eb17be56..5099c5132 100644 --- a/PySpice/Unit/Unit.py +++ b/PySpice/Unit/Unit.py @@ -43,6 +43,7 @@ from ..Tools.EnumFactory import EnumFactory from ..Tools.StringTools import str_spice +from ..Spice.Expressions import Expression #################################################################################################### @@ -904,6 +905,8 @@ def __init__(self, prefixed_unit, value): self._value = expr except: self._value = float(value) + elif isinstance(value, Expression): + self._value = value else: self._value = float(value) @@ -1014,7 +1017,6 @@ def __float__(self): ############################################## def str(self, spice=False, space=False, unit=True): - from ..Spice.Expressions import Expression if isinstance(self._value, Expression): string = '{{{}}}'.format(str_spice(self._value, unit=False)) else: diff --git a/unit-test/SpiceParser/test_SpiceParser.py b/unit-test/SpiceParser/test_SpiceParser.py index 0d838a57a..f62ded8c0 100644 --- a/unit-test/SpiceParser/test_SpiceParser.py +++ b/unit-test/SpiceParser/test_SpiceParser.py @@ -704,6 +704,21 @@ def test_source(self): 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) From 0387b7e48cfb5a3907d5b1d4868710291a0184ec Mon Sep 17 00:00:00 2001 From: jmgc Date: Fri, 26 May 2023 09:45:29 +0200 Subject: [PATCH 117/134] Update Unit.py Correction to avoid circular imports. --- PySpice/Unit/Unit.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/PySpice/Unit/Unit.py b/PySpice/Unit/Unit.py index 5099c5132..d45d1f741 100644 --- a/PySpice/Unit/Unit.py +++ b/PySpice/Unit/Unit.py @@ -43,7 +43,6 @@ from ..Tools.EnumFactory import EnumFactory from ..Tools.StringTools import str_spice -from ..Spice.Expressions import Expression #################################################################################################### @@ -883,6 +882,7 @@ 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 @@ -1017,6 +1017,8 @@ def __float__(self): ############################################## def str(self, spice=False, space=False, unit=True): + from ..Spice.Expressions import Expression + if isinstance(self._value, Expression): string = '{{{}}}'.format(str_spice(self._value, unit=False)) else: From eea45086db75ef632394e49b39681642841bea29 Mon Sep 17 00:00:00 2001 From: jmgc Date: Sun, 4 Jun 2023 08:41:37 +0200 Subject: [PATCH 118/134] Update voltage-divider.py Update the example. --- examples/resistor/voltage-divider.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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# From 5bad95db536d28917149cad1bacc52f2a48d6c5e Mon Sep 17 00:00:00 2001 From: jmgc Date: Sun, 4 Jun 2023 08:42:03 +0200 Subject: [PATCH 119/134] Delete Parser_jmgc.py It is subsided by EBNFParser. --- PySpice/Spice/Parser_jmgc.py | 1486 ---------------------------------- 1 file changed, 1486 deletions(-) delete mode 100644 PySpice/Spice/Parser_jmgc.py 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 From eca4d6856818dadeef8b8753e9ea74b943574aa9 Mon Sep 17 00:00:00 2001 From: jmgc Date: Sun, 4 Jun 2023 08:42:59 +0200 Subject: [PATCH 120/134] Update to use the EBNFParser in Library Updates on Library and Netlist to allow the use of EBNFParser in library. --- PySpice/Spice/Library.py | 26 +++++++++++------------ PySpice/Spice/Netlist.py | 13 +++++++++--- unit-test/Spice/test_Netlist.py | 7 +++++- unit-test/SpiceParser/test_SpiceParser.py | 13 +++++++++--- 4 files changed, 39 insertions(+), 20 deletions(-) diff --git a/PySpice/Spice/Library.py b/PySpice/Spice/Library.py index 4518d30b4..24425844c 100644 --- a/PySpice/Spice/Library.py +++ b/PySpice/Spice/Library.py @@ -26,7 +26,7 @@ #################################################################################################### from ..Tools.File import Directory -from .Parser import SpiceParser +from .EBNFSpiceParser import SpiceParser #################################################################################################### @@ -76,22 +76,22 @@ def __init__(self, root_path, recurse=False, section=None): 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) + spice_parser = SpiceParser.parse(path=path, library=True) + for subcircuit in spice_parser.subcircuits: + self._subcircuits[str(subcircuit.name).lower()] = subcircuit + for model in spice_parser.models: + self._models[str(model.name).lower()] = model except Exception as e: # 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 + for subcircuit in self._subcircuits.values(): + for sub in subcircuit._required_subcircuits: + if sub in self._subcircuits: + subcircuit._subcircuits.append(self._subcircuits[sub]) + for mod in subcircuit._required_models: + if mod in self._models: + subcircuit._models.append(self._models[mod]) ############################################## diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index 1fb09f7fb..e4a36f7f2 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -1147,11 +1147,18 @@ def _str_raw_spice(self): return netlist def include(self, path, entry=None): - from .EBNFSpiceParser import SpiceParser + from .EBNFSpiceParser import SpiceParser, ModelStatement, SubCircuitStatement """Include a file.""" - - if path not in self._includes: + if isinstance(path, ModelStatement): + model = path + model_def = model.build(parent=self) + self.model(model_def) + elif isinstance(path, SubCircuitStatement): + subcircuit = path + subcircuit_def = subcircuit.build(parent=self) + self.subcircuit(subcircuit_def) + elif path not in self._includes: self._includes.append(path) library = SpiceParser.parse(path=path) if entry is not None: diff --git a/unit-test/Spice/test_Netlist.py b/unit-test/Spice/test_Netlist.py index 506c267e2..fc114d5ea 100644 --- a/unit-test/Spice/test_Netlist.py +++ b/unit-test/Spice/test_Netlist.py @@ -213,7 +213,7 @@ 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', 'out', 9@u_kΩ) + circuit.R('load', 'in', circuit.gnd, 9@u_kΩ) simulator = circuit.simulator(simulator='xyce-serial', working_directory='.') simulator.save('all') if shutil.which('xyce'): @@ -222,6 +222,11 @@ def test_dc_list(self): 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()) #################################################################################################### diff --git a/unit-test/SpiceParser/test_SpiceParser.py b/unit-test/SpiceParser/test_SpiceParser.py index f62ded8c0..15709d207 100644 --- a/unit-test/SpiceParser/test_SpiceParser.py +++ b/unit-test/SpiceParser/test_SpiceParser.py @@ -435,6 +435,14 @@ def circuit_gft(prb): #################################################################################################### 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') ############################################## @@ -450,8 +458,7 @@ def test_parser(self): def test_library(self): from PySpice.Spice.Library import SpiceLibrary - import os - libraries_path = os.path.abspath('unit-test/SpiceParser') + libraries_path = self._getdir('unit-test/SpiceParser') spice_library = SpiceLibrary(libraries_path) circuit = Circuit('MOS Driver') circuit.include(spice_library['mosdriver']) @@ -636,7 +643,7 @@ def test_subcircuit(self): print(os.getcwd()) circuit = Circuit('MOS Driver\nSimple check') circuit.spice_sim = 'xyce' - circuit.include(os.path.join(os.getcwd(), 'unit-test/SpiceParser/mosdriver.lib')) + 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 From 7491d760a6b0e29c00249b12aa92263f7d86ec52 Mon Sep 17 00:00:00 2001 From: jmgc Date: Sun, 4 Jun 2023 08:59:15 +0200 Subject: [PATCH 121/134] Add the possibility to add raw components --- PySpice/Spice/Library.py | 40 +++++++++++++++-------- unit-test/SpiceParser/test_SpiceParser.py | 19 +++++++++++ 2 files changed, 46 insertions(+), 13 deletions(-) diff --git a/PySpice/Spice/Library.py b/PySpice/Spice/Library.py index 24425844c..c39f90607 100644 --- a/PySpice/Spice/Library.py +++ b/PySpice/Spice/Library.py @@ -62,36 +62,45 @@ class SpiceLibrary: '.mod@xyce', ) + def _add_parsed(self, parsed): + for subcircuit in parsed.subcircuits: + self._subcircuits[str(subcircuit.name).lower()] = subcircuit + for model in parsed.models: + self._models[str(model.name).lower()] = model + + def _update_subcircuits(self): + for subcircuit in self._subcircuits.values(): + for sub in subcircuit._required_subcircuits: + if sub in self._subcircuits: + subcircuit._subcircuits.append(self._subcircuits[sub]) + for mod in subcircuit._required_models: + if mod in self._models: + subcircuit._models.append(self._models[mod]) + ############################################## - 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 = {} + if root_path is 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.parse(path=path, library=True) - for subcircuit in spice_parser.subcircuits: - self._subcircuits[str(subcircuit.name).lower()] = subcircuit - for model in spice_parser.models: - self._models[str(model.name).lower()] = model + parsed = SpiceParser.parse(path=path, library=True) + self._add_parsed(parsed) except Exception as e: # Parse problem with this file, so skip it and keep going. self._logger.warn("Problem parsing {path} - {e}".format(**locals())) continue - for subcircuit in self._subcircuits.values(): - for sub in subcircuit._required_subcircuits: - if sub in self._subcircuits: - subcircuit._subcircuits.append(self._subcircuits[sub]) - for mod in subcircuit._required_models: - if mod in self._models: - subcircuit._models.append(self._models[mod]) + self._update_subcircuits() ############################################## @@ -148,3 +157,8 @@ 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) + self._update_subcircuits() diff --git a/unit-test/SpiceParser/test_SpiceParser.py b/unit-test/SpiceParser/test_SpiceParser.py index 15709d207..3d55ce490 100644 --- a/unit-test/SpiceParser/test_SpiceParser.py +++ b/unit-test/SpiceParser/test_SpiceParser.py @@ -456,6 +456,25 @@ def test_parser(self): 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 + +.subckt check 5 6 7 +.ends +""") + circuit = Circuit('Lib test') + circuit.include(spice_library['test1']) + x_mos = circuit.X('test', + 'test1', + 'hb', + 'hi', + 'ho') + def test_library(self): from PySpice.Spice.Library import SpiceLibrary libraries_path = self._getdir('unit-test/SpiceParser') From b3054d14cc12de303d0906e611bbd78e50d884be Mon Sep 17 00:00:00 2001 From: jmgc Date: Sun, 4 Jun 2023 12:15:49 +0200 Subject: [PATCH 122/134] Improved the library with parser integration Solved issues with the inclusion of the spice. --- PySpice/Spice/EBNFSpiceParser.py | 40 +++++++++--- PySpice/Spice/Library.py | 12 ++-- PySpice/Spice/Netlist.py | 78 +++++++++++++---------- unit-test/SpiceParser/test_SpiceParser.py | 31 ++++++--- 4 files changed, 102 insertions(+), 59 deletions(-) diff --git a/PySpice/Spice/EBNFSpiceParser.py b/PySpice/Spice/EBNFSpiceParser.py index 801844174..349f60994 100644 --- a/PySpice/Spice/EBNFSpiceParser.py +++ b/PySpice/Spice/EBNFSpiceParser.py @@ -140,7 +140,7 @@ def __init__(self, parent, filename): 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, library=True) + self._contents = SpiceParser.parse(path=file_name) except Exception as e: raise ParseError("{}: {:s}".format(parent.path, e)) @@ -172,10 +172,16 @@ class ModelStatement(Statement): """ + 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 = name + self._name = str(name).lower() self._model_type = device self._parameters = parameters @@ -456,10 +462,15 @@ class SubCircuitStatement(Statement): """ ############################################## + 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 = name + self._name = str(name).lower() self._nodes = nodes self._params = params @@ -638,7 +649,7 @@ def parameters(self): def __repr__(self): - text = 'Library {}'.format(self._title) + os.linesep + 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 @@ -1824,7 +1835,16 @@ def __init__(self): pass @staticmethod - def parse(path=None, source=None, library=False): + def _update_subcircuits(statement, library): + for sub in statement._required_subcircuits: + if sub not in statement._subcircuits and sub in library._subcircuits: + statement._subcircuits.append(library._subcircuits[sub]) + for mod in statement._required_models: + if mod not in statement._models and mod in library._models: + statement._models.append(library._models[mod]) + + @staticmethod + def parse(path=None, source=None, library=None): # Fixme: empty source if path is not None: @@ -1847,11 +1867,11 @@ def parse(path=None, source=None, library=False): path = os.getcwd() data = ParsingData(path) circuit = SpiceParser._walker.walk(model, data) - if library: - circuit._required_models = {model.name.lower() - for model in circuit._models} - circuit._required_subcircuits = {subckt.name.lower() - for subckt in circuit._subcircuits} + if library is not None: + SpiceParser._update_subcircuits(circuit, library) + for subcircuit in circuit._subcircuits: + SpiceParser._update_subcircuits(subcircuit, library) + try: SpiceParser._check_models(circuit) SpiceParser._sort_subcircuits(circuit) diff --git a/PySpice/Spice/Library.py b/PySpice/Spice/Library.py index c39f90607..2ae25cc24 100644 --- a/PySpice/Spice/Library.py +++ b/PySpice/Spice/Library.py @@ -22,6 +22,7 @@ import logging import re +import sys #################################################################################################### @@ -71,10 +72,10 @@ def _add_parsed(self, parsed): def _update_subcircuits(self): for subcircuit in self._subcircuits.values(): for sub in subcircuit._required_subcircuits: - if sub in self._subcircuits: + if sub not in subcircuit._subcircuits and sub in self._subcircuits: subcircuit._subcircuits.append(self._subcircuits[sub]) for mod in subcircuit._required_models: - if mod in self._models: + if mod not in subcircuit._models and mod in self._models: subcircuit._models.append(self._models[mod]) ############################################## @@ -87,6 +88,7 @@ def __init__(self, root_path=None, recurse=False, section=None): self._models = {} if root_path is None: + self._directory=None return for path in self._directory.iter_file(): @@ -94,12 +96,12 @@ def __init__(self, root_path=None, recurse=False, section=None): if extension in self.EXTENSIONS: self._logger.debug("Parse {}".format(path)) try: - parsed = SpiceParser.parse(path=path, library=True) + 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 + raise RuntimeError("Problem parsing {path} - {e}\n{}".format(**locals())).with_traceback(tb) self._update_subcircuits() ############################################## diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index e4a36f7f2..7820bc9f6 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -884,7 +884,7 @@ def __init__(self): self.raw_spice = '' - self.spice_sim = '' + self._spice_sim = '' ############################################## @@ -952,6 +952,16 @@ 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): @@ -1146,39 +1156,22 @@ def _str_raw_spice(self): netlist += os.linesep return netlist - def include(self, path, entry=None): - from .EBNFSpiceParser import SpiceParser, ModelStatement, SubCircuitStatement + def include(self, library): + from .Library import SpiceLibrary """Include a file.""" - if isinstance(path, ModelStatement): - model = path - model_def = model.build(parent=self) - self.model(model_def) - elif isinstance(path, SubCircuitStatement): - subcircuit = path - subcircuit_def = subcircuit.build(parent=self) - self.subcircuit(subcircuit_def) - elif path not in self._includes: - self._includes.append(path) - library = SpiceParser.parse(path=path) - if entry is not None: - library = library[entry] - models = library.models - for model in models: - model_name = str(model._name).lower() - self.model(model_name, model._model_type, **model._parameters) - self._models[model_name]._included = path - subcircuits = library.subcircuits - for subcircuit in subcircuits: - subcircuit_def = subcircuit.build(parent=self) - self.subcircuit(subcircuit_def) - self._subcircuits[subcircuit._name.lower()]._included = path - parameters = library.parameters - for param in parameters: - self.parameters(*param) + if isinstance(library, SpiceLibrary): + spice_library = library else: - self._logger.warn("Duplicated include") - + spice_library = SpiceLibrary(library) + models = spice_library.models + for model in models: + model_name = str(model).lower() + self.model(model_name, spice_library[model]._model_type, **spice_library[model]._parameters) + subcircuits = spice_library.subcircuits + for subcircuit in subcircuits: + subcircuit_def = spice_library[subcircuit].build(parent=self) + self.subcircuit(subcircuit_def) #################################################################################################### @@ -1280,16 +1273,31 @@ def check_nodes(self): ############################################## def __str__(self): + netlist = self._str_raw_spice() + + if self._subcircuits: + subcircuits = self._str_subcircuits() + netlist += subcircuits # before elements + netlist += os.linesep + """Return the formatted subcircuit definition.""" nodes = join_list(self._external_nodes) - netlist = '.subckt ' + join_list((self._name, nodes)) + 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 - netlist += super().__str__() + if self._parameters: + parameters = self._str_parameters() + netlist += parameters + netlist += os.linesep + if self._models: + models = self._str_models() + netlist += models + netlist += os.linesep + netlist += self._str_elements() + os.linesep netlist += '.ends ' + self._name + os.linesep return netlist @@ -1421,8 +1429,8 @@ def str(self, spice_sim=None): netlist = self._str_title() netlist += os.linesep # netlist += self._str_includes(simulator) - for sub_circuit in self.subcircuits: - sub_circuit.spice_sim = self.spice_sim + 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 diff --git a/unit-test/SpiceParser/test_SpiceParser.py b/unit-test/SpiceParser/test_SpiceParser.py index 3d55ce490..90b3081d7 100644 --- a/unit-test/SpiceParser/test_SpiceParser.py +++ b/unit-test/SpiceParser/test_SpiceParser.py @@ -467,20 +467,32 @@ def test_libraryRaw(self): .subckt check 5 6 7 .ends """) - circuit = Circuit('Lib test') - circuit.include(spice_library['test1']) - x_mos = circuit.X('test', - 'test1', - 'hb', - 'hi', - 'ho') + 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['mosdriver']) + circuit.include(spice_library) x_mos = circuit.X('driver', 'mosdriver', 'hb', @@ -668,12 +680,13 @@ def test_subcircuit(self): 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 +.model diode D (is=1.038e-15 n=1 tt=2e-08 cjo=5e-12 rs=0.5 bv=130) xhigh hoi hs hi vss source rhoi hoi ho 1ohm choi ho hs 1e-09 From cd7f725bf0144af2bef6751b716e0996260529fb Mon Sep 17 00:00:00 2001 From: jmgc Date: Mon, 26 Jun 2023 08:04:59 +0200 Subject: [PATCH 123/134] Update RawFile.py Change to numpy frombuffer to avoid warning. --- PySpice/Spice/RawFile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PySpice/Spice/RawFile.py b/PySpice/Spice/RawFile.py index 10475a078..b16053f2e 100644 --- a/PySpice/Spice/RawFile.py +++ b/PySpice/Spice/RawFile.py @@ -296,7 +296,7 @@ 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') input_data = input_data.reshape((self.number_of_points, number_of_columns)) input_data = input_data.transpose() # np.savetxt('raw.txt', input_data) From 87a1fd5d6907a9e3073e7cdf08ecacca9d71174c Mon Sep 17 00:00:00 2001 From: jmgc Date: Mon, 26 Jun 2023 08:17:56 +0200 Subject: [PATCH 124/134] Update Server.py Improve the errors information. --- PySpice/Spice/Xyce/Server.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/PySpice/Spice/Xyce/Server.py b/PySpice/Spice/Xyce/Server.py index 2c02e5d65..3a51eb4f1 100644 --- a/PySpice/Spice/Xyce/Server.py +++ b/PySpice/Spice/Xyce/Server.py @@ -77,7 +77,7 @@ def __init__(self, **kwargs): ############################################## - def _parse_stdout(self, stdout): + def _parse_stdout(self, stdout, spice_netlist): """Parse stdout for errors.""" @@ -100,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)) ############################################## @@ -122,8 +124,10 @@ def __call__(self, spice_input): 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))) @@ -135,7 +139,7 @@ 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() From 3061e8039c8851129a7e7d05d8b929a828a249a5 Mon Sep 17 00:00:00 2001 From: jmgc Date: Mon, 26 Jun 2023 08:18:29 +0200 Subject: [PATCH 125/134] Update BasicElement.py Improve the errors detection. --- PySpice/Spice/BasicElement.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/PySpice/Spice/BasicElement.py b/PySpice/Spice/BasicElement.py index 4e5efb7d5..04a27fde4 100644 --- a/PySpice/Spice/BasicElement.py +++ b/PySpice/Spice/BasicElement.py @@ -173,8 +173,12 @@ def __init__(self, netlist, name, subcircuit_name, *nodes, **kwargs): # 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)) From 875b65aa5add380f03ff0746576ffa3d9253a3c7 Mon Sep 17 00:00:00 2001 From: jmgc Date: Thu, 21 Sep 2023 10:47:33 +0200 Subject: [PATCH 126/134] Update tox.ini Updated to use python 311 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index ac9aefb53..6cc893196 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py310 +envlist = py311 [testenv] commands = pytest unit-test From 353fc6b528ea0d6b879674ba5b2b462448850c48 Mon Sep 17 00:00:00 2001 From: jmgc Date: Thu, 21 Sep 2023 10:48:03 +0200 Subject: [PATCH 127/134] Solved issues with the library parsing --- PySpice/Spice/EBNFSpiceParser.py | 258 ++++++++++++---------- PySpice/Spice/Library.py | 26 +-- PySpice/Spice/Netlist.py | 121 ++++++++-- unit-test/SpiceParser/test_SpiceParser.py | 51 ++++- 4 files changed, 293 insertions(+), 163 deletions(-) diff --git a/PySpice/Spice/EBNFSpiceParser.py b/PySpice/Spice/EBNFSpiceParser.py index 349f60994..856f353bb 100644 --- a/PySpice/Spice/EBNFSpiceParser.py +++ b/PySpice/Spice/EBNFSpiceParser.py @@ -1,8 +1,10 @@ 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 @@ -475,11 +477,12 @@ def __init__(self, name, *nodes, **params): self._params = params self._statements = [] - self._subcircuits = [] - self._models = [] - self._required_subcircuits = set() + self._subcircuits = OrderedDict() + self._models = OrderedDict() + self._required_subcircuits = OrderedDict() self._required_models = set() self._parameters = [] + self._parent = None ############################################## @@ -531,24 +534,26 @@ def __iter__(self): 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 statement's list. """ + """ Append a model to the models list. """ - self._models.append(statement) + self._models[statement._name] = statement def appendParam(self, statement): - """ Append a param to the statement's list. """ + """ Append a param to the parameters list. """ self._parameters.append(statement) def appendSubCircuit(self, statement): - """ Append a model to the statement's list. """ - - self._subcircuits.append(statement) + """ Append a subcircuit to the subcircuits list. """ + statement._parent = self + self._subcircuits[statement._name] = statement ############################################## @@ -563,23 +568,66 @@ def to_python(self, ground=0): ############################################## - def build(self, ground=0, parent=None): - subcircuit = SubCircuit(str(self._name).lower(), *self._nodes, **self._params) - subcircuit.parent = parent + def _build(self, ground, netlist): for statement in self._parameters: - statement.build(subcircuit) - for statement in self._models: - statement.build(subcircuit) - for statement in self._subcircuits: - subckt = statement.build(ground, parent=subcircuit) # Fixme: ok ??? - subcircuit.subcircuit(subckt) + 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(subcircuit, ground) - return subcircuit + 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(Statement): +class CircuitStatement(SubCircuitStatement): """ This class implements a circuit definition. Spice syntax:: @@ -591,6 +639,8 @@ class CircuitStatement(Statement): ############################################## def __init__(self, title, path): + super(CircuitStatement, self).__init__("") + if path is not None: self._path = str(path) else: @@ -599,14 +649,9 @@ def __init__(self, title, path): self._title = str(title) self._library_calls = [] - self._statements = [] self._libraries = {} - self._subcircuits = [] - self._models = [] - self._required_subcircuits = set() - self._required_models = set() - self._parameters = [] self._data = {} + self._library = None ############################################## @@ -667,12 +712,6 @@ def __iter__(self): ############################################## - def append(self, statement): - - """ Append a statement to the statement's list. """ - - self._statements.append(statement) - def appendData(self, statement): """ Append a model to the statement's list. """ @@ -691,24 +730,6 @@ def appendLibraryCall(self, statement): self._library_calls.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._parameters.append(statement) - - def appendSubCircuit(self, statement): - - """ Append a model to the statement's list. """ - - self._subcircuits.append(statement) - ############################################## def to_python(self, ground=0): @@ -721,21 +742,16 @@ def to_python(self, ground=0): ############################################## - def build(self, ground=0): + def build(self, ground=0, library=None): circuit = Circuit(self._title) - for statement in self._library_calls: - statement.build(circuit, self._libraries) - for statement in self._parameters: - statement.build(circuit) - for statement in self._models: - statement.build(circuit) - for statement in self._subcircuits: - subckt = statement.build(ground, parent=circuit) # Fixme: ok ??? - circuit.subcircuit(subckt) - for statement in self._statements: - if isinstance(statement, ElementStatement): - statement.build(circuit, ground) - return circuit + 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) #################################################################################################### @@ -1235,7 +1251,7 @@ def walk_Subcircuit(self, node, data): if node.parameters is not None: parameters = self.walk(node.parameters, data) kwargs.update(parameters) - data._present._required_subcircuits.add(subcircuit_name.lower()) + data._present._required_subcircuits[subcircuit_name.lower()] = None data._present.append( ElementStatement( SubCircuitElement, @@ -1497,8 +1513,15 @@ def walk_TransientPWL(self, node, data): if isinstance(data, list): parameters.update({"values": data}) else: - with open(data) as ifile: - ext = os.path.splitext(data)[1] + 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)) @@ -1639,9 +1662,23 @@ def walk_IncludeCmd(self, node, data): ) # The include statement makes available all the parameters, models and # subcircuits in the file. - data._present._parameters.extend(include._contents._parameters) - data._present._models.extend(include._contents._models) - data._present._subcircuits.extend(include._contents._subcircuits) + 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 @@ -1834,15 +1871,6 @@ class SpiceParser: def __init__(self): pass - @staticmethod - def _update_subcircuits(statement, library): - for sub in statement._required_subcircuits: - if sub not in statement._subcircuits and sub in library._subcircuits: - statement._subcircuits.append(library._subcircuits[sub]) - for mod in statement._required_models: - if mod not in statement._models and mod in library._models: - statement._models.append(library._models[mod]) - @staticmethod def parse(path=None, source=None, library=None): # Fixme: empty source @@ -1868,15 +1896,13 @@ def parse(path=None, source=None, library=None): data = ParsingData(path) circuit = SpiceParser._walker.walk(model, data) if library is not None: - SpiceParser._update_subcircuits(circuit, library) - for subcircuit in circuit._subcircuits: - SpiceParser._update_subcircuits(subcircuit, library) - + circuit._library = library try: SpiceParser._check_models(circuit) - SpiceParser._sort_subcircuits(circuit) + #SpiceParser._check_subcircuits(circuit) except Exception as e: - raise ParseError("{}: ".format(path) + str(e)) from e + tb = sys.exc_info()[2] + raise ParseError("{}: ".format(path) + str(e)).with_traceback(tb) return circuit @@ -1903,47 +1929,39 @@ def _regenerate(): @staticmethod def _check_models(circuit, available_models=set()): p_available_models = {model.lower() for model in available_models} - p_available_models.update([model.name.lower() for model in circuit._models]) - for subcircuit in circuit._subcircuits: + p_available_models.update([model.lower() for model in circuit._models]) + for name, subcircuit in circuit._subcircuits.items(): 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 = {subckt.lower() for subckt in available_subcircuits} - names = [subcircuit.name.lower() 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 + def _check_subcircuits(circuit): + p_available_subcircuits = dict(circuit._subcircuits) + library_available = circuit._library is not None + if library_available: + for name, subckt in circuit._library._subcircuits.items(): + if name not in p_available_subcircuits: + p_available_subcircuits[name] = subckt else: - result.append(subckt) - result_names.append(subckt.name.lower()) - 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) + raise NameError("Already used subcircuit name {} found in library {}".format( + name, + circuit._library.name)) + for subcircuit in circuit._subcircuits.values(): + required = subcircuit._revise_required_subcircuits(p_available_subcircuits) + for name, subckt in required.items(): + if name not in circuit._required_subcircuits: + circuit._required_subcircuits[name] = id(subckt) + for name, subcktid in circuit._required_subcircuits.items(): + if subcktid is None: + raise NameError("Unable to find subcircuit: {}".format(name)) + if name not in circuit._subcircuits: + if library_available: + if name in circuit._library._subcircuits: + circuit._subcircuits[name] = circuit._library._subcircuits[name] + continue + @property diff --git a/PySpice/Spice/Library.py b/PySpice/Spice/Library.py index 2ae25cc24..fbe78d984 100644 --- a/PySpice/Spice/Library.py +++ b/PySpice/Spice/Library.py @@ -23,6 +23,7 @@ import logging import re import sys +from collections import OrderedDict #################################################################################################### @@ -64,19 +65,10 @@ class SpiceLibrary: ) def _add_parsed(self, parsed): - for subcircuit in parsed.subcircuits: - self._subcircuits[str(subcircuit.name).lower()] = subcircuit - for model in parsed.models: - self._models[str(model.name).lower()] = model - - def _update_subcircuits(self): - for subcircuit in self._subcircuits.values(): - for sub in subcircuit._required_subcircuits: - if sub not in subcircuit._subcircuits and sub in self._subcircuits: - subcircuit._subcircuits.append(self._subcircuits[sub]) - for mod in subcircuit._required_models: - if mod not in subcircuit._models and mod in self._models: - subcircuit._models.append(self._models[mod]) + for name, subcircuit in parsed.subcircuits.items(): + self._subcircuits[name] = subcircuit + for name, model in parsed.models.items(): + self._models[name] = model ############################################## @@ -84,8 +76,8 @@ 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 @@ -101,8 +93,7 @@ def __init__(self, root_path=None, recurse=False, section=None): except Exception as e: tb = sys.exc_info()[2] # Parse problem with this file, so skip it and keep going. - raise RuntimeError("Problem parsing {path} - {e}\n{}".format(**locals())).with_traceback(tb) - self._update_subcircuits() + raise RuntimeError("Problem parsing {}".format(e)).with_traceback(tb) ############################################## @@ -163,4 +154,3 @@ def search(self, s): def insert(self, raw): parsed = SpiceParser.parse(source=raw) self._add_parsed(parsed) - self._update_subcircuits() diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index 7820bc9f6..ae0f45eb1 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -878,14 +878,16 @@ def __init__(self): self._elements = OrderedDict() # to keep the declaration order self._models = OrderedDict() self._includes = [] # .include - self._used_models = set() - self._used_subcircuits = set() + self._used_models = {} + self._used_subcircuits = {} self._parameters = OrderedDict() self.raw_spice = '' self._spice_sim = '' + self._parent = None + ############################################## def copy_to(self, netlist): @@ -1033,20 +1035,87 @@ 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.""" element_name = str(element.name).lower() if element_name not in self._elements: self._elements[element_name] = element if hasattr(element, 'model'): - model = element.model + model = str(element.model).lower() if model is not None: - self._used_models.add(model) + self._used_models[model] = None - if element.name[0] in "xX": + if isinstance(element, SubCircuitElement): subcircuit_name = str(element.subcircuit_name).lower() if subcircuit_name is not None: - self._used_subcircuits.add(subcircuit_name) + 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)) @@ -1083,14 +1152,10 @@ def model(self, name, model_type, **parameters): def subcircuit(self, subcircuit): """Add a sub-circuit.""" # Fixme: subcircuit is a class + assert isinstance(subcircuit, SubCircuit) self._subcircuits[str(subcircuit.name).lower()] = subcircuit - subcircuit.parent = self - for model in subcircuit._used_models: - if model not in subcircuit._models: - self._used_models.add(model) - for subckt in subcircuit._used_subcircuits: - if subckt not in subcircuit._subcircuits: - self._used_subcircuits.add(subckt) + subcircuit._parent = self + self._revise_required_models_subcircuits(self._subcircuits) ############################################## @@ -1165,14 +1230,25 @@ def include(self, library): else: spice_library = SpiceLibrary(library) models = spice_library.models - for model in models: - model_name = str(model).lower() - self.model(model_name, spice_library[model]._model_type, **spice_library[model]._parameters) + 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 in subcircuits: - subcircuit_def = spice_library[subcircuit].build(parent=self) + 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): @@ -1275,11 +1351,6 @@ def check_nodes(self): def __str__(self): netlist = self._str_raw_spice() - if self._subcircuits: - subcircuits = self._str_subcircuits() - netlist += subcircuits # before elements - netlist += os.linesep - """Return the formatted subcircuit definition.""" nodes = join_list(self._external_nodes) @@ -1297,6 +1368,10 @@ def __str__(self): 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 diff --git a/unit-test/SpiceParser/test_SpiceParser.py b/unit-test/SpiceParser/test_SpiceParser.py index 90b3081d7..bfffafb55 100644 --- a/unit-test/SpiceParser/test_SpiceParser.py +++ b/unit-test/SpiceParser/test_SpiceParser.py @@ -519,6 +519,54 @@ def test_library(self): if shutil.which('xyce'): simulator.transient(1e-9, 1e-3) + 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') @@ -680,13 +728,12 @@ def test_subcircuit(self): 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 -.model diode D (is=1.038e-15 n=1 tt=2e-08 cjo=5e-12 rs=0.5 bv=130) xhigh hoi hs hi vss source rhoi hoi ho 1ohm choi ho hs 1e-09 From 1746af21655b2bb9abcdf0055b3f5cd38be26496 Mon Sep 17 00:00:00 2001 From: jmgc Date: Sat, 23 Sep 2023 09:22:07 +0200 Subject: [PATCH 128/134] Update test_SpiceParser.py Added a new test to confirm that multiple elements are read. --- unit-test/SpiceParser/test_SpiceParser.py | 286 ++++++++++++++++++++++ 1 file changed, 286 insertions(+) diff --git a/unit-test/SpiceParser/test_SpiceParser.py b/unit-test/SpiceParser/test_SpiceParser.py index bfffafb55..64d8c3e8c 100644 --- a/unit-test/SpiceParser/test_SpiceParser.py +++ b/unit-test/SpiceParser/test_SpiceParser.py @@ -519,6 +519,292 @@ def test_library(self): 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(""" + + * INA901-SP + ***************************************************************************** + * (C) Copyright 2011 Texas Instruments Incorporated. All rights reserved. + ***************************************************************************** + ** This model is designed as an aid for customers of Texas Instruments. + ** TI and its licensors and suppliers make no warranties, either expressed + ** or implied, with respect to this model, including the warranties of + ** merchantability or fitness for a particular purpose. The model is + ** provided solely on an "as is" basis. The entire risk as to its quality + ** and performance is with the customer. + ***************************************************************************** + * + * This model is subject to change without notice. Texas Instruments + * Incorporated is not responsible for updating this model. + * + ***************************************************************************** + * + ** Released by: Analog eLab Design Center, Texas Instruments Inc. + * Part: INA901-SP + * Date: 27SEP2018 + * Model Type: ALL IN ONE + * Simulator: PSPICE + * Simulator Version: 16.2.0.p001 + * EVM Order Number: N/A + * EVM Users Guide: N/A + * Datasheet: SBOS938 OCTOBER 2018 + * + * Model Version: 1.0 + * + ***************************************************************************** + * + * Updates: + * + * Version 1.0 : + * Release to Web + * + ***************************************************************************** + * BEGIN MODEL INA901-SP + * MODEL FEATURES INCLUDE INPUT FULL SCALE RANGE, + * COMMON MODE VOLTAGE RANGE, COMMON MODE REJECTION + * WITH FREQUENCY EFFECTS, POWER SUPPLY REJECTION + * WITH FREQUENCY EFFECTS, OFFSET VOLTAGE WITH + * TEMPCO, INPUT BIAS CURRENT, BUFFER BIAS CURRENT + * WITH TEMPCO, INPUT VOLTAGE NOISE, GAIN AND GAIN + * ERROR, GAIN AND PHASE VERSUS FREQUENCY, CLOAD + * EFFECTS, PRE OUT OUTPUT RESISTANCE, ALL FIVE + * OUTPUT ERROR MODES, OUTPUT SWING, OUTPUT CURRENT + * LIMIT, OUTPUT CURRENT FLOWING THROUGH THE SUPPLY + * RAILS, SLEW RATE, AND SETTLING TIME. + * PINOUT ORDER IN- GND PREOUT BUFIN OUT V+ IN+ + * PINOUT ORDER 1 2 3 4 5 6 8 + .SUBCKT INA901-SP 1 2 3 4 5 6 8 + R42 2 1 1E12 + R43 9 1 2.5E3 + R44 9 8 2.5E3 + E5 10 0 1 0 1 + E6 11 0 8 0 1 + R47 12 10 1E3 + R48 12 11 1E3 + E7 9 2 12 0 1 + E8 13 0 14 0 10 + R49 0 15 1E6 + E9 16 0 15 0 1.0033 + R50 0 17 1E12 + E10 18 0 8 1 1 + E11 19 0 20 9 1000 + R53 0 19 1E12 + E12 21 0 9 22 100 + R54 0 21 1E12 + E13 23 0 14 24 1000 + R55 0 23 1E12 + V17 24 2 0.02 + M1 25 23 0 0 MNL + R56 25 26 1E5 + M2 25 19 0 0 MNL + M3 25 21 0 0 MNL + V18 26 0 5 + M5 27 19 0 0 MNL + R58 27 26 1E5 + M6 27 21 0 0 MNL + R59 15 13 5E3 + D1 28 15 DD + E14 29 0 25 0 0.04 + V19 20 2 0.03 + E15 30 0 31 32 0.5 + R60 0 30 1E12 + V20 31 0 0.3 + E16 32 0 14 0 15 + R61 0 32 1E12 + E17 33 0 30 34 1000 + D2 33 34 DD + R62 34 33 1E11 + R63 0 34 1E3 + E18 17 16 35 0 1 + R64 0 16 1E12 + R66 35 34 1E6 + M8 35 36 0 0 MNL + R67 2 37 36E3 + M9 38 27 0 0 MNL + R68 38 26 1E5 + M10 38 23 0 0 MNL + M12 36 38 0 0 MNL + R70 36 26 1E5 + R73 0 29 1E12 + E20 28 0 29 15 300 + R74 0 28 1E12 + D3 39 14 DD + E22 39 0 40 14 500 + R76 0 39 1E12 + V21 40 0 0.001 + R77 14 18 5E3 + R78 3 41 96E3 + R79 5 37 36E3 + E23 41 2 17 42 1 + Q21 43 44 45 QLN + R82 46 47 2 + R83 48 47 2 + R84 44 49 100 + R85 50 51 100 + R86 51 6 20 + R87 2 49 20 + R88 52 53 2E3 + R89 54 22 20 + R90 45 55 20 + D6 5 6 DX + D7 2 5 DX + D8 56 0 DIN + D9 57 0 DIN + I9 0 56 0.1E-3 + I10 0 57 0.1E-3 + E24 45 0 2 0 1 + E25 22 0 6 0 1 + D10 58 0 DVN + D11 59 0 DVN + I11 0 58 0.4E-6 + I12 0 59 0.4E-6 + E26 60 37 58 59 1.03 + G3 61 37 56 57 2.9E-7 + I13 6 2 490E-6 + R91 2 6 1E6 + E27 62 0 22 0 1 + E28 63 0 45 0 1 + E29 64 0 12 0 1 + R92 62 65 1E6 + R93 63 66 1E6 + R94 64 67 1E6 + R95 0 65 100 + R96 0 66 100 + R97 0 67 100 + E30 68 4 67 0 0.005 + R98 69 70 1E3 + R99 70 71 1E3 + C7 62 65 0.2E-12 + C8 63 66 0.2E-12 + C9 64 67 300E-12 + E31 72 68 66 0 0.001 + E32 73 72 65 0 0.001 + E33 74 45 22 45 0.5 + D12 52 22 DX + D13 45 52 DX + M13 75 76 49 49 NOUT L=3U W=200U + M14 77 78 51 51 POUT L=3U W=200U + M15 79 79 54 54 POUT L=3U W=200U + M16 80 81 46 46 PIN L=3U W=21U + M17 82 60 48 48 PIN L=3U W=21U + M18 83 83 55 55 NOUT L=3U W=200U + R100 84 78 100 + R101 85 76 100 + G4 52 74 86 74 0.2E-3 + R102 74 52 6E6 + C13 53 5 15E-12 + R103 45 80 1.7E3 + R104 45 82 1.7E3 + C14 80 82 6.5E-12 + C15 61 0 1E-12 + C16 60 0 1E-12 + C17 5 0 1E-12 + D14 76 43 DX + D15 87 78 DX + Q22 87 50 22 QLP + V24 61 81 -0.002 + M19 88 89 90 90 NIN L=3U W=21U + R105 91 90 2 + M20 92 60 93 93 NIN L=3U W=21U + R106 91 93 2 + R107 88 22 1.7E3 + R108 92 22 1.7E3 + C21 88 92 6.5E-12 + V25 81 89 0 + M21 94 95 96 96 PIN L=6U W=500U + M22 97 98 22 22 PIN L=6U W=500U + V26 22 95 1.9 + M23 91 94 45 45 NIN L=6U W=500U + M24 94 94 45 45 NIN L=6U W=500U + G7 52 74 99 74 0.2E-3 + I15 79 83 80E-6 + E34 71 0 61 0 1 + E35 69 0 37 0 1 + M25 98 98 22 22 PIN L=6U W=500U + I16 98 45 45E-6 + V27 97 47 0 + R109 5 77 50 + R110 75 5 50 + J2 22 61 22 NJ + J3 22 60 22 NJ + J4 60 45 60 NJ + J5 61 45 61 NJ + C22 61 60 0.1E-12 + E36 100 74 92 88 1 + R111 100 99 1E4 + C23 99 74 9E-12 + E37 101 74 82 80 1 + R112 101 86 1E4 + C24 86 74 9E-12 + G8 102 74 52 74 -1E-3 + G9 74 103 52 74 1E-3 + G10 74 104 83 45 1E-3 + G11 105 74 22 79 1E-3 + D18 105 102 DX + D19 103 104 DX + R113 102 105 100E6 + R114 104 103 100E6 + R115 105 22 1E3 + R116 45 104 1E3 + E38 22 84 22 105 1 + E39 85 45 104 45 1 + R117 103 74 1E6 + R118 104 74 1E6 + R119 74 105 1E6 + R120 74 102 1E6 + R121 4 68 1E9 + R122 68 72 1E9 + R123 72 73 1E9 + R124 37 60 1E9 + R125 84 22 1E9 + R126 45 85 1E9 + R127 74 86 1E9 + R128 74 99 1E9 + R129 70 0 1E9 + I21 60 0 1E-12 + R130 96 97 6.5E3 + C25 3 2 1P + R131 52 5 6E8 + E41 42 0 106 0 4.19 + R132 0 106 1E6 + R133 0 106 1E6 + R134 0 42 1E12 + R136 0 20 1E12 + R137 0 24 1E12 + R138 31 0 1E12 + R139 0 40 1E12 + I22 0 107 1E-3 + D20 107 0 DX + R140 0 107 1E9 + V28 107 108 0.6551 + R141 0 108 1E9 + R142 0 108 1E9 + G12 61 0 108 0 1.68E-8 + E42 61 73 108 0 7.01E-3 + R143 73 61 1E9 + I23 61 0 50E-9 + .MODEL MNL NMOS KP=200U VTO=0.7 IS=1E-18 + .MODEL DVN D KF=1.6E-16 IS=1E-16 + .MODEL DIN D + .MODEL DX D + .MODEL DD D + .MODEL NJ NJF + .MODEL QLP PNP + .MODEL QLN NPN + .MODEL POUT PMOS KP=200U VTO=-0.7 LAMBDA=0.01 + .MODEL NOUT NMOS KP=200U VTO=0.7 LAMBDA=0.01 + .MODEL PIN PMOS KP=200U VTO=-0.7 + .MODEL NIN NMOS KP=200U VTO=0.7 + .ENDS + * END MODEL INA901-SP + + + .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 ) + """) def test_library_str(self): from PySpice.Spice.Library import SpiceLibrary libraries_path = self._getdir('unit-test/SpiceParser') From 459a6397f9350feead06b615beddee55abd49706 Mon Sep 17 00:00:00 2001 From: jmgc Date: Sat, 23 Sep 2023 09:22:39 +0200 Subject: [PATCH 129/134] Update RawFile.py Added a check to avoid loosing files data. --- PySpice/Spice/RawFile.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/PySpice/Spice/RawFile.py b/PySpice/Spice/RawFile.py index b16053f2e..afcd7759f 100644 --- a/PySpice/Spice/RawFile.py +++ b/PySpice/Spice/RawFile.py @@ -297,6 +297,9 @@ def _read_variable_data(self, raw_data): raise NotImplementedError 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) From 7fd945450c83a9706fe149abf3cf70d0700159d6 Mon Sep 17 00:00:00 2001 From: jmgc Date: Sat, 23 Sep 2023 17:09:26 +0200 Subject: [PATCH 130/134] Solve an issue when checking models Correction of the method an associated test. --- PySpice/Spice/EBNFSpiceParser.py | 34 ++++------------------- unit-test/SpiceParser/test_SpiceParser.py | 10 +++++++ 2 files changed, 15 insertions(+), 29 deletions(-) diff --git a/PySpice/Spice/EBNFSpiceParser.py b/PySpice/Spice/EBNFSpiceParser.py index 856f353bb..2815d5065 100644 --- a/PySpice/Spice/EBNFSpiceParser.py +++ b/PySpice/Spice/EBNFSpiceParser.py @@ -1899,7 +1899,6 @@ def parse(path=None, source=None, library=None): circuit._library = library try: SpiceParser._check_models(circuit) - #SpiceParser._check_subcircuits(circuit) except Exception as e: tb = sys.exc_info()[2] raise ParseError("{}: ".format(path) + str(e)).with_traceback(tb) @@ -1933,37 +1932,14 @@ def _check_models(circuit, available_models=set()): for name, subcircuit in circuit._subcircuits.items(): SpiceParser._check_models(subcircuit, p_available_models) for model in circuit._required_models: + if model not in p_available_models: + if hasattr(circuit, "_library"): + if model in circuit._library._models: + circuit._models[model] = circuit._library._models[model] + p_available_models.add(model) if model not in p_available_models: raise ValueError("Model (%s) not available in (%s)" % (model, circuit.name)) - @staticmethod - def _check_subcircuits(circuit): - p_available_subcircuits = dict(circuit._subcircuits) - library_available = circuit._library is not None - if library_available: - for name, subckt in circuit._library._subcircuits.items(): - if name not in p_available_subcircuits: - p_available_subcircuits[name] = subckt - else: - raise NameError("Already used subcircuit name {} found in library {}".format( - name, - circuit._library.name)) - for subcircuit in circuit._subcircuits.values(): - required = subcircuit._revise_required_subcircuits(p_available_subcircuits) - for name, subckt in required.items(): - if name not in circuit._required_subcircuits: - circuit._required_subcircuits[name] = id(subckt) - for name, subcktid in circuit._required_subcircuits.items(): - if subcktid is None: - raise NameError("Unable to find subcircuit: {}".format(name)) - if name not in circuit._subcircuits: - if library_available: - if name in circuit._library._subcircuits: - circuit._subcircuits[name] = circuit._library._subcircuits[name] - continue - - - @property def circuit(self): """ Circuit statements. """ diff --git a/unit-test/SpiceParser/test_SpiceParser.py b/unit-test/SpiceParser/test_SpiceParser.py index 64d8c3e8c..cf3351b3a 100644 --- a/unit-test/SpiceParser/test_SpiceParser.py +++ b/unit-test/SpiceParser/test_SpiceParser.py @@ -805,6 +805,16 @@ def test_library_direct(self): .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) + + SpiceParser.parse(source=""" +XU5A np nn ncc nss nsee ADA4077 +Dup no ncc DI_1N5819 +Ddown 0 no DI_1N5819 +""", library=library) + circuit = Circuit("test") + def test_library_str(self): from PySpice.Spice.Library import SpiceLibrary libraries_path = self._getdir('unit-test/SpiceParser') From 9f42287b10f612c20e174295e6656d91d2d8d06f Mon Sep 17 00:00:00 2001 From: jmgc Date: Sat, 23 Sep 2023 17:16:33 +0200 Subject: [PATCH 131/134] Removed check_models as it is no longer needed --- PySpice/Spice/EBNFSpiceParser.py | 20 -- unit-test/SpiceParser/test_SpiceParser.py | 398 +++++++--------------- 2 files changed, 116 insertions(+), 302 deletions(-) diff --git a/PySpice/Spice/EBNFSpiceParser.py b/PySpice/Spice/EBNFSpiceParser.py index 2815d5065..d312e3a79 100644 --- a/PySpice/Spice/EBNFSpiceParser.py +++ b/PySpice/Spice/EBNFSpiceParser.py @@ -1897,11 +1897,6 @@ def parse(path=None, source=None, library=None): circuit = SpiceParser._walker.walk(model, data) if library is not None: circuit._library = library - try: - SpiceParser._check_models(circuit) - except Exception as e: - tb = sys.exc_info()[2] - raise ParseError("{}: ".format(path) + str(e)).with_traceback(tb) return circuit @@ -1925,21 +1920,6 @@ def _regenerate(): with open(model_file, 'w') as model_ofile: model_ofile.write(python_model) - @staticmethod - def _check_models(circuit, available_models=set()): - p_available_models = {model.lower() for model in available_models} - p_available_models.update([model.lower() for model in circuit._models]) - for name, subcircuit in circuit._subcircuits.items(): - SpiceParser._check_models(subcircuit, p_available_models) - for model in circuit._required_models: - if model not in p_available_models: - if hasattr(circuit, "_library"): - if model in circuit._library._models: - circuit._models[model] = circuit._library._models[model] - p_available_models.add(model) - if model not in p_available_models: - raise ValueError("Model (%s) not available in (%s)" % (model, circuit.name)) - @property def circuit(self): """ Circuit statements. """ diff --git a/unit-test/SpiceParser/test_SpiceParser.py b/unit-test/SpiceParser/test_SpiceParser.py index cf3351b3a..263ddbfd4 100644 --- a/unit-test/SpiceParser/test_SpiceParser.py +++ b/unit-test/SpiceParser/test_SpiceParser.py @@ -524,296 +524,130 @@ def test_library_direct(self): 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 +*$ - * INA901-SP - ***************************************************************************** - * (C) Copyright 2011 Texas Instruments Incorporated. All rights reserved. - ***************************************************************************** - ** This model is designed as an aid for customers of Texas Instruments. - ** TI and its licensors and suppliers make no warranties, either expressed - ** or implied, with respect to this model, including the warranties of - ** merchantability or fitness for a particular purpose. The model is - ** provided solely on an "as is" basis. The entire risk as to its quality - ** and performance is with the customer. - ***************************************************************************** - * - * This model is subject to change without notice. Texas Instruments - * Incorporated is not responsible for updating this model. - * - ***************************************************************************** - * - ** Released by: Analog eLab Design Center, Texas Instruments Inc. - * Part: INA901-SP - * Date: 27SEP2018 - * Model Type: ALL IN ONE - * Simulator: PSPICE - * Simulator Version: 16.2.0.p001 - * EVM Order Number: N/A - * EVM Users Guide: N/A - * Datasheet: SBOS938 OCTOBER 2018 - * - * Model Version: 1.0 - * - ***************************************************************************** - * - * Updates: - * - * Version 1.0 : - * Release to Web - * - ***************************************************************************** - * BEGIN MODEL INA901-SP - * MODEL FEATURES INCLUDE INPUT FULL SCALE RANGE, - * COMMON MODE VOLTAGE RANGE, COMMON MODE REJECTION - * WITH FREQUENCY EFFECTS, POWER SUPPLY REJECTION - * WITH FREQUENCY EFFECTS, OFFSET VOLTAGE WITH - * TEMPCO, INPUT BIAS CURRENT, BUFFER BIAS CURRENT - * WITH TEMPCO, INPUT VOLTAGE NOISE, GAIN AND GAIN - * ERROR, GAIN AND PHASE VERSUS FREQUENCY, CLOAD - * EFFECTS, PRE OUT OUTPUT RESISTANCE, ALL FIVE - * OUTPUT ERROR MODES, OUTPUT SWING, OUTPUT CURRENT - * LIMIT, OUTPUT CURRENT FLOWING THROUGH THE SUPPLY - * RAILS, SLEW RATE, AND SETTLING TIME. - * PINOUT ORDER IN- GND PREOUT BUFIN OUT V+ IN+ - * PINOUT ORDER 1 2 3 4 5 6 8 - .SUBCKT INA901-SP 1 2 3 4 5 6 8 - R42 2 1 1E12 - R43 9 1 2.5E3 - R44 9 8 2.5E3 - E5 10 0 1 0 1 - E6 11 0 8 0 1 - R47 12 10 1E3 - R48 12 11 1E3 - E7 9 2 12 0 1 - E8 13 0 14 0 10 - R49 0 15 1E6 - E9 16 0 15 0 1.0033 - R50 0 17 1E12 - E10 18 0 8 1 1 - E11 19 0 20 9 1000 - R53 0 19 1E12 - E12 21 0 9 22 100 - R54 0 21 1E12 - E13 23 0 14 24 1000 - R55 0 23 1E12 - V17 24 2 0.02 - M1 25 23 0 0 MNL - R56 25 26 1E5 - M2 25 19 0 0 MNL - M3 25 21 0 0 MNL - V18 26 0 5 - M5 27 19 0 0 MNL - R58 27 26 1E5 - M6 27 21 0 0 MNL - R59 15 13 5E3 - D1 28 15 DD - E14 29 0 25 0 0.04 - V19 20 2 0.03 - E15 30 0 31 32 0.5 - R60 0 30 1E12 - V20 31 0 0.3 - E16 32 0 14 0 15 - R61 0 32 1E12 - E17 33 0 30 34 1000 - D2 33 34 DD - R62 34 33 1E11 - R63 0 34 1E3 - E18 17 16 35 0 1 - R64 0 16 1E12 - R66 35 34 1E6 - M8 35 36 0 0 MNL - R67 2 37 36E3 - M9 38 27 0 0 MNL - R68 38 26 1E5 - M10 38 23 0 0 MNL - M12 36 38 0 0 MNL - R70 36 26 1E5 - R73 0 29 1E12 - E20 28 0 29 15 300 - R74 0 28 1E12 - D3 39 14 DD - E22 39 0 40 14 500 - R76 0 39 1E12 - V21 40 0 0.001 - R77 14 18 5E3 - R78 3 41 96E3 - R79 5 37 36E3 - E23 41 2 17 42 1 - Q21 43 44 45 QLN - R82 46 47 2 - R83 48 47 2 - R84 44 49 100 - R85 50 51 100 - R86 51 6 20 - R87 2 49 20 - R88 52 53 2E3 - R89 54 22 20 - R90 45 55 20 - D6 5 6 DX - D7 2 5 DX - D8 56 0 DIN - D9 57 0 DIN - I9 0 56 0.1E-3 - I10 0 57 0.1E-3 - E24 45 0 2 0 1 - E25 22 0 6 0 1 - D10 58 0 DVN - D11 59 0 DVN - I11 0 58 0.4E-6 - I12 0 59 0.4E-6 - E26 60 37 58 59 1.03 - G3 61 37 56 57 2.9E-7 - I13 6 2 490E-6 - R91 2 6 1E6 - E27 62 0 22 0 1 - E28 63 0 45 0 1 - E29 64 0 12 0 1 - R92 62 65 1E6 - R93 63 66 1E6 - R94 64 67 1E6 - R95 0 65 100 - R96 0 66 100 - R97 0 67 100 - E30 68 4 67 0 0.005 - R98 69 70 1E3 - R99 70 71 1E3 - C7 62 65 0.2E-12 - C8 63 66 0.2E-12 - C9 64 67 300E-12 - E31 72 68 66 0 0.001 - E32 73 72 65 0 0.001 - E33 74 45 22 45 0.5 - D12 52 22 DX - D13 45 52 DX - M13 75 76 49 49 NOUT L=3U W=200U - M14 77 78 51 51 POUT L=3U W=200U - M15 79 79 54 54 POUT L=3U W=200U - M16 80 81 46 46 PIN L=3U W=21U - M17 82 60 48 48 PIN L=3U W=21U - M18 83 83 55 55 NOUT L=3U W=200U - R100 84 78 100 - R101 85 76 100 - G4 52 74 86 74 0.2E-3 - R102 74 52 6E6 - C13 53 5 15E-12 - R103 45 80 1.7E3 - R104 45 82 1.7E3 - C14 80 82 6.5E-12 - C15 61 0 1E-12 - C16 60 0 1E-12 - C17 5 0 1E-12 - D14 76 43 DX - D15 87 78 DX - Q22 87 50 22 QLP - V24 61 81 -0.002 - M19 88 89 90 90 NIN L=3U W=21U - R105 91 90 2 - M20 92 60 93 93 NIN L=3U W=21U - R106 91 93 2 - R107 88 22 1.7E3 - R108 92 22 1.7E3 - C21 88 92 6.5E-12 - V25 81 89 0 - M21 94 95 96 96 PIN L=6U W=500U - M22 97 98 22 22 PIN L=6U W=500U - V26 22 95 1.9 - M23 91 94 45 45 NIN L=6U W=500U - M24 94 94 45 45 NIN L=6U W=500U - G7 52 74 99 74 0.2E-3 - I15 79 83 80E-6 - E34 71 0 61 0 1 - E35 69 0 37 0 1 - M25 98 98 22 22 PIN L=6U W=500U - I16 98 45 45E-6 - V27 97 47 0 - R109 5 77 50 - R110 75 5 50 - J2 22 61 22 NJ - J3 22 60 22 NJ - J4 60 45 60 NJ - J5 61 45 61 NJ - C22 61 60 0.1E-12 - E36 100 74 92 88 1 - R111 100 99 1E4 - C23 99 74 9E-12 - E37 101 74 82 80 1 - R112 101 86 1E4 - C24 86 74 9E-12 - G8 102 74 52 74 -1E-3 - G9 74 103 52 74 1E-3 - G10 74 104 83 45 1E-3 - G11 105 74 22 79 1E-3 - D18 105 102 DX - D19 103 104 DX - R113 102 105 100E6 - R114 104 103 100E6 - R115 105 22 1E3 - R116 45 104 1E3 - E38 22 84 22 105 1 - E39 85 45 104 45 1 - R117 103 74 1E6 - R118 104 74 1E6 - R119 74 105 1E6 - R120 74 102 1E6 - R121 4 68 1E9 - R122 68 72 1E9 - R123 72 73 1E9 - R124 37 60 1E9 - R125 84 22 1E9 - R126 45 85 1E9 - R127 74 86 1E9 - R128 74 99 1E9 - R129 70 0 1E9 - I21 60 0 1E-12 - R130 96 97 6.5E3 - C25 3 2 1P - R131 52 5 6E8 - E41 42 0 106 0 4.19 - R132 0 106 1E6 - R133 0 106 1E6 - R134 0 42 1E12 - R136 0 20 1E12 - R137 0 24 1E12 - R138 31 0 1E12 - R139 0 40 1E12 - I22 0 107 1E-3 - D20 107 0 DX - R140 0 107 1E9 - V28 107 108 0.6551 - R141 0 108 1E9 - R142 0 108 1E9 - G12 61 0 108 0 1.68E-8 - E42 61 73 108 0 7.01E-3 - R143 73 61 1E9 - I23 61 0 50E-9 - .MODEL MNL NMOS KP=200U VTO=0.7 IS=1E-18 - .MODEL DVN D KF=1.6E-16 IS=1E-16 - .MODEL DIN D - .MODEL DX D - .MODEL DD D - .MODEL NJ NJF - .MODEL QLP PNP - .MODEL QLN NPN - .MODEL POUT PMOS KP=200U VTO=-0.7 LAMBDA=0.01 - .MODEL NOUT NMOS KP=200U VTO=0.7 LAMBDA=0.01 - .MODEL PIN PMOS KP=200U VTO=-0.7 - .MODEL NIN NMOS KP=200U VTO=0.7 - .ENDS - * END MODEL INA901-SP - - - .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 ) - """) +.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) - SpiceParser.parse(source=""" + netlist = SpiceParser.parse(source=""" XU5A np nn ncc nss nsee ADA4077 Dup no ncc DI_1N5819 Ddown 0 no DI_1N5819 """, library=library) - circuit = Circuit("test") + circuit = netlist.build() def test_library_str(self): from PySpice.Spice.Library import SpiceLibrary From ebcba23ac5d215fb1655cc5bff5a2903bbf35902 Mon Sep 17 00:00:00 2001 From: jmgc Date: Wed, 25 Oct 2023 11:32:28 +0200 Subject: [PATCH 132/134] Update Expressions.py Improve docs. --- PySpice/Spice/Expressions.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/PySpice/Spice/Expressions.py b/PySpice/Spice/Expressions.py index 802a57c56..f2b4b1dfd 100644 --- a/PySpice/Spice/Expressions.py +++ b/PySpice/Spice/Expressions.py @@ -5,6 +5,8 @@ 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))) @@ -76,6 +78,18 @@ def __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): From be32047cb733ea3c79b8e98ef6a3e7e03304276d Mon Sep 17 00:00:00 2001 From: jmgc Date: Mon, 11 Nov 2024 11:52:01 +0100 Subject: [PATCH 133/134] Update setup.cfg Added tatsu requirement and update python version. --- setup.cfg | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 From 5f0cc060a57ceb1b61a7b3e592af71600c375093 Mon Sep 17 00:00:00 2001 From: jmgc Date: Wed, 13 Nov 2024 14:39:16 +0100 Subject: [PATCH 134/134] Update __init__.py Solve issue with raw streams. --- PySpice/Math/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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)