Page 1 of 1

A new way to distinguish 6502 variants in software

Posted: Wed Jan 29, 2020 11:07 pm
by Chromatix
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: Select all

.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.
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"… :mrgreen:

Re: A new way to distinguish 6502 variants in software

Posted: Thu Jan 30, 2020 12:03 am
by BillO
Nice! Thanks for this.

Re: A new way to distinguish 6502 variants in software

Posted: Thu Jan 30, 2020 12:04 am
by Chromatix
And I just found a bug in jsbeeb - it doesn't support the 65C02 that should be in the "parasite" side of the Master Turbo, and in fact seems to have put an NMOS 6502 there. Oops!

Re: A new way to distinguish 6502 variants in software

Posted: Thu Jan 30, 2020 3:30 pm
by cjs
This is so truly clever that I am overcome with awe.

Also, it turns out that cheap Chinese vendors are not the only ones doing remarking. It apparently also goes the other way around: py65 claims that its mpu6502 class "simulates the original NMOS 6502 microprocessor," but it's actually a re-marked 65SC02 emulator, at least according to this test. (In case it's not clear, test_chromatix_id is the function that links to. Here is the code under test.)

Oh well. At least he said "simulate" instead of "emulate." :-)

Re: A new way to distinguish 6502 variants in software

Posted: Thu Jan 30, 2020 4:02 pm
by Chromatix
It's pretty common for 6502 sims to implement only the documented NMOS opcodes, and leave the undocumented ones as NOPs. Indeed from the py65 README: "At this time, all of the documented opcodes are supported. Support for the illegal opcodes is planned for the future." That would be detected as 65SC02 behaviour, since I don't exercise any of the opcodes that are new on the 65SC02 in this code. So you should mostly use this code on real hardware or accurate sims.

ETA: to give a better result on py65 and similar, try appending the following after EOR $83:

Code: Select all

CMP #$53 ; ASCII 'S'
BNE *+6
BRA *+4
LDA #$6E ; ASCII 'n'
A real NMOS 6502 will take the BNE branch. A real 65SC02 will take the BRA. But a sim treating undocumented instructions as NOPs will fall through the BRA (possibly executing the $02 operand byte as another NOP) and end up replacing the accumulator value.

Re: A new way to distinguish 6502 variants in software

Posted: Thu Jan 30, 2020 4:46 pm
by Chromatix
Here's how my 65C02 sim (which tests correctly with Klaus' test suite) runs the extended code above:

Code: Select all

FFC0: LDA #$00        ; A=00 X=00 Y=00 S=00    I  ; 0 cyc	fetch FFC0 -> A9	fetch FFC1 -> 00
FFC2: STA  $84        ; A=00 X=00 Y=00 S=00    IZ ; 2 cyc	fetch FFC2 -> 85	fetch FFC3 -> 84	store 0084 <- 00
FFC4: STA  $85        ; A=00 X=00 Y=00 S=00    IZ ; 5 cyc	fetch FFC4 -> 85	fetch FFC5 -> 85	store 0085 <- 00
FFC6: LDA #$1D        ; A=00 X=00 Y=00 S=00    IZ ; 8 cyc	fetch FFC6 -> A9	fetch FFC7 -> 1D
FFC8: STA  $83        ; A=1D X=00 Y=00 S=00    I  ; 10 cyc	fetch FFC8 -> 85	fetch FFC9 -> 83	store 0083 <- 1D
FFCA: LDA #$25        ; A=1D X=00 Y=00 S=00    I  ; 13 cyc	fetch FFCA -> A9	fetch FFCB -> 25
FFCC: STA  $1D        ; A=25 X=00 Y=00 S=00    I  ; 15 cyc	fetch FFCC -> 85	fetch FFCD -> 1D	store 001D <- 25
FFCE: LDA #$4E        ; A=25 X=00 Y=00 S=00    I  ; 18 cyc	fetch FFCE -> A9	fetch FFCF -> 4E
FFD0: RMB4  $83       ; A=4E X=00 Y=00 S=00    I  ; 20 cyc	fetch FFD0 -> 47	fetch FFD1 -> 83	fetch 0083 -> 1D	store 0083 <- 0D
FFD2: EOR  $83        ; A=4E X=00 Y=00 S=00    I  ; 25 cyc	fetch FFD2 -> 45	fetch FFD3 -> 83	fetch 0083 -> 0D
FFD4: CMP #$53        ; A=43 X=00 Y=00 S=00    I  ; 28 cyc	fetch FFD4 -> C9	fetch FFD5 -> 53
FFD6: BNE *+6         ; A=43 X=00 Y=00 S=00 N  I  ; 30 cyc	fetch FFD6 -> D0	fetch FFD7 -> 04
FFDC: BRA *+0         ; A=43 X=00 Y=00 S=00 N  I  ; 33 cyc	fetch FFDC -> 80	fetch FFDD -> FE
FFDC: A=43 X=00 Y=00 S=00 N  I  
It correctly produces ASCII 'C' in the accumulator.

