mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2025-04-02 11:02:17 -04:00
1082 lines
25 KiB
Go
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
|
|
}
|