badline 0.11.0 → 0.12.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 +26 -0
- data/CLAUDE.md +24 -6
- data/CONTRIBUTING.md +15 -9
- data/README.md +16 -5
- data/doc/media.md +28 -1
- data/doc/pinned-behaviour.md +31 -1
- data/exe/badline-ruby +1 -1
- data/lib/badline/c128/bus.rb +4 -17
- data/lib/badline/c128/drives.rb +108 -0
- data/lib/badline/c128/fast_serial.rb +63 -0
- data/lib/badline/c128/io_pages.rb +35 -0
- data/lib/badline/c128/mmu.rb +24 -7
- data/lib/badline/c128/modes.rb +17 -4
- data/lib/badline/c128/saved_state.rb +23 -0
- data/lib/badline/c128/z80.rb +49 -0
- data/lib/badline/c128/z80_pages.rb +118 -0
- data/lib/badline/c128/z80_turns.rb +106 -0
- data/lib/badline/c128.rb +25 -26
- data/lib/badline/cartridge/action_replay.rb +2 -2
- data/lib/badline/cartridge/comal80.rb +1 -7
- data/lib/badline/cartridge/dinamic.rb +0 -8
- data/lib/badline/cartridge/final_cartridge3.rb +6 -6
- data/lib/badline/cartridge/fun_play.rb +0 -8
- data/lib/badline/cartridge/game_system.rb +0 -8
- data/lib/badline/cartridge/kcs_power.rb +10 -7
- data/lib/badline/cartridge/mach5.rb +2 -4
- data/lib/badline/cartridge/magic_desk.rb +0 -8
- data/lib/badline/cartridge/ocean.rb +0 -8
- data/lib/badline/cartridge/pagefox.rb +2 -2
- data/lib/badline/cartridge/retro_replay.rb +7 -6
- data/lib/badline/cartridge/rex_utility.rb +2 -6
- data/lib/badline/cartridge/rgcd.rb +2 -2
- data/lib/badline/cartridge/simons_basic.rb +0 -5
- data/lib/badline/cartridge/super_games.rb +4 -2
- data/lib/badline/cartridge/westermann.rb +2 -6
- data/lib/badline/cartridge.rb +37 -9
- data/lib/badline/cia/fast_forward.rb +60 -0
- data/lib/badline/cia/serial.rb +12 -0
- data/lib/badline/cia/timer.rb +39 -0
- data/lib/badline/cia.rb +10 -5
- data/lib/badline/computer/attachments.rb +4 -50
- data/lib/badline/computer/true_drives.rb +80 -0
- data/lib/badline/computer.rb +4 -0
- data/lib/badline/core.rb +2 -0
- data/lib/badline/drive/core.rb +147 -0
- data/lib/badline/drive/fast_serial.rb +73 -0
- data/lib/badline/{drive1541 → drive}/idle.rb +18 -29
- data/lib/badline/drive/idle_cpu.rb +28 -0
- data/lib/badline/{drive1541 → drive}/orbit.rb +3 -5
- data/lib/badline/drive/serial_lines.rb +73 -0
- data/lib/badline/{drive1541 → drive}/sleep.rb +4 -5
- data/lib/badline/drive/vias.rb +84 -0
- data/lib/badline/drive/watch.rb +150 -0
- data/lib/badline/drive.rb +11 -0
- data/lib/badline/drive1541/bus.rb +5 -129
- data/lib/badline/drive1541/byte_ready.rb +48 -0
- data/lib/badline/drive1541/cpu.rb +1 -19
- data/lib/badline/drive1541/disk.rb +77 -20
- data/lib/badline/drive1541/disk_state.rb +7 -2
- data/lib/badline/drive1541/disk_via.rb +3 -0
- data/lib/badline/drive1541/mechanism.rb +48 -14
- data/lib/badline/drive1541/mechanism_state.rb +5 -4
- data/lib/badline/drive1541/rotation.rb +5 -5
- data/lib/badline/drive1541/serial_port.rb +4 -60
- data/lib/badline/drive1541.rb +14 -172
- data/lib/badline/drive1571/bus.rb +111 -0
- data/lib/badline/drive1571/cpu.rb +11 -0
- data/lib/badline/drive1571/serial_port.rb +37 -0
- data/lib/badline/drive1571/serial_via.rb +25 -0
- data/lib/badline/drive1571/wd1770.rb +120 -0
- data/lib/badline/drive1571.rb +175 -0
- data/lib/badline/drive1581/bus.rb +137 -0
- data/lib/badline/drive1581/cpu.rb +11 -0
- data/lib/badline/drive1581/disk.rb +135 -0
- data/lib/badline/drive1581/disk_state.rb +87 -0
- data/lib/badline/drive1581/mechanism.rb +117 -0
- data/lib/badline/drive1581/serial_port.rb +52 -0
- data/lib/badline/drive1581/slot.rb +78 -0
- data/lib/badline/drive1581/track.rb +100 -0
- data/lib/badline/drive1581/track_bytes.rb +75 -0
- data/lib/badline/drive1581/wd1772/registers.rb +89 -0
- data/lib/badline/drive1581/wd1772/sector_writes.rb +73 -0
- data/lib/badline/drive1581/wd1772/sectors.rb +150 -0
- data/lib/badline/drive1581/wd1772/state.rb +66 -0
- data/lib/badline/drive1581/wd1772/stepping.rb +109 -0
- data/lib/badline/drive1581/wd1772/tracks.rb +125 -0
- data/lib/badline/drive1581/wd1772/turn.rb +73 -0
- data/lib/badline/drive1581/wd1772.rb +216 -0
- data/lib/badline/drive1581.rb +233 -0
- data/lib/badline/frontend/app.rb +2 -4
- data/lib/badline/frontend/audio_device.rb +67 -0
- data/lib/badline/frontend/audio_sink.rb +16 -50
- data/lib/badline/frontend/boot.rb +18 -10
- data/lib/badline/frontend/buttons.rb +7 -7
- data/lib/badline/frontend/drive_led.rb +1 -1
- data/lib/badline/frontend/keys.rb +33 -8
- data/lib/badline/frontend/media_slots.rb +42 -0
- data/lib/badline/frontend/{confirmation.rb → menu/confirmation.rb} +4 -4
- data/lib/badline/frontend/{file_browser.rb → menu/file_browser.rb} +18 -34
- data/lib/badline/frontend/{menu_dialogs.rb → menu/menu_dialogs.rb} +3 -7
- data/lib/badline/frontend/{menu_media.rb → menu/menu_media.rb} +26 -51
- data/lib/badline/frontend/{menu_pages.rb → menu/menu_pages.rb} +7 -9
- data/lib/badline/frontend/{name_field.rb → menu/name_field.rb} +11 -24
- data/lib/badline/frontend/{pause_menu.rb → menu/pause_menu.rb} +13 -28
- data/lib/badline/frontend/menu/theme.rb +18 -0
- data/lib/badline/frontend/{filter_view.rb → player/filter_view.rb} +4 -4
- data/lib/badline/frontend/{info_view.rb → player/info_view.rb} +3 -3
- data/lib/badline/frontend/{player_window.rb → player/player_controller.rb} +17 -25
- data/lib/badline/frontend/{player_footer.rb → player/player_footer.rb} +8 -8
- data/lib/badline/frontend/{player_header.rb → player/player_header.rb} +5 -5
- data/lib/badline/frontend/{player_screen.rb → player/player_screen.rb} +4 -5
- data/lib/badline/frontend/{scope.rb → player/scope.rb} +1 -1
- data/lib/badline/frontend/{sid_view.rb → player/sid_view.rb} +16 -20
- data/lib/badline/frontend/player/theme.rb +18 -0
- data/lib/badline/frontend/{visualizer_view.rb → player/visualizer_view.rb} +8 -8
- data/lib/badline/frontend/sound.rb +19 -38
- data/lib/badline/frontend/timeline.rb +3 -14
- data/lib/badline/frontend.rb +24 -20
- data/lib/badline/iec_bus.rb +68 -6
- data/lib/badline/machine.rb +9 -6
- data/lib/badline/media/boot_disk.rb +50 -0
- data/lib/badline/media/not_disk.rb +8 -0
- data/lib/badline/media/true_drive.rb +48 -25
- data/lib/badline/media.rb +9 -3
- data/lib/badline/options/event.rb +2 -2
- data/lib/badline/options/family.rb +3 -9
- data/lib/badline/options/help.rb +3 -1
- data/lib/badline/options/table.rb +1 -1
- data/lib/badline/roms/README +18 -0
- data/lib/badline/roms/dos1571.rom +0 -0
- data/lib/badline/roms/dos1581.rom +0 -0
- data/lib/badline/sdl.rb +37 -20
- data/lib/badline/snapshot/state.rb +5 -4
- data/lib/badline/storage/d64_image.rb +3 -0
- data/lib/badline/storage/d71_image.rb +9 -4
- data/lib/badline/storage/g64_image.rb +7 -2
- data/lib/badline/storage/p00.rb +8 -0
- data/lib/badline/time_of_day.rb +9 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline/via/fast_forward.rb +3 -3
- data/lib/badline/vic20/attachments.rb +23 -3
- data/lib/badline/vic20/saved_state.rb +2 -0
- data/lib/badline/vic20.rb +3 -0
- data/lib/badline/z80/arithmetic.rb +117 -0
- data/lib/badline/z80/bit_page.rb +55 -0
- data/lib/badline/z80/block_io.rb +58 -0
- data/lib/badline/z80/block_transfers.rb +59 -0
- data/lib/badline/z80/bus_cycles.rb +111 -0
- data/lib/badline/z80/core.rb +113 -0
- data/lib/badline/z80/exchanges.rb +50 -0
- data/lib/badline/z80/extended_page.rb +89 -0
- data/lib/badline/z80/flags.rb +37 -0
- data/lib/badline/z80/interrupt_acceptance.rb +46 -0
- data/lib/badline/z80/jumps.rb +82 -0
- data/lib/badline/z80/loads.rb +98 -0
- data/lib/badline/z80/main_page.rb +134 -0
- data/lib/badline/z80/registers.rb +120 -0
- data/lib/badline/z80/saved_state.rb +69 -0
- data/lib/badline/z80/shifts.rb +64 -0
- data/lib/badline/z80.rb +11 -0
- metadata +89 -24
- /data/lib/badline/frontend/{menu_ports.rb → menu/menu_ports.rb} +0 -0
- /data/lib/badline/frontend/{player_commands.rb → player/player_commands.rb} +0 -0
- /data/lib/badline/frontend/{player_state.rb → player/player_state.rb} +0 -0
- /data/lib/badline/frontend/{sid_history.rb → player/sid_history.rb} +0 -0
|
@@ -19,14 +19,6 @@ module Badline
|
|
|
19
19
|
|
|
20
20
|
private
|
|
21
21
|
|
|
22
|
-
def save_mapper(out)
|
|
23
|
-
save_windows(out, [*@banks, EMPTY_BANK])
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def load_mapper(input)
|
|
27
|
-
load_windows(input, [*@banks, EMPTY_BANK])
|
|
28
|
-
end
|
|
29
|
-
|
|
30
22
|
# In 16K mode the selected bank shows through ROMH as well.
|
|
31
23
|
def select_bank(number)
|
|
32
24
|
@roml = bank(@banks, number)
|
|
@@ -30,12 +30,12 @@ module Badline
|
|
|
30
30
|
private
|
|
31
31
|
|
|
32
32
|
def save_mapper(out)
|
|
33
|
-
|
|
33
|
+
super
|
|
34
34
|
@ram_banks.each { |ram| out.blob(ram.data) }
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def load_mapper(input)
|
|
38
|
-
|
|
38
|
+
super
|
|
39
39
|
@ram_banks.each { |ram| input.blob_into(ram.data) }
|
|
40
40
|
end
|
|
41
41
|
|
|
@@ -90,6 +90,11 @@ module Badline
|
|
|
90
90
|
|
|
91
91
|
attr_reader :flash
|
|
92
92
|
|
|
93
|
+
def self.from_jumpers(crt, input)
|
|
94
|
+
input.boolean?
|
|
95
|
+
new(crt, flash_jumper: input.boolean?, bank_jumper: input.boolean?)
|
|
96
|
+
end
|
|
97
|
+
|
|
93
98
|
def initialize(crt, flash_jumper: false, bank_jumper: false)
|
|
94
99
|
@nordic = crt.subtype == 1
|
|
95
100
|
@flash_jumper = flash_jumper
|
|
@@ -131,9 +136,7 @@ module Badline
|
|
|
131
136
|
end
|
|
132
137
|
end
|
|
133
138
|
|
|
134
|
-
def phi1_ultimax?
|
|
135
|
-
@phi1_ultimax
|
|
136
|
-
end
|
|
139
|
+
def phi1_ultimax? = @phi1_ultimax
|
|
137
140
|
|
|
138
141
|
def ultimax_a000
|
|
139
142
|
@isolated[ram_view_bank] if @nordic && @ram_at_a000 && @frozen
|
|
@@ -157,9 +160,7 @@ module Badline
|
|
|
157
160
|
|
|
158
161
|
private
|
|
159
162
|
|
|
160
|
-
def freeze_allowed?
|
|
161
|
-
!@no_freeze && !@flash_jumper
|
|
162
|
-
end
|
|
163
|
+
def freeze_allowed? = !@no_freeze && !@flash_jumper
|
|
163
164
|
|
|
164
165
|
def status
|
|
165
166
|
((@bank & 0x03) << 3) | ((@bank & 0x04) << 5) | ((@bank & 0x08) << 2) | (@flash_jumper ? 0x01 : 0) |
|
|
@@ -10,16 +10,12 @@ module Badline
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def peek(addr)
|
|
13
|
-
if addr >= 0xdf00
|
|
14
|
-
self.mode = (addr & 0xff) < 0xc0 ? :off : :rom8k
|
|
15
|
-
changed!
|
|
16
|
-
end
|
|
13
|
+
select_mode((addr & 0xff) < 0xc0 ? :off : :rom8k) if addr >= 0xdf00
|
|
17
14
|
open_bus(addr)
|
|
18
15
|
end
|
|
19
16
|
|
|
20
17
|
def reset
|
|
21
|
-
|
|
22
|
-
changed!
|
|
18
|
+
select_mode(:rom8k)
|
|
23
19
|
end
|
|
24
20
|
|
|
25
21
|
private
|
|
@@ -29,12 +29,12 @@ module Badline
|
|
|
29
29
|
private
|
|
30
30
|
|
|
31
31
|
def save_mapper(out)
|
|
32
|
-
|
|
32
|
+
super
|
|
33
33
|
out.boolean(@disabled)
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
def load_mapper(input)
|
|
37
|
-
|
|
37
|
+
super
|
|
38
38
|
@disabled = input.boolean?
|
|
39
39
|
end
|
|
40
40
|
|
|
@@ -21,15 +21,17 @@ module Badline
|
|
|
21
21
|
private
|
|
22
22
|
|
|
23
23
|
def save_mapper(out)
|
|
24
|
-
|
|
24
|
+
super
|
|
25
25
|
out.boolean(@locked)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def load_mapper(input)
|
|
29
|
-
|
|
29
|
+
super
|
|
30
30
|
@locked = input.boolean?
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
def windows = [*@roml_banks, *@romh_banks, EMPTY_BANK]
|
|
34
|
+
|
|
33
35
|
def select(value)
|
|
34
36
|
@roml = bank(@roml_banks, value & 0x03)
|
|
35
37
|
@romh = bank(@romh_banks, value & 0x03)
|
|
@@ -9,16 +9,12 @@ module Badline
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def peek(addr)
|
|
12
|
-
if addr >= 0xdf00
|
|
13
|
-
self.mode = :rom8k
|
|
14
|
-
changed!
|
|
15
|
-
end
|
|
12
|
+
select_mode(:rom8k) if addr >= 0xdf00
|
|
16
13
|
open_bus(addr)
|
|
17
14
|
end
|
|
18
15
|
|
|
19
16
|
def reset
|
|
20
|
-
|
|
21
|
-
changed!
|
|
17
|
+
select_mode(:rom16k)
|
|
22
18
|
end
|
|
23
19
|
|
|
24
20
|
private
|
data/lib/badline/cartridge.rb
CHANGED
|
@@ -63,11 +63,7 @@ module Badline
|
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
def from_crt(crt, **)
|
|
66
|
-
|
|
67
|
-
raise UnsupportedTypeError,
|
|
68
|
-
"Unsupported cartridge hardware type #{crt.hardware_type}"
|
|
69
|
-
end
|
|
70
|
-
mapper.new(crt, **)
|
|
66
|
+
mapper_for(crt).new(crt, **)
|
|
71
67
|
end
|
|
72
68
|
|
|
73
69
|
# A cartridge built afresh from what save_setup wrote, before
|
|
@@ -76,9 +72,21 @@ module Badline
|
|
|
76
72
|
return GeoRAM.new(size: input.int) if input.int == GEO_RAM_SETUP
|
|
77
73
|
|
|
78
74
|
crt = Storage::CRTFile.new(bytes: input.string)
|
|
79
|
-
|
|
75
|
+
mapper_for(crt).from_jumpers(crt, input)
|
|
76
|
+
end
|
|
80
77
|
|
|
81
|
-
|
|
78
|
+
# A cartridge from its image and what save_jumpers wrote: whether
|
|
79
|
+
# there are jumpers, then their settings on a mapper that has them.
|
|
80
|
+
def from_jumpers(crt, input)
|
|
81
|
+
input.boolean?
|
|
82
|
+
new(crt)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def mapper_for(crt)
|
|
86
|
+
HARDWARE_TYPES.fetch(crt.hardware_type) do
|
|
87
|
+
raise UnsupportedTypeError,
|
|
88
|
+
"Unsupported cartridge hardware type #{crt.hardware_type}"
|
|
89
|
+
end
|
|
82
90
|
end
|
|
83
91
|
end
|
|
84
92
|
|
|
@@ -95,6 +103,7 @@ module Badline
|
|
|
95
103
|
@exrom = crt.exrom
|
|
96
104
|
@game = crt.game
|
|
97
105
|
@roml = @romh = nil
|
|
106
|
+
@banks = nil
|
|
98
107
|
@on_change = nil
|
|
99
108
|
@on_nmi_change = nil
|
|
100
109
|
@nmi = false
|
|
@@ -193,9 +202,22 @@ module Badline
|
|
|
193
202
|
out.boolean(false)
|
|
194
203
|
end
|
|
195
204
|
|
|
196
|
-
|
|
205
|
+
# The mapper's own state: where its windows point, for a mapper that
|
|
206
|
+
# moves them, and whatever a mapper adds after calling super.
|
|
207
|
+
def save_mapper(out)
|
|
208
|
+
save_windows(out, windows) if windows
|
|
209
|
+
end
|
|
197
210
|
|
|
198
|
-
def load_mapper(
|
|
211
|
+
def load_mapper(input)
|
|
212
|
+
load_windows(input, windows) if windows
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# The banks and views ROML and ROMH can point at, which a snapshot
|
|
216
|
+
# names them by: the mapper's banks, or nothing for a mapper whose
|
|
217
|
+
# windows stay where install_chips put them.
|
|
218
|
+
def windows
|
|
219
|
+
[*@banks, EMPTY_BANK] if @banks
|
|
220
|
+
end
|
|
199
221
|
|
|
200
222
|
# Where the ROML and ROMH windows point, as their places in `windows`,
|
|
201
223
|
# the banks and views the mapper picks them from, or -1 for none.
|
|
@@ -232,6 +254,12 @@ module Badline
|
|
|
232
254
|
@exrom, @game = MODES.fetch(mode)
|
|
233
255
|
end
|
|
234
256
|
|
|
257
|
+
# Sets the lines and tells the machine to map the cartridge again.
|
|
258
|
+
def select_mode(mode)
|
|
259
|
+
self.mode = mode
|
|
260
|
+
changed!
|
|
261
|
+
end
|
|
262
|
+
|
|
235
263
|
def open_bus(addr)
|
|
236
264
|
@open_bus ? @open_bus.peek(addr) : 0xff
|
|
237
265
|
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class CIA
|
|
5
|
+
# Running a CIA a stretch of cycles at once, for Drive::Idle.
|
|
6
|
+
module FastForward
|
|
7
|
+
# quiet_cycles for a CIA whose counters will never set a flag.
|
|
8
|
+
QUIET = 1 << 40
|
|
9
|
+
|
|
10
|
+
# Whether fast_forward can run the CIA: timer B stopped with nothing
|
|
11
|
+
# in its pipeline, no interrupt or acknowledge on its way, CNT where it
|
|
12
|
+
# was, the TOD clock stopped, and timer A either stopped, or free
|
|
13
|
+
# running (Timer#free_running?) with its interrupt masked and the
|
|
14
|
+
# serial port an idle input. Drive::Idle sleeps the 1571's CIA through
|
|
15
|
+
# such cycles, which its DOS leaves timer A running in.
|
|
16
|
+
def quiet?
|
|
17
|
+
return false unless @tb.idle? && @icr.quiet && @tod.quiet? && cnt_steady?
|
|
18
|
+
|
|
19
|
+
@ta.idle? ? @serial.idle : timer_a_free_running?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# How many cycles the CIA can run with nothing but its counters
|
|
23
|
+
# moving: as quiet? allows, but with timer B free running too, up to
|
|
24
|
+
# the cycle before it underflows. The 1581's DOS leaves timer B
|
|
25
|
+
# interrupting every 10 ms.
|
|
26
|
+
def quiet_cycles
|
|
27
|
+
return 0 unless @icr.quiet && @tod.quiet? && cnt_steady? && timer_a_quiet?
|
|
28
|
+
return QUIET if @tb.idle?
|
|
29
|
+
return 0 unless @tb.free_running? && @control_b.value.nobits?(0x60)
|
|
30
|
+
|
|
31
|
+
@tb.counter - 1
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Runs +cycles+ cycles at once while quiet? or for quiet_cycles, as
|
|
35
|
+
# cycle! would: timer A's underflows flag in the ICR, and reach the
|
|
36
|
+
# serial port's underflow line.
|
|
37
|
+
def fast_forward(cycles)
|
|
38
|
+
@tod.fast_forward(cycles)
|
|
39
|
+
@tb.fast_forward(cycles) unless @tb.idle?
|
|
40
|
+
return if @ta.idle?
|
|
41
|
+
|
|
42
|
+
@icr.flag(:timer_a) if @ta.fast_forward(cycles).positive?
|
|
43
|
+
@serial.follow_underflow(@ta.underflowed)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def timer_a_quiet? = @ta.idle? ? @serial.idle : timer_a_free_running?
|
|
49
|
+
|
|
50
|
+
# Timer A free running with its interrupt masked, and the serial port
|
|
51
|
+
# an idle input it only clocks.
|
|
52
|
+
def timer_a_free_running?
|
|
53
|
+
@ta.free_running? && !@control_a.serial_mode? && @icr.mask.value.nobits?(0x01) && @serial.drained?
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Whether CNT holds the level the last cycle sampled.
|
|
57
|
+
def cnt_steady? = @serial.cnt == @cnt_high && !@cnt_rise
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
data/lib/badline/cia/serial.rb
CHANGED
|
@@ -79,6 +79,18 @@ module Badline
|
|
|
79
79
|
@control.serial_mode?
|
|
80
80
|
end
|
|
81
81
|
|
|
82
|
+
# Whether nothing is counting down or in the delay lines, so only
|
|
83
|
+
# timer A's underflows reach the register.
|
|
84
|
+
def drained? = idle?
|
|
85
|
+
|
|
86
|
+
# Takes timer A's underflow line as cycle! would have left it, with
|
|
87
|
+
# +underflowed+ its level on the last cycle, for a CIA run without
|
|
88
|
+
# the register in use (CIA#fast_forward).
|
|
89
|
+
def follow_underflow(underflowed)
|
|
90
|
+
@underflow_high = underflowed
|
|
91
|
+
@idle = !underflowed
|
|
92
|
+
end
|
|
93
|
+
|
|
82
94
|
def cnt_in=(level)
|
|
83
95
|
@cnt_in = level
|
|
84
96
|
@cnt = level unless output?
|
data/lib/badline/cia/timer.rb
CHANGED
|
@@ -24,6 +24,32 @@ module Badline
|
|
|
24
24
|
control.out_mode? ? @toggle : @underflowed
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
# Whether the timer is stopped with nothing in its pipeline, so a
|
|
28
|
+
# cycle changes nothing.
|
|
29
|
+
def idle? = @empty && !started?
|
|
30
|
+
|
|
31
|
+
# Whether the timer counts ø2 in continuous mode with nothing pending
|
|
32
|
+
# but the reload after an underflow, so fast_forward can run it. A
|
|
33
|
+
# latch below 2 is left out.
|
|
34
|
+
def free_running?
|
|
35
|
+
started? && !@control.run_mode? && @control.value.nobits?(0x20) && @latch >= 2 && @pipe == 0b11 &&
|
|
36
|
+
(@load_delay | @oneshot_linger).zero? && (@settled || @reload)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Runs a free-running timer +cycles+ cycles at once, as cycle! would:
|
|
40
|
+
# an underflow every latch + 1 cycles, each one followed by the
|
|
41
|
+
# reload. Returns the underflows.
|
|
42
|
+
def fast_forward(cycles)
|
|
43
|
+
return 0 if cycles.zero?
|
|
44
|
+
return after_underflow(cycles, 0) unless @settled
|
|
45
|
+
|
|
46
|
+
if cycles < @counter
|
|
47
|
+
@counter -= cycles
|
|
48
|
+
return 0
|
|
49
|
+
end
|
|
50
|
+
after_underflow(cycles - @counter, 1)
|
|
51
|
+
end
|
|
52
|
+
|
|
27
53
|
def cycle!(feed)
|
|
28
54
|
feed &&= @control.value & 0x01 != 0
|
|
29
55
|
if @settled
|
|
@@ -98,6 +124,19 @@ module Badline
|
|
|
98
124
|
@control.value & 0x01 != 0
|
|
99
125
|
end
|
|
100
126
|
|
|
127
|
+
# Where fast_forward lands +cycles+ after an underflow, with
|
|
128
|
+
# +underflows+ underflows counted so far.
|
|
129
|
+
def after_underflow(cycles, underflows)
|
|
130
|
+
periods, rest = cycles.divmod(@latch + 1)
|
|
131
|
+
underflows += periods
|
|
132
|
+
@toggle = !@toggle if underflows.odd?
|
|
133
|
+
@underflowed = rest.zero?
|
|
134
|
+
@reload = rest.zero?
|
|
135
|
+
@counter = @latch - (rest.zero? ? 0 : rest - 1)
|
|
136
|
+
settle
|
|
137
|
+
underflows
|
|
138
|
+
end
|
|
139
|
+
|
|
101
140
|
def enter
|
|
102
141
|
@pipe = 0b10
|
|
103
142
|
@empty = false
|
data/lib/badline/cia.rb
CHANGED
|
@@ -4,12 +4,14 @@ require "badline/cia/interrupt_register"
|
|
|
4
4
|
require "badline/cia/serial"
|
|
5
5
|
require "badline/cia/timer"
|
|
6
6
|
require "badline/cia/state"
|
|
7
|
+
require "badline/cia/fast_forward"
|
|
7
8
|
|
|
8
9
|
module Badline
|
|
9
10
|
# CIA (Complex Interface Adapter) chip
|
|
10
11
|
class CIA
|
|
11
12
|
include Addressable
|
|
12
13
|
include State
|
|
14
|
+
include FastForward
|
|
13
15
|
|
|
14
16
|
# The original NMOS 6526 of the breadbin C64, and the HMOS 6526A of the
|
|
15
17
|
# C64C, which differs from it only in its interrupt control register.
|
|
@@ -59,14 +61,16 @@ module Badline
|
|
|
59
61
|
@tb.latch = value
|
|
60
62
|
end
|
|
61
63
|
|
|
62
|
-
|
|
64
|
+
# +tod_hz+ is the rate of the pulses on the TOD pin, the region's mains
|
|
65
|
+
# unless a machine feeds it otherwise, and 0 for a pin held still.
|
|
66
|
+
def initialize(start: 0, peripheral: nil, model: :mos6526, region: Region::PAL, tod_hz: nil)
|
|
63
67
|
raise ArgumentError, "unknown CIA model #{model}" unless MODELS.include?(model)
|
|
64
68
|
|
|
65
69
|
addressable_at(start, length: 2**8)
|
|
66
70
|
|
|
67
71
|
@model = model
|
|
68
72
|
@clock_hz = region.clock_hz
|
|
69
|
-
@mains_hz = region.mains_hz
|
|
73
|
+
@mains_hz = tod_hz || region.mains_hz
|
|
70
74
|
|
|
71
75
|
@peripheral = peripheral
|
|
72
76
|
@port_b4_handler = nil
|
|
@@ -137,9 +141,10 @@ module Badline
|
|
|
137
141
|
|
|
138
142
|
# Port A as driven by the data/direction registers alone, without
|
|
139
143
|
# peripheral pulldown. Cheap path for the VIC bank lookup.
|
|
140
|
-
def port_a_lines
|
|
141
|
-
|
|
142
|
-
|
|
144
|
+
def port_a_lines = driven_lines(@data_port_a, @data_dir_a)
|
|
145
|
+
|
|
146
|
+
# Port B as driven by the data/direction registers alone.
|
|
147
|
+
def port_b_lines = driven_lines(@data_port_b, @data_dir_b)
|
|
143
148
|
|
|
144
149
|
def read_port_b
|
|
145
150
|
lines = driven_lines(@data_port_b, @data_dir_b)
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
module Badline
|
|
4
4
|
class Computer
|
|
5
5
|
# What plugs into the machine besides its chips, the cartridge, the
|
|
6
|
-
# disk device 8 serves through the traps,
|
|
7
|
-
# their state for a snapshot.
|
|
6
|
+
# disk device 8 serves through the traps, its true drives (TrueDrives)
|
|
7
|
+
# and an REU, and their state for a snapshot.
|
|
8
8
|
module Attachments
|
|
9
|
+
include TrueDrives
|
|
10
|
+
|
|
9
11
|
# Puts the cartridge in the expansion port and wires its clock and NMI
|
|
10
12
|
# line to the machine, leaving the machine's state as it is. A
|
|
11
13
|
# restored snapshot's cartridge comes back through here.
|
|
@@ -44,36 +46,8 @@ module Badline
|
|
|
44
46
|
# it.
|
|
45
47
|
attr_reader :iec_bus
|
|
46
48
|
|
|
47
|
-
# Plugs in a Drive1541, which then runs alongside the C64 on its own
|
|
48
|
-
# clock and talks to it over the serial bus. The serial traps stop
|
|
49
|
-
# answering the drive's device number, so the KERNAL's TALK, LISTEN and
|
|
50
|
-
# byte transfers reach the drive. The LOAD and SAVE traps stay, and
|
|
51
|
-
# still serve a mounted image.
|
|
52
|
-
def attach_drive1541(drive)
|
|
53
|
-
@iec_bus.detach(@drive1541) if @drive1541
|
|
54
|
-
drive.host_clock_hz = region.clock_hz
|
|
55
|
-
@drive1541 = drive
|
|
56
|
-
drive.connect(iec_bus)
|
|
57
|
-
@serial_trap&.device = serial_trap_device
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# Unplugs the Drive1541, leaving the serial bus with nothing on it.
|
|
61
|
-
def detach_drive1541
|
|
62
|
-
return unless @drive1541
|
|
63
|
-
|
|
64
|
-
@iec_bus.detach(@drive1541)
|
|
65
|
-
@drive1541 = nil
|
|
66
|
-
@serial_trap&.device = serial_trap_device
|
|
67
|
-
end
|
|
68
|
-
|
|
69
49
|
private
|
|
70
50
|
|
|
71
|
-
# The device number the serial traps answer: device 8, unless a true
|
|
72
|
-
# drive is on the bus as device 8.
|
|
73
|
-
def serial_trap_device
|
|
74
|
-
@drive1541&.device == KernalTrap::Routine::DEVICE ? nil : KernalTrap::Routine::DEVICE
|
|
75
|
-
end
|
|
76
|
-
|
|
77
51
|
# CIA 2's port A drives the serial bus, which reads CLK and DATA back
|
|
78
52
|
# into it.
|
|
79
53
|
def plug_serial_bus
|
|
@@ -126,26 +100,6 @@ module Badline
|
|
|
126
100
|
@save_trap.load_state(input)
|
|
127
101
|
end
|
|
128
102
|
|
|
129
|
-
# A true 1541 on the serial bus. The leading flag is the bus itself,
|
|
130
|
-
# which every machine now has, kept so older snapshots still load.
|
|
131
|
-
def save_serial_bus(out)
|
|
132
|
-
out.boolean(true).boolean(!@drive1541.nil?)
|
|
133
|
-
return unless @drive1541
|
|
134
|
-
|
|
135
|
-
out.int(@drive1541.device)
|
|
136
|
-
@drive1541.save_state(out)
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
def load_serial_bus(input)
|
|
140
|
-
input.boolean?
|
|
141
|
-
return detach_drive1541 unless input.boolean?
|
|
142
|
-
|
|
143
|
-
device = input.int
|
|
144
|
-
detach_drive1541 if @drive1541 && @drive1541.device != device
|
|
145
|
-
attach_drive1541(Drive1541.new(device:)) unless @drive1541
|
|
146
|
-
@drive1541.load_state(input)
|
|
147
|
-
end
|
|
148
|
-
|
|
149
103
|
# The REU, which the setup says the machine has, and whether it has
|
|
150
104
|
# asked for the bus. Its IRQ line reaches the CPU as it was.
|
|
151
105
|
def save_reu(out)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Computer
|
|
5
|
+
# The true drives on the C64's serial bus: a Drive1541, and a Drive1581
|
|
6
|
+
# (Drive1581::Slot), each in a field of its own, and their state for a
|
|
7
|
+
# snapshot.
|
|
8
|
+
module TrueDrives
|
|
9
|
+
include Drive1581::Slot
|
|
10
|
+
|
|
11
|
+
# Plugs in a Drive1541, which then runs alongside the C64 on its own
|
|
12
|
+
# clock and talks to it over the serial bus. The serial traps stop
|
|
13
|
+
# answering the drive's device number, so the KERNAL's TALK, LISTEN and
|
|
14
|
+
# byte transfers reach the drive. The LOAD and SAVE traps stay, and
|
|
15
|
+
# still serve a mounted image.
|
|
16
|
+
def attach_drive1541(drive)
|
|
17
|
+
@iec_bus.detach(@drive1541) if @drive1541
|
|
18
|
+
drive.host_clock_hz = region.clock_hz
|
|
19
|
+
@drive1541 = drive
|
|
20
|
+
drive.connect(iec_bus)
|
|
21
|
+
@serial_trap&.device = serial_trap_device
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# The true drive on the serial bus: the Drive1581 ahead of the
|
|
25
|
+
# Drive1541, or nil.
|
|
26
|
+
def true_drive = @drive1581 || @drive1541
|
|
27
|
+
|
|
28
|
+
# Plugs a Drive1541 in as device 8, in place of a 1581 there, and
|
|
29
|
+
# returns it.
|
|
30
|
+
def plug_true_drive
|
|
31
|
+
detach_drive1581 if drive1581_on_device8?
|
|
32
|
+
Drive1541.new.tap { |drive| attach_drive1541(drive) }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Unplugs the Drive1541, leaving the serial bus with nothing on it.
|
|
36
|
+
def detach_drive1541
|
|
37
|
+
return unless @drive1541
|
|
38
|
+
|
|
39
|
+
@iec_bus.detach(@drive1541)
|
|
40
|
+
@drive1541 = nil
|
|
41
|
+
@serial_trap&.device = serial_trap_device
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
# The device number the serial traps answer: device 8, unless a true
|
|
47
|
+
# drive is on the bus as device 8.
|
|
48
|
+
def serial_trap_device
|
|
49
|
+
device = KernalTrap::Routine::DEVICE
|
|
50
|
+
@drive1541&.device == device || drive1581_on_device8? ? nil : device
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# A true 1541 on the serial bus, then a 1581. The leading flag is the
|
|
54
|
+
# bus itself, which every machine now has.
|
|
55
|
+
def save_serial_bus(out)
|
|
56
|
+
out.boolean(true).boolean(!@drive1541.nil?)
|
|
57
|
+
if @drive1541
|
|
58
|
+
out.int(@drive1541.device)
|
|
59
|
+
@drive1541.save_state(out)
|
|
60
|
+
end
|
|
61
|
+
save_drive1581(out)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def load_serial_bus(input)
|
|
65
|
+
input.boolean?
|
|
66
|
+
load_drive1541(input)
|
|
67
|
+
load_drive1581(input)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def load_drive1541(input)
|
|
71
|
+
return detach_drive1541 unless input.boolean?
|
|
72
|
+
|
|
73
|
+
device = input.int
|
|
74
|
+
detach_drive1541 if @drive1541 && @drive1541.device != device
|
|
75
|
+
attach_drive1541(Drive1541.new(device:)) unless @drive1541
|
|
76
|
+
@drive1541.load_state(input)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
data/lib/badline/computer.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "badline/computer/true_drives"
|
|
3
4
|
require "badline/computer/attachments"
|
|
4
5
|
require "badline/computer/kernal_traps"
|
|
5
6
|
require "badline/computer/saved_state"
|
|
@@ -77,6 +78,7 @@ module Badline
|
|
|
77
78
|
@serial_trap = nil
|
|
78
79
|
@save_trap = nil
|
|
79
80
|
@drive1541 = nil
|
|
81
|
+
@drive1581 = nil
|
|
80
82
|
plug_serial_bus
|
|
81
83
|
@reu = reu ? plug_reu(reu) : nil
|
|
82
84
|
end
|
|
@@ -101,6 +103,7 @@ module Badline
|
|
|
101
103
|
watch_freeze if @freezing
|
|
102
104
|
clock_cpu
|
|
103
105
|
@drive1541&.host_cycle!
|
|
106
|
+
@drive1581&.host_cycle!
|
|
104
107
|
|
|
105
108
|
@cycles += 1
|
|
106
109
|
end
|
|
@@ -174,6 +177,7 @@ module Badline
|
|
|
174
177
|
@dma = false
|
|
175
178
|
@drive&.reset!
|
|
176
179
|
@drive1541&.reset!
|
|
180
|
+
@drive1581&.reset!
|
|
177
181
|
@freezing = false
|
|
178
182
|
@nmi_asserted = false
|
|
179
183
|
cpu.reset!
|
data/lib/badline/core.rb
CHANGED
|
@@ -38,6 +38,8 @@ require "badline/vic"
|
|
|
38
38
|
require "badline/keyboard_buffer"
|
|
39
39
|
require "badline/iec_bus"
|
|
40
40
|
require "badline/drive1541"
|
|
41
|
+
require "badline/drive1581"
|
|
42
|
+
require "badline/drive1581/slot"
|
|
41
43
|
require "badline/snapshot/state"
|
|
42
44
|
require "badline/snapshot/setup"
|
|
43
45
|
require "badline/computer"
|