i6502

package module
v0.0.0-...-db75afd Latest Latest
Warning

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

Go to latest
Published: Aug 9, 2021 License: MIT Imports: 2 Imported by: 1

README

i6502 - A 6502 Emulator

Build Status Go Docs

This is an emulator of the i6502 hardward project I'm doing.

It's written in Golang and comes fully tested.

WebsiteDocumentation

Background

The MOS 6502 Microprocessor has been around sinc 1975 and is used in many popular systems, like the Apple II, Atari 2600, Commodore 64 and the Nintendo Entertainment System (NES).

It features an 8-bit accumulator and ALU, two 8-bit index registers and a 16-bit memory bus, allowing the processor to access up to 64kB of memory.

I/O is mapped to memory, meaning that both RAM, ROM and I/O are addressed over the same 16-bit address bus.

Because of it's simple and elegant design and the plethora of information available about this microprocessor, the 6502 is very useful for learning and hobby projects.

The 65C02 is a updated version of the 6502. It includes some bug fixes and new instructions. The goal is for i6502 to fully support the 65C02 as well.

What's included in the emulator?

  • 6502 Microprocessor, fully tested
  • 16-bit address bus, with attachable memory
  • RAM Memory
  • 6551 Asynchronous Communications Interface Adapter (ACIA)

What's not (yet) included?

  • Proper Golang packaging and documentation
  • 65C02 support
  • Roms
  • I/O (VIA 6522)
  • Batteries

Getting started

Although this package contains everything you need, there is not single 'emulator' program yet. The CPU, address bus and RAM components are all available to you, but documentation is still lacking.

For now, you can checkout the project, and run the tests.

go get github.com/ariejan/i6502
cd $GOPATH/src/github.com/ariejan/i6502
go get -t
go test

License

This project is licensed under the MIT, see LICENSE for full details.

Contributors

Special thanks to the awesome folk at http://forum.6502.org for their support and shared knowledge.

Documentation

Overview

The i6502 package contains all the components needed to construct a working MOS 6502 emulated computer using different common parts, like the MOS 6502 or WDC 65C02, VIA 6522 (parallel I/O) and ACIA 6551 (serial I/O).

The CPU is the core of the system. It features an 8-bit accumulator (A) and two general purpose 8-bit index registers (X, Y). There is a 16-bit program counter (PC). The 8-bit stack pointer (SP) points to the 0x0100-0x1FF address space moves downward. The status register (P) contains bits indicating Zero, Negative, Break, Decimal, IrqDisable, Carry and Overflow conditions. The 6502 uses a 16-bit address bus to access 8-bit data values.

The AddressBus can be used to attach different components to different parts of the 16-bit address space, accessible by the 6502. Common layouts are

  • 64kB RAM at 0x0000-FFFF

Or

  • 32kB RAM at 0x0000-7FFF
  • VIA 6522 at 0x8000-800F
  • ACIA 6551 at 0x8800-8803
  • 16kB ROM at 0xC000-FFFF

Creating a new emulated machine entails three steps:

  1. Create the different memory components (Ram, Rom, IO)
  2. Create the AddressBus and attach memory
  3. Create the Cpu with the AddressBus

Example: create an emulator using the full 64kB address space for RAM

import "github.com/ariejan/i6502"

// Create Ram, 64kB in size
ram, err := i6502.NewRam(0x10000)

// Create the AddressBus
bus, err := i6502.NewAddressBus()

// And attach the Ram at 0x0000
bus.Attach(ram, 0x0000)

// Create the Cpu, with the AddressBus
cpu, err := i6502.NewCpu(bus)

The hardware pins `IRQ` and `RESB` are implemented and mapped to the functions `Interrupt()` and `Reset()`.

Running a program from memory can be done by loading the binary data into memory using `LoadProgram`. Keep in mind that the first two memory pages (0x0000-01FF) are reserved for zeropage and stack memory.

Example of loading a binary program from disk into memory:

import "io/ioutil"

