optimizing a gate level bcm to the end of the earth and back
at main 715 lines 26 kB view raw
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))