badline 0.11.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/CLAUDE.md +20 -3
  4. data/CONTRIBUTING.md +8 -3
  5. data/README.md +6 -2
  6. data/lib/badline/c128/drives.rb +95 -0
  7. data/lib/badline/c128/fast_serial.rb +62 -0
  8. data/lib/badline/c128/mmu.rb +15 -0
  9. data/lib/badline/c128/saved_state.rb +1 -0
  10. data/lib/badline/c128.rb +9 -7
  11. data/lib/badline/cia/fast_forward.rb +42 -0
  12. data/lib/badline/cia/serial.rb +12 -0
  13. data/lib/badline/cia/timer.rb +39 -0
  14. data/lib/badline/cia.rb +2 -0
  15. data/lib/badline/computer/attachments.rb +6 -0
  16. data/lib/badline/drive/core.rb +172 -0
  17. data/lib/badline/{drive1541 → drive}/idle.rb +18 -19
  18. data/lib/badline/drive/idle_cpu.rb +28 -0
  19. data/lib/badline/{drive1541 → drive}/orbit.rb +2 -1
  20. data/lib/badline/drive/serial_lines.rb +73 -0
  21. data/lib/badline/{drive1541 → drive}/sleep.rb +4 -3
  22. data/lib/badline/drive/watch.rb +150 -0
  23. data/lib/badline/drive.rb +9 -0
  24. data/lib/badline/drive1541/bus.rb +5 -129
  25. data/lib/badline/drive1541/byte_ready.rb +48 -0
  26. data/lib/badline/drive1541/cpu.rb +1 -19
  27. data/lib/badline/drive1541/disk.rb +77 -20
  28. data/lib/badline/drive1541/disk_state.rb +7 -2
  29. data/lib/badline/drive1541/disk_via.rb +3 -0
  30. data/lib/badline/drive1541/mechanism.rb +48 -14
  31. data/lib/badline/drive1541/mechanism_state.rb +5 -4
  32. data/lib/badline/drive1541/rotation.rb +5 -5
  33. data/lib/badline/drive1541/serial_port.rb +4 -60
  34. data/lib/badline/drive1541.rb +13 -172
  35. data/lib/badline/drive1571/bus.rb +111 -0
  36. data/lib/badline/drive1571/cpu.rb +11 -0
  37. data/lib/badline/drive1571/serial_port.rb +37 -0
  38. data/lib/badline/drive1571/serial_via.rb +25 -0
  39. data/lib/badline/drive1571/wd1770.rb +34 -0
  40. data/lib/badline/drive1571.rb +225 -0
  41. data/lib/badline/frontend/app.rb +1 -1
  42. data/lib/badline/frontend/drive_led.rb +1 -1
  43. data/lib/badline/frontend/menu_media.rb +3 -3
  44. data/lib/badline/frontend/menu_pages.rb +2 -1
  45. data/lib/badline/iec_bus.rb +54 -4
  46. data/lib/badline/machine.rb +7 -5
  47. data/lib/badline/media/true_drive.rb +26 -23
  48. data/lib/badline/options/help.rb +2 -1
  49. data/lib/badline/options/table.rb +1 -1
  50. data/lib/badline/roms/README +9 -0
  51. data/lib/badline/roms/dos1571.rom +0 -0
  52. data/lib/badline/storage/d64_image.rb +3 -0
  53. data/lib/badline/storage/d71_image.rb +9 -4
  54. data/lib/badline/storage/g64_image.rb +7 -2
  55. data/lib/badline/time_of_day.rb +9 -0
  56. data/lib/badline/version.rb +1 -1
  57. data/lib/badline/via/fast_forward.rb +3 -3
  58. data/lib/badline/vic20/attachments.rb +6 -0
  59. data/lib/badline/z80/arithmetic.rb +117 -0
  60. data/lib/badline/z80/bit_page.rb +55 -0
  61. data/lib/badline/z80/block_io.rb +58 -0
  62. data/lib/badline/z80/block_transfers.rb +59 -0
  63. data/lib/badline/z80/bus_cycles.rb +111 -0
  64. data/lib/badline/z80/core.rb +111 -0
  65. data/lib/badline/z80/exchanges.rb +50 -0
  66. data/lib/badline/z80/extended_page.rb +89 -0
  67. data/lib/badline/z80/flags.rb +37 -0
  68. data/lib/badline/z80/interrupt_acceptance.rb +46 -0
  69. data/lib/badline/z80/jumps.rb +82 -0
  70. data/lib/badline/z80/loads.rb +98 -0
  71. data/lib/badline/z80/main_page.rb +134 -0
  72. data/lib/badline/z80/registers.rb +120 -0
  73. data/lib/badline/z80/shifts.rb +64 -0
  74. data/lib/badline/z80.rb +11 -0
  75. metadata +36 -4
