m68kemu

package module
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 28, 2026 License: MIT Imports: 6 Imported by: 0

README

m68kemu

A Motorola 68000 emulator written in Go.

This project provides a Motorola 68000 CPU emulator for retro-computing projects, with a current focus on becoming part of an Atari ST emulator. The core aims to be timing-aware, testable, and easy to embed in a larger machine model.

Features

  • Motorola 68000 instruction set emulation.
  • Timing-aware execution with per-instruction cycle accounting.
  • Supervisor and user modes.
  • Interrupt handling and exception processing.
  • Correct short exception frames for group 1/2 exceptions and 68000 group 0 bus/address error frames.
  • 24-bit address bus with support for multiple devices, fixed-range mappings, and Atari ST-style region layout.
  • Tracing, breakpoints, cycle-budgeted execution, and verbose logging helpers with instruction-range disassembly.
  • Optional cycle scheduler hooks for machine-level devices such as timers, video, DMA, and interrupt controllers.

Current Status

The CPU core is in good shape for integration work:

  • Instruction execution, stack behavior, interrupts, and most commonly used addressing modes are covered by tests.
  • RESET now follows machine-friendly semantics for an Atari ST integration: the CPU instruction resets attached devices but does not erase RAM contents.
  • Bus and address faults now use the richer 68000 group 0 stack frame, which is important for realistic system error handling.
  • The bus has fast paths for simple memory setups and fixed-range mappings, which keeps the core practical for full-machine emulation.

Still missing for a complete Atari ST:

  • The rest of the ST chipset and memory-mapped I/O devices.
  • A full machine-level reset / cold-boot model on top of CPU RESET.
  • More detailed interrupt acknowledge and device-level timing behavior.
  • Prefetch-sensitive behavior and any remaining compatibility gaps found by larger TOS / software workloads.

Getting Started

This package is designed to be used as a library in your own projects.

Installation
go get github.com/jenska/m68kemu
Example Usage

Here's a simple example of how to set up the CPU, load a program, and run it:

package main

import (
 "fmt"
 "log"

 "github.com/jenska/m68kemu"
)

func main() {
 // Create a 64KB RAM device at address 0.
 ram := m68kemu.NewRAM(0, 64*1024)

 // Create a bus and attach the RAM.
 bus := m68kemu.NewBus(ram)

 // Set up the initial stack pointer and program counter.
 // SSP at 0x1000, PC at 0x2000.
 ram.Write(m68kemu.Long, 0, 0x1000)
 ram.Write(m68kemu.Long, 4, 0x2000)

 // Create the CPU.
 cpu, err := m68kemu.NewCPU(bus)
 if err != nil {
  log.Fatalf("Failed to create CPU: %v", err)
 }

 // Assemble a simple program: MOVEQ #5, D0 (opcode 0x7005)
 program := []byte{0x70, 0x05}
 startPC, _ := ram.Read(m68kemu.Long, 4)

 for i, b := range program {
  if err := ram.Write(m68kemu.Byte, startPC+uint32(i), uint32(b)); err != nil {
   log.Fatalf("Failed to write program: %v", err)
  }
 }

 // Step one instruction.
 if err := cpu.Step(); err != nil {
  log.Fatalf("CPU step failed: %v", err)
 }

 // Print registers to see the result.
 regs := cpu.Registers()
 fmt.Printf("D0 = %d\n", regs.D[0]) // Should be 5
 fmt.Printf("PC = 0x%04x\n", regs.PC) // Should be 0x2002
}
Atari ST-Oriented Bus Setup

The bus can also be built from explicit 24-bit address ranges, which is useful when wiring together an Atari ST memory map:

ram := m68kemu.NewRAM(0x000000, 512*1024)
tos := m68kemu.NewRAM(m68kemu.STTOSStart, 192*1024)

bus := m68kemu.NewAtariSTBus(
 m68kemu.STRegionMapping{Start: 0x000000, End: 0x07ffff, Device: ram},
 m68kemu.STRegionMapping{Start: m68kemu.STTOSStart, End: m68kemu.STTOSEnd, Device: tos},
)

