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
@@ -39,6 +39,7 @@ module Badline
39
39
  pending = @pending_keys
40
40
  out.ints("host.pending_keys", pending) if pending
41
41
  out.bool("host.typing", !pending.nil?)
42
+ save_init_keys(out)
42
43
  out.int("host.init_pending", @cycles <= init_threshold ? @init_handlers.length : 0)
43
44
  out.bool("host.holding_commodore", @holding_commodore)
44
45
  @typist.save_fields(out, "host.typist.")
@@ -55,6 +56,7 @@ module Badline
55
56
  @cpu_reset_pending = input.bool("cpu_reset_pending", @cpu_reset_pending)
56
57
  pending = input.ints("host.pending_keys", [])
57
58
  @pending_keys = input.bool("host.typing", !pending.empty?) ? pending.dup : nil
59
+ load_init_keys(input)
58
60
  @init_handlers_lost = [input.int("host.init_pending", 0) - @init_handlers.length, 0].max
59
61
  load_holding_fields(input)
60
62
  @typist.load_fields(input, "host.typist.")
data/lib/badline/c128.rb CHANGED
@@ -74,6 +74,8 @@ module Badline
74
74
 
75
75
  def family = :c128
76
76
 
77
+ def portless_board = nil
78
+
77
79
  def address_bus = @bus
78
80
 
79
81
  def region = @bus.region
@@ -124,6 +126,7 @@ module Badline
124
126
  @holding_commodore = false
125
127
  @bus = Bus.new(@model, sid_model: sid_model || @model.sid_model, mode:)
126
128
  @cpu = CPU.new(@bus, debug:)
129
+ @cpu.power_on! if @c64_built
127
130
  build_z80
128
131
  @vic = @bus.vic
129
132
  @vic.open_bus = -> { @bus.ram.peek(@cpu.program_counter) }
@@ -139,6 +142,7 @@ module Badline
139
142
  @restore_pulse = false
140
143
  @init_handlers = []
141
144
  @pending_keys = nil
145
+ @init_keys = nil
142
146
  @typist = Input::MatrixTypist.new(@bus.keyboard)
143
147
  @capture_output = nil
144
148
  init_drives
@@ -203,6 +207,7 @@ module Badline
203
207
  @vdc.power_on!
204
208
  @bus.power_on!
205
209
  reset!
210
+ @cpu.power_on! if @c64_built
206
211
  enter_c64_mode if @c64_built
207
212
  end
208
213
 
@@ -15,8 +15,14 @@ module Badline
15
15
  # alone at $DC00-$DFFF and the cartridge port in Ultimax mode.
16
16
  BOARDS = %i[c64 pet64 gs max].freeze
17
17
 
18
+ # The boards with no serial port for a disk drive: the C64GS's case
19
+ # covers its board's, and the MAX Machine has none.
20
+ NO_SERIAL_PORT = { gs: "the C64GS", max: "the MAX Machine" }.freeze
21
+
18
22
  attr_reader :board
19
23
 
24
+ def portless_board = NO_SERIAL_PORT[@board]
25
+
20
26
  # Fits the KERNAL ROM ROMs::KERNALS names, leaves the cassette port
21
27
  # empty unless `datasette`, and puts the chips in the board BOARDS
22
28
  # names.
@@ -39,6 +39,7 @@ module Badline
39
39
  out.bool("dma", @dma)
40
40
  pending = @pending_keys
41
41
  out.ints("host.pending_keys", pending) if pending
42
+ save_init_keys(out)
42
43
  out.int("host.init_pending", booting? ? @init_handlers.length : 0)
43
44
  end
44
45
 
@@ -52,6 +53,7 @@ module Badline
52
53
  @dma = input.bool("dma", @dma)
53
54
  pending = input.ints("host.pending_keys", [])
54
55
  @pending_keys = pending.empty? ? nil : pending.dup
56
+ load_init_keys(input)
55
57
  @init_handlers_lost = [input.int("host.init_pending", 0) - @init_handlers.length, 0].max
