Altifigence Academy

58 / 63 · 概念

FIFO・メモリの検証計画と独立したスコアボード

実装のポインタに依存しない queue スコアボードを作り、境界・reset・valid・未初期化読み出しを検証計画へ結び付ける。

正解を作るモデルは DUT と違う形にする

検証器は、契約から作った期待値を DUT と比較する。FIFOFIFO 先に入れたデータを先に取り出すキューです。満杯・空・同時読出しと書込みの許可規則も動作の取り決めに含まれます。 詳しく見る 内部の rptr、wptr をコピーして期待値を計算すると、同じポインタの誤りまで再現する恐れがある。独立したスコアボードスコアボード 検証環境で期待データと実出力を順に照合する参照状態です。実装の内部状態をそのまま写さず、独立して計算します。は、SystemVerilog の queue に論理的な順序だけを保存する。push を受け付けたら末尾へ追加し、pop を受け付けたら先頭から取り出す。

検証計画には、状態と要求の交差する組み合わせを記す。empty・途中まで格納・full のそれぞれについて、要求なし、push のみ、pop のみ、同時要求を確認する。さらに、繰り返しの周回、連続 pop、データが残った状態での reset を加える。各ケースを観測したかを記録し、乱数 seed と失敗時の入力を残して再現できるようにする。

期待する受け付けは、DUT の full や occupancy ではなく、queue の以前のサイズ NN から計算する。

q=pop∧(N>0)p=push∧((N<4)∨q)N+=N+p−q\begin{aligned} q&=\mathrm{pop}\land(N>0)\\ p&=\mathrm{push}\land((N<4)\lor q)\\ N^{+}&=N+p-q \end{aligned}

以前の先頭を取り出してから新しいデータを追加し、full の同時要求で以前の先頭を保つ。期待する q=1q=1 のときだけ dout を比較する。

完全なテストベンチ

前の講義の fifo4 とともにコンパイルする完全なテストベンチである。遅延と queue を使う検証コードであり、入力は立ち下がりエッジで変え、立ち上がりエッジの 1 ps 後に DUT のノンブロッキングノンブロッキング代入 順序 RTL で <= と書く代入です。右辺を評価して更新を予約するので、同じエッジのレジスタが更新前の状態に基づいて計算できます。 詳しく見る更新結果を比較する。

