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

Show complementary inputs as free in gate diagram

Input complements (A', B', C', D') are now shown as separate input
nodes rather than through inverter gates, since they're available
for free in the target technology.

- True inputs shown in light blue
- Complement inputs shown in light cyan
- No NOT gates in the diagram

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

dunkirk.sh b12c1dcd 25ded3bc

verified
+32 -30
+32 -30
bcd_optimization/export.py
··· 226 Render with: dot -Tpng circuit.dot -o circuit.png 227 Or: dot -Tsvg circuit.dot -o circuit.svg 228 229 Args: 230 result: The synthesis result 231 title: Title for the diagram ··· 240 lines.append(' fontsize=16;') 241 lines.append(' rankdir=LR;') 242 lines.append(' splines=ortho;') 243 - lines.append(' nodesep=0.4;') 244 lines.append(' ranksep=0.8;') 245 lines.append("") 246 247 - # Input nodes 248 - lines.append(" // Inputs") 249 - lines.append(' subgraph cluster_inputs {') 250 - lines.append(' label="Inputs";') 251 - lines.append(' style=dashed;') 252 - lines.append(' color=gray;') 253 - for var in ['A', 'B', 'C', 'D']: 254 - lines.append(f' {var} [shape=circle, style=filled, fillcolor=lightblue, label="{var}"];') 255 - lines.append(' }') 256 - lines.append("") 257 258 - # Inverters for negated inputs 259 - lines.append(" // Inverters") 260 - used_negations = set() 261 for impl, _ in result.shared_implicants: 262 for i, var in enumerate(['A', 'B', 'C', 'D']): 263 bit = 1 << (3 - i) 264 - if impl.mask & bit and not ((impl.value >> (3 - i)) & 1): 265 - used_negations.add(var) 266 267 - # Check non-shared implicants too 268 for segment in SEGMENT_NAMES: 269 if segment not in result.implicants_by_output: 270 continue 271 for impl in result.implicants_by_output[segment]: 272 - is_shared = any(impl == si for si, _ in result.shared_implicants) 273 - if is_shared: 274 - continue 275 for i, var in enumerate(['A', 'B', 'C', 'D']): 276 bit = 1 << (3 - i) 277 - if impl.mask & bit and not ((impl.value >> (3 - i)) & 1): 278 - used_negations.add(var) 279 280 - for var in sorted(used_negations): 281 - lines.append(f' not_{var} [shape=invtriangle, style=filled, fillcolor=lightyellow, label="NOT", width=0.4, height=0.4];') 282 - lines.append(f' {var} -> not_{var};') 283 lines.append("") 284 285 # AND gates for shared product terms ··· 291 292 for i, (impl, outputs) in enumerate(result.shared_implicants): 293 term_label = impl.to_expr_str() 294 - outputs_str = ",".join(outputs) 295 lines.append(f' and_{i} [shape=polygon, sides=4, style=filled, fillcolor=lightgreen, label="AND\\n{term_label}"];') 296 lines.append(' }') 297 lines.append("") ··· 305 if (impl.value >> (3 - j)) & 1: 306 lines.append(f' {var} -> and_{i};') 307 else: 308 - lines.append(f' not_{var} -> and_{i};') 309 lines.append("") 310 311 # OR gates for outputs ··· 337 if is_shared: 338 continue 339 340 - # Single literal - direct connection 341 if impl.num_literals == 1: 342 for j, var in enumerate(['A', 'B', 'C', 'D']): 343 bit = 1 << (3 - j) ··· 345 if (impl.value >> (3 - j)) & 1: 346 lines.append(f' {var} -> or_{segment};') 347 else: 348 - lines.append(f' not_{var} -> or_{segment};') 349 else: 350 # Multi-literal non-shared AND 351 term_label = impl.to_expr_str() ··· 357 if (impl.value >> (3 - j)) & 1: 358 lines.append(f' {var} -> {and_name};') 359 else: 360 - lines.append(f' not_{var} -> {and_name};') 361 lines.append(f' {and_name} -> or_{segment};') 362 nonshared_idx += 1 363 lines.append("")
··· 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 ··· 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 ··· 294 295 for i, (impl, outputs) in enumerate(result.shared_implicants): 296 term_label = impl.to_expr_str() 297 lines.append(f' and_{i} [shape=polygon, sides=4, style=filled, fillcolor=lightgreen, label="AND\\n{term_label}"];') 298 lines.append(' }') 299 lines.append("") ··· 307 if (impl.value >> (3 - j)) & 1: 308 lines.append(f' {var} -> and_{i};') 309 else: 310 + lines.append(f' n{var} -> and_{i};') 311 lines.append("") 312 313 # OR gates for outputs ··· 339 if is_shared: 340 continue 341 342 + # Single literal - direct connection from input 343 if impl.num_literals == 1: 344 for j, var in enumerate(['A', 'B', 'C', 'D']): 345 bit = 1 << (3 - j) ··· 347 if (impl.value >> (3 - j)) & 1: 348 lines.append(f' {var} -> or_{segment};') 349 else: 350 + lines.append(f' n{var} -> or_{segment};') 351 else: 352 # Multi-literal non-shared AND 353 term_label = impl.to_expr_str() ··· 359 if (impl.value >> (3 - j)) & 1: 360 lines.append(f' {var} -> {and_name};') 361 else: 362 + lines.append(f' n{var} -> {and_name};') 363 lines.append(f' {and_name} -> or_{segment};') 364 nonshared_idx += 1 365 lines.append("")