badline 0.18.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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -0
  3. data/CLAUDE.md +8 -0
  4. data/CONTRIBUTING.md +4 -0
  5. data/README.md +10 -7
  6. data/doc/library.md +5 -1
  7. data/doc/media.md +3 -2
  8. data/doc/pinned-behaviour.md +41 -1
  9. data/doc/snapshot-format.md +56 -24
  10. data/doc/snapshots.md +45 -8
  11. data/doc/vice.md +344 -0
  12. data/lib/badline/audio/renderer.rb +4 -8
  13. data/lib/badline/c128.rb +4 -0
  14. data/lib/badline/c64/bus/fittings.rb +6 -0
  15. data/lib/badline/c64.rb +6 -0
  16. data/lib/badline/cbm2/p500.rb +4 -0
  17. data/lib/badline/cbm2.rb +4 -0
  18. data/lib/badline/chips/cpu/core.rb +29 -4
  19. data/lib/badline/chips/cpu/interrupts.rb +9 -0
  20. data/lib/badline/chips/cpu/keyed_state.rb +15 -9
  21. data/lib/badline/chips/cpu/microcode.rb +10 -0
  22. data/lib/badline/chips/rriot.rb +4 -1
  23. data/lib/badline/chips/sid/decimator.rb +1 -1
  24. data/lib/badline/chips/sid/keyed_parts.rb +51 -18
  25. data/lib/badline/chips/sid.rb +6 -2
  26. data/lib/badline/chips/vic_i/keyed_parts.rb +6 -1
  27. data/lib/badline/chips/vic_i/light_pen.rb +89 -0
  28. data/lib/badline/chips/vic_i.rb +12 -7
  29. data/lib/badline/drives/model1541/sector_reader.rb +20 -3
  30. data/lib/badline/drives/model1541.rb +1 -0
  31. data/lib/badline/drives/model1551.rb +1 -0
  32. data/lib/badline/drives/model1571.rb +1 -0
  33. data/lib/badline/drives/model1581.rb +1 -0
  34. data/lib/badline/drives/model2031.rb +1 -0
  35. data/lib/badline/drives/model4040/mechanism.rb +6 -3
  36. data/lib/badline/drives/model4040.rb +17 -1
  37. data/lib/badline/drives/model8050/board.rb +2 -0
  38. data/lib/badline/drives/model8050/dos_board.rb +49 -6
  39. data/lib/badline/drives/model8050/fdc_bus.rb +7 -0
  40. data/lib/badline/drives/model8050/idle.rb +137 -24
  41. data/lib/badline/drives/model8050.rb +14 -0
  42. data/lib/badline/drives/model9060/idle.rb +30 -10
  43. data/lib/badline/drives/model9060.rb +2 -0
  44. data/lib/badline/input/joystick.rb +15 -2
  45. data/lib/badline/kim1/tape.rb +3 -1
  46. data/lib/badline/kim1.rb +4 -0
  47. data/lib/badline/machine.rb +3 -0
  48. data/lib/badline/media/true_drive.rb +10 -0
  49. data/lib/badline/media/vic20_media.rb +4 -2
  50. data/lib/badline/media.rb +5 -1
  51. data/lib/badline/memory.rb +2 -4
  52. data/lib/badline/options/table.rb +1 -1
  53. data/lib/badline/pet/superpet/dongle.rb +13 -2
  54. data/lib/badline/pet/superpet.rb +5 -0
  55. data/lib/badline/pet.rb +4 -0
  56. data/lib/badline/plus4.rb +4 -0
  57. data/lib/badline/roms/README +11 -1
  58. data/lib/badline/snapshot/container.rb +6 -4
  59. data/lib/badline/snapshot/field_doc.rb +8 -4
  60. data/lib/badline/snapshot/image.rb +15 -6
  61. data/lib/badline/snapshot/vice/pet.rb +1 -0
  62. data/lib/badline/snapshot/vice/pet_dongle.rb +72 -0
  63. data/lib/badline/snapshot/vice/pet_mem.rb +11 -5
  64. data/lib/badline/snapshot/vice/vic20.rb +70 -0
  65. data/lib/badline/snapshot/vice/vic20_cpu.rb +61 -0
  66. data/lib/badline/snapshot/vice/vic20_mem.rb +85 -0
  67. data/lib/badline/snapshot/vice/vic20_restore.rb +124 -0
  68. data/lib/badline/snapshot/vice/vic20_vici.rb +200 -0
  69. data/lib/badline/snapshot/vice.rb +1 -0
  70. data/lib/badline/snapshot.rb +4 -4
  71. data/lib/badline/storage/disk_image/writing.rb +2 -2
  72. data/lib/badline/version.rb +1 -1
  73. data/lib/badline/vic20/port_wiring.rb +11 -0
  74. data/lib/badline/vic20.rb +4 -0
  75. metadata +9 -1
