badline 0.15.0 → 0.16.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 (155) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +102 -0
  3. data/CLAUDE.md +15 -7
  4. data/README.md +142 -16
  5. data/doc/media.md +19 -0
  6. data/doc/pinned-behaviour.md +67 -0
  7. data/doc/snapshot-format.md +274 -31
  8. data/doc/snapshots.md +14 -0
  9. data/exe/badline-ruby +1 -1
  10. data/lib/badline/audio/machine_player.rb +2 -14
  11. data/lib/badline/c64/common.rb +1 -1
  12. data/lib/badline/c64/true_drives.rb +13 -1
  13. data/lib/badline/c64.rb +6 -0
  14. data/lib/badline/chips/crtc/keyed_state.rb +46 -0
  15. data/lib/badline/chips/crtc.rb +163 -0
  16. data/lib/badline/chips/pia/control_lines.rb +138 -0
  17. data/lib/badline/chips/pia/keyed_parts.rb +56 -0
  18. data/lib/badline/chips/pia.rb +138 -0
  19. data/lib/badline/chips/tia.rb +16 -0
  20. data/lib/badline/core.rb +3 -0
  21. data/lib/badline/drives/datasette/keyed_state.rb +8 -3
  22. data/lib/badline/drives/engine/core.rb +6 -14
  23. data/lib/badline/drives/engine/idle.rb +42 -4
  24. data/lib/badline/drives/engine/idle_cpu.rb +18 -0
  25. data/lib/badline/drives/engine/serial_connection.rb +25 -0
  26. data/lib/badline/drives/engine/sleep.rb +1 -0
  27. data/lib/badline/drives/engine.rb +1 -0
  28. data/lib/badline/drives/ieee_bus.rb +28 -0
  29. data/lib/badline/drives/model1541/byte_ready.rb +19 -3
  30. data/lib/badline/drives/model1541/mechanism.rb +10 -1
  31. data/lib/badline/drives/model1541.rb +1 -0
  32. data/lib/badline/drives/model1551/bus.rb +138 -0
  33. data/lib/badline/drives/model1551/cable.rb +107 -0
  34. data/lib/badline/drives/model1551/cpu.rb +14 -0
  35. data/lib/badline/drives/model1551/keyed_state.rb +65 -0
  36. data/lib/badline/drives/model1551/port.rb +74 -0
  37. data/lib/badline/drives/model1551/tia.rb +71 -0
  38. data/lib/badline/drives/model1551.rb +192 -0
  39. data/lib/badline/drives/model1571.rb +1 -0
  40. data/lib/badline/drives/model1581.rb +1 -0
  41. data/lib/badline/drives.rb +1 -0
  42. data/lib/badline/frontend/app.rb +1 -1
  43. data/lib/badline/frontend/boot.rb +20 -1
  44. data/lib/badline/frontend/controls.rb +23 -3
  45. data/lib/badline/frontend/gamepads.rb +10 -3
  46. data/lib/badline/frontend/keys.rb +88 -0
  47. data/lib/badline/frontend/media_slots.rb +1 -0
  48. data/lib/badline/frontend/menu/menu_dialogs.rb +15 -6
  49. data/lib/badline/frontend/menu/menu_media.rb +96 -19
  50. data/lib/badline/frontend/menu/menu_pages.rb +25 -7
  51. data/lib/badline/frontend/menu/menu_ports.rb +22 -1
  52. data/lib/badline/frontend/menu/pause_menu.rb +1 -0
  53. data/lib/badline/frontend/pet_keyboard.rb +124 -0
  54. data/lib/badline/frontend/player/player_controller.rb +11 -4
  55. data/lib/badline/frontend/screens.rb +2 -1
  56. data/lib/badline/frontend/timeline.rb +16 -9
  57. data/lib/badline/frontend.rb +1 -0
  58. data/lib/badline/kernal_trap/dos/writes.rb +3 -1
  59. data/lib/badline/kernal_trap/dos.rb +6 -1
  60. data/lib/badline/kernal_trap/frames.rb +117 -0
  61. data/lib/badline/kernal_trap/ieee.rb +148 -0
  62. data/lib/badline/kernal_trap/ieee_layout.rb +113 -0
  63. data/lib/badline/kernal_trap/keyed_state.rb +4 -4
  64. data/lib/badline/kernal_trap/routine.rb +3 -53
  65. data/lib/badline/kernal_trap/serial.rb +4 -102
  66. data/lib/badline/kernal_trap/steps.rb +64 -0
  67. data/lib/badline/kernal_trap.rb +4 -0
  68. data/lib/badline/machine/keyboard_buffer.rb +8 -0
  69. data/lib/badline/machine/settling.rb +1 -1
  70. data/lib/badline/machine.rb +37 -11
  71. data/lib/badline/media/extensions.rb +5 -2
  72. data/lib/badline/media/pet_media.rb +40 -0
  73. data/lib/badline/media/program.rb +14 -2
  74. data/lib/badline/media/true_drive.rb +7 -5
  75. data/lib/badline/media/vic20_media.rb +2 -2
  76. data/lib/badline/media.rb +36 -16
  77. data/lib/badline/options/family.rb +17 -6
  78. data/lib/badline/options/help.rb +23 -5
  79. data/lib/badline/options/option.rb +2 -1
  80. data/lib/badline/options/table.rb +28 -4
  81. data/lib/badline/options/validation.rb +117 -14
  82. data/lib/badline/options.rb +65 -21
  83. data/lib/badline/pet/bus/expansion.rb +106 -0
  84. data/lib/badline/pet/bus.rb +204 -0
  85. data/lib/badline/pet/canvas.rb +97 -0
  86. data/lib/badline/pet/cassettes.rb +63 -0
  87. data/lib/badline/pet/characters.rb +26 -0
  88. data/lib/badline/pet/cpu.rb +10 -0
  89. data/lib/badline/pet/crtc_video.rb +142 -0
  90. data/lib/badline/pet/discrete_video.rb +83 -0
  91. data/lib/badline/pet/ieee_ports.rb +66 -0
  92. data/lib/badline/pet/ieee_traps.rb +75 -0
  93. data/lib/badline/pet/keyboard_ports.rb +74 -0
  94. data/lib/badline/pet/keyed_modules.rb +85 -0
  95. data/lib/badline/pet/keyed_parts.rb +123 -0
  96. data/lib/badline/pet/keyed_state.rb +42 -0
  97. data/lib/badline/pet/model.rb +113 -0
  98. data/lib/badline/pet/rom_sets.rb +65 -0
  99. data/lib/badline/pet/sound.rb +128 -0
  100. data/lib/badline/pet/via.rb +10 -0
  101. data/lib/badline/pet/via_ports.rb +19 -0
  102. data/lib/badline/pet.rb +226 -0
  103. data/lib/badline/plus4/acia/frames.rb +55 -0
  104. data/lib/badline/plus4/acia.rb +276 -16
  105. data/lib/badline/plus4/bus.rb +181 -45
  106. data/lib/badline/plus4/keyed_modules.rb +22 -5
  107. data/lib/badline/plus4/keyed_parts.rb +33 -4
  108. data/lib/badline/plus4/keyed_state.rb +10 -5
  109. data/lib/badline/plus4/ram_expansion.rb +145 -0
  110. data/lib/badline/plus4/sid_card.rb +95 -0
  111. data/lib/badline/plus4/sound_mix.rb +51 -0
  112. data/lib/badline/plus4/tcbm_drive.rb +134 -0
  113. data/lib/badline/plus4/user_port.rb +11 -2
  114. data/lib/badline/plus4.rb +104 -22
  115. data/lib/badline/roms/README +146 -0
  116. data/lib/badline/roms/dos1551.rom +0 -0
  117. data/lib/badline/roms/pet/basic1.rom +0 -0
  118. data/lib/badline/roms/pet/basic2.rom +0 -0
  119. data/lib/badline/roms/pet/basic4.rom +0 -0
  120. data/lib/badline/roms/pet/characters1.rom +0 -0
  121. data/lib/badline/roms/pet/characters2.rom +0 -0
  122. data/lib/badline/roms/pet/editor1.rom +0 -0
  123. data/lib/badline/roms/pet/editor2-b.rom +0 -0
  124. data/lib/badline/roms/pet/editor2-n.rom +0 -0
  125. data/lib/badline/roms/pet/editor4-40-n-50hz.rom +0 -0
  126. data/lib/badline/roms/pet/editor4-40-n-60hz.rom +0 -0
  127. data/lib/badline/roms/pet/editor4-80-b-50hz-0283.rom +0 -0
  128. data/lib/badline/roms/pet/editor4-80-b-50hz.rom +0 -0
  129. data/lib/badline/roms/pet/editor4-80-b-60hz.rom +0 -0
  130. data/lib/badline/roms/pet/editor4-b.rom +0 -0
  131. data/lib/badline/roms/pet/editor4-n.rom +0 -0
  132. data/lib/badline/roms/pet/kernal1.rom +0 -0
  133. data/lib/badline/roms/pet/kernal2.rom +0 -0
  134. data/lib/badline/roms/pet/kernal4.rom +0 -0
  135. data/lib/badline/snapshot/container.rb +2 -0
  136. data/lib/badline/snapshot/field_doc/markdown.rb +1 -1
  137. data/lib/badline/snapshot/field_doc.rb +21 -6
  138. data/lib/badline/snapshot/image.rb +14 -7
  139. data/lib/badline/snapshot/pet_setup.rb +71 -0
  140. data/lib/badline/snapshot/plus4_setup.rb +52 -10
  141. data/lib/badline/snapshot/storage_setup.rb +7 -1
  142. data/lib/badline/snapshot.rb +9 -5
  143. data/lib/badline/storage/d80_image.rb +48 -0
  144. data/lib/badline/storage/d82_image.rb +21 -0
  145. data/lib/badline/storage/g64_image.rb +1 -1
  146. data/lib/badline/storage/tap.rb +16 -3
  147. data/lib/badline/storage.rb +2 -0
  148. data/lib/badline/support/pet_checkpoint.rb +38 -0
  149. data/lib/badline/timing.rb +5 -4
  150. data/lib/badline/version.rb +1 -1
  151. data/lib/badline/vic20/attachments.rb +7 -2
  152. data/lib/badline/vic20/bus/keyed_state.rb +5 -4
  153. data/lib/badline/vic20/bus.rb +5 -0
  154. data/lib/badline/vic20.rb +1 -1
  155. metadata +69 -1
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/chips/crtc/keyed_state"
4
+
5
+ module Badline
6
+ module Chips
7
+ # The MOS 6545 CRTC, as the later PETs drive their screens with: an
8
+ # address register at even addresses selects one of eighteen registers,
9
+ # which the odd addresses write. Of them only the light pen's, R16 and
10
+ # R17, and the cursor address, R14 and R15, read back; every other read
11
+ # finds 0.
12
+ #
13
+ # Its counters run one character a cycle, and compare with the
14
+ # registers as they stand that cycle, so a write lands from the next
15
+ # character on:
16
+ #
17
+ # - The character counter runs from 0 to R0, then a line ends. The
18
+ # display shows from character 0 until the counter equals R1, and
19
+ # horizontal sync starts where it equals R2. A compare the counter has
20
+ # already passed doesn't happen that line: the display then runs on
21
+ # to the line's end.
22
+ # - The line counter (RA, five bits) counts a character row's lines. A
23
+ # row ends after the line where it equals R9; past R9 it runs on to
24
+ # 31 and wraps.
25
+ # - The row counter (seven bits) counts rows to R4. R5 more lines of
26
+ # vertical adjust follow, then the frame starts again. The display
27
+ # stops at the start of the row the counter equals R6, and comes back
28
+ # at the next frame; vertical sync starts with row R7 and lasts
29
+ # VSYNC_LINES lines.
30
+ # - The memory address (MA, fourteen bits) starts each frame at
31
+ # R12:R13 and counts one a character. Each line starts again from its
32
+ # row's start, which the last line of a row moves on to the address
33
+ # where the counter equals R1.
34
+ #
35
+ # Each displayed character goes to the output's #character with MA, RA
36
+ # and the characters since horizontal sync last started; the output
37
+ # also hears of each #hsync as it starts, each #line_end, the
38
+ # #rows_end where the display stops for the rest of the frame, and each
39
+ # #frame_start.
40
+ class CRTC
41
+ # The bits each register keeps.
42
+ MASKS = [0xff, 0xff, 0xff, 0xff, 0x7f, 0x1f, 0x7f, 0x7f, 0xff, 0x1f, 0x7f, 0x1f,
43
+ 0x3f, 0xff, 0x3f, 0xff, 0x3f, 0xff].freeze
44
+
45
+ VSYNC_LINES = 16
46
+
47
+ attr_reader :registers, :column, :line, :row, :address
48
+
49
+ # The characters since horizontal sync last started.
50
+ attr_reader :since_hsync
51
+
52
+ # +registers+ is the register file, which the output may read too.
53
+ def initialize(output, registers: Array.new(MASKS.length, 0))
54
+ @output = output
55
+ @registers = registers
56
+ reset!
57
+ end
58
+
59
+ # The RES pin stops the counters at the start of a frame. The
60
+ # registers keep their values.
61
+ def reset!
62
+ @selected = 0
63
+ @column = 0
64
+ @since_hsync = 0
65
+ @vsync_lines = 0
66
+ start_frame
67
+ end
68
+
69
+ # Whether vertical sync is on.
70
+ def vsync? = @vsync_lines.positive?
71
+
72
+ # Whether the display shows the character the counters are at.
73
+ def display? = @row_display && @column_display
74
+
75
+ def peek(addr)
76
+ return 0 if addr.nobits?(0x01) || @selected < 14 || @selected >= MASKS.length
77
+
78
+ @registers[@selected]
79
+ end
80
+
81
+ def poke(addr, value)
82
+ if addr.nobits?(0x01)
83
+ @selected = value & 0x1f
84
+ elsif @selected < MASKS.length
85
+ @registers[@selected] = value & MASKS[@selected]
86
+ end
87
+ end
88
+
89
+ def cycle!
90
+ regs = @registers
91
+ column = @column
92
+ if column == regs[1]
93
+ @column_display = false
94
+ @row_start = @address if @line == regs[9]
95
+ end
96
+ @output.character(@address, @line, @since_hsync) if @column_display && @row_display
97
+ if column == regs[2]
98
+ @since_hsync = 1
99
+ @output.hsync
100
+ else
101
+ @since_hsync += 1
102
+ end
103
+ @address = (@address + 1) & 0x3fff
104
+ if column == regs[0]
105
+ @column = 0
106
+ end_line
107
+ else
108
+ @column = (column + 1) & 0xff
109
+ end
110
+ end
111
+
112
+ private
113
+
114
+ def end_line
115
+ @output.line_end
116
+ @vsync_lines -= 1 if @vsync_lines.positive?
117
+ @column_display = true
118
+ if @adjusting
119
+ @line += 1
120
+ @line >= @registers[5] ? start_frame : @address = @row_start
121
+ elsif @line == @registers[9]
122
+ end_row
123
+ else
124
+ @line = (@line + 1) & 0x1f
125
+ @address = @row_start
126
+ end
127
+ end
128
+
129
+ def end_row
130
+ @line = 0
131
+ if @row == @registers[4]
132
+ return start_frame if @registers[5].zero?
133
+
134
+ @adjusting = true
135
+ else
136
+ @row = (@row + 1) & 0x7f
137
+ start_row
138
+ end
139
+ @address = @row_start
140
+ end
141
+
142
+ def start_frame
143
+ @line = 0
144
+ @row = 0
145
+ @adjusting = false
146
+ @row_display = true
147
+ @column_display = true
148
+ @row_start = ((@registers[12] << 8) | @registers[13]) & 0x3fff
149
+ @address = @row_start
150
+ start_row
151
+ @output.frame_start
152
+ end
153
+
154
+ def start_row
155
+ if @row == @registers[6] && @row_display
156
+ @row_display = false
157
+ @output.rows_end
158
+ end
159
+ @vsync_lines = VSYNC_LINES if @row == @registers[7]
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class PIA
6
+ # One side's control register, CRA or CRB, its two control lines, CA1
7
+ # and CA2 or CB1 and CB2, and their interrupt flags.
8
+ #
9
+ # bit 0 C1 interrupt enable
10
+ # bit 1 C1 active edge, rising when set
11
+ # bit 2 the port (1) or its direction register (0) at the same address
12
+ # bits 5-3 C2:
13
+ # 0e0 input, e the interrupt enable, falling edge
14
+ # 1e0 input, rising edge
15
+ # 100 handshake output
16
+ # 101 pulse output
17
+ # 110 low output
18
+ # 111 high output
19
+ # bit 6 C2 flag, read only, held clear while C2 is an output
20
+ # bit 7 C1 flag, read only
21
+ #
22
+ # A flag is set by its line's active edge whether or not its interrupt
23
+ # is enabled, and both are cleared by a read of the port. In handshake
24
+ # mode C2 goes low on the strobing access and back high on the next
25
+ # active C1 edge; in pulse mode it goes low for the cycle after it.
26
+ # Side A strobes on a read of the port, side B on a write.
27
+ class ControlLines
28
+ attr_reader :c1_high, :c2_high
29
+
30
+ # The level driven on C2, high while it is an input.
31
+ attr_reader :c2_output
32
+
33
+ def initialize(strobe_on_read:)
34
+ @strobe_on_read = strobe_on_read
35
+ @c1_high = @c2_high = true
36
+ reset!
37
+ end
38
+
39
+ # Every bit clear: C2 an input, both interrupts off and the
40
+ # direction register selected. The input levels carry over.
41
+ def reset!
42
+ @control = 0
43
+ @c1_flag = @c2_flag = false
44
+ @handshake_low = false
45
+ @pulse = 0
46
+ drive
47
+ end
48
+
49
+ # The control register as read, the flags in bits 7 and 6.
50
+ def register
51
+ value = @control
52
+ value |= 0x80 if @c1_flag
53
+ value |= 0x40 if @c2_flag
54
+ value
55
+ end
56
+
57
+ # Writes bits 0-5; the flags can't be written. A new C2 mode starts
58
+ # with any handshake or pulse over.
59
+ def register=(value)
60
+ mode = c2_mode
61
+ @control = value & 0x3f
62
+ @c2_flag = false if c2_output?
63
+ unless c2_mode == mode
64
+ @handshake_low = false
65
+ @pulse = 0
66
+ end
67
+ drive
68
+ end
69
+
70
+ def port_selected? = @control.anybits?(0x04)
71
+
72
+ def irq? = (@c1_flag && @control.anybits?(0x01)) || (@c2_flag && @control & 0x28 == 0x08)
73
+
74
+ # Only the pulse output needs the clock.
75
+ def pulsing? = @pulse.positive?
76
+
77
+ def cycle!
78
+ @pulse -= 1
79
+ drive if @pulse.zero?
80
+ end
81
+
82
+ # A new C1 level. An active edge sets the flag and ends a handshake.
83
+ def c1=(high)
84
+ return if high == @c1_high
85
+
86
+ @c1_high = high
87
+ return unless high == @control.anybits?(0x02)
88
+
89
+ @c1_flag = true
90
+ @handshake_low = false
91
+ drive
92
+ end
93
+
94
+ # A new C2 level. An active edge sets the flag while C2 is an input.
95
+ def c2=(high)
96
+ return if high == @c2_high
97
+
98
+ @c2_high = high
99
+ @c2_flag = true if !c2_output? && high == @control.anybits?(0x10)
100
+ end
101
+
102
+ # A read of the port, which clears both flags.
103
+ def port_read!
104
+ @c1_flag = @c2_flag = false
105
+ strobe if @strobe_on_read
106
+ end
107
+
108
+ def port_written!
109
+ strobe unless @strobe_on_read
110
+ end
111
+
112
+ private
113
+
114
+ def c2_output? = @control.anybits?(0x20)
115
+
116
+ def c2_mode = c2_output? ? @control & 0x38 : 0
117
+
118
+ def strobe
119
+ case @control & 0x38
120
+ when 0x20 then @handshake_low = true
121
+ when 0x28 then @pulse = 2
122
+ else return
123
+ end
124
+ drive
125
+ end
126
+
127
+ def drive
128
+ @c2_output = case @control & 0x38
129
+ when 0x20 then !@handshake_low
130
+ when 0x28 then @pulse.zero?
131
+ when 0x30 then false
132
+ else true
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class PIA
6
+ class ControlLines
7
+ # A side's control register, its lines' levels, its flags and the
8
+ # handshake or pulse under way, under `prefix`. The level driven on
9
+ # C2 follows from them on load.
10
+ module KeyedState
11
+ def save_fields(out, prefix)
12
+ out.int("#{prefix}control", @control).bool("#{prefix}c1_high", @c1_high).bool("#{prefix}c2_high", @c2_high)
13
+ out.bool("#{prefix}c1_flag", @c1_flag).bool("#{prefix}c2_flag", @c2_flag)
14
+ out.bool("#{prefix}handshake_low", @handshake_low).int("#{prefix}pulse", @pulse)
15
+ end
16
+
17
+ def load_fields(input, prefix)
18
+ @control = input.masked("#{prefix}control", @control, 0x3f)
19
+ @c1_high = input.bool("#{prefix}c1_high", @c1_high)
20
+ @c2_high = input.bool("#{prefix}c2_high", @c2_high)
21
+ @c1_flag = input.bool("#{prefix}c1_flag", @c1_flag)
22
+ @c2_flag = input.bool("#{prefix}c2_flag", @c2_flag)
23
+ @handshake_low = input.bool("#{prefix}handshake_low", @handshake_low)
24
+ @pulse = input.masked("#{prefix}pulse", @pulse, 0x03)
25
+ drive
26
+ end
27
+ end
28
+
29
+ include KeyedState
30
+ end
31
+
32
+ # The PIA's fields in a keyed snapshot module (Snapshot::Keyed): the
33
+ # output and direction registers, and each side's control lines under
34
+ # ca. and cb. The peripheral restores its own state.
35
+ module KeyedState
36
+ def save_fields(out)
37
+ out.int("ora", @ora).int("orb", @orb).int("ddra", @ddra).int("ddrb", @ddrb)
38
+ @ca.save_fields(out, "ca.")
39
+ @cb.save_fields(out, "cb.")
40
+ out
41
+ end
42
+
43
+ def load_fields(input)
44
+ @ora = input.byte("ora", @ora)
45
+ @orb = input.byte("orb", @orb)
46
+ @ddra = input.byte("ddra", @ddra)
47
+ @ddrb = input.byte("ddrb", @ddrb)
48
+ @ca.load_fields(input, "ca.")
49
+ @cb.load_fields(input, "cb.")
50
+ end
51
+ end
52
+
53
+ include KeyedState
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/chips/pia/control_lines"
4
+ require "badline/chips/pia/keyed_parts"
5
+
6
+ module Badline
7
+ module Chips
8
+ # The MOS 6520 PIA (Peripheral Interface Adapter), the PET's two: two
9
+ # 8-bit ports, A and B, each with a direction register, a 1 making the
10
+ # pin an output, and a control register for its pair of control lines
11
+ # (ControlLines). Each side has its own IRQ output, IRQA and IRQB. There
12
+ # are no timers.
13
+ #
14
+ # Four registers repeat on two address lines: port A or DDRA, CRA, port
15
+ # B or DDRB and CRB, the port or the direction register picked by bit 2
16
+ # of its control register.
17
+ #
18
+ # Port A reads its pins, whatever drives them. Port B reads its output
19
+ # bits from its output register and its input bits from the pins. An
20
+ # input pin floats high, and a peripheral pulls lines low through
21
+ # read_a and read_b, as a VIA's does. The control lines take levels
22
+ # from outside through ca1= and friends.
23
+ class PIA
24
+ attr_reader :peripheral
25
+
26
+ def initialize(peripheral: nil)
27
+ @peripheral = peripheral
28
+ @ca = ControlLines.new(strobe_on_read: true)
29
+ @cb = ControlLines.new(strobe_on_read: false)
30
+ reset!
31
+ end
32
+
33
+ # The RES line clears every register, which leaves every line an
34
+ # input and releases both IRQs.
35
+ def reset!
36
+ @ora = @orb = @ddra = @ddrb = 0x00
37
+ @ca.reset!
38
+ @cb.reset!
39
+ end
40
+
41
+ def irqa? = @ca.irq?
42
+
43
+ def irqb? = @cb.irq?
44
+
45
+ def irq? = @ca.irq? || @cb.irq?
46
+
47
+ def cycle!
48
+ @ca.cycle! if @ca.pulsing?
49
+ @cb.cycle! if @cb.pulsing?
50
+ end
51
+
52
+ def cra = @ca.register
53
+
54
+ def crb = @cb.register
55
+
56
+ # Port A as driven by ORA and DDRA, input lines floating high.
57
+ def port_a_output = driven_lines(@ora, @ddra)
58
+
59
+ def port_b_output = driven_lines(@orb, @ddrb)
60
+
61
+ def ca1=(high)
62
+ @ca.c1 = high
63
+ end
64
+
65
+ def ca2=(high)
66
+ @ca.c2 = high
67
+ end
68
+
69
+ def cb1=(high)
70
+ @cb.c1 = high
71
+ end
72
+
73
+ def cb2=(high)
74
+ @cb.c2 = high
75
+ end
76
+
77
+ def ca2_output = @ca.c2_output
78
+
79
+ def cb2_output = @cb.c2_output
80
+
81
+ def peek(addr)
82
+ case addr & 0x03
83
+ when 0x00 then @ca.port_selected? ? read_port_a : @ddra
84
+ when 0x01 then @ca.register
85
+ when 0x02 then @cb.port_selected? ? read_port_b : @ddrb
86
+ else @cb.register
87
+ end
88
+ end
89
+
90
+ def poke(addr, value)
91
+ case addr & 0x03
92
+ when 0x00 then write_port_a(value)
93
+ when 0x01 then @ca.register = value
94
+ when 0x02 then write_port_b(value)
95
+ else @cb.register = value
96
+ end
97
+ end
98
+
99
+ private
100
+
101
+ def driven_lines(register, direction)
102
+ (register & direction) | (~direction & 0xff)
103
+ end
104
+
105
+ def read_port_a
106
+ lines = port_a_output
107
+ lines &= @peripheral.read_a(lines) if @peripheral
108
+ @ca.port_read!
109
+ lines
110
+ end
111
+
112
+ def read_port_b
113
+ lines = port_b_output
114
+ lines &= @peripheral.read_b(lines) if @peripheral
115
+ @cb.port_read!
116
+ (@orb & @ddrb) | (lines & ~@ddrb & 0xff)
117
+ end
118
+
119
+ def write_port_a(value)
120
+ if @ca.port_selected?
121
+ @ora = value
122
+ @ca.port_written!
123
+ else
124
+ @ddra = value
125
+ end
126
+ end
127
+
128
+ def write_port_b(value)
129
+ if @cb.port_selected?
130
+ @orb = value
131
+ @cb.port_written!
132
+ else
133
+ @ddrb = value
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -9,20 +9,28 @@ module Badline
9
9
  #
