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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +153 -0
- data/CLAUDE.md +11 -7
- data/CONTRIBUTING.md +7 -1
- data/README.md +169 -20
- data/doc/pinned-behaviour.md +128 -0
- data/doc/snapshot-format.md +253 -18
- data/doc/snapshots.md +57 -23
- data/exe/badline-ruby +5 -0
- data/lib/badline/audio/terminal.rb +34 -2
- data/lib/badline/c128/keyed_state.rb +2 -0
- data/lib/badline/c128.rb +1 -0
- data/lib/badline/c64/keyed_state.rb +2 -0
- data/lib/badline/c64.rb +1 -0
- data/lib/badline/cbm2/keyed_state.rb +4 -0
- data/lib/badline/cbm2/p500.rb +5 -3
- data/lib/badline/cbm2.rb +6 -3
- data/lib/badline/chips/cia/timer.rb +2 -2
- data/lib/badline/chips/cpu/instruction_set.rb +1 -1
- data/lib/badline/chips/m6809/arithmetic.rb +140 -0
- data/lib/badline/chips/m6809/bus_steps.rb +223 -0
- data/lib/badline/chips/m6809/core.rb +149 -0
- data/lib/badline/chips/m6809/indexed.rb +82 -0
- data/lib/badline/chips/m6809/interrupts.rb +159 -0
- data/lib/badline/chips/m6809/keyed_state.rb +147 -0
- data/lib/badline/chips/m6809/opcodes.rb +159 -0
- data/lib/badline/chips/m6809/operations.rb +258 -0
- data/lib/badline/chips/m6809/plans.rb +143 -0
- data/lib/badline/chips/m6809/registers.rb +82 -0
- data/lib/badline/chips/m6809/stack.rb +140 -0
- data/lib/badline/chips/m6809.rb +13 -0
- data/lib/badline/chips/riot/timer.rb +14 -3
- data/lib/badline/chips/rriot.rb +4 -2
- data/lib/badline/chips/sid/filter.rb +4 -4
- data/lib/badline/chips/sid/waveform.rb +6 -6
- data/lib/badline/chips/vic_i/keyed_parts.rb +7 -2
- data/lib/badline/chips/vic_i.rb +84 -21
- data/lib/badline/data_path.rb +3 -3
- data/lib/badline/drives/ieee_models.rb +54 -0
- data/lib/badline/drives/model1001.rb +37 -0
- data/lib/badline/drives/model2031.rb +6 -0
- data/lib/badline/drives/model4040/disk.rb +62 -0
- data/lib/badline/drives/model4040/ip_bus.rb +46 -0
- data/lib/badline/drives/model4040/keyed_state.rb +57 -0
- data/lib/badline/drives/model4040/mechanism.rb +192 -0
- data/lib/badline/drives/model4040/parts.rb +20 -0
- data/lib/badline/drives/model4040.rb +179 -0
- data/lib/badline/drives/model8050/board.rb +77 -0
- data/lib/badline/drives/model8050/dos_board.rb +91 -0
- data/lib/badline/drives/model8050/idle.rb +11 -7
- data/lib/badline/drives/model8050/keyed_state.rb +5 -2
- data/lib/badline/drives/model8050.rb +18 -130
- data/lib/badline/drives/model9060/idle.rb +149 -0
- data/lib/badline/drives/model9060/keyed_state.rb +123 -0
- data/lib/badline/drives/model9060/parts.rb +98 -0
- data/lib/badline/drives/model9060/sasi_bus.rb +88 -0
- data/lib/badline/drives/model9060/sasi_commands.rb +168 -0
- data/lib/badline/drives/model9060/sasi_target.rb +161 -0
- data/lib/badline/drives/model9060.rb +195 -0
- data/lib/badline/frontend/app.rb +8 -0
- data/lib/badline/frontend/boot.rb +3 -2
- data/lib/badline/frontend/controls.rb +62 -6
- data/lib/badline/frontend/keys.rb +36 -0
- data/lib/badline/frontend/kim1_panel.rb +208 -0
- data/lib/badline/frontend/media_slots.rb +8 -1
- data/lib/badline/frontend/menu/drive_types.rb +56 -0
- data/lib/badline/frontend/menu/menu_dialogs.rb +61 -14
- data/lib/badline/frontend/menu/menu_media.rb +42 -12
- data/lib/badline/frontend/menu/menu_pages.rb +97 -2
- data/lib/badline/frontend/menu/menu_ports.rb +38 -1
- data/lib/badline/frontend/menu/pause_menu.rb +5 -1
- data/lib/badline/frontend/pet_keyboard.rb +23 -1
- data/lib/badline/frontend/screen.rb +1 -1
- data/lib/badline/frontend/screens.rb +40 -2
- data/lib/badline/frontend/teletype_pane.rb +152 -0
- data/lib/badline/frontend/timeline.rb +31 -2
- data/lib/badline/frontend.rb +3 -0
- data/lib/badline/kim1/bus.rb +117 -0
- data/lib/badline/kim1/cpu.rb +25 -0
- data/lib/badline/kim1/host_terminal.rb +74 -0
- data/lib/badline/kim1/key_entry.rb +74 -0
- data/lib/badline/kim1/keyed_modules.rb +53 -0
- data/lib/badline/kim1/keyed_state.rb +133 -0
- data/lib/badline/kim1/panel.rb +176 -0
- data/lib/badline/kim1/rriots.rb +28 -0
- data/lib/badline/kim1/sound.rb +29 -0
- data/lib/badline/kim1/tape.rb +116 -0
- data/lib/badline/kim1/teletype.rb +193 -0
- data/lib/badline/kim1.rb +190 -0
- data/lib/badline/machine/ieee_drives.rb +94 -117
- data/lib/badline/machine/keyboard_buffer.rb +20 -3
- data/lib/badline/machine/line_sound.rb +122 -0
- data/lib/badline/machine.rb +46 -15
- data/lib/badline/media/extensions.rb +8 -4
- data/lib/badline/media/kim1_media.rb +150 -0
- data/lib/badline/media/pet_media.rb +3 -1
- data/lib/badline/media/true_drive.rb +42 -45
- data/lib/badline/media.rb +12 -6
- data/lib/badline/options/family.rb +98 -8
- data/lib/badline/options/help.rb +44 -12
- data/lib/badline/options/table.rb +43 -11
- data/lib/badline/options/validation.rb +44 -8
- data/lib/badline/options.rb +27 -8
- data/lib/badline/pet/bus.rb +16 -2
- data/lib/badline/pet/keyboard_ports.rb +9 -2
- data/lib/badline/pet/keyed_state.rb +4 -0
- data/lib/badline/pet/model.rb +11 -2
- data/lib/badline/pet/rom_sets.rb +1 -0
- data/lib/badline/pet/sound.rb +5 -105
- data/lib/badline/pet/superpet/acia.rb +12 -0
- data/lib/badline/pet/superpet/board.rb +221 -0
- data/lib/badline/pet/superpet/dongle.rb +81 -0
- data/lib/badline/pet/superpet/keyed_modules.rb +63 -0
- data/lib/badline/pet/superpet/keyed_parts.rb +76 -0
- data/lib/badline/pet/superpet/m6809.rb +32 -0
- data/lib/badline/pet/superpet/waterloo_traps.rb +199 -0
- data/lib/badline/pet/superpet.rb +216 -0
- data/lib/badline/pet.rb +19 -3
- data/lib/badline/plus4/keyed_state.rb +2 -0
- data/lib/badline/plus4.rb +1 -0
- data/lib/badline/roms/1001/fdc-251257-02a.rom +0 -0
- data/lib/badline/roms/4040/dos-1.2.rom +0 -0
- data/lib/badline/roms/4040/dos-2.1-rev.rom +0 -0
- data/lib/badline/roms/4040/dos-2.1.rom +0 -0
- data/lib/badline/roms/4040/fdc-901466-02.rom +0 -0
- data/lib/badline/roms/4040/fdc-901466-04.rom +0 -0
- data/lib/badline/roms/9060/dos-3.0.rom +0 -0
- data/lib/badline/roms/9060/sasi-300515-revb.rom +0 -0
- data/lib/badline/roms/README +166 -0
- data/lib/badline/roms/kim1/6530-002.rom +0 -0
- data/lib/badline/roms/kim1/6530-003.rom +0 -0
- data/lib/badline/roms/pet/characters-superpet.rom +0 -0
- data/lib/badline/roms/pet/superpet-a000.rom +0 -0
- data/lib/badline/roms/pet/superpet-c000.rom +0 -0
- data/lib/badline/roms/pet/superpet-e000-50hz.rom +0 -0
- data/lib/badline/roms/pet/superpet-e000.rom +0 -0
- data/lib/badline/snapshot/container.rb +2 -0
- data/lib/badline/snapshot/field_doc/markdown.rb +1 -1
- data/lib/badline/snapshot/field_doc.rb +27 -6
- data/lib/badline/snapshot/image.rb +4 -1
- data/lib/badline/snapshot/kim1_setup.rb +28 -0
- data/lib/badline/snapshot/pet_setup.rb +27 -11
- data/lib/badline/snapshot/storage_setup.rb +4 -1
- data/lib/badline/snapshot/vic20_setup.rb +11 -7
- data/lib/badline/snapshot/vice/pet.rb +13 -7
- data/lib/badline/snapshot/vice/pet_cpu.rb +39 -5
- data/lib/badline/snapshot/vice/pet_crtc.rb +15 -2
- data/lib/badline/snapshot/vice/pet_drive8050.rb +188 -68
- data/lib/badline/snapshot/vice/pet_mem.rb +49 -5
- data/lib/badline/snapshot/vice/pet_restore.rb +40 -11
- data/lib/badline/snapshot.rb +4 -0
- data/lib/badline/status.rb +30 -30
- data/lib/badline/storage/d67_image.rb +55 -0
- data/lib/badline/storage/d90_image.rb +180 -0
- data/lib/badline/storage/disk_image/writing.rb +2 -2
- data/lib/badline/storage/kim_tape.rb +254 -0
- data/lib/badline/storage/papertape.rb +90 -0
- data/lib/badline/storage.rb +4 -0
- data/lib/badline/support/kim1_checkpoint.rb +41 -0
- data/lib/badline/support/superpet_checkpoint.rb +35 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic20/keyed_modules.rb +6 -3
- data/lib/badline/vic20/keyed_state.rb +6 -4
- data/lib/badline/vic20.rb +26 -12
- data/lib/badline.rb +1 -0
- 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
|