mos6502-workbench 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +189 -0
- data/bin/assemble +122 -0
- data/bin/disassemble +58 -0
- data/bin/run_assembled_program +80 -0
- data/bin/run_klaus_functional_test +19 -0
- data/examples/README.md +147 -0
- data/examples/branching.asm +16 -0
- data/examples/countdown.asm +17 -0
- data/examples/labels_and_data.asm +28 -0
- data/examples/primes.asm +61 -0
- data/examples/simple_machine.rb +25 -0
- data/examples/traced_machine.rb +55 -0
- data/lib/mos6502/workbench/assembler.rb +725 -0
- data/lib/mos6502/workbench/bus.rb +264 -0
- data/lib/mos6502/workbench/cpu.rb +1292 -0
- data/lib/mos6502/workbench/device.rb +64 -0
- data/lib/mos6502/workbench/disassembler.rb +234 -0
- data/lib/mos6502/workbench/flags.rb +74 -0
- data/lib/mos6502/workbench/intel_hex.rb +116 -0
- data/lib/mos6502/workbench/machine.rb +140 -0
- data/lib/mos6502/workbench/memory.rb +159 -0
- data/lib/mos6502/workbench/registers.rb +19 -0
- data/lib/mos6502/workbench/tui.rb +537 -0
- data/lib/mos6502/workbench/version.rb +9 -0
- data/lib/mos6502/workbench.rb +12 -0
- metadata +126 -0
|
@@ -0,0 +1,1292 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'flags'
|
|
4
|
+
require_relative 'bus'
|
|
5
|
+
require_relative 'device'
|
|
6
|
+
require_relative 'memory'
|
|
7
|
+
require_relative 'registers'
|
|
8
|
+
|
|
9
|
+
module MOS6502
|
|
10
|
+
# Raised when the CPU encounters an opcode that is not present in the decode table.
|
|
11
|
+
class UnsupportedOpcode < StandardError; end
|
|
12
|
+
|
|
13
|
+
# A MOS 6502 CPU core with memory, registers, flags, and opcode dispatch.
|
|
14
|
+
#
|
|
15
|
+
# The implementation is intentionally stateful so it can be stepped instruction
|
|
16
|
+
# by instruction, which makes it a good fit for future tracing, disassembly,
|
|
17
|
+
# assembler integration, and TUI visualisation.
|
|
18
|
+
class CPU
|
|
19
|
+
include Registers
|
|
20
|
+
include Flags
|
|
21
|
+
include Memory
|
|
22
|
+
|
|
23
|
+
# Default address used for raw program loading when no reset vector is supplied.
|
|
24
|
+
DEFAULT_LOAD_ADDRESS = 0x0600
|
|
25
|
+
# Base address of the 6502 hardware stack.
|
|
26
|
+
STACK_BASE = 0x0100
|
|
27
|
+
# Vector used for non-maskable interrupts.
|
|
28
|
+
NMI_VECTOR = 0xfffa
|
|
29
|
+
# Vector read on reset.
|
|
30
|
+
RESET_VECTOR = 0xfffc
|
|
31
|
+
# Vector used for IRQ and BRK.
|
|
32
|
+
IRQ_VECTOR = 0xfffe
|
|
33
|
+
|
|
34
|
+
# Official opcode table mapping each supported byte to a mnemonic and
|
|
35
|
+
# addressing mode.
|
|
36
|
+
#
|
|
37
|
+
# @return [Hash{Integer => Array<Symbol>}]
|
|
38
|
+
OPCODES = {
|
|
39
|
+
0x00 => %i[brk implied],
|
|
40
|
+
0x01 => %i[ora indirect_x],
|
|
41
|
+
0x05 => %i[ora zero_page],
|
|
42
|
+
0x06 => %i[asl zero_page],
|
|
43
|
+
0x08 => %i[php implied],
|
|
44
|
+
0x09 => %i[ora immediate],
|
|
45
|
+
0x0a => %i[asl accumulator],
|
|
46
|
+
0x0d => %i[ora absolute],
|
|
47
|
+
0x0e => %i[asl absolute],
|
|
48
|
+
0x10 => %i[bpl relative],
|
|
49
|
+
0x11 => %i[ora indirect_y],
|
|
50
|
+
0x15 => %i[ora zero_page_x],
|
|
51
|
+
0x16 => %i[asl zero_page_x],
|
|
52
|
+
0x18 => %i[clc implied],
|
|
53
|
+
0x19 => %i[ora absolute_y],
|
|
54
|
+
0x1d => %i[ora absolute_x],
|
|
55
|
+
0x1e => %i[asl absolute_x],
|
|
56
|
+
0x20 => %i[jsr absolute],
|
|
57
|
+
0x21 => %i[and indirect_x],
|
|
58
|
+
0x24 => %i[bit zero_page],
|
|
59
|
+
0x25 => %i[and zero_page],
|
|
60
|
+
0x26 => %i[rol zero_page],
|
|
61
|
+
0x28 => %i[plp implied],
|
|
62
|
+
0x29 => %i[and immediate],
|
|
63
|
+
0x2a => %i[rol accumulator],
|
|
64
|
+
0x2c => %i[bit absolute],
|
|
65
|
+
0x2d => %i[and absolute],
|
|
66
|
+
0x2e => %i[rol absolute],
|
|
67
|
+
0x30 => %i[bmi relative],
|
|
68
|
+
0x31 => %i[and indirect_y],
|
|
69
|
+
0x35 => %i[and zero_page_x],
|
|
70
|
+
0x36 => %i[rol zero_page_x],
|
|
71
|
+
0x38 => %i[sec implied],
|
|
72
|
+
0x39 => %i[and absolute_y],
|
|
73
|
+
0x3d => %i[and absolute_x],
|
|
74
|
+
0x3e => %i[rol absolute_x],
|
|
75
|
+
0x40 => %i[rti implied],
|
|
76
|
+
0x41 => %i[eor indirect_x],
|
|
77
|
+
0x45 => %i[eor zero_page],
|
|
78
|
+
0x46 => %i[lsr zero_page],
|
|
79
|
+
0x48 => %i[pha implied],
|
|
80
|
+
0x49 => %i[eor immediate],
|
|
81
|
+
0x4a => %i[lsr accumulator],
|
|
82
|
+
0x4c => %i[jmp absolute],
|
|
83
|
+
0x4d => %i[eor absolute],
|
|
84
|
+
0x4e => %i[lsr absolute],
|
|
85
|
+
0x50 => %i[bvc relative],
|
|
86
|
+
0x51 => %i[eor indirect_y],
|
|
87
|
+
0x55 => %i[eor zero_page_x],
|
|
88
|
+
0x56 => %i[lsr zero_page_x],
|
|
89
|
+
0x58 => %i[cli implied],
|
|
90
|
+
0x59 => %i[eor absolute_y],
|
|
91
|
+
0x5d => %i[eor absolute_x],
|
|
92
|
+
0x5e => %i[lsr absolute_x],
|
|
93
|
+
0x60 => %i[rts implied],
|
|
94
|
+
0x61 => %i[adc indirect_x],
|
|
95
|
+
0x65 => %i[adc zero_page],
|
|
96
|
+
0x66 => %i[ror zero_page],
|
|
97
|
+
0x68 => %i[pla implied],
|
|
98
|
+
0x69 => %i[adc immediate],
|
|
99
|
+
0x6a => %i[ror accumulator],
|
|
100
|
+
0x6c => %i[jmp indirect],
|
|
101
|
+
0x6d => %i[adc absolute],
|
|
102
|
+
0x6e => %i[ror absolute],
|
|
103
|
+
0x70 => %i[bvs relative],
|
|
104
|
+
0x71 => %i[adc indirect_y],
|
|
105
|
+
0x75 => %i[adc zero_page_x],
|
|
106
|
+
0x76 => %i[ror zero_page_x],
|
|
107
|
+
0x78 => %i[sei implied],
|
|
108
|
+
0x79 => %i[adc absolute_y],
|
|
109
|
+
0x7d => %i[adc absolute_x],
|
|
110
|
+
0x7e => %i[ror absolute_x],
|
|
111
|
+
0x81 => %i[sta indirect_x],
|
|
112
|
+
0x84 => %i[sty zero_page],
|
|
113
|
+
0x85 => %i[sta zero_page],
|
|
114
|
+
0x86 => %i[stx zero_page],
|
|
115
|
+
0x88 => %i[dey implied],
|
|
116
|
+
0x8a => %i[txa implied],
|
|
117
|
+
0x8c => %i[sty absolute],
|
|
118
|
+
0x8d => %i[sta absolute],
|
|
119
|
+
0x8e => %i[stx absolute],
|
|
120
|
+
0x90 => %i[bcc relative],
|
|
121
|
+
0x91 => %i[sta indirect_y],
|
|
122
|
+
0x94 => %i[sty zero_page_x],
|
|
123
|
+
0x95 => %i[sta zero_page_x],
|
|
124
|
+
0x96 => %i[stx zero_page_y],
|
|
125
|
+
0x98 => %i[tya implied],
|
|
126
|
+
0x99 => %i[sta absolute_y],
|
|
127
|
+
0x9a => %i[txs implied],
|
|
128
|
+
0x9d => %i[sta absolute_x],
|
|
129
|
+
0xa0 => %i[ldy immediate],
|
|
130
|
+
0xa1 => %i[lda indirect_x],
|
|
131
|
+
0xa2 => %i[ldx immediate],
|
|
132
|
+
0xa4 => %i[ldy zero_page],
|
|
133
|
+
0xa5 => %i[lda zero_page],
|
|
134
|
+
0xa6 => %i[ldx zero_page],
|
|
135
|
+
0xa8 => %i[tay implied],
|
|
136
|
+
0xa9 => %i[lda immediate],
|
|
137
|
+
0xaa => %i[tax implied],
|
|
138
|
+
0xac => %i[ldy absolute],
|
|
139
|
+
0xad => %i[lda absolute],
|
|
140
|
+
0xae => %i[ldx absolute],
|
|
141
|
+
0xb0 => %i[bcs relative],
|
|
142
|
+
0xb1 => %i[lda indirect_y],
|
|
143
|
+
0xb4 => %i[ldy zero_page_x],
|
|
144
|
+
0xb5 => %i[lda zero_page_x],
|
|
145
|
+
0xb6 => %i[ldx zero_page_y],
|
|
146
|
+
0xb8 => %i[clv implied],
|
|
147
|
+
0xb9 => %i[lda absolute_y],
|
|
148
|
+
0xba => %i[tsx implied],
|
|
149
|
+
0xbc => %i[ldy absolute_x],
|
|
150
|
+
0xbd => %i[lda absolute_x],
|
|
151
|
+
0xbe => %i[ldx absolute_y],
|
|
152
|
+
0xc0 => %i[cpy immediate],
|
|
153
|
+
0xc1 => %i[cmp indirect_x],
|
|
154
|
+
0xc4 => %i[cpy zero_page],
|
|
155
|
+
0xc5 => %i[cmp zero_page],
|
|
156
|
+
0xc6 => %i[dec zero_page],
|
|
157
|
+
0xc8 => %i[iny implied],
|
|
158
|
+
0xc9 => %i[cmp immediate],
|
|
159
|
+
0xca => %i[dex implied],
|
|
160
|
+
0xcc => %i[cpy absolute],
|
|
161
|
+
0xcd => %i[cmp absolute],
|
|
162
|
+
0xce => %i[dec absolute],
|
|
163
|
+
0xd0 => %i[bne relative],
|
|
164
|
+
0xd1 => %i[cmp indirect_y],
|
|
165
|
+
0xd5 => %i[cmp zero_page_x],
|
|
166
|
+
0xd6 => %i[dec zero_page_x],
|
|
167
|
+
0xd8 => %i[cld implied],
|
|
168
|
+
0xd9 => %i[cmp absolute_y],
|
|
169
|
+
0xdd => %i[cmp absolute_x],
|
|
170
|
+
0xde => %i[dec absolute_x],
|
|
171
|
+
0xe0 => %i[cpx immediate],
|
|
172
|
+
0xe1 => %i[sbc indirect_x],
|
|
173
|
+
0xe4 => %i[cpx zero_page],
|
|
174
|
+
0xe5 => %i[sbc zero_page],
|
|
175
|
+
0xe6 => %i[inc zero_page],
|
|
176
|
+
0xe8 => %i[inx implied],
|
|
177
|
+
0xe9 => %i[sbc immediate],
|
|
178
|
+
0xea => %i[nop implied],
|
|
179
|
+
0xec => %i[cpx absolute],
|
|
180
|
+
0xed => %i[sbc absolute],
|
|
181
|
+
0xee => %i[inc absolute],
|
|
182
|
+
0xf0 => %i[beq relative],
|
|
183
|
+
0xf1 => %i[sbc indirect_y],
|
|
184
|
+
0xf5 => %i[sbc zero_page_x],
|
|
185
|
+
0xf6 => %i[inc zero_page_x],
|
|
186
|
+
0xf8 => %i[sed implied],
|
|
187
|
+
0xf9 => %i[sbc absolute_y],
|
|
188
|
+
0xfd => %i[sbc absolute_x],
|
|
189
|
+
0xfe => %i[inc absolute_x]
|
|
190
|
+
}.freeze
|
|
191
|
+
|
|
192
|
+
# @return [Integer] the number of instructions executed since the last reset
|
|
193
|
+
attr_reader :instruction_count
|
|
194
|
+
# @return [Integer] the number of base CPU cycles observed since the last reset
|
|
195
|
+
attr_reader :cycle_count
|
|
196
|
+
# @return [Integer, nil] the most recently executed opcode byte
|
|
197
|
+
attr_reader :last_opcode
|
|
198
|
+
# @return [Integer] the base cycle cost of the most recent instruction
|
|
199
|
+
attr_reader :last_cycles
|
|
200
|
+
# @return [Integer] the current program counter
|
|
201
|
+
attr_reader :program_counter
|
|
202
|
+
# @return [Integer] the current stack pointer offset within page `0x0100`
|
|
203
|
+
attr_reader :stack_pointer
|
|
204
|
+
# @return [Bus] the address bus used for all memory access
|
|
205
|
+
attr_reader :bus
|
|
206
|
+
|
|
207
|
+
BRANCH_MNEMONICS = %i[bpl bmi bvc bvs bcc bcs bne beq].freeze
|
|
208
|
+
TWO_CYCLE_IMPLIED_MNEMONICS = %i[
|
|
209
|
+
clc cld cli clv
|
|
210
|
+
sec sed sei
|
|
211
|
+
dex dey inx iny
|
|
212
|
+
nop
|
|
213
|
+
tax tay tsx txa txs tya
|
|
214
|
+
].freeze
|
|
215
|
+
|
|
216
|
+
# Creates a new CPU instance and powers it on.
|
|
217
|
+
#
|
|
218
|
+
# When no bus is supplied, the CPU installs a default standalone memory map
|
|
219
|
+
# consisting of 64 KiB of RAM and a synthetic reset vector pointing at
|
|
220
|
+
# {DEFAULT_LOAD_ADDRESS}. When a custom bus is supplied, callers can disable
|
|
221
|
+
# that convenience vector so a ROM-mapped machine can boot from real vectors.
|
|
222
|
+
#
|
|
223
|
+
# @param bus [Bus, nil] the address bus used by the CPU
|
|
224
|
+
# @param install_default_reset_vector [Boolean, nil] whether to install the standalone reset vector
|
|
225
|
+
# @return [void]
|
|
226
|
+
def initialize(bus: nil, install_default_reset_vector: nil)
|
|
227
|
+
@bus = bus || Bus.default
|
|
228
|
+
@step_subscribers = []
|
|
229
|
+
install_default_reset_vector = bus.nil? if install_default_reset_vector.nil?
|
|
230
|
+
power_on(install_default_reset_vector:)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Performs a full power-on sequence.
|
|
234
|
+
#
|
|
235
|
+
# Unlike {#reset}, this also clears RAM and installs the default reset vector.
|
|
236
|
+
#
|
|
237
|
+
# @param install_default_reset_vector [Boolean] whether to install the standalone reset vector
|
|
238
|
+
# @return [CPU] the CPU instance
|
|
239
|
+
def power_on(install_default_reset_vector: true)
|
|
240
|
+
memory_reset
|
|
241
|
+
write_word(RESET_VECTOR, DEFAULT_LOAD_ADDRESS) if install_default_reset_vector
|
|
242
|
+
reset
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Resets the CPU registers and control state without clearing memory.
|
|
246
|
+
#
|
|
247
|
+
# This mirrors real 6502 usage more closely than erasing RAM on every reset,
|
|
248
|
+
# which is important once programs are loaded from binaries or an assembler.
|
|
249
|
+
#
|
|
250
|
+
# @param program_counter [Integer, nil] an optional explicit reset address
|
|
251
|
+
# @return [CPU] the CPU instance
|
|
252
|
+
def reset(program_counter: nil)
|
|
253
|
+
registers_reset
|
|
254
|
+
flags_reset
|
|
255
|
+
@stack_pointer = 0xfd
|
|
256
|
+
@instruction_count = 0
|
|
257
|
+
@cycle_count = 0
|
|
258
|
+
@last_opcode = nil
|
|
259
|
+
@last_cycles = 0
|
|
260
|
+
self.interrupt = true
|
|
261
|
+
@program_counter = program_counter || read_word(RESET_VECTOR)
|
|
262
|
+
@program_counter &= 0xffff
|
|
263
|
+
self
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Loads program bytes into memory.
|
|
267
|
+
#
|
|
268
|
+
# @param program [String, #to_a] the raw bytes to write
|
|
269
|
+
# @param start_address [Integer] where to place the first byte
|
|
270
|
+
# @param set_reset_vector [Boolean] whether to point the reset vector at the program
|
|
271
|
+
# @return [Integer] the start address used for the load
|
|
272
|
+
def load(program, start_address: DEFAULT_LOAD_ADDRESS, set_reset_vector: true)
|
|
273
|
+
bytes = program.is_a?(String) ? program.bytes : program.to_a
|
|
274
|
+
bytes.each_with_index do |byte, offset|
|
|
275
|
+
write_byte(start_address + offset, byte)
|
|
276
|
+
end
|
|
277
|
+
write_word(RESET_VECTOR, start_address) if set_reset_vector
|
|
278
|
+
start_address
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# Loads an Intel HEX document directly into memory.
|
|
282
|
+
#
|
|
283
|
+
# The reset vector is pointed at the lowest loaded address by default, which
|
|
284
|
+
# matches the most common "load and run" workflow for assembled programs.
|
|
285
|
+
#
|
|
286
|
+
# @param source [String] the Intel HEX text to parse
|
|
287
|
+
# @param set_reset_vector [Boolean] whether to point the reset vector at the first record
|
|
288
|
+
# @return [Integer, nil] the lowest loaded address
|
|
289
|
+
# @raise [IntelHexError] if the document is malformed or unsupported
|
|
290
|
+
def load_intel_hex(source, set_reset_vector: true)
|
|
291
|
+
IntelHex.new.parse(source).load_into(self, set_reset_vector:)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Fetches, decodes, and executes a single instruction.
|
|
295
|
+
#
|
|
296
|
+
# @return [Integer] the executed opcode byte
|
|
297
|
+
# @raise [UnsupportedOpcode] if the opcode is not present in {OPCODES}
|
|
298
|
+
def step
|
|
299
|
+
instruction_address = @program_counter
|
|
300
|
+
@last_opcode = fetch_byte
|
|
301
|
+
instruction = OPCODES[@last_opcode]
|
|
302
|
+
raise UnsupportedOpcode, format('Opcode 0x%02X is not implemented', @last_opcode) unless instruction
|
|
303
|
+
|
|
304
|
+
mnemonic, mode = instruction
|
|
305
|
+
send(mnemonic, mode)
|
|
306
|
+
@instruction_count += 1
|
|
307
|
+
@last_cycles = base_cycles_for(mnemonic, mode)
|
|
308
|
+
@cycle_count += @last_cycles
|
|
309
|
+
emit_step(
|
|
310
|
+
address: instruction_address,
|
|
311
|
+
opcode: @last_opcode,
|
|
312
|
+
mnemonic:,
|
|
313
|
+
mode:,
|
|
314
|
+
cycles: @last_cycles,
|
|
315
|
+
snapshot: snapshot
|
|
316
|
+
)
|
|
317
|
+
@last_opcode
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# Executes a fixed number of instructions.
|
|
321
|
+
#
|
|
322
|
+
# @param max_instructions [Integer] how many instructions to execute
|
|
323
|
+
# @return [CPU] the CPU instance
|
|
324
|
+
def run(max_instructions:)
|
|
325
|
+
max_instructions.times { step }
|
|
326
|
+
self
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# Services a maskable interrupt request.
|
|
330
|
+
#
|
|
331
|
+
# If the interrupt-disable flag is set, the IRQ is ignored.
|
|
332
|
+
#
|
|
333
|
+
# @return [Boolean] true when the IRQ was taken, false when it was masked
|
|
334
|
+
def irq
|
|
335
|
+
return false if interrupt?
|
|
336
|
+
|
|
337
|
+
interrupt_sequence(vector: IRQ_VECTOR, break_flag: false)
|
|
338
|
+
@cycle_count += 7
|
|
339
|
+
true
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# Services a non-maskable interrupt.
|
|
343
|
+
#
|
|
344
|
+
# @return [Boolean] always true
|
|
345
|
+
def nmi
|
|
346
|
+
interrupt_sequence(vector: NMI_VECTOR, break_flag: false)
|
|
347
|
+
@cycle_count += 7
|
|
348
|
+
true
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# Captures the externally visible CPU state as a hash.
|
|
352
|
+
#
|
|
353
|
+
# This is intended as a convenient integration point for tracers, tests,
|
|
354
|
+
# and future UI layers.
|
|
355
|
+
#
|
|
356
|
+
# @return [Hash<Symbol, Integer, nil>] a snapshot of the current CPU state
|
|
357
|
+
def snapshot
|
|
358
|
+
{
|
|
359
|
+
accumulator: accumulator,
|
|
360
|
+
register_x: register_x,
|
|
361
|
+
register_y: register_y,
|
|
362
|
+
stack_pointer: stack_pointer,
|
|
363
|
+
program_counter: program_counter,
|
|
364
|
+
status: flags_encode,
|
|
365
|
+
instruction_count: instruction_count,
|
|
366
|
+
cycle_count: cycle_count,
|
|
367
|
+
last_cycles: last_cycles,
|
|
368
|
+
last_opcode: last_opcode
|
|
369
|
+
}
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# Registers a callback for each executed instruction.
|
|
373
|
+
#
|
|
374
|
+
# Subscribers receive a hash with the executed address, opcode, mnemonic,
|
|
375
|
+
# addressing mode, base cycles, and a post-instruction snapshot.
|
|
376
|
+
#
|
|
377
|
+
# @yieldparam event [Hash] the instruction event
|
|
378
|
+
# @return [Proc] the registered callback
|
|
379
|
+
def subscribe_steps(&block)
|
|
380
|
+
raise ArgumentError, 'A block is required' unless block
|
|
381
|
+
|
|
382
|
+
@step_subscribers << block
|
|
383
|
+
block
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
# Removes a previously registered step callback.
|
|
387
|
+
#
|
|
388
|
+
# @param subscriber [Proc] the callback returned by {#subscribe_steps}
|
|
389
|
+
# @return [Proc, nil] the removed callback
|
|
390
|
+
def unsubscribe_steps(subscriber)
|
|
391
|
+
@step_subscribers.delete(subscriber)
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
private
|
|
395
|
+
|
|
396
|
+
# Returns the base cycle cost for an opcode without dynamic penalties.
|
|
397
|
+
#
|
|
398
|
+
# These values intentionally exclude page-crossing and taken-branch penalties.
|
|
399
|
+
# They are useful for coarse timing and device ticking, but they are not yet a
|
|
400
|
+
# full cycle-accurate timing model.
|
|
401
|
+
#
|
|
402
|
+
# @param mnemonic [Symbol] the decoded mnemonic
|
|
403
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
404
|
+
# @return [Integer] the base cycle count
|
|
405
|
+
def base_cycles_for(mnemonic, mode)
|
|
406
|
+
return 7 if mnemonic == :brk
|
|
407
|
+
return 6 if %i[jsr rts rti].include?(mnemonic)
|
|
408
|
+
return { absolute: 3, indirect: 5 }.fetch(mode) if mnemonic == :jmp
|
|
409
|
+
return 3 if %i[pha php].include?(mnemonic)
|
|
410
|
+
return 4 if %i[pla plp].include?(mnemonic)
|
|
411
|
+
return shift_cycles(mode) if %i[asl lsr rol ror].include?(mnemonic)
|
|
412
|
+
return inc_dec_cycles(mode) if %i[inc dec].include?(mnemonic)
|
|
413
|
+
return store_cycles(mnemonic, mode) if %i[sta stx sty].include?(mnemonic)
|
|
414
|
+
return bit_cycles(mode) if mnemonic == :bit
|
|
415
|
+
return ldy_cycles(mode) if mnemonic == :ldy
|
|
416
|
+
return ldx_cycles(mode) if mnemonic == :ldx
|
|
417
|
+
return compare_register_cycles(mode) if %i[cpy cpx].include?(mnemonic)
|
|
418
|
+
return 2 if BRANCH_MNEMONICS.include?(mnemonic)
|
|
419
|
+
return 2 if TWO_CYCLE_IMPLIED_MNEMONICS.include?(mnemonic)
|
|
420
|
+
|
|
421
|
+
default_mode_cycles(mnemonic, mode)
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
# Broadcasts an executed-instruction event to registered subscribers.
|
|
425
|
+
#
|
|
426
|
+
# @param event [Hash] the instruction event payload
|
|
427
|
+
# @return [void]
|
|
428
|
+
def emit_step(event)
|
|
429
|
+
@step_subscribers.each { |subscriber| subscriber.call(event) }
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
# Returns the base timing for shift and rotate operations.
|
|
433
|
+
#
|
|
434
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
435
|
+
# @return [Integer] the base cycle count
|
|
436
|
+
def shift_cycles(mode)
|
|
437
|
+
{
|
|
438
|
+
accumulator: 2,
|
|
439
|
+
zero_page: 5,
|
|
440
|
+
zero_page_x: 6,
|
|
441
|
+
absolute: 6,
|
|
442
|
+
absolute_x: 7
|
|
443
|
+
}.fetch(mode)
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
# Returns the base timing for increment and decrement memory operations.
|
|
447
|
+
#
|
|
448
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
449
|
+
# @return [Integer] the base cycle count
|
|
450
|
+
def inc_dec_cycles(mode)
|
|
451
|
+
{
|
|
452
|
+
zero_page: 5,
|
|
453
|
+
zero_page_x: 6,
|
|
454
|
+
absolute: 6,
|
|
455
|
+
absolute_x: 7
|
|
456
|
+
}.fetch(mode)
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
# Returns the base timing for store instructions.
|
|
460
|
+
#
|
|
461
|
+
# @param mnemonic [Symbol] the decoded mnemonic
|
|
462
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
463
|
+
# @return [Integer] the base cycle count
|
|
464
|
+
def store_cycles(mnemonic, mode)
|
|
465
|
+
{
|
|
466
|
+
sta: {
|
|
467
|
+
zero_page: 3,
|
|
468
|
+
zero_page_x: 4,
|
|
469
|
+
absolute: 4,
|
|
470
|
+
absolute_x: 5,
|
|
471
|
+
absolute_y: 5,
|
|
472
|
+
indirect_x: 6,
|
|
473
|
+
indirect_y: 6
|
|
474
|
+
},
|
|
475
|
+
stx: {
|
|
476
|
+
zero_page: 3,
|
|
477
|
+
zero_page_y: 4,
|
|
478
|
+
absolute: 4
|
|
479
|
+
},
|
|
480
|
+
sty: {
|
|
481
|
+
zero_page: 3,
|
|
482
|
+
zero_page_x: 4,
|
|
483
|
+
absolute: 4
|
|
484
|
+
}
|
|
485
|
+
}.fetch(mnemonic).fetch(mode)
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
# Returns the base timing for `BIT`.
|
|
489
|
+
#
|
|
490
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
491
|
+
# @return [Integer] the base cycle count
|
|
492
|
+
def bit_cycles(mode)
|
|
493
|
+
{ zero_page: 3, absolute: 4 }.fetch(mode)
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
# Returns the base timing for `LDY`.
|
|
497
|
+
#
|
|
498
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
499
|
+
# @return [Integer] the base cycle count
|
|
500
|
+
def ldy_cycles(mode)
|
|
501
|
+
{
|
|
502
|
+
immediate: 2,
|
|
503
|
+
zero_page: 3,
|
|
504
|
+
zero_page_x: 4,
|
|
505
|
+
absolute: 4,
|
|
506
|
+
absolute_x: 4
|
|
507
|
+
}.fetch(mode)
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
# Returns the base timing for `LDX`.
|
|
511
|
+
#
|
|
512
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
513
|
+
# @return [Integer] the base cycle count
|
|
514
|
+
def ldx_cycles(mode)
|
|
515
|
+
{
|
|
516
|
+
immediate: 2,
|
|
517
|
+
zero_page: 3,
|
|
518
|
+
zero_page_y: 4,
|
|
519
|
+
absolute: 4,
|
|
520
|
+
absolute_y: 4
|
|
521
|
+
}.fetch(mode)
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
# Returns the base timing for `CPX` and `CPY`.
|
|
525
|
+
#
|
|
526
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
527
|
+
# @return [Integer] the base cycle count
|
|
528
|
+
def compare_register_cycles(mode)
|
|
529
|
+
{
|
|
530
|
+
immediate: 2,
|
|
531
|
+
zero_page: 3,
|
|
532
|
+
absolute: 4
|
|
533
|
+
}.fetch(mode)
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
# Returns the default base timing for operand-reading instructions.
|
|
537
|
+
#
|
|
538
|
+
# @param mnemonic [Symbol] the decoded mnemonic
|
|
539
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
540
|
+
# @return [Integer] the base cycle count
|
|
541
|
+
# @raise [ArgumentError] when no timing is defined for the mnemonic/mode pair
|
|
542
|
+
def default_mode_cycles(mnemonic, mode)
|
|
543
|
+
{
|
|
544
|
+
immediate: 2,
|
|
545
|
+
zero_page: 3,
|
|
546
|
+
zero_page_x: 4,
|
|
547
|
+
zero_page_y: 4,
|
|
548
|
+
absolute: 4,
|
|
549
|
+
absolute_x: 4,
|
|
550
|
+
absolute_y: 4,
|
|
551
|
+
indirect_x: 6,
|
|
552
|
+
indirect_y: 5
|
|
553
|
+
}.fetch(mode)
|
|
554
|
+
rescue KeyError
|
|
555
|
+
raise ArgumentError, "No base cycle timing is defined for #{mnemonic} #{mode}"
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
# Adds the operand and carry flag into the accumulator.
|
|
559
|
+
#
|
|
560
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
561
|
+
# @return [void]
|
|
562
|
+
def adc(mode)
|
|
563
|
+
add_with_carry(read_operand(mode))
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
# Performs a bitwise AND between the accumulator and the operand.
|
|
567
|
+
#
|
|
568
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
569
|
+
# @return [void]
|
|
570
|
+
def and(mode)
|
|
571
|
+
self.accumulator = set_zero_and_negative(accumulator & read_operand(mode))
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
# Arithmetic shift left.
|
|
575
|
+
#
|
|
576
|
+
# The top bit moves into carry and the result is shifted left by one.
|
|
577
|
+
#
|
|
578
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
579
|
+
# @return [void]
|
|
580
|
+
def asl(mode)
|
|
581
|
+
shift(mode) do |value|
|
|
582
|
+
self.carry = value.anybits?(0x80)
|
|
583
|
+
(value << 1) & 0xff
|
|
584
|
+
end
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
# Branches when the carry flag is clear.
|
|
588
|
+
#
|
|
589
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
590
|
+
# @return [void]
|
|
591
|
+
def bcc(mode)
|
|
592
|
+
branch_if(mode) { !carry? }
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
# Branches when the carry flag is set.
|
|
596
|
+
#
|
|
597
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
598
|
+
# @return [void]
|
|
599
|
+
def bcs(mode)
|
|
600
|
+
branch_if(mode) { carry? }
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
# Branches when the zero flag is set.
|
|
604
|
+
#
|
|
605
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
606
|
+
# @return [void]
|
|
607
|
+
def beq(mode)
|
|
608
|
+
branch_if(mode) { zero? }
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
# Tests bits in memory against the accumulator.
|
|
612
|
+
#
|
|
613
|
+
# `BIT` does not store a result; it updates zero from `A & M`, and copies
|
|
614
|
+
# bits 7 and 6 of the operand into negative and overflow.
|
|
615
|
+
#
|
|
616
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
617
|
+
# @return [void]
|
|
618
|
+
def bit(mode)
|
|
619
|
+
value = read_operand(mode)
|
|
620
|
+
self.zero = accumulator.nobits?(value)
|
|
621
|
+
self.overflow = value.anybits?(0x40)
|
|
622
|
+
self.negative = value.anybits?(0x80)
|
|
623
|
+
end
|
|
624
|
+
|
|
625
|
+
# Branches when the negative flag is set.
|
|
626
|
+
#
|
|
627
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
628
|
+
# @return [void]
|
|
629
|
+
def bmi(mode)
|
|
630
|
+
branch_if(mode) { negative? }
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
# Branches when the zero flag is clear.
|
|
634
|
+
#
|
|
635
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
636
|
+
# @return [void]
|
|
637
|
+
def bne(mode)
|
|
638
|
+
branch_if(mode) { !zero? }
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
# Branches when the negative flag is clear.
|
|
642
|
+
#
|
|
643
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
644
|
+
# @return [void]
|
|
645
|
+
def bpl(mode)
|
|
646
|
+
branch_if(mode) { !negative? }
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
# Software interrupt.
|
|
650
|
+
#
|
|
651
|
+
# `BRK` pushes the return address and status register, sets interrupt disable,
|
|
652
|
+
# and loads the IRQ/BRK vector.
|
|
653
|
+
#
|
|
654
|
+
# @param _mode [Symbol] the decoded addressing mode
|
|
655
|
+
# @return [void]
|
|
656
|
+
def brk(_mode)
|
|
657
|
+
@program_counter = (@program_counter + 1) & 0xffff
|
|
658
|
+
interrupt_sequence(vector: IRQ_VECTOR, break_flag: true)
|
|
659
|
+
end
|
|
660
|
+
|
|
661
|
+
# Branches when the overflow flag is clear.
|
|
662
|
+
#
|
|
663
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
664
|
+
# @return [void]
|
|
665
|
+
def bvc(mode)
|
|
666
|
+
branch_if(mode) { !overflow? }
|
|
667
|
+
end
|
|
668
|
+
|
|
669
|
+
# Branches when the overflow flag is set.
|
|
670
|
+
#
|
|
671
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
672
|
+
# @return [void]
|
|
673
|
+
def bvs(mode)
|
|
674
|
+
branch_if(mode) { overflow? }
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
# Clears the carry flag.
|
|
678
|
+
#
|
|
679
|
+
# @param _mode [Symbol] the decoded addressing mode
|
|
680
|
+
# @return [void]
|
|
681
|
+
def clc(_mode)
|
|
682
|
+
self.carry = false
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
# Clears the decimal mode flag.
|
|
686
|
+
#
|
|
687
|
+
# @param _mode [Symbol] the decoded addressing mode
|
|
688
|
+
# @return [void]
|
|
689
|
+
def cld(_mode)
|
|
690
|
+
self.decimal = false
|
|
691
|
+
end
|
|
692
|
+
|
|
693
|
+
# Clears the interrupt-disable flag.
|
|
694
|
+
#
|
|
695
|
+
# @param _mode [Symbol] the decoded addressing mode
|
|
696
|
+
# @return [void]
|
|
697
|
+
def cli(_mode)
|
|
698
|
+
self.interrupt = false
|
|
699
|
+
end
|
|
700
|
+
|
|
701
|
+
# Clears the overflow flag.
|
|
702
|
+
#
|
|
703
|
+
# @param _mode [Symbol] the decoded addressing mode
|
|
704
|
+
# @return [void]
|
|
705
|
+
def clv(_mode)
|
|
706
|
+
self.overflow = false
|
|
707
|
+
end
|
|
708
|
+
|
|
709
|
+
# Compares the accumulator with the operand.
|
|
710
|
+
#
|
|
711
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
712
|
+
# @return [void]
|
|
713
|
+
def cmp(mode)
|
|
714
|
+
compare(accumulator, read_operand(mode))
|
|
715
|
+
end
|
|
716
|
+
|
|
717
|
+
# Compares the X register with the operand.
|
|
718
|
+
#
|
|
719
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
720
|
+
# @return [void]
|
|
721
|
+
def cpx(mode)
|
|
722
|
+
compare(register_x, read_operand(mode))
|
|
723
|
+
end
|
|
724
|
+
|
|
725
|
+
# Compares the Y register with the operand.
|
|
726
|
+
#
|
|
727
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
728
|
+
# @return [void]
|
|
729
|
+
def cpy(mode)
|
|
730
|
+
compare(register_y, read_operand(mode))
|
|
731
|
+
end
|
|
732
|
+
|
|
733
|
+
# Decrements a memory location by one.
|
|
734
|
+
#
|
|
735
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
736
|
+
# @return [void]
|
|
737
|
+
def dec(mode)
|
|
738
|
+
write_operand(mode) { |value| set_zero_and_negative((value - 1) & 0xff) }
|
|
739
|
+
end
|
|
740
|
+
|
|
741
|
+
# Decrements the X register by one.
|
|
742
|
+
#
|
|
743
|
+
# @param _mode [Symbol] the decoded addressing mode
|
|
744
|
+
# @return [void]
|
|
745
|
+
def dex(_mode)
|
|
746
|
+
self.register_x = set_zero_and_negative((register_x - 1) & 0xff)
|
|
747
|
+
end
|
|
748
|
+
|
|
749
|
+
# Decrements the Y register by one.
|
|
750
|
+
#
|
|
751
|
+
# @param _mode [Symbol] the decoded addressing mode
|
|
752
|
+
# @return [void]
|
|
753
|
+
def dey(_mode)
|
|
754
|
+
self.register_y = set_zero_and_negative((register_y - 1) & 0xff)
|
|
755
|
+
end
|
|
756
|
+
|
|
757
|
+
# Performs a bitwise exclusive OR with the accumulator.
|
|
758
|
+
#
|
|
759
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
760
|
+
# @return [void]
|
|
761
|
+
def eor(mode)
|
|
762
|
+
self.accumulator = set_zero_and_negative(accumulator ^ read_operand(mode))
|
|
763
|
+
end
|
|
764
|
+
|
|
765
|
+
# Increments a memory location by one.
|
|
766
|
+
#
|
|
767
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
768
|
+
# @return [void]
|
|
769
|
+
def inc(mode)
|
|
770
|
+
write_operand(mode) { |value| set_zero_and_negative((value + 1) & 0xff) }
|
|
771
|
+
end
|
|
772
|
+
|
|
773
|
+
# Increments the X register by one.
|
|
774
|
+
#
|
|
775
|
+
# @param _mode [Symbol] the decoded addressing mode
|
|
776
|
+
# @return [void]
|
|
777
|
+
def inx(_mode)
|
|
778
|
+
self.register_x = set_zero_and_negative((register_x + 1) & 0xff)
|
|
779
|
+
end
|
|
780
|
+
|
|
781
|
+
# Increments the Y register by one.
|
|
782
|
+
#
|
|
783
|
+
# @param _mode [Symbol] the decoded addressing mode
|
|
784
|
+
# @return [void]
|
|
785
|
+
def iny(_mode)
|
|
786
|
+
self.register_y = set_zero_and_negative((register_y + 1) & 0xff)
|
|
787
|
+
end
|
|
788
|
+
|
|
789
|
+
# Jumps to a new program counter.
|
|
790
|
+
#
|
|
791
|
+
# `JMP` supports both absolute and indirect addressing. The indirect form
|
|
792
|
+
# preserves the original 6502 page-wrap bug.
|
|
793
|
+
#
|
|
794
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
795
|
+
# @return [void]
|
|
796
|
+
def jmp(mode)
|
|
797
|
+
@program_counter = if mode == :indirect
|
|
798
|
+
read_indirect_word(fetch_word)
|
|
799
|
+
else
|
|
800
|
+
fetch_word
|
|
801
|
+
end
|
|
802
|
+
end
|
|
803
|
+
|
|
804
|
+
# Jumps to a subroutine after pushing the return address.
|
|
805
|
+
#
|
|
806
|
+
# @param _mode [Symbol] the decoded addressing mode
|
|
807
|
+
# @return [void]
|
|
808
|
+
def jsr(_mode)
|
|
809
|
+
target = fetch_word
|
|
810
|
+
push_word((@program_counter - 1) & 0xffff)
|
|
811
|
+
@program_counter = target
|
|
812
|
+
end
|
|
813
|
+
|
|
814
|
+
# Loads the accumulator from memory or an immediate operand.
|
|
815
|
+
#
|
|
816
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
817
|
+
# @return [void]
|
|
818
|
+
def lda(mode)
|
|
819
|
+
self.accumulator = set_zero_and_negative(read_operand(mode))
|
|
820
|
+
end
|
|
821
|
+
|
|
822
|
+
# Loads the X register from memory or an immediate operand.
|
|
823
|
+
#
|
|
824
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
825
|
+
# @return [void]
|
|
826
|
+
def ldx(mode)
|
|
827
|
+
self.register_x = set_zero_and_negative(read_operand(mode))
|
|
828
|
+
end
|
|
829
|
+
|
|
830
|
+
# Loads the Y register from memory or an immediate operand.
|
|
831
|
+
#
|
|
832
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
833
|
+
# @return [void]
|
|
834
|
+
def ldy(mode)
|
|
835
|
+
self.register_y = set_zero_and_negative(read_operand(mode))
|
|
836
|
+
end
|
|
837
|
+
|
|
838
|
+
# Logical shift right.
|
|
839
|
+
#
|
|
840
|
+
# The low bit moves into carry and zero is shifted into bit 7.
|
|
841
|
+
#
|
|
842
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
843
|
+
# @return [void]
|
|
844
|
+
def lsr(mode)
|
|
845
|
+
shift(mode) do |value|
|
|
846
|
+
self.carry = value.anybits?(0x01)
|
|
847
|
+
(value >> 1) & 0xff
|
|
848
|
+
end
|
|
849
|
+
end
|
|
850
|
+
|
|
851
|
+
# No operation.
|
|
852
|
+
#
|
|
853
|
+
# @param _mode [Symbol] the decoded addressing mode
|
|
854
|
+
# @return [void]
|
|
855
|
+
def nop(_mode); end
|
|
856
|
+
|
|
857
|
+
# Performs a bitwise OR with the accumulator.
|
|
858
|
+
#
|
|
859
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
860
|
+
# @return [void]
|
|
861
|
+
def ora(mode)
|
|
862
|
+
self.accumulator = set_zero_and_negative(accumulator | read_operand(mode))
|
|
863
|
+
end
|
|
864
|
+
|
|
865
|
+
# Pushes the accumulator onto the hardware stack.
|
|
866
|
+
#
|
|
867
|
+
# @param _mode [Symbol] the decoded addressing mode
|
|
868
|
+
# @return [void]
|
|
869
|
+
def pha(_mode)
|
|
870
|
+
push_byte(accumulator)
|
|
871
|
+
end
|
|
872
|
+
|
|
873
|
+
# Pushes the processor status register onto the hardware stack.
|
|
874
|
+
#
|
|
875
|
+
# `PHP` always pushes the break and high bits set, matching 6502 behaviour.
|
|
876
|
+
#
|
|
877
|
+
# @param _mode [Symbol] the decoded addressing mode
|
|
878
|
+
# @return [void]
|
|
879
|
+
def php(_mode)
|
|
880
|
+
push_byte((flags_encode & 0xef) | 0x30)
|
|
881
|
+
end
|
|
882
|
+
|
|
883
|
+
# Pulls the accumulator from the hardware stack.
|
|
884
|
+
#
|
|
885
|
+
# @param _mode [Symbol] the decoded addressing mode
|
|
886
|
+
# @return [void]
|
|
887
|
+
def pla(_mode)
|
|
888
|
+
self.accumulator = set_zero_and_negative(pull_byte)
|
|
889
|
+
end
|
|
890
|
+
|
|
891
|
+
# Pulls the processor status register from the hardware stack.
|
|
892
|
+
#
|
|
893
|
+
# @param _mode [Symbol] the decoded addressing mode
|
|
894
|
+
# @return [void]
|
|
895
|
+
def plp(_mode)
|
|
896
|
+
flags_decode(pull_byte | 0x20)
|
|
897
|
+
end
|
|
898
|
+
|
|
899
|
+
# Rotate left through the carry flag.
|
|
900
|
+
#
|
|
901
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
902
|
+
# @return [void]
|
|
903
|
+
def rol(mode)
|
|
904
|
+
shift(mode) do |value|
|
|
905
|
+
new_value = ((value << 1) | bit_value(carry?)) & 0xff
|
|
906
|
+
self.carry = value.anybits?(0x80)
|
|
907
|
+
new_value
|
|
908
|
+
end
|
|
909
|
+
end
|
|
910
|
+
|
|
911
|
+
# Rotate right through the carry flag.
|
|
912
|
+
#
|
|
913
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
914
|
+
# @return [void]
|
|
915
|
+
def ror(mode)
|
|
916
|
+
shift(mode) do |value|
|
|
917
|
+
new_value = ((bit_value(carry?) << 7) | (value >> 1)) & 0xff
|
|
918
|
+
self.carry = value.anybits?(0x01)
|
|
919
|
+
new_value
|
|
920
|
+
end
|
|
921
|
+
end
|
|
922
|
+
|
|
923
|
+
# Returns from an interrupt by restoring status and program counter.
|
|
924
|
+
#
|
|
925
|
+
# @param _mode [Symbol] the decoded addressing mode
|
|
926
|
+
# @return [void]
|
|
927
|
+
def rti(_mode)
|
|
928
|
+
flags_decode(pull_byte | 0x20)
|
|
929
|
+
@program_counter = pull_word
|
|
930
|
+
end
|
|
931
|
+
|
|
932
|
+
# Returns from a subroutine.
|
|
933
|
+
#
|
|
934
|
+
# @param _mode [Symbol] the decoded addressing mode
|
|
935
|
+
# @return [void]
|
|
936
|
+
def rts(_mode)
|
|
937
|
+
@program_counter = (pull_word + 1) & 0xffff
|
|
938
|
+
end
|
|
939
|
+
|
|
940
|
+
# Subtracts the operand and the inverted carry from the accumulator.
|
|
941
|
+
#
|
|
942
|
+
# On the 6502, `SBC` interprets the carry flag as "no borrow".
|
|
943
|
+
#
|
|
944
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
945
|
+
# @return [void]
|
|
946
|
+
def sbc(mode)
|
|
947
|
+
subtract_with_carry(read_operand(mode))
|
|
948
|
+
end
|
|
949
|
+
|
|
950
|
+
# Sets the carry flag.
|
|
951
|
+
#
|
|
952
|
+
# @param _mode [Symbol] the decoded addressing mode
|
|
953
|
+
# @return [void]
|
|
954
|
+
def sec(_mode)
|
|
955
|
+
self.carry = true
|
|
956
|
+
end
|
|
957
|
+
|
|
958
|
+
# Sets the decimal mode flag.
|
|
959
|
+
#
|
|
960
|
+
# @param _mode [Symbol] the decoded addressing mode
|
|
961
|
+
# @return [void]
|
|
962
|
+
def sed(_mode)
|
|
963
|
+
self.decimal = true
|
|
964
|
+
end
|
|
965
|
+
|
|
966
|
+
# Sets the interrupt-disable flag.
|
|
967
|
+
#
|
|
968
|
+
# @param _mode [Symbol] the decoded addressing mode
|
|
969
|
+
# @return [void]
|
|
970
|
+
def sei(_mode)
|
|
971
|
+
self.interrupt = true
|
|
972
|
+
end
|
|
973
|
+
|
|
974
|
+
# Stores the accumulator into memory.
|
|
975
|
+
#
|
|
976
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
977
|
+
# @return [void]
|
|
978
|
+
def sta(mode)
|
|
979
|
+
write_byte(resolve_address(mode), accumulator)
|
|
980
|
+
end
|
|
981
|
+
|
|
982
|
+
# Stores the X register into memory.
|
|
983
|
+
#
|
|
984
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
985
|
+
# @return [void]
|
|
986
|
+
def stx(mode)
|
|
987
|
+
write_byte(resolve_address(mode), register_x)
|
|
988
|
+
end
|
|
989
|
+
|
|
990
|
+
# Stores the Y register into memory.
|
|
991
|
+
#
|
|
992
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
993
|
+
# @return [void]
|
|
994
|
+
def sty(mode)
|
|
995
|
+
write_byte(resolve_address(mode), register_y)
|
|
996
|
+
end
|
|
997
|
+
|
|
998
|
+
# Transfers the accumulator into X.
|
|
999
|
+
#
|
|
1000
|
+
# @param _mode [Symbol] the decoded addressing mode
|
|
1001
|
+
# @return [void]
|
|
1002
|
+
def tax(_mode)
|
|
1003
|
+
self.register_x = set_zero_and_negative(accumulator)
|
|
1004
|
+
end
|
|
1005
|
+
|
|
1006
|
+
# Transfers the accumulator into Y.
|
|
1007
|
+
#
|
|
1008
|
+
# @param _mode [Symbol] the decoded addressing mode
|
|
1009
|
+
# @return [void]
|
|
1010
|
+
def tay(_mode)
|
|
1011
|
+
self.register_y = set_zero_and_negative(accumulator)
|
|
1012
|
+
end
|
|
1013
|
+
|
|
1014
|
+
# Transfers the stack pointer into X.
|
|
1015
|
+
#
|
|
1016
|
+
# @param _mode [Symbol] the decoded addressing mode
|
|
1017
|
+
# @return [void]
|
|
1018
|
+
def tsx(_mode)
|
|
1019
|
+
self.register_x = set_zero_and_negative(stack_pointer)
|
|
1020
|
+
end
|
|
1021
|
+
|
|
1022
|
+
# Transfers X into the accumulator.
|
|
1023
|
+
#
|
|
1024
|
+
# @param _mode [Symbol] the decoded addressing mode
|
|
1025
|
+
# @return [void]
|
|
1026
|
+
def txa(_mode)
|
|
1027
|
+
self.accumulator = set_zero_and_negative(register_x)
|
|
1028
|
+
end
|
|
1029
|
+
|
|
1030
|
+
# Transfers X into the stack pointer.
|
|
1031
|
+
#
|
|
1032
|
+
# @param _mode [Symbol] the decoded addressing mode
|
|
1033
|
+
# @return [void]
|
|
1034
|
+
def txs(_mode)
|
|
1035
|
+
@stack_pointer = register_x
|
|
1036
|
+
end
|
|
1037
|
+
|
|
1038
|
+
# Transfers Y into the accumulator.
|
|
1039
|
+
#
|
|
1040
|
+
# @param _mode [Symbol] the decoded addressing mode
|
|
1041
|
+
# @return [void]
|
|
1042
|
+
def tya(_mode)
|
|
1043
|
+
self.accumulator = set_zero_and_negative(register_y)
|
|
1044
|
+
end
|
|
1045
|
+
|
|
1046
|
+
# Core implementation for `ADC`.
|
|
1047
|
+
#
|
|
1048
|
+
# This supports both binary and decimal arithmetic, while computing carry,
|
|
1049
|
+
# overflow, zero, and negative according to 6502 conventions.
|
|
1050
|
+
#
|
|
1051
|
+
# @param value [Integer] the operand to add
|
|
1052
|
+
# @return [void]
|
|
1053
|
+
def add_with_carry(value)
|
|
1054
|
+
a = accumulator
|
|
1055
|
+
carry_in = bit_value(carry?)
|
|
1056
|
+
binary_sum = a + value + carry_in
|
|
1057
|
+
self.overflow = (~(a ^ value) & (a ^ binary_sum)).anybits?(0x80)
|
|
1058
|
+
|
|
1059
|
+
result = if decimal?
|
|
1060
|
+
decimal_sum = binary_sum
|
|
1061
|
+
decimal_sum += 0x06 if ((a & 0x0f) + (value & 0x0f) + carry_in) > 0x09
|
|
1062
|
+
decimal_sum += 0x60 if decimal_sum > 0x99
|
|
1063
|
+
self.carry = decimal_sum > 0x99
|
|
1064
|
+
decimal_sum & 0xff
|
|
1065
|
+
else
|
|
1066
|
+
self.carry = binary_sum > 0xff
|
|
1067
|
+
binary_sum & 0xff
|
|
1068
|
+
end
|
|
1069
|
+
|
|
1070
|
+
self.accumulator = set_zero_and_negative(result)
|
|
1071
|
+
end
|
|
1072
|
+
|
|
1073
|
+
# Core implementation for `SBC`.
|
|
1074
|
+
#
|
|
1075
|
+
# @param value [Integer] the operand to subtract
|
|
1076
|
+
# @return [void]
|
|
1077
|
+
def subtract_with_carry(value)
|
|
1078
|
+
a = accumulator
|
|
1079
|
+
borrow = carry? ? 0 : 1
|
|
1080
|
+
binary_difference = a - value - borrow
|
|
1081
|
+
self.overflow = ((a ^ binary_difference) & (a ^ value)).anybits?(0x80)
|
|
1082
|
+
self.carry = binary_difference >= 0
|
|
1083
|
+
|
|
1084
|
+
result = if decimal?
|
|
1085
|
+
low = (a & 0x0f) - (value & 0x0f) - borrow
|
|
1086
|
+
high = (a >> 4) - (value >> 4)
|
|
1087
|
+
if low.negative?
|
|
1088
|
+
low -= 0x06
|
|
1089
|
+
high -= 1
|
|
1090
|
+
end
|
|
1091
|
+
high -= 0x06 if high.negative?
|
|
1092
|
+
((high << 4) | (low & 0x0f)) & 0xff
|
|
1093
|
+
else
|
|
1094
|
+
binary_difference & 0xff
|
|
1095
|
+
end
|
|
1096
|
+
|
|
1097
|
+
self.accumulator = set_zero_and_negative(result)
|
|
1098
|
+
end
|
|
1099
|
+
|
|
1100
|
+
# Applies a relative branch offset when the supplied condition is true.
|
|
1101
|
+
#
|
|
1102
|
+
# @param _mode [Symbol] the decoded addressing mode
|
|
1103
|
+
# @yieldreturn [Boolean] whether the branch should be taken
|
|
1104
|
+
# @return [void]
|
|
1105
|
+
def branch_if(_mode)
|
|
1106
|
+
offset = signed_byte(fetch_byte)
|
|
1107
|
+
@program_counter = (@program_counter + offset) & 0xffff if yield
|
|
1108
|
+
end
|
|
1109
|
+
|
|
1110
|
+
# Shared implementation for compare instructions.
|
|
1111
|
+
#
|
|
1112
|
+
# `CMP`, `CPX`, and `CPY` set carry on `register >= operand` and update
|
|
1113
|
+
# zero and negative from the subtraction result without storing it.
|
|
1114
|
+
#
|
|
1115
|
+
# @param register_value [Integer] the register to compare
|
|
1116
|
+
# @param operand [Integer] the operand to compare against
|
|
1117
|
+
# @return [Integer] the 8-bit subtraction result
|
|
1118
|
+
def compare(register_value, operand)
|
|
1119
|
+
result = (register_value - operand) & 0xff
|
|
1120
|
+
self.carry = register_value >= operand
|
|
1121
|
+
self.zero = result.zero?
|
|
1122
|
+
self.negative = result.anybits?(0x80)
|
|
1123
|
+
result
|
|
1124
|
+
end
|
|
1125
|
+
|
|
1126
|
+
# Reads the byte at the program counter and advances it.
|
|
1127
|
+
#
|
|
1128
|
+
# @return [Integer] the fetched byte
|
|
1129
|
+
def fetch_byte
|
|
1130
|
+
byte = read_byte(@program_counter)
|
|
1131
|
+
@program_counter = (@program_counter + 1) & 0xffff
|
|
1132
|
+
byte
|
|
1133
|
+
end
|
|
1134
|
+
|
|
1135
|
+
# Reads a little-endian word at the program counter and advances it.
|
|
1136
|
+
#
|
|
1137
|
+
# @return [Integer] the fetched 16-bit value
|
|
1138
|
+
def fetch_word
|
|
1139
|
+
low = fetch_byte
|
|
1140
|
+
high = fetch_byte
|
|
1141
|
+
low | (high << 8)
|
|
1142
|
+
end
|
|
1143
|
+
|
|
1144
|
+
# Pushes interrupt state and transfers control to the supplied vector.
|
|
1145
|
+
#
|
|
1146
|
+
# @param vector [Integer] the interrupt vector address
|
|
1147
|
+
# @param break_flag [Boolean] whether the pushed status should include the break bit
|
|
1148
|
+
# @return [void]
|
|
1149
|
+
def interrupt_sequence(vector:, break_flag:)
|
|
1150
|
+
push_word(@program_counter)
|
|
1151
|
+
status = flags_encode & 0xef
|
|
1152
|
+
status |= 0x10 if break_flag
|
|
1153
|
+
push_byte(status | 0x20)
|
|
1154
|
+
self.interrupt = true
|
|
1155
|
+
self.break = break_flag
|
|
1156
|
+
@program_counter = read_word(vector)
|
|
1157
|
+
end
|
|
1158
|
+
|
|
1159
|
+
# Pulls a byte from the hardware stack.
|
|
1160
|
+
#
|
|
1161
|
+
# @return [Integer] the byte restored from the stack
|
|
1162
|
+
def pull_byte
|
|
1163
|
+
@stack_pointer = (@stack_pointer + 1) & 0xff
|
|
1164
|
+
read_byte(STACK_BASE + @stack_pointer)
|
|
1165
|
+
end
|
|
1166
|
+
|
|
1167
|
+
# Pulls a 16-bit return address from the hardware stack.
|
|
1168
|
+
#
|
|
1169
|
+
# @return [Integer] the restored word value
|
|
1170
|
+
def pull_word
|
|
1171
|
+
low = pull_byte
|
|
1172
|
+
high = pull_byte
|
|
1173
|
+
low | (high << 8)
|
|
1174
|
+
end
|
|
1175
|
+
|
|
1176
|
+
# Pushes a byte onto the hardware stack.
|
|
1177
|
+
#
|
|
1178
|
+
# @param value [Integer] the value to push
|
|
1179
|
+
# @return [void]
|
|
1180
|
+
def push_byte(value)
|
|
1181
|
+
write_byte(STACK_BASE + @stack_pointer, value)
|
|
1182
|
+
@stack_pointer = (@stack_pointer - 1) & 0xff
|
|
1183
|
+
end
|
|
1184
|
+
|
|
1185
|
+
# Pushes a 16-bit value onto the hardware stack, high byte first.
|
|
1186
|
+
#
|
|
1187
|
+
# @param value [Integer] the word to push
|
|
1188
|
+
# @return [void]
|
|
1189
|
+
def push_word(value)
|
|
1190
|
+
push_byte(value >> 8)
|
|
1191
|
+
push_byte(value)
|
|
1192
|
+
end
|
|
1193
|
+
|
|
1194
|
+
# Reads a word through the indirect `JMP` page-wrap bug.
|
|
1195
|
+
#
|
|
1196
|
+
# On a real 6502, when the indirect pointer ends at `xxFF`, the high byte is
|
|
1197
|
+
# fetched from `xx00` rather than the next page.
|
|
1198
|
+
#
|
|
1199
|
+
# @param address [Integer] the indirect pointer location
|
|
1200
|
+
# @return [Integer] the resolved target address
|
|
1201
|
+
def read_indirect_word(address)
|
|
1202
|
+
low = read_byte(address)
|
|
1203
|
+
high_address = (address & 0xff00) | ((address + 1) & 0x00ff)
|
|
1204
|
+
high = read_byte(high_address)
|
|
1205
|
+
low | (high << 8)
|
|
1206
|
+
end
|
|
1207
|
+
|
|
1208
|
+
# Reads an operand value according to its addressing mode.
|
|
1209
|
+
#
|
|
1210
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
1211
|
+
# @return [Integer] the fetched operand byte
|
|
1212
|
+
def read_operand(mode)
|
|
1213
|
+
return fetch_byte if mode == :immediate
|
|
1214
|
+
|
|
1215
|
+
read_byte(resolve_address(mode))
|
|
1216
|
+
end
|
|
1217
|
+
|
|
1218
|
+
# Resolves an addressing mode into a concrete memory address.
|
|
1219
|
+
#
|
|
1220
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
1221
|
+
# @return [Integer] the resolved memory address
|
|
1222
|
+
# @raise [ArgumentError] if the mode does not point to memory
|
|
1223
|
+
def resolve_address(mode)
|
|
1224
|
+
case mode
|
|
1225
|
+
when :zero_page
|
|
1226
|
+
fetch_byte
|
|
1227
|
+
when :zero_page_x
|
|
1228
|
+
(fetch_byte + register_x) & 0xff
|
|
1229
|
+
when :zero_page_y
|
|
1230
|
+
(fetch_byte + register_y) & 0xff
|
|
1231
|
+
when :absolute
|
|
1232
|
+
fetch_word
|
|
1233
|
+
when :absolute_x
|
|
1234
|
+
(fetch_word + register_x) & 0xffff
|
|
1235
|
+
when :absolute_y
|
|
1236
|
+
(fetch_word + register_y) & 0xffff
|
|
1237
|
+
when :indirect_x
|
|
1238
|
+
pointer = (fetch_byte + register_x) & 0xff
|
|
1239
|
+
read_byte(pointer) | (read_byte((pointer + 1) & 0xff) << 8)
|
|
1240
|
+
when :indirect_y
|
|
1241
|
+
pointer = fetch_byte
|
|
1242
|
+
((read_byte(pointer) | (read_byte((pointer + 1) & 0xff) << 8)) + register_y) & 0xffff
|
|
1243
|
+
else
|
|
1244
|
+
raise ArgumentError, "Address mode #{mode} does not resolve to a memory location"
|
|
1245
|
+
end
|
|
1246
|
+
end
|
|
1247
|
+
|
|
1248
|
+
# Applies zero and negative flag updates to an 8-bit value.
|
|
1249
|
+
#
|
|
1250
|
+
# @param value [Integer] the raw arithmetic or logic result
|
|
1251
|
+
# @return [Integer] the masked 8-bit result
|
|
1252
|
+
def set_zero_and_negative(value)
|
|
1253
|
+
result = value & 0xff
|
|
1254
|
+
self.zero = result.zero?
|
|
1255
|
+
self.negative = result.anybits?(0x80)
|
|
1256
|
+
result
|
|
1257
|
+
end
|
|
1258
|
+
|
|
1259
|
+
# Shared implementation for accumulator and memory shifts or rotates.
|
|
1260
|
+
#
|
|
1261
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
1262
|
+
# @yieldparam value [Integer] the current value to transform
|
|
1263
|
+
# @yieldreturn [Integer] the transformed value
|
|
1264
|
+
# @return [void]
|
|
1265
|
+
def shift(mode)
|
|
1266
|
+
if mode == :accumulator
|
|
1267
|
+
self.accumulator = set_zero_and_negative(yield(accumulator))
|
|
1268
|
+
else
|
|
1269
|
+
write_operand(mode) { |value| set_zero_and_negative(yield(value)) }
|
|
1270
|
+
end
|
|
1271
|
+
end
|
|
1272
|
+
|
|
1273
|
+
# Interprets a byte as a signed 8-bit relative offset.
|
|
1274
|
+
#
|
|
1275
|
+
# @param value [Integer] the raw branch displacement byte
|
|
1276
|
+
# @return [Integer] the signed offset
|
|
1277
|
+
def signed_byte(value)
|
|
1278
|
+
value >= 0x80 ? value - 0x100 : value
|
|
1279
|
+
end
|
|
1280
|
+
|
|
1281
|
+
# Writes a transformed value back to the addressed memory operand.
|
|
1282
|
+
#
|
|
1283
|
+
# @param mode [Symbol] the decoded addressing mode
|
|
1284
|
+
# @yieldparam value [Integer] the current memory value
|
|
1285
|
+
# @yieldreturn [Integer] the new memory value
|
|
1286
|
+
# @return [void]
|
|
1287
|
+
def write_operand(mode)
|
|
1288
|
+
address = resolve_address(mode)
|
|
1289
|
+
write_byte(address, yield(read_byte(address)))
|
|
1290
|
+
end
|
|
1291
|
+
end
|
|
1292
|
+
end
|