badline 0.17.0 → 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 +153 -0
- data/CLAUDE.md +11 -7
- data/CONTRIBUTING.md +7 -1
- data/README.md +169 -20
- data/doc/pinned-behaviour.md +128 -0
- data/doc/snapshot-format.md +253 -18
- data/doc/snapshots.md +57 -23
- 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/keyed_state.rb +4 -0
- data/lib/badline/cbm2/p500.rb +5 -3
- data/lib/badline/cbm2.rb +6 -3
- data/lib/badline/chips/cia/timer.rb +2 -2
- data/lib/badline/chips/cpu/instruction_set.rb +1 -1
- 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/timer.rb +14 -3
- data/lib/badline/chips/rriot.rb +4 -2
- data/lib/badline/chips/sid/filter.rb +4 -4
- data/lib/badline/chips/sid/waveform.rb +6 -6
- 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/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/dos_board.rb +91 -0
- data/lib/badline/drives/model8050/idle.rb +11 -7
- data/lib/badline/drives/model8050/keyed_state.rb +5 -2
- data/lib/badline/drives/model8050.rb +18 -130
- 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 +8 -0
- data/lib/badline/frontend/boot.rb +3 -2
- data/lib/badline/frontend/controls.rb +62 -6
- data/lib/badline/frontend/keys.rb +36 -0
- 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 +42 -12
- data/lib/badline/frontend/menu/menu_pages.rb +97 -2
- data/lib/badline/frontend/menu/menu_ports.rb +38 -1
- data/lib/badline/frontend/menu/pause_menu.rb +5 -1
- data/lib/badline/frontend/pet_keyboard.rb +23 -1
- 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 +31 -2
- data/lib/badline/frontend.rb +3 -0
- 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 +94 -117
- data/lib/badline/machine/keyboard_buffer.rb +20 -3
- data/lib/badline/machine/line_sound.rb +122 -0
- data/lib/badline/machine.rb +46 -15
- 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/true_drive.rb +42 -45
- data/lib/badline/media.rb +12 -6
- data/lib/badline/options/family.rb +98 -8
- data/lib/badline/options/help.rb +44 -12
- data/lib/badline/options/table.rb +43 -11
- data/lib/badline/options/validation.rb +44 -8
- data/lib/badline/options.rb +27 -8
- data/lib/badline/pet/bus.rb +16 -2
- 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 +19 -3
- data/lib/badline/plus4/keyed_state.rb +2 -0
- data/lib/badline/plus4.rb +1 -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/9060/dos-3.0.rom +0 -0
- data/lib/badline/roms/9060/sasi-300515-revb.rom +0 -0
- data/lib/badline/roms/README +166 -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/container.rb +2 -0
- data/lib/badline/snapshot/field_doc/markdown.rb +1 -1
- data/lib/badline/snapshot/field_doc.rb +27 -6
- data/lib/badline/snapshot/image.rb +4 -1
- 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/pet.rb +13 -7
- data/lib/badline/snapshot/vice/pet_cpu.rb +39 -5
- data/lib/badline/snapshot/vice/pet_crtc.rb +15 -2
- data/lib/badline/snapshot/vice/pet_drive8050.rb +188 -68
- data/lib/badline/snapshot/vice/pet_mem.rb +49 -5
- data/lib/badline/snapshot/vice/pet_restore.rb +40 -11
- data/lib/badline/snapshot.rb +4 -0
- data/lib/badline/status.rb +30 -30
- data/lib/badline/storage/d67_image.rb +55 -0
- data/lib/badline/storage/d90_image.rb +180 -0
- data/lib/badline/storage/disk_image/writing.rb +2 -2
- data/lib/badline/storage/kim_tape.rb +254 -0
- data/lib/badline/storage/papertape.rb +90 -0
- data/lib/badline/storage.rb +4 -0
- data/lib/badline/support/kim1_checkpoint.rb +41 -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 +1 -0
- metadata +77 -1
|
@@ -10,6 +10,8 @@ require "badline/drives/model8050/parts"
|
|
|
10
10
|
require "badline/drives/model8050/ieee_port"
|
|
11
11
|
require "badline/drives/model8050/disk"
|
|
12
12
|
require "badline/drives/model8050/mechanism"
|
|
13
|
+
require "badline/drives/model8050/dos_board"
|
|
14
|
+
require "badline/drives/model8050/board"
|
|
13
15
|
require "badline/drives/model8050/idle"
|
|
14
16
|
require "badline/drives/model8050/keyed_state"
|
|
15
17
|
|
|
@@ -18,25 +20,21 @@ module Badline
|
|
|
18
20
|
# The 8050 dual disk drive of the PET and CBM-II, two drives on one
|
|
19
21
|
# IEEE-488 unit (schematic 8050001; the 8050/8250 service manual).
|
|
20
22
|
#
|
|
21
|
-
# Two 6502s share the board, clocked at 1 MHz from one 12 MHz
|
|
22
|
-
# on opposite phases. The interface processor (IP) runs DOS 2.7
|
|
23
|
-
# 16 KB of ROM at $C000, with two 6532s (IPBus): their RAM is its
|
|
24
|
-
# page and stack, and their ports face the IEEE-488 bus
|
|
25
|
-
# floppy controller (FDC) runs the 1 KB program in its
|
|
26
|
-
# a VIA and the 6530 facing the drives (FDCBus,
|
|
27
|
-
# meet in 4 KB of common RAM, which each reaches in
|
|
28
|
-
# the cycle: the IP leaves jobs there, and the FDC runs
|
|
23
|
+
# Two 6502s share the board (Board), clocked at 1 MHz from one 12 MHz
|
|
24
|
+
# crystal on opposite phases. The interface processor (IP) runs DOS 2.7
|
|
25
|
+
# from 16 KB of ROM at $C000, with two 6532s (IPBus): their RAM is its
|
|
26
|
+
# zero page and stack, and their ports face the IEEE-488 bus
|
|
27
|
+
# (IEEEPort). The floppy controller (FDC) runs the 1 KB program in its
|
|
28
|
+
# 6530's ROM, with a VIA and the 6530 facing the drives (FDCBus,
|
|
29
|
+
# Mechanism). The two meet in 4 KB of common RAM, which each reaches in
|
|
30
|
+
# its own half of the cycle: the IP leaves jobs there, and the FDC runs
|
|
31
|
+
# them.
|
|
29
32
|
#
|
|
30
33
|
# Reset starts both. The FDC waits on the common RAM for the IP, which
|
|
31
34
|
# tests it, copies the FDC's start-up code into it ($C370) and waits for
|
|
32
35
|
# the FDC to answer, before both settle into their idle loops.
|
|
33
|
-
#
|
|
34
|
-
# The drive clocks against the host through a phase accumulator, as
|
|
35
|
-
# Drives::Engine::Core does: each host cycle adds 1 MHz of phase, and
|
|
36
|
-
# every whole host period in it runs a cycle of both processors. While
|
|
37
|
-
# both processors idle, it sleeps instead (Idle), and the readers of its
|
|
38
|
-
# parts catch it up first.
|
|
39
36
|
class Model8050
|
|
37
|
+
include Board
|
|
40
38
|
include Idle
|
|
41
39
|
|
|
42
40
|
CLOCK_HZ = 1_000_000
|
|
@@ -45,37 +43,10 @@ module Badline
|
|
|
45
43
|
# run the IP's RAM test and the FDC's start-up and reads its jumpers.
|
|
46
44
|
BOOT_CYCLES = 1_500_000
|
|
47
45
|
|
|
48
|
-
attr_reader :device, :ieee_bus
|
|
49
|
-
|
|
50
|
-
def ip_cpu = settled(@ip_cpu)
|
|
51
|
-
|
|
52
|
-
def fdc_cpu = settled(@fdc_cpu)
|
|
53
|
-
|
|
54
|
-
def ip_bus = settled(@ip_bus)
|
|
55
|
-
|
|
56
|
-
def fdc_bus = settled(@fdc_bus)
|
|
57
|
-
|
|
58
|
-
def common = settled(@common)
|
|
59
|
-
|
|
60
|
-
def data_riot = settled(@data_riot)
|
|
61
|
-
|
|
62
|
-
def control_riot = settled(@control_riot)
|
|
63
|
-
|
|
64
|
-
def via = settled(@via)
|
|
65
|
-
|
|
66
|
-
def rriot = settled(@rriot)
|
|
67
|
-
|
|
68
|
-
def mechanism = settled(@mechanism)
|
|
69
|
-
|
|
70
|
-
def cycles
|
|
71
|
-
settle!
|
|
72
|
-
@cycles
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
attr_writer :host_clock_hz
|
|
76
|
-
|
|
77
46
|
def model_name = "8050"
|
|
78
47
|
|
|
48
|
+
def boot_cycles = BOOT_CYCLES
|
|
49
|
+
|
|
79
50
|
# The sides each drive's disk has.
|
|
80
51
|
def sides = 1
|
|
81
52
|
|
|
@@ -91,39 +62,10 @@ module Badline
|
|
|
91
62
|
build_fdc(fdc_rom || ROM.read("8050/fdc-901869-01.rom"))
|
|
92
63
|
@ip_cpu = IPCPU.new(@ip_bus)
|
|
93
64
|
@fdc_cpu = FDCCPU.new(@fdc_bus)
|
|
94
|
-
init_idle
|
|
65
|
+
init_idle(Idle::IP_LOOP, Idle::FDC_LOOP)
|
|
95
66
|
@ieee_bus.attach(self)
|
|
96
67
|
end
|
|
97
68
|
|
|
98
|
-
# Plugs the unit into an IEEE-488 bus, leaving the one it was on.
|
|
99
|
-
def connect(ieee_bus)
|
|
100
|
-
settle!
|
|
101
|
-
@ieee_bus.detach(self)
|
|
102
|
-
@ieee_bus = ieee_bus
|
|
103
|
-
@ieee_port.bus = ieee_bus
|
|
104
|
-
ieee_bus.attach(self)
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
# The lines the unit pulls on the bus while ATN is low or not
|
|
108
|
-
# (IEEEPort).
|
|
109
|
-
def ieee_low_lines(atn) = @ieee_port.ieee_low_lines(atn)
|
|
110
|
-
|
|
111
|
-
# The controller pushed its lines into the bus.
|
|
112
|
-
def host_written! = nil
|
|
113
|
-
|
|
114
|
-
# Which LEDs are lit: bit 0 drive 1's, bit 1 drive 0's, bit 2 the
|
|
115
|
-
# error LED. They hold still while the unit sleeps, so reading them
|
|
116
|
-
# leaves it asleep.
|
|
117
|
-
def leds = @ieee_port.leds
|
|
118
|
-
|
|
119
|
-
def led_on? = leds.anybits?(0x03)
|
|
120
|
-
|
|
121
|
-
# Puts a Disk in +drive+, 0 or 1. Nil takes the disk out.
|
|
122
|
-
def insert(disk, drive: 0)
|
|
123
|
-
settle!
|
|
124
|
-
@mechanism.insert(disk, drive)
|
|
125
|
-
end
|
|
126
|
-
|
|
127
69
|
# Puts in the .d80 or .d82 image at +path+, write-protected with
|
|
128
70
|
# `read_only`.
|
|
129
71
|
def insert_image(path, drive: 0, read_only: false)
|
|
@@ -131,65 +73,8 @@ module Badline
|
|
|
131
73
|
insert(Disk.new(image_class.new(path, read_only:)), drive:)
|
|
132
74
|
end
|
|
133
75
|
|
|
134
|
-
# The Disk in +drive+, or nil.
|
|
135
|
-
def disk(drive = 0) = @mechanism.disk(drive)
|
|
136
|
-
|
|
137
|
-
# Stores what the heads wrote in the disks' images, as the motors
|
|
138
|
-
# stopping do.
|
|
139
|
-
def flush
|
|
140
|
-
settle!
|
|
141
|
-
@mechanism.flush
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
def power_led_on? = true
|
|
145
|
-
|
|
146
|
-
# Runs the drive cycles that fall in one host cycle, then takes the
|
|
147
|
-
# controller's lines for the next. Asleep, the unit owes the cycles
|
|
148
|
-
# instead, and wakes once the controller has moved a line.
|
|
149
|
-
def host_cycle!
|
|
150
|
-
phase = @phase + CLOCK_HZ
|
|
151
|
-
while phase >= @host_clock_hz
|
|
152
|
-
@asleep ? owe_cycle : run_cycle
|
|
153
|
-
phase -= @host_clock_hz
|
|
154
|
-
end
|
|
155
|
-
@phase = phase
|
|
156
|
-
settle! if @asleep && @ieee_port.lines_moved?
|
|
157
|
-
@ieee_port.latch_host
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
def cycle!
|
|
161
|
-
settle!
|
|
162
|
-
run_cycle
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
# The bus's IFC line resets both processors and every chip. RAM keeps
|
|
166
|
-
# its contents.
|
|
167
|
-
def reset!
|
|
168
|
-
settle!
|
|
169
|
-
@data_riot.reset!
|
|
170
|
-
@control_riot.reset!
|
|
171
|
-
@via.reset!
|
|
172
|
-
@rriot.reset!
|
|
173
|
-
@ip_cpu.reset!
|
|
174
|
-
@fdc_cpu.reset!
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
def inspect
|
|
178
|
-
"#<#{self.class.name} cycles=#{@cycles} ip=(#{@ip_cpu.inspect}) fdc=(#{@fdc_cpu.inspect})>"
|
|
179
|
-
end
|
|
180
|
-
|
|
181
76
|
private
|
|
182
77
|
|
|
183
|
-
def settled(part)
|
|
184
|
-
settle!
|
|
185
|
-
part
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
def run_cycle
|
|
189
|
-
step
|
|
190
|
-
watch_loops if @idle_skip
|
|
191
|
-
end
|
|
192
|
-
|
|
193
78
|
def build_ip(rom)
|
|
194
79
|
@common = Memory.new([], length: 0x1000)
|
|
195
80
|
@ieee_bus = IEEEBus.new
|
|
@@ -210,6 +95,9 @@ module Badline
|
|
|
210
95
|
@mechanism.rriot = @rriot
|
|
211
96
|
end
|
|
212
97
|
|
|
98
|
+
# The disk a BL-D8-DISK module holds (KeyedState).
|
|
99
|
+
def disk_from_fields(input, detached) = Disk.from_fields(input, detached:)
|
|
100
|
+
|
|
213
101
|
# One cycle of the board: the drives, then the chips, then the IP and
|
|
214
102
|
# the FDC, each in its own half of the cycle. ATN reaches UE1's PA7 as
|
|
215
103
|
# the port latched it. UE1 pulls the IP's IRQ, and the VIA and the
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drives
|
|
5
|
+
class Model9060
|
|
6
|
+
# Sleeping through the two processors' idle loops, exactly, as
|
|
7
|
+
# Model8050::Idle does.
|
|
8
|
+
#
|
|
9
|
+
# With nothing to do, the IP goes round DOS 3.0's idle loop from
|
|
10
|
+
# IP_LOOP, and the SASI host round its job loop from SASI_LOOP, each
|
|
11
|
+
# pass finding the job queue empty, until ATN interrupts the IP. The
|
|
12
|
+
# host has no interrupt of its own. A pass puts back what it found, so
|
|
13
|
+
# each processor comes back to its loop's start as it left it.
|
|
14
|
+
#
|
|
15
|
+
# Each time a processor comes to its loop's start, the unit compares
|
|
16
|
+
# what that processor sees with what it saw last time round: the CPU,
|
|
17
|
+
# its chips but for their counters, its RAM and the common RAM. The
|
|
18
|
+
# same, with no read of a counter or an interrupt flag between, makes
|
|
19
|
+
# the pass repeatable. Once both are, with the hard disk's controller
|
|
20
|
+
# idle and no interrupt pulled, the unit sleeps until a counter is due
|
|
21
|
+
# to pull IRQ, the IEEE-488 controller moves a line, or something
|
|
22
|
+
# outside reads or changes the unit (settle!).
|
|
23
|
+
module Idle
|
|
24
|
+
IP_LOOP = 0xc131
|
|
25
|
+
SASI_LOOP = 0xf8f1
|
|
26
|
+
|
|
27
|
+
# Whether the unit may sleep through its idle loops.
|
|
28
|
+
attr_reader :idle_skip
|
|
29
|
+
|
|
30
|
+
def idle_skip=(on)
|
|
31
|
+
settle!
|
|
32
|
+
@idle_skip = on
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def asleep? = @asleep
|
|
36
|
+
|
|
37
|
+
# Brings the unit up to the present, cycle for cycle, and drops the
|
|
38
|
+
# passes it found, since whoever called may change it.
|
|
39
|
+
def settle!
|
|
40
|
+
wake! if @asleep
|
|
41
|
+
forget_passes
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def init_idle
|
|
47
|
+
@idle_skip = true
|
|
48
|
+
@asleep = false
|
|
49
|
+
@owed = 0
|
|
50
|
+
@budget = 0
|
|
51
|
+
forget_passes
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def forget_passes
|
|
55
|
+
@ip_mark = nil
|
|
56
|
+
@sasi_mark = nil
|
|
57
|
+
@ip_pass = nil
|
|
58
|
+
@sasi_pass = nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def owe_cycle
|
|
62
|
+
@owed += 1
|
|
63
|
+
wake! if @owed >= @budget
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def watch_loops
|
|
67
|
+
ip = @ip_cpu
|
|
68
|
+
@ip_pass = nil if ip.irq
|
|
69
|
+
@sasi_pass = nil if @sasi_cpu.irq
|
|
70
|
+
ip_pass_found if ip.program_counter == IP_LOOP && ip.boundary?
|
|
71
|
+
host = @sasi_cpu
|
|
72
|
+
sasi_pass_found if host.program_counter == SASI_LOOP && host.boundary?
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def ip_pass_found
|
|
76
|
+
state = ip_state
|
|
77
|
+
@ip_pass = repeated(@ip_mark, state, @ip_cpu, @ip_bus.volatile)
|
|
78
|
+
@ip_mark = [state, @ip_cpu.cycles, @ip_cpu.instructions]
|
|
79
|
+
@ip_bus.volatile = false
|
|
80
|
+
fall_asleep if @ip_pass && @sasi_pass
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def sasi_pass_found
|
|
84
|
+
state = sasi_state
|
|
85
|
+
@sasi_pass = repeated(@sasi_mark, state, @sasi_cpu, @sasi_bus.volatile)
|
|
86
|
+
@sasi_mark = [state, @sasi_cpu.cycles, @sasi_cpu.instructions]
|
|
87
|
+
@sasi_bus.volatile = false
|
|
88
|
+
fall_asleep if @ip_pass && @sasi_pass
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def repeated(mark, state, cpu, volatile)
|
|
92
|
+
return nil if mark.nil? || volatile || mark[0] != state
|
|
93
|
+
|
|
94
|
+
[cpu.cycles - mark[1], cpu.instructions - mark[2]]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def fall_asleep
|
|
98
|
+
target = @target
|
|
99
|
+
return unless target.quiet? && !target.busy? && !@ip_cpu.irq && !@sasi_cpu.irq
|
|
100
|
+
|
|
101
|
+
budget = quiet_cycles
|
|
102
|
+
return if budget < 2
|
|
103
|
+
|
|
104
|
+
@asleep = true
|
|
105
|
+
@owed = 0
|
|
106
|
+
@budget = budget
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def quiet_cycles
|
|
110
|
+
[@data_riot.quiet_cycles, @control_riot.quiet_cycles, @via.quiet_cycles].min
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def wake!
|
|
114
|
+
owed = @owed
|
|
115
|
+
@asleep = false
|
|
116
|
+
@owed = 0
|
|
117
|
+
return if owed.zero?
|
|
118
|
+
|
|
119
|
+
@data_riot.fast_forward(owed)
|
|
120
|
+
@control_riot.fast_forward(owed)
|
|
121
|
+
@via.fast_forward(owed)
|
|
122
|
+
@target.fast_forward(owed)
|
|
123
|
+
skip(@ip_cpu, @ip_pass, @ip_mark, owed)
|
|
124
|
+
skip(@sasi_cpu, @sasi_pass, @sasi_mark, owed)
|
|
125
|
+
@cycles += owed
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def skip(cpu, pass, mark, owed)
|
|
129
|
+
passes = owed / pass[0]
|
|
130
|
+
cpu.fast_forward(passes * pass[0], passes * pass[1])
|
|
131
|
+
mark[1] += passes * pass[0]
|
|
132
|
+
mark[2] += passes * pass[1]
|
|
133
|
+
(owed - (passes * pass[0])).times { cpu.cycle! }
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def ip_state
|
|
137
|
+
[*@ip_cpu.idle_state, *@data_riot.idle_state, *@control_riot.idle_state, @ip_bus.data, common_changes]
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def sasi_state
|
|
141
|
+
[*@sasi_cpu.idle_state, *@via.idle_state, *@lines.state, *@sasi_bus.ram.read(0, 0x80), @sasi_bus.data,
|
|
142
|
+
common_changes]
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def common_changes = @ip_bus.common_changes + @sasi_bus.common_changes
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drives
|
|
5
|
+
class Model9060
|
|
6
|
+
class SASITarget
|
|
7
|
+
# The controller's fields in a keyed snapshot module (BL-D8-SASI):
|
|
8
|
+
# the bus phase, REQ, the cycles left to the next, the command, the
|
|
9
|
+
# bytes of the phase and the place in them, the write under way,
|
|
10
|
+
# the sense bytes, the fast mode, its clock, the heads' cylinder, the
|
|
11
|
+
# geometry and the block buffer, then the image's path and how it
|
|
12
|
+
# was opened. The image itself stays in its file, which each write
|
|
13
|
+
# reached as it ended.
|
|
14
|
+
module KeyedState
|
|
15
|
+
def save_fields(out)
|
|
16
|
+
out.int("phase", @phase).bool("request", @request).int("delay", @delay)
|
|
17
|
+
out.ints("command", @command).ints("buffer", @buffer).int("index", @index)
|
|
18
|
+
out.bool("writing", @writing).ints("sense", @sense).ints("last_sense", @last_sense)
|
|
19
|
+
out.bool("fast", @fast).int("cycle", @cycle).int("cylinder", @cylinder)
|
|
20
|
+
out.int("heads", @heads).int("sectors", @sectors).ints("block_buffer", @block_buffer)
|
|
21
|
+
disk = @disk
|
|
22
|
+
out.text("path", disk.nil? ? "" : disk.path.to_s).bool("read_only", disk.nil? || disk.read_only?)
|
|
23
|
+
out.int("disk_heads", disk.nil? ? 0 : disk.heads)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# A detached reader, or a host file that is gone, leaves the drive
|
|
27
|
+
# empty.
|
|
28
|
+
def load_fields(input, detached: false)
|
|
29
|
+
load_bus_fields(input)
|
|
30
|
+
path = input.text("path", "")
|
|
31
|
+
read_only = input.bool("read_only", false)
|
|
32
|
+
heads = input.int("disk_heads", 4)
|
|
33
|
+
@disk = detached || !File.file?(path) ? nil : Storage::D90Image.new(path, read_only:, heads:)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def load_bus_fields(input)
|
|
39
|
+
@phase = input.int("phase", @phase)
|
|
40
|
+
@request = input.bool("request", @request)
|
|
41
|
+
@delay = input.int("delay", @delay)
|
|
42
|
+
@command = input.ints("command", @command).dup
|
|
43
|
+
@buffer = input.ints("buffer", @buffer).dup
|
|
44
|
+
@index = input.int("index", @index)
|
|
45
|
+
@writing = input.bool("writing", @writing)
|
|
46
|
+
@sense = input.ints("sense", @sense).dup
|
|
47
|
+
@last_sense = input.ints("last_sense", @last_sense).dup
|
|
48
|
+
@fast = input.bool("fast", @fast)
|
|
49
|
+
@cycle = input.int("cycle", @cycle)
|
|
50
|
+
@cylinder = input.int("cylinder", @cylinder)
|
|
51
|
+
@heads = input.int("heads", @heads)
|
|
52
|
+
@sectors = input.int("sectors", @sectors)
|
|
53
|
+
@block_buffer = input.ints("block_buffer", @block_buffer).dup
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
include KeyedState
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# The unit's own fields in a keyed snapshot module (BL-D8): its model,
|
|
61
|
+
# its clock phase and cycle count, each bus's last byte, the
|
|
62
|
+
# controller's lines as the port latched them, under ieee.*, and the
|
|
63
|
+
# SASI lines' ACK and RST levels. A save settles the unit first, so it
|
|
64
|
+
# is awake; a load finds its passes afresh.
|
|
65
|
+
module KeyedState
|
|
66
|
+
def save_fields(out)
|
|
67
|
+
settle!
|
|
68
|
+
out.text("model", model_name).int("phase", @phase).int("cycles", @cycles)
|
|
69
|
+
out.int("ip_data", @ip_bus.data).int("sasi_data", @sasi_bus.data)
|
|
70
|
+
out.int("ieee.host", @ieee_port.host)
|
|
71
|
+
ack_high, reset_high = @lines.state
|
|
72
|
+
out.bool("sasi.ack_high", ack_high).bool("sasi.reset_high", reset_high)
|
|
73
|
+
out
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def load_fields(input)
|
|
77
|
+
settle!
|
|
78
|
+
input.choice("model", [model_name], model_name)
|
|
79
|
+
@phase = input.int("phase", @phase)
|
|
80
|
+
@cycles = input.int("cycles", @cycles)
|
|
81
|
+
@ip_bus.data = input.byte("ip_data", @ip_bus.data)
|
|
82
|
+
@sasi_bus.data = input.byte("sasi_data", @sasi_bus.data)
|
|
83
|
+
@ieee_port.host = input.int("ieee.host", @ieee_port.host)
|
|
84
|
+
@lines.state = [input.bool("sasi.ack_high", true), input.bool("sasi.reset_high", false)]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# The unit as modules of its own, named from `name` (BL-D8 for
|
|
88
|
+
# device 8): its own fields, the common RAM, UC1 and UE1 with their
|
|
89
|
+
# RAM, each processor, the host's RAM and VIA, and the hard disk's
|
|
90
|
+
# controller.
|
|
91
|
+
def save_modules(sections, name)
|
|
92
|
+
sections << Snapshot::Keyed.section(name) { |out| save_fields(out) }
|
|
93
|
+
sections << Snapshot::Keyed.section("#{name}-COMMON") { |out| @common.save_fields(out, "ram") }
|
|
94
|
+
sections << Snapshot::Keyed.section("#{name}-RIOT1") { |out| @data_riot.save_fields(out) }
|
|
95
|
+
sections << Snapshot::Keyed.section("#{name}-RIOT2") { |out| @control_riot.save_fields(out) }
|
|
96
|
+
sections << Snapshot::Keyed.section("#{name}-IPCPU") { |out| @ip_cpu.save_fields(out) }
|
|
97
|
+
sections << Snapshot::Keyed.section("#{name}-SASICPU") { |out| @sasi_cpu.save_fields(out) }
|
|
98
|
+
sections << Snapshot::Keyed.section("#{name}-SASIRAM") { |out| @sasi_bus.ram.save_fields(out, "ram") }
|
|
99
|
+
sections << Snapshot::Keyed.section("#{name}-VIA") { |out| @via.save_fields(out) }
|
|
100
|
+
sections << Snapshot::Keyed.section("#{name}-SASI") { |out| @target.save_fields(out) }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# The unit from its modules, `input` being its own.
|
|
104
|
+
def load_modules(modules, input, name)
|
|
105
|
+
load_fields(input)
|
|
106
|
+
@common.load_fields(modules.required("#{name}-COMMON"), "ram")
|
|
107
|
+
@data_riot.load_fields(modules.required("#{name}-RIOT1"))
|
|
108
|
+
@control_riot.load_fields(modules.required("#{name}-RIOT2"))
|
|
109
|
+
@ip_cpu.load_fields(modules.required("#{name}-IPCPU"))
|
|
110
|
+
@sasi_cpu.load_fields(modules.required("#{name}-SASICPU"))
|
|
111
|
+
@sasi_bus.ram.load_fields(modules.required("#{name}-SASIRAM"), "ram")
|
|
112
|
+
@via.load_fields(modules.required("#{name}-VIA"))
|
|
113
|
+
@target.load_fields(modules.required("#{name}-SASI"), detached: modules.detached?)
|
|
114
|
+
@via.restored!
|
|
115
|
+
@ip_cpu.restored!
|
|
116
|
+
@sasi_cpu.restored!
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
include KeyedState
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drives
|
|
5
|
+
class Model9060
|
|
6
|
+
# The SASI host's VIA, at $0080, a class of its own so it keeps one
|
|
7
|
+
# type of peripheral.
|
|
8
|
+
class SASIVIA < Chips::VIA
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# The SASI host's 6502, on the SASIBus.
|
|
12
|
+
class SASICPU
|
|
13
|
+
include Badline::Chips::CPU::Core
|
|
14
|
+
include Engine::IdleCPU
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# The VIA's lines to the controller (SASITarget), on the board's
|
|
18
|
+
# transceivers:
|
|
19
|
+
#
|
|
20
|
+
# PA0-7 the data bus PB0 SEL out
|
|
21
|
+
# CA1 REQ, rising as it rises PB1 RST out
|
|
22
|
+
# CA2 ACK, pulsed on each PB2 C/D in
|
|
23
|
+
# read or write of ORA PB3 BSY in
|
|
24
|
+
# CB2 the transceivers' PB4 jumper J14: high for four
|
|
25
|
+
# direction heads (D9060), low for six
|
|
26
|
+
# PB6 I/O in
|
|
27
|
+
# PB7 MSG in
|
|
28
|
+
#
|
|
29
|
+
# The ports read and drive each line high while it is asserted, SEL
|
|
30
|
+
# and RST only while PB0 and PB1 are outputs.
|
|
31
|
+
# SEL raised selects the controller with the data bus as the host
|
|
32
|
+
# drives it, and RST raised resets it. ACK falling hands it the byte
|
|
33
|
+
# on the data bus.
|
|
34
|
+
class SASILines
|
|
35
|
+
SEL = 0x01
|
|
36
|
+
RST = 0x02
|
|
37
|
+
CD = 0x04
|
|
38
|
+
BSY = 0x08
|
|
39
|
+
J14 = 0x10
|
|
40
|
+
IO = 0x40
|
|
41
|
+
MSG = 0x80
|
|
42
|
+
|
|
43
|
+
attr_reader :target
|
|
44
|
+
attr_writer :via
|
|
45
|
+
|
|
46
|
+
# `four_heads` is J14.
|
|
47
|
+
def initialize(target, four_heads:)
|
|
48
|
+
@target = target
|
|
49
|
+
@four_heads = four_heads
|
|
50
|
+
@via = nil
|
|
51
|
+
@ack_high = true
|
|
52
|
+
@reset_high = false
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# The data bus: the controller's byte while it drives it.
|
|
56
|
+
def read_a(_lines)
|
|
57
|
+
target = @target
|
|
58
|
+
target.input? ? target.data : 0xff
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def read_b(_lines)
|
|
62
|
+
target = @target
|
|
63
|
+
value = 0x23
|
|
64
|
+
value |= CD if target.control?
|
|
65
|
+
value |= BSY if target.busy?
|
|
66
|
+
value |= J14 if @four_heads
|
|
67
|
+
value |= IO if target.input?
|
|
68
|
+
value |= MSG if target.message?
|
|
69
|
+
value
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# One cycle of the controller, then the host's ACK, RST and SEL to
|
|
73
|
+
# the controller, and REQ to CA1.
|
|
74
|
+
def cycle!
|
|
75
|
+
target = @target
|
|
76
|
+
via = @via
|
|
77
|
+
target.clock
|
|
78
|
+
ack_high = via.ca2_output
|
|
79
|
+
target.acknowledge(via.port_a_output) if @ack_high && !ack_high
|
|
80
|
+
@ack_high = ack_high
|
|
81
|
+
host = via.port_b_output & via.peek(0x82)
|
|
82
|
+
reset_high = host.anybits?(RST)
|
|
83
|
+
target.reset if reset_high && !@reset_high
|
|
84
|
+
@reset_high = reset_high
|
|
85
|
+
target.select(via.port_a_output) if host.anybits?(SEL)
|
|
86
|
+
via.ca1 = target.request?
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Everything the lines hold between cycles.
|
|
90
|
+
def state = [@ack_high, @reset_high]
|
|
91
|
+
|
|
92
|
+
def state=(values)
|
|
93
|
+
@ack_high, @reset_high = values
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drives
|
|
5
|
+
class Model9060
|
|
6
|
+
# The SASI host processor's bus on the DOS board (300010), where the
|
|
7
|
+
# 8050's floppy controller sits, with its decode and common RAM:
|
|
8
|
+
#
|
|
9
|
+
# $0000-$007F - the 6810's 128 bytes, mirrored at $0100 for the stack
|
|
10
|
+
# $0080-$008F - the VIA (SASILines)
|
|
11
|
+
# $0400-$13FF - the common RAM, 1 KB in each of the 8050's windows
|
|
12
|
+
# $F800-$FFFF - 300515, the host's program
|
|
13
|
+
#
|
|
14
|
+
# Writes to the ROM and to open bus go nowhere, and a read from open
|
|
15
|
+
# bus finds the last byte on the data bus.
|
|
16
|
+
class SASIBus
|
|
17
|
+
include Addressable
|
|
18
|
+
|
|
19
|
+
attr_reader :common, :ram, :via
|
|
20
|
+
attr_accessor :data
|
|
21
|
+
|
|
22
|
+
# How many writes have changed a byte of the common RAM
|
|
23
|
+
# (Model9060::Idle).
|
|
24
|
+
attr_reader :common_changes
|
|
25
|
+
|
|
26
|
+
# Whether a counter or an interrupt flag has been read since it was
|
|
27
|
+
# last cleared (Model9060::Idle).
|
|
28
|
+
attr_accessor :volatile
|
|
29
|
+
|
|
30
|
+
def initialize(rom:, common:, via:)
|
|
31
|
+
addressable_at(0, length: 2**16)
|
|
32
|
+
@rom = rom
|
|
33
|
+
@common = common
|
|
34
|
+
@via = via
|
|
35
|
+
@ram = Memory.new([], length: 0x80)
|
|
36
|
+
@data = 0
|
|
37
|
+
@volatile = false
|
|
38
|
+
@common_changes = 0
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def peek(addr)
|
|
42
|
+
@data = if addr >= 0xf800
|
|
43
|
+
@rom.peek(addr & 0x07ff)
|
|
44
|
+
elsif addr < 0x0200
|
|
45
|
+
peek_low(addr)
|
|
46
|
+
elsif addr.between?(0x0400, 0x13ff)
|
|
47
|
+
@common.peek(addr - 0x0400)
|
|
48
|
+
else
|
|
49
|
+
@data
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def poke(addr, value)
|
|
54
|
+
@data = value
|
|
55
|
+
if addr < 0x0200
|
|
56
|
+
poke_low(addr, value)
|
|
57
|
+
elsif addr.between?(0x0400, 0x13ff)
|
|
58
|
+
poke_common(addr - 0x0400, value)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def peek_low(addr)
|
|
65
|
+
return @ram.peek(addr & 0x7f) if addr.nobits?(0x80)
|
|
66
|
+
return @data if addr.anybits?(0x70)
|
|
67
|
+
|
|
68
|
+
register = addr & 0x0f
|
|
69
|
+
@volatile = true if register.between?(0x04, 0x0a) || register == 0x0d
|
|
70
|
+
@via.peek(0x80 | register)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def poke_low(addr, value)
|
|
74
|
+
if addr.nobits?(0x80)
|
|
75
|
+
@ram.poke(addr & 0x7f, value)
|
|
76
|
+
elsif addr.nobits?(0x70)
|
|
77
|
+
@via.poke(0x80 | (addr & 0x0f), value)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def poke_common(index, value)
|
|
82
|
+
@common_changes += 1 if @common.peek(index) != value
|
|
83
|
+
@common.poke(index, value)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|