論理合成(Logic Synthesis)


1. はじめに

計算機を設計する場合,以前は論理設計という行程が存在し, 機能設計が済んだら,その機能を実現する論理回路を人手で設計していた. 一般にどのような論理にすれば高集積で高速になるかという 法則のようなものは無いので,論理設計はかなりの試行錯誤が 必要な行程であった.

このような試行錯誤には回数をこなせるソフトウエアによる自動化が有効である. 今日では論理合成を用いて RTL 記述から論理回路を生成する場合が殆どである. メモリのようにアナログ回路を含む場合やごく一部の高速回路を除いて, 手作業による論理設計は行われなくなった.

ここでは,デバッグの済んだ RTL 記述から, Synopsys 社の Design Compiler を用いて論理合成を行う.

2. Design Compiler による論理合成

まず合成結果やログファイルをまとめて置くために、 サブモジュールが収められたディレクトリが並ぶ層に 論理合成用ディレクトリを作成する。
以下の例では "syn" というディレクトリを作成している。 次に,論理合成に使用するライブラリの設定を行う。 これはスクリプト中で不必要に文字列が長くなるのを防ぐと共に、 違うバージョンのライブラリを使用する場合でも同じスクリプトを利用できるようにするためである。 同様にサブモジュールが収められたディレクトリが並ぶ層に、ライブラリへのシンボリックリンクを張る。
  $ cd "サブモジュールディレクトリが見える階層"
  $ ln -s /usr/synopsys/lib/NangateOpenCellLibrary_PDKv1_3_v2010_12/Front_End/Liberty/DB DB
以上の設定を行うとディレクトリ構造は以下のようになるはずである。
  .
  +--DB -> /usr/synopsys/lib/NangateOpenCellLibrary_PDKv1_3_v2010_12/Front_End/Liberty/DB
  +--/decode
  +--/execute
  |  +--/adder
  |  +--/shifter
  +--/fetch
  |
  +--/top
  |
  +--/syn
次に /usr/eda/syn_sample/ にあるサンプルファイルを論理合成用ディレクトリにコピーする。 count4.v が 4bit カウンタを記述した Verilog HDL コードで、syn_cont4.tcl が tcl の文法で書かれた論理合成用スクリプトである。
 $ cd syn
 $ /usr/eda/syn_sample/count4.v .
 $ /usr/eda/syn_sample/syn_count4.tcl .
このサンプルスクリプトはそのまま動作するようになっているので、 論理合成用ディレクトリで次の様に Design Compiler を起動すると、 count4 が合成される。 このサンプルスクリプトには終了コマンド "exit" が書かれていないので ^d を押すか、exit とコマンドを打って終了する。
 $ dc_shell -f syn_count4.tcl | tee ログファイル名
スクリプト実行時に出力されるログを見て、Error や Warning が無ければ 論理合成が行われている。 このスクリプトは基本的には以下のような手順で条件を設定し、 コンパイルを行う。 各コマンドの働きについては サンプルスクリプトにコメントとして大まかな説明を記述してあるので それを参照して欲しい。

3. GUI を用いた論合成

以上のようにスクリプトで細かい値を指定して論理合成することができるが、 慣れない内は論路合成された結果がどのようなものであるか実感が湧かないであろう。 そこで Design Compiler の GUI フロントエンドである, Design Vision を用いて論理合成を試してみる. 論理合成用ディレクトリで次の様に Design Vision を起動する。
  $ design_vision
メインウインドウが現れるので, ここでライブラリや制約条件を設定したり、 Verilog HDL のソースコードを読み込んだりできるが、 非常に煩雑な作業となるので、GUI から先ほどのスクリプトを呼び出すことにする。 File メニュー以下に execute script というのがあるので、 これを選択し動作させたいスクリプトを指定する。

読み込みが始まり,log を表示するウィンドウが現れる. うまく行けば,Logical Hierachy と書かれたウィンドウに モジュールが示される. そのモジュールを選択し、右クリックもしくは Schematic メニューで Schematic View を選べば、回路図が示される。 メニューバー上のボタンやマウスホイールを操作し、 回路図を拡大縮小して合成結果を見てみよう。 なお、この回路図は階層表現もでき、 サブモジュールがある場合は四角いブロックとして表示されるので、 それをダブルクリックする事で下の階層に降りることができる。

4. 自分の作成したモジュールの合成

サンプルの合成が上手く行ったら、スクリプトを改造して自分の作成したモジュールを合成してみよう。 スクリプト中の read_verilog の所を書き換えれば別のファイルを読み込むこともできるし、 書き並べれば複数のファイルを読み込むこともできる。 ただし、大きな配列をもったメモリは 論理合成が極めて難しく事実上不可能なので、それは読み込まないようにする。 複数のモジュールを読み込んだ後、 current_design コマンドで最上位モジュールを指定して論理合成すると、 サブモジュールは自動的に展開される。 サブモジュール展開にはいくつかの方法があるが、 この演習程度の大きさなら、特に考えること無く標準の設定で大丈夫である。 少しずつスクリプトを変えて実行し、Error や Warning が出ていないかを 確認しながらできるだけ多くのモジュールを論理合成してみよう。 ここで、あるモジュールから呼び出しているモジュールが Design Compiler に読み込まれて いない時、Warning メッセージが出るが、 それが意図して読み込んでいないモジュールであれば、その Warning は無視してかまわない。

