ここで解説するのは、符号なし16 bit整数どうしの除算モジュールです。入力ポートのnumerator_pには被除数(分子)、denominator_pに除数(分母)を接続いたしますと、出力ポートのquotient_pに商が、surplus_pに余りが出力されます。
遅れを評価するため、トップモジュール“div_u16_nl”はクロック信号(clock)に同期して、入力信号を一旦レジスタに格納してその出力を除算モジュール“unsigned_div16”に送り、モジュールの出力も一旦レジスタに受けた後にポートから出力するようにしています。
クロックに同期した論理をVerilogで記述する方法について、少し後に出てきます除算のソースコードを例として、簡単に解説しておきます。
ポート定義部分の“output reg [15:0] surplus_p”およびその後の“reg [15:0] num, den;”の部分は右側に書かれた名前をレジスタであると定義しています。式の中にレジスタ名が現れると、レジスタの出力を参照します。
レジスタへの値のセットはクロックに同期して行います。これを記述しているのが“always @(posedge clock)”という部分で、“clock”という名の信号線の立ち上がり(posedge)に同期して以下の文を実行することを指定しています。
これに続く“bigin”は5行先の“end”と対となっており、Cの“{……}”と同様、その間の文を一まとめにして扱います。
always文の内部ではクロックの立ち上がりに同期してレジスタへの値の設定が行われます。この場合は“num <= numerator_p;”のように、“=”を用いるのではなく、“<=”を用いて代入演算を記述します。
always文の内部でレジスタが参照された場合、値が変化する以前のレジスタの値が参照されます。たとえば以下ように書くと、クロックの立ち上がりごとにレジスタaとレジスタbの内容が交換されます。
always@(posedge clock) begin a <= b; b <= a; end
FPGAに組み込まれる大規模な論理モジュールは、一般的に“RTL”と呼ばれる形式で記述されます。これは、レジスタとレジスタを論理回路で接続したもので、それぞれのレジスタには同一のクロック信号が供給されます。レジスタ−レジスタ間の論理回路の遅延時間が使用できるクロック周波数の上限を定めます。
レジスタ−レジスタ間に置かれた論理回路の遅延時間は、HDLの開発環境が自動的に計算します。今回は、この機能を用いて論理の性能評価をしようと考え、除算回路をレジスタの間に配置してテストを行いました。
以下に16ビット符号なし整数の除算論理を示します。モジュール名につけております“nl”は“no latency”の略で、演算処理の途中にはレジスタが置かれておらず、入力ポートの信号が確定したら、論理内部の遅延時間後には出力が確定することを意味します。
Verilogのコードをご紹介する前に、全体像を把握しやすいように回路図を示しておきましょう。
まず、トップモジュールは次のようになります。
16ビット除算モジュールの内部を以下に示します。
16ビット除算モジュールは8桁の除算を行うモジュール二つから構成され、同様に8桁の除算モジュールは4桁の除算モジュール二つ、4桁の除算モジュールは2桁の除算モジュール二つ、2桁の除算モジュールは1桁の除算モジュール二つから構成されます。
1桁の除算モジュールは以下のようになっています。
図の左側は少々読みにくいのですが、要は加算器を用いて減算を行い、引けたか引けなかったかによって、減算結果か被除数のいずれかを選択して出力するというものです。
この論理をVerilog-2001で記述したものを以下に示します。
module div_u16_nl( input [15:0] numerator_p, input [15:0] denominator_p, output reg [15:0] surplus_p, output reg [15:0] quotient_p, input clock); reg [15:0] num, den; wire [15:0] s, q; unsigned_div16 inst(.num(num), .den(den), .s(s), .q(q)); always @(posedge clock) begin num <= numerator_p; den <= denominator_p; surplus_p <= s; quotient_p <= q; end endmodule module unsigned_div1( input [31:0] num1, input [15:0] den1, output [31:0] s1, output q1); wire [32:0] work = {num1,1'b0} - {den1, 16'b0}; assign s1 = work[32] ? {num1[30:0], 1'b0} : work[31:0]; assign q1 = ~work[32]; endmodule module unsigned_div2( input [31:0] num2, input [15:0] den2, output [31:0] s2, output [1:0] q2); wire [31:0] work; unsigned_div1 div1_1(.num1(num2), .den1(den2), .s1(work), .q1(q2[1])); unsigned_div1 div1_2(.num1(work), .den1(den2), .s1(s2), .q1(q2[0])); endmodule module unsigned_div4( input [31:0] num4, input [15:0] den4, output [31:0] s4, output [3:0] q4); wire [31:0] work; unsigned_div2 div2_1(.num2(num4), .den2(den4), .s2(work), .q2(q4[3:2])); unsigned_div2 div2_2(.num2(work), .den2(den4), .s2(s4), .q2(q4[1:0])); endmodule module unsigned_div8( input [31:0] num8, input [15:0] den8, output [31:0] s8, output [7:0] q8); wire [31:0] work; unsigned_div4 div4_1(.num4(num8), .den4(den8), .s4(work), .q4(q8[7:4])); unsigned_div4 div4_2(.num4(work), .den4(den8), .s4(s8), .q4(q8[3:0])); endmodule module unsigned_div16( input [15:0] num, input [15:0] den, output [15:0] s, output [15:0] q); wire [31:0] work1, work2; assign s = work2[31:16]; unsigned_div8 div8_1(.num8({16'h0, num}), .den8(den), .s8(work1), .q8(q[15:8])); unsigned_div8 div8_2(.num8(work1), .den8(den), .s8(work2), .q8(q[7:0])); endmodule
この論理の動作を、Qualtus II付属のシミュレータでテストした結果を下図に示します。
クロック信号clockの立ち上がり(図に赤い線を入れました)に同期して、入力ポートの信号がレジスタnumとdenに取り込まれます。これらのレジスタの出力側は除算論理の入力側に接続されています。
次のクロック信号の立ち上がりで、除算論理の出力側が出力ポートに接続されたレジスタにセットされます。つまり、最初のクロック信号の立ち上がりの際に出入力信号の取り込みが行われ、2つ目のクロック信号の立ち上がりの際に結果のポート出力が行われます。
上の図では、被除数(分子:numerator)を12とした状態で除数(分母:denominator)を変化させています。商(quotient)と余り(surplus)が1クロック遅れた位置に現れます。
下の図は入力信号として最大値付近を与えた場合の動作をテストしたものです。最大値付近でも正常な除算が行われていることがわかります。
ここで、クロック周波数は10MHzとしています。レジスタから出力ポートまでの間にもFPGA内部の遅延があり、クロック立ち上がり後のわずかな時間、出力ポートの信号には乱れが生じます。
この論理をAltera社製のFPGA“Cyclone III EP3C25F324C6”に配置した場合の資源の消費量は、543個のロジックエレメントと64個のレジスタを使用するということが配置配線の結果として出力されました。レジスタは、4つの入出力ポートにそれぞれ16ビットのレジスタを配置しましたので、合計で64となります。16ビット除算器は1桁除算論理を16個配置しており、1桁除算論理ひとつあたりのロジックエレメントの消費量は約34となります。1桁除算器は33ビット幅の減算を行っていますので、この数字は妥当なところでしょう。
最後にタイミングをチェックしておきましょう。レジスタとレジスタの間に置かれた論理回路の最大遅延時間は47ns少々となり、レジスタに供給できる最大クロック周波数は21.16MHzであることが判りました。
最大クロック周波数を増加させる手段として、論理を前段と後段に分割して間にレジスタを置く「パイプライン」という方式があります。これにつきましては後ほど議論することといたしましょう。