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

いままでレジスタへの操作は値の転送と加算のみだったので、たいしたことができなかった。レジスタ間の転送と加算ができるようになれば世界は広がるだろう。
レジスタ4bitだけど。
レジスタ4個しかないけど。

今回は以下の2つの命令を追加する。
mov reg, reg
add reg, reg

命令フォーマット

まずは命令フォーマットから。しかし、

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 : jnc adr

というフォーマットだったので、これを見る限り、ビットパターンに余裕は無い。何かを削るとして、削れそうなものはjnc命令のアドレスが無駄に大きいあたりなので、jncのアドレスを6bitに減らすことでビットパターンに空きを作ることにしよう。
こうじゃ。

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

作成可能なプログラムのサイズが128命令から64命令に減った。下方修正である。ゲームで言うから弱体化である。こんな修正をしていたら運営はクソみたいなよくある情景が見られることになる。
んで、空いたところにmovとaddを突っ込む。

0 0 imm3 imm2 imm1 imm0 reg1 reg0 : mov reg, imm
0 1 imm3 imm2 imm1 imm0 reg1 reg0 : add reg, imm
1 0    X    0  rs1  rs0  rd1  rd0 : mov rd, rs
1 0    X    1  rs1  rs0  rd1  rd0 : add rd, rs
1 1 adr5 adr4 adr3 adr2 adr1 adr0 : jnc adr

rsがsource、rdがdestinationで、rsからrdに転送、もしくは加算する。destinationって聞きなれないが、物流業界ではFinalDestinationで最終仕向地なので人によってはよく目にする(よく目にするとは言ってない)。

命令デコーダ

さて、この命令を追加するにあたって、加算器を含めて部品部分には修正は必要無い。レジスタファイルも出力が2ポートある。必要なものはすでに揃っているのだ。
命令の入力から各回路の動作を指示する部分を修正して命令を増やすことになるが、この部分を命令デコーダと言う。

ジャンプ命令

ジャンプ命令のビットパターンが変わったことで、ジャンプ命令に関するデコーダを変更する必要がある。
今までは命令の1bitとキャリーフラグを見ていたが、命令の2bitを見るようにする。

# 最上位ビットが1かつ2ビット目が1かつキャリーフラグが0のときに1にすることでジャンプする
muxj.s = bus.o[0] & bus.o[1] & ~cf.o[0]

レジスタファイル

レジスタファイルの書き込み条件も変わる。

regf.w = ~(bus.o[0] & bus.o[1]) # 命令の上位2ビットが11以外の場合だけ書き込む

加算器

残りは加算器への入力だ。今まではROMからの命令の一部分を加算器に入力しておけばよかったが、今回追加される命令で、レジスタファイルの出力ポート1の値を入力するパターンが追加される。出力ポート1の選択信号は命令中のrsにあたる部分なので、出力ポート1の選択信号にそれを入力する。

# 出力ポートの接続
regf.sout0 = bus.o[6..7]
regf.sout1 = bus.o[4..5]

んで、命令の一部と出力ポート1の値のどっちを選ぶかのマルチプレクサを用意する。

# 出力ポート1とバスの入力を選択するマルチプレクサ
muxa = Mux1_n.new(4)
muxa.d = [regf.o1, bus.o[2..5]]
muxa.s = bus.o[0] # 最上位が0だったらバスの値を使う

加算器の入力をさきほどのマルチプレクサの値に変更する。

adder.d = [regf.o0, muxa.o] # レジスタファイルの出力ポート0とmuxaのデータを加算する

最後に、この加算器を通した値を使うか、通さない値を使うかを選択するマルチプレクサの条件を変更する。

muxd.d = [adder.o, muxa.o]
muxd.s = (~bus.o[0] & bus.o[1]) | (bus.o[0] & ~bus.o[1] & bus.o[3])

うん、ややこしくなってきた。
この接続イメージはたぶんこんな感じ。

フィボナッチ数列

レジスタ間転送と加算ができると世界が広がってフィボナッチ数列を計算することができるようになる。レジスタが4bitなので13までだが。

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

Bレジスタの値が結果になるようになっている。

おしまい

命令数を67%増やすという大幅拡張であった(3個→5個)。これによって非常に柔軟なコーディングができることも実感できた。また、命令を増やすとデコーダがどんどん複雑になっていくこともわかった。
一般的にCPUの命令のビットパターンは非常に慎重に設計される。同じ意味のビットは同じ場所に割り当てるように極力気を使うだろうし、その上でどのような命令があると便利なのかを取捨選択して、回路が簡単になるように努力する。回路の簡単さはクロック上限の引き上げとトランジスタ数の削減に効果がある。
このCPUモドキもレジスタの選択については同じ場所にくるように考えてある。でもmovとaddの選択が煩雑になってしまってマルチプレクサmuxdの選択信号は大変なことになった。なかなか難しい。
命令のビット長がさすがに苦しくなってきたので今後はその辺の拡張も考えていかなくてはならないだろう。PICマイコンなんかは命令長12bitとか14bitとかになっているので、半端な命令長も別に珍しい話ではない。
次回はまたネタが思いついてうまく行ったら更新する予定。
最後に、シミュレータ本体
https://gist.github.com/mirichi/b799ac8eb798abc2c72e
と、フィボナッチ数列計算回路
https://gist.github.com/mirichi/a0cf03d4e379c42a66aa