読み込み中...

Verilogにおける$monitorの基本的な使い方と活用例13選

$monitor 徹底解説 Verilog
この記事は約35分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラム(回路記述)の基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

●Verilogの$monitorとは?デバッグの強い味方

Verilog言語は、デジタル回路設計において欠かせないツールです。

その中でも$monitorは、デバッグ作業を大幅に効率化する非常に重要な機能です。

初めて聞く方もいるかもしれませんが、心配する必要はありません。

順を追って丁寧に説明していきますので、ゆっくりと理解していきましょう。

$monitorは、Verilogのシステムタスクの一つで、シミュレーション中の変数の値を継続的に監視し、その変化を自動的に表示する機能を持っています。

簡単に言えば、回路の動作を観察する「目」のような役割を果たすのです。

○$monitorの基本機能と使用目的

$monitorの基本的な機能は、指定した変数の値が変化するたびに、その新しい値を自動的に表示することです。

回路設計者にとって、この機能は非常に有用です。なぜなら、回路の動作を細かく追跡し、予期せぬ動作や問題点を素早く発見できるからです。

例えば、クロック信号やデータ信号の変化を監視したい場合、$monitorを使用すれば、それらの信号の状態変化を逐一確認できます。

手動でチェックする手間が省け、デバッグ作業の効率が飛躍的に向上するのです。

○$displayとの違いと使い分け

$monitorと似た機能を持つ$displayという別のシステムタスクがあります。

両者の違いを理解することは、効果的なデバッグを行う上で重要です。

$displayは、呼び出されたタイミングで1回だけ指定された情報を表示します。

一方、$monitorは一度設定すると、シミュレーション中に監視対象の変数が変化するたびに自動的に表示を更新します。

$displayは特定のポイントでの値を確認したい場合に適しています。

例えば、ある条件が満たされた時点での変数の値を知りたい場合などです。

対して$monitorは、信号の連続的な変化を追跡したい場合に適しています。

使い分けの基本は、「一回限りの表示なら$display、継続的な監視なら$monitor」と覚えておくとよいでしょう。

○サンプルコード1:基本的な$monitor使用例

では、実際に$monitorを使用したコード例を見てみましょう。

ここでは、簡単なカウンターの動作を監視する例を紹介します。

module counter_test;
  reg clk;
  reg reset;
  wire [3:0] count;

  // カウンターモジュールのインスタンス化
  counter dut (.clk(clk), .reset(reset), .count(count));

  // クロック生成
  always #5 clk = ~clk;

  initial begin
    clk = 0;
    reset = 1;
    #10 reset = 0;

    // $monitorの設定
    $monitor("Time=%0t, Count=%b", $time, count);

    #100 $finish;
  end
endmodule

// 4ビットカウンターモジュール
module counter (
  input clk,
  input reset,
  output reg [3:0] count
);
  always @(posedge clk or posedge reset) begin
    if (reset)
      count <= 4'b0000;
    else
      count <= count + 1;
  end
endmodule

このコードでは、4ビットのカウンターを定義し、その動作を$monitorで監視しています。

$monitorは時間とカウント値を表示するように設定されています。

実行結果は次のようになります。

Time=0, Count=xxxx
Time=10, Count=0000
Time=15, Count=0001
Time=25, Count=0010
Time=35, Count=0011
Time=45, Count=0100
...

結果を見ると、時間の経過とともにカウント値が変化していく様子が分かります。

このように$monitorを使用すると、回路の動作を簡単に追跡できるのです。

$monitorの基本的な使い方が分かったところで、次はより高度な使い方を見ていきましょう。

フォーマット指定を使って、出力をカスタマイズする方法を学んでいきます。

●$monitorのフォーマット指定で出力をカスタマイズ

$monitorの真価は、その柔軟な出力フォーマット指定にあります。

単に値を表示するだけでなく、見やすく、わかりやすい形式で情報を提示できるのです。

