badline 0.1.0 → 0.2.1
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 +4 -4
- data/CHANGELOG.md +185 -0
- data/CLAUDE.md +217 -0
- data/CODE_OF_CONDUCT.md +92 -0
- data/CONTRIBUTING.md +51 -0
- data/README.md +224 -31
- data/doc/pinned-behaviour.md +1117 -0
- data/exe/badline +22 -2
- data/exe/badline-sid +34 -0
- data/lib/badline/address_bus.rb +96 -25
- data/lib/badline/audio/aiff.rb +36 -0
- data/lib/badline/audio/bare_player.rb +129 -0
- data/lib/badline/audio/cli.rb +133 -0
- data/lib/badline/audio/console.rb +75 -0
- data/lib/badline/audio/jukebox.rb +82 -0
- data/lib/badline/audio/machine_player.rb +57 -0
- data/lib/badline/audio/options.rb +137 -0
- data/lib/badline/audio/pcm_writer.rb +57 -0
- data/lib/badline/audio/playback.rb +108 -0
- data/lib/badline/audio/renderer.rb +85 -0
- data/lib/badline/audio/sdl_sink.rb +102 -0
- data/lib/badline/audio/wav.rb +23 -0
- data/lib/badline/audio.rb +14 -0
- data/lib/badline/cartridge/action_replay.rb +111 -0
- data/lib/badline/cartridge/atomic_power.rb +24 -0
- data/lib/badline/cartridge/bank.rb +81 -0
- data/lib/badline/cartridge/comal80.rb +48 -0
- data/lib/badline/cartridge/dinamic.rb +34 -0
- data/lib/badline/cartridge/easy_flash.rb +122 -0
- data/lib/badline/cartridge/epyx_fastload.rb +71 -0
- data/lib/badline/cartridge/final_cartridge3.rb +77 -0
- data/lib/badline/cartridge/flash.rb +265 -0
- data/lib/badline/cartridge/freezer.rb +31 -0
- data/lib/badline/cartridge/fun_play.rb +38 -0
- data/lib/badline/cartridge/g_mod2.rb +62 -0
- data/lib/badline/cartridge/game_system.rb +40 -0
- data/lib/badline/cartridge/kcs_power.rb +72 -0
- data/lib/badline/cartridge/mach5.rb +34 -0
- data/lib/badline/cartridge/magic_desk.rb +6 -3
- data/lib/badline/cartridge/ocean.rb +11 -12
- data/lib/badline/cartridge/pagefox.rb +56 -0
- data/lib/badline/cartridge/retro_replay.rb +276 -0
- data/lib/badline/cartridge/rex_utility.rb +33 -0
- data/lib/badline/cartridge/rgcd.rb +48 -0
- data/lib/badline/cartridge/simons_basic.rb +40 -0
- data/lib/badline/cartridge/standard.rb +3 -19
- data/lib/badline/cartridge/super_games.rb +36 -0
- data/lib/badline/cartridge/westermann.rb +34 -0
- data/lib/badline/cartridge/zaxxon.rb +50 -0
- data/lib/badline/cartridge.rb +132 -16
- data/lib/badline/cia/interrupt_register.rb +102 -0
- data/lib/badline/cia/serial.rb +219 -0
- data/lib/badline/cia/timer.rb +43 -31
- data/lib/badline/cia.rb +138 -88
- data/lib/badline/color_memory.rb +14 -5
- data/lib/badline/computer.rb +97 -12
- data/lib/badline/control_ports.rb +54 -9
- data/lib/badline/cpu/addressing.rb +131 -0
- data/lib/badline/cpu/microcode.rb +169 -0
- data/lib/badline/cpu/operations.rb +88 -0
- data/lib/badline/cpu/stack_operations.rb +82 -0
- data/lib/badline/cpu.rb +109 -168
- data/lib/badline/cycleable.rb +3 -0
- data/lib/badline/datasette.rb +81 -0
- data/lib/badline/debug_register.rb +28 -0
- data/lib/badline/gui/application.rb +79 -18
- data/lib/badline/gui/gamepads.rb +97 -0
- data/lib/badline/gui/joy_map.rb +15 -7
- data/lib/badline/gui.rb +1 -0
- data/lib/badline/input/mouse1351.rb +41 -0
- data/lib/badline/input/paddles.rb +36 -0
- data/lib/badline/input.rb +4 -0
- data/lib/badline/instruction_set/arithmetic.rb +16 -21
- data/lib/badline/instruction_set/bitwise.rb +17 -22
- data/lib/badline/instruction_set/branch.rb +20 -19
- data/lib/badline/instruction_set/flag.rb +7 -7
- data/lib/badline/instruction_set/illegal.rb +14 -22
- data/lib/badline/instruction_set/inc_dec.rb +8 -10
- data/lib/badline/instruction_set/stack.rb +5 -57
- data/lib/badline/instruction_set/transfer.rb +9 -9
- data/lib/badline/instruction_set.rb +9 -31
- data/lib/badline/interrupts.rb +104 -0
- data/lib/badline/joystick.rb +9 -9
- data/lib/badline/kernal_trap/channel.rb +51 -0
- data/lib/badline/kernal_trap/drive/status.rb +49 -0
- data/lib/badline/kernal_trap/drive.rb +277 -0
- data/lib/badline/kernal_trap/file.rb +5 -25
- data/lib/badline/kernal_trap/load.rb +86 -24
- data/lib/badline/kernal_trap/routine.rb +51 -0
- data/lib/badline/kernal_trap/save.rb +46 -14
- data/lib/badline/kernal_trap/serial.rb +171 -0
- data/lib/badline/kernal_trap.rb +4 -0
- data/lib/badline/keyboard.rb +40 -16
- data/lib/badline/media.rb +48 -10
- data/lib/badline/memory.rb +5 -0
- data/lib/badline/roms/eapi/LICENSE.md +15 -0
- data/lib/badline/roms/eapi/README +41 -0
- data/lib/badline/roms/eapi/eapi-am29f040-14 +0 -0
- data/lib/badline/sid/decimator.rb +46 -0
- data/lib/badline/sid/envelope.rb +232 -0
- data/lib/badline/sid/filter.rb +213 -0
- data/lib/badline/sid/voice.rb +61 -0
- data/lib/badline/sid/waveform/combined.rb +126 -0
- data/lib/badline/sid/waveform/fast_forward.rb +105 -0
- data/lib/badline/sid/waveform/noise_writeback.rb +82 -0
- data/lib/badline/sid/waveform.rb +289 -0
- data/lib/badline/sid.rb +274 -7
- data/lib/badline/status.rb +251 -23
- data/lib/badline/storage/crt_file.rb +18 -2
- data/lib/badline/storage/d64_image.rb +6 -0
- data/lib/badline/storage/d71_image.rb +4 -0
- data/lib/badline/storage/d81_image.rb +4 -0
- data/lib/badline/storage/disk_image.rb +99 -13
- data/lib/badline/storage/host_directory.rb +30 -4
- data/lib/badline/storage/sid_file.rb +274 -0
- data/lib/badline/storage/song_lengths.rb +65 -0
- data/lib/badline/storage/t64.rb +67 -0
- data/lib/badline/storage/tap.rb +68 -0
- data/lib/badline/storage.rb +19 -0
- data/lib/badline/time_of_day.rb +105 -52
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic/bank.rb +14 -6
- data/lib/badline/vic/border_mask.rb +76 -0
- data/lib/badline/vic/collisions.rb +104 -0
- data/lib/badline/vic/color_patches.rb +33 -0
- data/lib/badline/vic/display_state.rb +92 -29
- data/lib/badline/vic/graphics_mode.rb +78 -53
- data/lib/badline/vic/graphics_shifter.rb +140 -0
- data/lib/badline/vic/register_log.rb +63 -0
- data/lib/badline/vic/registers.rb +3 -0
- data/lib/badline/vic/sequencer.rb +163 -137
- data/lib/badline/vic/sequencer_output.rb +116 -0
- data/lib/badline/vic/sprite/internal_bus.rb +49 -0
- data/lib/badline/vic/sprite/shifter.rb +129 -0
- data/lib/badline/vic/sprite.rb +275 -65
- data/lib/badline/vic/sprites.rb +220 -58
- data/lib/badline/vic.rb +389 -55
- data/lib/badline.rb +5 -0
- data/vendor/.gitkeep +0 -0
- metadata +104 -4
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class CPU
|
|
5
|
+
# Steps that read an instruction's operand bytes and work out the
|
|
6
|
+
# address it operates on, which they leave in @address. Also the
|
|
7
|
+
# single-step implied, accumulator and immediate instructions, and JMP.
|
|
8
|
+
module Addressing
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def op_implied_dummy
|
|
12
|
+
@memory.peek(@program_counter)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def op_implied_exec
|
|
16
|
+
@memory.peek(@program_counter)
|
|
17
|
+
send(@operation, nil, nil)
|
|
18
|
+
end_instruction
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def op_accumulator_exec
|
|
22
|
+
@memory.peek(@program_counter)
|
|
23
|
+
send(@operation, :accumulator, @a)
|
|
24
|
+
end_instruction
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def op_immediate_read
|
|
28
|
+
value = @memory.peek(@program_counter)
|
|
29
|
+
@program_counter = (@program_counter + 1) & 0xffff
|
|
30
|
+
send(@operation, nil, value)
|
|
31
|
+
end_instruction
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def op_zp
|
|
35
|
+
@address = @memory.peek(@program_counter)
|
|
36
|
+
@program_counter = (@program_counter + 1) & 0xffff
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def op_zp_x
|
|
40
|
+
@memory.peek(@address)
|
|
41
|
+
@address = (@address + @x) & 0xff
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def op_zp_y
|
|
45
|
+
@memory.peek(@address)
|
|
46
|
+
@address = (@address + @y) & 0xff
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def op_abs_low
|
|
50
|
+
@address = @memory.peek(@program_counter)
|
|
51
|
+
@program_counter = (@program_counter + 1) & 0xffff
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def op_abs_high
|
|
55
|
+
@address |= @memory.peek(@program_counter) << 8
|
|
56
|
+
@program_counter = (@program_counter + 1) & 0xffff
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def op_abs_high_x
|
|
60
|
+
high = @memory.peek(@program_counter)
|
|
61
|
+
@program_counter = (@program_counter + 1) & 0xffff
|
|
62
|
+
index_address(high, @x)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def op_abs_high_y
|
|
66
|
+
high = @memory.peek(@program_counter)
|
|
67
|
+
@program_counter = (@program_counter + 1) & 0xffff
|
|
68
|
+
index_address(high, @y)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def op_idx_dummy
|
|
72
|
+
@memory.peek(@dummy_address)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def op_zp_pointer
|
|
76
|
+
@pointer = @memory.peek(@program_counter)
|
|
77
|
+
@program_counter = (@program_counter + 1) & 0xffff
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def op_pointer_x
|
|
81
|
+
@memory.peek(@pointer)
|
|
82
|
+
@pointer = (@pointer + @x) & 0xff
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def op_pointer_low
|
|
86
|
+
@address = @memory.peek(@pointer)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def op_pointer_high
|
|
90
|
+
@address |= @memory.peek((@pointer + 1) & 0xff) << 8
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def op_pointer_high_y
|
|
94
|
+
index_address(@memory.peek((@pointer + 1) & 0xff), @y)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def op_jmp_high
|
|
98
|
+
@program_counter = @address | (@memory.peek(@program_counter) << 8)
|
|
99
|
+
end_instruction
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def op_indirect_low
|
|
103
|
+
@pointer = @address
|
|
104
|
+
@address = @memory.peek(@pointer)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# The pointer's high byte never increments, so JMP ($30FF) reads its
|
|
108
|
+
# target's high byte from $3000, not $3100.
|
|
109
|
+
def op_indirect_high
|
|
110
|
+
high = @memory.peek((@pointer & 0xff00) | ((@pointer + 1) & 0xff))
|
|
111
|
+
@program_counter = @address | (high << 8)
|
|
112
|
+
end_instruction
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Adds an index register to a base address. The 6502 adds to the low
|
|
116
|
+
# byte first and spends the next cycle reading from that address,
|
|
117
|
+
# before the carry reaches the high byte. Read instructions skip that
|
|
118
|
+
# cycle when there is no carry.
|
|
119
|
+
def index_address(high, index)
|
|
120
|
+
low = @address
|
|
121
|
+
@address = (((high << 8) | low) + index) & 0xffff
|
|
122
|
+
@boundary_crossed = (low + index) > 0xff
|
|
123
|
+
if @optional_dummy && !@boundary_crossed
|
|
124
|
+
@index += 1
|
|
125
|
+
else
|
|
126
|
+
@dummy_address = (high << 8) | ((low + index) & 0xff)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class CPU
|
|
5
|
+
class InvalidOpcodeError < StandardError; end
|
|
6
|
+
|
|
7
|
+
# Steps that write to memory. The CPU keeps running through BA on
|
|
8
|
+
# cycles that write, so Computer#cycle! asks before each cycle whether
|
|
9
|
+
# the next step is one of these.
|
|
10
|
+
WRITE_STEPS = %i[
|
|
11
|
+
op_mem_write op_rmw_write_original op_rmw_write_result op_push_exec op_jsr_push_high op_jsr_push_low
|
|
12
|
+
op_int_push_pch op_int_push_pcl op_int_push_status
|
|
13
|
+
].freeze
|
|
14
|
+
|
|
15
|
+
# The steps each addressing mode takes to work out the address an
|
|
16
|
+
# instruction operates on. They run after the opcode fetch.
|
|
17
|
+
ADDRESSING_STEPS = {
|
|
18
|
+
zeropage: %i[op_zp],
|
|
19
|
+
zeropage_x: %i[op_zp op_zp_x],
|
|
20
|
+
zeropage_y: %i[op_zp op_zp_y],
|
|
21
|
+
absolute: %i[op_abs_low op_abs_high],
|
|
22
|
+
absolute_x: %i[op_abs_low op_abs_high_x op_idx_dummy],
|
|
23
|
+
absolute_y: %i[op_abs_low op_abs_high_y op_idx_dummy],
|
|
24
|
+
indirect_x: %i[op_zp_pointer op_pointer_x op_pointer_low op_pointer_high],
|
|
25
|
+
indirect_y: %i[op_zp_pointer op_pointer_low op_pointer_high_y op_idx_dummy]
|
|
26
|
+
}.freeze
|
|
27
|
+
|
|
28
|
+
# The steps that read or write that address, once it is known.
|
|
29
|
+
OPERATION_STEPS = {
|
|
30
|
+
read: %i[op_mem_read],
|
|
31
|
+
write: %i[op_mem_write],
|
|
32
|
+
rmw: %i[op_rmw_read op_rmw_write_original op_rmw_write_result]
|
|
33
|
+
}.freeze
|
|
34
|
+
|
|
35
|
+
STORES = %i[sta stx sty sax sha shx shy tas].freeze
|
|
36
|
+
READ_MODIFY_WRITES = %i[asl lsr rol ror inc dec slo sre rla rra isc dcp].freeze
|
|
37
|
+
BRANCHES = %i[bcc bcs beq bmi bne bpl bvc bvs].freeze
|
|
38
|
+
|
|
39
|
+
# The addresses a 6502 reads after a JAM opcode, before it settles into
|
|
40
|
+
# JAMMED_PLAN.
|
|
41
|
+
JAM_ADDRESSES = [0xffff, 0xfffe, 0xfffe].freeze
|
|
42
|
+
|
|
43
|
+
# The plan between instructions: the next cycle fetches an opcode.
|
|
44
|
+
FETCH_PLAN = %i[op_fetch].freeze
|
|
45
|
+
|
|
46
|
+
IMPLIED_PLAN = %i[op_fetch op_implied_exec].freeze
|
|
47
|
+
ACCUMULATOR_PLAN = %i[op_fetch op_accumulator_exec].freeze
|
|
48
|
+
IMMEDIATE_PLAN = %i[op_fetch op_immediate_read].freeze
|
|
49
|
+
|
|
50
|
+
# $10, $30, $50, $70, $90, $B0, $D0, $F0 - relative - 2+ cycles.
|
|
51
|
+
# A branch that is not taken takes 2 cycles. A taken branch takes 3,
|
|
52
|
+
# or 4 when the target is on another page.
|
|
53
|
+
BRANCH_PLAN = %i[op_fetch op_relative op_branch op_branch_fixup].freeze
|
|
54
|
+
|
|
55
|
+
# $4C - absolute - 3 cycles
|
|
56
|
+
JMP_ABSOLUTE_PLAN = %i[op_fetch op_abs_low op_jmp_high].freeze
|
|
57
|
+
|
|
58
|
+
# $6C - indirect - 5 cycles
|
|
59
|
+
JMP_INDIRECT_PLAN = %i[op_fetch op_abs_low op_abs_high op_indirect_low op_indirect_high].freeze
|
|
60
|
+
|
|
61
|
+
# $20 - absolute - 6 cycles
|
|
62
|
+
JSR_PLAN = %i[op_fetch op_jsr_low op_jsr_dummy op_jsr_push_high op_jsr_push_low op_jsr_high].freeze
|
|
63
|
+
|
|
64
|
+
# $60 - implied - 6 cycles
|
|
65
|
+
RTS_PLAN = %i[op_fetch op_implied_dummy op_pull_dummy op_pull_low op_pull_high op_rts_fixup].freeze
|
|
66
|
+
|
|
67
|
+
# $40 - implied - 6 cycles
|
|
68
|
+
RTI_PLAN = %i[op_fetch op_implied_dummy op_pull_dummy op_rti_pull_status op_pull_low op_rti_pull_high].freeze
|
|
69
|
+
|
|
70
|
+
# $08, $48 - implied - 3 cycles
|
|
71
|
+
PUSH_PLAN = %i[op_fetch op_implied_dummy op_push_exec].freeze
|
|
72
|
+
|
|
73
|
+
# $28, $68 - implied - 4 cycles
|
|
74
|
+
PULL_PLAN = %i[op_fetch op_implied_dummy op_pull_dummy op_pull_exec].freeze
|
|
75
|
+
|
|
76
|
+
# $02, $12, $22, $32, $42, $52, $62, $72, $92, $B2, $D2, $F2 - illegal
|
|
77
|
+
JAM_PLAN = [:op_fetch, :op_implied_dummy, *Array.new(JAM_ADDRESSES.length, :op_jam)].freeze
|
|
78
|
+
|
|
79
|
+
# A jammed CPU reads $FFFF on every cycle until reset. It never reaches
|
|
80
|
+
# an instruction boundary, so a pending IRQ or NMI never starts.
|
|
81
|
+
JAMMED_PLAN = %i[op_jammed].freeze
|
|
82
|
+
|
|
83
|
+
# BRK and hardware interrupts push the return address and status, then
|
|
84
|
+
# read the new PC from a vector. Only the second step differs: BRK's
|
|
85
|
+
# selects the IRQ vector and sets the break flag it pushes.
|
|
86
|
+
#
|
|
87
|
+
# $00 - implied - 7 cycles (BRK)
|
|
88
|
+
INTERRUPT_PLAN = %i[
|
|
89
|
+
op_fetch op_implied_dummy op_int_push_pch op_int_push_pcl op_int_push_status
|
|
90
|
+
op_int_vector_low op_int_vector_high
|
|
91
|
+
].freeze
|
|
92
|
+
BRK_PLAN = %i[
|
|
93
|
+
op_fetch op_brk_dummy op_int_push_pch op_int_push_pcl op_int_push_status
|
|
94
|
+
op_int_vector_low op_int_vector_high
|
|
95
|
+
].freeze
|
|
96
|
+
|
|
97
|
+
SPECIAL_PLANS = {
|
|
98
|
+
brk: BRK_PLAN, jam: JAM_PLAN, jsr: JSR_PLAN, rts: RTS_PLAN,
|
|
99
|
+
rti: RTI_PLAN, pha: PUSH_PLAN, php: PUSH_PLAN,
|
|
100
|
+
pla: PULL_PLAN, plp: PULL_PLAN
|
|
101
|
+
}.freeze
|
|
102
|
+
|
|
103
|
+
# Everything the CPU needs to run one opcode:
|
|
104
|
+
# - +operation+: the instruction method to call, such as :lda
|
|
105
|
+
# - +plan+: the step to run on each cycle
|
|
106
|
+
# - +writes+: which of those steps write to memory
|
|
107
|
+
# - +optional_dummy+: true for indexed reads, which skip the extra
|
|
108
|
+
# cycle unless indexing crosses a page
|
|
109
|
+
Microcode = Struct.new(:operation, :plan, :writes, :optional_dummy) do
|
|
110
|
+
def self.table
|
|
111
|
+
Array.new(256) do |opcode|
|
|
112
|
+
instruction = Instruction.find(opcode)
|
|
113
|
+
raise InvalidOpcodeError, format("$%02x", opcode) unless instruction
|
|
114
|
+
|
|
115
|
+
build(instruction)
|
|
116
|
+
end.freeze
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def self.write_mask(plan)
|
|
120
|
+
plan.map { |step| WRITE_STEPS.include?(step) }.freeze
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def self.build(instruction)
|
|
124
|
+
plan = plan_for(instruction)
|
|
125
|
+
new(instruction.name, plan, write_mask(plan),
|
|
126
|
+
instruction.boundary_cycle?)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def self.plan_for(instruction)
|
|
130
|
+
case instruction.name
|
|
131
|
+
when :jmp
|
|
132
|
+
if instruction.addressing_mode == :indirect
|
|
133
|
+
JMP_INDIRECT_PLAN
|
|
134
|
+
else
|
|
135
|
+
JMP_ABSOLUTE_PLAN
|
|
136
|
+
end
|
|
137
|
+
when *BRANCHES then BRANCH_PLAN
|
|
138
|
+
else SPECIAL_PLANS[instruction.name] || addressed_plan(instruction)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def self.addressed_plan(instruction)
|
|
143
|
+
mode = instruction.addressing_mode
|
|
144
|
+
case mode
|
|
145
|
+
when :implied then IMPLIED_PLAN
|
|
146
|
+
when :accumulator then ACCUMULATOR_PLAN
|
|
147
|
+
when :immediate then IMMEDIATE_PLAN
|
|
148
|
+
else
|
|
149
|
+
[:op_fetch, *ADDRESSING_STEPS.fetch(mode),
|
|
150
|
+
*OPERATION_STEPS.fetch(operation_kind(instruction.name))].freeze
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def self.operation_kind(name)
|
|
155
|
+
return :write if STORES.include?(name)
|
|
156
|
+
return :rmw if READ_MODIFY_WRITES.include?(name)
|
|
157
|
+
|
|
158
|
+
:read
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
private_class_method :build, :plan_for, :addressed_plan, :operation_kind
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
FETCH_WRITES = Microcode.write_mask(FETCH_PLAN)
|
|
165
|
+
INTERRUPT_WRITES = Microcode.write_mask(INTERRUPT_PLAN)
|
|
166
|
+
JAMMED_WRITES = Microcode.write_mask(JAMMED_PLAN)
|
|
167
|
+
MICROCODE = Microcode.table
|
|
168
|
+
end
|
|
169
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class CPU
|
|
5
|
+
# Steps that read or write an instruction's operand once its address is
|
|
6
|
+
# known, plus the branch and JAM steps.
|
|
7
|
+
module Operations
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def op_mem_read
|
|
11
|
+
send(@operation, @address, @memory.peek(@address))
|
|
12
|
+
end_instruction
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def op_mem_write
|
|
16
|
+
send(@operation, @address, nil)
|
|
17
|
+
end_instruction
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Read-modify-write instructions (ASL, INC and the like) take three
|
|
21
|
+
# cycles here: read the value and compute the result, write the
|
|
22
|
+
# original value back, then write the result. The instruction method
|
|
23
|
+
# hands the result over through #write_modified.
|
|
24
|
+
def op_rmw_read
|
|
25
|
+
@value = @memory.peek(@address)
|
|
26
|
+
send(@operation, @address, @value)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def op_rmw_write_original
|
|
30
|
+
@memory.poke(@address, @value)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def op_rmw_write_result
|
|
34
|
+
@memory.poke(@address, @rmw_result)
|
|
35
|
+
end_instruction
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def op_relative
|
|
39
|
+
offset = @memory.peek(@program_counter)
|
|
40
|
+
@program_counter = (@program_counter + 1) & 0xffff
|
|
41
|
+
@address = (@program_counter + signed_int8(offset)) & 0xffff
|
|
42
|
+
@branch_taken = false
|
|
43
|
+
send(@operation, @address, nil)
|
|
44
|
+
if @branch_taken
|
|
45
|
+
# A taken branch within the same page doesn't poll on its last cycle.
|
|
46
|
+
@skip_poll = true if same_page?
|
|
47
|
+
else
|
|
48
|
+
end_instruction
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def op_branch
|
|
53
|
+
@memory.peek(@program_counter)
|
|
54
|
+
return unless same_page?
|
|
55
|
+
|
|
56
|
+
@program_counter = @address
|
|
57
|
+
end_instruction
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# A branch to another page spends an extra cycle reading from the target's low byte on the old page.
|
|
61
|
+
def op_branch_fixup
|
|
62
|
+
@memory.peek((@program_counter & 0xff00) | (@address & 0xff))
|
|
63
|
+
@program_counter = @address
|
|
64
|
+
end_instruction
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def same_page?
|
|
68
|
+
@address >> 8 == @program_counter >> 8
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Runs once for each address in JAM_ADDRESSES. By the time a step runs, @index already points at the next one.
|
|
72
|
+
def op_jam
|
|
73
|
+
position = @index - 2
|
|
74
|
+
@memory.peek(JAM_ADDRESSES[position - 1])
|
|
75
|
+
return unless position == JAM_ADDRESSES.length
|
|
76
|
+
|
|
77
|
+
@plan = JAMMED_PLAN
|
|
78
|
+
@writes = JAMMED_WRITES
|
|
79
|
+
@index = 0
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def op_jammed
|
|
83
|
+
@index = 0
|
|
84
|
+
@memory.peek(0xffff)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class CPU
|
|
5
|
+
# Steps that use the stack: PHA, PHP, PLA, PLP, JSR, RTS and RTI.
|
|
6
|
+
module StackOperations
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def op_push_exec
|
|
10
|
+
send(@operation, nil, nil)
|
|
11
|
+
end_instruction
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def op_pull_dummy
|
|
15
|
+
@memory.peek(stack_address)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def op_pull_exec
|
|
19
|
+
send(@operation, nil, nil)
|
|
20
|
+
end_instruction
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def op_pull_low
|
|
24
|
+
@stack_pointer = (@stack_pointer + 1) & 0xff
|
|
25
|
+
@address = @memory.peek(stack_address)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def op_pull_high
|
|
29
|
+
@stack_pointer = (@stack_pointer + 1) & 0xff
|
|
30
|
+
@address |= @memory.peek(stack_address) << 8
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def op_rts_fixup
|
|
34
|
+
@memory.peek(@address)
|
|
35
|
+
@program_counter = (@address + 1) & 0xffff
|
|
36
|
+
end_instruction
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def op_rti_pull_status
|
|
40
|
+
@stack_pointer = (@stack_pointer + 1) & 0xff
|
|
41
|
+
@status.value = @memory.peek(stack_address)
|
|
42
|
+
@status.break = false
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def op_rti_pull_high
|
|
46
|
+
@stack_pointer = (@stack_pointer + 1) & 0xff
|
|
47
|
+
@program_counter = @address | (@memory.peek(stack_address) << 8)
|
|
48
|
+
end_instruction
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def op_jsr_low
|
|
52
|
+
@address = @memory.peek(@program_counter)
|
|
53
|
+
@program_counter = (@program_counter + 2) & 0xffff
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def op_jsr_dummy
|
|
57
|
+
@memory.peek(stack_address)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def op_jsr_push_high
|
|
61
|
+
@memory.poke(stack_address, high_byte(return_address))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def op_jsr_push_low
|
|
65
|
+
@memory.poke(stack_address(-1), low_byte(return_address))
|
|
66
|
+
@stack_pointer = (@stack_pointer - 2) & 0xff
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# JSR reads its target's high byte after pushing the return address.
|
|
70
|
+
# A JSR running from the stack page can overwrite its own operand
|
|
71
|
+
# with that push, and then jumps to the overwritten address.
|
|
72
|
+
def op_jsr_high
|
|
73
|
+
@program_counter = @address | (@memory.peek(return_address) << 8)
|
|
74
|
+
end_instruction
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def return_address
|
|
78
|
+
(@program_counter - 1) & 0xffff
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|