badline 0.6.0 → 0.7.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 +4 -4
- data/CHANGELOG.md +77 -0
- data/CLAUDE.md +10 -9
- data/README.md +135 -42
- data/doc/pinned-behaviour.md +131 -30
- data/exe/badline-ruby +28 -24
- data/lib/badline/address_bus/extra_sids.rb +30 -0
- data/lib/badline/address_bus/sid_slots.rb +42 -0
- data/lib/badline/address_bus.rb +15 -1
- data/lib/badline/audio/aiff.rb +2 -2
- data/lib/badline/audio/bare_player.rb +23 -12
- data/lib/badline/audio/cli.rb +167 -59
- data/lib/badline/audio/console.rb +2 -1
- data/lib/badline/audio/jukebox.rb +121 -36
- data/lib/badline/audio/machine_player.rb +25 -10
- data/lib/badline/audio/pcm_writer.rb +10 -6
- data/lib/badline/audio/playback.rb +6 -5
- data/lib/badline/audio/queued_tune.rb +89 -0
- data/lib/badline/audio/renderer.rb +57 -10
- data/lib/badline/audio/stereo.rb +97 -0
- data/lib/badline/audio/terminal.rb +39 -6
- data/lib/badline/audio/wav.rb +1 -1
- data/lib/badline/audio.rb +2 -3
- data/lib/badline/computer.rb +6 -3
- data/lib/badline/cpu/boundary.rb +54 -0
- data/lib/badline/cpu/core.rb +165 -0
- data/lib/badline/cpu.rb +3 -214
- data/lib/badline/drive1541/cpu.rb +29 -0
- data/lib/badline/drive1541.rb +1 -0
- data/lib/badline/ffi.rb +157 -0
- data/lib/badline/frontend/app.rb +225 -0
- data/lib/badline/frontend/audio_sink.rb +73 -0
- data/lib/badline/frontend/boot.rb +52 -0
- data/lib/badline/frontend/buttons.rb +90 -0
- data/lib/badline/frontend/controls.rb +163 -0
- data/lib/badline/frontend/drive_led.rb +51 -0
- data/lib/badline/frontend/filter_view.rb +96 -0
- data/lib/badline/frontend/frame_rate.rb +83 -0
- data/lib/badline/frontend/gamepads.rb +76 -0
- data/lib/badline/frontend/info_view.rb +120 -0
- data/lib/badline/frontend/keys.rb +48 -0
- data/lib/badline/frontend/pacer.rb +100 -0
- data/lib/badline/frontend/pad_port.rb +61 -0
- data/lib/badline/frontend/painter.rb +175 -0
- data/lib/badline/frontend/player_commands.rb +33 -0
- data/lib/badline/frontend/player_footer.rb +86 -0
- data/lib/badline/frontend/player_header.rb +60 -0
- data/lib/badline/frontend/player_screen.rb +124 -0
- data/lib/badline/frontend/player_state.rb +67 -0
- data/lib/badline/frontend/player_window.rb +228 -0
- data/lib/badline/frontend/scope.rb +42 -0
- data/lib/badline/frontend/screen.rb +65 -0
- data/lib/badline/frontend/screenshot.rb +22 -0
- data/lib/badline/frontend/sid_history.rb +175 -0
- data/lib/badline/frontend/sid_view.rb +140 -0
- data/lib/badline/frontend/snapshots.rb +60 -0
- data/lib/badline/frontend/sound.rb +153 -0
- data/lib/badline/frontend/timeline.rb +171 -0
- data/lib/badline/frontend/visualizer_view.rb +101 -0
- data/lib/badline/frontend.rb +32 -0
- data/lib/badline/media/queue.rb +158 -0
- data/lib/badline/media.rb +13 -8
- data/lib/badline/options/event.rb +94 -0
- data/lib/badline/options/help.rb +107 -0
- data/lib/badline/options/option.rb +29 -0
- data/lib/badline/options/table.rb +64 -0
- data/lib/badline/options/validation.rb +54 -0
- data/lib/badline/options.rb +204 -149
- data/lib/badline/reu/dma/saved_state.rb +50 -0
- data/lib/badline/reu/dma.rb +56 -47
- data/lib/badline/reu/saved_state.rb +2 -1
- data/lib/badline/reu.rb +19 -10
- data/lib/badline/sdl.rb +133 -51
- data/lib/badline/sid/catch_up.rb +11 -1
- data/lib/badline/sid/filter.rb +9 -0
- data/lib/badline/sid/voice.rb +6 -0
- data/lib/badline/sid/waveform/noise_writeback.rb +33 -11
- data/lib/badline/sid/waveform.rb +22 -8
- data/lib/badline/sid.rb +35 -3
- data/lib/badline/snapshot/state.rb +1 -1
- data/lib/badline/storage/hvsc.rb +41 -0
- data/lib/badline/storage/sid_file.rb +45 -25
- data/lib/badline/storage/song_lengths.rb +6 -29
- data/lib/badline/storage/stil/credit.rb +50 -0
- data/lib/badline/storage/stil.rb +126 -0
- data/lib/badline/storage.rb +8 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic/bank.rb +21 -0
- data/lib/badline/vic.rb +15 -6
- metadata +49 -19
- data/.claude/hooks/session-start.sh +0 -57
- data/.claude/settings.json +0 -14
- data/lib/badline/audio/sdl_sink.rb +0 -70
- data/lib/badline/audio/stream.rb +0 -123
- data/lib/badline/gui/application.rb +0 -243
- data/lib/badline/gui/drive_led_pane.rb +0 -29
- data/lib/badline/gui/gamepads.rb +0 -104
- data/lib/badline/gui/joy_map.rb +0 -27
- data/lib/badline/gui/key_map.rb +0 -39
- data/lib/badline/gui/palette.rb +0 -35
- data/lib/badline/gui/pane.rb +0 -36
- data/lib/badline/gui/screen_pane.rb +0 -46
- data/lib/badline/gui/snapshots.rb +0 -67
- data/lib/badline/gui/window.rb +0 -61
- data/lib/badline/gui.rb +0 -14
- data/lib/badline/sdl/events.rb +0 -49
- data/lib/badline/sdl/functions.rb +0 -74
- data/vendor/.gitkeep +0 -0
|
@@ -10,9 +10,13 @@ module Badline
|
|
|
10
10
|
# stty and poll(2) for the native badline.
|
|
11
11
|
class Terminal
|
|
12
12
|
KEYS = {
|
|
13
|
-
"n" => :next, "
|
|
14
|
-
"
|
|
13
|
+
"n" => :next, "p" => :previous,
|
|
14
|
+
"\e[C" => :next_subtune, "\e[D" => :previous_subtune,
|
|
15
15
|
" " => :pause,
|
|
16
|
+
"s" => :shuffle,
|
|
17
|
+
"l" => :loop,
|
|
18
|
+
"a" => :all_subtunes,
|
|
19
|
+
"," => :back, "." => :forward,
|
|
16
20
|
"q" => :quit
|
|
17
21
|
}.freeze
|
|
18
22
|
|
|
@@ -20,6 +24,8 @@ module Badline
|
|
|
20
24
|
@input = input
|
|
21
25
|
@output = output
|
|
22
26
|
@closed = false
|
|
27
|
+
@tune = 1
|
|
28
|
+
@tunes = 1
|
|
23
29
|
end
|
|
24
30
|
|
|
25
31
|
# Raw input and a hidden cursor for the length of the block.
|
|
@@ -46,12 +52,39 @@ module Badline
|
|
|
46
52
|
|
|
47
53
|
def header(lines)
|
|
48
54
|
lines.each { |line| @output.print "#{line}\r\n" }
|
|
49
|
-
@output.print "
|
|
55
|
+
@output.print "←/→ subtune n/p tune ,/. seek space pause s shuffle l loop a all subtunes q quit\r\n"
|
|
50
56
|
end
|
|
51
57
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
# Prints lines above the status line, such as the next tune's header.
|
|
59
|
+
def announce(lines)
|
|
60
|
+
lines.each { |line| @output.print "\r\e[K#{line}\r\n" }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# The tune's place in the queue, which the status line shows once
|
|
64
|
+
# there is more than one.
|
|
65
|
+
def place(tune, tunes)
|
|
66
|
+
@tune = tune
|
|
67
|
+
@tunes = tunes
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Where :seek asks the subtune to play from, in seconds. Only a window
|
|
71
|
+
# asks for it, by a click on the time the subtune has played.
|
|
72
|
+
def seek_to = 0.0
|
|
73
|
+
|
|
74
|
+
# The files dropped on a window since it last said :drop.
|
|
75
|
+
def dropped = []
|
|
76
|
+
|
|
77
|
+
# Hears of the STIL fields for the tune and for the subtune starting,
|
|
78
|
+
# which the terminal prints as the CLI announces them.
|
|
79
|
+
def stil(fields, subtune, subtune_fields) = [fields, subtune, subtune_fields]
|
|
80
|
+
|
|
81
|
+
# Hears of each subtune's renderer as it starts playing.
|
|
82
|
+
def playing(renderer) = renderer
|
|
83
|
+
|
|
84
|
+
def status(subtune:, subtunes:, elapsed:, length:, notes: [])
|
|
85
|
+
line = format("subtune %<subtune>d/%<subtunes>d %<elapsed>s / %<length>s",
|
|
86
|
+
subtune:, subtunes:, elapsed: clock(elapsed), length: clock(length))
|
|
87
|
+
line = format("tune %<tune>d/%<tunes>d %<line>s", tune: @tune, tunes: @tunes, line:) if @tunes > 1
|
|
55
88
|
@output.print "\r\e[K#{([line] + notes).join(' ')}"
|
|
56
89
|
@output.flush
|
|
57
90
|
end
|
data/lib/badline/audio/wav.rb
CHANGED
data/lib/badline/audio.rb
CHANGED
|
@@ -3,13 +3,12 @@
|
|
|
3
3
|
require "badline/audio/pcm_writer"
|
|
4
4
|
require "badline/audio/wav"
|
|
5
5
|
require "badline/audio/aiff"
|
|
6
|
+
require "badline/audio/stereo"
|
|
6
7
|
require "badline/audio/bare_player"
|
|
7
8
|
require "badline/audio/machine_player"
|
|
8
9
|
require "badline/audio/renderer"
|
|
9
10
|
require "badline/audio/playback"
|
|
10
|
-
require "badline/audio/sdl_sink"
|
|
11
|
-
require "badline/audio/stream"
|
|
12
11
|
require "badline/audio/terminal"
|
|
13
|
-
require "badline/audio/console"
|
|
14
12
|
require "badline/audio/jukebox"
|
|
13
|
+
require "badline/audio/queued_tune"
|
|
15
14
|
require "badline/audio/cli"
|
data/lib/badline/computer.rb
CHANGED
|
@@ -226,12 +226,15 @@ module Badline
|
|
|
226
226
|
@cpu.pending_write? || !@vic.ba_low? ? @cpu.cycle! : @cpu.stall!
|
|
227
227
|
end
|
|
228
228
|
|
|
229
|
+
# The REU's /DMA line takes the bus from the CPU from the cycle after
|
|
230
|
+
# it asks for it, even while it waits for BA, but RDY only halts the
|
|
231
|
+
# CPU on a read, so a write it makes then goes nowhere.
|
|
229
232
|
def dma_cycle!
|
|
230
233
|
writing = @cpu.pending_write?
|
|
231
|
-
@reu.dma_cycle!(@vic.
|
|
232
|
-
return @cpu.stall! if @reu.
|
|
234
|
+
@reu.dma_cycle!(@vic.ba_low?)
|
|
235
|
+
return writing ? @address_bus.cycle_cpu_off_bus(@cpu) : @cpu.stall! if @reu.dma?
|
|
233
236
|
|
|
234
|
-
@dma =
|
|
237
|
+
@dma = false
|
|
235
238
|
writing || !@vic.ba_low? ? @cpu.cycle! : @cpu.stall!
|
|
236
239
|
end
|
|
237
240
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class CPU
|
|
5
|
+
# The steps at an instruction boundary: the opcode fetch that starts
|
|
6
|
+
# an instruction, and the ends of one.
|
|
7
|
+
module Boundary
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
# The first cycle of every instruction. Starts an interrupt if one was
|
|
11
|
+
# pending when the previous instruction ended. Otherwise reads the
|
|
12
|
+
# opcode and switches to its plan.
|
|
13
|
+
def op_fetch
|
|
14
|
+
return start_interrupt if @boundary_nmi || @boundary_irq
|
|
15
|
+
|
|
16
|
+
run_traps
|
|
17
|
+
opcode = @memory.peek(@program_counter)
|
|
18
|
+
log(opcode) if @debug
|
|
19
|
+
micro = MICROCODE[opcode]
|
|
20
|
+
@operation = micro.operation
|
|
21
|
+
@plan = micro.plan
|
|
22
|
+
@writes = micro.writes
|
|
23
|
+
@optional_dummy = micro.optional_dummy
|
|
24
|
+
@index = 1
|
|
25
|
+
@boundary_crossed = false
|
|
26
|
+
@program_counter = (@program_counter + 1) & 0xffff
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def end_instruction
|
|
30
|
+
@instructions += 1
|
|
31
|
+
end_sequence
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Makes the next cycle an opcode fetch, and saves the pending interrupt
|
|
35
|
+
# flags for op_fetch. The next cycle polls before op_fetch runs, which
|
|
36
|
+
# updates the live flags.
|
|
37
|
+
def end_sequence
|
|
38
|
+
@plan = FETCH_PLAN
|
|
39
|
+
@writes = FETCH_WRITES
|
|
40
|
+
@index = 0
|
|
41
|
+
@boundary_irq = @irq_pending
|
|
42
|
+
@boundary_nmi = @nmi_pending
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def log(opcode)
|
|
46
|
+
instruction = Instruction.find(opcode)
|
|
47
|
+
puts(
|
|
48
|
+
"#{@cycles}: PC: #{format16(@program_counter)} - " \
|
|
49
|
+
"#{instruction.name.upcase} #{instruction.addressing_mode}"
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "badline/cpu/microcode"
|
|
4
|
+
require "badline/cpu/addressing"
|
|
5
|
+
require "badline/cpu/operations"
|
|
6
|
+
require "badline/cpu/stack_operations"
|
|
7
|
+
require "badline/cpu/boundary"
|
|
8
|
+
require "badline/cpu/saved_state"
|
|
9
|
+
|
|
10
|
+
module Badline
|
|
11
|
+
class CPU
|
|
12
|
+
STATUS_FLAGS = [:carry, :zero, :interrupt, :decimal, :break, 1, :overflow, :negative].freeze
|
|
13
|
+
|
|
14
|
+
# The 6502 core, on whatever bus +memory+ is. The C64's 6510 (CPU) and
|
|
15
|
+
# the 1541's 6502 (Drive1541::CPU) each include it.
|
|
16
|
+
module Core
|
|
17
|
+
include IntegerHelper
|
|
18
|
+
include InstructionSet
|
|
19
|
+
include Interrupts
|
|
20
|
+
include Traps
|
|
21
|
+
include Addressing
|
|
22
|
+
include Operations
|
|
23
|
+
include StackOperations
|
|
24
|
+
include Boundary
|
|
25
|
+
include SavedState
|
|
26
|
+
|
|
27
|
+
attr_reader :memory, :instructions, :boundary_crossed, :cycles, :pending_write
|
|
28
|
+
attr_accessor :program_counter, :stack_pointer, :status, :a, :x, :y, :nmi, :irq
|
|
29
|
+
|
|
30
|
+
alias pending_write? pending_write
|
|
31
|
+
|
|
32
|
+
# +ane_constant+ is ANE's magic constant, which varies from chip to
|
|
33
|
+
# chip. The default is the C64 6510's.
|
|
34
|
+
def initialize(memory, debug: false, ane_constant: 0xef)
|
|
35
|
+
@debug = debug
|
|
36
|
+
@ane_constant = ane_constant
|
|
37
|
+
@memory = memory
|
|
38
|
+
@status = CPUStatus.new(STATUS_FLAGS, value: 0b00100000)
|
|
39
|
+
reset_registers
|
|
40
|
+
|
|
41
|
+
@nmi = @irq = false
|
|
42
|
+
@irq_sample = @irq_pending = false
|
|
43
|
+
@nmi_sample = @nmi_pending = false
|
|
44
|
+
@skip_poll = false
|
|
45
|
+
@boundary_crossed = false
|
|
46
|
+
@interrupt = nil
|
|
47
|
+
@brk = false
|
|
48
|
+
@pending_write = false
|
|
49
|
+
@stalled_at = nil
|
|
50
|
+
@so_high = true
|
|
51
|
+
|
|
52
|
+
@cycles = 0
|
|
53
|
+
@instructions = 0
|
|
54
|
+
@traps = nil
|
|
55
|
+
end_sequence
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def reset!
|
|
59
|
+
status.interrupt = true
|
|
60
|
+
reset_registers
|
|
61
|
+
@nmi = false
|
|
62
|
+
@irq_sample = @irq_pending = false
|
|
63
|
+
@nmi_sample = @nmi_pending = false
|
|
64
|
+
@skip_poll = false
|
|
65
|
+
@pending_write = false
|
|
66
|
+
end_sequence
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def p
|
|
70
|
+
status.value
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def p=(new_value)
|
|
74
|
+
status.value = new_value
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def cycle!
|
|
78
|
+
poll # the interrupt lines
|
|
79
|
+
|
|
80
|
+
# Run the next step
|
|
81
|
+
index = @index
|
|
82
|
+
@index = index + 1
|
|
83
|
+
send(@plan[index])
|
|
84
|
+
@cycles += 1
|
|
85
|
+
|
|
86
|
+
# Record if the next step writes to memory
|
|
87
|
+
@pending_write = @writes[@index]
|
|
88
|
+
nil
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Called instead of #cycle! on a cycle the VIC holds the CPU through
|
|
92
|
+
# BA. Records which cycle the CPU was stalled before, and keeps
|
|
93
|
+
# sampling the interrupt lines.
|
|
94
|
+
def stall!
|
|
95
|
+
@stalled_at = @cycles
|
|
96
|
+
sample_while_stalled
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# The 6502's SO (set overflow) pin, which the 6510 leaves out, so the
|
|
100
|
+
# C64 never drives it. A falling edge sets V and a held level does
|
|
101
|
+
# nothing more.
|
|
102
|
+
def so=(high)
|
|
103
|
+
@status.overflow = true if @so_high && !high
|
|
104
|
+
@so_high = high
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Pulses SO: pulls it low, which sets V, and releases it.
|
|
108
|
+
def so!
|
|
109
|
+
@status.overflow = true
|
|
110
|
+
@so_high = true
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def jammed?
|
|
114
|
+
@plan.equal?(JAMMED_PLAN)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Runs to the end of the instruction, or until the CPU jams.
|
|
118
|
+
def step!
|
|
119
|
+
cycle!
|
|
120
|
+
cycle! until @plan.equal?(FETCH_PLAN) || jammed?
|
|
121
|
+
nil
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Whether the next cycle fetches an opcode.
|
|
125
|
+
def boundary? = @index.zero? && @plan.equal?(FETCH_PLAN)
|
|
126
|
+
|
|
127
|
+
def trapped? = !@traps.nil?
|
|
128
|
+
|
|
129
|
+
def inspect
|
|
130
|
+
"Cycles: #{@cycles}, PC: #{format16(program_counter)}, " \
|
|
131
|
+
"SP: #{format8(stack_pointer)}, A: #{format8(a)}, X: #{format8(x)}, " \
|
|
132
|
+
"Y: #{format8(y)}, P: #{format8(p)}"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
private
|
|
136
|
+
|
|
137
|
+
# True when the CPU was stalled right before this cycle.
|
|
138
|
+
def stalled_before_this_cycle?
|
|
139
|
+
@stalled_at == @cycles
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# True when the CPU was stalled right before the cycle that preceded
|
|
143
|
+
# this one.
|
|
144
|
+
def stalled_before_previous_cycle?
|
|
145
|
+
@stalled_at == @cycles - 1
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def write_byte(addr, value)
|
|
149
|
+
@memory.poke(addr, value)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def stack_address(offset = 0)
|
|
153
|
+
0x0100 | ((@stack_pointer + offset) & 0xff)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def reset_registers
|
|
157
|
+
# The program counter is initialized from the reset vector
|
|
158
|
+
@program_counter = @memory.peek16(0xfffc)
|
|
159
|
+
# Stack pointer starts at 0x01ff and grows down
|
|
160
|
+
@stack_pointer = 0xff
|
|
161
|
+
@a = @x = @y = 0x0
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
data/lib/badline/cpu.rb
CHANGED
|
@@ -1,221 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "badline/cpu/
|
|
4
|
-
require "badline/cpu/addressing"
|
|
5
|
-
require "badline/cpu/operations"
|
|
6
|
-
require "badline/cpu/stack_operations"
|
|
7
|
-
require "badline/cpu/saved_state"
|
|
3
|
+
require "badline/cpu/core"
|
|
8
4
|
|
|
9
5
|
module Badline
|
|
6
|
+
# The C64's 6510, on the AddressBus.
|
|
10
7
|
class CPU
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
include IntegerHelper
|
|
14
|
-
include InstructionSet
|
|
15
|
-
include Interrupts
|
|
16
|
-
include Traps
|
|
17
|
-
include Addressing
|
|
18
|
-
include Operations
|
|
19
|
-
include StackOperations
|
|
20
|
-
include SavedState
|
|
21
|
-
|
|
22
|
-
attr_reader :memory, :instructions, :boundary_crossed, :cycles, :pending_write
|
|
23
|
-
attr_accessor :program_counter, :stack_pointer, :status, :a, :x, :y, :nmi, :irq
|
|
24
|
-
|
|
25
|
-
alias pending_write? pending_write
|
|
26
|
-
|
|
27
|
-
# +ane_constant+ is ANE's magic constant, which varies from chip to
|
|
28
|
-
# chip. The default is the C64 6510's.
|
|
29
|
-
def initialize(memory, debug: false, ane_constant: 0xef)
|
|
30
|
-
@debug = debug
|
|
31
|
-
@ane_constant = ane_constant
|
|
32
|
-
@memory = memory
|
|
33
|
-
@status = CPUStatus.new(STATUS_FLAGS, value: 0b00100000)
|
|
34
|
-
reset_registers
|
|
35
|
-
|
|
36
|
-
@nmi = @irq = false
|
|
37
|
-
@irq_sample = @irq_pending = false
|
|
38
|
-
@nmi_sample = @nmi_pending = false
|
|
39
|
-
@skip_poll = false
|
|
40
|
-
@boundary_crossed = false
|
|
41
|
-
@interrupt = nil
|
|
42
|
-
@brk = false
|
|
43
|
-
@pending_write = false
|
|
44
|
-
@stalled_at = nil
|
|
45
|
-
@so_high = true
|
|
46
|
-
|
|
47
|
-
@cycles = 0
|
|
48
|
-
@instructions = 0
|
|
49
|
-
@traps = nil
|
|
50
|
-
end_sequence
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def reset!
|
|
54
|
-
status.interrupt = true
|
|
55
|
-
reset_registers
|
|
56
|
-
@nmi = false
|
|
57
|
-
@irq_sample = @irq_pending = false
|
|
58
|
-
@nmi_sample = @nmi_pending = false
|
|
59
|
-
@skip_poll = false
|
|
60
|
-
@pending_write = false
|
|
61
|
-
end_sequence
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def p
|
|
65
|
-
status.value
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def p=(new_value)
|
|
69
|
-
status.value = new_value
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def cycle!
|
|
73
|
-
poll # the interrupt lines
|
|
74
|
-
|
|
75
|
-
# Run the next step
|
|
76
|
-
index = @index
|
|
77
|
-
@index = index + 1
|
|
78
|
-
send(@plan[index])
|
|
79
|
-
@cycles += 1
|
|
80
|
-
|
|
81
|
-
# Record if the next step writes to memory
|
|
82
|
-
@pending_write = @writes[@index]
|
|
83
|
-
nil
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
# Called instead of #cycle! on a cycle the VIC holds the CPU through
|
|
87
|
-
# BA. Records which cycle the CPU was stalled before, and keeps
|
|
88
|
-
# sampling the interrupt lines.
|
|
89
|
-
def stall!
|
|
90
|
-
@stalled_at = @cycles
|
|
91
|
-
sample_while_stalled
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
# The 6502's SO (set overflow) pin, which the 6510 leaves out, so the
|
|
95
|
-
# C64 never drives it. A falling edge sets V and a held level does
|
|
96
|
-
# nothing more.
|
|
97
|
-
def so=(high)
|
|
98
|
-
@status.overflow = true if @so_high && !high
|
|
99
|
-
@so_high = high
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
# Pulses SO: pulls it low, which sets V, and releases it.
|
|
103
|
-
def so!
|
|
104
|
-
@status.overflow = true
|
|
105
|
-
@so_high = true
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
def jammed?
|
|
109
|
-
@plan.equal?(JAMMED_PLAN)
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
# Runs to the end of the instruction, or until the CPU jams.
|
|
113
|
-
def step!
|
|
114
|
-
cycle!
|
|
115
|
-
cycle! until @plan.equal?(FETCH_PLAN) || jammed?
|
|
116
|
-
nil
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# Whether the next cycle fetches an opcode.
|
|
120
|
-
def boundary? = @index.zero? && @plan.equal?(FETCH_PLAN)
|
|
121
|
-
|
|
122
|
-
def trapped? = !@traps.nil?
|
|
123
|
-
|
|
124
|
-
# Everything the CPU holds but its cycle and instruction counts: the
|
|
125
|
-
# registers, the interrupt lines and pipeline, and what the last
|
|
126
|
-
# instruction left in its working registers. Drive1541::Idle compares
|
|
127
|
-
# it at two instruction boundaries.
|
|
128
|
-
def idle_state
|
|
129
|
-
[@program_counter, @stack_pointer, @a, @x, @y, @status.value,
|
|
130
|
-
@irq, @nmi, @irq_sample, @irq_pending, @nmi_sample, @nmi_pending, @skip_poll,
|
|
131
|
-
@boundary_irq, @boundary_nmi, @so_high, @interrupt, @brk, @pending_write, @stalled_at, @traps,
|
|
132
|
-
@operation, @optional_dummy, @boundary_crossed, @branch_taken,
|
|
133
|
-
@address, @pointer, @dummy_address, @value, @rmw_result]
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
# Counts +cycles+ cycles and +instructions+ instructions that ran
|
|
137
|
-
# without changing anything idle_state holds.
|
|
138
|
-
def fast_forward(cycles, instructions)
|
|
139
|
-
@cycles += cycles
|
|
140
|
-
@instructions += instructions
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
def inspect
|
|
144
|
-
"Cycles: #{@cycles}, PC: #{format16(program_counter)}, " \
|
|
145
|
-
"SP: #{format8(stack_pointer)}, A: #{format8(a)}, X: #{format8(x)}, " \
|
|
146
|
-
"Y: #{format8(y)}, P: #{format8(p)}"
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
private
|
|
150
|
-
|
|
151
|
-
# The first cycle of every instruction. Starts an interrupt if one was
|
|
152
|
-
# pending when the previous instruction ended. Otherwise reads the
|
|
153
|
-
# opcode and switches to its plan.
|
|
154
|
-
def op_fetch
|
|
155
|
-
return start_interrupt if @boundary_nmi || @boundary_irq
|
|
156
|
-
|
|
157
|
-
run_traps
|
|
158
|
-
opcode = @memory.peek(@program_counter)
|
|
159
|
-
log(opcode) if @debug
|
|
160
|
-
micro = MICROCODE[opcode]
|
|
161
|
-
@operation = micro.operation
|
|
162
|
-
@plan = micro.plan
|
|
163
|
-
@writes = micro.writes
|
|
164
|
-
@optional_dummy = micro.optional_dummy
|
|
165
|
-
@index = 1
|
|
166
|
-
@boundary_crossed = false
|
|
167
|
-
@program_counter = (@program_counter + 1) & 0xffff
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
def end_instruction
|
|
171
|
-
@instructions += 1
|
|
172
|
-
end_sequence
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
# Makes the next cycle an opcode fetch, and saves the pending interrupt
|
|
176
|
-
# flags for op_fetch. The next cycle polls before op_fetch runs, which
|
|
177
|
-
# updates the live flags.
|
|
178
|
-
def end_sequence
|
|
179
|
-
@plan = FETCH_PLAN
|
|
180
|
-
@writes = FETCH_WRITES
|
|
181
|
-
@index = 0
|
|
182
|
-
@boundary_irq = @irq_pending
|
|
183
|
-
@boundary_nmi = @nmi_pending
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
# True when the CPU was stalled right before this cycle.
|
|
187
|
-
def stalled_before_this_cycle?
|
|
188
|
-
@stalled_at == @cycles
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
# True when the CPU was stalled right before the cycle that preceded
|
|
192
|
-
# this one.
|
|
193
|
-
def stalled_before_previous_cycle?
|
|
194
|
-
@stalled_at == @cycles - 1
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
def write_byte(addr, value)
|
|
198
|
-
@memory.poke(addr, value)
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
def stack_address(offset = 0)
|
|
202
|
-
0x0100 | ((@stack_pointer + offset) & 0xff)
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
def reset_registers
|
|
206
|
-
# The program counter is initialized from the reset vector
|
|
207
|
-
@program_counter = @memory.peek16(0xfffc)
|
|
208
|
-
# Stack pointer starts at 0x01ff and grows down
|
|
209
|
-
@stack_pointer = 0xff
|
|
210
|
-
@a = @x = @y = 0x0
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
def log(opcode)
|
|
214
|
-
instruction = Instruction.find(opcode)
|
|
215
|
-
puts(
|
|
216
|
-
"#{@cycles}: PC: #{format16(@program_counter)} - " \
|
|
217
|
-
"#{instruction.name.upcase} #{instruction.addressing_mode}"
|
|
218
|
-
)
|
|
219
|
-
end
|
|
8
|
+
include Core
|
|
220
9
|
end
|
|
221
10
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Drive1541
|
|
5
|
+
# The drive's 6502, on the drive's Bus.
|
|
6
|
+
class CPU
|
|
7
|
+
include Badline::CPU::Core
|
|
8
|
+
|
|
9
|
+
# Everything the CPU holds but its cycle and instruction counts: the
|
|
10
|
+
# registers, the interrupt lines and pipeline, and what the last
|
|
11
|
+
# instruction left in its working registers. Drive1541::Idle compares
|
|
12
|
+
# it at two instruction boundaries.
|
|
13
|
+
def idle_state
|
|
14
|
+
[@program_counter, @stack_pointer, @a, @x, @y, @status.value,
|
|
15
|
+
@irq, @nmi, @irq_sample, @irq_pending, @nmi_sample, @nmi_pending, @skip_poll,
|
|
16
|
+
@boundary_irq, @boundary_nmi, @so_high, @interrupt, @brk, @pending_write, @stalled_at, @traps,
|
|
17
|
+
@operation, @optional_dummy, @boundary_crossed, @branch_taken,
|
|
18
|
+
@address, @pointer, @dummy_address, @value, @rmw_result]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Counts +cycles+ cycles and +instructions+ instructions that ran
|
|
22
|
+
# without changing anything idle_state holds.
|
|
23
|
+
def fast_forward(cycles, instructions)
|
|
24
|
+
@cycles += cycles
|
|
25
|
+
@instructions += instructions
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|