読み込み中...

Verilogにおける慣性遅延の基礎知識と活用10選

慣性遅延 徹底解説 Verilog
この記事は約34分で読めます。

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

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

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

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

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

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

●Verilogの慣性遅延とは?

今日は、デジタル回路設計において非常に重要な概念である「慣性遅延」について詳しく解説します。

慣性遅延を理解することで、より現実的で信頼性の高い回路設計が可能になります。

○遅延の意味と重要性

慣性遅延とは、回路内の信号が変化してから実際に出力に反映されるまでの時間のことを指します。

現実世界の電子部品には必ず遅延が存在します。

トランジスタやゲートが切り替わるのに時間がかかるためです。

Verilogでは、この遅延をシミュレーションに組み込むことができます。

遅延を考慮することで、設計した回路が実際のハードウェアでどのように動作するかを正確に予測できるようになります。

例えば、高速なデジタル回路では、遅延を無視すると予期せぬ動作や誤動作を引き起こす可能性があります。

特に非同期回路や複数のクロックドメインを持つ設計では、遅延の影響が顕著に現れます。

慣性遅延を適切に扱うことで、タイミング違反やレース条件といった問題を事前に発見し、対処することができます。

結果として、より安定した信頼性の高い回路を設計することが可能になるのです。

○Verilogでの遅延命令の基本

Verilogでは、遅延を表現するために「#」記号を使用します。

この記号は、信号の変化やイベントの発生を指定した時間だけ遅らせる役割を果たします。

基本的な遅延の記述方法は次のとおりです。

#<遅延時間> <文>

ここで、<遅延時間>は単位時間の倍数で指定し、<文>は遅延後に実行される処理を記述します。

例えば、10単位時間の遅延を挿入する場合は次のように記述します。

#10 a = b;

この文は、10単位時間経過後にaにbの値を代入することを意味します。

Verilogでは、遅延時間の単位はシミュレータの設定に依存します。

一般的には、ナノ秒(ns)やピコ秒(ps)といった単位が使用されます。

○サンプルコード1:基本的な遅延の実装

それでは、具体的なサンプルコードを見てみましょう。

ここでは、AND回路に遅延を組み込んだ簡単な例を見てみましょう。

module delayed_and(
    input a,
    input b,
    output reg y
);

always @(a or b) begin
    #5 y = a & b;  // 5単位時間の遅延後にAND演算を実行
end

endmodule

このモジュールでは、入力aまたはbが変化してから5単位時間後に、AND演算の結果がyに反映されます。

テストベンチを作成して、この遅延の効果を確認してみましょう。

module delayed_and_tb;
    reg a, b;
    wire y;

    delayed_and dut(
        .a(a),
        .b(b),
        .y(y)
    );

    initial begin
        $dumpfile("delayed_and.vcd");
        $dumpvars(0, delayed_and_tb);

        a = 0; b = 0;
        #10 a = 1;
        #10 b = 1;
        #20 $finish;
    end
endmodule

このテストベンチを実行すると、次のような波形が得られます。

       0   5   10  15  20  25  30  35  40
       |   |   |   |   |   |   |   |   |
a  ____|_______________________________

b  __________________|___________________

y  ______________________|_______________

ご覧のように、aとbが共に1になってから5単位時間後にyが1に変化しています。

慣性遅延の効果が明確に表れていますね。

●Verilogでの遅延を考慮した記述方法

遅延の基本を理解したところで、より実践的な遅延の使用方法を見ていきましょう。

Verilogでは、様々な場面で遅延を活用することができます。

○サンプルコード2:alwaysブロックでの遅延使用

alwaysブロックは、Verilogで頻繁に使用される構文です。

遅延をalwaysブロック内で使用することで、より複雑な動作をモデル化できます。

ここでは、立ち上がりエッジで動作するDフリップフロップの例を紹介します。

module d_flip_flop(
    input clk,
    input d,
    output reg q
);

