Subject: Feature Tests
After devising a
tortuous byte sequence to determine 65816 mode and
automatic host identification, I've considered various sequences to automatically determine host processor. This would allow, for example, block copy instructions to be used, if present, and alternative functionality, if not. Indeed, I hope that it is possible to provide a bitmask of processor bugs, bitmask of processor features, outline memory map and platform ABI revision. In the worst case, it should be possible to switch to an
extended 65C02 virtual machine or similar. In this topic, I concentrate on differentiating NMOS 6502, 65C02, 65CE02 and 65816, with and without Rockwell bit tests. Apologies if I omit other useful cases. Likewise, I am likely to make errors. Apologies in advance and thank you for back-checking.
On 65CE02 and 65816, opcode $F4 pushes 16 bit immediate on stack. This is a three byte sequence. On other processor variants, opcode $F4 is usually a two byte NOP. Therefore, LDY #0 // TSX // $F4 // INY // INY // INY // INY // INY // TXS should restore stack while counting the length of the opcode. On 65CE02 and 65816, RegY should be three. In all other known cases, RegY should be four.
To distinguish between 65CE02 and 65816, opcode $BB either performs LDZ abs,X or TYX. Therefore, LDX #1 // LDY #0 // $BB // INX // INX // INX // INX // INX leaves RegY as zero on 65CE02 or sets ReyX to one on 65816. (A similar test is described in the Transputer Handbook.) As a further back-check, RegX should be four or six on known variants.
To distinguish between NMOS 6502 and 65C02, merely use unconditional branch (opcode $80). On NMOS 6502, this is a two byte NOP.
So far, these three tests distinguish NMOS 6502, 65C02, 65CE02 and 65816. However, this is only the basics. It does not cover processor bugs, individual features (such as Rockwell bit test extension) or other processor variants.
It is preferable to avoid random stack manipulation or random writes. Therefore, a load opcode is preferable. In this case, to test unrelated functionality including Rockwell extension, use opcode $A7 and a spare location in zero page. Opcode $A7 has very distinct action on different classes of 6502 processor architecture:-
- On NMOS 6502, $A7 is an illegal instruction which simultaneously loads RegA and RegX. Specifically, LAX zp.
- On an [url=XXX]architecture where illegal instructions are routed to a second accumulator[/url], opcode $A7 performs LDB zp.
- On CMOS 6502 without Rockwell extension, it is a two byte NOP.
- On CMOS 6502 with Rockwell extension, it sets bit two in a zero page location. Specifically, SMB2 zp.
- On 65816, it specifies 24 bit indirection via zero page. Specifically, LDA [d].
- On other variants, such as 32 bit extensions, it is likely to have unrelated functionality, such as instruction prefix.
My technique to distinguish variations in functionality is to start with a candidate sequence, such as LDA #0 // STA <TEMP // $A7 <TEMP // ORA <TEMP and then assemble and disassemble fuzzed sequences until something useful emerges. In this case, LDY #0 // LDA #4 // STA <TEMP // LDA #7 // LDB <TEMP // INY // INY // INY // INY // INY // AND #2 // STA <TEMP // STB <TEMP // INY // INY // INY // INY // INY should be suitably perverse. (Feel free to devise smaller versions but the gains are minimal.)
- On NMOS 6502 this will be ... // LDA #4 // STA <TEMP // LAX <TEMP // ... // AND #2 // ... // SAX <TEMP // ... which will store zero.
- On a processor with second accumulator, this will store four, as expected.
- On CMOS 6502 without Rockwell extension, unknown instructions are NOPs of the appropriate length and it will store two.
- On CMOS 6502 with Rockwell extension (and 65CE02), the sequence ends with STA <TEMP // SMB2 <TEMP // ... which will store six.
- On 65816, operations intended for RegB are substituted with 24 bit indirection. Suitable set-up can be used to handle an unrelated value.
- On other variants, RegY can be used to determine the length of the opcode. Other tests may be required.
As an example of a 64 bit extension test, 65CE02 and
XR1600 can be distinguished by testing opcode $17 to determine bit operation or prefix extension. Specifically, LDA #0 // $17 $A9 // $17 $A9 only leaves RegA zero on 65CE02. It is either executed as LDA #0 // RMB1 <$A9 // RMB1 <$A9 or it is executed as LDA #0 // LDA.W #$A917. Tests for competing architecture extensions are most likely to test one or more $x7 or $xF opcodes.
Arguably, this is not feature testing but merely cleaving processor architectures into known cases. Therefore, I include a fairly basic feature test around PHX/PLX. If you are really pedantic, you can repeat it on PHY/PLY. On NMOS 6502, these are one byte NOPs. Therefore, LDX #1 // PHX // LDX #0 // PLX will correctly distinguish the presence of this basic CMOS functionality.
These tests generally destroy every available register. Regardless, I believe these tests and others yet to be devised would be useful if they were collected together and used to set bitmasks held within zero page. If this action is statically bound to an application and occurs at start-up, it may add less than 200 bytes to an application but allow guards which perform simultaneous feature tests before invoking one or more fall-backs. The only limit to this scheme is that a Rockwell bit test should not be used to determine the presence of Rockwell bit tests!