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.
Files changed (166) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/CLAUDE.md +24 -6
  4. data/CONTRIBUTING.md +15 -9
  5. data/README.md +16 -5
  6. data/doc/media.md +28 -1
  7. data/doc/pinned-behaviour.md +31 -1
  8. data/exe/badline-ruby +1 -1
  9. data/lib/badline/c128/bus.rb +4 -17
  10. data/lib/badline/c128/drives.rb +108 -0
  11. data/lib/badline/c128/fast_serial.rb +63 -0
  12. data/lib/badline/c128/io_pages.rb +35 -0
  13. data/lib/badline/c128/mmu.rb +24 -7
  14. data/lib/badline/c128/modes.rb +17 -4
  15. data/lib/badline/c128/saved_state.rb +23 -0
  16. data/lib/badline/c128/z80.rb +49 -0
  17. data/lib/badline/c128/z80_pages.rb +118 -0
  18. data/lib/badline/c128/z80_turns.rb +106 -0
  19. data/lib/badline/c128.rb +25 -26
  20. data/lib/badline/cartridge/action_replay.rb +2 -2
  21. data/lib/badline/cartridge/comal80.rb +1 -7
  22. data/lib/badline/cartridge/dinamic.rb +0 -8
  23. data/lib/badline/cartridge/final_cartridge3.rb +6 -6
  24. data/lib/badline/cartridge/fun_play.rb +0 -8
  25. data/lib/badline/cartridge/game_system.rb +0 -8
  26. data/lib/badline/cartridge/kcs_power.rb +10 -7
  27. data/lib/badline/cartridge/mach5.rb +2 -4
  28. data/lib/badline/cartridge/magic_desk.rb +0 -8
  29. data/lib/badline/cartridge/ocean.rb +0 -8
  30. data/lib/badline/cartridge/pagefox.rb +2 -2
  31. data/lib/badline/cartridge/retro_replay.rb +7 -6
  32. data/lib/badline/cartridge/rex_utility.rb +2 -6
  33. data/lib/badline/cartridge/rgcd.rb +2 -2
  34. data/lib/badline/cartridge/simons_basic.rb +0 -5
  35. data/lib/badline/cartridge/super_games.rb +4 -2
  36. data/lib/badline/cartridge/westermann.rb +2 -6
  37. data/lib/badline/cartridge.rb +37 -9
  38. data/lib/badline/cia/fast_forward.rb +60 -0
  39. data/lib/badline/cia/serial.rb +12 -0
  40. data/lib/badline/cia/timer.rb +39 -0
  41. data/lib/badline/cia.rb +10 -5
  42. data/lib/badline/computer/attachments.rb +4 -50
  43. data/lib/badline/computer/true_drives.rb +80 -0
  44. data/lib/badline/computer.rb +4 -0
  45. data/lib/badline/core.rb +2 -0
  46. data/lib/badline/drive/core.rb +147 -0
  47. data/lib/badline/drive/fast_serial.rb +73 -0
  48. data/lib/badline/{drive1541 → drive}/idle.rb +18 -29
  49. data/lib/badline/drive/idle_cpu.rb +28 -0
  50. data/lib/badline/{drive1541 → drive}/orbit.rb +3 -5
  51. data/lib/badline/drive/serial_lines.rb +73 -0
  52. data/lib/badline/{drive1541 → drive}/sleep.rb +4 -5
  53. data/lib/badline/drive/vias.rb +84 -0
  54. data/lib/badline/drive/watch.rb +150 -0
  55. data/lib/badline/drive.rb +11 -0
  56. data/lib/badline/drive1541/bus.rb +5 -129
  57. data/lib/badline/drive1541/byte_ready.rb +48 -0
  58. data/lib/badline/drive1541/cpu.rb +1 -19
  59. data/lib/badline/drive1541/disk.rb +77 -20
  60. data/lib/badline/drive1541/disk_state.rb +7 -2
  61. data/lib/badline/drive1541/disk_via.rb +3 -0
  62. data/lib/badline/drive1541/mechanism.rb +48 -14
  63. data/lib/badline/drive1541/mechanism_state.rb +5 -4
  64. data/lib/badline/drive1541/rotation.rb +5 -5
  65. data/lib/badline/drive1541/serial_port.rb +4 -60
  66. data/lib/badline/drive1541.rb +14 -172
  67. data/lib/badline/drive1571/bus.rb +111 -0
  68. data/lib/badline/drive1571/cpu.rb +11 -0
  69. data/lib/badline/drive1571/serial_port.rb +37 -0
  70. data/lib/badline/drive1571/serial_via.rb +25 -0
  71. data/lib/badline/drive1571/wd1770.rb +120 -0
  72. data/lib/badline/drive1571.rb +175 -0
  73. data/lib/badline/drive1581/bus.rb +137 -0
  74. data/lib/badline/drive1581/cpu.rb +11 -0
  75. data/lib/badline/drive1581/disk.rb +135 -0
  76. data/lib/badline/drive1581/disk_state.rb +87 -0
  77. data/lib/badline/drive1581/mechanism.rb +117 -0
  78. data/lib/badline/drive1581/serial_port.rb +52 -0
  79. data/lib/badline/drive1581/slot.rb +78 -0
  80. data/lib/badline/drive1581/track.rb +100 -0
  81. data/lib/badline/drive1581/track_bytes.rb +75 -0
  82. data/lib/badline/drive1581/wd1772/registers.rb +89 -0
  83. data/lib/badline/drive1581/wd1772/sector_writes.rb +73 -0
  84. data/lib/badline/drive1581/wd1772/sectors.rb +150 -0
  85. data/lib/badline/drive1581/wd1772/state.rb +66 -0
  86. data/lib/badline/drive1581/wd1772/stepping.rb +109 -0
  87. data/lib/badline/drive1581/wd1772/tracks.rb +125 -0
  88. data/lib/badline/drive1581/wd1772/turn.rb +73 -0
  89. data/lib/badline/drive1581/wd1772.rb +216 -0
  90. data/lib/badline/drive1581.rb +233 -0
  91. data/lib/badline/frontend/app.rb +2 -4
  92. data/lib/badline/frontend/audio_device.rb +67 -0
  93. data/lib/badline/frontend/audio_sink.rb +16 -50
  94. data/lib/badline/frontend/boot.rb +18 -10
  95. data/lib/badline/frontend/buttons.rb +7 -7
  96. data/lib/badline/frontend/drive_led.rb +1 -1
  97. data/lib/badline/frontend/keys.rb +33 -8
  98. data/lib/badline/frontend/media_slots.rb +42 -0
  99. data/lib/badline/frontend/{confirmation.rb → menu/confirmation.rb} +4 -4
  100. data/lib/badline/frontend/{file_browser.rb → menu/file_browser.rb} +18 -34
  101. data/lib/badline/frontend/{menu_dialogs.rb → menu/menu_dialogs.rb} +3 -7
  102. data/lib/badline/frontend/{menu_media.rb → menu/menu_media.rb} +26 -51
  103. data/lib/badline/frontend/{menu_pages.rb → menu/menu_pages.rb} +7 -9
  104. data/lib/badline/frontend/{name_field.rb → menu/name_field.rb} +11 -24
  105. data/lib/badline/frontend/{pause_menu.rb → menu/pause_menu.rb} +13 -28
  106. data/lib/badline/frontend/menu/theme.rb +18 -0
  107. data/lib/badline/frontend/{filter_view.rb → player/filter_view.rb} +4 -4
  108. data/lib/badline/frontend/{info_view.rb → player/info_view.rb} +3 -3
  109. data/lib/badline/frontend/{player_window.rb → player/player_controller.rb} +17 -25
  110. data/lib/badline/frontend/{player_footer.rb → player/player_footer.rb} +8 -8
  111. data/lib/badline/frontend/{player_header.rb → player/player_header.rb} +5 -5
  112. data/lib/badline/frontend/{player_screen.rb → player/player_screen.rb} +4 -5
  113. data/lib/badline/frontend/{scope.rb → player/scope.rb} +1 -1
  114. data/lib/badline/frontend/{sid_view.rb → player/sid_view.rb} +16 -20
  115. data/lib/badline/frontend/player/theme.rb +18 -0
  116. data/lib/badline/frontend/{visualizer_view.rb → player/visualizer_view.rb} +8 -8
  117. data/lib/badline/frontend/sound.rb +19 -38
  118. data/lib/badline/frontend/timeline.rb +3 -14
  119. data/lib/badline/frontend.rb +24 -20
  120. data/lib/badline/iec_bus.rb +68 -6
  121. data/lib/badline/machine.rb +9 -6
  122. data/lib/badline/media/boot_disk.rb +50 -0
  123. data/lib/badline/media/not_disk.rb +8 -0
  124. data/lib/badline/media/true_drive.rb +48 -25
  125. data/lib/badline/media.rb +9 -3
  126. data/lib/badline/options/event.rb +2 -2
  127. data/lib/badline/options/family.rb +3 -9
  128. data/lib/badline/options/help.rb +3 -1
  129. data/lib/badline/options/table.rb +1 -1
  130. data/lib/badline/roms/README +18 -0
  131. data/lib/badline/roms/dos1571.rom +0 -0
  132. data/lib/badline/roms/dos1581.rom +0 -0
  133. data/lib/badline/sdl.rb +37 -20
  134. data/lib/badline/snapshot/state.rb +5 -4
  135. data/lib/badline/storage/d64_image.rb +3 -0
  136. data/lib/badline/storage/d71_image.rb +9 -4
  137. data/lib/badline/storage/g64_image.rb +7 -2
  138. data/lib/badline/storage/p00.rb +8 -0
  139. data/lib/badline/time_of_day.rb +9 -0
  140. data/lib/badline/version.rb +1 -1
  141. data/lib/badline/via/fast_forward.rb +3 -3
  142. data/lib/badline/vic20/attachments.rb +23 -3
  143. data/lib/badline/vic20/saved_state.rb +2 -0
  144. data/lib/badline/vic20.rb +3 -0
  145. data/lib/badline/z80/arithmetic.rb +117 -0
  146. data/lib/badline/z80/bit_page.rb +55 -0
  147. data/lib/badline/z80/block_io.rb +58 -0
  148. data/lib/badline/z80/block_transfers.rb +59 -0
  149. data/lib/badline/z80/bus_cycles.rb +111 -0
  150. data/lib/badline/z80/core.rb +113 -0
  151. data/lib/badline/z80/exchanges.rb +50 -0
  152. data/lib/badline/z80/extended_page.rb +89 -0
  153. data/lib/badline/z80/flags.rb +37 -0
  154. data/lib/badline/z80/interrupt_acceptance.rb +46 -0
  155. data/lib/badline/z80/jumps.rb +82 -0
  156. data/lib/badline/z80/loads.rb +98 -0
  157. data/lib/badline/z80/main_page.rb +134 -0
  158. data/lib/badline/z80/registers.rb +120 -0
  159. data/lib/badline/z80/saved_state.rb +69 -0
  160. data/lib/badline/z80/shifts.rb +64 -0
  161. data/lib/badline/z80.rb +11 -0
  162. metadata +89 -24
  163. /data/lib/badline/frontend/{menu_ports.rb → menu/menu_ports.rb} +0 -0
  164. /data/lib/badline/frontend/{player_commands.rb → player/player_commands.rb} +0 -0
  165. /data/lib/badline/frontend/{player_state.rb → player/player_state.rb} +0 -0
  166. /data/lib/badline/frontend/{sid_history.rb → player/sid_history.rb} +0 -0
