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
|
@@ -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
|
|
data/lib/badline/cbm2/p500.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
105
|
+
@control.value.anybits?(0x01)
|
|
106
106
|
end
|
|
107
107
|
|
|
108
108
|
# Where fast_forward lands +cycles+ after an underflow, with
|
|
@@ -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
|