badline 0.11.1 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/CLAUDE.md +8 -7
  4. data/CONTRIBUTING.md +7 -6
  5. data/README.md +12 -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/address_bus/saved_state.rb +4 -10
  10. data/lib/badline/address_bus.rb +8 -27
  11. data/lib/badline/audio/bare_player.rb +1 -1
  12. data/lib/badline/c128/bus.rb +17 -52
  13. data/lib/badline/c128/bus_state.rb +5 -5
  14. data/lib/badline/c128/drives.rb +35 -22
  15. data/lib/badline/c128/fast_serial.rb +1 -0
  16. data/lib/badline/c128/io_pages.rb +35 -0
  17. data/lib/badline/c128/mmu.rb +9 -7
  18. data/lib/badline/c128/modes.rb +17 -4
  19. data/lib/badline/c128/saved_state.rb +22 -0
  20. data/lib/badline/c128/z80.rb +49 -0
  21. data/lib/badline/c128/z80_pages.rb +118 -0
  22. data/lib/badline/c128/z80_turns.rb +106 -0
  23. data/lib/badline/c128.rb +21 -83
  24. data/lib/badline/cartridge/action_replay.rb +2 -2
  25. data/lib/badline/cartridge/comal80.rb +1 -7
  26. data/lib/badline/cartridge/dinamic.rb +0 -8
  27. data/lib/badline/cartridge/final_cartridge3.rb +6 -6
  28. data/lib/badline/cartridge/fun_play.rb +0 -8
  29. data/lib/badline/cartridge/game_system.rb +0 -8
  30. data/lib/badline/cartridge/kcs_power.rb +10 -7
  31. data/lib/badline/cartridge/mach5.rb +2 -4
  32. data/lib/badline/cartridge/magic_desk.rb +0 -8
  33. data/lib/badline/cartridge/ocean.rb +0 -8
  34. data/lib/badline/cartridge/pagefox.rb +2 -2
  35. data/lib/badline/cartridge/retro_replay.rb +7 -6
  36. data/lib/badline/cartridge/rex_utility.rb +2 -6
  37. data/lib/badline/cartridge/rgcd.rb +2 -2
  38. data/lib/badline/cartridge/simons_basic.rb +0 -5
  39. data/lib/badline/cartridge/super_games.rb +4 -2
  40. data/lib/badline/cartridge/westermann.rb +2 -6
  41. data/lib/badline/cartridge.rb +37 -9
  42. data/lib/badline/cia/fast_forward.rb +21 -3
  43. data/lib/badline/cia.rb +8 -5
  44. data/lib/badline/computer/attachments.rb +14 -95
  45. data/lib/badline/computer/common.rb +34 -0
  46. data/lib/badline/computer/kernal_traps.rb +65 -5
  47. data/lib/badline/computer/true_drives.rb +69 -0
  48. data/lib/badline/computer.rb +9 -68
  49. data/lib/badline/core.rb +4 -2
  50. data/lib/badline/cpu_port.rb +112 -0
  51. data/lib/badline/drive/core.rb +8 -33
  52. data/lib/badline/drive/fast_serial.rb +73 -0
  53. data/lib/badline/drive/idle.rb +1 -11
  54. data/lib/badline/drive/orbit.rb +2 -5
  55. data/lib/badline/drive/sleep.rb +0 -2
  56. data/lib/badline/drive/vias.rb +84 -0
  57. data/lib/badline/drive.rb +2 -0
  58. data/lib/badline/drive1541/disk.rb +14 -17
  59. data/lib/badline/drive1541/disk_state.rb +7 -15
  60. data/lib/badline/drive1541.rb +1 -0
  61. data/lib/badline/drive1571/wd1770.rb +94 -8
  62. data/lib/badline/drive1571.rb +8 -58
  63. data/lib/badline/drive1581/bus.rb +137 -0
  64. data/lib/badline/drive1581/cpu.rb +11 -0
  65. data/lib/badline/drive1581/disk.rb +135 -0
  66. data/lib/badline/drive1581/disk_state.rb +87 -0
  67. data/lib/badline/drive1581/mechanism.rb +117 -0
  68. data/lib/badline/drive1581/serial_port.rb +52 -0
  69. data/lib/badline/drive1581/slot.rb +78 -0
  70. data/lib/badline/drive1581/track.rb +100 -0
  71. data/lib/badline/drive1581/track_bytes.rb +75 -0
  72. data/lib/badline/drive1581/wd1772/registers.rb +89 -0
  73. data/lib/badline/drive1581/wd1772/sector_writes.rb +73 -0
  74. data/lib/badline/drive1581/wd1772/sectors.rb +150 -0
  75. data/lib/badline/drive1581/wd1772/state.rb +66 -0
  76. data/lib/badline/drive1581/wd1772/stepping.rb +109 -0
  77. data/lib/badline/drive1581/wd1772/tracks.rb +125 -0
  78. data/lib/badline/drive1581/wd1772/turn.rb +73 -0
  79. data/lib/badline/drive1581/wd1772.rb +216 -0
  80. data/lib/badline/drive1581.rb +233 -0
  81. data/lib/badline/frontend/app.rb +1 -3
  82. data/lib/badline/frontend/audio_device.rb +67 -0
  83. data/lib/badline/frontend/audio_sink.rb +16 -50
  84. data/lib/badline/frontend/boot.rb +24 -67
  85. data/lib/badline/frontend/buttons.rb +7 -7
  86. data/lib/badline/frontend/keys.rb +33 -8
  87. data/lib/badline/frontend/media_slots.rb +42 -0
  88. data/lib/badline/frontend/{confirmation.rb → menu/confirmation.rb} +4 -4
  89. data/lib/badline/frontend/{file_browser.rb → menu/file_browser.rb} +18 -34
  90. data/lib/badline/frontend/{menu_dialogs.rb → menu/menu_dialogs.rb} +3 -7
  91. data/lib/badline/frontend/{menu_media.rb → menu/menu_media.rb} +26 -51
  92. data/lib/badline/frontend/{menu_pages.rb → menu/menu_pages.rb} +5 -8
  93. data/lib/badline/frontend/{name_field.rb → menu/name_field.rb} +11 -24
  94. data/lib/badline/frontend/{pause_menu.rb → menu/pause_menu.rb} +13 -28
  95. data/lib/badline/frontend/menu/theme.rb +18 -0
  96. data/lib/badline/frontend/{filter_view.rb → player/filter_view.rb} +4 -4
  97. data/lib/badline/frontend/{info_view.rb → player/info_view.rb} +3 -3
  98. data/lib/badline/frontend/{player_window.rb → player/player_controller.rb} +17 -25
  99. data/lib/badline/frontend/{player_footer.rb → player/player_footer.rb} +8 -8
  100. data/lib/badline/frontend/{player_header.rb → player/player_header.rb} +5 -5
  101. data/lib/badline/frontend/{player_screen.rb → player/player_screen.rb} +4 -5
  102. data/lib/badline/frontend/{scope.rb → player/scope.rb} +1 -1
  103. data/lib/badline/frontend/{sid_view.rb → player/sid_view.rb} +16 -20
  104. data/lib/badline/frontend/player/theme.rb +18 -0
  105. data/lib/badline/frontend/{visualizer_view.rb → player/visualizer_view.rb} +8 -8
  106. data/lib/badline/frontend/sound.rb +19 -38
  107. data/lib/badline/frontend/timeline.rb +3 -14
  108. data/lib/badline/frontend.rb +24 -20
  109. data/lib/badline/iec_bus.rb +14 -2
  110. data/lib/badline/kernal_trap/{file.rb → file_routine.rb} +1 -1
  111. data/lib/badline/kernal_trap/load.rb +1 -1
  112. data/lib/badline/kernal_trap/save.rb +1 -1
  113. data/lib/badline/kernal_trap.rb +1 -1
  114. data/lib/badline/machine/clocking.rb +50 -0
  115. data/lib/badline/machine.rb +63 -6
  116. data/lib/badline/media/boot_disk.rb +50 -0
  117. data/lib/badline/media/disk_list.rb +3 -3
  118. data/lib/badline/media/disk_set.rb +1 -1
  119. data/lib/badline/media/extensions.rb +33 -0
  120. data/lib/badline/media/not_disk.rb +8 -0
  121. data/lib/badline/media/program.rb +28 -0
  122. data/lib/badline/media/true_drive.rb +38 -17
  123. data/lib/badline/media/tune.rb +35 -0
  124. data/lib/badline/media/vic20_cartridge.rb +1 -7
  125. data/lib/badline/media/vic20_media.rb +38 -80
  126. data/lib/badline/media.rb +90 -81
  127. data/lib/badline/options/event.rb +4 -2
  128. data/lib/badline/options/family.rb +3 -9
  129. data/lib/badline/options/help.rb +2 -1
  130. data/lib/badline/roms/README +9 -0
  131. data/lib/badline/roms/dos1581.rom +0 -0
  132. data/lib/badline/sdl.rb +37 -20
  133. data/lib/badline/sid.rb +1 -1
  134. data/lib/badline/snapshot/machine_state.rb +9 -1
  135. data/lib/badline/snapshot/state.rb +6 -4
  136. data/lib/badline/snapshot.rb +11 -9
  137. data/lib/badline/storage/d64_image.rb +15 -7
  138. data/lib/badline/storage/disk_image/writing.rb +2 -2
  139. data/lib/badline/storage/disk_image.rb +1 -1
  140. data/lib/badline/storage/host_directory.rb +1 -1
  141. data/lib/badline/storage/p00.rb +8 -0
  142. data/lib/badline/storage/t64.rb +1 -1
  143. data/lib/badline/time_of_day.rb +3 -3
  144. data/lib/badline/version.rb +1 -1
  145. data/lib/badline/vic/bank.rb +1 -1
  146. data/lib/badline/vic/border_mask.rb +1 -1
  147. data/lib/badline/vic/collisions.rb +1 -1
  148. data/lib/badline/vic/color_patches.rb +1 -1
  149. data/lib/badline/vic/display_state.rb +1 -1
  150. data/lib/badline/vic/graphics_mode.rb +1 -1
  151. data/lib/badline/vic/graphics_shifter.rb +1 -1
  152. data/lib/badline/vic/register_log.rb +1 -1
  153. data/lib/badline/vic/registers.rb +1 -1
  154. data/lib/badline/vic/sequencer.rb +1 -1
  155. data/lib/badline/vic/sequencer_output.rb +1 -1
  156. data/lib/badline/vic/sequencer_state.rb +1 -1
  157. data/lib/badline/vic/sprite/internal_bus.rb +1 -1
  158. data/lib/badline/vic/sprite/saved_state.rb +1 -1
  159. data/lib/badline/vic/sprite/shifter.rb +1 -1
  160. data/lib/badline/vic/sprite/timing.rb +1 -1
  161. data/lib/badline/vic/sprite.rb +1 -1
  162. data/lib/badline/vic/sprites.rb +1 -1
  163. data/lib/badline/vic/sprites_state.rb +1 -1
  164. data/lib/badline/vic.rb +6 -5
  165. data/lib/badline/vic20/attachments.rb +15 -71
  166. data/lib/badline/vic20/saved_state.rb +2 -30
  167. data/lib/badline/vic20.rb +4 -37
  168. data/lib/badline/z80/core.rb +2 -0
  169. data/lib/badline/z80/saved_state.rb +69 -0
  170. metadata +61 -23
  171. data/lib/badline/cycleable.rb +0 -38
  172. /data/lib/badline/frontend/{menu_ports.rb → menu/menu_ports.rb} +0 -0
  173. /data/lib/badline/frontend/{player_commands.rb → player/player_commands.rb} +0 -0
  174. /data/lib/badline/frontend/{player_state.rb → player/player_state.rb} +0 -0
  175. /data/lib/badline/frontend/{sid_history.rb → player/sid_history.rb} +0 -0
