badline 0.16.1 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +60 -0
- data/CLAUDE.md +1 -1
- data/README.md +102 -12
- data/doc/media.md +5 -2
- data/doc/pinned-behaviour.md +61 -0
- data/doc/snapshot-format.md +376 -69
- data/doc/snapshots.md +148 -14
- data/lib/badline/cbm2/acia.rb +10 -0
- data/lib/badline/cbm2/bus.rb +263 -0
- data/lib/badline/cbm2/cpu.rb +58 -0
- data/lib/badline/cbm2/crtc_video.rb +133 -0
- data/lib/badline/cbm2/ieee_drive.rb +18 -0
- data/lib/badline/cbm2/ieee_layout.rb +69 -0
- data/lib/badline/cbm2/ieee_ports.rb +104 -0
- data/lib/badline/cbm2/ieee_traps.rb +81 -0
- data/lib/badline/cbm2/keyboard_ports.rb +60 -0
- data/lib/badline/cbm2/keyed_modules.rb +67 -0
- data/lib/badline/cbm2/keyed_state.rb +91 -0
- data/lib/badline/cbm2/model.rb +44 -0
- data/lib/badline/cbm2/p500/control_ports.rb +80 -0
- data/lib/badline/cbm2/p500/ieee_layout.rb +41 -0
- data/lib/badline/cbm2/p500/keyed_state.rb +85 -0
- data/lib/badline/cbm2/p500/model.rb +39 -0
- data/lib/badline/cbm2/p500/pages.rb +57 -0
- data/lib/badline/cbm2/p500.rb +262 -0
- data/lib/badline/cbm2/sid_page.rb +19 -0
- data/lib/badline/cbm2.rb +267 -0
- data/lib/badline/{plus4 → chips}/acia/frames.rb +1 -1
- data/lib/badline/chips/acia/keyed_state.rb +47 -0
- data/lib/badline/chips/acia.rb +307 -0
- data/lib/badline/chips/crtc.rb +3 -2
- data/lib/badline/chips/riot/fast_forward.rb +72 -0
- data/lib/badline/chips/riot/keyed_parts.rb +69 -0
- data/lib/badline/chips/riot/timer.rb +80 -0
- data/lib/badline/chips/riot.rb +162 -0
- data/lib/badline/chips/rriot/keyed_state.rb +30 -0
- data/lib/badline/chips/rriot.rb +132 -0
- data/lib/badline/chips/tpi/keyed_state.rb +38 -0
- data/lib/badline/chips/tpi.rb +233 -0
- data/lib/badline/chips/vic/bank.rb +60 -5
- data/lib/badline/chips/vic/sprite.rb +4 -3
- data/lib/badline/drives/datasette/keyed_state.rb +9 -1
- data/lib/badline/drives/datasette.rb +27 -3
- data/lib/badline/drives/model8050/disk.rb +163 -0
- data/lib/badline/drives/model8050/fdc_bus.rb +102 -0
- data/lib/badline/drives/model8050/idle.rb +177 -0
- data/lib/badline/drives/model8050/ieee_port.rb +147 -0
- data/lib/badline/drives/model8050/ip_bus.rb +93 -0
- data/lib/badline/drives/model8050/keyed_state.rb +160 -0
- data/lib/badline/drives/model8050/mechanism.rb +230 -0
- data/lib/badline/drives/model8050/parts.rb +37 -0
- data/lib/badline/drives/model8050.rb +243 -0
- data/lib/badline/frontend/app.rb +10 -3
- data/lib/badline/frontend/boot.rb +1 -1
- data/lib/badline/frontend/controls.rb +51 -15
- data/lib/badline/frontend/gamepads.rb +3 -3
- data/lib/badline/frontend/keys.rb +61 -2
- data/lib/badline/frontend/media_slots.rb +1 -1
- data/lib/badline/frontend/menu/menu_media.rb +14 -11
- data/lib/badline/frontend/menu/menu_pages.rb +18 -4
- data/lib/badline/frontend/menu/menu_ports.rb +40 -4
- data/lib/badline/frontend/menu/pause_menu.rb +1 -1
- data/lib/badline/frontend/pet_keyboard.rb +23 -7
- data/lib/badline/frontend/timeline.rb +19 -10
- data/lib/badline/kernal_trap/dos/disk_commands.rb +80 -0
- data/lib/badline/kernal_trap/dos/status.rb +3 -0
- data/lib/badline/kernal_trap/dos.rb +18 -5
- data/lib/badline/kernal_trap/ieee.rb +4 -2
- data/lib/badline/kernal_trap/ieee_layout.rb +95 -43
- data/lib/badline/machine/ieee_drives.rb +194 -0
- data/lib/badline/machine/settling.rb +4 -0
- data/lib/badline/machine.rb +23 -6
- data/lib/badline/media/cbm2_media.rb +29 -0
- data/lib/badline/media/program.rb +6 -3
- data/lib/badline/media/true_drive.rb +38 -9
- data/lib/badline/media.rb +15 -6
- data/lib/badline/options/family.rb +15 -3
- data/lib/badline/options/help.rb +16 -2
- data/lib/badline/options/table.rb +18 -4
- data/lib/badline/options/validation.rb +27 -3
- data/lib/badline/options.rb +21 -7
- 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.rb +15 -4
- data/lib/badline/plus4/acia.rb +4 -298
- data/lib/badline/plus4/keyed_parts.rb +0 -44
- data/lib/badline/plus4.rb +1 -0
- data/lib/badline/roms/8050/dos-2.7.rom +0 -0
- data/lib/badline/roms/8050/fdc-901869-01.rom +0 -0
- data/lib/badline/roms/8050/fdc-901885-04.rom +0 -0
- data/lib/badline/roms/README +111 -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/snapshot/cbm2_setup.rb +53 -0
- data/lib/badline/snapshot/container.rb +7 -2
- data/lib/badline/snapshot/field_doc/markdown.rb +4 -2
- data/lib/badline/snapshot/field_doc.rb +38 -3
- data/lib/badline/snapshot/image.rb +62 -9
- 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 +7 -3
- data/lib/badline/snapshot/vice/pet_cpu.rb +5 -2
- data/lib/badline/snapshot/vice/pet_crtc.rb +10 -15
- data/lib/badline/snapshot/vice/pet_drive.rb +180 -0
- data/lib/badline/snapshot/vice/pet_drive8050.rb +216 -0
- data/lib/badline/snapshot/vice/pet_restore.rb +70 -6
- 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 +11 -3
- data/lib/badline/storage/crt_file.rb +7 -1
- data/lib/badline/storage/d64_image.rb +7 -0
- data/lib/badline/storage/d80_image.rb +17 -1
- data/lib/badline/storage/d81_image.rb +14 -0
- data/lib/badline/storage/disk_image/bam.rb +18 -3
- data/lib/badline/storage/disk_image/formatting.rb +69 -0
- data/lib/badline/storage/disk_image/writing.rb +35 -0
- data/lib/badline/storage/disk_image.rb +2 -0
- data/lib/badline/storage.rb +1 -0
- data/lib/badline/support/cbm2_checkpoint.rb +48 -0
- data/lib/badline/support/p500_checkpoint.rb +35 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline.rb +1 -0
- metadata +72 -2
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Snapshot
|
|
5
|
+
module Vice
|
|
6
|
+
module P500
|
|
7
|
+
# VIC-II 1.3 as xcbm5x0 writes it, which runs VICE's line-based
|
|
8
|
+
# VIC-II rather than x64sc's, so the module is laid out otherwise
|
|
9
|
+
# than x64sc's under the same name: whether bad lines may happen,
|
|
10
|
+
# whether this line is one, the vertical border, the colour
|
|
11
|
+
# buffer, colour RAM, whether the sequencer is idle, the light
|
|
12
|
+
# pen's trigger and position, the video matrix buffer, the sprites
|
|
13
|
+
# whose DMA starts next line, a word xcbm5x0 keeps at $00100000,
|
|
14
|
+
# the beam's cycle and line, the registers, the sprite collisions
|
|
15
|
+
# pending, the sprites' DMA, the VIC's bank, VC, the matrix's
|
|
16
|
+
# width, VCBASE, then sprite and drawing fields with the bank
|
|
17
|
+
# again, the line its picture draws next and its size, and the
|
|
18
|
+
# picture. xcbm5x0's cycle in the line is badline's column.
|
|
19
|
+
#
|
|
20
|
+
# Written: the registers, colour RAM, the buffers, the beam with
|
|
21
|
+
# its counters and the sprites' DMA as badline's VIC has them, the
|
|
22
|
+
# bank as TPI2's PC6 and PC7 pick it, and the rest as xcbm5x0
|
|
23
|
+
# leaves it. The picture is left blank, as xcbm5x0 paints it again.
|
|
24
|
+
#
|
|
25
|
+
# Read: the registers, colour RAM, the buffers, the beam with VC,
|
|
26
|
+
# VCBASE and whether the sequencer is idle, the light pen and the
|
|
27
|
+
# sprites' DMA, through the x64sc module's reader (VICII::Import).
|
|
28
|
+
# The row counter follows from the line and YSCROLL, and the
|
|
29
|
+
# sprites' counters and the borders' flip-flops start afresh.
|
|
30
|
+
# xcbm5x0's VIC isn't x64sc's, so a program badline runs on from its
|
|
31
|
+
# snapshot reads the raster a cycle or so off now and then.
|
|
32
|
+
module VICII
|
|
33
|
+
NAME = Vice::VICII::NAME
|
|
34
|
+
MAJOR = 1
|
|
35
|
+
MINOR = 3
|
|
36
|
+
ALLOW_BAD_LINES = 0x000
|
|
37
|
+
BAD_LINE = 0x001
|
|
38
|
+
BLANK = 0x002
|
|
39
|
+
COLOR_BUFFER = 0x003
|
|
40
|
+
COLOR_RAM = 0x02b
|
|
41
|
+
IDLE = 0x42b
|
|
42
|
+
LIGHT_PEN = 0x42c
|
|
43
|
+
MATRIX_BUFFER = 0x42f
|
|
44
|
+
RAM_BASE = 0x458
|
|
45
|
+
CYCLE = 0x45c
|
|
46
|
+
LINE = 0x45d
|
|
47
|
+
REGISTERS = 0x45f
|
|
48
|
+
SPRITE_DMA = 0x4a0
|
|
49
|
+
BANK = 0x4a2
|
|
50
|
+
VC = 0x4a4
|
|
51
|
+
VC_ADD = 0x4a6
|
|
52
|
+
VC_BASE = 0x4a7
|
|
53
|
+
VIDEO_INT = 0x4a9
|
|
54
|
+
DRAW_FLAGS = 0x4cd
|
|
55
|
+
FETCH_BANK = 0x4cf
|
|
56
|
+
DRAWN_LINE = 0x4d1
|
|
57
|
+
PICTURE = 0x4d5
|
|
58
|
+
HEADER = 0x4e1
|
|
59
|
+
# The picture's size: PAL's and NTSC's.
|
|
60
|
+
SIZES = { 312 => [504, 312], 263 => [520, 275] }.freeze
|
|
61
|
+
|
|
62
|
+
module_function
|
|
63
|
+
|
|
64
|
+
def reads?(section) = section.major == MAJOR && section.minor == MINOR
|
|
65
|
+
|
|
66
|
+
# The lines xcbm5x0's picture has, which name its region.
|
|
67
|
+
def picture_lines(section) = FieldReader.new(section).skip(PICTURE + 4).dword
|
|
68
|
+
|
|
69
|
+
def export(machine)
|
|
70
|
+
vic = machine.vic
|
|
71
|
+
width, height = SIZES.fetch(vic.region.lines_per_frame)
|
|
72
|
+
bytes = Array.new(HEADER + (width * (height + 4)), 0)
|
|
73
|
+
sequencer(vic, bytes)
|
|
74
|
+
bytes[COLOR_RAM, 0x400] = Array.new(0x400) { |offset| machine.color_ram.nibble(0xd400 + offset) }
|
|
75
|
+
beam(vic, bytes)
|
|
76
|
+
bank = ((machine.tpi2.port_c_output >> 6) & 0x03) << 14
|
|
77
|
+
[BANK, FETCH_BANK].each { |at| word(bytes, at, bank) }
|
|
78
|
+
[RAM_BASE + 2, DRAW_FLAGS].each { |at| bytes[at] = 0x10 }
|
|
79
|
+
bytes[VIDEO_INT] = 1
|
|
80
|
+
[vic.rasterline, width, height, width].each_with_index do |value, n|
|
|
81
|
+
dword(bytes, DRAWN_LINE + (4 * n), value)
|
|
82
|
+
end
|
|
83
|
+
FieldWriter.new.bytes(bytes).section(NAME, MAJOR, MINOR)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# The sequencer's state and the buffers it reads.
|
|
87
|
+
def sequencer(vic, bytes)
|
|
88
|
+
state = vic.display_state
|
|
89
|
+
bytes[ALLOW_BAD_LINES] = state.bad_lines_enabled ? 1 : 0
|
|
90
|
+
bytes[BAD_LINE] = state.bad_line_condition? ? 1 : 0
|
|
91
|
+
bytes[BLANK] = vic.sequencer.borders.first ? 1 : 0
|
|
92
|
+
bytes[COLOR_BUFFER, 40] = vic.color_buffer.map { |value| value || 0 }
|
|
93
|
+
bytes[IDLE] = state.display? ? 0 : 1
|
|
94
|
+
bytes[MATRIX_BUFFER, 40] = vic.character_buffer.map { |value| value || 0 }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# The beam, the registers and the counters.
|
|
98
|
+
def beam(vic, bytes)
|
|
99
|
+
state = vic.display_state
|
|
100
|
+
bytes[CYCLE] = vic.column
|
|
101
|
+
word(bytes, LINE, vic.rasterline)
|
|
102
|
+
bytes[REGISTERS, 0x40] = vic.register_file
|
|
103
|
+
bytes[SPRITE_DMA] = 8.times.sum { |n| vic.sprites[n].displaying? ? 1 << n : 0 }
|
|
104
|
+
word(bytes, VC, state.vc)
|
|
105
|
+
bytes[VC_ADD] = 40
|
|
106
|
+
word(bytes, VC_BASE, state.vc_base)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def word(bytes, at, value) = bytes[at, 2] = [value & 0xffff].pack("v").bytes
|
|
110
|
+
|
|
111
|
+
def dword(bytes, at, value) = bytes[at, 4] = [value].pack("V").bytes
|
|
112
|
+
|
|
113
|
+
def import(section, vic, color_ram)
|
|
114
|
+
Import.new(section, vic.region).apply_to(vic, color_ram, 0xd400)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Reads xcbm5x0's module into the fields x64sc's reader puts into
|
|
118
|
+
# badline's VIC.
|
|
119
|
+
class Import < Vice::VICII::Import
|
|
120
|
+
# x64sc's cycle is badline's column plus one, its cycle 0 the
|
|
121
|
+
# last column of the line before (VICII.vice_cycle).
|
|
122
|
+
def initialize(section, region)
|
|
123
|
+
super(nil)
|
|
124
|
+
bytes = section.data.bytes
|
|
125
|
+
@registers = bytes[REGISTERS, 0x40]
|
|
126
|
+
line = bytes[LINE] | (bytes[LINE + 1] << 8)
|
|
127
|
+
@cycle = (bytes[CYCLE] + 1) % region.cycles_per_line
|
|
128
|
+
@line = @cycle.zero? ? (line + 1) % region.lines_per_frame : line
|
|
129
|
+
@frame_start = false
|
|
130
|
+
@irq_status = @registers[0x19]
|
|
131
|
+
@raster_match = false
|
|
132
|
+
@character_buffer = bytes[MATRIX_BUFFER, 40]
|
|
133
|
+
@color_buffer = bytes[COLOR_BUFFER, 40]
|
|
134
|
+
read_old_counters(bytes)
|
|
135
|
+
@color_ram = bytes[COLOR_RAM, 0x400]
|
|
136
|
+
@sprite_mc = Array.new(8, 0)
|
|
137
|
+
@sprite_mcbase = Array.new(8, 0)
|
|
138
|
+
@sprite_exp_ff = 0
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
private
|
|
142
|
+
|
|
143
|
+
def read_old_counters(bytes)
|
|
144
|
+
@bad_lines_enabled = !bytes[ALLOW_BAD_LINES].zero?
|
|
145
|
+
@collisions = [@registers[0x1e], @registers[0x1f]]
|
|
146
|
+
@display = bytes[IDLE].zero?
|
|
147
|
+
@vc_base = bytes[VC_BASE] | (bytes[VC_BASE + 1] << 8)
|
|
148
|
+
@vc = bytes[VC] | (bytes[VC + 1] << 8)
|
|
149
|
+
@rc = @display ? (@line - @registers[0x11]) & 0x07 : 7
|
|
150
|
+
@vmli = 0
|
|
151
|
+
@lp_low = false
|
|
152
|
+
@lp_triggered = !bytes[LIGHT_PEN].zero?
|
|
153
|
+
@fetch_d011 = @registers[0x11]
|
|
154
|
+
@dma_bits = bytes[SPRITE_DMA]
|
|
155
|
+
@display_bits = @dma_bits
|
|
156
|
+
blank = !bytes[BLANK].zero?
|
|
157
|
+
@vertical_border = blank
|
|
158
|
+
@vertical_armed = false
|
|
159
|
+
@main_border = blank
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -5,13 +5,16 @@ require "badline/snapshot/vice/pet_mem"
|
|
|
5
5
|
require "badline/snapshot/vice/pet_pias"
|
|
6
6
|
require "badline/snapshot/vice/pet_via"
|
|
7
7
|
require "badline/snapshot/vice/pet_crtc"
|
|
8
|
+
require "badline/snapshot/vice/pet_drive"
|
|
9
|
+
require "badline/snapshot/vice/pet_drive8050"
|
|
8
10
|
require "badline/snapshot/vice/pet_restore"
|
|
9
11
|
|
|
10
12
|
module Badline
|
|
11
13
|
module Snapshot
|
|
12
14
|
module Vice
|
|
13
|
-
# xpet's modules for the PET: the CPU, the RAM, the CRTC, both PIAs
|
|
14
|
-
# and
|
|
15
|
+
# xpet's modules for the PET: the CPU, the RAM, the CRTC, both PIAs,
|
|
16
|
+
# the VIA and a true 2031, written from badline's machine and read
|
|
17
|
+
# into it. Each
|
|
15
18
|
# module's file says what maps and what is approximated. They go
|
|
16
19
|
# through the PET's own keyed fields (Keyed), which a save writes and a
|
|
17
20
|
# load reads, so they read no chip's insides.
|
|
@@ -45,7 +48,8 @@ module Badline
|
|
|
45
48
|
[MainCPU.export(machine), CPU6809.export(machine), PETMem.export(machine),
|
|
46
49
|
CRTC.export(machine, reader(readers, "BL-CRTC"), reader(readers, "BL-VIDEO")),
|
|
47
50
|
PIAs.export("PIA1", machine.pia1, reader(readers, "BL-PIA1")),
|
|
48
|
-
PIAs.export("PIA2", machine.pia2, reader(readers, "BL-PIA2")), VIA.export(reader(readers, "BL-VIA"))
|
|
51
|
+
PIAs.export("PIA2", machine.pia2, reader(readers, "BL-PIA2")), VIA.export(reader(readers, "BL-VIA")),
|
|
52
|
+
*Drive2031.export(machine, readers), *Drive8050.export(machine, readers)] +
|
|
49
53
|
PERIPHERALS.map { |name, major, minor, bytes| FieldWriter.new.bytes(bytes).section(name, major, minor) }
|
|
50
54
|
end
|
|
51
55
|
|
|
@@ -25,14 +25,17 @@ module Badline
|
|
|
25
25
|
|
|
26
26
|
def reads?(section) = section.major == MAJOR && section.minor == MINOR
|
|
27
27
|
|
|
28
|
-
def export(machine)
|
|
28
|
+
def export(machine) = write(machine, [machine.pia1.irq?, machine.pia2.irq?, machine.via.irq?].count(true))
|
|
29
|
+
|
|
30
|
+
# The module for `machine`, whose CPU's IRQ line `irqs` sources
|
|
31
|
+
# pull.
|
|
32
|
+
def write(machine, irqs)
|
|
29
33
|
cpu = machine.cpu
|
|
30
34
|
fields = FieldWriter.new.qword(machine.cycles)
|
|
31
35
|
[cpu.a, cpu.x, cpu.y, cpu.stack_pointer].each { |register| fields.byte(register) }
|
|
32
36
|
fields.word(cpu.program_counter).byte(cpu.p)
|
|
33
37
|
fields.dword(0).dword(0).dword(0)
|
|
34
38
|
5.times { fields.qword(0) }
|
|
35
|
-
irqs = [machine.pia1.irq?, machine.pia2.irq?, machine.via.irq?].count(true)
|
|
36
39
|
fields.dword(irqs).dword(0).dword(irqs.positive? ? IK_IRQ : 0)
|
|
37
40
|
fields.section(NAME, MAJOR, MINOR)
|
|
38
41
|
end
|
|
@@ -82,7 +82,8 @@ module Badline
|
|
|
82
82
|
beam = if discrete
|
|
83
83
|
discrete_beam(video_input)
|
|
84
84
|
else
|
|
85
|
-
crtc_beam(run_on(machine, crtc_input, -CRTC_LEAD % frame_cycles(registers)),
|
|
85
|
+
crtc_beam(run_on(fresh_crtc(machine), crtc_input, -CRTC_LEAD % frame_cycles(registers)),
|
|
86
|
+
registers)
|
|
86
87
|
end
|
|
87
88
|
fields = FieldWriter.new.bytes(model.cbm8296? ? SETTINGS_8296 : SETTINGS).word(attributes(model))
|
|
88
89
|
fields.byte(0).byte(columns(model))
|
|
@@ -119,10 +120,12 @@ module Badline
|
|
|
119
120
|
|
|
120
121
|
def frame_cycles(registers) = (registers[0] + 1) * frame_lines(registers)
|
|
121
122
|
|
|
122
|
-
# The
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
# The CRTC of a machine built as `machine` was, at power-on.
|
|
124
|
+
def fresh_crtc(machine) = PETSetup.of(machine).build.crtc
|
|
125
|
+
|
|
126
|
+
# The BL-CRTC fields of `crtc`, a CRTC of its own, loaded with those
|
|
127
|
+
# in `input` and run `cycles` cycles.
|
|
128
|
+
def run_on(crtc, input, cycles)
|
|
126
129
|
crtc.load_fields(input)
|
|
127
130
|
cycles.times { crtc.cycle! }
|
|
128
131
|
Keyed::Reader.parse(Keyed.section("BL-CRTC") { |out| crtc.save_fields(out) })
|
|
@@ -135,16 +138,13 @@ module Badline
|
|
|
135
138
|
%w[adjusting row_display column_display].each { |key| out.bool(key, input.bool(key, false)) }
|
|
136
139
|
end
|
|
137
140
|
|
|
138
|
-
# Both move a row's start on at R1 of its last line
|
|
139
|
-
# moves it again where the vertical adjust runs past R9 lines,
|
|
140
|
-
# which xpet's doesn't.
|
|
141
|
+
# Both move a row's start on at R1 of its last line.
|
|
141
142
|
def crtc_beam(input, registers)
|
|
142
143
|
column = input.int("column", 0)
|
|
143
144
|
row = input.int("row", 0)
|
|
144
145
|
line = input.int("line", 0)
|
|
145
146
|
adjusting = input.bool("adjusting", false)
|
|
146
147
|
start = input.int("row_start", 0)
|
|
147
|
-
start = (start - registers[1]) & 0x3fff if adjusting && moved?(line, column, registers)
|
|
148
148
|
rows = registers[4] + 1
|
|
149
149
|
lines = registers[9] + 1
|
|
150
150
|
Beam.new(column:, row: adjusting ? rows : row, raster: adjusting ? 0 : line,
|
|
@@ -212,12 +212,7 @@ module Badline
|
|
|
212
212
|
adjusting = beam.row >= rows
|
|
213
213
|
line = adjusting ? beam.frame_line - (rows * (registers[9] + 1)) : beam.raster
|
|
214
214
|
start = start_address(beam.start, registers, columns, attributes)
|
|
215
|
-
if adjusting
|
|
216
|
-
start += registers[1] if line > registers[9]
|
|
217
|
-
elsif moved?(line, column_of(section), registers)
|
|
218
|
-
start -= registers[1]
|
|
219
|
-
end
|
|
220
|
-
start &= 0x3fff
|
|
215
|
+
start = (start - registers[1]) & 0x3fff if !adjusting && moved?(line, column_of(section), registers)
|
|
221
216
|
out.bytes("registers", registers.first(Chips::CRTC::MASKS.length)).int("selected", selected(section) & 0x1f)
|
|
222
217
|
out.int("column", 0).int("line", [line, 0].max).int("row", adjusting ? rows - 1 : beam.row)
|
|
223
218
|
out.int("since_hsync", registers[0] + 1 - registers[2]).int("vsync_lines", beam.vsync)
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Snapshot
|
|
5
|
+
module Vice
|
|
6
|
+
module PET
|
|
7
|
+
# A true 2031 on device 8, as xpet 3.10, xcbm2 and xcbm5x0 save it with true drive
|
|
8
|
+
# emulation: DRIVE8 to DRIVE11 2.0, one a unit, DRIVECPU0 1.3, the
|
|
9
|
+
# drive's 6502 and its RAM, and its VIAs as VIA 2.2 modules,
|
|
10
|
+
# 2031VIA1D0 at $1800 on the IEEE-488 bus and VIA2D0 at $1C00 on
|
|
11
|
+
# the mechanism.
|
|
12
|
+
#
|
|
13
|
+
# DRIVECPU0 holds the clock, the registers, the last opcode, the
|
|
14
|
+
# main CPU's clock, more clocks of the drive's interrupt bookkeeping,
|
|
15
|
+
# then the 2K of RAM from $5C and twelve bytes after it. DRIVE8
|
|
16
|
+
# holds the head's half track at $10, with track 18 at 36 as badline
|
|
17
|
+
# numbers them, the speed zone its VIA set at $24, $47 and $4B, the
|
|
18
|
+
# drive's type as a word at $28, and from $2C the disk's place in
|
|
19
|
+
# its turn and the clock it was last moved on at. The rest of it,
|
|
20
|
+
# the read electronics among it, badline doesn't read. xpet's head
|
|
21
|
+
# rests on a half track two phases on from where badline's stepper
|
|
22
|
+
# puts it (SLIP).
|
|
23
|
+
#
|
|
24
|
+
# Written: the registers and the RAM, the VIAs as the PET's VIA is
|
|
25
|
+
# written with the stepper's phase moved to xpet's, and DRIVE8 as
|
|
26
|
+
# xpet saves an idle 2031 but for the head's half track, the zone,
|
|
27
|
+
# and the disk at the start of its turn as of the snapshot's clock.
|
|
28
|
+
# The interrupt clocks are written as long past. The snapshot is
|
|
29
|
+
# settled with the drive's CPU between instructions too
|
|
30
|
+
# (PET#settle_for_vice!). One taken while the drive is mid-transfer
|
|
31
|
+
# on the bus may stall there in xpet, as xpet's own snapshots taken
|
|
32
|
+
# while it searches for a file do.
|
|
33
|
+
#
|
|
34
|
+
# Read: the registers, the RAM, the VIAs as the PET's VIA is read,
|
|
35
|
+
# and the head's half track, with the motor, the LED and the zone
|
|
36
|
+
# from VIA 2's port B and the stepper's phase as xpet's (SLIP). The
|
|
37
|
+
# disk turns on from where badline's left it, and the disk in the
|
|
38
|
+
# drive stays.
|
|
39
|
+
module Drive2031
|
|
40
|
+
UNITS = %w[DRIVE8 DRIVE9 DRIVE10 DRIVE11].freeze
|
|
41
|
+
DRIVE_MAJOR = 2
|
|
42
|
+
DRIVE_MINOR = 0
|
|
43
|
+
CPU = "DRIVECPU0"
|
|
44
|
+
CPU_MAJOR = 1
|
|
45
|
+
CPU_MINOR = 3
|
|
46
|
+
VIA1 = "2031VIA1D0"
|
|
47
|
+
VIA2 = "VIA2D0"
|
|
48
|
+
NAMES = [*UNITS, CPU, VIA1, VIA2].freeze
|
|
49
|
+
# Where DRIVECPU0's RAM starts, and its length.
|
|
50
|
+
RAM_AT = 0x5c
|
|
51
|
+
RAM_SIZE = 0x800
|
|
52
|
+
TAIL = 12
|
|
53
|
+
HALF_TRACK = 0x10
|
|
54
|
+
ZONES = [0x24, 0x47, 0x4b].freeze
|
|
55
|
+
SIZE = 145
|
|
56
|
+
# The disk's place in its turn, and the clock it was last moved on at.
|
|
57
|
+
TURN = 0x2c
|
|
58
|
+
# DRIVE8 as xpet saves an idle 2031, and a unit with no drive, by
|
|
59
|
+
# the bytes that aren't 0.
|
|
60
|
+
IDLE = { 0x00 => 0x01, 0x01 => 0x01, 0x02 => 0x01, 0x0f => 0x01, 0x10 => 0x25, 0x1a => 0x01, 0x1b => 0xdf,
|
|
61
|
+
0x1f => 0x54, 0x20 => 0x55, 0x24 => 0x03, 0x28 => 0xef, 0x29 => 0x07, 0x2c => 0x60, 0x2d => 0x41,
|
|
62
|
+
0x2e => 0x0f, 0x30 => 0x17, 0x31 => 0x51, 0x32 => 0x0f, 0x40 => 0x54, 0x41 => 0x01, 0x42 => 0x54,
|
|
63
|
+
0x47 => 0x03, 0x4b => 0x03, 0x6f => 0xcd, 0x70 => 0xab, 0x71 => 0x34, 0x72 => 0x12 }.freeze
|
|
64
|
+
NONE = { 0x00 => 0x01, 0x01 => 0x01, 0x02 => 0x01, 0x0e => 0x01, 0x0f => 0x01, 0x10 => 0x24, 0x1a => 0x01,
|
|
65
|
+
0x20 => 0x55, 0x6f => 0xcd, 0x70 => 0xab, 0x71 => 0x34, 0x72 => 0x12, 0x8f => 0x01 }.freeze
|
|
66
|
+
SLIP = 2
|
|
67
|
+
MOTOR = 0x04
|
|
68
|
+
LED = 0x08
|
|
69
|
+
|
|
70
|
+
module_function
|
|
71
|
+
|
|
72
|
+
# Whether the snapshot has a true 2031.
|
|
73
|
+
def present?(container) = !container[CPU].nil? && !container[VIA1].nil?
|
|
74
|
+
|
|
75
|
+
def reads?(section)
|
|
76
|
+
case section.name
|
|
77
|
+
when CPU then section.major == CPU_MAJOR && section.minor == CPU_MINOR
|
|
78
|
+
when VIA1, VIA2 then VIA.reads?(section)
|
|
79
|
+
else section.major == DRIVE_MAJOR && section.minor == DRIVE_MINOR
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# xpet's modules for the 2031 whose BL-D8 modules `readers` holds,
|
|
84
|
+
# and none without one or for an 8050 or 8250, which they leave
|
|
85
|
+
# out.
|
|
86
|
+
def export(machine, readers)
|
|
87
|
+
drive = readers["BL-D8"]
|
|
88
|
+
return [] if drive.nil? || drive.text("model", "2031") != "2031"
|
|
89
|
+
|
|
90
|
+
via2 = readers.fetch("BL-D8-VIA2")
|
|
91
|
+
mechanism = readers.fetch("BL-D8-MECH")
|
|
92
|
+
zone = ((via2.int("orb", 0) | ~via2.int("ddrb", 0)) >> 5) & 0x03
|
|
93
|
+
idle = filled(IDLE)
|
|
94
|
+
idle[HALF_TRACK] = mechanism.int("half_track", 36)
|
|
95
|
+
ZONES.each { |offset| idle[offset] = zone }
|
|
96
|
+
idle[TURN, 8] = [0, machine.cycles & 0xffff_ffff].pack("VV").bytes
|
|
97
|
+
[FieldWriter.new.bytes(idle).section(UNITS.first, DRIVE_MAJOR, DRIVE_MINOR),
|
|
98
|
+
*UNITS.drop(1).map { |name| unit(NONE, name) },
|
|
99
|
+
cpu(machine, readers.fetch("BL-D8-CPU"), readers.fetch("BL-D8-RAM")),
|
|
100
|
+
stepper(VIA.export(via2, VIA2), mechanism.int("slip", 0)),
|
|
101
|
+
VIA.export(readers.fetch("BL-D8-VIA1"), VIA1)]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# VIA 2's port B with the stepper's phase as xpet's head takes it
|
|
105
|
+
# where badline's head is: SLIP on from badline's.
|
|
106
|
+
def stepper(section, slip)
|
|
107
|
+
bytes = section.data.bytes
|
|
108
|
+
bytes[2] = (bytes[2] & 0xfc) | ((bytes[2] - slip + SLIP) & 0x03)
|
|
109
|
+
section.with(data: bytes.pack("C*"))
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def unit(bytes, name) = FieldWriter.new.bytes(filled(bytes)).section(name, DRIVE_MAJOR, DRIVE_MINOR)
|
|
113
|
+
|
|
114
|
+
def filled(bytes) = Array.new(SIZE) { |offset| bytes.fetch(offset, 0) }
|
|
115
|
+
|
|
116
|
+
def cpu(machine, input, ram)
|
|
117
|
+
bytes = ram.bytes("ram", "".b).bytes
|
|
118
|
+
cpu_section(machine, input, Array.new(RAM_SIZE) { |address| bytes[address] || 0 })
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# The drive's clock, at its 1 MHz, for the machine's at its own.
|
|
122
|
+
def drive_clock(machine) = machine.cycles * Drives::Model2031::CLOCK_HZ / machine.timing.clock_hz
|
|
123
|
+
|
|
124
|
+
# DRIVECPU0 for a drive CPU whose BL- fields `input` holds, with
|
|
125
|
+
# `ram` the drive's RAM as xpet lays it out: the drive's clock,
|
|
126
|
+
# the registers, the machine's clock, the drive's clock again, and
|
|
127
|
+
# the RAM.
|
|
128
|
+
def cpu_section(machine, input, ram)
|
|
129
|
+
clock = drive_clock(machine)
|
|
130
|
+
fields = FieldWriter.new.qword(clock)
|
|
131
|
+
%w[a x y sp].each { |key| fields.byte(input.int(key, 0)) }
|
|
132
|
+
fields.word(input.int("pc", 0)).byte(input.int("p", 0x20)).dword(0)
|
|
133
|
+
fields.qword(machine.cycles).zeros(16).qword(clock).zeros(1).qword(0).zeros(8).qword(0)
|
|
134
|
+
fields.zeros(16).bytes(ram).zeros(TAIL)
|
|
135
|
+
fields.section(CPU, CPU_MAJOR, CPU_MINOR)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# The 2031's keyed modules, named for device 8, from the
|
|
139
|
+
# snapshot's, with `disk` the drive's disk module badline keeps,
|
|
140
|
+
# or nil.
|
|
141
|
+
def import(container, disk)
|
|
142
|
+
via2 = container[VIA2]
|
|
143
|
+
sections = [Keyed.section("BL-D8") { |out| drive(container, out) },
|
|
144
|
+
Keyed.section("BL-D8-RAM") { |out| out.bytes("ram", ram(container[CPU])) },
|
|
145
|
+
Keyed.section("BL-D8-VIA1") { |out| VIA.import(container[VIA1], out) },
|
|
146
|
+
Keyed.section("BL-D8-VIA2") { |out| VIA.import(via2, out) },
|
|
147
|
+
Keyed.section("BL-D8-CPU") { |out| registers(container[CPU], out) },
|
|
148
|
+
Keyed.section("BL-D8-MECH") { |out| mechanism(container, via2, out) }]
|
|
149
|
+
disk ? sections + [disk] : sections
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def drive(container, out)
|
|
153
|
+
out.text("model", "2031").int("phase", 0).int("cycles", FieldReader.new(container[CPU]).qword)
|
|
154
|
+
out.bool("so_pending", false)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def ram(section) = FieldReader.new(section).skip(RAM_AT).string(RAM_SIZE)
|
|
158
|
+
|
|
159
|
+
def registers(section, out)
|
|
160
|
+
fields = FieldReader.new(section)
|
|
161
|
+
clock = fields.qword
|
|
162
|
+
out.int("a", fields.byte).int("x", fields.byte).int("y", fields.byte).int("sp", fields.byte)
|
|
163
|
+
out.int("pc", fields.word).int("p", fields.byte).int("cycles", clock)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# The head where DRIVE8 has it, and the stepper's phase as VIA 2
|
|
167
|
+
# drives it, which the half track's low two bits stand for.
|
|
168
|
+
def mechanism(container, via2, out)
|
|
169
|
+
half_track = FieldReader.new(container[UNITS.first]).skip(HALF_TRACK).byte
|
|
170
|
+
bytes = FieldReader.new(via2).bytes(4)
|
|
171
|
+
port_b = bytes[2] | (~bytes[3] & 0xff)
|
|
172
|
+
out.int("half_track", half_track).int("slip", SLIP)
|
|
173
|
+
out.int("zone", (port_b >> 5) & 0x03)
|
|
174
|
+
out.bool("motor", port_b.anybits?(MOTOR)).bool("led", port_b.anybits?(LED))
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Snapshot
|
|
5
|
+
module Vice
|
|
6
|
+
module PET
|
|
7
|
+
# A true 8050 or 8250 on device 8, as xpet 3.10, xcbm2 and xcbm5x0 save it with true
|
|
8
|
+
# drive emulation: DRIVE8 2.0 with both drives, DRIVE9 to DRIVE11
|
|
9
|
+
# with none, DRIVECPU0 1.3, the interface processor and its RAM,
|
|
10
|
+
# RIOT1D0 and RIOT2D0 0.0, its two 6532s, and FDC0 0.0. xpet runs
|
|
11
|
+
# the floppy controller at a high level, so its 6504, VIA and 6530
|
|
12
|
+
# aren't in the snapshot: FDC0 holds the controller's state, its
|
|
13
|
+
# last track in byte 6.
|
|
14
|
+
#
|
|
15
|
+
# DRIVE8 holds a block of 139 bytes for each drive, then 6 for the
|
|
16
|
+
# unit: each block has its head's half track, twice the track, at
|
|
17
|
+
# $10 and the drive's type as a word at $28 (8050 or 8250).
|
|
18
|
+
# DRIVECPU0 is laid out as the 2031's (Drive2031), its RAM the IP's
|
|
19
|
+
# page 0, the 6532s' 128 bytes each, then the 4K of common RAM.
|
|
20
|
+
# A RIOT module holds port A's output and direction registers, port
|
|
21
|
+
# B's, the edge control (bit 0 the PA7 interrupt, bit 1 a rising
|
|
22
|
+
# edge), the flags (bit 7 the timer's interrupt, which xpet raises
|
|
23
|
+
# IRQ for, bit 6 PA7's), the timer's
|
|
24
|
+
# counter, its divider as a word, two bytes xpet leaves 0, and
|
|
25
|
+
# whether the timer interrupts.
|
|
26
|
+
#
|
|
27
|
+
# Written, once the controller is in its idle loop between
|
|
28
|
+
# instructions (PET#settle_for_vice!): the IP's registers and RAM,
|
|
29
|
+
# the 6532s, the heads' tracks, and FDC0 as xpet leaves an idle
|
|
30
|
+
# controller with the track badline's controller last stepped to.
|
|
31
|
+
# A unit whose controller is still busy is left out of xpet's
|
|
32
|
+
# modules. Read: the IP's registers and RAM and the 6532s, with the
|
|
33
|
+
# controller as a unit switched on has it in its idle loop, its
|
|
34
|
+
# heads on track 1, from where its next job steps them. A job under
|
|
35
|
+
# way in xpet's controller starts again from there. The disks in
|
|
36
|
+
# the drives stay.
|
|
37
|
+
module Drive8050
|
|
38
|
+
UNIT = "DRIVE8"
|
|
39
|
+
CPU = Drive2031::CPU
|
|
40
|
+
RIOTS = %w[RIOT1D0 RIOT2D0].freeze
|
|
41
|
+
FDC = "FDC0"
|
|
42
|
+
NAMES = [UNIT, CPU, *RIOTS, FDC].freeze
|
|
43
|
+
TYPES = { 0x1f72 => "8050", 0x203a => "8250" }.freeze
|
|
44
|
+
# Where each drive's block starts in DRIVE8, and its fields.
|
|
45
|
+
DRIVES = [0x00, 0x8b].freeze
|
|
46
|
+
HALF_TRACK = 0x10
|
|
47
|
+
TYPE = 0x28
|
|
48
|
+
SIZE = 284
|
|
49
|
+
# DRIVE8 as xpet saves an idle 8050, by the bytes that aren't 0,
|
|
50
|
+
# but for the type.
|
|
51
|
+
IDLE = { 0x00 => 0x01, 0x01 => 0x02, 0x02 => 0x01, 0x0e => 0x01, 0x0f => 0x01, 0x10 => 0x4c, 0x1a => 0x01,
|
|
52
|
+
0x20 => 0x55, 0x30 => 0x06, 0x6f => 0xcd, 0x70 => 0xab, 0x71 => 0x34, 0x72 => 0x12, 0x99 => 0x01,
|
|
53
|
+
0x9a => 0x01, 0x9b => 0x24, 0xa5 => 0x01, 0xab => 0x55, 0x11a => 0x01 }.freeze
|
|
54
|
+
ZERO_PAGE = 0x80
|
|
55
|
+
COMMON = 0x1000
|
|
56
|
+
# FDC0 as xpet leaves an idle controller, and where the track goes.
|
|
57
|
+
FDC_IDLE = [0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00].freeze
|
|
58
|
+
FDC_TRACK = 6
|
|
59
|
+
# Where badline's controller keeps drive 0's track, in the 6530's
|
|
60
|
+
# RAM.
|
|
61
|
+
TRACK_AT = 0x21
|
|
62
|
+
DIVIDERS = [1, 8, 64, 1024].freeze
|
|
63
|
+
IRQ_EDGE = 0x01
|
|
64
|
+
RISING = 0x02
|
|
65
|
+
TIMER_FLAG = 0x80
|
|
66
|
+
PA7_FLAG = 0x40
|
|
67
|
+
|
|
68
|
+
module_function
|
|
69
|
+
|
|
70
|
+
# Whether the snapshot has a true 8050 or 8250.
|
|
71
|
+
def present?(container) = !container[FDC].nil? && !container[CPU].nil?
|
|
72
|
+
|
|
73
|
+
def reads?(section)
|
|
74
|
+
case section.name
|
|
75
|
+
when CPU then Drive2031.reads?(section)
|
|
76
|
+
when UNIT then section.major == Drive2031::DRIVE_MAJOR && section.minor == Drive2031::DRIVE_MINOR
|
|
77
|
+
else section.major.zero? && section.minor.zero?
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# The model DRIVE8 names, or nil.
|
|
82
|
+
def model(container)
|
|
83
|
+
unit = container[UNIT]
|
|
84
|
+
return nil if unit.nil? || unit.data.bytesize < SIZE
|
|
85
|
+
|
|
86
|
+
TYPES[FieldReader.new(unit).skip(TYPE).word]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# xpet's modules for the 8050 or 8250 whose BL-D8 modules `readers`
|
|
90
|
+
# holds, and none without one, or with its controller away from
|
|
91
|
+
# its idle loop.
|
|
92
|
+
def export(machine, readers)
|
|
93
|
+
drive = readers["BL-D8"]
|
|
94
|
+
model = drive&.text("model", "")
|
|
95
|
+
return [] unless TYPES.value?(model) && idle?(readers)
|
|
96
|
+
|
|
97
|
+
[unit(model, readers.fetch("BL-D8-MECH")), *Drive2031::UNITS.drop(1).map { |name| none(name) },
|
|
98
|
+
Drive2031.cpu_section(machine, readers.fetch("BL-D8-IPCPU"), ram(readers)),
|
|
99
|
+
riot(RIOTS[0], readers.fetch("BL-D8-RIOT1")), riot(RIOTS[1], readers.fetch("BL-D8-RIOT2")),
|
|
100
|
+
fdc(readers.fetch("BL-D8-RRIOT"))]
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Whether both processors are between instructions, the
|
|
104
|
+
# controller at the start of its idle loop.
|
|
105
|
+
def idle?(readers)
|
|
106
|
+
fdc = readers.fetch("BL-D8-FDCCPU")
|
|
107
|
+
!position?(fdc) && !position?(readers.fetch("BL-D8-IPCPU")) &&
|
|
108
|
+
fdc.int("pc", 0) == Drives::Model8050::Idle::FDC_LOOP
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def position?(cpu) = !cpu.text("pos.seq", "").empty?
|
|
112
|
+
|
|
113
|
+
def unit(model, mechanism)
|
|
114
|
+
bytes = Array.new(SIZE) { |offset| IDLE.fetch(offset, 0) }
|
|
115
|
+
cylinders = mechanism.ints("cylinders", [0, 0])
|
|
116
|
+
DRIVES.each_with_index do |at, drive|
|
|
117
|
+
bytes[at + HALF_TRACK] = ((cylinders[drive] || 0) + 1) * 2
|
|
118
|
+
bytes[at + TYPE, 2] = [TYPES.key(model)].pack("v").bytes
|
|
119
|
+
end
|
|
120
|
+
FieldWriter.new.bytes(bytes).section(UNIT, Drive2031::DRIVE_MAJOR, Drive2031::DRIVE_MINOR)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def none(name) = Drive2031.unit(Drive2031::NONE, name)
|
|
124
|
+
|
|
125
|
+
# The IP's RAM as xpet lays it out: page 0 from both 6532s, then
|
|
126
|
+
# the common RAM.
|
|
127
|
+
def ram(readers)
|
|
128
|
+
[readers.fetch("BL-D8-RIOT1"), readers.fetch("BL-D8-RIOT2")].flat_map do |riot|
|
|
129
|
+
filled(riot.bytes("ram", "".b).bytes, ZERO_PAGE)
|
|
130
|
+
end + filled(readers.fetch("BL-D8-COMMON").bytes("ram", "".b).bytes, COMMON)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def filled(bytes, length) = Array.new(length) { |index| bytes[index] || 0 }
|
|
134
|
+
|
|
135
|
+
def riot(name, input)
|
|
136
|
+
edge = input.bool("edge_irq_enabled", false) ? IRQ_EDGE : 0
|
|
137
|
+
edge |= RISING if input.bool("rising_edge", false)
|
|
138
|
+
timer = input.bool("timer.flag", false) && input.bool("timer.irq_enabled", false)
|
|
139
|
+
flags = (timer ? TIMER_FLAG : 0) | (input.bool("pa7_flag", false) ? PA7_FLAG : 0)
|
|
140
|
+
divider = DIVIDERS.fetch(input.int("timer.interval", 0) & 0x03)
|
|
141
|
+
fields = FieldWriter.new.byte(input.int("ora", 0)).byte(input.int("ddra", 0)).byte(input.int("orb", 0))
|
|
142
|
+
fields.byte(input.int("ddrb", 0)).byte(edge).byte(flags).byte(input.int("timer.counter", 0)).word(divider)
|
|
143
|
+
fields.zeros(2).flag(input.bool("timer.irq_enabled", false)).section(name, 0, 0)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def fdc(rriot)
|
|
147
|
+
bytes = FDC_IDLE.dup
|
|
148
|
+
bytes[FDC_TRACK] = rriot.bytes("ram", "".b).getbyte(TRACK_AT).to_i
|
|
149
|
+
FieldWriter.new.bytes(bytes).section(FDC, 0, 0)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# The unit's keyed modules, named for device 8, from the
|
|
153
|
+
# snapshot's: the IP's side from xpet's, the controller's side
|
|
154
|
+
# from `reference`, a unit's in its idle loop, and `disks`, the
|
|
155
|
+
# drives' disk modules badline keeps.
|
|
156
|
+
def import(container, reference, disks)
|
|
157
|
+
cpu = container[CPU]
|
|
158
|
+
page = FieldReader.new(cpu).skip(Drive2031::RAM_AT).string(ZERO_PAGE * 2)
|
|
159
|
+
common = FieldReader.new(cpu).skip(Drive2031::RAM_AT + (ZERO_PAGE * 2)).string(COMMON)
|
|
160
|
+
sections = [Keyed.section("BL-D8") { |out| unit_fields(container, reference, out) },
|
|
161
|
+
Keyed.section("BL-D8-COMMON") { |out| out.bytes("ram", common) },
|
|
162
|
+
riot_section("BL-D8-RIOT1", container[RIOTS[0]], page.byteslice(0, ZERO_PAGE)),
|
|
163
|
+
riot_section("BL-D8-RIOT2", container[RIOTS[1]], page.byteslice(ZERO_PAGE, ZERO_PAGE)),
|
|
164
|
+
Keyed.section("BL-D8-IPCPU") { |out| Drive2031.registers(cpu, out) }]
|
|
165
|
+
sections + reference.select { |section| CONTROLLER.include?(section.name) } + disks
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# The controller's side, which comes from a unit in its idle loop.
|
|
169
|
+
CONTROLLER = %w[BL-D8-FDCCPU BL-D8-VIA BL-D8-RRIOT BL-D8-MECH].freeze
|
|
170
|
+
|
|
171
|
+
def unit_fields(container, reference, out)
|
|
172
|
+
own = Keyed::Reader.parse(reference.find { |section| section.name == "BL-D8" })
|
|
173
|
+
out.text("model", own.text("model", "8050")).int("phase", 0)
|
|
174
|
+
out.int("cycles", FieldReader.new(container[CPU]).qword).int("fdc_data", own.int("fdc_data", 0))
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def riot_section(name, section, ram)
|
|
178
|
+
fields = FieldReader.new(section)
|
|
179
|
+
ora, ddra, orb, ddrb, edge, flags, counter = fields.bytes(7)
|
|
180
|
+
divider = fields.word
|
|
181
|
+
fields.skip(2)
|
|
182
|
+
irq = fields.flag?
|
|
183
|
+
Keyed.section(name) do |out|
|
|
184
|
+
out.bytes("ram", ram).int("ora", ora).int("ddra", ddra).int("orb", orb).int("ddrb", ddrb)
|
|
185
|
+
out.bool("edge_irq_enabled", edge.anybits?(IRQ_EDGE)).bool("rising_edge", edge.anybits?(RISING))
|
|
186
|
+
out.bool("pa7_flag", flags.anybits?(PA7_FLAG)).int("timer.counter", counter)
|
|
187
|
+
out.int("timer.interval", DIVIDERS.index(divider) || 0).int("timer.prescale", [divider, 1].max)
|
|
188
|
+
out.bool("timer.flag", flags.anybits?(TIMER_FLAG)).bool("timer.expired", flags.anybits?(TIMER_FLAG))
|
|
189
|
+
out.bool("timer.irq_enabled", irq)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# A unit of `model` switched on and run into its controller's idle
|
|
194
|
+
# loop, both processors between instructions, as keyed modules
|
|
195
|
+
# named for device 8, run once a model.
|
|
196
|
+
def reference(model)
|
|
197
|
+
@references ||= {}
|
|
198
|
+
@references[model] ||= booted(model)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def booted(model)
|
|
202
|
+
unit = model == "8250" ? Drives::Model8250.new : Drives::Model8050.new
|
|
203
|
+
Drives::Model8050::BOOT_CYCLES.times { unit.host_cycle! }
|
|
204
|
+
unit.cycle! until idle_loop?(unit)
|
|
205
|
+
unit.save_modules([], "BL-D8")
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def idle_loop?(unit)
|
|
209
|
+
cpu = unit.fdc_cpu
|
|
210
|
+
unit.ip_cpu.boundary? && cpu.boundary? && cpu.program_counter == Drives::Model8050::Idle::FDC_LOOP
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|