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

ついにシリーズも2桁突入である。そのわりには歩みは遅々としてなかなか進まないわけなのだが、素人がひとつずつやっているのでしょうがない。
なんでHDLとか使って既存のシミュレータソフトで動かしたりしないのか、そのほうがFPGAで実行したりできるし圧倒的によいじゃないか。というのは自分でも思うが、HDLってわりと高機能で、高レベルの言語で書いたら回路が自動的に構成される感じがあって、とりあえずNANDから構成してみたかったというのがあり、シミュレータを作ってみたかったというのもある。まあ、適当に興味本位でやってるだけである。
さて、今回は前回作った無条件ジャンプを条件ジャンプに変えてみる。簡単な話なので短い。

キャリーフラグ

加算を行うと最上位桁が更に上に繰り上がることがある。2桁の10進数同士を足すと100以上の数字になることがある、という話だ。今作っているCPUはレジスタが4bitなので、足して16以上になると5bitの値になってしまう。これをオーバーフローと言って、結果は繰り上がった部分はレジスタに収まらないので単純に捨てられる。C言語などでは普通に起こる。
計算した結果、オーバーフローが起こったかどうかを知ることができると、いろいろ便利になる。例えば、4bitレジスタを2つ繋げて8bitの値として扱う場合、下位4bit同士を足してから上位4bitを足す前に、下位4bitの計算でオーバーフローが起こっていたら上位4bitに1足す、とすることで、8bitの演算ができる。また、例えば4bitレジスタに0xdを入れておいて、1ずつ足していくと、3回目にオーバーフローが起こるので、これが検出できれば指定回数のループができる。
と言うことで、この繰り上がった最上位桁を1bitのレジスタに格納して、こいつが1だったらオーバーフローが起こったと認識できるようにする。このレジスタをキャリーフラグと言う。

キャリーフラグの実装

加算器のキャリー出力を1bitレジスタの入力に接続するだけである。
しょぼすぎるのでコードも出さない。

応用

現在、無条件ジャンプ命令であるjmpを、条件ジャンプであるjnc(jump not carry)に変えてみよう。キャリーフラグが立っていない(つまりLの)ときにジャンプする。
無条件ジャンプが無くなると不便なようだが、無条件ジャンプがしたければ直前にadd a, 0という無駄命令を発行しておけば、無条件でキャリーはLに落ちるのでそれでよい。そもそも無条件ジャンプのほうこそ必須じゃないのだ。

# キャリーフラグ用1bitレジスタ
cf = Register.new(1)
cf.clk = clk.o
cf.clrb = clrb.o
cf.w = vcc

# 中略

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

これで終わりである。

rom.d = [ # 8アドレスぶんのデータ
  [gnd, gnd, vcc, vcc, gnd, vcc, gnd, gnd], # 0 : mov a, 0xd
  [gnd, vcc, gnd, gnd, gnd, vcc, gnd, gnd], # 2 : add a, 0x1
  [vcc, gnd, gnd, gnd, gnd, gnd, gnd, vcc], # 3 : jnc 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


ちなみに、Z80x86といったCPUはゼロフラグ他いろいろなフラグを持っていて、計算結果がどうなったかをわりと細かく知ることができる。これで値の比較が可能になる。
また、MIPSアーキテクチャはフラグという概念を持たず、レジスタを比較してその結果でジャンプするようになっている。らしい。
今回は手軽にフラグを実装してみたが、別にこれもCPUに必須というわけではない。