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,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class M6809
6
+ # The condition code bits.
7
+ CARRY = 0x01
8
+ OVERFLOW = 0x02
9
+ ZERO = 0x04
10
+ NEGATIVE = 0x08
11
+ IRQ_MASK = 0x10
12
+ HALF_CARRY = 0x20
13
+ FIRQ_MASK = 0x40
14
+ ENTIRE = 0x80
15
+
16
+ # What an opcode runs: the instruction method its *_exec step calls,
17
+ # the steps that work out its address, one a cycle, and the steps
18
+ # that follow them, and the group the undocumented stores immediate
19
+ # take their flags from when it ran before them. An indexed opcode's
20
+ # address steps end in the postbyte, which picks the rest from
21
+ # INDEXED_PLANS.
22
+ Entry = Struct.new(:operation, :plan, :tail, :group)
23
+
24
+ # The groups of David Banks' "Flags for unprefixed Store Immediate".
25
+ GROUP_OTHER = 0
26
+ GROUP_N0_Z1 = 1
27
+ GROUP_A_00 = 2
28
+ GROUP_A_FF = 3
29
+ GROUP_A_01 = 4
30
+ GROUP_B_01 = 5
31
+ GROUP_WORD_01 = 6
32
+ GROUP_LEA = 7
33
+ GROUPS = {
34
+ GROUP_N0_Z1 => %i[bita cmpa],
35
+ GROUP_A_00 => %i[lda ldd],
36
+ GROUP_A_FF => %i[adca adda anda eora ora sbca suba],
37
+ GROUP_A_01 => %i[asla asra coma daa deca inca lsra nega rola rora subd tsta xdeca xnca],
38
+ GROUP_B_01 => %i[sex],
39
+ GROUP_WORD_01 => %i[cmpd cmps cmpu cmpx cmpy xaddd xaddu],
40
+ GROUP_LEA => %i[leas leau leax leay]
41
+ }.flat_map { |group, operations| operations.map { |operation| [operation, group] } }.to_h.freeze
42
+
43
+ # The plan between instructions: the next cycle fetches an opcode.
44
+ FETCH_PLAN = %i[fetch].freeze
45
+
46
+ # The tail an instruction has once it is running its tail, never run.
47
+ # The empty tail, JUMP_TAIL, runs the instruction method as the
48
+ # address steps end.
49
+ NO_TAIL = %i[none].freeze
50
+ JUMP_TAIL = [].freeze
51
+
52
+ DIRECT_PLAN = %i[direct dead_v].freeze
53
+ EXTENDED_PLAN = %i[extended_hi extended_lo dead_v].freeze
54
+ INDEXED_PLAN = %i[postbyte].freeze
55
+
56
+ # The steps that read or write the address once it is known.
57
+ TAILS = {
58
+ read8: %i[read_exec],
59
+ read16: %i[read_hi read_lo_exec],
60
+ read16_dead: %i[read_hi read_lo_exec dead_v],
61
+ write8: %i[write_exec],
62
+ write16: %i[write_hi_exec write_lo],
63
+ rmw: %i[rmw_read dead_v rmw_write],
64
+ test: %i[read_exec dead_v dead_v],
65
+ jump: JUMP_TAIL,
66
+ subroutine: %i[dead_ea dead_v push_pcl push_pch_exec],
67
+ load_address: %i[dead_v_exec]
68
+ }.freeze
69
+
70
+ IMMEDIATE_TAILS = {
71
+ read8: %i[immediate_exec],
72
+ read16: %i[immediate_hi immediate_lo_exec],
73
+ read16_dead: %i[immediate_hi immediate_lo_exec dead_v]
74
+ }.freeze
75
+
76
+ # The 12 bytes SWI and the hardware interrupts other than FIRQ stack,
77
+ # in the order they go onto S.
78
+ PUSH_ALL = %i[push_pcl push_pch push_wl push_wh push_yl push_yh push_xl push_xh push_dp push_b push_a
79
+ push_cc].freeze
80
+ VECTOR_STEPS = %i[dead_v vector_hi vector_lo dead_v].freeze
81
+
82
+ # SWI, SWI2, SWI3 and the undocumented XRES, XSWI2 and XFIRQ, after
83
+ # the opcode. The instruction method picks the vector.
84
+ SOFTWARE_INTERRUPT_TAIL = [:dead_pc, :dead_v_exec, *PUSH_ALL, *VECTOR_STEPS].freeze
85
+
86
+ # IRQ and NMI after the cycle that would have fetched the opcode, and
87
+ # FIRQ, which stacks only PC and CC.
88
+ INTERRUPT_PLAN = [:dead_pc, :dead_v_exec, *PUSH_ALL, *VECTOR_STEPS].freeze
89
+ FAST_INTERRUPT_PLAN = [:dead_pc, :dead_v_exec, :push_pcl, :push_pch, :push_cc, *VECTOR_STEPS].freeze
90
+
91
+ # The interrupt CWAI was waiting for: its vector, no more stacking.
92
+ WAITED_INTERRUPT_PLAN = %i[vector_hi vector_lo dead_v].freeze
93
+
94
+ # RTI after pulling CC, with E set and with E clear.
95
+ RETURN_ENTIRE_PLAN = %i[pull_a pull_b pull_dp pull_xh pull_xl pull_yh pull_yl pull_wh pull_wl pull_pch pull_pcl
96
+ dead_stack].freeze
97
+ RETURN_FAST_PLAN = %i[pull_pch pull_pcl dead_stack].freeze
98
+
99
+ # The cycles after RESET goes high: two dead reads of $FFFE, the
100
+ # vector, and a dead cycle.
101
+ RESET_PLAN = %i[reset_dead reset_dead reset_hi reset_lo dead_v].freeze
102
+
103
+ LONG_BRANCH_TAKEN_PLAN = %i[dead_v_take].freeze
104
+ SYNC_END_PLAN = %i[idle].freeze
105
+
106
+ # PSH and PUL by postbyte: the bytes in the order they go onto or
107
+ # come off the stack, PC first on and last off.
108
+ PUSH_ORDER = [[0x80, %i[push_pcl push_pch]], [0x40, %i[push_wl push_wh]], [0x20, %i[push_yl push_yh]],
109
+ [0x10, %i[push_xl push_xh]], [0x08, %i[push_dp]], [0x04, %i[push_b]], [0x02, %i[push_a]],
110
+ [0x01, %i[push_cc]]].freeze
111
+ PULL_ORDER = [[0x01, %i[pull_cc]], [0x02, %i[pull_a]], [0x04, %i[pull_b]], [0x08, %i[pull_dp]],
112
+ [0x10, %i[pull_xh pull_xl]], [0x20, %i[pull_yh pull_yl]], [0x40, %i[pull_wh pull_wl]],
113
+ [0x80, %i[pull_pch pull_pcl]]].freeze
114
+ PUSH_PLANS = Array.new(256) do |mask|
115
+ [:dead_v, :dead_v, :dead_stack, *PUSH_ORDER.flat_map { |bit, steps| mask.nobits?(bit) ? [] : steps }].freeze
116
+ end.freeze
117
+ PULL_PLANS = Array.new(256) do |mask|
118
+ [:dead_v, :dead_v, *PULL_ORDER.flat_map { |bit, steps| mask.nobits?(bit) ? [] : steps }, :dead_stack].freeze
119
+ end.freeze
120
+
121
+ # The steps after the postbyte, by postbyte, Figure 17 sheet 4.
122
+ INDEXED_PLANS = Array.new(256) do |post|
123
+ next %i[dead_pc dead_v].freeze if post < 0x80
124
+
125
+ plan = case post & 0x0f
126
+ when 0x0, 0x2 then %i[dead_pc dead_v dead_v]
127
+ when 0x1, 0x3 then %i[dead_pc dead_v dead_v dead_v]
128
+ when 0x4 then %i[dead_pc]
129
+ when 0x5, 0x6 then %i[dead_pc dead_v]
130
+ when 0x8 then %i[offset8 dead_v]
131
+ when 0x9 then %i[offset_hi offset_lo_register dead_pc dead_v dead_v]
132
+ when 0xb then %i[dead_pc dead_pc_next dead_pc_next2 dead_v dead_v]
133
+ when 0xc then %i[offset8_pc dead_v]
134
+ when 0xd then %i[offset_hi offset_lo_pc dead_pc dead_v dead_v dead_v]
135
+ when 0xf then %i[offset_hi offset_lo_absolute dead_pc]
136
+ else []
137
+ end
138
+ plan += %i[indirect_hi indirect_lo dead_v] if post.anybits?(0x10)
139
+ plan.freeze
140
+ end.freeze
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class M6809
6
+ # The programmer's registers, and the codes TFR and EXG and the
7
+ # indexed postbyte name them by.
8
+ module Registers
9
+ attr_accessor :a, :b, :x, :y, :u, :s, :pc, :dp, :cc
10
+
11
+ def d = (@a << 8) | @b
12
+
13
+ def d=(value)
14
+ @a = value >> 8
15
+ @b = value & 0xff
16
+ end
17
+
18
+ # Whether NMI is armed: a load of S since reset.
19
+ def nmi_armed? = @nmi_armed
20
+
21
+ private
22
+
23
+ def load_s(value)
24
+ @s = value
25
+ @nmi_armed = true
26
+ end
27
+
28
+ # The register bits 5 and 6 of an indexed postbyte name.
29
+ def index_register
30
+ case @post & 0x60
31
+ when 0x00 then @x
32
+ when 0x20 then @y
33
+ when 0x40 then @u
34
+ else @s
35
+ end
36
+ end
37
+
38
+ def index_register=(value)
39
+ case @post & 0x60
40
+ when 0x00 then @x = value
41
+ when 0x20 then @y = value
42
+ when 0x40 then @u = value
43
+ else load_s(value)
44
+ end
45
+ end
46
+
47
+ # A TFR or EXG register: D, X, Y, U, S and PC are 0 to 5, A, B, CC
48
+ # and DP 8 to 11. The other codes read as $FF or $FFFF.
49
+ def transfer_register(code)
50
+ case code
51
+ when 0x0 then d
52
+ when 0x1 then @x
53
+ when 0x2 then @y
54
+ when 0x3 then @u
55
+ when 0x4 then @s
56
+ when 0x5 then @pc
57
+ when 0x8 then @a
58
+ when 0x9 then @b
59
+ when 0xa then @cc
60
+ when 0xb then @dp
61
+ else code < 8 ? 0xffff : 0xff
62
+ end
63
+ end
64
+
65
+ def set_transfer_register(code, value)
66
+ case code
67
+ when 0x0 then self.d = value
68
+ when 0x1 then @x = value
69
+ when 0x2 then @y = value
70
+ when 0x3 then @u = value
71
+ when 0x4 then load_s(value)
72
+ when 0x5 then @pc = value
73
+ when 0x8 then @a = value
74
+ when 0x9 then @b = value
75
+ when 0xa then @cc = value
76
+ when 0xb then @dp = value
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class M6809
6
+ # The stack steps of PSH, PUL, JSR, BSR, RTS, RTI, the software
7
+ # interrupts and the hardware ones. Each pushes onto or pulls off the
8
+ # stack @stack_u names: U for PSHU and PULU, S otherwise. The "other"
9
+ # stack pointer, w, is the one that isn't.
10
+ module Stack
11
+ private
12
+
13
+ def push(value)
14
+ if @stack_u
15
+ @u = (@u - 1) & 0xffff
16
+ @bus.write(@u, value)
17
+ else
18
+ @s = (@s - 1) & 0xffff
19
+ @bus.write(@s, value)
20
+ end
21
+ end
22
+
23
+ def pull
24
+ if @stack_u
25
+ value = @bus.read(@u)
26
+ @u = (@u + 1) & 0xffff
27
+ else
28
+ value = @bus.read(@s)
29
+ @s = (@s + 1) & 0xffff
30
+ end
31
+ value
32
+ end
33
+
34
+ def dead_stack = @bus.read(@stack_u ? @u : @s)
35
+
36
+ def other_stack = @stack_u ? @s : @u
37
+
38
+ def other_stack=(value)
39
+ @stack_u ? load_s(value) : @u = value
40
+ end
41
+
42
+ def push_s_post = stack_post(false, PUSH_PLANS)
43
+
44
+ def push_u_post = stack_post(true, PUSH_PLANS)
45
+
46
+ def pull_s_post = stack_post(false, PULL_PLANS)
47
+
48
+ def pull_u_post = stack_post(true, PULL_PLANS)
49
+
50
+ def stack_post(stack_u, plans)
51
+ @stack_u = stack_u
52
+ switch_to(plans[read_pc])
53
+ end
54
+
55
+ def push_pcl = push(@pc & 0xff)
56
+ def push_pch = push(@pc >> 8)
57
+ def push_wl = push(other_stack & 0xff)
58
+ def push_wh = push(other_stack >> 8)
59
+ def push_yl = push(@y & 0xff)
60
+ def push_yh = push(@y >> 8)
61
+ def push_xl = push(@x & 0xff)
62
+ def push_xh = push(@x >> 8)
63
+ def push_dp = push(@dp)
64
+ def push_b = push(@b)
65
+ def push_a = push(@a)
66
+ def push_cc = push(@cc)
67
+
68
+ def push_pch_exec
69
+ push_pch
70
+ execute
71
+ end
72
+
73
+ def pull_cc
74
+ @cc = pull
75
+ end
76
+
77
+ def pull_a
78
+ @a = pull
79
+ end
80
+
81
+ def pull_b
82
+ @b = pull
83
+ end
84
+
85
+ def pull_dp
86
+ @dp = pull
87
+ end
88
+
89
+ def pull_xh
90
+ @x = (pull << 8) | (@x & 0xff)
91
+ end
92
+
93
+ def pull_xl
94
+ @x = (@x & 0xff00) | pull
95
+ end
96
+
97
+ def pull_yh
98
+ @y = (pull << 8) | (@y & 0xff)
99
+ end
100
+
101
+ def pull_yl
102
+ @y = (@y & 0xff00) | pull
103
+ end
104
+
105
+ def pull_wh
106
+ self.other_stack = (pull << 8) | (other_stack & 0xff)
107
+ end
108
+
109
+ def pull_wl
110
+ self.other_stack = (other_stack & 0xff00) | pull
111
+ end
112
+
113
+ def pull_pch
114
+ @pc = (pull << 8) | (@pc & 0xff)
115
+ end
116
+
117
+ def pull_pcl
118
+ @pc = (@pc & 0xff00) | pull
119
+ end
120
+
121
+ # RTI after pulling CC: the whole state when E is set, else PC.
122
+ def return_cc
123
+ pull_cc
124
+ switch_to(@cc.nobits?(ENTIRE) ? RETURN_FAST_PLAN : RETURN_ENTIRE_PLAN)
125
+ end
126
+
127
+ # The vector of the interrupt or SWI being taken. The masks it sets
128
+ # take effect as its first byte is read.
129
+ def vector_hi
130
+ @value = @bus.read(@vector) << 8
131
+ @cc |= @interrupt_mask
132
+ end
133
+
134
+ def vector_lo
135
+ @pc = @value | @bus.read(@vector + 1)
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/chips/m6809/core"
4
+
5
+ module Badline
6
+ module Chips
7
+ # The Motorola 6809E, which the SuperPET has beside its 6502. It is not
8
+ # part of badline/core, so a machine that has one requires it.
9
+ class M6809
10
+ include Core
11
+ end
12
+ end
13
+ end
@@ -13,8 +13,12 @@ module Badline
13
13
  # reads N - 1 until cycle T, 0 until cycle N x T, and $FF with the flag
