
我要先坦白一件事PyCircuit 这个项目最初的起点真的不是为了做一个面向大众的开源硬件开发框架。它的动机特别小小到说出来自己都嫌丢人——我就是想在画转接板时省掉在EDA里反复拖拽连线的十五分钟。结果大家都看见了为了那瓶醋我把这盘饺子重新包了一遍而且一包就是六个大版本。PyCircuit 6 是我最近整理完的大版本它做的事总结起来就一句你可以用Python代码描述一块电路板然后让工具自动产出原理图、PCB制造文件、物料清单连着引脚映射的头文件和硬件回归测试脚本一起丢给你。这篇复盘就围绕这盘饺子展开说说醋是什么、饺子皮怎么擀、馅儿怎么调以及包到第六版时踩过的几个真坑。1. 那瓶醋一个本该十五分钟做完的转接板1.1 需求从哪来去年我在给一块FPGA板子做原型验证。板子本身没问题但每验证一种新的引脚分配方案就要换一块转接板。FPGA的好处是GPIO能任意映射坏处是映射方案一变物理连线的意义就全变了。当时手头这个转接板特别不起眼一片电平转换芯片几个退耦电容两排排针外加几个测试点。按说放任何EDA里熟练工十分钟十五分钟就能画完。但我那段时间处在一种上午定方案、下午改方案、晚上再改回来的状态里一天下来光改连线就要折腾四五个来回。每改一次不是拉线拉到头大而是改完以后还要核对网络表、跑一遍DRC、重新导BOM整套流程四十多分钟起步。一周累计下去我发现时间全花在了确认设计没被我自己改坏上面而不是花在思考这个设计到底好不好。这个场景就是那瓶醋。它太具体了具体到我没法理直气壮地找一个现成工具来买。它不是一份需求文档而是一种爆发在重复劳动里的烦躁——而这种烦躁恰好是所有工具作者共同的起点。1.2 真正让我动手的是一个别扭光说嫌麻烦其实还差点火候。让我真正决定要自己折腾的是一种职业上的别扭。我写软件的时候代码就是设计本身。我可以提交一个PR别人能diff能在注释里讨论为什么这么写。可一转到硬件设计意图散落在一张图片、一堆坐标和一堆鼠标点击记录里。你想让同行审一下我为什么把这两个去耦电容放在这里他只能看着你的原理图猜。我想要的是那种硬件也有源码的感觉元件放哪里、线连到哪里、为什么这样连都能用文本记录下来。板子的图纸也好、Gerber也好都应该是从这份设计描述编译出来的产物而不是设计的起点。这个念头一旦冒出来就再也回不去了。1.3 触发点先拿JSON试水动手第一步特别土。我没打算写框架就写了一个Python脚本读一个JSON配置把里面列的电源网络、信号网络、接口引线整理成一份KiCad能导入的netlist。第一次跑通的时候说实话我盯着终端看了好几秒没缓过神。那感觉不是省了几分钟的爽而是我突然可以像开会讨论架构一样讨论一块电路板的新鲜感。我在JSON里加一个字段重新生成再打开EDA连线已经按新定义排好了。从那一刻起我就知道这瓶醋已经不止值一盘饺子了。它值好几屉。2. 盘饺子之前为什么现有Python方案不够吃2.1 当时能选的东西我基本都试了一遍决定要搞用代码描述硬件之后我首先想到的是看看开源社区已经有什么。毕竟自己造轮子之前至少得先确认轮子真的不合脚。那一圈试下来结论有点微妙。SKiDL老牌Python库用代码描述电路拓扑的思路和我想的很接近。但它出生的年代早底层抽象偏网表流它帮你生成的东西更接近给EDA导入的连接关系而不是一份人愿意直接阅读的原理图。它生成的图排版基本靠运气你想让一块电源模块和一块主控模块在纸上间隔拉开一点它根本不管。做原型验证可以做团队项目的交付物很吃力。schemdraw画原理图是好手出图漂亮教科书级。但它定位就是画示意图它根本不知道什么是电源域不知道两个Pin接在一起会不会冲突。你让它画一个桥式整流电路没问题让它告诉你这个电容接错管脚了它一脸无辜。各家EDA的脚本接口我也没少折腾。JLCEDA有脚本能力KiCad有Python插件框架但它们的本质是操作一个设计文件。你要生成点什么还得先创建一块板子、创建封装、连接网络。它们能自动化但设计意图还是得靠人一点点喂进去并没有变成一个可讨论、可复用的东西。2.2 共同的问题它们都把图当本体把逻辑当配菜这几种方案有个共同盲区原理图是它们的核心数据和唯一交付物。可我一直在想硬件开发里真正该被当作本体的是那张图吗不是。图是给人看的解释而真正的本体是连接关系和约束。连接关系是理性的可以被机器检查图是感性的是给人做心理按摩的。SKiDL把连接关系做了但没做好人的体验schemdraw把人的体验做了但没做过连接关系的检查。它们都假设画出一张像样的图是目的而我觉得图只是附带福利。这个认知差异看起来不大但会彻底影响架构如果你把图当本体那么一切工作都围绕排版、坐标、图框、标号、页间连接符来转如果你把连接与约束当本体那么一切能力都围绕检查、推导、复用、验证来转出图只是其中一个不算核心的后端。我的选择是后者。2.3 自研的边界只做编译前端不做EDA确定了方向也就有了边界意识。我不会傻到去做一个完整EDA那是无底洞。PCB布线、阻抗计算、仿真求解、元件库维护任何一项都够一个团队吃十年。我能做、也值得做的恰恰是中间这一层把Python里的硬件描述编译成结构化数据然后让不同的后端渲染器把这些数据变成原理图、网表、BOM、固件头文件。这套模式和我熟悉的编译器太像了前端解析代码、生成中间表示后端各自消费中间表示。一个IRN个后端各不干涉。这个定位后来救了我很多次。因为边界清楚所以我每个版本都在提醒自己渲染器与核心逻辑解耦凡是跟出图相关的功能都扔到后端去凡是跟电路对不对相关的功能都留在核心。谁也别污染谁。3. PyCircuit 6 的核心抽象把电路当作带约束的图3.1 三个类撑起整个建模如果你要用代码描述硬件最怕的是抽象不完整。我试过好几版最后沉淀下来只需要三个东西元件、管脚、网络。元件描述板上有什么它携带符号、封装、数据手册引用可能还有每一个管脚的行为描述。管脚描述电从哪进、从哪出它有方向输入、输出、双向、电源、接地有电气类型数字、模拟、功率、地。网络描述谁跟谁接在一起它把一组管脚连成一个电气等价点。定义逻辑上很简单但细节决定成败。比如电源管脚的命名很多芯片的数据手册上同一个地一会儿叫GND一会儿叫PGND一会儿又叫AGND封装库里它可能叫EP也就是exposed pad。如果你不在一开始就把裸露焊盘也是接地Net这件事建模后面DRC会把你折腾到怀疑人生。我的第一版实现就吃了这个亏只建了Net没建Pin的电气类型结果两个输出Pin接到同一个Net上生成器一声不吭板子打出来烙铁都焊完了上电才看到问题。所以后来我把角色和名字彻底分开名字可以随便叫角色必须唯一。3.2 层级复用把子电路当成函数来调用第二个让PyCircuit真正变得好用的设计是层级复用。硬件工程师都有体会一个系统里反复出现的往往不是大模块而是电源去耦加状态LED加串联电阻加去耦电容这种小组合。在传统EDA里你得复制粘贴然后祈祷后来不要改其中某个封装。在PyCircuit里这种组合就是一个普通函数。比如定义一个为某个电源域加一份去耦并留一个测试点的子电路def power_clean(domain, refdes_prefix): net_3v3 Net(domain) cap1 Component(cap_100n, C0603, refdesf{refdes_prefix}1) cap2 Component(cap_1u, C0603, refdesf{refdes_prefix}2) tp Component(testpoint, TP_1mm, refdesf{refdes_prefix}3) net_3v3.connect(cap1.pin(1), cap2.pin(1), tp.pin(1)) net_gnd Net(GND) net_gnd.connect(cap1.pin(2), cap2.pin(2), tp.pin(2)) return net_3v3, net_gnd然后主板上有几路独立的3.3V域就调用几次。参数化能力意味着同一个函数能衍生出不同容值的组合、不同位数的总线。这个感觉太像写代码了而且它保证了每一路电源域的做法完全一致不会出现手工画板时这块忘了加100nF电容的经典事故。3.3 把DRC前移连接检查应该在代码编写时传统硬件的DRC是在生成PCB之后才跑的晚到不能再晚。PyCircuit的做法是让检查跟着图建立的过程一起跑每次把一个管脚挂到Net上都要回答三个问题——这个管脚允许挂在这样一个名字的Net上吗这个Net上已经有没有东西在驱动它会不会有两个输出直接打架我不是一次性把DRC做完的是分版本一点点堆起来的。最开始只查管脚方向冲突后来加了电源域一致性检查再后来加了同一封装内两个功能管脚被接错的检查。每检查一种我手头的一个老项目就会多暴露几个隐藏问题这让我越来越相信硬件更应该被当作代码来管理。有个例子印象很深我在描述一块电池充电板的时候把保护芯片的一个管脚方向标错了方向。在PyCircuit里实例化完成的那一刻警告就弹出来了提示该Pin声明为Output但你把它连到了一个有源的充电器输出上。这是任何原理图工具都不会主动告诉你的。3.4 多后端生成一套描述五处产出到PyCircuit 6一个编译好的电路模型会同时喂给几个后端后端产出主要用途原理图渲染SVG / PDF带页间网络标号人读、评审、归档PCB生成KiCad工程、Gerber、钻孔文件打样、装配网表后端KiCad / LTspice 兼容网表仿真、EDA导入固件后端C/HAL头文件、引脚宏定义软件工程师直接使用测试后端每个关键Net的短路/开路测试桩工厂返修、上电自检这个结构明显是跟编译器学的核心只维护语义后端各自负责消费。好处是后面加任何新后端都不需要动核心数据结构坏处也不是没有——后端多了之后必须有一套强力的输出测试兜底否则一个改动会让五个产出的格式同时漂移。这也是v6把CI放在很高优先级的原因。4. 从v1到v6重新包这盘饺子的六个大版本4.1 v1一个能生成netlist的脚本最开始的版本根本配不上框架两个字。它就是一个一百多行的Python脚本输入JSON输出netlist。用户界面不存在的注释全靠我临时加能用的场景只有我自己那块转接板。但就是这样一个简陋玩意让我把板子从CAD文件的角度换成了板子是从数据推导出来的的角度。我发现netlist一旦能生成后面很多事都只是时间问题。4.2 v2引入元件库和封装库JSON写多了是地狱。很快我发现板级项目里真正需要反复喂的就是元件。于是v2引入了统一的元件描述格式用YAML维护了一个五脏俱全的小库。这一步现在看很平淡但当时救了我一个大跟头不同芯片厂商对同一个功能管脚的命名能差出十万八千里。比如同样是时钟输出管脚有的叫CLKOUT有的叫MOUT有的叫CO。如果不把这些语义统一到Pin的电气角色上所有下游检查都是空谈。元件库存在的意义是让物理管脚和电气角色分离后面所有智能化分析都从这里受益。4.3 v3原理图渲染器逼着我把原理图渲染器写出来的原因有点尴尬我一个人用脚本跑电路没问题但和外协、和同事沟通时没有人愿意看JSON。大家都要看图一张放在桌上能指指点点的纸。v3做的事就是从netlist反推原理图布局。说实话最开始的输出丑得没法看但它解决了有没有的问题再慢慢解决好不好看的问题。我后来用了一种简单但有效的启发式先按信号流方向给元件分层输入到处理再到输出再在同一层内做横向排列电源和地统一用电源轨拉出。这个规则本质上就是把工程师画电路图的习惯翻译成了布局算法。4.4 v4PCB交互导出v4是个分水岭。它让PyCircuit不再停留在纯生成的层面而是能跟真实EDA工作流互动。做法是通过KiCad的Python接口把生成的网表和初步布局推送到pcbnew里再由人在里面做精细的布线微调。这个半自动设计很重要。我发现全自动布局在原理图层面也许够用但一到PCB层面电磁兼容、走线宽度、热问题、机械约束全都不是纯逻辑能解决的。与其花五年去训练一整套物理优化器不如做一个聪明的自动化把元件按逻辑分组预先摆放让人来拖最后几毫米。4.5 v5从网表到固件框架v5有点野心了。既然电路连接关系已经完全存在于数据结构里那么板子上的芯片从哪个GPIO进来、哪个GPIO出去软件工程师是不是也能直接吃到现成的于是v5加了一个固件后端给定SoC的管脚映射和Net名称自动生成一份C头文件里面把所有复用功能、GPIO初始化需要的宏、外部中断映射的IO号全部定义好。从此硬件和软件的接口不再是N页的引脚定义PDF而是一个可以被diff、被review、被注释的文件。这个版本彻底改变了我的开发方式。以前硬件定型后我要花半天给固件同事解释引脚现在直接把生成的头文件发给对方他include进去就能开始写驱动。4.6 v6回归测试与CI如果项目只有我自己用v5就是终点。但v6我把它当成了一个成熟工具该有的样子来做核心是回归测试和持续集成。我在项目里建了一组金标准板卡这些板卡用PyCircuit描述对应的输出文件原理图SVG、网表、头文件、BOM经过人工确认后被冻成快照。之后任何代码改动CI都会重新生成这些板卡并和快照做diff。一旦某个Net顺序变了、BOM里多了个空格CI就报警。它还支持在CI里跑完整DRC这样每次commit都会触发描述语法检查加电气规则检查加输出快照比对三层验证。这一步让PyCircuit从一个人的脚本变成了可以放心交给别人用的工具链。5. 实测一把用PyCircuit 6从文字到贴片5.1 定一个具体目标纸上谈兵容易我们来看一个真实用的例子。最近我手头做一个环境监测小板主控用STM32G031G8外接两颗状态LED、一颗I2C温湿度传感器、一颗电压检测用的分压电阻网络外加一个2.54mm的调试排针电源用USB转串口模块的3.3V直接供给。这个板子复杂度中等但五脏俱全有电源域、有时钟复位、有通信接口、有低速IO还有典型的上电顺序问题。5.2 用PyCircuit描述这块板子先定义电源和地然后逐个放元件、连Net。核心逻辑大致长这样from pycircuit6 import Board, Component, Net, PowerNet board Board(titleenv_sense_v1) vcc PowerNet(3V3, voltage3.3) gnd PowerNet(GND, refAGND) # 电源去耦按惯例给MCU的每个电源脚就近放100nF board.place(Component(c_mcu_100n, C0603, refdesC1)) board.net_cap_filter(vcc, gnd, cap[100e-9, 1e-6]) # MCU mcu Component(stm32g031g8, UFQFPN28, refdesU1) board.add(mcu) board.net(PB1_LED).connect(mcu.pin(PB1)) board.net(PA9_UART_TX).connect(mcu.pin(PA9)) # LED 和限流电阻 led1 Component(led_0402, LED_0402, refdesD1) r1 Component(res_1k, R0603, refdesR1) board.series(vcc, r1.pin(1), led1.pin(1)) board.net(PB1_LED).connect(led1.pin(2)) # I2C sensor Component(sht40, DFN-4, refdesU2) board.net(I2C_SCL).connect(mcu.pin(PB6), sensor.pin(2)) board.net(I2C_SDA).connect(mcu.pin(PB7), sensor.pin(3))这段代码看着很短但每个函数背后都做了一堆隐性工作series帮我们把两个Pin串成一个串联子电路并检查方向net_cap_filter自动生成去耦电容并把两端正确挂到电源域。生成的模型里已经有完整的分层结构和DRC信息。5.3 运行、验证、渲染写完之后在项目终端里敲一条命令pycircuit build env_sense.py --emit pdf --emit kicad --emit header --emit bom几秒钟后输出目录里出现了原理图PDF、KiCad工程、bom.csv和stm32_hal_pins.h。我又特意跑了一条pycircuit check env_sense.py它返回了三条警告其中一条是MCU的PF2管脚被用作了复位引脚却在Net BRIDGE_ON上确实是我在描述阶段漏掉的。这种错误以前要在打板回来用万用表测半天才能发现现在生成阶段就拦住了。5.4 产出物清单一批文件直接可用文件格式用途env_sense_schematic.pdfPDF评审和归档env_sense.kicad_sch / .kicad_pcbKiCad工程文件继续精调或直接生产env_sense/gerber/*.gbrGerber找工厂打样env_sense/bom.csvCSV采购与贴片env_sense/stm32_hal_pins.hC头文件固件工程师直接include我拿Gerber去打了样板子回来焊好后两块板全部正常工作。两块板都一次点亮这件事说实话比我预期的要顺利因为以往这种中密度的板子第一次打样总会有几个小毛病要飞线。6. 真坑记录硬件生成器容易翻车的几个地方6.1 管脚命名是个无底洞做硬件描述框架最想不到的坑是管脚命名。芯片官方给的名字和EDA厂商库里用的名字和封装库里的物理引脚编号常常不是一套系统。比如某个MCU的SWD接口官方叫SWDIO封装库里叫PA13你的代码再用信号语义叫PROGRAM_DATA。如果元素库里没有把这三层关系建立好任何下游检查都会变成猜谜。我在PyCircuit里给出的答案是元件库的每个Pin至少要带上三份元数据——物理编号、符号名、电气角色。所有规则检查都优先信任电气角色这一层其余的只是展示面。你不这样分层设计后面换封装批次或者芯片改版时一定会炸。6.2 逻辑连通的电路物理上可能完全走不通这是所有代码生成硬件方案最容易被高估的地方。软件里逻辑正确基本等于结果正确硬件不是。举个简单例子代码里MCU的3V3管脚接了一个100nF去耦电容然后接地。逻辑完全正确。但如果布局时那个电容被放在离芯片5厘米远的地方这个连接在电磁上等于没接。电源完整性是物理问题不是逻辑问题。生成器可以在原理图层面保证你连对了却没法保证你放好了。我的应对是约束提示机制。在描述阶段可以直接给电源管脚标记decoupling_nearest提示给布局后端让它在自动摆放时优先把去耦电容贴近对应电源管脚。它不是一个物理优化器但至少是一个把经验写进代码的通道。6.3 渲染器的可读性要单独投入还有别以为原理图渲染器只是把元件画出来。真正有价值的可读性来自布局谁在左、谁在右、电源如何跨页、信号流方向是否一致。我一开始天真地按元件名称字母序排出的图完全没法看。后来把工程经验翻译成三条启发式输入放左、输出放右、电源轨统一上下。每条规则都是我对着自己手工画的图总结出来的。这条路上没有捷径只有不停地拿真实项目当测试集用工程师的眼睛做标定。6.4 别把生成器做成黑盒最后一个坑是工具边界本身。我见过太多自动生成工具输出像魔法你改一行代码结果文件变了但没人能解释为什么。PyCircuit从v3开始就坚持一个原则每一个生成文件都必须可被审计。网表可以按图索骥地查PDF可以找到每个元件对应的生成逻辑头文件里的每行宏定义都能追溯到那句电路描述。有一次我在DRC里加了一条规则后依赖顺序没调好同一个Net的枚举顺序全变了CI里快照diff立刻刷了几百行。如果没保留这个可解释性用户根本不知道是哪里出了问题。这个原则比任何花哨功能都重要。因为它意味着你的工具出错时团队还能降级回去干活如果工具是黑盒一出错就只能等作者修整个交付节奏都会被打断。如果让我说这六年最值的一点其实是设计即源码、图纸即产物这个工作方式带来的正反馈循环改动成本越低就越敢改越敢改设计质量就越好。如果你也想从画板切换到写板我的建议很简单别一上来就追求生成PCB。先把每天都要做的接口转接、电源去耦、MCU核心板这几个子电路封装成可复用组件每两周多封装一个半年之后你会发现自己的饺子馅已经不知不觉攒了一大盆。到那时再回头审视要不要全套自动生成你会比任何人都清楚这条路到底适不适合自己。