56
58
  end
57
59
 
data/lib/badline/c64.rb CHANGED
@@ -48,6 +48,10 @@ module Badline
48
48
 
49
49
  def datasette = address_bus.datasette
50
50
 
51
+ # The board's name when it has no serial port for a disk drive, or nil.
52
+ # Every family answers it, nil on all but these C64 boards.
53
+ def portless_board = address_bus.portless_board
54
+
51
55
  def install_debug_register(&) = address_bus.install_debug_register(&)
52
56
 
53
57
  # The machine options (sid_model:, cia_model:, vic_model:, region: and
@@ -63,6 +67,7 @@ module Badline
63
67
  @address_bus = Bus.new(**machine.except(*Bus::Fittings::NAMES))
64
68
  @address_bus.fit(**machine.slice(*Bus::Fittings::NAMES))
65
69
  @cpu = Chips::CPU.new(@address_bus, debug:)
70
+ @cpu.power_on!
66
71
  @vic = @address_bus.vic
67
72
  @vic.open_bus = -> { @address_bus.ram.peek(@cpu.program_counter) }
68
73
  @cia1 = @address_bus.cia1
@@ -80,6 +85,7 @@ module Badline
80
85
  @freeze_writes = 0
81
86
  @init_handlers = []
82
87
  @pending_keys = nil
88
+ @init_keys = nil
83
89
  @dos = nil
84
90
  @serial_trap = nil
85
91
  @save_trap = nil
@@ -140,6 +146,7 @@ module Badline
140
146
  vic.power_on!
141
147
  address_bus.power_on!
142
148
  reset!
149
+ cpu.power_on!
143
150
  end
144
151
 
145
152
  # The RES line reaches the CPU and its port, both CIAs, the SID, the
@@ -71,7 +71,9 @@ module Badline
71
71
  out.int("refresh", @refresh)
72
72
  pending = @pending_keys
73
73
  out.ints("host.pending_keys", pending) if pending
74
+ save_init_keys(out)
74
75
  out.int("host.init_pending", @cycles <= @init_threshold ? @init_handlers.length : 0)
76
+ save_ieee_drive_fields(out)
75
77
  end
76
78
 
77
79
  def load_machine_fields(input)
@@ -82,7 +84,9 @@ module Badline
82
84
  @refresh = input.int("refresh", 0).clamp(0, REFRESH_COUNT)
83
85
  pending = input.ints("host.pending_keys", [])
84
86
  @pending_keys = pending.empty? ? nil : pending.dup
87
+ load_init_keys(input)
85
88
  @init_handlers_lost = [input.int("host.init_pending", 0) - @init_handlers.length, 0].max
89
+ load_ieee_drive_fields(input)
86
90
  end
87
91
  end
88
92
 
@@ -55,6 +55,8 @@ module Badline
55
55
 
56
56
  def family = :cbm2
57
57
 
58
+ def portless_board = nil
59
+
58
60
  def peek(addr) = @bus.peek(addr)
59
61
 
60
62
  def poke(addr, value) = @bus.poke(addr, value)
@@ -71,8 +73,9 @@ module Badline
71
73
  def region = @model.region
72
74
 
73
75
  # +model+ names one of Model::ALL, and +mains_hz+ the mains, 50 or
74
- # 60, the model's region's unless given.
75
- def initialize(model: Model::DEFAULT, mains_hz: nil, debug: false)
76
+ # 60, the model's region's unless given. +true_drive_model+ names the
77
+ # drive plug_true_drive plugs in (Drives::IEEEModels).
78
+ def initialize(model: Model::DEFAULT, mains_hz: nil, debug: false, true_drive_model: Drives::IEEEModels::DEFAULT)
76
79
  @model = Model.named(model)
77
80
  @mains_hz = mains_hz || @model.region.mains_hz
78
81
  raise ArgumentError, "the mains is 50 or 60 Hz, not #{@mains_hz}" unless [50, 60].include?(@mains_hz)