@@ -0,0 +1,225 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/drive1571/cpu"
4
+ require "badline/drive1571/wd1770"
5
+ require "badline/drive1571/serial_port"
6
+ require "badline/drive1571/serial_via"
7
+ require "badline/drive1571/bus"
8
+
9
+ module Badline
10
+ # The 1571 disk drive, the C128D's built-in one: the 1541's 6502, two
11
+ # VIAs and GCR mechanism, with a second head, a 6526 CIA for fast
12
+ # serial, a WD1770 for MFM disks and DOS 3.0 in 32 KB of ROM. Drive::Core
13
+ # clocks it against the host and sleeps through the DOS's idle loop.
14
+ #
15
+ # VIA 1's port A picks the clock, 1 or 2 MHz on PA5, and the head on
16
+ # PA2, and reads the track 0 sensor and BYTE READY (SerialPort). The
17
+ # DOS resets at 2 MHz and drops to 1 MHz as a 1541 would run, which is
18
+ # how it stays until a C128 asks for fast serial. The disk turns at the
19
+ # same speed either way (Drive1541::Mechanism#clock_hz=).
20
+ #
21
+ # The CIA sits at $4000, on the IRQ line with both VIAs. Only its serial
22
+ # port is wired: through buffers PA1 turns round, its CNT drives SRQ and
23
+ # its SP DATA, or it hears them. The drive pushes its pins into the bus
24
+ # as they move, and hears the host's as the host pushes them, pin by
25
+ # pin, so each byte goes through both shift registers bit by bit. The
26
+ # CIA takes no part in a pass of the idle loop: the drive sleeps only
27
+ # while the CIA is quiet (CIA#quiet?), and any access to it, or the
28
+ # host moving its fast serial pins, wakes the drive.
29
+ class Drive1571
30
+ include Drive::Core
31
+
32
+ CLOCK_HZ = 1_000_000
33
+ FAST_CLOCK_HZ = 2_000_000
34
+
35
+ # Where the DOS 3.0 idle loop starts over, as DOS 2.6's does.
36
+ IDLE_LOOP = 0xebff
37
+
38
+ # VIA 1's port A outputs.
39
+ FAST_SERIAL_OUT = 0x02
40
+ SIDE = 0x04
41
+ FAST = 0x20
42
+
43
+ # Whether PA1 turns the fast serial buffers outwards, the drive's CIA
44
+ # driving DATA and SRQ.
45
+ attr_reader :fast_serial_out
46
+
47
+ def cia
48
+ settle!
49
+ @cia
50
+ end
51
+
52
+ def fdc
53
+ settle!
54
+ @fdc
55
+ end
56
+
57
+ def model_name = "1571"
58
+
59
+ # +rom+ covers $8000-$FFFF, and defaults to the DOS image in the ROM
60
+ # path. +device+ is the number the jumpers on VIA 1's PB5 and PB6 set,
61
+ # 8 to 11.
62
+ def initialize(rom: nil, host_clock_hz: CLOCK_HZ, device: 8, debug: false)
63
+ @device = device
64
+ @clock_hz = CLOCK_HZ
65
+ @fast_serial_out = false
66
+ @fast_output = 0
67
+ @mechanism = Drive1541::Mechanism.new(slip: 2)
68
+ @serial_port = SerialPort.new(@mechanism, device:)
69
+ @via1 = SerialVIA.new(start: 0x1800, peripheral: @serial_port, drive: self)
70
+ @via2 = Drive1541::DiskVIA.new(start: 0x1c00, mechanism: @mechanism)
71
+ @cia = CIA.new(start: 0x4000)
72
+ @fdc = WD1770.new
73
+ @bus = Bus.new(rom: rom || ROM.load("dos1571.rom", 0x8000), via1: @via1, via2: @via2, cia: @cia, fdc: @fdc)
74
+ @cpu = CPU.new(@bus, debug:)
75
+ @idle_loop = IDLE_LOOP
76
+ @host_clock_hz = host_clock_hz
77
+ @phase = 0
78
+ @cycles = 0
79
+ @serial_bus = nil
80
+ init_idle(debug)
81
+ # CA1 powers up at the level of a released ATN, without an edge.
82
+ @via1.ca1 = false
83
+ @via1.reset!
84
+ connect(IECBus.new)
85
+ end
86
+
87
+ # The serial bus's RESET line reaches the CPU, both VIAs and the CIA.
88
+ # RAM keeps its contents.
89
+ def reset!
90
+ settle!
91
+ @via1.reset!
92
+ @via2.reset!
93
+ @cia.reset!
94
+ @cpu.reset!
95
+ end
96
+
97
+ # VIA 1 drives port A to +lines+: the side, the clock rate and the fast
98
+ # serial direction follow it.
99
+ def port_a_written(lines)
100
+ @mechanism.side = lines.anybits?(SIDE) ? 1 : 0
101
+ fast_serial_out = lines.anybits?(FAST_SERIAL_OUT)
102
+ turn_fast_serial(fast_serial_out) if fast_serial_out != @fast_serial_out
103
+ clock_hz = lines.anybits?(FAST) ? FAST_CLOCK_HZ : CLOCK_HZ
104
+ return if clock_hz == @clock_hz
105
+
106
+ @clock_hz = clock_hz
107
+ @mechanism.clock_hz = clock_hz
108
+ end
109
+
110
+ # Whether the drive runs at 2 MHz.
111
+ def fast? = @clock_hz == FAST_CLOCK_HZ
112
+
113
+ # VIA 1's port B, with the fast serial pins pulling DATA and SRQ
114
+ # (IECBus::DRIVE_FAST_DATA and DRIVE_FAST_SRQ) above it.
115
+ def serial_output = @via1.port_b_output | @fast_output
116
+
117
+ # The host moved its fast serial pins: the CIA hears SRQ on CNT and
118
+ # DATA on SP while PA1 turns the buffers inwards.
119
+ def fast_lines_moved
120
+ settle!
121
+ hear_fast_serial unless @fast_serial_out
122
+ end
123
+
124
+ # The drive's whole state, as the 1541's (Drive1541#save_state), with
125
+ # the CIA and the WD1770's registers. The clock rate, the side and the
126
+ # fast serial direction follow from VIA 1's port A.
127
+ def save_state(out)
128
+ settle!
129
+ out.marker("DRIVE1571")
130
+ out.int(@phase).int(@cycles).boolean(@mechanism.so_pending)
131
+ @serial_port.save_state(out)
132
+ @via1.save_state(out)
133
+ @via2.save_state(out)
134
+ @cia.save_state(out)
135
+ @fdc.save_state(out)
136
+ @bus.save_state(out)
137
+ @cpu.save_state(out)
138
+ @mechanism.save_state(out)
139
+ end
140
+
141
+ def load_state(input)
142
+ settle!
143
+ input.marker("DRIVE1571")
144
+ @phase = input.int
145
+ @cycles = input.int
146
+ @mechanism.so_pending = input.boolean?
147
+ @serial_port.load_state(input)
148
+ @via1.load_state(input)
149
+ @via2.load_state(input)
150
+ @cia.load_state(input)
151
+ @fdc.load_state(input)
152
+ @bus.load_state(input)
153
+ @cpu.load_state(input)
154
+ @mechanism.load_state(input)
155
+ @fast_serial_out = @via1.port_a_output.anybits?(FAST_SERIAL_OUT)
156
+ port_a_written(@via1.port_a_output)
157
+ push_fast_output(@fast_serial_out ? fast_pins : 0)
158
+ forget_orbits
159
+ end
160
+
161
+ private
162
+
163
+ # One drive cycle, as the 1541's (Drive1541#step), with the CIA
164
+ # clocked beside the VIAs and pulling IRQ with them.
165
+ def step
166
+ so = @mechanism.take_so
167
+ @mechanism.cycle!
168
+ @via1.ca1 = @serial_port.atn_low?
169
+ @via1.cycle!
170
+ @via2.cycle!
171
+ @cia.cycle!
172
+ drive_fast_serial if @fast_serial_out
173
+ @cpu.irq = @via1.irq? || @via2.irq? || @cia.interrupted?
174
+ @cpu.so! if so
175
+ @cpu.cycle!
176
+ @cycles += 1
177
+ end
178
+
179
+ # The VIAs' quiet cycles while the CIA is quiet, and none otherwise.
180
+ def quiet_cycles
181
+ @cia.quiet? ? [@via1.quiet_cycles, @via2.quiet_cycles].min : 0
182
+ end
183
+
184
+ def fast_forward_chips(cycles)
185
+ @cia.fast_forward(cycles)
186
+ end
187
+
188
+ # PA1 turned the buffers round: outwards the CIA's pins drive the
189
+ # lines, and inwards they let go and hear them.
190
+ def turn_fast_serial(out)
191
+ @fast_serial_out = out
192
+ return unless @serial_bus
193
+
194
+ push_fast_output(out ? fast_pins : 0)
195
+ hear_fast_serial unless out
196
+ end
197
+
198
+ def drive_fast_serial
199
+ low = fast_pins
200
+ push_fast_output(low) if low != @fast_output
201
+ end
202
+
203
+ # The lines the CIA's CNT and SP pull while its serial port drives
204
+ # them.
205
+ def fast_pins
206
+ serial = @cia.serial
207
+ return 0 unless serial.output?
208
+
209
+ low = serial.cnt ? 0 : IECBus::DRIVE_FAST_SRQ
210
+ serial.sp_out ? low : low | IECBus::DRIVE_FAST_DATA
211
+ end
212
+
213
+ def push_fast_output(low)
214
+ @fast_output = low
215
+ @serial_bus&.drives_fast_moved!
216
+ end
217
+
218
+ def hear_fast_serial
219
+ low = @serial_bus.low_lines
220
+ serial = @cia.serial
221
+ serial.cnt_in = low.nobits?(IECBus::SRQ)
222
+ serial.sp_in = low.nobits?(IECBus::DATA)
223
+ end
224
+ end
225
+ end
@@ -42,7 +42,7 @@ module Badline
42
42
  @pacer.start(@started)
