2015年6月4日木曜日

struct 五段活用

久々に C 書くと毎度ごっちゃになるので整理しておく。

基礎知識

変数の宣言

 変数名

typedef の宣言

typedef 別名

構造体の定義

struct 構造体名 定義

1. 標準型

struct mystruct {
  int foo;
};
struct mystruct bar;

おそらく一番オーソドックスな使い方。
構造体だからと言って盲目的に mystruct_t と _t を付ける人もいるが、"struct mystruct" 全体が型名なので、個人的にはいちいち _t を付けなくても構造体であることは明示されていると思う。

2. typedef 型

typedef struct {
  int foo;
} mystruct_t;
mystruct_t bar;

いちいちstructを付けるのが面倒くさいときによくやるやり方。
無名の構造体を定義して、それに対して typedef で新しい名前を与えている。
この場合は型名に struct が付かないので、_t を付けるべき。

3. 混合型

typedef struct mystruct {
  int foo;
} mystruct_t;
struct mystruct bar;
mystruct_t buz;

今度は struct mystruct という構造体の定義と、それに対して mystruct_t という別名を付けるのを同時に行っている。
最初に読んだ C の教科書にこう書かれていたためか、昔は盲目的にこう書いていたが、どっちかに統一すればいいだけの話なのであんまり意味ないのよね。

4. トリッキー型

struct {
  int foo;
} bar;

一見 2 と似ているが、typedef の有無で全く意味が異なる。
こちらでは、無名の構造体を定義すると同時に、その型の変数 bar を宣言している。
型名がないので関数の引数にも何にも出来ないが、グローバル変数を構造体に纏めておきたいときや、union や 構造体の中で入れ子にしたいときなど名前を付ける必要の無いときには便利。しかし、typedef の付け忘れと見分けが付きにくいので使いどころに気をつけた方が良い。

5. ものぐさ型

struct mystruct {
  int foo;
} bar;
struct mystruct buz;

3 と 4 に近いが、こちらは struct mystruct という構造体を定義すると同時に、その型の変数 bar を宣言している。
性質の異なる複数のことを同時に行うのは混乱のもとなので個人的にはおすすめしない。
4 と同じく typedef の付け忘れと混同されないように注意が必要というか、typedef を付け忘れたけどよくわからんがとりあえず "struct mystruct" の方が使えるから良いかということで放置されているケースの方が多い気がする。

2015年6月3日水曜日

SystemC について思うこと

最近組み込み系の仕事から離れているせいかあまり聞かなくなった気がするのでもはや流行っていないかもしれないが、一昔前に SystemC というか ESL が結構流行ったときにハードのエンジニアと一緒に SystemC の講習を聞きに行った。

ソフトのエンジニアはどちらかというと利用する側なのであればありがたいぐらいの話だったが、講習が終わった後にハードのエンジニアになんとなく感想を聞いたら、「二重開発(SystemCとRTL)になるからやりたくないなー」という感じであまり乗り気ではないようだった。

そのときはまだペーペーだったので、「ふ~ん、そうですか」ぐらいしか思わなかったが、なにか引っかかるものがあった。その正体に気づいたのが数年前。その頃にはすでに組み込み関係から離れていたので誰にも話すことなく過ぎてしまったが、ちょうどいい場所をみつけたのでここで吐き出させてもらう。

ソフト屋と同じくハード屋も概してコード(ハードの場合はHDL)を書くのが好きだ。仕様検討もそこそこにコーディングに移る人も少なくない。しかしながら、楽しいコーディングもいずれは終わりその後に待ち構えているのは長く苦しい検証作業になる。

自分はハードのエンジニアではないのであまり詳しくはないが、横で見ていたところだと、ハードの検証は単体検証から始まる。単体検証では開発した機能モジュールに対してRTLシミュレーター上で入力のテストセットを入力し、出力データが期待値と一致するかを検証する。

単体検証で問題がなければ結合検証に移行する。ハードの場合は機能モジュール同士が配線により物理的に繋がるので、それに基づいて複数のモジュールを繋げて複数のモジュール全体に対して入力を与えて期待通りの出力を得ることが出来るか検証する。

SoCの開発になってくると、最終的にはシミュレータ上でシステム全体を組み上げてシステムとして想定通りに動くか検証を行って論理設計のフェーズは完了する。

ここでさらっと「期待値と一致する」と書いたが、じゃあどうやって期待値を作るかというと、C なり Perl なりでハードと等価な動作をするツールを作って、その出力と一致するかを見る。期待値不一致が起きたといって調べてみたら期待値生成用のソフトの方がバグってたなんてこともよくある。

