Oct 4, 2026 · Updated Oct 11, 2026 · @Craig Taylor
Machine language is the processor's own language: the bytes the 45GS02 fetches and executes. Every BASIC statement eventually becomes machine language, run by the BASIC 65 interpreter in ROM. Writing machine language yourself removes the interpreter from the middle, which is why it is so much faster and why it can do things BASIC cannot.
On the MEGA65 the speed argument is weaker than it was on the C64. At 40.5 MHz, BASIC 65 is fast enough for many jobs that once demanded machine language. The remaining reasons are still strong: raster interrupts and other precisely timed code, full control of the hardware, efficient use of memory beyond the 64KB window, and routines that are simply too slow when interpreted.
This chapter introduces the 45GS02 as a programmer sees it. It covers:
The chapters that follow build on it. Chapter 8 covers techniques specific to the MEGA65, Chapter 9 combines machine language with BASIC, and Chapter 10 is a complete reference to every instruction. Readers who already know 6502 machine language can skim the first sections and concentrate on what is new: the Z and B registers, the extra addressing modes, the word and quad instructions, and the change in meaning of STZ.
The 45GS02 works in binary, eight bits at a time. Programmers write those bits in hexadecimal, because each hex digit stands for exactly four bits and a byte is always two digits.
| Decimal | Binary | Hex |
|---|---|---|
| 0 | %00000000 |
$00 |
| 9 | %00001001 |
$09 |
| 10 | %00001010 |
$0A |
| 15 | %00001111 |
$0F |
| 16 | %00010000 |
$10 |
| 127 | %01111111 |
$7F |
| 128 | %10000000 |
$80 |
| 255 | %11111111 |
$FF |
This book follows the Commodore convention: $ marks
hexadecimal and % marks binary. Bits are numbered 0 to 7
from the right, so bit 7 is the leftmost and most significant. BASIC 65
understands $ constants directly, and HEX$ and DEC convert
between the two forms.
| Name | Size | Range | Typical use |
|---|---|---|---|
| Byte | 8 bits | $00-$FF |
Registers, characters, colours |
| Word | 16 bits | $0000-$FFFF |
CPU addresses, counters |
| Quad | 32 bits | $00000000-$FFFFFFFF |
28-bit addresses, large counters, Q register |
Words and quads are stored little-endian, low byte first. The address
$D020 appears in memory as $20 $D0, and the
28-bit address $0FF80000 as $00 $00 $F8 $0F.
This order is used everywhere on the 45GS02: in instruction operands, in
pointers, in vectors, and in the Q register.
The same eight bits can be read as unsigned, 0 to 255, or as signed
two's complement, -128 to 127. In the signed reading, bit 7 is the sign:
$7F is 127, $80 is -128, and $FF
is -1. To negate a value, invert every bit and add one.
The processor does not know which reading you intend. Addition and subtraction produce the right bit pattern either way; only the flags you test afterwards differ. Carry signals unsigned overflow, and the V flag signals signed overflow. The 65CE02's NEG instruction performs the invert-and-add-one in a single step.
Chapter 5 introduced the 45GS02's registers. Here is how each is used in practice.
| Register | Width | Role in programs | Transfers |
|---|---|---|---|
| A | 8 bits | Arithmetic, logic, most loads and stores | TAX, TAY, TAZ, TAB and their reverses |
| X | 8 bits | Index, loop counter | TXA, TXS, TSX |
| Y | 8 bits | Index, loop counter, indirect indexing | TYA, TYS, TSY |
| Z | 8 bits | Third index, the index for (zp),Z and
[zp],Z |
TZA |
| B | 8 bits | Base page: which page acts as zero page | TBA |
| SP | 8 or 16 bits | Stack pointer | TSX, TXS for the low byte; TSY, TYS for the high byte |
| PC | 16 bits | Address of the next instruction | Changed by jumps, branches, calls, returns |
The Z register is the 65CE02's main gift to programmers. Many loops
that needed both X and Y on a 6502, one as a counter and one as an
index, can now keep both and still have a register free. Z also replaces
the 65C02's plain (zp) mode: LDA ($10),Z with
Z set to zero behaves exactly like the 65C02's
LDA ($10).
The B register makes zero page movable. Zero-page instructions are shorter and faster than absolute ones, so a routine with many variables can point B at its own page and use zero-page addressing for all of them. B must be restored before calling the KERNAL or returning to BASIC, both of which expect B to be zero.
| Bit | Flag | Set when |
|---|---|---|
| 7 | N, negative | The result has bit 7 set |
| 6 | V, overflow | A signed addition or subtraction overflowed |
| 5 | E, extend disable | The stack is limited to 8 bits, as on a 6502 |
| 4 | B, break | Only meaningful in a copy of P pushed by BRK |
| 3 | D, decimal | Addition and subtraction work in binary-coded decimal |
| 2 | I, interrupt disable | IRQ interrupts are blocked |
| 1 | Z, zero | The result was zero |
| 0 | C, carry | Unsigned overflow from addition or a shift, or no borrow from subtraction |
The E flag is new. It occupies the bit that is unused on the 6502 and is set at reset, so the stack starts out 6502-compatible. CLE clears it to enable the 16-bit stack pointer; SEE sets it again. Code that pushes P and inspects it, as some 6502 tricks do, should not assume bit 5 is always set.
Note also that the Z flag and the Z register are unrelated. The flag
records whether a result was zero; the register is a general index.
Assemblers and this book keep them apart by context: BEQ and BNE test
the flag, while LDZ, STZ and ,Z refer to the register.
An addressing mode is the way an instruction finds its operand. The 45GS02 has every 6502 mode, most 65C02 modes, the 65CE02's additions, and one mode of its own.
| Mode | Example | Operand | Origin |
|---|---|---|---|
| Implied | INX |
None; the register is part of the instruction | 6502 |
| Accumulator | ASL A |
The A register | 6502 |
| Immediate | LDA #$41 |
The byte following the opcode | 6502 |
| Immediate word | PHW #$1234 |
The 16-bit value following the opcode | 65CE02 |
| Base page | LDA $10 |
Byte at offset $10 in the base page |
6502 (zero page) |
| Base page indexed | LDA $10,X |
Base page offset plus X, or plus Y for LDX and STX | 6502 |
| Absolute | LDA $D020 |
Byte at a 16-bit address | 6502 |
| Absolute indexed | LDA $0400,X |
16-bit address plus X or Y | 6502 |
| Indexed indirect | LDA ($10,X) |
Pointer at base page $10+X |
6502 |
| Indirect indexed | LDA ($10),Y |
Pointer at base page $10, plus Y |
6502 |
| Indirect indexed by Z | LDA ($10),Z |
Pointer at base page $10, plus Z |
65CE02 |
| Stack relative | LDA ($02,SP),Y |
Pointer on the stack at SP+2, plus Y | 65CE02 |
| Indirect | JMP ($0318) |
Jump through a 16-bit vector | 6502 |
| Indexed indirect jump | JSR ($1000,X) |
Jump or call through a table of vectors | 65C02 and 65CE02 |
| Relative | BNE loop |
8-bit signed offset, -128 to +127 | 6502 |
| Long relative | LBNE far |
16-bit signed offset, anywhere in 64KB | 65CE02 |
| Bit relative | BBS3 $10,done |
Tests one bit of a base page byte and branches | R65C02 |
| Flat indirect | LDA [$10],Z |
32-bit pointer at base page $10, plus Z |
45GS02 |
The term "base page" replaces "zero page" because of the B register. With B at zero the two are identical, and most assemblers accept either name.
Indirect indexed by Z lets a routine walk through a buffer with Z while X and Y keep other jobs. It is the mode most 45GS02 programs use most often after the 6502 basics.
Stack relative reads data that a caller pushed onto the stack. A routine can receive a pointer as a parameter on the stack and use it directly, without first copying it to the base page. This makes re-entrant and recursive routines practical.
Long relative branches remove the 6502's 128-byte
branch limit. Assemblers differ in how they write them: some use
separate mnemonics such as LBNE, others choose the long form
automatically when the target is too far away. The long forms are one
byte longer, so short branches remain the default. When encoding a long
branch by hand, note that its 16-bit offset is counted from the
instruction's second byte, not from the next instruction:
BNE $3000 at $2002 encodes as
$D3 $FC $0F (MEGA65 Book, p. L-13).
Flat indirect is the 45GS02's own mode, covered in
Chapter 5. The instruction is an ordinary (zp),Z
instruction preceded by a NOP prefix, $EA. The prefix tells
the CPU to read four bytes from the base page instead of two and to
treat them as a 28-bit address. Any instruction that supports
(zp),Z also supports [zp],Z.
The 45GS02 has far more instructions than the 6502's 56, but they fall into the same handful of families. This section surveys them; Chapter 10 gives every opcode, mode, flag effect and timing.
| Group | Instructions | Notes |
|---|---|---|
| Load and store | LDA LDX LDY LDZ, STA STX STY STZ | STZ stores the Z register |
| Transfer | TAX TXA TAY TYA TAZ TZA TAB TBA TSX TXS TSY TYS | Moves between registers |
| Arithmetic | ADC SBC NEG, CMP CPX CPY CPZ | NEG negates A |
| Logic | AND ORA EOR BIT, TSB TRB | TSB and TRB set or clear bits in memory |
| Shifts | ASL LSR ROL ROR ASR | ASR is an arithmetic shift right, keeping the sign |
| Increment | INC DEC INX DEX INY DEY INZ DEZ | INC A and DEC A work on the accumulator |
| Word operations | INW DEW ASW ROW PHW | 16-bit operations on memory |
| Single bits | RMB SMB BBR BBS | Clear, set or test one bit of a base page byte |
| Branches | BEQ BNE BCC BCS BMI BPL BVC BVS BRA, and long forms | BRA always branches |
| Jumps and calls | JMP JSR BSR RTS RTN | BSR is a relative call; RTN #n also drops n bytes of parameters |
| Stack | PHA PLA PHX PLX PHY PLY PHZ PLZ PHP PLP PHW | Push and pull |
| Flags | CLC SEC CLI SEI CLD SED CLV CLE SEE | CLE and SEE control the stack width |
| System | BRK RTI MAP EOM | MAP and EOM control memory mapping |
One difference catches nearly every programmer who has used a 65C02. On the 65C02, STZ means "store zero". On the 65CE02, and therefore the 45GS02, the same opcodes mean "store the Z register". Code written for a 65C02 that uses STZ to clear memory works only while Z happens to be zero. When moving 65C02 code to the MEGA65, either keep Z at zero or replace each STZ with a load and store of A.
The 45GS02's instruction timing depends on its speed (MEGA65 Book, p. K-5):
Prefix bytes cost time too: a quad or flat instruction must also
execute its prefix bytes, so LDQ takes longer than
LDA even though it moves four bytes.
The 45GS02 can also switch to a 6502 personality, in which the new opcodes are replaced by the NMOS 6502's undocumented instructions. The Book warns that this mode is incomplete and that most undocumented opcodes do not yet behave correctly (MEGA65 Book, p. K-4).
The word instructions handle 16-bit values in one step. On a 6502, incrementing a 16-bit pointer takes several instructions:
inc ptr
bne skip
inc ptr+1
skip
On the 45GS02 a single instruction does it:
inw ptr
INW and DEW increment and decrement a base page word, setting N from bit 15 and Z if the whole word becomes zero (MEGA65 Book, pp. L-82, L-86). ASW and ROW shift a 16-bit value in memory left. PHW pushes a 16-bit value, either an immediate constant or the contents of a memory word. It pushes the low byte first, so the word lies on the stack high byte first, the reverse of a return address; the stack-parameter example later in this chapter shows the consequence.
RMB and SMB clear or set one bit of a base page byte, and BBR and BBS
branch on whether one bit is clear or set. The bit number is part of the
instruction, written as SMB3 $10 or, in some assemblers,
SMB 3,$10. They are ideal for flags packed into a single
byte, such as the state of several game objects.
The 45GS02 can treat A, X, Y and Z together as a single 32-bit register called Q. Q is not a separate register: loading Q loads all four, and changing any one of them changes Q.
| Bits | 31-24 | 23-16 | 15-8 | 7-0 |
|---|---|---|---|---|
| Register | Z | Y | X | A |
The byte order matches memory: A holds the low byte, just as the
lowest address of a 32-bit value holds its low byte. A quad stored with
STQ at $10 places A at $10, X at
$11, Y at $12 and Z at $13.
| Instruction | Operation |
|---|---|
| LDQ, STQ | Load and store Q |
| ADCQ, SBCQ | 32-bit add and subtract with carry |
| CMPQ | 32-bit compare |
| ANDQ, ORQ, EORQ, BITQ | 32-bit logic |
| ASLQ, LSRQ, ROLQ, RORQ, ASRQ | 32-bit shifts |
| INQ, DEQ | 32-bit increment and decrement |
The usual addressing modes are base page, absolute, base page indirect and flat indirect. Not every instruction supports every mode; Chapter 10 has the details. Assembler mnemonics vary slightly: 64tass, for example, writes ADQ for ADCQ.
The quad instructions are ordinary 8-bit instructions preceded by two
NEG opcodes, $42 $42. The prefix tells the CPU to apply the
next instruction to Q instead of A. $42 $42 $A5 $10 is
LDQ $10, the quad form of LDA $10. For flat
indirect, the NOP prefix follows the NEG prefix:
$42 $42 $EA $B2 $10 is LDQ [$10],Z.
On a 6502, adding two 32-bit numbers takes a loop or four ADC instructions with their loads and stores. With Q it takes four instructions:
clc
ldq num1 ; Q = num1
adcq num2 ; Q = num1 + num2 + carry
stq result
The flags reflect the full 32-bit result: N comes from bit 31, Z is set only if all 32 bits are zero, and C holds the carry out of bit 31. That means 32-bit values can be compared and tested with ordinary branches.
Because Q includes Z, any quad instruction that loads or changes Q
also changes the Z register. Code that uses Z as an index must save it,
or set it again, after a quad operation. In the indirect quad modes,
only LDQ adds Z to the pointer, as in LDQ ($10),Z and
LDQ [$10],Z; the other quad instructions ignore Z as an
index (MEGA65 Book, p. L-12), since Z is itself part of the value being
loaded or stored.
The quad instructions are 45GS02-specific. Code that uses them will not run on a C65 or in an emulator that lacks them, so a program that may run elsewhere should check the CPU first.
The stack is a last-in, first-out area of memory used for return addresses, saved registers and, on the 45GS02, parameters. The stack pointer holds the address of the next free byte; a push stores there and moves SP down, and a pull moves SP up and reads.
With the E flag set, the stack works as on a 6502: the stack
pointer's low byte wraps within a single 256-byte page. That page is
normally page 1, $0100-$01FF, but it is set by the stack
pointer's high byte, so TYS can move the 8-bit stack to any page.
Clearing E with CLE makes SP 16 bits wide, so the stack can be any size
and anywhere in the 64KB window. TYS sets the high byte of SP from Y,
and TSY reads it back (MEGA65 Book, pp. L-78, L-105, L-116).
Most programs that call the KERNAL or return to BASIC should leave E set. A larger stack is useful for a self-contained program, such as a game that takes over the machine, or for deeply recursive code.
JSR pushes the return address and jumps; RTS pulls it and returns. The 45GS02 adds three useful relatives:
Together with stack-relative addressing, these make it practical to pass parameters on the stack, as compilers for larger processors do:
lda #>message ; push the address of the text,
pha ; high byte first, so it lies on
lda #<message ; the stack low byte first, like
pha ; the return address JSR pushes
jsr print
rts
print ldy #$00
loop lda ($03,sp),y ; SP+1/SP+2 hold the return address,
beq done ; SP+3/SP+4 hold the pushed pointer
jsr $ffd2 ; KERNAL CHROUT
iny
bne loop
done rtn #2 ; return and drop the 2-byte parameter
message .text "HELLO", 0
PHW would push the address in a single instruction, but it pushes
the low byte first, leaving the word on the stack high byte first. That
is the wrong way round for a pointer, so the example pushes the two
bytes itself (mega65-core, gs4510.vhdl, September 2026; Xemu, cpu65.c).
An interrupt makes the CPU stop what it is doing, push the program counter and status, and run a handler. RTI returns to the interrupted code. There are three sources:
| Source | Vector | Typical use |
|---|---|---|
| IRQ | $FFFE |
Raster interrupts, timers, keyboard scanning; blocked while I is set |
| NMI | $FFFA |
The RESTORE key and CIA 2 timers; cannot be blocked with SEI |
| BRK | $FFFE |
A software interrupt, used by monitors as a breakpoint |
The vectors are read from whatever is mapped at
$FFFA-$FFFF, usually the KERNAL ROM. The KERNAL handlers
jump through RAM vectors that programs can change; in C64 mode the IRQ
vector is at $0314, as on a C64. Chapter 11 lists the
MEGA65-mode vectors, and Chapter 8 shows how to write raster interrupt
handlers.
Two 45GS02 details matter here. First, MAP blocks interrupts until the next EOM, so a mapping change is never interrupted halfway. Second, a handler must restore every register it changes, and on the 45GS02 that includes Z and B as well as A, X and Y.
Many C64 programs acknowledge a raster interrupt with a
read-modify-write instruction such as INC $D019 or
ASL $D019. This works on a 6502 because such instructions
write the original value back before writing the new one, and that extra
write clears the interrupt. The 65CE02 dropped the redundant write to
save a cycle, which is why much C64 software failed on the C65. The
45GS02 restores the extra write, but only when the target is
$D019, so the C64 idiom works while every other
read-modify-write instruction keeps the faster timing (MEGA65 Book, p.
K-4). New code can use either the idiom or an explicit
LDA #$01 : STA $D019.
The MEGA65 has a machine-language monitor built in, so a first program needs no other software. Type MONITOR at the BASIC prompt to enter it; it also opens when a program executes a BRK instruction, or at start-up if RUN/STOP is held. It shows the registers on entry, including Z, the base page and the full 16-bit stack pointer, and it uses 32-bit addresses, so it can reach attic RAM and the rest of the 28-bit space (MEGA65 Book, pp. O-3, O-5).
The monitor assumes hexadecimal for every number it reads, including
operands in instructions: LDA #10 loads $10,
not decimal 10. Use a prefix whenever there is any doubt: $
hexadecimal, + decimal, & octal,
% binary, or ' for a PETSCII character (MEGA65
Book, p. O-4).
| Command | Purpose |
|---|---|
A address instruction |
Assemble an instruction; the monitor then offers the next address |
D [from [to]] |
Disassemble memory |
M address |
Display memory as bytes |
R |
Display the registers |
G [address] |
Jump to an address, like JMP |
J address |
Call a subroutine, like JSR |
C F H T |
Compare, fill, hunt and transfer memory |
L S V |
Load, save and verify memory to disk |
B address |
Show memory as 8×8 character bitmaps |
@ |
Disk command, directory or status |
X |
Exit to BASIC |
The full command reference is in the MEGA65 Book (p. O-6 onward).
The program below puts the border through every colour many times
over. It uses Z and Y as nested loop counters, so it also exercises one
of the 45GS02's new registers. It is placed at $42000, in
bank 4, which is free as long as no graphics commands are used.
Enter the first line, then type each following instruction at the address the monitor offers:
A 42000 LDZ #$00
A 42002 LDY #$00
A 42004 INC $D020
A 42007 DEY
A 42008 BNE $42004
A 4200A DEZ
A 4200B BNE $42002
A 4200D RTS
Branches are relative, so the code works wherever it is mapped:
assembled at $42000, it runs correctly when SYS maps it at $2000. Check
the result with D 42000, then leave the monitor with X and
run the program from BASIC:
BANK 4 : SYS $2000
Recall from Chapter 6 that during SYS, $2000-$7FFF comes
from the selected bank, which is why the code at $42000
runs at $2000, and that $8000-$FFFF always has
the system view with I/O mapped, which is why INC $D020
reaches the VIC-IV.
The border flickers with horizontal bands of colour rather than
changing as a whole. At 40.5 MHz the inner loop runs many times during
each scan line, so the colour changes partway across the screen. This is
the basis of the raster effects covered in Chapters 8 and 12. Type
BANK 128 afterwards to return BASIC to the system view.
The monitor is fine for experiments and for examining memory, but it has no labels, comments or source files. For anything larger, use a cross-assembler on a PC or an on-device assembler. The examples in the rest of this book use assembler syntax with labels.
$ and
binary with %. Words and quads are stored low byte first,
everywhere.(zp),Z, stack-relative, long branches, bit-relative and the
45GS02's flat [zp],Z.$42 $42 prefix.Chapter 8 applies these to the MEGA65's hardware: banking, DMA, interrupts and speed control from machine language.