mirror of
https://github.com/ariejan/i6502.git
synced 2025-04-12 01:37:05 +00:00
Add docs for AddressBus; minor refactorings
This commit is contained in:
parent
4eeba4d940
commit
22432de785
@ -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)
|
||||
}
|
||||
|
@ -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
53
doc.go
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user