自部署の業務がなくなってきててそろそろ部署が潰されそうな感じ。
なので、社内派遣業と化して、とある部署にてFPGA開発のヘルプの業務やってます。
で、そこのFPGAのバグ修正に伴う一連の修正作業をお願いされているんですが、まずろくな仕様書がないという状況にびっくり。
レジスタマップしかないドキュメント。このFPGAって何をするものなの?っていうことすら部外者にはわかりません。ある意味最強のセキュリティかも。
Verilog記述もひどいひどい。
一番最初につくったのが10年くらい前でその人は辞めていていないってのはよくある話。
その後にいろいろな人がちょこちょこと修正しており、Verison38っていう恐ろしいツギハギだらけ。
なので全体を把握している人がいない。
これで10年も世の中に出ている機器で動いているそうです。
まぁちょこちょことバグが見つかるんでVersion38っていうことになってるんですけどね。
それをまだ改変しろと。
バージョンレジスタは8bitあるんでまだまだダイジョブっすw
検証はテストベンチが無い(ってのもすごいんですが…)ので実機確認です。
今後この部署には近づきませんって心に誓って、エラーが出る最低限のソース修正。
IP(回路の方です)として使ってるファイルが、IP(ネットのほうです)直打ちで誰かのPCの共有ディレクトリ先のファイルをincludeしてたり。
そのIP先って誰?辞めてます。とか。
ブロッキング代入とノンブロッキング代入が混在してるんだけど、これいいの?(いいわけない)
a = b + 1; ってパッと見おかしく見えないかもしれませんが、それぞれのビット幅をつけてみると
a[6:0] = b[9:0] + 1; ってなって、ビット幅違うんだけどこれいいの?とか。
合成ツールでのWarningは山ほどでます。
驚いたことに上記のブロッキングとノンブロッキングが混在しててもWarningだけでエラーにはならないんですね。びっくりです。
あまり詳しく書くと特定されちゃいかねませんのでここらへんで辞めておきますが、ここまで酷いのは初めてみました。
なんかFPGAって軽い存在に見られてるようですね。バグでりゃすぐ修正できるしって感じで。
これでも動いているんだからいいじゃん。
でも、ちょこちょことバグが見つかるんで今回みたいな改変があるんですけどね。
で、その中の一コマ。
上記内容に比べたら可愛いものですが、トラブりやすいので自部署の新人研修でも話している内容になります。
case文のプラグマです。
コンパイルオプションというのかもしれません。
昔ASIC開発時代にDesignCompiler使ってたときはプラグマって言ってました。
他の会社や部署がどうなのかわかりませんが、最近ではシミュレーションとの振る舞いの違いが出るんでなるべく使わないように。使うのならちゃんと理解した上で使ってっていうスタイルで行っています。
ですが今のヘルプ業務のFPGAではちょこちょこと使われていまして、ちょっと話題になりました。
その中の一つ。合成オプションの中でよく見かけるparallel_caseです。
実際に簡単な回路を用いて説明してみました。
こういった入出力の定義です。
便宜上functionで記述してますが、まぁこういう記載です。
※①~⑤は後の説明で使います。
③no_parallel3
function [2:0] no_parallel3 ( input [2:0] SEL );
begin
no_parallel3[2] = SEL[2] ? 1'b1 : 1'b0;
no_parallel3[1] = SEL[1] ? 1'b1 : 1'b0;
no_parallel3[0] = SEL[0] ? 1'b1 : 1'b0;
end
endfunction // no_parallel3
入力の各ビットに対応したビットを立てて出力。
基本的にはSELワンホットな使われ方をすることが多いかと思います。
このような記述の方法は色々とあり、今回は上記を含めて5種類挙げてみます。
casezを用いて記述します。
①no_parallel1
function [2:0] no_parallel1( input [2:0] SEL );
begin
no_parallel1[2:0] = 3'b000;
casez( SEL[2:0] )
3'b1xx: no_parallel1[2] = 1'b1;
3'bx1x: no_parallel1[1] = 1'b1;
3'bxx1: no_parallel1[0] = 1'b1;
endcase // casez ( SEL[2:0] )
end
endfunction // no_parallel
同じcaseでも違った見方での書き方もあります。
②no_parallel2
function [2:0] no_parallel2 ( input [2:0] SEL );
begin
no_parallel2[2:0] = 3'b000;
case( 1'b1 )
SEL[2]: no_parallel2[2] = 1'b1;
SEL[1]: no_parallel2[1] = 1'b1;
SEL[0]: no_parallel2[0] = 1'b1;
endcase // case ( SEL[2:0] )
end
endfunction // no_parallel2
上記のそのそれぞれのcase文にparallel_caseというオプションをつけた場合の2種類がこちら。
④parallel1 ※①のcase文に対してオプションを付加
casez( SEL[2:0] ) // synthesis parallel_case
case( 1'b1 ) // synthesis parallel_case
parallel_case をつけると、そのcase分は並列で回路が生成されます。
つまりcase文の選択肢のいずれかにしか該当することがないという時(ワンホット)を前提で使われます。
3’b1xx: no_parallel1[2] = 1’b1;
3’bx1x: no_parallel1[1] = 1’b1;
3’bxx1: no_parallel1[0] = 1’b1;
こういう記述の時には、入力は100/010/001のいずれかにしか該当しませんってことです。
これはVerilogの仕様ではなく、合成ツールへの指示になりますので、ワンホット以外のときにはシミュレーションにおいては違った結果となってしまいます。
上記の場合は、Verilog的には上から優先順位がありますからね。
そこで、上記の記述について実際にシミュレーションと回路はどうなるのか調べてみました。
実際に合成させてみます。
自宅は今はQuartusメインなのでQuartus使ってます。
parallel_caseをつけた場合(④⑤)は、どちらの書き方も同じ回路となりました。
ワンホットゆえの結果となってます。
①の合計結果も上と同じ。
ただ、parallel_caseとつけなかった場合(②③)は、それぞれ生成される回路が変わってきます。
優先順位が生きてきますからね。
実際の挙動をシミュレーションでみてみましょう。
まずはRTLシミュレーション。
case文記述では優先順位がありますので、どれか1bitしか立たない結果となります。
合成オプションは無視されますので、①と④、②と⑤は全く同じになりますし、①と②も同じ。
すなわち③以外の①②④⑤は皆同じ結果となります。
次にGateシミュレーション
Gateすなわち合成結果にもとづいた回路でのシミュレーションなので合成オプションが効いているってことになります。
なので、RTLシミュレーションでは同じだった①と④、②と⑤が違う結果となり、③と同じになります。
これがparallel_caseの機能です。
入力がワンホットの領域で使うんであれば出力は1つしか立たないけど、そうでない入力があった際にはRTL(シミュレーション)と違った結果になるわけです。
オプションがあるcaseと無いcaseがありますけど、これを理解して使ってるんですか?
って聞くと、さぁ。以前からそうなってるんで。
っていうか自分が書いたソースじゃないんで。。。
まぁろくなテストベンチすら無いのでそういう指摘すら俺の知ったことじゃないぞ状態なんでしょうけどね。
今動いているんだから余計なことはしないで
言われたことだけやってくれればいいんだよ(意訳)
っていう依頼主の責任者が言ってるんで従いますが、本当にこんなFPGA世の中にだしていいの?
知らんよわし。
でも最終更新者がわしになってしまうので、今後のトラブルがあった際にわしに問い合わせがくるんじゃないの?
その時は上司はもう違う部署に行ってて誰もこのことは知らないってなりそうだなぁ。
はっ!今聞いている人もそういう状態の人なのかな。
コメント