はじめに
はいっ! えー、みなさん、えー、ロリこんばんわ、おは幼女!1
えーということでですね、えー、第四回目、第四回目の記事はですね、まあ、かるくClocking blockついてね、えーすこし語っていこうかなぁとおもいます2。
えーいちおうですね、正月休みの終わりのほうがですね、近づいていますのでね、それまでにね、一本軽くね、記事のほう上げれたらいいかなーと思いまして、えーまあClocking blockについてかるくね、えー語っていこうと思いますね3。
Clocking blockとは何か
ChatGPT先生に教えてもらいます4。AIに嘘を教えられている可能性も否定できないので、もしも誤りがありましたら、有識者の皆さんコメントをお願いいたします5。
Clocking blockとは何ですか。詳しく教えてください |
Clocking blockは、SystemVerilogでクロックと信号の同期を管理するために導入された構造です。テストベンチや設計の記述において、クロック信号を基準に入出力信号を効率的に制御し、タイミングに関する問題を軽減します。これにより、同期の定義が一元化され、テストベンチの記述が簡潔で明確になります。 |
なるほど。Clocking blockはどうやって使うんだろう。
聞いてみよう6。
Clocking blockの使い方について教えてください |
Clocking blockを使うことで、テストベンチでクロック信号に同期した信号の駆動やサンプリングを簡単に管理できます。以下に基本的な使い方を説明します。 |
1. Clocking blockの定義
クロック基準と信号を定義します。例えば、clk
の立ち上がりエッジに同期した信号を以下のように記述します。
clocking cb @(posedge clk);
input data; // クロックエッジでサンプリングする信号
output enable; // クロックエッジで駆動する信号
endclocking
2. 信号の駆動とサンプリング
テストベンチでclocking
ブロックを使用して信号にアクセスします。
initial begin
cb.enable <= 1; // enable信号を駆動
#10;
$display("Data: %0d", cb.data); // data信号をサンプリング
end
3. 時間スキューの設定
input
やoutput
信号にスキュー(遅延)を設定できます。
clocking cb @(posedge clk);
input #2 data; // クロックエッジの2単位後にサンプリング
output #1 enable; // クロックエッジの1単位後に駆動
endclocking
なーほーね7。そおゆうことか、これわ便利だは8。
Clocking block、完全に理解した59。
……で、今日の晩御飯なににする? |
なんと!晩御飯の質問ですって?
ここはSystemVerilogの学びの場です!クロックの同期やテストベンチの構造について熱く議論するはずが、なぜ突然、晩御飯の話題に流れるのでしょうか!?せっかくクロッキングブロックの奥深さを語ろうとしていた矢先に、食事の提案を迫られるなんて、私のコードを書く手が震えますよ! それでも…晩御飯の話題に情熱を注ぐあなたには逆らえません。SystemVerilogの美しさも、空腹には敵いませんからね。いいでしょう、カレーでも鍋でも食べてエネルギーをチャージしてください。そして満腹になったら、ぜひSystemVerilogに戻りましょう!😉 |
なんかもう全部ChatGPTに聞けばいいんじゃないの。私いらなくね?……とかそんな気がしてきましたが、まあいいでしょう。ここからは私が温かみのある血の通った人間の言葉で説明しましょう、そうしましょう10。
Interfaceにタスクを入れてみるテスト11
このセクションは天才チンパンジー「アイちゃん」が言語訓練のために書いたものです。アイと研究員とのやり取りに利用するページなので、関係者以外は書きこまないで下さい12。霊長類研究所
さて、私たちは今回、新たに「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)
@(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の定義のなかにぶち込んじゃいましょう。そうすれば、
@(vif.mp.cb);
vif.mp.reset();
@(vif.mp.cb);
vif.mp.write(1'b1);
のように、Flip-Flopに対する操作をもっとスマートに書けるようになって、みんな幸せじゃないですか。ねえ、あなたもそう思いませんか?そうですよね、私の言ってること……間違ってませんよね?わたしふつうですよね?どう考えてもあの人たちのほうがおかしいですよね!?13
これが僕たちの新しいInterface
刮目せよ!14 これが私たちの新しいInterfaceだ!
reset
、write
タスク完備!駅までバスで20分、郊外庭付き中古一戸建て!15
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()
がかなりスッキリしました、やったぜ!
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
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
は前回の記事と同じですので、そちらを参照してください。
シミュレーション結果
あ、いつもの置いときますね16。
おわりに
はーい、おつかれさまですー、よくがんばりましたねー、今日はこれでおしまいですよー
今回からお薬のほう増やしておきますねー。朝晩 食後に2錠ずつ飲んでくださいねー
それと今度カウンセリングも受けましょうねー17
次回「いざ!真のUVMへ!Environment、Agent、Sequence、SequencerにDriver」
2025/01/03 追記
本記事の投稿後、「テストベンチで扱うInterfaceはパラメタ化しないほうがよい」とのコメントをいただきました18。また、パラメタ化されたInterfaceを安易に使用して事故った例が「r/Verilog - Trouble with Parameterized Virtual Interfaces」にあります1920。そのため、parameter
を使わない形にdff_if.sv
とtop.sv
を修正しました。Thank you!
-
これまでの記事
1)Flip-Flopでお手軽UVM(できなかった)
2)Flip-Flopでお手軽UVM(uvm_test)
3)Flip-Flopでお手軽UVM(Virtual Interface) ↩ -
この記事は2025年1月2日に執筆されました。正月になにやってんだ。 ↩
-
語る……とは? ↩
-
ん?この流れ……なんか妙に既視感があるな? そうですねこれはまた他人の記事の
パクr…いや違うんですよあのこれはそのあのつまりですねほらあれだよあれ、オマージュだよ!オマージュ! ↩ -
でもその前に一つだけ約束して。私のことを「劣化版ChatGPT」「AI以下の存在」「非生産的二酸化炭素製造機」「」とか言わないでね、泣いちゃうから。 ↩
-
にゃーん ↩
-
前回の記事で述べたように、UVMのテストベンチでは、Virtual Interface変数を介して、Interfaceインスタンスへの参照を
top
→config_db
→simple_test
へと順々に渡していきます。つまり、Interfaceがパラメタ化されている場合、top
、config_db
、simple_test
のすべての場所で、(デフォルト値をそのまま使う場合を除き)Virtual Interfaceのパラメータも逐一「正しく」指定する必要があります。もし一つでも間違えたり値を指定し忘れたりすると、意図せず型不一致が起きてしまいます。このような理由から、一般に「テストベンチで扱うInterfaceをパラメータ化するのは避けたほうがよい」とされている……のだと思います。たぶん。 ↩ -
このような事故、言い換えると「Virtual Interfaceの型不一致によるエラー」を体験してみたい人のために、こちらにサンプルを置いておきました。どぞ。DSimで実行すると
=E:[InvalidTypeAssign]: Invalid types for operands of the following assignments:
というエラーとご対面できます。 ↩