@@ -101,6 +104,7 @@ module Badline
101
104
  keyboard_ports.connect(@tpi2)
102
105
  map_io
103
106
  @cpu = CPU.new(@bus, debug:)
107
+ @cpu.power_on!
104
108
  @cycles = 0
105
109
  @mains_half_period = clock_hz / (2 * @mains_hz)
106
110
  @mains_countdown = @mains_half_period
@@ -112,10 +116,11 @@ module Badline
112
116
  @acia_due = Chips::ACIA::NEVER
113
117
  @init_handlers = []
114
118
  @pending_keys = nil
119
+ @init_keys = nil
115
120
  @dos = nil
116
121
  @ieee_trap = nil
117
122
  @capture_output = nil
118
- init_ieee_drives
123
+ init_ieee_drives(true_drive_model)
119
124
  @init_threshold = INIT_THRESHOLD
120
125
  update_video_bus
121
126
  end
@@ -209,6 +214,7 @@ module Badline
209
214
  @color_ram.clear!
210
215
  @vic.power_on!
211
216
  reset!
217
+ @cpu.power_on!
212
218
  end
213
219
 
214
220
  # The cycle by which the KERNAL has tested segments 0 and 1 and BASIC
data/lib/badline/cbm2.rb CHANGED
@@ -74,6 +74,8 @@ module Badline
74
74
 
75
75
  def family = :cbm2
76
76
 
77
+ def portless_board = nil
78
+
77
79
  # Reads and writes memory as the CPU sees it, in its execution segment.
78
80
  def peek(addr) = @bus.peek(addr)
79
81
 
@@ -94,8 +96,10 @@ module Badline
94
96
  # +model+ names one of Model::ALL, +kernal+ one of Model::KERNALS, and
95
97
  # +mains_hz+ the mains, 50 or 60, which also sets a 600's jumper for
96
98
  # the editor's 60 Hz CRTC table. +refresh+ false leaves out the DRAM
97
- # refresh's stolen cycles.
98
- def initialize(model: Model::DEFAULT, kernal: "04a", mains_hz: 50, refresh: true, debug: false)
99
+ # refresh's stolen cycles. +true_drive_model+ names the drive
100
+ # plug_true_drive plugs in (Drives::IEEEModels).
101
+ def initialize(model: Model::DEFAULT, kernal: "04a", mains_hz: 50, refresh: true, debug: false,
102
+ true_drive_model: Drives::IEEEModels::DEFAULT)
99
103
  @model = Model.named(model)
100
104
  raise ArgumentError, "no CBM-II KERNAL named #{kernal}" unless Model::KERNALS.key?(kernal)
101
105
  raise ArgumentError, "the mains is 50 or 60 Hz, not #{mains_hz}" unless [50, 60].include?(mains_hz)
@@ -121,6 +125,7 @@ module Badline
121
125
  keyboard_ports.connect(@tpi2)
122
126
  map_io
123
127
  @cpu = CPU.new(@bus, debug:)
128
+ @cpu.power_on!
124
129
  @cycles = 0
125
130
  @mains_half_period = CLOCK_HZ / (2 * mains_hz)
126
131
  @mains_countdown = @mains_half_period
@@ -131,10 +136,11 @@ module Badline
131
136
  @acia_due = Chips::ACIA::NEVER
132
137
  @init_handlers = []
133
138
  @pending_keys = nil
139
+ @init_keys = nil
134
140
  @dos = nil
135
141
  @ieee_trap = nil
136
142
  @capture_output = nil
137
- init_ieee_drives
143
+ init_ieee_drives(true_drive_model)
138
144
  @init_threshold = boot_cycles
139
145
  end
140
146
 
@@ -221,6 +227,7 @@ module Badline
221
227
  def power_cycle!
222
228
  @bus.power_on!
223
229
  reset!
230
+ @cpu.power_on!
224
231
  end
225
232
 
226
233
  private
