Unifagfのブログ

間違った情報が多いです

sky130のnmosのvds-ids特性を描く

設計ことはじめとしてnmosのvds-ids特性を描きたい。 Vgsに対してパラメトリック解析を行いたいが、ngspiceの機能として備えていないためpythonスクリプトで対応してみた。

実際にやってみた感想としては、記述量が多くなってしまいあまり良い方法でないと思った。 良い方法があれば教えてほしいです。

回路図

f:id:Unifagf:20220205230901p:plain

ネットリスト

** 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ファイルを読み込むために必要。

グラフ

f:id:Unifagf:20220205233631p:plain

ゲート幅が1umより小さいので、チャネル長変調効果が強く出ている。

スクリプト

処理のフロー f:id:Unifagf:20220206095935p:plain

スクリプト本体(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