@@ -21,10 +21,10 @@ module Badline
21
21
  # volatile), makes the pass repeatable. Once both processors have a
22
22
  # repeatable pass, with the motors off, no head stepping, no disk
23
23
  # going in or out and no interrupt pulled, the unit sleeps:
24
- # host_cycle! only counts the cycles it owes, until one of the chips'
25
- # counters is due to pull IRQ (quiet_cycles), the controller moves a
26
- # line of the bus, or something outside reads or changes the unit
27
- # (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!).
28
28
  #
29
29
  # Waking runs the counters on in bulk, counts off each processor's
30
30
  # whole passes, and runs the rest of a pass on that processor alone.
@@ -32,8 +32,21 @@ module Badline
32
32
  # order the two run in doesn't matter. Then the unit runs cycle by
33
33
  # cycle again. Woken by a counter, it keeps the passes it found, and
34
34
  # sleeps again as soon as both repeat: the IP's, which the timer's
35
- # interrupt doesn't reach, at once, and the FDC's a pass after its
36
- # 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.
37
50
  module Idle
38
51
  IP_LOOP = 0xc5a1
39
52
  FDC_LOOP = 0xfc17
@@ -48,10 +61,17 @@ module Badline
48
61
 
49
62
  def asleep? = @asleep
50
63
 
64
+ # Whether the unit, or the IP's side of it, owes cycles.
65
+ def dozing? = @asleep || @fdc_alone
66
+
51
67
  # Brings the unit up to the present, cycle for cycle, and drops the
52
68
  # passes it found, since whoever called may change it.
53
69
  def settle!
54
- wake! if @asleep
70
+ if @asleep
71
+ wake!
72
+ @asleep = false
73
+ end
74
+ join_ip if @fdc_alone
55
75
  forget_passes
56
76
  end
57
77
 
@@ -65,6 +85,13 @@ module Badline
65
85
  @asleep = false
66
86
  @owed = 0
67
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
68
95
  forget_passes
69
96
  end
70
97
 
@@ -73,13 +100,53 @@ module Badline
73
100
  @fdc_mark = nil
74
101
  @ip_pass = nil
75
102
  @fdc_pass = nil
103
+ @fdc_proof = nil
76
104
  end
77
105
 
78
106
  # A cycle owed while asleep, which wakes the unit once the budget
79
- # is spent.
107
+ # is spent: the FDC's side alone if the IP's counters leave it
108
+ # room.
80
109
  def owe_cycle
81
110
  @owed += 1
82
- 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)
83
150
  end
84
151
 
85
152
  # Looks for the loops' starts after a cycle run awake. An interrupt
@@ -101,12 +168,44 @@ module Badline
101
168
  fall_asleep if @ip_pass && @fdc_pass
102
169
  end
103
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.
104
175
  def fdc_pass_found
105
- state = fdc_state
106
- @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
107
188
  @fdc_mark = [state, @fdc_cpu.cycles, @fdc_cpu.instructions]
108
- @fdc_bus.volatile = false
109
- 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
110
209
  end
111
210
 
112
211
  # The pass since +mark+ as [cycles, instructions] if it left the
@@ -118,6 +217,7 @@ module Badline
118
217
  end
119
218
 
120
219
  def fall_asleep
220
+ join_ip if @fdc_alone
121
221
  return unless @mechanism.quiet? && !@ip_cpu.irq && !@fdc_cpu.irq
122
222
 
123
223
  budget = quiet_cycles
@@ -136,18 +236,30 @@ module Badline
136
236
  # Runs the cycles owed: the counters in bulk, then each processor's
137
237
  # whole passes and the rest of a pass.
138
238
  def wake!
239
+ owe_slept
139
240
  owed = @owed
140
241
  @asleep = false
141
242
  @owed = 0
142
- 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
143
247
 