The built-in Atari ST constants map TOS ROM to 0xFC0000-0xFEFFFF and MMIO to 0xFF8000-0xFFFFFF. For now this is just a convenient fixed-range decoder; the actual ST devices still need to be implemented on top of it.

Cycle Scheduler

Machine devices can follow CPU time by attaching a scheduler:

scheduler := m68kemu.NewCycleScheduler()
cpu.SetScheduler(scheduler)

scheduler.ScheduleAfter(512, func(now uint64) {
 // Run a timer tick, trigger an interrupt, advance video state, etc.
})

The scheduler is intentionally small at this stage. It is meant as a foundation for ST components rather than a finished machine-timing framework.

Verbose Logging And Range Disassembly

The emulator includes helpers for both one-off disassembly and trace logging:

logger := m68kemu.NewVerboseLogger(cpu, bus, os.Stdout, m68kemu.VerboseLoggerOptions{
	IncludeRegisters: true,
	IncludeCycles:    true,
	MemoryRanges: []m68kemu.MemoryRange{
		{Start: 0x2000, Length: 0x10, Label: "program"},
	},
})
cpu.SetTracer(logger.Trace)

lines, err := m68kemu.DisassembleMemoryRange(bus, 0x2000, 0x10)
if err != nil {
	log.Fatalf("disassembly failed: %v", err)
}
for _, line := range lines {
	fmt.Println(line)
}

Verbose trace lines include the current PC, decoded assembly, and optionally the total cycle count. When the tracer has access to the fetched instruction bytes, the logger also includes the raw opcode and per-instruction cycle delta, for example:

TRACE PC 00002000 OPCODE 7005 DELTA 4 CYCLES 4 MOVEQ #5, D0

These helpers use the bus Peek path when available so debug output does not trigger device side effects, and the verbose logger prefers TraceInfo.Bytes for disassembly so the trace remains accurate even when fetch-side effects would make a second bus read misleading.

Testing

The emulator has an extensive test suite, including instruction-level tests and small programs.

To run the tests:

go test ./...

To run the benchmarks:

go test -bench=. ./...

Performance Notes

Recent profiling work focused on the interpreter hot path:

  • bus fast paths for simple and fixed-range mappings
  • reduced wait-state overhead when no device contributes extra wait states
  • fewer allocations on reset / benchmark loops
  • predecoded opcode metadata for common decode fields

On the current benchmark set, that work brought the project to roughly:

  • BenchmarkBubbleSort: ~3.08 ms/op
  • BenchmarkPrimeSieve: ~5.84 ms/op
  • BenchmarkRecursiveFibonacci: ~28.6 ms/op

See doc/benchmark_report.md for more detail.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Documentation

Index

Constants

View Source
const (
	Version           = "1.2.0"
	XBusError         = 2
	XAddressError     = 3
	XIllegal          = 4
	XDivByZero        = 5
	XPrivViolation    = 8
	XLineA            = 10
	XLineF            = 11
	XUninitializedInt = 15
	XTrap             = 32
)
View Source
const (
	STCartridgeStart uint32 = 0xFA0000
	STCartridgeEnd   uint32 = 0xFBFFFF
	STTOSStart       uint32 = 0xFC0000
	STTOSEnd         uint32 = 0xFEFFFF
	STIOStart        uint32 = 0xFF8000
	STIOEnd          uint32 = 0xFFFFFF
)

Variables

This section is empty.

Functions

This section is empty.

Types

type AddressBus

type AddressBus interface {
	Read(s Size, address uint32) (uint32, error)
	Write(s Size, address uint32, value uint32) error
	Reset()
}

AddressBus for accessing address areas

type AddressError

type AddressError uint32

func (AddressError) Error

func (ae AddressError) Error() string

type AddressRange added in v1.2.0

type AddressRange struct {
	Start uint32
	End   uint32
}

func (AddressRange) Contains added in v1.2.0

func (r AddressRange) Contains(address uint32) bool

type AddressRangeDevice added in v1.1.0

type AddressRangeDevice interface {
	AddressRange() (start uint32, end uint32)
}

AddressRangeDevice exposes a fixed address range that can be indexed by the bus.

