mirror of
https://github.com/romychs/z80go.git
synced 2026-04-16 08:44:20 +03:00
First refactoring from okemu
This commit is contained in:
commit
44c36ecb94
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
*.bak
|
||||||
|
*.lst
|
||||||
|
*.tmp
|
||||||
|
*.sh
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
src/.idea/
|
||||||
28
LICENSE
Normal file
28
LICENSE
Normal 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
5
README.md
Normal 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
58
constants.go
Normal 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
163
cpu.go
Normal 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
672
dis/z80disasm.go
Normal 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
91
dis/z80disasm_test.go
Normal 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
8
go.mod
Normal 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
3
go.sum
Normal 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
226
helper.go
Normal 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
1255
opcodes.go
Normal file
File diff suppressed because it is too large
Load Diff
162
opcodesCB.go
Normal file
162
opcodesCB.go
Normal 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
206
opcodesDDFD.go
Normal 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
281
opcodesED.go
Normal 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
18913
tests/tests.expected
Normal file
File diff suppressed because it is too large
Load Diff
9153
tests/tests.in
Normal file
9153
tests/tests.in
Normal file
File diff suppressed because it is too large
Load Diff
207
z80go.go
Normal file
207
z80go.go
Normal 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
598
z80go_test.go
Normal 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++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user