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/build_board.pydeleted−207@@ -1,207 +0,0 @@-"""Run with KiCad 10's python.exe. Stage 'base': footprints, nets, outline -> board + pads.json.-Stage 'final': base + tracks/vias/zones from routes.json, zones filled, saved."""-import json, os, sys, math-import pcbnew--HERE = os.path.dirname(os.path.abspath(__file__))-ROOT = os.path.dirname(HERE)-STD_FP = r"C:\Users\john\AppData\Local\Programs\KiCad\10.0\share\kicad\footprints"-PRJ_FP = os.path.join(ROOT, "kicad", "lib")-OUT = os.path.join(ROOT, "kicad", "ocp-oculink-riser.kicad_pcb")-STAGE = sys.argv[1] if len(sys.argv) > 1 else "base"--nl = json.load(open(os.path.join(ROOT, "design", "netlist.json")))-lay = json.load(open(os.path.join(ROOT, "design", "layout.json")))-MM = pcbnew.FromMM---def V(x, y):- return pcbnew.VECTOR2I(MM(x), MM(y))---LAYERS = {"L1": pcbnew.F_Cu, "L3": pcbnew.In2_Cu, "L5": pcbnew.In4_Cu, "L6": pcbnew.B_Cu,- "L2": pcbnew.In1_Cu, "L4": pcbnew.In3_Cu}---def new_board():- b = pcbnew.BOARD()- b.SetCopperLayerCount(6)- ds = b.GetDesignSettings()- ds.SetBoardThickness(MM(1.6))- # JLCPCB 6-layer standard process minimums (adom/pcb-design-rules jlcpcb-6L)- ds.m_MinClearance = MM(0.09); ds.m_TrackMinWidth = MM(0.09)- ds.m_ViasMinSize = MM(0.25); ds.m_ViasMinAnnularWidth = MM(0.05)- ds.m_MinThroughDrill = MM(0.15); ds.m_HoleToHoleMin = MM(0.2)- ds.m_HoleClearance = MM(0.12); ds.m_CopperEdgeClearance = MM(0.2)- try:- nc = ds.m_NetSettings.GetDefaultNetclass()- except Exception:- nc = None- if nc is not None:- nc.SetClearance(MM(0.12)); nc.SetTrackWidth(MM(0.15))- nc.SetViaDiameter(MM(0.4)); nc.SetViaDrill(MM(0.2))- try:- nc.SetDiffPairWidth(MM(0.163)); nc.SetDiffPairGap(MM(0.15))- except Exception:- pass- b.SetLayerName(pcbnew.In1_Cu, "In1.Cu")- # nets- nets = {}- ncp = os.path.join(ROOT, "design", "nc_nets.json")- nc = json.load(open(ncp)) if os.path.exists(ncp) else {}- for name in sorted(set(nl["nets"]) | set(nc.values())):- ni = pcbnew.NETINFO_ITEM(b, name)- b.Add(ni)- nets[name] = ni- pin_net = {}- for name, pins in nl["nets"].items():- for p in pins:- pin_net[p] = name- pin_net.update(nc)- # footprints- for c in nl["components"]:- lib, fpname = c["fp"].split(":")- libdir = os.path.join(PRJ_FP, lib + ".pretty") if lib == "Riser" else os.path.join(STD_FP, lib + ".pretty")- fp = pcbnew.FootprintLoad(libdir, fpname)- if fp is None:- raise SystemExit("cannot load " + c["fp"])- fp.SetReference(c["ref"])- fp.SetValue(c["value"])- fp.SetFPIDAsString(c["fp"])- for k, v in (("MPN", c.get("mpn", "")), ("Manufacturer", c.get("mfr", "")), ("Source", c.get("src", "")),- ("Description", c.get("desc", ""))):- fp.SetField(k, v or "")- f_ = fp.GetField(k)- if f_ is not None:- f_.SetVisible(False)- # silk: the silkscreen step places every reference; until then they are hidden. Chip R/C keep no body silk- # use the project's no-silk copies (Riser:*_NoSilk); ICs, LEDs and connectors keep their pin-1 / polarity marks.- fp.Reference().SetVisible(False)- x, y, rot, side = lay["placement"][c["ref"]]- b.Add(fp)- fp.SetPosition(V(x, y))- fp.SetOrientationDegrees(rot)- if side == "B":- fp.Flip(V(x, y), pcbnew.FLIP_DIRECTION_TOP_BOTTOM)- for pad in fp.Pads():- key = f'{c["ref"]}.{pad.GetNumber()}'- if key in pin_net:- pad.SetNet(nets[pin_net[key]])- # 3D model binding- if c.get("model"):- fp.Models().clear()- m = pcbnew.FP_3DMODEL()- m.m_Filename = c["model"]- fp.Models().push_back(m)- # outline: lines and arcs (every corner filleted)- for e in lay["edges"]:- s = pcbnew.PCB_SHAPE(b)- if e[0] == "line":- s.SetShape(pcbnew.SHAPE_T_SEGMENT)- s.SetStart(V(e[1], e[2])); s.SetEnd(V(e[3], e[4]))- else:- s.SetShape(pcbnew.SHAPE_T_ARC)- s.SetArcGeometry(V(e[1], e[2]), V(e[3], e[4]), V(e[5], e[6]))- s.SetLayer(pcbnew.Edge_Cuts); s.SetWidth(MM(0.05))- b.Add(s)- return b, nets---def export_pads(b):- out = []- for fp in b.GetFootprints():- for pad in fp.Pads():- sz = pad.GetSize(pcbnew.F_Cu)- lset = [pcbnew.BOARD.GetStandardLayerName(l) for l in pad.GetLayerSet().CuStack()]- poly = pad.GetEffectivePolygon(pcbnew.F_Cu if pad.IsOnLayer(pcbnew.F_Cu) else pcbnew.B_Cu, pcbnew.ERROR_INSIDE)- outline = []- for oi in range(poly.OutlineCount()):- ol = poly.Outline(oi)- outline.append([(pcbnew.ToMM(ol.CPoint(i).x), pcbnew.ToMM(ol.CPoint(i).y)) for i in range(ol.PointCount())])- dr = pad.GetDrillSize()- out.append(dict(ref=fp.GetReference(), num=pad.GetNumber(), net=pad.GetNetname(),- x=pcbnew.ToMM(pad.GetPosition().x), y=pcbnew.ToMM(pad.GetPosition().y),- w=pcbnew.ToMM(sz.x), h=pcbnew.ToMM(sz.y), layers=lset, poly=outline,- drill=[pcbnew.ToMM(dr.x), pcbnew.ToMM(dr.y)], attr=str(pad.GetAttribute())))- return out---def add_routes(b, nets, which="routes.json"):- r = json.load(open(os.path.join(ROOT, "design", which)))- for (lay_, net, x1, y1, x2, y2, w) in r["tracks"]:- t = pcbnew.PCB_TRACK(b)- t.SetStart(V(x1, y1)); t.SetEnd(V(x2, y2)); t.SetWidth(MM(w))- t.SetLayer(LAYERS[lay_]); t.SetNet(nets[net])- b.Add(t)- for (x, y, net, d, h) in r["vias"]:- v = pcbnew.PCB_VIA(b)- v.SetPosition(V(x, y)); v.SetWidth(MM(d)); v.SetDrill(MM(h))- v.SetViaType(pcbnew.VIATYPE_THROUGH)- v.SetLayerPair(pcbnew.F_Cu, pcbnew.B_Cu)- v.SetNet(nets[net])- b.Add(v)- for z in r.get("zones", []):- zone = pcbnew.ZONE(b)- zone.SetLayer(LAYERS[z["layer"]] if z["layer"] in LAYERS else getattr(pcbnew, z["layer"]))- if z.get("keepout"):- zone.SetIsRuleArea(True)- zone.SetDoNotAllowTracks(z.get("no_tracks", True)); zone.SetDoNotAllowVias(z.get("no_vias", True))- zone.SetDoNotAllowZoneFills(z.get("no_pour", True)); zone.SetDoNotAllowPads(False)- zone.SetDoNotAllowFootprints(False)- if z.get("layers"):- ls = pcbnew.LSET()- for ln in z["layers"]:- ls.AddLayer(LAYERS[ln])- zone.SetLayerSet(ls)- else:- zone.SetNet(nets[z["net"]])- zone.SetMinThickness(MM(0.15))- zone.SetLocalClearance(MM(z.get("clearance", 0.2)))- zone.SetAssignedPriority(z.get("priority", 0))- zone.SetPadConnection(pcbnew.ZONE_CONNECTION_THERMAL if z.get("thermal", True) else pcbnew.ZONE_CONNECTION_FULL)- zone.SetThermalReliefGap(MM(0.25)); zone.SetThermalReliefSpokeWidth(MM(0.3))- zone.SetIslandRemovalMode(pcbnew.ISLAND_REMOVAL_MODE_ALWAYS)- ol = zone.Outline()- ol.NewOutline()- for (x, y) in z["poly"]:- ol.Append(MM(x), MM(y))- b.Add(zone)- for t in r.get("texts", []):- tx = pcbnew.PCB_TEXT(b)- tx.SetText(t["text"]); tx.SetPosition(V(t["x"], t["y"]))- tx.SetLayer(getattr(pcbnew, t["layer"].replace(".", "_")))- tx.SetTextSize(V(t["size"], t["size"])); tx.SetTextThickness(MM(t["size"] * 0.15))- if t.get("angle"):- tx.SetTextAngleDegrees(t["angle"])- if t["layer"].startswith("B."):- tx.SetMirrored(True)- b.Add(tx)---if __name__ == "__main__":- b, nets = new_board()- if STAGE == "base":- json.dump(export_pads(b), open(os.path.join(ROOT, "design", "pads.json"), "w"))- pcbnew.SaveBoard(OUT.replace(".kicad_pcb", "-base.kicad_pcb"), b)- print("base ok", len(b.GetFootprints()), "footprints")- elif STAGE == "start":- # the run's start board: interface parts in place, the rest staged beside the outline, live copper held back- st = json.load(open(os.path.join(ROOT, "design", "routes_start.json")))- for fp in b.GetFootprints():- ref = fp.GetReference()- if ref in st["stage"]:- x, y, rot, side = st["stage"][ref]- fp.SetPosition(V(x, y)); fp.SetOrientationDegrees(rot)- add_routes(b, nets, "routes_start.json")- filler = pcbnew.ZONE_FILLER(b)- filler.Fill(b.Zones())- out = os.path.join(ROOT, "aiflow", "start", "ocp-oculink-riser.kicad_pcb")- os.makedirs(os.path.dirname(out), exist_ok=True)- pcbnew.SaveBoard(out, b)- print("start ok", len(b.GetTracks()), "items")- else:- add_routes(b, nets)- filler = pcbnew.ZONE_FILLER(b)- filler.Fill(b.Zones())- pcbnew.SaveBoard(OUT, b)- print("final ok", len(b.GetTracks()), "track/via items,", b.GetAreaCount(), "zones")