ハードウェアシミュレータその14

そろそろ佳境である(うそ)。
最近、16bit固定長命令セットなどをあれこれ考えていたのだが、CPUの命令セットを作るのは難しいんだなーとしみじみ思った次第。ビットパターンには限りがあり、命令によって必要ビット数は異なり、便利な命令を1つ入れると他の命令が4つ入らなくなるとか、回路がほとんど同じだから入れても複雑にならないがあってもあんまり意味が無い命令とか、取捨選択が難しい。
最低限必要な命令だけあればとりあえず何でも動くだろうけど、コードを書きやすくしつつ、コードサイズを小さくしつつ、実行も速くしつつ、回路規模も抑えつつ、となると何をどう選んだらいいのかさっぱりわからない。そもそも最低限必要な命令ってどれだよ。
個人的には効率気にせず適当に動作するっぽいCPUの回路を作るよりも命令セットを設計するほうがずっと難しいように思える。もちろん高速動作や小さい回路を目指したCPUは非常に難しいのは言うまでもない。パイプラインとか分岐予測とかキャッシュとかわけわかんない。
と、そんなことはさておき、今回は減算について。簡単なので短い。

加算器で減算

Wikipediaの加算器のページを見ると下のほうに減算器の説明が書かれている。この回路図でわかるように、減算器は全加算器を接続して作ることができる。説明が長々と書かれているが、要約すると、
「2の補数を取って加算すれば減算できるよ」
と言っているに過ぎない。
2の補数はビット反転して1足せばよいので、とりあえずNOTと加算器が必要になりそうに思えるが、全加算器のキャリー入力を1にすれば1足せるので余計な加算器は必要無い。つまり入力の片方をNOTしてキャリーを1にすれば加算器は減算器になるのである。
また、減算を加算で表現すると、減算イメージでは繰り下がりがない場合にキャリーが発生し、繰り下がりがある場合にキャリーが発生しない。従って、このキャリー出力をそのまま減算器のキャリーに入れてやれば分割した減算が可能である。減算時のキャリーはボローと呼ばれるが、通常は減算時でもキャリーフラグに持つ。減算時のキャリー出力を反転してキャリーフラグとして持つのがZ80インテル系のCPUで、ARMやPICはそのままの状態でキャリーフラグに持つ。反転して持ったほうがわかりやすいがいちいちNOTを入れないといけないので回路はちょこっと増える。

require './hsim'

# 減算器
class Subtracter
  def initialize(bit_width)
    # 減算にも加算器を使う
    @sub = Array.new(bit_width){FullAdder.new}

    # 2bit以上の減算の場合、加算器同士を接続する
    if bit_width > 1
      @sub[0..-2].each.with_index do |a, i|
        a.x = @sub[i+1].c
      end
    end
  end

  # データ入力(d1-d0)
  def d=(v)
    @sub.zip(*v).each do |a, d1, d0|
      a.a = d1
      a.b = ~d0 # 片方はNOTを通す
    end
  end

  # キャリー入力
  def x=(v)
    @sub.last.x = v
  end

  # キャリー出力
  def c
    @sub[0].c
  end

  # 出力
  def o
    @sub.map{|a|a.s}
  end

  def inspect
    o.map{|o|"#{HLHASH[o.get]}"}.join + "\n" +
    "#{HLHASH[c.get]}"
  end
end

_vcc = Line.new
_gnd = Line.new
vcc = _vcc.o
gnd = _gnd.o

sub = Subtracter.new(8)
sub.d = [[gnd, vcc, gnd, vcc, vcc, vcc, vcc, gnd], # d1
         [gnd, gnd, gnd, gnd, gnd, vcc, vcc, vcc]] # d0
sub.x = vcc # キャリー入力は立てる

_vcc.d = H
_gnd.d = L

p sub #=> LHLHLHHH
      #=> H

まあ、結局は加算器なので加算器に入れるときにNOT通してキャリーを1にすればよいだけで、減算器をわざわざ作る必要は無い。
この回路はボツである。