badline 0.16.0 → 0.17.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 (164) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +76 -0
  3. data/CLAUDE.md +1 -1
  4. data/README.md +130 -17
  5. data/doc/media.md +14 -2
  6. data/doc/pinned-behaviour.md +61 -0
  7. data/doc/snapshot-format.md +379 -70
  8. data/doc/snapshots.md +185 -10
  9. data/lib/badline/cbm2/acia.rb +10 -0
  10. data/lib/badline/cbm2/bus.rb +263 -0
  11. data/lib/badline/cbm2/cpu.rb +58 -0
  12. data/lib/badline/cbm2/crtc_video.rb +133 -0
  13. data/lib/badline/cbm2/ieee_drive.rb +18 -0
  14. data/lib/badline/cbm2/ieee_layout.rb +69 -0
  15. data/lib/badline/cbm2/ieee_ports.rb +104 -0
  16. data/lib/badline/cbm2/ieee_traps.rb +81 -0
  17. data/lib/badline/cbm2/keyboard_ports.rb +60 -0
  18. data/lib/badline/cbm2/keyed_modules.rb +67 -0
  19. data/lib/badline/cbm2/keyed_state.rb +91 -0
  20. data/lib/badline/cbm2/model.rb +44 -0
  21. data/lib/badline/cbm2/p500/control_ports.rb +80 -0
  22. data/lib/badline/cbm2/p500/ieee_layout.rb +41 -0
  23. data/lib/badline/cbm2/p500/keyed_state.rb +85 -0
  24. data/lib/badline/cbm2/p500/model.rb +39 -0
  25. data/lib/badline/cbm2/p500/pages.rb +57 -0
  26. data/lib/badline/cbm2/p500.rb +262 -0
  27. data/lib/badline/cbm2/sid_page.rb +19 -0
  28. data/lib/badline/cbm2.rb +267 -0
  29. data/lib/badline/{plus4 → chips}/acia/frames.rb +1 -1
  30. data/lib/badline/chips/acia/keyed_state.rb +47 -0
  31. data/lib/badline/chips/acia.rb +307 -0
  32. data/lib/badline/chips/crtc.rb +3 -2
  33. data/lib/badline/chips/riot/fast_forward.rb +72 -0
  34. data/lib/badline/chips/riot/keyed_parts.rb +69 -0
  35. data/lib/badline/chips/riot/timer.rb +80 -0
  36. data/lib/badline/chips/riot.rb +162 -0
  37. data/lib/badline/chips/rriot/keyed_state.rb +30 -0
  38. data/lib/badline/chips/rriot.rb +132 -0
  39. data/lib/badline/chips/tpi/keyed_state.rb +38 -0
  40. data/lib/badline/chips/tpi.rb +233 -0
  41. data/lib/badline/chips/vic/bank.rb +60 -5
  42. data/lib/badline/chips/vic/sprite.rb +4 -3
  43. data/lib/badline/drives/datasette/keyed_state.rb +9 -1
  44. data/lib/badline/drives/datasette.rb +27 -3
  45. data/lib/badline/drives/ieee_bus.rb +50 -4
  46. data/lib/badline/drives/model2031/ieee_port.rb +133 -0
  47. data/lib/badline/drives/model2031/keyed_state.rb +66 -0
  48. data/lib/badline/drives/model2031/parts.rb +25 -0
  49. data/lib/badline/drives/model2031.rb +92 -0
  50. data/lib/badline/drives/model8050/disk.rb +163 -0
  51. data/lib/badline/drives/model8050/fdc_bus.rb +102 -0
  52. data/lib/badline/drives/model8050/idle.rb +177 -0
  53. data/lib/badline/drives/model8050/ieee_port.rb +147 -0
  54. data/lib/badline/drives/model8050/ip_bus.rb +93 -0
  55. data/lib/badline/drives/model8050/keyed_state.rb +160 -0
  56. data/lib/badline/drives/model8050/mechanism.rb +230 -0
  57. data/lib/badline/drives/model8050/parts.rb +37 -0
  58. data/lib/badline/drives/model8050.rb +243 -0
  59. data/lib/badline/frontend/app.rb +10 -3
  60. data/lib/badline/frontend/boot.rb +1 -1
  61. data/lib/badline/frontend/controls.rb +51 -15
  62. data/lib/badline/frontend/gamepads.rb +3 -3
  63. data/lib/badline/frontend/keys.rb +61 -2
  64. data/lib/badline/frontend/media_slots.rb +1 -1
  65. data/lib/badline/frontend/menu/menu_media.rb +14 -11
  66. data/lib/badline/frontend/menu/menu_pages.rb +29 -6
  67. data/lib/badline/frontend/menu/menu_ports.rb +40 -4
  68. data/lib/badline/frontend/menu/pause_menu.rb +1 -1
  69. data/lib/badline/frontend/pet_keyboard.rb +23 -7
  70. data/lib/badline/frontend/timeline.rb +19 -10
  71. data/lib/badline/kernal_trap/dos/disk_commands.rb +80 -0
  72. data/lib/badline/kernal_trap/dos/status.rb +3 -0
  73. data/lib/badline/kernal_trap/dos.rb +18 -5
  74. data/lib/badline/kernal_trap/ieee.rb +7 -2
  75. data/lib/badline/kernal_trap/ieee_layout.rb +95 -43
  76. data/lib/badline/machine/ieee_drives.rb +194 -0
  77. data/lib/badline/machine/settling.rb +4 -0
  78. data/lib/badline/machine.rb +26 -8
  79. data/lib/badline/media/cbm2_media.rb +29 -0
  80. data/lib/badline/media/pet_media.rb +5 -5
  81. data/lib/badline/media/program.rb +6 -3
  82. data/lib/badline/media/true_drive.rb +50 -8
  83. data/lib/badline/media.rb +21 -8
  84. data/lib/badline/options/family.rb +15 -3
  85. data/lib/badline/options/help.rb +24 -6
  86. data/lib/badline/options/table.rb +25 -5
  87. data/lib/badline/options/validation.rb +47 -7
  88. data/lib/badline/options.rb +48 -15
  89. data/lib/badline/pet/bus/expansion.rb +99 -5
  90. data/lib/badline/pet/bus.rb +30 -12
  91. data/lib/badline/pet/cassettes.rb +22 -1
  92. data/lib/badline/pet/characters.rb +8 -2
  93. data/lib/badline/pet/crtc_video.rb +20 -7
  94. data/lib/badline/pet/ieee_drive.rb +17 -0
  95. data/lib/badline/pet/ieee_ports.rb +19 -1
  96. data/lib/badline/pet/ieee_traps.rb +10 -5
  97. data/lib/badline/pet/keyed_modules.rb +4 -1
  98. data/lib/badline/pet/keyed_parts.rb +10 -6
  99. data/lib/badline/pet/model.rb +56 -6
  100. data/lib/badline/pet/rom_sets.rb +30 -6
  101. data/lib/badline/pet.rb +45 -16
  102. data/lib/badline/plus4/acia.rb +4 -298
  103. data/lib/badline/plus4/keyed_parts.rb +0 -44
  104. data/lib/badline/plus4.rb +1 -0
  105. data/lib/badline/roms/8050/dos-2.7.rom +0 -0
  106. data/lib/badline/roms/8050/fdc-901869-01.rom +0 -0
  107. data/lib/badline/roms/8050/fdc-901885-04.rom +0 -0
  108. data/lib/badline/roms/README +144 -1
  109. data/lib/badline/roms/cbm2/basic128-02b.rom +0 -0
  110. data/lib/badline/roms/cbm2/basic128.rom +0 -0
  111. data/lib/badline/roms/cbm2/basic256.rom +0 -0
  112. data/lib/badline/roms/cbm2/characters-600.rom +0 -0
  113. data/lib/badline/roms/cbm2/characters-700.rom +0 -0
  114. data/lib/badline/roms/cbm2/kernal-03b.rom +0 -0
  115. data/lib/badline/roms/cbm2/kernal-04a.rom +0 -0
  116. data/lib/badline/roms/cbm2/p500-basic.rom +0 -0
  117. data/lib/badline/roms/cbm2/p500-kernal.rom +0 -0
  118. data/lib/badline/roms/dos2031.rom +0 -0
  119. data/lib/badline/roms/pet/characters-din.rom +0 -0
  120. data/lib/badline/roms/pet/editor4-80-b-50hz-8296.rom +0 -0
  121. data/lib/badline/snapshot/cbm2_setup.rb +53 -0
  122. data/lib/badline/snapshot/container.rb +9 -1
  123. data/lib/badline/snapshot/field_doc/markdown.rb +4 -2
  124. data/lib/badline/snapshot/field_doc.rb +39 -3
  125. data/lib/badline/snapshot/image.rb +79 -15
  126. data/lib/badline/snapshot/pet_setup.rb +30 -13
  127. data/lib/badline/snapshot/vice/cbm2.rb +93 -0
  128. data/lib/badline/snapshot/vice/cbm2_acia.rb +75 -0
  129. data/lib/badline/snapshot/vice/cbm2_crtc.rb +127 -0
  130. data/lib/badline/snapshot/vice/cbm2_mains.rb +49 -0
  131. data/lib/badline/snapshot/vice/cbm2_mem.rb +75 -0
  132. data/lib/badline/snapshot/vice/cbm2_restore.rb +204 -0
  133. data/lib/badline/snapshot/vice/cbm2_tpi.rb +58 -0
  134. data/lib/badline/snapshot/vice/cias.rb +13 -9
  135. data/lib/badline/snapshot/vice/p500.rb +96 -0
  136. data/lib/badline/snapshot/vice/p500_restore.rb +130 -0
  137. data/lib/badline/snapshot/vice/p500_vicii.rb +166 -0
  138. data/lib/badline/snapshot/vice/pet.rb +121 -0
  139. data/lib/badline/snapshot/vice/pet_cpu.rb +70 -0
  140. data/lib/badline/snapshot/vice/pet_crtc.rb +241 -0
  141. data/lib/badline/snapshot/vice/pet_drive.rb +180 -0
  142. data/lib/badline/snapshot/vice/pet_drive8050.rb +216 -0
  143. data/lib/badline/snapshot/vice/pet_mem.rb +93 -0
  144. data/lib/badline/snapshot/vice/pet_pias.rb +56 -0
  145. data/lib/badline/snapshot/vice/pet_restore.rb +214 -0
  146. data/lib/badline/snapshot/vice/pet_via.rb +165 -0
  147. data/lib/badline/snapshot/vice/sid_registers.rb +2 -1
  148. data/lib/badline/snapshot/vice/vicii_import.rb +10 -6
  149. data/lib/badline/snapshot/vice.rb +4 -0
  150. data/lib/badline/snapshot.rb +11 -3
  151. data/lib/badline/storage/crt_file.rb +7 -1
  152. data/lib/badline/storage/d64_image.rb +7 -0
  153. data/lib/badline/storage/d80_image.rb +17 -1
  154. data/lib/badline/storage/d81_image.rb +14 -0
  155. data/lib/badline/storage/disk_image/bam.rb +18 -3
  156. data/lib/badline/storage/disk_image/formatting.rb +69 -0
  157. data/lib/badline/storage/disk_image/writing.rb +35 -0
  158. data/lib/badline/storage/disk_image.rb +2 -0
  159. data/lib/badline/storage.rb +1 -0
  160. data/lib/badline/support/cbm2_checkpoint.rb +48 -0
  161. data/lib/badline/support/p500_checkpoint.rb +35 -0
  162. data/lib/badline/version.rb +1 -1
  163. data/lib/badline.rb +1 -0
  164. metadata +87 -2
