回路をテキストで宣言したら、基板が「ほぼ」組み上がった — 設計→自動配線→DRC を Python 1 本で

  • python
  • pcb
  • 電子工作
  • eda
  • プリント基板

先に、今回たどり着いた場所

このシリーズでは、KiCad を使わずに Python だけで基板を配線する、という小さな実験をしてきました。第 1 本は自作の素朴な迷路法で 22 本を通しただけの話、第 2 本は 5 通りの設定を競わせる小さなベンチ(router-arena)でした。どちらも見ていたのは、主に「配線」の部分でした。

でも、私が試したいのは、配線の前後まで含めた流れです。第 2 本の最後で「次は別系統の density router を競わせる」と予告していましたが、その比較は今回いったん横に置きます。回路を文章のように宣言したら、部品の配置も、配線も、製造前チェックまでも通って、基板データが 1 本で出てくる。理想を言えば、AI アシスタントと会話しながら回路を固めて、そのまま基板まで持っていく。今回はそのパイプラインを、端から端まで一度通してみた回です。

一気通貫は動きました。ただし、全自動で完璧、とはいきませんでした。 素朴に全部おまかせにすると、11 本中 9 本は通るものの、基板の端からトラック(配線)がはみ出して製造チェックに 3 件引っかかる。そこから先、どこが外れているかを機械が具体的に指してくれるので、私は配置と配線ノブの 2 つを手で動かして、ようやく 11 本すべてをきれいに収めました。自動化の値打ちは「ボタン一発の完璧さ」ではなく、「速くて、外れた場所を具体的に言ってくれるループ」の方にあったのだと思います。

テキストで回路を「宣言」する

今回使ったのは、第 1 本の pcbflow(外部ライブラリ)でも第 2 本の router-arena でもなく、同じ実験テーマを配線の前後まで広げるために手元で別に育てている小さな自作ツールです(純 Python + 描画に Pillow だけ、外部 CAD 非依存)。座標も配線経路も書きません。部品とネットを宣言するだけで、あとは 1 つのメソッドが配置・配線・チェック・保存までやります。

先に用語だけ短く整理します。フットプリント=部品が基板に占める実装形状(パッドの並びや穴の形。DIP-14、R0805 など)。ネット=「この端子とこの端子は電気的に繋がる」という結線の宣言。ビア=表と裏の銅箔層をつなぐ貫通穴(配線を裏に逃がすのに使う)。DRC(Design Rule Check)=線幅・すき間(クリアランス)・基板外形との距離などが製造ルールを満たすかの自動チェック。オートルート=ネットの通り道を自動で探す配線処理です。

題材はオペアンプ(LM324)を 1 個使った、抵抗 3・コンデンサ 2・IO ヘッダ 1 の小さな増幅回路。宣言はこれだけです。

from pcbtool import dsl
from pcbtool.chips import LM324

c = dsl.Circuit("LM324_Amp")
u1 = c.add_chip("U1", LM324)               # DIP-14 のオペアンプ
r1 = c.add("R1", fp="R0805", value="10k")  # 表面実装の抵抗(約 2.0 × 1.25 mm)
r2 = c.add("R2", fp="R0805", value="22k")
c1 = c.add("C1", fp="C0603", value="100nF")
j1 = c.add("J1", fp="HDR1x4", value="IO")  # 外部 IO ピンヘッダ
# ... 抵抗もう 1 本・コンデンサもう 1 個は省略

# 論理ピン名で結線(データシートのピン番号を覚えなくていい)
c.net("VCC",  u1.VCC,  c1[1], j1[1])       # 電源
c.net("GND",  u1.GND,  c1[2], j1[4])       # グラウンド
c.net("VOUT", u1.OUT1, r2[2], j1[3])       # 出力
# ... 入力側の NEG / POS / VIN も同様に宣言

# 電源ラインだけは太く・広く(ネットクラス)
c.net_class("POWER", "VCC", "GND", width="20mil", clearance="12mil")

result = c.build(autoroute_board=True, drc=True, outline_mm=(80, 60),
                 save_to="amp.pcb.json")
print(result.routed, len(result.drc_issues))

最後の net_class に出てくる ネットクラス=「このネットの束には別のルールを当てる」という指定です。電源とグラウンドは電流が乗るので、width="20mil"(線幅 20 mil=約 0.5 mm)と少し太めにしています(信号線の既定は 10 mil=約 0.25 mm)。クリアランスは導体どうしを何 mm 離すかの最小すき間です。

