前回の続き。
さすがに総当たりだと単純すぎるので、いわゆる枝刈りして余計な試行を削っていく。
var colors = ['red', 'blue', 'green', 'yellow'];
colors.forEach(function(tile1) {
colors.forEach(function(tile2) {
if(tile1 != tile2) {
colors.forEach(function(tile3) {
if(tile1 != tile3 && tile2 != tile3) {
colors.forEach(function(tile4) {
if(tile1 != tile4 && tile3 != tile4) {
colors.forEach(function(tile5) {
if(tile1 != tile5 && tile4 != tile5 && tile2 != tile5) {
document.write(tile1+', '
+tile2+', '
+tile3+', '
+tile4+', '
+tile5+'<br>');
}
});
}
});
}
});
}
});
});
たしかにこれで速くはなるのだが、いかんせんメンテナンス性がすこぶる悪い。
タイルを一枚増やしたり、条件を変更しようとするとあっちゃこっちゃ変えなくちゃならない。
2015年7月16日木曜日
Javascript で塗り分け問題を解いてみる (1)
以前なんかで見かけて、暇があったら勉強がてら JavaScript で書いてみようと思っていたプログラム。
いわゆる塗り分け問題で、細かいところは覚えていないが、とりあえず適当に下のような5枚のタイルを同じ色が隣接しないように赤青黄緑の4色で塗り分ける問題を解いてみる。
まずは何も考えずに愚直に書くとこうなる
var colors = ['red', 'blue', 'green', 'yellow'];
var i, j, k, l, m;
var tile1, tile2, tile3, tile4, tile5;
for(i = 0; i < colors.length; i++) {
tile1 = colors[i];
for(j = 0; j < colors.length; j++) {
tile2 = colors[j];
for(k = 0; k < colors.length; k++) {
tile3 = colors[k];
for(l = 0; l < colors.length; l++) {
tile4 = colors[l];
for(m = 0; m < colors.length; m++) {
tile5 = colors[m];
if(tile1 != tile2
&& tile1 != tile3
&& tile1 != tile4
&& tile1 != tile5
&& tile2 != tile3
&& tile3 != tile5
&& tile5 != tile4
&& tile4 != tile2) {
document.write(tile1+', '
+tile2+', '
+tile3+', '
+tile4+', '
+tile5+'<br>');
}
}
}
}
}
}
これでも解けることは解けるが、タイル数(N)が増えると試行回数が4(色数)のN乗で増えていくので、こんなんを大学の専攻のレポートで提出したら確実に赤点食らうだろう。
蛇足だが、forEachを使うと若干シンプルに書ける。
var colors = ['red', 'blue', 'green', 'yellow'];
var begin = Date.now();
colors.forEach(function(tile1) {
colors.forEach(function(tile2) {
colors.forEach(function(tile3) {
colors.forEach(function(tile4) {
colors.forEach(function(tile5) {
if(tile1 != tile2
&& tile1 != tile3
&& tile1 != tile4
&& tile1 != tile5
&& tile2 != tile3
&& tile3 != tile5
&& tile5 != tile4
&& tile4 != tile2) {
document.write(tile1+', '
+tile2+', '
+tile3+', '
+tile4+', '
+tile5+'<br>');
}
});
});
});
});
});
もっとも、シンプルに書けるだけで中身は変わらないので、まともにしようと思ったら工夫が必要になる。
いわゆる塗り分け問題で、細かいところは覚えていないが、とりあえず適当に下のような5枚のタイルを同じ色が隣接しないように赤青黄緑の4色で塗り分ける問題を解いてみる。
まずは何も考えずに愚直に書くとこうなる
var colors = ['red', 'blue', 'green', 'yellow'];
var i, j, k, l, m;
var tile1, tile2, tile3, tile4, tile5;
for(i = 0; i < colors.length; i++) {
tile1 = colors[i];
for(j = 0; j < colors.length; j++) {
tile2 = colors[j];
for(k = 0; k < colors.length; k++) {
tile3 = colors[k];
for(l = 0; l < colors.length; l++) {
tile4 = colors[l];
for(m = 0; m < colors.length; m++) {
tile5 = colors[m];
if(tile1 != tile2
&& tile1 != tile3
&& tile1 != tile4
&& tile1 != tile5
&& tile2 != tile3
&& tile3 != tile5
&& tile5 != tile4
&& tile4 != tile2) {
document.write(tile1+', '
+tile2+', '
+tile3+', '
+tile4+', '
+tile5+'<br>');
}
}
}
}
}
}
これでも解けることは解けるが、タイル数(N)が増えると試行回数が4(色数)のN乗で増えていくので、こんなんを大学の専攻のレポートで提出したら確実に赤点食らうだろう。
蛇足だが、forEachを使うと若干シンプルに書ける。
var colors = ['red', 'blue', 'green', 'yellow'];
var begin = Date.now();
colors.forEach(function(tile1) {
colors.forEach(function(tile2) {
colors.forEach(function(tile3) {
colors.forEach(function(tile4) {
colors.forEach(function(tile5) {
if(tile1 != tile2
&& tile1 != tile3
&& tile1 != tile4
&& tile1 != tile5
&& tile2 != tile3
&& tile3 != tile5
&& tile5 != tile4
&& tile4 != tile2) {
document.write(tile1+', '
+tile2+', '
+tile3+', '
+tile4+', '
+tile5+'<br>');
}
});
});
});
});
});
もっとも、シンプルに書けるだけで中身は変わらないので、まともにしようと思ったら工夫が必要になる。
2015年7月8日水曜日
GUI と DVFS
最近のプロセッサなら大抵は動作電圧と動作周波数を変更する機能が付いている。
OS は負荷が低いときは CPU の電圧と周波数を落とすことで消費電力を抑えることが出来る。
ってのが教科書的な DVFS の概略。
スマートフォンの場合は、何もせずにホーム画面を表示しているだけだと裏で何か処理をしていない限り基本的には何も処理していないので、負荷が一番低い=動作周波数が一番低い状態にある。
この状態で指で画面を横にスワイプすると、スワイプのアニメーション処理が走るため一気に負荷が上昇する。
最近の周波数マネージャがどうなっているか知らないが、一昔前の OS の周波数制御は非常に単純だったので、負荷が閾値を超えたら動作周波数と電圧を一段階上げて、それでもまだ負荷が高ければもう一段階あげて・・・とやっていくため、周波数が上がりきるまでに時間がかかっていた。
といっても影響があるのは最初の1~2フレーム程度なのだが、これが触ったときのちょっとした引っかかりの一因になる。
しかも、皮肉なことに周波数変更の刻みが細かい高価なプロセッサほどこれによる遅延が大きく、逆に中華製の安価なプロセッサの方が変更回数が少ない分、周波数の上がりが早くて、うまくはまると中華製の安価タブレットの方が下手な国産高級タブよりもタッチの反応が良かったりするケースもあった。
もともと GUI のような応答性重視で短時間で終わるような処理ってのは DVFS との相性は良くない。
とはいえ、制御工学で言うところのインパルス応答性の改善ってのと根っこは同じでやりようはあると思うので、最近の OS ならだいぶマシになっているはず、だと思う、と信じたい。
(個人的には本気でレスポンスを良くしたければ周波数を細かく設定できるようにするよりも、周波数は2段階だけで、その代わり周波数の切り替えを早くして、短時間でもやることが無ければ最低周波数、ちょっとでも処理するときは最大周波数と言った具合に on/off をこまめに切り替えられる方に注力した方が良いと思ってるんだが)
で、最近オクタコアなる CPUコア を8個も搭載したあほみたいなプロセッサが出てきた。といっても8個同時に使えるわけでは無く、いわゆる Big-Little とかいう技術で内部的には4コアの低速なプロセッサと4コアの高速なプロセッサが入っていて、軽い処理なら低速だが消費電力の少ない方のプロセッサで、重い処理の場合は消費電力は大きいが高速なプロセッサでといった具合に状況に応じて適した方を切り替えて使うそうだ。
さて、これで GUI 処理を行ったらどうなるだろうか。
先ほどのようにただ画面を表示しているだけの状態ならおそらく低速なプロセッサを最低周波数で動かすようにするのが普通だ。そこからスワイプにより一気に負荷が上昇すると、昔のアホな周波数制御なら一段階ずつ周波数を上げていくことになる。所詮GUIなんてたいしたことはやらないので、低速なプロセッサで最大値まで周波数を上げたところで負荷が落ち着いてくれれば良いが、まだ負荷が高ければ今度は高速な方のプロセッサに切り替わる羽目になる。メーカー曰くプロセッサの切り替えは高速に出来るそうだが、普通に考えるとそれでも周波数を切り替えるよりは遙かに時間がかかるだろうから、こんなことをやられると GUI の応答性能的にはグッダグダになることが容易に予想できる。
いくらでも回避方法はあると思うが、この手のやつは原因を見極めずに小手先の対応を積み重ねていくとどんどんドツボにはまってくで、その辺最近のはどうなってんだろうかと、最近出たオクタコア搭載スマフォがいまいちだという話を聞いてちょっと気になった。
OS は負荷が低いときは CPU の電圧と周波数を落とすことで消費電力を抑えることが出来る。
ってのが教科書的な DVFS の概略。
スマートフォンの場合は、何もせずにホーム画面を表示しているだけだと裏で何か処理をしていない限り基本的には何も処理していないので、負荷が一番低い=動作周波数が一番低い状態にある。
この状態で指で画面を横にスワイプすると、スワイプのアニメーション処理が走るため一気に負荷が上昇する。
最近の周波数マネージャがどうなっているか知らないが、一昔前の OS の周波数制御は非常に単純だったので、負荷が閾値を超えたら動作周波数と電圧を一段階上げて、それでもまだ負荷が高ければもう一段階あげて・・・とやっていくため、周波数が上がりきるまでに時間がかかっていた。
といっても影響があるのは最初の1~2フレーム程度なのだが、これが触ったときのちょっとした引っかかりの一因になる。
しかも、皮肉なことに周波数変更の刻みが細かい高価なプロセッサほどこれによる遅延が大きく、逆に中華製の安価なプロセッサの方が変更回数が少ない分、周波数の上がりが早くて、うまくはまると中華製の安価タブレットの方が下手な国産高級タブよりもタッチの反応が良かったりするケースもあった。
もともと GUI のような応答性重視で短時間で終わるような処理ってのは DVFS との相性は良くない。
とはいえ、制御工学で言うところのインパルス応答性の改善ってのと根っこは同じでやりようはあると思うので、最近の OS ならだいぶマシになっているはず、だと思う、と信じたい。
(個人的には本気でレスポンスを良くしたければ周波数を細かく設定できるようにするよりも、周波数は2段階だけで、その代わり周波数の切り替えを早くして、短時間でもやることが無ければ最低周波数、ちょっとでも処理するときは最大周波数と言った具合に on/off をこまめに切り替えられる方に注力した方が良いと思ってるんだが)
で、最近オクタコアなる CPUコア を8個も搭載したあほみたいなプロセッサが出てきた。といっても8個同時に使えるわけでは無く、いわゆる Big-Little とかいう技術で内部的には4コアの低速なプロセッサと4コアの高速なプロセッサが入っていて、軽い処理なら低速だが消費電力の少ない方のプロセッサで、重い処理の場合は消費電力は大きいが高速なプロセッサでといった具合に状況に応じて適した方を切り替えて使うそうだ。
さて、これで GUI 処理を行ったらどうなるだろうか。
先ほどのようにただ画面を表示しているだけの状態ならおそらく低速なプロセッサを最低周波数で動かすようにするのが普通だ。そこからスワイプにより一気に負荷が上昇すると、昔のアホな周波数制御なら一段階ずつ周波数を上げていくことになる。所詮GUIなんてたいしたことはやらないので、低速なプロセッサで最大値まで周波数を上げたところで負荷が落ち着いてくれれば良いが、まだ負荷が高ければ今度は高速な方のプロセッサに切り替わる羽目になる。メーカー曰くプロセッサの切り替えは高速に出来るそうだが、普通に考えるとそれでも周波数を切り替えるよりは遙かに時間がかかるだろうから、こんなことをやられると GUI の応答性能的にはグッダグダになることが容易に予想できる。
いくらでも回避方法はあると思うが、この手のやつは原因を見極めずに小手先の対応を積み重ねていくとどんどんドツボにはまってくで、その辺最近のはどうなってんだろうかと、最近出たオクタコア搭載スマフォがいまいちだという話を聞いてちょっと気になった。
2015年7月7日火曜日
Atom の remote-ftp が動かない
最近 Atom ってエディタが流行ってるので、職場でも Sublime から乗り換えてみた。
Sublime の時に重宝していた SFTP と同等のことが remote-ftp で出来るって言うんで早速導入してみたが、"Error: Timed out while waiting for handshake" ってエラーが出て動かない。
ググってもなかなか情報が無いのでソースコード見たりサーバーのログ見たりと色々と試行錯誤した結果、コンフィグファイルの connTimeout を 99999 に設定したら無事に繋がった。
どうも接続タイムアウトのデフォルト(10000、10秒?)が短すぎたらしいのだが、それなら一言 "Connection timeout" って言ってくれればすぐにわかったんだがなー
(とはいえ、このメッセージ出してるのはSSHのライブラリの方なので remote-ftp の作者さんに非は無いのかもしれんが)
ただまあ、社内ネットで接続に10秒以上かかるってのも変な話だし、サーバー側のログ見るとSSHの認証は通ってるっぽいし、普通にSSHでログインしている感覚からするとあれに10秒もかかっているように思えんし、なんかどっかで変なバグでも踏んでんのかな
Sublime の時に重宝していた SFTP と同等のことが remote-ftp で出来るって言うんで早速導入してみたが、"Error: Timed out while waiting for handshake" ってエラーが出て動かない。
ググってもなかなか情報が無いのでソースコード見たりサーバーのログ見たりと色々と試行錯誤した結果、コンフィグファイルの connTimeout を 99999 に設定したら無事に繋がった。
どうも接続タイムアウトのデフォルト(10000、10秒?)が短すぎたらしいのだが、それなら一言 "Connection timeout" って言ってくれればすぐにわかったんだがなー
(とはいえ、このメッセージ出してるのはSSHのライブラリの方なので remote-ftp の作者さんに非は無いのかもしれんが)
ただまあ、社内ネットで接続に10秒以上かかるってのも変な話だし、サーバー側のログ見るとSSHの認証は通ってるっぽいし、普通にSSHでログインしている感覚からするとあれに10秒もかかっているように思えんし、なんかどっかで変なバグでも踏んでんのかな
2015年7月1日水曜日
Mongoose の autoreconnect が動かない
やったことのメモ
Mongoose には自動再接続の機能があるらしいのだが思ったように動かない。
とりあえず connection の connected, disconnected, reconnected イベントを拾ってログを出すようにしてみたら、1回目の再接続時はきちんと disconnected → connected のイベントが出ていたが、2回目以降は disconnected は発生せずに reconnected だけ発生していた。
さらにややこしいのが一度でもデータベースに save をするとどのイベントも発生しなくなる(厳密には拾い方が変わるのかもしれんが)。readyState も変わらないのでどうしようも無い。
こちらのイベント処理が悪いのかと思って、autoreconnect を信じて全てのイベント処理を外してみても駄目。
バグなのか仕様なのか自分の使い方が悪いのかはわからんが、色々試行錯誤した結果以下のように save のコールバックで失敗した書き込みをキューイングして再接続させるようにしたらうまく動いた。
Mongoose には自動再接続の機能があるらしいのだが思ったように動かない。
とりあえず connection の connected, disconnected, reconnected イベントを拾ってログを出すようにしてみたら、1回目の再接続時はきちんと disconnected → connected のイベントが出ていたが、2回目以降は disconnected は発生せずに reconnected だけ発生していた。
さらにややこしいのが一度でもデータベースに save をするとどのイベントも発生しなくなる(厳密には拾い方が変わるのかもしれんが)。readyState も変わらないのでどうしようも無い。
こちらのイベント処理が悪いのかと思って、autoreconnect を信じて全てのイベント処理を外してみても駄目。
バグなのか仕様なのか自分の使い方が悪いのかはわからんが、色々試行錯誤した結果以下のように save のコールバックで失敗した書き込みをキューイングして再接続させるようにしたらうまく動いた。
mongoose.connect(dbURI);
// 送信失敗したデータを溜めるためのキュー
var instQueue = [];
var connecting = false;
function saveInst(inst) {
inst.save( function(err) {
if(err) {
console.log('save failed');
instQueue.push(inst);
if(connecting) {
connecting = true;
mongoose.connect(dbURI, function(err) {
connecting = false; // 接続に成功したら溜まっているキューを save
if(!err && instQueue.length > 0) {
saveInst(instQueue.shift());
}
});
}
} else {
console.log('saved'); // キューが溜まっていれば残りも save
if(instQueue.length > 0) {
saveInst(instQueue.shift());
}
}
}
connect 中にもう一回 connect するとエラーになるので connecting フラグを使って多重コネクトを行わないように管理していたが、自前でフラグを作らずに readyState を見ても良かったかもしれない。
今回は定期的に吐かれるログを保存するためのスクリプトだったので接続に失敗してもキューに突っ込んでおいて次回リトライできるが、そうでない場合は setTimeout 等を使って手動で connection を何度もリトライする必要が出てくる。
でもそれホントは autoreconnect の仕組みがやってくれるはずなんだよね・・・
2015年6月4日木曜日
struct 五段活用
久々に C 書くと毎度ごっちゃになるので整理しておく。
int foo;
};
struct mystruct bar;
おそらく一番オーソドックスな使い方。
構造体だからと言って盲目的に mystruct_t と _t を付ける人もいるが、"struct mystruct" 全体が型名なので、個人的にはいちいち _t を付けなくても構造体であることは明示されていると思う。
int foo;
} mystruct_t;
mystruct_t bar;
いちいちstructを付けるのが面倒くさいときによくやるやり方。
無名の構造体を定義して、それに対して typedef で新しい名前を与えている。
この場合は型名に struct が付かないので、_t を付けるべき。
int foo;
} mystruct_t;
struct mystruct bar;
mystruct_t buz;
今度は struct mystruct という構造体の定義と、それに対して mystruct_t という別名を付けるのを同時に行っている。
最初に読んだ C の教科書にこう書かれていたためか、昔は盲目的にこう書いていたが、どっちかに統一すればいいだけの話なのであんまり意味ないのよね。
int foo;
} bar;
一見 2 と似ているが、typedef の有無で全く意味が異なる。
こちらでは、無名の構造体を定義すると同時に、その型の変数 bar を宣言している。
型名がないので関数の引数にも何にも出来ないが、グローバル変数を構造体に纏めておきたいときや、union や 構造体の中で入れ子にしたいときなど名前を付ける必要の無いときには便利。しかし、typedef の付け忘れと見分けが付きにくいので使いどころに気をつけた方が良い。
int foo;
} bar;
struct mystruct buz;
3 と 4 に近いが、こちらは struct mystruct という構造体を定義すると同時に、その型の変数 bar を宣言している。
性質の異なる複数のことを同時に行うのは混乱のもとなので個人的にはおすすめしない。
4 と同じく typedef の付け忘れと混同されないように注意が必要というか、typedef を付け忘れたけどよくわからんがとりあえず "struct mystruct" の方が使えるから良いかということで放置されているケースの方が多い気がする。
基礎知識
変数の宣言
型 変数名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 が出来上がるので、チップを作ってからやっぱり違ったということをなくすことが出来るんじゃないかと思うんだがどうなんだろうか。
ソフトのエンジニアはどちらかというと利用する側なのであればありがたいぐらいの話だったが、講習が終わった後にハードのエンジニアになんとなく感想を聞いたら、「二重開発(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 が出来上がるので、チップを作ってからやっぱり違ったということをなくすことが出来るんじゃないかと思うんだがどうなんだろうか。
登録:
投稿 (Atom)






