mirror of
https://github.com/ivanizag/izapple2.git
synced 2024-10-07 12:55:59 +00:00
Emulation of the 65c02 processor variant.
This commit is contained in:
parent
f92bc2b08e
commit
df41114043
@ -1,33 +0,0 @@
|
||||
package core6502
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFunctional(t *testing.T) {
|
||||
m := new(FlatMemory)
|
||||
s := NewNMOS6502(m)
|
||||
|
||||
// Test suite from https://github.com/Klaus2m5/6502_65C02_functional_tests
|
||||
m.loadBinary("testdata/6502_functional_test.bin")
|
||||
|
||||
s.reg.setPC(0x0400)
|
||||
for true {
|
||||
testCase := s.mem.Peek(0x0200)
|
||||
if testCase >= 240 {
|
||||
break
|
||||
}
|
||||
log := testCase > 43
|
||||
if log {
|
||||
fmt.Printf("[ %d ] ", testCase)
|
||||
}
|
||||
s.SetTrace(log)
|
||||
pc := s.reg.getPC()
|
||||
s.ExecuteInstruction()
|
||||
if pc == s.reg.getPC() {
|
||||
t.Errorf("Failure in test %v.", testCase)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
package core6502
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
modeImplicit = iota + 1
|
||||
modeImplicitX
|
||||
@ -16,6 +18,10 @@ const (
|
||||
modeIndirect
|
||||
modeIndexedIndirectX
|
||||
modeIndirectIndexedY
|
||||
// Added on the 65c02
|
||||
modeIndirectZeroPage
|
||||
modeAbsoluteIndexedIndirectX
|
||||
modeZeroPageAndRelative
|
||||
)
|
||||
|
||||
func getWordInLine(line []uint8) uint16 {
|
||||
@ -84,6 +90,22 @@ func resolve(s *State, line []uint8, opcode opcode) (getValue func() uint8, setV
|
||||
case modeIndirectIndexedY:
|
||||
address = getZeroPageWord(s.mem, line[1]) +
|
||||
uint16(s.reg.getY())
|
||||
// 65c02 additions
|
||||
case modeIndirectZeroPage:
|
||||
address = getZeroPageWord(s.mem, line[1])
|
||||
case modeAbsoluteIndexedIndirectX:
|
||||
addressAddress := getWordInLine(line) + uint16(s.reg.getX())
|
||||
address = getWord(s.mem, addressAddress)
|
||||
case modeRelative:
|
||||
// This assumes that PC is already pointing to the next instruction
|
||||
address = s.reg.getPC() + uint16(int8(line[1])) // Note: line[1] is signed
|
||||
case modeZeroPageAndRelative:
|
||||
// Two addressing modes combined. We refer to the second one, relative,
|
||||
// placed one byte after the zeropage reference
|
||||
address = s.reg.getPC() + uint16(int8(line[2])) // Note: line[2] is signed
|
||||
|
||||
default:
|
||||
panic("Assert failed. Missing addressing mode")
|
||||
}
|
||||
|
||||
if hasAddress {
|
||||
@ -101,3 +123,47 @@ func resolve(s *State, line []uint8, opcode opcode) (getValue func() uint8, setV
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func lineString(line []uint8, opcode opcode) string {
|
||||
t := opcode.name
|
||||
switch opcode.addressMode {
|
||||
case modeImplicit:
|
||||
case modeImplicitX:
|
||||
case modeImplicitY:
|
||||
//Nothing
|
||||
case modeAccumulator:
|
||||
t += fmt.Sprintf(" A")
|
||||
case modeImmediate:
|
||||
t += fmt.Sprintf(" #%02x", line[1])
|
||||
case modeZeroPage:
|
||||
t += fmt.Sprintf(" $%02x", line[1])
|
||||
case modeZeroPageX:
|
||||
t += fmt.Sprintf(" $%02x,X", line[1])
|
||||
case modeZeroPageY:
|
||||
t += fmt.Sprintf(" $%02x,Y", line[1])
|
||||
case modeRelative:
|
||||
t += fmt.Sprintf(" *%+x", int8(line[1]))
|
||||
case modeAbsolute:
|
||||
t += fmt.Sprintf(" $%04x", getWordInLine(line))
|
||||
case modeAbsoluteX:
|
||||
t += fmt.Sprintf(" $%04x,X", getWordInLine(line))
|
||||
case modeAbsoluteY:
|
||||
t += fmt.Sprintf(" $%04x,Y", getWordInLine(line))
|
||||
case modeIndirect:
|
||||
t += fmt.Sprintf(" ($%04x)", getWordInLine(line))
|
||||
case modeIndexedIndirectX:
|
||||
t += fmt.Sprintf(" ($%02x,X)", line[1])
|
||||
case modeIndirectIndexedY:
|
||||
t += fmt.Sprintf(" ($%02x),Y", line[1])
|
||||
// 65c02 additions:
|
||||
case modeIndirectZeroPage:
|
||||
t += fmt.Sprintf(" ($%02x)", line[1])
|
||||
case modeAbsoluteIndexedIndirectX:
|
||||
t += fmt.Sprintf(" ($%04x,X)", getWordInLine(line))
|
||||
case modeZeroPageAndRelative:
|
||||
t += fmt.Sprintf(" $%02x *%+x", line[1], int8(line[2]))
|
||||
default:
|
||||
t += "UNKNOWN MODE"
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
165
core6502/cmos65c02.go
Normal file
165
core6502/cmos65c02.go
Normal file
@ -0,0 +1,165 @@
|
||||
package core6502
|
||||
|
||||
/*
|
||||
For the diffrences with NMOS6502 see:
|
||||
http://6502.org/tutorials/65c02opcodes.html
|
||||
http://wilsonminesco.com/NMOS-CMOSdif/
|
||||
http://www.obelisk.me.uk/65C02/reference.html
|
||||
http://www.obelisk.me.uk/65C02/addressing.html
|
||||
http://anyplatform.net/media/guides/cpus/65xx%20Processor%20Data.txt
|
||||
*/
|
||||
|
||||
// NewCMOS65c02 returns an initialized 65c02
|
||||
func NewCMOS65c02(m Memory) *State {
|
||||
var s State
|
||||
s.mem = m
|
||||
|
||||
var opcodes [256]opcode
|
||||
for i := 0; i < 256; i++ {
|
||||
opcodes[i] = opcodesNMOS6502[i]
|
||||
if opcodes65c02Delta[i].cycles != 0 {
|
||||
opcodes[i] = opcodes65c02Delta[i]
|
||||
}
|
||||
}
|
||||
add65c02NOPs(&opcodes)
|
||||
s.opcodes = &opcodes
|
||||
return &s
|
||||
}
|
||||
|
||||
func add65c02NOPs(opcodes *[256]opcode) {
|
||||
nop11 := opcode{"NOP", 1, 1, modeImplicit, opNOP}
|
||||
nop22 := opcode{"NOP", 2, 2, modeImmediate, opNOP}
|
||||
nop23 := opcode{"NOP", 2, 3, modeImmediate, opNOP}
|
||||
nop24 := opcode{"NOP", 2, 4, modeImmediate, opNOP}
|
||||
nop34 := opcode{"NOP", 3, 4, modeAbsolute, opNOP}
|
||||
nop38 := opcode{"NOP", 3, 8, modeAbsolute, opNOP}
|
||||
|
||||
opcodes[0x02] = nop22
|
||||
opcodes[0x22] = nop22
|
||||
opcodes[0x42] = nop22
|
||||
opcodes[0x62] = nop22
|
||||
opcodes[0x82] = nop22
|
||||
opcodes[0xc2] = nop22
|
||||
opcodes[0xe2] = nop22
|
||||
|
||||
opcodes[0x44] = nop23
|
||||
opcodes[0x54] = nop24
|
||||
opcodes[0xD4] = nop24
|
||||
opcodes[0xF4] = nop24
|
||||
|
||||
opcodes[0x5c] = nop38
|
||||
opcodes[0xdc] = nop34
|
||||
opcodes[0xfc] = nop34
|
||||
|
||||
for i := 0; i < 0x100; i = i + 0x10 {
|
||||
opcodes[i+0x03] = nop11
|
||||
// RMB and SMB; opcodes[i+0x07] = nop11
|
||||
opcodes[i+0x0b] = nop11
|
||||
// BBR and BBS: opcodes[i+0x0f] = nop11
|
||||
}
|
||||
}
|
||||
|
||||
var opcodes65c02Delta = [256]opcode{
|
||||
// Functional difference
|
||||
0x00: opcode{"BRK", 1, 7, modeImplicit, opBRKAlt},
|
||||
0x24: opcode{"BIT", 2, 3, modeZeroPage, opBIT},
|
||||
0x2C: opcode{"BIT", 3, 3, modeAbsolute, opBIT},
|
||||
|
||||
// Fixed BCD arithmetic flags
|
||||
0x69: opcode{"ADC", 2, 2, modeImmediate, opADCAlt},
|
||||
0x65: opcode{"ADC", 2, 3, modeZeroPage, opADCAlt},
|
||||
0x75: opcode{"ADC", 2, 4, modeZeroPageX, opADCAlt},
|
||||
0x6D: opcode{"ADC", 3, 4, modeAbsolute, opADCAlt},
|
||||
0x7D: opcode{"ADC", 3, 4, modeAbsoluteX, opADCAlt},
|
||||
0x79: opcode{"ADC", 3, 4, modeAbsoluteY, opADCAlt},
|
||||
0x61: opcode{"ADC", 2, 6, modeIndexedIndirectX, opADCAlt},
|
||||
0x71: opcode{"ADC", 2, 5, modeIndirectIndexedY, opADCAlt},
|
||||
0xE9: opcode{"SBC", 2, 2, modeImmediate, opSBCAlt},
|
||||
0xE5: opcode{"SBC", 2, 3, modeZeroPage, opSBCAlt},
|
||||
0xF5: opcode{"SBC", 2, 4, modeZeroPageX, opSBCAlt},
|
||||
0xED: opcode{"SBC", 3, 4, modeAbsolute, opSBCAlt},
|
||||
0xFD: opcode{"SBC", 3, 4, modeAbsoluteX, opSBCAlt},
|
||||
0xF9: opcode{"SBC", 3, 4, modeAbsoluteY, opSBCAlt},
|
||||
0xE1: opcode{"SBC", 2, 6, modeIndexedIndirectX, opSBCAlt},
|
||||
0xF1: opcode{"SBC", 2, 5, modeIndirectIndexedY, opSBCAlt},
|
||||
|
||||
// Different cycle count
|
||||
0x1e: opcode{"ASL", 3, 6, modeAbsoluteX, buildOpShift(true, false)},
|
||||
0x3e: opcode{"ROL", 3, 6, modeAbsoluteX, buildOpShift(true, true)},
|
||||
0x5e: opcode{"LSR", 3, 6, modeAbsoluteX, buildOpShift(false, false)},
|
||||
0x7e: opcode{"ROR", 3, 6, modeAbsoluteX, buildOpShift(false, true)},
|
||||
|
||||
// New indirect zero page addresssing mode
|
||||
0x12: opcode{"ORA", 2, 5, modeIndirectZeroPage, buildOpLogic(operationOr)},
|
||||
0x32: opcode{"AND", 2, 5, modeIndirectZeroPage, buildOpLogic(operationAnd)},
|
||||
0x52: opcode{"EOR", 2, 5, modeIndirectZeroPage, buildOpLogic(operationXor)},
|
||||
0x72: opcode{"ADC", 2, 5, modeIndirectZeroPage, opADCAlt},
|
||||
0x92: opcode{"STA", 2, 5, modeIndirectZeroPage, buildOpStore(regA)},
|
||||
0xb2: opcode{"LDA", 2, 5, modeIndirectZeroPage, buildOpLoad(regA)},
|
||||
0xd2: opcode{"CMP", 2, 5, modeIndirectZeroPage, buildOpCompare(regA)},
|
||||
0xf2: opcode{"SBC", 2, 5, modeIndirectZeroPage, opSBCAlt},
|
||||
|
||||
// New addressing options
|
||||
0x89: opcode{"BIT", 2, 2, modeImmediate, opBIT},
|
||||
0x34: opcode{"BIT", 2, 4, modeZeroPageX, opBIT},
|
||||
0x3c: opcode{"BIT", 3, 4, modeAbsoluteX, opBIT},
|
||||
0x1a: opcode{"INC", 1, 2, modeAccumulator, buildOpIncDec(true)},
|
||||
0x3a: opcode{"DEC", 1, 2, modeAccumulator, buildOpIncDec(false)},
|
||||
0x7c: opcode{"JMP", 3, 6, modeAbsoluteIndexedIndirectX, opJMP},
|
||||
|
||||
// Additional instructions: BRA, PHX, PHY, PLX, PLY, STZ, TRB, TSB
|
||||
0xda: opcode{"PHX", 1, 3, modeImplicit, buildOpPush(regX)},
|
||||
0x5a: opcode{"PHY", 1, 3, modeImplicit, buildOpPush(regY)},
|
||||
0xfa: opcode{"PLX", 1, 4, modeImplicit, buildOpPull(regX)},
|
||||
0x7a: opcode{"PLY", 1, 4, modeImplicit, buildOpPull(regY)},
|
||||
0x80: opcode{"BRA", 2, 4, modeRelative, opJMP},
|
||||
|
||||
0x64: opcode{"STZ", 2, 3, modeZeroPage, opSTZ},
|
||||
0x74: opcode{"STZ", 2, 4, modeZeroPageX, opSTZ},
|
||||
0x9c: opcode{"STZ", 3, 4, modeAbsolute, opSTZ},
|
||||
0x9e: opcode{"STZ", 3, 5, modeAbsoluteX, opSTZ},
|
||||
|
||||
0x14: opcode{"TRB", 2, 5, modeZeroPage, opTRB},
|
||||
0x1c: opcode{"TRB", 3, 6, modeAbsolute, opTRB},
|
||||
|
||||
0x04: opcode{"TSB", 2, 5, modeZeroPage, opTSB},
|
||||
0x0c: opcode{"TSB", 3, 6, modeAbsolute, opTSB},
|
||||
|
||||
// Additional in Rockwell 65c02 and WDC 65c02?
|
||||
// They have a double addressing mode: zeropage and relative.
|
||||
0x0f: opcode{"BBR0", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(0, false)},
|
||||
0x1f: opcode{"BBR1", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(1, false)},
|
||||
0x2f: opcode{"BBR2", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(2, false)},
|
||||
0x3f: opcode{"BBR3", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(3, false)},
|
||||
0x4f: opcode{"BBR4", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(4, false)},
|
||||
0x5f: opcode{"BBR5", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(5, false)},
|
||||
0x6f: opcode{"BBR6", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(6, false)},
|
||||
0x7f: opcode{"BBR7", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(7, false)},
|
||||
0x8f: opcode{"BBS0", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(0, true)},
|
||||
0x9f: opcode{"BBS1", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(1, true)},
|
||||
0xaf: opcode{"BBS2", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(2, true)},
|
||||
0xbf: opcode{"BBS3", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(3, true)},
|
||||
0xcf: opcode{"BBS4", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(4, true)},
|
||||
0xdf: opcode{"BBS5", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(5, true)},
|
||||
0xef: opcode{"BBS6", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(6, true)},
|
||||
0xff: opcode{"BBS7", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(7, true)},
|
||||
|
||||
0x07: opcode{"RMB0", 2, 5, modeZeroPage, buildOpSetBit(0, false)},
|
||||
0x17: opcode{"RMB1", 2, 5, modeZeroPage, buildOpSetBit(1, false)},
|
||||
0x27: opcode{"RMB2", 2, 5, modeZeroPage, buildOpSetBit(2, false)},
|
||||
0x37: opcode{"RMB3", 2, 5, modeZeroPage, buildOpSetBit(3, false)},
|
||||
0x47: opcode{"RMB4", 2, 5, modeZeroPage, buildOpSetBit(4, false)},
|
||||
0x57: opcode{"RMB5", 2, 5, modeZeroPage, buildOpSetBit(5, false)},
|
||||
0x67: opcode{"RMB6", 2, 5, modeZeroPage, buildOpSetBit(6, false)},
|
||||
0x77: opcode{"RMB7", 2, 5, modeZeroPage, buildOpSetBit(7, false)},
|
||||
0x87: opcode{"SMB0", 2, 5, modeZeroPage, buildOpSetBit(0, true)},
|
||||
0x97: opcode{"SMB1", 2, 5, modeZeroPage, buildOpSetBit(1, true)},
|
||||
0xa7: opcode{"SMB2", 2, 5, modeZeroPage, buildOpSetBit(2, true)},
|
||||
0xb7: opcode{"SMB3", 2, 5, modeZeroPage, buildOpSetBit(3, true)},
|
||||
0xc7: opcode{"SMB4", 2, 5, modeZeroPage, buildOpSetBit(4, true)},
|
||||
0xd7: opcode{"SMB5", 2, 5, modeZeroPage, buildOpSetBit(5, true)},
|
||||
0xe7: opcode{"SMB6", 2, 5, modeZeroPage, buildOpSetBit(6, true)},
|
||||
0xf7: opcode{"SMB7", 2, 5, modeZeroPage, buildOpSetBit(7, true)},
|
||||
|
||||
// Maybe additional Rockwell: STP, WAI
|
||||
}
|
32
core6502/cmos65c02_test.go
Normal file
32
core6502/cmos65c02_test.go
Normal file
@ -0,0 +1,32 @@
|
||||
package core6502
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCMOs65c02NoUndocumented(t *testing.T) {
|
||||
m := new(FlatMemory)
|
||||
s := NewCMOS65c02(m)
|
||||
|
||||
for i := 0; i < 256; i++ {
|
||||
if s.opcodes[i].cycles == 0 {
|
||||
t.Errorf("Opcode missing for $%02x.", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCMOS65c02asNMOS(t *testing.T) {
|
||||
m := new(FlatMemory)
|
||||
s := NewCMOS65c02(m)
|
||||
|
||||
m.loadBinary("testdata/6502_functional_test.bin")
|
||||
executeSuite(t, s, 0x200, 240, false, 255)
|
||||
}
|
||||
|
||||
func TestCMOS65c02(t *testing.T) {
|
||||
m := new(FlatMemory)
|
||||
s := NewCMOS65c02(m)
|
||||
|
||||
m.loadBinary("testdata/65C02_extended_opcodes_test.bin")
|
||||
executeSuite(t, s, 0x202, 240, false, 255)
|
||||
}
|
@ -63,7 +63,7 @@ func (s *State) ExecuteInstruction() {
|
||||
|
||||
if s.trace {
|
||||
//fmt.Printf("%#04x %#02x\n", pc-opcode.bytes, opcodeID)
|
||||
fmt.Printf("%#04x %-12s: ", pc-opcode.bytes, lineString(line, opcode))
|
||||
fmt.Printf("%#04x %-13s: ", pc-opcode.bytes, lineString(line, opcode))
|
||||
}
|
||||
opcode.action(s, line, opcode)
|
||||
s.cycles += uint64(opcode.cycles)
|
||||
@ -84,6 +84,16 @@ func (s *State) GetCycles() uint64 {
|
||||
return s.cycles
|
||||
}
|
||||
|
||||
// SetTrace activates tracing of the cpu execution
|
||||
func (s *State) SetTrace(trace bool) {
|
||||
s.trace = trace
|
||||
}
|
||||
|
||||
// GetTrace gets trhe tracing state of the cpu execution
|
||||
func (s *State) GetTrace() bool {
|
||||
return s.trace
|
||||
}
|
||||
|
||||
// Save saves the CPU state (registers and cycle counter)
|
||||
func (s *State) Save(w io.Writer) error {
|
||||
err := binary.Write(w, binary.BigEndian, s.cycles)
|
||||
@ -109,50 +119,3 @@ func (s *State) Load(r io.Reader) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func lineString(line []uint8, opcode opcode) string {
|
||||
t := opcode.name
|
||||
switch opcode.addressMode {
|
||||
case modeImplicit:
|
||||
case modeImplicitX:
|
||||
case modeImplicitY:
|
||||
//Nothing
|
||||
case modeAccumulator:
|
||||
t += fmt.Sprintf(" A")
|
||||
case modeImmediate:
|
||||
t += fmt.Sprintf(" #%02x", line[1])
|
||||
case modeZeroPage:
|
||||
t += fmt.Sprintf(" $%02x", line[1])
|
||||
case modeZeroPageX:
|
||||
t += fmt.Sprintf(" $%02x,X", line[1])
|
||||
case modeZeroPageY:
|
||||
t += fmt.Sprintf(" $%02x,Y", line[1])
|
||||
case modeRelative:
|
||||
t += fmt.Sprintf(" *%+x", int8(line[1]))
|
||||
case modeAbsolute:
|
||||
t += fmt.Sprintf(" $%04x", getWordInLine(line))
|
||||
case modeAbsoluteX:
|
||||
t += fmt.Sprintf(" $%04x,X", getWordInLine(line))
|
||||
case modeAbsoluteY:
|
||||
t += fmt.Sprintf(" $%04x,Y", getWordInLine(line))
|
||||
case modeIndirect:
|
||||
t += fmt.Sprintf(" ($%04x)", getWordInLine(line))
|
||||
case modeIndexedIndirectX:
|
||||
t += fmt.Sprintf(" ($%02x,X)", line[1])
|
||||
case modeIndirectIndexedY:
|
||||
t += fmt.Sprintf(" ($%02x),Y", line[1])
|
||||
default:
|
||||
t += "UNKNOWN MODE"
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// SetTrace activates tracing of the cpu execution
|
||||
func (s *State) SetTrace(trace bool) {
|
||||
s.trace = trace
|
||||
}
|
||||
|
||||
// GetTrace gets trhe tracing state of the cpu execution
|
||||
func (s *State) GetTrace() bool {
|
||||
return s.trace
|
||||
}
|
||||
|
@ -483,5 +483,3 @@ func TestStack(t *testing.T) {
|
||||
t.Errorf("Error in PLP, %v", s.reg)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Tests for BRK, JMP, JSR, RTI, RTS
|
||||
|
@ -16,10 +16,10 @@ var opcodesNMOS6502 = [256]opcode{
|
||||
0x40: opcode{"RTI", 1, 6, modeImplicit, opRTI},
|
||||
0x60: opcode{"RTS", 1, 6, modeImplicit, opRTS},
|
||||
|
||||
0x48: opcode{"PHA", 1, 3, modeImplicit, opPHA},
|
||||
0x08: opcode{"PHP", 1, 3, modeImplicit, opPHP},
|
||||
0x68: opcode{"PLA", 1, 4, modeImplicit, opPLA},
|
||||
0x28: opcode{"PLP", 1, 4, modeImplicit, opPLP},
|
||||
0x48: opcode{"PHA", 1, 3, modeImplicit, buildOpPush(regA)},
|
||||
0x08: opcode{"PHP", 1, 3, modeImplicit, buildOpPush(regP)},
|
||||
0x68: opcode{"PLA", 1, 4, modeImplicit, buildOpPull(regA)},
|
||||
0x28: opcode{"PLP", 1, 4, modeImplicit, buildOpPull(regP)},
|
||||
|
||||
0x09: opcode{"ORA", 2, 2, modeImmediate, buildOpLogic(operationOr)},
|
||||
0x05: opcode{"ORA", 2, 3, modeZeroPage, buildOpLogic(operationOr)},
|
||||
|
38
core6502/nmos6502_test.go
Normal file
38
core6502/nmos6502_test.go
Normal file
@ -0,0 +1,38 @@
|
||||
package core6502
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNMOS6502(t *testing.T) {
|
||||
m := new(FlatMemory)
|
||||
s := NewNMOS6502(m)
|
||||
|
||||
m.loadBinary("testdata/6502_functional_test.bin")
|
||||
executeSuite(t, s, 0x200, 240, false, 255)
|
||||
}
|
||||
|
||||
// To execute test suites from https://github.com/Klaus2m5/6502_65C02_functional_tests
|
||||
func executeSuite(t *testing.T, s *State, stepAddress uint16, steps uint8, showStep bool, traceCPUStep uint8) {
|
||||
s.reg.setPC(0x0400)
|
||||
currentStep := uint8(255)
|
||||
for true {
|
||||
testCase := s.mem.Peek(stepAddress)
|
||||
if testCase != currentStep {
|
||||
currentStep = testCase
|
||||
if showStep {
|
||||
fmt.Printf("[ Step %d ]\n", testCase)
|
||||
}
|
||||
s.SetTrace(testCase == traceCPUStep)
|
||||
}
|
||||
if testCase >= steps {
|
||||
break
|
||||
}
|
||||
pc := s.reg.getPC()
|
||||
s.ExecuteInstruction()
|
||||
if pc == s.reg.getPC() {
|
||||
t.Fatalf("Failure in test %v.", testCase)
|
||||
}
|
||||
}
|
||||
}
|
@ -70,13 +70,37 @@ func buildOpUpdateFlag(flag uint8, value bool) opFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func buildOpBranch(flag uint8, value bool) opFunc {
|
||||
func buildOpBranch(flag uint8, test bool) opFunc {
|
||||
return func(s *State, line []uint8, opcode opcode) {
|
||||
if s.reg.getFlag(flag) == value {
|
||||
// This assumes that PC is already pointing to the next instruction
|
||||
pc := s.reg.getPC()
|
||||
pc += uint16(int8(line[1]))
|
||||
s.reg.setPC(pc)
|
||||
if s.reg.getFlag(flag) == test {
|
||||
address := resolveAddress(s, line, opcode)
|
||||
s.reg.setPC(address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func buildOpBranchOnBit(bit uint8, test bool) opFunc {
|
||||
return func(s *State, line []uint8, opcode opcode) {
|
||||
// Note that those operations have two addressing modes:
|
||||
// one for the zero page value, another for the relative jump.
|
||||
// We will have to resolve the first one here.
|
||||
value := s.mem.Peek(uint16(line[1]))
|
||||
bitValue := ((value >> bit) & 1) == 1
|
||||
|
||||
if bitValue == test {
|
||||
address := resolveAddress(s, line, opcode)
|
||||
s.reg.setPC(address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func buildOpSetBit(bit uint8, set bool) opFunc {
|
||||
return func(s *State, line []uint8, opcode opcode) {
|
||||
value, setValue := resolveGetSetValue(s, line, opcode)
|
||||
if set {
|
||||
setValue(value | (1 << bit))
|
||||
} else {
|
||||
setValue(value &^ (1 << bit))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -84,10 +108,26 @@ func buildOpBranch(flag uint8, value bool) opFunc {
|
||||
func opBIT(s *State, line []uint8, opcode opcode) {
|
||||
value := resolveValue(s, line, opcode)
|
||||
acc := s.reg.getA()
|
||||
// Future note: The immediate addressing mode (65C02 or 65816 only) does not affect V.
|
||||
s.reg.updateFlag(flagZ, value&acc == 0)
|
||||
s.reg.updateFlag(flagN, value&(1<<7) != 0)
|
||||
s.reg.updateFlag(flagV, value&(1<<6) != 0)
|
||||
// The immediate addressing mode (65C02 or 65816 only) does not affect N & V.
|
||||
if opcode.addressMode != modeImmediate {
|
||||
s.reg.updateFlag(flagN, value&(1<<7) != 0)
|
||||
s.reg.updateFlag(flagV, value&(1<<6) != 0)
|
||||
}
|
||||
}
|
||||
|
||||
func opTRB(s *State, line []uint8, opcode opcode) {
|
||||
value, setValue := resolveGetSetValue(s, line, opcode)
|
||||
a := s.reg.getA()
|
||||
s.reg.updateFlag(flagZ, (value&a) == 0)
|
||||
setValue(value &^ a)
|
||||
}
|
||||
|
||||
func opTSB(s *State, line []uint8, opcode opcode) {
|
||||
value, setValue := resolveGetSetValue(s, line, opcode)
|
||||
a := s.reg.getA()
|
||||
s.reg.updateFlag(flagZ, (value&a) == 0)
|
||||
setValue(value | a)
|
||||
}
|
||||
|
||||
func buildOpCompare(reg int) opFunc {
|
||||
@ -140,6 +180,12 @@ func opADC(s *State, line []uint8, opcode opcode) {
|
||||
s.reg.updateFlag(flagV, signedTotal < -128 || signedTotal > 127)
|
||||
}
|
||||
|
||||
func opADCAlt(s *State, line []uint8, opcode opcode) {
|
||||
opADC(s, line, opcode)
|
||||
// The Z and N flags on BCD are fixed in 65c02.
|
||||
s.reg.updateFlagZN(s.reg.getA())
|
||||
}
|
||||
|
||||
func opSBC(s *State, line []uint8, opcode opcode) {
|
||||
value := resolveValue(s, line, opcode)
|
||||
aValue := s.reg.getA()
|
||||
@ -168,6 +214,12 @@ func opSBC(s *State, line []uint8, opcode opcode) {
|
||||
s.reg.updateFlag(flagV, signedTotal < -128 || signedTotal > 127)
|
||||
}
|
||||
|
||||
func opSBCAlt(s *State, line []uint8, opcode opcode) {
|
||||
opSBC(s, line, opcode)
|
||||
// The Z and N flags on BCD are fixed in 65c02.
|
||||
s.reg.updateFlagZN(s.reg.getA())
|
||||
}
|
||||
|
||||
const stackAddress uint16 = 0x0100
|
||||
|
||||
func pushByte(s *State, value uint8) {
|
||||
@ -193,23 +245,24 @@ func pullWord(s *State) uint16 {
|
||||
|
||||
}
|
||||
|
||||
func opPLA(s *State, line []uint8, opcode opcode) {
|
||||
value := pullByte(s)
|
||||
s.reg.setA(value)
|
||||
s.reg.updateFlagZN(value)
|
||||
func buildOpPull(regDst int) opFunc {
|
||||
return func(s *State, line []uint8, opcode opcode) {
|
||||
value := pullByte(s)
|
||||
s.reg.setRegister(regDst, value)
|
||||
if regDst != regP {
|
||||
s.reg.updateFlagZN(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func opPLP(s *State, line []uint8, opcode opcode) {
|
||||
value := pullByte(s)
|
||||
s.reg.setP(value)
|
||||
}
|
||||
|
||||
func opPHA(s *State, line []uint8, opcode opcode) {
|
||||
pushByte(s, s.reg.getA())
|
||||
}
|
||||
|
||||
func opPHP(s *State, line []uint8, opcode opcode) {
|
||||
pushByte(s, s.reg.getP()|(flagB+flag5))
|
||||
func buildOpPush(regSrc int) opFunc {
|
||||
return func(s *State, line []uint8, opcode opcode) {
|
||||
value := s.reg.getRegister(regSrc)
|
||||
if regSrc == regP {
|
||||
value |= flagB + flag5
|
||||
}
|
||||
pushByte(s, value)
|
||||
}
|
||||
}
|
||||
|
||||
func opJMP(s *State, line []uint8, opcode opcode) {
|
||||
@ -240,3 +293,18 @@ func opBRK(s *State, line []uint8, opcode opcode) {
|
||||
s.reg.setFlag(flagI)
|
||||
s.reg.setPC(getWord(s.mem, vectorBreak))
|
||||
}
|
||||
|
||||
func opBRKAlt(s *State, line []uint8, opcode opcode) {
|
||||
opBRK(s, line, opcode)
|
||||
/*
|
||||
The only difference in the BRK instruction on the 65C02 and the 6502
|
||||
is that the 65C02 clears the D (decimal) flag on the 65C02, whereas
|
||||
the D flag is not affected on the 6502.
|
||||
*/
|
||||
s.reg.clearFlag(flagD)
|
||||
}
|
||||
|
||||
func opSTZ(s *State, line []uint8, opcode opcode) {
|
||||
setValue := resolveSetValue(s, line, opcode)
|
||||
setValue(0)
|
||||
}
|
||||
|
BIN
core6502/testdata/65C02_extended_opcodes_test.bin
vendored
Normal file
BIN
core6502/testdata/65C02_extended_opcodes_test.bin
vendored
Normal file
Binary file not shown.
11510
core6502/testdata/65C02_extended_opcodes_test.lst
vendored
Normal file
11510
core6502/testdata/65C02_extended_opcodes_test.lst
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user