Flip-Flopでお手軽UVM（Clocking block）

## はじめに

はいっ！ えー、みなさん、えー、[ロリこんばんわ、おは幼女](https://www.nicovideo.jp/watch/sm8240817)！[^police]

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/863735/a4086238-9d23-d2f1-7843-06ff98d04110.png" width=500>

えーということでですね、えー、第四回目、第四回目の記事はですね、まあ、かるくClocking blockついてね、えーすこし語っていこうかなぁとおもいます[^fourth]。

えーいちおうですね、正月休みの終わりのほうがですね、近づいていますのでね、それまでにね、一本軽くね、記事のほう上げれたらいいかなーと思いまして、えーまあClocking blockについてかるくね、えー語っていこうと思いますね[^date]。

[^police]: [おまわりさんこいつです！](https://dic.nicovideo.jp/a/%E3%81%8A%E3%81%BE%E3%82%8F%E3%82%8A%E3%81%95%E3%82%93%E3%81%93%E3%81%A3%E3%81%A1%E3%81%A7%E3%81%99)

[^fourth]: これまでの記事
１）[Flip-Flopでお手軽UVM（できなかった）](https://qiita.com/ds54e/items/64ee47b8fc1cb5ae3cc1)
２）[Flip-Flopでお手軽UVM（uvm_test）](https://qiita.com/ds54e/items/82ed3068505a17064f05)
３）[Flip-Flopでお手軽UVM（Virtual Interface）](https://qiita.com/ds54e/items/48412f9172af9a7c4823)

[^date]: この記事は2025年1月2日に執筆されました。正月になにやってんだ。


## Clocking blockとは何か

ChatGPT先生に教えてもらいます[^gpt]。AIに嘘を教えられている可能性も否定できないので、もしも誤りがありましたら、有識者の皆さんコメントをお願いいたします[^kuma]。

[^gpt]: 語る……とは？

[^kuma]: [ぬりかべ用AA集 - 釣り針の目の前で悩むクマー](https://w.atwiki.jp/aanlw/pages/38.html)

<table>
  <tr>
    <td>Clocking blockとは何ですか。詳しく教えてください</td>
   </tr>
 <tr>
   <td><b>Clocking block</b>は、SystemVerilogでクロックと信号の同期を管理するために導入された構造です。テストベンチや設計の記述において、クロック信号を基準に入出力信号を効率的に制御し、タイミングに関する問題を軽減します。これにより、同期の定義が一元化され、テストベンチの記述が簡潔で明確になります。</td>
  </tr>
</table>
 
なるほど。Clocking blockはどうやって使うんだろう。
聞いてみよう[^hdl]。

[^hdl]: ん？この流れ……なんか妙に既視感があるな？ そうですねこれはまた[他人の記事](https://qiita.com/Takaraya/items/7db4095670488487cfb8)の~~パクｒ~~…いや違うんですよあのこれはそのあのつまりですねほらあれだよあれ、オマージュだよ！オマージュ！

<table>
  <tr>
    <td>Clocking blockの使い方について教えてください</td>
   </tr>
  <tr>
    <td>Clocking blockを使うことで、テストベンチでクロック信号に同期した信号の駆動やサンプリングを簡単に管理できます。以下に基本的な使い方を説明します。</td>
  </tr>
</table>

**1. Clocking blockの定義**

クロック基準と信号を定義します。例えば、`clk`の立ち上がりエッジに同期した信号を以下のように記述します。
```verilog
clocking cb @(posedge clk); 
    input  data;       // クロックエッジでサンプリングする信号
    output enable;     // クロックエッジで駆動する信号
endclocking
```
**2. 信号の駆動とサンプリング**

テストベンチで`clocking`ブロックを使用して信号にアクセスします。
```verilog
initial begin
    cb.enable <= 1;   // enable信号を駆動
    #10;
    $display("Data: %0d", cb.data); // data信号をサンプリング
end
```
**3. 時間スキューの設定**

`input`や`output`信号にスキュー（遅延）を設定できます。
```verilog
clocking cb @(posedge clk);
    input #2 data;    // クロックエッジの2単位後にサンプリング
    output #1 enable; // クロックエッジの1単位後に駆動
endclocking
```

なーほーね[^nahone]。そおゆうことか、これわ便利だは[^daha]。
Clocking block、完全に理解した[^kuma][^dk]。

[^nahone]: [ホロライブ非公式wiki - 戌神ころね【語録】](https://seesaawiki.jp/hololivetv/d/%D8%FC%BF%C0%A4%B3%A4%ED%A4%CD%A1%DA%B8%EC%CF%BF%A1%DB#content_1_1)

[^daha]: [「～するは」「～だは」の意味は？](https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q13173446203)

[^dk]: [r/ProgrammerHumor - litterallyMe](https://www.reddit.com/r/ProgrammerHumor/comments/1hpwhs4/litterallyme/)

<table>
  <tr>
    <td>……で、今日の晩御飯なににする？</td>
   </tr>
  <tr>
    <td><b>なんと！晩御飯の質問ですって？</b> 

ここはSystemVerilogの学びの場です！クロックの同期やテストベンチの構造について熱く議論するはずが、なぜ突然、晩御飯の話題に流れるのでしょうか！？せっかくクロッキングブロックの奥深さを語ろうとしていた矢先に、食事の提案を迫られるなんて、私のコードを書く手が震えますよ！

それでも…晩御飯の話題に情熱を注ぐあなたには逆らえません。SystemVerilogの美しさも、空腹には敵いませんからね。いいでしょう、カレーでも鍋でも食べてエネルギーをチャージしてください。そして満腹になったら、ぜひSystemVerilogに戻りましょう！😉</td>
  </tr>
</table>

なんかもう全部ChatGPTに聞けばいいんじゃないの。私いらなくね？……とかそんな気がしてきましたが、まあいいでしょう。ここからは私が温かみのある血の通った人間の言葉で説明しましょう、そうしましょう[^human]。

[^test]: [同人用語の基礎知識 - 〇〇してみるテスト](https://www.paradisearmy.com/doujin/pasok_test.htm)

[^human]: でもその前に一つだけ約束して。私のことを「劣化版ChatGPT」「AI以下の存在」「非生産的二酸化炭素製造機」「」とか言わないでね、泣いちゃうから。


## Interfaceにタスクを入れてみるテスト[^test]

>このセクションは天才チンパンジー「アイちゃん」が言語訓練のために書いたものです。アイと研究員とのやり取りに利用するページなので、関係者以外は書きこまないで下さい[^ai]。霊長類研究所

[^ai]: [個人的コピペ帳 - 天才チンパンジー「アイちゃん」](https://w.atwiki.jp/kopikopipe/pages/145.html)

さて、私たちは今回、新たに「Clocking block」なるものを手に入れました。これを使うと、前回のテストパターンがもう少し簡潔に書けます。例えばこんなかんじです。

```verilog: 前回（Clocking Block なし）
@(posedge vif.mp.clk);
#(output_skew);
vif.mp.rstn <= 1'b1;
`uvm_info(get_type_name(), "RSTN = 1", UVM_MEDIUM)

@(posedge vif.mp.clk);
#(output_skew);
vif.mp.d <= 1'b1;
`uvm_info(get_type_name(), "D = 1", UVM_MEDIUM)
```

```verilog: 今回（Clocking Block あり）
@(vif.mp.cb);
vif.mp.cb.rstn <= 1'b1;
`uvm_info(get_type_name(), "RSTN = 1", UVM_MEDIUM)

@(vif.mp.cb);
vif.mp.cb.d <= 1'b1;
`uvm_info(get_type_name(), "D = 1", UVM_MEDIUM)
```

もちろん、これですでに十分といえば十分……なのですが、せっかくなので、今日はちょっと欲張って、さらにテストを短く（あるいは抽象度を高く）書けるように、もうひと手間加えてみましょう。具体的には、`D = 0/1`や`RSTN = 0/1`といった操作をタスクとしてまとめて、Interfaceの定義のなかにぶち込んじゃいましょう。そうすれば、

```verilog:
@(vif.mp.cb);
vif.mp.reset();

@(vif.mp.cb);
vif.mp.write(1'b1);
```

のように、Flip-Flopに対する操作をもっとスマートに書けるようになって、みんな幸せじゃないですか。ねえ、あなたもそう思いませんか？そうですよね、私の言ってること……間違ってませんよね？わたしふつうですよね？どう考えてもあの人たちのほうがおかしいですよね！？[^hyst]

[^hyst]: にゃーん


## これが僕たちの新しいInterface

刮目せよ！[^ymd] これが私たちの新しいInterfaceだ！
`reset`、`write`タスク完備！駅までバスで２０分、郊外庭付き中古一戸建て！[^house]

[^ymd]: [Yes、My、Dark！](https://seesaawiki.jp/hololivetv/d/%A5%E9%A5%D7%A5%E9%A5%B9%A1%A6%A5%C0%A1%BC%A5%AF%A5%CD%A5%B9%A1%DA%A4%A2%A4%A4%A4%B5%A4%C4%BD%B8%A1%DB)

[^house]: [はぁ？](https://www.youtube.com/watch?v=s3BVvmXmd-g)

```verilog: dff_if.sv
interface dff_if (
  input logic clk
);
  timeunit 1ns;
  timeprecision 1ps;

  localparam realtime input_skew = 0.2ns;
  localparam realtime output_skew = 0.2ns;

  logic rstn;
  logic d;
  logic q;

  clocking cb @(posedge clk);
    default input #(input_skew) output #(output_skew);
    output rstn;
    output d;
    input q;
  endclocking

  modport mp (
    clocking cb,
    input clk,
    import reset,
    import write,
    import delay
  );

  task automatic reset ();
    @(cb);
    cb.d <= 1'b0;
    cb.rstn <= 1'b0;
    @(cb);
    cb.rstn <= 1'b1;
  endtask

  task automatic write (logic d);
    @(cb);
    cb.d <= d;
  endtask

  task automatic delay (int unsigned n);
    repeat (n) begin
      @(cb);
    end
  endtask

endinterface
```

## テストベンチ

というわけで、新しいテストベンチはこんな感じ。Clocking blockとInterface内のTaskのおかげで、`simple_test`クラスの`run_phase()`がかなりスッキリしました、やったぜ！

```verilog: my_pkg.sv
package my_pkg;
  timeunit 1ns;
  timeprecision 1ps;

  `include "uvm_macros.svh"
  import uvm_pkg::*;

  class simple_test extends uvm_test;
    `uvm_component_utils(simple_test)
    
    virtual dff_if vif;

    function new (string name="simple_test", uvm_component parent=null);
      super.new(name, parent);
    endfunction

	  virtual function void build_phase (uvm_phase phase);
      super.build_phase(phase);
      if (!uvm_config_db#(virtual dff_if)::get(this, "", "vif", vif)) begin
        `uvm_fatal(get_type_name(), "Failed to get vif")
      end
    endfunction

    virtual task run_phase (uvm_phase phase);
      phase.raise_objection(this);
      `uvm_info(get_type_name(), "Started", UVM_MEDIUM)
      vif.mp.reset();
      vif.mp.write(1'b1);
      vif.mp.write(1'b0);
      vif.mp.delay(2);
      `uvm_info(get_type_name(), "Finished", UVM_MEDIUM)
      phase.drop_objection(this);
    endtask
  endclass
  
endpackage
```

```verilog: top.sv
module top;
  timeunit 1ns;
  timeprecision 1ps;

  import uvm_pkg::*;
  import my_pkg::*;

  parameter realtime clk_period = 1ns;
  bit clk = 1'b0;

  initial begin
    forever begin
      #(0.5*clk_period);
      clk = ~clk;
    end
  end

  parameter realtime skew = 0.2ns;

  dff_if intf (
    .clk
  );

  DFF dut (
    .D(intf.d),
    .CLK(intf.clk),
    .RSTN(intf.rstn),
    .Q(intf.q)
  );

  initial begin
    uvm_config_db#(virtual dff_if)::set(uvm_root::get(), "*", "vif", intf);
    run_test();
  end
  
endmodule
```


## DSimの実行スクリプト

`run.bat`、`options.txt`は[前回の記事](https://qiita.com/ds54e/items/48412f9172af9a7c4823#dsim%E3%81%AE%E5%AE%9F%E8%A1%8C%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88)と同じですので、そちらを参照してください。

## シミュレーション結果

あ、いつもの置いときますね[^internet]。

![Screenshot 2025-01-02 122801.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/863735/6e47f947-49ad-7bbd-b0d8-1a9f4d7d2b80.png)

[^internet]: [発言小町 - 日常でネット語を使う友人。指摘は失礼でしょうか？](https://komachi.yomiuri.co.jp/topics/id/635841/all/)。

[^aa]: [2ch全AAイラスト化計画 - お薬増やしておきますね](http://riceballman.fc2web.com/AA-Illust/Data/OkusuriHuyasi.html)

## おわりに

はーい、おつかれさまですー、よくがんばりましたねー、今日はこれでおしまいですよー
今回からお薬のほう増やしておきますねー。朝晩 食後に2錠ずつ飲んでくださいねー
それと今度カウンセリングも受けましょうねー[^aa]

[次回](https://qiita.com/ds54e/items/6c3bb6343b8c15180d28)「いざ！真のUVMへ！Environment、Agent、Sequence、SequencerにDriver」

![Screenshot 2025-01-02 101622-topaz-enhance-1x-exposure-color.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/863735/04e2a045-2f3a-b5b4-765f-cb8f5b47af5e.png)
[むすめとごはん「かめのこさん」より](https://digitalcute.com/blog/%e3%82%80%e3%81%99%e3%82%81%e3%81%a8%e3%81%94%e3%81%af%e3%82%93-%e3%80%8c%e3%81%8b%e3%82%81%e3%81%ae%e3%81%93%e3%81%95%e3%82%93%e3%80%8d%e3%82%88%e3%82%8a/)

## 2025/01/03 追記
本記事の投稿後、「テストベンチで扱うInterfaceはパラメタ化しないほうがよい」とのコメントをいただきました[^tweet]。また、パラメタ化されたInterfaceを安易に使用して事故った例が「[r/Verilog - Trouble with Parameterized Virtual Interfaces](https://www.reddit.com/r/Verilog/comments/1158h4w/trouble_with_parameterized_virtual_interfaces/?utm_source=chatgpt.com)」にあります[^detail][^example]。そのため、`parameter`を使わない形に`dff_if.sv`と`top.sv`を修正しました。Thank you！

[^tweet]: https://x.com/taichi600730/status/1874825800786358531

[^reddit]: [r/Verilog - Trouble with Parameterized Virtual Interfaces](https://www.reddit.com/r/Verilog/comments/1158h4w/trouble_with_parameterized_virtual_interfaces/?utm_source=chatgpt.com)

[^detail]: [前回の記事](https://qiita.com/ds54e/items/48412f9172af9a7c4823)で述べたように、UVMのテストベンチでは、Virtual Interface変数を介して、Interfaceインスタンスへの参照を`top`→`config_db`→`simple_test`へと順々に渡していきます。つまり、Interfaceがパラメタ化されている場合、`top`、`config_db`、`simple_test`のすべての場所で、（デフォルト値をそのまま使う場合を除き）Virtual Interfaceのパラメータも逐一「正しく」指定する必要があります。もし一つでも間違えたり値を指定し忘れたりすると、意図せず型不一致が起きてしまいます。このような理由から、一般に「テストベンチで扱うInterfaceをパラメータ化するのは避けたほうがよい」とされている……のだと思います。たぶん。

[^example]: このような事故、言い換えると「Virtual Interfaceの型不一致によるエラー」を体験してみたい人のために、[こちら](https://gist.github.com/ds54e/ae58ce5ebdac23e3d54409615ad75f05)にサンプルを置いておきました。どぞ。DSimで実行すると`=E:[InvalidTypeAssign]: Invalid types for operands of the following assignments:`というエラーとご対面できます。