board
OCuLink Riser for the Supermicro AIOM Slot
Public Unreviewedby John Lauer
Passive OCP NIC 3.0 SFF card that breaks a Supermicro AIOM slot's PCIe x16 into four OCuLink 4i ports (4 x4, Gen4): 6-layer, 85 ohm, length-matched, DRC-clean KiCad project with Gerbers, BOM and CPL.
← Commit history
design/gen/route_all.pydeleted−275@@ -1,275 +0,0 @@-#!/usr/bin/env python3-"""Assemble every route: high-speed (hs.py), fixed extras, maze-routed low-speed nets, GND fan-out, stitching,-zones and keepouts -> design/routes.json for build_board.py final."""-import json, math, os, sys, time-import numpy as np-from shapely.geometry import Polygon, Point, LineString, box-from shapely.ops import unary_union-from shapely import contains_xy-sys.path.insert(0, os.path.dirname(__file__))-import hs, maze-from maze import G, NX, NY, LI--ROOT = os.path.join(os.path.dirname(__file__), "..")-pads = json.load(open(os.path.join(ROOT, "design", "pads.json")))-lay = json.load(open(os.path.join(ROOT, "design", "layout.json")))-nl = json.load(open(os.path.join(ROOT, "design", "netlist.json")))-OUTLINE = Polygon(lay["outline"])-LAYMAP = {"F.Cu": "L1", "In2.Cu": "L3", "In4.Cu": "L5", "B.Cu": "L6"}--T, V = [], [] # all tracks and vias for routes.json-B = maze.Board()--# ------------------------------------------------------------------ fixed high-speed copper-pairs, lines = hs.all_routes()-for p in pairs:- for t in p.tracks:- T.append(t); B.track(*t, hs=True)- for v in p.vias:- V.append(v); B.via(v[0], v[1], v[2], v[3], hs=True)-for l in lines:- for t in l.tracks:- T.append(t); B.track(*t)- for v in l.vias:- V.append(v); B.via(v[0], v[1], v[2], v[3])--# ------------------------------------------------------------------ pads-PAD = {}-for p in pads:- net = p["net"] or f"NC:{p['ref']}.{p['num']}"- PAD[(p["ref"], p["num"], round(p["x"], 3), round(p["y"], 3))] = p- geom = unary_union([Polygon(o) for o in p["poly"] if len(o) >= 3]) if p["poly"] else Point(p["x"], p["y"]).buffer(p["w"] / 2)- p["geom"] = geom- lis = [LI[LAYMAP[l]] for l in p["layers"] if l in LAYMAP]- if len(p["layers"]) >= 6 or p["drill"][0] > 0:- lis = [0, 1, 2, 3]- # a plated hole occupies every layer with at least its drill- dg = Point(p["x"], p["y"]).buffer(max(p["drill"]) / 2 + 0.05)- p["geom"] = geom.union(dg)- B.paint_geom(lis, p["geom"], B.nid(net))- p["lis"] = lis--# ------------------------------------------------------------------ fixed extras-def add_track(t):- T.append(t); B.track(*t)--def add_via(v, hsflag=False):- V.append(v); B.via(v[0], v[1], v[2], v[3], hs=hsflag)--pin_net = {}-for n, ps in nl["nets"].items():- for q in ps:- pin_net[q] = n-# GND gold fingers: short stub off the inner end so the outer-layer GND pour picks them up-for p in pads:- if p["ref"] == "J1" and p["net"] == "GND":- layer = "L1" if "F.Cu" in p["layers"] else "L6"- add_track((layer, "GND", p["x"], hs.PAD_TOP, p["x"], hs.PAD_TOP - 0.55, 0.3))-cols = {}-for p in pads:- if p["ref"] == "J1":- cols.setdefault(round(p["x"], 3), {})["F" if "F.Cu" in p["layers"] else "B"] = p["net"]-for x, sides in sorted(cols.items()):- if sides.get("F") == "GND" and sides.get("B") == "GND":- add_via((x, hs.PAD_TOP - 0.62, "GND", 0.4, 0.2))-# +12V fingers B1..B6: one bar-b1 = hs.finger("B1"); b6 = hs.finger("B6")-add_track(("L1", "+12V_EDGE", b1[0], b1[1], b1[0], 111.6, 0.3))-for n in range(2, 7):- x, y = hs.finger(f"B{n}")- add_track(("L1", "+12V_EDGE", x, y, x, 111.6, 0.3))-add_track(("L1", "+12V_EDGE", b1[0], 111.6, b6[0], 111.6, 0.3))-# OCuLink GND pads: via-in-pad (filled and capped)-for p in pads:- if p["ref"] in ("J2", "J3", "J4", "J5") and p["net"] == "GND" and p["num"] != "SH":- add_via((p["x"], p["y"], "GND", hs.VIP_D, hs.VIP_H), True)--# ------------------------------------------------------------------ finger escape reservations-# every used signal/power finger keeps a 2.4 mm column above it on its own layer, so no other net's via lands there-HS_NETS = set()-for _p in pairs:- HS_NETS |= {_p.nnet, _p.pnet}-for p in pads:- if p["ref"] != "J1" or not p["net"] or p["net"] in ("GND", "+12V_EDGE") or p["net"] in HS_NETS:- continue- li = LI["L1"] if "F.Cu" in p["layers"] else LI["L6"]- w = 0.2- B.paint_geom(li, box(p["x"] - w / 2, hs.PAD_TOP - 2.4, p["x"] + w / 2, hs.PAD_TOP), B.nid(p["net"]))--# ------------------------------------------------------------------ static keepouts-yy, xx = B.Y, B.X-inside = contains_xy(OUTLINE.buffer(-0.42), xx, yy)-for li in range(4):- B.block[li] |= ~inside-# long-edge strips: no copper on outer layers within 2.75 mm, inner layers within 1.0 mm (OCP Fig. 8/10)-strip_outer = (xx < 2.75) | (xx > 76 - 2.75)-strip_inner = (xx < 1.0) | (xx > 75.0)-B.block[LI["L1"]] |= strip_outer; B.block[LI["L6"]] |= strip_outer-B.block[LI["L3"]] |= strip_inner; B.block[LI["L5"]] |= strip_inner-# gold-finger field: nothing else on the outer layers past the finger tops; inner copper clear of the bevel-B.block[LI["L1"]] |= yy > 112.05-B.block[LI["L6"]] |= yy > 112.05-B.block[LI["L3"]] |= yy > 113.6-B.block[LI["L5"]] |= yy > 113.6-B.block_via = np.zeros_like(B.block)-for li in range(4):- B.block_via[li] = B.block[li] | (yy > 111.6)---def terminals(net):- out = []- for p in pads:- if p["net"] != net:- continue- cells = set()- x0, y0, x1, y1 = p["geom"].bounds- i0, i1, j0, j1 = int(x0 / G), int(x1 / G) + 1, int(y0 / G), int(y1 / G) + 1- sub = contains_xy(p["geom"].buffer(-0.02), xx[j0:j1 + 1, i0:i1 + 1], yy[j0:j1 + 1, i0:i1 + 1])- js, iss = np.nonzero(sub)- for li in p["lis"]:- for j, i in zip(js + j0, iss + i0):- cells.add((li, int(j), int(i)))- if not cells:- cells = {(li, int(round(p["y"] / G)), int(round(p["x"] / G))) for li in p["lis"]}- out.append(cells)- # existing copper of the net (fixed lines) is one more terminal- nid = B.nid(net)- ex = set()- for (lay_, n, x1, y1, x2, y2, w) in T:- if n == net:- L = max(1, int(math.hypot(x2 - x1, y2 - y1) / G))- for k in range(L + 1):- x = x1 + (x2 - x1) * k / L; y = y1 + (y2 - y1) * k / L- ex.add((LI[lay_], int(round(y / G)), int(round(x / G))))- if ex:- out.insert(0, ex)- return out---L6_REGION = (yy > 101.0) & (xx < 30.0)-ROUTE = [- # bottom-side (A) finger escapes first: each needs a via site above its own finger- ("PERST1#", 0.15, ("L1", "L3", "L5", "L6")), ("PRSNTA0#", 0.15, ("L1", "L3", "L6")),- ("PRSNTB2#", 0.15, ("L1", "L3", "L6")), ("SMCLK0", 0.15, ("L1", "L3", "L5", "L6")),- ("SMDAT0", 0.15, ("L1", "L3", "L5", "L6")), ("PERST2#", 0.15, ("L1", "L3", "L5", "L6")),- ("PERST3#", 0.15, ("L1", "L3", "L5", "L6")), ("WAKE0#", 0.15, ("L1", "L3", "L5", "L6")),- ("SLOT_ID1", 0.15, ("L1", "L3", "L5", "L6")),- # top side and power- ("+3V3_EDGE", 0.2, ("L1", "L3", "L5")), ("+12V_EDGE", 0.2, ("L1", "L3", "L5")),- ("PRSNTB3#", 0.15, ("L1", "L3")), ("PERST0#", 0.15, ("L1", "L3", "L5")),- ("NIC_PWR_GOOD0", 0.15, ("L1", "L3", "L5")), ("SCAN0_LD#", 0.15, ("L1", "L3", "L5")),- ("SCAN0_DATA_IN", 0.15, ("L1", "L3", "L5")), ("SCAN0_DATA_OUT", 0.15, ("L1", "L3", "L5")),- ("SCAN0_CLK", 0.15, ("L1", "L3", "L5")), ("SLOT_ID0", 0.15, ("L1", "L3", "L5")),- ("AUX_PWR_EN0", 0.15, ("L1", "L3", "L5")),- ("PERST0_BUF", 0.15, ("L1", "L3", "L5")), ("PERST1_BUF", 0.15, ("L1", "L3", "L5")),- ("PERST2_BUF", 0.15, ("L1", "L3", "L5")), ("PERST3_BUF", 0.15, ("L1", "L3", "L5")),- ("PORT0_PERST#", 0.15, ("L1", "L3", "L5")), ("PORT1_PERST#", 0.15, ("L1", "L3", "L5")),- ("PORT2_PERST#", 0.15, ("L1", "L3", "L5")), ("PORT3_PERST#", 0.15, ("L1", "L3", "L5")),- ("PORT0_CPRSNT#", 0.15, ("L1", "L3", "L5")), ("PORT1_CPRSNT#", 0.15, ("L1", "L3", "L5")),- ("PORT2_CPRSNT#", 0.15, ("L1", "L3", "L5")), ("PORT3_CPRSNT#", 0.15, ("L1", "L3", "L5")),- ("FRU_WP", 0.15, ("L1", "L3", "L5")), ("SR0_SER", 0.15, ("L1", "L3", "L5")),- ("LED3V3_A", 0.15, ("L1",)), ("LED12V_A", 0.15, ("L1",)),-]--CLR, CLR_HS = 0.14, 0.28---def paint_net(net, tracks, vias):- for t in tracks:- B.track(*t)- for v in vias:- B.via(v[0], v[1], v[2], v[3])---def route_list(order, routed, attempt_log):- fails = []- for (net, w, allow) in order:- terms = terminals(net)- if len(terms) < 2:- continue- paths, msg = maze.route_net(B, net, terms, w=w, clr=CLR, clr_hs=CLR_HS, allow=allow, l6_region=L6_REGION)- if paths is None:- fails.append(net); attempt_log.append(f"FAIL {net}: {msg}"); print(f"FAIL {net}: {msg}", flush=True); continue- tr, vi = [], []- for pth in paths:- a_, b_ = maze.path_to_copper(pth, net, w)- tr += a_; vi += b_- routed[net] = (tr, vi)- paint_net(net, tr, vi)- print(f"{net}: {len(paths)} branch(es), {len(tr)} tracks, {len(vi)} vias", flush=True)- return fails---def rebuild(routed):- """Fresh raster with fixed copper + pads + reservations + everything routed so far."""- global B- lab0, hs0, blk, blkv = BASE- B.lab = lab0.copy(); B.hs = hs0.copy()- for net, (tr, vi) in routed.items():- paint_net(net, tr, vi)---if __name__ == "__main__":- t0 = time.time()- FIXED_T, FIXED_V = list(T), list(V)- BASE = (B.lab.copy(), B.hs.copy(), B.block.copy(), B.block_via.copy())- W = {n: (w, a) for (n, w, a) in ROUTE}- routed = {}- log = []- fails = route_list(ROUTE, routed, log)- priority = [] # every net that ever failed, routed first from then on- for rnd in range(10):- if not fails:- break- for n in fails:- if n not in priority:- priority.append(n)- print(f"--- rip-up round {rnd+1}: {fails} priority {priority}", flush=True)- rip = set()- for net in priority:- for (li, j, i) in set().union(*terminals(net)):- r = int(1.2 / G)- for lj in range(4):- blk = B.lab[lj, max(0, j - r):j + r + 1, max(0, i - r):i + r + 1]- for nid in np.unique(blk):- if nid >= 0 and B.netname[nid] in routed:- rip.add(B.netname[nid])- rip |= set(priority)- for n in rip:- routed.pop(n, None)- rebuild(routed)- # the nets that just failed get first pick, then the older failures, then the ripped-up rest- first = list(fails) + [n for n in priority if n not in fails]- order = [(n, *W[n]) for n in first] + [(n, *W[n]) for n in [x[0] for x in ROUTE] if n in rip and n not in priority]- fails = route_list(order, routed, log)- # ESD grounds: each ESD's GND pins to the GND pads of its connector- for k in range(4):- ref, conn = f"U{5+k}", f"J{2+k}"- esd_cells, conn_cells = [], set()- for p in pads:- if p["net"] != "GND":- continue- if p["ref"] == ref or (p["ref"] == conn and p["num"] in ("B11", "B14", "B17", "B20")):- x0, y0, x1, y1 = p["geom"].bounds- i0, i1, j0, j1 = int(x0 / G), int(x1 / G) + 1, int(y0 / G), int(y1 / G) + 1- sub = contains_xy(p["geom"].buffer(-0.02), xx[j0:j1 + 1, i0:i1 + 1], yy[j0:j1 + 1, i0:i1 + 1])- js, iss = np.nonzero(sub)- cells = {(LI["L1"], int(j + j0), int(i + i0)) for j, i in zip(js, iss)}- if p["ref"] == ref:- esd_cells.append(cells)- else:- conn_cells |= cells- terms = [conn_cells] + esd_cells- paths, msg = maze.route_net(B, "GND", terms, w=0.2, clr=CLR, clr_hs=CLR_HS, allow=("L1",))- if paths is None:- print(f"FAIL ESD GND {ref}: {msg}"); fails.append(f"GND:{ref}"); continue- tr, vi = [], []- for pth in paths:- a_, b_ = maze.path_to_copper(pth, "GND", 0.2); tr += a_; vi += b_- routed[f"GND:{ref}"] = (tr, vi); paint_net("GND", tr, vi)- print(f"GND {ref}: {len(tr)} tracks")- for net, (tr, vi) in routed.items():- T.extend(tr); V.extend(vi)- json.dump({"tracks": T, "vias": V, "fails": fails, "fixed_tracks": FIXED_T, "fixed_vias": FIXED_V},- open(os.path.join(ROOT, "design", "routes_ls.json"), "w"))- print("fails:", fails, f"[{time.time()-t0:.0f}s]")