badline 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +153 -0
  3. data/CLAUDE.md +11 -7
  4. data/CONTRIBUTING.md +7 -1
  5. data/README.md +169 -20
  6. data/doc/pinned-behaviour.md +128 -0
  7. data/doc/snapshot-format.md +253 -18
  8. data/doc/snapshots.md +57 -23
  9. data/exe/badline-ruby +5 -0
  10. data/lib/badline/audio/terminal.rb +34 -2
  11. data/lib/badline/c128/keyed_state.rb +2 -0
  12. data/lib/badline/c128.rb +1 -0
  13. data/lib/badline/c64/keyed_state.rb +2 -0
  14. data/lib/badline/c64.rb +1 -0
  15. data/lib/badline/cbm2/keyed_state.rb +4 -0
  16. data/lib/badline/cbm2/p500.rb +5 -3
  17. data/lib/badline/cbm2.rb +6 -3
  18. data/lib/badline/chips/cia/timer.rb +2 -2
  19. data/lib/badline/chips/cpu/instruction_set.rb +1 -1
  20. data/lib/badline/chips/m6809/arithmetic.rb +140 -0
  21. data/lib/badline/chips/m6809/bus_steps.rb +223 -0
  22. data/lib/badline/chips/m6809/core.rb +149 -0
  23. data/lib/badline/chips/m6809/indexed.rb +82 -0
  24. data/lib/badline/chips/m6809/interrupts.rb +159 -0
  25. data/lib/badline/chips/m6809/keyed_state.rb +147 -0
  26. data/lib/badline/chips/m6809/opcodes.rb +159 -0
  27. data/lib/badline/chips/m6809/operations.rb +258 -0
  28. data/lib/badline/chips/m6809/plans.rb +143 -0
  29. data/lib/badline/chips/m6809/registers.rb +82 -0
  30. data/lib/badline/chips/m6809/stack.rb +140 -0
  31. data/lib/badline/chips/m6809.rb +13 -0
  32. data/lib/badline/chips/riot/timer.rb +14 -3
  33. data/lib/badline/chips/rriot.rb +4 -2
  34. data/lib/badline/chips/sid/filter.rb +4 -4
  35. data/lib/badline/chips/sid/waveform.rb +6 -6
  36. data/lib/badline/chips/vic_i/keyed_parts.rb +7 -2
  37. data/lib/badline/chips/vic_i.rb +84 -21
  38. data/lib/badline/data_path.rb +3 -3
  39. data/lib/badline/drives/ieee_models.rb +54 -0
  40. data/lib/badline/drives/model1001.rb +37 -0
  41. data/lib/badline/drives/model2031.rb +6 -0
  42. data/lib/badline/drives/model4040/disk.rb +62 -0
  43. data/lib/badline/drives/model4040/ip_bus.rb +46 -0
  44. data/lib/badline/drives/model4040/keyed_state.rb +57 -0
  45. data/lib/badline/drives/model4040/mechanism.rb +192 -0
  46. data/lib/badline/drives/model4040/parts.rb +20 -0
  47. data/lib/badline/drives/model4040.rb +179 -0
  48. data/lib/badline/drives/model8050/board.rb +77 -0
  49. data/lib/badline/drives/model8050/dos_board.rb +91 -0
  50. data/lib/badline/drives/model8050/idle.rb +11 -7
  51. data/lib/badline/drives/model8050/keyed_state.rb +5 -2
  52. data/lib/badline/drives/model8050.rb +18 -130
  53. data/lib/badline/drives/model9060/idle.rb +149 -0
  54. data/lib/badline/drives/model9060/keyed_state.rb +123 -0
  55. data/lib/badline/drives/model9060/parts.rb +98 -0
  56. data/lib/badline/drives/model9060/sasi_bus.rb +88 -0
  57. data/lib/badline/drives/model9060/sasi_commands.rb +168 -0
  58. data/lib/badline/drives/model9060/sasi_target.rb +161 -0
  59. data/lib/badline/drives/model9060.rb +195 -0
  60. data/lib/badline/frontend/app.rb +8 -0
  61. data/lib/badline/frontend/boot.rb +3 -2
  62. data/lib/badline/frontend/controls.rb +62 -6
  63. data/lib/badline/frontend/keys.rb +36 -0
  64. data/lib/badline/frontend/kim1_panel.rb +208 -0
  65. data/lib/badline/frontend/media_slots.rb +8 -1
  66. data/lib/badline/frontend/menu/drive_types.rb +56 -0
  67. data/lib/badline/frontend/menu/menu_dialogs.rb +61 -14
  68. data/lib/badline/frontend/menu/menu_media.rb +42 -12
  69. data/lib/badline/frontend/menu/menu_pages.rb +97 -2
  70. data/lib/badline/frontend/menu/menu_ports.rb +38 -1
  71. data/lib/badline/frontend/menu/pause_menu.rb +5 -1
  72. data/lib/badline/frontend/pet_keyboard.rb +23 -1
  73. data/lib/badline/frontend/screen.rb +1 -1
  74. data/lib/badline/frontend/screens.rb +40 -2
  75. data/lib/badline/frontend/teletype_pane.rb +152 -0
  76. data/lib/badline/frontend/timeline.rb +31 -2
  77. data/lib/badline/frontend.rb +3 -0
  78. data/lib/badline/kim1/bus.rb +117 -0
  79. data/lib/badline/kim1/cpu.rb +25 -0
  80. data/lib/badline/kim1/host_terminal.rb +74 -0
  81. data/lib/badline/kim1/key_entry.rb +74 -0
  82. data/lib/badline/kim1/keyed_modules.rb +53 -0
  83. data/lib/badline/kim1/keyed_state.rb +133 -0
  84. data/lib/badline/kim1/panel.rb +176 -0
  85. data/lib/badline/kim1/rriots.rb +28 -0
  86. data/lib/badline/kim1/sound.rb +29 -0
  87. data/lib/badline/kim1/tape.rb +116 -0
  88. data/lib/badline/kim1/teletype.rb +193 -0
  89. data/lib/badline/kim1.rb +190 -0
  90. data/lib/badline/machine/ieee_drives.rb +94 -117
  91. data/lib/badline/machine/keyboard_buffer.rb +20 -3
  92. data/lib/badline/machine/line_sound.rb +122 -0
  93. data/lib/badline/machine.rb +46 -15
  94. data/lib/badline/media/extensions.rb +8 -4
  95. data/lib/badline/media/kim1_media.rb +150 -0
  96. data/lib/badline/media/pet_media.rb +3 -1
  97. data/lib/badline/media/true_drive.rb +42 -45
  98. data/lib/badline/media.rb +12 -6
  99. data/lib/badline/options/family.rb +98 -8
  100. data/lib/badline/options/help.rb +44 -12
  101. data/lib/badline/options/table.rb +43 -11
  102. data/lib/badline/options/validation.rb +44 -8
  103. data/lib/badline/options.rb +27 -8
  104. data/lib/badline/pet/bus.rb +16 -2
  105. data/lib/badline/pet/keyboard_ports.rb +9 -2
  106. data/lib/badline/pet/keyed_state.rb +4 -0
  107. data/lib/badline/pet/model.rb +11 -2
  108. data/lib/badline/pet/rom_sets.rb +1 -0
  109. data/lib/badline/pet/sound.rb +5 -105
  110. data/lib/badline/pet/superpet/acia.rb +12 -0
  111. data/lib/badline/pet/superpet/board.rb +221 -0
  112. data/lib/badline/pet/superpet/dongle.rb +81 -0
  113. data/lib/badline/pet/superpet/keyed_modules.rb +63 -0
  114. data/lib/badline/pet/superpet/keyed_parts.rb +76 -0
  115. data/lib/badline/pet/superpet/m6809.rb +32 -0
  116. data/lib/badline/pet/superpet/waterloo_traps.rb +199 -0
  117. data/lib/badline/pet/superpet.rb +216 -0
  118. data/lib/badline/pet.rb +19 -3
  119. data/lib/badline/plus4/keyed_state.rb +2 -0
  120. data/lib/badline/plus4.rb +1 -0
  121. data/lib/badline/roms/1001/fdc-251257-02a.rom +0 -0
  122. data/lib/badline/roms/4040/dos-1.2.rom +0 -0
  123. data/lib/badline/roms/4040/dos-2.1-rev.rom +0 -0
  124. data/lib/badline/roms/4040/dos-2.1.rom +0 -0
  125. data/lib/badline/roms/4040/fdc-901466-02.rom +0 -0
  126. data/lib/badline/roms/4040/fdc-901466-04.rom +0 -0
  127. data/lib/badline/roms/9060/dos-3.0.rom +0 -0
  128. data/lib/badline/roms/9060/sasi-300515-revb.rom +0 -0
  129. data/lib/badline/roms/README +166 -0
  130. data/lib/badline/roms/kim1/6530-002.rom +0 -0
  131. data/lib/badline/roms/kim1/6530-003.rom +0 -0
  132. data/lib/badline/roms/pet/characters-superpet.rom +0 -0
  133. data/lib/badline/roms/pet/superpet-a000.rom +0 -0
  134. data/lib/badline/roms/pet/superpet-c000.rom +0 -0
  135. data/lib/badline/roms/pet/superpet-e000-50hz.rom +0 -0
  136. data/lib/badline/roms/pet/superpet-e000.rom +0 -0
  137. data/lib/badline/snapshot/container.rb +2 -0
  138. data/lib/badline/snapshot/field_doc/markdown.rb +1 -1
  139. data/lib/badline/snapshot/field_doc.rb +27 -6
  140. data/lib/badline/snapshot/image.rb +4 -1
  141. data/lib/badline/snapshot/kim1_setup.rb +28 -0
  142. data/lib/badline/snapshot/pet_setup.rb +27 -11
  143. data/lib/badline/snapshot/storage_setup.rb +4 -1
  144. data/lib/badline/snapshot/vic20_setup.rb +11 -7
  145. data/lib/badline/snapshot/vice/pet.rb +13 -7
  146. data/lib/badline/snapshot/vice/pet_cpu.rb +39 -5
  147. data/lib/badline/snapshot/vice/pet_crtc.rb +15 -2
  148. data/lib/badline/snapshot/vice/pet_drive8050.rb +188 -68
  149. data/lib/badline/snapshot/vice/pet_mem.rb +49 -5
  150. data/lib/badline/snapshot/vice/pet_restore.rb +40 -11
  151. data/lib/badline/snapshot.rb +4 -0
  152. data/lib/badline/status.rb +30 -30
  153. data/lib/badline/storage/d67_image.rb +55 -0
  154. data/lib/badline/storage/d90_image.rb +180 -0
  155. data/lib/badline/storage/disk_image/writing.rb +2 -2
  156. data/lib/badline/storage/kim_tape.rb +254 -0
  157. data/lib/badline/storage/papertape.rb +90 -0
  158. data/lib/badline/storage.rb +4 -0
  159. data/lib/badline/support/kim1_checkpoint.rb +41 -0
  160. data/lib/badline/support/superpet_checkpoint.rb +35 -0
  161. data/lib/badline/version.rb +1 -1
  162. data/lib/badline/vic20/keyed_modules.rb +6 -3
  163. data/lib/badline/vic20/keyed_state.rb +6 -4
  164. data/lib/badline/vic20.rb +26 -12
  165. data/lib/badline.rb +1 -0
  166. metadata +77 -1
