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.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +77 -0
  3. data/CLAUDE.md +10 -9
  4. data/README.md +135 -42
  5. data/doc/pinned-behaviour.md +131 -30
  6. data/exe/badline-ruby +28 -24
  7. data/lib/badline/address_bus/extra_sids.rb +30 -0
  8. data/lib/badline/address_bus/sid_slots.rb +42 -0
  9. data/lib/badline/address_bus.rb +15 -1
  10. data/lib/badline/audio/aiff.rb +2 -2
  11. data/lib/badline/audio/bare_player.rb +23 -12
  12. data/lib/badline/audio/cli.rb +167 -59
  13. data/lib/badline/audio/console.rb +2 -1
  14. data/lib/badline/audio/jukebox.rb +121 -36
  15. data/lib/badline/audio/machine_player.rb +25 -10
  16. data/lib/badline/audio/pcm_writer.rb +10 -6
  17. data/lib/badline/audio/playback.rb +6 -5
  18. data/lib/badline/audio/queued_tune.rb +89 -0
  19. data/lib/badline/audio/renderer.rb +57 -10
  20. data/lib/badline/audio/stereo.rb +97 -0
  21. data/lib/badline/audio/terminal.rb +39 -6
  22. data/lib/badline/audio/wav.rb +1 -1
  23. data/lib/badline/audio.rb +2 -3
  24. data/lib/badline/computer.rb +6 -3
  25. data/lib/badline/cpu/boundary.rb +54 -0
  26. data/lib/badline/cpu/core.rb +165 -0
  27. data/lib/badline/cpu.rb +3 -214
  28. data/lib/badline/drive1541/cpu.rb +29 -0
  29. data/lib/badline/drive1541.rb +1 -0
  30. data/lib/badline/ffi.rb +157 -0
  31. data/lib/badline/frontend/app.rb +225 -0
  32. data/lib/badline/frontend/audio_sink.rb +73 -0
  33. data/lib/badline/frontend/boot.rb +52 -0
  34. data/lib/badline/frontend/buttons.rb +90 -0
  35. data/lib/badline/frontend/controls.rb +163 -0
  36. data/lib/badline/frontend/drive_led.rb +51 -0
  37. data/lib/badline/frontend/filter_view.rb +96 -0
  38. data/lib/badline/frontend/frame_rate.rb +83 -0
  39. data/lib/badline/frontend/gamepads.rb +76 -0
  40. data/lib/badline/frontend/info_view.rb +120 -0
  41. data/lib/badline/frontend/keys.rb +48 -0
  42. data/lib/badline/frontend/pacer.rb +100 -0
  43. data/lib/badline/frontend/pad_port.rb +61 -0
  44. data/lib/badline/frontend/painter.rb +175 -0
  45. data/lib/badline/frontend/player_commands.rb +33 -0
  46. data/lib/badline/frontend/player_footer.rb +86 -0
  47. data/lib/badline/frontend/player_header.rb +60 -0
  48. data/lib/badline/frontend/player_screen.rb +124 -0
  49. data/lib/badline/frontend/player_state.rb +67 -0
  50. data/lib/badline/frontend/player_window.rb +228 -0
  51. data/lib/badline/frontend/scope.rb +42 -0
  52. data/lib/badline/frontend/screen.rb +65 -0
  53. data/lib/badline/frontend/screenshot.rb +22 -0
  54. data/lib/badline/frontend/sid_history.rb +175 -0
  55. data/lib/badline/frontend/sid_view.rb +140 -0
  56. data/lib/badline/frontend/snapshots.rb +60 -0
  57. data/lib/badline/frontend/sound.rb +153 -0
  58. data/lib/badline/frontend/timeline.rb +171 -0
  59. data/lib/badline/frontend/visualizer_view.rb +101 -0
  60. data/lib/badline/frontend.rb +32 -0
  61. data/lib/badline/media/queue.rb +158 -0
  62. data/lib/badline/media.rb +13 -8
  63. data/lib/badline/options/event.rb +94 -0
  64. data/lib/badline/options/help.rb +107 -0
  65. data/lib/badline/options/option.rb +29 -0
  66. data/lib/badline/options/table.rb +64 -0
  67. data/lib/badline/options/validation.rb +54 -0
  68. data/lib/badline/options.rb +204 -149
  69. data/lib/badline/reu/dma/saved_state.rb +50 -0
  70. data/lib/badline/reu/dma.rb +56 -47
  71. data/lib/badline/reu/saved_state.rb +2 -1
  72. data/lib/badline/reu.rb +19 -10
  73. data/lib/badline/sdl.rb +133 -51
  74. data/lib/badline/sid/catch_up.rb +11 -1
  75. data/lib/badline/sid/filter.rb +9 -0
  76. data/lib/badline/sid/voice.rb +6 -0
  77. data/lib/badline/sid/waveform/noise_writeback.rb +33 -11
  78. data/lib/badline/sid/waveform.rb +22 -8
  79. data/lib/badline/sid.rb +35 -3
  80. data/lib/badline/snapshot/state.rb +1 -1
  81. data/lib/badline/storage/hvsc.rb +41 -0
  82. data/lib/badline/storage/sid_file.rb +45 -25
  83. data/lib/badline/storage/song_lengths.rb +6 -29
  84. data/lib/badline/storage/stil/credit.rb +50 -0
  85. data/lib/badline/storage/stil.rb +126 -0
  86. data/lib/badline/storage.rb +8 -0
  87. data/lib/badline/version.rb +1 -1
  88. data/lib/badline/vic/bank.rb +21 -0
  89. data/lib/badline/vic.rb +15 -6
  90. metadata +49 -19
  91. data/.claude/hooks/session-start.sh +0 -57
  92. data/.claude/settings.json +0 -14
  93. data/lib/badline/audio/sdl_sink.rb +0 -70
  94. data/lib/badline/audio/stream.rb +0 -123
  95. data/lib/badline/gui/application.rb +0 -243
  96. data/lib/badline/gui/drive_led_pane.rb +0 -29
  97. data/lib/badline/gui/gamepads.rb +0 -104
  98. data/lib/badline/gui/joy_map.rb +0 -27
  99. data/lib/badline/gui/key_map.rb +0 -39
  100. data/lib/badline/gui/palette.rb +0 -35
  101. data/lib/badline/gui/pane.rb +0 -36
  102. data/lib/badline/gui/screen_pane.rb +0 -46
  103. data/lib/badline/gui/snapshots.rb +0 -67
  104. data/lib/badline/gui/window.rb +0 -61
  105. data/lib/badline/gui.rb +0 -14
  106. data/lib/badline/sdl/events.rb +0 -49
  107. data/lib/badline/sdl/functions.rb +0 -74
  108. 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, "\e[C" => :next,
