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.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -0
  3. data/CLAUDE.md +25 -6
  4. data/CONTRIBUTING.md +8 -3
  5. data/README.md +63 -4
  6. data/doc/pinned-behaviour.md +75 -0
  7. data/doc/snapshots.md +15 -0
  8. data/lib/badline/address_bus/pla.rb +3 -16
  9. data/lib/badline/address_bus.rb +14 -0
  10. data/lib/badline/c128/banks.rb +35 -0
  11. data/lib/badline/c128/bus.rb +347 -0
  12. data/lib/badline/c128/bus_state.rb +53 -0
  13. data/lib/badline/c128/color_lines.rb +39 -0
  14. data/lib/badline/c128/cpu.rb +10 -0
  15. data/lib/badline/c128/drives.rb +95 -0
  16. data/lib/badline/c128/fast_serial.rb +62 -0
  17. data/lib/badline/c128/keys.rb +35 -0
  18. data/lib/badline/c128/mmu.rb +197 -0
  19. data/lib/badline/c128/mmu_pages.rb +105 -0
  20. data/lib/badline/c128/model.rb +39 -0
  21. data/lib/badline/c128/modes.rb +48 -0
  22. data/lib/badline/c128/saved_state.rb +121 -0
  23. data/lib/badline/c128/vdc/memory.rb +54 -0
  24. data/lib/badline/c128/vdc/painter.rb +143 -0
  25. data/lib/badline/c128/vdc/raster.rb +97 -0
  26. data/lib/badline/c128/vdc/window.rb +215 -0
  27. data/lib/badline/c128/vdc.rb +268 -0
  28. data/lib/badline/c128.rb +322 -0
  29. data/lib/badline/chrout_trap.rb +3 -0
  30. data/lib/badline/cia/fast_forward.rb +42 -0
  31. data/lib/badline/cia/serial.rb +12 -0
  32. data/lib/badline/cia/timer.rb +39 -0
  33. data/lib/badline/cia.rb +2 -0
  34. data/lib/badline/computer/attachments.rb +10 -13
  35. data/lib/badline/computer/kernal_traps.rb +29 -0
  36. data/lib/badline/computer.rb +5 -0
  37. data/lib/badline/drive/core.rb +172 -0
  38. data/lib/badline/{drive1541 → drive}/idle.rb +18 -19
  39. data/lib/badline/drive/idle_cpu.rb +28 -0
  40. data/lib/badline/{drive1541 → drive}/orbit.rb +2 -1
  41. data/lib/badline/drive/serial_lines.rb +73 -0
  42. data/lib/badline/{drive1541 → drive}/sleep.rb +4 -3
  43. data/lib/badline/drive/watch.rb +150 -0
  44. data/lib/badline/drive.rb +9 -0
  45. data/lib/badline/drive1541/bus.rb +5 -129
  46. data/lib/badline/drive1541/byte_ready.rb +48 -0
  47. data/lib/badline/drive1541/cpu.rb +1 -19
  48. data/lib/badline/drive1541/disk.rb +77 -20
  49. data/lib/badline/drive1541/disk_state.rb +7 -2
  50. data/lib/badline/drive1541/disk_via.rb +3 -0
  51. data/lib/badline/drive1541/mechanism.rb +48 -14
  52. data/lib/badline/drive1541/mechanism_state.rb +5 -4
  53. data/lib/badline/drive1541/rotation.rb +5 -5
  54. data/lib/badline/drive1541/serial_port.rb +4 -60
  55. data/lib/badline/drive1541.rb +13 -172
  56. data/lib/badline/drive1571/bus.rb +111 -0
  57. data/lib/badline/drive1571/cpu.rb +11 -0
  58. data/lib/badline/drive1571/serial_port.rb +37 -0
  59. data/lib/badline/drive1571/serial_via.rb +25 -0
  60. data/lib/badline/drive1571/wd1770.rb +34 -0
  61. data/lib/badline/drive1571.rb +225 -0
  62. data/lib/badline/frontend/app.rb +35 -22
  63. data/lib/badline/frontend/boot.rb +31 -1
  64. data/lib/badline/frontend/controls.rb +11 -0
  65. data/lib/badline/frontend/drive_led.rb +1 -1
  66. data/lib/badline/frontend/keys.rb +28 -1
  67. data/lib/badline/frontend/machine_window.rb +16 -13
  68. data/lib/badline/frontend/menu_media.rb +3 -3
  69. data/lib/badline/frontend/menu_pages.rb +8 -2
  70. data/lib/badline/frontend/menu_ports.rb +1 -1
  71. data/lib/badline/frontend/pause_menu.rb +1 -0
  72. data/lib/badline/frontend/screen.rb +33 -8
  73. data/lib/badline/frontend/screens.rb +90 -0
  74. data/lib/badline/frontend/timeline.rb +15 -4
  75. data/lib/badline/frontend.rb +1 -0
  76. data/lib/badline/iec_bus.rb +54 -4
  77. data/lib/badline/kernal_trap/file.rb +1 -1
  78. data/lib/badline/kernal_trap/layout.rb +58 -8
  79. data/lib/badline/kernal_trap/load.rb +1 -1
  80. data/lib/badline/kernal_trap/save.rb +1 -1
  81. data/lib/badline/kernal_trap/serial.rb +5 -0
  82. data/lib/badline/machine.rb +20 -9
  83. data/lib/badline/media/true_drive.rb +26 -23
  84. data/lib/badline/media.rb +8 -6
  85. data/lib/badline/options/event.rb +5 -1
  86. data/lib/badline/options/family.rb +52 -0
  87. data/lib/badline/options/help.rb +7 -2
  88. data/lib/badline/options/table.rb +8 -2
  89. data/lib/badline/options/validation.rb +14 -1
  90. data/lib/badline/options.rb +20 -19
  91. data/lib/badline/roms/README +36 -0
  92. data/lib/badline/roms/c128/basic.rom +0 -0
  93. data/lib/badline/roms/c128/character.rom +0 -0
  94. data/lib/badline/roms/c128/kernal.rom +0 -0
  95. data/lib/badline/roms/dos1571.rom +0 -0
  96. data/lib/badline/snapshot/container.rb +2 -0
  97. data/lib/badline/snapshot/image.rb +14 -0
  98. data/lib/badline/snapshot.rb +9 -2
  99. data/lib/badline/storage/d64_image.rb +3 -0
  100. data/lib/badline/storage/d71_image.rb +9 -4
  101. data/lib/badline/storage/g64_image.rb +7 -2
  102. data/lib/badline/time_of_day.rb +9 -0
  103. data/lib/badline/version.rb +1 -1
  104. data/lib/badline/via/fast_forward.rb +3 -3
  105. data/lib/badline/vic/display_state.rb +12 -0
  106. data/lib/badline/vic/registers.rb +3 -0
  107. data/lib/badline/vic.rb +108 -7
  108. data/lib/badline/vic20/attachments.rb +6 -0
  109. data/lib/badline/z80/arithmetic.rb +117 -0
  110. data/lib/badline/z80/bit_page.rb +55 -0
  111. data/lib/badline/z80/block_io.rb +58 -0
  112. data/lib/badline/z80/block_transfers.rb +59 -0
  113. data/lib/badline/z80/bus_cycles.rb +111 -0
  114. data/lib/badline/z80/core.rb +111 -0
  115. data/lib/badline/z80/exchanges.rb +50 -0
  116. data/lib/badline/z80/extended_page.rb +89 -0
  117. data/lib/badline/z80/flags.rb +37 -0
  118. data/lib/badline/z80/interrupt_acceptance.rb +46 -0
  119. data/lib/badline/z80/jumps.rb +82 -0
  120. data/lib/badline/z80/loads.rb +98 -0
  121. data/lib/badline/z80/main_page.rb +134 -0
  122. data/lib/badline/z80/registers.rb +120 -0
  123. data/lib/badline/z80/shifts.rb +64 -0
  124. data/lib/badline/z80.rb +11 -0
  125. data/lib/badline.rb +1 -0
  126. 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,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class C128
5
+ # The C128's 8502, on the C128::Bus, which holds its I/O port.
6
+ class CPU
7
+ include Badline::CPU::Core
8
+ end
9
+ end
10
+ 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