読み込み中...

Verilogにおけるcross関数の基礎知識と活用10選

cross関数 徹底解説 Verilog
この記事は約70分で読めます。

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

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

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

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

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

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

●Verilogのcross関数とは?

ハードウェア設計の分野で重要な役割を果たすVerilogについて、皆さんはどのくらい詳しいでしょうか。

今回は、その中でも特に注目すべき機能である「cross関数」に焦点を当てて解説していきます。

設計検証の効率を大幅に向上させる、この強力な機能の魅力に迫ります。

○cross関数の定義と基本的な役割

Verilogにおけるcross関数は、複数の信号や変数の組み合わせを効率的に検証するための機能です。

設計者が予期していなかった信号の相互作用を捉えることができ、バグの早期発見に役立ちます。

複雑な設計において、全ての可能な状態を網羅的にテストすることは難しいですが、cross関数を使用することで、その課題を克服できます。

例えば、ある状態マシンと入力信号の関係を検証する場合、個別に見ていては気づきにくい問題も、cross関数を使用することで容易に発見できるようになります。

また、プロトコルの準拠性確認においても、複数の信号の関係性を一度に検証できるため、非常に有用です。

○cross関数の文法と使用方法

cross関数の基本的な文法は次のようになっています。

cross variable1, variable2, ..., variableN;

実際の使用例を見てみましょう。

covergroup cg @(posedge clk);
  cp_state: coverpoint state;
  cp_input: coverpoint input;
  cross_state_input: cross cp_state, cp_input;
endgroup

上記の例では、stateinputという2つの信号の全ての組み合わせをカバーしています。

クロックの立ち上がりエッジごとに、この組み合わせが記録されます。

cross関数は、covergroup内で定義します。

複数のcoverpointを指定することで、それらの信号の全ての組み合わせを自動的に生成し、検証します。

○サンプルコード1:シンプルなcross関数の実装

具体的な実装例を見てみましょう。

簡単な状態マシンと入力信号を使用したサンプルコードです。

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

  always @(posedge clk or posedge reset) begin
    if (reset)
      state <= 2'b00;
    else
      case (state)
        2'b00: state <= (input_signal == 2'b11) ? 2'b01 : 2'b00;
        2'b01: state <= (input_signal == 2'b00) ? 2'b10 : 2'b01;
        2'b10: state <= (input_signal == 2'b01) ? 2'b00 : 2'b10;
        default: state <= 2'b00;
      endcase
  end

  covergroup state_input_cg @(posedge clk);
    cp_state: coverpoint state;
    cp_input: coverpoint input_signal;
    cross_state_input: cross cp_state, cp_input;
  endgroup

  state_input_cg cg_inst = new();

endmodule

このサンプルコードでは、2ビットの状態(state)と2ビットの入力信号(input_signal)を使用しています。

state_input_cgというcovergroupを定義し、その中でcross関数を使用しています。

cross関数によって、stateinput_signalの全ての組み合わせ(4 x 4 = 16通り)がカバーされます。

シミュレーション中に、各組み合わせが少なくとも1回は発生したかどうかを確認できます。

実行結果を確認するには、シミュレーションツールのカバレッジレポート機能を使用します。

例えば、次のような出力が得られる可能性があります。

Coverage Report:
Covergroup: state_input_cg
  Cross: cross_state_input
    Covered combinations: 12/16 (75%)
    Uncovered combinations:
      state=2'b10, input_signal=2'b11
      state=2'b11, input_signal=2'b00
      state=2'b11, input_signal=2'b01
      state=2'b11, input_signal=2'b10

上記の結果から、16通りの組み合わせのうち12通りがカバーされていることがわかります。

未カバーの組み合わせは、設計上起こりえない状態か、あるいはテストケースが不十分である可能性を示唆しています。

●cross関数の重要性と活用法

設計の複雑さが増す中で、cross関数の重要性も高まっています。

単一の信号だけを見ていては気づけない問題も、複数の信号の相関関係を調べることで発見できるケースが多々あります。

○カバレッジ向上におけるcross関数の役割

カバレッジとは、設計のどの部分がテストされたかを示す指標です。

cross関数を使用することで、複数の信号や状態の組み合わせに対するカバレッジを効率的に測定できます。

例えば、ある機能ブロックの入力と出力の関係を検証する場合、個別に入力と出力のカバレッジを見るだけでは不十分です。

cross関数を使用すれば、入力と出力の全ての組み合わせをカバーできているかを一目で確認できます。

○複数信号の相関関係を検証する方法

複数の信号の相関関係を検証するためには、まず関連する信号を特定し、それらをcross関数の引数として指定します。

次に、期待される相関関係を定義し、シミュレーション結果と比較します。

具体的な手順は次の通りです。

  1. 関連する信号を特定する
  2. それらの信号に対するcoverpointを定義する
  3. cross関数を使用して、coverpointの組み合わせを指定する
  4. シミュレーションを実行する
  5. カバレッジレポートを分析し、未カバーの組み合わせや予期せぬ相関関係を確認する

この方法を使うことで、設計の隠れた問題点を効果的に発見できます。

○サンプルコード2:複雑な状態遷移のカバレッジ検証

より複雑な例として、ATMの状態遷移をモデル化したサンプルコードを見てみましょう。

module atm_state_machine(
  input clk,
  input reset,
  input card_inserted,
  input pin_entered,
  input transaction_selected,
  input transaction_completed,
  output reg [2:0] state
);

  localparam IDLE = 3'b000;
  localparam CARD_INSERTED = 3'b001;
  localparam PIN_ENTERED = 3'b010;
  localparam TRANSACTION_SELECTED = 3'b011;
  localparam PROCESSING = 3'b100;
  localparam COMPLETED = 3'b101;

  always @(posedge clk or posedge reset) begin
    if (reset)
      state <= IDLE;
    else
      case (state)
        IDLE: state <= card_inserted ? CARD_INSERTED : IDLE;
        CARD_INSERTED: state <= pin_entered ? PIN_ENTERED : CARD_INSERTED;
        PIN_ENTERED: state <= transaction_selected ? TRANSACTION_SELECTED : PIN_ENTERED;
        TRANSACTION_SELECTED: state <= PROCESSING;
        PROCESSING: state <= transaction_completed ? COMPLETED : PROCESSING;
        COMPLETED: state <= IDLE;
        default: state <= IDLE;
      endcase
  end

  covergroup atm_cg @(posedge clk);
    cp_state: coverpoint state;
    cp_card: coverpoint card_inserted;
    cp_pin: coverpoint pin_entered;
    cp_transaction: coverpoint transaction_selected;
    cp_completed: coverpoint transaction_completed;

    cross_state_inputs: cross cp_state, cp_card, cp_pin, cp_transaction, cp_completed;
  endgroup

  atm_cg cg_inst = new();

endmodule

このサンプルコードでは、ATMの動作を6つの状態(IDLE, CARD_INSERTED, PIN_ENTERED, TRANSACTION_SELECTED, PROCESSING, COMPLETED)でモデル化しています。

cross関数を使用して、状態と4つの入力信号(card_inserted, pin_entered, transaction_selected, transaction_completed)の全ての組み合わせをカバーしています。

