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 # 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
)
33 def read_netlist(filename
):
34 """Read a SPICE input deck, returning subcircuit nodes and definitions."""
39 f
= file(filename
, "rt")
51 if line
.startswith(".subckt"):
56 subckt_nodes
[name
] = nodes
57 elif line
.startswith(".ends"):
61 if not subckt_defns
.has_key(name
):
62 subckt_defns
[name
] = []
63 subckt_defns
[name
].append(line
)
65 # Top-level elements, skip blank lines, commands and comments
66 if len(line
.strip()) != 0 and line
[0] not in ('.', '*'):
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."""
80 for line
in subckt_defns
[s
]:
85 if name
in mod
.parts_consumed
:
88 elif name
in mod
.parts_kept
:
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
95 for i
in range(0, len(mod
.nodes
)):
96 pos2node
[mod
.nodes
[i
]] = i
99 subckt_defns
[s
] = lines
101 return mod
, subckt_defns
, pos2node
105 """Get a unique, increasing number."""
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."""
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()."""
123 for x
in range(1, n
+ 1):
124 fs
[x
] = get_floating()
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
]):
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
):
145 if model
!= model_needed
:
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)
155 raise "* No chips found with model %s and with pins %s free. Maybe you need more chips." % (model_needed
,
158 def find_pins_needed(pins
):
159 """From a mod.pins[x] dict, return the pins needed for each model, for find_chip()"""
161 for x
in pins
.values():
162 if type(x
) == types
.TupleType
:
164 if not need
.has_key(model
):
167 need
[model
].append(pin
)
168 elif type(x
) == types
.ListType
:
171 if not need
.has_key(model
):
174 need
[model
].append(pin
)
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
192 for p
in mod
.parts_generated
:
198 need
= find_pins_needed(p
)
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]
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]
215 for node_pin
in combine_dicts(mod
.global_pins
, mod
.pins
[option_num
]).iteritems():
218 if type(pin
) == types
.TupleType
:
220 findings
.append((node
, part
, pin
))
221 elif type(pin
) == types
.ListType
:
224 findings
.append((node
, part
, p
))
227 findings
.append((node
, part
, pin
))
229 for node
, part
, pin
in findings
:
230 #print "* %s -> %s:%s" % (node, part, pin)
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
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
:
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.
266 if w
in external_nodes
.keys():
267 new_words
.append(external_nodes
[w
])
270 new_words
.append(get_floating())
271 elif w
[0].isdigit(): # value
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
))
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
):
292 def dump_chips(chips
):
293 """Show the current chips and their pin connections."""
294 for i
, c
in enumerate(chips
):
297 if not any_pins_used(p
):
298 print "* Chip #%s - %s no pins used, skipping" % (i
, m
)
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):
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:"
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."""
322 d
.update(zip(internal
, external
))
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
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
340 new_name
= "%s$%s" % (circuit_inside
, new_name
)
343 new_name
= "%s$%s" % (prefix
, 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."""
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
)
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)
377 prefixes_to_pass
= {}
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
]]
383 nodes_to_pass
[n
] = nodes
[n
]
384 prefixes_to_pass
[nodes_to_pass
[n
]] = prefix
385 # Only append separator if not empty
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
))
398 # Nest reference designator
399 new_words
.append(rewrite_refdesg(inner_refdesg
, prefix
+ "$" + outer_refdesg
))
401 # Map internal to external nodes
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
))
427 new_words
.append(rewrite_node(prefix
, "", nodes
[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
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
])
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("")
459 """Demonstrate flattening of a hierarchical subcircuit."""
460 if os
.access("testcases", os
.R_OK | os
.X_OK
):
461 testdir
= "testcases"
466 for case_in
in sorted(os
.listdir(testdir
)):
467 if ".in" not in case_in
or ".swp" in case_in
:
470 case
= case_in
.replace(".in", "")
472 subckt_nodes
, subckt_defns
, toplevel
= read_netlist("%s/%s.in" % (testdir
, case
))
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
:
482 outfile
.write("%s\n" % (line
,))
485 if os
.system("diff -u %s/%s.exp %s/%s.act" % (testdir
, case
, testdir
, case
)) != 0:
486 print "%2d. FAILED: %s" % (i
, case
)
489 print "%2d. Passed: %s" % (i
, case
)
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")
502 ("CD4007", get_floating(14) ),
503 ("CD4016", get_floating(14) ),
504 #("CD4007", get_floating(14) )
508 chips
, extra
= assign_part(chips
, subckt_defns
, extra
, "tinv",
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",
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",
530 chips
, extra
= assign_part(chips
, subckt_defns
, extra
, "tg",
536 chips
, extra
= assign_part(chips
, subckt_defns
, extra
, "tg",
542 chips
, extra
= assign_part(chips
, subckt_defns
, extra
, "tg",
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
, )
569 if len(sys
.argv
) < 2:
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]
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
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
:
603 print "* Begin converted circuit"
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) ),
619 for line
in flat_toplevel
:
621 if words
[0][0] == 'X':
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
)
634 chips
, extra
= assign_part(chips
, subckt_defns
, extra
, model
, nodes
, refdesg
)
636 raise "Cannot synthesize model: %s, line: %s" % (model
, line
)
643 sys
.stdout
= sys
.stderr
647 os
.system("python pads.py %s" % (output_filename
,))
649 if __name__
== "__main__":
650 if len(sys
.argv
) > 1 and sys
.argv
[1] == "-t":