10
10
  # A pin read as an output returns what the port drives, and one read as
11
11
  # an input returns what the other side drives, #inputs, per port.
12
+ #
13
+ # A cable plugged in (Drives::Model1551::Cable) hears every write and
14
+ # reset, which can move the pins it carries.
12
15
  class TIA
13
16
  attr_reader :ports, :directions
14
17
 
15
18
  # What the other side drives onto each port's pins.
16
19
  attr_reader :inputs
17
20
 
21
+ # The cable to the other side, or nil.
22
+ attr_accessor :cable
23
+
18
24
  def initialize
19
25
  @inputs = [0, 0, 0]
26
+ @cable = nil
20
27
  reset!
21
28
  end
22
29
 
23
30
  def reset!
24
31
  @ports = [0, 0, 0]
25
32
  @directions = [0, 0, 0]
33
+ @cable&.moved
26
34
  end
27
35
 
28
36
  def peek(addr)
@@ -43,6 +51,14 @@ module Badline
43
51
  else
44
52
  @ports[register] = value
45
53
  end
54
+ @cable&.moved
55
+ end
56
+
57
+ # The levels on +port+'s pins as the chip leaves them: an output's
58
+ # level, and 1 for an input, which the chip doesn't drive.
59
+ def output(port)
60
+ direction = @directions[port]
61
+ (@ports[port] & direction) | (~direction & 0xff)
46
62
  end
