Initial commit of the i6502 emulator.

This commit is contained in:
Ariejan de Vroom 2014-08-07 09:15:41 +02:00
commit 4a155cad33
No known key found for this signature in database
GPG Key ID: AD739154F713697B
11 changed files with 1295 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
# RAM memory dumps
core
# Binary
i6502

100
bus/bus.go Normal file
View File

@ -0,0 +1,100 @@
// Package bus represents the 16-bit address and 8-bit data bus for the
// 6502. Different memory modules can be used for different addresses.
package bus
import (
"fmt"
"github.com/ariejan/i6502/memory"
)
type busModule struct {
memory memory.Memory
name string
start uint16
end uint16
}
// A 16-bit address and 8-bit data bus. It maps access to different
// attached modules, like Ram or Rom.
type Bus struct {
modules []busModule
}
func (module busModule) String() string {
return fmt.Sprintf("%s\t0x%04X-%04X\n", module.name, module.start, module.end)
}
func (bus *Bus) String() string {
output := "\n\nAddress bus modules:\n\n"
for _, module := range bus.modules {
// output = append(output, module.String())
output += module.String()
}
return output
}
// Creates a new Bus, no modules are attached by default.
func CreateBus() (*Bus, error) {
return &Bus{modules: make([]busModule, 0)}, nil
}
func (bus *Bus) Attach(memory memory.Memory, name string, offset uint16) error {
offsetMemory := OffsetMemory{Offset: offset, Memory: memory}
end := offset + uint16(memory.Size()-1)
module := busModule{memory: offsetMemory, name: name, start: offset, end: end}
bus.modules = append(bus.modules, module)
return nil
}
func (bus *Bus) backendFor(address uint16) (memory.Memory, error) {
for _, module := range bus.modules {
if address >= module.start && address <= module.end {
return module.memory, nil
}
}
return nil, fmt.Errorf("No module addressable at 0x%04X", address)
}
// Read an 8-bit value from the module mapped on the bus at the
// given 16-bit address.
func (bus *Bus) Read(address uint16) byte {
memory, err := bus.backendFor(address)
if err != nil {
panic(err)
}
value := memory.Read(address)
return value
}
// Write an 8-bit value to the module mapped on the bus through the
// 16-bit address.
func (bus *Bus) Write(address uint16, value byte) {
memory, err := bus.backendFor(address)
if err != nil {
panic(err)
}
memory.Write(address, value)
}
// Helper method to read a 16-bit value from
// Note: LSB goes first: address [lo] + address+1 [hi]
func (bus *Bus) Read16(address uint16) uint16 {
lo := uint16(bus.Read(address))
hi := uint16(bus.Read(address + 1))
return hi<<8 | lo
}
// Helper to write a 16-bit value to address and address + 1
// Note: LSB goes first: address [lo] + address+1 [hi]
func (bus *Bus) Write16(address uint16, value uint16) {
bus.Write(address, byte(value))
bus.Write(address+1, byte(value>>8))
}

20
bus/offset_memory.go Normal file
View File

@ -0,0 +1,20 @@
package bus
import (
"github.com/ariejan/i6502/memory"
)
// The AddressDecoder routes a full 16-bit address to the
// appropriate relatie Memory component address.
type OffsetMemory struct {
Offset uint16
memory.Memory
}
func (offsetMemory OffsetMemory) Read(address uint16) byte {
return offsetMemory.Memory.Read(address - offsetMemory.Offset)
}
func (offsetMemory OffsetMemory) Write(address uint16, value byte) {
offsetMemory.Memory.Write(address-offsetMemory.Offset, value)
}

634
cpu/cpu.go Normal file
View File