always @(posedge clk) begin
    #2 q <= d;  // クロックの立ち上がりから2単位時間後にdの値をqに反映
end

endmodule

このモジュールでは、クロックの立ち上がりエッジから2単位時間後にデータが出力に反映されます。

実際のフリップフロップのセットアップ時間とプロパゲーション遅延をシミュレートしています。

テストベンチを作成して動作を確認しましょう。

module d_flip_flop_tb;
    reg clk, d;
    wire q;

    d_flip_flop dut(
        .clk(clk),
        .d(d),
        .q(q)
    );

    initial begin
        clk = 0;
        forever #5 clk = ~clk;  // 10単位時間周期のクロックを生成
    end

    initial begin
        $dumpfile("d_flip_flop.vcd");
        $dumpvars(0, d_flip_flop_tb);

        d = 0;
        #12 d = 1;
        #10 d = 0;
        #10 d = 1;
        #20 $finish;
    end
endmodule

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

       0   5   10  15  20  25  30  35  40  45  50
       |   |   |   |   |   |   |   |   |   |   |
clk ___|___|___|___|___|___|___|___|___|___|___

d   ___________|_________|___________|__________

q   _______________|_________|___________|______

クロックの立ち上がりから2単位時間後に、qがdの値を反映していることがわかります。

○サンプルコード3:assign文での遅延表現

assign文でも遅延を使用できます。

これは組み合わせ回路の遅延をモデル化するのに適しています。

ここでは、遅延付きのOR回路の例をみてみましょう。

module delayed_or(
    input a,
    input b,
    output y
);

assign #3 y = a | b;  // 3単位時間の遅延後にOR演算を実行

endmodule

このモジュールでは、入力の変化から3単位時間後にOR演算の結果が出力に反映されます。

テストベンチを作成して動作を確認しましょう。

module delayed_or_tb;
    reg a, b;
    wire y;

    delayed_or dut(
        .a(a),
        .b(b),
        .y(y)
    );

    initial begin
        $dumpfile("delayed_or.vcd");
        $dumpvars(0, delayed_or_tb);

        a = 0; b = 0;
        #10 a = 1;
        #10 b = 1;
        #10 a = 0;
        #10 $finish;
    end
endmodule

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

       0   5   10  15  20  25  30  35  40
       |   |   |   |   |   |   |   |   |
a  ____|_______________|___________________

b  __________________|___________________

y  _________|_____________________________

入力の変化から3単位時間後に出力が変化していることがわかります。

○サンプルコード4:複雑な遅延パターンの実装

実際の回路では、異なる遅延値を持つ複数の経路が存在することがあります。

Verilogでは、このような複雑な遅延パターンもモデル化できます。

module complex_delay(
    input a,
    input b,
    input c,
    output y
);

wire ab, bc;

assign #2 ab = a & b;  // AND演算に2単位時間の遅延
assign #3 bc = b | c;  // OR演算に3単位時間の遅延
assign #1 y = ab ^ bc;  // XOR演算に1単位時間の遅延

endmodule

このモジュールでは、異なる遅延を持つ複数の演算が組み合わされています。

テストベンチを作成して動作を確認しましょう。

module complex_delay_tb;
    reg a, b, c;
    wire y;

    complex_delay dut(
        .a(a),
        .b(b),
        .c(c),
        .y(y)
    );

    initial begin
        $dumpfile("complex_delay.vcd");
        $dumpvars(0, complex_delay_tb);

        a = 0; b = 0; c = 0;
        #10 a = 1;
        #10 b = 1;
        #10 c = 1;
        #20 $finish;
    end
endmodule

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

       0   5   10  15  20  25  30  35  40  45  50
       |   |   |   |   |   |   |   |   |   |   |
a  ____|_______________________________________

b  __________________|_________________________

c  ____________________________|_________________

y  ___________|_________|_________|_____________

各演算の遅延が組み合わさって、複雑な出力パターンが生成されていることがわかります。

