118 lines
2.9 KiB
Go
118 lines
2.9 KiB
Go
package z80
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// mockMemory is a simple 64K RAM that satisfies Memory interface.
|
|
type mockMemory struct {
|
|
data [65536]byte
|
|
}
|
|
|
|
func (m *mockMemory) ReadByte(address uint16) byte { return m.data[address] }
|
|
func (m *mockMemory) WriteByte(address uint16, value byte) { m.data[address] = value }
|
|
func (m *mockMemory) ReadWord(address uint16) uint16 {
|
|
lo := m.data[address]
|
|
hi := m.data[address+1]
|
|
return (uint16(hi) << 8) | uint16(lo)
|
|
}
|
|
func (m *mockMemory) WriteWord(address uint16, value uint16) {
|
|
m.data[address] = byte(value)
|
|
m.data[address+1] = byte(value >> 8)
|
|
}
|
|
|
|
// mockIO is a trivial port device.
|
|
type mockIO struct {
|
|
lastOut map[uint16]byte
|
|
inVals map[uint16]byte
|
|
interrupt bool
|
|
}
|
|
|
|
func newMockIO() *mockIO {
|
|
return &mockIO{lastOut: make(map[uint16]byte), inVals: make(map[uint16]byte), interrupt: false}
|
|
}
|
|
|
|
func (io *mockIO) ReadPort(port uint16) byte { return io.inVals[port] }
|
|
func (io *mockIO) WritePort(port uint16, value byte) { io.lastOut[port] = value }
|
|
func (io *mockIO) CheckInterrupt() bool { return io.interrupt }
|
|
|
|
// testCPU creates a CPU with empty RAM/IO and PC=0, SP=0xFFFF.
|
|
func testCPU() (*CPU, *mockMemory, *mockIO) {
|
|
mem := &mockMemory{}
|
|
io := newMockIO()
|
|
cpu := New(mem, io)
|
|
cpu.SP = 0xFFFF
|
|
cpu.PC = 0x0000
|
|
return cpu, mem, io
|
|
}
|
|
|
|
// loadProgram writes bytes at address and sets PC to that address.
|
|
func loadProgram(cpu *CPU, mem *mockMemory, addr uint16, bytes ...byte) {
|
|
for i, b := range bytes {
|
|
mem.WriteByte(addr+uint16(i), b)
|
|
}
|
|
cpu.PC = addr
|
|
}
|
|
|
|
// mustStep runs one instruction and logs an error if cycles <= 0.
|
|
// Returns cycles consumed.
|
|
func mustStep(t *testing.T, cpu *CPU) int {
|
|
c := cpu.ExecuteOneInstruction()
|
|
if c <= 0 {
|
|
t.Errorf("ExecuteOneInstruction returned invalid cycles: %d", c)
|
|
}
|
|
return c
|
|
}
|
|
|
|
// assert helper shortcuts (non-fatal).
|
|
func assertEq[T comparable](t *testing.T, got, want T, msg string) {
|
|
t.Helper()
|
|
if got != want {
|
|
t.Errorf("%s: got %v, want %v", msg, got, want)
|
|
}
|
|
}
|
|
|
|
func assertFlag(t *testing.T, cpu *CPU, flag byte, want bool, msg string) {
|
|
t.Helper()
|
|
if cpu.GetFlag(flag) != want {
|
|
t.Errorf("%s: flag 0x%02X got %v, want %v (F=%02X)", msg, flag, cpu.GetFlag(flag), want, cpu.F)
|
|
}
|
|
}
|
|
|
|
// hexdump prints a compact hex + ascii line dump suitable for test logs.
|
|
func hexdump(p []byte, width int) string {
|
|
if width <= 0 {
|
|
width = 16
|
|
}
|
|
var b strings.Builder
|
|
for i := 0; i < len(p); i += width {
|
|
end := i + width
|
|
if end > len(p) {
|
|
end = len(p)
|
|
}
|
|
b.WriteString(fmt.Sprintf("%08X ", i))
|
|
for j := i; j < end; j++ {
|
|
b.WriteString(fmt.Sprintf("%02x ", p[j]))
|
|
}
|
|
for j := end; j < i+width; j++ {
|
|
b.WriteString(" ")
|
|
}
|
|
b.WriteString(" |")
|
|
for j := i; j < end; j++ {
|
|
c := p[j]
|
|
if c < 32 || c > 126 {
|
|
c = '.'
|
|
}
|
|
b.WriteByte(c)
|
|
}
|
|
b.WriteString("|\n")
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
// small helper to timestamp messages consistently
|
|
func ts() string { return time.Now().Format("15:04:05.000") }
|