@@ -2,9 +2,9 @@
2
2
 
3
3
  module Badline
4
4
  class C128
5
- # The true drives on the C128's serial bus: a Drive1541, 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.
5
+ # The true drives on the C128's serial bus: a Drive1541, the C128D's
6
+ # built-in 1571 (Drive1571) or a 1581 (Drive1581::Slot), each in a
7
+ # field of its own, so none makes another's per-cycle call a union.
8
8
  module Drives
9
9
  attr_reader :drive1571
10
10
 
@@ -28,11 +28,15 @@ module Badline
28
28
  @serial_trap&.device = serial_trap_device
29
29
  end
30
30
 
31
- # The true drive on the bus, the 1571 ahead of a 1541.
32
- def true_drive = @drive1571 || @drive1541
31
+ # The true drive on the bus, a 1581 ahead of the 1571 ahead of a 1541.
32
+ def true_drive = @drive1581 || @drive1571 || @drive1541
33
33
 
34
- # Plugs in the C128D's 1571 as device 8, and returns it.
35
- def plug_true_drive = Drive1571.new.tap { |drive| attach_drive1571(drive) }
34
+ # Plugs in the C128D's 1571 as device 8, in place of a 1581 there, and
35
+ # returns it.
36
+ def plug_true_drive
37
+ detach_drive1581 if drive1581_on_device8?
38
+ Drive1571.new.tap { |drive| attach_drive1571(drive) }
39
+ end
36
40
 