○サンプルコード5:SystemVerilogでの新しい遅延表現

SystemVerilogは、Verilogの拡張言語であり、より豊富な機能を提供します。

遅延の表現においても、新しい方法が導入されています。

ここでは、SystemVerilogの配列遅延を使用した例を見てみましょう。

module system_verilog_delay(
    input logic a,
    input logic b,
    output logic [1:0] y
);

// 複数の遅延値を指定
logic [1:0] temp;
assign #(2, 3) temp = {a, b};

// 条件付き遅延
always_comb begin
    if (a)
        #2 y[0] = temp[0];
    else
        #3 y[0] = temp[0];

    y[1] <= #4 temp[1];  // ノンブロッキング代入での遅延指定
end

endmodule

このモジュールでは、SystemVerilogの新機能である配列遅延と条件付き遅延を使用しています。

また、ノンブロッキング代入での遅延指定方法も表しています。

テストベンチを作成して動作を確認しましょう。

module system_verilog_delay_tb;
    logic a, b;
    logic [1:0] y;

    system_verilog_delay dut(
        .a(a),
        .b(b),
        .y(y)
    );

    initial begin
        $dumpfile("system_verilog_delay.vcd");
        $dumpvars(0, system_verilog_delay_tb);

        a = 0; b = 0;
        #10 a = 1;
        #10 b = 1;
        #10 a = 0;
        #20 $finish;
    end
endmodule

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

       0   5   10  15  20  25  30  35  40  45  50
       |   |   |   |   |   |   |   |   |   |   |
a  ____|_______________|___________________

b  __________________|___________________

y[0]_____|_________|___________|___________

y[1]___________|_______________|___________

異なる遅延値が適用され、複雑な出力パターンが生成されていることがわかります。

●クロックと遅延

Verilogにおけるクロックと遅延の関係は、デジタル回路設計の核心部分です。

クロック信号は回路全体のタイミングを制御し、遅延はそのタイミングに影響を与えます。

両者の適切な管理が、正確で信頼性の高い回路設計につながります。

○サンプルコード6:クロック信号を用いた遅延制御

クロック信号を利用した遅延制御は、同期回路設計の基本です。

次のサンプルコードでは、クロックエッジごとに一定の遅延を持つカウンタを実装します。

module delayed_counter(
    input clk,
    input reset,
    output reg [3:0] count
);

always @(posedge clk or posedge reset) begin
    if (reset) begin
        count <= 4'b0000;
    end else begin
        #2 count <= count + 1;  // 2単位時間の遅延後にカウントアップ
    end
end

endmodule

このモジュールでは、クロックの立ち上がりエッジから2単位時間後にカウンタの値が更新されます。

リセット信号は非同期で、即座にカウンタをゼロにリセットします。

テストベンチを作成して動作を確認しましょう。

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

    delayed_counter dut(
        .clk(clk),
        .reset(reset),
        .count(count)
    );

    initial begin
        clk = 0;
        forever #5 clk = ~clk;  // 10単位時間周期のクロックを生成
    end

    initial begin
        $dumpfile("delayed_counter.vcd");
        $dumpvars(0, delayed_counter_tb);

        reset = 1;
        #15 reset = 0;
        #100 $finish;
    end
endmodule

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

       0   10  20  30  40  50  60  70  80  90  100
       |   |   |   |   |   |   |   |   |   |   |
clk ___|___|___|___|___|___|___|___|___|___|___

rst ______|

cnt 0   0   0   1   2   3   4   5   6   7   8

カウンタの値が2単位時間遅れて更新される様子が分かります。

○サンプルコード7:エッジトリガーと遅延の組み合わせ

エッジトリガーと遅延を組み合わせることで、より複雑なタイミング制御が可能になります。

ここでは、立ち上がりエッジと立ち下がりエッジで異なる遅延を持つフリップフロップの例を紹介します。

module edge_delayed_ff(
    input clk,
    input d,
    output reg q
);

