diff --git a/README.md b/README.md index b3cdb0bb..aaeacc72 100644 --- a/README.md +++ b/README.md @@ -352,6 +352,20 @@ export interface AutoroutingPhaseProps extends RoutingTolerances { * boundary where fanout traces terminate. */ fanoutBoundaryPadding?: FanoutBoundaryPadding; + /** + * Copper layers available to boundary-terminated fanout buses. Source-only + * traces whose nets are mapped by `fanoutPourNetMap` terminate on their + * mapped plane layer. + */ + fanoutRoutingLayers?: LayerRefInput[]; + /** + * Maps copper layers to the net or nets poured on them. During fanout, + * source-only traces on those nets drop to the mapped layer instead of + * routing to the breakout boundary. + * + * This is inferred from `` components when omitted. + */ + fanoutPourNetMap?: FanoutPourNetMap; } ``` diff --git a/generated/COMPONENT_TYPES.md b/generated/COMPONENT_TYPES.md index 90272095..4bf31f42 100644 --- a/generated/COMPONENT_TYPES.md +++ b/generated/COMPONENT_TYPES.md @@ -155,6 +155,35 @@ export const pcbCoordinate = calcString.or(baseDistance) export { length } ``` +### fanoutBoundaryPadding + +```typescript +export interface DirectionalFanoutBoundaryPadding { + top?: Distance + right?: Distance + bottom?: Distance + left?: Distance +} +/** + * Padding between the union of the fanout source pads and the shared boundary + * where fanout traces terminate. Omitted directional values are treated as + * zero. + */ +export type FanoutBoundaryPadding = Distance | DirectionalFanoutBoundaryPadding + +const nonnegativeDistance = distance.refine((value) => value >= 0, { + message: "Fanout boundary padding cannot be negative", +}) +export const fanoutBoundaryPadding = z.union([ + nonnegativeDistance, + z.object({ + top: nonnegativeDistance.optional(), + right: nonnegativeDistance.optional(), + bottom: nonnegativeDistance.optional(), + left: nonnegativeDistance.optional(), + }), +``` + ### footprintProp ```typescript @@ -1148,10 +1177,16 @@ export interface AutoroutingPhaseProps extends RoutingTolerances { connections?: string[] reroute?: boolean busFanoutDirections?: Record + fanoutBoundaryPadding?: FanoutBoundaryPadding + fanoutRoutingLayers?: LayerRefInput[] + fanoutPourNetMap?: FanoutPourNetMap } /** - * Fanout direction for each named bus in this phase. `center` leaves the - * direction unconstrained. + * Maps copper layers to the net or nets poured on them. During fanout, + * source-only traces on those nets drop to the mapped layer instead of + * routing to the breakout boundary. + * + * This is inferred from `` components when omitted. */ export const autoroutingPhaseProps = z .object({ @@ -1173,6 +1208,11 @@ export const autoroutingPhaseProps = z connections: z.array(z.string()).optional(), reroute: z.boolean().optional(), busFanoutDirections: z.record(busFanoutDirection).optional(), + fanoutBoundaryPadding: fanoutBoundaryPadding.optional(), + fanoutRoutingLayers: z.array(layer_ref).min(1).optional(), + fanoutPourNetMap: z + .record(layer_ref, z.union([z.string(), z.array(z.string()).min(1)])) + .optional(), }) ``` @@ -1263,10 +1303,12 @@ export interface BreakoutProps paddingRight?: Distance paddingTop?: Distance paddingBottom?: Distance + fanoutBoundaryPadding?: FanoutBoundaryPadding } /** - * Autorouter used to escape the components inside the breakout boundary. - * Defaults to the multilayer fanout autorouter. + * Padding between the union of the fanout source pads and the shared + * boundary where fanout traces terminate. This is independent of the + * breakout group's layout padding. */ export const breakoutProps = subcircuitGroupProps.extend({ autorouter: autorouterProp.default("fanout"), @@ -1275,6 +1317,7 @@ export const breakoutProps = subcircuitGroupProps.extend({ paddingRight: distance.optional(), paddingTop: distance.optional(), paddingBottom: distance.optional(), + fanoutBoundaryPadding: fanoutBoundaryPadding.optional(), }) ``` diff --git a/generated/PROPS_OVERVIEW.md b/generated/PROPS_OVERVIEW.md index dc969ebb..db2c5db7 100644 --- a/generated/PROPS_OVERVIEW.md +++ b/generated/PROPS_OVERVIEW.md @@ -210,6 +210,25 @@ export interface AutoroutingPhaseProps extends RoutingTolerances { * direction unconstrained. */ busFanoutDirections?: Record + /** + * Padding between the union of the fanout source pads and the shared + * boundary where fanout traces terminate. + */ + fanoutBoundaryPadding?: FanoutBoundaryPadding + /** + * Copper layers available to boundary-terminated fanout buses. Source-only + * traces whose nets are mapped by `fanoutPourNetMap` terminate on their + * mapped plane layer. + */ + fanoutRoutingLayers?: LayerRefInput[] + /** + * Maps copper layers to the net or nets poured on them. During fanout, + * source-only traces on those nets drop to the mapped layer instead of + * routing to the breakout boundary. + * + * This is inferred from `` components when omitted. + */ + fanoutPourNetMap?: FanoutPourNetMap } @@ -408,11 +427,22 @@ export interface BreakoutPointProps export interface BreakoutProps extends Omit { + /** + * Autorouter used to escape the components inside the breakout boundary. + * Defaults to the multilayer fanout autorouter. + */ + autorouter?: AutorouterProp padding?: Distance paddingLeft?: Distance paddingRight?: Distance paddingTop?: Distance paddingBottom?: Distance + /** + * Padding between the union of the fanout source pads and the shared + * boundary where fanout traces terminate. This is independent of the + * breakout group's layout padding. + */ + fanoutBoundaryPadding?: FanoutBoundaryPadding } @@ -878,6 +908,14 @@ export interface DiodeProps } +export interface DirectionalFanoutBoundaryPadding { + top?: Distance + right?: Distance + bottom?: Distance + left?: Distance +} + + export interface DrcCheckProps { name?: string checkFn: CustomDrcCheckFn diff --git a/lib/components/autoroutingphase.ts b/lib/components/autoroutingphase.ts index c2723b7c..e2238f0a 100644 --- a/lib/components/autoroutingphase.ts +++ b/lib/components/autoroutingphase.ts @@ -1,5 +1,6 @@ import { expectTypesMatch } from "lib/typecheck" import { z } from "zod" +import { layer_ref, type LayerRef, type LayerRefInput } from "circuit-json" import { type FanoutBoundaryPadding, fanoutBoundaryPadding, @@ -22,6 +23,10 @@ export type BusFanoutDirection = direction: NinePointAnchor } +export type FanoutPourNetMap = Partial< + Record, string | string[]> +> + export interface AutoroutingPhaseProps extends RoutingTolerances { key?: any name?: string @@ -47,6 +52,20 @@ export interface AutoroutingPhaseProps extends RoutingTolerances { * boundary where fanout traces terminate. */ fanoutBoundaryPadding?: FanoutBoundaryPadding + /** + * Copper layers available to boundary-terminated fanout buses. Source-only + * traces whose nets are mapped by `fanoutPourNetMap` terminate on their + * mapped plane layer. + */ + fanoutRoutingLayers?: LayerRefInput[] + /** + * Maps copper layers to the net or nets poured on them. During fanout, + * source-only traces on those nets drop to the mapped layer instead of + * routing to the breakout boundary. + * + * This is inferred from `` components when omitted. + */ + fanoutPourNetMap?: FanoutPourNetMap } const busFanoutDirection = z.union([ @@ -75,6 +94,10 @@ export const autoroutingPhaseProps = z reroute: z.boolean().optional(), busFanoutDirections: z.record(busFanoutDirection).optional(), fanoutBoundaryPadding: fanoutBoundaryPadding.optional(), + fanoutRoutingLayers: z.array(layer_ref).min(1).optional(), + fanoutPourNetMap: z + .record(layer_ref, z.union([z.string(), z.array(z.string()).min(1)])) + .optional(), }) .superRefine((value, ctx) => { if ( diff --git a/tests/autoroutingphase.test.ts b/tests/autoroutingphase.test.ts index 33f69363..55c7ac7a 100644 --- a/tests/autoroutingphase.test.ts +++ b/tests/autoroutingphase.test.ts @@ -42,6 +42,27 @@ test("autorouting phase accepts per-bus fanout directions", () => { expect(autoroutingPhaseProps.parse(raw)).toEqual(raw) }) +test("autorouting phase accepts fanout routing layers", () => { + const parsed = autoroutingPhaseProps.parse({ + autorouter: "fanout", + fanoutRoutingLayers: ["top", { name: "inner3" }, "bottom"], + }) + + expect(parsed.fanoutRoutingLayers).toEqual(["top", "inner3", "bottom"]) +}) + +test("autorouting phase accepts a fanout pour net map", () => { + const raw = { + autorouter: "fanout", + fanoutPourNetMap: { + inner1: "GND", + inner2: ["VCC_CORE", "VCC_IO"], + }, + } satisfies AutoroutingPhaseProps + + expect(autoroutingPhaseProps.parse(raw)).toEqual(raw) +}) + test("autorouting phase accepts scalar fanout boundary padding", () => { const raw = { autorouter: "fanout", diff --git a/tests/bus.test.ts b/tests/bus.test.ts index 0fee5925..97c49c01 100644 --- a/tests/bus.test.ts +++ b/tests/bus.test.ts @@ -2,19 +2,10 @@ import { expect, test } from "bun:test" import { busProps, type BusProps } from "lib/components/bus" test("busProps accepts two or more connection references", () => { - const rawProps: BusProps = { + const rawProps = { name: "DATA", connections: ["D0", ".U1 > .D1"], - } + } satisfies BusProps expect(busProps.parse(rawProps)).toEqual(rawProps) }) - -test("busProps rejects a single connection", () => { - expect( - busProps.safeParse({ - name: "DATA", - connections: ["D0"], - }).success, - ).toBe(false) -})