~独田地獄斎が贈るPlayStation研究序説~
NNNNNN NNNNNNNNNNNN NNNNNNNNNNNNNNNN NNNNNNNNNNNNNNNNNNN NNNNNNNNN NNNNNNNNN NNNNNNNNN NNNNNNNNN NNNNNNNNN NNNNNNNNN NNNNNNNNN NNNNNNNNNN NNNNNNNNN NNNNNNNNNN NNNNNNNNN NNNNNNNNNN NNNNNNNNN NNNNNNNNNN NNNNNNNNN NNNNNNNNN NNNNNNNNN NNNNNNNN OONNNNNNNNN NNNNNNN YYYOOOONNNNNNNNN YYYYYYYYYYYNNNNNNNNN OOOOOOOOOOOO YYYYYYYYYYYYYYYNNNNNNNNNYYOOOOOOOOOOOOOOOOOOO OOOOOOYYYYYYYYY NNNNNNNNNYYYYYYYOOO OOOOOOOOO OOOOOOOOOOO YYNNNNNNNNNYYYYY OOOOOOOOOOO OOOOOOOOO OOOYYYYNNNNNNNNN YYYYYYYYYOOOOOO OOOOOOOOOOOOOOOOOONNNNNNNNN YYYYYYYYYYYYYYY OOOOOOOOOOOO NNNNNNNNNOYYYYYYYYYYYY NNNNNNNNNOOOOOOYYY NNNNOOOO
本ページは未来のPlayStationエミュレータ開発者への情報提供を目的とする。 ただし、ここでコーディングの講義をするつもりはない。 なぜならコーディングの観点からエミュレータ特有と呼べるものは何もないからだ。 動的コンパイルは珍しいかもしれないが、これはエミュレータの本質ではない。 換言すると、この程度のコーディングに躓くようではプログラマとして終っている。 基礎からやり直した方がいいんじゃないか。
SCPH-7000 (SONY)
近年はThinkPad X61 with X6 UltraBase
PS X-TERMINATOR (Future Console Design)
caetla 0.34 (K-Communications)
caetools (K-Communications)
directio (K-Communications)
GNU Binary Utilities 2.8.1 mipsel-generic-elf/ecoff (こぺる)
GNU C/C++ Compiler 2.7.2.3 mipsel-generic-elf/ecoff (こぺる)
Borland C++Compiler 5.5 (Borland)
http://www.scei.co.jp/Net/guide/user/
http://psx.rules.org/psxrul2.shtml
ただし、これらには多くの誤記があるので鵜呑みにしないこと。 正規開発マニュアルから抜粋したと思われる部分にすら誤記がある (正規マニュアルにも誤記はある)。 開発者ならば最終的には自分でウラをとること。 幸い、PlayStationにはその現実的な手段があるのだから。
http://www.semicon.toshiba.co.jp/product/micro/index.html
https://toshiba.semicon-storage.com/jp/product/microcomputer/tx19ah1-series/detail.TMP1941AFG.html
https://toshiba.semicon-storage.com/info/docget.jsp?did=1371&prodName=TMP1941AFG
LR33000をググれ。
アドレス | 内容 |
---|---|
0x00000000~0x001FFFFF | メインメモリ(命令キャッシュ有効) |
0x00200000~0x003FFFFF | メインメモリ ミラー(命令キャッシュ有効) |
0x00400000~0x005FFFFF | メインメモリ ミラー(命令キャッシュ有効) |
0x00600000~0x007FFFFF | メインメモリ ミラー(命令キャッシュ有効) |
0x1F000000~0x1F07FFFF | PIO |
0x1F800000~0x1F8003FF | Dキャッシュという名のスクラッチパッド |
0x1F801000~0x1F80207F | I/Oポート(いわゆるメモリマップドI/O) |
0x1FA00000~0x1FA00003 | SW1 |
0x1FC00000~0x1FC7FFFF | OS ROM |
0x80000000~0x801FFFFF | メインメモリ ミラー(命令キャッシュ有効) |
0x80200000~0x803FFFFF | メインメモリ ミラー(命令キャッシュ有効) |
0x80400000~0x805FFFFF | メインメモリ ミラー(命令キャッシュ有効) |
0x80600000~0x807FFFFF | メインメモリ ミラー(命令キャッシュ有効) |
0x9F000000~0x9F07FFFF | PIO ミラー |
0x9F800000~0x9F8003FF | Dキャッシュという名のスクラッチパッド ミラー |
0x9F801000~0x9F80207F | I/Oポート(いわゆるメモリマップドI/O) ミラー |
0x9FA00000~0x9FA00003 | SW1 ミラー |
0x9FC00000~0x9FC7FFFF | OS ROM ミラー |
0xA0000000~0xA01FFFFF | メインメモリ ミラー(命令キャッシュ無効) |
0xA0200000~0xA03FFFFF | メインメモリ ミラー(命令キャッシュ無効) |
0xA0400000~0xA05FFFFF | メインメモリ ミラー(命令キャッシュ無効) |
0xA0600000~0xA07FFFFF | メインメモリ ミラー(命令キャッシュ無効) |
0xBF000000~0xBF07FFFF | PIO ミラー |
0xBF801000~0xBF80207F | I/Oポート(いわゆるメモリマップドI/O) ミラー |
0xBFA00000~0xBFA00003 | SW1 ミラー |
0xBFC00000~0xBFC7FFFF | OS ROM ミラー |
0xFFFE0000~0xFFFE001F | SW2 |
0xFFFE0100~0xFFFE013F | SW3 FFFE0130:SwapCache? |
アドレス | レジスタ名(仮) | 内容 |
---|---|---|
0x1F801000 | ? | 拡張メモリ領域指定? |
0x1F801004 | ? | I/Oポート終端指定? |
0x1F801008 | ? | ? |
0x1F80100C | ? | ? |
0x1F801010 | ? | ? |
0x1F801014 | ? | SPU DMAのウェイト? |
0x1F801018 | ? | OTC DMAのウェイト? |
0x1F80101C | ? | ? |
0x1F801020 | ? | CD DMAのウェイト? |
0x1F801040 | COMA_DATA | COMAデータ |
0x1F801044 | COMA_STAT | COMAステータス |
0x1F801048 | COMA_MODE | COMAモード |
0x1F80104A | COMA_CTRL | COMAコントロール |
0x1F80104E | COMA_BAUD | COMAボー |
0x1F801050 | SIO_DATA | SIOデータ |
0x1F801054 | SIO_STAT | SIOステータス |
0x1F801058 | SIO_MODE | SIOモード |
0x1F80105A | SIO_CTRL | SIOコントロール |
0x1F80105E | SIO_BAUD | SIOボー |
0x1F801060 | RAM_SIZE | 実効メモリサイズ |
0x1F801070 | I_STAT | 割り込みステータス |
0x1F801074 | I_MASK | 割り込みマスク |
0x1F801080 | D0_MADR | DMACチャネル0(MDECin)メモリアドレス |
0x1F801084 | D0_BCR | DMACチャネル0(MDECin)ブロックカウント |
0x1F801088 | D0_CHCR | DMACチャネル0(MDECin)チャネルコントロール |
0x1F801090 | D1_MADR | DMACチャネル1(MDECout)メモリアドレス |
0x1F801094 | D1_BCR | DMACチャネル1(MDECout)ブロックカウント |
0x1F801098 | D1_CHCR | DMACチャネル1(MDECout)チャネルコントロール |
0x1F8010A0 | D2_MADR | DMACチャネル2(GPU)メモリアドレス |
0x1F8010A4 | D2_BCR | DMACチャネル2(GPU)ブロックカウント |
0x1F8010A8 | D2_CHCR | DMACチャネル2(GPU)チャネルコントロール |
0x1F8010B0 | D3_MADR | DMACチャネル3(CD)メモリアドレス |
0x1F8010B4 | D3_BCR | DMACチャネル3(CD)ブロックカウント |
0x1F8010B8 | D3_CHCR | DMACチャネル3(CD)チャネルコントロール |
0x1F8010C0 | D4_MADR | DMACチャネル4(SPU)メモリアドレス |
0x1F8010C4 | D4_BCR | DMACチャネル4(SPU)ブロックカウント |
0x1F8010C8 | D4_CHCR | DMACチャネル4(SPU)チャネルコントロール |
0x1F8010D0 | D5_MADR | DMACチャネル5(PIO)メモリアドレス |
0x1F8010D4 | D5_BCR | DMACチャネル5(PIO)ブロックカウント |
0x1F8010D8 | D5_CHCR | DMACチャネル5(PIO)チャネルコントロール |
0x1F8010E0 | D6_MADR | DMACチャネル6(OTC)メモリアドレス |
0x1F8010E4 | D6_BCR | DMACチャネル6(OTC)ブロックカウント |
0x1F8010E8 | D6_CHCR | DMACチャネル6(OTC)チャネルコントロール |
0x1F8010F0 | D_PCR | DMAC優先度コントロール |
0x1F8010F4 | D_ICR | DMAC割り込みコントロール |
0x1F801100 | T0_COUNT | タイマ0(システム/ピクセル)カウント |
0x1F801104 | T0_MODE | タイマ0(システム/ピクセル)モード |
0x1F801108 | T0_COMP | タイマ0(システム/ピクセル)コンペア |
0x1F801110 | T1_COUNT | タイマ1(システム/水平同期)カウント |
0x1F801114 | T1_MODE | タイマ1(システム/水平同期)モード |
0x1F801118 | T1_COMP | タイマ1(システム/水平同期)コンペア |
0x1F801120 | T2_COUNT | タイマ2(システム/8分周)カウント |
0x1F801124 | T2_MODE | タイマ2(システム/8分周)モード |
0x1F801128 | T2_COMP | タイマ2(システム/8分周)コンペア |
0x1F801800 | CD_REG0 | CDレジスタ0 |
0x1F801801 | CD_REG1 | CDレジスタ1 |
0x1F801802 | CD_REG2 | CDレジスタ2 |
0x1F801803 | CD_REG3 | CDレジスタ3 |
0x1F801810 | GPU_DATA | GPUデータ/コマンド |
0x1F801814 | GPU_CTRL | GPUステータス/コントロール |
0x1F801820 | MDEC_DATA | MDECデータ/コマンド |
0x1F801824 | MDEC_CTRL | MDECステータス/コントロール |
0x1F801C00 | SPU_REG00 | SPUレジスタ0 |
0x1F801C02 | SPU_REG01 | SPUレジスタ1 |
中略 | 中略 | 中略 |
0x1F801DFC | SPU_REGFE | SPUレジスタ254 |
0x1F801DFE | SPU_REGFF | SPUレジスタ255 |
0x1F802041 | ? | ディップスイッチ? |
PlayStationのCPUはMIPSのR3000をベースとしたカスタムCPUである。 動作周波数は33868800Hzと言われ、エンディアンはリトルである。 若干の違いはあるが、それは青本も同じなので、 基本的な理解は東芝のTX39のドキュメントで十分である。
COP1,COP3,TLB系は存在しない。 サービスコール内のatofでCOP1命令が使われているがatof自体が呼ばれた試しがない。 おそらく試作機ではデバグ処理のために存在するが、結局オミットされたのだろう。
31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 | 15 | 14 | 13 | 12 | 11 | 10 | 09 | 08 | 07 | 06 | 05 | 04 | 03 | 02 | 01 | 00 |
op | rs | rt | rd | sa | funct |
op | 0x00 | 0x01 | 0x02 | 0x03 | 0x04 | 0x05 | 0x06 | 0x07 |
---|---|---|---|---|---|---|---|---|
0x00 | SPECIAL | BCOND | J | JAL | BEQ | BNE | BLEZ | BGTZ |
0x08 | ADDI | ADDIU | SLTI | SLTIU | ANDI | ORI | XORI | LUI |
0x10 | COP0 | COP2 | ||||||
0x18 | ||||||||
0x20 | LB | LH | LWL | LW | LBU | LHU | LWR | |
0x28 | SB | SH | SWL | SW | SWR | |||
0x30 | LWC2 | |||||||
0x38 | SWC2 |
funct | 0x00 | 0x01 | 0x02 | 0x03 | 0x04 | 0x05 | 0x06 | 0x07 |
---|---|---|---|---|---|---|---|---|
0x00 | SLL | SRL | SRA | SLLV | SRLV | SRAV | ||
0x08 | JR | JALR | SYSCALL | BREAK | ||||
0x10 | MFHI | MTHI | MFLO | MTLO | ||||
0x18 | MULT | MULTU | DIV | DIVU | ||||
0x20 | ADD | ADDU | SUB | SUBU | AND | OR | XOR | NOR |
0x28 | SLT | SLTU | ||||||
0x30 | ||||||||
0x38 |
rs | 0x00 | 0x01 | 0x02 | 0x03 | 0x04 | 0x05 | 0x06 | 0x07 |
---|---|---|---|---|---|---|---|---|
0x00 | BLTZ | BGEZ | ||||||
0x08 | ||||||||
0x10 | BLTZAL | BGEZAL | ||||||
0x18 |
rs | 0x00 | 0x01 | 0x02 | 0x03 | 0x04 | 0x05 | 0x06 | 0x07 |
---|---|---|---|---|---|---|---|---|
0x00 | MFC0 | MTC0 | ||||||
0x08 | ||||||||
0x10 | CP0 | |||||||
0x18 |
funct | 0x00 | 0x01 | 0x02 | 0x03 | 0x04 | 0x05 | 0x06 | 0x07 |
---|---|---|---|---|---|---|---|---|
0x00 | ||||||||
0x08 | ||||||||
0x10 | RFE | |||||||
0x18 | ||||||||
0x20 | ||||||||
0x28 | ||||||||
0x30 | ||||||||
0x38 |
rs | 0x00 | 0x01 | 0x02 | 0x03 | 0x04 | 0x05 | 0x06 | 0x07 |
---|---|---|---|---|---|---|---|---|
0x00 | MFC2 | CFC2 | MTC2 | CTC2 | ||||
0x08 | ||||||||
0x10 | CP2 | |||||||
0x18 |
funct | 0x00 | 0x01 | 0x02 | 0x03 | 0x04 | 0x05 | 0x06 | 0x07 |
---|---|---|---|---|---|---|---|---|
0x00 | RTPS | NCLIP | ||||||
0x08 | OP | |||||||
0x10 | DPCS | INTPL | MVMVA | NCDS | CDP | NCDT | ||
0x18 | NCCS | CC | NCS | |||||
0x20 | NCT | |||||||
0x28 | SQR | DCPL | DPCT | AVSZ3 | AVSZ4 | |||
0x30 | RTPT | |||||||
0x38 | GPF | GPL | NCCT |
ネットやろうぜの記述には重要部であるほど誤りがあるので、注意が必要である。 まずメモリ空間とキャッシュ(バースト転送)の有効/無効の関係を示した表において メモリ空間を取り違えている。また
Iキャッシュが有効な論理メモリ空間上の命令コードは通常の約5倍の速度でCPUに読み込まれます。 また、一旦読み込まれた命令コードはCPU内のIキャッシュメモリに保存されますので、 再実行時にはメインメモリへのアクセスなしに実行されます。
という文章もおかしい。 後半の文章があるから、前半はキャッシュミスした状態での実行速度と考えるしかない。 ならば理論的に24/9倍速以上にはならない。
アドレス0x1F800000にマッピングされた、ストールの発生しないRAMを PlayStationの「方言」としてDキャッシュと呼んでいるが、 これは一般的な意味のDキャッシュではない。 一般的な意味のDキャッシュはPlayStationには存在しない。
RTPS,RTPT命令において、投影変換係数
(int) (h * 65536 / Sz + 0.5) |
が求められるが、実際は除算ではなく、逆数テーブルを用いた積算
(h * iSz + (1 << (N - 1))) >> N |
が行われる。iSzは0x10000~0x1FFFFの範囲であり、小数部16ビットの浮動小数に相当する。 iSzは必ずしも
(65536 << N) / Sz |
にはなっていないので、ブルートフォースアタックによる探索が必要になる。
Sz:32768~65535(N=16)について結果を示す。 Sz:0~32767については、Nを減らすだけなので割愛する。
コード表から分かるようにR3000は単純な命令だけで構成されており、既にマイクロコードであるとも言える。 従って、一コードに対して一個のコンパイル済みデータを予め用意し、実行時に変数部分を書き換えるだけでも 実用に耐える動的コンパイルが可能である。例えばSRAV,SRLVでは、
/* * */ uchar __SRAV[] = { 0x8B, 0x0D, '\\', '\\', '\\', '\\', /* 00 mov ecx,[GPR[rs]] 02 &GPR[rs] */ 0xA1, '\\', '\\', '\\', '\\', /* 06 mov eax,[GPR[rt]] 07 &GPR[rt] */ 0xD3, 0xF8, /* 0B sar eax,cl */ 0xA3, '\\', '\\', '\\', '\\' /* 0D mov [GPR[rd]],eax 0E &GPR[rd] */ }; /* * */ uchar __SRLV[] = { 0x8B, 0x0D, '\\', '\\', '\\', '\\', /* 00 mov ecx,[GPR[rs]] 02 &GPR[rs] */ 0xA1, '\\', '\\', '\\', '\\', /* 06 mov eax,[GPR[rt]] 07 &GPR[rt] */ 0xD3, 0xE8, /* 0B shr eax,cl */ 0xA3, '\\', '\\', '\\', '\\' /* 0D mov [GPR[rd]],eax 0E &GPR[rd] */ }; |
の様なデータを予め用意し、実行時に
/* * */ #define _rs_ ((opcode >> 21) & 31) #define _rt_ ((opcode >> 16) & 31) #define _rd_ ((opcode >> 11) & 31) /* * */ #define CPU_SET_SRAV() \ { \ *(ulong *) (&__SRAV[0x02]) = (ulong) & GPR[_rs_]; \ *(ulong *) (&__SRAV[0x07]) = (ulong) & GPR[_rt_]; \ *(ulong *) (&__SRAV[0x0E]) = (ulong) & GPR[_rd_]; \ CPU_OPCODE = __SRAV; \ CPU_OPSIZE = sizeof(__SRAV); \ } /* * */ #define CPU_SET_SRLV() \ { \ *(ulong *) (&__SRLV[0x02]) = (ulong) & GPR[_rs_]; \ *(ulong *) (&__SRLV[0x07]) = (ulong) & GPR[_rt_]; \ *(ulong *) (&__SRLV[0x0E]) = (ulong) & GPR[_rd_]; \ CPU_OPCODE = __SRLV; \ CPU_OPSIZE = sizeof(__SRLV); \ } |
の様に変数部分を書き換える。後はこれを連結コピーして関数を作成し、出来上がった関数を実行する。
巷で誤解があるようなので、注意を喚起すると、 実行毎に新たな関数を作成していたのでは、インタプリタよりも遅くなる。 出来上がった関数を管理・再利用することこそが、動的コンパイルの肝である。 動的コンパイルとは可変長データを対象としたキャッシュ機構を構築することに等しい。
アセンブラで直接記述する以外に、以下の様にCで記述し、 ファイル出力、逆アセンブルするのも一つの手である。
/* * */ #define _rs_ 1 /* 変数部分なので適当 */ #define _rt_ 2 /* 変数部分なので適当 */ #define _rd_ 3 /* 変数部分なので適当 */ /* * */ long GPR[32]; /* * */ void __SRAV(void) { GPR[_rd_] = GPR[_rt_] >> GPR[_rs_]; } /* * */ void ____SRAV(void) { } /* * */ void __SRLV(void) { GPR[_rd_] = ((ulong) GPR[_rt_]) >> GPR[_rs_]; } /* * */ void ____SRLV(void) { } /* * */ void SaveToFile(char *lpFileName, void *lpStart, void *lpEnd) { FILE *fp; if (NULL == (fp = fopen(lpFileName, "wb"))) { return; } fwrite(lpStart, (ulong) lpEnd - (ulong) lpStart, 1, fp); fclose(fp); } /* * */ int main(void) { SaveToFile("SRAV.X86", __SRAV, ____SRAV); SaveToFile("SRLV.X86", __SRLV, ____SRLV); return(0); } |
ロードした場合
15 | 14 | 13 | 12 | 11 | 10 | 09 | 08 | 07 | 06 | 05 | 04 | 03 | 02 | 01 | 00 |
S S P U |
S S I O |
S C O M A |
S T M R 2 |
S T M R 1 |
S T M R 0 |
S D M A C |
S C D |
S G P U |
S V S Y N C |
フィールド | 内容 |
---|---|
SVSYNC | VSYNCの割り込みステータス 0:VSYNCから割り込み要求がない 1:VSYNCから割り込み要求があった |
SGPU~SSPU | SVSYNCと同様 |
ストアした場合
15 | 14 | 13 | 12 | 11 | 10 | 09 | 08 | 07 | 06 | 05 | 04 | 03 | 02 | 01 | 00 |
C S P U |
C S I O |
C C O M A |
C T M R 2 |
C T M R 1 |
C T M R 0 |
C D M A C |
C C D |
C G P U |
C V S Y N C |
フィールド | 内容 |
---|---|
CVSYNC | VSYNCの割り込みクリア 0→SVSYNCを0にする |
CGPU~CSPU | CVSYNCと同様 |
ロードした場合
ストアした値が返る
ストアした場合
15 | 14 | 13 | 12 | 11 | 10 | 09 | 08 | 07 | 06 | 05 | 04 | 03 | 02 | 01 | 00 |
M S P U |
M S I O |
M C O M A |
M T M R 2 |
M T M R 1 |
M T M R 0 |
M D M A C |
M C D |
M G P U |
M V S Y N C |
フィールド | 内容 |
---|---|
MVSYNC | VSYNCの割り込みマスク MVSYNCとSVSYNCがともに1である限り、INTCはCPUに割り込みを要求し続ける。 |
MGPU~MSPU | MVSYNCと同様 |
準備中
タイマは設定によりシステムクロックではなくピクセル表示や水平同期によりカウントアップさせることができる。
タイプ | 画面幅 | [A] | [B]システムクロック/水平同期 | システムクロック/ピクセル表示 | システムクロック/垂直同期 | ||
---|---|---|---|---|---|---|---|
非インターレース | インターレース | ||||||
偶数フィールド | 奇数フィールド | ||||||
NTSC | 256 | 566107.5005 | [A]/263 | [A]/89683=[B]/341 | [B]*263 | [B]*263 | [B]*262 |
320 | [A]/112038=[B]/426 | ||||||
368 | [A]/128081=[B]/487 | ||||||
512 | [A]/179366=[B]/682 | ||||||
640 | [A]/224339=[B]/853 | ||||||
PAL | 256 | 674399.5367 | [A]/314 | [A]/106760=[B]/340 | [B]*314 | [B]*313 | [B]*312 |
320 | [A]/133764=[B]/426 | ||||||
368 | [A]/152604=[B]/486 | ||||||
512 | [A]/213834=[B]/681 | ||||||
640 | [A]/267214=[B]/851 |
準備中
準備中
準備中
準備中
準備中
ロードした場合
基本的には内部のリザルトレジスタの値が返るが、 StoreImageコマンド実行中の場合は、その結果が返る。
リザルトレジスタには、 GPU制御系コマンドの結果や StoreImageコマンドの最初の結果が格納される。 途中の結果は格納されない。
ストアした場合
データは内部の多目的FIFOに格納され、 必要な数のデータが貯まると、 GPU描画系コマンドが実行される。
ロードした場合
31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 | 15 | 14 | 13 | 12 | 11 | 10 | 09 | 08 | 07 | 06 | 05 | 04 | 03 | 02 | 01 | 00 |
o d e |
t m o d e |
i s e m p t y |
d a t a r d y |
i s i d l e |
d m a r d y |
r e s e r v e d |
i s s t o p |
i s i n t e r |
i s r g b 2 4 |
i s p a l |
v r e s |
h r e s 1 |
h r e s 2 |
r e s e r v e d |
u n k n o w n |
r e s e r v e d |
p b c |
p b w |
d f e |
d t d |
t p |
a b r |
t y |
t x |
フィールド | 内容 |
---|---|
ode | ビデオ出力の走査フィールドがフレームバッファ上の奇数ラインかどうか。 よってインターレースでは数百水平同期毎、非インターレースでは1水平同期毎(指定ブランク区間は0のまま)に変化する。 |
tmode | 転送モード |
isempty | FIFO(パケットバッファ)が空かどうか |
datardy | データレディ |
isidle | アイドル状態である |
dmardy | DMAレディ |
isstop | ビデオ出力を停止している |
isinter | インターレースで出力している(垂直解像度を倍密度にする場合は必須) |
isrgb24 | rgb24ビットモードである |
ispal | PALモードである |
vres | 垂直解像度 |
hres1 | 水平解像度1 |
hres2 | 水平解像度2(水平解像度1は無効になる) |
unknown | 不明(オン/オフ操作は可能) |
pbc | 優先度ビットコンペアを行う |
pbw | 優先度ビットを書き込む |
dfe | インターレースであっても、 非インターレースと同様、 走査フィールドに関係なく描画する |
dtd | ディザ処理を行う |
tp | テクスチャのビットモード |
abr | 半透明率 |
ty | テクスチャページのY座標 |
tx | テクスチャページのX座標 |
ストアした場合
GPU制御系コマンドが実行される。
全24チャネルの内蔵音源のこと。各チャネルは独立に 「波形データ(ADPCMまたは共通ノイズ)×エンベロープ×ボイス音量(左右独立)」 の三者の掛け合わせを出力する。
波形データにかけるバイアスのこと。
レート[*] | 実効レート | ||
---|---|---|---|
分母 | 分子(増加) | 分子(減少) | |
0~47 | 1 | (7 - (RATE & 3)) << (11 - (RATE >> 2)) | (-8 + (RATE & 3)) << (11 - (RATE >> 2)) |
48~ | 1 << ((RATE >> 2) - 11) | 7 - (RATE & 3) | -8 + (RATE & 3) |
モード | 内容 |
---|---|
線形増加 | 分母サンプリング時間毎にエンベロープレベルに分子(増加)を加算 |
線形減少 | 分母サンプリング時間毎にエンベロープレベルに分子(減少)を加算 |
指数増加 | 基本的には線形増加と同じだがエンベロープレベルが0x6000以上になるとレートが8増加 |
指数減少 | 分母サンプリング時間毎にエンベロープレベルに ((分子(減少) * レベル) >> 15)を加算 |
ステータス | 開始条件 | 終了条件 | 飽和下限 | 飽和上限 |
---|---|---|---|---|
初期化 | キーオンする | アタックレートに依存した初期化時間[*]が経過する | 0 | 0 |
アタック | 初期化が終了する | エンベロープレベルが32767以上になる | 32767 | |
ディケイ | アタックが終了する | エンベロープレベルがサステインレベル未満になる | 0 | |
サステイン | ディケイが終了する | キーオフする | 0 | 32767 |
リリース | キーオフする | エンベロープレベルが0以下になる | -1 | |
停止 | ・リリースが終了する ・ADPCMデコーダが強制停止する |
キーオンする | 0 | 0 |
ADPCMデコーダが圧縮データをデコードして得たサンプリング波形のこと。 重要なのはADPCMデコーダが(リバーブプロセスも)常時稼動していることだろう。 停止したように見えて、それはエンベロープを強制停止させた結果にすぎない。 つまり、常に割り込みが発生する可能性がある。 しかしながら、常時稼動のエミュレーションは、高負荷のワリに見返りが小さい。 モラトリアム期間を設けるのが現実解であろう。
フィールド | 内容 |
---|---|
0x04 | カレントブロックの先頭をループアドレスに設定する。 |
0x02 | エンベロープを強制停止しない。 |
0x01 | カレントブロックを演奏後、ループアドレスにジャンプする。 0x02が設定されていないと、エンベロープを強制停止する。 |
組み合わせとしては以下の5通りとなる(ループアドレスが強制的に設定された場合は、この限りではない)。
ループフラグ | 内容 |
---|---|
0x06,0x04 | カレントブロックの先頭をループアドレスに設定する。 |
0x03 | カレントブロック演奏後、ループアドレスにジャンプする。 |
0x07 | 上2つの組み合わせにより、カレントブロックを延々と演奏する。 |
0x01 | ループアドレスにジャンプするが、エンベロープを停止するので無音になる。 |
0x05 | カレントブロックを延々と演奏するが、エンベロープを停止するので無音になる。 |
図中、赤丸で示した点が波形データで、黒の破線が実際に出力される値である。 黒の実線は波形データと比較するために平行移動したものである。 赤の破線は参考として波形データに対しスプライン曲線を引いたものである。 重要なのは中間点以外も全く同じ処理がされているということであり、 出力は基本的に波形データを通過しないということである。 多くの人が想像するであろう、スプライン曲線とは、かなり違った形になる。
ちなみにg(x)=g(INT(x * 256)/256)であり、 4096個のテーブルデータとして実装していると思われる。 16ビット固定小数で実装したところ(1/32768)ほど値が異なることがあるので、 おそらくシフト値を有する、実質浮動小数になっていると推測される。
ADPCM波形の替わりに用いるランダム波形のこと。 チャネル毎にどちらかを選択できるが、 チャネル毎に異なるノイズ波形を得るといったことは出来ない。
アルゴリズム
/* * レベル変化時の計算式 */ char Addition[64] = { 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1 }; NoiseLevel = (short) (NoiseLevel + NoiseLevel + Addition[(NoiseLevel >> 10) & 63]); /* * レベル変化の周期 */ Freq = 0x8000 >> (NoiseClock >> 2); /* * 周期が半減する頻度 */ Half = ((NoiseClock & 3) * 2) / (4 + (NoiseClock & 3)); |
アルゴリズム
/* * PlayStation Reverberation Algorithm (C)Dr.Hell, 2005 * 厳密には、左右の処理のタイミングは1サンプリング時間ずれており、 * 各々は2サンプリング時間毎に実行される */ mAPF1 = *((ushort *) 0x1F801DC0) * 4; mAPF2 = *((ushort *) 0x1F801DC2) * 4; gIIR = *((short *) 0x1F801DC4) / 32768.0; gCOMB1 = *((short *) 0x1F801DC6) / 32768.0; gCOMB2 = *((short *) 0x1F801DC8) / 32768.0; gCOMB3 = *((short *) 0x1F801DCA) / 32768.0; gCOMB4 = *((short *) 0x1F801DCC) / 32768.0; gWALL = *((short *) 0x1F801DCE) / 32768.0; gAPF1 = *((short *) 0x1F801DD0) / 32768.0; gAPF2 = *((short *) 0x1F801DD2) / 32768.0; z0_Lsame = *((ushort *) 0x1F801DD4) * 4; z0_Rsame = *((ushort *) 0x1F801DD6) * 4; m1_Lcomb = *((ushort *) 0x1F801DD8) * 4; m1_Rcomb = *((ushort *) 0x1F801DDA) * 4; m2_Lcomb = *((ushort *) 0x1F801DDC) * 4; m2_Rcomb = *((ushort *) 0x1F801DDE) * 4; zm_Lsame = *((ushort *) 0x1F801DE0) * 4; zm_Rsame = *((ushort *) 0x1F801DE2) * 4; z0_Ldiff = *((ushort *) 0x1F801DE4) * 4; z0_Rdiff = *((ushort *) 0x1F801DE6) * 4; m3_Lcomb = *((ushort *) 0x1F801DE8) * 4; m3_Rcomb = *((ushort *) 0x1F801DEA) * 4; m4_Lcomb = *((ushort *) 0x1F801DEC) * 4; m4_Rcomb = *((ushort *) 0x1F801DEE) * 4; zm_Ldiff = *((ushort *) 0x1F801DF0) * 4; zm_Rdiff = *((ushort *) 0x1F801DF2) * 4; z0_Lapf1 = *((ushort *) 0x1F801DF4) * 4; z0_Rapf1 = *((ushort *) 0x1F801DF6) * 4; z0_Lapf2 = *((ushort *) 0x1F801DF8) * 4; z0_Rapf2 = *((ushort *) 0x1F801DFA) * 4; gLIN = *((short *) 0x1F801DFC) / 32768.0; gRIN = *((short *) 0x1F801DFE) / 32768.0; z1_Lsame = z0_Lsame - 1; z1_Rsame = z0_Rsame - 1; z1_Ldiff = z0_Ldiff - 1; z1_Rdiff = z0_Rdiff - 1; zm_Lapf1 = z0_Lapf1 - mAPF1; zm_Rapf1 = z0_Rapf1 - mAPF1; zm_Lapf2 = z0_Lapf2 - mAPF2; zm_Rapf2 = z0_Rapf2 - mAPF2; for (;;) { /* * LoadFromLowPassFilterは35もしくは39タップのFIRフィルタ * 最外周の係数が0でも結果は同じなので、35か39かは特定できず */ L_in = gLIN * LoadFromLowPassFilterL(); R_in = gRIN * LoadFromLowPassFilterR(); /* * Left -> Wall -> Left Reflection */ L_temp = ReadReverbWork(zm_Lsame); R_temp = ReadReverbWork(zm_Rsame); L_same = L_in + gWALL * L_temp; R_same = R_in + gWALL * R_temp; L_temp = ReadReverbWork(z1_Lsame); R_temp = ReadReverbWork(z1_Rsame); L_same = L_temp + gIIR * (L_same - L_temp); R_same = R_temp + gIIR * (R_same - R_temp); /* * Left -> Wall -> Right Reflection */ L_temp = ReadReverbWork(zm_Rdiff); R_temp = ReadReverbWork(zm_Ldiff); L_diff = L_in + gWALL * L_temp; R_diff = R_in + gWALL * R_temp; L_temp = ReadReverbWork(z1_Ldiff); R_temp = ReadReverbWork(z1_Rdiff); L_diff = L_temp + gIIR * (L_diff - L_temp); R_diff = R_temp + gIIR * (R_diff - R_temp); /* * Early Echo(Comb Filter) */ L_in = gCOMB1 * ReadReverbWork(m1_Lcomb) + gCOMB2 *ReadReverbWork(m2_Lcomb) + gCOMB3 *ReadReverbWork(m3_Lcomb) + gCOMB4 *ReadReverbWork(m4_Lcomb); R_in = gCOMB1 * ReadReverbWork(m1_Rcomb) + gCOMB2 *ReadReverbWork(m2_Rcomb) + gCOMB3 *ReadReverbWork(m3_Rcomb) + gCOMB4 *ReadReverbWork(m4_Rcomb); /* * Late Reverb(Two All Pass Filters) */ L_temp = ReadReverbWork(zm_Lapf1); R_temp = ReadReverbWork(zm_Rapf1); L_apf1 = L_in - gAPF1 * L_temp; R_apf1 = R_in - gAPF1 * R_temp; L_in = L_temp + gAPF1 * L_apf1; R_in = R_temp + gAPF1 * R_apf1; L_temp = ReadReverbWork(zm_Lapf2); R_temp = ReadReverbWork(zm_Rapf2); L_apf2 = L_in - gAPF2 * L_temp; R_apf2 = R_in - gAPF2 * R_temp; L_in = L_temp + gAPF2 * L_apf2; R_in = R_temp + gAPF2 * R_apf2; /* * Output */ SetOutputL(L_in); SetOutputR(R_in); /* * Write Buffer */ WriteReverbWork(z0_Lsame, L_same); WriteReverbWork(z0_Rsame, R_same); WriteReverbWork(z0_Ldiff, L_diff); WriteReverbWork(z0_Rdiff, R_diff); WriteReverbWork(z0_Lapf1, L_apf1); WriteReverbWork(z0_Rapf1, R_apf1); WriteReverbWork(z0_Lapf2, L_apf2); WriteReverbWork(z0_Rapf2, R_apf2); /* * Update Circular Buffer */ UpdateReverbWork(); } |
番号 | コマンド名 | 数 | 1 | 2 | 3 |
---|---|---|---|---|---|
0x00 | Sync | ||||
0x01 | Nop | 0 | |||
0x02 | Setloc | 3 | AMin | ASec | AFrac |
0x03 | Play | 0/1 | Track | ||
0x04 | Forward | 0 | |||
0x05 | Backward | 0 | |||
0x06 | ReadN | 0 | |||
0x07 | Standby | 0 | |||
0x08 | Stop | 0 | |||
0x09 | Pause | 0 | |||
0x0A | Reset | 0 | |||
0x0B | Mute | 0 | |||
0x0C | Demute | 0 | |||
0x0D | Setfilter | 2 | File | Chan | |
0x0E | Setmode | 1 | Mode | ||
0x0F | Getparam | 0 | |||
0x10 | GetlocL | 0 | |||
0x11 | GetlocP | 0 | |||
0x12 | ReadT | 1 | Session? | ||
0x13 | GetTN | 0 | |||
0x14 | GetTD | 1 | Track | ||
0x15 | SeekL | 0 | |||
0x16 | SeekP | 0 | |||
0x17 | Setclock | ||||
0x18 | Getclock | ||||
0x19 | Test | 1 | Num | ||
0x1A | Id | 0 | |||
0x1B | ReadS | 0 | |||
0x1C | Init | 0 | |||
0x1D | |||||
0x1E | ReadTOC | 0 | |||
0x1F |
番号 | コマンド名 | 所要 クロック |
σ% | 割り 込み |
数 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0x00 | Sync | ||||||||||||
0x01 | Nop | 30547 | 23 | 0x03 | 1 | Stat | |||||||
0x02 | Setloc | 31557 | 7 | 0x03 | 1 | Stat | |||||||
0x03 | Play | 51444 | 0x03 | 1 | Stat | ||||||||
0x04 | Forward | 193250 | 12 | 0x03 | 1 | Stat | |||||||
0x05 | Backward | 125119 | 0 | 0x03 | 1 | Stat | |||||||
0x06 | ReadN | 75701 | 7 | 0x03 | 1 | Stat | |||||||
0x07 | Standby | 74753 | 5 | 0x03 | 1 | Stat | |||||||
0x08 | Stop | 43503 | 13 | 0x03 | 1 | Stat | |||||||
0x09 | Pause | 27648 | 24 | 0x03 | 1 | Stat | |||||||
0x0A | Reset | 80295 | 12 | 0x03 | 1 | Stat | |||||||
0x0B | Mute | 36114 | 41 | 0x03 | 1 | Stat | |||||||
0x0C | Demute | 32768 | 10 | 0x03 | 1 | Stat | |||||||
0x0D | Setfilter | 39105 | 48 | 0x03 | 1 | Stat | |||||||
0x0E | Setmode | 41082 | 52 | 0x03 | 1 | Stat | |||||||
0x0F | Getparam | 40163 | 25 | 0x03 | 5 | Stat | Mode | 0x00 | File | Chan | |||
0x10 | GetlocL | 43335 | 20 | 0x03 | 8 | セクタの13番目~20番目のデータ(つまりRead,SeekLが必要) | |||||||
0x11 | GetlocP | 37937 | 17 | 0x03 | 8 | Track | Index | Min | Sec | Frac | AMin | ASec | AFrac |
0x12 | ReadT | 98762 | 13 | 0x03 | 1 | Stat | |||||||
0x13 | GetTN | 40833 | 0x03 | 3 | Stat | Begin | End | ||||||
0x14 | GetTD | 39091 | 0x03 | 3 | Stat | AMin | ASec | ||||||
0x15 | SeekL | 41771 | 25 | 0x03 | 1 | Stat | |||||||
0x16 | SeekP | 62041 | 40 | 0x03 | 1 | Stat | |||||||
0x17 | Setclock | ||||||||||||
0x18 | Getclock | ||||||||||||
0x19 | Test(0x20) | 40864 | 14 | 0x03 | 4 | Year | Mon | Day | Ver | ||||
0x1A | Id | 30980 | 22 | 0x03 | 1 | Stat | |||||||
0x1B | ReadS | 81925 | 2 | 0x03 | 1 | Stat | |||||||
0x1C | Init | 25847 | 29 | 0x03 | 1 | Stat | |||||||
0x1D | |||||||||||||
0x1E | ReadTOC | 73292 | 10 | 0x03 | 1 | Stat | |||||||
0x1F |
番号 | コマンド名 | 所要 クロック |
σ% | 割り 込み |
数 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0x00 | Sync | ||||||||||||
0x01 | Nop | ||||||||||||
0x02 | Setloc | ||||||||||||
0x03 | Play | 0x01 | 8 | Stat | Track | Index | A/Min | A/Sec | A/Frac | LvHi | LvLo | ||
0x04 | Forward | ||||||||||||
0x05 | Backward | ||||||||||||
0x06 | ReadN | 0x01 | 1 | Stat | |||||||||
0x07 | Standby | 51429486 | 55 | 0x02 | 1 | Stat | |||||||
0x08 | Stop | 12767194 | 2 | 0x02 | 1 | Stat | |||||||
0x09 | Pause | 3665794 | 0 | 0x02 | 1 | Stat | |||||||
0x0A | Reset | 3656479 | 6 | 0x02 | 1 | Stat | |||||||
0x0B | Mute | ||||||||||||
0x0C | Demute | ||||||||||||
0x0D | Setfilter | ||||||||||||
0x0E | Setmode | ||||||||||||
0x0F | Getparam | ||||||||||||
0x10 | GetlocL | ||||||||||||
0x11 | GetlocP | ||||||||||||
0x12 | ReadT | 57701665 | 0 | 0x02 | 1 | Stat | |||||||
0x13 | GetTN | ||||||||||||
0x14 | GetTD | ||||||||||||
0x15 | SeekL | 0x02 | 1 | Stat | |||||||||
0x16 | SeekP | 0x02 | 1 | Stat | |||||||||
0x17 | Setclock | ||||||||||||
0x18 | Getclock | ||||||||||||
0x19 | Test(0x20) | ||||||||||||
0x1A | Id | 19658 | 1 | 0x02 | 8 | 0x02 | 0x00 | 0x20 | 0x00 | 'S' | 'C' | 'E' | 'I' |
0x1B | ReadS | 0x01 | 1 | Stat | |||||||||
0x1C | Init | ||||||||||||
0x1D | |||||||||||||
0x1E | ReadTOC | 31330364 | 14 | 0x02 | 1 | Stat | |||||||
0x1F |
コマンド名 | 補足事項 |
---|---|
Play | Setmodeで0x04を立てると、AFrac(BCD)の下位4ビットが0の時にレポートを返す。上位が奇数の時は位置情報がトラック相対になるが、その際フラグとしてSec(BCD)の0x80が立つ。 |
Reset | 位置もホームポジションに戻る。 |
ReadT | そろそろ公表してもよかろう。こちらが本当の意味でのTOC更新コマンドである。引数は0x01以外上手くいかないので、おそらくセッションを意味し、ドライブとしてはマルチセッション対応と推測される。 |
ReadTOC | そろそろ公表してもよかろう。TOC更新はディスク入れ替えや電源ON時にシステムが自動でやるか、上記のReadTで行う。 こいつはプロテクト情報(リードイントラックのユーザデータ領域?)を読みにいくだけでTOCの更新は行わない。実機能としてはReadMAGICとでも呼ぶべきもの。この原理により、蓋開閉検知のポッチを押したままにして、ディスク入れ替えを検知させないようにすると、正規のゲームディスクのプロテクト情報を読ませてから、コピーディスクに入れ替え、ReadTでTOCを更新することで、コピーを起動させることが出来てしまった。そういうcaetla悪用クローンが出回った。そのため後期ゲームではゲーム中に再度ReadTOCを実行することが多くなった。 |
GetTD | 引数を0x00にすると全トラックの合計になる。 |
Init | 承認後も暫く時間をあける必要がある。 |
準備中
準備中
実践結果としてのエミュレータ「エミゅってしまうま(略称XEBRA)」を公開する。