14
14
  # set on cycle N x T: 52 at ÷8 sets the flag on cycle 416. From there
15
15
  # the counter counts every cycle, so it tells how long ago the flag
16
- # set, and each count through 0 sets the flag again. Only a write puts
17
- # the prescaler back.
16
+ # set, and each count through 0 sets the flag again. On the 6532 only
17
+ # a write puts the prescaler back. On the 6530, as the KIM-1 User
18
+ # Manual's appendix H has it, a read of the counter after the flag
19
+ # set does too, and the counter counts on from where it is at the
20
+ # interval written: a 6530 builds its timer with
21
+ # read_restores_divider. No test program has confirmed it.
18
22
  #
19
23
  # Reading or writing the counter clears the flag, except for a read on
20
24
  # the cycle that sets it. Each access takes A3 as the interrupt enable.
@@ -24,7 +28,8 @@ module Badline
24
28
  attr_reader :counter, :flag
25
29
  attr_accessor :irq_enabled
26
30
 
27
- def initialize
31
+ def initialize(read_restores_divider: false)
32
+ @read_restores_divider = read_restores_divider
28
33
  @counter = 0
29
34
  @divider = 1
30
35
  @prescale = 1
@@ -50,6 +55,7 @@ module Badline
50
55
  def read(irq_enabled)
