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
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class CBM2
|
|
5
|
+
# The 6509: a 6502 core with four more address lines, which pick one of
|
|
6
|
+
# the Bus's sixteen segments. Every access goes to the execution
|
|
7
|
+
# segment, but the ones at the address LDA (zp),Y and STA (zp),Y index,
|
|
8
|
+
# their data and the dummy read before it, which go to the indirection
|
|
9
|
+
# segment: the opcode and the zero-page pointer stay in the execution
|
|
10
|
+
# segment. Interrupts leave both segments alone.
|
|
11
|
+
class CPU
|
|
12
|
+
include Badline::Chips::CPU::Core
|
|
13
|
+
|
|
14
|
+
def initialize(memory, debug: false)
|
|
15
|
+
super
|
|
16
|
+
@indirect = false
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def reset!
|
|
20
|
+
super
|
|
21
|
+
@indirect = false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def op_pointer_high_y
|
|
27
|
+
super
|
|
28
|
+
@indirect = @operation == :lda || @operation == :sta
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def op_idx_dummy
|
|
32
|
+
return super unless @indirect
|
|
33
|
+
|
|
34
|
+
@memory.select_indirect!
|
|
35
|
+
super
|
|
36
|
+
@memory.select_execute!
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def op_mem_read
|
|
40
|
+
return super unless @indirect
|
|
41
|
+
|
|
42
|
+
@indirect = false
|
|
43
|
+
@memory.select_indirect!
|
|
44
|
+
super
|
|
45
|
+
@memory.select_execute!
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def op_mem_write
|
|
49
|
+
return super unless @indirect
|
|
50
|
+
|
|
51
|
+
@indirect = false
|
|
52
|
+
@memory.select_indirect!
|
|
53
|
+
super
|
|
54
|
+
@memory.select_execute!
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class CBM2
|
|
5
|
+
# What the B series makes of its 6845's outputs. Each character the
|
|
6
|
+
# CRTC displays reads the 2K screen RAM at its memory address, MA0-MA10,
|
|
7
|
+
# and the character ROM's 16-line glyph for the screen code's low seven
|
|
8
|
+
# bits, from the set TPI1's CA picks: the first 128 glyphs while it is
|
|
9
|
+
# low, the second while it is high. Bit 7 of the screen code reverses
|
|
10
|
+
# the glyph. The ROM's row is the CRTC's raster line, RA0-RA3, so the
|
|
11
|
+
# 600's eight lines show the 8x8 glyphs in the first half of each and
|
|
12
|
+
# the 700's fourteen the 8x16 ones.
|
|
13
|
+
#
|
|
14
|
+
# The cursor is the 6845's: on the character at R14:R15, from line
|
|
15
|
+
# R10's low five bits to line R11, it reverses the dots. R10's bits 5
|
|
16
|
+
# and 6 pick its blink: 00 steady, 01 no cursor, 10 on for 8 fields and
|
|
17
|
+
# off for 8, 11 on for 16 and off for 16.
|
|
18
|
+
#
|
|
19
|
+
# The picture is a PET::Canvas framed as xcbm2 frames its screenshots:
|
|
20
|
+
# 704 dots wide, the first displayed character 32 dots in, and 266
|
|
21
|
+
# lines high with the first row 33 lines down on a 600, or 367 lines
|
|
22
|
+
# with it 8 down on a 700. Each frame paints its displayed lines from
|
|
23
|
+
# there down. A frame laid out otherwise than the last, its lines,
|
|
24
|
+
# rows or sync moved, starts on a black canvas, so nothing the old
|
|
25
|
+
# layout painted stays.
|
|
26
|
+
class CRTCVideo
|
|
27
|
+
LEFT = 32
|
|
28
|
+
WIDTH = 704
|
|
29
|
+
|
|
30
|
+
# The lines of the picture and the line its first row starts on, for
|
|
31
|
+
# a 600 and a 700.
|
|
32
|
+
FRAMES = { false => [266, 33], true => [367, 8] }.freeze
|
|
33
|
+
|
|
34
|
+
attr_reader :canvas, :frames
|
|
35
|
+
|
|
36
|
+
# Whether it paints the characters: a run that never shows the
|
|
37
|
+
# picture can leave it alone.
|
|
38
|
+
attr_accessor :render
|
|
39
|
+
|
|
40
|
+
# +characters+ is the character ROM's bytes, +screen+ the screen RAM,
|
|
41
|
+
# +tpi+ TPI1, whose CA picks the set, and +registers+ the CRTC's.
|
|
42
|
+
def initialize(characters, screen, tpi, registers, high_profile:)
|
|
43
|
+
@glyphs = characters
|
|
44
|
+
@screen = screen
|
|
45
|
+
@tpi = tpi
|
|
46
|
+
@registers = registers
|
|
47
|
+
height, @top = FRAMES.fetch(high_profile)
|
|
48
|
+
@canvas = PET::Canvas.new(WIDTH, height)
|
|
49
|
+
@frames = 0
|
|
50
|
+
@line = 0
|
|
51
|
+
@line_shown = false
|
|
52
|
+
@origin = LEFT
|
|
53
|
+
@layout = []
|
|
54
|
+
@render = true
|
|
55
|
+
frame_start
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def character(address, raster, since_hsync)
|
|
59
|
+
@line_shown = true
|
|
60
|
+
y = @top + @line
|
|
61
|
+
return if y >= @canvas.height || !@render
|
|
62
|
+
|
|
63
|
+
@canvas.paint(@origin + (since_hsync << 3), y, row_bits(address, raster))
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def hsync; end
|
|
67
|
+
|
|
68
|
+
def line_end
|
|
69
|
+
@line += 1 if @line_shown
|
|
70
|
+
@line_shown = false
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def rows_end; end
|
|
74
|
+
|
|
75
|
+
# A frame starts painting from the first row's line again, its
|
|
76
|
+
# characters placed so the first one the line displays sits LEFT
|
|
77
|
+
# dots in.
|
|
78
|
+
def frame_start
|
|
79
|
+
regs = @registers
|
|
80
|
+
@frames += 1
|
|
81
|
+
@line = 0
|
|
82
|
+
@origin = LEFT - ((regs[0] + 1 - regs[2]) << 3)
|
|
83
|
+
layout = regs.values_at(0, 1, 2, 4, 5, 6, 9, 12, 13)
|
|
84
|
+
return if layout == @layout
|
|
85
|
+
|
|
86
|
+
@layout = layout
|
|
87
|
+
@canvas.clear!
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# The picture under canvas.*, the fields counted, the line the frame
|
|
91
|
+
# paints next and the layout it was framed by, in a keyed snapshot
|
|
92
|
+
# module (BL-VIDEO).
|
|
93
|
+
def save_fields(out)
|
|
94
|
+
out.int("frames", @frames).int("line", @line).bool("line_shown", @line_shown).int("origin", @origin)
|
|
95
|
+
out.ints("layout", @layout)
|
|
96
|
+
@canvas.save_fields(out, "canvas.")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def load_fields(input)
|
|
100
|
+
@frames = input.int("frames", @frames)
|
|
101
|
+
@line = input.int("line", @line)
|
|
102
|
+
@line_shown = input.bool("line_shown", @line_shown)
|
|
103
|
+
@origin = input.int("origin", @origin)
|
|
104
|
+
@layout = input.ints("layout", @layout).dup
|
|
105
|
+
@canvas.load_fields(input, "canvas.")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
private
|
|
109
|
+
|
|
110
|
+
def row_bits(address, raster)
|
|
111
|
+
code = @screen.peek(0xd000 | (address & 0x7ff))
|
|
112
|
+
glyph = (code & 0x7f) | (@tpi.ca? ? 0x80 : 0)
|
|
113
|
+
bits = @glyphs[(glyph << 4) | (raster & 0x0f)]
|
|
114
|
+
bits ^= 0xff if code >= 0x80
|
|
115
|
+
bits ^= 0xff if cursor?(address, raster)
|
|
116
|
+
bits
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def cursor?(address, raster)
|
|
120
|
+
regs = @registers
|
|
121
|
+
return false unless address == ((regs[14] << 8) | regs[15])
|
|
122
|
+
return false if raster < (regs[10] & 0x1f) || raster > regs[11]
|
|
123
|
+
|
|
124
|
+
case regs[10] & 0x60
|
|
125
|
+
when 0x00 then true
|
|
126
|
+
when 0x20 then false
|
|
127
|
+
when 0x40 then @frames.nobits?(0x08)
|
|
128
|
+
else @frames.nobits?(0x10)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "badline/machine/ieee_drives"
|
|
4
|
+
|
|
5
|
+
module Badline
|
|
6
|
+
class CBM2
|
|
7
|
+
# The CBM-II's true drive, a 2031, 8050 or 8250 on its IEEE-488 bus
|
|
8
|
+
# (IEEEPorts), as Machine::IEEEDrives has it, at the machine's clock:
|
|
9
|
+
# 2 MHz on the B series, the VIC-II's on the P500. Both include it.
|
|
10
|
+
module IEEEDrive
|
|
11
|
+
include Machine::IEEEDrives
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def ieee_host_clock_hz = timing.clock_hz
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class CBM2
|
|
5
|
+
# Where the B series KERNALs, 901244-04a and -03b, keep the eight
|
|
6
|
+
# IEEE-488 primitives, their file variables in segment 15, and the
|
|
7
|
+
# steps they take on the bus's lines (KernalTrap::IEEELayout). TALK and
|
|
8
|
+
# LISTEN take the device in A.
|
|
9
|
+
#
|
|
10
|
+
# The KERNAL drives the bus from TPI1's port A at $DE00 (PA3 ATN, PA4
|
|
11
|
+
# DAV, PA5 EOI, PA6 NDAC, PA7 NRFD, PA0-PA2 the transceivers'
|
|
12
|
+
# directions) and the CIA's port A at $DC00, the data lines inverted.
|
|
13
|
+
# TALK and LISTEN set both ports' directions for sending and pull ATN
|
|
14
|
+
# low, TKSA and ACPTR turn NRFD, NDAC and the data lines round for
|
|
15
|
+
# listening, and each handshake times out on CIA timer B, which the
|
|
16
|
+
# ROM starts with $FF to $DC07 and $11 to $DC0F and acknowledges by
|
|
17
|
+
# reading $DC0D. ACPTR leaves port A as TKSA does, without touching
|
|
18
|
+
# the directions. 901244-03b differs from -04a in setting PA0 where -04a
|
|
19
|
+
# clears it, at the end of UNTALK, UNLISTEN and ACPTR.
|
|
20
|
+
module IEEELayout
|
|
21
|
+
LineStep = KernalTrap::IEEELineStep
|
|
22
|
+
|
|
23
|
+
# A write of +value+ to +address+.
|
|
24
|
+
def self.write(address, value) = LineStep.new(address:, keep: 0, set: value)
|
|
25
|
+
|
|
26
|
+
# +address+'s bits in +keep+, ORed with +set+.
|
|
27
|
+
def self.update(address, keep, set) = LineStep.new(address:, keep:, set:)
|
|
28
|
+
|
|
29
|
+
TIMER = [write(0xdc07, 0xff), write(0xdc0f, 0x11),
|
|
30
|
+
LineStep.new(address: 0xdc0d, keep: KernalTrap::IEEE_LINE_READ, set: 0)].freeze
|
|
31
|
+
ATTENTION = [write(0xde03, 0x3f), write(0xdc00, 0xff), write(0xdc02, 0xff), write(0xde00, 0xfa),
|
|
32
|
+
update(0xde00, 0xf7, 0)].freeze
|
|
33
|
+
BYTE_SENT = [update(0xde00, 0xff, 0x12), write(0xdc00, 0xff)].freeze
|
|
34
|
+
RELEASE_ATTENTION = [update(0xde00, 0xff, 0x08)].freeze
|
|
35
|
+
LISTEN_TO_TALKER = [update(0xde00, 0x39, 0), write(0xde03, 0xc7), write(0xdc02, 0x00)].freeze
|
|
36
|
+
|
|
37
|
+
# The end of UNTALK and UNLISTEN, which leaves PA $F8, or $F9 on
|
|
38
|
+
# 901244-03b, before letting ATN go, and the end of ACPTR, which
|
|
39
|
+
# leaves PA0 clear, or set on 901244-03b.
|
|
40
|
+
def self.lines(release)
|
|
41
|
+
KernalTrap::IEEELines.new(
|
|
42
|
+
timer: TIMER, attention: ATTENTION, byte_sent: BYTE_SENT, release_attention: RELEASE_ATTENTION,
|
|
43
|
+
listen_to_talker: LISTEN_TO_TALKER, byte_received: [update(0xde00, 0x39, release & 0x01)].freeze,
|
|
44
|
+
release: (ATTENTION + BYTE_SENT + [write(0xde00, release)] + LISTEN_TO_TALKER.drop(1) +
|
|
45
|
+
RELEASE_ATTENTION).freeze
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# The file variables in segment 15's zero page: the file name's
|
|
50
|
+
# pointer at $90, SAVE's start at $99 and its end at $96, ST at $9C,
|
|
51
|
+
# FNLEN at $9D, FA at $9F, SA at $A0 and VERIFY at $035F.
|
|
52
|
+
ZERO_PAGE = KernalTrap::ZeroPage.new(
|
|
53
|
+
status: 0x9c, verify: 0x035f, start_address: 0x99, end_address: 0x96, filename_length: 0x9d,
|
|
54
|
+
secondary_address: 0xa0, device: 0x9f, filename_address: 0x90, save_start: 0x99, load_address: 0x99
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def self.layout(release)
|
|
58
|
+
KernalTrap::IEEELayout.new(
|
|
59
|
+
talk: 0xf230, listen: 0xf234, second: 0xf274, tksa: 0xf280, ciout: 0xf297, untalk: 0xf2ab,
|
|
60
|
+
unlisten: 0xf2af, acptr: 0xf30a, overwrites_device: false, device_in_a: true, zero_page: ZERO_PAGE,
|
|
61
|
+
lines: lines(release)
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# By the KERNAL's name in Model::KERNALS.
|
|
66
|
+
LAYOUTS = { "04a" => layout(0xf8), "03b" => layout(0xf9) }.freeze
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class CBM2
|
|
5
|
+
# The CBM-II's side of the IEEE-488 bus (Drives::IEEEBus): the data
|
|
6
|
+
# lines DIO1-8 on the CIA's port A at $DC00, and the handshake on
|
|
7
|
+
# TPI1's port A at $DE00:
|
|
8
|
+
#
|
|
9
|
+
# PA3 ATN PA4 DAV PA5 EOI PA6 NDAC PA7 NRFD
|
|
10
|
+
#
|
|
11
|
+
# PA0-PA2 set the transceivers' directions, which follow the ports'
|
|
12
|
+
# direction registers as the KERNAL sets them. Each line goes through
|
|
13
|
+
# its transceiver as it is: an output bit at 0 pulls its line low, and
|
|
14
|
+
# an input bit reads its line, 0 while it is low. It is TPI1's
|
|
15
|
+
# peripheral, and Data the CIA's.
|
|
16
|
+
#
|
|
17
|
+
# The machine pushes the lines it pulls into the bus for the drives on
|
|
18
|
+
# it once a cycle while a drive is on the bus (#cycle_lines), and only
|
|
19
|
+
# when they moved.
|
|
20
|
+
class IEEEPorts
|
|
21
|
+
ATN = 0x08
|
|
22
|
+
DAV = 0x10
|
|
23
|
+
EOI = 0x20
|
|
24
|
+
NDAC = 0x40
|
|
25
|
+
NRFD = 0x80
|
|
26
|
+
|
|
27
|
+
# The CIA's port A: the data lines.
|
|
28
|
+
class Data
|
|
29
|
+
def initialize(ports)
|
|
30
|
+
@ports = ports
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def read_a(_port_a, _port_b) = ~@ports.low_lines & 0xff
|
|
34
|
+
|
|
35
|
+
def read_b(_port_a, _port_b) = 0xff
|
|
36
|
+
|
|
37
|
+
def port_b4_high? = true
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
attr_reader :bus, :data
|
|
41
|
+
|
|
42
|
+
def initialize(bus)
|
|
43
|
+
@bus = bus
|
|
44
|
+
@data = Data.new(self)
|
|
45
|
+
@cia = nil
|
|
46
|
+
@tpi = nil
|
|
47
|
+
@pushed = 0
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def connect(cia, tpi)
|
|
51
|
+
@cia = cia
|
|
52
|
+
@tpi = tpi
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# TPI1's port A: the handshake lines and ATN.
|
|
56
|
+
def read_a(_lines)
|
|
57
|
+
low = low_lines
|
|
58
|
+
value = 0xff
|
|
59
|
+
value &= ~ATN if low.anybits?(Drives::IEEEBus::ATN)
|
|
60
|
+
value &= ~DAV if low.anybits?(Drives::IEEEBus::DAV)
|
|
61
|
+
value &= ~EOI if low.anybits?(Drives::IEEEBus::EOI)
|
|
62
|
+
value &= ~NDAC if low.anybits?(Drives::IEEEBus::NDAC)
|
|
63
|
+
value &= ~NRFD if low.anybits?(Drives::IEEEBus::NRFD)
|
|
64
|
+
value & 0xff
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def read_b(_lines) = 0xff
|
|
68
|
+
|
|
69
|
+
def read_c(_lines) = 0xff
|
|
70
|
+
|
|
71
|
+
# The lines low on the bus.
|
|
72
|
+
def low_lines = @bus.low_lines(controller_lines)
|
|
73
|
+
|
|
74
|
+
# The drives on the bus see the lines the machine pulls now, if they
|
|
75
|
+
# moved.
|
|
76
|
+
def cycle_lines
|
|
77
|
+
lines = controller_lines
|
|
78
|
+
push(lines) unless lines == @pushed
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# The drives on the bus see the lines the machine pulls now.
|
|
82
|
+
def push_lines = push(controller_lines)
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def push(lines)
|
|
87
|
+
@pushed = lines
|
|
88
|
+
@bus.host_lines = lines
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# The lines the machine pulls low.
|
|
92
|
+
def controller_lines
|
|
93
|
+
low = ~@cia.port_a_lines & Drives::IEEEBus::DIO
|
|
94
|
+
port_a = @tpi.port_a_output
|
|
95
|
+
low |= Drives::IEEEBus::ATN if port_a.nobits?(ATN)
|
|
96
|
+
low |= Drives::IEEEBus::DAV if port_a.nobits?(DAV)
|
|
97
|
+
low |= Drives::IEEEBus::EOI if port_a.nobits?(EOI)
|
|
98
|
+
low |= Drives::IEEEBus::NDAC if port_a.nobits?(NDAC)
|
|
99
|
+
low |= Drives::IEEEBus::NRFD if port_a.nobits?(NRFD)
|
|
100
|
+
low
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class CBM2
|
|
5
|
+
# The disk device 8 serves through the traps on the KERNAL's IEEE-488
|
|
6
|
+
# primitives (KernalTrap::IEEE, IEEELayout), and CHROUT capture. There
|
|
7
|
+
# is no LOAD or SAVE trap: the ROM's own LOAD and SAVE, which move the
|
|
8
|
+
# bytes into the segment they name, and BASIC's disk commands run
|
|
9
|
+
# through the primitives. Other devices find only a true drive on the
|
|
10
|
+
# bus (IEEEDrive), and the KERNAL reports the rest not present.
|
|
11
|
+
module IEEETraps
|
|
12
|
+
# Puts the storage in device 8. Mounting again swaps the disk while
|
|
13
|
+
# the machine runs, and the drive keeps its RAM and its status. The
|
|
14
|
+
# traps leave device 8 to a true drive with that number.
|
|
15
|
+
def mount(storage)
|
|
16
|
+
return @dos.insert(storage) if @dos
|
|
17
|
+
|
|
18
|
+
@dos = dos = KernalTrap::DOS.new(storage, channel_saves: true)
|
|
19
|
+
@ieee_trap = KernalTrap::IEEE.new(cpu:, bus:, layout: trap_layout, dos:).install
|
|
20
|
+
@ieee_trap.device = trap_device
|
|
21
|
+
@ieee_trap
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Takes device 8's storage out, and the traps with it.
|
|
25
|
+
def unmount
|
|
26
|
+
trap = @ieee_trap
|
|
27
|
+
return unless trap
|
|
28
|
+
|
|
29
|
+
trap.uninstall
|
|
30
|
+
@ieee_trap = nil
|
|
31
|
+
@dos = nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Whether device 8 serves a disk or directory through the traps.
|
|
35
|
+
def mounted? = !@dos.nil?
|
|
36
|
+
|
|
37
|
+
# The path of the disk or directory device 8 serves, or an empty one.
|
|
38
|
+
def mounted_path = @dos.nil? ? "" : @dos.path
|
|
39
|
+
|
|
40
|
+
# Device 8 through the traps in a keyed snapshot module (BL-TRAPDOS):
|
|
41
|
+
# the storage under storage.*, the DOS, and the IEEE-488 trap under
|
|
42
|
+
# ieee.*. Nothing mounted writes nothing.
|
|
43
|
+
def save_trap_dos_fields(out)
|
|
44
|
+
dos = @dos
|
|
45
|
+
return unless dos
|
|
46
|
+
|
|
47
|
+
Snapshot::StorageSetup.write_fields(dos.storage, out)
|
|
48
|
+
dos.save_fields(out)
|
|
49
|
+
@ieee_trap&.save_fields(out, "ieee.")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# `detached` mounts a disk image write-protected and no directory.
|
|
53
|
+
def load_trap_dos_fields(input, detached: false)
|
|
54
|
+
unmount
|
|
55
|
+
return if input.missing?
|
|
56
|
+
|
|
57
|
+
mount(Snapshot::StorageSetup.read_fields(input, detached:))
|
|
58
|
+
@dos&.load_fields(input)
|
|
59
|
+
@ieee_trap&.load_fields(input, "ieee.")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Records what the KERNAL prints through CHROUT (KernalTrap::Chrout).
|
|
63
|
+
def capture_output
|
|
64
|
+
@capture_output ||= KernalTrap::Chrout.new(cpu:, bus:, layout: trap_layout).tap do |trap|
|
|
65
|
+
cpu.install_trap(KernalTrap::Chrout::ADDRESS) { trap.call }
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# The IEEELayout of the KERNAL the machine runs.
|
|
70
|
+
def trap_layout = IEEELayout::LAYOUTS.fetch(@kernal)
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
# Device 8, unless a true drive is device 8.
|
|
75
|
+
def trap_device
|
|
76
|
+
device = KernalTrap::Routine::DEVICE
|
|
77
|
+
true_drive&.device == device ? nil : device
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class CBM2
|
|
5
|
+
# What TPI2's ports read from outside the chip: the key matrix and the
|
|
6
|
+
# board's jumpers. The KERNAL drives the matrix's sixteen column lines
|
|
7
|
+
# low one at a time from PB0-PB7 and PA0-PA7 and reads its six row
|
|
8
|
+
# lines on PC0-PC5, a 0 for each key down. PC6 and PC7 read the
|
|
9
|
+
# jumpers: PC7 high on a 700, whose editor then loads its own CRTC
|
|
10
|
+
# table, and PC6 high on a 600 at 60 Hz.
|
|
11
|
+
#
|
|
12
|
+
# MATRIX lists the keys on each column line, PB0 first, in row order,
|
|
13
|
+
# :none where no key sits, as the KERNAL's key table at $EA29 places
|
|
14
|
+
# them, named as the B series' keyboard matrix sheet labels them.
|
|
15
|
+
class KeyboardPorts
|
|
16
|
+
MATRIX = [
|
|
17
|
+
%i[f1 escape tab none shift control],
|
|
18
|
+
%i[f2 1 q a z none],
|
|
19
|
+
%i[f3 2 w s x c],
|
|
20
|
+
%i[f4 3 e d f v],
|
|
21
|
+
%i[f5 4 r t g b],
|
|
22
|
+
%i[f6 5 6 y h n],
|
|
23
|
+
%i[f7 7 u j m space],
|
|
24
|
+
%i[f8 8 i k , .],
|
|
25
|
+
%i[f9 9 o l ; /],
|
|
26
|
+
[:f10, :"0", :-, :p, :"[", :"'"],
|
|
27
|
+
[:cursor_down, :"=", :left_arrow, :"]", :return, :pi],
|
|
28
|
+
%i[cursor_up cursor_left cursor_right delete cbm none],
|
|
29
|
+
%i[clr_home kp_question kp7 kp4 kp1 kp0],
|
|
30
|
+
%i[off_rvs ce kp8 kp5 kp2 kp_period],
|
|
31
|
+
%i[norm_graph kp_multiply kp9 kp6 kp3 kp00],
|
|
32
|
+
%i[run_stop kp_divide kp_minus kp_plus kp_enter none]
|
|
33
|
+
].freeze
|
|
34
|
+
|
|
35
|
+
attr_reader :keyboard
|
|
36
|
+
|
|
37
|
+
# +jumpers+ is PC6 and PC7 as the board's jumpers drive them.
|
|
38
|
+
def initialize(keyboard, jumpers)
|
|
39
|
+
@keyboard = keyboard
|
|
40
|
+
@jumpers = jumpers
|
|
41
|
+
@tpi = nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# The TPI whose ports drive the column lines.
|
|
45
|
+
def connect(tpi)
|
|
46
|
+
@tpi = tpi
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def read_a(lines) = lines
|
|
50
|
+
|
|
51
|
+
def read_b(lines) = lines
|
|
52
|
+
|
|
53
|
+
def read_c(lines)
|
|
54
|
+
columns = (@tpi.port_a_output << 8) | @tpi.port_b_output
|
|
55
|
+
rows = @keyboard.scan(columns, lines | 0x3f).last
|
|
56
|
+
(rows & 0x3f) | (@jumpers & lines & 0xc0)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class CBM2
|
|
5
|
+
# The whole machine as keyed snapshot modules (Snapshot::Keyed), in the
|
|
6
|
+
# order a load applies them: BL-MACHINE with the setup, the RAM with
|
|
7
|
+
# the segment registers, the CRTC, the picture, the SID, the CIA, the ACIA, both
|
|
8
|
+
# TPIs, the CPU, device 8 through the traps and a true 2031 on the
|
|
9
|
+
# IEEE-488 bus.
|
|
10
|
+
#
|
|
11
|
+
# A load lays the pages out before the chips load, and works out what
|
|
12
|
+
# each unit derives from its fields once every unit is loaded.
|
|
13
|
+
module KeyedModules
|
|
14
|
+
def save_modules
|
|
15
|
+
bus = @bus
|
|
16
|
+
sections = [Snapshot::Keyed.section("BL-MACHINE") { |out| save_machine_fields(out) }]
|
|
17
|
+
sections << Snapshot::Keyed.section("BL-CPU") { |out| @cpu.save_fields(out) }
|
|
18
|
+
sections << Snapshot::Keyed.section("BL-RAM") { |out| bus.save_fields(out) }
|
|
19
|
+
sections << Snapshot::Keyed.section("BL-CRTC") { |out| @crtc.save_fields(out) }
|
|
20
|
+
sections << Snapshot::Keyed.section("BL-VIDEO") { |out| @video.save_fields(out) }
|
|
21
|
+
sections << Snapshot::Keyed.section("BL-SID") { |out| @sid.save_fields(out) }
|
|
22
|
+
sections << Snapshot::Keyed.section("BL-CIA") { |out| @cia.save_fields(out) }
|
|
23
|
+
sections << Snapshot::Keyed.section("BL-ACIA") { |out| @acia.save_fields(out) }
|
|
24
|
+
sections << Snapshot::Keyed.section("BL-TPI1") { |out| @tpi1.save_fields(out) }
|
|
25
|
+
sections << Snapshot::Keyed.section("BL-TPI2") { |out| @tpi2.save_fields(out) }
|
|
26
|
+
sections << Snapshot::Keyed.section("BL-TRAPDOS") { |out| save_trap_dos_fields(out) } if mounted?
|
|
27
|
+
save_drive_modules(sections)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Raises Snapshot::FormatError for the modules of a machine built
|
|
31
|
+
# another way.
|
|
32
|
+
def load_modules(modules)
|
|
33
|
+
check_snapshot_setup(Snapshot::CBM2Setup.read_fields(modules.machine))
|
|
34
|
+
load_machine_fields(modules.machine)
|
|
35
|
+
@bus.load_fields(modules.required("BL-RAM"))
|
|
36
|
+
@bus.restored!
|
|
37
|
+
@crtc.load_fields(modules.required("BL-CRTC"))
|
|
38
|
+
@video.load_fields(modules.required("BL-VIDEO"))
|
|
39
|
+
@sid.load_fields(modules.required("BL-SID"))
|
|
40
|
+
@cia.load_fields(modules.required("BL-CIA"))
|
|
41
|
+
@acia.load_fields(modules.required("BL-ACIA"))
|
|
42
|
+
@tpi1.load_fields(modules.required("BL-TPI1"))
|
|
43
|
+
@tpi2.load_fields(modules.required("BL-TPI2"))
|
|
44
|
+
@cpu.load_fields(modules.required("BL-CPU"))
|
|
45
|
+
trap_dos = modules.optional("BL-TRAPDOS")
|
|
46
|
+
trap_dos ? load_trap_dos_fields(trap_dos, detached: modules.detached?) : unmount
|
|
47
|
+
load_drive_modules(modules)
|
|
48
|
+
@cpu.restored!
|
|
49
|
+
@sid.restored!
|
|
50
|
+
@cia.restored!
|
|
51
|
+
@acia.restored!
|
|
52
|
+
self
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def check_snapshot_setup(setup)
|
|
58
|
+
ours = Snapshot::CBM2Setup.of(self)
|
|
59
|
+
return if setup == ours
|
|
60
|
+
|
|
61
|
+
raise Snapshot::FormatError, "the snapshot is of a #{setup}, not a #{ours}"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
include KeyedModules
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class CBM2
|
|
5
|
+
class Bus
|
|
6
|
+
# Segment 15's RAM, the screen RAM, the RAM board's segments, both
|
|
7
|
+
# segment registers, the last byte on the bus and a cartridge's ROMs
|
|
8
|
+
# by load address, in a keyed snapshot module (BL-RAM). The pages are
|
|
9
|
+
# laid out again once loaded (#restored!). The BASIC and KERNAL ROMs
|
|
10
|
+
# come with the machine.
|
|
11
|
+
module KeyedState
|
|
12
|
+
def save_fields(out)
|
|
13
|
+
@ram.save_fields(out, "system")
|
|
14
|
+
@screen.save_fields(out, "screen")
|
|
15
|
+
@segment_ram.each_with_index { |memory, segment| memory&.save_fields(out, "segment#{segment}") }
|
|
16
|
+
out.int("execution_segment", @execution_segment).int("indirect_segment", @indirect_segment)
|
|
17
|
+
out.int("data", @data)
|
|
18
|
+
out.ints("cartridge.addresses", @cartridge.map(&:start))
|
|
19
|
+
@cartridge.each { |rom| out.bytes("cartridge.#{rom.start}", rom.snapshot) }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def load_fields(input)
|
|
23
|
+
@ram.load_fields(input, "system")
|
|
24
|
+
@screen.load_fields(input, "screen")
|
|
25
|
+
@segment_ram.each_with_index { |memory, segment| memory&.load_fields(input, "segment#{segment}") }
|
|
26
|
+
@execution_segment = input.masked("execution_segment", @execution_segment, 0x0f)
|
|
27
|
+
@indirect_segment = input.masked("indirect_segment", @indirect_segment, 0x0f)
|
|
28
|
+
@data = input.byte("data", @data)
|
|
29
|
+
@cartridge = input.ints("cartridge.addresses", []).filter_map { |address| cartridge_rom(input, address) }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def restored! = map_pages
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
# The cartridge ROM saved for +address+, in $1000-$7FFF of segment
|
|
37
|
+
# 15, or nil.
|
|
38
|
+
def cartridge_rom(input, address)
|
|
39
|
+
data = input.bytes("cartridge.#{address}", "".b).bytes
|
|
40
|
+
return nil if data.empty? || address < 0x1000 || address + data.length > 0x8000
|
|
41
|
+
|
|
42
|
+
ROM.new(data, length: data.length, start: address)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
include KeyedState
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# The machine's own fields in a keyed snapshot module (Snapshot::Keyed,
|
|
50
|
+
# BL-MACHINE): the family, its setup by name (Snapshot::CBM2Setup), the
|
|
51
|
+
# cycle count, where the mains is in its period, the refresh counter,
|
|
52
|
+
# and the host's typing
|
|
53
|
+
# still to feed and on_init handlers still to run under host.*. The
|
|
54
|
+
# badline version that wrote it is written for reports and never read.
|
|
55
|
+
module KeyedState
|
|
56
|
+
# How many more on_init handlers the machine a snapshot was taken of had
|
|
57
|
+
# yet to run than this one has. Nil until the machine is restored.
|
|
58
|
+
attr_reader :init_handlers_lost
|
|
59
|
+
|
|
60
|
+
# Whether a snapshot saves now (Machine::Settling): the CPU between
|
|
61
|
+
# instructions or jammed.
|
|
62
|
+
def settled_for_snapshot? = @cpu.boundary? || @cpu.jammed?
|
|
63
|
+
|
|
64
|
+
# The SID's deferred cycles and writes, run up to this cycle.
|
|
65
|
+
def settle_chips_for_snapshot = @sid.catch_up!
|
|
66
|
+
|
|
67
|
+
def save_machine_fields(out)
|
|
68
|
+
out.text("family", "cbm2").text("written_by", VERSION)
|
|
69
|
+
Snapshot::CBM2Setup.of(self).write_fields(out)
|
|
70
|
+
out.int("cycles", @cycles).int("mains_countdown", @mains_countdown).bool("mains_high", @mains_high)
|
|
71
|
+
out.int("refresh", @refresh)
|
|
72
|
+
pending = @pending_keys
|
|
73
|
+
out.ints("host.pending_keys", pending) if pending
|
|
74
|
+
save_init_keys(out)
|
|
75
|
+
out.int("host.init_pending", @cycles <= @init_threshold ? @init_handlers.length : 0)
|
|
76
|
+
save_ieee_drive_fields(out)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def load_machine_fields(input)
|
|
80
|
+
@cycles = input.int("cycles", @cycles)
|
|
81
|
+
countdown = input.int("mains_countdown", @mains_countdown)
|
|
82
|
+
@mains_countdown = countdown.between?(1, @mains_half_period) ? countdown : @mains_half_period
|
|
83
|
+
@mains_high = input.bool("mains_high", @mains_high)
|
|
84
|
+
@refresh = input.int("refresh", 0).clamp(0, REFRESH_COUNT)
|
|
85
|
+
pending = input.ints("host.pending_keys", [])
|
|
86
|
+
@pending_keys = pending.empty? ? nil : pending.dup
|
|
87
|
+
load_init_keys(input)
|
|
88
|
+
@init_handlers_lost = [input.int("host.init_pending", 0) - @init_handlers.length, 0].max
|
|
89
|
+
load_ieee_drive_fields(input)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
include KeyedState
|
|
94
|
+
end
|
|
95
|
+
end
|