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/gen_schematic.pydeleted−361@@ -1,361 +0,0 @@-#!/usr/bin/env python3-"""Generate kicad/ocp-oculink-riser.kicad_sch from design/netlist.json.--Every pin gets a short wire and a net label (a 168-pin card edge and four 42-pin receptacles read best as labelled-blocks); unused pins get no-connect flags; power nets get PWR_FLAG. Symbols are embedded (lib_symbols): standard-ones copied verbatim from KiCad 10's own libraries, project ones (OCP edge, OCuLink, the ICs) generated here with the-datasheet pin numbers and names."""-import json, os, re, sys, uuid, datetime-sys.path.insert(0, os.path.dirname(__file__))-from footprints import ocp_pinout--HERE = os.path.dirname(__file__)-ROOT = os.path.join(HERE, "..")-KSYM = "/mnt/c/Users/john/AppData/Local/Programs/KiCad/10.0/share/kicad/symbols"-OUT = os.path.join(ROOT, "kicad", "ocp-oculink-riser.kicad_sch")-NS = uuid.UUID("0b8b4a1e-7f6d-4f0e-9d3a-2a6a1c5b9e71")-nl = json.load(open(os.path.join(ROOT, "design", "netlist.json")))-pin_net = {p: n for n, ps in nl["nets"].items() for p in ps}-comp = {c["ref"]: c for c in nl["components"]}-ROOT_UUID = str(uuid.uuid5(NS, "root"))---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 q(s):- return '"' + s.replace("\\", "\\\\").replace('"', '\\"') + '"'---# ------------------------------------------------------------------ s-expression extraction from KiCad libs-def extract(libfile, name):- txt = open(libfile).read()- m = re.search(r'\n[ \t]*\(symbol "' + re.escape(name) + r'"\s*\n', txt)- if not m:- raise SystemExit(f"{name} not in {libfile}")- i = txt.index("(", m.start())- depth, j, n = 0, i, len(txt)- while j < n:- c = txt[j]- if c == '"':- j += 1- while j < n and txt[j] != '"':- j += 2 if txt[j] == "\\" else 1- elif c == "(":- depth += 1- elif c == ")":- depth -= 1- if depth == 0:- return txt[i:j + 1]- j += 1- raise SystemExit("unbalanced")---def lib_symbol(lib, name):- s = extract(os.path.join(KSYM, lib + ".kicad_sym"), name)- return s.replace(f'(symbol "{name}"', f'(symbol "{lib}:{name}"', 1)---# ------------------------------------------------------------------ generated symbols-def gen_symbol(libid, refp, left, right, top=(), bottom=(), width=None, units=None, extra_text=None):- """left/right: lists of (number, name) or None for a gap. Returns (sexp, pin_positions{number:(x,y,angle)})."""- name = libid.split(":")[1]- P = 2.54- if units is None:- units = [(left, right, top, bottom)]- out = [f'(symbol {q(libid)} (pin_names (offset 1.016)) (exclude_from_sim no) (in_bom yes) (on_board yes)',- f' (property "Reference" {q(refp)} (at 0 0 0) (effects (font (size 1.27 1.27))))',- f' (property "Value" {q(name)} (at 0 0 0) (effects (font (size 1.27 1.27))))',- f' (property "Footprint" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))',- f' (property "Datasheet" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))']- pos = {}- for ui, (lf, rt, tp, bt) in enumerate(units, start=1):- n = max(len(lf), len(rt))- h = (n + 1) * P- maxlen = max([len(x[1]) for x in lf + rt if x] + [4])- w = width or max(20.32, round((maxlen * 1.27 * 2 + 6) / P) * P)- tw = max(len(tp), len(bt))- w = max(w, (tw + 1) * P)- x0, y0 = -w / 2, h / 2- out.append(f' (symbol {q(name + f"_{ui}_1")}')- out.append(f' (rectangle (start {f(x0)} {f(y0)}) (end {f(-x0)} {f(-y0)}) (stroke (width 0.254) (type default)) (fill (type background)))')- for side, lst in (("L", lf), ("R", rt)):- for k, pn in enumerate(lst):- if pn is None:- continue- num, pname = pn- y = y0 - (k + 1) * P- if side == "L":- x, ang = x0 - P, 0- else:- x, ang = -x0 + P, 180- out.append(f' (pin passive line (at {f(x)} {f(y)} {ang}) (length 2.54) (name {q(pname)} (effects (font (size 1.016 1.016)))) (number {q(num)} (effects (font (size 1.016 1.016)))))')- pos[(ui, num)] = (x, y, ang)- for side, lst in (("T", tp), ("B", bt)):- for k, pn in enumerate(lst):- num, pname = pn- x = x0 + (k + 1) * P- if side == "T":- y, ang = y0 + P, 270- else:- y, ang = -y0 - P, 90- out.append(f' (pin passive line (at {f(x)} {f(y)} {ang}) (length 2.54) (name {q(pname)} (effects (font (size 1.016 1.016)))) (number {q(num)} (effects (font (size 1.016 1.016)))))')- pos[(ui, num)] = (x, y, ang)- out.append(' )')- out.append(')')- return "\n".join(out), pos---edge = ocp_pinout()---def edge_units():- def pins(prefix, rng):- return [(f"{prefix}{n}", edge[f"{prefix}{n}"].replace("+3.3V_EDGE", "+3V3_EDGE")) for n in rng]- u1L = [(f"OCP_B{n}", edge[f"OCP_B{n}"]) for n in range(1, 15)] + [None] + pins("B", range(1, 17)) + [None] + pins("B", (42, 68, 69, 70))- u1R = [(f"OCP_A{n}", edge[f"OCP_A{n}"]) for n in range(1, 15)] + [None] + pins("A", range(1, 17)) + [None] + pins("A", (42, 68, 69, 70))- u2L = pins("B", [n for n in range(17, 42)]); u2R = pins("A", [n for n in range(17, 42)])- u3L = pins("B", range(43, 68)); u3R = pins("A", range(43, 68))- return [(u1L, u1R, (), ()), (u2L, u2R, (), ()), (u3L, u3R, (), ())]---def oculink_pins():- A = {1: "PWR_3V3_RX", 2: "GND", 3: "PERp0", 4: "PERn0", 5: "GND", 6: "PERp1", 7: "PERn1", 8: "GND", 9: "BP_TYPE",- 10: "CWAKE#", 11: "GND", 12: "REFCLK+", 13: "REFCLK-", 14: "GND", 15: "PERp2", 16: "PERn2", 17: "GND",- 18: "PERp3", 19: "PERn3", 20: "GND", 21: "PWR_5V_2"}- Bm = {1: "PWR_5V_1", 2: "GND", 3: "PETp0", 4: "PETn0", 5: "GND", 6: "PETp1", 7: "PETn1", 8: "GND", 9: "2W_CLK",- 10: "2W_DATA", 11: "GND", 12: "PERST#", 13: "CPRSNT#", 14: "GND", 15: "PETp2", 16: "PETn2", 17: "GND",- 18: "PETp3", 19: "PETn3", 20: "GND", 21: "PWR_3V3_TX"}- return [(f"A{n}", A[n]) for n in range(1, 22)], [(f"B{n}", Bm[n]) for n in range(1, 22)], (), [("SH", "SHIELD")]---SYMS = {}-POS = {}-SYMS["Riser:OCP_NIC3_SFF_4C+"], POS["Riser:OCP_NIC3_SFF_4C+"] = gen_symbol("Riser:OCP_NIC3_SFF_4C+", "J", None, None, units=edge_units(), width=50.8)-l, r, t, b = oculink_pins()-SYMS["Riser:OCuLink_4i_Root"], POS["Riser:OCuLink_4i_Root"] = gen_symbol("Riser:OCuLink_4i_Root", "J", l, r, t, b, width=30.48)-SYMS["Riser:SN74LV165A"], POS["Riser:SN74LV165A"] = gen_symbol(- "Riser:SN74LV165A", "U",- [("1", "SH/~{LD}"), ("2", "CLK"), ("15", "CLK_INH"), ("10", "SER"), None, ("11", "A"), ("12", "B"), ("13", "C"),- ("14", "D"), ("3", "E"), ("4", "F"), ("5", "G"), ("6", "H")],- [("9", "QH"), ("7", "~{QH}")], [("16", "VCC")], [("8", "GND")], width=17.78)-SYMS["Riser:BR24H64FJ"], POS["Riser:BR24H64FJ"] = gen_symbol(- "Riser:BR24H64FJ", "U", [("1", "A0"), ("2", "A1"), ("3", "A2"), ("7", "WP")], [("5", "SDA"), ("6", "SCL")],- [("8", "VCC")], [("4", "GND")], width=15.24)-SYMS["Riser:TPS3808G33"], POS["Riser:TPS3808G33"] = gen_symbol(- "Riser:TPS3808G33", "U", [("5", "SENSE"), ("3", "~{MR}"), ("4", "CT")], [("1", "~{RESET}")],- [("6", "VDD")], [("2", "GND")], width=15.24)-SYMS["Riser:SN74LVC125A"], POS["Riser:SN74LVC125A"] = gen_symbol(- "Riser:SN74LVC125A", "U",- [("2", "1A"), ("1", "~{1OE}"), ("5", "2A"), ("4", "~{2OE}"), ("9", "3A"), ("10", "~{3OE}"), ("12", "4A"), ("13", "~{4OE}")],- [("3", "1Y"), None, ("6", "2Y"), None, ("8", "3Y"), None, ("11", "4Y")], [("14", "VCC")], [("7", "GND")], width=15.24)-SYMS["Riser:TPD4E05U06"], POS["Riser:TPD4E05U06"] = gen_symbol(- "Riser:TPD4E05U06", "U", [("1", "D1+"), ("2", "D1-"), ("4", "D2+"), ("5", "D2-")],- [("10", "NC"), ("9", "NC"), ("7", "NC"), ("6", "NC")], (), [("3", "GND"), ("8", "GND")], width=12.7)-# Adom-style IC symbols (gen/ic_symbols.py -> adom-symbol create, chip outline baked in)-ADOMSYM = os.path.join(ROOT, "kicad", "lib", "adom-symbols")-for name in ("SN74LV165APWR", "BR24H64FJ-5ACE2", "TPS3808G33DBV", "SN74LVC125APWR", "TPD4E05U06QDQARQ1"):- txt = extract(os.path.join(ADOMSYM, name, name + ".kicad_sym"), name)- txt = txt.replace(f'(symbol "{name}"', f'(symbol "Riser:{name}"', 1)- SYMS[f"Riser:{name}"] = txt- POS[f"Riser:{name}"] = None # filled below by std_pins-STD = {"R": ("Device", "R"), "C": ("Device", "C"), "LED": ("Device", "LED"), "TP": ("Connector", "TestPoint"),- "JP": ("Jumper", "SolderJumper_2_Open"), "H": ("Mechanical", "MountingHole_Pad"), "PWR": ("power", "PWR_FLAG")}--# pin positions of standard symbols (read from the library text: (pin ... (at x y a) ... (number "n"-def std_pins(text):- pos = {}- for m in re.finditer(r'\(pin \w+ \w+\s*\(at ([-\d.]+) ([-\d.]+) ([-\d.]+)\).*?\(number "([^"]+)"', text, re.S):- pos[(1, m.group(4))] = (float(m.group(1)), float(m.group(2)), float(m.group(3)))- return pos---for key, (lib, name) in STD.items():- txt = lib_symbol(lib, name)- SYMS[f"{lib}:{name}"] = txt- POS[f"{lib}:{name}"] = std_pins(txt)-for k in list(POS):- if POS[k] is None:- POS[k] = std_pins(SYMS[k])--SYMOF = {}-for c in nl["components"]:- r = c["ref"]- if r == "J1": SYMOF[r] = "Riser:OCP_NIC3_SFF_4C+"- elif r in ("J2", "J3", "J4", "J5"): SYMOF[r] = "Riser:OCuLink_4i_Root"- elif r == "U1": SYMOF[r] = "Riser:SN74LV165APWR"- elif r == "U2": SYMOF[r] = "Riser:BR24H64FJ-5ACE2"- elif r == "U3": SYMOF[r] = "Riser:TPS3808G33DBV"- elif r == "U4": SYMOF[r] = "Riser:SN74LVC125APWR"- elif r in ("U5", "U6", "U7", "U8"): SYMOF[r] = "Riser:TPD4E05U06QDQARQ1"- elif r.startswith("R"): SYMOF[r] = "Device:R"- elif r.startswith("C"): SYMOF[r] = "Device:C"- elif r.startswith("D"): SYMOF[r] = "Device:LED"- elif r.startswith("TP"): SYMOF[r] = "Connector:TestPoint"- elif r.startswith("JP"): SYMOF[r] = "Jumper:SolderJumper_2_Open"- elif r.startswith("H"): SYMOF[r] = "Mechanical:MountingHole_Pad"--# ------------------------------------------------------------------ placement on an A1 sheet (mm, y down; 2.54 grid)-G = 2.54-def g(v):- return round(v / G) * G--PLACE = {} # ref -> list of (unit, x, y, rot)-PLACE["J1"] = [(1, g(60), g(110), 0), (2, g(60), g(285), 0), (3, g(60), g(430), 0)]-for k in range(4):- PLACE[f"J{2+k}"] = [(1, g(470 + 95 * k), g(105), 0)]-PLACE["U1"] = [(1, g(210), g(95), 0)]-PLACE["U2"] = [(1, g(210), g(190), 0)]-PLACE["U3"] = [(1, g(320), g(95), 0)]-PLACE["U4"] = [(1, g(320), g(190), 0)]-for k in range(4):- PLACE[f"U{5+k}"] = [(1, g(470 + 95 * k), g(235), 0)]-# two-pin parts: vertical (rot 90) in rows under their blocks-row = {"scan": (g(175), g(150)), "fru": (g(175), g(240)), "power": (g(285), g(140)), "perst": (g(285), g(255)),- "host": (g(175), g(285)), "test": (g(285), g(320))}-two = [c for c in nl["components"] if c["ref"] not in PLACE and c["ref"] not in ("H1", "H2")]-cursor = {}-for c in two:- blk = c["block"]- if blk.startswith("port"):- k = int(blk[-1]); base = (g(452 + 95 * k), g(285))- elif blk == "mech":- base = (g(285), g(380))- else:- base = row.get(blk, (g(285), g(380)))- n = cursor.get(base, 0); cursor[base] = n + 1- PLACE[c["ref"]] = [(1, base[0] + g(15.24) * (n % 6), base[1] + g(22.86) * (n // 6), 0)]-PLACE["H1"] = [(1, g(400), g(400), 0)]-PLACE["H2"] = [(1, g(420), g(400), 0)]--# ------------------------------------------------------------------ emit-items = []-labels_at = set()---def pin_world(libid, unit, num, x, y, rot):- px, py, ang = POS[libid][(unit, num)] if (unit, num) in POS[libid] else POS[libid][(1, num)]- # symbol coords are y-up; rotation not used (all 0)- return x + px, y - py, ang---def wire(x1, y1, x2, y2):- items.append(f' (wire (pts (xy {f(x1)} {f(y1)}) (xy {f(x2)} {f(y2)})) (stroke (width 0) (type default)) (uuid {q(U("w", x1, y1, x2, y2))}))')---def label(name, x, y, ang):- just = "left" if ang in (0, 270) else "right"- items.append(f' (global_label {q(name)} (shape passive) (at {f(x)} {f(y)} {ang}) (fields_autoplaced yes) (effects (font (size 1.016 1.016)) (justify {just})) (uuid {q(U("l", name, x, y))})\n (property "Intersheetrefs" "${{INTERSHEET_REFS}}" (at {f(x)} {f(y)} 0) (effects (font (size 1.016 1.016)) (hide yes))))')---def noconn(x, y):- items.append(f' (no_connect (at {f(x)} {f(y)}) (uuid {q(U("nc", x, y))}))')---syms_out = []-for c in nl["components"]:- ref = c["ref"]; libid = SYMOF[ref]- for (unit, x, y, rot) in PLACE[ref]:- # snap: shift the instance so its pin ends land on the 2.54 mm grid (world x = X + px, world y = Y - py)- _pp = [v for (u_, n_), v in POS[libid].items() if u_ == unit]- if _pp:- px0, py0 = _pp[0][0], _pp[0][1]- x = x + ((-px0) % G if abs((-px0) % G - G) > 1e-6 else 0.0) - 0.0- y = y + (py0 % G if abs(py0 % G - G) > 1e-6 else 0.0)- x, y = round(x, 4), round(y, 4)- props = [f' (property "Reference" {q(ref)} (at {f(x)} {f(y - 3)} 0) (effects (font (size 1.27 1.27))))',- f' (property "Value" {q(c["value"])} (at {f(x)} {f(y + 3)} 0) (effects (font (size 1.27 1.27))))',- f' (property "Footprint" {q(c["fp"])} (at {f(x)} {f(y)} 0) (effects (font (size 1.27 1.27)) (hide yes)))',- f' (property "Datasheet" "" (at {f(x)} {f(y)} 0) (effects (font (size 1.27 1.27)) (hide yes)))',- f' (property "MPN" {q(c.get("mpn",""))} (at {f(x)} {f(y)} 0) (effects (font (size 1.27 1.27)) (hide yes)))',- f' (property "Manufacturer" {q(c.get("mfr",""))} (at {f(x)} {f(y)} 0) (effects (font (size 1.27 1.27)) (hide yes)))',- f' (property "Description" {q(c.get("desc",""))} (at {f(x)} {f(y)} 0) (effects (font (size 1.27 1.27)) (hide yes)))']- if libid.startswith(("Device:", "Connector:", "Jumper:", "Mechanical:")):- props[0] = f' (property "Reference" {q(ref)} (at {f(x + 2.54)} {f(y - 1.27)} 0) (effects (font (size 1.27 1.27)) (justify left)))'- props[1] = f' (property "Value" {q(c["value"])} (at {f(x + 2.54)} {f(y + 1.27)} 0) (effects (font (size 1.27 1.27)) (justify left)))'- pins_here = [num for (u_, num) in POS[libid] if u_ == unit]- pinlines = "\n".join(f' (pin {q(num)} (uuid {q(U("pin", ref, num))}))' for num in sorted(set(pins_here)))- inbom = "no" if ref.startswith(("TP", "JP", "H")) or ref == "J1" else "yes"- syms_out.append(f' (symbol (lib_id {q(libid)}) (at {f(x)} {f(y)} {rot}) (unit {unit}) (exclude_from_sim no) (in_bom {inbom}) (on_board yes) (dnp no) (fields_autoplaced yes) (uuid {q(U("sym", ref, unit))})\n'- + "\n".join(props) + "\n" + pinlines +- f'\n (instances (project "ocp-oculink-riser" (path {q("/" + ROOT_UUID)} (reference {q(ref)}) (unit {unit}))))\n )')- for num in sorted(set(pins_here)):- wx, wy, ang = pin_world(libid, unit, num, x, y, rot)- net = pin_net.get(f"{ref}.{num}")- # direction away from the body- d = {0: (-1, 0), 180: (1, 0), 90: (0, 1), 270: (0, -1)}[int(ang) % 360]- ex, ey = wx + d[0] * G, wy + d[1] * G- if net is None:- noconn(wx, wy)- continue- wire(wx, wy, ex, ey)- lang = {(-1, 0): 180, (1, 0): 0, (0, 1): 270, (0, -1): 90}[d]- label(net, ex, ey, lang)--# PWR_FLAG on the off-board rails-pw = []-for i, net in enumerate(("GND", "+3V3_EDGE", "+12V_EDGE")):- x, y = g(400 + 20 * i), g(440)- syms_out.append(f' (symbol (lib_id "power:PWR_FLAG") (at {f(x)} {f(y)} 0) (unit 1) (exclude_from_sim no) (in_bom yes) (on_board yes) (dnp no) (uuid {q(U("pwr", net))})\n'- f' (property "Reference" {q(f"#FLG0{i+1}")} (at {f(x)} {f(y - 3)} 0) (effects (font (size 1.27 1.27)) (hide yes)))\n'- f' (property "Value" "PWR_FLAG" (at {f(x)} {f(y - 4.5)} 0) (effects (font (size 1.27 1.27))))\n'- f' (property "Footprint" "" (at {f(x)} {f(y)} 0) (effects (font (size 1.27 1.27)) (hide yes)))\n'- f' (property "Datasheet" "" (at {f(x)} {f(y)} 0) (effects (font (size 1.27 1.27)) (hide yes)))\n'- f' (pin "1" (uuid {q(U("pwrpin", net))}))\n'- f' (instances (project "ocp-oculink-riser" (path {q("/" + ROOT_UUID)} (reference {q(f"#FLG0{i+1}")}) (unit 1))))\n )')- wire(x, y, x, y + G)- label(net, x, y + G, 270)--# section titles and the design note-notes = [- (g(30), g(20), "OCP NIC 3.0 SFF (Supermicro AIOM) to 4x OCuLink 4i riser - passive, PCIe Gen4 x16 as 4 x4", 3.0),- (g(30), g(30), "Card edge J1: OCP NIC 3.0 v1.6.0 Table 20 (4C+). PRSNTB[3:0]# = 0b0011 -> 4 x4 card (Table 45). Host BIOS: set the AIOM slot to x4x4x4x4.", 1.8),- (g(30), g(36), "Port k = PCIe lanes 4k..4k+3 with REFCLKk and PERSTk# (Table 47). OCuLink pinout: SFF-9402 Table 6-1, root side.", 1.8),- (g(30), g(42), "No AC caps: host TX caps are on the baseboard, device TX caps are on the device. 85 ohm pairs, JLC06161H-3313 stack, see docs.", 1.8),- (g(175), g(70), "Scan chain SR0 (mandatory, OCP 4.5.6)", 2.0),- (g(175), g(170), "FRU EEPROM at 0xA0 + SLOT_ID (OCP 5.10)", 2.0),- (g(285), g(70), "NIC_PWR_GOOD (OCP 4.5.7.6)", 2.0),- (g(285), g(170), "PERST# buffers to the ports", 2.0),- (g(470), g(70), "OCuLink ports (SFF-8611/8621), root side", 2.0),- (g(470), g(215), "Port sideband ESD and termination", 2.0),- (g(30), g(70), "Gold fingers - sideband and power", 2.0),- (g(30), g(250), "Gold fingers - PCIe lanes 0-7", 2.0),- (g(30), g(395), "Gold fingers - PCIe lanes 8-15", 2.0),-]-for (x, y, s, sz) in notes:- items.append(f' (text {q(s)} (exclude_from_sim no) (at {f(x)} {f(y)} 0) (effects (font (size {sz} {sz}) (thickness {f(sz*0.15)}) (bold yes)) (justify left bottom)) (uuid {q(U("t", s))}))')--today = datetime.date(2026, 10, 12).isoformat()-doc = ['(kicad_sch (version 20231120) (generator "riser-gen") (generator_version "1.0")',- f' (uuid {q(ROOT_UUID)})', ' (paper "A1")',- f' (title_block (title "OCP NIC 3.0 SFF to 4x OCuLink riser") (date {q(today)}) (rev "A") (company "Adom") (comment 1 "Generated from design/netlist.json by gen/gen_schematic.py"))',- ' (lib_symbols']-for k, v in SYMS.items():- doc.append(" " + v.replace("\n", "\n "))-doc.append(' )')-doc += items-doc += syms_out-doc.append(' (sheet_instances (path "/" (page "1")))')-doc.append(')')-open(OUT, "w").write("\n".join(doc) + "\n")-print("wrote", OUT, len(syms_out), "symbol instances")--# ------------------------------------------------------------------ project symbol library and lib tables-lib = ['(kicad_symbol_lib (version 20231120) (generator "riser-gen") (generator_version "1.0")']-for k, v in SYMS.items():- if k.startswith("Riser:"):- lib.append(" " + v.replace(f'(symbol "{k}"', f'(symbol "{k.split(":")[1]}"', 1).replace("\n", "\n "))-lib.append(")")-os.makedirs(os.path.join(ROOT, "kicad", "lib"), exist_ok=True)-open(os.path.join(ROOT, "kicad", "lib", "Riser.kicad_sym"), "w").write("\n".join(lib) + "\n")-open(os.path.join(ROOT, "kicad", "sym-lib-table"), "w").write(- '(sym_lib_table\n (version 7)\n (lib (name "Riser")(type "KiCad")(uri "${KIPRJMOD}/lib/Riser.kicad_sym")(options "")(descr "OCP OCuLink riser project symbols"))\n)\n')-open(os.path.join(ROOT, "kicad", "fp-lib-table"), "w").write(- '(fp_lib_table\n (version 7)\n (lib (name "Riser")(type "KiCad")(uri "${KIPRJMOD}/lib/Riser.pretty")(options "")(descr "OCP OCuLink riser project footprints"))\n)\n')-print("wrote Riser.kicad_sym and lib tables")