ISim によるテストの自動化を考える のバックアップソース(No.20)

更新

[[公開メモ]]

#contents

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

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

ソフトウェア開発で最近はやりの手法として、
テスト駆動開発(TDD: test-driven development)というのがあります。

Xilinx WebPack の ISim を使って Verilog による回路設計で
テスト駆動開発をやりたいというのがこの記事の趣旨になります。

** 背景 [#ce299d88]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

上記の目的を果たす自動テストを ISim を使った Verilog 
コード検証で行うことを考えると、そのためには
Xilinx WebPack 上の ISim を使って、

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

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

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

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

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

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

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

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

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

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

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

基本的には、
- テストベンチから標準出力にエラーメッセージを表示
- 標準出力をログに記録
- ログ中にエラーメッセージを発見したら失敗・発見しなかったら成功

という方針で行こうと思います。

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

コード上でのテスト失敗位置を簡単に見つけられるようにしたいので、
エラーメッセージの表示に以下のようなマクロを使います。

これを使えば、ログに残ったエラーメッセージを見るだけで、
コードのどの部分でエラーが生じたかが一目瞭然になります。

テストベンチを書く際の手間が非常に小さくて済むのも
お勧めできる点です。

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

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

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

と書くと、

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

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

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

- 実は上記の _LINE_ や _FILE_ は verilog2001 には規定されておらず、
ISim のマニュアルにも記述がないのですが、C の流儀で書いてみたら通ってしまいました
- マクロが複数行にわたるときは \ で繋ぎます。これも C と同じ流儀。
- ~`"〜`" という不思議なクォーテーションは、マクロ引数を展開してくれる書き方だそうです
- エラーの発生時刻は $time で得られます

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

上記を応用して作りました。

src/utility/simulation.inc
 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, the_event) 
 
 `else
 
   // ISim 上での動作
   // ファイル名&行番号も表示する
   
   `define INFO(msg)   \
       $display(`../../"%s(%0d) INFO at #%0d msg.`",  `__FILE__, `__LINE__, $time)
   
   `define ERROR(msg)  \
       $display(`"../../%s(%0d) ERROR at #%0d msg.`", `__FILE__, `__LINE__, $time)
   
   `define DONE        \
       $display(`"../../%s(%0d) DONE at #%0d.`",      `__FILE__, `__LINE__, $time)
   
   // variable が value に等しいことを検証
   `define ASSERT_EQ(variable,value)                                             \
       if((variable)!==(value))                                                  \
           $display("../../%s(%0d) ERROR at #%0d: ", `__FILE__, `__LINE__, $time,      \
                    `"Assertion [ variable === value ] failed `",                \
                    variable, " != ", value, ".")
   
   // variable が常に value に等しいことを検証する always に展開される
   `define ASSERT_EQ_ALWAYS(variable, value)                                     \
       always @(variable, value)                                                 \
           if((variable)!==(value))                                              \
               $display("../../%s(%0d) ERROR at #%0d: ", `__FILE__, `__LINE__, $time,  \
                        `"Assertion [ variable === value ] failed `",            \
                        variable, " != ", value, ".")
   
   // variable が @(the_event) のタイミングで常に value に等しいことを検証する
   // the_event の部分は posedge clk とか negedge clk など @(the_event) 
   // としたときにおかしくない形で指定する
   `define ASSERT_EQ_AT(variable, value, the_event)                              \
       always @(the_event)                                                       \
           if((variable)!==(value))                                              \
               $display("../../%s(%0d) ERROR at #%0d: ", `__FILE__, `__LINE__, $time,  \
                        `"Assertion [ variable === value @ the_event ] failed `",\
                        variable, " != ", value, ".")
 
 `endif

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

ASSERT_EQ 系のチェックでは、失敗時に
 LANG:console
 C:/hoge_proj/moge_test.v(60) ERROR at #100: Assertion [ sum === a + b ] failed 142 != 322.

というように、評価式自体とその値の両方が表示されます。

必要に応じてメッセージの形式を後からいつでも一括して変更できるのも大きな利点になります。
例えば modelsim 用に $display ではなく 
[[$messagelog>電気回路/HDL/ModelSim で $messagelog を使う]] を使うように修正するのも簡単です。

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

細かい点:
- こういう場合、値の比較は != ではなく !== で行うのが良いですね。
(値に x や z が含まれているときの動作が異なります)
- ファイル名に ../../ を付けて表示しているのは、
下記で検討したフォルダ構成に合わせた物で、
beh/temp/*.log に保存されたログファイルからタグジャンプでソースを開くための工夫です。
- このようにエラーメッセージを統一しておけば /ERROR at/ という正規表現でエラー行を発見できます。

** specify の $setup / $hold / $width 違反を補足 [#gf259cbd]

verilog ではセットアップ時間、ホールド時間、パルス幅等のタイミング違反を
specify ブロックを使ってチェック可能です。

 LANG:verilog
    specify
        $setup(data, posedge clk, 10);
        $hold(data, posedge clk, 10);
        $width(edge clk, 10);
    endspecify

ISim ではこのようなチェックに違反すると

 WARNING: at 295 ns: Timing violation in /dac8812_test/mock/  $hold( data:265 ns, clk:295 ns,10 ns)

といったメッセージが現れます。

そこで、エラー検出の正規表現に /^WARNING:.*Timing violation in/ を追加すれば
specify 違反が検出可能になります。

** 波形を比較するというアプローチ [#v42d0aeb]

これはまだ構想段階で試してはいないのですが・・・

まずは波形を目視で確認する試験を行って、
もしうまく動いたことが確認できたならば、
そのときの波形を記録してしまえば、
信号値の理想波形が得られます。

そうしておいて、
その後のテストは実波形と理想波形との差分を取ることで行う、
という機械的なアプローチは、結構汎用的に使えるかもしれません。

パラメータによって
- 何もしない(目視のシミュレーション中)
- 実波形をファイルへ記録する(理想波形の記録)
- 実波形をファイルに記録された波形と比較して、必要に応じてエラーを吐く(以降のテスト)

の機能を切り替えられるようなデバッグ用のモジュールを作っておいて、
必要な信号線をありったけ繋いでおけば良さそうですね。

 LANG:verilog
 module wave_tester #(
     parameter FUNCTION  = "none", // "none" / "record" / "compare"
     parameter WAVE_FILE = "",
     parameter BIT_COUNT = 1,
     parameter PERIOD = 0  // >= 1 なら #PERIOD ごと、0 なら posedge clk ごとに検査
 ) (
     input wire gate,      // gate == 1 の時に限り検査される
     input wire [BIT_COUNT-1:0] data,
     input wire clk,       // PERIOD == 0 のときのみ使われる
     output wire error     // asserted when comparison fails
 );

こんな感じでしょうか?

信号波形の記録・読み出しには、アプロさんがコメント欄で教えて下さったように、
~$fopen, $fwrite, $fscanf 等が使えそうです。サブモジュールとする場合には
~$fclose を呼び出すタイミングが難しそうですが・・・~
http://www.asic-world.com/verilog/verilog2k3.html

通常プログラムが終了する際にはすべてのファイルハンドルが自動で閉じられる
ことが多いので、手抜きで閉じなくても大丈夫だったり?

ただ、この形だと繋いだ信号線の信号名は失われてしまうので、
エラーが生じた際の原因究明にはちょっと手間がかかるかもしれません?

もっとうまい方法はあるでしょうか?

tcl で書いた方が良いという可能性も調べてみなければ。

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

エラーメッセージをログファイルに溜めておいて、
その中から /ERROR at|^WARNING:.*Timing violation in/ 
という正規表現が見つかれば失敗、
見つからなければ成功、という単純な見分け方ができます。

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

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

本来なら xUnit 系のツールのようにインターフェースを gui 化して、
そのとき必要な範囲のテストだけを選んで走らせる、
なんてこともできればよいのだけれど、そこまでは望めない・・・かな?

** テストのコンパイル&実行の基本コマンド [#m15c1502]

ISE から Simulate Behavioral Model をダブルクリックしたときの動作を追うことで、
何をしたらよいかが分かります。

ISE の Console にコンパイルからシミュレータ起動までの一連の流れが表示されますので、
それをなぞるスクリプトを書けばよいことになります。

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

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

これをしておかないとコンパイルできなかったり、
テストベンチが起動しなかったり悩みます。

 LANG:console
 C:\hoge_proj> C:\Xilinx\12.4\ISE_DS\settings64.bat

私はテスト用のツールを cygwin 環境で使いたかったので、~
C:\Xilinx\13.1\ISE_DS\settings64.bat を同じディレクトリに~
C:\Xilinx\13.1\ISE_DS\settings64-cygwin.bat という名でコピーして、~
その中の

 REM Execute command if any

という行以降を

 C:
 chdir C:\cygwin\bin
 bash --login -i

に書き換えて、独自のスクリプトを作りました。

この .bat のショートカットを作成し、プロパティから
- 編集オプション : 簡易編集モード
- フォント
- レイアウト
- アイコン

等の設定をいじって、ISim デバッグ用のコンソール起動アイコンとして利用しています。

*** テストベンチソースファイルを fuse でコンパイルして *_isim_beh.exe ファイルを作る [#h7ad85c5]

hoge モジュールのテストベンチに hoge_test 
と言う名前を付けた場合、
通常の呼び出しは以下のようになっています。

 LANG:console
 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 が依存するモジュールファイルが記述されています。

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

モジュールの依存関係を自前で解析するのは骨が折れるので、
&COLOR(RED){""このファイルを別ディレクトリに取っておいて、その .prj ファイルを元に自動テストを行うことにしました。""};

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

できあがったシミュレータを実行するとき、
ISE では isim.cmd というファイルをバッチファイルとして使っています。

自分でやる際には標準入力に "run all" 等の文字列を送ってやれば
バッチファイルを作る必要はないようです。

標準出力をファイルにリダイレクトすることでログを取ることができます。

 LANG:console
 $ (echo "run all" | hoge_test_isim_beh.exe) > hoge_test_isim_beh.log
 $ cat hoge_test_isim_beh.log
  ISim log file
  Running: hoge_test_isim_beh.exe
  ISim M.81d (signature 0x12940baa)
  Time resolution is 1 ps
  Simulator is doing circuit initialization process.
  C:/hoge_proj/hoge_test.v(66) ERROR Assertion [ value == expected ] failed at #0: 0 != 1
  Finished circuit initialization process.
  C:/hoge_proj/hoge_test.v(60) ERROR エラーが起きた!
  C:/hoge_proj/hoge_test.v(62) DONE.
  Stopped at time : 100 ns : File "C:/hoge_proj/hoge_test.v" Line 63

run コマンドにより、テストベンチは自ら $finish; で終了するまで走り続け、
その間に、もしエラーが生じればログファイルにログを残します。

$finish を書き忘れると戻ってこないわけですが・・・
そこは書く方が気をつけると言うことで。

いざというときは Ctrl+C で強制終了できます。

夜間に多数のテストを一気に走らせるような場合を考えると、
+ ログファイルが大きくなりすぎた場合
+ 実行時間があまりに長すぎる場合

シミュレータを強制終了して次のテストへ移ってくれるのがベストですね。

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

上記の操作をすべてのテストベンチに対して繰り返せば自動実行できます。

しかし、ただ自動実行するだけではいろいろ使いづらいので、
実際の実行は rake を使って後ほど示すようなスクリプトで行います。

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

個々のテストベンチの成功・失敗をまとめたログファイルを作成して、
それをカラー表示します。

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

これ結構重要だと思うんですが、試行錯誤が必要になりそうでもあります。

今の ISE だとプロジェクトフォルダ内は一時ファイル等で
ぐちゃぐちゃになるので、ソースファイルを一段下に置くことにして、

 (project_home)/.git/
 (project_home)/.gitignore
 (project_home)/project.xise
 (project_home)/ipcore_dir/some_core.xise
 (project_home)/ipcore_dir/some_core.v
 (project_home)/iseconfig/filter.filter
 (project_home)/src/hoge.v
 (project_home)/src/fuba.v
 (project_home)/src/test/hoge_test.v
 (project_home)/src/test/hoge_test2.v
 (project_home)/src/test/hoge_test3.v
 (project_home)/src/test/fuba_test.v
 (project_home)/src/geda/geda.v
 (project_home)/src/geda/bopa.v
 (project_home)/src/geda/test/geda_test.v
 (project_home)/src/geda/test/bopa_test.v
 (project_home)/beh/hoge_test_beh.prj
 (project_home)/beh/hoge_test_beh.wcfg
 (project_home)/beh/hoge_test2_beh.prj
 (project_home)/beh/hoge_test2_beh.wcfg
 (project_home)/beh/hoge_test3_beh.prj
 (project_home)/beh/hoge_test3_beh.wcfg
 (project_home)/beh/fuba_test_beh.prj
 (project_home)/beh/fuba_test_beh.wcfg
 (project_home)/beh/geda_test_beh.prj
 (project_home)/beh/geda_test_beh.wcfg
 (project_home)/beh/bopa_test_beh.prj
 (project_home)/beh/bopa_test_beh.wcfg
 (project_home)/beh/temp/hoge_test_beh.exe
 (project_home)/beh/temp/hoge_test_beh.log
 (project_home)/beh/temp/hoge_test2_beh.exe
 (project_home)/beh/temp/hoge_test2_beh.log
 (project_home)/beh/temp/hoge_test3_beh.exe
 (project_home)/beh/temp/hoge_test3_beh.log
 (project_home)/beh/temp/fuba_test_beh.exe
 (project_home)/beh/temp/fuba_test_beh.log
 (project_home)/beh/temp/geda_test_beh.exe
 (project_home)/beh/temp/geda_test_beh.log
 (project_home)/beh/temp/bopa_test_beh.exe
 (project_home)/beh/temp/bopa_test_beh.log

なんてのを考えています。

テストベンチとソースを全然違うフォルダに置いてしまうと探しにくいので、
ルート直下のフォルダで分離するという形にはしていません。

自動テスト中にコンパイルした .exe ファイルは、
ログファイル生成後は必要ないので、
エラーが出ない限りは消してしまっても良いかも?

いや、エラーが出た場合には gui を起動して原因を探るかもしれないので取っておこう。

手動で波形を目視によりデバッグする際には、
gui 上に表示する波形の設定を保存した .wcfg
ファイルも重要なんですが、現状ではこのファイルは ISE 
が管理していないため、
どこに置いても使いにくいことには変わらない状況です。

エクスプローラの拡張子の関連づけでうまくやれば
.prj ファイルのダブルクリックで、同じディレクトリにある
.wcfg ファイルを使って gui を起動することもできるかも?
あ、.wcfg ファイルの方を関連づけした方が良いかな?
後でまた検討します。

* rake ファイルを作ってみた [#w5d2c35a]

やりたいこと、に対して、できたところ、まだのところ、両方あります。

自動テストの流れ:
+ テストベンチごとに beh/*.prj ファイルを用意しておく
+ 個々の beh/*.prj ファイルについて
++ fuse を用いてコンパイルして beh/temp/*.exe を作る
++ コンパイルエラーがあれば beh/temp/*.log に記録してテストベンチの実行をスキップ
++ コンパイルが無事に終わればテストベンチを実行して結果を beh/temp/*.log に保存
++ ログが大きくなりすぎるときは途中で記録を止める
(ハードディスクを食いつぶさないように)
++ 実行時間が制限を超えるときも途中で止める
(バグ回避用)&COLOR(RED){←まだできてない};
++ beh/temp/*.log ファイル中のエラーメッセージ数を調べて beh/temp/*.result に記録
+ beh/temp/simulation.log というファイルにレポートを作成
+ 実行結果をカラー表示

その際、
- ソースの編集状況を見て必要なファイルだけコンパイル&実行したい
- コンパイルエラーへ正しく対応する
- ログファイルの最大サイズをどのように制限するか

など、検討すべき点がいくつかあります。

*** 必要なファイルだけコンパイル&実行したい [#k364db4c]

テスト結果のログと、HDLソースファイルのどちらが古いか見て、
前回テストをしてからソースが書き換えられたものだけを
選んでコンパイル&実行しないと、テストベンチの数が多い場合は
無駄に時間がかかってしまいます。

モジュールファイルの依存関係は .prj ファイルにありますので、
これを元にタイムスタンプを確認するのですが、それ以外に
~`include の依存関係も追いたいところ。

