####1.背景
手っ取り早く回路図を描くのは、パワーポイントなどを利用したり、EDAツールの回路を画面コピーしたりするのが個人的には多い。同じ専門仲間では少々のグリッドずれや誤記でも、回路の本質を理解しあえるが、公式の文書や専門外に相手へ示すには正確な描画が望ましい。
そのようなきっかけから探して見つけたpythonのライブラリがschemdrawだが、基板回路を想定した素子が主で、IC回路で用いられるPMOS,NMOSトランジスタはライブラリにはなく、試しに自分で作ってみた。
####2.目標
定評のあるProf.B.RazaviのCMOSアナログ回路の教科書の回路図に近いMOSトランジスタ。
####3.環境
windows 10(64bit)
Jupyter Notebook 6.1.4
Python 3.8.5
schemdraw 0.11
####4.方針
拡張性や互換性を考慮して独自には描かず、schemdrawの既存のPFet、NFetのスクリプトをベースに変更する。
####5.MOSトランジスタ記述に必要な情報
Jupyter Notebookでは、"!"を行頭につけて、
!pip show schemdraw
でschemdrawの格納先のパスがわかる。例えば、
C:\Users\xxx...xx\anaconda3\lib\site-packages\schemdraw
で、その下位ディレクトリには要素となる素子の記述が複数ディレクトリとファイルで存在するので、その中から該当するファイルtransistors.pyを見つけた。
素子はclassで定義されており、NFetおよびPFetを読み解くと入力端子であるゲート端子が右側になっているが、回路図の入力信号は基本的に左側から右側なので、左右反転とした。
長さの単位としてreswidthとしているが、これはファイルtwoterm.pyに記載のある抵抗素子の定数に由来し、ここではスクリプト内に転記する。
なお、bulkは、基板を表す矢印で、回路記述の中でNMOS(bulk=1)と記述すれば矢印が表示される。
回路記述部分は、d = schemdraw.Drawing()に記述する。2次元の回路網を1次元のスクリプトに置換するため、節として3つ(基板を含めれば4つ)に分岐するMOSトランジスタや配線の分岐点、それらの接続する配線を枝として探索木を形成する。回路図で左上のトランジスタを起点として探索木を想定し、隣接する節を網羅的に辿っていくといったルールで書いた。pop()およびpush()でジャンプできるが、可読性が悪くなるので多用しないのが望ましい。
import schemdraw
import schemdraw.elements as elm
from schemdraw.segments import *
reswidth = 1.0 / 6 # Full (inner) length of resistor is 1.0 data unit
fets = reswidth*2
fetw = reswidth*4
feth = reswidth*5
fetl = feth/2
fetgap = reswidth*1.5
fetr = reswidth*0.7 # Radius of "not" bubble
class PMOS(elm.Element):
def __init__(self, *d, bulk: bool=False, **kwargs):
super().__init__(*d, **kwargs)
self.segments.append(Segment([(0, 0), (0, -fetl)]))
self.segments.append(SegmentArrow((0, -fetl), (-fets, -fetl), headwidth=0.2))
self.segments.append(Segment([(-fets, -fetl-fetw),(0, -fetl-fetw),(0, -2*fetl-fetw)]))
self.segments.append(Segment([(-fets, -fets),(-fets, -fets-feth)],lw=4))
self.segments.append(Segment([(-fets-fetgap, -fetl),(-fets-fetgap, -fetl-fetw)],lw=4))
self.segments.append(Segment([(-fets-fetgap-fetr, -fetl-fetw/2),(-fets-fetgap-fetl-fetr, -fetl-fetw/2)]))
self.anchors['drain'] = (0, -2*fetl-fetw)
self.anchors['source'] = (0, 0)
self.anchors['gate'] = (-fets-fetgap-fetl-fetr, -fetl-fetw/2)
self.params['drop'] = (0, -2*fetl-fetw)
self.params['lblloc'] = 'rgt'
if bulk:
self.segments.append(SegmentArrow((-fets, -fetl-fetw/2),(0, -fetl-fetw/2), headwidth=.2))
self.anchors['bulk'] = (0, -fetl-fetw/2)
class NMOS(elm.Element):
def __init__(self, *d, bulk: bool=False, **kwargs):
super().__init__(*d, **kwargs)
self.segments.append(Segment([(0, 0), (0, -fetl), (-fets, -fetl)]))
self.segments.append(SegmentArrow((-fets, -fetl-fetw),(0, -fetl-fetw), headwidth=.2))
self.segments.append(Segment([(0, -fetl-fetw),(0, -2*fetl-fetw)]))
self.segments.append(Segment([(-fets, -fets),(-fets, -fets-feth)],lw=4))
self.segments.append(Segment([(-fets-fetgap, -fetl),(-fets-fetgap, -fetl-fetw)],lw=4))
self.segments.append(Segment([(-fets-fetgap-fetr, -fetl-fetw/2),(-fets-fetgap-fetl-fetr, -fetl-fetw/2)]))
self.anchors['source'] = (0, -2*fetl-fetw)
self.anchors['drain'] = (0, 0)
self.anchors['gate'] = (-fets-fetgap-fetl-fetr, -fetl-fetw/2)
self.params['drop'] = (0, -2*fetl-fetw)
self.params['lblloc'] = 'rgt'
if bulk:
self.segments.append(SegmentArrow((0, -fetl-fetw/2),(-fets, -fetl-fetw/2), headwidth=.2))
self.anchors['bulk'] = (0, -fetl-fetw/2)
d = schemdraw.Drawing()
d += (pmos01 := PMOS().label('pmos01', fontsize=9,ofst=(0.1,0)))
d += elm.Line().left().at(pmos01.gate).length(0).label('$V_{b}$', 'left')
d += elm.Line().up().at(pmos01.source).length(0.75).label('$V_{dd}$', 'right',ofst=(0,-0.75))
d += elm.Vdd
d += (dot01 := elm.LineDot().down().at(pmos01.drain).length(0.5))
d += elm.Line().right().length(0.5).label('$V_{out}$', 'right')
d += (net01 := elm.Line().at(dot01.end).down().length(0.5))
d += (nmos01 := NMOS().label('nmos01', fontsize=9,ofst=(0.1,0)).at(net01.end).anchor('drain').theta(0))
d += elm.Line().right().at(nmos01.gate).length(0.5).label('$V_{in}$', 'left')
d += elm.Line().down().at(nmos01.source).length(0.5)
d += elm.Ground
d.draw()
#d.save('schematic01.jpg')
d = schemdraw.Drawing() 以降を下記のOpeampの記述に変更する。
d = schemdraw.Drawing()
d += (pmos01 := PMOS().label('pmos01', fontsize=9,ofst=(-2.5,0)).reverse())
d += (dot01 := elm.LineDot().at(pmos01.gate).right().length(0.75))
d += elm.Line().down().at(dot01.end).length(1.25)
d += elm.Line().left().length(1.75)
d += elm.Line().right().at(dot01.end).length(1.0)
d += elm.Line().up().at(pmos01.source).length(0.75).label('$V_{dd}$', 'right',ofst=(0,-0.75))
d += elm.Vdd
d += (dot02 := elm.LineDot().down().at(pmos01.drain).length(0.5))
d += (net01 := elm.Line().at(dot02.end).down().length(0.5))
d += (nmos01 := NMOS().label('nmos01', fontsize=9,ofst=(0,0)).at(net01.end).anchor('drain').theta(0))
d += elm.Line().left().at(nmos01.gate).length(0.5).label('$V_{in+}$', 'left')
d += (dot03 := elm.LineDot().right().at(nmos01.source).length(1.75))
d += (nmos02 := NMOS().label('nmos02', fontsize=9,ofst=(0,0)).at(dot03.end).anchor('drain').theta(0))
d += elm.Line().left().at(nmos02.gate).length(0.5).label('$V_{b}$', 'left')
d += elm.Line().down().at(nmos02.source).length(0.1)
d += elm.Ground
d += (net02 := elm.Line().right().at(dot03.end).length(1.75))
d += (nmos03 := NMOS().label('nmos03', fontsize=9,ofst=(-2.5,0)).at(net02.end).anchor('source').theta(0).reverse())
d += elm.Line().right().at(nmos03.gate).length(0.5).label('$V_{in-}$', 'right')
d += (dot04 := elm.LineDot().up().at(nmos03.drain).length(0.5))
d += elm.Line().right().at(dot04.end).length(0.5).label('$V_{out}$', 'right')
d += (net03 := elm.Line().up().at(dot04.end).length(0.5))
d += (pmos02 := PMOS().label('pmos02', fontsize=9,ofst=(0.1,0)).at(net03.end).anchor('drain').theta(0))
d += elm.Line().up().at(pmos02.source).length(0.75).label('$V_{dd}$', 'right',ofst=(0,-0.75))
d += elm.Vdd
d.draw()
#d.save('schematic01.jpg')
####6.参考サイト
公式サイト
https://schemdraw.readthedocs.io/en/latest/index.html
####7.考察
私の理解の範囲では、グループ化や階層化ができないため、数珠繋ぎで素子を記述していかなくてはならなく、毎度ゼロからの作成は時間がかかる。視認性や作成労力の観点で、回路規模もせいぜい数十素子ぐらいが適切である。あるいは日ごろから要素回路を用意しておくのがよさそうである。
####8.感想
アナログ回路設計ではGUI設計が主流のため、ソフトウェアとの縁が薄いが、python利用や学習のきっかけにはschemdrawでのお絵描きは適切な題材かと思う。classでトランジスタなどの素子が定義されており、classの理解の入り口にもなる。とはいえまだ私も理解が未熟ですが。。。