always @(posedge clk) begin
    #2 q <= d;  // 立ち上がりエッジから2単位時間後に更新
end

always @(negedge clk) begin
    #3 q <= ~d;  // 立ち下がりエッジから3単位時間後に反転して更新
end

endmodule

このモジュールでは、クロックの立ち上がりエッジでは2単位時間後に入力をそのまま出力し、立ち下がりエッジでは3単位時間後に入力を反転して出力します。

テストベンチは次のようになります。

module edge_delayed_ff_tb;
    reg clk, d;
    wire q;

    edge_delayed_ff dut(
        .clk(clk),
        .d(d),
        .q(q)
    );

    initial begin
        clk = 0;
        forever #5 clk = ~clk;  // 10単位時間周期のクロックを生成
    end

    initial begin
        $dumpfile("edge_delayed_ff.vcd");
        $dumpvars(0, edge_delayed_ff_tb);

        d = 0;
        #12 d = 1;
        #10 d = 0;
        #10 d = 1;
        #20 $finish;
    end
endmodule

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

       0   5   10  15  20  25  30  35  40  45  50
       |   |   |   |   |   |   |   |   |   |   |
clk ___|___|___|___|___|___|___|___|___|___|___

d   ___________|_________|___________|__________

q   ______|________|________|________|__________

クロックエッジごとに異なる遅延で出力が更新される様子が確認できます。

○サンプルコード8:クロックドメイン間の遅延管理

複数のクロックドメインを持つ設計では、ドメイン間の信号の受け渡しに注意が必要です。

次のサンプルコードでは、異なる周波数のクロックドメイン間でデータを転送する際の遅延管理をしています。

module clock_domain_crossing(
    input clk_slow,
    input clk_fast,
    input [7:0] data_in,
    output reg [7:0] data_out
);

reg [7:0] data_mid;

// スロークロックドメイン
always @(posedge clk_slow) begin
    #2 data_mid <= data_in;  // 2単位時間の遅延
end

// ファストクロックドメイン
always @(posedge clk_fast) begin
    #1 data_out <= data_mid;  // 1単位時間の遅延
end

endmodule

このモジュールでは、スロークロックドメインからファストクロックドメインへデータを転送しています。

各ドメインで適切な遅延を設定することで、タイミング違反を防いでいます。

テストベンチは次のようになります。

module clock_domain_crossing_tb;
    reg clk_slow, clk_fast;
    reg [7:0] data_in;
    wire [7:0] data_out;

    clock_domain_crossing dut(
        .clk_slow(clk_slow),
        .clk_fast(clk_fast),
        .data_in(data_in),
        .data_out(data_out)
    );

    initial begin
        clk_slow = 0;
        forever #10 clk_slow = ~clk_slow;  // 20単位時間周期
    end

    initial begin
        clk_fast = 0;
        forever #4 clk_fast = ~clk_fast;  // 8単位時間周期
    end

    initial begin
        $dumpfile("clock_domain_crossing.vcd");
        $dumpvars(0, clock_domain_crossing_tb);

        data_in = 8'h00;
        #25 data_in = 8'hAA;
        #20 data_in = 8'h55;
        #30 $finish;
    end
endmodule

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

       0   10  20  30  40  50  60  70
       |   |   |   |   |   |   |   |
clk_s ___|___|___|___|___|___|___|___

clk_f _|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_

d_in  00      AA      55

d_mid 00          AA          55

d_out 00              AA          55

異なるクロックドメイン間でデータが適切に転送される様子が確認できます。

○サンプルコード9:グローバルクロックと局所遅延の調整

大規模な設計では、グローバルクロックと局所的な遅延の調整が重要になります。

次のサンプルコードでは、グローバルクロックを使用しつつ、各モジュールで異なる遅延を設定しています。

module global_local_delay(
    input global_clk,
    input [7:0] data_in,
    output reg [7:0] data_out1,
    output reg [7:0] data_out2
);