51
56
  @irq_enabled = irq_enabled
52
57
  @flag = false unless @fired
58
+ restore_divider if @read_restores_divider && @expired
53
59
  @counter
54
60
  end
55
61
 
@@ -66,6 +72,11 @@ module Badline
66
72
 
67
73
  private
68
74
 
75
+ def restore_divider
76
+ @expired = false
77
+ @prescale = @divider
78
+ end
79
+
69
80
  def count
70
81
  @counter = (@counter - 1) & 0xff
71
82
  return unless @counter == 0xff
@@ -9,7 +9,9 @@ module Badline
9
9
  # of RAM, two 8-bit ports, A and B, each with a direction register, a 1
10
10
  # making the pin an output, and the 6532's interval timer (RIOT::Timer).
11
11
  # It has no edge detector. The 8050 and 8250's floppy controller has
12
- # one, its ROM the controller's program.
12
+ # one, its ROM the controller's program, and the KIM-1 two. Its timer
13
+ # reads its counter as the 6532's does, but a read after the flag sets
14
+ # puts the prescaler back (RIOT::Timer).
13
15
  #
14
16
  # Which addresses select the ROM, the RAM and the registers is part of
15
17
  # the mask, so the board decodes them and calls read_rom (A9-A0),
@@ -36,7 +38,7 @@ module Badline
36
38
  @peripheral = peripheral