144
- @data_riot.fast_forward(owed)
145
- @control_riot.fast_forward(owed)
146
- @rriot.fast_forward(owed)
147
- @via.fast_forward(owed)
148
- skip(@ip_cpu, @ip_pass, @ip_mark, owed)
149
- skip(@fdc_cpu, @fdc_pass, @fdc_mark, owed)
150
- @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
151
263
  end
152
264
 
153
265
  # Counts off the processor's whole passes, moving its mark on with
@@ -167,10 +279,11 @@ module Badline
167
279
  [*@ip_cpu.idle_state, *@data_riot.idle_state, *@control_riot.idle_state, @ip_bus.data, common_changes]
168
280
  end
169
281
 
170
- # What the FDC sees: its CPU, the VIA and the 6530 but for their
171
- # 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.
172
285
  def fdc_state
173
- [*@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,
174
287
  common_changes]
175
288
  end
176
289
 
@@ -61,7 +61,9 @@ module Badline
61
61
  build_ip(dos_rom || ROM.load("8050/dos-2.7.rom", 0xc000))
62
62
  build_fdc(fdc_rom || ROM.read("8050/fdc-901869-01.rom"))
63
63
  @ip_cpu = IPCPU.new(@ip_bus)
64
+ @ip_cpu.power_on!
64
65
  @fdc_cpu = FDCCPU.new(@fdc_bus)
66
+ @fdc_cpu.power_on!
65
67
  init_idle(Idle::IP_LOOP, Idle::FDC_LOOP)
66
68
  @ieee_bus.attach(self)
67
69
  end
@@ -118,6 +120,18 @@ module Badline
118
120
  @fdc_cpu.cycle!
119
121
  @cycles += 1
120
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
121
135
  end
122
136
 
123
137
  # The 8250: the 8050's board driving double-sided mechanisms, 154 tracks
@@ -19,7 +19,10 @@ module Badline
19
19
  # the pass repeatable. Once both are, with the hard disk's controller
20
20
  # idle and no interrupt pulled, the unit sleeps until a counter is due
21
21
  # to pull IRQ, the IEEE-488 controller moves a line, or something
22
- # outside reads or changes the unit (settle!).
22
+ # outside reads or changes the unit (settle!). Asleep, a host cycle
23
+ # only counts towards the one planned for the counter
24
+ # (DOSBoard#plan_sleep), and a moved line wakes the unit as the
25
+ # controller pushes it (DOSBoard#host_written!).
23
26
  module Idle
24
27
  IP_LOOP = 0xc131
25
28
  SASI_LOOP = 0xf8f1
@@ -34,6 +37,9 @@ module Badline
34
37
 
35
38
  def asleep? = @asleep
36
39
 
40
+ # Whether the unit owes cycles.
41
+ def dozing? = @asleep
42
+
37
43
  # Brings the unit up to the present, cycle for cycle, and drops the
38
44
  # passes it found, since whoever called may change it.
39
45
  def settle!
@@ -48,6 +54,9 @@ module Badline
48
54
  @asleep = false
49
55
  @owed = 0
50
56
  @budget = 0
57
+ @planned = false
58
+ @slept = 0
59
+ @wake_at = 0
51
60
  forget_passes
52
61
  end
53
62
 
@@ -110,19 +119,30 @@ module Badline
110
119
  [@data_riot.quiet_cycles, @control_riot.quiet_cycles, @via.quiet_cycles].min
111
120
  end
112
121
 
122
+ # The budget is spent (DOSBoard#slept_through?).
123
+ def doze = wake!
124
+
125
+ # Runs the cycles owed: the counters in bulk, then each processor's
126
+ # whole passes and the rest of a pass, and on a host cycle that ran
127
+ # past the budget, the rest cycle by cycle.
113
128
  def wake!
129
+ owe_slept
114
130
  owed = @owed
115
131
  @asleep = false
116
132
  @owed = 0
117
- return if owed.zero?
118
-
119
- @data_riot.fast_forward(owed)
120
- @control_riot.fast_forward(owed)
121
- @via.fast_forward(owed)
122
- @target.fast_forward(owed)
123
- skip(@ip_cpu, @ip_pass, @ip_mark, owed)
124
- skip(@sasi_cpu, @sasi_pass, @sasi_mark, owed)
125
- @cycles += owed
133
+ quiet = [owed, @budget].min
134
+ skip_passes(quiet) if quiet.positive?
135
+ (owed - quiet).times { @asleep ? owe_cycle : run_cycle }
136
+ end
137
+
138
+ def skip_passes(cycles)
139
+ @data_riot.fast_forward(cycles)
140
+ @control_riot.fast_forward(cycles)
141
+ @via.fast_forward(cycles)
142
+ @target.fast_forward(cycles)
143
+ skip(@ip_cpu, @ip_pass, @ip_mark, cycles)
144
+ skip(@sasi_cpu, @sasi_pass, @sasi_mark, cycles)
145
+ @cycles += cycles
126
146
  end
