Programming the MEGA65 — Chapter 8: ML Methods Specific to the MEGA65

Oct 4, 2026 · Updated Oct 11, 2026 · @Craig Taylor

Introduction

Chapter 7 described the 45GS02 in isolation. Real programs run on a machine full of state that someone else set up: the KERNAL's memory map, BASIC's choice of bank, the current I/O personality, the CPU speed, and interrupt handlers already installed. MEGA65 machine language is largely the craft of taking control of that state, changing it deliberately, and handing it back intact.

This chapter collects the techniques that are specific to the MEGA65 rather than to the 6502 family in general:

Two styles of program appear here. A cooperative program runs alongside BASIC and the KERNAL, called with SYS and returning with RTS; it must leave everything as it found it. A takeover program, such as a game or demo, replaces the system's map and interrupt handlers entirely and never returns. Most techniques apply to both, but where they differ the text says so.

The examples use the same assembler syntax as Chapter 7: .byte and .word for data, [zp],Z for flat indirect addressing, and labels throughout.

Taking Control

A cooperative routine called by SYS starts in a known state: the mapping described in Chapter 6, with bank 0 low memory, the selected bank in the middle, and the KERNAL and I/O on top. Its only obligation is to restore anything it changes, including B, Z and the E flag, before RTS.

A takeover program cannot rely on whatever state it inherits. Its first job is to set every banking mechanism, the I/O personality and the CPU state to values it chooses. The following skeleton does that, leaving all of bank 0 visible as RAM with I/O at $D000:

start   sei             ; no interrupts while the machine changes
        lda #$35
        sta $01         ; C64 port: RAM everywhere except I/O

        lda #$00
        tax
        tay
        taz
        map             ; MAP: no offsets, nothing mapped
        eom

        lda #$47
        sta $d02f
        lda #$53
        sta $d02f       ; VIC-IV I/O personality

        lda #$00
        sta $d030       ; no C65 ROM banking
        tab             ; base page = page 0
        see             ; 8-bit stack in page 1
        cld

        lda #65
        sta $00         ; full CPU speed

        lda #$7f
        sta $dc0d       ; disable CIA 1 interrupts
        sta $dd0d       ; disable CIA 2 interrupts
        lda $dc0d       ; acknowledge any pending
        lda $dd0d

The order matters in three places:

  1. Interrupts first. SEI comes before any change, because the KERNAL's interrupt handler assumes the KERNAL's map. An interrupt arriving halfway through would run with the ROM switched out.
  2. Knock before $D030. The ROM-banking bits of $D030 exist only in the VIC-III and VIC-IV personalities; in the VIC-II personality the address holds only the C128 2 MHz bit. So the knock must come first. MAP takes precedence over every other banking mechanism, but only for the 8KB blocks it maps (MEGA65 Book, p. K-7). The skeleton maps no blocks, so any ROM bits left set in $D030 would still bring ROM into those areas; clearing $D030 removes them.
  3. All three banking mechanisms. Clearing MAP alone leaves $01 and $D030 in charge of every block MAP does not map. Only when all three are set does the program know exactly what it sees.

The single MAP in the skeleton clears the offsets and block enables but not MAP's megabyte bytes, which only the two-MAP form described below changes. They are zero after reset and the KERNAL leaves them at zero, so this is normally safe (MEGA65 Book, p. 14-41); a program that may start after other software has used them can clear them first with LDA #0 : LDX #$0F : LDY #0 : LDZ #$0F : MAP before the skeleton's own MAP.

The MEGA65 Book describes a fuller version of the same procedure for programs that want every byte of chip RAM. It also removes I/O from $D000-$DFFF by clearing bits 0-2 of $01, moves colour data so the first 2KB of colour RAM can be used as general memory, and disables the ROM write-protection on banks 2 and 3 (MEGA65 Book, pp. 14-39, 14-42).