@ -0,0 +1,634 @@
package cpu
import (
"fmt"
"github.com/ariejan/i6502/bus"
"strings"
)
// Status register flags
const (
sCarry = iota
sZero
sInterrupt
sDecimal
sBreak
_
sOverflow
sNegative
)
// Beginning of the stack.
// The stack grows downward, so it starts at 0x1FF
const StackBase = 0x0100
type Cpu struct {
// Program counter
PC uint16
// Accumulator
A byte
// X, Y general purpose/index registers
X byte
Y byte
// Stack pointer
SP byte
// Status registers
SR byte
// Memory bus
Bus *bus.Bus
// Handle exiting
ExitChan chan int
}
// Reset the CPU, identical to triggering the RESB pin.
// This resets the status register and loads the _reset vector_ from
// address 0xFFFC into the Program Counter. Note this is a 16 bit value, read from
// 0xFFFC-FFFD
func (c *Cpu) Reset() {
c.PC = c.Bus.Read16(0xFFFC)
c.SR = 0x34
}
func (c *Cpu) String() string {
return fmt.Sprintf(
"CPU PC:0x%04X A:0x%02X X:0x%02X Y:0x%02X SP:0x%02X SR:%s",
c.PC, c.A, c.X, c.Y, c.SP,
c.statusString(),
)
}
func (c *Cpu) Step() {
// Read the instruction (including operands)
instruction := ReadInstruction(c.PC, c.Bus)
// Move the Program Counter forward, depending
// on the size of the optype we just read.
c.PC += uint16(instruction.Bytes)
// Execute the instruction
c.execute(instruction)
}
func (c *Cpu) stackHead(offset int8) uint16 {
return uint16(StackBase) + uint16(c.SP) + uint16(offset)
}
func (c *Cpu) resolveOperand(in Instruction) uint8 {
switch in.addressing {
case immediate:
return in.Op8
default:
return c.Bus.Read(c.memoryAddress(in))
}
}
func (c *Cpu) memoryAddress(in Instruction) uint16 {
switch in.addressing {
case absolute:
return in.Op16
case absoluteX:
return in.Op16 + uint16(c.X)
case absoluteY:
return in.Op16 + uint16(c.Y)
// Indexed Indirect (X)
// Operand is the zero-page location of a little-endian 16-bit base address.
// The X register is added (wrapping; discarding overflow) before loading.
// The resulting address loaded from (base+X) becomes the effective operand.
// (base + X) must be in zero-page.
case indirectX:
location := uint16(in.Op8 + c.X)
if location == 0xFF {
panic("Indexed indirect high-byte not on zero page.")
}
return c.Bus.Read16(location)
// Indirect Indexed (Y)
// Operand is the zero-page location of a little-endian 16-bit address.
// The address is loaded, and then the Y register is added to it.
// The resulting loaded_address + Y becomes the effective operand.
case indirectY:
return c.Bus.Read16(uint16(in.Op8)) + uint16(c.Y)
case zeropage:
return uint16(in.Op8)
case zeropageX:
return uint16(in.Op8 + c.X)
case zeropageY:
return uint16(in.Op8 + c.Y)
default:
panic("unhandled addressing")
}
}
func (c *Cpu) getStatus(bit uint8) bool {
return c.getStatusInt(bit) == 1
}
func (c *Cpu) getStatusInt(bit uint8) uint8 {
return (c.SR >> bit) & 1
}
func (c *Cpu) setStatus(bit uint8, state bool) {
if state {
c.SR |= 1 << bit
} else {
c.SR &^= 1 << bit
}
}
func (c *Cpu) updateStatus(value uint8) {
c.setStatus(sZero, value == 0)
c.setStatus(sNegative, (value>>7) == 1)
}
func (c *Cpu) statusString() string {
chars := "nv_bdizc"
out := make([]string, 8)
for i := 0; i < 8; i++ {
if c.getStatus(uint8(7 - i)) {
out[i] = string(chars[i])
} else {
out[i] = "-"
}
}
return strings.Join(out, "")
}
func (c *Cpu) branch(in Instruction) {
relative := int8(in.Op8) // signed
if relative >= 0 {
c.PC += uint16(relative)
} else {
c.PC -= uint16(-relative)
}
}
func (c *Cpu) execute(in Instruction) {
switch in.id {
case adc:
c.ADC(in)
case and:
c.AND(in)
case asl:
c.ASL(in)
case bcc:
c.BCC(in)
case bcs:
c.BCS(in)
case beq:
c.BEQ(in)
case bmi:
c.BMI(in)
case bne:
c.BNE(in)
case bpl:
c.BPL(in)
case brk:
c.BRK(in)
case clc:
c.CLC(in)
case cld:
c.CLD(in)
case cli:
c.CLI(in)
case cmp:
c.CMP(in)
case cpx:
c.CPX(in)
case cpy:
c.CPY(in)
case dec:
c.DEC(in)
case dex:
c.DEX(in)
case dey:
c.DEY(in)
case eor:
c.EOR(in)
case inc:
c.INC(in)
case inx:
c.INX(in)
case iny:
c.INY(in)
case jmp:
c.JMP(in)
case jsr:
c.JSR(in)
case lda:
c.LDA(in)
case ldx:
c.LDX(in)
case ldy:
c.LDY(in)
case lsr:
c.LSR(in)
case nop:
c.NOP(in)
case ora:
c.ORA(in)
case pha:
c.PHA(in)
case pla:
c.PLA(in)
case rol:
c.ROL(in)
case ror:
c.ROR(in)
case rts:
c.RTS(in)
case sbc:
c.SBC(in)
case sec:
c.SEC(in)
case sei:
c.SEI(in)
case sta:
c.STA(in)
case stx:
c.STX(in)
case sty:
c.STY(in)
case tax:
c.TAX(in)
case tay:
c.TAY(in)
case tsx:
c.TSX(in)
case txa:
c.TXA(in)
case txs:
c.TXS(in)
case tya:
c.TYA(in)
case _end:
c._END(in)
default:
panic(fmt.Sprintf("Unhandled instruction: %v", in))
}
}
// ADC: Add memory and carry to accumulator.
func (c *Cpu) ADC(in Instruction) {
value16 := uint16(c.A) + uint16(c.resolveOperand(in)) + uint16(c.getStatusInt(sCarry))
c.setStatus(sCarry, value16 > 0xFF)
c.A = uint8(value16)
c.updateStatus(c.A)
}
// AND: And accumulator with memory.
func (c *Cpu) AND(in Instruction) {
c.A &= c.resolveOperand(in)
c.updateStatus(c.A)
}
// ASL: Shift memory or accumulator left one bit.
func (c *Cpu) ASL(in Instruction) {
switch in.addressing {
case accumulator:
c.setStatus(sCarry, (c.A>>7) == 1) // carry = old bit 7
c.A <<= 1
c.updateStatus(c.A)
default:
address := c.memoryAddress(in)
value := c.Bus.Read(address)
c.setStatus(sCarry, (value>>7) == 1) // carry = old bit 7
value <<= 1
c.Bus.Write(address, value)
c.updateStatus(value)
}
}
// BCC: Branch if carry clear.
func (c *Cpu) BCC(in Instruction) {
if !c.getStatus(sCarry) {
c.branch(in)
}
}
// BCS: Branch if carry set.
func (c *Cpu) BCS(in Instruction) {
if c.getStatus(sCarry) {
c.branch(in)
}
}
// BEQ: Branch if equal (z=1).
func (c *Cpu) BEQ(in Instruction) {
if c.getStatus(sZero) {
c.branch(in)
}
}
// BMI: Branch if negative.
func (c *Cpu) BMI(in Instruction) {
if c.getStatus(sNegative) {
c.branch(in)
}
}
// BNE: Branch if not equal.
func (c *Cpu) BNE(in Instruction) {
if !c.getStatus(sZero) {
c.branch(in)
}
}
// BPL: Branch if positive.
func (c *Cpu) BPL(in Instruction) {
if !c.getStatus(sNegative) {
c.branch(in)
}
}
// BRK: software interrupt
func (c *Cpu) BRK(in Instruction) {
// temporarily used to dump status
fmt.Println("BRK:", c)
}
// CLC: Clear carry flag.
func (c *Cpu) CLC(in Instruction) {
c.setStatus(sCarry, false)
}
// CLD: Clear decimal mode flag.
func (c *Cpu) CLD(in Instruction) {
c.setStatus(sDecimal, false)
}
// CLI: Clear interrupt-disable flag.
func (c *Cpu) CLI(in Instruction) {
c.setStatus(sInterrupt, true)
}
// CMP: Compare accumulator with memory.
func (c *Cpu) CMP(in Instruction) {
value := c.resolveOperand(in)
c.setStatus(sCarry, c.A >= value)
c.updateStatus(c.A - value)
}
// CPX: Compare index register X with memory.
func (c *Cpu) CPX(in Instruction) {
value := c.resolveOperand(in)
c.setStatus(sCarry, c.X >= value)
c.updateStatus(c.X - value)
}
// CPY: Compare index register Y with memory.
func (c *Cpu) CPY(in Instruction) {
value := c.resolveOperand(in)
c.setStatus(sCarry, c.Y >= value)
c.updateStatus(c.Y - value)
}
// DEC: Decrement.
func (c *Cpu) DEC(in Instruction) {
address := c.memoryAddress(in)
value := c.Bus.Read(address) - 1
c.Bus.Write(address, value)
c.updateStatus(value)
}
// DEX: Decrement index register X.
func (c *Cpu) DEX(in Instruction) {
c.X--
c.updateStatus(c.X)
}
// DEY: Decrement index register Y.
func (c *Cpu) DEY(in Instruction) {
c.Y--
c.updateStatus(c.Y)
}
// EOR: Exclusive-OR accumulator with memory.
func (c *Cpu) EOR(in Instruction) {
value := c.resolveOperand(in)
c.A ^= value
c.updateStatus(c.A)
}
// INC: Increment.
func (c *Cpu) INC(in Instruction) {
address := c.memoryAddress(in)
value := c.Bus.Read(address) + 1
c.Bus.Write(address, value)
c.updateStatus(value)
}
// INX: Increment index register X.
func (c *Cpu) INX(in Instruction) {
c.X++
c.updateStatus(c.X)
}
// INY: Increment index register Y.
func (c *Cpu) INY(in Instruction) {
c.Y++
c.updateStatus(c.Y)
}
// JMP: Jump.
func (c *Cpu) JMP(in Instruction) {
c.PC = c.memoryAddress(in)
}
// JSR: Jump to subroutine.
func (c *Cpu) JSR(in Instruction) {
c.Bus.Write16(c.stackHead(-1), c.PC-1)
c.SP -= 2
c.PC = in.Op16
}
// LDA: Load accumulator from memory.
func (c *Cpu) LDA(in Instruction) {
c.A = c.resolveOperand(in)
c.updateStatus(c.A)
}
// LDX: Load index register X from memory.
func (c *Cpu) LDX(in Instruction) {
c.X = c.resolveOperand(in)
c.updateStatus(c.X)
}
// LDY: Load index register Y from memory.
func (c *Cpu) LDY(in Instruction) {
c.Y = c.resolveOperand(in)
c.updateStatus(c.Y)
}
// LSR: Logical shift memory or accumulator right.
func (c *Cpu) LSR(in Instruction) {
switch in.addressing {
case accumulator:
c.setStatus(sCarry, c.A&1 == 1)
c.A >>= 1
c.updateStatus(c.A)
default:
address := c.memoryAddress(in)
value := c.Bus.Read(address)
c.setStatus(sCarry, value&1 == 1)
value >>= 1
c.Bus.Write(address, value)
c.updateStatus(value)
}
}
// NOP: No operation.
func (c *Cpu) NOP(in Instruction) {
}
// ORA: OR accumulator with memory.
func (c *Cpu) ORA(in Instruction) {
c.A |= c.resolveOperand(in)
c.updateStatus(c.A)
}
// PHA: Push accumulator onto stack.
func (c *Cpu) PHA(in Instruction) {
c.Bus.Write(0x0100+uint16(c.SP), c.A)
c.SP--
}
// PLA: Pull accumulator from stack.
func (c *Cpu) PLA(in Instruction) {
c.SP++
c.A = c.Bus.Read(0x0100 + uint16(c.SP))
}
// ROL: Rotate memory or accumulator left one bit.
func (c *Cpu) ROL(in Instruction) {
carry := c.getStatusInt(sCarry)
switch in.addressing {
case accumulator:
c.setStatus(sCarry, c.A>>7 == 1)
c.A = c.A<<1 | carry
c.updateStatus(c.A)
default:
address := c.memoryAddress(in)
value := c.Bus.Read(address)
c.setStatus(sCarry, value>>7 == 1)
value = value<<1 | carry
c.Bus.Write(address, value)
c.updateStatus(value)
}
}
// ROR: Rotate memory or accumulator left one bit.
func (c *Cpu) ROR(in Instruction) {
carry := c.getStatusInt(sCarry)
switch in.addressing {
case accumulator:
c.setStatus(sCarry, c.A&1 == 1)
c.A = c.A>>1 | carry<<7
c.updateStatus(c.A)
default:
address := c.memoryAddress(in)
value := c.Bus.Read(address)
c.setStatus(sCarry, value&1 == 1)
value = value>>1 | carry<<7
c.Bus.Write(address, value)
c.updateStatus(value)
}
}
// RTS: Return from subroutine.
func (c *Cpu) RTS(in Instruction) {
c.PC = c.Bus.Read16(c.stackHead(1))
c.SP += 2
c.PC += 1
}
// SBC: Subtract memory with borrow from accumulator.
func (c *Cpu) SBC(in Instruction) {
valueSigned := int16(c.A) - int16(c.resolveOperand(in))
if !c.getStatus(sCarry) {
valueSigned--
}
c.A = uint8(valueSigned)
// v: Set if signed overflow; cleared if valid sign result.
// TODO: c.setStatus(sOverflow, something)
// c: Set if unsigned borrow not required; cleared if unsigned borrow.
c.setStatus(sCarry, valueSigned >= 0)
// n: Set if most significant bit of result is set; else cleared.
// z: Set if result is zero; else cleared.
c.updateStatus(c.A)
}
// SEC: Set carry flag.
func (c *Cpu) SEC(in Instruction) {
c.setStatus(sCarry, true)
}
// SEI: Set interrupt-disable flag.
func (c *Cpu) SEI(in Instruction) {
c.setStatus(sInterrupt, false)
}
// STA: Store accumulator to memory.
func (c *Cpu) STA(in Instruction) {
c.Bus.Write(c.memoryAddress(in), c.A)
}
// STX: Store index register X to memory.
func (c *Cpu) STX(in Instruction) {
c.Bus.Write(c.memoryAddress(in), c.X)
}
// STY: Store index register Y to memory.
func (c *Cpu) STY(in Instruction) {
c.Bus.Write(c.memoryAddress(in), c.Y)
}
// TAX: Transfer accumulator to index register X.
func (c *Cpu) TAX(in Instruction) {
c.X = c.A
c.updateStatus(c.X)
}
// TAY: Transfer accumulator to index register Y.
func (c *Cpu) TAY(in Instruction) {
c.Y = c.A
c.updateStatus(c.Y)
}
// TSX: Transfer stack pointer to index register X.
func (c *Cpu) TSX(in Instruction) {
c.X = c.SP
c.updateStatus(c.X)
}
// TXA: Transfer index register X to accumulator.
func (c *Cpu) TXA(in Instruction) {
c.A = c.X
c.updateStatus(c.A)
}
// TXS: Transfer index register X to stack pointer.
func (c *Cpu) TXS(in Instruction) {
c.SP = c.X
c.updateStatus(c.SP)
}
// TYA: Transfer index register Y to accumulator.
func (c *Cpu) TYA(in Instruction) {
c.A = c.Y
c.updateStatus(c.A)
}
func (c *Cpu) _END(in Instruction) {
c.ExitChan <- int(c.X)
}

