M1emu ‐ programmer - retrotruestory/M1DEV GitHub Wiki
- Architecture Overview
- Core Architecture Constants
- Memory Architecture
- CPU Registers
- Instruction Set
- Addressing Modes
- Microcode Implementation
- I/O Subsystem
- IDE/CF Card Interface
- UART Implementation
- Boot Process
- Emulator Components
- Data Structures
- Implementation Status
- Future Work
The Magic-1 is a 16-bit homebrew computer designed and built by Bill Buzbee using 74-series TTL logic. The emulator aims to recreate its functionality with cycle accuracy.
Key characteristics:
- 16-bit data path
- 16-bit memory addressing (64KB address space)
- 8-bit external data bus
- Microcoded implementation with 8K microcode ROM
- Three 16-bit general-purpose registers (A, B, C)
- Stack-based architecture with dedicated stack pointer
- Hardware support for memory protection and virtual memory
- Custom UART and IDE/CF interfaces
// CPU architecture constants
constexpr int DATA_BUS_WIDTH = 8; // 8-bit external data bus
constexpr int INTERNAL_DATA_WIDTH = 16; // 16-bit internal data paths
constexpr int PHYSICAL_ADDRESS_BITS = 22; // 22-bit physical address bus
constexpr int DEVICE_SELECT_BIT = 1; // 1 device select bit
constexpr int TOTAL_ADDRESS_BITS = 23; // 23-bit total addressing capability
constexpr int VIRTUAL_ADDRESS_BITS = 16; // 16-bit virtual addresses
constexpr int OPCODE_BITS = 8; // 8-bit opcodes (256 total)
constexpr int MIN_MICROINSTS_PER_INSTR = 2; // Fastest instructions: 2 microinstructions
constexpr int AVG_MICROINSTS_PER_INSTR = 6; // Average: 5-6 microinstructions
constexpr int MAX_MICROCODE_ROM_SIZE = 8192; // 8K microcode ROM entries
// Clock speed and timing
constexpr float CLOCK_SPEED_MHZ = 4.09f; // 4.09 MHz clock speed
constexpr int CYCLE_TIME_NS = 244; // Nanoseconds per cycle
// Memory map constants
constexpr uint16_t ROM_START = 0x0000; // ROM starts at address 0
constexpr uint16_t ROM_END = 0x3FFF; // ROM ends at 16KB-1
constexpr uint16_t RAM_START = 0x4000; // RAM starts at 16KB
constexpr uint16_t IO_START = 0xFF80; // I/O space starts at 0xFF80
constexpr uint16_t UART0_BASE = 0xFFF0; // UART0 base address
constexpr uint16_t UART1_BASE = 0xFFE0; // UART1 base address
constexpr uint16_t IDE_BASE = 0xFFB0; // IDE interface base address
// Page table constants
constexpr uint16_t PAGE_SIZE = 2048; // 2KB pages
constexpr uint8_t CODE_PAGES = 32; // 32 code pages
constexpr uint8_t DATA_PAGES = 32; // 32 data pages
constexpr uint8_t TOTAL_PAGES = 64; // 64 total pages
constexpr uint16_t PAGE_TABLE_BASE = 0x3800; // Default page table location
// Interrupt vector locations
constexpr uint16_t INT_VECTOR_RESET = 0x0000;
constexpr uint16_t INT_VECTOR_UART0 = 0x0002;
constexpr uint16_t INT_VECTOR_UART1 = 0x0004;
constexpr uint16_t INT_VECTOR_IDE = 0x0006;
constexpr uint16_t INT_VECTOR_TIMER = 0x0008;
constexpr uint16_t INT_VECTOR_PAGE_FAULT = 0x000A;
constexpr uint16_t INT_VECTOR_PROTECTION_VIOLATION = 0x000C;
constexpr uint16_t INT_VECTOR_PRIVILEGE_VIOLATION = 0x000E;
constexpr uint16_t INT_VECTOR_ILLEGAL_INSTRUCTION = 0x0010;
constexpr uint16_t INT_VECTOR_DIVIDE_BY_ZERO = 0x0012;
// Default trap handler addresses
constexpr uint16_t TRAP_HANDLER_PAGE_FAULT = 0x0100;
constexpr uint16_t TRAP_HANDLER_PROTECTION_VIOLATION = 0x0110;
constexpr uint16_t TRAP_HANDLER_PRIVILEGE_VIOLATION = 0x0120;
constexpr uint16_t TRAP_HANDLER_ILLEGAL_INSTRUCTION = 0x0130;
constexpr uint16_t TRAP_HANDLER_DIVIDE_BY_ZERO = 0x0140;
// Boot parameters memory block
constexpr uint16_t BOOT_PARAM_BLOCK = 0x0080; // Boot parameter block starts at 0x0080
The Magic-1 has a 64KB address space (16-bit addresses) with the following organization:
- ROM/Kernel Space (0x0000-0x3FFF): 16KB of ROM containing interrupt vectors, trap handlers, and kernel code
- RAM Space (0x4000-0xFF7F): 48KB of RAM for user programs and data
- I/O Space (0xFF80-0xFFFF): 128 bytes mapped to hardware devices
Memory paging provides virtual memory capabilities:
- 2KB page size
- Separate code and data page tables
- 32 code pages and 32 data pages
- Hardware page table base register (PTB)
- Memory Protection enabled via MSW register
Virtual-to-physical address translation:
- Architecture Overview
- Core Architecture Constants
- Memory Architecture
- CPU Registers
- Instruction Set
- Addressing Modes
- Microcode Implementation
- I/O Subsystem
- IDE/CF Card Interface
- UART Implementation
- Boot Process
- Emulator Components
- Data Structures
- Implementation Status
- Future Work
The Magic-1 is a 16-bit homebrew computer designed and built by Bill Buzbee using 74-series TTL logic. The emulator aims to recreate its functionality with cycle accuracy.
Key characteristics:
- 16-bit data path
- 16-bit memory addressing (64KB address space)
- 8-bit external data bus
- Microcoded implementation with 8K microcode ROM
- Three 16-bit general-purpose registers (A, B, C)
- Stack-based architecture with dedicated stack pointer
- Hardware support for memory protection and virtual memory
- Custom UART and IDE/CF interfaces
// CPU architecture constants
constexpr int DATA_BUS_WIDTH = 8; // 8-bit external data bus
constexpr int INTERNAL_DATA_WIDTH = 16; // 16-bit internal data paths
constexpr int PHYSICAL_ADDRESS_BITS = 22; // 22-bit physical address bus
constexpr int DEVICE_SELECT_BIT = 1; // 1 device select bit
constexpr int TOTAL_ADDRESS_BITS = 23; // 23-bit total addressing capability
constexpr int VIRTUAL_ADDRESS_BITS = 16; // 16-bit virtual addresses
constexpr int OPCODE_BITS = 8; // 8-bit opcodes (256 total)
constexpr int MIN_MICROINSTS_PER_INSTR = 2; // Fastest instructions: 2 microinstructions
constexpr int AVG_MICROINSTS_PER_INSTR = 6; // Average: 5-6 microinstructions
constexpr int MAX_MICROCODE_ROM_SIZE = 8192; // 8K microcode ROM entries
// Clock speed and timing
constexpr float CLOCK_SPEED_MHZ = 4.09f; // 4.09 MHz clock speed
constexpr int CYCLE_TIME_NS = 244; // Nanoseconds per cycle
// Memory map constants
constexpr uint16_t ROM_START = 0x0000; // ROM starts at address 0
constexpr uint16_t ROM_END = 0x3FFF; // ROM ends at 16KB-1
constexpr uint16_t RAM_START = 0x4000; // RAM starts at 16KB
constexpr uint16_t IO_START = 0xFF80; // I/O space starts at 0xFF80
constexpr uint16_t UART0_BASE = 0xFFF0; // UART0 base address
constexpr uint16_t UART1_BASE = 0xFFE0; // UART1 base address
constexpr uint16_t IDE_BASE = 0xFFB0; // IDE interface base address
// Page table constants
constexpr uint16_t PAGE_SIZE = 2048; // 2KB pages
constexpr uint8_t CODE_PAGES = 32; // 32 code pages
constexpr uint8_t DATA_PAGES = 32; // 32 data pages
constexpr uint8_t TOTAL_PAGES = 64; // 64 total pages
constexpr uint16_t PAGE_TABLE_BASE = 0x3800; // Default page table location
// Interrupt vector locations
constexpr uint16_t INT_VECTOR_RESET = 0x0000;
constexpr uint16_t INT_VECTOR_UART0 = 0x0002;
constexpr uint16_t INT_VECTOR_UART1 = 0x0004;
constexpr uint16_t INT_VECTOR_IDE = 0x0006;
constexpr uint16_t INT_VECTOR_TIMER = 0x0008;
constexpr uint16_t INT_VECTOR_PAGE_FAULT = 0x000A;
constexpr uint16_t INT_VECTOR_PROTECTION_VIOLATION = 0x000C;
constexpr uint16_t INT_VECTOR_PRIVILEGE_VIOLATION = 0x000E;
constexpr uint16_t INT_VECTOR_ILLEGAL_INSTRUCTION = 0x0010;
constexpr uint16_t INT_VECTOR_DIVIDE_BY_ZERO = 0x0012;
// Default trap handler addresses
constexpr uint16_t TRAP_HANDLER_PAGE_FAULT = 0x0100;
constexpr uint16_t TRAP_HANDLER_PROTECTION_VIOLATION = 0x0110;
constexpr uint16_t TRAP_HANDLER_PRIVILEGE_VIOLATION = 0x0120;
constexpr uint16_t TRAP_HANDLER_ILLEGAL_INSTRUCTION = 0x0130;
constexpr uint16_t TRAP_HANDLER_DIVIDE_BY_ZERO = 0x0140;
// Boot parameters memory block
constexpr uint16_t BOOT_PARAM_BLOCK = 0x0080; // Boot parameter block starts at 0x0080
The Magic-1 has a 64KB address space (16-bit addresses) with the following organization:
- ROM/Kernel Space (0x0000-0x3FFF): 16KB of ROM containing interrupt vectors, trap handlers, and kernel code
- RAM Space (0x4000-0xFF7F): 48KB of RAM for user programs and data
- I/O Space (0xFF80-0xFFFF): 128 bytes mapped to hardware devices
Memory paging provides virtual memory capabilities:
- 2KB page size
- Separate code and data page tables
- 32 code pages and 32 data pages
- Hardware page table base register (PTB)
- Memory Protection enabled via MSW register
Virtual-to-physical address translation: I'll create a comprehensive technical documentation file for the Magic-1 emulator project that covers all the architectural details, constants, and functions needed for a programmer to understand and continue development.
# Magic-1 Emulator Technical Documentation
## Table of Contents
1. [Architecture Overview](#architecture-overview)
2. [CPU Architecture](#cpu-architecture)
3. [Memory Architecture](#memory-architecture)
4. [Register Set](#register-set)
5. [Instruction Set](#instruction-set)
6. [Addressing Modes](#addressing-modes)
7. [Microcode Implementation](#microcode-implementation)
8. [I/O Subsystem](#io-subsystem)
9. [IDE/CF Card Interface](#idecf-card-interface)
10. [Boot Process](#boot-process)
11. [Emulator Implementation](#emulator-implementation)
12. [Implementation Status](#implementation-status)
13. [Future Work](#future-work)
## Architecture Overview
The Magic-1 is a 16-bit homebrew computer designed and built by Bill Buzbee using 74-series TTL logic. The architecture follows a one-address accumulator model with enhancements for C language support. This emulator aims to faithfully reproduce its behavior.
Key characteristics:
- 16-bit word size with 8-bit and 16-bit operations
- 16-bit virtual address space (64KB)
- Paged memory model with 22-bit physical address space
- Memory-mapped I/O
- External interrupt and DMA support
- Big-endian byte ordering
## CPU Architecture
### Architecture Constants
```cpp
// Word size
const int WORD_SIZE = 16; // 16-bit CPU
// Address space
const uint32_t VIRTUAL_ADDRESS_SPACE = 65536; // 64KB virtual address space
const uint32_t PHYSICAL_ADDRESS_SPACE = 4194304; // 4MB physical address space (22-bit)
// Page size
const uint16_t PAGE_SIZE = 2048; // 2KB pages
const uint16_t PAGE_COUNT = 64; // 32 code pages + 32 data pages
const uint16_t PAGE_OFFSET_MASK = 0x07FF; // 11 bits for page offset (0-2047)
const uint16_t PAGE_NUMBER_MASK = 0xF800; // 5 bits for page number (0-31)
const uint16_t PAGE_NUMBER_SHIFT = 11; // Shift to get page number
// Clock speed
const double DEFAULT_CLOCK_SPEED_MHZ = 4.0; // 4MHz default clock speed
// Interrupt lines
const int IRQ_COUNT = 6; // Six external interrupt request lines (IRQ0-IRQ5)
const int IRQ_PRIORITY_HIGHEST = 0; // IRQ0 = highest priority
const int IRQ_PRIORITY_LOWEST = 5; // IRQ5 = lowest priority
// Machine Status Word (MSW) bits
const uint16_t MSW_C = 0x0001; // Carry flag
const uint16_t MSW_Z = 0x0002; // Zero flag
const uint16_t MSW_S = 0x0004; // Sign flag
const uint16_t MSW_V = 0x0008; // Overflow flag
const uint16_t MSW_MODE = 0x0080; // Mode bit (0=supervisor, 1=user)
const uint16_t MSW_PAGING = 0x0040; // Paging enable
const uint16_t MSW_I = 0x0020; // Interrupt enable
const uint16_t MSW_DATA = 0x0010; // Data/code reference (in fault context)
// Page Table Entry (PTE) bits
const uint16_t PTE_P = 0x8000; // Page present
const uint16_t PTE_W = 0x4000; // Page writeable
const uint16_t PTE_M = 0x2000; // Memory vs device flag
const uint16_t PTE_RESERVED = 0x3000; // Reserved bits
const uint16_t PTE_PAGE_MASK = 0x0FFF; // Physical page number (bits 0-11)
// ROM/RAM switch
const bool ROM_MODE = true; // First 16KB is ROM
const bool RAM_MODE = false; // First 16KB is RAM
// Instruction timing
const int MIN_INSTRUCTION_CYCLES = 2; // Minimum cycles per instruction
const int AVG_INSTRUCTION_CYCLES = 6; // Average cycles per instruction
const int MAX_INSTRUCTION_CYCLES = 20; // Maximum cycles for complex instructions
// Memory regions
const uint16_t ROM_START = 0x0000; // ROM start address
const uint16_t ROM_END = 0x3FFF; // ROM end address (16KB)
const uint16_t RAM_START = 0x4000; // RAM start address
const uint16_t RAM_END = 0xFFFF; // RAM end address
// Device memory map (memory-mapped I/O)
const uint16_t UART0_BASE = 0xFFF0; // UART #0 (8 bytes)
const uint16_t UART1_BASE = 0xFFE0; // UART #1 (8 bytes)
const uint16_t RTC_BASE = 0xFFD0; // Real-time clock (8 bytes)
const uint16_t POST_DISPLAY = 0xFFC0; // 2-digit hex display (16 bytes)
const uint16_t IDE_BASE = 0xFFB0; // IDE interface (16 bytes)
const uint16_t PANEL_SWITCH = 0xFFA0; // Front panel switch block (16 bytes)
// Interrupt vector table (at beginning of address space)
const uint16_t INT_VECTOR_RESET = 0x0000; // Reset vector
const uint16_t INT_VECTOR_UART0 = 0x0002; // UART0 interrupt vector
const uint16_t INT_VECTOR_UART1 = 0x0004; // UART1 interrupt vector
const uint16_t INT_VECTOR_IDE = 0x0006; // IDE interrupt vector
const uint16_t INT_VECTOR_UNUSED = 0x0008;// Unused interrupt vector
const uint16_t INT_VECTOR_RTC = 0x000A; // RTC interrupt vector
// Default trap handler locations
const uint16_t TRAP_PAGE_FAULT = 0x0100; // Page fault handler
const uint16_t TRAP_PROTECTION_VIOLATION = 0x0110;// Protection violation handler
const uint16_t TRAP_PRIVILEGE_VIOLATION = 0x0120; // Privilege violation handler
const uint16_t TRAP_ILLEGAL_INSTRUCTION = 0x0130; // Illegal instruction handler
const uint16_t TRAP_DIVIDE_BY_ZERO = 0x0140; // Divide by zero handler
// Boot parameter block
const uint16_t BOOT_PARAMS_ADDR = 0x0080; // Boot parameters address
// General-purpose registers
const int REG_A = 0; // Accumulator (primary register)
const int REG_B = 1; // General register (often source operand)
const int REG_C = 2; // Count register for block operations and shifts
// Special-purpose registers
const int REG_MSW = 3; // Machine Status Word (flags and control)
const int REG_DP = 4; // Data Pointer (global data base pointer)
const int REG_SP = 5; // Stack Pointer (current stack top)
const int REG_SSP = 6; // Supervisor Stack Pointer (for supervisor mode)
const int REG_PC = 7; // Program Counter (next instruction address)
const int REG_PTB = 8; // Page Table Base (current page table location)
// Internal registers (not directly accessible by instructions)
const int REG_MAR = 9; // Memory Address Register
const int REG_MDR = 10; // Memory Data Register
const int REG_IR = 11; // Instruction Register
const int REG_TPC = 12; // Temporary Program Counter (for exceptions)
The Magic-1 instruction set contains 256 possible instructions with 8-bit opcodes.
// Instruction categories
enum InstructionCategory {
LOAD_STORE, // Load/store operations
ADDRESS_MANIP, // Address manipulation
REGISTER_ACCESS, // Special register access
ARITHMETIC, // Arithmetic operations
LOGICAL, // Logical operations
SHIFT, // Shift operations
CONTROL_FLOW, // Control flow instructions
SYSTEM // System operations
};
// Instruction formats
enum InstructionFormat {
ZERO_OPERAND, // No operands
REG_OPERAND, // Register operand
IMM8_OPERAND, // 8-bit immediate operand
IMM16_OPERAND, // 16-bit immediate operand
MEM_OPERAND, // Memory operand
REG_MEM_OPERAND, // Register and memory operands
REL_BRANCH // Relative branch offset
};
// Load/Store Instructions
const uint8_t LD8_A_IMM8 = 0x78; // ld.8 A,#u8
const uint8_t LD8_B_IMM8 = 0x79; // ld.8 B,#u8
const uint8_t LD16_A_IMM16 = 0x7C; // ld.16 A,#u16
const uint8_t LD16_B_IMM16 = 0x7D; // ld.16 B,#u16
const uint8_t LD16_C_IMM16 = 0xCE; // ld.16 C,#u16
const uint8_t LD8_A_MEM = 0x10; // ld.8 A,#u16(DP)
const uint8_t LD16_A_MEM = 0x18; // ld.16 A,#u16(DP)
const uint8_t ST8_MEM_A = 0xD0; // st.8 #u16(DP),A
const uint8_t ST16_MEM_A = 0xD8; // st.16 #u16(DP),A
// Stack operations
const uint8_t PUSH_A = 0x06; // push A
const uint8_t PUSH_B = 0x07; // push B
const uint8_t PUSH_C = 0x02; // push C
const uint8_t PUSH_MSW = 0x22; // push MSW
const uint8_t PUSH_DP = 0x04; // push DP
const uint8_t PUSH_PC = 0x03; // push PC
const uint8_t PUSH_SP = 0x26; // push SP
const uint8_t POP_A = 0x0E; // pop A
const uint8_t POP_B = 0x0F; // pop B
const uint8_t POP_C = 0x0A; // pop C
const uint8_t POP_MSW = 0x09; // pop MSW
const uint8_t POP_DP = 0x0C; // pop DP
const uint8_t POP_PC = 0x0B; // pop PC
const uint8_t POP_SP = 0x0D; // pop SP
// Arithmetic operations
const uint8_t ADD8_A_IMM8 = 0x34; // add.8 A,#i8
const uint8_t ADD8_A_B = 0x37; // add.8 A,B
const uint8_t ADD16_A_IMM16 = 0x3C; // add.16 A,#i16
const uint8_t ADD16_A_B = 0x3F; // add.16 A,B
const uint8_t SUB8_A_IMM8 = 0x24; // sub.8 A,#i8
const uint8_t SUB8_A_B = 0x27; // sub.8 A,B
const uint8_t SUB16_A_IMM16 = 0x2C; // sub.16 A,#i16
const uint8_t SUB16_A_B = 0x2F; // sub.16 A,B
const uint8_t ADC16_A_B = 0x7F; // adc.16 A,B
const uint8_t SBC16_A_B = 0xED; // sbc.16 A,B
// Logical operations
const uint8_t AND8_A_IMM8 = 0x64; // and.8 A,#i8
const uint8_t AND8_A_B = 0x67; // and.8 A,B
const uint8_t AND16_A_IMM16 = 0x6C; // and.16 A,#i16
const uint8_t AND16_A_B = 0x6F; // and.16 A,B
const uint8_t OR8_A_IMM8 = 0x54; // or.8 A,#i8
const uint8_t OR8_A_B = 0x57; // or.8 A,B
const uint8_t OR16_A_IMM16 = 0x5C; // or.16 A,#i16
const uint8_t OR16_A_B = 0x5F; // or.16 A,B
const uint8_t XOR16_A_B = 0xC7; // xor.16 A,B
// Comparison operations
const uint8_t CMP8_A_IMM8 = 0x44; // cmp.8 A,#i8
const uint8_t CMP8_A_B = 0x47; // cmp.8 A,B
const uint8_t CMP16_A_IMM16 = 0x4C; // cmp.16 A,#i16
const uint8_t CMP16_A_B = 0x4F; // cmp.16 A,B
// Shift operations
const uint8_t SHL16_A = 0xC2; // shl.16 A
const uint8_t SHR16_A = 0xC3; // shr.16 A
const uint8_t SHL16_B = 0xC4; // shl.16 B
const uint8_t SHR16_B = 0xC6; // shr.16 B
const uint8_t VSHL16_A = 0xE6; // vshl.16 A (shift count in C)
const uint8_t VSHR16_A = 0xEE; // vshr.16 A (shift count in C)
const uint8_t VSHL16_B = 0xE7; // vshl.16 B (shift count in C)
const uint8_t VSHR16_B = 0xEF; // vshr.16 B (shift count in C)
// Branch instructions
const uint8_t BR_EQ = 0x89; // br.eq #d16
const uint8_t BR_NE = 0x08; // br.ne #d16
const uint8_t BR_LT = 0xA6; // br.lt #d16
const uint8_t BR_LE = 0xB5; // br.le #d16
const uint8_t BR_GT = 0xBE; // br.gt #d16
const uint8_t BR_GE = 0xAE; // br.ge #d16
const uint8_t BR_LTU = 0xCF; // br.ltu #d16
const uint8_t BR_LEU = 0x56; // br.leu #d16
const uint8_t BR_GTU = 0x5E; // br.gtu #d16
const uint8_t BR_GEU = 0xC0; // br.geu #d16
const uint8_t BR = 0x83; // br #d16
const uint8_t SBR = 0x84; // sbr #d8 (short branch)
const uint8_t BR_A = 0x32; // br A (branch to address in A)
// Call/Return instructions
const uint8_t CALL = 0x80; // call #d16
const uint8_t CALL_A = 0x82; // call A
const uint8_t RET = 0x0B; // ret (alias for pop PC)
const uint8_t RETI = 0x8A; // reti (return from interrupt)
const uint8_t ENTER = 0xE4; // enter #fsize16
// System instructions
const uint8_t HALT = 0x00; // halt
const uint8_t TRAPO = 0x8B; // trapo (trap on overflow)
const uint8_t SYSCALL = 0x3A; // syscall #sys_num8
const uint8_t BKPT = 0xFA; // bkpt (breakpoint)
const uint8_t WCPTE = 0x2E; // wcpte A,(B) (write code page table entry)
const uint8_t WDPTE = 0xEC; // wdpte A,(B) (write data page table entry)
// Memory operations
const uint8_t MEMCOPY = 0xE8; // memcopy (block copy, count in C)
const uint8_t TOSYS = 0xE9; // tosys (copy to system space)
const uint8_t FROMSYS = 0xEA; // fromsys (copy from system space)
const uint8_t LDCODE8 = 0xE0; // ldcode.8 A,(B) (load from code space)
const uint8_t LDCLR8 = 0xEB; // ldclr.8 A,(B) (atomic load and clear)
// Miscellaneous
const uint8_t LEA_A = 0x70; // lea A,#addr (load effective address)
const uint8_t LEA_B = 0x74; // lea B,#addr
const uint8_t SEX_A = 0x52; // sex A (sign extend A)
const uint8_t SEX_B = 0xB2; // sex B (sign extend B)
const uint8_t NOP = 0x66; // nop (no operation)
// Addressing modes
enum AddressingMode {
AM_REGISTER, // Register (R)
AM_IMMEDIATE, // Immediate value (#imm)
AM_REGISTER_INDIRECT, // Register indirect with offset (R+offset)
AM_FRAME_LOCAL, // Frame local with offset (SP+offset)
AM_GLOBAL, // Global with offset (DP+offset)
AM_PC_RELATIVE, // PC-relative (PC+offset)
AM_PUSH, // Push (--SP)
AM_POP, // Pop (SP++)
AM_ABSOLUTE // Absolute address (addr)
};
// Register types for addressing
enum BaseRegisterType {
BR_A, // A register
BR_B, // B register
BR_SP, // Stack pointer
BR_DP, // Data pointer
BR_PC // Program counter
};
// Offset sizes for addressing modes
enum OffsetSize {
OS_NONE, // No offset
OS_8BIT, // 8-bit signed offset
OS_16BIT // 16-bit signed offset
};
The Magic-1 uses microcode to implement its instruction set. Each instruction maps to a sequence of microoperations.
// Microcode fields
struct MicrocodeFields {
uint8_t next; // Next microcode address (0x00=fetch, 0xFF=decode)
uint8_t latch; // Register latch control
uint8_t lmar; // Latch MAR flag
uint8_t lmdrlo; // Latch MDR (low byte) flag
uint8_t lmdrhi; // Latch MDR (high byte) flag
uint8_t emdrlo; // Enable MDR (low byte) on data bus
uint8_t emdrhi; // Enable MDR (high byte) on data bus
uint8_t priv; // Privileged instruction flag
uint8_t lmode; // Latch MODE bit in MSW
uint8_t lpaging; // Latch PAGING bit in MSW
uint8_t misc; // Miscellaneous control signals
uint8_t e_l; // Enable L-bus
uint8_t e_r; // Enable R-bus
uint8_t immval; // Immediate value
uint8_t aluop_size; // ALU operation size (0=16-bit, 1=8-bit)
uint8_t aluop; // ALU operation code
uint8_t carry; // Carry input to ALU
uint8_t l_size; // Latch size (0=byte, 1=word)
uint8_t br_sense; // Branch sense
uint8_t user_ptb; // User PTB override
uint8_t code_ptb; // Code PTB select (0=data, 1=code)
};
// Microcode control signal values
// Register latch values (LATCH field)
const uint8_t L_NONE = 0; // No register latching
const uint8_t L_MSW = 1; // Latch MSW
const uint8_t L_C = 2; // Latch C register
const uint8_t L_PC = 3; // Latch PC
const uint8_t L_DP = 4; // Latch DP
const uint8_t L_SP = 5; // Latch SP
const uint8_t L_A = 6; // Latch A register
const uint8_t L_B = 7; // Latch B register
const uint8_t L_MDR = 8; // Latch MDR
const uint8_t L_PTB = 9; // Latch PTB
const uint8_t L_SSP = 14; // Latch SSP
const uint8_t L_IR_REG = 15; // Latch IR register
// Miscellaneous control values (MISC field)
const uint8_t M_NONE = 0; // No misc operation
const uint8_t M_SYSCALL = 1; // System call
const uint8_t M_HALT = 2; // Halt CPU
const uint8_t M_BKPT = 3; // Breakpoint
const uint8_t M_TRAPO = 4; // Trap on overflow
const uint8_t M_LPTE = 5; // Latch PTE
const uint8_t M_SET_FLAGS = 6; // Set flags from ALU operation
const uint8_t M_INIT_INST = 7; // Initialize instruction
const uint8_t M_RSHIFT = 8; // Right shift ALU output
const uint8_t M_DMA_ACK = 9; // DMA acknowledge
const uint8_t M_LEI = 10; // Latch interrupt enable
const uint8_t M_DO_BRANCH = 11; // Do conditional branch
const uint8_t M_CLR_TRAP = 12; // Clear trap flag
const uint8_t M_COMMIT = 13; // Commit state changes
// L-bus source select values (E_L field)
const uint8_t EL_MAR = 0; // MAR to L-bus
const uint8_t EL_MSW = 1; // MSW to L-bus
const uint8_t EL_C = 2; // C register to L-bus
const uint8_t EL_PC = 3; // PC to L-bus
const uint8_t EL_DP = 4; // DP to L-bus
const uint8_t EL_SP = 5; // SP to L-bus
const uint8_t EL_A = 6; // A register to L-bus
const uint8_t EL_B = 7; // B register to L-bus
const uint8_t EL_MDR = 8; // MDR to L-bus
const uint8_t EL_PTB = 9; // PTB to L-bus
const uint8_t EL_SSP = 10; // SSP to L-bus
const uint8_t EL_TPC = 11; // TPC to L-bus
const uint8_t EL_FCODE = 12; // Fault code to L-bus
const uint8_t EL_IR_BASE = 15; // IR base to L-bus
// R-bus source select values (E_R field)
const uint8_t ER_MDR = 0; // MDR to R-bus
const uint8_t ER_IMM = 1; // Immediate value to R-bus
const uint8_t ER_FAULT = 2; // Fault code to R-bus
// Immediate values (IMMVAL field)
const uint8_t IMM_0 = 0; // Immediate value 0
const uint8_t IMM_1 = 1; // Immediate value 1
const uint8_t IMM_NEG2 = 2; // Immediate value -2
const uint8_t IMM_NEG1 = 3; // Immediate value -1
// ALU operations (ALUOP field)
const uint8_t OP_IR13 = 0; // Use IR[1..3] to select operation
const uint8_t OP_AND = 1; // Logical AND
const uint8_t OP_SUB = 2; // Subtraction
const uint8_t OP_ADD = 3; // Addition
// Operation size (ALUOP_SIZE field)
const uint8_t WORD = 0; // 16-bit operation
const uint8_t BYTE = 1; // 8-bit operation
// Latch size (L_SIZE field)
const uint8_t LBYTE = 0; // Latch byte
const uint8_t LWORD = 1; // Latch word
// Carry input (CARRY field)
const uint8_t NO_CARRY = 0; // No carry input
const uint8_t CARRY_IN = 1; // Use carry flag as input
// Branch sense (BR_SENSE field)
const uint8_t B_NORMAL = 0; // Normal branch condition
const uint8_t B_NEGATED = 1; // Negated branch condition
// Page table selection
const uint8_t DATA_SPACE = 0; // Select data space
const uint8_t CODE_SPACE = 1; // Select code space
const uint8_t PTB_NORMAL = 0; // Normal PTB
const uint8_t PTB_OVERRIDE = 1; // Override PTB
The Magic-1 uses memory-mapped I/O with devices mapped to high memory addresses.
// UART registers (8250/16550 compatible)
// Base addresses: UART0_BASE (0xFFF0), UART1_BASE (0xFFE0)
const uint8_t UART_REG_DATA = 0; // Data register (RBR/THR)
const uint8_t UART_REG_IER = 1; // Interrupt Enable Register
const uint8_t UART_REG_IIR = 2; // Interrupt Identification Register (read)
const uint8_t UART_REG_FCR = 2; // FIFO Control Register (write)
const uint8_t UART_REG_LCR = 3; // Line Control Register
const uint8_t UART_REG_MCR = 4; // Modem Control Register
const uint8_t UART_REG_LSR = 5; // Line Status Register
const uint8_t UART_REG_MSR = 6; // Modem Status Register
const uint8_t UART_REG_SCR = 7; // Scratch Register
// UART Line Status Register bits
const uint8_t UART_LSR_DR = 0x01; // Data Ready
const uint8_t UART_LSR_OE = 0x02; // Overrun Error
const uint8_t UART_LSR_PE = 0x04; // Parity Error
const uint8_t UART_LSR_FE = 0x08; // Framing Error
const uint8_t UART_LSR_BI = 0x10; // Break Interrupt
const uint8_t UART_LSR_THRE = 0x20; // Transmitter Holding Register Empty
const uint8_t UART_LSR_TEMT = 0x40; // Transmitter Empty
const uint8_t UART_LSR_FIFO_ERR = 0x80; // Error in FIFO
// UART Interrupt Enable Register bits
const uint8_t UART_IER_RDA = 0x01; // Received Data Available
const uint8_t UART_IER_THRE = 0x02; // Transmitter Holding Register Empty
const uint8_t UART_IER_RLS = 0x04; // Receiver Line Status
const uint8_t UART_IER_MSI = 0x08; // Modem Status Interrupt
// UART Line Control Register bits
const uint8_t UART_LCR_WLS0 = 0x01; // Word Length Select bit 0
const uint8_t UART_LCR_WLS1 = 0x02; // Word Length Select bit 1
const uint8_t UART_LCR_STB = 0x04; // Stop Bits (0=1 stop, 1=2 stop)
const uint8_t UART_LCR_PEN = 0x08; // Parity Enable
const uint8_t UART_LCR_EPS = 0x10; // Even Parity Select
const uint8_t UART_LCR_STICK = 0x20; // Stick Parity
const uint8_t UART_LCR_BREAK = 0x40; // Set Break
const uint8_t UART_LCR_DLAB = 0x80; // Divisor Latch Access Bit
// UART default settings
const uint8_t UART_DEFAULT_LCR = 0x03; // 8 bits, 1 stop bit, no parity
const uint32_t UART_DEFAULT_BAUD = 9600; // 9600 baud rate
// IDE interface registers (base address: IDE_BASE 0xFFB0)
const uint8_t IDE_REG_DATA = 0; // Data register
const uint8_t IDE_REG_ERROR = 1; // Error register (read)
const uint8_t IDE_REG_FEATURES = 1; // Features register (write)
const uint8_t IDE_REG_SECTOR_COUNT = 2; // Sector count
const uint8_t IDE_REG_SECTOR_NUM = 3; // Sector number (LBA lo)
const uint8_t IDE_REG_CYL_LOW = 4; // Cylinder low (LBA mid)
const uint8_t IDE_REG_CYL_HIGH = 5; // Cylinder high (LBA hi)
const uint8_t IDE_REG_DRIVE_HEAD = 6; // Drive/Head (LBA top 4 bits)
const uint8_t IDE_REG_STATUS = 7; // Status register (read)
const uint8_t IDE_REG_COMMAND = 7; // Command register (write)
// IDE register bit masks
const uint8_t IDE_STATUS_BSY = 0x80; // Busy
const uint8_t IDE_STATUS_DRDY = 0x40; // Drive ready
const uint8_t IDE_STATUS_DF = 0x20; // Drive fault
const uint8_t IDE_STATUS_DSC = 0x10; // Drive seek complete
const uint8_t IDE_STATUS_DRQ = 0x08; // Data request
const uint8_t IDE_STATUS_CORR = 0x04; // Corrected data
const uint8_t IDE_STATUS_IDX = 0x02; // Index
const uint8_t IDE_STATUS_ERR = 0x01; // Error
// IDE error register bits
const uint8_t IDE_ERR_BBK = 0x80; // Bad block
const uint8_t IDE_ERR_UNC = 0x40; // Uncorrectable data error
const uint8_t IDE_ERR_MC = 0x20; // Media changed
const uint8_t IDE_ERR_IDNF = 0x10; // ID not found
const uint8_t IDE_ERR_MCR = 0x08; // Media change request
const uint8_t IDE_ERR_ABRT = 0x04; // Command aborted
const uint8_t IDE_ERR_TK0NF = 0x02; // Track 0 not found
const uint8_t IDE_ERR_AMNF = 0x01; // Address mark not found
// IDE commands
const uint8_t IDE_CMD_READ_SECTORS = 0x20; // Read sectors
const uint8_t IDE_CMD_WRITE
I'll create a comprehensive technical document for the Magic-1 emulator in programmer.md that focuses on architecture constants, functions, and implementation details. The document will be well-structured and contain all the necessary information for a new programmer to understand and continue work on the project.
```markdown
# Magic-1 Emulator: Technical Reference
## Table of Contents
1. [Architecture Overview](#architecture-overview)
2. [CPU Architecture Constants](#cpu-architecture-constants)
3. [Memory Architecture](#memory-architecture)
4. [Register Set](#register-set)
5. [Instruction Set](#instruction-set)
6. [Addressing Modes](#addressing-modes)
7. [Machine Status Word (MSW)](#machine-status-word-msw)
8. [Memory Paging](#memory-paging)
9. [Microcode Implementation](#microcode-implementation)
10. [I/O Subsystem](#io-subsystem)
11. [UART Implementation](#uart-implementation)
12. [IDE/CF Card Interface](#idecf-card-interface)
13. [Boot Process](#boot-process)
14. [Emulator Components](#emulator-components)
15. [Core Classes](#core-classes)
16. [Key Functions](#key-functions)
17. [Implementation Status](#implementation-status)
18. [Future Work](#future-work)
## Architecture Overview
Magic-1 is a 16-bit homebrew computer designed by Bill Buzbee. It features:
- 16-bit word size with one-address accumulator design
- 8-bit and 16-bit operations
- 64KB virtual address space (16-bit)
- 4MB physical address space (22-bit)
- Big-endian byte order
- Memory-mapped I/O
- Hardware support for memory paging
## CPU Architecture Constants
```cpp
// Word size
constexpr int WORD_SIZE_BITS = 16; // 16-bit word size
constexpr int BYTE_SIZE_BITS = 8; // 8-bit byte size
constexpr int BYTES_PER_WORD = 2; // 2 bytes per word
// Address space
constexpr uint32_t VIRTUAL_ADDRESS_SPACE = 65536; // 64KB virtual address space
constexpr uint32_t PHYSICAL_ADDRESS_SPACE = 4194304; // 4MB physical address space (22-bit)
constexpr uint32_t DEVICE_ADDRESS_SPACE = 4194304; // 4MB device address space (1-bit select)
// Instruction encoding
constexpr int OPCODE_SIZE = 8; // 8-bit opcode field
constexpr int MAX_INSTR_SIZE = 3; // Maximum instruction size in bytes
constexpr int MAX_OPERAND_SIZE = 2; // Maximum operand size in bytes
// Clock and timing
constexpr double DEFAULT_CLOCK_SPEED_MHZ = 4.0; // 4MHz default clock speed
constexpr int CLOCK_PERIOD_NS = 250; // 250ns clock period (4MHz)
constexpr int MIN_CYCLES_PER_INSTRUCTION = 2; // Minimum cycles per instruction
constexpr int AVG_CYCLES_PER_INSTRUCTION = 6; // Average cycles per instruction
constexpr int MAX_CYCLES_PER_INSTRUCTION = 20; // Maximum cycles per instruction
// Interrupt system
constexpr int IRQ_LINES = 6; // 6 hardware interrupt lines
constexpr int IRQ_HIGHEST_PRIORITY = 0; // IRQ0 has highest priority
constexpr int IRQ_LOWEST_PRIORITY = 5; // IRQ5 has lowest priority
// Microcode
constexpr int MICROCODE_ADDRESS_BITS = 13; // 13-bit microcode address space
constexpr int MICROCODE_ROM_SIZE = 8192; // 8K microcode ROM entries
constexpr int MICROCODE_WORD_WIDTH = 56; // 56-bit wide microinstruction word
// Memory map constants
constexpr uint16_t MEMORY_SIZE = 65536; // 64KB address space
constexpr uint16_t ROM_START = 0x0000; // ROM start
constexpr uint16_t ROM_END = 0x3FFF; // ROM end (16KB)
constexpr uint16_t ROM_SIZE = 0x4000; // ROM size (16KB)
constexpr uint16_t RAM_START = 0x4000; // RAM start
constexpr uint16_t RAM_END = 0xFF7F; // RAM end
constexpr uint16_t RAM_SIZE = 0xBF80; // RAM size (approx 48KB)
constexpr uint16_t IO_START = 0xFF80; // I/O space start
constexpr uint16_t IO_END = 0xFFFF; // I/O space end
constexpr uint16_t IO_SIZE = 0x0080; // I/O space size (128 bytes)
// Boot parameters block
constexpr uint16_t BOOT_PARAM_BLOCK = 0x0080; // Boot parameter block address
constexpr uint16_t BOOT_PARAM_SIZE = 0x0020; // Boot parameter block size
// Interrupt vector table
constexpr uint16_t INT_VECTOR_BASE = 0x0000; // Interrupt vector table base
constexpr uint16_t INT_VECTOR_SIZE = 2; // 2 bytes per vector (address)
constexpr uint16_t INT_VECTOR_COUNT = 8; // 8 interrupt vectors
// Trap handler addresses
constexpr uint16_t PAGE_FAULT_HANDLER = 0x0100; // Page fault handler
constexpr uint16_t PROTECTION_VIOLATION_HANDLER = 0x0110; // Protection violation
constexpr uint16_t PRIVILEGE_VIOLATION_HANDLER = 0x0120; // Privilege violation
constexpr uint16_t ILLEGAL_INSTRUCTION_HANDLER = 0x0130; // Illegal instruction
constexpr uint16_t DIVIDE_BY_ZERO_HANDLER = 0x0140; // Divide by zero
// Memory-mapped device addresses
constexpr uint16_t UART0_BASE = 0xFFF0; // UART0 base (console)
constexpr uint16_t UART1_BASE = 0xFFE0; // UART1 base (auxiliary)
constexpr uint16_t RTC_BASE = 0xFFD0; // Real-time clock base
constexpr uint16_t POST_DISPLAY_BASE = 0xFFC0; // POST display base
constexpr uint16_t IDE_BASE = 0xFFB0; // IDE interface base
constexpr uint16_t PANEL_SWITCH_BASE = 0xFFA0; // Front panel switch base
// Register indices
enum RegisterIndex {
REG_A = 0, // Accumulator (primary register)
REG_B = 1, // Secondary register (often source operand)
REG_C = 2, // Count register (for block ops and shifts)
REG_MSW = 3, // Machine Status Word (flags and control)
REG_DP = 4, // Data Pointer (global base address)
REG_SP = 5, // Stack Pointer
REG_SSP = 6, // Supervisor Stack Pointer
REG_PC = 7, // Program Counter
REG_PTB = 8, // Page Table Base
// Internal registers (not directly accessible)
REG_MAR = 9, // Memory Address Register
REG_MDR = 10, // Memory Data Register
REG_IR = 11, // Instruction Register
REG_TPC = 12, // Temp Program Counter (saved during exceptions)
REG_COUNT = 13 // Total number of registers
};
// Register sizes
constexpr int REGISTER_WIDTH = 16; // 16-bit registers
constexpr int REGISTER_BYTE_SIZE = 2; // 2 bytes per register
// MSW bit positions
constexpr uint16_t MSW_C = 0x0001; // Carry flag (bit 0)
constexpr uint16_t MSW_Z = 0x0002; // Zero flag (bit 1)
constexpr uint16_t MSW_V = 0x0004; // Overflow flag (bit 2)
constexpr uint16_t MSW_S = 0x0008; // Sign flag (bit 3)
constexpr uint16_t MSW_DATA = 0x0010; // Data/code flag (bit 4, in fault context)
constexpr uint16_t MSW_I = 0x0020; // Interrupt enable (bit 5)
constexpr uint16_t MSW_P = 0x0040; // Paging enable (bit 6)
constexpr uint16_t MSW_SV = 0x0080; // Supervisor mode (bit 7)
// MSW bit positions by name
constexpr int MSW_BIT_CARRY = 0; // Carry flag bit position
constexpr int MSW_BIT_ZERO = 1; // Zero flag bit position
constexpr int MSW_BIT_OVERFLOW = 2; // Overflow flag bit position
constexpr int MSW_BIT_SIGN = 3; // Sign flag bit position
constexpr int MSW_BIT_DATA = 4; // Data flag bit position
constexpr int MSW_BIT_INTERRUPT = 5; // Interrupt enable bit position
constexpr int MSW_BIT_PAGING = 6; // Paging enable bit position
constexpr int MSW_BIT_SUPERVISOR = 7; // Supervisor mode bit position
// MSW default values
constexpr uint16_t MSW_RESET_VALUE = 0x0080; // Default after reset: supervisor mode
// Paging constants
constexpr int PAGE_SHIFT = 11; // 2KB pages (2^11)
constexpr uint16_t PAGE_SIZE = (1 << PAGE_SHIFT); // 2048 bytes per page
constexpr uint16_t PAGE_MASK = (PAGE_SIZE - 1); // Mask for extracting offset (0x07FF)
constexpr uint16_t PAGE_NUMBER_MASK = ~PAGE_MASK; // Mask for page number (0xF800)
constexpr int PAGES_PER_PROCESS = 64; // 32 code + 32 data pages
constexpr int CODE_PAGES = 32; // 32 code pages
constexpr int DATA_PAGES = 32; // 32 data pages
// Page table entry constants
constexpr uint16_t PTE_P = 0x8000; // Page Present bit (15)
constexpr uint16_t PTE_W = 0x4000; // Page Writeable bit (14)
constexpr uint16_t PTE_M = 0x2000; // Page Memory Type bit (13)
constexpr uint16_t PTE_RESERVED = 0x3000; // Reserved bits (12-11)
constexpr uint16_t PTE_PAGE_MASK = 0x0FFF; // Physical page number (11-0)
// Page table entry bit positions
constexpr int PTE_BIT_PRESENT = 15; // Present bit position
constexpr int PTE_BIT_WRITEABLE = 14; // Writeable bit position
constexpr int PTE_BIT_MEMORY = 13; // Memory type bit position
// Instruction categories
enum InstructionCategory {
IC_LOAD_STORE, // Load/store operations
IC_ARITHMETIC, // Arithmetic operations
IC_LOGICAL, // Logical operations
IC_SHIFT, // Shift operations
IC_BRANCH, // Branches and jumps
IC_SUBROUTINE, // Subroutine operations
IC_STACK, // Stack operations
IC_IO, // I/O operations
IC_SYSTEM, // System control operations
IC_COMPARISON, // Comparison operations
IC_SPECIAL // Special operations
};
// Instruction formats
enum InstructionFormat {
IF_IMPLICIT, // No operands (e.g., NOP)
IF_REGISTER, // Register operand(s)
IF_IMMEDIATE8, // 8-bit immediate operand
IF_IMMEDIATE16, // 16-bit immediate operand
IF_MEMORY8, // 8-bit memory operand
IF_MEMORY16, // 16-bit memory operand
IF_RELATIVE8, // 8-bit relative branch
IF_RELATIVE16 // 16-bit relative branch
};
// Key instruction opcodes
// Register operations
constexpr uint8_t OP_MOV_A_B = 0x9A; // copy A,B
constexpr uint8_t OP_MOV_A_C = 0xF2; // copy A,C
constexpr uint8_t OP_MOV_B_A = 0x92; // copy B,A
constexpr uint8_t OP_MOV_C_A = 0x96; // copy C,A
constexpr uint8_t OP_MOV_C_B = 0x42; // copy C,B
constexpr uint8_t OP_MOV_B_C = 0xF6; // copy B,C
// Arithmetic operations
constexpr uint8_t OP_ADD8_A_IMM = 0x34; // add.8 A,#i8
constexpr uint8_t OP_ADD8_A_B = 0x37; // add.8 A,B
constexpr uint8_t OP_ADD16_A_IMM = 0x3C; // add.16 A,#i16
constexpr uint8_t OP_ADD16_A_B = 0x3F; // add.16 A,B
constexpr uint8_t OP_SUB8_A_IMM = 0x24; // sub.8 A,#i8
constexpr uint8_t OP_SUB8_A_B = 0x27; // sub.8 A,B
constexpr uint8_t OP_SUB16_A_IMM = 0x2C; // sub.16 A,#i16
constexpr uint8_t OP_SUB16_A_B = 0x2F; // sub.16 A,B
constexpr uint8_t OP_ADC16_A_B = 0x7F; // adc.16 A,B
constexpr uint8_t OP_SBC16_A_B = 0xED; // sbc.16 A,B
// Logical operations
constexpr uint8_t OP_AND8_A_IMM = 0x64; // and.8 A,#i8
constexpr uint8_t OP_AND8_A_B = 0x67; // and.8 A,B
constexpr uint8_t OP_AND16_A_IMM = 0x6C; // and.16 A,#i16
constexpr uint8_t OP_AND16_A_B = 0x6F; // and.16 A,B
constexpr uint8_t OP_OR8_A_IMM = 0x54; // or.8 A,#i8
constexpr uint8_t OP_OR8_A_B = 0x57; // or.8 A,B
constexpr uint8_t OP_OR16_A_IMM = 0x5C; // or.16 A,#i16
constexpr uint8_t OP_OR16_A_B = 0x5F; // or.16 A,B
constexpr uint8_t OP_XOR16_A_A = 0x46; // xor.16 A,A
constexpr uint8_t OP_XOR16_A_B = 0xC7; // xor.16 A,B
// Load/store operations
constexpr uint8_t OP_LD8_A_IMM = 0x78; // ld.8 A,#u8
constexpr uint8_t OP_LD8_B_IMM = 0x79; // ld.8 B,#u8
constexpr uint8_t OP_LD16_A_IMM = 0x7C; // ld.16 A,#u16
constexpr uint8_t OP_LD16_B_IMM = 0x7D; // ld.16 B,#u16
constexpr uint8_t OP_LD16_C_IMM = 0xCE; // ld.16 C,#u16
constexpr uint8_t OP_LD8_A_DP = 0x10; // ld.8 A,#u16(DP)
constexpr uint8_t OP_LD16_A_DP = 0x18; // ld.16 A,#u16(DP)
constexpr uint8_t OP_ST8_DP_A = 0xD0; // st.8 #u16(DP),A
constexpr uint8_t OP_ST16_DP_A = 0xD8; // st.16 #u16(DP),A
// Stack operations
constexpr uint8_t OP_PUSH_A = 0x06; // push A
constexpr uint8_t OP_PUSH_B = 0x07; // push B
constexpr uint8_t OP_PUSH_C = 0x02; // push C
constexpr uint8_t OP_PUSH_MSW = 0x22; // push MSW
constexpr uint8_t OP_PUSH_PC = 0x03; // push PC
constexpr uint8_t OP_PUSH_DP = 0x04; // push DP
constexpr uint8_t OP_PUSH_SP = 0x26; // push SP
constexpr uint8_t OP_POP_A = 0x0E; // pop A
constexpr uint8_t OP_POP_B = 0x0F; // pop B
constexpr uint8_t OP_POP_C = 0x0A; // pop C
constexpr uint8_t OP_POP_MSW = 0x09; // pop MSW
constexpr uint8_t OP_POP_PC = 0x0B; // pop PC
constexpr uint8_t OP_POP_DP = 0x0C; // pop DP
constexpr uint8_t OP_POP_SP = 0x0D; // pop SP
// Branch operations
constexpr uint8_t OP_BR = 0x83; // br #d16
constexpr uint8_t OP_BR_EQ = 0x89; // br.eq #d16
constexpr uint8_t OP_BR_NE = 0x08; // br.ne #d16
constexpr uint8_t OP_BR_LT = 0xA6; // br.lt #d16
constexpr uint8_t OP_BR_LE = 0xB5; // br.le #d16
constexpr uint8_t OP_BR_GT = 0xBE; // br.gt #d16
constexpr uint8_t OP_BR_GE = 0xAE; // br.ge #d16
constexpr uint8_t OP_BR_LTU = 0xCF; // br.ltu #d16
constexpr uint8_t OP_BR_LEU = 0x56; // br.leu #d16
constexpr uint8_t OP_BR_GTU = 0x5E; // br.gtu #d16
constexpr uint8_t OP_BR_GEU = 0xC0; // br.geu #d16
constexpr uint8_t OP_BR_A = 0x32; // br A
constexpr uint8_t OP_SBR = 0x84; // sbr #d8
// Comparison operations
constexpr uint8_t OP_CMP8_A_IMM = 0x44; // cmp.8 A,#i8
constexpr uint8_t OP_CMP8_A_B = 0x47; // cmp.8 A,B
constexpr uint8_t OP_CMP16_A_IMM = 0x4C; // cmp.16 A,#i16
constexpr uint8_t OP_CMP16_A_B = 0x4F; // cmp.16 A,B
// Shift operations
constexpr uint8_t OP_SHL16_A = 0xC2; // shl.16 A
constexpr uint8_t OP_SHR16_A = 0xC3; // shr.16 A
constexpr uint8_t OP_SHL16_B = 0xC4; // shl.16 B
constexpr uint8_t OP_SHR16_B = 0xC6; // shr.16 B
constexpr uint8_t OP_VSHL16_A = 0xE6; // vshl.16 A
constexpr uint8_t OP_VSHR16_A = 0xEE; // vshr.16 A
constexpr uint8_t OP_VSHL16_B = 0xE7; // vshl.16 B
constexpr uint8_t OP_VSHR16_B = 0xEF; // vshr.16 B
// Subroutine operations
constexpr uint8_t OP_CALL = 0x80; // call #d16
constexpr uint8_t OP_CALL_A = 0x82; // call A
constexpr uint8_t OP_ENTER = 0xE4; // enter #fsize16
constexpr uint8_t OP_RETI = 0x8A; // reti
// System operations
constexpr uint8_t OP_HALT = 0x00; // halt
constexpr uint8_t OP_NOP = 0x66; // nop
constexpr uint8_t OP_SYSCALL = 0x3A; // syscall #num8
constexpr uint8_t OP_TRAPO = 0x8B; // trapo
constexpr uint8_t OP_BKPT = 0xFA; // bkpt
constexpr uint8_t OP_WCPTE = 0x2E; // wcpte A,(B)
constexpr uint8_t OP_WDPTE = 0xEC; // wdpte A,(B)
constexpr uint8_t OP_SET_MSW = 0xCA; // copy MSW,A
constexpr uint8_t OP_GET_MSW = 0x88; // copy A,MSW
// Special operations
constexpr uint8_t OP_SEX_A = 0x52; // sex A (sign extend)
constexpr uint8_t OP_SEX_B = 0xB2; // sex B (sign extend)
constexpr uint8_t OP_MEMCOPY = 0xE8; // memcopy
constexpr uint8_t OP_MEMCOPY4 = 0xE3; // memcopy4
constexpr uint8_t OP_STRCOPY = 0x6E; // strcopy
constexpr uint8_t OP_TOSYS = 0xE9; // tosys
constexpr uint8_t OP_FROMSYS = 0xEA; // fromsys
// Addressing mode enumeration
enum AddressingMode {
AM_IMPLICIT, // No operand
AM_REGISTER, // Register operand
AM_IMMEDIATE, // Immediate operand
AM_REGISTER_INDIRECT, // Register indirect [R]
AM_REG_INDIRECT_OFFSET8, // Register indirect with 8-bit offset [R+n8]
AM_REG_INDIRECT_OFFSET16, // Register indirect with 16-bit offset [R+n16]
AM_ABSOLUTE, // Absolute address [addr]
AM_PC_RELATIVE8, // PC-relative with 8-bit signed offset [PC+d8]
AM_PC_RELATIVE16, // PC-relative with 16-bit signed offset [PC+d16]
AM_PRE_DECREMENT, // Pre-decrement [--R]
AM_POST_INCREMENT // Post-increment [R++]
};
// Base registers for addressing modes
enum BaseRegister {
BR_NONE, // No base register
BR_A, // A register
BR_B, // B register
BR_C, // C register
BR_SP, // Stack pointer
BR_DP, // Data pointer
BR_PC // Program counter
};
// Addressing mode bit masks in instruction encoding
constexpr uint8_t ADDR_MODE_MASK = 0xF0; // Upper 4 bits typically encode mode
constexpr uint8_t REG_FIELD_MASK = 0x0C; // Bits 2-3 often encode register
constexpr uint8_t SIZE_BIT_MASK = 0x01; // Bit 0 often encodes size (8/16-bit)
// Microcode control fields
constexpr int NEXT_POS = 0; // Next microaddress field
constexpr int LATCH_POS = 1; // Register latch control field
constexpr int LMAR_POS = 2; // Latch MAR field
constexpr int LMDRLO_POS = 3; // Latch MDR (low byte) field
constexpr int LMDRHI_POS = 4; // Latch MDR (high byte) field
constexpr int EMDRLO_POS = 5; // Enable MDR (low byte) field
constexpr int EMDRHI_POS = 6; // Enable MDR (high byte) field
constexpr int PRIV_POS = 7; // Privileged instruction field
constexpr int LMODE_POS = 8; // Latch mode field
constexpr int LPAGING_POS = 9; // Latch paging field
constexpr int MISC_POS = 10; // Miscellaneous control field
constexpr int E_L_POS = 11; // Enable L-bus field
constexpr int E_R_POS = 12; // Enable R-bus field
constexpr int IMMVAL_POS = 13; // Immediate value field
constexpr int ALUOP_SIZE_POS = 14; // ALU operation size field
constexpr int ALUOP_POS = 15; // ALU operation field
constexpr int CARRY_POS = 16; // Carry input field
constexpr int L_SIZE_POS = 17; // Latch size field
constexpr int BR_SENSE_POS = 18; // Branch sense field
constexpr int USER_PTB_POS = 19; // User PTB field
constexpr int CODE_PTB_POS = 20; // Code PTB field
// Microcode register selection (LATCH field values)
constexpr uint8_t R_NONE = 0; // No register
constexpr uint8_t R_MSW = 1; // Machine Status Word
constexpr uint8_t R_C = 2; // C register
constexpr uint8_t R_PC = 3; // Program Counter
constexpr uint8_t R_DP = 4; // Data Pointer
constexpr uint8_t R_SP = 5; // Stack Pointer
constexpr uint8_t R_A = 6; // A register
constexpr uint8_t R_B = 7; // B register
constexpr uint8_t R_MDR = 8; // Memory Data Register
constexpr uint8_t R_PTB = 9; // Page Table Base
constexpr uint8_t R_SSP = 10; // Supervisor Stack Pointer
constexpr uint8_t R_TPC = 11; // Temporary Program Counter
constexpr uint8_t R_IR_REG = 15; // Instruction Register
// Microcode bus control values (E_L and E_R fields)
constexpr uint8_t EL_MAR = 0; // Memory Address Register to L-bus
constexpr uint8_t EL_MSW = 1; // Machine Status Word to L-bus
constexpr uint8_t EL_C = 2; // C register to L-bus
constexpr uint8_t EL_PC = 3; // Program Counter to L-bus
constexpr uint8_t EL_DP = 4; // Data Pointer to L-bus
constexpr uint8_t EL_SP = 5; // Stack Pointer to L-bus
constexpr uint8_t EL_A = 6; // A register to L-bus
constexpr uint8_t EL_B = 7; // B register to L-bus
constexpr uint8_t EL_MDR = 8; // Memory Data Register to L-bus
constexpr uint8_t EL_PTB = 9; // Page Table Base to L-bus
constexpr uint8_t EL_SSP = 10; // Supervisor Stack Pointer to L-bus
constexpr uint8_t EL_TPC = 11; // Temporary Program Counter to L-bus
constexpr uint8_t EL_FCODE = 12; // Fault code to L-bus
constexpr uint8_t EL_IR_BASE = 15; // Instruction Register base to L-bus
constexpr uint8_t ER_MDR = 0; // Memory Data Register to R-bus
constexpr uint8_t ER_IMM = 1; // Immediate value to R-bus
constexpr uint8_t ER_FAULT = 2; // Fault code to R-bus
// Microcode immediate values (IMMVAL field)
constexpr uint8_t IMM_0 = 0; // Immediate value 0
constexpr uint8_t IMM_1 = 1; // Immediate value 1
constexpr uint8_t IMM_NEG1 = 3; // Immediate value -1
constexpr uint8_t IMM_NEG2 = 2; // Immediate value -2
// Microcode miscellaneous control values (MISC field)
constexpr uint8_t M_NONE = 0; // No miscellaneous operation
constexpr uint8_t M_SYSCALL = 1; // System call
constexpr uint8_t M_HALT = 2; // Halt CPU
constexpr uint8_t M_BKPT = 3; // Breakpoint
constexpr uint8_t M_TRAPO = 4; // Trap on overflow
constexpr uint8_t M_LPTE = 5; // Latch page table entry
constexpr uint8_t M_SET_FLAGS = 6; // Set flags based on ALU result
constexpr uint8_t M_INIT_INST = 7; // Initialize instruction
constexpr uint8_t M_RSHIFT = 8; // Right shift
constexpr uint8_t M_DMA_ACK = 9; // DMA acknowledge
constexpr uint8_t M_LEI = 10; // Latch external interrupt
constexpr uint8_t M_DO_BRANCH = 11; // Execute conditional branch
constexpr uint8_t M_CLR_TRAP = 12; // Clear trap
constexpr uint8_t M_COMMIT = 13; // Commit changes
// ALU operations (ALUOP field)
constexpr uint8_t OP_IR13 = 0; // Operation from IR[1:3]
constexpr uint8_t OP_AND = 1; // Logical AND
constexpr uint8_t OP_SUB = 2; // Subtract
constexpr uint8_t OP_ADD = 3; // Add
// ALU operation size (ALUOP_SIZE field)
constexpr uint8_t WORD = 0; // 16-bit operation
constexpr uint8_t BYTE = 1; // 8-bit operation
// Carry input control (CARRY field)
constexpr uint8_t NO_CARRY = 0; // No carry input
constexpr uint8_t CARRY_IN = 1; // Use carry flag as input
// Latch size control (L_SIZE field)
constexpr uint8_t LBYTE = 0; // Latch byte
constexpr uint8_t LWORD = 1; // Latch word
// Branch control (BR_SENSE field)
constexpr uint8_t B_NORMAL = 0; // Normal branch condition
constexpr uint8_t B_NEGATED = 1; // Negated branch condition
// PTB selection (CODE_PTB and USER_PTB fields)
constexpr uint8_t CODE_SPACE = 1; // Code page table
constexpr uint8_t DATA_SPACE = 0; // Data page table
constexpr uint8_t PTB_OVERRIDE = 1; // Use override
constexpr uint8_t PTB_NORMAL = 0; // Use normal PTB
// Microcode special values
constexpr uint8_t USE_IR = 0xFF; // Use IR as next microcode address
constexpr uint8_t FETCH = 0x00; // Return to fetch microcode
// I/O device memory map
constexpr uint16_t IO_BASE = 0xFF80; // I/O space base address
constexpr uint16_t IO_SIZE = 128; // 128 bytes of I/O space
// Specific device addresses within I/O space
constexpr uint16_t UART0_BASE = 0xFFF0; // UART #0 (8 bytes)
constexpr uint16_t UART1_BASE = 0xFFE0; // UART #1 (8 bytes)
constexpr uint16_t RTC_BASE = 0xFFD0; // Real-time clock (8 bytes)
constexpr uint16_t POST_DISPLAY_BASE = 0xFFC0; // POST display (16 bytes)
constexpr uint16_t IDE_BASE = 0xFFB0; // IDE interface (16 bytes)
constexpr uint16_t PANEL_SWITCH_BASE = 0xFFA0; // Front panel switches (16 bytes)
// Device sizes
constexpr uint16_t UART_SIZE = 8; // 8 bytes per UART
constexpr uint16_t RTC_SIZE = 8; // 8 bytes for RTC
constexpr uint16_t POST_DISPLAY_SIZE = 16; // 16 bytes for POST display
constexpr uint16_t IDE_SIZE = 16; // 16 bytes for IDE interface
constexpr uint16_t PANEL_SWITCH_SIZE = 16; // 16 bytes for panel switches
// Interrupt assignments
constexpr int IRQ_IDE = 1; // IDE on IRQ1
constexpr int IRQ_UART1 = 2; // UART1 on IRQ2
constexpr int IRQ_UART0 = 3; // UART0 on IRQ3
constexpr int IRQ_RTC = 5; // RTC on IRQ5
# Magic-1 Emulator: Technical Reference for Programmers
## Table of Contents
1. [Introduction](#1-introduction)
2. [Magic-1 Architecture](#2-magic-1-architecture)
3. [Emulator Design](#3-emulator-design)
4. [Core Components](#4-core-components)
5. [CPU Implementation](#5-cpu-implementation)
6. [Memory System](#6-memory-system)
7. [I/O Subsystem](#7-io-subsystem)
8. [Microcode Engine](#8-microcode-engine)
9. [Build System](#9-build-system)
10. [CF Card and Boot Process](#10-cf-card-and-boot-process)
11. [GUI Implementation](#11-gui-implementation)
12. [Testing Framework](#12-testing-framework)
13. [Implementation Status](#13-implementation-status)
14. [Known Issues](#14-known-issues)
15. [Future Enhancements](#15-future-enhancements)
16. [Developer Quick Reference](#16-developer-quick-reference)
## 1. Introduction
The Magic-1 Emulator project aims to create a cycle-accurate software emulation of Bill Buzbee's homebrew Magic-1 computer. The Magic-1 was built using 74-series TTL logic and features a 16-bit word size, a custom instruction set, and a unique microcode implementation. This document provides technical information required to understand, modify, and extend the emulator.
### Codebase Organization
The emulator is organized as a Qt-based C++ project with the following structure:
M1emu1/ ├── src/ │ ├── core/ # Core emulation components │ ├── gui/ # User interface components │ ├── tools/ # Supporting tools and utilities │ ├── utils/ # Helper classes and functions │ └── main.cpp # Application entry point ├── tests/ # Unit and integration tests ├── resources/ # PROM images, CF card images, etc. ├── docs/ # Documentation └── CMakeLists.txt # Build system configuration
### Core Design Principles
1. **Cycle Accuracy**: Emulation aims to be cycle-accurate where possible
2. **Modularity**: Components are segregated to allow independent testing and modification
3. **Debuggability**: Extensive logging and debugging facilities
4. **Extensibility**: Clear interfaces for adding new features
## 2. Magic-1 Architecture
### Overview
The Magic-1 is a 16-bit, microcoded CPU with the following key features:
- 16-bit data paths but 8-bit data bus
- 22-bit physical addressing with banking
- Custom RISC-like instruction set
- Memory-mapped I/O
- Hardware-enforced memory protection
- Three general-purpose registers (A, B, C)
- Supervisor/user modes
- Paging support
- Compact Flash (CF) storage via IDE interface
- Two serial UART ports
### Register Set
The Magic-1 has the following registers:
- **A, B, C**: 16-bit general-purpose registers
- **PC**: Program Counter (16-bit)
- **SP**: Stack Pointer (16-bit)
- **DP**: Data Pointer (16-bit)
- **MSW**: Machine Status Word (16-bit, includes flags)
- **PTB**: Page Table Base (16-bit)
- **MAR**: Memory Address Register (internal, not programmer-visible)
- **MDR**: Memory Data Register (internal, not programmer-visible)
- **SSP**: Supervisor Stack Pointer (internal, saved when switching modes)
- **TPC**: Temporary PC (internal, used during exception handling)
### Status Flags (in MSW)
| Flag | Bit | Description |
|------|-----|----------------------------|
| C | 0 | Carry |
| Z | 1 | Zero |
| V | 2 | Overflow |
| S | 3 | Sign |
| I | 4 | Interrupt Enable |
| P | 5 | Paging Enable |
| M | 6 | Memory Protection |
| SV | 7 | Supervisor Mode |
### Memory Map
0x0000 - 0x003F : Interrupt Vectors 0x0040 - 0x3FFF : ROM/RAM (Depending on mode) 0x4000 - 0xFF7F : RAM 0xFF80 - 0xFFFF : Memory-mapped I/O
### I/O Map
0xFF80 - 0xFFAF : Reserved 0xFFB0 - 0xFFDF : IDE/CF Card Interface 0xFFE0 - 0xFFEF : UART1 0xFFF0 - 0xFFFF : UART0
### Instruction Format
Magic-1 instructions are variable length:
1. 1-byte instructions (opcode only)
2. 2-byte instructions (opcode + immediate byte)
3. 3-byte instructions (opcode + immediate word or address)
### Key Instructions
| Opcode | Mnemonic | Description |
|--------|----------|--------------------------|
| 0x00 | NOP | No Operation |
| 0x01 | MOV_AB | Move A to B |
| 0x10 | ADD_AB | Add A and B, store in A |
| 0x20 | AND_AB | AND A and B, store in A |
| 0x40 | LD_A | Load A from memory |
| 0x50 | ST_A | Store A to memory |
| 0x60 | JMP | Unconditional jump |
| 0x70 | CALL | Call subroutine |
| 0x72 | RET | Return from subroutine |
| 0x80 | PUSH_A | Push A onto stack |
| 0x84 | POP_A | Pop A from stack |
| 0x90 | IN_A | Input to A |
| 0x91 | OUT_A | Output from A |
| 0xF0 | HALT | Halt processor |
### Addressing Modes
1. **Immediate**: Value is part of the instruction
2. **Direct**: Memory address is part of the instruction
3. **Indirect via registers**: Using SP or DP registers
4. **Stack-based**: For PUSH/POP operations
### Exception Handling
Magic-1 has several exception vectors:
| Address | Exception Type |
|---------|---------------------------|
| 0x0000 | Reset |
| 0x0002 | UART0 Interrupt |
| 0x0004 | UART1 Interrupt |
| 0x0006 | IDE Interrupt |
| 0x0008 | Timer Interrupt |
| 0x000A | Page Fault |
| 0x000C | Protection Violation |
| 0x000E | Privilege Violation |
| 0x0010 | Illegal Instruction |
| 0x0012 | Divide by Zero |
## 3. Emulator Design
### Architecture Overview
The emulator is structured around several key C++ classes that model the hardware components of the Magic-1:
- **Emulator**: Top-level controller class
- **CPU**: Implements the processor logic and control flow
- **Memory**: Handles memory access, paging, and protection
- **Registers**: Manages processor registers and flags
- **Microcode**: Implements the microcode engine
- **Device**: Base class for I/O devices
- **UART**: Serial communication interfaces
- **IDE**: Handles CF card emulation
These components interact through well-defined interfaces to emulate the behavior of the Magic-1 hardware.
### Execution Flow
1. The emulator loads microcode from PROM images
2. The bootloader is loaded into memory
3. CF card image is mapped to the IDE interface
4. CPU starts execution at the reset vector (0x0000)
5. The microcode engine steps through each instruction
6. I/O operations are handled by device handlers
### Threading Model
The emulator uses a single-threaded model for core execution to avoid synchronization issues. The GUI runs on the main thread while the emulation can optionally run on a separate thread for better responsiveness.
### Namespace Usage
All emulator components are placed within the `magic1` namespace to avoid conflicts with other code.
## 4. Core Components
### Emulator Class
The `Emulator` class serves as the central coordinator for all components:
```cpp
class Emulator : public QObject {
public:
Emulator();
~Emulator();
bool initialize(const QString& microcodeDir, const QString& bootloaderFile, const QString& cfImageFile);
void run();
void stop();
void step();
void reset();
bool bootFromCFCard(int partitionIndex = 0, uint16_t sectorOffset = 0);
// Component access
CPU* getCPU() { return m_cpu; }
Memory* getMemory() { return m_memory; }
UART* getUART0() { return m_uart0; }
UART* getUART1() { return m_uart1; }
IDE* getIDE() { return m_ide; }
private:
CPU* m_cpu;
Memory* m_memory;
UART* m_uart0;
UART* m_uart1;
IDE* m_ide;
Microcode* m_microcode;
bool m_isInitialized;
void setupBootloaderMemoryParams(int partitionIndex = 0, uint16_t sectorOffset = 0);
void analyzeBootloaderVectors();
};
The CPU
class implements the processor logic:
class CPU : public QObject {
public:
CPU(QObject* parent = nullptr);
~CPU();
// CPU execution control
void step();
void run();
void stop();
void reset();
// Register access
uint16_t getRegister(Registers::RegisterType reg);
void setRegister(Registers::RegisterType reg, uint16_t value);
// Flag access
bool getFlag(Registers::FlagType flag);
void setFlag(Registers::FlagType flag, bool value);
// Trap handling
enum class TrapType {
PageFault,
ProtectionViolation,
PrivilegeViolation,
IllegalInstruction,
DivideByZero
};
void triggerTrap(TrapType type);
// Debug interface
uint8_t getCurrentOpcode() const;
uint16_t getMicroPC() const;
void setMicroPC(uint16_t address);
bool isDebugEnabled() const { return m_debugEnabled; }
private:
Memory* m_memory;
Registers m_registers;
Microcode* m_microcode;
bool m_running;
bool m_debugEnabled;
uint16_t m_microPC;
uint8_t m_currentOpcode;
void fetchInstruction();
void executeInstruction();
void processInterrupts();
void executeBootloaderHook();
};
The Memory
class handles memory operations:
class Memory : public QObject {
public:
Memory(QObject* parent = nullptr);
~Memory();
// Basic memory access
uint8_t readByte(uint16_t address);
void writeByte(uint16_t address, uint8_t value);
uint16_t readWord(uint16_t address);
void writeWord(uint16_t address, uint16_t value);
// Binary loading
bool loadBinary(const std::string& filename, uint16_t address);
// Device mapping
void mapDevice(Device* device, uint16_t baseAddress, uint16_t size);
void unmapDevice(Device* device);
// Memory control
void setRomMode(bool enabled) { m_romMode = enabled; }
bool isRomMode() const { return m_romMode; }
void setPagingEnabled(bool enabled) { m_pagingEnabled = enabled; }
void setPageTableBase(uint16_t address) { m_pageTableBase = address; }
// Reset memory state
void reset();
private:
std::vector<uint8_t> m_memory;
std::map<uint16_t, std::pair<Device*, uint16_t>> m_deviceMap;
bool m_romMode;
bool m_pagingEnabled;
uint16_t m_pageTableBase;
bool isDeviceMapped(uint16_t address, Device** device, uint16_t* deviceAddress);
bool translateAddress(uint16_t virtAddr, uint16_t& physAddr, bool isWrite);
};
The Device
class is the interface for all I/O devices:
class Device {
public:
Device();
virtual ~Device();
// Device interface methods
virtual uint8_t read(uint16_t address) = 0;
virtual void write(uint16_t address, uint8_t value) = 0;
virtual std::string getDeviceName() const = 0;
virtual void reset() = 0;
virtual void update() = 0;
};
The CPU implements a simple execution pipeline:
- Fetch: Read the instruction byte from memory at PC
- Decode: Determine the instruction type
- Execute: Perform the operation using microcode
- Memory: Handle any memory operations
- Writeback: Update registers with results
Unlike hardware CPUs with fixed instruction implementations, the Magic-1 uses microcode to control execution. The emulator implements this by:
- Loading microcode from PROM images
- Using a microcode PC (µPC) to track execution
- Executing individual microinstructions
When an interrupt occurs:
- CPU checks if interrupts are enabled (I flag)
- If enabled, the current PC is saved
- CPU enters supervisor mode
- PC is loaded with the appropriate interrupt vector
- After handling, an RTI (Return from Interrupt) instruction restores state
The CPU can generate several types of traps:
- Page Fault: Access to unmapped virtual memory
- Protection Violation: Illegal memory access attempt
- Privilege Violation: User-mode attempt to execute privileged instruction
- Illegal Instruction: Unknown opcode
- Divide by Zero: Division by zero
The CPU can be in one of the following states:
- Reset: Initial state, PC = 0x0000
- Running: Normal execution
- Halted: HALT instruction executed
- Error: Critical error occurred
- Wait: Awaiting interrupt
The 64KB address space is organized with:
- 0x0000-0x003F: Interrupt vectors
- 0x0040-0x3FFF: ROM/RAM area (depends on mode)
- 0x4000-0xFF7F: Main RAM
- 0xFF80-0xFFFF: Memory-mapped I/O
The memory system supports several modes:
- ROM Mode: Lower 16KB acts as ROM (write-protected)
- RAM Mode: Entire address space acts as RAM
- Paged Mode: Virtual addresses are translated through page tables
In ROM mode, writes to addresses 0x0000-0x3FFF are ignored except for:
- Interrupt vectors (0x0000-0x001F)
- Parameter block (0x0080-0x008F)
- ROM manager area (0x3FF0-0x3FFF)
When paging is enabled:
- The high 5 bits of the virtual address select a page table entry
- The page table entry contains the physical page number
- The lower 11 bits of the virtual address are the offset within the page
- Physical address = (page_table[va_high] << 11) | (va & 0x07FF)
The emulator maps devices to the high memory area (0xFF80-0xFFFF):
- 0xFFB0-0xFFDF: IDE/CF Card Interface
- 0xFFE0-0xFFEF: UART1
- 0xFFF0-0xFFFF: UART0
Access to these addresses is redirected to the appropriate device handler.
All I/O devices share a common interface (Device
class) with:
-
read(address)
: Read a byte from the device -
write(address, value)
: Write a byte to the device -
update()
: Perform periodic updates -
reset()
: Reset the device to initial state
The UART device emulates a 16550 UART with:
- Transmit and receive buffers
- Status registers
- Interrupt generation
class UART : public QObject, public virtual Device {
public:
UART(int uartNumber, QObject* parent = nullptr);
~UART();
// Device interface
virtual uint8_t read(uint16_t address) override;
virtual void write(uint16_t address, uint8_t value) override;
virtual std::string getDeviceName() const override;
virtual void reset() override;
virtual void update() override;
// UART-specific methods
bool sendByte(uint8_t byte);
bool receiveByte(uint8_t& byte);
// Connect to terminal
void connectTerminal(QObject* terminal);
signals:
void dataReceived(const QByteArray& data);
void transmitData(const QByteArray& data);
void interruptRequested(int level);
private:
int m_uartNumber;
CircularBuffer m_txBuffer;
CircularBuffer m_rxBuffer;
uint8_t m_registers[8];
bool m_interruptEnable;
};
The IDE device emulates a CompactFlash card interface:
class IDE : public QObject, public virtual Device {
public:
// ...existing code...
// CF card image handling
bool loadDiskImage(const std::string& filename);
// Partition handling
std::vector<PartitionInfo> getPartitionInfo() const;
PartitionInfo getPartition(int index) const;
private:
// ...existing code...
// Helpers
void executeCommand(uint8_t command);
void readSector();
void writeSector();
void identifyDevice();
uint32_t getLBAAddress();
QString getCHSString() const;
// CF card data
std::vector<uint8_t> m_diskImage;
uint32_t m_diskSizeBytes;
std::vector<PartitionInfo> m_partitions;
};
The Magic-1 uses microcode to implement its instruction set. Each instruction is broken down into a sequence of microoperations that control the internal data paths, ALU, and control signals.
Each microinstruction contains fields that control different aspects of the CPU:
- Next Address: Address of the next microinstruction
- L-Bus Source: Register driving the L bus
- R-Bus Source: Register driving the R bus
- ALU Operation: Function performed by the ALU
- Destination: Register to store the result
- Control Signals: Memory operations, flag updates, etc.
The microcode engine executes microinstructions in a cycle:
- Fetch microinstruction from microcode ROM
- Set up bus sources
- Perform ALU operation
- Store result to destination
- Handle memory operations
- Update microcode PC
The original Magic-1 microcode is stored in five PROMs:
- PROM0: Next address field
- PROM1: Latch and memory control signals
- PROM2: Privilege bits, miscellaneous, L-bus control
- PROM3: R-bus and ALU operation control
- PROM4: Branch control and PTB management
The emulator loads these from binary files at startup.
The microcode engine includes an opcode dispatch mechanism:
- Fetch instruction byte from memory
- Look up starting microcode address in dispatch table
- Begin executing microcode sequence
The project uses CMake for building. Key aspects:
- C++17 is required
- Qt5 is used for GUI components
- PROM and Boot image files are included in resources
- Tests are built conditionally
- Qt 5.12 or higher
- C++17 compatible compiler
- CMake 3.10 or higher
- GTest (for testing)
Standard steps:
mkdir build
cd build
cmake ..
make
-
magic1_Emu
: Main emulator executable -
magic1_tests
: Unit tests -
cf_image_tool
: CF card image manipulation tool
The Magic-1 CF card is organized with a specific partition structure:
Partition | Type | Start Sector | Size (sectors) | Purpose |
---|---|---|---|---|
0 | MAGIC1 | 0 | 32 | Bootloader |
1 | MAGIC1 | 32 | 20480 | Magic-1 Monitor |
2 | MINIX | 20512 | 131072 | Minix Root (p1) |
3 | MINIX | 151584 | 131072 | Minix /usr (p2) |
4 | MINIX | 282656 | 131072 | Minix /home (p3) |
- The Magic-1 starts execution at 0x0000 (reset vector)
- The bootloader initializes hardware and sets up the stack
- Bootloader parameters are read from address 0x0080
- The bootloader reads the partition table from the CF card
- The appropriate boot partition is loaded and executed
Magic-1 uses a specific boot image format:
typedef struct {
uint16_t magic; // Magic number (0x4D31 = "M1")
uint16_t entry_point; // Code entry point
uint32_t code_size; // Size of code segment
uint32_t data_size; // Size of data segment
uint8_t split; // Split code/data flag
uint8_t reserved[7]; // Padding for alignment
char name[16]; // Image name
uint16_t checksum; // Header checksum
} boot_image_header_t;
The bootloader expects a parameter block at address 0x0080:
- 0x0080: Boot partition (0-4)
- 0x0081-0x0082: Boot sector offset
- 0x0083-0x0084: CF cylinders (980)
- 0x0085: CF heads (16)
- 0x0086: Sectors per track (32)
- 0x0087-0x008A: Signature "M1CF"
- 0x008B: Boot mode (1=CF)
The GUI is structured around a main window with dockable panels:
- Memory View
- Register View
- Disassembly View
- Console View
- Control Panel
Provides controls for:
- Start/Stop execution
- Single step
- Reset
- Execution speed control
- Boot from specific partition
Displays memory contents with:
- Hex and ASCII representation
- Go to address
- Edit memory values
- Highlight different memory regions
Shows the current state of:
- A, B, C registers
- SP, DP, PC registers
- MSW with flag bits
- Internal registers
Displays:
- Disassembled instructions
- Current execution point
- Breakpoint management
- Stepping controls
Provides:
- UART output display
- Command input
- Debug messages
- Error notifications
Unit tests cover:
- CPU operations
- Memory system
- Device interfaces
- Individual instructions
- Microcode execution
Tests cover:
- Full instruction sequences
- I/O operations
- Boot process
- Exception handling
- Memory state verification
- Register state checking
- Expected vs. actual cycle counting
- Boot sequence validation
The current implementation status of the emulator:
Component | Status | Notes |
---|---|---|
CPU Core | Complete | All instruction execution working |
Memory System | Complete | Including paging and protection |
UART Interface | Complete | Both UARTs fully implemented |
IDE/CF Interface | Partial | Basic read/write works, needs refinement |
Microcode Engine | Complete | All microcode operations supported |
Bootloader Support | Partial | Basic boot works, some edge cases remain |
Minix Support | Partial | Basic filesystem access implemented |
GUI | Partial | Core views working, needs refinement |
Debugging Tools | Partial | Basic debugging works, needs expansion |
Documentation | In Progress | Core technical docs available |
Current known issues in the emulator:
- Timing accuracy issues with certain I/O operations
- Occasional CF card access errors with specific operations
- Memory protection violations not always properly caught
- Some microcode sequences execute with incorrect timing
- UART buffering issues with high-speed transfers
- GUI responsiveness issues during heavy computation
- Missing trap handlers for some exception conditions
- Bootloader parameter block handling inconsistencies
Planned improvements:
-
Enhanced debugging features:
- Memory watchpoints
- Conditional breakpoints
- Execution tracing
-
Performance optimizations:
- Microcode caching
- Memory access optimization
- On-demand device updates
-
Expansion features:
- Network device emulation
- Additional storage options
- Enhanced I/O capabilities
-
User interface improvements:
- Customizable layouts
- Improved disassembly
- Visual execution pipeline
mkdir build
cd build
cmake ..
make
./magic1_Emu --microcode=../resources --bootloader=../resources/bloader.BIN --cf-image=../resources/m1CFimg.dd
Emulator
├── CPU
│ ├── Registers
│ └── Microcode
├── Memory
└── Devices
├── UART0
├── UART1
└── IDE
-
src/core/emulator.h/cpp
: Main emulator class -
src/core/cpu.h/cpp
: CPU implementation -
src/core/memory.h/cpp
: Memory system -
src/core/microcode.h/cpp
: Microcode engine -
src/core/ide.h/cpp
: CF card interface -
src/core/uart.h/cpp
: UART implementation -
src/gui/mainwindow.h/cpp
: Main UI window
-
Adding a new instruction:
- Add opcode to
magic1_defs.h
- Add microcode sequence
- Add disassembly support
- Create unit tests
- Add opcode to
-
Debugging the boot process:
- Enable boot debug logging
- Check bootloader parameter block
- Verify CF card partitioning
- Monitor IDE access patterns
-
Adding a new device:
- Extend the
Device
class - Implement the required interface methods
- Add device mapping in
Emulator::initialize()
- Create UI components for the device
- Extend the
# Magic-1 Emulator Technical Reference
## Table of Contents
1. [Architecture Overview](#architecture-overview)
2. [Core Architecture Constants](#core-architecture-constants)
3. [Memory Architecture](#memory-architecture)
4. [CPU Registers](#cpu-registers)
5. [Instruction Set](#instruction-set)
6. [Addressing Modes](#addressing-modes)
7. [Microcode Implementation](#microcode-implementation)
8. [I/O Subsystem](#io-subsystem)
9. [IDE/CF Card Interface](#idecf-card-interface)
10. [UART Implementation](#uart-implementation)
11. [Boot Process](#boot-process)
12. [Emulator Components](#emulator-components)
13. [Data Structures](#data-structures)
14. [Implementation Status](#implementation-status)
15. [Future Work](#future-work)
## Architecture Overview
The Magic-1 is a 16-bit homebrew computer designed and built by Bill Buzbee using 74-series TTL logic. The emulator aims to recreate its functionality with cycle accuracy.
Key characteristics:
- 16-bit data path
- 16-bit memory addressing (64KB address space)
- 8-bit external data bus
- Microcoded implementation with 8K microcode ROM
- Three 16-bit general-purpose registers (A, B, C)
- Stack-based architecture with dedicated stack pointer
- Hardware support for memory protection and virtual memory
- Custom UART and IDE/CF interfaces
## Core Architecture Constants
```cpp
// CPU architecture constants
constexpr int DATA_BUS_WIDTH = 8; // 8-bit external data bus
constexpr int INTERNAL_DATA_WIDTH = 16; // 16-bit internal data paths
constexpr int PHYSICAL_ADDRESS_BITS = 22; // 22-bit physical address bus
constexpr int DEVICE_SELECT_BIT = 1; // 1 device select bit
constexpr int TOTAL_ADDRESS_BITS = 23; // 23-bit total addressing capability
constexpr int VIRTUAL_ADDRESS_BITS = 16; // 16-bit virtual addresses
constexpr int OPCODE_BITS = 8; // 8-bit opcodes (256 total)
constexpr int MIN_MICROINSTS_PER_INSTR = 2; // Fastest instructions: 2 microinstructions
constexpr int AVG_MICROINSTS_PER_INSTR = 6; // Average: 5-6 microinstructions
constexpr int MAX_MICROCODE_ROM_SIZE = 8192; // 8K microcode ROM entries
// Clock speed and timing
constexpr float CLOCK_SPEED_MHZ = 4.09f; // 4.09 MHz clock speed
constexpr int CYCLE_TIME_NS = 244; // Nanoseconds per cycle
// Memory map constants
constexpr uint16_t ROM_START = 0x0000; // ROM starts at address 0
constexpr uint16_t ROM_END = 0x3FFF; // ROM ends at 16KB-1
constexpr uint16_t RAM_START = 0x4000; // RAM starts at 16KB
constexpr uint16_t IO_START = 0xFF80; // I/O space starts at 0xFF80
constexpr uint16_t UART0_BASE = 0xFFF0; // UART0 base address
constexpr uint16_t UART1_BASE = 0xFFE0; // UART1 base address
constexpr uint16_t IDE_BASE = 0xFFB0; // IDE interface base address
// Page table constants
constexpr uint16_t PAGE_SIZE = 2048; // 2KB pages
constexpr uint8_t CODE_PAGES = 32; // 32 code pages
constexpr uint8_t DATA_PAGES = 32; // 32 data pages
constexpr uint8_t TOTAL_PAGES = 64; // 64 total pages
constexpr uint16_t PAGE_TABLE_BASE = 0x3800; // Default page table location
// Interrupt vector locations
constexpr uint16_t INT_VECTOR_RESET = 0x0000;
constexpr uint16_t INT_VECTOR_UART0 = 0x0002;
constexpr uint16_t INT_VECTOR_UART1 = 0x0004;
constexpr uint16_t INT_VECTOR_IDE = 0x0006;
constexpr uint16_t INT_VECTOR_TIMER = 0x0008;
constexpr uint16_t INT_VECTOR_PAGE_FAULT = 0x000A;
constexpr uint16_t INT_VECTOR_PROTECTION_VIOLATION = 0x000C;
constexpr uint16_t INT_VECTOR_PRIVILEGE_VIOLATION = 0x000E;
constexpr uint16_t INT_VECTOR_ILLEGAL_INSTRUCTION = 0x0010;
constexpr uint16_t INT_VECTOR_DIVIDE_BY_ZERO = 0x0012;
// Default trap handler addresses
constexpr uint16_t TRAP_HANDLER_PAGE_FAULT = 0x0100;
constexpr uint16_t TRAP_HANDLER_PROTECTION_VIOLATION = 0x0110;
constexpr uint16_t TRAP_HANDLER_PRIVILEGE_VIOLATION = 0x0120;
constexpr uint16_t TRAP_HANDLER_ILLEGAL_INSTRUCTION = 0x0130;
constexpr uint16_t TRAP_HANDLER_DIVIDE_BY_ZERO = 0x0140;
// Boot parameters memory block
constexpr uint16_t BOOT_PARAM_BLOCK = 0x0080; // Boot parameter block starts at 0x0080
The Magic-1 has a 64KB address space (16-bit addresses) with the following organization:
- ROM/Kernel Space (0x0000-0x3FFF): 16KB of ROM containing interrupt vectors, trap handlers, and kernel code
- RAM Space (0x4000-0xFF7F): 48KB of RAM for user programs and data
- I/O Space (0xFF80-0xFFFF): 128 bytes mapped to hardware devices
Memory paging provides virtual memory capabilities:
- 2KB page size
- Separate code and data page tables
- 32 code pages and 32 data pages
- Hardware page table base register (PTB)
- Memory Protection enabled via MSW register
Virtual-to-physical address translation:
- Architecture Overview
- Core Architecture Constants
- Memory Architecture
- CPU Registers
- Instruction Set
- Addressing Modes
- Microcode Implementation
- I/O Subsystem
- IDE/CF Card Interface
- UART Implementation
- Boot Process
- Emulator Components
- Data Structures
- Implementation Status
- Future Work
The Magic-1 is a 16-bit homebrew computer designed and built by Bill Buzbee using 74-series TTL logic. The emulator aims to recreate its functionality with cycle accuracy.
Key characteristics:
- 16-bit data path
- 16-bit memory addressing (64KB address space)
- 8-bit external data bus
- Microcoded implementation with 8K microcode ROM
- Three 16-bit general-purpose registers (A, B, C)
- Stack-based architecture with dedicated stack pointer
- Hardware support for memory protection and virtual memory
- Custom UART and IDE/CF interfaces
// CPU architecture constants
constexpr int DATA_BUS_WIDTH = 8; // 8-bit external data bus
constexpr int INTERNAL_DATA_WIDTH = 16; // 16-bit internal data paths
constexpr int PHYSICAL_ADDRESS_BITS = 22; // 22-bit physical address bus
constexpr int DEVICE_SELECT_BIT = 1; // 1 device select bit
constexpr int TOTAL_ADDRESS_BITS = 23; // 23-bit total addressing capability
constexpr int VIRTUAL_ADDRESS_BITS = 16; // 16-bit virtual addresses
constexpr int OPCODE_BITS = 8; // 8-bit opcodes (256 total)
constexpr int MIN_MICROINSTS_PER_INSTR = 2; // Fastest instructions: 2 microinstructions
constexpr int AVG_MICROINSTS_PER_INSTR = 6; // Average: 5-6 microinstructions
constexpr int MAX_MICROCODE_ROM_SIZE = 8192; // 8K microcode ROM entries
// Clock speed and timing
constexpr float CLOCK_SPEED_MHZ = 4.09f; // 4.09 MHz clock speed
constexpr int CYCLE_TIME_NS = 244; // Nanoseconds per cycle
// Memory map constants
constexpr uint16_t ROM_START = 0x0000; // ROM starts at address 0
constexpr uint16_t ROM_END = 0x3FFF; // ROM ends at 16KB-1
constexpr uint16_t RAM_START = 0x4000; // RAM starts at 16KB
constexpr uint16_t IO_START = 0xFF80; // I/O space starts at 0xFF80
constexpr uint16_t UART0_BASE = 0xFFF0; // UART0 base address
constexpr uint16_t UART1_BASE = 0xFFE0; // UART1 base address
constexpr uint16_t IDE_BASE = 0xFFB0; // IDE interface base address
// Page table constants
constexpr uint16_t PAGE_SIZE = 2048; // 2KB pages
constexpr uint8_t CODE_PAGES = 32; // 32 code pages
constexpr uint8_t DATA_PAGES = 32; // 32 data pages
constexpr uint8_t TOTAL_PAGES = 64; // 64 total pages
constexpr uint16_t PAGE_TABLE_BASE = 0x3800; // Default page table location
// Interrupt vector locations
constexpr uint16_t INT_VECTOR_RESET = 0x0000;
constexpr uint16_t INT_VECTOR_UART0 = 0x0002;
constexpr uint16_t INT_VECTOR_UART1 = 0x0004;
constexpr uint16_t INT_VECTOR_IDE = 0x0006;
constexpr uint16_t INT_VECTOR_TIMER = 0x0008;
constexpr uint16_t INT_VECTOR_PAGE_FAULT = 0x000A;
constexpr uint16_t INT_VECTOR_PROTECTION_VIOLATION = 0x000C;
constexpr uint16_t INT_VECTOR_PRIVILEGE_VIOLATION = 0x000E;
constexpr uint16_t INT_VECTOR_ILLEGAL_INSTRUCTION = 0x0010;
constexpr uint16_t INT_VECTOR_DIVIDE_BY_ZERO = 0x0012;
// Default trap handler addresses
constexpr uint16_t TRAP_HANDLER_PAGE_FAULT = 0x0100;
constexpr uint16_t TRAP_HANDLER_PROTECTION_VIOLATION = 0x0110;
constexpr uint16_t TRAP_HANDLER_PRIVILEGE_VIOLATION = 0x0120;
constexpr uint16_t TRAP_HANDLER_ILLEGAL_INSTRUCTION = 0x0130;
constexpr uint16_t TRAP_HANDLER_DIVIDE_BY_ZERO = 0x0140;
// Boot parameters memory block
constexpr uint16_t BOOT_PARAM_BLOCK = 0x0080; // Boot parameter block starts at 0x0080
The Magic-1 has a 64KB address space (16-bit addresses) with the following organization:
- ROM/Kernel Space (0x0000-0x3FFF): 16KB of ROM containing interrupt vectors, trap handlers, and kernel code
- RAM Space (0x4000-0xFF7F): 48KB of RAM for user programs and data
- I/O Space (0xFF80-0xFFFF): 128 bytes mapped to hardware devices
Memory paging provides virtual memory capabilities:
- 2KB page size
- Separate code and data page tables
- 32 code pages and 32 data pages
- Hardware page table base register (PTB)
- Memory Protection enabled via MSW register
Virtual-to-physical address translation: I'll create a comprehensive technical documentation file for the Magic-1 emulator project that covers all the architectural details, constants, and functions needed for a programmer to understand and continue development.
# Magic-1 Emulator Technical Documentation
## Table of Contents
1. [Architecture Overview](#architecture-overview)
2. [CPU Architecture](#cpu-architecture)
3. [Memory Architecture](#memory-architecture)
4. [Register Set](#register-set)
5. [Instruction Set](#instruction-set)
6. [Addressing Modes](#addressing-modes)
7. [Microcode Implementation](#microcode-implementation)
8. [I/O Subsystem](#io-subsystem)
9. [IDE/CF Card Interface](#idecf-card-interface)
10. [Boot Process](#boot-process)
11. [Emulator Implementation](#emulator-implementation)
12. [Implementation Status](#implementation-status)
13. [Future Work](#future-work)
## Architecture Overview
The Magic-1 is a 16-bit homebrew computer designed and built by Bill Buzbee using 74-series TTL logic. The architecture follows a one-address accumulator model with enhancements for C language support. This emulator aims to faithfully reproduce its behavior.
Key characteristics:
- 16-bit word size with 8-bit and 16-bit operations
- 16-bit virtual address space (64KB)
- Paged memory model with 22-bit physical address space
- Memory-mapped I/O
- External interrupt and DMA support
- Big-endian byte ordering
## CPU Architecture
### Architecture Constants
```cpp
// Word size
const int WORD_SIZE = 16; // 16-bit CPU
// Address space
const uint32_t VIRTUAL_ADDRESS_SPACE = 65536; // 64KB virtual address space
const uint32_t PHYSICAL_ADDRESS_SPACE = 4194304; // 4MB physical address space (22-bit)
// Page size
const uint16_t PAGE_SIZE = 2048; // 2KB pages
const uint16_t PAGE_COUNT = 64; // 32 code pages + 32 data pages
const uint16_t PAGE_OFFSET_MASK = 0x07FF; // 11 bits for page offset (0-2047)
const uint16_t PAGE_NUMBER_MASK = 0xF800; // 5 bits for page number (0-31)
const uint16_t PAGE_NUMBER_SHIFT = 11; // Shift to get page number
// Clock speed
const double DEFAULT_CLOCK_SPEED_MHZ = 4.0; // 4MHz default clock speed
// Interrupt lines
const int IRQ_COUNT = 6; // Six external interrupt request lines (IRQ0-IRQ5)
const int IRQ_PRIORITY_HIGHEST = 0; // IRQ0 = highest priority
const int IRQ_PRIORITY_LOWEST = 5; // IRQ5 = lowest priority
// Machine Status Word (MSW) bits
const uint16_t MSW_C = 0x0001; // Carry flag
const uint16_t MSW_Z = 0x0002; // Zero flag
const uint16_t MSW_S = 0x0004; // Sign flag
const uint16_t MSW_V = 0x0008; // Overflow flag
const uint16_t MSW_MODE = 0x0080; // Mode bit (0=supervisor, 1=user)
const uint16_t MSW_PAGING = 0x0040; // Paging enable
const uint16_t MSW_I = 0x0020; // Interrupt enable
const uint16_t MSW_DATA = 0x0010; // Data/code reference (in fault context)
// Page Table Entry (PTE) bits
const uint16_t PTE_P = 0x8000; // Page present
const uint16_t PTE_W = 0x4000; // Page writeable
const uint16_t PTE_M = 0x2000; // Memory vs device flag
const uint16_t PTE_RESERVED = 0x3000; // Reserved bits
const uint16_t PTE_PAGE_MASK = 0x0FFF; // Physical page number (bits 0-11)
// ROM/RAM switch
const bool ROM_MODE = true; // First 16KB is ROM
const bool RAM_MODE = false; // First 16KB is RAM
// Instruction timing
const int MIN_INSTRUCTION_CYCLES = 2; // Minimum cycles per instruction
const int AVG_INSTRUCTION_CYCLES = 6; // Average cycles per instruction
const int MAX_INSTRUCTION_CYCLES = 20; // Maximum cycles for complex instructions
// Memory regions
const uint16_t ROM_START = 0x0000; // ROM start address
const uint16_t ROM_END = 0x3FFF; // ROM end address (16KB)
const uint16_t RAM_START = 0x4000; // RAM start address
const uint16_t RAM_END = 0xFFFF; // RAM end address
// Device memory map (memory-mapped I/O)
const uint16_t UART0_BASE = 0xFFF0; // UART #0 (8 bytes)
const uint16_t UART1_BASE = 0xFFE0; // UART #1 (8 bytes)
const uint16_t RTC_BASE = 0xFFD0; // Real-time clock (8 bytes)
const uint16_t POST_DISPLAY = 0xFFC0; // 2-digit hex display (16 bytes)
const uint16_t IDE_BASE = 0xFFB0; // IDE interface (16 bytes)
const uint16_t PANEL_SWITCH = 0xFFA0; // Front panel switch block (16 bytes)
// Interrupt vector table (at beginning of address space)
const uint16_t INT_VECTOR_RESET = 0x0000; // Reset vector
const uint16_t INT_VECTOR_UART0 = 0x0002; // UART0 interrupt vector
const uint16_t INT_VECTOR_UART1 = 0x0004; // UART1 interrupt vector
const uint16_t INT_VECTOR_IDE = 0x0006; // IDE interrupt vector
const uint16_t INT_VECTOR_UNUSED = 0x0008;// Unused interrupt vector
const uint16_t INT_VECTOR_RTC = 0x000A; // RTC interrupt vector
// Default trap handler locations
const uint16_t TRAP_PAGE_FAULT = 0x0100; // Page fault handler
const uint16_t TRAP_PROTECTION_VIOLATION = 0x0110;// Protection violation handler
const uint16_t TRAP_PRIVILEGE_VIOLATION = 0x0120; // Privilege violation handler
const uint16_t TRAP_ILLEGAL_INSTRUCTION = 0x0130; // Illegal instruction handler
const uint16_t TRAP_DIVIDE_BY_ZERO = 0x0140; // Divide by zero handler
// Boot parameter block
const uint16_t BOOT_PARAMS_ADDR = 0x0080; // Boot parameters address
// General-purpose registers
const int REG_A = 0; // Accumulator (primary register)
const int REG_B = 1; // General register (often source operand)
const int REG_C = 2; // Count register for block operations and shifts
// Special-purpose registers
const int REG_MSW = 3; // Machine Status Word (flags and control)
const int REG_DP = 4; // Data Pointer (global data base pointer)
const int REG_SP = 5; // Stack Pointer (current stack top)
const int REG_SSP = 6; // Supervisor Stack Pointer (for supervisor mode)
const int REG_PC = 7; // Program Counter (next instruction address)
const int REG_PTB = 8; // Page Table Base (current page table location)
// Internal registers (not directly accessible by instructions)
const int REG_MAR = 9; // Memory Address Register
const int REG_MDR = 10; // Memory Data Register
const int REG_IR = 11; // Instruction Register
const int REG_TPC = 12; // Temporary Program Counter (for exceptions)
The Magic-1 instruction set contains 256 possible instructions with 8-bit opcodes.
// Instruction categories
enum InstructionCategory {
LOAD_STORE, // Load/store operations
ADDRESS_MANIP, // Address manipulation
REGISTER_ACCESS, // Special register access
ARITHMETIC, // Arithmetic operations
LOGICAL, // Logical operations
SHIFT, // Shift operations
CONTROL_FLOW, // Control flow instructions
SYSTEM // System operations
};
// Instruction formats
enum InstructionFormat {
ZERO_OPERAND, // No operands
REG_OPERAND, // Register operand
IMM8_OPERAND, // 8-bit immediate operand
IMM16_OPERAND, // 16-bit immediate operand
MEM_OPERAND, // Memory operand
REG_MEM_OPERAND, // Register and memory operands
REL_BRANCH // Relative branch offset
};
// Load/Store Instructions
const uint8_t LD8_A_IMM8 = 0x78; // ld.8 A,#u8
const uint8_t LD8_B_IMM8 = 0x79; // ld.8 B,#u8
const uint8_t LD16_A_IMM16 = 0x7C; // ld.16 A,#u16
const uint8_t LD16_B_IMM16 = 0x7D; // ld.16 B,#u16
const uint8_t LD16_C_IMM16 = 0xCE; // ld.16 C,#u16
const uint8_t LD8_A_MEM = 0x10; // ld.8 A,#u16(DP)
const uint8_t LD16_A_MEM = 0x18; // ld.16 A,#u16(DP)
const uint8_t ST8_MEM_A = 0xD0; // st.8 #u16(DP),A
const uint8_t ST16_MEM_A = 0xD8; // st.16 #u16(DP),A
// Stack operations
const uint8_t PUSH_A = 0x06; // push A
const uint8_t PUSH_B = 0x07; // push B
const uint8_t PUSH_C = 0x02; // push C
const uint8_t PUSH_MSW = 0x22; // push MSW
const uint8_t PUSH_DP = 0x04; // push DP
const uint8_t PUSH_PC = 0x03; // push PC
const uint8_t PUSH_SP = 0x26; // push SP
const uint8_t POP_A = 0x0E; // pop A
const uint8_t POP_B = 0x0F; // pop B
const uint8_t POP_C = 0x0A; // pop C
const uint8_t POP_MSW = 0x09; // pop MSW
const uint8_t POP_DP = 0x0C; // pop DP
const uint8_t POP_PC = 0x0B; // pop PC
const uint8_t POP_SP = 0x0D; // pop SP
// Arithmetic operations
const uint8_t ADD8_A_IMM8 = 0x34; // add.8 A,#i8
const uint8_t ADD8_A_B = 0x37; // add.8 A,B
const uint8_t ADD16_A_IMM16 = 0x3C; // add.16 A,#i16
const uint8_t ADD16_A_B = 0x3F; // add.16 A,B
const uint8_t SUB8_A_IMM8 = 0x24; // sub.8 A,#i8
const uint8_t SUB8_A_B = 0x27; // sub.8 A,B
const uint8_t SUB16_A_IMM16 = 0x2C; // sub.16 A,#i16
const uint8_t SUB16_A_B = 0x2F; // sub.16 A,B
const uint8_t ADC16_A_B = 0x7F; // adc.16 A,B
const uint8_t SBC16_A_B = 0xED; // sbc.16 A,B
// Logical operations
const uint8_t AND8_A_IMM8 = 0x64; // and.8 A,#i8
const uint8_t AND8_A_B = 0x67; // and.8 A,B
const uint8_t AND16_A_IMM16 = 0x6C; // and.16 A,#i16
const uint8_t AND16_A_B = 0x6F; // and.16 A,B
const uint8_t OR8_A_IMM8 = 0x54; // or.8 A,#i8
const uint8_t OR8_A_B = 0x57; // or.8 A,B
const uint8_t OR16_A_IMM16 = 0x5C; // or.16 A,#i16
const uint8_t OR16_A_B = 0x5F; // or.16 A,B
const uint8_t XOR16_A_B = 0xC7; // xor.16 A,B
// Comparison operations
const uint8_t CMP8_A_IMM8 = 0x44; // cmp.8 A,#i8
const uint8_t CMP8_A_B = 0x47; // cmp.8 A,B
const uint8_t CMP16_A_IMM16 = 0x4C; // cmp.16 A,#i16
const uint8_t CMP16_A_B = 0x4F; // cmp.16 A,B
// Shift operations
const uint8_t SHL16_A = 0xC2; // shl.16 A
const uint8_t SHR16_A = 0xC3; // shr.16 A
const uint8_t SHL16_B = 0xC4; // shl.16 B
const uint8_t SHR16_B = 0xC6; // shr.16 B
const uint8_t VSHL16_A = 0xE6; // vshl.16 A (shift count in C)
const uint8_t VSHR16_A = 0xEE; // vshr.16 A (shift count in C)
const uint8_t VSHL16_B = 0xE7; // vshl.16 B (shift count in C)
const uint8_t VSHR16_B = 0xEF; // vshr.16 B (shift count in C)
// Branch instructions
const uint8_t BR_EQ = 0x89; // br.eq #d16
const uint8_t BR_NE = 0x08; // br.ne #d16
const uint8_t BR_LT = 0xA6; // br.lt #d16
const uint8_t BR_LE = 0xB5; // br.le #d16
const uint8_t BR_GT = 0xBE; // br.gt #d16
const uint8_t BR_GE = 0xAE; // br.ge #d16
const uint8_t BR_LTU = 0xCF; // br.ltu #d16
const uint8_t BR_LEU = 0x56; // br.leu #d16
const uint8_t BR_GTU = 0x5E; // br.gtu #d16
const uint8_t BR_GEU = 0xC0; // br.geu #d16
const uint8_t BR = 0x83; // br #d16
const uint8_t SBR = 0x84; // sbr #d8 (short branch)
const uint8_t BR_A = 0x32; // br A (branch to address in A)
// Call/Return instructions
const uint8_t CALL = 0x80; // call #d16
const uint8_t CALL_A = 0x82; // call A
const uint8_t RET = 0x0B; // ret (alias for pop PC)
const uint8_t RETI = 0x8A; // reti (return from interrupt)
const uint8_t ENTER = 0xE4; // enter #fsize16
// System instructions
const uint8_t HALT = 0x00; // halt
const uint8_t TRAPO = 0x8B; // trapo (trap on overflow)
const uint8_t SYSCALL = 0x3A; // syscall #sys_num8
const uint8_t BKPT = 0xFA; // bkpt (breakpoint)
const uint8_t WCPTE = 0x2E; // wcpte A,(B) (write code page table entry)
const uint8_t WDPTE = 0xEC; // wdpte A,(B) (write data page table entry)
// Memory operations
const uint8_t MEMCOPY = 0xE8; // memcopy (block copy, count in C)
const uint8_t TOSYS = 0xE9; // tosys (copy to system space)
const uint8_t FROMSYS = 0xEA; // fromsys (copy from system space)
const uint8_t LDCODE8 = 0xE0; // ldcode.8 A,(B) (load from code space)
const uint8_t LDCLR8 = 0xEB; // ldclr.8 A,(B) (atomic load and clear)
// Miscellaneous
const uint8_t LEA_A = 0x70; // lea A,#addr (load effective address)
const uint8_t LEA_B = 0x74; // lea B,#addr
const uint8_t SEX_A = 0x52; // sex A (sign extend A)
const uint8_t SEX_B = 0xB2; // sex B (sign extend B)
const uint8_t NOP = 0x66; // nop (no operation)
// Addressing modes
enum AddressingMode {
AM_REGISTER, // Register (R)
AM_IMMEDIATE, // Immediate value (#imm)
AM_REGISTER_INDIRECT, // Register indirect with offset (R+offset)
AM_FRAME_LOCAL, // Frame local with offset (SP+offset)
AM_GLOBAL, // Global with offset (DP+offset)
AM_PC_RELATIVE, // PC-relative (PC+offset)
AM_PUSH, // Push (--SP)
AM_POP, // Pop (SP++)
AM_ABSOLUTE // Absolute address (addr)
};
// Register types for addressing
enum BaseRegisterType {
BR_A, // A register
BR_B, // B register
BR_SP, // Stack pointer
BR_DP, // Data pointer
BR_PC // Program counter
};
// Offset sizes for addressing modes
enum OffsetSize {
OS_NONE, // No offset
OS_8BIT, // 8-bit signed offset
OS_16BIT // 16-bit signed offset
};
The Magic-1 uses microcode to implement its instruction set. Each instruction maps to a sequence of microoperations.
// Microcode fields
struct MicrocodeFields {
uint8_t next; // Next microcode address (0x00=fetch, 0xFF=decode)
uint8_t latch; // Register latch control
uint8_t lmar; // Latch MAR flag
uint8_t lmdrlo; // Latch MDR (low byte) flag
uint8_t lmdrhi; // Latch MDR (high byte) flag
uint8_t emdrlo; // Enable MDR (low byte) on data bus
uint8_t emdrhi; // Enable MDR (high byte) on data bus
uint8_t priv; // Privileged instruction flag
uint8_t lmode; // Latch MODE bit in MSW
uint8_t lpaging; // Latch PAGING bit in MSW
uint8_t misc; // Miscellaneous control signals
uint8_t e_l; // Enable L-bus
uint8_t e_r; // Enable R-bus
uint8_t immval; // Immediate value
uint8_t aluop_size; // ALU operation size (0=16-bit, 1=8-bit)
uint8_t aluop; // ALU operation code
uint8_t carry; // Carry input to ALU
uint8_t l_size; // Latch size (0=byte, 1=word)
uint8_t br_sense; // Branch sense
uint8_t user_ptb; // User PTB override
uint8_t code_ptb; // Code PTB select (0=data, 1=code)
};
// Microcode control signal values
// Register latch values (LATCH field)
const uint8_t L_NONE = 0; // No register latching
const uint8_t L_MSW = 1; // Latch MSW
const uint8_t L_C = 2; // Latch C register
const uint8_t L_PC = 3; // Latch PC
const uint8_t L_DP = 4; // Latch DP
const uint8_t L_SP = 5; // Latch SP
const uint8_t L_A = 6; // Latch A register
const uint8_t L_B = 7; // Latch B register
const uint8_t L_MDR = 8; // Latch MDR
const uint8_t L_PTB = 9; // Latch PTB
const uint8_t L_SSP = 14; // Latch SSP
const uint8_t L_IR_REG = 15; // Latch IR register
// Miscellaneous control values (MISC field)
const uint8_t M_NONE = 0; // No misc operation
const uint8_t M_SYSCALL = 1; // System call
const uint8_t M_HALT = 2; // Halt CPU
const uint8_t M_BKPT = 3; // Breakpoint
const uint8_t M_TRAPO = 4; // Trap on overflow
const uint8_t M_LPTE = 5; // Latch PTE
const uint8_t M_SET_FLAGS = 6; // Set flags from ALU operation
const uint8_t M_INIT_INST = 7; // Initialize instruction
const uint8_t M_RSHIFT = 8; // Right shift ALU output
const uint8_t M_DMA_ACK = 9; // DMA acknowledge
const uint8_t M_LEI = 10; // Latch interrupt enable
const uint8_t M_DO_BRANCH = 11; // Do conditional branch
const uint8_t M_CLR_TRAP = 12; // Clear trap flag
const uint8_t M_COMMIT = 13; // Commit state changes
// L-bus source select values (E_L field)
const uint8_t EL_MAR = 0; // MAR to L-bus
const uint8_t EL_MSW = 1; // MSW to L-bus
const uint8_t EL_C = 2; // C register to L-bus
const uint8_t EL_PC = 3; // PC to L-bus
const uint8_t EL_DP = 4; // DP to L-bus
const uint8_t EL_SP = 5; // SP to L-bus
const uint8_t EL_A = 6; // A register to L-bus
const uint8_t EL_B = 7; // B register to L-bus
const uint8_t EL_MDR = 8; // MDR to L-bus
const uint8_t EL_PTB = 9; // PTB to L-bus
const uint8_t EL_SSP = 10; // SSP to L-bus
const uint8_t EL_TPC = 11; // TPC to L-bus
const uint8_t EL_FCODE = 12; // Fault code to L-bus
const uint8_t EL_IR_BASE = 15; // IR base to L-bus
// R-bus source select values (E_R field)
const uint8_t ER_MDR = 0; // MDR to R-bus
const uint8_t ER_IMM = 1; // Immediate value to R-bus
const uint8_t ER_FAULT = 2; // Fault code to R-bus
// Immediate values (IMMVAL field)
const uint8_t IMM_0 = 0; // Immediate value 0
const uint8_t IMM_1 = 1; // Immediate value 1
const uint8_t IMM_NEG2 = 2; // Immediate value -2
const uint8_t IMM_NEG1 = 3; // Immediate value -1
// ALU operations (ALUOP field)
const uint8_t OP_IR13 = 0; // Use IR[1..3] to select operation
const uint8_t OP_AND = 1; // Logical AND
const uint8_t OP_SUB = 2; // Subtraction
const uint8_t OP_ADD = 3; // Addition
// Operation size (ALUOP_SIZE field)
const uint8_t WORD = 0; // 16-bit operation
const uint8_t BYTE = 1; // 8-bit operation
// Latch size (L_SIZE field)
const uint8_t LBYTE = 0; // Latch byte
const uint8_t LWORD = 1; // Latch word
// Carry input (CARRY field)
const uint8_t NO_CARRY = 0; // No carry input
const uint8_t CARRY_IN = 1; // Use carry flag as input
// Branch sense (BR_SENSE field)
const uint8_t B_NORMAL = 0; // Normal branch condition
const uint8_t B_NEGATED = 1; // Negated branch condition
// Page table selection
const uint8_t DATA_SPACE = 0; // Select data space
const uint8_t CODE_SPACE = 1; // Select code space
const uint8_t PTB_NORMAL = 0; // Normal PTB
const uint8_t PTB_OVERRIDE = 1; // Override PTB
The Magic-1 uses memory-mapped I/O with devices mapped to high memory addresses.
// UART registers (8250/16550 compatible)
// Base addresses: UART0_BASE (0xFFF0), UART1_BASE (0xFFE0)
const uint8_t UART_REG_DATA = 0; // Data register (RBR/THR)
const uint8_t UART_REG_IER = 1; // Interrupt Enable Register
const uint8_t UART_REG_IIR = 2; // Interrupt Identification Register (read)
const uint8_t UART_REG_FCR = 2; // FIFO Control Register (write)
const uint8_t UART_REG_LCR = 3; // Line Control Register
const uint8_t UART_REG_MCR = 4; // Modem Control Register
const uint8_t UART_REG_LSR = 5; // Line Status Register
const uint8_t UART_REG_MSR = 6; // Modem Status Register
const uint8_t UART_REG_SCR = 7; // Scratch Register
// UART Line Status Register bits
const uint8_t UART_LSR_DR = 0x01; // Data Ready
const uint8_t UART_LSR_OE = 0x02; // Overrun Error
const uint8_t UART_LSR_PE = 0x04; // Parity Error
const uint8_t UART_LSR_FE = 0x08; // Framing Error
const uint8_t UART_LSR_BI = 0x10; // Break Interrupt
const uint8_t UART_LSR_THRE = 0x20; // Transmitter Holding Register Empty
const uint8_t UART_LSR_TEMT = 0x40; // Transmitter Empty
const uint8_t UART_LSR_FIFO_ERR = 0x80; // Error in FIFO
// UART Interrupt Enable Register bits
const uint8_t UART_IER_RDA = 0x01; // Received Data Available
const uint8_t UART_IER_THRE = 0x02; // Transmitter Holding Register Empty
const uint8_t UART_IER_RLS = 0x04; // Receiver Line Status
const uint8_t UART_IER_MSI = 0x08; // Modem Status Interrupt
// UART Line Control Register bits
const uint8_t UART_LCR_WLS0 = 0x01; // Word Length Select bit 0
const uint8_t UART_LCR_WLS1 = 0x02; // Word Length Select bit 1
const uint8_t UART_LCR_STB = 0x04; // Stop Bits (0=1 stop, 1=2 stop)
const uint8_t UART_LCR_PEN = 0x08; // Parity Enable
const uint8_t UART_LCR_EPS = 0x10; // Even Parity Select
const uint8_t UART_LCR_STICK = 0x20; // Stick Parity
const uint8_t UART_LCR_BREAK = 0x40; // Set Break
const uint8_t UART_LCR_DLAB = 0x80; // Divisor Latch Access Bit
// UART default settings
const uint8_t UART_DEFAULT_LCR = 0x03; // 8 bits, 1 stop bit, no parity
const uint32_t UART_DEFAULT_BAUD = 9600; // 9600 baud rate
// IDE interface registers (base address: IDE_BASE 0xFFB0)
const uint8_t IDE_REG_DATA = 0; // Data register
const uint8_t IDE_REG_ERROR = 1; // Error register (read)
const uint8_t IDE_REG_FEATURES = 1; // Features register (write)
const uint8_t IDE_REG_SECTOR_COUNT = 2; // Sector count
const uint8_t IDE_REG_SECTOR_NUM = 3; // Sector number (LBA lo)
const uint8_t IDE_REG_CYL_LOW = 4; // Cylinder low (LBA mid)
const uint8_t IDE_REG_CYL_HIGH = 5; // Cylinder high (LBA hi)
const uint8_t IDE_REG_DRIVE_HEAD = 6; // Drive/Head (LBA top 4 bits)
const uint8_t IDE_REG_STATUS = 7; // Status register (read)
const uint8_t IDE_REG_COMMAND = 7; // Command register (write)
// IDE register bit masks
const uint8_t IDE_STATUS_BSY = 0x80; // Busy
const uint8_t IDE_STATUS_DRDY = 0x40; // Drive ready
const uint8_t IDE_STATUS_DF = 0x20; // Drive fault
const uint8_t IDE_STATUS_DSC = 0x10; // Drive seek complete
const uint8_t IDE_STATUS_DRQ = 0x08; // Data request
const uint8_t IDE_STATUS_CORR = 0x04; // Corrected data
const uint8_t IDE_STATUS_IDX = 0x02; // Index
const uint8_t IDE_STATUS_ERR = 0x01; // Error
// IDE error register bits
const uint8_t IDE_ERR_BBK = 0x80; // Bad block
const uint8_t IDE_ERR_UNC = 0x40; // Uncorrectable data error
const uint8_t IDE_ERR_MC = 0x20; // Media changed
const uint8_t IDE_ERR_IDNF = 0x10; // ID not found
const uint8_t IDE_ERR_MCR = 0x08; // Media change request
const uint8_t IDE_ERR_ABRT = 0x04; // Command aborted
const uint8_t IDE_ERR_TK0NF = 0x02; // Track 0 not found
const uint8_t IDE_ERR_AMNF = 0x01; // Address mark not found
// IDE commands
const uint8_t IDE_CMD_READ_SECTORS = 0x20; // Read sectors
const uint8_t IDE_CMD_WRITE
I'll create a comprehensive technical document for the Magic-1 emulator in programmer.md that focuses on architecture constants, functions, and implementation details. The document will be well-structured and contain all the necessary information for a new programmer to understand and continue work on the project.
```markdown
# Magic-1 Emulator: Technical Reference
## Table of Contents
1. [Architecture Overview](#architecture-overview)
2. [CPU Architecture Constants](#cpu-architecture-constants)
3. [Memory Architecture](#memory-architecture)
4. [Register Set](#register-set)
5. [Instruction Set](#instruction-set)
6. [Addressing Modes](#addressing-modes)
7. [Machine Status Word (MSW)](#machine-status-word-msw)
8. [Memory Paging](#memory-paging)
9. [Microcode Implementation](#microcode-implementation)
10. [I/O Subsystem](#io-subsystem)
11. [UART Implementation](#uart-implementation)
12. [IDE/CF Card Interface](#idecf-card-interface)
13. [Boot Process](#boot-process)
14. [Emulator Components](#emulator-components)
15. [Core Classes](#core-classes)
16. [Key Functions](#key-functions)
17. [Implementation Status](#implementation-status)
18. [Future Work](#future-work)
## Architecture Overview
Magic-1 is a 16-bit homebrew computer designed by Bill Buzbee. It features:
- 16-bit word size with one-address accumulator design
- 8-bit and 16-bit operations
- 64KB virtual address space (16-bit)
- 4MB physical address space (22-bit)
- Big-endian byte order
- Memory-mapped I/O
- Hardware support for memory paging
## CPU Architecture Constants
```cpp
// Word size
constexpr int WORD_SIZE_BITS = 16; // 16-bit word size
constexpr int BYTE_SIZE_BITS = 8; // 8-bit byte size
constexpr int BYTES_PER_WORD = 2; // 2 bytes per word
// Address space
constexpr uint32_t VIRTUAL_ADDRESS_SPACE = 65536; // 64KB virtual address space
constexpr uint32_t PHYSICAL_ADDRESS_SPACE = 4194304; // 4MB physical address space (22-bit)
constexpr uint32_t DEVICE_ADDRESS_SPACE = 4194304; // 4MB device address space (1-bit select)
// Instruction encoding
constexpr int OPCODE_SIZE = 8; // 8-bit opcode field
constexpr int MAX_INSTR_SIZE = 3; // Maximum instruction size in bytes
constexpr int MAX_OPERAND_SIZE = 2; // Maximum operand size in bytes
// Clock and timing
constexpr double DEFAULT_CLOCK_SPEED_MHZ = 4.0; // 4MHz default clock speed
constexpr int CLOCK_PERIOD_NS = 250; // 250ns clock period (4MHz)
constexpr int MIN_CYCLES_PER_INSTRUCTION = 2; // Minimum cycles per instruction
constexpr int AVG_CYCLES_PER_INSTRUCTION = 6; // Average cycles per instruction
constexpr int MAX_CYCLES_PER_INSTRUCTION = 20; // Maximum cycles per instruction
// Interrupt system
constexpr int IRQ_LINES = 6; // 6 hardware interrupt lines
constexpr int IRQ_HIGHEST_PRIORITY = 0; // IRQ0 has highest priority
constexpr int IRQ_LOWEST_PRIORITY = 5; // IRQ5 has lowest priority
// Microcode
constexpr int MICROCODE_ADDRESS_BITS = 13; // 13-bit microcode address space
constexpr int MICROCODE_ROM_SIZE = 8192; // 8K microcode ROM entries
constexpr int MICROCODE_WORD_WIDTH = 56; // 56-bit wide microinstruction word
// Memory map constants
constexpr uint16_t MEMORY_SIZE = 65536; // 64KB address space
constexpr uint16_t ROM_START = 0x0000; // ROM start
constexpr uint16_t ROM_END = 0x3FFF; // ROM end (16KB)
constexpr uint16_t ROM_SIZE = 0x4000; // ROM size (16KB)
constexpr uint16_t RAM_START = 0x4000; // RAM start
constexpr uint16_t RAM_END = 0xFF7F; // RAM end
constexpr uint16_t RAM_SIZE = 0xBF80; // RAM size (approx 48KB)
constexpr uint16_t IO_START = 0xFF80; // I/O space start
constexpr uint16_t IO_END = 0xFFFF; // I/O space end
constexpr uint16_t IO_SIZE = 0x0080; // I/O space size (128 bytes)
// Boot parameters block
constexpr uint16_t BOOT_PARAM_BLOCK = 0x0080; // Boot parameter block address
constexpr uint16_t BOOT_PARAM_SIZE = 0x0020; // Boot parameter block size
// Interrupt vector table
constexpr uint16_t INT_VECTOR_BASE = 0x0000; // Interrupt vector table base
constexpr uint16_t INT_VECTOR_SIZE = 2; // 2 bytes per vector (address)
constexpr uint16_t INT_VECTOR_COUNT = 8; // 8 interrupt vectors
// Trap handler addresses
constexpr uint16_t PAGE_FAULT_HANDLER = 0x0100; // Page fault handler
constexpr uint16_t PROTECTION_VIOLATION_HANDLER = 0x0110; // Protection violation
constexpr uint16_t PRIVILEGE_VIOLATION_HANDLER = 0x0120; // Privilege violation
constexpr uint16_t ILLEGAL_INSTRUCTION_HANDLER = 0x0130; // Illegal instruction
constexpr uint16_t DIVIDE_BY_ZERO_HANDLER = 0x0140; // Divide by zero
// Memory-mapped device addresses
constexpr uint16_t UART0_BASE = 0xFFF0; // UART0 base (console)
constexpr uint16_t UART1_BASE = 0xFFE0; // UART1 base (auxiliary)
constexpr uint16_t RTC_BASE = 0xFFD0; // Real-time clock base
constexpr uint16_t POST_DISPLAY_BASE = 0xFFC0; // POST display base
constexpr uint16_t IDE_BASE = 0xFFB0; // IDE interface base
constexpr uint16_t PANEL_SWITCH_BASE = 0xFFA0; // Front panel switch base
// Register indices
enum RegisterIndex {
REG_A = 0, // Accumulator (primary register)
REG_B = 1, // Secondary register (often source operand)
REG_C = 2, // Count register (for block ops and shifts)
REG_MSW = 3, // Machine Status Word (flags and control)
REG_DP = 4, // Data Pointer (global base address)
REG_SP = 5, // Stack Pointer
REG_SSP = 6, // Supervisor Stack Pointer
REG_PC = 7, // Program Counter
REG_PTB = 8, // Page Table Base
// Internal registers (not directly accessible)
REG_MAR = 9, // Memory Address Register
REG_MDR = 10, // Memory Data Register
REG_IR = 11, // Instruction Register
REG_TPC = 12, // Temp Program Counter (saved during exceptions)
REG_COUNT = 13 // Total number of registers
};
// Register sizes
constexpr int REGISTER_WIDTH = 16; // 16-bit registers
constexpr int REGISTER_BYTE_SIZE = 2; // 2 bytes per register
// MSW bit positions
constexpr uint16_t MSW_C = 0x0001; // Carry flag (bit 0)
constexpr uint16_t MSW_Z = 0x0002; // Zero flag (bit 1)
constexpr uint16_t MSW_V = 0x0004; // Overflow flag (bit 2)
constexpr uint16_t MSW_S = 0x0008; // Sign flag (bit 3)
constexpr uint16_t MSW_DATA = 0x0010; // Data/code flag (bit 4, in fault context)
constexpr uint16_t MSW_I = 0x0020; // Interrupt enable (bit 5)
constexpr uint16_t MSW_P = 0x0040; // Paging enable (bit 6)
constexpr uint16_t MSW_SV = 0x0080; // Supervisor mode (bit 7)
// MSW bit positions by name
constexpr int MSW_BIT_CARRY = 0; // Carry flag bit position
constexpr int MSW_BIT_ZERO = 1; // Zero flag bit position
constexpr int MSW_BIT_OVERFLOW = 2; // Overflow flag bit position
constexpr int MSW_BIT_SIGN = 3; // Sign flag bit position
constexpr int MSW_BIT_DATA = 4; // Data flag bit position
constexpr int MSW_BIT_INTERRUPT = 5; // Interrupt enable bit position
constexpr int MSW_BIT_PAGING = 6; // Paging enable bit position
constexpr int MSW_BIT_SUPERVISOR = 7; // Supervisor mode bit position
// MSW default values
constexpr uint16_t MSW_RESET_VALUE = 0x0080; // Default after reset: supervisor mode
// Paging constants
constexpr int PAGE_SHIFT = 11; // 2KB pages (2^11)
constexpr uint16_t PAGE_SIZE = (1 << PAGE_SHIFT); // 2048 bytes per page
constexpr uint16_t PAGE_MASK = (PAGE_SIZE - 1); // Mask for extracting offset (0x07FF)
constexpr uint16_t PAGE_NUMBER_MASK = ~PAGE_MASK; // Mask for page number (0xF800)
constexpr int PAGES_PER_PROCESS = 64; // 32 code + 32 data pages
constexpr int CODE_PAGES = 32; // 32 code pages
constexpr int DATA_PAGES = 32; // 32 data pages
// Page table entry constants
constexpr uint16_t PTE_P = 0x8000; // Page Present bit (15)
constexpr uint16_t PTE_W = 0x4000; // Page Writeable bit (14)
constexpr uint16_t PTE_M = 0x2000; // Page Memory Type bit (13)
constexpr uint16_t PTE_RESERVED = 0x3000; // Reserved bits (12-11)
constexpr uint16_t PTE_PAGE_MASK = 0x0FFF; // Physical page number (11-0)
// Page table entry bit positions
constexpr int PTE_BIT_PRESENT = 15; // Present bit position
constexpr int PTE_BIT_WRITEABLE = 14; // Writeable bit position
constexpr int PTE_BIT_MEMORY = 13; // Memory type bit position
// Instruction categories
enum InstructionCategory {
IC_LOAD_STORE, // Load/store operations
IC_ARITHMETIC, // Arithmetic operations
IC_LOGICAL, // Logical operations
IC_SHIFT, // Shift operations
IC_BRANCH, // Branches and jumps
IC_SUBROUTINE, // Subroutine operations
IC_STACK, // Stack operations
IC_IO, // I/O operations
IC_SYSTEM, // System control operations
IC_COMPARISON, // Comparison operations
IC_SPECIAL // Special operations
};
// Instruction formats
enum InstructionFormat {
IF_IMPLICIT, // No operands (e.g., NOP)
IF_REGISTER, // Register operand(s)
IF_IMMEDIATE8, // 8-bit immediate operand
IF_IMMEDIATE16, // 16-bit immediate operand
IF_MEMORY8, // 8-bit memory operand
IF_MEMORY16, // 16-bit memory operand
IF_RELATIVE8, // 8-bit relative branch
IF_RELATIVE16 // 16-bit relative branch
};
// Key instruction opcodes
// Register operations
constexpr uint8_t OP_MOV_A_B = 0x9A; // copy A,B
constexpr uint8_t OP_MOV_A_C = 0xF2; // copy A,C
constexpr uint8_t OP_MOV_B_A = 0x92; // copy B,A
constexpr uint8_t OP_MOV_C_A = 0x96; // copy C,A
constexpr uint8_t OP_MOV_C_B = 0x42; // copy C,B
constexpr uint8_t OP_MOV_B_C = 0xF6; // copy B,C
// Arithmetic operations
constexpr uint8_t OP_ADD8_A_IMM = 0x34; // add.8 A,#i8
constexpr uint8_t OP_ADD8_A_B = 0x37; // add.8 A,B
constexpr uint8_t OP_ADD16_A_IMM = 0x3C; // add.16 A,#i16
constexpr uint8_t OP_ADD16_A_B = 0x3F; // add.16 A,B
constexpr uint8_t OP_SUB8_A_IMM = 0x24; // sub.8 A,#i8
constexpr uint8_t OP_SUB8_A_B = 0x27; // sub.8 A,B
constexpr uint8_t OP_SUB16_A_IMM = 0x2C; // sub.16 A,#i16
constexpr uint8_t OP_SUB16_A_B = 0x2F; // sub.16 A,B
constexpr uint8_t OP_ADC16_A_B = 0x7F; // adc.16 A,B
constexpr uint8_t OP_SBC16_A_B = 0xED; // sbc.16 A,B
// Logical operations
constexpr uint8_t OP_AND8_A_IMM = 0x64; // and.8 A,#i8
constexpr uint8_t OP_AND8_A_B = 0x67; // and.8 A,B
constexpr uint8_t OP_AND16_A_IMM = 0x6C; // and.16 A,#i16
constexpr uint8_t OP_AND16_A_B = 0x6F; // and.16 A,B
constexpr uint8_t OP_OR8_A_IMM = 0x54; // or.8 A,#i8
constexpr uint8_t OP_OR8_A_B = 0x57; // or.8 A,B
constexpr uint8_t OP_OR16_A_IMM = 0x5C; // or.16 A,#i16
constexpr uint8_t OP_OR16_A_B = 0x5F; // or.16 A,B
constexpr uint8_t OP_XOR16_A_A = 0x46; // xor.16 A,A
constexpr uint8_t OP_XOR16_A_B = 0xC7; // xor.16 A,B
// Load/store operations
constexpr uint8_t OP_LD8_A_IMM = 0x78; // ld.8 A,#u8
constexpr uint8_t OP_LD8_B_IMM = 0x79; // ld.8 B,#u8
constexpr uint8_t OP_LD16_A_IMM = 0x7C; // ld.16 A,#u16
constexpr uint8_t OP_LD16_B_IMM = 0x7D; // ld.16 B,#u16
constexpr uint8_t OP_LD16_C_IMM = 0xCE; // ld.16 C,#u16
constexpr uint8_t OP_LD8_A_DP = 0x10; // ld.8 A,#u16(DP)
constexpr uint8_t OP_LD16_A_DP = 0x18; // ld.16 A,#u16(DP)
constexpr uint8_t OP_ST8_DP_A = 0xD0; // st.8 #u16(DP),A
constexpr uint8_t OP_ST16_DP_A = 0xD8; // st.16 #u16(DP),A
// Stack operations
constexpr uint8_t OP_PUSH_A = 0x06; // push A
constexpr uint8_t OP_PUSH_B = 0x07; // push B
constexpr uint8_t OP_PUSH_C = 0x02; // push C
constexpr uint8_t OP_PUSH_MSW = 0x22; // push MSW
constexpr uint8_t OP_PUSH_PC = 0x03; // push PC
constexpr uint8_t OP_PUSH_DP = 0x04; // push DP
constexpr uint8_t OP_PUSH_SP = 0x26; // push SP
constexpr uint8_t OP_POP_A = 0x0E; // pop A
constexpr uint8_t OP_POP_B = 0x0F; // pop B
constexpr uint8_t OP_POP_C = 0x0A; // pop C
constexpr uint8_t OP_POP_MSW = 0x09; // pop MSW
constexpr uint8_t OP_POP_PC = 0x0B; // pop PC
constexpr uint8_t OP_POP_DP = 0x0C; // pop DP
constexpr uint8_t OP_POP_SP = 0x0D; // pop SP
// Branch operations
constexpr uint8_t OP_BR = 0x83; // br #d16
constexpr uint8_t OP_BR_EQ = 0x89; // br.eq #d16
constexpr uint8_t OP_BR_NE = 0x08; // br.ne #d16
constexpr uint8_t OP_BR_LT = 0xA6; // br.lt #d16
constexpr uint8_t OP_BR_LE = 0xB5; // br.le #d16
constexpr uint8_t OP_BR_GT = 0xBE; // br.gt #d16
constexpr uint8_t OP_BR_GE = 0xAE; // br.ge #d16
constexpr uint8_t OP_BR_LTU = 0xCF; // br.ltu #d16
constexpr uint8_t OP_BR_LEU = 0x56; // br.leu #d16
constexpr uint8_t OP_BR_GTU = 0x5E; // br.gtu #d16
constexpr uint8_t OP_BR_GEU = 0xC0; // br.geu #d16
constexpr uint8_t OP_BR_A = 0x32; // br A
constexpr uint8_t OP_SBR = 0x84; // sbr #d8
// Comparison operations
constexpr uint8_t OP_CMP8_A_IMM = 0x44; // cmp.8 A,#i8
constexpr uint8_t OP_CMP8_A_B = 0x47; // cmp.8 A,B
constexpr uint8_t OP_CMP16_A_IMM = 0x4C; // cmp.16 A,#i16
constexpr uint8_t OP_CMP16_A_B = 0x4F; // cmp.16 A,B
// Shift operations
constexpr uint8_t OP_SHL16_A = 0xC2; // shl.16 A
constexpr uint8_t OP_SHR16_A = 0xC3; // shr.16 A
constexpr uint8_t OP_SHL16_B = 0xC4; // shl.16 B
constexpr uint8_t OP_SHR16_B = 0xC6; // shr.16 B
constexpr uint8_t OP_VSHL16_A = 0xE6; // vshl.16 A
constexpr uint8_t OP_VSHR16_A = 0xEE; // vshr.16 A
constexpr uint8_t OP_VSHL16_B = 0xE7; // vshl.16 B
constexpr uint8_t OP_VSHR16_B = 0xEF; // vshr.16 B
// Subroutine operations
constexpr uint8_t OP_CALL = 0x80; // call #d16
constexpr uint8_t OP_CALL_A = 0x82; // call A
constexpr uint8_t OP_ENTER = 0xE4; // enter #fsize16
constexpr uint8_t OP_RETI = 0x8A; // reti
// System operations
constexpr uint8_t OP_HALT = 0x00; // halt
constexpr uint8_t OP_NOP = 0x66; // nop
constexpr uint8_t OP_SYSCALL = 0x3A; // syscall #num8
constexpr uint8_t OP_TRAPO = 0x8B; // trapo
constexpr uint8_t OP_BKPT = 0xFA; // bkpt
constexpr uint8_t OP_WCPTE = 0x2E; // wcpte A,(B)
constexpr uint8_t OP_WDPTE = 0xEC; // wdpte A,(B)
constexpr uint8_t OP_SET_MSW = 0xCA; // copy MSW,A
constexpr uint8_t OP_GET_MSW = 0x88; // copy A,MSW
// Special operations
constexpr uint8_t OP_SEX_A = 0x52; // sex A (sign extend)
constexpr uint8_t OP_SEX_B = 0xB2; // sex B (sign extend)
constexpr uint8_t OP_MEMCOPY = 0xE8; // memcopy
constexpr uint8_t OP_MEMCOPY4 = 0xE3; // memcopy4
constexpr uint8_t OP_STRCOPY = 0x6E; // strcopy
constexpr uint8_t OP_TOSYS = 0xE9; // tosys
constexpr uint8_t OP_FROMSYS = 0xEA; // fromsys
// Addressing mode enumeration
enum AddressingMode {
AM_IMPLICIT, // No operand
AM_REGISTER, // Register operand
AM_IMMEDIATE, // Immediate operand
AM_REGISTER_INDIRECT, // Register indirect [R]
AM_REG_INDIRECT_OFFSET8, // Register indirect with 8-bit offset [R+n8]
AM_REG_INDIRECT_OFFSET16, // Register indirect with 16-bit offset [R+n16]
AM_ABSOLUTE, // Absolute address [addr]
AM_PC_RELATIVE8, // PC-relative with 8-bit signed offset [PC+d8]
AM_PC_RELATIVE16, // PC-relative with 16-bit signed offset [PC+d16]
AM_PRE_DECREMENT, // Pre-decrement [--R]
AM_POST_INCREMENT // Post-increment [R++]
};
// Base registers for addressing modes
enum BaseRegister {
BR_NONE, // No base register
BR_A, // A register
BR_B, // B register
BR_C, // C register
BR_SP, // Stack pointer
BR_DP, // Data pointer
BR_PC // Program counter
};
// Addressing mode bit masks in instruction encoding
constexpr uint8_t ADDR_MODE_MASK = 0xF0; // Upper 4 bits typically encode mode
constexpr uint8_t REG_FIELD_MASK = 0x0C; // Bits 2-3 often encode register
constexpr uint8_t SIZE_BIT_MASK = 0x01; // Bit 0 often encodes size (8/16-bit)
// Microcode control fields
constexpr int NEXT_POS = 0; // Next microaddress field
constexpr int LATCH_POS = 1; // Register latch control field
constexpr int LMAR_POS = 2; // Latch MAR field
constexpr int LMDRLO_POS = 3; // Latch MDR (low byte) field
constexpr int LMDRHI_POS = 4; // Latch MDR (high byte) field
constexpr int EMDRLO_POS = 5; // Enable MDR (low byte) field
constexpr int EMDRHI_POS = 6; // Enable MDR (high byte) field
constexpr int PRIV_POS = 7; // Privileged instruction field
constexpr int LMODE_POS = 8; // Latch mode field
constexpr int LPAGING_POS = 9; // Latch paging field
constexpr int MISC_POS = 10; // Miscellaneous control field
constexpr int E_L_POS = 11; // Enable L-bus field
constexpr int E_R_POS = 12; // Enable R-bus field
constexpr int IMMVAL_POS = 13; // Immediate value field
constexpr int ALUOP_SIZE_POS = 14; // ALU operation size field
constexpr int ALUOP_POS = 15; // ALU operation field
constexpr int CARRY_POS = 16; // Carry input field
constexpr int L_SIZE_POS = 17; // Latch size field
constexpr int BR_SENSE_POS = 18; // Branch sense field
constexpr int USER_PTB_POS = 19; // User PTB field
constexpr int CODE_PTB_POS = 20; // Code PTB field
// Microcode register selection (LATCH field values)
constexpr uint8_t R_NONE = 0; // No register
constexpr uint8_t R_MSW = 1; // Machine Status Word
constexpr uint8_t R_C = 2; // C register
constexpr uint8_t R_PC = 3; // Program Counter
constexpr uint8_t R_DP = 4; // Data Pointer
constexpr uint8_t R_SP = 5; // Stack Pointer
constexpr uint8_t R_A = 6; // A register
constexpr uint8_t R_B = 7; // B register
constexpr uint8_t R_MDR = 8; // Memory Data Register
constexpr uint8_t R_PTB = 9; // Page Table Base
constexpr uint8_t R_SSP = 10; // Supervisor Stack Pointer
constexpr uint8_t R_TPC = 11; // Temporary Program Counter
constexpr uint8_t R_IR_REG = 15; // Instruction Register
// Microcode bus control values (E_L and E_R fields)
constexpr uint8_t EL_MAR = 0; // Memory Address Register to L-bus
constexpr uint8_t EL_MSW = 1; // Machine Status Word to L-bus
constexpr uint8_t EL_C = 2; // C register to L-bus
constexpr uint8_t EL_PC = 3; // Program Counter to L-bus
constexpr uint8_t EL_DP = 4; // Data Pointer to L-bus
constexpr uint8_t EL_SP = 5; // Stack Pointer to L-bus
constexpr uint8_t EL_A = 6; // A register to L-bus
constexpr uint8_t EL_B = 7; // B register to L-bus
constexpr uint8_t EL_MDR = 8; // Memory Data Register to L-bus
constexpr uint8_t EL_PTB = 9; // Page Table Base to L-bus
constexpr uint8_t EL_SSP = 10; // Supervisor Stack Pointer to L-bus
constexpr uint8_t EL_TPC = 11; // Temporary Program Counter to L-bus
constexpr uint8_t EL_FCODE = 12; // Fault code to L-bus
constexpr uint8_t EL_IR_BASE = 15; // Instruction Register base to L-bus
constexpr uint8_t ER_MDR = 0; // Memory Data Register to R-bus
constexpr uint8_t ER_IMM = 1; // Immediate value to R-bus
constexpr uint8_t ER_FAULT = 2; // Fault code to R-bus
// Microcode immediate values (IMMVAL field)
constexpr uint8_t IMM_0 = 0; // Immediate value 0
constexpr uint8_t IMM_1 = 1; // Immediate value 1
constexpr uint8_t IMM_NEG1 = 3; // Immediate value -1
constexpr uint8_t IMM_NEG2 = 2; // Immediate value -2
// Microcode miscellaneous control values (MISC field)
constexpr uint8_t M_NONE = 0; // No miscellaneous operation
constexpr uint8_t M_SYSCALL = 1; // System call
constexpr uint8_t M_HALT = 2; // Halt CPU
constexpr uint8_t M_BKPT = 3; // Breakpoint
constexpr uint8_t M_TRAPO = 4; // Trap on overflow
constexpr uint8_t M_LPTE = 5; // Latch page table entry
constexpr uint8_t M_SET_FLAGS = 6; // Set flags based on ALU result
constexpr uint8_t M_INIT_INST = 7; // Initialize instruction
constexpr uint8_t M_RSHIFT = 8; // Right shift
constexpr uint8_t M_DMA_ACK = 9; // DMA acknowledge
constexpr uint8_t M_LEI = 10; // Latch external interrupt
constexpr uint8_t M_DO_BRANCH = 11; // Execute conditional branch
constexpr uint8_t M_CLR_TRAP = 12; // Clear trap
constexpr uint8_t M_COMMIT = 13; // Commit changes
// ALU operations (ALUOP field)
constexpr uint8_t OP_IR13 = 0; // Operation from IR[1:3]
constexpr uint8_t OP_AND = 1; // Logical AND
constexpr uint8_t OP_SUB = 2; // Subtract
constexpr uint8_t OP_ADD = 3; // Add
// ALU operation size (ALUOP_SIZE field)
constexpr uint8_t WORD = 0; // 16-bit operation
constexpr uint8_t BYTE = 1; // 8-bit operation
// Carry input control (CARRY field)
constexpr uint8_t NO_CARRY = 0; // No carry input
constexpr uint8_t CARRY_IN = 1; // Use carry flag as input
// Latch size control (L_SIZE field)
constexpr uint8_t LBYTE = 0; // Latch byte
constexpr uint8_t LWORD = 1; // Latch word
// Branch control (BR_SENSE field)
constexpr uint8_t B_NORMAL = 0; // Normal branch condition
constexpr uint8_t B_NEGATED = 1; // Negated branch condition
// PTB selection (CODE_PTB and USER_PTB fields)
constexpr uint8_t CODE_SPACE = 1; // Code page table
constexpr uint8_t DATA_SPACE = 0; // Data page table
constexpr uint8_t PTB_OVERRIDE = 1; // Use override
constexpr uint8_t PTB_NORMAL = 0; // Use normal PTB
// Microcode special values
constexpr uint8_t USE_IR = 0xFF; // Use IR as next microcode address
constexpr uint8_t FETCH = 0x00; // Return to fetch microcode
// I/O device memory map
constexpr uint16_t IO_BASE = 0xFF80; // I/O space base address
constexpr uint16_t IO_SIZE = 128; // 128 bytes of I/O space
// Specific device addresses within I/O space
constexpr uint16_t UART0_BASE = 0xFFF0; // UART #0 (8 bytes)
constexpr uint16_t UART1_BASE = 0xFFE0; // UART #1 (8 bytes)
constexpr uint16_t RTC_BASE = 0xFFD0; // Real-time clock (8 bytes)
constexpr uint16_t POST_DISPLAY_BASE = 0xFFC0; // POST display (16 bytes)
constexpr uint16_t IDE_BASE = 0xFFB0; // IDE interface (16 bytes)
constexpr uint16_t PANEL_SWITCH_BASE = 0xFFA0; // Front panel switches (16 bytes)
// Device sizes
constexpr uint16_t UART_SIZE = 8; // 8 bytes per UART
constexpr uint16_t RTC_SIZE = 8; // 8 bytes for RTC
constexpr uint16_t POST_DISPLAY_SIZE = 16; // 16 bytes for POST display
constexpr uint16_t IDE_SIZE = 16; // 16 bytes for IDE interface
constexpr uint16_t PANEL_SWITCH_SIZE = 16; // 16 bytes for panel switches
// Interrupt assignments
constexpr int IRQ_IDE = 1; // IDE on IRQ1
constexpr int IRQ_UART1 = 2; // UART1 on IRQ2
constexpr int IRQ_UART0 = 3; // UART0 on IRQ3
constexpr int IRQ_RTC = 5; // RTC on IRQ5