TRS-80 DOS - VTOS 4.0 for the Model I - SYS9/SYS Extended Debugger Disassembled

Page Customization

Page Index

SYS9/SYS - Extended Debugger Overlay


Other Navigation

Introduction:

VTOS 4.0 SYS9/SYS Disassembly - Extended DEBUG Facility (Model I)

SYS9/SYS is the extended DEBUG facility overlay for VTOS 4.0 on the TRS-80 Model I. It is loaded into high memory when the user issues the DEBUG EXT command (setting the EXT flag). Once loaded, SYS9 augments the resident SYS5/SYS debugger with a full set of advanced commands that are not available in the standard debugger. According to the VTOS manual, the extended facility is loaded into high memory, meaning SYS9 installs itself above the current high-memory boundary at 4049H - it subtracts its own length from the boundary and copies its code there, making it permanently resident until the next cold boot.

SYS9 occupies addresses 4E00H-51F4H in its load image (the standard VTOS overlay slot). The overlay is structured around a central RST 28H dispatch table at 4E26H-4E2CH. The RST 28H SVC mechanism calls into SYS9 with a command character in Register A; SYS9 then dispatches to the appropriate handler by comparing A against a chain of command byte values. All extended commands are therefore accessible through the standard VTOS overlay call mechanism already used by SYS1-SYS8.

The extended commands provided by SYS9 are: B (Block Move - copy a block of memory), E (Enter data into memory byte by byte), F (Fill a memory range with a single byte value), J (Jump over the next instruction byte by incrementing the saved PC), L (Locate the next occurrence of a data byte in memory), N (position to the Next relative block or instruction), O (normal return to VTOS, exiting the debugger), P (Print memory in both hex and ASCII format), Q (Query an input port or send a data byte to an output port), T (Type ASCII data into memory interactively), V (Verify two memory blocks are identical), W (search memory for a Word Address), and the unnamed disk transfer command (transfer a cylinder or sector to or from disk). The P command also contains the shared hex-output subroutines used by several other extended commands.

SYS9 interacts closely with SYS5/SYS (the resident debugger), sharing the same memory work variables at 4060H, 4062H, 4063H, and 405FH. It also calls the SYS0 drive-select routine at 478FH and the disk I/O routines at 4763H, 4768H, and 4777H for the disk transfer command. The hex parameter input routines at 518AH and 51A3H (at the tail of the overlay) are shared entry points used by nearly every extended command.

Variables and Work Areas

Address Range
Size
Purpose
4049H-404AH
2 bytes
Detected RAM top address. SYS9 subtracts 03CDH from this value and stores the result back, reducing the high-memory boundary to accommodate its own installation. Also used as the upper-address limit for the B (Block Move) command length calculation.
405FH
1 byte
High byte of a 16-bit work register used by the W (Word search) and disk commands. Stores the high byte of the word being searched for (W command) or the computed sectors-per-track value (disk command).
4060H-4061H
2 bytes
Current address pointer / second parameter work register. Used by B, E, F, L, N, T, V, W, and disk commands as the running destination address, the second address parameter, or the current scan pointer.
4062H
1 byte
Low byte of a 16-bit work register / byte value work cell. Used by the L command as the byte to locate, by the W command as the low byte of the word to find, and by the disk command as the sector register cache and sector counter.
4063H-4064H
2 bytes
Display address / first parameter work register. Used by B, V, and W commands as the first (source) address parameter, and by L and N commands as the page-aligned display address to update after the search.
407BH-407CH
2 bytes
Saved program counter (resume address) in the SYS5 register save area. The J (Jump over) command increments this value by 1 to skip the next instruction byte.
4310H-4311H
2 bytes
Address register A - the primary address parameter used by the B command installation sequence. Stores the source address for the block move. Updated after the B command to reflect the new high-memory top after SYS9 installs itself.
5135H-5136H
2 bytes
Address register B - work cell within the SYS9 overlay's own address space. Stores the prior value of the high-memory pointer (from 4310H) before SYS9 adjusts it, used as the destination of the LDIR during self-installation.

Major Routines

