HOME

🎄 Hardware Description Language Advent Calendar 2023 16日目
@pgate1

Analogue Pocket で openFPGA ことはじめ

最終更新日 2023年12月16日 投稿日 2023年12月16日
Analogue社 のゲームボーイ&アドバンス互換機 『Pocket』のFPGAでLチカしていこう。

Pocket の他の互換コアで遊んでみたい? うん、それもあるけど…私はもっと openFPGA を知ろうと思う。 Lチカには正直、興味はないよ。だから見て確かめるんだ。 そうだね、これは自分のためだ。

はじめに

すっかりコンシューマゲーム高級互換機の雄となったAnalogue社は、 ファミコン『Nt mini』、スーファミ『SuperNt』、メガドライブ『MegaSg』そしてPCエンジン『Duo』や、 ついにはNINTENDO64『Analogue3D(仮称)』といった互換機を開発・販売・発表している。 特徴として高品質なプロダクトに加え、互換機能の実装にはFPGAを採用しているところ。

特に Pocket(アナログポケット、個称:あっぽけ)は、 発表当初からそのFPGAをユーザが現場でプログラム可能な感じになるとアナウンスされ、 私はそれを目的に Pocket を購入したと言っても過言じゃない。 その後、ユーザ向け開発用フレームワークとして 『openFPGA』が公開され、 有志によりすでに多くのゲーム機互換コアが移植されている。

2023年12月現在、この openFPGA を活用できるのは Pocket のみ。 予約祭りに乗じて運よく購入できた人は、 せっかく高価なものなのでその手の中にある Pocket でFPGA遊びにも興じてみるのもいいのかなって。

 Chronological Pocket.

必要なもの

openFPGA を使うための下準備

openFPGA は Pocket のユーザ向け開発用フレームワークで、 Pocket のFPGAとその周辺回路を使うための ドキュメントHDLサンプルが公開されている (別団体の OpenFPGA とは異なる)。 ここではLチカの下準備として、まずはサンプルがちゃんと動くか確認する。

ディレクトリを用意

テンプレートサンプル core-template を取得し、適当な場所に展開する。 そして、以下のようなディレクトリ構成を作成する。
dist/
 │
 ├─Assets/
 │  └─ex_platform/
 │     └─common/
 │
 ├─Cores/
 │  └─Developer.Core Template/
 │     ├─audio.json
 │     ├─bitstream.rbf_r
 │     ├─core.json
 │     ├─data.json
 │     ├─info.txt
 │     ├─input.json
 │     ├─interact.json
 │     ├─variants.json
 │     └─video.json
 │
 └─Platforms/
    ├─_images/
    │  └─ex_platform.bin
    │
    └─ex_platform.json

Assets/ex_platform/commonの中には通常、 FPGAコアから参照するデータ等を置く(Lチカでは使用しない)。

Cores/Developer.Core TemplateにはFPGAデータとそれに付随する情報を格納する。 *.json 類は core-template に含まれているものをとりあえずそのままコピー。 FPGAデータはcore-template/output/bitstream.rbf_rをコピーしてくる。

Platformscore-template/dist/platformsからそのままコピー。

USB経由でmicroSDカードにアクセス

もちろんSDカードライタで直接microSDカードにデータを書き込んでもいい。

Pocket でmicroSDカードにアクセスする設定は、 AnalogueOS メニューから[Tools]→[Developer]→[USB SD Access]にチェックを入れる。 Pocket にmicroSDカードをセットし電源を入れ、 PCとUSB接続するとエクスプローラからアクセスできるようになる。

エクスプローラからアクセスしたmicroSDカードの中身は、 このような感じにフォルダが作成されている。

上で作成した AssetsCoresPlatforms をmicroSDカードのルートに上書きコピーし、 右のBボタンやら×ボタンやらの位置のボタンでSDアクセスモードからexitする。 exitしたらUSBケーブルを徐に抜く。

コアを実行

FPGAコアを動かすには、 AnalogueOS メニューから[openFPGA]→[Example Platform]→[Run]を選択。 以下のようにグレーの画面が表示されていればサンプル実行は成功だ。

終了する場合は、下方中央の AnalogueMenu ボタンを押し、[Quit]→[Confirm]で終了。

Pocket でLチカ、やっていこう

Lチカと言っても、Pocket にはデバグ用LEDは付いてないので、 画面表示に代えてこれを行う。 そう、LCDチカなんだよ。
  1. サンプルHDLを書き換え
  2. FPGAデータを生成
  3. Pocketに入れて実行