47
63
 
48
64
  # The ports, their directions and what the other side drives, in a
data/lib/badline/core.rb CHANGED
@@ -18,6 +18,8 @@ require "badline/c64/banked_ram"
18
18
  require "badline/status"
19
19
  require "badline/input"
20
20
  require "badline/chips"
21
+ require "badline/chips/pia"
22
+ require "badline/chips/crtc"
21
23
  require "badline/drives"
22
24
  require "badline/c64/bus"
23
25
  require "badline/c64/reu"
@@ -27,6 +29,7 @@ require "badline/snapshot/storage_setup"
27
29
  require "badline/machine"
28
30
  require "badline/c64"
29
31
  require "badline/vic20"
32
+ require "badline/pet"
30
33
  require "badline/storage"
31
34
  require "badline/sid_tune"
32
35
  require "badline/c64/cartridge"
@@ -4,15 +4,17 @@ module Badline
4
4
  module Drives
5
5
  class Datasette
6
6
  # The datasette's fields in a keyed snapshot module (Snapshot::Keyed,
7
- # BL-TAPE): the keys, the motor, the countdown to the next pulse, and
8
- # a tape in it under tape.*: its path, its bytes and how far it has
9
- # played. A tape that isn't in leaves tape.* out. A datasette that
7
+ # BL-TAPE): the keys, the motor, the countdown to the next pulse,
8
+ # whether it records, the cycles since the write line's last edge and
9
+ # that line's level, and a tape in it under tape.*: its path, its
10
+ # bytes and how far it has played. A tape that isn't in leaves tape.* out. A datasette that
10
11
  # times the read line's level adds when in the countdown the line
