PySpice-org / PySpice

Simulate electronic circuit using Python and the Ngspice / Xyce simulators
https://pyspice.fabrice-salvaire.fr
GNU General Public License v3.0
659 stars 174 forks source link

Accessing internal device parameters #51

Closed eps82 closed 6 years ago

eps82 commented 7 years ago

Hello, I've been toying around with ngspice calls and tried to add custom .save lines to the netlists. Apparently the simulation runs, but the callbacks generate stuff PySpice can't deal with. I've seen the API of ngspice as a DLL is reasonably well documented so I'll definitely go through that too, but I'd like to know if there was something specific to PySpice regarding how it processes the returned data.

In particular I have a transistor M1 and I try to save M1[VTH] with an operating point analysis, This happens to generate a "nodes" entry with the corresponding name @m1[vth], but the data is empty. Wondering if I was getting the syntax wrong I tried M1[ID] and this time I found the drain current ID had been stored as "branches" current, although for some reason under an empty string ''. Other parameters don't seem to work.

So it seems to me PySpice only expects currents or voltages, and I was looking for advice on what I should look into first. Oh, and also if I'm reinventing the wheel and there's already a proper way of saving parameters other than temperature etc, of course (I just didn't find anything exploring the source)

Thanks!

eps82 commented 7 years ago

I've been doing some more tinkering and I found the causes for my (multiple) problems, mostly due to silent errors:

I think now I can keep customizing the code but any comments on this will be welcome :)

FabriceSalvaire commented 7 years ago

The code implements an automatic conversion from simulation output to waveform object.

  1. There is some hacks to fix Ngspice case/naming folk
  2. Code is probably not complete (type of simulation and variable)

I never tried to get such variables. Could you post some code ?

eps82 commented 7 years ago

Thanks for the reply. I'd been tracking NgSpiceShared.plot() because it was were most of the magic was happening. Basically I just created my own method to insert .SAVE statements such as

M1 yaddah yaddah .DC Vsomething VSTART VEND VSTEP .SAVE DC @M1[VTH]

Currently, without any extra tricks, this is identified as a voltage and stored as if it was a node, so it can be read using the corresponding label @m1[vth]. I've noticed ngspice outputs have unit identifiers so VTH (a voltage) is saved as if it was a node; same for M1[ID] for example as a current. Saving a capacitance such as CGG, however, would make it crash, because the unit type (21 for capacitances) is not defined in PySpice.

My solution at the moment is to try to intercept all outputs and store them in a separate dictionary regardless of the unit, then drop them if their units are not properly handled in pyspice before passing them on to the rest of the code (so currents and voltages will still be recorded as nodes/branches too). I'm not concerned about having some extra entries in those dictionaries (for example when VTH is also stored as a node voltage) because it won't cause me any trouble (unless I wanted to just go through all of them, but I'm not interested for now).

It's still a bit hard for me to track the flow at times because I'm not familiar with the class hierarchy but I think implementing this in a cleaner manner (my coding elegance is the equivalent of opening a can with an axe at this point) should be pretty straightforward given how easily I'm getting some results. I could post some code once I remove all the test points if you're interested, or maybe we can share impressions if you already have your own.

From what I've seen, doing it properly would take modifying is_voltage_node()/is_branch_current to make it smarter (so it doesn't just check units but whether the names are actual nodes/branches), and then expand the unit definitions. Also I'd check SIMULATION_TYPE because the enumeration I'm looking at doesn't add up (voltage is indeed code 3, but ngspice returns capacitance and charge as 21 and 22, yet they are 19 and 20 in the enumeration in NgSpiceShared)

FabriceSalvaire commented 7 years ago

If I am right @ variable require an explicit call to save, thus I have to enhance the dc simualtion code.

Yes post your code or things to be fixed (bullet list) Ngspice manual is impressive, so things are implemented incrementally

eps82 commented 7 years ago

Sure, as soon as I know what I'm doing I'll post something more concise.

And no need for excuses, really! It's a very nice piece of work as it is and I'm sure many including myself are very happy it exists. Front-ends always take time and effort.

FabriceSalvaire commented 7 years ago

Basic examples are also welcome, for example advanced dc simulation to show to newcomers what they can do with spice

eps82 commented 7 years ago

