Menjalankan mesin emulasi Zilog Z80 pada backend worker sering menghadapi masalah akurasi instruksi siklus mikro. Salah satu kendala paling umum saat memvalidasi Z80 core terhadap test suite standar (seperti ZEXALL atau fuse-emulator tests) adalah kegagalan penentuan nilai register F (Flag register), khususnya bit ke-2: flag Parity/Overflow (P/V).
Gejala Bug: Kegagalan Test Suite Aritmatika 8-Bit
Pada pipeline CI backend worker yang menjalankan virtual machine Z80 berbasis Go/C++, eksekusi instruksi aritmatika 8-bit seperti ADD A, n, ADC A, n, dan SUB n menghasilkan assertion failure pada test suite deterministik. Sementara instruksi logika seperti AND atau OR lolos pengujian, operasi aritmatika bertanda gagal menghasilkan status flag yang valid.
Contoh kegagalan trace register pada instruksi penambahan signed:
Input: A = 0x7F (127), B = 0x01 (1)
Opcode: ADD A, B
Hasil: A = 0x80 (-128)
Expected F: S=1, Z=0, H=1, P/V=1, N=0, C=0 (0x94)
Actual F: S=1, Z=0, H=1, P/V=0, N=0, C=0 (0x90) -> FAIL! Flag P/V mismatch.Root Cause: Kerancuan Parity Checking vs Signed Overflow
Arsitektur Zilog Z80 membagi peran bit 2 register F untuk dua fungsi yang saling eksklusif tergantung pada kelompok instruksinya:
- Parity (P): Digunakan pada operasi logika (
AND,OR,XOR). Bernilai1jika jumlah total bit bernilai 1 pada hasil operasi adalah genap, dan0jika ganjil. - Overflow (V): Digunakan pada operasi aritmatika 2's complement (
ADD,ADC,SUB,SBC). Bernilai1jika hasil operasi melampaui batas signed range integer 8-bit (-128 hingga +127), dan0jika tidak terjadi overflow.
Root cause muncul karena backend engine secara keliru mengaplikasikan fungsi penghitung parity bitwise ke seluruh operasi ALU, mengabaikan fakta bahwa operasi aritmatika memerlukan deteksi signed overflow.
Secara matematis, signed overflow pada penjumlahan A + B = R terjadi jika kedua operand memiliki sign bit yang sama (keduanya positif atau keduanya negatif), tetapi sign bit hasil R berbeda:
V = ((A ^ R) & (B ^ R) & 0x80) != 0Sedangkan pada pengurangan A - B = R, overflow terjadi jika operand memiliki sign bit berlawanan dan hasil R memiliki sign bit yang berbeda dari A:
V = ((A ^ B) & (A ^ R) & 0x80) != 0Implementasi Perbaikan Bit-Manipulation (Go)
Berikut adalah perbandingan implementasi ALU sebelum dan sesudah perbaikan pada backend core emulator Z80.
Implementasi Rusak (Sebelum)
// Salah: Menghitung Parity untuk instruksi aritmatika
func (cpu *CPU) Add8Bit(val byte) {
a := cpu.A
res := uint16(a) + uint16(val)
cpu.A = byte(res)
// Flag P/V dihitung menggunakan tabel parity logika (salah untuk ADD)
cpu.F = 0
if (cpu.A & 0x80) != 0 { cpu.F |= FlagS }
if cpu.A == 0 { cpu.F |= FlagZ }
if (a & 0x0F) + (val & 0x0F) > 0x0F { cpu.F |= FlagH }
if ParityTable[cpu.A] { cpu.F |= FlagPV } // Root Cause
if res > 0xFF { cpu.F |= FlagC }
}Implementasi Benar (Sesudah)
const (
FlagS byte = 1 << 7
FlagZ byte = 1 << 6
FlagH byte = 1 << 4
FlagPV byte = 1 << 2
FlagN byte = 1 << 1
FlagC byte = 1 << 0
)
// Benar: Memisahkan signed overflow untuk aritmatika
func (cpu *CPU) Add8Bit(val byte) {
a := cpu.A
res := uint16(a) + uint16(val)
r := byte(res)
cpu.F = 0
if (r & 0x80) != 0 { cpu.F |= FlagS }
if r == 0 { cpu.F |= FlagZ }
if (a & 0x0F) + (val & 0x0F) > 0x0F { cpu.F |= FlagH }
// Signed overflow: (+) + (+) = (-) atau (-) + (-) = (+)
if ((a ^ r) & (val ^ r) & 0x80) != 0 {
cpu.F |= FlagPV
}
if res > 0xFF { cpu.F |= FlagC }
cpu.A = r
}Unit Test Deterministik Validasi Flag Z80
Validasi dilakukan menggunakan unit test berbasis tabel tanpa framework tambahan untuk memastikan edge case boundary signed integer tercover.
package main
import "testing"
type CPU struct {
A byte
F byte
}
func TestZ80_AddSignedOverflow(t *testing.T) {
cases := []struct {
name string
a, b byte
wantA byte
wantPV bool
}{
{"Positive Overflow (127 + 1)", 0x7F, 0x01, 0x80, true},
{"No Overflow (64 + 63)", 0x40, 0x3F, 0x7F, false},
{"Negative Overflow (-128 + -1)", 0x80, 0xFF, 0x7F, true},
{"Mixed Sign (-64 + 64)", 0xC0, 0x40, 0x00, false},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
cpu := &CPU{A: tc.a}
cpu.Add8Bit(tc.b)
if cpu.A != tc.wantA {
t.Fatalf("A mismatch: got 0x%02X, want 0x%02X", cpu.A, tc.wantA)
}
gotPV := (cpu.F & FlagPV) != 0
if gotPV != tc.wantPV {
t.Fatalf("PV flag mismatch: got %v, want %v (F: 0x%02X)", gotPV, tc.wantPV, cpu.F)
}
})
}
}Kesimpulan
Kesalahan emulasi Z80 pada register status umumnya bersumber dari duplikasi semantik satu flag fisik untuk dua konteks berbeda: parity pada aljabar boolean dan overflow pada aritmatika bertanda. Isolasi penanganan flag berdasarkan kelompok instruksi menyelesaikan kegagalan eksekusi test suite pada backend worker secara tuntas.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!