First refactoring from okemu

This commit is contained in:
Роман Бойков 2026-04-01 18:47:30 +03:00
commit 44c36ecb94
18 changed files with 32036 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
*.bak
*.lst
*.tmp
*.sh
.vscode/
.idea/
src/.idea/

28
LICENSE Normal file
View File

@ -0,0 +1,28 @@
BSD 3-Clause License
Copyright (c) 2026, Roman Boykov
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

5
README.md Normal file
View File

@ -0,0 +1,5 @@
# Z80 processor emulator and disassembler
Pass ZEXALL tests. Undocumented instructions supported.
Based on ideas of [Superzazu Z80 emulator](https://github.com/superzazu/z80)

58
constants.go Normal file
View File

@ -0,0 +1,58 @@
package z80go
var cycles00 = [256]byte{
4, 10, 7, 6, 4, 4, 7, 4, 4, 11, 7, 6, 4, 4, 7, 4,
8, 10, 7, 6, 4, 4, 7, 4, 12, 11, 7, 6, 4, 4, 7, 4,
7, 10, 16, 6, 4, 4, 7, 4, 7, 11, 16, 6, 4, 4, 7, 4,
7, 10, 13, 6, 11, 11, 10, 4, 7, 11, 13, 6, 4, 4, 7, 4,
4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4,
4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4,
4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4,
7, 7, 7, 7, 7, 7, 4, 7, 4, 4, 4, 4, 4, 4, 7, 4,
4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4,
4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4,
4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4,
4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4,
5, 10, 10, 10, 10, 11, 7, 11, 5, 10, 10, 0, 10, 17, 7, 11,
5, 10, 10, 11, 10, 11, 7, 11, 5, 4, 10, 11, 10, 0, 7, 11,
5, 10, 10, 19, 10, 11, 7, 11, 5, 4, 10, 4, 10, 0, 7, 11,
5, 10, 10, 4, 10, 11, 7, 11, 5, 6, 10, 4, 10, 0, 7, 11,
}
var cyclesED = [256]byte{
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
12, 12, 15, 20, 8, 14, 8, 9, 12, 12, 15, 20, 8, 14, 8, 9,
12, 12, 15, 20, 8, 14, 8, 9, 12, 12, 15, 20, 8, 14, 8, 9,
12, 12, 15, 20, 8, 14, 8, 18, 12, 12, 15, 20, 8, 14, 8, 18,
12, 12, 15, 20, 8, 14, 8, 8, 12, 12, 15, 20, 8, 14, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
16, 16, 16, 16, 8, 8, 8, 8, 16, 16, 16, 16, 8, 8, 8, 8,
16, 16, 16, 16, 8, 8, 8, 8, 16, 16, 16, 16, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
}
var cyclesDDFD = [256]byte{
4, 4, 4, 4, 4, 4, 4, 4, 4, 15, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 15, 4, 4, 4, 4, 4, 4,
4, 14, 20, 10, 8, 8, 11, 4, 4, 15, 20, 10, 8, 8, 11, 4,
4, 4, 4, 4, 23, 23, 19, 4, 4, 15, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 8, 8, 19, 4, 4, 4, 4, 4, 8, 8, 19, 4,
4, 4, 4, 4, 8, 8, 19, 4, 4, 4, 4, 4, 8, 8, 19, 4,
8, 8, 8, 8, 8, 8, 19, 8, 8, 8, 8, 8, 8, 8, 19, 8,
19, 19, 19, 19, 19, 19, 4, 19, 4, 4, 4, 4, 8, 8, 19, 4,
4, 4, 4, 4, 8, 8, 19, 4, 4, 4, 4, 4, 8, 8, 19, 4,
4, 4, 4, 4, 8, 8, 19, 4, 4, 4, 4, 4, 8, 8, 19, 4,
4, 4, 4, 4, 8, 8, 19, 4, 4, 4, 4, 4, 8, 8, 19, 4,
4, 4, 4, 4, 8, 8, 19, 4, 4, 4, 4, 4, 8, 8, 19, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 14, 4, 23, 4, 15, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 10, 4, 4, 4, 4, 4, 4,
}

163
cpu.go Normal file
View File

@ -0,0 +1,163 @@
package z80go
import (
"errors"
)
const (
MemAccessRead = 1
MemAccessWrite = 2
)
// NewCPU initializes a Z80 CPU instance and return pointer to it
func NewCPU(core MemIoRW) *CPU {
z := CPU{}
z.Reset()
z.core = core
z.cycleCount = 0
z.codeCoverageEnabled = false
// z.codeCoverage = make(map[uint16]bool)
// z.memAccess =
z.codeCoverageEnabled = false
// z.codeCoverage map[uint16]bool
z.extendedStackEnabled = false
//z.extendedStack [65536]uint8
return &z
}
// RunInstruction executes the next instruction in memory + handles interrupts
func (z *CPU) RunInstruction() (uint32, *map[uint16]byte) {
z.memAccess = map[uint16]byte{}
if z.codeCoverageEnabled {
z.codeCoverage[z.PC] = true
}
pre := z.cycleCount
if z.Halted {
z.execOpcode(0x00)
} else {
opcode := z.nextB()
z.execOpcode(opcode)
}
z.processInterrupts()
return z.cycleCount - pre, &z.memAccess
}
// SetState set current CPU state
// Used by debuggers to override CPU state, set new PC, for example
func (z *CPU) SetState(state *CPU) {
z.A = state.A
z.B = state.B
z.C = state.C
z.D = state.D
z.E = state.E
z.H = state.H
z.L = state.L
z.AAlt = state.AAlt
z.BAlt = state.BAlt
z.CAlt = state.CAlt
z.DAlt = state.DAlt
z.EAlt = state.EAlt
z.HAlt = state.HAlt
z.LAlt = state.LAlt
z.PC = state.PC
z.SP = state.SP
z.IX = state.IX
z.IY = state.IY
z.I = state.I
z.R = state.R
z.Flags.S = state.Flags.S
z.Flags.Z = state.Flags.Z
z.Flags.Y = state.Flags.Y
z.Flags.H = state.Flags.H
z.Flags.X = state.Flags.X
z.Flags.P = state.Flags.P
z.Flags.N = state.Flags.N
z.Flags.C = state.Flags.C
z.IMode = state.IMode
z.Iff1 = state.Iff1
z.Iff2 = state.Iff2
z.Halted = state.Halted
z.IntOccurred = state.IntOccurred
z.NmiOccurred = false
}
func (z *CPU) GetState() *CPU {
return &CPU{
A: z.A,
B: z.B,
C: z.C,
D: z.D,
E: z.E,
H: z.H,
L: z.L,
AAlt: z.AAlt,
BAlt: z.BAlt,
CAlt: z.CAlt,
DAlt: z.DAlt,
EAlt: z.EAlt,
HAlt: z.HAlt,
LAlt: z.LAlt,
IX: z.IX,
IY: z.IY,
I: z.I,
R: z.R,
SP: z.SP,
PC: z.PC,
Flags: z.flags(),
FlagsAlt: z.altFlags(),
IMode: z.IMode,
Iff1: z.Iff1,
Iff2: z.Iff2,
Halted: z.Halted,
CycleCount: z.cycleCount,
IntOccurred: z.IntOccurred,
NmiOccurred: z.NmiOccurred,
memPtr: z.memPtr,
}
}
//func (z *CPU) PC() uint16 {
// return z.PC
//}
// ClearCodeCoverage - clears code coverage journal
func (z *CPU) ClearCodeCoverage() {
clear(z.codeCoverage)
}
// SetCodeCoverage - enable of disable code coverage journal
func (z *CPU) SetCodeCoverage(enabled bool) {
z.codeCoverageEnabled = enabled
if !enabled {
clear(z.codeCoverage)
}
}
// CodeCoverage - return list of addresses executed by CPU
func (z *CPU) CodeCoverage() map[uint16]bool {
return z.codeCoverage
}
// SetExtendedStack - enable or disable marking stack values by PushValueType*
func (z *CPU) SetExtendedStack(enabled bool) {
z.extendedStackEnabled = enabled
if enabled {
for addr := 0; addr < 65536; addr++ {
z.extendedStack[addr] = PushValueTypeDefault
}
}
}
// ExtendedStack - return array with markers of PushValueType* for each byte of memory
func (z *CPU) ExtendedStack() ([]byte, error) {
var err error
if !z.extendedStackEnabled {
err = errors.New("error, z80: ExtendedStack disabled")
}
return z.extendedStack[:], err
}

672
dis/z80disasm.go Normal file
View File

@ -0,0 +1,672 @@
package dis
import (
"fmt"
"okemu/z80"
"strings"
)
type Disassembler struct {
pc uint16
core z80.MemIoRW
}
type Disassembly interface {
Disassm(pc uint16) string
}
func NewDisassembler(core z80.MemIoRW) *Disassembler {
d := Disassembler{
pc: 0,
core: core,
}
return &d
}
// opcode & 0x07
var operands = []string{"B", "C", "D", "E", "H", "L", "(HL)", "A"}
var aluOp = []string{"ADD A" + sep, "ADC A" + sep, "SUB ", "SBC A" + sep, "AND ", "XOR ", "OR ", "CP "}
const sep = ", "
func (d *Disassembler) jp(op, cond string) string {
addr := d.getW()
if cond != "" {
cond += sep
}
return fmt.Sprintf("%s %s%s", op, cond, addr)
}
func (d *Disassembler) jr(op, cond string) string {
addr := d.pc
offset := d.getByte()
if offset&0x80 != 0 {
addr += 0xFF00 | uint16(offset)
} else {
addr += d.pc + uint16(offset)
}
if cond != "" {
cond += sep
}
return fmt.Sprintf("%s %s0x%04X", op, cond, addr)
}
func (d *Disassembler) getByte() byte {
b := d.core.MemRead(d.pc)
d.pc++
return b
}
func (d *Disassembler) Disassm(pc uint16) string {
d.pc = pc
result := fmt.Sprintf(" %04X ", d.pc)
op := d.getByte()
switch {
// == 00:0F
case op == 0x00:
result += "NOP"
case op == 0x01:
result += "LD BC" + sep + d.getW()
case op == 0x02:
result += "LD (BC)" + sep + "A"
case op == 0x03:
result += "INC BC"
case op == 0x04:
result += "INC B"
case op == 0x05:
result += "DEC B"
case op == 0x06:
result += "LD B" + sep + d.getB()
case op == 0x07:
result += "RLCA"
case op == 0x08:
result += "EX AF, AF'"
case op == 0x09:
result += "ADD HL" + sep + "BC"
case op == 0x0A:
result += "LD A" + sep + "(BC)"
case op == 0x0B:
result += "DEC BC"
case op == 0x0C:
result += "INC C"
case op == 0x0D:
result += "DEC C"
case op == 0x0E:
result += "LD C" + sep + d.getB()
case op == 0x0F:
result += "RRCA"
// 10:1F
case op == 0x10:
// DJNZ rel
result += d.jr("DJNZ", "")
case op == 0x11:
result += "LD DE" + sep + d.getW()
case op == 0x12:
result += "LD (DE)" + sep + "A"
case op == 0x13:
result += "INC DE"
case op == 0x14:
result += "INC D"
case op == 0x15:
result += "DEC D"
case op == 0x16:
result += "LD D" + sep + d.getB()
case op == 0x17:
result += "RLA"
case op == 0x18:
result += d.jr("JR", "")
case op == 0x19:
result += "ADD HL" + sep + "DE"
case op == 0x1A:
result += "LD A" + sep + "(DE)"
case op == 0x1B:
result += "DEC DE"
case op == 0x1C:
result += "INC E"
case op == 0x1D:
result += "DEC E"
case op == 0x1E:
result += "LD E" + sep + d.getB()
case op == 0x1F:
result += "RRA"
// == 20:2F
case op == 0x20:
result += d.jr("JR", "NZ")
case op == 0x21:
result += "LD HL" + sep + d.getW()
case op == 0x22:
// LD (nn),HL
result += "LD (" + d.getW() + ")" + sep + "HL"
case op == 0x23:
result += "INC HL"
case op == 0x24:
result += "INC H"
case op == 0x25:
result += "DEC H"
case op == 0x26:
result += "LD H" + sep + d.getB()
case op == 0x27:
result += "DAA"
case op == 0x28:
result += d.jr("JR", "Z")
case op == 0x29:
result += "ADD HL" + sep + "HL"
case op == 0x2A:
result += "LD HL" + sep + "(" + d.getW() + ")"
case op == 0x2B:
result += "DEC HL"
case op == 0x2C:
result += "INC L"
case op == 0x2D:
result += "DEC L"
case op == 0x2E:
result += "LD L" + sep + d.getB()
case op == 0x2F:
result += "CPL"
// == 30:3F
case op == 0x30:
result += d.jr("JR", "NC")
case op == 0x31:
result += "LD SP" + sep + d.getW()
case op == 0x32:
result += "LD (" + d.getW() + ")" + sep + "A"
case op == 0x33:
result += "INC SP"
case op == 0x34:
result += "INC (HL)"
case op == 0x35:
result += "DEC (HL)"
case op == 0x36:
result += "LD (HL)" + sep + d.getB()
case op == 0x37:
result += "SCF"
case op == 0x38:
result += d.jr("JR", "C")
case op == 0x39:
result += "ADD HL" + sep + "SP"
case op == 0x3A:
result += "LD A" + sep + "(" + d.getW() + ")"
case op == 0x3B:
result += "DEC SP"
case op == 0x3C:
result += "INC A"
case op == 0x3D:
result += "DEC A"
case op == 0x3E:
result += "LD A" + sep + d.getB()
case op == 0x3F:
result += "CCF"
case op == 0x76:
result += "HALT"
case op >= 0x40 && op <= 0x7F:
// LD op8, op8
result += "LD " + operands[(op>>3)&0x07] + sep + operands[op&0x07]
case op >= 0x80 && op <= 0xBF:
// ALU op8
result += aluOp[(op>>3)&0x07] + operands[op&0x07]
case op == 0xc0:
result += "RET NZ"
case op == 0xc1:
result += "POP BC"
case op == 0xc2:
result += d.jp("JP", "NZ")
case op == 0xc3:
result += d.jp("JP", "")
case op == 0xc4:
result += d.jp("CALL", "NZ")
case op == 0xc5:
result += "PUSH BC"
case op == 0xc6:
result += "ADD A" + sep + d.getB()
case op == 0xc7 || op == 0xd7 || op == 0xe7 || op == 0xf7 || op == 0xcf || op == 0xdf || op == 0xef || op == 0xff:
// RST nnH
result += fmt.Sprintf("RST %d%dH", (op>>4)&3, (op&1)*8)
case op == 0xc8:
result += "RET Z"
case op == 0xc9:
result += "RET"
case op == 0xca:
result += d.jp("JP", "Z")
case op == 0xcb:
result += d.opocodeCB()
case op == 0xcc:
result += d.jp("CALL", "Z")
case op == 0xcd:
result += d.jp("CALL", "")
case op == 0xce:
result += "ADC A" + sep + d.getB()
case op == 0xd0:
result += "RET NC"
case op == 0xd1:
result += "POP DE"
case op == 0xd2:
result += d.jp("JP", "NC")
case op == 0xd3:
result += "OUT (" + d.getB() + ")" + sep + "A"
case op == 0xd4:
result += d.jp("CALL", "NC")
case op == 0xd5:
result += "PUSH DE"
case op == 0xd6:
result += "SUB " + d.getB()
case op == 0xd8:
result += "RET C"
case op == 0xd9:
result += "EXX"
case op == 0xda:
result += d.jp("JP", "C")
case op == 0xdb:
result += "IN A" + sep + " (" + d.getB() + ")"
case op == 0xdc:
result += d.jp("CALL", "C")
case op == 0xdd:
result += d.opocodeDD(op)
case op == 0xde:
result += "SBC A" + sep + d.getB()
case op == 0xe0:
result += "RET PO"
case op == 0xe1:
result += "POP HL"
case op == 0xe2:
result += d.jp("JP", "PO")
case op == 0xe3:
result += "EX (SP)" + sep + "HL"
case op == 0xe4:
result += d.jp("CALL", "PO")
case op == 0xe5:
result += "PUSH HL"
case op == 0xe6:
result += "AND " + d.getB()
case op == 0xe8:
result += "RET PE"
case op == 0xe9:
result += "JP (HL)"
case op == 0xea:
result += d.jp("JP", "PE")
case op == 0xeb:
result += "EX DE" + sep + "HL"
case op == 0xec:
result += d.jp("CALL", "PE")
case op == 0xed:
result += d.opocodeED()
case op == 0xee:
result += "XOR " + d.getB()
case op == 0xf0:
result += "RET P"
case op == 0xf1:
result += "POP AF"
case op == 0xf2:
result += d.jp("JP", "P")
case op == 0xf3:
result += "DI"
case op == 0xf4:
result += d.jp("CALL", "P")
case op == 0xf5:
result += "PUSH AF"
case op == 0xf6:
result += "OR " + d.getB()
case op == 0xf8:
result += "RET M"
case op == 0xf9:
result += "LD SP" + sep + "HL"
case op == 0xfa:
result += d.jp("JP", "M")
case op == 0xfb:
result += "EI"
case op == 0xfc:
result += d.jp("CALL", "M")
case op == 0xfd:
result += d.opocodeDD(op)
case op == 0xfe:
result += "CP " + d.getB()
default:
// All unknown as DB
result += fmt.Sprintf("DB 0x%02X", op)
}
return result
}
func (d *Disassembler) getW() string {
lo := d.core.MemRead(d.pc)
d.pc++
hi := d.core.MemRead(d.pc)
d.pc++
return fmt.Sprintf("0x%02X%02X", hi, lo)
}
func (d *Disassembler) getB() string {
lo := d.core.MemRead(d.pc)
d.pc++
return fmt.Sprintf("0x%02X", lo)
}
func (d *Disassembler) getRel() string {
offset := d.core.MemRead(d.pc)
var sign string
if int8(offset) < 0 {
sign = "-"
} else {
sign = "+"
}
return sign + fmt.Sprintf("0x%02X", offset&0x7F)
}
var shiftOps = []string{"RLC", "RRC", "RL", "RR", "SLA", "SRA", "SLL", "SRL"}
var bitOps = []string{"BIT", "RES", "SET"}
// opocodeCB disassemble Z80 Opcodes, with CB first byte
func (d *Disassembler) opocodeCB() string {
op := ""
opcode := d.getByte()
if opcode <= 0x3F {
op = shiftOps[opcode>>3&0x07] + operands[opcode&0x7]
} else {
op = shiftOps[(opcode>>6&0x03)-1] + operands[opcode&0x7]
}
return op
}
func (d *Disassembler) opocodeDD(op byte) string {
opcode := d.getByte()
result := ""
switch opcode {
case 0x09:
result = "ADD ii" + sep + "BC"
case 0x19:
result = "ADD ii" + sep + "DE"
case 0x21:
result = "LD ii" + sep + d.getW()
case 0x22:
result = "LD (" + d.getW() + ")" + sep + "ii"
case 0x23:
result = "INC ii"
case 0x24:
result = "INC IXH"
case 0x25:
result = "DEC IXH"
case 0x26:
result = "LD IXH" + sep + "n"
case 0x29:
result = "ADD ii" + sep + "ii"
case 0x2A:
result = "LD ii" + sep + "(" + d.getW() + ")"
case 0x2B:
result = "DEC ii"
case 0x34:
result = "INC (ii" + d.getRel() + ")"
case 0x35:
result = "DEC (ii" + d.getRel() + ")"
case 0x36:
result = "LD (ii" + d.getRel() + ")" + sep + "n"
case 0x39:
result = "ADD ii" + sep + "SP"
case 0x46:
result = "LD B" + sep + "(ii" + d.getRel() + ")"
case 0x4E:
result = "LD C" + sep + "(ii" + d.getRel() + ")"
case 0x56:
result = "LD D" + sep + "(ii" + d.getRel() + ")"
case 0x5E:
result = "LD E" + sep + "(ii" + d.getRel() + ")"
case 0x66:
result = "LD H" + sep + "(ii" + d.getRel() + ")"
case 0x6E:
result = "LD L" + sep + "(ii" + d.getRel() + ")"
case 0x70:
result = "LD (ii" + d.getRel() + ")" + sep + "B"
case 0x71:
result = "LD (ii" + d.getRel() + ")" + sep + "C"
case 0x72:
result = "LD (ii" + d.getRel() + ")" + sep + "D"
case 0x73:
result = "LD (ii" + d.getRel() + ")" + sep + "E"
case 0x74:
result = "LD (ii" + d.getRel() + ")" + sep + "H"
case 0x75:
result = "LD (ii" + d.getRel() + ")" + sep + "L"
case 0x77:
result = "LD (ii" + d.getRel() + ")" + sep + "A"
case 0x7E:
result = "LD A" + sep + "(ii" + d.getRel() + ")"
case 0x86:
result = "ADD A" + sep + "(ii" + d.getRel() + ")"
case 0x8E:
result = "ADC A" + sep + "(ii" + d.getRel() + ")"
case 0x96:
result = "SUB (ii" + d.getRel() + ")"
case 0x9E:
result = "SBC A" + sep + "(ii" + d.getRel() + ")"
case 0xA6:
result = "AND (ii" + d.getRel() + ")"
case 0xAE:
result = "XOR (ii" + d.getRel() + ")"
case 0xB6:
result = "OR (ii" + d.getRel() + ")"
case 0xBE:
result = "CP (ii" + d.getRel() + ")"
case 0xCB:
result = d.opocodeDDCB(op, opcode)
case 0xE1:
result = "POP ii"
case 0xE3:
result = "EX (SP)" + sep + "ii"
case 0xE5:
result = "PUSH ii"
case 0xE9:
result = "JP (ii)"
case 0xF9:
result = "LD SP" + sep + "ii"
default:
return fmt.Sprintf("DB 0x%02X, 0x%02X", op, opcode)
}
reg := "IX"
if op == 0xFD {
reg = "IY"
}
return strings.ReplaceAll(result, "ii", reg)
}
func (d *Disassembler) opocodeDDCB(op1 byte, op2 byte) string {
opcode := d.getByte()
result := ""
switch opcode {
case 0x06:
result = "RLC (ii" + d.getRel() + ")"
case 0x0E:
result = "RRC (ii" + d.getRel() + ")"
case 0x16:
result = "RL (ii" + d.getRel() + ")"
case 0x1E:
result = "RR (ii" + d.getRel() + ")"
case 0x26:
result = "SLA (ii" + d.getRel() + ")"
case 0x2E:
result = "SRA (ii" + d.getRel() + ")"
case 0x3E:
result = "SRL (ii" + d.getRel() + ")"
case 0x46:
result = "BIT 0" + sep + "(ii" + d.getRel() + ")"
case 0x4E:
result = "BIT 1" + sep + "(ii" + d.getRel() + ")"
case 0x56:
result = "BIT 2" + sep + "(ii" + d.getRel() + ")"
case 0x5E:
result = "BIT 3" + sep + "(ii" + d.getRel() + ")"
case 0x66:
result = "BIT 4" + sep + "(ii" + d.getRel() + ")"
case 0x6E:
result = "BIT 5" + sep + "(ii" + d.getRel() + ")"
case 0x76:
result = "BIT 6" + sep + "(ii" + d.getRel() + ")"
case 0x7E:
result = "BIT 7" + sep + "(ii" + d.getRel() + ")"
case 0x86:
result = "RES 0" + sep + "(ii" + d.getRel() + ")"
case 0x8E:
result = "RES 1" + sep + "(ii" + d.getRel() + ")"
case 0x96:
result = "RES 2" + sep + "(ii" + d.getRel() + ")"
case 0x9E:
result = "RES 3" + sep + "(ii" + d.getRel() + ")"
case 0xA6:
result = "RES 4" + sep + "(ii" + d.getRel() + ")"
case 0xAE:
result = "RES 5" + sep + "(ii" + d.getRel() + ")"
case 0xB6:
result = "RES 6" + sep + "(ii" + d.getRel() + ")"
case 0xBE:
result = "RES 7" + sep + "(ii" + d.getRel() + ")"
case 0xC6:
result = "SET 0" + sep + "(ii" + d.getRel() + ")"
case 0xCE:
result = "SET 1" + sep + "(ii" + d.getRel() + ")"
case 0xD6:
result = "SET 2" + sep + "(ii" + d.getRel() + ")"
case 0xDE:
result = "SET 3" + sep + "(ii" + d.getRel() + ")"
case 0xE6:
result = "SET 4" + sep + "(ii" + d.getRel() + ")"
case 0xEE:
result = "SET 5" + sep + "(ii" + d.getRel() + ")"
case 0xF6:
result = "SET 6" + sep + "(ii" + d.getRel() + ")"
case 0xFE:
result = "SET 7" + sep + "(ii" + d.getRel() + ")"
default:
result = fmt.Sprintf("DB 0x%02X, 0x%02X, 0x%02X", op1, op2, opcode)
}
return result
}
func (d *Disassembler) opocodeED() string {
opcode := d.getByte()
result := ""
switch opcode {
case 0x40:
result = "IN B" + sep + "(C)"
case 0x41:
result = "OUT (C)" + sep + "B"
case 0x42:
result = "SBC HL" + sep + "BC"
case 0x43:
result = "LD (" + d.getW() + ")" + sep + "BC"
case 0x44, 0x4C, 0x54, 0x5C, 0x64, 0x6C, 0x74, 0x7C:
result = "NEG"
case 0x45, 0x55, 0x5D, 0x65, 0x6D, 0x75, 0x7D:
result = "RETN"
case 0x46, 0x4E, 0x66, 0x6E:
result = "IM 0"
case 0x47:
result = "LD I" + sep + "A"
case 0x48:
result = "IN C" + sep + "(C)"
case 0x49:
result = "OUT (C)" + sep + "C"
case 0x4A:
result = "ADC HL" + sep + "BC"
case 0x4B:
result = "LD BC" + sep + "(" + d.getW() + ")"
case 0x4D:
result = "REТI"
case 0x4F:
result = "LD R" + sep + "A"
case 0x50:
result = "IN D" + sep + "(C)"
case 0x51:
result = "OUT (C)" + sep + "D"
case 0x52:
result = "SBC HL" + sep + "DE"
case 0x53:
result = "LD (nn)" + sep + "DE"
case 0x56, 0x76:
result = "IM 1"
case 0x57:
result = "LD A" + sep + "I"
case 0x58:
result = "IN E" + sep + "(C)"
case 0x59:
result = "OUT (C)" + sep + "E"
case 0x5A:
result = "ADC HL" + sep + "DE"
case 0x5B:
result = "LD DE" + sep + "(" + d.getW() + ")"
case 0x5E, 0x7E:
result = "IM 2"
case 0x5F:
result = "LD A" + sep + "R"
case 0x60:
result = "IN H" + sep + "(C)"
case 0x61:
result = "OUT (C)" + sep + "H"
case 0x62:
result = "SBC HL" + sep + "HL"
case 0x63:
result = "LD (nn)" + sep + "HL"
case 0x67:
result = "RRD"
case 0x68:
result = "IN L" + sep + " (C)"
case 0x69:
result = "OUT (C)" + sep + "L"
case 0x6A:
result = "ADC HL" + sep + " HL"
case 0x6B:
result = "LD HL" + sep + " (nn)"
case 0x6F:
result = "RLD"
case 0x70:
result = "INF"
case 0x71:
result = "OUT (C)" + sep + " 0"
case 0x72:
result = "SBC HL" + sep + "SP"
case 0x73:
result = "LD (nn)" + sep + "SP"
case 0x78:
result = "IN A" + sep + "(C)"
case 0x79:
result = "OUT (C)" + sep + "A"
case 0x7A:
result = "ADC HL" + sep + "SP"
case 0x7B:
result = "LD SP" + sep + "(" + d.getW() + ")"
case 0xA0:
result = "LDI"
case 0xA1:
result = "CPI"
case 0xA2:
result = "INI"
case 0xA3:
result = "OUTI"
case 0xA8:
result = "LDD"
case 0xA9:
result = "CPD"
case 0xAA:
result = "IND"
case 0xAB:
result = "OUTD"
case 0xB0:
result = "LDIR"
case 0xB1:
result = "CPIR"
case 0xB2:
result = "INIR"
case 0xB3:
result = "OTIR"
case 0xB8:
result = "LDDR"
case 0xB9:
result = "CPDR"
case 0xBA:
result = "INDR"
case 0xBB:
result = "OTDR"
default:
result = fmt.Sprintf("DB 0xED, 0x%02X", opcode)
}
return result
}

91
dis/z80disasm_test.go Normal file
View File

@ -0,0 +1,91 @@
package dis
import "testing"
var disasm *Disassembler
type TestComp struct {
memory [65536]byte
}
func (t *TestComp) M1MemRead(addr uint16) byte {
return t.memory[addr]
}
func (t *TestComp) MemRead(addr uint16) byte {
return t.memory[addr]
}
func (t *TestComp) MemWrite(addr uint16, val byte) {
t.memory[addr] = val
}
func (t *TestComp) IORead(port uint16) byte {
return byte(port >> 8)
}
func (t *TestComp) IOWrite(port uint16, val byte) {
//
}
var testComp *TestComp
func init() {
testComp = &TestComp{}
for i := 0; i < 65536; i++ {
testComp.memory[i] = 0x3f
}
disasm = NewDisassembler(testComp)
}
func setMemory(addr uint16, value []byte) {
for i := 0; i < len(value); i++ {
testComp.memory[addr+uint16(i)] = value[i]
}
}
var test = []byte{0x31, 0x2c, 0x05, 0x11, 0x0e, 0x01, 0x0e, 0x09, 0xcd, 0x05, 0x00, 0xc3, 0x00, 0x00}
func Test_LD_SP_nn(t *testing.T) {
expected := " 0100 LD SP, 0x052C"
setMemory(0x100, test)
res := disasm.Disassm(0x100)
if res != expected {
t.Errorf("Error disasm LD SP, nn, result '%s', expected '%s'", res, expected)
}
}
func Test_LD_DE_nn(t *testing.T) {
expected := " 0103 LD DE, 0x010E"
setMemory(0x100, test)
res := disasm.Disassm(0x103)
if res != expected {
t.Errorf("Error disasm LD DE, nn, result '%s', expected '%s'", res, expected)
}
}
func Test_LD_C_n(t *testing.T) {
expected := " 0106 LD C, 0x09"
setMemory(0x100, test)
res := disasm.Disassm(0x106)
if res != expected {
t.Errorf("Error disasm LD C, n, result '%s', expected '%s'", res, expected)
}
}
func Test_CALL_nn(t *testing.T) {
expected := " 0108 CALL 0x0005"
setMemory(0x100, test)
res := disasm.Disassm(0x108)
if res != expected {
t.Errorf("Error disasm CALL nn, result '%s', expected '%s'", res, expected)
}
}
func Test_JP_nn(t *testing.T) {
expected := " 010B JP 0x0000"
setMemory(0x100, test)
res := disasm.Disassm(0x10b)
if res != expected {
t.Errorf("Error disasm JP nn, result '%s', expected '%s'", res, expected)
}
}

8
go.mod Normal file
View File

@ -0,0 +1,8 @@
module github.com/romychs/z80go
go 1.25
require (
github.com/sirupsen/logrus v1.9.4
gopkg.in/yaml.v3 v3.0.1
)

3
go.sum Normal file
View File

@ -0,0 +1,3 @@
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

226
helper.go Normal file
View File

@ -0,0 +1,226 @@
package z80go
import log "github.com/sirupsen/logrus"
func (z *CPU) flags() FlagsType {
return FlagsType{
S: z.Flags.S,
Z: z.Flags.Z,
Y: z.Flags.Y,
H: z.Flags.H,
X: z.Flags.X,
P: z.Flags.P,
N: z.Flags.N,
C: z.Flags.C,
}
}
func (z *CPU) altFlags() FlagsType {
return FlagsType{
S: z.FlagsAlt.S,
Z: z.FlagsAlt.Z,
Y: z.FlagsAlt.Y,
H: z.FlagsAlt.H,
X: z.FlagsAlt.X,
P: z.FlagsAlt.P,
N: z.FlagsAlt.N,
C: z.FlagsAlt.C,
}
}
func (z *CPU) rb(addr uint16) byte {
z.memAccess[addr] = MemAccessRead
return z.core.MemRead(addr)
}
func (z *CPU) wb(addr uint16, val byte) {
z.memAccess[addr] = MemAccessWrite
z.core.MemWrite(addr, val)
}
func (z *CPU) rw(addr uint16) uint16 {
z.memAccess[addr] = MemAccessRead
z.memAccess[addr+1] = MemAccessRead
return (uint16(z.core.MemRead(addr+1)) << 8) | uint16(z.core.MemRead(addr))
}
func (z *CPU) ww(addr uint16, val uint16) {
z.memAccess[addr] = MemAccessWrite
z.memAccess[addr+1] = MemAccessWrite
z.core.MemWrite(addr, byte(val))
z.core.MemWrite(addr+1, byte(val>>8))
}
func (z *CPU) pushW(val uint16) {
z.SP -= 2
z.ww(z.SP, val)
z.extendedStack[z.SP] = PushValueTypePush
}
func (z *CPU) popW() uint16 {
z.SP += 2
return z.rw(z.SP - 2)
}
func (z *CPU) nextB() byte {
b := z.core.MemRead(z.PC)
z.PC++
return b
}
func (z *CPU) nextW() uint16 {
w := (uint16(z.core.MemRead(z.PC+1)) << 8) | uint16(z.core.MemRead(z.PC))
z.PC += 2
return w
}
func (z *CPU) bc() uint16 {
return (uint16(z.B) << 8) | uint16(z.C)
}
func (z *CPU) de() uint16 {
return (uint16(z.D) << 8) | uint16(z.E)
}
func (z *CPU) hl() uint16 {
return (uint16(z.H) << 8) | uint16(z.L)
}
func (z *CPU) setBC(val uint16) {
z.B = byte(val >> 8)
z.C = byte(val)
}
func (z *CPU) setDE(val uint16) {
z.D = byte(val >> 8)
z.E = byte(val)
}
func (z *CPU) setHL(val uint16) {
z.H = byte(val >> 8)
z.L = byte(val)
}
func (z *CPU) f() byte {
val := byte(0)
if z.Flags.C {
val |= 0x01
}
if z.Flags.N {
val |= 0x02
}
if z.Flags.P {
val |= 0x04
}
if z.Flags.X {
val |= 0x08
}
if z.Flags.H {
val |= 0x10
}
if z.Flags.Y {
val |= 0x20
}
if z.Flags.Z {
val |= 0x40
}
if z.Flags.S {
val |= 0x80
}
return val
}
func (z *CPU) setF(val byte) {
z.Flags.C = val&1 != 0
z.Flags.N = (val>>1)&1 != 0
z.Flags.P = (val>>2)&1 != 0
z.Flags.X = (val>>3)&1 != 0
z.Flags.H = (val>>4)&1 != 0
z.Flags.Y = (val>>5)&1 != 0
z.Flags.Z = (val>>6)&1 != 0
z.Flags.S = (val>>7)&1 != 0
}
// increments R, keeping the highest byte intact
func (z *CPU) incR() {
z.R = (z.R & 0x80) | ((z.R + 1) & 0x7f)
}
func boolToInt32(b bool) int32 {
if b {
return 1
}
return 0
}
// returns if there was a carry between bit "bit_no" and "bit_no - 1" when
// executing "a + b + cy"
func carry(bitNo int, a uint16, b uint16, cy bool) bool {
result := int32(a) + int32(b) + boolToInt32(cy)
carry := result ^ int32(a) ^ int32(b)
return (carry & (1 << bitNo)) != 0
}
// parity returns the parity of byte: 0 if odd, else 1
func parity(val byte) bool {
ones := byte(0)
for i := 0; i < 8; i++ {
ones += (val >> i) & 1
}
return (ones & 1) == 0
}
// updateXY set undocumented 3rd (X) and 5th (Y) flags
func (z *CPU) updateXY(result byte) {
z.Flags.Y = result&0x20 != 0
z.Flags.X = result&0x08 != 0
}
func (z *CPU) DebugOutput() {
log.Debugf("PC: %04X, AF: %04X, BC: %04X, DE: %04X, HL: %04X, SP: %04X, IX: %04X, IY: %04X, I: %02X, R: %02X",
z.PC, (uint16(z.A)<<8)|uint16(z.f()), z.bc(), z.de(), z.hl(), z.SP,
z.IX, z.IY, z.I, z.R)
log.Debugf("\t(%02X %02X %02X %02X), cycleCount: %d\n", z.rb(z.PC), z.rb(z.PC+1),
z.rb(z.PC+2), z.rb(z.PC+3), z.cycleCount)
}
func (z *CPU) Reset() {
z.cycleCount = 0
z.PC = 0
z.SP = 0xFFFF
z.IX = 0
z.IY = 0
z.memPtr = 0
z.A = 0xFF
z.B = 0
z.C = 0
z.D = 0
z.E = 0
z.H = 0
z.L = 0
z.AAlt = 0
z.BAlt = 0
z.CAlt = 0
z.DAlt = 0
z.EAlt = 0
z.HAlt = 0
z.LAlt = 0
z.I = 0
z.R = 0
z.Flags.SetFlags(0xff)
z.FlagsAlt.SetFlags(0xff)
z.iffDelay = 0
z.IMode = 0
z.Iff1 = false
z.Iff2 = false
z.Halted = false
z.IntOccurred = false
z.NmiOccurred = false
z.intData = 0
}

1255
opcodes.go Normal file

File diff suppressed because it is too large Load Diff

162
opcodesCB.go Normal file
View File

@ -0,0 +1,162 @@
package z80go
import log "github.com/sirupsen/logrus"
// executes A CB opcode
func (z *CPU) execOpcodeCB(opcode byte) {
z.cycleCount += 8
z.incR()
// decoding instructions from http://z80.info/decoding.htm#cb
x_ := (opcode >> 6) & 3 // 0b11
y_ := (opcode >> 3) & 7 // 0b111
z_ := opcode & 7 // 0b111
var hl byte
v := byte(0)
reg := &v
switch z_ {
case 0:
reg = &z.B
case 1:
reg = &z.C
case 2:
reg = &z.D
case 3:
reg = &z.E
case 4:
reg = &z.H
case 5:
reg = &z.L
case 6:
hl = z.rb(z.hl())
reg = &hl
case 7:
reg = &z.A
}
switch x_ {
case 0:
// rot[y] r[z]
switch y_ {
case 0:
*reg = z.cbRlc(*reg)
case 1:
*reg = z.cbRrc(*reg)
case 2:
*reg = z.cbRl(*reg)
case 3:
*reg = z.cbRr(*reg)
case 4:
*reg = z.cbSla(*reg)
case 5:
*reg = z.cbSra(*reg)
case 6:
*reg = z.cbSll(*reg)
case 7:
*reg = z.cbSrl(*reg)
}
case 1:
// BIT y, r[z]
z.cbBit(*reg, y_)
// in bit (hl), x/y flags are handled differently:
if z_ == 6 {
z.updateXY(byte(z.memPtr >> 8))
z.cycleCount += 4
}
case 2:
*reg &= ^(1 << y_) // RES y, r[z]
case 3:
*reg |= 1 << y_ // SET y, r[z]
}
if (x_ == 0 || x_ == 2 || x_ == 3) && z_ == 6 {
z.cycleCount += 7
}
if reg == &hl {
z.wb(z.hl(), hl)
}
}
// execOpcodeDcb executes A displaced CB opcode (DDCB or FDCB)
func (z *CPU) execOpcodeDcb(opcode byte, addr uint16) {
val := z.rb(addr)
result := byte(0)
// decoding instructions from http://z80.info/decoding.htm#ddcb
x_ := (opcode >> 6) & 3 // 0b11
y_ := (opcode >> 3) & 7 // 0b111
z_ := opcode & 7 // 0b111
switch x_ {
case 0:
// rot[y] (iz+d)
switch y_ {
case 0:
result = z.cbRlc(val)
case 1:
result = z.cbRrc(val)
case 2:
result = z.cbRl(val)
case 3:
result = z.cbRr(val)
case 4:
result = z.cbSla(val)
case 5:
result = z.cbSra(val)
case 6:
result = z.cbSll(val)
case 7:
result = z.cbSrl(val)
}
case 1:
// bit y,(iz+d)
result = z.cbBit(val, y_)
z.updateXY(byte(addr >> 8))
case 2:
result = val & ^(1 << y_) // res y, (iz+d)
case 3:
result = val | (1 << y_) // set y, (iz+d)
default:
log.Errorf("Unknown XYCB opcode: %02X\n", opcode)
}
// ld r[z], rot[y] (iz+d)
// ld r[z], res y,(iz+d)
// ld r[z], set y,(iz+d)
if x_ != 1 && z_ != 6 {
switch z_ {
case 0:
z.B = result
case 1:
z.C = result
case 2:
z.D = result
case 3:
z.E = result
case 4:
z.H = result
case 5:
z.L = result
// always false
//case 6:
// z.wb(z.hl(), result)
case 7:
z.A = result
}
}
if x_ == 1 {
// bit instructions take 20 cycles, others take 23
z.cycleCount += 20
} else {
z.wb(addr, result)
z.cycleCount += 23
}
}

206
opcodesDDFD.go Normal file
View File

@ -0,0 +1,206 @@
package z80go
// executes A DD/FD opcode (IZ = IX or IY)
func (z *CPU) execOpcodeDDFD(opcode byte, iz *uint16) {
z.cycleCount += uint32(cyclesDDFD[opcode])
z.incR()
switch opcode {
case 0xE1:
*iz = z.popW() // pop iz
case 0xE5:
z.pushW(*iz) // push iz
case 0xE9:
// jp iz
z.PC = *iz
//z.jump(*iz)
case 0x09:
z.addIZ(iz, z.bc()) // add iz,bc
case 0x19:
z.addIZ(iz, z.de()) // add iz,de
case 0x29:
z.addIZ(iz, *iz) // add iz,iz
case 0x39:
z.addIZ(iz, z.SP) // add iz,sp
case 0x84:
z.A = z.addB(z.A, byte(*iz>>8), false) // add a,izh
case 0x85:
z.A = z.addB(z.A, byte(*iz), false) // add a,izl
case 0x8C:
z.A = z.addB(z.A, byte(*iz>>8), z.Flags.C) // adc a,izh
case 0x8D:
z.A = z.addB(z.A, byte(*iz), z.Flags.C) // adc a,izl
case 0x86:
z.A = z.addB(z.A, z.rb(z.displace(*iz, z.nextB())), false) // add a,(iz+*)
case 0x8E:
z.A = z.addB(z.A, z.rb(z.displace(*iz, z.nextB())), z.Flags.C) // adc a,(iz+*)
case 0x96:
z.A = z.subB(z.A, z.rb(z.displace(*iz, z.nextB())), false) // sub (iz+*)
case 0x9E:
z.A = z.subB(z.A, z.rb(z.displace(*iz, z.nextB())), z.Flags.C) // sbc (iz+*)
case 0x94:
z.A = z.subB(z.A, byte(*iz>>8), false) // sub izh
case 0x95:
z.A = z.subB(z.A, byte(*iz), false) // sub izl
case 0x9C:
z.A = z.subB(z.A, byte(*iz>>8), z.Flags.C) // sbc izh
case 0x9D:
z.A = z.subB(z.A, byte(*iz), z.Flags.C) // sbc izl
case 0xA6:
z.lAnd(z.rb(z.displace(*iz, z.nextB()))) // and (iz+*)
case 0xA4:
z.lAnd(byte(*iz >> 8)) // and izh
case 0xA5:
z.lAnd(byte(*iz)) // and izl
case 0xAE:
z.lXor(z.rb(z.displace(*iz, z.nextB()))) // xor (iz+*)
case 0xAC:
z.lXor(byte(*iz >> 8)) // xor izh
case 0xAD:
z.lXor(byte(*iz)) // xor izl
case 0xB6:
z.lOr(z.rb(z.displace(*iz, z.nextB()))) // or (iz+*)
case 0xB4:
z.lOr(byte(*iz >> 8)) // or izh
case 0xB5:
z.lOr(byte(*iz)) // or izl
case 0xBE:
z.cp(z.rb(z.displace(*iz, z.nextB()))) // cp (iz+*)
case 0xBC:
z.cp(byte(*iz >> 8)) // cp izh
case 0xBD:
z.cp(byte(*iz)) // cp izl
case 0x23:
*iz += 1 // inc iz
case 0x2B:
*iz -= 1 // dec iz
case 0x34:
// inc (iz+*)
addr := z.displace(*iz, z.nextB())
z.wb(addr, z.inc(z.rb(addr)))
case 0x35:
// dec (iz+*)
addr := z.displace(*iz, z.nextB())
z.wb(addr, z.dec(z.rb(addr)))
case 0x24:
*iz = (*iz & 0x00ff) | (uint16(z.inc(byte(*iz>>8))) << 8) // inc izh
case 0x25:
*iz = (*iz & 0x00ff) | (uint16(z.dec(byte(*iz>>8))) << 8) // dec izh
case 0x2C:
*iz = (*iz & 0xff00) | uint16(z.inc(byte(*iz))) // inc izl
case 0x2D:
*iz = (*iz & 0xff00) | uint16(z.dec(byte(*iz))) // dec izl
case 0x2A:
addr := z.nextW()
*iz = z.rw(addr) // ld iz,(**)
z.memPtr = addr + 1
case 0x22:
addr := z.nextW()
z.ww(addr, *iz) // ld (**),iz
z.memPtr = addr + 1
case 0x21:
*iz = z.nextW() // ld iz,**
case 0x36:
// ld (iz+*),*
addr := z.displace(*iz, z.nextB())
z.wb(addr, z.nextB())
case 0x70:
z.wb(z.displace(*iz, z.nextB()), z.B) // ld (iz+*),b
case 0x71:
z.wb(z.displace(*iz, z.nextB()), z.C) // ld (iz+*),c
case 0x72:
z.wb(z.displace(*iz, z.nextB()), z.D) // ld (iz+*),d
case 0x73:
z.wb(z.displace(*iz, z.nextB()), z.E) // ld (iz+*),e
case 0x74:
z.wb(z.displace(*iz, z.nextB()), z.H) // ld (iz+*),h
case 0x75:
z.wb(z.displace(*iz, z.nextB()), z.L) // ld (iz+*),l
case 0x77:
z.wb(z.displace(*iz, z.nextB()), z.A) // ld (iz+*),a
case 0x46:
z.B = z.rb(z.displace(*iz, z.nextB())) // ld b,(iz+*)
case 0x4E:
z.C = z.rb(z.displace(*iz, z.nextB())) // ld c,(iz+*)
case 0x56:
z.D = z.rb(z.displace(*iz, z.nextB())) // ld d,(iz+*)
case 0x5E:
z.E = z.rb(z.displace(*iz, z.nextB())) // ld e,(iz+*)
case 0x66:
z.H = z.rb(z.displace(*iz, z.nextB())) // ld h,(iz+*)
case 0x6E:
z.L = z.rb(z.displace(*iz, z.nextB())) // ld l,(iz+*)
case 0x7E:
z.A = z.rb(z.displace(*iz, z.nextB())) // ld a,(iz+*)
case 0x44:
z.B = byte(*iz >> 8) // ld b,izh
case 0x4C:
z.C = byte(*iz >> 8) // ld c,izh
case 0x54:
z.D = byte(*iz >> 8) // ld d,izh
case 0x5C:
z.E = byte(*iz >> 8) // ld e,izh
case 0x7C:
z.A = byte(*iz >> 8) // ld a,izh
case 0x45:
z.B = byte(*iz) // ld b,izl
case 0x4D:
z.C = byte(*iz) // ld c,izl
case 0x55:
z.D = byte(*iz) // ld d,izl
case 0x5D:
z.E = byte(*iz) // ld e,izl
case 0x7D:
z.A = byte(*iz) // ld a,izl
case 0x60:
*iz = (*iz & 0x00ff) | (uint16(z.B) << 8) // ld izh,b
case 0x61:
*iz = (*iz & 0x00ff) | (uint16(z.C) << 8) // ld izh,c
case 0x62:
*iz = (*iz & 0x00ff) | (uint16(z.D) << 8) // ld izh,d
case 0x63:
*iz = (*iz & 0x00ff) | (uint16(z.E) << 8) // ld izh,e
case 0x64: // ld izh,izh
case 0x65:
*iz = ((*iz & 0x00ff) << 8) | (*iz & 0x00ff) // ld izh,izl
case 0x67:
*iz = (uint16(z.A) << 8) | (*iz & 0x00ff) // ld izh,a
case 0x26:
*iz = (uint16(z.nextB()) << 8) | (*iz & 0x00ff) // ld izh,*
case 0x68:
*iz = (*iz & 0xff00) | uint16(z.B) // ld izl,b
case 0x69:
*iz = (*iz & 0xff00) | uint16(z.C) // ld izl,c
case 0x6A:
*iz = (*iz & 0xff00) | uint16(z.D) // ld izl,d
case 0x6B:
*iz = (*iz & 0xff00) | uint16(z.E) // ld izl,e
case 0x6C:
*iz = (*iz & 0xff00) | (*iz >> 8) // ld izl,izh
case 0x6D: // ld izl,izl
case 0x6F:
*iz = (*iz & 0xff00) | uint16(z.A) // ld izl,a
case 0x2E:
*iz = (*iz & 0xff00) | uint16(z.nextB()) // ld izl,*
case 0xF9:
z.SP = *iz // ld sp,iz
case 0xE3:
// ex (sp),iz
val := z.rw(z.SP)
z.ww(z.SP, *iz)
*iz = val
z.memPtr = val
case 0xCB:
addr := z.displace(*iz, z.nextB())
op := z.nextB()
z.execOpcodeDcb(op, addr)
default:
// any other FD/DD opcode behaves as a non-prefixed opcode:
z.execOpcode(opcode)
// R should not be incremented twice:
z.incR()
}
}

281
opcodesED.go Normal file
View File

@ -0,0 +1,281 @@
package z80go
import log "github.com/sirupsen/logrus"
// executes A ED opcode
func (z *CPU) execOpcodeED(opcode byte) {
z.cycleCount += uint32(cyclesED[opcode])
z.incR()
switch opcode {
case 0x47:
z.I = z.A // ld i,a
case 0x4F:
z.R = z.A // ld r,a
case 0x57:
// ld a,i
z.A = z.I
z.Flags.S = z.A&0x80 != 0
z.Flags.Z = z.A == 0
z.Flags.H = false
z.Flags.N = false
z.Flags.P = z.Iff2
z.updateXY(z.A)
case 0x5F:
// ld a,r
z.A = z.R
z.Flags.S = z.A&0x80 != 0
z.Flags.Z = z.A == 0
z.Flags.H = false
z.Flags.N = false
z.Flags.P = z.Iff2
case 0x45, 0x55, 0x5D, 0x65, 0x6D, 0x75, 0x7D:
// retn
z.Iff1 = z.Iff2
z.ret()
case 0x4D:
z.ret() // reti
case 0xA0:
z.ldi() // ldi
case 0xB0:
{
z.ldi()
if z.bc() != 0 {
z.PC -= 2
z.cycleCount += 5
z.memPtr = z.PC + 1
}
} // ldir
case 0xA8:
z.ldd() // ldd
case 0xB8:
{
z.ldd()
if z.bc() != 0 {
z.PC -= 2
z.cycleCount += 5
z.memPtr = z.PC + 1
}
} // lddr
case 0xA1:
z.cpi() // cpi
case 0xA9:
z.cpd() // cpd
case 0xB1:
// cpir
z.cpi()
if z.bc() != 0 && !z.Flags.Z {
z.PC -= 2
z.cycleCount += 5
z.memPtr = z.PC + 1
} else {
//z.mem_ptr++
}
//z.cpir()
case 0xB9:
// cpdr
z.cpd()
if z.bc() != 0 && !z.Flags.Z {
z.PC -= 2
z.cycleCount += 5
z.memPtr = z.PC + 1
} else {
//z.mem_ptr++
}
case 0x40:
z.inRC(&z.B) // in b, (c)
z.memPtr = z.bc() + 1
case 0x48:
z.memPtr = z.bc() + 1
z.inRC(&z.C) // in c, (c)
z.updateXY(z.C)
//case 0x4e:
// ld c,(iy+dd)
case 0x50:
z.inRC(&z.D) // in d, (c)
z.memPtr = z.bc() + 1
case 0x58:
// in e, (c)
z.inRC(&z.E)
z.memPtr = z.bc() + 1
z.updateXY(z.E)
case 0x60:
z.inRC(&z.H) // in h, (c)
z.memPtr = z.bc() + 1
case 0x68:
z.inRC(&z.L) // in l, (c)
z.memPtr = z.bc() + 1
z.updateXY(z.L)
case 0x70:
// in (c)
var val byte
z.inRC(&val)
z.memPtr = z.bc() + 1
case 0x78:
// in a, (c)
z.inRC(&z.A)
z.memPtr = z.bc() + 1
z.updateXY(z.A)
case 0xA2:
z.ini() // ini
case 0xB2:
// inir
z.ini()
if z.B > 0 {
z.PC -= 2
z.cycleCount += 5
}
case 0xAA:
// ind
z.ind()
case 0xBA:
// indr
z.ind()
if z.B > 0 {
z.PC -= 2
z.cycleCount += 5
}
case 0x41:
z.core.IOWrite(z.bc(), z.B) // out (c), b
z.memPtr = z.bc() + 1
case 0x49:
z.core.IOWrite(z.bc(), z.C) // out (c), c
z.memPtr = z.bc() + 1
case 0x51:
z.core.IOWrite(z.bc(), z.D) // out (c), d
z.memPtr = z.bc() + 1
case 0x59:
z.core.IOWrite(z.bc(), z.E) // out (c), e
z.memPtr = z.bc() + 1
case 0x61:
z.core.IOWrite(z.bc(), z.H) // out (c), h
z.memPtr = z.bc() + 1
case 0x69:
z.core.IOWrite(z.bc(), z.L) // out (c), l
z.memPtr = z.bc() + 1
case 0x71:
z.core.IOWrite(z.bc(), 0) // out (c), 0
z.memPtr = z.bc() + 1
case 0x79:
// out (c), a
z.core.IOWrite(z.bc(), z.A)
z.memPtr = z.bc() + 1
case 0xA3:
z.outi() // outi
case 0xB3:
// otir
z.outi()
if z.B > 0 {
z.PC -= 2
z.cycleCount += 5
}
case 0xAB:
z.outd() // outd
case 0xBB:
// otdr
z.outd()
if z.B > 0 {
z.cycleCount += 5
z.PC -= 2
}
case 0x42:
z.sbcHL(z.bc()) // sbc hl,bc
case 0x52:
z.sbcHL(z.de()) // sbc hl,de
case 0x62:
z.sbcHL(z.hl()) // sbc hl,hl
case 0x72:
z.sbcHL(z.SP) // sbc hl,sp
case 0x4A:
z.adcHL(z.bc()) // adc hl,bc
case 0x5A:
z.adcHL(z.de()) // adc hl,de
case 0x6A:
z.adcHL(z.hl()) // adc hl,hl
case 0x7A:
z.adcHL(z.SP) // adc hl,sp
case 0x43:
// ld (**), bc
addr := z.nextW()
z.ww(addr, z.bc())
z.memPtr = addr + 1
case 0x53:
// ld (**), de
addr := z.nextW()
z.ww(addr, z.de())
z.memPtr = addr + 1
case 0x63:
// ld (**), hl
addr := z.nextW()
z.ww(addr, z.hl())
z.memPtr = addr + 1
case 0x73:
// ld (**), hl
addr := z.nextW()
z.ww(addr, z.SP)
z.memPtr = addr + 1
case 0x4B:
// ld bc, (**)
addr := z.nextW()
z.setBC(z.rw(addr))
z.memPtr = addr + 1
case 0x5B:
// ld de, (**)
addr := z.nextW()
z.setDE(z.rw(addr))
z.memPtr = addr + 1
case 0x6B:
// ld hl, (**)
addr := z.nextW()
z.setHL(z.rw(addr))
z.memPtr = addr + 1
case 0x7B:
// ld sp,(**)
addr := z.nextW()
z.SP = z.rw(addr)
z.memPtr = addr + 1
case 0x44, 0x54, 0x64, 0x74, 0x4C, 0x5C, 0x6C, 0x7C:
z.A = z.subB(0, z.A, false) // neg
case 0x46, 0x4e, 0x66, 0x6e:
z.IMode = 0 // im 0
case 0x56, 0x76:
z.IMode = 1 // im 1
case 0x5E, 0x7E:
z.IMode = 2 // im 2
case 0x67:
// rrd
a := z.A
val := z.rb(z.hl())
z.A = (a & 0xF0) | (val & 0xF)
z.wb(z.hl(), (val>>4)|(a<<4))
z.Flags.N = false
z.Flags.H = false
z.updateXY(z.A)
z.Flags.Z = z.A == 0
z.Flags.S = z.A&0x80 != 0
z.Flags.P = parity(z.A)
z.memPtr = z.hl() + 1
case 0x6F:
// rld
a := z.A
val := z.rb(z.hl())
z.A = (a & 0xF0) | (val >> 4)
z.wb(z.hl(), (val<<4)|(a&0xF))
z.Flags.N = false
z.Flags.H = false
z.updateXY(z.A)
z.Flags.Z = z.A == 0
z.Flags.S = z.A&0x80 != 0
z.Flags.P = parity(z.A)
z.memPtr = z.hl() + 1
default:
log.Errorf("Unknown ED opcode: %02X\n", opcode)
}
}

18913
tests/tests.expected Normal file

File diff suppressed because it is too large Load Diff

9153
tests/tests.in Normal file

File diff suppressed because it is too large Load Diff

207
z80go.go Normal file
View File

@ -0,0 +1,207 @@
// Package z80go provides basic Z80 CPU emulation
package z80go
const (
PushValueTypeDefault = iota // Unknown status of word on stack
PushValueTypeCall // Return from call address on stack
PushValueTypeRst // Return from RST address on stack
PushValueTypePush // 16-bit value, PUSH-ed to stack
//PushValueTypeMaskableInt
//PushValueTypeNonMaskableInt
)
type PushValueType byte
// MemIoRW interface for CPU to access memory and io ports of emulated computer
type MemIoRW interface {
//// M1MemRead Read byte from memory for specified address @ M1 cycle
//M1MemRead(addr uint16) byte
// MemRead Read byte from memory for specified address
MemRead(addr uint16) byte
// MemWrite Write byte to memory to specified address
MemWrite(addr uint16, val byte)
// IORead Read byte from specified port
IORead(port uint16) byte
// IOWrite Write byte to specified port
IOWrite(port uint16, val byte)
}
// CPUInterface Interface to CPU emulator
type CPUInterface interface {
// Reset CPU to initial state
Reset()
// RunInstruction Run single instruction, return number of CPU cycles
RunInstruction() uint32
// GetState Get current CPU state
GetState() *CPU
// SetState Set current CPU state
SetState(state *CPU)
// DebugOutput out current CPU state
DebugOutput()
}
// FlagsType - Processor flags
type FlagsType struct {
S bool `json:"s,omitempty"` // Sign flag
Z bool `json:"z,omitempty"` // Zero flag
Y bool `json:"y,omitempty"` // Y undocumented flag
H bool `json:"h,omitempty"` // Half carry flag
X bool `json:"x,omitempty"` // X undocumented flag
P bool `json:"p,omitempty"` // Parity/Overflow flag
N bool `json:"n,omitempty"` // Add/Substract flag
C bool `json:"c,omitempty"` // Carry flag
}
// CPU - Processor state
type CPU struct {
A byte `json:"a,omitempty"`
B byte `json:"b,omitempty"`
C byte `json:"c,omitempty"`
D byte `json:"d,omitempty"`
E byte `json:"e,omitempty"`
H byte `json:"h,omitempty"`
L byte `json:"l,omitempty"`
AAlt byte `json:"AAlt,omitempty"`
BAlt byte `json:"BAlt,omitempty"`
CAlt byte `json:"CAlt,omitempty"`
DAlt byte `json:"DAlt,omitempty"`
EAlt byte `json:"EAlt,omitempty"`
HAlt byte `json:"HAlt,omitempty"`
LAlt byte `json:"LAlt,omitempty"`
IX uint16 `json:"IX,omitempty"`
IY uint16 `json:"IY,omitempty"`
I byte `json:"i,omitempty"`
R byte `json:"r,omitempty"`
SP uint16 `json:"SP,omitempty"`
PC uint16 `json:"PC,omitempty"`
Flags FlagsType `json:"flags"`
FlagsAlt FlagsType `json:"flagsAlt"`
IMode byte `json:"IMode,omitempty"`
Iff1 bool `json:"iff1,omitempty"`
Iff2 bool `json:"iff2,omitempty"`
Halted bool `json:"halted,omitempty"`
CycleCount uint32 `json:"cycleCount,omitempty"`
IntOccurred bool `json:"intOccurred,omitempty"`
NmiOccurred bool `json:"interruptOccurred,omitempty"`
memPtr uint16
core MemIoRW
intData byte
cycleCount uint32 // cycle count (t-states)
memAccess map[uint16]byte
codeCoverageEnabled bool
codeCoverage map[uint16]bool
extendedStackEnabled bool
extendedStack map[uint16]PushValueType
iffDelay byte
intPending bool
nmiPending bool
}
// Flags - return flags as byte value
// Used to simplify manipulations with AF register from debugger
func (f *FlagsType) Flags() byte {
var flags byte = 0
if f.S {
flags |= 0x80
}
if f.Z {
flags |= 0x40
}
if f.Y {
flags |= 0x20
}
if f.H {
flags |= 0x10
}
if f.X {
flags |= 0x08
}
if f.P {
flags |= 0x04
}
if f.N {
flags |= 0x02
}
if f.C {
flags |= 0x01
}
return flags
}
// String - return string representation of CPU flags
func (f *FlagsType) String() string {
flags := []byte{'-', '-', '-', '-', '-', '-', '-', '-'}
if f.S {
flags[0] = 'S'
}
if f.Z {
flags[1] = 'Z'
}
if f.Y {
flags[2] = '5'
}
if f.H {
flags[3] = 'H'
}
if f.X {
flags[4] = '3'
}
if f.P {
flags[5] = 'P'
}
if f.N {
flags[6] = 'N'
}
if f.C {
flags[7] = 'C'
}
return string(flags)
}
func (z *CPU) IIFStr() string {
flags := []byte{'-', '-'}
if z.Iff1 {
flags[0] = '1'
}
if z.Iff2 {
flags[1] = '2'
}
return string(flags)
}
//// Flags - return state of CPU flags
//func Flags(f byte) FlagsType {
// return FlagsType{
// S: f&0x80 != 0,
// Z: f&0x40 != 0,
// Y: f&0x20 != 0,
// H: f&0x10 != 0,
// X: f&0x08 != 0,
// P: f&0x04 != 0,
// N: f&0x02 != 0,
// C: f&0x01 != 0,
// }
//}
// SetFlags - set CPU flags by flags byte.
// Used to simplify manipulations with AF register from debugger
func (f *FlagsType) SetFlags(flags byte) {
f.S = flags&0x80 != 0
f.Z = flags&0x40 != 0
f.Y = flags&0x20 != 0
f.H = flags&0x10 != 0
f.X = flags&0x08 != 0
f.P = flags&0x04 != 0
f.N = flags&0x02 != 0
f.C = flags&0x01 != 0
}
// GetPC - return PC register value only, for fast breakpoints test from debugger
func (z *CPU) GetPC() uint16 {
return z.PC
}

598
z80go_test.go Normal file
View File

@ -0,0 +1,598 @@
package z80go_test
import (
"bufio"
"bytes"
_ "embed"
"okemu/z80"
"okemu/z80/c99"
"strconv"
"strings"
"testing"
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 *c99.Z80
memory [65536]byte
ports [256]byte
}
var z80TestsIn map[string]Z80TestIn
var z80TestsExpected map[string]Expect
var computer Computer
var testNames []string
//var z80 *c99.Z80
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 = c99.New(&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 {
cy += computer.cpu.RunInstruction()
if cy >= uint32(exp.state.tStates) {
break
}
}
checkComputerState(t, name)
}
}
func setComputerState(test Z80TestIn) {
state := z80.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: z80.GetFlags(byte(test.registers.AF)),
FlagsAlt: z80.GetFlags(byte(test.registers.AFa)),
IMode: test.state.IM,
Iff1: test.state.IFF1,
Iff2: test.state.IFF2,
Halted: test.state.isHalted,
DoDelayedDI: false,
DoDelayedEI: false,
CycleCount: 0,
InterruptOccurred: 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.GetFlags() {
t.Errorf("%s: Expected Flags to be %08b, got %08b", name, lo(exp.registers.AF), state.Flags.GetFlags())
}
if lo(exp.registers.AFa) != state.FlagsAlt.GetFlags() {
t.Errorf("%s: Expected Flags' to be %08b, got %08b", name, lo(exp.registers.AFa), state.FlagsAlt.GetFlags())
}
// 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++
}
}
}