And here is a Visual6502 demonstration!

Re: A new way to distinguish 6502 variants in software

Posted: Thu Jan 30, 2020 6:08 pm
by drogon
And when it outputs a lower case 'v' ?


This is on my Ruby 816 board. code is running in 8-bit register + memory mode.

Code: Select all

000273r 1  A9 00                LDA #0
000275r 1  85 84                STA $84
000277r 1  85 85                STA $85
000279r 1  A9 1D                LDA #$1D ; 'N' EOR 'S'
00027Br 1  85 83                STA $83
00027Dr 1  A9 25                LDA #$25 ; 'N' EOR 'S' EOR '8'
00027Fr 1  85 1D                STA $1D
000281r 1  A9 4E                LDA #$4E ; 'N'
000283r 1               
000283r 1                       .setcpu "65C02"
000283r 1  47 83                RMB4 $83 ; magic $47 opcode
000285r 1                       .setcpu "65816"
000285r 1               
000285r 1  45 83                EOR $83
000287r 1               
000287r 1               ; accumulator now contains ASCII:
000287r 1               ; N - NMOS 6502
000287r 1               ; S - 65SC02
000287r 1               ; C - 65C02 or 65CE02
000287r 1               ; 8 - 68C816 or 65C802
000287r 1               
000287r 1               ; output routine for BBC Micro
000287r 1               
000287r 1  20 93 FF             JSR osWrch
00028Ar 1  20 8C FF             JSR osNewl
00028Dr 1  60                   RTS
The .setcpu is needed as the assembler is called in 816 mode. That just lets the RMB4 opcode assemble. I tried it in emulated 6502 mode and got the same result. Not tried it in 16-bit mode yet.

-Gordon

Re: A new way to distinguish 6502 variants in software

Posted: Thu Jan 30, 2020 6:17 pm
by Chromatix
It seems that I goofed on the magic constant to stash in $1D. Instead of $25, it should be $6B. Fixing that should make it work as expected with an '816. I've made a couple of judicious edits above to reflect that.

Don't bother trying it in 16-bit mode. Although it'll work in native mode with M set (8-bit accumulator), the immediate operands to LDA will be the wrong size with M clear. It's really intended to be run in emulation mode, on the assumption that you're trying to figure out what a chip is from scratch.

Re: A new way to distinguish 6502 variants in software

Posted: Thu Jan 30, 2020 6:20 pm
by drogon
Chromatix wrote:
It seems that I goofed on the magic constant to stash in $1D. Instead of $25, it should be $6B. Fixing that should make it work as expected with an '816.
Yup - got "8" :)

-Gordon

Re: A new way to distinguish 6502 variants in software

Posted: Mon Jul 05, 2021 8:19 am
by wyattwong
Chromatix wrote:
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: Select all

.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.
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"… :mrgreen:
What assembler recognize the RMB4 mnemonic ?

Re: A new way to distinguish 6502 variants in software

Posted: Mon Jul 05, 2021 8:31 am
by GARTHWILSON
wyattwong wrote:
What assembler recognize the RMB4 mnemonic ?
Any 65c02 assembler should. RMBx, SMBx, BBSx, and BBRx (where "x" is the bit number) are standard instructions for Rockwell 65c02's and for WDC W65C02's (which is what all current production ones are).

For the 65c02's many improvements over the original NMOS 6502, see http://wilsonminesco.com/NMOS-CMOSdif/ .

Re: A new way to distinguish 6502 variants in software

Posted: Mon Jul 05, 2021 6:52 pm
by BigDumbDinosaur
GARTHWILSON wrote:
wyattwong wrote:
What assembler recognize the RMB4 mnemonic ?
Any 65c02 assembler should. RMBx, SMBx, BBSx, and BBRx (where "x" is the bit number) are standard instructions for Rockwell 65c02's and for WDC W65C02's (which is what all current production ones are).

For the 65c02's many improvements over the original NMOS 6502, see http://wilsonminesco.com/NMOS-CMOSdif/ .
Some assemblers expect RMB x,<operand> instead of RMBx <operand>. The Kowalski assembler takes the former syntax, also for SMB, BBR and BBS.

Incidentally, I refer to these four instructions as the "Rockwell extensions" in my writings, since Rockwell devised them for use in their modem chipsets. They were not part of the original 65C02 assembly language as promulgated by WDC and only appeared in the WDC parts some time after Rockwell was licensed to produce the 65C02.