type Breakpoint

type Breakpoint struct {
	Address   uint32
	OnExecute bool
	OnRead    bool
	OnWrite   bool
	Halt      bool
	Callback  func(BreakpointEvent) error
}

type BreakpointEvent

type BreakpointEvent struct {
	Type      BreakpointType
	Address   uint32
	Registers Registers
}

type BreakpointHit

type BreakpointHit struct {
	Address uint32
	Type    BreakpointType
}

func (BreakpointHit) Error

func (bh BreakpointHit) Error() string

type BreakpointType

type BreakpointType int
const (
	BreakpointExecute BreakpointType = iota
	BreakpointRead
	BreakpointWrite
)

func (BreakpointType) String

func (bt BreakpointType) String() string

type Bus

type Bus struct {
	// contains filtered or unexported fields
}

Bus multiplexes memory access between attached devices and performs common checks such as alignment and bus error handling.

func NewAtariSTBus added in v1.2.0

func NewAtariSTBus(mappings ...STRegionMapping) *Bus

func NewBus

func NewBus(devices ...Device) *Bus

NewBus constructs a bus optionally seeded with devices.

func (*Bus) AddDevice

func (b *Bus) AddDevice(device Device)

AddDevice attaches an additional device to the bus.

func (*Bus) Peek added in v1.1.0

func (b *Bus) Peek(s Size, address uint32) (uint32, error)

Peek reads from the mapped device without charging wait states. Devices may use this for debugger-friendly, side-effect-free inspection.

func (*Bus) Read

func (b *Bus) Read(s Size, address uint32) (uint32, error)

Read forwards a read to the mapped device after performing alignment and mapping checks.

func (*Bus) Reset

func (b *Bus) Reset()

Reset propagates a reset to all attached devices.

func (*Bus) SetWaitHook

func (b *Bus) SetWaitHook(hook WaitHook)

SetWaitHook installs a callback that receives the configured wait states for every transaction. Callers can use this to count cycles or block for a desired duration.

func (*Bus) SetWaitStates

func (b *Bus) SetWaitStates(states uint32)

SetWaitStates defines how many states the bus should report for each transaction when a WaitHook is configured.

func (*Bus) Write

func (b *Bus) Write(s Size, address uint32, value uint32) error

Write forwards a write to the mapped device after performing alignment and mapping checks.

type BusAccessCallback added in v1.2.0

type BusAccessCallback func(BusAccessInfo)

type BusAccessInfo added in v1.2.0

type BusAccessInfo struct {
	Address          uint32
	Size             Size
	Value            uint32
	Write            bool
	InstructionFetch bool
}

type BusError

type BusError uint32

func (BusError) Error

func (be BusError) Error() string

type CPU

type CPU interface {
	Registers() Registers
	DebugState() DebugState
	Step() error
	RunCycles(budget uint64) error
	RunInstructions(count uint64) error
	RunUntil(options RunUntilOptions) (RunResult, error)
	Reset() error
	SetTracer(TraceCallback)
	SetExceptionTracer(ExceptionCallback)
	SetBusTracer(BusAccessCallback)
	SetScheduler(*CycleScheduler)
	Scheduler() *CycleScheduler
	AddBreakpoint(Breakpoint)
	RequestInterrupt(level uint8, vector *uint8) error
	Cycles() uint64
}

CPU exposes the minimal interface for interacting with the emulator core.

func NewCPU

func NewCPU(bus AddressBus) (CPU, error)

type CycleListener added in v1.1.0

type CycleListener interface {
	AdvanceCycles(delta uint64, now uint64)
}

type CycleScheduler added in v1.1.0

type CycleScheduler struct {
	// contains filtered or unexported fields
}

func NewCycleScheduler added in v1.2.0

func NewCycleScheduler() *CycleScheduler

func (*CycleScheduler) AddListener added in v1.2.0

func (s *CycleScheduler) AddListener(listener CycleListener)

func (*CycleScheduler) Advance added in v1.2.0

func (s *CycleScheduler) Advance(delta uint64)

func (*CycleScheduler) Now added in v1.2.0

func (s *CycleScheduler) Now() uint64

