sycan.autodraw_hacks

Pattern-detect overrides for sycan.autodraw.

The base autodraw pipeline (SA placer + Lee BFS router) is generic; it has no concept of higher-level circuit idioms like a “diff-pair tail” or a “cross-coupled latch”. For those, the generic cost function gives layouts that are technically valid but visually wrong — a diff-pair tail wire that folds into a U-shape, or a cross-coupled gate-drain pair routed as two parallel zig-zags instead of the textbook X.

Every hook in this module is shaped the same way:

  1. Detect a specific topological pattern in the netlist.

  2. Override the placement constraint or the routing for that pattern, bypassing the generic stage.

These are intentionally pattern-specific (one-shot overrides, not model improvements). Adding a new idiom = a new detect-then-override pair here, without touching the generic pipeline.

Functions

apply_junction_clearance(branches, y_pos, ...)

Enforce a cross_gap between meeting pins at every junction.

cross_coupled_pinned_polylines(placed, nets, uf)

One-call API: detect every cross-coupled pair and return the union of their hand-routed X polylines, keyed by net.

detect_cross_coupled_descs(descs, uf)

Topology-only X-pair detector for the SA cost evaluator.

detect_cross_coupled_pairs(placed, uf)

Find FET pairs whose gates cross-couple to each other's drains.

detect_spine_junctions(branches, uf, spine_index)

Identify spine-junction nets and the branches meeting at each.

emit_cross_coupled_x(pair, nets, uf, placed)

Hand-route the two cross-coupling nets as a true diagonal X.

sycan.autodraw_hacks.detect_spine_junctions(branches, uf, spine_index)[source]

Identify spine-junction nets and the branches meeting at each.

A junction net has more than two spine endpoints. Branches whose bottom pin lands there are “above” the junction; branches whose top pin lands there are “below” it. Junctions where only one side is populated are dropped — the trunk has nothing to clear.

Parameters:
  • branches (Sequence[_Branch])

  • uf (_UF)

  • spine_index (dict[str, list[tuple[_CompDesc, str]]])

Return type:

list[dict]

sycan.autodraw_hacks.apply_junction_clearance(branches, y_pos, junctions, min_gap, sweep_branch)[source]

Enforce a cross_gap between meeting pins at every junction.

Splits the deficit symmetrically (above branches lift, below branches drop) in GRID_PX-aligned increments — sub-grid pushes get reverted by the snap_up/snap_down follow-up. Touched branches are then re-swept via the caller-provided sweep_branch(branches_iterable) so they stay inside the rail bounds.

Parameters:
  • branches (Sequence[_Branch])

  • y_pos (dict[int, float])

  • junctions (Sequence[dict])

  • min_gap (float)

Return type:

None

sycan.autodraw_hacks.detect_cross_coupled_descs(descs, uf)[source]

Topology-only X-pair detector for the SA cost evaluator.

Same predicate as detect_cross_coupled_pairs() but operating on bare _CompDesc`s no placement coordinates required, so it can run *before* the SA picks column / mirror state. Returns tuples of ``(desc_a, desc_b, net_w1, net_w2)` where net_w1 is the union-find canonical name of the a.gate b.drain net and net_w2 is b.gate a.drain. The geometric viability checks (gates inward, no third-component clip) are deferred to detect_cross_coupled_pairs() at routing time; here we only care that the topology is one the X-router will try to realise, so the cost function can stop punishing layouts that admit it.

Parameters:
  • descs (Sequence[_CompDesc])

  • uf (_UF)

Return type:

list[tuple[_CompDesc, _CompDesc, str, str]]

sycan.autodraw_hacks.detect_cross_coupled_pairs(placed, uf)[source]

Find FET pairs whose gates cross-couple to each other’s drains.

The classic latch / level-shifter / SR-flop pattern: two NMOS or two PMOS where M_a.gate == M_b.drain and M_b.gate == M_a.drain. Only pairs where the gates face each other (the geometry where an X actually fits in the column gap) are returned; other layouts fall back to the BFS router.

Parameters:
  • placed (Sequence[_Placed])

  • uf (_UF)

Return type:

list[tuple[_Placed, _Placed]]

sycan.autodraw_hacks.emit_cross_coupled_x(pair, nets, uf, placed)[source]

Hand-route the two cross-coupling nets as a true diagonal X.

Each arm is a 3-point polyline: gate elbow drain, where the elbow sits at (opposite_gate_x, drain_y). That places the bottom-left corner of the X at the left gate’s x and the bottom-right corner at the right gate’s x — the four diagonal endpoints all align inside a single [left_g.x, right_g.x] rectangle, so the X reads symmetric and each arm has a small horizontal stub from the elbow over to the actual drain pin (\_ for the left→right arm, _/ for the right→left arm). The schematic router otherwise enforces 90° corners; this is the one pattern where diagonal routing is allowed because the visual gain is unambiguous.

Bails (returns {}) and lets the BFS take over when:

  • The gates and drains aren’t each row-aligned (the X would visually skew).

  • The pin x-ordering doesn’t support a clean cross.

  • Any segment (diagonal or stub) would clip a third component’s bbox — we’d rather fall back to ugly Manhattan than draw a wire through a transistor.

Extras (third terminals on the coupling net, e.g. MN1.drain for the level-shifter OUT_P) are daisy-chained off the drain pin via Manhattan stubs.

Parameters:
  • pair (tuple[_Placed, _Placed])

  • nets (dict[str, list[tuple[_Placed, str]]])

  • uf (_UF)

  • placed (Sequence[_Placed])

Return type:

dict[str, list[list[tuple[float, float]]]]

sycan.autodraw_hacks.cross_coupled_pinned_polylines(placed, nets, uf)[source]

One-call API: detect every cross-coupled pair and return the union of their hand-routed X polylines, keyed by net.

Parameters:
  • placed (Sequence[_Placed])

  • nets (dict[str, list[tuple[_Placed, str]]])

  • uf (_UF)

Return type:

dict[str, list[list[tuple[float, float]]]]