I could post some diffs but instead I'll just post directly here (all code comments are mine):

First in Spice/Simulation.py I made the following changes to allow raw spice code:

class CircuitSimulation:
  def __init__():
    ...
    self._raw = [] # List of strings to be inserted in the netlist
    ...

  def raw(self, r):  # New
    self._raw.append(str_spice(r))

  def __str__(self):
    ...
    for raw in self._raw:  # Insert raw Spice code before closing netlist
      netlist += raw + os.linesep
    netlist += '.end' + os.linesep
    # It might save some time if it was a list, returning os.linesep.join(line_list) instead
    return netlist 

Then in Spice/NgSpice/Shared.py I did the following:

class Plot(dict):
    ...
   def to_analysis(self):
        # Don't return immediately, save as 'analysis' for now instead
        if self.plot_name.startswith('op'):
            analysis = self._to_operating_point_analysis()
        elif self.plot_name.startswith('sens'):
            analysis = self._to_sensitivity_analysis()
        elif self.plot_name.startswith('dc'):
            analysis = self._to_dc_analysis()
        elif self.plot_name.startswith('ac'):
            analysis = self._to_ac_analysis()
        elif self.plot_name.startswith('tran'):
            analysis = self._to_transient_analysis()
        else:
            raise NotImplementedError("Unsupported plot name {}".format(self.plot_name))
        # Attach raw data before returning
        analysis.raw = {key : self[key].data for key in self}
        return analysis

class NgSpiceShared:

    _logger = _module_logger.getChild('NgSpiceShared')

    SIMULATION_TYPE = EnumFactory('SimulationType', ( 
        # Fixed from include/ngspice/sim.h
        # 2 units were inserted, explaining why capacitance was breaking it
        'no_type',
        'time',
        'frequency',
        'voltage',
        'current',
        'voltage_density',  # inserted
        'current_density',  # inserted. 
        'sqr_voltage_density',  # changed from input/output noise/density etc
        'sqr_current_density',  # changed
        'sqr_voltage',  # changed
        'sqr_current',  # changed
        'pole',
        'zero',
        's_parameter',  # maybe should be sparam? I don't wanna break anything
        'temperature',  # same with temp
        'res',
        'impedance',
        'admittance',
        'power',
        'phase',
        'db',
        'capacitance',
        'charge'))

This is enough to allow me to do things like:

simulator.raw('.save dc @m1[cgg]')
analysis = simulator.dc(VG=slice(0, 1, 0.01)) # some generator of Vgs somewhere
cgg_data = analysis.raw['@m1[cgg]']  
# actually I made some functions to avoid using @m1[] every time, but just for the idea

I haven't tried nested sweeps or anything like that yet so I'm not sure how this would work (or if), but I think this would be enough for now for what I was trying to do. Also note I'm not filtering nodes/branches so voltages and currents will be stored there too.

