badline 0.9.0 → 0.10.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 +29 -0
- data/CLAUDE.md +6 -4
- data/README.md +39 -4
- data/doc/pinned-behaviour.md +146 -0
- data/doc/snapshots.md +13 -0
- data/lib/badline/address_bus/pla.rb +130 -0
- data/lib/badline/address_bus.rb +9 -88
- data/lib/badline/chrout_trap.rb +4 -3
- data/lib/badline/computer/attachments.rb +47 -7
- data/lib/badline/computer/saved_state.rb +1 -0
- data/lib/badline/computer.rb +44 -29
- data/lib/badline/control_ports.rb +10 -3
- data/lib/badline/core.rb +3 -0
- data/lib/badline/datasette.rb +39 -1
- data/lib/badline/drive1541/serial_port.rb +5 -8
- data/lib/badline/drive1541/sleep.rb +5 -6
- data/lib/badline/drive1541.rb +3 -3
- data/lib/badline/frontend/app.rb +62 -64
- data/lib/badline/frontend/boot.rb +33 -8
- data/lib/badline/frontend/controls.rb +12 -6
- data/lib/badline/frontend/drive_led.rb +9 -10
- data/lib/badline/frontend/frame_rate.rb +4 -4
- data/lib/badline/frontend/machine_window.rb +87 -0
- data/lib/badline/frontend/menu_media.rb +49 -24
- data/lib/badline/frontend/menu_pages.rb +23 -40
- data/lib/badline/frontend/menu_ports.rb +69 -0
- data/lib/badline/frontend/pacer.rb +6 -5
- data/lib/badline/frontend/pause_menu.rb +30 -33
- data/lib/badline/frontend/player_screen.rb +1 -1
- data/lib/badline/frontend/player_window.rb +1 -1
- data/lib/badline/frontend/screen.rb +39 -37
- data/lib/badline/frontend/sid_view.rb +1 -1
- data/lib/badline/frontend/sound.rb +11 -9
- data/lib/badline/frontend/timeline.rb +36 -5
- data/lib/badline/frontend.rb +2 -0
- data/lib/badline/iec_bus.rb +16 -28
- data/lib/badline/kernal_trap/drive.rb +3 -0
- data/lib/badline/kernal_trap/file.rb +1 -1
- data/lib/badline/kernal_trap/layout.rb +112 -0
- data/lib/badline/kernal_trap/load.rb +16 -27
- data/lib/badline/kernal_trap/routine.rb +11 -27
- data/lib/badline/kernal_trap/save.rb +13 -23
- data/lib/badline/kernal_trap/serial.rb +21 -19
- data/lib/badline/kernal_trap.rb +1 -0
- data/lib/badline/keyboard.rb +17 -14
- data/lib/badline/machine.rb +60 -0
- data/lib/badline/media/vic20_basic.rb +54 -0
- data/lib/badline/media/vic20_cartridge.rb +74 -0
- data/lib/badline/media/vic20_media.rb +132 -0
- data/lib/badline/media.rb +32 -18
- data/lib/badline/options/event.rb +5 -3
- data/lib/badline/options/help.rb +11 -3
- data/lib/badline/options/table.rb +11 -1
- data/lib/badline/options/validation.rb +55 -4
- data/lib/badline/options.rb +33 -33
- data/lib/badline/roms/README +27 -0
- data/lib/badline/roms/vic20/basic.rom +0 -0
- data/lib/badline/roms/vic20/character.rom +0 -0
- data/lib/badline/roms/vic20/kernal-ntsc.rom +0 -0
- data/lib/badline/roms/vic20/kernal-pal.rom +0 -0
- data/lib/badline/snapshot/container.rb +2 -0
- data/lib/badline/snapshot/image.rb +15 -0
- data/lib/badline/snapshot/state.rb +4 -3
- data/lib/badline/snapshot.rb +10 -0
- data/lib/badline/storage/crt_file.rb +16 -5
- data/lib/badline/storage/disk_image.rb +3 -0
- data/lib/badline/storage/host_directory.rb +3 -0
- data/lib/badline/storage/t64.rb +3 -0
- data/lib/badline/storage/tap.rb +38 -4
- data/lib/badline/storage/unavailable.rb +2 -0
- data/lib/badline/timing.rb +12 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline/via/shift_register.rb +43 -16
- data/lib/badline/vic/bank.rb +53 -16
- data/lib/badline/vic/registers.rb +20 -4
- data/lib/badline/vic.rb +34 -15
- data/lib/badline/vic20/attachments.rb +103 -0
- data/lib/badline/vic20/bus.rb +308 -0
- data/lib/badline/vic20/cpu.rb +10 -0
- data/lib/badline/vic20/keyboard_via_ports.rb +53 -0
- data/lib/badline/vic20/port_wiring.rb +50 -0
- data/lib/badline/vic20/saved_state.rb +152 -0
- data/lib/badline/vic20/sound/output.rb +83 -0
- data/lib/badline/vic20/sound.rb +233 -0
- data/lib/badline/vic20/user_via_ports.rb +47 -0
- data/lib/badline/vic20/vic/painter.rb +239 -0
- data/lib/badline/vic20/vic/saved_state.rb +55 -0
- data/lib/badline/vic20/vic/text_window.rb +71 -0
- data/lib/badline/vic20/vic.rb +267 -0
- data/lib/badline/vic20.rb +220 -0
- metadata +28 -2
- data/lib/badline/address_bus/ultimax_pages.rb +0 -38
|
@@ -6,13 +6,14 @@ module Badline
|
|
|
6
6
|
class FormatError < StandardError; end
|
|
7
7
|
|
|
8
8
|
SIGNATURE = "C64 CARTRIDGE ".b
|
|
9
|
+
VIC20_SIGNATURE = "VIC20 CARTRIDGE ".b
|
|
9
10
|
CHIP_SIGNATURE = "CHIP".b
|
|
10
11
|
CHIP_HEADER_SIZE = 0x10
|
|
11
12
|
RAM_CHIP = 1
|
|
12
13
|
|
|
13
14
|
Chip = Data.define(:chip_type, :bank, :address, :data)
|
|
14
15
|
|
|
15
|
-
attr_reader :hardware_type, :subtype, :exrom, :game, :name, :chips
|
|
16
|
+
attr_reader :machine, :hardware_type, :subtype, :exrom, :game, :name, :chips
|
|
16
17
|
|
|
17
18
|
# What .write needs of an image: the fields a parsed CRTFile reads.
|
|
18
19
|
Image = Data.define(:hardware_type, :subtype, :exrom, :game, :name, :chips)
|
|
@@ -33,16 +34,19 @@ module Badline
|
|
|
33
34
|
header + packets.join
|
|
34
35
|
end
|
|
35
36
|
|
|
36
|
-
# `bytes` stands in for the file's contents.
|
|
37
|
-
|
|
37
|
+
# `bytes` stands in for the file's contents. `machine` is the one the
|
|
38
|
+
# image has to be for, :c64 or :vic20, or nil for either.
|
|
39
|
+
def initialize(path = nil, bytes: nil, machine: :c64)
|
|
38
40
|
parse(bytes || File.binread(path))
|
|
41
|
+
return if machine.nil? || @machine == machine
|
|
42
|
+
|
|
43
|
+
raise FormatError, "Cartridge image is for the #{@machine}, not the #{machine}"
|
|
39
44
|
end
|
|
40
45
|
|
|
41
46
|
private
|
|
42
47
|
|
|
43
48
|
def parse(bytes)
|
|
44
|
-
|
|
45
|
-
|
|
49
|
+
@machine = signed_machine(bytes)
|
|
46
50
|
@hardware_type = bytes[0x16, 2].unpack1("n")
|
|
47
51
|
@exrom = bytes.getbyte(0x18)
|
|
48
52
|
@game = bytes.getbyte(0x19)
|
|
@@ -51,6 +55,13 @@ module Badline
|
|
|
51
55
|
@chips = parse_chips(bytes, bytes[0x10, 4].unpack1("N"))
|
|
52
56
|
end
|
|
53
57
|
|
|
58
|
+
def signed_machine(bytes)
|
|
59
|
+
return :c64 if bytes.start_with?(SIGNATURE)
|
|
60
|
+
return :vic20 if bytes.start_with?(VIC20_SIGNATURE)
|
|
61
|
+
|
|
62
|
+
raise FormatError, "Missing CRT signature"
|
|
63
|
+
end
|
|
64
|
+
|
|
54
65
|
# Trailing bytes too short to hold a CHIP header are ignored.
|
|
55
66
|
def parse_chips(bytes, offset)
|
|
56
67
|
chips = []
|
data/lib/badline/storage/t64.rb
CHANGED
|
@@ -20,6 +20,9 @@ module Badline
|
|
|
20
20
|
raise FormatError, "Missing T64 signature" unless @bytes[0, 3] == SIGNATURE
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
# The host file's path, as it was opened.
|
|
24
|
+
attr_reader :path
|
|
25
|
+
|
|
23
26
|
# What Storage.reopen needs to open the archive again: its path and
|
|
24
27
|
# its bytes.
|
|
25
28
|
def save_setup(out)
|
data/lib/badline/storage/tap.rb
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
module Badline
|
|
4
4
|
module Storage
|
|
5
5
|
# Raw datasette pulse stream. The header holds the signature, the
|
|
6
|
-
# version at $0c
|
|
7
|
-
#
|
|
8
|
-
# of 8 cycles.
|
|
6
|
+
# version at $0c, the platform at $0d (0 C64, 1 VIC-20, 2 C16) and the
|
|
7
|
+
# data size at $10; the data runs from $14, every byte the time to the
|
|
8
|
+
# next falling edge on the tape read line, in units of 8 cycles.
|
|
9
9
|
class TAP
|
|
10
10
|
class FormatError < StandardError; end
|
|
11
11
|
|
|
@@ -14,7 +14,14 @@ module Badline
|
|
|
14
14
|
OVERFLOW = 0x100 * 8
|
|
15
15
|
|
|
16
16
|
# The file it was read from, expanded, and the file's bytes.
|
|
17
|
-
attr_reader :version, :path, :bytes
|
|
17
|
+
attr_reader :version, :platform, :path, :bytes
|
|
18
|
+
|
|
19
|
+
# A blank version 1 tape for `platform` (0 C64, 1 VIC-20) at `path`,
|
|
20
|
+
# written to the host file.
|
|
21
|
+
def self.create(path, platform:)
|
|
22
|
+
File.binwrite(path, (SIGNATURE + [1, platform, 0, 0, 0, 0, 0, 0]).pack("C*"))
|
|
23
|
+
new(path)
|
|
24
|
+
end
|
|
18
25
|
|
|
19
26
|
# `bytes` stands in for the host file's contents, as a snapshot
|
|
20
27
|
# restores the tape it holds.
|
|
@@ -26,6 +33,8 @@ module Badline
|
|
|
26
33
|
@version = @bytes[0x0c].to_i
|
|
27
34
|
raise FormatError, "Unsupported TAP version #{@version}" if @version > 1
|
|
28
35
|
|
|
36
|
+
@platform = @bytes[0x0d].to_i
|
|
37
|
+
|
|
29
38
|
@end = tape_end
|
|
30
39
|
rewind
|
|
31
40
|
end
|
|
@@ -43,6 +52,31 @@ module Badline
|
|
|
43
52
|
|
|
44
53
|
def end? = @pos >= @end
|
|
45
54
|
|
|
55
|
+
# Records a pulse `cycles` long at the position, in place of the rest
|
|
56
|
+
# of the tape: a byte of units of 8 cycles, or a zero and the 24-bit
|
|
57
|
+
# cycle count for a long one (a lone zero, an overflow, on a version 0
|
|
58
|
+
# tape).
|
|
59
|
+
def record_pulse(cycles)
|
|
60
|
+
units = (cycles + 4) / 8
|
|
61
|
+
encoded = if units.between?(1, 0xff) then [units]
|
|
62
|
+
elsif version.zero? then [0]
|
|
63
|
+
else
|
|
64
|
+
count = [cycles, 0xffffff].min
|
|
65
|
+
[0, count & 0xff, (count >> 8) & 0xff, count >> 16]
|
|
66
|
+
end
|
|
67
|
+
@bytes.slice!(@pos..)
|
|
68
|
+
@bytes.concat(encoded)
|
|
69
|
+
@pos = @end = @bytes.length
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Writes the tape back to its host file, with the data size the
|
|
73
|
+
# header states.
|
|
74
|
+
def save
|
|
75
|
+
size = @bytes.length - HEADER_SIZE
|
|
76
|
+
@bytes[0x10, 4] = [size & 0xff, (size >> 8) & 0xff, (size >> 16) & 0xff, size >> 24]
|
|
77
|
+
File.binwrite(@path, @bytes.pack("C*"))
|
|
78
|
+
end
|
|
79
|
+
|
|
46
80
|
# Cycles until the next falling edge, or nil once the tape has run out.
|
|
47
81
|
def next_pulse
|
|
48
82
|
return if end?
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
# = Timing
|
|
5
|
+
#
|
|
6
|
+
# What a front end needs to run a machine's frames and show them: the CPU
|
|
7
|
+
# clock, the raster's cycles per line and lines per frame, and the
|
|
8
|
+
# [left, top, width, height] of the raster it shows, and how many of the
|
|
9
|
+
# window's square pixels wide each of its pixels shows. Each machine
|
|
10
|
+
# derives it from its region.
|
|
11
|
+
Timing = Data.define(:clock_hz, :cycles_per_line, :lines_per_frame, :crop, :pixel_width)
|
|
12
|
+
end
|
data/lib/badline/version.rb
CHANGED
|
@@ -13,14 +13,20 @@ module Badline
|
|
|
13
13
|
# modes 3 and 7 off whatever drives CB1 from outside.
|
|
14
14
|
#
|
|
15
15
|
# Under its own clock the register drives CB1, idling high. Reading or
|
|
16
|
-
# writing the data register starts a byte
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
16
|
+
# writing the data register starts a byte, but only while the register
|
|
17
|
+
# is idle and enabled: an access in mode 0, or one before the eighth bit
|
|
18
|
+
# is in, leaves the count alone. CB1 falls on the first clock tick of
|
|
19
|
+
# the byte and rises on the second, and so on, so a byte is eight pulses
|
|
20
|
+
# over sixteen ticks. The interrupt flag is set on the tick that raises
|
|
21
|
+
# CB1 over the eighth bit, and CB1 then stays high until the next
|
|
22
|
+
# access.
|
|
23
|
+
#
|
|
24
|
+
# In modes 2 and 6 a tick is a φ2 cycle, from the second cycle after
|
|
25
|
+
# the access on, which makes a byte sixteen cycles. In modes 1, 4 and 5
|
|
26
|
+
# it comes two cycles after each timer 2 low byte underflow, on the
|
|
27
|
+
# cycle after the low byte reloads, which makes a byte sixteen
|
|
28
|
+
# underflows. Mode 4 never stops and never interrupts: its counter is
|
|
29
|
+
# disabled, and the byte goes round and round.
|
|
24
30
|
#
|
|
25
31
|
# Under an external clock the register shifts on every CB1 edge, access
|
|
26
32
|
# or not. The access only arms the counter: the flag is set on the eighth
|
|
@@ -43,6 +49,10 @@ module Badline
|
|
|
43
49
|
@data = 0x00
|
|
44
50
|
# Bits still to count before the flag, zero when the counter is idle.
|
|
45
51
|
@bits = 0
|
|
52
|
+
# The clock's last two cycles, newest in bit 0: timer 2 low byte
|
|
53
|
+
# underflows in modes 1, 4 and 5, and whether the byte has started
|
|
54
|
+
# in modes 2 and 6.
|
|
55
|
+
@delay = 0
|
|
46
56
|
@clock = true
|
|
47
57
|
@cb2 = true
|
|
48
58
|
# Nothing drives CB2 from outside, so it floats high.
|
|
@@ -57,10 +67,13 @@ module Badline
|
|
|
57
67
|
@data = value & 0xff
|
|
58
68
|
end
|
|
59
69
|
|
|
60
|
-
#
|
|
61
|
-
#
|
|
70
|
+
# A read or write of the data register, which starts a byte if the
|
|
71
|
+
# register is idle and enabled.
|
|
62
72
|
def access!
|
|
73
|
+
return if @mode == DISABLED || @bits.nonzero?
|
|
74
|
+
|
|
63
75
|
@bits = 8
|
|
76
|
+
@delay = 0 if @mode == IN_PHI2 || @mode == OUT_PHI2
|
|
64
77
|
end
|
|
65
78
|
|
|
66
79
|
# Whether the register is counting bits off its own clock, which
|
|
@@ -68,20 +81,21 @@ module Badline
|
|
|
68
81
|
def clocking? = @bits.nonzero? && internal_clock?
|
|
69
82
|
|
|
70
83
|
def save_state(out)
|
|
71
|
-
out.int(@mode).int(@data).int(@bits).boolean(@clock).boolean(@cb2).boolean(@cb2_input)
|
|
84
|
+
out.int(@mode).int(@data).int(@bits).int(@delay).boolean(@clock).boolean(@cb2).boolean(@cb2_input)
|
|
72
85
|
end
|
|
73
86
|
|
|
74
87
|
def load_state(input)
|
|
75
88
|
@mode = input.int
|
|
76
89
|
@data = input.int
|
|
77
90
|
@bits = input.int
|
|
91
|
+
@delay = input.schema > 4 ? input.int : 0
|
|
78
92
|
@clock = input.boolean?
|
|
79
93
|
@cb2 = input.boolean?
|
|
80
94
|
@cb2_input = input.boolean?
|
|
81
95
|
end
|
|
82
96
|
|
|
83
97
|
# Everything the register holds, for comparing it at two points.
|
|
84
|
-
def state = [@mode, @data, @bits, @clock, @cb2, @cb2_input]
|
|
98
|
+
def state = [@mode, @data, @bits, @delay, @clock, @cb2, @cb2_input]
|
|
85
99
|
|
|
86
100
|
# The CB1 level the register drives, or nil when CB1 is an input.
|
|
87
101
|
def cb1_output
|
|
@@ -96,11 +110,9 @@ module Badline
|
|
|
96
110
|
# Clocked once per φ2 cycle, with whether timer 2's low byte underflowed
|
|
97
111
|
# on it. Returns true on the cycle that sets the interrupt flag.
|
|
98
112
|
def cycle!(t2_low_underflow)
|
|
99
|
-
return false if @bits.zero?
|
|
100
|
-
|
|
101
113
|
case @mode
|
|
102
|
-
when IN_PHI2, OUT_PHI2 then
|
|
103
|
-
when IN_T2, OUT_T2, OUT_FREE_RUNNING then t2_low_underflow
|
|
114
|
+
when IN_PHI2, OUT_PHI2 then @bits.nonzero? && phi2_tick
|
|
115
|
+
when IN_T2, OUT_T2, OUT_FREE_RUNNING then t2_tick(t2_low_underflow)
|
|
104
116
|
else false
|
|
105
117
|
end
|
|
106
118
|
end
|
|
@@ -123,6 +135,21 @@ module Badline
|
|
|
123
135
|
@mode != DISABLED && @mode != IN_EXTERNAL && @mode != OUT_EXTERNAL
|
|
124
136
|
end
|
|
125
137
|
|
|
138
|
+
# A φ2 cycle of a byte: every one but the first ticks.
|
|
139
|
+
def phi2_tick
|
|
140
|
+
started = @delay.nonzero?
|
|
141
|
+
@delay = 1
|
|
142
|
+
started && tick
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Ticks on the underflow from two cycles back, while a byte is under
|
|
146
|
+
# way.
|
|
147
|
+
def t2_tick(underflow)
|
|
148
|
+
due = @delay.anybits?(2)
|
|
149
|
+
@delay = ((@delay << 1) & 2) | (underflow ? 1 : 0)
|
|
150
|
+
due && @bits.nonzero? && tick
|
|
151
|
+
end
|
|
152
|
+
|
|
126
153
|
# One half period of the register's own clock: a falling edge puts a
|
|
127
154
|
# bit out, and a rising one takes a bit in and counts it.
|
|
128
155
|
def tick
|
data/lib/badline/vic/bank.rb
CHANGED
|
@@ -5,44 +5,81 @@ module Badline
|
|
|
5
5
|
# The 16K window the VIC sees into RAM, selected by CIA2 $DD00, in the
|
|
6
6
|
# bank a +256K picks for it. Character
|
|
7
7
|
# ROM shadows $1000-$1FFF in banks 0 and 2.
|
|
8
|
+
#
|
|
9
|
+
# The machine's bus pushes in what the VIC sees through #connect,
|
|
10
|
+
# #map_character_rom and #map whenever it changes. Until then the bank
|
|
11
|
+
# has a CIA 2, RAM, colour RAM and character ROM of its own.
|
|
8
12
|
class Bank
|
|
9
13
|
include Addressable
|
|
10
14
|
|
|
11
15
|
# The CIA2 port A bank bits are inverted: %11 selects bank 0.
|
|
12
16
|
BANK_STARTS = [0xc000, 0x8000, 0x4000, 0x0000].freeze
|
|
13
17
|
|
|
14
|
-
attr_reader :
|
|
18
|
+
attr_reader :ram, :color_ram
|
|
15
19
|
|
|
16
20
|
# The bank lines as #sample_lines last saw them.
|
|
17
21
|
attr_accessor :lines
|
|
18
22
|
|
|
19
|
-
def initialize(
|
|
23
|
+
def initialize(vic)
|
|
20
24
|
addressable_at(0x0000, length: 2**14)
|
|
21
|
-
|
|
25
|
+
connect(cia2: CIA.new(start: 0xdd00), color_ram: ColorMemory.new(vic))
|
|
26
|
+
map_character_rom(ROM.load("character.rom", 0xd000))
|
|
27
|
+
@base = 0
|
|
28
|
+
@starts = BANK_STARTS
|
|
29
|
+
map(Memory.new([], length: 2**16, start: 0))
|
|
22
30
|
@lines = 0b11
|
|
23
31
|
@swapped = false
|
|
24
32
|
end
|
|
25
33
|
|
|
34
|
+
# CIA 2, whose port A picks the bank, and the colour RAM, read from
|
|
35
|
+
# the address +color_base+ gives offset 0.
|
|
36
|
+
def connect(cia2:, color_ram:, color_base: 0xd800)
|
|
37
|
+
@cia2 = cia2
|
|
38
|
+
@color_ram = color_ram
|
|
39
|
+
@color_base = color_base
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# The character ROM, read from the address +base+ gives offset 0. It
|
|
43
|
+
# shows in the banks whose low line is high, unless +shadow+ is false.
|
|
44
|
+
def map_character_rom(rom, base: 0xc000, shadow: true)
|
|
45
|
+
@character_rom = rom
|
|
46
|
+
@character_base = base
|
|
47
|
+
@shadow_mask = shadow ? 0b01 : 0b00
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# The RAM the VIC's 64K starts at +base+ in, and the cartridge's
|
|
51
|
+
# Ultimax lines in each half of the cycle with the ROMH bank they
|
|
52
|
+
# show.
|
|
53
|
+
def map(ram, base: 0, phi1_ultimax: false, ultimax: false, romh: nil)
|
|
54
|
+
@ram = ram
|
|
55
|
+
unless base == @base
|
|
56
|
+
@base = base
|
|
57
|
+
@starts = BANK_STARTS.map { |start| base + start }.freeze
|
|
58
|
+
end
|
|
59
|
+
@phi1_ultimax = phi1_ultimax
|
|
60
|
+
@ultimax = ultimax
|
|
61
|
+
@romh = romh
|
|
62
|
+
end
|
|
63
|
+
|
|
26
64
|
# On the 8565, a port A write that swaps the two bank lines, driving
|
|
27
65
|
# one high as the other goes low, shows the VIC both lines low for the
|
|
28
66
|
# cycle after it: every access in that cycle reads bank 3. A swap that
|
|
29
67
|
# the DDR makes, releasing a line to float high, shows nothing.
|
|
30
68
|
# Called once a cycle, ahead of the cycle's accesses.
|
|
31
69
|
def sample_lines
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
@swapped = swap?(@lines, lines) && cia2.port_registers[2].allbits?(0b11)
|
|
70
|
+
lines = @cia2.port_a_lines & 0b11
|
|
71
|
+
@swapped = swap?(@lines, lines) && @cia2.port_registers[2].allbits?(0b11)
|
|
35
72
|
@lines = lines
|
|
36
73
|
end
|
|
37
74
|
|
|
38
75
|
def peek(offset)
|
|
39
|
-
return ultimax_peek(offset) if @
|
|
76
|
+
return ultimax_peek(offset) if @phi1_ultimax
|
|
40
77
|
|
|
41
78
|
bits = bank_switch_register
|
|
42
|
-
if bits
|
|
43
|
-
@
|
|
79
|
+
if (bits & @shadow_mask) == 0b01 && (offset & 0xf000) == 0x1000
|
|
80
|
+
@character_rom.peek(@character_base + offset)
|
|
44
81
|
else
|
|
45
|
-
@
|
|
82
|
+
@ram.peek(@starts[bits] + offset)
|
|
46
83
|
end
|
|
47
84
|
end
|
|
48
85
|
|
|
@@ -50,11 +87,11 @@ module Badline
|
|
|
50
87
|
# memory configuration the CPU does in that half. No cartridge drives
|
|
51
88
|
# Ultimax mode in the first half alone, so otherwise it reads as #peek.
|
|
52
89
|
def peek_phi2(offset)
|
|
53
|
-
@
|
|
90
|
+
@ultimax ? ultimax_peek(offset) : peek(offset)
|
|
54
91
|
end
|
|
55
92
|
|
|
56
93
|
def peek_color(offset)
|
|
57
|
-
|
|
94
|
+
@color_ram.nibble(@color_base + offset)
|
|
58
95
|
end
|
|
59
96
|
|
|
60
97
|
def poke(_addr, _value)
|
|
@@ -63,7 +100,7 @@ module Badline
|
|
|
63
100
|
|
|
64
101
|
# True if the VIC reads the character ROM at this offset.
|
|
65
102
|
def character_rom?(offset)
|
|
66
|
-
!@
|
|
103
|
+
!@phi1_ultimax && (bank_switch_register & @shadow_mask) == 0b01 && (offset & 0xf000) == 0x1000
|
|
67
104
|
end
|
|
68
105
|
|
|
69
106
|
def start
|
|
@@ -75,11 +112,11 @@ module Badline
|
|
|
75
112
|
# In Ultimax mode the cartridge ROMH replaces the character ROM
|
|
76
113
|
# shadow, visible at $3000-$3FFF of the window.
|
|
77
114
|
def ultimax_peek(offset)
|
|
78
|
-
romh = @
|
|
115
|
+
romh = @romh
|
|
79
116
|
if romh && offset.allbits?(0x3000)
|
|
80
117
|
romh.peek(0xe000 + (offset & 0x1fff))
|
|
81
118
|
else
|
|
82
|
-
@
|
|
119
|
+
@ram.peek(@starts[bank_switch_register] + offset)
|
|
83
120
|
end
|
|
84
121
|
end
|
|
85
122
|
|
|
@@ -88,7 +125,7 @@ module Badline
|
|
|
88
125
|
def bank_switch_register
|
|
89
126
|
return 0 if @swapped
|
|
90
127
|
|
|
91
|
-
@
|
|
128
|
+
@cia2.port_a_lines & 0b11
|
|
92
129
|
end
|
|
93
130
|
end
|
|
94
131
|
end
|
|
@@ -69,7 +69,13 @@ module Badline
|
|
|
69
69
|
# $D02D: Sprite 6 color
|
|
70
70
|
# $D02E: Sprite 7 color
|
|
71
71
|
#
|
|
72
|
-
#
|
|
72
|
+
# == VIC-IIe (8566 and 8564)
|
|
73
|
+
#
|
|
74
|
+
# $D02F: Extra keyboard lines K0-K2 in bits 0-2, bits 3-7 read 1
|
|
75
|
+
# $D030: FAST (2 MHz) in bit 0 and TEST in bit 1, bits 2-7 read 1
|
|
76
|
+
#
|
|
77
|
+
# $D02F-$D03F: Not in use on the 6569 and 8565, always reads 0xff,
|
|
78
|
+
# and $D031-$D03F on the VIC-IIe
|
|
73
79
|
# $D040-$D3FF: Repeat $D0000 to $D03F every 64 bytes
|
|
74
80
|
class Registers
|
|
75
81
|
include IntegerHelper
|
|
@@ -77,10 +83,12 @@ module Badline
|
|
|
77
83
|
# The raw register bytes, for the per-column reads on the hot path.
|
|
78
84
|
attr_reader :bytes
|
|
79
85
|
|
|
80
|
-
# Every register powers on as zero, as in VICE.
|
|
81
|
-
|
|
86
|
+
# Every register powers on as zero, as in VICE. A VIC-IIe reads
|
|
87
|
+
# $D02F and $D030 back as registers.
|
|
88
|
+
def initialize(iie: false)
|
|
82
89
|
@bytes = Array.new(2**6, 0)
|
|
83
90
|
@irq_line = false
|
|
91
|
+
@iie = iie
|
|
84
92
|
end
|
|
85
93
|
|
|
86
94
|
# True while any enabled latch bit is set in $D019/$D01A. Cached and
|
|
@@ -95,7 +103,9 @@ module Badline
|
|
|
95
103
|
when 0x18 then @bytes[reg] | 0x01 # bit 0 unused, reads 1
|
|
96
104
|
when 0x1a, 0x20..0x2e then @bytes[reg] | 0xf0
|
|
97
105
|
when 0x1e, 0x1f then read_clear(reg) # collision registers clear on read
|
|
98
|
-
when 0x2f
|
|
106
|
+
when 0x2f then @iie ? @bytes[reg] | 0xf8 : 0xff
|
|
107
|
+
when 0x30 then @iie ? @bytes[reg] | 0xfc : 0xff
|
|
108
|
+
when 0x31..0x3f then 0xff
|
|
99
109
|
else @bytes[reg]
|
|
100
110
|
end
|
|
101
111
|
end
|
|
@@ -134,6 +144,12 @@ module Badline
|
|
|
134
144
|
def border = @bytes[0x20] & 0x0f
|
|
135
145
|
def background(index = 0) = @bytes[0x21 + index] & 0x0f
|
|
136
146
|
|
|
147
|
+
# The VIC-IIe's $D02F and $D030 bits. The 6569 and 8565 drive no
|
|
148
|
+
# extra keyboard lines and run at 1 MHz.
|
|
149
|
+
def extra_keyboard_lines = @iie ? @bytes[0x2f] & 0x07 : 0x07
|
|
150
|
+
def fast? = @iie && @bytes[0x30].anybits?(0x01)
|
|
151
|
+
def test? = @iie && @bytes[0x30].anybits?(0x02)
|
|
152
|
+
|
|
137
153
|
def raster_target = uint16(@bytes[0x12], (@bytes[0x11] & 0x80) >> 7)
|
|
138
154
|
|
|
139
155
|
def latch_irq!(bit)
|
data/lib/badline/vic.rb
CHANGED
|
@@ -11,10 +11,20 @@ module Badline
|
|
|
11
11
|
include Addressable
|
|
12
12
|
include IntegerHelper
|
|
13
13
|
|
|
14
|
-
# The 6569 of the breadbin C64,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
# The 6569 of the breadbin C64, the HMOS 8565 of the C64C, and the
|
|
15
|
+
# C128's VIC-IIe, the 8566 (PAL) and 8564 (NTSC). The VIC-IIe draws as
|
|
16
|
+
# the 8565 and adds the $D02F and $D030 registers.
|
|
17
|
+
MODELS = %i[mos6569 mos8565 mos8566 mos8564].freeze
|
|
18
|
+
|
|
19
|
+
# The sixteen colours #display's palette indices stand for, as RGB.
|
|
20
|
+
PALETTE = [
|
|
21
|
+
0x000000, 0xffffff, 0x924a40, 0x84c5cc,
|
|
22
|
+
0x9351b6, 0x72b14b, 0x483aaa, 0xd5df7c,
|
|
23
|
+
0x675200, 0xc33d00, 0xc18178, 0x606060,
|
|
24
|
+
0x8a8a8a, 0xb3ec91, 0x867ade, 0xb3b3b3
|
|
25
|
+
].freeze
|
|
26
|
+
|
|
27
|
+
attr_reader :display, :width, :height, :vic_bank, :column,
|
|
18
28
|
:rasterline, :dirty_lines, :model, :region
|
|
19
29
|
|
|
20
30
|
# The parts a VICE snapshot reads and sets.
|
|
@@ -76,19 +86,20 @@ module Badline
|
|
|
76
86
|
SPRITE_BA_FIRST = 54
|
|
77
87
|
SPRITE_BA_LENGTH = 5
|
|
78
88
|
|
|
79
|
-
def initialize(
|
|
89
|
+
def initialize(debug: false, model: :mos6569, region: Region::PAL)
|
|
80
90
|
raise ArgumentError, "unknown VIC-II model #{model}" unless MODELS.include?(model)
|
|
81
91
|
|
|
82
92
|
addressable_at(0xd000, length: 2**10)
|
|
83
93
|
@model = model
|
|
94
|
+
@iie = %i[mos8566 mos8564].include?(model)
|
|
95
|
+
@core = model == :mos6569 ? :mos6569 : :mos8565
|
|
84
96
|
@region = region
|
|
85
|
-
@lightpen_extra =
|
|
86
|
-
@grey_dots =
|
|
87
|
-
@delayed_fetch =
|
|
88
|
-
@dma_delay_idle =
|
|
89
|
-
@bank_swaps =
|
|
90
|
-
@
|
|
91
|
-
@vic_bank = VIC::Bank.new(@address_bus)
|
|
97
|
+
@lightpen_extra = @core == :mos8565 ? 1 : 2
|
|
98
|
+
@grey_dots = @core == :mos8565
|
|
99
|
+
@delayed_fetch = @core == :mos8565
|
|
100
|
+
@dma_delay_idle = @core == :mos6569
|
|
101
|
+
@bank_swaps = @core == :mos8565
|
|
102
|
+
@vic_bank = VIC::Bank.new(self)
|
|
92
103
|
@debug = debug
|
|
93
104
|
|
|
94
105
|
@columns_per_line = region.cycles_per_line
|
|
@@ -114,12 +125,12 @@ module Badline
|
|
|
114
125
|
# registers, the raster position and the fetch and sprite state it
|
|
115
126
|
# starts with. The display keeps its buffers, cleared to black.
|
|
116
127
|
def power_on!
|
|
117
|
-
@registers = VIC::Registers.new
|
|
128
|
+
@registers = VIC::Registers.new(iie: @iie)
|
|
118
129
|
@register_bytes = @registers.bytes
|
|
119
130
|
@display_state = VIC::DisplayState.new(@registers, @last_column)
|
|
120
|
-
@sequencer = VIC::Sequencer.new(@width, @registers, @vic_bank, model: @
|
|
131
|
+
@sequencer = VIC::Sequencer.new(@width, @registers, @vic_bank, model: @core, region: @region)
|
|
121
132
|
@sequencer.render = @render
|
|
122
|
-
@sprites = VIC::Sprites.new(@registers, @vic_bank, @width, model: @
|
|
133
|
+
@sprites = VIC::Sprites.new(@registers, @vic_bank, @width, model: @core, region: @region)
|
|
123
134
|
@display.fill(0)
|
|
124
135
|
@lines.each { |line| line.fill(0) }
|
|
125
136
|
@dirty_lines.fill(true)
|
|
@@ -222,6 +233,12 @@ module Badline
|
|
|
222
233
|
@registers.irq_line?
|
|
223
234
|
end
|
|
224
235
|
|
|
236
|
+
# The VIC-IIe's $D02F lines K0-K2 and $D030 bits, for the machine to
|
|
237
|
+
# read. The 6569 and 8565 give 7, false and false.
|
|
238
|
+
def extra_keyboard_lines = @registers.extra_keyboard_lines
|
|
239
|
+
def fast? = @registers.fast?
|
|
240
|
+
def test? = @registers.test?
|
|
241
|
+
|
|
225
242
|
def peek(addr)
|
|
226
243
|
i = offset_of(addr) % (2**6)
|
|
227
244
|
case i
|
|
@@ -363,6 +380,8 @@ module Badline
|
|
|
363
380
|
@dirty_lines.fill(false)
|
|
364
381
|
end
|
|
365
382
|
|
|
383
|
+
def palette = PALETTE
|
|
384
|
+
|
|
366
385
|
private
|
|
367
386
|
|
|
368
387
|
def load_beam(input)
|