@@ -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
 
@@ -71,8 +71,9 @@ module Badline
71
71
  def region = @model.region
72
72
 
73
73
  # +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)
74
+ # 60, the model's region's unless given. +true_drive_model+ names the
75
+ # drive plug_true_drive plugs in (Drives::IEEEModels).
76
+ def initialize(model: Model::DEFAULT, mains_hz: nil, debug: false, true_drive_model: Drives::IEEEModels::DEFAULT)
76
77
  @model = Model.named(model)
77
78
  @mains_hz = mains_hz || @model.region.mains_hz
78
79
  raise ArgumentError, "the mains is 50 or 60 Hz, not #{@mains_hz}" unless [50, 60].include?(@mains_hz)
@@ -112,10 +113,11 @@ module Badline
112
113
  @acia_due = Chips::ACIA::NEVER
113
114
  @init_handlers = []
114
115
  @pending_keys = nil
116
+ @init_keys = nil
115
117
  @dos = nil
116
118
  @ieee_trap = nil
117
119
  @capture_output = nil
118
- init_ieee_drives
120
+ init_ieee_drives(true_drive_model)
119
121
  @init_threshold = INIT_THRESHOLD
120
122
  update_video_bus
121
123
  end
data/lib/badline/cbm2.rb CHANGED
@@ -94,8 +94,10 @@ module Badline
94
94
  # +model+ names one of Model::ALL, +kernal+ one of Model::KERNALS, and
