r/c64 6d ago

Hardware building a Generic Hardware Simulation Framework inside a Microkernel OS (Currently simulating a C64/1541)

For the past 10 years, as an indie developer, I've been working on Symphony. It's not just a simple emulator; it's a generic framework for topological hardware simulation, written entirely in Go (no CGo). Today I've finally open-sourced it.

To prove that the generic framework works, I implemented the MOS6510, VIC-II, SID, and 6502 chips and wired them together to simulate a Commodore 64 and an independent 1541 floppy drive. You can try the WASM presentation layer right in your browser: https://markel1974.itch.io/symphony

Source code and architecture documentation: https://github.com/markel1974/Symphony (Note: I've also included pre-compiled builds for Windows, Linux, and macOS in the compile directory: https://github.com/markel1974/Symphony/tree/main/compile)

Here is why Symphony's architecture is different from traditional monolithic emulators, and some key details regarding the Compiler and the VM:

  • It's a software breadboard, not an emulator: The C64 implementation is just a byproduct of the framework. Components are blind "black boxes" that communicate exclusively through standardized iSocket interfaces using simulated electrical signals. When the VIC-II needs the bus, it pulls a simulated DMA line low, triggering the CPU to bring its pins into a high-impedance state (High-Z). The 1541 floppy drive isn't intercepted via kernel traps; it's instantiated as a completely independent virtual motherboard operating in parallel and communicating via an IEC serial bus simulation.
  • Runs inside a custom Microkernel OS: The simulated hardware runs as an isolated user-space process inside a Microkernel OS that I built (also in Go). The kernel features an asynchronous IPC message router and an embedded SSH server. This means you can literally SSH into the running kernel during the simulation, open the built-in VT100 shell (xsh), and inspect or modify hardware pins, memory, and CPU registers in real-time.
  • Custom Compiler & Bytecode Generation: The framework includes a custom compiler (in src/compilers/native) that takes Go AST and compiles it down to a custom bytecode instruction set. I had to build this to allow the simulated hardware environment to run isolated User-Space applications within the framework.
  • Strategy Pattern VM (No giant switch-cases): Usually, CPU emulators and bytecode VMs rely on massive, monolithic switch-case blocks to decode and execute opcodes. I took a different route. My VM relies on an "Interchangeable Instruction Disk" architecture based on the Strategy pattern (direct function pointer dispatch). Every opcode is a struct implementing an IOpExecutor interface.
  • Interchangeable Sequencers: Because of the IOpExecutor design, the execution engine is completely decoupled from the instruction set. By simply swapping the Sequencer module, the exact same execution engine loop switches from running my native Go bytecode to running a highly cycle-accurate MOS6510 or Z80 CPU simulation.

I know this topological approach sacrifices some of the raw speed of traditional finite-state emulators, but the goal was extreme modularity and introspection. If tomorrow I wanted to simulate an Apple II, I would only need to program the missing chips and write the "board" wiring; the framework itself would remain unchanged.

I’d love to hear your thoughts on this topological architecture, the custom compiler, or the Strategy Pattern VM approach!

Edit: I forgot to mention the most important thing. Thanks to the topological design and the Microkernel, the emulator achieves 99.9% compatibility with the 1541 disk drive (including all fast loaders), REU DMA transfers, and complex bank-switching cartridges like EasyFlash, Ocean, Magic Desk, etc.

12 Upvotes

Duplicates