├── .gitignore ├── README.md ├── bus ├── bus.go └── offset_memory.go ├── cli └── options.go ├── cpu ├── cpu.go ├── cpu_test.go ├── instruction.go └── op_type.go ├── debugger ├── debugger.go └── symbols.go ├── go.mod ├── go.sum ├── go6502.go ├── ili9340 └── ili9340.go ├── memory ├── memory.go ├── ram.go └── rom.go ├── sd ├── response_string.go ├── sd.go ├── sd_card.go ├── sd_card_peripheral.go ├── sd_test.go └── state_string.go ├── speedometer └── speedometer.go ├── spi ├── pin_map.go ├── pin_map_test.go ├── slave.go └── spi.go ├── ssd1306 └── ssd1306.go └── via6522 ├── via6522.go └── via6522_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Core dump file. 2 | /core 3 | 4 | # Compiled binary. 5 | /go6502 6 | 7 | # ROM images (programs/data) 8 | /rom/* 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | go6502 2 | ====== 3 | 4 | ``` 5 | | | | | | | | | | | | | | | | | | | | | 6 | .----------------------------------------. 7 | | GO | 8 | | 6502 | 9 | | 1213 | 10 | `----------------------------------------' 11 | | | | | | | | | | | | | | | | | | | | | 12 | ``` 13 | 14 | A [go][golang]-based emulator and debugger for the [6502][6502]-based 15 | [pda6502 homebrew computer][pda6502]. 16 | 17 | [![GoDoc](https://godoc.org/github.com/pda/go6502?status.png)](https://godoc.org/github.com/pda/go6502) 18 | 19 | Background 20 | ---------- 21 | 22 | I've been designing and building a [6502-based homebrew computer][pda6502]. 23 | 24 | It's powered by an 8-bit 6502 (WDC 65C02), varitions of which powered the 25 | venerable Commodore 64, Apple II, Vic 20, Nintendo and lots more. 26 | 27 | 74HC-series logic chips map the 64K address space to 32K RAM, 8K ROM, a VIA 28 | 6522 I/O controller, and room for expansion. 29 | 30 | The first output device (beyond flashing LEDs) is a [128x32 pixel OLED][oled], 31 | connected to one of the VIA 6522 parallel ports, with bit-banged serial comms. 32 | 33 | 34 | go6502 35 | ------ 36 | 37 | go6502 emulates the 6502, address bus, RAM, ROM, 6522 and OLED display well 38 | enough to run the current pda6502 code and get the same display output. 39 | 40 | It has a flexible address bus, which paves the way to emulating other 41 | 6502-based systems. 42 | 43 | go6502 features a stepping debugger with breakpoints on instruction type, 44 | register values and memory location. This makes it far easier to get code 45 | working correctly before writing it to an actual EEPROM. 46 | 47 | 48 | Running it 49 | ---------- 50 | 51 | Get and run go6502: 52 | 53 | * Drop an 8 KB `kernal.rom` into `$PWD/rom/`, where ever that may be. 54 | * ([pda6502][pda6502] can help; see `memory.conf` and `Makefile`) 55 | * `go get github.com/pda/go6502` 56 | * `go6502` 57 | * `go6502 --help` 58 | * `go6502 --debug` 59 | 60 | 61 | Example usage 62 | ------------- 63 | 64 | Various invocations from my shell history; some run from this project, some from pda6502. 65 | 66 | ```sh 67 | time go run go6502.go --debug --debug-commands='bi nop;run;q' --via-ssd1306 && open output.png 68 | make && go6502 --debug --debug-symbol-file=build/debug --debug-commands="bi nop;c;q" --via-ssd1306 --sd-card=sd.bin 69 | go build -o g6 go6502.go && gtimeout -s INT 0.2 ./g6 --via-ssd1306 --speedometer 70 | time go run go6502.go --via-ssd1306 --debug 71 | time go run go6502.go --debug --debug-commands='bi nop;r;q' --via-ssd1306 && open output.png 72 | make && go6502 --debug --debug-symbol-file=build/debug --via-ssd1306 --sd-card=sd.bin 73 | time go run go6502.go --via-ssd1306 --debug --debug-commands='bi nop;r;q' && open output.png 74 | go run go6502.go --debug --debug-symbol-file=$HOME/code/pda6502/build/debug --via-ssd1306 --sd-card=$HOME/code/pda6502/sd.bin 75 | make && go install github.com/pda/go6502 && gtimeout -s INT 0.1 go6502 --via-ssd1306 --sd-card="sd.bin" ; open ssd1306.png 76 | go run go6502.go --via-ssd1306 --debug --debug-commands='bi nop;run' 77 | go build go6502.go && gtimeout -s INT 1 ./go6502 --via-ssd1306 --speedometer 78 | go build go6502.go && gtimeout -s INT 0.1 ./go6502 --via-ssd1306 --speedometer 79 | make && go6502 -via-ssd1306 -sd-card=sd4gb.fat32 -debug -debug-symbol-file=build/debug -debug-commands="ba Halt; c; q" && hd -s 0x6000 -n 512 core 80 | make && go6502 --debug --debug-symbol-file=build/debug --via-ssd1306 --sd-card=sd.bin --debug-commands="bi nop; c; q" 81 | make && go6502 --debug --debug-symbol-file=build/debug --debug-commands="bi nop;c;q" --via-ssd1306 --sd-card=sd.bin && open ssd1306.png 82 | go6502 -via-ssd1306 -sd-card=sd4gb.fat32 83 | go6502 --debug --debug-symbol-file=build/debug --via-ssd1306 --sd-card=sd.bin 84 | go run go6502.go --via-ssd1306 --debug 85 | go run go6502.go --debug --debug-commands='bi nop;run;q' --via-ssd1306 86 | ``` 87 | 88 | 89 | Debugger / Monitor 90 | ------------------ 91 | 92 | Given there's almost no I/O, you'll probably want a debugger / monitor session. 93 | 94 | ``` 95 | $ go6502 --debug 96 | CPU pc:0xE000 ac:0x00 x:0x00 y:0x00 sp:0xFF sr:-------- 97 | Instruction[SEI op:78 addr:6 bytes:1 cycles:2] op8:0x00 op16:0x0000 98 | $E000> help 99 | 100 | pda6502 debuger 101 | --------------- 102 | break-address (alias: ba) e.g. ba 0x1000 103 | break-instruction (alias: bi) e.g. bi NOP 104 | break-register (alias: br) e.g. br x 128 105 | continue (alias: c) Run continuously until breakpoint. 106 | exit (alias: quit, q) Shut down the emulator. 107 | help (alias: h, ?) This help. 108 | read
- Read and display 8-bit integer at address. 109 | read16
- Read and display 16-bit integer at address. 110 | step (alias: s) Run only the current instruction. 111 | (blank) Repeat the previous command. 112 | 113 | Hex input formats: 0x1234 $1234 114 | Commands expecting uint16 treat . as current address (PC). 115 | $E000> step 116 | CPU pc:0xE001 ac:0x00 x:0x00 y:0x00 sp:0xFF sr:-------- 117 | Instruction[LDX op:A2 addr:5 bytes:2 cycles:2] op8:0xFF op16:0x0000 118 | $E001> break-instruction NOP 119 | $E001> break-address $E003 120 | $E001> continue 121 | Breakpoint for PC address = $E003 122 | CPU pc:0xE003 ac:0x00 x:0xFF y:0x00 sp:0xFF sr:n------- 123 | Instruction[TXS op:9A addr:6 bytes:1 cycles:2] op8:0x00 op16:0x0000 124 | $E003> continue 125 | Breakpoint for instruction NOP 126 | CPU pc:0xE0FC ac:0xFF x:0x00 y:0x00 sp:0xFF sr:-----izc 127 | Instruction[NOP op:EA addr:6 bytes:1 cycles:2] op8:0x00 op16:0x0000 128 | $E0FC> read $FFFC 129 | $FFFC => $00 0b00000000 0 '\x00' 130 | $E0FC> read16 $FFFC 131 | $FFFC,FFFD => $E000 0b1110000000000000 57344 132 | $E0FC> quit 133 | ``` 134 | 135 | 136 | License 137 | ------- 138 | 139 | Copyright 2013–2014 Paul Annesley, released under MIT license. 140 | 141 | 142 | [6502]: http://en.wikipedia.org/wiki/MOS_Technology_6502 143 | [golang]: http://golang.org/ 144 | [c64.rb]: https://github.com/pda/c64.rb 145 | [pda6502]: https://github.com/pda/pda6502 146 | [homebrew]: http://brew.sh/ 147 | [oled]: https://www.adafruit.com/products/661 148 | -------------------------------------------------------------------------------- /bus/bus.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package bus provides a mappable 16-bit addressable 8-bit data bus for go6502. 3 | Different Memory backends can be attached at different base addresses. 4 | */ 5 | package bus 6 | 7 | import ( 8 | "fmt" 9 | 10 | "github.com/pda/go6502/memory" 11 | ) 12 | 13 | type busEntry struct { 14 | mem memory.Memory 15 | name string 16 | start uint16 17 | end uint16 18 | } 19 | 20 | // Bus is a 16-bit address, 8-bit data bus, which maps reads and writes 21 | // at different locations to different backend Memory. For example the 22 | // lower 32K could be RAM, the upper 8KB ROM, and some I/O in the middle. 23 | type Bus struct { 24 | entries []busEntry 25 | } 26 | 27 | func (b *Bus) String() string { 28 | return fmt.Sprintf("Address bus (TODO: describe)") 29 | } 30 | 31 | func CreateBus() (*Bus, error) { 32 | return &Bus{entries: make([]busEntry, 0)}, nil 33 | } 34 | 35 | // Attach maps a bus address range to a backend Memory implementation, 36 | // which could be RAM, ROM, I/O device etc. 37 | func (b *Bus) Attach(mem memory.Memory, name string, offset uint16) error { 38 | om := OffsetMemory{Offset: offset, Memory: mem} 39 | end := offset + uint16(mem.Size()-1) 40 | entry := busEntry{mem: om, name: name, start: offset, end: end} 41 | b.entries = append(b.entries, entry) 42 | return nil 43 | } 44 | 45 | func (b *Bus) backendFor(a uint16) (memory.Memory, error) { 46 | for _, be := range b.entries { 47 | if a >= be.start && a <= be.end { 48 | return be.mem, nil 49 | } 50 | } 51 | return nil, fmt.Errorf("No backend for address 0x%04X", a) 52 | } 53 | 54 | // Shutdown tells the address bus a shutdown is occurring, and to pass the 55 | // message on to subordinates. 56 | func (b *Bus) Shutdown() { 57 | for _, be := range b.entries { 58 | be.mem.Shutdown() 59 | } 60 | } 61 | 62 | // Read returns the byte from memory mapped to the given address. 63 | // e.g. if ROM is mapped to 0xC000, then Read(0xC0FF) returns the byte at 64 | // 0x00FF in that RAM device. 65 | func (b *Bus) Read(a uint16) byte { 66 | mem, err := b.backendFor(a) 67 | if err != nil { 68 | panic(err) 69 | } 70 | value := mem.Read(a) 71 | return value 72 | } 73 | 74 | // Read16 returns the 16-bit value stored in little-endian format with the 75 | // low byte at address, and the high byte at address+1. 76 | func (b *Bus) Read16(a uint16) uint16 { 77 | lo := uint16(b.Read(a)) 78 | hi := uint16(b.Read(a + 1)) 79 | return hi<<8 | lo 80 | } 81 | 82 | // Write the byte to the device mapped to the given address. 83 | func (b *Bus) Write(a uint16, value byte) { 84 | mem, err := b.backendFor(a) 85 | if err != nil { 86 | panic(err) 87 | } 88 | mem.Write(a, value) 89 | } 90 | 91 | // Write16 writes the given 16-bit value to the specifie address, storing it 92 | // little-endian, with high byte at address+1. 93 | func (b *Bus) Write16(a uint16, value uint16) { 94 | b.Write(a, byte(value)) 95 | b.Write(a+1, byte(value>>8)) 96 | } 97 | -------------------------------------------------------------------------------- /bus/offset_memory.go: -------------------------------------------------------------------------------- 1 | package bus 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/pda/go6502/memory" 7 | ) 8 | 9 | // OffsetMemory wraps a Memory object, rewriting read/write addresses by the 10 | // given offset. This makes it possible to mount memory into a larger address 11 | // space at a given base address. 12 | type OffsetMemory struct { 13 | Offset uint16 14 | memory.Memory 15 | } 16 | 17 | // Read returns a byte from the underlying Memory after rewriting the address 18 | // using the offset. 19 | func (om OffsetMemory) Read(a uint16) byte { 20 | return om.Memory.Read(a - om.Offset) 21 | } 22 | 23 | func (om OffsetMemory) String() string { 24 | return fmt.Sprintf("OffsetMemory(%v)", om.Memory) 25 | } 26 | 27 | // Write stores a byte in the underlying Memory after rewriting the address 28 | // using the offset. 29 | func (om OffsetMemory) Write(a uint16, value byte) { 30 | om.Memory.Write(a-om.Offset, value) 31 | } 32 | -------------------------------------------------------------------------------- /cli/options.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package cli provides command line support for go6502. 3 | 4 | It parses CLI flags and exposes the resulting options. 5 | */ 6 | package cli 7 | 8 | import ( 9 | "flag" 10 | "fmt" 11 | "strings" 12 | ) 13 | 14 | // Options stores the value of command line options after they're parsed. 15 | type Options struct { 16 | Debug bool 17 | DebugCmds commandList 18 | DebugSymbolFile string 19 | Ili9340 bool 20 | SdCard string 21 | Speedometer bool 22 | ViaDumpAscii bool 23 | ViaDumpBinary bool 24 | ViaSsd1306 bool 25 | } 26 | 27 | // ParseFlags uses the flag stdlib package to parse CLI options. 28 | func ParseFlags() *Options { 29 | opt := &Options{} 30 | 31 | flag.BoolVar(&opt.Debug, "debug", false, "Run debugger") 32 | flag.Var(&opt.DebugCmds, "debug-commands", "Debugger commands to run, semicolon separated.") 33 | flag.StringVar(&opt.DebugSymbolFile, "debug-symbol-file", "", "ld65 debug file to load.") 34 | flag.StringVar(&opt.SdCard, "sd-card", "", "Load file as SD card") 35 | flag.BoolVar(&opt.Speedometer, "speedometer", false, "Measure effective clock speed") 36 | flag.BoolVar(&opt.ViaDumpBinary, "via-dump-binary", false, "6522 dumps binary output") 37 | flag.BoolVar(&opt.ViaDumpAscii, "via-dump-ascii", false, "6522 dumps ASCII output") 38 | flag.BoolVar(&opt.ViaSsd1306, "via-ssd1306", false, "SSD1306 OLED display on 6522") 39 | flag.BoolVar(&opt.Ili9340, "ili9340", false, "ILI9340 TFT display on 6522") 40 | 41 | flag.Parse() 42 | return opt 43 | } 44 | 45 | type commandList []string 46 | 47 | func (cl *commandList) Set(value string) error { 48 | list := strings.Split(value, ";") 49 | for i, value := range list { 50 | list[i] = strings.TrimSpace(value) 51 | } 52 | *cl = list 53 | return nil 54 | } 55 | 56 | func (cl *commandList) String() string { 57 | return fmt.Sprint(*cl) 58 | } 59 | -------------------------------------------------------------------------------- /cpu/cpu.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package cpu implements the MOS 6502 processor. 3 | 4 | cpu.Cpu requires a bus.Bus to read/write 8-bit data to 16-bit addresses. 5 | 6 | cpu.Cpu also provides a monitor hook, allowing external code to observe 7 | and block on instructions before they're executed. 8 | */ 9 | package cpu 10 | 11 | import ( 12 | "fmt" 13 | "strings" 14 | 15 | "github.com/pda/go6502/bus" 16 | ) 17 | 18 | // status register bits 19 | const ( 20 | sCarry = iota 21 | sZero 22 | sInterrupt 23 | sDecimal 24 | sBreak 25 | _ 26 | sOverflow 27 | sNegative 28 | ) 29 | 30 | // StackBase is the base address of the stack, which begins at StackBase+0xFF 31 | // and grows downwards towards this address. 32 | const StackBase = 0x0100 33 | 34 | // Cpu represents the internal state of the CPU. 35 | type Cpu struct { 36 | 37 | // Program counter. 38 | PC uint16 39 | 40 | // Accumulator register. 41 | AC byte 42 | 43 | // X general purpose / index register. 44 | X byte 45 | 46 | // Y general purpose / index register. 47 | Y byte 48 | 49 | // Stack pointer (low byte of 0x0100..0x01FF). 50 | SP byte 51 | 52 | // Status register; carry, zero, interrupt, bcd, brk, _, overflow, sign. 53 | SR byte 54 | 55 | // Bus is the system address bus, mapping 64K of address space to 56 | // different back-end devices. 57 | Bus *bus.Bus 58 | 59 | monitor Monitor 60 | ExitChan chan int 61 | } 62 | 63 | // A Monitor is a blocking observer of instruction execution. 64 | type Monitor interface { 65 | BeforeExecute(Instruction) 66 | Shutdown() 67 | } 68 | 69 | // AttachMonitor sets the given Monitor to observe instructions before they 70 | // execute, in a blocking manner. This allows for logging, analysis, and 71 | // interactive debugging. 72 | func (c *Cpu) AttachMonitor(m Monitor) { 73 | c.monitor = m 74 | } 75 | 76 | // Shutdown tells the CPU to shut-down, and to pass the message on 77 | // to subordinates such as the address bus. 78 | func (c *Cpu) Shutdown() { 79 | c.Bus.Shutdown() 80 | if c.monitor != nil { 81 | c.monitor.Shutdown() 82 | } 83 | } 84 | 85 | // Reset the CPU, emulating triggering the RESB line. 86 | // From 65C02 manual: All Registers are initialized by software except the 87 | // Decimal and Interrupt disable mode select bits of the Processor Status 88 | // Register (P) are initialized by hardware. ... The program counter is loaded 89 | // with the reset vector from locations FFFC (low byte) and FFFD (high byte). 90 | func (c *Cpu) Reset() { 91 | c.PC = c.Bus.Read16(0xFFFC) 92 | c.SR = 0x34 // Manual says xx1101xx, this sets 00110100. 93 | } 94 | 95 | func (c *Cpu) Step() { 96 | in := ReadInstruction(c.PC, c.Bus) 97 | if c.monitor != nil { 98 | c.monitor.BeforeExecute(in) 99 | } 100 | c.PC += uint16(in.Bytes) 101 | c.execute(in) 102 | } 103 | 104 | func (c *Cpu) String() string { 105 | return fmt.Sprintf( 106 | "CPU PC:0x%04X AC:0x%02X X:0x%02X Y:0x%02X SP:0x%02X SR:%s", 107 | c.PC, c.AC, c.X, c.Y, c.SP, 108 | c.statusString(), 109 | ) 110 | } 111 | 112 | func (c *Cpu) stackHead(offset int8) uint16 { 113 | return uint16(StackBase) + uint16(c.SP) + uint16(offset) 114 | } 115 | 116 | func (c *Cpu) resolveOperand(in Instruction) uint8 { 117 | switch in.addressing { 118 | case immediate: 119 | return in.Op8 120 | default: 121 | return c.Bus.Read(c.memoryAddress(in)) 122 | } 123 | } 124 | 125 | func (c *Cpu) memoryAddress(in Instruction) uint16 { 126 | switch in.addressing { 127 | case absolute: 128 | return in.Op16 129 | case absoluteX: 130 | return in.Op16 + uint16(c.X) 131 | case absoluteY: 132 | return in.Op16 + uint16(c.Y) 133 | 134 | // Indexed Indirect (X) 135 | // Operand is the zero-page location of a little-endian 16-bit base address. 136 | // The X register is added (wrapping; discarding overflow) before loading. 137 | // The resulting address loaded from (base+X) becomes the effective operand. 138 | // (base + X) must be in zero-page. 139 | case indirectX: 140 | location := uint16(in.Op8 + c.X) 141 | if location == 0xFF { 142 | panic("Indexed indirect high-byte not on zero page.") 143 | } 144 | return c.Bus.Read16(location) 145 | 146 | // Indirect Indexed (Y) 147 | // Operand is the zero-page location of a little-endian 16-bit address. 148 | // The address is loaded, and then the Y register is added to it. 149 | // The resulting loaded_address + Y becomes the effective operand. 150 | case indirectY: 151 | return c.Bus.Read16(uint16(in.Op8)) + uint16(c.Y) 152 | 153 | case zeropage: 154 | return uint16(in.Op8) 155 | case zeropageX: 156 | return uint16(in.Op8 + c.X) 157 | case zeropageY: 158 | return uint16(in.Op8 + c.Y) 159 | default: 160 | panic("unhandled addressing") 161 | } 162 | } 163 | 164 | func (c *Cpu) getStatus(bit uint8) bool { 165 | return c.getStatusInt(bit) == 1 166 | } 167 | 168 | func (c *Cpu) getStatusInt(bit uint8) uint8 { 169 | return (c.SR >> bit) & 1 170 | } 171 | 172 | func (c *Cpu) setStatus(bit uint8, state bool) { 173 | if state { 174 | c.SR |= 1 << bit 175 | } else { 176 | c.SR &^= 1 << bit 177 | } 178 | } 179 | 180 | func (c *Cpu) updateStatus(value uint8) { 181 | c.setStatus(sZero, value == 0) 182 | c.setStatus(sNegative, (value>>7) == 1) 183 | } 184 | 185 | func (c *Cpu) statusString() string { 186 | chars := "nv_bdizc" 187 | out := make([]string, 8) 188 | for i := 0; i < 8; i++ { 189 | if c.getStatus(uint8(7 - i)) { 190 | out[i] = string(chars[i]) 191 | } else { 192 | out[i] = "-" 193 | } 194 | } 195 | return strings.Join(out, "") 196 | } 197 | 198 | func (c *Cpu) branch(in Instruction) { 199 | relative := int8(in.Op8) // signed 200 | if relative >= 0 { 201 | c.PC += uint16(relative) 202 | } else { 203 | c.PC -= uint16(-relative) 204 | } 205 | } 206 | 207 | func (c *Cpu) execute(in Instruction) { 208 | switch in.id { 209 | case adc: 210 | c.ADC(in) 211 | case and: 212 | c.AND(in) 213 | case asl: 214 | c.ASL(in) 215 | case bcc: 216 | c.BCC(in) 217 | case bcs: 218 | c.BCS(in) 219 | case beq: 220 | c.BEQ(in) 221 | case bit: 222 | c.BIT(in) 223 | case bmi: 224 | c.BMI(in) 225 | case bne: 226 | c.BNE(in) 227 | case bpl: 228 | c.BPL(in) 229 | case brk: 230 | c.BRK(in) 231 | case clc: 232 | c.CLC(in) 233 | case cld: 234 | c.CLD(in) 235 | case cli: 236 | c.CLI(in) 237 | case cmp: 238 | c.CMP(in) 239 | case cpx: 240 | c.CPX(in) 241 | case cpy: 242 | c.CPY(in) 243 | case dec: 244 | c.DEC(in) 245 | case dex: 246 | c.DEX(in) 247 | case dey: 248 | c.DEY(in) 249 | case eor: 250 | c.EOR(in) 251 | case inc: 252 | c.INC(in) 253 | case inx: 254 | c.INX(in) 255 | case iny: 256 | c.INY(in) 257 | case jmp: 258 | c.JMP(in) 259 | case jsr: 260 | c.JSR(in) 261 | case lda: 262 | c.LDA(in) 263 | case ldx: 264 | c.LDX(in) 265 | case ldy: 266 | c.LDY(in) 267 | case lsr: 268 | c.LSR(in) 269 | case nop: 270 | c.NOP(in) 271 | case ora: 272 | c.ORA(in) 273 | case pha: 274 | c.PHA(in) 275 | case pla: 276 | c.PLA(in) 277 | case rol: 278 | c.ROL(in) 279 | case ror: 280 | c.ROR(in) 281 | case rts: 282 | c.RTS(in) 283 | case sbc: 284 | c.SBC(in) 285 | case sec: 286 | c.SEC(in) 287 | case sei: 288 | c.SEI(in) 289 | case sta: 290 | c.STA(in) 291 | case stx: 292 | c.STX(in) 293 | case sty: 294 | c.STY(in) 295 | case tax: 296 | c.TAX(in) 297 | case tay: 298 | c.TAY(in) 299 | case tsx: 300 | c.TSX(in) 301 | case txa: 302 | c.TXA(in) 303 | case txs: 304 | c.TXS(in) 305 | case tya: 306 | c.TYA(in) 307 | case _end: 308 | c._END(in) 309 | default: 310 | panic(fmt.Sprintf("unhandled instruction: %v", in)) 311 | } 312 | } 313 | 314 | // ADC: Add memory and carry to accumulator. 315 | func (c *Cpu) ADC(in Instruction) { 316 | value16 := uint16(c.AC) + uint16(c.resolveOperand(in)) + uint16(c.getStatusInt(sCarry)) 317 | c.setStatus(sCarry, value16 > 0xFF) 318 | c.AC = uint8(value16) 319 | c.updateStatus(c.AC) 320 | } 321 | 322 | // AND: And accumulator with memory. 323 | func (c *Cpu) AND(in Instruction) { 324 | c.AC &= c.resolveOperand(in) 325 | c.updateStatus(c.AC) 326 | } 327 | 328 | // ASL: Shift memory or accumulator left one bit. 329 | func (c *Cpu) ASL(in Instruction) { 330 | switch in.addressing { 331 | case accumulator: 332 | c.setStatus(sCarry, (c.AC>>7) == 1) // carry = old bit 7 333 | c.AC <<= 1 334 | c.updateStatus(c.AC) 335 | default: 336 | address := c.memoryAddress(in) 337 | value := c.Bus.Read(address) 338 | c.setStatus(sCarry, (value>>7) == 1) // carry = old bit 7 339 | value <<= 1 340 | c.Bus.Write(address, value) 341 | c.updateStatus(value) 342 | } 343 | } 344 | 345 | // BCC: Branch if carry clear. 346 | func (c *Cpu) BCC(in Instruction) { 347 | if !c.getStatus(sCarry) { 348 | c.branch(in) 349 | } 350 | } 351 | 352 | // BCS: Branch if carry set. 353 | func (c *Cpu) BCS(in Instruction) { 354 | if c.getStatus(sCarry) { 355 | c.branch(in) 356 | } 357 | } 358 | 359 | // BEQ: Branch if equal (z=1). 360 | func (c *Cpu) BEQ(in Instruction) { 361 | if c.getStatus(sZero) { 362 | c.branch(in) 363 | } 364 | } 365 | 366 | // BIT: Bit Test. 367 | func (c *Cpu) BIT(in Instruction) { 368 | value := c.resolveOperand(in) 369 | c.setStatus(sZero, value&c.AC == 0) 370 | c.setStatus(sOverflow, value&(1<<6) != 0) 371 | c.setStatus(sNegative, value&(1<<7) != 0) 372 | } 373 | 374 | // BMI: Branch if negative. 375 | func (c *Cpu) BMI(in Instruction) { 376 | if c.getStatus(sNegative) { 377 | c.branch(in) 378 | } 379 | } 380 | 381 | // BNE: Branch if not equal. 382 | func (c *Cpu) BNE(in Instruction) { 383 | if !c.getStatus(sZero) { 384 | c.branch(in) 385 | } 386 | } 387 | 388 | // BPL: Branch if positive. 389 | func (c *Cpu) BPL(in Instruction) { 390 | if !c.getStatus(sNegative) { 391 | c.branch(in) 392 | } 393 | } 394 | 395 | // BRK: software interrupt 396 | func (c *Cpu) BRK(in Instruction) { 397 | // temporarily used to dump status 398 | fmt.Println("BRK:", c) 399 | } 400 | 401 | // CLC: Clear carry flag. 402 | func (c *Cpu) CLC(in Instruction) { 403 | c.setStatus(sCarry, false) 404 | } 405 | 406 | // CLD: Clear decimal mode flag. 407 | func (c *Cpu) CLD(in Instruction) { 408 | c.setStatus(sDecimal, false) 409 | } 410 | 411 | // CLI: Clear interrupt-disable flag. 412 | func (c *Cpu) CLI(in Instruction) { 413 | c.setStatus(sInterrupt, true) 414 | } 415 | 416 | // CMP: Compare accumulator with memory. 417 | func (c *Cpu) CMP(in Instruction) { 418 | value := c.resolveOperand(in) 419 | c.setStatus(sCarry, c.AC >= value) 420 | c.updateStatus(c.AC - value) 421 | } 422 | 423 | // CPX: Compare index register X with memory. 424 | func (c *Cpu) CPX(in Instruction) { 425 | value := c.resolveOperand(in) 426 | c.setStatus(sCarry, c.X >= value) 427 | c.updateStatus(c.X - value) 428 | } 429 | 430 | // CPY: Compare index register Y with memory. 431 | func (c *Cpu) CPY(in Instruction) { 432 | value := c.resolveOperand(in) 433 | c.setStatus(sCarry, c.Y >= value) 434 | c.updateStatus(c.Y - value) 435 | } 436 | 437 | // DEC: Decrement. 438 | func (c *Cpu) DEC(in Instruction) { 439 | address := c.memoryAddress(in) 440 | value := c.Bus.Read(address) - 1 441 | c.Bus.Write(address, value) 442 | c.updateStatus(value) 443 | } 444 | 445 | // DEX: Decrement index register X. 446 | func (c *Cpu) DEX(in Instruction) { 447 | c.X-- 448 | c.updateStatus(c.X) 449 | } 450 | 451 | // DEY: Decrement index register Y. 452 | func (c *Cpu) DEY(in Instruction) { 453 | c.Y-- 454 | c.updateStatus(c.Y) 455 | } 456 | 457 | // EOR: Exclusive-OR accumulator with memory. 458 | func (c *Cpu) EOR(in Instruction) { 459 | value := c.resolveOperand(in) 460 | c.AC ^= value 461 | c.updateStatus(c.AC) 462 | } 463 | 464 | // INC: Increment. 465 | func (c *Cpu) INC(in Instruction) { 466 | address := c.memoryAddress(in) 467 | value := c.Bus.Read(address) + 1 468 | c.Bus.Write(address, value) 469 | c.updateStatus(value) 470 | } 471 | 472 | // INX: Increment index register X. 473 | func (c *Cpu) INX(in Instruction) { 474 | c.X++ 475 | c.updateStatus(c.X) 476 | } 477 | 478 | // INY: Increment index register Y. 479 | func (c *Cpu) INY(in Instruction) { 480 | c.Y++ 481 | c.updateStatus(c.Y) 482 | } 483 | 484 | // JMP: Jump. 485 | func (c *Cpu) JMP(in Instruction) { 486 | c.PC = c.memoryAddress(in) 487 | } 488 | 489 | // JSR: Jump to subroutine. 490 | func (c *Cpu) JSR(in Instruction) { 491 | c.Bus.Write16(c.stackHead(-1), c.PC-1) 492 | c.SP -= 2 493 | c.PC = in.Op16 494 | } 495 | 496 | // LDA: Load accumulator from memory. 497 | func (c *Cpu) LDA(in Instruction) { 498 | c.AC = c.resolveOperand(in) 499 | c.updateStatus(c.AC) 500 | } 501 | 502 | // LDX: Load index register X from memory. 503 | func (c *Cpu) LDX(in Instruction) { 504 | c.X = c.resolveOperand(in) 505 | c.updateStatus(c.X) 506 | } 507 | 508 | // LDY: Load index register Y from memory. 509 | func (c *Cpu) LDY(in Instruction) { 510 | c.Y = c.resolveOperand(in) 511 | c.updateStatus(c.Y) 512 | } 513 | 514 | // LSR: Logical shift memory or accumulator right. 515 | func (c *Cpu) LSR(in Instruction) { 516 | switch in.addressing { 517 | case accumulator: 518 | c.setStatus(sCarry, c.AC&1 == 1) 519 | c.AC >>= 1 520 | c.updateStatus(c.AC) 521 | default: 522 | address := c.memoryAddress(in) 523 | value := c.Bus.Read(address) 524 | c.setStatus(sCarry, value&1 == 1) 525 | value >>= 1 526 | c.Bus.Write(address, value) 527 | c.updateStatus(value) 528 | } 529 | } 530 | 531 | // NOP: No operation. 532 | func (c *Cpu) NOP(in Instruction) { 533 | } 534 | 535 | // ORA: OR accumulator with memory. 536 | func (c *Cpu) ORA(in Instruction) { 537 | c.AC |= c.resolveOperand(in) 538 | c.updateStatus(c.AC) 539 | } 540 | 541 | // PHA: Push accumulator onto stack. 542 | func (c *Cpu) PHA(in Instruction) { 543 | c.Bus.Write(0x0100+uint16(c.SP), c.AC) 544 | c.SP-- 545 | } 546 | 547 | // PLA: Pull accumulator from stack. 548 | func (c *Cpu) PLA(in Instruction) { 549 | c.SP++ 550 | c.AC = c.Bus.Read(0x0100 + uint16(c.SP)) 551 | } 552 | 553 | // ROL: Rotate memory or accumulator left one bit. 554 | func (c *Cpu) ROL(in Instruction) { 555 | carry := c.getStatusInt(sCarry) 556 | switch in.addressing { 557 | case accumulator: 558 | c.setStatus(sCarry, c.AC>>7 == 1) 559 | c.AC = c.AC<<1 | carry 560 | c.updateStatus(c.AC) 561 | default: 562 | address := c.memoryAddress(in) 563 | value := c.Bus.Read(address) 564 | c.setStatus(sCarry, value>>7 == 1) 565 | value = value<<1 | carry 566 | c.Bus.Write(address, value) 567 | c.updateStatus(value) 568 | } 569 | } 570 | 571 | // ROR: Rotate memory or accumulator left one bit. 572 | func (c *Cpu) ROR(in Instruction) { 573 | carry := c.getStatusInt(sCarry) 574 | switch in.addressing { 575 | case accumulator: 576 | c.setStatus(sCarry, c.AC&1 == 1) 577 | c.AC = c.AC>>1 | carry<<7 578 | c.updateStatus(c.AC) 579 | default: 580 | address := c.memoryAddress(in) 581 | value := c.Bus.Read(address) 582 | c.setStatus(sCarry, value&1 == 1) 583 | value = value>>1 | carry<<7 584 | c.Bus.Write(address, value) 585 | c.updateStatus(value) 586 | } 587 | } 588 | 589 | // RTS: Return from subroutine. 590 | func (c *Cpu) RTS(in Instruction) { 591 | c.PC = c.Bus.Read16(c.stackHead(1)) 592 | c.SP += 2 593 | c.PC += 1 594 | } 595 | 596 | // SBC: Subtract memory with borrow from accumulator. 597 | func (c *Cpu) SBC(in Instruction) { 598 | valueSigned := int16(c.AC) - int16(c.resolveOperand(in)) 599 | if !c.getStatus(sCarry) { 600 | valueSigned-- 601 | } 602 | c.AC = uint8(valueSigned) 603 | 604 | // v: Set if signed overflow; cleared if valid sign result. 605 | // TODO: c.setStatus(sOverflow, something) 606 | 607 | // c: Set if unsigned borrow not required; cleared if unsigned borrow. 608 | c.setStatus(sCarry, valueSigned >= 0) 609 | 610 | // n: Set if most significant bit of result is set; else cleared. 611 | // z: Set if result is zero; else cleared. 612 | c.updateStatus(c.AC) 613 | } 614 | 615 | // SEC: Set carry flag. 616 | func (c *Cpu) SEC(in Instruction) { 617 | c.setStatus(sCarry, true) 618 | } 619 | 620 | // SEI: Set interrupt-disable flag. 621 | func (c *Cpu) SEI(in Instruction) { 622 | c.setStatus(sInterrupt, false) 623 | } 624 | 625 | // STA: Store accumulator to memory. 626 | func (c *Cpu) STA(in Instruction) { 627 | c.Bus.Write(c.memoryAddress(in), c.AC) 628 | } 629 | 630 | // STX: Store index register X to memory. 631 | func (c *Cpu) STX(in Instruction) { 632 | c.Bus.Write(c.memoryAddress(in), c.X) 633 | } 634 | 635 | // STY: Store index register Y to memory. 636 | func (c *Cpu) STY(in Instruction) { 637 | c.Bus.Write(c.memoryAddress(in), c.Y) 638 | } 639 | 640 | // TAX: Transfer accumulator to index register X. 641 | func (c *Cpu) TAX(in Instruction) { 642 | c.X = c.AC 643 | c.updateStatus(c.X) 644 | } 645 | 646 | // TAY: Transfer accumulator to index register Y. 647 | func (c *Cpu) TAY(in Instruction) { 648 | c.Y = c.AC 649 | c.updateStatus(c.Y) 650 | } 651 | 652 | // TSX: Transfer stack pointer to index register X. 653 | func (c *Cpu) TSX(in Instruction) { 654 | c.X = c.SP 655 | c.updateStatus(c.X) 656 | } 657 | 658 | // TXA: Transfer index register X to accumulator. 659 | func (c *Cpu) TXA(in Instruction) { 660 | c.AC = c.X 661 | c.updateStatus(c.AC) 662 | } 663 | 664 | // TXS: Transfer index register X to stack pointer. 665 | func (c *Cpu) TXS(in Instruction) { 666 | c.SP = c.X 667 | c.updateStatus(c.SP) 668 | } 669 | 670 | // TYA: Transfer index register Y to accumulator. 671 | func (c *Cpu) TYA(in Instruction) { 672 | c.AC = c.Y 673 | c.updateStatus(c.AC) 674 | } 675 | 676 | // _END: Custom go6502 instruction. 677 | // Exit, with contents of X register as exit status. 678 | func (c *Cpu) _END(in Instruction) { 679 | c.ExitChan <- int(c.X) 680 | } 681 | -------------------------------------------------------------------------------- /cpu/cpu_test.go: -------------------------------------------------------------------------------- 1 | package cpu 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/pda/go6502/bus" 8 | "github.com/pda/go6502/memory" 9 | ) 10 | 11 | func createCpu() *Cpu { 12 | ram := &memory.Ram{} 13 | addressBus, _ := bus.CreateBus() 14 | addressBus.Attach(ram, "ram", 0x8000) // upper 32K 15 | cpu := &Cpu{Bus: addressBus} 16 | cpu.Reset() 17 | return cpu 18 | } 19 | 20 | func TestBitInstruction(t *testing.T) { 21 | cpu := createCpu() 22 | cpu.Bus.Write(0x8000, 0xAA) 23 | 24 | instruction := Instruction{OpType: optypes[0x2C], Op16: 0x8000} 25 | expectedName := "BIT absolute $8000" 26 | actualName := instruction.String() 27 | if actualName != expectedName { 28 | t.Error(fmt.Sprintf("expected %s, got %s\n", expectedName, actualName)) 29 | } 30 | 31 | cpu.BIT(instruction) 32 | 33 | expectedStatus := "n-_b-iz-" 34 | actualStatus := cpu.statusString() 35 | if actualStatus != expectedStatus { 36 | t.Error(fmt.Sprintf("SR expected %s got %s\n", expectedStatus, actualStatus)) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /cpu/instruction.go: -------------------------------------------------------------------------------- 1 | package cpu 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/pda/go6502/bus" 7 | ) 8 | 9 | // Instruction is an OpType plus its operand. 10 | // One or both of the operand types will be zero. 11 | // This is determined by (ot.Bytes - 1) / 8 12 | type Instruction struct { 13 | OpType 14 | 15 | // The single-byte operand, for 2-byte instructions. 16 | Op8 uint8 17 | 18 | // The 16-bit operand, for 3-byte instructions. 19 | Op16 uint16 20 | } 21 | 22 | func (in Instruction) String() (s string) { 23 | switch in.Bytes { 24 | case 3: 25 | s = fmt.Sprintf("%v $%04X", in.OpType, in.Op16) 26 | case 2: 27 | s = fmt.Sprintf("%v $%02X", in.OpType, in.Op8) 28 | case 1: 29 | s = in.OpType.String() 30 | } 31 | return 32 | } 33 | 34 | // ReadInstruction reads an instruction from the bus starting at the given 35 | // address. An instruction may be 1, 2 or 3 bytes long, including its optional 36 | // 8 or 16 bit operand. 37 | func ReadInstruction(pc uint16, bus *bus.Bus) Instruction { 38 | opcode := bus.Read(pc) 39 | optype, ok := optypes[opcode] 40 | if !ok { 41 | panic(fmt.Sprintf("Illegal opcode $%02X at $%04X", opcode, pc)) 42 | } 43 | in := Instruction{OpType: optype} 44 | switch in.Bytes { 45 | case 1: // no operand 46 | case 2: 47 | in.Op8 = bus.Read(pc + 1) 48 | case 3: 49 | in.Op16 = bus.Read16(pc + 1) 50 | default: 51 | panic(fmt.Sprintf("unhandled instruction length: %d", in.Bytes)) 52 | } 53 | return in 54 | } 55 | -------------------------------------------------------------------------------- /cpu/op_type.go: -------------------------------------------------------------------------------- 1 | package cpu 2 | 3 | import "fmt" 4 | 5 | // addressing modes 6 | const ( 7 | _ = iota 8 | absolute 9 | absoluteX 10 | absoluteY 11 | accumulator 12 | immediate 13 | implied 14 | indirect 15 | indirectX 16 | indirectY 17 | relative 18 | zeropage 19 | zeropageX 20 | zeropageY 21 | ) 22 | 23 | var addressingNames = [...]string{ 24 | "", 25 | "absolute", 26 | "absoluteX", 27 | "absoluteY", 28 | "accumulator", 29 | "immediate", 30 | "implied", 31 | "(indirect)", 32 | "(indirect,X)", 33 | "(indirect),Y", 34 | "relative", 35 | "zeropage", 36 | "zeropageX", 37 | "zeropageY", 38 | } 39 | 40 | // adc..tya represent the 6502 instruction set mnemonics. Each mnemonic maps to 41 | // a number of different opcodes, depending on the addressing mode. 42 | const ( 43 | _ = iota 44 | adc 45 | and 46 | asl 47 | bcc 48 | bcs 49 | beq 50 | bit 51 | bmi 52 | bne 53 | bpl 54 | brk 55 | bvc 56 | bvs 57 | clc 58 | cld 59 | cli 60 | clv 61 | cmp 62 | cpx 63 | cpy 64 | dec 65 | dex 66 | dey 67 | eor 68 | inc 69 | inx 70 | iny 71 | jmp 72 | jsr 73 | lda 74 | ldx 75 | ldy 76 | lsr 77 | nop 78 | ora 79 | pha 80 | php 81 | pla 82 | plp 83 | rol 84 | ror 85 | rti 86 | rts 87 | sbc 88 | sec 89 | sed 90 | sei 91 | sta 92 | stx 93 | sty 94 | tax 95 | tay 96 | tsx 97 | txa 98 | txs 99 | tya 100 | _end 101 | ) 102 | 103 | var instructionNames = [...]string{ 104 | "", 105 | "ADC", 106 | "AND", 107 | "ASL", 108 | "BCC", 109 | "BCS", 110 | "BEQ", 111 | "BIT", 112 | "BMI", 113 | "BNE", 114 | "BPL", 115 | "BRK", 116 | "BVC", 117 | "BVS", 118 | "CLC", 119 | "CLD", 120 | "CLI", 121 | "CLV", 122 | "CMP", 123 | "CPX", 124 | "CPY", 125 | "DEC", 126 | "DEX", 127 | "DEY", 128 | "EOR", 129 | "INC", 130 | "INX", 131 | "INY", 132 | "JMP", 133 | "JSR", 134 | "LDA", 135 | "LDX", 136 | "LDY", 137 | "LSR", 138 | "NOP", 139 | "ORA", 140 | "PHA", 141 | "PHP", 142 | "PLA", 143 | "PLP", 144 | "ROL", 145 | "ROR", 146 | "RTI", 147 | "RTS", 148 | "SBC", 149 | "SEC", 150 | "SED", 151 | "SEI", 152 | "STA", 153 | "STX", 154 | "STY", 155 | "TAX", 156 | "TAY", 157 | "TSX", 158 | "TXA", 159 | "TXS", 160 | "TYA", 161 | "_END", 162 | } 163 | 164 | // OpType represents a 6502 op-code instruction, including the addressing 165 | // mode encoded into the op-code, but not the operand value following the 166 | // opcode in memory. 167 | type OpType struct { 168 | 169 | // Opcode is a byte representing an instruction and its addressing mode. 170 | Opcode byte 171 | 172 | // id is an internal identifier of the instruction type/mnemonic, e.g. ADC 173 | id uint8 174 | 175 | // addressing is an internal identifier for the addressing mode. 176 | addressing uint8 177 | 178 | // Bytes is the size of the instruction with its operand. 179 | // Opcodes with implicit/null operand are 1 byte. 180 | // Opcodes with immediate or zeropage operand are 2 bytes. 181 | // Opcodes with adddress operands are 3 bytes. 182 | Bytes uint8 183 | 184 | // Cycles is the number of times the system clock signal will rise and fall 185 | // before the instruction is complete. 186 | Cycles uint8 187 | } 188 | 189 | func (ot OpType) String() string { 190 | return fmt.Sprintf("%s %s", ot.Name(), addressingNames[ot.addressing]) 191 | } 192 | 193 | // Name returns the instruction mnemonic name, e.g. ADC or TYA. 194 | func (ot OpType) Name() (s string) { 195 | return instructionNames[ot.id] 196 | } 197 | 198 | func (ot OpType) IsAbsolute() bool { 199 | return ot.addressing == absolute 200 | } 201 | 202 | var optypes = map[uint8]OpType{ 203 | 0x69: OpType{0x69, adc, immediate, 2, 2}, 204 | 0x65: OpType{0x65, adc, zeropage, 2, 3}, 205 | 0x75: OpType{0x75, adc, zeropageX, 2, 4}, 206 | 0x6D: OpType{0x6D, adc, absolute, 3, 4}, 207 | 0x7D: OpType{0x7D, adc, absoluteX, 3, 4}, 208 | 0x79: OpType{0x79, adc, absoluteY, 3, 4}, 209 | 0x61: OpType{0x61, adc, indirectX, 2, 6}, 210 | 0x71: OpType{0x71, adc, indirectY, 2, 5}, 211 | 0x29: OpType{0x29, and, immediate, 2, 2}, 212 | 0x25: OpType{0x25, and, zeropage, 2, 3}, 213 | 0x35: OpType{0x35, and, zeropageX, 2, 4}, 214 | 0x2D: OpType{0x2D, and, absolute, 3, 4}, 215 | 0x3D: OpType{0x3D, and, absoluteX, 3, 4}, 216 | 0x39: OpType{0x39, and, absoluteY, 3, 4}, 217 | 0x21: OpType{0x21, and, indirectX, 2, 6}, 218 | 0x31: OpType{0x31, and, indirectY, 2, 5}, 219 | 0x0A: OpType{0x0A, asl, accumulator, 1, 2}, 220 | 0x06: OpType{0x06, asl, zeropage, 2, 5}, 221 | 0x16: OpType{0x16, asl, zeropageX, 2, 6}, 222 | 0x0E: OpType{0x0E, asl, absolute, 3, 6}, 223 | 0x1E: OpType{0x1E, asl, absoluteX, 3, 7}, 224 | 0x90: OpType{0x90, bcc, relative, 2, 2}, 225 | 0xB0: OpType{0xB0, bcs, relative, 2, 2}, 226 | 0xF0: OpType{0xF0, beq, relative, 2, 2}, 227 | 0x24: OpType{0x24, bit, zeropage, 2, 3}, 228 | 0x2C: OpType{0x2C, bit, absolute, 3, 4}, 229 | 0x30: OpType{0x30, bmi, relative, 2, 2}, 230 | 0xD0: OpType{0xD0, bne, relative, 2, 2}, 231 | 0x10: OpType{0x10, bpl, relative, 2, 2}, 232 | 0x00: OpType{0x00, brk, implied, 1, 7}, 233 | 0x50: OpType{0x50, bvc, relative, 2, 2}, 234 | 0x70: OpType{0x70, bvs, relative, 2, 2}, 235 | 0x18: OpType{0x18, clc, implied, 1, 2}, 236 | 0xD8: OpType{0xD8, cld, implied, 1, 2}, 237 | 0x58: OpType{0x58, cli, implied, 1, 2}, 238 | 0xB8: OpType{0xB8, clv, implied, 1, 2}, 239 | 0xC9: OpType{0xC9, cmp, immediate, 2, 2}, 240 | 0xC5: OpType{0xC5, cmp, zeropage, 2, 3}, 241 | 0xD5: OpType{0xD5, cmp, zeropageX, 2, 4}, 242 | 0xCD: OpType{0xCD, cmp, absolute, 3, 4}, 243 | 0xDD: OpType{0xDD, cmp, absoluteX, 3, 4}, 244 | 0xD9: OpType{0xD9, cmp, absoluteY, 3, 4}, 245 | 0xC1: OpType{0xC1, cmp, indirectX, 2, 6}, 246 | 0xD1: OpType{0xD1, cmp, indirectY, 2, 5}, 247 | 0xE0: OpType{0xE0, cpx, immediate, 2, 2}, 248 | 0xE4: OpType{0xE4, cpx, zeropage, 2, 3}, 249 | 0xEC: OpType{0xEC, cpx, absolute, 3, 4}, 250 | 0xC0: OpType{0xC0, cpy, immediate, 2, 2}, 251 | 0xC4: OpType{0xC4, cpy, zeropage, 2, 3}, 252 | 0xCC: OpType{0xCC, cpy, absolute, 3, 4}, 253 | 0xC6: OpType{0xC6, dec, zeropage, 2, 5}, 254 | 0xD6: OpType{0xD6, dec, zeropageX, 2, 6}, 255 | 0xCE: OpType{0xCE, dec, absolute, 3, 3}, 256 | 0xDE: OpType{0xDE, dec, absoluteX, 3, 7}, 257 | 0xCA: OpType{0xCA, dex, implied, 1, 2}, 258 | 0x88: OpType{0x88, dey, implied, 1, 2}, 259 | 0x49: OpType{0x49, eor, immediate, 2, 2}, 260 | 0x45: OpType{0x45, eor, zeropage, 2, 3}, 261 | 0x55: OpType{0x55, eor, zeropageX, 2, 4}, 262 | 0x4D: OpType{0x4D, eor, absolute, 3, 4}, 263 | 0x5D: OpType{0x5D, eor, absoluteX, 3, 4}, 264 | 0x59: OpType{0x59, eor, absoluteY, 3, 4}, 265 | 0x41: OpType{0x41, eor, indirectX, 2, 6}, 266 | 0x51: OpType{0x51, eor, indirectY, 2, 5}, 267 | 0xE6: OpType{0xE6, inc, zeropage, 2, 5}, 268 | 0xF6: OpType{0xF6, inc, zeropageX, 2, 6}, 269 | 0xEE: OpType{0xEE, inc, absolute, 3, 6}, 270 | 0xFE: OpType{0xFE, inc, absoluteX, 3, 7}, 271 | 0xE8: OpType{0xE8, inx, implied, 1, 2}, 272 | 0xC8: OpType{0xC8, iny, implied, 1, 2}, 273 | 0x4C: OpType{0x4C, jmp, absolute, 3, 3}, 274 | 0x6C: OpType{0x6C, jmp, indirect, 3, 5}, 275 | 0x20: OpType{0x20, jsr, absolute, 3, 6}, 276 | 0xA9: OpType{0xA9, lda, immediate, 2, 2}, 277 | 0xA5: OpType{0xA5, lda, zeropage, 2, 3}, 278 | 0xB5: OpType{0xB5, lda, zeropageX, 2, 4}, 279 | 0xAD: OpType{0xAD, lda, absolute, 3, 4}, 280 | 0xBD: OpType{0xBD, lda, absoluteX, 3, 4}, 281 | 0xB9: OpType{0xB9, lda, absoluteY, 3, 4}, 282 | 0xA1: OpType{0xA1, lda, indirectX, 2, 6}, 283 | 0xB1: OpType{0xB1, lda, indirectY, 2, 5}, 284 | 0xA2: OpType{0xA2, ldx, immediate, 2, 2}, 285 | 0xA6: OpType{0xA6, ldx, zeropage, 2, 3}, 286 | 0xB6: OpType{0xB6, ldx, zeropageY, 2, 4}, 287 | 0xAE: OpType{0xAE, ldx, absolute, 3, 4}, 288 | 0xBE: OpType{0xBE, ldx, absoluteY, 3, 4}, 289 | 0xA0: OpType{0xA0, ldy, immediate, 2, 2}, 290 | 0xA4: OpType{0xA4, ldy, zeropage, 2, 3}, 291 | 0xB4: OpType{0xB4, ldy, zeropageX, 2, 4}, 292 | 0xAC: OpType{0xAC, ldy, absolute, 3, 4}, 293 | 0xBC: OpType{0xBC, ldy, absoluteX, 3, 4}, 294 | 0x4A: OpType{0x4A, lsr, accumulator, 1, 2}, 295 | 0x46: OpType{0x46, lsr, zeropage, 2, 5}, 296 | 0x56: OpType{0x56, lsr, zeropageX, 2, 6}, 297 | 0x4E: OpType{0x4E, lsr, absolute, 3, 6}, 298 | 0x5E: OpType{0x5E, lsr, absoluteX, 3, 7}, 299 | 0xEA: OpType{0xEA, nop, implied, 1, 2}, 300 | 0x09: OpType{0x09, ora, immediate, 2, 2}, 301 | 0x05: OpType{0x05, ora, zeropage, 2, 3}, 302 | 0x15: OpType{0x15, ora, zeropageX, 2, 4}, 303 | 0x0D: OpType{0x0D, ora, absolute, 3, 4}, 304 | 0x1D: OpType{0x1D, ora, absoluteX, 3, 4}, 305 | 0x19: OpType{0x19, ora, absoluteY, 3, 4}, 306 | 0x01: OpType{0x01, ora, indirectX, 2, 6}, 307 | 0x11: OpType{0x11, ora, indirectY, 2, 5}, 308 | 0x48: OpType{0x48, pha, implied, 1, 3}, 309 | 0x08: OpType{0x08, php, implied, 1, 3}, 310 | 0x68: OpType{0x68, pla, implied, 1, 4}, 311 | 0x28: OpType{0x28, php, implied, 1, 4}, 312 | 0x2A: OpType{0x2A, rol, accumulator, 1, 2}, 313 | 0x26: OpType{0x26, rol, zeropage, 2, 5}, 314 | 0x36: OpType{0x36, rol, zeropageX, 2, 6}, 315 | 0x2E: OpType{0x2E, rol, absolute, 3, 6}, 316 | 0x3E: OpType{0x3E, rol, absoluteX, 3, 7}, 317 | 0x6A: OpType{0x6A, ror, accumulator, 1, 2}, 318 | 0x66: OpType{0x66, ror, zeropage, 2, 5}, 319 | 0x76: OpType{0x76, ror, zeropageX, 2, 6}, 320 | 0x6E: OpType{0x6E, ror, absolute, 3, 6}, 321 | 0x7E: OpType{0x7E, ror, absoluteX, 3, 7}, 322 | 0x40: OpType{0x40, rti, implied, 1, 6}, 323 | 0x60: OpType{0x60, rts, implied, 1, 6}, 324 | 0xE9: OpType{0xE9, sbc, immediate, 2, 2}, 325 | 0xE5: OpType{0xE5, sbc, zeropage, 2, 3}, 326 | 0xF5: OpType{0xF5, sbc, zeropageX, 2, 4}, 327 | 0xED: OpType{0xED, sbc, absolute, 3, 4}, 328 | 0xFD: OpType{0xFD, sbc, absoluteX, 3, 4}, 329 | 0xF9: OpType{0xF9, sbc, absoluteY, 3, 4}, 330 | 0xE1: OpType{0xE1, sbc, indirectX, 2, 6}, 331 | 0xF1: OpType{0xF1, sbc, indirectY, 2, 5}, 332 | 0x38: OpType{0x38, sec, implied, 1, 2}, 333 | 0xF8: OpType{0xF8, sed, implied, 1, 2}, 334 | 0x78: OpType{0x78, sei, implied, 1, 2}, 335 | 0x85: OpType{0x85, sta, zeropage, 2, 3}, 336 | 0x95: OpType{0x95, sta, zeropageX, 2, 4}, 337 | 0x8D: OpType{0x8D, sta, absolute, 3, 4}, 338 | 0x9D: OpType{0x9D, sta, absoluteX, 3, 5}, 339 | 0x99: OpType{0x99, sta, absoluteY, 3, 5}, 340 | 0x81: OpType{0x81, sta, indirectX, 2, 6}, 341 | 0x91: OpType{0x91, sta, indirectY, 2, 6}, 342 | 0x86: OpType{0x86, stx, zeropage, 2, 3}, 343 | 0x96: OpType{0x96, stx, zeropageY, 2, 4}, 344 | 0x8E: OpType{0x8E, stx, absolute, 3, 4}, 345 | 0x84: OpType{0x84, sty, zeropage, 2, 3}, 346 | 0x94: OpType{0x94, sty, zeropageX, 2, 4}, 347 | 0x8C: OpType{0x8C, sty, absolute, 3, 4}, 348 | 0xAA: OpType{0xAA, tax, implied, 1, 2}, 349 | 0xA8: OpType{0xA8, tay, implied, 1, 2}, 350 | 0xBA: OpType{0xBA, tsx, implied, 1, 2}, 351 | 0x8A: OpType{0x8A, txa, implied, 1, 2}, 352 | 0x9A: OpType{0x9A, txs, implied, 1, 2}, 353 | 0x98: OpType{0x98, tya, implied, 1, 2}, 354 | 0xFF: OpType{0xFF, _end, implied, 1, 1}, 355 | } 356 | -------------------------------------------------------------------------------- /debugger/debugger.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package debugger provides an interactive stepping debugger for go6502 with 3 | breakpoints on instruction type, register values and memory location. 4 | 5 | Example 6 | 7 | An example interactive debugging session: 8 | 9 | $ go run go6502.go --via-ssd1306 --debug 10 | CPU PC:0xF31F AC:0x00 X:0x00 Y:0x00 SP:0x00 SR:--_b-i-- 11 | Next: SEI implied 12 | $F31F> step 13 | CPU PC:0xF320 AC:0x00 X:0x00 Y:0x00 SP:0x00 SR:--_b---- 14 | Next: LDX immediate $FF 15 | $F320> break-register X $FF 16 | Breakpoint set: X = $FF (255) 17 | $F320> continue 18 | Breakpoint for X = $FF (255) 19 | CPU PC:0xF322 AC:0x00 X:0xFF Y:0x00 SP:0x00 SR:n-_b---- 20 | Next: TXS implied 21 | $F322> step 22 | Breakpoint for X = $FF (255) 23 | CPU PC:0xF323 AC:0x00 X:0xFF Y:0x00 SP:0xFF SR:n-_b---- 24 | Next: CLI implied 25 | $F323> 26 | Breakpoint for X = $FF (255) 27 | CPU PC:0xF324 AC:0x00 X:0xFF Y:0x00 SP:0xFF SR:n-_b-i-- 28 | Next: CLD implied 29 | $F324> 30 | Breakpoint for X = $FF (255) 31 | CPU PC:0xF325 AC:0x00 X:0xFF Y:0x00 SP:0xFF SR:n-_b-i-- 32 | Next: JMP absolute $F07B 33 | $F325> break-instruction nop 34 | $F325> r 35 | Breakpoint for X = $FF (255) 36 | CPU PC:0xF07B AC:0x00 X:0xFF Y:0x00 SP:0xFF SR:n-_b-i-- 37 | Next: LDA immediate $00 38 | $F07B> q 39 | */ 40 | package debugger 41 | 42 | /** 43 | * TODO: 44 | * - Command argument validation. 45 | * - Handle missing/multiple labels when entering address. 46 | * - Resolve addresses to symbols non-absolute instructions, e.g. branch. 47 | * - Tab completion from commands, not just debug symbols. 48 | * - `step n` e.g. `step 100` to step 100 instructions. 49 | * - Read and write CLI history file. 50 | */ 51 | 52 | import ( 53 | "fmt" 54 | "strconv" 55 | "strings" 56 | 57 | "github.com/pda/go6502/cpu" 58 | "github.com/peterh/liner" 59 | ) 60 | 61 | const ( 62 | debugCmdNone = iota 63 | debugCmdBreakAddress 64 | debugCmdBreakInstruction 65 | debugCmdBreakRegister 66 | debugCmdContinue 67 | debugCmdExit 68 | debugCmdHelp 69 | debugCmdInvalid 70 | debugCmdNext 71 | debugCmdRead 72 | debugCmdRead16 73 | debugCmdRead32 74 | debugCmdStep 75 | ) 76 | 77 | type Debugger struct { 78 | symbols debugSymbols 79 | inputQueue []string 80 | cpu *cpu.Cpu 81 | liner *liner.State 82 | lastCmd *cmd 83 | run bool 84 | breakAddress bool 85 | breakAddressValue uint16 86 | breakInstruction string 87 | breakRegA bool 88 | breakRegAValue byte 89 | breakRegX bool 90 | breakRegXValue byte 91 | breakRegY bool 92 | breakRegYValue byte 93 | } 94 | 95 | type cmd struct { 96 | id int 97 | input string 98 | arguments []string 99 | } 100 | 101 | // NewDebugger creates a debugger. 102 | // Be sure to defer a call to Debugger.Shutdown() afterwards, or your terminal 103 | // will be left in a broken state. 104 | func NewDebugger(cpu *cpu.Cpu, debugFile string) *Debugger { 105 | var symbols debugSymbols 106 | if len(debugFile) > 0 { 107 | var err error 108 | symbols, err = readDebugSymbols(debugFile) 109 | if err != nil { 110 | panic(err) 111 | } 112 | } 113 | 114 | liner := liner.NewLiner() 115 | liner.SetCompleter(linerCompleter(symbols)) 116 | 117 | return &Debugger{ 118 | liner: liner, 119 | cpu: cpu, 120 | symbols: symbols, 121 | } 122 | } 123 | 124 | // linerCompleter returns a tab-completion function for liner. 125 | func linerCompleter(symbols debugSymbols) func(string) []string { 126 | return func(line string) (c []string) { 127 | if len(line) == 0 { 128 | return 129 | } 130 | 131 | // find index of current word being typed. 132 | i := len(line) 133 | for i > 0 && line[i-1] != ' ' { 134 | i-- 135 | } 136 | prefix := line[:i] 137 | tail := line[i:] 138 | tailLower := strings.ToLower(tail) 139 | 140 | for _, l := range symbols.uniqueLabels() { 141 | if strings.HasPrefix(strings.ToLower(l), tailLower) { 142 | c = append(c, prefix+l) 143 | } 144 | } 145 | return 146 | } 147 | } 148 | 149 | // Shutdown the debugger session, including resetting the terminal to its previous 150 | // state. 151 | func (d *Debugger) Shutdown() { 152 | d.liner.Close() 153 | } 154 | 155 | // Queue a list of commands to be executed at the next prompt(s). 156 | // This is useful for accepting a list of commands as a CLI parameter. 157 | func (d *Debugger) QueueCommands(cmds []string) { 158 | d.inputQueue = append(d.inputQueue, cmds...) 159 | } 160 | 161 | func (d *Debugger) checkRegBreakpoint(regStr string, on bool, expect byte, actual byte) { 162 | if on && actual == expect { 163 | fmt.Printf("Breakpoint for %s = $%02X (%d)\n", regStr, expect, expect) 164 | d.run = false 165 | } 166 | } 167 | 168 | func (d *Debugger) doBreakpoints(in cpu.Instruction) { 169 | inName := in.Name() 170 | 171 | if inName == d.breakInstruction { 172 | fmt.Printf("Breakpoint for instruction %s\n", inName) 173 | d.run = false 174 | } 175 | 176 | if d.breakAddress && d.cpu.PC == d.breakAddressValue { 177 | fmt.Printf("Breakpoint for PC address = $%04X\n", d.breakAddressValue) 178 | d.run = false 179 | } 180 | 181 | d.checkRegBreakpoint("A", d.breakRegA, d.breakRegAValue, d.cpu.AC) 182 | d.checkRegBreakpoint("X", d.breakRegX, d.breakRegXValue, d.cpu.X) 183 | d.checkRegBreakpoint("Y", d.breakRegY, d.breakRegYValue, d.cpu.Y) 184 | } 185 | 186 | // BeforeExecute receives each cpu.Instruction just before the program 187 | // counter is incremented and the instruction executed. 188 | func (d *Debugger) BeforeExecute(in cpu.Instruction) { 189 | 190 | d.doBreakpoints(in) 191 | 192 | if d.run { 193 | return 194 | } 195 | 196 | fmt.Println(d.cpu) 197 | 198 | var symbols []string 199 | if in.IsAbsolute() { 200 | symbols = d.symbols.labelsFor(in.Op16) 201 | } 202 | 203 | if len(symbols) > 0 { 204 | fmt.Printf("Next: %v (%s)\n", in, strings.Join(symbols, ",")) 205 | } else { 206 | fmt.Println("Next:", in) 207 | } 208 | 209 | for !d.commandLoop(in) { 210 | // next 211 | } 212 | } 213 | 214 | // Returns true when control is to be released. 215 | func (d *Debugger) commandLoop(in cpu.Instruction) (release bool) { 216 | var ( 217 | cmd *cmd 218 | err error 219 | ) 220 | 221 | for cmd == nil && err == nil { 222 | cmd, err = d.getCommand() 223 | } 224 | if err != nil { 225 | panic(err) 226 | } 227 | 228 | switch cmd.id { 229 | case debugCmdBreakAddress: 230 | d.commandBreakAddress(cmd) 231 | case debugCmdBreakInstruction: 232 | d.breakInstruction = strings.ToUpper(cmd.arguments[0]) 233 | case debugCmdBreakRegister: 234 | d.commandBreakRegister(cmd) 235 | case debugCmdContinue: 236 | d.run = true 237 | release = true 238 | case debugCmdExit: 239 | d.cpu.ExitChan <- 0 240 | case debugCmdHelp: 241 | d.commandHelp(cmd) 242 | case debugCmdNext: 243 | d.commandNext(in) 244 | release = true 245 | case debugCmdNone: 246 | // pass 247 | case debugCmdRead: 248 | d.commandRead(cmd) 249 | case debugCmdRead16: 250 | d.commandRead16(cmd) 251 | case debugCmdRead32: 252 | d.commandRead32(cmd) 253 | case debugCmdStep: 254 | release = true 255 | case debugCmdInvalid: 256 | fmt.Println("Invalid command.") 257 | default: 258 | panic("Unknown command code.") 259 | } 260 | 261 | return 262 | } 263 | 264 | // Set a breakpoint for the address after the current instruction, then 265 | // continue execution. Steps over JSR, JMP etc. Probably doesn't do good 266 | // things for branch instructions. 267 | func (d *Debugger) commandNext(in cpu.Instruction) { 268 | addr := uint16(d.cpu.PC + uint16(in.Bytes)) 269 | d.breakAddress = true 270 | d.breakAddressValue = addr 271 | d.run = true 272 | } 273 | 274 | func (d *Debugger) commandRead(cmd *cmd) { 275 | addr, err := d.parseUint16(cmd.arguments[0]) 276 | if err != nil { 277 | panic(err) 278 | } 279 | v := d.cpu.Bus.Read(addr) 280 | fmt.Printf("$%04X => $%02X 0b%08b %d %q\n", addr, v, v, v, v) 281 | } 282 | 283 | func (d *Debugger) commandRead16(cmd *cmd) { 284 | addr, err := d.parseUint16(cmd.arguments[0]) 285 | if err != nil { 286 | panic(err) 287 | } 288 | addrLo := addr 289 | addrHi := addr + 1 290 | vLo := uint16(d.cpu.Bus.Read(addrLo)) 291 | vHi := uint16(d.cpu.Bus.Read(addrHi)) 292 | v := vHi<<8 | vLo 293 | fmt.Printf("$%04X,%04X => $%04X 0b%016b %d\n", addrLo, addrHi, v, v, v) 294 | } 295 | 296 | func (d *Debugger) commandRead32(cmd *cmd) { 297 | addr, err := d.parseUint16(cmd.arguments[0]) 298 | if err != nil { 299 | panic(err) 300 | } 301 | addr0 := addr 302 | addr1 := addr + 1 303 | addr2 := addr + 2 304 | addr3 := addr + 3 305 | v0 := uint32(d.cpu.Bus.Read(addr0)) 306 | v1 := uint32(d.cpu.Bus.Read(addr1)) 307 | v2 := uint32(d.cpu.Bus.Read(addr2)) 308 | v3 := uint32(d.cpu.Bus.Read(addr3)) 309 | v := v3<<24 | v2<<16 | v1<<8 | v0 310 | fmt.Printf("$%04X..%04X => $%08X 0b%032b %d\n", addr0, addr3, v, v, v) 311 | } 312 | 313 | func (d *Debugger) commandHelp(cmd *cmd) { 314 | fmt.Println("") 315 | fmt.Println("pda6502 debuger") 316 | fmt.Println("---------------") 317 | fmt.Println("break-address (alias: ba) e.g. ba 0x1000") 318 | fmt.Println("break-instruction (alias: bi) e.g. bi NOP") 319 | fmt.Println("break-register (alias: br) e.g. br x 128") 320 | fmt.Println("continue (alias: c) Run continuously until breakpoint.") 321 | fmt.Println("exit (alias: quit, q) Shut down the emulator.") 322 | fmt.Println("help (alias: h, ?) This help.") 323 | fmt.Println("next (alias: n) Next instruction; step over subroutines.") 324 | fmt.Println("read
- Read and display 8-bit integer at address.") 325 | fmt.Println("read16
- Read and display 16-bit integer at address.") 326 | fmt.Println("read32
- Read and display 32-bit integer at address.") 327 | fmt.Println("step (alias: s) Run only the current instruction.") 328 | fmt.Println("(blank) Repeat the previous command.") 329 | fmt.Println("") 330 | fmt.Println("Hex input formats: 0x1234 $1234") 331 | fmt.Println("Commands expecting uint16 treat . as current address (PC).") 332 | } 333 | 334 | func (d *Debugger) commandBreakAddress(cmd *cmd) { 335 | addr, err := d.parseUint16(cmd.arguments[0]) 336 | if err != nil { 337 | panic(err) 338 | } 339 | d.breakAddress = true 340 | d.breakAddressValue = addr 341 | fmt.Printf("break-address set to $%04X\n", addr) 342 | } 343 | 344 | func (d *Debugger) commandBreakRegister(cmd *cmd) { 345 | regStr := cmd.arguments[0] 346 | valueStr := cmd.arguments[1] 347 | 348 | var ptr *byte 349 | switch regStr { 350 | case "A", "a", "AC", "ac": 351 | d.breakRegA = true 352 | ptr = &d.breakRegAValue 353 | case "X", "x": 354 | d.breakRegX = true 355 | ptr = &d.breakRegXValue 356 | case "Y", "y": 357 | d.breakRegY = true 358 | ptr = &d.breakRegYValue 359 | default: 360 | panic(fmt.Errorf("Invalid register for break-register")) 361 | } 362 | 363 | value, err := d.parseUint8(valueStr) 364 | if err != nil { 365 | panic(err) 366 | } 367 | 368 | fmt.Printf("Breakpoint set: %s = $%02X (%d)\n", regStr, value, value) 369 | 370 | *ptr = value 371 | } 372 | 373 | func (d *Debugger) getCommand() (*cmd, error) { 374 | var ( 375 | id int 376 | cmdString string 377 | arguments []string 378 | c *cmd 379 | input string 380 | err error 381 | ) 382 | 383 | if len(d.inputQueue) > 0 { 384 | input = d.inputQueue[0] 385 | d.inputQueue = d.inputQueue[1:] 386 | fmt.Printf("%s%s\n", d.prompt(), input) 387 | } else { 388 | input, err = d.readInput() 389 | if err != nil { 390 | return nil, err 391 | } 392 | } 393 | 394 | fields := strings.Fields(input) 395 | 396 | if len(fields) >= 1 { 397 | cmdString = strings.ToLower(fields[0]) 398 | } 399 | if len(fields) >= 2 { 400 | arguments = fields[1:] 401 | } 402 | 403 | switch cmdString { 404 | case "": 405 | id = debugCmdNone 406 | case "break-address", "break-addr", "ba": 407 | id = debugCmdBreakAddress 408 | case "break-instruction", "bi": 409 | id = debugCmdBreakInstruction 410 | case "break-register", "break-reg", "br": 411 | id = debugCmdBreakRegister 412 | case "continue", "c": 413 | id = debugCmdContinue 414 | case "exit", "quit", "q": 415 | id = debugCmdExit 416 | case "help", "h", "?": 417 | id = debugCmdHelp 418 | case "next", "n": 419 | id = debugCmdNext 420 | case "read": 421 | id = debugCmdRead 422 | case "read16": 423 | id = debugCmdRead16 424 | case "read32": 425 | id = debugCmdRead32 426 | case "step", "st", "s": 427 | id = debugCmdStep 428 | default: 429 | id = debugCmdInvalid 430 | } 431 | 432 | if id == debugCmdNone && d.lastCmd != nil { 433 | c = d.lastCmd 434 | } else { 435 | c = &cmd{id, input, arguments} 436 | d.lastCmd = c 437 | } 438 | 439 | return c, nil 440 | } 441 | 442 | func (d *Debugger) readInput() (string, error) { 443 | input, err := d.liner.Prompt(d.prompt()) 444 | if err != nil { 445 | return "", err 446 | } 447 | d.liner.AppendHistory(input) 448 | return input, nil 449 | } 450 | 451 | func (d *Debugger) prompt() string { 452 | symbols := strings.Join(d.symbols.labelsFor(d.cpu.PC), ",") 453 | return fmt.Sprintf("$%04X %s> ", d.cpu.PC, symbols) 454 | } 455 | 456 | func (d *Debugger) parseUint8(s string) (uint8, error) { 457 | s = strings.Replace(s, "$", "0x", 1) 458 | result, err := strconv.ParseUint(s, 0, 8) 459 | return uint8(result), err 460 | } 461 | 462 | func (d *Debugger) parseUint16(s string) (uint16, error) { 463 | if s == "." { 464 | return d.cpu.PC, nil 465 | } 466 | 467 | addresses := d.symbols.addressesFor(s) 468 | if len(addresses) == 1 { 469 | return addresses[0], nil 470 | } else if len(addresses) > 1 { 471 | // TODO: show addresses as hex, not dec, in error. 472 | // TODO: include addresses as []uint16 in error. 473 | return 0, fmt.Errorf("Multiple addresses for %s: %v", s, addresses) 474 | } 475 | 476 | s = strings.Replace(s, "$", "0x", 1) 477 | result, err := strconv.ParseUint(s, 0, 16) 478 | return uint16(result), err 479 | } 480 | -------------------------------------------------------------------------------- /debugger/symbols.go: -------------------------------------------------------------------------------- 1 | package debugger 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | type debugSymbol struct { 12 | address uint16 13 | name string 14 | } 15 | 16 | type debugSymbols []debugSymbol 17 | 18 | func (d debugSymbol) String() string { 19 | return fmt.Sprintf("{%s => $%04X}", d.name, d.address) 20 | } 21 | 22 | // addressesFor returns the addresses labelled with the given name. 23 | func (symbols debugSymbols) addressesFor(name string) (result []uint16) { 24 | for _, s := range symbols { 25 | if strings.EqualFold(name, s.name) { 26 | result = append(result, s.address) 27 | } 28 | } 29 | return 30 | } 31 | 32 | // labelsFor returns label name(s) for the given address. 33 | func (symbols debugSymbols) labelsFor(addr uint16) (result []string) { 34 | for _, l := range symbols { 35 | if l.address == addr { 36 | result = append(result, l.name) 37 | } 38 | } 39 | return 40 | } 41 | 42 | // uniqueLabels is label names which resolve to a single address. 43 | func (symbols debugSymbols) uniqueLabels() (result []string) { 44 | counter := make(map[string]int) 45 | for _, l := range symbols { 46 | counter[l.name]++ 47 | } 48 | for l, count := range counter { 49 | if count == 1 { 50 | result = append(result, l) 51 | } 52 | } 53 | return 54 | } 55 | 56 | func readDebugSymbols(debugFile string) (symbols debugSymbols, err error) { 57 | file, err := os.Open(debugFile) 58 | if err != nil { 59 | return 60 | } 61 | 62 | symbols = make([]debugSymbol, 128) 63 | t := &tokenizer{state: sBegin} 64 | 65 | handleLine := func() { 66 | // old format: "label", new format: "lab" 67 | if t.line.data["type"][0:3] == "lab" { 68 | val, ok := t.line.data["val"] // new format 69 | if !ok { 70 | val = t.line.data["value"] // old format 71 | } 72 | addr, err := strconv.ParseUint(val, 0, 16) 73 | if err != nil { 74 | panic(err) 75 | } 76 | symbols = append(symbols, debugSymbol{address: uint16(addr), name: t.line.name}) 77 | } 78 | } 79 | 80 | s := bufio.NewScanner(file) 81 | s.Split(t.splitter) 82 | for s.Scan() { 83 | bytes := s.Bytes() 84 | switch t.state { 85 | case sBegin: 86 | if s.Text() == "sym" { 87 | t.line = debugLine{prefix: "sym", data: make(map[string]string)} 88 | t.enter(sTab) 89 | } else { 90 | t.enter(sReject) 91 | } 92 | case sReject: 93 | if bytes[0] == '\n' { 94 | t.enter(sBegin) 95 | } 96 | case sTab: 97 | if bytes[0] == '\t' { 98 | t.enter(sNameOrMap) 99 | } else { 100 | panic("Expected TAB after line type") 101 | } 102 | case sNameOrMap: 103 | if bytes[0] == '"' { 104 | // name (old debug format) 105 | text := s.Text() 106 | t.line.name = text[1 : len(text)-1] // strip quotes 107 | t.enter(sMap) 108 | } else { 109 | // map key (new debug format) 110 | t.line.key = s.Text() 111 | t.enter(sMapEquals) 112 | } 113 | case sMap: 114 | if bytes[0] == ',' { 115 | t.enter(sMapKey) 116 | } else if bytes[0] == '\n' { 117 | t.enter(sBegin) 118 | handleLine() 119 | } 120 | case sMapKey: 121 | t.line.key = s.Text() 122 | t.enter(sMapEquals) 123 | case sMapEquals: 124 | if bytes[0] != '=' { 125 | panic("Expected '=' in sMapEquals state") 126 | } else { 127 | t.enter(sMapValue) 128 | } 129 | case sMapValue: 130 | t.enter(sMap) 131 | if t.line.key == "name" { 132 | text := s.Text() 133 | t.line.name = text[1 : len(text)-1] // strip quotes 134 | } else { 135 | t.line.data[t.line.key] = s.Text() 136 | } 137 | } 138 | } 139 | if err = s.Err(); err != nil { 140 | return 141 | } 142 | 143 | return 144 | } 145 | 146 | // Tokenizer states. 147 | const ( 148 | sBegin = iota // initial state 149 | sReject // line is being rejected 150 | sTab // expect tab 151 | sNameOrMap // expecting name in old format, map in new format. 152 | sMap // expecting ,key=value,key=value 153 | sMapKey 154 | sMapEquals 155 | sMapValue 156 | ) 157 | 158 | type debugLine struct { 159 | prefix string 160 | name string 161 | key string 162 | data map[string]string 163 | } 164 | 165 | type tokenizer struct { 166 | state int 167 | line debugLine 168 | } 169 | 170 | func (t *tokenizer) enter(state int) { 171 | t.state = state 172 | } 173 | 174 | func (t *tokenizer) splitter(data []byte, atEOF bool) (advance int, token []byte, err error) { 175 | 176 | // return separators as separate tokens. 177 | if len(data) > 0 { 178 | switch data[0] { 179 | case '\t', '\n', ',', '=': 180 | return 1, data[0:1], nil 181 | } 182 | } 183 | 184 | // return tokens up to but not including the separator. 185 | for i, b := range data { 186 | switch b { 187 | case '\t', '\n', ',', '=': 188 | return i, data[0:i], nil 189 | } 190 | } 191 | 192 | return 0, nil, nil 193 | } 194 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/pda/go6502 2 | 3 | go 1.20 4 | 5 | require github.com/peterh/liner v1.2.2 6 | 7 | require ( 8 | github.com/mattn/go-runewidth v0.0.3 // indirect 9 | golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 // indirect 10 | ) 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4= 2 | github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 3 | github.com/peterh/liner v1.2.2 h1:aJ4AOodmL+JxOZZEL2u9iJf8omNRpqHc/EbrK+3mAXw= 4 | github.com/peterh/liner v1.2.2/go.mod h1:xFwJyiKIXJZUKItq5dGHZSTBRAuG/CpeNpWLyiNRNwI= 5 | golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 h1:kwrAHlwJ0DUBZwQ238v+Uod/3eZ8B2K5rYsUHBQvzmI= 6 | golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 7 | -------------------------------------------------------------------------------- /go6502.go: -------------------------------------------------------------------------------- 1 | /* 2 | go6502 emulates the pda6502 computer. This includes the MOS 6502 3 | processor, memory-mapping address bus, RAM and ROM, MOS 6522 VIA 4 | controller, SSD1306 OLED display, and perhaps more. 5 | 6 | Read more at https://github.com/pda/go6502 and https://github.com/pda/pda6502 7 | */ 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "os" 13 | "os/signal" 14 | 15 | "github.com/pda/go6502/bus" 16 | "github.com/pda/go6502/cli" 17 | "github.com/pda/go6502/cpu" 18 | "github.com/pda/go6502/debugger" 19 | "github.com/pda/go6502/ili9340" 20 | "github.com/pda/go6502/memory" 21 | "github.com/pda/go6502/sd" 22 | "github.com/pda/go6502/speedometer" 23 | "github.com/pda/go6502/spi" 24 | "github.com/pda/go6502/ssd1306" 25 | "github.com/pda/go6502/via6522" 26 | ) 27 | 28 | const ( 29 | kernalPath = "rom/kernal.rom" 30 | charRomPath = "rom/char.rom" 31 | ) 32 | 33 | func main() { 34 | os.Exit(mainReturningStatus()) 35 | } 36 | 37 | func mainReturningStatus() int { 38 | 39 | options := cli.ParseFlags() 40 | 41 | // Create addressable devices. 42 | 43 | kernal, err := memory.RomFromFile(kernalPath) 44 | if err != nil { 45 | panic(err) 46 | } 47 | 48 | charRom, err := memory.RomFromFile(charRomPath) 49 | if err != nil { 50 | panic(err) 51 | } 52 | 53 | ram := &memory.Ram{} 54 | 55 | via := via6522.NewVia6522(via6522.Options{ 56 | DumpAscii: options.ViaDumpAscii, 57 | DumpBinary: options.ViaDumpBinary, 58 | }) 59 | 60 | if options.Ili9340 { 61 | ili9340, err := ili9340.NewDisplay(spi.PinMap{ 62 | Sclk: 0, 63 | Mosi: 6, 64 | Miso: 7, 65 | Ss: 5, 66 | }) 67 | if err != nil { 68 | panic(err) 69 | } 70 | via.AttachToPortB(ili9340) 71 | } 72 | 73 | if options.ViaSsd1306 { 74 | ssd1306 := ssd1306.NewSsd1306() 75 | via.AttachToPortA(ssd1306) 76 | } 77 | 78 | if len(options.SdCard) > 0 { 79 | sd, err := sd.NewSdCardPeripheral(spi.PinMap{ 80 | Sclk: 0, 81 | Mosi: 6, 82 | Miso: 7, 83 | Ss: 4, 84 | }) 85 | if err != nil { 86 | panic(err) 87 | } 88 | err = sd.LoadFile(options.SdCard) 89 | if err != nil { 90 | panic(err) 91 | } 92 | via.AttachToPortB(sd) 93 | } 94 | 95 | via.Reset() 96 | 97 | // Attach devices to address bus. 98 | 99 | addressBus, _ := bus.CreateBus() 100 | addressBus.Attach(ram, "ram", 0x0000) 101 | addressBus.Attach(via, "VIA", 0x9000) 102 | addressBus.Attach(charRom, "char", 0xB000) 103 | addressBus.Attach(kernal, "kernal", 0xF000) 104 | 105 | exitChan := make(chan int, 0) 106 | 107 | cpu := &cpu.Cpu{Bus: addressBus, ExitChan: exitChan} 108 | defer cpu.Shutdown() 109 | if options.Debug { 110 | debugger := debugger.NewDebugger(cpu, options.DebugSymbolFile) 111 | debugger.QueueCommands(options.DebugCmds) 112 | cpu.AttachMonitor(debugger) 113 | } else if options.Speedometer { 114 | speedo := speedometer.NewSpeedometer() 115 | cpu.AttachMonitor(speedo) 116 | } 117 | cpu.Reset() 118 | 119 | // Dispatch CPU in a goroutine. 120 | go func() { 121 | for { 122 | cpu.Step() 123 | } 124 | }() 125 | 126 | var ( 127 | sig os.Signal 128 | exitStatus int 129 | ) 130 | 131 | sigChan := make(chan os.Signal, 1) 132 | signal.Notify(sigChan, os.Interrupt) 133 | 134 | select { 135 | case exitStatus = <-exitChan: 136 | // pass 137 | case sig = <-sigChan: 138 | fmt.Println("\nGot signal:", sig) 139 | exitStatus = 1 140 | } 141 | 142 | fmt.Println(cpu) 143 | fmt.Println("Dumping RAM into core file") 144 | ram.Dump("core") 145 | 146 | return exitStatus 147 | } 148 | -------------------------------------------------------------------------------- /ili9340/ili9340.go: -------------------------------------------------------------------------------- 1 | /* 2 | Emulates 240x320 TFT color display with SPI interface. 3 | http://www.adafruit.com/products/1480 4 | */ 5 | package ili9340 6 | 7 | import ( 8 | "fmt" 9 | "image" 10 | "image/color" 11 | "image/draw" 12 | "image/png" 13 | "os" 14 | 15 | "github.com/pda/go6502/spi" 16 | ) 17 | 18 | const ( 19 | dcMask uint8 = 1 << 2 20 | ) 21 | 22 | const ( 23 | stateUnknown = iota 24 | stateRamWrite 25 | stateColumnAddressSet 26 | statePageAddressSet 27 | ) 28 | 29 | const ( 30 | cmdRamWrite = 0x2C 31 | cmdColumnAddressSet = 0x2A 32 | cmdPageAddressSet = 0x2B 33 | ) 34 | 35 | const ( 36 | width = 320 37 | height = 240 38 | dumpFilename = "ili9340.png" 39 | ) 40 | 41 | type Display struct { 42 | spi *spi.Slave 43 | dataMode bool 44 | state uint 45 | paramIndex uint8 46 | paramData uint32 // accumulator for current parameter 47 | img *image.RGBA 48 | nextX uint16 49 | nextY uint16 50 | startCol uint16 51 | endCol uint16 52 | startRow uint16 53 | endRow uint16 54 | } 55 | 56 | func NewDisplay(pm spi.PinMap) (display *Display, err error) { 57 | img := createImage() 58 | display = &Display{ 59 | spi: spi.NewSlave(pm), 60 | img: img, 61 | startCol: 0, 62 | endCol: width - 1, 63 | startRow: 0, 64 | endRow: height - 1, 65 | } 66 | return 67 | } 68 | 69 | func createImage() (img *image.RGBA) { 70 | img = image.NewRGBA(image.Rect(0, 0, width, height)) 71 | draw.Draw(img, img.Bounds(), &image.Uniform{color.Black}, image.ZP, draw.Src) 72 | return 73 | } 74 | 75 | func (d *Display) PinMask() byte { 76 | return d.spi.PinMask() | dcMask 77 | } 78 | 79 | func (d *Display) Read() byte { 80 | return d.spi.Read() 81 | } 82 | 83 | func (d *Display) Write(b byte) { 84 | if b&dcMask == 0 && d.dataMode { 85 | d.dataMode = false 86 | } else if b&dcMask != 0 && !d.dataMode { 87 | d.dataMode = true 88 | } 89 | 90 | d.spi.Write(b) 91 | if d.spi.Done { 92 | d.acceptByte(d.spi.Mosi) 93 | } 94 | 95 | } 96 | 97 | func (d *Display) String() string { 98 | return "ILI9340" 99 | } 100 | 101 | func (d *Display) Shutdown() { 102 | d.writeImage() 103 | } 104 | 105 | func (d *Display) writeImage() { 106 | fmt.Println("Writing ILI9340 screen to", dumpFilename) 107 | writer, err := os.Create(dumpFilename) 108 | if err != nil { 109 | panic(err) 110 | } 111 | _ = png.Encode(writer, d.img) 112 | } 113 | 114 | func (d *Display) acceptByte(b byte) { 115 | if d.dataMode { 116 | d.acceptData(b) 117 | } else { 118 | d.acceptCommand(b) 119 | } 120 | } 121 | 122 | func (d *Display) acceptCommand(b byte) { 123 | d.paramIndex = 0 124 | switch b { 125 | case cmdRamWrite: 126 | d.nextX = d.startCol 127 | d.nextY = d.startRow 128 | d.state = stateRamWrite 129 | case cmdColumnAddressSet: 130 | d.state = stateColumnAddressSet 131 | case cmdPageAddressSet: 132 | d.state = statePageAddressSet 133 | default: 134 | if d.state != stateUnknown { 135 | d.state = stateUnknown 136 | } 137 | } 138 | } 139 | 140 | func (d *Display) acceptData(b byte) { 141 | if d.paramIndex == 0 { 142 | d.paramData = 0 143 | } 144 | d.paramData |= (uint32(b) << ((3 - d.paramIndex) * 8)) 145 | d.paramIndex++ 146 | 147 | switch d.state { 148 | case stateRamWrite: 149 | d.acceptRamWrite(b) 150 | case stateColumnAddressSet: 151 | d.acceptColumnAddressByte(b) 152 | case statePageAddressSet: 153 | d.acceptPageAddressByte(b) 154 | } 155 | } 156 | 157 | func (d *Display) acceptRamWrite(b byte) { 158 | if d.paramIndex == 2 { 159 | d.pixelWrite(uint16(d.paramData >> 16)) 160 | d.paramIndex = 0 161 | } 162 | } 163 | 164 | func (d *Display) pixelWrite(p16 uint16) { 165 | r := uint8((p16 & 0xF800) >> 8) // map high 5-bit to 8-bit color 166 | g := uint8((p16 & 0x07E0) >> 3) // map mid 6-bit to 8-bit color 167 | b := uint8((p16 & 0x001F) << 3) // map low 5-bit to 8-bit color 168 | c := color.RGBA{r, g, b, 0xFF} 169 | 170 | d.img.SetRGBA(int(d.nextX), int(d.nextY), c) 171 | 172 | if d.nextX == d.endCol { 173 | d.nextX = d.startCol 174 | if d.nextY == d.endRow { 175 | d.nextY = d.startRow 176 | } else { 177 | d.nextY++ 178 | } 179 | } else { 180 | d.nextX++ 181 | } 182 | } 183 | 184 | func (d *Display) acceptColumnAddressByte(b byte) { 185 | if d.paramIndex == 4 { 186 | d.startCol = uint16(d.paramData >> 16) 187 | d.endCol = uint16(d.paramData & 0xFFFF) 188 | fmt.Printf("ILI9340 column address range %d:%d\n", d.startCol, d.endCol) 189 | } 190 | } 191 | 192 | func (d *Display) acceptPageAddressByte(b byte) { 193 | if d.paramIndex == 4 { 194 | d.startRow = uint16(d.paramData >> 16) 195 | d.endRow = uint16(d.paramData & 0xFFFF) 196 | fmt.Printf("ILI9340 row address range %d:%d\n", d.startRow, d.endRow) 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /memory/memory.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package memory provides ROM & RAM for go6502; 16-bit address, 8-bit data. 3 | */ 4 | package memory 5 | 6 | // Memory is a general interface for reading and writing bytes to and from 7 | // 16-bit addresses. 8 | type Memory interface { 9 | Shutdown() 10 | Read(uint16) byte 11 | Write(uint16, byte) 12 | Size() int 13 | } 14 | -------------------------------------------------------------------------------- /memory/ram.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import "io/ioutil" 4 | 5 | // Ram (32 KiB) 6 | type Ram [0x8000]byte 7 | 8 | // Shutdown is part of the Memory interface, but takes no action for Ram. 9 | func (r *Ram) Shutdown() { 10 | } 11 | 12 | func (r *Ram) String() string { 13 | return "(RAM 32K)" 14 | } 15 | 16 | // Read a byte from a 16-bit address. 17 | func (mem *Ram) Read(a uint16) byte { 18 | return mem[a] 19 | } 20 | 21 | // Write a byte to a 16-bit address. 22 | func (mem *Ram) Write(a uint16, value byte) { 23 | mem[a] = value 24 | } 25 | 26 | // Size of the RAM in bytes. 27 | func (mem *Ram) Size() int { 28 | return 0x8000 // 32K 29 | } 30 | 31 | // Dump writes the RAM contents to the specified file path. 32 | func (mem *Ram) Dump(path string) { 33 | err := ioutil.WriteFile(path, mem[:], 0640) 34 | if err != nil { 35 | panic(err) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /memory/rom.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "io/ioutil" 7 | ) 8 | 9 | // A Rom provides read-only memory, with data generally pre-loaded from a file. 10 | type Rom struct { 11 | name string 12 | size int // bytes 13 | data []byte 14 | } 15 | 16 | // Shutdown is part of the Memory interface, but takes no action for Rom. 17 | func (r *Rom) Shutdown() { 18 | } 19 | 20 | // Read a byte from the given address. 21 | func (rom *Rom) Read(a uint16) byte { 22 | return rom.data[a] 23 | } 24 | 25 | // Create a new ROM, loading the contents from a file. 26 | // The size of the ROM is determined by the size of the file. 27 | func RomFromFile(path string) (*Rom, error) { 28 | data, err := ioutil.ReadFile(path) 29 | if err != nil { 30 | return nil, err 31 | } 32 | return &Rom{name: path, size: len(data), data: data}, nil 33 | } 34 | 35 | // Size of the Rom in bytes. 36 | func (r *Rom) Size() int { 37 | return r.size 38 | } 39 | 40 | func (r *Rom) String() string { 41 | return fmt.Sprintf("ROM[%dk:%s:%s..%s]", 42 | r.Size()/1024, 43 | r.name, 44 | hex.EncodeToString(r.data[0:2]), 45 | hex.EncodeToString(r.data[len(r.data)-2:])) 46 | } 47 | 48 | // Rom meets the go6502.Memory interface, but Write is not supported, and will 49 | // cause an error. 50 | func (r *Rom) Write(_ uint16, _ byte) { 51 | panic(fmt.Sprintf("%v is read-only", r)) 52 | } 53 | -------------------------------------------------------------------------------- /sd/response_string.go: -------------------------------------------------------------------------------- 1 | // generated by stringer -type response; DO NOT EDIT 2 | 3 | package sd 4 | 5 | import "fmt" 6 | 7 | const _response_name = "r1_readyr1_idle" 8 | 9 | var _response_index = [...]uint8{8, 15} 10 | 11 | func (i response) String() string { 12 | if i >= response(len(_response_index)) { 13 | return fmt.Sprintf("response(%d)", i) 14 | } 15 | hi := _response_index[i] 16 | lo := uint8(0) 17 | if i > 0 { 18 | lo = _response_index[i-1] 19 | } 20 | return _response_name[lo:hi] 21 | } 22 | -------------------------------------------------------------------------------- /sd/sd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package SD emulates an SD/MMC card. 3 | */ 4 | package sd 5 | -------------------------------------------------------------------------------- /sd/sd_card.go: -------------------------------------------------------------------------------- 1 | //go:generate stringer -type state 2 | //go:generate stringer -type response 3 | 4 | package sd 5 | 6 | import "fmt" 7 | 8 | type state uint8 9 | 10 | // states 11 | const ( 12 | sCommand state = iota // expect command 13 | sArgument // expect argument 14 | sChecksum // expect checksum 15 | sData // sending data until misoQueue empty. 16 | ) 17 | 18 | type response uint8 19 | 20 | const ( 21 | r1_ready response = 0x00 22 | r1_idle response = 0x01 23 | ) 24 | 25 | const ( 26 | // blockSize isn't strictly constant, but... 27 | blockSize = 512 28 | ) 29 | 30 | // sdCard is the state of SD protocol (layer above SPI protocol). 31 | type sdCard struct { 32 | state state 33 | acmd bool // next command is an application-specific command 34 | cmd uint8 35 | arg uint32 36 | argByte uint8 37 | misoQueue []byte // data waiting to be sent from card. 38 | prevCmd uint8 39 | prevAcmd uint8 40 | data []byte 41 | } 42 | 43 | func newSdCard() (sd *sdCard) { 44 | return &sdCard{ 45 | misoQueue: make([]byte, 0, 1024), 46 | } 47 | } 48 | 49 | func (sd *sdCard) enter(state state) { 50 | fmt.Printf("SD state %s -> %s\n", sd.state, state) 51 | sd.state = state 52 | } 53 | 54 | func (sd *sdCard) consumeByte(b byte) { 55 | switch sd.state { 56 | case sCommand: 57 | if b>>6 == 1 { 58 | sd.cmd = b & (0xFF >> 2) 59 | sd.enter(sArgument) 60 | sd.arg = 0x00000000 61 | sd.argByte = 0 62 | } 63 | case sArgument: 64 | sd.arg |= uint32(b) << ((3 - sd.argByte) * 8) 65 | if sd.argByte == 3 { 66 | sd.enter(sChecksum) 67 | } else { 68 | sd.argByte++ 69 | } 70 | case sChecksum: 71 | if sd.acmd { 72 | sd.handleAcmd() 73 | } else { 74 | sd.handleCmd() 75 | } 76 | case sData: 77 | // ignore; data it being sent. 78 | 79 | default: 80 | panic(fmt.Errorf("Unhandled state: %d", sd.state)) 81 | } 82 | } 83 | 84 | func (sd *sdCard) handleCmd() { 85 | fmt.Printf("SD CMD%d arg: 0x%08X\n", sd.cmd, sd.arg) 86 | switch sd.cmd { 87 | case 0: // GO_IDLE_STATE 88 | fmt.Println("SD CMD0 response: r1_idle") 89 | sd.queueMisoBytes(0xFF, 0xFF, byte(r1_idle)) // busy then idle 90 | sd.enter(sCommand) 91 | case 17: // READ_SINGLE_BLOCK 92 | fmt.Println("SD CMD17 response: r1_ready, data start block, data") 93 | sd.queueMisoBytes(0xFF, 0xFF, byte(r1_ready)) // busy then ready 94 | sd.queueMisoBytes(0xFF, 0xFF, 0xFF, 0xFF) // time before data block 95 | sd.queueMisoBytes(0xFE) // data start block 96 | sd.queueMisoBytes(sd.readBlock(sd.arg)...) 97 | sd.enter(sData) 98 | case 55: // APP_CMD 99 | fmt.Println("SD CMD55 response: r1_idle") 100 | sd.queueMisoBytes(byte(r1_idle)) // busy then idle 101 | sd.acmd = true 102 | sd.enter(sCommand) 103 | default: 104 | panic(fmt.Sprintf("Unhandled CMD%d", sd.cmd)) 105 | } 106 | sd.prevCmd = sd.cmd 107 | } 108 | 109 | func (sd *sdCard) handleAcmd() { 110 | fmt.Printf("SD ACMD%d arg: 0x%08X\n", sd.cmd, sd.arg) 111 | switch sd.cmd { 112 | case 41: // SD_SEND_OP_COND 113 | if sd.prevAcmd == 41 { 114 | // on second attempt, busy, busy, then ready. 115 | fmt.Println("SD ACMD41 response: r1_ready") 116 | sd.queueMisoBytes(0xFF, 0xFF, byte(r1_ready)) 117 | } else { 118 | // on first attempt, busy, busy, then idle (not yet ready). 119 | fmt.Println("SD ACMD41 response: r1_idle") 120 | sd.queueMisoBytes(0xFF, 0xFF, byte(r1_idle)) 121 | } 122 | sd.enter(sCommand) 123 | default: 124 | panic(fmt.Sprintf("Unhandled ACMD%d", sd.cmd)) 125 | } 126 | sd.prevAcmd = sd.cmd 127 | sd.acmd = false 128 | } 129 | 130 | func (sd *sdCard) queueMisoBytes(bytes ...byte) { 131 | sd.misoQueue = append(sd.misoQueue, bytes...) 132 | } 133 | 134 | func (sd *sdCard) shiftMiso() (b byte) { 135 | if len(sd.misoQueue) > 0 { 136 | b = sd.misoQueue[0] 137 | sd.misoQueue = sd.misoQueue[1:len(sd.misoQueue)] 138 | if len(sd.misoQueue) == 0 && sd.state == sData { 139 | // transition from sData to sCommand when all data sent. 140 | sd.enter(sCommand) 141 | } 142 | } else { 143 | b = 0x00 // default to low for empty buffer. 144 | } 145 | return 146 | } 147 | 148 | func (sd *sdCard) readBlock(start uint32) []byte { 149 | // TODO: bounds checking 150 | // TODO: zero-fill remainder of last page in sd.data? 151 | return sd.data[start : start+blockSize] 152 | } 153 | -------------------------------------------------------------------------------- /sd/sd_card_peripheral.go: -------------------------------------------------------------------------------- 1 | package sd 2 | 3 | import ( 4 | "io/ioutil" 5 | 6 | "github.com/pda/go6502/spi" 7 | ) 8 | 9 | type SdCardPeripheral struct { 10 | card *sdCard 11 | spi *spi.Slave 12 | } 13 | 14 | // SdFromFile creates a new SdCardPeripheral based on the contents of a file. 15 | func NewSdCardPeripheral(pm spi.PinMap) (sd *SdCardPeripheral, err error) { 16 | sd = &SdCardPeripheral{ 17 | card: newSdCard(), 18 | spi: spi.NewSlave(pm), 19 | } 20 | 21 | // two busy bytes, then ready. 22 | sd.card.queueMisoBytes(0x00, 0x00, 0xFF) 23 | 24 | return 25 | } 26 | 27 | // LoadFile is equivalent to inserting an SD card. 28 | func (sd *SdCardPeripheral) LoadFile(path string) (err error) { 29 | data, err := ioutil.ReadFile(path) 30 | if err != nil { 31 | return 32 | } 33 | sd.card.data = data 34 | return 35 | } 36 | 37 | // via6522.ParallelPeripheral interface 38 | 39 | func (sd *SdCardPeripheral) PinMask() byte { 40 | return sd.spi.PinMask() 41 | } 42 | 43 | func (sd *SdCardPeripheral) Read() byte { 44 | return sd.spi.Read() 45 | } 46 | 47 | func (sd *SdCardPeripheral) Shutdown() { 48 | } 49 | 50 | // Write takes an updated parallel port state. 51 | func (sd *SdCardPeripheral) Write(data byte) { 52 | if sd.spi.Write(data) { 53 | if sd.spi.Done { 54 | mosi := sd.spi.Mosi 55 | //fmt.Printf("SD MOSI $%02X %08b <-> $%02X %08b MISO\n", 56 | // mosi, mosi, sd.spi.Miso, sd.spi.Miso) 57 | 58 | // consume the byte read, queue miso bytes internally 59 | sd.card.consumeByte(mosi) 60 | // dequeues one miso byte, or a default byte if queue empty. 61 | sd.spi.QueueMisoBits(sd.card.shiftMiso()) 62 | } 63 | } 64 | } 65 | 66 | func (sd *SdCardPeripheral) String() string { 67 | return "SD card" 68 | } 69 | -------------------------------------------------------------------------------- /sd/sd_test.go: -------------------------------------------------------------------------------- 1 | package sd 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/pda/go6502/spi" 8 | ) 9 | 10 | func TestSdPinMask(t *testing.T) { 11 | sd, _ := NewSdCardPeripheral(spi.PinMap{Sclk: 4, Mosi: 5, Miso: 6, Ss: 7}) 12 | if sd.PinMask() != 0xF0 { 13 | t.Error(fmt.Sprintf("0b%08b != 0b%08b", sd.PinMask(), 0xF0)) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /sd/state_string.go: -------------------------------------------------------------------------------- 1 | // generated by stringer -type state; DO NOT EDIT 2 | 3 | package sd 4 | 5 | import "fmt" 6 | 7 | const _state_name = "sCommandsArgumentsChecksumsData" 8 | 9 | var _state_index = [...]uint8{8, 17, 26, 31} 10 | 11 | func (i state) String() string { 12 | if i >= state(len(_state_index)) { 13 | return fmt.Sprintf("state(%d)", i) 14 | } 15 | hi := _state_index[i] 16 | lo := uint8(0) 17 | if i > 0 { 18 | lo = _state_index[i-1] 19 | } 20 | return _state_name[lo:hi] 21 | } 22 | -------------------------------------------------------------------------------- /speedometer/speedometer.go: -------------------------------------------------------------------------------- 1 | package speedometer 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/pda/go6502/cpu" 8 | ) 9 | 10 | // Speedometer tracks how many instructions and cycles have executed in how 11 | // much time, to calculate an effective MHz etc. 12 | type Speedometer struct { 13 | cycles uint64 14 | instructions uint64 15 | timeStart time.Time 16 | cycleChan chan uint8 17 | } 18 | 19 | // NewSpeedometer creates a Speedometer, and starts a goroutine to receive 20 | // cycle counts from Speedometer.BeforeExecute(). 21 | func NewSpeedometer() *Speedometer { 22 | s := &Speedometer{ 23 | timeStart: time.Now(), 24 | cycleChan: make(chan uint8), 25 | } 26 | go func() { 27 | for { 28 | s.cycles += uint64(<-s.cycleChan) 29 | s.instructions++ 30 | } 31 | }() 32 | return s 33 | } 34 | 35 | // BeforeExecute meets go6502.Monitor interface. 36 | func (s *Speedometer) BeforeExecute(in cpu.Instruction) { 37 | s.cycleChan <- in.Cycles 38 | } 39 | 40 | // Shutdown the Speedometer session, reporting stats to stdout. 41 | func (s *Speedometer) Shutdown() { 42 | duration := time.Since(s.timeStart) 43 | us := float64(duration) / float64(time.Microsecond) 44 | 45 | fmt.Printf("Speedometer\n") 46 | fmt.Printf("----------------------------------\n") 47 | fmt.Printf("Instructions: % 20d\n", s.instructions) 48 | fmt.Printf("Cycles: % 20d\n", s.cycles) 49 | fmt.Printf("Seconds: % 20.2f\n", duration.Seconds()) 50 | fmt.Printf("MHz: % 20.2f\n", float64(s.cycles)/us) 51 | fmt.Printf("MIPS: % 20.2f\n", float64(s.instructions)/us) 52 | fmt.Printf("----------------------------------\n") 53 | } 54 | -------------------------------------------------------------------------------- /spi/pin_map.go: -------------------------------------------------------------------------------- 1 | package spi 2 | 3 | // PinMap associates SPI lines with parallel port pin numbers (0..7). 4 | type PinMap struct { 5 | Sclk uint 6 | Mosi uint 7 | Miso uint 8 | Ss uint 9 | } 10 | 11 | func (p PinMap) PinMask() byte { 12 | return 1< 0 58 | clock := data&s.maskSclk > 0 59 | 60 | rising := !s.clock && clock 61 | falling := s.clock && !clock 62 | s.clock = clock 63 | 64 | // sclk:rise -> miso -> sclk:fall -> mosi -> ... 65 | 66 | if rising { 67 | if s.misoBuffer&(1< 0 { 68 | s.readByte = 0x00 | s.maskMiso 69 | } else { 70 | s.readByte = 0x00 71 | } 72 | } 73 | 74 | if falling { 75 | if mosi { 76 | s.mosiBuffer |= (1 << s.index) 77 | } 78 | 79 | // after eigth bit 80 | if s.index == 0 { 81 | s.index = 7 82 | s.Mosi = s.mosiBuffer 83 | s.Miso = s.misoBuffer 84 | s.Done = true 85 | s.mosiBuffer = 0x00 86 | } else { 87 | s.index-- 88 | } 89 | } 90 | 91 | return true 92 | } 93 | 94 | // QueueMisoBits loads a byte into the MISO buffer, to be sent during the next 95 | // eight clock cycles. 96 | func (s *Slave) QueueMisoBits(b byte) { 97 | if s.index != 7 { 98 | panic("Cannot queue MISO; byte send in progress.") 99 | } 100 | s.misoBuffer = b 101 | } 102 | -------------------------------------------------------------------------------- /spi/spi.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package spi implements parts of Serial Peripheral Interface Bus. 3 | http://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus 4 | */ 5 | package spi 6 | -------------------------------------------------------------------------------- /ssd1306/ssd1306.go: -------------------------------------------------------------------------------- 1 | /* 2 | Emulates a 128x32 pixel monochrome OLED display with SPI interface. 3 | Exposes the display as a dynamically generated PNG available from an HTTP URL. 4 | 5 | Physical hardware example: https://www.adafruit.com/products/661 6 | */ 7 | package ssd1306 8 | 9 | import ( 10 | "fmt" 11 | "image" 12 | "image/color" 13 | "image/png" 14 | "net/http" 15 | "net/url" 16 | "os" 17 | "strconv" 18 | ) 19 | 20 | const ( 21 | // Filename where SSD1306 will write its display upon exit. 22 | DumpFilename = "ssd1306.png" 23 | 24 | // HttpUrl where screen data will be available. 25 | HttpUrl = "http://localhost:1234/ssd1306.png" 26 | ) 27 | 28 | // Ssd1306 implements ParallelPeripheral interface for Via6522. 29 | 30 | type Ssd1306 struct { 31 | lastClock bool 32 | inputBuffer byte 33 | inputIndex uint8 34 | img *image.Gray 35 | imgPixel uint32 36 | } 37 | 38 | func NewSsd1306() *Ssd1306 { 39 | s := Ssd1306{} 40 | s.inputIndex = 7 // MSB-first, decrementing index. 41 | s.img = image.NewGray(image.Rect(0, 0, 128, 32)) 42 | s.serveHttp() 43 | return &s 44 | } 45 | 46 | // TODO: configurable lines 47 | const ( 48 | mosiMask = 1 << 0 49 | clockMask = 1 << 1 50 | dcMask = 1 << 2 51 | resetMask = 1 << 3 52 | ) 53 | 54 | func (s *Ssd1306) String() string { 55 | return "SSD1306" 56 | } 57 | 58 | // PinMask declares the I/O pins the device is connected to. 59 | func (s *Ssd1306) PinMask() byte { 60 | return 0x0F 61 | } 62 | 63 | // Read 0x00; this peripheral is write-only. 64 | func (s *Ssd1306) Read() byte { 65 | return 0x00 66 | } 67 | 68 | // Write expects a byte representing the updated status of the parallel port 69 | // that the display is connected to. 70 | // Four bits are considered: MOSI, CLK, D/C, RST. 71 | // The other four bits are ignored. 72 | func (s *Ssd1306) Write(data byte) { 73 | 74 | mosi := data&mosiMask > 0 75 | clock := data&clockMask > 0 76 | 77 | if clock && !s.lastClock { 78 | // rising clock 79 | s.lastClock = clock 80 | if mosi { 81 | s.inputBuffer |= (1 << s.inputIndex) 82 | } 83 | if s.inputIndex == 0 { 84 | //fmt.Printf("Ssd1306: 0x%02X 0b%08b\n", s.inputBuffer, s.inputBuffer) 85 | s.inputIndex = 7 86 | s.inputBuffer = 0x00 87 | } else { 88 | s.inputIndex-- 89 | } 90 | 91 | if data&dcMask > 0 { 92 | //fmt.Printf("dat:%08b ", data) 93 | 94 | x := (s.imgPixel / 8) % 128 95 | y := (7 - s.imgPixel%8) + 8*(s.imgPixel/1024) 96 | 97 | //fmt.Printf("x:% 3d,y:% 3d ", x, y) 98 | if mosi { 99 | s.img.Set(int(x), int(y), color.White) 100 | } else { 101 | s.img.Set(int(x), int(y), color.Black) 102 | } 103 | 104 | s.imgPixel++ 105 | s.imgPixel %= (128 * 64) 106 | } 107 | } 108 | 109 | if !clock && s.lastClock { 110 | // falling clock 111 | s.lastClock = clock 112 | } 113 | 114 | } 115 | 116 | func (s *Ssd1306) Shutdown() { 117 | fmt.Println("Writing SSD1306 screen to", DumpFilename) 118 | writer, err := os.Create(DumpFilename) 119 | if err != nil { 120 | panic(err) 121 | } 122 | _ = png.Encode(writer, s.img) 123 | } 124 | 125 | func (s *Ssd1306) httpHandler(w http.ResponseWriter, r *http.Request) { 126 | w.Header().Add("Content-Type", "image/png") 127 | 128 | if refreshString := r.URL.Query().Get("refresh"); len(refreshString) > 0 { 129 | refresh, err := strconv.ParseFloat(refreshString, 64) 130 | if err == nil { 131 | w.Header().Add("Refresh", fmt.Sprintf("%0.2f", refresh)) 132 | } 133 | } 134 | 135 | png.Encode(w, s.img) 136 | } 137 | 138 | func (s *Ssd1306) serveHttp() { 139 | url, err := url.Parse(HttpUrl) 140 | if err != nil { 141 | panic(err) 142 | } 143 | 144 | srv := &http.Server{ 145 | Addr: url.Host, 146 | Handler: http.HandlerFunc(s.httpHandler), 147 | } 148 | fmt.Printf("Ssd1306 output at %s\n", url) 149 | go srv.ListenAndServe() 150 | } 151 | -------------------------------------------------------------------------------- /via6522/via6522.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package via6522 emulates MOS Technology 6522, or the modern WDC 65C22. 3 | This is a Versatile Interface Adapter (VIA) I/O controller 4 | designed for use with the 6502 microprocessor. 5 | 6 | The 4-bit RS (register select) is exposed as 16 bytes of address-space. The 7 | processor chooses the register using four bits of the 16-bit address bus and 8 | reads/writes using the 8-bit data bus. 9 | 10 | Peripheral ports 11 | 12 | The W65C22 includes functions for programmed control of two peripheral ports 13 | (Ports A and B). Two program controlled 8-bit bidirectional peripheral I/O 14 | ports allow direct interfacing between the microprocessor and selected 15 | peripheral units. Each port has input data latching capability. Two 16 | programmable Data Direction Registers (A and B) allow selection of data 17 | direction (input or output) on an individual line basis. 18 | 19 | RS registers relevant to peripheral ports: 20 | (a register is selected by setting an address to the 4-bit RS lines) 21 | 0x00: ORB/IRB; write: Output Register B, read: Input Register "B". 22 | 0x01: ORA/IRA; write: Output Register A, read: Input Register "A". 23 | 0x02: DDRB; Data Direction Register B 24 | 0x03: DDRA; Data Direction Register A 25 | 0x0C: PCR; Peripheral Control Register. 26 | 0: CA1 control, 1..3: CA2 control 27 | 4: CB1 control, 5..7: CB2 control. 28 | 29 | External interface relevant to peripheral ports: 30 | PORTA: 8-bit independently bidirectional data to peripheral. 31 | PORTB: 8-bit independently bidirectional data to peripheral. 32 | DATA: 8-bit bidirectional data to microprocessor. 33 | RS: 4-bit register select. 34 | CA: 2-bit control lines for PORTA. 35 | CB: 2-bit control lines for PORTB. 36 | 37 | Write handshake control (PORT A as example, PORT B is same for writes): 38 | CA2 (output) indicates data has been written to ORA and is ready. 39 | CA1 (input) indicates data has been taken. 40 | Default modes assuming PCR == 0x00: 41 | CA2: Input-negative active edge (one of eight options). 42 | CA1: negative active edge (one of two options). 43 | 44 | Timers 45 | 46 | Timers have not yet been implemented. 47 | 48 | Interrupts 49 | 50 | Interrupts have not yet been implemented. 51 | 52 | Reference Material 53 | 54 | The following data sheets and external resources may be useful. 55 | 56 | Original 6522: http://en.wikipedia.org/wiki/MOS_Technology_6522 57 | WCD 65C22: http://www.westerndesigncenter.com/wdc/w65c22-chip.cfm 58 | Data sheet: http://www.westerndesigncenter.com/wdc/documentation/w65c22.pdf 59 | */ 60 | package via6522 61 | 62 | import ( 63 | "fmt" 64 | "strconv" 65 | "unicode" 66 | ) 67 | 68 | const ( 69 | viaOrb = 0x0 70 | viaIrb = 0x0 71 | viaOra = 0x1 72 | viaIra = 0x1 73 | 74 | viaDdrb = 0x2 75 | viaDdra = 0x3 76 | 77 | // bit-offset into PCR for port A & B 78 | viaPcrOffsetA = 0 79 | viaPcrOffsetB = 4 80 | ) 81 | 82 | /** 83 | * Memory interface implementation. 84 | */ 85 | 86 | // The internal state of the 6522 VIA controller, and references to connected 87 | // peripheral devices. 88 | type Via6522 struct { 89 | // Note: It may be a mistake to consider ORx and IRx separate registers. 90 | // If so... fix it? 91 | ora byte // output register port A 92 | orb byte // output register port B 93 | ira byte // input register port A 94 | irb byte // input register port B 95 | ddra byte // data direction port A 96 | ddrb byte // data direction port B 97 | pcr byte // peripheral control register 98 | options Options 99 | paPeripherals []ParallelPeripheral 100 | pbPeripherals []ParallelPeripheral 101 | } 102 | 103 | type Options struct { 104 | DumpBinary bool 105 | DumpAscii bool 106 | } 107 | 108 | // ParallelPeripheral defines an interface for peripheral devices which can connect to 109 | // either of the parallel ports to read and write data. 110 | type ParallelPeripheral interface { 111 | 112 | // PinMask is a bitfield representing which VIA pins the device is 113 | // connected to. 1 = connected, 0 = not connected. 114 | PinMask() byte 115 | 116 | // Read returns the state of the device's output pins (VIA DDR input). 117 | // Bits not set in PinMask will be ignored. 118 | Read() byte 119 | 120 | // Shutdown runs tear-down tasks when the system is shutting down. 121 | Shutdown() 122 | 123 | // Write is passed the updated port state when data is written. 124 | // Bits not set in PinMask should be ignored. 125 | Write(byte) 126 | 127 | String() string 128 | } 129 | 130 | func NewVia6522(o Options) *Via6522 { 131 | via := &Via6522{} 132 | via.options = o 133 | via.paPeripherals = make([]ParallelPeripheral, 0) 134 | via.pbPeripherals = make([]ParallelPeripheral, 0) 135 | return via 136 | } 137 | 138 | // AttachToPortA attaches a ParallelPeripheral to PA. 139 | func (via *Via6522) AttachToPortA(p ParallelPeripheral) { 140 | fmt.Printf("%s PORTA attaching %s (pinmask: %08b)\n", via, p, p.PinMask()) 141 | via.paPeripherals = append(via.paPeripherals, p) 142 | } 143 | 144 | // AttachToPortA attaches a ParallelPeripheral to PB. 145 | func (via *Via6522) AttachToPortB(p ParallelPeripheral) { 146 | fmt.Printf("%s PORTB attaching %s (pinmask: %08b)\n", via, p, p.PinMask()) 147 | via.pbPeripherals = append(via.pbPeripherals, p) 148 | } 149 | 150 | // Shutdown tells Via6522 and its devices that the system is shutting down. 151 | func (via *Via6522) Shutdown() { 152 | var p ParallelPeripheral 153 | for _, p = range via.paPeripherals { 154 | fmt.Printf("%s shutting down PORTA peripheral: %s\n", via, p.String()) 155 | p.Shutdown() 156 | } 157 | for _, p = range via.pbPeripherals { 158 | fmt.Printf("%s shutting down PORTB peripheral: %s\n", via, p.String()) 159 | p.Shutdown() 160 | } 161 | } 162 | 163 | // CA1 or CB1 1-bit mode for the given port offset (viaPCR_OFFSET_x) 164 | func (via *Via6522) control1Mode(portOffset uint8) byte { 165 | return (via.pcr >> portOffset) & 1 166 | } 167 | 168 | // CA2 or CB2 3-bit mode for the given port offset (viaPCR_OFFSET_x) 169 | func (via *Via6522) control2Mode(portOffset uint8) byte { 170 | return (via.pcr >> (portOffset + 1)) & 0x7 171 | } 172 | 173 | // Print a byte as ASCII, using escape sequences where necessary. 174 | func printAsciiByte(b uint8) { 175 | r := rune(b) 176 | if unicode.IsPrint(r) || unicode.IsSpace(r) { 177 | fmt.Print(string(r)) 178 | } else { 179 | charStr := strconv.QuoteRuneToASCII(r) 180 | fmt.Print(charStr[1 : len(charStr)-1]) 181 | } 182 | } 183 | 184 | // Read the register specified by the given 4-bit address (0x00..0x0F). 185 | // TODO: Unlike IRA, reading IRB actully returns bits from ORA for pins 186 | // that are programmed as output. 187 | func (via *Via6522) Read(a uint16) byte { 188 | switch a { 189 | default: 190 | panic(fmt.Sprintf("read from 0x%X not handled by Via6522", a)) 191 | case 0x0: 192 | via.irb = 0x00 193 | for _, p := range via.pbPeripherals { 194 | via.irb |= (p.Read() & p.PinMask()) 195 | } 196 | return via.readMixedInputOutput(via.irb, via.orb, via.ddrb) 197 | case 0x1: 198 | via.ira = 0x00 199 | for _, p := range via.paPeripherals { 200 | via.ira |= (p.Read() & p.PinMask()) 201 | } 202 | return via.readMixedInputOutput(via.ira, via.ora, via.ddra) 203 | case 0x2: 204 | return via.ddrb 205 | case 0x3: 206 | return via.ddra 207 | case 0xC: 208 | return via.pcr 209 | } 210 | } 211 | 212 | // This represents the correct behavior for reading IRB, 213 | // and maybe an approximation of the correct behavior for IRA. 214 | func (via *Via6522) readMixedInputOutput(in byte, out byte, ddr byte) byte { 215 | return (out & ddr) | (in & ^ddr) 216 | } 217 | 218 | // From the datasheet: 219 | // Reset clears all internal registers 220 | // (except T1 and T2 counters and latches, and the SR.) 221 | func (via *Via6522) Reset() { 222 | via.ora = 0 223 | via.orb = 0 224 | via.ira = 0 225 | via.irb = 0 226 | via.ddra = 0 227 | via.ddrb = 0 228 | via.pcr = 0 229 | } 230 | 231 | // The address size of the memory-mapped IO. 232 | // Helps to meet the go6502.Memory interface. 233 | func (via *Via6522) Size() int { 234 | return 16 // 4-bit RS exposes 16 byte address space. 235 | } 236 | 237 | func (via *Via6522) String() string { 238 | return "VIA6522" 239 | } 240 | 241 | // Write to register specified by the given 4-bit address (0x00..0x0F). 242 | func (via *Via6522) Write(a uint16, data byte) { 243 | switch a { 244 | default: 245 | panic(fmt.Sprintf("write to 0x%X not handled by Via6522", a)) 246 | case 0x0: 247 | via.orb = data 248 | via.handleDataWrite(data&via.ddrb, via.pbPeripherals) 249 | case 0x1: 250 | via.ora = data 251 | via.handleDataWrite(data&via.ddra, via.paPeripherals) 252 | case 0x2: 253 | via.ddrb = data 254 | case 0x3: 255 | via.ddra = data 256 | case 0xC: 257 | via.pcr = data 258 | } 259 | } 260 | 261 | func (via *Via6522) handleDataWrite(data byte, peripherals []ParallelPeripheral) { 262 | if via.options.DumpBinary { 263 | fmt.Printf("VIA output: %08b (0x%02X)\n", data, data) 264 | } 265 | if via.options.DumpAscii { 266 | printAsciiByte(data) 267 | } 268 | for _, p := range peripherals { 269 | p.Write(data & p.PinMask()) 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /via6522/via6522_test.go: -------------------------------------------------------------------------------- 1 | package via6522 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | const ( 9 | iorb = 0x0 10 | iora = 0x1 11 | ddrb = 0x2 12 | ddra = 0x3 13 | ) 14 | 15 | func via() *Via6522 { 16 | return NewVia6522(Options{}) 17 | } 18 | 19 | func TestViaReadAndWriteToDataDirectionRegisters(t *testing.T) { 20 | via := via() 21 | via.Write(ddrb, 0x12) 22 | via.Write(ddra, 0x34) 23 | b := via.Read(ddrb) 24 | a := via.Read(ddra) 25 | if b != 0x12 { 26 | t.Error(fmt.Errorf("DDRB read back $%02X instead of $%02X", a, 0x12)) 27 | } 28 | if a != 0x34 { 29 | t.Error(fmt.Errorf("DDRA read back $%02X instead of $%02X", b, 0x34)) 30 | } 31 | } 32 | 33 | func TestViaReadAndWriteInputOutputPortRegisters(t *testing.T) { 34 | via := via() 35 | via.Write(ddrb, 0xAA) 36 | via.Write(iorb, 0xDE) 37 | b := via.Read(iorb) 38 | expected := uint8(0xAA & 0xDE) // assumes zero values in input register 39 | if b != expected { 40 | t.Error(fmt.Errorf("$%02X != $%02X", b, expected)) 41 | } 42 | } 43 | 44 | func TestViaReadAndWriteToNandPeripheral(t *testing.T) { 45 | via := via() 46 | via.AttachToPortB(&nand{}) 47 | via.Write(ddrb, 0x03) // output to bits 0,1 48 | 49 | // read back the output pins, and the NAND result at bit 7. 50 | assertPortBWriteThenRead(t, via, 0x0, 0x0|1<<7) 51 | assertPortBWriteThenRead(t, via, 0x1, 0x1|1<<7) 52 | assertPortBWriteThenRead(t, via, 0x2, 0x2|1<<7) 53 | assertPortBWriteThenRead(t, via, 0x3, 0x3|0) 54 | 55 | // input pins that were written to are not read back. 56 | assertPortBWriteThenRead(t, via, 0xFF, 0x3|0) 57 | assertPortBWriteThenRead(t, via, 0xFE, 0x82) 58 | } 59 | 60 | func assertPortBWriteThenRead(t *testing.T, via *Via6522, write, expect byte) { 61 | via.Write(iorb, write) 62 | result := via.Read(iorb) 63 | if result != expect { 64 | t.Error(fmt.Errorf("wrote 0b%08b, expected 0b%08b, got 0b%08b", write, expect, result)) 65 | } 66 | } 67 | 68 | func TestPinMaskBlocksWrites(t *testing.T) { 69 | nand := &nand{} 70 | via := via() 71 | via.AttachToPortB(nand) 72 | via.Write(ddrb, 0xFF) // all output 73 | via.Write(iorb, 0xFF) // write all bits 74 | if nand.value != nand.PinMask() { 75 | t.Error(fmt.Errorf("peripheral received 0b%08b despite 0b%08b pinmask", nand.value, nand.PinMask())) 76 | } 77 | } 78 | 79 | func TestDdrBlocksWrites(t *testing.T) { 80 | nand := &nand{} 81 | via := via() 82 | via.AttachToPortB(nand) 83 | via.Write(ddrb, 0x02) 84 | via.Write(iorb, 0xFF) 85 | if nand.value&^0x02 != 0 { 86 | t.Error(fmt.Errorf("peripheral received 0b%08b despite 0b%08b DDR", nand.value, 0xAA)) 87 | } 88 | } 89 | 90 | func TestWriteToMultipleOverlappingPeripherals(t *testing.T) { 91 | one := &flipflop{pinmask: 0xF8} // 0b11111000 92 | two := &flipflop{pinmask: 0x1F} // 0b00011111 93 | via := via() 94 | via.AttachToPortA(one) 95 | via.AttachToPortA(two) 96 | via.Write(ddra, 0xFF) // all output 97 | via.Write(iora, 0xAA) 98 | 99 | expectedOne := 0xAA & one.PinMask() 100 | expectedTwo := 0xAA & two.PinMask() 101 | 102 | if one.value != expectedOne { 103 | t.Error(fmt.Errorf("one: wrote 0b%08b, expected 0b%08b, got 0b%08b", 0xAA, expectedOne, one.value)) 104 | } 105 | 106 | if two.value != expectedTwo { 107 | t.Error(fmt.Errorf("two: wrote 0b%08b, expected 0b%08b, got 0b%08b", 0xAA, expectedTwo, two.value)) 108 | } 109 | } 110 | 111 | func TestReadFromMultipleOverlappingPeripherals(t *testing.T) { 112 | one := &flipflop{pinmask: 0xF8, value: 0xAA} // pinmask: 11111000, value: 10101010 113 | two := &flipflop{pinmask: 0x1F, value: 0xF0} // pinmask: 00011111, value: 11110000 114 | via := via() 115 | via.AttachToPortA(one) 116 | via.AttachToPortA(two) 117 | via.Write(ddra, 0x00) // all input 118 | result := via.Read(iora) 119 | expected := byte(0xB8) // 0b10111000 120 | if result != expected { 121 | t.Error(fmt.Errorf("one: expected 0b%08b, got 0b%08b", expected, result)) 122 | } 123 | } 124 | 125 | // --------------------------------------- 126 | // Test ParallelPeripheral implementations 127 | 128 | // nand: output to bits 0,1 then read NAND result on bit 7. 129 | 130 | type nand struct { 131 | value byte 132 | } 133 | 134 | func (nand *nand) PinMask() byte { 135 | return 0x83 // 0b10000011 136 | } 137 | 138 | func (nand *nand) Read() byte { 139 | if (nand.value & 0x3) == 0x3 { 140 | return 0 141 | } else { 142 | return (1 << 7) 143 | } 144 | } 145 | 146 | func (nand *nand) Shutdown() { 147 | } 148 | 149 | func (nand *nand) Write(in byte) { 150 | nand.value = in 151 | } 152 | 153 | func (nand *nand) String() string { 154 | return "NAND gate test peripheral" 155 | } 156 | 157 | // flipflop: simple memory (subject to external DDR, pinmask etc) 158 | 159 | type flipflop struct { 160 | value byte 161 | pinmask byte 162 | } 163 | 164 | func (ff *flipflop) PinMask() byte { 165 | return ff.pinmask 166 | } 167 | 168 | func (ff *flipflop) Read() byte { 169 | return ff.value 170 | } 171 | 172 | func (ff *flipflop) Shutdown() { 173 | } 174 | 175 | func (ff *flipflop) Write(in byte) { 176 | ff.value = in 177 | } 178 | 179 | func (ff *flipflop) String() string { 180 | return "flipflop test peripheral" 181 | } 182 | --------------------------------------------------------------------------------