42
cpu/instruction.go Normal file
View File

@ -0,0 +1,42 @@
package cpu
import (
"fmt"
"github.com/ariejan/i6502/bus"
)
// Instruction is a self-contained, variable length instruction,
// it includes the the operation type and a possible 8 or 16 bit operand.
type Instruction struct {
OpType
// 2 byte instructions have a single byte operand
Op8 uint8
// 3 byte instruction have a double byte operand
Op16 uint16
}
func ReadInstruction(pc uint16, bus *bus.Bus) Instruction {
// Read the opcode
opcode := bus.Read(pc)
// Do we know this opcode in our optypes table?
optype, ok := optypes[opcode]
if !ok {
panic(fmt.Sprintf("Unknown opcode $%02X at $04X", opcode, pc))
}
instruction := Instruction{OpType: optype}
switch instruction.Bytes {
case 1: // No operand.
case 2:
instruction.Op8 = bus.Read(pc + 1)
case 3:
instruction.Op16 = bus.Read16(pc + 1)
default:
panic(fmt.Sprintf("Unknown instruction length (%d) for $%02X at $04X", instruction.Bytes, opcode, pc))
}
return instruction
}

340
cpu/op_type.go Normal file
View File

@ -0,0 +1,340 @@
package cpu
// 6502 Addressing modes
const (
_ = iota
absolute
absoluteX
absoluteY
accumulator
immediate
implied
indirect
indirectX
indirectY
relative
zeropage
zeropageX
zeropageY
)
// Human readable address modes
var addressingNames = [...]string{
"",
"absolute",
"absoluteX",
"absoluteY",
"accumulator",
"immediate",
"implied",
"(indirect)",
"(indirect,X)",
"(indirect),Y",
"relative",
"zeropage",
"zeropageX",
"zeropageY",
}
// 6502 Instruction mnemonics.
// OpCodes are a combination of an instruction and an addressing mode
const (
_ = iota
adc
and
asl
bcc
bcs
beq
bit
bmi
bne
bpl
brk
bvc
bvs
clc
cld
cli
clv
cmp
cpx
cpy
dec
dex
dey
eor
inc
inx
iny
jmp
jsr
lda
ldx
ldy
lsr
nop
ora
pha
php
pla
plp
rol
ror
rti
rts
sbc
sec
sed
sei
sta
stx
sty
tax
tay
tsx
txa
txs
tya
_end
)
// Human readable instruction mnemonics
var instructionNames = [...]string{
"",
"ADC",
"AND",
"ASL",
"BCC",
"BCS",
"BEQ",
"BIT",
"BMI",
"BNE",
"BPL",
"BRK",
"BVC",
"BVS",
"CLC",
"CLD",
"CLI",
"CLV",
"CMP",
"CPX",
"CPY",
"DEC",
"DEX",
"DEY",
"EOR",
"INC",
"INX",
"INY",
"JMP",
"JSR",
"LDA",
"LDX",
"LDY",
"LSR",
"NOP",
"ORA",
"PHA",
"PHP",
"PLA",
"PLP",
"ROL",
"ROR",
"RTI",
"RTS",
"SBC",
"SEC",
"SED",
"SEI",
"STA",
"STX",
"STY",
"TAX",
"TAY",
"TSX",
"TXA",
"TXS",
"TYA",
"_END",
}
// The OpType consists of a 6502 instruction and addressing mode.
// It does not include any following operand
type OpType struct {
// Opcode is the instruction + addresing mode
Opcode byte
// Internal id for the used instruction/mnemonic
id uint8
// Internal id for the addressing mode
addressing uint8
// Bytes is the size of the instruction and its expected operands.
// 1 byte: opcode with implicit or null operand
// 2 bytes: opcode with immediate or zeropage operand (8-bit operand)
// 3 bytes: opcode with address operands
Bytes uint8
// Cycles, number of clock cycles it takes to execute this opcode
Cycles uint8
}
// See http://www.e-tradition.net/bytes/6502/6502_instruction_set.html for details
var optypes = map[uint8]OpType{
0x69: OpType{0x69, adc, immediate, 2, 2},
0x65: OpType{0x69, adc, zeropage, 2, 3},
0x75: OpType{0x75, adc, zeropageX, 2, 4},
0x6D: OpType{0x6D, adc, absolute, 3, 4},
0x7D: OpType{0x7D, adc, absoluteX, 3, 4},
0x79: OpType{0x79, adc, absoluteY, 3, 4},
0x61: OpType{0x61, adc, indirectX, 2, 6},
0x71: OpType{0x71, adc, indirectY, 2, 5},
0x29: OpType{0x29, and, immediate, 2, 2},
0x25: OpType{0x25, and, zeropage, 2, 3},
0x35: OpType{0x35, and, zeropageX, 2, 4},
0x2D: OpType{0x2D, and, absolute, 3, 4},
0x3D: OpType{0x3D, and, absoluteX, 3, 4},
0x39: OpType{0x39, and, absoluteY, 3, 4},
0x21: OpType{0x21, and, indirectX, 2, 6},
0x31: OpType{0x31, and, indirectY, 2, 5},
0x0A: OpType{0x0A, asl, accumulator, 1, 2},
0x06: OpType{0x06, asl, zeropage, 2, 5},
0x16: OpType{0x16, asl, zeropageX, 2, 6},
0x0E: OpType{0x0E, asl, absolute, 3, 6},
0x1E: OpType{0x1E, asl, absoluteX, 3, 7},
0x90: OpType{0x90, bcc, relative, 2, 2},
0xB0: OpType{0xB0, bcs, relative, 2, 2},
0xF0: OpType{0xF0, beq, relative, 2, 2},
0x24: OpType{0x24, bit, zeropage, 2, 3},
0x2C: OpType{0x2C, bit, absolute, 3, 4},
0x30: OpType{0x30, bmi, relative, 2, 2},
0xD0: OpType{0xD0, bne, relative, 2, 2},
0x10: OpType{0x10, bpl, relative, 2, 2},
0x00: OpType{0x00, brk, implied, 1, 7},
0x50: OpType{0x50, bvc, relative, 2, 2},
0x70: OpType{0x70, bvs, relative, 2, 2},
0x18: OpType{0x18, clc, implied, 1, 2},
0xD8: OpType{0xD8, cld, implied, 1, 2},
0x58: OpType{0x58, cli, implied, 1, 2},
0xB8: OpType{0xB8, clv, implied, 1, 2},
0xC9: OpType{0xC9, cmp, immediate, 2, 2},
0xC5: OpType{0xC5, cmp, zeropage, 2, 3},
0xD5: OpType{0xD5, cmp, zeropageX, 2, 4},
0xCD: OpType{0xCD, cmp, absolute, 3, 4},
0xDD: OpType{0xDD, cmp, absoluteX, 3, 4},
0xD9: OpType{0xD9, cmp, absoluteY, 3, 4},
0xC1: OpType{0xC1, cmp, indirectX, 2, 6},
0xD1: OpType{0xD1, cmp, indirectY, 2, 5},
0xE0: OpType{0xE0, cpx, immediate, 2, 2},
0xE4: OpType{0xE4, cpx, zeropage, 2, 3},
0xEC: OpType{0xEC, cpx, absolute, 3, 4},
0xC0: OpType{0xC0, cpy, immediate, 2, 2},
0xC4: OpType{0xC4, cpy, zeropage, 2, 3},
0xCC: OpType{0xCC, cpy, absolute, 3, 4},
0xC6: OpType{0xC6, dec, zeropage, 2, 5},
0xD6: OpType{0xD6, dec, zeropageX, 2, 6},
0xCE: OpType{0xCE, dec, absolute, 3, 3},
0xDE: OpType{0xDE, dec, absoluteX, 3, 7},
0xCA: OpType{0xCA, dex, implied, 1, 2},
0x88: OpType{0x88, dey, implied, 1, 2},
0x49: OpType{0x49, eor, immediate, 2, 2},
0x45: OpType{0x45, eor, zeropage, 2, 3},
0x55: OpType{0x55, eor, zeropageX, 2, 4},
0x4D: OpType{0x4D, eor, absolute, 3, 4},
0x5D: OpType{0x5D, eor, absoluteX, 3, 4},
0x59: OpType{0x59, eor, absoluteY, 3, 4},
0x41: OpType{0x41, eor, indirectX, 2, 6},
0x51: OpType{0x51, eor, indirectY, 2, 5},
0xE6: OpType{0xE6, inc, zeropage, 2, 5},
0xF6: OpType{0xF6, inc, zeropageX, 2, 6},
0xEE: OpType{0xEE, inc, absolute, 3, 6},
0xFE: OpType{0xFE, inc, absoluteX, 3, 7},
0xE8: OpType{0xE8, inx, implied, 1, 2},
0xC8: OpType{0xC8, iny, implied, 1, 2},
0x4C: OpType{0x4C, jmp, absolute, 3, 3},
0x6C: OpType{0x6C, jmp, indirect, 3, 5},
0x20: OpType{0x20, jsr, absolute, 3, 6},
0xA9: OpType{0xA9, lda, immediate, 2, 2},
0xA5: OpType{0xA5, lda, zeropage, 2, 3},
0xB5: OpType{0xB5, lda, zeropageX, 2, 4},
0xAD: OpType{0xAD, lda, absolute, 3, 4},
0xBD: OpType{0xBD, lda, absoluteX, 3, 4},
0xB9: OpType{0xB9, lda, absoluteY, 3, 4},
0xA1: OpType{0xA1, lda, indirectX, 2, 6},
0xB1: OpType{0xB1, lda, indirectY, 2, 5},
0xA2: OpType{0xA2, ldx, immediate, 2, 2},
0xA6: OpType{0xA6, ldx, zeropage, 2, 3},
0xB6: OpType{0xB6, ldx, zeropageY, 2, 4},
0xAE: OpType{0xAE, ldx, absolute, 3, 4},
0xBE: OpType{0xBE, ldx, absoluteY, 3, 4},
0xA0: OpType{0xA0, ldy, immediate, 2, 2},
0xA4: OpType{0xA4, ldy, zeropage, 2, 3},
0xB4: OpType{0xB4, ldy, zeropageX, 2, 4},
0xAC: OpType{0xAC, ldy, absolute, 3, 4},
0xBC: OpType{0xBC, ldy, absoluteX, 3, 4},
0x4A: OpType{0x4A, lsr, accumulator, 1, 2},
0x46: OpType{0x46, lsr, zeropage, 2, 5},
0x56: OpType{0x56, lsr, zeropageX, 2, 6},
0x4E: OpType{0x4E, lsr, absolute, 3, 6},
0x5E: OpType{0x5E, lsr, absoluteX, 3, 7},
0xEA: OpType{0xEA, nop, implied, 1, 2},
0x09: OpType{0x09, ora, immediate, 2, 2},
0x05: OpType{0x05, ora, zeropage, 2, 3},
0x15: OpType{0x15, ora, zeropageX, 2, 4},
0x0D: OpType{0x0D, ora, absolute, 3, 4},
0x1D: OpType{0x1D, ora, absoluteX, 3, 4},
0x19: OpType{0x19, ora, absoluteY, 3, 4},
0x01: OpType{0x01, ora, indirectX, 2, 6},
0x11: OpType{0x11, ora, indirectY, 2, 5},
0x48: OpType{0x48, pha, implied, 1, 3},
0x08: OpType{0x08, php, implied, 1, 3},
0x68: OpType{0x68, pla, implied, 1, 4},
0x28: OpType{0x28, php, implied, 1, 4},
0x2A: OpType{0x2A, rol, accumulator, 1, 2},
0x26: OpType{0x26, rol, zeropage, 2, 5},
0x36: OpType{0x36, rol, zeropageX, 2, 6},
0x2E: OpType{0x2E, rol, absolute, 3, 6},
0x3E: OpType{0x3E, rol, absoluteX, 3, 7},
0x6A: OpType{0x6A, ror, accumulator, 1, 2},
0x66: OpType{0x66, ror, zeropage, 2, 5},
0x76: OpType{0x76, ror, zeropageX, 2, 6},
0x6E: OpType{0x6E, ror, absolute, 3, 6},
0x7E: OpType{0x7E, ror, absoluteX, 3, 7},
0x40: OpType{0x40, rti, implied, 1, 6},
0x60: OpType{0x60, rts, implied, 1, 6},
0xE9: OpType{0xE9, sbc, immediate, 2, 2},
0xE5: OpType{0xE5, sbc, zeropage, 2, 3},
0xF5: OpType{0xF5, sbc, zeropageX, 2, 4},
0xED: OpType{0xED, sbc, absolute, 3, 4},
0xFD: OpType{0xFD, sbc, absoluteX, 3, 4},
0xF9: OpType{0xF9, sbc, absoluteY, 3, 4},
0xE1: OpType{0xE1, sbc, indirectX, 2, 6},
0xF1: OpType{0xF1, sbc, indirectY, 2, 5},
0x38: OpType{0x38, sec, implied, 1, 2},
0xF8: OpType{0xF8, sed, implied, 1, 2},
0x78: OpType{0x78, sei, implied, 1, 2},
0x85: OpType{0x85, sta, zeropage, 2, 3},
0x95: OpType{0x95, sta, zeropageX, 2, 4},
0x8D: OpType{0x8D, sta, absolute, 3, 4},
0x9D: OpType{0x9D, sta, absoluteX, 3, 5},
0x99: OpType{0x99, sta, absoluteY, 3, 5},
0x81: OpType{0x81, sta, indirectX, 2, 6},
0x91: OpType{0x91, sta, indirectY, 2, 6},
0x86: OpType{0x86, stx, zeropage, 2, 3},
0x96: OpType{0x96, stx, zeropageY, 2, 4},
0x8E: OpType{0x8E, stx, absolute, 3, 4},
0x84: OpType{0x84, sty, zeropage, 2, 3},
0x94: OpType{0x94, sty, zeropageX, 2, 4},
0x8C: OpType{0x8C, sty, absolute, 3, 4},
0xAA: OpType{0xAA, tax, implied, 1, 2},
0xA8: OpType{0xA8, tay, implied, 1, 2},
0xBA: OpType{0xBA, tsx, implied, 1, 2},
0x8A: OpType{0x8A, txa, implied, 1, 2},
0x9A: OpType{0x9A, txs, implied, 1, 2},
0x98: OpType{0x98, tya, implied, 1, 2},
0xFF: OpType{0xFF, _end, implied, 1, 1},
}