// モジュール1: 短い遅延
always @(posedge global_clk) begin
    #1 data_out1 <= data_in;
end

// モジュール2: 長い遅延
always @(posedge global_clk) begin
    #3 data_out2 <= data_in;
end

endmodule

このモジュールでは、共通のグローバルクロックを使用しながら、2つのサブモジュールで異なる遅延を設定しています。

テストベンチは次のようになります。

module global_local_delay_tb;
    reg global_clk;
    reg [7:0] data_in;
    wire [7:0] data_out1, data_out2;

    global_local_delay dut(
        .global_clk(global_clk),
        .data_in(data_in),
        .data_out1(data_out1),
        .data_out2(data_out2)
    );

    initial begin
        global_clk = 0;
        forever #5 global_clk = ~global_clk;  // 10単位時間周期
    end

    initial begin
        $dumpfile("global_local_delay.vcd");
        $dumpvars(0, global_local_delay_tb);

        data_in = 8'h00;
        #12 data_in = 8'hAA;
        #10 data_in = 8'h55;
        #20 $finish;
    end
endmodule

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

       0   5   10  15  20  25  30  35  40
       |   |   |   |   |   |   |   |   |
clk ___|___|___|___|___|___|___|___|___

d_in  00      AA      55

d_o1  00          AA      55

d_o2  00              AA          55

グローバルクロックに対して、各モジュールが異なる遅延で動作する様子が確認できます。

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

Verilogでの遅延設計において、いくつかの典型的なエラーが存在します。

正しい対処法を知ることで、より信頼性の高い設計が可能になります。

○セットアップとホールド時間

セットアップ時間とは、クロックエッジの前にデータが安定している必要がある時間です。

ホールド時間は、クロックエッジの後にデータが安定している必要がある時間です。

問題のある例

always @(posedge clk) begin
    q <= d;  // セットアップ時間違反の可能性
end

改善例

always @(posedge clk) begin
    #1 q <= d;  // 1単位時間の遅延を追加
end

遅延を追加することで、セットアップ時間違反のリスクを軽減できます。

○不要な遷移の抑制

信号の不要な遷移(グリッチ)は、回路の動作を不安定にする可能性があります。

問題のある例

assign y = a & b | c;  // グリッチの可能性

改善例

reg y_temp;
always @(*) begin
    #1 y_temp = a & b | c;  // 1単位時間の遅延を追加
end
assign y = y_temp;

小さな遅延を追加することで、不要な遷移を抑制できます。

○非同期信号の同期化

非同期信号をそのまま使用すると、メタステーブル状態を引き起こす可能性があります。

問題のある例

always @(posedge clk) begin
    if (async_reset)
        q <= 0;
    else
        q <= d;
end

改善例

reg async_reset_sync1, async_reset_sync2;

always @(posedge clk) begin
    async_reset_sync1 <= async_reset;
    async_reset_sync2 <= async_reset_sync1;

    if (async_reset_sync2)
        q <= 0;
    else
        q <= d;
end

二段のフリップフロップを使用して非同期信号を同期化することで、メタステーブル状態のリスクを大幅に低減できます。

●慣性遅延の応用例

Verilogにおける慣性遅延の知識を深めてきました。

理論的な理解を実践に移す時が来ました。

慣性遅延を活用することで、回路設計の質が飛躍的に向上します。

実際の設計現場で役立つ応用例を見ていきましょう。

○サンプルコード10:信号のデバウンス処理

機械式スイッチを扱う際、チャタリングという現象が発生します。

スイッチの接点がバウンドして、短時間に複数回のON/OFFを繰り返すのです。

デバウンス処理はチャタリングを除去する技術です。

慣性遅延を利用して、簡単なデバウンス回路を実装できます。

module debounce(
    input clk,
    input noisy_input,
    output reg clean_output
);

reg [1:0] shift_reg;