37
41
  private
38
42
 
@@ -43,6 +47,7 @@ module Badline
43
47
  @save_trap = nil
44
48
  @drive1541 = nil
45
49
  @drive1571 = nil
50
+ @drive1581 = nil
46
51
  plug_serial_bus
47
52
  plug_fast_serial
48
53
  end
@@ -50,10 +55,25 @@ module Badline
50
55
  # Device 8, unless a true drive is on the bus as device 8.
51
56
  def serial_trap_device
52
57
  device = KernalTrap::Routine::DEVICE
53
- @drive1571&.device == device || @drive1541&.device == device ? nil : device
58
+ @drive1571&.device == device || @drive1541&.device == device || drive1581_on_device8? ? nil : device
59
+ end
60
+
61
+ # A 1581 plugged in holds the machine's on_init handlers back until
62
+ # its DOS has booted, which takes longer than the C128 KERNAL's.
63
+ def drive1581_attached
64
+ @init_threshold = [@init_threshold, Drive1581::BOOT_CYCLES].max if @cycles < @init_threshold
54
65
  end
55
66
 
56
- # A true 1541 (Computer::Attachments#save_serial_bus), then a 1571.
67
+ # Unplugs the 1541 and the 1571 if either is device 8, for a 1581 to
68
+ # take its place.
69
+ def unplug_device8
70
+ device = KernalTrap::Routine::DEVICE
71
+ detach_drive1541 if @drive1541&.device == device
72
+ detach_drive1571 if @drive1571&.device == device
73
+ end
74
+
75
+ # A true 1541 (Computer::Attachments#save_serial_bus), then a 1571 and
76
+ # a 1581.
57
77
  def save_serial_bus(out)
