badline 0.17.0 → 0.19.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 (195) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +193 -0
  3. data/CLAUDE.md +19 -7
  4. data/CONTRIBUTING.md +11 -1
  5. data/README.md +178 -26
  6. data/doc/library.md +5 -1
  7. data/doc/media.md +3 -2
  8. data/doc/pinned-behaviour.md +169 -1
  9. data/doc/snapshot-format.md +302 -35
  10. data/doc/snapshots.md +97 -26
  11. data/doc/vice.md +344 -0
  12. data/exe/badline-ruby +5 -0
  13. data/lib/badline/audio/renderer.rb +4 -8
  14. data/lib/badline/audio/terminal.rb +34 -2
  15. data/lib/badline/c128/keyed_state.rb +2 -0
  16. data/lib/badline/c128.rb +5 -0
  17. data/lib/badline/c64/bus/fittings.rb +6 -0
  18. data/lib/badline/c64/keyed_state.rb +2 -0
  19. data/lib/badline/c64.rb +7 -0
  20. data/lib/badline/cbm2/keyed_state.rb +4 -0
  21. data/lib/badline/cbm2/p500.rb +9 -3
  22. data/lib/badline/cbm2.rb +10 -3
  23. data/lib/badline/chips/cia/timer.rb +2 -2
  24. data/lib/badline/chips/cpu/core.rb +29 -4
  25. data/lib/badline/chips/cpu/instruction_set.rb +1 -1
  26. data/lib/badline/chips/cpu/interrupts.rb +9 -0
  27. data/lib/badline/chips/cpu/keyed_state.rb +15 -9
  28. data/lib/badline/chips/cpu/microcode.rb +10 -0
  29. data/lib/badline/chips/m6809/arithmetic.rb +140 -0
  30. data/lib/badline/chips/m6809/bus_steps.rb +223 -0
  31. data/lib/badline/chips/m6809/core.rb +149 -0
  32. data/lib/badline/chips/m6809/indexed.rb +82 -0
  33. data/lib/badline/chips/m6809/interrupts.rb +159 -0
  34. data/lib/badline/chips/m6809/keyed_state.rb +147 -0
  35. data/lib/badline/chips/m6809/opcodes.rb +159 -0
  36. data/lib/badline/chips/m6809/operations.rb +258 -0
  37. data/lib/badline/chips/m6809/plans.rb +143 -0
  38. data/lib/badline/chips/m6809/registers.rb +82 -0
  39. data/lib/badline/chips/m6809/stack.rb +140 -0
  40. data/lib/badline/chips/m6809.rb +13 -0
  41. data/lib/badline/chips/riot/timer.rb +14 -3
  42. data/lib/badline/chips/rriot.rb +8 -3
  43. data/lib/badline/chips/sid/decimator.rb +1 -1
  44. data/lib/badline/chips/sid/filter.rb +4 -4
  45. data/lib/badline/chips/sid/keyed_parts.rb +51 -18
  46. data/lib/badline/chips/sid/waveform.rb +6 -6
  47. data/lib/badline/chips/sid.rb +6 -2
  48. data/lib/badline/chips/vic_i/keyed_parts.rb +12 -2
  49. data/lib/badline/chips/vic_i/light_pen.rb +89 -0
  50. data/lib/badline/chips/vic_i.rb +91 -23
  51. data/lib/badline/data_path.rb +3 -3
  52. data/lib/badline/drives/ieee_models.rb +54 -0
  53. data/lib/badline/drives/model1001.rb +37 -0
  54. data/lib/badline/drives/model1541/sector_reader.rb +20 -3
  55. data/lib/badline/drives/model1541.rb +1 -0
  56. data/lib/badline/drives/model1551.rb +1 -0
  57. data/lib/badline/drives/model1571.rb +1 -0
  58. data/lib/badline/drives/model1581.rb +1 -0
  59. data/lib/badline/drives/model2031.rb +7 -0
  60. data/lib/badline/drives/model4040/disk.rb +62 -0
  61. data/lib/badline/drives/model4040/ip_bus.rb +46 -0
  62. data/lib/badline/drives/model4040/keyed_state.rb +57 -0
  63. data/lib/badline/drives/model4040/mechanism.rb +195 -0
  64. data/lib/badline/drives/model4040/parts.rb +20 -0
  65. data/lib/badline/drives/model4040.rb +195 -0
  66. data/lib/badline/drives/model8050/board.rb +79 -0
  67. data/lib/badline/drives/model8050/dos_board.rb +134 -0
  68. data/lib/badline/drives/model8050/fdc_bus.rb +7 -0
  69. data/lib/badline/drives/model8050/idle.rb +148 -31
  70. data/lib/badline/drives/model8050/keyed_state.rb +5 -2
  71. data/lib/badline/drives/model8050.rb +32 -130
  72. data/lib/badline/drives/model9060/idle.rb +169 -0
  73. data/lib/badline/drives/model9060/keyed_state.rb +123 -0
  74. data/lib/badline/drives/model9060/parts.rb +98 -0
  75. data/lib/badline/drives/model9060/sasi_bus.rb +88 -0
  76. data/lib/badline/drives/model9060/sasi_commands.rb +168 -0
  77. data/lib/badline/drives/model9060/sasi_target.rb +161 -0
  78. data/lib/badline/drives/model9060.rb +197 -0
  79. data/lib/badline/frontend/app.rb +8 -0
  80. data/lib/badline/frontend/boot.rb +3 -2
  81. data/lib/badline/frontend/controls.rb +62 -6
  82. data/lib/badline/frontend/keys.rb +36 -0
  83. data/lib/badline/frontend/kim1_panel.rb +208 -0
  84. data/lib/badline/frontend/media_slots.rb +8 -1
  85. data/lib/badline/frontend/menu/drive_types.rb +56 -0
  86. data/lib/badline/frontend/menu/menu_dialogs.rb +61 -14
  87. data/lib/badline/frontend/menu/menu_media.rb +42 -12
  88. data/lib/badline/frontend/menu/menu_pages.rb +97 -2
  89. data/lib/badline/frontend/menu/menu_ports.rb +38 -1
  90. data/lib/badline/frontend/menu/pause_menu.rb +5 -1
  91. data/lib/badline/frontend/pet_keyboard.rb +23 -1
  92. data/lib/badline/frontend/screen.rb +1 -1
  93. data/lib/badline/frontend/screens.rb +40 -2
  94. data/lib/badline/frontend/teletype_pane.rb +152 -0
  95. data/lib/badline/frontend/timeline.rb +31 -2
  96. data/lib/badline/frontend.rb +3 -0
  97. data/lib/badline/input/joystick.rb +15 -2
  98. data/lib/badline/kim1/bus.rb +117 -0
  99. data/lib/badline/kim1/cpu.rb +25 -0
  100. data/lib/badline/kim1/host_terminal.rb +74 -0
  101. data/lib/badline/kim1/key_entry.rb +74 -0
  102. data/lib/badline/kim1/keyed_modules.rb +53 -0
  103. data/lib/badline/kim1/keyed_state.rb +133 -0
  104. data/lib/badline/kim1/panel.rb +176 -0
  105. data/lib/badline/kim1/rriots.rb +28 -0
  106. data/lib/badline/kim1/sound.rb +29 -0
  107. data/lib/badline/kim1/tape.rb +118 -0
  108. data/lib/badline/kim1/teletype.rb +193 -0
  109. data/lib/badline/kim1.rb +194 -0
  110. data/lib/badline/machine/ieee_drives.rb +94 -117
  111. data/lib/badline/machine/keyboard_buffer.rb +20 -3
  112. data/lib/badline/machine/line_sound.rb +122 -0
  113. data/lib/badline/machine.rb +49 -15
  114. data/lib/badline/media/extensions.rb +8 -4
  115. data/lib/badline/media/kim1_media.rb +150 -0
  116. data/lib/badline/media/pet_media.rb +3 -1
  117. data/lib/badline/media/true_drive.rb +52 -45
  118. data/lib/badline/media/vic20_media.rb +4 -2
  119. data/lib/badline/media.rb +16 -6
  120. data/lib/badline/memory.rb +2 -4
  121. data/lib/badline/options/family.rb +98 -8
  122. data/lib/badline/options/help.rb +44 -12
  123. data/lib/badline/options/table.rb +44 -12
  124. data/lib/badline/options/validation.rb +44 -8
  125. data/lib/badline/options.rb +27 -8
  126. data/lib/badline/pet/bus.rb +16 -2
  127. data/lib/badline/pet/keyboard_ports.rb +9 -2
  128. data/lib/badline/pet/keyed_state.rb +4 -0
  129. data/lib/badline/pet/model.rb +11 -2
  130. data/lib/badline/pet/rom_sets.rb +1 -0
  131. data/lib/badline/pet/sound.rb +5 -105
  132. data/lib/badline/pet/superpet/acia.rb +12 -0
  133. data/lib/badline/pet/superpet/board.rb +221 -0
  134. data/lib/badline/pet/superpet/dongle.rb +92 -0
  135. data/lib/badline/pet/superpet/keyed_modules.rb +63 -0
  136. data/lib/badline/pet/superpet/keyed_parts.rb +76 -0
  137. data/lib/badline/pet/superpet/m6809.rb +32 -0
  138. data/lib/badline/pet/superpet/waterloo_traps.rb +199 -0
  139. data/lib/badline/pet/superpet.rb +221 -0
  140. data/lib/badline/pet.rb +23 -3
  141. data/lib/badline/plus4/keyed_state.rb +2 -0
  142. data/lib/badline/plus4.rb +5 -0
  143. data/lib/badline/roms/1001/fdc-251257-02a.rom +0 -0
  144. data/lib/badline/roms/4040/dos-1.2.rom +0 -0
  145. data/lib/badline/roms/4040/dos-2.1-rev.rom +0 -0
  146. data/lib/badline/roms/4040/dos-2.1.rom +0 -0
  147. data/lib/badline/roms/4040/fdc-901466-02.rom +0 -0
  148. data/lib/badline/roms/4040/fdc-901466-04.rom +0 -0
  149. data/lib/badline/roms/9060/dos-3.0.rom +0 -0
  150. data/lib/badline/roms/9060/sasi-300515-revb.rom +0 -0
  151. data/lib/badline/roms/README +176 -0
  152. data/lib/badline/roms/kim1/6530-002.rom +0 -0
  153. data/lib/badline/roms/kim1/6530-003.rom +0 -0
  154. data/lib/badline/roms/pet/characters-superpet.rom +0 -0
  155. data/lib/badline/roms/pet/superpet-a000.rom +0 -0
  156. data/lib/badline/roms/pet/superpet-c000.rom +0 -0
  157. data/lib/badline/roms/pet/superpet-e000-50hz.rom +0 -0
  158. data/lib/badline/roms/pet/superpet-e000.rom +0 -0
  159. data/lib/badline/snapshot/container.rb +8 -4
  160. data/lib/badline/snapshot/field_doc/markdown.rb +1 -1
  161. data/lib/badline/snapshot/field_doc.rb +35 -10
  162. data/lib/badline/snapshot/image.rb +19 -7
  163. data/lib/badline/snapshot/kim1_setup.rb +28 -0
  164. data/lib/badline/snapshot/pet_setup.rb +27 -11
  165. data/lib/badline/snapshot/storage_setup.rb +4 -1
  166. data/lib/badline/snapshot/vic20_setup.rb +11 -7
  167. data/lib/badline/snapshot/vice/pet.rb +14 -7
  168. data/lib/badline/snapshot/vice/pet_cpu.rb +39 -5
  169. data/lib/badline/snapshot/vice/pet_crtc.rb +15 -2
  170. data/lib/badline/snapshot/vice/pet_dongle.rb +72 -0
  171. data/lib/badline/snapshot/vice/pet_drive8050.rb +188 -68
  172. data/lib/badline/snapshot/vice/pet_mem.rb +55 -5
  173. data/lib/badline/snapshot/vice/pet_restore.rb +40 -11
  174. data/lib/badline/snapshot/vice/vic20.rb +70 -0
  175. data/lib/badline/snapshot/vice/vic20_cpu.rb +61 -0
  176. data/lib/badline/snapshot/vice/vic20_mem.rb +85 -0
  177. data/lib/badline/snapshot/vice/vic20_restore.rb +124 -0
  178. data/lib/badline/snapshot/vice/vic20_vici.rb +200 -0
  179. data/lib/badline/snapshot/vice.rb +1 -0
  180. data/lib/badline/snapshot.rb +8 -4
  181. data/lib/badline/status.rb +30 -30
  182. data/lib/badline/storage/d67_image.rb +55 -0
  183. data/lib/badline/storage/d90_image.rb +180 -0
  184. data/lib/badline/storage/kim_tape.rb +254 -0
  185. data/lib/badline/storage/papertape.rb +90 -0
  186. data/lib/badline/storage.rb +4 -0
  187. data/lib/badline/support/kim1_checkpoint.rb +41 -0
  188. data/lib/badline/support/superpet_checkpoint.rb +35 -0
  189. data/lib/badline/version.rb +1 -1
  190. data/lib/badline/vic20/keyed_modules.rb +6 -3
  191. data/lib/badline/vic20/keyed_state.rb +6 -4
  192. data/lib/badline/vic20/port_wiring.rb +11 -0
  193. data/lib/badline/vic20.rb +30 -12
  194. data/lib/badline.rb +1 -0
  195. metadata +85 -1