43
43
  @menu.open? ? paused_frame : frame while @running
44
44
  @snapshots.finish
45
- @computer.drive1541&.flush
45
+ @computer.true_drive&.flush
46
46
  @gamepads.close
47
47
  @sound.close
48
48
  @window.close
@@ -14,7 +14,7 @@ module Badline
14
14
 
15
15
  # The LED of the machine's true drive, or nil without one.
16
16
  def self.for(computer)
17
- drive = computer.drive1541
17
+ drive = computer.true_drive
18
18
  drive ? new(drive) : nil
19
19
  end
20
20
 
@@ -28,7 +28,7 @@ module Badline
28
28
  # The expanded path of the disk in device 8, however it went in, or
29
29
  # an empty one.
30
30
  def disk_path
31
- drive = @computer.drive1541
31
+ drive = @computer.true_drive
32
32
  unless drive.nil?
33
33
  disk = drive.disk
34
34
  return disk.nil? ? "" : disk.path.to_s
@@ -39,7 +39,7 @@ module Badline
39
39
  end
40
40
 
41
41
  def inserted?
42
- drive = @computer.drive1541
42
+ drive = @computer.true_drive
43
43
  drive.nil? ? @computer.mounted? : !drive.disk.nil?
44
44
  end
45
45
 
@@ -73,7 +73,7 @@ module Badline
73
73
  end
