Layout Flow
Layout Flow
This page explains how cicpy sch2mag actually builds layout, and where placement and routing intent is supposed to live.
Entry Points
There are two main layout-generation entry points:
cicpy sch2mag <LIB> <CELL>
cicpy spi2mag <SPICE> <LIB> <CELL>
sch2magstarts from a schematic-driven project and first generates a SPICE netlist.spi2magstarts from an existing SPICE subcircuit.
Both end up building a LayoutCell, placing instances, running Python hooks, routing, and writing .mag and .cic output.
High-Level Flow
For sch2mag, the flow is:
- netlist the schematic to SPICE
- load technology rules from
tech/cic/<tech>.tech - scan the design library path for primitive Magic cells
- read the target SPICE subckt into a
LayoutCell - apply default placement from instance names and grouping
- import
<CELL>.pyif it exists - run layout hooks from that Python file
- finish routing and paint
- write
<CELL>.mag - write
<CELL>.cic - optionally run route-short and full connectivity checks
The key point is that the flow is schematic-driven, but not schematic-only. The schematic provides devices, nets, and grouping names. The Python file provides physical intent.
Inputs
A practical sch2mag run uses:
- a SPICE netlist for the target cell
- primitive layout libraries under the design tree
- a technology rules file
- an optional custom Python cell file named
<CELL>.py
Typical project structure:
design/<LIB>/<CELL>.schdesign/<LIB>/<CELL>.pydesign/<primitive_lib>/...tech/cic/<tech>.tech
Default Placement
Before any custom Python runs, cicpy derives a first placement from the instance list.
The default model relies heavily on naming:
- the non-numeric instance prefix becomes the placement group
- similarly named bus instances naturally stack together
- group order and stack composition are therefore strongly affected by schematic naming
That is why good schematic instance naming matters. If placement is wrong, the first fix is often to rename instances so the automatic grouping matches the intended physical columns.
Custom Python Hooks
If design/<LIB>/<CELL>.py exists, sch2mag imports it and uses its hook functions.
The supported hook functions are:
beforePlace(layout)afterPlace(layout)beforeRoute(layout)afterRoute(layout)beforePaint(layout)afterPaint(layout)
Not every cell uses every hook.
The execution order is:
beforePlace(layout)- default
place() afterPlace(layout)- internal dummy-route materialization and tile-map setup
beforeRoute(layout)- default
route() afterRoute(layout)beforePaint(layout)- late paint/post-processing stage
afterPaint(layout)addAllPorts()
beforePlace(layout)
Use this for coarse global controls such as:
- disabling default power paint with
layout.noPowerRoute = True - tuning group spacing with
layout.place_xspace,layout.place_yspace - changing group wrap behavior with
layout.place_groupbreak
This hook should set broad layout policy, not hand-place individual routes.
afterPlace(layout)
Use this to reshape the physical organization after the default placement exists.
Typical work here:
- create
CellGroups - create functional stacks with
group.addStack(...) - abut groups with
abutTop,abutBottom,abutLeft,abutRight - add taps with
stack.addTaps() - add dummy devices or route dummy devices
- update bounding boxes after structural edits
This is where analog floorplan intent usually belongs.
beforeRoute(layout)
Use this for explicit routing and supply hookup.
Typical calls are:
layout.addRouteRing(...)
layout.addPowerConnection(...)
layout.addRouteConnection(...)
layout.addConnectivityRoute(...)
layout.addOrthogonalConnectivityRoute(...)
This hook should express routing intent using the routing APIs, not by stamping arbitrary geometry unless there is no API for the job.
afterRoute(layout)
Use this for changes that depend on the normal routing pass already being finished.
Typical uses:
- inspect or adjust route results
- add small post-route geometry
- add cleanup that should not affect route discovery itself
If something can be expressed directly through the normal routing APIs, prefer beforeRoute(...).
beforePaint(layout)
Use this for late-stage work after routing but before the final paint/post-processing phase completes.
Typical uses:
- prepare geometry for late finishing
- do final state adjustments after route creation
- stage cleanup that should happen after the route graph is already settled
This is a less common hook than beforePlace, afterPlace, and beforeRoute.
afterPaint(layout)
Use this for the final structural adjustments before ports are added.
Typical uses:
- normalize origins
- final cleanup
- last-step geometry adjustments that should happen after placement and routing are complete
Placement APIs
The most common physical-organization APIs are:
group = layout.makeCellGroup("name")
stack = group.addStack("stack_name", instances)
stack.addTaps()
group.abutRight(other, space=...)
group.abutTop(other, space=...)
group.fillDummyTransistors(...)
These APIs let a cell be described in terms of physical branches and matched stacks, rather than individual coordinates.
Routing APIs
The two normal routing APIs are:
layout.addConnectivityRoute(layer, regex, routeType, options, cuts, excludeInstances, includeInstances)
layout.addOrthogonalConnectivityRoute(verticalLayer, horizontalLayer, regex, options, cuts, excludeInstances, includeInstances)
Use them as the default choice.
addConnectivityRoute(...)is the standard net-driven router for straight, L, U, and vertical routes.addOrthogonalConnectivityRoute(...)is better when the flow must follow explicit vertical/horizontal layer policy.
In analog sky130-style flows, that often means:
- M2 vertical trunks
- M3 horizontal branches
- M4 vertical trunks for higher-level routes
Terminal Access And Cuts
A recurring rule in cicpy is that terminal access should come from the primitive device layout, not from guessed landing bars.
Preferred pattern:
- use legal device access geometry already exposed by the primitive cell
- route into that access
- let the router place cuts
Current cut policy in this work:
- prefer only
1x2or2x1cuts - flatten cut geometry for Magic output when that is more robust than cut-cell references
Outputs
The main generated files are:
<CELL>.mag: Magic layout<CELL>.cic: JSON layout artifact
Important detail:
- the top-level
.cicproduced bysch2magusually contains the target cell and generated cut cells - referenced primitive library cells are typically not embedded in that same file
So for commands such as cicpy svg, include dependent library .cic files explicitly:
cicpy svg top.cic tech/cic/sky130A.tech TOP \
--I analog_lib.cic \
--I digital_lib.cic
That --I mechanism exists specifically so top-level generated cells can be rendered or inspected together with their referenced library cells.
Debug Flow
Fast iteration:
cicpy sch2mag <LIB> <CELL>
Deeper connectivity check:
cicpy sch2mag --check-connectivity <LIB> <CELL>
Use the fast route-short report first. Use --check-connectivity when the question is opens or split nets rather than obvious shorts.
Where Changes Should Go
The usual fix order is:
- fix schematic instance naming if grouping is wrong
- fix stack/group structure in
afterPlace(...) - fix routing intent in
beforeRoute(...) - only then add narrow custom geometry if the APIs still cannot express the requirement
That keeps the layout generator understandable and avoids burying physical intent in ad hoc geometry.
Concrete Example
LELOTEMP_CMP is a good example of this flow in practice:
beforePlace(...)disables default power routing and tightens placement spacingafterPlace(...)builds NMOS and PMOS groups, creates functional stacks, abuts them, and adds tapsbeforeRoute(...)adds power rings, supply hookups, and connectivity-driven signal routessch2mag --check-connectivityis then used when route geometry looks correct but opens or splits still need to be checked