03. パイプライン処理

このサイトのどこかで似たようなことを書いているかもしれませんが、忘れてしまいました。パイプライン処理、というエントリーは書いていないので、書こうと思います。

OVMやUVM(VMMとかは知りません)で、例えばAHBバスドライバーを記述しようとします。AHBはパイプライン動作しますけれど、uvm_driverクラスの記述方法が悪いと、残念な動きしか実現できないです ね。

例:AHB Lite

失敗例(ライト) → ライトトランザクションのデータフェーズで、次のライト命令をAHBバスドライバーが受け取れない

clk     ~~|___|~~~|___|~~~|___|~~~|___|~~~|___|~~~|___|~~~|___|~~~
haddr   XXXXXXX_A0____XXXXXXXXX_A1____XXXXXXXXX_A2____XXXXXXXXXXXX
hwrite  XXXXXXX~~~~~~~XXXXXXXXX~~~~~~~XXXXXXXXX~~~~~~~XXXXXXXXXXXX
hwdata  XXXXXXXXXXXXXXX_D0____XXXXXXXXX_D1____XXXXXXXXX_D2____XXXX
hready  ______|~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|___

これが期待した動作

clk     ~~|___|~~~|___|~~~|___|~~~|___|~~~|___|~~~
haddr   XXXXXXX_A0____X_A1____X_A2____XXXXXXXXXXXX
hwrite  XXXXXXX~~~~~~~~~~~~~~~~~~~~~~~XXXXXXXXXXXX
hwdata  XXXXXXXXXXXXXXX_D0____X_D1____X_D2____XXXX
hready  ______|~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|___

なぜ失敗するかといいますと、uvm_driverクラスの「itemを処理」するtaskの書き方が、「sequence_itemを受け取って(get_next_item)、1トランザクション制御してからitem_doneを返す、からなんです。

task run_phase(uvm_phase phase);

ahb_master_seq_item trans_item;

:

forever begin

seq_item_port.get_next_item(trans_item);

/// バスドライバー制御開始

/// ①

/// バスドライバー制御終了

seq_item_port.item_done();

end

endtask

OVMやUVMの使い始めでは、サンプルコードなどを見ながら記述すると思いますので、するととりあえず

  • パイプライン処理
  • スプリットトランザクション

系はうまく書けなくて、うーん、となりがちかもしれません。もちろん、以前からこのような制御系をモデリングされている方はサクッと解決するでしょう。

このような制御系を記述するときは、すぐにitem_doneを叩きます。しかし、以下のような考え方ではNGです。

task run_phase(uvm_phase phase);
  ahb_master_seq_item trans_item;
    :
  forever begin
    seq_item_port.get_next_item(trans_item);

seq_item_port.item_done();

/// バスドライバー制御開始

/// ①

/// バスドライバー制御終了

end

endtask

ポイントは、バスドライバーを制御するtaskを「分離」することと、受け取ったsequence_itemをどこかに「一時保管」し、分離したtaskでそれを「拾う」ことです。一時保管には、mailboxなりqueueなり使え ば実現できます。以下の例では、queueを使います。

ahb_master_seq_item item_q[$];
task run_phase(uvm_phase phase);

ahb_master_seq_item trans_item;

fork

bus_driver;

join_none

:

forever begin

seq_item_port.get_next_item(trans_item);

item_q.push_back(trans_item);

seq_item_port.item_done();

end

endtask

task bus_driver;

ahb_master_seq_item trans_item;

forever begin

while(item_q.size()==0) @(posedge vif.clk);

trans_item = item_q.pop_front();

/// バスドライバー制御開始

/// ①

/// バスドライバー制御終了

end

endtask

バスドライバーを、task bus_driverに定義するとしたとき、

  1. task run_phaseはUVMから自動で呼び出されるので、その中で「fork~join_none」を使い、bus_driverを別プロセスで呼び出します。これで、run_phaseとbus_driverが並列動作します。
  2. bus_driverの中では、例えばクロック同期でqueueのサイズをチェックさせて、中身があるときに、queueからitemを取り出して、バスドライバー制御します。

このような感じで制御できます。こうして、AHBのようなパイプライン動作するバスであったり、AXIのようにチャネルが分離動作するケースなど(少し考える必要ありますが)も制御できるようになります。

【追記】

ところで、上記のような処理にしただけだと、1つ問題が発生します。それは、テストシナリオを記述したシーケンスファイルにおいて、トランザクションの完了待ちができなくなる、というものです。

それではどうするかといいますと、次のように考える必要が出てくると思います。

  • シーケンスから発行する`uvm_do_with などにて、「命令タイプ」に該当する変数を使う。そのためには、uvm_sequence_itemクラスにメンバを追加する必要がある。
  • 命令タイプには、例えば以下のような分類をする必要がある
    • トランザクション発行
    • トランザクション完了待ち

このようにした上で、uvm_driverも修正を行います。(以下は、if文のelseの書き方をうろ覚えで書いているので、間違っていたらすみません)

task run_phase(uvm_phase phase);
  ahb_master_seq_item trans_item;
  fork
    bus_driver;
  join_none
    :
  forever begin
    seq_item_port.get_next_item(trans_item);
    if(trans_item.cmdkind==0)begin  //
      item_q.push_back(trans_item);
      seq_item_port.item_done();
    end else if(trans_item.cmdkind==1)begin
      // トランザクション完了まで待つ処理を記述、もしくは該当taskを定義して実行

seq_item_port.item_done();

    end
  end
endtask

シーケンスから受け取ったitem(trans_item)から命令の種別を判断し、トランザクション発行であればitemをqueueに入れてitem_doneを叩き、トランザクション完了待ちであればdriverでトランザクション完 了タイミングをチェックして、チェック完了後にitem_doneを叩く、というように処理を分離します。

なお、シーケンスからバスインターフェースの信号を参照して対応する、ということができるかもしれませんが、それはオススメしません。シーケンスは、あくまでドライバーへのコマンド発行役であって、実 際に信号を参照して制御する人はドライバーですので。