badline 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +153 -0
- data/CLAUDE.md +11 -7
- data/CONTRIBUTING.md +7 -1
- data/README.md +169 -20
- data/doc/pinned-behaviour.md +128 -0
- data/doc/snapshot-format.md +253 -18
- data/doc/snapshots.md +57 -23
- data/exe/badline-ruby +5 -0
- data/lib/badline/audio/terminal.rb +34 -2
- data/lib/badline/c128/keyed_state.rb +2 -0
- data/lib/badline/c128.rb +1 -0
- data/lib/badline/c64/keyed_state.rb +2 -0
- data/lib/badline/c64.rb +1 -0
- data/lib/badline/cbm2/keyed_state.rb +4 -0
- data/lib/badline/cbm2/p500.rb +5 -3
- data/lib/badline/cbm2.rb +6 -3
- data/lib/badline/chips/cia/timer.rb +2 -2
- data/lib/badline/chips/cpu/instruction_set.rb +1 -1
- data/lib/badline/chips/m6809/arithmetic.rb +140 -0
- data/lib/badline/chips/m6809/bus_steps.rb +223 -0
- data/lib/badline/chips/m6809/core.rb +149 -0
- data/lib/badline/chips/m6809/indexed.rb +82 -0
- data/lib/badline/chips/m6809/interrupts.rb +159 -0
- data/lib/badline/chips/m6809/keyed_state.rb +147 -0
- data/lib/badline/chips/m6809/opcodes.rb +159 -0
- data/lib/badline/chips/m6809/operations.rb +258 -0
- data/lib/badline/chips/m6809/plans.rb +143 -0
- data/lib/badline/chips/m6809/registers.rb +82 -0
- data/lib/badline/chips/m6809/stack.rb +140 -0
- data/lib/badline/chips/m6809.rb +13 -0
- data/lib/badline/chips/riot/timer.rb +14 -3
- data/lib/badline/chips/rriot.rb +4 -2
- data/lib/badline/chips/sid/filter.rb +4 -4
- data/lib/badline/chips/sid/waveform.rb +6 -6
- data/lib/badline/chips/vic_i/keyed_parts.rb +7 -2
- data/lib/badline/chips/vic_i.rb +84 -21
- data/lib/badline/data_path.rb +3 -3
- data/lib/badline/drives/ieee_models.rb +54 -0
- data/lib/badline/drives/model1001.rb +37 -0
- data/lib/badline/drives/model2031.rb +6 -0
- data/lib/badline/drives/model4040/disk.rb +62 -0
- data/lib/badline/drives/model4040/ip_bus.rb +46 -0
- data/lib/badline/drives/model4040/keyed_state.rb +57 -0
- data/lib/badline/drives/model4040/mechanism.rb +192 -0
- data/lib/badline/drives/model4040/parts.rb +20 -0
- data/lib/badline/drives/model4040.rb +179 -0
- data/lib/badline/drives/model8050/board.rb +77 -0
- data/lib/badline/drives/model8050/dos_board.rb +91 -0
- data/lib/badline/drives/model8050/idle.rb +11 -7
- data/lib/badline/drives/model8050/keyed_state.rb +5 -2
- data/lib/badline/drives/model8050.rb +18 -130
- data/lib/badline/drives/model9060/idle.rb +149 -0
- data/lib/badline/drives/model9060/keyed_state.rb +123 -0
- data/lib/badline/drives/model9060/parts.rb +98 -0
- data/lib/badline/drives/model9060/sasi_bus.rb +88 -0
- data/lib/badline/drives/model9060/sasi_commands.rb +168 -0
- data/lib/badline/drives/model9060/sasi_target.rb +161 -0
- data/lib/badline/drives/model9060.rb +195 -0
- data/lib/badline/frontend/app.rb +8 -0
- data/lib/badline/frontend/boot.rb +3 -2
- data/lib/badline/frontend/controls.rb +62 -6
- data/lib/badline/frontend/keys.rb +36 -0
- data/lib/badline/frontend/kim1_panel.rb +208 -0
- data/lib/badline/frontend/media_slots.rb +8 -1
- data/lib/badline/frontend/menu/drive_types.rb +56 -0
- data/lib/badline/frontend/menu/menu_dialogs.rb +61 -14
- data/lib/badline/frontend/menu/menu_media.rb +42 -12
- data/lib/badline/frontend/menu/menu_pages.rb +97 -2
- data/lib/badline/frontend/menu/menu_ports.rb +38 -1
- data/lib/badline/frontend/menu/pause_menu.rb +5 -1
- data/lib/badline/frontend/pet_keyboard.rb +23 -1
- data/lib/badline/frontend/screen.rb +1 -1
- data/lib/badline/frontend/screens.rb +40 -2
- data/lib/badline/frontend/teletype_pane.rb +152 -0
- data/lib/badline/frontend/timeline.rb +31 -2
- data/lib/badline/frontend.rb +3 -0
- data/lib/badline/kim1/bus.rb +117 -0
- data/lib/badline/kim1/cpu.rb +25 -0
- data/lib/badline/kim1/host_terminal.rb +74 -0
- data/lib/badline/kim1/key_entry.rb +74 -0
- data/lib/badline/kim1/keyed_modules.rb +53 -0
- data/lib/badline/kim1/keyed_state.rb +133 -0
- data/lib/badline/kim1/panel.rb +176 -0
- data/lib/badline/kim1/rriots.rb +28 -0
- data/lib/badline/kim1/sound.rb +29 -0
- data/lib/badline/kim1/tape.rb +116 -0
- data/lib/badline/kim1/teletype.rb +193 -0
- data/lib/badline/kim1.rb +190 -0
- data/lib/badline/machine/ieee_drives.rb +94 -117
- data/lib/badline/machine/keyboard_buffer.rb +20 -3
- data/lib/badline/machine/line_sound.rb +122 -0
- data/lib/badline/machine.rb +46 -15
- data/lib/badline/media/extensions.rb +8 -4
- data/lib/badline/media/kim1_media.rb +150 -0
- data/lib/badline/media/pet_media.rb +3 -1
- data/lib/badline/media/true_drive.rb +42 -45
- data/lib/badline/media.rb +12 -6
- data/lib/badline/options/family.rb +98 -8
- data/lib/badline/options/help.rb +44 -12
- data/lib/badline/options/table.rb +43 -11
- data/lib/badline/options/validation.rb +44 -8
- data/lib/badline/options.rb +27 -8
- data/lib/badline/pet/bus.rb +16 -2
- data/lib/badline/pet/keyboard_ports.rb +9 -2
- data/lib/badline/pet/keyed_state.rb +4 -0
- data/lib/badline/pet/model.rb +11 -2
- data/lib/badline/pet/rom_sets.rb +1 -0
- data/lib/badline/pet/sound.rb +5 -105
- data/lib/badline/pet/superpet/acia.rb +12 -0
- data/lib/badline/pet/superpet/board.rb +221 -0
- data/lib/badline/pet/superpet/dongle.rb +81 -0
- data/lib/badline/pet/superpet/keyed_modules.rb +63 -0
- data/lib/badline/pet/superpet/keyed_parts.rb +76 -0
- data/lib/badline/pet/superpet/m6809.rb +32 -0
- data/lib/badline/pet/superpet/waterloo_traps.rb +199 -0
- data/lib/badline/pet/superpet.rb +216 -0
- data/lib/badline/pet.rb +19 -3
- data/lib/badline/plus4/keyed_state.rb +2 -0
- data/lib/badline/plus4.rb +1 -0
- data/lib/badline/roms/1001/fdc-251257-02a.rom +0 -0
- data/lib/badline/roms/4040/dos-1.2.rom +0 -0
- data/lib/badline/roms/4040/dos-2.1-rev.rom +0 -0
- data/lib/badline/roms/4040/dos-2.1.rom +0 -0
- data/lib/badline/roms/4040/fdc-901466-02.rom +0 -0
- data/lib/badline/roms/4040/fdc-901466-04.rom +0 -0
- data/lib/badline/roms/9060/dos-3.0.rom +0 -0
- data/lib/badline/roms/9060/sasi-300515-revb.rom +0 -0
- data/lib/badline/roms/README +166 -0
- data/lib/badline/roms/kim1/6530-002.rom +0 -0
- data/lib/badline/roms/kim1/6530-003.rom +0 -0
- data/lib/badline/roms/pet/characters-superpet.rom +0 -0
- data/lib/badline/roms/pet/superpet-a000.rom +0 -0
- data/lib/badline/roms/pet/superpet-c000.rom +0 -0
- data/lib/badline/roms/pet/superpet-e000-50hz.rom +0 -0
- data/lib/badline/roms/pet/superpet-e000.rom +0 -0
- data/lib/badline/snapshot/container.rb +2 -0
- data/lib/badline/snapshot/field_doc/markdown.rb +1 -1
- data/lib/badline/snapshot/field_doc.rb +27 -6
- data/lib/badline/snapshot/image.rb +4 -1
- data/lib/badline/snapshot/kim1_setup.rb +28 -0
- data/lib/badline/snapshot/pet_setup.rb +27 -11
- data/lib/badline/snapshot/storage_setup.rb +4 -1
- data/lib/badline/snapshot/vic20_setup.rb +11 -7
- data/lib/badline/snapshot/vice/pet.rb +13 -7
- data/lib/badline/snapshot/vice/pet_cpu.rb +39 -5
- data/lib/badline/snapshot/vice/pet_crtc.rb +15 -2
- data/lib/badline/snapshot/vice/pet_drive8050.rb +188 -68
- data/lib/badline/snapshot/vice/pet_mem.rb +49 -5
- data/lib/badline/snapshot/vice/pet_restore.rb +40 -11
- data/lib/badline/snapshot.rb +4 -0
- data/lib/badline/status.rb +30 -30
- data/lib/badline/storage/d67_image.rb +55 -0
- data/lib/badline/storage/d90_image.rb +180 -0
- data/lib/badline/storage/disk_image/writing.rb +2 -2
- data/lib/badline/storage/kim_tape.rb +254 -0
- data/lib/badline/storage/papertape.rb +90 -0
- data/lib/badline/storage.rb +4 -0
- data/lib/badline/support/kim1_checkpoint.rb +41 -0
- data/lib/badline/support/superpet_checkpoint.rb +35 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic20/keyed_modules.rb +6 -3
- data/lib/badline/vic20/keyed_state.rb +6 -4
- data/lib/badline/vic20.rb +26 -12
- data/lib/badline.rb +1 -0
- metadata +77 -1
|
@@ -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
|
data/lib/badline/kim1.rb
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "badline/kim1/cpu"
|
|
4
|
+
require "badline/kim1/rriots"
|
|
5
|
+
require "badline/kim1/tape"
|
|
6
|
+
require "badline/kim1/panel"
|
|
7
|
+
require "badline/kim1/key_entry"
|
|
8
|
+
require "badline/kim1/bus"
|
|
9
|
+
require "badline/kim1/sound"
|
|
10
|
+
require "badline/kim1/teletype"
|
|
11
|
+
require "badline/kim1/host_terminal"
|
|
12
|
+
|
|
13
|
+
module Badline
|
|
14
|
+
# The MOS KIM-1: a 6502 at 1 MHz, 1K of RAM and two 6530 RRIOTs, U2
|
|
15
|
+
# with the monitor in its ROM and U3 with the tape routines (KIM1::Bus).
|
|
16
|
+
# U2's ports drive the keypad and the six LED digits (Panel), and its PB7
|
|
17
|
+
# the audio output (Sound) and the cassette (Tape). Nothing pulls IRQ
|
|
18
|
+
# unless U3's PB7 is wired to it, as the User Manual's appendix H has the
|
|
19
|
+
# user do for U3's timer.
|
|
20
|
+
#
|
|
21
|
+
# RS resets the CPU and both 6530s, and ST pulls NMI, which the monitor
|
|
22
|
+
# leaves to the vector at $17FA: $1C00 there saves the registers and
|
|
23
|
+
# shows the PC. With the SST switch on, every instruction outside the
|
|
24
|
+
# monitor ends in an NMI (CPU#single_step). With the TTY/KB jumper
|
|
25
|
+
# fitted, the monitor talks to a teletype (Teletype) in place of the
|
|
26
|
+
# keypad and display. It runs headless.
|
|
27
|
+
class KIM1
|
|
28
|
+
include Machine::Clocking
|
|
29
|
+
include Machine::Settling
|
|
30
|
+
include KeyEntry
|
|
31
|
+
|
|
32
|
+
# The cycle by which the monitor is scanning the display, with a
|
|
33
|
+
# margin.
|
|
34
|
+
BOOT_CYCLES = 10_000
|
|
35
|
+
|
|
36
|
+
attr_reader :bus, :cpu, :u2, :u3, :panel, :teletype, :cycles, :init_threshold
|
|
37
|
+
|
|
38
|
+
# Whether the SST switch is on.
|
|
39
|
+
attr_accessor :sst
|
|
40
|
+
|
|
41
|
+
# Whether U3's PB7, its timer's interrupt, is wired to IRQ.
|
|
42
|
+
attr_accessor :irq_wired
|
|
43
|
+
|
|
44
|
+
# The 1 MHz clock. With no video, a frame is nominal: 64 by 260
|
|
45
|
+
# cycles, some 60 a second.
|
|
46
|
+
TIMING = Timing.new(clock_hz: 1_000_000, cycles_per_line: 64, lines_per_frame: 260, crop: [0, 0, 0, 0],
|
|
47
|
+
pixel_width: 1, dots: 1)
|
|
48
|
+
|
|
49
|
+
def family = :kim1
|
|
50
|
+
|
|
51
|
+
def ram = @bus.ram
|
|
52
|
+
|
|
53
|
+
# Reads and writes memory as the CPU sees it, through the bus.
|
|
54
|
+
def peek(addr) = @bus.peek(addr)
|
|
55
|
+
|
|
56
|
+
def poke(addr, value) = @bus.poke(addr, value)
|
|
57
|
+
|
|
58
|
+
# With +tty+ the TTY/KB jumper is fitted, and the monitor talks to a
|
|
59
|
+
# teletype at +baud+ (Teletype::BAUDS) in place of the keypad and
|
|
60
|
+
# display. +ram+ names the RAM, one of Bus::RAM_CONFIGURATIONS, or
|
|
61
|
+
# nil for the stock 1K.
|
|
62
|
+
def initialize(tty: false, baud: 9600, ram: nil)
|
|
63
|
+
@cycles = 0
|
|
64
|
+
@panel = Panel.new(self)
|
|
65
|
+
@panel.tty = tty
|
|
66
|
+
@u2 = MonitorRRIOT.new(rom: ROM.read("kim1/6530-002.rom"), peripheral: @panel)
|
|
67
|
+
@u3 = UserRRIOT.new(rom: ROM.read("kim1/6530-003.rom"))
|
|
68
|
+
@panel.connect(@u2)
|
|
69
|
+
@bus = Bus.new(@u2, @u3, ram: ram || :"1k")
|
|
70
|
+
@cpu = CPU.new(@bus)
|
|
71
|
+
@sst = false
|
|
72
|
+
@irq_wired = false
|
|
73
|
+
@sound = Sound.new(@u2, @u3)
|
|
74
|
+
@teletype = Teletype.new(@panel, baud:)
|
|
75
|
+
@teletype.reset!(0) if tty
|
|
76
|
+
@init_handlers = []
|
|
77
|
+
@init_threshold = BOOT_CYCLES
|
|
78
|
+
@pending_keys = nil
|
|
79
|
+
@key_down = false
|
|
80
|
+
@next_key_at = 0
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Whether the TTY/KB jumper is fitted.
|
|
84
|
+
def tty? = @panel.tty
|
|
85
|
+
|
|
86
|
+
# Fits or takes out the TTY/KB jumper.
|
|
87
|
+
def tty=(fitted)
|
|
88
|
+
@panel.tty = fitted
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def timing = TIMING
|
|
92
|
+
|
|
93
|
+
# Types `text` on the teletype with the jumper fitted, and otherwise
|
|
94
|
+
# presses the keys it names (KeyEntry#type_keys).
|
|
95
|
+
def type_text(text)
|
|
96
|
+
return type_keys(text) unless @panel.tty
|
|
97
|
+
|
|
98
|
+
@teletype.type(text)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# What the teletype has printed: #output holds it all.
|
|
102
|
+
def capture_output = @teletype
|
|
103
|
+
|
|
104
|
+
# What a front end plays: U2's PB7 or the speaker (Sound), which
|
|
105
|
+
# answers #record and #drain_samples.
|
|
106
|
+
def sound_source = @sound
|
|
107
|
+
|
|
108
|
+
# Whether the speaker on U3's PA0 is wired, and plays in place of PB7.
|
|
109
|
+
def speaker? = @sound.speaker
|
|
110
|
+
|
|
111
|
+
def speaker=(wired)
|
|
112
|
+
@sound.speaker = wired
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# The machine as the pause menu names it.
|
|
116
|
+
def model_label = "KIM-1, #{ram_kb}K RAM"
|
|
117
|
+
|
|
118
|
+
# The RAM in K (Bus#ram_kb).
|
|
119
|
+
def ram_kb = @bus.ram_kb
|
|
120
|
+
|
|
121
|
+
# No drive plugs into the KIM-1, and nothing is mounted.
|
|
122
|
+
def true_drive = nil
|
|
123
|
+
|
|
124
|
+
def mounted? = false
|
|
125
|
+
|
|
126
|
+
def mounted_path = ""
|
|
127
|
+
|
|
128
|
+
# The 6530s count ahead of the CPU. With SST on, the board pulls NMI
|
|
129
|
+
# ahead of an opcode fetch outside the monitor.
|
|
130
|
+
def cycle!
|
|
131
|
+
handle_init if @cycles == @init_threshold
|
|
132
|
+
feed_keys unless @pending_keys.nil?
|
|
133
|
+
|
|
134
|
+
@u2.cycle!
|
|
135
|
+
@u3.cycle!
|
|
136
|
+
@sound.cycle! if @sound.recording?
|
|
137
|
+
@teletype.cycle!(@cycles, @u2.port_b_output.anybits?(0x01) && @teletype.line_high?) if @panel.tty
|
|
138
|
+
@cpu.irq = @irq_wired && @u3.irq?
|
|
139
|
+
@cpu.single_step if @sst
|
|
140
|
+
@cpu.cycle!
|
|
141
|
+
|
|
142
|
+
@cycles += 1
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# RS: the RST line resets the CPU and both 6530s. RAM keeps what it
|
|
146
|
+
# holds. A teletype then sends RUBOUT for the monitor to time.
|
|
147
|
+
def reset!
|
|
148
|
+
@u2.reset!
|
|
149
|
+
@u3.reset!
|
|
150
|
+
@cpu.reset!
|
|
151
|
+
@teletype.reset!(@cycles) if @panel.tty
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
alias press_reset reset!
|
|
155
|
+
|
|
156
|
+
# Switches the machine off and on: RAM starts cleared, and RS resets
|
|
157
|
+
# everything else.
|
|
158
|
+
def power_cycle!
|
|
159
|
+
@bus.power_on!
|
|
160
|
+
reset!
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# ST: an edge on NMI.
|
|
164
|
+
def press_stop
|
|
165
|
+
@cpu.nmi = true
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Holds down the key Panel::KEYS names `name`.
|
|
169
|
+
def press_key(name)
|
|
170
|
+
@panel.press(name)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def release_key
|
|
174
|
+
@panel.release
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# The segments each LED digit shows (Panel#display).
|
|
178
|
+
def display = @panel.display
|
|
179
|
+
|
|
180
|
+
# The display as text, such as "0200 A9" (Panel#display_text).
|
|
181
|
+
def display_text = @panel.display_text
|
|
182
|
+
|
|
183
|
+
# The cassette deck on U2's PB7.
|
|
184
|
+
def tape = @panel.tape
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
require "badline/snapshot/kim1_setup"
|
|
189
|
+
require "badline/kim1/keyed_state"
|
|
190
|
+
require "badline/kim1/keyed_modules"
|