58
78
  out.boolean(true).boolean(!@drive1541.nil?)
59
79
  if @drive1541
@@ -61,25 +81,18 @@ module Badline
61
81
  @drive1541.save_state(out)
62
82
  end
63
83
  out.boolean(!@drive1571.nil?)
64
- return unless @drive1571
65
-
66
- out.int(@drive1571.device)
67
- @drive1571.save_state(out)
84
+ if @drive1571
85
+ out.int(@drive1571.device)
86
+ @drive1571.save_state(out)
87
+ end
88
+ save_drive1581(out)
68
89
  end
69
90
 
70
91
  def load_serial_bus(input)
71
92
  input.boolean?
72
93
  load_drive1541(input)
73
94
  load_drive1571(input)
74
- 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)
95
+ load_drive1581(input)
83
96
  end
84
97
 
85
98
  def load_drive1571(input)
@@ -22,6 +22,7 @@ module Badline
22
22
  drive_fast_serial if @fast_serial_out
23
23
  @drive1541&.host_cycle!
24
24
  @drive1571&.host_cycle!
25
+ @drive1581&.host_cycle!
25
26
  end
26
27
 
27
28
  def turn_fast_serial
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class C128
5
+ class Bus
6
+ # The chips at $D000-$DFFF, which the 8502 sees while I/O is mapped
7
+ # in and the Z80 reaches through IN and OUT.
8
+ module IOPages
9
+ private
10
+
11
+ def map_io_pages
12
+ @io_mapped = true
13
+ lay_out_io(@read_pages, @write_pages)
14
+ @read_pages[0xd5] = @write_pages[0xd5] = @mmu.c64_mode? ? @open_bus : @mmu
15
+ map_cartridge_io if @cartridge
16
+ end
17
+
18
+ # The chips into +reads+ and +writes+, but for the MMU's page.
19
+ def lay_out_io(reads, writes)
20
+ reads.fill(@vic, 0xd0, 4)
21
+ writes.fill(@vic_writes, 0xd0, 4)
22
+ reads[0xd4] = writes[0xd4] = @sid
23
+ reads[0xd6] = writes[0xd6] = @vdc
24
+ reads[0xd7] = writes[0xd7] = @debug_page || @open_bus
25
+ reads.fill(@color_ram, 0xd8, 4)
26
+ writes.fill(@color_ram, 0xd8, 4)
27
+ reads[0xdc] = writes[0xdc] = @cia1
28
+ reads[0xdd] = writes[0xdd] = @cia2
29
+ reads.fill(@open_bus, 0xde, 2)
30
+ writes.fill(@open_bus, 0xde, 2)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -19,12 +19,11 @@ module Badline
19
19
  # A write to P0H or P1H waits until the matching P0L or P1L write
