badline 0.17.0 → 0.19.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.
Files changed (195) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +193 -0
  3. data/CLAUDE.md +19 -7
  4. data/CONTRIBUTING.md +11 -1
  5. data/README.md +178 -26
  6. data/doc/library.md +5 -1
  7. data/doc/media.md +3 -2
  8. data/doc/pinned-behaviour.md +169 -1
  9. data/doc/snapshot-format.md +302 -35
  10. data/doc/snapshots.md +97 -26
  11. data/doc/vice.md +344 -0
  12. data/exe/badline-ruby +5 -0
  13. data/lib/badline/audio/renderer.rb +4 -8
  14. data/lib/badline/audio/terminal.rb +34 -2
  15. data/lib/badline/c128/keyed_state.rb +2 -0
  16. data/lib/badline/c128.rb +5 -0
  17. data/lib/badline/c64/bus/fittings.rb +6 -0
  18. data/lib/badline/c64/keyed_state.rb +2 -0
  19. data/lib/badline/c64.rb +7 -0
  20. data/lib/badline/cbm2/keyed_state.rb +4 -0
  21. data/lib/badline/cbm2/p500.rb +9 -3
  22. data/lib/badline/cbm2.rb +10 -3
  23. data/lib/badline/chips/cia/timer.rb +2 -2
  24. data/lib/badline/chips/cpu/core.rb +29 -4
  25. data/lib/badline/chips/cpu/instruction_set.rb +1 -1
  26. data/lib/badline/chips/cpu/interrupts.rb +9 -0
  27. data/lib/badline/chips/cpu/keyed_state.rb +15 -9
  28. data/lib/badline/chips/cpu/microcode.rb +10 -0
  29. data/lib/badline/chips/m6809/arithmetic.rb +140 -0
  30. data/lib/badline/chips/m6809/bus_steps.rb +223 -0
  31. data/lib/badline/chips/m6809/core.rb +149 -0
  32. data/lib/badline/chips/m6809/indexed.rb +82 -0
  33. data/lib/badline/chips/m6809/interrupts.rb +159 -0
  34. data/lib/badline/chips/m6809/keyed_state.rb +147 -0
  35. data/lib/badline/chips/m6809/opcodes.rb +159 -0
  36. data/lib/badline/chips/m6809/operations.rb +258 -0
  37. data/lib/badline/chips/m6809/plans.rb +143 -0
  38. data/lib/badline/chips/m6809/registers.rb +82 -0
  39. data/lib/badline/chips/m6809/stack.rb +140 -0
  40. data/lib/badline/chips/m6809.rb +13 -0
  41. data/lib/badline/chips/riot/timer.rb +14 -3
  42. data/lib/badline/chips/rriot.rb +8 -3
  43. data/lib/badline/chips/sid/decimator.rb +1 -1
  44. data/lib/badline/chips/sid/filter.rb +4 -4
  45. data/lib/badline/chips/sid/keyed_parts.rb +51 -18
  46. data/lib/badline/chips/sid/waveform.rb +6 -6
  47. data/lib/badline/chips/sid.rb +6 -2
  48. data/lib/badline/chips/vic_i/keyed_parts.rb +12 -2
  49. data/lib/badline/chips/vic_i/light_pen.rb +89 -0
  50. data/lib/badline/chips/vic_i.rb +91 -23
  51. data/lib/badline/data_path.rb +3 -3
  52. data/lib/badline/drives/ieee_models.rb +54 -0
  53. data/lib/badline/drives/model1001.rb +37 -0
  54. data/lib/badline/drives/model1541/sector_reader.rb +20 -3
  55. data/lib/badline/drives/model1541.rb +1 -0
  56. data/lib/badline/drives/model1551.rb +1 -0
  57. data/lib/badline/drives/model1571.rb +1 -0
  58. data/lib/badline/drives/model1581.rb +1 -0
  59. data/lib/badline/drives/model2031.rb +7 -0
  60. data/lib/badline/drives/model4040/disk.rb +62 -0
  61. data/lib/badline/drives/model4040/ip_bus.rb +46 -0
  62. data/lib/badline/drives/model4040/keyed_state.rb +57 -0
  63. data/lib/badline/drives/model4040/mechanism.rb +195 -0
  64. data/lib/badline/drives/model4040/parts.rb +20 -0
  65. data/lib/badline/drives/model4040.rb +195 -0
  66. data/lib/badline/drives/model8050/board.rb +79 -0
  67. data/lib/badline/drives/model8050/dos_board.rb +134 -0
  68. data/lib/badline/drives/model8050/fdc_bus.rb +7 -0
  69. data/lib/badline/drives/model8050/idle.rb +148 -31
  70. data/lib/badline/drives/model8050/keyed_state.rb +5 -2
  71. data/lib/badline/drives/model8050.rb +32 -130
  72. data/lib/badline/drives/model9060/idle.rb +169 -0
  73. data/lib/badline/drives/model9060/keyed_state.rb +123 -0
  74. data/lib/badline/drives/model9060/parts.rb +98 -0
  75. data/lib/badline/drives/model9060/sasi_bus.rb +88 -0
  76. data/lib/badline/drives/model9060/sasi_commands.rb +168 -0
  77. data/lib/badline/drives/model9060/sasi_target.rb +161 -0
  78. data/lib/badline/drives/model9060.rb +197 -0
  79. data/lib/badline/frontend/app.rb +8 -0
  80. data/lib/badline/frontend/boot.rb +3 -2
  81. data/lib/badline/frontend/controls.rb +62 -6
  82. data/lib/badline/frontend/keys.rb +36 -0
  83. data/lib/badline/frontend/kim1_panel.rb +208 -0
  84. data/lib/badline/frontend/media_slots.rb +8 -1
  85. data/lib/badline/frontend/menu/drive_types.rb +56 -0
  86. data/lib/badline/frontend/menu/menu_dialogs.rb +61 -14
  87. data/lib/badline/frontend/menu/menu_media.rb +42 -12
  88. data/lib/badline/frontend/menu/menu_pages.rb +97 -2
  89. data/lib/badline/frontend/menu/menu_ports.rb +38 -1
  90. data/lib/badline/frontend/menu/pause_menu.rb +5 -1
  91. data/lib/badline/frontend/pet_keyboard.rb +23 -1
  92. data/lib/badline/frontend/screen.rb +1 -1
  93. data/lib/badline/frontend/screens.rb +40 -2
  94. data/lib/badline/frontend/teletype_pane.rb +152 -0
  95. data/lib/badline/frontend/timeline.rb +31 -2
  96. data/lib/badline/frontend.rb +3 -0
  97. data/lib/badline/input/joystick.rb +15 -2
  98. data/lib/badline/kim1/bus.rb +117 -0
  99. data/lib/badline/kim1/cpu.rb +25 -0
  100. data/lib/badline/kim1/host_terminal.rb +74 -0
  101. data/lib/badline/kim1/key_entry.rb +74 -0
  102. data/lib/badline/kim1/keyed_modules.rb +53 -0
  103. data/lib/badline/kim1/keyed_state.rb +133 -0
  104. data/lib/badline/kim1/panel.rb +176 -0
  105. data/lib/badline/kim1/rriots.rb +28 -0
  106. data/lib/badline/kim1/sound.rb +29 -0
  107. data/lib/badline/kim1/tape.rb +118 -0
  108. data/lib/badline/kim1/teletype.rb +193 -0
  109. data/lib/badline/kim1.rb +194 -0
  110. data/lib/badline/machine/ieee_drives.rb +94 -117
  111. data/lib/badline/machine/keyboard_buffer.rb +20 -3
  112. data/lib/badline/machine/line_sound.rb +122 -0
  113. data/lib/badline/machine.rb +49 -15
  114. data/lib/badline/media/extensions.rb +8 -4
  115. data/lib/badline/media/kim1_media.rb +150 -0
  116. data/lib/badline/media/pet_media.rb +3 -1
  117. data/lib/badline/media/true_drive.rb +52 -45
  118. data/lib/badline/media/vic20_media.rb +4 -2
  119. data/lib/badline/media.rb +16 -6
  120. data/lib/badline/memory.rb +2 -4
  121. data/lib/badline/options/family.rb +98 -8
  122. data/lib/badline/options/help.rb +44 -12
  123. data/lib/badline/options/table.rb +44 -12
  124. data/lib/badline/options/validation.rb +44 -8
  125. data/lib/badline/options.rb +27 -8
  126. data/lib/badline/pet/bus.rb +16 -2
  127. data/lib/badline/pet/keyboard_ports.rb +9 -2
  128. data/lib/badline/pet/keyed_state.rb +4 -0
  129. data/lib/badline/pet/model.rb +11 -2
  130. data/lib/badline/pet/rom_sets.rb +1 -0
  131. data/lib/badline/pet/sound.rb +5 -105
  132. data/lib/badline/pet/superpet/acia.rb +12 -0
  133. data/lib/badline/pet/superpet/board.rb +221 -0
  134. data/lib/badline/pet/superpet/dongle.rb +92 -0
  135. data/lib/badline/pet/superpet/keyed_modules.rb +63 -0
  136. data/lib/badline/pet/superpet/keyed_parts.rb +76 -0
  137. data/lib/badline/pet/superpet/m6809.rb +32 -0
  138. data/lib/badline/pet/superpet/waterloo_traps.rb +199 -0
  139. data/lib/badline/pet/superpet.rb +221 -0
  140. data/lib/badline/pet.rb +23 -3
  141. data/lib/badline/plus4/keyed_state.rb +2 -0
  142. data/lib/badline/plus4.rb +5 -0
  143. data/lib/badline/roms/1001/fdc-251257-02a.rom +0 -0
  144. data/lib/badline/roms/4040/dos-1.2.rom +0 -0
  145. data/lib/badline/roms/4040/dos-2.1-rev.rom +0 -0
  146. data/lib/badline/roms/4040/dos-2.1.rom +0 -0
  147. data/lib/badline/roms/4040/fdc-901466-02.rom +0 -0
  148. data/lib/badline/roms/4040/fdc-901466-04.rom +0 -0
  149. data/lib/badline/roms/9060/dos-3.0.rom +0 -0
  150. data/lib/badline/roms/9060/sasi-300515-revb.rom +0 -0
  151. data/lib/badline/roms/README +176 -0
  152. data/lib/badline/roms/kim1/6530-002.rom +0 -0
  153. data/lib/badline/roms/kim1/6530-003.rom +0 -0
  154. data/lib/badline/roms/pet/characters-superpet.rom +0 -0
  155. data/lib/badline/roms/pet/superpet-a000.rom +0 -0
  156. data/lib/badline/roms/pet/superpet-c000.rom +0 -0
  157. data/lib/badline/roms/pet/superpet-e000-50hz.rom +0 -0
  158. data/lib/badline/roms/pet/superpet-e000.rom +0 -0
  159. data/lib/badline/snapshot/container.rb +8 -4
  160. data/lib/badline/snapshot/field_doc/markdown.rb +1 -1
  161. data/lib/badline/snapshot/field_doc.rb +35 -10
  162. data/lib/badline/snapshot/image.rb +19 -7
  163. data/lib/badline/snapshot/kim1_setup.rb +28 -0
  164. data/lib/badline/snapshot/pet_setup.rb +27 -11
  165. data/lib/badline/snapshot/storage_setup.rb +4 -1
  166. data/lib/badline/snapshot/vic20_setup.rb +11 -7
  167. data/lib/badline/snapshot/vice/pet.rb +14 -7
  168. data/lib/badline/snapshot/vice/pet_cpu.rb +39 -5
  169. data/lib/badline/snapshot/vice/pet_crtc.rb +15 -2
  170. data/lib/badline/snapshot/vice/pet_dongle.rb +72 -0
  171. data/lib/badline/snapshot/vice/pet_drive8050.rb +188 -68
  172. data/lib/badline/snapshot/vice/pet_mem.rb +55 -5
  173. data/lib/badline/snapshot/vice/pet_restore.rb +40 -11
  174. data/lib/badline/snapshot/vice/vic20.rb +70 -0
  175. data/lib/badline/snapshot/vice/vic20_cpu.rb +61 -0
  176. data/lib/badline/snapshot/vice/vic20_mem.rb +85 -0
  177. data/lib/badline/snapshot/vice/vic20_restore.rb +124 -0
  178. data/lib/badline/snapshot/vice/vic20_vici.rb +200 -0
  179. data/lib/badline/snapshot/vice.rb +1 -0
  180. data/lib/badline/snapshot.rb +8 -4
  181. data/lib/badline/status.rb +30 -30
  182. data/lib/badline/storage/d67_image.rb +55 -0
  183. data/lib/badline/storage/d90_image.rb +180 -0
  184. data/lib/badline/storage/kim_tape.rb +254 -0
  185. data/lib/badline/storage/papertape.rb +90 -0
  186. data/lib/badline/storage.rb +4 -0
  187. data/lib/badline/support/kim1_checkpoint.rb +41 -0
  188. data/lib/badline/support/superpet_checkpoint.rb +35 -0
  189. data/lib/badline/version.rb +1 -1
  190. data/lib/badline/vic20/keyed_modules.rb +6 -3
  191. data/lib/badline/vic20/keyed_state.rb +6 -4
  192. data/lib/badline/vic20/port_wiring.rb +11 -0
  193. data/lib/badline/vic20.rb +30 -12
  194. data/lib/badline.rb +1 -0
  195. metadata +85 -1
