The A* routing functions (add_route_astar and add_bundle_astar) don't
search the layout continuously — internally they project everything onto a
uniform grid and search over that. Two consequences for you as a user:
- Routes always step on grid lines. The router won't return a path finer than the grid pitch.
- Obstacles are taken cell-by-cell. A cell that any obstacle polygon touches is treated as fully blocked.
The grid_unit parameter (in dbu) controls how fine that grid is. There's a
trade-off:
- Coarser grids (larger
grid_unit) search faster, but can miss valid routes that thread through narrow gaps and produce more "boxy" paths around obstacles. - Finer grids (smaller
grid_unit) follow obstacles more accurately and squeeze through tighter spaces, but the search space grows fast — A* slows down quadratically with the grid pitch.
There's also a hard lower bound: grid_unit must be at most half the
bend radius. The router enforces this because the bend's geometry
needs at least two grid cells along each axis to be representable. A
useful starting point is somewhere in [radius / 20, radius / 4] —
this notebook shows the difference between the two ends of that range.
Imports
A Route at a Coarse Grid
Routing on field1 with grid_unit = 2500 dbu (2.5 µm — exactly half
the 5 µm bend radius, the coarsest the router will accept). The
search has limited freedom: every step has to be a multiple of 2.5 µm,
so the route can only make wide detours and the path looks blocky.
c = gf.Component()
ref = c << dr.pcells.field1()
c.add_ports(ref)
dr.add_route_astar(
c=c,
start=ref.cell.insts["in"].ports["o2"],
stop=ref.cell.insts["out"].ports["o1"],
straight="straight",
bend={"component": "bend_euler", "settings": {"radius": 5}},
layers=["WG"],
grid_unit=2500,
)
dr.util.show_cell(c)
API key for organization 'DoPlayDo' found.
Same Route at a Finer Grid
Same start, stop, and obstacles, with grid_unit = 250 dbu (0.25 µm —
a twentieth of the bend radius). The router has many more grid points
to work with, so the path can hug obstacles closely and take a more
compact route.
c = gf.Component()
ref = c << dr.pcells.field1()
c.add_ports(ref)
dr.add_route_astar(
c=c,
start=ref.cell.insts["in"].ports["o2"],
stop=ref.cell.insts["out"].ports["o1"],
straight="straight",
bend={"component": "bend_euler", "settings": {"radius": 5}},
layers=["WG"],
grid_unit=250,
)
dr.util.show_cell(c)
API key for organization 'DoPlayDo' found.
Picking grid_unit
Things to keep in mind when you're tuning it:
- Hard upper bound: half the bend radius. The router refuses
anything coarser. For a 5 µm radius,
grid_unitmust be ≤ 2.5 µm (2500 dbu). - Practical sweet spot: between
radius / 20andradius / 4. Below that lower bound the search slows down without producing meaningfully different routes; above the upper bound the route starts to look boxy. - Match it to your obstacle dimensions. If your floorplan has 2 µm
routing channels, a
grid_unitclose to 2 µm risks collapsing them into "obstacle cells" — A* will then refuse to route through them. - If A* gives up with
RuntimeError: No route found!, halvinggrid_unitand re-running is the first thing to try. The narrow gap that blocked it may simply have been below the grid resolution.
The same advice applies to the bundle routers — add_bundle_astar
takes the same grid_unit parameter.
Next steps
- For the routing calls this notebook tunes, see A* Routing (single wire) and A* Bundle Routing (multi-wire).
- If routing fails at any grid size, the route may just not be possible. Check the layout for blocked corridors, or try Corners / Steps to specify the path manually.