always @(posedge clk) begin
    shift_reg <= {shift_reg[0], noisy_input};

    if (shift_reg == 2'b11)
        #10 clean_output <= 1;  // 10単位時間の遅延後に1に設定
    else if (shift_reg == 2'b00)
        #10 clean_output <= 0;  // 10単位時間の遅延後に0に設定
end

endmodule

2ビットのシフトレジスタを使用して、入力信号の状態を監視します。

シフトレジスタが2サイクル連続で同じ値を保持した場合にのみ、出力を更新します。

10単位時間の遅延を追加することで、短時間のノイズを効果的に除去できます。

テストベンチを作成して動作を確認しましょう。

module debounce_tb;
    reg clk, noisy_input;
    wire clean_output;

    debounce dut(
        .clk(clk),
        .noisy_input(noisy_input),
        .clean_output(clean_output)
    );

    initial begin
        clk = 0;
        forever #5 clk = ~clk;  // 10単位時間周期のクロック
    end

    initial begin
        $dumpfile("debounce.vcd");
        $dumpvars(0, debounce_tb);

        noisy_input = 0;
        #20 noisy_input = 1;
        #2  noisy_input = 0;
        #3  noisy_input = 1;
        #30 noisy_input = 0;
        #40 $finish;
    end
endmodule

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

       0   10  20  30  40  50  60  70  80  90
       |   |   |   |   |   |   |   |   |   |
clk ___|___|___|___|___|___|___|___|___|___

noisy _____|_|_|_____________________|______

clean ______________|__________________|____

ノイズの多い入力信号がクリーンな出力信号に変換されている様子が確認できます。

○サンプルコード11:パイプライン処理での遅延活用

パイプライン処理は、大規模な演算を複数のステージに分割して並列実行する手法です。

各ステージに適切な遅延を設定することで、効率的なパイプラインを構築できます。

module pipeline(
    input clk,
    input [7:0] data_in,
    output reg [7:0] data_out
);

reg [7:0] stage1, stage2, stage3;

always @(posedge clk) begin
    #2 stage1 <= data_in + 8'd1;  // ステージ1: 加算
    #3 stage2 <= stage1 * 2;      // ステージ2: 乗算
    #1 stage3 <= stage2 - 8'd3;   // ステージ3: 減算
    #1 data_out <= stage3;        // 最終出力
end

endmodule

各ステージに異なる遅延を設定することで、演算の複雑さに応じた処理時間を確保しています。

テストベンチを作成して動作を確認しましょう。

module pipeline_tb;
    reg clk;
    reg [7:0] data_in;
    wire [7:0] data_out;

    pipeline dut(
        .clk(clk),
        .data_in(data_in),
        .data_out(data_out)
    );

    initial begin
        clk = 0;
        forever #5 clk = ~clk;  // 10単位時間周期のクロック
    end

    initial begin
        $dumpfile("pipeline.vcd");
        $dumpvars(0, pipeline_tb);

        data_in = 8'd10;
        #20 data_in = 8'd20;
        #10 data_in = 8'd30;
        #40 $finish;
    end
endmodule

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

       0   10  20  30  40  50  60  70
       |   |   |   |   |   |   |   |
clk ___|___|___|___|___|___|___|___

in   10      20  30

out  00      00  00  19  39  59  59

入力データが段階的に処理され、最終的な出力が得られる様子が確認できます。

○サンプルコード12:FSMでの状態遷移遅延

有限状態機械(FSM)は、デジタル回路設計でよく使用されるモデルです。

状態遷移に遅延を追加することで、より現実的な動作をシミュレートできます。

module fsm_with_delay(
    input clk,
    input reset,
    input trigger,
    output reg [1:0] state
);

parameter S0 = 2'b00, S1 = 2'b01, S2 = 2'b10, S3 = 2'b11;

always @(posedge clk or posedge reset) begin
    if (reset)
        state <= S0;
    else begin
        case (state)
            S0: if (trigger) #3 state <= S1;  // 3単位時間の遅延
            S1: #5 state <= S2;               // 5単位時間の遅延
            S2: #2 state <= S3;               // 2単位時間の遅延
            S3: #4 state <= S0;               // 4単位時間の遅延
        endcase
    end
end

endmodule

各状態遷移に異なる遅延を設定することで、状態ごとの処理時間の違いを表現しています。

テストベンチを作成して動作を確認しましょう。

module fsm_with_delay_tb;
    reg clk, reset, trigger;
    wire [1:0] state;

    fsm_with_delay dut(
        .clk(clk),
        .reset(reset),
        .trigger(trigger),
        .state(state)
    );

    initial begin
        clk = 0;
        forever #5 clk = ~clk;  // 10単位時間周期のクロック
    end

    initial begin
        $dumpfile("fsm_with_delay.vcd");
        $dumpvars(0, fsm_with_delay_tb);

        reset = 1;
        trigger = 0;
        #15 reset = 0;
        #10 trigger = 1;
        #5  trigger = 0;
        #100 $finish;
    end
endmodule

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

       0   10  20  30  40  50  60  70  80  90  100
       |   |   |   |   |   |   |   |   |   |   |
clk ___|___|___|___|___|___|___|___|___|___|___

rst ___|___________________________________________

trg ___________|_|_________________________________

st  00  00  00  00  01  01  10  11  00  00  00  00

状態遷移が設定した遅延に従って行われる様子が確認できます。

○サンプルコード13:バスアービトレーションの遅延制御

複数のデバイスが共有バスを使用する場合、バスアービトレーションが必要になります。

遅延を利用して、優先度に基づくアービトレーションを実装できます。

module bus_arbiter(
    input clk,
    input reset,
    input [2:0] request,
    output reg [2:0] grant
);

always @(posedge clk or posedge reset) begin
    if (reset)
        grant <= 3'b000;
    else begin
        if (request[0])      #1 grant <= 3'b001;  // 最高優先度
        else if (request[1]) #2 grant <= 3'b010;  // 中間優先度
        else if (request[2]) #3 grant <= 3'b100;  // 最低優先度
        else                 #1 grant <= 3'b000;  // アイドル状態
    end
end

endmodule

優先度に応じて異なる遅延を設定することで、高優先度のリクエストがより速く処理される仕組みを実現しています。

テストベンチを作成して動作を確認しましょう。

module bus_arbiter_tb;
    reg clk, reset;
    reg [2:0] request;
    wire [2:0] grant;

    bus_arbiter dut(
        .clk(clk),
        .reset(reset),
        .request(request),
        .grant(grant)
    );

    initial begin
        clk = 0;
        forever #5 clk = ~clk;  // 10単位時間周期のクロック
    end

    initial begin
        $dumpfile("bus_arbiter.vcd");
        $dumpvars(0, bus_arbiter_tb);

        reset = 1;
        request = 3'b000;
        #15 reset = 0;
        #10 request = 3'b011;  // デバイス0と1が同時にリクエスト
        #20 request = 3'b100;  // デバイス2のみリクエスト
        #20 request = 3'b000;  // リクエストなし
        #40 $finish;
    end
endmodule

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

       0   10  20  30  40  50  60  70  80  90  100
       |   |   |   |   |   |   |   |   |   |   |
clk ___|___|___|___|___|___|___|___|___|___|___

rst ___|___________________________________________

req  000 000 011 011 100 100 000 000 000 000 000

gnt  000 000 000 001 001 100 100 000 000 000 000

優先度に基づいてバスの使用権が割り当てられる様子が確認できます。

まとめ

Verilogにおける慣性遅延の重要性と活用方法について、詳しく見てきました。

遅延を適切に設定することで、より現実的な回路動作をシミュレートし、潜在的な問題を事前に発見できます。

本記事で学んだことを基に、さらに深い知識を探求し、キャリアアップにつなげていってください。

Verilogの奥深さを楽しみながら、エンジニアとしての成長を続けていくことをお勧めします。