20
20
  # takes it.
21
21
  #
22
- # On the C128 the Z80 runs first and hands the bus to the 8502. Here the
23
- # 8502 runs from reset, with MCR as the Z80 leaves it. A machine built
24
- # for C64 mode resets into C64 mode, the state the C128 KERNAL leaves
25
- # when C= is held at power-on. In C64 mode the MMU answers nowhere, but
26
- # the CPU's RAM bank, common RAM and the VIC's bank it holds stay in
27
- # force.
22
+ # A reset clears MCR bit 0, so the Z80 runs first, boots and hands the
23
+ # bus to the 8502. A machine built for C64 mode resets into C64 mode
24
+ # instead, with the 8502 running, the state the C128 KERNAL leaves when
25
+ # C= is held at power-on. In C64 mode the MMU answers nowhere, but the
26
+ # CPU's RAM bank, common RAM and the VIC's bank it holds stay in force.
28
27
  class MMU
29
28
  CR = 0
30
29
  MCR = 5
@@ -86,7 +85,7 @@ module Badline
86
85
  def reset!
87
86
  fast_serial_out = fast_serial_out?
88
87
  @registers.fill(0)
89
- @registers[MCR] = @reset_mode == :c64 ? MCR_C64_MODE | MCR_8502 : MCR_8502
88
+ @registers[MCR] = @reset_mode == :c64 ? MCR_C64_MODE | MCR_8502 : 0
90
89
  @registers[P1L] = 0x01
91
90
  @registers[VR] = VERSION
92
91
  @p0h_latch = 0
@@ -101,6 +100,9 @@ module Badline
101
100
  # CIA 1's CNT and SP drive SRQ and DATA.
102
101
  def fast_serial_out? = @registers[MCR].anybits?(MCR_FSDIR)
103
102
 
103
+ # Whether MCR bit 0 gives the bus to the Z80.
104
+ def z80? = @registers[MCR].nobits?(MCR_8502)
105
+
104
106
  # :c64 or :c128, the mode MCR bit 6 selects.
105
107
  def mode = c64_mode? ? :c64 : :c128
106
108
 
@@ -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,9 +100,11 @@ module Badline
98
100
  @pending_keys = input.ints if input.boolean?
99
101
  @init_handlers_lost = [input.int - @init_handlers.length, 0].max
100
102
  load_cartridge(input)
103
+ @cpu_reset_pending = false
101
104
  @bus.load_state(input)
102
105
  push_serial_lines
103
106
  @cpu.load_state(input)
107
+ load_z80(input)
104
108
  load_trap_drive(input)
105
109
  load_serial_bus(input)