@@ -0,0 +1,233 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/chips/tpi/keyed_state"
4
+
5
+ module Badline
6
+ module Chips
7
+ # The MOS 6525 TPI (tri-port interface): three 8-bit ports, A, B and C,
8
+ # each with a direction register, a 1 making the pin an output. Its
9
+ # three address lines repeat the eight registers every 8 bytes:
10
+ #
11
+ # 0 - PRA
12
+ # 1 - PRB
13
+ # 2 - PRC, or in mode 1 the interrupt latches (bits 0-4) and the levels
14
+ # of IRQ, CA and CB (bits 5-7)
15
+ # 3 - DDRA
16
+ # 4 - DDRB
17
+ # 5 - DDRC, which in mode 1 is the interrupt mask, its bits 0-4 enabling
18
+ # I0-I4 with a 1, and reads back all eight bits written
19
+ # 6 - CR: bit 0 MC, the mode, bit 1 IP, the priority, bits 2-3 IE3 and
20
+ # IE4, the active edges of I3 and I4 (1 rising), bits 4-5 CA's mode
21
+ # and bits 6-7 CB's
22
+ # 7 - AIR, the active interrupt register, in mode 1
23
+ #
24
+ # A port reads its pins: an output's level, and what the peripheral
25
+ # lets through of an input, which floats high (read_a, read_b and
26
+ # read_c pull lines low, as a VIA's peripheral does).
27
+ #
28
+ # In mode 1 PC0-PC4 are the interrupt inputs I0-I4 (#interrupt_line).
29
+ # A falling edge on I0-I2, or the edge IE3 and IE4 pick on I3 and I4,
30
+ # sets the input's latch whether or not its mask bit is set. A write of
31
+ # 0 to a latch's bit of PRC clears it, and a 1 does nothing. PC5 is
32
+ # the IRQ output, open drain, low (#irq?) while the AIR holds a bit;
33
+ # PC6 is CA and PC7 CB (#ca?, #cb?).
34
+ #
35
+ # Without priority (IP 0), the AIR holds every unmasked latch that is
36
+ # set, and a read of it clears them and it. With priority (IP 1) the
37
+ # inputs rank I4 > I3 > I2 > I1 > I0, and the AIR holds the one highest
38
+ # unmasked latch that outranks the interrupt in service, the top of a
39
+ # five-level stack. A read of the AIR pushes it on the stack, clears it
40
+ # and its latch, and lets IRQ go; a write pops the stack, letting lower
41
+ # interrupts through again.
42
+ #
43
+ # CA: 00 set high by I3's active edge and low by a read of PRA, 01
44
+ # pulsed low by a read of PRA, 10 low, 11 high. CB: 00 set low by a
45
+ # write of PRB and high by I4's active edge, 01 pulsed low by a write of
46
+ # PRB, 10 low, 11 high. A pulse is shorter than a cycle, so CA and CB
47
+ # read high in mode 01.
48
+ #
49
+ # The rules are the MPS 6525 datasheet's (Commodore Semiconductor
50
+ # Group), pages 2-81 to 2-83.
51
+ class TPI
52
+ STACK_DEPTH = 5
53
+
54
+ attr_reader :peripheral, :latches, :control, :stack
55
+
56
+ def initialize(peripheral: nil)
57
+ @peripheral = peripheral
58
+ @lines = 0x1f
59
+ @stack = []
60
+ reset!
61
+ end
62
+
63
+ # The RES pin clears every register: mode 0, and every pin an input.
64
+ def reset!
65
+ @pra = @prb = @prc = 0
66
+ @ddra = @ddrb = @ddrc = 0
67
+ @control = 0
68
+ @latches = 0
69
+ @air = 0
70
+ @stack.clear
71
+ @ca = @cb = false
72
+ end
73
+
74
+ # The interrupt mask in mode 1: DDRC's bits 0-4.
75
+ def mask = @ddrc & 0x1f
76
+
77
+ # Whether IRQ is pulled low.
78
+ def irq? = @air != 0
79
+
80
+ # The levels of CA and CB in mode 1.
81
+ def ca? = @ca
82
+
83
+ def cb? = @cb
84
+
85
+ # The port lines as the chip drives them, inputs floating high.
86
+ def port_a_output = driven(@pra, @ddra)
87
+
88
+ def port_b_output = driven(@prb, @ddrb)
89
+
90
+ def port_c_output = mode1? ? pins_c_mode1 : driven(@prc, @ddrc)
91
+
92
+ # Something outside drives interrupt input +index+, I0 to I4, +high+
93
+ # or low. Its active edge sets its latch in mode 1.
94
+ def interrupt_line(index, high)
95
+ bit = 1 << index
96
+ was_high = @lines.anybits?(bit)
97
+ return if was_high == high
98
+
99
+ @lines = high ? @lines | bit : @lines & ~bit
100
+ return unless mode1? && high == rising_active?(index)
101
+
102
+ @latches |= bit
103
+ @ca = true if index == 3 && ca_mode.zero?
104
+ @cb = true if index == 4 && cb_mode.zero?
105
+ update_air
106
+ end
107
+
108
+ def peek(addr)
109
+ case addr & 0x07
110
+ when 0 then read_pra
111
+ when 1 then pins(@prb, @ddrb, @peripheral ? @peripheral.read_b(port_b_output) : 0xff)
112
+ when 2 then mode1? ? pins_c_mode1 : read_pins_c
113
+ when 3 then @ddra
114
+ when 4 then @ddrb
115
+ when 5 then @ddrc
116
+ when 6 then @control
117
+ else read_air
118
+ end
119
+ end
120
+
121
+ def poke(addr, value)
122
+ case addr & 0x07
123
+ when 0 then @pra = value
124
+ when 1 then write_prb(value)
125
+ when 2 then write_prc(value)
126
+ when 3 then @ddra = value
127
+ when 4 then @ddrb = value
128
+ when 5 then write_ddrc(value)
129
+ when 6 then write_control(value)
130
+ else pop_air
131
+ end
132
+ end
133
+
134
+ private
135
+
136
+ def mode1? = @control.anybits?(0x01)
137
+
138
+ def priority? = @control.anybits?(0x02)
139
+
140
+ def ca_mode = (@control >> 4) & 0x03
141
+
142
+ def cb_mode = (@control >> 6) & 0x03
143
+
144
+ # I3 and I4 latch on the edge IE3 and IE4 pick, the others falling.
145
+ def rising_active?(index)
146
+ case index
147
+ when 3 then @control.anybits?(0x04)
148
+ when 4 then @control.anybits?(0x08)
149
+ else false
150
+ end
151
+ end
152
+
153
+ def driven(register, direction) = (register & direction) | (~direction & 0xff)
154
+
155
+ def pins(register, direction, inputs) = (register & direction) | (inputs & ~direction & 0xff)
156
+
157
+ def read_pra
158
+ @ca = false if mode1? && ca_mode.zero?
159
+ pins(@pra, @ddra, @peripheral ? @peripheral.read_a(port_a_output) : 0xff)
160
+ end
161
+
162
+ def read_pins_c
163
+ pins(@prc, @ddrc, @peripheral ? @peripheral.read_c(port_c_output) : 0xff)
164
+ end
165
+
166
+ # The latches, IRQ's level, CA's and CB's.
167
+ def pins_c_mode1
168
+ @latches | (irq? ? 0 : 0x20) | (@ca ? 0x40 : 0) | (@cb ? 0x80 : 0)
169
+ end
170
+
171
+ def write_prb(value)
172
+ @prb = value
173
+ @cb = false if mode1? && cb_mode.zero?
174
+ end
175
+
176
+ def write_prc(value)
177
+ return @prc = value unless mode1?
178
+
179
+ @latches &= value | 0xe0
180
+ update_air
181
+ end
182
+
183
+ def write_ddrc(value)
184
+ @ddrc = value
185
+ update_air
186
+ end
187
+
188
+ def write_control(value)
189
+ @control = value
190
+ @ca = ca_mode.anybits?(0x01) unless ca_mode.zero?
191
+ @cb = cb_mode.anybits?(0x01) unless cb_mode.zero?
192
+ update_air
193
+ end
194
+
195
+ def read_air
196
+ value = @air
197
+ return value unless mode1?
198
+
199
+ @latches &= ~value
200
+ @air = 0
201
+ if priority?
202
+ @stack.shift if @stack.length == STACK_DEPTH
203
+ @stack << value
204
+ end
205
+ update_air
206
+ value
207
+ end
208
+
209
+ def pop_air
210
+ return unless mode1?
211
+
212
+ @stack.pop
213
+ update_air
214
+ end
215
+
216
+ # The AIR's bits from the latches, the mask and, with priority, the
217
+ # interrupt in service.
218
+ def update_air
219
+ pending = mode1? ? @latches & mask : 0
220
+ return @air = pending unless priority?
221
+
222
+ highest = highest_bit(pending)
223
+ @air = highest > (@stack.last || 0) ? highest : 0
224
+ end
225
+
226
+ def highest_bit(bits)
227
+ bit = 0x10
228
+ bit >>= 1 while bit.positive? && bits.nobits?(bit)
229
+ bit
230
+ end
231
+ end
232
+ end
233
+ end
@@ -10,6 +10,13 @@ module Badline
10
10
  # The machine's bus pushes in what the VIC sees through #connect,
