ISim によるテストの自動化を考える のバックアップ差分(No.3)

更新


  • 追加された行はこの色です。
  • 削除された行はこの色です。
[[公開メモ]]

#contents

* テストを自動化したい [#u7f97791]

** テスト駆動開発 [#za66cb5b]

ソフトウェア開発で最近はやりの手法として、
テスト駆動開発(TDD: test-driven development)というのがあります。
ISim を使って似たようなことをやりたいというのがこの記事の趣旨になります。

背景としては・・・

ソフトでも、回路でも、プロジェクトが大きくなり複雑になると、
コードの一部を修正したことが、思ってもみない部分に波及して、
新しいバグを生んでしまう(デグレードしてしまう)不幸が頻繁に起こります。

かといって、「動いているコードを変更するなんて愚の骨頂」などという
旧態依然とした態度でいると、バグつぶしや新機能の導入のたびに、
場当たり的なつぎはぎが行われ、コードの見通しが悪くなったり、
構造的なゆがみが蓄積してして、結局どこかで破綻をきたします。

そこで、コードに加えた変更がどこか他に悪影響を与えないことを
確信できるようにして、既存コードの改変を容易にしようというのが
テスト駆動開発の原点になっています。

(ソフトの方では、テストをしやすい
モジュール構成にすること自体が、結果的に見通しの良い設計を誘発するとか、
テストを書くことで仕様が明確になる、とか、他にもいろいろ利点が
あることが宣伝されていますので、興味のある方はgoogleってみて下さい。)

gui がからむソフト開発に比べ、回路は動作を機械的に定義しやすいので、
テスト駆動はとても向いてると思うんです。

** テスト駆動開発の流れ [#u89c3feb]

理想的なテスト駆動開発では、コードを書いたり、変更したりするのに先だって、
まずそのコードがクリアすべきテストを記述します。

そうやってテストを記述した後、実際のコードを書いて、
そのコードがちゃんとテストをクリアすることを確認して、
ようやく次のコードに移ります。

次のコードを書くときにはまた、先にテストを書いて、次にコードを書いて、
とやりますが、そのコードをテストする際には、はじめのテストと新しいテストの
両方が問題なく通ることを確認します。

つまり、後から書いたコードがそれ自身の目的を果たすだけでなく、
元のテストの結果を悪化させていないことも確認するわけです。

そのようにしていくと、コードが増えると同時に、テストもどんどん増えていき、
理想的にはすべてのコードがテストで守られている状況を作ることができます。

このようになっていれば、コードにどんなに大きな変更を加えても、
その後すべてのテストを走らせることで、
その変更がデグレードを引き起こしていないという確信を持てる
理想的な状況を実現できます。

この安心感があるおかげで、テスト駆動開発ではすでに動いているコード部分に
ついても、コードの読みやすさや、構造的な改善のため、一部を改変することが容易であり、
常にコードを読みやすく、直しやすい状態に保つことができます。

このようなテストに裏打ちされた既存コードの改善
(動作を変えずに読みやすさや構成を改善する変更)は
専門用語で「リファクタリング」と呼ばれています。

** テストの自動化が重要 [#ud0d3c96]

上記の説明で分かるとおり、テスト駆動開発ではコードと同じか、
あるいはそれを上回る量のテストが書かれます。

そして、コードの主要な改変のたびに何百、何千、
時には何万もあるすべてのテストを走らせる必要があります。

したがって、普通にテストベンチを書いて、その結果波形を目で追いながら確認する、
というような従来型のテストベンチの使い方は、テスト駆動開発では役に立ちません。

テスト駆動開発でのテストは、次のような自動テストであることが必須です。
+ テストはかならず「成功する」か「失敗する」かのどちらかを結果とする
+ すべてのテストを自動的に連続して走らせることができる
+ どのテストが成功して、どのテストが失敗したかを容易に把握できる

** ISim を使った自動テストに必要な技術 [#l6e21a7b]

同様の自動テストを ISim を使った Verilog 
コード検証で行いたいと思うのですが、そのためには
Xilinx WebPack 上の ISim を使って、

- 「成功」か「失敗」かを返すテストベンチの書き方
- 書きためた無数のテストベンチを一気に走らせる方法
- 書きためた複数のテストベンチを一気に走らせる方法
- テストベンチの結果(成功・失敗)を一覧する方法
- そのあたりを考慮したプロジェクトフォルダ内のファイル配置

を確立する必要があります。

* どこかにまとめられてたりしません? [#kfb7e2c8]

で、こういう内容を駆け出しの私一人で考えても前途多難なのは目に見えているのですが、
経験に裏付けされたお勧めのプラクティスとか、どこかで紹介されてないでしょうか?

どなたか知っていたら教えて下さい。

二度目に四角い車輪を発明するような愚は犯したくないのです(−−;

** 見つけられないでいます(TT [#yee8fa28]

とはいえ、自分ではこれまで上記のような内容の記事を見つけられていません。

しかたないので、以下で苦しむ過程を書きつつ、何とか良さそうな解を探ってみようと思います。

今更こんなことを調べているのはかなり不毛な気がするので、
どこかに答えがあればどなたかぜひ教えて下さい!

* 「成功」か「失敗」かを返すテストベンチの書き方 [#wdd688a8]

目視の試験を行った際に、うまく動いたことが確認できた波形を機械的に記録して、
その後は実波形と理想波形との差分を取る、といった機械的な方法もありかも?

できれば失敗位置をコード上で簡単に見つけられるようにしたい。
できればコード上でのテスト失敗位置を簡単に見つけられるようにしたい。

** テストベンチ記述に役に立ちそうなマクロ文法 [#saf52346]

 LANG:verilog
 `define ERROR(msg) \
    $display(`"%s(%0d) ERROR msg.`", `__FILE__, `__LINE__)

としておいて、テストベンチ中で

 LANG:verilog
 if(variable!=expected)
     `ERROR(思ってたのと値が違う!);

と書くと、

 LANG:console
 C:/hoge_proj/moge_test.v(60) ERROR 思ってたのと値が違う!.

などというエラーメッセージを表示できます。

このようなエラー行からは、例えば秀丸のタグジャンプでソースファイルに飛べるので便利。

実は上記の _LINE_ や _FILE_ は verilog2001 には規定されておらず、
ISim のマニュアルにも記述がないのですが、C の流儀で書いてみたら通ってしまいました。

~`"〜`" という不思議なクォーテーションは、マクロ引数を展開してくれる書き方だそうです。

** 自動テスト用テストベンチ記述に使うインクルードファイル [#x9c415ae]

 LANGUAGE:verilog
 `ifndef XILINX_ISIM
 
 // 基本は何もしない
 
 `define INFO(msg) 
 `define ERROR(msg) 
 `define DONE 
 `define ASSERT_EQ(variable, value) 
 `define ASSERT_EQ_ALWAYS(variable, value) 
 `define ASSERT_EQ_AT(variable, value, clk) 
 
 `else
 
 // ISim 上での動作
 // ファイル名&行番号も表示する
 
 `define INFO(msg) \
     $display(`"%s(%0d) INFO msg.`", `__FILE__, `__LINE__)
 
 `define ERROR(msg) \
     $display(`"%s(%0d) ERROR msg.`", `__FILE__, `__LINE__)
 
 `define DONE \
     $display(`"%s(%0d) DONE.`", `__FILE__, `__LINE__)
 
 `define ASSERT_EQ(variable,value) \
     if((variable)!=(value)) \
         $display("%s(%0d) ERROR ", `__FILE__, `__LINE__, \
                  `"Assertion [ variable == value ] not met: `", variable, " != ", value)
 
 `define ASSERT_EQ_ALWAYS(variable,value) \
     always @(variable) \
         if((variable)!=(value)) \
             $display("%s(%0d) ERROR ", `__FILE__, `__LINE__, \
                      `"Assertion [ variable == value ] not met at #%0d: `", $time, \
                      variable, " != ", value)
 
 `define ASSERT_EQ_AT(variable,value,clk) \
     always @(clk) \
         if((variable)!=(value)) \
             $display("%s(%0d) ERROR ", `__FILE__, `__LINE__, \
                      `"Assertion [ variable == value @ clk ] not met at #%0d: `", $time, \
                      variable, " != ", value)
 
 `endif

テストベンチでこれをインクルードすれば、分かりやすいエラーメッセージを簡単に表示できますね。

古い C のマクロを駆使してた頃を思い出します(−−;

** 成功と失敗の判別 [#cc762fd9]

エラーメッセージをログファイルに溜めておいて、
その中から "ERROR" という文字列が見つかれば失敗、
見つからなければ成功、という単純な見分け方ができるはず。

* 書きためた無数のテストベンチを一気に走らせる方法 [#vd7f748e]

前テストを走らせるのにある程度時間がかかってしまうのは仕方がない。

すべてのテストを走らせるのにある程度時間がかかってしまうのは仕方がないですが、
1つ1つ手で走らせるのは面倒なので、
バッチファイルあるいはmakefileのような物で一気にコンパイル&実行ができるようにしておいて、
結果だけを確認できる形にしたい。
結果だけを確認できるようにしたい。

本来なら gui 化して、必要な範囲のテストだけを走らせる、
なんてこともできればよいのだけれど、そこまでは望めない・・・かな?

** 基本コマンド [#m15c1502]

ISE から Simulate Behavioral Model をダブルクリックしたときの動作を追うことで、
コンパイルからシミュレータの起動までの一連の流れが Console に表示されるため、
これを自分でなぞればいい。

*** はじめに環境を整える [#gaf00001]

C:\Xilinx\12.4\ISE_DS\settings32.bat あるいは C:\Xilinx\12.4\ISE_DS\settings64.bat 
を起動することで、Xilinx 
のコマンドラインツールを使うのに必要な環境変数が正しくセットされます。

 Language:Console
 C:\hoge_proj>C:\Xilinx\12.4\ISE_DS\settings64.bat

*** fuse でコンパイルして *_isim_beh.exe ファイルを作る [#h7ad85c5]

通常の形式は以下の通り。

 fuse 
        -intstyle ise 
        -incremental 
        -lib unisims_ver 
        -lib unimacro_ver 
        -lib xilinxcorelib_ver 
        -o hoge_test_isim_beh.exe 
        -prj hoge_test_beh.prj 
        work.hoge_test work.glbl

hoge_test_beh.prj には
 verilog work "hoge.v"
 verilog work "hoge_test.v"
 verilog work "fuba_module.v"
 verilog work "C:/Xilinx/12.4/ISE_DS/ISE//verilog/src/glbl.v"

のように、テストベンチ hoge_test が依存するモジュールファイルが記述されている。

このファイルは ISE からテストベンチを起動するときに自動で作られるが、
何か別の作業をしているといつの間にか消えてしまう。

モジュールの依存関係を自前で解析するのは骨が折れるので、
このファイルを取っておいて、自動実行テストベンチの指示に使おう。

*** *_isim_beh.exe の実行 [#u665164d]

できあがったシミュレータを実行するときには tcl 
コマンドを記述したコマンドファイルが別途必要になります。

test.cmd
 run;

テストベンチ側がちゃんと必要なテストが終わったら $finish; 
を呼んで自分から終了するように記述されていれば、
上記のように run だけ書いたコマンドファイルで十分です。

これを引数にして、シミュレータを起動すれば、

 LANG:console
 C:\hoge_proj>hoge_test_isim_beh.exe -tclbatch test.cmd -log hoge_test_isim_beh.log
 ISim M.81d (signature 0x12940baa)
 Time resolution is 1 ps
 Simulator is doing circuit initialization process.
 C:/hoge_proj/foba_test.v(66) ERROR Assertion [ value == expected ] not met at #0: 0 != 1
 Finished circuit initialization process.
 C:/hoge_proj/foba_test.v(60) ERROR エラーが起きた!
 C:/hoge_proj/foba_test.v(62) DONE.
 Stopped at time : 100 ns : File "C:/hoge_proj/foba_test.v" Line 63

のようにしシミュレーションが終了し、
画面に表示されたのとほぼ同じ内容が .log ファイルに保存されます。

** 複数のテストベンチを一括で自動実行するには [#i2987a2b]

ISE が作ってくれる *_isim_beh.prj ファイルを多数 beh/ フォルダに入れておいて、
下記の ruby スクリプトを走らせると一括でコンパイル&実行することができます。

 LANG:ruby
 #!/usr/bin/ruby
 
 system("rm simulate_all.log")
 Dir.glob("beh/*.prj").each do |prj|
   prj =~ /([^\/]+)_beh.prj/
   module_name = $1
   prj = $~
   system("cp beh/#{prj} #{prj}")
   system("fuse "+
               "-intstyle ise "+
               "-incremental "+
               "-lib unisims_ver "+
               "-lib unimacro_ver "+
               "-lib xilinxcorelib_ver "+
               "-o #{module_name}_isim_beh.exe "+
               "-prj #{prj} "+
               "work.#{module_name} work.glbl")
   system("echo ===== START : #{module_name}_isim_beh.exe > simulate_all.log")
   system("echo run | ./#{module_name}_isim_beh.exe")
 end

ただ、コンパイルした結果の *_isim_beh.exe ファイルを実行した際に私の環境では
内部で fork してすぐに返ってきてしまうために、シミュレーションの終了を待たずに
次のコンパイルが始まってしまいました。

手動で起動した場合にも、tcl コマンドのプロンプトがバックグラウンドプロセスに
回ってしまっているような動作をしていて、とても変です。

Windows7 64bit で動かしているのがいけない?

もう少し調査してみます。

あと、fuse を起動する際には .prj ファイルのある位置を基準に
HDLソースファイルを探しに行くようで、.prj ファイルを beh/ 
フォルダに置いたままだとうまくコンパイルすることができませんでした。

このあたりも、もう少し詳しく調べる必要がありそうです。

* テストベンチの結果(成功・失敗)を一覧する方法 [#kd26a735]

ログファイルを grep あるいは ruby かなにかのスクリプトで
処理すればできるんじゃないかと考え中。

* プロジェクトフォルダ内のフォルダ構成をどうするのが良いか [#yb72c04c]

これ結構重要なんだけど、試行錯誤が必要になりそう。

* コメント [#h77e4c69]

#article_kcaptcha
**テストベンチで全部まかなう [#jf37d06a]
>[アプロ] (2011-02-16 (水) 18:48:34)~
~
期待値はテストベンチ内で用意し、テストベンチ内で、コンペアして、結果はログ・ファイルに出力する~
~
波形はデバッグ時に見るが、OK・NGはログ・ファイルで確認する~

//
- やはりその方向ですよね。現在、その方針で自分なりのコーディング規約を作ることと、そういうテストベンチが多数あったときに、自動的にすべて走らせて結果を一覧にまとめる方法を調べています。良い方法があったら教えて下さい。 -- [武内(管理人)] &new{2011-02-16 (水) 18:55:21};
- 相手のなんちゃってモデルを作って接続する。または、メーカーのシミュレーション・モデルと接続する。そーすると、CPUアクセスがメインになったり、そのなんちゃってモデルの制御になるため、コンベアが楽になります -- [アプロ] &new{2011-02-16 (水) 19:43:24};
- SystemVerilogでテストベンチが書けると、かなりイロイロと効率が上がりますが・・・taskとfunctionの塊になりますよ。テストケース毎にtask化するとデバッグが楽になります。また、共通化taskを作っておいて、それをコールしまくる -- [アプロ] &new{2011-02-16 (水) 19:47:13};
- $system(); は使えるかな? シェルをコールするシステムタスクです。Verilogで出来ないことは、外部プログラムで補う。perlとかを呼ぶことができる -- [アプロ] &new{2011-02-16 (水) 19:49:20};
- Verilog の規格のドラフト版を探してみてください。どっかに転がっていると思います。内緒ですが、アレは、ヒントが満載なんですよ -- [アプロ] &new{2011-02-16 (水) 20:31:55};
- SystemVerilog が使えるという意味でも modelsim は魅力的だったんですよね。ISim ではまだ実装されていない部分が多そうなので、自分でまかなうしかなさそうです(TT -- [武内(管理人)] &new{2011-02-16 (水) 20:37:34};
- いろいろ勉強になります。いろいろやりながら試行錯誤になりそうです・・・ -- [武内(管理人)] &new{2011-02-16 (水) 20:39:22};
- OVLも使ってみたらどうでしょうか? -- [marsee] &new{2011-02-16 (水) 21:27:45};
- OVLも検討中なのですが、エラーが出た時に何をさせればデバッグに役立つかが見えてこなくてペンディングになってしまっています。 -- [武内(管理人)] &new{2011-02-16 (水) 22:20:31};
- できると良いのは、最低限エラーが起きたことをログファイルから見分けられることですが、できればログファイルからコードのどの部分で、どのシミュレーション時刻に、どんなエラーが生じたかが分かることなのですが・・・ -- [武内(管理人)] &new{2011-02-16 (水) 22:21:24};

#comment_kcaptcha


Counter: 33888 (from 2010/06/03), today: 2, yesterday: 0