mirror of
https://github.com/zellyn/go6502.git
synced 2025-01-16 12:30:34 +00:00
Most simple instructions done.
This commit is contained in:
parent
90369c5a23
commit
7d26907926
145
cpu/cpu.go
Normal file
145
cpu/cpu.go
Normal file
@ -0,0 +1,145 @@
|
||||
/*
|
||||
6502 implementation.
|
||||
|
||||
TODO(zellyn): Provide configurable options
|
||||
TODO(zellyn): Implement IRQ, NMI
|
||||
*/
|
||||
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
/* Bugs and Quirks.
|
||||
See http://en.wikipedia.org/wiki/MOS_Technology_6502#Bugs_and_quirks
|
||||
*/
|
||||
const (
|
||||
// TODO(zellyn) implement
|
||||
// JMP xxFF reads xx00 instead of (xx+1)00
|
||||
OPTION_BUG_JMP_FF = true
|
||||
OPTION_BUG_INDEXED_ADDR_ACROSS_PAGE_INVALID_READ = true
|
||||
OPTION_BUG_READ_MODIFY_WRITE_TWO_WRITES = true
|
||||
OPTION_BUG_NVZ_INVALID_IN_BCD = true
|
||||
OPTION_BUG_NO_CLD_ON_INTERRUPT = true
|
||||
OPTION_BUG_INTERRUPTS_CLOBBER_BRK = true
|
||||
)
|
||||
|
||||
type Cpu interface {
|
||||
Reset()
|
||||
Step() error
|
||||
SetPC(uint16)
|
||||
A() byte
|
||||
X() byte
|
||||
Y() byte
|
||||
PC() uint16
|
||||
P() byte // [NV-BDIZC]
|
||||
SP() byte
|
||||
// TODO(zellyn): Add signaling of interrupts
|
||||
}
|
||||
|
||||
type Memory interface {
|
||||
Read(uint16) byte
|
||||
Write(uint16, byte)
|
||||
}
|
||||
|
||||
type Ticker interface {
|
||||
Tick()
|
||||
}
|
||||
|
||||
const (
|
||||
STACK_BASE = 0x100
|
||||
IRQ_VECTOR = 0xFFFE
|
||||
NMI_VECTOR = 0xFFFA
|
||||
RESET_VECTOR = 0xFFFC
|
||||
)
|
||||
|
||||
const (
|
||||
FLAG_C = 1 << iota
|
||||
FLAG_Z
|
||||
FLAG_I
|
||||
FLAG_D
|
||||
FLAG_B
|
||||
FLAG_UNUSED
|
||||
FLAG_V
|
||||
FLAG_N
|
||||
)
|
||||
|
||||
type registers struct {
|
||||
A byte
|
||||
X byte
|
||||
Y byte
|
||||
PC uint16
|
||||
P byte // [NV-BDIZC]
|
||||
SP byte
|
||||
}
|
||||
|
||||
type cpu struct {
|
||||
m Memory
|
||||
t Ticker
|
||||
r registers
|
||||
}
|
||||
|
||||
func NewCPU(memory Memory, ticker Ticker) Cpu {
|
||||
c := cpu{m: memory, t: ticker}
|
||||
c.r.P |= FLAG_UNUSED // Set unused flag to 1
|
||||
return &c
|
||||
}
|
||||
|
||||
func (c *cpu) A() byte {
|
||||
return c.r.A
|
||||
}
|
||||
func (c *cpu) X() byte {
|
||||
return c.r.X
|
||||
}
|
||||
func (c *cpu) Y() byte {
|
||||
return c.r.Y
|
||||
}
|
||||
func (c *cpu) PC() uint16 {
|
||||
return c.r.PC
|
||||
}
|
||||
func (c *cpu) P() byte {
|
||||
return c.r.P
|
||||
}
|
||||
func (c *cpu) SP() byte {
|
||||
return c.r.SP
|
||||
}
|
||||
|
||||
func (c *cpu) readWord(address uint16) uint16 {
|
||||
return uint16(c.m.Read(address)) + (uint16(c.m.Read(address+1)) << 8)
|
||||
}
|
||||
|
||||
func (c *cpu) Reset() {
|
||||
c.r.SP = 0
|
||||
c.r.PC = c.readWord(RESET_VECTOR)
|
||||
c.r.P |= FLAG_I // Turn interrupts off
|
||||
if !OPTION_BUG_NO_CLD_ON_INTERRUPT {
|
||||
c.r.P &^= FLAG_D
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cpu) Step() error {
|
||||
i := c.m.Read(c.r.PC)
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
|
||||
if f, ok := opcodes[i]; ok {
|
||||
f(c)
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("Unknown opcode at location $%04X: $%02X", c.r.PC, i)
|
||||
}
|
||||
|
||||
func (c *cpu) SetPC(address uint16) {
|
||||
c.r.PC = address
|
||||
}
|
||||
|
||||
func (c *cpu) SetNZ(value byte) {
|
||||
c.r.P = (c.r.P &^ FLAG_N) | (value & FLAG_N)
|
||||
if value == 0 {
|
||||
c.r.P |= FLAG_Z
|
||||
} else {
|
||||
c.r.P &^= FLAG_Z
|
||||
}
|
||||
}
|
366
cpu/opcodes.go
Normal file
366
cpu/opcodes.go
Normal file
@ -0,0 +1,366 @@
|
||||
package cpu
|
||||
|
||||
import (
|
||||
_ "fmt"
|
||||
)
|
||||
|
||||
func samePage(a1 uint16, a2 uint16) bool {
|
||||
return a1^a2&0xFF00 == 0
|
||||
}
|
||||
|
||||
// Simple, one-off instructions ----------------------------------------------
|
||||
|
||||
func clearFlag(flag byte) func(*cpu) {
|
||||
return func(c *cpu) {
|
||||
c.r.P &^= flag
|
||||
c.t.Tick()
|
||||
}
|
||||
}
|
||||
|
||||
func setFlag(flag byte) func(*cpu) {
|
||||
return func(c *cpu) {
|
||||
c.r.P |= flag
|
||||
c.t.Tick()
|
||||
}
|
||||
}
|
||||
|
||||
func dex(c *cpu) {
|
||||
c.r.X--
|
||||
c.SetNZ(c.r.X)
|
||||
c.t.Tick()
|
||||
}
|
||||
|
||||
func dey(c *cpu) {
|
||||
c.r.Y--
|
||||
c.SetNZ(c.r.Y)
|
||||
c.t.Tick()
|
||||
}
|
||||
|
||||
func inx(c *cpu) {
|
||||
c.r.X++
|
||||
c.SetNZ(c.r.X)
|
||||
c.t.Tick()
|
||||
}
|
||||
|
||||
func iny(c *cpu) {
|
||||
c.r.Y++
|
||||
c.SetNZ(c.r.Y)
|
||||
c.t.Tick()
|
||||
}
|
||||
|
||||
func pha(c *cpu) {
|
||||
c.t.Tick()
|
||||
c.m.Write(0x100+uint16(c.r.SP), c.r.A)
|
||||
c.r.SP--
|
||||
c.t.Tick()
|
||||
}
|
||||
|
||||
func php(c *cpu) {
|
||||
c.t.Tick()
|
||||
c.m.Write(0x100+uint16(c.r.SP), c.r.P)
|
||||
c.r.SP--
|
||||
c.t.Tick()
|
||||
}
|
||||
func pla(c *cpu) {
|
||||
c.t.Tick()
|
||||
c.r.SP++
|
||||
c.t.Tick()
|
||||
c.r.A = c.m.Read(0x100 + uint16(c.r.SP))
|
||||
c.t.Tick()
|
||||
}
|
||||
func plp(c *cpu) {
|
||||
c.t.Tick()
|
||||
c.r.SP++
|
||||
c.t.Tick()
|
||||
c.r.P = c.m.Read(0x100 + uint16(c.r.SP))
|
||||
c.t.Tick()
|
||||
}
|
||||
|
||||
func nop(c *cpu) {
|
||||
c.t.Tick()
|
||||
}
|
||||
|
||||
func tax(c *cpu) {
|
||||
c.r.X = c.r.A
|
||||
c.SetNZ(c.r.X)
|
||||
c.t.Tick()
|
||||
}
|
||||
|
||||
func tay(c *cpu) {
|
||||
c.r.Y = c.r.A
|
||||
c.SetNZ(c.r.Y)
|
||||
c.t.Tick()
|
||||
}
|
||||
|
||||
func tsx(c *cpu) {
|
||||
c.r.X = c.r.SP
|
||||
c.SetNZ(c.r.X)
|
||||
c.t.Tick()
|
||||
}
|
||||
|
||||
func txa(c *cpu) {
|
||||
c.r.A = c.r.X
|
||||
c.SetNZ(c.r.A)
|
||||
c.t.Tick()
|
||||
}
|
||||
|
||||
func txs(c *cpu) {
|
||||
c.r.SP = c.r.X
|
||||
c.SetNZ(c.r.SP)
|
||||
c.t.Tick()
|
||||
}
|
||||
|
||||
func tya(c *cpu) {
|
||||
c.r.A = c.r.Y
|
||||
c.SetNZ(c.r.A)
|
||||
c.t.Tick()
|
||||
}
|
||||
|
||||
func jmpAbsolute(c *cpu) {
|
||||
// T1
|
||||
c.r.PC++
|
||||
addr := uint16(c.m.Read(c.r.PC))
|
||||
c.t.Tick()
|
||||
// T2
|
||||
c.r.PC++
|
||||
addr |= (uint16(c.m.Read(c.r.PC)) << 8)
|
||||
c.r.PC = addr
|
||||
c.t.Tick()
|
||||
}
|
||||
|
||||
func jmpIndirect(c *cpu) {
|
||||
// T1
|
||||
c.r.PC++
|
||||
iAddr := uint16(c.m.Read(c.r.PC))
|
||||
c.t.Tick()
|
||||
// T2
|
||||
c.r.PC++
|
||||
iAddr |= (uint16(c.m.Read(c.r.PC)) << 8)
|
||||
c.t.Tick()
|
||||
// T3
|
||||
addr := uint16(c.m.Read(iAddr))
|
||||
c.t.Tick()
|
||||
// T4
|
||||
if (iAddr&0xff == 0xff) && OPTION_BUG_JMP_FF {
|
||||
addr |= (uint16(c.m.Read(iAddr&0xff00)) << 8)
|
||||
} else {
|
||||
addr |= (uint16(c.m.Read(iAddr+1)) << 8)
|
||||
}
|
||||
c.r.PC = addr
|
||||
c.t.Tick()
|
||||
}
|
||||
|
||||
func jsr(c *cpu) {
|
||||
// T1
|
||||
c.r.PC++
|
||||
addr := uint16(c.m.Read(c.r.PC)) // We actually push PC(next) - 1
|
||||
c.t.Tick()
|
||||
// T2
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
// T3
|
||||
c.m.Write(0x100+uint16(c.r.SP), byte(c.r.PC>>8))
|
||||
c.r.SP--
|
||||
c.t.Tick()
|
||||
// T4
|
||||
c.m.Write(0x100+uint16(c.r.SP), byte(c.r.PC&0xff))
|
||||
c.r.SP--
|
||||
c.t.Tick()
|
||||
// T5
|
||||
addr |= (uint16(c.m.Read(c.r.PC)) << 8)
|
||||
c.r.PC = addr
|
||||
c.t.Tick()
|
||||
}
|
||||
|
||||
func rts(c *cpu) {
|
||||
// T1
|
||||
c.t.Tick()
|
||||
// T2
|
||||
c.r.SP++
|
||||
c.t.Tick()
|
||||
// T3
|
||||
addr := uint16(c.m.Read(0x100 + uint16(c.r.SP)))
|
||||
c.r.SP++
|
||||
c.t.Tick()
|
||||
// T4
|
||||
addr |= (uint16(c.m.Read(0x100+uint16(c.r.SP))) << 8)
|
||||
c.t.Tick()
|
||||
// T5
|
||||
c.r.PC = addr + 1 // Since we pushed PC(next) - 1
|
||||
c.t.Tick()
|
||||
}
|
||||
|
||||
func rti(c *cpu) {
|
||||
// T1
|
||||
c.t.Tick()
|
||||
// T2
|
||||
c.r.SP++
|
||||
c.t.Tick()
|
||||
// T3
|
||||
c.r.P = c.m.Read(0x100 + uint16(c.r.SP))
|
||||
c.r.SP++
|
||||
// T4
|
||||
addr := uint16(c.m.Read(0x100 + uint16(c.r.SP)))
|
||||
c.r.SP++
|
||||
c.t.Tick()
|
||||
// T5
|
||||
addr |= (uint16(c.m.Read(0x100+uint16(c.r.SP))) << 8)
|
||||
c.r.PC = addr
|
||||
c.t.Tick()
|
||||
}
|
||||
|
||||
// Note that BRK skips the next instruction:
|
||||
// http://en.wikipedia.org/wiki/Interrupts_in_65xx_processors#Using_BRK_and_COP
|
||||
func brk(c *cpu) {
|
||||
// T1
|
||||
c.r.PC++
|
||||
c.r.SP--
|
||||
c.t.Tick()
|
||||
// T2
|
||||
c.m.Write(0x100+uint16(c.r.SP), byte(c.r.PC>>8))
|
||||
c.r.SP--
|
||||
c.t.Tick()
|
||||
// T3
|
||||
c.m.Write(0x100+uint16(c.r.SP), byte(c.r.PC&0xff))
|
||||
c.r.SP--
|
||||
c.t.Tick()
|
||||
// T4
|
||||
c.m.Write(0x100+uint16(c.r.SP), c.r.P|FLAG_B) // Set B flag
|
||||
c.r.P |= FLAG_I // Disable interrupts
|
||||
c.t.Tick()
|
||||
// T5
|
||||
addr := uint16(c.m.Read(IRQ_VECTOR))
|
||||
c.t.Tick()
|
||||
// T6
|
||||
addr |= (uint16(c.m.Read(IRQ_VECTOR+1)) << 8)
|
||||
c.r.PC = addr
|
||||
c.t.Tick()
|
||||
}
|
||||
|
||||
func branch(mask, value byte) func(*cpu) {
|
||||
return func(c *cpu) {
|
||||
offset := c.m.Read(c.r.PC)
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
oldPC := c.r.PC
|
||||
if c.r.P&mask == value {
|
||||
c.t.Tick()
|
||||
c.r.PC = c.r.PC + uint16(offset)
|
||||
if offset >= 128 {
|
||||
c.r.PC = c.r.PC - 256
|
||||
}
|
||||
if !samePage(c.r.PC, oldPC) {
|
||||
c.t.Tick()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func immediate(f func(*cpu, byte)) func(*cpu) {
|
||||
return func(c *cpu) {
|
||||
// T1
|
||||
value := c.m.Read(c.r.PC)
|
||||
c.r.PC++
|
||||
f(c, value)
|
||||
c.t.Tick()
|
||||
}
|
||||
}
|
||||
|
||||
func lda(c *cpu, value byte) {
|
||||
c.r.A = value
|
||||
c.SetNZ(value)
|
||||
}
|
||||
|
||||
func ldx(c *cpu, value byte) {
|
||||
c.r.X = value
|
||||
c.SetNZ(value)
|
||||
}
|
||||
|
||||
func ldy(c *cpu, value byte) {
|
||||
c.r.Y = value
|
||||
c.SetNZ(value)
|
||||
}
|
||||
|
||||
func ora(c *cpu, value byte) {
|
||||
c.r.A |= value
|
||||
c.SetNZ(c.r.A)
|
||||
}
|
||||
|
||||
func and(c *cpu, value byte) {
|
||||
c.r.A &= value
|
||||
c.SetNZ(c.r.A)
|
||||
}
|
||||
|
||||
func eor(c *cpu, value byte) {
|
||||
c.r.A ^= value
|
||||
c.SetNZ(c.r.A)
|
||||
}
|
||||
|
||||
func cmp(c *cpu, value byte) {
|
||||
v := c.r.A - value
|
||||
c.SetNZ(v)
|
||||
}
|
||||
|
||||
func cpx(c *cpu, value byte) {
|
||||
v := c.r.X - value
|
||||
c.SetNZ(v)
|
||||
}
|
||||
|
||||
func cpy(c *cpu, value byte) {
|
||||
v := c.r.Y - value
|
||||
c.SetNZ(v)
|
||||
}
|
||||
|
||||
var opcodes = map[byte]func(*cpu){
|
||||
0x18: clearFlag(FLAG_C), // CLC
|
||||
0xD8: clearFlag(FLAG_D), // CLD
|
||||
0x58: clearFlag(FLAG_I), // CLI
|
||||
0xB8: clearFlag(FLAG_V), // CLV
|
||||
0x38: setFlag(FLAG_C), // SEC
|
||||
0xF8: setFlag(FLAG_D), // SED
|
||||
0x78: setFlag(FLAG_I), // SEI
|
||||
0xEA: nop,
|
||||
0xAA: tax,
|
||||
0xA8: tay,
|
||||
0xBA: tsx,
|
||||
0x8A: txa,
|
||||
0x9A: txs,
|
||||
0x98: tya,
|
||||
|
||||
0xCA: dex,
|
||||
0x88: dey,
|
||||
0xE8: inx,
|
||||
0xC8: iny,
|
||||
0x48: pha,
|
||||
0x08: php,
|
||||
0x68: pla,
|
||||
0x28: plp,
|
||||
|
||||
0x4C: jmpAbsolute,
|
||||
0x6C: jmpIndirect,
|
||||
0x20: jsr,
|
||||
0x60: rts,
|
||||
0x40: rti,
|
||||
0x00: brk,
|
||||
|
||||
0x90: branch(FLAG_C, 0), // BCC
|
||||
0xB0: branch(FLAG_C, FLAG_C), // BCS
|
||||
0xF0: branch(FLAG_Z, FLAG_Z), // BEQ
|
||||
0x30: branch(FLAG_N, FLAG_N), // BMI
|
||||
0xD0: branch(FLAG_Z, 0), // BNE
|
||||
0x10: branch(FLAG_N, 0), // BPL
|
||||
0x50: branch(FLAG_V, 0), // BVC
|
||||
0x70: branch(FLAG_V, FLAG_V), // BVS
|
||||
|
||||
0x09: immediate(ora),
|
||||
0x29: immediate(and),
|
||||
0x49: immediate(eor),
|
||||
// 0x69: immediate(adc),
|
||||
0xC0: immediate(cpy),
|
||||
0xC9: immediate(cmp),
|
||||
0xA0: immediate(ldy),
|
||||
0xA2: immediate(ldx),
|
||||
0xA9: immediate(lda),
|
||||
0xE0: immediate(cpx),
|
||||
// 0xE9: immediate(sbc),
|
||||
}
|
123
docs/6502.org
Normal file
123
docs/6502.org
Normal file
@ -0,0 +1,123 @@
|
||||
* References
|
||||
- Detailed per-instruction timing, etc: http://www.masswerk.at/6502/6502_instruction_set.html
|
||||
- aaabbbcc-type breakdown: http://www.llx.com/~nparker/a2/opcodes.html
|
||||
- 6502 Hardware manual (KIM-1): http://users.telenet.be/kim1-6502/6502/hwman.html#AA
|
||||
- Stack explanation (annoying writing): http://homepage.ntlworld.com/cyborgsystems/CS_Main/6502/6502.htm#STACK
|
||||
- For doublechecking cycle counts: http://www.obelisk.demon.co.uk/6502/reference.html
|
||||
|
||||
* Reset sequences
|
||||
- http://www.pagetable.com/?p=410
|
||||
|
||||
** Reset/interrupt vectors
|
||||
|---------+-------------|
|
||||
| Address | Description |
|
||||
|---------+-------------|
|
||||
| $FFFE/F | IRQ |
|
||||
| $FFFA/B | NMI |
|
||||
| $FFFC/D | Reset |
|
||||
|---------+-------------|
|
||||
|
||||
* Misc references
|
||||
- http://axis.llx.com/~nparker/a2/opcodes.html
|
||||
|
||||
* Timing
|
||||
aaabbbcc
|
||||
|
||||
** cc = 01
|
||||
|
||||
| aaa | opcode | flags |
|
||||
|-----+--------+-------|
|
||||
| 000 | ORA | NZ |
|
||||
| 001 | AND | NZ |
|
||||
| 010 | EOR | NZ |
|
||||
| 011 | ADC | NZCV |
|
||||
| 100 | STA | |
|
||||
| 101 | LDA | NZ |
|
||||
| 110 | CMP | NZC |
|
||||
| 111 | SBC | NZCV |
|
||||
|
||||
| bbb | addressing mode |
|
||||
|-----+-----------------|
|
||||
| 000 | (zero page,X) |
|
||||
| 001 | zero page |
|
||||
| 010 | #immediate |
|
||||
| 011 | absolute |
|
||||
| 100 | (zero page),Y |
|
||||
| 101 | zero page,X |
|
||||
| 110 | absolute,Y |
|
||||
| 111 | absolute,X |
|
||||
|
||||
| Addressing Mode | ORA | | AND | | EOR | | ADC | | STA | | LDA | | CMP | | SBC | |
|
||||
|-----------------+-----+------+-----+------+-----+------+-----+------+-----+-----+-----+------+-----+------+-----+------|
|
||||
| (zp,X) | 01 | 2/6 | 21 | 2/6 | 41 | 2/6 | 61 | 2/6 | 81 | 2/6 | A1 | 2/6 | C1 | 2/6 | E1 | 2/6 |
|
||||
| zp | 05 | 2/3 | 25 | 2/3 | 45 | 2/3 | 65 | 2/3 | 85 | 2/3 | A5 | 2/3 | C5 | 2/3 | E5 | 2/3 |
|
||||
| # | 09 | 2/2 | 29 | 2/2 | 49 | 2/2 | 69 | 2/2 | | | A9 | 2/2 | C9 | 2/2 | E9 | 2/2 |
|
||||
| abs | 0D | 3/4 | 2D | 3/4 | 4D | 3/4 | 6D | 3/4 | 8D | 3/4 | AD | 3/4 | CD | 3/4 | ED | 3/4 |
|
||||
| (zp),Y | 11 | 2/5* | 31 | 2/5* | 51 | 2/5* | 71 | 2/5* | 91 | 2/6 | B1 | 2/5* | D1 | 2/5* | F1 | 2/5* |
|
||||
| zp,X | 15 | 2/4 | 35 | 2/4 | 55 | 2/4 | 75 | 2/4 | 95 | 2/4 | B5 | 2/4 | D5 | 2/4 | F5 | 2/4 |
|
||||
| abs,Y | 19 | 3/4* | 39 | 3/4* | 59 | 3/4* | 79 | 3/4* | 99 | 3/5 | B9 | 3/4* | D9 | 3/4* | F9 | 3/4* |
|
||||
| abs,X | 1D | 3/4* | 3D | 3/4* | 5D | 3/4* | 7D | 3/4* | 9D | 3/5 | BD | 3/4* | DD | 3/4* | FD | 3/4* |
|
||||
|
||||
(*) - add 1 to cycles if page boundary is crossed
|
||||
|
||||
** cc = 10
|
||||
|
||||
| aaa | opcode | flags |
|
||||
|-----+--------+-------|
|
||||
| 000 | ASL | NZC |
|
||||
| 001 | ROL | NZC |
|
||||
| 010 | LSR | NZC |
|
||||
| 011 | ROR | NZC |
|
||||
| 100 | STX | |
|
||||
| 101 | LDX | NZ |
|
||||
| 110 | DEC | NZ |
|
||||
| 111 | INC | NZ |
|
||||
|
||||
| bbb | addressing mode | |
|
||||
|-----+-----------------+-----------------------|
|
||||
| 000 | #immediate | |
|
||||
| 001 | zero page | |
|
||||
| 010 | accumulator | |
|
||||
| 011 | absolute | |
|
||||
| 101 | zero page,X | STX,LDX: zero page, Y |
|
||||
| 111 | absolute,X | LDX: absolute,Y |
|
||||
|
||||
| | ASL | | ROL | | LSR | | ROR | | STX | | LDX | | DEC | | INC | |
|
||||
|-------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+------+-----+-----+-----+-----|
|
||||
| # | | | | | | | | | | | A2 | 2/2 | | | | |
|
||||
| zp | 06 | 2/5 | 26 | 2/5 | 46 | 2/5 | 66 | 2/5 | 86 | 2/3 | A6 | 2/3 | C6 | 2/5 | E6 | 2/5 |
|
||||
| A | 0A | 1/2 | 2A | 1/2 | 4A | 1/2 | 6A | 1/2 | | | | | | | | |
|
||||
| abs | 0E | 3/6 | 2E | 3/6 | 4E | 3/6 | 6E | 3/6 | 8E | 3/4 | AE | 3/4 | CE | 3/3 | EE | 3/6 |
|
||||
| zp,X/zp,Y | 16 | 2/6 | 36 | 2/6 | 56 | 2/6 | 76 | 2/6 | 96 | 2/4 | B6 | 2/4 | D6 | 2/6 | F6 | 2/6 |
|
||||
| abs,X/abs,Y | 1E | 3/7 | 3E | 3/7 | 5E | 3/7 | 7E | 3/7 | ?? | | BE | 3/4* | DE | 3/7 | FE | 3/7 |
|
||||
|
||||
(*) - add 1 to cycles if page boundary is crossed
|
||||
|
||||
** cc = 00
|
||||
|
||||
| aaa | opcode | flags |
|
||||
|-----+-----------+-------|
|
||||
| 001 | BIT | NZV |
|
||||
| 010 | JMP | |
|
||||
| 011 | JMP (abs) | |
|
||||
| 100 | STY | |
|
||||
| 101 | LDY | |
|
||||
| 110 | CPY | NZC |
|
||||
| 111 | CPX | NZC |
|
||||
|
||||
|
||||
| bbb | addressing mode |
|
||||
|-----+-----------------|
|
||||
| 000 | #immediate |
|
||||
| 001 | zero page |
|
||||
| 011 | absolute |
|
||||
| 101 | zero page,X |
|
||||
| 111 | absolute,X |
|
||||
|
||||
| | BIT | | JMP | | JMP() | | STY | | LDY | | CPY | | CPX | |
|
||||
|-------+-----+-----+-----+-----+-------+-----+-----+-----+-----+------+-----+-----+-----+-----|
|
||||
| # | | | | | | | | | A0 | 2/2 | C0 | 2/2 | E0 | 2/2 |
|
||||
| zp | 24 | 2/3 | | | | | 84 | 2/3 | A4 | 2/3 | C4 | 2/3 | E4 | 2/3 |
|
||||
| abs | 2C | 3/4 | 4C | 3/3 | 6C | 3/5 | 8C | 3/4 | AC | 3/4 | CC | 3/4 | EC | 3/4 |
|
||||
| zp,X | | | | | | | 94 | 2/4 | B4 | 2/4 | | | | |
|
||||
| abs,X | | | | | | | | | BC | 3/4* | | | | |
|
276
docs/apple2.org
Normal file
276
docs/apple2.org
Normal file
@ -0,0 +1,276 @@
|
||||
Apple II notes
|
||||
|
||||
** Memory Map
|
||||
|
||||
|---------+-------------------------------------------|
|
||||
| Page | Use |
|
||||
|---------+-------------------------------------------|
|
||||
| $00 | Page Zero |
|
||||
| $01 | System stack |
|
||||
| $02 | Input buffer |
|
||||
| $03 | Monitor vector locations |
|
||||
| $04-$07 | Text/Lo-Res Primary |
|
||||
| $08-$0B | Text/Lo-Res Secondary |
|
||||
| $0C-$1F | Free RAM |
|
||||
| $20-$3F | Hires Primary |
|
||||
| $40-$5F | Hires Secondary |
|
||||
| $60-$BF | Free RAM |
|
||||
| $C0 | I/O and softswitches |
|
||||
| $C1-$C7 | ROM for seven peripheral cards |
|
||||
| $C8-$CF | switchable ROM for peripheral cards |
|
||||
| $D0-$D7 | empty ROM socket #1 / Programmer's Aid #1 |
|
||||
| $D8-$DF | empty ROM socket #2 |
|
||||
| $E0-$F7 | Integer BASIC ROM |
|
||||
| $F8-$FF | Monitor ROM |
|
||||
|---------+-------------------------------------------|
|
||||
|
||||
* Integer Basic Map
|
||||
|
||||
|-------------+---------------------------------|
|
||||
| Address | Use |
|
||||
|-------------+---------------------------------|
|
||||
| $E000-$F424 | BASIC |
|
||||
| $F425-$F4FB | Floating point math package 1/2 |
|
||||
| $F4FC-$F4FF | ??? (4 bytes) |
|
||||
| $F500-$F63C | Miniassembler |
|
||||
| $F63D-$F65D | Floating point math package 2/2 |
|
||||
| $F65E-$F665 | ??? (8 bytes) |
|
||||
| $F666-$F668 | Miniassembler entry point |
|
||||
| $F669-$F688 | ??? (32 bytes) |
|
||||
| $F689-$F7FC | SWEET 16 interpreter |
|
||||
| $F800-$FFFF | Monitor |
|
||||
|-------------+---------------------------------|
|
||||
|
||||
* I/O and softswitches
|
||||
|
||||
|---------+--------+------------------------------------------------------------------|
|
||||
| Address | Dec | Use |
|
||||
|---------+--------+------------------------------------------------------------------|
|
||||
| $C00X | -16384 | Read keyboard: High bit = key pressed |
|
||||
| $C01X | -16368 | Set to 0 to clear keyboard strobe |
|
||||
| $C01A | -16358 | Read: High bit 1 = TEXT switch "ON" |
|
||||
| $C01B | -16357 | Read: High bit 1 = MIXED switch "ON" |
|
||||
| $C01C | -16356 | Read: High bit 1 = PAGE2 switch "ON" |
|
||||
| $C01D | -16355 | Read: High bit 1 = HIRES switch "ON" |
|
||||
| $C02X | | Casette output toggle |
|
||||
| $C03X | -16336 | Speaker toggle |
|
||||
| $C04X | | Output strobe to Game I/O connector |
|
||||
| $C050 | -16304 | Set graphics mode |
|
||||
| $C051 | -16303 | Set text mode |
|
||||
| $C052 | -16302 | Set bottom 4 lines graphics |
|
||||
| $C053 | -16301 | Set bottom 4 lines text |
|
||||
| $C054 | -16300 | Display primary page |
|
||||
| $C055 | -16299 | Display secondary page |
|
||||
| $C056 | -16298 | Set HIRES graphics mode |
|
||||
| $C057 | -16297 | Set color graphics mode |
|
||||
| $CØ58 | -l6296 | Clear Game I/O ANØ output |
|
||||
| $CØ59 | -l6295 | Set Game I/O ANØ output |
|
||||
| $CØ5A | -l6294 | Clear Game I/O ANl output |
|
||||
| $CØ5B | -l6293 | Set Game I/O ANl output |
|
||||
| $CØ5C | -l6292 | Clear Game I/O AN2 output |
|
||||
| $CØ5D | -l629l | Set Game I/O AN2 output |
|
||||
| $CØ5E | -l629Ø | Clear Game I/O AN3 output |
|
||||
| $CØ5F | -l6289 | Set Game I/O AN3 output |
|
||||
| $C060/8 | | Tape in (bit 7) |
|
||||
| $C061/9 | -16287 | "SW1": PDL(0) switch: High bit 1 = "on" |
|
||||
| $C062/A | -16286 | "SW2": PDL(1) switch: High bit 1 = "on" |
|
||||
| $C063/B | -16285 | "SW3": PDL(2) switch: High bit 1 = "on" |
|
||||
| $C064/C | | Paddle 0 timer output: state of timer output in high bit |
|
||||
| $C065/D | | Paddle 1 timer output: state of timer output in high bit |
|
||||
| $C066/E | | Paddle 2 timer output: state of timer output in high bit |
|
||||
| $C067/F | | Paddle 3 timer output: state of timer output in high bit |
|
||||
| $C064 | | Paddle0 |
|
||||
| $C07X | | Trigger paddle timers during \Zero_2 |
|
||||
| $C08X | | Device select 0: Pin 41 on Peripheral Connector low during 0_2 |
|
||||
| $C09X | | Device select 1: Pin 41 on Peripheral Connector low during 0_2 |
|
||||
| $C0AX | | Device select 2: Pin 41 on Peripheral Connector low during 0_2 |
|
||||
| $C0BX | | Device select 3: Pin 41 on Peripheral Connector low during 0_2 |
|
||||
| $C0CX | | Device select 4: Pin 41 on Peripheral Connector low during 0_2 |
|
||||
| $C0DX | | Device select 5: Pin 41 on Peripheral Connector low during 0_2 |
|
||||
| $C0EX | | Device select 6: Pin 41 on Peripheral Connector low during 0_2 |
|
||||
| $C0FX | | Device select 7: Pin 41 on Peripheral Connector low during 0_2 |
|
||||
| $C10X | | Device select (8): Pin 41 on Peripheral Connector low during 0_2 |
|
||||
| $C11X | | Device select (9): Pin 41 on Peripheral Connector low during 0_2 |
|
||||
| $C12X | | Device select (A): Pin 41 on Peripheral Connector low during 0_2 |
|
||||
| $C13X | | Device select (B): Pin 41 on Peripheral Connector low during 0_2 |
|
||||
| $C14X | | Device select (C): Pin 41 on Peripheral Connector low during 0_2 |
|
||||
| $C15X | | Device select (D): Pin 41 on Peripheral Connector low during 0_2 |
|
||||
| $C16X | | Device select (E): Pin 41 on Peripheral Connector low during 0_2 |
|
||||
| $C17X | | Device select (F): Pin 41 on Peripheral Connector low during 0_2 |
|
||||
| $C1XX | | I/O Select 1: Pin 1 on Peripheral Connector low during 0_2 |
|
||||
| $C2XX | | I/O Select 2: Pin 1 on Peripheral Connector low during 0_2 |
|
||||
| $C3XX | | I/O Select 3: Pin 1 on Peripheral Connector low during 0_2 |
|
||||
| $C4XX | | I/O Select 4: Pin 1 on Peripheral Connector low during 0_2 |
|
||||
| $C5XX | | I/O Select 5: Pin 1 on Peripheral Connector low during 0_2 |
|
||||
| $C6XX | | I/O Select 6: Pin 1 on Peripheral Connector low during 0_2 |
|
||||
| $C7XX | | I/O Select 7: Pin 1 on Peripheral Connector low during 0_2 |
|
||||
| $C8XX | | I/O Select (8): Pin 1 on Peripheral Connector low during 0_2 |
|
||||
| $C9XX | | I/O Select (9): Pin 1 on Peripheral Connector low during 0_2 |
|
||||
| $CAXX | | I/O Select (A): Pin 1 on Peripheral Connector low during 0_2 |
|
||||
| $CBXX | | I/O Select (B): Pin 1 on Peripheral Connector low during 0_2 |
|
||||
| $CCXX | | I/O Select (C): Pin 1 on Peripheral Connector low during 0_2 |
|
||||
| $CDXX | | I/O Select (D): Pin 1 on Peripheral Connector low during 0_2 |
|
||||
| $CEXX | | I/O Select (E): Pin 1 on Peripheral Connector low during 0_2 |
|
||||
| $CFXX | | I/O Select (F): Pin 1 on Peripheral Connector low during 0_2 |
|
||||
| $CFFF | | switchable peripheral ROM - find out more |
|
||||
| | | |
|
||||
| | | |
|
||||
|
||||
* Page 00
|
||||
|
||||
|---------+---------+----------------------------------------------------------------|
|
||||
| Address | Dec | Description |
|
||||
|---------+---------+----------------------------------------------------------------|
|
||||
| $00-$1F | | Register area for "sweet 16" |
|
||||
| $18 | | (DOS) first track of data |
|
||||
| $19 | | (DOS) first sector of data |
|
||||
| $1A | | (DOS) number of sectors to load |
|
||||
| $1B | | (DOS) HIGH BYTE of buffer (LO is always 0) |
|
||||
| $1A/B | | Shape pointer used by DRAW and XDRAW |
|
||||
| $1C | | Last color used (HCOLOR converted to its color byte) |
|
||||
| $20 | 32 | Left edge of window (0-39) |
|
||||
| $21 | 33 | Width of the window (0 to 40 - Left) |
|
||||
| $22 | 34 | Top edge of window (0-23) |
|
||||
| $23 | 35 | Bottom of window (0-23) Bottom > Top |
|
||||
| $24 | 36 | HTAB: 0-39 |
|
||||
| $25 | 37 | VTAB: 0-23 |
|
||||
| $2B | 43 | Boot SLOT * 16 |
|
||||
| $2C | 44 | Lo-res line end-point |
|
||||
| $30 | 48 | Lores color * 17 |
|
||||
| $32 | 50 | Inverse($3F), Flash($7F), Normal($FF), Invisible($80) |
|
||||
| $33 | | Prompt-char |
|
||||
| $4A/B | 74-75 | LOMEM address (INT) |
|
||||
| $4C/D | 76-77 | HIMEM address (INT) |
|
||||
| $4E-$4F | 78-79 | 16-bit number, randomized with each key entry |
|
||||
| | 214 | Poke 255 to auto-run program |
|
||||
| | 212 | Error code flag in decimal |
|
||||
| | 216 | High bit set if error detected: zero to clear |
|
||||
| | 222 | Error code |
|
||||
| | 224-226 | HIRES GR X&Y coordinates |
|
||||
| $E4 | 228 | Color being used {0=0:42=1:85=2:127=3:128=4:170=5:213=6:255=7} |
|
||||
| $E6 | 230 | HIRES PLOTTING PAGE (32=1/64=2/96=3) |
|
||||
| $E7 | 231 | SCALE of shape |
|
||||
| $EA | 234 | COLLISION COUNTER for shapes |
|
||||
| | 241 | 256-SPEED |
|
||||
| $F3 | 243 | FLASH MASK |
|
||||
| $F9 | 249 | ROT |
|
||||
|
||||
* Page 03
|
||||
|
||||
|-------------+--------------------------------------|
|
||||
| Address | Description |
|
||||
|-------------+--------------------------------------|
|
||||
| $0320/1 | Low-endian HIRES X coordinate: 0-279 |
|
||||
| $0322 | HIRES Y coordinate: 0-159 |
|
||||
| $0324/5 | Start address of SHAPE TABLE |
|
||||
| $032C | COLOR for HIRES |
|
||||
| $03F8 | CTRL-Y in Monitor with JSR here |
|
||||
| $03FB | NMI |
|
||||
| $03FE-$03FF | IRQ sent to this address |
|
||||
| | |
|
||||
|
||||
* Basic locations
|
||||
|
||||
|---------+------+--------------------------------|
|
||||
| Address | Dec | Description |
|
||||
|---------+------+--------------------------------|
|
||||
| | 2049 | Set to 1 for "list protection" |
|
||||
|
||||
* DOS routines
|
||||
|---------+-------+-------------|
|
||||
| Address | Dec | Description |
|
||||
|---------+-------+-------------|
|
||||
| | 42350 | Catalog |
|
||||
|
||||
* Monitor routines
|
||||
|---------+-------+-------+------------------------------------------------------------|
|
||||
| Address | Dec | Dec | Description |
|
||||
|---------+-------+-------+------------------------------------------------------------|
|
||||
| | $F3D4 | -3116 | HGR2 |
|
||||
| | $F3DE | -3106 | HGR |
|
||||
| | $F3F2 | -3086 | CLEAR HIRES SCREEN |
|
||||
| | $F3F6 | -3082 | CLEAR HIRES SCREEN to HCOLOR |
|
||||
| | $FB2F | -1233 | TEXT |
|
||||
| | $FB40 | -1216 | GR |
|
||||
| | $FB60 | -1184 | PRINT "APPLE ][" |
|
||||
| | $FBE4 | -1052 | RING BELL |
|
||||
| | $FBF4 | -1036 | MOVE CURSOR RIGHT |
|
||||
| | $FC10 | -1008 | MOVE CURSOR LEFT |
|
||||
| | $FC1A | -998 | MOVE CURSOR UP 1 LINE |
|
||||
| | $FC58 | -936 | HOME |
|
||||
| | $FD0C | -756 | WAIT FOR KEYPRESS |
|
||||
| | $FD67 | -665 | GET a LINE of input with PROMPT, LINE FEED, and wait. |
|
||||
| | $FD6A | -662 | GET a LINE of input with PROMPT, NO LINE FEED, and wait. |
|
||||
| | $FD6F | -657 | GET a LINE of input with NO PROMPT or LINE FEED, and wait. |
|
||||
| | $FE80 | -384 | INVERSE |
|
||||
| | $FE84 | -380 | NORMAL |
|
||||
| | $FECA | -310 | WRITE to tape |
|
||||
| | $FEFD | -259 | READ from tape |
|
||||
| | | | |
|
||||
|
||||
|
||||
|
||||
** References
|
||||
- http://apple2history.org/history/ah03/
|
||||
- http://www.textfiles.com/apple/peekpk.txt
|
||||
- http://www.textfiles.com/apple/peekpoke.app
|
||||
- http://www.textfiles.com/apple/peeks.pokes.2
|
||||
- http://www.applefritter.com/node/24236
|
||||
|
||||
* Language card
|
||||
Install in slot 0.
|
||||
Replaces RAM chip in E3 with a cable to the card.
|
||||
Contains "autostart ROM"
|
||||
Adds Esc-ijkm movement
|
||||
Ctrl-S stop/start program listings
|
||||
|
||||
** Autostart ROM details
|
||||
$3F0/1 Break vector: $59,$FA
|
||||
$3F2/3 Reset vector: $03, $E0 (for non-disk systems after power-up reset)
|
||||
$3F4 Powered up mask: $45
|
||||
|
||||
** Memory details
|
||||
|-------------+-------------------------|
|
||||
| Address | Description |
|
||||
|-------------+-------------------------|
|
||||
| $D000-$DFFF | 4K bank-switched memory |
|
||||
| $E000-$F7FF | RAM |
|
||||
| $F800-$FFFF | Language card ROM |
|
||||
|-------------+-------------------------|
|
||||
|
||||
* Control codes
|
||||
Bit 2 is ignored.
|
||||
Bit 3 chooses which RAM banks is switched in to $D000-$DFFF.
|
||||
When RAM is deselected (and write-enabled), it can be written but not read.
|
||||
When RAM is deselected, Language Card ROM is mapped to $F800-$FFFF.
|
||||
Power-on RESET initializes ROM to read mode, and RAM to write mode,
|
||||
and selects the second 4K bank for $D000-$DFFF.
|
||||
|
||||
|----------------+---------------+---------------------------------------------------|
|
||||
| Second 4K Bank | First 4K Bank | Description |
|
||||
|----------------+---------------+---------------------------------------------------|
|
||||
| $C080 | $C088 | Select RAM read. Write-protect RAM |
|
||||
| $C081 | $C089 | Deselect RAM reaad (enable ROM) |
|
||||
| | | Two or more successive reads = write-enable RAM |
|
||||
| $C082 | $C08A | Deselect RAM read (enable ROM). Write-protect RAM |
|
||||
| $C083 | $C08B | Select RAM read. |
|
||||
| | | Two or more successive reads = write-enable RAM |
|
||||
|----------------+---------------+---------------------------------------------------|
|
||||
|
||||
* Revisions
|
||||
** II
|
||||
- Integer Basic
|
||||
- Revision 0
|
||||
- Only 4 hires colors: black, white, violet, green
|
||||
- Revision 1
|
||||
- 6 hires colors
|
||||
- Color killer
|
||||
- power-on-reset
|
||||
- auto-start
|
||||
- 24k memory map problem (?!)
|
||||
- keyboard strobe flip flop connected to reset
|
||||
** II+
|
||||
- Applesoft
|
||||
- Usually with 16k "Language Card"
|
||||
** IIe
|
||||
- 80-column card
|
5307
harness/6502_functional_test.a65
Normal file
5307
harness/6502_functional_test.a65
Normal file
File diff suppressed because it is too large
Load Diff
BIN
harness/6502_functional_test.bin
Normal file
BIN
harness/6502_functional_test.bin
Normal file
Binary file not shown.
52
harness/harness.go
Normal file
52
harness/harness.go
Normal file
@ -0,0 +1,52 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/zellyn/go6502/cpu"
|
||||
)
|
||||
|
||||
type K64 [65536]byte
|
||||
|
||||
func (m *K64) Read(address uint16) byte {
|
||||
return m[address]
|
||||
}
|
||||
|
||||
func (m *K64) Write(address uint16, value byte) {
|
||||
m[address] = value
|
||||
}
|
||||
|
||||
type CycleCount uint64
|
||||
|
||||
func (c *CycleCount) Tick() {
|
||||
*c += 1
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println("Hello, world.")
|
||||
bytes, err := ioutil.ReadFile("6502_functional_test.bin")
|
||||
if err != nil {
|
||||
panic("Cannot read file")
|
||||
}
|
||||
var m K64
|
||||
var cc CycleCount
|
||||
OFFSET := 0xa
|
||||
copy(m[OFFSET:len(bytes)+OFFSET], bytes)
|
||||
c := cpu.NewCPU(&m, &cc)
|
||||
c.Reset()
|
||||
c.SetPC(0x1000)
|
||||
for {
|
||||
oldPC := c.PC()
|
||||
err := c.Step()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
break
|
||||
}
|
||||
if c.PC() == oldPC {
|
||||
fmt.Printf("Stuck at 0x%X: 0x%X\n", oldPC, m[oldPC])
|
||||
break
|
||||
}
|
||||
}
|
||||
fmt.Println("Goodbye, world.")
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user