badline 0.11.1 → 0.12.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 +26 -0
- data/CLAUDE.md +8 -7
- data/CONTRIBUTING.md +7 -6
- data/README.md +12 -5
- data/doc/media.md +28 -1
- data/doc/pinned-behaviour.md +31 -1
- data/exe/badline-ruby +1 -1
- data/lib/badline/address_bus/saved_state.rb +4 -10
- data/lib/badline/address_bus.rb +8 -27
- data/lib/badline/audio/bare_player.rb +1 -1
- data/lib/badline/c128/bus.rb +17 -52
- data/lib/badline/c128/bus_state.rb +5 -5
- data/lib/badline/c128/drives.rb +35 -22
- data/lib/badline/c128/fast_serial.rb +1 -0
- data/lib/badline/c128/io_pages.rb +35 -0
- data/lib/badline/c128/mmu.rb +9 -7
- data/lib/badline/c128/modes.rb +17 -4
- data/lib/badline/c128/saved_state.rb +22 -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 +21 -83
- 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 +21 -3
- data/lib/badline/cia.rb +8 -5
- data/lib/badline/computer/attachments.rb +14 -95
- data/lib/badline/computer/common.rb +34 -0
- data/lib/badline/computer/kernal_traps.rb +65 -5
- data/lib/badline/computer/true_drives.rb +69 -0
- data/lib/badline/computer.rb +9 -68
- data/lib/badline/core.rb +4 -2
- data/lib/badline/cpu_port.rb +112 -0
- data/lib/badline/drive/core.rb +8 -33
- data/lib/badline/drive/fast_serial.rb +73 -0
- data/lib/badline/drive/idle.rb +1 -11
- data/lib/badline/drive/orbit.rb +2 -5
- data/lib/badline/drive/sleep.rb +0 -2
- data/lib/badline/drive/vias.rb +84 -0
- data/lib/badline/drive.rb +2 -0
- data/lib/badline/drive1541/disk.rb +14 -17
- data/lib/badline/drive1541/disk_state.rb +7 -15
- data/lib/badline/drive1541.rb +1 -0
- data/lib/badline/drive1571/wd1770.rb +94 -8
- data/lib/badline/drive1571.rb +8 -58
- 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 +1 -3
- data/lib/badline/frontend/audio_device.rb +67 -0
- data/lib/badline/frontend/audio_sink.rb +16 -50
- data/lib/badline/frontend/boot.rb +24 -67
- data/lib/badline/frontend/buttons.rb +7 -7
- 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} +5 -8
- 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 +14 -2
- data/lib/badline/kernal_trap/{file.rb → file_routine.rb} +1 -1
- data/lib/badline/kernal_trap/load.rb +1 -1
- data/lib/badline/kernal_trap/save.rb +1 -1
- data/lib/badline/kernal_trap.rb +1 -1
- data/lib/badline/machine/clocking.rb +50 -0
- data/lib/badline/machine.rb +63 -6
- data/lib/badline/media/boot_disk.rb +50 -0
- data/lib/badline/media/disk_list.rb +3 -3
- data/lib/badline/media/disk_set.rb +1 -1
- data/lib/badline/media/extensions.rb +33 -0
- data/lib/badline/media/not_disk.rb +8 -0
- data/lib/badline/media/program.rb +28 -0
- data/lib/badline/media/true_drive.rb +38 -17
- data/lib/badline/media/tune.rb +35 -0
- data/lib/badline/media/vic20_cartridge.rb +1 -7
- data/lib/badline/media/vic20_media.rb +38 -80
- data/lib/badline/media.rb +90 -81
- data/lib/badline/options/event.rb +4 -2
- data/lib/badline/options/family.rb +3 -9
- data/lib/badline/options/help.rb +2 -1
- data/lib/badline/roms/README +9 -0
- data/lib/badline/roms/dos1581.rom +0 -0
- data/lib/badline/sdl.rb +37 -20
- data/lib/badline/sid.rb +1 -1
- data/lib/badline/snapshot/machine_state.rb +9 -1
- data/lib/badline/snapshot/state.rb +6 -4
- data/lib/badline/snapshot.rb +11 -9
- data/lib/badline/storage/d64_image.rb +15 -7
- data/lib/badline/storage/disk_image/writing.rb +2 -2
- data/lib/badline/storage/disk_image.rb +1 -1
- data/lib/badline/storage/host_directory.rb +1 -1
- data/lib/badline/storage/p00.rb +8 -0
- data/lib/badline/storage/t64.rb +1 -1
- data/lib/badline/time_of_day.rb +3 -3
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic/bank.rb +1 -1
- data/lib/badline/vic/border_mask.rb +1 -1
- data/lib/badline/vic/collisions.rb +1 -1
- data/lib/badline/vic/color_patches.rb +1 -1
- data/lib/badline/vic/display_state.rb +1 -1
- data/lib/badline/vic/graphics_mode.rb +1 -1
- data/lib/badline/vic/graphics_shifter.rb +1 -1
- data/lib/badline/vic/register_log.rb +1 -1
- data/lib/badline/vic/registers.rb +1 -1
- data/lib/badline/vic/sequencer.rb +1 -1
- data/lib/badline/vic/sequencer_output.rb +1 -1
- data/lib/badline/vic/sequencer_state.rb +1 -1
- data/lib/badline/vic/sprite/internal_bus.rb +1 -1
- data/lib/badline/vic/sprite/saved_state.rb +1 -1
- data/lib/badline/vic/sprite/shifter.rb +1 -1
- data/lib/badline/vic/sprite/timing.rb +1 -1
- data/lib/badline/vic/sprite.rb +1 -1
- data/lib/badline/vic/sprites.rb +1 -1
- data/lib/badline/vic/sprites_state.rb +1 -1
- data/lib/badline/vic.rb +6 -5
- data/lib/badline/vic20/attachments.rb +15 -71
- data/lib/badline/vic20/saved_state.rb +2 -30
- data/lib/badline/vic20.rb +4 -37
- data/lib/badline/z80/core.rb +2 -0
- data/lib/badline/z80/saved_state.rb +69 -0
- metadata +61 -23
- data/lib/badline/cycleable.rb +0 -38
- /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
data/lib/badline/c128/drives.rb
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module Badline
|
|
4
4
|
class C128
|
|
5
|
-
# The true drives on the C128's serial bus: a Drive1541,
|
|
6
|
-
#
|
|
7
|
-
#
|
|
5
|
+
# The true drives on the C128's serial bus: a Drive1541, the C128D's
|
|
6
|
+
# built-in 1571 (Drive1571) or a 1581 (Drive1581::Slot), each in a
|
|
7
|
+
# field of its own, so none makes another's per-cycle call a union.
|
|
8
8
|
module Drives
|
|
9
9
|
attr_reader :drive1571
|
|
10
10
|
|
|
@@ -28,11 +28,15 @@ module Badline
|
|
|
28
28
|
@serial_trap&.device = serial_trap_device
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
# The true drive on the bus, the 1571 ahead of a 1541.
|
|
32
|
-
def true_drive = @drive1571 || @drive1541
|
|
31
|
+
# The true drive on the bus, a 1581 ahead of the 1571 ahead of a 1541.
|
|
32
|
+
def true_drive = @drive1581 || @drive1571 || @drive1541
|
|
33
33
|
|
|
34
|
-
# Plugs in the C128D's 1571 as device 8,
|
|
35
|
-
|
|
34
|
+
# Plugs in the C128D's 1571 as device 8, in place of a 1581 there, and
|
|
35
|
+
# returns it.
|
|
36
|
+
def plug_true_drive
|
|
37
|
+
detach_drive1581 if drive1581_on_device8?
|
|
38
|
+
Drive1571.new.tap { |drive| attach_drive1571(drive) }
|
|
39
|
+
end
|
|
36
40
|
|
|
37
41
|
private
|
|
38
42
|
|
|
@@ -43,6 +47,7 @@ module Badline
|
|
|
43
47
|
@save_trap = nil
|
|
44
48
|
@drive1541 = nil
|
|
45
49
|
@drive1571 = nil
|
|
50
|
+
@drive1581 = nil
|
|
46
51
|
plug_serial_bus
|
|
47
52
|
plug_fast_serial
|
|
48
53
|
end
|
|
@@ -50,10 +55,25 @@ module Badline
|
|
|
50
55
|
# Device 8, unless a true drive is on the bus as device 8.
|
|
51
56
|
def serial_trap_device
|
|
52
57
|
device = KernalTrap::Routine::DEVICE
|
|
53
|
-
@drive1571&.device == device || @drive1541&.device == device ? nil : device
|
|
58
|
+
@drive1571&.device == device || @drive1541&.device == device || drive1581_on_device8? ? nil : device
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# A 1581 plugged in holds the machine's on_init handlers back until
|
|
62
|
+
# its DOS has booted, which takes longer than the C128 KERNAL's.
|
|
63
|
+
def drive1581_attached
|
|
64
|
+
@init_threshold = [@init_threshold, Drive1581::BOOT_CYCLES].max if @cycles < @init_threshold
|
|
54
65
|
end
|
|
55
66
|
|
|
56
|
-
#
|
|
67
|
+
# Unplugs the 1541 and the 1571 if either is device 8, for a 1581 to
|
|
68
|
+
# take its place.
|
|
69
|
+
def unplug_device8
|
|
70
|
+
device = KernalTrap::Routine::DEVICE
|
|
71
|
+
detach_drive1541 if @drive1541&.device == device
|
|
72
|
+
detach_drive1571 if @drive1571&.device == device
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# A true 1541 (Computer::Attachments#save_serial_bus), then a 1571 and
|
|
76
|
+
# a 1581.
|
|
57
77
|
def save_serial_bus(out)
|
|
58
78
|
out.boolean(true).boolean(!@drive1541.nil?)
|
|
59
79
|
if @drive1541
|
|
@@ -61,25 +81,18 @@ module Badline
|
|
|
61
81
|
@drive1541.save_state(out)
|
|
62
82
|
end
|
|
63
83
|
out.boolean(!@drive1571.nil?)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
84
|
+
if @drive1571
|
|
85
|
+
out.int(@drive1571.device)
|
|
86
|
+
@drive1571.save_state(out)
|
|
87
|
+
end
|
|
88
|
+
save_drive1581(out)
|
|
68
89
|
end
|
|
69
90
|
|
|
70
91
|
def load_serial_bus(input)
|
|
71
92
|
input.boolean?
|
|
72
93
|
load_drive1541(input)
|
|
73
94
|
load_drive1571(input)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def load_drive1541(input)
|
|
77
|
-
return detach_drive1541 unless input.boolean?
|
|
78
|
-
|
|
79
|
-
device = input.int
|
|
80
|
-
detach_drive1541 if @drive1541 && @drive1541.device != device
|
|
81
|
-
attach_drive1541(Drive1541.new(device:)) unless @drive1541
|
|
82
|
-
@drive1541.load_state(input)
|
|
95
|
+
load_drive1581(input)
|
|
83
96
|
end
|
|
84
97
|
|
|
85
98
|
def load_drive1571(input)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class C128
|
|
5
|
+
class Bus
|
|
6
|
+
# The chips at $D000-$DFFF, which the 8502 sees while I/O is mapped
|
|
7
|
+
# in and the Z80 reaches through IN and OUT.
|
|
8
|
+
module IOPages
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def map_io_pages
|
|
12
|
+
@io_mapped = true
|
|
13
|
+
lay_out_io(@read_pages, @write_pages)
|
|
14
|
+
@read_pages[0xd5] = @write_pages[0xd5] = @mmu.c64_mode? ? @open_bus : @mmu
|
|
15
|
+
map_cartridge_io if @cartridge
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# The chips into +reads+ and +writes+, but for the MMU's page.
|
|
19
|
+
def lay_out_io(reads, writes)
|
|
20
|
+
reads.fill(@vic, 0xd0, 4)
|
|
21
|
+
writes.fill(@vic_writes, 0xd0, 4)
|
|
22
|
+
reads[0xd4] = writes[0xd4] = @sid
|
|
23
|
+
reads[0xd6] = writes[0xd6] = @vdc
|
|
24
|
+
reads[0xd7] = writes[0xd7] = @debug_page || @open_bus
|
|
25
|
+
reads.fill(@color_ram, 0xd8, 4)
|
|
26
|
+
writes.fill(@color_ram, 0xd8, 4)
|
|
27
|
+
reads[0xdc] = writes[0xdc] = @cia1
|
|
28
|
+
reads[0xdd] = writes[0xdd] = @cia2
|
|
29
|
+
reads.fill(@open_bus, 0xde, 2)
|
|
30
|
+
writes.fill(@open_bus, 0xde, 2)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/lib/badline/c128/mmu.rb
CHANGED
|
@@ -19,12 +19,11 @@ module Badline
|
|
|
19
19
|
# A write to P0H or P1H waits until the matching P0L or P1L write
|
|
20
20
|
# takes it.
|
|
21
21
|
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
# force.
|
|
22
|
+
# A reset clears MCR bit 0, so the Z80 runs first, boots and hands the
|
|
23
|
+
# bus to the 8502. A machine built for C64 mode resets into C64 mode
|
|
24
|
+
# instead, with the 8502 running, the state the C128 KERNAL leaves when
|
|
25
|
+
# C= is held at power-on. In C64 mode the MMU answers nowhere, but the
|
|
26
|
+
# CPU's RAM bank, common RAM and the VIC's bank it holds stay in force.
|
|
28
27
|
class MMU
|
|
29
28
|
CR = 0
|
|
30
29
|
MCR = 5
|
|
@@ -86,7 +85,7 @@ module Badline
|
|
|
86
85
|
def reset!
|
|
87
86
|
fast_serial_out = fast_serial_out?
|
|
88
87
|
@registers.fill(0)
|
|
89
|
-
@registers[MCR] = @reset_mode == :c64 ? MCR_C64_MODE | MCR_8502 :
|
|
88
|
+
@registers[MCR] = @reset_mode == :c64 ? MCR_C64_MODE | MCR_8502 : 0
|
|
90
89
|
@registers[P1L] = 0x01
|
|
91
90
|
@registers[VR] = VERSION
|
|
92
91
|
@p0h_latch = 0
|
|
@@ -101,6 +100,9 @@ module Badline
|
|
|
101
100
|
# CIA 1's CNT and SP drive SRQ and DATA.
|
|
102
101
|
def fast_serial_out? = @registers[MCR].anybits?(MCR_FSDIR)
|
|
103
102
|
|
|
103
|
+
# Whether MCR bit 0 gives the bus to the Z80.
|
|
104
|
+
def z80? = @registers[MCR].nobits?(MCR_8502)
|
|
105
|
+
|
|
104
106
|
# :c64 or :c128, the mode MCR bit 6 selects.
|
|
105
107
|
def mode = c64_mode? ? :c64 : :c128
|
|
106
108
|
|
data/lib/badline/c128/modes.rb
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module Badline
|
|
4
4
|
class C128
|
|
5
|
-
# Following the MMU between C128 and C64 mode: the KERNAL traps
|
|
6
|
-
# the KERNAL the machine runs, C= held from power-on
|
|
7
|
-
# mode through the C128 KERNAL, and #active_screen
|
|
8
|
-
# which screen the mode's editor prints to.
|
|
5
|
+
# Following the MMU between C128 and C64 mode: the KERNAL traps and
|
|
6
|
+
# typed keys go to the KERNAL the machine runs, C= held from power-on
|
|
7
|
+
# starts it in C64 mode through the C128 KERNAL, and #active_screen
|
|
8
|
+
# tells a front end which screen the mode's editor prints to.
|
|
9
9
|
module Modes
|
|
10
10
|
# Holds C= down from power-on until the C128 KERNAL has taken the
|
|
11
11
|
# machine to C64 mode, as a user does to start a C128 in C64 mode.
|
|
@@ -43,6 +43,19 @@ module Badline
|
|
|
43
43
|
@holding_commodore = false
|
|
44
44
|
@init_threshold = @cycles + Computer::INIT_THRESHOLD if @cycles < @init_threshold
|
|
45
45
|
end
|
|
46
|
+
|
|
47
|
+
# The keyboard buffer of the KERNAL the machine runs: the C64's, or in
|
|
48
|
+
# C128 mode BASIC 7.0's.
|
|
49
|
+
def feed_keyboard
|
|
50
|
+
c64 = mode == :c64
|
|
51
|
+
count = c64 ? KeyboardBuffer::COUNT : C128_KEYBOARD_COUNT
|
|
52
|
+
return unless ram.peek(count).zero?
|
|
53
|
+
|
|
54
|
+
chunk = @pending_keys.shift(KeyboardBuffer::CAPACITY)
|
|
55
|
+
ram.write(c64 ? KeyboardBuffer::ADDRESS : C128_KEYBOARD_BUFFER, chunk)
|
|
56
|
+
ram.poke(count, chunk.length)
|
|
57
|
+
@pending_keys = nil if @pending_keys.empty?
|
|
58
|
+
end
|
|
46
59
|
end
|
|
47
60
|
end
|
|
48
61
|
end
|
|
@@ -81,6 +81,8 @@ module Badline
|
|
|
81
81
|
save_cartridge(out)
|
|
82
82
|
@bus.save_state(out)
|
|
83
83
|
@cpu.save_state(out)
|
|
84
|
+
@z80.save_state(out)
|
|
85
|
+
out.int(@z80_due).int(@chips_ahead).boolean(@cpu_reset_pending)
|
|
84
86
|
save_trap_drive(out)
|
|
85
87
|
save_serial_bus(out)
|
|
86
88
|
end
|
|
@@ -98,9 +100,11 @@ module Badline
|
|
|
98
100
|
@pending_keys = input.ints if input.boolean?
|
|
99
101
|
@init_handlers_lost = [input.int - @init_handlers.length, 0].max
|
|
100
102
|
load_cartridge(input)
|
|
103
|
+
@cpu_reset_pending = false
|
|
101
104
|
@bus.load_state(input)
|
|
102
105
|
push_serial_lines
|
|
103
106
|
@cpu.load_state(input)
|
|
107
|
+
load_z80(input)
|
|
104
108
|
load_trap_drive(input)
|
|
105
109
|
load_serial_bus(input)
|
|
106
110
|
push_fast_serial
|
|
@@ -108,6 +112,24 @@ module Badline
|
|
|
108
112
|
|
|
109
113
|
private
|
|
110
114
|
|
|
115
|
+
# The Z80 and which CPU has the bus, which a state from before the
|
|
116
|
+
# Z80 leaves to the 8502. The bus, restored before it, hands the bus
|
|
117
|
+
# to its CPU without resetting the 8502.
|
|
118
|
+
def load_z80(input)
|
|
119
|
+
@z80_running = @bus.z80?
|
|
120
|
+
@z80_turn = @z80_running
|
|
121
|
+
@z80_due = @z80.cycles
|
|
122
|
+
@chips_ahead = 0
|
|
123
|
+
@cpu_reset_pending = false
|
|
124
|
+
return unless input.schema > 6
|
|
125
|
+
|
|
126
|
+
@z80.load_state(input)
|
|
127
|
+
@z80_due = input.int
|
|
128
|
+
@chips_ahead = input.int
|
|
129
|
+
@cpu_reset_pending = input.boolean?
|
|
130
|
+
@z80_turn = @z80_running || @chips_ahead.positive?
|
|
131
|
+
end
|
|
132
|
+
|
|
111
133
|
def check_setup(setup)
|
|
112
134
|
ours = [@model.name, @sid.model, @c64_built ? :c64 : :c128]
|
|
113
135
|
return if setup == ours
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "badline/z80/core"
|
|
4
|
+
|
|
5
|
+
module Badline
|
|
6
|
+
class C128
|
|
7
|
+
# The C128's Z80A, on the Z80's side of the C128::Bus.
|
|
8
|
+
class Z80
|
|
9
|
+
include Badline::Z80::Core
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# The Z80's accesses, through the pages C128::Bus lays out for it
|
|
13
|
+
# (Bus::Z80Pages). An IN or OUT first brings the chips up to the cycle
|
|
14
|
+
# it falls in, since the Z80 runs whole instructions ahead of them.
|
|
15
|
+
class Z80Bus
|
|
16
|
+
def initialize(bus)
|
|
17
|
+
@read_pages = bus.z80_read_pages
|
|
18
|
+
@write_pages = bus.z80_write_pages
|
|
19
|
+
@input_pages = bus.z80_input_pages
|
|
20
|
+
@output_pages = bus.z80_output_pages
|
|
21
|
+
@machine = nil
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# The machine whose chips an IN or OUT catches up.
|
|
25
|
+
attr_writer :machine
|
|
26
|
+
|
|
27
|
+
def fetch(addr) = @read_pages[addr >> 8].peek(addr)
|
|
28
|
+
|
|
29
|
+
def read(addr) = @read_pages[addr >> 8].peek(addr)
|
|
30
|
+
|
|
31
|
+
def write(addr, value)
|
|
32
|
+
@write_pages[addr >> 8].poke(addr, value)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def input(port)
|
|
36
|
+
@machine.z80_catch_up
|
|
37
|
+
@input_pages[port >> 8].peek(port)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def output(port, value)
|
|
41
|
+
@machine.z80_catch_up
|
|
42
|
+
@output_pages[port >> 8].poke(port, value)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Nothing drives the data bus in an interrupt acknowledge cycle.
|
|
46
|
+
def acknowledge = 0xff
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class C128
|
|
5
|
+
class Bus
|
|
6
|
+
# What the Z80 sees, in memory and through IN and OUT, laid over the
|
|
7
|
+
# 8502's map whenever the MMU, the port or a cartridge changes it
|
|
8
|
+
# while the Z80 has the bus.
|
|
9
|
+
#
|
|
10
|
+
# In C128 mode, with CR's RAM bank 0, the MMU translates $0000-$0FFF
|
|
11
|
+
# to $D000-$DFFF of bank 0: a read gets the Z80 BIOS, which the
|
|
12
|
+
# KERNAL ROM holds there, and a write or an IN or OUT the RAM at
|
|
13
|
+
# $Dxxx. With bank 1 those pages are RAM, common RAM's or bank 1's.
|
|
14
|
+
# $D000-$DFFF is RAM. In C64 mode the PLA decodes the Z80's memory
|
|
15
|
+
# accesses as it does the 8502's.
|
|
16
|
+
#
|
|
17
|
+
# In both modes the chips at $D000-$DFFF answer IN and OUT whether
|
|
18
|
+
# I/O is mapped in or not. The MMU's registers at $D500 take an OUT
|
|
19
|
+
# either way in C128 mode, but answer an IN only with CR's I/O bit
|
|
20
|
+
# on, memory answering it otherwise. While I/O is mapped in, the
|
|
21
|
+
# colour RAM the 8502 sees also answers at $1000-$13FF. An IN or OUT
|
|
22
|
+
# anywhere else reaches what memory has there.
|
|
23
|
+
module Z80Pages
|
|
24
|
+
# A colour RAM bank seen at $1000-$13FF.
|
|
25
|
+
class ColorWindow
|
|
26
|
+
def initialize(color_ram)
|
|
27
|
+
@color_ram = color_ram
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def peek(addr) = @color_ram.peek(0xd800 | (addr & 0x3ff))
|
|
31
|
+
def poke(addr, value) = @color_ram.poke(0xd800 | (addr & 0x3ff), value)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# The Z80's memory and I/O pages, each 256 entries.
|
|
35
|
+
attr_reader :z80_read_pages, :z80_write_pages, :z80_input_pages, :z80_output_pages
|
|
36
|
+
|
|
37
|
+
# Calls the block whenever MCR bit 0 hands the bus to the other CPU.
|
|
38
|
+
def on_processor_change(&block)
|
|
39
|
+
@on_processor_change = block
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Whether the Z80 has the bus, MCR bit 0 low.
|
|
43
|
+
def z80? = @z80
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
# Lays out the Z80's pages again while it has the bus, and tells the
|
|
48
|
+
# machine if MCR bit 0 handed the bus over.
|
|
49
|
+
def update_z80!
|
|
50
|
+
z80 = @mmu.z80?
|
|
51
|
+
map_z80_pages if z80
|
|
52
|
+
return if z80 == @z80
|
|
53
|
+
|
|
54
|
+
@z80 = z80
|
|
55
|
+
@on_processor_change&.call
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def build_z80_pages
|
|
59
|
+
@z80_bios = ROM.new(ROM.read("c128/kernal.rom")[0x1000, 0x1000], length: 0x1000, start: 0)
|
|
60
|
+
@z80_read_pages = Array.new(256)
|
|
61
|
+
@z80_write_pages = Array.new(256)
|
|
62
|
+
@z80_input_pages = Array.new(256)
|
|
63
|
+
@z80_output_pages = Array.new(256)
|
|
64
|
+
@z80_color_ram = [ColorWindow.new(@color_lines.color_ram(0)), ColorWindow.new(@color_lines.color_ram(1))]
|
|
65
|
+
@z80_translated = RAMWindow.new(@ram, 0xd000, 0xfff)
|
|
66
|
+
@z80 = @mmu.z80?
|
|
67
|
+
@on_processor_change = nil
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def map_z80_pages
|
|
71
|
+
c128_mode = !@mmu.c64_mode?
|
|
72
|
+
bios = c128_mode && @mmu.cr < 0x40
|
|
73
|
+
@z80_read_pages.replace(@read_pages)
|
|
74
|
+
@z80_write_pages.replace(@write_pages)
|
|
75
|
+
map_z80_ram_pages if c128_mode
|
|
76
|
+
map_z80_bios if bios
|
|
77
|
+
map_z80_color_ram(c128_mode) if @io_mapped
|
|
78
|
+
@z80_input_pages.replace(@z80_read_pages)
|
|
79
|
+
@z80_output_pages.replace(@z80_write_pages)
|
|
80
|
+
@z80_input_pages.fill(@z80_translated, 0, 16) if bios
|
|
81
|
+
@io_mapped ? map_z80_io_from_cpu : map_z80_io
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def map_z80_ram_pages
|
|
85
|
+
(0xd0..0xdf).each do |page|
|
|
86
|
+
ram = bank_of(page, @mmu.cpu_bank).zero? ? @ram : @bank1
|
|
87
|
+
@z80_read_pages[page] = @z80_write_pages[page] = ram
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def map_z80_bios
|
|
92
|
+
@z80_read_pages.fill(@z80_bios, 0, 16)
|
|
93
|
+
@z80_write_pages.fill(@z80_translated, 0, 16)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def map_z80_color_ram(c128_mode)
|
|
97
|
+
window = @z80_color_ram[c128_mode ? @io_port.value & 0x01 : 1]
|
|
98
|
+
@z80_read_pages.fill(window, 0x10, 4)
|
|
99
|
+
@z80_write_pages.fill(window, 0x10, 4)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def map_z80_io_from_cpu
|
|
103
|
+
(0xd0..0xdf).each do |page|
|
|
104
|
+
@z80_input_pages[page] = @read_pages[page]
|
|
105
|
+
@z80_output_pages[page] = @write_pages[page]
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# The chips at $D000-$DFFF while I/O is mapped out, leaving an IN
|
|
110
|
+
# at $D500 to memory.
|
|
111
|
+
def map_z80_io
|
|
112
|
+
lay_out_io(@z80_input_pages, @z80_output_pages)
|
|
113
|
+
@z80_output_pages[0xd5] = @mmu unless @mmu.c64_mode?
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class C128
|
|
5
|
+
# The Z80's turns on the bus. MCR bit 0 hands the bus between the 8502
|
|
6
|
+
# and the Z80, and while the Z80 has it each cycle clocks the chips,
|
|
7
|
+
# then gives the Z80 2 T-states unless BA holds it. The Z80 runs whole
|
|
8
|
+
# instructions while it is behind, so an IN or OUT first brings the
|
|
9
|
+
# chips up to the cycle it falls in (#z80_catch_up), and the cycles
|
|
10
|
+
# that follow clock nothing until the machine catches up with them.
|
|
11
|
+
module Z80Turns
|
|
12
|
+
# The T-states from the start of an I/O machine cycle to the one its
|
|
13
|
+
# data moves in. 1 and 2 pass c128modez80-23, whose IN reads open
|
|
14
|
+
# bus, and 0, 3 and 4 fail it.
|
|
15
|
+
IO_DELAY = 2
|
|
16
|
+
|
|
17
|
+
attr_reader :z80
|
|
18
|
+
|
|
19
|
+
# Brings the chips up to the cycle an IN or OUT of the Z80 falls in.
|
|
20
|
+
def z80_catch_up
|
|
21
|
+
while @z80.cycles + IO_DELAY >= @z80_due
|
|
22
|
+
clock_chips
|
|
23
|
+
@chips_ahead += 1
|
|
24
|
+
@z80_due += 2
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def build_z80
|
|
31
|
+
z80_bus = Z80Bus.new(@bus)
|
|
32
|
+
@z80 = Z80.new(z80_bus)
|
|
33
|
+
z80_bus.machine = self
|
|
34
|
+
@z80_running = @bus.z80?
|
|
35
|
+
@z80_turn = @z80_running
|
|
36
|
+
@z80_due = 0
|
|
37
|
+
@chips_ahead = 0
|
|
38
|
+
@cpu_reset_pending = @z80_running
|
|
39
|
+
@bus.on_processor_change { processor_changed }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# A cycle while the Z80 has the bus, or after it handed the bus to the
|
|
43
|
+
# 8502 while the chips were ahead.
|
|
44
|
+
def z80_cycle!
|
|
45
|
+
if @chips_ahead.positive?
|
|
46
|
+
@chips_ahead -= 1
|
|
47
|
+
@z80_turn = @z80_running || @chips_ahead.positive?
|
|
48
|
+
return
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
clock_chips
|
|
52
|
+
@z80_due += 2 unless @vic.ba_low?
|
|
53
|
+
z80 = @z80
|
|
54
|
+
z80.step! while @z80_running && z80.cycles < @z80_due
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# The chips' share of a cycle while the Z80 has the bus, with the
|
|
58
|
+
# interrupt lines going to the Z80.
|
|
59
|
+
def clock_chips
|
|
60
|
+
handle_init if @cycles == @init_threshold
|
|
61
|
+
feed_keyboard if @pending_keys
|
|
62
|
+
|
|
63
|
+
clock_bits = @clock_bits
|
|
64
|
+
@clock_bits = @vic.clock_bits
|
|
65
|
+
@vic.test_step! if clock_bits >= 0x02
|
|
66
|
+
|
|
67
|
+
@vic.cycle!
|
|
68
|
+
@cia1.cycle!
|
|
69
|
+
@cia2.cycle!
|
|
70
|
+
@sid.cycle!
|
|
71
|
+
@datasette.cycle!
|
|
72
|
+
@vdc.cycle!
|
|
73
|
+
|
|
74
|
+
@z80.int = @cia1.interrupted? || @vic.interrupted?
|
|
75
|
+
nmi = @cia2.interrupted? || @cartridge_nmi || @restore_pulse
|
|
76
|
+
@restore_pulse = false
|
|
77
|
+
@nmi_asserted = nmi
|
|
78
|
+
@z80.nmi = nmi
|
|
79
|
+
clock_serial_bus
|
|
80
|
+
|
|
81
|
+
@cycles += 1
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# MCR bit 0 handed the bus to the other CPU. The 8502 runs its reset
|
|
85
|
+
# vector the first time it gets the bus after a reset.
|
|
86
|
+
def processor_changed
|
|
87
|
+
@z80_running = @bus.z80?
|
|
88
|
+
@z80_turn = @z80_running || @chips_ahead.positive?
|
|
89
|
+
@z80_due = @z80.cycles
|
|
90
|
+
return if @z80_running || !@cpu_reset_pending
|
|
91
|
+
|
|
92
|
+
@cpu_reset_pending = false
|
|
93
|
+
@cpu.reset!
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# The RES line reaches the Z80, and the 8502 waits for the bus to run
|
|
97
|
+
# its reset vector, unless the machine resets into C64 mode.
|
|
98
|
+
def reset_z80
|
|
99
|
+
@chips_ahead = 0
|
|
100
|
+
@z80_turn = @z80_running
|
|
101
|
+
@z80.reset!
|
|
102
|
+
@c64_built ? @cpu.reset! : @cpu_reset_pending = true
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|