シミュレーション後のカバレッジレポートは次のようになる可能性があります。

Coverage Report:
Covergroup: atm_cg
  Cross: cross_state_inputs
    Covered combinations: 28/192 (14.58%)
    Uncovered combinations: 164
    Some uncovered combinations:
      state=IDLE, card_inserted=1, pin_entered=1, transaction_selected=1, transaction_completed=1
      state=CARD_INSERTED, card_inserted=0, pin_entered=1, transaction_selected=1, transaction_completed=1
      state=PIN_ENTERED, card_inserted=1, pin_entered=0, transaction_selected=1, transaction_completed=1
      ...

このレポートから、多くの組み合わせが未カバーであることがわかります。

しかし、全ての組み合わせが有効というわけではありません。

例えば、IDLE状態でpin_entered=1は意味を持ちません。

設計者は、このレポートを基に次のような分析を行えます。

  1. 有効な組み合わせが全てカバーされているか確認する
  2. 無効な組み合わせが発生していないか確認する
  3. 予期せぬ状態遷移が発生していないか確認する

cross関数を使用することで、このような複雑な状態遷移の検証を効率的に行うことができます。

カバレッジ結果を詳細に分析することで、設計の問題点や不足しているテストケースを特定し、より堅牢な設計を実現できるのです。

○サンプルコード3:プロトコル準拠性の確認

次に、簡略化したI2Cプロトコルの準拠性を確認するサンプルコードを見てみましょう。

I2Cプロトコルは、SCL(クロック)線とSDA(データ)線を使用する2線式のシリアル通信プロトコルです。

module i2c_protocol_checker(
  input clk,
  input reset,
  input scl,
  input sda,
  output reg [1:0] state
);

  localparam IDLE = 2'b00;
  localparam START = 2'b01;
  localparam DATA = 2'b10;
  localparam STOP = 2'b11;

  reg prev_scl, prev_sda;

  always @(posedge clk or posedge reset) begin
    if (reset) begin
      state <= IDLE;
      prev_scl <= 1'b1;
      prev_sda <= 1'b1;
    end else begin
      prev_scl <= scl;
      prev_sda <= sda;

      case (state)
        IDLE: state <= (prev_scl && scl && prev_sda && !sda) ? START : IDLE;
        START: state <= (!prev_scl && scl) ? DATA : START;
        DATA: begin
          if (prev_scl && scl && !prev_sda && sda)
            state <= STOP;
          else if (!prev_scl && scl)
            state <= DATA;
        end
        STOP: state <= IDLE;
      endcase
    end
  end

  covergroup i2c_cg @(posedge clk);
    cp_state: coverpoint state;
    cp_scl: coverpoint scl;
    cp_sda: coverpoint sda;

    cross_protocol: cross cp_state, cp_scl, cp_sda {
      ignore_bins invalid = binsof(cp_state) intersect {IDLE, STOP} &&
                            binsof(cp_scl) intersect {0};
    }
  endgroup

  i2c_cg cg_inst = new();

endmodule

上記のコードでは、I2Cプロトコルの基本的な状態(IDLE, START, DATA, STOP)をモデル化しています。

cross関数を使用して、状態とSCL、SDAの全ての組み合わせをカバーしています。

注目すべき点は、ignore_binsを使用している部分です。I2Cプロトコルでは、IDLE状態とSTOP状態でSCLが0になることは通常ありません。

そのため、状態がIDLEまたはSTOPで、かつSCLが0の組み合わせは無視するようにしています。

シミュレーション後のカバレッジレポートは次のようになる可能性があります。

Coverage Report:
Covergroup: i2c_cg
  Cross: cross_protocol
    Covered combinations: 12/16 (75%)
    Ignored combinations: 2
    Uncovered combinations:
      state=DATA, scl=0, sda=1
      state=STOP, scl=1, sda=0

このレポートから、次のような分析が可能です。

  1. START状態とDATA状態の全ての有効な組み合わせがカバーされているか
  2. 無視されるべき組み合わせ(IDLEまたはSTOP状態でSCL=0)が正しく無視されているか
  3. 未カバーの組み合わせが本当に発生しうるものか、それとも設計上の問題を示唆しているか

例えば、未カバーの組み合わせ「state=STOP, scl=1, sda=0」は、STOP条件(SCL=1, SDA: 0→1)の途中状態を表している可能性があります。

●cross関数と他の機能の比較

Verilogの設計検証において、cross関数は非常に有用なツールですが、他の機能との使い分けや連携も重要です。

ここでは、cross関数と他の主要な機能を比較し、それぞれの特徴や適切な使用場面について詳しく見ていきましょう。

○サンプルコード4:cross関数とcoverpointの使い分け

cross関数とcoverpointは、どちらもカバレッジを測定するための機能ですが、使用目的が異なります。

coverpointは単一の変数や信号の値をカバーするのに対し、cross関数は複数の変数や信号の組み合わせをカバーします。

次のサンプルコードで、実際の使い方の違いを見てみましょう。

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

  always @(posedge clk or posedge reset) begin
    if (reset)
      count <= 4'b0000;
    else if (enable)
      count <= count + 1;
  end

  covergroup cg @(posedge clk);
    cp_count: coverpoint count;
    cp_enable: coverpoint enable;
    cross_count_enable: cross cp_count, cp_enable;
  endgroup

  cg cg_inst = new();

endmodule

このコードでは、4ビットのカウンターとイネーブル信号を使用しています。

coverpointとcross関数を使って、次の点を検証しています。

  1. cp_count: カウンターの各値(0〜15)がカバーされているか
  2. cp_enable: イネーブル信号の両方の状態(0と1)がカバーされているか
  3. cross_count_enable: カウンターの各値とイネーブル信号の組み合わせがカバーされているか

シミュレーション後のカバレッジレポートは、次のようになる可能性があります。

Coverage Report:
Covergroup: cg
  Coverpoint: cp_count
    Bins:
      0: 1
      1-14: 1 each
      15: 1
    Coverage: 100%

  Coverpoint: cp_enable
    Bins:
      0: 1
      1: 1
    Coverage: 100%

  Cross: cross_count_enable
    Covered combinations: 30/32 (93.75%)
    Uncovered combinations:
      count=14, enable=0
      count=15, enable=0

このレポートから、次のような分析ができます。

  1. カウンターの全ての値(0〜15)がカバーされている
  2. イネーブル信号の両方の状態(0と1)がカバーされている
  3. カウンターの値とイネーブル信号の大部分の組み合わせがカバーされているが、カウンターが14と15の時にイネーブルが0になる組み合わせがカバーされていない

coverpointだけでは、カウンターの値とイネーブル信号の関係を詳細に把握することは難しいですが、cross関数を使用することで、より深い洞察が得られます。

例えば、カウンターが最大値に近い時にイネーブルが0になるケースが発生していないことがわかります。

設計者は、この情報を基に、テストケースを追加したり、設計の問題点を特定したりすることができます。

○サンプルコード5:cross関数とcovergroupの連携

cross関数は通常、covergroup内で使用されます。

covergroupは、複数のcoverpointやcross関数をグループ化し、関連するカバレッジ情報をまとめて管理するための機能です。

次のサンプルコードで、covergroupとcross関数の連携を見てみましょう。