74
74
 
75
75
  def eject_disk
76
- drive = @computer.drive1541
76
+ drive = @computer.true_drive
77
77
  drive.nil? ? @computer.unmount : drive.insert(nil)
78
78
  end
79
79
 
@@ -112,7 +112,8 @@ module Badline
112
112
  end
113
113
  toggle("WRITABLE", :writable, [%w[OFF ON], %i[protect unprotect], media.writable? ? 1 : 0])
114
114
  skip
115
- info("DEVICE", @computer.drive1541.nil? ? "FAST LOADING" : "1541, TRUE DRIVE")
115
+ drive = @computer.true_drive
116
+ info("DEVICE", drive.nil? ? "FAST LOADING" : "#{drive.model_name}, TRUE DRIVE")
116
117
  end
117
118
 
118
119
  def draw_datasette
@@ -16,6 +16,14 @@ module Badline
16
16
  # also pulls DATA while ATN IN differs from its ATN acknowledge on PB4
17
17
  # (an XOR gate), which answers ATN in hardware before the DOS gets to it.
18
18
  # SerialPort reads the lines back into VIA 1.
19
+ #
20
+ # Fast serial adds SRQ, which the C64 leaves alone. A C128 and a 1571
21
+ # clock bytes over it from a CIA's CNT, with the bits on DATA from its
22
+ # SP, through buffers each side turns towards the bus to send. A pin low
23
+ # pulls its line low. Each side pushes the lines it pulls when they
24
+ # move: the host through host_fast_lines=, which tells the drives
25
+ # (fast_lines_moved), and a drive through drives_fast_moved!, which
26
+ # tells the host (on_fast_change).
19
27
  class IECBus
