Undocumented Instruction Implementations

This commit is contained in:
tudnai 2021-05-16 18:48:22 -07:00
parent 6b43345ca2
commit c7b202c9d0
8 changed files with 327 additions and 7 deletions

View File

@ -25,13 +25,10 @@
#define __6502_INSTR_BRANCH_H__
INLINE void BRA( int8_t reladdr ) {
m6502.PC += reladdr;
#ifdef CLK_ABSOLUTE_PRECISE
uint8_t pg = m6502.PC >> 8;
m6502.clktime += m6502.PC >> 8 == pg ? 1 : 2;
#else
// m6502.clktime++;
#endif
m6502.PC += reladdr;
m6502.clkfrm += m6502.PC >> 8 == pg ? 1 : 2;
#ifdef DEBUG
if ( reladdr == -2 ) {
dbgPrintf2("Infinite Loop at %04X!\n", m6502.PC);
@ -208,4 +205,79 @@ INLINE void BVS( int8_t reladdr ) {
}
}
/**
BBR BBS - Branch on Bit Reset or Set
BBR and BBS test the specified zero page location and branch if the specified bit is clear (BBR) or set (BBS).
Note that as with TRB, the term reset in BBR is used to mean clear.
On the 6502 and 65C02, bit 7 is typically the most convenient bit to use for I/O and software flags because
it can be tested by several instructions, such as BIT and LDA. BBR and BBS can test any of the 8 bits without
affecting any flags or using any registers. Unlike other branch instructions, BBR and BBS always take the same
number of cycles (five) whether the branch is taken or not. It is often useful to test bit 0, for example, to test
whether a byte is even or odd. However, the usefulness of BBR and BBS is somewhat limited for a couple of reasons.
First, there is only a single addressing mode for these instructions -- no indexing by X or Y, for instance.
Second, they are restricted to zero page locations. For software flags this may be just fine, but it may not be very
convenient (or cost effective) to add any additional address decoding hardware that may be necessary to
map I/O locations to the zero page.
The addressing mode is a combination of zero page addressing and relative addressing -- really just a juxtaposition of the two.
The bit to test is typically specified as part of the instruction name rather than the operand, i.e.
Flags affected: none
OP LEN CYC MODE FLAGS SYNTAX
-- --- --- ---- ----- ------
0F 3 5 zp,rel ........ BBR0 $12,LABEL
1F 3 5 zp,rel ........ BBR1 $12,LABEL
2F 3 5 zp,rel ........ BBR2 $12,LABEL
3F 3 5 zp,rel ........ BBR3 $12,LABEL
4F 3 5 zp,rel ........ BBR4 $12,LABEL
5F 3 5 zp,rel ........ BBR5 $12,LABEL
6F 3 5 zp,rel ........ BBR6 $12,LABEL
7F 3 5 zp,rel ........ BBR7 $12,LABEL
8F 3 5 zp,rel ........ BBS0 $12,LABEL
9F 3 5 zp,rel ........ BBS1 $12,LABEL
AF 3 5 zp,rel ........ BBS2 $12,LABEL
BF 3 5 zp,rel ........ BBS3 $12,LABEL
CF 3 5 zp,rel ........ BBS4 $12,LABEL
DF 3 5 zp,rel ........ BBS5 $12,LABEL
EF 3 5 zp,rel ........ BBS6 $12,LABEL
FF 3 5 zp,rel ........ BBS7 $12,LABEL
**/
#define BBR(n) INLINE void BBR##n( uint8_t src, int8_t reladdr ) { \
dbgPrintf("BBR"#n" "); \
disPrintf(disassembly.inst, "BBR"#n); \
if ( ! (src & (1 << n) ) ) { \
BRA( reladdr ); \
} \
}
BBR(0)
BBR(1)
BBR(2)
BBR(3)
BBR(4)
BBR(5)
BBR(6)
BBR(7)
#define BBS(n) INLINE void BBS##n( uint8_t src, int8_t reladdr ) { \
dbgPrintf("BBS"#n" "); \
disPrintf(disassembly.inst, "BBS"#n); \
if ( (src & (1 << n) ) ) { \
BRA( reladdr ); \
} \
}
BBS(0)
BBS(1)
BBS(2)
BBS(3)
BBS(4)
BBS(5)
BBS(6)
BBS(7)
#endif // __6502_INSTR_BRANCH_H__

View File

@ -45,6 +45,9 @@ INLINE void JMP( uint16_t addr ) {
dbgPrintf("Infinite Loop at %04X!\n", m6502.PC);
}
#endif
if (m6502.PC >> 8 != addr >> 8) {
m6502.clkfrm += 1;
}
m6502.PC = addr;
}

View File

@ -46,6 +46,62 @@ INLINE void BIT( uint8_t src ) {
set_flags_Z(m6502.A & src);
}
/**
TRB - Test and Reset Bits
TRB can be one the more confusing instructions for a couple of reasons.
First, the term reset is used to refer to the clearing of a bit, whereas the term clear had been used consistently before, such as CLC
which stands for CLear Carry. Second, the effect on the Z flag is determined by a different function than the effect on memory.
TRB has the same effect on the Z flag that a BIT instruction does. Specifically, it is based on whether the result of a bitwise AND of the
accumulator with the contents of the memory location specified in the operand is zero. Also, like BIT, the accumulator is not affected.
The accumulator determines which bits in the memory location specified in the operand are cleared and which are not affected.
The bits in the accumulator that are ones are cleared (in memory), and the bits that are zeros (in the accumulator) are not affected (in memory).
This is the same as saying that the resulting memory contents are the bitwise AND of the memory contents with the complement of the
accumulator (i.e. the exclusive-or of the accululator with $FF).
OP LEN CYC MODE FLAGS SYNTAX
-- --- --- ---- ----- ------
14 2 5 zp ......Z. TRB $12
1C 3 6 abs ......Z. TRB $3456
**/
INLINE void TRB( uint16_t addr ) {
dbgPrintf("TRB(%02X) ", src);
disPrintf(disassembly.inst, "TRB");
set_flags_Z( WRLOMEM[addr] & m6502.A );
WRLOMEM[addr] &= ~m6502.A;
}
/**
TSB - Test and Set Bits
TSB, like TRB, can be confusing. For one, like TRB, the effect on the Z flag is determined by a different function than the effect on memory.
TSB, like TRB, has the same effect on the Z flag that a BIT instruction does. Specifically, it is based on whether the result of a bitwise AND
of the accumulator with the contents of the memory location specified in the operand is zero. Also, like BIT (and TRB), the accumulator is not affected.
The accumulator determines which bits in the memory location specified in the operand are set and which are not affected. The bits in the
accumulator that are ones are set to one (in memory), and the bits that are zeros (in the accumulator) are not affected (in memory).
This is the same as saying that the resulting memory contents are the bitwise OR of the memory contents with the accumulator.
Flags affected: Z
OP LEN CYC MODE FLAGS SYNTAX
-- --- --- ---- ----- ------
04 2 5 zp ......Z. TSB $12
0C 3 6 abs ......Z. TSB $3456
**/
INLINE void TSB( uint16_t addr ) {
dbgPrintf("TSB(%02X) ", src);
disPrintf(disassembly.inst, "TSB");
set_flags_Z( WRLOMEM[addr] & m6502.A );
WRLOMEM[addr] |= m6502.A;
}
/**
CMP Compare Memory with Accumulator

View File

@ -80,6 +80,23 @@ INLINE void INY() {
dbgPrintf("%02X ", m6502.Y);
}
/**
INA Increment Accumulator by One
A + 1 -> A N Z C I D V
+ + - - - -
addressing assembler opc bytes cyles
--------------------------------------------
implied INA C8 1 2
**/
INLINE void INA() {
dbgPrintf("INA %02X -> ", m6502.A);
disPrintf(disassembly.inst, "INA");
set_flags_NZ( ++m6502.A );
dbgPrintf("%02X ", m6502.A);
}
/**
DEC Decrement Memory by One
@ -135,5 +152,22 @@ INLINE void DEY() {
dbgPrintf("%02X ", m6502.Y);
}
/**
DEA Decrement Accumulator by One
A - 1 -> A N Z C I D V
+ + - - - -
addressing assembler opc bytes cyles
--------------------------------------------
implied DEC 88 1 2
**/
INLINE void DEA() {
dbgPrintf("DEA %02X -> ", m6502.A);
disPrintf(disassembly.inst, "DEA");
set_flags_NZ( --m6502.A );
dbgPrintf("%02X ", m6502.A);
}
#endif // __6502_INSTR_INC_DEC_H__

View File

@ -168,5 +168,24 @@ INLINE void STY( uint16_t addr ) {
STR(addr, m6502.Y);
}
/**
STZ Store Zero (0) in Memory
0 -> M N Z C I D V
- - - - - -
OP LEN CYC MODE FLAGS SYNTAX
-- --- --- ---- ----- ------
64 2 3 zp ........ STZ $12
74 2 4 zp,X ........ STZ $12,X
9C 3 4 abs ........ STZ $3456
9E 3 5 abs,X ........ STZ $3456,X
**/
INLINE void STZ( uint16_t addr ) {
dbgPrintf("STZ ");
disPrintf(disassembly.inst, "STZ");
STR(addr, 0);
}
#endif // __6502_INSTR_LOAD_STORE_H__

View File

@ -136,5 +136,75 @@ INLINE void SEI() {
m6502.I = 1;
}
/**
RMB SMB - Reset or Set Memory Bit
RMB and SMB clear (RMB) or set (SMB) the specified bit in the specified zero page location,
and can be used in conjuction with the BBR and BBS instructions. Again, note that as with BBR and TRB,
the term reset in RMB is used to mean clear.
The function of RMB and SMB is very similar to the function of TRB and TSB, except that RMB and SMB
can clear or set only one zero page bit, whereas TRB and TSB can clear or set any number of bits. Also,
only zero page addressing is available with RMB and SMB, whereas zero page and absolute addressing
are available for both TRB and TSB. As a result, RMB and SMB do not offer much that isn't already available
with TRB and TSB (which are available on 65C02s from all manufacturers). The main advantages are that
RMB and SMB, unlike TRB and TSB, do not use the accumulator, leaving it available, and do not affect any flags.
However, it is worth noting that it is rarely useful to preserve the value of the Z (zero) flag (the only flag affected by
TRB and TSB), unlike other flags (such as the carry).
Like BBR and BBS, the bit to test is typically specified as part of the instruction name rather than the operand, i.e.
Flags affected: none
OP LEN CYC MODE FLAGS SYNTAX
-- --- --- ---- ----- ------
07 2 5 zp ........ RMB0 $12
17 2 5 zp ........ RMB1 $12
27 2 5 zp ........ RMB2 $12
37 2 5 zp ........ RMB3 $12
47 2 5 zp ........ RMB4 $12
57 2 5 zp ........ RMB5 $12
67 2 5 zp ........ RMB6 $12
77 2 5 zp ........ RMB7 $12
87 2 5 zp ........ SMB0 $12
97 2 5 zp ........ SMB1 $12
A7 2 5 zp ........ SMB2 $12
B7 2 5 zp ........ SMB3 $12
C7 2 5 zp ........ SMB4 $12
D7 2 5 zp ........ SMB5 $12
E7 2 5 zp ........ SMB6 $12
F7 2 5 zp ........ SMB7 $12
**/
#define RMB(n) INLINE void RMB##n( uint8_t zpg ) { \
dbgPrintf("RMB"#n" "); \
disPrintf(disassembly.inst, "RMB"#n); \
WRLOMEM[zpg] &= ~(1 << n); \
}
RMB(0)
RMB(1)
RMB(2)
RMB(3)
RMB(4)
RMB(5)
RMB(6)
RMB(7)
#define SMB(n) INLINE void SMB##n( uint8_t zpg ) { \
dbgPrintf("SMB"#n" "); \
disPrintf(disassembly.inst, "SMB"#n); \
WRLOMEM[zpg] |= (1 << n); \
}
SMB(0)
SMB(1)
SMB(2)
SMB(3)
SMB(4)
SMB(5)
SMB(6)
SMB(7)
#endif // __6502_INSTR_SET_CLR_H__

View File

@ -40,7 +40,7 @@
absolute,X ASL oper,X 1E 3 7
**/
INLINE void _ASL( uint16_t addr ) {
m6502.C = WRLOMEM[addr] & 0x80;
m6502.C = memread(addr) & 0x80;
set_flags_NZ( WRLOMEM[addr] <<= 1 );
}
INLINE void ASL( uint16_t addr ) {

View File

@ -63,6 +63,38 @@ INLINE void PHA() {
PUSH( m6502.A );
}
/**
PHX Push index X on Stack
push X N Z C I D V
- - - - - -
addressing assembler opc bytes cyles
--------------------------------------------
implied PHX 48 1 3
**/
INLINE void PHX() {
dbgPrintf("PHX %02X ", m6502.X);
disPrintf(disassembly.inst, "PHX");
PUSH( m6502.X );
}
/**
PHY Push index Y on Stack
push Y N Z C I D V
- - - - - -
addressing assembler opc bytes cyles
--------------------------------------------
implied PHY 48 1 3
**/
INLINE void PHY() {
dbgPrintf("PHY %02X ", m6502.Y);
disPrintf(disassembly.inst, "PHY");
PUSH( m6502.Y );
}
/**
PLA Pull Accumulator from Stack
@ -80,6 +112,40 @@ INLINE void PLA() {
set_flags_NZ( m6502.A );
}
/**
PLX Pull index X from Stack
pull X N Z C I D V
+ + - - - -
addressing assembler opc bytes cyles
--------------------------------------------
implied PLX 68 1 4
**/
INLINE void PLX() {
m6502.X = POP();
dbgPrintf("PLX %02X ", m6502.X);
disPrintf(disassembly.inst, "PLX");
set_flags_NZ( m6502.X );
}
/**
PLY Pull index Y from Stack
pull Y N Z C I D V
+ + - - - -
addressing assembler opc bytes cyles
--------------------------------------------
implied PLY 68 1 4
**/
INLINE void PLY() {
m6502.Y = POP();
dbgPrintf("PLY %02X ", m6502.Y);
disPrintf(disassembly.inst, "PLY");
set_flags_NZ( m6502.Y );
}
/**
PHP Push Processor Status on Stack