1. サンプルHDLを書き換え

core-template/src/fpga/core/core_top.vの597行目に、 以下の598~602行目を追記し保存する。 HDLアドカレの貴重なHDL要素だからね。

core_top.v
            if(y_count >= VID_V_BPORCH && y_count < VID_V_ACTIVE+VID_V_BPORCH) begin
                // data enable. this is the active region of the line
                vidout_de <= 1;

                vidout_rgb[23:16] <= 8'd60;
                vidout_rgb[15:8]  <= 8'd60;
                vidout_rgb[7:0]   <= 8'd60;
                // ここから追記
                if (x_count < 60 || y_count > 200) begin
                    vidout_rgb[23:16] <= 8'd255; // Red
                    vidout_rgb[15:8]  <= 8'd00;  // Green
                    vidout_rgb[7:0]   <= 8'd00;  // Blue
               	end
                // ここまで追記
            end

2. FPGAデータを生成

VerilogHDL を論理合成してFPGAデータを生成するために、 IntelのFPGA開発環境である Quartus Prime を使う。 なお対象FPGAデバイスは Cyclone V で、 今回は openFPGA で使用されている Ver.18.1 を選択するけど、 Cyclone V をサポートしてるバージョンなら新しくてもいいかも。

Quartus Prime 18.1 Lite Edition をダウンロードしインストール。 Lite Edition はライセンスファイルの取得や設定が不要で使用できる。

Quartus のインストールが完了したら早速 Quartus を起動し、 サンプルのプロジェクトファイルcore-template/src/fpga/ap_core.qpfを開く。 そして、メニューの[Processing]→[Start Compilation]を実行。

論理合成・配置配線・FPGAデータ生成が完了したら、core-template/src/fpga/output_files/ap_core.rbfが更新されているはずだけど、 実はそのままでは openFPGA で使用できず、ビットリバース処理が必要。 なんだかレガシーな理由で .rbf のビットが逆順になっているらしい。

ビットリバースは自分でプログラム作ってやってもいいんだけど、 openFPGA で作成された最初のコアであるspacemen3/PDP-1 の Source code(PDP-1-1.1.0.zip)内にあるものを流用できる。 PDP-1-1.1.0/src/output_files内の reverse_bits.exerun.bat を、 ap_core.rbf と同じ場所の core-template/src/fpga/output_filesにコピー。 以下のように、run.bat の1行目以外は適宜 rem でコメントアウトするなりしてくれればいい。

run.bat
reverse_bits.exe ap_core.rbf bitstream.rbf_r
rem copy /y bitstream.rbf_r "H:\Cores\Spacemen3.PDP1\bitstream.rbf_r"
rem copy /y bitstream.rbf_r "..\..\dist\Cores\Spacemen3.PDP1\bitstream.rbf_r"

run.bat を実行すると bitstream.rbf_r が生成される。

3. Pocketに入れて実行

Pocket を起動しUSBケーブルでPCと接続後、 bitstream.rbf_rCores/Developer.Core Template/bitstream.rbf_rに上書きしSDアクセスモードからexitする。 サンプルを実行した時と同様に AnalogueOS メニューから[openFPGA]→[Example Platform]→[Run]から実行し、 赤のLが表示されれば成功。 目がチカチカするね。

もし赤のLが出ない場合は、microSDカードのフォルダSystem内にあるキャッシュファイル(*.bin)を全て削除してリトライするといいかも。

短い旅だったけど、これで君もアナログポケットマスターだね。



Intermission.

ダウンロードケーブルを使う

Lチカだけじゃなく、FPGA周辺の外部メモリやmicroSDカードアクセス等も試したい。 トライ&エラーすることを考えると、FPGAに直接FPGAデータ(.sof)をダウンロードケーブル経由でダウンロードし実行した方が早い。 ダウンロードケーブルを接続するJTAGヘッダピンについては本体裏の下部にあって、 Developer Pocket(色はグレー)はゴム蓋を外せばアクセスできるようになっている。

JTAGピンヘッダ \コンニチハ/

USB Blaster ことダウンロードケーブルは、Intel($300)かTerasic($69)のものを推奨する。 低価格低品質の互換ケーブルは動作しなかったときの問題の切り分けが面倒だからね。