20
28
  # CIA 2 port A
21
29
  HOST_ATN_OUT = 0x08
@@ -29,16 +37,56 @@ module Badline
29
37
  DRIVE_CLK_OUT = 0x08
30
38
  DRIVE_ATNA = 0x10
31
39
 
32
- # The lines pulled low, as ATN, CLK and DATA bits
40
+ # The lines pulled low, as ATN, CLK, DATA and SRQ bits
33
41
  ATN = 0x01
34
42
  CLK = 0x02
35
43
  DATA = 0x04
44
+ SRQ = 0x08
45
+
46
+ # A drive's serial_output bits above VIA 1's port B: its fast serial
47
+ # pins pulling DATA and SRQ.
48
+ DRIVE_FAST_DATA = 0x100
49
+ DRIVE_FAST_SRQ = 0x200
36
50
 
37
51
  attr_reader :drives, :host_lines
38
52
 
53
+ # The lines the host's fast serial pins pull, as DATA and SRQ bits.
54
+ attr_reader :host_fast_lines
55
+
39
56
  def initialize
40
57
  @drives = []
41
58
  @host_lines = 0
59
+ @host_fast_lines = 0
60
+ @drive_fast_lines = 0
61
+ @on_fast_change = nil
62
+ end
63
+
64
+ # The host's fast serial pins moved: +low+ is the lines they pull, as
65
+ # DATA and SRQ bits.
66
+ def host_fast_lines=(low)
67
+ return if low == @host_fast_lines
68
+
69
+ @host_fast_lines = low
70
+ @drives.each(&:fast_lines_moved)
71
+ end
72
+
73
+ # A drive's fast serial pins moved (serial_output's fast bits).
74
+ def drives_fast_moved!
75
+ low = 0
76
+ @drives.each do |drive|
77
+ output = drive.serial_output
78
+ low |= DATA if output.anybits?(DRIVE_FAST_DATA)
79
+ low |= SRQ if output.anybits?(DRIVE_FAST_SRQ)
80
+ end
81
+ return if low == @drive_fast_lines
82
+
83
+ @drive_fast_lines = low
84
+ @on_fast_change&.call
85
+ end
86
+
87
+ # Calls the block whenever the drives move their fast serial pins.
88
+ def on_fast_change(&block)
89
+ @on_fast_change = block
42
90
  end
43
91
 
44
92
  # The machine pushes the lines it pulls, as port A bits, whenever they
@@ -58,11 +106,13 @@ module Badline
58
106
  end
59
107
 
60
108
  # One pass over everything on the bus, for a reader that wants more
61
- # than one line. +host+ is the C64's port A lines as the reader sees
62
- # them, which a drive takes a cycle late (see Drive1541::SerialPort).
109
+ # than one line, SRQ included. +host+ is the C64's port A lines as the
110
+ # reader sees them, which a drive takes a cycle late (see
111
+ # Drive::SerialLines).
63
112
  def low_lines(host = @host_lines)
64
113
  atn = host.anybits?(HOST_ATN_OUT)
65
- low = atn ? ATN : 0
114
+ low = @host_fast_lines | @drive_fast_lines
115
+ low |= ATN if atn
66
116
  low |= CLK if host.anybits?(HOST_CLK_OUT)
67
117
  low |= DATA if host.anybits?(HOST_DATA_OUT)
68
118
  i = 0
@@ -24,7 +24,8 @@ module Badline
24
24
  # - Media: +ram+, +load_prg+, +type_text+, +mount+, +unmount+,
25
25
  # +mounted?+, +datasette+, +attach_cartridge+,
26
26
  # +press_cartridge_button+, +release_cartridge_button+,
27
- # +attach_drive1541+, +drive1541+, and +address_bus+ for its
27
+ # +attach_drive1541+, +drive1541+, +true_drive+ and +plug_true_drive+
28
+ # for the drive Media::TrueDrive plugs in, and +address_bus+ for its
28
29
  # +cartridge+ and +detach_cartridge+.
29
30
  # - Snapshots and tests: +snapshot+, +restore+, +save_snapshot+,
30
31
  # +install_debug_register+ and +capture_output+.
@@ -33,14 +34,15 @@ module Badline
33
34
  # input parts, with its one joystick as both +joystick1+ and +joystick2+
34
35
  # and nil for +control_ports+, and of the rest +ram+, +load_prg+,
35
36
  # +type_text+, +mount+, +unmount+, +mounted?+, +datasette+, +attach_cartridge+,
