クラスのデバッグ方法(How to debug Class )

クラスのインスタンスは、実行時に動的に生まれたり死んだりするので、従来の波形HistoryViewerは、使えません。では、どうやってデバッグして行くのか?という疑問が生じると思います。

シミュレータには、オブジェクトViewerと言った類のViewerが付属しています、それを使ってクラスのデバッグを行います。これは、通常のプログラミング言語におけるデバッガと同じです。

以下は、VeritakSVに付属するViewerでの話ですが、ステップ実行、ブレークポイント、コールスタック、等は他のシミュレータでも同等の機能を備えていると思います。


次のソース例で見ていきましょう。

オブジェクトビューアは、現在の実行状態の値の確認に使う

class C;
        int i1;
        function new();
                i1=100;
        endfunction
endclass

class B;
        C c1;
        function new();
                c1=new;
        endfunction
endclass

class A;
        B b1;
        function new();
                b1=new;
        endfunction
endclass


program test_top;
        A a1;
        
        initial begin
                a1=new; 
                $display("hi");
        end

endprogram

<ステップ実行、ツールチップ履歴>

これをステップ実行させて、4行目まで実行させた様子が下です。Notepad++の4行目の直前で実行を停止した状態になっています。 4行目の変数i1にマウスカーソルを置くと、ツールチップが出てきて現在の値を確認することができます。ツールチップは、右側のViewerの方にも送られて、履歴としても見ることができます。 現在の値は、intのdefault値、0で初期化された状態になっていることが確認できます。(i1=100;実行前)

<コールスタック>

この状態は、クラスCのコンストラクタfunction new が実行されていることは、分かります。では、この関数は、どこから起動されたのでしょうか?
それが分かるのがコールスタックです。4行目は、11行目の c1=newで起動され、同様に、18行目、最終的に、27行目のa1=newを起源とする事が分かります。

ファイル名(行No)をクリックすると、当該箇所にエディタが移動します。それぞれのスタックの状態は、スコープの列をクリックすることで見ることが出来ます。


下が、その様子ですが、スコープオブジェクトに、test_top アイテムが挿入されます。test_topのオブジェクトとしては、クラスハンドルa1があるので、その中身を見ることが出来ます。しかしながら、現在は、未だクラスAのコンストラクタが終了していないので、a1の中身は未だ存在しません。ですので、何も表示されていません。




もう1ステップだけ実行させると、28行目に着きました。

-この位置で静止している状態です。
-ステップ実行により、コールスタックとスコープオブジェクトを、クリアしています。 ツールチップ履歴は、履歴として、残しています。

さて、この状態では、クラスAのコンストラクタは、終了しているので、a1の中身を見ることができるはずです。スコープtest_topをクリックします。


確かに、クラスAは、クラスBを、クラスBは、クラスCを持ち、クラスCは、int i1を持ち、100に初期化(64Hex)されている様子が分かります。

+マークをクリックしていくと子どものオブジェクトを見ることができます。

このように、オブジェクトビューアで、現在のオブジェクトの値を見ることができます。

実行途中の状態を一々$displayしなくとも、ブレークポイント、ステップ実行、ツールチップ、コールスタック、オブジェクトビューアで、値を確認することができます。基本的にクラスオブジェクトは、ダイナミックな動きをするので、このような手法によるデバッグが有効でしょう。

-コンパイル時に全てのオブジェクトサイズが決定するハードウェアのデバッグとしては、波形ビューアが依然として有効でしょう。


再帰関数のデバッグ- automatic変数の違いを知る


HW設計者を悩ませるのは、一般のプログラミング言語では一般的なautomatic変数かもしれません。automatic変数は、再帰関数の動作を追っていくと よく理解ができるのではないかと思います。

例によって、C言語で書かれた階乗を求めるソースをSV版に書き換えてみました。


