大原則は、すべてのオペランドが符号付き(signed)でなければならない、ということです。
Verilog は、符号なし数(unsigned)に強く偏っているように思えます。次に挙げるものは、どれも 符号なし値を生成します。
- 2 つのオペランドに対する演算は、両方のオペランドが符号付きでない限り、符号なし値を返します。
- 基数を明示した数値(例:12'd10)は、明示的な s 修飾子が使われない限り符号なしです。
- ビット選択(bit-select)の結果も符号なしです。
- パート選択(part-select)の結果も符号なしです。
- 連結(concatenation)の結果も符号なしです。
つまり、結局のところ、$signed システム関数を使うか、符号付き wire と符号付きレジスタ(reg)を定義するかのどちらかです。
たとえば、符号付きレジスタと符号なしレジスタを乗算して、その結果を(当然のことながら)符号付き値として扱いたい場合は、次のようにします。
reg [15:0] a; // Unsigned
reg signed [15:0] b;
wire signed [16:0] signed_a;
wire signed [31:0] a_mult_b;
assign signed_a = a; // Convert to signed
assign a_mult_b = signed_a * b
ここで、signed_a のビット幅は a より 1 ビット広いことに注意してください。そのため、符号ビット用の場所があり、その符号ビットは常に 0 です。この追加ビットがなかったとすると、a の MSb(most significant bit=最上位ビット)が signed_a の符号ビットとして扱われていたでしょう。
ここで、signed_a の MSb を、単に a を使うのではなく、{1'b0, a} のようにして明示的に指定しなければならないと誤解してしまうかもしれません。しかし、Verilog 標準規格は、その数値が符号付きであるか符号なしであるかを決定するのは式だけであると明確に述べています。したがって、左辺はこの判定には影響しません。つまり、a は符号なし値として扱われ、ゼロ拡張されるのです。