ちゃんとソースファイルをスキャンして、
`include 行があればその先も依存関係に含めるようにしています。

そのほかの依存関係としては $readbinh なんかもありますが、
ファイル名の指定に parameter なんかを使う場合があるので、
こちらはちょっと大変そう。&COLOR(RED){←まだできてない};

*** コンパイルエラーへの対応 [#d23043df]

コンパイルエラーが生じた場合、最低限そのエラーを検出して、
古いテストベンチをそのまま走らせるようなことは避けなければなりません。

ただそれだけだと通常はそれ以降のテストが止まってしまいます。

非常に多くのテストを一気に実行したいような場合これでは不便なので、
そのテストだけ飛ばして残りを実行します。

コンパイルエラーが生じたことを、
それが分かる形でログに残しておいて、
結果表示ではテストエラーとは異なる形で表示します。

- テストの成功 = &COLOR(GREEN){■緑■}
- テストの失敗 = &COLOR(RED){■赤■}
- テストを完了できない = &COLOR(YELLOW){■黄■}
-- コンパイルエラー
-- ログファイルの最大サイズ違反
-- 実行時間の最大値違反
-- Ctrl+C で強制終了された

*** fuse のソース検索パス [#f8597286]

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

今は .prj ファイルをプロジェクトフォルダにコピーしてから
fuse を起動しているのですが、中間ファイルがすべてプロジェクトフォルダに
できてしまうため、望ましくありません。

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

最悪一時ファイルは自動的に消してしまうと言う対処でも良いんですが・・・

** rake ソースファイル [#z916a901]

現在のところ、
以下のファイルを simulation.rake と言う名前で保存して cygwin で使ってみています。

rake については [[ソフトウェア/cygwin/rakeを入れた]] に書きました。

2011/03/05 改訂:
 LANG:ruby(linenumber)
 #!/usr/bin/rake -f
 #
 # Usage: ./simulation.rake
 #   beh/*.prj を読んで必要なテストを実行し各テストの正否を表示する
 #
 # Usage: ./simulation.rake errors
 #   失敗したテスト結果の詳細を表示する
 #
 # Usage: ./simulation.rake clean
 #   beh/temp/* に作成された一時ファイルを消去する
 #
 
 TEMP = "beh/temp"
 LOG  = "#{TEMP}/simulation.log"
 PRJS = FileList['beh/*.prj']    # プロジェクトファイルのリスト
 RESULTS = []
                                 # ログファイルからエラー行を取り出す正規表現
 ERROR_REGEX      = /^ERROR:|ERROR at|^WARNING:.*Timing violation in/
                                 # テストを最後まで実行できなかったことを検出
 EXEC_ERROR_REGEX = /^==== EXECUTION ERROR : /
 MAXLOGSIZE = 1000000            # ログファイルの最大サイズ(これ以上は切り捨てられる)
 
 task :default => ["show"]
 
 directory TEMP
 
 # モジュール名 name のテストベンチを実行してログを取る
 # Window7 64bit + cygwin 環境でのおかしな動作を回避するため
 # popen で起動している。そうでないと実行終了を待てない?
 # 本当はタイムアウトも自動で行いたいが、プロセスの管理が
 # どうしてもうまくいかないのでそのままになっている。
 # 例えば、Process.kill :INT, io.pid が pid not found でエラーになる
 def executeTestbench(name, maxlogsize)
     open("beh/temp/#{name}.log", "w") do |log|
         trap(:INT) { log.puts "==== EXECUTION ERROR : INTERRUPTED ===="; exit }
         IO.popen("beh/temp/#{name}.exe", "r+") do |io|
             io.puts "run all"
             io.close_write
             io.each_line do |line|
                 log.puts line
                 if log.pos > maxlogsize
                     log.puts "==== EXECUTION ERROR : TOO LARGE LOG FILE (#{maxlogsize} bytes) ===="
                     break
                 end
             end
             io.each_line do |line|
                 ;
             end
         end
     end
 end
 
 # プロジェクトファイルからログファイルを作成する生成式を登録する
 PRJS.each do |prj|
 
   prj =~ /([^\/]+)_beh.prj/
   mod = $1
   exe = "#{TEMP}/#{$1}.exe"
   log = "#{TEMP}/#{$1}.log"
   res = "#{TEMP}/#{$1}.result"
 
   # .prj ファイルに書かれた依存関係を読み出す
   deps = []
   IO.readlines(prj).each do |l|
     deps << l.chomp.sub(/^[^"]+"/,'').sub(/".*/,'').gsub(/\\/, "/").sub(/^([A-Z]):/, "/cygdrive/\\1")
   end
 
   # `include をたどって依存関係を追加する
   def dependences(deps)
     result = []
     deps.each do |dep|
       IO.readlines(dep).each do |line|
         next unless line =~ /^\s*`include\s*"([^"]+)"/
         result << $1.gsub(/\\/, "/").sub(/^([A-Z]):/, "/cygdrive/\\1") 
       end
     end
     result = result.delete_if{|dep| deps.include?(dep)}.sort.uniq
     result << dependences(result) unless result.empty?
     result << deps
     return result.flatten
   end
   deps = dependences(deps) << "#{TEMP}" << prj
 
   # 依存関係と生成式を登録
   file log => deps  do |t|
 
     # メッセージ
     print "\n********** #{mod}_beh.exe\n\n"
 
     # .prj ファイルがプロジェクトフォルダにないと
     # fuse が HDL ソースを探せないのでコピーする
     sh "cp #{prj} #{mod}_beh.prj"
 
     # テストベンチをコンパイルする
     puts cmd = "fuse "+
             "-intstyle ise "+
             "-incremental "+
             "-lib unisims_ver "+
             "-lib unimacro_ver "+
             "-lib xilinxcorelib_ver "+
             "-d simulation " +
             "-o #{exe} "+
             "-prj #{mod}_beh.prj "+
             "work.#{mod} work.glbl"
     if system cmd + " > " + log + " 2>&1"
       # テストベンチを起動してログファイルを生成
       puts "executing #{exe}..."
       executeTestbench(mod, MAXLOGSIZE)
     else
       sh "echo '==== EXECUTION ERROR : compile error' >> " + log
     end
 
     # テストベンチで $finish を忘れると終了しないため注意!
   end
 
   file res => log do |t|
     open(res, "w") do |result|
       # エラー行数をカウント
       nerrors = 0
       execerr = false
       IO.readlines(log).each do |line|
         if line =~ EXEC_ERROR_REGEX
           execerr = true
           nerrors = nerrors + 1
         elsif line =~ ERROR_REGEX
           nerrors = nerrors + 1
         end
       end
       # レポートに追加
       result.write nerrors == 0 ? log[/[^\/]+$/] + "\n" : 
                    execerr      ? log[/[^\/]+$/] + " (Errors: #{nerrors}+)\n" :
                                   log[/[^\/]+$/] + " (Errors: #{nerrors})\n"
     end
   end
 
   # ログファイルの一覧に追加する
   RESULTS << res
 end
 
 # 個々の結果ファイルからレポート simulation.log を生成する
 file LOG => RESULTS do |t|
   open(LOG, "w") do |log|
     RESULTS.each do |result|
       log.write IO.read(result)
     end
   end
 end
 
 task "show" => LOG do |t|
   print "\n********** RESULT\n\n"
   all_green = true
   # ANSI カラーコードを付加してカラー表示にする
   print IO.readlines(LOG).map { |line|
             line =~ /^(.*?)( \(Errors: .*?(\+)?\))?$/
             all_green = false if $2
             $3 ? "y \e[43mYELLOW \e[0m #{$1}#{$2}\n" :
             $2 ? "x \e[41m  RED  \e[0m #{$1}#{$2}\n" :
                  "o \e[42m GREEN \e[0m #{$1}\n"
         }.sort.join
   puts "\nAll \e[42m GREEN \e[0m !!!\n" if all_green
 end
 
 task "errors" => LOG do |t|
   IO.readlines(LOG).each do |line|
     next unless line =~ /([^\/]+) \(Errors: .*/
     print "\n********** #{$1}\n\n"
     IO.readlines("beh/temp/#{$1}").each do |l|
       print l if l =~ ERROR_REGEX
     end
   end
 end
 
 task "clean" => [] do |t|
   sh "rm #{TEMP}/*" unless Dir["#{TEMP}/*"].empty?
 end

プロジェクトフォルダに beh/ というフォルダを掘って、
テストベンチの *_beh.prj というファイルを好きなだけ入れておきます。

プロジェクトフォルダで simulation.rake を起動すると、
+ すべての beh/*.prj に対して
-- コンパイルしてシミュレータ beh/temp/*.exe を作成
-- 実行結果を beh/temp/*.log として保存
-- エラー数を beh/temp/*.result に集計
+ 各テストベンチの吐いたエラー数を集計して beh/temp/simulation.log に保存
+ 集計結果をカラー表示

を自動実行します。

以下の例では ad7982_test_beh.prj pulser_test_beh.prj 
の2つだけが入っている状況で試しています。

 LANG:console
 $ ls beh/*.prj
 beh/ad7982_test_beh.prj  beh/pulser_test_beh.prj
 $ ./simulation.rake
 
 ********** ad7982_test_beh.exe
 
 cp beh/ad7982_test_beh.prj ad7982_test_beh.prj
 fuse -intstyle ise -incremental -lib unisims_ver -lib unimacro_ver -lib xilinxcorelib_ver -o beh/tem
 p/ad7982_test.exe -prj ad7982_test_beh.prj work.ad7982_test work.glbl
 
 (中略)
 
 ********** RESULT
 
 o GREEN : pulser_test.log
 x  RED  : ad7982_test.log (Errors: 1000)

実際には最後の結果表示は &COLOR(GREEN){GREEN}; と &COLOR(RED){RED}; 
が色分け表示されるので、
多数のテストベンチを走らせるとテスト駆動開発っぽい感じを味わえます。
(テスト駆動ではテストの成功をグリーン、失敗をレッドと呼びます)

rake を使っているので、もう一度同じコマンドを起動しても、
ソースファイルやインクルードファイルが変更されていなければテストは実行せず、
単に過去に実行したログを集計して表示します。

&attachref(simulation実行結果.png);

↑こんな風にカラーになります。

もちろん HDL ソースを変更すれば、
そのソースに依存したテストのみが再コンパイル&実行されます。

** 詳細なエラーの表示 [#oce557bf]

 LANG:console
 $ ./simulation.rake errors

とすると、詳細なエラーメッセージを表示できます。

やってるのは .log ファイルからエラーメッセージを grep しているだけです。

 LANG:console
 $ ./simulation.rake errors | head
 
 ********** ad7982_test.log
 
 ../../src/components/test/ad7982_test.v(131) ERROR at #1020 conv パルス幅は 20ns 以上必要.
 ../../src/components/test/ad7982_test.v(131) ERROR at #2020 conv パルス幅は 20ns 以上必要.
 ../../src/components/test/ad7982_test.v(131) ERROR at #3020 conv パルス幅は 20ns 以上必要.
 ../../src/components/test/ad7982_test.v(131) ERROR at #4020 conv パルス幅は 20ns 以上必要.
 ../../src/components/test/ad7982_test.v(131) ERROR at #5020 conv パルス幅は 20ns 以上必要.
 ../../src/components/test/ad7982_test.v(131) ERROR at #6020 conv パルス幅は 20ns 以上必要.

- 本当は 10ns あれば良いのですが、わざと失敗させるために 20ns としてみました。

チェック側を 10ns に書き換えてもう一度走らせると、

&attachref(simulation実行結果green.png);

のように「オールグリーン」になりました。

書き換えていない pulser_test.v の方はコンパイルや実行が行われていないことに注目して下さい。
rake を使うことで、更新の必要なテストだけを選択的に行うことができます。

上記スクリーンショットで分かるとおり、レポート表示の際にはエラーが見つけやすいように 
RED のものが GREEN よりも後に表示されるようになっています。

** 一時ファイルの消去 [#rb136570]

 LANG:console
 $ ./simulation.rake clean

とすると、beh/temp/* をすべて消去します。

`include も可能な限り調べて依存関係を構築していますが、
ソースの編集後、うまくテストが実行されない場合には
一旦 clean して、再実行すれば良いかもしれません。