@@ -52,7 +52,7 @@ module Badline
52
52
  end
53
53
 
54
54
  def cycle!(feed)
55
- feed &&= @control.value & 0x01 != 0
55
+ feed &&= @control.value.anybits?(0x01)
56
56
  if @settled
57
57
  return @counter -= 1 if feed && @counter > 1
58
58
  elsif @empty
@@ -102,7 +102,7 @@ module Badline
102
102
  private
103
103
 
104
104
  def started?
105
- @control.value & 0x01 != 0
105
+ @control.value.anybits?(0x01)
106
106
  end
107
107
 
108
108
  # Where fast_forward lands +cycles+ after an underflow, with
@@ -25,8 +25,8 @@ module Badline
25
25
  include Boundary
26
26
  include KeyedState
27
27
 
28
- attr_reader :memory, :instructions, :boundary_crossed, :cycles, :pending_write
29
- attr_accessor :program_counter, :stack_pointer, :status, :a, :x, :y, :nmi, :irq
28
+ attr_reader :memory, :instructions, :boundary_crossed, :cycles, :pending_write, :program_counter
29
+ attr_accessor :stack_pointer, :status, :a, :x, :y, :nmi, :irq
30
30
 
31
31
  alias pending_write? pending_write
32
32
 
@@ -56,15 +56,32 @@ module Badline
56
56
  end_sequence
57
57
  end
58
58
 
59
+ # Clears the registers and runs the reset sequence from its first
60
+ # stack read, the two reads at the program counter having run while
61
+ # the reset circuit held RES. The first opcode fetch comes 5 cycles
62
+ # after power-on, where x64sc's does.
63
+ def power_on!
64
+ @program_counter = @stack_pointer = 0
65
+ @a = @x = @y = 0
66
+ reset!
67
+ @index = 2
68
+ end
69
+
70
+ # Starts the reset sequence, RESET_PLAN, which reads the program
71
+ # counter from $FFFC and fetches the first opcode 7 cycles later. A,
72
+ # X and Y keep their values, and S ends 3 lower.
59
73
  def reset!
60
74
  status.interrupt = true
61
- reset_registers
62
75
  @nmi = false
63
76
  @irq_sample = @irq_pending = false
64
77
  @nmi_sample = @nmi_pending = false
65
78
  @skip_poll = false
66
79
  @pending_write = false
67
- end_sequence
80
+ @interrupt = 0xfffc
81
+ @brk = false
82
+ @plan = RESET_PLAN
83
+ @writes = RESET_WRITES
84
+ @index = 0
68
85
  end
69
86
 
70
87
  # Drops the instruction or interrupt sequence in flight and the
@@ -83,6 +100,14 @@ module Badline
83
100
  @pending_write = false
84
101
  end
85
102
 
103
+ # Sets the program counter. A CPU in its reset sequence leaves it,
104
+ # so the next cycle fetches an opcode at +address+ and not at the
105
+ # reset vector's.
106
+ def program_counter=(address)
107
+ @program_counter = address
108
+ end_sequence if @plan.equal?(RESET_PLAN)
109
+ end
110
+
86
111
  def p
87
112
  status.value
88
113
  end
@@ -40,7 +40,7 @@ module Badline
40
40
 
41
41
  def update_number_flags(value)
42
42
  status.zero = value.zero?
43
- status.negative = value & 0x80 != 0
43
+ status.negative = value.anybits?(0x80)
44
44
  value
45
45
  end
46
46
 
@@ -82,6 +82,15 @@ module Badline
82
82
  @nmi = false if @interrupt == 0xfffa
83
83
  end
84
84
 
85
+ def op_reset_dummy
86
+ @memory.peek(@program_counter)
87
+ end
88
+
89
+ def op_reset_stack
90
+ @memory.peek(stack_address)
91
+ @stack_pointer = (@stack_pointer - 1) & 0xff
92
+ end
93
+
85
94
  def op_int_vector_low
