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/layout.pydeleted−124@@ -1,124 +0,0 @@-#!/usr/bin/env python3-"""Board outline, keepouts and component placement for the riser (board frame: x 0..76, y 0 faceplate .. 115 fingers)."""-import json, math, os, sys-sys.path.insert(0, os.path.dirname(__file__))-import hs--W, L = 76.0, 115.0-H = 38.0---def outline():- """Closed list of (kind, ...) segments: ('line', x1,y1,x2,y2) or ('arc', sx,sy, mx,my, ex,ey).- OCP NIC 3.0 v1.6.0 Fig. 8/9 + SFF-TA-1002 Fig. 5-39 (tongue, keys, lead-in chamfers)."""- segs = []- F = hs.F- tongue = [(F - 31.480, F - 20.4 - 1.125), (F - 20.4 + 1.125, F - 0.925),- (F + 0.925, F + 12.26 - 1.375), (F + 12.26 + 1.375, F + 31.995)]- slots = [(F - 20.4, 2.25, 7.0), (F, 1.85, 7.0), (F + 12.26, 2.75, 7.0)]- ch = 0.75 # 45 degree lead-in on every tongue corner- # ears: x [0, 3.76] and [72.24, 76]; outer slots 2.50 wide, 6.10 deep; 30 degree chamfer 3.00 long- ear_w, oslot_w, oslot_d = 3.7625, 2.5, 6.10- c30 = 3.00 * math.tan(math.radians(30))- pts = []-- def P(x, y):- pts.append((x, y))-- # start at faceplate edge, top-left, go clockwise in board frame (x right, y down)- P(0, 0); P(W, 0)- # right long edge down to the latch notch: notch 3.50 wide, 2.75 deep, ends 30.00 from the card edge- ny1, ny0 = L - 30.0 - 3.5, L - 30.0- P(W, ny1); P(W - 2.75, ny1); P(W - 2.75, ny0); P(W, ny0)- # right ear with 30 degree chamfer- P(W, L - 3.00); P(W - c30, L); P(W - ear_w, L)- # right outer slot (round bottom)- segs_pts = list(pts)- return segs_pts, tongue, slots, (ear_w, oslot_w, oslot_d, c30, ny1, ny0, ch)---def outline_poly():- """The outline as a simple polygon (arcs as polylines) for checks, built independently of outline_segments."""- F = hs.F- pts = []- W_, L_ = W, L- c30 = 3.00 * math.tan(math.radians(30))- ear = 3.7625- ow, od = 2.5, 6.10- ch = 0.75- tongue = [(F - 31.480, F - 20.4 - 1.125), (F - 20.4 + 1.125, F - 0.925),- (F + 0.925, F + 12.26 - 1.375), (F + 12.26 + 1.375, F + 31.995)]- keys = [(F - 20.4, 2.25, 7.0), (F, 1.85, 7.0), (F + 12.26, 2.75, 7.0)]- ny1, ny0 = L_ - 33.5, L_ - 30.0-- def roundslot(xc, w, d, east_to_west=True, n=12):- r = w / 2- out = [(xc + r, L_), (xc + r, L_ - d + r)]- for i in range(1, n):- a = math.pi * i / n- out.append((xc + r * math.cos(a), L_ - d + r - r * math.sin(a)))- out += [(xc - r, L_ - d + r), (xc - r, L_)]- return out-- pts += [(0, 0), (W_, 0), (W_, ny1), (W_ - 2.75, ny1), (W_ - 2.75, ny0), (W_, ny0), (W_, L_ - 3.00), (W_ - c30, L_),- (W_ - ear, L_)]- # right outer slot- pts += roundslot(tongue[3][1] + ow / 2, ow, od)[1:-1]- for i in range(3, -1, -1):- a, b = tongue[i]- pts += [(b, L_ - ch), (b - ch, L_), (a + ch, L_), (a, L_ - ch)]- if i > 0:- xc, w, d = keys[i - 1]- pts += roundslot(xc, w, d)[1:-1]- pts += roundslot(tongue[0][0] - ow / 2, ow, od)[1:-1]- pts += [(ear, L_), (c30, L_), (0, L_ - 3.00), (0, ny0), (2.75, ny0), (2.75, ny1), (0, ny1)]- return pts---# ------------------------------------------------------------------------------------------ placement-def placement():- """ref -> (x, y, rot_deg, side). rot in KiCad degrees (counter-clockwise on screen)."""- P = {}- P["J1"] = (hs.F, L, 0, "F")- for k in range(4):- P[f"J{2+k}"] = (hs.CX[k], 0.0, 0, "F")- P["H1"] = (5.5, 115 - 109.76, 0, "F")- P["H2"] = (70.5, 115 - 109.76, 0, "F")- # west region (top side): scan chain, FRU, supervisor, PERST buffer- P["U1"] = (8.8, 60.0, 0, "F") # TSSOP-16- P["C1"] = (13.3, 56.4, 90, "F")- P["R3"] = (4.2, 55.4, 0, "F"); P["R4"] = (4.2, 56.75, 0, "F"); P["R5"] = (4.6, 64.0, 0, "F")- P["R6"] = (13.3, 62.6, 90, "F"); P["R7"] = (4.6, 65.4, 0, "F")- P["U2"] = (8.8, 72.0, 0, "F") # SOIC-8- P["C2"] = (13.4, 69.0, 90, "F"); P["R8"] = (4.6, 75.6, 0, "F"); P["JP1"] = (5.2, 78.2, 0, "F")- P["U3"] = (8.8, 48.6, 0, "F") # SOT-23-6- P["C3"] = (12.4, 48.6, 90, "F"); P["R9"] = (5.2, 50.8, 0, "F")- P["U4"] = (8.8, 39.0, 0, "F") # TSSOP-14- P["C4"] = (13.3, 35.6, 90, "F")- for k in range(4):- P[f"R{11+k}"] = (13.6, 38.2 + 1.3 * k, 0, "F") # 33R series- # power indication and bulk- P["C5"] = (14.2, 92.0, 90, "F")- P["D1"] = (5.0, 28.0, 90, "F"); P["R23"] = (5.0, 31.0, 90, "F")- P["D2"] = (7.4, 28.0, 90, "F"); P["R24"] = (7.4, 31.0, 90, "F")- P["TP1"] = (4.6, 87.0, 0, "F"); P["TP2"] = (4.6, 89.8, 0, "F"); P["TP3"] = (4.6, 92.6, 0, "F")- P["TP4"] = (4.6, 95.4, 0, "F"); P["TP5"] = (4.6, 98.2, 0, "F")- # 0.5 mm probe pads on their nets' routed top-layer traces (design/tp_spots.json, found by clearance search)- _sp = json.load(open(os.path.join(os.path.dirname(__file__), "..", "design", "tp_spots.json")))- for _ref, _net in (("TP6", "NIC_PWR_GOOD0"), ("TP7", "PORT0_PERST#"), ("TP8", "PORT1_PERST#"), ("TP9", "PORT2_PERST#"),- ("TP10", "PORT3_PERST#"), ("TP11", "SCAN0_CLK"), ("TP12", "SCAN0_DATA_OUT")):- P[_ref] = (_sp[_net][1], _sp[_net][2], 0, "F")- # PRSNT straps beside B70 (east)- P["R1"] = (71.6, 101.6, 90, "F"); P["R2"] = (70.4, 101.6, 90, "F")- # per-port ESD between the connector and the PERST# jog (pins 1..5 face the connector at rotation 270)- for k in range(4):- P[f"U{5+k}"] = (hs.CX[k] - 3.0, 9.6, 270, "F")- P[f"R{15+k}"] = (15.8, 38.2 + 1.3 * k, 0, "F") # PERST# pull-downs beside the 33 ohm series parts- return P---if __name__ == "__main__":- pl = placement()- import outline2- json.dump({"placement": pl, "outline": outline2.polygon(), "edges": outline2.fillet()[0]}, open(os.path.join(os.path.dirname(__file__), "..", "design", "layout.json"), "w"), indent=1)- print(len(pl), "placed;", len(outline_poly()), "outline points")