せっかちな人のための1ソースNESエミュレータ解説(6502編)

... というのを書こうとしていたらもうspoilerが出ていた。。めげずにVideo/Audioと続けたいところ。

TAS動画方面では日本でもよく知られるbisqwitが1ソース完結のNESエミュレータとその制作動画をアップロードして話題になっている。

このエミュレータはファイルからの入力しか受け付けないなど機能は制限されているが、サイクル正確なVideo/Audio/CPUエミュレーションを非常にコンパクトに記述している。
エミュレータをコンパクトに記述するために、全般にわたってハードウェアに忠実な実装になっている(VHDLVerilogによる実装と比較するのも興味深い問題だろう)。

CPU動作の分解とbittableによるマイクロコード記述

(この部分は↑のop.txtで既に解説されている)
nesemu1では6502エミュレーションをマイクロコードに近いアプローチで行なっている。つまり、1CPUサイクルごとに起きる可能性のある動作全てを列挙し、CPUの1命令はそれらの動作の組み合わせとして実現する。
(ここで利用されているマイクロコードは実際の6502マイクロコードとは異なっている。そもそも、NESのCPUは6502でなく6502風のIPコアでしかない。nesemu1の"The PLA of 6502 works on a slightly different principle"のPLAはマイクロコードROM(Programmable Logic Array)を指している。)
op.txtに可能な動作がすべて列挙されている。つまり、8bitアキュムレータ(= 変数)tへの代入や演算等。例えば、op.txtからINC命令(= レジスタAを1加算する)に相当する動作を持ってくると、

  • t = A
  • t = t + 1
  • A = t

の3つの動作となる。
nesemu1.ccでの該当行は、

...
        t("aa__ff__ab__,4  ____ -  ____     ", t &= A) // Many operations take A or X as operand. Some try in
...
        t("                         !  khnk ", t = u8(t + 1))  // inc,inx,iny,isb
...
        t("aa__aa__aa__ab__ 4 !____    ____ ", A = t)
...

が相当する。さて、t(文字列, 式)マクロの文字列部分が何を表しているかというと、ここは256+3bitの1bit定数の配列になっている。
256+3は、6502の8bit OPコード(256)と、内部的に特別扱いしている割り込み(3)の合計で、確かにこの文字列は33文字ある。
C++では1bit定数を記述するための良いリテラルは無いので、ここではBASE64風のエンコードを施し、デコード結果をenumにすることでコンパイル時にデコードされることを保証している。

        // Fetch op'th item from a bitstring encoded in a data-specific variant of base64,
        // where each character transmits 8 bits of information rather than 6.
        // This peculiar encoding was chosen to reduce the source code size.
        // Enum temporaries are used in order to ensure compile-time evaluation.
        #define t(s,code) { enum { \
            i=o8m & (s[o8]>90 ? (130+" (),-089<>?BCFGHJLSVWZ[^hlmnxy|}"[s[o8]-94]) \
                              : (s[o8]-" (("[s[o8]/39])) }; if(i) { code; } }

この256+3bitの1bit配列をINCのOPコードで引いていくと、上記の3行だけが1が引け、他の行では0になる。ここでは同時に立つビットに制約があることを利用して、テーブルを圧縮している。BASE64は本来1文字で6bitの情報しか表さない。
同様のアプローチはsykesのIOCCC2005エントリでも見られる。このエミュレータはCommodoreのPETエミュレータで、NESとは微妙に異なる6502をエミュレーションする。

こちらはbitcodeの転置を行なっていない。アドレスモードと動作で256x2のテーブルを持ち、(ネストした)3項演算子で必要な演算を選択している。