文件
phonerail/.claude/r2/b4_edge.py
T
zhou yang和Claude Opus 5 efcba30a0c R2 底板改 4 层(D15):规则 / 几何 / 背面摆位 / 4 层布线与铺铜,已下发
- 规则:铜层 2→4,差分 0.28/0.18,间距 0.15,过孔 0.3/0.2(rules_base)
- 几何:98×98 板框 + 4×M3,槽阵居中,L1 12V 大铜 + 指头(pcb_base)
- 摆位:五插槽留 L1,其余 133 件翻到 L4;背面镜像规则实测;Hub 放走廊 2、
  东移 2.5 mm;扇出圈 / 小件间距 1.5(去耦例外)/ 9 A 过孔阵禁区(place_base)
- 叠层按用户决定改为 L1 槽面 / L2 整层 +3V3 / L3 整层 GND / L4 器件面
- 布线:D± 七对按构造布通(USB3 掉头、USB1/2 北去掉头、USB4/5 东绕、
  J23 桥),+3V3 / GND / +12V 平面网每脚一孔,0.125 网格 + PathFinder
  开关(route_base、remote_route.ps1);剩 37 个网待手工补
- 铺铜:4 层重写,入口链自南向北切段 + 断面校核(pour_base);
  已下发布线 / 缝合孔 / 铜面轮廓,填铜未成功(待查)
- 工具:plot_base / plot_routes / shot_base,board_check 通孔两层都算

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_0168Bu6SEx3cXXCJLkdnE6zB
2026-09-10 21:02:10 +08:00