@@ -31,6 +31,10 @@ module Badline
31
31
  # last cleared (Model8050::Idle).
32
32
  attr_accessor :volatile
33
33
 
34
+ # The bytes of the 6530's RAM read or written since it was last
35
+ # cleared, a bit for each pair of them (Model8050::Idle).
36
+ attr_accessor :ram_touched
37
+
34
38
  def initialize(common:, via:, rriot:)
35
39
  addressable_at(0, length: 2**16)
36
40
  @common = common
@@ -38,6 +42,7 @@ module Badline
38
42
  @rriot = rriot
39
43
  @data = 0
40
44
  @volatile = false
45
+ @ram_touched = 0
41
46
  @common_changes = 0
42
47
  end
43
48
 
@@ -78,6 +83,7 @@ module Badline
78
83
  end
79
84
  return peek_via(addr & 0x0f) if addr.anybits?(0x40)
80
85
 
86
+ @ram_touched |= 1 << ((addr & 0x3f) >> 1)
81
87
  @rriot.read_ram(addr)
82
88
  end
83
89
 
@@ -93,6 +99,7 @@ module Badline
93
99
  elsif addr.anybits?(0x40)
94
100
  @via.poke(0x40 | (addr & 0x0f), value)
95
101
  else
102
+ @ram_touched |= 1 << ((addr & 0x3f) >> 1)
96
103
  @rriot.write_ram(addr, value)