For that last step, use the Hypervisor's rom_writeenable service, which always leaves the ROM writable:

        lda #$02
        sta $d641       ; rom_writeenable
        clv             ; single-cycle instruction after the trap

The Book's example uses trap $70 on $D640 instead, but that service toggles the protection: if something has already switched it off, it switches it back on, and the program's writes to $20000-$3FFFF are then silently ignored. LDA #$00 : STA $D641 turns the protection back on (MEGA65 Book, Hypervisor services appendix; mega65-core, Hyppo mem.asm).

After this the program owns the machine. The KERNAL is gone, so the hardware vectors at $FFFA-$FFFF are now plain RAM in bank 0. The program must store its own NMI and IRQ vectors there before it executes CLI, as shown in the raster interrupt section below. The RESTORE key raises an NMI, so even a program with no use for NMIs needs a vector pointing at an RTI.

As a starting point, place this skeleton after a BASIC header at $2001, as described in Chapter 6, so the program loads and runs with RUN.

Controlling CPU Speed

The MEGA65 inherited a speed control from each machine it is compatible with, and all of them still work. Two bits do most of the work in the VIC-III and VIC-IV personalities: FAST, bit 6 of $D031, and VFAST, bit 6 of $D054.

Speed FAST ($D031 bit 6) VFAST ($D054 bit 6) Other
1 MHz 0 0
2 MHz 0 0 C128 2 MHz bit set
3.5 MHz 1 0
40.5 MHz 1 1

The C128 bit is bit 0 of $D030 in the VIC-II personality. In the C65 personality that address is the C65 banking register, which is one reason the personality matters when changing speed.

The $00 shortcut

The CPU port's direction register at $00 has a special meaning for two values. Writing $41, decimal 65, forces full speed regardless of the bits above. Writing $40, decimal 64, removes the force and returns control to them. Neither value changes the port's direction bits. This is the simplest method and works in every personality:

        lda #$41
        sta $00         ; force 40.5 MHz

The CPU catches these values only when the write reaches address $0000 with the normal mapping there. If MAP covers $0000-$1FFF, or the base page register B is not $00 so that sta $00 lands elsewhere, the shortcut does nothing, and a takeover program that maps the lowest block must use the bits below instead.

Changing the bits directly

To select a particular speed, release the force and set the bits. The 45GS02's TSB and TRB instructions set and clear bits in memory without disturbing the rest:

        lda #$40
        sta $00         ; release forced full speed
        trb $d054       ; VFAST off
        tsb $d031       ; FAST on: 3.5 MHz

Writing to $D031 has a side effect worth knowing. It is one of the VIC-IV's hot registers, along with $D011, $D016, $D018 and the VIC bank bits of $DD00: any write to one of them, even writing back the value already there, recalculates all the VIC-IV's display settings from their VIC-II equivalents. FAST lives in $D031, so changing speed this way can undo a program's VIC-IV screen setup (MEGA65 Book, p. Q-17). Either clear HOTREG, bit 7 of $D05D, as described in Chapter 12, or change speed with the $00 shortcut instead.

From BASIC, SPEED 1, SPEED 3 and SPEED select 1, 3.5 and 40.5 MHz, and FAST is a synonym; RSPEED reports the current speed as 1, 3 or 40. Both have no effect, and RSPEED reports wrongly, once POKE 0,65 has forced full speed (MEGA65 Book, pp. B-106, B-225, B-245).

Things that change speed for you

A program cannot always assume the speed it set is the speed it gets. RUN/STOP-RESTORE resets the speed to 40.5 MHz (MEGA65 ROM change log), and the user can change speed in the Freezer.

Nor is 1 MHz an exact copy of a C64. At 1, 2 and 3.5 MHz each instruction takes the 6502's number of cycles, but the memory accesses inside it may fall on different cycles, and timing can differ by up to about 7 microseconds. That is harmless for nearly all software, including many fast loaders, but tricks that depend on the exact cycle of a write, such as VIC-II variable screen positioning, are unlikely to work (MEGA65 Book, p. K-5). Timing-critical code should synchronise to the raster rather than counting cycles, at any speed.

