Skip to content

The 65C02

The processor in your ACE is a WDC W65C02S. It is the 6502 from 1975, made in CMOS, with the bugs fixed and about thirty instructions added — and it is still in production, which is why you can buy a new one today.

Everything written for the original 6502 runs on it. The reverse is not true, and this chapter is mostly about the difference.

What it fixed

The original 6502 had a famous bug: an indirect jump through an address ending in $FF fetched the second half of its destination from the wrong page. JMP ($10FF) took its high byte from $1000 instead of $1100. The 65C02 does the sensible thing.

It also cleaned up decimal mode — after SED, the negative and zero flags now mean what they say — and turned every unused opcode into a well-behaved NOP instead of something between a lock-up and a surprise.

What it added

These are the ones you will use every day.

InstructionWhat it does
BRABranch always. A relative JMP, one byte shorter.
PHX PHY PLX PLYPush and pull X and Y directly, without going through A.
STZStore zero. No register needed, no register clobbered.
INC A DEC AIncrement and decrement the accumulator itself.
TRB TSBTest and reset, test and set — clear or set a mask of bits in memory in one instruction.
BIT #, BIT zp,X, BIT abs,XBIT in the addressing modes it always should have had.
JMP (abs,X)Jump through a table of addresses.

And one new addressing mode, worth a paragraph of its own.

Zero page indirect

On a 6502, following a pointer means (zp),Y — and if you did not want the Y offset, you loaded Y with zero first and hoped nothing else needed it.

The 65C02 adds plain (zp):

asm
lda (Pointer)                   ; the byte Pointer points at
sta (Destination)

Two bytes, five cycles, and Y stays yours. Most of the code in this section uses it.

This is the one that changes how you write

Once (zp) exists, a zero-page pointer becomes as cheap to use as a variable, and copying, walking a list, or following a table stops needing a spare register. If you learned 6502 on a C64, this is the habit worth rebuilding.

The ones the assembler needs telling about

Two more groups exist on the W65C02S specifically, and they are not part of the plain 65C02 instruction set:

Rockwell's bit operationsRMB0RMB7 and SMB0SMB7 clear or set a single bit in zero page; BBR0BBR7 and BBS0BBS7 branch on one. A flag byte in zero page can be set, cleared and tested in one instruction each.

WDC's twoWAI stops the processor until an interrupt arrives, which is both faster to wake from and dramatically lower power than a polling loop. STP stops it until a reset.

To use any of them, the source has to say

asm
.setcpu "W65C02"

instead of 65C02, and the assembler has to be new enough to know that name — see Installing cc65, which has the whole story. Everything in this section sets 65C02, which every version of cc65 in circulation understands.

Speed

The ACE runs at 1 MHz out of the box, and the J1 PHI2 SELECT jumper moves it to 2 MHz. Most instructions take two to five cycles, so at 1 MHz you have somewhere around 250,000 instructions a second to spend — perhaps twenty thousand per screen frame.

That sounds thin and isn't. The whole of the machine's screen is 960 characters; filling it byte by byte costs about five milliseconds.

Cycle counting, if you're going to do it

Two rules cover most of it. A branch that is taken costs one extra cycle, and two extra if it crosses into another page. An indexed read — lda Table,x — also costs one extra cycle when the index pushes the address over a page boundary, which is why hot tables get aligned to a page and why some code puts its inner loop entirely inside one.

Where to look things up

This guide is not going to reprint the opcode matrix badly when good ones exist:

Next: registers, flags, and how instructions reach memory.

Written for BIOS v1.5. Released under the MIT License.