Add docs for AddressBus; minor refactorings

This commit is contained in:
Ariejan de Vroom 2014-08-17 13:07:22 +02:00
parent 4eeba4d940
commit 22432de785
3 changed files with 89 additions and 35 deletions

View File

@ -4,6 +4,12 @@ import (
"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 {
addressables []addressable // Different components
}
@ -14,14 +20,16 @@ type addressable struct {
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)
}
// 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 {
output := "Address Bus:\n"
@ -32,10 +40,15 @@ func (a *AddressBus) String() string {
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) {
start := offset
end := offset + memory.Size() - 1
@ -44,16 +57,11 @@ func (a *AddressBus) Attach(memory Memory, offset uint16) {
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)
}
/*
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 (a *AddressBus) Read(address uint16) byte {
addressable, err := a.addressableForAddress(address)
if err != nil {
@ -63,6 +71,11 @@ func (a *AddressBus) Read(address uint16) byte {
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 {
lo := uint16(a.Read(address))
hi := uint16(a.Read(address + 1))
@ -70,6 +83,12 @@ func (a *AddressBus) Read16(address uint16) uint16 {
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) {
addressable, err := a.addressableForAddress(address)
if err != nil {
@ -79,7 +98,23 @@ func (a *AddressBus) Write(address uint16, data byte) {
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) {
a.Write(address, byte(data))
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)
}

View File

@ -13,7 +13,7 @@ func TestEmptyAddressBus(t *testing.T) {
assert.Nil(err)
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)
bus.Attach(ram, 0x0000)
assert.Equal(1, bus.AddressablesCount())
assert.Equal(1, len(bus.addressables))
}
func TestBusReadWrite(t *testing.T) {

53
doc.go
View File

@ -1,19 +1,35 @@
/*
The i6502 package contains all the components needed to construct
a working MOS 6502 emulated computer using different common parts,
like the MOS 6502, WDC 65C02, VIA 6522 and ACIA 6551.
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 8-bit registers and
ALU, and can address 16-bit of memory. It features a 16-bit program
counter (PC) that indicates where from memory the next instruction will
be read.
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.
Besides the Cpu, there is also an AddressBus, which maps the 16-bit
address space to different attached components that implement the Memory
interface. Ram is one such component.
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
Creating a new emulator is easy and straightforward. All that's required
is a Cpu, and AddressBus and attached components.
* 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
@ -34,12 +50,12 @@ Example: create an emulator using the full 64kB address space for RAM
The hardware pins `IRQ` and `RESB` are implemented and mapped to
the functions `Interrupt()` and `Reset()`.
Running a program from RAM is possible by loading it into
memory at the specified address. Note that this also sets the
Program Counter to the beginning of the loaded program.
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.
Keep in mind that 0x00xx is reserved for Zeropage instructions and
0x01xx is reserved for the stack.
Example of loading a binary program from disk into memory:
import "io/ioutil"
@ -49,8 +65,11 @@ Keep in mind that 0x00xx is reserved for Zeropage instructions and
// at 0x0200 and set cpu.PC to 0x0200 as well.
cpu.LoadProgram(program, 0x0200)
Running a program is as easy as calling `cpu.Step()`, which will
read and execute a single instruction.
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()