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

転送と加算ができたので、次は簡単な無条件直接ジャンプ命令を作ってみよう。
現状、ROMに入力するアドレスは4bitバイナリカウンタで生成していて、これはDフリップフロップを繋げただけのものであり、こいつに値を入力する機能が無い。これを書き換えることでジャンプを実現するわけだから、まずはここからなんとかしなければならない。

カウンタ機能を分割する

4bitバイナリカウンタはクロックが入力されるたびに1増えるという回路だが、これを修正して値を書き換える機能を追加するのは、以前クリア信号を追加するときに四苦八苦したのと同じような苦労が再現されそうなので遠慮しておくことにした。
ではどうするかと言うと、4bitバイナリカウンタを4bitレジスタと4bitインクリメンタに分け、レジスタの出力をインクリメンタとROMに、インクリメンタの出力をレジスタに入力するようにする。

インクリメンタは半加算器を繋げただけの簡単な1足す回路で、可変長で作れるようにクラスを作っておく。

class Incrementer
  def initialize(bit_width)
    @ha = Array.new(bit_width){HarfAdder.new}
    @ha.last.b = OutputPin.new(H)

    if bit_width > 1
      (bit_width-1).times do |i|
        @ha[i].b = @ha[i+1].c
      end
    end
  end

  def d=(v)
    @ha.zip(v).each do |ha, o|
      ha.a = o
    end
  end

  def o
    @ha.map{|ha|ha.s}
  end
end

これを4bitレジスタと接続し、ROMに接続する。4bitレジスタはクロックの立ち上がりで更新されるので、1クロックに1ずつ増えていくことになる。

# 4bitプログラムカウンタ用レジスタ
pc = Register.new(4)
pc.clk = clk.o
pc.clrb = clrb.o
pc.w = vcc

# 4bitインクリメンタ
inc = Incrementer.new(4)

# 上記2つを接続する
pc.d = inc.o
inc.d = pc.o

# ROMにカウンタの値を接続
rom.a = pc.o[1..3]
rom.csb = pc.o[0]

ジャンプ命令のフォーマット

それでは命令フォーマットを。8bitROMの最上位が余っているのでそれを使うことにする。
今は

X 0 imm3 imm2 imm1 imm0 reg1 reg0 : mov reg, imm
X 1 imm3 imm2 imm1 imm0 reg1 reg0 : add reg, imm

こうなっているので、これを、

0    0 imm3 imm2 imm1 imm0 reg1 reg0 : mov reg, imm
0    1 imm3 imm2 imm1 imm0 reg1 reg0 : add reg, imm
1 adr6 adr5 adr4 adr3 adr2 adr1 adr0 : jmp adr

という感じにする。ROMのデータが8個しかない(アドレス線はチップセレクト含めて4本ある)が、命令は贅沢に7bitアドレス指定である。ROMとPCを大きくすれば最大128個の命令を扱えるという仕様になった。
後で命令を追加していくにあたりこんなフォーマットではまったくダメなのではないかと思うのだが、まあ、とりあえず今はこれで。追加しないかもしれないし。

命令の解釈とジャンプの実装

PCレジスタの値を変更するのがジャンプ命令の意味であるので、ジャンプ命令が来たらPCレジスタへの書き込み値をROMの内容で差し替えるようにマルチプレクサを挟む。
図にするとこんな感じで、マルチプレクサの選択信号がROMの最上位ビットとなる。これが0のときはインクリメンタの値を、1のときはROMの値を出力させればよい。

コードはこう。

# 4bitプログラムカウンタ用レジスタ
pc = Register.new(4)
pc.clk = clk.o
pc.clrb = clrb.o
pc.w = vcc

# 4bitインクリメンタ
inc = Incrementer.new(4)

# ジャンプ命令用のマルチプレクサ
muxj = Mux1_n.new(4) # 命令中には7ビットのアドレスがあるが、この回路のアドレス線は4本しかない

# 上記3つを接続して1クロックに1ずつカウントさせつつジャンプ命令で値を直接指定できるようにする
pc.d = muxj.o
inc.d = pc.o
muxj.s = bus.o[0] # 命令の最上位ビットが0の場合はインクリメンタの出力を使う感じ
muxj.d = [bus.o[4..7], inc.o]

また、このままだとジャンプ命令のアドレス部分がmovやaddの回路のほうにも入力されてしまい、レジスタが思わぬ値に書き換えられてしまうことになるので、これをどうにかする必要がある。
どのようにどうにかするかはたぶんやりかたが色々あると思うのだが、今回はレジスタファイルへの書き込み信号をジャンプ命令のときにLに落とす、だけ、という形にしてみる。計算しようが何しようが、レジスタが更新されなければ問題は無いのだ。今のところは。
レジスタファイルの書き込み信号はこれまで強制的にHだったので、この入力にROM最上位ビットを反映させるようにする。このビットがHだとジャンプなので、NOTを挟んで入力する。

regf.w = ~bus.o[0] # 命令の最上位ビットが0の場合だけ書き込む

ちなみに相対ジャンプ命令を作る場合は、相対値を2の補数表現として、ROMの出力とインクリメンタの出力を足すように加算器を挟む。

できた

命令列は

rom.d = [ # 8アドレスぶんのデータ
  [gnd, gnd, gnd, gnd, gnd, vcc, gnd, gnd], # 0 : mov a, 0x1
  [gnd, vcc, gnd, gnd, gnd, vcc, gnd, gnd], # 2 : add a, 0x1
  [vcc, gnd, gnd, gnd, gnd, gnd, gnd, vcc], # 3 : jmp 1
  [gnd, gnd, gnd, gnd, vcc, vcc, vcc, gnd], # 2 : mov c, 0x3
  [gnd, gnd, gnd, vcc, gnd, gnd, vcc, vcc], # 3 : mov d, 0x4
  [gnd, vcc, gnd, vcc, gnd, vcc, gnd, vcc], # 5 : add b, 0x5
  [gnd, vcc, vcc, gnd, vcc, gnd, vcc, gnd], # 6 : add c, 0xa
  [gnd, vcc, vcc, gnd, vcc, gnd, vcc, vcc]  # 7 : add d, 0xa
].reverse

となっていて、後ろのほうは前回のゴミなのだが、これを省くとたぶんうまく動かないし、NOP命令が用意されていないのでゴミのまま残した。