optimizing a gate level bcm to the end of the earth and back
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

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))