The Disassembly View
You can view the disassembly of the memory with the View | Machine Views | Disassembly menu command:
Besides viewing the disassembly, you can add breakpoints by clicking with the right mouse button to the left of the line address (see address line 0007):
When you click an existing breakpoint with the right mouse button, the IDE removes it.
Currently, you can only add execution breakpoints to the Disassembly view. With interactive commands (see the ones starting with bp-), you can create memory and I/O operations breakpoints.
By default, when you display the disassembly, it uses the current value of the PC (Program Counter) register and disassembles up to 1024 bytes of code.
Turn off the Follow PC option to disassemble the entire memory (or a selected bank). With this option turned off, models supporting memory paging display a second row of options:
Display Options
The Disassembly View provides a few options to display and navigate the memory contents:
- Decimal: Turns on or off the decimal display (off, by default). When turned off, memory addresses, opcodes, and instructions are displayed with decimal numbers; otherwise, they are hexadecimal.
- Sys vars: Names 16-bit data addresses after the machine’s system variables (on, by default). See System Variable Names below.
- ROM labels: Shows the ROM’s annotations — its labels, comments and data regions (on, by default). It appears when the machine’s ROM has annotations. See ROM annotations below.
- Ram: This option (on, by default) allows you to disassemble the RAM’s contents.
- Screen: This option (off, by default) lets you disassemble the screen memory’s ($4000-$5AFF) contents.
- Bank: This option (on, by default) allows you to turn the display of bank labels on or off in front of memory addresses. This option is available only for models that support memory paging.
- Go To: Type an address into this box and press Enter to jump to the particular address. If the particular address is not at a disassembled instruction’s boundary, it will settle at the next instruction. In decimal mode, you can type decimal addresses; otherwise, use hexadecimal addresses.
A little icon (up or down arrow) is before the Go To address box. Unless the machine is paused, this icon is disabled. When the machine is in a paused state, clicking the button jumps to the disassembly at the address pointed by the PC. The direction of the arrow indicates where the PC position can be found relative to the current view.
System Variable Names
Wherever an instruction loads or stores through a literal 16-bit address, and that address is one of the machine’s system variables, the view prints the variable’s name instead of the number:
ld hl,LAST_K
ld (ERR_NR),a
ld sp,RAMTOP
ld (CHANS),hlThe names come from the same table the System Variables panel lists, so each machine
contributes its own — the ZX Spectrum 48K, 128K and +3, the ZX Spectrum Next, the ZX81 and the
ZX80 each have one.
Names are spelled so an assembler would accept them: the ROM manuals print several with a space or a
hyphen (ATTR P, CH-ADD), and every character that is not a letter, a digit or an underscore
becomes _ — so those read ATTR_P and CH_ADD.
Only data operands are named — the 16-bit loads and stores. JP and CALL targets keep their
L-prefixed labels, because the system variable area is not code: an address there matching a jump
target is a coincidence far more often than a fact worth printing. Naming applies to the Z80 and
Z80N machines; the Commodore 64’s 6510 listing shows its addresses unchanged.
The Sys vars switch turns the naming off and on. It is on by default, each view remembers its own setting, and turning it off shows the addresses exactly as they were before.
The same naming appears in the disassembly of a popped-out .NEX bank, with its own Sys vars
switch. There, a label you wrote yourself always wins: your label says something about this
program, while a system variable name only states a fact about the machine.
Branch Verdicts
While a machine is running or paused, the Disassembly View marks every conditional branch it can see with what that branch will do next, worked out from the CPU’s current flags and registers. A narrow column between the T-states and the instruction carries the mark:
This is the end of the ROM’s keyboard scan, and it happens to show every mark a 48K listing can
produce. The execution point is a jr c about to close a loop backwards, and the readout beside it
says where to, which condition decided it, and what that costs. Below: three ret z that will not
be taken, a ret and a ret nz that will return, a call drawn as an out-and-back hook, and two
jr nz jumping forwards.
| Mark | Meaning |
|---|---|
| Arrow pointing up | The branch will jump backwards, to an address at or below its own — a loop closing. |
| Arrow pointing down | The branch will jump forwards, skipping the code between. |
| Out-and-back hook | A CALL or RST that will be taken: control leaves and comes back. |
| Arrow pointing left | A RET or RET cc that will return, leaving through the stack. |
| Dashed arrow pointing right | Control leaves, but the destination cannot be worked out. See below. |
| Straight vertical line | The branch will not be taken; execution carries straight on to the next instruction. |
Five of the six say which way control goes; the hook says something different, that control will
return here afterwards. A CALL therefore looks the same whether its target is above or below —
it is a call either way — while a JP to the same address shows a plain arrow. RST n is CALL n
in a one-byte encoding, so it takes the hook too, and its readout says calls $0008 rather than
naming a direction.
A taken branch is coloured; a fall-through is neutral grey. Falling through is not an error, so it is never marked in red — red and orange belong to breakpoints and to the execution point.
Certain and speculative marks
The mark on the row at the execution point is drawn at full strength, because the flags it was worked out from are the flags the CPU will actually use.
Every other mark is dimmed, and this difference matters. A branch ten instructions below PC is judged against today’s flags, not the flags that will hold when execution finally reaches it — and the code in between will almost certainly change them. Read a dimmed mark as “this is what would happen if the CPU arrived here right now”, not as a prediction.
The execution point readout
The row at the execution point also spells the verdict out in full, after the instruction:
jumps back to $0EFD · NC met (C=0) · 12 TIt names where execution goes next, which condition decided it, the flag value behind that decision, and the T-states the instruction will actually spend — a conditional branch costs a different number of cycles depending on which way it goes, and this is the one it will really take.
In a narrow panel the readout shortens to → $0EFD C=0 12T. Hover it to see the full sentence.
Verdicts appear only when a machine is started. With no machine running there is no CPU state to judge against, so the view looks exactly as it always has — no extra column, no shifted layout. Verdicts are shown for Z80 and Z80N machines only.
Destinations that cannot be worked out
Three cases show a verdict but no destination, on purpose:
JP (C)on a ZX Spectrum Next jumps to an address whose low bits come from reading an I/O port. That read has not happened yet, and performing it just to fill in a debugger display would disturb the machine you are debugging. The readout says so rather than guessing.RET ccaway from the execution point. The return address sits on the stack, and the top of the stack belongs to whatever is executing now, not to the row you are looking at. It is only shown for the row at PC, where it is genuinely that instruction’s return address.RET ccin a banked view. With the 64K View switch off, the view holds one bank rather than the whole address space, so the stack is not part of what is on display.
While the machine is free-running rather than paused, the marks follow the CPU state as it is sampled, a few times a second. Pause or single-step for a verdict that stands still.
Annotations on Every Machine
The Disassembly view is an annotated listing: rows carry names, synopsis comments above them,
end-of-line comments beside them, and ranges marked as data list as data. The annotations are kept
in a file beside the program they describe — game.nex.dis, jetpac.z80.dis, game.p.dis
— and the one in use is the active annotation set. It is chosen for you when a program gets
into the machine:
nex-runuses the.NEXfile’s annotations;zx-snapshotuses<snapshot>.dis;tape-loadof a ZX80 or ZX81 program file uses<file>.dis;- opening a project uses
annotations.disin the project folder (or the file the project’sannotationssetting names).
The file does not have to exist: your first label or comment creates it. For anything else — a
game loaded from a Spectrum tape, say — use ann-new or
ann-open; ann-info says which set
is active.
Right-click a row to annotate it: Label…, Synopsis Comment…, End-of-Line Comment…,
Operand Label…, Mark As Code / Bytes / Words / Skip (and Text on the ZX80 and ZX81),
and Go to Definition. The same keys as in a popped-out .NEX bank work on the row you last
clicked (L for a label, C for an end-of-line comment, Shift+C for a synopsis). The label goes in
the bank the row is in — it follows that bank wherever it is paged — and the
label command does the same from the command prompt.
A row shows one name. Where the address has several — a label from your last build, one from
the annotations, one from the ROM’s — the build’s wins, and the label’s tooltip names where it
came from and lists the others. The same names appear in the Execution History, the Call Stack
(MAIN+12: the routine a return address is in), the Profiler’s routines and the dis command.
On the ZX80 and ZX81, memory appears at more than one address (the 16K RAM again at $8000 and
$C000, the ROM again at $2000). A label is kept for the byte, not the address, so it shows at every
mirror, and a mirror row says (mirror of $4082).
ROM annotations
Calls into the ROM read by name: call SA_BYTES rather than call $04C2. Klive ships annotations
for its ROMs, written for Klive, and finds them by the ROM’s bytes rather than by which ROM slot is
paged: a copy of the 48K ROM loaded from your own file gets them too, and on a ROM Klive does not know
(a modified 48K ROM, say) they apply wherever its bytes still match. ROM labels are set in italics so
your own stand out.
Use bp-set ROM:<name> to break at a ROM label, and ROM:<name> in a breakpoint condition.
Editing a ROM’s annotations
A ROM’s annotations are edited in a working copy, never in the shipped file. Make one with
rom-ann-new, or with Start Editing ROM Annotations… on a
ROM row’s context menu:
rom-ann-new sp48.romFor a ROM Klive ships, the working copy is an exact copy of its shipped annotations, kept in Klive’s
home folder as RomAnnotations/sp48.rom.dis, where it survives an update. For your own ROM file it is
<file>.dis beside it. Once it exists, it replaces the shipped annotations for that ROM: you can
rename, change or remove any shipped label, comment or region, and only the working copy is shown.
Without a working copy, the shipped annotations are shown and a ROM row cannot be edited.
Opening a working copy — from Open ROM Annotations on a ROM row, with
rom-ann-open, or by opening the .rom.dis file — shows
the ROM annotation editor. It finds the ROM by its bytes and lists its pages; Disassembly opens
a page with everything a NEX bank has: select rows, right-click for the annotation menu, or use the
shortcuts (L label, C end-of-line comment, Shift+C synopsis, O operand label, M/R manage
labels/regions, B bank comment). No machine needs to be running. If no ROM matches the file, the
editor says so and shows nothing else. With a working copy in place, ROM rows of the live view are
editable too, and every edit shows wherever ROM names appear.
The editor’s Ready chip says whether the file passes every check a shipped annotation file must
pass (the same as rom-ann-check). New entries chooses
whether what you add is recorded as observed (worked out from the bytes) or manual (an interface
fact from a manual); rom-ann-provenance changes an existing
entry. Next unlabelled steps through the calls and jumps that still have no label.
A working copy is written in exactly the format of Klive’s own ROM annotation files. When it is
ready, it can be copied into src/public/roms/ of a Klive checkout unchanged.
Banked Disassembly Views
When your model supports memory paging, a new switch, 64K View, is displayed in the second header row. It is turned on by default, and the current 64K memory (according to the current memory page settings) is displayed. When you turn this option off, you can choose the bank to display (ROM pages or RAM banks, depending on the machine type) from the Select bank dropdown. The Disassembly View will display only the disassembled contents of that segment, even if that segment is not paged in.
The Offset dropdown lets you select a disassembly offset from one of the 8K page boundaries. This selection may help set a disassembly base address to emulate the page’s location.
Popped-Out .NEX Banks
A bank of a .NEX file opened as its own document shows the same disassembly listing, with a toolbar
of its own. Beside the Go To box is a button that scrolls the listing to the program counter,
the same way the arrow does in the Disassembly View.
It is available only when the machine is paused and the program counter is currently inside this bank. Both halves matter, and neither is fussiness:
- A running machine’s program counter has moved on by the time it is read, so the row it scrolled to would be an arbitrary instruction rather than the one about to execute.
- A pop-out shows one 16K bank. The program spends most of its time in other banks, and a bank the processor is not in has no row to go to at all — so the button is off far more often here than in the Disassembly View, and that is the honest answer rather than a fault.
The header’s Bank readout tells you which case you are in: it ends with · PC exactly when the
program counter is in this bank. The button lands on the row carrying the execution-point marker,
which follows the listing’s own numbering — so it is right even when the Offset dropdown
lists the bank at an address it is not paged at.
Byte formats
When you mark a range as Bytes, the region dialog also asks how to write it:
- 4 bytes, hex (the default):
.defb $3C, $42, $81, $FF - 2 bytes, hex: two values to a row
- 1 byte, hex: one value to a row
- 1 byte, binary: one value to a row with all eight bits,
.defb %00111100— handy for bitmaps, masks and flag tables
The dialog opens on the format the range already has, and the Manage Regions list shows any format other than the default beside the type. Binary rows stay binary when you switch the listing to decimal, and they export to source that the Klive assembler reads back.
Copper Lists and DMA Programs
When a .NEX file has an annotation file, you can tell the listing what a range of a bank holds. The
Annotations menu and the region dialogs offer Disassembly, Bytes, Words, Skip, and
two kinds for ZX Spectrum Next hardware programs:
- Copper lists the range as a Copper list, one
.copperinstruction per two-byte word, with what each instruction does in the comment column. The range must have an even number of bytes. - DMA lists the range as a zxnDMA program — the bytes a program sends to port
$6B— one.dmacommand per row.
The rows use the same .copper and
.dma pragmas the assembler accepts, and every row assembles back to the
exact bytes it shows:
.copper wait 96, 3 ; line 96 · x 24
.copper move $41, $E0 ; Palette Value (8 bit) ← $E0
.copper halt ; halt until the next restart
.dma reset ; WR6: reset
.dma wr0 a_to_b, transfer, $4000, $0020
.dma wr4 continuous, $4800 ; WR4: continuous mode, port B $4800A DMA range is read the way the DMA reads it: the bits of each command byte decide how many bytes
follow. A command the pragma cannot spell is shown as .dma cmd or .defb, with its meaning in the
comment, and a command cut off by the end of the range says how many bytes it still expects. A label
on an address or a length inside a command splits it into the
runtime-patching form, with the labelled field
as a .defw row of its own.
$00 is a valid DMA command, so a range that runs into zero-filled memory lists many
.dma wr2 memory, decrement rows. When a DMA range ends in a long run of zeros, the region dialogs
offer to trim it.
The annotation file stores both kinds as byte regions with a decode key, so an older Klive opens the
file and shows these ranges as plain .defb rows.
Machine-Specific Memory Partitions
The bank column and the bank chooser name partitions exactly as the rest of the IDE does. See Machine-Specific Memory Partitions in the Memory View for the names each machine uses.



