6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Mon Apr 29, 2024 5:28 am

All times are UTC




Post new topic Reply to topic  [ 12 posts ] 
Author Message
PostPosted: Wed Jan 29, 2020 11:07 pm 
Offline

Joined: Mon May 21, 2018 8:09 pm
Posts: 1462
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
Screenshot 2020-01-30 01.20.19.png [ 117.87 KiB | Viewed 1895 times ]
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:


Last edited by Chromatix on Thu Jan 30, 2020 6:18 pm, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Thu Jan 30, 2020 12:03 am 
Offline
User avatar

Joined: Fri Dec 12, 2008 10:40 pm
Posts: 1000
Location: Canada
Nice! Thanks for this.

_________________
Bill


Top
 Profile  
Reply with quote  
PostPosted: Thu Jan 30, 2020 12:04 am 
Offline

Joined: Mon May 21, 2018 8:09 pm
Posts: 1462
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!


Top
 Profile  
Reply with quote  
PostPosted: Thu Jan 30, 2020 3:30 pm 
Offline
User avatar

Joined: Sat Dec 01, 2018 1:53 pm
Posts: 727
Location: Tokyo, Japan
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." :-)

_________________
Curt J. Sampson - github.com/0cjs


Top
 Profile  
Reply with quote  
PostPosted: Thu Jan 30, 2020 4:02 pm 
Offline

Joined: Mon May 21, 2018 8:09 pm
Posts: 1462
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:
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.


Top
 Profile  
Reply with quote  
PostPosted: Thu Jan 30, 2020 4:46 pm 
Offline

Joined: Mon May 21, 2018 8:09 pm
Posts: 1462
Here's how my 65C02 sim (which tests correctly with Klaus' test suite) runs the extended code above:
Code:
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!


Last edited by Chromatix on Thu Jan 30, 2020 6:21 pm, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Thu Jan 30, 2020 6:08 pm 
Offline
User avatar

Joined: Wed Feb 14, 2018 2:33 pm
Posts: 1399
Location: Scotland
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:
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

_________________
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/


Top
 Profile  
Reply with quote  
PostPosted: Thu Jan 30, 2020 6:17 pm 
Offline

Joined: Mon May 21, 2018 8:09 pm
Posts: 1462
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.


Last edited by Chromatix on Thu Jan 30, 2020 6:23 pm, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Thu Jan 30, 2020 6:20 pm 
Offline
User avatar

Joined: Wed Feb 14, 2018 2:33 pm
Posts: 1399
Location: Scotland
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

_________________
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/


Top
 Profile  
Reply with quote  
PostPosted: Mon Jul 05, 2021 8:19 am 
Offline

Joined: Wed Jan 20, 2021 5:47 am
Posts: 5
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:
.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"… :mrgreen:


What assembler recognize the RMB4 mnemonic ?


Top
 Profile  
Reply with quote  
PostPosted: Mon Jul 05, 2021 8:31 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8428
Location: Southern California
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/ .

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Mon Jul 05, 2021 6:52 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8154
Location: Midwestern USA
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.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 12 posts ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 34 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
cron