module traffic_light_controller(
  input clk,
  input reset,
  output reg [1:0] main_street,
  output reg [1:0] side_street
);

  localparam RED = 2'b00, YELLOW = 2'b01, GREEN = 2'b10;

  reg [2:0] state;
  localparam S0 = 3'b000, S1 = 3'b001, S2 = 3'b010, S3 = 3'b011, S4 = 3'b100;

  always @(posedge clk or posedge reset) begin
    if (reset) begin
      state <= S0;
      main_street <= RED;
      side_street <= RED;
    end else begin
      case (state)
        S0: begin
          state <= S1;
          main_street <= GREEN;
          side_street <= RED;
        end
        S1: begin
          state <= S2;
          main_street <= YELLOW;
          side_street <= RED;
        end
        S2: begin
          state <= S3;
          main_street <= RED;
          side_street <= RED;
        end
        S3: begin
          state <= S4;
          main_street <= RED;
          side_street <= GREEN;
        end
        S4: begin
          state <= S0;
          main_street <= RED;
          side_street <= YELLOW;
        end
      endcase
    end
  end

  covergroup traffic_cg @(posedge clk);
    cp_state: coverpoint state;
    cp_main: coverpoint main_street;
    cp_side: coverpoint side_street;

    cross_main_side: cross cp_main, cp_side {
      illegal_bins illegal = binsof(cp_main) intersect {GREEN} &&
                             binsof(cp_side) intersect {GREEN};
    }

    cross_state_lights: cross cp_state, cp_main, cp_side;
  endgroup

  traffic_cg cg_inst = new();

endmodule

このコードは、簡単な交通信号制御システムをモデル化しています。

covergroupとcross関数を使用して、次の点を検証しています。

  1. cp_state: 全ての状態が適切に遷移しているか
  2. cp_main, cp_side: メインストリートとサイドストリートの信号が全ての状態(赤、黄、緑)を取るか
  3. cross_main_side: メインストリートとサイドストリートの信号の組み合わせが適切か(同時に緑にならないことを確認)
  4. cross_state_lights: 状態と両方の信号の組み合わせが適切か

シミュレーション後のカバレッジレポートは、次のようになる可能性があります。

Coverage Report:
Covergroup: traffic_cg
  Coverpoint: cp_state
    Bins:
      S0-S4: 1 each
    Coverage: 100%

  Coverpoint: cp_main
    Bins:
      RED: 1
      YELLOW: 1
      GREEN: 1
    Coverage: 100%

  Coverpoint: cp_side
    Bins:
      RED: 1
      YELLOW: 1
      GREEN: 1
    Coverage: 100%

  Cross: cross_main_side
    Covered combinations: 8/9 (88.89%)
    Illegal combinations: 1
      main_street=GREEN, side_street=GREEN

  Cross: cross_state_lights
    Covered combinations: 5/45 (11.11%)
    Uncovered combinations: 40

このレポートから、次のような分析ができます。

  1. 全ての状態が適切に遷移している
  2. メインストリートとサイドストリートの信号が全ての状態を取っている
  3. メインストリートとサイドストリートが同時に緑になる不正な組み合わせが発生していない
  4. 状態と信号の組み合わせのうち、一部しかカバーされていない

特に、cross_state_lightsのカバレッジが低い点に注目すべきです。

全ての組み合わせがカバーされる必要はありませんが、設計者は意図した組み合わせが全てカバーされているか確認する必要があります。

○サンプルコード6:cross関数とアサーションの組み合わせ

cross関数とアサーションを組み合わせることで、より包括的な検証が可能になります。

cross関数がカバレッジを測定する一方で、アサーションは特定の条件が常に満たされていることを確認します。

