NES on FPGA 21:01 2010/10/08

dev CPU (6502カスタム)

 CPUの実装では命令の実行クロック数を合わせています。 多くのゲームではVBlankでタイミングをとっているため、 実行クロック数を合わせる必要は無いように思われたのですが、 一部のゲームではラスタスクロールを行っているものがあるためです。

 オリジナルは全てのクロックにおいてメモリアクセスが発生しますが、 今のところ必要なタイミングのみでのアクセスで問題ないようです。 また仕様外命令も存在しますが、今のところ動作したゲームでは確認できません。 ただし、仕様外命令を実装することで回路の削減が可能なら実装したいところです。

! W65C02Sの石やデータシートは、Western Design Centerから入手可能.

_
▼ Ver.1 なにはともあれ

 とりあえず動くものをと、全命令において命令フェッチ、実行、ストアの各ステージをごちゃまぜで記述。 SFL2VHDLでメモリオーバ、変換できず。そりゃね。
ということで、アドレッシングごとに多少の論理最適化やってみてようやく変換できたので合成、 デフォルトの面積最適化でクリティカル60ns。33MHzで動いて欲しいのでこれはちょっと遅い。
で、速度最適化、周期30.303nsで合成

Total equivalent gate count for design:  12,367

 ぎちぎちに最適化された6502のIPが同一条件下での合成結果で7,600ゲート程度だったので、 まずはこんなもんですか。拙速主義バンザイ。

_
▼ Ver.2 もっと良く見せて

 もう少し見栄えを良くしようかということで、 アドレッシングと実行、割り込みを別ステージに分けました。 スクラッチから8時間で完成。

Total equivalent gate count for design:  11,221
 ゲート数あんまへらねえなあ。

 とここでハイドライドスペシャルが途中でフリーズ。 プログラムカウンタ(PC)が$0012を指してる。 PCは$8000〜しか指さないものと思い込み、 デバッグのためPCが$0000〜$7FFFを指すと停止するようにしていた。 どうやらWRAM上にプログラムを書いているらしく、 JSRで$0012へ飛んでた。

_
▼ Ver.3 ぎちぎち

 根本的になにか設計方法を間違ってるような気がしてならない。 しかしこれ以上の最適化はますます記述の見通しが悪くなるばかり。 回路規模を減らす必要があればですが。

 やはり内部RAMを使ったマイクロコード方式にして、 アドレスデコーダをなくすべきか?

! いくら見やすいようにとはいえ変な設計。

_
▼ Ver.4 新天地へ

 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を停止させるような回路をつけておくと便利。 通常はスイッチを切っておけばよい。
[想定外命令セット圧縮結果]

_
▼ テキスト比較による検証方法 2008/10/17

 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命令前までの差分として現れている。

_
▼ クロック数を合わせよ 2008/10/20


敵キャラがずれずれ

 ギャラクシアンの動作確認で、本来なら敵キャラ全体が横にスクロールすべきところで、 ずれずれだった(Y座標35と97付近)。 これは、Xスクロールレジスタへのwriteタイミングが問題だと考えられたため、 その付近の命令を抽出したところ次のようなものだった。
クロック数
adrs 命令opcodeEmu誤HDL
E288 DECC6 E0  5 4
E28A DECC6 E0  5 4
E28C DECC6 E0  5 4
E28E DEY88  2 2
E28F BEQF0 03  2 2
E291 JMP4C 88 E2  3 3

 これを見ると、ライン40付近までDEC命令でカウントし、 0になったらXスクロールレジスタを書き換えているようだった。 注目すべきはDEC命令の実行クロック数がエミュとHDLで異なっていたこと。 本来なら5クロック要するところを4クロックで実行していたため、 予定よりも早くXレジスタへのwriteが行われてしまっていたようだ。

 HDLを修正し、5クロックで実行するようにしたところ、 期待通り敵キャラのみ横にスクロールするようになった。 他の命令でもクロック数を厳密に仕様と合わせた。 クロック数を考えてプログラムされているゲームが他にもありそうだ。

_
▼ 命令パイプラインはない! 2010/10/05

 「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はパイプラインの夢を見る。固定長命令じゃないけど可能なのか?

_
▼ 10進演算が無いワケ?

 ファミコンに使用されているカスタム6502にはBCD演算(2進化10進表現)が無いということは有名な話。 点数表示などでBCDがあれば表示ルーチンが簡単になるような気がするが。 ではなぜBCD演算が無いのか、以下のような理由を考えてみた。

  • 回路規模を削減するため
    ALUをFPGAに実装したときの場合だが、 BCD回路を削除すると120→96LCsに減り、クリティカルパスも24→20nsに減った。 BCDは意外とリソースを消費するらしい。

  • カスタムLSIの原価を抑えるため?
    BCD回路部分がオプション扱いで、できるだけ原価を抑えるためにこれを外した、という推測。

  • 高精度な演算を想定していない?
    科学技術計算や金融システムなどではBCDが重要な場合がある。 2進演算では、小数点以下の丸め誤差が発生してしまうためだ。 ファミコンではここまでの精度を求めなかったのだろうと思われる。

  • 将来的にBCD回路は削除対象になりそうだった?
    HDLによる設計なら簡単に回路を削除できるが、当時は紙でレイアウト設計していたらしい。 後々にプロセスが向上して、コストを抑えるためにBCDが削除対象になるなら、 初めから削除しておこう、という推測。 これによりBCDを使用したプログラムは作られなかった。

  • 実は不具合
    BCD回路は存在するがDフラグが接続されていない…。

 実際のところRichoの開発者に聞くのが一番なわけだけど…。 [Chip Images : RP2A03]には、 「いくつかのレジスタが意図的に削除されているようだ」というようなことが書いてある。 BCD演算回路自体は存在するが、Dフラグが接続されていないという予想がいちばん近いのかも。

 ちなみにBCD回路が搭載されているオリジナル6502と、 CMOS版の各社65C02ではクロック数が異なり、 65C02の場合はDフラグがセットされているとき+1クロックとなる。 これは演算結果によるフラグ確定を次クロックにして動作周波数を上げようとしたものだと思われる。


Copyright(C) pgate1 All Rights Reserved.