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