Emulator SNES hanya berjarak beberapa piksel dari kesempurnaan absolut


Kami sangat dekat untuk membuat emulator yang dapat dengan sempurna menciptakan kembali semua fungsi perangkat keras dan perangkat lunak SNES yang sebenarnya.

Selama 15 tahun terakhir, sebagai coder untuk emulator bsnes, saya mencoba untuk menyempurnakan emulasi Super Nintendo, tetapi sekarang kita dihadapkan dengan masalah terakhir: pengaturan waktu yang akurat dari siklus clock prosesor video SNES. Untuk mencapai tahap akhir ketepatan emulasi ini, bantuan seluruh komunitas diperlukan, dan saya berharap atas dukungan Anda. Tapi pertama-tama, saya akan memberi tahu Anda apa yang telah kami capai.

Kondisi saat ini


Saat ini, situasi dengan emulasi SNES sangat baik. Terlepas dari periferal yang tidak biasa yang menahan persaingan (misalnya, klub golf dengan sensor cahaya , simulator sepeda, dan modem dial-up, yang digunakan di Jepang untuk taruhan pacuan kuda di Jepang), semua game SNES resmi berlisensi sepenuhnya dapat dimainkan, dan tidak ada permainan yang memiliki masalah nyata.

Emulasi SNES menjadi sangat tepat sehingga saya bahkan harus membagi emulator menjadi dua versi: higan , yang mengupayakan akurasi dan konsistensi mutlak dengan dokumentasi perangkat keras, dan bsnes , yang mengupayakan kecepatan, kemampuan luas, dan kemudahan penggunaan.

Baru-baru ini, di bidang persaingan SNES, banyak prestasi menarik telah diterima, termasuk:


โ€ฆ dan banyak lagi!

Jadi, apakah sudah selesai? Apakah semua orang bekerja dengan baik, selamat tinggal, dan terima kasih untuk ikannya? Ya tidak cukup.

Hari ini, kami telah mencapai keakuratan pada level beat dari hampir semua komponen SNES. Satu-satunya pengecualian adalah PPU (unit pemrosesan gambar, modul pemrosesan gambar) yang digunakan untuk menghasilkan bingkai video yang dikirimkan ke layar. Kita sebagian besar tahu bagaimana PPU bekerja, tetapi untuk beberapa fungsi kita harus menggunakan dugaan, yang mengarah pada akurasi yang tidak sempurna.

Pada skala umum, masalah yang tersisa cukup kecil. Jika Anda tidak mengupayakan idealitas persaingan sempurna untuk kecintaan pada seni, maka saya tidak dapat meyakinkan Anda tentang perlunya meningkatkan emulasi PPU lebih lanjut. Seperti dalam bidang apa pun, semakin dekat kita dengan yang ideal, semakin rendah pengembaliannya.

Tapi saya bisa mengatakan mengapa hal ini penting untuk saya : ini adalah karya seluruh hidup saya, dan saya tidak ingin saya untuk mengatakan bahwa aku begitu dekat dengan penyelesaian tanpa mengambil langkah terakhir. Saya menua dan saya tidak abadi. Saya ingin bagian terakhir dari teka-teki diselesaikan, sehingga, setelah pensiun, saya yakin bahwa warisan SNES dapat diandalkan dan sepenuhnya dipertahankan berkat emulasi. Saya ingin mengatakan bahwa masalahnya sudah terpecahkan .

Jika Anda masih tertarik kemudian lanjutkan membaca untuk berkenalan dengan latar belakang masalah dan solusi yang ditawarkan oleh saya.

Pemodelan Arsitektur SNES


Mari kita mulai dengan mendaftar komponen yang membentuk SNES:


Diagram sistem Super NES.

Panah menunjukkan arah di mana berbagai prosesor SNES dapat bertukar data satu sama lain, dan garis putus-putus menunjukkan koneksi ke chip memori.

Yang paling penting bagi kami sekarang adalah memperhatikan bahwa output video dan suara ditransmisikan langsung dari PPU dan DSP. Ini berarti bahwa mereka bertindak sebagai "kotak hitam", dan kita tidak bisa melihat apa yang terjadi di dalamnya. Nanti itu akan menjadi penting bagi kita.

Ketepatan


Bayangkan bahwa kita mengemulasi perintah CPU โ€œmultiplyโ€, yang mengambil dua register (variabel), mengalikannya, menerima hasilnya dan beberapa flag yang mengindikasikan keadaan hasil (misalnya, overflow ).

Kita dapat menulis sebuah program yang mengalikan nilai yang mungkin dari 0 hingga 255 sebagai faktor dan pengganda. Kemudian kita dapat memperoleh hasil penggandaan angka dan bendera. Jadi, kita mendapatkan dua tabel dari 65.536 elemen.

Dengan menganalisis tabel ini, kita dapat secara akurat menentukan bagaimana dan di mana hasil perhitungan CPU diatur dengan cara tertentu. Kemudian kita dapat memodifikasi emulator sehingga saat menjalankan tes yang sama kita mendapatkan tabel yang sama persis pada waktu yang sama.

