本記事の後半部分は以前公開していたものから大幅に変更しています。
m4 について
Circuit macros を使う場合、m4 の知識が無くてもだいたいどうにかなりますが、有ればより理解が深まり、柔軟に書くこともできます。
ということで、まずは簡単に m4 について説明しておきましょう。m4 はマクロプロセッサで、C プリプロセッサがやっていることと似ています。極端にいえば、define する / 展開する、だけです。m4 について一切知識が無いという方は手始めに m4 マクロプロセッサ (Oracle) あたりを読むのが良いかと思います。引用符がバッククォート + シングルクォート `
'
である、という点に注意すれば、そんなに難しくないでしょう。
m4 は最も有名なところでは、autoconf (いつもの configure とか生成するやつ) で使われています。configure の元になる configure.in や configure.ac はいずれも m4 マクロです。
Full adder with comments
で、肝心の論理回路の書き方ですが、Quick Start として第1回の全加算器のファイルにコメントを付けたものを以下に載せます。あとは、本家のマニュアルを眺めたり、本家のサンプルを見て、それっぽいものを探し (下の方へ行くと「Circuit とは…」という気分になりますが)、その .m4 ファイルを参照する、というのがてっとり早いと思います。
dnl とりあえず最初に .PS, 最後に .PE を書く
.PS
dnl liblog.m4 (論理回路ライブラリ) を使うときにはこれを書く
log_init
dnl AND1 は名前。この名前で AND_gate を描く
AND1: AND_gate
dnl 線を、左の方向へ、AND の幅の 2 倍分の長さで、AND1 の入力端子 In1 (上) から引き、
dnl 文字列 "$a_n$" を右揃えで書く
dnl from が無い場合は現在の地点から描く
line left AND_wd*2*L_unit from AND1.In1; "$a_n$" rjust
line left AND_wd*2*L_unit from AND1.In2; "$b_n$" rjust
dnl XOR1 という名前で、XOR_gate を AND1 の位置から AND の高さの 2 倍分下にずらして描く
XOR1: XOR_gate at AND1-(0,AND_ht*2)*L_unit
dnl 左の方向へ、AND の幅の半分の長さの線を、XOR1 の入力端子 In1 から引き、
dnl さらに上へ AND の高さの 2 倍分の長さで引く
dnl そして点を打つ
line left AND_wd/2*L_unit from XOR1.In1 \
then up AND_ht*2*L_unit ; dot
line left AND_wd*L_unit from XOR1.In2 \
then up AND_ht*2*L_unit ; dot
AND2: AND_gate at XOR1+(AND_wd*2.5,-AND_ht/4)*L_unit
line from XOR1.Out to AND2.In1
OR1: OR_gate at AND2+(AND_wd*2,AND_ht)*L_unit
dnl 左の方向へ、AND の幅の半分の長さの線を、OR1 の入力 In1 から引き、
line left 0.5*AND_wd*L_unit from OR1.In1
dnl そこから、垂直に AND1 の出力の位置まで線を引き、さらに AND1 の出力まで引く
line to (Here.x, AND1.Out.y) then to AND1.Out
dnl ポイント:
dnl Here.x で現在位置の x 座標を取得、同様に AND1.Out.y で AND1.Out の
dnl y 座標を取得。これで「垂直」に線が引ける
line left 0.5*AND_wd*L_unit from OR1.In2
line to (Here.x, AND2.Out.y) then to AND2.Out
line right AND_wd*L_unit from OR1.Out; "$c_n$" ljust
XOR2: XOR_gate at AND2+(0,-AND_ht*2)*L_unit
line left AND_wd/2*L_unit from AND2.In2
line to (Here.x, XOR2.In2.y); dot
line from XOR2.In2 to (AND1.In1.x - AND_wd*2*L_unit, XOR2.In2.y)
"$c_{n-1}$" rjust
line left AND_wd*L_unit from XOR2.In1
line to (Here.x, AND2.In1.y); dot
line from XOR2.Out to (OR1.Out.x + AND_wd*L_unit, XOR2.Out.y); "$s_n$" ljust
dnl おしまい
.PE
あとはひたすらトライ & エラーです 😉
知っておくと便利なテクニックとして、
{ }
を使うと、現在位置と方向を save/restore できます- loop も使えるので、上手く使うと簡潔に書ける場合があります
- スライドなどで使う場合には、
などとして、全体を大きくしつつ線・交点を太くすると良いと思いますdefine(`L_unit',L_unit*1.5) define(`dotrad_',dotrad_*1.5) linethick_(2.0)
新しい部品を作ってみる
CPU 等を描きたいときに必要になる ALU のシンボルが Circuit macros では用意されていないようなので、それを (なるべく流用できそうな形で) 作成してみました。
divert(-1)
ALU.m4 Arithmetic Logic Unit (for Circuit macros)
define(`ALU_wid',8) `ALU defaults'
define(`ALU_ht',18)
`alu(label, width, height)
args 2 and 3 are L_unit values'
define(`alu',`[
define(`m4ALUwid',`ifelse(`$2',,ALU_wid,((`$2')/(L_unit)))')dnl
define(`m4ALUht',`ifelse(`$3',,ALU_ht,((`$3')/(L_unit)))')dnl
define(`m4Edgesize',0.15*m4ALUwid)dnl
W: (0,0)
C: svec_((m4ALUwid+m4Edgesize)/2,0)
EDGEC: svec_(m4Edgesize,0)
EDGEN: svec_(0,m4Edgesize)
EDGES: svec_(0,-m4Edgesize)
NW: svec_(0,0.5*m4ALUht)
NE: svec_(m4ALUwid,0.3*m4ALUht)
SE: svec_(m4ALUwid,-0.3*m4ALUht)
SW: svec_(0,-0.5*m4ALUht)
Out: svec_(m4ALUwid,0)
Out0: svec_(m4ALUwid,0)
Out1: svec_(m4ALUwid,0.15*m4ALUht)
Out2: svec_(m4ALUwid,-0.15*m4ALUht)
Ctrl1: svec_(m4ALUwid/2,0.4*m4ALUht)
Ctrl2: svec_(m4ALUwid/2,-0.4*m4ALUht)
In1: svec_(0,(0.5*m4ALUht+m4Edgesize)/2)
In2: svec_(0,-(0.5*m4ALUht+m4Edgesize)/2)
line from EDGEC to EDGEN then to NW then to NE then to SE then to SW then to EDGES then to EDGEC
ifelse(`$1',,,`"ifsvg(`svg_small($1,75)',`\scriptsize $1')" at C')
]')
divert(0)dnl
ピンはこのような感じで定義しています:
以下に簡単な使用例を示します。
.PS
darrow_init
log_init
include(`ALU.m4')
ALU: alu(\textbf{ALU},,1.2*ALU_ht*L_unit)
lg_pin(ALU.In1,in1,,w,,0)
lg_pin(ALU.In2,in2,,w,,0)
darrow(from ALU.Out right_ 10*L_unit)
darrow(from ALU.In1.w-(10*L_unit,0) to ALU.In1.w)
darrow(from ALU.In2.w-(10*L_unit,0) to ALU.In2.w)
.PE