通常の Pocket にも同様にJTAGピンヘッダは存在してて、裏蓋の特殊ネジT6星型を外せばアクセスできる。 ただ、裏蓋を開けるとバッテリーが鎮座してることもあって、そのままの使用は避けたいところ。 それでも使いたい諸賢においては、裏蓋に穴をあけるか3Dプリンタで穴の開いた裏蓋を作成してほしい。
引用: Digging into the Analogue Pocket, a portable FPGA game system(Youtube)
引用: Analogue Pocket Teardown(Youtube)

通常の Pocket でもダウンロードケーブルが使用できるようなカスタム裏蓋が発売されることを期待してる。

ちょっとした問題が…。 たまたまTBSラジオ(FM90.5MHz)を付けていたら、FPGAデータダウンロード時にひどいノイズを受信してしまう。 文化放送(FM91.6MHz)には影響ないみたい。 ノイズ源どこ…。

Lチカのちょっと先

他のFPGAボードで動かしていたポリゴンデモを移植してみた。 解像度を 640 x 480 に変更し、キー入力で操作できるようにした。

Analogue Pocket(あっぽけ)でポリゴンデモ動いた
VGA出力回路がそのまま使えるんだね pic.twitter.com/LnOuwTZejw

— かんな丸⁧!!⁨ (@pgate1) October 11, 2022

デバッグには観測性が必要なので、 回路内ロジックアナライザや仮想JTAG経由のデータ送受信などでFPGA内データを観測する。

ダウンロードケーブルを使っての開発のコツとしては、 FPGAコアに関連する環境である各種パラメータを記述した *.json と読み込ませるデータ等をあらかじめmicroSDカード内に配置しておき、 openFPGA から仮のFPGAコアを起動した状態で Quartus Programmer からFPGAデータを書き込むこと。 こうすることで、書き込んだFPGAデータは仮のFPGAコアが起動した環境で動作する形になる。 言い換えると、*.json の記述とFPGAデータのパラメータ等が異なるとうまく動作しない。 例えば、解像度が異なる設定の video.json を読み込んでいる状態でFPGAデータを書き込んでも画面が期待通りに表示されないなど。

openFPGA へのさらなる旅路

キミが openFPGA をもっと知りたいと言うのなら、一緒にこの先へ進んでみるかい? ざっくりとした Pocket 内のFPGAと周辺デバイス構成を示すよ。

openFPGA の構成としてはプライマリFPGA(Cyclone V E、5CEBA4F23C8N、約18kALMs≒約49kLEs)の他に、 画面スケーリング処理等を行うセカンダリFPGA(Cyclone 10 LP、10CL016YU256C8G、約15kLEs)と、 AnalogueOS機能を実行するマイコン(PIC32MX)が搭載されている。 ユーザが主に書き換え可能なのはプライマリFPGAの Cyclone V。 openFPGA のドキュメントにある図では、プライマリFPGAはSOCRATES FPGA、 セカンダリFPGAはARISTOTLE FPGAと記載がある。 以下プライマリFPGAを単にFPGAとして説明する。

実際の基板はこんな感じ。

引用: Analogue OS
openFPGA の経緯については次のサイトが詳しい。

参考: 今話題の「openFPGA」とは何か? 中華エミュ機とはまったく異なるその実力と魅力

FPGA

FPGAには、APF(Analogue Platform Framework)をベースとして、 その中に AnalogueOS とやり取りするブリッジ機能と共にユーザコアを実装する。

ちなみに、Pocket のFPGAとほぼ同等のものが Terasic社のFPGAボードDE0-CV (Cyclone V E、5CEBA4F23C7)に搭載されているので、 ユーザコアをDE0-CVで開発し Pocket に移植、という手が使える。 ただし Pocket のFPGAの方がスピードグレードがワンランク下なので、 動作周波数をメットさせるのに苦労するかもしれない(した)。 …残念ながらDE0-CVは2023年11月をもって生産終了したんだけどね。

ちなみにちなみに、Pocket 搭載FPGAと同じ型番のFPGAが、 同社の『SuperNt』や『MegaSg』にも使用されていたことと、 AnalogueOS機能実行には ARMプロセッサ内蔵の Cyclone V SoC ではなく別にPICを置いたのは、 このFPGAをボリュームディスカウントで購入したのかな?と勝手に妄想したり。 でもさすがに携帯機で Cyclone V SoC 使うのは消費電力や熱対策なんかが大変なことになるか。 そしてすでにリリースされた『Duo』ではどんなFPGAが使われてるか「analogue duo teardown」で500年くらいググってる。

周辺回路

FPGA周辺回路として使用可能なものを紹介する。 回路図が公開されていないのが残念だけど、サンプルのピンアサインファイルを流用すれば使えるのでまぁいいや。 core_top.vのポート宣言をベースに解説する。