11
11
  # #map_character_rom and #map whenever it changes. Until then the bank
12
12
  # has a CIA 2, RAM, colour RAM and character ROM of its own.
13
+ #
14
+ # The P500 wires the VIC another way (#map_video_bus): its phi1
15
+ # reads go to the character ROM while VICDOTSEL is high, and its phi2
16
+ # reads to the 1K static video matrix while STATVID is high, VA12
17
+ # high picking the matrix and VA12 low reading 0. Otherwise both go to
18
+ # the DRAM's 16K bank that VA14 and VA15 pick (#video_bus_lines). The
19
+ # colour nibbles come from the colour RAM in every phi2 read.
13
20
  class Bank
14
21
  include Addressable
15
22
 
@@ -30,6 +37,11 @@ module Badline
30
37
  map(Memory.new([], length: 2**16, start: 0))
31
38
  @lines = 0b11
32
39
  @swapped = false
40
+ @video_bus = false
41
+ @matrix = @ram
42
+ @statvid = false
43
+ @dotsel = false
44
+ @video_base = 0
33
45
  end
34
46
 
35
47
  # CIA 2, whose port A picks the bank, and the colour RAM, read from
@@ -57,9 +69,31 @@ module Badline
57
69
  @base = base
58
70
  @starts = BANK_STARTS.map { |start| base + start }.freeze