Sekarang katakanlah bahwa CPU dapat melakukan penggandaan 16-bit x 16-bit. Saat menguji setiap nilai yang mungkin, 4 miliar hasil akan dihasilkan, yang hampir tidak mungkin diuji dalam jumlah waktu yang wajar. Jika CPU memiliki multiplikasi 32 bit x 32 bit, maka dalam praktiknya tidak akan mungkin untuk menguji semua kombinasi nilai input sebelum kematian termal Semesta (setidaknya pada tingkat teknologi saat ini).

Dalam kasus seperti itu, kami bertindak lebih selektif dalam pengujian dan mencoba menentukan kapan bendera dapat benar-benar berubah, kapan hasilnya mungkin meluap, dan sebagainya. Kalau tidak, kita harus menjalankan tes yang tidak akan pernah berakhir.

Perkalian adalah operasi yang agak sepele, tetapi prinsip yang sama dapat diperluas ke seluruh proses rekayasa terbalik, termasuk operasi yang lebih kompleks, misalnya, transmisi data melalui DMA (akses memori langsung) selama pengembalian horizontal balok. Kami membuat tes yang mencoba untuk menentukan apa yang terjadi dalam kasus batas, dan kemudian memeriksa apakah persaingan kami berperilaku identik dengan perilaku SNES nyata.

Generator Sinyal dan Ketukan


SNES memiliki dua generator sinyal (osilator): osilator kristal yang beroperasi pada frekuensi sekitar 21 MHz (mengendalikan modul CPU dan PPU), dan resonator keramik yang beroperasi pada frekuensi sekitar 24 MHz, yang mengontrol SMP dan DSP. Dalam coprocessors cartridge, kadang-kadang osilator kristal 21 MHz digunakan, dan kadang-kadang generator sinyal sendiri bekerja dengan frekuensi lain.


Menciptakan papan sirkuit Super Famicom ini dalam kode lebih sulit daripada kedengarannya.

Jam adalah elemen dasar dari pengaturan waktu sistem apa pun, dan SNES dirancang untuk melakukan berbagai tugas dengan frekuensi dan interval waktu tertentu.

Jika Anda membayangkan jam 100 hertz, ini akan menjadi perangkat dengan keluaran biner yang beralih ke kondisi logis sinyal yang tinggi (misalnya, +5 V), dan kemudian ke status sinyal yang rendah (0 V, atau arde) 100 kali per detik. Artinya, setiap detik tegangan pada output akan berfluktuasi 200 kali: meningkat 100 kali dan 100 kali menurunkan bagian depan sinyal clock.

Siklus jam biasanya dianggap sebagai transisi penuh, yaitu siklus 100 Hz akan menghasilkan 100 siklus clock per detik. Beberapa sistem memerlukan perbedaan antara naik dan turunnya tepi, dan bagi mereka kami memecah siklus menjadi setengah-siklus untuk menunjukkan setiap fase (tinggi atau rendah) dari sinyal clock.

Tugas paling penting dari sebuah emulator yang akurat adalah menyelesaikan tugas dengan cara yang persis sama dan pada waktu yang sama persis seperti pada peralatan nyata. Namun, tidak terlalu penting bagaimana tugas dilakukan. Satu-satunya hal yang penting adalah bahwa emulator, menerima sinyal input yang sama, menghasilkan sinyal output yang sama dalam waktu yang sama seperti pada perangkat keras nyata.

Pengaturan waktu


Terkadang operasi membutuhkan waktu. Ambil contoh perkalian dalam SNES CPU. Alih-alih berhenti dan menunggu perkalian selesai, SNES CPU menghitung hasil perkalian satu per satu di latar belakang selama delapan siklus clock opcode CPU. Ini berpotensi memungkinkan kode untuk melakukan tugas-tugas lain sambil menunggu perkalian selesai.

Kemungkinan besar, perangkat lunak komersial apa pun akan menunggu selama delapan siklus ini, karena jika Anda mencoba membaca hasilnya sebelum siap, kami akan mendapatkan hasil yang diselesaikan sebagian. Namun, sebelum SNES, emulator memberikan hasil yang benar secara instan , tanpa menunggu siklus clock tambahan ini.

Ketika penggemar konsol mulai membuat dan menguji perangkat lunak yang ditulis sendiri dalam emulator, perbedaan ini mulai menyebabkan masalah tertentu. Sebagai bagian dari peranti lunak, misalnya, banyak peretas ROM Dunia Super Mario pertama , bekerja dengan benar hanya di emulator lama ini, tetapi tidak pada peranti keras SNES yang asli. Ini terjadi karena mereka dikembangkan dengan mempertimbangkan instan (tidak dapat diandalkan dari sudut pandang peralatan nyata) untuk memperoleh hasil perkalian.

Dalam proses meningkatkan emulator, kompatibilitas perangkat lunak lama rusak, dan karena itu kami harus menambahkan opsi kompatibilitas ke emulator baru agar tidak kehilangan program ini. Ya, tidak peduli seberapa nyata kedengarannya, tetapi hari ini emulator harus meniru emulator lainnya!

Kemudahan penundaan multiplikasi dalam CPU ini terletak pada kenyataan bahwa hal itu sangat dapat diprediksi: siklus perhitungan delapan jam dimulai segera setelah permintaan operasi penggandaan. Dengan menulis kode yang membaca hasil setelah setiap siklus, kami dapat memverifikasi bahwa SNES CPU menggunakan algoritma Booth untuk perkalian .