GBカートリッジコネクタ

GB互換機を作る予定はないので省略。

赤外線通信

何かに使えそうではある。
core_top.v
// infrared
input   wire            port_ir_rx,
output  wire            port_ir_tx,
output  wire            port_ir_rx_disable, 

GBAリンクポート

GBA互換機を作る予定はないので省略。

PSRAM

16MByteの PSRAM が2つ使える。 型番:AS1C8M16PL-70BIN、16bit x 8M word x 2。 CE0とCE1を同時にenabledしないように気を付けろ、とのこと。
core_top.v
// cellular psram 0 and 1, two chips (64mbit x2 dual die per chip)

output  wire    [21:16] cram0_a,
inout   wire    [15:0]  cram0_dq,
input   wire            cram0_wait,
output  wire            cram0_clk,
output  wire            cram0_adv_n,
output  wire            cram0_cre,
output  wire            cram0_ce0_n,
output  wire            cram0_ce1_n,
output  wire            cram0_oe_n,
output  wire            cram0_we_n,
output  wire            cram0_ub_n,
output  wire            cram0_lb_n,

output  wire    [21:16] cram1_a,
inout   wire    [15:0]  cram1_dq,
input   wire            cram1_wait,
output  wire            cram1_clk,
output  wire            cram1_adv_n,
output  wire            cram1_cre,
output  wire            cram1_ce0_n,
output  wire            cram1_ce1_n,
output  wire            cram1_oe_n,
output  wire            cram1_we_n,
output  wire            cram1_ub_n,
output  wire            cram1_lb_n,

SDRAM

64MByteの SDR-SDRAM 。 型番:AS4C32M16MSA-6BIN、16bit x 32M word。
core_top.v
// sdram, 512mbit 16bit

output  wire    [12:0]  dram_a,
output  wire    [1:0]   dram_ba,
inout   wire    [15:0]  dram_dq,
output  wire    [1:0]   dram_dqm,
output  wire            dram_clk,
output  wire            dram_cke,
output  wire            dram_ras_n,
output  wire            dram_cas_n,
output  wire            dram_we_n,

SRAM

256kByteの非同期SRAM。 型番:AS6C2016-55BIN、16bit x 128k word。
core_top.v
// sram, 1mbit 16bit

output  wire    [16:0]  sram_a,
inout   wire    [15:0]  sram_dq,
output  wire            sram_oe_n,
output  wire            sram_we_n,
output  wire            sram_ub_n,
output  wire            sram_lb_n,

RFU

RFU用のポート。 要は Reserved for Future Use.
core_top.v
// RFU internal i2c bus 

inout   wire            aux_sda,
output  wire            aux_scl,

ビデオ出力/オーディオ出力

ビデオ出力はVGA信号同様のフォーマットで出力すればプラットフォームのスケーラーが合わせてくれる。 video.jsonの記述とも合わせること。 また、複数の解像度を設定すれば、コアから選択できる。 LCDの最大解像度は、スペックとしてはGBの10倍にあたる 1600 x 1440 だけど、 openFPGA として設定できるのは今のところ 800 x 720 まで。 上位モジュールの APF で 24bitRGB を 12bitDDR に変換している。

参考:video.json definition

オーディオ出力は 16bit ステレオの 48kHz が出せて、 I2Cで通信ということでAudioDACチップが載ってる。

core_top.v
// video, audio output to scaler
output  wire    [23:0]  video_rgb,
output  wire            video_rgb_clock,
output  wire            video_rgb_clock_90,
output  wire            video_de,
output  wire            video_skip,
output  wire            video_vs,
output  wire            video_hs,
    
output  wire            audio_mclk,
input   wire            audio_adc,
output  wire            audio_dac,
output  wire            audio_lrck,

ブリッジバス

AnalogueOS 上でのコマンド送受信によりmicroSDカードのデータ読み書きや、 コア動作の制御を行う。 詳しくは下で説明。
core_top.v
// bridge bus connection
// synchronous to clk_74a
output  wire            bridge_endian_little,
input   wire    [31:0]  bridge_addr,
input   wire            bridge_rd,
output  reg     [31:0]  bridge_rd_data,
input   wire            bridge_wr,
input   wire    [31:0]  bridge_wr_data,

コントローラ入力

Pocket のボタンとしてはcont1_key[15:0]に割り当てられている。 各キーのビット割り当てについてはコメントの通り。 Pocket 用の Dock と、例えば DualShock4 をペアリングすれば、 L2 R2 等も使えるのでは。使えるといいな。

