After a lot of preparatory work, we are now in position to finally tighten
the types of the 6502 registers defined in the CPURegs struct of sim65.
All registers were previously defined as bare 'unsigned', leading to subtle
bugs where the bits beyond the 8 or 16 "true" bits in the register could
become non-zero. Tightening the types of the registers to uint8_t and
uint16_t as appropriate gets rid of these subtle bugs once and for all,
assisted by the semantics of C when assigning an unsigned value to an
unsigned type with less bits: the high-order bits are simply discarded,
which is precisely what we'd want to happen.
This change cleans up a lot of spurious failures of sim65 against the
65x02 test-set. For the 6502 and 65C02, we're now *functionally*
compliant. For timing (i.e., clock cycle counts for each instruction),
some work remains.
The linkage of the 'Regs' variable in 6502.c was changed from static
to extern. This makes the Regs type visible (and even alterable) from
the outside.
This change helps tools to inspect the CPU state. In particular, it
was implemented to facilitate a tool that verifies opcode
functionality using the '65x02' testsuite. But the change is also
potentially useful for e.g. an online debugger that wants to inspect
the CPU state while the 6502 is neing simulated.
The current (before-this-patch) version of sim65.c does not correctly implement
the ADC and SBC instructions in 65C02 mode. This PR fixes that.
The 6502 and 65C02 behave identically in binary mode; in decimal behavior
however they diverge, both in the handling of inputs that are not BCD values,
and in the handling of processor flags.
This fix restructures the original "ADC" and "SBC" macros in versions that
are specific for the 6502 and the 65C02, and updates the opcode tables to
ensure that they point to the correct implementations.
Considering the ADC instruction for a moment, the original "ADC" macro was
changed to two macros ADC_6502 and ADC_65C02. These check the D (decimal
mode) bit, and defer their implementation to any of three macros ADC_BINARY_MODE,
ADC_DECIMAL_MODE_6502, and ADC_DECIMAL_MODE_65C02. This is a bit verbose but it
makes it very clear what's going on.
(For the SBC changes, the analogous changes were made.)
The correctness of the changes made is ensured as follows:
First, an in-depth study was made how ADC and SBC work, both in the original
6502 and the later 65C02 processor. The actual behavior of both processors
was captured on hardware (an Atari 800 XL with a 6502 and a Neo6502 equipped
with a WDC 65C02 processor), and was analyzed. The results were cross-referenced
with internet sources, leading to a C implementation that reproduces the exact
result of the hardware processors. See:
https://github.com/sidneycadot/6502-test/blob/main/functional_test/adc_sbc/c_and_python_implementations/6502_adc_sbc.c
Next, these C implementations of ADC and SBC were fitted into sim65's macro-
based implementation scheme, replacing the existing 6502-only implementation.
Finally, the new sim65 implementation was tested against the 65x02 testsuite,
showing that (1) the 6502 implementation was still correct; and (2) that
the 65C02 implementation is now also correct.
As an added bonus, this new implementation of ADC/SBC no longer relies on a
dirty implementation detail in sim65: the previous implementation relied on
the fact that currently, the A register in the simulator is implemented as
an "unsigned", with more bits than the actual A register (8 bits). In the
future we want to change the register width to 8 bits, and this updated
ADC/SBC is a necessary precursor to that change.
Issue #2539 brings to light a number of issues in the sim65 simulator.
Several issues can be traced back to undesirable side effects of the
use of bare 'unsigned' types for the CPU registers in the 'CPURegs'
type defined in src/sim65/6502.h.
The intention is to tighten the types of the registers defined there
to uint8_t and uint16_t, in accordance with the actual number of bits
that those registers have in the 6502. However, it turns out that a
handful of opcode implementations depend on the fact that the register
types currently have more bits than the actual 6502 registers themselves
for correct operation. This mostly involves operations that involve
the carry bit (ROL, ROR, ADC, SBC).
In preparation of fixing the CPURegs field types, we will first make
sure that those opcode implementations are changed in such a way that
they still work if the underlying register types are tightened to their
actual bit width.
This PR concerns this specific change for the ROL and ROR operations.
The correct functioning of ROL and ROR after this patch has been verified
by testing against the 65x02 test suite.
In sim65, simulator memory access to a 64 KB array is implemented via functions
defined in src/sim65/memory.h and src/sim65/memory.c.
In the old version, the types for both content bytes (8 bits), content words
(16 bits), regular addresses (16 bits), and zero-page addresses (8 bits) were
all given as bare 'unsigned'.
This lead to several cases of address overrun (e.g., when an instruction wraps
around from address 0xffff to 0x0000) when running the simulator against a
stress test (specifically, the 65x02 test suite). To protect from this, and to
more properly express the bit width of the types involved which is a good idea
anyway, we start using the fixed-width types provided by 'stdint.h'.
In the process, we also change the MemReadByte macro to a full function call.
This may impact performance (by a small amount), but it improves memory safety,
as cases where the address is accidentally expressed as a value exceeding 0xffff
are handled by wrap-around (as it is in the actual hardware), rather than causing
access outside of the Mem[] array where the 64 KB of simulated RAM resides.
The reason for this patch is twofold.
(1) It is a partial patch for issue #2539.
Several issues brought to the surface by running the 65x02 testsuite are
eliminated by these changes. In the discussion about this issue, it was
concluded that it is a Good Idea to use the stdint-types, both for the
simulated CPU registers and for the memory. This patch addresses the
memory-part of that change.
(2) It is a precursor patch for issue #2355.
For that issue, we will implement a memory-mapped timer register. This will
make handling of memory access in the simulator a bit more complex.
Having proper functions with the proper types in place will help to make the
timer register patch easier.
The BIT #imm instruction behaves differently from the BIT instruction with other
addressing modes, in that it does /not/ set the N and V flags according to the
value of its operand. It only sets the Z flag, in accordance to the value of
(A & operand).
This is corroborated in two ways:
- The 65x02 test suite;
- Documentation about BIT #imm such as http://www.6502.org/tutorials/65c02opcodes.html
This patch implements the correct behavior for BIT with immediate addressing.
The patched version passes the 65x02 test suite for 65C02 opcode 0x89.
Branch instructions, when taken, take three or four cycles,
depending on whether a page is crossed by the branch.
The proper check to determine whether the extra cycle must be added
is the target address of the branch vs the address immediately
following the branch.
In the former version of the BRANCH instruction handler, the target
address was incorrectly checked vs the address of the branch instruction
itself.
The corrected behavior was verified against a real 6502 (Atari) and
the 65x02 testsuite.
The JMP (ind) bug is present in the 6502 which is emulated by both the "6502" and "6502X"
emulation targets of sim65; specifically, the OPC_6502_6C handler. In the old code, the
bug-exhibiting code was not executed when the target was set to 6502X, which is incorrect.
the patch removes the (CPU == CPU_6502) check, which made no sense.
The JMP (ind) bug was actually fixed in the 65c02. Indeed, the OPC_65C02_6C opcode handler
has code that implements the 'right' behavior.