mirror of
https://github.com/romychs/Ocean-240.2-Emulator.git
synced 2026-04-21 11:03:21 +03:00
551 lines
12 KiB
Go
551 lines
12 KiB
Go
package z80em
|
|
|
|
const SpDefault uint16 = 0xdff0
|
|
|
|
// FlagsType - Processor flags
|
|
type FlagsType struct {
|
|
S bool
|
|
Z bool
|
|
Y bool
|
|
H bool
|
|
X bool
|
|
P bool
|
|
N bool
|
|
C bool
|
|
}
|
|
|
|
// StateType - Processor state
|
|
type StateType struct {
|
|
A byte
|
|
B byte
|
|
C byte
|
|
D byte
|
|
E byte
|
|
H byte
|
|
L byte
|
|
APrime byte
|
|
BPrime byte
|
|
CPrime byte
|
|
DPrime byte
|
|
EPrime byte
|
|
HPrime byte
|
|
LPrime byte
|
|
IX byte
|
|
IY byte
|
|
I byte
|
|
R byte
|
|
SP uint16
|
|
PC uint16
|
|
Flags FlagsType
|
|
FlagsPrime FlagsType
|
|
|
|
IMode byte
|
|
Iff1 byte
|
|
Iff2 byte
|
|
Halted bool
|
|
DoDelayedDI bool
|
|
DoDelayedEI bool
|
|
CycleCounter byte
|
|
interruptOccurred bool
|
|
core MemIoRW
|
|
}
|
|
|
|
type MemIoRW interface {
|
|
// M1MemRead Read byte from memory for specified address
|
|
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)
|
|
}
|
|
|
|
type Z80em interface {
|
|
// Reset CPU to initial state
|
|
Reset()
|
|
// RunInstruction Run single instruction, return number of CPU cycles
|
|
RunInstruction() byte
|
|
// GetState Get current CPU state
|
|
GetState() *StateType
|
|
// SetState Set current CPU state
|
|
SetState(state *StateType)
|
|
// setFlagsRegister Set value for CPU flags by specified byte [7:0] = SZYHXPNC
|
|
setFlagsRegister(flags byte)
|
|
|
|
decodeInstruction(opcode byte) byte
|
|
pushWord(pc uint16)
|
|
}
|
|
|
|
func (s StateType) Reset() {
|
|
s.A = 0
|
|
s.R = 0
|
|
s.SP = SpDefault
|
|
s.PC = 0
|
|
s.setFlagsRegister(0)
|
|
// Interrupts disabled
|
|
s.IMode = 0
|
|
s.Iff1 = 0
|
|
s.Iff2 = 0
|
|
s.interruptOccurred = false
|
|
|
|
// Start not halted
|
|
s.Halted = false
|
|
s.DoDelayedDI = false
|
|
s.DoDelayedEI = false
|
|
// no cycles
|
|
s.CycleCounter = 0
|
|
}
|
|
|
|
func (s StateType) GetState() *StateType {
|
|
return &StateType{
|
|
A: s.A,
|
|
B: s.B,
|
|
C: s.C,
|
|
D: s.D,
|
|
E: s.E,
|
|
H: s.H,
|
|
L: s.L,
|
|
APrime: s.APrime,
|
|
BPrime: s.BPrime,
|
|
CPrime: s.CPrime,
|
|
DPrime: s.DPrime,
|
|
EPrime: s.EPrime,
|
|
HPrime: s.HPrime,
|
|
IX: s.IX,
|
|
IY: s.IY,
|
|
R: s.R,
|
|
SP: s.SP,
|
|
PC: s.PC,
|
|
Flags: s.Flags,
|
|
FlagsPrime: s.FlagsPrime,
|
|
IMode: s.IMode,
|
|
Iff1: s.Iff1,
|
|
Iff2: s.Iff2,
|
|
Halted: s.Halted,
|
|
DoDelayedDI: s.DoDelayedDI,
|
|
DoDelayedEI: s.DoDelayedEI,
|
|
CycleCounter: s.CycleCounter,
|
|
}
|
|
}
|
|
|
|
func (s StateType) SetState(state *StateType) {
|
|
s.A = state.A
|
|
s.B = state.B
|
|
s.C = state.C
|
|
s.D = state.D
|
|
s.E = state.E
|
|
s.H = state.H
|
|
s.L = state.L
|
|
s.APrime = state.APrime
|
|
s.BPrime = state.BPrime
|
|
s.CPrime = state.CPrime
|
|
s.DPrime = state.DPrime
|
|
s.EPrime = state.EPrime
|
|
s.HPrime = state.HPrime
|
|
s.IX = state.IX
|
|
s.IY = state.IY
|
|
s.I = state.I
|
|
s.R = state.R
|
|
s.SP = state.SP
|
|
s.PC = state.PC
|
|
s.Flags = state.Flags
|
|
s.FlagsPrime = state.FlagsPrime
|
|
s.IMode = state.IMode
|
|
s.Iff1 = state.Iff1
|
|
s.Iff2 = state.Iff2
|
|
s.Halted = state.Halted
|
|
s.DoDelayedDI = state.DoDelayedDI
|
|
s.DoDelayedEI = state.DoDelayedEI
|
|
s.CycleCounter = state.CycleCounter
|
|
}
|
|
|
|
// New Create new
|
|
func New(memIoRW MemIoRW) *StateType {
|
|
return &StateType{
|
|
A: 0,
|
|
B: 0,
|
|
C: 0,
|
|
D: 0,
|
|
E: 0,
|
|
H: 0,
|
|
L: 0,
|
|
APrime: 0,
|
|
BPrime: 0,
|
|
CPrime: 0,
|
|
DPrime: 0,
|
|
EPrime: 0,
|
|
HPrime: 0,
|
|
IX: 0,
|
|
IY: 0,
|
|
I: 0,
|
|
|
|
R: 0,
|
|
SP: SpDefault,
|
|
PC: 0,
|
|
Flags: FlagsType{false, false, false, false, false, false, false, false},
|
|
FlagsPrime: FlagsType{false, false, false, false, false, false, false, false},
|
|
IMode: 0,
|
|
Iff1: 0,
|
|
Iff2: 0,
|
|
Halted: false,
|
|
DoDelayedDI: false,
|
|
DoDelayedEI: false,
|
|
CycleCounter: 0,
|
|
interruptOccurred: false,
|
|
core: memIoRW,
|
|
}
|
|
}
|
|
|
|
func (s StateType) RunInstruction() byte {
|
|
|
|
// R is incremented at the start of every instruction cycle,
|
|
// before the instruction actually runs.
|
|
// The high bit of R is not affected by this increment,
|
|
// it can only be changed using the LD R, A instruction.
|
|
// Note: also a HALT does increment the R register.
|
|
s.R = (s.R & 0x80) | (((s.R & 0x7f) + 1) & 0x7f)
|
|
|
|
if !s.Halted {
|
|
// If the previous instruction was a DI or an EI,
|
|
// we'll need to disable or enable interrupts
|
|
// after whatever instruction we're about to run is finished.
|
|
doingDelayedDi := false
|
|
doingDelayedEi := false
|
|
if s.DoDelayedDI {
|
|
s.DoDelayedDI = false
|
|
doingDelayedDi = true
|
|
} else if s.DoDelayedEI {
|
|
s.DoDelayedEI = false
|
|
doingDelayedEi = true
|
|
}
|
|
|
|
// Read the byte at the PC and run the instruction it encodes.
|
|
opcode := s.core.M1MemRead(s.PC)
|
|
s.decodeInstruction(opcode)
|
|
|
|
// HALT does not increase the PC
|
|
if !s.Halted {
|
|
s.PC++
|
|
}
|
|
|
|
// Actually do the delayed interrupt disable/enable if we have one.
|
|
if doingDelayedDi {
|
|
s.Iff1 = 0
|
|
s.Iff2 = 0
|
|
} else if doingDelayedEi {
|
|
s.Iff1 = 1
|
|
s.Iff2 = 1
|
|
}
|
|
|
|
// And finally clear out the cycle counter for the next instruction
|
|
// before returning it to the emulator core.
|
|
cycleCounter := s.CycleCounter
|
|
s.CycleCounter = 0
|
|
return cycleCounter
|
|
} else { // HALTED
|
|
// During HALT, NOPs are executed which is 4T
|
|
s.core.M1MemRead(s.PC) // HALT does a normal M1 fetch to keep the memory refresh active. The result is ignored (NOP).
|
|
return 4
|
|
}
|
|
}
|
|
|
|
// Simulates pulsing the processor's INT (or NMI) pin
|
|
//
|
|
// nonMaskable - true if this is a non-maskable interrupt
|
|
// data - the value to be placed on the data bus, if needed
|
|
func (s StateType) interrupt(nonMaskable bool, data byte) {
|
|
if nonMaskable {
|
|
// An interrupt, if halted, does increase the PC
|
|
if s.Halted {
|
|
s.PC++
|
|
}
|
|
|
|
// The high bit of R is not affected by this increment,
|
|
// it can only be changed using the LD R, A instruction.
|
|
s.R = (s.R & 0x80) | (((s.R & 0x7f) + 1) & 0x7f)
|
|
|
|
// Non-maskable interrupts are always handled the same way;
|
|
// clear IFF1 and then do a CALL 0x0066.
|
|
// Also, all interrupts reset the HALT state.
|
|
|
|
s.Halted = false
|
|
s.Iff2 = s.Iff1
|
|
s.Iff1 = 0
|
|
s.pushWord(s.PC)
|
|
s.PC = 0x66
|
|
|
|
s.CycleCounter += 11
|
|
} else if s.Iff1 != 0 {
|
|
// An interrupt, if halted, does increase the PC
|
|
if s.Halted {
|
|
s.PC++
|
|
}
|
|
|
|
// The high bit of R is not affected by this increment,
|
|
// it can only be changed using the LD R,A instruction.
|
|
s.R = (s.R & 0x80) | (((s.R & 0x7f) + 1) & 0x7f)
|
|
|
|
s.Halted = false
|
|
s.Iff1 = 0
|
|
s.Iff2 = 0
|
|
|
|
if s.IMode == 0 {
|
|
// In the 8080-compatible interrupt mode,
|
|
// decode the content of the data bus as an instruction and run it.
|
|
// it's probably a RST instruction, which pushes (PC+1) onto the stack
|
|
// so we should decrement PC before we decode the instruction
|
|
s.PC--
|
|
s.decodeInstruction(data)
|
|
s.PC++ // increment PC upon return
|
|
s.CycleCounter += 2
|
|
} else if s.IMode == 1 {
|
|
// Mode 1 is always just RST 0x38.
|
|
s.pushWord(s.PC)
|
|
s.PC = 0x0038
|
|
s.CycleCounter += 13
|
|
} else if s.IMode == 2 {
|
|
// Mode 2 uses the value on the data bus as in index
|
|
// into the vector table pointer to by the I register.
|
|
s.pushWord(s.PC)
|
|
|
|
// The Z80 manual says that this address must be 2-byte aligned,
|
|
// but it doesn't appear that this is actually the case on the hardware,
|
|
// so we don't attempt to enforce that here.
|
|
vectorAddress := (uint16(s.I) << 8) | uint16(data)
|
|
s.PC = uint16(s.core.MemRead(vectorAddress)) | (uint16(s.core.MemRead(vectorAddress+1)) << 8)
|
|
s.CycleCounter += 19
|
|
// A "notification" is generated so that the calling program can break on it.
|
|
s.interruptOccurred = true
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s StateType) pushWord(pc uint16) {
|
|
// TODO: Implement
|
|
panic("not yet implemented")
|
|
}
|
|
|
|
func (s StateType) getOperand(opcode byte) byte {
|
|
switch opcode & 0x07 {
|
|
case 0:
|
|
return s.B
|
|
case 1:
|
|
return s.C
|
|
case 2:
|
|
return s.D
|
|
case 3:
|
|
return s.E
|
|
case 4:
|
|
return s.H
|
|
case 5:
|
|
return s.L
|
|
case 6:
|
|
return s.core.MemRead(uint16(s.H)<<8 | uint16(s.L))
|
|
default:
|
|
return s.A
|
|
}
|
|
}
|
|
|
|
func (s StateType) decodeInstruction(opcode byte) {
|
|
// Handle HALT right up front, because it fouls up our LD decoding
|
|
// by falling where LD (HL), (HL) ought to be.
|
|
if opcode == OP_HALT {
|
|
s.Halted = true
|
|
} else if opcode >= OP_LD_B_B && opcode < OP_ADD_A_B {
|
|
// This entire range is all 8-bit register loads.
|
|
// Get the operand and assign it to the correct destination.
|
|
s.load8bit(opcode, s.getOperand(opcode))
|
|
} else if (opcode >= OP_ADD_A_B) && (opcode < OP_RET_NZ) {
|
|
// These are the 8-bit register ALU instructions.
|
|
// We'll get the operand and then use this "jump table"
|
|
// to call the correct utility function for the instruction.
|
|
s.alu8bit(opcode, s.getOperand(opcode))
|
|
} else {
|
|
// This is one of the less formulaic instructions;
|
|
// we'll get the specific function for it from our array.
|
|
s.otherInstructions(opcode)
|
|
}
|
|
s.CycleCounter += CYCLE_COUNTS[opcode]
|
|
}
|
|
|
|
func (s StateType) load8bit(opcode byte, operand byte) {
|
|
switch (opcode & 0x38) >> 3 {
|
|
case 0:
|
|
s.B = operand
|
|
case 1:
|
|
s.C = operand
|
|
case 2:
|
|
s.D = operand
|
|
case 3:
|
|
s.E = operand
|
|
case 4:
|
|
s.H = operand
|
|
case 5:
|
|
s.L = operand
|
|
case 6:
|
|
s.core.MemWrite(uint16(s.H)<<8|uint16(s.L), operand)
|
|
default:
|
|
s.A = operand
|
|
}
|
|
}
|
|
|
|
func (s StateType) alu8bit(opcode byte, operand byte) {
|
|
switch (opcode & 0x38) >> 3 {
|
|
case 0:
|
|
s.doAdd(operand)
|
|
case 1:
|
|
s.doAdc(operand)
|
|
case 2:
|
|
s.doSub(operand)
|
|
case 3:
|
|
s.doSbc(operand)
|
|
case 4:
|
|
s.doAnd(operand)
|
|
case 5:
|
|
s.doXor(operand)
|
|
case 6:
|
|
s.doOr(operand)
|
|
default:
|
|
s.doCp(operand)
|
|
}
|
|
}
|
|
|
|
func (s StateType) doAdd(operand byte) {
|
|
|
|
}
|
|
|
|
func (s StateType) doAdc(operand byte) {
|
|
|
|
}
|
|
|
|
func (s StateType) doSub(operand byte) {
|
|
|
|
}
|
|
|
|
func (s StateType) doSbc(operand byte) {
|
|
|
|
}
|
|
|
|
func (s StateType) doAnd(operand byte) {
|
|
|
|
}
|
|
|
|
func (s StateType) doXor(operand byte) {
|
|
|
|
}
|
|
|
|
func (s StateType) doOr(operand byte) {
|
|
|
|
}
|
|
|
|
func (s StateType) doCp(operand byte) {
|
|
|
|
}
|
|
|
|
func (s StateType) otherInstructions(opcode byte) {
|
|
|
|
}
|
|
|
|
// getFlagsRegister return whole F register
|
|
func (s StateType) getFlagsRegister() byte {
|
|
return getFlags(&s.Flags)
|
|
}
|
|
|
|
// getFlagsRegister return whole F' register
|
|
func (s StateType) getFlagsPrimeRegister() byte {
|
|
return getFlags(&s.FlagsPrime)
|
|
}
|
|
|
|
func getFlags(f *FlagsType) 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
|
|
|
|
}
|
|
|
|
func (s StateType) setFlagsRegister(flags byte) {
|
|
setFlags(flags, &s.Flags)
|
|
}
|
|
|
|
func (s StateType) setFlagsPrimeRegister(flags byte) {
|
|
setFlags(flags, &s.FlagsPrime)
|
|
}
|
|
|
|
func setFlags(flags byte, f *FlagsType) {
|
|
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
|
|
}
|
|
|
|
func (s StateType) updateXYFlags(result byte) {
|
|
// Most of the time, the undocumented flags
|
|
// (sometimes called X and Y, or 3 and 5),
|
|
// take their values from the corresponding bits
|
|
// of the result of the instruction,
|
|
// or from some other related value.
|
|
// This is a utility function to set those flags based on those bits.
|
|
s.Flags.Y = (result&0x20)>>5 != 0
|
|
s.Flags.X = (result&0x08)>>3 != 0
|
|
}
|
|
|
|
func getParity(value byte) bool {
|
|
return PARITY_BITS[value]
|
|
}
|
|
|
|
func (s StateType) PushWord(operand uint16) {
|
|
// Pretty obvious what this function does; given a 16-bit value,
|
|
// decrement the stack pointer, write the high byte to the new
|
|
// stack pointer location, then repeat for the low byte.
|
|
s.SP--
|
|
s.core.MemWrite(s.SP, byte((operand&0xff00)>>8))
|
|
s.SP--
|
|
s.core.MemWrite(s.SP, byte(operand&0x00ff))
|
|
}
|
|
|
|
func (s StateType) PopWord() uint16 {
|
|
// Again, not complicated; read a byte off the top of the stack,
|
|
// increment the stack pointer, rinse and repeat.
|
|
result := uint16(s.core.MemRead(s.SP))
|
|
s.SP++
|
|
result |= uint16(s.core.MemRead(s.SP)) << 8
|
|
s.SP++
|
|
return result
|
|
}
|