program, err := ioutil.ReadFile(path)

// This will load the program (if it fits within memory)
// at 0x0200 and set cpu.PC to 0x0200 as well.
cpu.LoadProgram(program, 0x0200)

With all memory connected and a program loaded, all that's left is executing instructions on the Cpu. A single call to `Step()` will read and execute a single (1, 2 or 3 byte) instruction from memory.

To create a Cpu and have it running, simple create a go-routine.

go for {
    cpu.Step()
}()

Index

Constants

View Source
const (
	ZeropageBase = 0x0000 // 0x0000-00FF Reserved for zeropage instructions
	StackBase    = 0x0100 // 0x0100-01FF Reserved for stack

	ResetVector = 0xFFFC // 0xFFFC-FFFD
	IrqVector   = 0xFFFE // 0xFFFE-FFFF

)

Variables

This section is empty.

Functions

This section is empty.

Types

type Acia6551

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

ACIA 6551 Serial IO

This Asynchronous Communications Interface Adapater can be directly attached to the 6502's address and data busses.

It provides serial IO.

The supplied Rx and Tx channels can be used to read and wirte data to the ACIA 6551.

func NewAcia6551

func NewAcia6551(output chan []byte) (*Acia6551, error)

func (*Acia6551) Read

func (a *Acia6551) Read(p []byte) (n int, err error)

Implements io.Reader, for external programs to read TX'ed data from the serial output.

func (*Acia6551) ReadByte

func (a *Acia6551) ReadByte(address uint16) byte

Used by the AddressBus to read data from the ACIA 6551

func (*Acia6551) Reset

func (a *Acia6551) Reset()

Emulates a hardware reset

func (*Acia6551) Size

func (a *Acia6551) Size() uint16

func (*Acia6551) Write

func (a *Acia6551) Write(p []byte) (n int, err error)

Implements io.Writer, for external programs to write to the ACIA's RX

func (*Acia6551) WriteByte

func (a *Acia6551) WriteByte(address uint16, data byte)

Used by the AddressBus to write data to the ACIA 6551

type AddressBus

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

The AddressBus contains a list of all attached memory components, like Ram, Rom and IO. It takes care of mapping the global 16-bit address space of the Cpu to the relative memory addressing of each component.

func NewAddressBus

func NewAddressBus() (*AddressBus, error)

Creates a new, empty 16-bit AddressBus

func (*AddressBus) Attach

func (a *AddressBus) Attach(memory Memory, offset uint16)

Attach the given Memory at the specified memory offset.

To attach 16kB ROM at 0xC000-FFFF, you simple attach the Rom at address 0xC000, the Size of the Memory determines the end-address.

rom, _ := i6502.NewRom(0x4000)
bus.Attach(rom, 0xC000)

func (*AddressBus) Read16

func (a *AddressBus) Read16(address uint16) uint16

Convenience method to quickly read a 16-bit value from address and address + 1.

Note that we first read the LOW byte from address and then the HIGH byte from address + 1.

func (*AddressBus) ReadByte

func (a *AddressBus) ReadByte(address uint16) byte

Read an 8-bit value from Memory attached at the 16-bit address.

This will panic if you try to read from an address that has no Memory attached.

func (*AddressBus) String

func (a *AddressBus) String() string

Returns a string with details about the AddressBus and attached memory

func (*AddressBus) Write16

func (a *AddressBus) Write16(address uint16, data uint16)

Convenience method to quickly write a 16-bit value to address and address + 1.

Note that the LOW byte will be stored in address and the high byte in address + 1.

func (*AddressBus) WriteByte

func (a *AddressBus) WriteByte(address uint16, data byte)

Write an 8-bit value to the Memory at the 16-bit address.

This will panic if you try to write to an address that has no Memory attached or Memory that is read-only, like Rom.

type Cpu

type Cpu struct {
	A byte // Accumulator
	X byte // Index register X
	Y byte // Index register Y

	PC uint16 // 16-bit program counter
	P  byte   // Status Register
	SP byte   // Stack Pointer

	Bus *AddressBus // The address bus
}