次のサンプルコードで、cross関数とアサーションの組み合わせを見てみましょう。

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

  reg [1:0] state;

  always @(posedge clk or posedge reset) begin
    if (reset) begin
      state <= 2'b00;
      grant <= 2'b00;
    end else begin
      case (state)
        2'b00: begin
          if (request[0]) begin
            grant <= 2'b01;
            state <= 2'b01;
          end else if (request[1]) begin
            grant <= 2'b10;
            state <= 2'b10;
          end
        end
        2'b01: begin
          if (!request[0]) begin
            grant <= 2'b00;
            state <= 2'b00;
          end
        end
        2'b10: begin
          if (!request[1]) begin
            grant <= 2'b00;
            state <= 2'b00;
          end
        end
      endcase
    end
  end

  covergroup arb_cg @(posedge clk);
    cp_request: coverpoint request;
    cp_grant: coverpoint grant;
    cross_req_grant: cross cp_request, cp_grant;
  endgroup

  arb_cg cg_inst = new();

  // アサーション
  property mutex_grant;
    @(posedge clk) $onehot0(grant);
  endproperty

  assert property (mutex_grant)
    else $error("Multiple grants detected!");

  // バインド済みカバーポイント
  cover property (@(posedge clk) request[0] ##1 grant[0]);
  cover property (@(posedge clk) request[1] ##1 grant[1]);

endmodule

このコードは、2つのリクエストを処理するシンプルなアービターをモデル化しています。

cross関数とアサーションを使用して、次の点を検証しています。

  1. cross_req_grant: リクエストとグラントの全ての組み合わせがカバーされているか
  2. mutex_grant: 常に最大1つのグラントのみが発行されているか(アサーション)
  3. 各リクエストが適切にグラントされているか(バインド済みカバーポイント)

シミュレーション後のカバレッジレポートとアサーション結果は、次のようになる可能性があります。

Coverage Report:
Covergroup: arb_cg
  Coverpoint: cp_request
    Bins:
      00: 1
      01: 1
      10: 1
      11: 1
    Coverage: 100%

  Coverpoint: cp_grant
    Bins:
      00: 1
      01: 1
      10: 1
    Coverage: 100%

  Cross: cross_req_grant
    Covered combinations: 11/16 (68.75%)
    Uncovered combinations:
      request=01, grant=10
      request=10, grant=01
      request=11, grant=00
      request=11, grant=01
      request=11, grant=10

Assertion Results:
  mutex_grant: Pass (0 failures)

Bound Coverpoints:
  request[0] ##1 grant[0]: Covered
  request[1] ##1 grant[1]: Covered

このレポートから、次のような分析ができます。

  1. リクエストとグラントの大部分の組み合わせがカバーされているが、一部の組み合わせ(特に両方のリクエストが同時に発生する場合)がカバーされていない
  2. 常に最大1つのグラントのみが発行されている(アサーションが成功)
  3. 各リクエストが適切にグラントされている(バインド済みカバーポイントがカバーされている)

cross関数によるカバレッジ測定だけでなく、アサーションとバインド済みカバーポイントを組み合わせることで、設計の正確性と完全性をより包括的に検証できます。

カバーされていない組み合わせについては、設計の意図を再確認し、必要に応じてテストケースを追加することが推奨されます。

●cross関数の実践的な活用シナリオ

cross関数の理論的な理解だけでなく、実際の設計検証シナリオでどのように活用できるかを知ることは非常に重要です。

ここでは、具体的な活用例を通じて、cross関数の実践的な使い方を学んでいきましょう。

○サンプルコード7:設計検証でのバグ早期発見

複雑な設計では、特定の条件の組み合わせでのみ発生するバグを見つけるのが難しい場合があります。

cross関数を使用することで、そのようなバグを早期に発見できる可能性が高まります。

次のサンプルコードで、FIFOの設計におけるバグ発見のシナリオを見てみましょう。

module fifo #(
  parameter WIDTH = 8,
  parameter DEPTH = 16
)(
  input clk,
  input reset,
  input write_en,
  input read_en,
  input [WIDTH-1:0] data_in,
  output reg [WIDTH-1:0] data_out,
  output reg full,
  output reg empty
);

  reg [WIDTH-1:0] mem [0:DEPTH-1];
  reg [$clog2(DEPTH):0] write_ptr, read_ptr, count;

  always @(posedge clk or posedge reset) begin
    if (reset) begin
      write_ptr <= 0;
      read_ptr <= 0;
      count <= 0;
      full <= 0;
      empty <= 1;
    end else begin
      if (write_en && !full) begin
        mem[write_ptr] <= data_in;
        write_ptr <= (write_ptr + 1) % DEPTH;
        count <= count + 1;
      end
      if (read_en && !empty) begin
        data_out <= mem[read_ptr];
        read_ptr <= (read_ptr + 1) % DEPTH;
        count <= count - 1;
      end
      full <= (count == DEPTH - 1);
      empty <= (count == 0);
    end
  end

  covergroup fifo_cg @(posedge clk);
    cp_write_en: coverpoint write_en;
    cp_read_en: coverpoint read_en;
    cp_full: coverpoint full;
    cp_empty: coverpoint empty;
    cp_count: coverpoint count {
      bins low = {0, 1};
      bins mid = {[2:DEPTH-2]};
      bins high = {DEPTH-1, DEPTH};
    }

    cross_ops: cross cp_write_en, cp_read_en, cp_full, cp_empty;
    cross_count_ops: cross cp_count, cp_write_en, cp_read_en;
  endgroup

  fifo_cg cg_inst = new();

  // アサーション
  property valid_count;
    @(posedge clk) (count >= 0) && (count <= DEPTH);
  endproperty

  assert property (valid_count)
    else $error("Invalid count detected!");

endmodule

このFIFO設計では、cross関数とアサーションを組み合わせて、以下の点を検証しています。

  1. cross_ops: 書き込み、読み出し、full、emptyの全ての組み合わせがカバーされているか
  2. cross_count_ops: FIFOの占有状態(低、中、高)と操作(書き込み、読み出し)の組み合わせがカバーされているか
  3. valid_count: カウントが常に有効な範囲内にあるか(アサーション)

シミュレーション後のカバレッジレポートとアサーション結果は、次のようになる可能性があります。

Coverage Report:
Covergroup: fifo_cg
  Cross: cross_ops
    Covered combinations: 15/16 (93.75%)
    Uncovered combinations:
      write_en=1, read_en=1, full=1, empty=1

  Cross: cross_count_ops
    Covered combinations: 11/12 (91.67%)
    Uncovered combinations:
      count=high, write_en=1, read_en=0

Assertion Results:
  valid_count: Fail (1 failure at time 1250ns)

このレポートから、次のような分析ができます。

  1. FIFOが同時にfullとemptyになる状態が発生していない(正常な動作)
  2. FIFOがほぼ満杯の状態で書き込みのみを行う状況がカバーされていない
  3. カウントが無効な値になる瞬間が存在する(アサーション失敗)

特に注目すべきは、アサーションの失敗です。

カウントが無効な値になるということは、FIFOの基本的な動作に問題があることを示唆しています。

設計者は、この情報を基に、カウントの更新ロジックを詳細に調査する必要があります。

具体的には、次のような修正が考えられます。

  always @(posedge clk or posedge reset) begin
    if (reset) begin
      // ... (reset logic remains the same)
    end else begin
      if (write_en && !full) begin
        mem[write_ptr] <= data_in;
        write_ptr <= (write_ptr + 1) % DEPTH;
        if (!read_en || empty) count <= count + 1;
      end
      if (read_en && !empty) begin
        data_out <= mem[read_ptr];
        read_ptr <= (read_ptr + 1) % DEPTH;
        if (!write_en || full) count <= count - 1;
      end
      full <= (count == DEPTH - 1 && write_en && !read_en) || (count == DEPTH);
      empty <= (count == 1 && read_en && !write_en) || (count == 0);
    end
  end

この修正では、同時に読み書きが行われる場合のカウントの更新ロジックを調整し、fullとemptyフラグの設定条件も微調整しています。

cross関数を使用することで、FIFOの様々な状態と操作の組み合わせを効率的にカバーし、通常のテストケースでは見逃されやすいエッジケースを発見することができました。

また、アサーションと組み合わせることで、設計の正確性を継続的に検証することが可能になります。

○サンプルコード8:デバッグ時のトラブルシューティング

デバッグ過程でcross関数を活用することで、問題の根本原因を特定しやすくなります。

例えば、特定の状態遷移でのみ発生する問題を追跡する場合、cross関数が非常に役立ちます。

次のサンプルコードで、バス・プロトコル・コントローラーのデバッグシナリオを見てみましょう。

module bus_controller(
  input clk,
  input reset,
  input request,
  input [1:0] priority,
  input [7:0] data,
  output reg grant,
  output reg [7:0] bus_data
);

  typedef enum {IDLE, ARBITRATION, TRANSFER, COMPLETE} state_t;
  state_t state, next_state;

  reg [1:0] current_priority;
  reg [7:0] buffered_data;

  always @(posedge clk or posedge reset) begin
    if (reset) begin
      state <= IDLE;
      grant <= 0;
      bus_data <= 8'h00;
      current_priority <= 2'b00;
      buffered_data <= 8'h00;
    end else begin
      state <= next_state;
      case (state)
        IDLE: begin
          if (request) begin
            current_priority <= priority;
            buffered_data <= data;
          end
        end
        ARBITRATION: begin
          if (priority > current_priority) begin
            current_priority <= priority;
            buffered_data <= data;
          end
        end
        TRANSFER: begin
          grant <= 1;
          bus_data <= buffered_data;
        end
        COMPLETE: begin
          grant <= 0;
          bus_data <= 8'h00;
        end
      endcase
    end
  end

  always @* begin
    next_state = state;
    case (state)
      IDLE: if (request) next_state = ARBITRATION;
      ARBITRATION: next_state = TRANSFER;
      TRANSFER: next_state = COMPLETE;
      COMPLETE: next_state = IDLE;
    endcase
  end

  covergroup bus_cg @(posedge clk);
    cp_state: coverpoint state;
    cp_next_state: coverpoint next_state;
    cp_request: coverpoint request;
    cp_priority: coverpoint priority;

    cross_state_trans: cross cp_state, cp_next_state;
    cross_state_req_pri: cross cp_state, cp_request, cp_priority;
  endgroup

  bus_cg cg_inst = new();

  // アサーション
  property valid_arbitration;
    @(posedge clk) (state == ARBITRATION) |=> (state == TRANSFER);
  endproperty

  assert property (valid_arbitration)
    else $error("Invalid arbitration detected!");

endmodule

このバス・コントローラー設計では、cross関数とアサーションを使用して、次の点を検証しています。

  1. cross_state_trans: 全ての状態遷移の組み合わせがカバーされているか
  2. cross_state_req_pri: 各状態でのリクエストと優先度の組み合わせがカバーされているか
  3. valid_arbitration: 仲裁状態が常に転送状態に移行するか(アサーション)

シミュレーション後のカバレッジレポートとアサーション結果は、次のようになる可能性があります。

Coverage Report:
Covergroup: bus_cg
  Cross: cross_state_trans
    Covered combinations: 5/16 (31.25%)
    Uncovered combinations:
      state=IDLE, next_state=TRANSFER
      state=IDLE, next_state=COMPLETE
      state=ARBITRATION, next_state=IDLE
      state=ARBITRATION, next_state=COMPLETE
      ...

  Cross: cross_state_req_pri
    Covered combinations: 18/32 (56.25%)
    Uncovered combinations:
      state=TRANSFER, request=1, priority=2'b01
      state=TRANSFER, request=1, priority=2'b10
      state=COMPLETE, request=1, priority=2'b01
      state=COMPLETE, request=1, priority=2'b10
      ...

Assertion Results:
  valid_arbitration: Pass (0 failures)

このレポートから、次のような分析ができます。

  1. 一部の状態遷移しかカバーされていない(例:IDLEからTRANSFERへの直接遷移)
  2. 転送状態とcomplete状態でリクエストが発生するケースがカバーされていない
  3. 仲裁から転送への遷移は常に正しく行われている(アサーション成功)

特に注目すべきは、cross_state_transのカバレッジが低い点です。

設計意図としては、IDLEからARBITRATION、ARBITRATIONからTRANSFER、TRANSFERからCOMPLETE、COMPLETEからIDLEという遷移のみが許可されるはずですが、他の遷移も理論的には可能な状態になっています。

この情報を基に、設計者は状態遷移ロジックを再確認し、必要に応じて修正を加えることができます。

例えば、次のような修正が考えられます。

  always @* begin
    next_state = state;
    case (state)
      IDLE: if (request) next_state = ARBITRATION;
      ARBITRATION: next_state = TRANSFER;
      TRANSFER: next_state = COMPLETE;
      COMPLETE: next_state = IDLE;
      default: next_state = IDLE;  // 安全のため、未定義状態はIDLEに戻す
    endcase
  end

また、転送状態とcomplete状態でのリクエスト処理も再検討する必要があるかもしれません。

例えば、これらの状態でリクエストを無視するか、バッファリングするかを決定する必要があります。

cross関数を使用することで、バス・コントローラーの様々な状態と入力の組み合わせを効率的にカバーし、潜在的な設計上の問題や未考慮のケースを発見することができました。

また、アサーションと組み合わせることで、重要な動作(この場合は仲裁から転送への遷移)が常に正しく行われていることを確認できます。

○サンプルコード9:効率的なテストベンch作成

cross関数を活用することで、より効率的で包括的なテストベンchを作成することができます。

特に、ランダムなテストケース生成と組み合わせることで、設計の隅々まで検証することが可能になります。

次のサンプルコードで、キャッシュ・コントローラーのテストベンch作成シナリオを見てみましょう。

module cache_controller(
  input clk,
  input reset,
  input read_req,
  input write_req,
  input [9:0] address,
  input [31:0] write_data,
  output reg hit,
  output reg [31:0] read_data
);

  // キャッシュの簡略化されたモデル
  reg [41:0] cache [0:63];  // [valid][tag][data]
  wire [5:0] index = address[5:0];
  wire [3:0] tag = address[9:6];

  always @(posedge clk or posedge reset) begin
    if (reset) begin
      hit <= 0;
      read_data <= 32'h0;
      for (int i = 0; i < 64; i++) cache[i] <= 42'h0;
    end else begin
      if (read_req) begin
        hit <= cache[index][41] && (cache[index][40:37] == tag);
        read_data <= cache[index][31:0];
      end else if (write_req) begin
        cache[index] <= {1'b1, tag, write_data};
        hit <= 1;
      end else begin
        hit <= 0;
      end
    end
  end

endmodule

module testbench;
  reg clk, reset, read_req, write_req;
  reg [9:0] address;
  reg [31:0] write_data;
  wire hit;
  wire [31:0] read_data;

  cache_controller dut (.*);

  initial begin
    clk = 0;
    forever #5 clk = ~clk;
  end

  covergroup cache_cg @(posedge clk);
    cp_read_req: coverpoint read_req;
    cp_write_req: coverpoint write_req;
    cp_address_index: coverpoint address[5:0];
    cp_address_tag: coverpoint address[9:6];
    cp_hit: coverpoint hit;

    cross_req_type: cross cp_read_req, cp_write_req {
      illegal_bins illegal = binsof(cp_read_req) intersect {1} &&
                             binsof(cp_write_req) intersect {1};
    }

    cross_addr_hit: cross cp_address_index, cp_address_tag, cp_hit;
  endgroup

  cache_cg cg = new();

  task random_test;
    repeat(1000) begin
      @(posedge clk);
      read_req = $random;
      write_req = read_req ? 0 : $random;  // read_reqとwrite_reqは排他的
      address = $random;
      write_data = $random;
    end
  endtask

  task directed_test;
    // 特定のシナリオをテスト
    @(posedge clk);
    write_req = 1;
    read_req = 0;
    address = 10'h3FF;
    write_data = 32'hDEADBEEF;
    @(posedge clk);
    write_req = 0;
    read_req = 1;
    @(posedge clk);
    read_req = 0;
  endtask

  initial begin
    reset = 1;
    read_req = 0;
    write_req = 0;
    address = 0;
    write_data = 0;
    @(posedge clk);
    reset = 0;

    random_test();
    directed_test();

    $finish;
  end

  // アサーション
  property exclusive_req;
    @(posedge clk) !(read_req && write_req);
  endproperty

  assert property (exclusive_req)
    else $error("Read and write requests are not exclusive!");

endmodule

このテストベンchでは、cross関数とランダムテストを組み合わせて、次の点を検証しています。

  1. cross_req_type: 読み込みと書き込み要求の全ての有効な組み合わせがカバーされているか
  2. cross_addr_hit: アドレス(インデックスとタグ)とヒット/ミスの全ての組み合わせがカバーされているか
  3. ランダムなテストケースで、様々な入力の組み合わせがカバーされているか
  4. 特定のシナリオ(directed_test)で、重要なケースが確実にテストされているか
  5. 読み込みと書き込み要求が排他的であること(アサーション)

シミュレーション後のカバレッジレポートとアサーション結果は、次のようになる可能性があります。

Coverage Report:
Covergroup: cache_cg
  Cross: cross_req_type
    Covered combinations: 3/4 (75%)
    Illegal combinations: 1
      read_req=1, write_req=1

  Cross: cross_addr_hit
    Covered combinations: 492/512 (96.09%)
    Uncovered combinations: 20
      (Various combinations of index, tag, and hit)

Assertion Results:
  exclusive_req: Pass (0 failures)

このレポートから、次のような分析ができます。

  1. 読み込みと書き込み要求の有効な組み合わせは全てカバーされている
  2. アドレスとヒット/ミスの組み合わせは、ほとんどカバーされているが、一部のケースがまだカバーされていない
  3. 読み込みと書き込み要求が確実に排他的である(アサーション成功)

カバーされていない組み合わせについては、次のような対応が考えられます。

  1. ランダムテストの繰り返し回数を増やす
  2. カバーされていない特定の組み合わせを狙ったdirected_testを追加する
  3. コンストレインド・ランダム・テストを実装して、カバレッジを向上させる

例えば、次のようなコンストレインド・ランダム・テストを追加することができます。

  class cache_transaction;
    rand bit read_req;
    rand bit write_req;
    rand bit [9:0] address;
    rand bit [31:0] write_data;

    constraint c_exclusive_req {
      read_req != write_req;  // 読み込みと書き込みは排他的
    }

    constraint c_interesting_addresses {
      address[5:0] inside {0, 63};  // インデックスの境界値をテスト
      address[9:6] inside {0, 15};  // タグの境界値をテスト
    }
  endclass

  task constrained_random_test;
    cache_transaction trans = new();
    repeat(500) begin
      assert(trans.randomize());
      @(posedge clk);
      read_req = trans.read_req;
      write_req = trans.write_req;
      address = trans.address;
      write_data = trans.write_data;
    end
  endtask

このコンストレインド・ランダム・テストをinitialブロック内で実行することで、カバレッジを向上させることができます。

●cross関数の最適化テクニック

Verilogのcross関数は非常に強力なツールですが、適切に使用しないと、シミュレーション時間の増大やメモリ使用量の増加といった問題を引き起こす可能性があります。最適化テクニックを学ぶことで、効率的かつ効果的にcross関数を活用できるようになります。

○ビルトイン関数を活用したパフォーマンス向上

cross関数のパフォーマンスを向上させるために、Verilogには便利なビルトイン関数が用意されています。例えば、$onehot関数や$countones関数を使用することで、特定の条件下でのcrossポイントの数を制限し、メモリ使用量を削減できます。

具体的な例を見てみましょう。8ビットのバスを持つシステムで、アクティブなビットが1つだけの場合のみをカバーしたい場合を考えます。

covergroup cg @(posedge clk);
  cp_bus: coverpoint bus;
  cross cp_bus {
    bins active = cp_bus with ($onehot(bus));
  }
endgroup

上記のコードでは、$onehot関数を使用して、バスの値が1ホットエンコーディング(1ビットだけが1で、他は全て0)の場合のみをカバーします。256個の可能な組み合わせのうち、8個だけをカバーすることになり、メモリ使用量を大幅に削減できます。

○binの効果的な使用法

binを効果的に使用することで、カバレッジの粒度を調整し、重要な値や範囲に焦点を当てることができます。例えば、値の範囲を適切に分割したり、特定の値を個別にカバーしたりすることで、より意味のあるカバレッジ結果を得られます。

以下の例で、温度センサーの値をカバーするcross関数を考えてみましょう。

covergroup temperature_cg @(posedge clk);
  cp_temp: coverpoint temperature {
    bins low = {[0:9]};
    bins normal = {[10:35]};
    bins high = {[36:50]};
    bins extreme = {[51:100]};
  }
  cp_alarm: coverpoint alarm;

  cross cp_temp, cp_alarm;
endgroup

このコードでは、温度の範囲を4つのビンに分割し、アラーム信号とのcrossを取っています。温度範囲を適切に分割することで、重要な境界値付近の動作を効率的に検証できます。

○サンプルコード10:Synopsys VCSでの最適実装

Synopsys VCSなどの商用シミュレータを使用する場合、シミュレータ固有の最適化機能を活用することで、さらなるパフォーマンス向上が期待できます。以下は、VCSでの最適化されたcross関数の実装例です。

module optimized_cross_example(
  input clk,
  input reset,
  input [7:0] data,
  input valid,
  output reg [7:0] result
);

  always @(posedge clk or posedge reset) begin
    if (reset)
      result <= 8'h00;
    else if (valid)
      result <= data + 1;
  end

  covergroup optimized_cg @(posedge clk);
    option.per_instance = 1;
    option.goal = 100;
    option.weight = 2;

    cp_data: coverpoint data {
      option.weight = 2;
      bins low = {[0:63]};
      bins high = {[64:255]};
    }
    cp_valid: coverpoint valid;
    cp_result: coverpoint result {
      option.weight = 3;
      bins even = {[0:254]} with (item % 2 == 0);
      bins odd = {[1:255]} with (item % 2 == 1);
    }

    data_valid_cross: cross cp_data, cp_valid {
      option.weight = 4;
      ignore_bins invalid = binsof(cp_valid) intersect {0};
    }
  endgroup

  optimized_cg cg_inst = new();

endmodule

このコードでは、VCS固有の最適化オプションを使用しています。option.per_instance = 1を設定することで、各インスタンスごとにカバレッジを収集し、より詳細な情報を得られます。option.goaloption.weightを使用して、カバレッジの目標と重み付けを指定しています。

また、ignore_binsを使用して、無効な組み合わせ(この場合、validが0の場合)を無視しています。これにより、不要なカバレッジポイントを減らし、メモリ使用量を削減できます。

VCSでこのコードをシミュレーションすると、最適化されたカバレッジレポートが生成されます。例えば、以下のような出力が得られる可能性があります。

Covergroup Coverage:
  optimized_cg                      100.00%
    cp_data                         100.00%
      low                           100.00%
      high                          100.00%
    cp_valid                        100.00%
    cp_result                       100.00%
      even                          100.00%
      odd                           100.00%
    data_valid_cross                100.00%

Total Coverage: 100.00%

この結果から、全てのカバレッジポイントが達成されていることがわかります。VCSの最適化機能を使用することで、効率的にカバレッジを収集し、シミュレーション時間とメモリ使用量を削減できます。

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

cross関数を使用する際、いくつかの一般的なエラーや問題に遭遇することがあります。

ここでは、頻繁に発生するエラーとその対処法について説明します。

○シンタックスエラーの回避方法

シンタックスエラーは、cross関数を使用する際によく発生する問題です。

正しい構文を理解し、注意深くコードを記述することで、多くのエラーを回避できます。

例えば、次のようなエラーがよく見られます。

covergroup incorrect_cg @(posedge clk);
  cp_a: coverpoint a;
  cp_b: coverpoint b;
  cross cp_a, cp_b;  // エラー: クロスポイントに名前がない
endgroup

このエラーを修正するには、クロスポイントに名前を付ける必要があります。

covergroup correct_cg @(posedge clk);
  cp_a: coverpoint a;
  cp_b: coverpoint b;
  cross_ab: cross cp_a, cp_b;  // 修正: クロスポイントに名前を付ける
endgroup

また、binの定義で誤った範囲指定をするのも、よくあるミスです。

covergroup incorrect_range_cg @(posedge clk);
  cp_value: coverpoint value {
    bins low = [0:10];  // エラー: 角括弧は範囲指定に使用できない
    bins high = [11:20];
  }
endgroup

正しい範囲指定は中括弧を使用します。

covergroup correct_range_cg @(posedge clk);
  cp_value: coverpoint value {
    bins low = {[0:10]};  // 修正: 中括弧を使用して範囲を指定
    bins high = {[11:20]};
  }
endgroup

○カバレッジ不足の原因と対策

カバレッジが100%に達しない場合、設計の問題やテストケースの不足が原因である可能性があります。

カバレッジ不足の主な原因と対策について説明します。

  1. テストケースの不足 -> 対策: より多様なテストケースを追加し、未カバーの条件を意図的に発生させます。
  2. 無効な組み合わせ -> 対策: ignore_binsを使用して、無効な組み合わせを除外します。
  3. 到達不可能な状態 -> 対策: 設計を見直し、本当に到達不可能な状態かどうかを確認します。必要に応じて、カバレッジモデルを修正します。

例えば、次のようなカバレッジグループがあるとします。

covergroup state_cg @(posedge clk);
  cp_state: coverpoint state;
  cp_input: coverpoint input;
  cross_state_input: cross cp_state, cp_input;
endgroup

カバレッジレポートで、特定の状態と入力の組み合わせがカバーされていないことがわかった場合、次のような対策を取ることができます。

  1. テストケースの追加 -> 未カバーの組み合わせを意図的に発生させるテストケースを作成します。
  2. 無効な組み合わせの除外 -> 特定の状態で特定の入力が発生しない場合、それを無効な組み合わせとして除外します。
covergroup improved_state_cg @(posedge clk);
  cp_state: coverpoint state;
  cp_input: coverpoint input;
  cross_state_input: cross cp_state, cp_input {
    ignore_bins invalid = binsof(cp_state) intersect {IDLE} &&
                          binsof(cp_input) intersect {BUSY};
  }
endgroup
  1. 設計の見直し -> 到達不可能な状態がある場合、設計自体を見直し、必要に応じて修正します。

○実行時エラーのデバッグテクニック

実行時エラーは、シミュレーション中に発生する予期せぬ問題です。

cross関数に関連する実行時エラーをデバッグするためのテクニックを紹介します。

  1. シミュレーションログの詳細な分析 -> エラーメッセージを注意深く読み、問題が発生した正確な時間とコンテキストを特定します。
  2. 波形ビューアの活用 -> クロスポイントの入力信号の動作を波形ビューアで確認し、予期せぬ動作を特定します。
  3. アサーションの追加 -> クリティカルな条件にアサーションを追加し、問題の早期発見と原因特定を容易にします。

例えば、次のようなcross関数があるとします。

covergroup debug_cg @(posedge clk);
  cp_addr: coverpoint addr;
  cp_data: coverpoint data;
  cross_addr_data: cross cp_addr, cp_data;
endgroup

実行時エラーが発生した場合、次のようなデバッグ手法を適用できます。

□詳細なログ出力

クリティカルなポイントでの値をログ出力し、問題の原因を特定します。

always @(posedge clk) begin
  $display("Time %0t: addr = %h, data = %h", $time, addr, data);
end

□アサーションの追加

予期せぬ値の組み合わせをチェックするアサーションを追加します。

property valid_addr_data;
  @(posedge clk) (addr < 16'h1000) |-> (data < 32'h1000_0000);
endproperty

assert property (valid_addr_data)
  else $error("Invalid addr-data combination detected!");

□カバレッジの細分化

問題が発生しそうな範囲をより細かく分割し、詳細なカバレッジ情報を得ます。

covergroup detailed_debug_cg @(posedge clk);
  cp_addr: coverpoint addr {
    bins low = {[0:255]};
    bins mid = {[256:4095]};
    bins high = {[4096:65535]};
  }
  cp_data: coverpoint data {
    bins small = {[0:65535]};
    bins medium = {[65536:16777215]};
    bins large = {[16777216:4294967295]};
  }
  cross_addr_data: cross cp_addr, cp_data;
endgroup

これらのテクニックを組み合わせることで、実行時エラーの原因をより迅速かつ正確に特定し、問題を解決することができます。

●cross関数の応用例

Verilogのcross関数は、様々な複雑な設計シナリオで活用できる優れた機能です。

ここでは、実際の設計現場で遭遇する可能性が高い応用例を紹介します。

各例を通じて、cross関数の柔軟性と力強さを体感してください。

○サンプルコード11:データパスとコントロールの相関チェック

データパスとコントロール信号の相関関係を確認することは、設計の正確性を保証する上で非常に重要です。

cross関数を使用することで、両者の関係を効率的に検証できます。

ここでは、簡単な算術論理演算ユニット(ALU)のデータパスとコントロール信号の相関をチェックする例を見てみましょう。

module alu(
  input [3:0] a, b,
  input [1:0] op,
  output reg [3:0] result
);

  always @(*) begin
    case(op)
      2'b00: result = a + b;
      2'b01: result = a - b;
      2'b10: result = a & b;
      2'b11: result = a | b;
    endcase
  end

  covergroup alu_cg @(posedge clk);
    cp_a: coverpoint a;
    cp_b: coverpoint b;
    cp_op: coverpoint op;
    cp_result: coverpoint result;

    cross_alu: cross cp_a, cp_b, cp_op, cp_result {
      bins addition = binsof(cp_op) intersect {2'b00};
      bins subtraction = binsof(cp_op) intersect {2'b01};
      bins bit_and = binsof(cp_op) intersect {2'b10};
      bins bit_or = binsof(cp_op) intersect {2'b11};
    }
  endgroup

  alu_cg cg_inst = new();

endmodule

このコードでは、ALUの入力(a, b)、操作(op)、結果(result)の全ての組み合わせをcross関数で検証しています。

特に注目すべき点は、各操作に対応するbinsを定義していることです。

この方法により、各演算が正しく実行されているかを効率的に確認できます。

シミュレーション結果は次のようになるでしょう。

Covergroup: alu_cg
  Cross: cross_alu
    Covered bins:
      addition: 256 hits
      subtraction: 256 hits
      bit_and: 256 hits
      bit_or: 256 hits
    Total coverage: 100%

全ての操作が均等にカバーされていることが分かります。

もし特定の操作のカバレッジが低い場合、その操作に関するテストケースを追加する必要があるかもしれません。

○サンプルコード12:複雑なステートマシンの完全検証

複雑なステートマシンの全ての状態遷移を検証することは、手動では非常に困難です。

cross関数を使用することで、全ての可能な状態遷移を自動的にカバーすることができます。

ここでは、交通信号制御システムのステートマシンを検証する例を紹介します。

module traffic_light_controller(
  input clk, reset,
  input sensor,
  output reg [1:0] main_light, side_light
);

  localparam RED = 2'b00, YELLOW = 2'b01, GREEN = 2'b10;
  localparam S0 = 3'b000, S1 = 3'b001, S2 = 3'b010, S3 = 3'b011, S4 = 3'b100;

  reg [2:0] state, next_state;

  always @(posedge clk or posedge reset) begin
    if (reset) state <= S0;
    else state <= next_state;
  end

  always @(*) begin
    case(state)
      S0: next_state = S1;
      S1: next_state = sensor ? S2 : S1;
      S2: next_state = S3;
      S3: next_state = S4;
      S4: next_state = S0;
      default: next_state = S0;
    endcase
  end

  always @(*) begin
    case(state)
      S0: begin main_light = GREEN; side_light = RED; end
      S1: begin main_light = GREEN; side_light = RED; end
      S2: begin main_light = YELLOW; side_light = RED; end
      S3: begin main_light = RED; side_light = GREEN; end
      S4: begin main_light = RED; side_light = YELLOW; end
      default: begin main_light = RED; side_light = RED; end
    endcase
  end

  covergroup state_cg @(posedge clk);
    cp_state: coverpoint state;
    cp_next_state: coverpoint next_state;
    cp_sensor: coverpoint sensor;
    cp_main_light: coverpoint main_light;
    cp_side_light: coverpoint side_light;

    cross_state_trans: cross cp_state, cp_next_state, cp_sensor {
      ignore_bins invalid = binsof(cp_state) intersect {S0, S2, S3, S4} &&
                            binsof(cp_sensor) intersect {1};
    }
    cross_state_lights: cross cp_state, cp_main_light, cp_side_light;
  endgroup

  state_cg cg_inst = new();

endmodule

このコードでは、cross_state_transで全ての有効な状態遷移を検証し、cross_state_lightsで各状態における信号の組み合わせを確認しています。

ignore_binsを使用して、センサーが意味を持たない状態での組み合わせを除外しています。

シミュレーション結果は次のようになるでしょう。

Covergroup: state_cg
  Cross: cross_state_trans
    Covered bins: 6/6 (100%)
    Ignored bins: 4
  Cross: cross_state_lights
    Covered bins: 5/5 (100%)
  Total coverage: 100%

全ての有効な状態遷移と信号の組み合わせがカバーされていることが確認できます。

○サンプルコード13:高度なプロトコル検証の実装

複雑なプロトコルの検証では、多数の信号の相互作用を考慮する必要があります。

cross関数は、このような複雑な相互作用を効率的に検証するのに適しています。

ここでは、簡略化したPCIeプロトコルの一部を検証する例を紹介します。

module pcie_protocol_checker(
  input clk, reset,
  input [1:0] tx_type,
  input [2:0] tx_fmt,
  input [7:0] tx_length,
  input tx_valid,
  output reg rx_ready
);

  localparam MRD = 2'b00, MWR = 2'b01, CPL = 2'b10, MSG = 2'b11;

  always @(posedge clk or posedge reset) begin
    if (reset) rx_ready <= 1'b0;
    else rx_ready <= $random;
  end

  covergroup pcie_cg @(posedge clk);
    cp_tx_type: coverpoint tx_type;
    cp_tx_fmt: coverpoint tx_fmt;
    cp_tx_length: coverpoint tx_length {
      bins small = {[0:63]};
      bins medium = {[64:127]};
      bins large = {[128:255]};
    }
    cp_tx_valid: coverpoint tx_valid;
    cp_rx_ready: coverpoint rx_ready;

    cross_tx: cross cp_tx_type, cp_tx_fmt, cp_tx_length, cp_tx_valid, cp_rx_ready {
      ignore_bins invalid = binsof(cp_tx_type) intersect {MRD, MWR} &&
                            binsof(cp_tx_fmt) intersect {3'b000, 3'b001};
    }
  endgroup

  pcie_cg cg_inst = new();

endmodule

このコードでは、PCIeのトランザクションタイプ、フォーマット、長さ、有効性、受信準備状態の全ての組み合わせをcross関数で検証しています。

ignore_binsを使用して、無効な組み合わせ(例:メモリ読み込み/書き込み操作で特定のフォーマットを使用)を除外しています。

シミュレーション結果は次のようになるでしょう。

Covergroup: pcie_cg
  Cross: cross_tx
    Covered bins: 172/192 (89.58%)
    Ignored bins: 20
  Total coverage: 89.58%

カバレッジが100%に達していないのは、一部の稀なケースがシミュレーション中に発生しなかったためかもしれません。

このような場合、より長時間のシミュレーションや、特定のケースを狙ったテストケースの追加が必要になる可能性があります。

○サンプルコード14:大規模設計における効率的なcross関数の使用

大規模な設計では、全ての可能な組み合わせをカバーすることが現実的ではない場合があります。

そのような状況では、重要な組み合わせに焦点を当てたcross関数の使用が効果的です。

次のコードは、複雑なネットワークスイッチの一部を検証する例です。

module network_switch(
  input clk, reset,
  input [3:0] src_port,
  input [3:0] dst_port,
  input [1:0] priority,
  input [7:0] packet_type,
  input valid,
  output reg [3:0] out_port,
  output reg drop
);

  // 簡略化されたスイッチングロジック
  always @(posedge clk or posedge reset) begin
    if (reset) begin
      out_port <= 4'b0000;
      drop <= 1'b0;
    end else if (valid) begin
      if (src_port == dst_port) begin
        drop <= 1'b1;
        out_port <= 4'b0000;
      end else begin
        drop <= 1'b0;
        out_port <= dst_port;
      end
    end
  end

  covergroup switch_cg @(posedge clk);
    cp_src_port: coverpoint src_port;
    cp_dst_port: coverpoint dst_port;
    cp_priority: coverpoint priority;
    cp_packet_type: coverpoint packet_type {
      bins control = {8'h00, 8'h01, 8'h02};
      bins data = {[8'h03:8'hFE]};
      bins special = {8'hFF};
    }
    cp_valid: coverpoint valid;
    cp_out_port: coverpoint out_port;
    cp_drop: coverpoint drop;

    cross_routing: cross cp_src_port, cp_dst_port, cp_priority, cp_packet_type, cp_valid {
      option.weight = 0;
      bins high_priority = binsof(cp_priority) intersect {2'b11};
      bins control_packets = binsof(cp_packet_type) intersect {control};
      bins valid_packets = binsof(cp_valid) intersect {1'b1};
    }

    cross_output: cross cp_dst_port, cp_out_port, cp_drop;
  endgroup

  switch_cg cg_inst = new();

endmodule

このコードでは、cross_routingで入力ポート、出力ポート、優先度、パケットタイプ、有効信号の組み合わせを検証しています。

ただし、全ての組み合わせをカバーするのではなく、高優先度パケット、制御パケット、有効なパケットに焦点を当てています。

option.weight = 0を使用して、デフォルトの組み合わせを無視し、指定したbinsのみをカバーしています。

また、cross_outputで出力ポートとドロップ信号の関係を検証しています。

シミュレーション結果は次のようになるでしょう。

Covergroup: switch_cg
  Cross: cross_routing
    Covered bins:
      high_priority: 32 hits
      control_packets: 48 hits
      valid_packets: 128 hits
    Total coverage: 100%
  Cross: cross_output
    Covered bins: 32/32 (100%)
  Total coverage: 100%

全ての指定された重要な組み合わせがカバーされていることが確認できます。

大規模設計では、このように重要な部分に焦点を当てることで、効率的かつ効果的な検証が可能になります。

まとめ

Verilogのcross関数は、設計検証において非常に強力かつ柔軟なツールです。

基本的な使用方法から高度な応用例まで、様々なシナリオでcross関数が活躍することを見てきました。

cross関数の使用に習熟することで、より効率的で信頼性の高い設計検証が可能になります。複雑な設計でも高いカバレッジを達成し、潜在的なバグを早期に発見することができるでしょ