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

更新


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

#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]

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

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

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

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

 LANG:verilog
 `define ERROR(msg) \
    $display(`"%s(%0d) ERROR at #%0d: msg.`", `__FILE__, `__LINE__, $time)
 `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: 思ってたのと値が違う!.
 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
 
   // シミュレーション時以外は何もしない
   // (パラメータ付マクロの時、無駄な /* */ を入れておかないとマクロ自体が
   // 定義されないバグがある? Xilinx ISE 13.1(nt64) O.40d)
 
   `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) 
   `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) /* */
   `define ASSERT_FINISH_IN(time) /* */
 
 `else
 
   // ISim 上での動作
   // ファイル名&行番号も表示する
   
   `define INFO(msg)   \
       $display(`"%s(%0d) INFO at #%0d msg.`",  `__FILE__, `__LINE__, $time)
       $display(`../../"%s(%0d) INFO at #%0d msg.`",  `__FILE__, `__LINE__, $time);
   
   `define ERROR(msg)  \
       $display(`"%s(%0d) ERROR at #%0d msg.`", `__FILE__, `__LINE__, $time)
       $display(`"../../%s(%0d) ERROR at #%0d msg.`", `__FILE__, `__LINE__, $time);
   
   `define DONE        \
       $display(`"%s(%0d) DONE at #%0d.`",      `__FILE__, `__LINE__, $time)
       $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,      \
           $display("../../%s(%0d) ERROR at #%0d: ", `__FILE__, `__LINE__, $time,      \
                    `"Assertion [ variable === value ] failed `",                \
                    variable, " != ", value, ".")
                    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,  \
               $display("../../%s(%0d) ERROR at #%0d: ", `__FILE__, `__LINE__, $time,  \
                        `"Assertion [ variable === value ] failed `",            \
                        variable, " != ", value, ".")
                        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,  \
               $display("../../%s(%0d) ERROR at #%0d: ", `__FILE__, `__LINE__, $time,  \
                        `"Assertion [ variable === value @ the_event ] failed `",\
                        variable, " != ", value, ".")
                        variable, " != ", value, ".");
 
   // シミュレーションが time が経過する前に終了することを検証します。
   // 終了する前に($stop / $finish が呼ばれる前に) time の時間が経過した場合に
   // エラーメッセージを表示して終了します。テストベンチがバグのために永遠に終了
   // しなくなってしまう問題を回避するために使用します。
   `define ASSERT_FINISH_IN(time)                                            \
         initial begin                                                       \
             time;                                                           \
             $display("==== EXECUTION ERROR : INTERRUPTED ====");            \
             $stop;                                                          \
         end
 
 `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 違反が検出可能になります。

*** specify による応用的な検証 [#z174c683]

http://www.archi-brain.com/techno/verilog/Verilog11.html に詳しい。~
http://verilog.renerta.com/mobile/source/vrg00052.htm あたりも参照

 LANG:verilog
    specify
        $width(posedge start, 10);                    // start pulse の幅が 10 ns 以上
        $setup(data, posedge clk &&& !fastmode, 100); // 条件付き検証
        $setup(data, posedge clk &&& fastmode, 10);   // fastmode の時は 10 ns でOK
        $skew(posedge start, posedge done, 1000);     // start から done までに 1 us 以下
    endspecify

** 波形を比較するというアプローチ [#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 等が使えそうです。サブモジュールとする場合に、
~$fopen, $fwrite, $fscanf 等が使えそうです。サブモジュールとする場合には
~$fclose を呼び出すタイミングが難しそうですが・・・~
http://www.asic-world.com/verilog/verilog2k3.html

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

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

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

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

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

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

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

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

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

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

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

ISE の 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
これをしておかないとコンパイルできなかったり、
テストベンチが起動しなかったり悩みます。

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

私はテスト用のツールを cygwin 環境で使いたかったので、~
C:\Xilinx\12.4\ISE_DS\settings64.bat を同じディレクトリに~
C:\Xilinx\12.4\ISE_DS\settings64-cygwin.bat という名でコピーして、~
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 のショートカットを作成して、プロパティから
この .bat のショートカットを作成し、プロパティから
- 編集オプション : 簡易編集モード
- フォント
- レイアウト
- アイコン

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

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

私は hoge モジュールのテストベンチに hoge_test 
と言う名前を付けているので、
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_isim_beh.prj には
ここで参照している 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]

できあがったシミュレータを実行するときは、
標準入力に run を送ってやる必要があります。
(もちろん、バッチファイルを記述しても良いのですが、
標準入力に run を送った方が楽なので、ここではそうしています)
できあがったシミュレータを実行するとき、
ISE では isim.cmd というファイルをバッチファイルとして使っています。

また、標準出力をファイルにリダイレクトすることでログを取ることができます。
自分でやる際には標準入力に "run all" 等の文字列を送ってやれば
バッチファイルを作る必要はないようです。

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

 LANG:console
 $ (echo run | hoge_test_isim_beh.exe) > hoge_test_isim_beh.log
 $ (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/foba_test.v(66) ERROR Assertion [ value == expected ] not met at #0: 0 != 1
  C:/hoge_proj/hoge_test.v(66) ERROR Assertion [ value == expected ] failed 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
  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; で終了するまで走り続け、
その間に、もしエラーが生じればログファイルにログを残します。

** 複数のテストベンチを一括で自動実行するには [#i2987a2b]
$finish を書き忘れると戻ってこないわけですが・・・
そこは書く方が気をつけると言うことで。

ISE が作ってくれる *_isim_beh.prj ファイルを多数 beh/ フォルダに入れておいて、
下記の ruby スクリプトを走らせると、
それらを順番にコンパイルして実行することができます。
いざというときは Ctrl+C で強制終了できます。

 LANG:ruby
 #!/usr/bin/ruby
 
 # 古いログファイルを消す
 system("rm simulate_all.log")
 
 # .prj ファイルすべてをコンパイル&実行する
 Dir.glob("beh/*.prj").each do |prj|
   prj =~ /([^\/]+)_beh.prj/
   module_name = $1
   prj = $~                          # 先頭の beh/ を取り除く
   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 ===== #{module_name}_isim_beh.exe >> simulate_all.log")
   system("echo run | ./#{module_name}_isim_beh.exe >> simulate_all.log")
 end
夜間に多数のテストを一気に走らせるような場合を考えると、
+ ログファイルが大きくなりすぎた場合
+ 実行時間があまりに長すぎる場合

この場合、実行結果は simulate_all.log と言うファイルにログとして残ります。
シミュレータを強制終了して次のテストへ移ってくれるのがベストですね。

ただ、まだいろいろ改善したいところも残ってます。
** 複数のテストベンチを一括で自動実行する [#i2987a2b]

*** 必要なファイルだけコンパイル&実行したい [#k364db4c]
上記の操作をすべてのテストベンチに対して繰り返せば自動実行できます。

上記スクリプトではファイルが更新されたかどうかにかかわらず、
すべての .prj をコンパイル&実行しています。
ログファイルとソースファイルのどちらが古いかをみて、
必要な物だけ実行するよう変える必要がありますね。
しかし、ただ自動実行するだけではいろいろ使いづらいので、
実際の実行は rake を使って後ほど示すようなスクリプトで行います。

*** テストベンチ .exe の挙動がおかしい [#baf8f5fb]

上記で、一応動くことは動くんですが、
コンパイルしてできた *_isim_beh.exe ファイルを実行する際に、
私の環境では内部で fork してすぐに返ってきてしまうようで、
シミュレーションの終了を待たずに次のコンパイルが始まってしまいました。

これだとPCに余計な負荷がかかるだけでなく、ログファイルにはき出される
ログの順序が乱れてしまうことも考えられます。

この現象、スクリプトからでなく手動で起動した場合にも、
tcl コマンドのプロンプトがバックグラウンドプロセスに
回ってしまっているような動作をしていて、とても不思議です。

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

もう少し調査してみますが、最悪は Windows API 
でプロセス一覧を調べるようなアホなことをしなきゃならないかも。

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

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

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

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

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

*** コンパイルエラーが生じたときの対応 [#d23043df]

そうそう、コンパイルエラーへの対処もちゃんとしなきゃ。

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

ログファイルを grep あるいは ruby かなにかのスクリプトで
処理すればできるんじゃないかと考え中。
個々のテストベンチの成功・失敗をまとめたログファイルを作成して、
それをカラー表示します。

簡単には、
 LANG:console
 $ grep -E "ERROR|^=====" simulate_all.log | nkf -s

とすることで、実行したテストベンチ名と、
その中でもしあればエラー行のみを一覧表示できるようです。

* プロジェクトフォルダ内のフォルダ構成をどうするのが良いか [#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

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

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

シミュレーション結果のログファイルを、
テストベンチごとに異なる名前にしておけば、
その .log ファイルと、関連ソースファイルと、
どちらが新しいかを比べて、必要なテストだけを走らせることができそうです。

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

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

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

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

* とりあえず上記方針でやってみる [#x4a0c197]
* rake ファイルを作ってみた [#w5d2c35a]

プロジェクトフォルダの構成とか変更して、
試してみようと思います。
やりたいこと、に対して、できたところ、まだのところ、両方あります。

git はファイルの移動も覚えてくれるはずなので、
とりあえずやってみて、うまくいかなければ戻せばいいはず。
自動テストの流れ:
+ テストベンチごとに 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 というファイルにレポートを作成
+ 実行結果をカラー表示

ファイルの移動の巻き戻しとか、あんまりやったこと無いけど(^^;
その際、
- ソースの編集状況を見て必要なファイルだけコンパイル&実行したい
- コンパイルエラーへ正しく対応する
- ログファイルの最大サイズをどのように制限するか

* ISim 13.1 ではいろいろ変わるらしい [#z77c8942]
など、検討すべき点がいくつかあります。

ISim 12.4 のマニュアルによると、
ISim 13.1 ではシミュレータを落とさずにテストベンチを再コンパイルできるようになるそうです。
*** 必要なファイルだけコンパイル&実行したい [#k364db4c]

ModelSim XE と ISim との比較の記述を抜粋>
>シングルクリックコンパイルおよび更新
>
>ModelSim XE では、スタンドアロンGUI にテキストエディタがビルトインされているので、HDL コードの変更、再コンパイル、および再シミュレーションが実行できます。
>
>ISim GUI にはテキストビューアはありますが、テキストエディタはありません。ファイルに変更しても、再コンパイルおよび再シミュレーションは実行できません。既存のシミュレーションを閉じて、再起動する必要があります。この制限は、13.1 のISim で修正される予定です。
テスト結果のログと、HDLソースファイルのどちらが古いか見て、
前回テストをしてからソースが書き換えられたものだけを
選んでコンパイル&実行しないと、テストベンチの数が多い場合は
無駄に時間がかかってしまいます。

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

そうなると、生成されるのは .exe ではなく .dll になるんでしょうし、
現状でいろいろ工夫しても、すぐにはしごを外されるのかもしれません。
ちゃんとソースファイルをスキャンして、
`include 行があればその先も依存関係に含めるようにしています。

ModelSim XE の時がそうだったし・・・~
書きためた数十の .fdo/.udo ファイルとか、消すのももったいないけど、
すでに無用の長物とか、泣けてきます(TT
そのほかの依存関係としては $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/07/06 改訂:
 LANG:ruby(linenumber)
 #!ruby -Ks
 #
 # simulation.rake
 # Written by Osamu Takeuchi <osamu@big.jp>.
 # URL: http://dora.bk.tsukuba.ac.jp/~takeuchi/
 #
 # 2011.07.06 ISE のバージョンアップに伴い .prj ファイルに含まれる
 #            glbl.v へのリンクが古くなってしまう現象に対応
 # 2011.03.07 ヘッダ情報を追加、beh/temp を #{TEMP} に書き換え
 #            desc を追加、-Ks を追加、設定用定数を整理、
 #            表示メッセージを整理
 # 2011.03.05 コンパイルエラーへの対応、.result の導入
 # 2011.02.28 specify エラーの検出、表示バグの除去
 # 2011.02.25 テストベンチのプロセス管理に popen を使う
 # 2011.02.25 初出
 #
 # Usage: ./simulation.rake -T
 #   使用可能なタスク一覧を表示する
 #
 # Usage: ./simulation.rake -P
 #   依存関係一覧を表示する
 #
 # Usage: ./simulation.rake
 #   beh/*.prj を読んで必要なテストを実行し各テストの正否を表示する
 #
 # Usage: ./simulation.rake errors
 #   失敗したテスト結果の詳細を表示する
 #
 # Usage: ./simulation.rake gui
 #   失敗したテストを isimgui で起動する
 #
 # Usage: ./simulation.rake clean
 #   beh/temp/* に作成された一時ファイルを消去する
 #
 # Usage: ./simulation.rake clean
 #   beh/temp/ フォルダ自体を消去する
 
 # rake は -Ks を認識しないので、
 # shebang に #!ruby -Ks /usr/bin/rake -f と書きたいところなんだけど、
 # cygwin の bash では shebang に複数のオプションを書けないため、
 # 以下のトリックを使う。
 unless defined? Rake
     # → rake を経由せずに起動された場合、自分自身を rake に渡す
     ARGV.unshift $PROGRAM_NAME
     ARGV.unshift "-f"
     load `which rake`.chomp
     exit
 end
 
 # 各種設定
 DIR  = "beh"                    # プロジェクトフォルダ
 TEMP = "#{DIR}/temp"            # 一時ファイル置き場
 LOG  = "#{TEMP}/simulation.log" # 実行結果ログファイル
 PRJS = FileList["#{DIR}/*.prj"] # プロジェクトファイルのリスト
 ERROR_REGEX =                   # ログファイルからエラー行を取り出す正規表現
     /^ERROR:|ERROR at|^WARNING:.*Timing violation in/
 MAXLOGSIZE = 1000000            # ログファイルの最大サイズ(これ以上は切り捨てられる)
 LIBS =                          # fuse でリンクするライブラリ名
     [ "unisims_ver", "unimacro_ver", "xilinxcorelib_ver" ]
 DEFS =                          # fuse で定義するマクロ名 ("name=value" の形で値も指定可能)
     [ "simulation" ]
 GLOBAL =                        # fuse に指定するグローバルモジュール名
     [ "work.glbl" ]
 MULTITHREAD = "on"              # "on", "off", "1", "2", "3", ...
 
 # デフォルトタスク
 task :default => ["show"]
 
 # テンポラリフォルダの定義
 directory TEMP
 
 # テストを最後まで実行できなかったことを示すメッセージ
 EXEC_ERROR_MARK  =
     "==== EXECUTION ERROR : "
 
 # テストを最後まで実行できなかったことを検出
 EXEC_ERROR_REGEX =
     /^#{EXEC_ERROR_MARK}/
 
 # テストを最後まで実行できなかったことを示すメッセージ
 def executeErrorMessage(msg)
     "\n#{EXEC_ERROR_MARK}#{msg} ===="
 end
 
 # モジュール名 name のテストベンチを実行してログを取る
 # Window7 64bit + cygwin 環境でのおかしな動作を回避するため
 # popen で起動している。そうでないと実行終了を待てない?
 # 本当はタイムアウト処理も自動で行いたいが、プロセスの管理が
 # どうしてもうまくいかないのでそのままになっている。
 # 例えば、Process.kill :INT, io.pid が pid not found でエラーになる
 def executeTestbench(name)
     open("#{TEMP}/#{name}.log", "w") do |log|
         # Ctrl+C による中断を検出してログに残す
         Signal.trap(:INT) { log.puts executeErrorMessage("INTERRUPTED"); exit }
         # テストベンチを起動
         IO.popen("#{TEMP}/#{name}.exe", "r+") do |io|
             # run all を送ったらすぐ標準入力を閉じる
             io.puts "run all"
             io.close_write
             # 標準出力をログファイルに保存
             io.each_line do |line|
                 log.puts line
                 # ファイルサイズが規定値を超えたらメッセージをログに残して終了
                 if log.pos > MAXLOGSIZE
                     log.puts executeErrorMessage(
                                 "TOO LARGE LOG FILE (#{maxlogsize} bytes)" )
                     break
                 end
             end
             # 残りは読み捨てる(本当は Kill したいのだけれどどうすれば?)
             io.each_line do |line|
                 ; # 何もしない
             end
         end
         # メッセージをディスクにはき出してからトラップを解除
         log.flush
         Signal.trap(:INT, "DEFAULT")
     end
 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
 
 # 結果ファイルのリストを初期化
 RESULTS = []
 
 # プロジェクトファイルからログファイルを作成する生成式を登録する
 PRJS.each do |prj|
 
     # .prj ファイル名からモジュール名を取り出す
     mod = prj[/([^\/]+)_beh\.prj$/, 1]
     unless mod
         # うまく取り出せなければスキップ
         print "********** Ignoring #{prj}\n\n"
         next
     end
     exe = "#{TEMP}/#{mod}.exe"    # テストベンチ名
     log = "#{TEMP}/#{mod}.log"    # ログファイル名
     res = "#{TEMP}/#{mod}.result" # 結果ファイル名
 
     # .prj ファイルの各行を読み込む
     prj_lines = IO.readlines(prj).map do |line|
         # Xilinxフォルダのバージョンが上がっている場合に対応する
         line.sub(/.:\/Xilinx\/\d\d.\d\/ISE_DS\/ISE\//, ENV['XILINX'].gsub(/\\/,'/'))
     end
     
     # .prj ファイルに書かれた依存関係を読み出す
     deps = prj_lines.map do |line|
         line[/"(.*)"/,1].sub(/^([A-Z]):/, "/cygdrive/\\1")
     end
 
     # `include をたどって依存関係を追加する
     deps = dependences(deps) << "#{TEMP}" << prj
 
     # .log の依存関係と生成式を登録
     file log => deps  do
 
         # メッセージ
         print "\n********** #{mod}_beh.exe\n\n"
 
         # .prj ファイルがプロジェクトフォルダにないと
         # fuse が HDL ソースを探せないのでコピーする
         open("#{mod}_beh.prj", "w") { |prj|
             prj.write(prj_lines.join)
         }
 
         # テストベンチをコンパイルしエラーを処理
         puts cmd = "fuse "+
                 "-intstyle ise "+
                 "-incremental "+
                 LIBS.map{|lib| "-lib #{lib} "}.join +
                 DEFS.map{|d| "-d #{d} "}.join +
                 "-o #{exe} "+
                 "-prj #{mod}_beh.prj "+
                 "--mt #{MULTITHREAD} "+
                 "work.#{mod} "+
                 GLOBAL.join(" ")
         if system cmd + " > " + log + " 2>&1"
             # テストベンチを起動してログファイルを生成
             puts "#{exe} > #{log}"
             executeTestbench(mod)
         else
             # エラーが生じたらログに記録
             sh "echo '#{executeErrorMessage('compile error')}' >> " + log
         end
 
         # テストベンチで $finish を忘れると終了しないため注意!
     end
 
     file res => log do
         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
             # レポートに追加
             message = nerrors == 0 ? log[/[^\/]+$/] : 
                       execerr      ? log[/[^\/]+$/] + " (Errors: #{nerrors}+)" :
                                      log[/[^\/]+$/] + " (Errors: #{nerrors})"
             puts "echo \"#{message}\" > #{res}"
             result.puts message
         end
     end
 
     RESULTS << res
 end
 
 # 個々の結果ファイルからレポート simulation.log を生成する
 file LOG => RESULTS do
     puts "\n**** simulation.log\n"
     puts "\ncat #{TEMP}/*.result > #{LOG}\n"
     open(LOG, "w") do |log|
         RESULTS.each do |result|
             log.write IO.read(result)
         end
     end
 end
 
 desc "テスト結果一覧を表示する"
 task "show" => LOG do
     puts "\n********** RESULT\n"
     puts "\n#{LOG}\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
 
 desc "詳細なエラーメッセージを表示する"
 task "errors" => LOG do |t|
     IO.readlines(LOG).each do |line|
         next unless line =~ /([^\/]+) \(Errors: .*/
         print "\n********** #{$1}\n\n"
         IO.readlines("#{TEMP}/#{$1}").each do |l|
             print l if l =~ ERROR_REGEX
         end
     end
 end
 
 desc "#{TEMP} の一時ファイルを消去する"
 task "clean" => [] do |t|
     sh "rm #{TEMP}/*" unless Dir["#{TEMP}/*"].empty?
 end
 
 desc "#{TEMP} フォルダを消去する"
 task "clobber" => [] do |t|
     sh "rm -r #{TEMP}" if FileTest.exist?(TEMP) && FileTest.directory?(TEMP)
 end
 
 desc "エラーのあったテストを isimgui で開く"
 task "gui" => LOG do |t|
     IO.readlines(LOG).each do |line|
         next unless line =~ /([^\/]+) \(Errors: .*/
         exe = $1.sub(/\.log$/, ".exe")
         system "#{TEMP}/#{exe} -gui &"
         break
     end
 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)

※モードによって条件が変化するような場合には、こうやって独自に書くための
イディオムを知っているのは便利かもですね。
(イーサーネットMACの10M/100M/1Gとか。)

** エラー行の検出 [#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){■} マークが付けられます。

** 実行時間が長くかかりすぎる時にテストベンチを強制終了したい [#w7c18ceb]

テストベンチあるいは非テストモジュールのバグで
テストベンチがいつまで経っても終了しない場合、
そのテストベンチを終了して次のテストベンチへ移りたいのですが、
上記の不具合のためにそれができません。

そこで以下のマクロを追加しました。

 LANG:verilog
   `define ASSERT_FINISH_IN(time)                                            \
         initial begin                                                       \
             time;                                                           \
             $display("==== EXECUTION ERROR : INTERRUPTED ====");            \
             $stop;                                                          \
         end

シミュレーション時間で time 経ったらエラーメッセージを出してテストベンチを終了します。

 LANG:verilog
   `ASSERT_FINISH_IN(#10000000)

のようにして使います。

これを書いておくことで、永遠に終了しないテストベンチになってしまうことを回避できると思います。

* ISE のバージョンアップに対応 [#g7574371]

ISE をバージョンアップしたところ、.prj ファイルに含まれていた 
 verilog work "C:/Xilinx/12.4/ISE_DS/ISE//verilog/src/glbl.v"

というような行が古いフォルダを指してたままであったため、
エラーになってしまいました。

現在使われている XILINX フォルダの位置は、環境変数 XILINX に
 C:\Xilinx\13.1\ISE_DS\ISE

のような形で入っています。

そこで、.prj ファイルに
 C:/Xilinx/??.?/ISE_DS/ISE/

という文字が含まれていたら、環境変数 XILINX の内容で書き換えるように
変更しました。

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

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

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

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

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

* ISim 13.1 対応状況 [#z77c8942]

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

(7/6追記)~
すみません、嘘でした。

~.prj ファイルに書かれた glbl.v への依存性に XILINX フォルダへのフルパスが記述されているために、ISE をバージョンアップして古いライブラリを消してしまうと、古い .prj ファイルをコンパイルできなくなってしまう問題がありました。

rake ファイルを修正し、.prj ファイルに書かれたフルパスを XILINX 環境変数で上書きすることで対応しました。

* コメント [#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};
- vcdでダンプするはだめですか? 波形保存 -- [アプロ] &new{2011-03-07 (月) 19:57:43};
- 画面にカラー付くのカッコいいです!! -- [アプロ] &new{2011-03-07 (月) 19:58:48};
- 確かに、vcdでダンプしてテストベンチ外で比較する方が楽そうですね。ダンプファイルに特定の名前を付ければrakeファイルで自動的に対応することもできそうです。検討してみたいと思います。 -- [武内(管理人)] &new{2011-03-07 (月) 20:48:20};
- 機能的にはカラー表示が無くても問題ないのですが、あった方がやる気が出るんですよね(笑 -- [武内(管理人)] &new{2011-03-07 (月) 20:50:09};
- やってみました。$dumpvars で vcd を作成するのはとても簡単なんですね。ただ、これを比較して差異を人に分かるように表示するのはちょっと面倒かもしれません。良いツールは無いでしょうか? -- [武内(管理人)] &new{2011-03-07 (月) 22:21:37};
- http://www.verilog.net/free.html ここを探して見てくまさい -- [アプロ] &new{2011-03-08 (火) 12:28:24};
- 情報ありがとうございます。VTracer というのが魅力的に見えたのですが Perl で書かれているようで、大きなファイルでは実用性が無いかもしれません?時間を掛けて探すより自分で書いた方が早いとか??? -- [武内(管理人)] &new{2011-03-08 (火) 12:38:41};

#comment_kcaptcha


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