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/hs.pydeleted−559@@ -1,559 +0,0 @@-#!/usr/bin/env python3-"""High-speed routing for the OCP NIC 3.0 SFF -> 4x OCuLink riser.--Board frame: x across the card (0..76), y from the faceplate edge (0) to the gold-finger edge (115), KiCad y-down.-Layers: L1=F.Cu, L2=In1 GND, L3=In2, L4=In3 GND, L5=In4, L6=B.Cu GND (and the A-side fingers).--Each differential pair is a centreline of nodes (x, y, layer). Consecutive nodes on one layer are a trace; a layer-change is a via pair placed across the direction of travel. The n trace is the left offset (west when heading north),-p the right one; that holds the whole way because every path is an octilinear S-shape.--Lane reversal (OCuLink ports face the faceplate, so each port sees its lanes in the opposite order to the fingers):- stage A (just past the via row): lanes 0<->1 and 2<->3 cross; lane 0 on L1, lane 1 on L3, lane 3 on L1, lane 2 on L5- lane 3 drops L1 -> L5 in band M- stage B (before the connector): block {1,0} (L3, L1) crosses block {3,2} (both L5)-"""-import math, json, os, sys-sys.path.insert(0, os.path.dirname(__file__))-from footprints import ocp_pin_x--F = 38.0 - 0.258 # datum F: H (card centre line, x=38) minus 0.258 (SFF-TA-1002 Fig 5-39, OCP Detail A)-EDGE_Y = 115.0-PAD_TOP = EDGE_Y - 3.00 # inner end of every gold finger-CX = [16.1 + 14.6 * k for k in range(4)] # OCuLink port centres (pitch 14.6, inside the 59.1 mm faceplate zone)--# 85 ohm differential, JLC06161H-3313 (design/zsolve.py, 2D field solve)-PAIR = {"L1": (0.163, 0.15), "L6": (0.163, 0.15), "L3": (0.131, 0.15), "L5": (0.121, 0.15)}-KLAY = {"L1": "F.Cu", "L3": "In2.Cu", "L5": "In4.Cu", "L6": "B.Cu"}-VIA_P = 0.70 # signal via pair pitch-VIA_D, VIA_H = 0.40, 0.20 # transition vias-VIP_D, VIP_H = 0.25, 0.15 # via-in-pad at the OCuLink pads (JLC 6L: filled and capped by default)---def pitch(layer):- w, s = PAIR[layer]- return w + s---class Pair:- def __init__(self, name, nnet, pnet, kind):- self.name, self.nnet, self.pnet, self.kind = name, nnet, pnet, kind- self.nodes = [] # (x, y, layer)- self.start = None # ((xn, yn), (xp, yp), layer) pad ends- self.end = None # ((xn, yn), (xp, yp), layer, via_in_pad)- self.tracks = [] # (layer, net, x1, y1, x2, y2, w)- self.vias = [] # (x, y, net, d, h)-- def add(self, x, y, layer):- self.nodes.append((round(x, 4), round(y, 4), layer))- return self---def unit(dx, dy):- L = math.hypot(dx, dy)- return (dx / L, dy / L)---def left(d):- return (d[1], -d[0])---def offset_polyline(pts, off):- """Mitred offset of an octilinear polyline (left side positive)."""- out = []- n = len(pts)- for i in range(n):- if i == 0:- d = unit(pts[1][0] - pts[0][0], pts[1][1] - pts[0][1]); l = left(d)- out.append((pts[0][0] + l[0] * off, pts[0][1] + l[1] * off))- elif i == n - 1:- d = unit(pts[-1][0] - pts[-2][0], pts[-1][1] - pts[-2][1]); l = left(d)- out.append((pts[-1][0] + l[0] * off, pts[-1][1] + l[1] * off))- else:- d1 = unit(pts[i][0] - pts[i - 1][0], pts[i][1] - pts[i - 1][1])- d2 = unit(pts[i + 1][0] - pts[i][0], pts[i + 1][1] - pts[i][1])- l1, l2 = left(d1), left(d2)- bis = (l1[0] + l2[0], l1[1] + l2[1])- bl = math.hypot(*bis)- if bl < 1e-9:- raise ValueError("reversal in path")- bis = (bis[0] / bl, bis[1] / bl)- cosh = bis[0] * l1[0] + bis[1] * l1[1]- k = off / cosh- out.append((pts[i][0] + bis[0] * k, pts[i][1] + bis[1] * k))- return out---def build(pair):- """Turn a pair's centreline nodes into n/p tracks and vias."""- nodes = []- for nd in pair.nodes:- if nodes and abs(nd[0] - nodes[-1][0]) < 1e-6 and abs(nd[1] - nodes[-1][1]) < 1e-6 and nd[2] == nodes[-1][2]:- continue- nodes.append(nd)- # drop collinear middle points on the same layer- clean = [nodes[0]]- for i in range(1, len(nodes) - 1):- a, b, c = clean[-1], nodes[i], nodes[i + 1]- if a[2] == b[2] == c[2]:- cr = (b[0] - a[0]) * (c[1] - b[1]) - (b[1] - a[1]) * (c[0] - b[0])- dot = (b[0] - a[0]) * (c[0] - b[0]) + (b[1] - a[1]) * (c[1] - b[1])- if abs(cr) < 1e-9 and dot > 0:- continue- clean.append(b)- clean.append(nodes[-1])- nodes = clean- pair.nodes = nodes- # split into runs per layer- runs = []- cur = [nodes[0]]- for nd in nodes[1:]:- if nd[2] == cur[-1][2]:- cur.append(nd)- else:- runs.append(cur)- cur = [nd]- runs.append(cur)- vias = [] # positions between runs: last node of run i == first node xy of run i+1- for i in range(len(runs) - 1):- a = runs[i][-1]; b = runs[i + 1][0]- assert abs(a[0] - b[0]) < 1e-6 and abs(a[1] - b[1]) < 1e-6, (pair.name, a, b)- for ri, run in enumerate(runs):- layer = run[0][2]- p = pitch(layer); w = PAIR[layer][0]- pts = [(x, y) for (x, y, _) in run]- # entry and exit fans to via pitch- fan_in = ri > 0- fan_out = ri < len(runs) - 1- for sign, net in ((+1, pair.nnet), (-1, pair.pnet)):- off = sign * p / 2- line = offset_polyline(pts, off)- if fan_in:- d = unit(pts[1][0] - pts[0][0], pts[1][1] - pts[0][1]); l = left(d)- vx, vy = pts[0][0] + l[0] * sign * VIA_P / 2, pts[0][1] + l[1] * sign * VIA_P / 2- f = (VIA_P - p) / 2- line[0] = (line[0][0] + d[0] * f, line[0][1] + d[1] * f)- line.insert(0, (vx, vy))- if fan_out:- d = unit(pts[-1][0] - pts[-2][0], pts[-1][1] - pts[-2][1]); l = left(d)- vx, vy = pts[-1][0] + l[0] * sign * VIA_P / 2, pts[-1][1] + l[1] * sign * VIA_P / 2- f = (VIA_P - p) / 2- line[-1] = (line[-1][0] - d[0] * f, line[-1][1] - d[1] * f)- line.append((vx, vy))- # pad ends- if ri == 0 and pair.start is not None:- padpt = pair.start[0] if sign > 0 else pair.start[1]- line = pad_join(padpt, line, at_start=True, p=p)- if ri == len(runs) - 1 and pair.end is not None:- padpt = pair.end[0] if sign > 0 else pair.end[1]- line = pad_join(padpt, line, at_start=False, p=p)- for (x1, y1), (x2, y2) in zip(line, line[1:]):- if math.hypot(x2 - x1, y2 - y1) > 1e-6:- pair.tracks.append((layer, net, x1, y1, x2, y2, w))- if fan_out:- d = unit(pts[-1][0] - pts[-2][0], pts[-1][1] - pts[-2][1]); l = left(d)- for sign, net in ((+1, pair.nnet), (-1, pair.pnet)):- pair.vias.append((pts[-1][0] + l[0] * sign * VIA_P / 2, pts[-1][1] + l[1] * sign * VIA_P / 2, net, VIA_D, VIA_H))- if pair.end is not None and pair.end[3]:- for (pt, net) in ((pair.end[0], pair.nnet), (pair.end[1], pair.pnet)):- pair.vias.append((pt[0], pt[1], net, VIP_D, VIP_H))- return pair---def pad_join(padpt, line, at_start, p):- """Join a trace end to a pad point with a 45 degree jog (pads are on the pair's axis line)."""- if at_start:- q = line[0]- dx, dy = q[0] - padpt[0], q[1] - padpt[1]- # go straight along the major axis, then 45 degrees- if abs(dx) < 1e-6:- return [padpt] + line- s = 1 if dy > 0 else -1- mid = (padpt[0], q[1] - s * abs(dx))- return [padpt, mid] + line- else:- q = line[-1]- dx, dy = padpt[0] - q[0], padpt[1] - q[1]- if abs(dx) < 1e-6:- return line + [padpt]- s = 1 if dy > 0 else -1- mid = (padpt[0], q[1] + s * abs(dx)) if abs(dy) > abs(dx) else None- # move diagonally first, then straight onto the pad- mid = (q[0] + dx, q[1] + s * abs(dx))- return line + [mid, padpt]---# ------------------------------------------------------------------------------------------ geometry plan-PX = ocp_pin_x()---def finger(pin):- return (F + PX[pin], PAD_TOP)---Y_FAN0 = 107.6 # fan diagonals start (clear of the key slots, which end at y = 108.0)-Y_VIA = 97.6 # transition via row-Y_SA0 = 96.9 # stage A start-LANE_PITCH = 2.8-WITHIN = 0.7 # P/R offset from the lane cell centre after the via row-# lane cell centres at the via row-XC = []-x = 21.0-for k in range(4):- row = []- for l in range(4):- row.append(x + l * LANE_PITCH)- XC.append(row)- x = row[-1] + LANE_PITCH + 1.5--# which side of its cell each pair sits on after the via row (P first, R second), per lane index in port-# b0: P west / R east, b1: R west / P east, b2: P west / R east, b3: R west / P east-SIDE = {0: (-1, +1), 1: (+1, -1), 2: (-1, +1), 3: (+1, -1)}-# layer of each bundle in band S and at the connector-LAYER_S = {0: "L1", 1: "L3", 2: "L5", 3: "L1"}-LAYER_C = {0: "L1", 1: "L3", 2: "L5", 3: "L5"}--# connector-end pair centres relative to the port centre, and how each pair ends-# P pairs land on B-row pads, R pairs pass the B row through a gap and land on A-row pads-CONN = {- # lane: (P centre, R gap x, R pad centre)- 0: (4.0, 4.0, 3.5),- 1: (2.5, 1.0, 2.0),- 2: (-2.0, -0.5, -2.5),- 3: (-3.5, -5.15, -4.0),-}-Y_BROW, Y_AROW = 7.37, 5.18-Y_STRIP = 6.27 # between the A and B rows, where R pairs turn to their A pads-Y_NB_END = 13.0 # stage B finished: pairs at their connector x-Y_NB = 19.5 # stage B starts (block swap, 45 degree)---def lane_pins(lane):- starts = [17, 20, 23, 26, 30, 33, 36, 39, 44, 47, 50, 53, 56, 59, 62, 65]- s = starts[lane]- return f"{s}", f"{s+1}"---# band M straight-section positions relative to the port centre (before the band-M2 shift back west)-# order W->E: R1, P1, P0, R0 | crossing-via gap | R3, P3, P2, R2-XEND = {("R", 1): 1.0, ("P", 1): 2.5, ("P", 0): 4.0, ("R", 0): 5.2,- ("R", 3): -5.15, ("P", 3): -3.5, ("P", 2): -2.0, ("R", 2): -0.5}-D_SWAP = 6.0-def xm_rel(kind, l):- x = XEND[(kind, l)]- return x - D_SWAP if l in (0, 1) else x + D_SWAP--S_EAST = 6.0 # band M corridors sit this far east of their connectors-Y_SW = 93.4 # lane 3 drops L1 -> L5 here-Y_M0 = 92.6 # band M diagonals start-Y_M2 = 36.0 # band M2: shift back west by S_EAST-Y_NB = Y_NB_END + D_SWAP---def plan_lane(k, l):- """(P pair, R pair) for port k, port-lane l, finger to OCuLink pad."""- lane = 4 * k + l- n, p = lane_pins(lane)- P = Pair(f"P{k}{l}", f"PETn{lane}", f"PETp{lane}", "PET")- R = Pair(f"R{k}{l}", f"PERn{lane}", f"PERp{lane}", "PER")- cx = CX[k]- xc_f = F + PX["B" + n] + 0.3- sP, sR = SIDE[l]- partner = {0: 1, 1: 0, 2: 3, 3: 2}[l]- for pr, kind, s_side, startlayer in ((P, "P", sP, "L1"), (R, "R", sR, "L6")):- xv = XC[k][l] + s_side * WITHIN- xa = XC[k][partner] + s_side * WITHIN- xm = cx + S_EAST + xm_rel(kind, l)- xn = cx + xm_rel(kind, l)- xe = cx + XEND[(kind, l)]- pin_n = ("B" if kind == "P" else "A") + n- pin_p = ("B" if kind == "P" else "A") + p- pr.start = (finger(pin_n), finger(pin_p), startlayer)- pc = pitch(startlayer)- y0 = PAD_TOP - 0.25 - (0.6 - pc) / 2- pr.add(xc_f, y0, startlayer)- pr.add(xc_f, Y_FAN0, startlayer)- pr.add(xv, Y_FAN0 - abs(xv - xc_f), startlayer)- lay = LAYER_S[l]- pr.add(xv, Y_VIA, startlayer)- if lay != startlayer:- pr.add(xv, Y_VIA, lay)- pr.add(xv, Y_SA0, lay)- pr.add(xa, Y_SA0 - abs(xa - xv), lay)- if l == 3: # lane 3 drops to L5 for stage B- pr.add(xa, Y_SW, lay)- pr.add(xa, Y_SW, "L5")- lay = "L5"- pr.add(xa, Y_M0, lay)- pr.add(xm, Y_M0 - abs(xm - xa), lay)- for (tx, ty) in TUNE.get(pr.name, []):- pr.add(tx, ty, lay)- pr.add(xm, Y_M2, lay)- pr.add(xn, Y_M2 - abs(xn - xm), lay)- pr.add(xn, Y_NB, lay)- pr.add(xe, Y_NB_END, lay)- connector_end(pr, kind, l, cx, lay)- return P, R---def connector_end(pr, kind, l, cx, lay):- xe = cx + XEND[(kind, l)]- if kind == "P":- pads = (cx + 5.25 - 0.5 * (b_pin(l, "n") - 1), cx + 5.25 - 0.5 * (b_pin(l, "p") - 1))- xc = (pads[0] + pads[1]) / 2- assert abs(xc - xe) < 1e-6, (pr.name, xc, xe)- pr.add(xe, Y_BROW + 0.45, lay)- pr.end = ((pads[0], Y_BROW), (pads[1], Y_BROW), lay, lay != "L1")- return- # R pairs: L1 (lane 0) drops to L3 first, then the B-row gap, the strip, the A pads- if lay == "L1":- yv = 11.6- pr.add(xe, yv, "L1")- pr.add(xe, yv, "L3")- lay = "L3"- xg = cx + CONN[l][1]- pr.add(xe, yv - 0.6, lay)- pr.add(xg, yv - 0.6 - abs(xg - xe), lay)- xg = cx + CONN[l][1]- xa = cx + CONN[l][2]- pads = (cx + 4.75 - 0.5 * (a_pin(l, "n") - 1), cx + 4.75 - 0.5 * (a_pin(l, "p") - 1))- assert abs((pads[0] + pads[1]) / 2 - xa) < 1e-6, (pr.name, pads, xa)- c = min(0.3, abs(xa - xg) / 2)- sgn = 1 if xa > xg else -1- pr.add(xg, Y_STRIP + c, lay)- pr.add(xg + sgn * c, Y_STRIP, lay)- pr.add(xa - sgn * c, Y_STRIP, lay)- pr.add(xa, Y_STRIP - c, lay)- pr.add(xa, Y_AROW + 0.42, lay)- pr.end = ((pads[0], Y_AROW), (pads[1], Y_AROW), lay, True)---def b_pin(l, pol):- return {0: {"p": 3, "n": 4}, 1: {"p": 6, "n": 7}, 2: {"p": 15, "n": 16}, 3: {"p": 18, "n": 19}}[l][pol]---def a_pin(l, pol):- return {0: {"p": 3, "n": 4}, 1: {"p": 6, "n": 7}, 2: {"p": 15, "n": 16}, 3: {"p": 18, "n": 19}}[l][pol]---TUNE = {} # pair name -> centreline points inserted in band M (filled by tune())--# band-M tuning windows and direction (away from same-layer neighbours)-TUNE_DIR = {("P", 0): -1, ("R", 0): +1, ("R", 1): -1, ("P", 1): +1,- ("R", 3): -1, ("P", 3): +1, ("P", 2): -1, ("R", 2): +1}-# y windows (top, bottom) so that P3 (east bumps) and P2 (west bumps) never bulge at the same height-TUNE_WIN = {("P", 0): (56.0, 38.0), ("R", 0): (56.0, 38.0), ("R", 1): (77.0, 58.0), ("P", 1): (77.0, 58.0),- ("R", 3): (77.0, 58.0), ("P", 3): (77.0, 67.6), ("P", 2): (67.0, 58.0), ("R", 2): (77.0, 58.0)}---def accordion(x0, ytop, side, amp, n, g=1.1, c=0.4):- """Centreline points of n accordion periods heading north from (x0, ytop) (y decreasing)."""- pts = []- y = ytop- x1 = x0 + side * amp- for i in range(n):- y1 = y; y2 = y - g; y3 = y - 2 * g- pts += [(x0, y1 + c), (x0 + side * c, y1), (x1 - side * c, y1), (x1, y1 - c),- (x1, y2 + c), (x1 - side * c, y2), (x0 + side * c, y2), (x0, y2 - c)]- y = y3- return pts---def bumps(x0, ytop, side, amp, n, flat=0.4, gap=0.5):- pts = []- y = ytop- for i in range(n):- pts += [(x0, y), (x0 + side * amp, y - amp), (x0 + side * amp, y - amp - flat), (x0, y - 2 * amp - flat)]- y = y - 2 * amp - flat - gap- return pts---def tune(iterations=4, tol_ps=0.3):- """Delay-match each port's TX lanes and RX lanes (by ps, including via barrels)."""- import delay- TUNE.clear()- for it in range(iterations):- pairs = [build(pp) for k in range(4) for l in range(4) for pp in plan_lane(k, l)]- byname = {pp.name: pp for pp in pairs}- changed = False- for k in range(4):- for kind in "PR":- grp = [byname[f"{kind}{k}{l}"] for l in range(4)]- d = {pp.name: delay.pair_delay(pp) for pp in grp}- tgt = max(d.values())- for pp in grp:- l = int(pp.name[2])- need_ps = tgt - d[pp.name]- if need_ps < tol_ps:- continue- lay = "L5" if l == 3 else LAYER_S[l]- need_mm = need_ps / delay.per_mm(lay)- cur = TUNE.get(pp.name)- xm = CX[k] + S_EAST + xm_rel(kind, l)- ytop, ybot = TUNE_WIN[(kind, l)]- side = TUNE_DIR[(kind, l)]- # current extra length of the existing tuning, then add the shortfall- have = getattr(tune, "extra", {}).get(pp.name, 0.0)- want = have + need_mm- if l == 0:- n = max(1, math.ceil(want / 4.6))- amp = want / (2 * n) + 0.234 # chamfer correction: 4 corners x 0.586c / (2 per period)- pts = accordion(xm, ytop, side, amp, n)- else:- n = max(1, math.ceil(want / 0.45))- amp = want / (0.828 * n)- pts = bumps(xm, ytop, side, amp, n)- if pts[-1][1] < ybot:- raise SystemExit(f"tuning for {pp.name} overruns its window: {pts[-1][1]:.1f} < {ybot}")- TUNE[pp.name] = pts- tune.extra = getattr(tune, "extra", {})- tune.extra[pp.name] = want- changed = True- if not changed:- break- return TUNE---def all_lanes():- out = []- for k in range(4):- for l in range(4):- P, R = plan_lane(k, l)- out += [build(P), build(R)]- return out---# ------------------------------------------------------------------------------------------ refclks and the band-M bus-Y_BUS = {"PERST1": 80.0, "REFCLK2": 81.5, "PERST2": 83.0, "REFCLK3": 84.5, "PERST3": 86.0, "REFCLK1": 87.5}-X_CORR_VIA = lambda k: CX[k] + S_EAST + 0.025 # in the R0-R3 gap of corridor k (band M)-G_CONT, G_PERST, G_REF = 12.35, 13.15, 14.4 # positions in gap (k-1, k), relative to cx[k-1] (band M)-Y_REF_TURN, Y_PERST_TURN = 12.3, 10.9-BUS_START_X = 16.0 # PERST bus lines start here (L1 -> L5 via)---def corner_path(pts_xy, layer, c=0.3):- """Expand a rectilinear list of points into chamfered octilinear nodes (45 degree chamfer c)."""- out = [pts_xy[0]]- for i in range(1, len(pts_xy) - 1):- a, b, cc = pts_xy[i - 1], pts_xy[i], pts_xy[i + 1]- d1 = unit(b[0] - a[0], b[1] - a[1]); d2 = unit(cc[0] - b[0], cc[1] - b[1])- if abs(d1[0] * d2[1] - d1[1] * d2[0]) < 1e-9:- out.append(b); continue- out.append((b[0] - d1[0] * c, b[1] - d1[1] * c))- out.append((b[0] + d2[0] * c, b[1] + d2[1] * c))- out.append(pts_xy[-1])- return out---def bus_route(k_from, k_to, y, first_layer="L5"):- """Waypoint list (x, y, layer) for a bus line that starts west of corridor k_from on L5 and exits into- gap (k_to - 1, k_to). Each corridor: L5 under its west half, a via in the R0-R3 gap, L3 over its east half;- each gap passed through: back to L5."""- nodes = []- for k in range(k_from, k_to):- xv = X_CORR_VIA(k)- nodes.append((xv, y, "L5"))- nodes.append((xv, y, "L3"))- if k < k_to - 1:- xg = CX[k] + G_CONT- nodes.append((xg, y, "L3"))- nodes.append((xg, y, "L5"))- return nodes---def refclk(k):- """REFCLK k from its gold fingers to OCuLink port k (A12 p / A13 n)."""- P = Pair(f"CLK{k}", f"REFCLKn{k}", f"REFCLKp{k}", "REFCLK")- if k in (0, 1):- pins = ("B14", "B15") if k == 0 else ("A14", "A15")- else:- pins = ("OCP_B11", "OCP_B12") if k == 2 else ("OCP_A11", "OCP_A12")- lay0 = "L1" if pins[0].startswith(("B", "OCP_B")) else "L6"- P.start = (finger(pins[0]), finger(pins[1]), lay0)- xc_f = F + PX[pins[0]] + 0.3- y0 = PAD_TOP - 0.25 - (0.6 - pitch(lay0)) / 2- P.add(xc_f, y0, lay0)- cx = CX[k]- if k == 0:- P.add(xc_f, 107.9, "L1"); P.add(19.1, 107.9 - (xc_f - 19.1), "L1"); P.add(19.1, 97.0, "L1")- P.add(19.1, 97.0, "L3"); P.add(19.1, 96.3, "L3"); P.add(3.6, 96.3 - 15.5, "L3")- for (x, y) in corner_path([(3.6, 80.8), (3.6, Y_REF_TURN), (cx - 0.5, Y_REF_TURN), (cx - 0.5, Y_REF_TURN - 0.8)], "L3")[1:]:- P.add(x, y, "L3")- lay = "L3"- else:- yb = Y_BUS[f"REFCLK{k}"]- if k == 1:- P.add(xc_f, 108.4, "L6"); P.add(17.6, 108.4 - (xc_f - 17.6), "L6"); P.add(17.6, 98.0, "L6")- P.add(17.6, 98.0, "L5"); xs = 17.6- elif k == 2:- P.add(xc_f, 107.9, "L1"); P.add(12.6, 107.9 - (xc_f - 12.6), "L1"); P.add(12.6, 106.5, "L1")- P.add(12.6, 106.5, "L5"); xs = 12.6- else:- P.add(xc_f, 107.9, "L6"); P.add(14.6, 107.9 - (14.6 - xc_f), "L6"); P.add(14.6, 106.5, "L6")- P.add(14.6, 106.5, "L5"); xs = 14.6- for (x, y) in corner_path([(xs, 106.0 if k > 1 else 97.6), (xs, yb), (X_CORR_VIA(0), yb)], "L5")[1:]:- P.add(x, y, "L5")- for nd in bus_route(0, k, yb)[1:]:- P.add(*nd)- g = CX[k - 1] + G_REF- lay = "L3"- pts = corner_path([(P.nodes[-1][0], yb), (g, yb), (g, Y_M2)], "L3")- for (x, y) in pts[1:]:- P.add(x, y, lay)- P.add(g - S_EAST, Y_M2 - S_EAST, lay)- for (x, y) in corner_path([(g - S_EAST, Y_M2 - S_EAST), (g - S_EAST, Y_REF_TURN), (cx - 0.5, Y_REF_TURN), (cx - 0.5, Y_REF_TURN - 0.8)], lay)[1:]:- P.add(x, y, lay)- # B-row gap B11/B14, strip, A12/A13- xg, xa = cx - 0.5, cx - 1.0- c = 0.25- P.add(xg, Y_STRIP + c, lay); P.add(xg - c, Y_STRIP, lay); P.add(xa + c, Y_STRIP, lay); P.add(xa, Y_STRIP - c, lay)- P.add(xa, Y_AROW + 0.42, lay)- P.end = ((cx + 4.75 - 0.5 * 12, Y_AROW), (cx + 4.75 - 0.5 * 11, Y_AROW), lay, True)- return build(P)---class Line:- """A single-ended route: nodes (x, y, layer); a layer change places one via."""- def __init__(self, name, net, w=0.15):- self.name, self.net, self.w = name, net, w- self.nodes, self.tracks, self.vias = [], [], []-- def add(self, x, y, layer):- self.nodes.append((round(x, 4), round(y, 4), layer)); return self-- def build(self):- for a, b in zip(self.nodes, self.nodes[1:]):- if a[2] != b[2]:- assert abs(a[0] - b[0]) < 1e-6 and abs(a[1] - b[1]) < 1e-6- self.vias.append((a[0], a[1], self.net, VIA_D, VIA_H))- elif math.hypot(b[0] - a[0], b[1] - a[1]) > 1e-6:- self.tracks.append((a[2], self.net, a[0], a[1], b[0], b[1], self.w))- return self---def perst_bus(k):- """PORTk_PERST# from its bus start via (fed from U4 through R1x by the maze router) to the B12 pad of port k."""- L = Line(f"PERST{k}", f"PORT{k}_PERST#")- y = Y_BUS[f"PERST{k}"]- cx = CX[k]- L.add(BUS_START_X, y, "L1"); L.add(BUS_START_X, y, "L5")- for nd in bus_route(0, k, y):- L.add(*nd)- g = CX[k - 1] + G_PERST- L.add(g, y, "L3"); L.add(g, y, "L1")- for (x, yy) in corner_path([(g, y), (g, Y_M2), (g - S_EAST, Y_M2 - S_EAST), (g - S_EAST, Y_PERST_TURN),- (cx - 0.25, Y_PERST_TURN), (cx - 0.25, Y_BROW)], "L1", c=0.3)[1:]:- L.add(x, yy, "L1")- return L.build()---def all_routes():- tune()- pairs = all_lanes() + [refclk(k) for k in range(4)]- lines = [perst_bus(k) for k in (1, 2, 3)]- return pairs, lines---if __name__ == "__main__":- pairs = all_lanes()- print(len(pairs), "pairs", sum(len(p.tracks) for p in pairs), "tracks", sum(len(p.vias) for p in pairs), "vias")