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,179 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "badline/drives/model8050"
|
|
4
|
+
require "badline/drives/model4040/ip_bus"
|
|
5
|
+
require "badline/drives/model4040/parts"
|
|
6
|
+
require "badline/drives/model4040/disk"
|
|
7
|
+
require "badline/drives/model4040/mechanism"
|
|
8
|
+
require "badline/drives/model4040/keyed_state"
|
|
9
|
+
|
|
10
|
+
module Badline
|
|
11
|
+
module Drives
|
|
12
|
+
# The 4040 dual disk drive of the PET and CBM-II, two Shugart SA390
|
|
13
|
+
# drives on one IEEE-488 unit (schematic 320806, the 8050's parent).
|
|
14
|
+
#
|
|
15
|
+
# The board is the 8050's (Model8050::Board) with an older floppy side.
|
|
16
|
+
# A 16 MHz crystal clocks both processors at 1 MHz on opposite phases.
|
|
17
|
+
# The interface processor runs DOS 2.1 from 12 KB of ROM at $D000, with
|
|
18
|
+
# the 8050's 6532s on the bus and the same common RAM, on a bus that
|
|
19
|
+
# doesn't decode A15 (IPBus). The floppy controller is a 6504, with no
|
|
20
|
+
# SO pin, on the 8050's map (Model8050::FDCBus): it runs the program in
|
|
21
|
+
# its 6530 (901466-04) and polls the VIA for byte ready, whose own
|
|
22
|
+
# interrupt line goes nowhere, so only the 6530's timer interrupts it
|
|
23
|
+
# (Mechanism). The disks are the 1541's, 35 tracks of 17 to 21
|
|
24
|
+
# sectors, a .d64 each (Disk).
|
|
25
|
+
#
|
|
26
|
+
# At reset the controller waits for the common RAM's $0400 to read 0,
|
|
27
|
+
# starts, and writes its timer's period there. The interface processor
|
|
28
|
+
# tests its RAM and ROMs, has the controller reset its drives, and
|
|
29
|
+
# bumps both heads to track 1.
|
|
30
|
+
class Model4040
|
|
31
|
+
include Model8050::Board
|
|
32
|
+
include Model8050::Idle
|
|
33
|
+
include Model8050::KeyedState
|
|
34
|
+
|
|
35
|
+
CLOCK_HZ = Model8050::CLOCK_HZ
|
|
36
|
+
|
|
37
|
+
# The host cycles by which a unit switched on with the machine has
|
|
38
|
+
# run the IP's tests and bumped both heads, and reads its jumpers.
|
|
39
|
+
BOOT_CYCLES = 2_500_000
|
|
40
|
+
|
|
41
|
+
# Where DOS 2.1's idle loop and the controller's job loop start over
|
|
42
|
+
# (Model8050::Idle).
|
|
43
|
+
IP_LOOP = 0xd4b7
|
|
44
|
+
FDC_LOOP = 0xfc54
|
|
45
|
+
|
|
46
|
+
def model_name = "4040"
|
|
47
|
+
|
|
48
|
+
def boot_cycles = BOOT_CYCLES
|
|
49
|
+
|
|
50
|
+
def sides = 1
|
|
51
|
+
|
|
52
|
+
# +dos_rom+ covers $D000-$FFFF and +fdc_rom+ is the 6530's mask, each
|
|
53
|
+
# defaulting to the image in the ROM path: the revised DOS 2.1 that
|
|
54
|
+
# xpet's 4040 runs, and 901466-04. +device+ is the number the jumpers
|
|
55
|
+
# set, 8 to 15.
|
|
56
|
+
def initialize(dos_rom: nil, fdc_rom: nil, host_clock_hz: CLOCK_HZ, device: 8)
|
|
57
|
+
@device = device
|
|
58
|
+
@host_clock_hz = host_clock_hz
|
|
59
|
+
@phase = 0
|
|
60
|
+
@cycles = 0
|
|
61
|
+
build_ip(dos_rom || ROM.load(dos_file, dos_start))
|
|
62
|
+
build_fdc(fdc_rom || ROM.read(fdc_file))
|
|
63
|
+
@ip_cpu = IPCPU.new(@ip_bus)
|
|
64
|
+
@fdc_cpu = FDCCPU.new(@fdc_bus)
|
|
65
|
+
init_idle(ip_loop, fdc_loop)
|
|
66
|
+
@ieee_bus.attach(self)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Puts in the .d64 image at +path+, or the DOS 1 disk a .d67 holds,
|
|
70
|
+
# write-protected with `read_only`.
|
|
71
|
+
def insert_image(path, drive: 0, read_only: false)
|
|
72
|
+
image = if File.extname(path).casecmp?(".d67")
|
|
73
|
+
Storage::D67Image.new(path, read_only:)
|
|
74
|
+
else
|
|
75
|
+
Storage::D64Image.new(path, read_only:)
|
|
76
|
+
end
|
|
77
|
+
insert(Disk.new(image), drive:)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def dos_file = "4040/dos-2.1-rev.rom"
|
|
83
|
+
|
|
84
|
+
def dos_start = 0xd000
|
|
85
|
+
|
|
86
|
+
def fdc_file = "4040/fdc-901466-04.rom"
|
|
87
|
+
|
|
88
|
+
def ip_loop = IP_LOOP
|
|
89
|
+
|
|
90
|
+
def fdc_loop = FDC_LOOP
|
|
91
|
+
|
|
92
|
+
def build_ip(rom)
|
|
93
|
+
@common = Memory.new([], length: 0x1000)
|
|
94
|
+
@ieee_bus = IEEEBus.new
|
|
95
|
+
@ieee_port = Model8050::IEEEPort.new(device: @device, bus: @ieee_bus)
|
|
96
|
+
@data_riot = Model8050::DataRIOT.new(peripheral: Model8050::DataLines.new(@ieee_port))
|
|
97
|
+
@control_riot = Model8050::ControlRIOT.new(peripheral: Model8050::ControlLines.new(@ieee_port))
|
|
98
|
+
@ieee_port.data_riot = @data_riot
|
|
99
|
+
@ieee_port.control_riot = @control_riot
|
|
100
|
+
@ip_bus = IPBus.new(rom:, common: @common, data_riot: @data_riot, control_riot: @control_riot)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def build_fdc(rom)
|
|
104
|
+
@mechanism = Mechanism.new
|
|
105
|
+
@via = Model8050::ControllerVIA.new(start: 0x0040, peripheral: ReadLines.new(@mechanism))
|
|
106
|
+
@rriot = Model8050::ControllerRRIOT.new(rom:, peripheral: StatusLines.new(@mechanism))
|
|
107
|
+
@fdc_bus = Model8050::FDCBus.new(common: @common, via: @via, rriot: @rriot)
|
|
108
|
+
@mechanism.via = @via
|
|
109
|
+
@mechanism.rriot = @rriot
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# The disk a BL-D8-DISK module holds (Model8050::KeyedState).
|
|
113
|
+
def disk_from_fields(input, detached) = Disk.from_fields(input, detached:)
|
|
114
|
+
|
|
115
|
+
# One cycle of the board: the drives, then the chips, then the IP and
|
|
116
|
+
# the FDC, each in its own half of the cycle, as on the 8050. Only the
|
|
117
|
+
# 6530 pulls the FDC's IRQ, and the FDC finds byte ready by polling.
|
|
118
|
+
def step
|
|
119
|
+
@mechanism.cycle!
|
|
120
|
+
@control_riot.pa7 = @ieee_port.atn_low?
|
|
121
|
+
@data_riot.cycle!
|
|
122
|
+
@control_riot.cycle!
|
|
123
|
+
@via.cycle!
|
|
124
|
+
@rriot.cycle!
|
|
125
|
+
@ip_cpu.irq = @control_riot.irq?
|
|
126
|
+
@ip_cpu.cycle!
|
|
127
|
+
@fdc_cpu.irq = @rriot.irq?
|
|
128
|
+
@fdc_cpu.cycle!
|
|
129
|
+
@cycles += 1
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# The 3040: the 4040's board with the first DOS 2.1, as xpet's 3040
|
|
134
|
+
# runs it, 901468-11 to -13.
|
|
135
|
+
class Model3040 < Model4040
|
|
136
|
+
# Where the first DOS 2.1's idle loop starts over.
|
|
137
|
+
IP_LOOP = 0xd4b8
|
|
138
|
+
|
|
139
|
+
def model_name = "3040"
|
|
140
|
+
|
|
141
|
+
private
|
|
142
|
+
|
|
143
|
+
def dos_file = "4040/dos-2.1.rom"
|
|
144
|
+
|
|
145
|
+
def ip_loop = IP_LOOP
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# The 2040: the 4040's board with DOS 1.2 in the sockets at $E000 and
|
|
149
|
+
# $F000, UJ1 at $D000 left empty, and 901466-02 in the controller's
|
|
150
|
+
# 6530, as the 3040 with DOS 1 has it too. Its disks are DOS 1's .d67,
|
|
151
|
+
# with 20 sectors on tracks 18 to 24 (Storage::D67Image). The
|
|
152
|
+
# controller starts at once, with no handshake on the common RAM, and
|
|
153
|
+
# the interface processor leaves the common RAM alone at boot.
|
|
154
|
+
class Model2040 < Model4040
|
|
155
|
+
# Where DOS 1.2's idle loop and its controller's job loop start over.
|
|
156
|
+
IP_LOOP = 0xe27e
|
|
157
|
+
FDC_LOOP = 0xfc47
|
|
158
|
+
|
|
159
|
+
def model_name = "2040"
|
|
160
|
+
|
|
161
|
+
# Puts in the .d67 image at +path+, write-protected with `read_only`.
|
|
162
|
+
def insert_image(path, drive: 0, read_only: false)
|
|
163
|
+
insert(Disk.new(Storage::D67Image.new(path, read_only:)), drive:)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
private
|
|
167
|
+
|
|
168
|
+
def dos_file = "4040/dos-1.2.rom"
|
|
169
|
+
|
|
170
|
+
def dos_start = 0xe000
|
|
171
|
+
|
|
172
|
+
def fdc_file = "4040/fdc-901466-02.rom"
|
|
173
|
+
|
|
174
|
+
def ip_loop = IP_LOOP
|
|
175
|
+
|
|
176
|
+
def fdc_loop = FDC_LOOP
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drives
|
|
5
|
+
class Model8050
|
|
6
|
+
# The board of a unit with two processors on the IEEE-488 bus, as the
|
|
7
|
+
# 8050 family and the 4040 family share it: the DOS board's interface
|
|
8
|
+
# processor side (DOSBoard), and the floppy controller with its VIA
|
|
9
|
+
# and 6530 facing the drives. Each unit's class includes it, with
|
|
10
|
+
# Idle and KeyedState, and builds its own chips, so no call it makes
|
|
11
|
+
# on them is a union: the class sets DOSBoard's fields and @fdc_cpu,
|
|
12
|
+
# @fdc_bus, @rriot and @mechanism, and runs one cycle of the board in
|
|
13
|
+
# its own step. While both processors idle, it sleeps (Idle).
|
|
14
|
+
module Board
|
|
15
|
+
include DOSBoard
|
|
16
|
+
|
|
17
|
+
def fdc_cpu = settled(@fdc_cpu)
|
|
18
|
+
|
|
19
|
+
def fdc_bus = settled(@fdc_bus)
|
|
20
|
+
|
|
21
|
+
def rriot = settled(@rriot)
|
|
22
|
+
|
|
23
|
+
def mechanism = settled(@mechanism)
|
|
24
|
+
|
|
25
|
+
# Whether the unit is where VICE saves it: both processors between
|
|
26
|
+
# instructions, the FDC at the start of its idle loop.
|
|
27
|
+
def settled_for_vice?
|
|
28
|
+
fdc = fdc_cpu
|
|
29
|
+
ip_cpu.boundary? && fdc.boundary? && fdc.program_counter == @fdc_loop
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Whether a drive's LED is lit: of the LEDs, bit 0 is drive 1's,
|
|
33
|
+
# bit 1 drive 0's, and bit 2 the error LED.
|
|
34
|
+
def led_on? = leds.anybits?(0x03)
|
|
35
|
+
|
|
36
|
+
# Puts a Disk in +drive+, 0 or 1. Nil takes the disk out.
|
|
37
|
+
def insert(disk, drive: 0)
|
|
38
|
+
settle!
|
|
39
|
+
@mechanism.insert(disk, drive)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# The Disk in +drive+, or nil.
|
|
43
|
+
def disk(drive = 0) = @mechanism.disk(drive)
|
|
44
|
+
|
|
45
|
+
# Stores what the heads wrote in the disks' images, as the motors
|
|
46
|
+
# stopping do.
|
|
47
|
+
def flush
|
|
48
|
+
settle!
|
|
49
|
+
@mechanism.flush
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# The bus's IFC line resets both processors and every chip. RAM keeps
|
|
53
|
+
# its contents.
|
|
54
|
+
def reset!
|
|
55
|
+
settle!
|
|
56
|
+
@data_riot.reset!
|
|
57
|
+
@control_riot.reset!
|
|
58
|
+
@via.reset!
|
|
59
|
+
@rriot.reset!
|
|
60
|
+
@ip_cpu.reset!
|
|
61
|
+
@fdc_cpu.reset!
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def inspect
|
|
65
|
+
"#<#{self.class.name} cycles=#{@cycles} ip=(#{@ip_cpu.inspect}) fdc=(#{@fdc_cpu.inspect})>"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def run_cycle
|
|
71
|
+
step
|
|
72
|
+
watch_loops if @idle_skip
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drives
|
|
5
|
+
class Model8050
|
|
6
|
+
# A .d80 or .d82 image as the floppy controller finds it on the disk:
|
|
7
|
+
# each track a ring of byte cells, each cell ten bit cells the GCR ROM
|
|
8
|
+
# turns into a data byte (Track::SYNC for a run of ones), so the
|
|
9
|
+
# mechanism needs no GCR. A track is laid out as the DOS's format code
|
|
10
|
+
# writes it ($C000-$C363 of DOS 2.7, run on the controller), sector
|
|
11
|
+
# after sector:
|
|
12
|
+
#
|
|
13
|
+
# 4 sync, $08, checksum, sector, track, ID 2, ID 1, $0F,
|
|
14
|
+
# 19 x $0F, 5 sync, $07, 256 data bytes, checksum, then $00
|
|
15
|
+
#
|
|
16
|
+
# the header checksum the EOR of the track, sector and IDs, and the
|
|
17
|
+
# data block's that of its bytes. The $00 gaps share out what the
|
|
18
|
+
# track has left over.
|
|
19
|
+
#
|
|
20
|
+
# Tracks come off the image as the head first reaches them. What the
|
|
21
|
+
# head writes goes back to the image when the motor stops (flush):
|
|
22
|
+
# every data block found on a written track whose header and checksum
|
|
23
|
+
# read cleanly.
|
|
24
|
+
class Disk
|
|
25
|
+
SYNC = 0x100
|
|
26
|
+
|
|
27
|
+
attr_reader :image
|
|
28
|
+
|
|
29
|
+
def initialize(image)
|
|
30
|
+
@image = image
|
|
31
|
+
@tracks = {}
|
|
32
|
+
@written = {}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def write_protected? = @image.read_only?
|
|
36
|
+
|
|
37
|
+
# The image's host file.
|
|
38
|
+
def path = @image.path
|
|
39
|
+
|
|
40
|
+
# How many tracks a side holds: 77.
|
|
41
|
+
def side_tracks = Storage::D80Image::SIDE_TRACKS
|
|
42
|
+
|
|
43
|
+
def sides = @image.is_a?(Storage::D82Image) ? 2 : 1
|
|
44
|
+
|
|
45
|
+
# The cells of +track+, 1 to 77 on the first side and 78 to 154 on
|
|
46
|
+
# the second, or nil past the disk's last.
|
|
47
|
+
def track(number)
|
|
48
|
+
return nil unless number.between?(1, side_tracks * sides)
|
|
49
|
+
|
|
50
|
+
@tracks[number] ||= build(number)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# The cells a track of the zone holds in one turn at 300 rpm: the
|
|
54
|
+
# 200,000 cycles of a turn over the zone's ten bit cells a byte at
|
|
55
|
+
# 6 MHz / (16 - zone).
|
|
56
|
+
def self.cells_in(zone) = 120_000 / (16 - zone)
|
|
57
|
+
|
|
58
|
+
# The zone a track is recorded in, 3 outermost to 0 innermost.
|
|
59
|
+
def self.zone_of(track)
|
|
60
|
+
cylinder = ((track - 1) % Storage::D80Image::SIDE_TRACKS) + 1
|
|
61
|
+
3 - Storage::D80Image::ZONE_LAST_TRACKS.count { |last| cylinder > last }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# The head wrote to +track+.
|
|
65
|
+
def written(number)
|
|
66
|
+
@written[number] = true
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Stores the blocks the head wrote in the image.
|
|
70
|
+
def flush
|
|
71
|
+
return if @written.empty?
|
|
72
|
+
|
|
73
|
+
blocks = []
|
|
74
|
+
@written.each_key { |number| blocks.concat(read_blocks(@tracks[number])) }
|
|
75
|
+
@written.clear
|
|
76
|
+
@image.store_blocks(blocks) unless blocks.empty?
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def build(number)
|
|
82
|
+
id1, id2 = disk_id
|
|
83
|
+
cells = []
|
|
84
|
+
sectors = @image.sectors_in(number)
|
|
85
|
+
sectors.times do |sector|
|
|
86
|
+
data = @image.read_block(number, sector)
|
|
87
|
+
cells.push(SYNC, SYNC, SYNC, SYNC, 0x08, number ^ sector ^ id1 ^ id2, sector, number, id2, id1)
|
|
88
|
+
cells.concat(Array.new(20, 0x0f))
|
|
89
|
+
cells.push(SYNC, SYNC, SYNC, SYNC, SYNC, 0x07)
|
|
90
|
+
cells.concat(data)
|
|
91
|
+
cells << checksum(data)
|
|
92
|
+
end
|
|
93
|
+
pad(cells, sectors, self.class.cells_in(self.class.zone_of(number)))
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Shares the $00 gap out after each sector.
|
|
97
|
+
def pad(cells, sectors, length)
|
|
98
|
+
sector_length = cells.length / sectors
|
|
99
|
+
gap = (length - cells.length) / sectors
|
|
100
|
+
padded = []
|
|
101
|
+
sectors.times do |sector|
|
|
102
|
+
padded.concat(cells[sector * sector_length, sector_length])
|
|
103
|
+
padded.concat(Array.new(sector == sectors - 1 ? length - padded.length : gap, 0x00))
|
|
104
|
+
end
|
|
105
|
+
padded
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def checksum(bytes)
|
|
109
|
+
sum = 0
|
|
110
|
+
bytes.each { |byte| sum ^= byte }
|
|
111
|
+
sum
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# The ID the header block holds at $18.
|
|
115
|
+
def disk_id
|
|
116
|
+
header = @image.read_block(*@image.header_block)
|
|
117
|
+
[header[0x18], header[0x19]]
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Every block whose header and data read cleanly off +cells+, as
|
|
121
|
+
# [track, sector, data, nil].
|
|
122
|
+
def read_blocks(cells)
|
|
123
|
+
blocks = []
|
|
124
|
+
index = 0
|
|
125
|
+
length = cells.length
|
|
126
|
+
while (index = next_block(cells, index, 0x08))
|
|
127
|
+
header = Array.new(5) { |offset| cells[(index + offset) % length] }
|
|
128
|
+
index = next_block(cells, index, 0x07)
|
|
129
|
+
break unless index
|
|
130
|
+
|
|
131
|
+
block = data_block(cells, index, header)
|
|
132
|
+
blocks << block if block
|
|
133
|
+
break if index + 260 >= length
|
|
134
|
+
end
|
|
135
|
+
blocks
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# The cell after the next sync run followed by +mark+, from
|
|
139
|
+
# +index+, or nil.
|
|
140
|
+
def next_block(cells, index, mark)
|
|
141
|
+
length = cells.length
|
|
142
|
+
while index < length
|
|
143
|
+
return index + 2 if cells[index] == SYNC && cells[(index + 1) % length] == mark
|
|
144
|
+
|
|
145
|
+
index += 1
|
|
146
|
+
end
|
|
147
|
+
nil
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def data_block(cells, index, header)
|
|
151
|
+
checksum, sector, track, id2, id1 = header
|
|
152
|
+
return nil unless checksum == (track ^ sector ^ id1 ^ id2)
|
|
153
|
+
|
|
154
|
+
length = cells.length
|
|
155
|
+
data = Array.new(256) { |offset| cells[(index + offset) % length] }
|
|
156
|
+
return nil if data.include?(SYNC) || checksum(data) != cells[(index + 256) % length]
|
|
157
|
+
|
|
158
|
+
[track, sector, data, nil]
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drives
|
|
5
|
+
class Model8050
|
|
6
|
+
# The half of the DOS board every unit with an interface processor
|
|
7
|
+
# shares: the interface processor with its two 6532s on the IEEE-488
|
|
8
|
+
# bus, the common RAM, and the second processor's VIA, whichever side
|
|
9
|
+
# the second processor runs. Board adds the floppy controller's side
|
|
10
|
+
# to it, and Model9060 the SASI host's. Each unit's class sets
|
|
11
|
+
# @ip_cpu, @ip_bus, @common, @data_riot, @control_riot, @via,
|
|
12
|
+
# @ieee_bus and @ieee_port, and has its own run_cycle and settle!.
|
|
13
|
+
#
|
|
14
|
+
# The unit clocks against the host through a phase accumulator, as
|
|
15
|
+
# Drives::Engine::Core does: each host cycle adds 1 MHz of phase, and
|
|
16
|
+
# every whole host period in it runs a cycle of both processors. While
|
|
17
|
+
# both processors idle, it sleeps instead, and the readers of its
|
|
18
|
+
# parts catch it up first.
|
|
19
|
+
module DOSBoard
|
|
20
|
+
attr_reader :device, :ieee_bus
|
|
21
|
+
|
|
22
|
+
def ip_cpu = settled(@ip_cpu)
|
|
23
|
+
|
|
24
|
+
def ip_bus = settled(@ip_bus)
|
|
25
|
+
|
|
26
|
+
def common = settled(@common)
|
|
27
|
+
|
|
28
|
+
def data_riot = settled(@data_riot)
|
|
29
|
+
|
|
30
|
+
def control_riot = settled(@control_riot)
|
|
31
|
+
|
|
32
|
+
def via = settled(@via)
|
|
33
|
+
|
|
34
|
+
def cycles
|
|
35
|
+
settle!
|
|
36
|
+
@cycles
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
attr_writer :host_clock_hz
|
|
40
|
+
|
|
41
|
+
# Plugs the unit into an IEEE-488 bus, leaving the one it was on.
|
|
42
|
+
def connect(ieee_bus)
|
|
43
|
+
settle!
|
|
44
|
+
@ieee_bus.detach(self)
|
|
45
|
+
@ieee_bus = ieee_bus
|
|
46
|
+
@ieee_port.bus = ieee_bus
|
|
47
|
+
ieee_bus.attach(self)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# The lines the unit pulls on the bus while ATN is low or not
|
|
51
|
+
# (IEEEPort).
|
|
52
|
+
def ieee_low_lines(atn) = @ieee_port.ieee_low_lines(atn)
|
|
53
|
+
|
|
54
|
+
# The controller pushed its lines into the bus.
|
|
55
|
+
def host_written! = nil
|
|
56
|
+
|
|
57
|
+
# Which LEDs are lit, as the unit's port drives them. They hold
|
|
58
|
+
# still while the unit sleeps, so reading them leaves it asleep.
|
|
59
|
+
def leds = @ieee_port.leds
|
|
60
|
+
|
|
61
|
+
def power_led_on? = true
|
|
62
|
+
|
|
63
|
+
# Runs the drive cycles that fall in one host cycle, then takes the
|
|
64
|
+
# controller's lines for the next. Asleep, the unit owes the cycles
|
|
65
|
+
# instead, and wakes once the controller has moved a line.
|
|
66
|
+
def host_cycle!
|
|
67
|
+
phase = @phase + CLOCK_HZ
|
|
68
|
+
while phase >= @host_clock_hz
|
|
69
|
+
@asleep ? owe_cycle : run_cycle
|
|
70
|
+
phase -= @host_clock_hz
|
|
71
|
+
end
|
|
72
|
+
@phase = phase
|
|
73
|
+
settle! if @asleep && @ieee_port.lines_moved?
|
|
74
|
+
@ieee_port.latch_host
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def cycle!
|
|
78
|
+
settle!
|
|
79
|
+
run_cycle
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def settled(part)
|
|
85
|
+
settle!
|
|
86
|
+
part
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drives
|
|
5
|
+
class Model8050
|
|
6
|
+
# The floppy controller's bus (schematic 8050001, sheet 2), which
|
|
7
|
+
# decodes A0-A12 only, so it repeats every 8 KB. A 74LS42 on A10-A12
|
|
8
|
+
# picks a 1 KB block:
|
|
9
|
+
#
|
|
10
|
+
# $0000-$03FF - the 6530 and the VIA: with A7 set the 6530's
|
|
11
|
+
# registers, else with A6 set the VIA's, else the
|
|
12
|
+
# 6530's 64 bytes of RAM, which the controller also
|
|
13
|
+
# uses as its stack at $0100
|
|
14
|
+
# $0400-$13FF - the common RAM
|
|
15
|
+
# $1400-$1BFF - nothing: open bus
|
|
16
|
+
# $1C00-$1FFF - the 6530's ROM
|
|
17
|
+
#
|
|
18
|
+
# Writes to the ROM and to open bus go nowhere, and a read from open
|
|
19
|
+
# bus finds the last byte on the data bus.
|
|
20
|
+
class FDCBus
|
|
21
|
+
include Addressable
|
|
22
|
+
|
|
23
|
+
attr_reader :common, :via, :rriot
|
|
24
|
+
attr_accessor :data
|
|
25
|
+
|
|
26
|
+
# How many writes have changed a byte of the common RAM
|
|
27
|
+
# (Model8050::Idle).
|
|
28
|
+
attr_reader :common_changes
|
|
29
|
+
|
|
30
|
+
# Whether a counter or an interrupt flag has been read since it was
|
|
31
|
+
# last cleared (Model8050::Idle).
|
|
32
|
+
attr_accessor :volatile
|
|
33
|
+
|
|
34
|
+
def initialize(common:, via:, rriot:)
|
|
35
|
+
addressable_at(0, length: 2**16)
|
|
36
|
+
@common = common
|
|
37
|
+
@via = via
|
|
38
|
+
@rriot = rriot
|
|
39
|
+
@data = 0
|
|
40
|
+
@volatile = false
|
|
41
|
+
@common_changes = 0
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def peek(addr)
|
|
45
|
+
addr &= 0x1fff
|
|
46
|
+
@data = if addr < 0x0400
|
|
47
|
+
peek_chips(addr)
|
|
48
|
+
elsif addr < 0x1400
|
|
49
|
+
@common.peek(addr - 0x0400)
|
|
50
|
+
elsif addr >= 0x1c00
|
|
51
|
+
@rriot.read_rom(addr)
|
|
52
|
+
else
|
|
53
|
+
@data
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def poke(addr, value)
|
|
58
|
+
@data = value
|
|
59
|
+
addr &= 0x1fff
|
|
60
|
+
if addr < 0x0400
|
|
61
|
+
poke_chips(addr, value)
|
|
62
|
+
elsif addr < 0x1400
|
|
63
|
+
poke_common(addr - 0x0400, value)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def poke_common(index, value)
|
|
70
|
+
@common_changes += 1 if @common.peek(index) != value
|
|
71
|
+
@common.poke(index, value)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def peek_chips(addr)
|
|
75
|
+
if addr.anybits?(0x80)
|
|
76
|
+
@volatile = true if addr.anybits?(0x04)
|
|
77
|
+
return @rriot.peek(addr & 0x0f)
|
|
78
|
+
end
|
|
79
|
+
return peek_via(addr & 0x0f) if addr.anybits?(0x40)
|
|
80
|
+
|
|
81
|
+
@rriot.read_ram(addr)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# The VIA's counters, shift register and flags move on their own.
|
|
85
|
+
def peek_via(register)
|
|
86
|
+
@volatile = true if register.between?(0x04, 0x0a) || register == 0x0d
|
|
87
|
+
@via.peek(0x40 | register)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def poke_chips(addr, value)
|
|
91
|
+
if addr.anybits?(0x80)
|
|
92
|
+
@rriot.poke(addr & 0x0f, value)
|
|
93
|
+
elsif addr.anybits?(0x40)
|
|
94
|
+
@via.poke(0x40 | (addr & 0x0f), value)
|
|
95
|
+
else
|
|
96
|
+
@rriot.write_ram(addr, value)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|