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
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,15 +100,36 @@ 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)
|
|
110
|
+
push_fast_serial
|
|
106
111
|
end
|
|
107
112
|
|
|
108
113
|
private
|
|
109
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
|
+
|
|
110
133
|
def check_setup(setup)
|
|
111
134
|
ours = [@model.name, @sid.model, @c64_built ? :c64 : :c128]
|
|
112
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
|
data/lib/badline/c128.rb
CHANGED
|
@@ -4,25 +4,33 @@ require "badline/c128/model"
|
|
|
4
4
|
require "badline/c128/mmu"
|
|
5
5
|
require "badline/c128/vdc"
|
|
6
6
|
require "badline/c128/mmu_pages"
|
|
7
|
+
require "badline/c128/io_pages"
|
|
8
|
+
require "badline/c128/z80_pages"
|
|
7
9
|
require "badline/c128/color_lines"
|
|
8
10
|
require "badline/c128/banks"
|
|
9
11
|
require "badline/c128/bus_state"
|
|
10
12
|
require "badline/c128/bus"
|
|
11
13
|
require "badline/c128/cpu"
|
|
14
|
+
require "badline/c128/z80"
|
|
15
|
+
require "badline/c128/z80_turns"
|
|
12
16
|
require "badline/c128/saved_state"
|
|
13
17
|
require "badline/c128/keys"
|
|
14
18
|
require "badline/c128/modes"
|
|
19
|
+
require "badline/c128/drives"
|
|
20
|
+
require "badline/c128/fast_serial"
|
|
21
|
+
require "badline/drive1571"
|
|
15
22
|
|
|
16
23
|
module Badline
|
|
17
24
|
# The Commodore 128: the 8502 on the VIC-IIe's clock, two CIAs, the SID,
|
|
18
25
|
# the VDC and its 80 column display, and 128K of RAM, which the 8722 MMU
|
|
19
26
|
# maps (C128::Bus).
|
|
20
27
|
#
|
|
21
|
-
# Built for C128 mode, it
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
28
|
+
# Built for C128 mode, it powers on as the C128 does: the Z80 runs the
|
|
29
|
+
# boot code in its BIOS and hands the bus to the 8502, which then runs
|
|
30
|
+
# its reset vector into BASIC 7.0 and the C128 KERNAL. The KERNAL goes
|
|
31
|
+
# to C64 mode as on the C128, with C= held at reset, a C64 cartridge or
|
|
32
|
+
# GO64. MCR bit 0 hands the bus back and forth from then on, and the
|
|
33
|
+
# Z80 runs 2 T-states in each cycle the VIC leaves it the bus.
|
|
26
34
|
#
|
|
27
35
|
# Built for C64 mode, the default, it powers on in C64 mode, the state
|
|
28
36
|
# the C128 KERNAL reaches when C= is held at power-on, and a reset comes
|
|
@@ -40,6 +48,9 @@ module Badline
|
|
|
40
48
|
include Computer::KernalTraps
|
|
41
49
|
include Keys
|
|
42
50
|
include Modes
|
|
51
|
+
include Drives
|
|
52
|
+
include FastSerial
|
|
53
|
+
include Z80Turns
|
|
43
54
|
|
|
44
55
|
# The C64's 8x8 matrix and the three rows K0-K2 select, in port B
|
|
45
56
|
# column order.
|
|
@@ -111,6 +122,7 @@ module Badline
|
|
|
111
122
|
@holding_commodore = false
|
|
112
123
|
@bus = Bus.new(@model, sid_model: sid_model || @model.sid_model, mode:)
|
|
113
124
|
@cpu = CPU.new(@bus, debug:)
|
|
125
|
+
build_z80
|
|
114
126
|
@vic = @bus.vic
|
|
115
127
|
@vic.open_bus = -> { @bus.ram.peek(@cpu.program_counter) }
|
|
116
128
|
@cia1 = @bus.cia1
|
|
@@ -125,12 +137,8 @@ module Badline
|
|
|
125
137
|
@restore_pulse = false
|
|
126
138
|
@init_handlers = []
|
|
127
139
|
@pending_keys = nil
|
|
128
|
-
@drive = nil
|
|
129
|
-
@serial_trap = nil
|
|
130
|
-
@save_trap = nil
|
|
131
|
-
@drive1541 = nil
|
|
132
140
|
@capture_output = nil
|
|
133
|
-
|
|
141
|
+
init_drives
|
|
134
142
|
enter_c64_mode if @c64_built
|
|
135
143
|
@bus.on_mode_change { mode_changed }
|
|
136
144
|
end
|
|
@@ -140,6 +148,8 @@ module Badline
|
|
|
140
148
|
# mode the CPU runs in both halves of the cycle (#clock_fast), and the
|
|
141
149
|
# TEST bit steps the raster counter in every cycle.
|
|
142
150
|
def cycle!
|
|
151
|
+
return z80_cycle! if @z80_turn
|
|
152
|
+
|
|
143
153
|
handle_init if @cycles == @init_threshold
|
|
144
154
|
feed_keyboard if @pending_keys
|
|
145
155
|
|
|
@@ -158,7 +168,7 @@ module Badline
|
|
|
158
168
|
|
|
159
169
|
drive_nmi
|
|
160
170
|
clock_bits.odd? ? clock_fast : clock_cpu
|
|
161
|
-
|
|
171
|
+
clock_serial_bus
|
|
162
172
|
|
|
163
173
|
@cycles += 1
|
|
164
174
|
end
|
|
@@ -230,7 +240,7 @@ module Badline
|
|
|
230
240
|
end
|
|
231
241
|
|
|
232
242
|
# The RES line reaches the CPU and its port, the MMU, both CIAs, the
|
|
233
|
-
# SID, the cartridge port and, through the serial bus, the
|
|
243
|
+
# SID, the cartridge port and, through the serial bus, the drives.
|
|
234
244
|
def reset!
|
|
235
245
|
@bus.reset!
|
|
236
246
|
@cia1.reset!
|
|
@@ -239,8 +249,10 @@ module Badline
|
|
|
239
249
|
@bus.cartridge&.reset
|
|
240
250
|
@drive&.reset!
|
|
241
251
|
@drive1541&.reset!
|
|
252
|
+
@drive1571&.reset!
|
|
253
|
+
@drive1581&.reset!
|
|
242
254
|
@nmi_asserted = false
|
|
243
|
-
|
|
255
|
+
reset_z80
|
|
244
256
|
end
|
|
245
257
|
|
|
246
258
|
# CHROUT is at $FFD2 in both KERNALs, and the trap follows the mode.
|
|
@@ -303,18 +315,5 @@ module Badline
|
|
|
303
315
|
def handle_init
|
|
304
316
|
@init_handlers.each(&:call)
|
|
305
317
|
end
|
|
306
|
-
|
|
307
|
-
# The keyboard buffer of the KERNAL the machine runs: the C64's, or in
|
|
308
|
-
# C128 mode BASIC 7.0's.
|
|
309
|
-
def feed_keyboard
|
|
310
|
-
c64 = mode == :c64
|
|
311
|
-
count = c64 ? KeyboardBuffer::COUNT : C128_KEYBOARD_COUNT
|
|
312
|
-
return unless ram.peek(count).zero?
|
|
313
|
-
|
|
314
|
-
chunk = @pending_keys.shift(KeyboardBuffer::CAPACITY)
|
|
315
|
-
ram.write(c64 ? KeyboardBuffer::ADDRESS : C128_KEYBOARD_BUFFER, chunk)
|
|
316
|
-
ram.poke(count, chunk.length)
|
|
317
|
-
@pending_keys = nil if @pending_keys.empty?
|
|
318
|
-
end
|
|
319
318
|
end
|
|
320
319
|
end
|
|
@@ -65,12 +65,12 @@ module Badline
|
|
|
65
65
|
private
|
|
66
66
|
|
|
67
67
|
def save_mapper(out)
|
|
68
|
-
|
|
68
|
+
super
|
|
69
69
|
out.int(window_index(@rom, windows)).boolean(@active).boolean(@io2_ram).blob(@ram_data)
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
def load_mapper(input)
|
|
73
|
-
|
|
73
|
+
super
|
|
74
74
|
@rom = window_at(input.int, windows)
|
|
75
75
|
@active = input.boolean?
|
|
76
76
|
@io2_ram = input.boolean?
|
|
@@ -33,13 +33,7 @@ module Badline
|
|
|
33
33
|
|
|
34
34
|
private
|
|
35
35
|
|
|
36
|
-
def
|
|
37
|
-
save_windows(out, [*@roml_banks, *@romh_banks, EMPTY_BANK])
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def load_mapper(input)
|
|
41
|
-
load_windows(input, [*@roml_banks, *@romh_banks, EMPTY_BANK])
|
|
42
|
-
end
|
|
36
|
+
def windows = [*@roml_banks, *@romh_banks, EMPTY_BANK]
|
|
43
37
|
|
|
44
38
|
def select(number, mode)
|
|
45
39
|
@roml = bank(@roml_banks, number)
|
|
@@ -24,14 +24,6 @@ module Badline
|
|
|
24
24
|
|
|
25
25
|
private
|
|
26
26
|
|
|
27
|
-
def save_mapper(out)
|
|
28
|
-
save_windows(out, [*@banks, EMPTY_BANK])
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def load_mapper(input)
|
|
32
|
-
load_windows(input, [*@banks, EMPTY_BANK])
|
|
33
|
-
end
|
|
34
|
-
|
|
35
27
|
def install_chips(chips)
|
|
36
28
|
@banks = banks_from(chips).first
|
|
37
29
|
self.mode = :rom8k
|
|
@@ -49,31 +49,31 @@ module Badline
|
|
|
49
49
|
@phi1_ultimax = false
|
|
50
50
|
self.nmi = false
|
|
51
51
|
select(0)
|
|
52
|
-
|
|
53
|
-
changed!
|
|
52
|
+
select_mode(:rom16k)
|
|
54
53
|
end
|
|
55
54
|
|
|
56
55
|
def freeze!
|
|
57
56
|
@register_visible = true
|
|
58
57
|
@phi1_ultimax = false
|
|
59
|
-
|
|
60
|
-
changed!
|
|
58
|
+
select_mode(:ultimax)
|
|
61
59
|
end
|
|
62
60
|
|
|
63
61
|
private
|
|
64
62
|
|
|
65
63
|
def save_mapper(out)
|
|
66
|
-
|
|
64
|
+
super
|
|
67
65
|
out.boolean(@register_visible).boolean(@phi1_ultimax)
|
|
68
66
|
end
|
|
69
67
|
|
|
70
68
|
def load_mapper(input)
|
|
71
|
-
|
|
69
|
+
super
|
|
72
70
|
@io_bank = @roml
|
|
73
71
|
@register_visible = input.boolean?
|
|
74
72
|
@phi1_ultimax = input.boolean?
|
|
75
73
|
end
|
|
76
74
|
|
|
75
|
+
def windows = [*@roml_banks, *@romh_banks, EMPTY_BANK]
|
|
76
|
+
|
|
77
77
|
def select(number)
|
|
78
78
|
@io_bank = @roml = bank(@roml_banks, number)
|
|
79
79
|
@romh = bank(@romh_banks, number)
|
|
@@ -28,14 +28,6 @@ module Badline
|
|
|
28
28
|
|
|
29
29
|
private
|
|
30
30
|
|
|
31
|
-
def save_mapper(out)
|
|
32
|
-
save_windows(out, [*@banks, EMPTY_BANK])
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def load_mapper(input)
|
|
36
|
-
load_windows(input, [*@banks, EMPTY_BANK])
|
|
37
|
-
end
|
|
38
|
-
|
|
39
31
|
def install_chips(chips)
|
|
40
32
|
@banks = []
|
|
41
33
|
chips.each { |chip| @banks[self.class.bank_number(chip.bank)] = rom_bank(chip.data) }
|
|
@@ -25,14 +25,6 @@ module Badline
|
|
|
25
25
|
|
|
26
26
|
private
|
|
27
27
|
|
|
28
|
-
def save_mapper(out)
|
|
29
|
-
save_windows(out, [*@banks, EMPTY_BANK])
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def load_mapper(input)
|
|
33
|
-
load_windows(input, [*@banks, EMPTY_BANK])
|
|
34
|
-
end
|
|
35
|
-
|
|
36
28
|
def select(addr)
|
|
37
29
|
@roml = bank(@banks, addr & 0x3f)
|
|
38
30
|
changed!
|
|
@@ -26,7 +26,7 @@ module Badline
|
|
|
26
26
|
|
|
27
27
|
def peek(addr)
|
|
28
28
|
if addr < 0xdf00
|
|
29
|
-
|
|
29
|
+
select_mode(addr.anybits?(0x02) ? :off : :rom8k)
|
|
30
30
|
@roml.peek(IO1_ROM_PAGE | (addr & 0xff))
|
|
31
31
|
elsif addr.anybits?(0x80)
|
|
32
32
|
(@exrom << 7) | (@game << 6) | (open_bus(addr) & 0x3f)
|
|
@@ -37,7 +37,7 @@ module Badline
|
|
|
37
37
|
|
|
38
38
|
def poke(addr, value)
|
|
39
39
|
if addr < 0xdf00
|
|
40
|
-
|
|
40
|
+
select_mode(addr.anybits?(0x02) ? :ultimax : :rom16k)
|
|
41
41
|
elsif addr.nobits?(0x80)
|
|
42
42
|
@io_ram[addr & 0x7f] = value
|
|
43
43
|
end
|
|
@@ -45,19 +45,22 @@ module Badline
|
|
|
45
45
|
|
|
46
46
|
def reset
|
|
47
47
|
self.nmi = false
|
|
48
|
-
|
|
48
|
+
select_mode(:rom16k)
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
def freeze!
|
|
52
52
|
self.nmi = false
|
|
53
|
-
|
|
53
|
+
select_mode(:ultimax)
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
private
|
|
57
57
|
|
|
58
|
-
def
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
def save_mapper(out)
|
|
59
|
+
out.blob(@io_ram)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def load_mapper(input)
|
|
63
|
+
input.blob_into(@io_ram)
|
|
61
64
|
end
|
|
62
65
|
|
|
63
66
|
def install_chips(chips)
|
|
@@ -14,13 +14,11 @@ module Badline
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def poke(addr, _value)
|
|
17
|
-
|
|
18
|
-
changed!
|
|
17
|
+
select_mode(addr < 0xdf00 ? :rom8k : :off)
|
|
19
18
|
end
|
|
20
19
|
|
|
21
20
|
def reset
|
|
22
|
-
|
|
23
|
-
changed!
|
|
21
|
+
select_mode(:rom8k)
|
|
24
22
|
end
|
|
25
23
|
|
|
26
24
|
private
|
|
@@ -20,14 +20,6 @@ module Badline
|
|
|
20
20
|
|
|
21
21
|
private
|
|
22
22
|
|
|
23
|
-
def save_mapper(out)
|
|
24
|
-
save_windows(out, [*@banks, EMPTY_BANK])
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def load_mapper(input)
|
|
28
|
-
load_windows(input, [*@banks, EMPTY_BANK])
|
|
29
|
-
end
|
|
30
|
-
|
|
31
23
|
def install_chips(chips)
|
|
32
24
|
@banks = banks_from(chips).first
|
|
33
25
|
@bank_mask = (bank_mask(@banks) | 0x03) & 0x7f
|