20年ほど前に作ったFPGA版PC-8001が出てきたので動かしてみようと思ったところ、
S端子があるテレビが既にないことに気づきました。
黄色のビデオ入力はあるのでYとCを混合すれば...まあいいや。一旦置いておこう。
そういえば今はHDMI端子が付いてる安いFPGAボードがあるな。
ということでTang Nano 20kを買ってみました。
これだとBSRAMだけでPC-8001のメモリ全部を実装できるので、難しいSDRAMに触らなくても試せそうだったからです。


届いたので早速HDMIのサンプルを試してみました。

https://github.com/sipeed/TangNano-20K-example/tree/main/hdmi

ん?画面が出ない...
別のテレビで試すと、一瞬出て消えたり...
しかしモニタで試すとちゃんと出ました。
1080pではないので安物テレビでは出なかったみたいです。

testpattern.vをPC-8001のCRTCっぽく書き換え、fz80とROMイメージなどを入れるとN-BASICのプロンプトが出ました。
相変わらず初めてちゃんと動作すると嬉しくなるものです。

とここで、BSRAMをたくさん繋いでいるということは、
インストラクション専用32ビットバスを用意してPCからの4バイトが常に見えていれば一発でデコードできるし、
データ専用バスを16ビットにすれば一発で読み書きできる。
さらにパイプライン化すれば多くの命令は1クロック(スループット;以下同様)で実行できるのでは?
と思いつきました。

まあ、いろんな人が思いついても、面倒だから実装には進みませんよね。
自分は久しぶりにVerilog書きたくなったので実装してみました。

設計を進め、1命令中にリードとライト両方がある、例えばINC (HL)などは2クロック、
またRET(条件付きの場合は成立時)はリターンアドレスを得られるのが次のクロックなので2クロック、
その他は全て1クロックで実行できるようにしました。
実物のZ80に比べ同一クロックで4〜20倍速になります。
20倍は、例えばLD IX,(nnnn)です。これを1クロックで実行できます。
インデックスレジスタを使ってもクロック数が増えないのがメリットです。

実装を終え、N-BASIC起動時や学生のころ作ったオールマシン語のゲームなどを使ってデバッグしました。
ちょっと困ったのは直後のBIT命令を書き換えて任意のビット位置をテストするコード、いわゆる自己書き換えです。
パイプラインなので書き換えと同じサイクルでデコードされていなければいけないので、バグりました。
しかし、今時のプロセッサと違って、こういうコードも正しく動かなければいけません。

マニュアルを見て、BSRAMをWrite-throughモードで動かせば解決できることが分かり、それを推論できるコードに書き換えました。
IPジェネレータではなく推論で生成しているのは、$readmemhで初期化できる利点があるからです。
$readmemhってシミュレーションでしか使えないと思っており、
初期値を変更するには*.miファイルを変更してIPを再生成するしかないのかなーとchatGPTに訊いたら
$readmemh使えばいいじゃん、合成でも使えるよ!と言われました。
これは便利。

シミュレーションで正常になるまでデバッグしたものをFPGAに持っていくと、少しの間正常に動いても、フリーズする現象が出ました。
クロックをfmaxの半分以下に遅くすると長時間正常に動きます。
気になるwarningが出ていましたが修正の仕方がわかりません...
WARN  (PR1014) : Generic routing resource will be used to clock signal 'I_clk_d'
by the specified constraint. And then it may lead to the excessive delay or skew
もしかしてこれは関係なくてUSBからの給電が弱いから?と思って別電源をFPGA基板の+5V〜GND間に与えても、結果は変わりません。
書き込みはGOWIN EDAからはできなかったのでopenFPGALoaderで行っていますが、たまにエラーが出たり、
エラーは出なくてもHDL上は消灯しているはずのLEDがまれに点灯しっぱなしになることがあるのも気になります。

どうもよくわからないので安定性が解決するまで保留にします。
ここまでのソースを以下に置きました。

https://github.com/kwhr0/fz80wide

フォントは

https://github.com/dhepper/font8x8

を使わせていただきました。

サンプルはマンデルブロ、文字表示、矩形塗りつぶしのループです。
矩形の色が染み出しているのはPC-8001の仕様です。
13.5MHzで動かしており、マンデルブロは7秒くらいです。

(2025/12/07更新)
Tang Nanoは諦めて、試しにTang Console 60kを買いました。
ConsoleはUSB-Cが2つありますが、書き込みのためにつなぐのは"FPGA"ではなく"MCU"の方です。
# 最初間違えました

トップモジュールなどをConsole用に書き換えました。
ConsoleはGW5で、Nano 20kのGW2より速いですね。
43MHzまではタイミングエラーが出ません。

ただ、なぜかopenFPGALoaderによる書き込みがとても遅いです。
JTAGクロックはNanoのときと同じ6MHzなのですが、.fsファイルの大きさに比例、よりも倍以上かかります。
まあとりあえず使えるので...

