diff --git a/address_bus.go b/address_bus.go new file mode 100644 index 0000000..05d28b8 --- /dev/null +++ b/address_bus.go @@ -0,0 +1,85 @@ +package i6502 + +import ( + "fmt" +) + +type AddressBus struct { + addressables []addressable // Different components +} + +type addressable struct { + memory Memory // Actual memory + start uint16 // First 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 { + return fmt.Sprintf("\t0x%04X-%04X\n", a.start, a.end) +} + +func (a *AddressBus) String() string { + output := "Address Bus:\n" + + for _, addressable := range a.addressables { + output += addressable.String() + } + + return output +} + +func (a *AddressBus) AddressablesCount() int { + return len(a.addressables) +} + +func (a *AddressBus) Attach(memory Memory, offset uint16) { + start := offset + end := offset + memory.Size() - 1 + addressable := addressable{memory: memory, start: start, end: end} + + a.addressables = append(a.addressables, addressable) +} + +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) +} + +func (a *AddressBus) Read(address uint16) byte { + addressable, err := a.addressableForAddress(address) + if err != nil { + panic(err) + } + + return addressable.memory.Read(address - addressable.start) +} + +func (a *AddressBus) Read16(address uint16) uint16 { + lo := uint16(a.Read(address)) + hi := uint16(a.Read(address + 1)) + + return (hi << 8) | lo +} + +func (a *AddressBus) Write(address uint16, data byte) { + addressable, err := a.addressableForAddress(address) + if err != nil { + panic(err) + } + + addressable.memory.Write(address-addressable.start, data) +} + +func (a *AddressBus) Write16(address uint16, data uint16) { + a.Write(address, byte(data)) + a.Write(address+1, byte(data>>8)) +} diff --git a/address_bus_test.go b/address_bus_test.go new file mode 100644 index 0000000..88b9edb --- /dev/null +++ b/address_bus_test.go @@ -0,0 +1,65 @@ +package i6502 + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestEmptyAddressBus(t *testing.T) { + assert := assert.New(t) + + bus, err := NewAddressBus() + + assert.Nil(err) + + if assert.NotNil(bus) { + assert.Equal(0, bus.AddressablesCount()) + } +} + +func TestAttachToAddressBus(t *testing.T) { + assert := assert.New(t) + + bus, _ := NewAddressBus() + ram, _ := NewRam(0x10000) + + bus.Attach(ram, 0x0000) + assert.Equal(1, bus.AddressablesCount()) +} + +func TestBusReadWrite(t *testing.T) { + assert := assert.New(t) + + bus, _ := NewAddressBus() + ram, _ := NewRam(0x8000) + ram2, _ := NewRam(0x8000) + bus.Attach(ram, 0x0000) + bus.Attach(ram2, 0x8000) + + // 8-bit Writing + bus.Write(0x1234, 0xFA) + assert.Equal(0xFA, ram.Read(0x1234)) + + // 16-bit Writing + bus.Write16(0x1000, 0xAB42) + assert.Equal(0x42, ram.Read(0x1000)) + assert.Equal(0xAB, ram.Read(0x1001)) + + // 8-bit Reading + ram.Write(0x5522, 0xDA) + assert.Equal(0xDA, bus.Read(0x5522)) + + // 16-bit Reading + ram.Write(0x4440, 0x7F) + ram.Write(0x4441, 0x56) + assert.Equal(0x567F, bus.Read16(0x4440)) + + //// Test addressing memory not mounted at 0x0000 + + // Read from relative addressable Ram2: $C123 + ram2.Write(0x4123, 0xEF) + assert.Equal(0xEF, bus.Read(0xC123)) + + bus.Write(0x8001, 0x12) + assert.Equal(0x12, ram2.Read(0x0001)) +} diff --git a/cpu.go b/cpu.go index 92157f8..c1a1166 100644 --- a/cpu.go +++ b/cpu.go @@ -1,8 +1,25 @@ package i6502 type Cpu struct { + bus *AddressBus // The address bus + + PC uint16 // 16-bit program counter + P byte // Status Register } -func NewCpu() (*Cpu, error) { - return &Cpu{}, nil +const ( + ResetVector = 0xFFFC +) + +func NewCpu(bus *AddressBus) (*Cpu, error) { + return &Cpu{bus: bus}, nil +} + +func (c *Cpu) HasAddressBus() bool { + return c.bus != nil +} + +func (c *Cpu) Reset() { + c.PC = c.bus.Read16(ResetVector) + c.P = 0x34 } diff --git a/cpu_test.go b/cpu_test.go index 5227209..0b690bb 100644 --- a/cpu_test.go +++ b/cpu_test.go @@ -5,9 +5,44 @@ import ( "testing" ) +// Creates a new machine, returning the different parts +func NewRamMachine() (*Cpu, *AddressBus, *Ram) { + ram, _ := NewRam(0x100000) // Full 64kB + bus, _ := NewAddressBus() + bus.Attach(ram, 0x0000) + cpu, _ := NewCpu(bus) + + return cpu, bus, ram +} + func TestNewCpu(t *testing.T) { - cpu, err := NewCpu() + cpu, err := NewCpu(nil) assert.NotNil(t, cpu) assert.Nil(t, err) } + +func TestCpuAddressBus(t *testing.T) { + assert := assert.New(t) + + cpu, _, _ := NewRamMachine() + assert.True(cpu.HasAddressBus()) +} + +func TestCpuState(t *testing.T) { + assert := assert.New(t) + + cpu, _, _ := NewRamMachine() + + cpu.bus.Write(0xFFFC, 0x34) + cpu.bus.Write(0xFFFD, 0x12) + + cpu.Reset() + + // **1101** is specified, but we are satisfied with + // 00110100 here. + assert.Equal(0x34, cpu.P) + + // Read PC from $FFFC-FFFD + assert.Equal(0x1234, cpu.PC) +} diff --git a/memory.go b/memory.go new file mode 100644 index 0000000..84bd5ee --- /dev/null +++ b/memory.go @@ -0,0 +1,7 @@ +package i6502 + +type Memory interface { + Size() uint16 + Read(address uint16) byte + Write(address uint16, data byte) +} diff --git a/ram.go b/ram.go new file mode 100644 index 0000000..8b3ea29 --- /dev/null +++ b/ram.go @@ -0,0 +1,21 @@ +package i6502 + +type Ram struct { + data []byte +} + +func NewRam(size int) (*Ram, error) { + return &Ram{data: make([]byte, size)}, nil +} + +func (r *Ram) Size() uint16 { + return uint16(len(r.data)) +} + +func (r *Ram) Read(address uint16) byte { + return r.data[address] +} + +func (r *Ram) Write(address uint16, data byte) { + r.data[address] = data +} diff --git a/ram_test.go b/ram_test.go new file mode 100644 index 0000000..6890621 --- /dev/null +++ b/ram_test.go @@ -0,0 +1,26 @@ +package i6502 + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestRamSize(t *testing.T) { + assert := assert.New(t) + + ram, _ := NewRam(0x8000) // 32 kB + assert.Equal(0x8000, ram.Size()) +} + +func TestRamReadWrite(t *testing.T) { + assert := assert.New(t) + ram, _ := NewRam(0x8000) // 32 kB + + // Ram zeroed out initially + for i := 0; i < 0x8000; i++ { + assert.Equal(0x00, ram.data[i]) + } + + ram.Write(0x1000, 0x42) + assert.Equal(0x42, ram.Read(0x1000)) +}