mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-13 22:32:03 +00:00
Merge pull request #1162 from TomHarte/65C02BCDTest
Extend BCD testing to the 65C02; clean up implementation
This commit is contained in:
commit
ecec9ff6dc
31
Numeric/Carry.hpp
Normal file
31
Numeric/Carry.hpp
Normal file
@ -0,0 +1,31 @@
|
||||
//
|
||||
// Carry.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 30/08/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Carry_hpp
|
||||
#define Carry_hpp
|
||||
|
||||
namespace Numeric {
|
||||
|
||||
/// @returns @c true if there was carry out of @c bit when @c source1 and @c source2 were added, producing @c result.
|
||||
template <int bit, typename IntT> bool carried_out(IntT source1, IntT source2, IntT result) {
|
||||
// 0 and 0 => didn't.
|
||||
// 0 and 1 or 1 and 0 => did if 0.
|
||||
// 1 and 1 => did.
|
||||
return IntT(1 << bit) & (source1 | source2) & ((source1 & source2) | ~result);
|
||||
}
|
||||
|
||||
/// @returns @c true if there was carry into @c bit when @c source1 and @c source2 were added, producing @c result.
|
||||
template <int bit, typename IntT> bool carried_in(IntT source1, IntT source2, IntT result) {
|
||||
// 0 and 0 or 1 and 1 => did if 1
|
||||
// 0 and 1 or 1 and 0 => did if 0
|
||||
return IntT(1 << bit) & (source1 ^ source2 ^ result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif /* Carry_hpp */
|
@ -1101,6 +1101,7 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
4281572E2AA0334300E16AA1 /* Carry.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Carry.hpp; sourceTree = "<group>"; };
|
||||
428168372A16C25C008ECD27 /* LineLayout.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LineLayout.hpp; sourceTree = "<group>"; };
|
||||
428168392A37AFB4008ECD27 /* DispatcherTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DispatcherTests.mm; sourceTree = "<group>"; };
|
||||
42AD552E2A0C4D5000ACE410 /* 68000.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000.hpp; sourceTree = "<group>"; };
|
||||
@ -3313,6 +3314,7 @@
|
||||
4B66E1A8297719270057ED0F /* NumericCoder.hpp */,
|
||||
4BB5B995281B1D3E00522DA9 /* RegisterSizes.hpp */,
|
||||
4BFEA2F12682A90200EBF94C /* Sizes.hpp */,
|
||||
4281572E2AA0334300E16AA1 /* Carry.hpp */,
|
||||
);
|
||||
name = Numeric;
|
||||
path = ../../Numeric;
|
||||
|
@ -11,10 +11,10 @@ import XCTest
|
||||
|
||||
class BCDTest: XCTestCase, CSTestMachineTrapHandler {
|
||||
|
||||
func testBCD() {
|
||||
func testBCD(processor: CSTestMachine6502Processor) {
|
||||
if let filename = Bundle(for: type(of: self)).path(forResource: "BCDTEST_beeb", ofType: nil) {
|
||||
if let bcdTest = try? Data(contentsOf: URL(fileURLWithPath: filename)) {
|
||||
let machine = CSTestMachine6502(processor: .processor6502)
|
||||
let machine = CSTestMachine6502(processor: processor)
|
||||
machine.trapHandler = self
|
||||
|
||||
machine.setData(bcdTest, atAddress: 0x2900)
|
||||
@ -40,13 +40,23 @@ class BCDTest: XCTestCase, CSTestMachineTrapHandler {
|
||||
}
|
||||
}
|
||||
|
||||
func test6502BCD() {
|
||||
testBCD(processor: .processor6502)
|
||||
}
|
||||
|
||||
func test65C02BCD() {
|
||||
testBCD(processor: .processor65C02)
|
||||
}
|
||||
|
||||
private var output: String = ""
|
||||
func testMachine(_ testMachine: CSTestMachine, didTrapAtAddress address: UInt16) {
|
||||
let machine6502 = testMachine as! CSTestMachine6502
|
||||
|
||||
// Only OSWRCH is trapped, so...
|
||||
let character = machine6502.value(for: .A)
|
||||
output.append(Character(UnicodeScalar(character)!))
|
||||
let character = Character(UnicodeScalar(machine6502.value(for: .A))!)
|
||||
if character != "\r" { // The test internally uses \r\n; keep only one of those.
|
||||
output.append(character)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
|
||||
#include "../6502Esque/6502Esque.hpp"
|
||||
#include "../6502Esque/Implementation/LazyFlags.hpp"
|
||||
#include "../../Numeric/Carry.hpp"
|
||||
#include "../../Numeric/RegisterSizes.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
|
@ -282,55 +282,102 @@ template <Personality personality, typename T, bool uses_ready_line> void Proces
|
||||
// MARK: - ADC/SBC (and INS)
|
||||
|
||||
case OperationINS:
|
||||
operand_++;
|
||||
++operand_;
|
||||
[[fallthrough]];
|
||||
case OperationSBC:
|
||||
operand_ = ~operand_;
|
||||
|
||||
if(flags_.decimal && has_decimal_mode(personality)) {
|
||||
const uint16_t notCarry = flags_.carry ^ 0x1;
|
||||
const uint16_t decimalResult = uint16_t(a_) - uint16_t(operand_) - notCarry;
|
||||
uint16_t temp16;
|
||||
uint8_t result = a_ + operand_ + flags_.carry;
|
||||
|
||||
temp16 = (a_&0xf) - (operand_&0xf) - notCarry;
|
||||
if(temp16 > 0xf) temp16 -= 0x6;
|
||||
temp16 = (temp16&0x0f) | ((temp16 > 0x0f) ? 0xfff0 : 0x00);
|
||||
temp16 += (a_&0xf0) - (operand_&0xf0);
|
||||
// All flags are set based only on the decimal result.
|
||||
flags_.zero_result = result;
|
||||
flags_.carry = Numeric::carried_out<7>(a_, operand_, result);
|
||||
flags_.negative_result = result;
|
||||
flags_.overflow = (( (result ^ a_) & (result ^ operand_) ) & 0x80) >> 1;
|
||||
|
||||
flags_.overflow = ( ( (decimalResult^a_)&(~decimalResult^operand_) )&0x80) >> 1;
|
||||
flags_.negative_result = uint8_t(temp16);
|
||||
flags_.zero_result = uint8_t(decimalResult);
|
||||
// General SBC logic:
|
||||
//
|
||||
// Because the range of valid numbers starts at 0, any subtraction that should have
|
||||
// caused decimal carry and which requires a digit fix up will definitely have caused
|
||||
// binary carry: the subtraction will have crossed zero and gone into negative numbers.
|
||||
//
|
||||
// So just test for carry (well, actually borrow, which is !carry).
|
||||
|
||||
if(temp16 > 0xff) temp16 -= 0x60;
|
||||
// The bottom nibble is adjusted if there was borrow into the top nibble;
|
||||
// on a 6502 this doesn't cause additional carry but on a 65C02 it does.
|
||||
// This difference affects invalid BCD numbers only.
|
||||
if(!Numeric::carried_in<4>(a_, operand_, result)) {
|
||||
if constexpr (is_65c02(personality)) {
|
||||
result += 0xfa;
|
||||
} else {
|
||||
result = (result & 0xf0) | ((result + 0xfa) & 0xf);
|
||||
}
|
||||
}
|
||||
|
||||
flags_.carry = (temp16 > 0xff) ? 0 : Flag::Carry;
|
||||
a_ = uint8_t(temp16);
|
||||
// The top nibble is adjusted only if there was borrow out of the whole byte.
|
||||
if(!flags_.carry) {
|
||||
result += 0xa0;
|
||||
}
|
||||
|
||||
if(is_65c02(personality)) {
|
||||
a_ = result;
|
||||
|
||||
// fix up in case this was INS.
|
||||
if(cycle == OperationINS) operand_ = ~operand_;
|
||||
|
||||
if constexpr (is_65c02(personality)) {
|
||||
// 65C02 fix: set the N and Z flags based on the final, decimal result.
|
||||
// Read into `operation_` for the sake of reading somewhere; the value isn't
|
||||
// used and INS will write `operand_` back to memory.
|
||||
flags_.set_nz(a_);
|
||||
read_mem(operand_, address_.full);
|
||||
read_mem(operation_, address_.full);
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
operand_ = ~operand_;
|
||||
}
|
||||
[[fallthrough]];
|
||||
|
||||
case OperationADC:
|
||||
if(flags_.decimal && has_decimal_mode(personality)) {
|
||||
const uint16_t decimalResult = uint16_t(a_) + uint16_t(operand_) + uint16_t(flags_.carry);
|
||||
uint8_t result = a_ + operand_ + flags_.carry;
|
||||
flags_.zero_result = result;
|
||||
flags_.carry = Numeric::carried_out<7>(a_, operand_, result);
|
||||
|
||||
uint8_t low_nibble = (a_ & 0xf) + (operand_ & 0xf) + flags_.carry;
|
||||
if(low_nibble >= 0xa) low_nibble = ((low_nibble + 0x6) & 0xf) + 0x10;
|
||||
uint16_t result = uint16_t(a_ & 0xf0) + uint16_t(operand_ & 0xf0) + uint16_t(low_nibble);
|
||||
flags_.negative_result = uint8_t(result);
|
||||
flags_.overflow = (( (result^a_)&(result^operand_) )&0x80) >> 1;
|
||||
if(result >= 0xa0) result += 0x60;
|
||||
// General ADC logic:
|
||||
//
|
||||
// Detecting decimal carry means finding occasions when two digits added together totalled
|
||||
// more than 9. Within each four-bit window that means testing the digit itself and also
|
||||
// testing for carry — e.g. 5 + 5 = 0xA, which is detectable only by the value of the final
|
||||
// digit, but 9 + 9 = 0x18, which is detectable only by spotting the carry.
|
||||
|
||||
flags_.carry = (result >> 8) ? 1 : 0;
|
||||
a_ = uint8_t(result);
|
||||
flags_.zero_result = uint8_t(decimalResult);
|
||||
// Only a single bit of carry can flow from the bottom nibble to the top.
|
||||
//
|
||||
// So if that carry already happened, fix up the bottom without permitting another;
|
||||
// otherwise permit the carry to happen (and check whether carry then rippled out of bit 7).
|
||||
if(Numeric::carried_in<4>(a_, operand_, result)) {
|
||||
result = (result & 0xf0) | ((result + 0x06) & 0x0f);
|
||||
} else if((result & 0xf) > 0x9) {
|
||||
flags_.carry |= result >= 0x100 - 0x6;
|
||||
result += 0x06;
|
||||
}
|
||||
|
||||
if(is_65c02(personality)) {
|
||||
// 6502 quirk: N and V are set before the full result is computed but
|
||||
// after the low nibble has been corrected.
|
||||
flags_.negative_result = result;
|
||||
flags_.overflow = (( (result ^ a_) & (result ^ operand_) ) & 0x80) >> 1;
|
||||
|
||||
// i.e. fix high nibble if there was carry out of bit 7 already, or if the
|
||||
// top nibble is too large (in which case there will be carry after the fix-up).
|
||||
flags_.carry |= result >= 0xa0;
|
||||
if(flags_.carry) {
|
||||
result += 0x60;
|
||||
}
|
||||
|
||||
a_ = result;
|
||||
|
||||
if constexpr (is_65c02(personality)) {
|
||||
// 65C02 fix: N and Z are set correctly based on the final BCD result, at the cost of
|
||||
// an extra cycle.
|
||||
flags_.set_nz(a_);
|
||||
read_mem(operand_, address_.full);
|
||||
break;
|
||||
@ -342,7 +389,7 @@ template <Personality personality, typename T, bool uses_ready_line> void Proces
|
||||
flags_.carry = (result >> 8)&1;
|
||||
}
|
||||
|
||||
// fix up in case this was INS
|
||||
// fix up in case this was INS.
|
||||
if(cycle == OperationINS) operand_ = ~operand_;
|
||||
continue;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user