106
110
  push_fast_serial
@@ -108,6 +112,24 @@ module Badline
108
112
 
109
113
  private
110
114
 
115
+ # The Z80 and which CPU has the bus, which a state from before the
116
+ # Z80 leaves to the 8502. The bus, restored before it, hands the bus
117
+ # to its CPU without resetting the 8502.
118
+ def load_z80(input)
119
+ @z80_running = @bus.z80?
120
+ @z80_turn = @z80_running
121
+ @z80_due = @z80.cycles
122
+ @chips_ahead = 0
123
+ @cpu_reset_pending = false
124
+ return unless input.schema > 6
125
+
126
+ @z80.load_state(input)
127
+ @z80_due = input.int
128
+ @chips_ahead = input.int
129
+ @cpu_reset_pending = input.boolean?
130
+ @z80_turn = @z80_running || @chips_ahead.positive?
131
+ end
132
+
111
133
  def check_setup(setup)
112
134
  ours = [@model.name, @sid.model, @c64_built ? :c64 : :c128]
113
135
  return if setup == ours
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/z80/core"
4
+
5
+ module Badline
6
+ class C128
7
+ # The C128's Z80A, on the Z80's side of the C128::Bus.
8
+ class Z80
9
+ include Badline::Z80::Core
10
+ end
11
+
12
+ # The Z80's accesses, through the pages C128::Bus lays out for it
13
+ # (Bus::Z80Pages). An IN or OUT first brings the chips up to the cycle
14
+ # it falls in, since the Z80 runs whole instructions ahead of them.
15
+ class Z80Bus
16
+ def initialize(bus)
17
+ @read_pages = bus.z80_read_pages
18
+ @write_pages = bus.z80_write_pages
19
+ @input_pages = bus.z80_input_pages
20
+ @output_pages = bus.z80_output_pages
21
+ @machine = nil
22
+ end
23
+
24
+ # The machine whose chips an IN or OUT catches up.
25
+ attr_writer :machine
26
+
27
+ def fetch(addr) = @read_pages[addr >> 8].peek(addr)
28
+
29
+ def read(addr) = @read_pages[addr >> 8].peek(addr)
30
+
31
+ def write(addr, value)
32
+ @write_pages[addr >> 8].poke(addr, value)
33
+ end
34
+
35
+ def input(port)
36
+ @machine.z80_catch_up
37
+ @input_pages[port >> 8].peek(port)
38
+ end
39
+
40
+ def output(port, value)
41
+ @machine.z80_catch_up
42
+ @output_pages[port >> 8].poke(port, value)
43
+ end
44
+
45
+ # Nothing drives the data bus in an interrupt acknowledge cycle.
46
+ def acknowledge = 0xff
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class C128
5
+ class Bus
6
+ # What the Z80 sees, in memory and through IN and OUT, laid over the
7
+ # 8502's map whenever the MMU, the port or a cartridge changes it
8
+ # while the Z80 has the bus.
9
+ #
10
+ # In C128 mode, with CR's RAM bank 0, the MMU translates $0000-$0FFF
11
+ # to $D000-$DFFF of bank 0: a read gets the Z80 BIOS, which the
12
+ # KERNAL ROM holds there, and a write or an IN or OUT the RAM at
13
+ # $Dxxx. With bank 1 those pages are RAM, common RAM's or bank 1's.
14
+ # $D000-$DFFF is RAM. In C64 mode the PLA decodes the Z80's memory
15
+ # accesses as it does the 8502's.
16
+ #
17
+ # In both modes the chips at $D000-$DFFF answer IN and OUT whether
18
+ # I/O is mapped in or not. The MMU's registers at $D500 take an OUT
19
+ # either way in C128 mode, but answer an IN only with CR's I/O bit
20
+ # on, memory answering it otherwise. While I/O is mapped in, the
21
+ # colour RAM the 8502 sees also answers at $1000-$13FF. An IN or OUT
22
+ # anywhere else reaches what memory has there.
23
+ module Z80Pages
24
+ # A colour RAM bank seen at $1000-$13FF.
25
+ class ColorWindow
26
+ def initialize(color_ram)
27
+ @color_ram = color_ram
28
+ end
29
+
30
+ def peek(addr) = @color_ram.peek(0xd800 | (addr & 0x3ff))
31
+ def poke(addr, value) = @color_ram.poke(0xd800 | (addr & 0x3ff), value)
32
+ end
33
+
34
+ # The Z80's memory and I/O pages, each 256 entries.
35
+ attr_reader :z80_read_pages, :z80_write_pages, :z80_input_pages, :z80_output_pages
36
+
37
+ # Calls the block whenever MCR bit 0 hands the bus to the other CPU.
38
+ def on_processor_change(&block)
39
+ @on_processor_change = block
40
+ end
41
+
42
+ # Whether the Z80 has the bus, MCR bit 0 low.
43
+ def z80? = @z80
44
+
45
+ private
46
+
47
+ # Lays out the Z80's pages again while it has the bus, and tells the
48
+ # machine if MCR bit 0 handed the bus over.
49
+ def update_z80!
50
+ z80 = @mmu.z80?
51
+ map_z80_pages if z80
52
+ return if z80 == @z80
53
+
54
+ @z80 = z80
55
+ @on_processor_change&.call
56
+ end
57
+
58
+ def build_z80_pages
59
+ @z80_bios = ROM.new(ROM.read("c128/kernal.rom")[0x1000, 0x1000], length: 0x1000, start: 0)
60
+ @z80_read_pages = Array.new(256)
61
+ @z80_write_pages = Array.new(256)
62
+ @z80_input_pages = Array.new(256)
63
+ @z80_output_pages = Array.new(256)
64
+ @z80_color_ram = [ColorWindow.new(@color_lines.color_ram(0)), ColorWindow.new(@color_lines.color_ram(1))]
65
+ @z80_translated = RAMWindow.new(@ram, 0xd000, 0xfff)
66
+ @z80 = @mmu.z80?
67
+ @on_processor_change = nil
68
+ end
69
+
70
+ def map_z80_pages
71
+ c128_mode = !@mmu.c64_mode?
72
+ bios = c128_mode && @mmu.cr < 0x40
73
+ @z80_read_pages.replace(@read_pages)
74
+ @z80_write_pages.replace(@write_pages)
75
+ map_z80_ram_pages if c128_mode
76
+ map_z80_bios if bios
77
+ map_z80_color_ram(c128_mode) if @io_mapped
78
+ @z80_input_pages.replace(@z80_read_pages)
79
+ @z80_output_pages.replace(@z80_write_pages)
80
+ @z80_input_pages.fill(@z80_translated, 0, 16) if bios
81
+ @io_mapped ? map_z80_io_from_cpu : map_z80_io
82
+ end
83
+
84
+ def map_z80_ram_pages
85
+ (0xd0..0xdf).each do |page|
86
+ ram = bank_of(page, @mmu.cpu_bank).zero? ? @ram : @bank1
87
+ @z80_read_pages[page] = @z80_write_pages[page] = ram
88
+ end
89
+ end
90
+
91
+ def map_z80_bios
92
+ @z80_read_pages.fill(@z80_bios, 0, 16)
93
+ @z80_write_pages.fill(@z80_translated, 0, 16)
94
+ end
95
+
96
+ def map_z80_color_ram(c128_mode)
97
+ window = @z80_color_ram[c128_mode ? @io_port.value & 0x01 : 1]
98
+ @z80_read_pages.fill(window, 0x10, 4)
99
+ @z80_write_pages.fill(window, 0x10, 4)
100
+ end
101
+
102
+ def map_z80_io_from_cpu
103
+ (0xd0..0xdf).each do |page|
104
+ @z80_input_pages[page] = @read_pages[page]
105
+ @z80_output_pages[page] = @write_pages[page]
106
+ end
107
+ end
108
+
109
+ # The chips at $D000-$DFFF while I/O is mapped out, leaving an IN
110
+ # at $D500 to memory.
111
+ def map_z80_io
112
+ lay_out_io(@z80_input_pages, @z80_output_pages)
113
+ @z80_output_pages[0xd5] = @mmu unless @mmu.c64_mode?
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class C128
5
+ # The Z80's turns on the bus. MCR bit 0 hands the bus between the 8502
6
+ # and the Z80, and while the Z80 has it each cycle clocks the chips,
7
+ # then gives the Z80 2 T-states unless BA holds it. The Z80 runs whole
8
+ # instructions while it is behind, so an IN or OUT first brings the
9
+ # chips up to the cycle it falls in (#z80_catch_up), and the cycles
10
+ # that follow clock nothing until the machine catches up with them.
11
+ module Z80Turns
12
+ # The T-states from the start of an I/O machine cycle to the one its
13
+ # data moves in. 1 and 2 pass c128modez80-23, whose IN reads open
14
+ # bus, and 0, 3 and 4 fail it.
15
+ IO_DELAY = 2
16
+
17
+ attr_reader :z80
18
+
19
+ # Brings the chips up to the cycle an IN or OUT of the Z80 falls in.
20
+ def z80_catch_up
21
+ while @z80.cycles + IO_DELAY >= @z80_due
22
+ clock_chips
23
+ @chips_ahead += 1
24
+ @z80_due += 2
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def build_z80
31
+ z80_bus = Z80Bus.new(@bus)
32
+ @z80 = Z80.new(z80_bus)
33
+ z80_bus.machine = self
34
+ @z80_running = @bus.z80?
35
+ @z80_turn = @z80_running
36
+ @z80_due = 0
37
+ @chips_ahead = 0
38
+ @cpu_reset_pending = @z80_running
39
+ @bus.on_processor_change { processor_changed }
40
+ end
41
+
42
+ # A cycle while the Z80 has the bus, or after it handed the bus to the
43
+ # 8502 while the chips were ahead.
44
+ def z80_cycle!
45
+ if @chips_ahead.positive?
46
+ @chips_ahead -= 1
47
+ @z80_turn = @z80_running || @chips_ahead.positive?
48
+ return
49
+ end
50
+
51
+ clock_chips
52
+ @z80_due += 2 unless @vic.ba_low?
53
+ z80 = @z80
54
+ z80.step! while @z80_running && z80.cycles < @z80_due
55
+ end
56
+
57
+ # The chips' share of a cycle while the Z80 has the bus, with the
58
+ # interrupt lines going to the Z80.
59
+ def clock_chips
60
+ handle_init if @cycles == @init_threshold
61
+ feed_keyboard if @pending_keys
62
+
63
+ clock_bits = @clock_bits
64
+ @clock_bits = @vic.clock_bits
65
+ @vic.test_step! if clock_bits >= 0x02
66
+
67
+ @vic.cycle!
68
+ @cia1.cycle!
69
+ @cia2.cycle!
70
+ @sid.cycle!
71
+ @datasette.cycle!
72
+ @vdc.cycle!
73
+
74
+ @z80.int = @cia1.interrupted? || @vic.interrupted?
75
+ nmi = @cia2.interrupted? || @cartridge_nmi || @restore_pulse
76
+ @restore_pulse = false
77
+ @nmi_asserted = nmi
78
+ @z80.nmi = nmi
79
+ clock_serial_bus
80
+
81
+ @cycles += 1
82
+ end
83
+
84
+ # MCR bit 0 handed the bus to the other CPU. The 8502 runs its reset
85
+ # vector the first time it gets the bus after a reset.
86
+ def processor_changed
87
+ @z80_running = @bus.z80?
88
+ @z80_turn = @z80_running || @chips_ahead.positive?
89
+ @z80_due = @z80.cycles
90
+ return if @z80_running || !@cpu_reset_pending
91
+
92
+ @cpu_reset_pending = false
93
+ @cpu.reset!
94
+ end
95
+
96
+ # The RES line reaches the Z80, and the 8502 waits for the bus to run
97
+ # its reset vector, unless the machine resets into C64 mode.
98
+ def reset_z80
99
+ @chips_ahead = 0
100
+ @z80_turn = @z80_running
101
+ @z80.reset!
102
+ @c64_built ? @cpu.reset! : @cpu_reset_pending = true
103
+ end
104
+ end
105
+ end
106
+ end