mirror of
https://github.com/ariejan/i6502.git
synced 2024-12-28 21:30:19 +00:00
Merge pull request #5 from ariejan/issue_5_documentation
Update Godoc documentation
This commit is contained in:
commit
b4bbadd369
@ -4,6 +4,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
type AddressBus struct {
|
type AddressBus struct {
|
||||||
addressables []addressable // Different components
|
addressables []addressable // Different components
|
||||||
}
|
}
|
||||||
@ -14,14 +20,16 @@ type addressable struct {
|
|||||||
end uint16 // Last address in address space
|
end uint16 // Last address in address space
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAddressBus() (*AddressBus, error) {
|
|
||||||
return &AddressBus{addressables: make([]addressable, 0)}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *addressable) String() string {
|
func (a *addressable) String() string {
|
||||||
return fmt.Sprintf("\t0x%04X-%04X\n", a.start, a.end)
|
return fmt.Sprintf("\t0x%04X-%04X\n", a.start, a.end)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Creates a new, empty 16-bit AddressBus
|
||||||
|
func NewAddressBus() (*AddressBus, error) {
|
||||||
|
return &AddressBus{addressables: make([]addressable, 0)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a string with details about the AddressBus and attached memory
|
||||||
func (a *AddressBus) String() string {
|
func (a *AddressBus) String() string {
|
||||||
output := "Address Bus:\n"
|
output := "Address Bus:\n"
|
||||||
|
|
||||||
@ -32,10 +40,15 @@ func (a *AddressBus) String() string {
|
|||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AddressBus) AddressablesCount() int {
|
/*
|
||||||
return len(a.addressables)
|
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 (a *AddressBus) Attach(memory Memory, offset uint16) {
|
func (a *AddressBus) Attach(memory Memory, offset uint16) {
|
||||||
start := offset
|
start := offset
|
||||||
end := offset + memory.Size() - 1
|
end := offset + memory.Size() - 1
|
||||||
@ -44,16 +57,11 @@ func (a *AddressBus) Attach(memory Memory, offset uint16) {
|
|||||||
a.addressables = append(a.addressables, addressable)
|
a.addressables = append(a.addressables, addressable)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AddressBus) addressableForAddress(address uint16) (*addressable, error) {
|
/*
|
||||||
for _, addressable := range a.addressables {
|
Read an 8-bit value from Memory attached at the 16-bit address.
|
||||||
if addressable.start <= address && addressable.end >= address {
|
|
||||||
return &addressable, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("No addressable memory found at 0x%04X", address)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
This will panic if you try to read from an address that has no Memory attached.
|
||||||
|
*/
|
||||||
func (a *AddressBus) Read(address uint16) byte {
|
func (a *AddressBus) Read(address uint16) byte {
|
||||||
addressable, err := a.addressableForAddress(address)
|
addressable, err := a.addressableForAddress(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -63,6 +71,11 @@ func (a *AddressBus) Read(address uint16) byte {
|
|||||||
return addressable.memory.Read(address - addressable.start)
|
return addressable.memory.Read(address - addressable.start)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
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 (a *AddressBus) Read16(address uint16) uint16 {
|
func (a *AddressBus) Read16(address uint16) uint16 {
|
||||||
lo := uint16(a.Read(address))
|
lo := uint16(a.Read(address))
|
||||||
hi := uint16(a.Read(address + 1))
|
hi := uint16(a.Read(address + 1))
|
||||||
@ -70,6 +83,12 @@ func (a *AddressBus) Read16(address uint16) uint16 {
|
|||||||
return (hi << 8) | lo
|
return (hi << 8) | lo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
func (a *AddressBus) Write(address uint16, data byte) {
|
func (a *AddressBus) Write(address uint16, data byte) {
|
||||||
addressable, err := a.addressableForAddress(address)
|
addressable, err := a.addressableForAddress(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -79,7 +98,23 @@ func (a *AddressBus) Write(address uint16, data byte) {
|
|||||||
addressable.memory.Write(address-addressable.start, data)
|
addressable.memory.Write(address-addressable.start, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
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 (a *AddressBus) Write16(address uint16, data uint16) {
|
func (a *AddressBus) Write16(address uint16, data uint16) {
|
||||||
a.Write(address, byte(data))
|
a.Write(address, byte(data))
|
||||||
a.Write(address+1, byte(data>>8))
|
a.Write(address+1, byte(data>>8))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the addressable for the specified address, or an error if no addressable exists.
|
||||||
|
func (a *AddressBus) addressableForAddress(address uint16) (*addressable, error) {
|
||||||
|
for _, addressable := range a.addressables {
|
||||||
|
if addressable.start <= address && addressable.end >= address {
|
||||||
|
return &addressable, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("No addressable memory found at 0x%04X", address)
|
||||||
|
}
|
||||||
|
@ -13,7 +13,7 @@ func TestEmptyAddressBus(t *testing.T) {
|
|||||||
assert.Nil(err)
|
assert.Nil(err)
|
||||||
|
|
||||||
if assert.NotNil(bus) {
|
if assert.NotNil(bus) {
|
||||||
assert.Equal(0, bus.AddressablesCount())
|
assert.Equal(0, len(bus.addressables))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ func TestAttachToAddressBus(t *testing.T) {
|
|||||||
ram, _ := NewRam(0x10000)
|
ram, _ := NewRam(0x10000)
|
||||||
|
|
||||||
bus.Attach(ram, 0x0000)
|
bus.Attach(ram, 0x0000)
|
||||||
assert.Equal(1, bus.AddressablesCount())
|
assert.Equal(1, len(bus.addressables))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBusReadWrite(t *testing.T) {
|
func TestBusReadWrite(t *testing.T) {
|
||||||
|
51
cpu.go
51
cpu.go
@ -2,40 +2,58 @@ package i6502
|
|||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
type Cpu struct {
|
type Cpu struct {
|
||||||
|
A byte // Accumulator
|
||||||
|
X byte // Index register X
|
||||||
|
Y byte // Index register Y
|
||||||
|
|
||||||
PC uint16 // 16-bit program counter
|
PC uint16 // 16-bit program counter
|
||||||
P byte // Status Register
|
P byte // Status Register
|
||||||
SP byte // Stack Pointer
|
SP byte // Stack Pointer
|
||||||
|
|
||||||
A byte // Accumulator
|
|
||||||
X byte // X index register
|
|
||||||
Y byte // Y index register
|
|
||||||
|
|
||||||
Bus *AddressBus // The address bus
|
Bus *AddressBus // The address bus
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
ZeropageBase = 0x0000 // 0x0000-00FF Reserved for zeropage instructions
|
||||||
|
StackBase = 0x0100 // 0x0100-01FF Reserved for stack
|
||||||
|
|
||||||
ResetVector = 0xFFFC // 0xFFFC-FFFD
|
ResetVector = 0xFFFC // 0xFFFC-FFFD
|
||||||
IrqVector = 0xFFFE // 0xFFFE-FFFF
|
IrqVector = 0xFFFE // 0xFFFE-FFFF
|
||||||
|
|
||||||
StackBase = 0x0100 // One page 0x0100-01FF
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Create an new Cpu instance with the specified AddressBus
|
// Create an new Cpu, using the AddressBus for accessing memory.
|
||||||
func NewCpu(bus *AddressBus) (*Cpu, error) {
|
func NewCpu(bus *AddressBus) (*Cpu, error) {
|
||||||
return &Cpu{Bus: bus}, nil
|
return &Cpu{Bus: bus}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a string containing the current state of the CPU.
|
||||||
func (c *Cpu) String() string {
|
func (c *Cpu) String() string {
|
||||||
str := ">>> CPU [ A ] [ X ] [ Y ] [ SP ] [ PC ] NVxBDIZC\n>>> 0x%02X 0x%02X 0x%02X 0x%02X 0x%04X %08b\n"
|
str := ">>> CPU [ A ] [ X ] [ Y ] [ SP ] [ PC ] NVxBDIZC\n>>> 0x%02X 0x%02X 0x%02X 0x%02X 0x%04X %08b\n"
|
||||||
return fmt.Sprintf(str, c.A, c.X, c.Y, c.SP, c.PC, c.P)
|
return fmt.Sprintf(str, c.A, c.X, c.Y, c.SP, c.PC, c.P)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cpu) hasAddressBus() bool {
|
/*
|
||||||
return c.Bus != nil
|
Reset the CPU, emulating the RESB pin.
|
||||||
}
|
|
||||||
|
|
||||||
// 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 (c *Cpu) Reset() {
|
func (c *Cpu) Reset() {
|
||||||
c.PC = c.Bus.Read16(ResetVector)
|
c.PC = c.Bus.Read16(ResetVector)
|
||||||
c.P = 0x34
|
c.P = 0x34
|
||||||
@ -47,11 +65,17 @@ func (c *Cpu) Reset() {
|
|||||||
c.SP = 0xFF
|
c.SP = 0xFF
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simulate the IRQ pin
|
/*
|
||||||
|
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 (c *Cpu) Interrupt() {
|
func (c *Cpu) Interrupt() {
|
||||||
c.handleIrq(c.PC)
|
c.handleIrq(c.PC)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handles an interrupt or BRK.
|
||||||
func (c *Cpu) handleIrq(PC uint16) {
|
func (c *Cpu) handleIrq(PC uint16) {
|
||||||
c.stackPush(byte(PC >> 8))
|
c.stackPush(byte(PC >> 8))
|
||||||
c.stackPush(byte(PC))
|
c.stackPush(byte(PC))
|
||||||
@ -63,7 +87,7 @@ func (c *Cpu) handleIrq(PC uint16) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load the specified program data at the given memory location
|
// Load the specified program data at the given memory location
|
||||||
// and point the Program Counter to the beginning of the program
|
// and point the Program Counter to the beginning of the program.
|
||||||
func (c *Cpu) LoadProgram(data []byte, location uint16) {
|
func (c *Cpu) LoadProgram(data []byte, location uint16) {
|
||||||
for i, b := range data {
|
for i, b := range data {
|
||||||
c.Bus.Write(location+uint16(i), b)
|
c.Bus.Write(location+uint16(i), b)
|
||||||
@ -72,13 +96,14 @@ func (c *Cpu) LoadProgram(data []byte, location uint16) {
|
|||||||
c.PC = location
|
c.PC = location
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute the instruction pointed to by the Program Counter (PC)
|
// Read and execute the instruction pointed to by the Program Counter (PC)
|
||||||
func (c *Cpu) Step() {
|
func (c *Cpu) Step() {
|
||||||
instruction := c.readNextInstruction()
|
instruction := c.readNextInstruction()
|
||||||
c.PC += uint16(instruction.Size)
|
c.PC += uint16(instruction.Size)
|
||||||
c.execute(instruction)
|
c.execute(instruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle the execution of an instruction
|
||||||
func (c *Cpu) execute(instruction Instruction) {
|
func (c *Cpu) execute(instruction Instruction) {
|
||||||
switch instruction.opcodeId {
|
switch instruction.opcodeId {
|
||||||
case nop:
|
case nop:
|
||||||
|
@ -76,6 +76,9 @@ func TestCpuReset(t *testing.T) {
|
|||||||
// **1101** is specified, but we are satisfied with
|
// **1101** is specified, but we are satisfied with
|
||||||
// 00110100 here.
|
// 00110100 here.
|
||||||
assert.Equal(0x34, cpu.P)
|
assert.Equal(0x34, cpu.P)
|
||||||
|
assert.True(cpu.getIrqDisable())
|
||||||
|
assert.False(cpu.getDecimal())
|
||||||
|
assert.True(cpu.getBreak())
|
||||||
|
|
||||||
// Read PC from $FFFC-FFFD
|
// Read PC from $FFFC-FFFD
|
||||||
assert.Equal(0x1234, cpu.PC)
|
assert.Equal(0x1234, cpu.PC)
|
||||||
|
79
doc.go
Normal file
79
doc.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
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()
|
||||||
|
}()
|
||||||
|
|
||||||
|
*/
|
||||||
|
package i6502
|
@ -5,19 +5,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Instruction struct {
|
type Instruction struct {
|
||||||
// Embed OpType
|
OpType // Embed OpType
|
||||||
OpType
|
|
||||||
|
|
||||||
// 8-bit operand for 2-byte instructions
|
Op8 byte // 8-bit operand for 2-byte instructions
|
||||||
Op8 byte
|
Op16 uint16 // 16-bit operand for 3-byte instructions
|
||||||
|
|
||||||
// 16-bit operand for 3-byte instructions
|
Address uint16 // Address location where this instruction got read, for debugging purposes
|
||||||
Op16 uint16
|
|
||||||
|
|
||||||
// Address location where this instruction got read
|
|
||||||
Address uint16
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return a string containing debug information about the instruction and operands.
|
||||||
func (i Instruction) String() (output string) {
|
func (i Instruction) String() (output string) {
|
||||||
switch i.Size {
|
switch i.Size {
|
||||||
case 1:
|
case 1:
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
package i6502
|
package i6502
|
||||||
|
|
||||||
|
/*
|
||||||
|
Anything implementing the Memory interface can be attached to the AddressBus
|
||||||
|
and become accessible by the Cpu.
|
||||||
|
*/
|
||||||
type Memory interface {
|
type Memory interface {
|
||||||
Size() uint16
|
Size() uint16
|
||||||
Read(address uint16) byte
|
Read(address uint16) byte
|
||||||
|
17
opcodes.go
17
opcodes.go
@ -160,20 +160,13 @@ var instructionNames = [...]string{
|
|||||||
// addressing mode. It also includes some extra information for the
|
// addressing mode. It also includes some extra information for the
|
||||||
// emulator, like number of cycles
|
// emulator, like number of cycles
|
||||||
type OpType struct {
|
type OpType struct {
|
||||||
// The actual Opcode byte read from memory
|
Opcode byte // 65(C)02 Opcode, this includes an instruction and addressing mode
|
||||||
Opcode byte
|
|
||||||
|
|
||||||
// Opcode ID
|
opcodeId uint8 // Decoded opcode Id,
|
||||||
opcodeId uint8
|
addressingId uint8 // Decoded address mode Id
|
||||||
|
|
||||||
// Addressing Mode ID
|
Size uint8 // Size of the entire instruction in bytes
|
||||||
addressingId uint8
|
Cycles uint8 // Number of clock cycles required to complete this instruction
|
||||||
|
|
||||||
// Size of this instruction, either 1, 2 or 3 bytes
|
|
||||||
Size uint8
|
|
||||||
|
|
||||||
// Number of clock cycles this instruction needs
|
|
||||||
Cycles uint8
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var opTypes = map[uint8]OpType{
|
var opTypes = map[uint8]OpType{
|
||||||
|
4
ram.go
4
ram.go
@ -1,9 +1,13 @@
|
|||||||
package i6502
|
package i6502
|
||||||
|
|
||||||
|
/*
|
||||||
|
Random Access Memory, read/write, can be of any size.
|
||||||
|
*/
|
||||||
type Ram struct {
|
type Ram struct {
|
||||||
data []byte
|
data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a new Ram component of the given size.
|
||||||
func NewRam(size int) (*Ram, error) {
|
func NewRam(size int) (*Ram, error) {
|
||||||
return &Ram{data: make([]byte, size)}, nil
|
return &Ram{data: make([]byte, size)}, nil
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user