2進からBCDへの変換回路 の変更点
更新- 追加された行はこの色です。
- 削除された行はこの色です。
- 電気回路/HDL/2進からBCDへの変換回路 へ行く。
- 電気回路/HDL/2進からBCDへの変換回路 の差分を削除
[[公開メモ]] #contents * 概要 [#y15acbf7] (2010-10-28) すみません、回路サイズの見積もりなどで間違っていた部分があったので、大幅に記述を見直しました。 marsee さんから頂いたお題で、通常の「2進数符号なし整数」を「BCD (二進化十進表現)」 に変換する回路を書いてみました。 BCD (二進化十進表現)とは~ http://ja.wikipedia.org/wiki/%E4%BA%8C%E9%80%B2%E5%8C%96%E5%8D%81%E9%80%B2%E8%A1%A8%E7%8F%BE 採用したアルゴリズムは非常に単純なもので、たとえば BITS = 10 のときは、 +1000で割った商を始めの桁に +その余りを100で割って、その商を次の桁に +その余りを10で割って、その商を次の桁に +その余りを1で割って、その商を最後の桁に(本当は割る必要ないのだけれど) となってます。 ステートを工夫して最後の割り算を飛ばしたり、 BCD1桁あたりのクロック数を削ったりもできそうですが、 速度が要求される部分でもないでしょうから、 回路規模を優先してそのままにしています。 合成結果は Spartan3 で - BITS = 8 のとき、 42 スライス程度 - BITS = 10 のとき、 52 スライス程度 - BITS = 16 のとき、 67 スライス程度 - BITS = 32 のとき、 124 スライス程度 でした。ちゃんと回路図書いたらもっと小さくできる・・・かな???~ 入力ビット数を 32 にしてもクロックは 100MHz 以上で回るようです。~ まあ、主には引き算と比較しかしていないから当然か。 アルゴリズム、コーディングスタイルその他に関するアドバイス、大歓迎です。 ** 模範解答がこちらにあるそうです [#c4bcba6c] このページの以下の部分は、「私がやったらこうなった」という意味で残しておきますが、 もっとずっと効率の良いアルゴリズムが [[FPGA の部屋>http://marsee101.blog19.fc2.com/blog-entry-1630.html#comment]] で紹介されていました。 [ame_feb4さんに教えていただいた回路] という部分に書かれている~ http://jjmk.dk/MMMI/Lessons/06_Arithmetics/No6_Conversion/Index.htm ~ のページです。 動作周波数、回路規模のどちらをとっても、 ここで紹介した回路とは比較にならないほど優れていますので、 実用的な回路を求めている方はそちらをお使いください。 ** 自前の回路で %%苦労した%% 工夫した点 [#bdc6cc8a] 特に、ビット数を parameter 化して、自由に設定できるようにするのは大変でした。 + localparam を計算してから input / output のビット幅を記述するため、 古い C 言語のような module 定義になっている点 + BCD_DIGITS_BITS の求め方がちょっとアレな件 (もっと良い方法はないものか???) + initialize for による ROM の初期化 + 多ビットレジスタへの部分代入はできないため、一旦 bcd_internal に受けて、bcd につなぎ直している点 + bcd につなぐ際、generate for 中で +: を使っている点 + テストベンチでの値の比較に != ではなく !== を使っている点 あたりが最近覚えたテクニックだったりします。 ビット指定していない演算がいくつかあるので BITS > 32 ではちゃんと動かないと思います。ご注意ください。 一生懸命ビット指定してもうまく動かせなかったので、 手抜きのままになっています(汗 * ソース [#ebed19cc] LANG:verilog(linenumber) // 2進数を BCD に直す回路 // bin に変換元の符号なし整数をセットして start に1を立てる // 次クロックで busy が立つので、降りるまで待って bcd を読む // BITS により、任意ビット数に対応可能 // 計算時間は BCD_DIGITS * 6 クロック程度 module bin2bcd #( parameter BITS = 10 ) (clk, rst, start, busy, bin, bcd); // log 16 = 1.20412 < 1205/1000 を使って BCD 表現に必要な桁数を求める localparam BCD_DIGITS = BITS * 1205 / 1000 / 4 + 1; localparam BCD_DIGITS_BITS = BCD_DIGITS < 2 ? 1 : BCD_DIGITS < 4 ? 2 : BCD_DIGITS < 8 ? 3 : BCD_DIGITS < 16 ? 4 : BCD_DIGITS < 32 ? 5 : BCD_DIGITS < 64 ? 6 : BCD_DIGITS < 128 ? 7 : 8; input wire clk; input wire rst; input wire start; output wire busy; input wire [BITS-1:0] bin; output wire [BCD_DIGITS*4-1:0] bcd; // 割り算用レジスタ reg [BITS-1:0] numerator; // 分子 reg [BITS+3-1:0] denominator; // 分母 reg [3:0] quotient; // 商 // 10^n を保持する ROM reg [BITS-1:0] denominators[0:BCD_DIGITS-1]; integer i; initial // ROM の初期化 for (i=0; i<BCD_DIGITS; i=i+1) denominators[i] = 10 ** i; // ステートマシンは state, digit を変数として動作する reg [2:0] state; reg [BCD_DIGITS_BITS-1:0] digit; localparam stIdle = 0; localparam stDivide0 = 1; localparam stDivide1 = 2; localparam stDivide2 = 3; localparam stDivide3 = 4; localparam stDivide4 = 5; localparam stNext = 6; // 計算結果を保持するレジスタ reg [3:0] bcd_internal[0:BCD_DIGITS-1]; always @(posedge clk) if (rst) begin state <= stIdle; end else case (state) stIdle: begin numerator <= bin; digit <= BCD_DIGITS - 1; if (start) state <= stDivide0; end stDivide0: begin // 分母に 10^n << 3 を用意する denominator <= denominators[digit] << 3; state <= stDivide1; end stDivide1, stDivide2, stDivide3, stDivide4: begin // 割り算して商を得る if (numerator >= denominator) begin quotient <= { quotient, 1'b1 }; numerator <= numerator - denominator; end else begin quotient <= { quotient, 1'b0 }; end denominator <= denominator >> 1; state <= state + 1; end stNext: begin // 結果を格納して次へ bcd_internal[digit] <= quotient; if (digit==0) begin state <= stIdle; end else begin digit <= digit - 1; state <= stDivide0; end end endcase // 出力に繋ぐ assign busy = state != stIdle; generate genvar j; for (j=0; j<BCD_DIGITS; j=j+1) begin: bcd_connection assign bcd[j*4 +: 4] = bcd_internal[j]; end endgenerate endmodule * テストベンチ [#tc58bda1] LANG:verilog(linenumber) module bin2bcd_test; // ここを変えれば異なるビット数についてもテスト可能 parameter BITS = 16; localparam BCD_DIGITS = BITS * 1205 / 1000 / 4 + 1; // log 16 = 1.20412 // Inputs reg clk; reg rst; reg start; reg [BITS-1:0] bin; // Outputs wire busy; wire [BCD_DIGITS*4-1:0] bcd; // Instantiate the Unit Under Test (UUT) bin2bcd #(BITS) uut ( .clk(clk), .rst(rst), .start(start), .busy(busy), .bin(bin), .bcd(bcd) ); // bin2bcd の function バージョン function [BCD_DIGITS*4-1:0] bin2bcd_func; input [BITS-1:0] bin; integer i; reg [BCD_DIGITS*4-1:0] result; begin for (i=BCD_DIGITS-1; i>=0; i=i-1) begin result[i*4 +: 4] = bin / 10**i; bin = bin % 10**i; end bin2bcd_func = result; end endfunction // value を入力して正しい値が出てくることを確かめる task test; input [BITS-1:0] value; reg [BCD_DIGITS*4-1:0] expect; begin bin = value; start = 1; @(posedge clk); start = 0; @(negedge busy); expect = bin2bcd_func(value); if (bcd!==expect) $display("*** ERROR: expecting %x (%d) but found %x.", expect, value, bcd); end endtask // クロック always #5 clk = !clk; integer i; initial begin // Initialize Inputs clk = 0; rst = 1; start = 0; bin = 0; // Wait 100 ns for global reset to finish #100; // Add stimulus here rst = 0; repeat(10) @(posedge clk); // まず境界値的な値をチェック test({BITS{1'b0}}); // 0 test({BITS{1'b1}}); // (1 << BITS) - 1 test({1'b1, {BITS-1{1'b0}}}); // (1 << (BITS-1)) test({1'b0, {BITS-1{1'b1}}}); // (1 << (BITS-1)) - 1 // あとは適当にチェック for(i=0; i<10000-1; i=i+1) test($random); $display("*** done"); $stop(); end endmodule * 改良 [#i6f7bd6f] 最後の桁を求める際に「1で割る」無駄な操作を無くしました。 ソースは多少ごちゃごちゃしましたが、回路は逆に小さくなるようでした。 LANG:verilog(linenumber) // 2進数を BCD に直す回路 // bin に変換元の符号なし整数をセットして start に1を立てる // 次クロックで busy が立つので、降りるまで待って bcd を読む // BITS により、任意ビット数に対応可能 // 計算時間は (BCD_DIGITS-1) * 6 クロック程度 module bin2bcd #( parameter BITS = 10 ) (clk, rst, start, busy, bin, bcd); // log 16 = 1.20412 < 1205/1000 を使って BCD 表現に必要な桁数を求める localparam BCD_DIGITS = BITS * 1205 / 1000 / 4 + 1; localparam BCD_DIGITS_BITS = BCD_DIGITS < 2 ? 1 : BCD_DIGITS < 4 ? 2 : BCD_DIGITS < 8 ? 3 : BCD_DIGITS < 16 ? 4 : BCD_DIGITS < 32 ? 5 : BCD_DIGITS < 64 ? 6 : BCD_DIGITS < 128 ? 7 : 8; input wire clk; input wire rst; input wire start; output wire busy; input wire [BITS-1:0] bin; output wire [BCD_DIGITS*4-1:0] bcd; // 割り算用レジスタ reg [BITS-1:0] numerator; // 分子 reg [BITS+3-1:0] denominator; // 分母 reg [3:0] quotient; // 商 // 10^n を保持する ROM reg [BITS-1:0] denominators[1:BCD_DIGITS-1]; integer i; initial // ROM の初期化 for (i=1; i<BCD_DIGITS; i=i+1) denominators[i] = 10 ** i; // ステートマシンは state, digit を変数として動作する reg [2:0] state; reg [BCD_DIGITS_BITS-1:0] digit; localparam stIdle = 0; localparam stDivide0 = 1; localparam stDivide1 = 2; localparam stDivide2 = 3; localparam stDivide3 = 4; localparam stDivide4 = 5; localparam stNext = 6; // 計算結果を保持するレジスタ reg [3:0] bcd_internal[2:BCD_DIGITS-1]; always @(posedge clk) if (rst) begin state <= stIdle; end else case (state) stIdle: begin digit <= BCD_DIGITS - 1; if (start) begin numerator <= bin; state <= stDivide0; end end stDivide0: begin // 分母に 10^n << 3 を用意する denominator <= denominators[digit] << 3; state <= stDivide1; end stDivide1, stDivide2, stDivide3, stDivide4: begin // 割り算して商を得る if (numerator >= denominator) begin quotient <= { quotient, 1'b1 }; numerator <= numerator - denominator; end else begin quotient <= { quotient, 1'b0 }; end denominator <= denominator >> 1; state <= state + 1; end stNext: begin // 結果を格納して次へ bcd_internal[digit] <= quotient; if (digit==1) begin state <= stIdle; end else begin digit <= digit - 1; state <= stDivide0; end end endcase // 出力に繋ぐ assign busy = state != stIdle; generate genvar j; for (j=2; j<BCD_DIGITS; j=j+1) begin: bcd_connection assign bcd[j*4 +: 4] = bcd_internal[j]; end endgenerate assign bcd[7:4] = quotient; assign bcd[3:0] = numerator[3:0]; endmodule * 一旦 quotient に受けているのが良いらしい [#c036e8cc] 上記ソースから、quotient を無くして、 割り算結果を直接 bcd_internal にシフトインしていけば、 下記のように Verilog ソースをかなり単純化できることに気づいたのですが、 実際に変更を加えて合成してみると、、、使用スライス数が若干ですが増えてしまいました。(汗 Verilog ソースを単純化することと、 実際の回路を最小化することとは別なんだということを 思い知らされました。 BITS = 10 で比較すると Spartan 3A DSP では - 上記回路 (BCD_DIGITS-1) x 6 + 1 クロック, 最大147MHz, 47 スライス - 下記回路 (BCD_DIGITS-1) x 6 + 1 クロック, 最大145MHz, 49 スライス でした。 まあ、この程度は誤差なので、実際には単純に書ける方を選ぶべきかも? LANG:verilog(linenumber) // 2進数を BCD に直す回路 // bin に変換元の符号なし整数をセットして start に1を立てる // 次クロックで busy が立つので、降りるまで待って bcd を読む // BITS により、任意ビット数数値を変換可能 // 計算時間は (BCD_DIGITS-1) * 6 クロック程度 module bin2bcd #( parameter BITS = 10 ) (clk, rst, start, busy, bin, bcd); // log 16 = 1.20412 を使って BCD 表現に必要な桁数を求める localparam BCD_DIGITS = BITS * 1205 / 1000 / 4 + 1; localparam BCD_DIGITS_BITS = BCD_DIGITS < 2 ? 1 : BCD_DIGITS < 4 ? 2 : BCD_DIGITS < 8 ? 3 : BCD_DIGITS < 16 ? 4 : BCD_DIGITS < 32 ? 5 : BCD_DIGITS < 64 ? 6 : BCD_DIGITS < 128 ? 7 : 8; input wire clk; input wire rst; input wire start; output wire busy; input wire [BITS-1:0] bin; output wire [BCD_DIGITS*4-1:0] bcd; // 割り算用レジスタ reg [BITS-1:0] numerator; // 分子 reg [BITS+4-1:0] denominator; // 分母 // 10^n を保持する ROM reg [BITS-1:0] denominators[1:BCD_DIGITS-1]; integer i; localparam [BITS-1:0] ten = 10; initial // ROM の初期化 for (i=1; i<BCD_DIGITS; i=i+1) denominators[i] = ten ** i; // ステートマシンは state, digit を変数として動作する reg [2:0] state; reg [BCD_DIGITS_BITS-1:0] digit; localparam stIdle = 0; localparam stDivide0 = 1; localparam stDivide1 = 2; localparam stDivide2 = 3; localparam stDivide3 = 4; localparam stDivide4 = 5; localparam stNext = 6; reg [(BCD_DIGITS-1)*4-1:0] bcd_internal; always @(posedge clk) if (rst) begin state <= stIdle; end else case (state) stIdle: begin digit <= BCD_DIGITS - 1; if (start) begin numerator <= bin; state <= stDivide0; end end stDivide0: begin // 分母に 10^n << 3 を用意する denominator <= denominators[digit] << 3; state <= stDivide1; end stDivide1, stDivide2, stDivide3, stDivide4: begin // 割り算して商を得る if (numerator >= denominator) begin bcd_internal <= { bcd_internal, 1'b1 }; numerator <= numerator - denominator; end else begin bcd_internal <= { bcd_internal, 1'b0 }; end denominator <= denominator >> 1; state <= state + 1; end stNext: begin if (digit!=1) begin state <= stDivide0; end else begin state <= stIdle; end digit <= digit - 1; end endcase // 出力に繋ぐ assign busy = state != stIdle; assign bcd = { bcd_internal, numerator[3:0] }; endmodule 上記の stNext にほとんど内容がないので、 動作を stDivide4 に押し込めてしまうことで、 遅延を (BCD_DIGITS-1) x 5 + 1 に減らせますが、 スライス数が 10 ほど増えてしまうようでした。 * コメント [#h185d7db] #article_kcaptcha **yCwZWbCvkkwE [#p1c3a950] >[Celina] (2013-01-05 (土) 06:03:34)~ ~ All of these artliecs have saved me a lot of headaches.~ // #comment_kcaptcha **2進数からBCDへの変換回路 [#a6f91af8] >[marsee] (2010-10-27 (水) 10:23:32)~ ~ こんにちは。わざわざ変換回路を作っていただいて、ありがとうございました。~ 私の2進数からBCDへの変換回路も恥ずかしいですが、ブログに貼っておきました。~ http://marsee101.blog19.fc2.com/blog-entry-1628.html~ // - marsee さんの回路、拝見しました。かなり目からうろこでした。が、ちょっと一般化はしづらそうですかね。主要な定数をパラメータ化した module を書こうとすると、verilog の不思議な制約に引っかかってしまっていつも苦労しています・・・ -- [武内(管理人)] &new{2010-10-27 (水) 11:40:50}; #comment_kcaptcha
Counter: 27342 (from 2010/06/03),
today: 1,
yesterday: 11