ハードウェアシミュレータその15
今回のネタは乗算。掛け算である。
最近の多くのCPUには乗算命令が装備されているが、乗算回路は規模が大きくなりがちなので、昔はそんなもの無いか、多くのクロックを消費して実行する形だった。さすがに足し算を延々繰り返すような実装はしないだろうが、シフトと加算を繰り返すような感じだろう。
2進数の乗算は筆算のイメージで考えればシフトと加算で処理できるのがわかる。乗算回路の基本はそれで、こことかを見ると丁寧に解説されている。簡潔で難しいがWikipediaでもよい。
つまりビット幅に応じてシフタと加算器を大量に作れば乗算は1発で結果を出すこともできるわけで、最近のCPUではそのような回路により1クロックで結果を出す。らしい。シミュレータ上の回路も別に規模や速度は意識していないので同様に1クロックで処理できるように構成する。
シフトした値を作る回路
シフトするのは前にシフタを作ったのでそれを使えばいいのだが、固定幅の固定回数シフトなのでバレルシフタを使う必要もなく、それ以前に出力ピン配列をずらせばいいだけなので、乗算回路に埋め込んでしまうことにする。
# 符号なし乗算器 class Multiplier def initialize(bit_width) # 入力線 @buf0 = Buffer.new(bit_width) @buf1 = Buffer.new(bit_width) # L固定の出力ピン opl = OutputPin.new(L) # シフトした配列を作る @buf = Array.new(bit_width) do |i| tmp1 = [opl] * (bit_width * 2) # Lの出力ピンをbit_width*2だけ並べた配列 tmp2 = @buf0.o.map{|b|b & @buf1.o[bit_width - i - 1]} # buf0の各bitとbuf1の1bitのandを取る tmp1[bit_width - i, bit_width] = tmp2 # tmp1配列の一部をtmp2に差し替える tmp1 end end def d=(v) @buf0.d = v[0] @buf1.d = v[1] end def inspect @buf[0].map{|o|"#{HLHASH[o.get]}"}.join + "\n" + @buf[1].map{|o|"#{HLHASH[o.get]}"}.join + "\n" + @buf[2].map{|o|"#{HLHASH[o.get]}"}.join + "\n" + @buf[3].map{|o|"#{HLHASH[o.get]}"}.join + "\n" + @buf[4].map{|o|"#{HLHASH[o.get]}"}.join + "\n" + @buf[5].map{|o|"#{HLHASH[o.get]}"}.join + "\n" + @buf[6].map{|o|"#{HLHASH[o.get]}"}.join + "\n" + @buf[7].map{|o|"#{HLHASH[o.get]}"}.join + "\n" end end _vcc = Line.new _gnd = Line.new vcc = _vcc.o gnd = _gnd.o mult = Multiplier.new(8) mult.d = [[vcc, vcc, gnd, vcc, vcc, vcc, vcc, vcc], # d1 [vcc, gnd, vcc, vcc, gnd, vcc, vcc, vcc]] # d0 _vcc.d = H _gnd.d = L p mult #=> LLLLLLLLHHLHHHHH #=> LLLLLLLHHLHHHHHL #=> LLLLLLHHLHHHHHLL #=> LLLLLLLLLLLLLLLL #=> LLLLHHLHHHHHLLLL #=> LLLHHLHHHHHLLLLL #=> LLLLLLLLLLLLLLLL #=> LHHLHHHHHLLLLLLL
という感じでシフトした値を生成しておいて、あとはこれらを全部ガシャーンと加算すればできあがりである。内部のバッファはinitialize時に回路を構成するために使っているが、d=で構成するようにすれば必要なくなる。シミュレータの効率を上げる必要性がでてきたらそういった修正を入れていくことである程度は対処できそうである。同様の考えでシフタの効率も改善できそうだ。
加算回路
加算はAdderクラスを使えばよい。例えば8個の値を加算するなら7個のAdderを直列に接続すればいいが、ツリー状に構成することで段数を減らせる。ハードウェアは並列に動作できるので段数が減ると結果が出るまでの時間が減る。入力の変化による影響範囲が減るのでシミュレータの効率も良くなると思われる。たぶんだけど。
require './hsim' # 符号なし乗算器 class Multiplier def initialize(bit_width) # 入力線 @buf0 = Buffer.new(bit_width) @buf1 = Buffer.new(bit_width) # L固定の出力ピン opl = OutputPin.new(L) # シフトした配列を作る buf = Array.new(bit_width) do |i| tmp1 = [opl] * (bit_width * 2) # Lの出力ピンをbit_width*2だけ並べた配列 tmp2 = @buf0.o.map{|b|b & @buf1.o[bit_width - i - 1]} # buf0の各bitとbuf1の1bitのandを取る tmp1[bit_width - i, bit_width] = tmp2 # tmp1配列の一部をtmp2に差し替える tmp1 end # シフトした値を加算するための加算器 @adder = Array.new(Math.log(bit_width, 2)){|i|Array.new(2**i){Adder.new(bit_width*2)}} @adder.last.each.with_index do |adder, i| adder.d = [buf[i * 2], buf[i * 2 + 1]] adder.x = opl end (1...(@adder.size)).each do |i| @adder[i-1].each.with_index do |adder, j| adder.d = [@adder[i][j * 2].o, @adder[i][j * 2 + 1].o] adder.x = opl end end end def d=(v) @buf0.d = v[0] @buf1.d = v[1] end def o @adder[0][0].o end def inspect o.map{|o|"#{HLHASH[o.get]}"}.join end end _vcc = Line.new _gnd = Line.new vcc = _vcc.o gnd = _gnd.o mult = Multiplier.new(8) mult.d = [[vcc, vcc, gnd, vcc, vcc, vcc, vcc, vcc], # d1 #=> 223 [vcc, gnd, vcc, vcc, gnd, vcc, vcc, vcc]] # d0 #=> 183 _vcc.d = H _gnd.d = L p mult #=> HLLHHHHHLHHLHLLH #=> 40809
符号付き乗算
これは理屈がよくわかっていないのだが、符号付きの値が2の補数であれば、その特性をうまいこと使うことで筆算のイメージにちょいと細工すればできるらしい。符号はずして計算して後で符号だけ設定したらあかんの?NOTとかガシガシ入れるよりもそっちのほうが効率よさそうな気がするんだけど、ダメな理由がわからん。
require './hsim' # 符号あり乗算器 class SignedMultiplier def initialize(bit_width) # 入力線 @buf0 = Buffer.new(bit_width) @buf1 = Buffer.new(bit_width) # HL固定の出力ピン oph = OutputPin.new(H) opl = OutputPin.new(L) # シフトした配列を作る @buf = Array.new(bit_width) do |i| tmp1 = [opl] * (bit_width * 2) # Lの出力ピンをbit_width*2だけ並べた配列 tmp2 = @buf1.o[bit_width - i - 1] if i == (bit_width-1) # 最後のみ # 先頭以外反転 tmp2 = [@buf0.o[0] & tmp2] + @buf0.o[1..-1].map{|b|~(b & tmp2)} else # 先頭だけ反転 tmp2 = [~(@buf0.o[0] & tmp2)] + @buf0.o[1..-1].map{|b|b & tmp2} end tmp1[bit_width - i, bit_width] = tmp2 # tmp1配列の一部をtmp2に差し替える if i == (bit_width-1) or i == 0 # 最初と最後 tmp1[bit_width - i - 1] = oph end tmp1 end # シフトした値を加算するための加算器 @adder = Array.new(Math.log(bit_width, 2)){|i|Array.new(2**i){Adder.new(bit_width*2)}} @adder.last.each.with_index do |adder, i| adder.d = [@buf[i * 2], @buf[i * 2 + 1]] adder.x = opl end (1...(@adder.size)).each do |i| @adder[i-1].each.with_index do |adder, j| adder.d = [@adder[i][j*2].o, @adder[i][j*2+1].o] adder.x = opl end end end def d=(v) @buf0.d = v[0] @buf1.d = v[1] end def o @adder[0][0].o end def inspect @buf[0].map{|o|"#{HLHASH[o.get]}"}.join + "\n" + @buf[1].map{|o|"#{HLHASH[o.get]}"}.join + "\n" + @buf[2].map{|o|"#{HLHASH[o.get]}"}.join + "\n" + @buf[3].map{|o|"#{HLHASH[o.get]}"}.join + "\n" + @buf[4].map{|o|"#{HLHASH[o.get]}"}.join + "\n" + @buf[5].map{|o|"#{HLHASH[o.get]}"}.join + "\n" + @buf[6].map{|o|"#{HLHASH[o.get]}"}.join + "\n" + @buf[7].map{|o|"#{HLHASH[o.get]}"}.join + "\n" + o.map{|o|"#{HLHASH[o.get]}"}.join end end _vcc = Line.new _gnd = Line.new vcc = _vcc.o gnd = _gnd.o mult = SignedMultiplier.new(8) mult.d = [[vcc, vcc, gnd, vcc, vcc, vcc, vcc, vcc], # d1 => -33 [gnd, gnd, gnd, gnd, gnd, vcc, vcc, vcc]] # d- => 7 _vcc.d = H _gnd.d = L p mult #=> LLLLLLLHLHLHHHHH #=> LLLLLLLLHLHHHHHL #=> LLLLLLLHLHHHHHLL #=> LLLLLHLLLLLLLLLL #=> LLLLHLLLLLLLLLLL #=> LLLHLLLLLLLLLLLL #=> LLHLLLLLLLLLLLLL #=> HLHHHHHHHLLLLLLL #=> HHHHHHHHLLLHHLLH #=> -231
うん、さっぱりわからん。でも結果は合ってるようだ。