netlist: Fix namespaces for subsheet packages
[geda-gaf.git] / src / gaf / netlist / netlist.py
blob99259462191b4c68e8c4dccd84b5ae20db447fac
1 # gaf.netlist - gEDA Netlist Extraction and Generation
2 # Copyright (C) 1998-2010 Ales Hvezda
3 # Copyright (C) 1998-2010 gEDA Contributors (see ChangeLog for details)
4 # Copyright (C) 2013-2020 Roland Lutz
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software Foundation,
18 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 ## \namespace gaf.netlist.netlist
21 ## Main entry point for netlist generation.
23 # See the class Netlist for details.
25 import os, sys
26 from gettext import gettext as _
27 import gaf.attrib
28 import gaf.read
29 import gaf.netlist.blueprint
30 import gaf.netlist.instance
31 import gaf.netlist.net
32 import gaf.netlist.package
33 import gaf.netlist.pp_graphical
34 import gaf.netlist.pp_hierarchy
35 import gaf.netlist.pp_netattrib
36 import gaf.netlist.pp_power
37 import gaf.netlist.pp_slotting
38 import gaf.netlist.slib
40 ## Global netlist object representing the result of a netlister run.
42 class Netlist:
43 ## Create a netlist.
45 # This is the main function which creates a netlist. The most
46 # important argument is \a toplevel_filenames; it contains the
47 # filenames of the schematic pages which should be traversed.
48 # Other schematic pages are loaded as necessary if the \a
49 # traverse_hierarchy argument is set.
51 # \param [in] toplevel_filenames
52 # list of filenames for the toplevel schematics, as given on
53 # the command line
55 # \param [in] traverse_hierarchy
56 # whether to descend into sub-schematics
58 # \param [in] verbose_mode
59 # whether to print "Loading schematic" messages
61 # \param [in] prefer_netname_attribute
62 # whether to prefer net names set via a net segment's \c
63 # netname= attribute over net names set via a pin's \c net=
64 # attribute
66 # \param [in] flat_package_namespace
67 # whether to use a common package namespace for all subsheets
69 # \param [in] flat_netname_namespace
70 # whether to use a common \c netname= namespace for all subsheets
72 # \param [in] flat_netattrib_namespace
73 # whether to use a common \c net= namespace for all subsheets
75 # \param [in] refdes_mangle_func
76 # function for mangling package/component refdes's
78 # \param [in] netname_mangle_func
79 # function for mangling net names
81 # \param [in] default_net_name
82 # naming template for unnamed nets
84 # \param [in] default_bus_name
85 # naming template for unnamed buses
87 def __init__(self, toplevel_filenames,
88 traverse_hierarchy,
89 verbose_mode = False,
90 prefer_netname_attribute = False,
91 flat_package_namespace = False,
92 flat_netname_namespace = False,
93 flat_netattrib_namespace = False,
94 refdes_mangle_func = NotImplemented,
95 netname_mangle_func = NotImplemented,
96 default_net_name = 'unnamed_net',
97 default_bus_name = 'unnamed_bus',
98 show_error_coordinates = False):
99 ## Aggregated list of all components in the netlist.
100 self.components = []
101 ## List of sheets for the schematics named on the command line.
102 self.toplevel_sheets = []
103 ## List of sheets.
104 self.sheets = []
106 ## Whether an error has occurred.
107 self.failed = False
109 ## List of nets.
111 # Populated by gaf.netlist.net.
112 self.nets = None
114 ## List of packages.
116 # Populated by gaf.netlist.package.
117 self.packages = None
119 ## Convenience dictionary for looking up packages by their refdes.
120 self.packages_by_refdes = None
122 ## Convenience dictionary for looking up nets by their name.
123 self.nets_by_name = None
125 ## List of schematic blueprints.
126 self.schematics = []
127 ## Dictionary mapping filenames to schematic blueprints.
128 self.schematics_by_filename = {}
130 ## Whether a common package namespace for all subsheets was used.
131 self.flat_package_namespace = flat_package_namespace
132 ## Function which was used for mangling package/component refdes's.
133 self.refdes_mangle_func = refdes_mangle_func
135 ## Whether to print coordinate hints for errors.
136 self.show_error_coordinates = show_error_coordinates
138 def load_schematic(filename):
139 if filename in self.schematics_by_filename:
140 return
142 self.schematics_by_filename[filename] = None
144 if verbose_mode:
145 sys.stderr.write(_("Loading schematic [%s]\n") % filename)
147 try:
148 rev = gaf.read.read(filename, load_symbols = True)
149 except Exception as e:
150 if str(e):
151 sys.stderr.write(_("ERROR: Failed to load '%s': %s\n")
152 % (filename, e))
153 else:
154 sys.stderr.write(_("ERROR: Failed to load '%s'\n")
155 % filename)
156 sys.exit(2)
158 rev.finalize()
159 schematic = gaf.netlist.blueprint.Schematic(
160 rev, filename, self)
161 self.schematics.append(schematic)
162 self.schematics_by_filename[filename] = schematic
164 # Check if the component object represents a subsheet (i.e.,
165 # has a "source=" attribute), and if so, get the filenames.
167 for component in schematic.components:
168 component.composite_sources = []
170 for value in component.get_attributes('source'):
171 for filename in value.split(','):
172 if filename.startswith(' '):
173 warn(_("leading spaces in source names "
174 "are deprecated"))
175 filename = filename.lstrip(' ')
177 full_filename = \
178 gaf.netlist.slib.s_slib_search_single(
179 filename)
180 if full_filename is None:
181 component.error(
182 _("failed to load subcircuit '%s': "
183 "schematic not found in source library")
184 % filename)
185 continue
187 load_schematic(full_filename)
188 component.composite_sources.append(
189 self.schematics_by_filename[full_filename])
191 for filename in toplevel_filenames:
192 load_schematic(filename)
194 gaf.netlist.pp_power.postproc_blueprints(self)
195 gaf.netlist.pp_hierarchy.postproc_blueprints(self)
196 gaf.netlist.pp_slotting.postproc_blueprints(self)
197 gaf.netlist.pp_netattrib.postproc_blueprints(self)
198 gaf.netlist.pp_graphical.postproc_blueprints(self)
199 gaf.netlist.package.postproc_blueprints(self)
201 # look for component type conflicts
202 for schematic in self.schematics:
203 for component in schematic.components:
204 if component.composite_sources and component.is_graphical:
205 # Do not bother traversing the hierarchy if the symbol
206 # has an graphical attribute attached to it.
207 component.warn(_("source= is set for graphical component"))
208 component.composite_sources = []
210 if component.has_netname_attrib and \
211 component.has_portname_attrib:
212 component.error(_("netname= and portname= attributes "
213 "are mutually exclusive"))
215 if component.has_netname_attrib and \
216 component.composite_sources:
217 component.error(_("power symbol can't be a subschematic"))
218 component.composite_sources = []
219 if component.has_portname_attrib and \
220 component.composite_sources:
221 component.error(_("I/O symbol can't be a subschematic"))
222 component.composite_sources = []
224 if component.has_netname_attrib and component.is_graphical:
225 component.error(_("power symbol can't be graphical"))
226 if component.has_portname_attrib and component.is_graphical:
227 component.error(_("I/O symbol can't be graphical"))
229 # collect parameters
230 for schematic in self.schematics:
231 for component in schematic.components:
232 component.parameters = {}
234 for func in [gaf.attrib.search_inherited,
235 gaf.attrib.search_attached]:
236 names = set()
237 for val in func(component.ob, 'param'):
238 try:
239 name, value = gaf.attrib.parse_string(val)
240 except gaf.attrib.MalformedAttributeError:
241 component.error(
242 _("malformed param= attribute: %s") % val)
243 continue
245 if name in names:
246 component.error(
247 _("duplicate param= attribute: %s") % name)
248 continue
250 component.parameters[name] = value
251 names.add(name)
253 # Traverse the schematic files and create the component objects
254 # accordingly.
256 def s_traverse_sheet1(sheet):
257 for component in sheet.components:
258 # now you need to traverse any underlying schematics
260 # Check if the component object represents a subsheet (i.e.,
261 # has a "source=" attribute), and if so, traverse that sheet.
263 for subschematic in component.blueprint.composite_sources:
264 # can't do the following, don't know why... HACK TODO
265 #component.hierarchy_tag = u_basic_strdup(refdes)
266 subsheet = gaf.netlist.instance.Sheet(
267 component.sheet.netlist, subschematic, component)
268 s_traverse_sheet1(subsheet)
270 for filename in toplevel_filenames:
271 sheet = gaf.netlist.instance.Sheet(
272 self, self.schematics_by_filename[filename], None)
273 self.toplevel_sheets.append(sheet)
274 if traverse_hierarchy:
275 s_traverse_sheet1(sheet)
277 # now that all the sheets have been read, go through and do the
278 # post processing work
280 # List the components in the same order as the old gnetlist code.
282 def collect_components(sheet):
283 for component in sheet.components:
284 sheet.netlist.components.append(component)
285 for subsheet in component.subsheets:
286 collect_components(subsheet)
288 for sheet in self.toplevel_sheets:
289 collect_components(sheet)
291 # create net objects
292 gaf.netlist.net.postproc_instances(
293 self, { False: flat_netname_namespace,
294 True: flat_netattrib_namespace },
295 prefer_netname_attribute,
296 default_net_name, default_bus_name)
298 # assign net names
299 for net in self.nets:
300 net.name = netname_mangle_func(
301 net.unmangled_name, net.namespace)
303 # assign component pins
304 for component in self.components:
305 for cpin in component.cpins:
306 if (component.sheet, cpin.blueprint.net) \
307 not in cpin.local_net.net.sheets_and_net_blueprints:
308 cpin.local_net.net.sheets_and_net_blueprints.append(
309 (component.sheet, cpin.blueprint.net))
311 for net in self.nets:
312 for sheet, net_blueprint in net.sheets_and_net_blueprints:
313 for cpin_blueprint in net_blueprint.pins:
314 if cpin_blueprint.ob is not None:
315 assert cpin_blueprint.ob.data().is_pin
317 cpin = sheet \
318 .components_by_blueprint[cpin_blueprint.component] \
319 .cpins_by_blueprint[cpin_blueprint]
321 assert cpin not in net.component_pins
322 net.component_pins.append(cpin)
324 # Resolve hierarchy
325 gaf.netlist.pp_hierarchy.postproc_instances(self)
327 gaf.netlist.pp_graphical.postproc_instances(self)
329 # group components into packages
330 gaf.netlist.package.postproc_instances(
331 self, flat_package_namespace)
333 # see if any unconnected subsheet pins are connected to
334 # multiple I/O ports; these need to be preserved or the
335 # internal connections in the subsheet will be lost
336 for net in self.nets:
337 if net.is_unconnected_pin and len(net.connections) > 1:
338 net.is_unconnected_pin = False
340 # remove nets for unconnected pins
341 self.nets = [net for net in self.nets if not net.is_unconnected_pin]
344 # assign component refdes
345 for component in self.components:
346 if component.blueprint.refdes is not None:
347 component.refdes = refdes_mangle_func(
348 component.blueprint.refdes,
349 component.sheet.namespace)
351 # assign package refdes
352 for package in self.packages:
353 if package.namespace is not None:
354 package.refdes = refdes_mangle_func(
355 package.unmangled_refdes, package.namespace)
356 else:
357 # If refdes mangling is disabled, packages don't have
358 # a sheet attribute, so just use the unmangled refdes.
359 package.refdes = package.unmangled_refdes
361 # compile convenience hashes, checking for cross-page name clashes
362 self.packages_by_refdes = {}
363 for package in self.packages:
364 if package.refdes in self.packages_by_refdes:
365 other_package = self.packages_by_refdes[package.refdes]
366 self.error(_("refdes conflict across hierarchy: "
367 "refdes `%s' is used by package `%s' on page "
368 "`%s' and by package `%s' on page `%s'") % (
369 package.refdes,
370 other_package.unmangled_refdes,
371 refdes_mangle_func('', other_package.namespace),
372 package.unmangled_refdes,
373 refdes_mangle_func('', package.namespace)))
374 self.packages_by_refdes[package.refdes] = package
376 self.nets_by_name = {}
377 for net in self.nets:
378 if net.name in self.nets_by_name:
379 other_net = self.nets_by_name[net.name]
380 self.error(_("net name conflict across hierarchy: "
381 "net name `%s' is used by net `%s' on page "
382 "`%s' and by net `%s' on page `%s'") % (
383 net.name,
384 other_net.unmangled_name,
385 netname_mangle_func('', other_net.namespace),
386 net.unmangled_name,
387 netname_mangle_func('', net.namespace)))
388 self.nets_by_name[net.name] = net
390 ## Return the value of a toplevel attribute.
392 # Searches for an floating attribute with the name \a name in the
393 # schematic files listed on the command line. Calls \ref error if
394 # multiple attributes with different values are found.
396 # Traditionally, this function returned <tt>'not found'</tt> when
397 # no such attribute existed in the toplevel schematic.
399 # \throws ValueError if no matching attribute was found and no \a
400 # default was given
402 def get_toplevel_attribute(self, name, default = KeyError):
403 if not isinstance(name, basestring):
404 raise ValueError
406 values = []
407 for sheet in self.toplevel_sheets:
408 values += gaf.attrib.search_floating(
409 sheet.blueprint.rev, name)
411 if values:
412 for value in values[1:]:
413 if value != values[0]:
414 self.error(
415 _("inconsistent values for toplevel attribute "
416 "\"%s\": %s") % (
417 name, _(" vs. ").join(_("\"%s\"") % value
418 for value in values)))
419 return values[0]
420 return values[0]
422 if default is not KeyError:
423 return default
424 raise KeyError
426 ## Print an error message and mark the netlist as failed.
428 def error(self, msg):
429 sys.stderr.write(_("error: %s\n" % msg))
430 self.failed = True
432 ## Print a warning message.
434 def warn(self, msg):
435 sys.stderr.write(_("warning: %s\n" % msg))