11
12
  # rises, and whether a recording's low half wave is on the tape. The
12
13
  # sense and flag handlers don't fire on load.
13
14
  module KeyedState
14
15
  def save_fields(out)
15
16
  out.bool("playing", @playing).bool("motor", @motor).int("countdown", @countdown)
17
+ out.bool("recording", @recording).int("since", @since).bool("write_high", @write_high)
16
18
  out.int("rise_at", @rise_at).bool("low_recorded", @low_recorded) if @level
17
19
  tape = @tape
18
20
  return out unless tape
@@ -26,6 +28,9 @@ module Badline
26
28
  @playing = input.bool("playing", @playing)
27
29
  @motor = input.bool("motor", @motor)
28
30
  @countdown = input.int("countdown", @countdown)
31
+ @recording = input.bool("recording", @recording)
32
+ @since = input.int("since", @since)
33
+ @write_high = input.bool("write_high", @write_high)
29
34
  @rise_at = input.int("rise_at", @rise_at)
30
35
  @low_recorded = input.bool("low_recorded", @low_recorded)
31
36
  bytes = input.bytes("tape.bytes", "".b).bytes
@@ -24,8 +24,11 @@ module Badline
24
24
  # sleeps through them too (see Orbit).
25
25
  #
26
26
  # A model includes Core and builds its parts, sets @clock_hz and