36
- # +attach_drive1541+, +drive1541+, +install_debug_register+ and
37
- # +capture_output+.
37
+ # +attach_drive1541+, +drive1541+, +true_drive+, +plug_true_drive+,
38
+ # +install_debug_register+ and +capture_output+.
38
39
  #
39
40
  # The C128 answers all but the cartridge button, and besides +mode+,
40
41
  # :c64, as Machine.build builds it, or :c128, +vdc+, the VDC a front
41
42
  # end can show in place of +video+ once +vdc_shown=+ renders it,
42
- # +press_caps_lock+, +release_caps_lock+, +press_display_key+ and
43
- # +release_display_key+.
43
+ # +press_caps_lock+, +release_caps_lock+, +press_display_key+,
44
+ # +release_display_key+, and +attach_drive1571+ and +drive1571+ for the
45
+ # C128D's 1571, its true drive.
44
46
  module Machine
45
47
  # A new machine of `family`, :c64, :vic20 or :c128, the model of it
46
48
  # `model` names. A C64's model is one of Model::ALL, with its SID
@@ -2,51 +2,54 @@
2
2
 
3
3
  module Badline
4
4
  module Media
5
- # Device 8 as a true 1541 instead of the KERNAL traps. Once a Drive1541
6
- # is plugged in as device 8, disk media go into it rather than into the
7
- # traps, so a LOAD runs the DOS and every byte crosses the serial bus.
8
- # A .g64 holds the disk's raw GCR, which only a true drive reads, so it
9
- # plugs one in when there's none yet. The 1541 reads only .d64 and .g64
10
- # images, so the traps' other disk media, a .d71, .d81 or .t64 or a host
11
- # directory, raise Error.
5
+ # Device 8 as a true drive instead of the KERNAL traps: a 1541, or on
6
+ # the C128 its 1571. Once a true drive is plugged in as device 8, disk
7
+ # media go into it rather than into the traps, so a LOAD runs the DOS
8
+ # and every byte crosses the serial bus. A .g64 holds the disk's raw
9
+ # GCR, which only a true drive reads, so it plugs one in when there's
10
+ # none yet. The 1541 reads only .d64 and .g64 images, and the 1571
11
+ # .d71 and .g71 as well, so the traps' other disk media, a .d81 or
12
+ # .t64 or a host directory, raise Error.
12
13
  module TrueDrive
13
14
  class Error < ArgumentError; end
14
15
 
15
16
  class << self
16
- # Plugs a Drive1541 in as device 8, unless one is there already, and
17
- # returns it.
17
+ # Plugs the machine's true drive in as device 8, unless one is there
18
+ # already, and returns it.
18
19
  def plug(computer)
19
- drive(computer) || Drive1541.new.tap { |drive| computer.attach_drive1541(drive) }
20
+ drive(computer) || computer.plug_true_drive
20
21
  end
21
22
 
22
- # The Drive1541 on device 8, or nil.
23
+ # The true drive on device 8, or nil.
23
24
  def drive(computer)
24
- drive = computer.drive1541
25
+ drive = computer.true_drive
25
26
  drive if drive && drive.device == KernalTrap::Routine::DEVICE
26
27
  end
27
28
 
28
- # Whether the path goes in a true drive: a .g64 always, and other
29
- # disk media, which the traps would mount, when a true drive is
30
- # device 8.
29
+ # Whether the path goes in a true drive: a .g64 or .g71 always, and
30
+ # other disk media, which the traps would mount, when a true drive
31
+ # is device 8.
31
32
  def takes?(computer, path)
32
- return true if File.extname(path).casecmp?(".g64")
33
+ return true if %w[.g64 .g71].include?(File.extname(path).downcase)
33
34
 
34
35
  !drive(computer).nil? && (File.directory?(path) || MOUNT_TYPES.key?(File.extname(path).downcase))
35
36
  end
36
37
 
37
- # Puts the .d64 or .g64 in the drive on device 8, plugging one in
38
- # when there's none, and takes out what was mounted through the
39
- # traps, so LOAD and SAVE reach the drive too. `read_only` puts it
40
- # in write-protected.
38
+ # Puts the image in the drive on device 8, plugging one in when
39
+ # there's none, and takes out what was mounted through the traps,
40
+ # so LOAD and SAVE reach the drive too. `read_only` puts it in
41
+ # write-protected.
41
42
  def insert(computer, path, read_only: false)
