Gopher2600/hardware/cpu/cpu.go
steve eb17bc1f63 o registers
- replaced both register implementations with just one that uses
    32 bit integers as the underlying implementation. very smart and
    very quick by comparison to the bit array implementation.
2020-01-05 18:58:26 +00:00

981 lines
22 KiB
Go

package cpu
// TODO List
// ---------
// . NMOS indexed addressing extra read when crossing page boundaries
// . Binary Decimal Mode
import (
"fmt"
"headlessVCS/hardware/cpu/definitions"
"headlessVCS/hardware/cpu/register"
"headlessVCS/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
memory memory.CPUBus
opCodes map[uint8]definitions.InstructionDefinition
// endCycle is a closure that contains details of the current instruction
// if it is undefined then no execution is currently being executed
// (see IsExecutingInstruction method)
endCycle func()
}
// NewCPU is the preferred method of initialisation for the CPU structure
func NewCPU(memory memory.CPUBus) *CPU {
var err error
mc := new(CPU)
mc.memory = memory
mc.PC, err = register.NewRegister(0, 16, "PC")
if err != nil {
return nil
}
mc.A, err = register.NewRegister(0, 8, "A")
if err != nil {
log.Fatalln(err)
}
mc.X, err = register.NewRegister(0, 8, "X")
if err != nil {
return nil
}
mc.Y, err = register.NewRegister(0, 8, "Y")
if err != nil {
return nil
}
mc.SP, err = register.NewRegister(0, 8, "SP")
if err != nil {
return nil
}
mc.Status = NewStatusRegister("ST")
mc.opCodes, err = definitions.GetInstructionDefinitions()
if err != nil {
return nil
}
mc.Reset()
return mc
}
func (mc *CPU) String() string {
return fmt.Sprintf("%v\n%v\n%v\n%v\n%v\n%v\n", mc.PC, mc.A, mc.X, mc.Y, mc.SP, mc.Status)
}
// 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() {
// sanity check
if mc.IsExecuting() {
panic(fmt.Errorf("can't call cpu.Reset() in the middle of cpu.ExecuteInstruction()"))
}
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
}
// 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 call cpu.LoadPC() in the middle of cpu.ExecuteInstruction()"))
}
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) read8Bit(address uint16) (uint8, error) {
val, err := mc.memory.Read(address)
if err != nil {
}
mc.endCycle()
return val, nil
}
func (mc *CPU) read16Bit(address uint16) (uint16, error) {
lo, err := mc.memory.Read(address)
if err != nil {
return 0, err
}
mc.endCycle()
hi, err := mc.memory.Read(address + 1)
if err != nil {
return 0, err
}
mc.endCycle()
var val uint16
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
}
mc.PC.Add(1, false)
return op, nil
}
func (mc *CPU) read16BitPC() (uint16, error) {
val, err := mc.read16Bit(mc.PC.ToUint16())
if err != nil {
return 0, err
}
mc.PC.Add(2, false)
return val, nil
}
func (mc *CPU) branch(flag bool, address uint16, result *InstructionResult) error {
// 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 == true {
// 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. note that the CPU will panic if a CPU
// method is called during a callback.
func (mc *CPU) ExecuteInstruction(cycleCallback func()) (*InstructionResult, error) {
// sanity check
if mc.IsExecuting() {
panic(fmt.Errorf("calling ExecuteInstruction() before previous call to this function has completed"))
}
// prepare StepResult structure
result := new(InstructionResult)
result.ProgramCounter = mc.PC.ToUint16()
// register end cycle callback
mc.endCycle = func() {
result.ActualCycles++
cycleCallback()
}
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 {
return nil, fmt.Errorf("unimplemented instruction (0x%x)", operator)
}
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
}
address = uint16(value)
result.InstructionData = address
case definitions.Absolute:
if defn.Effect != definitions.Subroutine {
// +2 cycles
address, err = mc.read16BitPC()
if err != nil {
return nil, err
}
result.InstructionData = address
}
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.memory.Read(indirectAddress)
if err != nil {
return nil, err
}
// +1 cycle
mc.endCycle()
hi, err := mc.memory.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, err := register.NewRegister(mc.X, 8, "")
if err != nil {
return nil, err
}
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, err := register.NewRegister(mc.Y, 16, "")
if err != nil {
return nil, err
}
adder.Add(indexedAddress&0x00ff, false)
address = adder.ToUint16()
// check for page fault
result.PageFault = defn.PageSensitive && (address&0xff00 != indexedAddress&0xff00)
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, err := register.NewRegister(mc.X, 16, "")
if err != nil {
return nil, err
}
// add index to LSB of address
adder.Add(indirectAddress&0x00ff, false)
address = adder.ToUint16()
// check for page fault
result.PageFault = defn.PageSensitive && (address&0xff00 != indirectAddress&0xff00)
if result.PageFault {
// phantom read
// +1 cycle
_, err := mc.read8Bit(address)
if err != nil {
return nil, err
}
result.ActualCycles++
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, err := register.NewRegister(mc.Y, 16, "")
if err != nil {
return nil, err
}
// add index to LSB of address
adder.Add(indirectAddress&0x00ff, false)
address = adder.ToUint16()
// check for page fault
result.PageFault = defn.PageSensitive && (address&0xFF00 != indirectAddress&0xFF00)
if result.PageFault {
// phantom read
// +1 cycle
_, err := mc.read8Bit(address)
if err != nil {
return nil, err
}
result.ActualCycles++
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, err := register.NewRegister(indirectAddress, 8, "")
if err != nil {
return nil, err
}
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, err := register.NewRegister(indirectAddress, 8, "")
if err != nil {
return nil, err
}
adder.Add(mc.Y, false)
address = adder.ToUint16()
result.InstructionData = indirectAddress
// +1 cycle
mc.endCycle()
default:
log.Printf("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
err = mc.memory.Write(address, value)
if err != nil {
return nil, err
}
mc.endCycle()
}
}
// actually perform instruction based on mnemonic
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":
err = mc.memory.Write(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":
err = mc.memory.Write(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":
err = mc.memory.Write(address, mc.A.ToUint8())
if err != nil {
return nil, err
}
case "STX":
err = mc.memory.Write(address, mc.X.ToUint8())
if err != nil {
return nil, err
}
case "STY":
err = mc.memory.Write(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":
mc.Status.Carry = mc.A.ASL()
mc.Status.Zero = mc.A.IsZero()
mc.Status.Sign = mc.A.IsNegative()
if defn.Effect == definitions.RMW {
value = mc.A.ToUint8()
}
case "LSR":
mc.Status.Carry = mc.A.LSR()
mc.Status.Zero = mc.A.IsZero()
mc.Status.Sign = mc.A.IsNegative()
if defn.Effect == definitions.RMW {
value = mc.A.ToUint8()
}
case "ADC":
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":
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":
mc.Status.Carry = mc.A.ROR(mc.Status.Carry)
mc.Status.Zero = mc.A.IsZero()
mc.Status.Sign = mc.A.IsNegative()
if defn.Effect == definitions.RMW {
value = mc.A.ToUint8()
}
case "ROL":
mc.Status.Carry = mc.A.ROL(mc.Status.Carry)
mc.Status.Zero = mc.A.IsZero()
mc.Status.Sign = mc.A.IsNegative()
if defn.Effect == definitions.RMW {
value = mc.A.ToUint8()
}
case "INC":
r, err := register.NewRegister(value, 8, "")
if err != nil {
return nil, err
}
r.Add(1, false)
mc.Status.Zero = mc.A.IsZero()
mc.Status.Sign = mc.A.IsNegative()
value = r.ToUint8()
case "DEC":
r, err := register.NewRegister(value, 8, "")
if err != nil {
return nil, err
}
r.Add(255, false)
mc.Status.Zero = mc.A.IsZero()
mc.Status.Sign = mc.A.IsNegative()
value = r.ToUint8()
case "CMP":
cmp, err := register.NewRegister(mc.A, mc.A.Size(), "")
if err != nil {
return nil, err
}
mc.Status.Carry, _ = cmp.Subtract(value, true)
mc.Status.Zero = cmp.IsZero()
mc.Status.Sign = cmp.IsNegative()
case "CPX":
cmp, err := register.NewRegister(mc.X, mc.X.Size(), "")
if err != nil {
return nil, err
}
mc.Status.Carry, _ = cmp.Subtract(value, true)
mc.Status.Zero = cmp.IsZero()
mc.Status.Sign = cmp.IsNegative()
case "CPY":
cmp, err := register.NewRegister(mc.Y, mc.Y.Size(), "")
if err != nil {
return nil, err
}
mc.Status.Carry, _ = cmp.Subtract(value, true)
mc.Status.Zero = cmp.IsZero()
mc.Status.Sign = cmp.IsNegative()
case "BIT":
cmp, err := register.NewRegister(mc.A, mc.A.Size(), "")
if err != nil {
return nil, err
}
cmp.AND(value)
mc.Status.Zero = cmp.IsZero()
mc.Status.Sign = cmp.IsNegative()
mc.Status.Overflow = cmp.IsBitV()
case "JMP":
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()
// push MSB of PC onto stack, and decrement SP
// +1 cycle
err = mc.memory.Write(mc.SP.ToUint16(), uint8((mc.PC.ToUint16()&0xFF00)>>8))
if err != nil {
return nil, err
}
mc.SP.Add(255, false)
mc.endCycle()
// push LSB of PC onto stack, and decrement SP
// +1 cycle
err = mc.memory.Write(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)
mc.PC.Load(address)
// we would normally store the InstructionData in the addressing mode
// switch but JSR bypasses all that so we'll do it here
result.InstructionData = address
case "RTS":
// +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
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 {
err = mc.memory.Write(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
}