フォーマット指定を上手に活用することで、デバッグの効率をさらに高められます。

○サンプルコード2:変数表示のフォーマット指定

変数の値を表示する際、単に数値を出力するだけでは、その意味を把握しづらい場合があります。

そこで、フォーマット指定子を使って、値の意味や形式を明確にすることができます。

次のコードを見てみましょう。

module format_test;
  reg [7:0] data;
  reg [3:0] address;

  initial begin
    data = 8'hA5;
    address = 4'b1010;

    $monitor("Time: %0t, Data: 0x%h (Bin: %b), Address: %d", $time, data, data, address);

    #10 data = 8'h3C;
    #10 address = 4'b1100;
    #10 $finish;
  end
endmodule

このコードでは、8ビットのデータと4ビットのアドレスを監視しています。

フォーマット指定子を使って、それぞれの値を異なる形式で表示しています。

実行結果は次のようになります。

Time: 0, Data: 0xA5 (Bin: 10100101), Address: 10
Time: 10, Data: 0x3C (Bin: 00111100), Address: 10
Time: 20, Data: 0x3C (Bin: 00111100), Address: 12

この出力では、データを16進数と2進数の両方で表示し、アドレスは10進数で表示しています。

このように、フォーマット指定を工夫することで、値の意味をより明確に伝えることができます。

○サンプルコード3:時間情報の出力方法

シミュレーションにおいて、時間情報は非常に重要です。

$monitorでは、時間の表示方法もカスタマイズできます。

次のコードを見てみましょう。

module time_format_test;
  reg clk;

  always #5 clk = ~clk;

  initial begin
    clk = 0;
    $timeformat(-9, 3, " ns", 10);
    $monitor("Simulation time: %t, Clock: %b", $time, clk);

    #100 $finish;
  end
endmodule

このコードでは、$timeformatを使って時間の表示形式を設定しています。

-9はナノ秒単位、3は小数点以下の桁数、” ns”は単位を表す文字列、10は最小フィールド幅を指定しています。

実行結果は次のようになります。

Simulation time:    0.000 ns, Clock: 0
Simulation time:    5.000 ns, Clock: 1
Simulation time:   10.000 ns, Clock: 0
Simulation time:   15.000 ns, Clock: 1
Simulation time:   20.000 ns, Clock: 0
...

時間がナノ秒単位で表示され、小数点以下3桁まで表示されています。

このように時間情報を適切にフォーマットすることで、シミュレーションの進行をより正確に把握できます。

○サンプルコード4:複数の変数を同時に監視

複雑な回路設計では、複数の変数を同時に監視する必要がしばしば生じます。

$monitorは、この要求にも柔軟に対応できます。

次のコードを見てみましょう。

module multi_var_monitor;
  reg [3:0] counter;
  reg enable, reset;
  wire [3:0] output_data;

  // 仮想的な処理モジュール
  assign output_data = enable ? counter : 4'b0000;

  initial begin
    counter = 0;
    enable = 0;
    reset = 0;

    $monitor("Time=%0t, Enable=%b, Reset=%b, Counter=%h, Output=%h",
             $time, enable, reset, counter, output_data);

    #10 enable = 1;
    #10 counter = 4'h5;
    #10 reset = 1;
    #10 reset = 0;
    #10 counter = 4'hA;
    #10 enable = 0;
    #10 $finish;
  end
endmodule

このコードでは、カウンター、イネーブル信号、リセット信号、出力データの4つの変数を同時に監視しています。

実行結果は次のようになります。

Time=0, Enable=0, Reset=0, Counter=0, Output=0
Time=10, Enable=1, Reset=0, Counter=0, Output=0
Time=20, Enable=1, Reset=0, Counter=5, Output=5
Time=30, Enable=1, Reset=1, Counter=5, Output=5
Time=40, Enable=1, Reset=0, Counter=5, Output=5
Time=50, Enable=1, Reset=0, Counter=A, Output=A
Time=60, Enable=0, Reset=0, Counter=A, Output=0