42
- unless %w[.d64 .g64].include?(File.extname(path).downcase)
43
- raise Error, "#{path} is not a .d64 or .g64 image, which the 1541 reads"
43
+ name = drive(computer)&.model_name || (computer.family == :c128 ? "1571" : "1541")
44
+ types = name == "1571" ? %w[.d64 .g64 .d71 .g71] : %w[.d64 .g64]
45
+ unless types.include?(File.extname(path).downcase)
46
+ raise Error, "#{path} is not a #{types[0..-2].join(', ')} or #{types.last} image, which the #{name} reads"
44
47
  end
45
48
 
46
49
  disk = Drive1541::Disk.open(path, read_only:)
47
50
  computer.unmount
48
51
  plug(computer).insert(disk)
49
- "Inserted #{path} in the 1541 as device 8"
52
+ "Inserted #{path} in the #{name} as device 8"
50
53
  end
51
54
  end
52
55
  end
@@ -60,7 +60,8 @@ module Badline
60
60
  window also autosaves every two minutes of play, which only the
61
61
  pause menu loads. Device 8 answers through traps on the KERNAL's
62
62
  disk routines, unless --true-drive puts an emulated 1541 there,
63
- which runs its own DOS and reads .d64 and .g64 images only. A .sid
63
+ which runs its own DOS and reads .d64 and .g64 images only, or on
64
+ the C128 the C128D's 1571, which reads .d71 and .g71 too. A .sid
64
65
  tune opens in the SID player, as `sid` plays it, unless an option
65
66
  of the emulator's window asks for the machine.
66
67
 
@@ -35,7 +35,7 @@ module Badline
35
35
  Option.new("--sound", "Play the SID through the host's audio device (F10 mutes)",
36
36
  section: "Window options", needs: :window),
37
37
  Option.new("--no-sound", "Don't play the SID", section: "Window options", needs: :window, build: :native),