Sinkronisasi Jam


Operasi lain tidak mudah dimodelkan karena dijalankan secara tidak serempak di latar belakang. Salah satu kasus tersebut adalah pembaruan DRAM dari prosesor SNES pusat.

Selama rendering setiap baris raster, seluruh SNES CPU pada tahap tertentu menghentikan operasinya untuk waktu yang singkat sementara isi chip RAM diperbarui. Ini diperlukan karena untuk mengurangi biaya dalam SNES, RAM dinamis (daripada statis) digunakan sebagai memori utama CPU. Untuk menyimpan konten RAM dinamis, itu harus diperbarui secara berkala.


Untuk membuat emulator yang benar-benar sempurna tidak cukup untuk memastikan pemutaran ketiga setengah ribu game SNES. Juga diperlukan untuk mencapai simulasi masing-masing fungsi sistem dengan akurasi kebijaksanaan yang sempurna.

Faktor kunci dalam analisis ketepatan waktu operasi ini adalah kemungkinan menggunakan penghitung PPU horizontal dan vertikal. Penghitung ini melakukan peningkatan dan direset setelah setiap perjalanan balok horisontal dan vertikal terbalik. Namun, akurasinya hanya seperempat dari frekuensi generator sinyal CPU SNES; dengan kata lain, penghitung horizontal bertambah setiap empat siklus jam.

Membaca beberapa kali nilai dari penghitung, saya dapat menentukan seperempat dari siklus jam penghitung mana yang selaras. Menggabungkan pengetahuan ini dengan fungsi-fungsi yang dibuat khusus yang dapat mengambil langkah menuju jumlah siklus clock yang tepat yang ditunjukkan oleh pengguna, saya dapat dengan sempurna mencocokkan CPU SNES dengan posisi tepat dari siklus clock yang saya butuhkan.

Berkat lintasan berulang dari banyak siklus clock, saya dapat menentukan kapan operasi tertentu benar-benar terjadi (misalnya, memperbarui DRAM, mentransmisikan HDMA, interupsi polling, dll.). Setelah itu, saya bisa membuat ulang semua ini dalam persaingan.

Chip SMPKonsol SNES juga memiliki timer sendiri, dan reverse engineering yang berhasil juga dilakukan untuk prosesor ini. Saya dapat mencurahkan seluruh artikel hanya untuk register SMP TEST, yang memungkinkan programmer untuk mengontrol pembagi frekuensi SMP dan pengatur waktunya, belum lagi hal-hal buruk lainnya. Sudah cukup untuk mengatakan bahwa itu bukan proses yang mudah dan cepat, tetapi pada akhirnya kami menang.

Kami mengumpulkan coprocessor



Chip SuperFX hanyalah salah satu dari banyak coprocessor kartrid yang dapat ditangani oleh emulator SNES.

Ada sejumlah besar prosesor SNES yang digunakan di dalam berbagai kartrid permainan yang juga harus kami jinak. Dari masing-masing CPU serba guna seperti SuperFX dan SA-1 , prosesor sinyal digital seperti DSP-1 dan Cx4 hingga akselerator dekompresi seperti S-DD1 dan SPC7110, atau jam real-time Sharp dan Epson, dan banyak lagi ...

Ini berarti bahwa emulator SNES harus mengatasi instruksi SuperFX dan cache pixel; dengan skema resolusi konflik memori bus SA-1 (memungkinkan SNES dan SA-1 CPU menggunakan ROM dan chip RAM yang sama secara bersamaan); dengan firmware terintegrasi DSP-1 dan Cx4; dengan encoders aritmatika berbasis prediksi S-DD1 dan SPC7110; serta dengan kasus garis batas BCD (desimal berkode biner) di generator waktu nyata. Perlahan tapi pasti, menggunakan semua teknik untuk menentukan kebenaran dan timing yang dijelaskan di atas, kami berhasil belajar bagaimana meniru semua chip ini dengan hampir sempurna.

Butuh banyak upaya dan ribuan dolar untuk menghapus penutup chip dan menghapus firmware dari prosesor sinyal digital yang digunakan dalam gim yang berbeda. Dalam satu kasus, emulasi NEC uPD772x diizinkangunakan kode dari higan untuk menyimpan suara almarhum Stephen Hawking! .

Dalam kasus lain, kami perlu merekayasa balik seluruh rangkaian instruksi untuk arsitektur Hitachi HG51B, karena tidak ada yang pernah menerbitkan dokumentasi untuk arsitektur ini. Dalam kasus lain, ternyata satu game ( Hayazashi Nidan Morita Shougi 2 ) memiliki CPU ARM6 32-bit yang kuat dengan frekuensi 21 MHz, yang mempercepat game shogi Jepang!

Menyelamatkan semua prosesor SNES ternyata merupakan proses jangka panjang, penuh kesulitan dan kejutan.

Pemrosesan sinyal digital


Chip Sony S-DSP (Digital Signal Processor), yang tidak boleh disamakan dengan coprocessor cartridge DSP-1, menghasilkan suara SNES yang unik. Dalam chip ini, delapan saluran audio dengan encoding ADPCM 4-bit terhubung, yang memastikan terciptanya sinyal stereo 16-bit.

