sky130のnmosのvds-ids特性を描く
設計ことはじめとしてnmosのvds-ids特性を描きたい。 Vgsに対してパラメトリック解析を行いたいが、ngspiceの機能として備えていないためpythonスクリプトで対応してみた。
実際にやってみた感想としては、記述量が多くなってしまいあまり良い方法でないと思った。 良い方法があれば教えてほしいです。
回路図
ネットリスト
** sch_path: /home/unifagf/work/xschem/tutor/test2/test2.sch **.subckt test2 XM1 net2 net1 GND GND sky130_fd_pr__nfet_01v8 L=0.15 W=1 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' + pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' + sa=0 sb=0 sd=0 mult=1 m=1 V1 net2 GND 1.8 V2 net1 GND VG **** begin user architecture code .lib /home/unifagf/skywater/skywater-pdk/libraries/sky130_fd_pr_ngspice/latest/models/sky130.lib.spice tt .dc V1 0 1.8 0.1 .parameter VG=1.8 .save all .control set filetype=ASCII .endc **** end user architecture code **.ends .GLOBAL GND .end
set filetype=ASCIIは、rawファイルをbinaryではなくASCIIで出力させるためのコマンド。 spyci(https://pypi.org/project/spyci/)でrawファイルを読み込むために必要。
グラフ
ゲート幅が1umより小さいので、チャネル長変調効果が強く出ている。
スクリプト
処理のフロー
スクリプト本体(run.py)
#!/usr/bin/env python import argparse import subprocess import itertools import os import os.path import numpy as np import pandas as pd import sys import re from spyci import spyci import matplotlib.pyplot as plt import shutil # print(' '.join([f'"{s}"' for s in sys.argv])) scriptdir = os.path.dirname(sys.argv[0]) # pythonスクリプトがおいてあるディレクトリ # 引数の設定解析 parser = argparse.ArgumentParser( description='Run parametric analysis from xschem file.') parser.add_argument('schema', help='Original xschem file.') parser.add_argument('-c', '--clean', action='store_true', help='Clean previous work directories.') parser.add_argument('-n', '--netlist', action='store_true', help='Generate parameterized netlists.') parser.add_argument('-s', '--simulation', action='store_true', help='Run simulations.') parser.add_argument('-r', '--result', action='store_true', help='Export results.') parser.add_argument('-o', '--output', nargs='?', default=scriptdir, help='Specify output directory') args = parser.parse_args() # ディレクトリ生成 schname = os.path.splitext(os.path.basename(args.schema))[0] # 拡張子なしのファイル名を取得 netdir = os.path.join(args.output, 'tmp') rawdir = os.path.join(args.output, 'tmp') logdir = os.path.join(args.output, 'tmp') retdir = os.path.join(args.output, 'result') if args.netlist: os.makedirs(netdir, exist_ok=True) if args.simulation: os.makedirs(rawdir, exist_ok=True) os.makedirs(logdir, exist_ok=True) if args.result: os.makedirs(retdir, exist_ok=True) # パラメータ設定 params = {'VG': ['{:01.1f}'.format(n) for n in np.linspace(0, 1.8, 10)]} # パラメータの組み合わせ生成 keys = list() param_comb = list() df = pd.DataFrame() if len(params) >= 0: # パラメータがある場合 keys = list(params.keys()) param_comb = list(itertools.product(*[params[k] for k in keys])) df = pd.DataFrame(param_comb, columns=keys) df['ngspice_net'] = '-' df['ngspice_raw'] = '-' df['ngspice_out'] = '-' for index, row in df.iterrows(): filename = f'{schname}_' + "_".join([f'{k}{str(row[k])}' for k in keys]) df.at[index, 'ngspice_net'] = os.path.join(netdir, f'{filename}.net') df.at[index, 'ngspice_raw'] = os.path.join(rawdir, f'{filename}.raw') df.at[index, 'ngspice_out'] = os.path.join(logdir, f'{filename}.out') else: # パラメータがない場合 filename = f'{schname}' _d = {'ngspice_net': os.path.join(netdir, f'{filename}_.net'), 'ngspice_raw': os.path.join(rawdir, f'{filename}_.raw'), 'ngspice_out': os.path.join(logdir, f'{filename}_.out')} df = pd.DataFrame(_d) def subprocess_run(cmd): """ サブプロセスを実行する関数 """ result = subprocess.run(cmd, stdout=subprocess.PIPE, encoding='utf-8', shell=True) return result # ファイル削除 if args.clean: shutil.rmtree(netdir) shutil.rmtree(rawdir) shutil.rmtree(logdir) shutil.rmtree(retdir) # ネットリスト生成 if args.netlist: original_netlist = os.path.join(netdir, f'orig_{schname}.net.spice') # 回路図から生成したネットリスト cmd = 'xschem --netlist ' + \ f'--netlist_path "{netdir}" ' + \ f'--netlist_filename "{original_netlist}" ' + \ f'--quit "{os.path.abspath(args.schema)}"' print(cmd) proc = subprocess_run(cmd) # パラメータを変更したネットリストを生成 with open(original_netlist, 'r', newline='') as rf: print() orig_lines = rf.readlines() for index, row in df.iterrows(): with open(row['ngspice_net'], 'w') as wf: for line in orig_lines: s = line for k in params.keys(): m = re.match(r'^\.parameter\s+' + k + r'=', s) # 「.parameter hoge=...」という行を置換 if m is not None: v = row[k] s = f'.parameter {k}={v}\n' wf.write(s) print('{} generated.'.format(row['ngspice_net'])) # simulation実行 if args.simulation: print('run simulation') for index, row in df.iterrows(): cmd = 'ngspice --autorun --batch ' + \ '--output="{}" '.format(row['ngspice_out']) + \ '--rawfile="{}" "{}"'.format(row['ngspice_raw'], row['ngspice_net']) print(cmd) result = subprocess_run(cmd) # グラフを描画 if args.result: print(df) # パラメータ一覧を表示 print(spyci.list_vars(df.at[0, 'ngspice_raw'])) # 信号名一覧を表示 fig, ax = plt.subplots() for index, row in df.iterrows(): data = spyci.load_raw(row['ngspice_raw']) x = np.real(data['values']['v(v-sweep)']) y = -np.real(data['values']['i(v1)']) ax.plot(x, y, label='VG={}'.format(row['VG'])) ax.set_xlabel('Vds (V)') ax.set_ylabel('Ids (A)') ax.legend(loc='upper right') ax.grid(visible=True) ax.set_xlim([0, 1.8]) graphimg = os.path.join(retdir, 'nmos_idsvds.png') plt.savefig(graphimg)
使い方
./run.py -nsr test2.sch