宣言はこれで終わり。座標は 1 つも書いていません。

build する前に、機械が下読みしてくれる

build() を呼ぶ前に、lint() という軽い下読みがかけられます。これは配線を試す前に、明らかな不整合(未登録のフットプリント、同じ端子が 2 つのネットに割り当たっている、どのネットにも繋がっていない孤立部品、など)をまとめて洗い出す関数です。今回の宣言だと、返ってきたのはこれだけでした。

[layout] U1 は配置未指定 (auto layout で配置される)
[layout] R1 は配置未指定 (auto layout で配置される)
... (全部品について同じ)

「配置場所を書いていないので、自動レイアウトに回すよ」という情報だけ。結線そのものにおかしな所は無い、と先に分かります。設計判断(何と何を繋ぐか)は私が持ち、幾何と整合チェックは機械が持つ。この分担が私には気持ちよくて、今回いちばん作って良かった部分でした。

素朴に全部おまかせにすると

まず、配置を一切指定せず、自動レイアウト(部品を等間隔の格子に並べるだけ)に丸投げして build しました。結果がこれです。

素朴な自動配置。部品が広い基板の左下に固まり、未配線 2 本(シアンの破線)と、基板の外形からはみ出す配線が DRC 違反(赤い×)になっている

数字にすると、配線は 11 本中 9 本、DRC 違反が 3 件、配置の警告が 2 件(ここでの「本数」は結線の単位=2 端子区間の数です。今回は電源・GND も含めて 6 ネットあり、端子が 3 つ以上のネットは区間に分かれるので、合わせて 11 区間になります)。図の色は基板 CAD の慣習どおり、赤=表の銅箔、青=裏の銅箔です。通らなかった 2 本はシアンの破線(ラッツネスト=まだ配線されていない接続を、直線で仮に結んだ表示)で残っています。

DRC 違反 3 件の中身は、どれも同じ種類でした。

基板外形クリアランス違反 top: track -5mil < 11.811mil

top は表の銅箔層、-5mil(約 −0.13 mm)は、その配線が基板の左端(x=0)より外側に少しはみ出している、という意味です。私の自動配線は、基板の外形を「越えてはいけない壁」としてはまだ扱えておらず、探索の格子が外形の外まで少し漏れることがある。これは第 2 本のベンチには無かった弱点で、全ループに繋いで初めて見えたものでした(直さずに、観察として残してあります)。

80 × 60 mm の広い基板を指定したのに、部品は左下の一角に固まっています。広く取れば楽に通るだろう、という素朴な期待は外れました。 空いた面積は使われず、むしろ端に寄った配線が外形を割る。ここが今回の出発点です。

「どこが外れたか」を読んで、配置を手で導く

ここからは手作業で直します。ただ、勘で直すのではありません。DRC と配置警告が「基板外形」「左下」「この 2 本が未配線」と具体的に指しているので、それを読んで、部品を意図した位置に置き直します。基板も 80 × 60 から 70 × 50 mm に詰めました。

……のですが、私の 1 回目の手直しは、むしろ悪化しました。 ヘッダ J1 を「基板の左辺にスナップ」で置いたら、ヘッダのパッドが基板の外にはみ出して(pad J1.1 -32mil=約 −0.8 mm)、DRC が 7 件に増えたのです。辺スナップの既定マージンと、ヘッダの端子列の幅の関係を、私が読み違えていました。

配置は一発では決まりません。フットプリントの実寸(DIP-14 なら端子列は約 15 mm × 7.6 mm)を確認して、各部品を基板の内側に余裕を持たせた明示座標で置き直したのが、次の図です。

配置を手で導いた版。全部品が基板内にきれいに収まり、DRC 違反はゼロ。ただし配線は 7 本止まりで、4 本が破線で残っている

DRC 違反 0・配置警告 0 まで来ました。基板内にもきれいに収まりました。ところが今度は、配線が 7 本しか通らず、4 本が破線で残りました。部品を広げてゆったり置いたぶん、1 本 1 本の配線が長く・遠回りになり、既定の配線処理(先に引いた線が後から来た線を塞いだら、そこで諦める方式)では、経路が見つからなかったのです。