func (*CycleScheduler) Reset added in v1.2.0

func (s *CycleScheduler) Reset(now uint64)

func (*CycleScheduler) Schedule added in v1.2.0

func (s *CycleScheduler) Schedule(at uint64, fn func(now uint64))

func (*CycleScheduler) ScheduleAfter added in v1.2.0

func (s *CycleScheduler) ScheduleAfter(delta uint64, fn func(now uint64))

type DebugFaultInfo added in v1.2.0

type DebugFaultInfo struct {
	Address          uint32
	PC               uint32
	Opcode           uint16
	FunctionCode     uint16
	Write            bool
	InstructionFetch bool
	Valid            bool
}

type DebugState added in v1.2.0

type DebugState struct {
	Registers     Registers
	InException   bool
	InterruptMask uint8
	LastFault     DebugFaultInfo
	LastException ExceptionInfo
	HasException  bool
}

type Device

type Device interface {
	Contains(address uint32) bool
	Read(Size, uint32) (uint32, error)
	Write(Size, uint32, uint32) error
	Reset()
}

Device represents a memory-mapped peripheral on the address bus. Implementations are expected to be safe for repeated Reset calls and must internally validate the address ranges they cover.

func MapDevice added in v1.1.0

func MapDevice(start, end uint32, device Device) Device

type DisassemblyLine added in v1.2.0

type DisassemblyLine struct {
	Address  uint32
	Bytes    []byte
	Assembly string
}

DisassemblyLine captures one decoded instruction plus the backing bytes.

func DisassembleInstruction added in v1.2.0

func DisassembleInstruction(bus AddressBus, address uint32) (DisassemblyLine, error)

DisassembleInstruction decodes one instruction at the given bus address.

func DisassembleMemoryRange added in v1.2.0

func DisassembleMemoryRange(bus AddressBus, start uint32, length uint32) ([]DisassemblyLine, error)

DisassembleMemoryRange decodes instructions sequentially until the range is covered.

func (DisassemblyLine) String added in v1.2.0

func (line DisassemblyLine) String() string

String renders a disassembly line with its bytes for human-readable logs.

type ExceptionCallback added in v1.2.0

type ExceptionCallback func(ExceptionInfo)

type ExceptionInfo added in v1.2.0

type ExceptionInfo struct {
	Vector        uint32
	PC            uint32
	NewPC         uint32
	Opcode        uint16
	FaultAddress  uint32
	FaultValid    bool
	SR            uint16
	NewSR         uint16
	InterruptMask uint8
	Group0        bool
}

type InterruptController

type InterruptController struct {
	// contains filtered or unexported fields
}

func NewInterruptController

func NewInterruptController() *InterruptController

func (*InterruptController) HasPending added in v1.2.0

func (ic *InterruptController) HasPending(mask uint16) bool

func (*InterruptController) Pending

func (ic *InterruptController) Pending(mask uint16) (uint8, uint32, bool)

func (*InterruptController) Request

func (ic *InterruptController) Request(level uint8, vector *uint8) error

func (*InterruptController) Reset added in v1.2.0

func (ic *InterruptController) Reset()

type MappedDevice added in v1.1.0

type MappedDevice struct {
	// contains filtered or unexported fields
}

MappedDevice wraps another device with an explicit 24-bit address range.

func (*MappedDevice) AddressRange added in v1.1.0

func (d *MappedDevice) AddressRange() (uint32, uint32)

func (*MappedDevice) Contains added in v1.1.0

func (d *MappedDevice) Contains(address uint32) bool

func (*MappedDevice) Peek added in v1.1.0

func (d *MappedDevice) Peek(size Size, address uint32) (uint32, error)

func (*MappedDevice) Read added in v1.1.0

func (d *MappedDevice) Read(size Size, address uint32) (uint32, error)

func (*MappedDevice) Reset added in v1.1.0

func (d *MappedDevice) Reset()

func (*MappedDevice) Write added in v1.1.0

func (d *MappedDevice) Write(size Size, address uint32, value uint32) error

type MemoryRange added in v1.2.0

type MemoryRange struct {
	Start  uint32
	Length uint32
	Label  string
}