69
i6502.go Normal file
View File

@ -0,0 +1,69 @@
/*
i6502 is a software emulator of my i6502 home-built computer. It uses
the 65C02 microprocessor, 32kB RAM and 16kB ROM.
*/
package main
import (
"fmt"
"github.com/ariejan/i6502/bus"
"github.com/ariejan/i6502/cpu"
"github.com/ariejan/i6502/memory"
"os"
"os/signal"
)
func main() {
os.Exit(mainReturningStatus())
}
func mainReturningStatus() int {
// 32kB RAM
ram := memory.CreateRam()
// 16kB ROM, filled from file
rom, err := memory.LoadRomFromFile("rom/test.rom")
if err != nil {
panic(err)
}
// 16-bit address bus
bus, _ := bus.CreateBus()
bus.Attach(ram, "32kB RAM", 0x0000)
bus.Attach(rom, "16kB ROM", 0xC000)
fmt.Println(bus)
exitChan := make(chan int, 0)
cpu := &cpu.Cpu{Bus: bus, ExitChan: exitChan}
cpu.Reset()
go func() {
for {
cpu.Step()
}
}()
var (
sig os.Signal
exitStatus int
)
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt)
select {
case exitStatus = <-exitChan:
// Okay, handle the rest of the code
case sig = <-sigChan:
fmt.Println("\nGot signal: ", sig)
exitStatus = 1
}
fmt.Println(cpu)
fmt.Println("Dumping RAM into `core` file")
ram.Dump("core")
return exitStatus
}

