Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions lib/components/primitive-components/Group/Group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ import { Group_doInitialSchematicTraceRender } from "./Group_doInitialSchematicT
import { Group_doInitialSimulationSpiceEngineRender } from "./Group_doInitialSimulationSpiceEngineRender"
import { Group_doInitialSourceAddConnectivityMapKey } from "./Group_doInitialSourceAddConnectivityMapKey"
import { Group_getRoutingPhasePlans } from "./Group_getRoutingPhasePlans"
import { Group_getFanoutPourNetMap } from "./Group_getFanoutPourNetMap"
import {
cacheLocalAutoroutingPhaseResult,
getCachedLocalAutoroutingPhaseResult,
Expand Down Expand Up @@ -924,14 +925,6 @@ export class Group<Props extends z.ZodType<any, any, any> = typeof groupProps>
props.defaultTraceWidth ?? props.nominalTraceWidth ?? minTraceWidth,
)

const { simpleRouteJson: baseSimpleRouteJson } =
getSimpleRouteJsonFromCircuitJson({
db,
minTraceWidth,
nominalTraceWidth,
subcircuit_id: this.subcircuit_id,
subcircuitComponent: this,
})
const routingPhasePlans = this._getRoutingPhasePlans()
const hasPhasedAutorouting = Group_hasPhasedAutorouting(routingPhasePlans)
const routingStages = routingPhasePlans.flatMap((routingPhasePlan) => {
Expand All @@ -950,6 +943,21 @@ export class Group<Props extends z.ZodType<any, any, any> = typeof groupProps>
routingPhasePlan,
}))
})
const hasFanoutStage = routingStages.some(({ autorouterConfig }) =>
["fanout", "single_layer_fanout"].includes(autorouterConfig.preset ?? ""),
)
const fanoutPourNetMap = hasFanoutStage
? Group_getFanoutPourNetMap(this, routingPhasePlans)
: undefined
const { simpleRouteJson: baseSimpleRouteJson } =
getSimpleRouteJsonFromCircuitJson({
db,
minTraceWidth,
nominalTraceWidth,
subcircuit_id: this.subcircuit_id,
subcircuitComponent: this,
fanoutPourNetMap,
})
const outputTraces: SimplifiedPcbTrace[] = []
const outputJumpers: Array<{
jumper_footprint: string
Expand Down Expand Up @@ -1174,6 +1182,7 @@ export class Group<Props extends z.ZodType<any, any, any> = typeof groupProps>
commonAutorouterOptions,
busFanoutDirections: routingPhasePlan.busFanoutDirections,
fanoutBoundaryPadding: routingPhasePlan.fanoutBoundaryPadding,
fanoutRoutingLayers: routingPhasePlan.fanoutRoutingLayers,
})
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export interface RoutingPhasePlan {
connectionSelectors?: string[]
busFanoutDirections?: AutoroutingPhaseProps["busFanoutDirections"]
fanoutBoundaryPadding?: AutoroutingPhaseProps["fanoutBoundaryPadding"]
fanoutRoutingLayers?: string[]
fanoutPourNetMap?: AutoroutingPhaseProps["fanoutPourNetMap"]
drcTolerances?: RoutingPhaseDrcTolerances
nets: Net[]
traces: Trace[]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import type { FanoutPourNetMap } from "@tscircuit/props"
import type { PrimitiveComponent } from "../../base-components/PrimitiveComponent"
import type { CopperPour } from "../CopperPour/CopperPour"
import type { Group } from "./Group"
import type { RoutingPhasePlan } from "./GroupRoutingPhasePlan"
import type { ISubcircuit } from "./Subcircuit/ISubcircuit"

const addPourNetMap = (
target: Map<string, Set<string>>,
pourNetMap: FanoutPourNetMap,
) => {
for (const [layer, netOrNets] of Object.entries(pourNetMap)) {
if (!netOrNets) continue
const nets = Array.isArray(netOrNets) ? netOrNets : [netOrNets]
const layerNets = target.get(layer) ?? new Set<string>()
for (const net of nets) layerNets.add(net)
target.set(layer, layerNets)
}
}

const toFanoutPourNetMap = (
netsByLayer: Map<string, Set<string>>,
): FanoutPourNetMap =>
Object.fromEntries(
Array.from(netsByLayer, ([layer, nets]) => [
layer,
nets.size === 1 ? Array.from(nets)[0]! : Array.from(nets),
]),
)

/**
* Gets the plane intent for fanout routing. An explicit phase map is
* authoritative. When no phase supplies one, copper pours in the current or
* enclosing subcircuits are used.
*/
export const Group_getFanoutPourNetMap = (
group: Group<any>,
routingPhasePlans: RoutingPhasePlan[],
): FanoutPourNetMap | undefined => {
const explicitMaps = routingPhasePlans
.map((plan) => plan.fanoutPourNetMap)
.filter((map): map is FanoutPourNetMap => map !== undefined)

const netsByLayer = new Map<string, Set<string>>()
if (explicitMaps.length > 0) {
for (const explicitMap of explicitMaps) {
addPourNetMap(netsByLayer, explicitMap)
}
return toFanoutPourNetMap(netsByLayer)
}

let scope: PrimitiveComponent | null = group
while (scope) {
if (scope.isSubcircuit) {
const subcircuit = scope as unknown as ISubcircuit
const copperPours = subcircuit.selectAll<CopperPour>("copperpour")
for (const copperPour of copperPours) {
if (copperPour.getSubcircuit() !== subcircuit) continue
const { layer, connectsTo } = copperPour._parsedProps
addPourNetMap(netsByLayer, {
[layer]: connectsTo,
})
}
}
scope = scope.parent
}

return netsByLayer.size > 0 ? toFanoutPourNetMap(netsByLayer) : undefined
}
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,10 @@ export function Group_getRoutingPhasePlans(
plan.busFanoutDirections = phaseProps?.busFanoutDirections
plan.fanoutBoundaryPadding =
phaseProps?.fanoutBoundaryPadding ?? groupFanoutBoundaryPadding
plan.fanoutRoutingLayers = phaseProps?.fanoutRoutingLayers?.map((layer) =>
typeof layer === "string" ? layer : layer.name,
)
plan.fanoutPourNetMap = phaseProps?.fanoutPourNetMap
plan.drcTolerances = phaseProps
? getDrcTolerancesFromAutoroutingPhaseProps(phaseProps)
: undefined
Expand Down
53 changes: 53 additions & 0 deletions lib/utils/autorouting/FanoutAutorouter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
FanoutSolver,
type FanoutBorderTarget,
type FanoutDirection,
type FanoutSolverOptions,
} from "@tscircuit/fanout-solver"
import type {
Expand All @@ -25,6 +26,7 @@ export interface FanoutAutorouterOptions {
mode: FanoutAutorouterMode
busFanoutDirections?: Readonly<Record<string, BusFanoutDirection>>
fanoutBoundaryPadding?: FanoutBoundaryPadding
fanoutRoutingLayers?: string[]
}

const getNinePointAnchor = (
Expand Down Expand Up @@ -59,6 +61,27 @@ const getFanoutBorderTarget = (
}
}

const getPlaneFanoutDirection = (
anchor: NinePointAnchor,
): FanoutDirection | undefined => {
switch (anchor) {
case "top_left":
case "top_center":
case "top_right":
return "up"
case "center_left":
return "left"
case "center":
return undefined
case "center_right":
return "right"
case "bottom_left":
case "bottom_center":
case "bottom_right":
return "down"
}
}

const createDownstreamSimpleRouteJson = ({
fanoutSimpleRouteJson,
sourceComponentIds,
Expand Down Expand Up @@ -151,6 +174,11 @@ export class FanoutAutorouter implements GenericLocalAutorouter {
| undefined {
if (!this.options.busFanoutDirections) return undefined
const knownBusIds = new Set(this.input.buses?.map((bus) => bus.busId) ?? [])
const planeBusIds = new Set(
this.input.buses
?.filter((bus) => bus.termination?.type === "plane")
.map((bus) => bus.busId) ?? [],
)
const preferences: Record<string, FanoutBorderTarget> = {}
for (const [busId, fanoutDirection] of Object.entries(
this.options.busFanoutDirections,
Expand All @@ -160,17 +188,42 @@ export class FanoutAutorouter implements GenericLocalAutorouter {
`Fanout direction references unknown bus "${busId}" in this autorouting phase`,
)
}
if (planeBusIds.has(busId)) continue
const target = getFanoutBorderTarget(getNinePointAnchor(fanoutDirection))
if (target) preferences[busId] = target
}
return Object.keys(preferences).length > 0 ? preferences : undefined
}

private getPlaneBusDirections():
| Readonly<Record<string, FanoutDirection>>
| undefined {
if (!this.options.busFanoutDirections) return undefined
const planeBusIds = new Set(
this.input.buses
?.filter((bus) => bus.termination?.type === "plane")
.map((bus) => bus.busId) ?? [],
)
const directions: Record<string, FanoutDirection> = {}
for (const [busId, fanoutDirection] of Object.entries(
this.options.busFanoutDirections,
)) {
if (!planeBusIds.has(busId)) continue
const direction = getPlaneFanoutDirection(
getNinePointAnchor(fanoutDirection),
)
if (direction) directions[busId] = direction
}
return Object.keys(directions).length > 0 ? directions : undefined
}

private getFanoutSolverOptions(): FanoutSolverOptions {
const commonOptions: FanoutSolverOptions = {
borderDistribution: "even",
compactBusTracks: true,
busDirections: this.getPlaneBusDirections(),
busExitPreferences: this.getBusExitPreferences(),
escapeLayers: this.options.fanoutRoutingLayers,
}
if (this.options.mode === "single_layer_fanout") {
return {
Expand Down
10 changes: 10 additions & 0 deletions lib/utils/autorouting/SimpleRouteJson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,20 @@ export type SimpleRouteDifferentialPair = {
}

/** A group of connections that an autorouter should keep together. */
export type SimpleRouteBusTermination =
| {
type: "boundary"
}
| {
type: "plane"
layer: string
}

export type SimpleRouteBus = {
busId: string
name?: string
connectionNames: SrjConnectionName[]
termination?: SimpleRouteBusTermination
}

export type SimpleRouteJson = Omit<
Expand Down
Loading
Loading