@tscircuit/fanout-solver 0.0.29 → 0.0.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -259,7 +259,7 @@ function getBusDepthInRows(bus: PreparedBus): number {
259
259
  )
260
260
  }
261
261
 
262
- function createPreferredLayerAssignment(params: {
262
+ function createInitialLayerAssignment(params: {
263
263
  buses: PreparedBus[]
264
264
  escapeLayers: string[]
265
265
  escapeLayersByBusId: Readonly<Record<string, readonly string[]>>
@@ -314,16 +314,16 @@ function createPreferredLayerAssignment(params: {
314
314
  }
315
315
 
316
316
  function prioritizeLayerAssignment(params: {
317
- preferredAssignment: Readonly<Record<string, string>>
317
+ initialAssignment: Readonly<Record<string, string>>
318
318
  generatedAssignments: Array<Readonly<Record<string, string>>>
319
319
  maxAssignments: number
320
320
  }): Array<Readonly<Record<string, string>>> {
321
- const { preferredAssignment, generatedAssignments, maxAssignments } = params
322
- const preferredKey = JSON.stringify(preferredAssignment)
321
+ const { initialAssignment, generatedAssignments, maxAssignments } = params
322
+ const initialKey = JSON.stringify(initialAssignment)
323
323
  return [
324
- preferredAssignment,
324
+ initialAssignment,
325
325
  ...generatedAssignments.filter(
326
- (assignment) => JSON.stringify(assignment) !== preferredKey,
326
+ (assignment) => JSON.stringify(assignment) !== initialKey,
327
327
  ),
328
328
  ].slice(0, maxAssignments)
329
329
  }
@@ -335,7 +335,12 @@ function getCandidateEscapeLayersForBus(params: {
335
335
  staticClearanceCache: RouteBusStaticClearanceCache
336
336
  }): string[] {
337
337
  const { bus, srj, config, staticClearanceCache } = params
338
- const individuallyRoutableLayers = config.escapeLayers.filter(
338
+ const busAllowedLayers = bus.allowedLayers
339
+ const allowedEscapeLayers =
340
+ busAllowedLayers === undefined
341
+ ? config.escapeLayers
342
+ : config.escapeLayers.filter((layer) => busAllowedLayers.includes(layer))
343
+ const individuallyRoutableLayers = allowedEscapeLayers.filter(
339
344
  (targetLayer) =>
340
345
  routeBus({
341
346
  srj,
@@ -357,9 +362,11 @@ function getCandidateEscapeLayersForBus(params: {
357
362
  // route this bus by itself cannot become viable later in an assignment.
358
363
  // Preserve the original candidates when none route so impossible problems
359
364
  // still produce the usual failed-solver result instead of throwing here.
360
- return individuallyRoutableLayers.length > 0
361
- ? individuallyRoutableLayers
362
- : config.escapeLayers
365
+ const candidateLayers =
366
+ individuallyRoutableLayers.length > 0
367
+ ? individuallyRoutableLayers
368
+ : allowedEscapeLayers
369
+ return candidateLayers
363
370
  }
364
371
 
365
372
  export class FanoutSolver extends BaseSolver {
@@ -400,6 +407,24 @@ export class FanoutSolver extends BaseSolver {
400
407
  this.config = resolveConfig(inputSrj, options)
401
408
  this.preparedBuses = prepareFanoutBuses(inputSrj, options)
402
409
  for (const bus of this.preparedBuses) {
410
+ for (const allowedLayer of bus.allowedLayers ?? []) {
411
+ if (!this.config.layerNames.includes(allowedLayer)) {
412
+ throw new Error(
413
+ `FanoutSolver: bus "${bus.busId}" allows unavailable layer "${allowedLayer}"`,
414
+ )
415
+ }
416
+ }
417
+ if (
418
+ bus.termination.type === "boundary" &&
419
+ bus.allowedLayers !== undefined &&
420
+ !bus.allowedLayers.some((layer) =>
421
+ this.config.escapeLayers.includes(layer),
422
+ )
423
+ ) {
424
+ throw new Error(
425
+ `FanoutSolver: bus "${bus.busId}" has no allowed layer in escapeLayers`,
426
+ )
427
+ }
403
428
  if (bus.termination.type !== "plane") continue
404
429
  const planeLayer = bus.termination.layer
405
430
  if (!this.config.layerNames.includes(planeLayer)) {
@@ -407,6 +432,14 @@ export class FanoutSolver extends BaseSolver {
407
432
  `FanoutSolver: plane-terminated bus "${bus.busId}" targets unavailable layer "${planeLayer}"`,
408
433
  )
409
434
  }
435
+ if (
436
+ bus.allowedLayers !== undefined &&
437
+ !bus.allowedLayers.includes(planeLayer)
438
+ ) {
439
+ throw new Error(
440
+ `FanoutSolver: plane-terminated bus "${bus.busId}" targets disallowed layer "${planeLayer}"`,
441
+ )
442
+ }
410
443
  if (
411
444
  bus.connections.some(
412
445
  (connection) => connection.sourceLayer === planeLayer,
@@ -454,7 +487,7 @@ export class FanoutSolver extends BaseSolver {
454
487
  ...fixedPlaneAssignments,
455
488
  }))
456
489
  this.layerAssignments = prioritizeLayerAssignment({
457
- preferredAssignment: createPreferredLayerAssignment({
490
+ initialAssignment: createInitialLayerAssignment({
458
491
  buses: this.preparedBuses,
459
492
  escapeLayers: this.config.escapeLayers,
460
493
  escapeLayersByBusId,
@@ -407,6 +407,26 @@ function resolvePreferredExit(
407
407
  return value
408
408
  }
409
409
 
410
+ function resolveAllowedLayers(
411
+ busId: string,
412
+ allowedLayers: readonly string[] | undefined,
413
+ ): string[] | undefined {
414
+ if (allowedLayers === undefined) return undefined
415
+ if (allowedLayers.length === 0) {
416
+ throw new Error(
417
+ `FanoutSolver: bus "${busId}" must allow at least one layer`,
418
+ )
419
+ }
420
+ for (const layer of allowedLayers) {
421
+ if (typeof layer !== "string" || layer.length === 0) {
422
+ throw new Error(
423
+ `FanoutSolver: bus "${busId}" has an invalid allowed layer`,
424
+ )
425
+ }
426
+ }
427
+ return [...new Set(allowedLayers)]
428
+ }
429
+
410
430
  export function resolveAvailableBoundaryRegions(
411
431
  value: readonly FanoutAvailableCornerAndSideInput[] | undefined,
412
432
  ): AvailableBoundaryRegion[] | undefined {
@@ -491,6 +511,10 @@ function resolveBusSpecs(
491
511
  (requestedBus as FanoutBusSpec).preferredExit ??
492
512
  options.defaultPreferredExit,
493
513
  )
514
+ const allowedLayers = resolveAllowedLayers(
515
+ requestedBus.busId,
516
+ (requestedBus as FanoutBusSpec).allowedLayers,
517
+ )
494
518
  if (termination.type === "plane" && preferredExit !== undefined) {
495
519
  throw new Error(
496
520
  `FanoutSolver: plane-terminated bus "${requestedBus.busId}" cannot also specify preferredExit`,
@@ -506,6 +530,7 @@ function resolveBusSpecs(
506
530
  (requestedBus as FanoutBusSpec).direction ??
507
531
  options.defaultDirection,
508
532
  preferredExit,
533
+ ...(allowedLayers === undefined ? {} : { allowedLayers }),
509
534
  termination,
510
535
  })
511
536
  }
@@ -1020,6 +1045,7 @@ export function prepareFanoutBuses(
1020
1045
  busId: busSpec.busId,
1021
1046
  direction: resolvedExit.direction,
1022
1047
  preferredExit: resolvedExit.preferredExit,
1048
+ allowedLayers: busSpec.allowedLayers,
1023
1049
  termination: busSpec.termination ?? { type: "boundary" },
1024
1050
  connections: preparedConnections,
1025
1051
  componentId: sourceGrid.componentId,
package/lib/types.ts CHANGED
@@ -76,6 +76,8 @@ export interface FanoutBusSpec extends SimpleRouteBus {
76
76
  sourceComponentId?: string
77
77
  direction?: FanoutDirection
78
78
  preferredExit?: FanoutBorderTarget
79
+ /** Layers to which this bus is allowed to escape. */
80
+ allowedLayers?: readonly string[]
79
81
  /**
80
82
  * Preferred downstream routing point for each connection after it leaves the
81
83
  * fanout boundary. This is routing guidance only and does not replace the
@@ -244,6 +246,8 @@ export interface PreparedBus {
244
246
  busId: string
245
247
  direction: FanoutDirection
246
248
  preferredExit?: FanoutBorderTarget
249
+ /** Layers to which this bus is allowed to escape. */
250
+ allowedLayers?: readonly string[]
247
251
  termination: FanoutBusTermination
248
252
  connections: PreparedConnection[]
249
253
  componentId: string
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fanout-solver",
3
- "version": "0.0.29",
3
+ "version": "0.0.31",
4
4
  "description": "BGA fanout solver with coordinated bus-layer escapes for SimpleRouteJson",
5
5
  "module": "lib/index.ts",
6
6
  "type": "module",