37
39
  @irq_on_pb7 = irq_on_pb7
38
40
  @ram = Array.new(64, 0)
39
- @timer = RIOT::Timer.new
41
+ @timer = RIOT::Timer.new(read_restores_divider: true)
40
42
  reset!
41
43
  end
42
44
 
@@ -72,7 +74,10 @@ module Badline
72
74
 
73
75
  # Everything the RRIOT holds but its timer's counter, for comparing
74
76
  # it at two points.
75
- def idle_state = [*@ram, @ora, @orb, @ddra, @ddrb, *@timer.state, @timer.flag]
77
+ def idle_state = [*@ram, *port_state]
78
+
79
+ # The ports and the timer but for its counter.
80
+ def port_state = [@ora, @orb, @ddra, @ddrb, *@timer.state, @timer.flag]
76
81
 
77
82
  def read_rom(addr) = @rom[addr & 0x3ff]
78
83
 
@@ -21,7 +21,7 @@ module Badline
21
21
  end
22
22
 
23
23
  # Cycles until the current window closes, counting the next one as 1.
24
- attr_reader :cycles_to_close
24
+ attr_reader :cycles_to_close, :rate, :clock_hz
25
25
 
26
26
  # How far the current window has got, in a keyed snapshot module
27
27
  # (Snapshot::Keyed) under `prefix`. The clock and the rate are the
