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,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
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drives
|
|
5
|
+
class Model9060
|
|
6
|
+
# The commands the DOS board's SASI ROM (300515) sends, against the
|
|
7
|
+
# disk's blocks. A command's second to fourth bytes hold the drive
|
|
8
|
+
# (bits 5-7) and the block's LBA, its fifth the blocks to move.
|
|
9
|
+
#
|
|
10
|
+
# | Op | Command | Data |
|
|
11
|
+
# | --- | --- | --- |
|
|
12
|
+
# | $00 | Test Unit Ready | |
|
|
13
|
+
# | $01 | Recalibrate: seek to cylinder 0 | |
|
|
14
|
+
# | $03 | Request Sense: the last command's error and its LBA | 4 in |
|
|
15
|
+
# | $04 | Format Unit | |
|
|
16
|
+
# | $08 | Read | 256 a block in |
|
|
17
|
+
# | $0A | Write | 256 a block out |
|
|
18
|
+
# | $0B | Seek | |
|
|
19
|
+
# | $0C | Vendor: read the controller's block buffer (unverified) | 256 in |
|
|
20
|
+
# | $0E | Vendor: write the controller's block buffer (unverified) | 256 out |
|
|
21
|
+
# | $E3 | Vendor: four zero bytes from a drive that is there (unverified) | 4 in |
|
|
22
|
+
# | $C0 | Vendor: set the geometry (unverified) | |
|
|
23
|
+
#
|
|
24
|
+
# DOS 3.0 sends $E3 before it first reads a drive, and leaves a drive
|
|
25
|
+
# alone that fails it as DRIVE NOT READY, as drive 1 does here.
|
|
26
|
+
#
|
|
27
|
+
# $C0's fifth and sixth bytes are taken as the last head and the last
|
|
28
|
+
# sector of a head, as the ROM sends them (5 and 31 on a D9090 at
|
|
29
|
+
# boot). Its other bytes aren't used.
|
|
30
|
+
#
|
|
31
|
+
# An error's sense byte has its class in bits 4-5 and its code in bits
|
|
32
|
+
# 0-3, with bit 7 set when the LBA after it names the block. The ROM
|
|
33
|
+
# reads that byte from the message phase after a check condition,
|
|
34
|
+
# and the controller sends it there.
|
|
35
|
+
#
|
|
36
|
+
# Format Unit erases the disk at once and puts the controller in a
|
|
37
|
+
# fast mode, where Read, Write and Seek take no seek or rotation time,
|
|
38
|
+
# for the DOS's passes over the whole disk. Any other command ends
|
|
39
|
+
# it. Outside it a seek takes 3 ms a cylinder and 15 ms to settle, and
|
|
40
|
+
# the disk turns 32 blocks in 16.7 ms.
|
|
41
|
+
module SASICommands
|
|
42
|
+
DRIVE_NOT_READY = 0x04
|
|
43
|
+
WRITE_FAULT = 0x03
|
|
44
|
+
INVALID_COMMAND = 0x20
|
|
45
|
+
ILLEGAL_ADDRESS = 0xa1
|
|
46
|
+
|
|
47
|
+
BLOCK = 256
|
|
48
|
+
SECTOR_CYCLES = 521
|
|
49
|
+
STEP_CYCLES = 3_000
|
|
50
|
+
SETTLE_CYCLES = 15_000
|
|
51
|
+
|
|
52
|
+
DISK_COMMANDS = [0x00, 0x01, 0x04, 0x08, 0x0a, 0x0b, 0xe3].freeze
|
|
53
|
+
FAST_COMMANDS = [0x08, 0x0a, 0x0b].freeze
|
|
54
|
+
|
|
55
|
+
attr_reader :heads, :sectors, :cylinder
|
|
56
|
+
|
|
57
|
+
def fast? = @fast
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def reset_geometry
|
|
62
|
+
@heads = 4
|
|
63
|
+
@sectors = 32
|
|
64
|
+
@block_buffer = Array.new(BLOCK, 0)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def execute
|
|
68
|
+
@fast = false unless FAST_COMMANDS.include?(@command[0])
|
|
69
|
+
case @command[0]
|
|
70
|
+
when 0x03 then enter(SASITarget::DATA_IN, @last_sense.dup, 1)
|
|
71
|
+
when 0x0c then enter(SASITarget::DATA_IN, @block_buffer.dup, 1)
|
|
72
|
+
when 0x0e then enter(SASITarget::DATA_OUT, Array.new(BLOCK, 0), 1)
|
|
73
|
+
when 0xc0 then set_geometry
|
|
74
|
+
else disk_command
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def disk_command
|
|
79
|
+
return refuse(INVALID_COMMAND) unless DISK_COMMANDS.include?(@command[0])
|
|
80
|
+
return refuse(DRIVE_NOT_READY) unless @disk && (@command[1] >> 5).zero?
|
|
81
|
+
|
|
82
|
+
case @command[0]
|
|
83
|
+
when 0x00 then report
|
|
84
|
+
when 0x01 then report(travel(0))
|
|
85
|
+
when 0x04 then format_unit
|
|
86
|
+
when 0xe3 then enter(SASITarget::DATA_IN, [0, 0, 0, 0], 1)
|
|
87
|
+
else block_command
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def block_command
|
|
92
|
+
return refuse(ILLEGAL_ADDRESS) unless @disk.blocks?(lba, @command[0] == 0x0b ? 1 : count)
|
|
93
|
+
|
|
94
|
+
case @command[0]
|
|
95
|
+
when 0x08 then read
|
|
96
|
+
when 0x0a then enter(SASITarget::DATA_OUT, Array.new(count * BLOCK, 0), 1)
|
|
97
|
+
else report(travel(cylinder_of(lba)))
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def lba = ((@command[1] & 0x1f) << 16) | (@command[2] << 8) | @command[3]
|
|
102
|
+
|
|
103
|
+
def count = @command[4].zero? ? 256 : @command[4]
|
|
104
|
+
|
|
105
|
+
def cylinder_of(block) = block / (@heads * @sectors)
|
|
106
|
+
|
|
107
|
+
def set_geometry
|
|
108
|
+
@heads = @command[4] + 1
|
|
109
|
+
@sectors = @command[5] + 1
|
|
110
|
+
report
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def read
|
|
114
|
+
bytes = @disk.read_blocks(lba, count)
|
|
115
|
+
@block_buffer = bytes.last(BLOCK)
|
|
116
|
+
enter(SASITarget::DATA_IN, bytes, access_cycles)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def data_received
|
|
120
|
+
if @command[0] == 0x0e
|
|
121
|
+
@block_buffer = @buffer.dup
|
|
122
|
+
report
|
|
123
|
+
else
|
|
124
|
+
@writing = true
|
|
125
|
+
@delay = access_cycles
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def store_buffer
|
|
130
|
+
@disk.write_blocks(lba, @buffer)
|
|
131
|
+
@block_buffer = @buffer.last(BLOCK)
|
|
132
|
+
report
|
|
133
|
+
rescue Storage::WriteError
|
|
134
|
+
refuse(WRITE_FAULT)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def format_unit
|
|
138
|
+
blocks = @heads * @sectors
|
|
139
|
+
(0...@disk.block_count).step(blocks) do |first|
|
|
140
|
+
@disk.write_blocks(first, Array.new([blocks, @disk.block_count - first].min * BLOCK, 0))
|
|
141
|
+
end
|
|
142
|
+
@fast = true
|
|
143
|
+
report
|
|
144
|
+
rescue Storage::WriteError
|
|
145
|
+
refuse(WRITE_FAULT)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# The seek to the block's cylinder, the turn until the block comes
|
|
149
|
+
# under the head, and the blocks passing under it.
|
|
150
|
+
def access_cycles
|
|
151
|
+
return 1 if @fast
|
|
152
|
+
|
|
153
|
+
seek = travel(cylinder_of(lba))
|
|
154
|
+
slot = ((@cycle + seek) / SECTOR_CYCLES) % @sectors
|
|
155
|
+
seek + ((((lba % @sectors) - slot) % @sectors) * SECTOR_CYCLES) + (count * SECTOR_CYCLES)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def travel(target)
|
|
159
|
+
steps = (target - @cylinder).abs
|
|
160
|
+
@cylinder = target
|
|
161
|
+
return 1 if @fast || steps.zero?
|
|
162
|
+
|
|
163
|
+
(steps * STEP_CYCLES) + SETTLE_CYCLES
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "badline/drives/model9060/sasi_commands"
|
|
4
|
+
|
|
5
|
+
module Badline
|
|
6
|
+
module Drives
|
|
7
|
+
class Model9060
|
|
8
|
+
# The D9060's and D9090's hard disk controller (Tandon 188008) as the
|
|
9
|
+
# DOS board's SASI host sees it, at the level of the bus's lines.
|
|
10
|
+
#
|
|
11
|
+
# The host selects it by raising SEL with bit 0 on the data bus, and
|
|
12
|
+
# it answers with BSY. It then runs one command through the bus's
|
|
13
|
+
# phases, which C/D, I/O and MSG name: six command bytes from the
|
|
14
|
+
# host, the data in or out, a status byte and a message byte, after
|
|
15
|
+
# which it lets go of BSY. Each byte waits on REQ, which the host
|
|
16
|
+
# answers with ACK, and REQ rises again a cycle later for the next.
|
|
17
|
+
#
|
|
18
|
+
# The controller's work between the phases (a seek, the turn to the
|
|
19
|
+
# block, the block under the head) delays the next REQ. `clock`
|
|
20
|
+
# counts that time out, one cycle of the DOS board's 1 MHz at a time,
|
|
21
|
+
# and `quiet?` says when there is none left to count. SASICommands
|
|
22
|
+
# runs the commands against the disk, a Storage::D90Image, or nil
|
|
23
|
+
# with none inserted.
|
|
24
|
+
class SASITarget
|
|
25
|
+
include SASICommands
|
|
26
|
+
|
|
27
|
+
BUS_FREE = 0
|
|
28
|
+
COMMAND = 1
|
|
29
|
+
DATA_IN = 2
|
|
30
|
+
DATA_OUT = 3
|
|
31
|
+
STATUS = 4
|
|
32
|
+
MESSAGE = 5
|
|
33
|
+
|
|
34
|
+
COMMAND_LENGTH = 6
|
|
35
|
+
|
|
36
|
+
attr_accessor :disk
|
|
37
|
+
attr_reader :phase
|
|
38
|
+
|
|
39
|
+
def initialize(disk = nil)
|
|
40
|
+
@disk = disk
|
|
41
|
+
@cycle = 0
|
|
42
|
+
@cylinder = 0
|
|
43
|
+
reset_geometry
|
|
44
|
+
reset
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# RST: the controller drops whatever it was doing and frees the bus.
|
|
48
|
+
def reset
|
|
49
|
+
@phase = BUS_FREE
|
|
50
|
+
@request = false
|
|
51
|
+
@delay = 0
|
|
52
|
+
@command = []
|
|
53
|
+
@buffer = []
|
|
54
|
+
@index = 0
|
|
55
|
+
@writing = false
|
|
56
|
+
@sense = [0, 0, 0, 0]
|
|
57
|
+
@last_sense = @sense
|
|
58
|
+
@fast = false
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def busy? = @phase != BUS_FREE
|
|
62
|
+
|
|
63
|
+
def request? = @request
|
|
64
|
+
|
|
65
|
+
# C/D: a command, status or message byte rather than data.
|
|
66
|
+
def control? = [COMMAND, STATUS, MESSAGE].include?(@phase)
|
|
67
|
+
|
|
68
|
+
# I/O: a byte from the controller to the host.
|
|
69
|
+
def input? = [DATA_IN, STATUS, MESSAGE].include?(@phase)
|
|
70
|
+
|
|
71
|
+
def message? = @phase == MESSAGE
|
|
72
|
+
|
|
73
|
+
def quiet? = @delay.zero?
|
|
74
|
+
|
|
75
|
+
# The byte the controller puts on the data bus, while I/O is set.
|
|
76
|
+
def data = input? ? @buffer[@index] || 0 : 0
|
|
77
|
+
|
|
78
|
+
# SEL with the host's data bus. The controller answers to bit 0
|
|
79
|
+
# while the bus is free.
|
|
80
|
+
def select(bus)
|
|
81
|
+
return if busy? || bus.nobits?(0x01)
|
|
82
|
+
|
|
83
|
+
enter(COMMAND, Array.new(COMMAND_LENGTH, 0), 1)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# ACK for the byte REQ asked for. The host's data bus carries a
|
|
87
|
+
# byte to the controller in the command and data out phases, and
|
|
88
|
+
# is ignored in the others.
|
|
89
|
+
def acknowledge(bus)
|
|
90
|
+
return unless @request
|
|
91
|
+
|
|
92
|
+
@request = false
|
|
93
|
+
@buffer[@index] = bus if @phase == COMMAND || @phase == DATA_OUT
|
|
94
|
+
@index += 1
|
|
95
|
+
@index < @buffer.length ? @delay = 1 : finish_phase
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Runs the clock on through cycles the controller has nothing to
|
|
99
|
+
# count down in.
|
|
100
|
+
def fast_forward(cycles)
|
|
101
|
+
@cycle += cycles
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def clock
|
|
105
|
+
@cycle += 1
|
|
106
|
+
return if @delay.zero?
|
|
107
|
+
|
|
108
|
+
@delay -= 1
|
|
109
|
+
arrive if @delay.zero?
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
def enter(phase, bytes, delay)
|
|
115
|
+
@phase = phase
|
|
116
|
+
@buffer = bytes
|
|
117
|
+
@index = 0
|
|
118
|
+
@request = false
|
|
119
|
+
@delay = [delay, 1].max
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def arrive
|
|
123
|
+
if @writing
|
|
124
|
+
@writing = false
|
|
125
|
+
store_buffer
|
|
126
|
+
else
|
|
127
|
+
@request = true
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def finish_phase
|
|
132
|
+
case @phase
|
|
133
|
+
when COMMAND then start(@buffer.dup)
|
|
134
|
+
when DATA_IN then report
|
|
135
|
+
when DATA_OUT then data_received
|
|
136
|
+
when STATUS then enter(MESSAGE, [@sense[0]], 1)
|
|
137
|
+
else @phase = BUS_FREE
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def start(command)
|
|
142
|
+
@command = command
|
|
143
|
+
@last_sense = @sense
|
|
144
|
+
@sense = [0, 0, 0, 0]
|
|
145
|
+
execute
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# The status byte: 0 for a command that went well, $02 (check
|
|
149
|
+
# condition) for one that failed, whose error Request Sense reads.
|
|
150
|
+
def report(delay = 1)
|
|
151
|
+
enter(STATUS, [@sense[0].positive? ? 0x02 : 0x00], delay)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def refuse(error)
|
|
155
|
+
@sense = [error, (@command[1] & 0xe0) | ((lba >> 16) & 0x1f), (lba >> 8) & 0xff, lba & 0xff]
|
|
156
|
+
report
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "badline/drives/model8050"
|
|
4
|
+
require "badline/drives/model9060/sasi_target"
|
|
5
|
+
require "badline/drives/model9060/sasi_bus"
|
|
6
|
+
require "badline/drives/model9060/parts"
|
|
7
|
+
require "badline/drives/model9060/idle"
|
|
8
|
+
require "badline/drives/model9060/keyed_state"
|
|
9
|
+
|
|
10
|
+
module Badline
|
|
11
|
+
module Drives
|
|
12
|
+
# The D9060 hard disk of the PET and CBM-II, one 5 MB drive on an
|
|
13
|
+
# IEEE-488 unit (technical manual PN 990441; DOS board 300010).
|
|
14
|
+
#
|
|
15
|
+
# The DOS board is the 8050's with the floppy half given over to a SASI
|
|
16
|
+
# host. The interface processor (IP) runs DOS 3.0 from 16 KB of ROM at
|
|
17
|
+
# $C000, with the 8050's two 6532s on the IEEE-488 bus (Model8050's
|
|
18
|
+
# IPBus and IEEEPort). The SASI host, a second 6502 in the floppy
|
|
19
|
+
# controller's place, runs 300515 from 2 KB at $F800, with a 6810 for
|
|
20
|
+
# its zero page and stack and a VIA facing the hard disk's controller
|
|
21
|
+
# (SASIBus, SASILines, SASITarget). The two meet in the 8050's 4 KB of
|
|
22
|
+
# common RAM, where the IP leaves jobs and the host turns each into a
|
|
23
|
+
# SASI command.
|
|
24
|
+
#
|
|
25
|
+
# Reset starts both. The host waits on the common RAM for the IP, which
|
|
26
|
+
# tests it and answers, and the DOS settles into its idle loop with the
|
|
27
|
+
# host polling the job queue.
|
|
28
|
+
#
|
|
29
|
+
# The interface processor's side of the board and the clocking against
|
|
30
|
+
# the host are the 8050's (Model8050::DOSBoard), and the unit sleeps
|
|
31
|
+
# through both processors' idle loops (Idle).
|
|
32
|
+
#
|
|
33
|
+
# N runs as the DOS runs it, but for the six passes that write and
|
|
34
|
+
# read back test patterns over every block, an hour of the hardware's
|
|
35
|
+
# time: once Format Unit has erased the disk, the IP goes from the first
|
|
36
|
+
# pass straight on to where they end, as if every block had passed.
|
|
37
|
+
# The DOS then writes the bad-block list, the BAM, the header and the
|
|
38
|
+
# directory as c1541 formats a .d90, byte for byte.
|
|
39
|
+
class Model9060
|
|
40
|
+
include Model8050::DOSBoard
|
|
41
|
+
include Idle
|
|
42
|
+
|
|
43
|
+
CLOCK_HZ = 1_000_000
|
|
44
|
+
|
|
45
|
+
# The host cycles by which a unit switched on with the machine has
|
|
46
|
+
# run its RAM tests and the host's start-up, and reads its jumpers.
|
|
47
|
+
BOOT_CYCLES = 1_500_000
|
|
48
|
+
|
|
49
|
+
# DOS 3.0's format: the first of its pattern passes (FORMAT_PASSES),
|
|
50
|
+
# where the pass number ($2D) is 0, and the code after the last
|
|
51
|
+
# (FORMAT_DONE), with the pass number at 6.
|
|
52
|
+
FORMAT_PASSES = 0xcb02
|
|
53
|
+
FORMAT_DONE = 0xcc15
|
|
54
|
+
PASS = 0x2d
|
|
55
|
+
|
|
56
|
+
def sasi_cpu = settled(@sasi_cpu)
|
|
57
|
+
|
|
58
|
+
def sasi_bus = settled(@sasi_bus)
|
|
59
|
+
|
|
60
|
+
def target = settled(@target)
|
|
61
|
+
|
|
62
|
+
def model_name = "9060"
|
|
63
|
+
|
|
64
|
+
# The heads the drive has, which J14 tells the DOS.
|
|
65
|
+
def heads = 4
|
|
66
|
+
|
|
67
|
+
def boot_cycles = BOOT_CYCLES
|
|
68
|
+
|
|
69
|
+
# Whether the unit is where VICE saves it: both processors between
|
|
70
|
+
# instructions, the host at the start of its job loop.
|
|
71
|
+
def settled_for_vice?
|
|
72
|
+
host = sasi_cpu
|
|
73
|
+
ip_cpu.boundary? && host.boundary? && host.program_counter == Idle::SASI_LOOP
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# +dos_rom+ covers $C000-$FFFF and +sasi_rom+ $F800-$FFFF of the
|
|
77
|
+
# host's, each defaulting to the image in the ROM path. +device+ is
|
|
78
|
+
# the number the jumpers set, 8 to 15. Only the default DOS has its
|
|
79
|
+
# pattern passes skipped.
|
|
80
|
+
def initialize(dos_rom: nil, sasi_rom: nil, host_clock_hz: CLOCK_HZ, device: 8)
|
|
81
|
+
@device = device
|
|
82
|
+
@host_clock_hz = host_clock_hz
|
|
83
|
+
@phase = 0
|
|
84
|
+
@cycles = 0
|
|
85
|
+
@format_trap = dos_rom.nil?
|
|
86
|
+
build_ip(dos_rom || ROM.load("9060/dos-3.0.rom", 0xc000))
|
|
87
|
+
build_sasi(sasi_rom || ROM.read("9060/sasi-300515-revb.rom"))
|
|
88
|
+
@ip_cpu = Model8050::IPCPU.new(@ip_bus)
|
|
89
|
+
@sasi_cpu = SASICPU.new(@sasi_bus)
|
|
90
|
+
init_idle
|
|
91
|
+
@ieee_bus.attach(self)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Whether the activity LED is lit: of the LEDs, bit 1 is the
|
|
95
|
+
# activity LED and bit 2 the status LED.
|
|
96
|
+
def led_on? = leds.anybits?(0x02)
|
|
97
|
+
|
|
98
|
+
# Puts the Storage::D90Image in the drive. Nil takes it out.
|
|
99
|
+
def insert(image)
|
|
100
|
+
settle!
|
|
101
|
+
@target.disk = image
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Puts in the .d90 image at +path+, write-protected with `read_only`.
|
|
105
|
+
# An image of neither drive's size takes this drive's heads.
|
|
106
|
+
def insert_image(path, read_only: false)
|
|
107
|
+
image_heads = Storage::D90Image.heads_for(File.size(path)) || heads
|
|
108
|
+
insert(Storage::D90Image.new(path, read_only:, heads: image_heads))
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# The image in the drive, or nil.
|
|
112
|
+
def disk = @target.disk
|
|
113
|
+
|
|
114
|
+
# Writes reach the image as each Write ends, so there is nothing to
|
|
115
|
+
# store.
|
|
116
|
+
def flush = nil
|
|
117
|
+
|
|
118
|
+
# The bus's IFC line resets both processors and every chip. RAM keeps
|
|
119
|
+
# its contents, and the hard disk's controller runs on.
|
|
120
|
+
def reset!
|
|
121
|
+
settle!
|
|
122
|
+
@data_riot.reset!
|
|
123
|
+
@control_riot.reset!
|
|
124
|
+
@via.reset!
|
|
125
|
+
@ip_cpu.reset!
|
|
126
|
+
@sasi_cpu.reset!
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def inspect
|
|
130
|
+
"#<#{self.class.name} cycles=#{@cycles} ip=(#{@ip_cpu.inspect}) sasi=(#{@sasi_cpu.inspect})>"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
private
|
|
134
|
+
|
|
135
|
+
def run_cycle
|
|
136
|
+
step
|
|
137
|
+
skip_format_passes if @format_trap && @target.fast?
|
|
138
|
+
watch_loops if @idle_skip
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def build_ip(rom)
|
|
142
|
+
@common = Memory.new([], length: 0x1000)
|
|
143
|
+
@ieee_bus = IEEEBus.new
|
|
144
|
+
@ieee_port = Model8050::IEEEPort.new(device: @device, bus: @ieee_bus)
|
|
145
|
+
@data_riot = Model8050::DataRIOT.new(peripheral: Model8050::DataLines.new(@ieee_port))
|
|
146
|
+
@control_riot = Model8050::ControlRIOT.new(peripheral: Model8050::ControlLines.new(@ieee_port))
|
|
147
|
+
@ieee_port.data_riot = @data_riot
|
|
148
|
+
@ieee_port.control_riot = @control_riot
|
|
149
|
+
@ip_bus = Model8050::IPBus.new(rom:, common: @common, data_riot: @data_riot, control_riot: @control_riot)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def build_sasi(rom)
|
|
153
|
+
@target = SASITarget.new
|
|
154
|
+
@lines = SASILines.new(@target, four_heads: heads == 4)
|
|
155
|
+
@via = SASIVIA.new(start: 0x0080, peripheral: @lines)
|
|
156
|
+
@lines.via = @via
|
|
157
|
+
@sasi_bus = SASIBus.new(rom: ROM.new(rom, length: rom.length), common: @common, via: @via)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# One cycle of the board: the controller and the chips, then the IP
|
|
161
|
+
# and the host, each in its own half of the cycle. ATN reaches UE1's
|
|
162
|
+
# PA7 as the port latched it. UE1 pulls the IP's IRQ and the VIA the
|
|
163
|
+
# host's.
|
|
164
|
+
def step
|
|
165
|
+
@control_riot.pa7 = @ieee_port.atn_low?
|
|
166
|
+
@data_riot.cycle!
|
|
167
|
+
@control_riot.cycle!
|
|
168
|
+
@via.cycle!
|
|
169
|
+
@lines.cycle!
|
|
170
|
+
@ip_cpu.irq = @control_riot.irq?
|
|
171
|
+
@ip_cpu.cycle!
|
|
172
|
+
@sasi_cpu.irq = @via.irq?
|
|
173
|
+
@sasi_cpu.cycle!
|
|
174
|
+
@cycles += 1
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Sends the IP from the first pattern pass on to the end of the last.
|
|
178
|
+
def skip_format_passes
|
|
179
|
+
ip = @ip_cpu
|
|
180
|
+
return unless ip.program_counter == FORMAT_PASSES && ip.boundary? && @ip_bus.peek(PASS).zero?
|
|
181
|
+
|
|
182
|
+
@ip_bus.poke(PASS, 6)
|
|
183
|
+
ip.program_counter = FORMAT_DONE
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# The D9090: the D9060's board on a six-head drive, 7.5 MB, with J14
|
|
188
|
+
# telling the DOS so.
|
|
189
|
+
class Model9090 < Model9060
|
|
190
|
+
def model_name = "9090"
|
|
191
|
+
|
|
192
|
+
def heads = 6
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|