第4章 回路図の自動生成 ― xschemを使わずに描く
この章のねらい。 第3章で手作業で描いた
これは「応用編」ですが、余興ではありません。第3章の検証で実際に起きたことが、この章を書く理由になっています。
inverter.sch を、今度はPythonのスクリプトで生成します。GUIで部品を置き、線を引く代わりに、座標とラベルをコードで書き、テキストとして書き出します。これは「応用編」ですが、余興ではありません。第3章の検証で実際に起きたことが、この章を書く理由になっています。
4.1 なぜ、自動化するのか
結論から言います。第3章の検証中、GUIでの手作業は10回以上、目に見えない失敗を起こしました。テキストを直接書いた場合は、一度も失敗しませんでした。これは主観的な感想ではなく、実際の検証ログに残っている事実です。
実際に起きたこと(第3章の検証より)
テストベンチ
テストベンチ
tb_inverter.schを作る際、電源3個(VDD・VIN・VSSSRC)と負荷容量1個(CL)を配線する作業で、次のことが起きました。
- VINの
+端子を引いたつもりが、近くにあった別のラベル線(VDD)に誤って接続されていた - 端子同士を重ねて接続を試みたが、電気的にはつながっていなかった
- 画面を拡大すると、1個の接続点が巨大な丸に見え、あたかも配線が重複しているかのように誤認した
- 逆に、通常の縮尺では、接続の有無を目で判別できなかった
selected: 1 と出ます。見た目には2個に見えていたものが、実際には1個の部品でした。目視では判断できず、xschem自身に選択させて、初めて分かりました。
唯一、確実だった判断方法
この検証の中で、最終的に信頼できたのは1つだけでした。
これは、xschemが内部で使っている判定そのものです。人間の目やAIの目で図を判定するのではなく、xschem自身に判定させ、その結果をテキストで受け取るのが、確実な方法でした。
この検証の中で、最終的に信頼できたのは1つだけでした。
xschem -n -q -x でネットリストを出力し、ノード名が自動生成の net1 のような名前ではなく、意図した名前(VDD・IN・OUTなど)になっているかを確認するという方法です。これは、xschemが内部で使っている判定そのものです。人間の目やAIの目で図を判定するのではなく、xschem自身に判定させ、その結果をテキストで受け取るのが、確実な方法でした。
ここから導かれる結論はシンプルです。回路図はテキストである以上、テキストとして生成し、テキストとして検証すればよい。座標を目で合わせる必要も、端子をクリックで探す必要もありません。
4.2 .schファイルの文法
第3章3.7節ですでに見たとおり、.schファイルは平文のテキストです。ここで、その文法を整理します。
| 記法 | 意味 |
|---|---|
C {シンボルパス} X Y 回転 反転 {属性...} | 部品(Component)の配置 |
N X1 Y1 X2 Y2 {lab=ラベル名} | 配線(Net)。始点と終点の座標を結ぶ |
部品の行(
C {...})C {sky130_fd_pr/nfet_01v8.sym} -2070 -830 0 0 {name=M1
W=1
L=0.15
...}
最初の {...} がシンボルのパス、続く2つの数字が配置座標(X, Y)、その次の2つが回転と反転(通常は 0 0)です。最後の {...} の中に、name=やW=などの属性が改行区切りで並びます。
配線の行(
N {...})N -2050 -980 -2050 -950 {lab=VDD}
4つの数字が、始点(X1, Y1)と終点(X2, Y2)です。lab=で指定した名前が、その線につながっているノードの名前になります。同じlab=を持つ配線・部品端子は、離れた場所にあっても電気的につながります。これが、第3章でVDD電源の端子とVDDピンを直接線でつながなくても、同じlab=VDDを持たせれば接続できる理由です。
部品の端子座標は、必ずシンボルファイルで確認してください
部品を配置しただけでは、その端子がどの座標にあるかは分かりません。配置座標に、シンボル自身が持つ端子の相対座標を足し算する必要があります。推測で決め打ちすると、第3章の検証で実際に起きたように、1グリッドずれて接続に失敗します。
確認方法は、シンボルファイルを直接読むことです。
部品を配置しただけでは、その端子がどの座標にあるかは分かりません。配置座標に、シンボル自身が持つ端子の相対座標を足し算する必要があります。推測で決め打ちすると、第3章の検証で実際に起きたように、1グリッドずれて接続に失敗します。
確認方法は、シンボルファイルを直接読むことです。
grep -n "name=" $PDK_ROOT/sky130A/libs.tech/xschem/sky130_fd_pr/nfet_01v8.symこう出れば確認できたことになります
B 5 17.5 -32.5 22.5 -27.5 {name=D dir=inout}
B 5 -22.5 -2.5 -17.5 2.5 {name=G dir=in}
B 5 17.5 27.5 22.5 32.5 {name=S dir=inout}
B 5 19.921875 -0.078125 20.078125 0.078125 {name=B dir=in}
B 5 x1 y1 x2 y2 {name=... dir=...}という行が、端子(ピン)の定義です。座標は、配置時の回転・反転が0 0(無回転)であれば、そのまま配置座標に足し算できます。
4.3 Pythonでインバータを生成する
第3章3.5〜3.7節で手作業で作ったのと同じ回路を、今度はスクリプトで生成します。
① 部品の端子座標を確認してください
前節のとおり、必要なシンボルすべてについて、事前に
前節のとおり、必要なシンボルすべてについて、事前に
grepで端子座標を確認します。
grep -n "name=" $PDK_ROOT/sky130A/libs.tech/xschem/sky130_fd_pr/nfet_01v8.sym grep -n "name=" $PDK_ROOT/sky130A/libs.tech/xschem/sky130_fd_pr/pfet_01v8.sym grep -n "name=" /foss/tools/xschem/share/xschem/xschem_library/devices/ipin.sym grep -n "name=" /foss/tools/xschem/share/xschem/xschem_library/devices/opin.sym grep -n "name=" /foss/tools/xschem/share/xschem/xschem_library/devices/iopin.symこの確認を省略しないでください。省略して覚えている数値のまま書くと、第3章で実際に起きた接続ミスを繰り返します。
② スクリプトを作成してください
/foss/designs/inverter/gen_inverter.pyとして、次の内容を作成します。
cat > gen_inverter.py << 'PYEOF'
#!/usr/bin/env python3
"""
インバータの回路図(inverter.sch)を自動生成する。
座標は第3章3.5〜3.7節で手作業により確認済みのものを使用。
"""
# 部品の配置。(シンボルパス, X, Y, 属性の辞書)
parts = [
("sky130_fd_pr/pfet_01v8.sym", -2070, -920,
{"name": "M2", "W": "1", "L": "0.15", "nf": "1", "mult": "1",
"model": "pfet_01v8", "spiceprefix": "X"}),
("sky130_fd_pr/nfet_01v8.sym", -2070, -830,
{"name": "M1", "W": "1", "L": "0.15", "nf": "1", "mult": "1",
"model": "nfet_01v8", "spiceprefix": "X"}),
("iopin.sym", -2050, -980, {"name": "p1", "lab": "VDD"}),
("iopin.sym", -2050, -780, {"name": "p2", "lab": "VSS"}),
("ipin.sym", -2090, -880, {"name": "p4", "lab": "IN"}),
("opin.sym", -1990, -880, {"name": "p3", "lab": "OUT"}),
]
# 配線。(X1, Y1, X2, Y2, ラベル名)
wires = [
(-2050, -980, -2050, -950, "VDD"),
(-2050, -950, -2050, -920, "VDD"),
(-2050, -800, -2050, -780, "VSS"),
(-2050, -890, -2050, -860, "OUT"),
(-2050, -880, -1990, -880, "OUT"),
(-2090, -920, -2090, -880, "IN"),
(-2090, -880, -2090, -830, "IN"),
(-2050, -830, -2050, -780, "VSS"),
# バックゲート(第3章3.6節⑥に対応)
(-2030, -920, -2010, -920, "VDD"), # PMOSのB→VDD
(-2010, -920, -2010, -950, "VDD"),
(-2030, -830, -2010, -830, "VSS"), # NMOSのB→VSS
(-2010, -830, -2010, -800, "VSS"),
]
def format_component(sym, x, y, attrs):
attr_lines = "\n".join(f"{k}={v}" for k, v in attrs.items())
return f"C {{{sym}}} {x} {y} 0 0 {{{attr_lines}\n}}"
def format_wire(x1, y1, x2, y2, lab):
return f"N {x1} {y1} {x2} {y2} {{lab={lab}}}"
def build_sch():
lines = [
'v {xschem version=3.4.8RC file_version=1.3}',
'G {}', 'K {}', 'V {}', 'S {}', 'F {}', 'E {}',
]
for w in wires:
lines.append(format_wire(*w))
for p in parts:
lines.append(format_component(*p))
return "\n".join(lines) + "\n"
if __name__ == "__main__":
with open("inverter_generated.sch", "w") as f:
f.write(build_sch())
print("inverter_generated.sch を書き出しました")
PYEOF
バックゲートの配線座標について
上記スクリプトのバックゲート配線は、説明のための簡略化した座標です。実際にお使いになる際は、①で確認したB端子の実座標(シンボル配置座標+シンボル内相対座標)に基づいて、正確に計算し直してください。座標を仮に置いたまま実行すると、第3章で起きたのと同じ接続ミスが起こります。
上記スクリプトのバックゲート配線は、説明のための簡略化した座標です。実際にお使いになる際は、①で確認したB端子の実座標(シンボル配置座標+シンボル内相対座標)に基づいて、正確に計算し直してください。座標を仮に置いたまま実行すると、第3章で起きたのと同じ接続ミスが起こります。
③ 実行してください
python3 gen_inverter.pyこう出れば成功です
inverter_generated.sch を書き出しました
④ 検証してください
第3章3.7節と同じ方法で検査します。ここが自動化の核心です。生成したファイルが正しいかどうかを、人間の目ではなく、xschem自身に判定させます。
もし
第3章3.7節と同じ方法で検査します。ここが自動化の核心です。生成したファイルが正しいかどうかを、人間の目ではなく、xschem自身に判定させます。
xschem -n -q -x inverter_generated.sch cat inverter_generated.spice第3章3.7節で確認したのと同じ形(
XM1 OUT IN VSS VSS ...、XM2 OUT IN VDD VDD ...)が、エラー・ワーニングなしで出力されれば成功です。もし
net1のような自動生成名が出た場合は、②のバックゲート座標が間違っています。①に戻って、正確な座標を再計算してください。
これが、手作業とどう違うのか
手作業では、部品を置き、端子をクリックし、線を引き、保存し、ネットリストを確認する、という一連の操作を、部品や配線の数だけ繰り返します。**その1回1回に、クリックの誤差やGUIの反応待ちといった、不確実な要素が入り込みます。**
スクリプトでは、座標は数値として1箇所にまとまり、実行するたびに同じ結果が出ます。間違いがあるとすれば、それは①の座標確認を怠ったときだけです。そして、間違いがあれば④の検証で必ず検出できます。
手作業では、部品を置き、端子をクリックし、線を引き、保存し、ネットリストを確認する、という一連の操作を、部品や配線の数だけ繰り返します。**その1回1回に、クリックの誤差やGUIの反応待ちといった、不確実な要素が入り込みます。**
スクリプトでは、座標は数値として1箇所にまとまり、実行するたびに同じ結果が出ます。間違いがあるとすれば、それは①の座標確認を怠ったときだけです。そして、間違いがあれば④の検証で必ず検出できます。
4.4 テストベンチも同じ方法で生成する
第3章3.8節のテストベンチ(電源・負荷容量・code_shownを含む回路)も、同じ考え方で生成できます。ここは特に、GUIでの失敗が集中した箇所です。
複数行の
第3章3.8節で確認したとおり、
value属性を、スクリプトで組み立てる第3章3.8節で確認したとおり、
code_shownのvalue=は、全体をダブルクォートで囲む必要があります。Pythonで書く場合、次のように組み立てます。
spice_code = '''.lib /foss/pdks/sky130A/libs.tech/combined/sky130.lib.spice tt
.temp 27
.control
dc VIN 0 1.8 0.002
meas dc vsp when v(OUT)=v(IN)
quit
.endc
.end'''
code_shown_attrs = {
"name": "s1",
"only_toplevel": "false",
"value": f'"\n{spice_code}"',
}
クォートで囲む処理を、スクリプトの中に固定してしまえば、GUIで貼り付けるたびにクォートを付け忘れる、という失敗自体が起こらなくなります。
電源の接続も、同じ
第3章3.8節で、VIN電源の
lab=の仕組みで解決します第3章3.8節で、VIN電源の
+端子を誤ってVDDのラベル網に接続してしまった失敗がありました。スクリプトでは、電源の配置座標とシンボルの端子座標(vsource.symのp/m端子)から実座標を計算し、意図したラベル(IN)を明示的に指定するだけです。近くの別のラベル線に誤って触れる、ということが構造上起こりません。
# vsource.sym: p端子は中心から上へ30、m端子は下へ30(第3章4.2節相当の確認による) vin_x, vin_y = -2170, -910 wires.append((vin_x, vin_y - 30, vin_x, vin_y - 10, "IN")) # VINの+側→IN wires.append((vin_x, vin_y + 10, vin_x, vin_y + 30, "0")) # VINの-側→接地
検証してください
xschem -n -q -x tb_inverter_generated.sch cat tb_inverter_generated.spice第3章3.8節④で確認したのと同じ形(
VDD VDD 0 1.8、VIN IN 0 0、VSSSRC VSS 0 0、CL OUT 0 10f)が出力されれば成功です。
4.5 xschemで開いて、見た目も確認する
ネットリストが正しくても、図として破綻していないかは、別に確認する価値があります。座標の計算間違いで、部品が重なったり、配線が不自然に長く伸びたりすることがあるためです。
生成したファイルをxschemで開いてください
xschem inverter_generated.sch &図3-1と見比べて、部品の配置や配線の形が大きく崩れていないかを確認します。多少の見た目の違いは問題ありませんが、部品同士が重なっている場合は、座標を見直してください。
この章のまとめ。
- 自動化の根拠 第3章の検証で、GUIでの手作業は繰り返し失敗し、テキストの直接編集は一度も失敗しなかった。この事実に基づいて自動化する。
- 判断基準 図を目で見て判断するのではなく、
xschem -n -q -xのネットリスト出力を、常に判断の根拠にする。 .schの文法C {...}が部品、N {...}が配線。同じlab=を持つもの同士が電気的につながる。- 端子座標 推測せず、必ずシンボルファイルを
grepして確認する。この確認を省略すると、手作業と同じ失敗を繰り返す。 - 複数行の
valueクォートで囲む処理をスクリプトに固定すれば、GUIでの入力ミスが構造的に起こらなくなる。 - 再現性 スクリプトは、実行するたびに同じ結果を出す。手作業のような、1回ごとのばらつきが無い。