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/footprints.pydeleted−216@@ -1,216 +0,0 @@-#!/usr/bin/env python3-"""Project footprints for the OCP NIC 3.0 SFF to 4x OCuLink riser, written as KiCad s-expressions.--Sources (every number below is quoted from these):- OCP NIC 3.0 Design Specification v1.6.0 (2025-03-31): Table 20 (SFF pinout), Table 8 (mating sequence),- Fig. 8/9/10 (SFF outline, faceplate grounding pads).- SFF-TA-1002 Rev 1.6.2a: Fig. 5-39 (AIC 4C+ mating card), Fig. 5-41 (Detail D pad dimensions).- Amphenol drawing G14A421X1BXXXHR rev D (OCuLink 4X R/A receptacle, shell DIP; option C = 2.90 mm legs),- 'Recommended PCB layout - top view'.-"""-import os, uuid--OUT = os.path.join(os.path.dirname(__file__), "..", "kicad", "lib", "Riser.pretty")-NS = uuid.UUID("6f1c2b0e-0c3a-4b6e-9a51-6f2c1d0e4a10")---def u(*k):- return str(uuid.uuid5(NS, "/".join(map(str, k))))---def f(v):- s = f"{v:.4f}".rstrip("0").rstrip(".")- return "0" if s in ("-0", "") else s---def header(name, descr, tags, attr="smd"):- return [f'(footprint "{name}"', ' (version 20241229)', ' (generator "riser-gen")', ' (generator_version "1.0")',- ' (layer "F.Cu")', f' (descr "{descr}")', f' (tags "{tags}")',- f' (attr {attr})']---def text(kind, val, x, y, layer, hide=False, size=0.8):- h = " (hide yes)" if hide else ""- return (f' (property "{kind}" "{val}" (at {f(x)} {f(y)} 0) (layer "{layer}"){h} (uuid "{u(kind, val, x, y)}")\n'- f' (effects (font (size {size} {size}) (thickness {f(size*0.15)}))))')---def line(x1, y1, x2, y2, layer, w=0.12):- return f' (fp_line (start {f(x1)} {f(y1)}) (end {f(x2)} {f(y2)}) (stroke (width {f(w)}) (type solid)) (layer "{layer}") (uuid "{u("l", x1, y1, x2, y2, layer)}"))'---def rect(x1, y1, x2, y2, layer, w=0.05, fill=False):- fl = "yes" if fill else "no"- return f' (fp_rect (start {f(x1)} {f(y1)}) (end {f(x2)} {f(y2)}) (stroke (width {f(w)}) (type solid)) (fill {fl}) (layer "{layer}") (uuid "{u("r", x1, y1, x2, y2, layer)}"))'---def smd(num, x, y, w, h, layer="F", shape="rect", extra=""):- lay = f'"{layer}.Cu" "{layer}.Mask"' + (f' "{layer}.Paste"' if layer and "nopaste" not in extra else "")- return f' (pad "{num}" smd {shape} (at {f(x)} {f(y)}) (size {f(w)} {f(h)}) (layers {lay}){extra.replace("nopaste","")} (uuid "{u("p", num, x, y, layer)}"))'---def write(name, lines):- os.makedirs(OUT, exist_ok=True)- with open(os.path.join(OUT, name + ".kicad_mod"), "w") as fh:- fh.write("\n".join(lines) + "\n)\n")---# ---------------------------------------------------------------- OCP 4C+ gold fingers-def ocp_edge():- """Origin: datum F (centre of the B28/B29 key) on the card edge (datum G). -y points into the card.- Pads: 0.38 wide (SFF-TA-1002 Fig 5-41), top end 3.00 from G; bottom end 0.88 from G for 1st-mate- (all GND, OCP Table 8) and 1.28 from G for 2nd-mate. B pins on F.Cu, A pins on B.Cu, A under B."""- name = "OCP_NIC3_SFF_4CPlus_CardEdge"- L = header(name, "OCP NIC 3.0 SFF primary connector 4C+ gold fingers (SFF-TA-1002 AIC 4C+), 0.6 mm pitch, two-stage mating",- "OCP NIC 3.0 SFF-TA-1002 4C+ card edge gold finger", "smd exclude_from_pos_files exclude_from_bom allow_soldermask_bridges")- L.append(text("Reference", "J1", 0, -5.2, "F.SilkS", size=1.0))- L.append(text("Value", "OCP_NIC3_4C+", 0, -3.8, "F.Fab"))- L.append(text("Footprint", name, 0, 0, "F.Fab", hide=True))- pins = ocp_pin_x()- gnd = set(n for n, net in ocp_pinout().items() if net == "GND")- for pin, x in pins.items():- side = "F" if pin.startswith("B") or pin.startswith("OCP_B") else "B"- first = pin in gnd- y_top = -3.00- y_bot = -0.88 if first else -1.28- cy = (y_top + y_bot) / 2- L.append(f' (pad "{pin}" smd rect (at {f(x)} {f(cy)}) (size 0.38 {f(y_bot - y_top)}) (layers "{side}.Cu" "{side}.Mask") (uuid "{u("ocp", pin)}"))')- # mask opening over the whole finger field (mask not closer than 2.87 mm to datum G)- for side in ("F", "B"):- for (a, b) in ((-31.48, -20.4 - 1.125), (-20.4 + 1.125, -0.925), (0.925, 12.26 - 1.375), (12.26 + 1.375, 31.995)):- L.append(rect(a + 0.05, -2.95, b - 0.05, 0.0, f"{side}.Mask", w=0, fill=True))- L.append(line(-31.48, -3.05, 31.995, -3.05, "F.Fab", 0.1))- L.append(rect(-31.48, -7.0, 31.995, 0, "F.CrtYd", 0.05))- write(name, L)---def ocp_pin_x():- """x of every finger relative to datum F (SFF-TA-1002 Fig 5-39)."""- xs = {}- for n in range(1, 15):- xs[f"OCP_B{n}"] = -30.400 + (n - 1) * 0.6- xs[f"OCP_A{n}"] = -30.400 + (n - 1) * 0.6- for n in range(1, 71):- if n <= 28:- x = -18.205 + (n - 1) * 0.6- elif n <= 42:- x = 2.005 + (n - 29) * 0.6- else:- x = 14.715 + (n - 43) * 0.6- xs[f"B{n}"] = x- xs[f"A{n}"] = x- return xs---def ocp_pinout():- """OCP NIC 3.0 v1.6.0 Table 20, SFF Pin Definition (x16), primary connector 4C+."""- p = {}- ocpB = ["NIC_PWR_GOOD0", "MAIN_PWR_EN0", "SCAN0_LD#", "SCAN0_DATA_IN", "SCAN0_DATA_OUT", "SCAN0_CLK", "SLOT_ID0",- "FLEXIO_0_2", "FLEXIO_0_1", "GND", "REFCLKn2", "REFCLKp2", "GND", "FLEXIO_0_0"]- ocpA = ["PERST2#", "PERST3#", "WAKE0#", "RBT0_ARB_IN", "RBT0_ARB_OUT", "SLOT_ID1", "FLEXIO_0_3", "FLEXIO_0_5",- "FLEXIO_0_4", "GND", "REFCLKn3", "REFCLKp3", "GND", "RBT0_CLK_IN"]- for i in range(14):- p[f"OCP_B{i+1}"] = ocpB[i]- p[f"OCP_A{i+1}"] = ocpA[i]- B = {1: "+12V_EDGE", 2: "+12V_EDGE", 3: "+12V_EDGE", 4: "+12V_EDGE", 5: "+12V_EDGE", 6: "+12V_EDGE",- 7: "BIF0#", 8: "BIF1#", 9: "BIF2#", 10: "PERST0#", 11: "+3.3V_EDGE", 12: "AUX_PWR_EN0", 13: "GND",- 14: "REFCLKn0", 15: "REFCLKp0", 16: "GND", 42: "PRSNTB0#", 68: "RFU1", 69: "RFU2", 70: "PRSNTB3#"}- A = {1: "GND", 2: "GND", 3: "GND", 4: "GND", 5: "GND", 6: "GND", 7: "SMCLK0", 8: "SMDAT0", 9: "SMRST0#",- 10: "PRSNTA0#", 11: "PERST1#", 12: "PRSNTB2#", 13: "GND", 14: "REFCLKn1", 15: "REFCLKp1", 16: "GND",- 42: "PRSNTB1#", 68: "USB_DAT0n", 69: "USB_DAT0p", 70: "PWRBRK0#"}- lanes = [] # (n_pin, p_pin, lane)- starts = [17, 20, 23, 26, 30, 33, 36, 39, 44, 47, 50, 53, 56, 59, 62, 65]- for lane, s in enumerate(starts):- B[s] = f"PETn{lane}"; B[s + 1] = f"PETp{lane}"- A[s] = f"PERn{lane}"; A[s + 1] = f"PERp{lane}"- for n in range(1, 71):- p[f"B{n}"] = B.get(n, "GND")- p[f"A{n}"] = A.get(n, "GND")- return p---# ---------------------------------------------------------------- Amphenol G14A42111C12HR-def oculink():- """Origin: connector centre on the PCB edge, mating face toward -y (off the board), +y into the board.- Amphenol layout rotated 180 deg: B_n at x = 5.25 - 0.5(n-1), y = 7.37; A_n at x = 4.75 - 0.5(n-1), y = 5.18.- Pads 0.31 x 0.91. Shell legs: plated slots 0.60 x 1.30 in 1.20 x 1.90 pads at x = +/-6.58, y = 7.05 and 2.26.- Pegs: NPTH dia 1.00 at (+5.41, 4.46) and (-5.94, 4.46). Body 13.15 wide, 9.00 deep, front 1.98 past the edge."""- name = "Amphenol_G14A42111C12HR_OCuLink_4X_RA"- L = header(name, "Amphenol G14A42111C12HR OCuLink 4X (SFF-8611/8621, 42 pos) right-angle receptacle, shell DIP 2.90 mm; land pattern from Amphenol drawing G14A421X1BXXXHR rev D",- "OCuLink SFF-8611 SFF-8621 PCIe x4 receptacle right angle Amphenol G14", "smd")- L.append(text("Reference", "J", 0, 10.2, "F.SilkS", size=1.0))- L.append(text("Value", "G14A42111C12HR", 0, 0.5, "F.Fab"))- L.append(text("Footprint", name, 0, 0, "F.Fab", hide=True))- for n in range(1, 22):- L.append(smd(f"B{n}", 5.25 - 0.5 * (n - 1), 7.37, 0.31, 0.91))- if n in (1, 21):- # A1/A21 (PWR, no wire in OCuLink cables) sit 0.07/0.10 mm from the peg NPTH in Amphenol's layout; trim- # their inner end to y >= 4.875 so the pads clear the peg holes by 0.15 mm (JLC hole clearance)- L.append(smd(f"A{n}", 4.75 - 0.5 * (n - 1), 5.255, 0.31, 0.76))- else:- L.append(smd(f"A{n}", 4.75 - 0.5 * (n - 1), 5.18, 0.31, 0.91))- for sx in (-6.58, 6.58):- for sy in (7.05, 2.26):- L.append(f' (pad "SH" thru_hole oval (at {f(sx)} {f(sy)}) (size 1.2 1.9) (drill oval 0.6 1.3) (layers "*.Cu" "*.Mask") (uuid "{u("sh", sx, sy)}"))')- for (px, py) in ((5.41, 4.46), (-5.94, 4.46)):- L.append(f' (pad "" np_thru_hole circle (at {f(px)} {f(py)}) (size 1.0 1.0) (drill 1.0) (layers "*.Cu" "*.Mask") (uuid "{u("peg", px)}"))')- # body outline: 13.15 wide, front face 1.98 past the board edge, 9.00 deep- x0, x1, y0, y1 = -6.575, 6.575, -1.98, -1.98 + 9.0- for lay, w in (("F.Fab", 0.1),):- L.append(rect(x0, y0, x1, y1, lay, w))- # no body silk: the body edges run through the shell-leg pads and along the contact rows, and the receptacle- # itself marks its place; the reference designator is the only silk- L.append(rect(-7.28, -1.98, 7.28, 8.05, "F.CrtYd", 0.05)) # body 13.15 + shell-leg pads (+/-7.18); Amphenol 16.63 keep-out is for other parts- L.append(line(0, -1.98, 0, -1.4, "F.Fab", 0.1))- L.append(' (model "${KIPRJMOD}/3d/Amphenol_G14A42111C12HR.step" (offset (xyz 0 0 0)) (scale (xyz 1 1 1)) (rotate (xyz 0 0 0)))')- write(name, L)---def faceplate_mount():- """OCP SFF faceplate grounding: dia 3.20 PTH, dia 5.50 plated pad on top (Fig. 8), dia 7.00 on the bottom (Fig. 10)."""- name = "OCP_SFF_Faceplate_Ground_3.2mm"- L = header(name, "OCP NIC 3.0 SFF faceplate mounting/grounding hole: 3.20 mm PTH, 5.50 mm top pad, 7.00 mm bottom pad (OCP NIC 3.0 v1.6.0 Fig. 8 and 10)",- "OCP faceplate mounting hole ground", "exclude_from_pos_files exclude_from_bom")- L.append(text("Reference", "H", 0, -4.2, "F.SilkS", hide=True))- L.append(text("Value", "Faceplate_GND", 0, 4.2, "F.Fab", hide=True))- L.append(text("Footprint", name, 0, 0, "F.Fab", hide=True))- L.append(f' (pad "1" thru_hole circle (at 0 0) (size 5.5 5.5) (drill 3.2) (layers "*.Cu" "*.Mask") (uuid "{u("fp1")}"))')- L.append(f' (pad "1" smd circle (at 0 0) (size 7.0 7.0) (layers "B.Cu" "B.Mask") (uuid "{u("fp2")}"))')- L.append(f' (fp_circle (center 0 0) (end 2.85 0) (stroke (width 0.05) (type solid)) (fill no) (layer "F.CrtYd") (uuid "{u("fpc")}"))')- L.append(f' (fp_circle (center 0 0) (end 3.6 0) (stroke (width 0.05) (type solid)) (fill no) (layer "B.CrtYd") (uuid "{u("fpcb")}"))')- write(name, L)---def testpad_05():- """0.5 mm round exposed copper test pad, no paste (the adom/testpoint-pad-0-5mm land): a probe spot that sits on- its net's existing top-layer trace, so it needs no routing of its own."""- name = "TestPoint_Pad_D0.5mm"- L = header(name, "0.5 mm round test pad, exposed copper, no paste (adom/testpoint-pad-0-5mm)", "test point pad probe",- "smd exclude_from_pos_files exclude_from_bom")- L.append(text("Reference", "TP", 0, -0.9, "F.SilkS", hide=True, size=0.5))- L.append(text("Value", "TP", 0, 0.9, "F.Fab", hide=True, size=0.5))- L.append(text("Footprint", name, 0, 0, "F.Fab", hide=True, size=0.5))- L.append(f' (pad "1" smd circle (at 0 0) (size 0.5 0.5) (layers "F.Cu" "F.Mask") (uuid "{u("tp05")}"))')- L.append(f' (fp_circle (center 0 0) (end 0.4 0) (stroke (width 0.05) (type solid)) (fill no) (layer "F.CrtYd") (uuid "{u("tp05c")}"))')- write(name, L)---STD_FP = "/mnt/c/Users/john/AppData/Local/Programs/KiCad/10.0/share/kicad/footprints"---def chip_nosilk():- """KiCad 10 stock chip R/C land patterns without their body silk: adjacent 0402 outlines overlap at this board's- pitch. Pads, courtyard, fab and 3D model are the stock ones, unchanged."""- import re- for lib, name in (("Resistor_SMD", "R_0402_1005Metric"), ("Capacitor_SMD", "C_0402_1005Metric"),- ("Capacitor_SMD", "C_0603_1608Metric")):- src = open(os.path.join(STD_FP, lib + ".pretty", name + ".kicad_mod")).read()- parts = re.split(r"(?=\r?\n\t\()", src)- keep = [q for q in parts if not (re.match(r"\r?\n\t\((fp_line|fp_poly|fp_arc|fp_circle)\b", q) and '"F.SilkS"' in q)]- txt = "".join(keep).replace(f'(footprint "{name}"', f'(footprint "{name}_NoSilk"', 1)- open(os.path.join(OUT, name + "_NoSilk.kicad_mod"), "w").write(txt)---if __name__ == "__main__":- ocp_edge(); oculink(); faceplate_mount(); testpad_05(); chip_nosilk()- print("wrote", sorted(os.listdir(OUT)))