forked from cechrist/cardoon
/
circuit.py
1165 lines (972 loc) · 39.1 KB
/
circuit.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""
:mod:`circuit` -- Classes for internal circuit representation
-------------------------------------------------------------
.. moduleauthor:: Carlos Christoffersen
Example of how to use this module
+++++++++++++++++++++++++++++++++
The following example was originally taken from
``analyses/nodal.py``. The objective of this code is to assign
row/column numbers to all non-reference nodes and -1 to all reference
nodes in a ``Circuit`` instance (``ckt``)::
# make a list of all non-reference terminals in circuit
ckt.nD_termList = ckt.termDict.values() + ckt.get_internal_terms()
# get reference node (if any)
if ckt.has_term('gnd'):
ckt.nD_ref = ckt.get_term('gnd')
# remove ground node from terminal list
ckt.nD_termList.remove(ckt.nD_ref)
# remove ground node from terminal list
ckt.nD_termList.remove(ckt.nD_ref)
# For reference nodes assign -1
ckt.nD_ref.nD_namRC = -1
# Make a list of all elements
ckt.nD_elemList = ckt.elemDict.values()
# Set RC number of reference terminals to -1
for elem in ckt.nD_elemList:
if elem.localReference:
elem.neighbour[elem.localReference].nD_namRC = -1
# Assign a number (starting from 0) to all nodes.
for i, term in enumerate(ckt.nD_termList):
term.nD_namRC = i
The following example shows how to create and add devices from the
point of view of a parser::
import circuit as cir
from devices import devClass
# Create circuit:
mainckt = cir.Circuit()
# Add elements with private model
# creates element type 'ind'
dev = devClass['ind']('Lchoke')
# override 'l' parameter value (otherwise default value is used)
dev.set_param('l', 1e-9)
# add to circuit
mainckt.add_elem(dev)
# connect to terminals 'vdd' and and 'drain', automatically created
mainckt.connect(dev, ['vdd', 'drain'])
# add element with shared (public) model: model 'my_nmos' is created
# if it is not already in circuit
m1 = devClass['mosacm']('mos1n')
mainckt.add_elem(m1, 'my_nmos')
# To later retrieve model
model = cir.get_model('my_nmos')
# if model not found returns None, use cir.add_model() if needed
# Subcircuits: create and add subcircuit instance
x1 = cir.Xsubckt('x1', 'LM741')
mainckt.add_subckt(x1)
# Connect subcircuit instance with cir.connect()
# Subcircuit definition: tl2 are the external connections
tl2 = ['vdd', 'vee', 'in', 'out']
amp1 = cir.SubCircuit('LM741', tl2)
# now you can treat amp1 as a regular circuit instance
dev = devClass['ind']('L2')
dev.override = [('l', 2e-9)]
amp1.add_elem(dev)
amp1.connect(dev, ['in', 'n1'])
Notes
+++++
* If anything goes wrong a CircuitError exception is thrown
* The device type is automatically added to Element names. This
prevents name conflicts from devices of different type. Example:
'mosacm:mos1n'
* When all circuits and subcircuits are entered, you can flatten() the
main circuit to remove subcircuit hierarchy (optional).
* Before elements can be used, the circuit should be initialized with
init(). This performs some simple checks and initializes all
elements in circuit (and sub-circuits if not flattened).
* .model statements have global scope: they can be used and referred
from any circuit/subcircuit.
* The same applies to subcircuit definitions. They are global and can
be referred from any circuit/subcircuit.
"""
from __future__ import print_function
from paramset import ParamSet, Model
from globalVars import glVar
import copy
#---------------------------------------------------------------------
class CircuitError(Exception):
"""
Used for exceptions raised in this module
"""
pass
#---------------------------------------------------------------------
class GraphNode(object):
"""
Simple graph node base class (not circuit node)
Used for circuit Elements, subcircuits and terminals. Links are
stored using lists.
"""
def __init__(self, nodeName):
"""
nodeName is the Name of the node. No check is made here that
the name is not already in use.
"""
self.nodeName = nodeName
self.neighbour = []
def __str__(self):
"""convert to string"""
desc = self.nodeName + '\n'
desc += 'Linked nodes: '
for n in self.neighbour:
desc += ' ' + n.nodeName
return(desc)
#---------------------------------------------------------------------
class Terminal(GraphNode):
"""
Represent circuit terminals (i.e., nodes)
This class should used only for 'external' terminals (i.e.,
terminals that appear in the netlist). See also InternalTerminal
"""
# By default terminals have volts as units. Devices may change this
unit = 'V'
def __init__(self, instanceName):
# Call base class constructors
GraphNode.__init__(self, instanceName)
def __str__(self):
"""convert to string"""
desc = 'Terminal({0}): {1}'.format(self.unit, GraphNode.__str__(self))
return(desc)
#---------------------------------------------------------------------
class InternalTerminal(Terminal):
"""
Represent terminals that are internal to one Element instance
They only have one neighbour (the parent Element instance)
"""
def __init__(self, element, name):
"""
Creates and connects internal terminal
element: parent Element instance
name: internal name (only unique to parent Element)
"""
# Call base class constructor
Terminal.__init__(self, name)
# Connect to parent element
self.neighbour.append(element)
element.neighbour.append(self)
def __str__(self):
"""convert to string"""
desc = 'Internal Terminal ({0}): {1}:{2}'.format(
self.unit,
self.neighbour[0].nodeName,
self.nodeName)
return(desc)
#---------------------------------------------------------------------
class Element(GraphNode, ParamSet):
"""
Base class for circuit Elements.
Default flags are set here.
"""
# numTerms = 0 means do not (automatically) check number of connections
numTerms = 0
# temperature parameter: must be called 'temp' (if needed at all)
tempItem = (('temp', ('Device temperature (None: use global temp.)',
'C', float, None)), )
# Has electro-thermal implementation (using autothermal.py)
# Element must implement all nonlinear functions to use this
makeAutoThermal = False
# Flags for nonlinear devices.
isNonlinear = False
# Number of time-delayed control ports
nDelays = 0
# Used for transmission lines, etc.
isFreqDefined = False
# Element contributes to source vector (DC, time-domain and freq-domain)
isDCSource = False
isTDSource = False
isFDSource = False
# General attributes
linearVCCS = []
linearVCQS = []
noisePorts = ()
# Local reference: if not zero, reference (internal) terminal number
localReference = 0
def __init__(self, instanceName):
"""
The name of the element is formed by combining the given
instanceName and the device type.
Example: diode:d1
"""
# Call base class constructors
GraphNode.__init__(self, self.devType + ':' + instanceName)
# Note: paramDict must be defined by the derived class
ParamSet.__init__(self, self.paramDict)
# Default is not to have a separate model
self.dotModel = False
# Printing and info-related functions ----------------------------------
def __str__(self):
"""convert to string"""
desc = 'Element ' + GraphNode.__str__(self)
desc += '\nDevice type: ' + self.devType + '\n'
if self.dotModel:
desc += 'Model: {0}\n'.format(self.dotModel.name)
desc += 'Overridden parameters: ' + ParamSet.netlist_string(self)
return(desc)
def netlist_string(self):
"""
Output netlist-formatted string in netlist format
"""
desc = '{0} '.format(self.nodeName)
# Add terminals
for i, term in enumerate(self.neighbour):
# Do not include internal terminals. The following works
# even when numTerms is not set.
if issubclass(type(term), InternalTerminal):
break
desc += term.nodeName + ' '
# Model (if any)
if self.dotModel:
desc += 'model = {0} '.format(self.dotModel.name)
# Parameters
desc += ParamSet.netlist_string(self)
return(desc)
def print_vars(self):
"""
Nicely print parameter list and OP (if any) with values and units
"""
print('Parameter values:\n')
print(ParamSet.__str__(self))
if hasattr(self, 'OP'):
print('Operating point information:\n')
print(' Variable | Value ')
print('-------------------------')
print(self.format_OP())
def format_OP(self):
"""
Return OP information in a formatted string
"""
try:
# Format operating point information
s = ''
for key in sorted(self.OP.iterkeys()):
s += '{0:10} | {1}\n'.format(key, self.OP[key])
return s
except AttributeError:
return ''
# General initialization --------------------------------------------
def init(self):
"""
General initialization function
Set the attribute values, check basic terminal connectivity
and process parameters.
"""
self.set_attributes()
self.check_terms()
self.process_params()
# Parameter-related functions ----------------------------------------
def is_set(self, paramName):
"""
Returns True if paramName is valid and manually set
"""
# check if parameter in model first
if self.dotModel:
answer = self.dotModel.is_set(paramName)
else:
answer = False
# now check in element
if not answer:
answer = ParamSet.is_set(self, paramName)
return answer
def set_attributes(self):
"""
Set parameters as attributes.
Priority is as follows: first manually set parameters, then
manually set parameters in model (if any) and finally default
values
"""
# Ambient temperature (temp) initially set to global
# temperature, but may be overriden by the model or the
# element line attributes
self.temp = glVar.temp
# Set overrides first
if self.dotModel:
ParamSet.set_attributes(self, useDefaults = False)
# Get othe attributes from model
self.dotModel.set_missing_attributes(self)
else:
ParamSet.set_attributes(self, useDefaults = True)
# Connectivity-related functions -----------------------------------------
def check_terms(self):
"""
Checks terminal connections
If numTerms is not zero, checks that the number of connected
terminals is equal to numTerms. Raises an exception if they do
not match.
Else sets numTerms = number of connected terminals
"""
if self.numTerms:
if (len(self.neighbour) != self.numTerms):
raise CircuitError(self.nodeName +
': must have ' + str(self.numTerms)
+ ' terminals.')
else:
# Set numterms to number of external connections (useful
# to quickly find internal terminals)
self.numTerms = len(self.neighbour)
def disconnect(self, terminal):
"""
Disconnect a terminal from an element. Arguments are Element and
Terminal instances.
Assumption is that if terminal is in element's list, then the nodes are
linked and element should be on terminal's list. If nodes not connected
an exception is raised.
"""
try:
self.neighbour.remove(terminal)
terminal.neighbour.remove(self)
except ValueError:
raise CircuitError('Nodes not linked')
def add_internal_term(self, name, unit):
"""
Create and connect one internal terminal
name: internal terminal name
unit: internal variable unit
Returns internal terminal index
"""
# Create internal term (connects automatically)
term = InternalTerminal(self, name)
term.unit = unit
return len(self.neighbour) - 1
def add_reference_term(self):
"""
Create and connect one internal terminal to be used as local reference
"""
# There is no need for more than one
assert not self.localReference
# Create internal term (connects automatically)
term = InternalTerminal(self, 'lref')
term.unit = '-'
# Set to reference terminal number
self.localReference = len(self.neighbour) - 1
return self.localReference
def get_internal_terms(self):
"""
Returns a list of internal terms (if any) excluding local references
"""
intTerms = self.neighbour[self.numTerms:]
if self.localReference:
intTerms.pop(self.localReference - self.numTerms)
return intTerms
def clean_internal_terms(self):
"""
Disconnect any internal terms,
Normally used before calling process_params() for a second
time or when an element is removed from circuit.
"""
for term in self.neighbour[self.numTerms:]:
self.disconnect(term)
# Chop adjacency list
self.neighbour = self.neighbour[:self.numTerms]
# Clean local reference
self.localReference = 0
#---------------------------------------------------------------------
class Xsubckt(GraphNode):
"""
Represent subcircuit instances (not definitions, use SubCircuit
for those)
"""
def __init__(self, instanceName, cktName):
"""
instanceName: self-documenting
cktName: name of the circuit that contains the definition for
this instance.
"""
# Call base class constructors
assert instanceName[0] == 'x'
GraphNode.__init__(self, instanceName)
self.cktName = cktName
def __str__(self):
"""convert to string"""
desc = 'xsubckt ' + GraphNode.__str__(self)
desc += '\nSubcircuit definition: ' + self.cktName
return(desc)
def netlist_string(self):
"""
Convert to string in netlist format
"""
desc = self.nodeName + ' '
for term in self.neighbour:
desc += term.nodeName + ' '
desc += self.cktName
return desc
def check_terms(self):
# First make sure the definition exists
try:
cktDef = Circuit.cktDict[self.cktName]
except KeyError:
raise CircuitError(self.nodeName + \
': subcircuit definition "'\
+ self.cktName + '" not found')
else:
if len(self.neighbour) != len(cktDef.extConnectionList):
raise CircuitError(self.nodeName + \
': xsubckt connections do not match '\
+ 'definition in "' + self.cktName \
+ '"')
#---------------------------------------------------------------------
class OutRequest:
"""
Holds a set of output variables requests
Output request consist in:
1. Type of of request (``type``): dc, ac_*, tran, etc.
2. List of variables (``varlist``): for external terminals,
these are strings with terminal name. For internal terminals,
a list with device and terminal names.
After initialization the circuit adds a list of terminals in the
``termlist`` attribute.
"""
validTypes = ['dc', 'ac', 'ac_mag', 'ac_phase', 'ac_dB', 'tran', 'hb']
def __init__(self, reqtype, varlist):
if reqtype not in self.validTypes:
raise CircuitError(
'Not a valid output request type: {0}'.format(reqtype)
+ '\nValid types: {0}'.format(self.validTypes))
self.type = reqtype
self.varlist = varlist
#---------------------------------------------------------------------
class Circuit(object):
"""
Holds a circuit.
There are 2 global dictionaries defined at the class level:
* cktDict: Contains references to all circuit/subcircuit
definitions
* modelDict: References to all models in any circuit. Thus .model
statements are global and can be defined and referred anywhere
Element, Xsubckt and (external) Terminal references are stored in
dictionaries, empty by default:
* elemDict
* subcktDict
* termDict
Internal terminals must be accessed directly from the parent
Element instance.
Ground node: terminals '0' and 'gnd' are considered to be the same
reference node. If a circuit does not contain a ground node then
it is up to the user to set a reference.
Plot/Save requests are stored in lists: plotReqList/saveReqList
In the future we may implement topology checking utilities here.
"""
cktDict = dict()
modelDict = dict()
def __init__(self, name):
"""
name: circuit name.
"""
self.name = name
self._initialized = False
self._flattened = False
# adds itself to main dictionary
if self.cktDict.has_key(name):
raise CircuitError('Circuit "' + name + '" already exists')
self.cktDict[name] = self
# Create (empty) dictionaries
self.termDict = dict()
self.elemDict = dict()
self.subcktDict = dict()
self.plotReqList = list()
self.saveReqList = list()
# Printing stuff ---------------------------------------------
def __str__(self):
"""
Generates a short descriptive string
"""
desc = 'Circuit: {0}\n'.format(self.name)
if hasattr(self, 'title'):
desc = self.title + '\n'
return desc
def netlist_string(self):
"""
Generates a 'netlist-like' description of the circuit
"""
desc = ''
if hasattr(self, 'title'):
desc = self.title + '\n'
desc += '# Circuit: ' + self.name + '\n'
desc += '# *** Elements ***\n'
for elem in self.elemDict.itervalues():
desc += elem.netlist_string() + '\n'
usedSubCKTs = set()
if not self._flattened and self.subcktDict:
desc += '# *** Subcircuit instances ***\n'
for subckt in self.subcktDict.itervalues():
desc += subckt.netlist_string() + '\n'
# Save used subckt name
usedSubCKTs.add(subckt.cktName)
desc += '# *** Subcircuit Definitions ***\n'
for cktName in usedSubCKTs:
desc += Circuit.cktDict[cktName].netlist_string()
for outreq in self.plotReqList:
desc += '\n.plot ' + outreq.type
for name in outreq.varlist:
desc += ' ' + name
for outreq in self.saveReqList:
desc += '\n.save ' + outreq.type
for name in outreq.varlist:
desc += ' ' + name
desc += '\n'
return desc
def globals_to_str(self):
"""
Convert all global stuff to netlist format
This includes: models, netlist variables and .options variables
"""
desc = '.vars '
for param, value in ParamSet.netVar.iteritems():
desc += '{0} = {1} '.format(param, value)
desc += '\n\n'
desc += '.options ' + glVar.netlist_string()
desc += '\n\n'
desc += '# *** Models *** \n'
for model in Circuit.modelDict.itervalues():
desc += model.netlist_string() + '\n\n'
return desc
# Actions on the whole circuit --------------------------------------
def copy(self, newName):
"""
Make a copy of an uninitialized circuit
newName is the circuit name for the copy
The elements in the copy point to the ParamSet of the original
element (models are not cloned). Elements are shallow-copied,
but terminal connections are re-generated.
If the circuit has been flattened, the copy does not include
subcircuit instances, otherwise a shallow copy of subcircuit
instances are generated.
"""
assert not self._initialized
# create new circuit with given name
cktCopy = Circuit(newName)
# Get all elements, add and connect them to this circuit
for elem in self.elemDict.itervalues():
# We need a copy of the element without the terminal
# connections. Make shallow copy:
elemCopy = copy.copy(elem)
# Clean terminal list
elemCopy.neighbour = []
# Add to circuit
cktCopy.add_elem(elemCopy)
# Create connection list
termList = [term.nodeName for term in elem.neighbour]
self.connect(elemCopy, termList)
if not self._flattened:
for xsubckt in self.subcktDict.itervalues():
xsubcktCopy = copy.copy(xsubckt)
# Clean terminal list
xsubcktCopy.neighbour = []
# Add to circuit
cktCopy.add_subckt(xsubcktCopy)
# Create connection list
termList = [term.nodeName for term in xsubckt.neighbour]
self.connect(xsubcktCopy, termList)
return cktCopy
def init(self):
"""
To be used after all elements/terminals have been created. Can
be called multiple times but only has an effect if
``self._initialized`` is False
1. Initialize all elements. This includes a check for terminal
connections and parameter processing. Elements may add
internal terminals at this point.
2. If not flattened, checks that subcircuit definitions exist
and checks number of terminal connections.
3. Checks that output requests contain valid terminal names
"""
# Can only initialize once
if self._initialized:
return
for elem in self.elemDict.itervalues():
elem.init()
if not self._flattened:
for xsubckt in self.subcktDict.itervalues():
xsubckt.check_terms()
# Initialize subcircuit definition
Circuit.cktDict[xsubckt.cktName].init()
# Check and initialize output requests
for outreq in self.plotReqList + self.saveReqList:
outreq.termlist = []
for termname in outreq.varlist:
# Distinguish external/internal terminals
if type(termname) == str:
# External terminal
if termname == '0':
# Special treatment for ground terminal
termname = 'gnd'
try:
outreq.termlist.append(self.termDict[termname])
except KeyError:
raise CircuitError(
'Output request for nonexistent terminal: '
+ termname)
else:
# Internal terminal
try:
elem = self.elemDict[termname[0]]
except KeyError:
raise CircuitError(
'Output request for nonexistent element: '
+ termname[0] + ':' + termname[1])
iterm = None
for term in elem.get_internal_terms():
if term.nodeName == termname[1]:
iterm = term
break
if iterm:
outreq.termlist.append(iterm)
else:
raise CircuitError(
'Output request for nonexistent terminal: '
+ termname[0] + ':' + termname[1])
self._initialized = True
def get_internal_terms(self):
"""
Returns a list with all non-reference internal terminals
Circuit must be initialized first. Note that the same effect
can be achieved by directly polling the elements.
"""
assert self._initialized
intTermList = []
for elem in self.elemDict.itervalues():
intTermList += elem.get_internal_terms()
return intTermList
def flatten(self):
"""
Expand all subcircuit instances in place, assuming the circuit
and subcircuits have not been initialized.
The flattened elements point to the ParamSet of the original
element (models are not cloned). Elements are shallow-copied,
but terminal connections are re-generated.
"""
assert not self._initialized
# Can only flatten once
if self._flattened:
return
for xsubckt in self.subcktDict.itervalues():
cktDef = self.cktDict[xsubckt.cktName]
# Recursively flatten nested subcircuits
if not cktDef._flattened:
cktDef.flatten()
# Get circuit from subcircuit definition
cktDef.get_circuit(xsubckt, self)
self._flattened = True
def check_sanity(self):
"""
This is slow for large circuits. Use for debugging to
make sure that all elements and terminals have been added to
the circuit. Also checks for floating terminals.
For now only works for unflattened circuits
"""
for elem in self.elemDict.itervalues():
for term in elem.neighbour:
assert self.termDict[term.nodeName] == term
if not self._flattened:
# If flattened Xsubckt instances are ignored
for subckt in self.subcktDict.itervalues():
for term in subckt.neighbour:
assert self.termDict[term.nodeName] == term
for term in self.termDict.itervalues():
# Make sure there are no floating terminals
assert term.neighbour
for node in term.neighbour:
if isinstance(node, Element):
assert self.elemDict[node.nodeName] == node
else:
assert self.subcktDict[node.nodeName] == node
# Actions on individual elements/terminals -------------------------
def connect(self, element, termList):
"""
Connect an Element (or subckt) instance to terminals
Terminals are specified by a list of terminal names
(termList). If a terminal does not exist in the circuit it is
created and added.
**Important notes**:
* Element/subckt should be in circuit dictionary. No
attempt to check this is made (use ``add_elem()``).
* Order in the adjacency list is important for Elements
For example, for a MOSFET the first node corresponds to
the drain, the second to the gate, etc.
"""
# Some sanity checking. This function should be used once per
# element (we could change this)
assert not element.neighbour
for termName in termList:
terminal = self.get_term(termName)
terminal.neighbour.append(element)
element.neighbour.append(terminal)
def add_elem(self, elem, modelName = None):
"""
Adds an element to a circuit.
If modelName is not given it is assumed that no global model
will be used
Otherwise the model is retrieved (or created if necessary) and
assigned to elem.dotModel. This model can be shared with other
elements.
A check is made to make sure the instance name is unique
"""
if self.elemDict.has_key(elem.nodeName):
raise CircuitError(elem.nodeName + ': Element already exists')
else:
self.elemDict[elem.nodeName] = elem
if modelName:
if elem.dotModel:
raise CircuitError('{0} already has an ' +
'assigned model'.format(elem.nodeName))
# Public model: Check if model already in circuit
if Circuit.modelDict.has_key(modelName):
model = Circuit.modelDict[modelName]
if model.modelType != elem.devType:
raise CircuitError(
'Incorrect model type "{0}" for element "{1}"'.format(
model.modelType, elem.nodeName))
elem.dotModel = model
else:
elem.dotModel = Model(modelName, elem.devType, elem.paramDict)
Circuit.modelDict[modelName] = elem.dotModel
def remove_elem(self, elemName):
"""
Disconnect and remove an element from a circuit. Internal
terminals are also removed.
"""
# Make sure the node is in circuit (and take it out)
try:
elem = self.elemDict.pop(elemName)
except KeyError:
raise CircuitError(elemName + ': Element not found' )
else:
# Remove internal terminals first
elem.clean_internal_terms(self)
# unlink connections (may be a bit slow)
for n1 in elem.neighbour:
# Disconnect any terminals from elem
n1.neighbour.remove(elem)
def add_subckt(self, xsubckt):
"""
Adds a Xsubckt instance to circuit
"""
if self.subcktDict.has_key(xsubckt.nodeName):
raise CircuitError('add_subckt: Subcircuit instance name "'\
+ xsubckt.nodeName + '" already in use')
else:
self.subcktDict[xsubckt.nodeName] = xsubckt
def get_term(self, termName):
"""
Returns an external terminal instance with the given name.
A new instance is created if necessary
"""
if termName == '0':
# Special treatment for ground terminal
termName = 'gnd'
if self.termDict.has_key(termName):
term = self.termDict[termName]
else:
term = self.termDict[termName] = Terminal(termName)
return term
def has_term(self, termName):
"""
Returns True if terminal present in circuit
"""
if termName == '0':
# Special treatment for ground terminal
termName = 'gnd'
if self.termDict.has_key(termName):
return True
else:
return False
def remove_term(self, termName):
"""
Removes a terminal from a circuit. Links are *not* removed, you
must take care of that.
"""
# Make sure the node is in circuit (and take it out)
try:
term = self.termDict.pop(termName)
except KeyError:
raise CircuitError(termName + ': Terminal not found' )
def add_plot_request(self, outreq):
self.plotReqList.append(outreq)
def add_save_request(self, outreq):
self.saveReqList.append(outreq)
def get_requested_terms(self, reqtype):
"""
Returns a set with terminals to be plotted or saved
Set generated from all plot and save requests of the specified
type.
"""
# Uses a set to avoid repeated terminals
termSet = set()
for outreq in self.plotReqList + self.saveReqList:
if outreq.type == reqtype:
for term in outreq.termlist:
termSet.add(term)
return termSet
#---------------------------------------------------------------------
class SubCircuit(Circuit):
"""
Almost identical to Circuit but adds support for external connections.
External subcircuit terminals have an additional attribute set:
``subCKTconnection``. This attribute is set to the subcircuit's
connection number. Example::
.subckt inverter in out vplus vminus
in.subCKTconnection = 0
out.subCKTconnection = 1
vplus.subCKTconnection = 2
vminus.subCKTconnection = 3
The reference node ('gnd' or '0') is global , *i.e.*, a terminal
named 'gnd' in a subcircuit is assumed to be connected to the same
ground node in all other circuits/subcircuits. To avoid problems,
the ground terminal can not be included in the external terminal
list. One of the possible problems is short-circuiting a node to
ground when a subcircuit is connected. Example of invalid code::
vdc:vcc 1 0 vdc=10V
vdc:vee 2 0 vdc=-10V
xamp1 1 2 in out amplifier
.subckt amplifier 1 gnd in out
res:rc 1 out r=5k
cap:cin in 2 c=1uF
bjt:q1 out 2 gnd type=npn
.ends
Possible solutions:
1. Rename 'gnd' node in subcircuit to something else
2. Remove 'gnd' node (implicitly connected to terminal '0' in
main circuit) from subcircuit external terminal list
"""