59
71
  end
60
- @phi1_ultimax = phi1_ultimax
61
- @ultimax = ultimax
62
72
  @romh = romh
73
+ @phi1_other = phi1_ultimax
74
+ @phi2_other = ultimax
75
+ end
76
+
77
+ # The P500's video bus: +dram+ is segment 0's 64K, +matrix+ the
78
+ # static video matrix, read from its start address on, and
79
+ # +color_ram+ the colour RAM, read from +color_base+ on. The
80
+ # character ROM is the one #map_character_rom gives, read from
81
+ # +base+ on.
82
+ def map_video_bus(dram, matrix, color_ram, color_base)
83
+ @ram = dram
84
+ @matrix = matrix
85
+ @color_ram = color_ram
86
+ @color_base = color_base
87
+ @video_bus = true
88
+ @phi1_other = true
89
+ @phi2_other = true
90
+ end
91
+
92
+ # STATVID, VICDOTSEL and VA14-VA15, as the P500's TPIs drive them.
93
+ def video_bus_lines(statvid, dotsel, bank)
94
+ @statvid = statvid
95
+ @dotsel = dotsel
96
+ @video_base = bank << 14
63
97
  end
64
98
 
65
99
  # On the 8565, a port A write that swaps the two bank lines, driving
@@ -74,7 +108,7 @@ module Badline
74
108
  end