@@ -195,7 +195,7 @@ module Badline
195
195
  @unfiltered = 0
196
196
  route_voice(voice1.output >> 7, 0x1)
197
197
  route_voice(voice2.output >> 7, 0x2)
198
- if @routing & 0x4 != 0
198
+ if @routing.anybits?(0x4)
199
199
  @input += voice3.output >> 7
200
200
  elsif !@voice3_off
201
201
  @unfiltered += voice3.output >> 7
@@ -216,9 +216,9 @@ module Badline
216
216
 
217
217
  def filtered
218
218
  value = 0
219
- value += @lowpass if @mode & 0x1 != 0
220
- value += @bandpass if @mode & 0x2 != 0
221
- value += @highpass if @mode & 0x4 != 0
219
+ value += @lowpass if @mode.anybits?(0x1)
220
+ value += @bandpass if @mode.anybits?(0x2)
221
+ value += @highpass if @mode.anybits?(0x4)
222
222
  value
223
223
  end
224
224
  end
@@ -221,7 +221,11 @@ module Badline
221
221
  # voice's under voice1. to voice3., and the filter's under filter.
222
222
  # and ext. Saving catches the DSP up first, so no span or deferred
223
223
  # write is left to save. What the registers decode to is worked out
224
- # from them on load.
224
+ # from them on load. A recording SID also saves how far its recording
225
+ # has got under rec., with the rate, the clock and the filter chunk it
226
+ # records at: the samples' windows bound the filter's steps, so a SID
227
+ # loaded from it that records at the same rate and clock carries on
228
+ # from that window and integrates the steps the saved one does.
225
229
  module KeyedState