Dari luar, dan dari diagram sistem yang disajikan di atas, pada awalnya tampak bahwa DSP adalah "kotak hitam": kami menyesuaikan saluran suara dan parameter mixer, setelah itu chip menghasilkan suara yang dikirim ke speaker.

Tapi satu fungsi penting memungkinkan pengembang di bawah nama panggilan blargg untuk melakukan rekayasa balik lengkap dari chip ini: itu adalah buffer gema. SNES DSP memiliki fungsi yang menggabungkan output dari sampel sebelumnya untuk membuat efek gema. Ini terjadi di bagian paling akhir dari proses pembuatan suara (selain dari bendera penghalang suara yang terakhir, yang dapat digunakan untuk mematikan seluruh keluaran suara.)

Dengan menulis kode dengan waktu yang tepat dari langkah-langkah dan melacak gema yang dihasilkan, kami dapat menentukan urutan operasi yang dilakukan oleh DSP untuk menghasilkan dari setiap sampel dan menciptakan suara yang sempurna dan ketepatan mengalahkan.

Menyimpan PPU


Semua ini membawa kami ke bagian terakhir dari skema arsitektur SNES: chip PPU-1 dan PPU-2. Berkat John McMaster, kami memiliki pemindaian chip S-PPU1 (revisi 1) dan S-PPU2 (revisi 3) dengan peningkatan dua kali lipat.


Pemindaian dua kali lipat kristal PPU SNES pertama ...


... dan PPU kedua.

Kedua pemindaian kristal memberi tahu kami bahwa chip jelas bukan CPU untuk keperluan umum, juga bukan arsitektur khusus yang mengeksekusi kode operasi dari ROM internal dari program firmware. Ini adalah sirkuit logis terpisah dengan logika kode-keras yang menerima sinyal masuk dari register dan memori yang berbeda, dan membuat sinyal video ke monitor satu garis raster pada satu waktu.

PPU tetap menjadi hambatan terakhir untuk meniru SNES karena, tidak seperti semua komponen yang dijelaskan di atas, PPU sebenarnya adalah kotak hitam. Kami dapat mengonfigurasinya ke keadaan apa pun, tetapi SNES CPU tidak dapat secara langsung memantau apa yang mereka hasilkan.

Jika kita menggunakan contoh sebelumnya dengan perkalian sebagai analogi, bayangkan Anda meminta hasilnya 3 * 7, tetapi alih-alih jawaban biner, Anda mendapatkan gambar analog fuzzy dari angka "21" di layar. Siapa pun yang menjalankan perangkat lunak Anda akan dapat melihat 21, tetapi Anda tidak dapat menulis program pengujian untuk secara otomatis memeriksa apakah dia melihat jawaban yang benar. Verifikasi manual seseorang atas hasil semacam itu tidak dapat ditingkatkan ke lebih dari beberapa ribu tes, dan jutaan akan diminta untuk memaksimalkan perilaku PPU.

Saya tahu apa yang Anda pikirkan: "Tetapi apakah lebih mudah menggunakan kartu tangkap, melakukan pemrosesan gambar, kira-kira membandingkannya dengan gambar pada layar digital emulator, dan melakukan tes berdasarkan ini?"

Ya, itu mungkin! Apalagi jika tes ini untuk memeriksa dua angka besar yang menempati seluruh layar.

Tetapi bagaimana jika pengujian memiliki banyak nuansa, dan kami mencoba mengenali perbedaan warna setengah-nada dari satu piksel? Bagaimana jika kita ingin menjalankan sejuta tes secara berurutan, dan kita tidak selalu tahu apa yang akan kita hasilkan, tetapi masih ingin membandingkan hasilnya dengan output persaingan kita?

Tidak ada yang mengalahkan kenyamanan dan akurasi dengan data digital - aliran bit yang akurat yang hanya bisa cocok atau tidak cocok. Sifat analog dari sinyal CRT tidak dapat memberi kita ini.

Mengapa ini penting?


Dengan pengecualian satu game ( Patroli Serangan Udara ), semua perangkat lunak SNES resmi berlisensi (seharusnya) berdasarkan string raster. Game-game ini tidak mencoba untuk mengubah keadaan rendering PPU di tengah garis raster yang diberikan saat ini (trik seperti itu oleh programmer disebut "efek raster"). Ini berarti bahwa waktu pelaksanaan sebagian besar permainan tidak harus akurat; jika Anda punya waktu untuk garis raster penuh berikutnya, maka semuanya beres.

Tapi ini penting untuk satu game tunggal.




Rangkaian gambar ini menunjukkan efek persaingan yang kompleks yang digunakan dalam pesan "Keberuntungan" Patroli Serangan Udara .

Pada gambar di atas, Anda melihat teks frame-by-frame "Good Luck" dari Air Strike Patrol . Gim ini mengimplementasikannya dengan mengubah posisi menggulirkan layer 3 (BG3) secara vertikal. Namun, tampilan dasbor di sebelah kiri (di mana Anda dapat melihat bahwa pemain memiliki 39 rudal) juga berada di lapisan latar belakang yang sama.