38
- Option.new("--true-drive", "Put a true 1541 on device 8 instead of the KERNAL traps",
38
+ Option.new("--true-drive", "Put a true 1541 on device 8 instead of the KERNAL traps, a 1571 on the C128",
39
39
  section: "Window options", needs: :window),
40
40
  Option.new("--reu SIZE", "Plug in an REU of SIZE K: 128, 256, 512 (a 1750) or up to 16384",
41
41
  section: "Window options", needs: :window),
@@ -61,6 +61,15 @@ https://www.zimmers.net/anonftp/pub/cbm/firmware/drives/new/1541/
61
61
  (1541-c000.325302-01.bin and 1541-e000.901229-05.bin) and concatenated.
62
62
  SHA-1 ab16f56989b27d89babe5f89c5a8cb3da71a82f0.
63
63
 
64
+ dos1571.rom
65
+ -----------
66
+ The 1571 disk drive's DOS 3.0, 310654-05, 32 KB for $8000-$FFFF.
67
+ Drive1571 loads it.
68
+
69
+ Downloaded from
70
+ https://www.zimmers.net/anonftp/pub/cbm/firmware/drives/new/1571/1571-rom.310654-05.bin
71
+ SHA-1 f1be619c106641a685f6609e4d43d6fc9eac1e70.
72
+
64
73
  Commodore VIC-20 ROMs
65
74
  =====================
66
75
 
Binary file
@@ -17,6 +17,9 @@ module Badline
17
17
 
18
18
  def storage_kind = D64
19
19
 
20
+ # The sides of a disk the image's tracks fill.
21
+ def sides = 1
22
+
20
23
  def sectors_in(track)
21
24
  case track
22
25
  when 1..17 then 21
@@ -7,6 +7,15 @@ module Badline
7
7
 
8
8
  def storage_kind = D71
9
9
 
10
+ # Tracks 1-35 on the first side and 36-70 on the second.
11
+ def track_count = 70
12
+
13
+ def sides = 2
14
+
15
+ def sectors_in(track)
16
+ track > 35 ? super(track - 35) : super
17
+ end
18
+
10
19
  private
11
20
 
12
21
  def error_tables = ERROR_TABLES
@@ -22,10 +31,6 @@ module Badline
22
31
  def bam_bitmap(track) = track > 35 ? [53, 0, 3 * (track - 36)] : super
23
32
 
24
33
  def double_sided? = @bytes[sector_offset(18, 0) + 3].anybits?(0x80)
25
-
26
- def sectors_in(track)
27
- track > 35 ? super(track - 35) : super
28
- end
29
34
  end
30
35
  end
31
36
  end
@@ -5,7 +5,9 @@ module Badline
5
5
  # A .g64 image: a 1541 disk's flux as raw GCR, track by track, for the
6
6
  # true drive to read the way it reads a real disk, copy protection and
7
7
  # all. It has no file system of its own, so only the true drive
8
- # (Drive1541::Disk.from_g64) can use it.
8
+ # (Drive1541::Disk.from_g64) can use it. A .g71 holds a 1571 disk the
9
+ # same way, with "GCR-1571" for its signature and 168 half tracks,
10
+ # the first side's 84 and then the second's.
9
11
  #
10
12
  # The header is the signature "GCR-1541", a version byte ($00), the
11
13
  # number of half tracks the tables cover (84, tracks 1 to 42.5) and
@@ -36,6 +38,7 @@ module Badline
36
38
  class FormatError < StandardError; end
37
39
 
38
40
  SIGNATURE = "GCR-1541"
41
+ SIGNATURE_1571 = "GCR-1571"
39
42
  HEADER_SIZE = 12
40
43
  HALF_TRACKS = 84
41
44
  MAX_TRACK_SIZE = 7928
@@ -124,7 +127,9 @@ module Badline
124
127
  private
125
128
 
126
129
  def parse
127
- raise FormatError, "Missing G64 signature" unless @data.start_with?(SIGNATURE)
130
+ unless @data.start_with?(SIGNATURE) || @data.start_with?(SIGNATURE_1571)
131
+ raise FormatError, "Missing G64 signature"
132
+ end
128
133
  raise FormatError, "G64 header is cut short" if @data.bytesize < HEADER_SIZE
129
134
 
130
135
  version, @half_tracks, @max_track_size = @data.byteslice(8, 4).unpack("CCv")
@@ -52,6 +52,15 @@ module Badline
52
52
  @latch = input.boolean? ? FIELDS.to_h { |field| [field, input.int] } : nil
53
53
  end
54
54
 
55
+ # Whether the clock is stopped with no alarm to raise, so its cycles
56
+ # move only the divider's phase.
57
+ def quiet? = @stopped && !@alarm_pending
58
+
59
+ # Runs +cycles+ quiet cycles at once.
60
+ def fast_forward(cycles)
61
+ @accumulator = (@accumulator + (cycles * @mains_hz)) % @cycles_per_pulse
62
+ end
63
+
55
64
  def cycle!
56
65
  @accumulator += @mains_hz
57
66
  pulse! if @accumulator >= @cycles_per_pulse
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Badline
4
- VERSION = "0.11.0"
4
+ VERSION = "0.11.1"
5
5
  end
@@ -3,14 +3,14 @@
3
3
  module Badline
4
4
  class VIA
5
5
  # Running a VIA a stretch of cycles at once while nothing but its
6
- # counters would change, for Drive1541::Idle.
6
+ # counters would change, for Drive::Idle.
7
7
  module FastForward
8
8
  # quiet_cycles for a VIA that will never set a flag of its own.
9
9
  QUIET = 1 << 40
10
10
 
11
11
  # How many cycles the VIA can run with nothing but its counters
12
12
  # changing: no flag set, no pulse on a control line and no bit
13
- # shifted. See Drive1541::Idle.
13
+ # shifted. See Drive::Idle.
14
14
  def quiet_cycles
15
15
  return 0 if @sr_uses_t2 || @shift_register.clocking? || @ca.pulsing? || @cb.pulsing?
16
16
 
@@ -26,7 +26,7 @@ module Badline
26
26
  @t2.fast_forward(cycles)
27
27
  end
28
28
 
29
- # The counters an orbit (see Drive1541::Idle) has to bring back: those
29
+ # The counters an orbit (see Drive::Idle) has to bring back: those
30
30
  # of the armed timers, and of the timers +touched+ names (bit 0 for
31
31
  # timer 1, bit 1 for timer 2). The others only count.
32
32
  def counter_state(touched)
@@ -75,6 +75,12 @@ module Badline
75
75
  @serial_trap&.device = serial_trap_device
76
76
  end
77
77
 
78
+ # The true drive on the serial bus: the Drive1541, or nil.
79
+ def true_drive = @drive1541
80
+
81
+ # Plugs a Drive1541 in as device 8, and returns it.
82
+ def plug_true_drive = Drive1541.new.tap { |drive| attach_drive1541(drive) }
83
+
78
84
  # Unplugs the Drive1541, leaving the serial bus with nothing on it.
79
85
  def detach_drive1541
80
86
  return unless @drive1541