APF 内 io_pad_controllerモジュールでキー入力値をSPIで取得し、 各コントローラレジスタにセットしている模様。

core_top.v
// controller data
// 
// key bitmap:
//   [0]    dpad_up
//   [1]    dpad_down
//   [2]    dpad_left
//   [3]    dpad_right
//   [4]    face_a
//   [5]    face_b
//   [6]    face_x
//   [7]    face_y
//   [8]    trig_l1
//   [9]    trig_r1
//   [10]   trig_l2
//   [11]   trig_r2
//   [12]   trig_l3
//   [13]   trig_r3
//   [14]   face_select
//   [15]   face_start
//   [31:28] type
// joy values - unsigned
//   [ 7: 0] lstick_x
//   [15: 8] lstick_y
//   [23:16] rstick_x
//   [31:24] rstick_y
// trigger values - unsigned
//   [ 7: 0] ltrig
//   [15: 8] rtrig
//
input   wire    [31:0]  cont1_key,
input   wire    [31:0]  cont2_key,
input   wire    [31:0]  cont3_key,
input   wire    [31:0]  cont4_key,
input   wire    [31:0]  cont1_joy,
input   wire    [31:0]  cont2_joy,
input   wire    [31:0]  cont3_joy,
input   wire    [31:0]  cont4_joy,
input   wire    [15:0]  cont1_trig,
input   wire    [15:0]  cont2_trig,
input   wire    [15:0]  cont3_trig,
input   wire    [15:0]  cont4_trig

microSDカードからのデータ読み込み

microSDのデータ読み込みは、APF のブリッジ経由で行う。 ここではSDRAMもしくは内蔵RAMへのデータリードを説明する。

microSD内のデータファイルにはdata.jsonでターゲットIDや、 ファイルアクセスオプション等を指定しておく。 こうすることでFPGAコアロード時に APF に対して、 ブリッジからどのデータファイルにアクセスされるかといった情報がセットされる。

参考:data.json definition

以下の記述例では、BIOSファイル(scph5500.bin)はターゲットID 0x20 としてコアロード時に自動セットする。 ROMイメージファイルはコアロード前に選べるようにし、ターゲットID 0x40 として最大約783MByteまでをサポートする。
data.json
{
    "data": {
        "magic": "APF_VER_1",
        "data_slots": [
		{
			"name": "BIOS",
			"id": "0x20",
			"required": false,
			"parameters": "0x008",
			"deferload": true,
			"filename": "scph5500.bin",
			"size_exact": 524288,
			"address": "0x00000000"
		},
		{
			"name": "ROM",
			"id": "0x40",
			"required": true,
			"parameters": "0x008",
			"deferload": true,
			"extensions": ["bin", "img", "exe"],
			"size_maximum": "0x40000000",
			"address": "0x00080000"
		}
	]
    }
}

ユーザコアからブリッジへ、SDRAMへのリードリクエストを出す場合の記述は以下。 ターゲットファイルは APF のスロットにセットされているので、 スロット内アドレスを指定して、SDRAMの指定アドレスに指定バイトリードする。
core.sflp
	stage_name apf2sdram { task do(target_id_reg, target_slotoffset_reg, target_bridgeaddr_reg, target_length_reg); }

	stage apf2sdram {
		first_state st1;
		state st1 par{
			// 信号の同期化待ち
			reg_wr wait_count<4>;
			wait_count++;
			if(/&wait_count) goto st2;
		}
		state st2 if(sdram_ack){
			// リードリクエスト
			set_sdram_reg := 0b1;
			goto st3;
		}
		state st3 if(target_dataslot_read){
			set_sdram_reg := 0b0;
			goto st4;
		}
		state st4 if(^ram_reloading){
			goto st1;
			finish;
		}
	}

	// SDRAMへのリードリクエスト起動
	// ターゲットファイルID、スロット内アドレス、SDRAMアドレス、リードバイトサイズを指定
	generate apf2sdram.do(0x0020, 0, 0x01000000, 524288);