86
95
  @address = @memory.peek(@interrupt)
87
96
  end
@@ -4,14 +4,17 @@ module Badline
4
4
  module Chips
5
5
  class CPU
6
6
  # Where a CPU caught inside an instruction is, as a snapshot names it:
7
- # an opcode's instruction, an IRQ or NMI sequence, or jammed.
8
- POSITIONS = %w[boundary op interrupt jam].freeze
7
+ # an opcode's instruction, an IRQ or NMI sequence, jammed, or the
8
+ # reset sequence.
9
+ POSITIONS = %w[boundary op interrupt jam reset].freeze
9
10
 
10
11
  # The CPU's fields in a keyed snapshot module (Snapshot::Keyed): the
11
12
  # registers, the interrupt lines' pipeline, the counts and, inside an
12
13
  # instruction, where it is in hardware terms (pos.*), rather than as
13
14
  # the microcode lays it out.
14
15
  module KeyedState
16
+ SEQUENCE_NAMES = { INTERRUPT => "interrupt", RESET => "reset" }.freeze
17
+
15
18
  def save_fields(out)
16
19
  out.int("pc", @program_counter).int("sp", @stack_pointer).int("a", @a).int("x", @x).int("y", @y)
17
20
  out.int("p", @status.value).bool("nmi", @nmi).bool("so_high", @so_high)
@@ -75,8 +78,8 @@ module Badline
75
78
  return
76
79
  end
77
80
 
78
- out.text("pos.seq", source == INTERRUPT ? "interrupt" : "op")
79
- out.int("pos.op", source) unless source == INTERRUPT
81
+ out.text("pos.seq", SEQUENCE_NAMES.fetch(source, "op"))
82
+ out.int("pos.op", source) unless SEQUENCE_NAMES.key?(source)
80
83
  out.int("pos.t", position_t(source)).bool("pos.crossed", @boundary_crossed)
81
84
  out.bool("pos.branch_taken", @branch_taken == true).bool("pos.brk", @brk)
82
85
  out.optional_int("pos.vector", @interrupt)
@@ -88,7 +91,7 @@ module Badline
88
91
  # The cycles of the instruction or sequence run so far, counting the
89
92
  # opcode fetch as 1 and leaving out stalled cycles.
90
93
  def position_t(source)
91
- return @index if source == INTERRUPT
94
+ return @index if SEQUENCE_NAMES.key?(source)
92
95
 
93
96
  MICROCODE.fetch(source).t_at(@index, @boundary_crossed)
94
97
  end
@@ -96,7 +99,8 @@ module Badline
96
99
  def load_position(input)
97
100
  case input.choice("pos.seq", POSITIONS, "boundary")
98
101
  when "op" then load_instruction(input)
99
- when "interrupt" then load_interrupt(input)
102
+ when "interrupt" then load_sequence(input, INTERRUPT_PLAN, INTERRUPT_WRITES, 1)
103
+ when "reset" then load_sequence(input, RESET_PLAN, RESET_WRITES, 0)
100
104
  when "jam" then load_jam
101
105
  else load_boundary
102
106
  end
@@ -112,10 +116,11 @@ module Badline
112
116
  load_latches(input)
113
117
  end
114
118
 
115
- def load_interrupt(input)
116
- use_plan(INTERRUPT_PLAN, INTERRUPT_WRITES)
119
+ # An interrupt or reset sequence, whose plan starts at step +first+.
120
+ def load_sequence(input, plan, writes, first)
121
+ use_plan(plan, writes)
117
122
  @boundary_crossed = input.bool("pos.crossed", false)
118
- @index = input.int("pos.t", 1).clamp(1, INTERRUPT_PLAN.length - 1)
123
+ @index = input.int("pos.t", first).clamp(first, plan.length - 1)
119
124
  load_latches(input)
120
125
  end
121
126
 
@@ -162,6 +167,7 @@ module Badline
162
167
  return FETCH if @plan.equal?(FETCH_PLAN)