Practical MAP Techniques

Chapter 5 described MAP's registers. This section shows how to use it for the most common job: opening an 8KB window onto memory outside the first megabyte, such as attic RAM.

Choosing between MAP, flat addressing and DMA

The three ways to reach distant memory suit different jobs:

Method Best for Cost
Flat [zp],Z Scattered reads and writes Pointer setup; two extra cycles per load and three per store
DMA Moving or filling blocks Building a DMA list; CPU waits while it runs
MAP Running code from, or working intensively in, a region Changes what the CPU sees; affects every access in that block

MAP is the only one of the three that lets the CPU execute code held outside the 64KB window, and it lets ordinary absolute and indexed instructions work on distant data at full speed.

A window onto attic RAM

To make attic RAM at $8000000 appear at $4000-$5FFF, the lower half needs megabyte $80, and an offset within that megabyte that turns CPU address $4000 into $00000. That offset is $00000 - $4000, which wraps within 20 bits to $FC000. So A holds $C0, offset bits 15-8, and the low nibble of X holds $F, offset bits 19-16. Block 2 is enabled by bit 6 of X:

        lda #$80        ; megabyte $80: attic RAM
        ldx #$0f        ; X = $0F: A selects the lower megabyte
        ldy #$00
        ldz #$00
        map
        lda #$c0        ; lower offset bits 15-8
        ldx #$4f        ; enable block 2 ($4000), offset bits 19-16 = $F
        ldy #$00
        ldz #$00        ; upper half unmapped
        map
        eom

The megabyte selection persists until changed, so the second MAP sets the offset within megabyte $80. Only one EOM is needed, after the final MAP; interrupts stay blocked between the two. Moving the window is just a matter of repeating the second MAP with a different offset. To return to the first megabyte, select megabyte 0 the same way.

In general, the offset for a window is (target - window) AND $FFFFF, where target is the address within the selected megabyte and window is the CPU address of the block. A takes offset bits 15-8, and the low nibble of X takes bits 19-16. Moving the window up by 8KB means adding $2000 to the offset, so $FC000 becomes $FE000, then $00000, and so on.

Saving and restoring the map

The 45GS02 has no instruction to read the current MAP settings back, but the Hypervisor does. Trap $74, hyppo_get_mapping, writes the MAP state, both halves including their megabyte bytes, as six bytes to a page-aligned address in the bottom 32KB of bank 0, with the page number in Y (MEGA65 Book, pp. 14-41, 14-42):

        ldy #$62        ; save the map at $6200-$6205
        lda #$74        ; hyppo_get_mapping
        sta $d640
        clv
        bcc error

A cooperative routine can use this to save the map it inherited, change it, and later restore it with the matching service, trap $76, hyppo_set_mapping, which loads the map from the same six-byte layout at page Y (MEGA65 Book, Appendix N). Flat addressing and DMA remain simpler when they suffice, since they leave the map alone, but MAP is no longer off-limits to code that must return cleanly to BASIC. A takeover program can still simply keep a copy of the values it last set.

Advanced DMA

Chapter 5 showed a single fill job. DMAgic fills memory at 40.5MB per second and copies at 20.25MB per second, against about 3.5MB and 1.7MB per second on a C65 (MEGA65 Book, p. P-3), so almost any bulk data movement is worth handing to it. This section covers the options that make it useful beyond simple blocks.

All examples use enhanced jobs, started by writing the low byte of the list address to $D705, and the F018A list layout from Chapter 5: command, count, source, destination, modulo. Two details of the trigger registers matter. Writing the bank register $D702 also clears the megabyte register $D704, so write $D702 first and $D704 after it. And BASIC 65 itself uses DMA to scroll the screen, so a program should rewrite all the list address registers before each job rather than assume they still hold its values (MEGA65 Book, pp. P-3, P-20, P-23). Two other triggers exist: $D706 takes the list address in the current CPU memory map, and $D707 runs a list placed directly after the instruction that triggers it, continuing execution after the list (MEGA65 Book, p. P-24).