Gim berhasil melakukan pemisahan ini dengan mengubah posisi gulir BG3 di setiap baris raster setelah merender dasbor kiri, tetapi sebelum teks "Keberuntungan" mulai ditampilkan. Ini dapat dilakukan karena di luar dasbor dan teks, BG3 transparan dan tidak ada yang menarik di antara kedua titik ini, terlepas dari nilai register pengguliran vertikal. Perilaku ini menunjukkan kepada kita bahwa register pengguliran dapat diubah pada setiap tahap rendering.


Bayangan kecil di bawah pesawat ini menyebabkan banyak sakit kepala bagi pengembang emulator yang terobsesi dengan presisi.

Gambar di atas menunjukkan bayangan pesawat yang terkenal. Efek ini diberikan dengan mengubah register kecerahan layar dengan riak pendek di atas lima garis raster.

Selama permainan, Anda dapat melihat bahwa bayangan ini agak kacau. Pada gambar di atas, tampilannya agak mirip huruf "c," tetapi bentuknya di setiap garis raster berubah panjang dan titik awal dengan masing-masing bingkai. Para pengembang Patroli Serangan Udara hanya secara kasar menguraikan di mana bayangan akan muncul, dan menyelesaikan masalah ini secara langsung. Dalam kebanyakan kasus ini berfungsi.

Emulasi yang benar dari perilaku semacam itu membutuhkan pengaturan waktu yang sempurna, yang benar-benar sangat sulit diperoleh dalam emulator .


Pada layar jeda Patroli Udara , efek raster digunakan yang tidak sengaja digunakan dalam game SNES lainnya.

Sekarang mari kita bicara tentang layar jeda. Menghidupkan BG3 sambil menggambar perbatasan kuning-hitam di sebelah kiri dan mematikannya lagi selama perbatasan yang sama di sebelah kanan untuk menggambar garis abu-abu di layar. Dia juga secara bergantian melalui frame mengganti garis raster di mana garis abu-abu ini ditampilkan untuk menciptakan efek jitter overlay.

Jika Anda memperbesar gambar yang ditiru yang ditunjukkan di atas, Anda akan melihat bahwa selama sepasang garis raster di sudut kiri garis abu-abu ini ada beberapa piksel yang hilang. Itu terjadi karena emulasi PPU saya 100% tidak sempurna dalam siklus clock. Dalam hal ini, itu menyebabkan efek mengaktifkan BG3 sedikit lebih lambat dari yang seharusnya.

Saya dapat dengan mudah mengubah timing sehingga gambar ini ditampilkan dengan benar. Tetapi perubahan seperti itu kemungkinan akan berdampak buruk pada game lain yang mengubah register tampilan PPU di tengah garis raster. Meskipun Air Strike Patrol adalah satu-satunya game yang melakukan ini dengan sengaja, setidaknya ada selusin game di mana hal ini terjadi secara kebetulan (mungkin IRQ menembak mereka terlalu cepat atau lambat).

Kadang-kadang ini menyebabkan kerusakan nyata pada gambar, yang tidak diperhatikan selama pengembangan (misalnya, di Balap Throttle Penuhselama transisi antara toko dan game). Kadang-kadang perekaman dilakukan ketika layar dinyatakan transparan, dan karenanya tidak menyebabkan anomali visual (misalnya, seperti dalam kasus menampilkan status HP di Dai Kaijuu Monogatari II .) Tetapi bahkan kasus perbatasan "tak terlihat" seperti itu dapat menyebabkan masalah dalam rendering garis raster yang kurang akurat. yang digunakan dalam emulator paling produktif.

Sekalipun Anda mengabaikan Patroli Serangan Udara , semua efek raster acak (tetapi valid) ini dalam perangkat lunak SNES tidak memungkinkan Anda untuk mendesain renderer PPU yang menghasilkan seluruh garis raster secara fungsional dengan akurasi jam sempurna.

Dalam kasus bsnes selama bertahun-tahun coba-coba, kami telah membuat daftar game semacam itu dengan "efek raster". Kami juga menciptakan posisi rendering individual yang memungkinkan rendering lebih cepat berdasarkan garis raster untuk menampilkan semua game ini dengan benar (kecuali untuk Patroli Serangan Udara , tentu saja). Tetapi pada dasarnya, ini adalah sekelompok peretasan yang tidak menyenangkan bagi kami, yang dirancang untuk permainan tertentu.

Saya juga memiliki penyaji PPU berbasis jam yang tidak memerlukan semua peretasan ini, tetapi dari waktu ke waktu itu menghasilkan perbedaan kecil (satu hingga empat piksel) dengan perenderan peralatan ini, seperti pada tangkapan layar di atas Patroli Serangan Udara .

Register Latch Internal


Alasan untuk semua kesalahan kecil ini adalah karena ketepatan waktu.

Katakanlah SNES membuat mode 7 yang terkenal , yang merupakan transformasi tekstur affine dengan perubahan parameter di setiap baris raster. Untuk menentukan piksel layar, Anda perlu melakukan perhitungan serupa:

px = a * klip (hoffset - hcenter) + b * klip (voffset - vcenter) +
b * y + (hcenter << 8)

py = c * klip (hoffset - hcenter) + d * klip (voffset - vcenter) +
d * y + (vcenter << 8)

