mirror of
https://github.com/romychs/z80go.git
synced 2026-04-16 08:44:20 +03:00
616 lines
14 KiB
Go
616 lines
14 KiB
Go
package z80go_test
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
_ "embed"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/romychs/z80go"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
const (
|
|
ScanNone int = iota
|
|
ScanDesc
|
|
ScanEvent
|
|
ScanRegs
|
|
ScanState
|
|
ScanMem
|
|
ScanEnd
|
|
)
|
|
|
|
type Registers struct {
|
|
AF uint16
|
|
BC uint16
|
|
DE uint16
|
|
HL uint16
|
|
AFa uint16
|
|
BCa uint16
|
|
DEa uint16
|
|
HLa uint16
|
|
IX uint16
|
|
IY uint16
|
|
SP uint16
|
|
PC uint16
|
|
MemPtr uint16
|
|
}
|
|
|
|
type State struct {
|
|
I byte
|
|
R byte
|
|
IFF1 bool
|
|
IFF2 bool
|
|
IM byte
|
|
isHalted bool
|
|
tStates uint16
|
|
}
|
|
|
|
type MemorySetup struct {
|
|
addr uint16
|
|
values []byte
|
|
}
|
|
|
|
type Event struct {
|
|
time uint16
|
|
typ string
|
|
addr uint16
|
|
data byte
|
|
}
|
|
|
|
type Z80TestIn struct {
|
|
// description string
|
|
registers Registers
|
|
state State
|
|
memorySetup []MemorySetup
|
|
}
|
|
|
|
type Expect struct {
|
|
events []Event
|
|
registers Registers
|
|
state State
|
|
memory []MemorySetup
|
|
}
|
|
|
|
//go:embed tests/tests.in
|
|
var testIn []byte
|
|
|
|
//go:embed tests/tests.expected
|
|
var testExpected []byte
|
|
|
|
type Computer struct {
|
|
cpu *z80go.CPU
|
|
memory [65536]byte
|
|
ports [256]byte
|
|
}
|
|
|
|
var z80TestsIn map[string]Z80TestIn
|
|
var z80TestsExpected map[string]Expect
|
|
|
|
var computer Computer
|
|
|
|
var testNames []string
|
|
|
|
func init() {
|
|
z80TestsIn = make(map[string]Z80TestIn)
|
|
z80TestsExpected = make(map[string]Expect)
|
|
parseTestIn()
|
|
parseTestExpected()
|
|
for addr := 0; addr < 65535; addr++ {
|
|
computer.memory[addr] = 0x00
|
|
}
|
|
for addr := 0; addr < 255; addr++ {
|
|
computer.ports[addr] = 0
|
|
}
|
|
computer.cpu = z80go.NewCPU(&computer)
|
|
}
|
|
|
|
func (c *Computer) M1MemRead(addr uint16) byte {
|
|
return c.memory[addr]
|
|
}
|
|
|
|
func (c *Computer) MemRead(addr uint16) byte {
|
|
return c.memory[addr]
|
|
}
|
|
|
|
func (c *Computer) MemWrite(addr uint16, val byte) {
|
|
c.memory[addr] = val
|
|
}
|
|
|
|
func (c *Computer) IOWrite(port uint16, val byte) {
|
|
c.ports[port&0x00ff] = val
|
|
}
|
|
|
|
func (c *Computer) IORead(port uint16) byte {
|
|
return byte(port >> 8) //c.ports[port&0x00ff]
|
|
}
|
|
|
|
func parseTestExpected() {
|
|
exScanner := bufio.NewScanner(bytes.NewReader(testExpected))
|
|
scanState := ScanNone
|
|
|
|
testName := ""
|
|
var events []Event
|
|
registers := Registers{}
|
|
state := State{}
|
|
var memorySetup []MemorySetup
|
|
|
|
for exScanner.Scan() {
|
|
line := exScanner.Text()
|
|
if len(strings.TrimSpace(line)) == 0 {
|
|
if scanState == ScanMem {
|
|
scanState = ScanEnd
|
|
} else {
|
|
scanState = ScanNone
|
|
continue
|
|
}
|
|
}
|
|
if ScanNone == scanState {
|
|
scanState = ScanDesc
|
|
} else if len(line) > 0 && line[0] == ' ' {
|
|
scanState = ScanEvent
|
|
}
|
|
//else {
|
|
// if scanState == ScanEvent {
|
|
// scanState = ScanRegs
|
|
// }
|
|
//}
|
|
|
|
switch scanState {
|
|
case ScanDesc:
|
|
testName = line
|
|
scanState = ScanRegs
|
|
case ScanEvent:
|
|
events = append([]Event{}, *parseEvent(line))
|
|
scanState = ScanRegs
|
|
case ScanRegs:
|
|
registers = *parseRegisters(line)
|
|
scanState = ScanState
|
|
case ScanState:
|
|
state = *parseState(line)
|
|
scanState = ScanMem
|
|
case ScanMem:
|
|
memorySetup = append(memorySetup, *parseMemory(line))
|
|
//scanState = ScanMem
|
|
case ScanEnd:
|
|
z80TestsExpected[testName] = Expect{
|
|
events: events,
|
|
registers: registers,
|
|
state: state,
|
|
memory: memorySetup,
|
|
}
|
|
events = []Event{}
|
|
memorySetup = []MemorySetup{}
|
|
scanState = ScanNone
|
|
default:
|
|
panic("unhandled default case")
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
func parseTestIn() {
|
|
inScanner := bufio.NewScanner(bytes.NewReader(testIn))
|
|
scanState := ScanNone
|
|
testName := ""
|
|
registers := Registers{}
|
|
state := State{}
|
|
var memorySetup []MemorySetup
|
|
for inScanner.Scan() {
|
|
line := inScanner.Text()
|
|
if len(line) == 0 || strings.TrimSpace(line) == "" {
|
|
scanState = ScanNone
|
|
continue
|
|
}
|
|
if ScanNone == scanState {
|
|
scanState = ScanDesc
|
|
} else if line == "-1" {
|
|
scanState = ScanEnd
|
|
}
|
|
switch scanState {
|
|
case ScanDesc:
|
|
testName = line
|
|
scanState = ScanRegs
|
|
case ScanRegs:
|
|
registers = *parseRegisters(line)
|
|
scanState = ScanState
|
|
case ScanState:
|
|
state = *parseState(line)
|
|
scanState = ScanMem
|
|
case ScanMem:
|
|
memorySetup = append(memorySetup, *parseMemory(line))
|
|
//scanState = ScanMem
|
|
case ScanEnd:
|
|
test := Z80TestIn{
|
|
registers: registers,
|
|
state: state,
|
|
memorySetup: memorySetup,
|
|
}
|
|
testNames = append(testNames, testName)
|
|
z80TestsIn[testName] = test
|
|
scanState = ScanNone
|
|
default:
|
|
panic("unhandled default case")
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
func parseEvent(event string) *Event {
|
|
e := Event{}
|
|
line := strings.TrimSpace(event)
|
|
//4 MR 0000 00
|
|
//012345678
|
|
if len(line) < 9 {
|
|
log.Errorf("Invalid event: %s", line)
|
|
return nil
|
|
}
|
|
sp := strings.Index(line, " ")
|
|
if sp == -1 {
|
|
log.Errorf("Invalid event: %s", line)
|
|
return nil
|
|
}
|
|
e.time = parseDecW(line[:sp])
|
|
e.typ = line[sp+1 : sp+3]
|
|
e.addr = parseHexW(line[sp+4 : sp+8])
|
|
if len(line) > sp+9 {
|
|
//println("Event: ", line)
|
|
|
|
e.data = parseHexB(line[sp+9:])
|
|
}
|
|
|
|
return &e
|
|
}
|
|
|
|
func parseMemory(line string) *MemorySetup {
|
|
m := MemorySetup{}
|
|
//0000 00 -1
|
|
//0123456789
|
|
m.addr = parseHexW(line[0:4])
|
|
mem := line[5:]
|
|
for {
|
|
st := mem[:2]
|
|
if st == "-1" {
|
|
break
|
|
}
|
|
m.values = append(m.values, parseHexB(st))
|
|
mem = strings.TrimSpace(mem[2:])
|
|
}
|
|
return &m
|
|
}
|
|
|
|
func parseRegisters(line string) *Registers {
|
|
r := Registers{}
|
|
line = strings.TrimSpace(line)
|
|
if len(line) != 64 {
|
|
log.Errorf("Invalid register line: %s", line)
|
|
} else {
|
|
// 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
|
|
for ctr := 0; ctr < 13; ctr++ {
|
|
hexString := line[ctr*5 : ctr*5+4]
|
|
v, err := strconv.ParseUint(hexString, 16, 16)
|
|
if err != nil {
|
|
log.Errorf("Invalid register value: %s in line %s", hexString, line)
|
|
break
|
|
}
|
|
switch ctr {
|
|
case 0:
|
|
r.AF = uint16(v)
|
|
case 1:
|
|
r.BC = uint16(v)
|
|
case 2:
|
|
r.DE = uint16(v)
|
|
case 3:
|
|
r.HL = uint16(v)
|
|
case 4:
|
|
r.AFa = uint16(v)
|
|
case 5:
|
|
r.BCa = uint16(v)
|
|
case 6:
|
|
r.DEa = uint16(v)
|
|
case 7:
|
|
r.HLa = uint16(v)
|
|
case 8:
|
|
r.IX = uint16(v)
|
|
case 9:
|
|
r.IY = uint16(v)
|
|
case 10:
|
|
r.SP = uint16(v)
|
|
case 11:
|
|
r.PC = uint16(v)
|
|
case 12:
|
|
r.MemPtr = uint16(v)
|
|
}
|
|
}
|
|
|
|
}
|
|
return &r
|
|
}
|
|
|
|
func parseHexB(line string) byte {
|
|
v, err := strconv.ParseUint(line, 16, 8)
|
|
if err != nil {
|
|
log.Errorf("Invalid HexB value: %s", line)
|
|
}
|
|
return byte(v)
|
|
}
|
|
|
|
func parseHexW(line string) uint16 {
|
|
v, err := strconv.ParseUint(line, 16, 16)
|
|
if err != nil {
|
|
log.Errorf("Invalid HexW value: %s", line)
|
|
}
|
|
return uint16(v)
|
|
}
|
|
|
|
func parseDecB(line string) byte {
|
|
v, err := strconv.ParseUint(line, 10, 8)
|
|
if err != nil {
|
|
log.Errorf("Invalid B value: %s", line)
|
|
}
|
|
return byte(v)
|
|
}
|
|
|
|
func parseDecW(line string) uint16 {
|
|
v, err := strconv.ParseUint(line, 10, 16)
|
|
if err != nil {
|
|
log.Errorf("Invalid W value: %s", line)
|
|
}
|
|
return uint16(v)
|
|
}
|
|
|
|
func parseBoolB(line string) bool {
|
|
v, err := strconv.ParseUint(line, 10, 8)
|
|
if err != nil {
|
|
log.Errorf("Invalid state I value: %s", line)
|
|
}
|
|
return v != 0
|
|
}
|
|
|
|
func parseState(line string) *State {
|
|
s := State{}
|
|
line = strings.TrimSpace(line)
|
|
if len(line) < 15 {
|
|
log.Errorf("Invalid state line: %s", line)
|
|
} else {
|
|
//00 00 0 0 0 0 1
|
|
//0123456789012345678
|
|
s.I = parseHexB(line[0:2])
|
|
s.R = parseHexB(line[3:5])
|
|
s.IFF1 = parseBoolB(line[6:7])
|
|
s.IFF2 = parseBoolB(line[8:9])
|
|
s.IM = parseDecB(line[10:11])
|
|
s.isHalted = parseBoolB(line[12:13])
|
|
s.tStates = parseDecW(strings.TrimSpace(line[13:]))
|
|
}
|
|
return &s
|
|
}
|
|
|
|
func TestZ80Fuse(t *testing.T) {
|
|
t.Logf("Fuse-type Z80 emulator test")
|
|
computer.cpu.Reset()
|
|
for _, name := range testNames {
|
|
setComputerState(z80TestsIn[name])
|
|
exp, exists := z80TestsExpected[name]
|
|
if !exists {
|
|
t.Errorf("Expected values for test %s not exists!", name)
|
|
return
|
|
}
|
|
cy := uint32(0)
|
|
for {
|
|
c, _ := computer.cpu.RunInstruction()
|
|
cy += c
|
|
if cy >= uint32(exp.state.tStates) {
|
|
break
|
|
}
|
|
}
|
|
checkComputerState(t, name)
|
|
}
|
|
}
|
|
|
|
func setComputerState(test Z80TestIn) {
|
|
state := z80go.CPU{
|
|
A: byte(test.registers.AF >> 8),
|
|
B: byte(test.registers.BC >> 8),
|
|
C: byte(test.registers.BC),
|
|
D: byte(test.registers.DE >> 8),
|
|
E: byte(test.registers.DE),
|
|
H: byte(test.registers.HL >> 8),
|
|
L: byte(test.registers.HL),
|
|
AAlt: byte(test.registers.AFa >> 8),
|
|
BAlt: byte(test.registers.BCa >> 8),
|
|
CAlt: byte(test.registers.BCa),
|
|
DAlt: byte(test.registers.DEa >> 8),
|
|
EAlt: byte(test.registers.DEa),
|
|
HAlt: byte(test.registers.HLa >> 8),
|
|
LAlt: byte(test.registers.HLa),
|
|
IX: test.registers.IX,
|
|
IY: test.registers.IY,
|
|
I: test.state.I,
|
|
R: test.state.R,
|
|
SP: test.registers.SP,
|
|
PC: test.registers.PC,
|
|
Flags: *z80go.NewFlags(byte(test.registers.AF)),
|
|
FlagsAlt: *z80go.NewFlags(byte(test.registers.AFa)),
|
|
IMode: test.state.IM,
|
|
Iff1: test.state.IFF1,
|
|
Iff2: test.state.IFF2,
|
|
Halted: test.state.isHalted,
|
|
CycleCount: 0,
|
|
IntOccurred: false,
|
|
NmiOccurred: false,
|
|
MemPtr: test.registers.MemPtr,
|
|
}
|
|
|
|
// Setup CPU
|
|
computer.cpu.SetState(&state)
|
|
|
|
// Setup Memory
|
|
for _, ms := range test.memorySetup {
|
|
addr := ms.addr
|
|
for _, b := range ms.values {
|
|
computer.memory[addr] = b
|
|
addr++
|
|
}
|
|
}
|
|
}
|
|
|
|
func lo(w uint16) byte {
|
|
return byte(w)
|
|
}
|
|
|
|
func hi(w uint16) byte {
|
|
return byte(w >> 8)
|
|
}
|
|
|
|
func checkComputerState(t *testing.T, name string) {
|
|
state := computer.cpu.GetState()
|
|
exp, exists := z80TestsExpected[name]
|
|
if !exists {
|
|
t.Errorf("Expected values for test %s not exists!", name)
|
|
return
|
|
}
|
|
|
|
// A,B,C,D,E,H,L
|
|
if hi(exp.registers.AF) != state.A {
|
|
t.Errorf("%s: Expected A to be %x, got %x", name, hi(exp.registers.AF), state.A)
|
|
}
|
|
if hi(exp.registers.BC) != state.B {
|
|
t.Errorf("%s: Expected B to be %x, got %x", name, hi(exp.registers.BC), state.B)
|
|
computer.cpu.DebugOutput()
|
|
}
|
|
|
|
if lo(exp.registers.BC) != state.C {
|
|
t.Errorf("%s: Expected C to be %x, got %x", name, lo(exp.registers.BC), state.C)
|
|
}
|
|
|
|
if hi(exp.registers.DE) != state.D {
|
|
t.Errorf("%s: Expected D to be %x, got %x", name, hi(exp.registers.DE), state.D)
|
|
}
|
|
|
|
if lo(exp.registers.DE) != state.E {
|
|
t.Errorf("%s: Expected E to be %x, got %x", name, lo(exp.registers.DE), state.E)
|
|
}
|
|
|
|
if hi(exp.registers.HL) != state.H {
|
|
t.Errorf("%s: Expected H to be %x, got %x", name, hi(exp.registers.HL), state.H)
|
|
}
|
|
|
|
if lo(exp.registers.HL) != state.L {
|
|
t.Errorf("%s: Expected L to be %x, got %x", name, lo(exp.registers.BC), state.L)
|
|
}
|
|
|
|
// Alt A,B,C,D,E,H,L
|
|
if hi(exp.registers.AFa) != state.AAlt {
|
|
t.Errorf("%s: Expected A' to be %x, got %x", name, hi(exp.registers.AFa), state.AAlt)
|
|
}
|
|
if hi(exp.registers.BCa) != state.BAlt {
|
|
t.Errorf("%s: Expected B' to be %x, got %x", name, hi(exp.registers.BCa), state.BAlt)
|
|
}
|
|
|
|
if lo(exp.registers.BCa) != state.CAlt {
|
|
t.Errorf("%s: Expected C' to be %x, got %x", name, lo(exp.registers.BCa), state.CAlt)
|
|
}
|
|
|
|
if hi(exp.registers.DEa) != state.DAlt {
|
|
t.Errorf("%s: Expected D' to be %x, got %x", name, hi(exp.registers.DEa), state.DAlt)
|
|
}
|
|
|
|
if lo(exp.registers.DEa) != state.EAlt {
|
|
t.Errorf("%s: Expected E' to be %x, got %x", name, lo(exp.registers.DEa), state.EAlt)
|
|
}
|
|
|
|
if hi(exp.registers.HLa) != state.HAlt {
|
|
t.Errorf("%s: Expected H' to be %x, got %x", name, hi(exp.registers.HLa), state.HAlt)
|
|
}
|
|
|
|
if lo(exp.registers.HLa) != state.LAlt {
|
|
t.Errorf("%s: Expected L' to be %x, got %x", name, lo(exp.registers.BCa), state.LAlt)
|
|
}
|
|
|
|
// 16b regs PC, SP, Meme, R, I
|
|
|
|
if exp.registers.IX != state.IX {
|
|
t.Errorf("%s: Expected IX to be %x, got %x", name, exp.registers.IX, state.IX)
|
|
}
|
|
|
|
if exp.registers.IY != state.IY {
|
|
t.Errorf("%s: Expected IX to be %x, got %x", name, exp.registers.IX, state.IX)
|
|
}
|
|
|
|
if exp.registers.PC != state.PC {
|
|
t.Errorf("%s: Expected PC to be %x, got %x", name, exp.registers.PC, state.PC)
|
|
}
|
|
|
|
if exp.registers.SP != state.SP {
|
|
t.Errorf("%s: Expected SP to be %x, got %x", name, exp.registers.SP, state.SP)
|
|
}
|
|
|
|
if exp.registers.MemPtr != state.MemPtr {
|
|
t.Errorf("%s: Expected MemPtr to be %x, got %x", name, exp.registers.MemPtr, state.MemPtr)
|
|
}
|
|
|
|
// State
|
|
if exp.state.I != state.I {
|
|
t.Errorf("%s: Expected I to be %x, got %x", name, exp.state.I, state.I)
|
|
}
|
|
|
|
if exp.state.IM != state.IMode {
|
|
t.Errorf("%s: Expected IM to be %d, got %d", name, exp.state.IM, state.IMode)
|
|
}
|
|
|
|
if exp.state.IFF1 != state.Iff1 {
|
|
t.Errorf("%s: Expected IIF1 to be %t, got %t", name, exp.state.IFF1, state.Iff1)
|
|
}
|
|
|
|
if exp.state.IFF2 != state.Iff2 {
|
|
t.Errorf("%s: Expected IIF2 to be %t, got %t", name, exp.state.IFF2, state.Iff2)
|
|
}
|
|
|
|
if exp.state.isHalted != state.Halted {
|
|
t.Errorf("%s: Expected isHalted to be %t, got %t", name, exp.state.isHalted, state.Halted)
|
|
}
|
|
|
|
// FLAGS
|
|
if lo(exp.registers.AF) != state.Flags.AsByte() {
|
|
t.Errorf("%s: Expected Flags to be %08b, got %08b", name, lo(exp.registers.AF), state.Flags.AsByte())
|
|
}
|
|
|
|
if lo(exp.registers.AFa) != state.FlagsAlt.AsByte() {
|
|
t.Errorf("%s: Expected Flags' to be %08b, got %08b", name, lo(exp.registers.AFa), state.FlagsAlt.AsByte())
|
|
}
|
|
|
|
// Check memory
|
|
for _, mExpect := range exp.memory {
|
|
addr := mExpect.addr
|
|
for _, b := range mExpect.values {
|
|
if computer.memory[addr] != b {
|
|
t.Errorf("%s: Expected memory[%x] to be %x, got %x", name, addr, b, computer.memory[addr])
|
|
}
|
|
addr++
|
|
}
|
|
}
|
|
}
|
|
|
|
func setMemory(addr uint16, value []byte) {
|
|
for i := 0; i < len(value); i++ {
|
|
computer.memory[addr+uint16(i)] = value[i]
|
|
}
|
|
}
|
|
|
|
var testJRm = []byte{0x70, 0x7d, 0xb3, 0x3c, 0x28, 0x09, 0xb3, 0xab, 0x4f, 0x7d, 0xa3, 0xb1, 0x6f, 0x18, 0xf1, 0x7d}
|
|
|
|
func TestZ80_JR_mnn(t *testing.T) {
|
|
setMemory(0x31eb, testJRm)
|
|
state := computer.cpu.GetState()
|
|
state.PC = 0x31F8
|
|
computer.cpu.SetState(state)
|
|
computer.cpu.RunInstruction()
|
|
expected := uint16(0x31EB)
|
|
if computer.cpu.PC != expected {
|
|
t.Errorf("Error JR -nn, result PC=0x%04X, expected: 0x%04X", computer.cpu.PC, expected)
|
|
}
|
|
}
|