SystemVerilog
module tb_fifo4;
  timeunit 1ns;
  timeprecision 1ps;
  typedef logic [7:0] word_t;
  logic clk=0, rst=1, push=0, pop=0;
  word_t din=0, dout;
  logic dout_valid, full, empty;
  logic [2:0] occupancy;
  word_t model[$], expected;
  bit expect_pop, expect_push;
  fifo4 dut (.*);
  always #5ns clk = ~clk;

  always @(posedge clk) begin
    if (rst) begin
      model.delete();
      expect_pop = 0;
    end else begin
      expect_pop = pop && (model.size() != 0);
      expect_push = push && ((model.size() < 4) || expect_pop);
      if (expect_pop) expected = model.pop_front();
      if (expect_push) model.push_back(din);
    end
    #1ps;
    assert (dout_valid === expect_pop)
      else $fatal(1, "valid mismatch");
    if (expect_pop)
      assert (dout === expected)
        else $fatal(1, "data mismatch");
    assert (int'(occupancy) === model.size())
      else $fatal(1, "count mismatch");
    assert (empty === (model.size() == 0))
      else $fatal(1, "empty mismatch");
    assert (full === (model.size() == 4))
      else $fatal(1, "full mismatch");
  end

  task automatic step(input bit r, p, o, input word_t d);
    @(negedge clk);
    rst=r; push=p; pop=o; din=d;
  endtask

  initial begin
    step(0,0,1,0);
    step(0,1,1,8'hA0);
    for (int i=1; i<4; i++)
      step(0,1,0,word_t'(160+i));
    step(0,1,0,8'hEE);
    step(0,1,1,8'hB0);
    repeat (5) step(0,0,1,0);
    repeat (3) begin
      for (int i=0; i<4; i++) step(0,1,0,word_t'(i));
      repeat (4) step(0,0,1,0);
    end
    step(0,1,0,8'h5A);
    step(1,1,1,8'hFF);
    step(0,0,1,0);
    repeat (100)
      step(0,bit'($urandom_range(1,0)),bit'($urandom_range(1,0)),
           word_t'($urandom));
    repeat (4) step(0,0,1,0);
    @(negedge clk);
    $display("PASS");
    $finish;
  end
endmodule

最初の立ち上がりエッジでは rst=1 のため、DUT とモデルを空にする。=== は X も含めて厳密に比較する。valid=0 のときは初期化していない dout を比較しないが、valid 自体が X なら失敗する。最後の 4 回の pop は残りのデータをすべて検査する。PASS は、このテストを実際に実行してすべての assertion を通過したときのメッセージであり、ここにコードを提示しただけで実行済みという意味ではない。

書き込みデータ B0 を追加する前に、以前の先頭 A0 を期待出力として分離する。ポインタや物理スロットではなく、データの論理的な順序を比較する手順である。

full の同時要求に対するスコアボードの処理順
  1. 以前の queue: [A0, A1, A2, A3]
  2. pop の期待値: 以前の先頭 A0 を保存
  3. push を反映: [A1, A2, A3, B0]
  4. 応答を比較: valid=1 のとき dout=A0

境界の例でスコアボードを検算する

queue が [A0,A1,A2,A3] の状態で push のみが来ると、サイズが 4 なので EE を破棄する。次の同時要求では、先に expected=A0 を保存し、B0 を末尾へ追加して [A1,A2,A3,B0] とする。DUT が新しい B0 を直ちに出すと、データの assertion が失敗する。逆に empty の同時要求では expect_pop=0、expect_push=1 なので、出力比較をせずに 1 項目を格納する。reset 中は両要求を無視し、queue を空にして valid=0 を検査する。

メモリ検証にも同じ原則を使う。アドレスごとの参照配列と initialized 表を持ち、read-first では参照側の書き込み更新前に読み出し期待値を確保する。レジスタレジスタ 複数ビットの状態を記憶する回路です。このコースの同期式レジスタは、クロックエッジで定められた入力を記憶します。 詳しく見るファイルのバイパスでは、競合時に wr_data を選んでから参照配列へ書き込みを反映する。未初期化アドレスの読み出しを禁止するか、未定義の応答を許すかはテストの前提に記し、勝手に 0 を正解としてはならない。アドレス指定型メモリには専用の検証器が必要である。

full での同時 pop に新しい値を出力する、同時受け付けで個数を増やす、empty の pop で valid を有効にする、といった不具合を一つずつ注入し、対応する検査が失敗することを確かめる。このようなレビューと境界ごとのカバレッジによって、テスト自体の漏れを見つける。RAM リソースの配置とタイミングは、別途合成とタイミング検証で確認する。

自分で考えてみましょう

誤ったスコアボードが先に push 要求を queue に追加し、その後の更新済みサイズで pop の可否を判断している。empty で push と pop が同時に来ると、どのような誤判定が起こるか。full で同時要求が来た場合、期待データはいつ保存するべきか。reset 後に毎回 dout=0 を検査する規則も評価せよ。

解説を見る

empty の同時要求で新しい項目をすぐに pop できると誤判定し、expect_pop=1 を作ってしまう。契約ではエッジ直前のサイズで受け付けを決めるため、push だけを受け付け、出力 valid は 0 でなければならない。full では、まず以前の先頭を expected として保存して取り除き、その後で新しいデータを末尾へ追加する。reset は dout を 0 にしないので、valid=0 と queue・occupancy が空であることだけを確認する。データ比較は expect_pop=1 のときに行い、DUT の valid も別に比較する。そうしないと、valid を常に 0 にする不具合を見逃す。

選択はこのブラウザに適用されます。フッターからいつでも変更できます。