この出力から、各信号の変化とそれに伴う出力の変化を一目で確認できます。

複数の変数を同時に監視することで、信号間の関係や回路全体の動作をより深く理解できるのです。

○サンプルコード5:条件付き$monitor出力

時には、特定の条件下でのみ$monitorの出力を行いたい場合があります。

条件付き$monitorを使用すると、必要な情報だけを効率的に表示できます。

次のコードを見てみましょう。

module conditional_monitor;
  reg [3:0] counter;
  reg clk;

  always #5 clk = ~clk;

  always @(posedge clk)
    counter <= counter + 1;

  initial begin
    clk = 0;
    counter = 0;

    $monitor("Time=%0t, Counter=%h", $time, counter);

    fork
      begin
        @(counter == 4'h8)
        $monitoroff;
        $display("Monitor turned off at time %0t", $time);
      end
      begin
        @(counter == 4'hC)
        $monitoron;
        $display("Monitor turned on at time %0t", $time);
      end
    join_none

    #100 $finish;
  end
endmodule

このコードでは、カウンターが8になった時点で$monitoroffを使って出力を停止し、12になった時点で$monitoronを使って出力を再開しています。

実行結果は次のようになります。

Time=0, Counter=0
Time=5, Counter=1
Time=15, Counter=2
Time=25, Counter=3
Time=35, Counter=4
Time=45, Counter=5
Time=55, Counter=6
Time=65, Counter=7
Monitor turned off at time 75
Monitor turned on at time 115
Time=115, Counter=c
Time=125, Counter=d
Time=135, Counter=e
Time=145, Counter=f

この出力から、カウンターが8から11の間は$monitorの出力が停止されていることがわかります。

このテクニックを使うと、特定の条件下でのみデバッグ情報を表示できるため、大規模なシミュレーションでの情報の取捨選択に役立ちます。

●$monitorの活用でシミュレーション効率アップ

$monitorを使いこなすことで、シミュレーションの効率を大幅に向上させることができます。

ただ単に変数の値を表示するだけでなく、クロック生成や複雑なテストベンチとの連携、さらにはファイル出力まで、$monitorの活用範囲は広範囲に及びます。

具体的な例を見ながら、$monitorの真の力を引き出す方法を探っていきましょう。

○サンプルコード6:クロック生成との連携

クロック信号は、デジタル回路設計において中心的な役割を果たします。

$monitorをクロック生成と連携させることで、タイミング関連の問題を効果的に検出できます。

次のコードを見てみましょう。

module clock_monitor;
  reg clk;
  reg [3:0] counter;

  // クロック生成
  always #5 clk = ~clk;

  // カウンター
  always @(posedge clk)
    counter <= counter + 1;

  initial begin
    clk = 0;
    counter = 0;
    $monitor("Time=%0t, Clock=%b, Counter=%h", $time, clk, counter);
    #100 $finish;
  end
endmodule

このコードでは、5時間単位ごとにクロックが反転し、立ち上がりエッジでカウンターが増加します。

$monitorを使用して、時間、クロック状態、カウンター値を同時に監視しています。

実行結果は次のようになります。

Time=0, Clock=0, Counter=0
Time=5, Clock=1, Counter=0
Time=10, Clock=0, Counter=1
Time=15, Clock=1, Counter=1
Time=20, Clock=0, Counter=2
Time=25, Clock=1, Counter=2
...

結果から、クロックの変化とカウンターの増加が正確に同期していることが確認できます。

クロックに関連する問題、例えばタイミング違反やクロックスキューなどを早期に発見するのに役立ちます。

○サンプルコード7:テストベンチでの$monitor活用

テストベンチは、設計したモジュールの動作を検証するための重要なツールです。

$monitorをテストベンチに組み込むことで、テスト結果をリアルタイムで確認できます。

次のコードを見てみましょう。

module adder_testbench;
  reg [3:0] a, b;
  wire [4:0] sum;

  // 被テストモジュール(4ビット加算器)
  adder dut (.a(a), .b(b), .sum(sum));

  initial begin
    $monitor("Time=%0t: a=%b, b=%b, sum=%b", $time, a, b, sum);

    // テストケース
    a = 4'b0000; b = 4'b0000; #10;
    a = 4'b0001; b = 4'b0010; #10;
    a = 4'b1111; b = 4'b0001; #10;
    a = 4'b1010; b = 4'b0101; #10;

    $finish;
  end
endmodule

// 4ビット加算器モジュール
module adder(
  input [3:0] a, b,
  output [4:0] sum
);
  assign sum = a + b;
endmodule

このテストベンチでは、4ビット加算器の動作を検証しています。

$monitorを使用して、入力値と出力結果をリアルタイムで表示しています。

実行結果は次のようになります。

Time=0: a=0000, b=0000, sum=00000
Time=10: a=0001, b=0010, sum=00011
Time=20: a=1111, b=0001, sum=10000
Time=30: a=1010, b=0101, sum=01111

この結果から、各テストケースにおける加算器の動作を簡単に確認できます。

特に、オーバーフローが発生するケース(1111 + 0001)も正確に捉えられています。

○サンプルコード8:ファイルへの出力設定

長時間のシミュレーションや大量のデータを扱う場合、コンソール出力だけでは不十分です。

$monitorの出力をファイルに保存することで、後からじっくりと解析することができます。

次のコードを見てみましょう。

module file_output_test;
  reg [7:0] data;
  integer file;

  initial begin
    // ファイルをオープン
    file = $fopen("monitor_output.txt", "w");

    // ファイルへの出力を設定
    $monitor(file, "Time=%0t, Data=%h", $time, data);

    // テストデータ
    data = 8'h00;
    #10 data = 8'hA5;
    #10 data = 8'hFF;
    #10 data = 8'h3C;

    // ファイルをクローズ
    #10 $fclose(file);
    $finish;
  end
endmodule

このコードでは、$monitorの出力を”monitor_output.txt”というファイルに保存しています。

$fopen関数でファイルを開き、$monitorの第一引数にファイル識別子を指定することで、出力先をファイルに切り替えています。

実行後、”monitor_output.txt”ファイルには次の内容が保存されます。

Time=0, Data=00
Time=10, Data=a5
Time=20, Data=ff
Time=30, Data=3c

ファイル出力を利用することで、長時間のシミュレーション結果を保存し、後から詳細な解析を行うことができます。

大規模プロジェクトでは特に有用なテクニックです。

○サンプルコード9:SystemVerilogでの拡張機能

SystemVerilogは、Verilogの機能を拡張した言語です。

SystemVerilogでは、$monitorにも新たな機能が追加されています。

特に、構造体やクラスオブジェクトの監視が容易になっています。

次のコードを見てみましょう。

typedef struct {
  bit [7:0] data;
  bit valid;
} packet_t;

module systemverilog_monitor;
  packet_t packet;

  initial begin
    $monitor("Time=%0t, Packet={data=%h, valid=%b}", $time, packet.data, packet.valid);

    packet.data = 8'h00;
    packet.valid = 0;
    #10;

    packet.data = 8'hA5;
    packet.valid = 1;
    #10;

    packet.data = 8'hFF;
    packet.valid = 0;
    #10;

    $finish;
  end
endmodule

このコードでは、packet_tという構造体を定義し、その内容を$monitorで監視しています。

SystemVerilogでは、構造体のフィールドに直接アクセスして監視することができます。

実行結果は次のようになります。

Time=0, Packet={data=00, valid=0}
Time=10, Packet={data=a5, valid=1}
Time=20, Packet={data=ff, valid=0}

SystemVerilogの拡張機能を使うことで、より複雑なデータ構造も簡単に監視できます。

オブジェクト指向プログラミングの概念を取り入れた設計においても、$monitorは強力なツールとして活躍します。

●よくあるエラーと対処法

$monitorは非常に便利なツールですが、使用する際にはいくつかの注意点があります。

よくあるエラーとその対処法を知っておくことで、より効果的に$monitorを活用できるでしょう。

○$monitorが動作しない場合の確認ポイント

$monitorを設定したにもかかわらず、期待通りの出力が得られない場合があります。

主な原因と確認ポイントは次の通りです。

  1. $monitorの設定位置 -> $monitorは、設定された時点から動作を開始します。シミュレーションの開始時に設定されていることを確認しましょう。
  2. シミュレーション時間 -> $monitorは、シミュレーション時間が進まないと動作しません。#0などの遅延を入れて、時間を進めてみましょう。
  3. 変数の変化 -> $monitorは、監視対象の変数が変化した時のみ出力を行います。変数が実際に変化しているか確認しましょう。
  4. 複数の$monitor -> 複数の$monitorが設定されている場合、最後に設定されたものだけが有効になります。不要な$monitorを削除するか、$monitoroffと$monitoronを使って制御しましょう。

例えば、次のようなコードでは$monitorが正しく動作しない可能性があります。

module monitor_error_example;
  reg [3:0] data;

  initial begin
    data = 4'b0000;
    $monitor("Data=%b", data);
    data = 4'b1010;  // この変化は捉えられない
    $finish;
  end
endmodule

修正後のコードは次のようになります。

module monitor_error_fixed;
  reg [3:0] data;

  initial begin
    $monitor("Time=%0t, Data=%b", $time, data);
    data = 4'b0000;
    #10 data = 4'b1010;  // 遅延を入れることで変化を捉えられる
    #10 $finish;
  end
endmodule

○出力が期待通りでない時のデバッグ方法

$monitorの出力は得られるものの、期待通りの結果でない場合もあります。

次のデバッグ方法を試してみましょう。

  1. フォーマット指定子の確認 -> 正しいフォーマット指定子を使用しているか確認します。例えば、16進数を表示したい場合は%hを使用します。
  2. 変数の型とサイズ -> 監視対象の変数の型とサイズが正しいか確認します。不適切な型やサイズの指定により、予期せぬ結果が表示される可能性があります。
  3. 時間単位の確認 -> `timescale指令を使用して、適切な時間単位を設定しているか確認します。
  4. 条件付き$monitor -> 特定の条件下でのみ出力を行いたい場合は、条件付き$monitorの使用を検討します。

例えば、次のコードでは出力が期待通りにならない可能性があります。

module unexpected_output;
  reg [7:0] data;

  initial begin
    $monitor("Data=%d", data);  // 誤ったフォーマット指定子
    data = 8'hA5;
    #10 $finish;
  end
endmodule

修正後のコードは以下のようになります。

module output_fixed;
  reg [7:0] data;

  initial begin
    $monitor("Data=%h", data);  // 正しいフォーマット指定子
    data = 8'hA5;
    #10 $finish;
  end
endmodule

○シミュレーション速度低下時の最適化テクニック

$monitorを多用すると、シミュレーション速度が低下する場合があります。

次の最適化テクニックを活用して、パフォーマンスを向上させましょう。

  1. 必要最小限の変数監視 -> 本当に必要な変数だけを監視対象にします。不要な変数を監視すると、処理オーバーヘッドが増加します。
  2. 条件付き$monitor -> 特定の条件下でのみ$monitorを動作させることで、出力量を削減できます。
  3. サンプリング間隔の調整 -> 高頻度な変化を持つ信号の場合、適切なサンプリング間隔を設定することで、出力量を抑えられます。
  4. ファイル出力の利用 -> 大量のデータを扱う場合、コンソール出力よりもファイル出力の方が効率的です。

例えば、次のコードはシミュレーション速度を低下させる可能性があります。

module performance_issue;
  reg clk;
  reg [31:0] counter;

  always #1 clk = ~clk;

  always @(posedge clk)
    counter <= counter + 1;

  initial begin
    clk = 0;
    counter = 0;
    $monitor("Time=%0t, Clock=%b, Counter=%d", $time, clk, counter);
    #1000000 $finish;
  end
endmodule

最適化後のコードは次のようになります。

module optimized_performance;
  reg clk;
  reg [31:0] counter;

  always #1 clk = ~clk;

  always @(posedge clk)
    counter <= counter + 1;

  initial begin
    clk = 0;
    counter = 0;
    $monitor("Time=%0t, Counter=%d", $time, counter);
    #1000000 $finish;
  end

  // 1000クロックサイクルごとに出力
  always @(posedge clk)
    if (counter % 1000 == 0)
      $display("Time=%0t, Clock=%b, Counter=%d", $time, clk, counter);
endmodule

このように、$monitorと$displayを適切に組み合わせることで、シミュレーション速度を維持しつつ、必要な情報を効率的に取得できます。

●$monitorの応用例と高度なテクニック

$monitorの基本的な使い方を習得したら、次は応用編に挑戦しましょう。

高度なテクニックを身につけることで、より複雑な回路設計やデバッグに対応できるようになります。

ここでは、実践的な応用例を通じて、$monitorの真の力を引き出す方法を探ります。

○サンプルコード10:複数の$monitorの使い分け

大規模な設計では、異なる部分の動作を同時に監視する必要が生じます。

複数の$monitorを使い分けることで、効率的な監視が可能になります。

module multi_monitor_example;
  reg clk, reset;
  reg [7:0] data_in;
  wire [7:0] data_out;

  // 仮想的な処理モジュール
  processing_unit dut (.clk(clk), .reset(reset), .data_in(data_in), .data_out(data_out));

  // クロック生成
  always #5 clk = ~clk;

  initial begin
    clk = 0;
    reset = 1;
    data_in = 8'h00;

    // 入力データ監視
    $monitor("Input Monitor: Time=%0t, Data_in=%h", $time, data_in);

    // 出力データ監視(条件付き)
    always @(posedge clk)
      if (data_out != 8'h00)
        $display("Output Monitor: Time=%0t, Data_out=%h", $time, data_out);

    #10 reset = 0;
    #10 data_in = 8'hA5;
    #10 data_in = 8'h3C;
    #10 data_in = 8'hFF;
    #50 $finish;
  end
endmodule

// 仮想的な処理ユニット
module processing_unit(
  input clk, reset,
  input [7:0] data_in,
  output reg [7:0] data_out
);
  always @(posedge clk or posedge reset) begin
    if (reset)
      data_out <= 8'h00;
    else
      data_out <= data_in + 8'h01; // 単純な加算処理
  end
endmodule

このコードでは、入力データを常時監視する$monitorと、特定の条件下で出力データを表示する$displayを組み合わせています。

実行結果は次のようになります。

Input Monitor: Time=0, Data_in=00
Input Monitor: Time=20, Data_in=a5
Output Monitor: Time=25, Data_out=a6
Input Monitor: Time=30, Data_in=3c
Output Monitor: Time=35, Data_out=3d
Input Monitor: Time=40, Data_in=ff
Output Monitor: Time=45, Data_out=00

この方法により、入力の変化を逃さず捉えつつ、興味のある出力のみを表示することができます。

大規模な設計での効率的なデバッグに役立ちます。

○サンプルコード11:階層化設計での$monitor活用

階層化された設計では、各階層でのデータの流れを追跡することが重要です。

$monitorを階層的に配置することで、複雑な設計の動作を理解しやすくなります。

module hierarchical_monitor;
  reg clk, reset;
  reg [7:0] top_input;
  wire [7:0] mid_output, bottom_output;

  // 中間層モジュール
  mid_layer mid (.clk(clk), .reset(reset), .in(top_input), .out(mid_output));

  // 最下層モジュール
  bottom_layer bottom (.clk(clk), .reset(reset), .in(mid_output), .out(bottom_output));

  // クロック生成
  always #5 clk = ~clk;

  initial begin
    clk = 0;
    reset = 1;
    top_input = 8'h00;

    $monitor("Top Monitor: Time=%0t, Input=%h, Mid_out=%h, Bottom_out=%h",
             $time, top_input, mid_output, bottom_output);

    #10 reset = 0;
    #10 top_input = 8'hA5;
    #10 top_input = 8'h3C;
    #10 top_input = 8'hFF;
    #50 $finish;
  end
endmodule

module mid_layer(
  input clk, reset,
  input [7:0] in,
  output reg [7:0] out
);
  always @(posedge clk or posedge reset) begin
    if (reset)
      out <= 8'h00;
    else
      out <= in + 8'h01;
  end

  // 中間層のモニター
  always @(out)
    $display("Mid Monitor: Time=%0t, Mid_out=%h", $time, out);
endmodule

module bottom_layer(
  input clk, reset,
  input [7:0] in,
  output reg [7:0] out
);
  always @(posedge clk or posedge reset) begin
    if (reset)
      out <= 8'h00;
    else
      out <= in * 2;
  end

  // 最下層のモニター
  always @(out)
    $display("Bottom Monitor: Time=%0t, Bottom_out=%h", $time, out);
endmodule

このコードでは、トップレベル、中間層、最下層の各階層で異なる$monitorや$displayを使用しています。

実行結果は次のようになります。

Top Monitor: Time=0, Input=00, Mid_out=xx, Bottom_out=xx
Mid Monitor: Time=0, Mid_out=00
Bottom Monitor: Time=0, Bottom_out=00
Top Monitor: Time=20, Input=a5, Mid_out=00, Bottom_out=00
Mid Monitor: Time=25, Mid_out=a6
Bottom Monitor: Time=25, Bottom_out=4c
Top Monitor: Time=30, Input=3c, Mid_out=a6, Bottom_out=4c
Mid Monitor: Time=35, Mid_out=3d
Bottom Monitor: Time=35, Bottom_out=7a
Top Monitor: Time=40, Input=ff, Mid_out=3d, Bottom_out=7a
Mid Monitor: Time=45, Mid_out=00
Bottom Monitor: Time=45, Bottom_out=00

この方法により、各階層でのデータの変化を追跡し、設計全体の動作を把握することができます。

○サンプルコード12:動的な$monitor制御

シミュレーションの進行に応じて、監視の焦点を変更したい場合があります。

$monitoron、$monitoroff関数を使用することで、動的に$monitorの有効/無効を切り替えることができます。

module dynamic_monitor_control;
  reg clk, reset;
  reg [7:0] data;
  reg [1:0] state;

  // クロック生成
  always #5 clk = ~clk;

  // 簡単なステートマシン
  always @(posedge clk or posedge reset) begin
    if (reset) begin
      state <= 2'b00;
      data <= 8'h00;
    end else begin
      case (state)
        2'b00: begin state <= 2'b01; data <= data + 8'h01; end
        2'b01: begin state <= 2'b10; data <= data + 8'h02; end
        2'b10: begin state <= 2'b11; data <= data + 8'h03; end
        2'b11: begin state <= 2'b00; data <= data + 8'h04; end
      endcase
    end
  end

  initial begin
    clk = 0;
    reset = 1;
    $monitor("Default Monitor: Time=%0t, State=%b, Data=%h", $time, state, data);

    #10 reset = 0;

    // ステート0と1の間だけモニターを変更
    fork
      begin
        @(state == 2'b00);
        $monitoroff;
        $monitor("State 0-1 Monitor: Time=%0t, State=%b, Data=%h", $time, state, data);
        $monitoron;
        @(state == 2'b10);
        $monitoroff;
        $monitor("Default Monitor: Time=%0t, State=%b, Data=%h", $time, state, data);
        $monitoron;
      end
    join_none

    #100 $finish;
  end
endmodule

このコードでは、ステートマシンの特定の状態の間だけ、異なる$monitorを使用しています。

実行結果は次のようになります。

Default Monitor: Time=0, State=00, Data=00
State 0-1 Monitor: Time=15, State=01, Data=01
State 0-1 Monitor: Time=25, State=10, Data=03
Default Monitor: Time=35, State=11, Data=06
Default Monitor: Time=45, State=00, Data=0a
State 0-1 Monitor: Time=55, State=01, Data=0b
State 0-1 Monitor: Time=65, State=10, Data=0d
Default Monitor: Time=75, State=11, Data=10
Default Monitor: Time=85, State=00, Data=14
State 0-1 Monitor: Time=95, State=01, Data=15

この方法により、シミュレーションの特定のフェーズや状態に応じて、適切な情報を表示することができます。

○サンプルコード13:高度なエラー検出システム

$monitorを使って、高度なエラー検出システムを構築することもできます。

予期せぬ動作や違反を自動的に検出し、報告するシステムを作ってみましょう。

module advanced_error_detection;
  reg clk, reset;
  reg [7:0] data;
  reg [2:0] state;
  reg error_flag;

  // クロック生成
  always #5 clk = ~clk;

  // データと状態の更新
  always @(posedge clk or posedge reset) begin
    if (reset) begin
      state <= 3'b000;
      data <= 8'h00;
      error_flag <= 0;
    end else begin
      state <= state + 1;
      case (state)
        3'b000: data <= 8'h11;
        3'b001: data <= 8'h22;
        3'b010: data <= 8'h33;
        3'b011: data <= 8'h44;
        3'b100: data <= 8'h55;
        3'b101: data <= 8'h66;
        3'b110: data <= 8'h77;
        3'b111: data <= 8'h88;
      endcase
    end
  end

  // エラー検出ロジック
  always @(posedge clk) begin
    if (!reset) begin
      if ((state == 3'b011 && data != 8'h44) || (state == 3'b111 && data != 8'h88)) begin
        error_flag <= 1;
        $display("ERROR: Unexpected data value at Time=%0t, State=%b, Data=%h", $time, state, data);
      end
    end
  end

  // モニター設定
  initial begin
    clk = 0;
    reset = 1;
    $monitor("Monitor: Time=%0t, State=%b, Data=%h, Error=%b", $time, state, data, error_flag);

    #10 reset = 0;
    #100 data = 8'hFF; // 意図的にエラーを発生させる
    #50 $finish;
  end
endmodule

このコードでは、特定の状態で予期せぬデータ値が発生した場合にエラーフラグを立て、詳細な情報を表示します。

実行結果は次のようになります。

Monitor: Time=0, State=000, Data=00, Error=0
Monitor: Time=15, State=001, Data=11, Error=0
Monitor: Time=25, State=010, Data=22, Error=0
Monitor: Time=35, State=011, Data=33, Error=0
Monitor: Time=45, State=100, Data=44, Error=0
Monitor: Time=55, State=101, Data=55, Error=0
Monitor: Time=65, State=110, Data=66, Error=0
Monitor: Time=75, State=111, Data=77, Error=0
Monitor: Time=85, State=000, Data=88, Error=0
Monitor: Time=95, State=001, Data=11, Error=0
Monitor: Time=105, State=010, Data=ff, Error=0
ERROR: Unexpected data value at Time=115, State=011, Data=ff
Monitor: Time=115, State=011, Data=ff, Error=1

この方法により、複雑な条件下でのエラーを自動的に検出し、即座に報告することができます。

大規模なプロジェクトでのデバッグ効率を大幅に向上させることができるでしょう。

まとめ

基本的な使用方法から高度なテクニックまで、様々な活用法を解説してきました。

$monitorを使いこなすことで、複雑な回路設計のデバッグ効率を大幅に向上させることができます。

本記事が、皆さんのVerilog設計スキル向上の参考となれば幸いです。

$monitorを使いこなし、より高度で効率的な設計を実現してください。