badline 0.13.0 → 0.15.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 +72 -1
- data/CLAUDE.md +42 -12
- data/CONTRIBUTING.md +4 -3
- data/README.md +76 -13
- data/doc/media.md +2 -1
- data/doc/pinned-behaviour.md +174 -0
- data/doc/snapshot-format.md +1197 -0
- data/doc/snapshots.md +90 -40
- data/lib/badline/audio/bare_player.rb +46 -17
- data/lib/badline/audio/machine_player.rb +6 -10
- data/lib/badline/audio/renderer.rb +32 -21
- data/lib/badline/c128/banks.rb +7 -6
- data/lib/badline/c128/bus/keyed_state.rb +45 -0
- data/lib/badline/c128/bus.rb +36 -4
- data/lib/badline/c128/drives.rb +6 -25
- data/lib/badline/c128/keyed_modules.rb +117 -0
- data/lib/badline/c128/keyed_state.rb +91 -0
- data/lib/badline/c128/keys.rb +9 -0
- data/lib/badline/c128/mmu.rb +9 -8
- data/lib/badline/c128/mmu_pages.rb +27 -0
- data/lib/badline/c128/modes.rb +19 -9
- data/lib/badline/c128/z80_turns.rb +1 -1
- data/lib/badline/c128.rb +8 -8
- data/lib/badline/c64/attachments.rb +0 -44
- data/lib/badline/c64/banked_ram/plus256k.rb +18 -10
- data/lib/badline/c64/banked_ram/plus60k.rb +8 -6
- data/lib/badline/c64/banked_ram/unexpanded.rb +2 -2
- data/lib/badline/c64/bus/debug_register.rb +32 -0
- data/lib/badline/c64/bus/extra_sids.rb +80 -0
- data/lib/badline/c64/bus/keyed_state.rb +41 -0
- data/lib/badline/c64/bus.rb +3 -1
- data/lib/badline/c64/cartridge/action_replay.rb +6 -15
- data/lib/badline/c64/cartridge/comal80.rb +1 -0
- data/lib/badline/c64/cartridge/dinamic.rb +3 -1
- data/lib/badline/c64/cartridge/easy_flash.rb +0 -15
- data/lib/badline/c64/cartridge/epyx_fastload.rb +0 -8
- data/lib/badline/c64/cartridge/final_cartridge3.rb +1 -12
- data/lib/badline/c64/cartridge/flash.rb +3 -3
- data/lib/badline/c64/cartridge/fun_play.rb +2 -1
- data/lib/badline/c64/cartridge/g_mod2.rb +0 -12
- data/lib/badline/c64/cartridge/game_system.rb +2 -1
- data/lib/badline/c64/cartridge/geo_ram.rb +0 -16
- data/lib/badline/c64/cartridge/kcs_power.rb +0 -8
- data/lib/badline/c64/cartridge/keyed_state.rb +514 -0
- data/lib/badline/c64/cartridge/magic_desk.rb +2 -1
- data/lib/badline/c64/cartridge/ocean.rb +1 -0
- data/lib/badline/c64/cartridge/pagefox.rb +1 -10
- data/lib/badline/c64/cartridge/retro_replay.rb +0 -8
- data/lib/badline/c64/cartridge/rgcd.rb +2 -11
- data/lib/badline/c64/cartridge/super_games.rb +3 -12
- data/lib/badline/c64/cartridge/zaxxon.rb +1 -16
- data/lib/badline/c64/cartridge.rb +4 -30
- data/lib/badline/c64/kernal_traps.rb +28 -25
- data/lib/badline/c64/keyed_modules.rb +115 -0
- data/lib/badline/c64/keyed_state.rb +68 -0
- data/lib/badline/c64/reu/dma.rb +0 -4
- data/lib/badline/c64/reu/keyed_state.rb +140 -0
- data/lib/badline/c64/reu/ram.rb +0 -15
- data/lib/badline/c64/reu.rb +1 -3
- data/lib/badline/c64/true_drives.rb +56 -5
- data/lib/badline/c64.rb +7 -3
- data/lib/badline/chips/cia/interrupt_register.rb +0 -17
- data/lib/badline/chips/cia/keyed_parts.rb +213 -0
- data/lib/badline/chips/cia/serial.rb +0 -4
- data/lib/badline/chips/cia/time_of_day.rb +0 -22
- data/lib/badline/chips/cia/timer.rb +0 -20
- data/lib/badline/chips/cia.rb +2 -2
- data/lib/badline/chips/cpu/core.rb +18 -2
- data/lib/badline/chips/cpu/keyed_state.rb +177 -0
- data/lib/badline/chips/cpu/microcode.rb +16 -0
- data/lib/badline/chips/cpu/port.rb +20 -3
- data/lib/badline/chips/cpu.rb +5 -0
- data/lib/badline/chips/sid/decimator.rb +10 -8
- data/lib/badline/chips/sid/envelope.rb +1 -6
- data/lib/badline/chips/sid/filter.rb +0 -14
- data/lib/badline/chips/sid/keyed_parts.rb +286 -0
- data/lib/badline/chips/sid/voice.rb +0 -10
- data/lib/badline/chips/sid/waveform.rb +0 -2
- data/lib/badline/chips/sid.rb +1 -2
- data/lib/badline/chips/ted/beam.rb +118 -0
- data/lib/badline/chips/ted/fetches.rb +199 -0
- data/lib/badline/chips/ted/keyed_state.rb +156 -0
- data/lib/badline/chips/ted/modes.rb +134 -0
- data/lib/badline/chips/ted/palette.rb +43 -0
- data/lib/badline/chips/ted/picture.rb +182 -0
- data/lib/badline/chips/ted/sound/output.rb +86 -0
- data/lib/badline/chips/ted/sound.rb +284 -0
- data/lib/badline/chips/ted/timers.rb +66 -0
- data/lib/badline/chips/ted.rb +190 -0
- data/lib/badline/chips/tia.rb +61 -0
- data/lib/badline/chips/vdc/keyed_parts.rb +105 -0
- data/lib/badline/chips/vdc/painter.rb +0 -12
- data/lib/badline/chips/vdc/raster.rb +0 -16
- data/lib/badline/chips/vdc/window.rb +0 -9
- data/lib/badline/chips/vdc.rb +1 -2
- data/lib/badline/chips/via/control_lines.rb +0 -15
- data/lib/badline/chips/via/interrupt_register.rb +0 -9
- data/lib/badline/chips/via/keyed_parts.rb +163 -0
- data/lib/badline/chips/via/shift_register.rb +0 -14
- data/lib/badline/chips/via/timer1.rb +0 -13
- data/lib/badline/chips/via/timer2.rb +0 -18
- data/lib/badline/chips/via.rb +1 -2
- data/lib/badline/chips/vic/border_mask.rb +0 -10
- data/lib/badline/chips/vic/collisions.rb +0 -11
- data/lib/badline/chips/vic/color_patches.rb +0 -11
- data/lib/badline/chips/vic/display_state.rb +0 -19
- data/lib/badline/chips/vic/graphics_shifter.rb +0 -4
- data/lib/badline/chips/vic/keyed_parts.rb +228 -0
- data/lib/badline/chips/vic/keyed_sprites.rb +225 -0
- data/lib/badline/chips/vic/keyed_state.rb +102 -0
- data/lib/badline/chips/vic/register_log.rb +0 -18
- data/lib/badline/chips/vic/registers.rb +0 -9
- data/lib/badline/chips/vic/sequencer.rb +0 -2
- data/lib/badline/chips/vic/sprite/internal_bus.rb +0 -11
- data/lib/badline/chips/vic/sprite.rb +0 -2
- data/lib/badline/chips/vic/sprites.rb +0 -3
- data/lib/badline/chips/vic.rb +6 -2
- data/lib/badline/chips/vic_i/keyed_parts.rb +142 -0
- data/lib/badline/chips/vic_i/painter.rb +0 -4
- data/lib/badline/chips/vic_i/sound.rb +38 -46
- data/lib/badline/chips/vic_i.rb +2 -2
- data/lib/badline/chips/z80/core.rb +2 -2
- data/lib/badline/chips/z80/keyed_state.rb +73 -0
- data/lib/badline/chips.rb +10 -0
- data/lib/badline/core.rb +4 -21
- data/lib/badline/drives/datasette/keyed_state.rb +43 -0
- data/lib/badline/drives/datasette.rb +62 -12
- data/lib/badline/drives/engine/core.rb +3 -0
- data/lib/badline/drives/engine/serial_lines.rb +4 -7
- data/lib/badline/drives/model1541/bus.rb +0 -13
- data/lib/badline/drives/model1541/disk.rb +9 -8
- data/lib/badline/drives/model1541/keyed_state.rb +251 -0
- data/lib/badline/drives/model1541/mechanism.rb +0 -2
- data/lib/badline/drives/model1541.rb +1 -2
- data/lib/badline/drives/model1571/bus.rb +0 -13
- data/lib/badline/drives/model1571/keyed_state.rb +108 -0
- data/lib/badline/drives/model1571/wd1770.rb +0 -14
- data/lib/badline/drives/model1571.rb +1 -2
- data/lib/badline/drives/model1581/bus.rb +0 -13
- data/lib/badline/drives/model1581/disk.rb +0 -4
- data/lib/badline/drives/model1581/keyed_state.rb +280 -0
- data/lib/badline/drives/model1581/mechanism.rb +0 -21
- data/lib/badline/drives/model1581/slot.rb +0 -18
- data/lib/badline/drives/model1581/wd1772.rb +0 -2
- data/lib/badline/drives/model1581.rb +2 -2
- data/lib/badline/drives.rb +8 -0
- data/lib/badline/frontend/app.rb +3 -3
- data/lib/badline/frontend/boot.rb +4 -2
- data/lib/badline/frontend/controls.rb +25 -5
- data/lib/badline/frontend/drive_led.rb +44 -21
- data/lib/badline/frontend/keys.rb +14 -2
- data/lib/badline/frontend/media_slots.rb +11 -1
- data/lib/badline/frontend/menu/menu_dialogs.rb +4 -3
- data/lib/badline/frontend/menu/menu_media.rb +13 -3
- data/lib/badline/frontend/menu/menu_pages.rb +8 -8
- data/lib/badline/frontend/menu/menu_ports.rb +13 -1
- data/lib/badline/frontend/screen.rb +33 -12
- data/lib/badline/frontend/screens.rb +2 -1
- data/lib/badline/frontend/snapshots.rb +12 -1
- data/lib/badline/frontend/sound.rb +4 -4
- data/lib/badline/frontend/timeline.rb +43 -12
- data/lib/badline/input/matrix_typist.rb +98 -0
- data/lib/badline/input.rb +1 -0
- data/lib/badline/kernal_trap/chrout.rb +45 -0
- data/lib/badline/kernal_trap/dos/channel.rb +0 -27
- data/lib/badline/kernal_trap/dos/channels.rb +0 -25
- data/lib/badline/kernal_trap/dos/memory.rb +0 -9
- data/lib/badline/kernal_trap/dos/status.rb +0 -8
- data/lib/badline/kernal_trap/dos/write_file.rb +0 -18
- data/lib/badline/kernal_trap/dos.rb +0 -19
- data/lib/badline/kernal_trap/file_routine.rb +4 -4
- data/lib/badline/kernal_trap/keyed_state.rb +150 -0
- data/lib/badline/kernal_trap/layout.rb +75 -11
- data/lib/badline/kernal_trap/load.rb +14 -16
- data/lib/badline/kernal_trap/routine.rb +12 -0
- data/lib/badline/kernal_trap/save.rb +8 -18
- data/lib/badline/kernal_trap/serial.rb +1 -17
- data/lib/badline/kernal_trap.rb +2 -0
- data/lib/badline/machine/keyboard_buffer.rb +49 -0
- data/lib/badline/machine/settling.rb +29 -0
- data/lib/badline/machine.rb +29 -6
- data/lib/badline/media/boot_disk.rb +14 -2
- data/lib/badline/media/extensions.rb +10 -0
- data/lib/badline/media/plus4_media.rb +68 -0
- data/lib/badline/media/program.rb +20 -6
- data/lib/badline/media.rb +33 -12
- data/lib/badline/memory.rb +3 -6
- data/lib/badline/options/family.rb +6 -2
- data/lib/badline/options/help.rb +66 -16
- data/lib/badline/options/table.rb +17 -2
- data/lib/badline/options/validation.rb +26 -2
- data/lib/badline/options.rb +42 -9
- data/lib/badline/plus4/acia.rb +45 -0
- data/lib/badline/plus4/bus.rb +375 -0
- data/lib/badline/plus4/cassette_sense.rb +21 -0
- data/lib/badline/plus4/cpu.rb +10 -0
- data/lib/badline/plus4/cpu_port.rb +93 -0
- data/lib/badline/plus4/keyboard_port.rb +76 -0
- data/lib/badline/plus4/keyed_modules.rb +99 -0
- data/lib/badline/plus4/keyed_parts.rb +143 -0
- data/lib/badline/plus4/keyed_state.rb +41 -0
- data/lib/badline/plus4/model.rb +38 -0
- data/lib/badline/plus4/user_port.rb +31 -0
- data/lib/badline/plus4.rb +240 -0
- data/lib/badline/roms/README +38 -0
- data/lib/badline/roms/plus4/basic.rom +0 -0
- data/lib/badline/roms/plus4/function-high.rom +0 -0
- data/lib/badline/roms/plus4/function-low.rom +0 -0
- data/lib/badline/roms/plus4/kernal-ntsc.rom +0 -0
- data/lib/badline/roms/plus4/kernal-pal.rom +0 -0
- data/lib/badline/snapshot/c128_setup.rb +9 -20
- data/lib/badline/snapshot/c64_setup.rb +28 -24
- data/lib/badline/snapshot/container.rb +6 -0
- data/lib/badline/snapshot/field_doc/markdown.rb +113 -0
- data/lib/badline/snapshot/field_doc.rb +148 -0
- data/lib/badline/snapshot/image.rb +143 -28
- data/lib/badline/snapshot/keyed/cursor.rb +40 -0
- data/lib/badline/snapshot/keyed/modules.rb +66 -0
- data/lib/badline/snapshot/keyed/reader.rb +188 -0
- data/lib/badline/snapshot/keyed/recorder.rb +88 -0
- data/lib/badline/snapshot/keyed/report.rb +26 -0
- data/lib/badline/snapshot/keyed/writer.rb +55 -0
- data/lib/badline/snapshot/keyed.rb +64 -0
- data/lib/badline/snapshot/machine_state.rb +17 -64
- data/lib/badline/snapshot/plus4_setup.rb +39 -0
- data/lib/badline/snapshot/storage_setup.rb +29 -34
- data/lib/badline/snapshot/vic20_setup.rb +6 -15
- data/lib/badline/snapshot/vice/reu1764.rb +2 -2
- data/lib/badline/snapshot/vice.rb +7 -31
- data/lib/badline/snapshot.rb +48 -20
- data/lib/badline/storage/crt_file.rb +3 -1
- data/lib/badline/storage/g64_image.rb +9 -4
- data/lib/badline/storage/tap.rb +57 -20
- data/lib/badline/support/checkpoint.rb +3 -2
- data/lib/badline/support/plus4_checkpoint.rb +42 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic20/attachments.rb +3 -0
- data/lib/badline/vic20/bus/keyed_state.rb +52 -0
- data/lib/badline/vic20/bus.rb +1 -2
- data/lib/badline/vic20/keyed_modules.rb +73 -0
- data/lib/badline/vic20/keyed_state.rb +45 -0
- data/lib/badline/vic20.rb +5 -5
- data/lib/badline.rb +1 -0
- metadata +75 -42
- data/lib/badline/c128/bus/saved_state.rb +0 -53
- data/lib/badline/c128/saved_state.rb +0 -117
- data/lib/badline/c64/bus/saved_state.rb +0 -68
- data/lib/badline/c64/cartridge/flash/saved_state.rb +0 -40
- data/lib/badline/c64/cartridge/retro_replay/saved_state.rb +0 -47
- data/lib/badline/c64/cartridge/saved_state.rb +0 -63
- data/lib/badline/c64/reu/dma/saved_state.rb +0 -52
- data/lib/badline/c64/reu/saved_state.rb +0 -55
- data/lib/badline/c64/saved_state.rb +0 -112
- data/lib/badline/chips/cia/saved_state.rb +0 -49
- data/lib/badline/chips/cia/serial/saved_state.rb +0 -45
- data/lib/badline/chips/cpu/saved_state.rb +0 -117
- data/lib/badline/chips/sid/envelope/saved_state.rb +0 -70
- data/lib/badline/chips/sid/filter/saved_state.rb +0 -42
- data/lib/badline/chips/sid/saved_state.rb +0 -52
- data/lib/badline/chips/sid/waveform/saved_state.rb +0 -68
- data/lib/badline/chips/vdc/saved_state.rb +0 -36
- data/lib/badline/chips/via/saved_state.rb +0 -44
- data/lib/badline/chips/vic/graphics_shifter/saved_state.rb +0 -40
- data/lib/badline/chips/vic/saved_state.rb +0 -82
- data/lib/badline/chips/vic/sequencer/saved_state.rb +0 -91
- data/lib/badline/chips/vic/sprite/saved_state.rb +0 -66
- data/lib/badline/chips/vic/sprites/saved_state.rb +0 -59
- data/lib/badline/chips/vic_i/painter/saved_state.rb +0 -40
- data/lib/badline/chips/vic_i/saved_state.rb +0 -55
- data/lib/badline/chips/z80/saved_state.rb +0 -71
- data/lib/badline/chrout_trap.rb +0 -43
- data/lib/badline/debug_register.rb +0 -28
- data/lib/badline/drives/datasette/saved_state.rb +0 -37
- data/lib/badline/drives/model1541/disk/saved_state.rb +0 -88
- data/lib/badline/drives/model1541/mechanism/saved_state.rb +0 -72
- data/lib/badline/drives/model1541/saved_state.rb +0 -44
- data/lib/badline/drives/model1571/saved_state.rb +0 -47
- data/lib/badline/drives/model1581/disk/saved_state.rb +0 -89
- data/lib/badline/drives/model1581/saved_state.rb +0 -42
- data/lib/badline/drives/model1581/wd1772/saved_state.rb +0 -68
- data/lib/badline/keyboard_buffer.rb +0 -33
- data/lib/badline/snapshot/setup.rb +0 -31
- data/lib/badline/snapshot/state.rb +0 -179
- data/lib/badline/vic20/bus/saved_state.rb +0 -35
- data/lib/badline/vic20/saved_state.rb +0 -110
data/lib/badline/options.rb
CHANGED
|
@@ -12,9 +12,9 @@ module Badline
|
|
|
12
12
|
# Either opens the window for any media, or with --headless or
|
|
13
13
|
# --audio-out plays or renders a .sid tune without one. `sid`, given
|
|
14
14
|
# first, plays .sid tunes and directories of them in the terminal.
|
|
15
|
-
# `c64`, `vic20` or `
|
|
16
|
-
# without one the family is the C64, or the C128 for a program
|
|
17
|
-
# where C128 BASIC starts. The native build adds --no-sound and
|
|
15
|
+
# `c64`, `vic20`, `c128` or `plus4`, given first, names the machine
|
|
16
|
+
# family, and without one the family is the C64, or the C128 for a program
|
|
17
|
+
# that loads where C128 BASIC starts. The native build adds --no-sound and
|
|
18
18
|
# --version, and badline-ruby adds --disable-jit.
|
|
19
19
|
#
|
|
20
20
|
# TABLE lists the options, and both the parser and the help read it. It
|
|
@@ -48,6 +48,13 @@ module Badline
|
|
|
48
48
|
"32k" => :"32k", "all" => :all
|
|
49
49
|
}.freeze
|
|
50
50
|
|
|
51
|
+
# The Plus/4's RAM sizes as --ram names them, in K.
|
|
52
|
+
PLUS4_RAM = { "16k" => 16, "32k" => 32, "64k" => 64 }.freeze
|
|
53
|
+
|
|
54
|
+
# The Plus/4's cartridge slots, by the options that fill them and
|
|
55
|
+
# xplus4's names (Media::Plus4Media::SLOTS).
|
|
56
|
+
CARTRIDGE_SLOTS = { "--c1lo" => "c1lo", "--c1hi" => "c1hi", "--c2lo" => "c2lo", "--c2hi" => "c2hi" }.freeze
|
|
57
|
+
|
|
51
58
|
# A Float as OptionParser took one: digits with an optional sign,
|
|
52
59
|
# fraction and exponent.
|
|
53
60
|
DECIMAL = /\A[-+]?(\d+(\.\d+)?|\.\d+)([eE][-+]?\d+)?\z/
|
|
@@ -57,14 +64,22 @@ module Badline
|
|
|
57
64
|
attr_reader :program, :media_path, :tune_paths, :subtune, :sid_model, :reu, :frames, :screenshot, :save_snapshot,
|
|
58
65
|
:audio_out, :seconds, :songlengths, :filter_chunk, :timeline
|
|
59
66
|
|
|
60
|
-
# The machine family to build (Badline::Machine), :c64, :vic20
|
|
61
|
-
# :
|
|
67
|
+
# The machine family to build (Badline::Machine), :c64, :vic20, :c128
|
|
68
|
+
# or :plus4.
|
|
62
69
|
attr_reader :family
|
|
63
70
|
|
|
64
71
|
# The VIC-20's RAM expansion --ram names, a key of
|
|
65
72
|
# Vic20::Bus::RAM_CONFIGURATIONS, or nil without --ram.
|
|
66
73
|
attr_reader :ram
|
|
67
74
|
|
|
75
|
+
# The Plus/4's RAM in K --ram names, one of PLUS4_RAM's, or nil without
|
|
76
|
+
# --ram.
|
|
77
|
+
attr_reader :ram_kb
|
|
78
|
+
|
|
79
|
+
# The Plus/4's cartridge ROM images --c1lo, --c1hi, --c2lo and --c2hi
|
|
80
|
+
# name, by slot: { "c1lo" => path }.
|
|
81
|
+
attr_reader :cartridges
|
|
82
|
+
|
|
68
83
|
def self.parse(argv, native: false) = new(native:).parse(argv)
|
|
69
84
|
|
|
70
85
|
# The native build plays the SID in the window unless --no-sound says
|
|
@@ -84,6 +99,8 @@ module Badline
|
|
|
84
99
|
@models = []
|
|
85
100
|
@reu = nil
|
|
86
101
|
@ram = nil
|
|
102
|
+
@ram_kb = nil
|
|
103
|
+
@cartridges = {}
|
|
87
104
|
@autostart = true
|
|
88
105
|
@writable = false
|
|
89
106
|
@sound = native
|
|
@@ -94,6 +111,7 @@ module Badline
|
|
|
94
111
|
@true_drive = false
|
|
95
112
|
@screenshot = ""
|
|
96
113
|
@save_snapshot = ""
|
|
114
|
+
@vice = false
|
|
97
115
|
@timeline = []
|
|
98
116
|
@jit = true
|
|
99
117
|
@help = false
|
|
@@ -171,11 +189,14 @@ module Badline
|
|
|
171
189
|
|
|
172
190
|
def true_drive? = @true_drive
|
|
173
191
|
|
|
192
|
+
# Whether --save-snapshot adds VICE's modules, so x64sc opens it.
|
|
193
|
+
def vice? = @vice
|
|
194
|
+
|
|
174
195
|
def jit? = @jit
|
|
175
196
|
|
|
176
197
|
# The name of the model to build: a C64 Badline::Model names, a VIC-20
|
|
177
|
-
# of VIC20_MODELS
|
|
178
|
-
# ntsc.
|
|
198
|
+
# of VIC20_MODELS, a C128 of C128_MODELS or a Plus/4 of PLUS4_MODELS.
|
|
199
|
+
# --ntsc is short for --model ntsc.
|
|
179
200
|
def model = @models.first || family_models.first
|
|
180
201
|
|
|
181
202
|
# Whether --c64 asks the C128 to start in C64 mode, holding C= at
|
|
@@ -197,7 +218,8 @@ module Badline
|
|
|
197
218
|
# A .sid on its own plays in the SID player, as `sid` would, unless an
|
|
198
219
|
# option of the emulator's window asks for the machine.
|
|
199
220
|
def play_lone_tune
|
|
200
|
-
return if @sid_command || @family
|
|
221
|
+
return if @sid_command || %i[vic20 plus4].include?(@family) || headless? || !@window_only.empty?
|
|
222
|
+
return if @media_path.nil?
|
|
201
223
|
|
|
202
224
|
@sid_command = File.extname(@media_path).casecmp?(".sid")
|
|
203
225
|
end
|
|
@@ -250,7 +272,8 @@ module Badline
|
|
|
250
272
|
when "--sid" then @sid_model = sid_model_for(value)
|
|
251
273
|
when "--model" then @models << value
|
|
252
274
|
when "--reu" then @reu = reu_size(value)
|
|
253
|
-
when "--ram" then
|
|
275
|
+
when "--ram" then ram_option(value)
|
|
276
|
+
when "--c1lo", "--c1hi", "--c2lo", "--c2hi" then @cartridges[CARTRIDGE_SLOTS.fetch(name)] = value
|
|
254
277
|
when "--frames" then @frames = number(flag, value)
|
|
255
278
|
when "--screenshot" then @screenshot = value
|
|
256
279
|
when "--save-snapshot" then @save_snapshot = value
|
|
@@ -263,6 +286,15 @@ module Badline
|
|
|
263
286
|
end
|
|
264
287
|
end
|
|
265
288
|
|
|
289
|
+
# --ram names the VIC-20's RAM expansion, or the Plus/4's RAM.
|
|
290
|
+
def ram_option(value)
|
|
291
|
+
if @family == :plus4
|
|
292
|
+
@ram_kb = plus4_ram(value)
|
|
293
|
+
else
|
|
294
|
+
@ram = ram_configuration(value)
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
266
298
|
def switch(name)
|
|
267
299
|
case name
|
|
268
300
|
when "--no-autostart" then @autostart = false
|
|
@@ -272,6 +304,7 @@ module Badline
|
|
|
272
304
|
when "--no-vsync" then @vsync = false
|
|
273
305
|
when "--verbose" then @verbose = true
|
|
274
306
|
when "--true-drive" then @true_drive = true
|
|
307
|
+
when "--vice" then @vice = true
|
|
275
308
|
when "--ntsc" then @models << "ntsc"
|
|
276
309
|
when "--c64" then @c64_mode = true
|
|
277
310
|
when "--unpaced" then @paced = false
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Plus4
|
|
5
|
+
# The Plus/4's 6551 ACIA at $FD00-$FD0F, its four registers repeated,
|
|
6
|
+
# as a stub with nothing on the line: it never receives, its
|
|
7
|
+
# transmitter is always empty, and it never interrupts. The command and
|
|
8
|
+
# control registers keep what is written to them, and a write to the
|
|
9
|
+
# status register, a programmed reset, clears the command register's
|
|
10
|
+
# low five bits.
|
|
11
|
+
class ACIA
|
|
12
|
+
STATUS = 0x10
|
|
13
|
+
|
|
14
|
+
attr_reader :command, :control
|
|
15
|
+
|
|
16
|
+
def initialize
|
|
17
|
+
reset!
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def reset!
|
|
21
|
+
@command = 0
|
|
22
|
+
@control = 0
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def irq? = false
|
|
26
|
+
|
|
27
|
+
def peek(addr)
|
|
28
|
+
case addr & 0x03
|
|
29
|
+
when 1 then STATUS
|
|
30
|
+
when 2 then @command
|
|
31
|
+
when 3 then @control
|
|
32
|
+
else 0
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def poke(addr, value)
|
|
37
|
+
case addr & 0x03
|
|
38
|
+
when 1 then @command &= 0xe0
|
|
39
|
+
when 2 then @command = value
|
|
40
|
+
when 3 then @control = value
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Plus4
|
|
5
|
+
# The CPU's bus:
|
|
6
|
+
#
|
|
7
|
+
# $0000-$0001 - the 7501's port (CPUPort), over RAM
|
|
8
|
+
# $0002-$7FFF - RAM
|
|
9
|
+
# $8000-$BFFF - the low ROM bank, or RAM
|
|
10
|
+
# $C000-$FCFF - the high ROM bank, or RAM; $FC00-$FCFF is always the
|
|
11
|
+
# KERNAL's while ROM is selected
|
|
12
|
+
# $FD00-$FD0F - the 6551 ACIA (Plus/4 only)
|
|
13
|
+
# $FD10-$FD1F - the user port's 6529 (Plus/4 only)
|
|
14
|
+
# $FD30-$FD3F - the keyboard's 6529 (KeyboardPort)
|
|
15
|
+
# $FDD0-$FDDF - the ROM bank latch
|
|
16
|
+
# $FF00-$FF1F - the TED
|
|
17
|
+
# $FF3E/$FF3F - a write selects ROM or RAM at $8000-$FFFF
|
|
18
|
+
# $FF40-$FFFF - the high ROM bank, or RAM
|
|
19
|
+
#
|
|
20
|
+
# A write to $FF3E selects ROM and one to $FF3F RAM, everywhere in
|
|
21
|
+
# $8000-$FFFF but $FD00-$FF3F, which holds I/O either way. ROM is
|
|
22
|
+
# selected at power-on and reset. A write to ROM goes to the RAM under
|
|
23
|
+
# it. The ROM bank latch takes the address written to, not the byte:
|
|
24
|
+
# bits 0-1 pick the low bank and bits 2-3 the high bank, 0 for BASIC
|
|
25
|
+
# and the KERNAL, 1 for the Plus/4's function ROMs, 2 and 3 for the two
|
|
26
|
+
# cartridges.
|
|
27
|
+
#
|
|
28
|
+
# RAM smaller than 64K repeats through the 64K, as the C16's 16K does
|
|
29
|
+
# without A14 and A15. Where nothing answers, a read finds the last byte
|
|
30
|
+
# on the bus.
|
|
31
|
+
#
|
|
32
|
+
# The TED fetches through the same banks: the video matrix and the
|
|
33
|
+
# attributes from ROM or RAM at $8000-$FFFF as the CPU sees it, with no
|
|
34
|
+
# I/O in the way (#ted_fetch), and character and bitmap data from RAM,
|
|
35
|
+
# or from ROM with $FF12 bit 2 set, where below $8000 and in an empty
|
|
36
|
+
# bank it finds the last byte on the bus (#ted_pattern_fetch, Plus/4
|
|
37
|
+
# openmem). Each byte it fetches is left on the bus.
|
|
38
|
+
class Bus
|
|
39
|
+
include Addressable
|
|
40
|
+
|
|
41
|
+
RAM_POWER_ON = 0x00
|
|
42
|
+
|
|
43
|
+
# RAM through a size it repeats at.
|
|
44
|
+
class MirroredRAM
|
|
45
|
+
def initialize(ram, mask)
|
|
46
|
+
@ram = ram
|
|
47
|
+
@mask = mask
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def peek(addr) = @ram.peek(addr & @mask)
|
|
51
|
+
|
|
52
|
+
def poke(addr, value)
|
|
53
|
+
@ram.poke(addr & @mask, value)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# The last byte the CPU moved, which an empty spot reads back.
|
|
58
|
+
class OpenBus
|
|
59
|
+
def initialize(bus)
|
|
60
|
+
@bus = bus
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def peek(_addr) = @bus.data
|
|
64
|
+
def poke(_addr, _value); end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Page 0: the port at $00 and $01, RAM above it.
|
|
68
|
+
class ZeroPage
|
|
69
|
+
def initialize(port, ram)
|
|
70
|
+
@port = port
|
|
71
|
+
@ram = ram
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def peek(addr) = addr < 2 ? @port.peek(addr) : @ram.peek(addr)
|
|
75
|
+
|
|
76
|
+
def poke(addr, value)
|
|
77
|
+
return @port.poke(addr, value) if addr < 2
|
|
78
|
+
|
|
79
|
+
@ram.poke(addr, value)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Page $FD: the ACIA, the user port, the keyboard port and the ROM
|
|
84
|
+
# bank latch, by the address's bits 4-7. +acia+ and +user_port+ are
|
|
85
|
+
# nil where the machine has none, and +cassette_sense+ answers in the
|
|
86
|
+
# user port's place on a machine without one (CassetteSense).
|
|
87
|
+
class IOPage
|
|
88
|
+
def initialize(keyboard_port:, acia: nil, user_port: nil, cassette_sense: nil)
|
|
89
|
+
@bus = nil
|
|
90
|
+
@acia = acia
|
|
91
|
+
@user_port = user_port
|
|
92
|
+
@cassette_sense = cassette_sense
|
|
93
|
+
@keyboard_port = keyboard_port
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# The bus whose last byte an empty spot reads and whose bank latch
|
|
97
|
+
# a write to $FDD0-$FDDF sets.
|
|
98
|
+
def connect(bus)
|
|
99
|
+
@bus = bus
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def peek(addr)
|
|
103
|
+
case addr & 0xf0
|
|
104
|
+
when 0x00 then @acia ? @acia.peek(addr) : @bus.data
|
|
105
|
+
when 0x10 then user_port_peek(addr)
|
|
106
|
+
when 0x30 then @keyboard_port.peek(addr)
|
|
107
|
+
else @bus.data
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def poke(addr, value)
|
|
112
|
+
case addr & 0xf0
|
|
113
|
+
when 0x00 then @acia&.poke(addr, value)
|
|
114
|
+
when 0x10 then @user_port&.poke(addr, value)
|
|
115
|
+
when 0x30 then @keyboard_port.poke(addr, value)
|
|
116
|
+
when 0xd0 then @bus.select_banks(addr)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
def user_port_peek(addr)
|
|
123
|
+
return @user_port.peek(addr) if @user_port
|
|
124
|
+
return @cassette_sense.read(@bus.data) if @cassette_sense
|
|
125
|
+
|
|
126
|
+
@bus.data
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# VICE's debug cartridge register at $FDCF, a spot in the I/O page
|
|
131
|
+
# that nothing answers (plus4world forum post 31417). A write there
|
|
132
|
+
# calls the handler with the byte, the exit code the VICE testprogs
|
|
133
|
+
# report. Every other access falls through to the I/O page.
|
|
134
|
+
class DebugRegister
|
|
135
|
+
ADDRESS = 0xfdcf
|
|
136
|
+
|
|
137
|
+
def initialize(io, &handler)
|
|
138
|
+
@io = io
|
|
139
|
+
@handler = handler
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def peek(addr) = @io.peek(addr)
|
|
143
|
+
|
|
144
|
+
def poke(addr, value)
|
|
145
|
+
return @handler.call(value) if addr == ADDRESS
|
|
146
|
+
|
|
147
|
+
@io.poke(addr, value)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Page $FE with a 1551's TCBM interface plugged in: the 6523 TIA
|
|
152
|
+
# that talks to drive 9 at $FEC0-$FEDF and drive 8's at $FEE0-$FEFF.
|
|
153
|
+
# The rest of the page is open.
|
|
154
|
+
class TCBMPage
|
|
155
|
+
def initialize(bus, drive8:, drive9:)
|
|
156
|
+
@bus = bus
|
|
157
|
+
@drive8 = drive8
|
|
158
|
+
@drive9 = drive9
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def peek(addr)
|
|
162
|
+
return @bus.data if addr < 0xfec0
|
|
163
|
+
|
|
164
|
+
addr < 0xfee0 ? @drive9.peek(addr) : @drive8.peek(addr)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def poke(addr, value)
|
|
168
|
+
return if addr < 0xfec0
|
|
169
|
+
|
|
170
|
+
addr < 0xfee0 ? @drive9.poke(addr, value) : @drive8.poke(addr, value)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Page $FF: the TED in $FF00-$FF3F, its two phantom registers, and
|
|
175
|
+
# ROM or RAM above.
|
|
176
|
+
class TopPage
|
|
177
|
+
def initialize(bus, ted)
|
|
178
|
+
@bus = bus
|
|
179
|
+
@ted = ted
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def peek(addr)
|
|
183
|
+
return @ted.peek(addr) if addr < 0xff40
|
|
184
|
+
|
|
185
|
+
@bus.top_memory.peek(addr)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def poke(addr, value)
|
|
189
|
+
return @bus.ram_view.poke(addr, value) if addr >= 0xff40
|
|
190
|
+
|
|
191
|
+
case addr
|
|
192
|
+
when 0xff3e then @bus.rom_selected = true
|
|
193
|
+
when 0xff3f then @bus.rom_selected = false
|
|
194
|
+
else @ted.poke(addr, value)
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
attr_reader :ram, :ram_view, :data, :low_bank, :high_bank
|
|
200
|
+
|
|
201
|
+
# What the top page reads above $FF3F: the high ROM bank or RAM.
|
|
202
|
+
attr_reader :top_memory
|
|
203
|
+
|
|
204
|
+
# The CPU sees ROM at $8000-$FFFF.
|
|
205
|
+
attr_reader :rom_selected
|
|
206
|
+
|
|
207
|
+
# +io+ is the IOPage, +model+ a Model profile, whose KERNAL it maps
|
|
208
|
+
# and whose function ROMs it maps where the model has them, and
|
|
209
|
+
# +ram_kb+ the RAM fitted.
|
|
210
|
+
def initialize(ted:, port:, io:, model: Model::PLUS4, ram_kb: model.ram_kb)
|
|
211
|
+
addressable_at(0, length: 2**16)
|
|
212
|
+
size = ram_kb * 1024
|
|
213
|
+
@ram = Memory.new(Array.new(size, RAM_POWER_ON), length: size, start: 0)
|
|
214
|
+
@ram_view = MirroredRAM.new(@ram, size - 1)
|
|
215
|
+
@ted = ted
|
|
216
|
+
@open_bus = OpenBus.new(self)
|
|
217
|
+
@low_roms = [ROM.load("plus4/basic.rom", 0x8000), nil, nil, nil]
|
|
218
|
+
@high_roms = [ROM.load(model.kernal, 0xc000), nil, nil, nil]
|
|
219
|
+
if model.plus4_parts
|
|
220
|
+
@low_roms[1] = ROM.load("plus4/function-low.rom", 0x8000)
|
|
221
|
+
@high_roms[1] = ROM.load("plus4/function-high.rom", 0xc000)
|
|
222
|
+
end
|
|
223
|
+
@zero_page = ZeroPage.new(port, @ram_view)
|
|
224
|
+
@io_page = io
|
|
225
|
+
io.connect(self)
|
|
226
|
+
@top_page = TopPage.new(self, ted)
|
|
227
|
+
@tcbm_page = @open_bus
|
|
228
|
+
@debug_register = nil
|
|
229
|
+
@data = 0
|
|
230
|
+
@read_pages = Array.new(256, @open_bus)
|
|
231
|
+
@write_pages = Array.new(256, @ram_view)
|
|
232
|
+
@ted_pages = Array.new(256, @ram_view)
|
|
233
|
+
@ted_rom_pages = Array.new(256, @open_bus)
|
|
234
|
+
@low_bank = @high_bank = 0
|
|
235
|
+
@rom_selected = true
|
|
236
|
+
@top_memory = @ram_view
|
|
237
|
+
map_writes
|
|
238
|
+
map_reads
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def power_on!
|
|
242
|
+
@ram.clear!(Array.new(@ram.length, RAM_POWER_ON))
|
|
243
|
+
reset!
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# The RESET line selects ROM and the first bank of each.
|
|
247
|
+
def reset!
|
|
248
|
+
@low_bank = @high_bank = 0
|
|
249
|
+
self.rom_selected = true
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def rom_selected=(selected)
|
|
253
|
+
@rom_selected = selected
|
|
254
|
+
@ted.rom_selected = selected
|
|
255
|
+
map_reads
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Whether the CPU reads the KERNAL at $C000-$FFFF: ROM selected, with
|
|
259
|
+
# the KERNAL's bank, high bank 0.
|
|
260
|
+
def kernal_selected? = @rom_selected && @high_bank.zero?
|
|
261
|
+
|
|
262
|
+
# A write to the ROM bank latch at +addr+.
|
|
263
|
+
def select_banks(addr)
|
|
264
|
+
@low_bank = addr & 0x03
|
|
265
|
+
@high_bank = (addr >> 2) & 0x03
|
|
266
|
+
map_reads
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Calls the block with each byte written to $FDCF (DebugRegister).
|
|
270
|
+
def install_debug_register(&)
|
|
271
|
+
@debug_register = DebugRegister.new(@io_page, &)
|
|
272
|
+
map_writes
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# Plugs in a 1551's TCBM interface, the TIAs at $FEC0-$FEFF.
|
|
276
|
+
def plug_tcbm(drive8:, drive9:)
|
|
277
|
+
@tcbm_page = TCBMPage.new(self, drive8:, drive9:)
|
|
278
|
+
map_writes
|
|
279
|
+
map_reads
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Puts a cartridge's ROM chips in their slots, as a PLUS4 CARTRIDGE
|
|
283
|
+
# .crt numbers them: a chip's bank picks cartridge 1 (0) or 2 (1), in
|
|
284
|
+
# ROM banks 2 and 3, and its address the low ($8000) or the high
|
|
285
|
+
# ($C000) slot. A chip shorter than 16K repeats through the slot.
|
|
286
|
+
# Slots the chips don't name keep what they held.
|
|
287
|
+
def attach_cartridge(chips)
|
|
288
|
+
chips.each do |chip|
|
|
289
|
+
raise ArgumentError, "no Plus/4 cartridge slot in bank #{chip.bank}" unless chip.bank.between?(0, 1)
|
|
290
|
+
|
|
291
|
+
rom = chip.data
|
|
292
|
+
length = rom.length
|
|
293
|
+
data = Array.new(0x4000) { |i| length.zero? ? 0xff : rom[i % length] }
|
|
294
|
+
if chip.address < 0xc000
|
|
295
|
+
@low_roms[2 + chip.bank] = ROM.new(data, length: 0x4000, start: 0x8000)
|
|
296
|
+
else
|
|
297
|
+
@high_roms[2 + chip.bank] = ROM.new(data, length: 0x4000, start: 0xc000)
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
map_reads
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# Empties both cartridges' slots, ROM banks 2 and 3, which then read
|
|
304
|
+
# as the open bus.
|
|
305
|
+
def detach_cartridges
|
|
306
|
+
@low_roms[2] = @low_roms[3] = @high_roms[2] = @high_roms[3] = nil
|
|
307
|
+
map_reads
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# The slots that hold a cartridge's ROM, by xplus4's names: c1lo,
|
|
311
|
+
# c1hi, c2lo and c2hi.
|
|
312
|
+
def cartridge_slots
|
|
313
|
+
slots = []
|
|
314
|
+
slots << "c1lo" unless @low_roms[2].nil?
|
|
315
|
+
slots << "c1hi" unless @high_roms[2].nil?
|
|
316
|
+
slots << "c2lo" unless @low_roms[3].nil?
|
|
317
|
+
slots << "c2hi" unless @high_roms[3].nil?
|
|
318
|
+
slots
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def peek(addr)
|
|
322
|
+
@data = @read_pages[addr >> 8].peek(addr)
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def poke(addr, value)
|
|
326
|
+
@data = value
|
|
327
|
+
@write_pages[addr >> 8].poke(addr, value)
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# A TED fetch of the video matrix or the attributes.
|
|
331
|
+
def ted_fetch(addr)
|
|
332
|
+
@data = @ted_pages[addr >> 8].peek(addr)
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# A TED fetch of character or bitmap data, from ROM when +rom+.
|
|
336
|
+
def ted_pattern_fetch(addr, rom)
|
|
337
|
+
@data = rom ? @ted_rom_pages[addr >> 8].peek(addr) : @ram_view.peek(addr)
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
private
|
|
341
|
+
|
|
342
|
+
def map_writes
|
|
343
|
+
@write_pages[0x00] = @zero_page
|
|
344
|
+
@write_pages[0xfd] = @debug_register || @io_page
|
|
345
|
+
@write_pages[0xfe] = @tcbm_page
|
|
346
|
+
@write_pages[0xff] = @top_page
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def map_reads
|
|
350
|
+
@read_pages.fill(@ram_view, 0x00, 0xfd)
|
|
351
|
+
@read_pages[0x00] = @zero_page
|
|
352
|
+
map_roms(@read_pages) if @rom_selected
|
|
353
|
+
@top_memory = @rom_selected ? @high_roms[@high_bank] || @open_bus : @ram_view
|
|
354
|
+
@read_pages[0xfd] = @io_page
|
|
355
|
+
@read_pages[0xfe] = @tcbm_page
|
|
356
|
+
@read_pages[0xff] = @top_page
|
|
357
|
+
map_ted_reads
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
def map_ted_reads
|
|
361
|
+
@ted_pages.fill(@ram_view, 0x80, 0x80)
|
|
362
|
+
map_roms(@ted_pages) if @rom_selected
|
|
363
|
+
map_roms(@ted_rom_pages)
|
|
364
|
+
@ted_pages.fill(@high_roms[@high_bank] || @open_bus, 0xfd, 3) if @rom_selected
|
|
365
|
+
@ted_rom_pages.fill(@high_roms[@high_bank] || @open_bus, 0xfd, 3)
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def map_roms(pages)
|
|
369
|
+
pages.fill(@low_roms[@low_bank] || @open_bus, 0x80, 0x40)
|
|
370
|
+
pages.fill(@high_roms[@high_bank] || @open_bus, 0xc0, 0x3c)
|
|
371
|
+
pages[0xfc] = @high_roms[0]
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Plus4
|
|
5
|
+
# The C16's and C116's cassette sense, which has no 6529 to go through:
|
|
6
|
+
# a read of $FD10-$FD1F enables a 74LS125 that drives the sense line
|
|
7
|
+
# onto data bit 2, low while a key is down on the deck (C16 schematic
|
|
8
|
+
# 251788, sheet 1, U11). Nothing drives the other bits, which keep the
|
|
9
|
+
# last byte on the bus, and a write goes nowhere.
|
|
10
|
+
class CassetteSense
|
|
11
|
+
TAPE_SENSE = 0x04
|
|
12
|
+
|
|
13
|
+
def initialize(datasette)
|
|
14
|
+
@datasette = datasette
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# The byte a read finds, from +data+, the last byte on the bus.
|
|
18
|
+
def read(data) = @datasette.sense_low? ? data & ~TAPE_SENSE : data | TAPE_SENSE
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Plus4
|
|
5
|
+
# The 7501's I/O port at $00 (direction) and $01 (data). Its seven
|
|
6
|
+
# pins carry the serial bus and the cassette port, and none of them
|
|
7
|
+
# banks memory:
|
|
8
|
+
#
|
|
9
|
+
# - P0, P1 and P2 drive the serial bus's DATA, CLK and ATN through
|
|
10
|
+
# inverters, so a 1 pulls the line low, and P1 is the cassette write
|
|
11
|
+
# line too.
|
|
12
|
+
# - P3 runs the cassette motor while it is low.
|
|
13
|
+
# - P4 reads the cassette read line, P6 the serial CLK line and P7 the
|
|
14
|
+
# DATA line, 1 while the line is high.
|
|
15
|
+
# - P5 has no pin and reads 1.
|
|
16
|
+
#
|
|
17
|
+
# An output pin reads back what it drives. A pin left as an input reads
|
|
18
|
+
# what drives it from outside, and the serial outputs, P0-P2, float high
|
|
19
|
+
# there, so they pull their lines, as an output of 1 does.
|
|
20
|
+
class CPUPort
|
|
21
|
+
DATA_OUT = 0x01
|
|
22
|
+
CLK_OUT = 0x02
|
|
23
|
+
ATN_OUT = 0x04
|
|
24
|
+
MOTOR = 0x08
|
|
25
|
+
TAPE_READ = 0x10
|
|
26
|
+
CLK_IN = 0x40
|
|
27
|
+
DATA_IN = 0x80
|
|
28
|
+
|
|
29
|
+
attr_reader :ddr, :data
|
|
30
|
+
|
|
31
|
+
def initialize(serial_bus:, datasette:)
|
|
32
|
+
@serial_bus = serial_bus
|
|
33
|
+
@datasette = datasette
|
|
34
|
+
@ddr = 0
|
|
35
|
+
@data = 0
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def reset!
|
|
39
|
+
@ddr = 0
|
|
40
|
+
@data = 0
|
|
41
|
+
changed
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def peek(addr)
|
|
45
|
+
return @ddr if addr.zero?
|
|
46
|
+
|
|
47
|
+
(@data & @ddr) | (inputs & ~@ddr & 0xff)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def poke(addr, value)
|
|
51
|
+
if addr.zero?
|
|
52
|
+
@ddr = value
|
|
53
|
+
else
|
|
54
|
+
@data = value
|
|
55
|
+
end
|
|
56
|
+
changed
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# The pins' levels as they leave the chip, inputs floating high.
|
|
60
|
+
def levels = (@data & @ddr) | (~@ddr & 0xff)
|
|
61
|
+
|
|
62
|
+
# The serial bus lines the port pulls low, as Drives::SerialBus host
|
|
63
|
+
# lines.
|
|
64
|
+
def serial_lines
|
|
65
|
+
out = levels
|
|
66
|
+
lines = 0
|
|
67
|
+
lines |= Drives::SerialBus::HOST_ATN_OUT if out.anybits?(ATN_OUT)
|
|
68
|
+
lines |= Drives::SerialBus::HOST_CLK_OUT if out.anybits?(CLK_OUT)
|
|
69
|
+
lines |= Drives::SerialBus::HOST_DATA_OUT if out.anybits?(DATA_OUT)
|
|
70
|
+
lines
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def inputs
|
|
76
|
+
value = 0x2f
|
|
77
|
+
value |= TAPE_READ if @datasette.read_high?
|
|
78
|
+
low = @serial_bus.low_lines
|
|
79
|
+
value |= CLK_IN unless low.anybits?(Drives::SerialBus::CLK)
|
|
80
|
+
value |= DATA_IN unless low.anybits?(Drives::SerialBus::DATA)
|
|
81
|
+
value
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def changed
|
|
85
|
+
lines = serial_lines
|
|
86
|
+
@serial_bus.host_lines = lines unless lines == @serial_bus.host_lines
|
|
87
|
+
out = levels
|
|
88
|
+
@datasette.motor = out.nobits?(MOTOR)
|
|
89
|
+
@datasette.write_line = out.anybits?(CLK_OUT)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|