program recursive_function_test;

        function automatic int Factorial2(int n);
 
                if (n == 0 || n == 1)        /* nが0か1なら */
                        return (1);             /* 1を返す */
                else                         /* nに自分より1小さい自分を掛ける */
                        return (n * Factorial2(n -1));
        endfunction

        initial begin
                $display("Fatorial2(3)=%d",Factorial2(3));
                $display("Fctorial2(4)=%d",Factorial2(4));
        
        end

endprogram

これをステップ実行し、6行目に到達するまで実行しました。

unction がautomatic と宣言されているので、関数内の変数nは、automatic 変数になります。
functionの場合、VeritakSVでは、C言語と同様に、スタック上にautomatic 変数(C言語的には、ローカル変数)を確保しています。

コールスタックで、各スタックのスコープを出力して見ると、一番子どものn (コールスタック最上位)が 1であることが読み取れます。その子どもを呼んだ親のn は、2、さらにその親は、3という風に値が読みとれます。また、各スタック状態には、アドレスが割り振られていてそれぞれ別のアドレスが割り当てられていることが分かります。

static変数は、一生を通じて、一つのアドレスしか持ちませんが、automatic変数は、このように、呼び出し状態によって、局所的なアドレスと値を持つことになることが理解できると思います。

上で、function automatic のautomatic を付け忘れると、nは、static 変数となり、一つのアドレスしか持たないので、上書きされ再帰動作をすることができなくなってしまいます。

コールスタックの仕組み

コールスタックの表示機構についての説明です。(ユーザシミュレーションとは直接関係ありません。)

このアドレスは、ユーザからは、普通は見えないようになっていますが、オブジェクトビューアで見えてしまっています。EBPは、X86のフレームレジスタという物理レジスタの値です。function内のローカル変数は、EBPが示すアドレスからのその変数固有のオフセットを持ったアドレスに値を持ちます。 この例の場合nは、 EBP-0x28アドレスに値があります。

関数を呼ぶ毎に、新しいスタックフレームが、上側(アドレス低位側)に確保されていきます。関数から戻るには、スタックフレームを巻き戻していきます。

EBPからのオフセット Remarks
スタックフレーム -0x28 n 4バイトあれば十分だが、16バイト確保している
-0x18 Factorial2 4バイトあれば十分だが、16バイト確保している
0x0 親のEBP
+0x04 戻り番地 (X86の場合)
+0x08 第一引数
スタックフレーム
スタックフレーム

これをメモリビューアで確認してみます。

コールスタック最上位のEBPが示すアドレスa4cf9c0を見てみると,

-X86はリトルエンディアンであることに注意して値を読み取ります。sizeof(void*)は、4バイト,sizeof(int)は、4バイトです。

EBP+0 0a4cfa1c 前の(親のフレームポインタ)
EBP+4 100b00e5 戻り番地
EBP+8 1 第一引数

となっていることが分かります。

同様に、子から親へスタックフレームを辿る(スタックウォーク)ことで、各スタックフレームの内容を知ることができます。コンパイラは、各変数のEBPに対するオフセットを承知しているので、値を読み取ることができるという訳です。

以上は、functionの話です。functionが高速なのは、ローカル変数の確保に殆ど時間がかからないためです。しかし、functionは、taskと違い時刻を跨いで呼びだすことができません。これは、単純なスタックによる実装だからです。(LRMは、実装を既定していませんが想定していると思います。) 




インターフェースのデバッグ

インターフェース自体は、moduleと同じ静的なオブジェクトですので、波形ビューアで履歴を見ることができます。しかし、インターフェースを参照する側がどのように見ているかは、波形ビューアでは、分かりませんので、その場合はオブジェクトビューアで見ることになります。

下は、ブレークポイントを置いて止めたものです。このスコープの中に、インタフェースハンドルが含まれるので、スコープオブジェクトに出力して+展開することで、インターフェースオブジェクトを見ることが出来ます。あるいは、単にc1をツールチップで参照することができます。



仮想インターフェースのデバッグ

インタフェース自体は静的ですが、仮想インタフェースは、ダイナミックな参照です。以下の例では、仮想インターフェースv1をツールチップで見ています。