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