Gopher2600/hardware/cpu/cpu.go
steve 35e31cccb7 o errors
- refined error package
2020-01-05 18:58:28 +00:00

1082 lines
25 KiB
Go

package cpu
// TODO List
// ---------
// . NMOS indexed addressing extra read when crossing page boundaries
import (
"fmt"
"gopher2600/errors"
"gopher2600/hardware/cpu/definitions"
"gopher2600/hardware/cpu/register"
"gopher2600/hardware/cpu/result"
"gopher2600/hardware/memory"
"log"
)
// CPU is the main container structure for the package
type CPU struct {
PC *register.Register
A *register.Register
X *register.Register
Y *register.Register
SP *register.Register
Status StatusRegister
mem memory.CPUBus
opCodes map[uint8]definitions.InstructionDefinition
// endCycle is called at the end of the imaginary CPU cycle. for example,
// reading a byte from memory takes one cycle and so the emulation will
// call endCycle() at that point. ExecuteInstruction() accepts an argument
// cycleCallback which is called by endCycle for additional functionality
//
// by definition: if it is undefined then no execution is currently being
// executed (see IsExecutingInstruction method)
endCycle func()
// controls whether cpu executes a cycle when it receives a clock tick (pin
// 3 of the 6507)
RdyFlg bool
// it is somtimes useful to ignore branching instructions and other
// side-effects. we use this in the disassembly package to make sure
// we reach every part of the program
NoSideEffects bool
// silently ignore addressing errors unless StrictAddressing is true
StrictAddressing bool
}
// NewCPU is the preferred method of initialisation for the CPU structure
func NewCPU(mem memory.CPUBus) (*CPU, error) {
var err error
mc := new(CPU)
mc.mem = mem
mc.PC = register.NewRegister(0, 16, "PC", "PC")
mc.A = register.NewRegister(0, 8, "A", "A")
mc.X = register.NewRegister(0, 8, "X", "X")
mc.Y = register.NewRegister(0, 8, "Y", "Y")
mc.SP = register.NewRegister(0, 8, "SP", "SP")
mc.Status = NewStatusRegister("Status", "SR")
mc.opCodes, err = definitions.GetInstructionDefinitions()
if err != nil {
return nil, err
}
mc.Reset()
return mc, nil
}
// MachineInfoTerse returns the cpu information in terse format
func (mc *CPU) MachineInfoTerse() string {
return fmt.Sprintf("%s %s %s %s %s %s", mc.PC.MachineInfoTerse(), mc.A.MachineInfoTerse(), mc.X.MachineInfoTerse(), mc.Y.MachineInfoTerse(), mc.SP.MachineInfoTerse(), mc.Status.MachineInfoTerse())
}
// MachineInfo returns the cpu information in verbose format
func (mc *CPU) MachineInfo() string {
return fmt.Sprintf("%v\n%v\n%v\n%v\n%v\n%v", mc.PC, mc.A, mc.X, mc.Y, mc.SP, mc.Status)
}
// map String to MachineInfo
func (mc *CPU) String() string {
return mc.MachineInfo()
}
// IsExecuting returns true if it is called during an ExecuteInstruction() callback
func (mc *CPU) IsExecuting() bool {
return mc.endCycle != nil
}
// Reset reinitialises all registers
func (mc *CPU) Reset() error {
// sanity check
if mc.IsExecuting() {
panic(fmt.Errorf("can't reset CPU in the middle of an instruction"))
}
mc.PC.Load(0)
mc.A.Load(0)
mc.X.Load(0)
mc.Y.Load(0)
mc.SP.Load(255)
mc.Status.reset()
mc.Status.Zero = mc.A.IsZero()
mc.Status.Sign = mc.A.IsNegative()
mc.Status.InterruptDisable = true
mc.Status.Break = true
mc.endCycle = nil
mc.RdyFlg = true
return nil
}
// LoadPC loads the contents of indirectAddress into the PC
func (mc *CPU) LoadPC(indirectAddress uint16) error {
// sanity check
if mc.IsExecuting() {
panic(fmt.Errorf("can't alter program counter in the middle of an instruction"))
}
// because we call this LoadPC() outside of the CPU's ExecuteInstruction()
// cycle we need to make sure endCycle() is in a valid state for the duration
// of the function
mc.endCycle = func() {}
defer func() {
mc.endCycle = nil
}()
val, err := mc.read16Bit(indirectAddress)
if err != nil {
return err
}
mc.PC.Load(val)
return nil
}
func (mc *CPU) write8Bit(address uint16, value uint8) error {
err := mc.mem.Write(address, value)
if err != nil {
switch err := err.(type) {
case errors.GopherError:
// don't worry about unwritable addresses (unless strict addressing
// is on)
if mc.StrictAddressing || err.Errno != errors.UnwritableAddress {
return err
}
default:
return err
}
}
return nil
}
func (mc *CPU) read8Bit(address uint16) (uint8, error) {
val, err := mc.mem.Read(address)
if err != nil {
switch err := err.(type) {
case errors.GopherError:
// don't worry about unreadable addresses (unless strict addressing
// is on)
if mc.StrictAddressing || err.Errno != errors.UnreadableAddress {
return 0, err
}
default:
return 0, err
}
}
mc.endCycle()
return val, nil
}
func (mc *CPU) read16Bit(address uint16) (uint16, error) {
lo, err := mc.mem.Read(address)
if err != nil {
return 0, err
}
mc.endCycle()
hi, err := mc.mem.Read(address + 1)
if err != nil {
return 0, err
}
mc.endCycle()
val := uint16(hi) << 8
val |= uint16(lo)
return val, nil
}
func (mc *CPU) read8BitPC() (uint8, error) {
op, err := mc.read8Bit(mc.PC.ToUint16())
if err != nil {
return 0, err
}
carry, _ := mc.PC.Add(1, false)
if carry {
return 0, errors.NewGopherError(errors.ProgramCounterCycled, nil)
}
return op, nil
}
func (mc *CPU) read16BitPC() (uint16, error) {
val, err := mc.read16Bit(mc.PC.ToUint16())
if err != nil {
return 0, err
}
// strictly, PC should be incremented by one after reading the lo byte of
// the next instruction but I don't believe this has any side-effects
carry, _ := mc.PC.Add(2, false)
if carry {
return 0, errors.NewGopherError(errors.ProgramCounterCycled, nil)
}
return val, nil
}
func (mc *CPU) branch(flag bool, address uint16, result *result.Instruction) error {
// return early if IgnoreBranching flag is turned on
if mc.NoSideEffects {
return nil
}
// in the case of branchng (relative addressing) we've read an 8bit value
// rather than a 16bit value to use as the "address". we do this kind of
// thing all over the place and it normally doesn't matter but because we'll
// sometimes be doing subtractions with this value we need to make sure the
// sign bit of the 8bit value has been propogated into the most-significant
// bits of the 16bit value.
if address&0x0080 == 0x0080 {
address |= 0xff00
}
if flag {
// phantom read
// +1 cycle
_, err := mc.read8Bit(mc.PC.ToUint16())
if err != nil {
return err
}
// note current PC for reference
oldPC := mc.PC.ToUint16()
// add LSB to PC
// this is a bit wierd but without implementing the PC differently (with
// two 8bit bytes perhaps) this is the only way I can see how to do it with
// the desired cycle accuracy:
// o Add full (sign extended) 16bit address to PC
// o note whether a page fault has occurred
// o restore the MSB of the PC using the MSB of the old PC value
mc.PC.Add(address, false)
result.PageFault = oldPC&0xff00 != mc.PC.ToUint16()&0xff00
mc.PC.Load(oldPC&0xff00 | mc.PC.ToUint16()&0x00ff)
// check to see whether branching has crossed a page
if result.PageFault {
// phantom read
// +1 cycle
_, err := mc.read8Bit(mc.PC.ToUint16())
if err != nil {
return err
}
// correct program counter
if address&0xff00 == 0xff00 {
mc.PC.Add(0xff00, false)
} else {
mc.PC.Add(0x0100, false)
}
// note that we've triggered a page fault
result.PageFault = true
}
}
return nil
}
// ExecuteInstruction steps CPU forward one instruction, calling
// cycleCallback() after every cycle
func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*result.Instruction, error) {
// sanity check
if mc.IsExecuting() {
panic(fmt.Errorf("can't call cpu.ExecuteInstruction() in the middle of another cpu.ExecuteInstruction()"))
}
// do nothing and return nothing if ready flag is false
if !mc.RdyFlg {
cycleCallback(nil)
return nil, nil
}
// prepare StepResult structure
result := new(result.Instruction)
result.Address = mc.PC.ToUint16()
// register end cycle callback
mc.endCycle = func() {
result.ActualCycles++
cycleCallback(result)
}
defer func() {
mc.endCycle = nil
}()
var err error
// read next instruction (end cycle part of read8BitPC)
// +1 cycle
operator, err := mc.read8BitPC()
if err != nil {
return nil, err
}
defn, found := mc.opCodes[operator]
if !found {
if operator == 0xff {
return nil, errors.NewGopherError(errors.NullInstruction, nil)
}
return nil, errors.NewGopherError(errors.UnimplementedInstruction, operator, mc.PC.ToUint16()-1)
}
result.Defn = defn
// address is the actual address to use to access memory (after any indexing
// has taken place)
var address uint16
// value is nil if addressing mode is implied and is read from the program for
// immediate/relative mode, and from non-program memory for all other modes
// note that for instructions which are read-modify-write, the value will
// change during execution and be used to write back to memory
var value uint8
// get address to use when reading/writing from/to memory (note that in the
// case of immediate addressing, we are actually getting the value to use in
// the instruction, not the address). we also take the opportuinity to set
// the InstructionData value for the StepResult and whether a page fault has
// occured
switch defn.AddressingMode {
case definitions.Implied:
// phantom read
// +1 cycle
_, err := mc.read8Bit(mc.PC.ToUint16())
if err != nil {
return nil, err
}
case definitions.Immediate:
// for immediate mode, the value is the next byte in the program
// therefore, we don't set the address and we read the value through the PC
// +1 cycle
value, err = mc.read8BitPC()
if err != nil {
return nil, err
}
result.InstructionData = value
case definitions.Relative:
// relative addressing is only used for branch instructions, the address
// is an offset value from the current PC position
// +1 cycle
value, err := mc.read8BitPC()
if err != nil {
return nil, err
}
result.InstructionData = value
address = uint16(value)
case definitions.Absolute:
if defn.Effect != definitions.Subroutine {
// +2 cycles
address, err = mc.read16BitPC()
if err != nil {
return nil, err
}
result.InstructionData = address
}
// else... for JSR, addresses are read slightly differently so we defer
// this part of the operation to the mnemonic switch below
case definitions.ZeroPage:
// +1 cycle
value, err := mc.read8BitPC()
if err != nil {
return nil, err
}
address = uint16(value)
result.InstructionData = address
case definitions.Indirect:
// indirect addressing (without indexing) is only used for the JMP command
// +2 cycles
indirectAddress, err := mc.read16BitPC()
if err != nil {
return nil, err
}
// implement NMOS 6502 Indirect JMP bug
if indirectAddress&0x00ff == 0x00ff {
lo, err := mc.mem.Read(indirectAddress)
if err != nil {
return nil, err
}
// +1 cycle
mc.endCycle()
hi, err := mc.mem.Read(indirectAddress & 0xff00)
if err != nil {
return nil, err
}
address = uint16(hi) << 8
address |= uint16(lo)
result.InstructionData = indirectAddress
result.Bug = fmt.Sprintf("Indirect JMP Bug")
// +1 cycle
mc.endCycle()
} else {
// normal, non-buggy behaviour
// +2 cycles
address, err = mc.read16Bit(indirectAddress)
if err != nil {
return nil, err
}
result.InstructionData = indirectAddress
}
case definitions.PreIndexedIndirect:
// +1 cycle
indirectAddress, err := mc.read8BitPC()
if err != nil {
return nil, err
}
// using 8bit addition because we don't want a page-fault
adder := register.NewAnonRegister(mc.X, 8)
adder.Add(indirectAddress, false)
// +1 cycle
mc.endCycle()
// +2 cycles
address, err = mc.read16Bit(adder.ToUint16())
if err != nil {
return nil, err
}
// never a page fault wth pre-index indirect addressing
result.InstructionData = indirectAddress
case definitions.PostIndexedIndirect:
// +1 cycle
indirectAddress, err := mc.read8BitPC()
if err != nil {
return nil, err
}
// +2 cycles
indexedAddress, err := mc.read16Bit(uint16(indirectAddress))
if err != nil {
return nil, err
}
adder := register.NewAnonRegister(mc.Y, 16)
adder.Add(indexedAddress&0x00ff, false)
address = adder.ToUint16()
// check for page fault
result.PageFault = defn.PageSensitive && (address&0xff00 == 0x0100)
if result.PageFault {
// phantom read
// +1 cycle
_, err := mc.read8Bit(address)
if err != nil {
return nil, err
}
result.ActualCycles++
}
adder.Add(indexedAddress&0xff00, false)
address = adder.ToUint16()
result.InstructionData = indirectAddress
case definitions.AbsoluteIndexedX:
// +2 cycles
indirectAddress, err := mc.read16BitPC()
if err != nil {
return nil, err
}
adder := register.NewAnonRegister(mc.X, 16)
// add index to LSB of address
adder.Add(indirectAddress&0x00ff, false)
address = adder.ToUint16()
// check for page fault
result.PageFault = defn.PageSensitive && (address&0xff00 == 0x0100)
if result.PageFault {
// phantom read
// +1 cycle
_, err := mc.read8Bit(address)
if err != nil {
return nil, err
}
}
adder.Add(indirectAddress&0xff00, false)
address = adder.ToUint16()
result.InstructionData = indirectAddress
case definitions.AbsoluteIndexedY:
// +2 cycles
indirectAddress, err := mc.read16BitPC()
if err != nil {
return nil, err
}
adder := register.NewAnonRegister(mc.Y, 16)
// add index to LSB of address
adder.Add(indirectAddress&0x00ff, false)
address = adder.ToUint16()
// check for page fault
result.PageFault = defn.PageSensitive && (address&0xff00 == 0x0100)
if result.PageFault {
// phantom read
// +1 cycle
_, err := mc.read8Bit(address)
if err != nil {
return nil, err
}
}
adder.Add(indirectAddress&0xff00, false)
address = adder.ToUint16()
result.InstructionData = indirectAddress
case definitions.IndexedZeroPageX:
// +1 cycles
indirectAddress, err := mc.read8BitPC()
if err != nil {
return nil, err
}
adder := register.NewAnonRegister(indirectAddress, 8)
adder.Add(mc.X, false)
address = adder.ToUint16()
result.InstructionData = indirectAddress
// +1 cycle
mc.endCycle()
case definitions.IndexedZeroPageY:
// used exclusively for LDX ZeroPage,y
// +1 cycles
indirectAddress, err := mc.read8BitPC()
if err != nil {
return nil, err
}
adder := register.NewAnonRegister(indirectAddress, 8)
adder.Add(mc.Y, false)
address = adder.ToUint16()
result.InstructionData = indirectAddress
// +1 cycle
mc.endCycle()
default:
log.Fatalf("unknown addressing mode for %s", defn.Mnemonic)
}
// read value from memory using address found in AddressingMode switch above only when:
// a) addressing mode is not 'implied' or 'immediate'
// - for immediate modes, we already have the value in lieu of an address
// - for implied modes, we don't need a value
// b) instruction is 'Read' OR 'ReadWrite'
// - for write modes, we only use the address to write a value we already have
// - for flow modes, the use of the address is very specific
if !(defn.AddressingMode == definitions.Implied || defn.AddressingMode == definitions.Immediate) {
if defn.Effect == definitions.Read {
// +1 cycle
value, err = mc.read8Bit(address)
if err != nil {
return nil, err
}
} else if defn.Effect == definitions.RMW {
// +1 cycle
value, err = mc.read8Bit(address)
if err != nil {
return nil, err
}
// phantom write
// +1 cycle
if !mc.NoSideEffects {
err = mc.write8Bit(address, value)
if err != nil {
return nil, err
}
}
mc.endCycle()
}
}
// actually perform instruction based on mnemonic group
switch defn.Mnemonic {
case "NOP":
// does nothing
case "CLI":
mc.Status.InterruptDisable = false
case "SEI":
mc.Status.InterruptDisable = true
case "CLC":
mc.Status.Carry = false
case "SEC":
mc.Status.Carry = true
case "CLD":
mc.Status.DecimalMode = false
case "SED":
mc.Status.DecimalMode = true
case "CLV":
mc.Status.Overflow = false
case "PHA":
if !mc.NoSideEffects {
err = mc.write8Bit(mc.SP.ToUint16(), mc.A.ToUint8())
if err != nil {
return nil, err
}
}
mc.SP.Add(255, false)
case "PLA":
// +1 cycle
mc.SP.Add(1, false)
mc.endCycle()
// +1 cycle
value, err = mc.read8Bit(mc.SP.ToUint16())
if err != nil {
return nil, err
}
mc.A.Load(value)
case "PHP":
if !mc.NoSideEffects {
err = mc.write8Bit(mc.SP.ToUint16(), mc.Status.ToUint8())
if err != nil {
return nil, err
}
}
mc.SP.Add(255, false)
case "PLP":
// +1 cycle
mc.SP.Add(1, false)
mc.endCycle()
// +1 cycle
value, err = mc.read8Bit(mc.SP.ToUint16())
if err != nil {
return nil, err
}
mc.Status.FromUint8(value)
case "TXA":
mc.A.Load(mc.X)
mc.Status.Zero = mc.A.IsZero()
mc.Status.Sign = mc.A.IsNegative()
case "TAX":
mc.X.Load(mc.A)
mc.Status.Zero = mc.X.IsZero()
mc.Status.Sign = mc.X.IsNegative()
case "TAY":
mc.Y.Load(mc.A)
mc.Status.Zero = mc.Y.IsZero()
mc.Status.Sign = mc.Y.IsNegative()
case "TYA":
mc.A.Load(mc.Y)
mc.Status.Zero = mc.A.IsZero()
mc.Status.Sign = mc.A.IsNegative()
case "TSX":
mc.X.Load(mc.SP)
mc.Status.Zero = mc.X.IsZero()
mc.Status.Sign = mc.X.IsNegative()
case "TXS":
mc.SP.Load(mc.X)
// does not affect status register
case "EOR":
mc.A.EOR(value)
mc.Status.Zero = mc.A.IsZero()
mc.Status.Sign = mc.A.IsNegative()
case "ORA":
mc.A.ORA(value)
mc.Status.Zero = mc.A.IsZero()
mc.Status.Sign = mc.A.IsNegative()
case "AND":
mc.A.AND(value)
mc.Status.Zero = mc.A.IsZero()
mc.Status.Sign = mc.A.IsNegative()
case "LDA":
mc.A.Load(value)
mc.Status.Zero = mc.A.IsZero()
mc.Status.Sign = mc.A.IsNegative()
case "LDX":
mc.X.Load(value)
mc.Status.Zero = mc.X.IsZero()
mc.Status.Sign = mc.X.IsNegative()
case "LDY":
mc.Y.Load(value)
mc.Status.Zero = mc.Y.IsZero()
mc.Status.Sign = mc.Y.IsNegative()
case "STA":
if !mc.NoSideEffects {
err = mc.write8Bit(address, mc.A.ToUint8())
if err != nil {
return nil, err
}
}
case "STX":
if !mc.NoSideEffects {
err = mc.write8Bit(address, mc.X.ToUint8())
if err != nil {
return nil, err
}
}
case "STY":
if !mc.NoSideEffects {
err = mc.write8Bit(address, mc.Y.ToUint8())
if err != nil {
return nil, err
}
}
case "INX":
mc.X.Add(1, false)
mc.Status.Zero = mc.X.IsZero()
mc.Status.Sign = mc.X.IsNegative()
case "INY":
mc.Y.Add(1, false)
mc.Status.Zero = mc.Y.IsZero()
mc.Status.Sign = mc.Y.IsNegative()
case "DEX":
mc.X.Add(255, false)
mc.Status.Zero = mc.X.IsZero()
mc.Status.Sign = mc.X.IsNegative()
case "DEY":
mc.Y.Add(255, false)
mc.Status.Zero = mc.Y.IsZero()
mc.Status.Sign = mc.Y.IsNegative()
case "ASL":
var r *register.Register
if defn.Effect == definitions.RMW {
r = register.NewAnonRegister(value, mc.A.Size())
} else {
r = mc.A
}
mc.Status.Carry = r.ASL()
mc.Status.Zero = r.IsZero()
mc.Status.Sign = r.IsNegative()
value = r.ToUint8()
case "LSR":
var r *register.Register
if defn.Effect == definitions.RMW {
r = register.NewAnonRegister(value, mc.A.Size())
} else {
r = mc.A
}
mc.Status.Carry = r.LSR()
mc.Status.Zero = r.IsZero()
mc.Status.Sign = r.IsNegative()
value = r.ToUint8()
case "ADC":
if mc.Status.DecimalMode {
mc.Status.Carry = mc.A.AddDecimal(value, mc.Status.Carry)
// decimal mode doesn't affect overflow flag (yet?)
} else {
mc.Status.Carry, mc.Status.Overflow = mc.A.Add(value, mc.Status.Carry)
}
mc.Status.Zero = mc.A.IsZero()
mc.Status.Sign = mc.A.IsNegative()
case "SBC":
if mc.Status.DecimalMode {
mc.Status.Carry = mc.A.SubtractDecimal(value, mc.Status.Carry)
// decimal mode doesn't affect overflow flag (yet?)
} else {
mc.Status.Carry, mc.Status.Overflow = mc.A.Subtract(value, mc.Status.Carry)
}
mc.Status.Zero = mc.A.IsZero()
mc.Status.Sign = mc.A.IsNegative()
case "ROR":
var r *register.Register
if defn.Effect == definitions.RMW {
r = register.NewAnonRegister(value, mc.A.Size())
} else {
r = mc.A
}
mc.Status.Carry = r.ROR(mc.Status.Carry)
mc.Status.Zero = r.IsZero()
mc.Status.Sign = r.IsNegative()
value = r.ToUint8()
case "ROL":
var r *register.Register
if defn.Effect == definitions.RMW {
r = register.NewAnonRegister(value, mc.A.Size())
} else {
r = mc.A
}
mc.Status.Carry = r.ROL(mc.Status.Carry)
mc.Status.Zero = r.IsZero()
mc.Status.Sign = r.IsNegative()
value = r.ToUint8()
case "INC":
r := register.NewAnonRegister(value, 8)
r.Add(1, false)
mc.Status.Zero = r.IsZero()
mc.Status.Sign = r.IsNegative()
value = r.ToUint8()
case "DEC":
r := register.NewAnonRegister(value, 8)
r.Add(255, false)
mc.Status.Zero = r.IsZero()
mc.Status.Sign = r.IsNegative()
value = r.ToUint8()
case "CMP":
cmp := register.NewAnonRegister(mc.A, mc.A.Size())
// maybe surprisingly, CMP can be implemented with binary subtract even
// if decimal mode is active (the meaning is the same)
mc.Status.Carry, _ = cmp.Subtract(value, true)
mc.Status.Zero = cmp.IsZero()
mc.Status.Sign = cmp.IsNegative()
case "CPX":
cmp := register.NewAnonRegister(mc.X, mc.X.Size())
mc.Status.Carry, _ = cmp.Subtract(value, true)
mc.Status.Zero = cmp.IsZero()
mc.Status.Sign = cmp.IsNegative()
case "CPY":
cmp := register.NewAnonRegister(mc.Y, mc.Y.Size())
mc.Status.Carry, _ = cmp.Subtract(value, true)
mc.Status.Zero = cmp.IsZero()
mc.Status.Sign = cmp.IsNegative()
case "BIT":
cmp := register.NewAnonRegister(value, mc.A.Size())
mc.Status.Sign = cmp.IsNegative()
mc.Status.Overflow = cmp.IsBitV()
cmp.AND(mc.A)
mc.Status.Zero = cmp.IsZero()
case "JMP":
if !mc.NoSideEffects {
mc.PC.Load(address)
}
case "BCC":
err := mc.branch(!mc.Status.Carry, address, result)
if err != nil {
return nil, err
}
case "BCS":
err := mc.branch(mc.Status.Carry, address, result)
if err != nil {
return nil, err
}
case "BEQ":
err := mc.branch(mc.Status.Zero, address, result)
if err != nil {
return nil, err
}
case "BMI":
err := mc.branch(mc.Status.Sign, address, result)
if err != nil {
return nil, err
}
case "BNE":
err := mc.branch(!mc.Status.Zero, address, result)
if err != nil {
return nil, err
}
case "BPL":
err := mc.branch(!mc.Status.Sign, address, result)
if err != nil {
return nil, err
}
case "BVC":
err := mc.branch(!mc.Status.Overflow, address, result)
if err != nil {
return nil, err
}
case "BVS":
err := mc.branch(mc.Status.Overflow, address, result)
if err != nil {
return nil, err
}
case "JSR":
// +1 cycle
lsb, err := mc.read8BitPC()
if err != nil {
return nil, err
}
// the current value of the PC is now correct, even though we've only read
// one byte of the address so far. remember, RTS increments the PC when
// read from the stack, meaning that the PC will be correct at that point
// with that in mind, we're not sure what this extra cycle is for
// +1 cycle
mc.endCycle()
if !mc.NoSideEffects {
// push MSB of PC onto stack, and decrement SP
// +1 cycle
err = mc.write8Bit(mc.SP.ToUint16(), uint8((mc.PC.ToUint16()&0xFF00)>>8))
if err != nil {
return nil, err
}
}
mc.SP.Add(255, false)
mc.endCycle()
if !mc.NoSideEffects {
// push LSB of PC onto stack, and decrement SP
// +1 cycle
err = mc.write8Bit(mc.SP.ToUint16(), uint8(mc.PC.ToUint16()&0x00FF))
if err != nil {
return nil, err
}
}
mc.SP.Add(255, false)
mc.endCycle()
// perform jump
msb, err := mc.read8BitPC()
if err != nil {
return nil, err
}
address = (uint16(msb) << 8) | uint16(lsb)
if !mc.NoSideEffects {
mc.PC.Load(address)
}
// store address in theInstructionData field of result
//
// we would normally do this in the addressing mode switch above. however,
// JSR uses absolute addressing and we deliberately do nothing in that
// switch for 'sub-routine' commands
result.InstructionData = address
case "RTS":
if !mc.NoSideEffects {
// +1 cycle
mc.SP.Add(1, false)
mc.endCycle()
// +2 cycles
rtsAddress, err := mc.read16Bit(mc.SP.ToUint16())
if err != nil {
return nil, err
}
mc.SP.Add(1, false)
// load and correct PC
mc.PC.Load(rtsAddress)
mc.PC.Add(1, false)
// +1 cycle
mc.endCycle()
}
case "BRK":
// TODO: implement BRK
case "RTI":
// TODO: implement RTI
// undocumented instructions
case "dop":
// does nothing
case "lax":
mc.A.Load(value)
mc.Status.Zero = mc.A.IsZero()
mc.Status.Sign = mc.A.IsNegative()
mc.X.Load(value)
default:
// this should never, ever happen
log.Fatalf("WTF! unknown mnemonic! (%s)", defn.Mnemonic)
}
// write altered value back to memory for RMW instructions
if defn.Effect == definitions.RMW {
if !mc.NoSideEffects {
err = mc.write8Bit(address, value)
if err != nil {
return nil, err
}
}
// +1 cycle
mc.endCycle()
}
// consume an extra cycle for the extra memory access for Write instructions
if defn.Effect == definitions.Write {
// +1 cycle
mc.endCycle()
}
// finalise result
result.Final = true
return result, nil
}