97
104
  end
98
105
  end
@@ -5,10 +5,11 @@ module Badline
5
5
  class Model8050
6
6
  # Sleeping through the two processors' idle loops, exactly.
7
7
  #
8
- # With nothing to do, the IP goes round DOS 2.7's idle loop from
9
- # IP_LOOP, and the FDC round its job loop from FDC_LOOP, each pass
10
- # finding the job queue empty, until ATN interrupts the IP or the
11
- # 6530's timer the FDC, every 5 ms. A pass puts back what it found,
8
+ # With nothing to do, the IP goes round its DOS's idle loop from
9
+ # @ip_loop, and the FDC round its job loop from @fdc_loop (IP_LOOP and
10
+ # FDC_LOOP on the 8050), each pass finding the job queue empty, until
11
+ # ATN interrupts the IP or the 6530's timer the FDC, every 5 ms on the
12
+ # 8050. A pass puts back what it found,
12
13
  # so each processor comes back to its loop's start as it left it, and
13
14
  # only the chips' counters and the cycle counts move.
14
15
  #
@@ -20,10 +21,10 @@ module Badline
20
21
  # volatile), makes the pass repeatable. Once both processors have a
21
22
  # repeatable pass, with the motors off, no head stepping, no disk
22
23
  # going in or out and no interrupt pulled, the unit sleeps:
23
- # host_cycle! only counts the cycles it owes, until one of the chips'
24
- # counters is due to pull IRQ (quiet_cycles), the controller moves a
25
- # line of the bus, or something outside reads or changes the unit
26
- # (settle!).
24
+ # host_cycle! only counts host cycles, until the one in which a chip's
25
+ # counter is due to pull IRQ (quiet_cycles), until the controller
26
+ # moves a line of the bus (host_written!), or until something outside
27
+ # reads or changes the unit (settle!).
27
28
  #
28
29
  # Waking runs the counters on in bulk, counts off each processor's
29
30
  # whole passes, and runs the rest of a pass on that processor alone.
@@ -31,8 +32,21 @@ module Badline
31
32
  # order the two run in doesn't matter. Then the unit runs cycle by
32
33
  # cycle again. Woken by a counter, it keeps the passes it found, and
33
34
  # sleeps again as soon as both repeat: the IP's, which the timer's
34
- # interrupt doesn't reach, at once, and the FDC's a pass after its
35
- # interrupt. Woken from outside, it finds both afresh.
35
+ # interrupt doesn't reach, at once, and the FDC's once it is back at
36
+ # its loop's start where its last pass proved to repeat but for the
37
+ # bytes the interrupt changed (fdc_pass_found). Woken from outside, it
38
+ # finds both afresh.
39
+ #
40
+ # Woken by a counter on the FDC's side, as the 6530's timer wakes the
41
+ # controller every 5 ms, the unit wakes only the FDC's side, as the
42
+ # board does: the FDC with its VIA, its 6530 and the drives run cycle
43
+ # by cycle (fdc_step) through the interrupt, and the IP owes its
44
+ # cycles. The two sides meet only in the common RAM, and the IP's
45
+ # pass reads none of the FDC's chips, so while no write changes the
46
+ # common RAM, the IP's pass runs on as it did. The IP catches up on its
47
+ # passes as the unit falls asleep again, as soon as a write changes
48
+ # the common RAM, before a counter on its side is due to pull its IRQ,
49
+ # and when something outside reads or changes the unit.
36
50
  module Idle
37
51
  IP_LOOP = 0xc5a1
38
52
  FDC_LOOP = 0xfc17
@@ -47,20 +61,37 @@ module Badline
47
61
 
48
62
  def asleep? = @asleep
49
63
 
64
+ # Whether the unit, or the IP's side of it, owes cycles.
65
+ def dozing? = @asleep || @fdc_alone
66
+
50
67
  # Brings the unit up to the present, cycle for cycle, and drops the
51
68
  # passes it found, since whoever called may change it.
52
69
  def settle!
53
- wake! if @asleep
70
+ if @asleep
71
+ wake!
72
+ @asleep = false
73
+ end
74
+ join_ip if @fdc_alone
54
75
  forget_passes
55
76
  end
56
77
 
57
78
  private
58
79
 
59
- def init_idle
80
+ # The unit's loops start at +ip_loop+ and +fdc_loop+.
81
+ def init_idle(ip_loop, fdc_loop)
82
+ @ip_loop = ip_loop
83
+ @fdc_loop = fdc_loop
60
84
  @idle_skip = true