127
147
 
128
148
  def skip(cpu, pass, mark, owed)
@@ -86,7 +86,9 @@ module Badline
86
86
  build_ip(dos_rom || ROM.load("9060/dos-3.0.rom", 0xc000))
87
87
  build_sasi(sasi_rom || ROM.read("9060/sasi-300515-revb.rom"))
88
88
  @ip_cpu = Model8050::IPCPU.new(@ip_bus)
89
+ @ip_cpu.power_on!
89
90
  @sasi_cpu = SASICPU.new(@sasi_bus)
91
+ @sasi_cpu.power_on!
90
92
  init_idle
91
93
  @ieee_bus.attach(self)
92
94
  end
@@ -11,16 +11,29 @@ module Badline
11
11
 
12
12
  def initialize
13
13
  @port_bits = 0xff
14
+ @change_handler = nil
15
+ end
16
+
17
+ # Register a handler called after a press or a release. On the VIC-20,
18
+ # this passes the fire button on to the VIC's light pen input.
19
+ def on_change(&handler)
20
+ @change_handler = handler
14
21
  end
15
22
 
16
23
  def press(direction)
17
24
  bit = DIRECTIONS[direction]
18
- @port_bits &= ~(1 << bit) if bit
25
+ return unless bit
26
+
27
+ @port_bits &= ~(1 << bit)
28
+ @change_handler&.call
19
29
  end
20
30
 
21
31
  def release(direction)
22
32
  bit = DIRECTIONS[direction]
23
- @port_bits |= 1 << bit if bit
33
+ return unless bit
34
+
35
+ @port_bits |= 1 << bit
36
+ @change_handler&.call
24
37
  end
25
38
  end
26
39
  end
@@ -8,7 +8,9 @@ module Badline
8
8
  # Recording, it puts each change of PB7, which DUMPT ($1800) toggles as
9
9
  # a square wave, on the tape as a crossing, in place of whatever was
10
10
  # on the tape from there on. Stopping a recording writes the tape to
11
- # its file.
11
+ # its file. DUMPT's half periods run 135 to 139 cycles for the 3,700 Hz
12
+ # tone and 203 to 209 for the 2,400 Hz one, a bit taking about 7.38 ms,
13
+ # where the User Manual's figure E-1 gives 7.452.
12
14
  #
13
15
  # Nothing on the board starts or stops the deck: the tape moves while
14
16
  # PLAY or RECORD is down, a microsecond, a cycle, at a time.
data/lib/badline/kim1.rb CHANGED
@@ -48,6 +48,8 @@ module Badline
48
48
 
49
49
  def family = :kim1
50
50
 
51
+ def portless_board = nil
52
+
51
53
  def ram = @bus.ram
52
54
 
53
55
  # Reads and writes memory as the CPU sees it, through the bus.
@@ -68,6 +70,7 @@ module Badline
68
70
  @panel.connect(@u2)
69
71
  @bus = Bus.new(@u2, @u3, ram: ram || :"1k")
70
72
  @cpu = CPU.new(@bus)
73
+ @cpu.power_on!
71
74
  @sst = false
72
75
  @irq_wired = false
73
76
  @sound = Sound.new(@u2, @u3)
@@ -158,6 +161,7 @@ module Badline
158
161
  def power_cycle!
159
162
  @bus.power_on!
160
163
  reset!
164
+ @cpu.power_on!
161
165
  end
162
166
 
163
167
  # ST: an edge on NMI.
@@ -70,6 +70,9 @@ module Badline
70
70
  # +mounted?+, +datasette+, +true_drive+, nil, +install_debug_register+
71
71
  # and +capture_output+, and besides +model_label+ and +ram_kb+.
72
72
  #
73
+ # Every machine answers +portless_board+, nil unless it's a C64 whose
74
+ # board has no serial port for device 8 (C64#portless_board).
75
+ #
73
76
  # Each machine includes Machine::Clocking for the clocking part.