I think in general it would be good to allow "low level channels" for data through the class stack. I imagine the kind of person who wants to script spice already has a very powerful reason to do so, so they have in general lower requirements when it comes to functionality. Having full access to both netlisting and data retrieval as a clean/simplified ngspice wrapper in a slightly less barbaric way than what I propose above would be attractive to such users (they're likely to write their own ad-hoc netlisters too), while the higher level tools would still make it usable to a wider public but without discouraging the first group when something hasn't been fully implemented (since they won't really need it even if they'd welcome it). But of course I was the one looking for a wrapper in the first place so I may be -cough- biased.

FabriceSalvaire commented 7 years ago

Reference

Output parameters are those additional parameters that are available for many types of instances for the output of operating point and debugging information. These parameters are specified as @device[keyword] and are available for the most recent point computed or, if specified in a .save statement, for an entire simulation as a normal output vector. Thus, to monitor the gate-to-source capacitance of a MOSFET, a command

save @m1[cgs]

given before a transient simulation causes the specified capacitance value to be saved at each time-point.

General form:

.save all <@...>

@device_identifier.subcircuit_name.< subcircuit_name_nn >
+.device_name[parameter]

e.g.

save all @m. xmos1.xmos2.m1[vdsat]

FabriceSalvaire commented 7 years ago

Simulation type are Ngspice version dependent

fixed in 09cb29c

GProtoZeroW commented 6 years ago

@FabriceSalvaire @eps82 so I am also needing the @ to access device parameters, particularly MOSFET level 8 (BSIM 3.3.). Also having used Virtuoso this what there "Calculator " is doing in the background. So getting this into PySpice would be very powerful

So first off I have looked at the Berkely docs and for a level, 8 gm is not explicitly listed. So how are things like the transconductance being calculated by NgSpice? In the following doc https://class.ee.washington.edu/cadta/hspice/chapter_15.pdf for gm, they are calculating it via definition as seen on page 18. While for Cgg the same doc has it calculating from BSIM eqs and also from a measurement of charge. So then are all these @ just doing the BSIM class or are they acutely taking a measure during the simulation.

That brings me to the second questions. If there @ are just being calculated from def should they just be integrated into the PySpice BaseElement classes? Or on the other hand, they are measuring simulated things such as charge during the simulation should then effort be made to add this to the simulation as @eps82 has done.

Sorry for the degradation into spice theory but it's not taught so had to ask the question.

eps82 commented 6 years ago

@GProtoZeroW Beware this is a frontend for ngspice and not the simulator itself, so questions about "internals" should really be addressed to ngspice's lists https://sourceforge.net/p/ngspice/mailman/

Regarding transistor parameters, some of these are explicit variables inside the models, some aren't (in which case they are computed by the simulator). For example the transconductance gm or the output conductance gds measure how the transistor, as a whole, behaves, instead of being simple internal parameters or direct functions of them.

For the second part, if you're doing too low level stuff it may be simpler to just take the raw data (on my own github I uploaded a version of the simplified library I'm using). It all boils down to whether you take advantage of the high level functions and it's worth adding some extras for your own stuff, or on the contrary it's better to go up from the basics building only what you need on top of it.

GProtoZeroW commented 6 years ago

@eps82 okay thanks for clearing some of that info. So first off as someone who has had to use Virtuoso without docs in school, and has had its simulator try to outsmart me. I want to have a viable means of telling these non to fab Spice systems to F-Off. And I also want to try leveraging other Python libs like say machine learning to as Boris Murmann put it "kill the spice Monkey ".

So @eps82 I will take a look at your work. But I think moving forward in terms of PySpice. Would be to identify some values that are being done by NgSpice and are independent of say the Berkley simulation for exsample, and get calls to that wrapped into PySpice Circuit.Simulation. For other stuff that is calculated from the model that is not as dynamic things like Cgg. Do some updates to the SpiceLibary parser function to extract the model parameters to a class attributes to the instance of the device in the circuit or even a dic. So that users can then call in those values dynamically without having to do text processing on a model file. And then add to the example how to do those calculations.

eps82 commented 6 years ago

Just to explain myself better, in case there’s any misunderstanding:

some of these are explicit variables inside the models, some aren't (in which case they are computed by the simulator)

I meant there are some (usually physical) parameters that are directly linked to the model equations, still functions of voltage/current (for example the threshold voltage), but this is different from gm or gds, which are small signal parameters that define the behaviour of the transistor (you can define the DC transconductance for any 2-port network regardless of physics), and this comes from a small signal analysis, not the model equations themselves. In both cases you need the simulator to calculate this stuff for you, but they’re slightly different in origin.

As for the rest, I don’t quite understand what you mean to do, but from personal experience, my first impulse when I started doing design was to despise netlist-based interfaces compared to ADS or Cadence Virtuoso. Over time however I realised there were many situations where I needed to get my hands dirty as graphical interfaces only take us so far, and some cases that are still doable from a GUI can actually be more convenient in text because of the freedom with which one can script simulations.

In this sense, ngspice having an interface as a shared library one can call from python is very helpful for many tasks even when having access to commercial/professional tools. But of course anything you do often enough is worth wrapping with higher level functions.

FabriceSalvaire commented 6 years ago

done in 9cfbce

GProtoZeroW commented 6 years ago

@FabriceSalvaire how do I call the data from that using the NMOS from http://www.ece.umd.edu/~newcomb/courses/spring2010/303/tsmc180nmcmos.lib file needs to be modified to LEVEL=8, VERSION=3.3

