badline 0.10.1 → 0.11.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 +23 -0
- data/CLAUDE.md +25 -6
- data/CONTRIBUTING.md +8 -3
- data/README.md +63 -4
- data/doc/pinned-behaviour.md +75 -0
- data/doc/snapshots.md +15 -0
- data/lib/badline/address_bus/pla.rb +3 -16
- data/lib/badline/address_bus.rb +14 -0
- data/lib/badline/c128/banks.rb +35 -0
- data/lib/badline/c128/bus.rb +347 -0
- data/lib/badline/c128/bus_state.rb +53 -0
- data/lib/badline/c128/color_lines.rb +39 -0
- data/lib/badline/c128/cpu.rb +10 -0
- data/lib/badline/c128/drives.rb +95 -0
- data/lib/badline/c128/fast_serial.rb +62 -0
- data/lib/badline/c128/keys.rb +35 -0
- data/lib/badline/c128/mmu.rb +197 -0
- data/lib/badline/c128/mmu_pages.rb +105 -0
- data/lib/badline/c128/model.rb +39 -0
- data/lib/badline/c128/modes.rb +48 -0
- data/lib/badline/c128/saved_state.rb +121 -0
- data/lib/badline/c128/vdc/memory.rb +54 -0
- data/lib/badline/c128/vdc/painter.rb +143 -0
- data/lib/badline/c128/vdc/raster.rb +97 -0
- data/lib/badline/c128/vdc/window.rb +215 -0
- data/lib/badline/c128/vdc.rb +268 -0
- data/lib/badline/c128.rb +322 -0
- data/lib/badline/chrout_trap.rb +3 -0
- data/lib/badline/cia/fast_forward.rb +42 -0
- data/lib/badline/cia/serial.rb +12 -0
- data/lib/badline/cia/timer.rb +39 -0
- data/lib/badline/cia.rb +2 -0
- data/lib/badline/computer/attachments.rb +10 -13
- data/lib/badline/computer/kernal_traps.rb +29 -0
- data/lib/badline/computer.rb +5 -0
- data/lib/badline/drive/core.rb +172 -0
- data/lib/badline/{drive1541 → drive}/idle.rb +18 -19
- data/lib/badline/drive/idle_cpu.rb +28 -0
- data/lib/badline/{drive1541 → drive}/orbit.rb +2 -1
- data/lib/badline/drive/serial_lines.rb +73 -0
- data/lib/badline/{drive1541 → drive}/sleep.rb +4 -3
- data/lib/badline/drive/watch.rb +150 -0
- data/lib/badline/drive.rb +9 -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 +13 -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 +34 -0
- data/lib/badline/drive1571.rb +225 -0
- data/lib/badline/frontend/app.rb +35 -22
- data/lib/badline/frontend/boot.rb +31 -1
- data/lib/badline/frontend/controls.rb +11 -0
- data/lib/badline/frontend/drive_led.rb +1 -1
- data/lib/badline/frontend/keys.rb +28 -1
- data/lib/badline/frontend/machine_window.rb +16 -13
- data/lib/badline/frontend/menu_media.rb +3 -3
- data/lib/badline/frontend/menu_pages.rb +8 -2
- data/lib/badline/frontend/menu_ports.rb +1 -1
- data/lib/badline/frontend/pause_menu.rb +1 -0
- data/lib/badline/frontend/screen.rb +33 -8
- data/lib/badline/frontend/screens.rb +90 -0
- data/lib/badline/frontend/timeline.rb +15 -4
- data/lib/badline/frontend.rb +1 -0
- data/lib/badline/iec_bus.rb +54 -4
- data/lib/badline/kernal_trap/file.rb +1 -1
- data/lib/badline/kernal_trap/layout.rb +58 -8
- data/lib/badline/kernal_trap/load.rb +1 -1
- data/lib/badline/kernal_trap/save.rb +1 -1
- data/lib/badline/kernal_trap/serial.rb +5 -0
- data/lib/badline/machine.rb +20 -9
- data/lib/badline/media/true_drive.rb +26 -23
- data/lib/badline/media.rb +8 -6
- data/lib/badline/options/event.rb +5 -1
- data/lib/badline/options/family.rb +52 -0
- data/lib/badline/options/help.rb +7 -2
- data/lib/badline/options/table.rb +8 -2
- data/lib/badline/options/validation.rb +14 -1
- data/lib/badline/options.rb +20 -19
- data/lib/badline/roms/README +36 -0
- data/lib/badline/roms/c128/basic.rom +0 -0
- data/lib/badline/roms/c128/character.rom +0 -0
- data/lib/badline/roms/c128/kernal.rom +0 -0
- data/lib/badline/roms/dos1571.rom +0 -0
- data/lib/badline/snapshot/container.rb +2 -0
- data/lib/badline/snapshot/image.rb +14 -0
- data/lib/badline/snapshot.rb +9 -2
- 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/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/vic/display_state.rb +12 -0
- data/lib/badline/vic/registers.rb +3 -0
- data/lib/badline/vic.rb +108 -7
- data/lib/badline/vic20/attachments.rb +6 -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 +111 -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/shifts.rb +64 -0
- data/lib/badline/z80.rb +11 -0
- data/lib/badline.rb +1 -0
- metadata +59 -4
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class C128
|
|
5
|
+
# The 8502's bus over the 128K of RAM, as the MMU maps it.
|
|
6
|
+
#
|
|
7
|
+
# In C128 mode CR picks what the CPU sees: BASIC at $4000-$BFFF, the
|
|
8
|
+
# screen editor, the character ROM and the KERNAL at $C000-$FFFF, an
|
|
9
|
+
# empty function ROM socket, or RAM, and I/O at $D000-$DFFF. The MMU's
|
|
10
|
+
# registers sit at $D500 and its configuration registers at
|
|
11
|
+
# $FF00-$FF04.
|
|
12
|
+
#
|
|
13
|
+
# In C64 mode the 8721 PLA decodes $01's LORAM, HIRAM and CHAREN and the
|
|
14
|
+
# cartridge's EXROM and GAME as a C64's does (AddressBus::PLA), and the
|
|
15
|
+
# MMU answers nowhere.
|
|
16
|
+
#
|
|
17
|
+
# In both modes the RAM is the bank CR picks, with common RAM in bank 0,
|
|
18
|
+
# and a page that page 0 or page 1 moves to reaches page 0 or page 1 of
|
|
19
|
+
# that bank. C128 mode also moves page 0 and page 1 there. The I/O area:
|
|
20
|
+
#
|
|
21
|
+
# $D000-$D3FF - the VIC-IIe
|
|
22
|
+
# $D400-$D4FF - the SID, with no mirrors above it
|
|
23
|
+
# $D500-$D5FF - the MMU in C128 mode, nothing in C64 mode
|
|
24
|
+
# $D600-$D6FF - the VDC
|
|
25
|
+
# $D700-$D7FF - nothing, but for VICE's debug register at $D7FF
|
|
26
|
+
# $D800-$DBFF - colour RAM
|
|
27
|
+
# $DC00-$DCFF - CIA 1
|
|
28
|
+
# $DD00-$DDFF - CIA 2
|
|
29
|
+
# $DE00-$DFFF - I/O 1 and 2, for cartridges
|
|
30
|
+
#
|
|
31
|
+
# An empty page reads the byte the VIC fetched in the preceding phi1
|
|
32
|
+
# half-cycle, as I/O 1 and 2 do on the C64.
|
|
33
|
+
#
|
|
34
|
+
# Colour RAM has two 1K banks (ColorLines).
|
|
35
|
+
class Bus
|
|
36
|
+
include Addressable
|
|
37
|
+
include AddressBus::PLA
|
|
38
|
+
include AddressBus::ROMs
|
|
39
|
+
include MMUPages
|
|
40
|
+
include SavedState
|
|
41
|
+
|
|
42
|
+
# The 8502's port has seven pins. P0-P5 are the 6510's, and P6 senses
|
|
43
|
+
# the CAPS LOCK key, high while it is up. Bit 7 has no pin, and holds
|
|
44
|
+
# the charge last driven onto it, as the 6510's floating bits do.
|
|
45
|
+
PORT_PULLUPS = 0b0001_0111
|
|
46
|
+
PORT_FLOATING = 0b1000_1000
|
|
47
|
+
CAPS_LOCK = 0b0100_0000
|
|
48
|
+
TAPE_SENSE = 0b0001_0000
|
|
49
|
+
|
|
50
|
+
# The C64's power-on pattern (AddressBus::RAM_POWER_ON) in both 64K
|
|
51
|
+
# banks. The C128's own DRAMs' pattern is unmeasured.
|
|
52
|
+
RAM_POWER_ON = Array.new(2**17) { |addr| AddressBus::RAM_POWER_ON[addr & 0xffff] }.freeze
|
|
53
|
+
|
|
54
|
+
# The VIC's write side: a write to $D02F also drives the extra
|
|
55
|
+
# keyboard lines K0-K2 onto the keyboard's rows 8-10.
|
|
56
|
+
class VICWrites
|
|
57
|
+
def initialize(vic, control_ports)
|
|
58
|
+
@vic = vic
|
|
59
|
+
@control_ports = control_ports
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def peek(addr) = @vic.peek(addr)
|
|
63
|
+
|
|
64
|
+
def poke(addr, value)
|
|
65
|
+
@vic.poke(addr, value)
|
|
66
|
+
@control_ports.extra_rows = 0xf8 | @vic.extra_keyboard_lines if (addr & 0x3f) == 0x2f
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# The $D7xx page with VICE's debug register at $D7FF: a write there
|
|
71
|
+
# hands the handler its byte, and the rest of the page is empty.
|
|
72
|
+
class DebugPage
|
|
73
|
+
ADDRESS = 0xd7ff
|
|
74
|
+
|
|
75
|
+
def initialize(open_bus, &handler)
|
|
76
|
+
@open_bus = open_bus
|
|
77
|
+
@handler = handler
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def peek(addr) = @open_bus.peek(addr)
|
|
81
|
+
|
|
82
|
+
def poke(addr, value)
|
|
83
|
+
@handler.call(value) if addr == ADDRESS
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# RAM seen somewhere else: the address's bits in +mask+ from +base+
|
|
88
|
+
# on. Bank 1 is the whole 64K from $10000, and a relocated page the
|
|
89
|
+
# low byte from the page it moved to.
|
|
90
|
+
class RAMWindow
|
|
91
|
+
attr_accessor :base
|
|
92
|
+
|
|
93
|
+
def initialize(ram, base, mask)
|
|
94
|
+
@ram = ram
|
|
95
|
+
@base = base
|
|
96
|
+
@mask = mask
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def peek(addr) = @ram.peek(@base | (addr & @mask))
|
|
100
|
+
def poke(addr, value) = @ram.poke(@base | (addr & @mask), value)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# The page at $FF00 in C128 mode: CR and the LCRs at $FF00-$FF04, and
|
|
104
|
+
# whatever CR maps there above them.
|
|
105
|
+
class ConfigurationPage
|
|
106
|
+
attr_accessor :below
|
|
107
|
+
|
|
108
|
+
def initialize(mmu, below)
|
|
109
|
+
@mmu = mmu
|
|
110
|
+
@below = below
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def peek(addr) = addr < 0xff05 ? @mmu.peek_configuration(addr) : @below.peek(addr)
|
|
114
|
+
|
|
115
|
+
def poke(addr, value)
|
|
116
|
+
addr < 0xff05 ? @mmu.poke_configuration(addr, value) : @below.poke(addr, value)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
attr_reader :io_port, :ram, :mmu, :vic, :sid, :vdc, :color_ram, :cia1, :cia2, :keyboard, :joystick1,
|
|
121
|
+
:joystick2, :control_ports, :cartridge, :ultimax, :phi1_ultimax, :datasette, :region
|
|
122
|
+
|
|
123
|
+
# The C128 mode ROMs: BASIC's two halves, the screen editor, the
|
|
124
|
+
# KERNAL and the character ROM's C128 set.
|
|
125
|
+
attr_reader :basic_low_rom, :basic_high_rom, :editor_rom, :c128_kernal_rom, :c128_character_rom
|
|
126
|
+
|
|
127
|
+
# Whether CAPS LOCK is down, holding P6 low.
|
|
128
|
+
attr_reader :caps_lock
|
|
129
|
+
|
|
130
|
+
# The chips +model+, a Model::Profile, names, with the SID
|
|
131
|
+
# +sid_model+. +mode+ is the mode the MMU resets into.
|
|
132
|
+
def initialize(model, sid_model: model.sid_model, mode: :c64)
|
|
133
|
+
@region = model.region
|
|
134
|
+
@ram = Memory.new(RAM_POWER_ON, length: 2**17, start: 0)
|
|
135
|
+
@bank1 = RAMWindow.new(@ram, 0x10000, 0xffff)
|
|
136
|
+
@relocated = Array.new(4) { RAMWindow.new(@ram, 0, 0xff) }
|
|
137
|
+
@mmu = MMU.new(mode)
|
|
138
|
+
@cartridge = nil
|
|
139
|
+
@debug_page = nil
|
|
140
|
+
@caps_lock = false
|
|
141
|
+
|
|
142
|
+
load_roms
|
|
143
|
+
load_c128_roms
|
|
144
|
+
@banks = Banks.new(@ram, @mmu, @c128_kernal_rom)
|
|
145
|
+
@c64_mode = @mmu.c64_mode?
|
|
146
|
+
@on_mode_change = nil
|
|
147
|
+
plug_chips(model, sid_model)
|
|
148
|
+
|
|
149
|
+
@datasette = Datasette.new
|
|
150
|
+
@datasette.on_flag { @cia1.flag! }
|
|
151
|
+
@datasette.on_sense_change { @io_port.value = port_value }
|
|
152
|
+
|
|
153
|
+
@color_lines = ColorLines.new(@vic, @cia2, character_rom, @c128_character_rom)
|
|
154
|
+
@color_ram = @color_lines.color_ram(1)
|
|
155
|
+
@vic_writes = VICWrites.new(@vic, @control_ports)
|
|
156
|
+
@open_bus = AddressBus::OpenBus.new(@vic)
|
|
157
|
+
@configuration_reads = ConfigurationPage.new(@mmu, @open_bus)
|
|
158
|
+
@configuration_writes = ConfigurationPage.new(@mmu, @open_bus)
|
|
159
|
+
|
|
160
|
+
@port_ddr = 0x00
|
|
161
|
+
@port_out = 0x00
|
|
162
|
+
@port_floating = 0x00
|
|
163
|
+
@io_port = PortStatus.new(%i[basic kernal io tape_out tape_switch tape_motor], value: port_value)
|
|
164
|
+
|
|
165
|
+
@read_pages = Array.new(256)
|
|
166
|
+
@write_pages = Array.new(256)
|
|
167
|
+
@address = 0
|
|
168
|
+
@data = 0
|
|
169
|
+
@io_mapped = false
|
|
170
|
+
@mmu.on_change { update_overlays! }
|
|
171
|
+
update_overlays!
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def attach_cartridge(cartridge)
|
|
175
|
+
@cartridge = cartridge
|
|
176
|
+
cartridge.connect(ram: @ram, open_bus: @open_bus)
|
|
177
|
+
cartridge.on_change { update_overlays! }
|
|
178
|
+
update_overlays!
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Takes the cartridge out of the expansion port.
|
|
182
|
+
def detach_cartridge
|
|
183
|
+
@cartridge = nil
|
|
184
|
+
update_overlays!
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def power_on!
|
|
188
|
+
@ram.clear!(RAM_POWER_ON)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# The RES line clears the port's direction and output registers and
|
|
192
|
+
# resets the MMU. The port's floating bit keeps its charge.
|
|
193
|
+
def reset!
|
|
194
|
+
@port_ddr = 0x00
|
|
195
|
+
@port_out = 0x00
|
|
196
|
+
@mmu.reset!
|
|
197
|
+
update_port!
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Holds CAPS LOCK down, or lets it up.
|
|
201
|
+
def caps_lock=(down)
|
|
202
|
+
@caps_lock = down
|
|
203
|
+
@io_port.value = port_value
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Calls the block whenever MCR bit 6 takes the machine into C64 mode or
|
|
207
|
+
# a reset brings it back to C128 mode.
|
|
208
|
+
def on_mode_change(&block)
|
|
209
|
+
@on_mode_change = block
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Whether the CPU sees the C128 KERNAL, in C128 mode with CR's system
|
|
213
|
+
# ROM at $C000-$FFFF.
|
|
214
|
+
def system_rom_mapped? = !@mmu.c64_mode? && @mmu.cr.nobits?(0x30)
|
|
215
|
+
|
|
216
|
+
# The RAM the C128 KERNAL's bank number +bank+ reaches (Banks).
|
|
217
|
+
def peek_bank(bank, addr) = @banks.peek(bank, addr)
|
|
218
|
+
|
|
219
|
+
def write_bank(bank, addr, bytes) = @banks.write(bank, addr, bytes)
|
|
220
|
+
|
|
221
|
+
def install_debug_register(&)
|
|
222
|
+
@debug_page = DebugPage.new(@open_bus, &)
|
|
223
|
+
update_overlays!
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# The address and the byte of the CPU's last access, which a VIC-IIe
|
|
227
|
+
# in FAST mode fetches in place of its own, and which tells the
|
|
228
|
+
# machine when an access reached I/O.
|
|
229
|
+
attr_reader :address, :data
|
|
230
|
+
|
|
231
|
+
def peek(addr)
|
|
232
|
+
@address = addr
|
|
233
|
+
@data = if addr > 0x01
|
|
234
|
+
@read_pages[addr >> 8].peek(addr)
|
|
235
|
+
else
|
|
236
|
+
addr.zero? ? @port_ddr : @io_port.value
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Whether the last access went to a chip at $D000-$DFFF, which runs
|
|
241
|
+
# at 1 MHz.
|
|
242
|
+
def io_access? = @io_mapped && (@address & 0xf000) == 0xd000
|
|
243
|
+
|
|
244
|
+
# Whether the last access went to the VIC's registers.
|
|
245
|
+
def vic_access? = @io_mapped && (@address & 0xfc00) == 0xd000
|
|
246
|
+
|
|
247
|
+
# A write to $00 or $01 goes to the port, and the RAM below takes the
|
|
248
|
+
# byte the VIC fetched in the phi1 half of the cycle.
|
|
249
|
+
def poke(addr, value)
|
|
250
|
+
@address = addr
|
|
251
|
+
@data = value
|
|
252
|
+
if addr < 0x02
|
|
253
|
+
@write_pages[0].poke(addr, @vic.phi1_data)
|
|
254
|
+
addr.zero? ? @port_ddr = value : @port_out = value
|
|
255
|
+
update_port!
|
|
256
|
+
else
|
|
257
|
+
@write_pages[addr >> 8].poke(addr, value)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def inspect
|
|
262
|
+
"#<#{self.class.name} mode=#{@mmu.mode} port=#{format('0x%02x', @io_port.value)} " \
|
|
263
|
+
"cartridge=#{@cartridge ? @cartridge.class.name : 'none'} " \
|
|
264
|
+
"ultimax=#{@ultimax}>"
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
private
|
|
268
|
+
|
|
269
|
+
def load_c128_roms
|
|
270
|
+
basic = ROM.read("c128/basic.rom")
|
|
271
|
+
kernal = ROM.read("c128/kernal.rom")
|
|
272
|
+
@basic_low_rom = ROM.new(basic[0, 0x4000], length: 0x4000, start: 0x4000)
|
|
273
|
+
@basic_high_rom = ROM.new(basic[0x4000, 0x4000], length: 0x4000, start: 0x8000)
|
|
274
|
+
@editor_rom = ROM.new(kernal[0, 0x1000], length: 0x1000, start: 0xc000)
|
|
275
|
+
@c128_kernal_rom = ROM.new(kernal[0x2000, 0x2000], length: 0x2000, start: 0xe000)
|
|
276
|
+
characters = ROM.read("c128/character.rom")
|
|
277
|
+
@c128_character_rom = ROM.new(characters[0x1000, 0x1000], length: 0x1000, start: 0xd000)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def plug_chips(model, sid_model)
|
|
281
|
+
@keyboard = Keyboard.new(matrix: KEYBOARD_MATRIX)
|
|
282
|
+
@joystick1 = Joystick.new
|
|
283
|
+
@joystick2 = Joystick.new
|
|
284
|
+
@control_ports = ControlPorts.new(keyboard: @keyboard, joystick1: @joystick1, joystick2: @joystick2)
|
|
285
|
+
@vic = VIC.new(model: model.vic_model, region: @region)
|
|
286
|
+
@cia1 = CIA.new(start: 0xdc00, peripheral: @control_ports, model: model.cia_model, region: @region)
|
|
287
|
+
@cia2 = CIA.new(start: 0xdd00, model: model.cia_model, region: @region)
|
|
288
|
+
@control_ports.port_a_source = @cia1
|
|
289
|
+
@cia1.on_port_b4_change { |high| @vic.lightpen_level(high) }
|
|
290
|
+
@sid = SID.new(model: sid_model, pots: @control_ports)
|
|
291
|
+
@vdc = VDC.new(model: model.vdc_model, ram_kb: model.vdc_ram_kb, clock_hz: @region.clock_hz)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def update_port!
|
|
295
|
+
driven = @port_ddr & PORT_FLOATING
|
|
296
|
+
@port_floating = (@port_floating & ~driven) | (@port_out & driven)
|
|
297
|
+
@io_port.value = port_value
|
|
298
|
+
@datasette.motor = !@io_port.tape_motor?
|
|
299
|
+
update_overlays!
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def port_value
|
|
303
|
+
input = PORT_PULLUPS | (@port_floating & PORT_FLOATING)
|
|
304
|
+
input |= CAPS_LOCK unless @caps_lock
|
|
305
|
+
input &= ~TAPE_SENSE if @datasette.sense_low?
|
|
306
|
+
(@port_out & @port_ddr) | (input & ~@port_ddr & 0xff)
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def update_overlays!
|
|
310
|
+
@ultimax = @cartridge ? @cartridge.ultimax? : false
|
|
311
|
+
@phi1_ultimax = @cartridge ? @cartridge.phi1_ultimax? : false
|
|
312
|
+
@mmu.game = @cartridge ? @cartridge.game : 1
|
|
313
|
+
@mmu.exrom = @cartridge ? @cartridge.exrom : 1
|
|
314
|
+
c64_mode = @mmu.c64_mode?
|
|
315
|
+
port = @io_port.value
|
|
316
|
+
@color_lines.push(c64_mode, port)
|
|
317
|
+
@vic.vic_bank.map(@ram, base: @mmu.vic_bank << 16, phi1_ultimax: @phi1_ultimax, ultimax: @ultimax,
|
|
318
|
+
romh: @cartridge&.romh)
|
|
319
|
+
@color_ram = @color_lines.cpu_color_ram(c64_mode, port)
|
|
320
|
+
c64_mode ? map_pla_pages : map_c128_pages
|
|
321
|
+
mode_changed if c64_mode != @c64_mode
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def mode_changed
|
|
325
|
+
@c64_mode = !@c64_mode
|
|
326
|
+
@on_mode_change&.call
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def map_io_pages
|
|
330
|
+
@io_mapped = true
|
|
331
|
+
@read_pages.fill(@vic, 0xd0, 4)
|
|
332
|
+
@write_pages.fill(@vic_writes, 0xd0, 4)
|
|
333
|
+
@read_pages[0xd4] = @write_pages[0xd4] = @sid
|
|
334
|
+
@read_pages[0xd5] = @write_pages[0xd5] = @mmu.c64_mode? ? @open_bus : @mmu
|
|
335
|
+
@read_pages[0xd6] = @write_pages[0xd6] = @vdc
|
|
336
|
+
@read_pages[0xd7] = @write_pages[0xd7] = @debug_page || @open_bus
|
|
337
|
+
@read_pages.fill(@color_ram, 0xd8, 4)
|
|
338
|
+
@write_pages.fill(@color_ram, 0xd8, 4)
|
|
339
|
+
@read_pages[0xdc] = @write_pages[0xdc] = @cia1
|
|
340
|
+
@read_pages[0xdd] = @write_pages[0xdd] = @cia2
|
|
341
|
+
@read_pages.fill(@open_bus, 0xde, 2)
|
|
342
|
+
@write_pages.fill(@open_bus, 0xde, 2)
|
|
343
|
+
map_cartridge_io if @cartridge
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class C128
|
|
5
|
+
class Bus
|
|
6
|
+
# The bus's state for a snapshot, as AddressBus::SavedState is the
|
|
7
|
+
# C64's.
|
|
8
|
+
module SavedState
|
|
9
|
+
# The machine's memory, the last access and everything on the bus but
|
|
10
|
+
# the keyboard, the joysticks, the pot devices and CAPS LOCK, which the
|
|
11
|
+
# host holds. A cartridge is already in the port, built from its setup.
|
|
12
|
+
def save_state(out)
|
|
13
|
+
out.marker("C128 BUS")
|
|
14
|
+
out.int(@port_ddr).int(@port_out).int(@port_floating).int(@address).int(@data)
|
|
15
|
+
@mmu.save_state(out)
|
|
16
|
+
@ram.save_state(out)
|
|
17
|
+
@color_lines.color_ram(0).save_state(out)
|
|
18
|
+
@color_lines.color_ram(1).save_state(out)
|
|
19
|
+
@cartridge&.save_state(out)
|
|
20
|
+
@datasette.save_state(out)
|
|
21
|
+
@vic.save_state(out)
|
|
22
|
+
@cia1.save_state(out)
|
|
23
|
+
@cia2.save_state(out)
|
|
24
|
+
@sid.save_state(out)
|
|
25
|
+
@vdc.save_state(out)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def load_state(input)
|
|
29
|
+
input.marker("C128 BUS")
|
|
30
|
+
@port_ddr = input.int
|
|
31
|
+
@port_out = input.int
|
|
32
|
+
@port_floating = input.int
|
|
33
|
+
@address = input.int
|
|
34
|
+
@data = input.int
|
|
35
|
+
@mmu.load_state(input)
|
|
36
|
+
@ram.load_state(input)
|
|
37
|
+
@color_lines.color_ram(0).load_state(input)
|
|
38
|
+
@color_lines.color_ram(1).load_state(input)
|
|
39
|
+
@cartridge&.load_state(input)
|
|
40
|
+
@datasette.load_state(input)
|
|
41
|
+
@io_port.value = port_value
|
|
42
|
+
update_overlays!
|
|
43
|
+
@vic.load_state(input)
|
|
44
|
+
@control_ports.extra_rows = 0xf8 | @vic.extra_keyboard_lines
|
|
45
|
+
@cia1.load_state(input)
|
|
46
|
+
@cia2.load_state(input)
|
|
47
|
+
@sid.load_state(input)
|
|
48
|
+
@vdc.load_state(input)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class C128
|
|
5
|
+
# The C128's two 1K colour RAM banks, and the colour RAM and character
|
|
6
|
+
# ROM it pushes into the VIC's bank. In C128 mode the 8502's port picks
|
|
7
|
+
# them: P0 the colour RAM bank the CPU sees, P1 the one the VIC sees,
|
|
8
|
+
# and P2 low shows the VIC the character ROM's C128 set. In C64 mode
|
|
9
|
+
# both see colour RAM bank 1, and the VIC the C64 set.
|
|
10
|
+
class ColorLines
|
|
11
|
+
def initialize(vic, cia2, c64_characters, c128_characters)
|
|
12
|
+
@vic_bank = vic.vic_bank
|
|
13
|
+
@cia2 = cia2
|
|
14
|
+
@c64_characters = c64_characters
|
|
15
|
+
@c128_characters = c128_characters
|
|
16
|
+
@bank0 = ColorMemory.new(vic)
|
|
17
|
+
@bank1 = ColorMemory.new(vic)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Colour RAM bank +number+, 0 or 1.
|
|
21
|
+
def color_ram(number) = number.zero? ? @bank0 : @bank1
|
|
22
|
+
|
|
23
|
+
# The colour RAM bank the CPU sees.
|
|
24
|
+
def cpu_color_ram(c64_mode, port) = c64_mode ? @bank1 : color_ram(port & 0x01)
|
|
25
|
+
|
|
26
|
+
# Pushes the colour RAM and the character ROM the VIC sees into its
|
|
27
|
+
# bank.
|
|
28
|
+
def push(c64_mode, port)
|
|
29
|
+
if c64_mode
|
|
30
|
+
@vic_bank.connect(cia2: @cia2, color_ram: @bank1)
|
|
31
|
+
@vic_bank.map_character_rom(@c64_characters)
|
|
32
|
+
else
|
|
33
|
+
@vic_bank.connect(cia2: @cia2, color_ram: color_ram((port >> 1) & 0x01))
|
|
34
|
+
@vic_bank.map_character_rom(@c128_characters, shadow: port.nobits?(0x04))
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class C128
|
|
5
|
+
# The true drives on the C128's serial bus: a Drive1541, or the
|
|
6
|
+
# C128D's built-in 1571 (Drive1571), each in a field of its own, so
|
|
7
|
+
# neither makes the other's per-cycle call a union.
|
|
8
|
+
module Drives
|
|
9
|
+
attr_reader :drive1571
|
|
10
|
+
|
|
11
|
+
# Plugs in a Drive1571, which then runs alongside the C128 on its own
|
|
12
|
+
# clock and talks to it over the serial bus, as attach_drive1541 does
|
|
13
|
+
# for a 1541.
|
|
14
|
+
def attach_drive1571(drive)
|
|
15
|
+
@iec_bus.detach(@drive1571) if @drive1571
|
|
16
|
+
drive.host_clock_hz = region.clock_hz
|
|
17
|
+
@drive1571 = drive
|
|
18
|
+
drive.connect(iec_bus)
|
|
19
|
+
@serial_trap&.device = serial_trap_device
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Unplugs the Drive1571.
|
|
23
|
+
def detach_drive1571
|
|
24
|
+
return unless @drive1571
|
|
25
|
+
|
|
26
|
+
@iec_bus.detach(@drive1571)
|
|
27
|
+
@drive1571 = nil
|
|
28
|
+
@serial_trap&.device = serial_trap_device
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# The true drive on the bus, the 1571 ahead of a 1541.
|
|
32
|
+
def true_drive = @drive1571 || @drive1541
|
|
33
|
+
|
|
34
|
+
# Plugs in the C128D's 1571 as device 8, and returns it.
|
|
35
|
+
def plug_true_drive = Drive1571.new.tap { |drive| attach_drive1571(drive) }
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
# The serial bus, with no disk in device 8's traps and no true drive.
|
|
40
|
+
def init_drives
|
|
41
|
+
@drive = nil
|
|
42
|
+
@serial_trap = nil
|
|
43
|
+
@save_trap = nil
|
|
44
|
+
@drive1541 = nil
|
|
45
|
+
@drive1571 = nil
|
|
46
|
+
plug_serial_bus
|
|
47
|
+
plug_fast_serial
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Device 8, unless a true drive is on the bus as device 8.
|
|
51
|
+
def serial_trap_device
|
|
52
|
+
device = KernalTrap::Routine::DEVICE
|
|
53
|
+
@drive1571&.device == device || @drive1541&.device == device ? nil : device
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# A true 1541 (Computer::Attachments#save_serial_bus), then a 1571.
|
|
57
|
+
def save_serial_bus(out)
|
|
58
|
+
out.boolean(true).boolean(!@drive1541.nil?)
|
|
59
|
+
if @drive1541
|
|
60
|
+
out.int(@drive1541.device)
|
|
61
|
+
@drive1541.save_state(out)
|
|
62
|
+
end
|
|
63
|
+
out.boolean(!@drive1571.nil?)
|
|
64
|
+
return unless @drive1571
|
|
65
|
+
|
|
66
|
+
out.int(@drive1571.device)
|
|
67
|
+
@drive1571.save_state(out)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def load_serial_bus(input)
|
|
71
|
+
input.boolean?
|
|
72
|
+
load_drive1541(input)
|
|
73
|
+
load_drive1571(input)
|
|
74
|
+
end
|
|
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)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def load_drive1571(input)
|
|
86
|
+
return detach_drive1571 unless input.boolean?
|
|
87
|
+
|
|
88
|
+
device = input.int
|
|
89
|
+
detach_drive1571 if @drive1571 && @drive1571.device != device
|
|
90
|
+
attach_drive1571(Drive1571.new(device:)) unless @drive1571
|
|
91
|
+
@drive1571.load_state(input)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class C128
|
|
5
|
+
# Fast serial: CIA 1's CNT and SP on the serial bus's SRQ and DATA,
|
|
6
|
+
# through buffers MCR's FSDIR turns round. Outwards, the machine pushes
|
|
7
|
+
# the lines the pins pull into the bus as they move. Inwards, CIA 1
|
|
8
|
+
# hears the lines whenever a drive moves its own fast serial pins
|
|
9
|
+
# (IECBus#on_fast_change).
|
|
10
|
+
module FastSerial
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def plug_fast_serial
|
|
14
|
+
@fast_serial_out = false
|
|
15
|
+
@bus.mmu.on_fast_serial_change { turn_fast_serial }
|
|
16
|
+
@iec_bus.on_fast_change { hear_fast_serial unless @fast_serial_out }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# The machine's side of the serial bus each cycle: CIA 1's pins
|
|
20
|
+
# pushed while FSDIR has them out, then the drives.
|
|
21
|
+
def clock_serial_bus
|
|
22
|
+
drive_fast_serial if @fast_serial_out
|
|
23
|
+
@drive1541&.host_cycle!
|
|
24
|
+
@drive1571&.host_cycle!
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def turn_fast_serial
|
|
28
|
+
push_fast_serial
|
|
29
|
+
hear_fast_serial unless @fast_serial_out
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Pushes the lines CIA 1's pins pull while FSDIR has them out. A
|
|
33
|
+
# restored machine pushes them again, its CIA 1 hearing the lines as
|
|
34
|
+
# it last did.
|
|
35
|
+
def push_fast_serial
|
|
36
|
+
@fast_serial_out = @bus.mmu.fast_serial_out?
|
|
37
|
+
@iec_bus.host_fast_lines = @fast_serial_out ? fast_pins : 0
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def drive_fast_serial
|
|
41
|
+
@iec_bus.host_fast_lines = fast_pins
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# The lines CIA 1's CNT and SP pull while its serial port drives
|
|
45
|
+
# them.
|
|
46
|
+
def fast_pins
|
|
47
|
+
serial = @cia1.serial
|
|
48
|
+
return 0 unless serial.output?
|
|
49
|
+
|
|
50
|
+
low = serial.cnt ? 0 : IECBus::SRQ
|
|
51
|
+
serial.sp_out ? low : low | IECBus::DATA
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def hear_fast_serial
|
|
55
|
+
low = @iec_bus.low_lines
|
|
56
|
+
serial = @cia1.serial
|
|
57
|
+
serial.cnt_in = low.nobits?(IECBus::SRQ)
|
|
58
|
+
serial.sp_in = low.nobits?(IECBus::DATA)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class C128
|
|
5
|
+
# The three keys outside the keyboard matrix: RESTORE, CAPS LOCK and
|
|
6
|
+
# 40/80 DISPLAY.
|
|
7
|
+
module Keys
|
|
8
|
+
# RESTORE pulses NMI for a cycle, as on the C64.
|
|
9
|
+
def press_restore
|
|
10
|
+
@restore_pulse = true
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def release_restore; end
|
|
14
|
+
|
|
15
|
+
# CAPS LOCK locks down and up, and holds the 8502's P6 low while down.
|
|
16
|
+
def press_caps_lock
|
|
17
|
+
@bus.caps_lock = true
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def release_caps_lock
|
|
21
|
+
@bus.caps_lock = false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# The 40/80 DISPLAY key locks down and up. The KERNAL reads it at reset
|
|
25
|
+
# and starts the screen editor on the VDC's 80 columns while it is down.
|
|
26
|
+
def press_display_key
|
|
27
|
+
@bus.mmu.display_key = true
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def release_display_key
|
|
31
|
+
@bus.mmu.display_key = false
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|