badline 0.1.0 → 0.2.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 +167 -0
- data/CLAUDE.md +217 -0
- data/CODE_OF_CONDUCT.md +92 -0
- data/CONTRIBUTING.md +51 -0
- data/README.md +224 -31
- data/doc/pinned-behaviour.md +1117 -0
- data/exe/badline +22 -2
- data/exe/badline-sid +34 -0
- data/lib/badline/address_bus.rb +88 -23
- data/lib/badline/audio/aiff.rb +36 -0
- data/lib/badline/audio/bare_player.rb +129 -0
- data/lib/badline/audio/cli.rb +133 -0
- data/lib/badline/audio/console.rb +75 -0
- data/lib/badline/audio/jukebox.rb +82 -0
- data/lib/badline/audio/machine_player.rb +57 -0
- data/lib/badline/audio/options.rb +137 -0
- data/lib/badline/audio/pcm_writer.rb +57 -0
- data/lib/badline/audio/playback.rb +108 -0
- data/lib/badline/audio/renderer.rb +85 -0
- data/lib/badline/audio/sdl_sink.rb +102 -0
- data/lib/badline/audio/wav.rb +23 -0
- data/lib/badline/audio.rb +14 -0
- data/lib/badline/cartridge/action_replay.rb +111 -0
- data/lib/badline/cartridge/atomic_power.rb +24 -0
- data/lib/badline/cartridge/bank.rb +81 -0
- data/lib/badline/cartridge/comal80.rb +48 -0
- data/lib/badline/cartridge/dinamic.rb +34 -0
- data/lib/badline/cartridge/easy_flash.rb +122 -0
- data/lib/badline/cartridge/epyx_fastload.rb +71 -0
- data/lib/badline/cartridge/final_cartridge3.rb +77 -0
- data/lib/badline/cartridge/flash.rb +265 -0
- data/lib/badline/cartridge/freezer.rb +31 -0
- data/lib/badline/cartridge/fun_play.rb +38 -0
- data/lib/badline/cartridge/g_mod2.rb +62 -0
- data/lib/badline/cartridge/game_system.rb +40 -0
- data/lib/badline/cartridge/kcs_power.rb +72 -0
- data/lib/badline/cartridge/mach5.rb +34 -0
- data/lib/badline/cartridge/magic_desk.rb +6 -3
- data/lib/badline/cartridge/ocean.rb +11 -12
- data/lib/badline/cartridge/pagefox.rb +56 -0
- data/lib/badline/cartridge/retro_replay.rb +276 -0
- data/lib/badline/cartridge/rex_utility.rb +33 -0
- data/lib/badline/cartridge/rgcd.rb +48 -0
- data/lib/badline/cartridge/simons_basic.rb +40 -0
- data/lib/badline/cartridge/standard.rb +3 -19
- data/lib/badline/cartridge/super_games.rb +36 -0
- data/lib/badline/cartridge/westermann.rb +34 -0
- data/lib/badline/cartridge/zaxxon.rb +50 -0
- data/lib/badline/cartridge.rb +131 -15
- data/lib/badline/cia/interrupt_register.rb +88 -0
- data/lib/badline/cia/serial.rb +219 -0
- data/lib/badline/cia/timer.rb +43 -31
- data/lib/badline/cia.rb +109 -74
- data/lib/badline/color_memory.rb +14 -5
- data/lib/badline/computer.rb +68 -8
- data/lib/badline/control_ports.rb +54 -9
- data/lib/badline/cpu/addressing.rb +131 -0
- data/lib/badline/cpu/microcode.rb +171 -0
- data/lib/badline/cpu/operations.rb +88 -0
- data/lib/badline/cpu/stack_operations.rb +82 -0
- data/lib/badline/cpu.rb +108 -167
- data/lib/badline/cycleable.rb +3 -0
- data/lib/badline/datasette.rb +81 -0
- data/lib/badline/debug_register.rb +28 -0
- data/lib/badline/gui/application.rb +79 -18
- data/lib/badline/gui/gamepads.rb +97 -0
- data/lib/badline/gui/joy_map.rb +15 -7
- data/lib/badline/gui.rb +1 -0
- data/lib/badline/input/mouse1351.rb +41 -0
- data/lib/badline/input/paddles.rb +36 -0
- data/lib/badline/input.rb +4 -0
- data/lib/badline/instruction_set/arithmetic.rb +16 -21
- data/lib/badline/instruction_set/bitwise.rb +17 -22
- data/lib/badline/instruction_set/branch.rb +20 -19
- data/lib/badline/instruction_set/flag.rb +7 -7
- data/lib/badline/instruction_set/illegal.rb +14 -22
- data/lib/badline/instruction_set/inc_dec.rb +8 -10
- data/lib/badline/instruction_set/stack.rb +5 -57
- data/lib/badline/instruction_set/transfer.rb +9 -9
- data/lib/badline/instruction_set.rb +9 -31
- data/lib/badline/interrupts.rb +104 -0
- data/lib/badline/joystick.rb +9 -9
- data/lib/badline/kernal_trap/channel.rb +33 -0
- data/lib/badline/kernal_trap/drive.rb +239 -0
- data/lib/badline/kernal_trap/file.rb +4 -24
- data/lib/badline/kernal_trap/load.rb +2 -2
- data/lib/badline/kernal_trap/routine.rb +34 -0
- data/lib/badline/kernal_trap/save.rb +1 -1
- data/lib/badline/kernal_trap/serial.rb +139 -0
- data/lib/badline/kernal_trap.rb +4 -0
- data/lib/badline/keyboard.rb +40 -16
- data/lib/badline/media.rb +48 -10
- data/lib/badline/roms/eapi/LICENSE.md +15 -0
- data/lib/badline/roms/eapi/README +41 -0
- data/lib/badline/roms/eapi/eapi-am29f040-14 +0 -0
- data/lib/badline/sid/decimator.rb +46 -0
- data/lib/badline/sid/envelope.rb +232 -0
- data/lib/badline/sid/filter.rb +213 -0
- data/lib/badline/sid/voice.rb +61 -0
- data/lib/badline/sid/waveform/combined.rb +126 -0
- data/lib/badline/sid/waveform/fast_forward.rb +105 -0
- data/lib/badline/sid/waveform/noise_writeback.rb +82 -0
- data/lib/badline/sid/waveform.rb +289 -0
- data/lib/badline/sid.rb +274 -7
- data/lib/badline/status.rb +32 -27
- data/lib/badline/storage/crt_file.rb +18 -2
- data/lib/badline/storage/disk_image.rb +52 -5
- data/lib/badline/storage/host_directory.rb +18 -4
- data/lib/badline/storage/sid_file.rb +274 -0
- data/lib/badline/storage/song_lengths.rb +65 -0
- data/lib/badline/storage/t64.rb +67 -0
- data/lib/badline/storage/tap.rb +68 -0
- data/lib/badline/storage.rb +19 -0
- data/lib/badline/time_of_day.rb +105 -52
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic/bank.rb +14 -6
- data/lib/badline/vic/border_mask.rb +76 -0
- data/lib/badline/vic/collisions.rb +104 -0
- data/lib/badline/vic/color_patches.rb +33 -0
- data/lib/badline/vic/display_state.rb +92 -29
- data/lib/badline/vic/graphics_mode.rb +78 -53
- data/lib/badline/vic/graphics_shifter.rb +140 -0
- data/lib/badline/vic/register_log.rb +63 -0
- data/lib/badline/vic/registers.rb +3 -0
- data/lib/badline/vic/sequencer.rb +163 -137
- data/lib/badline/vic/sequencer_output.rb +116 -0
- data/lib/badline/vic/sprite/internal_bus.rb +49 -0
- data/lib/badline/vic/sprite/shifter.rb +129 -0
- data/lib/badline/vic/sprite.rb +275 -65
- data/lib/badline/vic/sprites.rb +220 -58
- data/lib/badline/vic.rb +373 -49
- data/lib/badline.rb +5 -0
- data/vendor/.gitkeep +0 -0
- metadata +103 -4
data/lib/badline/cartridge.rb
CHANGED
|
@@ -1,33 +1,72 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "badline/cartridge/bank"
|
|
4
|
+
require "badline/cartridge/flash"
|
|
3
5
|
require "badline/cartridge/standard"
|
|
6
|
+
require "badline/cartridge/simons_basic"
|
|
4
7
|
require "badline/cartridge/ocean"
|
|
8
|
+
require "badline/cartridge/fun_play"
|
|
9
|
+
require "badline/cartridge/super_games"
|
|
10
|
+
require "badline/cartridge/epyx_fastload"
|
|
11
|
+
require "badline/cartridge/westermann"
|
|
12
|
+
require "badline/cartridge/rex_utility"
|
|
13
|
+
require "badline/cartridge/game_system"
|
|
14
|
+
require "badline/cartridge/dinamic"
|
|
15
|
+
require "badline/cartridge/zaxxon"
|
|
5
16
|
require "badline/cartridge/magic_desk"
|
|
17
|
+
require "badline/cartridge/comal80"
|
|
18
|
+
require "badline/cartridge/easy_flash"
|
|
19
|
+
require "badline/cartridge/mach5"
|
|
20
|
+
require "badline/cartridge/pagefox"
|
|
21
|
+
require "badline/cartridge/rgcd"
|
|
22
|
+
require "badline/cartridge/g_mod2"
|
|
23
|
+
require "badline/cartridge/freezer"
|
|
24
|
+
require "badline/cartridge/action_replay"
|
|
25
|
+
require "badline/cartridge/atomic_power"
|
|
26
|
+
require "badline/cartridge/final_cartridge3"
|
|
27
|
+
require "badline/cartridge/retro_replay"
|
|
28
|
+
require "badline/cartridge/kcs_power"
|
|
6
29
|
|
|
7
30
|
module Badline
|
|
8
31
|
class Cartridge
|
|
9
32
|
class UnsupportedTypeError < StandardError; end
|
|
10
33
|
|
|
11
|
-
ROML_START = 0x8000
|
|
12
34
|
ROMH_START = 0xa000
|
|
13
|
-
ULTIMAX_ROMH_START = 0xe000
|
|
14
35
|
BANK_SIZE = 0x2000
|
|
15
36
|
|
|
16
|
-
HARDWARE_TYPES = {
|
|
37
|
+
HARDWARE_TYPES = {
|
|
38
|
+
0 => :Standard, 1 => :ActionReplay, 2 => :KCSPower, 3 => :FinalCartridge3,
|
|
39
|
+
4 => :SimonsBasic, 5 => :Ocean, 7 => :FunPlay, 8 => :SuperGames,
|
|
40
|
+
9 => :AtomicPower, 10 => :EpyxFastload, 11 => :Westermann,
|
|
41
|
+
12 => :RexUtility, 15 => :GameSystem, 17 => :Dinamic, 18 => :Zaxxon,
|
|
42
|
+
19 => :MagicDesk, 21 => :Comal80, 32 => :EasyFlash, 36 => :RetroReplay,
|
|
43
|
+
51 => :Mach5, 53 => :Pagefox, 57 => :RGCD, 60 => :GMod2
|
|
44
|
+
}.freeze
|
|
45
|
+
|
|
46
|
+
# The EXROM and GAME line levels of each memory configuration. The lines
|
|
47
|
+
# are active low.
|
|
48
|
+
MODES = {
|
|
49
|
+
rom8k: [0, 1].freeze, rom16k: [0, 0].freeze,
|
|
50
|
+
ultimax: [1, 0].freeze, off: [1, 1].freeze
|
|
51
|
+
}.freeze
|
|
17
52
|
|
|
18
53
|
attr_reader :name, :exrom, :game, :roml, :romh
|
|
19
54
|
|
|
55
|
+
# A callable returning the current cycle, for mappers with timed state.
|
|
56
|
+
attr_writer :clock
|
|
57
|
+
|
|
20
58
|
class << self
|
|
21
|
-
|
|
22
|
-
|
|
59
|
+
# Options set the cartridge's jumpers, for the mappers that have them.
|
|
60
|
+
def from_file(path, **)
|
|
61
|
+
from_crt(Storage::CRTFile.new(path), **)
|
|
23
62
|
end
|
|
24
63
|
|
|
25
|
-
def from_crt(crt)
|
|
64
|
+
def from_crt(crt, **)
|
|
26
65
|
type = HARDWARE_TYPES.fetch(crt.hardware_type) do
|
|
27
66
|
raise UnsupportedTypeError,
|
|
28
67
|
"Unsupported cartridge hardware type #{crt.hardware_type}"
|
|
29
68
|
end
|
|
30
|
-
const_get(type).new(crt)
|
|
69
|
+
const_get(type).new(crt, **)
|
|
31
70
|
end
|
|
32
71
|
end
|
|
33
72
|
|
|
@@ -37,6 +76,10 @@ module Badline
|
|
|
37
76
|
@game = crt.game
|
|
38
77
|
@roml = @romh = nil
|
|
39
78
|
@on_change = nil
|
|
79
|
+
@on_nmi_change = nil
|
|
80
|
+
@nmi = false
|
|
81
|
+
@open_bus = nil
|
|
82
|
+
@clock = nil
|
|
40
83
|
install_chips(crt.chips)
|
|
41
84
|
end
|
|
42
85
|
|
|
@@ -44,32 +87,105 @@ module Badline
|
|
|
44
87
|
@on_change = block
|
|
45
88
|
end
|
|
46
89
|
|
|
90
|
+
# Called with the level of the cartridge's pull on the NMI line when it
|
|
91
|
+
# changes. The line is wired-OR with CIA 2's.
|
|
92
|
+
def on_nmi_change(&block)
|
|
93
|
+
@on_nmi_change = block
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def nmi?
|
|
97
|
+
@nmi
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# The address bus hands over the C64's RAM, for mappers that write
|
|
101
|
+
# through to it, and the open bus, for I/O reads the cartridge doesn't
|
|
102
|
+
# drive.
|
|
103
|
+
def connect(ram:, open_bus:)
|
|
104
|
+
@ram = ram
|
|
105
|
+
@open_bus = open_bus
|
|
106
|
+
end
|
|
107
|
+
|
|
47
108
|
def ultimax?
|
|
48
109
|
@game.zero? && @exrom == 1
|
|
49
110
|
end
|
|
50
111
|
|
|
51
|
-
#
|
|
52
|
-
|
|
53
|
-
|
|
112
|
+
# Whether the first half of the cycle, where the VIC does most of its
|
|
113
|
+
# fetching, sees Ultimax mode. Some cartridges drive GAME and EXROM
|
|
114
|
+
# differently in the two halves; #ultimax? is the second, the CPU's.
|
|
115
|
+
def phi1_ultimax?
|
|
116
|
+
ultimax?
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# The I/O pages ($de, $df) whose reads the mapper drives. Reads of the
|
|
120
|
+
# others are open bus.
|
|
121
|
+
def readable_io_pages
|
|
122
|
+
[]
|
|
54
123
|
end
|
|
55
124
|
|
|
56
125
|
def poke(_addr, _value); end
|
|
57
126
|
|
|
127
|
+
# A window taking writes to $E000-$FFFF while reads there see the C64's
|
|
128
|
+
# memory, for a cartridge that asserts Ultimax on write cycles only.
|
|
129
|
+
def romh_writes; end
|
|
130
|
+
|
|
131
|
+
# A window at $A000-$BFFF in Ultimax mode, where the C64 maps nothing.
|
|
132
|
+
def ultimax_a000; end
|
|
133
|
+
|
|
134
|
+
# The RES line on the expansion port.
|
|
135
|
+
def reset; end
|
|
136
|
+
|
|
137
|
+
# The freeze button, which cartridges without one ignore.
|
|
138
|
+
def press_button; end
|
|
139
|
+
|
|
140
|
+
def release_button; end
|
|
141
|
+
|
|
58
142
|
private
|
|
59
143
|
|
|
144
|
+
def nmi=(level)
|
|
145
|
+
return if @nmi == level
|
|
146
|
+
|
|
147
|
+
@nmi = level
|
|
148
|
+
@on_nmi_change&.call(level)
|
|
149
|
+
end
|
|
150
|
+
|
|
60
151
|
def changed!
|
|
61
152
|
@on_change&.call
|
|
62
153
|
end
|
|
63
154
|
|
|
155
|
+
def mode=(mode)
|
|
156
|
+
@exrom, @game = MODES.fetch(mode)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def open_bus(addr)
|
|
160
|
+
@open_bus ? @open_bus.peek(addr) : 0xff
|
|
161
|
+
end
|
|
162
|
+
|
|
64
163
|
def install_chips(_chips)
|
|
65
164
|
raise NotImplementedError
|
|
66
165
|
end
|
|
67
166
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
167
|
+
def rom_bank(data)
|
|
168
|
+
Bank.new(data)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def bank(banks, number)
|
|
172
|
+
banks[number] || EMPTY_BANK
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Sorts the CHIP packets into ROML and ROMH banks by load address. A 16K
|
|
176
|
+
# chip at $8000 spans both.
|
|
177
|
+
def banks_from(chips)
|
|
178
|
+
roml = []
|
|
179
|
+
romh = []
|
|
180
|
+
chips.each do |chip|
|
|
181
|
+
if chip.address >= ROMH_START
|
|
182
|
+
romh[chip.bank] = rom_bank(chip.data)
|
|
183
|
+
else
|
|
184
|
+
roml[chip.bank] = rom_bank(chip.data[0, BANK_SIZE])
|
|
185
|
+
romh[chip.bank] = rom_bank(chip.data[BANK_SIZE, BANK_SIZE]) if chip.data.length > BANK_SIZE
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
[roml, romh]
|
|
73
189
|
end
|
|
74
190
|
end
|
|
75
191
|
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class CIA
|
|
5
|
+
# The interrupt control register: the source flags, their mask and the
|
|
6
|
+
# IR bit that drives the interrupt line, with the 6526's acknowledge
|
|
7
|
+
# timing.
|
|
8
|
+
class InterruptRegister
|
|
9
|
+
attr_reader :status, :mask, :quiet
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
@mask = Status.new([:timer_a, :timer_b, :alarm, :serial, :flag, 0, 0, 0])
|
|
13
|
+
@status = Status.new([:timer_a, :timer_b, :alarm, :serial, :flag, 0, 0, :interrupt])
|
|
14
|
+
@pending = 0
|
|
15
|
+
@read = @read_last_cycle = @read_two_cycles_ago = nil
|
|
16
|
+
@timer_b_bug = false
|
|
17
|
+
@quiet = true
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# The CIA skips this while quiet: no read is recent enough to matter
|
|
21
|
+
# and no assert is due.
|
|
22
|
+
def cycle!
|
|
23
|
+
@read_two_cycles_ago = @read_last_cycle
|
|
24
|
+
@read_last_cycle = @read
|
|
25
|
+
@read = nil
|
|
26
|
+
if @pending.positive?
|
|
27
|
+
@pending -= 1
|
|
28
|
+
status.interrupt = true if @pending.zero?
|
|
29
|
+
end
|
|
30
|
+
@quiet = @pending.zero? && @read_last_cycle.nil? && @read_two_cycles_ago.nil?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def interrupted? = status.value >= 0x80
|
|
34
|
+
|
|
35
|
+
# Schedules IR to rise after delay cycles, keeping an earlier one.
|
|
36
|
+
def assert!(delay = 1)
|
|
37
|
+
@pending = @pending.positive? ? [@pending, delay].min : delay
|
|
38
|
+
@quiet = false
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Latch a source, pulling the interrupt line if it is armed.
|
|
42
|
+
def flag(source)
|
|
43
|
+
status.public_send(:"#{source}=", true)
|
|
44
|
+
assert! if mask.public_send(:"#{source}?")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# The 6526 timer B bug: an underflow on the cycle after a read still
|
|
48
|
+
# raises the flag, but the next read drops it unseen.
|
|
49
|
+
def timer_b_underflow!
|
|
50
|
+
@timer_b_bug = !@read_last_cycle.nil?
|
|
51
|
+
flag(:timer_b)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# A read releases the interrupt line at once, and cancels an assert
|
|
55
|
+
# due on the next cycle, but on the 6526 its IR acknowledge lands a
|
|
56
|
+
# cycle late: the next cycle still reads IR set if it was set, or
|
|
57
|
+
# about to be.
|
|
58
|
+
def read
|
|
59
|
+
status.timer_b = false if @timer_b_bug
|
|
60
|
+
@timer_b_bug = false
|
|
61
|
+
value = status.value
|
|
62
|
+
@read = @pending == 1 ? value | 0x80 : value
|
|
63
|
+
@quiet = false
|
|
64
|
+
value |= 0x80 if @read_last_cycle&.anybits?(0x80)
|
|
65
|
+
status.value = 0x0
|
|
66
|
+
@pending = 0
|
|
67
|
+
value
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Bit 7 picks between setting and clearing the mask bits given.
|
|
71
|
+
def write(value)
|
|
72
|
+
if value.nobits?(0x80)
|
|
73
|
+
mask.value &= ~(value & 0x1f)
|
|
74
|
+
else
|
|
75
|
+
mask.value |= (value & 0x1f)
|
|
76
|
+
end
|
|
77
|
+
if mask.value.anybits?(status.value & 0x1f)
|
|
78
|
+
assert!(2) unless interrupted?
|
|
79
|
+
elsif @pending == 1 && @read_two_cycles_ago
|
|
80
|
+
# On the 6526, masking the pending source cancels the assert due
|
|
81
|
+
# on the next cycle only while a read two cycles back is
|
|
82
|
+
# acknowledging.
|
|
83
|
+
@pending = 0
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class CIA
|
|
5
|
+
# The serial shift register and the CNT line that clocks it.
|
|
6
|
+
#
|
|
7
|
+
# With CRA bit 6 clear the port is an input: SP is sampled on every
|
|
8
|
+
# rising CNT edge, and the byte reaches the data register after eight of
|
|
9
|
+
# them. With the bit set the CIA drives CNT itself, toggling it on every
|
|
10
|
+
# timer A underflow, so a byte takes sixteen underflows to go out. CNT falls
|
|
11
|
+
# as each bit is put on SP, and rises again to clock it in.
|
|
12
|
+
#
|
|
13
|
+
# The register counts itself empty as soon as the eighth bit is on SP,
|
|
14
|
+
# fifteen underflows in: the serial flag follows a few cycles later
|
|
15
|
+
# rather than waiting for the sixteenth underflow, and a byte written
|
|
16
|
+
# from that point on goes out on the next one.
|
|
17
|
+
class Serial
|
|
18
|
+
BITS = 8
|
|
19
|
+
|
|
20
|
+
# A byte written to the data register reaches the shift register one
|
|
21
|
+
# cycle later. The empty countdown starts on the underflow that puts
|
|
22
|
+
# the eighth bit on SP and is decremented on that same cycle, so the
|
|
23
|
+
# flag lands four cycles after it.
|
|
24
|
+
LOAD_DELAY = 1
|
|
25
|
+
EMPTY_DELAY = 5
|
|
26
|
+
BUSY_DELAY = 5
|
|
27
|
+
|
|
28
|
+
# The logic watching the shift register runs a few cycles behind it,
|
|
29
|
+
# so the level saying a bit is still on its way out is scheduled
|
|
30
|
+
# through a delay line: a bit set at position 7 - n takes effect n
|
|
31
|
+
# cycles later. Putting a bit on SP raises the level after one cycle
|
|
32
|
+
# and drops it again after two before raising it for good after
|
|
33
|
+
# three; the eighth bit skips straight to the last of those. CNT
|
|
34
|
+
# rising over a bit lowers the level four cycles later.
|
|
35
|
+
IN_FLIGHT = 0x50
|
|
36
|
+
LAST_IN_FLIGHT = 0x10
|
|
37
|
+
BOUNCE = 0x20
|
|
38
|
+
SETTLED = 0x08
|
|
39
|
+
|
|
40
|
+
attr_reader :data, :sp_out, :cnt, :cnt_in, :idle
|
|
41
|
+
attr_accessor :sp_in
|
|
42
|
+
|
|
43
|
+
def initialize(control)
|
|
44
|
+
@control = control
|
|
45
|
+
@data = 0x0
|
|
46
|
+
@shift = 0x0
|
|
47
|
+
@steps = 0
|
|
48
|
+
@empty_in = nil
|
|
49
|
+
@busy_in = nil
|
|
50
|
+
@busy = false
|
|
51
|
+
@abandoned = false
|
|
52
|
+
@pending = nil
|
|
53
|
+
@underflow_high = false
|
|
54
|
+
@in_flight = false
|
|
55
|
+
@flight_up = 0x0
|
|
56
|
+
@flight_down = 0x0
|
|
57
|
+
@idle = true
|
|
58
|
+
# Nothing drives the user port, so both lines float high.
|
|
59
|
+
@cnt = true
|
|
60
|
+
@cnt_in = true
|
|
61
|
+
@sp_in = true
|
|
62
|
+
@sp_out = true
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def output?
|
|
66
|
+
@control.serial_mode?
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def cnt_in=(level)
|
|
70
|
+
@cnt_in = level
|
|
71
|
+
@cnt = level unless output?
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def write(value)
|
|
75
|
+
@data = value
|
|
76
|
+
@pending = LOAD_DELAY
|
|
77
|
+
@idle = false
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Clocked every cycle with timer A's underflow line. Yields once the
|
|
81
|
+
# shift register reports itself empty, which it does without waiting
|
|
82
|
+
# for the underflow that raises CNT over the eighth bit.
|
|
83
|
+
def cycle!(underflowed)
|
|
84
|
+
return if @idle && !underflowed
|
|
85
|
+
|
|
86
|
+
shift(underflowed)
|
|
87
|
+
@pending -= 1 if @pending&.positive?
|
|
88
|
+
drain_flight
|
|
89
|
+
count_busy
|
|
90
|
+
@idle = !underflowed && idle?
|
|
91
|
+
return if @empty_in.nil?
|
|
92
|
+
|
|
93
|
+
@empty_in -= 1
|
|
94
|
+
return unless @empty_in.zero?
|
|
95
|
+
|
|
96
|
+
@empty_in = nil
|
|
97
|
+
@busy = false
|
|
98
|
+
yield
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Clocked by rising CNT edges while receiving. Yields once a whole
|
|
102
|
+
# byte has arrived.
|
|
103
|
+
def rising_edge!
|
|
104
|
+
return if output?
|
|
105
|
+
|
|
106
|
+
@shift = ((@shift << 1) | (@sp_in ? 1 : 0)) & 0xff
|
|
107
|
+
@steps += 1
|
|
108
|
+
return unless @steps == BITS
|
|
109
|
+
|
|
110
|
+
@steps = 0
|
|
111
|
+
@data = @shift
|
|
112
|
+
yield if block_given?
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# A mode change drops the byte in flight and hands CNT back to
|
|
116
|
+
# whichever side drives it now, and yields: that byte counts as gone.
|
|
117
|
+
# Handing the port over as an input tears a transmission down, so a
|
|
118
|
+
# busy register reports it; the same change latches how far the bit
|
|
119
|
+
# in flight had got, and taking the port back reports that.
|
|
120
|
+
def reset!
|
|
121
|
+
abandoned = output? ? @abandoned : @busy
|
|
122
|
+
@abandoned = output? ? false : @in_flight
|
|
123
|
+
@in_flight = false
|
|
124
|
+
@steps = 0
|
|
125
|
+
@empty_in = nil
|
|
126
|
+
@busy_in = nil
|
|
127
|
+
@busy = false
|
|
128
|
+
@pending = nil
|
|
129
|
+
@cnt = output? || @cnt_in
|
|
130
|
+
yield if abandoned
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
private
|
|
134
|
+
|
|
135
|
+
# Without an underflow, a register with nothing counting down and
|
|
136
|
+
# nothing in the delay lines has nothing to do on a cycle.
|
|
137
|
+
def idle?
|
|
138
|
+
!@pending&.positive? && (@flight_up | @flight_down).zero? &&
|
|
139
|
+
@busy_in.nil? && @empty_in.nil?
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# The shift register picks up a waiting byte whenever the underflow
|
|
143
|
+
# line is asserted, but every half-step after that needs a fresh edge
|
|
144
|
+
# on it. A zero latch holds the line down for good rather than
|
|
145
|
+
# pulsing it, so such a byte stops after its first bit and never
|
|
146
|
+
# reaches the data register.
|
|
147
|
+
def shift(underflowed)
|
|
148
|
+
edge = underflowed && !@underflow_high
|
|
149
|
+
@underflow_high = underflowed
|
|
150
|
+
clock(edge) if underflowed && output?
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def clock(edge)
|
|
154
|
+
start if @steps <= 1
|
|
155
|
+
if @steps.zero?
|
|
156
|
+
@flight_down |= SETTLED
|
|
157
|
+
elsif edge || @steps == BITS * 2
|
|
158
|
+
half_step
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def half_step
|
|
163
|
+
@steps -= 1
|
|
164
|
+
@cnt = @steps.even?
|
|
165
|
+
if @cnt
|
|
166
|
+
@flight_down |= SETTLED
|
|
167
|
+
else
|
|
168
|
+
shift_out
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# The register reads as busy from a few cycles after the first bit
|
|
173
|
+
# reaches SP until it reports itself empty over the eighth.
|
|
174
|
+
def mark_busy
|
|
175
|
+
case @steps
|
|
176
|
+
when (BITS * 2) - 1 then @busy_in = BUSY_DELAY
|
|
177
|
+
when 1
|
|
178
|
+
@empty_in = EMPTY_DELAY
|
|
179
|
+
@busy_in = nil
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def count_busy
|
|
184
|
+
return if @busy_in.nil?
|
|
185
|
+
|
|
186
|
+
@busy_in -= 1
|
|
187
|
+
return unless @busy_in.zero?
|
|
188
|
+
|
|
189
|
+
@busy_in = nil
|
|
190
|
+
@busy = true
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def drain_flight
|
|
194
|
+
@in_flight = false if @flight_down.anybits?(0x80)
|
|
195
|
+
@flight_down = (@flight_down << 1) & 0xff
|
|
196
|
+
@in_flight = true if @flight_up.anybits?(0x80)
|
|
197
|
+
@flight_up = (@flight_up << 1) & 0xff
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# A byte only moves out of the data register once the one before it
|
|
201
|
+
# has put its last bit on SP.
|
|
202
|
+
def start
|
|
203
|
+
return unless @pending&.zero?
|
|
204
|
+
|
|
205
|
+
@pending = nil
|
|
206
|
+
@shift = @data
|
|
207
|
+
@steps = BITS * 2
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def shift_out
|
|
211
|
+
@sp_out = @shift.anybits?(0x80)
|
|
212
|
+
@shift = (@shift << 1) & 0xff
|
|
213
|
+
mark_busy
|
|
214
|
+
@flight_up |= @steps == 1 ? LAST_IN_FLIGHT : IN_FLIGHT
|
|
215
|
+
@flight_down |= BOUNCE
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
data/lib/badline/cia/timer.rb
CHANGED
|
@@ -8,33 +8,38 @@ module Badline
|
|
|
8
8
|
|
|
9
9
|
def initialize(control)
|
|
10
10
|
@control = control
|
|
11
|
-
@counter = @latch =
|
|
11
|
+
@counter = @latch = 0xffff
|
|
12
12
|
@pipe = 0
|
|
13
13
|
@load_delay = 0
|
|
14
14
|
@reload = false
|
|
15
15
|
@underflowed = false
|
|
16
16
|
@oneshot_linger = 0
|
|
17
17
|
@toggle = true
|
|
18
|
+
settle
|
|
18
19
|
end
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
# The level this timer drives on its port B pin: a square wave that
|
|
22
|
+
# flips on each underflow, or a single high tick when one happens.
|
|
23
|
+
def output?
|
|
24
|
+
control.out_mode? ? @toggle : @underflowed
|
|
22
25
|
end
|
|
23
26
|
|
|
24
|
-
def cycle!(feed
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
def cycle!(feed)
|
|
28
|
+
feed &&= @control.value & 0x01 != 0
|
|
29
|
+
if @settled
|
|
30
|
+
return @counter -= 1 if feed && @counter > 1
|
|
31
|
+
elsif @empty
|
|
32
|
+
enter if feed
|
|
29
33
|
return
|
|
30
34
|
end
|
|
31
|
-
run_tick(feed
|
|
35
|
+
run_tick(feed)
|
|
32
36
|
end
|
|
33
37
|
|
|
34
|
-
def run_tick(feed
|
|
38
|
+
def run_tick(feed)
|
|
35
39
|
@underflowed = false
|
|
36
40
|
@oneshot_linger -= 1 if @oneshot_linger.positive?
|
|
37
|
-
tick(feed
|
|
41
|
+
tick(feed)
|
|
42
|
+
settle
|
|
38
43
|
end
|
|
39
44
|
|
|
40
45
|
def write_control(value)
|
|
@@ -42,6 +47,7 @@ module Badline
|
|
|
42
47
|
@oneshot_linger = 2 if control.run_mode? && value.nobits?(0x08)
|
|
43
48
|
control.value = value & ~0x10
|
|
44
49
|
@load_delay = 3 if value.anybits?(0x10)
|
|
50
|
+
settle
|
|
45
51
|
end
|
|
46
52
|
|
|
47
53
|
def write_latch_low(value)
|
|
@@ -56,23 +62,33 @@ module Badline
|
|
|
56
62
|
private
|
|
57
63
|
|
|
58
64
|
def started?
|
|
59
|
-
control.value
|
|
65
|
+
@control.value & 0x01 != 0
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def enter
|
|
69
|
+
@pipe = 0b10
|
|
70
|
+
@empty = false
|
|
60
71
|
end
|
|
61
72
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
73
|
+
# With no load, reload or one-shot linger pending, a full pipe only
|
|
74
|
+
# counts and an empty one only waits to be fed.
|
|
75
|
+
def settle
|
|
76
|
+
quiet = (@load_delay | @oneshot_linger).zero? && !@reload
|
|
77
|
+
@settled = quiet && @pipe == 0b11
|
|
78
|
+
@empty = quiet && @pipe.zero?
|
|
65
79
|
end
|
|
66
80
|
|
|
67
|
-
def tick(feed
|
|
68
|
-
counting = @pipe.anybits?(0b01)
|
|
81
|
+
def tick(feed)
|
|
82
|
+
counting = @pipe.anybits?(0b01)
|
|
69
83
|
@pipe = (@pipe >> 1) | (feed ? 0b10 : 0)
|
|
84
|
+
loading = @reload || @load_delay == 1
|
|
85
|
+
@reload = false
|
|
70
86
|
|
|
71
|
-
if
|
|
87
|
+
if loading
|
|
88
|
+
reload
|
|
89
|
+
elsif premature_underflow?
|
|
72
90
|
underflow
|
|
73
|
-
elsif
|
|
74
|
-
return
|
|
75
|
-
elsif counting && @load_delay != 1
|
|
91
|
+
elsif counting
|
|
76
92
|
count
|
|
77
93
|
end
|
|
78
94
|
|
|
@@ -87,20 +103,15 @@ module Badline
|
|
|
87
103
|
end
|
|
88
104
|
|
|
89
105
|
# the final pipeline stage, before any pending load lands
|
|
90
|
-
def premature_underflow?
|
|
91
|
-
@counter.zero? &&
|
|
92
|
-
started?
|
|
106
|
+
def premature_underflow?
|
|
107
|
+
@counter.zero? && @pipe.anybits?(0b01) && started?
|
|
93
108
|
end
|
|
94
109
|
|
|
95
|
-
#
|
|
96
|
-
def
|
|
97
|
-
return false unless @reload
|
|
98
|
-
|
|
99
|
-
@reload = false
|
|
110
|
+
# A load consumes its tick, so the counter never decrements on it
|
|
111
|
+
def reload
|
|
100
112
|
@counter = @latch
|
|
101
113
|
# A zero latch underflows again on the reload tick while running
|
|
102
|
-
underflow if @counter.zero? && started?
|
|
103
|
-
true
|
|
114
|
+
underflow if @counter.zero? && started? && @pipe.anybits?(0b01)
|
|
104
115
|
end
|
|
105
116
|
|
|
106
117
|
def count
|
|
@@ -111,6 +122,7 @@ module Badline
|
|
|
111
122
|
def underflow
|
|
112
123
|
@underflowed = true
|
|
113
124
|
@reload = true
|
|
125
|
+
@counter = @latch
|
|
114
126
|
@toggle = !@toggle
|
|
115
127
|
return unless control.run_mode? || @oneshot_linger.positive?
|
|
116
128
|
|