SPICEの小瓶(41) LTspiceでNETリストをバッチ・シミュレーションからCSV

Joseph Halfmoon

前回LTspiceとテキストファイルの間のデータ入出力をやってみました。しかし思い起こせば昔のサーキット・シミュレータというとネットリストを手で打ち込んでました。そして現在のLTspiceでもテキストファイル(ネットリスト)で回路記述を読みこみ可能です。ついでに結果をCSVファイルに輸出するスクリプトも作成。

※「SPICEの小瓶」投稿順インデックスはこちら

※ Analog Devices, Inc. LTspice を使用させていただいて動作確認しております。今回使用のバージョンは以下です。

XVII(x64) (17.0.37.0)

SPICEネットリスト

遠い昔、回路シミュレータとして有名なSPICEはUCバークレーで開発されたそうな。このお惚け老人が業界に入ったころには既に古典となっていたくらい古い古いソフトです。MOSモデルのレベルの扱いなど、それこそ半導体会社毎にSPICEに独自の改良を加えて各様に発展をとげていったもの。

現在は知らず、当初はFORTRANで書かれたプログラムだった筈。回路図キャプチャだの、波形ビューワーだのという便利なものはまだ存在せず(存在したかも知れんけど、キャラクタ端末しか使えんかったし)、テキストエディタで回路図(ネットリスト)を編集し、バッチで実行、結果はラインプリンタに印字して眺めておりました。

ネットリストでの抵抗の書き方などあげればこんな感じ。

R1 N001 N002 100k

抵抗なら頭の文字はRとか、デバイス毎に決まってます。その直後に固有の数字を書いて識別します。そして回路のノード(配線)にはテキトーな番号をふっていきます。唯一のお約束は「0」という番号のノードはGNDだということです。こいつが計算するときも基準電位ね。上記ではN001という配線とN002という配線の間にR1という抵抗が接続されておると。そして値は100kΩです。なお大昔のSPICEでは頭にNとかも書かずただ数字でノードを示した記憶。でもモダンなSPICEでは文字も使えると。

こんな感じに1行づつデバイスを並べていけば、いかなる回路も記述できる筈。そしてこのSPICE伝統のネットリスト記法は現在のLTspice(そして他のSPICEでも)有効であります(ちょっと方言ありそうだけれども。)以下、LTspiceのHELPファイルから1か所引用させていただきましたRsyntaxEC

マーカ部分をご覧いただければ、伝統のシンタックスがそのまま残っておりますぞ。

よってLTspiceにても、回路図を描くことなく、テキストエディタでネットリストを記述してシミュレーションすることが可能っす。

たとえば、以下の回路図は、schema

以下のようなネットリストになります。簡単?

* C:\proj\circuit\spice_batch\RST.asc
Vpower N001 0 3.3
R1 N001 RST 100k
C1 RST 0 100n
.tran 0.04 startup
.backanno
.end
LTspiceのバッチ・シミュレーション

バッチ・シミュレーションについては、三共社の渋谷先生(LTspiceの神的存在)のページに詳しく書かれています。

https://www.sankyosha.co.jp/engineer_doc/pdf/2008-2_ASCII-Output.pdf

ざっくりまとめてしまえば、

    1. LTspiceの実行ファイル XVIIx64.exe にPATHを通し
    2. ネットリスト XXX.net を作製済であれば、以下で実行
$ XVIIx64 -b -ascii XXX.net

であります。なお、.asc 拡張子の回路図からネットリストをバッチで生成することも可能で、その場合は、上記のバッチ実行前に以下のようにせよと。

$ XVIIx64 -netlist XXX.asc

上記でネットリストのテキストファイル XXX.NET が生成されます。

なお、バッチシミュレーションの時に -b というバッチ指定に加えて -ascii と指定していることがミソです。LTspiceのGUI使ってシミュレーションしているときも、xxx.rawといったシミュレーション結果ファイルが生成されますが、中身の肝心の部分はバイナリで処理しずらいです。そこで-asciiとするとフツーのテキストで出力してくれます(データ量が膨れるけど。)波形ビューワーとしてLTspiceを使うのなら-ascii不要ですが。

しかしここまで来たのなら、RAWファイルをCSV化して、表計算ソフトでもなんでも読み込んでグラフにしてみるか、という感じっす。そこで作成してみました。-ascii で出力されたRAWファイルをCSV化するPythonスクリプトです。

暫定版のソース全文、末尾に掲げました。

SPICEの小瓶(40) LTspiceのテキスト形式入出力、コマケー話なんだが へ戻る

SPICEの小瓶(42) 2SC1815のスパイスパラメータ「2種」、実機と比べてみた へ進む

使用したPythonソース(暫定版)
#!/usr/bin/python
# coding: utf-8
### @file  raw2csv.py
### @brief LTspice raw(ascii) file to csv converter
###
### @author: Joseph Halfmoon
### @date:   April 9, 2024
###
"""
LTspice raw(ascii) file to csv converter v0.0

input
=====
--RAW LTspice raw(ascii) format input file name.
--CSV CSV format output file name.
-V show Version.

output
======
csv format file.

"""
import argparse
import sys
import unittest

versionSTR = "raw2csv.py v0.0"

def parse2int(st):
    """parse string to int
    
    Returns at Fail: None
    """
    try:
        work = st.split(":")
        num = int(work[1])
    except:
        return None
    return num

def processraw(rfile, wfile):
    with open(wfile, "w") as ofile:
        nV = 0
        valflag = False
        vflag = False
        vlist = []
        vcnt = 0
        recnum = 0
        with open(rfile, "r") as ifile:
            line = ifile.readline()
            while line:
                if valflag:
                    work = line.split()
                    if len(work) == 3:
                        vlist.append(work[1])
                        vcnt += 1
                if vflag:
                    work = line.split()
                    if len(work) == 2:
                        recnum = 1
                        ofile.write(work[0])
                        ofile.write(",")
                        ofile.write(work[1])
                        ofile.write(",")
                    if len(work) == 1:
                        recnum += 1
                        ofile.write(work[0])
                        if recnum == nV:
                            ofile.write("\n")
                        else:
                            ofile.write(",")
                if line.startswith("No. Variables:"):
                    nV = parse2int(line)
                if line.startswith("Variables:"):
                    valflag = True
                if line.startswith("Values:"):
                    valflag = False
                    vflag = True
                    reccnt = 0
                    if nV != vcnt:
                        return False
                    else:
                        ofile.write("recnum,")
                        for item in vlist[:nV-1]:
                            ofile.write(item)
                            ofile.write(",")
                        ofile.write(vlist[nV-1])
                        ofile.write("\n")
                line = ifile.readline()
    return True

def main():
    """main program.
    
    process options
    """
    parser = argparse.ArgumentParser(description=versionSTR)
    parser.add_argument('--RAW', nargs=1, help='raw(ascii) file name')
    parser.add_argument('--CSV', nargs=1, help='csv file name')
    parser.add_argument('-V', dest='VERSION', help='show Version, then exit', action='store_true', default=False)
    args = parser.parse_args()

    if args.VERSION:
        print( versionSTR )
        sys.exit(0)
    rfile = "sim.raw"
    if args.RAW is not None:
        rfile = args.RAW[0]
    wfile = "sim.csv"
    if args.CSV is not None:
        wfile = args.CSV[0]

    if processraw(rfile, wfile):
        print("File successfully processed.")
    else:
        print("ERROR:")
    sys.exit(0)

if __name__ == "__main__":
    main()