74
77
  module Machine
75
78
  # Media the machine named doesn't take.
@@ -30,9 +30,19 @@ module Badline
30
30
  # Plugs the machine's true drive in as device 8, unless one is there
31
31
  # already, and returns it.
32
32
  def plug(computer)
33
+ refuse_portless(computer, "A true drive")
33
34
  drive(computer) || computer.plug_true_drive
34
35
  end
35
36
 
37
+ # Raises Machine::Unsupported over `what`, a disk or a drive, on a
38
+ # C64 whose board has no serial port to plug a drive into.
39
+ def refuse_portless(computer, what)
40
+ board = computer.portless_board
41
+ return if board.nil?
42
+
43
+ raise Machine::Unsupported, "#{what} needs the serial port, and #{board} has none"
44
+ end
45
+
36
46
  # The true drive on device 8, or nil.
37
47
  def drive(computer)
38
48
  drive = computer.true_drive
@@ -41,11 +41,13 @@ module Badline
41
41
  end
42
42
 
43
43
  # The program at `path`, or the first on the disk or in the
44
- # directory there, or nil.
44
+ # directory there, or nil, as for a disk with no program on it.
45
45
  def first_program(path)
46
46
  kind = Extensions.kind(path)
47
- if File.directory?(path) || %i[disk archive].include?(kind)
47
+ if File.directory?(path) || %i[disk archive ieee_disk].include?(kind)
48
48
  Media.open_storage(path).read_file("*")
49
+ elsif kind == :hard_disk
50
+ Storage::D90Image.new(path, read_only: true).read_file("*")
49
51
  elsif File.file?(path) && !%i[tune cartridge tape gcr].include?(kind)
50
52
  Media.program_bytes(path)
51
53
  end
data/lib/badline/media.rb CHANGED
@@ -80,6 +80,7 @@ module Badline
80
80
  # read, raise TrueDrive::Error, an ArgumentError. Anything else
81
81
  # raises NotDisk.
82
82
  def insert_disk(computer, path, read_only: false)
83
+ TrueDrive.refuse_portless(computer, path)
83
84
  return TrueDrive.insert(computer, path, read_only:) if TrueDrive.takes?(computer, path)
84
85
 
85
86
  raise NotDisk, "#{path} is not a disk image or a directory" unless disk?(path)
@@ -134,9 +135,12 @@ module Badline
134
135
 
135
136
  # The VIC-20 and the 264 series have no SID to play a .sid tune on,
136
137
  # only the PET and the CBM-II have the IEEE-488 drive a .d80, .d82
137
- # or .d90 goes in, and only the KIM-1 reads papertape.
138
+ # or .d90 goes in, and only the KIM-1 reads papertape. The C64GS and
139
+ # the MAX Machine have no serial port for what goes in device 8.
138
140
  def refuse_unsupported(computer, path)
139
141
  family = computer.family
142
+ device8 = File.directory?(path) || Extensions::DEVICE8.include?(File.extname(path).downcase)
143
+ TrueDrive.refuse_portless(computer, path) if device8
140
144
  raise Machine::Unsupported, "#{path} needs a KIM-1" if Extensions.kind(path) == :papertape
141
145
  if %i[ieee_disk hard_disk].include?(Extensions.kind(path)) && !%i[pet cbm2].include?(family)
142
146
  raise ArgumentError, "#{path} needs a PET's or a CBM-II's IEEE-488 drive"
@@ -46,10 +46,8 @@ module Badline
46
46
  end
47
47
 
48
48
  def zero_fill(initial)
49
- array = initial.dup
50
- 0.upto(length - 1) do |i|
51
- array[i] ||= blank_value
52
- end
49
+ array = Array.new(length, blank_value)
50
+ initial.each_with_index { |v, i| array[i] = v.to_i unless v.nil? }
53
51
  array
54
52
  end
55
53
  end
@@ -164,7 +164,7 @@ module Badline
164
164
  section: "Testing options", needs: :window),
165
165
  Option.new("--save-snapshot FILE", "Save the machine as a .vsf snapshot after the last frame",
166
166
  section: "Testing options", needs: :window),
