mirror of
https://github.com/pevans/erc-c.git
synced 2024-10-07 01:55:57 +00:00
8e810e724f
This makes testing _slightly_ easier, because now the handlers require a type of state in the cpu vs. a specific opcode state in the segment that we execute from. (The latter being just more complex to work with and require in testing.)
252 lines
6.0 KiB
C
252 lines
6.0 KiB
C
/*
|
|
* mos6502.bits.c
|
|
*
|
|
* The code here is used to implement instructions which operate
|
|
* specifically on bits of values.
|
|
*/
|
|
|
|
#include "mos6502/mos6502.h"
|
|
#include "mos6502/enums.h"
|
|
|
|
/*
|
|
* The and instruction will assign the bitwise-and of the accumulator
|
|
* and a given operand.
|
|
*/
|
|
DEFINE_INST(and)
|
|
{
|
|
MOS_CHECK_NZ(cpu->A & oper);
|
|
cpu->A &= oper;
|
|
}
|
|
|
|
/*
|
|
* This is the "arithmetic" shift left instruction.
|
|
*
|
|
* Here we will shift the contents of the given operand left by one bit.
|
|
* If the operand was the accumulator, then we'll store it back there;
|
|
* if not, we will store it in the last effective address in memory.
|
|
*
|
|
* Note that we use the carry bit to help us figure out what the "last
|
|
* bit" is, and whether we should now set the carry bit as a result of
|
|
* our operation.
|
|
*/
|
|
DEFINE_INST(asl)
|
|
{
|
|
vm_8bit result = oper << 1;
|
|
|
|
MOS_CHECK_NZ(result);
|
|
cpu->P &= ~MOS_CARRY;
|
|
if (oper & 0x80) {
|
|
cpu->P |= MOS_CARRY;
|
|
}
|
|
|
|
if (cpu->addr_mode == ACC) {
|
|
cpu->A = result;
|
|
} else {
|
|
mos6502_set(cpu, cpu->eff_addr, result);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The bit instruction will test a given operand for certain
|
|
* characteristics, and assign the negative, overflow, and/or carry bits
|
|
* in the status register as a result.
|
|
*/
|
|
DEFINE_INST(bit)
|
|
{
|
|
// Zero is set if the accumulator AND the operand results in zero.
|
|
cpu->P &= ~MOS_ZERO;
|
|
if (!(cpu->A & oper)) {
|
|
cpu->P |= MOS_ZERO;
|
|
}
|
|
|
|
// But negative is set not by any operation on the accumulator; it
|
|
// is, rather, set by evaluating the operand itself.
|
|
cpu->P &= ~MOS_NEGATIVE;
|
|
if (oper & 0x80) {
|
|
cpu->P |= MOS_NEGATIVE;
|
|
}
|
|
|
|
// Normally, overflow is handled by checking if bit 7 flipped from 0
|
|
// to 1 or vice versa, and that's done by comparing the result to
|
|
// the operand. But in the case of BIT, all we want to know is if
|
|
// bit 6 is high.
|
|
cpu->P &= ~MOS_OVERFLOW;
|
|
if (oper & 0x40) {
|
|
cpu->P |= MOS_OVERFLOW;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The BIM instruction (which is made up--it's not a real instruction)
|
|
* is here to handle the specific use-case of a BIT instruction in
|
|
* immediate (IMM) mode. We do this in a separate instruction to avoid
|
|
* the need to add logic to the BIT instruction such that it has to know
|
|
* or care about its opcode or its address mode.
|
|
*/
|
|
DEFINE_INST(bim)
|
|
{
|
|
// This is the same behavior as BIT
|
|
cpu->P &= ~MOS_ZERO;
|
|
if (!(cpu->A & oper)) {
|
|
cpu->P |= MOS_ZERO;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Compute the bitwise-exclusive-or between the accumulator and operand,
|
|
* and store the result in A.
|
|
*/
|
|
DEFINE_INST(eor)
|
|
{
|
|
MOS_CHECK_NZ(cpu->A ^ oper);
|
|
cpu->A ^= oper;
|
|
}
|
|
|
|
/*
|
|
* This is pretty similar in spirit to the ASL instruction, except we
|
|
* shift right rather than left.
|
|
*
|
|
* Note that the letters in the instruction stand for "logical" shift
|
|
* right.
|
|
*/
|
|
DEFINE_INST(lsr)
|
|
{
|
|
vm_8bit result = oper >> 1;
|
|
|
|
// The N flag is ALWAYS cleared in LSR, because a zero is always
|
|
// entered as bit 7
|
|
cpu->P &= ~MOS_NEGATIVE;
|
|
|
|
MOS_CHECK_Z(result);
|
|
|
|
// Carry is set to the value of the bit we're "losing" in the shift
|
|
// operation
|
|
cpu->P &= ~MOS_CARRY;
|
|
if (oper & 0x1) {
|
|
cpu->P |= MOS_CARRY;
|
|
}
|
|
|
|
if (cpu->addr_mode == ACC) {
|
|
cpu->A = result;
|
|
} else {
|
|
mos6502_set(cpu, cpu->eff_addr, result);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Compute the bitwise-or of the accumulator and operand, and store the
|
|
* result in the A register.
|
|
*/
|
|
DEFINE_INST(ora)
|
|
{
|
|
MOS_CHECK_NZ(cpu->A | oper);
|
|
cpu->A |= oper;
|
|
}
|
|
|
|
/*
|
|
* This instruction is interesting; it's a _rotation_ left, which means
|
|
* that what was in the 8th bit will move to the 1st bit, and everything
|
|
* else moves down one place.
|
|
*/
|
|
DEFINE_INST(rol)
|
|
{
|
|
vm_8bit result = oper << 1;
|
|
|
|
// Rotations are effectively _9-bit_. So we aren't rotating bit 7
|
|
// into bit 0; we're rotating bit 7 into the carry bit, and we're
|
|
// rotating the _previous value of the carry bit_ into bit 0.
|
|
if (cpu->P & MOS_CARRY) {
|
|
result |= 0x1;
|
|
}
|
|
|
|
cpu->P &= ~MOS_CARRY;
|
|
if (oper & 0x80) {
|
|
cpu->P |= MOS_CARRY;
|
|
}
|
|
|
|
MOS_CHECK_NZ(result);
|
|
|
|
if (cpu->addr_mode == ACC) {
|
|
cpu->A = result;
|
|
} else {
|
|
mos6502_set(cpu, cpu->eff_addr, result);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Here it's a rotation to the right, just like the ROL instruction. All
|
|
* bits are maintained.
|
|
*/
|
|
DEFINE_INST(ror)
|
|
{
|
|
vm_8bit result = oper >> 1;
|
|
|
|
// See the code for ROL for my note on 9-bit rotation (vs. 8-bit).
|
|
if (cpu->P & MOS_CARRY) {
|
|
result |= 0x80;
|
|
}
|
|
|
|
cpu->P &= ~MOS_CARRY;
|
|
if (oper & 0x01) {
|
|
cpu->P |= MOS_CARRY;
|
|
}
|
|
|
|
MOS_CHECK_NZ(result);
|
|
|
|
if (cpu->addr_mode == ACC) {
|
|
cpu->A = result;
|
|
} else {
|
|
mos6502_set(cpu, cpu->eff_addr, result);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This is a really funky instruction. And not in the good, dancy kinda
|
|
* way.
|
|
*
|
|
* First, it does a BIT-style test to see if A & oper are zero; if so,
|
|
* it sets the Z flag.
|
|
*
|
|
* Second, it clears all bits in eff_addr where A's corresponding bits
|
|
* are set to 1. It ignores all bits in eff_addr where A's bits are
|
|
* zero.
|
|
*
|
|
* E.g.:
|
|
*
|
|
* A: 01011001 (accumulator)
|
|
* M: 11111111 (value in memory)
|
|
* R: 10100110 (result)
|
|
*
|
|
* And, as following that, the Z flag should be zero because A&M is a
|
|
* non-zero result.
|
|
*/
|
|
DEFINE_INST(trb)
|
|
{
|
|
cpu->P &= ~MOS_ZERO;
|
|
if (!(cpu->A & oper)) {
|
|
cpu->P |= MOS_ZERO;
|
|
}
|
|
|
|
mos6502_set(cpu, cpu->eff_addr,
|
|
(cpu->A ^ 0xff) & oper);
|
|
}
|
|
|
|
/*
|
|
* Test to see if (A & oper) are zero and set Z flag if so.
|
|
* Additionally, set the bits in the byte at a given effective address
|
|
* (M) to 1 where the A register's bits are also 1. (Bits that are 0 in
|
|
* A are unchanged in M.)
|
|
*/
|
|
DEFINE_INST(tsb)
|
|
{
|
|
cpu->P &= ~MOS_ZERO;
|
|
if (!(cpu->A & oper)) {
|
|
cpu->P |= MOS_ZERO;
|
|
}
|
|
|
|
// The behavior described in the docblock here can be accomplished
|
|
// simply by OR'ing the accumulator and the operand, and storing
|
|
// back into memory at eff_addr.
|
|
mos6502_set(cpu, cpu->eff_addr, cpu->A | oper);
|
|
}
|