Machine Monitor — Post-merge Cleanup & Architecture Follow-ups
Follow-up issue tracking review feedback from @GideonZ on the machine monitor PR (merged into test-merge, not yet on master).
To avoid merge conflicts and to keep things simple, it is probably best to address these issues as follows:
- Merge
test-merge into master, possibly after addressing the most important issues shown below.
- Branch off a new branch from
master and fix remaining issues mentioned below.
1. UIPathPicker should be folded into TreeBrowser
File: software/userinterface/path_picker.h / path_picker.cc
The path picker is a thin wrapper around TreeBrowser that re-implements window setup, key handling and a text-entry fallback. TreeBrowser is already abstract enough to host this — it isn't file-specific despite its name.
Recommended solution
- Delete
UIPathPicker as a separate UIObject.
- Express "pick a path" as a configuration of
TreeBrowser itself (e.g. a PICK_PATH extension of the existing PickMode enum, plus the text-entry overlay as an internal mode of the browser).
- Callers (FTP Move/Copy-to, monitor Save dialog, future consumers) construct a
TreeBrowser directly with that mode rather than instantiating a parallel picker class.
This removes the duplicated init/poll/keymap plumbing and keeps a single authoritative entry point for tree-based selection.
2. Browsable::getFileInfo() does not belong on the abstract base
File: software/userinterface/browsable.h
Browsable represents any line in the line-based browser — not necessarily a file. virtual FileInfo *getFileInfo() therefore leaks a file concept into the base class, and isPathPickerWrapper() is a similar abstraction leak
added by the picker work.
Recommended solution
- Move
getFileInfo() off Browsable and onto a dedicated interface/subclass (e.g. BrowsableFileEntry) implemented only by browsables that actually back a FileInfo.
- Callers that currently call
entry->getFileInfo() perform a typed downcast / dynamic_cast-style check instead, so the type system enforces the distinction.
- Remove
isPathPickerWrapper() once the picker is folded into TreeBrowser (item 1) — the wrapper class disappears with it.
@GideonZ offered to take this on; documenting here so it is tracked either way.
3. Context menu should not reach into TreeBrowser state
File: software/userinterface/context_menu.cc:55-62
The context menu currently inspects state->browser->pick_mode, state->browser->can_pick(...) and contextable->pickAsCurrentPath() to decide whether to inject a synthetic "Select" action. This couples the context menu to the browser's pick state, when by design it should depend only on the Browsable under the cursor.
Recommended solution
- Introduce a
BrowsablePickEntry (or similar) type that exposes its own fetch_context_items(...) returning the appropriate "Select" action.
- The context menu calls
contextable->fetch_context_items(actions) and is done — no knowledge of pick_mode or the browser.
- The picker (item 1) wraps/promotes entries into
BrowsablePickEntry while building the list, so the action only appears when picking is actually active.
Result: ContextMenu :: get_items() collapses back to a single contextable->fetch_context_items(actions) call.
4. machine_monitor_actions[256] registry is the wrong shape
Files: software/monitor/monitor_init.cc:20-65, monitor_init.h, software/test/monitor/machine_monitor_test.cc:82-101
The current implementation is a 256-entry Action * array indexed by subsys_id, with register_machine_monitor_task / get_machine_monitor_task guarded by __attribute__((weak)) so the test build can shadow them.
@GideonZ's concern: this isn't congruent with how subsys_ids are intended to be used. SubSystem already maintains a registry (SubSystem::getSubSystems()), and ids are sparse and few (currently 12).
A 256-slot sidecar array indexed by the same ids is essentially a parallel registry that bypasses the existing SubSystem machinery.
Recommended solution
- Replace the sidecar array with a small, fixed lookup that mirrors the shape of the subsystem registry — e.g. a single
Action * per machine type (C64 / U64), stored where the machine itself is constructed, and resolved through C64::getMachine() rather than a numeric id.
- The caller in
tree_browser.cc:447-459 then asks the current machine for "your monitor action" instead of probing SUBSYSID_U64 then falling back to SUBSYSID_C64.
- This also removes the need for
__attribute__((weak)) and the duplicated test stub in machine_monitor_test.cc.
5. Test stub string_box overloads have unused parameters
File: software/test/stubs/userinterface.h:65-80
int string_box(const char *msg, char *buffer, int maxlen, bool) { ... }
int string_box(const char *msg, char *buffer, int maxlen, bool, bool) { ... }
The stubs match the real overload set (template_mode, uppercase) but silently discard the flags. That's misleading: tests that exercise template or uppercase behavior pass through unchanged.
Recommended solution
- Either implement minimal honest semantics in the stub (e.g. uppercase input when
uppercase=true), or
- Remove the extra overloads and have the production code that needs them use a single signature. The stub then matches that signature directly.
The unused-parameter overloads should not stay as-is.
6. U64 PLA / memory-mapping emulation is too clunky (architectural)
File: software/u64/u64_machine.cc
@GideonZ's summary of the current arbitration / decode pipeline:
CPU -> PIO ---------------------------\
CPU -> bus -> |\ |
VIC -> bus -> | \ v
DirectDMA -> bus -> | arbitration -> decode (PLA) -> CPU view
ExternDMA -> bus -> |/ ^
External Pins (EXROM/GAME) ------------/
DirectDMA has a flag that forces the decode block to end up in RAM; what actually puts the system "in the game" is mostly the external EXROM/GAME pins.
To peek/poke under the CPU's view, the monitor today emulates the PLA in software (see read_cpu_mapped_byte / write_cpu_mapped_byte in u64_machine.cc) — duplicating banking logic that already exists in hardware. This is hard to keep correct and complete.
Recommended solution (Gideon's proposal)
Pull the external pins (EXROM/GAME) and the PIO pins before arbitration and make them programmable on the DirectDMA port. Each bus agent then gets its own view of memory, interleaved cleanly:
- The application drives "virtual" EXROM/GAME/PIO levels on the DirectDMA port, independent of the real system-level pins.
- The existing decode (PLA) block then produces the correct view for that agent automatically — no software re-implementation of banking.
read_cpu_block / read_cpu_mapped_byte / write_cpu_mapped_byte and the cpu_port plumbing in U64Machine collapse to "set virtual pins, do a regular DMA read/write".
This is a gateware change (with a software follow-up to remove the PLA emulation), so it is scoped as its own piece of work. Filing here for tracking.
7. override_cpu_port does not work via DMA
File: software/u64/u64_machine.cc:37-47
override_cpu_port writes $0000 / $0001 to swing the CPU port over the DMA bus before a peek/poke. Per @GideonZ: the CPU port is not accessible through DMA. The current sequence reads back whatever is in zero-page RAM and writes there, but the CPU port itself is unchanged.
Recommended solution
- Remove
override_cpu_port / restore_cpu_port and the cpu_port parameter on peek_cpu / poke_cpu / read_cpu_block in their current form.
- Once item 6 lands, the CPU view is selected by feeding virtual PIO pins into the bus subsystem at DirectDMA — no zero-page mutation required.
- Until then, document the limitation in the monitor (the "CPU view" peek/poke produces the value the PLA emulator computes, not what the real CPU sees after toggling the port).
8. C64_DMA_MEMONLY is not readable
File: software/u64/u64_machine.cc — every saved_memonly = C64_DMA_MEMONLY site
The register is write-only. The sequence
uint8_t saved_memonly = C64_DMA_MEMONLY; // always 0
C64_DMA_MEMONLY = 0;
...
C64_DMA_MEMONLY = saved_memonly; // always restores 0
works by accident today because callers always want it back at 0 after the access. It is, however, misleading and brittle.
Recommended solution
- Drop the read-back entirely.
before_memory_access / after_memory_access already own the lifetime (C64_DMA_MEMONLY = ... on entry, = 0 on exit) - let them be the single source of truth.
- Remove the unused
saved_memonly locals in read_visible_block, peek_visible, write_visible_byte, and read_visible_byte.
9. C64_MODE_NMI jump won't work in External Slot mode
File: software/monitor/monitor_file_io.cc:230-272 (monitor_io::jump_to)
@GideonZ noted that using C64_MODE_NMI to trigger the jump trampoline won't work when the machine is in External Slot mode. Today the monitor "jump to" feature is fine for normal/cartridge modes but will silently misbehave with an external cartridge.
Recommended solution
- Short-term: detect External Slot mode in
jump_to and either refuse the operation with a clear message, or fall back to the SUBSYSID_C64 / C64_DMA_BUFFER / RUNCODE_DMALOAD_JUMP path that the non-U64 branch already uses.
- Long-term: revisit alongside breakpoint support — at that point a more general "run from address" primitive should subsume both paths.
Suggested ordering
- Items 5, 8, 9 — small, local, no design dependencies.
- Item 4 — registry shape; clears
__attribute__((weak)) plumbing.
- Items 1 + 2 + 3 — done together; the
Browsable / context-menu cleanup makes the picker fold-in straightforward.
- Items 6 + 7 — gateware-led; software cleanup follows.
Machine Monitor — Post-merge Cleanup & Architecture Follow-ups
Follow-up issue tracking review feedback from @GideonZ on the machine monitor PR (merged into
test-merge, not yet onmaster).To avoid merge conflicts and to keep things simple, it is probably best to address these issues as follows:
test-mergeintomaster, possibly after addressing the most important issues shown below.masterand fix remaining issues mentioned below.1.
UIPathPickershould be folded intoTreeBrowserFile:
software/userinterface/path_picker.h/path_picker.ccThe path picker is a thin wrapper around
TreeBrowserthat re-implements window setup, key handling and a text-entry fallback.TreeBrowseris already abstract enough to host this — it isn't file-specific despite its name.Recommended solution
UIPathPickeras a separateUIObject.TreeBrowseritself (e.g. aPICK_PATHextension of the existingPickModeenum, plus the text-entry overlay as an internal mode of the browser).TreeBrowserdirectly with that mode rather than instantiating a parallel picker class.This removes the duplicated init/poll/keymap plumbing and keeps a single authoritative entry point for tree-based selection.
2.
Browsable::getFileInfo()does not belong on the abstract baseFile:
software/userinterface/browsable.hBrowsablerepresents any line in the line-based browser — not necessarily a file.virtual FileInfo *getFileInfo()therefore leaks a file concept into the base class, andisPathPickerWrapper()is a similar abstraction leakadded by the picker work.
Recommended solution
getFileInfo()offBrowsableand onto a dedicated interface/subclass (e.g.BrowsableFileEntry) implemented only by browsables that actually back aFileInfo.entry->getFileInfo()perform a typed downcast /dynamic_cast-style check instead, so the type system enforces the distinction.isPathPickerWrapper()once the picker is folded intoTreeBrowser(item 1) — the wrapper class disappears with it.@GideonZ offered to take this on; documenting here so it is tracked either way.
3. Context menu should not reach into
TreeBrowserstateFile:
software/userinterface/context_menu.cc:55-62The context menu currently inspects
state->browser->pick_mode,state->browser->can_pick(...)andcontextable->pickAsCurrentPath()to decide whether to inject a synthetic "Select" action. This couples the context menu to the browser's pick state, when by design it should depend only on theBrowsableunder the cursor.Recommended solution
BrowsablePickEntry(or similar) type that exposes its ownfetch_context_items(...)returning the appropriate "Select" action.contextable->fetch_context_items(actions)and is done — no knowledge ofpick_modeor the browser.BrowsablePickEntrywhile building the list, so the action only appears when picking is actually active.Result:
ContextMenu :: get_items()collapses back to a singlecontextable->fetch_context_items(actions)call.4.
machine_monitor_actions[256]registry is the wrong shapeFiles:
software/monitor/monitor_init.cc:20-65,monitor_init.h,software/test/monitor/machine_monitor_test.cc:82-101The current implementation is a 256-entry
Action *array indexed bysubsys_id, withregister_machine_monitor_task/get_machine_monitor_taskguarded by__attribute__((weak))so the test build can shadow them.@GideonZ's concern: this isn't congruent with how
subsys_idsare intended to be used.SubSystemalready maintains a registry (SubSystem::getSubSystems()), and ids are sparse and few (currently 12).A 256-slot sidecar array indexed by the same ids is essentially a parallel registry that bypasses the existing
SubSystemmachinery.Recommended solution
Action *per machine type (C64 / U64), stored where the machine itself is constructed, and resolved throughC64::getMachine()rather than a numeric id.tree_browser.cc:447-459then asks the current machine for "your monitor action" instead of probingSUBSYSID_U64then falling back toSUBSYSID_C64.__attribute__((weak))and the duplicated test stub inmachine_monitor_test.cc.5. Test stub
string_boxoverloads have unused parametersFile:
software/test/stubs/userinterface.h:65-80The stubs match the real overload set (
template_mode,uppercase) but silently discard the flags. That's misleading: tests that exercise template or uppercase behavior pass through unchanged.Recommended solution
uppercase=true), orThe unused-parameter overloads should not stay as-is.
6. U64 PLA / memory-mapping emulation is too clunky (architectural)
File:
software/u64/u64_machine.cc@GideonZ's summary of the current arbitration / decode pipeline:
DirectDMAhas a flag that forces the decode block to end up in RAM; what actually puts the system "in the game" is mostly the external EXROM/GAME pins.To peek/poke under the CPU's view, the monitor today emulates the PLA in software (see
read_cpu_mapped_byte/write_cpu_mapped_byteinu64_machine.cc) — duplicating banking logic that already exists in hardware. This is hard to keep correct and complete.Recommended solution (Gideon's proposal)
Pull the external pins (EXROM/GAME) and the PIO pins before arbitration and make them programmable on the DirectDMA port. Each bus agent then gets its own view of memory, interleaved cleanly:
read_cpu_block/read_cpu_mapped_byte/write_cpu_mapped_byteand thecpu_portplumbing inU64Machinecollapse to "set virtual pins, do a regular DMA read/write".This is a gateware change (with a software follow-up to remove the PLA emulation), so it is scoped as its own piece of work. Filing here for tracking.
7.
override_cpu_portdoes not work via DMAFile:
software/u64/u64_machine.cc:37-47override_cpu_portwrites$0000/$0001to swing the CPU port over the DMA bus before a peek/poke. Per @GideonZ: the CPU port is not accessible through DMA. The current sequence reads back whatever is in zero-page RAM and writes there, but the CPU port itself is unchanged.Recommended solution
override_cpu_port/restore_cpu_portand thecpu_portparameter onpeek_cpu/poke_cpu/read_cpu_blockin their current form.8.
C64_DMA_MEMONLYis not readableFile:
software/u64/u64_machine.cc— everysaved_memonly = C64_DMA_MEMONLYsiteThe register is write-only. The sequence
works by accident today because callers always want it back at 0 after the access. It is, however, misleading and brittle.
Recommended solution
before_memory_access/after_memory_accessalready own the lifetime (C64_DMA_MEMONLY = ...on entry,= 0on exit) - let them be the single source of truth.saved_memonlylocals inread_visible_block,peek_visible,write_visible_byte, andread_visible_byte.9.
C64_MODE_NMIjump won't work in External Slot modeFile:
software/monitor/monitor_file_io.cc:230-272(monitor_io::jump_to)@GideonZ noted that using
C64_MODE_NMIto trigger the jump trampoline won't work when the machine is in External Slot mode. Today the monitor "jump to" feature is fine for normal/cartridge modes but will silently misbehave with an external cartridge.Recommended solution
jump_toand either refuse the operation with a clear message, or fall back to theSUBSYSID_C64 / C64_DMA_BUFFER / RUNCODE_DMALOAD_JUMPpath that the non-U64 branch already uses.Suggested ordering
__attribute__((weak))plumbing.Browsable/ context-menu cleanup makes the picker fold-in straightforward.