dc_shell について

5. 階層的合成

多くの場合,RTL 記述は複数の階層とモジュールを持っているはずである. この階層をもった構造を論理合成するに当たって,いくつかの手法がある.

5.1 トップダウン合成

最も単純な方法は,一度に全てのモジュールを読み込み, 最上位モジュールを current design として指定し,合成を行う方法である. Design_compiler は階層化されたサブモジュールを発見すると, 自動的に合成を行う. 小規模な回路に適した方法である.

5.2 ボトムアップ合成

大規模な回路を一度に合成すると良い結果が得られない事がある. また,合成時間が長くなる. そこで,サブモジュール毎に合成を行う. 上位モジュールを合成する場合には, これらの合成されたサブモジュールを読み込んで微調整のみを行う.

ここで問題になるのは,下位モジュールの合成時にはモジュール間の 遅延時間が明確でない事である. このモジュール間の遅延を設定するために,幾つかの方法がある. 例えば上記 4 節で設定したように,手作業でおおよその値を推定する方法がある. また,一度おおよその値を設定して論理合成し, 上位モジュールの合成結果からモジュール間の遅延値を計算し直すという手法がある. これらの手法は,明確に区別できるものではなく,単純に 合成済み下位モジュールを読み込んで, トップモジュールを指定して合成するだけでも合成時間の短縮等の効果が得られる.

本演習では,手作業でモジュール間遅延を設定して下位モジュールを合成する. 各々の合成が完了したら,合成済み下位モジュールを読み込んで, 上位モジュールを current design に指定して全体を合成し直す手法をとる. 次のように read コマンドを使えば合成結果ファイルを直接読み込むことができる.

read_db {if.db, id.db}

5.3 複数インスタンスの解決

汎用レジスタファイルのように,同一の構造をもつインスタンスが, 複数回現れる事がある. この場合,各々のインスタンスに別々のハードウエアを割り当てる必要がある. この割り当ての手法に二つある.

5.3.1 uniquify

uniquify コマンドを使うと,全ての同一構造インスタンスに, 別々の design 名を割り当て,全く別の回路として認識される. 各々回路は全く関連が無く,独自の条件で合成される. 合成時間が長くなってしまうが,良い結果が得られることがある.

5.3.2 同じ構造をコピーする.

レジスタファイルのように, 各々の条件に差があまりないと言うことが判っている場合は, 同じ回路をコピーした方が効率がよい. この場合,set_dont_touch コマンドを使い,次のように指定すると, 一度合成された構造が弄られることなく,同じ回路がコピーされる. ここで,各インスタンス名の起点は current_design であり,"/"で 下位階層のインスタンス名を指定する.
set_dont_touch {g_reg/r00 g_reg/r01 g_reg/r02}
規則的構造を持つ場合に有効な手法である.

5.4 階層構造の破壊

階層的に RTL を記述した方が,可読性が高く理解しやすい. しかし,論理合成を考えると,あまり細かく階層を分けるのは考え物である. 階層間では最適化が働かないからである. 特に,小規模なサブモジュールが存在するときには問題となる. そこで,合成時に階層を破壊して一様な回路として合成する 手法が設けられている.次のように ungroup コマンドを使って階層を破壊する.
ungroup {add01 add02 mul01}
サブモジュールが小規模であると予測できるときには, 積極的に ungroup しよう. Design_Compiler がライブラリを呼ぶことによって生じたサブモジュール等も ungroup した方が良い結果が得られることが多い. Design_Compiler はしばしば DW と付くサブモジュール(注2)を生成するので, これを ungroup するには,一度コンパイルした後, 次のように指定する.2 行目はモジュール中使っていないセルのポートを消去するように 指定するコマンドである.
set_ungroup [find cell *DW*]
remove_unconnected_ports -blast [find cell "*"]
(注2) これは DesignWare と呼ばれる演算回路合成ツールが生成する モジュールである.
(注3) 小さなモジュールだからと言って,常に ungroup した方が 良い結果が得られるとは限らない. レジスタファイルのように規則的構造を持つときは, その構造を残した方が配置配線に有利となるかもしれない.
(注4) 階層の破壊は 5.3 節複数インスタンスの参照解決にも使用することができる.

6. 課題


注意
このような CAD のログファイルや出力ファイルは意外と大きくなる. 制限にかかりそうだと思ったら早めに消すこと. スクリプトが有れば,また作成できる.