1
0
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:
Thomas Harte 2023-09-02 11:45:35 -04:00 committed by GitHub
commit ecec9ff6dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 125 additions and 34 deletions

31
Numeric/Carry.hpp Normal file
View 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 */

View File

@ -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;

View File

@ -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)
}
}
}

View File

@ -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"

View File

@ -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;