75
109
 
76
110
  def peek(offset)
77
- return ultimax_peek(offset) if @phi1_ultimax
111
+ return phi1_other(offset) if @phi1_other
78
112
 
79
113
  bits = bank_switch_register
80
114
  if (bits & @shadow_mask) == 0b01 && (offset & 0xf000) == 0x1000
@@ -88,7 +122,13 @@ module Badline
88
122
  # memory configuration the CPU does in that half. No cartridge drives
89
123
  # Ultimax mode in the first half alone, so otherwise it reads as #peek.
90
124
  def peek_phi2(offset)
91
- @ultimax ? ultimax_peek(offset) : peek(offset)
125
+ @phi2_other ? phi2_other(offset) : peek(offset)
126
+ end
127
+
128
+ # A sprite's s-access in phi2: on the P500's video bus as the other
129
+ # phi2 reads, and otherwise as #peek.
130
+ def peek_sprite(offset)
131
+ @video_bus ? phi2_other(offset) : peek(offset)
92
132
  end
93
133
 
94
134
  def peek_color(offset)
@@ -101,7 +141,7 @@ module Badline
101
141
 
102
142
  # True if the VIC reads the character ROM at this offset.
103
143
  def character_rom?(offset)
104
- !@phi1_ultimax && (bank_switch_register & @shadow_mask) == 0b01 && (offset & 0xf000) == 0x1000
144
+ !@phi1_other && (bank_switch_register & @shadow_mask) == 0b01 && (offset & 0xf000) == 0x1000
105
145
  end
