mirror of https://github.com/ariejan/i6502.git
Initial commit of the i6502 emulator.
This commit is contained in:
commit
4a155cad33
|
@ -0,0 +1,5 @@
|
||||||
|
# RAM memory dumps
|
||||||
|
core
|
||||||
|
|
||||||
|
# Binary
|
||||||
|
i6502
|
|
@ -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))
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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},
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
../../asm/build/kernal.rom
|
Loading…
Reference in New Issue