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
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class M6809
6
+ # The IRQ, FIRQ, NMI and HALT lines, SYNC and CWAI.
7
+ #
8
+ # The CPU samples each line once a cycle, and an instruction boundary
9
+ # sees the sample of two cycles before: a line asserted on the
10
+ # instruction's next-to-last cycle is taken after it, as in Figures 8
11
+ # and 9 of the MC6809E datasheet. NMI is an edge, latched until taken,
12
+ # and ignored until a program loads S after reset.
13
+ module Interrupts
14
+ attr_reader :irq, :firq, :nmi, :halt
15
+
16
+ # Asserts (true) or releases the IRQ line.
17
+ def irq=(asserted)
18
+ @irq = asserted
19
+ end
20
+
21
+ def firq=(asserted)
22
+ @firq = asserted
23
+ end
24
+
25
+ def nmi=(asserted)
26
+ @nmi_edge = true if asserted && !@nmi && @nmi_armed
27
+ @nmi = asserted
28
+ end
29
+
30
+ # The HALT line: the CPU stops at the next instruction boundary and
31
+ # leaves the bus alone until it is released.
32
+ def halt=(asserted)
33
+ @halt = asserted
34
+ end
35
+
36
+ def halted? = boundary? && @halt_seen
37
+
38
+ # Whether the CPU is waiting in SYNC or CWAI, or running free.
39
+ def waiting?
40
+ step = @plan[@index]
41
+ step == :sync_wait || step == :cwai_wait || step == :free_run || halted?
42
+ end
43
+
44
+ private
45
+
46
+ def clear_interrupts
47
+ @irq = @firq = @nmi = @halt = false
48
+ @irq_latched = @irq_seen = false
49
+ @firq_latched = @firq_seen = false
50
+ @nmi_edge = @nmi_latched = @nmi_seen = false
51
+ @halt_latched = @halt_seen = false
52
+ end
53
+
54
+ def sample_lines
55
+ @irq_seen = @irq_latched
56
+ @irq_latched = @irq
57
+ @firq_seen = @firq_latched
58
+ @firq_latched = @firq
59
+ @nmi_seen = @nmi_latched
60
+ @nmi_latched = @nmi_edge
61
+ @halt_seen = @halt_latched
62
+ @halt_latched = @halt
63
+ end
64
+
65
+ def interrupt_due?
66
+ @nmi_seen || (@firq_seen && @cc.nobits?(FIRQ_MASK)) || (@irq_seen && @cc.nobits?(IRQ_MASK))
67
+ end
68
+
69
+ # The cycle that would have fetched an opcode reads it and drops it,
70
+ # and the interrupt's sequence follows.
71
+ def take_interrupt
72
+ dead_pc
73
+ @stack_u = false
74
+ if @nmi_seen
75
+ @operation = :enter_nmi
76
+ switch_to(INTERRUPT_PLAN)
77
+ elsif @firq_seen && @cc.nobits?(FIRQ_MASK)
78
+ @operation = :enter_firq
79
+ switch_to(FAST_INTERRUPT_PLAN)
80
+ else
81
+ @operation = :enter_irq
82
+ switch_to(INTERRUPT_PLAN)
83
+ end
84
+ end
85
+
86
+ def enter_nmi
87
+ clear_nmi
88
+ @cc |= ENTIRE
89
+ interrupt_vector(0xfffc, IRQ_MASK | FIRQ_MASK)
90
+ end
91
+
92
+ def enter_firq
93
+ @cc &= ~ENTIRE & 0xff
94
+ interrupt_vector(0xfff6, IRQ_MASK | FIRQ_MASK)
95
+ end
96
+
97
+ def enter_irq
98
+ @cc |= ENTIRE
99
+ interrupt_vector(0xfff8, IRQ_MASK)
100
+ end
101
+
102
+ def interrupt_vector(vector, mask)
103
+ @vector = vector
104
+ @interrupt_mask = mask
105
+ end
106
+
107
+ def clear_nmi
108
+ @nmi_edge = @nmi_latched = @nmi_seen = false
109
+ end
110
+
111
+ def reset_dead = @bus.read(0xfffe)
112
+
113
+ def reset_hi
114
+ @value = @bus.read(0xfffe) << 8
115
+ end
116
+
117
+ def reset_lo
118
+ @pc = @value | @bus.read(0xffff)
119
+ end
120
+
121
+ def halted_cycle
122
+ @index = 0
123
+ end
124
+
125
+ # SYNC leaves the bus alone until any interrupt line is asserted,
126
+ # masked or not, then for one more cycle.
127
+ def sync_wait
128
+ if @nmi_latched || @firq_latched || @irq_latched
129
+ switch_to(SYNC_END_PLAN)
130
+ else
131
+ @index -= 1
132
+ end
133
+ end
134
+
135
+ # CWAI ANDs CC with its operand and sets E before it stacks.
136
+ def cwai_mask
137
+ @cc = (read_pc & @cc) | ENTIRE
138
+ end
139
+
140
+ # CWAI, all stacked, reads $FFFF until an interrupt it may take
141
+ # comes, and then fetches that interrupt's vector.
142
+ def cwai_wait
143
+ dead_v
144
+ if @nmi_latched
145
+ clear_nmi
146
+ interrupt_vector(0xfffc, IRQ_MASK | FIRQ_MASK)
147
+ elsif @firq_latched && @cc.nobits?(FIRQ_MASK)
148
+ interrupt_vector(0xfff6, IRQ_MASK | FIRQ_MASK)
149
+ elsif @irq_latched && @cc.nobits?(IRQ_MASK)
150
+ interrupt_vector(0xfff8, IRQ_MASK)
151
+ else
152
+ return @index -= 1
153
+ end
154
+ switch_to(WAITED_INTERRUPT_PLAN)
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
@@ -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