MemoryRange describes a region of memory to disassemble for debug output.

type PeekDevice added in v1.1.0

type PeekDevice interface {
	Peek(Size, uint32) (uint32, error)
}

PeekDevice exposes a side-effect-free read path for debugging and disassembly.

type RAM

type RAM struct {
	// contains filtered or unexported fields
}

simple flat memory structure

func NewRAM

func NewRAM(offset, size uint32) *RAM

func (*RAM) AddressRange added in v1.2.0

func (ram *RAM) AddressRange() (uint32, uint32)

func (*RAM) Contains

func (ram *RAM) Contains(address uint32) bool

func (*RAM) Peek added in v1.2.0

func (ram *RAM) Peek(s Size, address uint32) (uint32, error)

func (*RAM) Read

func (ram *RAM) Read(s Size, address uint32) (uint32, error)

func (*RAM) Reset

func (ram *RAM) Reset()

func (*RAM) Write

func (ram *RAM) Write(s Size, address uint32, value uint32) error

type Registers

type Registers struct {
	D   [8]int32
	A   [8]uint32
	PC  uint32
	SR  uint16
	SSP uint32
	USP uint32
	IR  uint16 // instruction register
}

Registers represents the programmer visible registers of the 68000 CPU.

func (*Registers) String

func (regs *Registers) String() string

type RunResult added in v1.2.0

type RunResult struct {
	Reason       RunStopReason
	Instructions uint64
	Cycles       uint64
	PC           uint32
	Exception    ExceptionInfo
	HasException bool
}

type RunStopReason added in v1.2.0

type RunStopReason int
const (
	RunStopNone RunStopReason = iota
	RunStopInstructionLimit
	RunStopPCInRange
	RunStopPCOutsideRange
	RunStopException
	RunStopIllegalOpcode
)

func (RunStopReason) String added in v1.2.0

func (reason RunStopReason) String() string

type RunUntilOptions added in v1.2.0

type RunUntilOptions struct {
	MaxInstructions   uint64
	StopOnException   bool
	StopOnIllegal     bool
	StopOnPCRange     *AddressRange
	StopWhenPCOutside *AddressRange
}

type STRegionMapping added in v1.2.0

type STRegionMapping struct {
	Start  uint32
	End    uint32
	Device Device
}

type ScheduledEvent added in v1.1.0

type ScheduledEvent struct {
	At uint64
	Fn func(now uint64)
}

type Size

type Size uint32
const (
	Byte Size = 1
	Word Size = 2
	Long Size = 4
)

type TraceCallback

type TraceCallback func(TraceInfo)

type TraceInfo

type TraceInfo struct {
	PC         uint32
	SR         uint16
	Registers  Registers
	Opcode     uint16
	Bytes      []byte
	CycleDelta uint32
}

type VerboseLogger added in v1.2.0

type VerboseLogger struct {
	// contains filtered or unexported fields
}

VerboseLogger formats trace callbacks with disassembly and optional state dumps.

func NewVerboseLogger added in v1.2.0

func NewVerboseLogger(cpu CPU, bus AddressBus, writer io.Writer, options VerboseLoggerOptions) *VerboseLogger

NewVerboseLogger builds a trace callback helper that writes detailed execution logs.

func (*VerboseLogger) Trace added in v1.2.0

func (logger *VerboseLogger) Trace(info TraceInfo)

Trace implements TraceCallback for use with CPU.SetTracer.

type VerboseLoggerOptions added in v1.2.0

type VerboseLoggerOptions struct {
	IncludeRegisters bool
	IncludeCycles    bool
	MemoryRanges     []MemoryRange
}

VerboseLoggerOptions controls how much detail a VerboseLogger emits.

type WaitHook

type WaitHook func(states uint32)

WaitHook can be used to simulate wait states or count cycles for bus access.

type WaitStateDevice

type WaitStateDevice interface {
	WaitStates(Size, uint32) uint32
}

WaitStateDevice optionally advertises additional wait states a device imposes per transaction. Implementations may vary their contribution based on access size and address.

Directories

Path Synopsis
cmd
qsortdemo command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL