A multi-processor emulator for the fictional BCC-500 computer system, inspired by the SDS 940 architecture. Features 24-bit word size, memory-mapped I/O, an interactive debugger, and support for up to 6 parallel processors.
- 🎨 GUI Interface — Beautiful ncurses-based terminal UI with visual displays
- 📝 Assembly Language — Write programs in human-readable assembly instead of hex
- 📁 File Browser — Visual file picker to load programs easily
- 📚 Example Programs — 5+ ready-to-run test programs included
- ⚡ Live Updates — Real-time processor state and terminal display
- 🔨 Build System — Simple Makefile for easy compilation
- Multi-processor support — Emulates up to 6 independent processors with shared memory
- 24-bit architecture — Authentic word size with 18-bit addressing (256K words)
- Extended instruction set — 52 opcodes based on the SDS 940
- Memory-mapped I/O — Tape drive, card reader, console, and terminal devices
- Interactive debugger — Breakpoints, single-stepping, memory inspection
- Binary file loader — Load and save programs in native 24-bit format
- Thread-safe design — Proper synchronization for concurrent processor execution
make allThis builds both emulator versions:
emulator— Original command-line versiongui-emulator— New GUI version with assembler
- C++17 compatible compiler
- pthreads
- ncurses development library (for GUI version)
Install ncurses:
On Fedora/RHEL:
sudo dnf install ncurses-develOn Ubuntu/Debian:
sudo apt-get install libncurses-dev# GUI emulator (recommended)
g++ -std=c++17 -Wall -Wextra -pthread -O2 -o gui-emulator GuiEmulator.cpp -lncurses
# Original emulator
g++ -std=c++17 -Wall -Wextra -pthread -O2 -o emulator Emulator.cpp./gui-emulatorUsing the GUI:
- Press F1 to open file browser
- Navigate to
examplesdirectory - Select a program (e.g.,
hello.asm) - Press Enter to load
- Press F3 to run!
See QUICKSTART.md for detailed GUI guide.
./emulatorThis launches a demo program that prints "Hello, BCC-500!" to the terminal display, then enters the interactive debugger.
| Register | Size | Description |
|---|---|---|
| ACC (A) | 24-bit | Primary accumulator |
| B | 24-bit | Secondary accumulator |
| X | 24-bit | Index register |
| PC | 18-bit | Program counter |
| SP | 18-bit | Stack pointer |
| IR | 24-bit | Instruction register |
| Flag | Description |
|---|---|
| Z | Zero — Set when result is zero |
| N | Negative — Set when sign bit is set |
| O | Overflow — Set on arithmetic overflow |
| C | Carry — Set on unsigned overflow |
| I | Interrupt Enable — Controls interrupt handling |
| Address Range | Size | Description |
|---|---|---|
| 0x00000–0x3EFFF | 254K words | Main memory |
| 0x3F000–0x3FFFF | 4K words | Memory-mapped I/O |
Each instruction is a 24-bit word:
[23:18] Opcode (6 bits)
[17:0] Address/Operand (18 bits)
| Opcode | Hex | Mnemonic | Description |
|---|---|---|---|
| 0x00 | HALT | Halt processor | |
| 0x01 | NOP | No operation | |
| 0x02 | LOAD | Load ACC from memory | |
| 0x03 | STORE | Store ACC to memory | |
| 0x04 | ADD | Add memory to ACC | |
| 0x05 | SUB | Subtract memory from ACC | |
| 0x06 | AND | Bitwise AND with memory | |
| 0x07 | OR | Bitwise OR with memory | |
| 0x08 | XOR | Bitwise XOR with memory |
| Opcode | Hex | Mnemonic | Description |
|---|---|---|---|
| 0x09 | JUMP | Unconditional jump | |
| 0x0A | JZE | Jump if zero | |
| 0x0B | JNZ | Jump if not zero | |
| 0x0C | JGT | Jump if greater than zero | |
| 0x0D | JLT | Jump if less than zero | |
| 0x0E | CALL | Call subroutine | |
| 0x0F | RET | Return from subroutine | |
| 0x2E | JOV | Jump on overflow | |
| 0x2F | BRU | Branch unconditional |
| Opcode | Hex | Mnemonic | Description |
|---|---|---|---|
| 0x10 | PUSH | Push ACC onto stack | |
| 0x11 | POP | Pop stack into ACC |
| Opcode | Hex | Mnemonic | Description |
|---|---|---|---|
| 0x12 | SHIFT_L | Shift left by operand bits | |
| 0x13 | SHIFT_R | Shift right by operand bits | |
| 0x28 | ROL | Rotate left | |
| 0x29 | ROR | Rotate right |
| Opcode | Hex | Mnemonic | Description |
|---|---|---|---|
| 0x14 | MUL | Multiply ACC by memory | |
| 0x15 | DIV | Divide ACC by memory | |
| 0x16 | MOD | Modulo ACC by memory | |
| 0x17 | CMP | Compare ACC with memory (sets flags only) | |
| 0x22 | ADC | Add with carry | |
| 0x23 | SBC | Subtract with carry | |
| 0x24 | NEG | Negate ACC (two's complement) | |
| 0x25 | COM | Complement ACC (one's complement) | |
| 0x26 | INC | Increment ACC | |
| 0x27 | DEC | Decrement ACC |
| Opcode | Hex | Mnemonic | Description |
|---|---|---|---|
| 0x18 | MOVE | Move memory to memory | |
| 0x19 | INPUT | Read from console to ACC | |
| 0x1A | OUTPUT | Write ACC to console | |
| 0x1B | LDA | Load A register | |
| 0x1C | LDB | Load B register | |
| 0x1D | STA | Store A register | |
| 0x1E | STB | Store B register | |
| 0x1F | LDX | Load index register | |
| 0x20 | STX | Store index register |
| Opcode | Hex | Mnemonic | Description |
|---|---|---|---|
| 0x2A | SKE | Skip next if equal (zero flag set) | |
| 0x2B | SKN | Skip next if not equal | |
| 0x2C | SKG | Skip next if greater | |
| 0x2D | SKL | Skip next if less |
| Opcode | Hex | Mnemonic | Description |
|---|---|---|---|
| 0x21 | EOR | Exclusive OR (alias for XOR) | |
| 0x30 | EXU | Execute instruction at address | |
| 0x31 | MIY | Add index register to memory | |
| 0x32 | INT | Software interrupt | |
| 0x33 | RTI | Return from interrupt |
| Address | Device | Register |
|---|---|---|
| 0x3F000 | Tape Drive | Status |
| 0x3F001 | Tape Drive | Data |
| 0x3F002 | Tape Drive | Command |
| 0x3F010 | Card Reader | Status |
| 0x3F011 | Card Reader | Data |
| 0x3F020 | Console | Status |
| 0x3F021 | Console | Data |
| 0x3F030 | Terminal | Status |
| 0x3F031 | Terminal | Data |
| 0x3F032 | Terminal | Command |
Simulates a magnetic tape unit with read, write, and positioning capabilities.
Status bits:
- Bit 0: Mounted
- Bit 1: Ready
- Bit 2: End of tape
Commands:
- 0: Rewind
- 1: Backward one record
- 2: Forward one record
Simulates a punch card reader (read-only).
Status bits:
- Bit 0: Ready
- Bit 1: Card available
Simple character I/O device that echoes to stdout.
Status bits:
- Bit 0: Ready
- Bit 1: Input available
- Bit 2: Output pending
80×24 character terminal with cursor control and screen buffer.
Status bits:
- Bit 0: Ready
- Bit 1: Input available
- Bits 8–15: Cursor X position
- Bits 16–23: Cursor Y position
Commands:
- 0: Clear screen
- 1: Set cursor X (position in bits 8–15)
- 2: Set cursor Y (position in bits 8–15)
- 3: Set cursor X,Y (X in bits 8–15, Y in bits 16–23)
- 4: Set cursor visibility (bit 8)
The emulator includes a built-in debugger with the following commands:
| Command | Alias | Description |
|---|---|---|
help |
Show command list | |
break <addr> |
b |
Set breakpoint at hex address |
delete <addr> |
d |
Remove breakpoint |
list |
List all breakpoints | |
continue |
c |
Continue execution |
step |
s |
Execute one instruction |
run <id> |
r |
Start processor (0–5) |
stop |
Stop all processors | |
print [id] |
p |
Print processor or system state |
memory <addr> |
m |
Examine memory at hex address |
display |
disp |
Render terminal screen |
load <file> [id] [addr] |
Load binary file | |
quit |
q |
Exit emulator |
dbg> break 0x10
Breakpoint set at 0x10
dbg> run 0
Running processor 0
dbg> print 0
Processor 0 State:
State: BREAKPOINT
PC: 0x00010
ACC: 0x000048
...
dbg> memory 200
Memory[0x200] = 0x000048
dbg> continue
dbg> display
Programs are stored as raw 24-bit words in big-endian format (3 bytes per word):
Byte 0: bits 23–16 (high byte)
Byte 1: bits 15–8 (middle byte)
Byte 2: bits 7–0 (low byte)
From the debugger:
dbg> load program.bin 0 0x100
Loaded 42 words from program.bin
This loads program.bin into processor 0 starting at address 0x100.
Write programs in human-readable assembly:
; Comments start with semicolon
LABEL: OPCODE OPERAND
; Data directives
.WORD value ; 24-bit word
.BYTE value ; 8-bit byte
.STRING "text" ; String dataLOAD 0x200 ; Hex number
LOAD 512 ; Decimal number
LOAD 'A' ; Character literal
LOAD MYLABEL ; Label referenceAll examples are in the examples/ directory and can be loaded via the GUI.
; Print "Hello, World!" to terminal
START:
LOAD CHAR_H
OUTPUT 0x3F031 ; Terminal data register
LOAD CHAR_E
OUTPUT 0x3F031
; ... (more characters)
HALT
CHAR_H: .BYTE 'H'
CHAR_E: .BYTE 'e'
; ... (more data); Count down from 10 to 0
START:
LOAD COUNT
LOOP:
ADD ASCII_ZERO ; Convert to ASCII
OUTPUT 0x3F031 ; Display digit
LOAD SPACE
OUTPUT 0x3F031
LOAD COUNT
DEC
STORE COUNT
JNZ LOOP ; Loop if not zero
HALT
COUNT: .WORD 10
ASCII_ZERO: .WORD 48
SPACE: .WORD 32; Generate first 10 Fibonacci numbers
START:
LOAD ZERO
STORE FIB_A
LOAD ONE
STORE FIB_B
LOAD TEN
STORE COUNTER
LOOP:
LOAD FIB_A
OUTPUT 0x3F031
; Calculate next: FIB_C = FIB_A + FIB_B
LOAD FIB_A
ADD FIB_B
STORE FIB_C
; Shift: A=B, B=C
LOAD FIB_B
STORE FIB_A
LOAD FIB_C
STORE FIB_B
LOAD COUNTER
DEC
STORE COUNTER
JNZ LOOP
HALT
ZERO: .WORD 0
ONE: .WORD 1
TEN: .WORD 10
FIB_A: .WORD 0
FIB_B: .WORD 0
FIB_C: .WORD 0
COUNTER: .WORD 0; Test PUSH, POP, CALL, RET
START:
LOAD VAL1
PUSH
LOAD VAL2
PUSH
LOAD VAL3
PUSH
CALL DISPLAY_SUM
HALT
DISPLAY_SUM:
POP
STORE TEMP1
POP
STORE TEMP2
POP
ADD TEMP1
ADD TEMP2
OUTPUT 0x3F031
RET
VAL1: .WORD 100
VAL2: .WORD 200
VAL3: .WORD 300
TEMP1: .WORD 0
TEMP2: .WORD 0For direct machine code generation:
// Encode instructions as: (opcode << 18) | address
std::vector<Word24> program = {
(0x02 << 18) | 0x200, // LOAD 0x200
(0x1A << 18) | 0x3F031, // OUTPUT to terminal
(0x00 << 18) | 0x00 // HALT
};
// Store data
memory.write(0x200, 'H');┌──────────────────────────────────────────────────────────────────┐
│ [F1]Load [F2]Assemble [F3]Run [F4]Step [F5]Stop [F6]Reset [F10]Quit │
├────────────────────────┬─────────────────────────────────────────┤
│ │ │
│ Terminal Display │ Processor 0 State │
│ (Program output │ State: IDLE │
│ appears here) │ PC: 0x00000 │
│ │ ACC: 0x000000 │
│ │ B: 0x000000 │
│ │ X: 0x000000 │
│ │ SP: 0x0FFFF │
│ │ Flags: ---- │
│ │ Cycles: 0 │
│ │ Instructions: 0 │
├────────────────────────┴─────────────────────────────────────────┤
│ Memory View (0x0000-0x007F) │
│ 0000: 000000 000000 000000 000000 000000 000000 000000 000000 │
│ 0008: 000000 000000 000000 000000 000000 000000 000000 000000 │
│ ... │
├───────────────────────────────────────────────────────────────────┤
│ Status: Welcome to BCC-500! Press F1 to load a program... │
└───────────────────────────────────────────────────────────────────┘
| Key | Action | Description |
|---|---|---|
| F1 | Load | Browse and load assembly files |
| F2 | Assemble | Reload/reassemble current program |
| F3 | Run | Execute the loaded program |
| F4 | Step | Execute single instruction |
| F5 | Stop | Halt execution |
| F6 | Reset | Reset processor state |
| F10 | Quit | Exit emulator |
When you press F1:
- Use ↑/↓ arrow keys to navigate
- Press Enter on directories to open them
- Press Enter on
.asmfiles to load them - Press ESC to cancel
The assembler automatically compiles the program and loads it into memory.
#include "Assembler.h"
Assembler assembler;
std::string source = "START: LOAD 0x100\n HALT";
// Assemble the program
if (assembler.assemble(source)) {
const std::vector<Word24>& machineCode = assembler.getMachineCode();
const std::map<std::string, Addr18>& labels = assembler.getLabels();
// Load into system
system.loadProgram(machineCode, 0, 0);
} else {
// Handle errors
const std::vector<std::string>& errors = assembler.getErrors();
for (const auto& error : errors) {
std::cerr << error << std::endl;
}
}Main system class that manages processors, memory, and I/O.
BCC500System system;
// Load and run a program
system.loadProgram(program, processorId, startAddress);
system.runProcessor(processorId, maxCycles);
// Async execution
system.runProcessorAsync(processorId);
system.runAllProcessorsAsync();
system.stopAllProcessors();
system.joinAllProcessors();
// Access components
system.getProcessor(id);
system.getMemory();
system.getDebugger();
system.getIOController();Utility class for loading and saving binary programs.
// Load a binary file
auto program = BinaryLoader::loadBinary("program.bin");
// Save a program to binary
BinaryLoader::saveBinary("output.bin", program);The emulator uses the following synchronization:
SharedMemory: Mutex-protected read/write operationsIOController: Mutex-protected device accessDebugger: Condition variable for breakpoint synchronizationProcessorState: Atomic operations for state changes
Multiple processors can safely execute concurrently with shared memory access properly serialized.
bccemulator/
├── emulator # Original CLI emulator
├── gui-emulator # New GUI emulator (recommended)
├── Emulator.cpp # Original source with demo
├── EmulatorCore.h # Core classes (no main)
├── Assembler.h # Assembly language parser
├── GuiEmulator.cpp # GUI implementation
├── Makefile # Build system
├── readme.md # This file
├── QUICKSTART.md # User-friendly guide
├── CLAUDE.md # Developer documentation
└── examples/ # Example programs
├── hello.asm # Hello World
├── counter.asm # Countdown demo
├── fibonacci.asm # Fibonacci sequence
├── multiply.asm # Multiplication
└── stack_test.asm # Stack operations
| File | Description | Demonstrates |
|---|---|---|
hello.asm |
Prints "Hello, World!" | Basic I/O, data storage |
counter.asm |
Counts down from 10 | Loops, conditionals |
fibonacci.asm |
First 10 Fibonacci numbers | Arithmetic, loops |
multiply.asm |
Multiply 7 × 6 by repeated addition | Arithmetic loops |
stack_test.asm |
Tests stack operations | PUSH, POP, CALL, RET |
All examples can be loaded through the GUI file browser (F1).
make all # Build both emulator versions
make emulator # Build original CLI version
make gui-emulator # Build GUI version
make run # Run original emulator
make run-gui # Run GUI emulator
make clean # Remove build artifacts
make debug # Build with debug symbols- QUICKSTART.md — Quick start guide for GUI emulator with examples
- CLAUDE.md — Technical architecture and developer guide
- readme.md — This file, complete reference
- Use meaningful labels —
LOOP:instead ofL1: - Add comments — Explain what your code does
- Organize data — Put data sections at the end
- Test incrementally — Use F4 (step) to debug
- Start simple — Load and run the example programs first
- Watch the registers — Monitor PC, ACC, and flags during execution
- Single-step for debugging — Press F4 to step through instructions
- Check the terminal — Program output appears in the Terminal Display
- Check the status bar — Error messages appear there
- Verify addresses — Terminal is 0x3F031, Console is 0x3F021
- Watch for HALT — Programs should end with HALT instruction
- Reset when needed — Press F6 to reset and F2 to reassemble
- No floating-point support
- No virtual memory or memory protection
- Simplified interrupt model
- Terminal display uses ANSI escape codes (may not work on all terminals)
- GUI requires ncurses library
GUI won't compile:
# Install ncurses development library
# Fedora/RHEL:
sudo dnf install ncurses-devel
# Ubuntu/Debian:
sudo apt-get install libncurses-devAssembly errors:
- Check syntax:
OPCODE OPERAND - Verify label names are defined
- Ensure data directives have values
Program doesn't run:
- Make sure it's loaded (F1)
- Check that it ends with HALT
- Verify OUTPUT addresses are correct
Terminal output not showing:
- Use address 0x3F031 for terminal
- Make sure you press F3 to run
- Check Terminal Display window (top-left)
This project is provided as-is for educational purposes.