ShiboSoftwareDev/f1c100s-linux-gameboy
Restores PCB assembly metadata by assigning part numbers, managing 3D CAD models and orientations, removing test-point bodies, and validating top-side SMT solder-paste features.
- Version
- 1.0.8
- License
- unset
- Stars
- 0
scripts/verify.mjs
import assert from "node:assert/strict"
import fs from "node:fs"
const circuit = JSON.parse(fs.readFileSync("dist/index/circuit.json", "utf8"))
const of = (type) => circuit.filter((element) => element.type === type)
const parents = new Map()
const root = (id) => {
if (!parents.has(id)) parents.set(id, id)
if (parents.get(id) !== id) parents.set(id, root(parents.get(id)))
return parents.get(id)
}
const join = (a, b) => parents.set(root(a), root(b))
for (const trace of of("source_trace")) {
const ids = [...(trace.connected_source_port_ids ?? []), ...(trace.connected_source_net_ids ?? [])]
for (const id of ids.slice(1)) join(ids[0], id)
}
const component = (name) => {
const matches = of("source_component").filter((part) => part.name === name)
assert.equal(matches.length, 1, `unique component ${name}`)
return matches[0]
}
const pcbComponent = (name) => {
const source = component(name)
const matches = of("pcb_component").filter(
(part) => part.source_component_id === source.source_component_id,
)
assert.equal(matches.length, 1, `unique PCB component ${name}`)
return matches[0]
}
const port = (name, pinNumber) => {
const part = component(name)
const match = of("source_port").find(
(candidate) => candidate.source_component_id === part.source_component_id && candidate.pin_number === pinNumber,
)
assert.ok(match, `${name}.pin${pinNumber}`)
return match.source_port_id
}
const net = (name) => {
const match = of("source_net").find((candidate) => candidate.name === name)
assert.ok(match, `net.${name}`)
return match.source_net_id
}
let connectivityAssertions = 0
const expectNet = (name, pinNumber, netName) => {
assert.equal(root(port(name, pinNumber)), root(net(netName)), `${name}.pin${pinNumber} -> ${netName}`)
connectivityAssertions += 1
}
const board = of("pcb_board")[0]
assert.ok(board, "PCB board emitted")
assert.equal(board.width, 106)
assert.equal(board.height, 150)
assert.equal(board.num_layers, 4)
const bootJumper = pcbComponent("JP_BOOT")
assert.ok(
bootJumper.center.x + bootJumper.width / 2 < -43 ||
bootJumper.center.x - bootJumper.width / 2 > 43 ||
bootJumper.center.y + bootJumper.height / 2 < 13 ||
bootJumper.center.y - bootJumper.height / 2 > 63,
"JP_BOOT is fully outside and clear of the LCD module outline",
)
const cutouts = of("pcb_cutout")
assert.equal(cutouts.length, 1, "one battery-cable cutout")
const batteryNotch = cutouts[0]
assert.equal(batteryNotch.shape, "rect")
assert.equal(batteryNotch.width, 5)
assert.equal(batteryNotch.height, 13)
assert.ok(
batteryNotch.center.y - batteryNotch.height / 2 < -board.height / 2,
"battery-cable notch opens through the lower board edge",
)
const errors = circuit.filter((element) => element.type?.endsWith("_error"))
assert.deepEqual(errors, [], "circuit-json contains zero DRC/electrical errors")
const vias = of("pcb_via")
assert.ok(vias.length > 250, "routed four-layer board has vias")
assert.ok(vias.every((via) => via.hole_diameter === 0.3), "every via drill is 0.30 mm")
assert.ok(vias.every((via) => via.outer_diameter === 0.45), "every via pad is 0.45 mm")
const pours = of("pcb_copper_pour")
assert.deepEqual(new Set(pours.map((pour) => pour.layer)), new Set(["top", "inner1", "inner2", "bottom"]))
const physicalParts = of("source_component").filter((part) => part.ftype !== "simple_test_point")
assert.equal(physicalParts.length, 126, "complete fitted assembly")
for (const part of physicalParts) {
const ids = part.supplier_part_numbers?.jlcpcb ?? []
assert.ok(ids.some((id) => /^C\d+$/.test(id)), `${part.name} has an explicit JLCPCB/LCSC part`)
}
assert.equal(of("pcb_component").filter((part) => part.layer === "bottom").length, 0, "single-sided SMT assembly")
for (const [part, pinNumber, rail] of [
["U1", 22, "V1V2"], ["U1", 35, "V1V2"], ["U1", 71, "V1V2"],
["U1", 30, "V2V5"], ["U1", 31, "V2V5"], ["U1", 32, "V2V5"],
["U1", 34, "V2V5"], ["U1", 36, "V2V5"], ["U1", 80, "V2V8"],
["U1", 4, "V3V3"], ["U1", 5, "V3V3"], ["U1", 20, "V3V3"],
["U1", 50, "V3V3"], ["U1", 67, "V3V3"], ["U1", 73, "V3V3"],
]) expectNet(part, pinNumber, rail)
for (const [pinNumber, signal] of [
[42, "LCD_CS"], [41, "LCD_MOSI"], [40, "LCD_SCK"], [39, "LCD_MISO"],
[47, "LCD_DC"], [46, "LCD_RESET"], [45, "GAMEPAD_INT"],
[38, "I2C_SCL"], [37, "I2C_SDA"], [1, "CODEC_HPL"],
[68, "USB_N"], [69, "USB_P"], [70, "RESET_N"],
]) expectNet("U1", pinNumber, signal)
const lcdNets = ["V5_IN", "BOARD_GND", "LCD_CS", "LCD_RESET", "LCD_DC", "LCD_MOSI", "LCD_SCK", "V3V3", "LCD_MISO"]
lcdNets.forEach((netName, index) => expectNet("J_LCD", index + 1, netName))
const buttonNames = ["UP", "DOWN", "LEFT", "RIGHT", "A", "B", "X", "Y", "SELECT", "START"]
buttonNames.forEach((name, index) => {
expectNet(`SW_${name}`, 1, `BTN_${name}`)
expectNet(`SW_${name}`, 4, "BOARD_GND")
expectNet("U_GAMEPAD", index < 8 ? index + 4 : index + 5, `BTN_${name}`)
})
expectNet("U_GAMEPAD", 1, "GAMEPAD_INT")
expectNet("U_GAMEPAD", 23, "I2C_SDA")
expectNet("U_GAMEPAD", 22, "I2C_SCL")
expectNet("U_GAMEPAD", 24, "V3V3")
for (const pinNumber of [7, 9]) expectNet("J_USB", pinNumber, "USB_N")
for (const pinNumber of [8, 10]) expectNet("J_USB", pinNumber, "USB_P")
expectNet("J_USB", 6, "USB_CC1")
expectNet("J_USB", 12, "USB_CC2")
expectNet("R_CC1", 2, "BOARD_GND")
expectNet("R_CC2", 2, "BOARD_GND")
expectNet("U_SPK_AMP", 4, "V5_IN")
expectNet("U_SPK_AMP", 6, "V5_IN")
expectNet("U_SPK_AMP", 13, "V5_IN")
expectNet("U_SPK_AMP", 11, "BOARD_GND")
expectNet("J_SPK", 3, "BOARD_GND")
expectNet("J_SPK", 4, "BOARD_GND")
expectNet("J_BAT", 2, "BOARD_GND")
const uniqueJlcIds = [...new Set(physicalParts.flatMap((part) => part.supplier_part_numbers.jlcpcb))].sort()
const report = {
board: { widthMm: board.width, heightMm: board.height, layers: board.num_layers },
sourceComponents: of("source_component").length,
fittedParts: physicalParts.length,
pcbComponents: of("pcb_component").length,
pcbTraces: of("pcb_trace").length,
pcbVias: vias.length,
viaGeometryMm: { hole: 0.3, pad: 0.45 },
bottomAssemblyComponents: 0,
uniqueJlcpcbParts: uniqueJlcIds.length,
jlcpcbPartNumbers: uniqueJlcIds,
connectivityAssertions,
drcErrors: errors.length,
}
fs.writeFileSync("verification.json", `${JSON.stringify(report, null, 2)}\n`)
console.log(JSON.stringify(report, null, 2))