コア内の内蔵RAMへのリードリクエストの場合はこう。 同様にスロット内アドレスを指定して、ここでは4kByteの内蔵RAMにリードしている。 任意のリードバイトサイズの時は、リードエンドで ram_reloading が 0 となり、 残りの内蔵RAMデータはガベージになる。
core.sflp
	ram_4k file_ram; // 内蔵RAMインスタンス(32bit x 1024word)

	stage_name apf2bram { task do(target_id_reg, target_slotoffset_reg, target_length_reg); }

	stage apf2bram {
		first_state st1;
		state st1 par{
			// リードリクエスト
			set_bram_reg := 0b1;
			goto st2;
		}
		state st2 if(target_dataslot_read){
			set_bram_reg := 0b0;
			goto st3;
		}
		state st3 if(bram_word_wr | ^ram_reloading){
			reg_wr adrs<10>;
			file_ram.write(adrs, bram_wdata);
			adrs++;
			if(/&adrs) goto st4;
		}
		state st4 if(^ram_reloading){
			file_ram.read(0);
			goto st1;
			finish;
		}
	}

	// 内蔵RAMへのリードリクエスト起動
	// ターゲットファイルID、スロット内アドレス、リードバイトサイズを指定
	generate apf2bram.do(0x0040, file_adrs<31:12>||0x000, 4096);

いずれも、リードバイトサイズがファイルサイズ上限境界を超えている場合、 リードリクエストは APF で失効されるので注意。

ユーザコアからのリードリクエスト信号はシンクロナイザを通してブリッジへのリクエストに変える。 ブリッジから APF へのリードリクエストが通り、 microSDからリードされたデータがブリッジ経由で1.4us毎に32bit Writeされてくる。

core_top.v
	always @(posedge clk_74a) begin

		// ユーザコアからのReadリクエストを処理
		case(reload_state)
		0: begin
			// コアからのReadリクエスト待ち
			if(set_sdram_r | set_bram_r) begin
				// SDRAMへのReadリクエストの場合
				if(set_sdram_r) set_sdram_in <= 1;
				// コア内蔵RAMへのReadリクエストの場合
				if(set_bram_r) set_bram_in <= 1;

				ram_reloading <= 1;

				// ブリッジへ渡す各データ情報
				target_dataslot_id <= target_id;
				target_dataslot_slotoffset <= target_slotoffset;
				target_dataslot_bridgeaddr <= target_bridgeaddr;
				target_dataslot_length <= target_length;
				target_dataslot_read <= 1;

				reload_state <= 1;
			end
		end
		1: begin
			// wait for ack
			if(target_dataslot_ack) begin
				target_dataslot_read <= 0;

				reload_state <= 2;
			end
		end
		2: begin
			if(target_dataslot_done) begin
				set_sdram_in <= 0;
				set_bram_in <= 0;
				ram_reloading <= 0;

				reload_state <= 0;
			end
		end
    		endcase

		// ブリッジからのデータWriteを捌く
		sdram_word_wr <= 0;
		bram_word_wr <= 0;
		if(bridge_wr) begin
			casex(bridge_addr[31:24])
			8'b000000xx: begin
				// SDRAMへのWrite信号
				if(set_sdram_in) sdram_word_wr <= 1;
				// コア内蔵RAMへのWrite信号
				if(set_bram_in) bram_word_wr <= 1;

				sdram_word_wrmask <= 2'b00;
				sdram_word_addr <= bridge_addr[25:2];
				sdram_word_data <= bridge_wr_data;
			end
			endcase
		end

	end

microSDカードへのデータ書き込み

調査中。

画面解像度の変更

ゲームによって 256 x 240 or 512 x 240 だったり、 320 x 240 or 640 x 480 だったりするので、 適切な解像度を設定して画面表示をちゃんとする。 調査中。

指定したファイルの関連ファイルを APF にセット

コアロード前に指定したファイルに関連するファイル(セーブデータなど)を読み込むための data.json の書き方があるらしい。 調査中。

初代PlayStation は Pocket で動くのか?

他のFPGAボードで作ってた PlayStation on FPGA を、 試しにDE0-CVに移植してみたら動いちゃったので、 某FPGAクローン機開発界隈にハッパをかける意味もあって、 Pocket に移植してみた。 FPGAリソースが限られているため、サウンド回路はオミットしている。 もしかしたら内蔵サウンド回路を使ってないタイプのCD-DA、CD-XAは鳴らせるようにできるかもしれないけど。

外部メモリは上手く使いこなせた方がいい

PlayStation用のメインメモリ(2MByte)とVRAM(1MByte)としてSDRAMを共用している。 といってもSDRAMのアクセス制御をさぼっていて、拙速でCPUとGPUの動作を排他制御しているので、 ゲームプレイが10fpsくらいになってしまっている (バスIPを使えってことなのかもしれない)。 このあたり、CPU内GTEのジオメトリ計算処理と、GPUレンダリング処理を並列で実行できるようにして、 SDRAMのアービトレーションができれば、もう少しフレームレートを上げられるはず。 ちなみにSDRAMのデータバスは16ビットのため、32ビットシステムで使用するためにバースト長2にて使用している。