27
- # @idle_loop, and runs one drive cycle in step. It hears ATN on
28
- # connecting (hear_atn), drives the serial bus (serial_output), and
27
+ # @idle_loop, and runs one drive cycle in step. Its @serial_port faces
28
+ # the host: it says whether the host moved the lines the drive
29
+ # listens to (atn_moved?) and takes them for the next host cycle
30
+ # (latch_host), as the serial port does for ATN on the serial bus
31
+ # (SerialConnection) and the TCBM cable does on the 1551. The model
29
32
  # answers Idle's and Orbit's questions about its chips, as VIAs does
30
33
  # for the 1541 and the 1571.
31
34
  module Core
@@ -33,7 +36,7 @@ module Badline
33
36
  include Idle
34
37
  include Orbit
35
38
 
36
- attr_reader :device, :serial_bus
39
+ attr_reader :device
37
40
 
38
41
  def cpu
39
42
  settle!
@@ -62,17 +65,6 @@ module Badline
62
65
  @host_clock_hz = clock_hz
63
66
  end
64
67
 
65
- # Plugs the drive into a serial bus, leaving the one it was on. A drive
66
- # starts out on a bus of its own, with nothing else on it.
67
- def connect(serial_bus)
68
- settle!
69
- @serial_bus&.detach(self)
70
- @serial_bus = serial_bus
71
- @serial_port.bus = serial_bus
72
- serial_bus.attach(self)
73
- hear_atn
74
- end
75
-
76
68
  # Puts a Disk in the drive (Disk.from_d64 makes one from an image).
77
69
  # Nil takes the disk out.
78
70
  def insert(disk)