163
168
  return INTERRUPT if @plan.equal?(INTERRUPT_PLAN)
164
169
  return JAMMED if @plan.equal?(JAMMED_PLAN)
170
+ return RESET if @plan.equal?(RESET_PLAN)
165
171
 
166
172
  MICROCODE.index { |micro| micro.plan.equal?(@plan) && micro.operation == @operation } ||
167
173
  MICROCODE.index { |micro| micro.plan.equal?(@plan) }
@@ -95,6 +95,14 @@ module Badline
95
95
  op_int_vector_low op_int_vector_high
96
96
  ].freeze
97
97
 
98
+ # RES runs BRK's sequence with its three stack writes turned into
99
+ # reads, which still count S down, and reads the vector at $FFFC. Its
100
+ # first two cycles read the program counter and leave it where it was.
101
+ RESET_PLAN = %i[
102
+ op_reset_dummy op_reset_dummy op_reset_stack op_reset_stack op_reset_stack
103
+ op_int_vector_low op_int_vector_high
104
+ ].freeze
105
+
98
106
  SPECIAL_PLANS = {
99
107
  brk: BRK_PLAN, jam: JAM_PLAN, jsr: JSR_PLAN, rts: RTS_PLAN,
100
108
  rti: RTI_PLAN, pha: PUSH_PLAN, php: PUSH_PLAN,
@@ -181,6 +189,7 @@ module Badline
181
189
  FETCH_WRITES = Microcode.write_mask(FETCH_PLAN)
182
190
  INTERRUPT_WRITES = Microcode.write_mask(INTERRUPT_PLAN)
183
191
  JAMMED_WRITES = Microcode.write_mask(JAMMED_PLAN)
192
+ RESET_WRITES = Microcode.write_mask(RESET_PLAN)
184
193
  MICROCODE = Microcode.table
185
194
 
186
195
  # The instruction methods, in a fixed order a snapshot names them by.
@@ -190,6 +199,7 @@ module Badline
190
199
  FETCH = -1
191
200
  INTERRUPT = -2
192
201
  JAMMED = -3
202
+ RESET = -4
193
203
  end
194
204
  end
195
205
  end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class M6809
6
+ # The ALU: each method returns its result and sets the flags it
7
+ # changes.
8
+ module Arithmetic
9
+ private
10
+
11
+ def flags(clear, set)
12
+ @cc = (@cc & ~clear & 0xff) | set
13
+ end
14
+
15
+ def nz8(result) = (result.nobits?(0x80) ? 0 : NEGATIVE) | (result.zero? ? ZERO : 0)
16
+
17
+ def nz16(result) = (result.nobits?(0x8000) ? 0 : NEGATIVE) | (result.zero? ? ZERO : 0)
18
+
19
+ # Loads, stores, AND, OR, EOR and TST: N and Z, V cleared.
20
+ def logic8(result)
21
+ flags(NEGATIVE | ZERO | OVERFLOW, nz8(result))
22
+ result
23
+ end
24
+
25
+ def logic16(result)
26
+ flags(NEGATIVE | ZERO | OVERFLOW, nz16(result))
27
+ result
28
+ end
29
+
30
+ def add8(left, right, carry)
31
+ sum = left + right + carry
32
+ result = sum & 0xff
33
+ overflow = ((left ^ result) & (right ^ result)).nobits?(0x80) ? 0 : OVERFLOW
34
+ half = (left ^ right ^ sum).nobits?(0x10) ? 0 : HALF_CARRY
35
+ flags(HALF_CARRY | NEGATIVE | ZERO | OVERFLOW | CARRY,
36
+ half | nz8(result) | overflow | (sum > 0xff ? CARRY : 0))
37
+ result
38
+ end
39
+
40
+ def sub8(left, right, borrow)
41
+ difference = left - right - borrow
42
+ result = difference & 0xff
43
+ overflow = ((left ^ right) & (left ^ result)).nobits?(0x80) ? 0 : OVERFLOW
44
+ flags(NEGATIVE | ZERO | OVERFLOW | CARRY, nz8(result) | overflow | (difference.negative? ? CARRY : 0))
45
+ result
46
+ end
47
+
48
+ def add16(left, right)
49
+ sum = left + right
50
+ result = sum & 0xffff
51
+ @alu_word = result
52
+ overflow = ((left ^ result) & (right ^ result)).nobits?(0x8000) ? 0 : OVERFLOW
53
+ flags(NEGATIVE | ZERO | OVERFLOW | CARRY, nz16(result) | overflow | (sum > 0xffff ? CARRY : 0))
54
+ result
55
+ end
56
+
57
+ def sub16(left, right)
58
+ difference = left - right
59
+ result = difference & 0xffff
60
+ @alu_word = result
61
+ overflow = ((left ^ right) & (left ^ result)).nobits?(0x8000) ? 0 : OVERFLOW
62
+ flags(NEGATIVE | ZERO | OVERFLOW | CARRY, nz16(result) | overflow | (difference.negative? ? CARRY : 0))
63
+ result
64
+ end
65
+
66
+ def carry = @cc & CARRY
67
+
68
+ def neg8(value) = sub8(0, value, 0)
69
+
70
+ def com8(value)
71
+ result = ~value & 0xff
72
+ flags(NEGATIVE | ZERO | OVERFLOW | CARRY, nz8(result) | CARRY)
73
+ result
74
+ end
75
+
76
+ # The undocumented XNC: NEG with C clear, COM with C set.
77
+ def xnc8(value) = carry.zero? ? neg8(value) : com8(value)
78
+
79
+ def lsr8(value)
80
+ result = value >> 1
81
+ flags(NEGATIVE | ZERO | CARRY, nz8(result) | (value & 1))
82
+ result
83
+ end
84
+
85
+ def ror8(value)
86
+ result = (value >> 1) | (carry << 7)
87
+ flags(NEGATIVE | ZERO | CARRY, nz8(result) | (value & 1))
88
+ result
89
+ end
90
+
91
+ def asr8(value)
92
+ result = (value >> 1) | (value & 0x80)
93
+ flags(NEGATIVE | ZERO | CARRY, nz8(result) | (value & 1))
94
+ result
95
+ end
96
+
97
+ def asl8(value) = rotate_left8(value, 0)
98
+
99
+ def rol8(value) = rotate_left8(value, carry)
100
+
101
+ def rotate_left8(value, low)
102
+ result = ((value << 1) | low) & 0xff
103
+ overflow = (value ^ (value << 1)).nobits?(0x80) ? 0 : OVERFLOW
104
+ flags(NEGATIVE | ZERO | OVERFLOW | CARRY, nz8(result) | overflow | (value >> 7))
105
+ result
106
+ end
107
+
108
+ def dec8(value)
109
+ result = (value - 1) & 0xff
110
+ flags(NEGATIVE | ZERO | OVERFLOW, nz8(result) | (value == 0x80 ? OVERFLOW : 0))
111
+ result
112
+ end
113
+
114
+ # The undocumented XDEC: DEC, with C set unless the operand was 0.
115
+ def xdec8(value)
116
+ result = dec8(value)
117
+ flags(CARRY, value.zero? ? 0 : CARRY)
118
+ result
119
+ end
120
+
121
+ def inc8(value)
122
+ result = (value + 1) & 0xff
123
+ flags(NEGATIVE | ZERO | OVERFLOW, nz8(result) | (value == 0x7f ? OVERFLOW : 0))
124
+ result
125
+ end
126
+
127
+ def clr8
128
+ flags(NEGATIVE | ZERO | OVERFLOW | CARRY, ZERO)
129
+ 0
130
+ end
131
+
132
+ # The undocumented XCLRA and XCLRB: CLR, but C stays.
133
+ def xclr8
134
+ flags(NEGATIVE | ZERO | OVERFLOW, ZERO)
135
+ 0
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end