Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix implementation of BCD mode quirks on 6502 and 65c02 #56

Draft
wants to merge 20 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
7fec386
Test harness for executing Klaus Dormann's 6502-based test suites, see
KrisKennaway May 10, 2017
33b48ff
Merge branch 'master' of https://github.com/mnaberez/py65
May 10, 2017
5e7ade9
Add support for optionally tracing execution
KrisKennaway May 11, 2017
32896cc
Support tracing execution for particular PC values. Use this to
KrisKennaway May 11, 2017
666cd9c
Use the decimally adjusted aluresult to compute the value of flags.
KrisKennaway May 11, 2017
4f52c95
Add functional test cases that exhaustively test BCD mode on 6502 and
KrisKennaway Jul 22, 2017
9b5e1f3
Move two BCD test cases from the common 65x02 test cases to
KrisKennaway Jul 22, 2017
430fec1
Merge remote-tracking branch 'upstream/master'
KrisKennaway Aug 20, 2019
fab1174
Revert "Add support for optionally tracing execution"
KrisKennaway Aug 20, 2019
8b9cf7d
Revert "Revert "Add support for optionally tracing execution""
KrisKennaway Aug 20, 2019
254b2fb
Test cases pass except for devices/65C02_extended_opcodes_test_modifi…
KrisKennaway Aug 20, 2019
0e7066a
Clean up code, tests still equally working (but still not yet the
KrisKennaway Aug 20, 2019
e0f56e4
Switch to cc65 versions of these sources, courtesy of github user
KrisKennaway Aug 20, 2019
3d30544
Clean up binary test execution framework
KrisKennaway Aug 20, 2019
3468cdc
Simplify and clean up
KrisKennaway Aug 20, 2019
803632c
Add instructions on how to assemble the binary test cases
KrisKennaway Aug 20, 2019
7d5a29c
Oh actually the remaining test case did pass, I just had the wrong
KrisKennaway Aug 20, 2019
5b28c00
More cleanup to functional tests. Automatically skip the Klaus
KrisKennaway Aug 20, 2019
d6ccff2
Rename to decimal_flags_use_adjusted_results
KrisKennaway Aug 20, 2019
d4459da
Tweak wording
KrisKennaway Aug 20, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 103 additions & 49 deletions py65/devices/mpu6502.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import sys

from py65.utils.conversions import itoa
from py65.utils.devices import make_instruction_decorator

from py65 import disassembler

class MPU:
# vectors
Expand Down Expand Up @@ -41,6 +43,8 @@ def __init__(self, memory=None, pc=0x0000):
self.memory = memory
self.start_pc = pc

self.disassembler = disassembler.Disassembler(self)

# init
self.reset()

Expand All @@ -55,8 +59,12 @@ def __repr__(self):
return self.reprformat() % (indent, self.name, self.pc, self.a,
self.x, self.y, self.sp, flags)

def step(self):
def step(self, trace=False):
instructCode = self.memory[self.pc]
if trace:
out = str(self) + " $%04X: %s" % (
self.pc, self.disassembler.instruction_at(self.pc)[1])
print >> sys.stderr, "\n".join(out.split('\n'))
self.pc = (self.pc + 1) & self.addrMask
self.excycles = 0
self.addcycles = self.extracycles[instructCode]
Expand Down Expand Up @@ -291,6 +299,9 @@ def opEOR(self, x):
self.FlagsNZ(self.a)

def opADC(self, x):
return self._opADC(x, decimal_flags_use_adjusted_result=False)

def _opADC(self, x, decimal_flags_use_adjusted_result):
data = self.ByteAt(x())

if self.p & self.DECIMAL:
Expand All @@ -307,24 +318,45 @@ def opADC(self, x):
adjust1 = 6
decimalcarry = 1

# the ALU outputs are not decimally adjusted
# The ALU outputs are not yet decimally adjusted
nibble0 = nibble0 & 0xf
nibble1 = nibble1 & 0xf
aluresult = (nibble1 << 4) + nibble0

# the final A contents will be decimally adjusted
# Partial result with only low nibble decimally adjusted
nibble0 = (nibble0 + adjust0) & 0xf
halfadjresult = (nibble1 << 4) + nibble0

# the final A contents has both nibbles decimally adjusted
nibble1 = (nibble1 + adjust1) & 0xf
adjresult = (nibble1 << 4) + nibble0

self.p &= ~(self.CARRY | self.OVERFLOW | self.NEGATIVE | self.ZERO)
if aluresult == 0:

if decimal_flags_use_adjusted_result: # 65C02 and 65816
# Z and N use adjusted (i.e. decimal) result
zerores = adjresult
negativeres = adjresult
else: # 6502
# Z uses unadjusted (i.e. binary) ALU result
zerores = aluresult
# N effectively uses ALU result with only low nibble
# decimally adjusted - but see here for what is really going on
# https://atariage.com/forums/topic/163876-flags-on-decimal-mode-on-the-nmos-6502/
negativeres = halfadjresult

if zerores == 0:
self.p |= self.ZERO
else:
self.p |= aluresult & self.NEGATIVE
self.p |= negativeres & self.NEGATIVE

if decimalcarry == 1:
self.p |= self.CARRY

if (~(self.a ^ data) & (self.a ^ aluresult)) & self.NEGATIVE:
self.p |= self.OVERFLOW
self.a = (nibble1 << 4) + nibble0

self.a = adjresult
else:
if self.p & self.CARRY:
tmp = 1
Expand Down Expand Up @@ -387,56 +419,78 @@ def opCMPR(self, get_address, register_value):
self.p |= (register_value - tbyte) & self.NEGATIVE

def opSBC(self, x):
data = self.ByteAt(x())
self._opSBC(x, decimal_flags_use_adjusted_result=False)

def _opSBC(self, x, decimal_flags_use_adjusted_result):
if self.p & self.DECIMAL:
halfcarry = 1
decimalcarry = 0
adjust0 = 0
adjust1 = 0
self._opSBCDecimal(x, decimal_flags_use_adjusted_result)
return

nibble0 = (self.a & 0xf) + (~data & 0xf) + (self.p & self.CARRY)
if nibble0 <= 0xf:
halfcarry = 0
adjust0 = 10
nibble1 = ((self.a >> 4) & 0xf) + ((~data >> 4) & 0xf) + halfcarry
if nibble1 <= 0xf:
adjust1 = 10 << 4
data = self.ByteAt(x())

# the ALU outputs are not decimally adjusted
aluresult = self.a + (~data & self.byteMask) + \
(self.p & self.CARRY)
result = self.a + (~data & self.byteMask) + (self.p & self.CARRY)
self.p &= ~(self.CARRY | self.ZERO | self.OVERFLOW | self.NEGATIVE)
if ((self.a ^ data) & (self.a ^ result)) & self.NEGATIVE:
self.p |= self.OVERFLOW
data = result & self.byteMask
if data == 0:
self.p |= self.ZERO
if result > self.byteMask:
self.p |= self.CARRY
self.p |= data & self.NEGATIVE
self.a = data

if aluresult > self.byteMask:
decimalcarry = 1
aluresult &= self.byteMask
def _opSBCDecimal(self, x, flags_use_adjusted_result=False):
"""SBC opcode in BCD mode.

# but the final result will be adjusted
nibble0 = (aluresult + adjust0) & 0xf
nibble1 = ((aluresult + adjust1) >> 4) & 0xf
See e.g. http://6502.org/tutorials/decimal_mode.html#A for details
"""
data = self.ByteAt(x())

self.p &= ~(self.CARRY | self.ZERO | self.NEGATIVE | self.OVERFLOW)
if aluresult == 0:
self.p |= self.ZERO
else:
self.p |= aluresult & self.NEGATIVE
if decimalcarry == 1:
self.p |= self.CARRY
if ((self.a ^ data) & (self.a ^ aluresult)) & self.NEGATIVE:
self.p |= self.OVERFLOW
self.a = (nibble1 << 4) + nibble0
# the ALU outputs are not yet decimally adjusted
aluresult = self.a + (~data & self.byteMask) + (self.p & self.CARRY)

decimalcarry = aluresult > self.byteMask
aluresult &= self.byteMask

al = (self.a & 0xf) - (data & 0xf) + (self.p & self.CARRY) - 1

if flags_use_adjusted_result: # 65C02 but not 65816
a = self.a - data + (self.p & self.CARRY) - 1
if a < 0:
a -= 0x60
if al < 0:
a -= 0x6

else: # 6502
# Note: 65816 apparently also uses this logic instead of 65C02
if al < 0:
al = ((al - 0x6) & 0xf) - 0x10
a = (self.a & 0xf0) - (data & 0xf0) + al
if a < 0:
a -= 0x60

adjresult = a & self.byteMask

if flags_use_adjusted_result: # 65C02 and 65816
# Z and N use adjusted (i.e. decimal) result
zerores = adjresult
negativeres = adjresult
else: # 6502
# Z and N uses unadjusted (i.e. binary) ALU result
zerores = aluresult
negativeres = aluresult

self.p &= ~(self.CARRY | self.ZERO | self.NEGATIVE | self.OVERFLOW)
if zerores == 0:
self.p |= self.ZERO
else:
result = self.a + (~data & self.byteMask) + (self.p & self.CARRY)
self.p &= ~(self.CARRY | self.ZERO | self.OVERFLOW | self.NEGATIVE)
if ((self.a ^ data) & (self.a ^ result)) & self.NEGATIVE:
self.p |= self.OVERFLOW
data = result & self.byteMask
if data == 0:
self.p |= self.ZERO
if result > self.byteMask:
self.p |= self.CARRY
self.p |= data & self.NEGATIVE
self.a = data
self.p |= negativeres & self.NEGATIVE
if decimalcarry == 1:
self.p |= self.CARRY
if ((self.a ^ data) & (self.a ^ aluresult)) & self.NEGATIVE:
self.p |= self.OVERFLOW
self.a = adjresult

def opDECR(self, x):
if x is None:
Expand Down
10 changes: 8 additions & 2 deletions py65/devices/mpu65c02.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ def __init__(self, *args, **kwargs):
self.name = '65C02'
self.waiting = False

def step(self):
def step(self, trace=False):
if self.waiting:
self.processorCycles += 1
else:
mpu6502.MPU.step(self)
mpu6502.MPU.step(self, trace)
return self

# Make copies of the lists
Expand Down Expand Up @@ -63,6 +63,12 @@ def opTRB(self, x):
self.p |= self.ZERO
self.memory[address] = m & ~self.a

def opADC(self, x):
return self._opADC(x, decimal_flags_use_adjusted_result=True)

def opSBC(self, x):
return self._opSBC(x, decimal_flags_use_adjusted_result=True)

# instructions

@instruction(name="BRK", mode="imp", cycles=7)
Expand Down
Binary file added py65/tests/devices/bcd/6502_decimal_test.bin
Binary file not shown.
Loading