267 行
13 KiB
Python
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# -*- coding: utf-8 -*-
u"""R2 刀片 **B4-EDGE** 页:PCI-E x4 金手指 + PRSNT# 短接 + D± 退路座。
/c/Python314/python .claude/r2/b4_edge.py # 只算 + 本地自检
/c/Python314/python .claude/r2/b4_edge.py --push # 清页 + 重画(幂等)
M2 的最后一页(前四页见 b1_power / b2_ctrl / b3_phone / b3b_usb)。
## T4 已核(2026-09-08):针位表对着 PCI-E CEM 逐脚核过,方案文档 §5.2 有两处是错的
| 针 | 方案文档(凭记忆写的) | CEM 实际 | 后果 |
|---|---|---|---|
| **B3** | 「RSVD,拿来做 +5V」 | **+12V**(B1/B2/B3 三针都是) | 刀片误插进真 PC:主板把 12 V 灌进刀片的 +5V 轨,U5 TPS22918(6V abs max)与手机 VBUS 全灭 |
| B12 | 「RSVD」 | CEM 1.x = RSVD,2.0 起 = CLKREQ# | 不能当自由针用(真主板上有 3.3V 上拉、真卡上是开漏输出)|
⇒ **+5V 改挂 A19 / A32** —— 这两针在 CEM 1.x~5.0 每一版里都是 RSVD,
真主板上不接、真卡上也不接,是两个方向都安全的仅有选择。
代价:两针都在 A 排,图上是两个同名 `+5V` 端口(见下面 ALLOW_SPLIT)。
其余针位与 §5.2 一致:+12V=B1/B2/B3/A2/A3、+3V3=B8/A9/A10、SCL=B5(SMCLK)、
SDA=B6(SMDAT)、INT#=B11(WAKE#)、EFUSE_EN=A11(PERST#)、D±=B14/B15(PET0,两侧 B13/B16 天然夹地)、
PRSNT#=A1↔B31(x4 全长在位;B17 那个是 x1 用的,本板当地用)。
**VDAC 定在 A16**(PERp0):上下两针 A15 / A17 都是地,是全连接器夹得最干净的一个位置。
方案文档原来写「预留差分对 A16/A17」—— 那对差分在刀片上没有对端,留着也用不了,
让给需要屏蔽的模拟量更划算。
## 悬空的 6 根针(`b4-nc.json`)
`A5~A8`(JTAG TCK/TDI/TDO/TMS)、`B9`(JTAG TRST#)、**`B10`(+3.3Vaux)**。
⚠ B10 是必须悬空的那一根:真卡插进底板槽时,底板若在这针上给了 5V 会直接打穿它的 aux 域。
其余用不上的针(B12 CLKREQ# / B17 PRSNT#2(x1) / B30 PWRBRK# / 全部 lane 针)一律接地:
两个方向都无害(主板侧要么开漏、要么 AC 耦合),换来 39 根地针的回流面积。
## 版图:地母线甩到最左/最右,信号端口排在母线与引脚之间
抄 `b3b_usb` 的 J4(50 脚 BM24)那一套 —— 地是长横线一路拉到母线,信号是短横线接端口,
两者 y 不同所以永不交叉。竖线只在同一网络的几针之间用,且都躲在母线之外的空白区。
## ⚠ ALLOW_SPLIT 放宽了,补一层更硬的判据
连接器页天然靠同名端口/旗成网,不是靠导线:`GND`(三段梳子)、`+12V`(A/B 两排)、
`+3V3`(A/B 两排)、`+5V`(A19/A32 两针)、`USB_DP/DM`(金手指 + 退路座)都是分块的。
放宽 ALLOW_SPLIT 会让「漏连」这条检查失效,所以本脚本额外做一件事:
逐脚断言「这根针踩着的那段导线,声明的网络名 == PIN_NET 里写的」(`check_pin_nets`)。
少接一根、接错一根都会当场报出来。
"""
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 # noqa: E402
PAGE, NAME = 'c8f10dbd1286853b', 'B4-EDGE'
EDGE, WV4 = 'PCIE-X4-EDGE-64P', 'C225106'
# ── 版面常量 ─────────────────────────────────────────────────────────
J1 = (620, 470) # 金手指符号中心;A 排引脚 x=585,B 排 x=655
AX, BX = J1[0] - 35, J1[0] + 35
ABUS, BBUS = 340, 900 # 左/右地母线
APORT, BPORT = 490, 760 # 信号端口列
J2 = (1000, 700) # D± 退路座(1.25mm 4P),引脚朝左
def row(n):
u"""金手指第 n 号针的 y(A/B 两排同高,pitch 10)。"""
return J1[1] + 155 - 10 * (n - 1)
# ── 64 根针的网络分配(T4 已核,出处见表头)──────────────────────────
PIN_NET = {}
for _n, _net in [(1, 'PRSNT#'), (2, '+12V'), (3, '+12V'), (4, 'GND'),
(5, 'NC'), (6, 'NC'), (7, 'NC'), (8, 'NC'),
(9, '+3V3'), (10, '+3V3'), (11, 'EFUSE_EN'),
(12, 'GND'), (13, 'GND'), (14, 'GND'), (15, 'GND'),
(16, 'VDAC'), (17, 'GND'), (18, 'GND'), (19, '+5V'),
(32, '+5V')]:
PIN_NET['A%d' % _n] = _net
for _n in range(20, 32): # A20~A31 全部接地
PIN_NET['A%d' % _n] = 'GND'
for _n, _net in [(1, '+12V'), (2, '+12V'), (3, '+12V'), (4, 'GND'),
(5, 'SCL'), (6, 'SDA'), (7, 'GND'), (8, '+3V3'),
(9, 'NC'), (10, 'NC'), (11, 'INT#'),
(12, 'GND'), (13, 'GND'), (14, 'USB_DP'), (15, 'USB_DM'),
(31, 'PRSNT#'), (32, 'GND')]:
PIN_NET['B%d' % _n] = _net
for _n in range(16, 31): # B16~B30 全部接地
PIN_NET['B%d' % _n] = 'GND'
assert len(PIN_NET) == 64, len(PIN_NET)
NC = {'J1': [p for p, n in PIN_NET.items() if n == 'NC']}
assert sorted(NC['J1']) == sorted(['A5', 'A6', 'A7', 'A8', 'B9', 'B10']), NC['J1']
# 地母线要按「连续成段」切:中间隔着信号针的地方断开,每一段自带一面地旗。
AGND = [n for n in range(1, 33) if PIN_NET['A%d' % n] == 'GND']
BGND_MAIN = [n for n in range(1, 33)
if PIN_NET['B%d' % n] == 'GND' and n != 32]
# 同名端口/旗成网、导线上天然分块的网络(判据换成 check_pin_nets,见表头)
ALLOW_SPLIT = set(['GND', '+12V', '+3V3', '+5V', 'USB_DP', 'USB_DM'])
EDGE_REL = {}
for _n in range(1, 33):
EDGE_REL['A%d' % _n] = (-35, 155 - 10 * (_n - 1))
EDGE_REL['B%d' % _n] = (35, 155 - 10 * (_n - 1))
# A1251WV-S-4P rot 0 实测:1~4 自上而下、5/6 是两个固定脚,全部朝左出
WV4_REL = {'1': (-25, 15), '2': (-25, 5), '3': (-25, -5), '4': (-25, -15),
'5': (-25, 35), '6': (-25, -35)}
def placement():
return [['J1', EDGE, J1[0], J1[1], 0],
['J2', WV4, J2[0], J2[1], 0]]
def expect():
return {'J1': by_rel(J1[0], J1[1], EDGE_REL),
'J2': by_rel(J2[0], J2[1], WV4_REL)}
def build():
s = Sheet(PAGE, NAME)
# ── 1. 地:左右两把梳子 ──────────────────────────────────────────
for n in AGND:
s.w('GND', (AX, row(n)), (ABUS, row(n)))
s.w('GND', (ABUS, row(AGND[0])), (ABUS, 625)) # 母线往上甩出去挂旗
s.w('GND', (ABUS, row(AGND[0])), (ABUS, row(AGND[-1])))
s.gnd(ABUS, 625, 180)
for n in BGND_MAIN:
s.w('GND', (BX, row(n)), (BBUS, row(n)))
# ⚠ 母线在 B8(+3V3) 那一行故意断开:B9/B10 悬空、这一段是全页唯一
# 能让 +3V3 横穿到母线以东挂电源旗的通道(否则右半边被梳子封死)。
s.w('GND', (BBUS, row(4)), (BBUS, 625))
s.w('GND', (BBUS, row(4)), (BBUS, row(7)))
s.gnd(BBUS, 625, 180)
s.w('GND', (BBUS, row(12)), (BBUS, row(30)))
s.w('GND', (BBUS, row(30)), (BBUS, 310))
s.gnd(BBUS, 310, 0)
# B32 单独一根(下面就是 PRSNT# 的过桥,梳子伸不下去)
s.w('GND', (BX, row(32)), (BX, 290))
s.gnd(BX, 290, 0)
# ── 2. +12V:A2/A3 与 B1/B2/B3,各自并起来出一个端口 ─────────────
s.w('+12V', (AX, row(2)), (520, row(2)))
s.w('+12V', (AX, row(3)), (520, row(3)))
s.w('+12V', (520, 650), (520, row(3)))
s.w('+12V', (520, 650), (470, 650))
s.port('OUT', '+12V', 470, 650, 0)
for n in (1, 2, 3):
s.w('+12V', (BX, row(n)), (720, row(n)))
s.w('+12V', (720, row(1)), (720, row(3)))
s.w('+12V', (720, row(1)), (720, 660))
s.w('+12V', (720, 660), (790, 660))
s.port('OUT', '+12V', 790, 660, 180)
# ── 3. +3V3:A9/A10 一面旗,B8 横穿母线缺口到东边一面旗 ──────────
s.w('+3V3', (AX, row(9)), (450, row(9)))
s.w('+3V3', (AX, row(10)), (450, row(10)))
s.w('+3V3', (450, row(9)), (450, row(10)))
s.w('+3V3', (450, row(9)), (450, 570))
s.pwr('+3V3', 450, 570, 0)
s.w('+3V3', (BX, row(8)), (1000, row(8)))
s.w('+3V3', (1000, row(8)), (1000, 580))
s.pwr('+3V3', 1000, 580, 0)
# ── 4. +5V:A19 / A32 两根 RSVD 针,各一个同名端口 ───────────────
for n in (19, 32):
s.w('+5V', (AX, row(n)), (APORT, row(n)))
s.port('OUT', '+5V', APORT, row(n), 0)
# ── 5. A 排信号 ─────────────────────────────────────────────────
s.w('EFUSE_EN', (AX, row(11)), (APORT, row(11)))
s.port('OUT', 'EFUSE_EN', APORT, row(11), 0)
s.w('VDAC', (AX, row(16)), (APORT, row(16)))
s.port('OUT', 'VDAC', APORT, row(16), 0)
# ── 6. B 排信号 ─────────────────────────────────────────────────
for n, net, d in ((5, 'SCL', 'BI'), (6, 'SDA', 'BI'), (11, 'INT#', 'IN'),
(14, 'USB_DP', 'BI'), (15, 'USB_DM', 'BI')):
s.w(net, (BX, row(n)), (BPORT, row(n)))
s.port(d, net, BPORT, row(n), 180)
# ── 7. PRSNT#:A1 ↔ B31,从符号上方绕到下方(x4 全长在位)────────
s.w('PRSNT#', (AX, row(1)), (AX, 680))
s.w('PRSNT#', (AX, 680), (290, 680))
s.w('PRSNT#', (290, 680), (290, 245))
s.w('PRSNT#', (290, 245), (700, 245))
s.w('PRSNT#', (700, 245), (700, row(31)))
s.w('PRSNT#', (700, row(31)), (BX, row(31)))
# 这个端口在刀片上没有对端(PRSNT# 只在金手指内部短接),纯粹为了给网络起名 ——
# 不放的话 EasyEDA 会给它一个 $4N... 默认名,PCB 侧脚本按名字就认不出来。
# xpage.py 的 NAMING_ONLY 里已加了它。
s.w('PRSNT#', (620, 245), (620, 215))
s.port('OUT', 'PRSNT#', 620, 215, 0)
# ── 8. J2 D± 退路座(1=+5V 2=DP 3=DM 4/5/6=GND)──────────────────
jx, jy = J2
for dy, net, d in ((15, '+5V', 'OUT'), (5, 'USB_DP', 'BI'), (-5, 'USB_DM', 'BI')):
s.w(net, (jx - 25, jy + dy), (890, jy + dy))
s.port(d, net, 890, jy + dy, 0)
for dy in (35, -15, -35): # 4 = 地,5/6 = 固定脚
s.w('GND', (jx - 25, jy + dy), (1030, jy + dy))
s.w('GND', (1030, jy + 35), (1030, jy - 35))
s.w('GND', (1030, jy + 35), (1030, jy + 60))
s.gnd(1030, jy + 60, 180)
# ── 9. 文字 ─────────────────────────────────────────────────────
s.txt(45, 795, u'【B4-EDGE】PCI-E x4 金手指(只借机械与针位语义,不是 PCIe)+ D± 退路座', 14)
s.txt(45, 230, u'⚠ NOT PCIe —— 底板插槽旁必须丝印「PhoneRail blade only」。', 11)
s.txt(45, 212, u'T4 已核 CEM 引脚表:B1/B2/B3 都是 +12V(不是 RSVD),', 10)
s.txt(45, 194, u' 所以 +5V 挪到 A19 / A32 —— 每一版 CEM 里它俩都是 RSVD。', 10)
s.txt(45, 176, u'⚠ B10 = +3.3Vaux 必须悬空;B12/B17/B30 与全部 lane 针接地。', 10)
s.txt(45, 158, u'PRSNT# = A1 ↔ B31 短接(x4 全长在位判据)。', 10)
s.txt(45, 140, u'J2 = D± 退路座:PCB 上金手指到 J2 之间留割断位(D12)。', 10)
return s
def check_pin_nets(s):
u"""逐脚断言「针踩着的那段导线声明的网络」== PIN_NET。ALLOW_SPLIT 放宽之后靠这条兜底。"""
from schcheck import _on
errs = []
pins = dict((p, (AX if p[0] == 'A' else BX, row(int(p[1:]))))
for p in PIN_NET)
for p, xy in sorted(pins.items()):
want = PIN_NET[p]
got = sorted(set(seg[2] for seg in s.W if _on(xy, seg[0], seg[1])))
if want == 'NC':
if got:
errs.append(u'%s 应当悬空,却踩在 %s 上' % (p, got))
elif got != [want]:
errs.append(u'%s 应当是 %s,实际踩着 %s' % (p, want, got or u'(空)'))
return errs
def main():
push = '--push' in sys.argv
place = placement()
exp = expect()
s = build()
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) + check_pin_nets(s)
ngnd = len([1 for p, n in PIN_NET.items() if n == 'GND'])
print(u'金手指 64 针:地 %d / 悬空 %d / 有名信号 %d'
% (ngnd, len(NC['J1']), 64 - ngnd - len(NC['J1'])))
s.report(len(place), errs)
with io.open(os.path.join(HERE, 'b4-nc.json'), '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/b4-nc.json')
if __name__ == '__main__':
main()