Better names for additional subcircuit parts (currently resistors), but still is...
[trinary.git] / bb.py
blob3b7e8729f3ef9e29cba12fc556a77eaf490b087a
1 #!/usr/bin/env python
2 # Created:20080411
3 # By Jeff Connelly
5 # Breadboard/circuit pin layout program
7 import copy
8 import types
9 import time
11 import tg
12 import tinv
14 PROGRAM_NAME = "bb.py"
16 def combine_dicts(dict1, dict2):
17 """Combine two dictionaries; dict2 takes priority over dict1."""
18 ret = copy.deepcopy(dict1)
19 ret.update(dict2)
20 return ret
22 def read_netlist(filename):
23 """Read a SPICE input deck, returning subcircuit nodes and definitions."""
25 f = file(filename, "rt")
26 subckt_nodes = {}
27 subckt_defns = {}
28 toplevel = []
29 name = None
30 while True:
31 line = f.readline()
32 if len(line) == 0:
33 break
35 line = line.strip()
37 if line.startswith(".subckt"):
38 words = line.split()
39 name = words[1]
40 nodes = words[2:]
42 subckt_nodes[name] = nodes
43 elif line.startswith(".ends"):
44 name = None
45 else:
46 if name is not None:
47 if not subckt_defns.has_key(name):
48 subckt_defns[name] = []
49 subckt_defns[name].append(line)
50 else:
51 # Top-level elements, skip blank lines, commands and comments
52 if len(line.strip()) != 0 and line[0] not in ('.', '*'):
53 toplevel.append(line)
55 print "* Converted from netlist %s by %s on %s" % (filename, PROGRAM_NAME, time.asctime())
56 return subckt_nodes, subckt_defns, toplevel
58 def rewrite_subckt(subckt_defns, s):
59 """Partially rewrite subcircuit 's', using rules from a module of the same name.
61 Removes parts in mod.parts_consumed, keeps parts in mod.parts_kept.
63 Does not generate any new parts."""
65 mod = globals()[s]
66 lines = []
67 for line in subckt_defns[s]:
68 words = line.split()
69 name = words[0]
70 args = words[1:]
72 if name in mod.parts_consumed:
73 # Skip this line
74 pass
75 elif name in mod.parts_kept:
76 lines.append(line)
77 else:
78 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)
80 # Map node positions in arguments list (subckt invocation, X..), to node names
81 pos2node = {}
82 for i in range(0, len(mod.nodes)):
83 pos2node[mod.nodes[i]] = i
85 # Rewrite
86 subckt_defns[s] = lines
88 return mod, subckt_defns, pos2node
90 next_serial = -1
91 def get_serial():
92 """Get a unique, increasing number."""
93 global next_serial
95 next_serial += 1
96 return next_serial
98 def get_floating(n=None):
99 """Return a unique disconnected (floating) node name.
101 If n is given, return a dict of n floating node names. Otherwise returns one in a string."""
102 if n is not None:
103 return get_floating_n(n)
105 return "NC_%s_" % (get_serial(), )
107 def get_floating_n(n):
108 """Get n floating nodes, see get_floating()."""
109 fs = {}
110 for x in range(1, n + 1):
111 fs[x] = get_floating()
112 return fs
114 def is_floating(node_name):
115 """Return whether the given node name is probably not connected;
116 generated from get_floating()."""
117 return node_name.startswith("NC_")
119 def chip_has_pins_available(pins_needed, pins):
120 """Return whether 'pins' has not-connected pins, all those required in pins_needed."""
121 for p in pins_needed:
122 if not is_floating(pins[p]):
123 return False
124 return True
126 def find_chip(chips, model_needed, pins_needed_options):
127 """Return an index into the chips array, and what pins, with the given model and the pins free.
129 pins_needed_options is a list of lists, of any acceptable set of pins to use."""
130 for i, chip in enumerate(chips):
131 model, pins = chip
132 if model != model_needed:
133 continue
135 for option_num, option in enumerate(pins_needed_options):
136 # Note: I do not yet check if the chip has pins that are used, but are assigned to
137 # the same node that is required. The pins must be unused.
138 if chip_has_pins_available(option, pins):
139 #print "* Found model %s with pins %s free: chip #%s" % (model_needed, option, i)
140 return i, option_num
142 raise "* No chips found with model %s and with pins %s free. Maybe you need more chips." % (model_needed,
143 pins_needed_options)
145 def find_pins_needed(pins):
146 """From a mod.pins[x] dict, return the pins needed for each model, for find_chip()"""
147 need = {}
148 for x in pins.values():
149 if type(x) == types.TupleType:
150 model, pin = x
151 if not need.has_key(model):
152 need[model] = []
154 need[model].append(pin)
156 return need
158 def assign_part(chips, subckt_defns, extra, model_name, external_nodes, refdesg):
159 """Assign a given model to a physical chip, using the names of the external nodes.
161 chips: array of existing physical chips that can be assigned from
162 mod: logical model to map to, like 'tinv'
163 external_nodes: dict mapping internal nodes in the model, to external nodes in the world
166 mod = globals()[model_name]
167 subckt_lines = subckt_defns[model_name]
170 # Store new pin assignments
171 assignments = {}
172 for p in mod.parts_generated:
173 assignments[p] = {}
175 need_options = []
176 the_model = None
177 for p in mod.pins:
178 need = find_pins_needed(p)
179 if len(need) > 1:
180 raise "Sorry, more than one model is needed: %s, but only one is supported right now." % (need,)
181 if the_model is None:
182 the_model = need.keys()[0]
183 else:
184 if the_model != need.keys()[0]:
185 raise "Sorry, different models are needed: %s, but already decided on %s earlier. This is not yet supported." % (the_model, need[0])
187 need_options.append(need.values()[0])
189 #print "* Searching for model %s with one of pins: %s" % (the_model, need_options)
190 chip_num, option_num = find_chip(chips, the_model, need_options)
191 #print "* FOUND CHIP:",chip_num
192 #print "* WITH PINS (option #%s):" % (option_num,), mod.pins[option_num]
194 for node, pin in combine_dicts(mod.global_pins, mod.pins[option_num]).iteritems():
195 if type(pin) == types.TupleType:
196 part, pin = pin
197 else:
198 part = None
200 #print "* %s -> %s:%s" % (node, part, pin)
202 if part is not None:
203 if node.startswith("$G_") or node == "0":
204 external_node = node # global node (LTspice)
205 else:
206 external_node = external_nodes[node] # map internal to external node
208 chips[chip_num][1][pin] = external_node
210 # Now place any additional parts (resistors, etc.) within the subcircuit model
211 # that connect to the chip, but are not part of the chip.
212 for line in subckt_lines:
213 words = line.split()
214 new_words = []
216 #name = "%s_%s_%s_%s" % (words[0], model_name, chip_num, refdesg)
217 name = "%s%s$%s" % (words[0][0], refdesg, words[0])
219 new_words.append(name)
221 # Replace internal nodes with external nodes.
222 for w in words[1:]:
223 if w in external_nodes.keys():
224 new_words.append(external_nodes[w])
225 elif is_floating(w):
226 # TODO ???
227 new_words.append(get_floating())
228 elif w[0].isdigit(): # value
229 new_words.append(w)
230 else:
231 raise "Could not map argument '%s' in subcircuit line %s" % (w, line)
232 extra.append(" ".join(new_words))
233 # TODO: get new part names!!!!!!!!
235 return chips, extra
237 def any_pins_used(pins):
238 """Return whether any of the pins on a chip are used. If False, chip isn't used."""
239 for p in pins.values():
240 if not is_floating(p):
241 return True
242 return False
244 def dump_chips(chips):
245 """Show the current chips and their pin connections."""
246 for i, c in enumerate(chips):
247 m, p = c
249 if not any_pins_used(p):
250 print "* Chip #%s - %s no pins used, skipping" % (i, m)
251 continue
253 print "* Chip #%s - %s pinout:" % (i, m)
254 for k, v in p.iteritems():
255 print "* \t%s: %s" % (k, v)
257 print "X_IC_%s " % (i, ),
258 # Assumes all chips are 14-pin, arguments from 1 to 14 positional
259 for k in range(1, 14+1):
260 print p[k],
261 print m
262 print
264 def dump_extra(extra):
265 """Shows the extra, supporting subcircuit parts that support the IC and are part of the subcircuit."""
266 print "* Parts to support subcircuits:"
267 for e in extra:
268 print e
270 def make_node_mapping(internal, external):
271 """Make a node mapping dictionary from internal to external nodes.
272 Keys of the returned dictionary are internal nodes (of subcircuit), values are external."""
273 d = {}
274 d.update(zip(internal, external))
275 return d
277 def rewrite_refdesg(original, prefix):
278 """Prefix a reference designator, preserving the first character."""
279 return "%s%s$%s" % (original[0], prefix, original)
281 def expand(subckt_defns, line, prefix):
282 """Recursively expand a subcircuit instantiation if needed."""
283 words = line.split()
284 refdesg = words[0]
285 if words[0][0] == 'X':
286 model = words[-1]
287 args = words[1:-1]
289 nodes = make_node_mapping(subckt_defns[model], args)
290 new_lines = []
291 new_lines.append(("* Instance of subcircuit %s: %s" % (model, " ".join(args))))
292 for sline in subckt_defns[model]:
293 words = sline.split()
294 if words[0][0] == 'X' and words[-1] not in ('tg', 'tinv', 'tnor', 'tnor3', 'tnand', 'tnand3'):
295 new_lines.extend(expand(subckt_defns, sline, "%s$" % (words[0]),))
296 else:
297 new_words = []
298 # Nest reference designator
299 new_words.append("%s%s%s$%s" % (words[0][0], prefix, refdesg, words[0]))
300 #new_words.append(rewrite_refdesg(rewrite_refdesg(words[0], refdesg), prefix)) # XXX TODO
301 # Map internal to external nodes
302 for word in words[1:]:
303 print word
304 if word in nodes.keys():
305 new_words.append(nodes[word])
306 elif is_floating(word):
307 new_words.append(get_floating())
308 else:
309 new_words.append(word)
310 new_lines.append(" ".join(new_words))
311 #new_lines.append("")
312 else:
313 new_lines = [line]
315 return new_lines
317 def test_flatten():
318 """Demonstrate flattening of a hierarchical subcircuit."""
319 subckt_nodes, subckt_defns, toplevel = read_netlist("mux3-1_test.net")
321 for line in toplevel:
322 print "\n".join(expand(subckt_defns, line, ""))
324 def main():
325 subckt_nodes, subckt_defns, toplevel = read_netlist("tinv_test.net")
327 mod_tinv, subckt_defns, pos2node_tinv = rewrite_subckt(subckt_defns, "tinv")
328 #tg_tinv, subckt_defns, pos2node_tg = rewrite_subckt(subckt_defns, "tg")
330 # First semi-flatten the circuit
331 flat_toplevel = []
332 for line in toplevel:
333 flat_toplevel.extend((expand(subckt_defns, line, "")))
335 print "* Flattened top-level, before part assignment:"
336 for f in flat_toplevel:
337 print "** %s" % (f,)
338 print "* Begin converted circuit"
339 print
341 # Available chips
342 chips = [
343 ("CD4007", get_floating(14) ),
344 ("CD4007", get_floating(14) ),
345 ("CD4007", get_floating(14) ),
346 ("CD4016", get_floating(14) ),
349 extra = []
350 for line in flat_toplevel:
351 words = line.split()
352 if words[0][0] == 'X':
353 refdesg = words[0]
354 model = words[-1]
355 args = words[1:-1]
357 #print "MODEL=%s, args=%s" % (model, args)
359 #print subckt_defns[model]
360 #print subckt_nodes[model]
362 if model in ('tg', 'tinv'):
363 nodes = make_node_mapping(subckt_nodes[model], args)
364 chips, extra = assign_part(chips, subckt_defns, extra, model, nodes, refdesg)
365 else:
366 raise "Cannot synthesize model: %s, line: %s" % (model, line)
367 else:
368 print line
370 dump_chips(chips)
371 dump_extra(extra)
373 def test_assignment():
374 """Demonstrate subcircuit assignment."""
375 subckt_nodes, subckt_defns, toplevel = read_netlist("mux3-1_test.net")
377 mod_tinv, subckt_defns, pos2node_tinv = rewrite_subckt(subckt_defns, "tinv")
378 tg_tinv, subckt_defns, pos2node_tg = rewrite_subckt(subckt_defns, "tg")
381 # Available chips
382 chips = [
383 ("CD4007", get_floating(14) ),
384 ("CD4016", get_floating(14) ),
385 #("CD4007", get_floating(14) )
388 # TODO: parse from SPICE files, assigning nodes based on pos2node_*
389 # TODO: sti
390 # line = "XX1 IN NC_01 OUT NC_02 tinv"
392 extra = []
393 chips, extra = assign_part(chips, subckt_defns, extra, "tinv",
395 "Vin": "IN_1",
396 "PTI_Out": "PTI_Out_1",
397 "NTI_Out": "NTI_Out_1",
398 "STI_Out": "STI_Out_1",
401 chips, extra = assign_part(chips, subckt_defns, extra, "tinv",
403 "Vin": "IN_2",
404 "PTI_Out": "PTI_Out_2",
405 "NTI_Out": "NTI_Out_2",
406 "STI_Out": "STI_Out_2",
409 chips, extra = assign_part(chips, subckt_defns, extra, "tg",
411 "IN_OUT": "IN_1",
412 "OUT_IN": "OUT_1",
413 "CONTROL": "CTRL_1",
415 chips, extra = assign_part(chips, subckt_defns, extra, "tg",
417 "IN_OUT": "IN_2",
418 "OUT_IN": "OUT_2",
419 "CONTROL": "CTRL_2",
421 chips, extra = assign_part(chips, subckt_defns, extra, "tg",
423 "IN_OUT": "IN_3",
424 "OUT_IN": "OUT_3",
425 "CONTROL": "CTRL_3",
427 chips, extra = assign_part(chips, subckt_defns, extra, "tg",
429 "IN_OUT": "IN_4",
430 "OUT_IN": "OUT_4",
431 "CONTROL": "CTRL_4",
434 dump_chips(chips)
435 dump_extra(extra)
437 if __name__ == "__main__":
438 #test_flatten()
439 #test_assignment()
440 main()