AddressName and Purpose
4E00HSYS9 Installation Entry Point
Called when the user issues DEBUG EXT. Subtracts 03CDH (the overlay length) from the high-memory pointer at 4049H, copies the SYS9 code into the newly allocated high-memory block via LDIR from 4E27H, and updates 4310H with the new top address. On return the extended debugger is permanently resident.
4E26HRST 28H Dispatch Stub
The single byte EFH (RST 28H) followed immediately by the command dispatch table. The VTOS overlay dispatcher arrives here with the command character in A and chains through the comparison sequence to reach the correct handler.
4E2DHO Command - Return to VTOS
Calls the hex input routine to consume any trailing input, then jumps to the DOS READY exit at 402DH, returning control from the debugger to the VTOS command prompt.
4E38HB Command - Block Move
Parses up to three hex parameters (source address, destination address, byte count). Loads the three values into HL, DE, and BC respectively and executes LDIR to copy the block. Updates 4060H with the final DE value after the transfer.
4E84HE Command - Enter Data into Memory
Parses a starting address, then enters an interactive loop: displays the current address and its current byte value, accepts a new hex value, stores it, and advances to the next address. Continues until the Carry flag signals end of input.
4EB7HF Command - Fill Memory
Parses start address, end address, and fill byte. Repeatedly stores the fill byte at the current address, increments the address, and loops until the address would exceed the end address (16-bit compare via SBC HL,BC). Returns when the fill range is exhausted.
4ED7HJ Command - Jump Over Next Instruction Byte
Loads the saved PC from 407BH, increments it by 1, and stores it back. This advances the resume address by one byte without executing it, skipping a single instruction byte in the suspended program.
4EE3HL Command - Locate Byte in Memory
Searches forward from the current address in 4060H for the next occurrence of a target byte. Optionally accepts a new starting address and target byte from the user. Uses the Z80 CPIR instruction to scan memory. On a match, page-aligns the found address and stores it to 4063H for the display engine.
4F2AHN Command - Position to Next Relative Block
Reads the current address from 4060H, reads the length byte at (address+1), and advances the address pointer past the current load block or relative instruction. Page-aligns the resulting address and updates 4063H.
4F4AHQ Command - Query/Output Port
Parses a port number into C. If only one parameter is given, reads the port via IN A,(C) and displays the result as hex. If a second parameter (data byte) is also given, sends it to the port via OUT (C),L. Covers both the Qii (input) and Qoo,dd (output) forms from the manual.
4F6EHT Command - Type ASCII Data into Memory
Parses an optional starting address, then enters an interactive loop: displays the current byte's ASCII representation, accepts a new character from the keyboard via the hex input engine, stores it, and advances. Skips storage (but still advances) when the input is a space character (20H). Loops until Carry is set by the input routine.
4FADHV Command - Verify Memory Blocks
Parses source address, destination address, and optional byte count. Compares the two blocks byte by byte using a manual loop (LD A,(DE) / CP A,(HL)). On a mismatch, advances both pointers and continues. On full match, returns with both pointers updated to reflect the end of the compared range.
5008HW Command - Search for Word Address
Parses a starting address and a 16-bit word value to find. Searches memory using CPIR for the low byte and then checks the following byte for the high byte match. On a 2-byte match, page-aligns and stores the address to 4063H.
505EHDisk Transfer Command
Handles the extended debugger disk I/O command: d,[c],[s],o,addr,[lth]. Parses the drive number, selects the drive via CALL 478FH, computes the sectors-per-track from the drive parameter block at IY+07H, then parses cylinder, sector, memory address, and length. Dispatches to the appropriate SYS0 read (4777H / 4763H) or write (4768H) routine based on the operation character (R/W/*).
5132HP Command - Print Memory (Hex and ASCII)
Parses start and end addresses. Outputs memory as a formatted hex dump: each line begins with the 4-digit hex address, followed by 16 bytes in hex (grouped in fours with spacing), then a blank column separator, then the same 16 bytes as ASCII characters (non-printable bytes displayed as a period). Continues until the end address is reached or exceeded.
518AHHex Input with Separator Detection
Shared input engine. Calls the RST 28H dispatcher to read a character, then calls 51A3H to accumulate hex digits into HL. Returns Z set if a separator (comma, space, or CR) was found; Carry set on error or BREAK. Used by nearly every extended command to parse parameters.
51A3HHex Nibble Accumulator
Reads one byte from (HL), converts the low nibble to ASCII using the ADD 90H / DAA / ADC 40H / DAA technique in reverse (as a decoder), accumulates the result into HL by shifting left 4 and ORing the new nibble, then INCrements HL. Also serves as the shared output-byte-as-hex entry point for the P command's hex dump loop.

Cross-Reference Notes

SYS9 is called by SYS5/SYS (the resident debugger at 4E00H of its own overlay slot) when the user activates the extended DEBUG facility. SYS9 itself calls the following SYS0 routines: 402DH (DOS READY exit), 478FH (drive select - sets IY to drive parameter block), 4763H (read sector - W operation), 4768H (write sector - * operation), and 4777H (read sector - R operation). SYS9 also calls ROM routines at 0033H (character output) and 003BH (printer/alternate character output, used by the P command's hex dump).

Disassembly:

4E00H - SYS9 Installation: Copy Extended Debugger to High Memory

Entry point called when DEBUG EXT is issued. Allocates space below the high-memory boundary at 4049H, copies the entire SYS9 overlay code into that space via LDIR, and updates the high-memory pointer so VTOS knows the region is in use.

4E00
LD A,40H 3E 40
Load Register A with 40H. This is the command dispatch selector byte that will be passed to the RST 28H overlay mechanism. 40H is the code for the extended debugger's own parameter-parsing entry point within SYS9's dispatch table.
4E02
GOSUB to the RST 28H dispatch stub at 4E26H. With A=40H, the dispatch table will match the 40H entry, set the Zero flag, and return immediately. This call effectively initialises any parameter state and confirms the overlay mechanism is active before beginning the installation sequence.
4E05
OR A,A B7
Test Register A against itself to set flags from its current value. If A is zero (Z flag set) then no valid response was returned from the dispatch call.
4E06
RET Z C8
If the Z flag is set (A was zero, meaning the dispatch call signalled no action required), RETURN immediately without installing SYS9. This is a guard that prevents re-installation if SYS9 is already resident.

Installation Sequence Begin
The following instructions calculate the destination address for SYS9 in high memory, adjust the high-memory boundary pointer, and copy SYS9's code into place using LDIR.

4E07
LD HL,(4310H) 2A 10 43
Load Register Pair HL with the 16-bit value stored at 4310H (the current high-memory address register A, tracking the top of the installed code area). This is the address below which SYS9 will copy itself.
4E0A
LD (5135H),HL 22 35 51
Store the current high-memory top address (in HL) to 5135H (the address register B work cell within SYS9's own overlay space). This saves the destination address to which SYS9 will be copied.
4E0D
LD HL,(4049H) 2A 49 40
Load Register Pair HL with the 16-bit value at 4049H (the detected RAM top / current high-memory boundary). This is the upper limit of available user RAM, below which SYS9 will be installed.
4E10
LD BC,03CDH 01 CD 03
Load Register Pair BC with 03CDH (decimal 973). This is the byte length of the SYS9 overlay. The overlay runs from 4E27H (the first byte of actual code after the RST 28H byte at 4E26H) for 03CDH bytes, placing the end of the image at approximately 51F4H.
4E13
XOR A,A AF
Set Register A to zero and clear all flags, specifically clearing the Carry flag before the 16-bit subtract that follows (SBC HL,BC requires Carry to be clear for a plain subtraction).
4E14
SBC HL,BC ED 42
Subtract BC (03CDH, the overlay length) from HL (the current RAM top at 4049H). The result is the new high-memory boundary: the address at which the SYS9 code block will begin after installation. The Carry flag is clear from the XOR A preceding this instruction, so this performs a pure 16-bit subtraction.
4E16
LD (4049H),HL 22 49 40
Store the new (lowered) high-memory boundary back to 4049H. This commits the RAM reservation: VTOS now recognises the block from HL to the old top as belonging to the extended debugger and will not allocate it for other purposes.
4E19
INC HL 23
INCrement HL by 1. After reserving the block, HL points to the first byte of the reserved area. This INC adjusts HL to become the destination address for the LDIR copy (the byte following the boundary marker).
4E1A
PUSH HL E5
Save the destination address (the start of the reserved high-memory block) onto the stack. It will be popped into HL after the LDIR to update 4310H.
4E1B
EX DE,HL EB
Exchange Register Pairs DE and HL. HL (the destination address) moves into DE, ready for use as the LDIR destination. DE's previous content (undefined at this point) moves into HL.
4E1C
LD HL,4E27H 21 27 4E
Point Register Pair HL to 4E27H, the first byte of SYS9's code after the RST 28H dispatch byte. This is the source address for the LDIR block copy.
4E1F
LDIR ED B0
Block copy: source HL (4E27H, start of SYS9 code), destination DE (computed high-memory address), byte count BC (03CDH = 973 bytes). INCrements both HL and DE after each byte, DECrements BC. Terminates when BC reaches zero. This physically installs the SYS9 extended debugger into high memory.
4E21
POP HL E1
Restore Register Pair HL with the destination address that was saved before the LDIR. HL now holds the start address of the newly installed SYS9 block in high memory.
4E22
LD (4310H),HL 22 10 43
Store the high-memory installation address to 4310H (address register A), updating the pointer to reflect where the extended debugger now lives. Future invocations of extended commands will be dispatched through this installed copy.
4E25
RET C9
RETURN to the caller. The SYS9 extended debugger is now installed in high memory and all 13 extended commands are available for use.

4E26H - RST 28H Dispatch Stub and Command Dispatch Entry

The single RST 28H byte (EFH) at 4E26H is the SVC call instruction. The VTOS overlay dispatcher calls into SYS9 here. Execution falls through immediately to the dispatch sequence beginning at 4E27H, which compares Register A against each extended command code in turn and branches to the appropriate handler.

4E26
RST 28H EF
Issue a RST 28H (Restart at address 0028H). In the VTOS environment, the vector at 0028H jumps to 400CH (the overlay dispatcher), which loads the SYS9 overlay and re-enters here with the command character in Register A. The byte immediately following (FEH at 4E27H) is the first instruction of the dispatch table and also serves as the data byte consumed by the RST 28H inline-parameter mechanism.
4E27
CP A,40H FE 40
Compare Register A against 40H. A value of 40H is the base entry / address-parse mode code: it signals that no extended command is being dispatched, and the routine should simply return success to the installation sequence at 4E00H.
4E29
If the NZ flag is set (A is not 40H), JUMP to 4E2DH to continue dispatch checking for the remaining command codes.
4E2B
XOR A,A AF
Set Register A to zero and clear all flags, including the Zero flag. This sets Z=1 (since A is now zero) as the success return code for the 40H base-mode call.
4E2C
RET C9
RETURN with Z flag set (A=0). The installation sequence at 4E00H will see Z set, clear A is zero, and fall through to check OR A,A correctly (the RET Z at 4E06H fires on this Z). Wait - because A=0 and the caller does OR A,A / RET Z, this causes immediate return from the installation sequence. The 40H code is therefore a no-op / not-installed check path.

4E2DH - O Command: Return to VTOS

Handles the O (capital letter O) extended DEBUG command: "normal return to VTOS". Flushes any pending input from the terminal and then jumps to the VTOS DOS READY exit, cleanly terminating the debug session and returning the user to the VTOS command prompt.

4E2D
CP A,4FH FE 4F
Compare Register A against 4FH (ASCII O). If A equals 4FH the O command has been requested. If the NZ flag is set, the command character is something other than O, so dispatch continues.
4E2F
If the NZ flag is set (A is not O), JUMP to 4E38H to continue dispatch checking for the B command and beyond.
4E31
GOSUB to the hex input routine at 518AH. This consumes any trailing characters the user may have typed after the O command (flushing the input buffer). The routine returns with the Carry flag set if a BREAK was pressed or with no further input pending.
4E34
RET NC D0
If the No Carry flag is set (input was received without BREAK), RETURN to the dispatcher. This handles an edge case where the user typed extra characters after O - the routine absorbs one token and returns to let the caller decide.
4E35
JUMP to the DOS READY / No-Error Exit at 402DH in SYS0. This exits the extended debugger cleanly, restores the normal VTOS command environment, and displays the "VTOS READY" prompt.

4E38H - B Command: Block Move (Three-Parameter Version)

Handles the B extended DEBUG command: "BLOCK MOVE l bytes of memory from s to d". Parses up to three hex parameters - source address, destination address, and byte count - then performs an LDIR block copy from source to destination for the specified number of bytes.

4E38
CP A,42H FE 42
Compare Register A against 42H (ASCII B). If A equals 42H the B command has been requested.
4E3A
If the NZ flag is set (A is not B), JUMP to 4E84H to continue dispatch checking for the E command.

Parse First Parameter: Source Address
4063H holds the current display address and serves as the first parameter register for the B command. The routine reads any user-typed address into 4063H, falling through to print a comma separator if the address was entered (Z not set from CALL 51A3H).

4E3C
LD HL,(4063H) 2A 63 40
Load Register Pair HL with the 16-bit value at 4063H (the current display/source address register). This pre-loads HL with the default source address before calling the accumulator, so if the user types no address the current display address is used.
4E3F
GOSUB to the hex nibble accumulator at 51A3H to read and accumulate a hex digit from the input stream into HL. Returns Carry set if input ended (BREAK or separator seen at first char), Zero set if the separator immediately follows (no digits entered).
4E42
RET C D8
If the Carry flag is set (BREAK pressed or input error), RETURN immediately without performing the block move.
4E43
LD (4063H),HL 22 63 40
Store the parsed source address (in HL) to 4063H (the display/source address register). This commits the first parameter.
4E46
If the NZ flag is set (at least one hex digit was entered for the source address), JUMP to 4E50H to skip displaying the echoed address - the user typed it, so no echo is needed. If Z is set (no address typed), fall through to display the current address as a prompt.
4E48
GOSUB to the 4-digit hex display routine at 51D4H to print the source address (currently in HL, reflecting 4063H) on screen as a prompt for the user to confirm or override.
4E4B
LD A,2CH 3E 2C
Load Register A with 2CH (ASCII comma ,), the parameter separator character.
4E4D
GOSUB to ROM routine at 0033H to display the comma separator character on screen.

Parse Second Parameter: Destination Address
4060H holds the destination address for the B command.

4E50
LD HL,(4060H) 2A 60 40
Load Register Pair HL with the 16-bit value at 4060H (the current destination address register). This pre-loads the default destination before parsing the second parameter.
4E53
GOSUB to 51A3H to read and accumulate additional hex digits for the destination address into HL.
4E56
LD (4060H),HL 22 60 40
Store the parsed destination address (in HL) to 4060H.
4E59
If the NZ flag is set (digits were entered), JUMP to 4E65H to skip echoing the destination address.
4E5B
PUSH AF F5
Save Register Pair AF (specifically the flags) onto the stack before the display call modifies them.
4E5C
GOSUB to 51D4H to display the destination address (in HL) on screen as a prompt.
4E5F
LD A,2CH 3E 2C
Load Register A with 2CH (ASCII comma ,).
4E61
GOSUB to ROM 0033H to display the comma separator.
4E64
POP AF F1
Restore Register Pair AF from the stack, recovering the flags from before the display calls.

Parse Third Parameter: Byte Count
The default byte count is 0100H (256 bytes) if the user provides no third parameter. If Carry is set from the previous accumulator call the third-parameter parse is skipped.

4E65
LD HL,0100H 21 00 01
Load Register Pair HL with 0100H (256 decimal) as the default byte count for the block move. If the user does not type a count, 256 bytes will be transferred.
4E68
If the Carry flag is set (the previous separator was a CR, indicating end of all parameters), JUMP to 4E6FH to use the default count of 0100H and proceed directly to the LDIR.
4E6A
GOSUB to 51A3H to read and accumulate hex digits for the byte count into HL. If the user provides a count, HL will hold that value on return.
4E6D
If the NZ flag is set (digits were entered for the count), JUMP to 4E74H to skip echoing the default count.
4E6F
PUSH HL E5
Save the byte count (in HL, either 0100H default or user-entered value) onto the stack before the display call.
4E70
GOSUB to 51D4H to display the byte count (in HL) on screen so the user can see what count will be used.
4E73
POP HL E1
Restore the byte count from the stack back into HL.

Execute Block Move
HL now holds the byte count. The source address is in 4063H and the destination is in 4060H. These are loaded into the appropriate register pairs for LDIR.

4E74
LD B,H 44
Load Register B with the high byte of HL (the high byte of the block move byte count).
4E75
LD C,L 4D
Load Register C with the low byte of HL (the low byte of the byte count). BC now holds the full 16-bit byte count for LDIR.
4E76
LD HL,(4063H) 2A 63 40
Load Register Pair HL with the source address from 4063H. HL is the LDIR source pointer.
4E79
LD DE,(4060H) ED 5B 60 40
Load Register Pair DE with the destination address from 4060H. DE is the LDIR destination pointer.
4E7D
LDIR ED B0
Block copy: source HL (from 4063H), destination DE (from 4060H), byte count BC (user-specified or default 256). Copies BC bytes from the source to the destination, INCrementing both HL and DE after each byte.
4E7F
LD (4060H),DE ED 53 60 40
Store the final DE value (the address one byte past the last byte written) back to 4060H. This updates the destination address pointer to the end of the transferred block, ready for a subsequent command.
4E83
RET C9
RETURN. The block move is complete.

4E84H - E Command: Enter Data into Memory

Handles the E extended DEBUG command: "ENTER data into memory". Accepts a starting address, then enters an interactive loop that displays each memory byte in turn (as its address and current value), waits for a new hex value from the user, stores it, and advances to the next address.

4E84
CP A,45H FE 45
Compare Register A against 45H (ASCII E). If A equals 45H the E command has been requested.
4E86
If the NZ flag is set (A is not E), JUMP to 4EB7H to continue dispatch checking for the F command.

Parse Starting Address
4060H holds the current memory address pointer. If the user types an address it is stored here; otherwise the current value is used as the default.

4E88
LD HL,(4060H) 2A 60 40
Load Register Pair HL with the current address from 4060H as the default starting address for memory entry.
4E8B
GOSUB to 51A3H to accumulate a hex address typed by the user into HL.
4E8E
LD (4060H),HL 22 60 40
Store the parsed starting address to 4060H.
4E91
RET C D8
If the Carry flag is set (BREAK or error), RETURN immediately.
4E92
If the NZ flag is set (an address was entered), JUMP to 4E9AH to skip the echo of the address (the user just typed it).
4E94
GOSUB to 51D4H to display the current address (HL) on screen as a prompt.
4E97
GOSUB to 51F2H to print a space character as a separator between the displayed address and the value prompt.

Loop Start - Enter Data Loop
Each iteration of this loop displays the current address, displays the byte currently at that address as a dash-prefix prompt, waits for the user to type a new hex value, stores it at the current address, and advances HL to the next address. The loop continues until BREAK is pressed.

4E9A
LD A,1EH 3E 1E
Load Register A with 1EH (ASCII RS - Record Separator / cursor-right control character). In the VTOS video display system this character positions the cursor at the start of a new display field.
4E9C
GOSUB to ROM 0033H to output the 1EH cursor-control character.
4E9F
GOSUB to the display-byte-from-memory routine at 51D0H. This reads the byte at the current address (HL), displays it as a 2-digit hex value on screen, and INCrements HL. The displayed value is the current content of the memory byte being offered for modification.
4EA2
DEC HL 2B
DECrement HL by 1, stepping back to the same address that was just displayed. The 51D0H routine left HL pointing one byte past the displayed byte; this restores HL to point at the current target byte.
4EA3
LD A,2DH 3E 2D
Load Register A with 2DH (ASCII hyphen/dash -) as a visual prompt indicating the user can overtype the displayed value.
4EA5
GOSUB to ROM 0033H to display the dash prompt character.
4EA8
EX DE,HL EB
Exchange Register Pairs DE and HL, moving the current memory address into DE so it is preserved while HL is used as the hex input accumulator by 51A3H.
4EA9
GOSUB to 51A3H to read and accumulate a hex byte value typed by the user into HL (the new byte to store at the current address).
4EAC
EX DE,HL EB
Restore Register Pairs: HL gets the memory address back (from DE), DE gets the newly entered value (from HL, where 51A3H stored it). After this exchange HL points to the target memory location and DE contains the new byte in its low byte (L).
4EAD
If the Z flag is set (the user pressed Enter/CR without typing a value - keeping the existing byte), JUMP to 4EB0H to skip the store instruction and leave the byte unchanged.
4EAF
LD (HL),E 73
Store the new byte value (in Register E, the low byte of the entered value) to the address pointed to by HL. This writes the user's new value into the target memory location.
4EB0
RET C D8
If the Carry flag is set (BREAK was pressed during input), RETURN and terminate the enter-data session.
4EB1
INC HL 23
INCrement HL by 1, advancing the address pointer to the next memory byte.
4EB2
LD (4060H),HL 22 60 40
Store the updated address to 4060H so it persists for the next iteration.
4EB5
Loop End
LOOP BACK to 4E9AH to display the next address, show its current byte, and prompt for another entry. This loop continues byte by byte until BREAK is pressed.

4EB7H - F Command: Fill Memory Range with Byte Value

Handles the F extended DEBUG command: "FILL memory from aaaa to bbbb, inclusively, with the data byte cc". Parses start address, end address, and fill byte, then stores the fill byte at every address from start through end.

4EB7
CP A,46H FE 46
Compare Register A against 46H (ASCII F). If A equals 46H the F command has been requested.
4EB9
If the NZ flag is set (A is not F), JUMP to 4ED7H to continue dispatch checking for the J command.
4EBB
GOSUB to 51A3H to parse the start address (first parameter) into HL.
4EBE
RET Z C8
If the Z flag is set (no parameter entered), RETURN immediately - the F command requires all three parameters.
4EBF
PUSH HL E5
Save the start address (in HL) onto the stack.
4EC0
GOSUB to 51A3H to parse the end address (second parameter) into HL.
4EC3
EX (SP),HL E3
Exchange HL with the top of the stack. After this: HL holds the start address (retrieved from the stack), and the top of the stack now holds the end address (formerly in HL). This efficiently swaps the two parsed addresses.
4EC4
POP BC C1
Pop the end address from the stack into BC. Now HL = start address, BC = end address.
4EC5
RET Z C8
If the Z flag is set (no end address was entered), RETURN immediately.
4EC6
PUSH HL E5
Save the start address (in HL) onto the stack again for use after the fill-byte is parsed.
4EC7
GOSUB to 51A3H to parse the fill byte (third parameter) into HL.
4ECA
LD E,L 5D
Load Register E with the low byte of HL (the parsed fill byte value). Register E will hold the fill byte for the store loop.
4ECB
POP HL E1
Restore the start address from the stack into HL. Now HL = start address, BC = end address, E = fill byte.
4ECC
RET Z C8
If the Z flag is set (no fill byte was entered), RETURN immediately.

Loop Start - Fill Loop
Each iteration stores E at (HL), INCrements HL, and checks whether HL has passed BC (the end address) using a 16-bit subtraction. The loop runs from start through end inclusive.

4ECD
XOR A,A AF
Set Register A to zero and clear all flags (especially Carry) before the 16-bit SBC that follows.
4ECE
PUSH HL E5
Save the current fill address (HL) onto the stack before the comparison destroys it.
4ECF
SBC HL,BC ED 42
Subtract BC (end address) from HL (current address). If the Carry flag is clear after this subtraction, HL was greater than or equal to BC, meaning the current address has passed the end address - the fill is done.
4ED1
POP HL E1
Restore the current fill address into HL (the SBC result is discarded; only the Carry flag is used).
4ED2
RET NC D0
If the No Carry flag is set (current address >= end address), RETURN - the entire range has been filled.
4ED3
LD (HL),E 73
Store the fill byte (in Register E) to the current address (HL). This writes the fill value into memory.
4ED4
INC HL 23
INCrement HL to advance to the next address to fill.
4ED5
Loop End
LOOP BACK to 4ECDH to test the new address against the end and store the next fill byte.

4ED7H - J Command: Jump Over Next Instruction Byte

Handles the J extended DEBUG command: "Jump over the next instruction byte (increment PC by 1)". Simply increments the saved program counter in the SYS5 register save area at 407BH, advancing the resume point past one byte without executing it.

4ED7
CP A,4AH FE 4A
Compare Register A against 4AH (ASCII J). If A equals 4AH the J command has been requested.
4ED9
If the NZ flag is set (A is not J), JUMP to 4EE3H to continue dispatch for the L command.
4EDB
LD HL,(407BH) 2A 7B 40
Load Register Pair HL with the 16-bit value at 407BH (the saved program counter in the SYS5 register save area at 407BH-407CH). This is the address at which the suspended user program will resume execution.
4EDE
INC HL 23
INCrement HL by 1, advancing the saved PC by one byte. This skips exactly one byte of the instruction stream at the resume point.
4EDF
LD (407BH),HL 22 7B 40
Store the incremented PC back to 407BH, committing the skip. The next time the debugger resumes execution (via the G command), the user program will start one byte later than it would have without the J command.
4EE2
RET C9
RETURN. The J command is complete.

4EE3H - L Command: Locate Next Occurrence of Data Byte

Handles the L extended DEBUG command: "LOCATE the next occurrence of the data byte dd, optionally starting at address aaaa". Searches forward in memory from the current address for the first byte matching the target value, using the Z80 CPIR instruction. On a match, page-aligns the found address and stores it to the display address register 4063H.

4EE3
CP A,4CH FE 4C
Compare Register A against 4CH (ASCII L). If A equals 4CH the L command has been requested.
4EE5
If the NZ flag is set (A is not L), JUMP to 4F2AH to continue dispatch for the N command.

Parse Optional Starting Address
4060H holds the current address pointer. If the user provides a new starting address it is stored here; otherwise the search begins from the current 4060H value.

4EE7
LD HL,(4060H) 2A 60 40
Load Register Pair HL with the current address from 4060H as the default search starting address.
4EEA
INC HL 23
INCrement HL by 1, advancing past the current address so the search begins at the next byte (the L command locates the next occurrence, not the current one).
4EEB
GOSUB to 51A3H to optionally accumulate a user-supplied starting address into HL.
4EEE
LD (4060H),HL 22 60 40
Store the (potentially updated) starting address to 4060H.
4EF1
If the NZ flag is set (a starting address was entered), JUMP to 4F01H to skip echoing the address and loading the default target byte.
4EF3
PUSH AF F5
Save Register Pair AF (flags) before the display call.
4EF4
GOSUB to 51D4H to display the starting address on screen.
4EF7
LD A,2CH 3E 2C
Load Register A with 2CH (ASCII comma).
4EF9
GOSUB to ROM 0033H to display the comma separator between the address and the target byte prompt.
4EFC
POP AF F1
Restore Register Pair AF.
4EFD
LD A,(4062H) 3A 62 40
Load Register A with the byte at 4062H (the previously used search target byte). This loads the default target byte, which was set by the previous L or W command (or is zero at initialisation).
4F00
LD L,A 6F
Load Register L with the default target byte (from A). HL now contains the default target in its low byte, ready to be overwritten by 51A3H if the user types a new value.

Parse Optional Target Byte
The user may type a new byte value to locate. If no byte is entered, the previous target byte (from 4062H) is used.

4F01
If the Carry flag is set (input already ended), JUMP to 4F0EH to proceed with the current target byte without attempting to parse another parameter.
4F03
GOSUB to 51A3H to optionally accumulate a new target byte typed by the user into HL.
4F06
If the Z flag is set (no new byte entered), JUMP to 4F0EH to use the existing target byte in L.
4F08
LD A,L 7D
Load Register A with the new target byte (low byte of HL, as parsed by 51A3H).
4F09
LD (4062H),A 32 62 40
Store the new target byte to 4062H, saving it for future L commands as the new default.
4F0C
JUMP to 4F12H to proceed with the CPIR search using the newly stored target byte.
4F0E
LD A,L 7D
Load Register A with the target byte from L (the pre-loaded default from 4062H). This is the path taken when the user enters no new target byte.
4F0F
GOSUB to the hex byte output routine at 51D9H to display the target byte (in A) on screen so the user knows which byte is being searched for.

Execute Memory Search via CPIR
CPIR searches forward from (HL), comparing each byte to A, decrementing BC after each comparison. With BC=0000H, the instruction searches through the entire 64K address space (wrapping around) until a match is found (Z set) or BC wraps to 0 (NZ).

4F12
LD HL,(4060H) 2A 60 40
Load Register Pair HL with the search starting address from 4060H.
4F15
LD A,(4062H) 3A 62 40
Load Register A with the target byte from 4062H. A is the comparison value used by CPIR.
4F18
LD BC,0000H 01 00 00
Load Register Pair BC with 0000H. For CPIR, a count of 0000H causes the instruction to scan 65536 bytes (the full 16-bit address space), wrapping around if necessary - ensuring the entire memory is searched.
4F1B
CPIR ED B1
Compare and Increment Repeat: compare A against (HL), INCrement HL, DECrement BC; repeat while BC != 0 and (HL) != A. On termination, Z flag is set if A was found at (HL-1), NZ if the full 64K was scanned with no match. HL points one byte past the matching byte on a successful find.
4F1D
RET NZ C0
If the NZ flag is set (no match found in the 64K scan), RETURN without updating the display address.

Match Found - Compute Page-Aligned Display Address
HL points one byte past the match. The address is adjusted back one byte, then its low byte is page-aligned (low 6 bits cleared) to produce a 64-byte-aligned display address for the memory dump window.

4F1E
DEC HL 2B
DECrement HL by 1, stepping back to the actual address where the match was found (CPIR left HL one past the match).
4F1F
LD (4060H),HL 22 60 40
Store the exact match address to 4060H, updating the current address pointer to the found byte.
4F22
LD A,L 7D
Load Register A with the low byte of the match address (from L) to perform the page-alignment calculation.
4F23
AND A,0C0H E6 C0
AND Register A with 0C0H (binary 11000000). This clears the lower 6 bits of the address low byte, rounding the address down to the nearest 64-byte boundary. The result is the page-aligned display base address for the memory dump.
4F25
LD L,A 6F
Load Register L with the page-aligned low byte. HL now holds the page-aligned display address: the same high byte as the match address, but with the low byte rounded down to the nearest 64-byte boundary.
4F26
LD (4063H),HL 22 63 40
Store the page-aligned display address to 4063H (the display address register). The SYS5/SYS memory dump will begin displaying from this aligned address, with the found byte visible in the dump window.
4F29
RET C9
RETURN. The L command has found the byte and updated the display address.

4F2AH - N Command: Position to Next Relative Block or Instruction

Handles the N extended DEBUG command: "position to the NEXT relative block/instruction (such as JR X or load block)". Reads the current address from 4060H, fetches the length byte at (address+1), and advances the address pointer past the current record. The result is page-aligned and stored to 4063H for display.

4F2A
CP A,4EH FE 4E
Compare Register A against 4EH (ASCII N). If A equals 4EH the N command has been requested.
4F2C
If the NZ flag is set (A is not N), JUMP to 4F4AH to continue dispatch for the Q command.
4F2E
LD HL,(4060H) 2A 60 40
Load Register Pair HL with the current address from 4060H. HL points to the first byte of the current relative block or instruction.
4F31
GOSUB to 51A3H to optionally read a new starting address from the user into HL. If the user types nothing, HL retains the value from 4060H.
4F34
INC HL 23
INCrement HL by 1, advancing from the current block's first byte (the type/opcode byte) to the length byte at offset +1 within the block structure.
4F35
LD D,00H 16 00
Load Register D with 00H, initialising the high byte of a DE offset accumulator to zero. This prepares DE to hold the block length as a 16-bit value.
4F37
LD E,(HL) 5E
Load Register E with the byte at (HL), which is the length byte of the current block record at address+1. This is the number of data bytes in the current block.
4F38
LD A,E 7B
Load Register A with the length byte (from E) to test it.
4F39
OR A,A B7
Test Register A against itself. If the length byte is zero, the Z flag is set. A length of zero is a special case in the VTOS load module format indicating a full-size block (254 bytes).
4F3A
If the NZ flag is set (length byte is non-zero), JUMP to 4F3DH to skip the length adjustment.
4F3C
INC D 14
If the length byte was zero (indicating a 256-byte block in the load module format), INCrement D by 1. Combined with E=00H, DE now equals 0100H (256), correctly representing the full-block length.
4F3D
INC DE 13
INCrement DE by 1. This advances past the length byte itself (the record header overhead is at minimum 1 byte for the length field; additional header bytes such as the type byte are accounted for by the INC HL at 4F34H).
4F3E
ADD HL,DE 19
ADD DE (block length + 1) to HL (current position pointing at the length byte). The result is HL pointing to the first byte of the next block in the load module stream.
4F3F
LD (4060H),HL 22 60 40
Store the updated address (start of next block) to 4060H.
4F42
LD A,L 7D
Load Register A with the low byte of the new address (from L) to perform page-alignment.
4F43
AND A,0C0H E6 C0
AND Register A with 0C0H to clear the lower 6 bits, rounding the address down to the nearest 64-byte boundary for display purposes.
4F45
LD L,A 6F
Load Register L with the page-aligned low byte.
4F46
LD (4063H),HL 22 63 40
Store the page-aligned display address to 4063H so the memory dump will display the region containing the start of the next block.
4F49
RET C9
RETURN. The N command is complete; 4060H points to the next block and 4063H is page-aligned for display.

4F4AH - Q Command: Query Input Port / Output to Port

Handles the Q extended DEBUG command in both its forms: "Qii" (query input port ii) and "Qoo,dd" (send data byte dd to output port oo). Parses the port number into C, then either reads the port via IN A,(C) and displays the result, or writes the second parameter to the port via OUT (C),L.

4F4A
CP A,51H FE 51
Compare Register A against 51H (ASCII Q). If A equals 51H the Q command has been requested.
4F4C
If the NZ flag is set (A is not Q), JUMP to 4F6EH to continue dispatch for the T command.
4F4E
LD A,1EH 3E 1E
Load Register A with 1EH (cursor right / field separator control character) to position the display before the port output.
4F50
GOSUB to ROM 0033H to output the cursor-control character.
4F53
GOSUB to 51A3H to parse the port number (the "ii" or "oo" parameter) into HL.
4F56
RET Z C8
If the Z flag is set (no port number entered), RETURN immediately.
4F57
LD C,L 4D
Load Register C with the low byte of HL (the parsed port number). The Z80 IN and OUT instructions use C as the 8-bit port number.
4F58
If the Carry flag is set (input terminated after the port number - the "Qoo,dd" form with data to follow ended early, or this is the read form "Qii"), JUMP to 4F61H to execute a port READ rather than a port write.
4F5A
GOSUB to 51A3H to parse the data byte (the "dd" parameter of the Qoo,dd form) into HL.
4F5D
RET Z C8
If the Z flag is set (no data byte entered), RETURN.
4F5E
OUT (C),L ED 69
Send the data byte in Register L to I/O port C. This is the Qoo,dd output form: the byte parsed from the "dd" parameter is written to the port number in C.
4F60
RET C9
RETURN. The port output is complete.
4F61
LD A,3DH 3E 3D
Load Register A with 3DH (ASCII equals sign =) as a display separator preceding the port's read value.
4F63
GOSUB to ROM 0033H to display the = character.
4F66
IN A,(C) ED 78
Read data from I/O port C into Register A. This is the Qii input form: the current value of the port at the address in C is read and placed in A.
4F68
GOSUB to the hex byte output routine at 51D9H to display the read port value (in A) as two hex digits on screen.
4F6B
JUMP to the hex input routine at 518AH. After displaying the read value, the routine waits for acknowledgement input from the user (allowing another value to be read or the command to end). Using JP rather than CALL/RET allows the RET inside 518AH to return directly to the Q command's caller.

4F6EH - T Command: Type ASCII Data into Memory

Handles the T extended DEBUG command: "TYPE ASCII data into memory, optionally starting at address aaaa". Accepts an optional starting address, then enters an interactive loop that displays each memory byte's ASCII value with a dash prompt and stores keyboard input characters directly into memory. A space input skips (does not overwrite) the current byte but advances the pointer.

4F6E
CP A,54H FE 54
Compare Register A against 54H (ASCII T). If A equals 54H the T command has been requested.
4F70
If the NZ flag is set (A is not T), JUMP to 4FADH to continue dispatch for the V command.
4F72
LD HL,(4060H) 2A 60 40
Load Register Pair HL with the current address from 4060H as the default starting address for memory entry.
4F75
GOSUB to 51A3H to optionally accumulate a user-supplied starting address into HL.
4F78
LD (4060H),HL 22 60 40
Store the (potentially updated) starting address to 4060H.
4F7B
RET C D8
If the Carry flag is set (BREAK or error during address input), RETURN immediately.
4F7C
If the NZ flag is set (an address was entered), JUMP to 4F81H to skip the echo of the address.
4F7E
GOSUB to 51D4H to display the starting address on screen.

Loop Start - Type Data Loop
Each iteration outputs a 1EH cursor control, displays the current byte as an ASCII character (or period for non-printable), outputs a dash prompt, reads an input character, stores it (unless it is a space), advances the pointer, and loops.

4F81
LD A,1EH 3E 1E
Load Register A with 1EH (cursor-right control character) to position the display for the next byte's prompt.
4F83
GOSUB to ROM 0033H to output the 1EH cursor control.
4F86
GOSUB to 51F2H to output a space character, separating the cursor-control position from the displayed byte.
4F89
LD A,(HL) 7E
Load Register A with the byte currently at the address pointed to by HL. This is the existing byte in memory at the current T-command position.
4F8A
AND A,7FH E6 7F
AND Register A with 7FH to strip the high bit (bit 7), converting any high-bit-set character to its 7-bit ASCII equivalent. This normalises display characters to the standard ASCII range.
4F8C
CP A,20H FE 20
Compare Register A against 20H (ASCII space). If A is less than 20H (a control character below space), the Carry flag is set.
4F8E
If the Carry flag is set (byte is a non-printable control character below 20H), JUMP to 4F94H to display a period instead.
4F90
CP A,0C0H FE C0
Compare Register A against 0C0H. If A is less than 0C0H (i.e., in the printable ASCII range 20H-BFH), the Carry flag is set. Values C0H and above are TRS-80 semigraphics characters - non-standard and displayed as periods.
4F92
If the Carry flag is set (A is in the printable range 20H-BFH), JUMP to 4F96H to display the character as-is.
4F94
LD A,2EH 3E 2E
Load Register A with 2EH (ASCII period .). Non-printable and semigraphics bytes are displayed as a period to indicate their presence without showing garbage characters.
4F96
GOSUB to ROM 0033H to display the current byte (as its ASCII character or a period) on screen.
4F99
LD A,2DH 3E 2D
Load Register A with 2DH (ASCII dash -) as the input prompt, mirroring the E command's display style.
4F9B
GOSUB to ROM 0033H to display the dash prompt.
4F9E
GOSUB to the hex input routine at 518AH to read the next character from the user. For the T command, the input is treated as a raw ASCII character rather than a hex digit - the lower-level 518AH routine returns the raw character in A.
4FA1
RET C D8
If the Carry flag is set (BREAK pressed), RETURN and terminate the T command session.
4FA2
CP A,20H FE 20
Compare Register A against 20H (ASCII space). The manual specifies that a space input skips the current byte without modifying it but still advances the pointer.
4FA4
If the Z flag is set (user typed a space), JUMP to 4FA7H to advance the pointer without storing anything.
4FA6
LD (HL),A 77
Store the input character (in A) into the memory byte at (HL). This writes the new ASCII character directly into memory at the current T-command address.
4FA7
INC HL 23
INCrement HL to advance to the next memory address.
4FA8
LD (4060H),HL 22 60 40
Store the updated address to 4060H.
4FAB
Loop End
LOOP BACK to 4F81H to display and prompt for the next byte.

4FADH - V Command: Verify Memory Blocks

Handles the V extended DEBUG command: "VERIFY the memory block of lth bytes at aaaa to the memory block at bbbb". Parses two address parameters and an optional byte count, then compares the two memory blocks byte by byte. Both address pointers advance through their respective ranges, updated in 4063H and 4060H as the comparison proceeds.

4FAD
CP A,56H FE 56
Compare Register A against 56H (ASCII V). If A equals 56H the V command has been requested.
4FAF
If the NZ flag is set (A is not V), JUMP to 5008H to continue dispatch for the W command.

Parse First Parameter: Source Address

4FB1
LD HL,(4063H) 2A 63 40
Load Register Pair HL with the current first address from 4063H (the display/source address register) as the default source block start.
4FB4
GOSUB to 51A3H to optionally accumulate a user-supplied source address into HL.
4FB7
LD (4063H),HL 22 63 40
Store the parsed source address to 4063H.
4FBA
If the NZ flag is set (an address was entered), JUMP to 4FC6H to skip echoing it.
4FBC
PUSH AF F5
Save Register Pair AF before the display call.
4FBD
GOSUB to 51D4H to display the source address on screen.
4FC0
LD A,2CH 3E 2C
Load Register A with 2CH (ASCII comma).
4FC2
GOSUB to ROM 0033H to display the comma separator.
4FC5
POP AF F1
Restore Register Pair AF.

Parse Second Parameter: Destination Address

4FC6
If the Carry flag is set (no more parameters follow), JUMP to 4FDDH to use default values for the destination and count.
4FC8
LD HL,(4060H) 2A 60 40
Load Register Pair HL with the current second address from 4060H as the default destination block start.
4FCB
GOSUB to 51A3H to optionally accumulate a user-supplied destination address into HL.
4FCE
LD (4060H),HL 22 60 40
Store the parsed destination address to 4060H.
4FD1
If the NZ flag is set (a destination address was entered), JUMP to 4FDDH to skip the echo.
4FD3
PUSH AF F5
Save Register Pair AF before the display call.
4FD4
GOSUB to 51D4H to display the destination address on screen.
4FD7
LD A,2CH 3E 2C
Load Register A with 2CH (ASCII comma).
4FD9
GOSUB to ROM 0033H to display the comma separator.
4FDC
POP AF F1
Restore Register Pair AF.

Parse Optional Third Parameter: Byte Count
The default count is 0000H. With BC=0000H the comparison loop will run for 65536 iterations (the full address space) if no mismatch is found.

4FDD
LD HL,0000H 21 00 00
Load Register Pair HL with 0000H as the default byte count for the verify operation.
4FE0
If the Carry flag is set (no more parameters), JUMP to 4FECH to use the default count of 0.
4FE2
GOSUB to 51A3H to optionally accumulate a user-supplied byte count into HL.
4FE5
If the NZ flag is set (a count was entered), JUMP to 4FECH with the user-supplied count.
4FE7
PUSH HL E5
Save the byte count (HL) before the display call.
4FE8
GOSUB to 51D4H to display the byte count on screen.
4FEB
POP HL E1
Restore the byte count from the stack.

Execute Verify Loop
HL = byte count (BC after the next two instructions). Source at 4063H (in HL after load), destination at 4060H (in DE). Each byte pair is compared; the loop runs until BC=0 or a mismatch is found.

4FEC
LD B,H 44
Load Register B with the high byte of HL (high byte of the verify byte count).
4FED
LD C,L 4D
Load Register C with the low byte of HL. BC now holds the full byte count for the verify loop.
4FEE
LD HL,(4063H) 2A 63 40
Load Register Pair HL with the source address from 4063H.
4FF1
LD DE,(4060H) ED 5B 60 40
Load Register Pair DE with the destination address from 4060H.

Loop Start - Verify Loop
Each iteration reads one byte from each block and compares them. On a mismatch the loop continues (both pointers advance). On a full match (BC exhausted), both updated pointers are stored back.

4FF5
LD A,(DE) 1A
Load Register A with the byte at the destination address (DE). This is the byte from the second (destination) memory block.
4FF6
CP A,(HL) BE
Compare Register A (destination byte) against the byte at (HL) (source byte). If they are equal, the Z flag is set; if they differ, the NZ flag is set.
4FF7
If the NZ flag is set (the two bytes are different), JUMP to 5000H to store the current pointer positions and return - reporting the mismatch location.
4FF9
INC DE 13
INCrement DE to advance the destination pointer to the next byte.
4FFA
INC HL 23
INCrement HL to advance the source pointer to the next byte.
4FFB
DEC BC 0B
DECrement BC (the remaining byte count) by 1.
4FFC
LD A,B 78
Load Register A with B (the high byte of BC) to test whether BC has reached zero.
4FFD
OR A,C B1
OR Register A with C (the low byte of BC). If both B and C are zero, A will be zero and the Z flag is set.
4FFE
Loop End
If the NZ flag is set (BC is not yet zero - more bytes to compare), LOOP BACK to 4FF5H to compare the next pair.
5000
LD (4060H),DE ED 53 60 40
Store the current destination pointer (DE) to 4060H, recording where the verify ended (either the mismatch point or the end of the compared range).
5004
LD (4063H),HL 22 63 40
Store the current source pointer (HL) to 4063H, recording the corresponding source position.
5007
RET C9
RETURN. The V command is complete; 4060H and 4063H hold the final pointer positions (either the first mismatch or the end of a successful match).

5008H - W Command: Search Memory for Word Address

Handles the W extended DEBUG command: "search memory for the WORD ADDRESS dddd, optionally starting at address aaaa". Parses a starting address and a 16-bit word value, then searches memory for a two-byte sequence matching the low byte followed by the high byte (little-endian word match) using CPIR. On a match, page-aligns the address and stores it to 4063H.

5008
CP A,57H FE 57
Compare Register A against 57H (ASCII W). If A equals 57H the W command has been requested.
500A
If the NZ flag is set (A is not W), JUMP to 505EH to continue to the disk transfer command dispatch.

Parse Starting Address and Target Word
The W command needs a starting address and the 16-bit word to find. The starting address is read from 4060H (with INC HL to start searching at the next address). The word to find is stored split across 4062H (low byte) and 405FH (high byte).

500C
LD HL,(4060H) 2A 60 40
Load Register Pair HL with the current address from 4060H as the default starting address for the word search.
500F
INC HL 23
INCrement HL to advance past the current address, starting the new search at the next byte.
5010
INC HL 23
INCrement HL a second time, advancing two bytes total. Since the W command searches for a 2-byte word, this ensures the search for the next occurrence of the word begins past both bytes of any word at the current address.
5011
GOSUB to 51A3H to optionally accumulate a user-supplied starting address into HL.
5014
LD (4060H),HL 22 60 40
Store the (potentially updated) starting address to 4060H.
5017
If the NZ flag is set (an address was entered), JUMP to 502BH to skip the address echo and target-word loading.
5019
PUSH AF F5
Save Register Pair AF before the display call.
501A
GOSUB to 51D4H to display the starting address on screen.
501D
LD A,2CH 3E 2C
Load Register A with 2CH (ASCII comma).
501F
GOSUB to ROM 0033H to display the comma separator.
5022
POP AF F1
Restore Register Pair AF.
5023
LD A,(4062H) 3A 62 40
Load Register A with the low byte of the previously used search word from 4062H. This loads the default low byte for the word to find.
5026
LD L,A 6F
Load Register L with the default low byte (from A). L will hold the low byte of the target word.
5027
LD A,(405FH) 3A 5F 40
Load Register A with the high byte of the previously used search word from 405FH. This loads the default high byte.
502A
LD H,A 67
Load Register H with the default high byte (from A). HL now holds the complete default 16-bit word to search for.

Parse Optional Target Word
If a new target word is entered it overwrites the defaults in HL. The parsed word is then split back into 4062H (low byte) and 405FH (high byte) for future W commands.

502B
If the Carry flag is set (no further input), JUMP to 503CH to use the existing target word in HL without parsing a new one.
502D
GOSUB to 51A3H to optionally accumulate a new 16-bit word value into HL.
5030
If the Z flag is set (no new word entered), JUMP to 503CH to use the loaded defaults.
5032
LD A,L 7D
Load Register A with the low byte of the newly parsed word (from L).
5033
LD (4062H),A 32 62 40
Store the new low byte to 4062H for future W command invocations.
5036
LD A,H 7C
Load Register A with the high byte of the newly parsed word (from H).
5037
LD (405FH),A 32 5F 40
Store the new high byte to 405FH for future W command invocations.
503A
JUMP to 503FH to proceed with the updated target word.
503C
GOSUB to 51D4H to display the (default) target word value on screen so the user can see what is being searched for.

Execute Word Search via CPIR (Two-Phase)
The search uses CPIR to find the low byte, then manually checks that the following byte matches the high byte. This implements a little-endian 16-bit word search: low byte at address aaaa, high byte at aaaa+1.

503F
LD HL,(4060H) 2A 60 40
Load Register Pair HL with the search starting address from 4060H.
5042
LD BC,0000H 01 00 00
Load Register Pair BC with 0000H to set up a full 64K CPIR scan.
5045
LD A,(4062H) 3A 62 40
Load Register A with the low byte of the target word from 4062H. A is the comparison value for CPIR's search for the word's low byte.
5048
CPIR ED B1
Search forward from (HL) for the first byte matching A (the low byte of the target word). INCrements HL and DECrements BC after each comparison. Terminates on a match (Z set, HL points one past the low byte) or when BC wraps to 0 (NZ, no match).
504A
RET NZ C0
If the NZ flag is set (no occurrence of the low byte found in 64K), RETURN without updating the display address.
504B
LD A,(405FH) 3A 5F 40
Load Register A with the high byte of the target word from 405FH. The low byte has been found at HL-1; now check whether (HL) matches the high byte.
504E
CP A,(HL) BE
Compare Register A (high byte of the target word) against the byte at (HL) (the byte immediately following the found low byte). If they match, the Z flag is set - a complete little-endian word match has been found.
504F
If the NZ flag is set (high byte does not match - this was a false positive from the CPIR), LOOP BACK to 5045H to continue searching for the next occurrence of the low byte.

Word Match Found - Compute Address and Page-Align
HL points one past the high byte (i.e., two bytes past the start of the found word). The routine steps back two bytes to reach the word start, stores it, then page-aligns for display.

5051
DEC HL 2B
DECrement HL by 1, stepping back from the byte after the high byte to the high byte itself.
5052
DEC HL 2B
DECrement HL by 1 again, stepping back to the low byte - the first byte of the found word. HL now points to the exact address of the matched word.
5053
LD (4060H),HL 22 60 40
Store the match address to 4060H, updating the current address pointer.
5056
LD A,L 7D
Load Register A with the low byte of the match address for page-alignment.
5057
AND A,0C0H E6 C0
AND Register A with 0C0H to clear the lower 6 bits, rounding down to the nearest 64-byte boundary.
5059
LD L,A 6F
Load Register L with the page-aligned low byte.
505A
LD (4063H),HL 22 63 40
Store the page-aligned display address to 4063H.
505D
RET C9
RETURN. The W command has found the word and updated both address registers.

505EH - Disk Transfer Command: d,[c],[s],o,addr,[lth]

Handles the unnamed extended DEBUG disk transfer command: "transfer a cylinder or sector to or from a disk unit". The command syntax is d,[c],[s],o,addr,[lth] where d is the drive number (0-7), c is the optional cylinder, s is the optional sector, o is the operation (R=read, W=write, *=directory write), addr is the memory address, and lth is the optional length in sectors. Dispatches to the appropriate SYS0 I/O routine based on the operation character.

Drive Number Dispatch Check
The disk command is triggered not by a letter but by a decimal digit (0-7) as the command character. The check tests that A is in the range 30H-37H (ASCII digits '0' to '7').

505E
CP A,30H FE 30
Compare Register A against 30H (ASCII 0). If A is less than 30H, the Carry flag is set - the character is below the digit range.
5060
If the Carry flag is set (character below 0), JUMP to 50CBH. This leads to the P command handler at 5132H (or falls through to an unrecognised-command path).
5062
CP A,38H FE 38
Compare Register A against 38H (ASCII 8). If A is greater than or equal to 38H, the No Carry flag is set - the character is above drive 7.
5064
If the No Carry flag is set (character above 7), JUMP to 50CBH. Together with the previous check, only ASCII digits '0' through '7' pass through to the disk command handler.

Select Drive and Compute Sectors-Per-Track
The drive number is converted from ASCII to binary, the drive parameter block is selected via CALL 478FH, and the sectors-per-track value is derived from IY+07H (the sectors/track and interleave byte in the drive parameter block).

5066
SUB A,30H D6 30
SUBtract 30H from Register A, converting the ASCII drive digit (30H-37H) to the binary drive number (0-7).
5068
LD C,A 4F
Load Register C with the binary drive number (0-7). The SYS0 drive-select routine at 478FH expects the drive number in C.
5069
GOSUB to the SYS0 drive-select routine at 478FH. This sets Register Pair IY to point to the 10-byte drive parameter block for the selected drive (at 4700H + drive_number x 10).
506C
LD A,(IY+07H) FD 7E 07
Load Register A with the byte at IY+07H (the sectors/track and interleave field of the drive parameter block). The high bits of this byte encode the number of sectors per track.
506F
AND A,0E0H E6 E0
AND Register A with 0E0H (binary 11100000), isolating the three high bits of the sectors/track byte. These three bits encode the sectors-per-track value as a 3-bit field.
5071
RLCA 07
Rotate Register A Left Circular (first of three). Shifts the 3-bit sectors field leftward, beginning to extract the value from bits 7-5.
5072
RLCA 07
Rotate Register A Left Circular (second).
5073
RLCA 07
Rotate Register A Left Circular (third). After three RLCA operations, the three bits that were in positions 7-5 are now in positions 2-0. A contains the raw sectors-per-track count (0-7 from the field, representing typical values 10, 18, etc.).
5074
INC A 3C
INCrement Register A by 1, converting the 0-based encoded value to the actual sectors-per-track count (the field uses 0-origin encoding).
5075
LD B,A 47
Load Register B with the sectors-per-track count. B will be used as the loop counter in the multiply below.
5076
LD A,(IY+07H) FD 7E 07
Reload Register A with the sectors/track byte from IY+07H. The low 5 bits of this byte encode the sector interleave, but here the low nibble (bits 4-0) gives the sector size increment value used to compute the total sectors per track.
5079
AND A,1FH E6 1F
AND Register A with 1FH to isolate the lower 5 bits of the sectors/track byte. This extracts the per-sector size increment value.
507B
INC A 3C
INCrement A by 1 to convert from 0-based to 1-based.
507C
LD H,A 67
Load Register H with this per-sector value. H will be added repeatedly to A in the multiplication loop.
507D
XOR A,A AF
Set Register A to zero as the multiplication accumulator.

Loop Start - Multiply Loop (Sectors-per-Track Calculation)
This loop computes A = H * B by repeated addition. B is the sectors-per-track count; H is the per-sector value. The result in A is the total sectors per cylinder.

507E
ADD A,H 84
ADD Register H to Register A. Each iteration accumulates one sector's worth of the per-track count.
507F
Loop End
DECrement B and LOOP BACK to 507EH if B is not zero. When B reaches zero, A holds the total sectors-per-cylinder (sectors-per-track * per-sector-value).
5081
BIT 5,(IY+04H) FD CB 04 6E
Test bit 5 of the byte at IY+04H (the drive select latch byte in the drive parameter block). Bit 5 in the VTOS drive select latch is the double-density (MFM) flag. If set, the disk uses double-density recording and has double the sectors per track.
5085
If the Z flag is set (bit 5 is clear - single density), JUMP to 5088H, skipping the doubling step. For single-density disks the sectors-per-track count is already correct.
5087
RLCA 07
Rotate Register A Left Circular, effectively multiplying A by 2. For double-density disks, the sectors-per-cylinder count is doubled.
5088
LD (405FH),A 32 5F 40
Store the computed sectors-per-cylinder value to 405FH. This will be used later in the disk command to compute the absolute sector address from the cylinder and sector numbers provided by the user.

Parse Command Parameters: Cylinder, Sector, Operation, Address, Length
The disk command now parses the remaining parameters. 518AH reads the full input line; 51A3H accumulates hex digits. The operation character (R/W/*) is stored in B. The parsed track, sector, and length are used to drive the SYS0 I/O routines.

508B
LD A,1EH 3E 1E
Load Register A with 1EH (cursor-right control) to position the display.
508D
GOSUB to ROM 0033H to output the cursor control character.
5090
GOSUB to the hex input routine at 518AH to read the next parameter (cylinder number, if provided, or the operation character if cylinder is omitted).
5093
RET C D8
If the Carry flag is set (BREAK or end of input), RETURN.
5094
GOSUB to 51A3H to accumulate further digits of the cylinder parameter into HL (if more digits follow).
5097
RET C D8
If the Carry flag is set (BREAK), RETURN.
5098
LD D,L 55
Load Register D with the low byte of HL (the parsed cylinder number or the default). D will hold the cylinder (track) number for the disk I/O call.
5099
If the NZ flag is set (a cylinder number was entered), JUMP to 509EH to proceed with sector parsing.
509B
LD D,(IY+09H) FD 56 09
Load Register D with the byte at IY+09H (the sector register cache in the drive parameter block - the last-used sector for this drive). When no cylinder is specified, the command uses the currently cached sector position.
509E
GOSUB to 51A3H to parse the sector number into HL.
50A1
LD E,L 5D
Load Register E with the parsed sector number (low byte of HL). DE now holds track (D) and sector (E) for the disk I/O calls.
50A2
LD A,01H 3E 01
Load Register A with 01H as the default sector count (transfer 1 sector if no length is specified).
50A4
If the NZ flag is set (a sector number was entered), JUMP to 50ABH to skip setting E to the default and proceed to storing the sector count.
50A6
LD E,00H 1E 00
Load Register E with 00H. When no sector is specified, E=00H instructs the SYS0 I/O routine to perform a full cylinder read/write (all sectors on the track).
50A8
LD A,(405FH) 3A 5F 40
Load Register A with the sectors-per-cylinder value from 405FH (computed earlier from IY+07H). This becomes the transfer length when a full cylinder is requested.
50AB
LD (4062H),A 32 62 40
Store the sector count (1 for a single-sector transfer, or sectors-per-cylinder for a full-cylinder transfer) to 4062H. This is used as the iteration counter for the multi-sector loop.
50AE
RET C D8
If the Carry flag is set (BREAK), RETURN.
50AF
GOSUB to 518AH to read the operation character (R, W, or *) from the user input into Register A.
50B2
RET C D8
If the Carry flag is set (BREAK), RETURN.
50B3
LD B,A 47
Load Register B with the operation character (R=52H, W=57H, *=2AH). B will be used to select the appropriate SYS0 I/O routine.
50B4
GOSUB to 518AH to read the memory address parameter (the "addr" field) from the user. The address is accumulated into HL via 51A3H within 518AH.
50B7
RET C D8
If the Carry flag is set (BREAK), RETURN.
50B8
GOSUB to 51A3H to accumulate the memory address digits into HL.
50BB
PUSH HL E5
Save the memory address (HL) onto the stack.
50BC
If the Carry flag is set (no length parameter follows - end of input after address), JUMP to 50CDH to use the previously stored sector count in 4062H as the length.

Parse Optional Length Parameter
If the user specifies a length (number of sectors) it overrides the default count.

50BE
PUSH HL E5
Save the memory address again (it will be needed after parsing the length).
50BF
GOSUB to 51A3H to accumulate the length (sector count) into HL.
50C2
LD A,L 7D
Load Register A with the low byte of the parsed length.
50C3
POP HL E1
Restore the memory address into HL.
50C4
If the Z flag is set (no length digits entered), JUMP to 50CDH and use the default count.
50C6
LD (4062H),A 32 62 40
Store the user-supplied sector count to 4062H, overriding the default.
50C9
JUMP to 50CDH to proceed to the I/O dispatch.
50CB
JUMP to 5132H (the P command handler). This is the dispatch target for any command character that falls outside the recognised command ranges - effectively redirecting to the P command path (which performs a final check for the 50H / P character, or jumps to 0000H for cold restart on a truly unrecognised command).

Dispatch to I/O Routine Based on Operation Character
The operation character in B is checked against R (52H), W (57H), and * (2AH). Each dispatches to the appropriate SYS0 read or write routine. Unknown operation characters fall through to the sector-step loop which processes the transfer iteratively.

50CD
LD A,B 78
Load Register A with the operation character (from B, where it was saved at 50B3H). This is R (52H), W (57H), or * (2AH).
50CE
CP A,52H FE 52
Compare Register A against 52H (ASCII R). If A equals 52H a read operation has been requested.
50D0
If the Z flag is set (R operation), JUMP to 5102H to perform a sector READ via the SYS0 read routine at 4777H.
50D2
CP A,57H FE 57
Compare Register A against 57H (ASCII W). If A equals 57H a write operation has been requested.
50D4
If the Z flag is set (W operation), JUMP to 510DH to perform a sector WRITE via the SYS0 write routine at 4763H.
50D6
CP A,2AH FE 2A
Compare Register A against 2AH (ASCII asterisk *). If A equals 2AH a directory write operation has been requested.
50D8
If the Z flag is set (* operation), JUMP to 5114H to perform a directory sector WRITE via the SYS0 write routine at 4768H.

Loop Start - Multi-Sector Transfer Loop
For operations that do not immediately dispatch to a single-call I/O routine, the following loop advances through DE (track and sector), decrements the sector count, and repeats. This handles multi-sector transfers by iterating one sector at a time.

50DA
INC H 24
INCrement Register H. H is the high byte of the memory address HL; INCrementing it advances the memory pointer by 256 bytes (one sector worth of 256-byte sectors) per iteration.
50DB
INC E 1C
INCrement Register E (the sector number within DE). This advances to the next sector on the current track.
50DC
LD A,(405FH) 3A 5F 40
Load Register A with the sectors-per-cylinder count from 405FH (computed at 5088H).
50DF
DEC A 3D
DECrement A by 1 to convert the sectors-per-cylinder count to a 0-based maximum sector index.
50E0
CP A,E BB
Compare Register A (maximum sector index) against Register E (current sector number). If A is less than E, the Carry flag is set meaning the sector number has wrapped past the end of the track.
50E1
If the No Carry flag is set (sector is still within the valid range), JUMP to 50E6H to skip the track-advance logic.
50E3
LD E,00H 1E 00
Load Register E with 00H, resetting the sector number to sector 0 (the first sector of the next track).
50E5
INC D 14
INCrement Register D (the track/cylinder number), advancing to the next cylinder as the sector counter wraps around.
50E6
LD A,(4062H) 3A 62 40
Load Register A with the remaining sector count from 4062H.
50E9
DEC A 3D
DECrement Register A by 1, reducing the remaining sector count.
50EA
LD (4062H),A 32 62 40
Store the updated sector count back to 4062H.
50ED
Loop End
If the NZ flag is set (remaining sector count is not yet zero), LOOP BACK to 50CDH to dispatch the next sector transfer. When the count reaches zero, fall through.
50EF
POP HL E1
Restore Register Pair HL from the stack (the original memory address that was saved at 50BB).
50F0
LD A,B 78
Load Register A with the operation character (from B).
50F1
CP A,52H FE 52
Compare Register A against 52H (R). If this was a read operation, the data was loaded into memory and 4063H/4060H should be updated to reflect the loaded area.
50F3
RET NZ C0
If the NZ flag is set (not a read operation - a write operation completed), RETURN.

Post-Read: Update Display Addresses
For a read operation, after all sectors have been loaded into memory, the display address registers are updated to point at the start of the loaded data so the user can immediately examine the result.

50F4
LD L,00H 2E 00
Load Register L with 00H, clearing the low byte of HL. Combined with H still holding the high byte of the original memory address, this aligns HL to the 256-byte page boundary of the load address.
50F6
LD (4063H),HL 22 63 40
Store the page-aligned load address to 4063H (the display address register), pointing the memory dump at the start of the data that was just read from disk.
50F9
LD (4060H),HL 22 60 40
Store the same page-aligned address to 4060H (the current address pointer), ready for subsequent commands to work with the newly loaded data.
50FC
LD A,53H 3E 53
Load Register A with 53H (ASCII S). This is the step-mode command character used by SYS5/SYS to activate screen display mode.
50FE
LD (405EH),A 32 5E 40
Store 53H to 405EH (the single-step mode flag in the SYS5 work area at 405EH). Setting this flag to the S character activates the full-screen debugger display mode, causing the debugger to immediately show a screen-full of the loaded data upon return.
5101
RET C9
RETURN. The disk read is complete and the display mode has been activated.

Single I/O Dispatch Routines
The following three short sections handle direct single-call dispatches to the three SYS0 I/O routines for the R, W, and * operations respectively. Each calls the SYS0 routine and then either loops back for another sector or exits.

5102
GOSUB to the SYS0 sector read routine at 4777H. This reads one sector from the disk into memory at the address specified by HL, using the track and sector numbers in DE. (R operation.)
5105
If the Z flag is set (read succeeded), JUMP to 50DAH to advance the track/sector and memory address pointers and loop for the next sector (multi-sector read) or exit the loop when count reaches zero.
5107
CP A,06H FE 06
Compare Register A against 06H. After a disk read error, A holds the error code returned by SYS0. Code 06H (or similar) may represent a non-fatal condition (such as end of file or a benign I/O status) that allows the read to be considered complete.
5109
If the Z flag is set (error code 06H - treated as success/continue), JUMP to 50DAH to continue with the next sector.
510B
JUMP to 5119H to display the error status and prompt for acknowledgement before returning.
510D
GOSUB to the SYS0 sector write routine at 4763H. This writes one sector from memory at HL to disk at the track and sector in DE. (W operation.)
5110
If the Z flag is set (write succeeded), JUMP to 50DAH to advance pointers and continue.
5112
JUMP to 5119H to display the error and prompt on write failure.
5114
GOSUB to the SYS0 directory-write routine at 4768H. This performs a directory-area sector write (the * operation), writing the sector with the directory-area flag active.
5117
If the Z flag is set (directory write succeeded), JUMP to 50DAH to continue.

I/O Error Display and Acknowledge
On an I/O error, the routine displays a space, the error code as two hex digits, an asterisk pair flanking the current operation character, and then waits for the user to press any key before returning to the multi-sector loop or exiting.

5119
PUSH DE D5
Save Register Pair DE (current track and sector number) onto the stack before the error display sequence modifies registers.
511A
PUSH AF F5
Save Register Pair AF (the error code in A) onto the stack.
511B
GOSUB to 51F2H to print a space character.
511E
LD A,2AH 3E 2A
Load Register A with 2AH (ASCII asterisk *) as a visual error indicator.
5120
GOSUB to ROM 0033H to display the first asterisk.
5123
POP AF F1
Restore Register Pair AF, recovering the error code in A.
5124
GOSUB to the hex byte output routine at 51D9H to display the error code (in A) as two hex digits.
5127
LD A,2AH 3E 2A
Load Register A with 2AH (ASCII asterisk) for the closing error indicator.
5129
GOSUB to ROM 0033H to display the second asterisk. The error display now shows *nnH* where nn is the error code.
512C
GOSUB to 518AH to wait for the user to press any key, acknowledging the error before the disk command continues or exits.
512F
POP DE D1
Restore Register Pair DE (track and sector) from the stack.
5130
JUMP to 50DAH to advance to the next sector and continue with the multi-sector loop (or decrement the count to zero and exit).

5132H - P Command: Print Memory in Hex and ASCII

Handles the P extended DEBUG command: "PRINT memory from aaaa through bbbb, inclusively, in both hex and ASCII". Parses two address parameters, then outputs a formatted hex dump: each line shows a 4-digit hex address followed by 16 bytes in hex (with grouping spaces every 4 bytes), then a separator, then the same 16 bytes as ASCII characters (non-printable bytes shown as a period). The dump continues line by line until the end address is reached or exceeded. This section also contains the shared hex output subroutines used throughout SYS9.

5132
CP A,50H FE 50
Compare Register A against 50H (ASCII P). If A equals 50H the P command has been requested. Any command character that reached 5132H via the 50CBH dispatch and is not 50H represents a completely unrecognised command.
5134
If the NZ flag is set (the command character is not P - an unrecognised command was issued), JUMP to 0000H (cold restart). This is the extended debugger's last-resort handler for commands that fell through all dispatch checks: an unknown command causes a full system reset.

Parse Start and End Addresses
The P command requires exactly two parameters: start address and end address. If either is zero (Z set from 51A3H) the command returns immediately.

5137
GOSUB to 51A3H to parse the start address into HL.
513A
RET Z C8
If the Z flag is set (no start address entered), RETURN.
513B
PUSH HL E5
Save the start address onto the stack.
513C
GOSUB to 51A3H to parse the end address into HL.
513F
EX (SP),HL E3
Exchange HL with the top of the stack. HL now holds the start address (from the stack), and the stack top holds the end address. This efficiently swaps start and end addresses.
5140
POP BC C1
Pop the end address from the stack into BC. Now HL = start address, BC = end address.
5141
RET Z C8
If the Z flag is set (no end address entered), RETURN.

Align Start Address to 16-Byte Boundary
The P command's hex dump lines are 16 bytes wide. The start address is rounded down to the nearest 16-byte boundary (clearing the low 4 bits) so the first displayed line begins at a clean hex address ending in 0.

5142
LD A,L 7D
Load Register A with the low byte of the start address for alignment.
5143
AND A,0F0H E6 F0
AND Register A with 0F0H (binary 11110000), clearing the lower 4 bits and rounding the address down to the nearest 16-byte (paragraph) boundary.
5145
LD L,A 6F
Load Register L with the aligned low byte. HL now holds the 16-byte-aligned start address for the dump.
5146
LD A,0DH 3E 0D
Load Register A with 0DH (carriage return, CR).
5148
GOSUB to ROM at 003BH to output the carriage return, starting a new line.
514B
GOSUB to ROM 003BH to output a second carriage return, producing a blank line before the dump begins.

Loop Start - Outer Dump Loop (One Line = 16 Bytes per Iteration)
Each outer iteration saves HL (the current 16-byte line start address), outputs the 4-digit hex address, then outputs 16 bytes in hex, then outputs the 16 bytes as ASCII, then checks whether the end address BC has been reached.

514E
PUSH HL E5
Save the current dump address (HL) onto the stack. It will be popped back after the hex portion to reuse for the ASCII portion of the line.
514F
LD A,H 7C
Load Register A with the high byte of HL (high byte of the current line address) for hex display.
5150
RRA 1F
Rotate Register A Right through Carry (first of four), shifting the high nibble of the high byte into position.
5151
RRA 1F
Rotate Register A Right through Carry (second).
5152
RRA 1F
Rotate Register A Right through Carry (third).
5153
RRA 1F
Rotate Register A Right through Carry (fourth). After four RRA operations the high nibble of the original A is now in bits 3-0, ready for nibble-to-ASCII conversion.
5154
AND A,0FH E6 0F
AND Register A with 0FH to isolate the nibble (clear any carry bits shifted in by RRA).
5156
ADD A,90H C6 90
ADD 90H to the nibble value. This is the first step of the standard Z80 nibble-to-ASCII hex digit technique using DAA.
5158
DAA 27
Decimal Adjust Register A. Combined with the preceding ADD A,90H, this converts nibble values 0-9 to ASCII '0'-'9' and sets Carry for values A-F.
5159
ADC A,40H CE 40
ADD 40H plus Carry to Register A. For nibbles A-F (where DAA set Carry), this produces ASCII 'A'-'F'. For nibbles 0-9 (Carry clear), the result is already the correct ASCII digit from the DAA step.
515B
DAA 27
Decimal Adjust again to finalise the ASCII conversion.
515C
GOSUB to ROM 003BH to output the first (most significant) hex digit of the address high byte.
515F
LD A,H 7C
Reload Register A with the address high byte to extract its low nibble.
5160
AND A,0FH E6 0F
AND Register A with 0FH to isolate the low nibble of the high byte.
5162
ADD A,90H C6 90
First step of nibble-to-ASCII conversion.
5164
DAA 27
Decimal Adjust.
5165
ADC A,40H CE 40
Second step of nibble-to-ASCII conversion.
5167
DAA 27
Final Decimal Adjust.
5168
GOSUB to ROM 003BH to output the second hex digit (low nibble of the address high byte).
516B
LD A,L 7D
Load Register A with the low byte of the current address (from L) to output the third and fourth hex digits.
516C
RRA 1F
Rotate Right through Carry (first of four) to isolate the high nibble of the low byte.
516D
RRA 1F
Rotate Right through Carry (second).
516E
RRA 1F
Rotate Right through Carry (third).
516F
RRA 1F
Rotate Right through Carry (fourth). The high nibble of L is now in bits 3-0.
5170
AND A,0FH E6 0F
Isolate the nibble.
5172
ADD A,90H C6 90
First step of nibble-to-ASCII.
5174
DAA 27
Decimal Adjust.
5175
ADC A,40H CE 40
Second step of nibble-to-ASCII.
5177
DAA 27
Final Decimal Adjust.
5178
GOSUB to ROM 003BH to output the third hex digit (high nibble of the address low byte).
517B
LD A,L 7D
Reload Register A with the low byte of the address to extract its low nibble.
517C
AND A,0FH E6 0F
AND Register A with 0FH to isolate the low nibble of the low byte.
517E
ADD A,90H C6 90
First step of nibble-to-ASCII.
5180
DAA 27
Decimal Adjust.
5181
ADC A,40H CE 40
Second step of nibble-to-ASCII.
5183
DAA 27
Final Decimal Adjust.
5184
GOSUB to ROM 003BH to output the fourth (least significant) hex digit of the address - completing the 4-digit address prefix.
5187
LD A,20H 3E 20
Load Register A with 20H (ASCII space) to separate the address from the hex data bytes.
5189
GOSUB to ROM 003BH to output the first space separator.
518C
GOSUB to ROM 003BH to output a second space separator, providing a double-space gap between the address and the first hex byte.
518F
JUMP to 5193H, entering the per-byte hex output loop at the point after the grouping-space check. The first byte's hex output begins immediately after the address.

Inner Hex Byte Output Loop Entry (from Line-Wrap)
5191H is the re-entry point for subsequent lines of the dump (reached via the JR C,5191H at 51E7H). It re-enters the outer loop at 514EH to begin a new line.

5191
LOOP BACK to 514EH to begin a new output line. This is the re-entry point for line continuation: after completing the ASCII portion of one line, if there is more data to print, execution returns here to start the next line's address and hex output.

Loop Start - Inner Hex Byte Output Loop (16 bytes per line)
Each iteration of this inner loop outputs one byte as two hex digits, followed by a space, then checks for group spacing (extra space every 4 bytes) and line-end (after 16 bytes). HL points to the current byte; INC HL advances after each byte.

5193
LD A,(HL) 7E
Load Register A with the byte at the current dump address (HL). This is the byte to be output as hex.
5194
RRA 1F
Rotate Right through Carry (first of four) to extract the high nibble for hex output.
5195
RRA 1F
Rotate Right through Carry (second).
5196
RRA 1F
Rotate Right through Carry (third).
5197
RRA 1F
Rotate Right through Carry (fourth).
5198
AND A,0FH E6 0F
Isolate the high nibble.
519A
ADD A,90H C6 90
First step of nibble-to-ASCII conversion.
519C
DAA 27
Decimal Adjust.
519D
ADC A,40H CE 40
Second step of nibble-to-ASCII.
519F
DAA 27
Final Decimal Adjust.
51A0
GOSUB to ROM 003BH to output the high hex digit of the current byte.

Shared Entry Point: 51A3H - Low Nibble Output
51A3H is both the second half of the per-byte hex output (outputting the low nibble after the high nibble has been sent) and a shared entry point used by the extended command parameter parsers throughout SYS9 to accumulate a hex digit from input into HL. When called from 51A0H it outputs the low nibble of (HL); when called directly by parameter parsers it reads one more nibble from the keyboard and accumulates it.

51A3
LD A,(HL) 7E
Load Register A with the byte at (HL). In the hex dump path, this re-reads the same byte whose high nibble was just output. In the hex input path, this reads the next character from the input buffer.
51A4
AND A,0FH E6 0F
AND Register A with 0FH to isolate the low nibble of the byte.
51A6
ADD A,90H C6 90
First step of nibble-to-ASCII conversion.
51A8
DAA 27
Decimal Adjust.
51A9
ADC A,40H CE 40
Second step of nibble-to-ASCII.
51AB
DAA 27
Final Decimal Adjust.
51AC
GOSUB to ROM 003BH to output the low hex digit of the current byte, completing the 2-digit hex representation.
51AF
LD A,20H 3E 20
Load Register A with 20H (ASCII space) as the separator following the hex byte.
51B1
GOSUB to ROM 003BH to output the space after the hex byte.
51B4
INC HL 23
INCrement HL to advance to the next byte in the dump.
51B5
LD A,L 7D
Load Register A with the low byte of the updated address (L) to test for grouping and line-end conditions.
51B6
AND A,0FH E6 0F
AND Register A with 0FH to isolate the lower 4 bits of the address. A value of 00H means the address has advanced to a new 16-byte line boundary - time to end this hex line and begin the ASCII portion.
51B8
If the Z flag is set (lower 4 bits are zero - 16 bytes have been output), JUMP to 51C3H to transition from hex output to ASCII output.
51BA
AND A,03H E6 03
AND the lower 4 bits (A was already ANDed with 0FH) with 03H to isolate bits 1 and 0. The result is zero every 4 bytes (when bits 1:0 are both zero), used to detect 4-byte group boundaries.
51BC
LD A,20H 3E 20
Load Register A with 20H (space) as the group-spacing character.
51BE
GOSUB to ROM 003BH only if the Z flag is set (i.e., bits 1:0 of the address low byte are both zero - every 4 bytes). This adds an extra space between groups of 4 hex bytes, improving readability. The CALL Z form ensures the extra space is output only at the 4-byte group boundaries, not after every byte.
51C1
Loop End (Hex Output)
LOOP BACK to 5193H to output the next byte in hex. This inner loop continues until the address reaches a 16-byte boundary, producing the full hex portion of the current line.

ASCII Portion of Dump Line
After 16 hex bytes have been output, a separator space is added and then the same 16 bytes are displayed again as ASCII characters. The saved HL from the PUSH at 514EH is popped to restore the line-start address.

51C3
LD A,20H 3E 20
Load Register A with 20H (space) as the separator between the hex bytes and the ASCII portion of the line.
51C5
GOSUB to ROM 003BH to output the separator space.
51C8
POP HL E1
Restore Register Pair HL with the start address of the current 16-byte line (saved at 514EH). HL now points to the first byte of the 16 that were just displayed in hex, ready to be output as ASCII.

Loop Start - ASCII Byte Output Loop (16 bytes per line)
Each iteration reads one byte, determines whether it is printable ASCII (20H-7FH), outputs the character or a period substitute, and advances HL. The loop ends when the address reaches the same 16-byte boundary it stopped at during the hex output.

51C9
LD A,(HL) 7E
Load Register A with the next byte from memory (the byte whose ASCII equivalent is to be displayed).
51CA
CP A,20H FE 20
Compare Register A against 20H (ASCII space). If A is less than 20H (a non-printable control character), the Carry flag is set.
51CC
If the Carry flag is set (byte is a control character below space), JUMP to 51D2H to substitute a period.
51CE
CP A,80H FE 80
Compare Register A against 80H. If A is less than 80H (standard printable ASCII range 20H-7FH), the Carry flag is set and the character can be displayed as-is.
51D0
If the Carry flag is set (byte is in the printable range 20H-7FH), JUMP to 51D4H to output the character directly.
51D2
LD A,2EH 3E 2E
Load Register A with 2EH (ASCII period .) as the substitute for non-printable characters (below 20H or 80H and above).
51D4
GOSUB to ROM 003BH to output the ASCII character (or period substitute) for the current byte.
51D7
INC HL 23
INCrement HL to advance to the next byte.
51D8
LD A,L 7D
Load Register A with the updated low byte of HL to test for the line-end boundary.
51D9
AND A,0FH E6 0F
AND Register A with 0FH to isolate the lower 4 bits of the address. Zero means 16 bytes have been output.
51DB
Loop End (ASCII Output)
If the NZ flag is set (lower 4 bits are not zero - fewer than 16 bytes output on this line), LOOP BACK to 51C9H to output the next ASCII character.

End of Line - Carriage Return and End-of-Dump Test
After the 16th ASCII character has been output, a carriage return ends the line. The current address HL is then compared against the end address BC; if HL >= BC the dump is complete.

51DD
LD A,0DH 3E 0D
Load Register A with 0DH (carriage return) to terminate the current dump line.
51DF
GOSUB to ROM 003BH to output the carriage return, ending the current line and beginning a new one.
51E2
PUSH HL E5
Save the current address (HL, now pointing to the start of the next 16-byte line) onto the stack temporarily for the end-of-dump comparison.
51E3
XOR A,A AF
Set Register A to zero and clear all flags (clearing Carry before the 16-bit SBC).
51E4
SBC HL,BC ED 42
Subtract BC (end address) from HL (current address). If HL was less than BC (current address has not yet passed the end address), the Carry flag is set. If HL >= BC (current address equals or exceeds the end address), Carry is clear.
51E6
POP HL E1
Restore the current address into HL (the SBC result is discarded; only the Carry flag is used).
51E7
Outer Loop End
If the Carry flag is set (current address is still before the end address), LOOP BACK to 5191H which jumps to 514EH to begin the next dump line. When Carry is clear (current address has reached or passed the end address), fall through to output the trailing carriage returns and return.

End of Dump - Output Trailing Carriage Returns
Three carriage returns are output via a fall-through and a final JP 003BH to terminate the dump display cleanly.

51E9
LD A,0DH 3E 0D
Load Register A with 0DH (carriage return) for the first trailing carriage return after the dump.
51EB
GOSUB to ROM 003BH to output the first trailing carriage return.
51EE
GOSUB to ROM 003BH to output the second trailing carriage return.
51F1
JUMP to ROM 003BH to output the third and final trailing carriage return. Using JP instead of CALL/RET allows the RET inside ROM 003BH to return directly to the P command's original caller, cleanly terminating the P command. This is the last instruction in the SYS9 overlay (END 4E00H confirms the entry point; the overlay ends here at 51F4H).