astra/am3352-sbc
A 4-layer AM3352 single-board computer with 128‑MiB DDR3, PMIC power regulation, USB‑C 5‑V PD input, dual USB host ports, HDMI output with ESD protection, microSD/UART/JTAG interfaces, addressable RGB indicators, and a PWM-driven buzzer.
- Version
- 0.1.19
- License
- unset
- Stars
- 0
tests/ddr-compliance.test.ts
import { describe, test, expect } from "bun:test";
import type { SimpleRouteJson, Trace } from "@tscircuit/bus-lanes-solver";
import { DDR_RULES, ddrTimingGroups, ddrPairs, ddrTraceName } from "../design/ddr-rules";
import { sharedNets } from "../design/ddr-netlist";
import { assertDdrConstraints, auditDdrGeometry } from "../design/ddr-compliance";
function fixture() {
const names=Object.fromEntries(sharedNets.map((n,i)=>[`c${i}`,ddrTraceName(n)]));
const id=(name:string)=>Object.keys(names).find(k=>names[k]===name)!;
const input:SimpleRouteJson={layerCount:4,minTraceWidth:.1,allowedLayers:["inner1","inner2"],obstacles:[],bounds:{minX:-40,maxX:40,minY:-40,maxY:40},
connections:sharedNets.map((signal,i)=>{const ca=ddrTimingGroups[2].signals.includes(signal);return {name:`c${i}`,pointsToConnect:[{x:0,y:0,layer:"top"},{x:ca?14:20,y:ca?14:0,layer:"top"}]};}),
buses:ddrTimingGroups.map(g=>({busId:g.name,name:g.name,connectionNames:g.signals.map(id),maxLengthSkew:g.maxLengthSkew,allowedLayers:["inner1","inner2"]})),
differentialPairs:ddrPairs.map(p=>({connectionNames:p.signals.map(id) as [string,string],lengthTolerance:DDR_RULES.pairSkewMm})),
};
const traces:Trace[]=input.connections.map(c=>{
const [a,b]=c.pointsToConnect,ca=b.y!==0, layer="inner1";
const wire=(x:number,y:number,l=layer)=>({route_type:"wire" as const,x,y,layer:l,width:.1});
return {connection_name:c.name,route:[wire(a.x,a.y,"top"),wire(.25,0,"top"),{route_type:"via",x:.25,y:0,from_layer:"top",to_layer:layer},wire(.25,0),
...(ca?[wire(.25,-3.81),wire(b.x-.25,-3.81),wire(b.x-.25,b.y)]:[wire(b.x-.25,b.y)]),
{route_type:"via",x:b.x-.25,y:b.y,from_layer:layer,to_layer:"top"},wire(b.x-.25,b.y,"top"),wire(b.x,b.y,"top")]};
});
return {input,traces,names};
}
describe("DDR timing acceptance",()=>{
test("groups cover both bytes and clocked CA exactly once, leaving RESET separate",()=>{
const members=ddrTimingGroups.flatMap(g=>g.signals);
expect(ddrTimingGroups.map(g=>g.signals.length)).toEqual([11,11,24]);
expect(new Set(members).size).toBe(46);
expect(members.includes("DDR_RESETn")).toBe(false);
});
test("accepts a synthetic pad-to-pad timing-compliant fixture (not a copper DRC test)",()=>{
const f=fixture(); expect(auditDdrGeometry(f.input,f.traces,f.names).failures).toEqual([]);
});
test("rejects pairs-only seeding and missing DM even if the solver claims success",()=>{
const f=fixture(); f.input.buses=[];
expect(()=>assertDdrConstraints(f.input,f.names)).toThrow("DDR_BYTE0");
const g=fixture();g.input.buses![0].connectionNames.splice(8,1);
expect(()=>assertDdrConstraints(g.input,g.names)).toThrow("DDR_BYTE0");
});
test("rejects perfect skew when all byte routes exceed DQLM",()=>{
const f=fixture();
const ids=new Set(f.input.buses![0].connectionNames);
for(const t of f.traces.filter(t=>ids.has(t.connection_name)))t.route.splice(4,0,{route_type:"wire",x:.25,y:-10,width:.1,layer:"inner1"},{route_type:"wire",x:.25,y:0,width:.1,layer:"inner1"});
const result=auditDdrGeometry(f.input,f.traces,f.names);
expect(result.groups[0].skewPass).toBe(true);
expect(result.groups[0].absoluteLengthPass).toBe(false);
expect(result.pass).toBe(false);
});
test("rejects endpoint, carrier layer, and missing-route errors",()=>{
const f=fixture(); f.traces[0].route[0].x=5;
expect(auditDdrGeometry(f.input,f.traces,f.names).pass).toBe(false);
const g=fixture(); for(const p of g.traces[0].route)if(p.route_type==="wire"&&p.layer==="inner1")p.layer="bottom";
expect(auditDdrGeometry(g.input,g.traces,g.names).pass).toBe(false);
const h=fixture();h.traces.pop();expect(auditDdrGeometry(h.input,h.traces,h.names).pass).toBe(false);
});
test("rejects invalid tolerances and duplicate differential pair members",()=>{
const f=fixture(); f.input.buses![0].maxLengthSkew=NaN;
expect(()=>assertDdrConstraints(f.input,f.names)).toThrow("DDR_BYTE0");
const g=fixture();g.input.differentialPairs![0].connectionNames[1]=g.input.differentialPairs![0].connectionNames[0];
expect(()=>assertDdrConstraints(g.input,g.names)).toThrow("DDR_DQS_PAIR0");
});
test("an unrouted byte cannot pass its group audit with zero lengths",()=>{
const f=fixture();
const byteIds=new Set(f.input.buses![0].connectionNames);
const audit=auditDdrGeometry(f.input,f.traces.filter(t=>!byteIds.has(t.connection_name)),f.names);
expect(audit.groups[0].pass).toBe(false);
expect(audit.groups[0].routingPass).toBe(false);
expect(audit.groups[1].pass).toBe(true);
});
});