書くことなんてないけれど、アドベントカレンダー用の記事を書いてたらqiitaの初投稿容量制限に引っかかったのでうんち記事を投げておく。ほぼ自分用。
シフト演算子が絡むときは括弧をつける
いきなりsigned演算と関係がないが、私はシフト演算子の優先順位が覚えられない人である。
たし算とかけ算ならたし算の方が優先される。これはまあわかる。
わからんのはシフト演算子である。こうなっている。
優先順位
高 * / %
+ -
低 << >>
エーッ!? って感じじゃないですか? 違いますか。そうか。
verilogとかを書くとシフト演算子を多用することになるが、もう何度引っかかったかわからないのでシフトが絡む演算は全部括弧を付けて書くようにしてしまった。
unsignedを計算の中に入れる時
unsignedとsignedで計算する時はこう書いてやる必要がある。
reg [8-1:0] a;
reg signed [9-1:0] b;
reg signed [10-1:0] c;
c <= $signed({1'b0, a}) + b;
こうしないとunsignedの計算と解釈されてしまうっぽい。定義されていないwireを使った場合勝手に1ビットのwireとして解釈する件とかもそうだが警告ぐらいしてくれればいいのに。
※追記:
1ビットwireについては、モジュール外で
`default_nettype none
//(モジュールの記述)
`default_nettype wire
のように指定しておくとエラーを出してくれるようです。
計算結果が溢れないように逐一wireでビット幅を指定する
一番大事。特に一行で
reg signed [32-1:0] a;
reg signed [32-1:0] b;
reg signed [32-1:0] c;
a <= (b + c) >>> 1;
のような計算をする場合、たとえ最終的な計算結果は32ビットに収まるとしても、途中のb + c
の計算結果が33ビットに溢れてしまうと計算結果が狂うことがある。というか大抵狂う。
なので、自分はこんな感じで書く。
reg signed [32-1:0] a;
reg signed [32-1:0] b;
reg signed [32-1:0] c;
wire signed [33-1:0] d;
wire assign d = b + c;
a <= d >>> 1;
算術シフトしろ
signedにシフト演算子を適用するときはシフト(>>)ではなく算術シフト(>>>)を使おう。<<<は<<と意味が変わらないのでどっちでもいいが、自分のように頻繁に左右を間違えちゃう場合は<<<で書いておいた方が安心である。
(即値にビット幅を指定しない)
今からすごく適当なことを書く。
signedのレジスタと即値の計算において、例えば a - 63'd6
と書いていたところをa - 63
と書き直したらなぜかうまくいく、というようなことがよくあった。他に正しい指定の仕方があるのかもしれないし、開発環境さんサイドの解釈がどうなるかによると思う。
まとめ
- シフト演算子を扱っててどうも演算結果がおかしいときは演算子の優先順位を疑おう。
- 優先順位が覚えられない人は常に括弧をつけるようにしよう。
- unsignedを計算に入れる時は適宜0ビットを足す。
- 長い計算をやる時は個々の計算結果が最大何ビットになるか考えてwireを使ってビット幅を指定してあげよう。
-
- (即値にはビット幅を指定しないとうまくいくかもよ)
簡潔に書くためにシンプルな例を使ったが、実際にはもっとややこしい計算で発生した事態に対してこれらの手段を講じたので、挙げた例が必ずしも正しいとは限らない。まあ開発環境の解釈と付き合う一助になれば。