該当の .log ファイルのみを削除した方がコンパイル時間が
かからず楽ですが。

** .log ファイルからのタグジャンプ [#cf976102]

テストベンチの実行結果は beh/temp/simulation.log 
と言う名前のファイルにまとめられます。

このファイルの中身は非常に単純で、

 ad7982_test.log (Errors: 1000)
 pulser_test.log

のように、個々のテストベンチのログファイル名と、
その中で失敗したテストの数が表示されます。

秀丸などでは対応する行から F10 でタグジャンプすれば、
該当のログファイルを開けます。

失敗した個々のログファイルには、エラー発生を示す

 ../../src/components/test/ad7982_test.v(130) ERROR at #1020 conv パルス幅は 20ns 以上必要.

のような行が含まれています。
秀丸などではこのような行からタグジャンプすれば、
該当する HDL ソースのエラー発生行へ飛ぶことができます。

 LANG:verilog
    ...
 
    real t;
    always @(posedge conv)
        fork
            begin
                t = $realtime;
                @(negedge conv);
                if ( $realtime() - t < 20 ) begin
                    $display($realtime() - t);
                    `ERROR(conv パルス幅は 20ns 以上必要);   // <= こことか
                end
            end
        join

※このようなパルス幅のチェックは、specify ブロックの $width 
を使えばもっと簡単に書けますね(忘れてましたorz)

** エラー行の検出 [#v528de27]

現在、エラー行は /^ERROR:|ERROR at|^WARNING:.*Timing violation in/ という正規表現で検出しています。

- ^ERROR : コンパイルエラーを検出
- ERROR at : `ASSERT_XXX によるエラーを検出
- ^WARNING:.*Timing violation in : specify によるエラーを検出

上記 rake ファイルの ERROR_REGEX 定数を変更することにより任意の形式のエラーを検出可能です。

検証に ovl などを使った場合にも対応できるかもしれません?

* Windows7 64bit 環境での ISim テストベンチ実行 [#bfbc6d92]

- 以下の内容は ISim 12.4 での調査結果です。~
13.1 では多少改善されていそうですが、まだ確かめていません。

何が悪いのか分かりませんが、私の環境では非常にバグバグしています。

普通にコマンドラインからテストベンチを起動すると、
すぐさまコマンドプロンプトが表示され、
その後、テストベンチのプロンプトが表示されます。

キー入力は、不思議な具合にシェルとテストベンチとに振り分けられて、
どちらに入力されるか一定しません。テストベンチへの入力はエコーされず、
シェルへの入力はエコーされるので、ようやくどちらに入るか分かる感じ。

この現象は通常のコマンドプロンプトでも cygwin 環境でも変わらないので、
cygwin が悪いとかそういう話ではなく、(少なくとも私の) Windows7 64bit 
環境と ISim テストベンチとの相性が悪いみたいに感じられます。

そのせいなのかどうか、ruby スクリプトから system 関数等で
テストベンチを起動すると、テストベンチの終了を待たずすぐさま
ruby スクリプトに戻ってきてしまうため、
その後すぐにログを読んでしまうと、正しくすべてのログを読めません。

この現象を回避するには、IO.popen でテストベンチの標準入出力へ
パイプを繋ぐのが良いようでした。上ではそのようにしてあります。
パイプの readline が空になるまで読み出すことで、
テストベンチの終了を待つことができます。

ただ、このように起動したテストベンチも、
やはり通常の正しいプロセス管理が及ばないようです。

- テストベンチの終了前に ruby スクリプトがエラーで落ちてもテストベンチが走り続ける
- IO.close は本来テストベンチ側の終了を待つはずなのに、待たずに戻ってきてしまう
- IO.pid への Process.kill によるシグナル送信に失敗する
- popen を呼び出したスレッドやプロセスを kill しても、テストベンチだけ走り続ける

本当なら、テストベンチ実行中にログファイルが大きくなりすぎたり、
非常識に時間がかかったりした場合には、そのテストベンチを強制終了して次のテストベンチへ
移りたいのですが、上記の不具合のためにそれができていません。

現状では、テストベンチ実行中にログファイルが大きくなりすぎた場合には、
それ以上ログを書き込まず、末尾に === EXECUTION ERROR : TOO LARGE LOG FILE === 
というマーカーが残ります。

例えば $finish を書き忘れたテストベンチなどはいくら待っても終了しないわけですが、
そのようなテストベンチを Ctrl+C で落とした場合、ログの末尾に
==== EXECUTION ERROR : INTERRUPTED ==== というマーカーが残ります。

このように、正しく終了しなかったテストベンチについては、
simulation.log のエラー数が + 付きで表示され、
&COLOR(YELLOW){■};イエロー&COLOR(YELLOW){■}; マークが付けられます。

&attachref(simulation-result-yellow.png);

コンパイルエラーも同様に &COLOR(YELLOW){■};イエロー&COLOR(YELLOW){■}; マークが付けられます。

* しばらく上記方針でやってみる [#x4a0c197]

数個のテストベンチを突っ込んでみましたが、
思った通りに動いてくれているようですので、
しばらくこれで続けてみようと思います。

テストベンチを増やしてグリーンの数が増えるのが楽しみになったりすると、
すでにテスト駆動開発の罠にはまってたりするわけです(笑

実際、毎回テストでグリーンを見るのは開発者に安心感とやる気を持たせる
心理的な効果があるそうで、そのあたりまで考えた上での自動テストなんだそうです。

レガシーなテストベンチを徐々に移行して、現在はこんな感じ:~
&attachref(auto_tests.png);

* ISim 13.1 対応状況 [#z77c8942]

ISim 13.1 ではシミュレータを落とさずにテストベンチを再コンパイルできるようになりましたが、
コマンドラインからの利用には大きな変更は無かったようで、12.4 用に書いた rake 
ファイルがそのまま使えました。

* コメント [#h77e4c69]

#article_kcaptcha
**ModelSimPE キャンペーン中 [#df4b392e]
>[アプロ] (2011-02-24 (木) 14:53:01)~
~
2011年3月25日受注まで、~
http://www.paltek.co.jp/event/index.htm~
または、~
http://www.altima.jp/campaigns/modelsim_xe_xse_campaign.html~
ModelSimXE のライセンスファイルが必要みたいです~

//
- かなり安くなっていますね。でもまだ手が出せる値段ではないです orz -- [武内(管理人)] &new{2011-02-24 (木) 15:07:17};
- ModelSim PE Student Edition - HDL Simulation は無料みたいですが・・・ http://model.com/content/modelsim-pe-student-edition-hdl-simulation -- [アプロ] &new{2011-02-24 (木) 16:06:55};
- 高等教育機関向け製品貸与プログラム(HEP) http://www.mentorg.co.jp/company/higher_ed/index.html -- [アプロ] &new{2011-02-24 (木) 16:14:26};
- 情報ありがとうございます! HEP というのもあるのですね。これは非常に魅力的なので調べてみようと思います。 -- [武内(管理人)] &new{2011-02-24 (木) 16:19:43};
- \82,800 で、憧れのQuestaでアサーションが使い放題(笑)  -- [アプロ] &new{2011-02-24 (木) 16:47:29};

#comment_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};
- テストケースは、taskにして、for でループさせるとよいですよ。ひとつのテストベンチで済みます -- [アプロ] &new{2011-02-17 (木) 08:11:59};
- テストベンチ部品の task 化、便利に使っています。verilog 言語は task や function のスコープがモジュール内限定のグローバルであることや、テストベンチでは下位モジュール内の信号やイベントに直接介入可能であることなど、言語仕様に自由度が大きいので、自分なりのコーディング規約が定めにくく、経験不足を感じています。 -- [武内(管理人)] &new{2011-02-17 (木) 09:05:44};
- http://www.asic-world.com/verilog/verilog2k1.html (2k2, 2k3とすると次のページが出ます) task、function に automatic を付けると、コールされるとメモリに動的に生成します。これで、task、function 内でローカル変数をバシバシ使ってください -- [アプロ] &new{2011-02-17 (木) 18:29:24};
- ログファイルは、テストベンチ内で、ファイル名を生成することで、いかようにも制御可能になります -- [アプロ] &new{2011-02-18 (金) 12:17:58};
- automatic 知りませんでした。付けないと同時に呼び出せないんですね。テストベンチ内からログファイルを開くのも一考の余地がありそうです。 -- [武内(管理人)] &new{2011-02-18 (金) 22:17:58};
- Verilog HDL&VHDLテストベンチ記述の初歩 by CQ出版 はどーでしか? 目次を見た限りでは、よさそうな気がします。私も買ってはいないのですが(笑) -- [アプロ] &new{2011-02-21 (月) 12:19:23};
- 買って、積んで、忘れてましたw パターンファイルを使った検証あたりなどは上で考えているのとかぶるところも多そうですね。改めて見なおしてみます。 -- [武内(管理人)] &new{2011-02-21 (月) 12:40:11};
- ISE 13.1リリース -- [アプロ] &new{2011-03-01 (火) 16:58:22};
- うわ、はやw ダウンロードするのが怖い(^^;;  >情報ありがとうございます -- [武内(管理人)] &new{2011-03-01 (火) 17:22:56};

#comment_kcaptcha

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