61
85
  @asleep = false
62
86
  @owed = 0
63
87
  @budget = 0
88
+ @fdc_alone = false
89
+ @ip_owed = 0
90
+ @ip_budget = 0
91
+ @alone_changes = 0
92
+ @planned = false
93
+ @slept = 0
94
+ @wake_at = 0
64
95
  forget_passes
65
96
  end
66
97
 
@@ -69,13 +100,53 @@ module Badline
69
100
  @fdc_mark = nil
70
101
  @ip_pass = nil
71
102
  @fdc_pass = nil
103
+ @fdc_proof = nil
72
104
  end
73
105
 
74
106
  # A cycle owed while asleep, which wakes the unit once the budget
75
- # is spent.
107
+ # is spent: the FDC's side alone if the IP's counters leave it
108
+ # room.
76
109
  def owe_cycle
77
110
  @owed += 1
78
- wake! if @owed >= @budget
111
+ return if @owed < @budget
112
+
113
+ wake!
114
+ wake_fdc unless @asleep
115
+ end
116
+
117
+ # The FDC's side runs on alone while the IP owes its cycles.
118
+ def wake_fdc
119
+ budget = [@data_riot.quiet_cycles, @control_riot.quiet_cycles].min
120
+ return if budget < 2 || @ip_pass.nil? || @fdc_alone
121
+
122
+ @fdc_alone = true
123
+ @ip_owed = 0
124
+ @ip_budget = budget
125
+ @alone_changes = common_changes
126
+ end
127
+
128
+ # A cycle of the FDC's side alone, the IP owing it. A write that
129
+ # changed the common RAM, or the IP's counters coming due, has the
130
+ # IP catch up and the unit run whole again.
131
+ def fdc_alone_cycle
132
+ fdc_step
133
+ @ip_owed += 1
134
+ return join_ip if common_changes != @alone_changes || @ip_owed >= @ip_budget
135
+
136
+ fdc = @fdc_cpu
137
+ @fdc_pass = nil if fdc.irq
138
+ fdc_pass_found if fdc.program_counter == @fdc_loop && fdc.boundary?
139
+ end
140
+
141
+ # Runs the IP's side over the cycles it owes, its counters in bulk
142
+ # and its passes, and the unit whole from then on.
143
+ def join_ip
144
+ owed = @ip_owed
145
+ @fdc_alone = false
146
+ @ip_owed = 0
147
+ @data_riot.fast_forward(owed)
148
+ @control_riot.fast_forward(owed)
149
+ skip(@ip_cpu, @ip_pass, @ip_mark, owed)
79
150
  end
80
151
 
81
152
  # Looks for the loops' starts after a cycle run awake. An interrupt
@@ -84,9 +155,9 @@ module Badline
84
155
  ip = @ip_cpu
85
156
  @ip_pass = nil if ip.irq
86
157
  @fdc_pass = nil if @fdc_cpu.irq
87
- ip_pass_found if ip.program_counter == IP_LOOP && ip.boundary?
158
+ ip_pass_found if ip.program_counter == @ip_loop && ip.boundary?
88
159
  fdc = @fdc_cpu
89
- fdc_pass_found if fdc.program_counter == FDC_LOOP && fdc.boundary?
160
+ fdc_pass_found if fdc.program_counter == @fdc_loop && fdc.boundary?
90
161
  end
91
162
 
92
163
  def ip_pass_found
@@ -97,12 +168,44 @@ module Badline
97
168
  fall_asleep if @ip_pass && @fdc_pass
98
169
  end
99
170
 
171
+ # The FDC is at its loop's start. A pass that repeated is kept as a
172
+ # proof with the bytes of the 6530's RAM it touched: from a state
173
+ # that differs from the proof's only in other bytes, as the timer's
174
+ # interrupt leaves it, the pass runs the same way and repeats too.
100
175
  def fdc_pass_found
101
- state = fdc_state
102
- @fdc_pass = repeated(@fdc_mark, state, @fdc_cpu, @fdc_bus.volatile)
176
+ registers = fdc_state
177
+ ram = @rriot.ram.dup
178
+ state = [registers, ram]
179
+ bus = @fdc_bus
180
+ touched = bus.ram_touched
181
+ pass = repeated(@fdc_mark, state, @fdc_cpu, bus.volatile)
182
+ if pass
183
+ @fdc_proof = [registers, ram, touched, pass]
184
+ elsif proven?(registers, ram)
185
+ pass = @fdc_proof[3]
186
+ end
187
+ @fdc_pass = pass
103
188
  @fdc_mark = [state, @fdc_cpu.cycles, @fdc_cpu.instructions]