9
memory/memory.go Normal file
View File

@ -0,0 +1,9 @@
// Package memory provides different memory components
// and a common interface.
package memory
type Memory interface {
Size() int
Read(address uint16) byte
Write(address uint16, value byte)
}

33
memory/ram.go Normal file
View File

@ -0,0 +1,33 @@
package memory
import (
"io/ioutil"
)
// Ram provides 32kB of Random Access Memory
type Ram struct {
data [0x8000]byte
}
func (ram *Ram) Size() int {
return len(ram.data)
}
func (ram *Ram) Read(address uint16) byte {
return ram.data[address]
}
func (ram *Ram) Write(address uint16, value byte) {
ram.data[address] = value
}
func CreateRam() *Ram {
return &Ram{}
}
func (ram *Ram) Dump(path string) {
err := ioutil.WriteFile(path, ram.data[:], 0640)
if err != nil {
panic(err)
}
}

42
memory/rom.go Normal file
View File

@ -0,0 +1,42 @@
package memory
import (
"io/ioutil"
)
// Rom provies 16kB of Read Only Memory, typcially loaded from file.
type Rom struct {
data [0x4000]byte
}
func (rom *Rom) Size() int {
return len(rom.data)
}
func (rom *Rom) Read(address uint16) byte {
return rom.data[address]
}
func (rom *Rom) Write(address uint16, value byte) {
panic("Cannot write to ROM!")
}
// Load ROM from a binary file. The data is placed
// at the beginning of ROM.
func LoadRomFromFile(path string) (*Rom, error) {
// Read data from file
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
// Create the rom instance
rom := &Rom{}
// Load data into ROM
for i, b := range data {
rom.data[i] = b
}
return rom, nil
}

1
rom/test.rom Symbolic link
View File

@ -0,0 +1 @@
../../asm/build/kernal.rom