@@ -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 go to
6
- # the KERNAL the machine runs, C= held from power-on starts it in C64
7
- # mode through the C128 KERNAL, and #active_screen tells a front end
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 runs BASIC 7.0 and the C128 KERNAL from reset.
22
- # The C128's Z80 runs first and hands the bus to the 8502, but here the
23
- # 8502 starts at its reset vector, with the MMU as the Z80 leaves it.
24
- # The KERNAL goes to C64 mode as on the C128, with C= held at reset, a
25
- # C64 cartridge or GO64.
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
- plug_serial_bus
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
- @drive1541&.host_cycle!
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 drive.
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
- @cpu.reset!
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
- save_windows(out, windows)
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
- load_windows(input, windows)
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 save_mapper(out)
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
- self.mode = :rom16k
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
- self.mode = :ultimax
60
- changed!
58
+ select_mode(:ultimax)
61
59
  end
62
60
 
63
61
  private
64
62
 
65
63
  def save_mapper(out)
66
- save_windows(out, [*@roml_banks, *@romh_banks, EMPTY_BANK])
64
+ super
67
65
  out.boolean(@register_visible).boolean(@phi1_ultimax)
68
66
  end
69
67
 
70
68
  def load_mapper(input)
71
- load_windows(input, [*@roml_banks, *@romh_banks, EMPTY_BANK])
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
- switch(addr.anybits?(0x02) ? :off : :rom8k)
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
- switch(addr.anybits?(0x02) ? :ultimax : :rom16k)
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
- switch(:rom16k)
48
+ select_mode(:rom16k)
49
49
  end
50
50
 
51
51
  def freeze!
52
52
  self.nmi = false
53
- switch(:ultimax)
53
+ select_mode(:ultimax)
54
54
  end
55
55
 
56
56
  private
57
57
 
58
- def switch(mode)
59
- self.mode = mode
60
- changed!
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
- self.mode = addr < 0xdf00 ? :rom8k : :off
18
- changed!
17
+ select_mode(addr < 0xdf00 ? :rom8k : :off)
19
18
  end
20
19
 
21
20
  def reset
22
- self.mode = :rom8k
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