およそ20年ぶりにVerilogを書いて、4種類の小さなCPUを作り、
やっぱり自分はチマチマしたロジックが好きなんだなーと改めて思いました。
中高年になって、気力が続くかなど、こういう作業ができるのもあと何年もないかもしれない。
ならば、今のうちにもう少し書いてみよう。
ということで、6800と6809にトライします。
世の中にすでに存在するIPと同じものを作っても面白みがないので、
fz80wideのように、一つのインストラクション全体を一度にフェッチして多くの命令を1クロックで実行できるワイドバス版を作ります。
テストに使うアプリケーションは、
https://github.com/kwhr0/bm2-xevious
をベースにしました。
ビットマップの構成を横96ドット(12バイト)x縦160ドットとしました。
実行した様子はfz80wideのところにある動画のようになりますが、左右2ドットずつ切り詰めた表示になります。
FCC(Fuzix Compiler Kit), chibicc-6800, CMOC, SDCCのいずれでもビルドできるようにしています。
https://github.com/EtchedPixels/Fuzix-Compiler-Kit
https://codeberg.org/EtchedPixels/Fuzix-Compiler-Kit
https://github.com/zu2/chibicc-6800-v1
http://gvlsywt.cluster051.hosting.ovh.net/dev/cmoc.html
https://sdcc.sourceforge.net
変数名などに長い名前を避け、本来は不要なキャストを入れたりしています。
https://github.com/kwhr0/xevious8
実はこれ、去年FCCでビルドしたものをエミュレータで色々なプロセッサで走らせ、
見つけたコンパイラのバグを20個以上報告したり、プルリクエストを出したりするのに使いました。
なので今回は最初にFCCでビルドすることで、コンパイラのバグを疑わずにRTLのデバッグに専念できました。
(2026/6/28更新)
FCCのURLを更新しました。
また、xevious8をこのページ内の全てのプロサッサ、コンパイラの組み合わせでビルドできるように更新しました。
Makefile中の変数、MPUとCCを編集してビルドしてください。
8080
まず、ワイドバス版の基本構造を思い出すためにfz80wideをダウングレードして8080相当にしていきます。
6800と対決するためという目論見もあります。
もともとZ80として動いていたので、FCCで8080用バイナリを生成して試すと、あっさり動きました。
6800
このプロセッサは、数年前と違って良質なコードを生成するクロスコンパイラがあります。
元々6800を書いてみようと思ったのは小さくまとまれば結構使えるかもしれないと思ったからです。
ビッグエンディアンのプロセッサは初めて書いたのですが、
データバスが16ビットだとバイトサイズは上位8ビットを使うのが自然なことがわかりました。
TMS9995(元は9900)で、「バイトサイズはレジスタ上位8ビット」という仕様が腑に落ちました。
実装を進め、かかるクロック数は以下のようになりました。
多くの命令は1クロック(スループット;以下同様)
1命令中にリードとライト両方がある、例えばINC ,Xなどは2クロック
RTSはリターンアドレスを得られるのが次のクロックなので2クロック
複数のPSH/PULを伴う割り込みとRTI/SWI/WAIは数クロックかかります。
割り込みなしの状態までは順調だったのですが、割り込みを使うとFPGAで10分以上経ってからフリーズするという厄介なバグが。
仕方がないので全ての命令に割り込むベンチを書いてシミュレーションしました。
1命令ごとに全てのレジスタをPSH/PULしてRETIするのでめちゃくちゃ時間がかかります。
再現条件は、Nフラグを参照する条件分岐で割り込まれ、その直前がSTA命令でAレジスタとBレジスタの符号が異なる場合
というレアケースでした。
6800ではロードだけではなくストアでもフラグが変化することを失念しており、後から実装したのですが、これが不完全でした。
ロードとストアではパイプラインステージが異なるため、ストアで変化するフラグは一旦保持する必要があるのですが、そこで割り込まれた場合の不具合でした。
6809
6800を書いたらやっぱり6809も書いてZ80と対決だ!ということで書きました。
6309に挑戦しようかと思いましたが、ネイティブコードを吐くコンパイラがなく、無駄に大きくなるだけなので6809にしました。
まず、6809は今までのプロセッサと違い、命令が最大5バイトになるのでインストラクションバスを豪華64ビットに拡張しました。
基本6800と似ているのでこれをベースに、途中までは楽に書くことができました。
ところが、6800にはないインデクスモードの中の間接アクセス(パイプラインストールがある)、
対象アドレスをビットパターンで表すPSH/PUL(対象でないレジスタの分はクロックを消費しない)、
レジスタ間転送のTFR/EXG(通常の経路以外のセレクタが大量に必要)など、拡張部分がけっこうあります。
結果、6800よりだいぶ大きくなってしまいました。
命令の実行クロック数は6800と同じようになりますが、他に
PUL/TFR/EXGでPCを書き換える場合は2クロック、
インデクスモードで間接アクセスがあると+1クロック、
PSH/PULはポストバイトのビットが立っている数のクロック(全て0の場合は1クロック)になります。
FCCでは割とすんなり動いたのですが、CMOCでビルドした場合は色々とバグが出ました。
エミュレータではCMOCの方がだいぶ速いコードを吐くことを確認していたのですが、アセンブルリストを見てみても凝った命令列になっていました。
一例を挙げます。
6809はPCを変更する方法が多彩で、ブランチ、ジャンプ、リターンのほか、PULにPCを含む方法、TFR/EXGでPCを書き換える方法があります。
最後の方法はFCCでは使っておらず、CMOCで使っていたのでバグが見つかったりしました。
このような、FCCでは現れなかった命令やアドレシングがいくつかあり、バグ出しに有効でした。
(2026/5/31更新)
6502
68系と言っていいのか、6502もメジャーなチップなので作ってみました。
コンパイラは歴史があるcc65を使います。
とりあえずHomebrewでインストールし、xevious8をビルド。それをエミュレータで実行すると動作がおかしいところが。
そういえば以前cc65を使ったときにコンパイラのバグを回避するように書いたことを思い出します。
最新では直っているかもしれない。ということで
https://github.com/cc65/cc65
の最新のソースをビルドして試すと動作は正常になりました。
エミュレータからダンプを取ってテストデータができたので、次はVerilogを書きます。
6800のソースを6502に書き換えていって、6800には無い間接アドレシングは6809から持ってきたりしました。
6502で特徴的なのは、2バイトのデータアクセスです。
2番目のデータのアドレスは下位8ビットだけインクリメントし、上位8ビットは変わりません。
その辺りを整え、いくつかバグを取るとちゃんと走るようになりました。
クロック数については、6800に準じます。間接アドレシング(n,X)と(n),Yは+1クロックです。
回路規模は6800より小さくなりました。元々6502の開発経緯からしたら当たり前かもしれません。
メモリ使用量と速度に関して:
6502のハードウェアスタックは256バイトしかないので、cc65ではソフトウェアスタックを使います。
これが遅いので、関数内の自動変数をstatic扱いにする(リエントラント・リカーシブができなくなる)
-Clという涙ぐましいオプションがあります。
さらに-Oiオプションを付けて計測しました。
これでメモリ使用量は増えますが速度は6800/FCCと同程度となりました。
(2026/6/7更新)
最近、バグ報告していたものが修正され、FCCでも正常に走るようになりました。
コンパイルオプション-Oではメモリに入りきらないので-Osで計測しました。
(2026/6/21更新)
8070
マイナーですが、SC/MP IIIの型番です。
エミュレータを作ったことがある8ビットプロセッサということで、ついでに作りました。
インストラクションフェッチ時、大抵のプロセッサはフェッチしたあとPCをインクリメントしますが、
8070はフェッチ前にインクリメントする仕様です。
リセットでPC=0になりますが、最初に実行するのは1番地の命令というのが変わっています。
これをそのまま実装すると少し遅くなりそうな気がします。
幸い、ワイド版の実装ではPCからの4バイトが常に見えており、8070の命令長は最大3バイトなので、
PC+1〜PC+3の部分を使えばいいと気がつきました。
これでやっかいなプリインクリメントは不要になります。
他に、今までのプロセッサと違うところといえば除算命令があるところです。
これは汎用性があるので別モジュールにしました。
また、そこから完了が返ってくるまでウェイトする仕組みも入れました。
クロック数について:
多くの命令は1クロック(スループット;以下同様)
1命令中にリードとライト両方がある、ILD/DLDは2クロック
RET/LD PC,EA/XCH EA,PCは2クロック
除算は18クロックです。
実チップは1命令にかかるクロック数がかなり多いのですが、本実装は上記のとおりなので、まあまあ速いです。
8070は非力そうに見えて、16ビットの四則演算を持っています。
規模は除算回路も含むのでシンプルな割には少し大きめです。
結果
回路規模と最高周波数、ソース規模
Reg LUT ALU MULT fMax 行数
6800 152 774 119 58 <500
8080 167 853 54 57 <600
6809 263 1938 199 1 41 <700
Z80 345 1584 122 42 <1000
6502 124 604 104 62 <400
8070 237 929 128 1 48 <500
# Z80だけ未定義命令の実行、バスリクエストやウェイト機能があります
次にスピードとメモリ効率。
通常はVSync割り込みで滑らかにスクロールしますが、スピード対決なのでそれを無視し、
それぞれfMaxでxevious8をスタートさせてから最後の16面が終わるまでの時間を計りました。
最適化は全て-O相当です。
コンパイラ メモリ使用率 時間
8080 FCC 85% 57秒
6800 FCC 85% 25秒
6800 CHIBICC 80% 15秒
Z80 FCC 85% 53秒
Z80 SDCC 81% 25秒
6809 FCC 75% 28秒
6809 CMOC 77% 18秒
6502 FCC 93% 95秒
6502 CC65 94% 35秒
8070 FCC 84% 32秒
FCCは、複数のレトロプロセッサをサポートするコンパイラですが、それぞれの専用コンパイラには敵わないようです。
6800とZ80では倍ほど速さが違いました。
スピードに関しては、FCCだけなら6809が優勝だったのですが、chibicc-6800の登場で6800と互角になっています。
6800はシンプルな分fMaxが高い、というのも大きいですが。
メモリ効率に関しては6809が一番です。
一つの命令は長くなりがちですが、同じことをするのに命令数は少なく済んでいるということですね。
(2026/10/18更新)
GOWIN EDAのバージョン、FPGAのバージョン指定が入り乱れていたので、計測し直し、上記の表に反映しました。
各バージョンは以下のとおりです。
なお、使用リソースとfMaxはEDAの設定がデフォルトの場合です。
GOWIN EDA 1.9.12.04
FPGA B
FCC c323ddb 2026/10/18 6:10
CC65 6bd01e8a5 2026/10/17 22:47
CHIBICC 02fe266 2026/09/28 20:09
CMOC 0.1.100
SDCC 4.6.0
https://github.com/kwhr0/f8080wide
https://github.com/kwhr0/fz80wide
https://github.com/kwhr0/f6800wide
https://github.com/kwhr0/f6809wide
https://github.com/kwhr0/f6502wide
https://github.com/kwhr0/f8070wide