002. moduleとclass
ぼくは元々ハード屋さんで、HDLという言語を使って論理回路を記述したりしてきましたが、業務でSpecmanと出会ったことがきっかけでその後OVM、UVMというclassベースの機能検証技術に携わるようになり ました。そして、Androidアプリ開発でJavaを、スクリプト言語のRubyを学ぶことで、classを用いることが半ば当たり前になりました。
ここのつぶやきでは、転職前の会社で悔しい思いをしたことへの再挑戦をしたいと思います。
それは、「classの良さ、使う理由を設計よりの技術者たちに伝えられなかったこと」です。
設計者はいいました。
「moduleで信号定義、task, function使えるんだから、classなんて使う必要ないでしょ」と。
たしかに、classにも信号(というかメンバ)、task, functionを定義して使うこともあります。当時のぼくは、自分の頭の中にあるイメージをうまく言葉にできないでいました。
じゃあ今はどうなのかと。
まずはclass(の中)でできることから書いてみます(知識から引っ張るだけなので抜けはいっぱいあります)
- task, function定義と、virtual化(moduleのtask, functionはvirtual化できませんってかしませんね)
- メンバ定義と、rand属性の付加(moduleの信号にrand属性はつけられません)
- rand属性メンバへの制約定義
- covergroupの定義
とりあえずこんなもんですかね。これだけ見ると、random検証しないならclassなんていらないのでは?と思えなくもありません。
で、動作モデルを定義するのに、classを使うか使わないかという話になったとき。んー具体的なものがあった方がいいですね。amba AXIをネタにしてみます。
AXIといえば、ライトトランザクションに3ch、リードトランザクションに2ch使うバスI/Fですね、たしか。各chは独立しています。さて、どうしましょうか。
以下は、ぼくの頭の中で行われている妄想です。
- 独立しているんだから、module定義にalways文使うんじゃないの?
- → まぁalways文使えば独立動作を書けるけど、なにが嬉しくて論理回路定義用に制限された文法を使うんですかねぇ。
- always使わないんだったら、時間処理はtask使うしかない。じゃあtaskを各ch分用意して、fork join使うのかい?
- → それは正解です。ついでにclassも使いましょうよ。
- え?なんでclassなんて使わなきゃならないの?(俺class知らないし…)そんなの使わなくたって動作定義できるだろう?
- → まぁそうなんですが。じゃあ例えばライトトランザクションは、ライトアドレスチャネルとライトデータチャネルのハンドシェイクが終わった後で、ライトレスポンスチャネルを処理しますよね。どんな 感じで処理しようと思ってます?
- んー…。ライトアドレスチャネルのハンドシェイク完了フラグと、ライトデータチャネルのハンドシェイク完了フラグをANDした信号を生成して、その信号がアクティブになったらレスポンスチャネルの処 理を開始する。レスポンスチャネルの処理を始めたら、ライトアドレスチャネルとライトデータチャネルは次のアドレス、データの処理を始めるようにしたらいいんじゃない?
- → えっと、それだとアドレスチャネルとデータチャネルに「不要」な待ちが入りますよね。ダメじゃないですか(笑)
- あ、FIFOが必要だな。
- → 必要ですね。いくつ必要です?
- そりゃあ各チャネルの、ハンドシェイク以外の信号の数だけだよ。当たり前でしょ。
- → あのー、動作モデルを作るのに、なんで論理回路の発想してるんですか?そんなめんどくさいこと。
- ムッ…じゃあどうするんだよ?
- → だからclass使いましょうよ、ってさっき言ったじゃないですか。
- class使って、どう解決するん?
- → そうですねぇ、classとmailboxを使いましょうか。classには、各チャネルのハンドシェイク以外の信号を定義します。5つチャネルがあるので、チャネル毎にclassを定義しましょう。
- mailboxってなにそれ。
- → SystemVerilogに初めから定義された型です。指定した型のデータを出し入れできるんです。
- んー、それとclassに何の関係が?
- → classは「ユーザが定義した型」なので、イメージとしては各チャネルの命令の「かたまり」をclassで型定義してmailboxに入れるんです。FIFOにデータを入れる感じで。あ、mailboxも各チャネル分必要 ですね。
- なになに?そんなことができるのか?(まだイメージができていない)
- → できます。で、各チャネルを処理するtaskは、それぞれのチャネルのmailboxを監視させておいて、classのかたまりが入ったら取り出して、クロック同期でチャネルを制御すればいいんです。そして、ア ドレスとデータチャネルのハンドシェイクが終わったら、完了情報をqueue配列にでも入れておく。レスポンスチャネルはqueueを監視しておいて、queueがemptyじゃなくなったら制御を始めるんです。 あ、queueだと自分でポーリング処理を書かなきゃならないので、queueよりmailboxの方がいいかも。
- なんか訳わからなくなってきた。整理してくれない?
- → 了解です。イメージとしては、各チャネルの「型」をmailboxに入れます。チャネル制御taskは、mailboxから「型」を受け取って、バスを制御します。
- あーなるほどね!(わかったふりをしている…かも)
長々と妄想してみました。
言いたいことを簡潔に書いてみると、「論理回路を書くわけでなく、ビヘイビアを書くだけなんだから、よりシンプルに構造を記述する方法を使いましょう」ということ。
んーどうかなー。これで伝わるかなぁ。
---追記
classは、元の記述を変えることなく、記述を追加したり処理を追加、変更することができます。この仕様をうまく利用して、コードの再利用性を向上させることができます。そのうち、何らかの説明を追加しよう と思います。(そのときはここのつぶやきの場所ではありません)