106
146
 
107
147
  def start
@@ -110,6 +150,21 @@ module Badline
110
150
 
111
151
  private
112
152
 
153
+ def phi1_other(offset)
154
+ return ultimax_peek(offset) unless @video_bus
155
+ return @character_rom.peek(@character_base + (offset & 0x0fff)) if @dotsel
156
+
157
+ @ram.peek(@video_base + offset)
158
+ end
159
+
160
+ def phi2_other(offset)
161
+ return ultimax_peek(offset) unless @video_bus
162
+ return @ram.peek(@video_base + offset) unless @statvid
163
+ return 0 if offset.nobits?(0x1000)
164
+
165
+ @matrix.peek(@matrix.start + (offset & 0x03ff))
166
+ end
167
+
113
168
  # In Ultimax mode the cartridge ROMH replaces the character ROM
114
169
  # shadow, visible at $3000-$3FFF of the window. The MAX is in
115
170
  # Ultimax mode with no cartridge too.
@@ -322,12 +322,13 @@ module Badline
322
322
  end
323
323
 
324
324
  # The three s-accesses step MC through the sprite's block, wrapping
325
- # within it.
325
+ # within it. The first and third fall in phi2 and the second in
326
+ # phi1 (Bank#peek_sprite).
326
327
  def row_bits(counter)
327
328
  base = pointer * 64
328
- (@bank.peek(base + counter) << 16) |
329
+ (@bank.peek_sprite(base + counter) << 16) |
329
330
  (@bank.peek(base + ((counter + 1) & MC_MASK)) << 8) |
330
- @bank.peek(base + ((counter + 2) & MC_MASK))
331
+ @bank.peek_sprite(base + ((counter + 2) & MC_MASK))
331
332
  end
332
333
 
333
334
  def pointer = @bank.peek(@registers.screen_base + 0x3f8 + index)
@@ -10,7 +10,8 @@ module Badline
10
10
  # bytes and how far it has played. A tape that isn't in leaves tape.* out. A datasette that
11
11
  # times the read line's level adds when in the countdown the line
12
12
  # rises, and whether a recording's low half wave is on the tape. The
13
- # sense and flag handlers don't fire on load.
13
+ # sense and flag handlers don't fire on load, and the running handler
14
+ # does when the load starts or stops the tape.
14
15
  module KeyedState
15
16
  def save_fields(out)
16
17
  out.bool("playing", @playing).bool("motor", @motor).int("countdown", @countdown)
@@ -33,6 +34,13 @@ module Badline
33
34
  @write_high = input.bool("write_high", @write_high)
34
35
  @rise_at = input.int("rise_at", @rise_at)
35
36
  @low_recorded = input.bool("low_recorded", @low_recorded)
37
+ load_tape(input)
38
+ update_running
39
+ end
40
+
41
+ private
42
+
43
+ def load_tape(input)
36
44
  bytes = input.bytes("tape.bytes", "".b).bytes
37
45
  return @tape = nil if bytes.empty?
38
46
 
@@ -14,7 +14,6 @@ module Badline
14
14
  # What inserting a tape raises on a machine without a cassette port.
15
15
  class Missing < ArgumentError; end
16
16
  attr_reader :tape
17
- attr_writer :motor
18
17
 
19
18
  def initialize(tape = nil, level: false)
20
19
  @tape = tape
@@ -30,6 +29,8 @@ module Badline
30
29
  @low_recorded = false
31
30
  @flag_handler = nil
32
31
  @sense_handler = nil
32
+ @running_handler = nil
33
+ @running = false
33
34
  end
34
35
 
35
36
  # Falling edges on the tape read line.
@@ -42,6 +43,17 @@ module Badline
42
43
  @sense_handler = handler
43
44
  end
44
45
 
46
+ # Whether the tape runs (#running?), each time that changes, a
47
+ # snapshot's load included.
48
+ def on_running_change(&handler)
49
+ @running_handler = handler
50
+ end
51
+
52
+ def motor=(on)
53
+ @motor = on
54
+ update_running
55
+ end
56
+
45
57
  # Whether the machine has a datasette at its cassette port. The SX-64
46
58
  # has none: its keys stay up and it takes no tape.
47
59
  def connected? = @connected
@@ -56,11 +68,13 @@ module Badline
56
68
 
57
69
  @tape = tape
58
70
  rewind
71
+ update_running
59
72
  end
