Skip to content
Permalink
 
 
Cannot retrieve contributors at this time
# gaf.netlist - gEDA Netlist Extraction and Generation
# Copyright (C) 1998-2010 Ales Hvezda
# Copyright (C) 1998-2010 gEDA Contributors (see ChangeLog for details)
# Copyright (C) 2013-2020 Roland Lutz
#
# 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 2 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, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# gnetlist back end for SPICE
# Copyright (C) 2012, 2013 John P. Doty
import re, sys
# forward and reverse refdes maps
munges = {}
refdes_reserved = set()
# Expand a string in context of a particular package.
#
# Splits string into alternating fragments of whitespace and ink and
# passes each through check_and_expand_field.
#
# This is how we convert symbol data and connections into SPICE cards.
MAYBE_WHITESPACE_REGEXP = re.compile('\s+|\S+')
def expand_string(netlist, package, s):
fields = []
pos = 0
while pos < len(s):
m = MAYBE_WHITESPACE_REGEXP.match(s, pos)
fields.append(check_and_expand_field(netlist, package, m.group(0)))
pos = m.end()
return ''.join(fields)
# gnetlist associates net with pinnumber, but traditionally SPICE
# backends for gnetlist have keyed on pinseq. This function implements that.
def get_net_by_pinseq(package, n):
try:
pin = package.get_pin_by_pinseq(n)
except KeyError:
package.error("pinseq=%s not found. "
"This may indicate an erroneously duplicated refdes." % n)
return ''
return get_net(package, pin.number)
# Get the net attached to a particular pin.
def get_net(package, pin):
if pin not in package.pins_by_number:
package.error("pinnumber=%s not found" % pin)
return ''
net = package.pins_by_number[pin].net
if net.unnamed_counter is not None:
return '%d' % net.unnamed_counter
return net.name
# Get the munged version of refdes
def get_munged(prefix, refdes):
try:
return munges[prefix, refdes]
except KeyError:
# Make unique munged version.
# Append X until we have a unique refdes.
candidate = prefix + refdes
while candidate in refdes_reserved:
candidate += 'X'
refdes_reserved.add(candidate)
munges[prefix, refdes] = candidate
return candidate
# Get package or toplevel attribute.
def expand_value(package, name, default):
# Select either package attribute or toplevel attribute
if package is not None:
return package.get_attribute(name, default)
else:
return netlist.get_toplevel_attribute(name, default)
# Check field for magic, and expand accordingly if necessary.
# We only expand a given field once, on the first magic character discovered.
def check_and_expand_field(netlist, package, field):
# Find magic character for field expansion.
try:
i = next(i for i, c in enumerate(field) if c in '?#=@%')
except StopIteration:
return field
# Split field at the magic character
left, key, right = field[:i], field[i], field[i + 1:]
if key == '?':
# Expand refdes, munging if asked. Note that an empty prefix
# matches any string, here, so The Right Thing (nothing) happens.
if package.refdes.lower().startswith(left.lower()):
return package.refdes + right
else:
return get_munged(left, package.refdes) + right
elif key == '#':
# Get name of net connected to pin.
# The only issue is whether "left" specifies an alternate refdes.
if not left:
return get_net(package, right)
else:
return get_net(netlist.packages_by_refdes[left], right)
elif key == '=':
# Expand attribute. Result is name=value.
# Empty string if it doesn't exist, and no default given.
value = expand_value(package, left, right)
if not value:
return ''
else:
return left + '=' + value
elif key == '@':
return expand_value(package, left, right)
elif key == '%':
# Expand "%" variables.
# For now, reconstruct input for unrecognized ones.
# Should be error instead?
if right == 'pinseq':
# get all net connections in pinseq order
# Check for slotting, error if present.
try:
numslots = int(package.get_attribute('numslots', 'unknown'))
except ValueError:
pass
else:
if numslots > 0:
package.error("This package uses slotting. "
"You must list its connections by "
"pinnumber, not pinseq.")
return left
return left + ' '.join(get_net_by_pinseq(package, n + 1)
for n in range(len(package.pins)))
if right == 'io':
# get nets attached to SPICE io pins
return left + ' '.join(get_net(package, '1')
for package in spice_io_pins)
if right == 'up':
# List input/output symbols in lexical order
return left + ' '.join(get_net(package, '1')
for package in io_pins)
if right == 'down':
# list pins sorted by pinlabel
pins_by_label = package.pins
pins_by_label.sort(
key = lambda pin:
(pin.get_attribute('pinlabel', 'unknown'), pin))
# get all net connections in lexical pinlabel order
return left + ' '.join(get_net(package, pin.number)
for pin in pins_by_label)
return field
# ============================================================================
# Essentially, this back end works by collecting lists of output "cards"
# and then generating output.
# List of files to include
files = []
# This variable will hold the .subckt card if given.
subcircuit = False
# List of cards in the circuit or subcircuit.
# Note that a string here might actually represent a group
# of cards, using embedded newline characters.
cards = []
# Build sort key for package: a pair (number, refdes)
def number_package(package):
# convert refdes tu number, ignoring non-numeric chars
value = ''.join(c for c in package.refdes if c in '0123456789')
try:
return int(value), package.refdes
except ValueError:
return None, package.refdes
# Include "cards" from appropriate toplevel attributes.
def process_toplevel(netlist, attr):
t = netlist.get_toplevel_attribute(attr, None)
if t is not None:
collect_card(netlist, expand_string(netlist, None, t))
# Put the "card" in the right place
def collect_card(netlist, card):
global subcircuit
if card.lower().startswith('.subckt'):
# record a subcircuit card, error if more than one
if subcircuit:
netlist.error("More than one .subckt card generated!")
else:
subcircuit = card
else:
cards.append(card)
def run(f, netlist):
# Collect input/output symbols and SPICE subcircuit IO pin symbols
io_pins = []
spice_io_pins = []
for package in netlist.packages:
device = package.get_attribute('device', 'unknown')
if device in ['INPUT', 'OUTPUT', 'IO']:
io_pins.append(package)
if device == 'spice-IO':
spice_io_pins.append(package)
io_pins.sort(key = lambda package: package.refdes)
# Sort list of packages by the numeric part of their refdes
spice_io_pins.sort(key = number_package)
# Write a header. Critical because SPICE may treat the first line
# as a comment, even if it's not!
f.write('* %s\n' % ' '.join(sys.argv))
f.write('* SPICE file generated by spice-noqsi version 20130710\n'
'* Send requests or bug reports to jpd@noqsi.com\n')
for package in netlist.packages:
# prevent munging from accidentally duplicating an existing
# refdes by reserving all existing refdeses.
refdes_reserved.add(package.refdes)
# Collect file= attribute values.
# Unlike previous SPICE back ends for gnetlist, this one allows
# any symbol to request a file to be included.
for package in netlist.packages:
filename = package.get_attribute('file', None)
if filename is not None:
if filename in files:
files.remove(filename)
files.append(filename)
process_toplevel(netlist, 'spice-prolog')
for package in netlist.packages:
# To process a part, we must find a suitable prototype,
# fill in that prototype with instance data, and then figure
# out if this is an ordinary "card" or a .SUBCKT
proto = package.get_attribute('spice-prototype', None)
if proto is None:
# If no spice-prototype attribute, get prototype according to
# the device attribute, or use the default for the "unknown"
# device.
try:
proto = prototypes[package.get_attribute('device', 'unknown')]
except KeyError:
proto = prototypes['unknown']
collect_card(netlist, expand_string(netlist, package, proto))
process_toplevel(netlist, 'spice-epilog')
if subcircuit:
f.write('%s\n' % subcircuit)
for f_ in files:
f.write('.INCLUDE %s\n' % f_)
for s in cards:
f.write('%s\n' % s)
if subcircuit:
f.write('.ENDS\n')
# Default prototypes by device
# Note that the "unknown" prototype applies to all unlisted devices.
# Standard prototypes. Most of these are intended to be similar to the
# hard-wired behavior of spice-sdb.
prototypes = {
'unknown': '? %pinseq value@ model-name@ spice-args@',
'AOP-Standard': 'X? %pinseq model-name@',
'BATTERY': 'V? #1 #2 spice-args@',
'SPICE-cccs': 'F? #1 #2 V? value@\n'
'V? #3 #4 DC 0',
'SPICE-ccvs': 'H? #1 #2 V? value@\n'
'V? #3 #4 DC 0',
'directive': 'value@',
'include': '*', # just a place to hang file=
'options': '.OPTIONS value@',
'CURRENT_SOURCE': 'I? %pinseq value@',
'K': 'K? inductors@ value@',
'SPICE-nullor': 'N? %pinseq value@1E6',
'SPICE-NPN': 'Q? %pinseq model-name@ spice-args@ ic= temp=',
'PNP_TRANSISTOR': 'Q? %pinseq model-name@ spice-args@ ic= temp=',
'NPN_TRANSISTOR': 'Q? %pinseq model-name@ spice-args@ ic= temp=',
'spice-subcircuit-LL': '.SUBCKT model-name@ %io',
'spice-IO': '*',
'SPICE-VC-switch': 'S? %pinseq model-name@ value@',
'T-line': 'T? %pinseq value@',
'vac': 'V? %pinseq value@',
'SPICE-vccs': 'G? %pinseq value@',
'SPICE-vcvs': 'E? %pinseq value@',
'VOLTAGE_SOURCE': 'V? %pinseq value@',
'vexp': 'V? %pinseq value@',
'vpulse': 'V? %pinseq value@',
'vpwl': 'V? %pinseq value@',
'vsin': 'V? %pinseq value@',
'VOLTAGE_SOURCE': 'V? %pinseq value@',
'INPUT': '*',
'OUTPUT': '*',
'CAPACITOR': 'C? %pinseq value@ model-name@ spice-args@ l= w= area= ic=',
'DIODE': 'D? %pinseq model-name@ spice-args@ area= ic= temp=',
'NMOS_TRANSISTOR': 'M? %pinseq model-name@ spice-args@ '
'l= w= as= ad= pd= ps= nrd= nrs= temp= ic= m=',
'PMOS_TRANSISTOR': 'M? %pinseq model-name@ spice-args@ '
'l= w= as= ad= pd= ps= nrd= nrs= temp= ic= m=',
'RESISTOR': 'R? %pinseq value@ model-name@ spice-args@ w= l= area= temp=',
'DUAL_OPAMP': 'X1? #3 #2 #8 #4 #1 model-name@\n'
'X2? #5 #6 #8 #4 #7 model-name@',
'QUAD_OPAMP': 'X1? #3 #2 #11 #4 #1 model-name@\n'
'X2? #5 #6 #11 #4 #7 model-name@\n'
'X3? #10 #9 #11 #4 #8 model-name@\n'
'X4? #12 #13 #11 #4 #14 model-name@',
'model': '* refdes@',
}