MFC is My First Computer: a software-defined WDC 65C02 machine with an interactive monitor, a resident FAT16 filesystem, BASIC, FORTH, and a built-in assembler β comprehensive enough to program itself, small enough to read.
The icon is the machine's own boot prompt, ] and its cursor, set in the CP437
character ROM the VIC renders text with (tools/make_icon.py reads the glyphs
straight out of include/computer/Cp437Font.h, so the mark cannot drift from the
typeface it is drawn in).
This project started as a continuation of a CPU/assembler/disassembler I wrote in Python. I wanted to create an actual running environment to enter code directly or load from a file and run.
I also wanted to test the abilities of AI as part of the development and documentation process.
This project implements a complete 6502-based computer system kernel for emulated environments. The kernel provides low-level system initialization, hardware control, and most importantly, a powerful interactive monitor program for direct system interaction.
Key Features:
- Complete 6502 assembly language kernel optimized for emulated environments
- Cycle-stepped WDC 65C02 CPU emulator (full CMOS instruction set, validated against the Klaus2m5/amb5l functional, decimal, and 65C02-extended test suites)
- Interactive monitor with comprehensive debugging tools
- MFC/OS DOS shell (
]prompt) with a resident FAT16 filesystem and launch-by-name for disk programs - Disk applications: EDIT (full-screen editor), TERM (ANSI/telnet terminal with XMODEM), IRC (chat client), plus games (CHESS, KERNEL PANIC, VENTURE, The Sunless Vault roguelike, and the Scott Adams adventures) β TERM and IRC keep a RAM scrollback buffer you page with PgUp/PgDn
- Built-in MFC BASIC interpreter (derived from EhBASIC), launched by typing
BASICat the DOS prompt (with human-readable.basLOAD/SAVE via a host file dialog) - System-wide
--More--pager: long output from any program (DOS, monitor, BASIC, FORTH) pauses each screenful (SPACE advances, ESC stops) - Memory manipulation and program execution capabilities
- Streamlined architecture with universal commands and simplified modes
- File I/O operations for loading and saving programs
- Comprehensive search, fill, move, and copy operations
- Assembly examples in
examples/β a dozen runnable 6502 programs (loops, keyboard input, color, hex dump, an 8Γ8 multiply, a guess-the-number game) with a guide and ABI quick reference (examples/README.md)
The heart of this system is the 6502 Monitor - a complete interactive debugging and programming environment that provides direct control over the computer's memory and execution. The monitor offers a command-line interface with powerful tools for memory operations, program execution, and system inspection.
The monitor features a streamlined architecture with:
- Two primary modes: Command mode (default) and Write mode for interactive editing
- Simplified command processing with consistent syntax and error handling
- Command repeatability recall last command for quick replay or modification
The machine boots into the MFC/OS DOS shell, which shows a sign-on splash and
the ] prompt:
]
From the DOS prompt you run disk programs by name (EDIT, TERM, IRC, CHESS, β¦),
manage files (CATALOG, TYPE, COPY, β¦), and launch the monitor with MON.
The monitor prompts with the current address followed by > (? for help, Q to
return to DOS):
0000>
The NNNN> prompt indicates you're in monitor command mode. You can now enter any monitor command.
For complete command documentation including syntax and examples, see the Monitor manual.
| Category | Commands | Description |
|---|---|---|
| Memory Operations | R:, W:, F:, M:, X: | Read, write, fill, move/copy, and search memory |
| Program Operations | G:, L:, S: | Execute, load, and save programs |
| Number Conversion | D:, H: | Convert between decimal and hexadecimal |
| Display Commands | C:, T:, Z: | Clear screen, show stack, show zero page |
| System Commands | ?, ESC, . | Help, exit mode, command recall |
Commands are listed alphabetically by command letter (matching the on-screen ? help):
- C: Clear Screen - Clear the display
- D: Decimal to Hex - Convert decimal (0-65535) to hexadecimal format
- F: Fill Memory - High-performance memory filling with progress feedback
- G: Go/Run - Direct program execution with return to monitor
- H: Hex to Decimal - Convert hexadecimal (0000-FFFF) to decimal format
- L: Load File - Load a host-selected file to an address:
L:8000(host shows a file dialog) - M: Move/Copy - Smart memory operations with overlap detection (
M:src-end,dest,Bwhere B: 0=copy, 1=move) - R: Read Memory - Display bytes in memory, supports single addresses or ranges
- S: Save File - Save a memory range to a host-selected file:
S:8000-8FFF(host shows a file dialog) - T: Stack - Display the stack page ($0100-$01FF), paged
- W: Write Memory - Interactive hex editing with address advancement
- X: Search Memory - Multi-byte pattern search with paged output
- Z: Zero Page - Display zero page ($0000-$00FF), paged
- ESC - Exit the current mode and return to the command prompt
The monitor provides clear, consistent error messages:
ERROR?- Invalid command syntax or parametersRANGE?- Invalid or out-of-bounds address rangeVALUE?- Invalid hexadecimal characters in input
- $0000-$00FF: Zero Page (system workspace; monitor uses $14-$39, EhBASIC uses the rest)
- $0100-$01FF: Stack memory
- $0200-$03FF: Monitor variables and command buffers
- $0400-$07FF: Formerly the screen (the 80Γ25 color screen now lives behind the VIC register port at
$FE2D-$FE37, not in the address map). Claimed by the monitor:$0400is theT:/Z:snapshot,$0500-$07FFthe assembler's symbol table - $0800-$AFFF: User RAM (module working RAM; EhBASIC program/variable space)
- $B000-$EFFF: Module window (16 KB; bank 0 = RAM, banks 1..255 = ROM modules β BASIC 1, FORTH 3, MONITOR 4; bank 2 free since the assembler joined the monitor)
The chipset drawn as a board β bus, chips, I/O decode and interrupt lines β is in docs/BOARD.md.
- $F000-$FFFF: Kernel BIOS (4 KB; CODE ~1,560 bytes, rest free for growth). The monitor is bank 4, not here.
- $FE00-$FE22: PIA registers (keyboard, file I/O, timer) β an I/O page reserved within the kernel region
See docs/ARCHITECTURE.md for the full memory map and zero-page allocation.
User programs can access kernel services via the jump table at $FF00:
| Address | Service | Description |
|---|---|---|
| $FF00 | PRINT_CHAR | Print single character |
| $FF03 | PRINT_MESSAGE | Print null-terminated string |
| $FF06 | PRINT_NEWLINE | Print carriage return/line feed |
| $FF09 | GET_KEYSTROKE | Wait for key press |
| $FF0C | CLEAR_SCREEN | Clear display |
| $FF0F | GET_RANDOM_NUMBER | Generate random byte |
| $FF12 | RETURN_FROM_MODULE | Module exit point β unmaps the bank, returns to DOS (BASIC BYE) |
| $FF2D | SET_ATTR | Set the color/attribute latch for subsequent output (A = [R][BR][bg:3][fg:3]) |
(Abridged β see docs/ARCHITECTURE.md for the full 20-entry ABI table, including the decimal-conversion, module-launch, sound, and timing services.)
The kernel provides memory-mapped file I/O at:
- $FE10: File command register
- $FE11: File status register
- $FE12-$FE13: Address registers
- $FE14-$FE1F: Filename buffer
- $FE20-$FE21: End address (for save operations)
| Dependency | Needed for | If missing |
|---|---|---|
| CMake 3.20+, Ninja | the build itself | nothing builds |
| C++20 compiler (GCC 10+, Clang 10+, MSVC 2019+) | the emulator | nothing builds |
cc65 (ca65, ld65, cl65) |
assembling every ROM and .PRG |
configuration fails β the emulator cannot run without its ROMs |
| Qt6 or Qt5 β Core, Widgets, Network | the GUI machine; Network drives TERM/IRC | you get a non-interactive console demo, not the computer |
| Qt Multimedia | SID audio | everything works, silently |
| Python 3 | opcode-table drift test | that one test is skipped |
GoogleTest is not a prerequisite β the build fetches v1.14.0 itself when tests are enabled.
# Debian / Ubuntu / Mint (verified)
sudo apt install build-essential cmake ninja-build cc65 qt6-base-dev qt6-multimedia-dev
# Fedora (untested)
sudo dnf install gcc-c++ cmake ninja-build cc65 qt6-qtbase-devel qt6-qtmultimedia-devel
# Arch (untested)
sudo pacman -S base-devel cmake ninja cc65 qt6-base qt6-multimedia
# macOS (untested)
brew install cmake ninja cc65 qtIf you install a dependency later, delete the build directory before rebuilding. CMake caches "not found" results, so adding Qt or Qt Multimedia to an existing build tree leaves the emulator GUI-less or mute with no error.
./build.sh --freshdoes this for you.
# Option 1: the build script -- configures, builds every program, assembles disk.img
./build.sh
./build.sh --fresh # after installing a new dependency
# Option 2: CMake presets directly (see CMakePresets.json)
cmake --preset dev # dev | debug | release | no-gui
cmake --build --preset dev
cmake --build --preset dev-everything # programs + ROMs + disk.imgOnce configured, two targets cover the whole pipeline:
ninja -C cmake-build-debug everything # every program, every ROM, the app, the disk
ninja -C cmake-build-debug run # ...then boot the machineThe .PRG files are build outputs, not committed artifacts β they are produced into
cmake-build-debug/programs/<name>/ and staged from there, exactly like the ROMs.
That is deliberate: when they were checked in, editing a program's C and forgetting
to rebuild left the disk carrying yesterday's binary while the test blobs, compiled
from the same sources by CMake, picked the change up β the tests agreed with you and
the machine did not. The twelve Scott Adams games under disk/GAMES/ are committed:
they are content, not built from sources in this repo.
Configuration ends with a summary of what was actually enabled β check it before filing a bug about missing sound or a missing window:
======== 6502-kernel configuration ========
Qt GUI ......... yes (Qt6)
SID audio ...... yes
cc65 ROMs ...... yes
Tests .......... yes
===========================================
Build outputs land in cmake-build-debug/: bin/6502-kernel, kernel/*.rom
(kernel, dos, basic, assembler, forth), kernel/kernel.map, and disk.img.
cd cmake-build-debug/bin && ./6502-kernelRun it from bin/. The ROMs and disk image are opened by relative path
(../kernel/*.rom, ../disk.img), so launching from anywhere else fails with
"Could not open kernel.rom". A healthy boot lands at the MFC/OS prompt:
MFC 6502 OPERATIONAL
MFC/OS 1.21 32768 BYTES FREE
]
Type CATALOG to list the disk, or HELP for the command set.
ctest --test-dir cmake-build-debug # 27 tests: CPU, banking, FAT16, ACIA/XMODEM, SID, RTC, VIC, ROM layout, disk programsThe GUI loads cmake-build-debug/disk.img, assembled from programs/catalog.txt
β the single source of truth for everything that can go on a disk. One section per
item, saying where its files live, how to build them (or that they are committed
content needing no build),
and where each lands on the disk. Files are declared by what they are: a program
(the .PRG), data the program reads and writes at run time, or a doc for a human
to TYPE. The distinction is there so data can never be separated from the program
that needs it β naming term inescapably brings SYSTEM/DIAL.LST, because the
catalog records that TERM reads and writes it.
The build derives both the staging commands and the diskmap.txt that mkdisk
consumes, so adding a program is one catalog entry rather than three edits that fail
silently if you miss one. CMake refuses to configure if a programs/*/build.sh has no
catalog entry. Drawers grow across as many FAT16 clusters as they need, so a drawer is
not capped at one cluster of files.
ninja disk # build the programs, then assemble the image
ninja everything # ...and the ROMs and the app as wellBoth are explicit β a plain ninja never rewrites the disk. disk builds every
catalog program before staging it, so the image can never carry a stale binary.
The mkdisk host tool (cmake-build-debug/bin/mkdisk) also works standalone:
mkdisk create <image> <diskmap.txt> # build a fresh image from a bundle
mkdisk read <image> <outdir> # extract an image into a bundle (+ diskmap.txt)
mkdisk update <image> <diskmap.txt> # replace/add listed files, keep the rest6502-kernel/
βββ src/ # C++ emulator sources
β βββ computer/ # CPU, memory, VIC, PIA, ACIA, SID, RTC, block device
β βββ ui/ # Qt GUI (MainWindow, DisplayWidget)
β βββ kernel/ # 6502 assembly: kernel.asm, basic.asm, dos/, assembler/, forth/
βββ include/ # C++ headers
βββ programs/ # cc65/asm disk programs: edit, term, irc, venture, kpanic,
β # chess, frontier, micromax, scottfree, vault, common
β # (catalog.txt lists every one and where it lands on disk)
βββ examples/ # Runnable 6502 assembly examples (+ README.md)
βββ disk/ # committed disk content (GAMES/ Scott Adams .PRGs)
βββ vendor/ # Pristine upstream sources we port/derive from
βββ tools/ # Host tools: cmake modules, mkdisk, mkfat16, dat2c
βββ docs/ # Documentation
βββ tests/ # Unit and integration tests (GoogleTest)
For detailed development information and project context, see:
- CLAUDE.md - Development guidelines and architecture documentation
- docs/README.md - Documentation index: program manuals (MONITOR, DOS, BASIC, ASSEMBLER, FORTH, EDIT, TERM, IRC), the architecture reference (ARCHITECTURE.md), and the internals deep-dive (SYSTEM_INTERNALS.md)
- Start with Help: Use
?to see all available commands - Use Command Recall: The
.command saves time when refining commands - File Operations:
L:8000loads andS:8000-8FFFsaves; the host shows a file dialog to pick the file - Search Effectively: Use X: with multiple byte patterns for precise matching
- Number Conversion: Use D: and H: commands to convert between decimal and hex
- Program Development: Load programs with L:, test with G:, save modifications with S:
This project stands on the shoulders of the classic 6502 and free-software community. With thanks to the authors whose work we have ported, derived from, or studied:
- micro-Max by H.G. Muller β the remarkably small but complete chess
engine (full FIDE rules and move legality) behind
CHESS.PRG. We compile the freely published 1.6 source with cc65 and wrap it in a console front-end for MFC-DOS. Pristine upstream sources are kept undervendor/micromax/. https://home.hccnet.nl/h.g.muller/max-src2.html - ScottFree by Alan Cox / Swansea University Computer Society β the
GPL Scott Adams adventure interpreter we port to run the classic Adventure
International games on MFC-DOS. The interpreter is in
programs/scottfree; the host tooldat2cpre-parses a game.datinto linkable C tables. - Scott Adams / Adventure International β author and publisher of the
twelve classic text adventures (Adventureland, Pirate Adventure, β¦ The Golden
Voyage). The shareware
.datdatabases are obtained separately and are not redistributed here. - kilo by Salvatore Sanfilippo (antirez) β the inspiration for the
EDITtext editor. EDIT is our own implementation (it renders straight to screen RAM rather than a terminal), but its structure and the incremental search are lifted from kilo's design. https://github.com/antirez/kilo - EhBASIC (Enhanced 6502 BASIC) by the late Lee Davison β the basis for the built-in MFC BASIC interpreter.
- fig-FORTH for the 6502 by William F. Ragsdale and the FORTH Interest
Group (FIG) β the public-domain FIG model behind the
FORTHmodule (bank 3). We mechanically convert the original assembler listing to ca65 (verified byte-identical at its native$0200origin), then relocate it into the ROM module window and wire its I/O to the kernel. The pristine listing and the conversion/verification tooling live undervendor/fig-forth/. - cc65 β the 6502 C cross-compiler and toolchain used to build the C
programs (
CHESS.PRG, the Scott Adams games). https://cc65.github.io/ - XMODEM/CRC for the 65C02 by Daryl Rictor (2002) β the serial
file-transfer routine behind the emulated 6551 ACIA spike. We retarget its
built-in 6551 driver to our memory-mapped ACIA and relocate it for the host
test harness; the pristine original is kept under
vendor/xmodem/. - IBM VGA 8Γ16 CP437 font β the character generator ROM behind the 80Γ25
display (full CP437: box-drawing, blocks, accented, symbols). The raw bitmap
of the IBM VGA ROM font is public domain (U.S. copyright protects scalable
outline programs, not bitmap font data). The dump comes from VileR's
vga-text-mode-fontscollection (https://int10h.org/), gratefully acknowledged; the pristineVGA8.F16and the header generator are undervendor/cp437font/. - MOS 6581/8580 SID β the sound chip our software SID is modeled on (three
voices, ADSR, multimode filter). The synthesizer is written from scratch from
public SID documentation (register layout, envelope rates, filter behavior) β
no reSID or other GPL code is used. With thanks to the SID/C64 community
whose datasheets and reverse-engineering notes made a faithful model possible.
See
docs/sound_design.md. - The Sunless Vault (
VAULT.PRG) β an original text roguelike written from scratch for MFC (no ported code). Its integer, turn-based, data-driven engine follows the design of the author's own Dungeon of Yacor, and its play draws inspiration from two classics of the genre β Telengard by Daniel Lawrence and Sword of Fargoal by Jeff McCord β as design influences only; no code or assets from those games are used. Seeprograms/vault/.
See docs/cc65_to_prg.md for the C-to-.PRG build pipeline.
Where we port or adapt third-party code, the original, unmodified source is
preserved under vendor/ so its authorship and licensing remain clear.
The monitor is designed for both interactive exploration and efficient program development workflows.
