0hmX/am3352
This code suite comprises TypeScript scripts that analyze, verify, and assemble complex DDR memory interface hardware, focusing on physical routing, via and pad placement, electrical clearance, and physical constraints, often involving precise geometric calculations and consistent provenance tracking.
- Version
- 1.0.5
- License
- unset
- Stars
- 0
tests/exits.test.ts
import { test, expect } from 'bun:test'
import { getAM3352Exits, transformAM3352Exit } from '../src/exits'
import { AM3352_SIGNAL_CONNECTIONS } from '../src/layout'
import { getAM3352TracePaths } from '../src/saved-paths'
import { LAYOUT_PROFILES, getAM3352Bounds, getAM3352Layers, type LayoutProfile } from '../src/profiles'
for (const profile of Object.keys(LAYOUT_PROFILES) as LayoutProfile[]) {
test(`${profile} exits cover every external signal and match actual saved copper`, () => {
const exits = getAM3352Exits(profile)
expect(new Set(exits.map(exit => exit.pinNumber))).toEqual(new Set(AM3352_SIGNAL_CONNECTIONS.map(signal => signal.pinNumber)))
expect(exits.length).toBe(LAYOUT_PROFILES[profile].signalExitCount)
for (const exit of exits) {
const path = getAM3352TracePaths(profile).find(path => path.connection === `U1.${exit.ball}`)!
expect(path.route.at(-1)).toMatchObject({ x: exit.x, y: exit.y, layer: exit.layer })
const bounds=getAM3352Bounds(profile)
expect(exit.side==='left'?exit.x:exit.side==='right'?exit.x:exit.y).toBeCloseTo(({left:bounds.minX,right:bounds.maxX,top:bounds.maxY,bottom:bounds.minY})[exit.side])
for (const direction of [exit.outwardDirection, exit.finalSegmentDirection, exit.sourceEscapeDirection]) expect(Math.hypot(direction.x, direction.y)).toBeCloseTo(1)
const length = path.route.slice(1).reduce((sum, point, index) => sum + Math.hypot(point.x - path.route[index]!.x, point.y - path.route[index]!.y), 0)
expect(exit.planarLengthMm).toBeCloseTo(length, 8)
expect(Object.values(exit.planarLengthByLayerMm).reduce((a, b) => a + b, 0)).toBeCloseTo(length, 8)
expect(exit.logicalVias.length).toBe(path.route.filter(point => point.route_type === 'via').length)
expect(exit.physicalViaLayers).toEqual(getAM3352Layers(profile))
}
})
}
test('placement rotates directions and via/exit coordinates without changing local side or layer', () => {
const exit = getAM3352Exits().find(exit => exit.logicalVias.length > 0)!
const before = structuredClone(exit)
const placed = transformAM3352Exit(exit, { pcbX: 25, pcbY: 10, pcbRotation: 90 })
expect(placed.x).toBeCloseTo(25 - exit.y, 9)
expect(placed.y).toBeCloseTo(10 + exit.x, 9)
expect(placed.outwardDirection.x).toBeCloseTo(-exit.outwardDirection.y, 9)
expect(placed.outwardDirection.y).toBeCloseTo(exit.outwardDirection.x, 9)
expect(placed.finalSegmentDirection.x).toBeCloseTo(-exit.finalSegmentDirection.y, 9)
expect(placed.sourceEscapeDirection.y).toBeCloseTo(exit.sourceEscapeDirection.x, 9)
expect(placed.logicalVias[0]!.x).toBeCloseTo(25 - exit.logicalVias[0]!.y, 9)
expect(placed.logicalVias[0]!.y).toBeCloseTo(10 + exit.logicalVias[0]!.x, 9)
expect(placed.side).toBe(exit.side)
expect(placed.layer).toBe(exit.layer)
expect(placed.planarLengthMm).toBe(exit.planarLengthMm)
expect(exit).toEqual(before)
const diagonal = transformAM3352Exit(exit, { pcbRotation: 37 })
expect(Math.hypot(diagonal.x, diagonal.y)).toBeCloseTo(Math.hypot(exit.x, exit.y), 9)
expect(Math.hypot(diagonal.outwardDirection.x, diagonal.outwardDirection.y)).toBeCloseTo(1, 9)
})
test('invalid profile and placement fail explicitly', () => {
expect(() => getAM3352Exits('missing' as LayoutProfile)).toThrow()
expect(() => transformAM3352Exit(getAM3352Exits()[0]!, { pcbRotation: NaN })).toThrow(/finite/)
})
test('bounds dimensions and per-side padding remain anchored at CPU center', () => {
for(const profile of Object.keys(LAYOUT_PROFILES) as LayoutProfile[]) {
const settings=LAYOUT_PROFILES[profile],bounds=getAM3352Bounds(profile),half=settings.packageSizeMm/2
expect(settings.fanoutWidthMm).toBeCloseTo(bounds.maxX-bounds.minX,9)
expect(settings.fanoutHeightMm).toBeCloseTo(bounds.maxY-bounds.minY,9)
expect(settings.padding.left).toBeCloseTo(-bounds.minX-half,9)
expect(settings.padding.right).toBeCloseTo(bounds.maxX-half,9)
expect(settings.padding.top).toBeCloseTo(bounds.maxY-half,9)
expect(settings.padding.bottom).toBeCloseTo(-bounds.minY-half,9)
bounds.minX=1000
expect(getAM3352Bounds(profile).minX).not.toBe(1000)
}
})