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:
- Create the different memory components (Ram, Rom, IO)
- Create the AddressBus and attach memory
- 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
- type Acia6551
- type AddressBus
- func (a *AddressBus) Attach(memory Memory, offset uint16)
- func (a *AddressBus) Read16(address uint16) uint16
- func (a *AddressBus) ReadByte(address uint16) byte
- func (a *AddressBus) String() string
- func (a *AddressBus) Write16(address uint16, data uint16)
- func (a *AddressBus) WriteByte(address uint16, data byte)
- type Cpu
- type Instruction
- type Memory
- type OpType
- type Ram
- type Rom
Constants ¶
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 (*Acia6551) Read ¶
Implements io.Reader, for external programs to read TX'ed data from the serial output.
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 ¶
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).
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.