システム検証でも CPU だとかメモリだとか外部のUSBデバイスなどを含めた全てをRTLシミュレータで動かすのは骨なので、通常はCPUの代わりにISSを使って、メモリや外部デバイスも仮想的な動作モデルを用意して組み合わせたりして現実的な時間で検証が終わるようにしている。(それでもRTOSのブートに余裕で一晩かかるが)

で、この辺をよくよく見返してみると、期待値生成用のツールとかシステム検証用のISSとか動作モデルって体系化されていないだけで部品部品で見たら ESL と同じじゃん。二重開発はいやだと言いながら、結局検証のフェーズで ESL と同じようなもの作ってるじゃん。ということに気がついた。



じゃあ、ESL をテストセットを生成するものと見なしたときに、開発フローはどうなるんだろうかと考えた。

そうすると、まず最初に作るべきなのはシステム全体の動作モデルになる。これ自体は SystemC でなくとも普通の PC 上のアプリケーションでかまわない。大切なのは、この段階で UI 含めてきちんと動作すること、ソフトならこのまま出荷できるレベルまで作り込みと検証を行い、このときのシステムの入力と出力(例えば UI 上の操作や表示)を記録しておく。


次に、このシステムモデルを幾つかのブロックに分けてもう一段抽象度を落としたモデルを開発する。このあたりから SystemC の出番となる。各ブロックが出来上がったら、全体を組み合わせて検証する。全体に対してシステムモデルと同じ入力を与えて同じ出力が得られれば全体としての動作の検証が出来たことになる。このとき、各ブロックの入出力を記録しておく。


さらに、各ブロックを分割してもう一段抽象度を落としたモデルを開発する。今度は1階層上のブロックの入出力と一致すればよいことになる。


このように、段階的に抽象度を落としながら1階層上の入出力と一致するように作っていって、最終的に RTL のレベルまで落とし込んでいく。

一見、非常にまだるっこしいことをしているように見えるが、結局のところは「抽象機能モデル」→「アンタイムドモデル」→「タイムドモデル」→「RTL」と段階的に落とし込んでいるだけだ。大切なのは、上位階層できちんと検証をして、その入出力を引き継いでいくことで最終的に全体を組み上げたときにシステム全体としての品質が担保できるという点にある。


こうやって淡々と書いていると、自分が不勉強なだけで非常に当たり前のことを書いているような気もしてくる(実際そうかもしれない)。しかしながら、抽象度の高いレイヤーから抽象度の低いレイヤーに入出力を引き継ぐときに、必ずしもそのまま引き継げるわけではなくタイミング等の時間的な曖昧さを厳密にしていく必要がありそうな気がする。

で、これだけ聞くと単に RTL 書いた後に行う検証を先に行うようにしただけで手間暇は変わらないように見えるが、検証の時間のかかり方は大きく変わる。

前にも書いたように RTL でシステム検証を行おうとするとすさまじく時間がかかる。シミュレーターだとまともなアプリの動作はほぼ不可能で、エミュレーターでも大金かけて部分的な動作しか出来ないだろう(最近は技術も進んでいるかもしれないが)。

この方法では RTL のレイヤーで行う検証はこれまでの単体検証か結合検証と同程度のものだ。じゃあ、ここで作った RTL のブロックを組み上げていったときに、もう一度結合検証やシステム検証が必要かというと、接続ミス確認用の疎通テストぐらいはしておいた方がいいかもしれないが、原理的には上位から継承してきた入出力を保証できれば全体組み上げたときに抽象機能モデルと同等の動作をすることが保証できていることになる。



何よりも、先にテストセットを作っておくというのは、ソフト開発で言うところのテストファーストとかテスト駆動開発とかと同じ発想で、その先にあるのはアジャイルだ。

これまでのハードウェア開発は基本的にウォーターフォール型なので、上位の仕様から落とし込んで各ブロックの仕様を決めていくが、上位の仕様が本当に正しいかは組み上げて全体を動かして見ないとわからない。加えて、システム検証は膨大な時間がかかるのでまともなテストは出来ず、結局のところチップが出来上がってから動かして見て、やっぱ想定と違うとなって ES 品の山が積み上がっていくことになる。

このやり方も一見すると抽象度の高いレイヤーから低いレイヤーに降りていくので、ウォーターフォールっぽく見えるが、実際のところ機能要件的なところはスピードが速くて変更が容易な抽象機能モデルで作り込んでしまう。あとは、同じ機能を維持しつつ抽象度を落としていけば等価な動きをする RTL が出来上がるので、チップを作ってからやっぱり違ったということをなくすことが出来るんじゃないかと思うんだがどうなんだろうか。