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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +40 -0
- data/CLAUDE.md +8 -0
- data/CONTRIBUTING.md +4 -0
- data/README.md +10 -7
- data/doc/library.md +5 -1
- data/doc/media.md +3 -2
- data/doc/pinned-behaviour.md +41 -1
- data/doc/snapshot-format.md +56 -24
- data/doc/snapshots.md +45 -8
- data/doc/vice.md +344 -0
- data/lib/badline/audio/renderer.rb +4 -8
- data/lib/badline/c128.rb +4 -0
- data/lib/badline/c64/bus/fittings.rb +6 -0
- data/lib/badline/c64.rb +6 -0
- data/lib/badline/cbm2/p500.rb +4 -0
- data/lib/badline/cbm2.rb +4 -0
- data/lib/badline/chips/cpu/core.rb +29 -4
- data/lib/badline/chips/cpu/interrupts.rb +9 -0
- data/lib/badline/chips/cpu/keyed_state.rb +15 -9
- data/lib/badline/chips/cpu/microcode.rb +10 -0
- data/lib/badline/chips/rriot.rb +4 -1
- data/lib/badline/chips/sid/decimator.rb +1 -1
- data/lib/badline/chips/sid/keyed_parts.rb +51 -18
- data/lib/badline/chips/sid.rb +6 -2
- data/lib/badline/chips/vic_i/keyed_parts.rb +6 -1
- data/lib/badline/chips/vic_i/light_pen.rb +89 -0
- data/lib/badline/chips/vic_i.rb +12 -7
- data/lib/badline/drives/model1541/sector_reader.rb +20 -3
- data/lib/badline/drives/model1541.rb +1 -0
- data/lib/badline/drives/model1551.rb +1 -0
- data/lib/badline/drives/model1571.rb +1 -0
- data/lib/badline/drives/model1581.rb +1 -0
- data/lib/badline/drives/model2031.rb +1 -0
- data/lib/badline/drives/model4040/mechanism.rb +6 -3
- data/lib/badline/drives/model4040.rb +17 -1
- data/lib/badline/drives/model8050/board.rb +2 -0
- data/lib/badline/drives/model8050/dos_board.rb +49 -6
- data/lib/badline/drives/model8050/fdc_bus.rb +7 -0
- data/lib/badline/drives/model8050/idle.rb +137 -24
- data/lib/badline/drives/model8050.rb +14 -0
- data/lib/badline/drives/model9060/idle.rb +30 -10
- data/lib/badline/drives/model9060.rb +2 -0
- data/lib/badline/input/joystick.rb +15 -2
- data/lib/badline/kim1/tape.rb +3 -1
- data/lib/badline/kim1.rb +4 -0
- data/lib/badline/machine.rb +3 -0
- data/lib/badline/media/true_drive.rb +10 -0
- data/lib/badline/media/vic20_media.rb +4 -2
- data/lib/badline/media.rb +5 -1
- data/lib/badline/memory.rb +2 -4
- data/lib/badline/options/table.rb +1 -1
- data/lib/badline/pet/superpet/dongle.rb +13 -2
- data/lib/badline/pet/superpet.rb +5 -0
- data/lib/badline/pet.rb +4 -0
- data/lib/badline/plus4.rb +4 -0
- data/lib/badline/roms/README +11 -1
- data/lib/badline/snapshot/container.rb +6 -4
- data/lib/badline/snapshot/field_doc.rb +8 -4
- data/lib/badline/snapshot/image.rb +15 -6
- data/lib/badline/snapshot/vice/pet.rb +1 -0
- data/lib/badline/snapshot/vice/pet_dongle.rb +72 -0
- data/lib/badline/snapshot/vice/pet_mem.rb +11 -5
- data/lib/badline/snapshot/vice/vic20.rb +70 -0
- data/lib/badline/snapshot/vice/vic20_cpu.rb +61 -0
- data/lib/badline/snapshot/vice/vic20_mem.rb +85 -0
- data/lib/badline/snapshot/vice/vic20_restore.rb +124 -0
- data/lib/badline/snapshot/vice/vic20_vici.rb +200 -0
- data/lib/badline/snapshot/vice.rb +1 -0
- data/lib/badline/snapshot.rb +4 -4
- data/lib/badline/storage/disk_image/writing.rb +2 -2
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic20/port_wiring.rb +11 -0
- data/lib/badline/vic20.rb +4 -0
- 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
|
|
25
|
-
#
|
|
26
|
-
# line of the bus, or something outside
|
|
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
|
|
36
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
106
|
-
|
|
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
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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
|
-
|
|
145
|
-
@
|
|
146
|
-
@
|
|
147
|
-
@
|
|
148
|
-
|
|
149
|
-
skip(@
|
|
150
|
-
@
|
|
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
|
|
171
|
-
# counters, its bus's last
|
|
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.
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
@cycles
|
|
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
|
-
|
|
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
|
-
|
|
33
|
+
return unless bit
|
|
34
|
+
|
|
35
|
+
@port_bits |= 1 << bit
|
|
36
|
+
@change_handler&.call
|
|
24
37
|
end
|
|
25
38
|
end
|
|
26
39
|
end
|
data/lib/badline/kim1/tape.rb
CHANGED
|
@@ -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.
|
data/lib/badline/machine.rb
CHANGED
|
@@ -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"
|
data/lib/badline/memory.rb
CHANGED
|
@@ -46,10 +46,8 @@ module Badline
|
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def zero_fill(initial)
|
|
49
|
-
array =
|
|
50
|
-
|
|
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 =
|
|
84
|
+
register = registers[bit]
|
|
74
85
|
stage = (register ^ (incoming >> bit)) & 1
|
|
75
|
-
|
|
86
|
+
registers[bit] = (register >> 1) | (stage << (LENGTHS[bit] - 1))
|
|
76
87
|
end
|
|
77
88
|
end
|
|
78
89
|
end
|
data/lib/badline/pet/superpet.rb
CHANGED
|
@@ -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.
|
data/lib/badline/roms/README
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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
|