95
95
  # +mains_hz+ the mains, 50 or 60, which also sets a 600's jumper for
96
96
  # 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)
97
+ # refresh's stolen cycles. +true_drive_model+ names the drive
98
+ # plug_true_drive plugs in (Drives::IEEEModels).
99
+ def initialize(model: Model::DEFAULT, kernal: "04a", mains_hz: 50, refresh: true, debug: false,
100
+ true_drive_model: Drives::IEEEModels::DEFAULT)
99
101
  @model = Model.named(model)
100
102
  raise ArgumentError, "no CBM-II KERNAL named #{kernal}" unless Model::KERNALS.key?(kernal)
101
103
  raise ArgumentError, "the mains is 50 or 60 Hz, not #{mains_hz}" unless [50, 60].include?(mains_hz)
@@ -131,10 +133,11 @@ module Badline
131
133
  @acia_due = Chips::ACIA::NEVER
132
134
  @init_handlers = []
133
135
  @pending_keys = nil
136
+ @init_keys = nil
134
137
  @dos = nil
135
138
  @ieee_trap = nil
136
139
  @capture_output = nil
137
- init_ieee_drives
140
+ init_ieee_drives(true_drive_model)
138
141
  @init_threshold = boot_cycles
139
142
  end
140
143
 
@@ -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
@@ -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
 
