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 は 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
をコピーしてくる。
Platforms
もcore-template/dist/platforms
からそのままコピー。
もちろんSDカードライタで直接microSDカードにデータを書き込んでもいい。
Pocket でmicroSDカードにアクセスする設定は、
AnalogueOS メニューから[Tools]→[Developer]→[USB SD Access]にチェックを入れる。
Pocket にmicroSDカードをセットし電源を入れ、
PCとUSB接続するとエクスプローラからアクセスできるようになる。
エクスプローラからアクセスしたmicroSDカードの中身は、
このような感じにフォルダが作成されている。
上で作成した Assets、Cores、Platforms をmicroSDカードのルートに上書きコピーし、
右のBボタンやら×ボタンやらの位置のボタンでSDアクセスモードからexitする。
exitしたらUSBケーブルを徐に抜く。
FPGAコアを動かすには、
AnalogueOS メニューから[openFPGA]→[Example Platform]→[Run]を選択。
以下のようにグレーの画面が表示されていればサンプル実行は成功だ。
終了する場合は、下方中央の AnalogueMenu ボタンを押し、[Quit]→[Confirm]で終了。
Lチカと言っても、Pocket にはデバグ用LEDは付いてないので、 画面表示に代えてこれを行う。 そう、LCDチカなんだよ。
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
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.exe と run.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 が生成される。
Pocket を起動しUSBケーブルでPCと接続後、
bitstream.rbf_r をCores/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プリンタで穴の開いた裏蓋を作成してほしい。
通常の Pocket でもダウンロードケーブルが使用できるようなカスタム裏蓋が発売されることを期待してる。
ちょっとした問題が…。 たまたまTBSラジオ(FM90.5MHz)を付けていたら、FPGAデータダウンロード時にひどいノイズを受信してしまう。 文化放送(FM91.6MHz)には影響ないみたい。 ノイズ源どこ…。
他のFPGAボードで動かしていたポリゴンデモを移植してみた。 解像度を 640 x 480 に変更し、キー入力で操作できるようにした。
Analogue Pocket(あっぽけ)でポリゴンデモ動いた
— かんな丸!! (@pgate1) October 11, 2022
VGA出力回路がそのまま使えるんだね pic.twitter.com/LnOuwTZejw
デバッグには観測性が必要なので、 回路内ロジックアナライザや仮想JTAG経由のデータ送受信などでFPGA内データを観測する。
ダウンロードケーブルを使っての開発のコツとしては、 FPGAコアに関連する環境である各種パラメータを記述した *.json と読み込ませるデータ等をあらかじめmicroSDカード内に配置しておき、 openFPGA から仮のFPGAコアを起動した状態で Quartus Programmer からFPGAデータを書き込むこと。 こうすることで、書き込んだFPGAデータは仮のFPGAコアが起動した環境で動作する形になる。 言い換えると、*.json の記述とFPGAデータのパラメータ等が異なるとうまく動作しない。 例えば、解像度が異なる設定の video.json を読み込んでいる状態でFPGAデータを書き込んでも画面が期待通りに表示されないなど。
キミが 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として説明する。
実際の基板はこんな感じ。
参考: 今話題の「openFPGA」とは何か? 中華エミュ機とはまったく異なるその実力と魅力
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』や『Duo』にも使用されていたことと、 AnalogueOS機能実行には ARMプロセッサ内蔵の Cyclone V SoC ではなく別にPICを置いたのは、 このFPGAをボリュームディスカウントで購入したのかな?と勝手に妄想したり。 でもさすがに携帯機で Cyclone V SoC 使うのは消費電力や熱対策なんかが大変なことになるか。
FPGA周辺回路として使用可能なものを紹介する。
回路図が公開されていないのが残念だけど、サンプルのピンアサインファイルを流用すれば使えるのでまぁいいや。
core_top.v
のポート宣言をベースに解説する。
GB互換機を作る予定はないので省略。
何かに使えそうではある。
core_top.v
// infrared
input wire port_ir_rx,
output wire port_ir_tx,
output wire port_ir_rx_disable,
GBA互換機を作る予定はないので省略。
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,
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,
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用のポート。 要は Reserved for Future Use.
core_top.v
// RFU internal i2c bus
inout wire aux_sda,
output wire aux_scl,
ビデオ出力はVGA信号同様のフォーマットで出力すればプラットフォームのスケーラーが合わせてくれる。
video.json
の記述とも合わせること。
ピクセル表示外の時は video_de に 0 を出力し、
各信号を出力前にレジスタで叩けば同期されやすい。
また、複数の解像度を設定すれば、コアから選択できる(調査中)。
LCDの最大解像度は、スペックとしてはGBの10倍にあたる 1600 x 1440 だけど、
openFPGA として設定できるのは今のところ 16 x 16 から 800 x 720 まで。
上位モジュールの APF で 24bitRGB を 12bitDDR に変換している。
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,
オーディオ出力は 16bit ステレオの 48kHz が出せて、 コアからはI2CでAudioDACチップに信号を出力する。
core_top.v
output wire audio_mclk,
input wire audio_adc,
output wire audio_dac,
output wire audio_lrck,
各出力に対する信号生成は core-example-basicassets を流用できる。
ただ、DAC向けマスタークロックの audgen_mclk についてサンプルでは21bitのカウンタを使用しているけど、
74.25MHzと24.576MHz(出力12.288MHz)の最大公約数が6000なので、
実は以下の通りカウンタは15bitで済む。
core_top.v
assign audio_mclk = audgen_mclk;
/*
// generate MCLK = 12.288mhz with fractional accumulator
reg [21:0] audgen_accum = 0;
reg audgen_mclk;
parameter [20:0] CYCLE_48KHZ = 21'd122880 * 2;
always @(posedge clk_74a) begin
audgen_accum <= audgen_accum + CYCLE_48KHZ;
if(audgen_accum >= 21'd742500) begin
audgen_mclk <= ~audgen_mclk;
audgen_accum <= audgen_accum - 21'd742500 + CYCLE_48KHZ;
end
end
*/
// generate MCLK = 12.288mhz with fractional accumulator
reg [14:0] audgen_accum = 0;
reg audgen_mclk;
parameter [14:0] CYCLE_48KHZ = 15'd2048 * 2;
always @(posedge clk_74a) begin
audgen_accum <= audgen_accum + CYCLE_48KHZ;
if(audgen_accum >= 15'd12375) begin
audgen_mclk <= ~audgen_mclk;
audgen_accum <= audgen_accum - 15'd12375 + CYCLE_48KHZ;
end
end
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のデータ読み込みは、APF のブリッジ経由で行う。
ここではSDRAMもしくは内蔵RAMへのデータリードを説明する。
microSD内のデータファイルにはdata.json
でターゲットIDや、
ファイルアクセスオプション等を指定しておく。
こうすることでFPGAコアロード時に APF に対して、
ブリッジからどのデータファイルにアクセスされるかといった情報がセットされる。
ファイル名にマルチバイト文字は使用できない。
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 で失効されるので注意。
ファイルサイズが数MByte以下で、ロード時にSDRAMへ全て読み込んでおきたい場合は、 ブリッジコマンド経由でROMサイズを取得してその分だけリードリクエストするか(調査中)、 リードバイトサイズ(target_length)に -1(0xFFFFFFFF)を指定すれば自動でファイルサイズ分だけ読み込みが可能という方法もある。
ユーザコアからのリードリクエスト信号はシンクロナイザを通してブリッジへのリクエストに変える。 ブリッジから 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
調査中。
ゲームによって 256 x 240 or 512 x 240 だったり、 320 x 240 or 640 x 480 だったりするので、 適切な解像度を設定して画面表示をちゃんとする。 調査中。
Coresフォルダ内にはFPGAデータに加え、 ROMデータ情報や入出力に関するjsonファイルを置くわけだけど、 余分なjsonファイルを置いたままにするとコアが正常に起動しない。 例えば、video.json と同じフォルダ内に video_640.json と言う名前のファイルがあると、 「openFPGA」を選択しただけで画面が明滅してコアを起動できず電源を落とすしかなくなる。 また、jsonファイル内の記述についても記述間違いがあると謎のQRコードが表示されてコアは起動しない。 これらのような場合はjsonファイル群を正常な形にして、 キャッシュ(Systemフォルダ内の corelist_cache.bin、cores_cache.bin、platforms_cache.bin など) を削除すればコアを起動できるようになるはず。
コアロード前にゲームのROMファイルを選択するけど、
セーブが可能なゲームの場合はセーブデータも考慮する必要がある。
例えばSNESコアでROMとセーブデータを読み込む data.json は以下の通り。
ポイントはセーブデータの方の"nonvolatile": true
と、
"parameters"のビット2(0x004:Nonvolatile filename)。
Nonvolatile filename フラグが1だとスロットID 0 のROMファイル名(例えば Game_ABC.sfc を指定した場合)
と同じファイル名のデータ(Game_ABC.srm)がSaves/core_name/common/
から読み込まれる。
コアがセーブ書き込み機能もサポートするなら、ビット3(0x008:Read-only)を0にして書き込み可とする必要がある。
他にも機能的にはフラグ調整が必要だけど、まだ調査中。
data.json
{
"data": {
"magic": "APF_VER_1",
"data_slots": [
{
"name": "ROM",
"id": "0x00",
"required": true,
"parameters": "0x008",
"deferload": true,
"extensions": ["sfc", "smc"],
"size_maximum": "0x00800000",
"address": "0x01000000"
},
{
"name": "SAVE",
"id": "0x10",
"required": false,
"parameters": "0x00C",
"nonvolatile": true,
"deferload": true,
"extensions": ["srm"],
"size_maximum": "0x00020000",
"address": "0x08000000"
}
]
}
}
他の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個あるということは、これを描画フレームバッファとして使えということなのか。
実は『リッジレーサー』のプログラムやグラフィックデータはまとめて4MByte以下のため、 まるっとSDRAMに置けて実行できてしまう。 しかし『トバルNo.1』や『アインハンダー』といった多くのPlayStatoinゲームのROMイメージデータは、 ムービーデータを含んでいるせいか数十~数百MByteあるのが普通。 もちろん、まとめてSDRAMに置くことはできず、 PlayStationコアのCD-ROMコントローラが要求するタイミングで読み込む必要がある。
ROMイメージデータの置き場所としては実質的にmicroSDしかなく、 またAnalogueOS管理内に大きなメモリ領域があるわけではなさそうなので、 数百MByteのデータを一度に全て読み込むことはできない。 そのためFPGA内に自前で適切なサイズのバッファを用意し、 その分だけブリッジに対してリードコマンドをリクエストする。 microSDからのデータは、ある程度のサイクルをかけて送られてくる。
バッファについては、一度に読み込むデータのサイズにより適切なバッファサイズを用意する。 一度のリードコマンドでできるだけたくさんのデータをリードする方がウエイトがかからず全体的なデータ読み込み時間を短くできる。 逆にバッファサイズが小さいとリードコマンドの回数が多くなり、 その分だけウェイト回数が多くなり全体的なリード時間が長くなってしまう。 計測したところバッファサイズとリード速度は次のようになった。
microSDカードから16MByteを複数回に分けてリードしたテスト結果:
バッファサイズ | リード回数 | リード時間 | リード速度 |
---|---|---|---|
2 kByte | 8192 | 26 sec | 630 kByte/sec |
4 kByte | 4096 | 20 sec | 820 kByte/sec |
8 kByte | 2048 | 15 sec | 1090 kByte/sec |
16 kByte | 1024 | 11 sec | 1490 kByte/sec |
32 kByte | 512 | 10 sec | 1630 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:
— かんな丸!! (@pgate1) January 19, 2023
あっぽけことAnalogue PocketでトバルNo.1をプレイできるようになった。まだCPUとGPUの並列実行ができてないのでスローだけど、ちらつきは少ないかな。まぁほぼ生ポリみたいな感じなので。 pic.twitter.com/JmS1UY2ujA
PlayStation on FPGA feat. Pocket:ちらつきの低減と、CD-ROMコントローラの機能追加、横512ピクセルへの対応で、デュープリズムやサガフロンティア2を動かせました! pic.twitter.com/skdXEh7d9D
— かんな丸!! (@pgate1) January 29, 2023
動画後半の『アインハンダー』はちらついてしまい目チカがひどいけど、
現状はちらつかないよう改善できた。
まぁ、遊べないこともない…?
プレイできる程度に移植はしたものの、やはり外部メモリの使い方がへたっピでFPSが出ない。 バースト転送・バッファ・複数リクエストの処理など、 メモリの使い方を制する者がFPGAを制すると言っても過言ではない。 あと複数クロック。それからタイミング制約。
しかし、Pocket 向けの中途半端なPlayStationコアをリリースしたあと、 界隈の焚き付けに失敗し、結局は自分でまともなものを出さざるを得ないような雰囲気になってる。 これが期待値のアンダーシュートだよ…。
ターゲット | ALM | RAM Block | DSP | Fmax |
---|---|---|---|---|
DE0-CV | 92% | 79% | 98% | 49.8 MHz |
93% | 99% | 98% | 47.5 MHz |
NES on FPGA も移植した。 PRG-ROMはSDRAMで、CHR-ROMを256kBの内蔵RAMで賄っている。 SDRAMを共有するのがいいんだけど仮実装でそんな感じに。 リソース使用率は 17% ALMsほど。
NES on FPGA feat. Pocket のFPGAアセットはこちらhttps://t.co/ZHXNnfLLJO pic.twitter.com/BRLZLgh23b
— かんな丸!! (@pgate1) 2025年4月29日
SNES on FPGA もDE0-CVで動いてるので、そのうち移植したい移植した。
ROMはSDRAMに配置して、WorkRAM・VRAM・AudioRAMは内蔵RAMを使用した。
ついでにセーブデータ読み込みについても調査し実装できた。
リソース使用率は 63% ALMs。
これまでのFPGAボードではゲームパッド接続が面倒でまともに操作できなかったのに対して、 Pocket ではちゃんとプレイもできるしサウンドもきれいだし、最高だね。 あとは画面表示をもう少しLCDにフィットさせたい。 それからセーブデータの保存や、 できればステートセーブ&ロードも実装できればと思うので、 そのためにブリッジ経由でのmicroSDHCカードへのデータ書き込みについても調査が必要。SNES on FPGA feat. Pocket のFPGAアセットはこちらhttps://t.co/WxY4xv2qSZ pic.twitter.com/uvU92KKULw
— かんな丸!! (@pgate1) 2025年4月29日
公式のクリアケースは強固過ぎて飾る用だね。 2DSLLのポーチがサイズ的にちょうど良い。
あっぽけの寝床にちょうどよいポーチ見っけた pic.twitter.com/p36WMeNeoF
— かんな丸!! (@pgate1) January 23, 2023
NintendoDS向けに発売された『ニンテンドーDSブラウザー』には、 『DSメモリー拡張カートリッジ』が同梱されている。 特に初期ロットのものはGBAカートリッジ形状のため、Pocket にセット可能。 チップはEM64と書かれたものが搭載されていて、64MbitのSRAMらしい。 ただ、初期化処理をしないと拡張メモリエリアにアクセスできない仕様なうえ、 カスタム品なので仕様も見つけられず。 完全にネタ画像となってしまっている。Pocketのメモリが足りない!?
— かんな丸!! (@pgate1) January 31, 2023
そんなときはコレを…こうじゃ
これで64MbitのSRAM追加なのじゃ! pic.twitter.com/G74uiVP3Op
また『DS振動カートリッジ』についてはすでに openFPGA の GBC/GBAコアでサポートされていて、 振動対応ゲームプレイ時にちゃんと振動する。 これも初期のGBAカートリッジ形状のものが Pocket に無理なくセット可能。Pocketに振動機能を追加したい?
— かんな丸!! (@pgate1) 2024年9月21日
そんなこともあろうかと…こうじゃ! pic.twitter.com/eJVbeq82TE
AnalogueからはPocket用にMIDI系のケーブルが出ているので、 上手く使えばFPGAからMIDI信号を生成してMIDI音源モジュールを使えないかなと妄想。
Analogue Pocket(非公式)で同人GBソフト(非公式)を遊ぶっていう、 もはやアーキテクチャのみが引き継がれてる郷愁的未来感。 ロストロジックしないノスタルジック。
令和のGBソフト『NEKO Can DREAM』が到着したので、Pocket開発の合間に遊ぶ!猫分儀スミレさんありがとう! pic.twitter.com/gohyaceAHD
— かんな丸!! (@pgate1) January 7, 2023
最大解像度テトリス作ろうかと思ったけど時間ないや、誰か作って。
うっひょ~♪(FPGAに食べられながら)FPGAには無限の可能性があるんだよ…。
ゲームプレイもいいけど、openFPGA という立派な開発用フレームワークが公開されたので、 ポータブルゲーミングFPGA環境として動かせると楽しいわけで。 つまり Pocket を持ち歩けばどこでもHDLの実装ができるということ。 本記事も openFPGA の使い方が分かるに従い追記していく。
openFPGA については、今後 openFPGA をサポートした『SuperNt』や『MegaSg』の後継機等が出てくる可能性は…? すでに移植されたコアが動くように Pocket と同じFPGAを搭載するのか。 それとも openFPGA2 みたいに改良してくるのか。 それにしても、すでに発表されている『Duo』や『3D』が openFPGA をサポートしないとアナウンスされた時の界隈の落胆っぷりがすごかったよね。 (2025/05/05 追記)Duo の基板画像を見つけたので確認したところ、 メインFPGAに加えサブFPGAとPICで AnalogueOS を使用してはいるものの、 Pocket にあった openFPGA 用の SDRAM や PSRAM が搭載されてないため、 ファームウェアアップデートでの openFPGA 対応は今後もなさそう。