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
scripts/import-ddr-host-capture.ts
/** Import a caller's full RAM component capture, preserving reference copper and
* interior trunk taps. No routing or electrical signoff is asserted here. */
import {readFileSync,writeFileSync,mkdirSync} from 'node:fs'
import {resolve} from 'node:path'
import {createHash} from 'node:crypto'
import {sanitizeDdrPortIdentities} from './sanitize-ddr-port-identities'
const [ramDirectory,captureDirectory]=process.argv.slice(2)
if(!ramDirectory||!captureDirectory)throw Error('Usage: RAM-project capture-directory')
const read=(file:string)=>JSON.parse(readFileSync(file,'utf8'))
const raw=read(resolve(captureDirectory,'unrouted.circuit.json')),sourceInput=read(resolve(captureDirectory,'routing-input.json'))
const sanitized=sanitizeDdrPortIdentities(raw),original=sanitized.circuit
const {getAM3352FixedObstacles}=await import(resolve(ramDirectory,'scripts/am3352-fixed-obstacles.ts'))
const {getRamFanoutTerminals,getRamFanoutTracePaths,adaptRamPathsToHost}=await import(resolve(ramDirectory,'src/index.tsx'))
const captureReport=read(resolve(captureDirectory,'capture-report.json'))
if(captureReport.emittedErrors?.length||captureReport.cachedPathsPreserved!==457)throw Error('Capture did not preserve the complete rendered interface')
const hostProfiles=['am3352-byte0','am3352-byte1']
const native=getRamFanoutTracePaths('bus_grouped')
const memory={profile:'bus_grouped',hostProfiles,placements:captureReport.placements,pathsByByte:hostProfiles.map(p=>adaptRamPathsToHost(native,p)),terminalsByByte:hostProfiles.map(p=>getRamFanoutTerminals('bus_grouped',p)),sourceSha256:createHash('sha256').update(JSON.stringify(native)).digest('hex')}
if(memory.sourceSha256!==captureReport.sourceSha256)throw Error('RAM cache changed after component capture')
for(const [byte,paths] of memory.pathsByByte.entries()){const placement=memory.placements[byte];if(placement.pcbRotation!==180)throw Error('This import requires the captured 180-degree RAM orientation');for(const path of paths){const route=path.route.map((p:any)=>({...p,x:placement.pcbX-Number(p.x),y:placement.pcbY-Number(p.y)}));if(!original.some((t:any)=>t.type==='pcb_trace'&&t.route.length===route.length&&t.route.every((p:any,i:number)=>{const q=route[i];return p.route_type===q.route_type&&Math.hypot(p.x-q.x,p.y-q.y)<1e-7&&(p.route_type==='wire'?p.layer===q.layer&&Math.abs(p.width-q.width)<1e-8:p.from_layer===q.from_layer&&p.to_layer===q.to_layer)})))throw Error(`Current RAM host path differs from captured copper: byte${byte} ${path.connection}`)}}
const byId=new Map<string,any>();for(const e of original)for(const [k,v] of Object.entries(e))if(k===`${e.type}_id`)byId.set(String(v),e)
const signalMap=original.filter((e:any)=>e.type==='source_trace'&&/^RAM[01]_.*_external$/.test(e.name??'')).map((e:any)=>{
const [,byte,terminal]=e.name.match(/^RAM([01])_(.*)_external$/),ports=e.connected_source_port_ids.map((id:string)=>byId.get(id))
const cpu=ports.find((p:any)=>p.port_hints?.some((h:string)=>h.startsWith('DDR_'))),ram=ports.find((p:any)=>p.port_hints?.includes(`SIG_${terminal}`))
if(!cpu||!ram)throw Error(`Unresolved explicit DDR source ports: ${e.name}`)
const connection=sourceInput.connections.find((c:any)=>c.source_trace_id===e.source_trace_id)
if(!connection||connection.pointsToConnect.length!==2)throw Error(`Missing branch: ${e.name}`)
const ids=connection.pointsToConnect.map((p:any)=>{const endpoint=byId.get(p.pointId);if(!endpoint||endpoint.type!=='pcb_breakout_point'||Math.hypot(endpoint.x-p.x,endpoint.y-p.y)>1e-7||endpoint.layer!==p.layer)throw Error(`Invalid actual exit identity: ${e.name}`);return endpoint.source_port_id}).sort()
if(JSON.stringify(ids)!==JSON.stringify([cpu.source_port_id,ram.source_port_id].sort()))throw Error(`Branch points disagree with explicit source ports: ${e.name}`)
return {sourceTraceId:e.source_trace_id,name:e.name,ramByte:Number(byte),ramTerminal:terminal,cpuSignal:cpu.port_hints.find((h:string)=>h.startsWith('DDR_')),cpuBall:cpu.name,ramBall:ram.name}
})
if(signalMap.length!==78||new Set(signalMap.map((s:any)=>s.cpuSignal)).size!==50)throw Error('Incomplete two-byte interface')
// Reconstruct physical obstacles from the emitted copper, without inheriting
// any aliases inferred by an earlier captured obstacle list.
const fixed=getAM3352FixedObstacles(original,[])
const input={...sourceInput,obstacles:fixed.obstacles,connections:sourceInput.connections.map((c:any)=>({...c,...Object.fromEntries(Object.entries(signalMap.find((s:any)=>s.sourceTraceId===c.source_trace_id)!).filter(([k])=>['ramByte','ramTerminal'].includes(k)))}))}
const captureHash=createHash('sha256').update(JSON.stringify({original,memory,signalMap,input})).digest('hex')
input.__ddrCaptureHash=captureHash
const out=resolve('dist/ddr-system',`host-taps-${captureHash.slice(0,12)}`);mkdirSync(out,{recursive:true})
const snapshot={...memory,captureHash}
const files={'unrouted.circuit.json':original,'memory-source-snapshot.json':snapshot,'signal-map.json':signalMap,'routing-input.json':input,'capture-report.json':{...captureReport,captureHash,status:'unrouted-host-tap-draft',portIdentityAudit:sanitized.report,fixedCopperCoverage:fixed.report}}
for(const [name,data] of Object.entries(files))writeFileSync(resolve(out,name),JSON.stringify(data,null,2)+'\n')
const hash=(name:string)=>createHash('sha256').update(readFileSync(resolve(out,name))).digest('hex')
writeFileSync(resolve(out,'provenance.json'),JSON.stringify({captureHash,files:Object.fromEntries(['unrouted.circuit.json','memory-source-snapshot.json','signal-map.json','routing-input.json'].map(name=>[name,hash(name)]))},null,2)+'\n')
console.log(JSON.stringify({out,captureHash,branches:signalMap.length,physicalObstacles:fixed.obstacles.length,trunkTaps:memory.terminalsByByte.map((ts:any[])=>ts.filter(t=>t.interface==='shared-trunk-tap').length),referencePours:original.filter((e:any)=>e.type==='pcb_copper_pour').length}))