optimizing a gate level bcm to the end of the earth and back
1"""
2Export synthesized circuits to various formats (Verilog, VHDL, etc).
3"""
4
5from .solver import SynthesisResult
6from .truth_tables import SEGMENT_NAMES
7from .quine_mccluskey import Implicant
8
9
10def to_verilog(result: SynthesisResult, module_name: str = "bcd_to_7seg") -> str:
11 """
12 Export synthesis result to Verilog.
13
14 Args:
15 result: The synthesis result
16 module_name: Name for the Verilog module
17
18 Returns:
19 Verilog source code as string
20 """
21 lines = []
22 lines.append(f"// BCD to 7-segment decoder")
23 lines.append(f"// Synthesized with {result.cost} gate inputs using {result.method}")
24 lines.append(f"// Shared terms: {len(result.shared_implicants)}")
25 lines.append("")
26 lines.append(f"module {module_name} (")
27 lines.append(" input wire [3:0] bcd, // BCD input (0-9 valid)")
28 lines.append(" output wire [6:0] seg // 7-segment output (a=seg[6], g=seg[0])")
29 lines.append(");")
30 lines.append("")
31 lines.append(" // Input aliases")
32 lines.append(" wire A = bcd[3];")
33 lines.append(" wire B = bcd[2];")
34 lines.append(" wire C = bcd[1];")
35 lines.append(" wire D = bcd[0];")
36 lines.append("")
37
38 # Generate wire declarations for shared terms
39 if result.shared_implicants:
40 lines.append(" // Shared product terms")
41 for i, (impl, outputs) in enumerate(result.shared_implicants):
42 term_name = f"term_{i}"
43 expr = impl_to_verilog(impl)
44 lines.append(f" wire {term_name} = {expr}; // used by {', '.join(outputs)}")
45 lines.append("")
46
47 # Generate output assignments
48 lines.append(" // Segment outputs")
49 for i, segment in enumerate(SEGMENT_NAMES):
50 if segment in result.implicants_by_output:
51 terms = []
52 for impl in result.implicants_by_output[segment]:
53 # Check if this is a shared term
54 shared_idx = None
55 for j, (shared_impl, _) in enumerate(result.shared_implicants):
56 if impl == shared_impl:
57 shared_idx = j
58 break
59
60 if shared_idx is not None:
61 terms.append(f"term_{shared_idx}")
62 else:
63 terms.append(impl_to_verilog(impl))
64
65 expr = " | ".join(terms) if terms else "1'b0"
66 seg_idx = 6 - i # a=seg[6], b=seg[5], ..., g=seg[0]
67 lines.append(f" assign seg[{seg_idx}] = {expr}; // {segment}")
68
69 lines.append("")
70 lines.append("endmodule")
71
72 return "\n".join(lines)
73
74
75def impl_to_verilog(impl: Implicant) -> str:
76 """Convert an implicant to a Verilog expression."""
77 var_names = ['A', 'B', 'C', 'D']
78 terms = []
79
80 for i in range(4):
81 bit = 1 << (3 - i)
82 if impl.mask & bit:
83 if (impl.value >> (3 - i)) & 1:
84 terms.append(var_names[i])
85 else:
86 terms.append(f"~{var_names[i]}")
87
88 if not terms:
89 return "1'b1"
90 elif len(terms) == 1:
91 return terms[0]
92 else:
93 return "(" + " & ".join(terms) + ")"
94
95
96def to_equations(result: SynthesisResult) -> str:
97 """
98 Export synthesis result as Boolean equations.
99
100 Args:
101 result: The synthesis result
102
103 Returns:
104 Human-readable Boolean equations
105 """
106 lines = []
107 lines.append(f"BCD to 7-Segment Decoder Equations")
108 lines.append(f"Method: {result.method}")
109 lines.append(f"Total gate inputs: {result.cost}")
110 lines.append(f"Shared terms: {len(result.shared_implicants)}")
111 lines.append("")
112
113 if result.shared_implicants:
114 lines.append("Shared product terms:")
115 for impl, outputs in result.shared_implicants:
116 lines.append(f" {impl.to_expr_str():12} -> {', '.join(outputs)}")
117 lines.append("")
118
119 lines.append("Output equations:")
120 for segment in SEGMENT_NAMES:
121 if segment in result.expressions:
122 lines.append(f" {segment} = {result.expressions[segment]}")
123
124 return "\n".join(lines)
125
126
127def to_c_code(result: SynthesisResult, func_name: str = "bcd_to_7seg") -> str:
128 """
129 Export synthesis result as C code.
130
131 Args:
132 result: The synthesis result
133 func_name: Name for the C function
134
135 Returns:
136 C source code as string
137 """
138 lines = []
139 lines.append("/*")
140 lines.append(" * BCD to 7-segment decoder")
141 lines.append(f" * Synthesized with {result.cost} gate inputs using {result.method}")
142 lines.append(" */")
143 lines.append("")
144 lines.append("#include <stdint.h>")
145 lines.append("")
146 lines.append(f"uint8_t {func_name}(uint8_t bcd) {{")
147 lines.append(" // Extract individual bits")
148 lines.append(" uint8_t A = (bcd >> 3) & 1;")
149 lines.append(" uint8_t B = (bcd >> 2) & 1;")
150 lines.append(" uint8_t C = (bcd >> 1) & 1;")
151 lines.append(" uint8_t D = bcd & 1;")
152 lines.append(" uint8_t nA = !A, nB = !B, nC = !C, nD = !D;")
153 lines.append("")
154
155 # Generate shared terms
156 if result.shared_implicants:
157 lines.append(" // Shared product terms")
158 for i, (impl, _) in enumerate(result.shared_implicants):
159 expr = impl_to_c(impl)
160 lines.append(f" uint8_t t{i} = {expr};")
161 lines.append("")
162
163 # Generate output bits
164 lines.append(" // Compute segment outputs")
165 segment_exprs = []
166 for seg_idx, segment in enumerate(SEGMENT_NAMES):
167 if segment in result.implicants_by_output:
168 terms = []
169 for impl in result.implicants_by_output[segment]:
170 shared_idx = None
171 for j, (shared_impl, _) in enumerate(result.shared_implicants):
172 if impl == shared_impl:
173 shared_idx = j
174 break
175
176 if shared_idx is not None:
177 terms.append(f"t{shared_idx}")
178 else:
179 terms.append(impl_to_c(impl))
180
181 expr = " | ".join(terms) if terms else "0"
182 lines.append(f" uint8_t {segment} = {expr};")
183 segment_exprs.append(segment)
184
185 lines.append("")
186 lines.append(" // Pack into result (bit 6 = a, bit 0 = g)")
187 pack_expr = " | ".join(
188 f"({segment} << {6-i})"
189 for i, segment in enumerate(SEGMENT_NAMES)
190 )
191 lines.append(f" return {pack_expr};")
192 lines.append("}")
193
194 return "\n".join(lines)
195
196
197def impl_to_c(impl: Implicant) -> str:
198 """Convert an implicant to a C expression."""
199 var_map = {
200 'A': 'A', 'B': 'B', 'C': 'C', 'D': 'D',
201 "A'": 'nA', "B'": 'nB', "C'": 'nC', "D'": 'nD',
202 }
203 var_names = ['A', 'B', 'C', 'D']
204 terms = []
205
206 for i in range(4):
207 bit = 1 << (3 - i)
208 if impl.mask & bit:
209 if (impl.value >> (3 - i)) & 1:
210 terms.append(var_names[i])
211 else:
212 terms.append(f"n{var_names[i]}")
213
214 if not terms:
215 return "1"
216 elif len(terms) == 1:
217 return terms[0]
218 else:
219 return "(" + " & ".join(terms) + ")"
220
221
222def to_dot(result: SynthesisResult, title: str = "BCD to 7-Segment Decoder") -> str:
223 """
224 Export synthesis result as Graphviz DOT format.
225
226 Render with: dot -Tpng circuit.dot -o circuit.png
227 Or: dot -Tsvg circuit.dot -o circuit.svg
228
229 Note: Inputs and their complements (A, A', B, B', C, C', D, D') are
230 shown as free inputs - no inverter gates are needed.
231
232 Args:
233 result: The synthesis result
234 title: Title for the diagram
235
236 Returns:
237 DOT source code as string
238 """
239 lines = []
240 lines.append("digraph BCD_7Seg {")
241 lines.append(f' label="{title}\\n{result.cost} gate inputs, {len(result.shared_implicants)} shared terms";')
242 lines.append(' labelloc="t";')
243 lines.append(' fontsize=16;')
244 lines.append(' rankdir=LR;')
245 lines.append(' splines=ortho;')
246 lines.append(' nodesep=0.3;')
247 lines.append(' ranksep=0.8;')
248 lines.append("")
249
250 # Determine which inputs (true and complement) are actually used
251 used_inputs = set() # Will contain 'A', 'nA', 'B', 'nB', etc.
252
253 for impl, _ in result.shared_implicants:
254 for i, var in enumerate(['A', 'B', 'C', 'D']):
255 bit = 1 << (3 - i)
256 if impl.mask & bit:
257 if (impl.value >> (3 - i)) & 1:
258 used_inputs.add(var)
259 else:
260 used_inputs.add(f"n{var}")
261
262 for segment in SEGMENT_NAMES:
263 if segment not in result.implicants_by_output:
264 continue
265 for impl in result.implicants_by_output[segment]:
266 for i, var in enumerate(['A', 'B', 'C', 'D']):
267 bit = 1 << (3 - i)
268 if impl.mask & bit:
269 if (impl.value >> (3 - i)) & 1:
270 used_inputs.add(var)
271 else:
272 used_inputs.add(f"n{var}")
273
274 # Input nodes (true and complement forms are free)
275 lines.append(" // Inputs (active high and low available for free)")
276 lines.append(' subgraph cluster_inputs {')
277 lines.append(' label="Inputs";')
278 lines.append(' style=dashed;')
279 lines.append(' color=gray;')
280 for var in ['A', 'B', 'C', 'D']:
281 if var in used_inputs:
282 lines.append(f' {var} [shape=circle, style=filled, fillcolor=lightblue, label="{var}"];')
283 if f"n{var}" in used_inputs:
284 lines.append(f' n{var} [shape=circle, style=filled, fillcolor=lightcyan, label="{var}\'"];')
285 lines.append(' }')
286 lines.append("")
287
288 # AND gates for shared product terms (only multi-literal terms need AND gates)
289 # Single-literal terms are just wires from input to OR
290 multi_literal_shared = [(i, impl, outputs) for i, (impl, outputs) in enumerate(result.shared_implicants) if impl.num_literals >= 2]
291 single_literal_shared = [(i, impl, outputs) for i, (impl, outputs) in enumerate(result.shared_implicants) if impl.num_literals < 2]
292
293 if multi_literal_shared:
294 lines.append(" // Shared AND gates (multi-literal product terms)")
295 lines.append(' subgraph cluster_and {')
296 lines.append(' label="Product Terms";')
297 lines.append(' style=dashed;')
298 lines.append(' color=gray;')
299
300 for i, impl, outputs in multi_literal_shared:
301 term_label = impl.to_expr_str()
302 lines.append(f' and_{i} [shape=polygon, sides=4, style=filled, fillcolor=lightgreen, label="AND\\n{term_label}"];')
303 lines.append(' }')
304 lines.append("")
305
306 # Connect inputs to AND gates
307 lines.append(" // Input to AND connections")
308 for i, impl, _ in multi_literal_shared:
309 for j, var in enumerate(['A', 'B', 'C', 'D']):
310 bit = 1 << (3 - j)
311 if impl.mask & bit:
312 if (impl.value >> (3 - j)) & 1:
313 lines.append(f' {var} -> and_{i};')
314 else:
315 lines.append(f' n{var} -> and_{i};')
316 lines.append("")
317
318 # OR gates for outputs
319 lines.append(" // Output OR gates")
320 lines.append(' subgraph cluster_or {')
321 lines.append(' label="Output OR Gates";')
322 lines.append(' style=dashed;')
323 lines.append(' color=gray;')
324 for segment in SEGMENT_NAMES:
325 lines.append(f' or_{segment} [shape=ellipse, style=filled, fillcolor=lightsalmon, label="OR\\n{segment}"];')
326 lines.append(' }')
327 lines.append("")
328
329 # Connect AND gates to OR gates (multi-literal shared terms)
330 lines.append(" // AND to OR connections")
331 for i, impl, outputs in multi_literal_shared:
332 for segment in outputs:
333 lines.append(f' and_{i} -> or_{segment};')
334 lines.append("")
335
336 # Connect single-literal shared terms directly from inputs to OR gates
337 if single_literal_shared:
338 lines.append(" // Single-literal terms (direct wires)")
339 for i, impl, outputs in single_literal_shared:
340 for j, var in enumerate(['A', 'B', 'C', 'D']):
341 bit = 1 << (3 - j)
342 if impl.mask & bit:
343 src = var if (impl.value >> (3 - j)) & 1 else f"n{var}"
344 for segment in outputs:
345 lines.append(f' {src} -> or_{segment};')
346 lines.append("")
347
348 # Handle non-shared terms (direct connections or inline ANDs)
349 lines.append(" // Non-shared terms")
350 nonshared_idx = 0
351 for segment in SEGMENT_NAMES:
352 if segment not in result.implicants_by_output:
353 continue
354 for impl in result.implicants_by_output[segment]:
355 is_shared = any(impl == si for si, _ in result.shared_implicants)
356 if is_shared:
357 continue
358
359 # Single literal - direct connection from input
360 if impl.num_literals == 1:
361 for j, var in enumerate(['A', 'B', 'C', 'D']):
362 bit = 1 << (3 - j)
363 if impl.mask & bit:
364 if (impl.value >> (3 - j)) & 1:
365 lines.append(f' {var} -> or_{segment};')
366 else:
367 lines.append(f' n{var} -> or_{segment};')
368 else:
369 # Multi-literal non-shared AND
370 term_label = impl.to_expr_str()
371 and_name = f"and_ns_{nonshared_idx}"
372 lines.append(f' {and_name} [shape=polygon, sides=4, style=filled, fillcolor=palegreen, label="AND\\n{term_label}"];')
373 for j, var in enumerate(['A', 'B', 'C', 'D']):
374 bit = 1 << (3 - j)
375 if impl.mask & bit:
376 if (impl.value >> (3 - j)) & 1:
377 lines.append(f' {var} -> {and_name};')
378 else:
379 lines.append(f' n{var} -> {and_name};')
380 lines.append(f' {and_name} -> or_{segment};')
381 nonshared_idx += 1
382 lines.append("")
383
384 # Output nodes
385 lines.append(" // Outputs")
386 lines.append(' subgraph cluster_outputs {')
387 lines.append(' label="Outputs";')
388 lines.append(' style=dashed;')
389 lines.append(' color=gray;')
390 for segment in SEGMENT_NAMES:
391 lines.append(f' out_{segment} [shape=doublecircle, style=filled, fillcolor=lightpink, label="{segment}"];')
392 lines.append(' }')
393 lines.append("")
394
395 # Connect OR gates to outputs
396 lines.append(" // OR to output connections")
397 for segment in SEGMENT_NAMES:
398 lines.append(f' or_{segment} -> out_{segment};')
399
400 lines.append("}")
401
402 return "\n".join(lines)
403
404
405def to_verilog_exact(result: SynthesisResult, module_name: str = "bcd_to_7seg") -> str:
406 """
407 Export exact synthesis result to Verilog.
408
409 Args:
410 result: The synthesis result with gates list populated
411 module_name: Name for the Verilog module
412
413 Returns:
414 Verilog source code as string
415 """
416 if not result.gates:
417 raise ValueError("No gates in result - use to_verilog for SOP results")
418
419 lines = []
420 lines.append(f"// BCD to 7-segment decoder (exact synthesis)")
421 lines.append(f"// {len(result.gates)} gates, {result.cost} total gate inputs")
422 lines.append(f"// Method: {result.method}")
423 lines.append("")
424 lines.append(f"module {module_name} (")
425 lines.append(" input wire [3:0] bcd, // BCD input (0-9 valid)")
426 lines.append(" output wire [6:0] seg // 7-segment output (a=seg[6], g=seg[0])")
427 lines.append(");")
428 lines.append("")
429 lines.append(" // Input aliases")
430 lines.append(" wire A = bcd[3];")
431 lines.append(" wire B = bcd[2];")
432 lines.append(" wire C = bcd[1];")
433 lines.append(" wire D = bcd[0];")
434 lines.append("")
435
436 n_inputs = 4
437 node_names = ['A', 'B', 'C', 'D']
438
439 # Generate gate wires
440 lines.append(" // Internal gate outputs")
441 for gate in result.gates:
442 node_names.append(f"g{gate.index}")
443 in1 = node_names[gate.input1]
444 in2 = node_names[gate.input2]
445 expr = _gate_to_verilog_expr(gate.func, in1, in2)
446 lines.append(f" wire g{gate.index} = {expr};")
447
448 lines.append("")
449 lines.append(" // Segment output assignments")
450
451 for i, segment in enumerate(SEGMENT_NAMES):
452 if segment in result.output_map:
453 node_idx = result.output_map[segment]
454 src = node_names[node_idx]
455 seg_idx = 6 - i # a=seg[6], b=seg[5], ..., g=seg[0]
456 lines.append(f" assign seg[{seg_idx}] = {src}; // {segment}")
457
458 lines.append("")
459 lines.append("endmodule")
460
461 return "\n".join(lines)
462
463
464def _gate_to_verilog_expr(func: int, in1: str, in2: str) -> str:
465 """Convert gate function code to Verilog expression."""
466 # func encodes 2-input truth table: bit i = f(p,q) where i = p*2 + q
467 # Bit 0: f(0,0), Bit 1: f(0,1), Bit 2: f(1,0), Bit 3: f(1,1)
468 expressions = {
469 0b0000: "1'b0", # constant 0
470 0b0001: f"~({in1} | {in2})", # NOR
471 0b0010: f"(~{in1} & {in2})", # B AND NOT A
472 0b0011: f"~{in1}", # NOT A
473 0b0100: f"({in1} & ~{in2})", # A AND NOT B
474 0b0101: f"~{in2}", # NOT B
475 0b0110: f"({in1} ^ {in2})", # XOR
476 0b0111: f"~({in1} & {in2})", # NAND
477 0b1000: f"({in1} & {in2})", # AND
478 0b1001: f"~({in1} ^ {in2})", # XNOR
479 0b1010: in2, # B (pass through)
480 0b1011: f"(~{in1} | {in2})", # NOT A OR B
481 0b1100: in1, # A (pass through)
482 0b1101: f"({in1} | ~{in2})", # A OR NOT B
483 0b1110: f"({in1} | {in2})", # OR
484 0b1111: "1'b1", # constant 1
485 }
486 return expressions.get(func, f"/* unknown func {func} */")
487
488
489def _decompose_gate_function(func: int) -> tuple[str, bool, bool]:
490 """
491 Decompose a 4-bit gate function into base gate type and input inversions.
492
493 Returns: (gate_type, input1_inverted, input2_inverted)
494 """
495 decomposition = {
496 0b0000: ("CONST0", False, False),
497 0b0001: ("NOR", False, False),
498 0b0010: ("AND", True, False), # !A & B
499 0b0011: ("BUF", True, False), # !A (ignore B)
500 0b0100: ("AND", False, True), # A & !B
501 0b0101: ("BUF", False, True), # !B (ignore A)
502 0b0110: ("XOR", False, False),
503 0b0111: ("NAND", False, False),
504 0b1000: ("AND", False, False),
505 0b1001: ("XNOR", False, False),
506 0b1010: ("BUF", False, False), # B (ignore A)
507 0b1011: ("OR", True, False), # !A | B
508 0b1100: ("BUF", False, False), # A (ignore B)
509 0b1101: ("OR", False, True), # A | !B
510 0b1110: ("OR", False, False),
511 0b1111: ("CONST1", False, False),
512 }
513 return decomposition.get(func, ("???", False, False))
514
515
516def to_dot_exact(result: SynthesisResult, title: str = "BCD to 7-Segment Decoder") -> str:
517 """
518 Export exact synthesis result as Graphviz DOT format.
519
520 Args:
521 result: The synthesis result with gates list populated
522 title: Title for the diagram
523
524 Returns:
525 DOT source code as string
526 """
527 if not result.gates:
528 raise ValueError("No gates in result - use to_dot for SOP results")
529
530 lines = []
531 lines.append("digraph BCD_7Seg {")
532 lines.append(f' label="{title}\\n{len(result.gates)} gates, {result.cost} gate inputs";')
533 lines.append(' labelloc="t";')
534 lines.append(' fontsize=16;')
535 lines.append(' rankdir=LR;')
536 lines.append(' splines=ortho;')
537 lines.append(' nodesep=0.5;')
538 lines.append(' ranksep=1.0;')
539 lines.append("")
540
541 n_inputs = 4
542 node_names = ['A', 'B', 'C', 'D']
543
544 # Input nodes
545 lines.append(' // Inputs')
546 lines.append(' subgraph cluster_inputs {')
547 lines.append(' label="Inputs";')
548 lines.append(' style=dashed;')
549 lines.append(' color=gray;')
550 for name in node_names:
551 lines.append(f' {name} [shape=circle, style=filled, fillcolor=lightblue, label="{name}"];')
552 lines.append(' }')
553 lines.append("")
554
555 # Gate nodes - color by base gate type
556 lines.append(' // Gates')
557 lines.append(' subgraph cluster_gates {')
558 lines.append(' label="Logic Gates";')
559 lines.append(' style=dashed;')
560 lines.append(' color=gray;')
561
562 gate_colors = {
563 'AND': 'lightgreen',
564 'OR': 'lightsalmon',
565 'XOR': 'lightyellow',
566 'XNOR': 'khaki',
567 'NAND': 'palegreen',
568 'NOR': 'peachpuff',
569 'BUF': 'lightgray',
570 'CONST0': 'white',
571 'CONST1': 'white',
572 }
573
574 # Pre-compute gate decompositions
575 gate_decomp = {}
576 for gate in result.gates:
577 base_type, inv1, inv2 = _decompose_gate_function(gate.func)
578 gate_decomp[gate.index] = (base_type, inv1, inv2)
579 node_names.append(f"g{gate.index}")
580 color = gate_colors.get(base_type, 'lightgray')
581 lines.append(f' g{gate.index} [shape=box, style=filled, fillcolor={color}, label="{base_type}"];')
582 lines.append(' }')
583 lines.append("")
584
585 # Gate connections with inversion markers on edges
586 lines.append(' // Gate input connections')
587 node_names_lookup = ['A', 'B', 'C', 'D'] + [f"g{g.index}" for g in result.gates]
588 for gate in result.gates:
589 base_type, inv1, inv2 = gate_decomp[gate.index]
590 in1 = node_names_lookup[gate.input1]
591 in2 = node_names_lookup[gate.input2]
592
593 # Add inversion indicator as edge label
594 label1 = " [taillabel=\"'\", labeldistance=2]" if inv1 else ""
595 label2 = " [taillabel=\"'\", labeldistance=2]" if inv2 else ""
596
597 lines.append(f' {in1} -> g{gate.index}{label1};')
598 lines.append(f' {in2} -> g{gate.index}{label2};')
599 lines.append("")
600
601 # Output nodes
602 lines.append(' // Outputs')
603 lines.append(' subgraph cluster_outputs {')
604 lines.append(' label="Segment Outputs";')
605 lines.append(' style=dashed;')
606 lines.append(' color=gray;')
607 for segment in SEGMENT_NAMES:
608 lines.append(f' out_{segment} [shape=doublecircle, style=filled, fillcolor=lightpink, label="{segment}"];')
609 lines.append(' }')
610 lines.append("")
611
612 # Output connections
613 lines.append(' // Output connections')
614 for segment in SEGMENT_NAMES:
615 if segment in result.output_map:
616 node_idx = result.output_map[segment]
617 src = node_names_lookup[node_idx]
618 lines.append(f' {src} -> out_{segment};')
619
620 lines.append("}")
621
622 return "\n".join(lines)
623
624
625def to_c_exact(result: SynthesisResult, func_name: str = "bcd_to_7seg") -> str:
626 """
627 Export exact synthesis result as C code.
628
629 Args:
630 result: The synthesis result with gates list populated
631 func_name: Name for the C function
632
633 Returns:
634 C source code as string
635 """
636 if not result.gates:
637 raise ValueError("No gates in result - use to_c_code for SOP results")
638
639 lines = []
640 lines.append("/*")
641 lines.append(" * BCD to 7-segment decoder (exact synthesis)")
642 lines.append(f" * {len(result.gates)} gates, {result.cost} total gate inputs")
643 lines.append(f" * Method: {result.method}")
644 lines.append(" */")
645 lines.append("")
646 lines.append("#include <stdint.h>")
647 lines.append("")
648 lines.append(f"uint8_t {func_name}(uint8_t bcd) {{")
649 lines.append(" // Extract individual bits")
650 lines.append(" uint8_t A = (bcd >> 3) & 1;")
651 lines.append(" uint8_t B = (bcd >> 2) & 1;")
652 lines.append(" uint8_t C = (bcd >> 1) & 1;")
653 lines.append(" uint8_t D = bcd & 1;")
654 lines.append("")
655
656 node_names = ['A', 'B', 'C', 'D']
657
658 lines.append(" // Gate outputs")
659 for gate in result.gates:
660 node_names.append(f"g{gate.index}")
661 in1 = node_names[gate.input1]
662 in2 = node_names[gate.input2]
663 expr = _gate_to_c_expr(gate.func, in1, in2)
664 lines.append(f" uint8_t g{gate.index} = {expr};")
665
666 lines.append("")
667 lines.append(" // Pack segment outputs (bit 6 = a, bit 0 = g)")
668
669 pack_parts = []
670 for i, segment in enumerate(SEGMENT_NAMES):
671 if segment in result.output_map:
672 node_idx = result.output_map[segment]
673 src = node_names[node_idx]
674 pack_parts.append(f"({src} << {6-i})")
675
676 lines.append(f" return {' | '.join(pack_parts)};")
677 lines.append("}")
678
679 return "\n".join(lines)
680
681
682def _gate_to_c_expr(func: int, in1: str, in2: str) -> str:
683 """Convert gate function code to C expression."""
684 # func encodes 2-input truth table: bit i = f(p,q) where i = p*2 + q
685 expressions = {
686 0b0000: "0", # constant 0
687 0b0001: f"!({in1} | {in2})", # NOR
688 0b0010: f"(!{in1} & {in2})", # B AND NOT A
689 0b0011: f"!{in1}", # NOT A
690 0b0100: f"({in1} & !{in2})", # A AND NOT B
691 0b0101: f"!{in2}", # NOT B
692 0b0110: f"({in1} ^ {in2})", # XOR
693 0b0111: f"!({in1} & {in2})", # NAND
694 0b1000: f"({in1} & {in2})", # AND
695 0b1001: f"!({in1} ^ {in2})", # XNOR
696 0b1010: in2, # B (pass through)
697 0b1011: f"(!{in1} | {in2})", # NOT A OR B
698 0b1100: in1, # A (pass through)
699 0b1101: f"({in1} | !{in2})", # A OR NOT B
700 0b1110: f"({in1} | {in2})", # OR
701 0b1111: "1", # constant 1
702 }
703 return expressions.get(func, f"/* unknown func {func} */")
704
705
706if __name__ == "__main__":
707 from .solver import BCDTo7SegmentSolver
708
709 solver = BCDTo7SegmentSolver()
710 result = solver.solve()
711
712 print("=" * 60)
713 print("DOT OUTPUT (save as .dot, render with Graphviz)")
714 print("=" * 60)
715 print(to_dot(result))