I spent a bit of time today cross-referencing opcode maps to come up with the following routine, 20 bytes long and with no branches whatsoever, which produces a (vaguely) human-readable ASCII code in the accumulator indicating the type of core it's running on:
Code:
.P65C02
TestCPU:
LDA #0
STA $84
STA $85
LDA #$1D ; 'N' EOR 'S'
STA $83
LDA #$6B ; 'N' EOR 'S' EOR '8'
STA $1D
LDA #$4E ; 'N'
RMB4 $83 ; magic $47 opcode
EOR $83
; accumulator now contains ASCII:
; N - NMOS 6502
; S - 65SC02
; C - 65C02 or 65CE02
; 8 - 68C816 or 65C802
; output routine for BBC Micro
JSR $FFEE ; OSWRCH
JSR $FFE7 ; OSNEWL
RTS
Further work may be needed to identify sub-types within those four families, but the above will usually be enough to identify a mis-labelled CPU from China, provided you can get it to boot in the first place. The old NMOS 6502s with missing ROR and the 65CE02 are rare enough that you're unlikely to encounter them outside of a well-curated collection, and distinguishing a Rockwell 65C02 from a WDC example is tricky in generic code that doesn't know where to find a source of interrupts and timing.
Attachment:
Screenshot 2020-01-30 01.20.19.png
I thought it might be instructive to explain how this code works. As the comments hint, the key is in the "magic" $47 opcode, which behaves differently on all four families. Other opcodes with a similar class of behaviour could have been used, but $47 has some nice properties that make it work particularly well here.
The first 8 instructions, immediately preceding the "magic opcode", set up some zero-page locations with "magic data". These are needed to cause some of the CPUs to behave in useful ways once given the $47 opcode. Address $83 was chosen because it is available to user programs on the BBC Micro, and is a 1-byte NOP on the 65SC02. Any other 1-byte NOP opcode could be substituted for this address, as long as the zeroes stored at $84 and $85 are also moved to follow it. Address $1D is important for a different reason and should not casually be moved; on the BBC Micro it harmlessly clobbers BASIC's DATA pointer. It can be moved out of zero-page if the data stored at $84 is changed to match, but $83 must contain $1D.
An NMOS 6502 interprets $47 $83 as a concatenation of LSR $83 and EOR $83, in that effective order. Since that instruction is immediately followed by another EOR $83, the accumulator is left unchanged from its initial value of ASCII 'N'.
A 65SC02 interprets both $47 and $83 as 1-byte NOPs. The following EOR $83 changes the accumulator to ASCII 'S' using the magic value $1D stored there earlier.
A 65C02 interprets $47 $83 as RMB4 $83 (as written in the listing), which clears bit 4 of that memory location. The subsequent EOR now produces ASCII 'C' in the accumulator; it's a useful coincidence that 'S' and 'C' differ by only one bit. The rare 65CE02 is backwards compatible with the 65C02, and will be identified as one by this routine.
A 65816 (or 65802) interprets $47 $83 as EOR [$83], indirecting through the 3-byte long pointer we set up there earlier. The magic value stored there combines with the $1D magic value to convert 'N' into '8' in ASCII.
I'm sure some of you are now thinking of the "Story of Mel"…