badline 0.16.1 → 0.18.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 +213 -0
- data/CLAUDE.md +11 -7
- data/CONTRIBUTING.md +7 -1
- data/README.md +265 -26
- data/doc/media.md +5 -2
- data/doc/pinned-behaviour.md +189 -0
- data/doc/snapshot-format.md +616 -74
- data/doc/snapshots.md +192 -24
- data/exe/badline-ruby +5 -0
- data/lib/badline/audio/terminal.rb +34 -2
- data/lib/badline/c128/keyed_state.rb +2 -0
- data/lib/badline/c128.rb +1 -0
- data/lib/badline/c64/keyed_state.rb +2 -0
- data/lib/badline/c64.rb +1 -0
- 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 +95 -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 +264 -0
- data/lib/badline/cbm2/sid_page.rb +19 -0
- data/lib/badline/cbm2.rb +270 -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/cia/timer.rb +2 -2
- data/lib/badline/chips/cpu/instruction_set.rb +1 -1
- data/lib/badline/chips/crtc.rb +3 -2
- data/lib/badline/chips/m6809/arithmetic.rb +140 -0
- data/lib/badline/chips/m6809/bus_steps.rb +223 -0
- data/lib/badline/chips/m6809/core.rb +149 -0
- data/lib/badline/chips/m6809/indexed.rb +82 -0
- data/lib/badline/chips/m6809/interrupts.rb +159 -0
- data/lib/badline/chips/m6809/keyed_state.rb +147 -0
- data/lib/badline/chips/m6809/opcodes.rb +159 -0
- data/lib/badline/chips/m6809/operations.rb +258 -0
- data/lib/badline/chips/m6809/plans.rb +143 -0
- data/lib/badline/chips/m6809/registers.rb +82 -0
- data/lib/badline/chips/m6809/stack.rb +140 -0
- data/lib/badline/chips/m6809.rb +13 -0
- 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 +91 -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 +134 -0
- data/lib/badline/chips/sid/filter.rb +4 -4
- data/lib/badline/chips/sid/waveform.rb +6 -6
- 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/chips/vic_i/keyed_parts.rb +7 -2
- data/lib/badline/chips/vic_i.rb +84 -21
- data/lib/badline/data_path.rb +3 -3
- data/lib/badline/drives/datasette/keyed_state.rb +9 -1
- data/lib/badline/drives/datasette.rb +27 -3
- data/lib/badline/drives/ieee_models.rb +54 -0
- data/lib/badline/drives/model1001.rb +37 -0
- data/lib/badline/drives/model2031.rb +6 -0
- data/lib/badline/drives/model4040/disk.rb +62 -0
- data/lib/badline/drives/model4040/ip_bus.rb +46 -0
- data/lib/badline/drives/model4040/keyed_state.rb +57 -0
- data/lib/badline/drives/model4040/mechanism.rb +192 -0
- data/lib/badline/drives/model4040/parts.rb +20 -0
- data/lib/badline/drives/model4040.rb +179 -0
- data/lib/badline/drives/model8050/board.rb +77 -0
- data/lib/badline/drives/model8050/disk.rb +163 -0
- data/lib/badline/drives/model8050/dos_board.rb +91 -0
- data/lib/badline/drives/model8050/fdc_bus.rb +102 -0
- data/lib/badline/drives/model8050/idle.rb +181 -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 +163 -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 +131 -0
- data/lib/badline/drives/model9060/idle.rb +149 -0
- data/lib/badline/drives/model9060/keyed_state.rb +123 -0
- data/lib/badline/drives/model9060/parts.rb +98 -0
- data/lib/badline/drives/model9060/sasi_bus.rb +88 -0
- data/lib/badline/drives/model9060/sasi_commands.rb +168 -0
- data/lib/badline/drives/model9060/sasi_target.rb +161 -0
- data/lib/badline/drives/model9060.rb +195 -0
- data/lib/badline/frontend/app.rb +18 -3
- data/lib/badline/frontend/boot.rb +4 -3
- data/lib/badline/frontend/controls.rb +107 -15
- data/lib/badline/frontend/gamepads.rb +3 -3
- data/lib/badline/frontend/keys.rb +97 -2
- data/lib/badline/frontend/kim1_panel.rb +208 -0
- data/lib/badline/frontend/media_slots.rb +8 -1
- data/lib/badline/frontend/menu/drive_types.rb +56 -0
- data/lib/badline/frontend/menu/menu_dialogs.rb +61 -14
- data/lib/badline/frontend/menu/menu_media.rb +47 -14
- data/lib/badline/frontend/menu/menu_pages.rb +113 -4
- data/lib/badline/frontend/menu/menu_ports.rb +77 -4
- data/lib/badline/frontend/menu/pause_menu.rb +6 -2
- data/lib/badline/frontend/pet_keyboard.rb +45 -7
- data/lib/badline/frontend/screen.rb +1 -1
- data/lib/badline/frontend/screens.rb +40 -2
- data/lib/badline/frontend/teletype_pane.rb +152 -0
- data/lib/badline/frontend/timeline.rb +49 -11
- data/lib/badline/frontend.rb +3 -0
- 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 +4 -2
- data/lib/badline/kernal_trap/ieee_layout.rb +95 -43
- data/lib/badline/kim1/bus.rb +117 -0
- data/lib/badline/kim1/cpu.rb +25 -0
- data/lib/badline/kim1/host_terminal.rb +74 -0
- data/lib/badline/kim1/key_entry.rb +74 -0
- data/lib/badline/kim1/keyed_modules.rb +53 -0
- data/lib/badline/kim1/keyed_state.rb +133 -0
- data/lib/badline/kim1/panel.rb +176 -0
- data/lib/badline/kim1/rriots.rb +28 -0
- data/lib/badline/kim1/sound.rb +29 -0
- data/lib/badline/kim1/tape.rb +116 -0
- data/lib/badline/kim1/teletype.rb +193 -0
- data/lib/badline/kim1.rb +190 -0
- data/lib/badline/machine/ieee_drives.rb +171 -0
- data/lib/badline/machine/keyboard_buffer.rb +20 -3
- data/lib/badline/machine/line_sound.rb +122 -0
- data/lib/badline/machine/settling.rb +4 -0
- data/lib/badline/machine.rb +62 -14
- data/lib/badline/media/cbm2_media.rb +29 -0
- data/lib/badline/media/extensions.rb +8 -4
- data/lib/badline/media/kim1_media.rb +150 -0
- data/lib/badline/media/pet_media.rb +3 -1
- data/lib/badline/media/program.rb +6 -3
- data/lib/badline/media/true_drive.rb +51 -25
- data/lib/badline/media.rb +23 -8
- data/lib/badline/options/family.rb +108 -6
- data/lib/badline/options/help.rb +58 -12
- data/lib/badline/options/table.rb +60 -14
- data/lib/badline/options/validation.rb +71 -11
- data/lib/badline/options.rb +46 -13
- data/lib/badline/pet/bus.rb +16 -2
- data/lib/badline/pet/cassettes.rb +22 -1
- data/lib/badline/pet/ieee_drive.rb +5 -64
- data/lib/badline/pet/ieee_ports.rb +7 -0
- data/lib/badline/pet/keyboard_ports.rb +9 -2
- data/lib/badline/pet/keyed_state.rb +4 -0
- data/lib/badline/pet/model.rb +11 -2
- data/lib/badline/pet/rom_sets.rb +1 -0
- data/lib/badline/pet/sound.rb +5 -105
- data/lib/badline/pet/superpet/acia.rb +12 -0
- data/lib/badline/pet/superpet/board.rb +221 -0
- data/lib/badline/pet/superpet/dongle.rb +81 -0
- data/lib/badline/pet/superpet/keyed_modules.rb +63 -0
- data/lib/badline/pet/superpet/keyed_parts.rb +76 -0
- data/lib/badline/pet/superpet/m6809.rb +32 -0
- data/lib/badline/pet/superpet/waterloo_traps.rb +199 -0
- data/lib/badline/pet/superpet.rb +216 -0
- data/lib/badline/pet.rb +33 -6
- data/lib/badline/plus4/acia.rb +4 -298
- data/lib/badline/plus4/keyed_parts.rb +0 -44
- data/lib/badline/plus4/keyed_state.rb +2 -0
- data/lib/badline/plus4.rb +2 -0
- data/lib/badline/roms/1001/fdc-251257-02a.rom +0 -0
- data/lib/badline/roms/4040/dos-1.2.rom +0 -0
- data/lib/badline/roms/4040/dos-2.1-rev.rom +0 -0
- data/lib/badline/roms/4040/dos-2.1.rom +0 -0
- data/lib/badline/roms/4040/fdc-901466-02.rom +0 -0
- data/lib/badline/roms/4040/fdc-901466-04.rom +0 -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/9060/dos-3.0.rom +0 -0
- data/lib/badline/roms/9060/sasi-300515-revb.rom +0 -0
- data/lib/badline/roms/README +277 -0
- 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/kim1/6530-002.rom +0 -0
- data/lib/badline/roms/kim1/6530-003.rom +0 -0
- data/lib/badline/roms/pet/characters-superpet.rom +0 -0
- data/lib/badline/roms/pet/superpet-a000.rom +0 -0
- data/lib/badline/roms/pet/superpet-c000.rom +0 -0
- data/lib/badline/roms/pet/superpet-e000-50hz.rom +0 -0
- data/lib/badline/roms/pet/superpet-e000.rom +0 -0
- data/lib/badline/snapshot/cbm2_setup.rb +53 -0
- data/lib/badline/snapshot/container.rb +9 -2
- data/lib/badline/snapshot/field_doc/markdown.rb +4 -2
- data/lib/badline/snapshot/field_doc.rb +59 -3
- data/lib/badline/snapshot/image.rb +65 -9
- data/lib/badline/snapshot/kim1_setup.rb +28 -0
- data/lib/badline/snapshot/pet_setup.rb +27 -11
- data/lib/badline/snapshot/storage_setup.rb +4 -1
- data/lib/badline/snapshot/vic20_setup.rb +11 -7
- 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 +20 -10
- data/lib/badline/snapshot/vice/pet_cpu.rb +42 -5
- data/lib/badline/snapshot/vice/pet_crtc.rb +25 -17
- data/lib/badline/snapshot/vice/pet_drive.rb +180 -0
- data/lib/badline/snapshot/vice/pet_drive8050.rb +336 -0
- data/lib/badline/snapshot/vice/pet_mem.rb +49 -5
- data/lib/badline/snapshot/vice/pet_restore.rb +100 -7
- data/lib/badline/snapshot/vice/pet_via.rb +6 -5
- 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 +3 -0
- data/lib/badline/snapshot.rb +15 -3
- data/lib/badline/status.rb +30 -30
- data/lib/badline/storage/crt_file.rb +7 -1
- data/lib/badline/storage/d64_image.rb +7 -0
- data/lib/badline/storage/d67_image.rb +55 -0
- data/lib/badline/storage/d80_image.rb +17 -1
- data/lib/badline/storage/d81_image.rb +14 -0
- data/lib/badline/storage/d90_image.rb +180 -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 +37 -2
- data/lib/badline/storage/disk_image.rb +2 -0
- data/lib/badline/storage/kim_tape.rb +254 -0
- data/lib/badline/storage/papertape.rb +90 -0
- data/lib/badline/storage.rb +5 -0
- data/lib/badline/support/cbm2_checkpoint.rb +48 -0
- data/lib/badline/support/kim1_checkpoint.rb +41 -0
- data/lib/badline/support/p500_checkpoint.rb +35 -0
- data/lib/badline/support/superpet_checkpoint.rb +35 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic20/keyed_modules.rb +6 -3
- data/lib/badline/vic20/keyed_state.rb +6 -4
- data/lib/badline/vic20.rb +26 -12
- data/lib/badline.rb +2 -0
- metadata +148 -2
|
@@ -11,7 +11,8 @@ module Badline
|
|
|
11
11
|
# Both record from VIA PB3. The KERNAL reads each tape as the falling
|
|
12
12
|
# edges its read line makes, as the C64 and VIC-20 do. The machine
|
|
13
13
|
# passes the motor and write lines on after each write to the I/O
|
|
14
|
-
# chips (#lines_written)
|
|
14
|
+
# chips (#lines_written), and clocks the decks only while a tape runs
|
|
15
|
+
# (#running?).
|
|
15
16
|
class Cassettes
|
|
16
17
|
attr_reader :datasette1, :datasette2
|
|
17
18
|
|
|
@@ -22,6 +23,18 @@ module Badline
|
|
|
22
23
|
@datasette2 = Drives::Datasette.new
|
|
23
24
|
@datasette1.on_flag { pulse1 }
|
|
24
25
|
@datasette2.on_flag { pulse2 }
|
|
26
|
+
@running = false
|
|
27
|
+
@running_handler = nil
|
|
28
|
+
@datasette1.on_running_change { update_running }
|
|
29
|
+
@datasette2.on_running_change { update_running }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Whether either deck's tape runs.
|
|
33
|
+
def running? = @running
|
|
34
|
+
|
|
35
|
+
# Whether either deck's tape runs, each time that changes.
|
|
36
|
+
def on_running_change(&handler)
|
|
37
|
+
@running_handler = handler
|
|
25
38
|
end
|
|
26
39
|
|
|
27
40
|
def cycle!
|
|
@@ -48,6 +61,14 @@ module Badline
|
|
|
48
61
|
|
|
49
62
|
private
|
|
50
63
|
|
|
64
|
+
def update_running
|
|
65
|
+
running = @datasette1.running? || @datasette2.running?
|
|
66
|
+
return if running == @running
|
|
67
|
+
|
|
68
|
+
@running = running
|
|
69
|
+
@running_handler&.call
|
|
70
|
+
end
|
|
71
|
+
|
|
51
72
|
# A falling edge on a read line, and the line back up.
|
|
52
73
|
def pulse1
|
|
53
74
|
@pia1.ca1 = false
|
|
@@ -1,76 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "badline/
|
|
3
|
+
require "badline/machine/ieee_drives"
|
|
4
4
|
|
|
5
5
|
module Badline
|
|
6
6
|
class PET
|
|
7
|
-
# The PET's true drive
|
|
8
|
-
# (IEEEPorts),
|
|
9
|
-
# the PET's RES line reaches IFC. The traps on the KERNAL's primitives
|
|
10
|
-
# (IEEETraps) leave device 8 to the drive while it is device 8.
|
|
7
|
+
# The PET's true drive, a 2031, 8050 or 8250 on its IEEE-488 bus
|
|
8
|
+
# (IEEEPorts), as Machine::IEEEDrives has it, at the PET's 1 MHz.
|
|
11
9
|
module IEEEDrive
|
|
12
|
-
|
|
13
|
-
attr_reader :drive2031
|
|
14
|
-
|
|
15
|
-
# Plugs in a Drives::Model2031, in place of the one there.
|
|
16
|
-
def attach_drive2031(drive)
|
|
17
|
-
detach_drive2031
|
|
18
|
-
drive.host_clock_hz = 1_000_000
|
|
19
|
-
drive.connect(@ieee.bus)
|
|
20
|
-
@drive2031 = drive
|
|
21
|
-
@ieee.lines_written
|
|
22
|
-
@ieee_trap&.device = trap_device
|
|
23
|
-
@init_threshold = [@init_threshold, Badline::Drives::Model2031::BOOT_CYCLES].max if @cycles < @init_threshold
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# Unplugs the 2031, leaving it on a bus of its own.
|
|
27
|
-
def detach_drive2031
|
|
28
|
-
drive = @drive2031
|
|
29
|
-
return unless drive
|
|
30
|
-
|
|
31
|
-
drive.connect(Badline::Drives::IEEEBus.new)
|
|
32
|
-
@drive2031 = nil
|
|
33
|
-
@ieee_trap&.device = trap_device
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# The true drive on the bus, or nil.
|
|
37
|
-
def true_drive = @drive2031
|
|
38
|
-
|
|
39
|
-
# Plugs in a 2031 as device 8, in place of the drive there, and
|
|
40
|
-
# returns it.
|
|
41
|
-
def plug_true_drive
|
|
42
|
-
Badline::Drives::Model2031.new.tap { |drive| attach_drive2031(drive) }
|
|
43
|
-
end
|
|
10
|
+
include Machine::IEEEDrives
|
|
44
11
|
|
|
45
12
|
private
|
|
46
13
|
|
|
47
|
-
|
|
48
|
-
# BL-D8, BL-D8-CPU and so on for device 8.
|
|
49
|
-
def save_drive_modules(sections)
|
|
50
|
-
drive = @drive2031
|
|
51
|
-
drive&.save_modules(sections, "BL-D#{drive.device}")
|
|
52
|
-
sections
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
# The 2031 a snapshot's modules hold, built afresh unless one is on
|
|
56
|
-
# the bus as the same device, or none when they hold no drive. The
|
|
57
|
-
# bus then carries the PET's lines as its chips left them.
|
|
58
|
-
def load_drive_modules(modules)
|
|
59
|
-
device = 8
|
|
60
|
-
input = modules.optional("BL-D8")
|
|
61
|
-
while input.nil? && device < 11
|
|
62
|
-
device += 1
|
|
63
|
-
input = modules.optional("BL-D#{device}")
|
|
64
|
-
end
|
|
65
|
-
if input
|
|
66
|
-
detach_drive2031 if @drive2031 && @drive2031.device != device
|
|
67
|
-
attach_drive2031(Badline::Drives::Model2031.new(device:)) unless @drive2031
|
|
68
|
-
@drive2031.load_modules(modules, input, "BL-D#{device}")
|
|
69
|
-
else
|
|
70
|
-
detach_drive2031
|
|
71
|
-
end
|
|
72
|
-
@ieee.lines_written
|
|
73
|
-
end
|
|
14
|
+
def ieee_host_clock_hz = 1_000_000
|
|
74
15
|
end
|
|
75
16
|
end
|
|
76
17
|
end
|
|
@@ -57,6 +57,13 @@ module Badline
|
|
|
57
57
|
bus.host_lines = controller_lines unless bus.drives.empty?
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
+
# The drives on the bus see the lines the PET pulls now.
|
|
61
|
+
def push_lines = lines_written
|
|
62
|
+
|
|
63
|
+
# Nothing to push once a cycle: the PET pushes its lines as its chips
|
|
64
|
+
# are written.
|
|
65
|
+
def cycle_lines = nil
|
|
66
|
+
|
|
60
67
|
private
|
|
61
68
|
|
|
62
69
|
def low_lines = @bus.low_lines(controller_lines)
|
|
@@ -42,8 +42,12 @@ module Badline
|
|
|
42
42
|
|
|
43
43
|
attr_reader :keyboard
|
|
44
44
|
|
|
45
|
+
# Whether the SuperPET's board pulls the diagnostic sense low.
|
|
46
|
+
attr_accessor :diagnostic_low
|
|
47
|
+
|
|
45
48
|
def initialize(keyboard)
|
|
46
49
|
@keyboard = keyboard
|
|
50
|
+
@diagnostic_low = false
|
|
47
51
|
@pia = nil
|
|
48
52
|
@ieee = nil
|
|
49
53
|
@cassettes = nil
|
|
@@ -60,8 +64,11 @@ module Badline
|
|
|
60
64
|
|
|
61
65
|
# Port A's other lines: PA4 and PA5 read the cassette sense switches,
|
|
62
66
|
# PA6 the IEEE bus's EOI, and the diagnostic sense on PA7 floats high
|
|
63
|
-
# with nothing on it.
|
|
64
|
-
def read_a(_lines)
|
|
67
|
+
# with nothing on it but the SuperPET's board, which can pull it low.
|
|
68
|
+
def read_a(_lines)
|
|
69
|
+
lines = @ieee.eoi_input & @cassettes.sense_input
|
|
70
|
+
@diagnostic_low ? lines & 0x7f : lines
|
|
71
|
+
end
|
|
65
72
|
|
|
66
73
|
def read_b(lines)
|
|
67
74
|
row = @pia.port_a_output & 0x0f
|
|
@@ -25,7 +25,9 @@ module Badline
|
|
|
25
25
|
out.int("cycles", @cycles).bool("vertical_drive", @vertical_drive)
|
|
26
26
|
pending = @pending_keys
|
|
27
27
|
out.ints("host.pending_keys", pending) if pending
|
|
28
|
+
save_init_keys(out)
|
|
28
29
|
out.int("host.init_pending", @cycles <= @init_threshold ? @init_handlers.length : 0)
|
|
30
|
+
save_ieee_drive_fields(out)
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
def load_machine_fields(input)
|
|
@@ -33,7 +35,9 @@ module Badline
|
|
|
33
35
|
@vertical_drive = input.bool("vertical_drive", @vertical_drive)
|
|
34
36
|
pending = input.ints("host.pending_keys", [])
|
|
35
37
|
@pending_keys = pending.empty? ? nil : pending.dup
|
|
38
|
+
load_init_keys(input)
|
|
36
39
|
@init_handlers_lost = [input.int("host.init_pending", 0) - @init_handlers.length, 0].max
|
|
40
|
+
load_ieee_drive_fields(input)
|
|
37
41
|
end
|
|
38
42
|
end
|
|
39
43
|
|
data/lib/badline/pet/model.rb
CHANGED
|
@@ -17,7 +17,8 @@ module Badline
|
|
|
17
17
|
# 1K twice over and the 80-column ones with 2K. The 8296 has 128K of
|
|
18
18
|
# dynamic RAM, the 32K below $8000, the 32K above it, of which the
|
|
19
19
|
# screen is the start and the rest lies under the ROMs, and the 64K
|
|
20
|
-
# board (Bus::Expansion).
|
|
20
|
+
# board (Bus::Expansion). The SuperPET is an 8032 with the 901640-01
|
|
21
|
+
# character ROM, twice the 8032's size, and its own board (SuperPET).
|
|
21
22
|
module Model
|
|
22
23
|
Profile = Data.define(:name, :ram_kb, :basic, :editor_50hz, :editor_60hz, :kernal, :characters, :keyboard,
|
|
23
24
|
:video, :expansion) do
|
|
@@ -28,6 +29,9 @@ module Badline
|
|
|
28
29
|
# The 8296, whose board decodes its memory in a PLA.
|
|
29
30
|
def cbm8296? = name == "8296"
|
|
30
31
|
|
|
32
|
+
# The SuperPET, an 8032 with the Waterloo board (SuperPET).
|
|
33
|
+
def superpet? = name == "superpet"
|
|
34
|
+
|
|
31
35
|
# The screen RAM's bytes, and on the 8296 all 32K of RAM from $8000.
|
|
32
36
|
def screen_bytes
|
|
33
37
|
return 0x8000 if cbm8296?
|
|
@@ -86,7 +90,12 @@ module Badline
|
|
|
86
90
|
kernal: "pet/kernal4.rom", characters: "pet/characters2.rom", keyboard: :business,
|
|
87
91
|
video: :crtc80, expansion: true)
|
|
88
92
|
|
|
89
|
-
|
|
93
|
+
SUPERPET = Profile.new(name: "superpet", ram_kb: 32, basic: "pet/basic4.rom",
|
|
94
|
+
editor_50hz: "pet/editor4-80-b-50hz.rom", editor_60hz: "pet/editor4-80-b-60hz.rom",
|
|
95
|
+
kernal: "pet/kernal4.rom", characters: "pet/characters-superpet.rom",
|
|
96
|
+
keyboard: :business, video: :crtc80, expansion: false)
|
|
97
|
+
|
|
98
|
+
ALL = [PET2001, PET3032, PET3032B, PET4032, CBM8032, CBM8096, CBM8296, SUPERPET].freeze
|
|
90
99
|
|
|
91
100
|
DEFAULT = "8032"
|
|
92
101
|
|
data/lib/badline/pet/rom_sets.rb
CHANGED
|
@@ -39,6 +39,7 @@ module Badline
|
|
|
39
39
|
"4032" => { "4" => %w[4-40-n-50hz 4-40-n-60hz] },
|
|
40
40
|
"8032" => { "4" => %w[4-80-b-50hz 4-80-b-50hz-0283 4-80-b-60hz] },
|
|
41
41
|
"8096" => { "4" => %w[4-80-b-50hz 4-80-b-50hz-0283 4-80-b-60hz] },
|
|
42
|
+
"superpet" => { "4" => %w[4-80-b-50hz 4-80-b-50hz-0283 4-80-b-60hz] },
|
|
42
43
|
"8296" => { "4" => %w[4-80-b-50hz-8296 4-80-b-50hz 4-80-b-50hz-0283 4-80-b-60hz] }
|
|
43
44
|
}.freeze
|
|
44
45
|
|
data/lib/badline/pet/sound.rb
CHANGED
|
@@ -8,121 +8,21 @@ module Badline
|
|
|
8
8
|
# added. CB2 carries the shift register's bits in its shift-out modes,
|
|
9
9
|
# mode 4's free-running square wave under timer 2 for the bell and most
|
|
10
10
|
# PET music, and the PCR's CB2 output otherwise, held high or low by
|
|
11
|
-
# hand or floating high as an input.
|
|
12
|
-
#
|
|
13
|
-
# The machine calls #cycle! once a cycle while #recording?, from #record
|
|
14
|
-
# on, so a sound nobody records costs only that check. Each sample is
|
|
15
|
-
# the share of its cycles CB2 spent high, through the output stage: a
|
|
16
|
-
# one-pole low-pass at 5 kHz and a one-pole high-pass at 100 Hz, so a
|
|
17
|
-
# line left high or low settles to silence.
|
|
11
|
+
# hand or floating high as an input. Machine::LineSound turns it into
|
|
12
|
+
# samples.
|
|
18
13
|
class Sound
|
|
19
|
-
|
|
20
|
-
HIGH_PASS_HZ = 100
|
|
21
|
-
|
|
22
|
-
# A swing from CB2 low to CB2 high, as a signed 16-bit sample.
|
|
23
|
-
SCALE = 24_000.0
|
|
14
|
+
include Machine::LineSound
|
|
24
15
|
|
|
25
16
|
def initialize(via)
|
|
26
17
|
@via = via
|
|
27
|
-
|
|
28
|
-
@high = true
|
|
29
|
-
@run = 0
|
|
30
|
-
@rate = 0
|
|
31
|
-
@clock_hz = 0
|
|
32
|
-
@remainder = 0
|
|
33
|
-
@window = 0
|
|
34
|
-
@left = 0
|
|
35
|
-
@sum = 0
|
|
36
|
-
@low = 0.0
|
|
37
|
-
@drift = 0.0
|
|
38
|
-
@low_pass = 0.0
|
|
39
|
-
@high_pass = 0.0
|
|
40
|
-
@samples = []
|
|
18
|
+
init_line_sound
|
|
41
19
|
end
|
|
42
20
|
|
|
43
21
|
def model = :cb2
|
|
44
22
|
|
|
45
|
-
attr_reader :recording
|
|
46
|
-
alias recording? recording
|
|
47
|
-
|
|
48
|
-
# Starts collecting the output, averaged down to `rate` samples a
|
|
49
|
-
# second at `clock_hz` cycles a second, for #drain_samples. The
|
|
50
|
-
# output stage takes CB2's level now as settled.
|
|
51
|
-
def record(rate:, clock_hz: 1_000_000)
|
|
52
|
-
@rate = rate
|
|
53
|
-
@clock_hz = clock_hz
|
|
54
|
-
@remainder = 0
|
|
55
|
-
@window = next_window
|
|
56
|
-
@left = @window
|
|
57
|
-
@sum = 0
|
|
58
|
-
@high = @via.cb2_output
|
|
59
|
-
@run = 0
|
|
60
|
-
@low = @drift = @high ? 1.0 : 0.0
|
|
61
|
-
@low_pass = 1.0 - Math.exp(-2.0 * Math::PI * LOW_PASS_HZ / rate)
|
|
62
|
-
@high_pass = 1.0 - Math.exp(-2.0 * Math::PI * HIGH_PASS_HZ / rate)
|
|
63
|
-
@samples = []
|
|
64
|
-
@recording = true
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
# Takes CB2's level for this cycle, while recording.
|
|
68
|
-
def cycle!
|
|
69
|
-
high = @via.cb2_output
|
|
70
|
-
if high == @high
|
|
71
|
-
@run += 1
|
|
72
|
-
else
|
|
73
|
-
hold(@high, @run)
|
|
74
|
-
@high = high
|
|
75
|
-
@run = 1
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# The samples closed since the last drain.
|
|
80
|
-
def drain_samples
|
|
81
|
-
return [] unless @recording
|
|
82
|
-
|
|
83
|
-
hold(@high, @run)
|
|
84
|
-
@run = 0
|
|
85
|
-
samples = @samples
|
|
86
|
-
@samples = []
|
|
87
|
-
samples
|
|
88
|
-
end
|
|
89
|
-
|
|
90
23
|
private
|
|
91
24
|
|
|
92
|
-
|
|
93
|
-
# they finish.
|
|
94
|
-
def hold(high, cycles)
|
|
95
|
-
left = @left
|
|
96
|
-
while cycles >= left
|
|
97
|
-
@sum += left if high
|
|
98
|
-
cycles -= left
|
|
99
|
-
close
|
|
100
|
-
left = @left
|
|
101
|
-
end
|
|
102
|
-
@sum += cycles if high
|
|
103
|
-
@left = left - cycles
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def close
|
|
107
|
-
@low += @low_pass * (@sum.fdiv(@window) - @low)
|
|
108
|
-
@drift += @high_pass * (@low - @drift)
|
|
109
|
-
sample = ((@low - @drift) * SCALE).round
|
|
110
|
-
sample = 32_767 if sample > 32_767
|
|
111
|
-
sample = -32_768 if sample < -32_768
|
|
112
|
-
@samples << sample
|
|
113
|
-
@sum = 0
|
|
114
|
-
@window = next_window
|
|
115
|
-
@left = @window
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
# Each window is a whole number of cycles, and they add up to the
|
|
119
|
-
# clock's cycles over a second.
|
|
120
|
-
def next_window
|
|
121
|
-
@remainder += @clock_hz
|
|
122
|
-
cycles = @remainder / @rate
|
|
123
|
-
@remainder -= cycles * @rate
|
|
124
|
-
cycles
|
|
125
|
-
end
|
|
25
|
+
def line_high? = @via.cb2_output
|
|
126
26
|
end
|
|
127
27
|
end
|
|
128
28
|
end
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class PET
|
|
5
|
+
class SuperPET < PET
|
|
6
|
+
# The Waterloo board, after Levak's PET-Interfaces.txt, André
|
|
7
|
+
# Fachat's SuperPET page and Commodore's 324037 schematic: 64K of
|
|
8
|
+
# RAM in 16 banks of 4K, one at a time at $9000-$9FFF, and the I/O
|
|
9
|
+
# page at $EF00 (IOPage), which both CPUs reach.
|
|
10
|
+
#
|
|
11
|
+
# The bank latch at $EFFC-$EFFD picks the bank with bits 0-3, and its
|
|
12
|
+
# bit 7 lets writes reach the system latch at $EFF8-$EFFB:
|
|
13
|
+
#
|
|
14
|
+
# bit 0 the CPU while SW2 is at PROG: 1 the 6502, 0 the 6809
|
|
15
|
+
# bit 1 the bank RAM while SW1 is at PROG: 1 read/write, 0 read only
|
|
16
|
+
# bit 3 pulls the user port's diagnostic sense, PIA 1's PA7, low
|
|
17
|
+
#
|
|
18
|
+
# Bit 0 of $EFFE-$EFFF puts the board's own ROM socket at $9000 in
|
|
19
|
+
# place of the RAM, which badline leaves empty, while the 6502 runs.
|
|
20
|
+
# All three are write-only, and the RES line clears them, so the
|
|
21
|
+
# board starts at bank 0, and with SW2 at PROG with the 6809.
|
|
22
|
+
#
|
|
23
|
+
# SW1 is READ, R/W or PROG, the bank RAM's write protect, and SW2
|
|
24
|
+
# PROG, 6502 or 6809, the CPU. A write to the bank RAM while it is
|
|
25
|
+
# protected goes nowhere.
|
|
26
|
+
#
|
|
27
|
+
# While the 6809 runs, the 8032's ROMs step aside through its NO ROM
|
|
28
|
+
# line, and the Waterloo ROMs answer at $A000-$E7FF and $F000-$FFFF,
|
|
29
|
+
# the $E000 one with the CRTC table for the frame rate.
|
|
30
|
+
class Board
|
|
31
|
+
CPU_SWITCH = %w[6502 6809 prog].freeze
|
|
32
|
+
|
|
33
|
+
RAM_SWITCH = %w[rw read prog].freeze
|
|
34
|
+
|
|
35
|
+
# $EF00-$EFFF: the 6702 at $EFE0-$EFE3, the ACIA at $EFF0-$EFF3
|
|
36
|
+
# and the latches above. A read of a latch finds the last byte on
|
|
37
|
+
# the bus, and below $EFF0, where neither chip answers, $FF.
|
|
38
|
+
class IOPage
|
|
39
|
+
def initialize(board, bus, dongle, acia)
|
|
40
|
+
@board = board
|
|
41
|
+
@bus = bus
|
|
42
|
+
@dongle = dongle
|
|
43
|
+
@acia = acia
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def peek(addr)
|
|
47
|
+
low = addr & 0xff
|
|
48
|
+
return @bus.data if low >= 0xf4
|
|
49
|
+
return @acia.peek(addr) if low >= 0xf0
|
|
50
|
+
return @dongle.peek(addr) if low >= 0xe0 && low < 0xe4
|
|
51
|
+
|
|
52
|
+
0xff
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def poke(addr, value)
|
|
56
|
+
low = addr & 0xff
|
|
57
|
+
if low >= 0xfe
|
|
58
|
+
@board.socket = value
|
|
59
|
+
elsif low >= 0xfc
|
|
60
|
+
@board.bank_latch = value
|
|
61
|
+
elsif low >= 0xf8
|
|
62
|
+
@board.system_latch = value
|
|
63
|
+
elsif low >= 0xf0
|
|
64
|
+
@acia.poke(addr, value) if low < 0xf4
|
|
65
|
+
elsif low >= 0xe0
|
|
66
|
+
@dongle.poke(addr, value) if low < 0xe4
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
attr_reader :cells, :bank_latch, :system_latch, :cpu_switch, :ram_switch
|
|
72
|
+
|
|
73
|
+
# Bit 0 of $EFFE: 1 for the ROM socket at $9000.
|
|
74
|
+
attr_reader :socket
|
|
75
|
+
|
|
76
|
+
# +bus+ is the PET's, whose pages the board maps, and
|
|
77
|
+
# +keyboard_ports+ PIA 1's, whose PA7 the diagnostic sense is.
|
|
78
|
+
# +cpu_switch+ and +ram_switch+ are SW2's and SW1's positions, one
|
|
79
|
+
# of CPU_SWITCH's and RAM_SWITCH's, and +refresh_hz+ picks the
|
|
80
|
+
# Waterloo $E000 ROM, 50 or 60.
|
|
81
|
+
def initialize(bus, dongle:, acia:, keyboard_ports:, cpu_switch: "6502", ram_switch: "rw",
|
|
82
|
+
refresh_hz: 50)
|
|
83
|
+
raise ArgumentError, "the CPU switch has no position #{cpu_switch}" unless CPU_SWITCH.include?(cpu_switch)
|
|
84
|
+
raise ArgumentError, "the RAM switch has no position #{ram_switch}" unless RAM_SWITCH.include?(ram_switch)
|
|
85
|
+
|
|
86
|
+
@bus = bus
|
|
87
|
+
@dongle = dongle
|
|
88
|
+
@keyboard_ports = keyboard_ports
|
|
89
|
+
@cpu_switch = cpu_switch
|
|
90
|
+
@ram_switch = ram_switch
|
|
91
|
+
@cells = Array.new(0x10000, 0)
|
|
92
|
+
@banks = Array.new(16) { |bank| Bus::Expansion::Window.new(@cells, bank << 12, 0x9000) }
|
|
93
|
+
@io = IOPage.new(self, bus, dongle, acia)
|
|
94
|
+
@bank_latch = 0
|
|
95
|
+
@system_latch = 0
|
|
96
|
+
@socket = 0
|
|
97
|
+
@rom_a = ROM.load("pet/superpet-a000.rom", 0xa000)
|
|
98
|
+
@rom_c = ROM.load("pet/superpet-c000.rom", 0xc000)
|
|
99
|
+
@rom_e = ROM.load(refresh_hz == 50 ? "pet/superpet-e000-50hz.rom" : "pet/superpet-e000.rom", 0xe000)
|
|
100
|
+
@m6809 = m6809_selected?
|
|
101
|
+
@select_handler = nil
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Calls the block when the CPU the switch and the system latch pick
|
|
105
|
+
# changes.
|
|
106
|
+
def on_select(&handler)
|
|
107
|
+
@select_handler = handler
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Whether the 6809 has the bus.
|
|
111
|
+
def m6809? = @m6809
|
|
112
|
+
|
|
113
|
+
def bank = @bank_latch & 0x0f
|
|
114
|
+
|
|
115
|
+
def bank_latch=(value)
|
|
116
|
+
@bank_latch = value
|
|
117
|
+
@bus.map_pages
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def system_latch=(value)
|
|
121
|
+
return if @bank_latch.nobits?(0x80)
|
|
122
|
+
|
|
123
|
+
@system_latch = value
|
|
124
|
+
@keyboard_ports.diagnostic_low = value.anybits?(0x08)
|
|
125
|
+
select
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def socket=(value)
|
|
129
|
+
@socket = @m6809 ? 0 : value & 0x01
|
|
130
|
+
@bus.map_pages
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Moves SW2, which picks the CPU at the next RES.
|
|
134
|
+
def cpu_switch=(position)
|
|
135
|
+
raise ArgumentError, "the CPU switch has no position #{position}" unless CPU_SWITCH.include?(position)
|
|
136
|
+
|
|
137
|
+
@cpu_switch = position
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Moves SW1, which protects the bank RAM from the next write on.
|
|
141
|
+
def ram_switch=(position)
|
|
142
|
+
raise ArgumentError, "the RAM switch has no position #{position}" unless RAM_SWITCH.include?(position)
|
|
143
|
+
|
|
144
|
+
@ram_switch = position
|
|
145
|
+
@bus.map_pages
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Whether writes to the bank RAM go nowhere.
|
|
149
|
+
def write_protected?
|
|
150
|
+
case @ram_switch
|
|
151
|
+
when "read" then true
|
|
152
|
+
when "rw" then false
|
|
153
|
+
else @system_latch.nobits?(0x02)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Whether the CPU switch, or the system latch with it at PROG,
|
|
158
|
+
# picks the 6809.
|
|
159
|
+
def m6809_selected?
|
|
160
|
+
case @cpu_switch
|
|
161
|
+
when "6502" then false
|
|
162
|
+
when "6809" then true
|
|
163
|
+
else @system_latch.nobits?(0x01)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# The RES line clears the latches, and the 6702.
|
|
168
|
+
def reset!
|
|
169
|
+
@bank_latch = 0
|
|
170
|
+
@system_latch = 0
|
|
171
|
+
@socket = 0
|
|
172
|
+
@keyboard_ports.diagnostic_low = false
|
|
173
|
+
@dongle.reset!
|
|
174
|
+
select
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def power_on!
|
|
178
|
+
@cells.fill(0)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Lays the bank, or the empty socket, over $9000-$9FFF, the I/O
|
|
182
|
+
# page over $EF00, and the Waterloo ROMs over the 8032's while the
|
|
183
|
+
# 6809 runs.
|
|
184
|
+
def map(reads, writes)
|
|
185
|
+
if @socket.zero?
|
|
186
|
+
window = @banks[bank]
|
|
187
|
+
window.read_only = write_protected?
|
|
188
|
+
reads.fill(window, 0x90, 0x10)
|
|
189
|
+
writes.fill(window, 0x90, 0x10)
|
|
190
|
+
else
|
|
191
|
+
reads.fill(@bus.open_bus, 0x90, 0x10)
|
|
192
|
+
writes.fill(@bus.open_bus, 0x90, 0x10)
|
|
193
|
+
end
|
|
194
|
+
reads[0xef] = @io
|
|
195
|
+
writes[0xef] = @io
|
|
196
|
+
map_waterloo(reads) if @m6809
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
private
|
|
200
|
+
|
|
201
|
+
# Picks the CPU again, and hands the bus over if that changed it.
|
|
202
|
+
# The 6809 taking over clears the socket bit.
|
|
203
|
+
def select
|
|
204
|
+
m6809 = m6809_selected?
|
|
205
|
+
changed = m6809 != @m6809
|
|
206
|
+
@m6809 = m6809
|
|
207
|
+
@socket = 0 if m6809
|
|
208
|
+
@bus.map_pages
|
|
209
|
+
@select_handler&.call if changed
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def map_waterloo(reads)
|
|
213
|
+
reads.fill(@rom_a, 0xa0, 0x20)
|
|
214
|
+
reads.fill(@rom_c, 0xc0, 0x20)
|
|
215
|
+
reads.fill(@rom_e, 0xe0, 0x08)
|
|
216
|
+
reads.fill(@rom_e, 0xf0, 0x10)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class PET
|
|
5
|
+
class SuperPET < PET
|
|
6
|
+
# The 6702 at $EFE0-$EFE3, the protection chip the Waterloo languages
|
|
7
|
+
# check for. Its behaviour comes from David Roberts' Visual 6702, a
|
|
8
|
+
# switch-level simulation of the decapped chip's netlist, driven a
|
|
9
|
+
# bus cycle at a time.
|
|
10
|
+
#
|
|
11
|
+
# Each data line has a shift register of its own, LENGTHS long, whose
|
|
12
|
+
# first stage the line reads, inverted where INVERTED has the bit
|
|
13
|
+
# set. A latch keeps the last byte written, all ones from the RES
|
|
14
|
+
# line. A write that sets D0 where the last one left it clear shifts
|
|
15
|
+
# every register one stage at the end of the next cycle, whichever
|
|
16
|
+
# cycle that is, so a read in it still finds the old byte. Each
|
|
17
|
+
# register takes in its first stage XORed with the latch's bit,
|
|
18
|
+
# inverted where FEEDBACK has the bit clear, the latch as that cycle
|
|
19
|
+
# leaves it. The RES line clears the registers.
|
|
20
|
+
class Dongle
|
|
21
|
+
LENGTHS = [6, 3, 7, 8, 1, 3, 5, 2].freeze
|
|
22
|
+
|
|
23
|
+
INVERTED = 0xd6
|
|
24
|
+
|
|
25
|
+
FEEDBACK = 0x29
|
|
26
|
+
|
|
27
|
+
# No shift waiting.
|
|
28
|
+
NONE = -1
|
|
29
|
+
|
|
30
|
+
# The registers, bit 0 of each the stage its line reads, the latch,
|
|
31
|
+
# and the cycle at whose end the registers shift, or NONE.
|
|
32
|
+
attr_reader :registers, :latch, :shift_at
|
|
33
|
+
|
|
34
|
+
# +clock+ answers #cycles, the cycle the CPU's access falls in.
|
|
35
|
+
def initialize(clock)
|
|
36
|
+
@clock = clock
|
|
37
|
+
@registers = Array.new(8, 0)
|
|
38
|
+
reset!
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def reset!
|
|
42
|
+
@registers.fill(0)
|
|
43
|
+
@latch = 0xff
|
|
44
|
+
@shift_at = NONE
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def peek(_addr)
|
|
48
|
+
now = @clock.cycles
|
|
49
|
+
shift if @shift_at != NONE && @shift_at < now
|
|
50
|
+
value = 0
|
|
51
|
+
8.times { |bit| value |= (@registers[bit] & 1) << bit }
|
|
52
|
+
value ^ INVERTED
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def poke(_addr, value)
|
|
56
|
+
now = @clock.cycles
|
|
57
|
+
shift if @shift_at != NONE && @shift_at < now
|
|
58
|
+
raising = value.anybits?(0x01) && @latch.nobits?(0x01)
|
|
59
|
+
@latch = value
|
|
60
|
+
if @shift_at == now
|
|
61
|
+
shift
|
|
62
|
+
elsif raising
|
|
63
|
+
@shift_at = now + 1
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def shift
|
|
70
|
+
@shift_at = NONE
|
|
71
|
+
incoming = @latch ^ FEEDBACK ^ 0xff
|
|
72
|
+
8.times do |bit|
|
|
73
|
+
register = @registers[bit]
|
|
74
|
+
stage = (register ^ (incoming >> bit)) & 1
|
|
75
|
+
@registers[bit] = (register >> 1) | (stage << (LENGTHS[bit] - 1))
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|