104
- @fdc_bus.volatile = false
105
- fall_asleep if @ip_pass && @fdc_pass
189
+ bus.volatile = false
190
+ bus.ram_touched = 0
191
+ fall_asleep if @ip_pass && pass
192
+ end
193
+
194
+ # Whether the FDC stands where the proof's pass started, but for
195
+ # bytes of the 6530's RAM the pass didn't touch.
196
+ def proven?(registers, ram)
197
+ proof = @fdc_proof
198
+ return false if proof.nil? || proof[0] != registers
199
+
200
+ start = proof[1]
201
+ touched = proof[2]
202
+ index = 0
203
+ while index < 64
204
+ return false if touched.anybits?(1 << (index >> 1)) && start[index] != ram[index]
205
+
206
+ index += 1
207
+ end
208
+ true
106
209
  end
107
210
 
108
211
  # The pass since +mark+ as [cycles, instructions] if it left the
@@ -114,6 +217,7 @@ module Badline
114
217
  end
115
218
 
116
219
  def fall_asleep
220
+ join_ip if @fdc_alone
117
221
  return unless @mechanism.quiet? && !@ip_cpu.irq && !@fdc_cpu.irq
118
222
 
119
223
  budget = quiet_cycles
@@ -132,18 +236,30 @@ module Badline
132
236
  # Runs the cycles owed: the counters in bulk, then each processor's
133
237
  # whole passes and the rest of a pass.
134
238
  def wake!
239
+ owe_slept
135
240
  owed = @owed
136
241
  @asleep = false
137
242
  @owed = 0
138
- return if owed.zero?
243
+ quiet = [owed, @budget].min
244
+ skip_passes(quiet) if quiet.positive?
245
+ (owed - quiet).times { @asleep ? owe_cycle : run_cycle }
246
+ end
139
247
 
140
- @data_riot.fast_forward(owed)
141
- @control_riot.fast_forward(owed)
142
- @rriot.fast_forward(owed)
143
- @via.fast_forward(owed)
144
- skip(@ip_cpu, @ip_pass, @ip_mark, owed)
145
- skip(@fdc_cpu, @fdc_pass, @fdc_mark, owed)
146
- @cycles += owed
248
+ def skip_passes(cycles)
249
+ @data_riot.fast_forward(cycles)
250
+ @control_riot.fast_forward(cycles)
251
+ @rriot.fast_forward(cycles)
252
+ @via.fast_forward(cycles)
253
+ skip(@ip_cpu, @ip_pass, @ip_mark, cycles)
254
+ skip(@fdc_cpu, @fdc_pass, @fdc_mark, cycles)
255
+ @cycles += cycles
256
+ end
257
+
258
+ # The budget is spent: the unit wakes, its FDC's side alone if it
259
+ # can.
260
+ def doze
261
+ wake!
262
+ wake_fdc unless @asleep
147
263
  end
148
264
 
149
265
  # Counts off the processor's whole passes, moving its mark on with
@@ -163,10 +279,11 @@ module Badline
163
279
  [*@ip_cpu.idle_state, *@data_riot.idle_state, *@control_riot.idle_state, @ip_bus.data, common_changes]
164
280
  end
165
281
 
166
- # What the FDC sees: its CPU, the VIA and the 6530 but for their
167
- # counters, its bus's last byte, the drives and the common RAM.
282
+ # What the FDC sees but the 6530's RAM: its CPU, the VIA and the
283
+ # 6530's ports and timer but for their counters, its bus's last
284
+ # byte, the drives and the common RAM.
168
285
  def fdc_state