SRAMはサイズが小さいのでPSメモリーカードデータ置き場として使えそう。 あとPSRAMが2個あるということは、これを描画フレームバッファとして使えということなのか。

ゲームのROMデータを読み込み

実は『リッジレーサー』のプログラムやグラフィックデータはまとめて4MByte以下のため、 まるっとSDRAMに置けて実行できてしまう。 しかし『トバルNo.1』や『アインハンダー』といった多くのPlayStatoinゲームのROMイメージデータは、 ムービーデータを含んでいるせいか数十~数百MByteあるのが普通。 もちろん、まとめてSDRAMに置くことはできず、 PlayStationコアのCD-ROMコントローラが要求するタイミングで読み込む必要がある。

ROMイメージデータの置き場所としては実質的にmicroSDしかなく、 またAnalogueOS管理内に大きなメモリ領域があるわけではなさそうなので、 数百MByteのデータを一度に全て読み込むことはできない。 そのためFPGA内に自前で適切なサイズのバッファを用意し、 その分だけブリッジに対してリードコマンドをリクエストする。 microSDからのデータは、ある程度のサイクルをかけて送られてくる。

バッファについては、一度に読み込むデータのサイズにより適切なバッファサイズを用意する。 一度のリードコマンドでできるだけたくさんのデータをリードする方がウエイトがかからず全体的なデータ読み込み時間を短くできる。 逆にバッファサイズが小さいとリードコマンドの回数が多くなり、 その分だけウェイト回数が多くなり全体的なリード時間が長くなってしまう。 計測したところバッファサイズとリード速度は次のようになった。

microSDカードから16MByteを複数回に分けてリードしたテスト結果:

バッファサイズ リード回数 リード時間 リード速度
2 kByte819226 sec630 kByte/sec
4 kByte409620 sec820 kByte/sec
8 kByte204815 sec1090 kByte/sec
16 kByte102411 sec1490 kByte/sec
32 kByte51210 sec1630 kByte/sec

FPGAの内蔵RAMをバッファとして使用してるけど、 PlayStationコアではリソースが厳しく4kByte程度しか確保できなかった。 これだと、CD-ROMコントローラからの1回のシーケンシャルリード要求が2,340Byteのため、 毎回microSDカードへアクセスしてしまうことになる。 そこで、APF 内で使われてる謎の内蔵ROM領域(更新日時等が記録されるらしい) をコメントアウトしても動いたので、バッファを4kByteから8kByteにできた。 これでmicroSDカードアクセスの頻度が1/3になって、 PSロゴからSQUAREロゴが表示されるまでほんの少し短くなった気がする。 …というのがファームウェアバージョンbeta-7までの話。 この時はバッファサイズ8kByteでもリード速度250kByte/secしか出てなかったので効果があったんだけどね。

ファームウェアバージョンが1.1に上がってmicroSDカードアクセスが平均5倍ほどに高速化したため (上の表はこの1.1のもの)、 バッファサイズが4kByteでも8kByteでも変わらなくなったので今は4kByteでやってる。 ちなみに、SDHCでもリード速度約1MByte/sec出ているので、 PlayStation実機のCD-ROMリード速度である300kByte/secは満足している。

一応、初代プレステが動くっちゃ動いた

村長「こちらが『GB互換機をPlayStationにする魔法』でございます」
某エルフ「いいじゃん」
村長「しかしPocketとPlayStationで検索しても、ポケステしか出てきません」
某エルフ「PocketStationって名付けなくてよかったよね」

(合成のフリーマン ~FPGAの魔法~)

PlayStation on FPGA feat. Pocket:
あっぽけことAnalogue PocketでトバルNo.1をプレイできるようになった。まだCPUとGPUの並列実行ができてないのでスローだけど、ちらつきは少ないかな。まぁほぼ生ポリみたいな感じなので。 pic.twitter.com/JmS1UY2ujA

— かんな丸⁧!!⁨ (@pgate1) January 19, 2023

PlayStation on FPGA feat. Pocket:ちらつきの低減と、CD-ROMコントローラの機能追加、横512ピクセルへの対応で、デュープリズムやサガフロンティア2を動かせました! pic.twitter.com/skdXEh7d9D

