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.
21 PROGRAM_NAME
= "bb.py"
23 # Start chip numbering at this value.
26 # Subcircuits to map that should be mapped physical ICs
27 SUBCIRCUITS_TO_MAP
= ('tg', 'tinv', 'tnor', 'tnor3', 'tnand', 'tnand3', 'sp3t-1', 'sp3t-2', 'sp3t-3')
28 SUBCIRCUITS_CAN_MAP
= ('tg', 'tinv', 'tnor', 'tnand') # subcircuits we actually can map to ICs, as of yet
29 SUBCIRCUITS_PASS
= ('sp3t-1', 'sp3t-2', 'sp3t-3') # pass unchanged to pads.py
31 def combine_dicts(dict1
, dict2
):
32 """Combine two dictionaries; dict2 takes priority over dict1."""
33 ret
= copy
.deepcopy(dict1
)
37 def read_netlist(filename
):
38 """Read a SPICE input deck, returning subcircuit nodes and definitions."""
43 f
= file(filename
, "rt")
55 if line
.startswith(".subckt"):
60 subckt_nodes
[name
] = nodes
61 elif line
.startswith(".ends"):
65 if not subckt_defns
.has_key(name
):
66 subckt_defns
[name
] = []
67 subckt_defns
[name
].append(line
)
69 # Top-level elements, skip blank lines, commands and comments
70 if len(line
.strip()) != 0 and line
[0] not in ('.', '*'):
73 return subckt_nodes
, subckt_defns
, toplevel
75 def rewrite_subckt(subckt_defns
, s
):
76 """Partially rewrite subcircuit 's', using rules from a module of the same name.
78 Removes parts in mod.parts_consumed, keeps parts in mod.parts_kept.
80 Does not generate any new parts."""
84 for line
in subckt_defns
[s
]:
89 if name
in mod
.parts_consumed
:
92 elif name
in mod
.parts_kept
:
95 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
)
97 # Map node positions in arguments list (subckt invocation, X..), to node names
99 for i
in range(0, len(mod
.nodes
)):
100 pos2node
[mod
.nodes
[i
]] = i
103 subckt_defns
[s
] = lines
105 return mod
, subckt_defns
, pos2node
109 """Get a unique, increasing number."""
115 def get_floating(n
=None):
116 """Return a unique disconnected (floating) node name.
118 If n is given, return a dict of n floating node names. Otherwise returns one in a string."""
120 return get_floating_n(n
)
122 return "NC__%s" % (get_serial(), )
124 def get_floating_n(n
):
125 """Get n floating nodes, see get_floating()."""
127 for x
in range(1, n
+ 1):
128 fs
[x
] = get_floating()
131 def is_floating(node_name
):
132 """Return whether the given node name is probably not connected;
133 generated from get_floating()."""
134 return node_name
.startswith("NC_")
136 def chip_has_pins_available(pins_needed
, pins
):
137 """Return whether 'pins' has not-connected pins, all those required in pins_needed."""
138 for p
in pins_needed
:
139 if not is_floating(pins
[p
]):
143 # This program doesn't thoroughly parse SPICE cards, so you must
144 # give it parameters that are not nodes, to not map at all.
145 # For example, '12k' for the 12k resistors. TODO: better parsing
146 def find_chip(chips
, model_needed
, pins_needed_options
):
147 """Return an index into the chips array, and what pins, with the given model and the pins free.
149 pins_needed_options is a list of lists, of any acceptable set of pins to use.
151 A new chip is added if none are found. """
153 result
= find_chip_no_add(chips
, model_needed
, pins_needed_options
)
154 if result
is not None:
157 # No chips found with model model_needed and with pins free. Need more chips.
158 if model_needed
not in ("CD4016", "CD4007"):
159 raise "Model %s not known, it is not CD4016 nor CD4007, not recognized!" % (model_needed
,)
162 # Assume all are 14-pin chips
163 chips
.append((model_needed
, get_floating(14)))
165 result
= find_chip_no_add(chips
, model_needed
, pins_needed_options
)
166 if result
is not None:
169 raise "Tried to find model %s with pins %s free, then added a new chip but couldn't find it!" % (
170 model_needed
, pins_needed_options
)
172 def find_chip_no_add(chips
, model_needed
, pins_needed_options
):
173 """Find chip to use (see find_chip), but return None if not found instead of adding."""
174 for i
, chip
in enumerate(chips
):
176 if model
!= model_needed
:
179 for option_num
, option
in enumerate(pins_needed_options
):
180 # Note: I do not yet check if the chip has pins that are used, but are assigned to
181 # the same node that is required. The pins must be unused.
182 if chip_has_pins_available(option
, pins
):
183 #print "* Found model %s with pins %s free: chip #%s" % (model_needed, option, i)
187 def find_pins_needed(pins
):
188 """From a mod.pins[x] dict, return the pins needed for each model, for find_chip()"""
190 for x
in pins
.values():
191 if type(x
) == types
.TupleType
:
193 if not need
.has_key(model
):
196 need
[model
].append(pin
)
197 elif type(x
) == types
.ListType
:
200 if not need
.has_key(model
):
203 need
[model
].append(pin
)
207 def assign_part(chips
, subckt_defns
, extra
, model_name
, external_nodes
, refdesg
):
208 """Assign a given model to a physical chip, using the names of the external nodes.
210 chips: array of existing physical chips that can be assigned from
211 mod: logical model to map to, like 'tinv'
212 external_nodes: dict mapping internal nodes in the model, to external nodes in the world
215 mod
= globals()[model_name
]
216 subckt_lines
= subckt_defns
[model_name
]
219 # Store new pin assignments
221 for p
in mod
.parts_generated
:
227 need
= find_pins_needed(p
)
229 raise "Sorry, more than one model is needed: %s, but only one is supported right now." % (need
,)
230 if the_model
is None:
231 the_model
= need
.keys()[0]
233 if the_model
!= need
.keys()[0]:
234 raise "Sorry, different models are needed: %s, but already decided on %s earlier. This is not yet supported." % (the_model
, need
[0])
236 need_options
.append(need
.values()[0])
238 #print "* Searching for model %s with one of pins: %s" % (the_model, need_options)
239 chip_num
, option_num
= find_chip(chips
, the_model
, need_options
)
240 #print "* FOUND CHIP:",chip_num
241 #print "* WITH PINS (option #%s):" % (option_num,), mod.pins[option_num]
244 for node_pin
in combine_dicts(mod
.global_pins
, mod
.pins
[option_num
]).iteritems():
247 if type(pin
) == types
.TupleType
:
249 findings
.append((node
, part
, pin
))
250 elif type(pin
) == types
.ListType
:
253 findings
.append((node
, part
, p
))
256 findings
.append((node
, part
, pin
))
258 for node
, part
, pin
in findings
:
259 #print "* %s -> %s:%s" % (node, part, pin)
262 if node
.startswith("$G_") or node
== "0":
263 new_node
= node
# global node (LTspice)
264 elif external_nodes
.has_key(node
):
265 #sys.stderr.write("Mapping external node %s, map = %s\n" % (node, external_nodes))
266 new_node
= external_nodes
[node
] # map internal to external node
268 #sys.stderr.write("Mapping internal-only node %s\n" % (node,))
269 # 'refdesg' here is prefixed with X, for a subcircuit instantation,
270 # but we only need the subcircuit prefix for a node, without the
272 assert refdesg
[0:2] == 'X$', "Assumed refdesg %s began with X$, but it didn't" % (refdesg
,)
273 refdesg_without_letter
= refdesg
[2:]
274 new_node
= rewrite_node("", refdesg_without_letter
, node
)
275 sys
.stderr
.write("Rewriting to node = %s\n" % (new_node
,))
277 sys
.stderr
.write("Adding to chips: %s\n" % (new_node
,))
278 chips
[chip_num
][1][pin
] = new_node
280 internal_only_nodes
= {}
282 # Now place any ++additional parts++ (resistors, etc.) within the subcircuit model
283 # that connect to the chip, but are not part of the chip.
284 for line
in subckt_lines
:
288 if words
[0][0] == "M":
289 raise ("This line:\t%s\nIn the subcircuit '%s', has a MOSFET left " +
290 "over that wasn't converted. Probably it was meant to be converted to an IC? " +
291 "Comment out this line in %s if you are sure you want this, otherwise " +
292 "double-check the model definition for '%s', specifically, " +
293 "parts_consumed and parts_kept.\nAlso, check if the model was rewritten!") % (line
, model_name
, PROGRAM_NAME
, model_name
)
295 #name = "%s_%s_%s_%s" % (words[0], model_name, chip_num, refdesg)
296 name
= "%s%s$%s" % (words
[0][0], refdesg
, words
[0])
298 new_words
.append(name
)
301 model_name
= words
[-1]
303 # Replace internal nodes with external nodes.
305 if w
in external_nodes
.keys():
306 new_words
.append(external_nodes
[w
])
309 new_words
.append(get_floating())
311 if not internal_only_nodes
.has_key(w
):
312 #internal_only_nodes[w] = "%s$%s$%s" % (w[0], refdesg, w)
313 assert refdesg
[0:2] == 'X$', "Reference designator %s expected to begin with X$, but didn't" % (refdesg
,)
314 refdesg_without_letter
= refdesg
[2:]
315 internal_only_nodes
[w
] = rewrite_node("", refdesg_without_letter
, w
)
316 new_words
.append(internal_only_nodes
[w
])
318 # TODO: comment this out, if the above works
319 #raise "Could not map argument '%s' in subcircuit line %s, not found in %s" % (w, line, external_nodes)
321 new_words
.append(model_name
)
323 extra
.append(" ".join(new_words
))
327 def any_pins_used(pins
):
328 """Return whether any of the pins on a chip are used. If False, chip isn't used."""
329 for p
in pins
.values():
330 if not is_floating(p
):
334 def dump_chips(chips
):
335 """Show the current chips and their pin connections."""
336 for i
, c
in enumerate(chips
):
339 if not any_pins_used(p
):
340 print "* Chip #%s - %s no pins used, skipping" % (i
+ CHIP_NO_START
, m
)
343 print "* Chip #%s - %s pinout:" % (i
+ CHIP_NO_START
, m
)
344 for k
, v
in p
.iteritems():
345 print "* \t%s: %s" % (k
, v
)
347 print "IC_%s_%s" % (m
, i
+ CHIP_NO_START
),
348 # Assumes all chips are 14-pin, arguments from 1 to 14 positional
349 for k
in range(1, 14+1):
354 def dump_extra(extra
):
355 """Shows the extra, supporting subcircuit parts that support the IC and are part of the subcircuit."""
356 print "* Parts to support subcircuits:"
360 def make_node_mapping(internal
, external
):
361 """Make a node mapping dictionary from internal to external nodes.
362 Keys of the returned dictionary are internal nodes (of subcircuit), values are external."""
364 d
.update(zip(internal
, external
))
367 def rewrite_refdesg(original
, prefix
):
368 """Prefix a reference designator, preserving the first character."""
369 new_refdesg
= "%s%s$%s" % (original
[0], prefix
, original
)
371 #sys.stderr.write("@ rewrite_refdesg = %s\n" % (new_refdesg,))
374 def rewrite_node(prefix
, circuit_inside
, original_node_name
):
375 """Rewrite a node name inside a subcircuit, prefixing it with
376 prefix and the name of the circuit that it is inside (both
379 # Globals never rewritten
380 if original_node_name
.startswith("$G_") or original_node_name
== "0":
381 return original_node_name
383 new_name
= original_node_name
385 new_name
= "%s$%s" % (circuit_inside
, new_name
)
388 new_name
= "%s$%s" % (prefix
, new_name
)
390 # Nodes don't need to begin with spurious '$'s (can happen if no prefix)
391 if new_name
[0] == '$':
392 new_name
= new_name
[1:]
394 #sys.stderr.write("@@ rewrite_node = %s\n" % (new_name,))
397 def is_expandable_subcircuit(refdesg
, model
):
398 """Return whether the SPICE reference designator and model is
399 a) a subcircuit, b) _and_ it can be hierarchically expanded further."""
400 return refdesg
[0] == 'X' and model
not in SUBCIRCUITS_TO_MAP
402 def expand(subckt_defns
, subckt_nodes
, line
, prefix
, outer_nodes
, outer_prefixes
):
403 """Recursively expand a subcircuit instantiation if needed."""
405 outer_refdesg
= words
[0]
406 outer_model
= words
[-1]
407 outer_args
= words
[1:-1]
409 sys
.stderr
.write("expand(%s,%s,%s,%s)\n" % (line
, prefix
, outer_nodes
, outer_prefixes
))
410 if is_expandable_subcircuit(outer_refdesg
, outer_model
):
411 nodes
= make_node_mapping(subckt_nodes
[outer_model
], outer_args
)
413 new_lines
.append(("* %s: Instance of subcircuit %s: %s" % (outer_refdesg
, outer_model
, " ".join(outer_args
))))
415 # Notes that are internal to the subcircuit, not exposed in any ports
416 internal_only_nodes
= {}
418 for sline
in subckt_defns
[outer_model
]:
419 swords
= sline
.split()
420 inner_refdesg
= swords
[0]
421 inner_model
= swords
[-1]
422 inner_args
= swords
[1:-1]
424 if is_expandable_subcircuit(inner_refdesg
, inner_model
):
425 # Recursively expand subcircuits, to transistor-level subcircuits (SUBCIRCUITS_TO_MAP)
427 prefixes_to_pass
= {}
429 if outer_nodes
.has_key(nodes
[n
]):
430 nodes_to_pass
[n
] = outer_nodes
[nodes
[n
]]
431 prefixes_to_pass
[nodes_to_pass
[n
]] = outer_prefixes
[nodes_to_pass
[n
]]
433 nodes_to_pass
[n
] = nodes
[n
]
434 prefixes_to_pass
[nodes_to_pass
[n
]] = prefix
435 # Only append separator if not empty
437 prefixes_to_pass
[nodes_to_pass
[n
]] += "$"
439 # Chop '$' if begins with it
440 if len(prefixes_to_pass
[nodes_to_pass
[n
]]) >= 1 and prefixes_to_pass
[nodes_to_pass
[n
]][0] == '$':
441 prefixes_to_pass
[nodes_to_pass
[n
]] = prefixes_to_pass
[nodes_to_pass
[n
]][1:]
442 # TODO: get the prefixes right!
443 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
))
444 sys
.stderr
.write("\tPASSING PREFIXES: %s (outer=%s)\n" % (prefixes_to_pass
, outer_prefixes
))
445 new_lines
.extend(expand(subckt_defns
, subckt_nodes
, sline
, prefix
+
446 "$" + outer_refdesg
, nodes_to_pass
, prefixes_to_pass
))
449 # Nest reference designator
450 new_words
.append(rewrite_refdesg(inner_refdesg
, prefix
+ "$" + outer_refdesg
))
452 # Map internal to external nodes
455 if w
in nodes
.keys():
456 # XXX TODO: Need to follow up hierarchy! This leads to
457 # incomplete nets. For example, dtflop-ms_test.net maps:
459 # In nodes {'Q': 'Q', 'C': 'CLK', 'D': 'D'}, rewrite C -> CLK, prefix [correct]
460 # In nodes {'Q': 'Q', 'C': 'CLK', 'D': 'D'}, rewrite C -> CLK, prefix [correct]
462 # but because the 'nodes' dict is only for the parent, it
463 # doesn't map this correctly, leading to an unconnected node:
465 # In nodes {'OUT': '_C', 'IN': 'C'}, rewrite IN -> C, prefix Xflipflop [wrong]
467 # It should be CLK, not Xflipflop$C. These problems will occur
468 # with more nesting of subcircuits
469 if nodes
[w
] in outer_nodes
:
470 # This is a port of this subcircuit, ascends hierarchy
471 new_words
.append(outer_prefixes
[outer_nodes
[nodes
[w
]]] + outer_nodes
[nodes
[w
]])
472 # TODO XXX: get the prefixes right! what if it isn't top-level?
473 #new_words.append(outer_nodes[nodes[w]])
474 sys
.stderr
.write("Node %s -> %s -> %s (outer nodes=%s, prefixes=%s) (prefix=%s, refdesgs=%s,%s)\n" %
475 (w
, nodes
[w
], outer_nodes
[nodes
[w
]], outer_nodes
, outer_prefixes
, prefix
,
476 outer_refdesg
, inner_refdesg
))
478 new_words
.append(rewrite_node(prefix
, "", nodes
[w
]))
481 # This is a port, but that is not connected on the outside, but
482 # still may be internally-connected so it needs a node name.
483 # Name it what it is called inside, hierarchically nested.
484 inner_node_map
= make_node_mapping(inner_args
, subckt_nodes
[inner_model
])
485 new_words
.append(rewrite_node(prefix
, outer_refdesg
, inner_node_map
[w
]))
486 #print "Floating:",w," now=",new_words,"node map=",inner_node_map
488 # A signal only connected within this subcircuit, but not a port.
489 # Make a new node name for it and replace it.
490 if not internal_only_nodes
.has_key(w
):
491 internal_only_nodes
[w
] = rewrite_node(prefix
, outer_refdesg
, w
)
492 #print "* sline: %s, Subcircuit %s, mapping internal-only node %s -> %s" % (sline, outer_model, w, internal_only_nodes[w])
494 new_words
.append(internal_only_nodes
[w
])
496 #raise "Expanding subcircuit line '%s' (for line '%s'), couldn't map word %s, nodes=%s" % (sline, line, w, nodes)
498 new_words
.append(inner_model
)
500 new_lines
.append(" ".join(new_words
))
501 #new_lines.append("")
508 """Demonstrate flattening of a hierarchical subcircuit."""
509 if os
.access("testcases", os
.R_OK | os
.X_OK
):
510 testdir
= "testcases"
515 for case_in
in sorted(os
.listdir(testdir
)):
516 if ".in" not in case_in
or ".swp" in case_in
:
518 case
= case_in
.replace(".in", "")
520 subckt_nodes
, subckt_defns
, toplevel
= read_netlist("%s/%s.in" % (testdir
, case
))
523 for line
in toplevel
:
524 actual_out
.extend(expand(subckt_defns
, subckt_nodes
, line
, "", {}, {}))
526 outfile
= file("%s/%s.act" % (testdir
, case
), "wt")
527 for line
in actual_out
:
530 outfile
.write("%s\n" % (line
,))
533 if os
.system("diff -u %s/%s.exp %s/%s.act" % (testdir
, case
, testdir
, case
)) != 0:
534 print "%2d. FAILED: %s" % (i
, case
)
537 print "%2d. Passed: %s" % (i
, case
)
541 def test_assignment():
542 """Demonstrate subcircuit assignment."""
543 subckt_nodes
, subckt_defns
, toplevel
= read_netlist("mux3-1_test.net")
545 mod_tinv
, subckt_defns
, pos2node_tinv
= rewrite_subckt(subckt_defns
, "tinv")
546 tg_tinv
, subckt_defns
, pos2node_tg
= rewrite_subckt(subckt_defns
, "tg")
551 ("CD4007", get_floating(14) ),
552 ("CD4016", get_floating(14) ),
553 #("CD4007", get_floating(14) )
557 chips
, extra
= assign_part(chips
, subckt_defns
, extra
, "tinv",
560 "PTI_Out": "PTI_Out_1",
561 "NTI_Out": "NTI_Out_1",
562 "STI_Out": "STI_Out_1",
565 chips
, extra
= assign_part(chips
, subckt_defns
, extra
, "tinv",
568 "PTI_Out": "PTI_Out_2",
569 "NTI_Out": "NTI_Out_2",
570 "STI_Out": "STI_Out_2",
573 chips
, extra
= assign_part(chips
, subckt_defns
, extra
, "tg",
579 chips
, extra
= assign_part(chips
, subckt_defns
, extra
, "tg",
585 chips
, extra
= assign_part(chips
, subckt_defns
, extra
, "tg",
591 chips
, extra
= assign_part(chips
, subckt_defns
, extra
, "tg",
602 print """usage: %s input-filename [output-filename | -p]
604 input-filename A transistor-level SPICE netlist (.net)
605 output-filename Chip-level SPICE netlist (.net2)
607 If output-filename is omitted, input-filename is used but
608 with '2' appended, so .net becomes .net2 by convention.
610 Either filenames can be "-" for stdin or stdout, respectively.
612 The -p flag, instead of an output filename will also run pads.py
613 so that both .net2 and .pads files will be generated.
614 """ % (PROGRAM_NAME
, )
618 if len(sys
.argv
) < 2:
620 input_filename
= sys
.argv
[1]
622 generate_pads
= len(sys
.argv
) > 2 and sys
.argv
[2] == "-p"
624 subckt_nodes
, subckt_defns
, toplevel
= read_netlist(input_filename
)
626 # Redirect stdout to output file
627 if len(sys
.argv
) > 2 and sys
.argv
[2] != "-p":
628 output_filename
= sys
.argv
[2]
630 output_filename
= input_filename
+ "2"
632 if output_filename
!= "-":
633 sys
.stdout
= file(output_filename
, "wt")
635 print "* Chip-level netlist, converted from %s by %s" % (input_filename
, PROGRAM_NAME
)
637 # Circuits to rewrite need to be loaded first. Any transistor-level circuits
638 # you want to replace with ICs, are loaded here.
639 modules
= pos2node
= {}
640 for s
in SUBCIRCUITS_CAN_MAP
:
641 if subckt_defns
.has_key(s
):
642 modules
[s
], subckt_defns
, pos2node
[s
] = rewrite_subckt(subckt_defns
, s
)
644 # First semi-flatten the circuit
646 for line
in toplevel
:
647 flat_toplevel
.extend((expand(subckt_defns
, subckt_nodes
, line
, "", {}, {})))
649 print "* Flattened top-level, before part assignment:"
650 for f
in flat_toplevel
:
652 print "* Begin converted circuit"
659 for line
in flat_toplevel
:
661 if words
[0][0] == 'X':
666 #print "MODEL=%s, args=%s" % (model, args)
668 #print subckt_defns[model]
669 #print subckt_nodes[model]
671 if model
in SUBCIRCUITS_CAN_MAP
:
672 nodes
= make_node_mapping(subckt_nodes
[model
], args
)
674 chips
, extra
= assign_part(chips
, subckt_defns
, extra
, model
, nodes
, refdesg
)
675 elif model
in SUBCIRCUITS_PASS
:
678 raise "Cannot synthesize model: %s, line: %s" % (model
, line
)
685 sys
.stdout
= sys
.stderr
689 os
.system("python pads.py %s" % (output_filename
,))
691 if __name__
== "__main__":
692 if len(sys
.argv
) > 1 and sys
.argv
[1] == "-t":