# -*- coding: utf-8 -*- u"""R2 底板 **A5-SLOTS** 页:PCI-E x4 插槽阵(槽 1~3)。 /c/Python314/python .claude/r2/a5_slots.py # 只算 + 本地自检 /c/Python314/python .claude/r2/a5_slots.py --push # 清页 + 重画(幂等,只有主代理能跑) 配套的 `a5b_slots.py` 画槽 4/5 + 5 个 D± 退路座(J39~J43)。**为什么要拆两页见下面 §版面**。 本页是 `b4_edge.py`(刀片金手指)的**对端**:64 针的语义完全由 `b4_edge.PIN_NET` 定, 本脚本直接 `import` 它,不再抄一份,改针位表只能改那一处。 ## ⚠️ 唯一一处**故意不镜像**刀片:`PRSNT#` 刀片(`b4_edge.py`)把 `A1` 和 `B31` **短接在一起**,而且那个网络在刀片上没有别的对端 —— 它就是一段悬空的跳线(PCI-E CEM 里 add-in card 的标准做法)。 所以在底板(= 主板侧)上这两针**必须是两个不同的节点**,才构成在位检测回路: | | `A1`(PRSNT1#) | `B31`(PRSNT2#) | |---|---|---| | 真 PCIe 主板 | 接地 | 上拉 + 被读 | | **本页(底板)** | **`GND`** | **`PRSNT{n}#`** → A3 的 ESP32 输入(上拉也在 A3)| | 刀片 B4-EDGE | ←── 两针短接(插上才把 B31 拉到地)──→ | ⛔ **如果底板也照字面「镜像」、把 A1/B31 并成同一个 `PRSNT{n}#`**, 整条回路里就一个对地通路都没有:ESP32 靠上拉永远读到高, **插不插刀片都报「未插入」,而且 DRC/ERC/几何验证一道都不会响**。 `ROLE_OVERRIDE` 里就这一条,改它之前先把上面这张表看完。 ## 版面:5 个槽一页放不下,拆成 A5 + A5B(先算账再动手) 每槽 64 针两排同高、pitch 10 ⇒ 引脚跨度 **310**,两排引脚横跨 70。 按「地梳齿 + 母线 + 信号端口排在母线与引脚之间」(抄 `b4_edge`)压到最紧: ``` cx-170 ── 地旗体 ─┐ cx-160 ── A 排地母线(19 根梳齿从 cx-35 拉过来) cx-140 ── A 排信号端口列(标签朝内伸,符号也朝内,左边不占地) cx-80 / cx-65 ── A 排 +12V / +3V3 汇流竖线 cx-35 / cx+35 ── 引脚列 cx+80 ── B 排 +12V 汇流竖线 cx+140 ── B 排信号端口列 cx+160 ── B 排地母线 cx+175 ── B8 的 +3V3 东出通道(母线在这一行断开) cx+185 ── 旗体右边界 ``` ⇒ 每槽净宽 **355**、槽距 ≥ 365。一行 3 槽 = 1085 ≤ 页面可用 1125 ✅; 4 槽 = 1450 ❌;5 槽 = 1815 ❌。竖着排两行也不行:一行连旗带引脚约 400, 两行 820 > 802,而且下面那一行必然压进标题栏(`x>455 且 y<192`)。 **⇒ 结论:A5-SLOTS 放槽 1~3,新开 A5B-SLOTS 放槽 4~5 + 全部 5 个 D± 退路座。** 退路座整组放在 A5B 是因为 A5 上一寸空白都不剩了,而 A5B 空出一大片; 它们是同一件事(D12 的退路),放一起也好找。 ## 交叉点 0 是设计出来的,不是碰巧 母线在这种页面上是一堵墙,**任何信号都别想横穿**。所以: - A 排:所有信号支线止步于 `cx-140`、电源汇流止步于 `cx-65`,母线在 `cx-160` —— 母线可以从 A4 一路连到 A31 **不用断**,全 A 排只要一面地旗。 - B 排:母线只在 **B8 那一行断开**(B9/B10 本来就悬空), 这是 `+3V3` 横穿到母线以东挂旗的唯一通道,两段各自带一面地旗(抄 `b4_edge` §17.2)。 - `A1` 的地不往西走(会和 A2/A3 的 +12V 汇流竖线打架),改成从引脚**竖直往上引 25 挂旗**。 ## ALLOW_SPLIT 放宽了哪些,凭什么 `GND` `+12V` `+3V3` `+5V` 是靠同名旗/端口成网的,天然分块。 另外放行 **`INT#`**:五个槽的 B11 是 wire-OR 并联,**唯一一颗上拉在 A3**, 每槽出一个同名 OUT 端口是这种总线的标准画法(而且它还跨两页,本来就连不成一块)。 `PRSNT{n}#` / `SDA{n}` / `SCL{n}` / `VDAC{n}` / `EFUSE_EN{n}` **都不放行**,一块就是一块。 放宽之后靠 `check_pin_nets()` 兜底:**5 槽 × 64 针逐脚**断言 「这根针踩着的那段导线,声明的网络名 == 期望名」,少接一根、接错一根当场报出来。 """ import io import json import os import sys HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, HERE) from schcheck import Sheet, by_rel, _on # noqa: E402 from b4_edge import PIN_NET, EDGE_REL, WV4_REL # noqa: E402 PAGE, NAME = '2c73c5bf77f34053', 'A5-SLOTS' EDGE, WV4 = 'C19188869', 'C225106' # PCIE-64P11L(SOFNG)/ A1251WV-S-4P # ── ⚠ 2026-09-08 换料结掉 T8:SAMTEC PCIE-064-02-F-D-TH → SOFNG PCIE-64P11L ───── # 用户要 PC 标准的便宜料。要注意的是**两颗的脚号体系不一样**: # # 刀片 `PCIE-X4-EDGE-64P`(金手指,符号借自 Samtec) 脚号 = `A1`~`A32` / `B1`~`B32` # 底板 `PCIE-64P11L`(真插槽) 脚号 = `1`~`64`,**脚名**才是 A1~B32 # # 换算:`A(n) → 脚号 n`、`B(n) → 脚号 32+n`。 # 引脚**相对坐标两颗完全一致**(A 排 x=-35 / B 排 x=+35,y = 155-10*(n-1)), # 所以版图、母线、端口位置一个字都不用改 —— 只有 `expect()` / `nc_table()` 换脚号。 # ⚠ 它比金手指**多一根 `65` 脚 = 外壳/屏蔽,在 (0,+175)**(符号顶端正中),本页接 GND; # 不接的话 `verify_sch` 会报悬空引脚。 # ⛔ 它是**贴片立式**(原来那颗是直插):保持力只靠焊盘剪切。五片刀片各驮一块手机主板, # M4 布板时必须另加固定件(梳齿 / 压条),不能只靠插槽 —— 已写进页面文字与 eda-parts role。 SHIELD_PIN, SHIELD_REL = '65', (0, 175) def slot_pin(cem): u"""CEM 针名(`A1`~`A32` / `B1`~`B32`)→ `PCIE-64P11L` 符号的脚号(`'1'`~`'64'`)。""" i = int(cem[1:]) assert 1 <= i <= 32 and cem[0] in 'AB', cem return str(i if cem[0] == 'A' else 32 + i) SLOT_REL = dict((slot_pin(k), v) for k, v in EDGE_REL.items()) SLOT_REL[SHIELD_PIN] = SHIELD_REL assert len(SLOT_REL) == 65 and SLOT_REL['1'] == (-35, 155) and SLOT_REL['33'] == (35, 155) assert SLOT_REL['64'] == (35, -155), SLOT_REL['64'] SLOT_REF = {1: 'J34', 2: 'J35', 3: 'J36', 4: 'J37', 5: 'J38'} WV_REF = {1: 'J39', 2: 'J40', 3: 'J41', 4: 'J42', 5: 'J43'} # ── 本页的槽位(A5B 画槽 4/5,坐标在 a5b_slots.py)──────────────────── SLOT_XY = {1: (200, 470), 2: (565, 470), 3: (930, 470)} # 槽距 365 # ── 针 → 网络:刀片的 PIN_NET 加槽号后缀,只有 A1 例外(见表头)────── ROLE_OVERRIDE = {'A1': 'GND'} # ⚠ 底板 = 主板侧,A1 接地;改前先读表头 GLOBAL = set(['GND', '+12V', '+3V3', '+5V', 'INT#', 'NC']) # 不加槽号的 def base_net(pin, n): u"""刀片侧针名 → 底板侧网络名(n = 槽号)。""" role = ROLE_OVERRIDE.get(pin, PIN_NET[pin]) if role in GLOBAL: return role if role == 'PRSNT#': return 'PRSNT%d#' % n if role.startswith('USB_'): # USB_DP / USB_DM return 'USB%d_%s' % (n, role[4:]) return '%s%d' % (role, n) # SCL / SDA / VDAC / EFUSE_EN # 逐脚自检:镜像关系必须是「除了 A1 全都对得上」 assert len(PIN_NET) == 64, len(PIN_NET) assert PIN_NET['A1'] == 'PRSNT#' and PIN_NET['B31'] == 'PRSNT#', u'刀片侧 PRSNT# 针位变了' assert base_net('A1', 1) == 'GND' and base_net('B31', 1) == 'PRSNT1#' assert [p for p in PIN_NET if PIN_NET[p] != base_net(p, 1).rstrip('1') and base_net(p, 1) not in ('USB1_DP', 'USB1_DM', 'PRSNT1#')] == ['A1'], \ u'除了 A1 之外还有针没有镜像刀片 —— 要么 PIN_NET 变了,要么 base_net 写漏了' NCPINS = sorted([p for p, r in PIN_NET.items() if r == 'NC']) assert NCPINS == sorted(['A5', 'A6', 'A7', 'A8', 'B9', 'B10']), NCPINS AGND = [n for n in range(1, 33) if base_net('A%d' % n, 1) == 'GND'] BGND = [n for n in range(1, 33) if base_net('B%d' % n, 1) == 'GND'] assert AGND[0] == 1 and len(AGND) == 20, AGND # A1 也归了地 assert len(BGND) == 20, BGND BGND_TOP = [n for n in BGND if n <= 7] # B4 / B7,母线上段 BGND_BOT = [n for n in BGND if n >= 12] # B12 以下,母线下段 assert BGND_TOP == [4, 7] and BGND_BOT[0] == 12 and BGND_BOT[-1] == 32 # A 排信号(脚号, 端口方向);A1 已经归地,不在这里 A_SIG = [(11, 'IN'), (16, 'IN'), (19, 'IN'), (32, 'IN')] B_SIG = [(5, 'BI'), (6, 'BI'), (11, 'OUT'), (14, 'BI'), (15, 'BI'), (31, 'OUT')] # D± 退路座(A1251WV-S-4P):逐槽镜像刀片 J2 的 1=+5V 2=DP 3=DM 4/5/6=GND WV_NET = {'1': '+5V', '2': 'USB%d_DP', '3': 'USB%d_DM', '4': 'GND', '5': 'GND', '6': 'GND'} # ── 一个槽的版图 ────────────────────────────────────────────────────── def geom(cx, cy): return dict(AX=cx - 35, BX=cx + 35, ABUS=cx - 160, BBUS=cx + 160, APORT=cx - 140, BPORT=cx + 140, A12=cx - 80, A33=cx - 65, B12=cx + 80, B33=cx + 175) def rowy(cy, n): u"""第 n 号针的 y(A/B 两排同高,pitch 10,A1/B1 在 cy+155)。""" return cy + 155 - 10 * (n - 1) def draw_slot(s, n, cx, cy): g = geom(cx, cy) AX, BX = g['AX'], g['BX'] def r(k): return rowy(cy, k) def net(pin): return base_net(pin, n) # ── A 排:地 ──────────────────────────────────────────────────── # A1 单独往上挂旗:它往西走会和 A2/A3 的 +12V 汇流竖线在 (A12, r1) 撞成交叉 s.w('GND', (AX, r(1)), (AX, r(1) + 25)) s.gnd(AX, r(1) + 25, 180) # 65 脚 = 连接器外壳/屏蔽(符号顶端正中 (0,+175)),接地。金手指那颗没有这根针。 # ⚠ 不要往**上**引再挂一面地旗:cy+195 那一带是页眉文字的地盘(A5B 上就撞了)。 # 往左 35 正好落在 A1 那根地短线 (AX, r1)~(AX, r1+25) 的中段上 —— T 型相连, # 不新增旗、不占用连接器上方的任何空间。 s.w('GND', (cx, cy + 175), (AX, cy + 175)) for k in AGND[1:]: s.w('GND', (AX, r(k)), (g['ABUS'], r(k))) # 母线连续(A4 → A31):信号止步于 cx-140、电源止步于 cx-65,没人横穿 s.w('GND', (g['ABUS'], r(4)), (g['ABUS'], r(31) - 20)) s.gnd(g['ABUS'], r(31) - 20, 0) # ── A 排:+12V(A2/A3)与 +3V3(A9/A10),各自一面 Power 旗 ────── for k in (2, 3): s.w('+12V', (AX, r(k)), (g['A12'], r(k))) s.w('+12V', (g['A12'], r(3)), (g['A12'], r(1) + 10)) s.pwr('+12V', g['A12'], r(1) + 10, 0) for k in (9, 10): s.w('+3V3', (AX, r(k)), (g['A33'], r(k))) # 往上挂到 A5~A8 那段悬空针留出来的空白里(旗体上沿离 A4 的梳齿还有 10) s.w('+3V3', (g['A33'], r(10)), (g['A33'], r(9) + 20)) s.pwr('+3V3', g['A33'], r(9) + 20, 0) # ── A 排:信号端口(+5V ×2 / EFUSE_ENn / VDACn)────────────────── for k, d in A_SIG: nm = net('A%d' % k) s.w(nm, (AX, r(k)), (g['APORT'], r(k))) s.port(d, nm, g['APORT'], r(k), 180) # IN + rot180 = 符号/标签朝内 # ── B 排:地(母线在 B8 那一行断开,给 +3V3 让出东出通道)──────── for k in BGND_TOP: s.w('GND', (BX, r(k)), (g['BBUS'], r(k))) s.w('GND', (g['BBUS'], r(4) + 20), (g['BBUS'], r(7))) s.gnd(g['BBUS'], r(4) + 20, 180) for k in BGND_BOT: s.w('GND', (BX, r(k)), (g['BBUS'], r(k))) s.w('GND', (g['BBUS'], r(12)), (g['BBUS'], r(32) - 20)) s.gnd(g['BBUS'], r(32) - 20, 0) # ── B 排:+12V(B1/B2/B3)与 +3V3(B8,穿母线缺口)─────────────── for k in (1, 2, 3): s.w('+12V', (BX, r(k)), (g['B12'], r(k))) s.w('+12V', (g['B12'], r(3)), (g['B12'], r(1) + 20)) s.pwr('+12V', g['B12'], r(1) + 20, 0) s.w('+3V3', (BX, r(8)), (g['B33'], r(8))) s.w('+3V3', (g['B33'], r(8)), (g['B33'], r(8) - 20)) s.pwr('+3V3', g['B33'], r(8) - 20, 180) # ── B 排:信号端口(SCLn/SDAn/INT#/USBn_DP/DM/PRSNTn#)─────────── for k, d in B_SIG: nm = net('B%d' % k) s.w(nm, (BX, r(k)), (g['BPORT'], r(k))) s.port(d, nm, g['BPORT'], r(k), 180) # BI/OUT + rot180 = 标签朝内 def draw_wv(s, n, jx, jy): u"""D± 退路座(D12):1=+5V 2=USBn_DP 3=USBn_DM 4/5/6=GND,引脚全部朝左。""" px, gx, ptx = jx - 25, jx - 45, jx - 75 for dy, nm, d, rot in ((15, '+5V', 'IN', 0), (5, 'USB%d_DP' % n, 'BI', 180), (-5, 'USB%d_DM' % n, 'BI', 180)): s.w(nm, (px, jy + dy), (ptx, jy + dy)) s.port(d, nm, ptx, jy + dy, rot) # 标签一律朝左(背离座子) # 地分两段:脚 5(固定脚,在最上)往上挂,脚 4/6 往下挂 —— # 中间三行是信号,一根竖着的地母线穿过去就是三个交叉点 s.w('GND', (px, jy + 35), (gx, jy + 35)) s.w('GND', (gx, jy + 35), (gx, jy + 55)) s.gnd(gx, jy + 55, 180) s.w('GND', (px, jy - 15), (gx, jy - 15)) s.w('GND', (px, jy - 35), (gx, jy - 35)) s.w('GND', (gx, jy - 15), (gx, jy - 55)) s.gnd(gx, jy - 55, 0) # ── 组装 ────────────────────────────────────────────────────────────── def placement(slots, wvs): return ([[SLOT_REF[n], EDGE, cx, cy, 0] for n, (cx, cy) in sorted(slots.items())] + [[WV_REF[n], WV4, jx, jy, 0] for n, (jx, jy) in sorted(wvs.items())]) def expect(slots, wvs): out = {} for n, (cx, cy) in slots.items(): out[SLOT_REF[n]] = by_rel(cx, cy, SLOT_REL) # 脚号 1~64 + 65 屏蔽 for n, (jx, jy) in wvs.items(): out[WV_REF[n]] = by_rel(jx, jy, WV4_REL) return out def nc_table(slots): return dict((SLOT_REF[n], [slot_pin(p) for p in NCPINS]) for n in slots) def allow_split(slots, wvs): u"""只放行「靠同名旗/端口成网」的:电源地、跨页 wire-OR 的 INT#、以及本页同时有槽和退路座的 D±。""" out = set(['GND', '+12V', '+3V3', '+5V', 'INT#']) for n in wvs: if n in slots: # 槽和它的退路座在同一页 = 天然两块 out.add('USB%d_DP' % n) out.add('USB%d_DM' % n) return out def build(slots, wvs, texts, page, name): s = Sheet(page, name) for n, (cx, cy) in sorted(slots.items()): draw_slot(s, n, cx, cy) for n, (jx, jy) in sorted(wvs.items()): draw_wv(s, n, jx, jy) for t in texts: s.txt(t[0], t[1], t[2], t[3]) return s # ── 逐脚对账(ALLOW_SPLIT 放宽之后的硬判据)────────────────────────── def check_pin_nets(s, slots, wvs): u"""5 槽 × 64 针 + 退路座 6 针:断言每根针踩着的导线声明的网络名 == 期望名。""" errs, seen = [], 0 for n, (cx, cy) in sorted(slots.items()): g = geom(cx, cy) for p in sorted(PIN_NET): x = g['AX'] if p[0] == 'A' else g['BX'] xy = (x, rowy(cy, int(p[1:]))) want = base_net(p, n) got = sorted(set(w[2] for w in s.W if _on(xy, w[0], w[1]))) seen += 1 if want == 'NC': if got: errs.append(u'%s(槽%d).%s 应当悬空,却踩在 %s 上' % (SLOT_REF[n], n, p, got)) elif got != [want]: errs.append(u'%s(槽%d).%s 应当是 %s,实际踩着 %s' % (SLOT_REF[n], n, p, want, got or u'(空)')) for n, (jx, jy) in sorted(wvs.items()): for p, dy in (('1', 15), ('2', 5), ('3', -5), ('4', -15), ('5', 35), ('6', -35)): xy = (jx - 25, jy + dy) want = WV_NET[p] want = want % n if '%d' in want else want got = sorted(set(w[2] for w in s.W if _on(xy, w[0], w[1]))) seen += 1 if got != [want]: errs.append(u'%s(槽%d).%s 应当是 %s,实际踩着 %s' % (WV_REF[n], n, p, want, got or u'(空)')) return errs, seen def text_clash(s, exp): u"""文字压在导线 / 引脚 / 旗端口上(抄 a1_input.py,本页文字全靠这条守住空白带)。""" errs, boxes, pts = [], [], [] for x, y, txt, size in s.TX: cjk = sum(1 for ch in txt if ord(ch) > 0x2E7F) w = size * (cjk + 0.5 * (len(txt) - cjk)) boxes.append((x, y - size * 0.8, x + w, y + size * 0.8, txt)) for d, ps in exp.items(): for num, xy in ps.items(): pts.append((xy[0], xy[1], u'引脚 %s.%s' % (d, num))) for it in s.F + s.PT: pts.append((it[2], it[3], u'旗/端口 %s' % it[1])) for x0, y0, x1, y1, txt in boxes: for px, py, what in pts: if x0 <= px <= x1 and y0 <= py <= y1: errs.append(u'文字压住 %s @(%d,%d): %s' % (what, px, py, txt[:26])) for a, b, nt in s.W: if a[1] == b[1]: if y0 <= a[1] <= y1 and min(a[0], b[0]) <= x1 and max(a[0], b[0]) >= x0: errs.append(u'文字压住导线 %s y=%d: %s' % (nt, a[1], txt[:26])) elif x0 <= a[0] <= x1 and min(a[1], b[1]) <= y1 and max(a[1], b[1]) >= y0: errs.append(u'文字压住导线 %s x=%d: %s' % (nt, a[0], txt[:26])) return sorted(set(errs)) def flag_clash(s, gx=20, gy=10): u"""两面旗/端口的符号叠在一起 —— crossings() 查不到(旗不是导线)。""" errs, items = [], [(it[1], it[2], it[3]) for it in s.F + s.PT] for i in range(len(items)): for j in range(i + 1, len(items)): (n1, x1, y1), (n2, x2, y2) = items[i], items[j] if abs(x1 - x2) < gx and abs(y1 - y2) < gy: errs.append(u'旗/端口 %s @(%d,%d) 与 %s @(%d,%d) 挨太近' % (n1, x1, y1, n2, x2, y2)) return sorted(set(errs)) def port_extent(s, slots, wvs, pad=20): u"""端口的符号+标签往哪边伸多远(契约 §7 第 8 条:约 70~100 个单位,flag_clash 够不着)。 IN + rot0 / BI/OUT + rot180 → 标签在**左**;IN + rot180 / BI/OUT + rot0 → 在**右**(skill §8b)。 这里只查一件事:某个端口的标签有没有伸进**别的槽**的引脚列。 """ errs = [] cols = [] for n, (cx, cy) in slots.items(): cols.append((SLOT_REF[n], cx - 35, cx + 35, rowy(cy, 32), rowy(cy, 1))) for n, (jx, jy) in wvs.items(): cols.append((WV_REF[n], jx - 25, jx + 25, jy - 35, jy + 35)) for d, nm, x, y, rot in s.PT: w = 40 + 6.7 * len(nm) # skill §8b 的保守估算 left = (d == 'IN') == (rot == 0) # IN+rot0 / BI·OUT+rot180 → 标签在左 x0, x1 = (x - w, x) if left else (x, x + w) # 归属:先按 y 落在谁的引脚带里,再按 x 取最近的一件 cand = [c for c in cols if c[3] <= y <= c[4]] or cols own = min(cand, key=lambda c: abs(x - (c[1] + c[2]) / 2.0))[0] for ref, cx0, cx1, cy0, cy1 in cols: if ref == own: continue # 自己那一件:标签朝内压在自己的引出线上是有意的 if cy0 - 40 <= y <= cy1 + 40 and x0 <= cx1 + pad and x1 >= cx0 - pad: errs.append(u'端口 %s @(%d,%d) 的标签(%d~%d)伸进了 %s 的地盘' % (nm, x, y, x0, x1, ref)) return sorted(set(errs)) # ── 页面文字 ────────────────────────────────────────────────────────── TEXTS = [ (45, 800, u'【A5-SLOTS】5 个 PCI-E x4 插槽之 槽 1~3(槽 4/5 与 5 个 D± 退路座在 A5B-SLOTS:一页排不下 5 槽,算账见脚本表头)', 13), (45, 778, u'⚠ 插槽旁必须丝印「NOT PCIe — PhoneRail blade only」(方案 §5.2)—— 本页只是写在这里,PCB 阶段落地', 11), (45, 758, u'针位逐脚镜像刀片 B4-EDGE 的 PIN_NET(T4 已对 CEM 核过):+12V=A2/A3/B1/B2/B3,+3V3=A9/A10/B8,+5V=A19/A32(每版 CEM 都是 RSVD)', 10), (45, 740, u'SCL=B5 / SDA=B6 / INT#=B11(五槽 wire-OR,唯一一颗上拉在 A3)/ EFUSE_EN=A11 / VDAC=A16 / D±=B14·B15(两侧 B13·B16 天然夹地)', 10), (45, 722, u'⚠ B10(+3.3Vaux)必须悬空:真显卡误插进本槽时,底板若在这针上给电会直接打穿它的 aux 域。A5~A8、B9 同样悬空,每槽共 6 针', 10), (45, 704, u'⚠ B3 是 +12V 不是 RSVD、B12 是 CLKREQ# 不是 RSVD(方案文档旧描述有误,以 b4_edge.py 的 PIN_NET 为准);B12/B17/B30 与全部 lane 针接地', 10), (45, 686, u'✅ T8 已结(2026-09-08 换料):J34~J38 = SOFNG PCIE-64P11L(C19188869),PC 标准 x4 64PIN。⛔ 贴片立式,保持力只靠焊盘 —— 刀片要另加固定件', 10), (45, 250, u'⚠ PRSNT 是全页唯一**不镜像**刀片的地方:底板 A1 = GND、B31 = PRSNTn#(上拉与读取都在 A3)。', 11), (45, 232, u' 刀片上 A1↔B31 是一段悬空短接线,插上才把 B31 拉到地 —— 底板若也把两针并成一个网,回路里没有对地通路,ESP32 永远读「未插入」', 10), (45, 214, u'每槽 64 针 = 20 地 + 6 悬空 + 18 有名信号。地针走「梳齿 + 母线 + 每段一面地旗」;A 排母线(cx−160)从 A4 连到 A31 不用断,', 10), (45, 196, u' B 排母线(cx+160)只在 B8 那一行断开,让 +3V3 横穿到母线以东挂旗(抄 B4-EDGE §17.2);所有信号止步于 cx±140 ⇒ 交叉点 0', 10), ] def main(slots=None, wvs=None, texts=None, page=PAGE, name=NAME): slots = SLOT_XY if slots is None else slots wvs = {} if wvs is None else wvs texts = TEXTS if texts is None else texts push = '--push' in sys.argv place = placement(slots, wvs) exp = expect(slots, wvs) nc = nc_table(slots) s = build(slots, wvs, texts, page, name) s.assert_grid() pins = dict((d, dict((k, tuple(v)) for k, v in want.items())) for d, want in exp.items()) errs = s.netcheck(pins, nc, allow_split(slots, wvs)) pin_errs, npin = check_pin_nets(s, slots, wvs) errs += pin_errs + text_clash(s, exp) + flag_clash(s) + port_extent(s, slots, wvs) nnc = sum(len(v) for v in nc.values()) print(u'%s:%d 个插槽 × 64 针 + %d 个 D± 退路座 × 6 针 = 逐脚对账 %d 根' % (name, len(slots), len(wvs), npin)) print(u' 每槽 地 %d / 悬空 %d / 有名信号 %d;本页悬空针合计 %d' % (len(AGND) + len(BGND), len(NCPINS), 64 - len(AGND) - len(BGND) - len(NCPINS), nnc)) s.report(len(place), errs) if page is None: print(u'⚠ 本页还没有 uuid(等主代理建页后填 PAGE),--push 不可用') return with io.open(os.path.join(HERE, '%s-nc.json' % name.lower()), 'w', encoding='utf-8') as f: f.write(json.dumps(nc, ensure_ascii=False, indent=1)) if not push: return s.clear() s.place(place, exp) s.push_geometry() print(u'接着跑 verify_sch.py --nets --nc .claude/r2/%s-nc.json' % name.lower()) if __name__ == '__main__': main()