167
- Option.new("--vice", "Add VICE's modules to --save-snapshot's C64, PET or CBM-II snapshot",
167
+ Option.new("--vice", "Add VICE's modules to --save-snapshot's C64, VIC-20, PET or CBM-II snapshot",
168
168
  section: "Testing options", needs: :window),
169
169
  Option.new("--at FRAME:EVENT", "Run EVENT once FRAME frames have run (see above)",
170
170
  section: "Testing options", needs: :window),
@@ -64,15 +64,26 @@ module Badline
64
64
  end
65
65
  end
66
66
 
67
+ # The registers as they stand once a shift waiting has happened.
68
+ def settled_registers
69
+ registers = @registers.dup
70
+ shift_registers(registers) if @shift_at != NONE
71
+ registers
72
+ end
73
+
67
74
  private
68
75
 
69
76
  def shift
70
77
  @shift_at = NONE
78
+ shift_registers(@registers)
79
+ end
80
+
81
+ def shift_registers(registers)
71
82
  incoming = @latch ^ FEEDBACK ^ 0xff
72
83
  8.times do |bit|
73
- register = @registers[bit]
84
+ register = registers[bit]
74
85
  stage = (register ^ (incoming >> bit)) & 1
75
- @registers[bit] = (register >> 1) | (stage << (LENGTHS[bit] - 1))
86
+ registers[bit] = (register >> 1) | (stage << (LENGTHS[bit] - 1))
76
87
  end
77
88
  end
78
89
  end
@@ -26,6 +26,11 @@ module Badline
26
26
  # 324037 schematic one-shots gate the 6809's RES), and the other stops
27
27
  # where it is. The PET's IRQ line, the ACIA's with it, reaches both.
28
28
  #
29
+ # The Waterloo ROMs hold only the menu, setup and the monitor. The
30
+ # menu loads every language, microBASIC, microAPL, FORTRAN, Pascal,
31
+ # COBOL, the editor and the development package, from disk into RAM,
32
+ # so 6809 mode needs a disk on device 8 to run one.
33
+ #
29
34
  # In 6809 mode device 8 answers through traps on the Waterloo ROM's
30
35
  # IEEE-488 routines (WaterlooTraps) as well as the KERNAL's, from the
31
36
  # same disk, or a true drive on the bus answers. type_text types
data/lib/badline/pet.rb CHANGED
@@ -75,6 +75,8 @@ module Badline
75
75
 
76
76
  def family = :pet
77
77
 
78
+ def portless_board = nil
79
+
78
80
  # The datasette on cassette port #1, where a tape goes in (Cassettes).
79
81
  def datasette = @cassettes.datasette1
80
82
 
@@ -129,6 +131,7 @@ module Badline
129
131
  ieee.lines_written
130
132
  end
131
133
  @cpu = CPU.new(@bus, debug:)
134
+ @cpu.power_on!
132
135
  @cycles = 0
133
136
  @init_handlers = []
134
137
  @pending_keys = nil
@@ -222,6 +225,7 @@ module Badline
222
225
  def power_cycle!
223
226
  @bus.power_on!
224
227
  reset!
228
+ @cpu.power_on!
225
229
  end
226
230
 
227
231
  # Puts a program file's bytes where its first two say, through the bus,
data/lib/badline/plus4.rb CHANGED
@@ -67,6 +67,8 @@ module Badline
67
67
 
68
68
  def family = :plus4
69
69
 
70
+ def portless_board = nil
71
+
70
72
  # The joysticks reach the TED's keyboard latch, so there are no C64
71
73
  # control ports to plug a pot device into.
72
74
  def control_ports = nil
@@ -139,6 +141,7 @@ module Badline
139
141
  @tias = tcbm ? [Chips::TIA.new, Chips::TIA.new] : []
140
142
  @bus.plug_tcbm(drive8: @tias[0], drive9: @tias[1]) if tcbm
141
143
  @cpu = CPU.new(@bus, debug:)
144
+ @cpu.power_on!
142
145
  @sound = Chips::TED::Sound.new(self, @model.ted.clock_hz)
143
146
  @ted.sound = @sound
144
147
  @sound_mix = @sid_card ? SoundMix.new(@sound, @sid_card) : nil
@@ -202,6 +205,7 @@ module Badline
202
205
  @ted.power_on!
203
206
  @sound.power_on!
204
207
  reset!
208
+ @cpu.power_on!
205
209
  end
206
210
 
207
211
  # The ACIA's IRQ line, and the cycle it next has something to do at.
@@ -134,7 +134,10 @@ The SFD-1001's floppy controller program, 251257-02A: the first 1 KB
134
134
  of the 2716 on its 6530 adaptor (251252-01), whose A10 is grounded, so
135
135
  the controller runs it from $FC00-$FFFF in place of the 6530's mask.
136
136
  Its interface processor runs 8050/dos-2.7.rom, the same 16 KB as
137
- 901887-01 and 901888-01. Drives::Model1001 loads it.
137
+ 901887-01 and 901888-01. Drives::Model1001 loads it. The unit's
138
+ mechanism, a Matsushita JU-570-2, has 100 tracks an inch, as its spec
139
+ sheet in the 8250LP and SFD-1001 technical manual gives it, not the 96
140
+ that zimmers.net's description says.
138
141
  SHA-1 473664d405942865d46a6e723eb7d3f243584f7a (the whole 2 KB EPROM,
139
142
  SHA-1 3b954eb34f7ea088eed1d33ebc6d6e83a3e9be15).
140
143
 
@@ -148,6 +151,10 @@ processor's $E000-$FFFF: 901468-06 ($E000-$EFFF, SHA-1
148
151
  4d9658f2e6ff3276e5c6e224611a66ce44b16fc7) followed by 901468-07