SNES nyata tidak akan dapat menyelesaikan semua enam perkalian ini cukup cepat untuk setiap piksel yang diberikan dalam bingkai. Tetapi tidak ada dari nilai-nilai ini yang berubah untuk setiap piksel (atau, setidaknya, tidak boleh berubah), jadi kita hanya perlu menghitung px dan py satu kali di awal setiap baris raster. Artinya, PPU cache hasil statis dalam kait, yang pada dasarnya adalah salinan register PPU. Di masa depan, mereka dapat diubah, atau tetap tidak berubah.

Kemudian koordinat x, y ditransformasikan oleh mode 7 sebagai berikut:

ox = (px + a * x) >> 8

oy = (py + c * x) >> 8

Meskipun x bervariasi untuk setiap piksel, kami tahu bahwa kenaikan dilakukan oleh satu setiap kali. Berkat penyimpanan drive internal, kita cukup menambahkan nilai konstan a dan c ke ox dan oy untuk setiap piksel, daripada melakukan dua perkalian untuk setiap piksel.

Kemudian muncul pertanyaan di hadapan kita: di posisi manakah dari siklus clock apakah PPU membaca nilai a dan c dari register PPU eksternal yang diakses CPU?

Jika kami mengambilnya terlalu cepat, maka ini dapat merusak beberapa game. Jika kita terlambat, itu bisa merusak game lain.

Cara termudah adalah dengan menunggu laporan bug dan menyesuaikan posisi ini untuk memperbaiki masalah di setiap game tertentu. Tetapi dalam kasus ini, kita tidak akan pernah menemukan posisi yang tepat , hanya perkiraan mereka.

Dan setiap kali kami mengubah salah satu variabel ini, tidak realistis bagi kami untuk menguji ulang ketiga setengah ribu game dari perpustakaan SNES untuk mendeteksi penurunan yang dapat dilakukan oleh perubahan yang kami lakukan.

Keluar dari wajan ke dalam api



Interpretasi artistik dari proses menghilangkan kesalahan emulasi.

Metodologi pengujian yang serupa, "kami hanya akan membuat game yang kami tertarik bekerja dengan biaya berapa pun" menyebabkan fenomena, yang saya sebut emulasi "dari api, tetapi ke api".

Pada awal pengembangan emulasi SNES, ketika masalah muncul dalam game, maka setiap koreksi dalam game ini yang memungkinkannya untuk bekerja diterima dan ditambahkan ke emulator. Perbaikan ini tentu merusak beberapa gim lainnya. Dan kemudian mereka memperbaiki game ini , setelah yang ketiga pecah. Memperbaiki game ketiga kembali mematahkan yang pertama. Ini berlangsung selama bertahun-tahun.

Kesalahannya di sini adalah bahwa pengembang mencoba untuk memperhitungkan hanya satu variabel pada satu waktu. Misalkan kita memiliki permainan, dan agar bisa berfungsi, peristiwa harus terjadi antara ukuran 20 dan 120. Kami tidak tahu ukuran pasti, jadi pilih saja 70, tepatnya di tengah.

Kemudian kami mendapatkan laporan bug di game lain, dan menentukan bahwa agar game ini berfungsi , nilai ukurannya harus antara 10 dan 60. Jadi sekarang kami ubah ke 40, yang berfungsi untuk kedua game. Kedengarannya logis!

Tapi kemudian game ketiga muncul , di mana acara tersebut harus bekerja antara ukuran 80 dan 160! Sekarang kami tidak dapat membuat ketiga game bekerja secara bersamaan dengan nilai yang sama.

Ini memaksa pengembang emulator untuk membuat retasan untuk game tertentu. Coders tidak ingin merilis emulator di mana Anda tidak dapat menjalankan Mario , Zelda atau Metroid . Oleh karena itu, untuk kasus umum, clock cycle 40 digunakan, tetapi saat memuat Metroid, kami memaksakan nilai pengaturan waktunya menjadi 100.

Bagaimana ini mungkin, mengapa dua game membutuhkan nilai yang berbeda? Ini terjadi karena tidak hanya satu variabel yang terlibat di sini. Waktu yang sebelumnya Anda gunakan untuk memicu acara lain dapat mempengaruhi nilai waktu yang diperlukan untuk acara berikutnya .

Bayangkan ini dalam bentuk ekspresi aljabar sederhana:

2x + y = 120

Anda dapat menyelesaikannya dengan mengambil x = 10, y = 100. Atau x = 20, y = 80. Atau x = 30, y = 60. Jika kami hanya memikirkan nilai x, yang memungkinkan Anda menjalankan satu set game secara bersamaan, maka kami kehilangan fakta bahwa sebenarnya masalahnya mungkin salah y!

Versi pertama emulator untuk meningkatkan kompatibilitas cukup mendefinisikan kembali nilai x tergantung pada game yang sedang berjalan. Peretasan gim individual seperti itu tetap ada, bahkan jika nilai x yang benar, satu kali ditemukan. Jadi y masalah tidak akan pernah terpecahkan!

