Save prefix_to_pass where the node ends, which makes the
[trinary.git] / bb / bb.py
blobc6622616b7aa76124818f00be8fbe491cdd0df1f
1 #!/usr/bin/env python
2 # Created:20080411
3 # By Jeff Connelly
5 # Circuit pin layout program, for mapping sub-chip (or partially sub-chip)
6 # subcircuits to actual integrated circuit chips plus optional extra components,
7 # outside the chip. For example, the 'tinv' subcircuit has a complementary pair
8 # of MOSFETs, which this program maps to part of CD4007, assigning the pins,
9 # and mapping the other pair inside the CD4007 if another 'tinv' comes along.
11 import copy
12 import types
13 import sys
15 import tg
16 import tinv
17 import tnor
18 import tnand
19 import os
21 PROGRAM_NAME = "bb.py"
23 # Subcircuits to map that should be mapped physical ICs
24 SUBCIRCUITS_TO_MAP = ('tg', 'tinv', 'tnor', 'tnor3', 'tnand', 'tnand3')
25 SUBCIRCUITS_CAN_MAP = ('tg', 'tinv', 'tnor', 'tnand') # subcircuits we actually can map, as of yet
27 def combine_dicts(dict1, dict2):
28 """Combine two dictionaries; dict2 takes priority over dict1."""
29 ret = copy.deepcopy(dict1)
30 ret.update(dict2)
31 return ret
33 def read_netlist(filename):
34 """Read a SPICE input deck, returning subcircuit nodes and definitions."""
36 if filename == "-":
37 f = sys.stdin
38 else:
39 f = file(filename, "rt")
40 subckt_nodes = {}
41 subckt_defns = {}
42 toplevel = []
43 name = None
44 while True:
45 line = f.readline()
46 if len(line) == 0:
47 break
49 line = line.strip()
51 if line.startswith(".subckt"):
52 words = line.split()
53 name = words[1]
54 nodes = words[2:]
56 subckt_nodes[name] = nodes
57 elif line.startswith(".ends"):
58 name = None
59 else:
60 if name is not None:
61 if not subckt_defns.has_key(name):
62 subckt_defns[name] = []
63 subckt_defns[name].append(line)
64 else:
65 # Top-level elements, skip blank lines, commands and comments
66 if len(line.strip()) != 0 and line[0] not in ('.', '*'):
67 toplevel.append(line)
69 return subckt_nodes, subckt_defns, toplevel
71 def rewrite_subckt(subckt_defns, s):
72 """Partially rewrite subcircuit 's', using rules from a module of the same name.
74 Removes parts in mod.parts_consumed, keeps parts in mod.parts_kept.
76 Does not generate any new parts."""
78 mod = globals()[s]
79 lines = []
80 for line in subckt_defns[s]:
81 words = line.split()
82 name = words[0]
83 args = words[1:]
85 if name in mod.parts_consumed:
86 # Skip this line
87 pass
88 elif name in mod.parts_kept:
89 lines.append(line)
90 else:
91 raise "Subcircuit %s defined in module %s has parts consumed list: %s and parts kept list: %s, but the name '%s' is in neither. Add it to either." % (s, name, mod.parts_consumed, mod.parts_kept, name)
93 # Map node positions in arguments list (subckt invocation, X..), to node names
94 pos2node = {}
95 for i in range(0, len(mod.nodes)):
96 pos2node[mod.nodes[i]] = i
98 # Rewrite
99 subckt_defns[s] = lines
101 return mod, subckt_defns, pos2node
103 next_serial = -1
104 def get_serial():
105 """Get a unique, increasing number."""
106 global next_serial
108 next_serial += 1
109 return next_serial
111 def get_floating(n=None):
112 """Return a unique disconnected (floating) node name.
114 If n is given, return a dict of n floating node names. Otherwise returns one in a string."""
115 if n is not None:
116 return get_floating_n(n)
118 return "NC__%s" % (get_serial(), )
120 def get_floating_n(n):
121 """Get n floating nodes, see get_floating()."""
122 fs = {}
123 for x in range(1, n + 1):
124 fs[x] = get_floating()
125 return fs
127 def is_floating(node_name):
128 """Return whether the given node name is probably not connected;
129 generated from get_floating()."""
130 return node_name.startswith("NC_")
132 def chip_has_pins_available(pins_needed, pins):
133 """Return whether 'pins' has not-connected pins, all those required in pins_needed."""
134 for p in pins_needed:
135 if not is_floating(pins[p]):
136 return False
137 return True
139 def find_chip(chips, model_needed, pins_needed_options):
140 """Return an index into the chips array, and what pins, with the given model and the pins free.
142 pins_needed_options is a list of lists, of any acceptable set of pins to use."""
143 for i, chip in enumerate(chips):
144 model, pins = chip
145 if model != model_needed:
146 continue
148 for option_num, option in enumerate(pins_needed_options):
149 # Note: I do not yet check if the chip has pins that are used, but are assigned to
150 # the same node that is required. The pins must be unused.
151 if chip_has_pins_available(option, pins):
152 #print "* Found model %s with pins %s free: chip #%s" % (model_needed, option, i)
153 return i, option_num
155 raise "* No chips found with model %s and with pins %s free. Maybe you need more chips." % (model_needed,
156 pins_needed_options)
158 def find_pins_needed(pins):
159 """From a mod.pins[x] dict, return the pins needed for each model, for find_chip()"""
160 need = {}
161 for x in pins.values():
162 if type(x) == types.TupleType:
163 model, pin = x
164 if not need.has_key(model):
165 need[model] = []
167 need[model].append(pin)
168 elif type(x) == types.ListType:
169 for mp in x:
170 model, pin = mp
171 if not need.has_key(model):
172 need[model] = []
174 need[model].append(pin)
176 return need
178 def assign_part(chips, subckt_defns, extra, model_name, external_nodes, refdesg):
179 """Assign a given model to a physical chip, using the names of the external nodes.
181 chips: array of existing physical chips that can be assigned from
182 mod: logical model to map to, like 'tinv'
183 external_nodes: dict mapping internal nodes in the model, to external nodes in the world
186 mod = globals()[model_name]
187 subckt_lines = subckt_defns[model_name]
190 # Store new pin assignments
191 assignments = {}
192 for p in mod.parts_generated:
193 assignments[p] = {}
195 need_options = []
196 the_model = None
197 for p in mod.pins:
198 need = find_pins_needed(p)
199 if len(need) > 1:
200 raise "Sorry, more than one model is needed: %s, but only one is supported right now." % (need,)
201 if the_model is None:
202 the_model = need.keys()[0]
203 else:
204 if the_model != need.keys()[0]:
205 raise "Sorry, different models are needed: %s, but already decided on %s earlier. This is not yet supported." % (the_model, need[0])
207 need_options.append(need.values()[0])
209 #print "* Searching for model %s with one of pins: %s" % (the_model, need_options)
210 chip_num, option_num = find_chip(chips, the_model, need_options)
211 #print "* FOUND CHIP:",chip_num
212 #print "* WITH PINS (option #%s):" % (option_num,), mod.pins[option_num]
214 findings = []
215 for node_pin in combine_dicts(mod.global_pins, mod.pins[option_num]).iteritems():
216 node, pin = node_pin
218 if type(pin) == types.TupleType:
219 part, pin = pin
220 findings.append((node, part, pin))
221 elif type(pin) == types.ListType:
222 for pp in pin:
223 part, p = pp
224 findings.append((node, part, p))
225 else:
226 part = None
227 findings.append((node, part, pin))
229 for node, part, pin in findings:
230 #print "* %s -> %s:%s" % (node, part, pin)
232 if part is not None:
233 if node.startswith("$G_") or node == "0":
234 new_node = node # global node (LTspice)
235 elif external_nodes.has_key(node):
236 #sys.stderr.write("Mapping external node %s, map = %s\n" % (node, external_nodes))
237 new_node = external_nodes[node] # map internal to external node
238 else:
239 #sys.stderr.write("Mapping internal-only node %s\n" % (node,))
240 new_node = rewrite_node("", refdesg, node)
242 chips[chip_num][1][pin] = new_node
244 internal_only_nodes = {}
246 # Now place any ++additional parts++ (resistors, etc.) within the subcircuit model
247 # that connect to the chip, but are not part of the chip.
248 for line in subckt_lines:
249 words = line.split()
250 new_words = []
252 if words[0][0] == "M":
253 raise ("This line:\t%s\nIn the subcircuit '%s', has a MOSFET left " +
254 "over that wasn't converted. Probably it was meant to be converted to an IC? " +
255 "Comment out this line in %s if you are sure you want this, otherwise " +
256 "double-check the model definition for '%s', specifically, " +
257 "parts_consumed and parts_kept.\nAlso, check if the model was rewritten!") % (line, model_name, PROGRAM_NAME, model_name)
259 #name = "%s_%s_%s_%s" % (words[0], model_name, chip_num, refdesg)
260 name = "%s%s$%s" % (words[0][0], refdesg, words[0])
262 new_words.append(name)
264 # Replace internal nodes with external nodes.
265 for w in words[1:]:
266 if w in external_nodes.keys():
267 new_words.append(external_nodes[w])
268 elif is_floating(w):
269 # TODO ???
270 new_words.append(get_floating())
271 elif w[0].isdigit(): # value
272 new_words.append(w)
273 else:
274 if not internal_only_nodes.has_key(w):
275 internal_only_nodes[w] = "%s$%s$%s" % (w[0], refdesg, w)
276 new_words.append(internal_only_nodes[w])
278 # TODO: comment this out, if the above works
279 #raise "Could not map argument '%s' in subcircuit line %s, not found in %s" % (w, line, external_nodes)
281 extra.append(" ".join(new_words))
283 return chips, extra
285 def any_pins_used(pins):
286 """Return whether any of the pins on a chip are used. If False, chip isn't used."""
287 for p in pins.values():
288 if not is_floating(p):
289 return True
290 return False
292 def dump_chips(chips):
293 """Show the current chips and their pin connections."""
294 for i, c in enumerate(chips):
295 m, p = c
297 if not any_pins_used(p):
298 print "* Chip #%s - %s no pins used, skipping" % (i, m)
299 continue
301 print "* Chip #%s - %s pinout:" % (i, m)
302 for k, v in p.iteritems():
303 print "* \t%s: %s" % (k, v)
305 print "X_IC_%s_%s" % (i, m),
306 # Assumes all chips are 14-pin, arguments from 1 to 14 positional
307 for k in range(1, 14+1):
308 print p[k],
309 print m
310 print
312 def dump_extra(extra):
313 """Shows the extra, supporting subcircuit parts that support the IC and are part of the subcircuit."""
314 print "* Parts to support subcircuits:"
315 for e in extra:
316 print e
318 def make_node_mapping(internal, external):
319 """Make a node mapping dictionary from internal to external nodes.
320 Keys of the returned dictionary are internal nodes (of subcircuit), values are external."""
321 d = {}
322 d.update(zip(internal, external))
323 return d
325 def rewrite_refdesg(original, prefix):
326 """Prefix a reference designator, preserving the first character."""
327 return "%s%s$%s" % (original[0], prefix, original)
329 def rewrite_node(prefix, circuit_inside, original_node_name):
330 """Rewrite a node name inside a subcircuit, prefixing it with
331 prefix and the name of the circuit that it is inside (both
332 are optional)."""
334 # Globals never rewritten
335 if original_node_name.startswith("$G_") or original_node_name == "0":
336 return original_node_name
338 new_name = original_node_name
339 if circuit_inside:
340 new_name = "%s$%s" % (circuit_inside, new_name)
342 if prefix:
343 new_name = "%s$%s" % (prefix, new_name)
345 return new_name
347 def is_expandable_subcircuit(refdesg, model):
348 """Return whether the SPICE reference designator and model is
349 a) a subcircuit, b) _and_ it can be hierarchically expanded further."""
350 return refdesg[0] == 'X' and model not in SUBCIRCUITS_TO_MAP
352 def expand(subckt_defns, subckt_nodes, line, prefix, outer_nodes, outer_prefixes):
353 """Recursively expand a subcircuit instantiation if needed."""
354 words = line.split()
355 outer_refdesg = words[0]
356 outer_model = words[-1]
357 outer_args = words[1:-1]
359 sys.stderr.write("expand(%s,%s,%s,%s)\n" % (line, prefix, outer_nodes, outer_prefixes))
360 if is_expandable_subcircuit(outer_refdesg, outer_model):
361 nodes = make_node_mapping(subckt_nodes[outer_model], outer_args)
362 new_lines = []
363 new_lines.append(("* %s: Instance of subcircuit %s: %s" % (outer_refdesg, outer_model, " ".join(outer_args))))
365 # Notes that are internal to the subcircuit, not exposed in any ports
366 internal_only_nodes = {}
368 for sline in subckt_defns[outer_model]:
369 swords = sline.split()
370 inner_refdesg = swords[0]
371 inner_model = swords[-1]
372 inner_args = swords[1:-1]
374 if is_expandable_subcircuit(inner_refdesg, inner_model):
375 # Recursively expand subcircuits, to transistor-level subcircuits (SUBCIRCUITS_TO_MAP)
376 nodes_to_pass = {}
377 prefixes_to_pass = {}
378 for n in nodes:
379 if outer_nodes.has_key(nodes[n]):
380 nodes_to_pass[n] = outer_nodes[nodes[n]]
381 prefixes_to_pass[nodes_to_pass[n]] = outer_prefixes[nodes_to_pass[n]]
382 else:
383 nodes_to_pass[n] = nodes[n]
384 prefixes_to_pass[nodes_to_pass[n]] = prefix
385 # Only append separator if not empty
386 if prefix:
387 prefixes_to_pass[nodes_to_pass[n]] += "$"
389 #if len(prefixes_to_pass[nodes_to_pass[n]]) >= 1 and prefixes_to_pass[nodes_to_pass[n]][0] == '$':
390 # prefixes_to_pass[nodes_to_pass[n]] = prefixes_to_pass[nodes_to_pass[n]][0][1:]
391 # TODO: get the prefixes right!
392 sys.stderr.write("PASSING NODES: %s (outer=%s, inner=%s), outer_refdesg=%s, prefix=%s\n" % (nodes_to_pass, outer_nodes, nodes, outer_refdesg, prefix))
393 sys.stderr.write("\tPASSING PREFIXES: %s (outer=%s)\n" % (prefixes_to_pass, outer_prefixes))
394 new_lines.extend(expand(subckt_defns, subckt_nodes, sline, prefix +
395 "$" + outer_refdesg, nodes_to_pass, prefixes_to_pass))
396 else:
397 new_words = []
398 # Nest reference designator
399 new_words.append(rewrite_refdesg(inner_refdesg, prefix + "$" + outer_refdesg))
401 # Map internal to external nodes
402 for w in inner_args:
403 #print "****", word
404 if w in nodes.keys():
405 # XXX TODO: Need to follow up hierarchy! This leads to
406 # incomplete nets. For example, dtflop-ms_test.net maps:
408 # In nodes {'Q': 'Q', 'C': 'CLK', 'D': 'D'}, rewrite C -> CLK, prefix [correct]
409 # In nodes {'Q': 'Q', 'C': 'CLK', 'D': 'D'}, rewrite C -> CLK, prefix [correct]
411 # but because the 'nodes' dict is only for the parent, it
412 # doesn't map this correctly, leading to an unconnected node:
414 # In nodes {'OUT': '_C', 'IN': 'C'}, rewrite IN -> C, prefix Xflipflop [wrong]
416 # It should be CLK, not Xflipflop$C. These problems will occur
417 # with more nesting of subcircuits
418 if nodes[w] in outer_nodes:
419 # This is a port of this subcircuit, ascends hierarchy
420 new_words.append(outer_prefixes[outer_nodes[nodes[w]]] + outer_nodes[nodes[w]])
421 # TODO XXX: get the prefixes right! what if it isn't top-level?
422 #new_words.append(outer_nodes[nodes[w]])
423 sys.stderr.write("Node %s -> %s -> %s (outer nodes=%s, prefixes=%s) (prefix=%s, refdesgs=%s,%s)\n" %
424 (w, nodes[w], outer_nodes[nodes[w]], outer_nodes, outer_prefixes, prefix,
425 outer_refdesg, inner_refdesg))
426 else:
427 new_words.append(rewrite_node(prefix, "", nodes[w]))
429 elif is_floating(w):
430 # This is a port, but that is not connected on the outside, but
431 # still may be internally-connected so it needs a node name.
432 # Name it what it is called inside, hierarchically nested.
433 inner_node_map = make_node_mapping(inner_args, subckt_nodes[inner_model])
434 new_words.append(rewrite_node(prefix, outer_refdesg, inner_node_map[w]))
435 #print "Floating:",w," now=",new_words,"node map=",inner_node_map
436 elif w[0].isdigit():
437 new_words.append(w)
438 else:
439 # A signal only connected within this subcircuit, but not a port.
440 # Make a new node name for it and replace it.
441 if not internal_only_nodes.has_key(w):
442 internal_only_nodes[w] = rewrite_node(prefix, outer_refdesg, w)
443 #print "* sline: %s, Subcircuit %s, mapping internal-only node %s -> %s" % (sline, outer_model, w, internal_only_nodes[w])
445 new_words.append(internal_only_nodes[w])
446 #new_words.append(w)
447 #raise "Expanding subcircuit line '%s' (for line '%s'), couldn't map word %s, nodes=%s" % (sline, line, w, nodes)
449 new_words.append(inner_model)
451 new_lines.append(" ".join(new_words))
452 #new_lines.append("")
453 else:
454 new_lines = [line]
456 return new_lines
458 def test_flatten():
459 """Demonstrate flattening of a hierarchical subcircuit."""
460 if os.access("testcases", os.R_OK | os.X_OK):
461 testdir = "testcases"
462 else:
463 testdir = "."
465 i = 0
466 for case_in in sorted(os.listdir(testdir)):
467 if ".in" not in case_in or ".swp" in case_in:
468 continue
469 i += 1
470 case = case_in.replace(".in", "")
472 subckt_nodes, subckt_defns, toplevel = read_netlist("%s/%s.in" % (testdir, case))
474 actual_out = []
475 for line in toplevel:
476 actual_out.extend(expand(subckt_defns, subckt_nodes, line, "", {}, {}))
478 outfile = file("%s/%s.act" % (testdir, case), "wt")
479 for line in actual_out:
480 if line[0] == '*':
481 continue
482 outfile.write("%s\n" % (line,))
483 outfile.close()
485 if os.system("diff -u %s/%s.exp %s/%s.act" % (testdir, case, testdir, case)) != 0:
486 print "%2d. FAILED: %s" % (i, case)
487 raise SystemExit
488 else:
489 print "%2d. Passed: %s" % (i, case)
490 print "-" * 70
492 def test_assignment():
493 """Demonstrate subcircuit assignment."""
494 subckt_nodes, subckt_defns, toplevel = read_netlist("mux3-1_test.net")
496 mod_tinv, subckt_defns, pos2node_tinv = rewrite_subckt(subckt_defns, "tinv")
497 tg_tinv, subckt_defns, pos2node_tg = rewrite_subckt(subckt_defns, "tg")
500 # Available chips
501 chips = [
502 ("CD4007", get_floating(14) ),
503 ("CD4016", get_floating(14) ),
504 #("CD4007", get_floating(14) )
507 extra = []
508 chips, extra = assign_part(chips, subckt_defns, extra, "tinv",
510 "Vin": "IN_1",
511 "PTI_Out": "PTI_Out_1",
512 "NTI_Out": "NTI_Out_1",
513 "STI_Out": "STI_Out_1",
516 chips, extra = assign_part(chips, subckt_defns, extra, "tinv",
518 "Vin": "IN_2",
519 "PTI_Out": "PTI_Out_2",
520 "NTI_Out": "NTI_Out_2",
521 "STI_Out": "STI_Out_2",
524 chips, extra = assign_part(chips, subckt_defns, extra, "tg",
526 "IN_OUT": "IN_1",
527 "OUT_IN": "OUT_1",
528 "CONTROL": "CTRL_1",
530 chips, extra = assign_part(chips, subckt_defns, extra, "tg",
532 "IN_OUT": "IN_2",
533 "OUT_IN": "OUT_2",
534 "CONTROL": "CTRL_2",
536 chips, extra = assign_part(chips, subckt_defns, extra, "tg",
538 "IN_OUT": "IN_3",
539 "OUT_IN": "OUT_3",
540 "CONTROL": "CTRL_3",
542 chips, extra = assign_part(chips, subckt_defns, extra, "tg",
544 "IN_OUT": "IN_4",
545 "OUT_IN": "OUT_4",
546 "CONTROL": "CTRL_4",
549 dump_chips(chips)
550 dump_extra(extra)
552 def usage():
553 print """usage: %s input-filename [output-filename | -p]
555 input-filename A transistor-level SPICE netlist (.net)
556 output-filename Chip-level SPICE netlist (.net2)
558 If output-filename is omitted, input-filename is used but
559 with '2' appended, so .net becomes .net2 by convention.
561 Either filenames can be "-" for stdin or stdout, respectively.
563 The -p flag, instead of an output filename will also run pads.py
564 so that both .net2 and .pads files will be generated.
565 """ % (PROGRAM_NAME, )
566 raise SystemExit
568 def main():
569 if len(sys.argv) < 2:
570 usage()
571 input_filename = sys.argv[1]
573 generate_pads = len(sys.argv) > 2 and sys.argv[2] == "-p"
575 subckt_nodes, subckt_defns, toplevel = read_netlist(input_filename)
577 # Redirect stdout to output file
578 if len(sys.argv) > 2 and sys.argv[2] != "-p":
579 output_filename = sys.argv[2]
580 else:
581 output_filename = input_filename + "2"
583 if output_filename != "-":
584 sys.stdout = file(output_filename, "wt")
586 print "* Chip-level netlist, converted from %s by %s" % (input_filename, PROGRAM_NAME)
588 # Circuits to rewrite need to be loaded first. Any transistor-level circuits
589 # you want to replace with ICs, are loaded here.
590 modules = pos2node = {}
591 for s in SUBCIRCUITS_CAN_MAP:
592 if subckt_defns.has_key(s):
593 modules[s], subckt_defns, pos2node[s] = rewrite_subckt(subckt_defns, s)
595 # First semi-flatten the circuit
596 flat_toplevel = []
597 for line in toplevel:
598 flat_toplevel.extend((expand(subckt_defns, subckt_nodes, line, "", {}, {})))
600 print "* Flattened top-level, before part assignment:"
601 for f in flat_toplevel:
602 print "** %s" % (f,)
603 print "* Begin converted circuit"
604 print
606 # Available chips
607 chips = [
608 ("CD4007", get_floating(14) ),
609 ("CD4007", get_floating(14) ),
610 ("CD4007", get_floating(14) ),
611 ("CD4007", get_floating(14) ),
612 ("CD4007", get_floating(14) ),
613 ("CD4007", get_floating(14) ),
614 ("CD4007", get_floating(14) ),
615 ("CD4016", get_floating(14) ),
618 extra = []
619 for line in flat_toplevel:
620 words = line.split()
621 if words[0][0] == 'X':
622 refdesg = words[0]
623 model = words[-1]
624 args = words[1:-1]
626 #print "MODEL=%s, args=%s" % (model, args)
628 #print subckt_defns[model]
629 #print subckt_nodes[model]
631 if model in SUBCIRCUITS_CAN_MAP:
632 nodes = make_node_mapping(subckt_nodes[model], args)
633 #print nodes
634 chips, extra = assign_part(chips, subckt_defns, extra, model, nodes, refdesg)
635 else:
636 raise "Cannot synthesize model: %s, line: %s" % (model, line)
637 else:
638 print line
640 dump_chips(chips)
641 dump_extra(extra)
643 sys.stdout = sys.stderr
645 if generate_pads:
646 import os
647 os.system("python pads.py %s" % (output_filename,))
649 if __name__ == "__main__":
650 if len(sys.argv) > 1 and sys.argv[1] == "-t":
651 test_flatten()
652 raise SystemExit
653 #test_assignment()
654 main()