60
73
 
61
74
  def eject
62
75
  stop!
63
76
  @tape = nil
77
+ update_running
64
78
  end
65
79
 
66
80
  def rewind
@@ -117,7 +131,8 @@ module Badline
117
131
 
118
132
  def motor? = @motor
119
133
 
120
- def running? = @playing && @motor && !@tape.nil?
134
+ # Whether the tape moves: a key down, the motor on and a tape in.
135
+ def running? = @running
121
136
 
122
137
  # The tape read line's level, on a datasette built with +level: true+.
123
138
  # Each pulse holds it low for its first half and high for its second,
@@ -131,7 +146,7 @@ module Badline
131
146
  end
132
147
 
133
148
  def cycle!
134
- return unless running?
149
+ return unless @running
135
150
  return @since += 1 if @recording
136
151
 
137
152
  next_pulse if @countdown.zero?
@@ -175,9 +190,18 @@ module Badline
175
190
 
176
191
  finish_recording unless down
177
192
  @playing = down
193
+ update_running
178
194
  @sense_handler&.call
179
195
  end
180
196
 
197
+ def update_running
198
+ running = @playing && @motor && !@tape.nil?
199
+ return if running == @running
200
+
201
+ @running = running
202
+ @running_handler&.call
203
+ end
204
+
181
205
  # Releasing the keys ends a recording, and writes the tape out.
182
206
  def finish_recording
183
207
  return unless @recording
@@ -8,9 +8,13 @@ module Badline
8
8
  # pulls it low. The PET is the controller and always on the bus, and
9
9
  # reads back every line it drives.
10
10
  #
11
- # No device plugs in yet, so the lines carry the controller's levels
12
- # alone: nobody answers a handshake, and the KERNAL reports DEVICE NOT
13
- # PRESENT for every device the traps don't serve.
11
+ # The controller pushes the lines it pulls whenever they may have moved
12
+ # (host_lines=), and the bus holds them for the drives, which take them
13
+ # a host cycle late. Each drive on the bus answers the lines it pulls
14
+ # (ieee_low_lines) while ATN is or isn't low, from its own outputs, which
15
+ # hold still while it sleeps. With no drive on the bus nobody answers a
16
+ # handshake, and the KERNAL reports DEVICE NOT PRESENT for every device
17
+ # the traps don't serve.
14
18
  class IEEEBus
15
19
  # The lines as bits, the data lines in the low byte, DIO1 in bit 0.
16
20
  DIO = 0x00ff
@@ -20,9 +24,51 @@ module Badline
20
24
  NDAC = 0x0800
21
25
  ATN = 0x1000
22
26
 
27
+ # The lines the controller last pushed.
28
+ attr_reader :host_lines
29
+
30
+ attr_reader :drives
31
+
32
+ def initialize
33
+ @drives = []
34
+ @host_lines = 0
35
+ end
36
+
37
+ # The controller pushes the lines it pulls, and the drives look at
38
+ # them again.
39
+ def host_lines=(lines)
40
+ @host_lines = lines
41
+ drives = @drives
42
+ i = 0
43
+ while i < drives.length
44
+ drives[i].host_written!
45
+ i += 1
46
+ end
47
+ end
48
+
49
+ def attach(drive)
50
+ @drives << drive unless @drives.include?(drive)
51
+ end
52
+
53
+ def detach(drive)
54
+ @drives.delete(drive)
55
+ end
56
+
23
57
  # The lines low on the bus while the controller pulls the lines in
24
58
  # +controller+ low.
25
- def low_lines(controller) = controller
59
+ def low_lines(controller)
60
+ low = controller
61
+ drives = @drives
62
+ return low if drives.empty?
63
+
64
+ atn = controller.anybits?(ATN)
65
+ i = 0
66
+ while i < drives.length
67
+ low |= drives[i].ieee_low_lines(atn)
68
+ i += 1
69
+ end
70
+ low
71
+ end
26
72
  end
27
73
  end
28
74
  end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Drives