The Cpu only contains the AddressBus, through which 8-bit values can be read and written at 16-bit addresses.

The Cpu has an 8-bit accumulator (A) and two 8-bit index registers (X,Y). There is a 16-bit Program Counter (PC) and an 8-bit Stack Pointer (SP), pointing to addresses in 0x0100-01FF.

The status register (P) contains flags for Zero, Negative, Break, Decimal, IrqDisable, Carry and Overflow flags.

func NewCpu

func NewCpu(bus *AddressBus) (*Cpu, error)

Create an new Cpu, using the AddressBus for accessing memory.

func (*Cpu) Interrupt

func (c *Cpu) Interrupt()

Simulate the IRQ pin.

This will push the current Cpu state to the stack (P + PC) and set the PC to the address read from the `IrqVector` (0xFFFE-FFFF)

func (*Cpu) LoadProgram

func (c *Cpu) LoadProgram(data []byte, location uint16)

Load the specified program data at the given memory location and point the Program Counter to the beginning of the program.

func (*Cpu) Reset

func (c *Cpu) Reset()

Reset the CPU, emulating the RESB pin.

The status register is reset to a know state (0x34, IrqDisabled set, Decimal unset, Break set).

Then the Program Counter is set to the value read from `ResetVector` (0xFFFC-FFFD).

Normally, no assumptions can be made about registers (A, X, Y) and the Stack Pointer. For convenience, these are reset to 0x00 (A,X,Y) and 0xFF (SP).

func (*Cpu) Step

func (c *Cpu) Step()

Read and execute the instruction pointed to by the Program Counter (PC)

func (*Cpu) Steps

func (c *Cpu) Steps(steps int)

func (*Cpu) String

func (c *Cpu) String() string

Returns a string containing the current state of the CPU.

type Instruction

type Instruction struct {
	OpType // Embed OpType

	Op8  byte   // 8-bit operand for 2-byte instructions
	Op16 uint16 // 16-bit operand for 3-byte instructions

	Address uint16 // Address location where this instruction got read, for debugging purposes
}

func (Instruction) String

func (i Instruction) String() (output string)

Return a string containing debug information about the instruction and operands.

type Memory

type Memory interface {
	Size() uint16
	ReadByte(address uint16) byte
	WriteByte(address uint16, data byte)
}

Anything implementing the Memory interface can be attached to the AddressBus and become accessible by the Cpu.

type OpType

type OpType struct {
	Opcode byte // 65(C)02 Opcode, this includes an instruction and addressing mode

	Size   uint8 // Size of the entire instruction in bytes
	Cycles uint8 // Number of clock cycles required to complete this instruction
	// contains filtered or unexported fields
}

OpType is the operation type, it includes the instruction and addressing mode. It also includes some extra information for the emulator, like number of cycles

type Ram

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

Random Access Memory, read/write, can be of any size.

func NewRam

func NewRam(size int) (*Ram, error)

Create a new Ram component of the given size.

func (*Ram) ReadByte

func (r *Ram) ReadByte(address uint16) byte

func (*Ram) ReadBytes

func (r *Ram) ReadBytes(address uint16, length uint16) []byte

func (*Ram) Size

func (r *Ram) Size() uint16

func (*Ram) WriteByte

func (r *Ram) WriteByte(address uint16, data byte)

func (*Ram) WriteBytes

func (r *Ram) WriteBytes(address uint16, data []byte)

type Rom

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

Read-Only Memory

func NewRom

func NewRom(path string) (*Rom, error)

Create a new Rom component, using the content of `path`. The file automatically specifies the size of Rom.

func (*Rom) ReadByte

func (r *Rom) ReadByte(address uint16) byte

func (*Rom) Size

func (r *Rom) Size() uint16

func (*Rom) WriteByte

func (r *Rom) WriteByte(address uint16, data byte)

Jump to

Keyboard shortcuts

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