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:
Detect a specific topological pattern in the netlist.
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
|
Enforce a |
|
One-call API: detect every cross-coupled pair and return the union of their hand-routed X polylines, keyed by net. |
|
Topology-only X-pair detector for the SA cost evaluator. |
|
Find FET pairs whose gates cross-couple to each other's drains. |
|
Identify spine-junction nets and the branches meeting at each. |
|
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_gapbetween 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 thesnap_up/snap_downfollow-up. Touched branches are then re-swept via the caller-providedsweep_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)`wherenet_w1is the union-find canonical name of thea.gate ↔ b.drainnet andnet_w2isb.gate ↔ a.drain. The geometric viability checks (gates inward, no third-component clip) are deferred todetect_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.drainandM_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.drainfor the level-shifterOUT_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]]]]