Namun, dalam kasus SNES, tidak satu atau dua variabel yang terlibat secara bersamaan. PPU konsol SNES sendiri memiliki 52 register eksternal, yaitu sekitar 130 parameter. Dalam proses render garis raster tunggal, semua 130 parameter ini dan sejumlah register internal dan kait yang tidak diketahui terlibat. Ini terlalu banyak informasi bagi seseorang di luar untuk dapat mewujudkan keadaan penuh PPU pada titik waktu tertentu.

Aspek emulasi ini tidak jelas bagi yang belum tahu, tetapi sangat adil: akurasi tidak sama dengan kompatibilitas. Kami dapat membuat emulator dengan akurasi 99 persen, mampu menjalankan 10% game. Dan Anda dapat menulis emulator 80% akurat yang menjalankan 98% game. Terkadang implementasi yang benar dalam jangka pendek merusak game-game populer. Ini adalah pengorbanan yang diperlukan jika Anda mencoba untuk mencapai akurasi 100% dan kompatibilitas 100%.

Menyelesaikan masalah


Kita sampai pada tahap emulasi PPU saat ini berkat penalaran deduktif dan hasil di dunia nyata.

Kita tahu bahwa dua PPU memiliki akses ke dua chip VRAM. Kita tahu bahwa mereka dapat membaca dari setiap chip sejumlah byte data yang diketahui per baris raster. Kami tahu detail kasar tentang bagaimana masing-masing mode video SNES bekerja. Dan berdasarkan ini, kita dapat menguraikan pola umum tentang bagaimana arsitektur mungkin terlihat. Misalnya, berikut adalah contoh singkat tentang bagaimana tiga mode video SNES pertama dapat bekerja:

if (io.bgMode == 0) {

bg4.fetchNameTable ();

bg3.fetchNameTable ();

bg2.fetchNameTable ();

bg1.fetchNameTable ();

bg4.fetchCharacter (0);

bg3.fetchCharacter (0);

bg2.fetchCharacter (0);

bg1.fetchCharacter (0);

}

if (io.bgMode == 1) {

bg3.fetchNameTable ();

bg2.fetchNameTable ();

bg1.fetchNameTable ();

bg3.fetchCharacter (0);

bg2.fetchCharacter (0);

bg2.fetchCharacter (1);

bg1.fetchCharacter (0);

bg1.fetchCharacter (1);

}

if (io.bgMode == 2) {

bg2.fetchNameTable ();

bg1.fetchNameTable ();

bg3.fetchOffset(0);

bg3.fetchOffset(8);

bg2.fetchCharacter(0);

bg2.fetchCharacter(1);

bg1.fetchCharacter(0);

bg1.fetchCharacter(1);

}


PPU mengungkapkan kepada pengamat pihak ketiga hanya sebagian kecil dari negaranya: flag blanking horizontal / vertikal, jumlah pixel horizontal dan vertikal, dan flag tile overlay dalam interval untuk sprite. Ini tidak terlalu banyak, tapi saya ulangi - setiap elemen kecil dari negara yang dapat diakses oleh pengamat membantu kita.

VRAM (RAM video, memori video) dari chip PPU selama rendering tertutup untuk SNES CPU, bahkan untuk membaca. Tetapi ternyata, OAM (memori sprite) dan CGRAM (memori palet) terbuka. Kuncinya adalah bahwa pada saat ini, PPU mengontrol bus alamat. Oleh karena itu, membaca OAM dan CGRAM selama rendering layar, saya dapat mengamati apa yang PPU dapatkan dari dua blok memori ini pada saat yang kritis.

Ini bukan semua bagian dari teka-teki, tetapi cukup bagi saya untuk dapat menerapkan pola yang benar-benar benar untuk mendapatkan sprite.

Menggunakan pola akses untuk OAM dan CGRAM, bendera PPU terbuka, pengamatan umum (mis. Tebakan) dari laporan kesalahan untuk game yang berbeda, dan penalaran deduktif, kami dapat membuat render PPU berbasis jam yang hampir dapat dengan sempurna meluncurkan semua game yang dirilis.

Tetapi situasinya masih genting: jika seseorang mulai membuat game homebrew menggunakan waktu kutu dan efek raster yang akurat, maka semua emulator modern kita tidak akan mampu menangani ini. Termasuk implementasi perangkat lunak dan perangkat keras berdasarkan FPGA.

Saya harus mengatakan dengan jelas: hari ini semuanyamereka hanya mengetahui urutan operasi internal dan perilaku snap di chip PPU dari konsol SNES. Tidak ada yang tahu bagaimana meniru mereka dengan sempurna. Setidaknya untuk sekarang.

Solusi yang memungkinkan


Apa yang akan kita lakukan dengan ini? Bagaimana menentukan urutan operasi yang tepat dalam PPU jika, dari sudut pandang SNES CPU, ini adalah "kotak hitam"?

Saya melihat empat opsi yang memungkinkan: penganalisa logika, output video digital dalam mode uji, bangun dan melepas penutup dari chip.

Analisis logika


Jika Anda melihat pindaian kristal PPU yang ditunjukkan di atas, Anda akan melihat area hitam di tepi chip. Ini adalah platform yang menghubungkan ke kontak chip.

Pin ini menyimpan status chip PPU selama setiap siklus clock. Di sini Anda dapat menemukan alamat saat ini di mana chip mengakses chip memori video, nilai-nilai data yang ditransfer dari satu PPU ke yang kedua, dan banyak lagi.