14
- "p" => :previous, "\e[D" => :previous,
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 "n/→ next p/← previous space pause q quit\r\n"
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
- def status(song:, songs:, elapsed:, length:, notes: [])
53
- line = format("song %<song>d/%<songs>d %<elapsed>s / %<length>s",
54
- song:, songs:, elapsed: clock(elapsed), length: clock(length))
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
@@ -10,7 +10,7 @@ module Badline
10
10
 
11
11
  def header
12
12
  ["RIFF", 0, "WAVE",
13
- "fmt ", 16, 1, 1, rate, rate * 2, 2, 16,
13
+ "fmt ", 16, 1, channels, rate, rate * channels * 2, channels * 2, 16,
14
14
  "data", 0].pack("a4Va4a4VvvVVvva4V")
15
15
  end
16
16
 
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"
@@ -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.reu_ba_low?, writing)
232
- return @cpu.stall! if @reu.holds_bus?
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 = @reu.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/microcode"
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
- STATUS_FLAGS = [:carry, :zero, :interrupt, :decimal, :break, 1, :overflow, :negative].freeze
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
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "badline/drive1541/bus"
4
+ require "badline/drive1541/cpu"
4
5
  require "badline/drive1541/serial_port"
5
6
  require "badline/drive1541/gcr"
6
7
  require "badline/drive1541/track"