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/silk.pydeleted−299@@ -1,299 +0,0 @@-"""Silkscreen layout for the riser (run with KiCad 10's python.exe).--Reads the saved board, builds per-face obstacles (pads with their mask openings, vias, holes, part courtyards, the-board edge, the OCP long-edge strips, the finger field) and places, greedily with a bounded search of positions,-rotations and sizes measured with KiCad's own stroke-font bounds:- - every reference (and every R/C value beside it), test-point net labels, port and LED legends on F.SilkS,- - finger pin numbers at the ends of each finger group on both faces,- - the board identity, link and the BIOS note on B.SilkS.-Writes design/silk_plan.json (field updates by native field UUID, standalone texts) and, with "apply", a copy of the-board with the plan applied for DRC (design/silk_check.kicad_pcb). Every number in the plan is from this board."""-import json, os, sys, math-import pcbnew--HERE = os.path.dirname(os.path.abspath(__file__))-ROOT = os.path.dirname(HERE)-SRC = sys.argv[1]-MODE = sys.argv[2] if len(sys.argv) > 2 else "plan"-MM, TO = pcbnew.FromMM, pcbnew.ToMM-b = pcbnew.LoadBoard(SRC)--STROKE = 0.15 # JLC pack: min text thickness 0.15 mm-REF_SIZES = (1.0, 0.8) # JLC pack: min text height 0.8 mm-VAL_SIZES = (0.8,)-CLR = 0.15 # silk to exposed copper / other silk--KEYED = {"R11", "R12", "R13", "R14", "R15", "R16", "R17", "R18"} # PERST# series 33R and pull-down 100k block-TP_LABEL = {"TP1": "3V3", "TP2": "GND", "TP3": "SMCLK", "TP4": "SMDAT", "TP5": "12V", "TP6": "PWRGD",- "TP7": "PERST0", "TP8": "PERST1", "TP9": "PERST2", "TP10": "PERST3", "TP11": "SCLK", "TP12": "SDOUT"}-LED_LABEL = {"D1": "3V3", "D2": "12V"}---def box(x0, y0, x1, y1):- return (min(x0, x1), min(y0, y1), max(x0, x1), max(y0, y1))---def grow(r, d):- return (r[0] - d, r[1] - d, r[2] + d, r[3] + d)---def hit(r, s):- return not (r[2] <= s[0] or s[2] <= r[0] or r[3] <= s[1] or s[3] <= r[1])---def bb(item):- e = item.GetBoundingBox()- return box(TO(e.GetLeft()), TO(e.GetTop()), TO(e.GetRight()), TO(e.GetBottom()))---# ------------------------------------------------------------------ obstacles per face-obst = {"F": [], "B": []}-for fp in b.GetFootprints():- for side, lay in (("F", pcbnew.F_CrtYd), ("B", pcbnew.B_CrtYd)):- cy = fp.GetCourtyard(lay)- if cy.OutlineCount():- e = cy.BBox()- r = box(TO(e.GetLeft()), TO(e.GetTop()), TO(e.GetRight()), TO(e.GetBottom()))- if fp.GetReference() != "J1": # J1's courtyard is the finger field, handled below- obst[side].append(("crtyd", fp.GetReference(), r))- for p in fp.Pads():- r = grow(bb(p), CLR + 0.05) # mask expansion + clearance- if p.IsOnLayer(pcbnew.F_Cu) or p.GetAttribute() in (pcbnew.PAD_ATTRIB_PTH, pcbnew.PAD_ATTRIB_NPTH):- obst["F"].append(("pad", fp.GetReference(), r))- if p.IsOnLayer(pcbnew.B_Cu) or p.GetAttribute() in (pcbnew.PAD_ATTRIB_PTH, pcbnew.PAD_ATTRIB_NPTH):- obst["B"].append(("pad", fp.GetReference(), r))-# vias are tented on both faces (board setup), so silk may cross them; only exposed copper is an obstacle-W, H = 76.0, 115.0-for side in ("F", "B"):- obst[side] += [("edge", "", (-1, -1, 3.05, H + 1)), ("edge", "", (W - 3.05, -1, W + 1, H + 1)), # strips + margin- ("edge", "", (-1, -1, W + 1, 0.4)), ("fingers", "", (-1, 111.7, W + 1, H + 1))]--placed = {"F": [], "B": []}---def free(side, r, ignore=()):- if r[0] < 0.4 or r[2] > W - 0.4 or r[1] < 0.4 or r[3] > H - 0.4:- return False- for kind, ref, s in obst[side]:- if ref in ignore and kind == "crtyd":- continue- if hit(r, s):- return False- return not any(hit(grow(r, CLR / 2), grow(s, CLR / 2)) for s in placed[side])---def text_box(text, x, y, size, rot, side, align="center"):- t = pcbnew.PCB_TEXT(b)- t.SetText(text); t.SetTextSize(pcbnew.VECTOR2I(MM(size), MM(size))); t.SetTextThickness(MM(STROKE))- t.SetPosition(pcbnew.VECTOR2I(MM(x), MM(y))); t.SetTextAngleDegrees(rot)- t.SetLayer(pcbnew.F_SilkS if side == "F" else pcbnew.B_SilkS)- t.SetHorizJustify(pcbnew.GR_TEXT_H_ALIGN_CENTER if align == "center" else pcbnew.GR_TEXT_H_ALIGN_LEFT)- if side == "B":- t.SetMirrored(True)- return bb(t)---def search(text, cx, cy, around, side, sizes, ignore=(), prefer=None):- """Positions on a ring around `around` (a box), nearest first, at each size and rotation."""- best = None- for size in sizes:- for rot in (0, 90):- for gap in (0.2, 0.5, 0.9, 1.4, 2.0, 2.8):- cands = []- x0, y0, x1, y1 = around- mx, my = (x0 + x1) / 2, (y0 + y1) / 2- tb = text_box(text, 0, 0, size, rot, side)- hw, hh = (tb[2] - tb[0]) / 2, (tb[3] - tb[1]) / 2- pts = []- for k in range(-6, 7): # slide along each side of the part- o = 0.35 * k- pts += [(mx + o, y0 - gap - hh), (mx + o, y1 + gap + hh), (x0 - gap - hw, my + o), (x1 + gap + hw, my + o)]- for (px, py) in pts:- cands.append((math.hypot(px - cx, py - cy), px, py))- if prefer:- cands.sort(key=lambda c: (c[0] + (0 if prefer(c[1], c[2]) else 50)))- else:- cands.sort()- for _, px, py in cands:- r = text_box(text, px, py, size, rot, side)- # centre the box on (px, py): KiCad positions by the text anchor, so shift- dx, dy = px - (r[0] + r[2]) / 2, py - (r[1] + r[3]) / 2- r = (r[0] + dx, r[1] + dy, r[2] + dx, r[3] + dy)- if free(side, r, ignore):- return dict(x=round(px + dx, 3), y=round(py + dy, 3), size=size, rot=rot, box=r)- return best---plan = {"fields": [], "texts": [], "unresolved": []}-# labels that failed on an earlier pass go first (a bounded repack instead of a single greedy order)-_pri_path = os.path.join(ROOT, "design", "silk_priority.json")-PRI = json.load(open(_pri_path)) if os.path.exists(_pri_path) else []-fps = sorted(b.GetFootprints(), key=lambda f: (f.GetReference() not in PRI, PRI.index(f.GetReference()) if f.GetReference() in PRI else 0,- f.GetReference()[0], f.GetPosition().y, f.GetPosition().x))--# ------------------------------------------------------------------ fixed legends first (they anchor the reading)-def add_text(text, x, y, size, side, rot=0, align="center", why=""):- r = text_box(text, x, y, size, rot, side, align)- placed[side].append(r)- plan["texts"].append(dict(text=text, layer=f"{side}.SilkS", x=round(x, 3), y=round(y, 3), height=size,- stroke=STROKE, rotation=rot, align=align, why=why))---# finger group ends, both faces: B pins on top, A pins below (A under B)-J1 = b.FindFootprintByReference("J1")-jx, jy = TO(J1.GetPosition().x), TO(J1.GetPosition().y)-ends = [("OCP_B1", -30.400), ("OCP_B14", -30.400 + 13 * 0.6), ("B1", -18.205), ("B28", -2.005), ("B29", 2.005),- ("B42", 9.805), ("B43", 14.715), ("B70", 30.915)]-for k, (name, dx) in enumerate(ends):- # first pin of a group: text runs inward (right) from the finger; last pin: inward (left); clear of the slots- for side, nm in (("F", name), ("B", name.replace("B", "A"))):- r = text_box(nm, 0, 0, 0.8, 0, side)- half = (r[2] - r[0]) / 2- x = jx + dx + (half - 0.19 if k % 2 == 0 else -(half - 0.19))- yy = 109.75 if name == "OCP_B14" else 110.9 # the short OCP group: its two labels on two rows- add_text(nm, x, yy, 0.8, side, why=f"finger pin number at the {'start' if k % 2 == 0 else 'end'} of its group ({'top = B' if side == 'F' else 'bottom = A'} side)")--# the ports on the bottom face too (a mounted card may show only its underside)-for _k, _r in enumerate(("J2", "J3", "J4", "J5")):- _fp = b.FindFootprintByReference(_r)- _x = TO(_fp.GetPosition().x)- for _y in (12.0, 12.8, 13.6, 14.4, 15.2):- if free("B", text_box(f"PORT {_k}", _x, _y, 1.0, 0, "B")):- add_text(f"PORT {_k}", _x, _y, 1.0, "B", why=f"{_r} port number, bottom face")- break- else:- plan["unresolved"].append(f"{_r}: bottom-face port legend found no space")--# ------------------------------------------------------------------ references, values, labels-for fp in fps:- ref = fp.GetReference()- if ref in ("J1", "H1", "H2"):- continue- side = "B" if fp.IsFlipped() else "F"- cy_ = fp.GetCourtyard(pcbnew.F_CrtYd if side == "F" else pcbnew.B_CrtYd)- e = cy_.BBox() if cy_.OutlineCount() else fp.GetBoundingBox()- around = box(TO(e.GetLeft()), TO(e.GetTop()), TO(e.GetRight()), TO(e.GetBottom()))- cx, cy = (around[0] + around[2]) / 2, (around[1] + around[3]) / 2- label = ref- if ref in TP_LABEL:- label = f"{ref} {TP_LABEL[ref]}"- elif ref in LED_LABEL:- label = f"{ref} {LED_LABEL[ref]}"- elif ref == "JP1":- label = "JP1 FRU WRITE"- elif ref[0] == "J" and ref in ("J2", "J3", "J4", "J5"):- label = f"{ref} PORT {int(ref[1]) - 2}"- # the connectors face the faceplate: their labels go on the board side of the ESD parts- prefer = (lambda x, y: y > 11.5) if ref in ("J2", "J3", "J4", "J5") else None- got = search(label, cx, cy, around, side, REF_SIZES, ignore=(ref,), prefer=prefer)- split = None- if not got and label != ref:- got = search(ref, cx, cy, around, side, REF_SIZES, ignore=(ref,))- if got:- split = label[len(ref) + 1:] # the function goes beside the reference, as its own text- label = ref- if not got:- plan["unresolved"].append(f"{ref}: no free spot for its reference at 0.8 mm")- continue- placed[side].append(got["box"])- if label == ref:- f = fp.Reference()- plan["fields"].append(dict(ref=ref, field="Reference", id=f.m_Uuid.AsString(), x=got["x"], y=got["y"],- rotation=got["rot"], layer=f"{side}.SilkS", height=got["size"], stroke=STROKE,- visible=True))- else:- add_text(label, got["x"], got["y"], got["size"], side, got["rot"], why=f"{ref} with its function")- placed[side].pop() # add_text appended the same box again- if split:- sg = search(split, got["x"], got["y"], got["box"], side, (0.8,), ignore=(ref,))- if sg:- add_text(split, sg["x"], sg["y"], sg["size"], side, sg["rot"], why=f"{ref} function, beside its reference")- else:- plan["unresolved"].append(f"{ref}: its function '{split}' has no spot beside the reference")- # the value beside the reference, for R and C (the dense PERST# resistor block gets one key instead)- if ref[0] in "RC" and ref not in KEYED:- val = fp.GetValue()- vg = search(val, got["x"], got["y"], got["box"], side, VAL_SIZES, ignore=(ref,))- if vg:- placed[side].append(vg["box"])- f = fp.Value()- plan["fields"].append(dict(ref=ref, field="Value", id=f.m_Uuid.AsString(), x=vg["x"], y=vg["y"],- rotation=vg["rot"], layer=f"{side}.SilkS", height=vg["size"], stroke=STROKE,- visible=True))- else:- plan["unresolved"].append(f"{ref}: value {val} has no free spot beside its reference (in the bottom key)")--# one value key for the PERST# resistor block, as near the block as it fits-_xs = [TO(b.FindFootprintByReference(r).GetPosition().x) for r in KEYED]; _ys = [TO(b.FindFootprintByReference(r).GetPosition().y) for r in KEYED]-_blk = (min(_xs) - 0.6, min(_ys) - 0.4, max(_xs) + 0.6, max(_ys) + 0.4)-for _txt in ("R11-R14 33 R15-R18 100k", "R11-14 33 / R15-18 100k"):- kg = search(_txt, (_blk[0] + _blk[2]) / 2, (_blk[1] + _blk[3]) / 2, _blk, "F", (0.8,))- if kg:- add_text(_txt, kg["x"], kg["y"], 0.8, "F", kg["rot"], why="values of the PERST# resistor block (33 ohm series, 100k pull-down)")- break-else:- # under the block on the bottom face (mirrored, reads from below), still beside the parts it names- for _txt in ("R11-R14 33 R15-R18 100k", "R11-14 33 / R15-18 100k"):- kg = search(_txt, (_blk[0] + _blk[2]) / 2, (_blk[1] + _blk[3]) / 2, grow(_blk, -1.0), "B", (0.8,))- if kg:- add_text(_txt, kg["x"], kg["y"], 0.8, "B", kg["rot"], why="values of the PERST# resistor block, under it on the bottom face")- plan["unresolved"].append("R11-R18: values are keyed on the bottom face under the block (no top-side room)")- break- else:- plan["unresolved"].append("R11-R18: the value key found no spot near the block")--# ------------------------------------------------------------------ bottom: identity, link, BIOS note, value key-ident = [("OCP NIC 3.0 SFF > 4x OCuLink 4i", 1.6), ("PCIe Gen4 x16 as 4 x4 rev A", 1.2),- ("Set the slot to x4x4x4x4 in BIOS", 1.0), ("wiki.adom.inc/john/oculink-aiom-riser", 1.0)]-y = 40.0-for text, size in ident:- r = text_box(text, 38.0, y, size, 0, "B")- while not free("B", r) and y < 100:- y += 0.5; r = text_box(text, 38.0, y, size, 0, "B")- if free("B", r):- add_text(text, 38.0, y, size, "B", why="board identity / service note")- y += size * 1.9- else:- plan["unresolved"].append(f"bottom: '{text}' found no space")-missing_vals = [u.split(":")[0] for u in plan["unresolved"] if "value " in u and b.FindFootprintByReference(u.split(":")[0]) is not None]-if missing_vals:- for chunk_start in range(0, len(missing_vals), 6):- chunk = missing_vals[chunk_start:chunk_start + 6]- line = " ".join(f"{r}={b.FindFootprintByReference(r).GetValue()}" for r in chunk)- r = text_box(line, 38.0, y, 0.8, 0, "B")- while not free("B", r) and y < 105:- y += 0.5; r = text_box(line, 38.0, y, 0.8, 0, "B")- if free("B", r):- add_text(line, 38.0, y, 0.8, "B", why="value key for parts whose value did not fit beside them")- y += 1.6--json.dump(plan, open(os.path.join(ROOT, "design", "silk_plan.json"), "w"), indent=1)-newpri = PRI + [u.split(":")[0] for u in plan["unresolved"] if u.split(":")[0] not in PRI and not u.startswith("bottom")]-json.dump(newpri, open(_pri_path, "w"))-print(f"fields {len(plan['fields'])}, texts {len(plan['texts'])}, unresolved {len(plan['unresolved'])}")-for u in plan["unresolved"]:- print(" unresolved:", u)--if MODE == "apply":- for fl in plan["fields"]:- fp = b.FindFootprintByReference(fl["ref"])- f = fp.Reference() if fl["field"] == "Reference" else fp.Value()- f.SetVisible(True)- f.SetLayer(pcbnew.F_SilkS if fl["layer"] == "F.SilkS" else pcbnew.B_SilkS)- f.SetTextSize(pcbnew.VECTOR2I(MM(fl["height"]), MM(fl["height"]))); f.SetTextThickness(MM(fl["stroke"]))- f.SetTextAngleDegrees(fl["rotation"])- f.SetHorizJustify(pcbnew.GR_TEXT_H_ALIGN_CENTER)- f.SetPosition(pcbnew.VECTOR2I(MM(fl["x"]), MM(fl["y"])))- for tx in plan["texts"]:- t = pcbnew.PCB_TEXT(b)- t.SetText(tx["text"]); t.SetTextSize(pcbnew.VECTOR2I(MM(tx["height"]), MM(tx["height"])))- t.SetTextThickness(MM(tx["stroke"])); t.SetPosition(pcbnew.VECTOR2I(MM(tx["x"]), MM(tx["y"])))- t.SetTextAngleDegrees(tx["rotation"])- t.SetLayer(pcbnew.F_SilkS if tx["layer"] == "F.SilkS" else pcbnew.B_SilkS)- t.SetHorizJustify(pcbnew.GR_TEXT_H_ALIGN_CENTER if tx["align"] == "center" else pcbnew.GR_TEXT_H_ALIGN_LEFT)- if tx["layer"] == "B.SilkS":- t.SetMirrored(True)- b.Add(t)- out = os.path.join(ROOT, "design", "silk_check.kicad_pcb")- pcbnew.SaveBoard(out, b)- print("applied ->", out)