Informasi ini tidak tersedia untuk kode yang berjalan pada SNES CPU, tetapi memberikan pengamatan berharga tentang urutan internal operasi PPU.


Menghubungkan PPU konsol Super NES ke penganalisis logika yang sama bisa menjadi kunci ke kotak hitam.

Masalah kritis dari penganalisa logika adalah mereka tidak terlalu nyaman untuk dikelola: jika Anda mencoba mengambil sampel data langsung dari sistem kerja, kami akan mendapatkan aliran hasil yang agak sulit untuk diuraikan. Anda akan mengalami masalah yang sama jika Anda mencoba untuk menganalisis output RGB analog dari sistem: untuk menangkap data ini, Anda harus secara manual melakukan masing-masing tes. Sistem seperti itu tidak terlalu baik untuk membuat tes regresi otomatis yang dapat direproduksi.

Output video digital dalam mode uji


Baru-baru ini, melalui pemindaian irisan kristal dengan perbesaran 20x, mode uji rahasia telah ditemukan dalam chip PPU konsol SNES. Jika Anda membuat modifikasi perangkat keras kecil, maka PPU akan mulai mengeluarkan sinyal RGB digital 15-bit !

Ini hampir yang kita butuhkan! Namun, mode ini memiliki masalah, karena mode terkenal 7 tidak dapat menampilkan gambar yang benar di dalamnya. Tampaknya fungsi ini belum sepenuhnya selesai.

Selain itu, untuk menerapkan metode ini, modifikasi manual konsol SNES dan mekanisme yang sesuai untuk menangkap dan menganalisis output dalam mode uji masih diperlukan. Namun demikian, tidak seperti solusi dengan menangkap sinyal RGB analog, sinyal digital seperti itu dapat mengalami pengujian otomatis, yang memungkinkan kita untuk dengan cepat menyelesaikan sejumlah besar pekerjaan pada rekayasa balik PPU.

Terbangun


Mengingat bahwa PPU bersifat statis, kami dapat menghapus chip PPU dari konsol SNES yang berfungsi dan menghubungkannya ke papan prototyping atau papan sirkuit yang dibuat khusus bersama dengan dua chip VRAM. Setelah itu, Anda dapat menempatkan mikrokontroler antara PPU dan antarmuka USB, dan menghubungkan antarmuka ke PC, yang memungkinkan encoder untuk memprogram semua register memori video eksternal dan PPU. Selain itu, encoder akan dapat secara manual mengontrol siklus clock PPU dan membaca sinyal yang dihasilkan pada konektor I / O, register dan dalam memori PPU di setiap siklus clock.

Dengan memodifikasi emulator perangkat lunak sehingga menghasilkan nilai internal yang sama dari konektor I / O, kita dapat langsung membandingkan perangkat keras nyata dengan emulasi, bahkan secara real time. Namun, ini akan menjadi kerja yang sangat sulit karena kami belum dapat melihat operasi PPU internal.

Penghapusan penutup


Terakhir, solusi paling ekstrem adalah mempelajari kristal lebih lanjut dengan melepas penutup chip. Kami sudah memiliki pemindaian kristal dengan pembesaran 20x, tetapi resolusi mereka tidak cukup untuk menganalisis dan membuat ulang rangkaian logika individu, seperti yang dilakukan dalam proyek Visual 6502 . Jika kita bisa mendapatkan pindaian kristal kedua PPU dengan perbesaran 100x, maka kita bisa memulai kerja keras menyusun sirkuit PPU dan mengubahnya menjadi tabel koneksi atau kode VHDL. Kemudian mereka dapat digunakan langsung dalam FPGA, serta porting ke C ++ atau bahasa pemrograman lain, yang berlaku untuk membuat emulator perangkat lunak.

Salah satu spesialis yang telah melakukan ini sebelumnya memberi saya perkiraan kasar: akan butuh sekitar 600 jam untuk memetakan kedua PPU. Tugas ini jauh lebih tinggi daripada tingkat "Mari kita mengumpulkan uang dengan menggalang dana dan membayar seseorang", dan idealnya jatuh ke dalam kategori "Mari kita berharap bahwa seseorang yang sangat berbakat dengan serangkaian keterampilan yang unik ingin membantu kita secara sukarela."

Tentu saja, ini tidak berarti bahwa saya tidak akan senang memberi imbalan finansial kepada seseorang atas bantuan mereka, saya dapat membayar untuk rincian yang diperlukan dan bekerja.

Minta bantuan


Untuk meringkas: Saya melangkah sejauh mungkin dalam proyek emulator SNES saya, dan saya perlu bantuan untuk menyelesaikan tugas akhir ini. Jika Anda telah membaca sampai akhir, maka Anda mungkin ingin membantu! Dukungan apa pun, termasuk partisipasi dalam proyek bsnes di GitHub , atau dokumentasi penelitian apa pun tentang cara kerja internal chip PPU, akan terbukti sangat berharga bagi kami!

Terima kasih telah membaca dan atas dukungan Anda! Merupakan suatu kehormatan bagi saya selama lima belas tahun untuk menjadi anggota komunitas emulasi SNES.

All Articles