TestCir=Circuit('DCBench')
Vgs=TestCir.V('gs', 'gate', TestCir.gnd, 1.8@u_V)
Vbs=TestCir.V('bs', 'base', TestCir.gnd, 0@u_V)
Vds=TestCir.V('ds', 'drain', TestCir.gnd, 1.8@u_V)

TestCir.include(spice_library['TSMC180nmN'])

M0=TestCir.MOSFET('0', 'drain', 'gate', TestCir.gnd, 'base',
                          model='TSMC180nmN', length=200@u_nm, width=200@u_nm)
print(TestCir)
TempC=27
TestCirSim=TestCir.simulator(temperature=TempC, nominal_temperature=TempC)
TestCirSim.save('@M0[gm], @M0[id]')
TestCirSimData=TestCirSim.dc(Vgs=slice(0, 5, .01))

So now how do I exstract the @M0[gm] data to a np array. ?

FabriceSalvaire commented 6 years ago

First you have to upgrade PySpice to the master version v1.2, see installation page:

pip install git+https://github.com/FabriceSalvaire/PySpice

from PySpice.Spice.Netlist import Circuit
from PySpice.Spice.Library import SpiceLibrary
from PySpice.Unit import *

####################################################################################################

import PySpice.Logging.Logging as Logging
logger = Logging.setup_logging()

####################################################################################################

spice_library = SpiceLibrary('.')

####################################################################################################

circuit = Circuit('DCBench')
Vgs = circuit.V('gs', 'gate', circuit.gnd, 1.8@u_V)
Vbs = circuit.V('bs', 'base', circuit.gnd, 0@u_V)
Vds = circuit.V('ds', 'drain', circuit.gnd, 1.8@u_V)

circuit.include(spice_library['TSMC180nmN'])

M0 = circuit.MOSFET('0', 'drain', 'gate', circuit.gnd, 'base',
                  model='TSMC180nmN', length=200@u_nm, width=200@u_nm)
print(circuit)

TempC = 27
simulator = circuit.simulator(temperature=TempC, nominal_temperature=TempC)
simulator.save_internal_parameters('@M0[gm]', '@M0[id]')
analysis = simulator.dc(Vgs=slice(0, 5, .01))

print(analysis['@M0[gm]'].str_data()[:100])
print(analysis['@M0[id]'].str_data()[:100])
GProtoZeroW commented 6 years ago

Can a subcircuits internal device parameter be accessed from the main circuit sim? Say monitoring the power of 'RP1' in the OpAmp ex https://pyspice.fabrice-salvaire.fr/examples/operational-amplifier/operational-amplifier.html . I have tried adding 'Xop' in various to '@RP1[p]' with no luck

FabriceSalvaire commented 6 years ago

I believe we can do it, but check ngspice manual

SahilJain122 commented 6 years ago

in what paths are these libraries searched?

nataraj-pinakapani commented 2 years ago

Can a subcircuits internal device parameter be accessed from the main circuit sim? Say monitoring the power of 'RP1' in the OpAmp ex https://pyspice.fabrice-salvaire.fr/examples/operational-amplifier/operational-amplifier.html . I have tried adding 'Xop' in various to '@rp1[p]' with no luck

The following code prints the current through the resistor R1 which is inside the subcircuit X1 import PySpice.Logging.Logging as Logging logger = Logging.setup_logging()

from PySpice.Spice.Netlist import Circuit, SubCircuit, SubCircuitFactory from PySpice.Unit import * class ParallelResistor(SubCircuitFactory): NAME = 'parallel_resistor' NODES = ('n1', 'n2') def init(self, R1=1@u_Ω, R2=2@u_Ω): super().init() self.R(1, 'n1', 'n2', R1) self.R(2, 'n1', 'n2', R2)

circuit = Circuit('Test') circuit.subcircuit(ParallelResistor(R2=3@u_Ω)) circuit.X('1', 'parallel_resistor', 1, circuit.gnd) circuit.V('1', 1, circuit.gnd, 1)

print(circuit)

simulator = circuit.simulator(temperature=25, nominal_temperature=25) simulator.save_internal_parameters('@V1[i]', '@R.X1.R1[i]') analysis = simulator.dc(V1=slice(5,5,5))

print(analysis['@V1[i]'].str_data()) print(analysis['@R.X1.R1[i]'].str_data())