← 全部文章
NOTE 06Python · Schemdraw · Verification

用 Schemdraw 把电路拓扑写成可验证的绘图程序

以 11 管复合放大电路为例:先建立网络契约,再用命名锚点、正交布线与机器断言生成可复现原理图。

发布于 2026/08/158 分钟阅读通用公开示例

复杂原理图最难的地方,并不是把晶体管符号放到画布上,而是让电气连接、版面关系和视觉语义同时正确。一张图能成功导出,只能证明程序运行了;它没有证明镜像管方向正确,也没有证明一条看似无连接点的线没有恰好落在器件端点上。

这篇笔记以一张 11 管复合放大电路为例,整理一套更接近软件工程的 Schemdraw 用法:把拓扑当作源数据,把绘图当作渲染,再为容易出错的结构写可执行的验收规则。示例材料使用 Schemdraw 0.23,完整产物包括 Python 源码、SVG、PNG、独立网络表和复盘记录。

Schemdraw 生成的 11 管复合放大电路,包含差分输入、有源负载、电压放大级、密勒补偿和互补输出级
图 1 最终程序化原理图。点击可查看 SVG;蓝色为有源器件,黑色为互连与电源网络。

先把“画图”改写成一条编译管线

如果直接从左到右堆元素,坐标会很快取代电路本身成为注意力中心。更稳妥的顺序,是先声明电路事实,再逐级降低抽象层次。

01 · CONTRACT器件与主网络端口、极性、C/B/E 或 D/G/S
02 · GEOMETRY功能级与锚点差分级、镜像源、VAS、输出级
03 · ROUTING显式正交连线水平/垂直段、结点与端口语义
04 · ACCEPTANCE断言 + 看图结构检查与视觉检查互不替代
图 2 Schemdraw 在这套方法里是渲染器,而不是拓扑的唯一事实来源。

这个例子先被拆成五个功能段:Q1/Q2 是 NMOS 差分输入;Q4/Q5 是 PNP 电流镜有源负载;Q10/Q3 与 R1 形成尾电流镜;Q7、Q11/Q6 组成第二电压增益级及其负载;D1/D2 与 Q8/Q9 构成偏置和互补射极跟随输出。C1 从第二级输出回到第一级输出,承担密勒补偿。

器件清单还不够。真正能抵抗误连的是“端子—网络”契约。例如输出级先写成下面这张表,再决定它在页面右侧排成什么形状:

端子必须连接绝不能混入
Q8.C+12 VD1 节点、输出
Q8.BD1 高偏置节点+12 V、输出
Q8.Euout电源轨
Q9.C−12 VD2 节点、输出
Q9.BD2 低偏置节点−12 V、输出
Q9.Euout电源轨

这一步挡住了一个很常见的“漂亮错误”:为了让输出级紧凑,把两只管子的多个端子并到同一根竖线上。视觉上像一个整齐的输出柱,电气上却已经短接。

用命名锚点连接,不用目测坐标猜端子

一个轻量的 Builder 负责三件事:登记器件、保存每段路线、区分实心结点与空心外部端口。每个器件加入图后,都以编号作为名字保存;后面的连接只读取 gate、drain、source 或 base、collector、emitter 等锚点。

class Builder:
    def device(self, ref, element):
        element.color(BLUE).linewidth(1.65)
        placed = self.d.add(element)
        self.devices[ref] = placed
        return placed

q1 = b.device("Q1", elm.NMos().right().at((7.0, 9.2)))
q2 = b.device("Q2", elm.NMos().right().reverse().at((11.0, 9.2)))
b.route([q1.source, (q1.source.x, tail[1]), tail], name="Q1 source to tail")

这里的坐标只负责版面,q1.source 才负责电气语义。即使以后移动 Q1,只要它的源极锚点仍然成立,连接代码就不会把“原来的某个点”误当成源极。

同时,每只关键器件都显式指定方向。材料中的一次失败说明:.at(...) 只确定位置,不一定重置 Schemdraw 前一个定向元素留下的绘制方向。差分管明明坐标正确,却可能横躺;在向下绘制二极管之后创建输出管,也可能继承错误方向。显式的 .right()、.reverse() 或 .flip(),是在消除隐藏状态。

布线是受约束的几何,而不是装饰

外部连线统一拆成水平或垂直线段,任何斜线都会在加入图纸前触发异常。密集模拟电路仍允许晶体管符号内部保留标准斜引脚,但不让外部路线为了“走捷径”穿过器件。

def segment(self, start, end, *, name="wire"):
    a, b = self.xy(start), self.xy(end)
    if not horizontal_or_vertical(a, b):
        raise ValueError(f"Non-orthogonal route {name}: {a} -> {b}")
    self.d.add(elm.Line().at(a).to(b))
    self.routes.append((name, a, b))

结点也必须有明确语义:

  • 实心点只表示真实的 T 形分支或多个内部端子共网;
  • 空心圆只允许出现在 u+、u−、uout 三个外部信号端口;
  • 普通折角和两段线的连续处不画点;
  • “没有点”不能抵消端点落在线上的事实。一个器件端子若正好压在无关导线上,几何上仍然是短接风险。

这也是为什么输入端口最终被移到 −12 V 电源轨下方。早期版本虽然画了空心圆,但圆心恰好位于电源轨上;符号看起来像端口,坐标事实却意味着输入被短到负电源。

把容易回归的图形规则写成断言

机器检查适合验证数量、集合、方向和坐标关系。示例脚本在保存前确认 11 只晶体管全部存在,空心端口恰好有 3 个,全部外部路线正交,三组电流镜均向内相对,并核对输出级端子表。

required = {f"Q{i}" for i in range(1, 12)}
assert set(b.devices) == required
assert b.open_ports == {(5.0, input_y), (14.0, input_y), (25.0, 7.0)}

for left, right in ((q4, q5), (q10, q3), (q11, q6)):
    assert left.base.x > left.collector.x
    assert right.base.x < right.collector.x
MACHINE GATE程序证明什么

器件数、端口白名单、正交线段、端子集合、镜像方向、电源轨终点、线路是否穿过器件包围盒。

VISUAL GATE人眼仍要证明什么

电路级次是否易读、标签是否遮挡、留白是否均衡、符号朝向是否符合习惯、交叉关系是否可能被误解。

两道门不能互相替代。程序成功导出 SVG,只说明语法和断言通过;像素级没有重叠,也不代表读者能一眼理解信号流。最终验收必须回到真实 SVG/PNG,按功能级、器件方向、每个端子、结点和页面边缘逐项查看。

一次生成,保留四种证据

适合长期维护的交付不是单独一张 PNG,而是四件互相补强的产物:

  1. Python 源码:可以修改和重建;
  2. SVG:保留矢量质量,适合网页放大与排版;
  3. PNG:便于快速预览、批注和跨软件查看;
  4. 独立网络表与复盘记录:在坐标变化时保护拓扑,在再次失败时保留原因。

这套方法的边界也要写清楚:它能提高绘图拓扑和表达的一致性,但不会替代 SPICE 仿真,也不证明器件参数、环路稳定性、温漂或功率裕量。Schemdraw 负责让电路意图成为可审查、可再生的工程文档;电路本身是否工作,仍要交给模型、计算、仿真与实测。