From 4a155cad33dcca8d4077eed595dcdd0d5591cc15 Mon Sep 17 00:00:00 2001 From: Ariejan de Vroom Date: Thu, 7 Aug 2014 09:15:41 +0200 Subject: [PATCH] Initial commit of the i6502 emulator. --- .gitignore | 5 + bus/bus.go | 100 +++++++ bus/offset_memory.go | 20 ++ cpu/cpu.go | 634 +++++++++++++++++++++++++++++++++++++++++++ cpu/instruction.go | 42 +++ cpu/op_type.go | 340 +++++++++++++++++++++++ i6502.go | 69 +++++ memory/memory.go | 9 + memory/ram.go | 33 +++ memory/rom.go | 42 +++ rom/test.rom | 1 + 11 files changed, 1295 insertions(+) create mode 100644 .gitignore create mode 100644 bus/bus.go create mode 100644 bus/offset_memory.go create mode 100644 cpu/cpu.go create mode 100644 cpu/instruction.go create mode 100644 cpu/op_type.go create mode 100644 i6502.go create mode 100644 memory/memory.go create mode 100644 memory/ram.go create mode 100644 memory/rom.go create mode 120000 rom/test.rom diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c601c53 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# RAM memory dumps +core + +# Binary +i6502 diff --git a/bus/bus.go b/bus/bus.go new file mode 100644 index 0000000..bd6e4a7 --- /dev/null +++ b/bus/bus.go @@ -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)) +} diff --git a/bus/offset_memory.go b/bus/offset_memory.go new file mode 100644 index 0000000..7de1bdd --- /dev/null +++ b/bus/offset_memory.go @@ -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) +} diff --git a/cpu/cpu.go b/cpu/cpu.go new file mode 100644 index 0000000..6cc8e28 --- /dev/null +++ b/cpu/cpu.go @@ -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) +} diff --git a/cpu/instruction.go b/cpu/instruction.go new file mode 100644 index 0000000..7d85095 --- /dev/null +++ b/cpu/instruction.go @@ -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 +} diff --git a/cpu/op_type.go b/cpu/op_type.go new file mode 100644 index 0000000..104f198 --- /dev/null +++ b/cpu/op_type.go @@ -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}, +} diff --git a/i6502.go b/i6502.go new file mode 100644 index 0000000..623b4c8 --- /dev/null +++ b/i6502.go @@ -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 +} diff --git a/memory/memory.go b/memory/memory.go new file mode 100644 index 0000000..8cb94c2 --- /dev/null +++ b/memory/memory.go @@ -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) +} diff --git a/memory/ram.go b/memory/ram.go new file mode 100644 index 0000000..a1c3737 --- /dev/null +++ b/memory/ram.go @@ -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) + } +} diff --git a/memory/rom.go b/memory/rom.go new file mode 100644 index 0000000..800f3d7 --- /dev/null +++ b/memory/rom.go @@ -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 +} diff --git a/rom/test.rom b/rom/test.rom new file mode 120000 index 0000000..f7a0f95 --- /dev/null +++ b/rom/test.rom @@ -0,0 +1 @@ +../../asm/build/kernal.rom \ No newline at end of file