149
152
  ($F000-$FFFF, SHA-1 6a51c7954938439ca8342fc295bda050c06e1791). No DOS
150
153
  1.0 image is public, so both DOS 1 units run it, as xpet's 2040 does.
154
+ The 2040 is often listed with DOS 1.0, but this set is 1.2: 901468-07's
155
+ IRQ vector is $E2D6, and VICE-testprogs' vdrive/diskaddrchange.bas
156
+ names the drive whose $FFFF reads $E2 "2040 v1.2". The Commodore ROM
157
+ Genealogy (1980) lists it as the 2040's production set.
151
158
  Drives::Model2040 loads it.
152
159
  SHA-1 e37b3bfd801eb19dea50cf8b4a6af8613c3520d3.
153
160
 
@@ -202,6 +209,9 @@ SHA-1 59dafbd4855083074ba8dc96a04d4daa5b76e0d6.
202
209
 
203
210
  Both downloaded from
204
211
  https://www.zimmers.net/anonftp/pub/cbm/firmware/drives/old/9090/
212
+ The harddrives.zip elsewhere on zimmers.net is not the D9060's DOS: it
213
+ is a 1981 prototype DOS for one processor, driving an 8" floppy and an
214
+ 8" hard disk.
205
215
 
206
216
  Commodore VIC-20 ROMs
207
217
  =====================
@@ -45,9 +45,10 @@ module Badline
45
45
  # VICE has no KIM-1, so badline names it.
46
46
  KIM1 = "KIM1"
47
47
  # The file format version each machine's VICE emulator reads, where
48
- # it isn't FORMAT_MAJOR.0: xpet and xcbm2 refuse any other than 1.0, and
49
- # xcbm5x0 writes 0.0.
50
- FORMAT_MAJORS = { PET => 1, CBM2 => 1, CBM2_5X0 => 0 }.freeze
48
+ # it isn't FORMAT_MAJOR.0: xpet and xcbm2 refuse any other than 1.0,
49
+ # xcbm5x0 writes 0.0, and xvic refuses any other than 3.1.
50
+ FORMAT_MAJORS = { PET => 1, CBM2 => 1, CBM2_5X0 => 0, VIC20 => 3 }.freeze
51
+ FORMAT_MINORS = { VIC20 => 1 }.freeze
51
52
  GZIP_MAGIC = "\x1f\x8b".b
52
53
 
53
54
  # The VICE version block, when VICE wrote the file: four version
@@ -77,7 +78,8 @@ module Badline
77
78
  def [](name) = @sections.find { |section| section.name == name }
78
79
 
79
80
  def to_s
80
- header = MAGIC + [FORMAT_MAJORS.fetch(@machine, FORMAT_MAJOR), FORMAT_MINOR].pack("CC") + padded(@machine)
81
+ version = [FORMAT_MAJORS.fetch(@machine, FORMAT_MAJOR), FORMAT_MINORS.fetch(@machine, FORMAT_MINOR)]
82
+ header = MAGIC + version.pack("CC") + padded(@machine)
81
83
  header += VERSION_MAGIC + @vice_version if @vice_version
82
84
  header + @sections.map { |section| encode(section) }.join
83
85
  end