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,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class M6809
6
+ # Every plan the CPU runs and every tail it keeps, by the name a
7
+ # snapshot gives it: the plans of their own by name, an indexed
8
+ # postbyte's, a PSH's and a PUL's by postbyte, and an opcode's by its
9
+ # page and opcode. A plan that has several names goes by the first.
10
+ module PlanNames
11
+ OWN = { "fetch" => FETCH_PLAN, "reset" => RESET_PLAN, "prefix" => PREFIX_PLAN, "none" => NO_TAIL,
12
+ "jump" => JUMP_TAIL, "interrupt" => INTERRUPT_PLAN, "fast_interrupt" => FAST_INTERRUPT_PLAN,
13
+ "waited_interrupt" => WAITED_INTERRUPT_PLAN, "return_entire" => RETURN_ENTIRE_PLAN,
14
+ "return_fast" => RETURN_FAST_PLAN, "long_branch_taken" => LONG_BRANCH_TAKEN_PLAN,
15
+ "sync_end" => SYNC_END_PLAN }.freeze
16
+
17
+ PAGES = [PAGE1, PAGE2, PAGE3].freeze
18
+
19
+ TABLE = (OWN.to_a +
20
+ { "indexed" => INDEXED_PLANS, "push" => PUSH_PLANS, "pull" => PULL_PLANS }.flat_map do |kind, plans|
21
+ Array.new(256) { |post| ["#{kind}.#{post}", plans[post]] }
22
+ end +
23
+ Array.new(PAGES.length * 256) do |slot|
24
+ entry = PAGES[slot >> 8][slot & 0xff]
25
+ name = "op.#{(slot >> 8) + 1}.#{slot & 0xff}"
26
+ [["#{name}.plan", entry.plan], ["#{name}.tail", entry.tail]]
27
+ end.flatten(1)).freeze
28
+
29
+ NAMES = TABLE.map(&:first).freeze
30
+ PLANS = TABLE.map(&:last).freeze
31
+
32
+ # What the opcodes and the interrupts run, by name.
33
+ OPERATIONS = (%w[none enter_nmi enter_firq enter_irq] +
34
+ PAGES.flat_map { |page| page.map { |entry| entry.operation.to_s } }).uniq.freeze
35
+
36
+ module_function
37
+
38
+ def name_of(plan)
39
+ index = PLANS.index { |each_plan| each_plan.equal?(plan) }
40
+ index.nil? ? "fetch" : NAMES[index]
41
+ end
42
+
43
+ def named(name)
44
+ index = NAMES.index(name)
45
+ index.nil? ? nil : PLANS[index]
46
+ end
47
+ end
48
+
49
+ # The CPU's fields in a keyed snapshot module (Snapshot::Keyed): the
50
+ # registers, the interrupt lines and what the CPU has sampled of
51
+ # them, the cycle count, the working registers the steps share, and
52
+ # off an instruction boundary the plan running, the step in it and
53
+ # the tail to follow, by PlanNames. A plan it doesn't know puts the
54
+ # CPU on a boundary.
55
+ module KeyedState
56
+ def save_fields(out)
57
+ out.int("a", @a).int("b", @b).int("x", @x).int("y", @y).int("u", @u).int("s", @s).int("pc", @pc)
58
+ out.int("dp", @dp).int("cc", @cc).int("cycles", @cycles)
59
+ out.bool("irq", @irq).bool("firq", @firq).bool("nmi", @nmi).bool("halt", @halt)
60
+ out.bool("irq.latched", @irq_latched).bool("irq.seen", @irq_seen)
61
+ out.bool("firq.latched", @firq_latched).bool("firq.seen", @firq_seen)
62
+ out.bool("nmi.edge", @nmi_edge).bool("nmi.latched", @nmi_latched).bool("nmi.seen", @nmi_seen)
63
+ out.bool("nmi.armed", @nmi_armed).bool("halt.latched", @halt_latched).bool("halt.seen", @halt_seen)
64
+ save_working_fields(out)
65
+ out
66
+ end
67
+
68
+ def load_fields(input)
69
+ load_registers(input)
70
+ load_lines(input)
71
+ load_working_fields(input)
72
+ end
73
+
74
+ def restored! = nil
75
+
76
+ private
77
+
78
+ def save_working_fields(out)
79
+ out.int("ea", @ea).int("value", @value).int("post", @post).int("opcode", @opcode)
80
+ out.text("operation", @operation.to_s).int("page", PlanNames::PAGES.index { |page| page.equal?(@page) } + 1)
81
+ out.int("group", @group).int("previous_group", @previous_group).int("alu_word", @alu_word)
82
+ out.int("vector", @vector).int("interrupt_mask", @interrupt_mask).bool("stack_u", @stack_u)
83
+ return if boundary?
84
+
85
+ out.text("pos.plan", PlanNames.name_of(@plan)).int("pos.index", @index)
86
+ out.text("pos.tail", PlanNames.name_of(@tail))
87
+ end
88
+
89
+ def load_registers(input)
90
+ @a = input.byte("a", @a)
91
+ @b = input.byte("b", @b)
92
+ @x = input.word("x", @x)
93
+ @y = input.word("y", @y)
94
+ @u = input.word("u", @u)
95
+ @s = input.word("s", @s)
96
+ @pc = input.word("pc", @pc)
97
+ @dp = input.byte("dp", @dp)
98
+ @cc = input.byte("cc", @cc)
99
+ @cycles = input.int("cycles", @cycles)
100
+ end
101
+
102
+ def load_lines(input)
103
+ @irq = input.bool("irq", false)
104
+ @firq = input.bool("firq", false)
105
+ @nmi = input.bool("nmi", false)
106
+ @halt = input.bool("halt", false)
107
+ @irq_latched = input.bool("irq.latched", false)
108
+ @irq_seen = input.bool("irq.seen", false)
109
+ @firq_latched = input.bool("firq.latched", false)
110
+ @firq_seen = input.bool("firq.seen", false)
111
+ @nmi_edge = input.bool("nmi.edge", false)
112
+ @nmi_latched = input.bool("nmi.latched", false)
113
+ @nmi_seen = input.bool("nmi.seen", false)
114
+ @nmi_armed = input.bool("nmi.armed", @nmi_armed)
115
+ @halt_latched = input.bool("halt.latched", false)
116
+ @halt_seen = input.bool("halt.seen", false)
117
+ end
118
+
119
+ def load_working_fields(input)
120
+ @ea = input.word("ea", 0)
121
+ @value = input.word("value", 0)
122
+ @post = input.byte("post", 0)
123
+ @opcode = input.byte("opcode", 0)
124
+ @operation = input.choice("operation", PlanNames::OPERATIONS, "none").to_sym
125
+ @page = PlanNames::PAGES.fetch(input.int("page", 1).clamp(1, 3) - 1)
126
+ @group = input.int("group", GROUP_OTHER)
127
+ @previous_group = input.int("previous_group", GROUP_OTHER)
128
+ @alu_word = input.int("alu_word", 0)
129
+ @vector = input.word("vector", 0xfffe)
130
+ @interrupt_mask = input.byte("interrupt_mask", 0)
131
+ @stack_u = input.bool("stack_u", false)
132
+ load_position(input)
133
+ end
134
+
135
+ def load_position(input)
136
+ plan = PlanNames.named(input.text("pos.plan", "fetch"))
137
+ tail = PlanNames.named(input.text("pos.tail", "none"))
138
+ return boundary if plan.nil? || tail.nil? || plan.empty?
139
+
140
+ @plan = plan
141
+ @index = input.int("pos.index", 0).clamp(0, plan.length - 1)
142
+ @tail = tail
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/chips/m6809/plans"
4
+
5
+ module Badline
6
+ module Chips
7
+ class M6809
8
+ # The read-modify-write group: $00-$0F direct, $60-$6F indexed and
9
+ # $70-$7F extended, and $40-$4F and $50-$5F on A and B.
10
+ MEMORY_GROUP = %i[neg neg xnc com lsr lsr ror asr asl rol dec xdec inc tst jmp clr].freeze
11
+ A_GROUP = %i[nega nega xnca coma lsra lsra rora asra asla rola deca xdeca inca tsta xclra clra].freeze
12
+ B_GROUP = %i[negb negb xncb comb lsrb lsrb rorb asrb aslb rolb decb xdecb incb tstb xclrb clrb].freeze
13
+
14
+ # $80-$BF and $C0-$FF by low nibble: the instruction and its kind.
15
+ A_COLUMN = [%i[suba read8], %i[cmpa read8], %i[sbca read8], %i[subd read16_dead], %i[anda read8],
16
+ %i[bita read8], %i[lda read8], %i[sta write8], %i[eora read8], %i[adca read8], %i[ora read8],
17
+ %i[adda read8], %i[cmpx read16_dead], %i[jsr subroutine], %i[ldx read16], %i[stx write16]].freeze
18
+ B_COLUMN = [%i[subb read8], %i[cmpb read8], %i[sbcb read8], %i[addd read16_dead], %i[andb read8],
19
+ %i[bitb read8], %i[ldb read8], %i[stb write8], %i[eorb read8], %i[adcb read8], %i[orb read8],
20
+ %i[addb read8], %i[ldd read16], %i[std write16], %i[ldu read16], %i[stu write16]].freeze
21
+ PAGE2_COLUMN = { 0x3 => %i[cmpd read16_dead], 0xc => %i[cmpy read16_dead], 0xe => %i[ldy read16],
22
+ 0xf => %i[sty write16] }.freeze
23
+ PAGE2_B_COLUMN = { 0x3 => %i[xaddd read16_dead], 0xe => %i[lds read16], 0xf => %i[sts write16] }.freeze
24
+ PAGE3_COLUMN = { 0x3 => %i[cmpu read16_dead], 0xc => %i[cmps read16_dead] }.freeze
25
+ PAGE3_B_COLUMN = { 0x3 => %i[xaddu read16_dead] }.freeze
26
+
27
+ # Opcodes with a plan of their own, after the opcode fetch.
28
+ INHERENT = {
29
+ 0x12 => [:nop, %i[dead_pc_exec]],
30
+ 0x13 => [:sync, %i[dead_pc sync_wait]],
31
+ 0x14 => [:none, %i[free_run]],
32
+ 0x15 => [:none, %i[free_run]],
33
+ 0x16 => [:jmp, %i[offset_hi offset_lo_pc dead_v dead_v_exec]],
34
+ 0x17 => [:jsr, %i[offset_hi offset_lo_pc dead_v dead_v dead_ea dead_v push_pcl push_pch_exec]],
35
+ 0x18 => [:x18, %i[next_byte_exec dead_v]],
36
+ 0x19 => [:daa, %i[dead_pc_exec]],
37
+ 0x1a => [:orcc, %i[immediate_exec dead_pc]],
38
+ 0x1b => [:nop, %i[dead_pc_exec]],
39
+ 0x1c => [:andcc, %i[immediate_exec dead_pc]],
40
+ 0x1d => [:sex, %i[dead_pc_exec]],
41
+ 0x1e => [:exg, %i[read_post dead_v dead_v dead_v dead_v dead_v dead_v_exec]],
42
+ 0x1f => [:tfr, %i[read_post dead_v dead_v dead_v dead_v_exec]],
43
+ 0x34 => [:none, %i[push_s_post]],
44
+ 0x35 => [:none, %i[pull_s_post]],
45
+ 0x36 => [:none, %i[push_u_post]],
46
+ 0x37 => [:none, %i[pull_u_post]],
47
+ 0x38 => [:andcc, %i[idle immediate_exec dead_pc]],
48
+ 0x39 => [:none, %i[dead_pc pull_pch pull_pcl dead_v]],
49
+ 0x3a => [:abx, %i[dead_pc dead_v_exec]],
50
+ 0x3b => [:none, %i[dead_pc return_cc]],
51
+ 0x3c => [:none, [:cwai_mask, :dead_pc, :dead_v, *PUSH_ALL, :cwai_wait]],
52
+ 0x3d => [:mul, %i[dead_pc dead_v dead_v dead_v dead_v dead_v dead_v dead_v dead_v dead_v_exec]],
53
+ 0x3e => [:xres, SOFTWARE_INTERRUPT_TAIL],
54
+ 0x3f => [:swi, SOFTWARE_INTERRUPT_TAIL],
55
+ 0x87 => [:xsta, %i[immediate_exec]],
56
+ 0x8d => [:jsr, %i[offset8_branch dead_v dead_ea dead_v push_pcl push_pch_exec]],
57
+ 0x8f => [:xstx, %i[immediate_discard store_immediate]],
58
+ 0xc7 => [:xstb, %i[immediate_exec]],
59
+ 0xcd => [:none, %i[free_run]],
60
+ 0xcf => [:xstu, %i[immediate_discard store_immediate]]
61
+ }.freeze
62
+
63
+ PAGE2_INHERENT = {
64
+ 0x3e => [:xswi2, SOFTWARE_INTERRUPT_TAIL],
65
+ 0x3f => [:swi2, SOFTWARE_INTERRUPT_TAIL],
66
+ 0x87 => [:xsta2, %i[immediate_exec]],
67
+ 0x8f => [:xsty, %i[immediate_discard store_immediate]],
68
+ 0xc7 => [:xstb2, %i[immediate_exec]],
69
+ 0xcf => [:xsts, %i[immediate_discard store_immediate]]
70
+ }.freeze
71
+
72
+ PAGE3_INHERENT = {
73
+ 0x3e => [:xfirq, SOFTWARE_INTERRUPT_TAIL],
74
+ 0x3f => [:swi3, SOFTWARE_INTERRUPT_TAIL],
75
+ 0x87 => [:xsta2, %i[immediate_exec]],
76
+ 0x8f => [:xstx2, %i[immediate_discard store_immediate]],
77
+ 0xc7 => [:xstb2, %i[immediate_exec]],
78
+ 0xcf => [:xstu2, %i[immediate_discard store_immediate]]
79
+ }.freeze
80
+
81
+ SHORT_BRANCH_TAIL = %i[offset8_branch dead_v_branch].freeze
82
+ LONG_BRANCH_TAIL = %i[offset_hi offset_lo_pc dead_v_long_branch].freeze
83
+ LEA = { 0x30 => :leax, 0x31 => :leay, 0x32 => :leas, 0x33 => :leau }.freeze
84
+
85
+ # Builds the opcode tables.
86
+ module Opcodes
87
+ MODES = [nil, DIRECT_PLAN, INDEXED_PLAN, EXTENDED_PLAN].freeze
88
+
89
+ module_function
90
+
91
+ def entry(operation, plan, tail)
92
+ Entry.new(operation, plan.freeze, tail.freeze, GROUPS.fetch(operation, GROUP_OTHER))
93
+ end
94
+
95
+ def tail(kind, mode)
96
+ return IMMEDIATE_TAILS.fetch(kind) if mode.zero?
97
+
98
+ TAILS.fetch(kind)
99
+ end
100
+
101
+ def memory(opcode)
102
+ mode = if opcode < 0x10 then DIRECT_PLAN
103
+ elsif opcode < 0x70 then INDEXED_PLAN
104
+ else EXTENDED_PLAN
105
+ end
106
+ name = MEMORY_GROUP[opcode & 0x0f]
107
+ entry(name, mode, TAILS.fetch({ tst: :test, jmp: :jump }.fetch(name, :rmw)))
108
+ end
109
+
110
+ # $80-$FF: immediate, direct, indexed and extended by bits 4 and 5.
111
+ def column(opcode, column)
112
+ name, kind = column.fetch(opcode & 0x0f)
113
+ mode = (opcode >> 4) & 0x3
114
+ return entry(:none, [], NO_TAIL) if mode.zero? && !IMMEDIATE_TAILS.key?(kind)
115
+
116
+ entry(name, mode.zero? ? [] : MODES.fetch(mode), tail(kind, mode))
117
+ end
118
+
119
+ def page1(opcode)
120
+ if (inherent = INHERENT[opcode]) then entry(inherent[0], [], inherent[1])
121
+ elsif opcode < 0x10 || (0x60..0x7f).cover?(opcode) then memory(opcode)
122
+ elsif (0x40..0x5f).cover?(opcode) then inherent_register(opcode)
123
+ elsif (0x20..0x2f).cover?(opcode) then entry(:none, [], SHORT_BRANCH_TAIL)
124
+ elsif LEA.key?(opcode) then entry(LEA[opcode], INDEXED_PLAN, TAILS[:load_address])
125
+ elsif opcode >= 0x80 then column(opcode, opcode >= 0xc0 ? B_COLUMN : A_COLUMN)
126
+ else entry(:none, [], NO_TAIL)
127
+ end
128
+ end
129
+
130
+ def inherent_register(opcode)
131
+ name = (opcode < 0x50 ? A_GROUP : B_GROUP)[opcode & 0x0f]
132
+ entry(name, [], %i[dead_pc_exec])
133
+ end
134
+
135
+ # Pages 2 and 3. An opcode a page doesn't define runs as on page 1,
136
+ # after the prefix's cycle.
137
+ def paged(opcode, page1, inherent, columns)
138
+ return entry(inherent[opcode][0], [], inherent[opcode][1]) if inherent.key?(opcode)
139
+ return entry(:none, [], LONG_BRANCH_TAIL) if columns.first == PAGE2_COLUMN && (0x20..0x2f).cover?(opcode)
140
+
141
+ column = opcode >= 0xc0 ? columns[1] : columns[0]
142
+ return page1[opcode] if opcode < 0x80 || !column.key?(opcode & 0x0f)
143
+
144
+ name, kind = column[opcode & 0x0f]
145
+ mode = (opcode >> 4) & 0x3
146
+ entry(name, mode.zero? ? [] : MODES.fetch(mode), tail(kind, mode))
147
+ end
148
+ end
149
+
150
+ PAGE1 = Array.new(256) { |opcode| Opcodes.page1(opcode) }.freeze
151
+ PAGE2 = Array.new(256) do |opcode|
152
+ Opcodes.paged(opcode, PAGE1, PAGE2_INHERENT, [PAGE2_COLUMN, PAGE2_B_COLUMN])
153
+ end.freeze
154
+ PAGE3 = Array.new(256) do |opcode|
155
+ Opcodes.paged(opcode, PAGE1, PAGE3_INHERENT, [PAGE3_COLUMN, PAGE3_B_COLUMN])
156
+ end.freeze
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,258 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class M6809
6
+ # The instruction methods the plans' *_exec steps call. A read takes
7
+ # its operand from @value; a write and a read-modify-write leave the
8
+ # byte or word to write there.
9
+ module Operations
10
+ private
11
+
12
+ def none; end
13
+ def nop; end
14
+ def sync; end
15
+
16
+ def neg = @value = neg8(@value)
17
+ def xnc = @value = xnc8(@value)
18
+ def com = @value = com8(@value)
19
+ def lsr = @value = lsr8(@value)
20
+ def ror = @value = ror8(@value)
21
+ def asr = @value = asr8(@value)
22
+ def asl = @value = asl8(@value)
23
+ def rol = @value = rol8(@value)
24
+ def dec = @value = dec8(@value)
25
+ def xdec = @value = xdec8(@value)
26
+ def inc = @value = inc8(@value)
27
+ def tst = logic8(@value)
28
+ def clr = @value = clr8
29
+
30
+ def jmp
31
+ @pc = @ea
32
+ end
33
+
34
+ def nega = @a = neg8(@a)
35
+ def xnca = @a = xnc8(@a)
36
+ def coma = @a = com8(@a)
37
+ def lsra = @a = lsr8(@a)
38
+ def rora = @a = ror8(@a)
39
+ def asra = @a = asr8(@a)
40
+ def asla = @a = asl8(@a)
41
+ def rola = @a = rol8(@a)
42
+ def deca = @a = dec8(@a)
43
+ def xdeca = @a = xdec8(@a)
44
+ def inca = @a = inc8(@a)
45
+ def tsta = logic8(@a)
46
+ def xclra = @a = xclr8
47
+ def clra = @a = clr8
48
+
49
+ def negb = @b = neg8(@b)
50
+ def xncb = @b = xnc8(@b)
51
+ def comb = @b = com8(@b)
52
+ def lsrb = @b = lsr8(@b)
53
+ def rorb = @b = ror8(@b)
54
+ def asrb = @b = asr8(@b)
55
+ def aslb = @b = asl8(@b)
56
+ def rolb = @b = rol8(@b)
57
+ def decb = @b = dec8(@b)
58
+ def xdecb = @b = xdec8(@b)
59
+ def incb = @b = inc8(@b)
60
+ def tstb = logic8(@b)
61
+ def xclrb = @b = xclr8
62
+ def clrb = @b = clr8
63
+
64
+ def suba = @a = sub8(@a, @value, 0)
65
+ def cmpa = sub8(@a, @value, 0)
66
+ def sbca = @a = sub8(@a, @value, carry)
67
+ def anda = @a = logic8(@a & @value)
68
+ def bita = logic8(@a & @value)
69
+ def lda = @a = logic8(@value)
70
+ def sta = @value = logic8(@a)
71
+ def eora = @a = logic8(@a ^ @value)
72
+ def adca = @a = add8(@a, @value, carry)
73
+ def ora = @a = logic8(@a | @value)
74
+ def adda = @a = add8(@a, @value, 0)
75
+
76
+ def subb = @b = sub8(@b, @value, 0)
77
+ def cmpb = sub8(@b, @value, 0)
78
+ def sbcb = @b = sub8(@b, @value, carry)
79
+ def andb = @b = logic8(@b & @value)
80
+ def bitb = logic8(@b & @value)
81
+ def ldb = @b = logic8(@value)
82
+ def stb = @value = logic8(@b)
83
+ def eorb = @b = logic8(@b ^ @value)
84
+ def adcb = @b = add8(@b, @value, carry)
85
+ def orb = @b = logic8(@b | @value)
86
+ def addb = @b = add8(@b, @value, 0)
87
+
88
+ def subd = self.d = sub16(d, @value)
89
+ def addd = self.d = add16(d, @value)
90
+ def cmpd = sub16(d, @value)
91
+ def cmpx = sub16(@x, @value)
92
+ def cmpy = sub16(@y, @value)
93
+ def cmpu = sub16(@u, @value)
94
+ def cmps = sub16(@s, @value)
95
+ def xaddd = add16(d, @value)
96
+ def xaddu = add16(@u | 0xff00, @value)
97
+
98
+ def ldd = self.d = logic16(@value)
99
+ def ldx = @x = logic16(@value)
100
+ def ldy = @y = logic16(@value)
101
+ def ldu = @u = logic16(@value)
102
+ def lds = load_s(logic16(@value))
103
+ def std = @value = logic16(d)
104
+ def stx = @value = logic16(@x)
105
+ def sty = @value = logic16(@y)
106
+ def stu = @value = logic16(@u)
107
+ def sts = @value = logic16(@s)
108
+
109
+ # The undocumented stores immediate. Prefixed, they set N and clear
110
+ # Z and V. Unprefixed, their flags hang on the instruction before
111
+ # (Banks' "Flags for unprefixed Store Immediate"), and the 16-bit
112
+ # ones also clear Z unless the register's low byte is 0.
113
+ def xsta = flags(NEGATIVE | ZERO | OVERFLOW, previous_group_flags)
114
+ def xstb = flags(NEGATIVE | ZERO | OVERFLOW, previous_group_flags)
115
+ def xsta2 = flags(NEGATIVE | ZERO | OVERFLOW, NEGATIVE)
116
+ def xstb2 = flags(NEGATIVE | ZERO | OVERFLOW, NEGATIVE)
117
+ def xstx = @value = store_word_low(@x)
118
+ def xstu = @value = store_word_low(@u)
119
+
120
+ def xsty = @value = store_word_low_prefixed(@y)
121
+ def xsts = @value = store_word_low_prefixed(@s)
122
+ def xstx2 = @value = store_word_low_prefixed(@x)
123
+ def xstu2 = @value = store_word_low_prefixed(@u)
124
+
125
+ def store_word_low(word)
126
+ low = word & 0xff
127
+ set = previous_group_flags & (NEGATIVE | ZERO)
128
+ set &= ~ZERO unless low.zero?
129
+ flags(NEGATIVE | ZERO | OVERFLOW, set)
130
+ low
131
+ end
132
+
133
+ def store_word_low_prefixed(word)
134
+ flags(NEGATIVE | ZERO | OVERFLOW, NEGATIVE)
135
+ word & 0xff
136
+ end
137
+
138
+ def previous_group_flags
139
+ case @previous_group
140
+ when GROUP_N0_Z1 then ZERO
141
+ when GROUP_A_00 then nz8(@a)
142
+ when GROUP_A_FF then (@a == 0xff ? ZERO : 0) | (@a.anybits?(0x80) ? 0 : NEGATIVE)
143
+ when GROUP_A_01 then decremented_flags(@a)
144
+ when GROUP_B_01 then decremented_flags(@b)
145
+ when GROUP_WORD_01 then decremented_flags(@alu_word >> 8)
146
+ when GROUP_LEA then decremented_flags((@alu_word >> 8) | (@alu_word & 0xff))
147
+ else NEGATIVE
148
+ end
149
+ end
150
+
151
+ # Z for 1, N for a negative value less one, V for $80.
152
+ def decremented_flags(value)
153
+ (value == 1 ? ZERO : 0) | ((value - 1).nobits?(0x80) ? 0 : NEGATIVE) | (value == 0x80 ? OVERFLOW : 0)
154
+ end
155
+
156
+ # JSR, BSR and LBSR, once the return address is stacked.
157
+ def jsr
158
+ @pc = @ea
159
+ end
160
+
161
+ def leax
162
+ @alu_word = @ea
163
+ @x = @ea
164
+ flags(ZERO, @ea.zero? ? ZERO : 0)
165
+ end
166
+
167
+ def leay
168
+ @alu_word = @ea
169
+ @y = @ea
170
+ flags(ZERO, @ea.zero? ? ZERO : 0)
171
+ end
172
+
173
+ def leas
174
+ @alu_word = @ea
175
+ load_s(@ea)
176
+ end
177
+
178
+ def leau
179
+ @alu_word = @ea
180
+ @u = @ea
181
+ end
182
+
183
+ def orcc = @cc |= @value
184
+ def andcc = @cc &= @value
185
+
186
+ def abx = @x = (@x + @b) & 0xffff
187
+
188
+ def mul
189
+ self.d = @a * @b
190
+ flags(ZERO | CARRY, (d.zero? ? ZERO : 0) | (@b >> 7))
191
+ end
192
+
193
+ def sex
194
+ @a = @b.nobits?(0x80) ? 0 : 0xff
195
+ flags(NEGATIVE | ZERO, nz16(d))
196
+ end
197
+
198
+ # DAA. V is C xor bit 7 of the result, as Banks found, where the
199
+ # datasheet clears it.
200
+ def daa
201
+ correction = 0
202
+ correction |= 0x06 if @cc.anybits?(HALF_CARRY) || (@a & 0x0f) > 0x09
203
+ high = @a & 0xf0
204
+ correction |= 0x60 if @cc.anybits?(CARRY) || high > 0x90 || (high > 0x80 && (@a & 0x0f) > 0x09)
205
+ result = (@a + correction) & 0xff
206
+ carry = correction >= 0x60 ? CARRY : 0
207
+ overflow = (carry == 1) == result.anybits?(0x80) ? 0 : OVERFLOW
208
+ flags(NEGATIVE | ZERO | OVERFLOW | CARRY, nz8(result) | overflow | carry)
209
+ @a = result
210
+ end
211
+
212
+ # The undocumented $18: each flag, ANDed with its bit of the byte
213
+ # after the opcode, moves one bit up, and V also takes Z ANDed with
214
+ # bit 2.
215
+ def x18
216
+ masked = @cc & @value
217
+ @cc = ((masked << 1) & 0xff) | (masked.nobits?(ZERO) ? 0 : OVERFLOW)
218
+ end
219
+
220
+ def exg
221
+ first = @post >> 4
222
+ second = @post & 0x0f
223
+ value = transfer_register(second)
224
+ set_transfer_register(second, sized(transfer_register(first), first, second))
225
+ set_transfer_register(first, sized(value, second, first))
226
+ end
227
+
228
+ def tfr
229
+ source = @post >> 4
230
+ destination = @post & 0x0f
231
+ set_transfer_register(destination, sized(transfer_register(source), source, destination))
232
+ end
233
+
234
+ # A value moved from register code +from+ to +to+: an 8-bit register
235
+ # fills a 16-bit one's high byte with $FF, and a 16-bit one gives an
236
+ # 8-bit one its low byte.
237
+ def sized(value, from, to)
238
+ return value & 0xff if to >= 8
239
+ return value | 0xff00 if from >= 8
240
+
241
+ value
242
+ end
243
+
244
+ def swi = software_interrupt(0xfffa, IRQ_MASK | FIRQ_MASK, ENTIRE)
245
+ def swi2 = software_interrupt(0xfff4, 0, ENTIRE)
246
+ def swi3 = software_interrupt(0xfff2, 0, ENTIRE)
247
+ def xres = software_interrupt(0xfffe, 0, 0)
248
+ def xswi2 = software_interrupt(0xfff4, 0, 0)
249
+ def xfirq = software_interrupt(0xfff6, 0, 0)
250
+
251
+ def software_interrupt(vector, mask, entire)
252
+ @cc |= entire
253
+ interrupt_vector(vector, mask)
254
+ end
255
+ end
256
+ end
257
+ end
258
+ end