226
230
  def save_fields(out)
227
231
  catch_up
@@ -230,6 +234,8 @@ module Badline
230
234
  @voice2.save_fields(out, "voice2.")
231
235
  @voice3.save_fields(out, "voice3.")
232
236
  @filter.save_fields(out, "filter.")
237
+ recording = @decimator || @resumed_recording
238
+ save_recording(out, recording) if recording
233
239
  out
234
240
  end
235
241
 
@@ -243,23 +249,7 @@ module Badline
243
249
  @voice2.load_fields(input, "voice2.")
244
250
  @voice3.load_fields(input, "voice3.")
245
251
  @filter.load_fields(input, "filter.")
246
- end
247
-
248
- # How far the recording has got into the sample it is averaging,
249
- # under `prefix`, for a host that carries on recording from a
250
- # snapshot: it calls #record, then #load_recording_fields.
251
- def save_recording_fields(out, prefix)
252
- decimator = @decimator
253
- raise ArgumentError, "the SID isn't recording" if decimator.nil?
254
-
255
- decimator.save_fields(out, prefix)
256
- end
257
-
258
- def load_recording_fields(input, prefix)
259
- decimator = @decimator
260
- raise ArgumentError, "the SID isn't recording" if decimator.nil?
261
-
262
- decimator.load_fields(input, prefix)
252
+ load_recording(input)
263
253
  end
264
254
 
265
255
  def restored!
@@ -272,6 +262,49 @@ module Badline
272
262
 
273
263
  private
274
264
 
265
+ def save_recording(out, decimator)
266
+ out.int("rec.rate", decimator.rate).int("rec.clock_hz", decimator.clock_hz)
267
+ out.int("rec.filter_chunk", @filter_chunk)
268
+ decimator.save_fields(out, "rec.")
269
+ end
270
+
271
+ # A SID recording already carries on from the loaded window when it
272
+ # records at its rate, clock and filter chunk, and starts a new
273
+ # window otherwise.
274
+ def load_recording(input)
275
+ @resumed_recording = nil
276
+ live = @decimator
277
+ loaded = loaded_recording(input)
278
+ chunk = loaded ? input.int("rec.filter_chunk", @filter_chunk) : @filter_chunk
279
+ if live.nil?
280
+ @filter_chunk = chunk
281
+ @resumed_recording = loaded
282
+ elsif loaded && chunk == @filter_chunk && loaded.rate == live.rate && loaded.clock_hz == live.clock_hz
283
+ @decimator = loaded
284
+ else
285
+ @decimator = Decimator.new(clock_hz: live.clock_hz, rate: live.rate)
286
+ end
287
+ end
288
+
289
+ def loaded_recording(input)
290
+ rate = input.int("rec.rate", 0)
291
+ return unless rate.positive?
292
+
293
+ decimator = Decimator.new(clock_hz: input.int("rec.clock_hz", Region::PAL.clock_hz), rate:)
294
+ decimator.load_fields(input, "rec.")
295
+ decimator
296
+ end
297
+
298
+ # The window a loaded snapshot's recording had got to, when this
299
+ # recording is at its rate, clock and filter chunk.
300
+ def resumed_recording(rate, filter_chunk, clock_hz)
301
+ resumed = @resumed_recording
302
+ @resumed_recording = nil
303
+ return if resumed.nil? || filter_chunk != @filter_chunk
304
+
305
+ resumed if resumed.rate == rate && resumed.clock_hz == clock_hz
306
+ end
307
+
275
308
  def restore_voice(voice, base)
