コンパイラ指示子を使いこなす

代表的なコンパイラ指示子

シミュレーションのときにいろいろ便利な指示が出来るのがコンパイラ指示子です。これはシミュレータに固有の機能のことがありますが共通のものを紹介します。

コンパイラ指示子とはシミュレーションのコンパイル時に行う処理を指定するものです。

コンパイルとはシミュレータが記述を読み込んだときに行う文法チェックや実行のための準備のことです。

コンパイラ指示子はたくさんの種類がありますが、ここでは代表的な4つを紹介します。

  • すべてのコンパイラ指示子はバッククォートで始まります。
  • `define はテキスト置換を行います。
  • `include はファイルを読み込み、その内容を読み込んだ部分に展開します。
  • `timescale はシミュレーションの時間単位を設定します。
  • `ifdef は条件によってコンパイルする記述を変えます。

これらのコンパイラ指示子はモジュールの外側でも内側でも配置することが出来ます。

コンパイラ指示子はこのほかにも多数ありますが、シミュレータによって独自のものもあります。使用する場合にはマニュアルで確認して使用しましょう。

ここで紹介する4つは共通に使用できるものです。

次のページからこれらを詳しく説明します。

もふねこ

もふねこ:
コンパイラ指示子(`define とか `timescale)って、C言語のマクロみたいなものだよ🐾
特に `timescale 1ns / 1ps はテストベンチを書くときのおまじないだから、忘れずに一番上に書いておこうね!

1. `define (文字列・テキスト置換)

文字列置換を行うコンパイラ指示子が`defineです。

深い階層をもった記述の下位階層の信号を観測する場合インスタンス名をいくつも列挙して記述します。

階層が深いと記述量が増えてきます。

観測する信号がたくさんある場合には記述する手間がかかります。

このようなときに文字列置換を使うと非常に便利です。

文字列置換の定義は`defineのあとに定義文字列と置換文字列を書きます。


// 定義: `define 定義文字列 置換文字列
`define A top_blk.addr_blk
        

例えばこの記述の場合、top_blk.addr_blkという文字列をAという文字列で定義します。

このAを呼び出せば記述量を大幅に減らすことができ、可読性の高い記述になります。


initial begin
    // 展開後: top_blk.addr_blk.signal_name
    $display("%b", `A.signal_name); 
end
        

定義した文字列を呼び出すときにもバッククォートが必要です。

バッククォートを忘れると未定義信号として扱われます。注意してください。

2. `include (ファイルの読み込み)

graph TD subgraph testbench.v TB[module TESTBENCH] INC[`include "target_module.v"] end subgraph target_module.v MOD[module TARGET] end INC -.->|ファイル読み込み| MOD TB -->|インスタンス化| MOD style TB fill:#e3f2fd,stroke:#1976d2; style MOD fill:#e8f5e9,stroke:#388e3c;

記述中にファイルを読み込むコンパイラ指示子が`includeです。

記述の一部を別のファイルにしておき、そのファイルを`includeで読み込みます。

複数の記述で共通に使用するタスクやクロックそのほかの信号をファイルにしておくことで、可読性の高い記述を効率よく作成できます。

例えばテストベンチの記述では各種の信号宣言や検証対象の接続などを記述したファイルをつくります。

タスクを使用している場合はタスクの定義を記述したファイルもつくります。


// testbench.v
`include "define.v"
`include "task.v"

module testbench;
    // デバッグ用の特別な処理や、追加の監視タスクなどを記述
endmodule
        

作成した各種宣言やタスクのファイルを`includeで読みこむようにすれば、大規模な記述でも可読性がよくなります。

シミュレーション実行時はコマンドラインにincludeファイルを書くとエラーになります。

3. `timescale (シミュレーション時間単位)

シミュレーションの時間の単位を設定するコンパイラ指示子が`timescaleです。

シミュレーションの時間単位を実時間に設定します。

設定は`timescaleのあとに実時間/丸め精度という書き方をします。


// 定義: `timescale 実時間 / 丸め精度
`timescale 1ns / 1ps
        

丸め精度とは時間計算の最小単位です。

実時間と同じか小さい値を設定します(実時間≧丸め精度)。

時間計算するときに指定した桁の下の桁を四捨五入します。

実時間や丸め誤差に設定する数値は1、10、100のいずれかで、そのほかの数値は設定できません。時間の単位はps、nsなどを設定できます。それぞれピコセカンド、ナノセカンドなどを表します。

実際の記述ではテストベンチなどの最上位階層ファイルの先頭に記述します。

時間単位は一度設定するとそれ以降に読み込んだすべてのファイルに有効です。

したがってシミュレーション実行時には常に最上位階層から読み込みます。

読み込む順番を間違えると時間単位の設定されないモジュールが発生するため、実行できなくなります。

RTLシミュレーションでは遅延時間を考慮しないので、`timescaleの設定は必要ありません。

ゲートレベルでの評価は遅延を含んだシミュレーションですから必ず設定してください。そのとき設定する値はシミュレーションライブラリに依存します。

ライブラリを確認して設定しましょう。

4. `ifdef (条件付きコンパイル)

記述に対するコンパイルの実行を制御するコンパイラ指示子が`ifdefです。

一つの記述の中に違う処理をする二つの記述をかき、シミュレーションするときにどちらの記述をコンパイルするか制御します。

二つの記述を区別するために`ifdefのあとにマクロ名を書きます。else以降は省略することもできます。

`ifdefをネスティングさせることもできます。


`ifdef MACRO_NAME
    // 記述1
`else
    // 記述2
`endif
        

記述1を実行する場合には、シミュレータ実行コマンド、ファイルリストに続きコンパイルオプションとして+define+の後にマクロ名を書いて実行します。


> simulator_command file_list +define+MACRO_NAME
        

記述2を実行する場合にはシミュレータ実行コマンドの後に、ファイルリストを書くだけです。コンパイルオプションをつけません。


> simulator_command file_list
        

例えば3種類のテストベンチを一つのファイルに記述し、コンパイルオプションによってテストベンチを選択するという使い方が出来ます。

5. `define と parameter の違い

`defineとparameterの違いを理解して使い分けましょう。

定数の設定は`defineもparameterも同じように使えます。


// `define の場合
`define MAX_COUNT 8

// parameter の場合
parameter MAX_COUNT_P = 8;
        

`defineでは呼び出すときにバッククォートを忘れないようにしましょう。

文字列の設定では`defineは使えますが、parameterでは問題があります。

parameterで文字列を宣言しても、下位層アクセスの中で使用すると文法エラーになってしまいます。


`define PATH top.sub_module

initial begin
    // 展開後: top.sub_module.sig となり正常にアクセス可能
    $display("%b", `PATH.sig); 
end
                

parameter PATH = "top.sub_module";

initial begin
    // 展開後: "top.sub_module".sig となり文法エラー
    $display("%b", PATH.sig); 
end