#はじめに。
こんにちは。 @tethys_seesaa です。
皆さんはSystemVerilogアサーション(SVA)をお使いでしょうか? 自分は使っていません。
スクラッチで、複数サイクルにまたがるプロパティやシーケンスを書き、アサーションとしてシミュレーションで使うのはリスキーなんですよねぇ。
欲張って書こうとすると、意図しないシーケンスがヒットしたり、シミュレーションがパスってもアサーションがエラーだったりと、大抵は お世話する子が2倍になる 印象です。
HDL+αを狙うと、SVAは簡単に書けそうでついつい手を出しがちですが、開発工数とのトレードオフを考えるとあまりやりたくありません。むろん、そういう命令が来たら従うほかしかない身分ですけどね。
やるとしたら、ベンダのライブラリを使ってbindし、限定的な範囲に留める感じですかねー。今じゃバスシステムもAXIとかデファクトですし、それだったらベンダから購入した方が効率的と思います。それをbindするのも一仕事ですし、それが面白いか面白くないかは人によりけりと思いますのでそこまでは言及しません。
#即時アサーション
というわけでSVAなんですけど、使いたいときはたまに使います。
SVA=コンカレントアサーションで学ぶ人が多いというか、セミナー等だとコンカレントアサーションの内容がほぼ全てといった印象だから、ここでは、 即時アサーション(イミディエイトアサーション) にフォーカスします。
対象は、RTLのコネクティビティ(接続性)チェックです。
コネクティビティチェックってどうやってやりますかね。
VHDLせよVerilogにせよ、比較的大きな規模の回路となると、コードでコネクティビティをチェックするのは結構大変です。
地味な作業ですが、回避するのは難しいです。
自分の場合、隣のプロジェクトチームが二つのクロックを逆につないでいることが ES時に発覚し、 リスピンを余儀なくされたのを目の当たりにしましたことがあります。いやー、大変そうですねあはは。
今回紹介する方法は、その工夫の 手段の一つ です。
#デザイン(DUT)
まずこれはDUTとテストで共通して使うコードです。
2を底とする対数を出します。このコードだとDEPTHは2のべき乗に限られます。
従って、WIDTHは8となります。以下、DUTもテストもデータのビット幅は8bitとします。
function int log2(int v);
if (v <= 1)
return 1;
v = v-1;
for (log2=0; v>0; log2++)
v >>= 1;
endfunction
parameter DEPTH = 256;
parameter WITDH = log2(DEPTH);
typedef logic [WITDH-1:0] data_t;
次はDUTです。
ざっくり説明すると、二つの信号をANDするモジュールと、二つの信号を加算するモジュールを置き、
上位モジュールのcoreでどちらかの出力信号を選択します。i_sel=1だとAND、i_sel=0だと加算を選択します。
もし、わかりにくかったらブロック図とか作成して追記しますから、コメントでリクエスト下さい。
今回は、ここの選択部分が、トップ階層まで接続されているかをチェックします。
`timescale 1ns/1ps
module design_top # (
parameter WITDH = log2(DEPTH)
)(
input logic rst_n, clk,
input logic i_sel,
input data_t i_a, i_b,
output data_t o_c
);
core u_core(.*);
endmodule
module core # (
parameter WITDH = log2(DEPTH)
)(
input logic rst_n, clk,
input logic i_sel,
input data_t i_a, i_b,
output data_t o_c
);
data_t and_c, add_c;
m_and u_and(.o_c(and_c), .*);
m_add u_add(.o_c(add_c), .*);
always_comb
if(i_sel)
o_c = and_c;
else
o_c = add_c;
endmodule
module m_and # (
parameter WITDH = log2(DEPTH)
)(
input logic rst_n, clk,
input data_t i_a, i_b,
output data_t o_c
);
always_ff @(negedge rst_n, posedge clk)
if(!rst_n)
o_c <= '0;
else
o_c <= i_a & i_b;
endmodule
module m_add # (
parameter WITDH = log2(DEPTH)
)(
input logic rst_n, clk,
input data_t i_a, i_b,
output data_t o_c
);
function data_t f_add (data_t a, data_t b);
logic [WITDH:0] t;
t = a + b;
return t[WITDH-1:0];
endfunction
always_ff @(negedge rst_n, posedge clk)
if(!rst_n)
o_c <= '0;
else
o_c <= f_add(i_a, i_b);
endmodule
#テスト
テキトーにリセット、クロック、i_selを発生させて、二つの8bit信号をランダムに入力させ、
i_selが変化したら、テキトーなタイミングでコネクティビティをチェックします。
コネクティビティをチェックしているのは、connect_check()タスクです。
ここで、即時アサーションでコネクティビティを表明し、成立しなかったらconnect_error(" ")タスクをコールします。
シミュレーションを実行すると、connect_check()タスクで$displayで書かれてあるところが、コンソールにドカドカ出てきて、シミュレーションが終了すると思います。
if(i_sel)を、わざと if(!i_sel) と書き直すと、i_selが変化してしばらくしてシミュレーションがエラーで終了します。
`timescale 1ns/1ps
module tb();
parameter CYC = 1_000_000;
parameter AP = 525;
parameter AN = 2400;
parameter CHK = 10;
logic rst_n, clk;
logic i_sel;
data_t i_a, i_b;
data_t o_c;
event e;
design_top dut (.*);
task gen_rst();
rst_n = '0;
#7 rst_n = '1;
endtask
task gen_clk();
clk = '0;
forever
#5 clk = ~clk;
endtask
task gen_data();
i_a = '0;
i_b = '0;
forever @(posedge clk) begin
i_a = $random();
i_b = $random();
end
endtask
task gen_sel(int p, n);
i_sel = '0;
forever begin
repeat(p) begin
i_sel = '1;
@(posedge clk);
end
repeat(n) begin
i_sel = '0;
@(posedge clk);
end
end
endtask
task sel_p();
forever begin
@(posedge i_sel);
#CHK -> e;
end
endtask
task sel_n();
forever begin
@(negedge i_sel);
#CHK -> e;
end
endtask
task ctrl_test(int cycle);
repeat(cycle)
@(posedge clk);
$finish(2);
endtask
task connect_check();
forever begin
$display("Connectivity Check start.");
@(e);
if(i_sel)
assert (o_c == dut.u_core.u_and.o_c)
else connect_error("m_and");
else
assert (o_c == dut.u_core.u_add.o_c)
else connect_error("m_add");
$display("Connectivity Check end.");
end
endtask
task connect_error(string m);
$display ("ERROR! : %s Connectivity is wrong.", m);
$finish(1);
endtask
initial begin
fork
gen_rst();
gen_clk();
gen_data();
gen_sel(AP, AN);
ctrl_test(CYC);
sel_p();
sel_n();
connect_check();
join
end
endmodule
#というわけで、
いかがでしたでしょうか。
このぐらいの規模だと即時アサーション絡みのコードが冗長に思えるかもしれません。
このテストのやり方が効果を発揮するのは、回路の規模が大きくなったり、同一モジュールを多くインスタンスしているときなどになるかと思います。
上記の場合、SVA自体も記述するのが大変になってくるので、Excelとかのスプレッドシートで視認性を向上させ、そこからVBAなりを使ってSVAに落とし込むといったやり方がいい思います。
Excelですと、他のお仕事(ピンチェックとか)にも応用が利き、手動で行う部分を減らしてプロジェクト全体のバグ削減が見込めるでしょう。
自分はLinuxが開発のメインプラットフォームなので、ExcelをPythonで読み込んでSVAに出力させています。
#ちなみに、
これくらいは誰か既に考えているだろうと思って調べてみたら、それに近いコードがVerification Academyに載ってました。
https://forum.verificationacademy.com/forum/verification-methodology-discussion-forum/systemverilog-and-other-languages-forum/25805-connectivity-checking
`uvm_errorを使ってUVMのフローに組み込んでいるようですね。UVMを使えばオサレに見えるかもしれません。
#おわりに。
各方面を調べてみると、コネクティビティチェックはやはり課題となっているようで、フォーマル検証ツールを俯瞰すると、コネクティビティチェックのSVAを自動で生成する機能が備わっているものが多いです。
フォーマル検証ツールだと、検証に実行するまでの準備に時間がかかるので、この機能はとても便利そうに見えます。皆さん是非使って下さい。つーか、 使えよお前ら。
##おまけ
MentorGraphics Questaだと、以下でたぶん動きます。
vlib work
vlog -mfcu ./common.sv ./design.sv ./tb.sv
vsim -c tb
Synopsys VCSだとたぶんこんな感じ。
vcs -sverilog ./common.sv ./design.sv ./tb.sv
./simv
Cadence Incisiveはこうかなあ。
irun ./common.sv ./design.sv ./tb.sv
これら、SystemVerilogコードはSublime Text 2で書きました。
二度言いますね、これら、SystemVerilogコードは Sublime Text 2 で書きました。