Reaching any megabyte

The three address bytes in a DMA list cover only the first megabyte: low byte, high byte, and a bank in the low nibble of the third byte. Option $80 sets the source megabyte and option $81 the destination megabyte. This copies 16KB of level data from attic RAM at $8010000 to bank 4:

level   .byte $0a       ; F018A format
        .byte $80, $80  ; source megabyte $80 (attic RAM)
        .byte $81, $00  ; destination megabyte $00 (chip RAM)
        .byte $00       ; end of options
        .byte $00       ; command: copy
        .word $4000     ; 16KB
        .word $0000     ; source address within bank
        .byte $01       ; source bank 1: $8010000
        .word $0000     ; destination address within bank
        .byte $04       ; destination bank 4: $0040000
        .word $0000     ; modulo, unused

Addresses wrap within the selected megabyte during a job: a copy that runs past the end of a megabyte continues at its start. To let a long copy or fill cross a megabyte boundary, include option $01 in the job, or set bit 1 of $D703 to make that the default for every job. Both work from core 0.97 and are ignored in hypervisor mode (mega65-core, gs4510.vhdl; mega65-core PR #830).

Colour RAM at $FF80000 is reached the same way, with megabyte $FF and bank $08.

Skipping bytes

Normally DMAgic steps one byte at a time. Options $83 and $85 set the step, in whole bytes, for the source and destination. With a destination step of 80, a fill draws a vertical line down an 80-column text screen:

vline   .byte $0a
        .byte $85, 80   ; destination step: 80 bytes, one row
        .byte $00
        .byte $03       ; command: fill
        .word 25        ; 25 rows
        .word $00a0     ; fill with screen code $A0, a solid block
        .byte $00
        .word $0800+40  ; column 40 of the screen at $0800
        .byte $00
        .word $0000

Steps are useful wherever data is interleaved: a column of a screen or bitmap, every second byte of the two-byte colour RAM cells used by the VIC-IV's enhanced text modes, or one channel of interleaved sample data.

Chaining jobs

Bit 2 of the command byte marks a job as chained: when it finishes, DMAgic continues with the list that immediately follows, until it reaches a job without the chain bit. A single trigger can then perform a whole sequence of operations, such as clearing the screen, clearing colour RAM and copying in a new character set. In enhanced mode each job in the chain begins with its own option bytes, so each can use different megabytes and steps. Options carry over from one job to the next until the chain ends, though: a job that needs an option back at its default must set it again, because leaving it out does not reset it.

clear   .byte $0a, $00  ; job 1 options: F018A, end
        .byte $07       ; fill, chained (bit 2 set)
        .word 2000
        .word $0020     ; spaces
        .byte $00
        .word $0800
        .byte $00
        .word $0000
        .byte $0a       ; job 2 options: F018A
        .byte $81, $ff  ; destination megabyte $FF
        .byte $00
        .byte $03       ; fill, not chained: last job
        .word 2000
        .word $0001     ; colour 1, white
        .byte $00
        .word $0000
        .byte $08       ; bank 8 of megabyte $FF: colour RAM
        .word $0000

Transparency and line drawing

The option list offers more than megabytes and steps (MEGA65 Book, p. P-14). Option $07 turns on a transparent value and $86 sets it: bytes equal to that value are skipped instead of copied, leaving the destination unchanged. That turns a DMA copy into a simple blitter for shapes with a background colour:

blit    .byte $0a
        .byte $07       ; enable transparency
        .byte $86, $00  ; bytes of $00 are not written
        .byte $00       ; end of options
        .byte $00       ; command: copy
        .word 64        ; one FCM character's 64 bytes
        .word $2000     ; source $42000
        .byte $04
        .word $0040     ; destination $40040
        .byte $04
        .word $0000

Options $82 and $84 add fractional steps, in 256ths of a byte, which DMAgic uses for texture scaling, and options $87 to $8F and $97 to $9F configure a line-drawing mode that steps the destination or source along a slope. The Book quotes rates of up to 20 million pixels per second for scaling and 40 million for line drawing; its DMA chapter documents the parameters (MEGA65 Book, pp. P-13, P-14). On release 0.97 cores a line comes out two pixels shorter than its count, so add 2. Development cores fix this, let the direction bit reverse the major axis, and add line scaling through bit 4 of option $8F or $9F (mega65-core PR #887).

Not every F018 operation exists. Only copy and fill are implemented: the command values for swap and mix, and the minterm bits that mix used, do nothing useful on the MEGA65 (MEGA65 Book, pp. P-6, P-24).

DMA and the CPU

The CPU stops while a job runs and resumes at the instruction after the trigger. There is no need to poll for completion, but because the DMA controller is part of the CPU, a job cannot be interrupted, and a long one delays interrupts too (MEGA65 Book, p. P-3). A raster interrupt that must fire on time should not compete with a very large transfer; split big jobs into pieces timed to fit between interrupts. The command byte has a bit documented as "yield to interrupts", bit 3, but the Book's own text says jobs cannot be interrupted, so do not rely on it.

Audio DMA is the exception: it keeps playing during an ordinary job, stealing the cycles it needs from it, so sound does not stop while DMA runs (MEGA65 Book, p. P-3).

Raster Interrupts

A raster interrupt fires when the video chip reaches a chosen line of the display. It is the standard way to change colours, scroll registers or screen modes partway down the screen, and to run code once per frame at a steady rate. The VIC-IV supports the VIC-II's raster interrupt registers unchanged, so the C64 technique carries over.

Register Purpose
$D012 Raster compare, bits 7-0
$D011 bit 7 Raster compare, bit 8
$D01A bit 0 Enable the raster interrupt
$D019 bit 0 Raster interrupt has occurred; write 1 to acknowledge

A split-screen example

This continues from the takeover skeleton earlier in the chapter, with interrupts disabled, CIA interrupts off and the KERNAL switched out. It installs its own vectors and changes the border colour at two raster lines, splitting the border into two colours:

        lda #<nmi
        sta $fffa
        lda #>nmi
        sta $fffb
        lda #<irq
        sta $fffe
        lda #>irq
        sta $ffff

        lda #$00
        sta line        ; first interrupt at line 0
        sta $d012
        lda $d011
        and #$7f        ; compare bit 8 = 0
        sta $d011
        lda #$01
        sta $d01a       ; enable raster interrupt
        sta $d019       ; clear any pending one
        cli

main    jmp main        ; the main program runs here

irq     pha
        lda line
        bne lower
        lda #$06        ; blue at the top
        sta $d020
        lda #$90
        bra set
lower   lda #$02        ; red from line $90 down
        sta $d020
        lda #$00
set     sta line
        sta $d012       ; next interrupt line
        lda #$01
        sta $d019       ; acknowledge
        pla
        rti

nmi     rti             ; ignore RESTORE

line    .byte 0

The handler alternates between two lines, setting the next compare value each time. It saves only A because it changes only A. A handler that uses X, Y or Z must save those too, and one that changes B must restore it before RTI.

Stability

On a C64, raster code at 1 MHz suffers from jitter: the interrupt can start a few cycles late depending on which instruction it interrupted, which shows as a ragged edge on colour changes. At 40.5 MHz the same few cycles are a tiny fraction of a scan line, so most raster effects are clean without the elaborate stabilisation C64 code needs. Code that runs at 1 MHz for compatibility still has the C64's jitter.

The VIC-II-style registers count VIC-II raster lines. The VIC-IV also works in physical display lines, which allows finer positioning (MEGA65 Book, pp. Q-43, Q-44):

Register Name Purpose
$D052, low bits of $D053 FNRASTER Current physical raster line (read)
$D053 bit 7 FNRST Reads back the compare mode: 0 physical, 1 VIC-II lines
$D079, low bits of $D07A RASCMP Physical raster compare value, used when the compare mode is physical
$D07A bit 6 EXTIRQS Enable extra interrupt sources, such as raster X position
$D06F bits 5-0 RASLINE0 First VIC-II raster line within the physical display

The compare mode is selected by FNRSTCMP, bit 7 of $D07A: clear for physical lines, set for VIC-II lines. FNRST reads back the same bit. There are roughly twice as many physical lines as VIC-II lines in the standard display, so a physical compare can split the screen between two VIC-II lines.

Cooperative raster interrupts

A program that keeps the KERNAL running can still use raster interrupts, by chaining into the KERNAL's handler instead of replacing the hardware vector. In C64 mode this works exactly as on a C64, through the vector at $0314, with the handler ending in a jump to the KERNAL's own handler so that keyboard scanning continues. Chapter 11 gives the equivalent MEGA65-mode vector.

Loading Files Through the Hypervisor

The KERNAL loads files from disk drives and disk images. The Hypervisor can load files straight from the SD card's FAT32 file system, without a disk image, and it works even when a takeover program has switched the KERNAL out. It can also load directly into attic RAM; the KERNAL can too only from ROM 920398, through SETBNK's 28-bit form.

All Hypervisor services follow the same convention: put the service number in A, write it to $D640, and follow with CLV. The CPU may or may not execute the byte after the trap when the service returns, which is why a harmless CLV goes there; NOP also works, but it can act as a prefix to the next instruction, so CLV is safer. On return, carry set means success; carry clear means failure, with the error code in A. Registers not used to return values are preserved, and the trap register exists only in the MEGA65 I/O personality (MEGA65 Book, pp. N-4, N-5). The Book documents Hyppo version 1.2.

The services

A Service Inputs
$2E hyppo_setname Y: page of a zero-terminated ASCII file name
$36 hyppo_loadfile X, Y, Z: bits 7-0, 15-8 and 23-16 of the load address in chip memory
$3E hyppo_loadfile_attic X, Y, Z: bits 7-0, 15-8 and 23-16 of the load address in attic RAM

hyppo_setname has strict requirements: the name must start on a page boundary somewhere in $0000-$7E00, must be shorter than 63 characters, and must end with a zero byte. Only Y is passed, the page number. The name is ASCII, and Hyppo does not convert from PETSCII; it also sets the Hypervisor's transfer area to that page as a side effect (MEGA65 Book, p. N-38).

The load services load to $00ZZYYXX in chip memory or $08ZZYYXX in attic RAM, and can load files up to 16MB, wrapping around at the end of the 16MB range. The commonest failure is error $88, file not found in the current directory (MEGA65 Book, pp. N-27, N-28).

Example: loading level data into bank 4

load    ldy #>fname     ; page of the file name
        lda #$2e        ; hyppo_setname
        sta $d640
        clv
        bcc error

        ldx #$00        ; load address $0040000
        ldy #$00
        ldz #$04
        lda #$36        ; hyppo_loadfile
        sta $d640
        clv
        bcc error
        rts

error   ; A holds the error code, e.g. $88 file not found
        rts

        .align 256      ; the name must start a page (syntax varies)
fname   .text "LEVEL1.BIN", 0

The Hypervisor ignores case when it matches names, but it needs ASCII codes. Many assemblers' text directives produce PETSCII, which swaps the cases: a PETSCII directive such as ACME's !pet turns capitals into codes $C1-$DA, which the Hypervisor will not match. Use a directive that stores text unconverted (mega65-core, Hyppo dos.asm).

The file is loaded exactly as stored. A file saved as a PRG carries a two-byte load address at the front, and the load call places those two bytes in memory too; for raw data it is simpler to save files without them.

Hyppo reads files from the FAT32 file system on the SD cards, in the current directory; it does not read floppy disks or disk images (MEGA65 Book, p. N-38). Hypervisor services have changed between versions, and newer ones may not exist on an older Hypervisor, so a program that depends on them should check the version first with hyppo_getversion, service $00.

Debugging

Machine language fails silently: a wrong address or a missing RTS usually means a hung machine with no message. The MEGA65 offers several tools for finding out what happened, each suited to a different kind of problem.

The built-in monitor

The MONITOR introduced in Chapter 7 is an extended version of the C65's monitor (MEGA65 Book, p. O-6). Beyond assembling and disassembling, it can display and change registers, fill, compare, transfer and search memory, and load and save memory to disk. Because it understands 28-bit addresses, it can inspect attic RAM, colour RAM and the I/O personalities directly. The MEGA65 Book lists every command (MEGA65 Book, p. O-6).

The simplest breakpoint is a BRK instruction. Place one where you want execution to stop, run the program, and the monitor appears with the registers as they were at that point. This works only while the KERNAL and its vectors are in place, because the KERNAL's BRK handler starts the monitor; a takeover program's BRK goes to its own interrupt handler. Replace the BRK with the original instruction afterwards. Check the stack pointer and B in the register display first: a wrong B or a runaway stack is a common cause of crashes.

The border as a probe

For timing problems and code that runs inside interrupts, the oldest trick still works best. An INC $D020 at the start of a routine and a DEC $D020 at the end turn the border a different colour while the routine runs. The height of the coloured band shows how much of the frame the routine takes, and a band that stops moving shows where a program has hung.

The Freezer

Holding RESTORE for about a second and releasing it stops any program and opens the Freezer, which saves the complete machine state. From there you can examine memory in the frozen program, change settings, or resume. This works even when a takeover program has disabled interrupts and switched the KERNAL out, because the Freezer runs under the Hypervisor rather than the KERNAL.

Matrix Mode and remote debugging

Matrix Mode is a debugging console run by a small helper processor in the core, separate from the main CPU; the Hypervisor only switches it on and off (mega65-core, monitor_top.v; MEGA65 Book, monitor appendix). Hold MEGA and press TAB to open it over the running display, and press the same combination again to close it. Because it runs on its own processor, it works even when the program has hung. Its R command, or just RETURN, shows all the CPU registers, including the MAPHI and MAPLO values that no instruction can read, which makes it the quickest way to see what a program has done to the memory map (MEGA65 Book, pp. 14-22, O-14).

Matrix Mode can also step through a program. t1 pauses the CPU in trace mode, and each press of RETURN then runs one instruction and shows the registers; t0 lets the program run again. From core 0.97, interrupts are blocked while stepping with t1, so an IRQ does not take the step into the interrupt handler; t2 pauses with interrupts still allowed. The W command sets a watchpoint that stops the program when a chosen byte is written (MEGA65 Book, monitor appendix; mega65-core PR #848).

On release 0.97 cores, single-stepping or inspecting memory from Matrix Mode while a program runs INQ, DEQ or one of the Q register shift instructions can make the program skip a byte and crash; development cores fix this (mega65-core PR #943). The same console is available over a UART serial or JTAG connection, where cross-development tools on a PC can also load programs and inspect memory.

For larger projects, a PC-based emulator with a debugger, such as Xemu, is often quicker still. Its monitor offers an execution breakpoint and a watchpoint that stops when a chosen byte changes, the fastest way to find out which piece of code is overwriting a variable; Matrix Mode's W command does the same on real hardware (Xemu; MEGA65 Book, monitor appendix). An emulator's timing may not match the hardware in every detail, so timing-critical code should always be checked on real hardware.

Summary

Chapter 9 turns to combining machine language with BASIC 65.