276
309
  control = register(base + 4)
277
310
  voice.waveform.restore_registers(register(base) | (register(base + 1) << 8),
@@ -234,7 +234,7 @@ module Badline
234
234
  def compare_pulse(phase = @accumulator >> 12) = (@pulse = @test || phase >= @pulse_width ? 0xfff : 0x000)
235
235
 
236
236
  # More than one bit set in the selection.
237
- def combined?(selected) = selected & (selected - 1) != 0
237
+ def combined?(selected) = selected.anybits?(selected - 1)
238
238
 
239
239
  # The one point in the cycle where the waveform selector drives the DAC:
240
240
  # either it latches a fresh value, or nothing drives the line and the
@@ -274,8 +274,8 @@ module Badline
274
274
  def advance
275
275
  previous = @accumulator
276
276
  @accumulator = (previous + @frequency) & 0xffffff
277
- @msb_rising = @accumulator & ~previous & MSB != 0
278
- if @accumulator & ~previous & 0x080000 != 0
277
+ @msb_rising = (@accumulator & ~previous).anybits?(MSB)
278
+ if (@accumulator & ~previous).anybits?(0x080000)
279
279
  @shift_pipeline = 2
280
280
  elsif @shift_pipeline != 0 && (@shift_pipeline -= 1).zero?
281
281
  write_back_shift
@@ -292,12 +292,12 @@ module Badline
292
292
  # TriXOR = !Saw & ((!V3 & Ring) ^ bit23), where V3 is the modulating
293
293
  # voice's MSB (SID/ringmod).
294
294
  def inverted?(selected)
295
- return false if selected & 0x2 != 0
295
+ return false if selected.anybits?(0x2)
296
296
 
297
- msb = @accumulator & MSB != 0
297
+ msb = @accumulator.anybits?(MSB)
298
298
  return msb unless @ring
299
299
 
300
- msb == (@sync_source.accumulator & MSB != 0)
300
+ msb == @sync_source.accumulator.anybits?(MSB)
301
301
  end
302
302
  end
303
303
  end
@@ -70,6 +70,7 @@ module Badline
70
70
  @pending_cycles = 0
71
71
  @deferred_writes = []
72
72
  @decimator = nil
73
+ @resumed_recording = nil
73
74
  @samples = []
74
75
  @voice_samples = nil
75
76
  @filter_chunk = filter_chunk
@@ -120,12 +121,15 @@ module Badline
120
121
  # Starts collecting the output, averaged down to `rate` samples a
121
122
  # second, for #drain_samples. `filter_chunk` overrides the one the SID
122
123
  # was built with, for a machine that built its own. `clock_hz` is the
123
- # rate the SID's cycles are taken to run at.
124
+ # rate the SID's cycles are taken to run at. A SID loaded from a
125
+ # recording one carries on from the window that one had got to, at the
126
+ # same rate, clock and filter chunk.
124
127
  def record(rate:, filter_chunk: @filter_chunk, clock_hz: Region::PAL.clock_hz)
125
128
  synthesize!
126
129
  catch_up
130
+ resumed = resumed_recording(rate, filter_chunk, clock_hz)
127
131
  @filter_chunk = filter_chunk
128
- @decimator = Decimator.new(clock_hz:, rate:)
132
+ @decimator = resumed || Decimator.new(clock_hz:, rate:)
129
133
  @samples = []
130
134
  end
131
135