BMDS comprises a number of program units:
Main Form
This unit (uMainForm) operates in one of two states; either running the machine emulator only, with options for reset, and load/save programs; or in developer mode where an additional debug window supports register modification, single step, trace, breakpoints, memory viewer, etc.
The MainForm manages the selected machine build and run. It also supports the main debug window creation in developer mode, along with the disassembler and compare windows. A system wide INI file, and generic About form, are also managed here.
Helper Classes
Each machine has its own hardware, CPU, memory map, display, etc and there are some units that help in building them.
Memory Manager (uMemoryMgr) – the memory manager class permits a machine’s memory map to be built in a simple way. It creates a memory block, defines memory sections (e.g. RAM or ROM) and handles generic read/writes handing off any special requirements to specific machine provided routines. These include I/O port allocations and operations.
Files Manager (uFilesMgr) – the files manager supports loading of machine files (e.g. ROMs and Character set images) into specific areas of machine memory, and tests each file against a CRC if provided.
Graphics Manager (uGfxMgr) – BMDS uses the SDL2 framework for graphics and sound. The graphics manager provides an interface between each machine and SDL2 supporting window creation, textures management, and rendering.
Sound Manager (uSoundsMgr) – the sounds manager provides a similar interface to SDL2 managing the set of sounds required to support a machine, where applicable.
INI File (uIniFile) – a single application INI file is used globally across BMDS for saving information required to support the program’s functionality, including window sizes and positions, configuration settings, etc. It is opened and closed by the main form, but accessed by most units as required.
About Form (uAboutForm) – a simple about form showing the version and build state of the program and the versions of Lazarus and FPC used to build it. A set of images relevant to the program are displayed at intervals.
Machine Classes
Each machine can have two units; one covering the main function of the specific machine including memory map and display; the second providing a preferences frame for the machine which is shown via the tree view in the main preferences window. These are named uMachineXXX and uPrefsXXX (where XXX = Microtan65, CHIP8 or SpaceInvaders) and have supporting base classes. Details are on the page specific to each machine:
The uMachineBase class also provides the MachineFactory which allows each machine to be registered without direct reference, and specific machines created as required. The global variable Machine is defined here.
CPU Classes
As it turns out each machine I chose to emulate had a different CPU. There are several units available for each CPU named as below (where XXX = 6502, CHIP8 or 8080):
- uCpuXXX – this unit provides the main functionality of the CPU including registers, interrupts and instruction execution. It also supports some helper functions for execution trace, disassembly and test. These inherit from the TCpuBase class.
- uDefsXXX – this provides the definitions specific to each CPU including registers, flags and opcode / mnemonics. This data is also used in the assembler and disassembler modules.
- uDisXXX – provides CPU specific disassembly information.
- uRegistersFrameXXX – this unit provides a frame that is shown in the composite debug window. It shows CPU specific register states whenever the machine is stopped or single-stepped, and includes stack information and simple disassembly of the current instruction. These inherit from the TRegistersFrame base class.
- uTestXXXForm (6502 and 8080 currently) – brings up a window where a diagnostic test program can be run against the CPU emulation testing the majority of it instructions.
Debugger
In developer mode, a composite debug window (uDebugForm) is shown supporting a number of functions with their own frame units:
-
- Registers (uRegistersFrameXXX) – as described under CPU classes above.
- Memory Display (uMemoryFrame) – shows the whole of the allocated memory space, and includes buttons to jump to the main ROM section, stack, or specific address.
- Breakpoints (uBreakpointsFrame) – allows machine addresses to be entered which are checked during program execution and will stop the machine if one of the listed addresses is accessed. When stopped all other debug windows are updated with their current state.
- Execution Trace (uTraceFrame) – when execution is stopped this frame lists the last 2048 instructions executed, showing the address, opcode, simple disassembly, registers and flags for each.
- Watches (uWatchesFrame) – [TODO: still to be coded].
- Compare (uCompareForm) – a separate form within the debug folder. Used to compare memory blocks, ROM files or Hex files against each other.
- Registers (uRegistersFrameXXX) – as described under CPU classes above.
Assembler
There is assembler functionality for each CPU which is run by a single assembler element that calls upon the CPU specific definition units described above. This two-pass assembler comprises a number of units:
- Main assembler form (uAssemblerForm) – uses units in the Editor folder to provide a multi-tabbed syntax highlighted editing facility with most of the standard editing functions; cut, copy etc.
- Main assembler unit (uAssembler) – this unit accesses the CPU definitions via the Machine instance, builds a symbol table and populates the command directives and CPU mnemonics. When given an assembler file to execute it then creates a parser, file manager and listing managers to support reading each token and taking the appropriate action, including:
- scanning lines checking labels, mnemonics and operands, generating code
- managing # command directives
- producing a listing of the assembled code
- Files Manager (uAsmFiles) – processes the reading of lines from assembler files, managing additional nested include files as required.
- Parser (uParser) – for each line the parser builds tokens for the main assembler identifying labels, mnemonics / commands, operands, operators and comments.
- Hash Tables (uHashTables) – the hash tables are used to provide easily searchable tables of symbols and mnemonics / commands.
- Symbols manager (uSymbols) – this unit provides the means for adding and finding symbols / identifiers as required by the assembler, using the hash tables above. As the hash table cannot keep entries in any sorted order, a secondary StringList is used to keep a sorted list.
- Listing unit (uAsmListing) – provides a formatted listing of the generated code produced by an assembly run.
- HTML List unit (*uAsmListHtml) – as above, but in HTML format with coloured syntax.
- Read/Write Hex files (uReadWriteHex) – generates output code files in either Intel hex or Motorola record formats.
- Errors (uErrorDefs) – the error constants as required during the assembly process.
- Preferences Frame (uAsmPrefsFrame) – a preferences frame for the assembler which is shown in the tree view in the main preferences window, permitting user selection of listing options, and whether generated code is to be output to memory or hex file.
Operation of the assembler is described on the assembler page.
Disassembler
One form (uDisassembler) generates disassembly listing for a range of memory addresses. Data address ranges can be added as required and will disassembled as byte constants, e.g DB/FCB.
Two listings are provided; a straight disassembly and a listing without bytes displayed but with labels, intended to be ‘assembler ready’. This form utilises CPU specific disassembly routines provided by each CPU unit.
Can save / load settings specific for current disassembly to a .DIS file (INI format).
Operation of the disassembler is described on the disassembler page
References
- Link to BMDS source code on Github