mirror of
https://github.com/ivanizag/izapple2.git
synced 2024-12-21 02:32:06 +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
|
package core6502
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
modeImplicit = iota + 1
|
modeImplicit = iota + 1
|
||||||
modeImplicitX
|
modeImplicitX
|
||||||
@ -16,6 +18,10 @@ const (
|
|||||||
modeIndirect
|
modeIndirect
|
||||||
modeIndexedIndirectX
|
modeIndexedIndirectX
|
||||||
modeIndirectIndexedY
|
modeIndirectIndexedY
|
||||||
|
// Added on the 65c02
|
||||||
|
modeIndirectZeroPage
|
||||||
|
modeAbsoluteIndexedIndirectX
|
||||||
|
modeZeroPageAndRelative
|
||||||
)
|
)
|
||||||
|
|
||||||
func getWordInLine(line []uint8) uint16 {
|
func getWordInLine(line []uint8) uint16 {
|
||||||
@ -84,6 +90,22 @@ func resolve(s *State, line []uint8, opcode opcode) (getValue func() uint8, setV
|
|||||||
case modeIndirectIndexedY:
|
case modeIndirectIndexedY:
|
||||||
address = getZeroPageWord(s.mem, line[1]) +
|
address = getZeroPageWord(s.mem, line[1]) +
|
||||||
uint16(s.reg.getY())
|
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 {
|
if hasAddress {
|
||||||
@ -101,3 +123,47 @@ func resolve(s *State, line []uint8, opcode opcode) (getValue func() uint8, setV
|
|||||||
}
|
}
|
||||||
return
|
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 {
|
if s.trace {
|
||||||
//fmt.Printf("%#04x %#02x\n", pc-opcode.bytes, opcodeID)
|
//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)
|
opcode.action(s, line, opcode)
|
||||||
s.cycles += uint64(opcode.cycles)
|
s.cycles += uint64(opcode.cycles)
|
||||||
@ -84,6 +84,16 @@ func (s *State) GetCycles() uint64 {
|
|||||||
return s.cycles
|
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)
|
// Save saves the CPU state (registers and cycle counter)
|
||||||
func (s *State) Save(w io.Writer) error {
|
func (s *State) Save(w io.Writer) error {
|
||||||
err := binary.Write(w, binary.BigEndian, s.cycles)
|
err := binary.Write(w, binary.BigEndian, s.cycles)
|
||||||
@ -109,50 +119,3 @@ func (s *State) Load(r io.Reader) error {
|
|||||||
}
|
}
|
||||||
return nil
|
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)
|
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},
|
0x40: opcode{"RTI", 1, 6, modeImplicit, opRTI},
|
||||||
0x60: opcode{"RTS", 1, 6, modeImplicit, opRTS},
|
0x60: opcode{"RTS", 1, 6, modeImplicit, opRTS},
|
||||||
|
|
||||||
0x48: opcode{"PHA", 1, 3, modeImplicit, opPHA},
|
0x48: opcode{"PHA", 1, 3, modeImplicit, buildOpPush(regA)},
|
||||||
0x08: opcode{"PHP", 1, 3, modeImplicit, opPHP},
|
0x08: opcode{"PHP", 1, 3, modeImplicit, buildOpPush(regP)},
|
||||||
0x68: opcode{"PLA", 1, 4, modeImplicit, opPLA},
|
0x68: opcode{"PLA", 1, 4, modeImplicit, buildOpPull(regA)},
|
||||||
0x28: opcode{"PLP", 1, 4, modeImplicit, opPLP},
|
0x28: opcode{"PLP", 1, 4, modeImplicit, buildOpPull(regP)},
|
||||||
|
|
||||||
0x09: opcode{"ORA", 2, 2, modeImmediate, buildOpLogic(operationOr)},
|
0x09: opcode{"ORA", 2, 2, modeImmediate, buildOpLogic(operationOr)},
|
||||||
0x05: opcode{"ORA", 2, 3, modeZeroPage, 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) {
|
return func(s *State, line []uint8, opcode opcode) {
|
||||||
if s.reg.getFlag(flag) == value {
|
if s.reg.getFlag(flag) == test {
|
||||||
// This assumes that PC is already pointing to the next instruction
|
address := resolveAddress(s, line, opcode)
|
||||||
pc := s.reg.getPC()
|
s.reg.setPC(address)
|
||||||
pc += uint16(int8(line[1]))
|
}
|
||||||
s.reg.setPC(pc)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
func opBIT(s *State, line []uint8, opcode opcode) {
|
||||||
value := resolveValue(s, line, opcode)
|
value := resolveValue(s, line, opcode)
|
||||||
acc := s.reg.getA()
|
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(flagZ, value&acc == 0)
|
||||||
s.reg.updateFlag(flagN, value&(1<<7) != 0)
|
// The immediate addressing mode (65C02 or 65816 only) does not affect N & V.
|
||||||
s.reg.updateFlag(flagV, value&(1<<6) != 0)
|
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 {
|
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)
|
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) {
|
func opSBC(s *State, line []uint8, opcode opcode) {
|
||||||
value := resolveValue(s, line, opcode)
|
value := resolveValue(s, line, opcode)
|
||||||
aValue := s.reg.getA()
|
aValue := s.reg.getA()
|
||||||
@ -168,6 +214,12 @@ func opSBC(s *State, line []uint8, opcode opcode) {
|
|||||||
s.reg.updateFlag(flagV, signedTotal < -128 || signedTotal > 127)
|
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
|
const stackAddress uint16 = 0x0100
|
||||||
|
|
||||||
func pushByte(s *State, value uint8) {
|
func pushByte(s *State, value uint8) {
|
||||||
@ -193,23 +245,24 @@ func pullWord(s *State) uint16 {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func opPLA(s *State, line []uint8, opcode opcode) {
|
func buildOpPull(regDst int) opFunc {
|
||||||
value := pullByte(s)
|
return func(s *State, line []uint8, opcode opcode) {
|
||||||
s.reg.setA(value)
|
value := pullByte(s)
|
||||||
s.reg.updateFlagZN(value)
|
s.reg.setRegister(regDst, value)
|
||||||
|
if regDst != regP {
|
||||||
|
s.reg.updateFlagZN(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func opPLP(s *State, line []uint8, opcode opcode) {
|
func buildOpPush(regSrc int) opFunc {
|
||||||
value := pullByte(s)
|
return func(s *State, line []uint8, opcode opcode) {
|
||||||
s.reg.setP(value)
|
value := s.reg.getRegister(regSrc)
|
||||||
}
|
if regSrc == regP {
|
||||||
|
value |= flagB + flag5
|
||||||
func opPHA(s *State, line []uint8, opcode opcode) {
|
}
|
||||||
pushByte(s, s.reg.getA())
|
pushByte(s, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func opPHP(s *State, line []uint8, opcode opcode) {
|
|
||||||
pushByte(s, s.reg.getP()|(flagB+flag5))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func opJMP(s *State, line []uint8, opcode opcode) {
|
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.setFlag(flagI)
|
||||||
s.reg.setPC(getWord(s.mem, vectorBreak))
|
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