@@ -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
@@ -0,0 +1,223 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class M6809
6
+ PREFIX_PLAN = %i[fetch_paged].freeze
7
+
8
+ # The steps that fetch opcodes and operands, work out direct and
9
+ # extended addresses and branch targets, and read and write the
10
+ # operand, one bus cycle each.
11
+ module BusSteps
12
+ private
13
+
14
+ def read_pc
15
+ value = @bus.read(@pc)
16
+ @pc = (@pc + 1) & 0xffff
17
+ value
18
+ end
19
+
20
+ def fetch
21
+ return halted_cycle if @halt_seen
22
+ return take_interrupt if interrupt_due?
23
+
24
+ opcode = read_pc
25
+ @opcode = opcode
26
+ case opcode
27
+ when 0x10
28
+ @page = PAGE2
29
+ switch_to(PREFIX_PLAN)
30
+ when 0x11
31
+ @page = PAGE3
32
+ switch_to(PREFIX_PLAN)
33
+ else start(PAGE1[opcode])
34
+ end
35
+ end
36
+
37
+ # The opcode after a prefix. A further prefix only costs a cycle.
38
+ def fetch_paged
39
+ opcode = read_pc
40
+ return @index -= 1 if [0x10, 0x11].include?(opcode)
41
+
42
+ @opcode = opcode
43
+ start(@page[opcode])
44
+ end
45
+
46
+ def dead_pc = @bus.read(@pc)
47
+
48
+ def dead_pc_next = @bus.read((@pc + 1) & 0xffff)
49
+
50
+ def dead_pc_next2 = @bus.read((@pc + 2) & 0xffff)
51
+
52
+ def dead_v = @bus.read(0xffff)
53
+
54
+ def dead_ea = @bus.read(@ea)
55
+
56
+ # A cycle without a bus access.
57
+ def idle; end
58
+
59
+ def dead_pc_exec
60
+ dead_pc
61
+ execute
62
+ end
63
+
64
+ # A dead read of the byte after the opcode, which the undocumented
65
+ # $18 uses.
66
+ def next_byte_exec
67
+ @value = @bus.read(@pc)
68
+ execute
69
+ end
70
+
71
+ def dead_v_exec
72
+ dead_v
73
+ execute
74
+ end
75
+
76
+ def direct
77
+ @ea = (@dp << 8) | read_pc
78
+ end
79
+
80
+ def extended_hi
81
+ @ea = read_pc << 8
82
+ end
83
+
84
+ def extended_lo
85
+ @ea |= read_pc
86
+ end
87
+
88
+ def immediate_exec
89
+ @value = read_pc
90
+ execute
91
+ end
92
+
93
+ def immediate_hi
94
+ @value = read_pc << 8
95
+ end
96
+
97
+ def immediate_lo_exec
98
+ @value |= read_pc
99
+ execute
100
+ end
101
+
102
+ def immediate_discard = read_pc
103
+
104
+ # The undocumented 16-bit stores immediate write the register's low
105
+ # byte over the operand's.
106
+ def store_immediate
107
+ execute
108
+ @bus.write(@pc, @value)
109
+ @pc = (@pc + 1) & 0xffff
110
+ end
111
+
112
+ def read_post
113
+ @post = read_pc
114
+ end
115
+
116
+ def read_exec
117
+ @value = @bus.read(@ea)
118
+ execute
119
+ end
120
+
121
+ def read_hi
122
+ @value = @bus.read(@ea) << 8
123
+ end
124
+
125
+ def read_lo_exec
126
+ @value |= @bus.read((@ea + 1) & 0xffff)
127
+ execute
128
+ end
129
+
130
+ def write_exec
131
+ execute
132
+ @bus.write(@ea, @value)
133
+ end
134
+
135
+ def write_hi_exec
136
+ execute
137
+ @bus.write(@ea, @value >> 8)
138
+ end
139
+
140
+ def write_lo
141
+ @bus.write((@ea + 1) & 0xffff, @value & 0xff)
142
+ end
143
+
144
+ def rmw_read
145
+ @value = @bus.read(@ea)
146
+ execute
147
+ end
148
+
149
+ def rmw_write
150
+ @bus.write(@ea, @value)
151
+ end
152
+
153
+ # A short branch's offset, and the target it gives.
154
+ def offset8_branch
155
+ offset = read_pc
156
+ @ea = (@pc + offset - ((offset & 0x80) * 2)) & 0xffff
157
+ end
158
+
159
+ def offset_hi
160
+ @value = read_pc << 8
161
+ end
162
+
163
+ # The low byte of a 16-bit offset from PC, and its target.
164
+ def offset_lo_pc
165
+ @value |= read_pc
166
+ @ea = (@pc + @value) & 0xffff
167
+ end
168
+
169
+ def dead_v_branch
170
+ dead_v
171
+ @pc = @ea if condition?(@opcode)
172
+ end
173
+
174
+ # A long branch taken takes one more cycle.
175
+ def dead_v_long_branch
176
+ dead_v
177
+ switch_to(LONG_BRANCH_TAKEN_PLAN) if condition?(@opcode)
178
+ end
179
+
180
+ def dead_v_take
181
+ dead_v
182
+ @pc = @ea
183
+ end
184
+
185
+ # The undocumented $14, $15 and $CD read on from PC, a byte a cycle,
186
+ # until reset.
187
+ def free_run
188
+ read_pc
189
+ @index -= 1
190
+ end
191
+
192
+ def condition?(opcode)
193
+ cc = @cc
194
+ case opcode & 0x0f
195
+ when 0x0 then true
196
+ when 0x1 then false
197
+ when 0x2 then cc.nobits?(CARRY | ZERO)
198
+ when 0x3 then cc.anybits?(CARRY | ZERO)
199
+ when 0x4 then cc.nobits?(CARRY)
200
+ when 0x5 then cc.anybits?(CARRY)
201
+ when 0x6 then cc.nobits?(ZERO)
202
+ when 0x7 then cc.anybits?(ZERO)
203
+ when 0x8 then cc.nobits?(OVERFLOW)
204
+ when 0x9 then cc.anybits?(OVERFLOW)
205
+ when 0xa then cc.nobits?(NEGATIVE)
206
+ when 0xb then cc.anybits?(NEGATIVE)
207
+ else signed_condition?(opcode, cc)
208
+ end
209
+ end
210
+
211
+ def signed_condition?(opcode, cc)
212
+ less = cc.anybits?(NEGATIVE) != cc.anybits?(OVERFLOW)
213
+ case opcode & 0x0f
214
+ when 0xc then !less
215
+ when 0xd then less
216
+ when 0xe then !less && cc.nobits?(ZERO)
217
+ else less || cc.anybits?(ZERO)
218
+ end
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/chips/m6809/opcodes"
4
+ require "badline/chips/m6809/registers"
5
+ require "badline/chips/m6809/bus_steps"
6
+ require "badline/chips/m6809/indexed"
7
+ require "badline/chips/m6809/stack"
8
+ require "badline/chips/m6809/interrupts"
9
+ require "badline/chips/m6809/arithmetic"
10
+ require "badline/chips/m6809/operations"
11
+ require "badline/chips/m6809/keyed_state"
12
+
13
+ module Badline
14
+ module Chips
15
+ class M6809
16
+ # The Motorola 6809E, one bus cycle per #cycle!, on whatever bus +bus+
17
+ # is. The bus answers read(address) and write(address, value). Dead
18
+ # cycles are reads like any other, of the addresses Figure 17 of the
19
+ # MC6809E datasheet gives them, and the cycles the CPU leaves the bus
20
+ # alone (SYNC's, and HALT's) call nothing.
21
+ #
22
+ # Each instruction is a plan of steps, one a cycle, as the 6502 core
23
+ # runs them: the opcode fetch picks an Entry, whose address steps
24
+ # run first and whose tail follows.
25
+ module Core
26
+ include Registers
27
+ include BusSteps
28
+ include Indexed
29
+ include Stack
30
+ include Interrupts
31
+ include Arithmetic
32
+ include Operations
33
+ include KeyedState
34
+
35
+ attr_reader :bus, :cycles
36
+
37
+ def initialize(bus)
38
+ @bus = bus
39
+ @cycles = 0
40
+ @a = @b = @dp = 0
41
+ @x = @y = @u = @s = @pc = 0
42
+ @cc = IRQ_MASK | FIRQ_MASK
43
+ @ea = @value = @post = @opcode = 0
44
+ @group = @previous_group = GROUP_OTHER
45
+ @alu_word = 0
46
+ @vector = 0xfffe
47
+ @interrupt_mask = 0
48
+ @stack_u = false
49
+ @operation = :none
50
+ @page = PAGE1
51
+ @tail = NO_TAIL
52
+ clear_interrupts
53
+ reset!
54
+ end
55
+
56
+ # RESET going high: the CPU masks FIRQ and IRQ, clears DP, disarms
57
+ # NMI until S is loaded, and reads its vector from $FFFE.
58
+ def reset!
59
+ @cc |= IRQ_MASK | FIRQ_MASK
60
+ @dp = 0
61
+ @nmi_armed = false
62
+ clear_interrupts
63
+ @tail = NO_TAIL
64
+ @plan = RESET_PLAN
65
+ @index = 0
66
+ end
67
+
68
+ # Drops the instruction in flight so the next cycle fetches an
69
+ # opcode at PC, and sets the cycle count.
70
+ def resume(cycles)
71
+ @cycles = cycles
72
+ clear_interrupts
73
+ boundary
74
+ end
75
+
76
+ def cycle!
77
+ index = @index
78
+ @index = index + 1
79
+ send(@plan[index])
80
+ @cycles += 1
81
+ next_plan if @index >= @plan.length
82
+ sample_lines
83
+ nil
84
+ end
85
+
86
+ # Runs to the end of the instruction or interrupt sequence.
87
+ def step!
88
+ cycle!
89
+ cycle! until boundary? || waiting?
90
+ nil
91
+ end
92
+
93
+ # Whether the next cycle fetches an opcode, or takes an interrupt.
94
+ def boundary? = @index.zero? && @plan.equal?(FETCH_PLAN)
95
+
96
+ def inspect
97
+ format("Cycles: %<cycles>d, PC: %<pc>04x, S: %<s>04x, U: %<u>04x, X: %<x>04x, Y: %<y>04x, " \
98
+ "A: %<a>02x, B: %<b>02x, DP: %<dp>02x, CC: %<cc>02x",
99
+ cycles: @cycles, pc: @pc, s: @s, u: @u, x: @x, y: @y, a: @a, b: @b, dp: @dp, cc: @cc)
100
+ end
101
+
102
+ private
103
+
104
+ def boundary
105
+ @plan = FETCH_PLAN
106
+ @index = 0
107
+ @tail = NO_TAIL
108
+ end
109
+
110
+ # Starts an opcode's plan after its fetch.
111
+ def start(entry)
112
+ @operation = entry.operation
113
+ @previous_group = @group
114
+ @group = entry.group
115
+ @stack_u = false
116
+ plan = entry.plan
117
+ if plan.empty?
118
+ @tail = NO_TAIL
119
+ switch_to(entry.tail)
120
+ else
121
+ @tail = entry.tail
122
+ @plan = plan
123
+ @index = 0
124
+ end
125
+ end
126
+
127
+ # Replaces the rest of the plan.
128
+ def switch_to(plan)
129
+ @plan = plan
130
+ @index = 0
131
+ end
132
+
133
+ def next_plan
134
+ tail = @tail
135
+ return boundary if tail.equal?(NO_TAIL)
136
+
137
+ @tail = NO_TAIL
138
+ return switch_to(tail) unless tail.empty?
139
+
140
+ execute
141
+ boundary
142
+ end
143
+
144
+ # Runs the instruction method of the opcode in flight.
145
+ def execute = send(@operation)
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class M6809
6
+ # Indexed addressing: the postbyte, and the steps INDEXED_PLANS run
7
+ # after it.
8
+ module Indexed
9
+ private
10
+
11
+ # Reads the postbyte, works out every address that needs no more
12
+ # bytes, and moves on to the postbyte's plan. The undefined
13
+ # postbytes $x7, $xA and $xE address $0000 and take no cycles.
14
+ def postbyte
15
+ post = read_pc
16
+ @post = post
17
+ if post < 0x80
18
+ @ea = (index_register + (post & 0x0f) - (post & 0x10)) & 0xffff
19
+ else
20
+ register_address(post & 0x0f)
21
+ end
22
+ plan = INDEXED_PLANS[post]
23
+ return next_plan if plan.empty?
24
+
25
+ switch_to(plan)
26
+ end
27
+
28
+ def register_address(mode)
29
+ case mode
30
+ when 0x0 then post_increment(1)
31
+ when 0x1 then post_increment(2)
32
+ when 0x2 then pre_decrement(1)
33
+ when 0x3 then pre_decrement(2)
34
+ when 0x4 then @ea = index_register
35
+ when 0x5 then @ea = (index_register + signed8(@b)) & 0xffff
36
+ when 0x6 then @ea = (index_register + signed8(@a)) & 0xffff
37
+ when 0xb then @ea = (index_register + d) & 0xffff
38
+ when 0x7, 0xa, 0xe then @ea = 0
39
+ end
40
+ end
41
+
42
+ def post_increment(amount)
43
+ @ea = index_register
44
+ self.index_register = (@ea + amount) & 0xffff
45
+ end
46
+
47
+ def pre_decrement(amount)
48
+ @ea = (index_register - amount) & 0xffff
49
+ self.index_register = @ea
50
+ end
51
+
52
+ def signed8(value) = value - ((value & 0x80) << 1)
53
+
54
+ def offset8
55
+ @ea = (index_register + signed8(read_pc)) & 0xffff
56
+ end
57
+
58
+ def offset8_pc
59
+ offset = signed8(read_pc)
60
+ @ea = (@pc + offset) & 0xffff
61
+ end
62
+
63
+ def offset_lo_register
64
+ @value |= read_pc
65
+ @ea = (index_register + @value) & 0xffff
66
+ end
67
+
68
+ def offset_lo_absolute
69
+ @ea = @value | read_pc
70
+ end
71
+
72
+ def indirect_hi
73
+ @value = @bus.read(@ea) << 8
74
+ end
75
+
76
+ def indirect_lo
77
+ @ea = @value | @bus.read((@ea + 1) & 0xffff)
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end