Verilogのすごく細かいバッドノウハウ的なお話

この記事はHDL Advent Calendar 2013の参加記事というか、途切れてしまうと寂しいので間をつなぐためにお茶を濁してみました的な記事です。

間違い探し

0から15まで数える16進カウンタが必要なので、とりあえず何も考えないでこんな RTL を書いたとします。

  logic [3:0] count;

  always_ff @(posedge clk or negedge rst_b)
    if (~rst_b)
      count <= 4'd0;
    else
      count <= count + 1'b1;

いや、加算器の両側は、ビット幅そろえて書くだろ。

      count <= count + 4'd1;

と思ったあなた。あなたはこの先の罠にははまらないので、もう読まなくても大丈夫です。でも、せっかくなので読んでおくと、今後、いらんデバッグで時間をつぶさずにすむ可能性がちびっとだけ増えるかもしれません。

さて、ここまで書いたところで、カウンタ値は符号付の値と演算するのでカウンタ値も符号付にしなきゃいけない、と後から分かったとします。*1 修正しましょう。宣言だけでなく、リテラルも書き換えないといけませんね。

  logic signed [3:0] count;

  always_ff @(posedge clk or negedge rst_b)
    if (~rst_b)
      count <= 4'sd0;
    else
      count <= count + 1'sb1;

(わざとらしく) おっと! 符号ビットの分、ビット幅を増やすのを忘れていました。そうするとオーバーフローにも対処しないといけませんね!

  logic signed [4:0] count;

  always_ff @(posedge clk or negedge rst_b)
    if (~rst_b)
      count <= 5'sd0;
    else if (count == 5'sd15)
      count <= 5'sd0;
    else
      count <= count + 1'sb1;

さて、いちおうシミュレーションして動作を確認してみましょう。

……あれ?

間違い探しの答え

えー、まー、ここまで読んでいただいた方には、もうほとんど分かってると思うんですけど、私はこれでハマって、すくなくとも数時間は浪費したことがあります。ばかと呼んでください。

      count <= count + 1'sb1;

ここの 1'sb1 が問題なんですね。インクリメントしてほしいところがなぜかデクリメントされてる。はて、考えてみると符号付1ビット幅の数とはなんぞや。符号ビットしかないじゃないか、と。

3ビット幅あたりから考えてみましょうか。符号付3ビット幅の数の取りうる値の範囲は[-4,3]。2の補数表現を全部並べると[100,101,110,111,000,001,010,011]。同様に2ビットでは[-2,1]、[10,11,00,01]。*2

というわけで、1ビットでは[-1,0]ということになりますね。1'sb1 とは -1 のことなんです。count + 1'sb1 とは count + (-1) のことなんです! こんなん絶対だまされるわ!

回避方法

は、すでに書きましたね。加算器の両側のビット幅はそろえましょう。

うぅ、なんか当時を思い出して力がぬけてきた。

補足

このシチュエーションなら、カウンタは unsigned のままで $signed({1'b0, count}) とかってしたほうがよさげですね。

なお、この記事に限らず、私個人の見解は所属組織の見解とは関係ございませんので、あしからずご承知お願いいたします。

*1:Verilog がこんないいかげんな文法なのに Spyglass であんなにうるさくチェックするの、徒労になってる面が多々あると思うんだ……

*2: (2013-12-20修正) 恥ずかしいことに2の補数表現を間違えていたのを修正しました。