— かんな丸⁧!!⁨ (@pgate1) January 29, 2023


動画後半の『アインハンダー』はちらついてしまい目チカがひどいけど、 現状はちらつかないよう改善できた。 まぁ、遊べないこともない…?

プレイできる程度に移植はしたものの、やはり外部メモリの使い方がへたっピでFPSが出ない。 バースト転送・バッファ・複数リクエストの処理など、 メモリの使い方を制する者がFPGAを制すると言っても過言ではない。 あと複数クロック。それからタイミング制約。

しかし、Pocket 向けの中途半端なPlayStationコアをリリースしたあと、 界隈の焚き付けに失敗し、結局は自分でまともなものを出さざるを得ないような雰囲気になってる。 これが期待値のアンダーシュートだよ…。

Fitting report.

ターゲット 使用率 内蔵RAM DSP Fmax
DE0-CV92%79%98%49.8 MHz
Pocket93%99%98%47.5 MHz
Pocket の方が APF の分だけロジックが増えてるのと、 4kByteのバッファを追加してるので内蔵RAMがもうギリ。 要求周波数の50MHzに届いてない。

ネタ

スーファミ互換機能の移植

SNES on FPGA もDE0-CVで動いてるので、そのうち移植したい。 できればステートセーブ&ロードも実装したりして。 そのためにはブリッジ経由でのmicroSDカードへのデータ書き込みについて要調査。

寝床

公式のクリアケースは強固過ぎて飾る用だね。 2DSLLのポーチがサイズ的にちょうど良い。

あっぽけの寝床にちょうどよいポーチ見っけた pic.twitter.com/p36WMeNeoF

— かんな丸⁧!!⁨ (@pgate1) January 23, 2023

非公式公式拡張カートリッジ

Pocketのメモリが足りない!?
そんなときはコレを…こうじゃ
これで64MbitのSRAM追加なのじゃ! pic.twitter.com/G74uiVP3Op

— かんな丸⁧!!⁨ (@pgate1) January 31, 2023
NintendoDS向けに発売された『ニンテンドーDSブラウザー』には、 『DSメモリー拡張カートリッジ』が同梱されている。 特に初期ロットのものはGBAカートリッジ形状のため、Pocket にセット可能。 チップはEM64と書かれたものが搭載されていて、64MbitのSRAMらしい。 ただ、初期化処理をしないと拡張メモリエリアにアクセスできない仕様なうえ、 カスタム品なので仕様も見つけられず。 完全にネタ画像となってしまっている。

また『DS振動カートリッジ』についてはすでに openFPGA の GBC/GBAコアでサポートされていて、 振動対応ゲームプレイ時にちゃんと振動する。 これも初期のGBAカートリッジ形状のものが Pocket に無理なくセット可能。

MIDI機器との連携

AnalogueからはPocket用にMIDI系のケーブルが出ているので、 上手く使えばFPGAからMIDI信号を生成してMIDI音源モジュールを使えないかなと妄想。

ロストロジック≠ノスタルジック

Analogue Pocket(非公式)で同人GBソフト(非公式)を遊ぶっていう、 もはやアーキテクチャのみが引き継がれてる郷愁的未来感。 ロストロジックしないノスタルジック。

令和のGBソフト『NEKO Can DREAM』が到着したので、Pocket開発の合間に遊ぶ!猫分儀スミレさんありがとう! pic.twitter.com/gohyaceAHD

— かんな丸⁧!!⁨ (@pgate1) January 7, 2023

Ultra-XGAテトリス

最大解像度 1600 x 1440 テトリス作ろうかと思ったけど時間ないや、誰か作って。

おわりに

うっひょ~♪(FPGAに食べられながら)FPGAには無限の可能性があるんだよ…。

ゲームプレイもいいけど、openFPGA という立派な開発用フレームワークが公開されたので、 ポータブルゲーミングFPGA環境として動かせると楽しいわけで。 つまり Pocket を持ち歩けばどこでもHDLの実装ができるということ。 本記事も openFPGA の使い方が分かるに従い追記していく。

openFPGA については、今後 openFPGA をサポートした『SuperNt』や『MegaSg』の後継機等が出てくる可能性は…? すでに移植されたコアが動くように Pocket と同じFPGAを搭載するのか。 それとも openFPGA2 みたいに改良してくるのか。 それにしても、すでに発表されている『Duo』や『3D』が openFPGA をサポートしないとアナウンスされた時の界隈の落胆っぷりがすごかったよね。


©2023 pgate1