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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +193 -0
- data/CLAUDE.md +19 -7
- data/CONTRIBUTING.md +11 -1
- data/README.md +178 -26
- data/doc/library.md +5 -1
- data/doc/media.md +3 -2
- data/doc/pinned-behaviour.md +169 -1
- data/doc/snapshot-format.md +302 -35
- data/doc/snapshots.md +97 -26
- data/doc/vice.md +344 -0
- data/exe/badline-ruby +5 -0
- data/lib/badline/audio/renderer.rb +4 -8
- data/lib/badline/audio/terminal.rb +34 -2
- data/lib/badline/c128/keyed_state.rb +2 -0
- data/lib/badline/c128.rb +5 -0
- data/lib/badline/c64/bus/fittings.rb +6 -0
- data/lib/badline/c64/keyed_state.rb +2 -0
- data/lib/badline/c64.rb +7 -0
- data/lib/badline/cbm2/keyed_state.rb +4 -0
- data/lib/badline/cbm2/p500.rb +9 -3
- data/lib/badline/cbm2.rb +10 -3
- data/lib/badline/chips/cia/timer.rb +2 -2
- data/lib/badline/chips/cpu/core.rb +29 -4
- data/lib/badline/chips/cpu/instruction_set.rb +1 -1
- data/lib/badline/chips/cpu/interrupts.rb +9 -0
- data/lib/badline/chips/cpu/keyed_state.rb +15 -9
- data/lib/badline/chips/cpu/microcode.rb +10 -0
- 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 +8 -3
- data/lib/badline/chips/sid/decimator.rb +1 -1
- data/lib/badline/chips/sid/filter.rb +4 -4
- data/lib/badline/chips/sid/keyed_parts.rb +51 -18
- data/lib/badline/chips/sid/waveform.rb +6 -6
- data/lib/badline/chips/sid.rb +6 -2
- data/lib/badline/chips/vic_i/keyed_parts.rb +12 -2
- data/lib/badline/chips/vic_i/light_pen.rb +89 -0
- data/lib/badline/chips/vic_i.rb +91 -23
- 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/model1541/sector_reader.rb +20 -3
- data/lib/badline/drives/model1541.rb +1 -0
- data/lib/badline/drives/model1551.rb +1 -0
- data/lib/badline/drives/model1571.rb +1 -0
- data/lib/badline/drives/model1581.rb +1 -0
- data/lib/badline/drives/model2031.rb +7 -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 +195 -0
- data/lib/badline/drives/model4040/parts.rb +20 -0
- data/lib/badline/drives/model4040.rb +195 -0
- data/lib/badline/drives/model8050/board.rb +79 -0
- data/lib/badline/drives/model8050/dos_board.rb +134 -0
- data/lib/badline/drives/model8050/fdc_bus.rb +7 -0
- data/lib/badline/drives/model8050/idle.rb +148 -31
- data/lib/badline/drives/model8050/keyed_state.rb +5 -2
- data/lib/badline/drives/model8050.rb +32 -130
- data/lib/badline/drives/model9060/idle.rb +169 -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 +197 -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/input/joystick.rb +15 -2
- 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 +118 -0
- data/lib/badline/kim1/teletype.rb +193 -0
- data/lib/badline/kim1.rb +194 -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 +49 -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 +52 -45
- data/lib/badline/media/vic20_media.rb +4 -2
- data/lib/badline/media.rb +16 -6
- data/lib/badline/memory.rb +2 -4
- data/lib/badline/options/family.rb +98 -8
- data/lib/badline/options/help.rb +44 -12
- data/lib/badline/options/table.rb +44 -12
- 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 +92 -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 +221 -0
- data/lib/badline/pet.rb +23 -3
- data/lib/badline/plus4/keyed_state.rb +2 -0
- data/lib/badline/plus4.rb +5 -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 +176 -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 +8 -4
- data/lib/badline/snapshot/field_doc/markdown.rb +1 -1
- data/lib/badline/snapshot/field_doc.rb +35 -10
- data/lib/badline/snapshot/image.rb +19 -7
- 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 +14 -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_dongle.rb +72 -0
- data/lib/badline/snapshot/vice/pet_drive8050.rb +188 -68
- data/lib/badline/snapshot/vice/pet_mem.rb +55 -5
- data/lib/badline/snapshot/vice/pet_restore.rb +40 -11
- data/lib/badline/snapshot/vice/vic20.rb +70 -0
- data/lib/badline/snapshot/vice/vic20_cpu.rb +61 -0
- data/lib/badline/snapshot/vice/vic20_mem.rb +85 -0
- data/lib/badline/snapshot/vice/vic20_restore.rb +124 -0
- data/lib/badline/snapshot/vice/vic20_vici.rb +200 -0
- data/lib/badline/snapshot/vice.rb +1 -0
- data/lib/badline/snapshot.rb +8 -4
- 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/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/port_wiring.rb +11 -0
- data/lib/badline/vic20.rb +30 -12
- data/lib/badline.rb +1 -0
- metadata +85 -1
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Chips
|
|
5
|
+
class M6809
|
|
6
|
+
# The condition code bits.
|
|
7
|
+
CARRY = 0x01
|
|
8
|
+
OVERFLOW = 0x02
|
|
9
|
+
ZERO = 0x04
|
|
10
|
+
NEGATIVE = 0x08
|
|
11
|
+
IRQ_MASK = 0x10
|
|
12
|
+
HALF_CARRY = 0x20
|
|
13
|
+
FIRQ_MASK = 0x40
|
|
14
|
+
ENTIRE = 0x80
|
|
15
|
+
|
|
16
|
+
# What an opcode runs: the instruction method its *_exec step calls,
|
|
17
|
+
# the steps that work out its address, one a cycle, and the steps
|
|
18
|
+
# that follow them, and the group the undocumented stores immediate
|
|
19
|
+
# take their flags from when it ran before them. An indexed opcode's
|
|
20
|
+
# address steps end in the postbyte, which picks the rest from
|
|
21
|
+
# INDEXED_PLANS.
|
|
22
|
+
Entry = Struct.new(:operation, :plan, :tail, :group)
|
|
23
|
+
|
|
24
|
+
# The groups of David Banks' "Flags for unprefixed Store Immediate".
|
|
25
|
+
GROUP_OTHER = 0
|
|
26
|
+
GROUP_N0_Z1 = 1
|
|
27
|
+
GROUP_A_00 = 2
|
|
28
|
+
GROUP_A_FF = 3
|
|
29
|
+
GROUP_A_01 = 4
|
|
30
|
+
GROUP_B_01 = 5
|
|
31
|
+
GROUP_WORD_01 = 6
|
|
32
|
+
GROUP_LEA = 7
|
|
33
|
+
GROUPS = {
|
|
34
|
+
GROUP_N0_Z1 => %i[bita cmpa],
|
|
35
|
+
GROUP_A_00 => %i[lda ldd],
|
|
36
|
+
GROUP_A_FF => %i[adca adda anda eora ora sbca suba],
|
|
37
|
+
GROUP_A_01 => %i[asla asra coma daa deca inca lsra nega rola rora subd tsta xdeca xnca],
|
|
38
|
+
GROUP_B_01 => %i[sex],
|
|
39
|
+
GROUP_WORD_01 => %i[cmpd cmps cmpu cmpx cmpy xaddd xaddu],
|
|
40
|
+
GROUP_LEA => %i[leas leau leax leay]
|
|
41
|
+
}.flat_map { |group, operations| operations.map { |operation| [operation, group] } }.to_h.freeze
|
|
42
|
+
|
|
43
|
+
# The plan between instructions: the next cycle fetches an opcode.
|
|
44
|
+
FETCH_PLAN = %i[fetch].freeze
|
|
45
|
+
|
|
46
|
+
# The tail an instruction has once it is running its tail, never run.
|
|
47
|
+
# The empty tail, JUMP_TAIL, runs the instruction method as the
|
|
48
|
+
# address steps end.
|
|
49
|
+
NO_TAIL = %i[none].freeze
|
|
50
|
+
JUMP_TAIL = [].freeze
|
|
51
|
+
|
|
52
|
+
DIRECT_PLAN = %i[direct dead_v].freeze
|
|
53
|
+
EXTENDED_PLAN = %i[extended_hi extended_lo dead_v].freeze
|
|
54
|
+
INDEXED_PLAN = %i[postbyte].freeze
|
|
55
|
+
|
|
56
|
+
# The steps that read or write the address once it is known.
|
|
57
|
+
TAILS = {
|
|
58
|
+
read8: %i[read_exec],
|
|
59
|
+
read16: %i[read_hi read_lo_exec],
|
|
60
|
+
read16_dead: %i[read_hi read_lo_exec dead_v],
|
|
61
|
+
write8: %i[write_exec],
|
|
62
|
+
write16: %i[write_hi_exec write_lo],
|
|
63
|
+
rmw: %i[rmw_read dead_v rmw_write],
|
|
64
|
+
test: %i[read_exec dead_v dead_v],
|
|
65
|
+
jump: JUMP_TAIL,
|
|
66
|
+
subroutine: %i[dead_ea dead_v push_pcl push_pch_exec],
|
|
67
|
+
load_address: %i[dead_v_exec]
|
|
68
|
+
}.freeze
|
|
69
|
+
|
|
70
|
+
IMMEDIATE_TAILS = {
|
|
71
|
+
read8: %i[immediate_exec],
|
|
72
|
+
read16: %i[immediate_hi immediate_lo_exec],
|
|
73
|
+
read16_dead: %i[immediate_hi immediate_lo_exec dead_v]
|
|
74
|
+
}.freeze
|
|
75
|
+
|
|
76
|
+
# The 12 bytes SWI and the hardware interrupts other than FIRQ stack,
|
|
77
|
+
# in the order they go onto S.
|
|
78
|
+
PUSH_ALL = %i[push_pcl push_pch push_wl push_wh push_yl push_yh push_xl push_xh push_dp push_b push_a
|
|
79
|
+
push_cc].freeze
|
|
80
|
+
VECTOR_STEPS = %i[dead_v vector_hi vector_lo dead_v].freeze
|
|
81
|
+
|
|
82
|
+
# SWI, SWI2, SWI3 and the undocumented XRES, XSWI2 and XFIRQ, after
|
|
83
|
+
# the opcode. The instruction method picks the vector.
|
|
84
|
+
SOFTWARE_INTERRUPT_TAIL = [:dead_pc, :dead_v_exec, *PUSH_ALL, *VECTOR_STEPS].freeze
|
|
85
|
+
|
|
86
|
+
# IRQ and NMI after the cycle that would have fetched the opcode, and
|
|
87
|
+
# FIRQ, which stacks only PC and CC.
|
|
88
|
+
INTERRUPT_PLAN = [:dead_pc, :dead_v_exec, *PUSH_ALL, *VECTOR_STEPS].freeze
|
|
89
|
+
FAST_INTERRUPT_PLAN = [:dead_pc, :dead_v_exec, :push_pcl, :push_pch, :push_cc, *VECTOR_STEPS].freeze
|
|
90
|
+
|
|
91
|
+
# The interrupt CWAI was waiting for: its vector, no more stacking.
|
|
92
|
+
WAITED_INTERRUPT_PLAN = %i[vector_hi vector_lo dead_v].freeze
|
|
93
|
+
|
|
94
|
+
# RTI after pulling CC, with E set and with E clear.
|
|
95
|
+
RETURN_ENTIRE_PLAN = %i[pull_a pull_b pull_dp pull_xh pull_xl pull_yh pull_yl pull_wh pull_wl pull_pch pull_pcl
|
|
96
|
+
dead_stack].freeze
|
|
97
|
+
RETURN_FAST_PLAN = %i[pull_pch pull_pcl dead_stack].freeze
|
|
98
|
+
|
|
99
|
+
# The cycles after RESET goes high: two dead reads of $FFFE, the
|
|
100
|
+
# vector, and a dead cycle.
|
|
101
|
+
RESET_PLAN = %i[reset_dead reset_dead reset_hi reset_lo dead_v].freeze
|
|
102
|
+
|
|
103
|
+
LONG_BRANCH_TAKEN_PLAN = %i[dead_v_take].freeze
|
|
104
|
+
SYNC_END_PLAN = %i[idle].freeze
|
|
105
|
+
|
|
106
|
+
# PSH and PUL by postbyte: the bytes in the order they go onto or
|
|
107
|
+
# come off the stack, PC first on and last off.
|
|
108
|
+
PUSH_ORDER = [[0x80, %i[push_pcl push_pch]], [0x40, %i[push_wl push_wh]], [0x20, %i[push_yl push_yh]],
|
|
109
|
+
[0x10, %i[push_xl push_xh]], [0x08, %i[push_dp]], [0x04, %i[push_b]], [0x02, %i[push_a]],
|
|
110
|
+
[0x01, %i[push_cc]]].freeze
|
|
111
|
+
PULL_ORDER = [[0x01, %i[pull_cc]], [0x02, %i[pull_a]], [0x04, %i[pull_b]], [0x08, %i[pull_dp]],
|
|
112
|
+
[0x10, %i[pull_xh pull_xl]], [0x20, %i[pull_yh pull_yl]], [0x40, %i[pull_wh pull_wl]],
|
|
113
|
+
[0x80, %i[pull_pch pull_pcl]]].freeze
|
|
114
|
+
PUSH_PLANS = Array.new(256) do |mask|
|
|
115
|
+
[:dead_v, :dead_v, :dead_stack, *PUSH_ORDER.flat_map { |bit, steps| mask.nobits?(bit) ? [] : steps }].freeze
|
|
116
|
+
end.freeze
|
|
117
|
+
PULL_PLANS = Array.new(256) do |mask|
|
|
118
|
+
[:dead_v, :dead_v, *PULL_ORDER.flat_map { |bit, steps| mask.nobits?(bit) ? [] : steps }, :dead_stack].freeze
|
|
119
|
+
end.freeze
|
|
120
|
+
|
|
121
|
+
# The steps after the postbyte, by postbyte, Figure 17 sheet 4.
|
|
122
|
+
INDEXED_PLANS = Array.new(256) do |post|
|
|
123
|
+
next %i[dead_pc dead_v].freeze if post < 0x80
|
|
124
|
+
|
|
125
|
+
plan = case post & 0x0f
|
|
126
|
+
when 0x0, 0x2 then %i[dead_pc dead_v dead_v]
|
|
127
|
+
when 0x1, 0x3 then %i[dead_pc dead_v dead_v dead_v]
|
|
128
|
+
when 0x4 then %i[dead_pc]
|
|
129
|
+
when 0x5, 0x6 then %i[dead_pc dead_v]
|
|
130
|
+
when 0x8 then %i[offset8 dead_v]
|
|
131
|
+
when 0x9 then %i[offset_hi offset_lo_register dead_pc dead_v dead_v]
|
|
132
|
+
when 0xb then %i[dead_pc dead_pc_next dead_pc_next2 dead_v dead_v]
|
|
133
|
+
when 0xc then %i[offset8_pc dead_v]
|
|
134
|
+
when 0xd then %i[offset_hi offset_lo_pc dead_pc dead_v dead_v dead_v]
|
|
135
|
+
when 0xf then %i[offset_hi offset_lo_absolute dead_pc]
|
|
136
|
+
else []
|
|
137
|
+
end
|
|
138
|
+
plan += %i[indirect_hi indirect_lo dead_v] if post.anybits?(0x10)
|
|
139
|
+
plan.freeze
|
|
140
|
+
end.freeze
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Chips
|
|
5
|
+
class M6809
|
|
6
|
+
# The programmer's registers, and the codes TFR and EXG and the
|
|
7
|
+
# indexed postbyte name them by.
|
|
8
|
+
module Registers
|
|
9
|
+
attr_accessor :a, :b, :x, :y, :u, :s, :pc, :dp, :cc
|
|
10
|
+
|
|
11
|
+
def d = (@a << 8) | @b
|
|
12
|
+
|
|
13
|
+
def d=(value)
|
|
14
|
+
@a = value >> 8
|
|
15
|
+
@b = value & 0xff
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Whether NMI is armed: a load of S since reset.
|
|
19
|
+
def nmi_armed? = @nmi_armed
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def load_s(value)
|
|
24
|
+
@s = value
|
|
25
|
+
@nmi_armed = true
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# The register bits 5 and 6 of an indexed postbyte name.
|
|
29
|
+
def index_register
|
|
30
|
+
case @post & 0x60
|
|
31
|
+
when 0x00 then @x
|
|
32
|
+
when 0x20 then @y
|
|
33
|
+
when 0x40 then @u
|
|
34
|
+
else @s
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def index_register=(value)
|
|
39
|
+
case @post & 0x60
|
|
40
|
+
when 0x00 then @x = value
|
|
41
|
+
when 0x20 then @y = value
|
|
42
|
+
when 0x40 then @u = value
|
|
43
|
+
else load_s(value)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# A TFR or EXG register: D, X, Y, U, S and PC are 0 to 5, A, B, CC
|
|
48
|
+
# and DP 8 to 11. The other codes read as $FF or $FFFF.
|
|
49
|
+
def transfer_register(code)
|
|
50
|
+
case code
|
|
51
|
+
when 0x0 then d
|
|
52
|
+
when 0x1 then @x
|
|
53
|
+
when 0x2 then @y
|
|
54
|
+
when 0x3 then @u
|
|
55
|
+
when 0x4 then @s
|
|
56
|
+
when 0x5 then @pc
|
|
57
|
+
when 0x8 then @a
|
|
58
|
+
when 0x9 then @b
|
|
59
|
+
when 0xa then @cc
|
|
60
|
+
when 0xb then @dp
|
|
61
|
+
else code < 8 ? 0xffff : 0xff
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def set_transfer_register(code, value)
|
|
66
|
+
case code
|
|
67
|
+
when 0x0 then self.d = value
|
|
68
|
+
when 0x1 then @x = value
|
|
69
|
+
when 0x2 then @y = value
|
|
70
|
+
when 0x3 then @u = value
|
|
71
|
+
when 0x4 then load_s(value)
|
|
72
|
+
when 0x5 then @pc = value
|
|
73
|
+
when 0x8 then @a = value
|
|
74
|
+
when 0x9 then @b = value
|
|
75
|
+
when 0xa then @cc = value
|
|
76
|
+
when 0xb then @dp = value
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Chips
|
|
5
|
+
class M6809
|
|
6
|
+
# The stack steps of PSH, PUL, JSR, BSR, RTS, RTI, the software
|
|
7
|
+
# interrupts and the hardware ones. Each pushes onto or pulls off the
|
|
8
|
+
# stack @stack_u names: U for PSHU and PULU, S otherwise. The "other"
|
|
9
|
+
# stack pointer, w, is the one that isn't.
|
|
10
|
+
module Stack
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def push(value)
|
|
14
|
+
if @stack_u
|
|
15
|
+
@u = (@u - 1) & 0xffff
|
|
16
|
+
@bus.write(@u, value)
|
|
17
|
+
else
|
|
18
|
+
@s = (@s - 1) & 0xffff
|
|
19
|
+
@bus.write(@s, value)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def pull
|
|
24
|
+
if @stack_u
|
|
25
|
+
value = @bus.read(@u)
|
|
26
|
+
@u = (@u + 1) & 0xffff
|
|
27
|
+
else
|
|
28
|
+
value = @bus.read(@s)
|
|
29
|
+
@s = (@s + 1) & 0xffff
|
|
30
|
+
end
|
|
31
|
+
value
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def dead_stack = @bus.read(@stack_u ? @u : @s)
|
|
35
|
+
|
|
36
|
+
def other_stack = @stack_u ? @s : @u
|
|
37
|
+
|
|
38
|
+
def other_stack=(value)
|
|
39
|
+
@stack_u ? load_s(value) : @u = value
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def push_s_post = stack_post(false, PUSH_PLANS)
|
|
43
|
+
|
|
44
|
+
def push_u_post = stack_post(true, PUSH_PLANS)
|
|
45
|
+
|
|
46
|
+
def pull_s_post = stack_post(false, PULL_PLANS)
|
|
47
|
+
|
|
48
|
+
def pull_u_post = stack_post(true, PULL_PLANS)
|
|
49
|
+
|
|
50
|
+
def stack_post(stack_u, plans)
|
|
51
|
+
@stack_u = stack_u
|
|
52
|
+
switch_to(plans[read_pc])
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def push_pcl = push(@pc & 0xff)
|
|
56
|
+
def push_pch = push(@pc >> 8)
|
|
57
|
+
def push_wl = push(other_stack & 0xff)
|
|
58
|
+
def push_wh = push(other_stack >> 8)
|
|
59
|
+
def push_yl = push(@y & 0xff)
|
|
60
|
+
def push_yh = push(@y >> 8)
|
|
61
|
+
def push_xl = push(@x & 0xff)
|
|
62
|
+
def push_xh = push(@x >> 8)
|
|
63
|
+
def push_dp = push(@dp)
|
|
64
|
+
def push_b = push(@b)
|
|
65
|
+
def push_a = push(@a)
|
|
66
|
+
def push_cc = push(@cc)
|
|
67
|
+
|
|
68
|
+
def push_pch_exec
|
|
69
|
+
push_pch
|
|
70
|
+
execute
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def pull_cc
|
|
74
|
+
@cc = pull
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def pull_a
|
|
78
|
+
@a = pull
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def pull_b
|
|
82
|
+
@b = pull
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def pull_dp
|
|
86
|
+
@dp = pull
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def pull_xh
|
|
90
|
+
@x = (pull << 8) | (@x & 0xff)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def pull_xl
|
|
94
|
+
@x = (@x & 0xff00) | pull
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def pull_yh
|
|
98
|
+
@y = (pull << 8) | (@y & 0xff)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def pull_yl
|
|
102
|
+
@y = (@y & 0xff00) | pull
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def pull_wh
|
|
106
|
+
self.other_stack = (pull << 8) | (other_stack & 0xff)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def pull_wl
|
|
110
|
+
self.other_stack = (other_stack & 0xff00) | pull
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def pull_pch
|
|
114
|
+
@pc = (pull << 8) | (@pc & 0xff)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def pull_pcl
|
|
118
|
+
@pc = (@pc & 0xff00) | pull
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# RTI after pulling CC: the whole state when E is set, else PC.
|
|
122
|
+
def return_cc
|
|
123
|
+
pull_cc
|
|
124
|
+
switch_to(@cc.nobits?(ENTIRE) ? RETURN_FAST_PLAN : RETURN_ENTIRE_PLAN)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# The vector of the interrupt or SWI being taken. The masks it sets
|
|
128
|
+
# take effect as its first byte is read.
|
|
129
|
+
def vector_hi
|
|
130
|
+
@value = @bus.read(@vector) << 8
|
|
131
|
+
@cc |= @interrupt_mask
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def vector_lo
|
|
135
|
+
@pc = @value | @bus.read(@vector + 1)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "badline/chips/m6809/core"
|
|
4
|
+
|
|
5
|
+
module Badline
|
|
6
|
+
module Chips
|
|
7
|
+
# The Motorola 6809E, which the SuperPET has beside its 6502. It is not
|
|
8
|
+
# part of badline/core, so a machine that has one requires it.
|
|
9
|
+
class M6809
|
|
10
|
+
include Core
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -13,8 +13,12 @@ module Badline
|
|
|
13
13
|
# reads N - 1 until cycle T, 0 until cycle N x T, and $FF with the flag
|
|
14
14
|
# set on cycle N x T: 52 at ÷8 sets the flag on cycle 416. From there
|
|
15
15
|
# the counter counts every cycle, so it tells how long ago the flag
|
|
16
|
-
# set, and each count through 0 sets the flag again.
|
|
17
|
-
# the prescaler back.
|
|
16
|
+
# set, and each count through 0 sets the flag again. On the 6532 only
|
|
17
|
+
# a write puts the prescaler back. On the 6530, as the KIM-1 User
|
|
18
|
+
# Manual's appendix H has it, a read of the counter after the flag
|
|
19
|
+
# set does too, and the counter counts on from where it is at the
|
|
20
|
+
# interval written: a 6530 builds its timer with
|
|
21
|
+
# read_restores_divider. No test program has confirmed it.
|
|
18
22
|
#
|
|
19
23
|
# Reading or writing the counter clears the flag, except for a read on
|
|
20
24
|
# the cycle that sets it. Each access takes A3 as the interrupt enable.
|
|
@@ -24,7 +28,8 @@ module Badline
|
|
|
24
28
|
attr_reader :counter, :flag
|
|
25
29
|
attr_accessor :irq_enabled
|
|
26
30
|
|
|
27
|
-
def initialize
|
|
31
|
+
def initialize(read_restores_divider: false)
|
|
32
|
+
@read_restores_divider = read_restores_divider
|
|
28
33
|
@counter = 0
|
|
29
34
|
@divider = 1
|
|
30
35
|
@prescale = 1
|
|
@@ -50,6 +55,7 @@ module Badline
|
|
|
50
55
|
def read(irq_enabled)
|
|
51
56
|
@irq_enabled = irq_enabled
|
|
52
57
|
@flag = false unless @fired
|
|
58
|
+
restore_divider if @read_restores_divider && @expired
|
|
53
59
|
@counter
|
|
54
60
|
end
|
|
55
61
|
|
|
@@ -66,6 +72,11 @@ module Badline
|
|
|
66
72
|
|
|
67
73
|
private
|
|
68
74
|
|
|
75
|
+
def restore_divider
|
|
76
|
+
@expired = false
|
|
77
|
+
@prescale = @divider
|
|
78
|
+
end
|
|
79
|
+
|
|
69
80
|
def count
|
|
70
81
|
@counter = (@counter - 1) & 0xff
|
|
71
82
|
return unless @counter == 0xff
|
data/lib/badline/chips/rriot.rb
CHANGED
|
@@ -9,7 +9,9 @@ module Badline
|
|
|
9
9
|
# of RAM, two 8-bit ports, A and B, each with a direction register, a 1
|
|
10
10
|
# making the pin an output, and the 6532's interval timer (RIOT::Timer).
|
|
11
11
|
# It has no edge detector. The 8050 and 8250's floppy controller has
|
|
12
|
-
# one, its ROM the controller's program.
|
|
12
|
+
# one, its ROM the controller's program, and the KIM-1 two. Its timer
|
|
13
|
+
# reads its counter as the 6532's does, but a read after the flag sets
|
|
14
|
+
# puts the prescaler back (RIOT::Timer).
|
|
13
15
|
#
|
|
14
16
|
# Which addresses select the ROM, the RAM and the registers is part of
|
|
15
17
|
# the mask, so the board decodes them and calls read_rom (A9-A0),
|
|
@@ -36,7 +38,7 @@ module Badline
|
|
|
36
38
|
@peripheral = peripheral
|
|
37
39
|
@irq_on_pb7 = irq_on_pb7
|
|
38
40
|
@ram = Array.new(64, 0)
|
|
39
|
-
@timer = RIOT::Timer.new
|
|
41
|
+
@timer = RIOT::Timer.new(read_restores_divider: true)
|
|
40
42
|
reset!
|
|
41
43
|
end
|
|
42
44
|
|
|
@@ -72,7 +74,10 @@ module Badline
|
|
|
72
74
|
|
|
73
75
|
# Everything the RRIOT holds but its timer's counter, for comparing
|
|
74
76
|
# it at two points.
|
|
75
|
-
def idle_state = [*@ram,
|
|
77
|
+
def idle_state = [*@ram, *port_state]
|
|
78
|
+
|
|
79
|
+
# The ports and the timer but for its counter.
|
|
80
|
+
def port_state = [@ora, @orb, @ddra, @ddrb, *@timer.state, @timer.flag]
|
|
76
81
|
|
|
77
82
|
def read_rom(addr) = @rom[addr & 0x3ff]
|
|
78
83
|
|
|
@@ -21,7 +21,7 @@ module Badline
|
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
# Cycles until the current window closes, counting the next one as 1.
|
|
24
|
-
attr_reader :cycles_to_close
|
|
24
|
+
attr_reader :cycles_to_close, :rate, :clock_hz
|
|
25
25
|
|
|
26
26
|
# How far the current window has got, in a keyed snapshot module
|
|
27
27
|
# (Snapshot::Keyed) under `prefix`. The clock and the rate are the
|
|
@@ -195,7 +195,7 @@ module Badline
|
|
|
195
195
|
@unfiltered = 0
|
|
196
196
|
route_voice(voice1.output >> 7, 0x1)
|
|
197
197
|
route_voice(voice2.output >> 7, 0x2)
|
|
198
|
-
if @routing
|
|
198
|
+
if @routing.anybits?(0x4)
|
|
199
199
|
@input += voice3.output >> 7
|
|
200
200
|
elsif !@voice3_off
|
|
201
201
|
@unfiltered += voice3.output >> 7
|
|
@@ -216,9 +216,9 @@ module Badline
|
|
|
216
216
|
|
|
217
217
|
def filtered
|
|
218
218
|
value = 0
|
|
219
|
-
value += @lowpass if @mode
|
|
220
|
-
value += @bandpass if @mode
|
|
221
|
-
value += @highpass if @mode
|
|
219
|
+
value += @lowpass if @mode.anybits?(0x1)
|
|
220
|
+
value += @bandpass if @mode.anybits?(0x2)
|
|
221
|
+
value += @highpass if @mode.anybits?(0x4)
|
|
222
222
|
value
|
|
223
223
|
end
|
|
224
224
|
end
|
|
@@ -221,7 +221,11 @@ module Badline
|
|
|
221
221
|
# voice's under voice1. to voice3., and the filter's under filter.
|
|
222
222
|
# and ext. Saving catches the DSP up first, so no span or deferred
|
|
223
223
|
# write is left to save. What the registers decode to is worked out
|
|
224
|
-
# from them on load.
|
|
224
|
+
# from them on load. A recording SID also saves how far its recording
|
|
225
|
+
# has got under rec., with the rate, the clock and the filter chunk it
|
|
226
|
+
# records at: the samples' windows bound the filter's steps, so a SID
|
|
227
|
+
# loaded from it that records at the same rate and clock carries on
|
|
228
|
+
# from that window and integrates the steps the saved one does.
|
|
225
229
|
module KeyedState
|
|
226
230
|
def save_fields(out)
|
|
227
231
|
catch_up
|
|
@@ -230,6 +234,8 @@ module Badline
|
|
|
230
234
|
@voice2.save_fields(out, "voice2.")
|
|
231
235
|
@voice3.save_fields(out, "voice3.")
|
|
232
236
|
@filter.save_fields(out, "filter.")
|
|
237
|
+
recording = @decimator || @resumed_recording
|
|
238
|
+
save_recording(out, recording) if recording
|
|
233
239
|
out
|
|
234
240
|
end
|
|
235
241
|
|
|
@@ -243,23 +249,7 @@ module Badline
|
|
|
243
249
|
@voice2.load_fields(input, "voice2.")
|
|
244
250
|
@voice3.load_fields(input, "voice3.")
|
|
245
251
|
@filter.load_fields(input, "filter.")
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
# How far the recording has got into the sample it is averaging,
|
|
249
|
-
# under `prefix`, for a host that carries on recording from a
|
|
250
|
-
# snapshot: it calls #record, then #load_recording_fields.
|
|
251
|
-
def save_recording_fields(out, prefix)
|
|
252
|
-
decimator = @decimator
|
|
253
|
-
raise ArgumentError, "the SID isn't recording" if decimator.nil?
|
|
254
|
-
|
|
255
|
-
decimator.save_fields(out, prefix)
|
|
256
|
-
end
|
|
257
|
-
|
|
258
|
-
def load_recording_fields(input, prefix)
|
|
259
|
-
decimator = @decimator
|
|
260
|
-
raise ArgumentError, "the SID isn't recording" if decimator.nil?
|
|
261
|
-
|
|
262
|
-
decimator.load_fields(input, prefix)
|
|
252
|
+
load_recording(input)
|
|
263
253
|
end
|
|
264
254
|
|
|
265
255
|
def restored!
|
|
@@ -272,6 +262,49 @@ module Badline
|
|
|
272
262
|
|
|
273
263
|
private
|
|
274
264
|
|
|
265
|
+
def save_recording(out, decimator)
|
|
266
|
+
out.int("rec.rate", decimator.rate).int("rec.clock_hz", decimator.clock_hz)
|
|
267
|
+
out.int("rec.filter_chunk", @filter_chunk)
|
|
268
|
+
decimator.save_fields(out, "rec.")
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# A SID recording already carries on from the loaded window when it
|
|
272
|
+
# records at its rate, clock and filter chunk, and starts a new
|
|
273
|
+
# window otherwise.
|
|
274
|
+
def load_recording(input)
|
|
275
|
+
@resumed_recording = nil
|
|
276
|
+
live = @decimator
|
|
277
|
+
loaded = loaded_recording(input)
|
|
278
|
+
chunk = loaded ? input.int("rec.filter_chunk", @filter_chunk) : @filter_chunk
|
|
279
|
+
if live.nil?
|
|
280
|
+
@filter_chunk = chunk
|
|
281
|
+
@resumed_recording = loaded
|
|
282
|
+
elsif loaded && chunk == @filter_chunk && loaded.rate == live.rate && loaded.clock_hz == live.clock_hz
|
|
283
|
+
@decimator = loaded
|
|
284
|
+
else
|
|
285
|
+
@decimator = Decimator.new(clock_hz: live.clock_hz, rate: live.rate)
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def loaded_recording(input)
|
|
290
|
+
rate = input.int("rec.rate", 0)
|
|
291
|
+
return unless rate.positive?
|
|
292
|
+
|
|
293
|
+
decimator = Decimator.new(clock_hz: input.int("rec.clock_hz", Region::PAL.clock_hz), rate:)
|
|
294
|
+
decimator.load_fields(input, "rec.")
|
|
295
|
+
decimator
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# The window a loaded snapshot's recording had got to, when this
|
|
299
|
+
# recording is at its rate, clock and filter chunk.
|
|
300
|
+
def resumed_recording(rate, filter_chunk, clock_hz)
|
|
301
|
+
resumed = @resumed_recording
|
|
302
|
+
@resumed_recording = nil
|
|
303
|
+
return if resumed.nil? || filter_chunk != @filter_chunk
|
|
304
|
+
|
|
305
|
+
resumed if resumed.rate == rate && resumed.clock_hz == clock_hz
|
|
306
|
+
end
|
|
307
|
+
|
|
275
308
|
def restore_voice(voice, base)
|
|
276
309
|
control = register(base + 4)
|
|
277
310
|
voice.waveform.restore_registers(register(base) | (register(base + 1) << 8),
|
|
@@ -234,7 +234,7 @@ module Badline
|
|
|
234
234
|
def compare_pulse(phase = @accumulator >> 12) = (@pulse = @test || phase >= @pulse_width ? 0xfff : 0x000)
|
|
235
235
|
|
|
236
236
|
# More than one bit set in the selection.
|
|
237
|
-
def combined?(selected) = selected
|
|
237
|
+
def combined?(selected) = selected.anybits?(selected - 1)
|
|
238
238
|
|
|
239
239
|
# The one point in the cycle where the waveform selector drives the DAC:
|
|
240
240
|
# either it latches a fresh value, or nothing drives the line and the
|
|
@@ -274,8 +274,8 @@ module Badline
|
|
|
274
274
|
def advance
|
|
275
275
|
previous = @accumulator
|
|
276
276
|
@accumulator = (previous + @frequency) & 0xffffff
|
|
277
|
-
@msb_rising = @accumulator & ~previous
|
|
278
|
-
if @accumulator & ~previous
|
|
277
|
+
@msb_rising = (@accumulator & ~previous).anybits?(MSB)
|
|
278
|
+
if (@accumulator & ~previous).anybits?(0x080000)
|
|
279
279
|
@shift_pipeline = 2
|
|
280
280
|
elsif @shift_pipeline != 0 && (@shift_pipeline -= 1).zero?
|
|
281
281
|
write_back_shift
|
|
@@ -292,12 +292,12 @@ module Badline
|
|
|
292
292
|
# TriXOR = !Saw & ((!V3 & Ring) ^ bit23), where V3 is the modulating
|
|
293
293
|
# voice's MSB (SID/ringmod).
|
|
294
294
|
def inverted?(selected)
|
|
295
|
-
return false if selected
|
|
295
|
+
return false if selected.anybits?(0x2)
|
|
296
296
|
|
|
297
|
-
msb = @accumulator
|
|
297
|
+
msb = @accumulator.anybits?(MSB)
|
|
298
298
|
return msb unless @ring
|
|
299
299
|
|
|
300
|
-
msb ==
|
|
300
|
+
msb == @sync_source.accumulator.anybits?(MSB)
|
|
301
301
|
end
|
|
302
302
|
end
|
|
303
303
|
end
|
data/lib/badline/chips/sid.rb
CHANGED
|
@@ -70,6 +70,7 @@ module Badline
|
|
|
70
70
|
@pending_cycles = 0
|
|
71
71
|
@deferred_writes = []
|
|
72
72
|
@decimator = nil
|
|
73
|
+
@resumed_recording = nil
|
|
73
74
|
@samples = []
|
|
74
75
|
@voice_samples = nil
|
|
75
76
|
@filter_chunk = filter_chunk
|
|
@@ -120,12 +121,15 @@ module Badline
|
|
|
120
121
|
# Starts collecting the output, averaged down to `rate` samples a
|
|
121
122
|
# second, for #drain_samples. `filter_chunk` overrides the one the SID
|
|
122
123
|
# was built with, for a machine that built its own. `clock_hz` is the
|
|
123
|
-
# rate the SID's cycles are taken to run at.
|
|
124
|
+
# rate the SID's cycles are taken to run at. A SID loaded from a
|
|
125
|
+
# recording one carries on from the window that one had got to, at the
|
|
126
|
+
# same rate, clock and filter chunk.
|
|
124
127
|
def record(rate:, filter_chunk: @filter_chunk, clock_hz: Region::PAL.clock_hz)
|
|
125
128
|
synthesize!
|
|
126
129
|
catch_up
|
|
130
|
+
resumed = resumed_recording(rate, filter_chunk, clock_hz)
|
|
127
131
|
@filter_chunk = filter_chunk
|
|
128
|
-
@decimator = Decimator.new(clock_hz:, rate:)
|
|
132
|
+
@decimator = resumed || Decimator.new(clock_hz:, rate:)
|
|
129
133
|
@samples = []
|
|
130
134
|
end
|
|
131
135
|
|