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.
main
John Lauer
README: component table per the wiki format, open-source section, no duplicate hero; generators at gen/
39a59ef
7d ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
#!/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)))