1. Dari Silikon ke Matematika: Mesin Turing Universal dan Isomorfisme FSM
Nintendo Entertainment System (1983), Super Nintendo (1990), PlayStation 2 (2000), dan GameCube (2001) bukanlah komputer pribadi serbaguna, melainkan kumpulan sirkuit terpadu khusus (ASIC dan chip LSI kustom). Namun saat ini, tanpa memiliki satu keping pun silikon asli tersebut, kita dapat menjalankan ROM game komersialnya di PC atau ponsel pintar berprosesor x86-64 maupun ARM64 dengan ketepatan mutlak hingga ke level bit. Disiplin rekayasa perangkat lunak yang memungkinkan keajaiban ini adalah emulasi perangkat keras (Hardware Emulation).
Bagaimana sebuah program perangkat lunak pada arsitektur yang sama sekali berbeda dapat meniru perilaku fisik jutaan transistor dan osilator kristal? Landasan matematisnya berasal dari teorema paling fundamental dalam ilmu komputer yang dibuktikan oleh Alan Turing pada tahun 1936: Mesin Turing Universal (UTM):
$$U(\langle M \rangle, x) = M(x)$$Dipadukan dengan Tesis Church-Turing, persamaan ini membuktikan bahwa jika $M$ adalah perangkat keras fisik konsol, $\langle M \rangle$ adalah kode sumber emulator yang mengenkode aturan logika sirkuit $M$, dan $x$ adalah data ROM beserta input stik kendali, maka setiap komputer host Turing-complete $U$ yang menjalankan $\langle M \rangle$ pasti menghasilkan urutan status dan keluaran audiovisual $M(x)$ yang identik dengan konsol aslinya.
Dari sudut pandang rangkaian logika digital, setiap sirkuit digital sinkron yang digerakkan oleh detak мастер (CPU, PPU grafis, APU audio) secara matematis dapat direduksi menjadi Mesin Status Hingga Deterministik (Deterministic Finite State Machine, FSM). Misalkan $S_t \in \mathcal{S}$ adalah vektor status keseluruhan yang mencakup semua register, flag, VRAM, dan WRAM pada siklus detak diskrit $t$, serta $I_t \in \mathcal{I}$ adalah vektor input eksternal. Di antara siklus $t$ dan $t+1$, jaringan gerbang logika kombinasional menghitung dua fungsi matematika murni:
$$S_{t+1} = \delta(S_t, I_t), \qquad O_t = \lambda(S_t, I_t)$$di mana $\delta$ adalah fungsi transisi status dan $\lambda$ adalah fungsi keluaran yang menghasilkan piksel RGB serta sampel audio PCM. Aljabar Boolean yang diwujudkan secara fisik oleh tegangan listrik ($0\,\text{V}$ dan $3,3\,\text{V}/5\,\text{V}$) pada transistor CMOS isomorfik secara matematis dengan operasi bitwise (&, |, ^, ~, <<) yang dieksekusi CPU host pada variabel uint32_t dan uint64_t.
Oleh karena itu, tantangan terbesar dalam emulasi bukanlah komputabilitas, melainkan kompleksitas komputasi di bawah tenggat waktu nyata (Real-Time) yang ketat. Pada Super Nintendo NTSC dengan kristal master $21,477272\,\text{MHz}$, satu bingkai (frame) terdiri dari $262$ garis pindai (scanline) yang masing-masing berdurasi $1364$ siklus master:
$$f_{\text{frame}} = \frac{21.477.272\,\text{Hz}}{262 \times 1364} \approx 60,0988\,\text{Hz} \implies T_{\text{frame}} \approx 16,639\,\text{ms}$$Pada perangkat keras fisik, jutaan gerbang logika mengevaluasi $\delta(S_t, I_t)$ secara paralel spasial pada setiap nanodetik, sedangkan emulator harus melakukan serialisasi paralelisme masif tersebut menjadi aliran instruksi sekuensial dan menyelesaikan seluruh $357.368$ siklus bingkai dalam waktu kurang dari $16,639\,\text{milidetik}$.
2. Dasar Emulasi CPU: Dari Interpreter Switch hingga Rekompilasi Dinamis (JIT / Dynarec)
2.1 Interpreter Switch Klasik dan Penalti Prediksi Percabangan
Cara paling lugas untuk mengemulasikan CPU tamu (Guest) adalah meniru siklus Fetch $\to$ Decode $\to$ Execute di dalam perulangan while yang memuat pernyataan switch (opcode) berukuran besar.
Meskipun sederhana, struktur ini mengalami hambatan mikroarsitektur yang parah pada CPU superskalar modern: seluruh blok switch dikompilasi menjadi satu instruksi lompatan tidak langsung (jmp *[rax + rdx*8]). Karena opcode tamu terus berubah secara dinamis, prediktor percabangan tidak langsung (BTB) pada CPU host terus-menerus salah menebak target lompatan, memaksa pengosongan (flush) pipa instruksi 14–20 tahap dan membuang puluhan siklus pada setiap instruksi. Teknik Direct Threaded Code menggunakan ekstensi alamat label (goto *dispatch_table[opcode]) menyebarkan lompatan tidak langsung ke akhir setiap handler opcode, namun masih menyisakan beban dekode satu per satu.
2.2 Translasi Biner Dinamis (JIT / Dynarec) dan Eliminasi Flag Mati
Untuk menembus batas performa tersebut, emulator modern berkinerja tinggi (Dolphin, PCSX2, RPCS3, QEMU TCG, Apple Rosetta 2) menerapkan kompilasi Just-In-Time / Rekompilasi Dinamis (Dynarec): menerjemahkan satu blok dasar (Basic Block) kode mesin tamu sekaligus menjadi kode mesin asli host (x86-64 / ARM64) saat runtime dan mengeksekusinya langsung dari cache kode.
flowchart TD
G["Blok Dasar Kode Mesin Tamu (MIPS / PowerPC / 65816)"] --> L["1. Pengangkatan ke SSA IR (Instruction Lifting)<br/>Dekomposisi opcode tamu menjadi representasi menengah bebas-arsitektur"]
L --> O["2. Tahap Optimasi IR (Optimization Passes)<br/>Propagasi konstanta dan eliminasi flag mati (Dead-Flag Elision)"]
O --> R["3. Alokasi Register (Register Allocation)<br/>Pemetaan Linear Scan dari register tamu ke register fisik CPU host"]
R --> E["4. Emisi Kode & Perantaian Blok (Block Chaining)<br/>Penulisan ke memori eksekusi dan penambalan langsung lompatan jmp asli"]
E --> X["5. Eksekusi Asli Berkecepatan Penuh pada CPU Host<br/>Jebakan proteksi halaman menangkap kode pemodifikasi-diri (SMC)"]
Optimasi utama pada mesin Dynarec meliputi:
- Eliminasi Flag Mati (Dead-Flag Elision): Instruksi aritmetika tamu selalu memperbarui flag status (Z, N, C, V), padahal sebagian besar langsung ditimpa oleh instruksi berikutnya tanpa pernah dibaca. Melalui analisis keaktifan (Liveness Analysis) mundur pada IR SSA, JIT mampu membuang lebih dari $80\,\%$ perhitungan flag yang sia-sia.
- Alokasi Register Fisik: Algoritma Linear Scan menempatkan register tamu yang paling sering dipakai langsung di dalam register perangkat keras CPU host, menghilangkan akses baca/tulis berulang ke struktur
CPUStatedi RAM. - Perantaian Blok (Block Chaining): Ketika blok terkompilasi A melompat ke blok terkompilasi B, JIT menambal instruksi
jmpdi akhir blok A agar langsung menuju alamat memori asli blok B tanpa kembali ke perulangan dispatcher C++. - Deteksi Kode Pemodifikasi-Diri (SMC): Saat game menulis ulang kodenya sendiri di RAM, emulator menangkap penulisan tersebut melalui mask perangkat lunak atau proteksi halaman baca-saja (
mprotect/VirtualProtect) dan segera membatalkan (invalidate) blok JIT yang usang.
| Kriteria Evaluasi | Interpreter Switch | Interpreter Threaded | Rekompilator Dinamis (JIT / Dynarec) |
|---|---|---|---|
| Biaya per Instruksi Tamu | $20 \sim 80+$ instruksi host | $12 \sim 35$ instruksi host | $1 \sim 4$ instruksi asli host |
| Prediksi Percabangan | Sangat buruk (satu titik macet BTB) | Sedang (lompatan tersebar) | Sangat baik (lompatan langsung & chaining) |
| Eliminasi Flag Mati | Mustahil (tanpa konteks antar-opcode) | Sangat terbatas | Lengkap (analisis global pada SSA IR) |
| Kompleksitas & Portabilitas | Sangat mudah, C/Rust standar portabel | Butuh ekstensi kompiler | Sangat tinggi (butuh backend tiap arsitektur) |
| Kendali Akurasi Siklus | Sangat mudah (bisa berhenti tiap siklus) | Mudah | Sulit (siklus dihitung per batas blok) |
3. Bus Memori dan Fastmem: Membajak MMU Perangkat Keras Host
Pada konsol game, CPU mengendalikan chip video, audio, dan DMA dengan membaca serta menulis ke register Memory-Mapped I/O (MMIO). Memeriksa setiap akses memori dengan percabangan if-else perangkat lunak untuk membedakan RAM dan MMIO akan menghancurkan kecepatan JIT.
Teknik Fastmem mengatasi masalah ini dengan memanfaatkan Unit Manajemen Memori (MMU) perangkat keras dari sistem operasi host 64-bit:
- Emulator memesan ruang memori virtual berdampingan di ruang alamat 64-bit host sebesar seluruh ruang alamat tamu (misalnya $4\,\text{GB}$ melalui
mmap/VirtualAllocuntuk konsol 32-bit) dan menyimpan pointer dasarnya di satu register host (rbx). - Area RAM fisik beserta cerminannya dipetakan dengan izin baca dan tulis (
PROT_READ | PROT_WRITE). - Semua halaman yang mewakili register perangkat keras MMIO sengaja diatur tanpa izin akses (
PROT_NONE/PAGE_NOACCESS).
Dengan demikian, kompiler JIT dapat menerjemahkan setiap pembacaan memori tamu menjadi satu instruksi mesin asli tunggal:
| |
- Jalur Cepat ($99,9\,\%$ akses RAM biasa): MMU dan TLB fisik prosesor host menyelesaikan pembacaan dalam 3–4 siklus dengan beban perangkat lunak nol.
- Jalur Lambat ($0,1\,\%$ akses MMIO): Saat menyentuh halaman
PROT_NONE, MMU host langsung memicu page fault perangkat keras (SIGSEGVdi POSIX atau eksepsi ter-vektorSTATUS_ACCESS_VIOLATIONdi Windows). Penangan sinyal emulator menangkap jebakan tersebut, mendekode instruksi, memanggil fungsi C++mmio_read/write(), mengisi register tujuan, dan memajukan penunjuk instruksiRIP.
4. Akurasi Siklus (Cycle Accuracy) dan Sinkronisasi Korutin Multi-Chip
Mengapa mendiang arsitek emulator legendaris Near (byuu) pernah menyatakan bahwa mengemulasikan Super Nintendo berkecepatan $3,58\,\text{MHz}$ dengan akurasi siklus 100% membutuhkan CPU desktop modern berkecepatan $3\,\text{GHz}$?
Emulator lawas berbasis scanline menjalankan CPU selama satu garis penuh ($228$ siklus) terlebih dahulu, baru kemudian menyuruh PPU menggambar seluruh 256 piksel garis tersebut sekaligus. Namun, banyak game memodifikasi register grafis (palet warna, guliran layar, atau matriks Mode 7) tepat di tengah penggambaran garis pindai (Mid-Scanline) melalui CPU atau HDMA. Tanpa sinkronisasi tepat pada siklus pikselnya, tampilan akan rusak atau macet.
flowchart TD
subgraph Inaccurate["Emulasi Berbasis Scanline (Cepat tetapi gagal pada efek raster tengah-baris)"]
S1["1. CPU mengeksekusi 228 siklus sekaligus<br/>(mengubah register palet pada siklus ke-112)"] --> S2["2. PPU merender seluruh 256 piksel baris sesudahnya<br/>(keliru menerapkan palet baru sejak piksel ke-0!)"]
end
subgraph CycleAcc["Sinkronisasi Korutin Akurat-Siklus (Cycle-Accurate Catch-up)"]
C1["1. CPU berjalan tepat hingga siklus t = 112<br/>lalu menulis ke register MMIO palet PPU"] --> C2["2. Beralih seketika ke korutin PPU<br/>PPU mengejar waktu (Catch-up) merender piksel 0 hingga 111"]
C2 --> C3["3. Palet baru diterapkan tepat pada siklus t = 112<br/>PPU merender piksel 112 hingga 255 tanpa cacat sedikit pun"]
end
Dalam emulator akurat-siklus seperti bsnes / higan / Ares, CPU, PPU, SMP, dan DSP berjalan sebagai korutin kooperatif ruang pengguna (libco) yang independen. Setiap kali sebuah chip melakukan aksi yang dapat diamati chip lain, co_switch() mengalihkan konteks hanya dalam ~10 instruksi assembly agar chip target mengejar (Catch-up) waktu detak master saat itu.
Selain itu, rekayasa emulasi modern juga menjawab tiga tantangan mutakhir:
- Floating-Point Non-IEEE-754: Emotion Engine dan unit vektor PS2 tidak mengenal
InfmaupunNaNserta langsung mengubah angka denormal menjadi nol, sehingga PCSX2 harus menerapkan masking dan penjepitan SIMD agar fisika game tidak meledak. - Run-Ahead untuk Eliminasi Latensi Input: Dengan menghitung $N$ bingkai ke depan secara diam-diam di latar belakang menggunakan penyimpanan status (Savestates) super cepat di RAM, emulator melompati penundaan buffer internal game dan memberikan respons yang bahkan lebih cepat daripada konsol asli di TV tabung (CRT).
- FPGA dan Pelestarian Warisan Digital: Bersama rekreasi gerbang logika berbasis FPGA (MiSTer) dalam Verilog/VHDL, kode sumber emulator presisi berfungsi sebagai dokumentasi perangkat keras yang dapat dieksekusi, menyelamatkan sejarah seni interaktif manusia dari kerusakan fisik chip silikon abad ke-20 yang tak terhindarkan.