5
+ class Model2031
6
+ # VIA 1's side of the IEEE-488 bus (Drives::IEEEBus), through a 75160
7
+ # for the data lines and a 75161 for the control lines (schematic
8
+ # 2031038, sheet 2):
9
+ #
10
+ # PA0-7 DIO1-8 PB4 T/R, high to talk
11
+ # PB0 ATN acknowledge (ATNA) PB5 HD SEL, unused
12
+ # PB1 NRFD PB6 DAV
13
+ # PB2 NDAC PB7 ATN in, high while ATN is low
14
+ # PB3 EOI CA1 ATN in, as PB7
15
+ #
16
+ # The ports read and drive the lines' levels, 0 for a low line. T/R
17
+ # turns the transceivers: talking, the drive drives the data lines,
18
+ # DAV and EOI, and reads NRFD and NDAC; listening, the other way round.
19
+ # EOI turns back to the bus while ATN is low. A pin the transceiver
20
+ # drives the bus from reads the drive's own side, high but for what
21
+ # the VIA or a jumper pulls low.
22
+ #
23
+ # An XOR of ATN in and ATNA drives two 7406s that pull NRFD and NDAC
24
+ # low on the bus, so the drive answers ATN before the DOS gets to it,
25
+ # and holds the bus until the DOS acknowledges. ATNA reaches the XOR
26
+ # only while PB0 is an output, and is low there otherwise, which
27
+ # leaves the bus alone while the DOS reads the jumpers.
28
+ #
29
+ # The device number jumpers are diodes from PB0 and PB1 to CA2: with
30
+ # CA2 low, a fitted diode pulls its line low, and the DOS reads the
31
+ # device number off the two at reset, 8 with both fitted. Lifting the
32
+ # one on PB0 adds 1, and the one on PB1 2.
33
+ #
34
+ # The drive takes the controller's lines a host cycle late, as the
35
+ # serial drives do (Engine::SerialLines): latch_host takes them at the
36
+ # end of each host cycle.
37
+ class IEEEPort
38
+ ATNA = 0x01
39
+ NRFD = 0x02
40
+ NDAC = 0x04
41
+ EOI = 0x08
42
+ TALK = 0x10
43
+ DAV = 0x40
44
+ ATN_IN = 0x80
45
+
46
+ attr_reader :bus
47
+
48
+ # The lines the controller pulled, as latch_host took them.
49
+ attr_reader :host
50
+
51
+ def initialize(device:, bus:)
52
+ raise ArgumentError, "a 2031 is device 8 to 11, not #{device}" unless device.between?(8, 11)
53
+
54
+ @diodes = ~(device - 8) & 0x03
55
+ @via = nil
56
+ @host = 0
57
+ self.bus = bus
58
+ end
59
+
60
+ # The drive's VIA 1, whose outputs it drives the bus with.
61
+ attr_writer :via
62
+
63
+ def bus=(bus)
64
+ @bus = bus
65
+ latch_host
66
+ end
67
+
68
+ # Takes the controller's lines for the drive cycles of the next host
69
+ # cycle.
70
+ def latch_host
71
+ @host = @bus.host_lines
72
+ end
73
+
74
+ # Whether the controller has moved ATN since the last latch_host.
75
+ def atn_moved? = (@bus.host_lines ^ @host).anybits?(IEEEBus::ATN)
76
+
77
+ def atn_low? = @host.anybits?(IEEEBus::ATN)
78
+
79
+ # The data lines, while the transceiver listens.
80
+ def read_a(_lines) = talking? ? 0xff : ~@bus.low_lines(@host) & 0xff
81
+
82
+ # The control lines the transceiver turns towards the drive, and
83
+ # ATN.
84
+ def read_b(_lines)
85
+ low = @bus.low_lines(@host)
86
+ value = 0x7f
87
+ if talking?
88
+ value &= ~NRFD if low.anybits?(IEEEBus::NRFD)
89
+ value &= ~NDAC if low.anybits?(IEEEBus::NDAC)
90
+ value &= ~EOI if low.anybits?(IEEEBus::EOI) && low.anybits?(IEEEBus::ATN)
91
+ else
92
+ value &= ~EOI if low.anybits?(IEEEBus::EOI)
93
+ value &= ~DAV if low.anybits?(IEEEBus::DAV)
94
+ end
95
+ value |= ATN_IN if low.anybits?(IEEEBus::ATN)
96
+ value & jumpers
97
+ end
98
+
99
+ # The lines the drive pulls low while ATN is low or not, as bus bits.
100
+ def ieee_low_lines(atn)
101
+ via = @via
102
+ lines = via.port_b_output & jumpers
103
+ low = 0
104
+ if lines.anybits?(TALK)
105
+ low |= ~via.port_a_output & IEEEBus::DIO
106
+ low |= IEEEBus::DAV if lines.nobits?(DAV)
107
+ low |= IEEEBus::EOI if lines.nobits?(EOI) && !atn
108
+ else
109
+ low |= IEEEBus::NRFD if lines.nobits?(NRFD)
110
+ low |= IEEEBus::NDAC if lines.nobits?(NDAC)
111
+ end
112
+ low |= IEEEBus::NRFD | IEEEBus::NDAC if atn != (lines & via.port_b_directions).anybits?(ATNA)
113
+ low
114
+ end
115
+
116
+ # The controller's lines as the port latched them, under `prefix`.
117
+ def save_fields(out, prefix) = out.int("#{prefix}host", @host)
118
+
119
+ def load_fields(input, prefix)
120
+ @host = input.int("#{prefix}host", @host)
121
+ end
122
+
123
+ private
124
+
125
+ def talking? = @via.port_b_output.anybits?(TALK)
126
+
127
+ # PB0 and PB1 as the jumper diodes leave them: pulled low through
128
+ # each fitted one while CA2 is low.
129
+ def jumpers = @via.ca2_output ? 0xff : ~@diodes & 0xff
130
+ end
131
+ end
132
+ end
133
+ end