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,133 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class KIM1
|
|
5
|
+
class Bus
|
|
6
|
+
# The RAM, the expansion's too, and the last byte on the bus, in a
|
|
7
|
+
# keyed snapshot module (BL-RAM). The 6530s keep their own 64 bytes.
|
|
8
|
+
module KeyedState
|
|
9
|
+
def save_fields(out)
|
|
10
|
+
@ram.save_fields(out, "ram")
|
|
11
|
+
out.int("data", @data)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def load_fields(input)
|
|
15
|
+
@ram.load_fields(input, "ram")
|
|
16
|
+
@data = input.byte("data", @data)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
include KeyedState
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class Panel
|
|
24
|
+
# The panel's fields in a keyed snapshot module (BL-PANEL): the
|
|
25
|
+
# TTY/KB jumper, the key held down, the digit lit and its segments
|
|
26
|
+
# since the cycle they lit, the cycles each segment has been lit,
|
|
27
|
+
# and what each digit last showed and when. The teletype restores
|
|
28
|
+
# the line in.
|
|
29
|
+
module KeyedState
|
|
30
|
+
def save_fields(out)
|
|
31
|
+
out.bool("tty", @tty).int("held", @held).int("digit", @digit).int("segments", @segments)
|
|
32
|
+
out.int("since", @since).ints("lit", @lit).ints("shown", @shown).ints("shown_at", @shown_at)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def load_fields(input)
|
|
36
|
+
@tty = input.bool("tty", false)
|
|
37
|
+
held = input.int("held", -1)
|
|
38
|
+
@held = held.between?(0, KEYS.length - 1) ? held : -1
|
|
39
|
+
digit = input.int("digit", -1)
|
|
40
|
+
@digit = digit.between?(0, DIGITS - 1) ? digit : -1
|
|
41
|
+
@segments = input.masked("segments", 0, 0x7f)
|
|
42
|
+
@since = input.int("since", 0)
|
|
43
|
+
input.ints_into("lit", @lit)
|
|
44
|
+
input.ints_into("shown", @shown)
|
|
45
|
+
input.ints_into("shown_at", @shown_at)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
include KeyedState
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
class Tape
|
|
53
|
+
# The deck's fields in a keyed snapshot module (BL-DECK): whether
|
|
54
|
+
# PLAY or RECORD is down, how far into the tape the deck is, the next
|
|
55
|
+
# of the tape's edges to play and PB7's level from it, the level U2
|
|
56
|
+
# last drove PB7 to, and the tape itself under tape.*: its file's
|
|
57
|
+
# path, and the time from each crossing to the next, from the start.
|
|
58
|
+
# The tape goes back in from the module, and STOP after a recording
|
|
59
|
+
# writes it to that path.
|
|
60
|
+
module KeyedState
|
|
61
|
+
def save_fields(out)
|
|
62
|
+
out.bool("playing", @playing).bool("recording", @recording).int("position", position)
|
|
63
|
+
out.int("next_edge", @next_edge).bool("high", @high).bool("output_high", @output_high)
|
|
64
|
+
crossings = @tape.crossings
|
|
65
|
+
gaps = crossings.each_with_index.map { |time, index| index.zero? ? time : time - crossings[index - 1] }
|
|
66
|
+
out.text("tape.path", @tape.path).ints("tape.gaps", gaps)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def load_fields(input)
|
|
70
|
+
time = 0
|
|
71
|
+
crossings = input.ints("tape.gaps", []).map { |gap| time += gap }
|
|
72
|
+
@tape = Storage::KIMTape.new(input.text("tape.path", ""), crossings:)
|
|
73
|
+
@playing = input.bool("playing", false)
|
|
74
|
+
@recording = input.bool("recording", false) && !@playing
|
|
75
|
+
@stopped_at = input.int("position", 0)
|
|
76
|
+
@started = @clock.cycles
|
|
77
|
+
@edges = @playing ? @tape.edges : []
|
|
78
|
+
@next_edge = input.int("next_edge", 0).clamp(0, @edges.length)
|
|
79
|
+
@high = input.bool("high", true)
|
|
80
|
+
@output_high = input.bool("output_high", true)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
include KeyedState
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# The machine's own fields in a keyed snapshot module (BL-MACHINE):
|
|
88
|
+
# the family, its setup (Snapshot::KIM1Setup), the cycle count, the
|
|
89
|
+
# SST switch and U3's PB7 wiring, and under host.* whether type_keys
|
|
90
|
+
# is typing, the keys it has still to press, by their place in KEY_NAMES, the one held, when
|
|
91
|
+
# the next goes, and the on_init handlers still to run. The badline
|
|
92
|
+
# version that wrote it is written for reports and never read.
|
|
93
|
+
module KeyedState
|
|
94
|
+
# The keys type_keys presses, as host.pending_keys numbers them.
|
|
95
|
+
KEY_NAMES = (Panel::KEYS + %w[ST RS]).freeze
|
|
96
|
+
|
|
97
|
+
# How many more on_init handlers the machine a snapshot was taken of
|
|
98
|
+
# had yet to run than this one has. Nil until the machine is restored.
|
|
99
|
+
attr_reader :init_handlers_lost
|
|
100
|
+
|
|
101
|
+
# Whether a snapshot saves now (Machine::Settling): the CPU between
|
|
102
|
+
# instructions or jammed.
|
|
103
|
+
def settled_for_snapshot? = @cpu.boundary? || @cpu.jammed?
|
|
104
|
+
|
|
105
|
+
# Nothing on the board defers its work.
|
|
106
|
+
def settle_chips_for_snapshot = nil
|
|
107
|
+
|
|
108
|
+
def save_machine_fields(out)
|
|
109
|
+
out.text("family", "kim1").text("written_by", VERSION)
|
|
110
|
+
Snapshot::KIM1Setup.of(self).write_fields(out)
|
|
111
|
+
out.int("cycles", @cycles).bool("sst", @sst).bool("irq_wired", @irq_wired)
|
|
112
|
+
pending = @pending_keys
|
|
113
|
+
out.bool("host.typing", !pending.nil?)
|
|
114
|
+
out.ints("host.pending_keys", pending.map { |name| KEY_NAMES.index(name) }) if pending
|
|
115
|
+
out.bool("host.key_down", @key_down).int("host.next_key_at", @next_key_at)
|
|
116
|
+
out.int("host.init_pending", @cycles <= @init_threshold ? @init_handlers.length : 0)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def load_machine_fields(input)
|
|
120
|
+
@cycles = input.int("cycles", @cycles)
|
|
121
|
+
@sst = input.bool("sst", false)
|
|
122
|
+
@irq_wired = input.bool("irq_wired", false)
|
|
123
|
+
pending = input.ints("host.pending_keys", []).filter_map { |index| KEY_NAMES[index] if index >= 0 }
|
|
124
|
+
@pending_keys = input.bool("host.typing", false) ? pending : nil
|
|
125
|
+
@key_down = input.bool("host.key_down", false)
|
|
126
|
+
@next_key_at = input.int("host.next_key_at", 0)
|
|
127
|
+
@init_handlers_lost = [input.int("host.init_pending", 0) - @init_handlers.length, 0].max
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
include KeyedState
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class KIM1
|
|
5
|
+
# The keypad and display on U2's ports (User Manual, figures 3.5 and
|
|
6
|
+
# 3.6), and the TTY/KB jumper.
|
|
7
|
+
#
|
|
8
|
+
# PB1-PB4 feed a 74145 decoder, U24. Its outputs 0-2 select the
|
|
9
|
+
# keypad's three rows, 3 the jumper, and 4-9 the six LED digits, left
|
|
10
|
+
# to right. PA0-PA6 drive the selected digit's segments a-g, a high
|
|
11
|
+
# line lighting one, and read the selected row's keys, a pressed key
|
|
12
|
+
# pulling its line low: PA6 is the row's first key, PA0 its seventh.
|
|
13
|
+
# Fitted, the jumper pulls PA0 low while output 3 is selected, which
|
|
14
|
+
# the monitor reads as teletype mode, and PA7 takes the teletype's
|
|
15
|
+
# line in (Teletype). PB7 is the cassette's line (Tape).
|
|
16
|
+
#
|
|
17
|
+
# Nothing holds the display lit. A program lights each digit in turn
|
|
18
|
+
# (the monitor's SCANDS, $1F1F), so the panel times how long each
|
|
19
|
+
# segment is lit from the writes to U2's ports: lit_cycles hands the
|
|
20
|
+
# totals to a front end, and display reads back what the digits show.
|
|
21
|
+
class Panel
|
|
22
|
+
# The keys of the matrix, by the code the monitor's GETKEY ($1F6A)
|
|
23
|
+
# gives them: the row times 7 plus the column.
|
|
24
|
+
KEYS = %w[0 1 2 3 4 5 6 7 8 9 A B C D E F AD DA + GO PC].freeze
|
|
25
|
+
|
|
26
|
+
# The monitor's segment patterns for 0-F, its table at $1FE7 without
|
|
27
|
+
# bit 7, which PA7 doesn't drive.
|
|
28
|
+
HEX_SEGMENTS = [0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07,
|
|
29
|
+
0x7f, 0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71].freeze
|
|
30
|
+
|
|
31
|
+
DIGITS = 6
|
|
32
|
+
|
|
33
|
+
# The decoder output of the leftmost digit.
|
|
34
|
+
FIRST_DIGIT = 4
|
|
35
|
+
|
|
36
|
+
# A digit lit for less than this many cycles at a time doesn't count
|
|
37
|
+
# as showing anything: the monitor passes its digits through that
|
|
38
|
+
# quickly between patterns.
|
|
39
|
+
SHOWN_CYCLES = 100
|
|
40
|
+
|
|
41
|
+
# A digit not lit for this many cycles, some five of the monitor's
|
|
42
|
+
# scans, reads as dark.
|
|
43
|
+
DARK_CYCLES = 20_000
|
|
44
|
+
|
|
45
|
+
# Whether the TTY/KB jumper is fitted.
|
|
46
|
+
attr_accessor :tty
|
|
47
|
+
|
|
48
|
+
# Whether the teletype's line in is high, its idle level. Low, it
|
|
49
|
+
# pulls PA7 low while PB5 is low, which gates it through.
|
|
50
|
+
attr_accessor :tty_in_high
|
|
51
|
+
|
|
52
|
+
# The cassette deck on PB7.
|
|
53
|
+
attr_reader :tape
|
|
54
|
+
|
|
55
|
+
# +clock+ answers +cycles+, the cycle each write to the ports lands
|
|
56
|
+
# on.
|
|
57
|
+
def initialize(clock)
|
|
58
|
+
@clock = clock
|
|
59
|
+
@rriot = nil
|
|
60
|
+
@tty = false
|
|
61
|
+
@tty_in_high = true
|
|
62
|
+
@tape = Tape.new(clock)
|
|
63
|
+
@held = -1
|
|
64
|
+
@digit = -1
|
|
65
|
+
@segments = 0
|
|
66
|
+
@since = 0
|
|
67
|
+
@lit = Array.new(DIGITS * 7, 0)
|
|
68
|
+
@shown = Array.new(DIGITS, 0)
|
|
69
|
+
@shown_at = Array.new(DIGITS, -DARK_CYCLES - 1)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# U2, whose port B selects the row the keys are read from.
|
|
73
|
+
def connect(rriot)
|
|
74
|
+
@rriot = rriot
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Holds down the key KEYS names `name`, and lets go of any other.
|
|
78
|
+
def press(name)
|
|
79
|
+
code = KEYS.index(name)
|
|
80
|
+
raise ArgumentError, "no KIM-1 key named #{name}" if code.nil?
|
|
81
|
+
|
|
82
|
+
@held = code
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def release
|
|
86
|
+
@held = -1
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# The name of the key held down, or nil.
|
|
90
|
+
def held = @held.negative? ? nil : KEYS[@held]
|
|
91
|
+
|
|
92
|
+
def read_a(_lines)
|
|
93
|
+
port_b = @rriot.port_b_output
|
|
94
|
+
row = decoded(port_b)
|
|
95
|
+
mask = @tty_in_high || port_b.anybits?(0x20) ? 0xff : 0x7f
|
|
96
|
+
mask &= 0xfe if row == 3 && @tty
|
|
97
|
+
held = @held
|
|
98
|
+
mask &= ~(0x40 >> (held % 7)) & 0xff if held >= 0 && held / 7 == row
|
|
99
|
+
mask
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def read_b(_lines) = @tape.input
|
|
103
|
+
|
|
104
|
+
# The lines U2's ports drive after a write to them.
|
|
105
|
+
def lines_written(port_a, port_b)
|
|
106
|
+
@tape.output(port_b)
|
|
107
|
+
digit = decoded(port_b) - FIRST_DIGIT
|
|
108
|
+
digit = -1 unless digit >= 0 && digit < DIGITS
|
|
109
|
+
segments = digit.negative? ? 0 : port_a & 0x7f
|
|
110
|
+
return if digit == @digit && segments == @segments
|
|
111
|
+
|
|
112
|
+
now = @clock.cycles
|
|
113
|
+
end_lit(now)
|
|
114
|
+
@digit = digit
|
|
115
|
+
@segments = segments
|
|
116
|
+
@since = now
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# The cycles each segment has been lit since the last call, seven to
|
|
120
|
+
# a digit from the left, a to g, and starts counting again.
|
|
121
|
+
def lit_cycles
|
|
122
|
+
now = @clock.cycles
|
|
123
|
+
end_lit(now)
|
|
124
|
+
@since = now
|
|
125
|
+
totals = @lit.dup
|
|
126
|
+
@lit.map! { 0 }
|
|
127
|
+
totals
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# The segments, a in bit 0 to g in bit 6, each digit shows: its
|
|
131
|
+
# pattern the last time it was lit for SHOWN_CYCLES or more, or 0
|
|
132
|
+
# once it has been dark for DARK_CYCLES.
|
|
133
|
+
def display
|
|
134
|
+
now = @clock.cycles
|
|
135
|
+
Array.new(DIGITS) do |digit|
|
|
136
|
+
if digit == @digit && @segments != 0 && now - @since >= SHOWN_CYCLES
|
|
137
|
+
@segments
|
|
138
|
+
elsif now - @shown_at[digit] <= DARK_CYCLES
|
|
139
|
+
@shown[digit]
|
|
140
|
+
else
|
|
141
|
+
0
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# The display as text, such as "0200 A9": a space for a dark digit
|
|
147
|
+
# and ? for a pattern that isn't a hex digit, with a space between
|
|
148
|
+
# the address and the data.
|
|
149
|
+
def display_text
|
|
150
|
+
chars = display.map do |segments|
|
|
151
|
+
next " " if segments.zero?
|
|
152
|
+
|
|
153
|
+
index = HEX_SEGMENTS.index(segments)
|
|
154
|
+
index.nil? ? "?" : index.to_s(16).upcase
|
|
155
|
+
end
|
|
156
|
+
"#{chars[0, 4].join} #{chars[4, 2].join}"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
private
|
|
160
|
+
|
|
161
|
+
def decoded(port_b) = (port_b >> 1) & 0x0f
|
|
162
|
+
|
|
163
|
+
def end_lit(now)
|
|
164
|
+
digit = @digit
|
|
165
|
+
return if digit.negative? || @segments.zero?
|
|
166
|
+
|
|
167
|
+
lit = now - @since
|
|
168
|
+
7.times { |segment| @lit[(digit * 7) + segment] += lit if @segments.anybits?(1 << segment) }
|
|
169
|
+
return if lit < SHOWN_CYCLES
|
|
170
|
+
|
|
171
|
+
@shown[digit] = @segments
|
|
172
|
+
@shown_at[digit] = now
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class KIM1
|
|
5
|
+
# Each 6530 a class of its own, so each keeps one type of peripheral.
|
|
6
|
+
|
|
7
|
+
# U2, the 6530-002: the monitor's ROM, and the ports that drive the
|
|
8
|
+
# keypad, the display, the teletype and the tape. Its peripheral is
|
|
9
|
+
# the Panel, which hears every write to the port and direction
|
|
10
|
+
# registers, so it can time how long each LED segment is lit.
|
|
11
|
+
class MonitorRRIOT < Chips::RRIOT
|
|
12
|
+
def reset!
|
|
13
|
+
super
|
|
14
|
+
@peripheral&.lines_written(port_a_output, port_b_output)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def poke(addr, value)
|
|
18
|
+
super
|
|
19
|
+
@peripheral.lines_written(port_a_output, port_b_output) if addr.nobits?(0x04)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# U3, the 6530-003: the tape routines' ROM, and the ports the
|
|
24
|
+
# application connector carries for the user, with nothing on them.
|
|
25
|
+
class UserRRIOT < Chips::RRIOT
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class KIM1
|
|
5
|
+
# The KIM-1's sound: U2's PB7, the tape output, which programs such as
|
|
6
|
+
# the First Book of KIM's music player toggle, or with the speaker
|
|
7
|
+
# wired, U3's PA0, where the User Manual's chapter 5 puts a speaker.
|
|
8
|
+
# Machine::LineSound turns it into samples.
|
|
9
|
+
class Sound
|
|
10
|
+
include Machine::LineSound
|
|
11
|
+
|
|
12
|
+
# Whether the speaker on U3's PA0 is wired, and plays in place of PB7.
|
|
13
|
+
attr_accessor :speaker
|
|
14
|
+
|
|
15
|
+
def initialize(monitor, user)
|
|
16
|
+
@monitor = monitor
|
|
17
|
+
@user = user
|
|
18
|
+
@speaker = false
|
|
19
|
+
init_line_sound
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def model = :pb7
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def line_high? = @speaker ? @user.port_a_output.anybits?(0x01) : @monitor.port_b_output.anybits?(0x80)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class KIM1
|
|
5
|
+
# The cassette deck on U2's PB7 (User Manual, appendix E). Playing, it
|
|
6
|
+
# drives PB7 as the board's PLL and comparator would from the tape
|
|
7
|
+
# (Storage::KIMTape#edges), which the monitor's LOADT ($1873) reads.
|
|
8
|
+
# Recording, it puts each change of PB7, which DUMPT ($1800) toggles as
|
|
9
|
+
# a square wave, on the tape as a crossing, in place of whatever was
|
|
10
|
+
# on the tape from there on. Stopping a recording writes the tape to
|
|
11
|
+
# its file.
|
|
12
|
+
#
|
|
13
|
+
# Nothing on the board starts or stops the deck: the tape moves while
|
|
14
|
+
# PLAY or RECORD is down, a microsecond, a cycle, at a time.
|
|
15
|
+
class Tape
|
|
16
|
+
attr_reader :tape
|
|
17
|
+
|
|
18
|
+
# +clock+ answers +cycles+.
|
|
19
|
+
def initialize(clock)
|
|
20
|
+
@clock = clock
|
|
21
|
+
@tape = Storage::KIMTape.new
|
|
22
|
+
@playing = false
|
|
23
|
+
@recording = false
|
|
24
|
+
@stopped_at = 0
|
|
25
|
+
@started = 0
|
|
26
|
+
@edges = []
|
|
27
|
+
@next_edge = 0
|
|
28
|
+
@high = true
|
|
29
|
+
@output_high = true
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def playing? = @playing
|
|
33
|
+
|
|
34
|
+
def recording? = @recording
|
|
35
|
+
|
|
36
|
+
# How far into the tape the deck is, in microseconds.
|
|
37
|
+
def position = moving? ? @stopped_at + @clock.cycles - @started : @stopped_at
|
|
38
|
+
|
|
39
|
+
# Puts `tape` in the deck, wound back to its start.
|
|
40
|
+
def insert_tape(tape)
|
|
41
|
+
stop
|
|
42
|
+
@tape = tape
|
|
43
|
+
@stopped_at = 0
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Takes the tape out, leaving a blank one with no file.
|
|
47
|
+
def eject
|
|
48
|
+
insert_tape(Storage::KIMTape.new)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def rewind
|
|
52
|
+
stop
|
|
53
|
+
@stopped_at = 0
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Presses PLAY.
|
|
57
|
+
def play
|
|
58
|
+
stop
|
|
59
|
+
@edges = @tape.edges
|
|
60
|
+
at = @stopped_at
|
|
61
|
+
index = 0
|
|
62
|
+
index += 1 while index < @edges.length && @edges[index] <= at
|
|
63
|
+
@next_edge = index
|
|
64
|
+
@high = index.even?
|
|
65
|
+
start
|
|
66
|
+
@playing = true
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Presses RECORD and PLAY.
|
|
70
|
+
def record
|
|
71
|
+
stop
|
|
72
|
+
@tape.erase_from(@stopped_at)
|
|
73
|
+
start
|
|
74
|
+
@recording = true
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def stop
|
|
78
|
+
finish_recording = @recording
|
|
79
|
+
@stopped_at = position
|
|
80
|
+
@playing = @recording = false
|
|
81
|
+
@tape.save if finish_recording && !@tape.path.empty?
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# PB7 as the deck drives it, in bit 7 of a port B read: the
|
|
85
|
+
# comparator's level while the tape plays, and high otherwise.
|
|
86
|
+
def input
|
|
87
|
+
return 0xff unless @playing
|
|
88
|
+
|
|
89
|
+
at = position
|
|
90
|
+
edges = @edges
|
|
91
|
+
while @next_edge < edges.length && edges[@next_edge] <= at
|
|
92
|
+
@high = !@high
|
|
93
|
+
@next_edge += 1
|
|
94
|
+
end
|
|
95
|
+
@high ? 0xff : 0x7f
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# U2's port B after a write to it, which records a change of PB7.
|
|
99
|
+
def output(port_b)
|
|
100
|
+
high = port_b.anybits?(0x80)
|
|
101
|
+
return if high == @output_high
|
|
102
|
+
|
|
103
|
+
@output_high = high
|
|
104
|
+
@tape.record(position) if @recording
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
def moving? = @playing || @recording
|
|
110
|
+
|
|
111
|
+
def start
|
|
112
|
+
@started = @clock.cycles
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class KIM1
|
|
5
|
+
# A teletype on the KIM-1's 20 mA loop, full duplex, at `baud`
|
|
6
|
+
# (User Manual, section 2.6 and figure 3.7).
|
|
7
|
+
#
|
|
8
|
+
# It sends what #type is given on its line to the board, a start bit,
|
|
9
|
+
# eight data bits from the lowest and two stop bits a character, and
|
|
10
|
+
# the board takes the line in on U2's PA7 while PB5 is low. It prints
|
|
11
|
+
# what the board's printer line carries: PB0, which the monitor drives,
|
|
12
|
+
# and, through the NAND U26, every bit coming in, so the hardware
|
|
13
|
+
# echoes what is typed. It reads each character at its own baud,
|
|
14
|
+
# sampling the middle of each bit from the start bit's edge, a start
|
|
15
|
+
# bit high again by its middle a glitch, and keeps all it has printed
|
|
16
|
+
# in #output, without bit 7.
|
|
17
|
+
#
|
|
18
|
+
# The monitor reads a character only while it waits for one, so the
|
|
19
|
+
# teletype sends the next only once both lines have been quiet for
|
|
20
|
+
# GAP_CHARACTERS characters' time. After a reset it sends RUBOUT, which
|
|
21
|
+
# the monitor times to learn the baud.
|
|
22
|
+
class Teletype
|
|
23
|
+
RUBOUT = 0x7f
|
|
24
|
+
GAP_CHARACTERS = 2
|
|
25
|
+
|
|
26
|
+
# The cycles from a reset to the RUBOUT, by when the monitor waits
|
|
27
|
+
# for its start bit.
|
|
28
|
+
RUBOUT_DELAY = 2_000
|
|
29
|
+
|
|
30
|
+
BAUDS = [110, 300, 1200, 2400, 4800, 9600].freeze
|
|
31
|
+
|
|
32
|
+
# The bits of a character: the start bit, eight data bits and two
|
|
33
|
+
# stop bits.
|
|
34
|
+
FRAME_BITS = 11
|
|
35
|
+
|
|
36
|
+
attr_reader :baud, :output
|
|
37
|
+
|
|
38
|
+
def initialize(panel, baud: 9600)
|
|
39
|
+
@panel = panel
|
|
40
|
+
@queue = []
|
|
41
|
+
@output = +""
|
|
42
|
+
@line_high = true
|
|
43
|
+
@sending = 0
|
|
44
|
+
@sent = FRAME_BITS
|
|
45
|
+
@send_start = 0
|
|
46
|
+
@quiet_until = 0
|
|
47
|
+
@receiving = false
|
|
48
|
+
@received = 0
|
|
49
|
+
@bits = 0
|
|
50
|
+
@edge = 0
|
|
51
|
+
@sample_at = 0
|
|
52
|
+
@last_printer = true
|
|
53
|
+
self.baud = baud
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Sets the rate both ways, which the monitor learns at the next reset.
|
|
57
|
+
def baud=(baud)
|
|
58
|
+
raise ArgumentError, "no teletype at #{baud} baud" unless BAUDS.include?(baud)
|
|
59
|
+
|
|
60
|
+
@baud = baud
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Queues `text`'s bytes to send, its letters in capitals, as a Model
|
|
64
|
+
# 33 types them.
|
|
65
|
+
def type(text)
|
|
66
|
+
text.upcase.bytes.each { |byte| @queue << byte }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Whether characters are queued or on their way in or out.
|
|
70
|
+
def busy? = !@queue.empty? || @sent < FRAME_BITS || @receiving
|
|
71
|
+
|
|
72
|
+
# Whether the line to the board is high, its idle level.
|
|
73
|
+
def line_high? = @line_high
|
|
74
|
+
|
|
75
|
+
# Drops what was on its way in and sends RUBOUT once the monitor
|
|
76
|
+
# is ready for it, at cycle `now`.
|
|
77
|
+
def reset!(now)
|
|
78
|
+
@queue.clear
|
|
79
|
+
@sent = FRAME_BITS
|
|
80
|
+
drive(true)
|
|
81
|
+
@queue << RUBOUT
|
|
82
|
+
@quiet_until = now + RUBOUT_DELAY
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Runs cycle `now`, with the printer line `printer_high`.
|
|
86
|
+
def cycle!(now, printer_high)
|
|
87
|
+
send_bits(now)
|
|
88
|
+
receive(now, printer_high)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# The teletype's fields in a keyed snapshot module (BL-TTY): the
|
|
92
|
+
# baud, what is queued, the character going out and the one coming
|
|
93
|
+
# in, by the cycles they started at, and all it has printed.
|
|
94
|
+
def save_fields(out)
|
|
95
|
+
out.int("baud", @baud).ints("queue", @queue).bytes("output", @output)
|
|
96
|
+
out.bool("line_high", @line_high).int("send.byte", @sending).int("send.bits", @sent)
|
|
97
|
+
out.int("send.start", @send_start).int("quiet_until", @quiet_until)
|
|
98
|
+
out.bool("receive.busy", @receiving).int("receive.byte", @received).int("receive.bits", @bits)
|
|
99
|
+
out.int("receive.edge", @edge).int("receive.sample_at", @sample_at).bool("printer_high", @last_printer)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def load_fields(input)
|
|
103
|
+
baud = input.int("baud", @baud)
|
|
104
|
+
@baud = BAUDS.include?(baud) ? baud : @baud
|
|
105
|
+
@queue = input.ints("queue", []).map { |byte| byte & 0xff }
|
|
106
|
+
@output = input.bytes("output", "".b).dup
|
|
107
|
+
@line_high = input.bool("line_high", true)
|
|
108
|
+
@sending = input.int("send.byte", 0) & 0xff
|
|
109
|
+
@sent = input.int("send.bits", FRAME_BITS).clamp(0, FRAME_BITS)
|
|
110
|
+
@send_start = input.int("send.start", 0)
|
|
111
|
+
@quiet_until = input.int("quiet_until", 0)
|
|
112
|
+
@receiving = input.bool("receive.busy", false)
|
|
113
|
+
@received = input.int("receive.byte", 0) & 0xff
|
|
114
|
+
@bits = input.int("receive.bits", 0).clamp(-1, 8)
|
|
115
|
+
@edge = input.int("receive.edge", 0)
|
|
116
|
+
@sample_at = input.int("receive.sample_at", 0)
|
|
117
|
+
@last_printer = input.bool("printer_high", true)
|
|
118
|
+
@panel.tty_in_high = @line_high
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
private
|
|
122
|
+
|
|
123
|
+
# The cycles `halves` half bits take, rounded up.
|
|
124
|
+
def half_bits(halves) = ((halves * 500_000) + @baud - 1) / @baud
|
|
125
|
+
|
|
126
|
+
# Each bit goes out once its time from the start bit has passed.
|
|
127
|
+
def send_bits(now)
|
|
128
|
+
if @sent >= FRAME_BITS
|
|
129
|
+
start_character(now) if !@queue.empty? && !@receiving && now >= @quiet_until
|
|
130
|
+
elsif now >= @send_start + half_bits(2 * @sent)
|
|
131
|
+
drive(bit_high?(@sent))
|
|
132
|
+
@sent += 1
|
|
133
|
+
@quiet_until = now + half_bits(2 * GAP_CHARACTERS * FRAME_BITS) if @sent == FRAME_BITS
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# The character's eleven bits, the start bit going out now.
|
|
138
|
+
def start_character(now)
|
|
139
|
+
@sending = @queue.shift
|
|
140
|
+
@sent = 0
|
|
141
|
+
@send_start = now
|
|
142
|
+
send_bits(now)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# The start bit low, the data bits from the lowest, the stop bits
|
|
146
|
+
# high.
|
|
147
|
+
def bit_high?(index)
|
|
148
|
+
return false if index.zero?
|
|
149
|
+
return true if index > 8
|
|
150
|
+
|
|
151
|
+
@sending.anybits?(1 << (index - 1))
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def drive(high)
|
|
155
|
+
return if high == @line_high
|
|
156
|
+
|
|
157
|
+
@line_high = high
|
|
158
|
+
@panel.tty_in_high = high
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def receive(now, printer_high)
|
|
162
|
+
if @receiving
|
|
163
|
+
sample(now, printer_high) if now >= @sample_at
|
|
164
|
+
elsif @last_printer && !printer_high
|
|
165
|
+
@receiving = true
|
|
166
|
+
@received = 0
|
|
167
|
+
@bits = -1
|
|
168
|
+
@edge = now
|
|
169
|
+
@sample_at = now + half_bits(1)
|
|
170
|
+
end
|
|
171
|
+
@last_printer = printer_high
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# The middle of the start bit, which has to be low still, each data
|
|
175
|
+
# bit's, then the stop bit's.
|
|
176
|
+
def sample(now, printer_high)
|
|
177
|
+
if @bits.negative?
|
|
178
|
+
@receiving = !printer_high
|
|
179
|
+
@bits = 0
|
|
180
|
+
elsif @bits < 8
|
|
181
|
+
@received |= 1 << @bits if printer_high
|
|
182
|
+
@bits += 1
|
|
183
|
+
else
|
|
184
|
+
@receiving = false
|
|
185
|
+
@output << (@received & 0x7f).chr if printer_high
|
|
186
|
+
@quiet_until = [@quiet_until, now + half_bits(2 * GAP_CHARACTERS * FRAME_BITS)].max
|
|
187
|
+
return
|
|
188
|
+
end
|
|
189
|
+
@sample_at = @edge + half_bits((2 * @bits) + 3)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|