@@ -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,118 @@
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. DUMPT's half periods run 135 to 139 cycles for the 3,700 Hz
12
+ # tone and 203 to 209 for the 2,400 Hz one, a bit taking about 7.38 ms,
13
+ # where the User Manual's figure E-1 gives 7.452.
14
+ #
15
+ # Nothing on the board starts or stops the deck: the tape moves while
16
+ # PLAY or RECORD is down, a microsecond, a cycle, at a time.
17
+ class Tape
18
+ attr_reader :tape
19
+
20
+ # +clock+ answers +cycles+.
21
+ def initialize(clock)
22
+ @clock = clock
23
+ @tape = Storage::KIMTape.new
24
+ @playing = false
25
+ @recording = false
26
+ @stopped_at = 0
27
+ @started = 0
28
+ @edges = []
29
+ @next_edge = 0
30
+ @high = true
31
+ @output_high = true
32
+ end
33
+
34
+ def playing? = @playing
35
+
36
+ def recording? = @recording
37
+
38
+ # How far into the tape the deck is, in microseconds.
39
+ def position = moving? ? @stopped_at + @clock.cycles - @started : @stopped_at
40
+
41
+ # Puts `tape` in the deck, wound back to its start.
42
+ def insert_tape(tape)
43
+ stop
44
+ @tape = tape
45
+ @stopped_at = 0
46
+ end
47
+
48
+ # Takes the tape out, leaving a blank one with no file.
49
+ def eject
50
+ insert_tape(Storage::KIMTape.new)
51
+ end
52
+
53
+ def rewind
54
+ stop
55
+ @stopped_at = 0
56
+ end
57
+
58
+ # Presses PLAY.
59
+ def play
60
+ stop
61
+ @edges = @tape.edges
62
+ at = @stopped_at
63
+ index = 0
64
+ index += 1 while index < @edges.length && @edges[index] <= at
65
+ @next_edge = index
66
+ @high = index.even?
67
+ start
68
+ @playing = true
69
+ end
70
+
71
+ # Presses RECORD and PLAY.
72
+ def record
73
+ stop
74
+ @tape.erase_from(@stopped_at)
75
+ start
76
+ @recording = true
77
+ end
78
+
79
+ def stop
80
+ finish_recording = @recording
81
+ @stopped_at = position
82
+ @playing = @recording = false
83
+ @tape.save if finish_recording && !@tape.path.empty?
84
+ end
85
+
86
+ # PB7 as the deck drives it, in bit 7 of a port B read: the
87
+ # comparator's level while the tape plays, and high otherwise.
88
+ def input
89
+ return 0xff unless @playing
90
+
91
+ at = position
92
+ edges = @edges
93
+ while @next_edge < edges.length && edges[@next_edge] <= at
94
+ @high = !@high
95
+ @next_edge += 1
96
+ end
97
+ @high ? 0xff : 0x7f
98
+ end
99
+
100
+ # U2's port B after a write to it, which records a change of PB7.
101
+ def output(port_b)
102
+ high = port_b.anybits?(0x80)
103
+ return if high == @output_high
104
+
105
+ @output_high = high
106
+ @tape.record(position) if @recording
107
+ end
108
+
109
+ private
110
+
111
+ def moving? = @playing || @recording
112
+
113
+ def start
114
+ @started = @clock.cycles
115
+ end
116
+ end
117
+ end
118
+ 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
@@ -0,0 +1,194 @@
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 portless_board = nil
52
+
53
+ def ram = @bus.ram
54
+
55
+ # Reads and writes memory as the CPU sees it, through the bus.
56
+ def peek(addr) = @bus.peek(addr)
57
+
58
+ def poke(addr, value) = @bus.poke(addr, value)
59
+
60
+ # With +tty+ the TTY/KB jumper is fitted, and the monitor talks to a
61
+ # teletype at +baud+ (Teletype::BAUDS) in place of the keypad and
62
+ # display. +ram+ names the RAM, one of Bus::RAM_CONFIGURATIONS, or
63
+ # nil for the stock 1K.
64
+ def initialize(tty: false, baud: 9600, ram: nil)
65
+ @cycles = 0
66
+ @panel = Panel.new(self)
67
+ @panel.tty = tty
68
+ @u2 = MonitorRRIOT.new(rom: ROM.read("kim1/6530-002.rom"), peripheral: @panel)
69
+ @u3 = UserRRIOT.new(rom: ROM.read("kim1/6530-003.rom"))
70
+ @panel.connect(@u2)
71
+ @bus = Bus.new(@u2, @u3, ram: ram || :"1k")
72
+ @cpu = CPU.new(@bus)
73
+ @cpu.power_on!
74
+ @sst = false
75
+ @irq_wired = false
76
+ @sound = Sound.new(@u2, @u3)
77
+ @teletype = Teletype.new(@panel, baud:)
78
+ @teletype.reset!(0) if tty
79
+ @init_handlers = []
80
+ @init_threshold = BOOT_CYCLES
81
+ @pending_keys = nil
82
+ @key_down = false
83
+ @next_key_at = 0
84
+ end
85
+
86
+ # Whether the TTY/KB jumper is fitted.
87
+ def tty? = @panel.tty
88
+
89
+ # Fits or takes out the TTY/KB jumper.
90
+ def tty=(fitted)
91
+ @panel.tty = fitted
92
+ end
93
+
94
+ def timing = TIMING
95
+
96
+ # Types `text` on the teletype with the jumper fitted, and otherwise
97
+ # presses the keys it names (KeyEntry#type_keys).
98
+ def type_text(text)
99
+ return type_keys(text) unless @panel.tty
100
+
101
+ @teletype.type(text)
102
+ end
103
+
104
+ # What the teletype has printed: #output holds it all.
105
+ def capture_output = @teletype
106
+
107
+ # What a front end plays: U2's PB7 or the speaker (Sound), which
108
+ # answers #record and #drain_samples.
109
+ def sound_source = @sound
110
+
111
+ # Whether the speaker on U3's PA0 is wired, and plays in place of PB7.
112
+ def speaker? = @sound.speaker
113
+
114
+ def speaker=(wired)
115
+ @sound.speaker = wired
116
+ end
117
+
118
+ # The machine as the pause menu names it.
119
+ def model_label = "KIM-1, #{ram_kb}K RAM"
120
+
121
+ # The RAM in K (Bus#ram_kb).
122
+ def ram_kb = @bus.ram_kb
123
+
124
+ # No drive plugs into the KIM-1, and nothing is mounted.
125
+ def true_drive = nil
126
+
127
+ def mounted? = false
128
+
129
+ def mounted_path = ""
130
+
131
+ # The 6530s count ahead of the CPU. With SST on, the board pulls NMI
132
+ # ahead of an opcode fetch outside the monitor.
133
+ def cycle!
134
+ handle_init if @cycles == @init_threshold
135
+ feed_keys unless @pending_keys.nil?
136
+
137
+ @u2.cycle!
138
+ @u3.cycle!
139
+ @sound.cycle! if @sound.recording?
140
+ @teletype.cycle!(@cycles, @u2.port_b_output.anybits?(0x01) && @teletype.line_high?) if @panel.tty
141
+ @cpu.irq = @irq_wired && @u3.irq?
142
+ @cpu.single_step if @sst
143
+ @cpu.cycle!
144
+
145
+ @cycles += 1
146
+ end
147
+
148
+ # RS: the RST line resets the CPU and both 6530s. RAM keeps what it
149
+ # holds. A teletype then sends RUBOUT for the monitor to time.
150
+ def reset!
151
+ @u2.reset!
152
+ @u3.reset!
153
+ @cpu.reset!
154
+ @teletype.reset!(@cycles) if @panel.tty
155
+ end
156
+
157
+ alias press_reset reset!
158
+
159
+ # Switches the machine off and on: RAM starts cleared, and RS resets
160
+ # everything else.
161
+ def power_cycle!
162
+ @bus.power_on!
163
+ reset!
164
+ @cpu.power_on!
165
+ end
166
+
167
+ # ST: an edge on NMI.
168
+ def press_stop
169
+ @cpu.nmi = true
170
+ end
171
+
172
+ # Holds down the key Panel::KEYS names `name`.
173
+ def press_key(name)
174
+ @panel.press(name)
175
+ end
176
+
177
+ def release_key
178
+ @panel.release
179
+ end
180
+
181
+ # The segments each LED digit shows (Panel#display).
182
+ def display = @panel.display
183
+
184
+ # The display as text, such as "0200 A9" (Panel#display_text).
185
+ def display_text = @panel.display_text
186
+
187
+ # The cassette deck on U2's PB7.
188
+ def tape = @panel.tape
189
+ end
190
+ end
191
+
192
+ require "badline/snapshot/kim1_setup"
193
+ require "badline/kim1/keyed_state"
194
+ require "badline/kim1/keyed_modules"