169
- [*@fdc_cpu.idle_state, *@via.idle_state, *@rriot.idle_state, @fdc_bus.data, *@mechanism.idle_state,
286
+ [*@fdc_cpu.idle_state, *@via.idle_state, *@rriot.port_state, @fdc_bus.data, *@mechanism.idle_state,
170
287
  common_changes]
171
288
  end
172
289
 
@@ -16,7 +16,7 @@ module Badline
16
16
  def save_fields(out)
17
17
  image = @image
18
18
  out.text("path", image.path.to_s).bool("read_only", image.read_only?)
19
- out.text("image.format", image.is_a?(Storage::D82Image) ? "d82" : "d80")
19
+ out.text("image.format", image_format)
20
20
  out.bytes("image", image.contents)
21
21
  @tracks.each do |number, cells|
22
22
  out.ints("track#{number}", cells) unless cells == build(number)
@@ -37,6 +37,9 @@ module Badline
37
37
 
38
38
  include KeyedState
39
39
 
40
+ # The kind of image the disk lays out, as image.format names it.
41
+ def image_format = @image.is_a?(Storage::D82Image) ? "d82" : "d80"
42
+
40
43
  # The disk a BL-D8-DISK module holds. A detached reader gets the
41
44
  # image write-protected, and it and a host file that is gone leave
42
45
  # it no path to write back to.
@@ -141,7 +144,7 @@ module Badline
141
144
  @rriot.load_fields(modules.required("#{name}-RRIOT"))
142
145
  disks = [0, 1].map do |drive|
143
146
  disk = modules.optional(disk_module(name, drive))
144
- disk.nil? ? nil : Disk.from_fields(disk, detached: modules.detached?)
147
+ disk.nil? ? nil : disk_from_fields(disk, modules.detached?)
145
148
  end
146
149
  @mechanism.load_fields(modules.required("#{name}-MECH"), disks)
147
150
  @via.restored!
@@ -10,6 +10,8 @@ require "badline/drives/model8050/parts"
10
10
  require "badline/drives/model8050/ieee_port"
11
11
  require "badline/drives/model8050/disk"
12
12
  require "badline/drives/model8050/mechanism"
13
+ require "badline/drives/model8050/dos_board"
14
+ require "badline/drives/model8050/board"
13
15
  require "badline/drives/model8050/idle"
14
16
  require "badline/drives/model8050/keyed_state"
15
17
 
@@ -18,25 +20,21 @@ module Badline
18
20
  # The 8050 dual disk drive of the PET and CBM-II, two drives on one
19
21
  # IEEE-488 unit (schematic 8050001; the 8050/8250 service manual).
20
22
  #
21
- # Two 6502s share the board, clocked at 1 MHz from one 12 MHz crystal
22
- # on opposite phases. The interface processor (IP) runs DOS 2.7 from
23
- # 16 KB of ROM at $C000, with two 6532s (IPBus): their RAM is its zero
24
- # page and stack, and their ports face the IEEE-488 bus (IEEEPort). The
25
- # floppy controller (FDC) runs the 1 KB program in its 6530's ROM, with
26
- # a VIA and the 6530 facing the drives (FDCBus, Mechanism). The two
27
- # meet in 4 KB of common RAM, which each reaches in its own half of
28
- # the cycle: the IP leaves jobs there, and the FDC runs them.
23
+ # Two 6502s share the board (Board), clocked at 1 MHz from one 12 MHz
24
+ # crystal on opposite phases. The interface processor (IP) runs DOS 2.7
25
+ # from 16 KB of ROM at $C000, with two 6532s (IPBus): their RAM is its
26
+ # zero page and stack, and their ports face the IEEE-488 bus
27
+ # (IEEEPort). The floppy controller (FDC) runs the 1 KB program in its
28
+ # 6530's ROM, with a VIA and the 6530 facing the drives (FDCBus,
29
+ # Mechanism). The two meet in 4 KB of common RAM, which each reaches in
30
+ # its own half of the cycle: the IP leaves jobs there, and the FDC runs
31
+ # them.
29
32
  #
30
33
  # Reset starts both. The FDC waits on the common RAM for the IP, which
31
34
  # tests it, copies the FDC's start-up code into it ($C370) and waits for
32
35
  # the FDC to answer, before both settle into their idle loops.
33
- #
34
- # The drive clocks against the host through a phase accumulator, as
35
- # Drives::Engine::Core does: each host cycle adds 1 MHz of phase, and
36
- # every whole host period in it runs a cycle of both processors. While
37
- # both processors idle, it sleeps instead (Idle), and the readers of its
38
- # parts catch it up first.
39
36
  class Model8050
37
+ include Board
40
38
  include Idle
41
39
 
42
40
  CLOCK_HZ = 1_000_000
@@ -45,37 +43,10 @@ module Badline
45
43
  # run the IP's RAM test and the FDC's start-up and reads its jumpers.
46
44
  BOOT_CYCLES = 1_500_000
47
45
 
48
- attr_reader :device, :ieee_bus
49
-
50
- def ip_cpu = settled(@ip_cpu)
51
-
52
- def fdc_cpu = settled(@fdc_cpu)
53
-
54
- def ip_bus = settled(@ip_bus)
55
-
56
- def fdc_bus = settled(@fdc_bus)
57
-
58
- def common = settled(@common)
59
-
60
- def data_riot = settled(@data_riot)
61
-
62
- def control_riot = settled(@control_riot)
63
-
64
- def via = settled(@via)
65
-
66
- def rriot = settled(@rriot)
67
-
68
- def mechanism = settled(@mechanism)
69
-
70
- def cycles
71
- settle!
72
- @cycles
73
- end
74
-
75
- attr_writer :host_clock_hz
76
-
77
46
  def model_name = "8050"
78
47
 
48
+ def boot_cycles = BOOT_CYCLES
49
+
79
50
  # The sides each drive's disk has.
80
51
  def sides = 1
81
52
 
@@ -90,40 +61,13 @@ module Badline
90
61
  build_ip(dos_rom || ROM.load("8050/dos-2.7.rom", 0xc000))
91
62
  build_fdc(fdc_rom || ROM.read("8050/fdc-901869-01.rom"))
92
63
  @ip_cpu = IPCPU.new(@ip_bus)
64
+ @ip_cpu.power_on!
93
65
  @fdc_cpu = FDCCPU.new(@fdc_bus)
94
- init_idle
66
+ @fdc_cpu.power_on!
67
+ init_idle(Idle::IP_LOOP, Idle::FDC_LOOP)
95
68
  @ieee_bus.attach(self)
96
69
  end
97
70
 
98
- # Plugs the unit into an IEEE-488 bus, leaving the one it was on.
99
- def connect(ieee_bus)
100
- settle!
101
- @ieee_bus.detach(self)
102
- @ieee_bus = ieee_bus
103
- @ieee_port.bus = ieee_bus
104
- ieee_bus.attach(self)
105
- end
106
-
107
- # The lines the unit pulls on the bus while ATN is low or not
108
- # (IEEEPort).
109
- def ieee_low_lines(atn) = @ieee_port.ieee_low_lines(atn)
110
-
111
- # The controller pushed its lines into the bus.
112
- def host_written! = nil
113
-
114
- # Which LEDs are lit: bit 0 drive 1's, bit 1 drive 0's, bit 2 the
115
- # error LED. They hold still while the unit sleeps, so reading them
116
- # leaves it asleep.
117
- def leds = @ieee_port.leds
118
-
119
- def led_on? = leds.anybits?(0x03)
120
-
121
- # Puts a Disk in +drive+, 0 or 1. Nil takes the disk out.
122
- def insert(disk, drive: 0)
123
- settle!
124
- @mechanism.insert(disk, drive)
125
- end
126
-
127
71
  # Puts in the .d80 or .d82 image at +path+, write-protected with
128
72
  # `read_only`.
129
73
  def insert_image(path, drive: 0, read_only: false)
@@ -131,65 +75,8 @@ module Badline
131
75
  insert(Disk.new(image_class.new(path, read_only:)), drive:)
132
76
  end
133
77
 
134
- # The Disk in +drive+, or nil.
135
- def disk(drive = 0) = @mechanism.disk(drive)
136
-
137
- # Stores what the heads wrote in the disks' images, as the motors
138
- # stopping do.
139
- def flush
140
- settle!
141
- @mechanism.flush
142
- end
143
-
144
- def power_led_on? = true
145
-
146
- # Runs the drive cycles that fall in one host cycle, then takes the
147
- # controller's lines for the next. Asleep, the unit owes the cycles
148
- # instead, and wakes once the controller has moved a line.
149
- def host_cycle!
150
- phase = @phase + CLOCK_HZ
151
- while phase >= @host_clock_hz
152
- @asleep ? owe_cycle : run_cycle
153
- phase -= @host_clock_hz
154
- end
155
- @phase = phase
156
- settle! if @asleep && @ieee_port.lines_moved?
157
- @ieee_port.latch_host
158
- end
159
-
160
- def cycle!
161
- settle!
162
- run_cycle
163
- end
164
-
165
- # The bus's IFC line resets both processors and every chip. RAM keeps
166
- # its contents.
167
- def reset!
168
- settle!
169
- @data_riot.reset!
170
- @control_riot.reset!
171
- @via.reset!
172
- @rriot.reset!
173
- @ip_cpu.reset!
174
- @fdc_cpu.reset!
175
- end
176
-
177
- def inspect
178
- "#<#{self.class.name} cycles=#{@cycles} ip=(#{@ip_cpu.inspect}) fdc=(#{@fdc_cpu.inspect})>"
179
- end
180
-
181
78
  private
182
79
 
183
- def settled(part)
184
- settle!
185
- part
186
- end
187
-
188
- def run_cycle
189
- step
190
- watch_loops if @idle_skip
191
- end
192
-
193
80
  def build_ip(rom)
194
81
  @common = Memory.new([], length: 0x1000)
195
82
  @ieee_bus = IEEEBus.new
@@ -210,6 +97,9 @@ module Badline
210
97
  @mechanism.rriot = @rriot
211
98
  end
212
99
 
100
+ # The disk a BL-D8-DISK module holds (KeyedState).
101
+ def disk_from_fields(input, detached) = Disk.from_fields(input, detached:)
102
+
213
103
  # One cycle of the board: the drives, then the chips, then the IP and
214
104
  # the FDC, each in its own half of the cycle. ATN reaches UE1's PA7 as
215
105
  # the port latched it. UE1 pulls the IP's IRQ, and the VIA and the
@@ -230,6 +120,18 @@ module Badline
230
120
  @fdc_cpu.cycle!
231
121
  @cycles += 1
232
122
  end
123
+
124
+ # One cycle of the FDC's side alone, as step runs it (Idle).
125
+ def fdc_step
126
+ so = @mechanism.take_so
127
+ @mechanism.cycle!
128
+ @via.cycle!
129
+ @rriot.cycle!
130
+ @fdc_cpu.irq = @via.irq? || @rriot.irq?
131
+ @fdc_cpu.so! if so
132
+ @fdc_cpu.cycle!
133
+ @cycles += 1
134
+ end
233
135
  end
234
136
 
235
137
  # The 8250: the 8050's board driving double-sided mechanisms, 154 tracks