badline 0.13.0 → 0.14.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 +49 -1
- data/CLAUDE.md +27 -7
- data/CONTRIBUTING.md +4 -3
- data/README.md +8 -5
- data/doc/media.md +2 -1
- data/doc/pinned-behaviour.md +29 -0
- data/doc/snapshot-format.md +1047 -0
- data/doc/snapshots.md +72 -39
- data/lib/badline/audio/bare_player.rb +50 -17
- data/lib/badline/audio/machine_player.rb +15 -9
- data/lib/badline/audio/renderer.rb +32 -21
- data/lib/badline/c128/banks.rb +6 -3
- data/lib/badline/c128/bus/keyed_state.rb +45 -0
- data/lib/badline/c128/bus.rb +17 -3
- 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 +20 -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/keyed_state.rb +41 -0
- data/lib/badline/c64/bus.rb +2 -1
- data/lib/badline/c64/cartridge/action_replay.rb +0 -13
- 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 +0 -12
- data/lib/badline/c64/cartridge/flash.rb +3 -3
- data/lib/badline/c64/cartridge/g_mod2.rb +0 -12
- 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 +393 -0
- data/lib/badline/c64/cartridge/pagefox.rb +0 -10
- data/lib/badline/c64/cartridge/retro_replay.rb +0 -8
- data/lib/badline/c64/cartridge/rgcd.rb +0 -10
- data/lib/badline/c64/cartridge/super_games.rb +0 -10
- data/lib/badline/c64/cartridge/zaxxon.rb +0 -16
- data/lib/badline/c64/cartridge.rb +1 -27
- data/lib/badline/c64/kernal_traps.rb +28 -25
- data/lib/badline/c64/keyed_modules.rb +111 -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 +119 -0
- data/lib/badline/chips/ted/timers.rb +66 -0
- data/lib/badline/chips/ted.rb +143 -0
- data/lib/badline/chips/tia.rb +49 -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 +38 -0
- data/lib/badline/drives/datasette.rb +40 -7
- 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 +1 -1
- data/lib/badline/frontend/controls.rb +19 -3
- data/lib/badline/frontend/drive_led.rb +44 -21
- data/lib/badline/frontend/menu/menu_dialogs.rb +2 -2
- data/lib/badline/frontend/snapshots.rb +12 -1
- data/lib/badline/frontend/timeline.rb +28 -10
- 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 +5 -2
- data/lib/badline/media/boot_disk.rb +14 -2
- data/lib/badline/media/extensions.rb +10 -0
- data/lib/badline/memory.rb +3 -6
- data/lib/badline/options/family.rb +5 -2
- data/lib/badline/options/help.rb +42 -16
- data/lib/badline/options/table.rb +2 -0
- data/lib/badline/options/validation.rb +2 -0
- data/lib/badline/options.rb +6 -1
- data/lib/badline/plus4/acia.rb +45 -0
- data/lib/badline/plus4/bus.rb +312 -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 +51 -0
- data/lib/badline/plus4/model.rb +38 -0
- data/lib/badline/plus4/user_port.rb +31 -0
- data/lib/badline/plus4.rb +164 -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 +4 -0
- data/lib/badline/snapshot/field_doc/markdown.rb +112 -0
- data/lib/badline/snapshot/field_doc.rb +109 -0
- data/lib/badline/snapshot/image.rb +134 -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/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 +43 -20
- data/lib/badline/storage/crt_file.rb +3 -1
- data/lib/badline/storage/g64_image.rb +8 -3
- data/lib/badline/storage/tap.rb +43 -15
- 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 +61 -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
|
@@ -14,7 +14,7 @@ module Badline
|
|
|
14
14
|
# first, plays .sid tunes and directories of them in the terminal.
|
|
15
15
|
# `c64`, `vic20` or `c128`, given first, names the machine family, and
|
|
16
16
|
# without one the family is the C64, or the C128 for a program that loads
|
|
17
|
-
# where C128 BASIC starts. The native build adds --no-sound and
|
|
17
|
+
# where C128 BASIC starts. `plus4` is refused until the Plus/4 runs. 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
|
|
@@ -94,6 +94,7 @@ module Badline
|
|
|
94
94
|
@true_drive = false
|
|
95
95
|
@screenshot = ""
|
|
96
96
|
@save_snapshot = ""
|
|
97
|
+
@vice = false
|
|
97
98
|
@timeline = []
|
|
98
99
|
@jit = true
|
|
99
100
|
@help = false
|
|
@@ -171,6 +172,9 @@ module Badline
|
|
|
171
172
|
|
|
172
173
|
def true_drive? = @true_drive
|
|
173
174
|
|
|
175
|
+
# Whether --save-snapshot adds VICE's modules, so x64sc opens it.
|
|
176
|
+
def vice? = @vice
|
|
177
|
+
|
|
174
178
|
def jit? = @jit
|
|
175
179
|
|
|
176
180
|
# The name of the model to build: a C64 Badline::Model names, a VIC-20
|
|
@@ -272,6 +276,7 @@ module Badline
|
|
|
272
276
|
when "--no-vsync" then @vsync = false
|
|
273
277
|
when "--verbose" then @verbose = true
|
|
274
278
|
when "--true-drive" then @true_drive = true
|
|
279
|
+
when "--vice" then @vice = true
|
|
275
280
|
when "--ntsc" then @models << "ntsc"
|
|
276
281
|
when "--c64" then @c64_mode = true
|
|
277
282
|
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,312 @@
|
|
|
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
|
+
class Bus
|
|
32
|
+
include Addressable
|
|
33
|
+
|
|
34
|
+
RAM_POWER_ON = 0x00
|
|
35
|
+
|
|
36
|
+
# RAM through a size it repeats at.
|
|
37
|
+
class MirroredRAM
|
|
38
|
+
def initialize(ram, mask)
|
|
39
|
+
@ram = ram
|
|
40
|
+
@mask = mask
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def peek(addr) = @ram.peek(addr & @mask)
|
|
44
|
+
|
|
45
|
+
def poke(addr, value)
|
|
46
|
+
@ram.poke(addr & @mask, value)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# The last byte the CPU moved, which an empty spot reads back.
|
|
51
|
+
class OpenBus
|
|
52
|
+
def initialize(bus)
|
|
53
|
+
@bus = bus
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def peek(_addr) = @bus.data
|
|
57
|
+
def poke(_addr, _value); end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Page 0: the port at $00 and $01, RAM above it.
|
|
61
|
+
class ZeroPage
|
|
62
|
+
def initialize(port, ram)
|
|
63
|
+
@port = port
|
|
64
|
+
@ram = ram
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def peek(addr) = addr < 2 ? @port.peek(addr) : @ram.peek(addr)
|
|
68
|
+
|
|
69
|
+
def poke(addr, value)
|
|
70
|
+
return @port.poke(addr, value) if addr < 2
|
|
71
|
+
|
|
72
|
+
@ram.poke(addr, value)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Page $FD: the ACIA, the user port, the keyboard port and the ROM
|
|
77
|
+
# bank latch, by the address's bits 4-7. +acia+ and +user_port+ are
|
|
78
|
+
# nil where the machine has none.
|
|
79
|
+
class IOPage
|
|
80
|
+
def initialize(keyboard_port:, acia: nil, user_port: nil)
|
|
81
|
+
@bus = nil
|
|
82
|
+
@acia = acia
|
|
83
|
+
@user_port = user_port
|
|
84
|
+
@keyboard_port = keyboard_port
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# The bus whose last byte an empty spot reads and whose bank latch
|
|
88
|
+
# a write to $FDD0-$FDDF sets.
|
|
89
|
+
def connect(bus)
|
|
90
|
+
@bus = bus
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def peek(addr)
|
|
94
|
+
case addr & 0xf0
|
|
95
|
+
when 0x00 then @acia ? @acia.peek(addr) : @bus.data
|
|
96
|
+
when 0x10 then @user_port ? @user_port.peek(addr) : @bus.data
|
|
97
|
+
when 0x30 then @keyboard_port.peek(addr)
|
|
98
|
+
else @bus.data
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def poke(addr, value)
|
|
103
|
+
case addr & 0xf0
|
|
104
|
+
when 0x00 then @acia&.poke(addr, value)
|
|
105
|
+
when 0x10 then @user_port&.poke(addr, value)
|
|
106
|
+
when 0x30 then @keyboard_port.poke(addr, value)
|
|
107
|
+
when 0xd0 then @bus.select_banks(addr)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# VICE's debug cartridge register at $FDCF, a spot in the I/O page
|
|
113
|
+
# that nothing answers (plus4world forum post 31417). A write there
|
|
114
|
+
# calls the handler with the byte, the exit code the VICE testprogs
|
|
115
|
+
# report. Every other access falls through to the I/O page.
|
|
116
|
+
class DebugRegister
|
|
117
|
+
ADDRESS = 0xfdcf
|
|
118
|
+
|
|
119
|
+
def initialize(io, &handler)
|
|
120
|
+
@io = io
|
|
121
|
+
@handler = handler
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def peek(addr) = @io.peek(addr)
|
|
125
|
+
|
|
126
|
+
def poke(addr, value)
|
|
127
|
+
return @handler.call(value) if addr == ADDRESS
|
|
128
|
+
|
|
129
|
+
@io.poke(addr, value)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Page $FE with a 1551's TCBM interface plugged in: the 6523 TIA
|
|
134
|
+
# that talks to drive 9 at $FEC0-$FEDF and drive 8's at $FEE0-$FEFF.
|
|
135
|
+
# The rest of the page is open.
|
|
136
|
+
class TCBMPage
|
|
137
|
+
def initialize(bus, drive8:, drive9:)
|
|
138
|
+
@bus = bus
|
|
139
|
+
@drive8 = drive8
|
|
140
|
+
@drive9 = drive9
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def peek(addr)
|
|
144
|
+
return @bus.data if addr < 0xfec0
|
|
145
|
+
|
|
146
|
+
addr < 0xfee0 ? @drive9.peek(addr) : @drive8.peek(addr)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def poke(addr, value)
|
|
150
|
+
return if addr < 0xfec0
|
|
151
|
+
|
|
152
|
+
addr < 0xfee0 ? @drive9.poke(addr, value) : @drive8.poke(addr, value)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Page $FF: the TED in $FF00-$FF3F, its two phantom registers, and
|
|
157
|
+
# ROM or RAM above.
|
|
158
|
+
class TopPage
|
|
159
|
+
def initialize(bus, ted)
|
|
160
|
+
@bus = bus
|
|
161
|
+
@ted = ted
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def peek(addr)
|
|
165
|
+
return @ted.peek(addr) if addr < 0xff40
|
|
166
|
+
|
|
167
|
+
@bus.top_memory.peek(addr)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def poke(addr, value)
|
|
171
|
+
return @bus.ram_view.poke(addr, value) if addr >= 0xff40
|
|
172
|
+
|
|
173
|
+
case addr
|
|
174
|
+
when 0xff3e then @bus.rom_selected = true
|
|
175
|
+
when 0xff3f then @bus.rom_selected = false
|
|
176
|
+
else @ted.poke(addr, value)
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
attr_reader :ram, :ram_view, :data, :low_bank, :high_bank
|
|
182
|
+
|
|
183
|
+
# What the top page reads above $FF3F: the high ROM bank or RAM.
|
|
184
|
+
attr_reader :top_memory
|
|
185
|
+
|
|
186
|
+
# The CPU sees ROM at $8000-$FFFF.
|
|
187
|
+
attr_reader :rom_selected
|
|
188
|
+
|
|
189
|
+
# +io+ is the IOPage, +model+ a Model profile, whose KERNAL it maps
|
|
190
|
+
# and whose function ROMs it maps where the model has them, and
|
|
191
|
+
# +ram_kb+ the RAM fitted.
|
|
192
|
+
def initialize(ted:, port:, io:, model: Model::PLUS4, ram_kb: model.ram_kb)
|
|
193
|
+
addressable_at(0, length: 2**16)
|
|
194
|
+
size = ram_kb * 1024
|
|
195
|
+
@ram = Memory.new(Array.new(size, RAM_POWER_ON), length: size, start: 0)
|
|
196
|
+
@ram_view = MirroredRAM.new(@ram, size - 1)
|
|
197
|
+
@ted = ted
|
|
198
|
+
@open_bus = OpenBus.new(self)
|
|
199
|
+
@low_roms = [ROM.load("plus4/basic.rom", 0x8000), nil, nil, nil]
|
|
200
|
+
@high_roms = [ROM.load(model.kernal, 0xc000), nil, nil, nil]
|
|
201
|
+
if model.plus4_parts
|
|
202
|
+
@low_roms[1] = ROM.load("plus4/function-low.rom", 0x8000)
|
|
203
|
+
@high_roms[1] = ROM.load("plus4/function-high.rom", 0xc000)
|
|
204
|
+
end
|
|
205
|
+
@zero_page = ZeroPage.new(port, @ram_view)
|
|
206
|
+
@io_page = io
|
|
207
|
+
io.connect(self)
|
|
208
|
+
@top_page = TopPage.new(self, ted)
|
|
209
|
+
@tcbm_page = @open_bus
|
|
210
|
+
@debug_register = nil
|
|
211
|
+
@data = 0
|
|
212
|
+
@read_pages = Array.new(256, @open_bus)
|
|
213
|
+
@write_pages = Array.new(256, @ram_view)
|
|
214
|
+
@low_bank = @high_bank = 0
|
|
215
|
+
@rom_selected = true
|
|
216
|
+
@top_memory = @ram_view
|
|
217
|
+
map_writes
|
|
218
|
+
map_reads
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def power_on!
|
|
222
|
+
@ram.clear!(Array.new(@ram.length, RAM_POWER_ON))
|
|
223
|
+
reset!
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# The RESET line selects ROM and the first bank of each.
|
|
227
|
+
def reset!
|
|
228
|
+
@low_bank = @high_bank = 0
|
|
229
|
+
self.rom_selected = true
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def rom_selected=(selected)
|
|
233
|
+
@rom_selected = selected
|
|
234
|
+
@ted.rom_selected = selected
|
|
235
|
+
map_reads
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Whether the CPU reads the KERNAL at $C000-$FFFF: ROM selected, with
|
|
239
|
+
# the KERNAL's bank, high bank 0.
|
|
240
|
+
def kernal_selected? = @rom_selected && @high_bank.zero?
|
|
241
|
+
|
|
242
|
+
# A write to the ROM bank latch at +addr+.
|
|
243
|
+
def select_banks(addr)
|
|
244
|
+
@low_bank = addr & 0x03
|
|
245
|
+
@high_bank = (addr >> 2) & 0x03
|
|
246
|
+
map_reads
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Calls the block with each byte written to $FDCF (DebugRegister).
|
|
250
|
+
def install_debug_register(&)
|
|
251
|
+
@debug_register = DebugRegister.new(@io_page, &)
|
|
252
|
+
map_writes
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Plugs in a 1551's TCBM interface, the TIAs at $FEC0-$FEFF.
|
|
256
|
+
def plug_tcbm(drive8:, drive9:)
|
|
257
|
+
@tcbm_page = TCBMPage.new(self, drive8:, drive9:)
|
|
258
|
+
map_writes
|
|
259
|
+
map_reads
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Puts a cartridge's ROM chips in the slots of cartridge 1: a chip at
|
|
263
|
+
# $8000 in low bank 2 and one at $C000 in high bank 2, each repeated
|
|
264
|
+
# through the bank's 16K.
|
|
265
|
+
def attach_cartridge(chips)
|
|
266
|
+
chips.each do |chip|
|
|
267
|
+
data = chip.data * (0x4000 / chip.data.length)
|
|
268
|
+
if chip.address < 0xc000
|
|
269
|
+
@low_roms[2] = ROM.new(data, length: 0x4000, start: 0x8000)
|
|
270
|
+
else
|
|
271
|
+
@high_roms[2] = ROM.new(data, length: 0x4000, start: 0xc000)
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
map_reads
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def peek(addr)
|
|
278
|
+
@data = @read_pages[addr >> 8].peek(addr)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def poke(addr, value)
|
|
282
|
+
@data = value
|
|
283
|
+
@write_pages[addr >> 8].poke(addr, value)
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
private
|
|
287
|
+
|
|
288
|
+
def map_writes
|
|
289
|
+
@write_pages[0x00] = @zero_page
|
|
290
|
+
@write_pages[0xfd] = @debug_register || @io_page
|
|
291
|
+
@write_pages[0xfe] = @tcbm_page
|
|
292
|
+
@write_pages[0xff] = @top_page
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def map_reads
|
|
296
|
+
@read_pages.fill(@ram_view, 0x00, 0xfd)
|
|
297
|
+
@read_pages[0x00] = @zero_page
|
|
298
|
+
map_roms if @rom_selected
|
|
299
|
+
@top_memory = @rom_selected ? @high_roms[@high_bank] || @open_bus : @ram_view
|
|
300
|
+
@read_pages[0xfd] = @io_page
|
|
301
|
+
@read_pages[0xfe] = @tcbm_page
|
|
302
|
+
@read_pages[0xff] = @top_page
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def map_roms
|
|
306
|
+
@read_pages.fill(@low_roms[@low_bank] || @open_bus, 0x80, 0x40)
|
|
307
|
+
@read_pages.fill(@high_roms[@high_bank] || @open_bus, 0xc0, 0x3c)
|
|
308
|
+
@read_pages[0xfc] = @high_roms[0]
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
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
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Plus4
|
|
5
|
+
# The keyboard: the 6529 at $FD30-$FD3F, whose eight outputs drive the
|
|
6
|
+
# key matrix's lines, and the TED's K0-K7 inputs, which $FF08 latches
|
|
7
|
+
# (Chips::TED#keyboard_port=). The KERNAL's scan ($DB11) writes each
|
|
8
|
+
# line's mask to $FD30 and $FF08 and reads the keys back from $FF08,
|
|
9
|
+
# and its key table at $E026 puts each key at line times 8 plus input
|
|
10
|
+
# bit.
|
|
11
|
+
#
|
|
12
|
+
# The 6529 is a quasi-bidirectional latch: a read returns what was
|
|
13
|
+
# written to it.
|
|
14
|
+
class KeyboardPort
|
|
15
|
+
# One row of keys per 6529 output, in K0-K7 order, after the
|
|
16
|
+
# KERNAL's key table. HELP is the KERNAL's F7, and the two SHIFT keys
|
|
17
|
+
# share one place.
|
|
18
|
+
MATRIX = [
|
|
19
|
+
%i[delete return £ help f1 f2 f3 @],
|
|
20
|
+
%i[3 w a 4 z s e lshift],
|
|
21
|
+
%i[5 r d 6 c f t x],
|
|
22
|
+
%i[7 y g 8 b h u v],
|
|
23
|
+
%i[9 i j 0 m k o n],
|
|
24
|
+
%i[crsr_down p l crsr_up . : - ,],
|
|
25
|
+
%i[crsr_left * ; crsr_right esc = + /],
|
|
26
|
+
%i[1 clr_home control 2 space cbm q run_stop]
|
|
27
|
+
].freeze
|
|
28
|
+
|
|
29
|
+
attr_reader :keyboard, :output
|
|
30
|
+
|
|
31
|
+
def initialize(keyboard)
|
|
32
|
+
@keyboard = keyboard
|
|
33
|
+
@output = 0xff
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def reset!
|
|
37
|
+
@output = 0xff
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def peek(_addr) = @output
|
|
41
|
+
|
|
42
|
+
def poke(_addr, value)
|
|
43
|
+
@output = value
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# The K0-K7 inputs the TED latches on a write of +_value+ to $FF08:
|
|
47
|
+
# high but where a held key joins a line the 6529 pulls low.
|
|
48
|
+
def latch(_value) = @keyboard.scan(@output, 0xff).last
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Plus4
|
|
5
|
+
# The 264 series machines badline builds by name, after xplus4's
|
|
6
|
+
# -model names, with the C116 as another name for the C16: one board
|
|
7
|
+
# design with its RAM, its video standard and KERNAL, and whether the
|
|
8
|
+
# Plus/4's own parts are fitted (the 3-plus-1 function ROMs, the 6551
|
|
9
|
+
# ACIA and the user port's 6529).
|
|
10
|
+
module Model
|
|
11
|
+
Profile = Data.define(:name, :ram_kb, :ted, :kernal, :plus4_parts)
|
|
12
|
+
|
|
13
|
+
PLUS4 = Profile.new(name: "plus4", ram_kb: 64, ted: Chips::TED::PAL, kernal: "plus4/kernal-pal.rom",
|
|
14
|
+
plus4_parts: true)
|
|
15
|
+
PLUS4_NTSC = Profile.new(name: "plus4ntsc", ram_kb: 64, ted: Chips::TED::NTSC,
|
|
16
|
+
kernal: "plus4/kernal-ntsc.rom", plus4_parts: true)
|
|
17
|
+
C16 = Profile.new(name: "c16", ram_kb: 16, ted: Chips::TED::PAL, kernal: "plus4/kernal-pal.rom",
|
|
18
|
+
plus4_parts: false)
|
|
19
|
+
C16_NTSC = Profile.new(name: "c16ntsc", ram_kb: 16, ted: Chips::TED::NTSC, kernal: "plus4/kernal-ntsc.rom",
|
|
20
|
+
plus4_parts: false)
|
|
21
|
+
|
|
22
|
+
ALL = [PLUS4, PLUS4_NTSC, C16, C16_NTSC].freeze
|
|
23
|
+
|
|
24
|
+
# Other names a model goes by.
|
|
25
|
+
ALIASES = { "c116" => "c16" }.freeze
|
|
26
|
+
|
|
27
|
+
# The RAM sizes a machine can have, in K.
|
|
28
|
+
RAM_SIZES = [16, 32, 64].freeze
|
|
29
|
+
|
|
30
|
+
def self.named(name)
|
|
31
|
+
model = ALL.find { |candidate| candidate.name == ALIASES.fetch(name, name) }
|
|
32
|
+
raise ArgumentError, "no Plus/4 model named #{name}" if model.nil?
|
|
33
|
+
|
|
34
|
+
model
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Plus4
|
|
5
|
+
# The Plus/4's user port 6529 at $FD10-$FD1F, a quasi-bidirectional
|
|
6
|
+
# latch: a line reads low where the latch or the outside pulls it. With
|
|
7
|
+
# nothing plugged into the user port, the outside pulls only bit 2, the
|
|
8
|
+
# cassette sense switch, low while a key is down on the deck. The
|
|
9
|
+
# KERNAL's IRQ reads it there ($CFBF).
|
|
10
|
+
class UserPort
|
|
11
|
+
TAPE_SENSE = 0x04
|
|
12
|
+
|
|
13
|
+
attr_reader :output
|
|
14
|
+
|
|
15
|
+
def initialize(datasette)
|
|
16
|
+
@datasette = datasette
|
|
17
|
+
@output = 0xff
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def reset!
|
|
21
|
+
@output = 0xff
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def peek(_addr) = @datasette.sense_low? ? @output & ~TAPE_SENSE : @output
|
|
25
|
+
|
|
26
|
+
def poke(_addr, value)
|
|
27
|
+
@output = value
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|