(2026/01/25更新)
Tang Consoleの書き込みが遅い問題が解決しました。
ただ単に最新のファームウェアにアップしただけです...

https://nand2mario.github.io/tangcore/user-guide/installation

のTangCore Release Packageの先のtangcore-0.9.zipを展開したら、tangcore-0.9/cores/firmware-bl616下がファームウェアです。
あとはFirmware Installationのとおりにすればアップデートできます。


サンプルはxevious8001を衝突無視、サウンド有効にしたデモモードです。
VSync割込みでVRAMスワップとサウンドの処理をしています。
Consoleだとfmaxギリギリでも長時間安定して動作しました。

マンデルブロは2秒ちょっとになりました。
エミュレータのPC-8001で750MHzくらいに相当します。
ウェイトもDMAも使わない全力のZ80なら400〜500MHzくらいでしょうか。

(2026/3/22更新)

SPI液晶版

f1802のサンプルを作るために160x128の液晶を買ったとき、
PC-8001のセミグラフィックが160x100なので、そのうちxevious8001を表示してみよう、と思っていました。
Z80のコードにSPIで出力する部分を追加することもできますが、
使い回しできるようにVRAMを読み取り、SPIで出力する部分を独立して作ろうと思いました。
液晶は初期化シーケンスなどがあり、CPUが欲しいところ。
そこで、手持ちの中で最も小さいf1802で作ってみました。

今回はTang Primer 25kを使います。
これがGW2ではなくGW5であることを知り、買ってみました。
xevious8001はなぜかTang Nano 20kでは不安定だったので...
Tang Console 60kでもいいのですが、小さい方がP&Rも書き込みも速いので。

できる限り高速化したいので、Cではなくアセンブラで書きました。
まずは液晶を初期化して一色でフィルするテストプログラムを書き、
シミュレーションではCで書いたときと同じ正しいデータを送っていることを確認できたのですが、
FPGAに持っていくと表示されません。

もちろん、SPIのデータを送っている最中に次を送ろうとしていないことは確認しました。
違いといえば次のデータを送り始めるまでの間隔が短くなっていること...
もしかしてと思い余分なディレイを入れたところ表示されました。
Cだといい具合に遅いので表面化しなかっただけで、ディレイが必要な部分があると。

一通り書き上げていろいろ修正していき、まともに表示されるようになりました。

f1802は80MHzまで動きました。SPIの速度はCPUクロックの半分、40MHzになります。
ST7735のデータシートを見ると15MHzくらいまでらしいのですが、
ディレイが入っていればそれでも動いたので、その状態から余分なディレイを削っていきます。
結果、82fpsで表示することができました。



# ST7735は、フロントポーチ、バックポーチなどを削れば100fpsくらいまで行けます
xevious8001は、20fpsでちょうどいい速さになるように作っているので、4倍速になります。



肉眼で見ると偽色は無く、もっと滑らかです。
# ST7735自体はVSyncが取れるみたいですが、使用した基板では取れないので、完全なスムーススクロールではないですが
PLLの設定をいじってclk1を20MHzにするとちょうどいい速さで表示されるようになります。

ところで、SPI液晶でよく使われるテクニックに、変化があった部分だけを送る、というのがあります。
これも実装してみました。
lcd.asmの
DIF	equ	0
を1に変更すると差分転送になります。
多少凝ったことをする場合もCPUを使っていると楽です。

ところで、リソース使用状況を見るとf1802が妙に大きくなっていました。
調べると、Tang Primer 25kのFPGAはSSRAMが使えないことが判明。
COSMACの特徴である大量のレジスタがFFで実装されたためでした。

(2026/6/14更新)
Tang Primer 25kのFPGAはバージョンAだからSSRAMが使えない...
と思っていたのですが、買ったやつはバージョン上がったりしてないか?と
試しにバージョンBを指定したところ正常に動作しました。
f1802のリソース使用状況は以下のように変化しました。
version	Reg	LUT	ALU	SSRAM
A	313	377	23	0
B	57	204	23	4

(2026/8/23更新)
久しぶりに動かしてみたらHDMI版でもSPI版でも画面が真っ黒...
おかしい。GitHubに上げるときに動作確認したのに。
あれから変わったことといえば...
そうだ。GOWIN EDAを最新にしたっけ。
ということで、V1.9.12.01に戻してみると正常に動きました。
とりあえず、support@gowinsemi.comに報告しておきました。

(2026/10/11更新)
V1.9.12.04で正常に動作するようになりました。
ところで、6月に「試しにバージョンBを指定した」と書きましたが、
チップの印刷、3行目の5桁目(デートコードの次)がバージョンなのですね。
ルーペを使ってやっと見える...


https://github.com/kwhr0/fz80wide/tree/lcd

試す場合は、
Configuration->Dual-Pursose Pin->Use CPU as regular IO
にチェックを入れてください。