dev CPU (6502カスタム)
_
|
根本的になにか設計方法を間違ってるような気がしてならない。 しかしこれ以上の最適化はますます記述の見通しが悪くなるばかり。 回路規模を減らす必要があればですが。 やはり内部RAMを使ったマイクロコード方式にして、 アドレスデコーダをなくすべきか? | ! | いくら見やすいようにとはいえ変な設計。 |
DE1ボードへの移植に伴い、最大動作周波数も50MHzが要求される。 論理合成ツールはAltera QuartusII 9.0 Web Editionを使用。 SFLからHDLへの変換は、SFL2VHDLからsfl2vlに変更。 これにより回路規模は5分の3まで削減。 速度性能も2割以上アップし、50MHzは余裕でクリアできた。 さらに各演算命令を散発的な演算子で行っていたものをALUにモジュール化したり、 ロジックで組んでいた加算器を演算子に変更したり、 データバスを一部見直したりで動作周波数63MHzまで高速化。
まあクロックドメインを分ければ別に5MHz程度でもいいんですが。
各演算命令における演算結果とフラグの変化は以下のようになっています。
[ADC演算結果(420KByte)]
[SBC演算結果(390KByte)]
[CMP演算結果(220KByte)] CPX、CPYも同様です。
命令マトリクスを見ながら命令別の圧縮を行いました。
[命令マトリクス(Excel形式)] 印刷して使用すると便利!
[命令セット圧縮結果]
元来6502は68系に似たMPUであるためレジスタへのデータの読み込みは、 そのクロック内で行わなければならず、 メモリが遅いとMPUのクロック周波数を落とす必要がありました。 今回は33MHzをベースクロックとしているので、 80系のCPUに見られるようなレディ入力を用意し、 こいつにメモリからACKを返すことでレジスタへのデータ読み込みを行うようにしています。
ちなみに6502にはRDY入力がありますが、 このRDY入力は単に動作を停止させるためのものであるため、 レジスタとは関係ありません。
論理設計エラーもだが、FPGA外部の物理的なエラーとは厄介なもので、
いったんこいつにけつまずくと数時間を無駄にしかねない。
そこで命令レジスタに想定外の命令がロードされた場合は
CPUを停止させるような回路をつけておくと便利。
通常はスイッチを切っておけばよい。
[想定外命令セット圧縮結果]
FPGA上でDr.マリオやDQ3のタイトル画面が表示されなかった。 エミュではちゃんと動作しているので、SFLをsfl2vlでverilogに変換し、 verilatorにて実行しログを取得、エミュの動作ログと比較することで原因を特定する。
このようなCPUのログ比較はテキストベースで行うと便利だ。
エミュからは波形よりテキストが簡単に取れるし、
HDL側で波形を取得するためにはテストベンチ作成が面倒だし。
テキスト比較ツールはあいまい比較もできるのでオススメである。
WinMergeによるテキストログ比較
タイトル画面表示あたりまで50000命令実行後から30000命令のログを取得。
73187命令実行後(同一命令の連続実行は除外)のTSX($BA)にてXレジスタに入る値が異なっていた。
エミュとSFLのコードを比較したところ、エミュでは正しく
r_x = r_stack;
と、スタックの値をストアしているが、
SFLでは
rx := pstate;
と、誤ってフラグステータスをストアする記述になってしまっていた。
これを修正し、FPGA上でも問題なくタイトル画面が表示された。
なお、7827、16337、66669、109339命令実行時にVBlankが、 24987、44252、53763、63445、72858命令実行時にNMIが発生するため、 SFL側でもそれらを擬似的に発生させている。 それら発生タイミングでのログの違いが、 73187命令前までの差分として現れている。
ギャラクシアンの動作確認で、本来なら敵キャラ全体が横にスクロールすべきところで、 ずれずれだった(Y座標35と97付近)。 これは、Xスクロールレジスタへのwriteタイミングが問題だと考えられたため、 その付近の命令を抽出したところ次のようなものだった。
クロック数 | |||
adrs 命令 | opcode | Emu | 誤HDL |
E288 DEC | C6 E0 | 5 | 4 |
E28A DEC | C6 E0 | 5 | 4 |
E28C DEC | C6 E0 | 5 | 4 |
E28E DEY | 88 | 2 | 2 |
E28F BEQ | F0 03 | 2 | 2 |
E291 JMP | 4C 88 E2 | 3 | 3 |
これを見ると、ライン40付近までDEC命令でカウントし、 0になったらXスクロールレジスタを書き換えているようだった。 注目すべきはDEC命令の実行クロック数がエミュとHDLで異なっていたこと。 本来なら5クロック要するところを4クロックで実行していたため、 予定よりも早くXレジスタへのwriteが行われてしまっていたようだ。
HDLを修正し、5クロックで実行するようにしたところ、 期待通り敵キャラのみ横にスクロールするようになった。 他の命令でもクロック数を厳密に仕様と合わせた。 クロック数を考えてプログラムされているゲームが他にもありそうだ。
「6502はパイプライン機構を持っている」
などと雑誌やWikiなどで書かれていることがあるが、これには疑問があって調べてみた。
結論から言うと、
6502はストール対策やフォワーディングなどのような本格的な命令パイプラインは実装されていないことが分かった。
実際には、演算命令など一部の命令で、最後のクロックでメモリアクセスしない場合、
演算と次の命令フェッチをオーバーラップすることで1クロック短縮する。
これをパイプラインだと言う人は言う、とのことのようだ。
NOP & JMP loop の波形
そこで右の波形を見ていただこう。
これは、NOP($EA)とJMP($4C)でループしているファミコンの波形を某氏から入手したものだが、
一見、NOPの次のJMP命令をオーバーラップで先行フェッチしているように見える。
しかしデータシートではNOPは2クロック、JMPは3クロックであり、
先行フェッチによるクロック短縮は観測されない。
これは命令フェッチの後に必ず第一オペランドがフェッチされる仕様によるもので、
@ NOP命令をフェッチ
A NOP実行(何もしない)& 次のアドレスをフェッチ
B JMP命令をフェッチ
C 下位アドレスをフェッチ
D 上位アドレスをフェッチ
ということが分かる。
Aで次のJMP命令を先行フェッチしているように見えるが、
推測されるべきはこれはdataレジスタへのフェッチで、
instructionレジスタへのフェッチではないということ。
なぜ推測かというと、ファミコンのCPUはカスタム品なので、
一般の6502にあるSYNC信号ピン(命令フェッチのときにHiになる)がないから。
これによりNOPの場合、命令フェッチはオーバーラップしていないと考えられる。
オーバーラップについては他にも演算と命令フェッチをオーバーラップすることで、 実行クロックを2クロックから1クロックに短縮できそうな命令が見られる。 しかし必ず第一オペランドをフェッチする仕様を優先したためか (これによって回路規模が削減された)、 6502には1クロックの命令は存在しない。
余談だが、6502の元となったといわれる6800のブランチ命令は4クロックだが、 6502は2〜4クロックと短縮されている。 これはアドレス加算において上位バイトへの桁上げが発生しない場合に有効であり、 パイプラインとは関係ないがクロック削減に寄与している。
いずれにしても、6502は命令パイプラインを持っていないと考える。 6502はパイプラインの夢を見る。固定長命令じゃないけど可能なのか?
ファミコンに使用されているカスタムチップRP2A03(6502+サウンド回路)はBCD演算(2進化10進表現)が使用できないというのは有名な話。 点数表示などでBCDがあれば表示ルーチンが簡単になるような気がするが。 ではなぜBCD演算が無いのか、以下のような理由を考えてみた。
実際のところRichoの開発者に聞くのが一番なわけだけど。
[RP2A03Gのチップ画像]を見ると、 6502部分のレイアウトはMOS TECHNOLOGY社のレイアウトと基本同じに見えるため、BCD演算回路もそのままのようだ。 [Chip Images : RP2A03] には「いくつかのレジスタが意図的に削除されているようだ」というようなことが書いてある。 BCD演算回路自体は存在するが、Dフラグが接続されていないという予想がいちばん近いのかも。 なぜ接続されていないのかは不明だが、 特許に関連する理由でBCD演算回路を使用不可にせざるを得なかったというのが有力? [Integrated circuit microprocessor with parallel binary adder having on-the-fly correction to provide decimal results]
ちなみにBCD回路が搭載されているオリジナル6502と、
CMOS版の各社65C02ではクロック数が異なり、
65C02の場合はDフラグがセットされているとき+1クロックとなる。
これは演算結果によるフラグ確定を次クロックにして動作周波数を上げようとしたものだと思われる。