2進数のシフトは2nを掛けたり2nで割ったりする演算に相当します。Verilogにはシフト演算子“>>”および“<<”も定義されていますが、ここではシフト演算子を使用せずにコードを書き下すことといたします。
オーバーフローの生じない1ビット左シフトコードの一例を以下に示します。
module unsigned_shift_left_1( input [15:0] in, output [16:0] result); assign result = {in, 1'b0}; endmodule
入出力のビット幅が同じ場合にはオーバーフローが生じる可能性があります。オーバーフローを検出して最大値にクリッピングする1ビット左シフトのコードの一例を以下に示します。
module unsigned_shift_left_1_clip( input [15:0] in, output [15:0] result, output error); wire [16:0] work = {in, 1'b0}; assign error = work[16]; assign result = error ? 16'hFFFF : work[15:0]; endmodule
このモジュールを添付資料に記載したトップモジュールから呼び出す形としたものをAltera社の開発環境Qualtus IIでコンパイルした結果を下の図に示します。
モジュール内での信号遅延を測定するため、モジュールの入り口と出口にレジスタを配置しましたが、遅延時間は7ns以下と短く、入出力ポートの最大周波数250MHzが上限のクロック周波数となりました。
右シフトでは、入出力のビット幅が同じであっても、オーバーフローは発生しません。符号なし整数の定数右シフトコードをあえて書き下せば次のようになります。
module unsigned_shift_right_1( input [15:0] in, output [15:0] result); assign result = {1'b0, in[15:1]}; endmodule
符号なし整数の定数右シフトは、単に“in[15:1]”のように、信号線のLSB側をシフト数に相当するビット幅だけ切り捨てることで実現できます。
任意のビット数だけシフトさせるモジュールは「バレルシフタ」と呼ばれます。以下に、入力信号を0〜15ビットの任意のビット数だけ右シフトしたものを出力するモジュールを示します。
module unsigned_shift_right_variable( input [15:0] in, input [3:0] shift, output [15:0] result); wire [15:0] work0 = shift[0] ? {1'b0, in[15:1]} : in; wire [15:0] work1 = shift[1] ? {2'b0, work0[15:2]} : work0; wire [15:0] work2 = shift[2] ? {4'b0, work1[15:4]} : work1; assign result = shift[3] ? {8'b0, work2[15:8]} : work2; endmodule
符号付整数のシフト演算は、右シフトの際に上位に符号ビットをシフトインするという点が符号なし整数のシフト演算と異なっています。また、オーバーフローの検出とクリッピング処理も、当然ですが、異なる処理となります。クリッピング処理に関してはmodule signed_clipを参照してください。
以下は符号付整数の1ビット右シフト演算モジュールです。同様の演算は、符号なし数の定数右シフト演算と同様に、シフトの数だけ信号線幅を縮小して“in[15:1]”の形で表現することも可能です。
module signed_shift_right_1( input [15:0] in, output [15:0] result); assign result = {in[15], in[15:1]}; endmodule
符号付整数の「バレルシフタ」の例として、入力信号を0〜15ビットの任意のビット数だけ右シフトしたものを出力するモジュールを示します。
module signed_shift_right_variable( input [15:0] in, input [3:0] shift, output [15:0] result); wire [15:0] work0 = shift[0] ? {in[15], in[15:1]} : in; wire [15:0] work1 = shift[1] ? {{2{work0[15]}}, work0[15:2]} : work0; wire [15:0] work2 = shift[2] ? {{4{work1[15]}}, work1[15:4]} : work1; assign result = shift[3] ? {{8{work2[15]}}, work2[15:8]} : work2; endmodule
符号付整数を右シフトさせる「バレルシフタ」を以下に示します。双方を組み合わせればシフト数を符号付整数で与えて、正の場合は左シフト、負の場合は右シフトするようにモジュールを構成することもできます。
module signed_shift_left_variable_clip( input [15:0] in, input [3:0] shift, output [15:0] result, output error); wire [16:0] work0 = shift[0] ? {in, 1'b0} : {1'b0, in}; wire [18:0] work1 = shift[1] ? {work0, 2'b0} : {2'b0, work0}; wire [22:0] work2 = shift[2] ? {work1, 4'b0} : {4'b0, work1}; wire [30:0] work3 = shift[3] ? {work2, 8'b0} : {8'b0, work2}; assign error = ~&work3[30:15] & |work3[30:15]; assign result = error ? (work3[30] ? 16'h8000 : 16'h7FFF) : work3[15:0]; endmodule
このモジュールをコンパイルした結果を下の図に示します。
遅延時間は2.966ns、最大周波数は142.17MHzとなりました。上図よりわかりますように、このモジュールは信号が入力ポートから出力ポートまで到達する間に、シフト数のそれぞれの桁に対応する4つのマルチプレクサとオーバーフローの際に制限値を与える1つのマルチプレクサの、計5段の論理ブロックを順次通過します。このため、それぞれの論理ブロックの遅延時間が足し合わされて全体の遅延時間を大きなものとしています。
乗算は論理回路を書き下すことによっても構成可能ですが、多くのFPGAには通常の論理ブロックのほかに乗算専用の論理ブロック(乗算器)が備わっており、通常はこれを利用して乗算を行います。
乗算器の構成はFPGAごとによって異なり、標準的な利用方法はありません。そこで、通常はFPGAベンダが供給する乗算モジュール(IP)を利用します。
乗数の一方が定数である場合は、シフトと加算を組み合わせて定数乗算器を構成します。定数乗算器の一例として、符号なし整数を5倍(3'b101倍)するモジュールを以下に示します。
module unsigned_mul_5( input [15:0] in, output [17:0] result); assign result = {in, 2'b0} + {2'b0, in}; endmodule
上記モジュールは、入力とこれを4倍したものを加算することで5倍を作っています。5倍された値を格納するためには、最大で入力のビット幅よりも3だけ長いビット長が必要となります。上のモジュールでは、オーバーフローの発生を防止するため、出力信号のビット幅は入力信号のビット幅よりも3だけ長くしています。加算演算は足される数よりも1だけ長いビット長を出力することに注意してください。
シフト演算と定数乗算に関するVerilog-2001のソースコードをこのファイルに置きました。
一般的な定数乗算をもっとも単純な加減算とシフト演算の組合せで構成する方法を見出すという問題は、NP完全問題と呼ばれる、解を見出すには非常に時間がかかる問題として知られています。厳密な意味での最適解を得ることをあきらめれば、実用的な時間で定数乗算をある程度簡素化することができます。このページに置きました「定数乗算最適化テストプログラム」はこのような試みの一例です。ご興味がありましたらダウンロードして走らせてみてください。