詰めれば端を割り、広げれば経路が見つからない。このトレードオフは、第 2 本で見た「配線処理と基板ルールは同じ目で見て調整するもの」という話の、一段手前の版でした。配置と配線は別々の工程に見えて、実は一緒に効いている。

配線ノブを 1 つ足すと、残り 4 本も通る

残り 4 本は、配置ではなく配線処理側のノブで解けそうでした。第 2 本のベンチで、通る本数を押し上げる場面があった rip-up & retry(一度引いた線を剥がして、配線順や経路を変えて引き直す手法)を、ここで効かせてみます。呼び出しはノブを 1 つ渡すだけです。

result = c.build(autoroute_board=True,
                 autoroute_kwargs=dict(ripup=True),   # ← これだけ
                 drc=True, outline_mm=(70, 50))

結果は 11 本すべて配線・DRC 違反 0・配置警告 0。これで全部通りました。

配置を導き、rip-up を効かせた最終状態。11 本すべてが配線され(破線は消え)、赤=表・青=裏の 2 層とビア(黄の菱形)で収まっている

配線が引かれていく様子も録りました。トラックが 1 本ずつ commit され、途中で塞がると rip-up で引き剥がして引き直す(この録画では剥がしが 24 回起きています)。残りの破線が少しずつ減っていくのが見えます。

rip-up 込みの自動配線の進行。トラックが引かれ、詰まると剥がして引き直しながら、破線が消えていく

この動画では、引き剥がして引き直す動きがいちばん分かりやすいです。第 2 本の静止画の順位表では分かりにくかった「配線処理が試行錯誤している様子」が、動くと掴みやすくなります。

詰まった所と、見えてきたこと

通してみて、手が止まったのは配線アルゴリズムそのものではなく、その周りでした。

  • 基板の外形を、まだ硬い壁として扱えていない。素朴版の 3 件はここでした。探索の格子に外形をきちんと食わせるのが次の宿題です。
  • 辺スナップのマージンを読み違えて、部品を基板の外に出した。便利機能ほど、既定値の前提を確認せずに使うと外します。
  • 配置を広げると配線が届かず、詰めると端を割る。この綱引きに「万能の一手」は、少なくとも私のこの実装の範囲では見当たりませんでした。効いたのは、配置を手で導きつつ、配線ノブ(rip-up)を足すという合わせ技でした。

最後に、これをやっている理由も書いておきます。基板のレイアウトは、回路図をよく分かっている本人が自分でやると都合の良い場面があります。とくに伝送線路として気を使うほど高速でない、だいたい 100 MHz 以下くらいの信号なら、自分の意図をすぐ反映できるので判断が速いです。一方でレイアウトだけを外注すると、配線自体はとても綺麗に仕上げてもらえますが、稀に「そこはそういう意図ではなかった」という認識のズレが起きることもあります。優劣の話ではなく、ここが自動化と対話の使いどころだと思っています。回路の意図を持っている本人が、宣言文で設計を決めて、レイアウトも対話しながら進められたら面白いです。今回そのループを端から端まで一度通してみて、その手応えが少し具体的になりました。

ひとつ付け加えると、宣言文の下書きを AI アシスタントに手伝ってもらう場合でも、出てきたものを lint と DRC で検証できるので、安心して任せられます。何を繋ぎ、どこに置くかの判断は、最後まで手元に残ります。

再現と、次へ

この記事の数字と図を出したツールは、リポジトリに置いてあります(純 Python、python examples/lm324_amp.py を実行すると、上の 3 段階=9/11 → 7/11 → 11/11 が順に出ます)。

次にやってみたいのは 2 つ。1 つは、素朴版で見えた 外形を硬い壁として扱う配線(端の 3 件を根から消す)。もう 1 つは、第 2 本で予告しておいて今回は横に置いた density 系(混雑を反復で見積もって経路コストに乗せる配線) を、この全ループの上で効かせてみることです。今回は「配線処理をもう一段」より「まず端から端まで通す」を優先したので、そちらは持ち越しにしました。

まだ趣味の実験の域ですが、配線単体から、宣言→配置→配線→チェックの輪をひとまず一周できたのは、私にとっては小さくない一歩でした。おかしな所や、もっと良い計測軸があれば教えてください。読んでくださって、ありがとうございました。

この記事は Zenn に初出したものを加筆・補足したものです ── Zenn の元記事を見る